geojson-polyline
Version:
Convert GeoJSON coordinates to and from encoded polylines. Supports all major GeoJSON types.
445 lines (397 loc) • 12.3 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.GeoJSONPolyline = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
var assign = require('object-assign')
var polyline = require('@mapbox/polyline')
var GeoJSONPolyline = module.exports = {
// Pick and choose your verbs
encode: geo2poly,
decode: poly2geo,
//
geojson: poly2geo,
geoJson: poly2geo,
geoJSON: poly2geo,
//
polyline: geo2poly,
polyLine: geo2poly,
//
toEncoded: geo2poly,
fromEncoded: poly2geo,
//
toGeoJSON: poly2geo,
fromGeoJSON: geo2poly,
//
toPolyline: geo2poly,
fromPolyline: poly2geo
}
/**
* Encodes a GeoJSON geometry as a polyline.
*
* @param {Object} geojson
* @param {Object} options
* @returns {Object} encoded geojson
*/
function geo2poly (geojson, options) {
var precision = typeof (options) === 'object' && options.precision
switch (geojson.type) {
// Coordinate arrays
case 'Point': {
return assign({}, geojson, {
coordinates: encode([geojson.coordinates], precision)
})
}
case 'MultiPoint':
case 'LineString': {
return assign({}, geojson, {
coordinates: encode(geojson.coordinates, precision)
})
}
// Arrays of coordinates
case 'MultiLineString':
case 'Polygon': {
return assign({}, geojson, {
coordinates: geojson.coordinates.map(function (coords) {
return encode(coords, precision)
})
})
}
case 'Feature': {
return assign({}, geojson, {
geometry: geo2poly(geojson.geometry, precision)
})
}
case 'FeatureCollection': {
return assign({}, geojson, {
features: geojson.features.map(function (feature) {
return geo2poly(feature, precision)
})
})
}
case 'GeometryCollection': {
return assign({}, geojson, {
geometries: geojson.geometries.map(function (geo) {
return geo2poly(geo, precision)
})
})
}
case 'MultiPolygon': {
return assign({}, geojson, {
coordinates: geojson.coordinates.map(function (polygons) {
return polygons.map(function (coords) {
return encode(coords, precision)
})
})
})
}
// Return the original object for unsupported types
default:
return geojson
}
}
/**
* Decode a polyline-encoded GeoJSON geometry.
*
* @param {Object} geojson
* @param {Object} options
* @returns {Object} decoded geojson
*/
function poly2geo (geojson, options) {
var precision = typeof (options) === 'object' && options.precision
switch (geojson.type) {
// Translate a single-element array back to a single coordinate array
case 'Point': {
return assign({}, geojson, {
coordinates: decode(geojson.coordinates, precision)[0]
})
}
// Decode into a coordinate array
case 'MultiPoint':
case 'LineString': {
return assign({}, geojson, {
coordinates: decode(geojson.coordinates, precision)
})
}
// Arrays of coordinate arrays
case 'MultiLineString':
case 'Polygon': {
return assign({}, geojson, {
coordinates: geojson.coordinates.map(function (coords) {
return decode(coords, precision)
})
})
}
// Arrays of polygons
case 'MultiPolygon': {
return assign({}, geojson, {
coordinates: geojson.coordinates.map(function (polygons) {
return polygons.map(function (coords) {
return decode(coords, precision)
})
})
})
}
// GeoJSON object is
case 'Feature': {
return assign({}, geojson, {
geometry: poly2geo(geojson.geometry, precision)
})
}
case 'FeatureCollection': {
return assign({}, geojson, {
features: geojson.features.map(function (feature) {
return poly2geo(feature, precision)
})
})
}
case 'GeometryCollection': {
return assign({}, geojson, {
geometries: geojson.geometries.map(function (geometry) {
return poly2geo(geometry, precision)
})
})
}
// Return the original object for unsupported types
default:
return geojson
}
}
function encode (coordinates, precision) {
return polyline.encode(flip(coordinates), precision)
}
function decode (str, precision) {
return flip(polyline.decode(str, precision))
}
function flip (coords) {
var flipped = []
for (var i = 0; i < coords.length; i++) {
flipped.push(coords[i].slice().reverse())
}
return flipped
}
if (typeof addEventListener !== 'undefined') {
/* global addEventListener, postMessage */
addEventListener('message', function (message) {
var method = message.data[0]
if (method in GeoJSONPolyline) {
var geojson = message.data[1]
var precision = message.data[2]
var converted = GeoJSONPolyline[method](geojson, precision)
postMessage(converted)
}
})
}
},{"@mapbox/polyline":2,"object-assign":3}],2:[function(require,module,exports){
;
/**
* Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)
*
* Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)
* by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)
*
* @module polyline
*/
var polyline = {};
function py2_round(value) {
// Google's polyline algorithm uses the same rounding strategy as Python 2, which is different from JS for negative values
return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);
}
function encode(current, previous, factor) {
current = py2_round(current * factor);
previous = py2_round(previous * factor);
var coordinate = current - previous;
coordinate <<= 1;
if (current - previous < 0) {
coordinate = ~coordinate;
}
var output = '';
while (coordinate >= 0x20) {
output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);
coordinate >>= 5;
}
output += String.fromCharCode(coordinate + 63);
return output;
}
/**
* Decodes to a [latitude, longitude] coordinates array.
*
* This is adapted from the implementation in Project-OSRM.
*
* @param {String} str
* @param {Number} precision
* @returns {Array}
*
* @see https://github.com/Project-OSRM/osrm-frontend/blob/master/WebContent/routing/OSRM.RoutingGeometry.js
*/
polyline.decode = function(str, precision) {
var index = 0,
lat = 0,
lng = 0,
coordinates = [],
shift = 0,
result = 0,
byte = null,
latitude_change,
longitude_change,
factor = Math.pow(10, precision || 5);
// Coordinates have variable length when encoded, so just keep
// track of whether we've hit the end of the string. In each
// loop iteration, a single coordinate is decoded.
while (index < str.length) {
// Reset shift, result, and byte
byte = null;
shift = 0;
result = 0;
do {
byte = str.charCodeAt(index++) - 63;
result |= (byte & 0x1f) << shift;
shift += 5;
} while (byte >= 0x20);
latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));
shift = result = 0;
do {
byte = str.charCodeAt(index++) - 63;
result |= (byte & 0x1f) << shift;
shift += 5;
} while (byte >= 0x20);
longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));
lat += latitude_change;
lng += longitude_change;
coordinates.push([lat / factor, lng / factor]);
}
return coordinates;
};
/**
* Encodes the given [latitude, longitude] coordinates array.
*
* @param {Array.<Array.<Number>>} coordinates
* @param {Number} precision
* @returns {String}
*/
polyline.encode = function(coordinates, precision) {
if (!coordinates.length) { return ''; }
var factor = Math.pow(10, precision || 5),
output = encode(coordinates[0][0], 0, factor) + encode(coordinates[0][1], 0, factor);
for (var i = 1; i < coordinates.length; i++) {
var a = coordinates[i], b = coordinates[i - 1];
output += encode(a[0], b[0], factor);
output += encode(a[1], b[1], factor);
}
return output;
};
function flipped(coords) {
var flipped = [];
for (var i = 0; i < coords.length; i++) {
flipped.push(coords[i].slice().reverse());
}
return flipped;
}
/**
* Encodes a GeoJSON LineString feature/geometry.
*
* @param {Object} geojson
* @param {Number} precision
* @returns {String}
*/
polyline.fromGeoJSON = function(geojson, precision) {
if (geojson && geojson.type === 'Feature') {
geojson = geojson.geometry;
}
if (!geojson || geojson.type !== 'LineString') {
throw new Error('Input must be a GeoJSON LineString');
}
return polyline.encode(flipped(geojson.coordinates), precision);
};
/**
* Decodes to a GeoJSON LineString geometry.
*
* @param {String} str
* @param {Number} precision
* @returns {Object}
*/
polyline.toGeoJSON = function(str, precision) {
var coords = polyline.decode(str, precision);
return {
type: 'LineString',
coordinates: flipped(coords)
};
};
if (typeof module === 'object' && module.exports) {
module.exports = polyline;
}
},{}],3:[function(require,module,exports){
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
;
/* eslint-disable no-unused-vars */
var getOwnPropertySymbols = Object.getOwnPropertySymbols;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;
function toObject(val) {
if (val === null || val === undefined) {
throw new TypeError('Object.assign cannot be called with null or undefined');
}
return Object(val);
}
function shouldUseNative() {
try {
if (!Object.assign) {
return false;
}
// Detect buggy property enumeration order in older V8 versions.
// https://bugs.chromium.org/p/v8/issues/detail?id=4118
var test1 = new String('abc'); // eslint-disable-line no-new-wrappers
test1[5] = 'de';
if (Object.getOwnPropertyNames(test1)[0] === '5') {
return false;
}
// https://bugs.chromium.org/p/v8/issues/detail?id=3056
var test2 = {};
for (var i = 0; i < 10; i++) {
test2['_' + String.fromCharCode(i)] = i;
}
var order2 = Object.getOwnPropertyNames(test2).map(function (n) {
return test2[n];
});
if (order2.join('') !== '0123456789') {
return false;
}
// https://bugs.chromium.org/p/v8/issues/detail?id=3056
var test3 = {};
'abcdefghijklmnopqrst'.split('').forEach(function (letter) {
test3[letter] = letter;
});
if (Object.keys(Object.assign({}, test3)).join('') !==
'abcdefghijklmnopqrst') {
return false;
}
return true;
} catch (err) {
// We don't expect any of the above to throw, but better to be safe.
return false;
}
}
module.exports = shouldUseNative() ? Object.assign : function (target, source) {
var from;
var to = toObject(target);
var symbols;
for (var s = 1; s < arguments.length; s++) {
from = Object(arguments[s]);
for (var key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}
if (getOwnPropertySymbols) {
symbols = getOwnPropertySymbols(from);
for (var i = 0; i < symbols.length; i++) {
if (propIsEnumerable.call(from, symbols[i])) {
to[symbols[i]] = from[symbols[i]];
}
}
}
}
return to;
};
},{}]},{},[1])(1)
});