congressional-district-finder

a node module for finding a congressional district based on latitude and longitude

Usage no npm install needed!

<script type="module">
  import congressionalDistrictFinder from 'https://cdn.skypack.dev/congressional-district-finder';
</script>

README

Congressional District Finder

A small library of functions for determining US Congressional representation based on location.

CircleCI Coverage Status Dependency Status Dev Dependency Status Known Vulnerabilities

Fetches and parses data from:

And uses geolib to determine if a given latitude or longitude resides in a state or district's geoJSON boundaries.

None of these require API tokens but getDistricts fetches from the Github Contents API, which rate-limits unauthenticated requests to 60 per hour from a given IP.

Requirements

Requires NodeJS version 4 or greater.

Installation

$ npm install congressional-district-finder --save

Usage

All methods return promises, using Request-Promise-Native for various http GET requests.

Get a District by Latitude and Longitude

var finder = require('congressional-district-finder');

finder.getDistrictByLatLng(40.718031, -73.9583047)
    .then(function(result) {
        console.log(result.isMatched); // outputs true
        console.log(result.district.name); //outputs "New York 12th"
        console.log(result.district.districtCode); //outputs "NY-12"
    });

If coordinates are outside the US:

finder.getDistrictByLatLng(31.6538179, -106.5890206)
    .catch(function(err) {
        console.log(err.message);
        // Outputs:
        //      "The specified latitude: 31.6538179 and longitude: -106.5890206
        //       are for the country: MX. To Find a Congressional District,
        //       please provide coordinates in the US."
    });

Get a District by Address

finder.getDistrictByAddress('45 Main Street Brooklyn')
    .then(function(result) {
        console.log(result.isMatched); // outputs true
        console.log(result.district.name); //outputs "New York 7th"
        console.log(result.district.districtCode); //outputs "NY-07"
    });

If the address is too vague:

finder.getDistrictByAddress('Pittsburgh, PA')
    .catch(function (err) {
        console.log(err.statusCode); //outputs 400
        console.log(err.message);
        // Outputs:
        //      "The specified address: ''Pittsburgh, PA'' appears to be too vague.
        //       Try including a street name and number."
    });

If the address is outside the US:

finder.getDistrictByAddress('3895 Boulevard St-Laurent, Montreal')
    .catch(function (err) {
        console.log(err.statusCode); //outputs 404
        console.log(err.message);
        // Outputs:
        //      "The specified address: '3895 Boulevard St-Laurent, Montreal'
        //       appears to be from the country: CA. To find a Congressional District,
        //       please provide coordinates in the US. More specific coordinates might also work."
    });

Handling the 404 for DC and Puerto Rico's At-Large Districts

Currently the repository of GeoJSON for 2016 congressional districts doesn't contain a district shape for DC's at-large district (DC-0 or DC-AL). To handle this situation, you can check the message on a 404 response against the template used to format the message for this particular error.

finder.getDistrictByAddress('1600 Pennsylvania Avenue DC')
    .catch(function(err) {
        if (err.message === finder.DISTRICT_NOT_FOUND('DC-0')) {
            throw new Error('Taxation without representation.');
        }
        throw err;
    })
    .catch(function(err) {
        console.log(err.message); // Outputs: "Taxation without representation."
    });

Since DC is an at-large district, it's safe to assume that the provided coordinates reside in this district.

Likewise, because it's a territory, Puerto Rico will also throw a 404. While Puerto Rico's at-large district is also missing from the 2016 GEOJson repo, this particular error is thrown because it doesn't reside in the US. It can be detected and handled as such:

var address = 'San Juan, Puerto Rico';
finder.getDistrictByAddress(address)
    .catch(function(err) {
        if (err.statusCode != 404) {
            throw err;
        }
        switch (err.message) {
            case finder.ADDRESS_OUTSIDE_US(address, 'PR') :
            return Promise.resolve({
                district: {
                    districtCode: 'PR-AL',
                    name: 'Puerto Rico At-Large'
                }
            });
            default:
                throw err;
        }
    })
    .then(function(result) {
        console.log(result.district.name); // Outputs "Puerto Rico At-Large"
    });

Error templates are also exported for COORDS_OUTSIDE_US: 404, and ADDRESS_TOO_VAGUE: 400.

Other Methods

Get a List of All US Congressional Districts

finder.getDistricts()
    .then(function(result) {
        console.log(result.districts.length); // outputs all 435 US Congressional Districts
        console.log(result.districts[0]);// outputs AK-0
        console.log(result.districts[434]);// outputs WY-0
    });

Note that this particular method uses the Github Contents API Without Github API auth credentials, you are limited to 60 requests per hour from a given IP. If your application will likely exceed this, you can do the following:

var districts, myCachedEtag = '"36bac568759f240e06955cf597493555"';
finder.getDistricts({'If-None-Match': myCachedEtag})
    .then(function(result) {
        //update your cached etag and cached list of districts
        myCachedEtag = result.headers.etag;
        districts = result.districts;
        console.log(myCachedEtag);
    })
    .catch(function(err) {
        if (err.statusCode === 304) {
           console.log('just use your cached districts.');
        }
    });

Read more about Github's rate limit rules.

Check Coordinates Against a State

var lat = 40.718031;
var lng = -73.9583047;

finder.checkLatLngInState(lat, lng, 'NY')
    .then(function(result) {
        console.log('Check coordinates in New York...');
        console.log(result); // outputs { isMatched: true, stateId: 'NY', latitude: 40.718031, longitude: -73.9583047 }
    });

finder.checkLatLngInState(lat, lng, 'CT')
    .then(function(result) {
        console.log('Check coordinates in Connecticut...');
        console.log(result); // outputs {isMatched: false, stateId: 'CT',latitude: 40.718031, ongitude: -73.9583047 }
    });

Check Coordinates Against a District

var honolulu = {latitude: 21.3069, longitude: -157.8583};

finder.checkLatLngInDistrict(honolulu.latitude, honolulu.longitude, 'HI-1')
    .then(function(result) {
        console.log(result.districtId); // outputs HI-1
        console.log(result.district.districtCode); // outputs HI-01
        console.log(result.district.name); // outputs Hawaii 1st
        console.log(result.isMatched); // outputs true
        console.log('-');
    });

finder.checkLatLngInDistrict(honolulu.latitude, honolulu.longitude, 'HI-2')
    .then(function(result) {
        console.log(result.districtId); // outputs HI-2
        console.log(result.district.districtCode); // outputs HI-02
        console.log(result.district.name); // outputs Hawaii 2nd
        console.log(result.isMatched); // outputs false
        console.log('-');
    });

Tests

$ npm test

Contributing

Code is transpiled from ES6/ES2015. You can lint code by running:

$ npm run lint