/* * Utility functions to decode/encode numbers and array's of numbers * to/from strings (Google maps polyline encoding) * * Extends the L.Polyline and L.Polygon object with methods to convert * to and create from these strings. * * Jan Pieter Waagmeester * * Original code from: * http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/ * (which is down as of december 2014) */ (function () { 'use strict'; var defaultOptions = function (options) { if (typeof options === 'number') { // Legacy options = { precision: options }; } else { options = options || {}; } options.precision = options.precision || 5; options.factor = options.factor || Math.pow(10, options.precision); options.dimension = options.dimension || 2; return options; }; var PolylineUtil = { encode: function (points, options) { options = defaultOptions(options); var flatPoints = []; for (var i = 0, len = points.length; i < len; ++i) { var point = points[i]; if (options.dimension === 2) { flatPoints.push(point.lat || point[0]); flatPoints.push(point.lng || point[1]); } else { for (var dim = 0; dim < options.dimension; ++dim) { flatPoints.push(point[dim]); } } } return this.encodeDeltas(flatPoints, options); }, decode: function (encoded, options) { options = defaultOptions(options); var flatPoints = this.decodeDeltas(encoded, options); var points = []; for (var i = 0, len = flatPoints.length; i + (options.dimension - 1) < len;) { var point = []; for (var dim = 0; dim < options.dimension; ++dim) { point.push(flatPoints[i++]); } points.push(point); } return points; }, encodeDeltas: function (numbers, options) { options = defaultOptions(options); var lastNumbers = []; for (var i = 0, len = numbers.length; i < len;) { for (var d = 0; d < options.dimension; ++d, ++i) { var num = numbers[i].toFixed(options.precision); var delta = num - (lastNumbers[d] || 0); lastNumbers[d] = num; numbers[i] = delta; } } return this.encodeFloats(numbers, options); }, decodeDeltas: function (encoded, options) { options = defaultOptions(options); var lastNumbers = []; var numbers = this.decodeFloats(encoded, options); for (var i = 0, len = numbers.length; i < len;) { for (var d = 0; d < options.dimension; ++d, ++i) { numbers[i] = Math.round((lastNumbers[d] = numbers[i] + (lastNumbers[d] || 0)) * options.factor) / options.factor; } } return numbers; }, encodeFloats: function (numbers, options) { options = defaultOptions(options); for (var i = 0, len = numbers.length; i < len; ++i) { numbers[i] = Math.round(numbers[i] * options.factor); } return this.encodeSignedIntegers(numbers); }, decodeFloats: function (encoded, options) { options = defaultOptions(options); var numbers = this.decodeSignedIntegers(encoded); for (var i = 0, len = numbers.length; i < len; ++i) { numbers[i] /= options.factor; } return numbers; }, encodeSignedIntegers: function (numbers) { for (var i = 0, len = numbers.length; i < len; ++i) { var num = numbers[i]; numbers[i] = (num < 0) ? ~(num << 1) : (num << 1); } return this.encodeUnsignedIntegers(numbers); }, decodeSignedIntegers: function (encoded) { var numbers = this.decodeUnsignedIntegers(encoded); for (var i = 0, len = numbers.length; i < len; ++i) { var num = numbers[i]; numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); } return numbers; }, encodeUnsignedIntegers: function (numbers) { var encoded = ''; for (var i = 0, len = numbers.length; i < len; ++i) { encoded += this.encodeUnsignedInteger(numbers[i]); } return encoded; }, decodeUnsignedIntegers: function (encoded) { var numbers = []; var current = 0; var shift = 0; for (var i = 0, len = encoded.length; i < len; ++i) { var b = encoded.charCodeAt(i) - 63; current |= (b & 0x1f) << shift; if (b < 0x20) { numbers.push(current); current = 0; shift = 0; } else { shift += 5; } } return numbers; }, encodeSignedInteger: function (num) { num = (num < 0) ? ~(num << 1) : (num << 1); return this.encodeUnsignedInteger(num); }, // This function is very similar to Google's, but I added // some stuff to deal with the double slash issue. encodeUnsignedInteger: function (num) { var value, encoded = ''; while (num >= 0x20) { value = (0x20 | (num & 0x1f)) + 63; encoded += (String.fromCharCode(value)); num >>= 5; } value = num + 63; encoded += (String.fromCharCode(value)); return encoded; } }; // Export Node module if (typeof module === 'object' && typeof module.exports === 'object') { module.exports = PolylineUtil; } // Inject functionality into Leaflet if (typeof L === 'object') { if (!(L.Polyline.prototype.fromEncoded)) { L.Polyline.fromEncoded = function (encoded, options) { return L.polyline(PolylineUtil.decode(encoded), options); }; } if (!(L.Polygon.prototype.fromEncoded)) { L.Polygon.fromEncoded = function (encoded, options) { return L.polygon(PolylineUtil.decode(encoded), options); }; } var encodeMixin = { encodePath: function () { return PolylineUtil.encode(this.getLatLngs()); } }; if (!L.Polyline.prototype.encodePath) { L.Polyline.include(encodeMixin); } if (!L.Polygon.prototype.encodePath) { L.Polygon.include(encodeMixin); } L.PolylineUtil = PolylineUtil; } })();