@raruto/leaflet-elevation 2.2.7 → 2.3.0

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.
@@ -0,0 +1,636 @@
1
+ /*
2
+ * Copyright (c) 2023, GPL-3.0+ Project, Raruto
3
+ *
4
+ * This file is free software: you may copy, redistribute and/or modify it
5
+ * under the terms of the GNU General Public License as published by the
6
+ * Free Software Foundation, either version 2 of the License, or (at your
7
+ * option) any later version.
8
+ *
9
+ * This file is distributed in the hope that it will be useful, but
10
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ * General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program. If not, see .
16
+ *
17
+ * This file incorporates work covered by the following copyright and
18
+ * permission notice:
19
+ *
20
+ * Copyright (c) GPL-3.0+ Project - 2018- Dražen Tutić - https://github.com/dtutic/Leaflet.EdgeScaleBar
21
+ * Copyright (c) MIT License (MIT) - 2015- Xisco Guaita - https://github.com/xguaita/Leaflet.MapCenterCoord
22
+ */
23
+
24
+ /**
25
+ * Original source: https://github.com/xguaita/Leaflet.MapCenterCoord
26
+ */
27
+ L.Control.EdgeScale = L.Control.extend({
28
+
29
+ // Defaults
30
+ options: {
31
+ position: 'bottomleft',
32
+ icon: true,
33
+ coords: true,
34
+ bar: true,
35
+ onMove: true,
36
+ template: '{y} | {x}', // https://en.wikipedia.org/wiki/ISO_6709
37
+ projected: false,
38
+ formatProjected: '#.##0,000',
39
+ latlngFormat: 'DD', // DD, DM, DMS
40
+ latlngDesignators: true,
41
+ latLngFormatter: undefined,
42
+ iconStyle: {
43
+ background: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 100 100'%3E%3Cg stroke='%23fff'%3E%3Ccircle cx='50' cy='50.2' r='3.9' stroke-width='2' /%3E%3Cpath stroke-width='3' d='M5 54h32a4 4 0 1 0 0-8H5a4 4 0 1 0 0 8z M54 5a4 4 0 1 0-8 0v32a4 4 0 1 0 8 0V5z M99 50c0-2-2-4-4-4H63a4 4 0 1 0 0 8h32c2 0 4-1 4-4zM46 95a4 4 0 1 0 8 0V64a4 4 0 1 0-8 0v31z'/%3E%3C/g%3E%3C/svg%3E%0A")`,
44
+ width: '24px',
45
+ height: '24px',
46
+ left: 'calc(50% - 12px)',
47
+ top: 'calc(50% - 12px)',
48
+ content: '',
49
+ display: 'block',
50
+ position: 'absolute',
51
+ zIndex: 999,
52
+ pointerEvents: 'none',
53
+ },
54
+ containerStyle: {
55
+ backgroundColor: 'rgba(255, 255, 255, 0.7)',
56
+ boxShadow: '0 0 5px #bbb',
57
+ borderRadius: '3px',
58
+ padding: '3px 2px',
59
+ color: '#333',
60
+ font: '11px/1.5 Consolas, monaco, monospace',
61
+ writingMode: 'vertical-lr',
62
+ },
63
+ },
64
+
65
+ initialize: function(options) {
66
+ L.setOptions(this, options);
67
+ },
68
+
69
+ onAdd: function (map) {
70
+
71
+ if (this.options.bar) {
72
+ this._scaleBar = (new L.Control.EdgeScale.Layer(true === this.options.bar ? {} : this.options.bar)).addTo(map);
73
+ }
74
+
75
+ // create a DOM element and put it into overlayPane
76
+ if (this.options.icon) {
77
+ this._icon = L.DomUtil.create('div', 'leaflet-crosshair');
78
+ Object.assign(this._icon.style, this.options.iconStyle);
79
+ map.getContainer().insertBefore(this._icon, map.getContainer().firstChild);
80
+ }
81
+
82
+ // Control container
83
+ this._container = L.DomUtil.create('div', 'leaflet-control-mapcentercoord');
84
+
85
+ Object.assign(this._container.style, this.options.containerStyle);
86
+
87
+ if (!this.options.coords) {
88
+ this._container.style.display = 'none';
89
+ }
90
+
91
+ L.DomEvent.disableClickPropagation(this._container);
92
+
93
+ this._container.innerHTML = this._getMapCenterCoord();
94
+
95
+ // Add events listeners for updating coordinates & icon's position
96
+ map.on('move', this._onMapMove, this);
97
+ map.on('moveend', this._onMapMove, this);
98
+
99
+ return this._container;
100
+ },
101
+
102
+ onRemove: function (map) {
103
+ if (this.options.bar) {
104
+ this._scaleBar.remove();
105
+ }
106
+
107
+ // remove icon's DOM elements and listeners
108
+ if (this.options.icon) {
109
+ map.getContainer().removeChild(this._icon);
110
+ }
111
+ map.off('move', this._onMapMove, this);
112
+ map.off('moveend', this._onMapMove, this);
113
+ },
114
+
115
+ // update coordinates
116
+ _onMapMove: function (e) {
117
+ if (this.options.onMove || 'moveend' === e.type) {
118
+ this._container.innerHTML = this._getMapCenterCoord();
119
+ }
120
+ },
121
+
122
+ _getMapCenterCoord: function () {
123
+ const center = this._map.getCenter();
124
+ return this.options.projected
125
+ ? this._getProjectedCoord(this._map.options.crs.project(center))
126
+ : this._getLatLngCoord(center);
127
+ },
128
+
129
+ _getProjectedCoord: function (center) {
130
+ return L.Util.template(
131
+ this.options.template,
132
+ {
133
+ x: this._format(this.options.formatProjected, center.x),
134
+ y: this._format(this.options.formatProjected, center.y)
135
+ }
136
+ );
137
+ },
138
+
139
+ _getLatLngCoord: function (latLng) {
140
+
141
+ const { latLngFormatter, latlngFormat, latlngDesignators: designators } = this.options;
142
+
143
+ if (undefined !== latLngFormatter ) {
144
+ return latLngFormatter(latLng.lat, latLng.lng);
145
+ }
146
+
147
+ let lat, lng, deg, min;
148
+
149
+ // make a copy of center so we aren't affecting leaflet's internal state
150
+ let center = {
151
+ lat: latLng.lat,
152
+ lng: latLng.lng,
153
+ lng_neg: latLng.lng < 0,
154
+ lat_neg: latLng.lat < 0,
155
+ };
156
+
157
+ // 180 degrees & negative
158
+ if (center.lng < 0) {
159
+ center.lng = Math.abs(center.lng);
160
+ }
161
+ if (center.lng > 180) {
162
+ center.lng = 360 - center.lng;
163
+ center.lng_neg = !center.lng_neg;
164
+ }
165
+ if (center.lat < 0) {
166
+ center.lat = Math.abs(center.lat);
167
+ }
168
+
169
+ // format
170
+ if ('DM' === latlngFormat) {
171
+ deg = parseInt(center.lng);
172
+ lng = deg + 'º ' + this._format('00.000', (center.lng - deg) * 60) + "'";
173
+ deg = parseInt(center.lat);
174
+ lat = deg + 'º ' + this._format('00.000', (center.lat - deg) * 60) + "'";
175
+ } else if ('DMS' === latlngFormat) {
176
+ deg = parseInt(center.lng);
177
+ min = (center.lng - deg) * 60;
178
+ lng = deg + 'º ' + this._format('00', parseInt(min)) + "' " + this._format('00.0', (min - parseInt(min)) * 60) + "''";
179
+ deg = parseInt(center.lat);
180
+ min = (center.lat - deg) * 60;
181
+ lat = deg + 'º ' + this._format('00', parseInt(min)) + "' " + this._format('00.0', (min - parseInt(min)) * 60) + "''";
182
+ } else { // 'DD'
183
+ lng = this._format('#0.00000', center.lng) + 'º';
184
+ lat = this._format('##0.00000', center.lat) + 'º';
185
+ }
186
+
187
+ return L.Util.template(this.options.template, {
188
+ x: (!designators && center.lng_neg ? '-' : '') + lng + (designators ? (center.lng_neg ? ' W' : ' E') : ''),
189
+ y: (!designators && center.lat_neg ? '-' : '') + lat + (designators ? (center.lat_neg ? ' S' : ' N') : '')
190
+ });
191
+ },
192
+
193
+ /**
194
+ * IntegraXor Web SCADA - JavaScript Number Formatter
195
+ *
196
+ * @see https://code.google.com/p/javascript-number-formatter
197
+ * @authors KPL, KHL
198
+ */
199
+ _format: function (m, v) {
200
+ if (!m || isNaN(+v)) {
201
+ return v; // return as it is.
202
+ }
203
+
204
+ v = m.charAt(0) == '-' ? -v : +v; // convert any string to number according to formation sign.
205
+ let isNegative = v < 0 ? v = -v : 0; // process only abs(), and turn on flag.
206
+
207
+ let result = m.match(/[^\d\-\+#]/g); // search for separator for grp & decimal, anything not digit, not +/- sign, not #.
208
+ let Decimal = (result && result[result.length - 1]) || '.'; // treat the right most symbol as decimal
209
+ let Group = (result && result[1] && result[0]) || ','; // treat the left most symbol as group separator
210
+
211
+ m = m.split(Decimal); // split the decimal for the format string if any.
212
+ v = v.toFixed(m[1] && m[1].length); // Fix the decimal first, toFixed will auto fill trailing zero.
213
+ v = +(v) + ''; // convert number to string to trim off *all* trailing decimal zero(es)
214
+
215
+ let pos_trail_zero = m[1] && m[1].lastIndexOf('0'); // fill back any trailing zero according to format (look for last zero in format)
216
+ let part = v.split('.');
217
+
218
+ if (!part[1] || part[1] && part[1].length <= pos_trail_zero) { // integer will get !part[1]
219
+ v = (+v).toFixed(pos_trail_zero + 1);
220
+ }
221
+ let szSep = m[0].split(Group); // look for separator
222
+ m[0] = szSep.join(''); // join back without separator for counting the pos of any leading 0.
223
+
224
+ let pos_lead_zero = m[0] && m[0].indexOf('0');
225
+ if (pos_lead_zero > -1) {
226
+ while (part[0].length < (m[0].length - pos_lead_zero)) {
227
+ part[0] = '0' + part[0];
228
+ }
229
+ } else if (+part[0] == 0) {
230
+ part[0] = '';
231
+ }
232
+ v = v.split('.');
233
+ v[0] = part[0];
234
+
235
+ var pos_separator = (szSep[1] && szSep[szSep.length - 1].length); // process the first group separator from decimal (.) only, the rest ignore. Get the length of the last slice of split result.
236
+ if (pos_separator) {
237
+ let integer = v[0];
238
+ let str = '';
239
+ let offset = integer.length % pos_separator;
240
+ for (let i = 0, l = integer.length; i < l; i++) {
241
+ str += integer.charAt(i); // ie6 only support charAt for sz.
242
+ if (
243
+ !((i - offset + 1) % pos_separator) &&
244
+ i < l - pos_separator // -pos_separator so that won't trail separator on full length
245
+ ) {
246
+ str += Group;
247
+ }
248
+ }
249
+ v[0] = str;
250
+ }
251
+
252
+ v[1] = (m[1] && v[1]) ? Decimal + v[1] : "";
253
+ return (isNegative ? '-' : '') + v[0] + v[1]; // put back any negation and combine integer and fraction.
254
+ }
255
+
256
+ });
257
+
258
+ /**
259
+ * Original Source: https://github.com/dtutic/Leaflet.EdgeScaleBar
260
+ *
261
+ * Draws the metric scale bars in Web Mercator map along top and right edges.
262
+ * Authors: Dražen Tutić (dtutic@geof.hr), Ana Kuveždić Divjak (akuvezdic@geof.hr)
263
+ * University of Zagreb, Faculty of Geodesy, GEOF-OSGL Lab
264
+ * Inspired by LatLonGraticule Leaflet plugin by: lanwei@cloudybay.com.tw
265
+ */
266
+ L.Control.EdgeScale.Layer = L.Layer.extend({
267
+
268
+ includes: L.Evented ? L.Evented.prototype : L.Mixin.Events,
269
+
270
+ options: {
271
+ opacity: 1,
272
+ weight: 0.8,
273
+ gradient: {
274
+ size: 10,
275
+ opacity: 0.5,
276
+ },
277
+ color: '#000',
278
+ font: '11px Arial',
279
+ zoomInterval: [
280
+ {start: 0, end: 2, interval: 5000000},
281
+ {start: 3, end: 3, interval: 2000000},
282
+ {start: 4, end: 4, interval: 1000000},
283
+ {start: 5, end: 5, interval: 500000},
284
+ {start: 6, end: 7, interval: 200000},
285
+ {start: 8, end: 8, interval: 100000},
286
+ {start: 9, end: 9, interval: 50000},
287
+ {start: 10, end: 10, interval: 20000},
288
+ {start: 11, end: 11, interval: 10000},
289
+ {start: 12, end: 12, interval: 5000},
290
+ {start: 13, end: 13, interval: 2000},
291
+ {start: 14, end: 14, interval: 1000},
292
+ {start: 15, end: 15, interval: 500},
293
+ {start: 16, end: 16, interval: 200},
294
+ {start: 17, end: 17, interval: 100},
295
+ {start: 18, end: 18, interval: 50},
296
+ {start: 19, end: 19, interval: 20},
297
+ {start: 20, end: 20, interval: 10}
298
+ ],
299
+ pane: 'edgescalePane'
300
+ },
301
+
302
+ initialize: function (options) {
303
+
304
+ L.setOptions(this, options);
305
+
306
+ // Constants of the WGS84 ellipsoid needed to calculate meridian length or latitute
307
+ const a = this._a = 6378137.0;
308
+ const b = this._b = 6356752.3142;
309
+ const n = this._n = (a - b)/(a + b);
310
+ const a2 = a * a;
311
+ const b2 = b * b;
312
+ const n2 = n * n;
313
+ const n3 = n2 * n;
314
+ const n4 = n3 * n;
315
+ const n5 = n4 * n;
316
+ this._A = a * (1.0 - n) * (1.0 - n2) * (1.0 + 9.0/4.0 * n2 + 225.0/64.0 * n4);
317
+ this._e2 = (a2 - b2) / a2;
318
+ this._ic1 = 1.5 * n - 29.0/12.0 * n3 + 553.0/80.0 * n5;
319
+ this._ic2 = 21.0/8.0 * n2 - 1537.0/128.0 * n4;
320
+ this._ic3 = 151.0/24.0 * n3 - 32373.0/640.0 * n5;
321
+ this._ic4 = 1097.0/64.0 * n4;
322
+ this._ic5 = 8011.0/150.0 * n5;
323
+ this._c1 = -1.5 * n + 31.0/24.0 * n3 - 669.0/640.0 * n5;
324
+ this._c2 = 15.0/18.0 * n2 - 435.0/128.0 * n4;
325
+ this._c3 = -35.0/12.0 * n3 + 651.0/80.0 * n5;
326
+ this._c4 = 315.0/64.0 * n4;
327
+ this._c5 = -693.0/80.0 * n5;
328
+
329
+ // Latitude limit of the Web Mercator projection
330
+ this._LIMIT_PHI = 1.484419982;
331
+ },
332
+
333
+ onAdd: function (map) {
334
+ this._map = map;
335
+
336
+ let pane = map.getPane(this.options.pane);
337
+ if (!pane) {
338
+ pane = this._pane = map.createPane('edgescalePane', map.getPane('norotatePane') || map.getPane('mapPane'));
339
+ pane.style.zIndex = 625; // This pane is above markers but below popups.
340
+ pane.style.pointerEvents = 'none';
341
+ }
342
+
343
+ this._pane = pane;
344
+
345
+ // if (this._renderer) this._renderer.remove()
346
+ // this._renderer = L.canvas({ pane: "edgescalePane" }).addTo(this._map); // default leaflet svg renderer
347
+
348
+ if (!this._canvas) {
349
+ this._initCanvas();
350
+ }
351
+
352
+ this._pane.appendChild(this._canvas);
353
+
354
+ map.on('viewreset', this._reset, this);
355
+ map.on('move', this._reset, this);
356
+ map.on('moveend', this._reset, this);
357
+ map.on('rotate', this._reset, this);
358
+
359
+ this._reset();
360
+ },
361
+
362
+ onRemove: function (map) {
363
+ this._pane.removeChild(this._canvas);
364
+
365
+ map.off('viewreset', this._reset, this);
366
+ map.off('move', this._reset, this);
367
+ map.off('moveend', this._reset, this);
368
+ },
369
+
370
+ addTo: function (map) {
371
+ map.addLayer(this);
372
+ return this;
373
+ },
374
+
375
+ setOpacity: function (opacity) {
376
+ this.options.opacity = opacity;
377
+ L.DomUtil.setOpacity(this._canvas, this.options.opacity);
378
+ return this;
379
+ },
380
+
381
+ bringToFront: function () {
382
+ if (this._canvas) {
383
+ this._pane.appendChild(this._canvas);
384
+ }
385
+ return this;
386
+ },
387
+
388
+ bringToBack: function () {
389
+ if (this._canvas) {
390
+ this._pane.insertBefore(this._canvas, pane.firstChild);
391
+ }
392
+ return this;
393
+ },
394
+
395
+ _initCanvas: function () {
396
+ this._canvas = L.DomUtil.create('canvas', '');
397
+ this._ctx = this._canvas.getContext('2d');
398
+
399
+ this.setOpacity();
400
+
401
+ L.extend(this._canvas, {
402
+ onselectstart: L.Util.falseFn,
403
+ onmousemove: L.Util.falseFn,
404
+ onload: L.bind(this._onCanvasLoad, this)
405
+ });
406
+ },
407
+
408
+ _reset: function () {
409
+ var canvas = this._canvas,
410
+ size = this._map.getSize();
411
+
412
+ this._setCanvasPosition();
413
+
414
+ canvas.width = size.x;
415
+ canvas.height = size.y;
416
+ canvas.style.width = size.x + 'px';
417
+ canvas.style.height = size.y + 'px';
418
+
419
+ /**
420
+ * @TODO add support for "leaflet-rotate"
421
+ */
422
+ if (this._map._bearing) {
423
+ return;
424
+ }
425
+
426
+ const { gradient } = this.options;
427
+
428
+ // horizontal gradient
429
+ if (!this._hor_gradient) {
430
+ this._hor_gradient = this._ctx.createLinearGradient(0, 0, 0, gradient.size);
431
+ this._hor_gradient.addColorStop(0,"rgba(255, 255, 255, " + gradient.opacity + ")");
432
+ this._hor_gradient.addColorStop(1,"rgba(255, 255, 255, 0)");
433
+ }
434
+ this._ctx.fillStyle = this._hor_gradient;
435
+ this._ctx.fillRect(0, 0, size.x, gradient.size);
436
+
437
+ // vertical gradient
438
+ if (!this._vert_gradient) {
439
+ this._vert_gradient = this._ctx.createLinearGradient(0, 0, gradient.size, 0);
440
+ this._vert_gradient.addColorStop(0,"rgba(255, 255, 255, " + gradient.opacity + ")");
441
+ this._vert_gradient.addColorStop(1,"rgba(255, 255, 255, 0)");
442
+ }
443
+ this._ctx.fillStyle = this._vert_gradient;
444
+ this._ctx.fillRect(0, 0, gradient.size, size.y);
445
+
446
+ this._ctx.beginPath();
447
+ this._ctx.moveTo(0,0);
448
+ this._ctx.lineTo(size.x,0);
449
+ this._ctx.lineTo(size.x,size.y);
450
+ this._ctx.stroke();
451
+
452
+ this._calcInterval();
453
+ this._draw();
454
+ },
455
+
456
+ _onCanvasLoad: function () {
457
+ this.fire('load');
458
+ },
459
+
460
+ _calcInterval: function() {
461
+ const { zoomInterval } = this.options;
462
+ const zoom = this._map.getZoom();
463
+
464
+ if (undefined !== zoomInterval) {
465
+ // Manually set scale using a custom this.options.zoomInterval object
466
+ for (const idx in zoomInterval) {
467
+ const dict = zoomInterval[idx];
468
+ if (dict.start <= zoom && dict.end && dict.end >= zoom) {
469
+ this._interval = dict.interval;
470
+ break;
471
+ }
472
+ }
473
+ } else {
474
+ // Autamatically get current scale using L.Control.Scale
475
+ // Source: https://gis.stackexchange.com/a/198444
476
+ this._interval = L.Control.Scale.prototype._getRoundNum(
477
+ this._map
478
+ .containerPointToLatLng([0, this._map.getSize().y / 2 ])
479
+ .distanceTo(
480
+ this._map.containerPointToLatLng([L.Control.Scale.prototype.options.maxWidth, this._map.getSize().y / 2 ]
481
+ )
482
+ )
483
+ );
484
+ }
485
+
486
+ this._currZoom = zoom;
487
+
488
+ },
489
+
490
+ _draw: function() {
491
+ this._ctx.strokeStyle = this.options.color;
492
+ this._create_lat_ticks();
493
+ this._create_lon_ticks();
494
+
495
+ this._ctx.fillStyle = this.options.color;
496
+ this._ctx.font = this.options.font;
497
+
498
+ const size = this._map.getSize();
499
+ const text = this._interval >= 1000 ? (this._interval / 1000 + ' km') : this._interval + ' m';
500
+
501
+ this._ctx.textAlign = 'left';
502
+ this._ctx.textBaseline = 'middle';
503
+ this._ctx.fillText(text, +12, size.y / 2);
504
+
505
+ this._ctx.textAlign = 'center';
506
+ this._ctx.textBaseline = 'top';
507
+ this._ctx.fillText(text, size.x / 2, 12);
508
+ },
509
+
510
+ _create_lat_ticks: function() {
511
+ const { weight } = this.options;
512
+ const size = this._map.getSize();
513
+ const to_rad = Math.PI/180.0;
514
+ const center = this._merLength(this._map.containerPointToLatLng(L.point(0, size.y / 2)).lat * to_rad);
515
+ const top = this._merLength(this._map.containerPointToLatLng(L.point(0,0)).lat * to_rad);
516
+ const bottom = this._merLength(this._map.containerPointToLatLng(L.point(0, size.y)).lat * to_rad);
517
+
518
+ // draw major ticks
519
+ for (let i = center + this._interval / 2; i < top; i = i + this._interval) {
520
+ const phi = this._invmerLength(i);
521
+ if ((phi < this._LIMIT_PHI) && (phi > -this._LIMIT_PHI)) {
522
+ this._draw_lat_tick(phi, 10, weight * 1.5);
523
+ }
524
+ }
525
+ for (let i = center - this._interval / 2; i > bottom; i = i - this._interval) {
526
+ const phi = this._invmerLength(i);
527
+ if ((phi > -this._LIMIT_PHI) && (phi < this._LIMIT_PHI)) {
528
+ this._draw_lat_tick(phi, 10, weight * 1.5);
529
+ }
530
+ }
531
+
532
+ // draw minor ticks
533
+ for (let i = center; i < top; i = i + this._interval / 10.0) {
534
+ const phi = this._invmerLength(i);
535
+ if ((phi < this._LIMIT_PHI) && (phi > -this._LIMIT_PHI)) {
536
+ this._draw_lat_tick(phi, 4, weight);
537
+ }
538
+ }
539
+ for (let i = center - this._interval / 10; i > bottom; i = i - this._interval / 10.0) {
540
+ const phi = this._invmerLength(i);
541
+ if ((phi > -this._LIMIT_PHI) && (phi < this._LIMIT_PHI)) {
542
+ this._draw_lat_tick(phi, 4, weight);
543
+ }
544
+ }
545
+ },
546
+
547
+ _create_lon_ticks: function() {
548
+ const { weight } = this.options;
549
+ const size = this._map.getSize();
550
+ const to_rad = Math.PI/180.0;
551
+ const to_deg = 180.0/Math.PI;
552
+ const center = this._map.containerPointToLatLng(L.point(size.x / 2, 0));
553
+ const left = this._map.containerPointToLatLng(L.point(0, 0));
554
+ const right = this._map.containerPointToLatLng(L.point(size.x, 0));
555
+ const sinPhi2 = Math.pow(Math.sin(center.lat * to_rad), 2);
556
+ const N = this._a / Math.sqrt(1.0 - this._e2 * sinPhi2);
557
+ const dl = this._interval / (N * Math.cos(center.lat * to_rad)) * to_deg;
558
+
559
+ // draw major ticks
560
+ for (let i = center.lng + dl / 2; i < right.lng; i = i + dl) this._draw_lon_tick(i, 10, weight * 1.5);
561
+ for (let i = center.lng - dl / 2; i > left.lng; i = i - dl) this._draw_lon_tick(i, 10, weight * 1.5);
562
+
563
+ // draw minor ticks
564
+ for (let i = center.lng; i < right.lng; i = i + dl / 10) this._draw_lon_tick(i, 4, weight);
565
+ for (let i = center.lng - dl / 10; i > left.lng; i = i - dl / 10) this._draw_lon_tick(i, 4, weight);
566
+ },
567
+
568
+ _setCanvasPosition: function() {
569
+ let lt = this._map.containerPointToLayerPoint([0, 0]);
570
+ /**
571
+ * @TODO add support for "leaflet-rotate"
572
+ */
573
+ if (this._map._bearing) {
574
+ lt = this._map.rotatedPointToMapPanePoint(
575
+ this._map.containerPointToLayerPoint(L.point(this._map._container.getBoundingClientRect()))
576
+ );
577
+ }
578
+ L.DomUtil.setPosition(this._canvas, lt);
579
+ },
580
+
581
+ _latLngToCanvasPoint: function (latlng) {
582
+ return L.point(
583
+ this._map
584
+ .project(L.latLng(latlng))
585
+ ._subtract(this._map.getPixelOrigin())
586
+ ).add(this._map._getMapPanePos());
587
+ },
588
+
589
+ _draw_lat_tick: function (phi, lenght, weight) {
590
+ const to_deg = 180.0/Math.PI;
591
+ const size = this._map.getSize();
592
+ const y = this._latLngToCanvasPoint(L.latLng(phi * to_deg, 0.0)).y;
593
+ this._ctx.lineWidth = weight;
594
+ this._ctx.beginPath();
595
+ this._ctx.moveTo(0, y);
596
+ this._ctx.lineTo(+ lenght, y);
597
+ this._ctx.stroke();
598
+ },
599
+
600
+ _draw_lon_tick: function(lam, lenght, weight) {
601
+ const x = this._latLngToCanvasPoint(L.latLng(0.0, lam)).x;
602
+ this._ctx.lineWidth = weight;
603
+ this._ctx.beginPath();
604
+ this._ctx.moveTo(x, 0);
605
+ this._ctx.lineTo(x, lenght);
606
+ this._ctx.stroke();
607
+ },
608
+
609
+ _merLength: function(phi) {
610
+ const cos2 = Math.cos(2.0 * phi);
611
+ const sin2 = Math.sin(2.0 * phi);
612
+ return this._A * (phi + sin2 * (this._c1 + (this._c2 + (this._c3 + (this._c4 + this._c5 * cos2) * cos2) *cos2) * cos2));
613
+ },
614
+
615
+ _invmerLength: function(s) {
616
+ const psi = s/this._A;
617
+ const cos2 = Math.cos(2.0 * psi);
618
+ const sin2 = Math.sin(2.0 * psi);
619
+ return psi + sin2 * (this._ic1 + (this._ic2 + (this._ic3 + (this._ic4 + this._ic5 * cos2) * cos2) * cos2) * cos2);
620
+ },
621
+ });
622
+
623
+ L.control.edgeScale = function (options) {
624
+ return new L.Control.EdgeScale(options);
625
+ };
626
+
627
+ L.Map.mergeOptions({
628
+ edgeScaleControl: false
629
+ });
630
+
631
+ L.Map.addInitHook(function () {
632
+ if (this.options.edgeScaleControl) {
633
+ this.edgeScaleControl = new L.Control.EdgeScale();
634
+ this.addControl(this.edgeScaleControl);
635
+ }
636
+ });
@@ -0,0 +1 @@
1
+ L.Control.EdgeScale=L.Control.extend({options:{position:"bottomleft",icon:!0,coords:!0,bar:!0,onMove:!0,template:"{y} | {x}",projected:!1,formatProjected:"#.##0,000",latlngFormat:"DD",latlngDesignators:!0,latLngFormatter:void 0,iconStyle:{background:"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xml:space='preserve' viewBox='0 0 100 100'%3E%3Cg stroke='%23fff'%3E%3Ccircle cx='50' cy='50.2' r='3.9' stroke-width='2' /%3E%3Cpath stroke-width='3' d='M5 54h32a4 4 0 1 0 0-8H5a4 4 0 1 0 0 8z M54 5a4 4 0 1 0-8 0v32a4 4 0 1 0 8 0V5z M99 50c0-2-2-4-4-4H63a4 4 0 1 0 0 8h32c2 0 4-1 4-4zM46 95a4 4 0 1 0 8 0V64a4 4 0 1 0-8 0v31z'/%3E%3C/g%3E%3C/svg%3E%0A\")",width:"24px",height:"24px",left:"calc(50% - 12px)",top:"calc(50% - 12px)",content:"",display:"block",position:"absolute",zIndex:999,pointerEvents:"none"},containerStyle:{backgroundColor:"rgba(255, 255, 255, 0.7)",boxShadow:"0 0 5px #bbb",borderRadius:"3px",padding:"3px 2px",color:"#333",font:"11px/1.5 Consolas, monaco, monospace",writingMode:"vertical-lr"}},initialize:function(options){L.setOptions(this,options)},onAdd:function(map){return this.options.bar&&(this._scaleBar=new L.Control.EdgeScale.Layer(!0===this.options.bar?{}:this.options.bar).addTo(map)),this.options.icon&&(this._icon=L.DomUtil.create("div","leaflet-crosshair"),Object.assign(this._icon.style,this.options.iconStyle),map.getContainer().insertBefore(this._icon,map.getContainer().firstChild)),this._container=L.DomUtil.create("div","leaflet-control-mapcentercoord"),Object.assign(this._container.style,this.options.containerStyle),this.options.coords||(this._container.style.display="none"),L.DomEvent.disableClickPropagation(this._container),this._container.innerHTML=this._getMapCenterCoord(),map.on("move",this._onMapMove,this),map.on("moveend",this._onMapMove,this),this._container},onRemove:function(map){this.options.bar&&this._scaleBar.remove(),this.options.icon&&map.getContainer().removeChild(this._icon),map.off("move",this._onMapMove,this),map.off("moveend",this._onMapMove,this)},_onMapMove:function(e){(this.options.onMove||"moveend"===e.type)&&(this._container.innerHTML=this._getMapCenterCoord())},_getMapCenterCoord:function(){const center=this._map.getCenter();return this.options.projected?this._getProjectedCoord(this._map.options.crs.project(center)):this._getLatLngCoord(center)},_getProjectedCoord:function(center){return L.Util.template(this.options.template,{x:this._format(this.options.formatProjected,center.x),y:this._format(this.options.formatProjected,center.y)})},_getLatLngCoord:function(latLng){const{latLngFormatter:latLngFormatter,latlngFormat:latlngFormat,latlngDesignators:designators}=this.options;if(void 0!==latLngFormatter)return latLngFormatter(latLng.lat,latLng.lng);let lat,lng,deg,min,center={lat:latLng.lat,lng:latLng.lng,lng_neg:latLng.lng<0,lat_neg:latLng.lat<0};return center.lng<0&&(center.lng=Math.abs(center.lng)),center.lng>180&&(center.lng=360-center.lng,center.lng_neg=!center.lng_neg),center.lat<0&&(center.lat=Math.abs(center.lat)),"DM"===latlngFormat?(deg=parseInt(center.lng),lng=deg+"º "+this._format("00.000",60*(center.lng-deg))+"'",deg=parseInt(center.lat),lat=deg+"º "+this._format("00.000",60*(center.lat-deg))+"'"):"DMS"===latlngFormat?(deg=parseInt(center.lng),min=60*(center.lng-deg),lng=deg+"º "+this._format("00",parseInt(min))+"' "+this._format("00.0",60*(min-parseInt(min)))+"''",deg=parseInt(center.lat),min=60*(center.lat-deg),lat=deg+"º "+this._format("00",parseInt(min))+"' "+this._format("00.0",60*(min-parseInt(min)))+"''"):(lng=this._format("#0.00000",center.lng)+"º",lat=this._format("##0.00000",center.lat)+"º"),L.Util.template(this.options.template,{x:(!designators&&center.lng_neg?"-":"")+lng+(designators?center.lng_neg?" W":" E":""),y:(!designators&&center.lat_neg?"-":"")+lat+(designators?center.lat_neg?" S":" N":"")})},_format:function(m,v){if(!m||isNaN(+v))return v;let isNegative=(v="-"==m.charAt(0)?-v:+v)<0?v=-v:0,result=m.match(/[^\d\-\+#]/g),Decimal=result&&result[result.length-1]||".",Group=result&&result[1]&&result[0]||",";m=m.split(Decimal),v=+(v=v.toFixed(m[1]&&m[1].length))+"";let pos_trail_zero=m[1]&&m[1].lastIndexOf("0"),part=v.split(".");(!part[1]||part[1]&&part[1].length<=pos_trail_zero)&&(v=(+v).toFixed(pos_trail_zero+1));let szSep=m[0].split(Group);m[0]=szSep.join("");let pos_lead_zero=m[0]&&m[0].indexOf("0");if(pos_lead_zero>-1)for(;part[0].length<m[0].length-pos_lead_zero;)part[0]="0"+part[0];else 0==+part[0]&&(part[0]="");(v=v.split("."))[0]=part[0];var pos_separator=szSep[1]&&szSep[szSep.length-1].length;if(pos_separator){let integer=v[0],str="",offset=integer.length%pos_separator;for(let i=0,l=integer.length;i<l;i++)str+=integer.charAt(i),!((i-offset+1)%pos_separator)&&i<l-pos_separator&&(str+=Group);v[0]=str}return v[1]=m[1]&&v[1]?Decimal+v[1]:"",(isNegative?"-":"")+v[0]+v[1]}}),L.Control.EdgeScale.Layer=L.Layer.extend({includes:L.Evented?L.Evented.prototype:L.Mixin.Events,options:{opacity:1,weight:.8,gradient:{size:10,opacity:.5},color:"#000",font:"11px Arial",zoomInterval:[{start:0,end:2,interval:5e6},{start:3,end:3,interval:2e6},{start:4,end:4,interval:1e6},{start:5,end:5,interval:5e5},{start:6,end:7,interval:2e5},{start:8,end:8,interval:1e5},{start:9,end:9,interval:5e4},{start:10,end:10,interval:2e4},{start:11,end:11,interval:1e4},{start:12,end:12,interval:5e3},{start:13,end:13,interval:2e3},{start:14,end:14,interval:1e3},{start:15,end:15,interval:500},{start:16,end:16,interval:200},{start:17,end:17,interval:100},{start:18,end:18,interval:50},{start:19,end:19,interval:20},{start:20,end:20,interval:10}],pane:"edgescalePane"},initialize:function(options){L.setOptions(this,options);const a=this._a=6378137,b=this._b=6356752.3142,n=this._n=(a-b)/(a+b),a2=a*a,b2=b*b,n2=n*n,n3=n2*n,n4=n3*n,n5=n4*n;this._A=a*(1-n)*(1-n2)*(1+9/4*n2+225/64*n4),this._e2=(a2-b2)/a2,this._ic1=1.5*n-29/12*n3+6.9125*n5,this._ic2=21/8*n2-1537/128*n4,this._ic3=151/24*n3-32373/640*n5,this._ic4=1097/64*n4,this._ic5=8011/150*n5,this._c1=-1.5*n+31/24*n3-669/640*n5,this._c2=15/18*n2-435/128*n4,this._c3=-35/12*n3+8.1375*n5,this._c4=315/64*n4,this._c5=-8.6625*n5,this._LIMIT_PHI=1.484419982},onAdd:function(map){this._map=map;let pane=map.getPane(this.options.pane);pane||(pane=this._pane=map.createPane("edgescalePane",map.getPane("norotatePane")||map.getPane("mapPane")),pane.style.zIndex=625,pane.style.pointerEvents="none"),this._pane=pane,this._canvas||this._initCanvas(),this._pane.appendChild(this._canvas),map.on("viewreset",this._reset,this),map.on("move",this._reset,this),map.on("moveend",this._reset,this),map.on("rotate",this._reset,this),this._reset()},onRemove:function(map){this._pane.removeChild(this._canvas),map.off("viewreset",this._reset,this),map.off("move",this._reset,this),map.off("moveend",this._reset,this)},addTo:function(map){return map.addLayer(this),this},setOpacity:function(opacity){return this.options.opacity=opacity,L.DomUtil.setOpacity(this._canvas,this.options.opacity),this},bringToFront:function(){return this._canvas&&this._pane.appendChild(this._canvas),this},bringToBack:function(){return this._canvas&&this._pane.insertBefore(this._canvas,pane.firstChild),this},_initCanvas:function(){this._canvas=L.DomUtil.create("canvas",""),this._ctx=this._canvas.getContext("2d"),this.setOpacity(),L.extend(this._canvas,{onselectstart:L.Util.falseFn,onmousemove:L.Util.falseFn,onload:L.bind(this._onCanvasLoad,this)})},_reset:function(){var canvas=this._canvas,size=this._map.getSize();if(this._setCanvasPosition(),canvas.width=size.x,canvas.height=size.y,canvas.style.width=size.x+"px",canvas.style.height=size.y+"px",this._map._bearing)return;const{gradient:gradient}=this.options;this._hor_gradient||(this._hor_gradient=this._ctx.createLinearGradient(0,0,0,gradient.size),this._hor_gradient.addColorStop(0,"rgba(255, 255, 255, "+gradient.opacity+")"),this._hor_gradient.addColorStop(1,"rgba(255, 255, 255, 0)")),this._ctx.fillStyle=this._hor_gradient,this._ctx.fillRect(0,0,size.x,gradient.size),this._vert_gradient||(this._vert_gradient=this._ctx.createLinearGradient(0,0,gradient.size,0),this._vert_gradient.addColorStop(0,"rgba(255, 255, 255, "+gradient.opacity+")"),this._vert_gradient.addColorStop(1,"rgba(255, 255, 255, 0)")),this._ctx.fillStyle=this._vert_gradient,this._ctx.fillRect(0,0,gradient.size,size.y),this._ctx.beginPath(),this._ctx.moveTo(0,0),this._ctx.lineTo(size.x,0),this._ctx.lineTo(size.x,size.y),this._ctx.stroke(),this._calcInterval(),this._draw()},_onCanvasLoad:function(){this.fire("load")},_calcInterval:function(){const{zoomInterval:zoomInterval}=this.options,zoom=this._map.getZoom();if(void 0!==zoomInterval)for(const idx in zoomInterval){const dict=zoomInterval[idx];if(dict.start<=zoom&&dict.end&&dict.end>=zoom){this._interval=dict.interval;break}}else this._interval=L.Control.Scale.prototype._getRoundNum(this._map.containerPointToLatLng([0,this._map.getSize().y/2]).distanceTo(this._map.containerPointToLatLng([L.Control.Scale.prototype.options.maxWidth,this._map.getSize().y/2])));this._currZoom=zoom},_draw:function(){this._ctx.strokeStyle=this.options.color,this._create_lat_ticks(),this._create_lon_ticks(),this._ctx.fillStyle=this.options.color,this._ctx.font=this.options.font;const size=this._map.getSize(),text=this._interval>=1e3?this._interval/1e3+" km":this._interval+" m";this._ctx.textAlign="left",this._ctx.textBaseline="middle",this._ctx.fillText(text,12,size.y/2),this._ctx.textAlign="center",this._ctx.textBaseline="top",this._ctx.fillText(text,size.x/2,12)},_create_lat_ticks:function(){const{weight:weight}=this.options,size=this._map.getSize(),to_rad=Math.PI/180,center=this._merLength(this._map.containerPointToLatLng(L.point(0,size.y/2)).lat*to_rad),top=this._merLength(this._map.containerPointToLatLng(L.point(0,0)).lat*to_rad),bottom=this._merLength(this._map.containerPointToLatLng(L.point(0,size.y)).lat*to_rad);for(let i=center+this._interval/2;i<top;i+=this._interval){const phi=this._invmerLength(i);phi<this._LIMIT_PHI&&phi>-this._LIMIT_PHI&&this._draw_lat_tick(phi,10,1.5*weight)}for(let i=center-this._interval/2;i>bottom;i-=this._interval){const phi=this._invmerLength(i);phi>-this._LIMIT_PHI&&phi<this._LIMIT_PHI&&this._draw_lat_tick(phi,10,1.5*weight)}for(let i=center;i<top;i+=this._interval/10){const phi=this._invmerLength(i);phi<this._LIMIT_PHI&&phi>-this._LIMIT_PHI&&this._draw_lat_tick(phi,4,weight)}for(let i=center-this._interval/10;i>bottom;i-=this._interval/10){const phi=this._invmerLength(i);phi>-this._LIMIT_PHI&&phi<this._LIMIT_PHI&&this._draw_lat_tick(phi,4,weight)}},_create_lon_ticks:function(){const{weight:weight}=this.options,size=this._map.getSize(),to_rad=Math.PI/180,to_deg=180/Math.PI,center=this._map.containerPointToLatLng(L.point(size.x/2,0)),left=this._map.containerPointToLatLng(L.point(0,0)),right=this._map.containerPointToLatLng(L.point(size.x,0)),sinPhi2=Math.pow(Math.sin(center.lat*to_rad),2),N=this._a/Math.sqrt(1-this._e2*sinPhi2),dl=this._interval/(N*Math.cos(center.lat*to_rad))*to_deg;for(let i=center.lng+dl/2;i<right.lng;i+=dl)this._draw_lon_tick(i,10,1.5*weight);for(let i=center.lng-dl/2;i>left.lng;i-=dl)this._draw_lon_tick(i,10,1.5*weight);for(let i=center.lng;i<right.lng;i+=dl/10)this._draw_lon_tick(i,4,weight);for(let i=center.lng-dl/10;i>left.lng;i-=dl/10)this._draw_lon_tick(i,4,weight)},_setCanvasPosition:function(){let lt=this._map.containerPointToLayerPoint([0,0]);this._map._bearing&&(lt=this._map.rotatedPointToMapPanePoint(this._map.containerPointToLayerPoint(L.point(this._map._container.getBoundingClientRect())))),L.DomUtil.setPosition(this._canvas,lt)},_latLngToCanvasPoint:function(latlng){return L.point(this._map.project(L.latLng(latlng))._subtract(this._map.getPixelOrigin())).add(this._map._getMapPanePos())},_draw_lat_tick:function(phi,lenght,weight){const to_deg=180/Math.PI,size=this._map.getSize(),y=this._latLngToCanvasPoint(L.latLng(phi*to_deg,0)).y;this._ctx.lineWidth=weight,this._ctx.beginPath(),this._ctx.moveTo(0,y),this._ctx.lineTo(+lenght,y),this._ctx.stroke()},_draw_lon_tick:function(lam,lenght,weight){const x=this._latLngToCanvasPoint(L.latLng(0,lam)).x;this._ctx.lineWidth=weight,this._ctx.beginPath(),this._ctx.moveTo(x,0),this._ctx.lineTo(x,lenght),this._ctx.stroke()},_merLength:function(phi){const cos2=Math.cos(2*phi),sin2=Math.sin(2*phi);return this._A*(phi+sin2*(this._c1+(this._c2+(this._c3+(this._c4+this._c5*cos2)*cos2)*cos2)*cos2))},_invmerLength:function(s){const psi=s/this._A,cos2=Math.cos(2*psi),sin2=Math.sin(2*psi);return psi+sin2*(this._ic1+(this._ic2+(this._ic3+(this._ic4+this._ic5*cos2)*cos2)*cos2)*cos2)}}),L.control.edgeScale=function(options){return new L.Control.EdgeScale(options)},L.Map.mergeOptions({edgeScaleControl:!1}),L.Map.addInitHook((function(){this.options.edgeScaleControl&&(this.edgeScaleControl=new L.Control.EdgeScale,this.addControl(this.edgeScaleControl))}));