aboutsummaryrefslogtreecommitdiffstats
path: root/extlib/leaflet/src/geometry/LineUtil.js
blob: 72a80855bfef7d82cf1c318547baf6c09e3a70fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
 * 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);
	}	
};