diff --git a/src/ol/parser/polyline.js b/src/ol/parser/polyline.js new file mode 100644 index 0000000000..c0793d0ccc --- /dev/null +++ b/src/ol/parser/polyline.js @@ -0,0 +1,335 @@ +goog.provide('ol.parser.polyline'); + + +/** + * Encode a list of coordinates in a flat array and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.} flatPoints A flat array of coordinates. + * @param {number=} opt_dimension The dimension of the coordinates in the array. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeFlatCoordinates = + function(flatPoints, opt_dimension) { + var dimension = opt_dimension || 2; + return ol.parser.polyline.encodeDeltas(flatPoints, dimension); +}; + + +/** + * Decode a list of coordinates from an encoded string into a flat array + * + * @param {string} encoded An encoded string. + * @param {number=} opt_dimension The dimension of the coordinates in the + * encoded string. + * @return {Array.} A flat array of coordinates. + */ +ol.parser.polyline.decodeFlatCoordinates = function(encoded, opt_dimension) { + var dimension = opt_dimension || 2; + return ol.parser.polyline.decodeDeltas(encoded, dimension); +}; + + +/** + * Encode a list of n-dimensional points and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.} numbers A list of n-dimensional points. + * @param {number} dimension The dimension of the points in the list. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeDeltas = function(numbers, dimension, opt_factor) { + var factor = opt_factor || 1e5; + var d; + + var lastNumbers = new Array(dimension); + for (d = 0; d < dimension; ++d) { + lastNumbers[d] = 0; + } + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength;) { + for (d = 0; d < dimension; ++d, ++i) { + var num = numbers[i]; + var delta = num - lastNumbers[d]; + lastNumbers[d] = num; + + numbers[i] = delta; + } + } + + return ol.parser.polyline.encodeFloats(numbers, factor); +}; + + +/** + * Decode a list of n-dimensional points from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number} dimension The dimension of the points in the encoded string. + * @param {number=} opt_factor The factor by which the resulting numbers will + * be divided. + * @return {Array.} A list of n-dimensional points. + */ +ol.parser.polyline.decodeDeltas = function(encoded, dimension, opt_factor) { + var factor = opt_factor || 1e5; + var d; + + var lastNumbers = new Array(dimension); + for (d = 0; d < dimension; ++d) { + lastNumbers[d] = 0; + } + + var numbers = ol.parser.polyline.decodeFloats(encoded, factor); + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength;) { + for (d = 0; d < dimension; ++d, ++i) { + lastNumbers[d] += numbers[i]; + + numbers[i] = lastNumbers[d]; + } + } + + return numbers; +}; + + +/** + * Encode a list of floating point numbers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.} numbers A list of floating point numbers. + * @param {number=} opt_factor The factor by which the numbers will be + * multiplied. The remaining decimal places will get rounded away. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeFloats = function(numbers, opt_factor) { + var factor = opt_factor || 1e5; + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + numbers[i] = Math.round(numbers[i] * factor); + } + + return ol.parser.polyline.encodeSignedIntegers(numbers); +}; + + +/** + * Decode a list of floating point numbers from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number=} opt_factor The factor by which the result will be divided. + * @return {Array.} A list of floating point numbers. + */ +ol.parser.polyline.decodeFloats = function(encoded, opt_factor) { + var factor = opt_factor || 1e5; + + var numbers = ol.parser.polyline.decodeSignedIntegers(encoded); + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + numbers[i] /= factor; + } + + return numbers; +}; + + +/** + * Encode a list of signed integers and return an encoded string + * + * Attention: This function will modify the passed array! + * + * @param {Array.} numbers A list of signed integers. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeSignedIntegers = function(numbers) { + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + var num = numbers[i]; + + var signedNum = num << 1; + if (num < 0) { + signedNum = ~(signedNum); + } + + numbers[i] = signedNum; + } + + return ol.parser.polyline.encodeUnsignedIntegers(numbers); +}; + + +/** + * Decode a list of signed integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.} A list of signed integers. + */ +ol.parser.polyline.decodeSignedIntegers = function(encoded) { + var numbers = ol.parser.polyline.decodeUnsignedIntegers(encoded); + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + var num = numbers[i]; + numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1); + } + + return numbers; +}; + + +/** + * Encode a list of unsigned integers and return an encoded string + * + * @param {Array.} numbers A list of unsigned integers. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeUnsignedIntegers = function(numbers) { + var encoded = ''; + + var numbersLength = numbers.length; + for (var i = 0; i < numbersLength; ++i) { + encoded += ol.parser.polyline.encodeUnsignedInteger(numbers[i]); + } + + return encoded; +}; + + +/** + * Decode a list of unsigned integers from an encoded string + * + * @param {string} encoded An encoded string. + * @return {Array.} A list of unsigned integers. + */ +ol.parser.polyline.decodeUnsignedIntegers = function(encoded) { + var numbers = []; + + var current = 0; + var shift = 0; + + var encodedLength = encoded.length; + for (var i = 0; i < encodedLength; ++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; +}; + + +/** + * Encode one single floating point number and return an encoded string + * + * @param {number} num Floating point number that should be encoded. + * @param {number=} opt_factor The factor by which num will be multiplied. + * The remaining decimal places will get rounded away. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeFloat = function(num, opt_factor) { + num = Math.round(num * (opt_factor || 1e5)); + return ol.parser.polyline.encodeSignedInteger(num); +}; + + +/** + * Decode one single floating point number from an encoded string + * + * @param {string} encoded An encoded string. + * @param {number=} opt_factor The factor by which the result will be divided. + * @return {number} The decoded floating point number. + */ +ol.parser.polyline.decodeFloat = function(encoded, opt_factor) { + var result = ol.parser.polyline.decodeSignedInteger(encoded); + return result / (opt_factor || 1e5); +}; + + +/** + * Encode one single signed integer and return an encoded string + * + * @param {number} num Signed integer that should be encoded. + * @return {string} The encoded string. + */ +ol.parser.polyline.encodeSignedInteger = function(num) { + var signedNum = num << 1; + if (num < 0) { + signedNum = ~(signedNum); + } + + return ol.parser.polyline.encodeUnsignedInteger(signedNum); +}; + + +/** + * Decode one single signed integer from an encoded string + * + * @param {string} encoded An encoded string. + * @return {number} The decoded signed integer. + */ +ol.parser.polyline.decodeSignedInteger = function(encoded) { + var result = ol.parser.polyline.decodeUnsignedInteger(encoded); + return ((result & 1) ? ~(result >> 1) : (result >> 1)); +}; + + +/** + * Encode one single unsigned integer and return an encoded string + * + * @param {number} num Unsigned integer that should be encoded. + * @return {string} The encoded string. + */ +ol.parser.polyline.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; +}; + + +/** + * Decode one single unsigned integer from an encoded string + * + * @param {string} encoded An encoded string. + * @return {number} The decoded unsigned integer. + */ +ol.parser.polyline.decodeUnsignedInteger = function(encoded) { + var result = 0; + var shift = 0; + + var encodedLength = encoded.length; + for (var i = 0; i < encodedLength; ++i) { + var b = encoded.charCodeAt(i) - 63; + + result |= (b & 0x1f) << shift; + + if (b < 0x20) + break; + + shift += 5; + } + + return result; +}; diff --git a/test/spec/ol/parser/polyline.test.js b/test/spec/ol/parser/polyline.test.js new file mode 100644 index 0000000000..62aac783b7 --- /dev/null +++ b/test/spec/ol/parser/polyline.test.js @@ -0,0 +1,262 @@ +goog.provide('ol.test.parser.polyline'); + +describe('ol.parser.polyline', function() { + + var flatPoints, encodedFlatPoints; + var floats, smallFloats, encodedFloats; + var signedIntegers, encodedSignedIntegers; + var unsignedIntegers, encodedUnsignedIntegers; + + function resetTestingData() { + flatPoints = [38.50000, -120.20000, + 40.70000, -120.95000, + 43.25200, -126.45300]; + encodedFlatPoints = '_p~iF~ps|U_ulLnnqC_mqNvxq`@'; + + floats = [0.00, 0.15, -0.01, -0.16, 0.16, 0.01]; + smallFloats = [0.00000, 0.00015, -0.00001, -0.00016, 0.00016, 0.00001]; + encodedFloats = '?]@^_@A'; + + signedIntegers = [0, 15, -1, -16, 16, 1]; + encodedSignedIntegers = '?]@^_@A'; + + unsignedIntegers = [0, 30, 1, 31, 32, 2, 174]; + encodedUnsignedIntegers = '?]@^_@AmD'; + } + + // Reset testing data + beforeEach(resetTestingData); + + + + describe('encodeFlatCoordinates', function() { + it('returns expected value', function() { + var encodeFlatCoordinates = ol.parser.polyline.encodeFlatCoordinates; + + // from the "Encoded Polyline Algorithm Format" page at Google + expect(encodeFlatCoordinates(flatPoints)).toEqual(encodedFlatPoints); + }); + }); + + describe('decodeFlatCoordinates', function() { + it('returns expected value', function() { + var decodeFlatCoordinates = ol.parser.polyline.decodeFlatCoordinates; + + // from the "Encoded Polyline Algorithm Format" page at Google + expect(decodeFlatCoordinates(encodedFlatPoints)).toEqual(flatPoints); + }); + }); + + + + describe('encodeDeltas', function() { + it('returns expected value', function() { + var encodeDeltas = ol.parser.polyline.encodeDeltas; + + expect(encodeDeltas(flatPoints, 2)).toEqual(encodedFlatPoints); + }); + }); + + describe('decodeDeltas', function() { + it('returns expected value', function() { + var decodeDeltas = ol.parser.polyline.decodeDeltas; + + expect(decodeDeltas(encodedFlatPoints, 2)).toEqual(flatPoints); + }); + }); + + + + describe('encodeFloats', function() { + it('returns expected value', function() { + var encodeFloats = ol.parser.polyline.encodeFloats; + + expect(encodeFloats(smallFloats)).toEqual(encodedFloats); + + resetTestingData(); + expect(encodeFloats(smallFloats, 1e5)).toEqual(encodedFloats); + + expect(encodeFloats(floats, 1e2)).toEqual(encodedFloats); + }); + }); + + describe('decodeFloats', function() { + it('returns expected value', function() { + var decodeFloats = ol.parser.polyline.decodeFloats; + + expect(decodeFloats(encodedFloats)).toEqual(smallFloats); + expect(decodeFloats(encodedFloats, 1e5)).toEqual(smallFloats); + expect(decodeFloats(encodedFloats, 1e2)).toEqual(floats); + }); + }); + + + + describe('encodeSignedIntegers', function() { + it('returns expected value', function() { + var encodeSignedIntegers = ol.parser.polyline.encodeSignedIntegers; + + expect(encodeSignedIntegers( + signedIntegers)).toEqual(encodedSignedIntegers); + }); + }); + + describe('decodeSignedIntegers', function() { + it('returns expected value', function() { + var decodeSignedIntegers = ol.parser.polyline.decodeSignedIntegers; + + expect(decodeSignedIntegers( + encodedSignedIntegers)).toEqual(signedIntegers); + }); + }); + + + + describe('encodeUnsignedIntegers', function() { + it('returns expected value', function() { + var encodeUnsignedIntegers = ol.parser.polyline.encodeUnsignedIntegers; + + expect(encodeUnsignedIntegers( + unsignedIntegers)).toEqual(encodedUnsignedIntegers); + }); + }); + + describe('decodeUnsignedIntegers', function() { + it('returns expected value', function() { + var decodeUnsignedIntegers = ol.parser.polyline.decodeUnsignedIntegers; + + expect(decodeUnsignedIntegers( + encodedUnsignedIntegers)).toEqual(unsignedIntegers); + }); + }); + + + + describe('encodeFloat', function() { + it('returns expected value', function() { + var encodeFloat = ol.parser.polyline.encodeFloat; + + expect(encodeFloat(0.00000)).toEqual('?'); + expect(encodeFloat(-0.00001)).toEqual('@'); + expect(encodeFloat(0.00001)).toEqual('A'); + expect(encodeFloat(-0.00002)).toEqual('B'); + expect(encodeFloat(0.00002)).toEqual('C'); + expect(encodeFloat(0.00015)).toEqual(']'); + expect(encodeFloat(-0.00016)).toEqual('^'); + + expect(encodeFloat(-0.1, 10)).toEqual('@'); + expect(encodeFloat(0.1, 10)).toEqual('A'); + + expect(encodeFloat(16 * 32 / 1e5)).toEqual('__@'); + expect(encodeFloat(16 * 32 * 32 / 1e5)).toEqual('___@'); + + // from the "Encoded Polyline Algorithm Format" page at Google + expect(encodeFloat(-179.9832104)).toEqual('`~oia@'); + }); + }); + + describe('decodeFloat', function() { + it('returns expected value', function() { + var decodeFloat = ol.parser.polyline.decodeFloat; + + expect(decodeFloat('?')).toEqual(0.00000); + expect(decodeFloat('@')).toEqual(-0.00001); + expect(decodeFloat('A')).toEqual(0.00001); + expect(decodeFloat('B')).toEqual(-0.00002); + expect(decodeFloat('C')).toEqual(0.00002); + expect(decodeFloat(']')).toEqual(0.00015); + expect(decodeFloat('^')).toEqual(-0.00016); + + expect(decodeFloat('@', 10)).toEqual(-0.1); + expect(decodeFloat('A', 10)).toEqual(0.1); + + expect(decodeFloat('__@')).toEqual(16 * 32 / 1e5); + expect(decodeFloat('___@')).toEqual(16 * 32 * 32 / 1e5); + + // from the "Encoded Polyline Algorithm Format" page at Google + expect(decodeFloat('`~oia@')).toEqual(-179.98321); + }); + }); + + + + describe('encodeSignedInteger', function() { + it('returns expected value', function() { + var encodeSignedInteger = ol.parser.polyline.encodeSignedInteger; + + expect(encodeSignedInteger(0)).toEqual('?'); + expect(encodeSignedInteger(-1)).toEqual('@'); + expect(encodeSignedInteger(1)).toEqual('A'); + expect(encodeSignedInteger(-2)).toEqual('B'); + expect(encodeSignedInteger(2)).toEqual('C'); + expect(encodeSignedInteger(15)).toEqual(']'); + expect(encodeSignedInteger(-16)).toEqual('^'); + + expect(encodeSignedInteger(16)).toEqual('_@'); + expect(encodeSignedInteger(16 * 32)).toEqual('__@'); + expect(encodeSignedInteger(16 * 32 * 32)).toEqual('___@'); + }); + }); + + describe('decodeSignedInteger', function() { + it('returns expected value', function() { + var decodeSignedInteger = ol.parser.polyline.decodeSignedInteger; + + expect(decodeSignedInteger('?')).toEqual(0); + expect(decodeSignedInteger('@')).toEqual(-1); + expect(decodeSignedInteger('A')).toEqual(1); + expect(decodeSignedInteger('B')).toEqual(-2); + expect(decodeSignedInteger('C')).toEqual(2); + expect(decodeSignedInteger(']')).toEqual(15); + expect(decodeSignedInteger('^')).toEqual(-16); + + expect(decodeSignedInteger('_@')).toEqual(16); + expect(decodeSignedInteger('__@')).toEqual(16 * 32); + expect(decodeSignedInteger('___@')).toEqual(16 * 32 * 32); + }); + }); + + + + describe('encodeUnsignedInteger', function() { + it('returns expected value', function() { + var encodeUnsignedInteger = ol.parser.polyline.encodeUnsignedInteger; + + expect(encodeUnsignedInteger(0)).toEqual('?'); + expect(encodeUnsignedInteger(1)).toEqual('@'); + expect(encodeUnsignedInteger(2)).toEqual('A'); + expect(encodeUnsignedInteger(30)).toEqual(']'); + expect(encodeUnsignedInteger(31)).toEqual('^'); + expect(encodeUnsignedInteger(32)).toEqual('_@'); + + expect(encodeUnsignedInteger(32 * 32)).toEqual('__@'); + expect(encodeUnsignedInteger(5 * 32 * 32)).toEqual('__D'); + expect(encodeUnsignedInteger(32 * 32 * 32)).toEqual('___@'); + + // from the "Encoded Polyline Algorithm Format" page at Google + expect(encodeUnsignedInteger(174)).toEqual('mD'); + }); + }); + + describe('decodeUnsignedInteger', function() { + it('returns expected value', function() { + var decodeUnsignedInteger = ol.parser.polyline.decodeUnsignedInteger; + + expect(decodeUnsignedInteger('?')).toEqual(0); + expect(decodeUnsignedInteger('@')).toEqual(1); + expect(decodeUnsignedInteger('A')).toEqual(2); + expect(decodeUnsignedInteger(']')).toEqual(30); + expect(decodeUnsignedInteger('^')).toEqual(31); + expect(decodeUnsignedInteger('_@')).toEqual(32); + + expect(decodeUnsignedInteger('__@')).toEqual(32 * 32); + expect(decodeUnsignedInteger('__D')).toEqual(5 * 32 * 32); + expect(decodeUnsignedInteger('___@')).toEqual(32 * 32 * 32); + + // from the "Encoded Polyline Algorithm Format" page at Google + expect(decodeUnsignedInteger('mD')).toEqual(174); + }); + }); +}); + +goog.require('ol.parser.polyline');