Skip to content

Commit 3873dce

Browse files
committed
✨ add limit option to main function
Can now request first `n` ZIP codes instead of just one result. Resolves #8
1 parent d359202 commit 3873dce

File tree

3 files changed

+131
-32
lines changed

3 files changed

+131
-32
lines changed

README.md

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,110 @@
1-
# geo2zip [![Travis](https://img.shields.io/travis/blakek/geo2zip/master.svg)](https://travis-ci.org/blakek/geo2zip)
1+
# geo2zip
22

3-
> Translates latitude / longitude geolocations to the nearest corresponding U.S. zip code
3+
> 🌎🔍 Quickly find the the nearest U.S. ZIP codes to a latitude/longitude geolocation using k-nearest neighbors
44
5-
Takes a geolocation and returns the nearest US ZIP code.
5+
[![Travis](https://img.shields.io/travis/blakek/geo2zip/master.svg)][1]
66

7-
⚠️ **NOTE:** this may not return the actual ZIP code of the location; it returns the ZIP code where the approximate center of the ZIP is nearest the location.
7+
Takes a geolocation and returns the nearest US ZIP codes.
8+
9+
⚠️ **NOTE:** The top result may not return the _actual_ ZIP code containing the
10+
location. This library returns the ZIP code where the approximate center of the
11+
ZIP is nearest the given location. Larger ZIP code areas may show lower in the
12+
results when searching nearby points because the center can be so far away. If
13+
you're relying on something as inaccurate as ZIP codes, the top result is likely
14+
close enough.
15+
16+
Also, ZIP codes change. They change in shape and location. New ones are
17+
created and old ones are removed. They're even recycled. If you know of a
18+
reliable, more up-to-date list of ZIP codes, please let us know by opening an
19+
issue.
20+
21+
**Can I use this in the browser?** Yes but please don't. This is made to run on
22+
a Node.js server. This depends on a roughly [2 MB list of US ZIP
23+
codes][2] and sorts those in-memory. Please
24+
don't do that to my phone. If you're experimenting or know what you're doing,
25+
use this wherever and however it works for you.
26+
27+
**Isn't sorting and filtering on a 2MB file slow in JavaScript?** No. A lookup
28+
is created when the main file is imported/required. After the lookup is created,
29+
you should be able to run the examples in this README millions of times per
30+
second; showing output would be the limit. The tradeoff is your server may take
31+
a 1-2 seconds longer to start up initially (e.g. from `npm run start`).
32+
33+
## Install
34+
35+
With [Yarn](https://yarnpkg.com/en/) or [npm](https://npmjs.org/) installed,
36+
run:
37+
38+
```shell
39+
yarn add geo2zip
40+
41+
# ...or, if using `npm`
42+
npm install geo2zip
43+
```
844

945
## Usage
1046

1147
Get the nearest ZIP to a location:
1248

1349
```js
14-
const geo2zip = require('geo2zip')
50+
import geo2zip from 'geo2zip'
1551

16-
const somewhere = {
52+
const location = {
1753
latitude: 34.659698,
1854
longitude: -88.242903
1955
}
2056

21-
geo2zip(somewhere).then(zip => {
22-
console.log(zip) // '38873'
23-
})
57+
const closestZip = await geo2zip(location)
58+
59+
console.log(closestZip) // ['38873']
2460
```
2561

26-
## Install
62+
Get the closest 5 ZIPs to a location:
2763

28-
With [npm](https://npmjs.org/) installed, run
64+
```js
65+
import geo2zip from 'geo2zip'
2966

67+
const location = {
68+
latitude: 34.659698,
69+
longitude: -88.242903
70+
}
71+
72+
await geo2zip(location, { limit: 5 })
73+
// [ '38873', '38838', '38827', '38859', '38852' ]
3074
```
31-
$ npm install geo2zip
32-
```
75+
76+
## API
77+
78+
### `geo2zip(location, options)`
79+
80+
#### location
81+
82+
Type: `Object`
83+
84+
A geolocation with valid `latitude` and `longitude` properties.
85+
86+
#### options
87+
88+
Type: `Object`
89+
90+
Properties:
91+
92+
- `limit` - the number of results to return
93+
- Type: `Number`
94+
- Default: `1`
3395

3496
## See Also
3597

36-
* [`blakek/standardize-geolocation`](https://github.com/blakek/standardize-geolocation) - takes geolocations of different formats and outputs a standardized version
37-
* [`blakek/us-zips`](https://github.com/blakek/us-zips) - a list of US ZIP codes and their geolocations
98+
- [`standardize-geolocation`][3] - takes geolocations of different formats and
99+
outputs a standardized version
100+
- [`sphere-knn`][4] - find the k nearest neighbors for points on a sphere
101+
- [`us-zips`][2] - a list of US ZIP codes and their geolocations
38102

39103
## License
40104

41105
MIT
106+
107+
[1]: https://travis-ci.org/blakek/geo2zip
108+
[2]: https://github.com/blakek/us-zips
109+
[3]: https://github.com/blakek/standardize-geolocation
110+
[4]: https://github.com/darkskyapp/sphere-knn

index.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,23 @@ const sphereKnn = require('sphere-knn')
22
const { standardizeGeolocation } = require('standardize-geolocation')
33
const usZips = require('us-zips/array')
44

5+
// Start building lookup when module imported
56
const lookup = sphereKnn(usZips)
67

7-
function geo2zip (rawLocation) {
8-
const { latitude, longitude } = standardizeGeolocation(rawLocation)
9-
return Promise.resolve(lookup(latitude, longitude, 1)[0].zipCode)
8+
// Retain most-used behavior
9+
const defaultOptions = { limit: 1 }
10+
11+
// Don't recreate this function on every run
12+
const pluckZipCode = obj => obj.zipCode
13+
14+
function geo2zip (location, extraOptions) {
15+
return new Promise(resolve => {
16+
const options = { ...defaultOptions, ...extraOptions }
17+
const { latitude, longitude } = standardizeGeolocation(location)
18+
const results = lookup(latitude, longitude, options.limit)
19+
20+
resolve(results.map(pluckZipCode))
21+
})
1022
}
1123

1224
module.exports = geo2zip

test.js

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,42 @@
11
import test from 'ava'
22
import geo2zip from '.'
33

4-
test('returns zip code from same geolocation', async t => {
5-
const exactly38873 = {
6-
latitude: 34.659698,
7-
longitude: -88.242903
8-
}
4+
test('returns zip code nearest a geolocation', async t => {
5+
const testPoints = [
6+
{
7+
expected: '38873',
8+
location: { latitude: 34.659698, longitude: -88.242903 }
9+
},
10+
{
11+
expected: '37076',
12+
location: { latitude: 36.142226, longitude: -86.618779 }
13+
}
14+
]
915

10-
const zip = await geo2zip(exactly38873)
16+
const tests = testPoints.map(async ({ expected, location }) => {
17+
const actual = await geo2zip(location)
18+
t.is(actual[0], expected)
19+
})
1120

12-
t.is(zip, '38873')
21+
return Promise.all(tests)
1322
})
1423

15-
test('returns nearest zip code for geolocation', async t => {
16-
const near37076 = {
17-
latitude: 36.142226,
18-
longitude: -86.618779
19-
}
24+
test('returns a list of zip codes nearest a location, ordered by closeness', async t => {
25+
const testPoints = [
26+
{
27+
expected: '38859',
28+
location: { latitude: 34.659698, longitude: -88.242903 }
29+
},
30+
{
31+
expected: '37214',
32+
location: { latitude: 36.1432503, longitude: -86.6202267 }
33+
}
34+
]
2035

21-
const zip = await geo2zip(near37076)
36+
const tests = testPoints.map(async ({ expected, location }) => {
37+
const actual = await geo2zip(location, { limit: 5 })
38+
t.true(actual.includes(expected))
39+
})
2240

23-
t.is(zip, '37076')
41+
return Promise.all(tests)
2442
})

0 commit comments

Comments
 (0)