Significantly improved line simplification performance
This commit is contained in:
parent
5d9c4f08fc
commit
1cc885056b
@ -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
|
||||||
|
@ -13,73 +13,81 @@ 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);
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < len - 1; i++) {
|
return newPoints;
|
||||||
dist2 = this._sqPointToSegmentDist(points[i], points[0], points[len - 1]);
|
},
|
||||||
if (dist2 > maxDist2) {
|
|
||||||
|
_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
|
||||||
|
|
||||||
|
var maxSqDist = 0,
|
||||||
|
index, i, sqDist;
|
||||||
|
|
||||||
|
for (i = first + 1; i <= last - 1; i++) {
|
||||||
|
sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
|
||||||
|
|
||||||
|
if (sqDist > maxSqDist) {
|
||||||
index = i;
|
index = i;
|
||||||
maxDist2 = dist2;
|
maxSqDist = sqDist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var part1, part2;
|
if (maxSqDist > sqTolerance) {
|
||||||
|
markers[index] = 1;
|
||||||
|
|
||||||
if (maxDist2 >= t2) {
|
this._simplifyDPStep(points, markers, sqTolerance, first, index);
|
||||||
part1 = points.slice(0, index);
|
this._simplifyDPStep(points, markers, sqTolerance, index, last);
|
||||||
part2 = points.slice(index);
|
|
||||||
|
|
||||||
part1 = this.simplifyDP(part1, tol);
|
|
||||||
part2 = this.simplifyDP(part2, tol);
|
|
||||||
|
|
||||||
return part1.concat(part2);
|
|
||||||
} else {
|
|
||||||
return [points[0], points[len - 1]];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 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]);
|
reducedPoints.push(points[i]);
|
||||||
prev = 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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user