65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2016年11月22日

CPOL

3分钟阅读

viewsIcon

21579

downloadIcon

369

了解如何轻松编写控制台应用程序,使用免费的地理编码 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 日:初稿
© . All rights reserved.