Significantly improved line simplification performance

This commit is contained in:
mourner 2012-02-03 12:07:06 +02:00
parent 5d9c4f08fc
commit 1cc885056b
2 changed files with 64 additions and 52 deletions

View File

@ -18,6 +18,7 @@ Leaflet Changelog
* Map now preserves its center after resize. * Map now preserves its center after resize.
* When panning to another copy of the world (that's infinite horizontally), map overlays now jump to corresponding positions. [#273](https://github.com/CloudMade/Leaflet/issues/273) * When panning to another copy of the world (that's infinite horizontally), map overlays now jump to corresponding positions. [#273](https://github.com/CloudMade/Leaflet/issues/273)
* Limited maximum zoom change on a single mouse wheel movement (so you won't zoom across the whole zoom range in one scroll). [#149](https://github.com/CloudMade/Leaflet/issues/149) * Limited maximum zoom change on a single mouse wheel movement (so you won't zoom across the whole zoom range in one scroll). [#149](https://github.com/CloudMade/Leaflet/issues/149)
* Significantly improved line simplification performance (noticeable when rendering polylines/polygons with tens of thousands of points)
* Improved circles performance by not drawing them if they're off the clip region. * Improved circles performance by not drawing them if they're off the clip region.
#### API improvements #### API improvements

View File

@ -13,72 +13,80 @@ L.LineUtil = {
return points.slice(); return points.slice();
} }
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction // stage 1: vertex reduction
points = this.reducePoints(points, tolerance); points = this._reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification // stage 2: Douglas-Peucker simplification
points = this.simplifyDP(points, tolerance); points = this._simplifyDP(points, sqTolerance);
return points; return points;
}, },
// distance from a point to a segment between two points // distance from a point to a segment between two points
pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
return Math.sqrt(this._sqPointToSegmentDist(p, p1, p2)); return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
}, },
closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) { closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
var point = this._sqClosestPointOnSegment(p, p1, p2); return this._sqClosestPointOnSegment(p, p1, p2);
point.distance = Math.sqrt(point._sqDist);
return point;
}, },
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
simplifyDP: function (points, tol) { _simplifyDP: function (points, sqTolerance) {
var maxDist2 = 0,
index = 0,
t2 = tol * tol,
len = points.length,
i, dist2;
if (len < 3) { var len = points.length,
return points; ArrayConstructor = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
} markers = new ArrayConstructor(len);
for (i = 0; i < len - 1; i++) { markers[0] = markers[len - 1] = 1;
dist2 = this._sqPointToSegmentDist(points[i], points[0], points[len - 1]);
if (dist2 > maxDist2) { this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
index = i;
maxDist2 = dist2; var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
} }
} }
var part1, part2; return newPoints;
},
if (maxDist2 >= t2) { _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
part1 = points.slice(0, index);
part2 = points.slice(index);
part1 = this.simplifyDP(part1, tol); var maxSqDist = 0,
part2 = this.simplifyDP(part2, tol); index, i, sqDist;
return part1.concat(part2); for (i = first + 1; i <= last - 1; i++) {
} else { sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
return [points[0], points[len - 1]];
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
this._simplifyDPStep(points, markers, sqTolerance, first, index);
this._simplifyDPStep(points, markers, sqTolerance, index, last);
} }
}, },
// reduce points that are too close to each other to a single point // reduce points that are too close to each other to a single point
reducePoints: function (points, tol) { _reducePoints: function (points, sqTolerance) {
var reducedPoints = [points[0]], var reducedPoints = [points[0]];
t2 = tol * tol;
for (var i = 1, prev = 0, len = points.length; i < len; i++) { for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (this._sqDist(points[i], points[prev]) < t2) { if (this._sqDist(points[i], points[prev]) > sqTolerance) {
continue; reducedPoints.push(points[i]);
prev = i;
} }
reducedPoints.push(points[i]);
prev = i;
} }
if (prev < len - 1) { if (prev < len - 1) {
reducedPoints.push(points[len - 1]); reducedPoints.push(points[len - 1]);
@ -168,27 +176,30 @@ L.LineUtil = {
return dx * dx + dy * dy; return dx * dx + dy * dy;
}, },
// return closest point on segment with attribute _sqDist - square distance to segment // return closest point on segment or distance to that point
_sqClosestPointOnSegment: function (p, p1, p2) { _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
var x2 = p2.x - p1.x, var x = p1.x,
y2 = p2.y - p1.y, y = p1.y,
apoint = p1; dx = p2.x - x,
if (x2 || y2) { dy = p2.y - y,
var dot = (p.x - p1.x) * x2 + (p.y - p1.y) * y2, dot = dx * dx + dy * dy,
t = dot / this._sqDist(p1, p2); t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) { if (t > 1) {
apoint = p2; x = p2.x;
y = p2.y;
} else if (t > 0) { } else if (t > 0) {
apoint = new L.Point(p1.x + x2 * t, p1.y + y2 * t); x += dx * t;
y += dy * t;
} }
} }
apoint._sqDist = this._sqDist(p, apoint);
return apoint;
},
// distance from a point to a segment between two points dx = p.x - x;
_sqPointToSegmentDist: function (p, p1, p2) { dy = p.y - y;
return this._sqClosestPointOnSegment(p, p1, p2)._sqDist;
return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
} }
}; };