如何使用地理编码 Web 服务确定地址的国会选区





5.00/5 (5投票s)
了解如何轻松编写控制台应用程序,使用免费的地理编码 Web 服务查找美国地址的国会选区。
引言
最近的一个 Dynamics CRM 项目要求美国地址包含其国会选区的名称和编号。我偶然发现了三个地理编码 Web 服务,它们包含我需要的最新立法信息,并且可以免费使用(在很大程度上)。本文介绍如何用 C# 编写一个控制台应用程序,以适应 JSON 响应的不同数据结构。
背景
有许多地理编码 Web 服务可用,但美国人口普查局、LatLon.io 和 Geocod.io 提供的 API 包含了我要查找的国会选区信息。虽然美国人口普查局 Web 服务对于单个地址请求是完全免费且无限使用的,但 LatLon.io 和 Geocod.io 限制了免费请求的数量。
Using the Code
本文中的所有源代码都包含在可下载的材料中。 演示项目是使用 Visual Studio 2015 SP3 构建和测试的,但是没有理由说明源代码不能在 Visual Studio 和 .NET 4.5 的早期版本中工作。 我使用 Newtonsoft 的 Json.NET 反序列化 JSON 响应,并且是 .NET System 程序集之外唯一需要的包。 从 http://www.newtonsoft.com 下载最新版本的 JSON.NET 或在 Visual Studio 中使用 NuGet 包管理器。
设计
为了适应 JSON 响应的不同数据结构,每个服务(例如 Census、Geocodio 和 LatLon)都使用了单独的类。 类属性需要反映 JSON 响应的结构才能被反序列化。
// Deserialize the Json Response (data)
Census json = JsonConvert.DeserializeObject<Census>(data);
Geocode 对象为每个地理编码 Web 服务使用自定义泛型。 实例化 Census
类看起来像这样
// Census
var census = new Geocode<Census>();
census.Service = new Census();
var geocodeCensus = census.Service.GeocodeAddress
("1600 Pennsylvania Ave NW", "Washington", "DC", "20500");
源代码
Geocode.cs
Geocode 类将 T
参数约束为类类型。
重要提示:GetResponse()
方法中的 SSL/TLS 类型设置为 SecurityProtocolType.Tls12
或 TLS 版本 1.2。 如果发现安全漏洞并且这些服务升级并禁用 TLS 1.2 协议,则会发生类似于“发送请求时发生错误”的错误消息。
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
namespace GeocodeConsole
{
internal interface IGeocode
{
HttpResponseMessage GetResponse(string url, string parameters);
}
public class Geocode<T> : IGeocode
where T : class
{
private T service;
public double Latitude { get; set; }
public double Longitude { get; set; }
public string CongressionalDistrictName { get; set; }
public Geocode()
{
}
public T Service
{
get
{
return service;
}
set
{
service = value;
}
}
public HttpResponseMessage GetResponse(string url, string parameters)
{
// SSL/TLS Type
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(url);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add
(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync(parameters).Result;
return response;
}
}
}
}
Census.cs
美国人口普查局地理编码服务可以免费使用,并且没有使用限制、密钥或注册。 话虽如此,该 API 是这三个 API 中最慢的。 该服务有相当完善的文档,但有关具体信息的查找可能很困难。 有关更多信息,请访问 http://census.gov/data/developers/data-sets/Geocoding-services.html。
重要提示:Geography
子类定义了 CongressionalDistricts
数组,并且 JsonProperty
属性已硬编码为当前国会会期的名称(例如第 115 届国会选区)。
using Newtonsoft.Json;
using System.Net.Http;
namespace GeocodeConsole
{
internal interface ICensus
{
Geocode<Census> GeocodeAddress
(string street, string city, string state, string zip);
}
/*
U.S. Census Bureau
https://tigerweb.geo.census.gov
*/
public class Census : ICensus
{
private string apiUrl;
private string apiParameters;
public Result result { get; set; }
public Census()
{
apiUrl = @"https://geocoding.geo.census.gov/geocoder/geographies/address";
// Parameters: Benchmark, Vintage, Format, Layers
// 56 - 115th Congressional Districts
// 86 - Counties
// For more information on layers,
// https://tigerweb.geo.census.gov/arcgis/rest/services/TIGERweb/tigerWMS_Current/MapServer
apiParameters = @"&benchmark=4&vintage=4&format=json&layers=54,86";
}
public class Result
{
public AddressMatches[] addressMatches { get; set; }
}
public class Address
{
public string state { get; set; }
public string street { get; set; }
public string city { get; set; }
}
public class AddressMatches
{
public Geographies geographies { get; set; }
public string matchedAddress { get; set; }
public Coordinates coordinates { get; set; }
public AddressComponents addressComponents { get; set; }
}
public class Geographies
{
[JsonProperty("115th Congressional Districts")]
public CongressionalDistricts[] congressionalDistricts { get; set; }
public Counties[] counties { get; set; }
}
public class Counties
{
public int STATE { get; set; }
public string NAME { get; set; }
public string BASENAME { get; set; }
}
public class CongressionalDistricts
{
public string NAME { get; set; }
public int CDSESSN { get; set; }
public string BASENAME { get; set; }
}
public class Coordinates
{
public double x { get; set; }
public double y { get; set; }
}
public class AddressComponents
{
public string preDirection { get; set; }
public string preType { get; set; }
public string streetName { get; set; }
public string suffixType { get; set; }
public string toAddress { get; set; }
public string preQualifier { get; set; }
public string suffixDirection { get; set; }
public string suffixQualifier { get; set; }
public string fromAddress { get; set; }
public string state { get; set; }
public string zip { get; set; }
public string city { get; set; }
}
public Geocode<Census> GeocodeAddress
(string street, string city = "",
string state = "", string zip = "")
{
Census json = null;
var geocode = new Geocode<Census>();
HttpResponseMessage response = geocode.GetResponse
(this.apiUrl, this.GetUrlParameters(street, city, state, zip));
using (response)
{
if (response.IsSuccessStatusCode)
{
var data = response.Content.ReadAsStringAsync().Result;
json = JsonConvert.DeserializeObject<Census>(data);
if (json != null)
{
geocode.Latitude = json.result.addressMatches[0].coordinates.x;
geocode.Longitude =
json.result.addressMatches[0].coordinates.y;
geocode.CongressionalDistrictName =
json.result.addressMatches[0].geographies.congressionalDistricts[0].NAME;
}
}
return geocode;
}
}
internal string GetUrlParameters
(string street,
string city = "", string state = "", string zip = "")
{
string urlParameters = "";
urlParameters += "?street=" + street;
if (city.Length > 0) urlParameters += "&city=" + city;
if (state.Length > 0) urlParameters += "&state=" + state;
if (zip.Length > 0) urlParameters += "&zip=" + zip;
// Append API Parameters
urlParameters += this.apiParameters;
return urlParameters;
}
}
}
Geocodio.cs
Geocodio 需要注册,并且每天的免费地理编码请求限制为 2,500 个。 该服务易于使用,并且文档和示例很有帮助。 可以配置多个 API 密钥,并且没有访问限制。 确保将 apiKey 值 "***API KEY***"
替换为有效的密钥。
要开始使用,请访问 https://geocod.io
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
namespace GeocodeConsole
{
internal interface IGeocodio
{
Geocode<Geocodio> GeocodeAddress(string address);
}
/*
Geocodio
https://geocod.io
*/
class Geocodio : IGeocodio
{
private string apiKey;
private string apiUrl;
private string apiParameters;
public Input input { get; set; }
public List<Result> results { get; set; }
public Geocodio()
{
// API Key from Geocod.io
apiKey = "***API KEY***";
apiUrl = @"https://api.geocod.io/v1/geocode";
// Fields: Congressional District (cd)
apiParameters = "&fields=cd&api_key=" + apiKey;
}
public class AddressComponents
{
public string number { get; set; }
public string street { get; set; }
public string suffix { get; set; }
public string formatted_street { get; set; }
public string city { get; set; }
public string state { get; set; }
public string zip { get; set; }
public string country { get; set; }
}
public class Input
{
public AddressComponents address_components { get; set; }
public string formatted_address { get; set; }
}
public class Location
{
public double lat { get; set; }
public double lng { get; set; }
}
public class CongressionalDistrict
{
public string name { get; set; }
public string district_number { get; set; }
public string congress_number { get; set; }
public string congress_years { get; set; }
}
public class Fields
{
public CongressionalDistrict congressional_district { get; set; }
}
public class Result
{
public AddressComponents address_components { get; set; }
public string formatted_address { get; set; }
public Location location { get; set; }
public double accuracy { get; set; }
public string accuracy_type { get; set; }
public string source { get; set; }
public Fields fields { get; set; }
}
public Geocode<Geocodio> GeocodeAddress(string address)
{
Geocodio json = null;
var geocode = new Geocode<Geocodio>();
HttpResponseMessage response =
geocode.GetResponse(this.apiUrl, this.GetUrlParameters(address));
using (response)
{
if (response.IsSuccessStatusCode)
{
var data = response.Content.ReadAsStringAsync().Result;
json = JsonConvert.DeserializeObject<Geocodio>(data);
if (json != null)
{
geocode.Latitude = json.results[0].location.lat;
geocode.Longitude = json.results[0].location.lng;
geocode.CongressionalDistrictName =
json.results[0].fields.congressional_district.name;
}
}
return geocode;
}
}
internal string GetUrlParameters(string address)
{
string urlParameters = "";
urlParameters += "?q=" + address;
// Append API Parameters
urlParameters += this.apiParameters;
return urlParameters;
}
}
}
LatLon.cs
LatLon 需要注册,并且每月的免费地理编码请求限制为 15,000 个。 该服务易于使用,并且帐户收到单个 API 令牌。 确保将 apiKey 值 "***API KEY***"
替换为有效的密钥。
要开始使用,请访问 https://latlon.io
using Newtonsoft.Json;
using System.Net.Http;
namespace GeocodeConsole
{
internal interface ILatLon
{
Geocode<LatLon> GeocodeAddress(string address);
}
/*
LatLon
https://latlon.io
*/
public class LatLon : ILatLon
{
private string apiKey;
private string apiUrl;
private string apiParameters;
public double lat { get; set; }
public double lon { get; set; }
public Address address { get; set; }
public Geography geography { get; set; }
public CongressionalDistrict congressional_district { get; set; }
public LatLon()
{
// API Key from LatLon.io
apiKey = "***API KEY***";
apiUrl = @"https://latlon.io/api/v1/geocode";
// Fields: Congressional District, Geography
apiParameters = @"&fields=congressional_district,
geography&token=" + apiKey;
}
public class Address
{
public string address { get; set; }
public string prefix { get; set; }
public string number { get; set; }
public string street_name { get; set; }
public string street_type { get; set; }
public string suffix { get; set; }
public string unit { get; set; }
public string city { get; set; }
public string state { get; set; }
public string zip { get; set; }
}
public class Geography
{
public string state { get; set; }
public string county { get; set; }
public string neighborhood { get; set; }
public Fips fips { get; set; }
}
public class Fips
{
public string state { get; set; }
public string county { get; set; }
public string tract { get; set; }
public int block_group { get; set; }
public int block { get; set; }
}
public class CongressionalDistrict
{
public string name { get; set; }
public int district_number { get; set; }
public string congress_number { get; set; }
}
public Geocode<LatLon> GeocodeAddress(string address)
{
LatLon json = null;
var geocode = new Geocode<LatLon>();
HttpResponseMessage response = geocode.GetResponse
(this.apiUrl, this.GetUrlParameters(address));
using (response)
{
if (response.IsSuccessStatusCode)
{
var data = response.Content.ReadAsStringAsync().Result;
json = JsonConvert.DeserializeObject<LatLon>(data);
if (json != null)
{
geocode.Latitude = json.lat;
geocode.Longitude = json.lon;
geocode.CongressionalDistrictName =
json.congressional_district.name;
}
}
return geocode;
}
}
internal string GetUrlParameters(string address)
{
string urlParameters = "";
urlParameters += "?address=" + address;
// Append API Parameters
urlParameters += this.apiParameters;
return urlParameters;
}
}
}
Program.cs
主程序只是演示了在同一地址上使用所有三个 Web 服务。
using System;
namespace GeocodeConsole
{
class Program
{
static void Main(string[] args)
{
string street = "350 5th Ave";
string city = "New York";
string state = "NY";
string zip = "10118";
// Census
var census = new Geocode<census>();
census.Service = new Census();
var geocodeCensus = census.Service.GeocodeAddress(street, city, state, zip);
if (geocodeCensus != null)
{
Console.WriteLine(geocodeCensus.Latitude + " : " +
geocodeCensus.Longitude + " = " +
geocodeCensus.CongressionalDistrictName);
}
// Geocodio
var geocodio = new Geocode<geocodio>();
geocodio.Service = new Geocodio();
var geocodeGeocodio =
geocodio.Service.GeocodeAddress(street + "+" + city + "+" + state + "+" + zip);
if (geocodeGeocodio != null)
{
Console.WriteLine(geocodeGeocodio.Latitude + " : " +
geocodeGeocodio.Longitude + " = " +
geocodeGeocodio.CongressionalDistrictName);
}
// LatLon
var latlon = new Geocode<latlon>();
latlon.Service = new LatLon();
var geocodeLatLon = latlon.Service.GeocodeAddress
(street + "+" + city + "+" + state + "+" + zip);
if (geocodeLatLon != null)
{
Console.WriteLine(geocodeLatLon.Latitude + " : " +
geocodeLatLon.Longitude + " = " +
geocodeLatLon.CongressionalDistrictName);
}
}
}
}
接下来呢?
在以后的文章中,我将使用此项目将国会选区名称添加到 Dynamics CRM 中的美国地址。
历史
- 2016 年 11 月 22 日:初稿