Skip to content

Commit 27af7f7

Browse files
Merge pull request #4 from conveyal/improvements
Improvements
2 parents 34b25b5 + 457eb5d commit 27af7f7

File tree

8 files changed

+415
-72
lines changed

8 files changed

+415
-72
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ lib-cov
1313

1414
# Coverage directory used by tools like istanbul
1515
coverage
16+
tmp
1617

1718
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
1819
.grunt

.travis.yml

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ notifications:
44
email: false
55
node_js:
66
- '6'
7+
before_install:
8+
- npm i -g codecov
9+
script:
10+
- npm run lint
11+
- npm run cover
12+
- codecov
713
after_success:
814
- npm run semantic-release
915
branches:

README.md

+237-34
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,257 @@ Lon/lat normalization cause...**sigh**.
77

88
No one has agreed on a standard way of representing lon/lat. This is a small normalization library. Use this to convert all outside input before processing internally and convert to an external format right when it's being output.
99

10-
## Just use the `{lon: ${longitude}, lat: ${latitude}}` representation
10+
## API
1111

12-
Utilizing this won't always be possible/easiest, so please at least adopt the following conventions. Any variables or functions that contain the following names should be represented by the accompanying structure:
12+
### lonlat(input)
1313

14-
* `lonlat`: `{lon: ${longitude}, lat: ${latitude}}`
15-
* `coordinates`: `[${longitude}, ${latitude}]`
16-
* `point`: `{x: ${longitude}, y: ${latitude}}`
14+
Tries parse input and transform to an output of normalized coordinates. Will throw an error upon finding invalid coordinates.
1715

18-
If you must convert it to a string, put it in the following format:
16+
#### Arguments
1917

20-
* `'${longitude},${latitude}'`
18+
`input (*)`: Can be any of the following:
19+
- an array in the format: [longitude, latitude]
20+
- a string in the format: '{longitude},{latitude}'
21+
- an object with a `x` attribute representing `longitude` and a `y` attribute representing `latitude`
22+
- an object with a `lon`, `lng` or `longitude` attribute and a `lat` or `latitude` attribute
2123

22-
## API
24+
#### Returns
25+
26+
`(Object)`: An object with `lon` and `lat` attributes.
27+
28+
#### Example
29+
30+
```js
31+
var lonlat = require('@conveyal/lonlat')
32+
33+
// Object with lon/lat-ish attributes
34+
var position = lonlat({ lon: 12, lat: 34 }) // { lon: 12, lat: 34 }
35+
position = lonlat({ lng: 12, lat: 34 }) // { lon: 12, lat: 34 }
36+
position = lonlat({ longitude: 12, latitude: 34 }) // { lon: 12, lat: 34 }
37+
position = lonlat({ lng: 12, latitude: 34 }) // { lon: 12, lat: 34 }
38+
39+
// coordinate array
40+
position = lonlat([12, 34]) // { lon: 12, lat: 34 }
41+
42+
// string
43+
position = lonlat('12,34') // { lon: 12, lat: 34 }
44+
45+
// object with x and y attributes
46+
position = lonlat({ x: 12, y: 34 }) // { lon: 12, lat: 34 }
47+
48+
// the following will throw errors
49+
position = lonlat({ lon: 999, lat: 34 }) // Error: Invalid longitude value: 999
50+
position = lonlat({ lon: 12, lat: 999 }) // Error: Invalid latitude value: 999
51+
position = lonlat({}) // Error: Invalid latitude value: undefined
52+
position = lonlat(null) // Error: Value must not be null or undefined
53+
```
54+
55+
### lonlat.fromCoordinates(arr) or lonlat.fromGeoJSON(arr)
56+
57+
Tries to parse from an array of coordinates. Will throw an error upon finding invalid coordinates.
58+
59+
#### Arguments
60+
61+
`arr (Array)`: An array in the format: [longitude, latitude]
62+
63+
#### Returns
64+
65+
`(Object)`: An object with `lon` and `lat` attributes.
66+
67+
#### Example
68+
69+
```js
70+
var lonlat = require('@conveyal/lonlat')
71+
72+
var position = lonlat.fromCoordinates([12, 34]) // { lon: 12, lat: 34 }
73+
position = lonlat.fromGeoJSON([12, 34]) // { lon: 12, lat: 34 }
74+
```
75+
76+
### lonlat.fromLatlng(obj) or lonlat.fromLeaflet(obj)
77+
78+
Tries to parse from an object. Will throw an error upon finding invalid coordinates.
79+
80+
#### Arguments
81+
82+
`obj (Object)`: An object with a `lon`, `lng` or `longitude` attribute and a `lat` or `latitude` attribute
83+
84+
#### Returns
85+
86+
`(Object)`: An object with `lon` and `lat` attributes.
87+
88+
#### Example
2389

2490
```js
25-
const assert = require('assert')
26-
const ll = require('@conveyal/lonlat')
91+
var lonlat = require('@conveyal/lonlat')
92+
93+
var position = lonlat.fromLatlng({ longitude: 12, latitude: 34 }) // { lon: 12, lat: 34 }
94+
position = lonlat.fromLeaflet({ lng: 12, lat: 34 }) // { lon: 12, lat: 34 }
95+
```
96+
97+
### lonlat.fromPoint(obj)
2798

28-
const lat = 38.13234
29-
const lon = 70.01232
30-
const lonlat = {lon, lat}
31-
const point = {x: lon, y: lat}
32-
const coordinates = [lon, lat]
33-
const str = `${lon},${lat}`
34-
const latlng = {lat, lng: lon}
99+
Tries to parse from an object. Will throw an error upon finding invalid coordinates.
35100

36-
const pairs = [
37-
// normalization
38-
[lonlat, ll(lonlat)],
39-
[lonlat, ll(point)],
40-
[lonlat, ll(coordinates)],
41-
[lonlat, ll(str)],
101+
#### Arguments
42102

43-
// convert to type, normalizes to `latlng` first in each function
44-
[ll.toCoordinates(lonlat), coordinates],
45-
[ll.toPoint(lonlat), point],
46-
[ll.toString(lonlat), str],
103+
`obj (Object)`: An object with a `x` attribute representing `longitude` and a `y` attribute representing `latitude`
47104

48-
// if the type is known, use the specific convert function directly
49-
[lonlat, ll.fromLatlng(latlng)],
50-
[lonlat, ll.fromCoordinates(coordinates)],
51-
[lonlat, ll.fromPoint(point)],
52-
[lonlat, ll.fromString(str)]
53-
]
105+
#### Returns
106+
107+
`(Object)`: An object with `lon` and `lat` attributes.
108+
109+
#### Example
110+
111+
```js
112+
var lonlat = require('@conveyal/lonlat')
54113

55-
pairs.forEach((pair) => assert.deepEqual(pair[0], pair[1]))
114+
var position = lonlat.fromPoint({ x: 12, y: 34 }) // { lon: 12, lat: 34 }
56115
```
57116

117+
### lonlat.fromString(str)
118+
119+
Tries to parse from a string. Will throw an error upon finding invalid coordinates.
120+
121+
#### Arguments
122+
123+
`str (string)`: A string in the format: '{longitude},{latitude}'
124+
125+
#### Returns
126+
127+
`(Object)`: An object with `lon` and `lat` attributes.
128+
129+
#### Example
130+
131+
```js
132+
var lonlat = require('@conveyal/lonlat')
133+
134+
var position = lonlat.fromString('12,34') // { lon: 12, lat: 34 }
135+
```
136+
137+
### lonlat.print(input, [fixed=5])
138+
139+
Returns a pretty string
140+
141+
#### Arguments
142+
143+
- `input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
144+
- `[fixed=5] (Number)`: The number of digits to round to
145+
146+
#### Returns
147+
148+
`(string)`: A string with the latitude and longitude rounded to the number of decimal places as specified by `fixed`
149+
150+
#### Example
151+
152+
```js
153+
var lonlat = require('@conveyal/lonlat')
154+
155+
var pretty = lonlat.print('12.345678,34') // '12.34568, 34.00000'
156+
```
157+
158+
### lonlat.isEqual(lonlat1, lonlat2, [epsilon=0])
159+
160+
Checks equality of two inputs within an allowable difference.
161+
162+
#### Arguments
163+
164+
- `lonlat1 (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
165+
- `lonlat2 (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
166+
- `[epsilon=0] (Number)`: The maximum allowable difference of between each latitude and longitude
167+
168+
#### Returns
169+
170+
`(boolean)`: Returns `true` if the inputs are equal or `false` if they are not
171+
172+
#### Example
173+
174+
```js
175+
var lonlat = require('@conveyal/lonlat')
176+
177+
var isEqual = lonlat.isEqual('12,34', [12, 34]) // true
178+
```
179+
180+
### lonlat.toCoordinates(input)
181+
182+
Translates to a coordinate array.
183+
184+
#### Arguments
185+
186+
`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
187+
188+
#### Returns
189+
190+
`(Array)`: An array in the format: [longitude, latitude]
191+
192+
#### Example
193+
194+
```js
195+
var lonlat = require('@conveyal/lonlat')
196+
197+
var coords = lonlat.toCoordinates('12,34') // [12, 34]
198+
```
199+
200+
### lonlat.toPoint(input)
201+
202+
Translates to point Object.
203+
204+
#### Arguments
205+
206+
`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
207+
208+
#### Returns
209+
210+
`(Object)`: An object with `x` and `y` attributes representing latitude and longitude respectively
211+
212+
#### Example
213+
214+
```js
215+
var lonlat = require('@conveyal/lonlat')
216+
217+
var point = lonlat.toPoint('12,34') // { x: 12, y: 34 }
218+
```
219+
220+
### lonlat.toString(input)
221+
222+
Translates to coordinate string.
223+
224+
#### Arguments
225+
226+
`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
227+
228+
#### Returns
229+
230+
`(string)`: A string in the format 'latitude,longitude'
231+
232+
#### Example
233+
234+
```js
235+
var lonlat = require('@conveyal/lonlat')
236+
237+
var str = lonlat.toString({ lat: 12, long: 34 }) // '12,34'
238+
```
239+
240+
### lonlat.toLeaflet(input)
241+
242+
Translates to [Leaflet LatLng](http://leafletjs.com/reference.html#latlng) object. This function requires Leaflet to be installed as a global variable `L` in the window environment.
243+
244+
#### Arguments
245+
246+
`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
247+
248+
#### Returns
249+
250+
`(Object)`: A [Leaflet LatLng](http://leafletjs.com/reference.html#latlng) object
251+
252+
#### Example
253+
254+
```js
255+
var lonlat = require('@conveyal/lonlat')
256+
257+
var position = lonlat.toLeaflet({ lat: 12, long: 34 }) // Leaflet LatLng object
258+
```
259+
260+
58261
[npm-image]: https://img.shields.io/npm/v/@conveyal/lonlat.svg?maxAge=2592000&style=flat-square
59262
[npm-url]: https://www.npmjs.com/package/@conveyal/lonlat
60263
[travis-image]: https://img.shields.io/travis/conveyal/lonlat.svg?style=flat-square

__snapshots__/index.test.js.snap

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
exports[`lonlat errors invalid coordinates should throw error when parsing: "-999,999" 1`] = `"Invalid longitude value: -999"`;
2+
3+
exports[`lonlat errors invalid coordinates should throw error when parsing: "0,999" 1`] = `"Invalid longitude value: 999"`;
4+
5+
exports[`lonlat errors invalid coordinates should throw error when parsing: {"lng":1,"latitude":1234} 1`] = `"Invalid longitude value: 1234"`;
6+
7+
exports[`lonlat errors invalid coordinates should throw error when parsing: {} 1`] = `"Invalid longitude value: undefined"`;
8+
9+
exports[`lonlat errors invalid coordinates should throw error when parsing: undefined 1`] = `"Value must not be null or undefined."`;
10+
11+
exports[`lonlat errors toLeaflet should throw error if leaflet is not loaded 1`] = `"Leaflet not found."`;

index.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,37 @@ function fromPoint (point) {
5151
}
5252

5353
function fromString (str) {
54-
const arr = str.split(',')
54+
var arr = str.split(',')
5555
return floatize({lon: arr[0], lat: arr[1]})
5656
}
5757

5858
function floatize (lonlat) {
59-
const lon = parseFloat(lonlat.lon || lonlat.lng || lonlat.longitude)
60-
return {lon: lon, lat: parseFloat(lonlat.lat || lonlat.latitude)}
59+
var lon = parseFloatWithAlternates([lonlat.lon, lonlat.lng, lonlat.longitude])
60+
var lat = parseFloatWithAlternates([lonlat.lat, lonlat.latitude])
61+
if ((!lon || lon > 180 || lon < -180) && lon !== 0) {
62+
throw new Error(`Invalid longitude value: ${lonlat.lon || lonlat.lng || lonlat.longitude}`)
63+
}
64+
if ((!lat || lat > 90 || lat < -90) && lat !== 0) {
65+
throw new Error(`Invalid longitude value: ${lonlat.lat || lonlat.latitude}`)
66+
}
67+
return {lon: lon, lat: lat}
68+
}
69+
70+
function parseFloatWithAlternates (alternates) {
71+
if (alternates.length > 0) {
72+
var num = parseFloat(alternates[0])
73+
if (isNaN(num)) {
74+
return parseFloatWithAlternates(alternates.slice(1))
75+
} else {
76+
return num
77+
}
78+
}
6179
}
6280

6381
function normalize (unknown) {
6482
if (!unknown) throw new Error('Value must not be null or undefined.')
6583
if (Array.isArray(unknown)) return fromCoordinates(unknown)
6684
else if (typeof unknown === 'string') return fromString(unknown)
67-
else if (unknown.x && unknown.y) return fromPoint(unknown)
85+
else if ((unknown.x || unknown.x === 0) && (unknown.y || unknown.y === 0)) return fromPoint(unknown)
6886
return floatize(unknown)
6987
}

0 commit comments

Comments
 (0)