Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Ready for packaging

  • Loading branch information...
commit c21ce257be63319dbffbd4b52c900b219ba45861 1 parent d6b4f28
@davglass authored
View
4 .gitignore
@@ -1,2 +1,2 @@
-scripts/zips.csv
-scripts/zips.csv.gz
+node_modules
+scripts/*.csv
View
30 LICENSE
@@ -0,0 +1,30 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2007, Dav Glass <davglass@gmail.com>.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* The name of Dav Glass may not be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Dav Glass.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
View
14 Makefile
@@ -1,5 +1,15 @@
-all:
+all: zips test
-
zips:
./scripts/fetch.sh
+
+zip: zips
+
+codes: zips
+
+test: tests
+
+tests:
+ ./tests/lookup.js
+
+.PHONY: test tests zips
View
104 README.md
@@ -0,0 +1,104 @@
+Zip Code Lookups
+================
+
+A localized (flatfile) zipcode lookup.
+
+Zipcode data was taken from here: http://federalgovernmentzipcodes.us/
+
+It was then transformed into a JSON object and then wrapped with some helper methods.
+
+Usage
+-----
+
+ var zipcodes = require('zipcodes');
+
+
+Zipcode Lookup
+--------------
+
+ var hills = zipcodes.lookup(90210);
+
+ { zip: '90210',
+ latitude: 34.088808,
+ longitude: -118.406125,
+ city: 'Beverly Hills',
+ state: 'CA' }
+
+Distance
+--------
+
+This is not driving distance, it's line of sight distance
+
+
+ var dist = zipcodes.distance(62959, 90210); //In Miles
+ // dist = 1662
+
+ var kilo = zipcodes.toKilometers(dist); //Convert to Kilometers
+ // kilo = 2675
+
+ var miles = zipcodes.toMiles(zipcodes.toKilometers(dist)); //Convert to Kilometers, then to miles
+ // miles = 1662
+
+
+Lookup By Name
+--------------
+
+ var l = zipcodes.lookupByName('Cupertino', 'CA');
+
+ //Always returns an array, since cities can have multiple zip codes
+ [ { zip: '95015',
+ latitude: 37.323,
+ longitude: -122.0527,
+ city: 'Cupertino',
+ state: 'CA' } ]
+
+
+Lookup by Radius
+----------------
+
+Get all zipcodes within the milage radius of this zipcode
+
+ var rad = zipcodes.radius(95014, 50);
+ // rad.length == 385
+
+ [ '93901',
+ '93902',
+ '93905',
+ '93906',
+ '93907',
+ '93912',
+ '93933',
+ '93942',
+ '93944',
+ '93950',
+ ...
+ '95377',
+ '95378',
+ '95385',
+ '95387',
+ '95391'
+ ]
+
+
+TODO
+----
+
+Add support for importing into MongoDB or CouchDB to speed up searchs.
+
+Development
+-----------
+
+The original CSV file that I am using for this data is not included in this repo, but I did wrap up
+the best way to get the data and how to convert it into the format that this module uses.
+
+To develop with this module, just `make` it and it will fetch the latest zipcodes and reprocess them.
+
+ make
+
+To just fetch and process the zipcodes:
+
+ make codes
+
+To run the very simple test suite:
+
+ make tests
View
3  lib/codes.js
2 additions, 1 deletion not shown
View
80 lib/index.js
@@ -0,0 +1,80 @@
+
+var codes = require('./codes'),
+ states = require('./states');
+
+exports.states = states;
+exports.codes = codes.codes;
+
+var lookup = function(zip) {
+ return codes.codes[zip];
+};
+
+exports.lookup = lookup;
+
+var byName = function(city, state) {
+ city = city.toUpperCase();
+ state = states.normalize(state.toUpperCase());
+
+ //console.log('byname', city, state);
+
+ var ret = [];
+
+ codes.stateMap[state].forEach(function(zip) {
+ var item = codes.codes[zip];
+ if (city === item.city.toUpperCase()) {
+ ret.push(item);
+ }
+ });
+
+ return ret;
+}
+
+exports.lookupByName = byName;
+
+var dist = function(zipA, zipB) {
+ zipA = lookup(zipA);
+ zipB = lookup(zipB);
+
+ var distance = Math.sin(deg2rad(zipA.latitude))
+ * Math.sin(deg2rad(zipB.latitude))
+ + Math.cos(deg2rad(zipA.latitude))
+ * Math.cos(deg2rad(zipB.latitude))
+ * Math.cos(deg2rad(zipA.longitude - zipB.longitude));
+
+ distance = (rad2deg(Math.acos(distance))) * 69.09;
+ return Math.round(distance);
+};
+
+exports.distance = dist;
+
+
+//This is SLLOOOOWWWWW
+exports.radius = function(zip, miles, full) {
+ var ret = [], i, d;
+
+ for (i in codes.codes) {
+ if (dist(zip, i) <= miles) {
+ ret.push(((full) ? codes.codes[i] : i));
+ }
+ }
+
+ return ret;
+};
+
+
+var rad2deg = function(value) {
+ value = Number(value);
+ return (value * (180/Math.PI));
+}
+var deg2rad = function(value) {
+ value = Number(value);
+ return (value * (Math.PI/180));
+}
+
+exports.toMiles = function(kilos) {
+ return Math.round(kilos / 1.609344);
+};
+
+exports.toKilometers = function(miles) {
+ return Math.round(miles * 1.609344);
+};
View
73 lib/states.js
@@ -0,0 +1,73 @@
+var full = {
+ 'ALABAMA': 'AL',
+ 'ALASKA': 'AK',
+ 'ARIZONA': 'AZ',
+ 'ARKANSAS': 'AR',
+ 'CALIFORNIA': 'CA',
+ 'COLORADO': 'CO',
+ 'CONNECTICUT': 'CT',
+ 'DELAWARE': 'DE',
+ 'DISTRICT OF COLUMBIA': 'DC',
+ 'FLORIDA': 'FL',
+ 'GEORGIA': 'GA',
+ 'HAWAII': 'HI',
+ 'IDAHO': 'ID',
+ 'ILLINOIS': 'IL',
+ 'INDIANA': 'IN',
+ 'IOWA': 'IA',
+ 'KANSAS': 'KS',
+ 'KENTUCKY': 'KY',
+ 'LOUISIANA': 'LA',
+ 'MAINE': 'ME',
+ 'MARYLAND': 'MD',
+ 'MASSACHUSETTS': 'MA',
+ 'MICHIGAN': 'MI',
+ 'MINNESOTA': 'MN',
+ 'MISSISSIPPI': 'MS',
+ 'MISSOURI': 'MO',
+ 'MONTANA': 'MT',
+ 'NEBRASKA': 'NE',
+ 'NEVADA': 'NV',
+ 'NEW HAMPSHIRE': 'NH',
+ 'NEW JERSEY': 'NJ',
+ 'NEW MEXICO': 'NM',
+ 'NEW YORK': 'NY',
+ 'NORTH CAROLINA': 'NC',
+ 'NORTH DAKOTA': 'ND',
+ 'OHIO': 'OH',
+ 'OKLAHOMA': 'OK',
+ 'OREGON': 'OR',
+ 'PENNSYLVANIA': 'PA',
+ 'RHODE ISLAND': 'RI',
+ 'SOUTH CAROLINA': 'SC',
+ 'SOUTH DAKOTA': 'SD',
+ 'TENNESSEE': 'TN',
+ 'TEXAS': 'TX',
+ 'UTAH': 'UT',
+ 'VERMONT': 'VT',
+ 'VIRGINIA': 'VA',
+ 'WASHINGTON': 'WA',
+ 'WEST VIRGINIA': 'WV',
+ 'WISCONSIN': 'WI',
+ 'WYOMING': 'WY'
+};
+
+var abbr = {};
+
+exports.full = full;
+
+
+for (var i in full) {
+ abbr[full[i]] = i;
+}
+
+exports.abbr = abbr;
+
+exports.normalize = function(state) {
+ state = state.toUpperCase();
+
+ if (state.length !== 2) {
+ state = full[state];
+ }
+ return state;
+}
View
18 package.json
@@ -0,0 +1,18 @@
+{
+ "name": "zipcodes",
+ "description": "Useful zipcode database with helper methods",
+ "version": "0.1.0",
+ "author": "Dav Glass <davglass@gmail.com>",
+ "bugs": { "web": "http://github.com/davglass/zipcodes/issues" },
+ "os": [ "darwin", "linux" ],
+ "engines": { "node": ">=0.4.0" },
+ "main": "./lib/index.js",
+ "licenses": [{
+ "type": "BSD",
+ "url": "http://github.com/davglass/zipcodes/"
+ }],
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/davglass/zipcodes.git"
+ }
+}
View
19 scripts/fetch.sh
@@ -2,22 +2,13 @@
cd "$(dirname "$0")"
-echo "Fetching zipscodes CSV File"
-if [ -f ./zips.csv.gz ]; then
- rm ./zips.csv.gz
+if [ ! -f ./free-zipcode-database.csv ]; then
+ echo "Fetching zipscodes CSV File"
+ wget -nv "http://federalgovernmentzipcodes.us/free-zipcode-database.csv"
fi
-if [ -f ./zips.csv ]; then
- rm ./zips.csv
-fi
-
-wget -nv "http://sourceforge.net/projects/zips/files/zips/zips.csv.gz/zips.csv.gz/download"
-
-wait
-echo "Unzipping CSV file"
-gzip -d ./zips.csv.gz
wait
echo "Processing CSV file."
@@ -26,4 +17,8 @@ echo "Processing CSV file."
wait
+rm ./free-zipcode-database.csv
+
+wait
+
echo "Build Complete"
View
48 scripts/process.js
@@ -3,27 +3,55 @@
var fs = require('fs'),
path = require('path'),
zips = {}, str,
- data = fs.readFileSync('./zips.csv', 'utf8').split('\n');
+ data = fs.readFileSync('./free-zipcode-database.csv', 'utf8').replace(/\r/g, '').split('\n');
data.shift();
-data.forEach(function(line) {
- line = line.replace(/ /g, '').replace(/"/g, '').split(',');
+var clean = function(str) {
+ return str.replace(/"/g, '').trimLeft();
+}
+
+var ucfirst = function(str) {
+ str = str.toLowerCase();
+ var lines = str.split(' ');
+ lines.forEach(function(s, i) {
+ var firstChar = s.charAt(0),
+ upperFirstChar = firstChar.toUpperCase();
+
+ lines[i] = upperFirstChar + s.substring(1);
+
+ });
+ return lines.join(' ');
+};
+
+data.forEach(function(line, num) {
+ line = line.split(',');
if (line.length > 1) {
var o = {};
- o.state = line.pop();
- o.city = line.pop();
- o.longitude = line.pop();
- o.latitude = line.pop();
- o.abbr = line.pop();
- o.zip = line.pop();
+ o.zip = clean(line.shift());
+ o.latitude = Number(clean(line.shift()));
+ o.longitude = Number(clean(line.shift()));
+ o.city = ucfirst(clean(line.shift()));
+ o.state = clean(line.shift());
zips[o.zip] = o;
}
});
-str = 'exports.zips = ' + JSON.stringify(zips) + ';\n';
+
+
+var stateMap = {};
+
+for (var i in zips) {
+ var item = zips[i];
+ stateMap[item.state] = stateMap[item.state] || [];
+
+ stateMap[item.state].push(item.zip);
+}
+
+str = 'exports.codes = ' + JSON.stringify(zips) + ';\n';
+str += 'exports.stateMap = ' + JSON.stringify(stateMap) + ';\n';
fs.writeFileSync(path.join('../', 'lib', 'codes.js'), str, 'utf8');
View
57 tests/lookup.js
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+
+var path = require('path'),
+ assert = require('assert');
+
+var zipcodes = require(path.join(__dirname, '../', 'lib'));
+
+assert.ok(zipcodes);
+assert.ok(zipcodes.lookup);
+
+var i = 1;
+
+var marion = zipcodes.lookup(62959);
+assert.equal(marion.city, 'Marion');
+
+var hills = zipcodes.lookup(90210);
+assert.equal(hills.city, 'Beverly Hills');
+
+var dist = zipcodes.distance(62959, 90210);
+assert.equal(dist, 1662);
+
+var dist2 = zipcodes.distance(62959, 62959);
+assert.equal(dist2, 0);
+
+var dist3 = zipcodes.distance(62959, 63801);
+assert.equal(dist3, 68);
+
+var dist4 = zipcodes.distance(62959, 95014);
+assert.equal(dist4, 1805);
+assert.equal(zipcodes.toKilometers(dist4), 2905);
+assert.equal(zipcodes.toMiles(zipcodes.toKilometers(dist4)), dist4);
+
+var dist5 = zipcodes.distance(62959, 90210);
+assert.equal(dist5, 1662);
+
+var l = zipcodes.lookupByName('Marion', 'il');
+assert.equal(l.length, 1);
+
+var l = zipcodes.lookupByName('Marion', 'Illinois');
+assert.equal(l.length, 1);
+
+var l = zipcodes.lookupByName('Cupertino', 'CA');
+assert.equal(l.length, 1);
+
+var l = zipcodes.lookupByName('New York', 'New York');
+assert.equal(l.length, 71);
+
+var l = zipcodes.lookupByName('New York', 'NY');
+assert.equal(l.length, 71);
+
+var rad = zipcodes.radius(62959, 20);
+assert.equal(rad.length, 35);
+
+var rad = zipcodes.radius(95014, 50);
+assert.equal(rad.length, 385);
+
+console.log('Woot! All tests passed');
Please sign in to comment.
Something went wrong with that request. Please try again.