Merge pull request #1376 from twpayne/vector-api-simplify-schaub
[vector-api] Schaub simplification
This commit is contained in:
@@ -143,10 +143,10 @@ ol.geom.MultiPolygon.prototype.getSimplifiedGeometryInternal =
|
|||||||
function(squaredTolerance) {
|
function(squaredTolerance) {
|
||||||
var simplifiedFlatCoordinates = [];
|
var simplifiedFlatCoordinates = [];
|
||||||
var simplifiedEndss = [];
|
var simplifiedEndss = [];
|
||||||
simplifiedFlatCoordinates.length =
|
simplifiedFlatCoordinates.length = ol.geom.simplify.quantizess(
|
||||||
ol.geom.simplify.douglasPeuckerss(this.flatCoordinates, 0,
|
this.flatCoordinates, 0, this.endss_, this.stride,
|
||||||
this.endss_, this.stride, squaredTolerance, simplifiedFlatCoordinates,
|
Math.sqrt(squaredTolerance),
|
||||||
0, simplifiedEndss);
|
simplifiedFlatCoordinates, 0, simplifiedEndss);
|
||||||
var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null);
|
var simplifiedMultiPolygon = new ol.geom.MultiPolygon(null);
|
||||||
simplifiedMultiPolygon.setFlatCoordinates(
|
simplifiedMultiPolygon.setFlatCoordinates(
|
||||||
ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss);
|
ol.geom.GeometryLayout.XY, simplifiedFlatCoordinates, simplifiedEndss);
|
||||||
|
|||||||
@@ -157,8 +157,9 @@ ol.geom.Polygon.prototype.getSimplifiedGeometryInternal =
|
|||||||
function(squaredTolerance) {
|
function(squaredTolerance) {
|
||||||
var simplifiedFlatCoordinates = [];
|
var simplifiedFlatCoordinates = [];
|
||||||
var simplifiedEnds = [];
|
var simplifiedEnds = [];
|
||||||
simplifiedFlatCoordinates.length = ol.geom.simplify.douglasPeuckers(
|
simplifiedFlatCoordinates.length = ol.geom.simplify.quantizes(
|
||||||
this.flatCoordinates, 0, this.ends_, this.stride, squaredTolerance,
|
this.flatCoordinates, 0, this.ends_, this.stride,
|
||||||
|
Math.sqrt(squaredTolerance),
|
||||||
simplifiedFlatCoordinates, 0, simplifiedEnds);
|
simplifiedFlatCoordinates, 0, simplifiedEnds);
|
||||||
var simplifiedPolygon = new ol.geom.Polygon(null);
|
var simplifiedPolygon = new ol.geom.Polygon(null);
|
||||||
simplifiedPolygon.setFlatCoordinates(
|
simplifiedPolygon.setFlatCoordinates(
|
||||||
|
|||||||
@@ -233,3 +233,167 @@ ol.geom.simplify.radialDistance = function(flatCoordinates, offset, end,
|
|||||||
}
|
}
|
||||||
return simplifiedOffset;
|
return simplifiedOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} value Value.
|
||||||
|
* @param {number} tolerance Squared tolerance.
|
||||||
|
* @return {number} Rounded value.
|
||||||
|
*/
|
||||||
|
ol.geom.simplify.snap = function(value, tolerance) {
|
||||||
|
return tolerance * Math.round(value / tolerance);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplifies a line string using an algorithm designed by Tim Schaub.
|
||||||
|
* Coordinates are snapped to the nearest value in a virtual grid and
|
||||||
|
* consecutive duplicate coordinates are discarded. This effectively preserves
|
||||||
|
* topology as the simplification of any subsection of a line string is
|
||||||
|
* independent of the rest of the line string. This means that, for examples,
|
||||||
|
* the common edge between two polygons will be simplified to the same line
|
||||||
|
* string independently in both polygons. This implementation uses a single
|
||||||
|
* pass over the coordinates and eliminates intermediate collinear points.
|
||||||
|
* @param {Array.<number>} flatCoordinates Flat coordinates.
|
||||||
|
* @param {number} offset Offset.
|
||||||
|
* @param {number} end End.
|
||||||
|
* @param {number} stride Stride.
|
||||||
|
* @param {number} tolerance Squared tolerance.
|
||||||
|
* @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
|
||||||
|
* coordinates.
|
||||||
|
* @param {number} simplifiedOffset Simplified offset.
|
||||||
|
* @return {number} Simplified offset.
|
||||||
|
*/
|
||||||
|
ol.geom.simplify.quantize = function(flatCoordinates, offset, end, stride,
|
||||||
|
tolerance, simplifiedFlatCoordinates, simplifiedOffset) {
|
||||||
|
// do nothing if the line is empty
|
||||||
|
if (offset == end) {
|
||||||
|
return simplifiedOffset;
|
||||||
|
}
|
||||||
|
// snap the first coordinate (P1)
|
||||||
|
var x1 = ol.geom.simplify.snap(flatCoordinates[offset], tolerance);
|
||||||
|
var y1 = ol.geom.simplify.snap(flatCoordinates[offset + 1], tolerance);
|
||||||
|
offset += stride;
|
||||||
|
// add the first coordinate to the output
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = x1;
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = y1;
|
||||||
|
// find the next coordinate that does not snap to the same value as the first
|
||||||
|
// coordinate (P2)
|
||||||
|
var x2, y2;
|
||||||
|
do {
|
||||||
|
x2 = ol.geom.simplify.snap(flatCoordinates[offset], tolerance);
|
||||||
|
y2 = ol.geom.simplify.snap(flatCoordinates[offset + 1], tolerance);
|
||||||
|
offset += stride;
|
||||||
|
if (offset == end) {
|
||||||
|
// all coordinates snap to the same value, the line collapses to a point
|
||||||
|
// push the last snapped value anyway to ensure that the output contains
|
||||||
|
// at least two points
|
||||||
|
// FIXME should we really return at least two points anyway?
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = x2;
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = y2;
|
||||||
|
return simplifiedOffset;
|
||||||
|
}
|
||||||
|
} while (x2 == x1 && y2 == y1);
|
||||||
|
while (offset < end) {
|
||||||
|
var x3, y3;
|
||||||
|
// snap the next coordinate (P3)
|
||||||
|
x3 = ol.geom.simplify.snap(flatCoordinates[offset], tolerance);
|
||||||
|
y3 = ol.geom.simplify.snap(flatCoordinates[offset + 1], tolerance);
|
||||||
|
offset += stride;
|
||||||
|
// skip P3 if it is equal to P2
|
||||||
|
if (x3 == x2 && y3 == y2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// calculate the delta between P1 and P2
|
||||||
|
var dx1 = x2 - x1;
|
||||||
|
var dy1 = y2 - y1;
|
||||||
|
// calculate the delta between P3 and P1
|
||||||
|
var dx2 = x3 - x1;
|
||||||
|
var dy2 = y3 - y1;
|
||||||
|
// if P1, P2, and P3 are colinear and P3 is further from P1 than P2 is from
|
||||||
|
// P1 in the same direction then P2 is on the straight line between P1 and
|
||||||
|
// P3
|
||||||
|
if ((dx1 * dy2 == dy1 * dx2) &&
|
||||||
|
((dx1 < 0 && dx2 < dx1) || dx1 == dx2 || (dx1 > 0 && dx2 > dx1)) &&
|
||||||
|
((dy1 < 0 && dy2 < dy1) || dy1 == dy2 || (dy1 > 0 && dy2 > dy1))) {
|
||||||
|
// discard P2 and set P2 = P3
|
||||||
|
x2 = x3;
|
||||||
|
y2 = y3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// either P1, P2, and P3 are not colinear, or they are colinear but P3 is
|
||||||
|
// between P3 and P1 or on the opposite half of the line to P2. add P2,
|
||||||
|
// and continue with P1 = P2 and P2 = P3
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = x2;
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = y2;
|
||||||
|
x1 = x2;
|
||||||
|
y1 = y2;
|
||||||
|
x2 = x3;
|
||||||
|
y2 = y3;
|
||||||
|
}
|
||||||
|
// add the last point (P2)
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = x2;
|
||||||
|
simplifiedFlatCoordinates[simplifiedOffset++] = y2;
|
||||||
|
return simplifiedOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array.<number>} flatCoordinates Flat coordinates.
|
||||||
|
* @param {number} offset Offset.
|
||||||
|
* @param {Array.<number>} ends Ends.
|
||||||
|
* @param {number} stride Stride.
|
||||||
|
* @param {number} tolerance Squared tolerance.
|
||||||
|
* @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
|
||||||
|
* coordinates.
|
||||||
|
* @param {number} simplifiedOffset Simplified offset.
|
||||||
|
* @param {Array.<number>} simplifiedEnds Simplified ends.
|
||||||
|
* @return {number} Simplified offset.
|
||||||
|
*/
|
||||||
|
ol.geom.simplify.quantizes = function(
|
||||||
|
flatCoordinates, offset, ends, stride,
|
||||||
|
tolerance,
|
||||||
|
simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds) {
|
||||||
|
var i, ii;
|
||||||
|
for (i = 0, ii = ends.length; i < ii; ++i) {
|
||||||
|
var end = ends[i];
|
||||||
|
simplifiedOffset = ol.geom.simplify.quantize(
|
||||||
|
flatCoordinates, offset, end, stride,
|
||||||
|
tolerance,
|
||||||
|
simplifiedFlatCoordinates, simplifiedOffset);
|
||||||
|
simplifiedEnds.push(simplifiedOffset);
|
||||||
|
offset = end;
|
||||||
|
}
|
||||||
|
return simplifiedOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array.<number>} flatCoordinates Flat coordinates.
|
||||||
|
* @param {number} offset Offset.
|
||||||
|
* @param {Array.<Array.<number>>} endss Endss.
|
||||||
|
* @param {number} stride Stride.
|
||||||
|
* @param {number} tolerance Squared tolerance.
|
||||||
|
* @param {Array.<number>} simplifiedFlatCoordinates Simplified flat
|
||||||
|
* coordinates.
|
||||||
|
* @param {number} simplifiedOffset Simplified offset.
|
||||||
|
* @param {Array.<Array.<number>>} simplifiedEndss Simplified endss.
|
||||||
|
* @return {number} Simplified offset.
|
||||||
|
*/
|
||||||
|
ol.geom.simplify.quantizess = function(
|
||||||
|
flatCoordinates, offset, endss, stride,
|
||||||
|
tolerance,
|
||||||
|
simplifiedFlatCoordinates, simplifiedOffset, simplifiedEndss) {
|
||||||
|
var i, ii;
|
||||||
|
for (i = 0, ii = endss.length; i < ii; ++i) {
|
||||||
|
var ends = endss[i];
|
||||||
|
var simplifiedEnds = [];
|
||||||
|
simplifiedOffset = ol.geom.simplify.quantizes(
|
||||||
|
flatCoordinates, offset, ends, stride,
|
||||||
|
tolerance,
|
||||||
|
simplifiedFlatCoordinates, simplifiedOffset, simplifiedEnds);
|
||||||
|
simplifiedEndss.push(simplifiedEnds);
|
||||||
|
offset = ends[ends.length - 1];
|
||||||
|
}
|
||||||
|
return simplifiedOffset;
|
||||||
|
};
|
||||||
|
|||||||
@@ -321,17 +321,17 @@ describe('ol.geom.Polygon', function() {
|
|||||||
describe('#getSimplifiedGeometry', function() {
|
describe('#getSimplifiedGeometry', function() {
|
||||||
|
|
||||||
it('returns the expected result', function() {
|
it('returns the expected result', function() {
|
||||||
var simplifiedGeometry = polygon.getSimplifiedGeometry(1);
|
var simplifiedGeometry = polygon.getSimplifiedGeometry(9);
|
||||||
expect(simplifiedGeometry).to.be.an(ol.geom.Polygon);
|
expect(simplifiedGeometry).to.be.an(ol.geom.Polygon);
|
||||||
expect(simplifiedGeometry.getCoordinates()).to.eql(
|
expect(simplifiedGeometry.getCoordinates()).to.eql(
|
||||||
[[[3, 0], [0, 6], [6, 6], [4, 3]]]);
|
[[[3, 0], [0, 3], [0, 6], [6, 6], [3, 3]]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('caches multiple simplified geometries', function() {
|
it('caches multiple simplified geometries', function() {
|
||||||
var simplifiedGeometry1 = polygon.getSimplifiedGeometry(1);
|
var simplifiedGeometry1 = polygon.getSimplifiedGeometry(4);
|
||||||
var simplifiedGeometry2 = polygon.getSimplifiedGeometry(2);
|
var simplifiedGeometry2 = polygon.getSimplifiedGeometry(9);
|
||||||
var simplifiedGeometry3 = polygon.getSimplifiedGeometry(1);
|
var simplifiedGeometry3 = polygon.getSimplifiedGeometry(4);
|
||||||
var simplifiedGeometry4 = polygon.getSimplifiedGeometry(2);
|
var simplifiedGeometry4 = polygon.getSimplifiedGeometry(9);
|
||||||
expect(simplifiedGeometry1).to.be(simplifiedGeometry3);
|
expect(simplifiedGeometry1).to.be(simplifiedGeometry3);
|
||||||
expect(simplifiedGeometry2).to.be(simplifiedGeometry4);
|
expect(simplifiedGeometry2).to.be(simplifiedGeometry4);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -287,6 +287,71 @@ describe('ol.geom.simplify', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ol.geom.simplify.quantize', function() {
|
||||||
|
|
||||||
|
it('handles empty coordinates', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[], 0, 0, 2, 2, simplifiedFlatCoordinates, 0)).to.be(0);
|
||||||
|
expect(simplifiedFlatCoordinates).to.be.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('expands points to a zero-length line', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0, 0, 0, 0], 0, 4, 2, 2, simplifiedFlatCoordinates, 0)).to.be(4);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 0, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('snaps near-by points to the same value', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0.1, 0, 0, 0.1], 0, 4, 2, 2, simplifiedFlatCoordinates, 0)).to.be(4);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 0, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('eliminates duplicate snapped points', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0.1, 0, 2, 0, 2.1, 0, 2, 0.1, 1.9, 0, 2, -0.1], 0, 12, 2, 2,
|
||||||
|
simplifiedFlatCoordinates, 0)).to.be(4);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 2, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('eliminates horizontal colinear points', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0, 0, 2, 0, 4, 0, 6, 0], 0, 8, 2, 2,
|
||||||
|
simplifiedFlatCoordinates, 0)).to.be(4);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 6, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('eliminates vertical colinear points', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0, 0, 0, -2, 0, -4, 0, -6], 0, 8, 2, 2,
|
||||||
|
simplifiedFlatCoordinates, 0)).to.be(4);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 0, -6]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('eliminates diagonal colinear points', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0, 0, 2, -2, 4, -4, 6, -6], 0, 8, 2, 2,
|
||||||
|
simplifiedFlatCoordinates, 0)).to.be(4);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 6, -6]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles switchbacks', function() {
|
||||||
|
var simplifiedFlatCoordinates = [];
|
||||||
|
expect(ol.geom.simplify.quantize(
|
||||||
|
[0, 0, 2, 0, 0, 0, 4, 0], 0, 8, 2, 2,
|
||||||
|
simplifiedFlatCoordinates, 0)).to.be(8);
|
||||||
|
expect(simplifiedFlatCoordinates).to.eql([0, 0, 2, 0, 0, 0, 4, 0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user