UNPKG

6.69 kBJavaScriptView Raw
1import {Point} from './Point';
2import * as Util from '../core/Util';
3
4
5/*
6 * @namespace LineUtil
7 *
8 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
9 */
10
11// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
12// Improves rendering performance dramatically by lessening the number of points to draw.
13
14// @function simplify(points: Point[], tolerance: Number): Point[]
15// Dramatically reduces the number of points in a polyline while retaining
16// its shape and returns a new array of simplified points, using the
17// [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
18// Used for a huge performance boost when processing/displaying Leaflet polylines for
19// each zoom level and also reducing visual noise. tolerance affects the amount of
20// simplification (lesser value means higher quality but slower and with more points).
21// Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
22export function simplify(points, tolerance) {
23 if (!tolerance || !points.length) {
24 return points.slice();
25 }
26
27 var sqTolerance = tolerance * tolerance;
28
29 // stage 1: vertex reduction
30 points = _reducePoints(points, sqTolerance);
31
32 // stage 2: Douglas-Peucker simplification
33 points = _simplifyDP(points, sqTolerance);
34
35 return points;
36}
37
38// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
39// Returns the distance between point `p` and segment `p1` to `p2`.
40export function pointToSegmentDistance(p, p1, p2) {
41 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
42}
43
44// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
45// Returns the closest point from a point `p` on a segment `p1` to `p2`.
46export function closestPointOnSegment(p, p1, p2) {
47 return _sqClosestPointOnSegment(p, p1, p2);
48}
49
50// Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
51function _simplifyDP(points, sqTolerance) {
52
53 var len = points.length,
54 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
55 markers = new ArrayConstructor(len);
56
57 markers[0] = markers[len - 1] = 1;
58
59 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
60
61 var i,
62 newPoints = [];
63
64 for (i = 0; i < len; i++) {
65 if (markers[i]) {
66 newPoints.push(points[i]);
67 }
68 }
69
70 return newPoints;
71}
72
73function _simplifyDPStep(points, markers, sqTolerance, first, last) {
74
75 var maxSqDist = 0,
76 index, i, sqDist;
77
78 for (i = first + 1; i <= last - 1; i++) {
79 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
80
81 if (sqDist > maxSqDist) {
82 index = i;
83 maxSqDist = sqDist;
84 }
85 }
86
87 if (maxSqDist > sqTolerance) {
88 markers[index] = 1;
89
90 _simplifyDPStep(points, markers, sqTolerance, first, index);
91 _simplifyDPStep(points, markers, sqTolerance, index, last);
92 }
93}
94
95// reduce points that are too close to each other to a single point
96function _reducePoints(points, sqTolerance) {
97 var reducedPoints = [points[0]];
98
99 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
100 if (_sqDist(points[i], points[prev]) > sqTolerance) {
101 reducedPoints.push(points[i]);
102 prev = i;
103 }
104 }
105 if (prev < len - 1) {
106 reducedPoints.push(points[len - 1]);
107 }
108 return reducedPoints;
109}
110
111var _lastCode;
112
113// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
114// Clips the segment a to b by rectangular bounds with the
115// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
116// (modifying the segment points directly!). Used by Leaflet to only show polyline
117// points that are on the screen or near, increasing performance.
118export function clipSegment(a, b, bounds, useLastCode, round) {
119 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
120 codeB = _getBitCode(b, bounds),
121
122 codeOut, p, newCode;
123
124 // save 2nd code to avoid calculating it on the next segment
125 _lastCode = codeB;
126
127 while (true) {
128 // if a,b is inside the clip window (trivial accept)
129 if (!(codeA | codeB)) {
130 return [a, b];
131 }
132
133 // if a,b is outside the clip window (trivial reject)
134 if (codeA & codeB) {
135 return false;
136 }
137
138 // other cases
139 codeOut = codeA || codeB;
140 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
141 newCode = _getBitCode(p, bounds);
142
143 if (codeOut === codeA) {
144 a = p;
145 codeA = newCode;
146 } else {
147 b = p;
148 codeB = newCode;
149 }
150 }
151}
152
153export function _getEdgeIntersection(a, b, code, bounds, round) {
154 var dx = b.x - a.x,
155 dy = b.y - a.y,
156 min = bounds.min,
157 max = bounds.max,
158 x, y;
159
160 if (code & 8) { // top
161 x = a.x + dx * (max.y - a.y) / dy;
162 y = max.y;
163
164 } else if (code & 4) { // bottom
165 x = a.x + dx * (min.y - a.y) / dy;
166 y = min.y;
167
168 } else if (code & 2) { // right
169 x = max.x;
170 y = a.y + dy * (max.x - a.x) / dx;
171
172 } else if (code & 1) { // left
173 x = min.x;
174 y = a.y + dy * (min.x - a.x) / dx;
175 }
176
177 return new Point(x, y, round);
178}
179
180export function _getBitCode(p, bounds) {
181 var code = 0;
182
183 if (p.x < bounds.min.x) { // left
184 code |= 1;
185 } else if (p.x > bounds.max.x) { // right
186 code |= 2;
187 }
188
189 if (p.y < bounds.min.y) { // bottom
190 code |= 4;
191 } else if (p.y > bounds.max.y) { // top
192 code |= 8;
193 }
194
195 return code;
196}
197
198// square distance (to avoid unnecessary Math.sqrt calls)
199function _sqDist(p1, p2) {
200 var dx = p2.x - p1.x,
201 dy = p2.y - p1.y;
202 return dx * dx + dy * dy;
203}
204
205// return closest point on segment or distance to that point
206export function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
207 var x = p1.x,
208 y = p1.y,
209 dx = p2.x - x,
210 dy = p2.y - y,
211 dot = dx * dx + dy * dy,
212 t;
213
214 if (dot > 0) {
215 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
216
217 if (t > 1) {
218 x = p2.x;
219 y = p2.y;
220 } else if (t > 0) {
221 x += dx * t;
222 y += dy * t;
223 }
224 }
225
226 dx = p.x - x;
227 dy = p.y - y;
228
229 return sqDist ? dx * dx + dy * dy : new Point(x, y);
230}
231
232
233// @function isFlat(latlngs: LatLng[]): Boolean
234// Returns true if `latlngs` is a flat array, false is nested.
235export function isFlat(latlngs) {
236 return !Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
237}
238
239export function _flat(latlngs) {
240 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
241 return isFlat(latlngs);
242}