Merge pull request #914 from Turbo87/encoded-polyline

EncodedPolyline: Backported ol3 polyline parser library
This commit is contained in:
ahocevar
2013-03-22 06:12:52 -07:00
2 changed files with 615 additions and 75 deletions

View File

@@ -62,15 +62,16 @@ OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, {
else if (this.geometryType != "point" && this.geometryType != "polygon")
return null;
var points = this.decode(encoded, 2);
var pointGeometries = new Array();
for (var i in points) {
var point = points[i];
pointGeometries.push(
new OpenLayers.Geometry.Point(point[1] * 1e-5, point[0] * 1e-5)
);
var flatPoints = this.decodeDeltas(encoded, 2);
var flatPointsLength = flatPoints.length;
var pointGeometries = [];
for (var i = 0; i + 1 < flatPointsLength;) {
var y = flatPoints[i++], x = flatPoints[i++];
pointGeometries.push(new OpenLayers.Geometry.Point(x, y));
}
if (this.geometryType == "point")
return new OpenLayers.Feature.Vector(
pointGeometries[0]
@@ -102,29 +103,18 @@ OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, {
* coordinates.
*/
decode: function(encoded, dims) {
var points = new Array();
var point = new Array(dims);
var flatPoints = this.decodeDeltas(encoded, dims, 1);
var flatPointsLength = flatPoints.length;
// Reset the point array
for (var i = 0; i < point.length; ++i)
point[i] = 0;
var points = [];
for (var i = 0; i + (dims - 1) < flatPointsLength;) {
var point = [];
for (var i = 0; i < encoded.length;) {
for (var dim = 0; dim < dims; ++dim) {
var result = 0;
var shift = 0;
var b;
do {
b = encoded.charCodeAt(i++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
point[dim] += ((result & 1) ? ~(result >> 1) : (result >> 1));
point.push(flatPoints[i++])
}
points.push(point.slice(0));
points.push(point);
}
return points;
@@ -163,16 +153,16 @@ OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, {
else
return null;
var points = new Array();
for (var i in pointGeometries) {
var flatPoints = [];
var pointGeometriesLength = pointGeometries.length;
for (var i = 0; i < pointGeometriesLength; ++i) {
var pointGeometry = pointGeometries[i];
var point = [Math.round(pointGeometry.y * 1e5),
Math.round(pointGeometry.x * 1e5)];
points.push(point);
flatPoints.push(pointGeometry.y);
flatPoints.push(pointGeometry.x);
}
var result = this.encode(points, 2);
return result;
return this.encodeDeltas(flatPoints, 2);
},
/**
@@ -188,66 +178,377 @@ OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, {
* {String} An encoded string
*/
encode: function (points, dims) {
var encoded_points = "";
var flatPoints = [];
var lastPoint = new Array(dims);
for (var i = 0; i < lastPoint.length; ++i)
lastPoint[i] = 0;
for (var i = 0; i < points.length; i++) {
var pointsLength = points.length;
for (var i = 0; i < pointsLength; ++i) {
var point = points[i];
for (var dim = 0; dim < lastPoint.length; ++dim) {
var delta = point[dim] - lastPoint[dim];
encoded_points += this.encodeSignedNumber(delta);
for (var dim = 0; dim < dims; ++dim) {
flatPoints.push(point[dim]);
}
lastPoint = point;
}
return encoded_points;
return this.encodeDeltas(flatPoints, dims, 1);
},
/**
* Method: encodeSignedNumber
* APIMethod: encodeDeltas
* Encode a list of n-dimensional points and return an encoded string
*
* Attention: This function will modify the passed array!
*
* Parameters:
* numbers - {Array.<number>} A list of n-dimensional points.
* dimension - {number} The dimension of the points in the list.
* opt_factor - {number=} The factor by which the numbers will be
* multiplied. The remaining decimal places will get rounded away.
*
* Returns:
* {string} The encoded string.
*/
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 this.encodeFloats(numbers, factor);
},
/**
* APIMethod: decodeDeltas
* Decode a list of n-dimensional points from an encoded string
*
* Parameters:
* encoded - {string} An encoded string.
* dimension - {number} The dimension of the points in the encoded string.
* opt_factor - {number=} The factor by which the resulting numbers will
* be divided.
*
* Returns:
* {Array.<number>} A list of n-dimensional points.
*/
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 = this.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;
},
/**
* APIMethod: encodeFloats
* Encode a list of floating point numbers and return an encoded string
*
* Attention: This function will modify the passed array!
*
* Parameters:
* numbers - {Array.<number>} A list of floating point numbers.
* opt_factor - {number=} The factor by which the numbers will be
* multiplied. The remaining decimal places will get rounded away.
*
* Returns:
* {string} The encoded string.
*/
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 this.encodeSignedIntegers(numbers);
},
/**
* APIMethod: decodeFloats
* Decode a list of floating point numbers from an encoded string
*
* Parameters:
* encoded - {string} An encoded string.
* opt_factor - {number=} The factor by which the result will be divided.
*
* Returns:
* {Array.<number>} A list of floating point numbers.
*/
decodeFloats: function(encoded, opt_factor) {
var factor = opt_factor || 1e5;
var numbers = this.decodeSignedIntegers(encoded);
var numbersLength = numbers.length;
for (var i = 0; i < numbersLength; ++i) {
numbers[i] /= factor;
}
return numbers;
},
/**
* APIMethod: encodeSignedIntegers
* Encode a list of signed integers and return an encoded string
*
* Attention: This function will modify the passed array!
*
* Parameters:
* numbers - {Array.<number>} A list of signed integers.
*
* Returns:
* {string} The encoded string.
*/
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 this.encodeUnsignedIntegers(numbers);
},
/**
* APIMethod: decodeSignedIntegers
* Decode a list of signed integers from an encoded string
*
* Parameters:
* encoded - {string} An encoded string.
*
* Returns:
* {Array.<number>} A list of signed integers.
*/
decodeSignedIntegers: function(encoded) {
var numbers = this.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;
},
/**
* APIMethod: encodeUnsignedIntegers
* Encode a list of unsigned integers and return an encoded string
*
* Parameters:
* numbers - {Array.<number>} A list of unsigned integers.
*
* Returns:
* {string} The encoded string.
*/
encodeUnsignedIntegers: function(numbers) {
var encoded = '';
var numbersLength = numbers.length;
for (var i = 0; i < numbersLength; ++i) {
encoded += this.encodeUnsignedInteger(numbers[i]);
}
return encoded;
},
/**
* APIMethod: decodeUnsignedIntegers
* Decode a list of unsigned integers from an encoded string
*
* Parameters:
* encoded - {string} An encoded string.
*
* Returns:
* {Array.<number>} A list of unsigned integers.
*/
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;
},
/**
* Method: encodeFloat
* Encode one single floating point number and return an encoded string
*
* Parameters:
* num - {number} Floating point number that should be encoded.
* opt_factor - {number=} The factor by which num will be multiplied.
* The remaining decimal places will get rounded away.
*
* Returns:
* {string} The encoded string.
*/
encodeFloat: function(num, opt_factor) {
num = Math.round(num * (opt_factor || 1e5));
return this.encodeSignedInteger(num);
},
/**
* Method: decodeFloat
* Decode one single floating point number from an encoded string
*
* Parameters:
* encoded - {string} An encoded string.
* opt_factor - {number=} The factor by which the result will be divided.
*
* Returns:
* {number} The decoded floating point number.
*/
decodeFloat: function(encoded, opt_factor) {
var result = this.decodeSignedInteger(encoded);
return result / (opt_factor || 1e5);
},
/**
* Method: encodeSignedInteger
* Encode one single signed integer and return an encoded string
*
* Parameters:
* num - {int} A signed integer that should be encoded
* num - {number} Signed integer that should be encoded.
*
* Returns:
* {String} An encoded string
* {string} The encoded string.
*/
encodeSignedNumber: function (num) {
var sgn_num = num << 1;
if (num < 0)
sgn_num = ~(sgn_num);
encodeSignedInteger: function(num) {
var signedNum = num << 1;
if (num < 0) {
signedNum = ~(signedNum);
}
return this.encodeNumber(sgn_num);
return this.encodeUnsignedInteger(signedNum);
},
/**
* Method: encodeNumber
* Encode one single unsigned integer and return an encoded string
*
* encodeSignedNumber should be used instead of using this method directly!
* Method: decodeSignedInteger
* Decode one single signed integer from an encoded string
*
* Parameters:
* num - {int} An unsigned integer that should be encoded
* encoded - {string} An encoded string.
*
* Returns:
* {String} An encoded string
* {number} The decoded signed integer.
*/
encodeNumber: function (num) {
var encodeString = "";
var value;
while (num >= 0x20) {
value = (0x20 | (num & 0x1f)) + 63;
encodeString += (String.fromCharCode(value));
num >>= 5;
}
value = num + 63;
encodeString += (String.fromCharCode(value));
return encodeString;
decodeSignedInteger: function(encoded) {
var result = this.decodeUnsignedInteger(encoded);
return ((result & 1) ? ~(result >> 1) : (result >> 1));
},
/**
* Method: encodeUnsignedInteger
* Encode one single unsigned integer and return an encoded string
*
* Parameters:
* num - {number} Unsigned integer that should be encoded.
*
* Returns:
* {string} The encoded string.
*/
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;
},
/**
* Method: decodeUnsignedInteger
* Decode one single unsigned integer from an encoded string
*
* Parameters:
* encoded - {string} An encoded string.
*
* Returns:
* {number} The decoded unsigned integer.
*/
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;
},
CLASS_NAME: "OpenLayers.Format.EncodedPolyline"

View File

@@ -3,6 +3,27 @@
<script src="../OLLoader.js"></script>
<script type="text/javascript">
var flatPoints;
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];
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';
}
var basePoints = new Array(
new Array(3850000, -12020000),
new Array(4070000, -12095000),
@@ -10,12 +31,9 @@
);
var points = [
new OpenLayers.Geometry.Point(basePoints[0][1] * 1e-5,
basePoints[0][0] * 1e-5),
new OpenLayers.Geometry.Point(basePoints[1][1] * 1e-5,
basePoints[1][0] * 1e-5),
new OpenLayers.Geometry.Point(basePoints[2][1] * 1e-5,
basePoints[2][0] * 1e-5)
new OpenLayers.Geometry.Point(-120.200, 38.500),
new OpenLayers.Geometry.Point(-120.950, 40.700),
new OpenLayers.Geometry.Point(-126.453, 43.252)
];
var singlePoint = new OpenLayers.Feature.Vector(points[0]);
@@ -126,6 +144,227 @@
t.eq(format.encode(basePoints, 2), encoded);
}
function test_encodeDeltas_returns_expected_value(t) {
t.plan(1);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeDeltas(flatPoints, 2), encoded);
}
function test_decodeDeltas_returns_expected_value(t) {
t.plan(1);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeDeltas(encoded, 2), flatPoints);
}
function test_encodeFloats_returns_expected_value(t) {
t.plan(3);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeFloats(smallFloats), encodedFloats);
resetTestingData();
t.eq(format.encodeFloats(smallFloats, 1e5), encodedFloats);
t.eq(format.encodeFloats(floats, 1e2), encodedFloats);
}
function test_decodeFloats_returns_expected_value(t) {
t.plan(3);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeFloats(encodedFloats), smallFloats);
t.eq(format.decodeFloats(encodedFloats, 1e5), smallFloats);
t.eq(format.decodeFloats(encodedFloats, 1e2), floats);
}
function test_encodeSignedIntegers_returns_expected_value(t) {
t.plan(1);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeSignedIntegers(
signedIntegers), encodedSignedIntegers);
}
function test_decodeSignedIntegers_returns_expected_value(t) {
t.plan(1);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeSignedIntegers(
encodedSignedIntegers), signedIntegers);
}
function test_encodeUnsignedIntegers_returns_expected_value(t) {
t.plan(1);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeUnsignedIntegers(
unsignedIntegers), encodedUnsignedIntegers);
}
function test_decodeUnsignedIntegers_returns_expected_value(t) {
t.plan(1);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeUnsignedIntegers(
encodedUnsignedIntegers), unsignedIntegers);
}
function test_encodeFloat_returns_expected_value(t) {
t.plan(12);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeFloat(0.00000), '?');
t.eq(format.encodeFloat(-0.00001), '@');
t.eq(format.encodeFloat(0.00001), 'A');
t.eq(format.encodeFloat(-0.00002), 'B');
t.eq(format.encodeFloat(0.00002), 'C');
t.eq(format.encodeFloat(0.00015), ']');
t.eq(format.encodeFloat(-0.00016), '^');
t.eq(format.encodeFloat(-0.1, 10), '@');
t.eq(format.encodeFloat(0.1, 10), 'A');
t.eq(format.encodeFloat(16 * 32 / 1e5), '__@');
t.eq(format.encodeFloat(16 * 32 * 32 / 1e5), '___@');
// from the "Encoded Polyline Algorithm Format" page at Google
t.eq(format.encodeFloat(-179.9832104), '`~oia@');
}
function test_decodeFloat_returns_expected_value(t) {
t.plan(12);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeFloat('?'), 0.00000);
t.eq(format.decodeFloat('@'), -0.00001);
t.eq(format.decodeFloat('A'), 0.00001);
t.eq(format.decodeFloat('B'), -0.00002);
t.eq(format.decodeFloat('C'), 0.00002);
t.eq(format.decodeFloat(']'), 0.00015);
t.eq(format.decodeFloat('^'), -0.00016);
t.eq(format.decodeFloat('@', 10), -0.1);
t.eq(format.decodeFloat('A', 10), 0.1);
t.eq(format.decodeFloat('__@'), 16 * 32 / 1e5);
t.eq(format.decodeFloat('___@'), 16 * 32 * 32 / 1e5);
// from the "Encoded Polyline Algorithm Format" page at Google
t.eq(format.decodeFloat('`~oia@'), -179.98321);
}
function test_encodeSignedInteger_returns_expected_value(t) {
t.plan(10);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeSignedInteger(0), '?');
t.eq(format.encodeSignedInteger(-1), '@');
t.eq(format.encodeSignedInteger(1), 'A');
t.eq(format.encodeSignedInteger(-2), 'B');
t.eq(format.encodeSignedInteger(2), 'C');
t.eq(format.encodeSignedInteger(15), ']');
t.eq(format.encodeSignedInteger(-16), '^');
t.eq(format.encodeSignedInteger(16), '_@');
t.eq(format.encodeSignedInteger(16 * 32), '__@');
t.eq(format.encodeSignedInteger(16 * 32 * 32), '___@');
}
function test_decodeSignedInteger_returns_expected_value(t) {
t.plan(10);
resetTestingData();
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeSignedInteger('?'), 0);
t.eq(format.decodeSignedInteger('@'), -1);
t.eq(format.decodeSignedInteger('A'), 1);
t.eq(format.decodeSignedInteger('B'), -2);
t.eq(format.decodeSignedInteger('C'), 2);
t.eq(format.decodeSignedInteger(']'), 15);
t.eq(format.decodeSignedInteger('^'), -16);
t.eq(format.decodeSignedInteger('__@'), 16 * 32);
t.eq(format.decodeSignedInteger('___@'), 16 * 32 * 32);
t.eq(format.decodeSignedInteger('_@'), 16);
}
function test_encodeUnsignedInteger_returns_expected_value(t) {
t.plan(10);
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.encodeUnsignedInteger(0), '?');
t.eq(format.encodeUnsignedInteger(1), '@');
t.eq(format.encodeUnsignedInteger(2), 'A');
t.eq(format.encodeUnsignedInteger(30), ']');
t.eq(format.encodeUnsignedInteger(31), '^');
t.eq(format.encodeUnsignedInteger(32), '_@');
t.eq(format.encodeUnsignedInteger(32 * 32), '__@');
t.eq(format.encodeUnsignedInteger(5 * 32 * 32), '__D');
t.eq(format.encodeUnsignedInteger(32 * 32 * 32), '___@');
// from the "Encoded Polyline Algorithm Format" page at Google
t.eq(format.encodeUnsignedInteger(174), 'mD');
}
function test_decodeUnsignedInteger_returns_expected_value(t) {
t.plan(10);
var format = new OpenLayers.Format.EncodedPolyline();
t.eq(format.decodeUnsignedInteger('?'), 0);
t.eq(format.decodeUnsignedInteger('@'), 1);
t.eq(format.decodeUnsignedInteger('A'), 2);
t.eq(format.decodeUnsignedInteger(']'), 30);
t.eq(format.decodeUnsignedInteger('^'), 31);
t.eq(format.decodeUnsignedInteger('_@'), 32);
t.eq(format.decodeUnsignedInteger('__@'), 32 * 32);
t.eq(format.decodeUnsignedInteger('__D'), 5 * 32 * 32);
t.eq(format.decodeUnsignedInteger('___@'), 32 * 32 * 32);
// from the "Encoded Polyline Algorithm Format" page at Google
t.eq(format.decodeUnsignedInteger('mD'), 174);
}
</script>
</head>
<body>