159 lines
4.5 KiB
JavaScript
159 lines
4.5 KiB
JavaScript
/*
|
|
* L.LineUtil contains different utility functions for line segments
|
|
* and polylines (clipping, simplification, distances, etc.)
|
|
*/
|
|
|
|
L.LineUtil = {
|
|
/*
|
|
* Simplify polyline with vertex reduction and Douglas-Peucker simplification.
|
|
* Improves rendering performance dramatically by lessening the number of points to draw.
|
|
*/
|
|
simplify: function(/*Point[]*/ points, /*Number*/ tolerance) {
|
|
if (!tolerance) return points.slice();
|
|
|
|
// stage 1: vertex reduction
|
|
points = this.reducePoints(points, tolerance);
|
|
|
|
// stage 2: Douglas-Peucker simplification
|
|
points = this.simplifyDP(points, tolerance);
|
|
|
|
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));
|
|
},
|
|
|
|
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
|
|
simplifyDP: function(points, tol) {
|
|
var maxDist2 = 0,
|
|
index = 0,
|
|
t2 = tol * tol;
|
|
|
|
for (var i = 1, len = points.length, dist2; i < len - 1; i++) {
|
|
dist2 = this._sqPointToSegmentDist(points[i], points[0], points[len - 1]);
|
|
if (dist2 > maxDist2) {
|
|
index = i;
|
|
maxDist2 = dist2;
|
|
}
|
|
}
|
|
|
|
if (maxDist2 >= t2) {
|
|
var part1 = points.slice(0, index),
|
|
part2 = points.slice(index),
|
|
simplifiedPart1 = this.simplifyDP(part1, tol).slice(0, len - 2),
|
|
simplifiedPart2 = this.simplifyDP(part2, tol);
|
|
|
|
return simplifiedPart1.concat(simplifiedPart2);
|
|
} else {
|
|
return [points[0], points[len - 1]];
|
|
}
|
|
},
|
|
|
|
// reduce points that are too close to each other to a single point
|
|
reducePoints: function(points, tol) {
|
|
var reducedPoints = [points[0]],
|
|
t2 = tol * tol;
|
|
|
|
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
|
|
if (this._sqDist(points[i], points[prev]) < t2) continue;
|
|
reducedPoints.push(points[i]);
|
|
prev = i;
|
|
}
|
|
if (prev < len - 1) {
|
|
reducedPoints.push(points[len - 1]);
|
|
}
|
|
return reducedPoints;
|
|
},
|
|
|
|
/*
|
|
* Cohen-Sutherland line clipping algorithm.
|
|
* Used to avoid rendering parts of a polyline that are not currently visible.
|
|
*/
|
|
clipSegment: function(a, b, bounds, useLastCode) {
|
|
var min = bounds.min,
|
|
max = bounds.max;
|
|
|
|
var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
|
|
codeB = this._getBitCode(b, bounds);
|
|
|
|
// save 2nd code to avoid calculating it on the next segment
|
|
this._lastCode = codeB;
|
|
|
|
while (true) {
|
|
// if a,b is inside the clip window (trivial accept)
|
|
if (!(codeA | codeB)) {
|
|
return [a, b];
|
|
// if a,b is outside the clip window (trivial reject)
|
|
} else if (codeA & codeB) {
|
|
return false;
|
|
// other cases
|
|
} else {
|
|
var codeOut = codeA || codeB,
|
|
p = this._getEdgeIntersection(a, b, codeOut, bounds),
|
|
newCode = this._getBitCode(p, bounds);
|
|
|
|
if (codeOut == codeA) {
|
|
a = p;
|
|
codeA = newCode;
|
|
} else {
|
|
b = p;
|
|
codeB = newCode;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_getEdgeIntersection: function(a, b, code, bounds) {
|
|
var dx = b.x - a.x,
|
|
dy = b.y - a.y,
|
|
min = bounds.min,
|
|
max = bounds.max;
|
|
|
|
if (code & 8) { // top
|
|
return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
|
|
} else if (code & 4) { // bottom
|
|
return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
|
|
} else if (code & 2){ // right
|
|
return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
|
|
} else if (code & 1) { // left
|
|
return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
|
|
}
|
|
},
|
|
|
|
_getBitCode: function(/*Point*/ p, bounds) {
|
|
var code = 0;
|
|
|
|
if (p.x < bounds.min.x) code |= 1; // left
|
|
else if (p.x > bounds.max.x) code |= 2; // right
|
|
if (p.y < bounds.min.y) code |= 4; // bottom
|
|
else if (p.y > bounds.max.y) code |= 8; // top
|
|
|
|
return code;
|
|
},
|
|
|
|
// square distance (to avoid unnecessary Math.sqrt calls)
|
|
_sqDist: function(p1, p2) {
|
|
var dx = p2.x - p1.x,
|
|
dy = p2.y - p1.y;
|
|
return dx * dx + dy * dy;
|
|
},
|
|
|
|
// square distance from point to a segment
|
|
_sqPointToSegmentDist: function(p, p1, p2) {
|
|
var x2 = p2.x - p1.x,
|
|
y2 = p2.y - p1.y;
|
|
|
|
if (!x2 && !y2) return this._sqDist(p, p1);
|
|
|
|
var dot = (p.x - p1.x) * x2 + (p.y - p1.y) * y2,
|
|
t = dot / this._sqDist(p1, p2);
|
|
|
|
if (t < 0) return this._sqDist(p, p1);
|
|
if (t > 1) return this._sqDist(p, p2);
|
|
|
|
var proj = new L.Point(p1.x + x2 * t, p1.y + y2 * t);
|
|
return this._sqDist(p, proj);
|
|
}
|
|
}; |