bigmap-plugin 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1,754 @@
1
- var U=Object.defineProperty;var l=(s,t)=>U(s,"name",{value:t,configurable:!0});(function(s){s(window.BM)})(function(s){"use strict";return s.Polyline._flat=s.LineUtil.isFlat||s.Polyline._flat||function(t){return!s.Util.isArray(t[0])||typeof t[0][0]!="object"&&typeof t[0][0]<"u"},s.GeometryUtil=s.extend(s.GeometryUtil||{},{distance:l(function(t,e,i){return t.latLngToLayerPoint(e).distanceTo(t.latLngToLayerPoint(i))},"distance"),distanceSegment:l(function(t,e,i,n){var o=t.latLngToLayerPoint(e),r=t.latLngToLayerPoint(i),f=t.latLngToLayerPoint(n);return s.LineUtil.pointToSegmentDistance(o,r,f)},"distanceSegment"),readableDistance:l(function(t,e){var i=e!=="imperial",n;return i?t>1e3?n=(t/1e3).toFixed(2)+" km":n=Math.ceil(t)+" m":(t*=1.09361,t>1760?n=(t/1760).toFixed(2)+" miles":n=Math.ceil(t)+" yd"),n},"readableDistance"),belongsSegment:l(function(t,e,i,n){n=n===void 0?.2:n;var o=e.distanceTo(i),r=e.distanceTo(t)+t.distanceTo(i)-o;return r/o<n},"belongsSegment"),length:l(function(t){var e=s.GeometryUtil.accumulatedLengths(t);return e.length>0?e[e.length-1]:0},"length"),accumulatedLengths:l(function(t){if(typeof t.getLatLngs=="function"&&(t=t.getLatLngs()),t.length===0)return[];for(var e=0,i=[0],n=0,o=t.length-1;n<o;n++)e+=t[n].distanceTo(t[n+1]),i.push(e);return i},"accumulatedLengths"),closestOnSegment:l(function(t,e,i,n){var o=t.getMaxZoom();o===1/0&&(o=t.getZoom());var r=t.project(e,o),f=t.project(i,o),a=t.project(n,o),c=s.LineUtil.closestPointOnSegment(r,f,a);return t.unproject(c,o)},"closestOnSegment"),closest:l(function(t,e,i,n){var o,r=1/0,f=null,a,c,u,g;if(e instanceof Array)if(e[0]instanceof Array&&typeof e[0][0]!="number"){for(a=0;a<e.length;a++)g=s.GeometryUtil.closest(t,e[a],i,n),g.distance<r&&(r=g.distance,f=g);return f}else if(e[0]instanceof s.LatLng||typeof e[0][0]=="number"||typeof e[0].lat=="number")e=s.polyline(e);else return f;if(!(e instanceof s.Polyline))return f;if(o=JSON.parse(JSON.stringify(e.getLatLngs().slice(0))),e instanceof s.Polygon){var h=l(function(d){if(s.Polyline._flat(d))d.push(d[0]);else for(var m=0;m<d.length;m++)h(d[m])},"addLastSegment");h(o)}if(s.Polyline._flat(o)){if(n){for(a=0,c=o.length;a<c;a++){var L=o[a];u=s.GeometryUtil.distance(t,i,L),u<r&&(r=u,f=L,f.distance=u)}return f}for(a=0,c=o.length;a<c-1;a++){var v=o[a],y=o[a+1];u=s.GeometryUtil.distanceSegment(t,i,v,y),u<=r&&(r=u,f=s.GeometryUtil.closestOnSegment(t,i,v,y),f.distance=u)}return f}else{for(a=0;a<o.length;a++)g=s.GeometryUtil.closest(t,o[a],i,n),g.distance<r&&(r=g.distance,f=g);return f}},"closest"),closestLayer:l(function(t,e,i){for(var n=1/0,o=null,r=null,f=1/0,a=0,c=e.length;a<c;a++){var u=e[a];if(u instanceof s.LayerGroup){var g=s.GeometryUtil.closestLayer(t,u.getLayers(),i);g.distance<n&&(n=g.distance,o=g)}else typeof u.getLatLng=="function"?(r=u.getLatLng(),f=s.GeometryUtil.distance(t,i,r)):(r=s.GeometryUtil.closest(t,u,i),r&&(f=r.distance)),f<n&&(n=f,o={layer:u,latlng:r,distance:f})}return o},"closestLayer"),nClosestLayers:l(function(t,e,i,n){if(n=typeof n=="number"?n:e.length,n<1||e.length<1)return null;for(var o=[],r,f,a=0,c=e.length;a<c;a++){var u=e[a];if(u instanceof s.LayerGroup){var g=s.GeometryUtil.closestLayer(t,u.getLayers(),i);o.push(g)}else typeof u.getLatLng=="function"?(f=u.getLatLng(),r=s.GeometryUtil.distance(t,i,f)):(f=s.GeometryUtil.closest(t,u,i),f&&(r=f.distance)),o.push({layer:u,latlng:f,distance:r})}return o.sort(function(h,L){return h.distance-L.distance}),o.length>n?o.slice(0,n):o},"nClosestLayers"),layersWithin:l(function(t,e,i,n){n=typeof n=="number"?n:1/0;for(var o=[],r=null,f=0,a=0,c=e.length;a<c;a++){var u=e[a];typeof u.getLatLng=="function"?(r=u.getLatLng(),f=s.GeometryUtil.distance(t,i,r)):(r=s.GeometryUtil.closest(t,u,i),r&&(f=r.distance)),r&&f<n&&o.push({layer:u,latlng:r,distance:f})}var g=o.sort(function(h,L){return h.distance-L.distance});return g},"layersWithin"),closestLayerSnap:l(function(t,e,i,n,o){n=typeof n=="number"?n:1/0,o=typeof o=="boolean"?o:!0;var r=s.GeometryUtil.closestLayer(t,e,i);if(!r||r.distance>n)return null;if(o&&typeof r.layer.getLatLngs=="function"){var f=s.GeometryUtil.closest(t,r.layer,r.latlng,!0);f.distance<n&&(r.latlng=f,r.distance=s.GeometryUtil.distance(t,f,i))}return r},"closestLayerSnap"),interpolateOnPointSegment:l(function(t,e,i){return s.point(t.x*(1-i)+i*e.x,t.y*(1-i)+i*e.y)},"interpolateOnPointSegment"),interpolateOnLine:l(function(t,e,i){e=e instanceof s.Polyline?e.getLatLngs():e;var n=e.length;if(n<2)return null;if(i=Math.max(Math.min(i,1),0),i===0)return{latLng:e[0]instanceof s.LatLng?e[0]:s.latLng(e[0]),predecessor:-1};if(i==1)return{latLng:e[e.length-1]instanceof s.LatLng?e[e.length-1]:s.latLng(e[e.length-1]),predecessor:e.length-2};var o=t.getMaxZoom();o===1/0&&(o=t.getZoom());for(var r=[],f=0,a=0;a<n;a++)r[a]=t.project(e[a],o),a>0&&(f+=r[a-1].distanceTo(r[a]));for(var c=f*i,u=0,g=0,a=0;g<c;a++){var h=r[a],L=r[a+1];u=g,g+=h.distanceTo(L)}if(h==null&&L==null)var h=r[0],L=r[1],a=1;var v=g-u!==0?(c-u)/(g-u):0,y=s.GeometryUtil.interpolateOnPointSegment(h,L,v);return{latLng:t.unproject(y,o),predecessor:a-1}},"interpolateOnLine"),locateOnLine:l(function(t,e,i){var n=e.getLatLngs();if(i.equals(n[0]))return 0;if(i.equals(n[n.length-1]))return 1;for(var o=s.GeometryUtil.closest(t,e,i,!1),r=s.GeometryUtil.accumulatedLengths(n),f=r[r.length-1],a=0,c=!1,u=0,g=n.length-1;u<g;u++){var h=n[u],L=n[u+1];if(a=r[u],s.GeometryUtil.belongsSegment(o,h,L,1e-4)){a+=h.distanceTo(o),c=!0;break}}if(!c)throw"Could not interpolate "+i.toString()+" within "+e.toString();return a/f},"locateOnLine"),reverse:l(function(t){return s.polyline(t.getLatLngs().slice(0).reverse())},"reverse"),extract:l(function(t,e,i,n){if(i>n)return s.GeometryUtil.extract(t,s.GeometryUtil.reverse(e),1-i,1-n);i=Math.max(Math.min(i,1),0),n=Math.max(Math.min(n,1),0);var o=e.getLatLngs(),r=s.GeometryUtil.interpolateOnLine(t,e,i),f=s.GeometryUtil.interpolateOnLine(t,e,n);if(i==n){var a=s.GeometryUtil.interpolateOnLine(t,e,n);return[a.latLng]}r.predecessor==-1&&(r.predecessor=0),f.predecessor==-1&&(f.predecessor=0);var c=o.slice(r.predecessor+1,f.predecessor+1);return c.unshift(r.latLng),c.push(f.latLng),c},"extract"),isBefore:l(function(t,e){if(!e)return!1;var i=t.getLatLngs(),n=e.getLatLngs();return i[i.length-1].equals(n[0])},"isBefore"),isAfter:l(function(t,e){if(!e)return!1;var i=t.getLatLngs(),n=e.getLatLngs();return i[0].equals(n[n.length-1])},"isAfter"),startsAtExtremity:l(function(t,e){if(!e)return!1;var i=t.getLatLngs(),n=e.getLatLngs(),o=i[0];return o.equals(n[0])||o.equals(n[n.length-1])},"startsAtExtremity"),computeAngle:l(function(t,e){return Math.atan2(e.y-t.y,e.x-t.x)*180/Math.PI},"computeAngle"),computeSlope:l(function(t,e){var i=(e.y-t.y)/(e.x-t.x),n=t.y-i*t.x;return{a:i,b:n}},"computeSlope"),rotatePoint:l(function(t,e,i,n){var o=t.getMaxZoom();o===1/0&&(o=t.getZoom());var r=i*Math.PI/180,f=t.project(e,o),a=t.project(n,o),c=Math.cos(r)*(f.x-a.x)-Math.sin(r)*(f.y-a.y)+a.x,u=Math.sin(r)*(f.x-a.x)+Math.cos(r)*(f.y-a.y)+a.y;return t.unproject(new s.Point(c,u),o)},"rotatePoint"),bearing:l(function(t,e){var i=Math.PI/180,n=t.lat*i,o=e.lat*i,r=t.lng*i,f=e.lng*i,a=Math.sin(f-r)*Math.cos(o),c=Math.cos(n)*Math.sin(o)-Math.sin(n)*Math.cos(o)*Math.cos(f-r),u=(Math.atan2(a,c)*180/Math.PI+360)%360;return u>=180?u-360:u},"bearing"),destination:l(function(t,e,i){e=(e+360)%360;var n=Math.PI/180,o=180/Math.PI,r=6378137,f=t.lng*n,a=t.lat*n,c=e*n,u=Math.sin(a),g=Math.cos(a),h=Math.cos(i/r),L=Math.sin(i/r),v=Math.asin(u*h+g*L*Math.cos(c)),y=f+Math.atan2(Math.sin(c)*L*g,h-u*Math.sin(v));return y=y*o,y=y>180?y-360:y<-180?y+360:y,s.latLng([v*o,y])},"destination"),angle:l(function(t,e,i){var n=t.latLngToContainerPoint(e),o=t.latLngToContainerPoint(i),r=Math.atan2(o.y-n.y,o.x-n.x)*180/Math.PI+90;return r+=r<0?360:0,r},"angle"),destinationOnSegment:l(function(t,e,i,n){var o=s.GeometryUtil.angle(t,e,i),r=s.GeometryUtil.destination(e,o,n);return s.GeometryUtil.closestOnSegment(t,r,e,i)},"destinationOnSegment")}),s.GeometryUtil});
1
+ // Packaging/modules magic dance.
2
+ (function (factory) {
3
+ factory(window.BM)
4
+ }(function (BM) {
5
+ "use strict";
6
+
7
+ BM.Polyline._flat = BM.LineUtil.isFlat || BM.Polyline._flat || function (latlngs) {
8
+ // true if it's a flat array of latlngs; false if nested
9
+ return !BM.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
10
+ };
11
+
12
+ /**
13
+ * @fileOverview Geometry utilities for distances and linear referencing.
14
+ * @name BM.GeometryUtil
15
+ */
16
+
17
+ BM.GeometryUtil = BM.extend(BM.GeometryUtil || {}, {
18
+
19
+ /**
20
+ Shortcut function for planar distance between two {BM.LatLng} at current zoom.
21
+
22
+ @tutorial distance-length
23
+
24
+ @param {BM.Map} map map to be used for this method
25
+ @param {BM.LatLng} latlngA geographical point A
26
+ @param {BM.LatLng} latlngB geographical point B
27
+ @returns {Number} planar distance
28
+ */
29
+ distance: function (map, latlngA, latlngB) {
30
+ return map.latLngToLayerPoint(latlngA).distanceTo(map.latLngToLayerPoint(latlngB));
31
+ },
32
+
33
+ /**
34
+ Shortcut function for planar distance between a {BM.LatLng} and a segment (A-B).
35
+ @param {BM.Map} map map to be used for this method
36
+ @param {BM.LatLng} latlng - The position to search
37
+ @param {BM.LatLng} latlngA geographical point A of the segment
38
+ @param {BM.LatLng} latlngB geographical point B of the segment
39
+ @returns {Number} planar distance
40
+ */
41
+ distanceSegment: function (map, latlng, latlngA, latlngB) {
42
+ var p = map.latLngToLayerPoint(latlng),
43
+ p1 = map.latLngToLayerPoint(latlngA),
44
+ p2 = map.latLngToLayerPoint(latlngB);
45
+ return BM.LineUtil.pointToSegmentDistance(p, p1, p2);
46
+ },
47
+
48
+ /**
49
+ Shortcut function for converting distance to readable distance.
50
+ @param {Number} distance distance to be converted
51
+ @param {String} unit 'metric' or 'imperial'
52
+ @returns {String} in yard or miles
53
+ */
54
+ readableDistance: function (distance, unit) {
55
+ var isMetric = (unit !== 'imperial'),
56
+ distanceStr;
57
+ if (isMetric) {
58
+ // show metres when distance is < 1km, then show km
59
+ if (distance > 1000) {
60
+ distanceStr = (distance / 1000).toFixed(2) + ' km';
61
+ }
62
+ else {
63
+ distanceStr = Math.ceil(distance) + ' m';
64
+ }
65
+ }
66
+ else {
67
+ distance *= 1.09361;
68
+ if (distance > 1760) {
69
+ distanceStr = (distance / 1760).toFixed(2) + ' miles';
70
+ }
71
+ else {
72
+ distanceStr = Math.ceil(distance) + ' yd';
73
+ }
74
+ }
75
+ return distanceStr;
76
+ },
77
+
78
+ /**
79
+ Returns true if the latlng belongs to segment A-B
80
+ @param {BM.LatLng} latlng - The position to search
81
+ @param {BM.LatLng} latlngA geographical point A of the segment
82
+ @param {BM.LatLng} latlngB geographical point B of the segment
83
+ @param {?Number} [tolerance=0.2] tolerance to accept if latlng belongs really
84
+ @returns {boolean}
85
+ */
86
+ belongsSegment: function(latlng, latlngA, latlngB, tolerance) {
87
+ tolerance = tolerance === undefined ? 0.2 : tolerance;
88
+ var hypotenuse = latlngA.distanceTo(latlngB),
89
+ delta = latlngA.distanceTo(latlng) + latlng.distanceTo(latlngB) - hypotenuse;
90
+ return delta/hypotenuse < tolerance;
91
+ },
92
+
93
+ /**
94
+ * Returns total length of line
95
+ * @tutorial distance-length
96
+ *
97
+ * @param {BM.Polyline|Array<BM.Point>|Array<BM.LatLng>} coords Set of coordinates
98
+ * @returns {Number} Total length (pixels for Point, meters for LatLng)
99
+ */
100
+ length: function (coords) {
101
+ var accumulated = BM.GeometryUtil.accumulatedLengths(coords);
102
+ return accumulated.length > 0 ? accumulated[accumulated.length-1] : 0;
103
+ },
104
+
105
+ /**
106
+ * Returns a list of accumulated length along a line.
107
+ * @param {BM.Polyline|Array<BM.Point>|Array<BM.LatLng>} coords Set of coordinates
108
+ * @returns {Array<Number>} Array of accumulated lengths (pixels for Point, meters for LatLng)
109
+ */
110
+ accumulatedLengths: function (coords) {
111
+ if (typeof coords.getLatLngs == 'function') {
112
+ coords = coords.getLatLngs();
113
+ }
114
+ if (coords.length === 0)
115
+ return [];
116
+ var total = 0,
117
+ lengths = [0];
118
+ for (var i = 0, n = coords.length - 1; i< n; i++) {
119
+ total += coords[i].distanceTo(coords[i+1]);
120
+ lengths.push(total);
121
+ }
122
+ return lengths;
123
+ },
124
+
125
+ /**
126
+ Returns the closest point of a {BM.LatLng} on the segment (A-B)
127
+
128
+ @tutorial closest
129
+
130
+ @param {BM.Map} map map to be used for this method
131
+ @param {BM.LatLng} latlng - The position to search
132
+ @param {BM.LatLng} latlngA geographical point A of the segment
133
+ @param {BM.LatLng} latlngB geographical point B of the segment
134
+ @returns {BM.LatLng} Closest geographical point
135
+ */
136
+ closestOnSegment: function (map, latlng, latlngA, latlngB) {
137
+ var maxzoom = map.getMaxZoom();
138
+ if (maxzoom === Infinity)
139
+ maxzoom = map.getZoom();
140
+ var p = map.project(latlng, maxzoom),
141
+ p1 = map.project(latlngA, maxzoom),
142
+ p2 = map.project(latlngB, maxzoom),
143
+ closest = BM.LineUtil.closestPointOnSegment(p, p1, p2);
144
+ return map.unproject(closest, maxzoom);
145
+ },
146
+
147
+ /**
148
+ Returns the closest latlng on layer.
149
+
150
+ Accept nested arrays
151
+
152
+ @tutorial closest
153
+
154
+ @param {BM.Map} map map to be used for this method
155
+ @param {Array<BM.LatLng>|Array<Array<BM.LatLng>>|BM.PolyLine|BM.Polygon} layer - Layer that contains the result
156
+ @param {BM.LatLng} latlng - The position to search
157
+ @param {?boolean} [vertices=false] - Whether to restrict to path vertices.
158
+ @returns {BM.LatLng} Closest geographical point or null if layer param is incorrect
159
+ */
160
+ closest: function (map, layer, latlng, vertices) {
161
+
162
+ var latlngs,
163
+ mindist = Infinity,
164
+ result = null,
165
+ i, n, distance, subResult;
166
+
167
+ if (layer instanceof Array) {
168
+ // if layer is Array<Array<T>>
169
+ if (layer[0] instanceof Array && typeof layer[0][0] !== 'number') {
170
+ // if we have nested arrays, we calc the closest for each array
171
+ // recursive
172
+ for (i = 0; i < layer.length; i++) {
173
+ subResult = BM.GeometryUtil.closest(map, layer[i], latlng, vertices);
174
+ if (subResult.distance < mindist) {
175
+ mindist = subResult.distance;
176
+ result = subResult;
177
+ }
178
+ }
179
+ return result;
180
+ } else if (layer[0] instanceof BM.LatLng
181
+ || typeof layer[0][0] === 'number'
182
+ || typeof layer[0].lat === 'number') { // we could have a latlng as [x,y] with x & y numbers or {lat, lng}
183
+ layer = BM.polyline(layer);
184
+ } else {
185
+ return result;
186
+ }
187
+ }
188
+
189
+ // if we don't have here a Polyline, that means layer is incorrect
190
+ // see https://github.com/makinacorpus/.GeometryUtil/issues/23
191
+ if (! ( layer instanceof BM.Polyline ) )
192
+ return result;
193
+
194
+ // deep copy of latlngs
195
+ latlngs = JSON.parse(JSON.stringify(layer.getLatLngs().slice(0)));
196
+
197
+ // add the last segment for BM.Polygon
198
+ if (layer instanceof BM.Polygon) {
199
+ // add the last segment for each child that is a nested array
200
+ var addLastSegment = function(latlngs) {
201
+ if (BM.Polyline._flat(latlngs)) {
202
+ latlngs.push(latlngs[0]);
203
+ } else {
204
+ for (var i = 0; i < latlngs.length; i++) {
205
+ addLastSegment(latlngs[i]);
206
+ }
207
+ }
208
+ };
209
+ addLastSegment(latlngs);
210
+ }
211
+
212
+ // we have a multi polygon / multi polyline / polygon with holes
213
+ // use recursive to explore and return the good result
214
+ if ( ! BM.Polyline._flat(latlngs) ) {
215
+ for (i = 0; i < latlngs.length; i++) {
216
+ // if we are at the lower level, and if we have a BM.Polygon, we add the last segment
217
+ subResult = BM.GeometryUtil.closest(map, latlngs[i], latlng, vertices);
218
+ if (subResult.distance < mindist) {
219
+ mindist = subResult.distance;
220
+ result = subResult;
221
+ }
222
+ }
223
+ return result;
224
+
225
+ } else {
226
+
227
+ // Lookup vertices
228
+ if (vertices) {
229
+ for(i = 0, n = latlngs.length; i < n; i++) {
230
+ var ll = latlngs[i];
231
+ distance = BM.GeometryUtil.distance(map, latlng, ll);
232
+ if (distance < mindist) {
233
+ mindist = distance;
234
+ result = ll;
235
+ result.distance = distance;
236
+ }
237
+ }
238
+ return result;
239
+ }
240
+
241
+ // Keep the closest point of all segments
242
+ for (i = 0, n = latlngs.length; i < n-1; i++) {
243
+ var latlngA = latlngs[i],
244
+ latlngB = latlngs[i+1];
245
+ distance = BM.GeometryUtil.distanceSegment(map, latlng, latlngA, latlngB);
246
+ if (distance <= mindist) {
247
+ mindist = distance;
248
+ result = BM.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB);
249
+ result.distance = distance;
250
+ }
251
+ }
252
+ return result;
253
+ }
254
+
255
+ },
256
+
257
+ /**
258
+ Returns the closest layer to latlng among a list of layers.
259
+
260
+ @tutorial closest
261
+
262
+ @param {BM.Map} map map to be used for this method
263
+ @param {Array<BM.ILayer>} layers Set of layers
264
+ @param {BM.LatLng} latlng - The position to search
265
+ @returns {object} ``{layer, latlng, distance}`` or ``null`` if list is empty;
266
+ */
267
+ closestLayer: function (map, layers, latlng) {
268
+ var mindist = Infinity,
269
+ result = null,
270
+ ll = null,
271
+ distance = Infinity;
272
+
273
+ for (var i = 0, n = layers.length; i < n; i++) {
274
+ var layer = layers[i];
275
+ if (layer instanceof BM.LayerGroup) {
276
+ // recursive
277
+ var subResult = BM.GeometryUtil.closestLayer(map, layer.getLayers(), latlng);
278
+ if (subResult.distance < mindist) {
279
+ mindist = subResult.distance;
280
+ result = subResult;
281
+ }
282
+ } else {
283
+ // Single dimension, snap on points, else snap on closest
284
+ if (typeof layer.getLatLng == 'function') {
285
+ ll = layer.getLatLng();
286
+ distance = BM.GeometryUtil.distance(map, latlng, ll);
287
+ }
288
+ else {
289
+ ll = BM.GeometryUtil.closest(map, layer, latlng);
290
+ if (ll) distance = ll.distance; // Can return null if layer has no points.
291
+ }
292
+ if (distance < mindist) {
293
+ mindist = distance;
294
+ result = {layer: layer, latlng: ll, distance: distance};
295
+ }
296
+ }
297
+ }
298
+ return result;
299
+ },
300
+
301
+ /**
302
+ Returns the n closest layers to latlng among a list of input layers.
303
+
304
+ @param {BM.Map} map - map to be used for this method
305
+ @param {Array<BM.ILayer>} layers - Set of layers
306
+ @param {BM.LatLng} latlng - The position to search
307
+ @param {?Number} [n=layers.length] - the expected number of output layers.
308
+ @returns {Array<object>} an array of objects ``{layer, latlng, distance}`` or ``null`` if the input is invalid (empty list or negative n)
309
+ */
310
+ nClosestLayers: function (map, layers, latlng, n) {
311
+ n = typeof n === 'number' ? n : layers.length;
312
+
313
+ if (n < 1 || layers.length < 1) {
314
+ return null;
315
+ }
316
+
317
+ var results = [];
318
+ var distance, ll;
319
+
320
+ for (var i = 0, m = layers.length; i < m; i++) {
321
+ var layer = layers[i];
322
+ if (layer instanceof BM.LayerGroup) {
323
+ // recursive
324
+ var subResult = BM.GeometryUtil.closestLayer(map, layer.getLayers(), latlng);
325
+ results.push(subResult);
326
+ } else {
327
+ // Single dimension, snap on points, else snap on closest
328
+ if (typeof layer.getLatLng == 'function') {
329
+ ll = layer.getLatLng();
330
+ distance = BM.GeometryUtil.distance(map, latlng, ll);
331
+ }
332
+ else {
333
+ ll = BM.GeometryUtil.closest(map, layer, latlng);
334
+ if (ll) distance = ll.distance; // Can return null if layer has no points.
335
+ }
336
+ results.push({layer: layer, latlng: ll, distance: distance});
337
+ }
338
+ }
339
+
340
+ results.sort(function(a, b) {
341
+ return a.distance - b.distance;
342
+ });
343
+
344
+ if (results.length > n) {
345
+ return results.slice(0, n);
346
+ } else {
347
+ return results;
348
+ }
349
+ },
350
+
351
+ /**
352
+ * Returns all layers within a radius of the given position, in an ascending order of distance.
353
+ @param {BM.Map} map map to be used for this method
354
+ @param {Array<ILayer>} layers - A list of layers.
355
+ @param {BM.LatLng} latlng - The position to search
356
+ @param {?Number} [radius=Infinity] - Search radius in pixels
357
+ @return {object[]} an array of objects including layer within the radius, closest latlng, and distance
358
+ */
359
+ layersWithin: function(map, layers, latlng, radius) {
360
+ radius = typeof radius == 'number' ? radius : Infinity;
361
+
362
+ var results = [];
363
+ var ll = null;
364
+ var distance = 0;
365
+
366
+ for (var i = 0, n = layers.length; i < n; i++) {
367
+ var layer = layers[i];
368
+
369
+ if (typeof layer.getLatLng == 'function') {
370
+ ll = layer.getLatLng();
371
+ distance = BM.GeometryUtil.distance(map, latlng, ll);
372
+ }
373
+ else {
374
+ ll = BM.GeometryUtil.closest(map, layer, latlng);
375
+ if (ll) distance = ll.distance; // Can return null if layer has no points.
376
+ }
377
+
378
+ if (ll && distance < radius) {
379
+ results.push({layer: layer, latlng: ll, distance: distance});
380
+ }
381
+ }
382
+
383
+ var sortedResults = results.sort(function(a, b) {
384
+ return a.distance - b.distance;
385
+ });
386
+
387
+ return sortedResults;
388
+ },
389
+
390
+ /**
391
+ Returns the closest position from specified {LatLng} among specified layers,
392
+ with a maximum tolerance in pixels, providing snapping behaviour.
393
+
394
+ @tutorial closest
395
+
396
+ @param {BM.Map} map map to be used for this method
397
+ @param {Array<ILayer>} layers - A list of layers to snap on.
398
+ @param {BM.LatLng} latlng - The position to snap
399
+ @param {?Number} [tolerance=Infinity] - Maximum number of pixels.
400
+ @param {?boolean} [withVertices=true] - Snap to layers vertices or segment points (not only vertex)
401
+ @returns {object} with snapped {LatLng} and snapped {Layer} or null if tolerance exceeded.
402
+ */
403
+ closestLayerSnap: function (map, layers, latlng, tolerance, withVertices) {
404
+ tolerance = typeof tolerance == 'number' ? tolerance : Infinity;
405
+ withVertices = typeof withVertices == 'boolean' ? withVertices : true;
406
+
407
+ var result = BM.GeometryUtil.closestLayer(map, layers, latlng);
408
+ if (!result || result.distance > tolerance)
409
+ return null;
410
+
411
+ // If snapped layer is linear, try to snap on vertices (extremities and middle points)
412
+ if (withVertices && typeof result.layer.getLatLngs == 'function') {
413
+ var closest = BM.GeometryUtil.closest(map, result.layer, result.latlng, true);
414
+ if (closest.distance < tolerance) {
415
+ result.latlng = closest;
416
+ result.distance = BM.GeometryUtil.distance(map, closest, latlng);
417
+ }
418
+ }
419
+ return result;
420
+ },
421
+
422
+ /**
423
+ Returns the Point located on a segment at the specified ratio of the segment length.
424
+ @param {BM.Point} pA coordinates of point A
425
+ @param {BM.Point} pB coordinates of point B
426
+ @param {Number} the length ratio, expressed as a decimal between 0 and 1, inclusive.
427
+ @returns {BM.Point} the interpolated point.
428
+ */
429
+ interpolateOnPointSegment: function (pA, pB, ratio) {
430
+ return BM.point(
431
+ (pA.x * (1 - ratio)) + (ratio * pB.x),
432
+ (pA.y * (1 - ratio)) + (ratio * pB.y)
433
+ );
434
+ },
435
+
436
+ /**
437
+ Returns the coordinate of the point located on a line at the specified ratio of the line length.
438
+ @param {BM.Map} map map to be used for this method
439
+ @param {Array<BM.LatLng>|BM.PolyLine} latlngs Set of geographical points
440
+ @param {Number} ratio the length ratio, expressed as a decimal between 0 and 1, inclusive
441
+ @returns {Object} an object with latLng ({LatLng}) and predecessor ({Number}), the index of the preceding vertex in the Polyline
442
+ (-1 if the interpolated point is the first vertex)
443
+ */
444
+ interpolateOnLine: function (map, latLngs, ratio) {
445
+ latLngs = (latLngs instanceof BM.Polyline) ? latLngs.getLatLngs() : latLngs;
446
+ var n = latLngs.length;
447
+ if (n < 2) {
448
+ return null;
449
+ }
450
+
451
+ // ensure the ratio is between 0 and 1;
452
+ ratio = Math.max(Math.min(ratio, 1), 0);
453
+
454
+ if (ratio === 0) {
455
+ return {
456
+ latLng: latLngs[0] instanceof BM.LatLng ? latLngs[0] : BM.latLng(latLngs[0]),
457
+ predecessor: -1
458
+ };
459
+ }
460
+ if (ratio == 1) {
461
+ return {
462
+ latLng: latLngs[latLngs.length -1] instanceof BM.LatLng ? latLngs[latLngs.length -1] : BM.latLng(latLngs[latLngs.length -1]),
463
+ predecessor: latLngs.length - 2
464
+ };
465
+ }
466
+
467
+ // project the LatLngs as Points,
468
+ // and compute total planar length of the line at max precision
469
+ var maxzoom = map.getMaxZoom();
470
+ if (maxzoom === Infinity)
471
+ maxzoom = map.getZoom();
472
+ var pts = [];
473
+ var lineLength = 0;
474
+ for(var i = 0; i < n; i++) {
475
+ pts[i] = map.project(latLngs[i], maxzoom);
476
+ if(i > 0)
477
+ lineLength += pts[i-1].distanceTo(pts[i]);
478
+ }
479
+
480
+ var ratioDist = lineLength * ratio;
481
+
482
+ // follow the line segments [ab], adding lengths,
483
+ // until we find the segment where the points should lie on
484
+ var cumulativeDistanceToA = 0, cumulativeDistanceToB = 0;
485
+ for (var i = 0; cumulativeDistanceToB < ratioDist; i++) {
486
+ var pointA = pts[i], pointB = pts[i+1];
487
+
488
+ cumulativeDistanceToA = cumulativeDistanceToB;
489
+ cumulativeDistanceToB += pointA.distanceTo(pointB);
490
+ }
491
+
492
+ if (pointA == undefined && pointB == undefined) { // Happens when line has no length
493
+ var pointA = pts[0], pointB = pts[1], i = 1;
494
+ }
495
+
496
+ // compute the ratio relative to the segment [ab]
497
+ var segmentRatio = ((cumulativeDistanceToB - cumulativeDistanceToA) !== 0) ? ((ratioDist - cumulativeDistanceToA) / (cumulativeDistanceToB - cumulativeDistanceToA)) : 0;
498
+ var interpolatedPoint = BM.GeometryUtil.interpolateOnPointSegment(pointA, pointB, segmentRatio);
499
+ return {
500
+ latLng: map.unproject(interpolatedPoint, maxzoom),
501
+ predecessor: i-1
502
+ };
503
+ },
504
+
505
+ /**
506
+ Returns a float between 0 and 1 representing the location of the
507
+ closest point on polyline to the given latlng, as a fraction of total line length.
508
+ (opposite of BM.GeometryUtil.interpolateOnLine())
509
+ @param {BM.Map} map map to be used for this method
510
+ @param {BM.PolyLine} polyline Polyline on which the latlng will be search
511
+ @param {BM.LatLng} latlng The position to search
512
+ @returns {Number} Float between 0 and 1
513
+ */
514
+ locateOnLine: function (map, polyline, latlng) {
515
+ var latlngs = polyline.getLatLngs();
516
+ if (latlng.equals(latlngs[0]))
517
+ return 0.0;
518
+ if (latlng.equals(latlngs[latlngs.length-1]))
519
+ return 1.0;
520
+
521
+ var point = BM.GeometryUtil.closest(map, polyline, latlng, false),
522
+ lengths = BM.GeometryUtil.accumulatedLengths(latlngs),
523
+ total_length = lengths[lengths.length-1],
524
+ portion = 0,
525
+ found = false;
526
+ for (var i=0, n = latlngs.length-1; i < n; i++) {
527
+ var l1 = latlngs[i],
528
+ l2 = latlngs[i+1];
529
+ portion = lengths[i];
530
+ if (BM.GeometryUtil.belongsSegment(point, l1, l2, 0.0001)) {
531
+ portion += l1.distanceTo(point);
532
+ found = true;
533
+ break;
534
+ }
535
+ }
536
+ if (!found) {
537
+ throw "Could not interpolate " + latlng.toString() + " within " + polyline.toString();
538
+ }
539
+ return portion / total_length;
540
+ },
541
+
542
+ /**
543
+ Returns a clone with reversed coordinates.
544
+ @param {BM.PolyLine} polyline polyline to reverse
545
+ @returns {BM.PolyLine} polyline reversed
546
+ */
547
+ reverse: function (polyline) {
548
+ return BM.polyline(polyline.getLatLngs().slice(0).reverse());
549
+ },
550
+
551
+ /**
552
+ Returns a sub-part of the polyline, from start to end.
553
+ If start is superior to end, returns extraction from inverted line.
554
+ @param {BM.Map} map map to be used for this method
555
+ @param {BM.PolyLine} polyline Polyline on which will be extracted the sub-part
556
+ @param {Number} start ratio, expressed as a decimal between 0 and 1, inclusive
557
+ @param {Number} end ratio, expressed as a decimal between 0 and 1, inclusive
558
+ @returns {Array<BM.LatLng>} new polyline
559
+ */
560
+ extract: function (map, polyline, start, end) {
561
+ if (start > end) {
562
+ return BM.GeometryUtil.extract(map, BM.GeometryUtil.reverse(polyline), 1.0-start, 1.0-end);
563
+ }
564
+
565
+ // Bound start and end to [0-1]
566
+ start = Math.max(Math.min(start, 1), 0);
567
+ end = Math.max(Math.min(end, 1), 0);
568
+
569
+ var latlngs = polyline.getLatLngs(),
570
+ startpoint = BM.GeometryUtil.interpolateOnLine(map, polyline, start),
571
+ endpoint = BM.GeometryUtil.interpolateOnLine(map, polyline, end);
572
+ // Return single point if start == end
573
+ if (start == end) {
574
+ var point = BM.GeometryUtil.interpolateOnLine(map, polyline, end);
575
+ return [point.latLng];
576
+ }
577
+ // Array.slice() works indexes at 0
578
+ if (startpoint.predecessor == -1)
579
+ startpoint.predecessor = 0;
580
+ if (endpoint.predecessor == -1)
581
+ endpoint.predecessor = 0;
582
+ var result = latlngs.slice(startpoint.predecessor+1, endpoint.predecessor+1);
583
+ result.unshift(startpoint.latLng);
584
+ result.push(endpoint.latLng);
585
+ return result;
586
+ },
587
+
588
+ /**
589
+ Returns true if first polyline ends where other second starts.
590
+ @param {BM.PolyLine} polyline First polyline
591
+ @param {BM.PolyLine} other Second polyline
592
+ @returns {bool}
593
+ */
594
+ isBefore: function (polyline, other) {
595
+ if (!other) return false;
596
+ var lla = polyline.getLatLngs(),
597
+ llb = other.getLatLngs();
598
+ return (lla[lla.length-1]).equals(llb[0]);
599
+ },
600
+
601
+ /**
602
+ Returns true if first polyline starts where second ends.
603
+ @param {BM.PolyLine} polyline First polyline
604
+ @param {BM.PolyLine} other Second polyline
605
+ @returns {bool}
606
+ */
607
+ isAfter: function (polyline, other) {
608
+ if (!other) return false;
609
+ var lla = polyline.getLatLngs(),
610
+ llb = other.getLatLngs();
611
+ return (lla[0]).equals(llb[llb.length-1]);
612
+ },
613
+
614
+ /**
615
+ Returns true if first polyline starts where second ends or start.
616
+ @param {BM.PolyLine} polyline First polyline
617
+ @param {BM.PolyLine} other Second polyline
618
+ @returns {bool}
619
+ */
620
+ startsAtExtremity: function (polyline, other) {
621
+ if (!other) return false;
622
+ var lla = polyline.getLatLngs(),
623
+ llb = other.getLatLngs(),
624
+ start = lla[0];
625
+ return start.equals(llb[0]) || start.equals(llb[llb.length-1]);
626
+ },
627
+
628
+ /**
629
+ Returns horizontal angle in degres between two points.
630
+ @param {BM.Point} a Coordinates of point A
631
+ @param {BM.Point} b Coordinates of point B
632
+ @returns {Number} horizontal angle
633
+ */
634
+ computeAngle: function(a, b) {
635
+ return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI);
636
+ },
637
+
638
+ /**
639
+ Returns slope (Ax+B) between two points.
640
+ @param {BM.Point} a Coordinates of point A
641
+ @param {BM.Point} b Coordinates of point B
642
+ @returns {Object} with ``a`` and ``b`` properties.
643
+ */
644
+ computeSlope: function(a, b) {
645
+ var s = (b.y - a.y) / (b.x - a.x),
646
+ o = a.y - (s * a.x);
647
+ return {'a': s, 'b': o};
648
+ },
649
+
650
+ /**
651
+ Returns LatLng of rotated point around specified LatLng center.
652
+ @param {BM.LatLng} latlngPoint: point to rotate
653
+ @param {double} angleDeg: angle to rotate in degrees
654
+ @param {BM.LatLng} latlngCenter: center of rotation
655
+ @returns {BM.LatLng} rotated point
656
+ */
657
+ rotatePoint: function(map, latlngPoint, angleDeg, latlngCenter) {
658
+ var maxzoom = map.getMaxZoom();
659
+ if (maxzoom === Infinity)
660
+ maxzoom = map.getZoom();
661
+ var angleRad = angleDeg*Math.PI/180,
662
+ pPoint = map.project(latlngPoint, maxzoom),
663
+ pCenter = map.project(latlngCenter, maxzoom),
664
+ x2 = Math.cos(angleRad)*(pPoint.x-pCenter.x) - Math.sin(angleRad)*(pPoint.y-pCenter.y) + pCenter.x,
665
+ y2 = Math.sin(angleRad)*(pPoint.x-pCenter.x) + Math.cos(angleRad)*(pPoint.y-pCenter.y) + pCenter.y;
666
+ return map.unproject(new BM.Point(x2,y2), maxzoom);
667
+ },
668
+
669
+ /**
670
+ Returns the bearing in degrees clockwise from north (0 degrees)
671
+ from the first BM.LatLng to the second, at the first LatLng
672
+ @param {BM.LatLng} latlng1: origin point of the bearing
673
+ @param {BM.LatLng} latlng2: destination point of the bearing
674
+ @returns {float} degrees clockwise from north.
675
+ */
676
+ bearing: function(latlng1, latlng2) {
677
+ var rad = Math.PI / 180,
678
+ lat1 = latlng1.lat * rad,
679
+ lat2 = latlng2.lat * rad,
680
+ lon1 = latlng1.lng * rad,
681
+ lon2 = latlng2.lng * rad,
682
+ y = Math.sin(lon2 - lon1) * Math.cos(lat2),
683
+ x = Math.cos(lat1) * Math.sin(lat2) -
684
+ Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
685
+
686
+ var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360;
687
+ return bearing >= 180 ? bearing-360 : bearing;
688
+ },
689
+
690
+ /**
691
+ Returns the point that is a distance and heading away from
692
+ the given origin point.
693
+ @param {BM.LatLng} latlng: origin point
694
+ @param {float} heading: heading in degrees, clockwise from 0 degrees north.
695
+ @param {float} distance: distance in meters
696
+ @returns {BM.latLng} the destination point.
697
+ Many thanks to Chris Veness at http://www.movable-type.co.uk/scripts/latlong.html
698
+ for a great reference and examples.
699
+ */
700
+ destination: function(latlng, heading, distance) {
701
+ heading = (heading + 360) % 360;
702
+ var rad = Math.PI / 180,
703
+ radInv = 180 / Math.PI,
704
+ R = 6378137, // approximation of Earth's radius
705
+ lon1 = latlng.lng * rad,
706
+ lat1 = latlng.lat * rad,
707
+ rheading = heading * rad,
708
+ sinLat1 = Math.sin(lat1),
709
+ cosLat1 = Math.cos(lat1),
710
+ cosDistR = Math.cos(distance / R),
711
+ sinDistR = Math.sin(distance / R),
712
+ lat2 = Math.asin(sinLat1 * cosDistR + cosLat1 *
713
+ sinDistR * Math.cos(rheading)),
714
+ lon2 = lon1 + Math.atan2(Math.sin(rheading) * sinDistR *
715
+ cosLat1, cosDistR - sinLat1 * Math.sin(lat2));
716
+ lon2 = lon2 * radInv;
717
+ lon2 = lon2 > 180 ? lon2 - 360 : lon2 < -180 ? lon2 + 360 : lon2;
718
+ return BM.latLng([lat2 * radInv, lon2]);
719
+ },
720
+
721
+ /**
722
+ Returns the the angle of the given segment and the Equator in degrees,
723
+ clockwise from 0 degrees north.
724
+ @param {BM.Map} map: map to be used for this method
725
+ @param {BM.LatLng} latlngA: geographical point A of the segment
726
+ @param {BM.LatLng} latlngB: geographical point B of the segment
727
+ @returns {Float} the angle in degrees.
728
+ */
729
+ angle: function(map, latlngA, latlngB) {
730
+ var pointA = map.latLngToContainerPoint(latlngA),
731
+ pointB = map.latLngToContainerPoint(latlngB),
732
+ angleDeg = Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180 / Math.PI + 90;
733
+ angleDeg += angleDeg < 0 ? 360 : 0;
734
+ return angleDeg;
735
+ },
736
+
737
+ /**
738
+ Returns a point snaps on the segment and heading away from the given origin point a distance.
739
+ @param {BM.Map} map: map to be used for this method
740
+ @param {BM.LatLng} latlngA: geographical point A of the segment
741
+ @param {BM.LatLng} latlngB: geographical point B of the segment
742
+ @param {float} distance: distance in meters
743
+ @returns {BM.latLng} the destination point.
744
+ */
745
+ destinationOnSegment: function(map, latlngA, latlngB, distance) {
746
+ var angleDeg = BM.GeometryUtil.angle(map, latlngA, latlngB),
747
+ latlng = BM.GeometryUtil.destination(latlngA, angleDeg, distance);
748
+ return BM.GeometryUtil.closestOnSegment(map, latlng, latlngA, latlngB);
749
+ },
750
+ });
751
+
752
+ return BM.GeometryUtil;
753
+
754
+ }));