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.
* 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)
* 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.
#### API improvements

View File

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