Skip to content

Commit

Permalink
Merge pull request #4 from conveyal/improvements
Browse files Browse the repository at this point in the history
Improvements
  • Loading branch information
trevorgerhardt authored Dec 23, 2016
2 parents 34b25b5 + 457eb5d commit 27af7f7
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 72 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ lib-cov

# Coverage directory used by tools like istanbul
coverage
tmp

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
Expand Down
6 changes: 6 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ notifications:
email: false
node_js:
- '6'
before_install:
- npm i -g codecov
script:
- npm run lint
- npm run cover
- codecov
after_success:
- npm run semantic-release
branches:
Expand Down
271 changes: 237 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,54 +7,257 @@ Lon/lat normalization cause...**sigh**.

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.

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

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:
### lonlat(input)

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

If you must convert it to a string, put it in the following format:
#### Arguments

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

## API
#### Returns

`(Object)`: An object with `lon` and `lat` attributes.

#### Example

```js
var lonlat = require('@conveyal/lonlat')

// Object with lon/lat-ish attributes
var position = lonlat({ lon: 12, lat: 34 }) // { lon: 12, lat: 34 }
position = lonlat({ lng: 12, lat: 34 }) // { lon: 12, lat: 34 }
position = lonlat({ longitude: 12, latitude: 34 }) // { lon: 12, lat: 34 }
position = lonlat({ lng: 12, latitude: 34 }) // { lon: 12, lat: 34 }

// coordinate array
position = lonlat([12, 34]) // { lon: 12, lat: 34 }

// string
position = lonlat('12,34') // { lon: 12, lat: 34 }

// object with x and y attributes
position = lonlat({ x: 12, y: 34 }) // { lon: 12, lat: 34 }

// the following will throw errors
position = lonlat({ lon: 999, lat: 34 }) // Error: Invalid longitude value: 999
position = lonlat({ lon: 12, lat: 999 }) // Error: Invalid latitude value: 999
position = lonlat({}) // Error: Invalid latitude value: undefined
position = lonlat(null) // Error: Value must not be null or undefined
```

### lonlat.fromCoordinates(arr) or lonlat.fromGeoJSON(arr)

Tries to parse from an array of coordinates. Will throw an error upon finding invalid coordinates.

#### Arguments

`arr (Array)`: An array in the format: [longitude, latitude]

#### Returns

`(Object)`: An object with `lon` and `lat` attributes.

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var position = lonlat.fromCoordinates([12, 34]) // { lon: 12, lat: 34 }
position = lonlat.fromGeoJSON([12, 34]) // { lon: 12, lat: 34 }
```

### lonlat.fromLatlng(obj) or lonlat.fromLeaflet(obj)

Tries to parse from an object. Will throw an error upon finding invalid coordinates.

#### Arguments

`obj (Object)`: An object with a `lon`, `lng` or `longitude` attribute and a `lat` or `latitude` attribute

#### Returns

`(Object)`: An object with `lon` and `lat` attributes.

#### Example

```js
const assert = require('assert')
const ll = require('@conveyal/lonlat')
var lonlat = require('@conveyal/lonlat')

var position = lonlat.fromLatlng({ longitude: 12, latitude: 34 }) // { lon: 12, lat: 34 }
position = lonlat.fromLeaflet({ lng: 12, lat: 34 }) // { lon: 12, lat: 34 }
```

### lonlat.fromPoint(obj)

const lat = 38.13234
const lon = 70.01232
const lonlat = {lon, lat}
const point = {x: lon, y: lat}
const coordinates = [lon, lat]
const str = `${lon},${lat}`
const latlng = {lat, lng: lon}
Tries to parse from an object. Will throw an error upon finding invalid coordinates.

const pairs = [
// normalization
[lonlat, ll(lonlat)],
[lonlat, ll(point)],
[lonlat, ll(coordinates)],
[lonlat, ll(str)],
#### Arguments

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

// if the type is known, use the specific convert function directly
[lonlat, ll.fromLatlng(latlng)],
[lonlat, ll.fromCoordinates(coordinates)],
[lonlat, ll.fromPoint(point)],
[lonlat, ll.fromString(str)]
]
#### Returns

`(Object)`: An object with `lon` and `lat` attributes.

#### Example

```js
var lonlat = require('@conveyal/lonlat')

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

### lonlat.fromString(str)

Tries to parse from a string. Will throw an error upon finding invalid coordinates.

#### Arguments

`str (string)`: A string in the format: '{longitude},{latitude}'

#### Returns

`(Object)`: An object with `lon` and `lat` attributes.

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var position = lonlat.fromString('12,34') // { lon: 12, lat: 34 }
```

### lonlat.print(input, [fixed=5])

Returns a pretty string

#### Arguments

- `input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
- `[fixed=5] (Number)`: The number of digits to round to

#### Returns

`(string)`: A string with the latitude and longitude rounded to the number of decimal places as specified by `fixed`

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var pretty = lonlat.print('12.345678,34') // '12.34568, 34.00000'
```

### lonlat.isEqual(lonlat1, lonlat2, [epsilon=0])

Checks equality of two inputs within an allowable difference.

#### Arguments

- `lonlat1 (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
- `lonlat2 (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)
- `[epsilon=0] (Number)`: The maximum allowable difference of between each latitude and longitude

#### Returns

`(boolean)`: Returns `true` if the inputs are equal or `false` if they are not

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var isEqual = lonlat.isEqual('12,34', [12, 34]) // true
```

### lonlat.toCoordinates(input)

Translates to a coordinate array.

#### Arguments

`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)

#### Returns

`(Array)`: An array in the format: [longitude, latitude]

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var coords = lonlat.toCoordinates('12,34') // [12, 34]
```

### lonlat.toPoint(input)

Translates to point Object.

#### Arguments

`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)

#### Returns

`(Object)`: An object with `x` and `y` attributes representing latitude and longitude respectively

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var point = lonlat.toPoint('12,34') // { x: 12, y: 34 }
```

### lonlat.toString(input)

Translates to coordinate string.

#### Arguments

`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)

#### Returns

`(string)`: A string in the format 'latitude,longitude'

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var str = lonlat.toString({ lat: 12, long: 34 }) // '12,34'
```

### lonlat.toLeaflet(input)

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.

#### Arguments

`input (*)`: Any format mentioned in [lonlat(input)](#lonlatinput)

#### Returns

`(Object)`: A [Leaflet LatLng](http://leafletjs.com/reference.html#latlng) object

#### Example

```js
var lonlat = require('@conveyal/lonlat')

var position = lonlat.toLeaflet({ lat: 12, long: 34 }) // Leaflet LatLng object
```


[npm-image]: https://img.shields.io/npm/v/@conveyal/lonlat.svg?maxAge=2592000&style=flat-square
[npm-url]: https://www.npmjs.com/package/@conveyal/lonlat
[travis-image]: https://img.shields.io/travis/conveyal/lonlat.svg?style=flat-square
Expand Down
11 changes: 11 additions & 0 deletions __snapshots__/index.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
exports[`lonlat errors invalid coordinates should throw error when parsing: "-999,999" 1`] = `"Invalid longitude value: -999"`;

exports[`lonlat errors invalid coordinates should throw error when parsing: "0,999" 1`] = `"Invalid longitude value: 999"`;

exports[`lonlat errors invalid coordinates should throw error when parsing: {"lng":1,"latitude":1234} 1`] = `"Invalid longitude value: 1234"`;

exports[`lonlat errors invalid coordinates should throw error when parsing: {} 1`] = `"Invalid longitude value: undefined"`;

exports[`lonlat errors invalid coordinates should throw error when parsing: undefined 1`] = `"Value must not be null or undefined."`;

exports[`lonlat errors toLeaflet should throw error if leaflet is not loaded 1`] = `"Leaflet not found."`;
26 changes: 22 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,37 @@ function fromPoint (point) {
}

function fromString (str) {
const arr = str.split(',')
var arr = str.split(',')
return floatize({lon: arr[0], lat: arr[1]})
}

function floatize (lonlat) {
const lon = parseFloat(lonlat.lon || lonlat.lng || lonlat.longitude)
return {lon: lon, lat: parseFloat(lonlat.lat || lonlat.latitude)}
var lon = parseFloatWithAlternates([lonlat.lon, lonlat.lng, lonlat.longitude])
var lat = parseFloatWithAlternates([lonlat.lat, lonlat.latitude])
if ((!lon || lon > 180 || lon < -180) && lon !== 0) {
throw new Error(`Invalid longitude value: ${lonlat.lon || lonlat.lng || lonlat.longitude}`)
}
if ((!lat || lat > 90 || lat < -90) && lat !== 0) {
throw new Error(`Invalid longitude value: ${lonlat.lat || lonlat.latitude}`)
}
return {lon: lon, lat: lat}
}

function parseFloatWithAlternates (alternates) {
if (alternates.length > 0) {
var num = parseFloat(alternates[0])
if (isNaN(num)) {
return parseFloatWithAlternates(alternates.slice(1))
} else {
return num
}
}
}

function normalize (unknown) {
if (!unknown) throw new Error('Value must not be null or undefined.')
if (Array.isArray(unknown)) return fromCoordinates(unknown)
else if (typeof unknown === 'string') return fromString(unknown)
else if (unknown.x && unknown.y) return fromPoint(unknown)
else if ((unknown.x || unknown.x === 0) && (unknown.y || unknown.y === 0)) return fromPoint(unknown)
return floatize(unknown)
}
Loading

0 comments on commit 27af7f7

Please sign in to comment.