Click here to Skip to main content
15,883,901 members
Articles / Programming Languages / C#

How to Use Geocoding Web Services to Determine the Congressional District for an Address

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
23 Nov 2016CPOL3 min read 20.3K   319   14   3
Learn how easy it is to write a console application to find the Congressional District of a U.S. address using free geocoding web services.

Introduction

A recent Dynamics CRM project required U.S. addresses to include their Congressional District name and number. I happened to find three geocoding web services that included the latest legislative information I needed and could be used for free (for the most part). This article describes how to write a console application in C# that can accommodate the different data structures of the JSON response.

Background

There are many geocoding web services available, but APIs provided by the U.S. Census Bureau, LatLon.io and Geocod.io included the Congressional District information I was looking for. While the U.S. Census Bureau web services are completely free and unlimited usage for single address requests, LatLon.io and Geocod.io limit the number of free requests.

Using the Code

All of the source code in the article has been included in the downloadable materials. The demo project was built and tested using Visual Studio 2015 SP3 but there is no reason why the source code shouldn’t work in earlier versions of Visual Studio and .NET 4.5. I used Newtonsoft’s Json.NET to deserialize the JSON responses and is the only package required outside of .NET System assemblies. Download the latest version of JSON.NET at http://www.newtonsoft.com or use the NuGet Package Manager in Visual Studio.

Design

In order to accommodate the different data structures of the JSON responses, separate classes were used for each service (e.g. Census, Geocodio and LatLon). The class properties need to reflect the structure of the JSON response in order to be deserialized.

C#
// Deserialize the Json Response (data)
Census json = JsonConvert.DeserializeObject<Census>(data);

The Geocode object uses a custom Generic for each geocode web service. Instantiating the Census class looks like this:

C#
// Census
var census = new Geocode<Census>();

census.Service = new Census();

var geocodeCensus = census.Service.GeocodeAddress
("1600 Pennsylvania Ave NW", "Washington", "DC", "20500");

Source Code

Geocode.cs

The Geocode class constrains the T parameter to a Class Type.

Important Note: The SSL/TLS type in the GetResponse() method is set to SecurityProtocolType.Tls12 or TLS version 1.2. If a security flaw is found and these services upgrade and disable the TLS 1.2 protocol, an error message resembling, "An error occurred while sending the request." will occur.

C#
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

The U.S. Census Bureau Geocoding Services are free to use and there are no usage limits, keys or registration. With that said, the API is the slowest among the three. The service is fairly well documented but information on specifics can be hard to find. For more information, go to http://census.gov/data/developers/data-sets/Geocoding-services.html.

Important Note: The Geography subclass defines an array of CongressionalDistricts and the JsonProperty attribute is hard-coded with the name of the current session of Congress (e.g. 115th Congressional Districts).

C#
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 requires registration and free geocoding requests are limited to 2,500 requests per day. The service is easy to use and the documentation and examples are helpful. Multiple API keys can be configured and there are no access constraints. Be sure to replace the apiKey value "***API KEY***" with a valid key.

To get started, go to https://geocod.io:

C#
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 requires registration and free geocoding requests are limited to 15,000 requests per month. The service is easy to use and accounts receive a single API token. Be sure to replace the apiKey value "***API KEY***" with a valid key.

To get started, go to https://latlon.io:

C#
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

The main program simply demonstrates the use of all three web services on the same address.

C#
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);
            }
        }
    }
}

Now What?

In a future article, I will use this project to add the Congressional District name to U.S. addresses in Dynamics CRM.

History

  • 22-Nov-2016: First draft

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionServicePointManager is for global settings Pin
jonny8324-Nov-16 15:25
jonny8324-Nov-16 15:25 
AnswerRe: ServicePointManager is for global settings Pin
robert_chang26-Nov-16 13:10
robert_chang26-Nov-16 13:10 
GeneralRe: ServicePointManager is for global settings Pin
jonny8329-Dec-16 15:43
jonny8329-Dec-16 15:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.