leaflet-rotate 0.5.0 → 0.5.2
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.
- package/README.md +17 -6
- package/dist/leaflet-rotate-src.js +606 -168
- package/dist/leaflet-rotate-src.js.map +4 -4
- package/dist/leaflet-rotate.js +1 -1
- package/dist/leaflet-rotate.js.map +4 -4
- package/lib/debug.js +62 -88
- package/lib/debug.min.js +16 -1
- package/package.json +1 -1
- package/src/control/Rotate.js +102 -118
- package/src/index.js +1 -0
- package/src/layer/tile/GridLayer.js +20 -1
- package/src/layer/vector/Renderer.js +37 -34
- package/src/map/Map.js +369 -72
- package/src/map/handler/CompassBearing.js +7 -0
- package/src/map/handler/ShiftKeyRotate.js +64 -13
- package/src/map/handler/TouchGestures.js +45 -3
- package/src/map/handler/TouchPitch.js +53 -0
- package/test/features/example.feature +8 -4
- package/test/features/gestures.feature +4 -0
- package/test/features/rotation.feature +35 -0
- package/test/index.html +559 -185
- package/test/steps/example.steps.js +56 -29
- package/test/steps/gestures.steps.js +55 -0
- package/test/steps/{bearing.steps.js → rotation.steps.js} +312 -15
package/lib/debug.js
CHANGED
|
@@ -8,21 +8,31 @@
|
|
|
8
8
|
this._map = map;
|
|
9
9
|
|
|
10
10
|
document.head.insertAdjacentHTML('beforeend', `<style>
|
|
11
|
-
.leaflet-rotate-
|
|
12
|
-
|
|
11
|
+
.leaflet-rotate-tools {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-wrap: wrap;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 4px 16px;
|
|
16
|
+
margin-bottom: 6px;
|
|
13
17
|
}
|
|
14
|
-
.leaflet-rotate-
|
|
15
|
-
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
/* max-width: 7em; */
|
|
18
|
-
text-overflow: ellipsis;
|
|
19
|
-
/* white-space: nowrap; */
|
|
18
|
+
.leaflet-rotate-tools input, .leaflet-rotate-tools button {
|
|
19
|
+
vertical-align: middle;
|
|
20
20
|
}
|
|
21
|
-
.leaflet-rotate-info
|
|
22
|
-
|
|
21
|
+
.leaflet-rotate-info {
|
|
22
|
+
border-collapse: collapse;
|
|
23
23
|
}
|
|
24
|
-
.leaflet-rotate-info th {
|
|
24
|
+
.leaflet-rotate-info td, .leaflet-rotate-info th {
|
|
25
|
+
padding: 1px 12px 1px 0;
|
|
25
26
|
text-align: left;
|
|
27
|
+
white-space: nowrap;
|
|
28
|
+
vertical-align: top;
|
|
29
|
+
}
|
|
30
|
+
.leaflet-rotate-info td:nth-of-type(1), .leaflet-rotate-info td:nth-of-type(2) {
|
|
31
|
+
font-family: monospace;
|
|
32
|
+
min-width: 12ch;
|
|
33
|
+
}
|
|
34
|
+
.leaflet-rotate-info tr[title] {
|
|
35
|
+
cursor: help;
|
|
26
36
|
}
|
|
27
37
|
div.crosshair {
|
|
28
38
|
position:absolute;
|
|
@@ -115,73 +125,35 @@
|
|
|
115
125
|
`<div class="crosshair c1"></div><div class="crosshair c2"></div>`
|
|
116
126
|
);
|
|
117
127
|
|
|
118
|
-
|
|
128
|
+
const html = `
|
|
129
|
+
<div class="leaflet-rotate-tools">
|
|
130
|
+
<span>Bearing
|
|
131
|
+
${[0, 15, 30, 45, 60, 75, 90, 180].map(b => `<button onclick="L.Rotate._map.setBearing(${b});" title="map.setBearing(${b});">${b}</button>`).join('')}
|
|
132
|
+
<input title="increase/decrease rotation angle" type="range" min="0" max="360" step="1" value="0" name="rho" id='rho_input' /><span id='rho'></span>
|
|
133
|
+
</span>
|
|
134
|
+
<label>Bounds padding <input title="increase/decrease padding bounds" type="range" min="-0.25" max="0" value="0" step="0.01" name="pad" id='pad_input' /></label>
|
|
135
|
+
<span>Markers
|
|
136
|
+
<button onclick="L.Rotate.randomRotateMarkers(L.Rotate._map);" title="markers.setRotation(Math.random() * Math.PI / 30 + 0.1)">Rotate</button>
|
|
137
|
+
<button onclick="L.Rotate.randomMarkers(L.Rotate._map, 100);" title="add 100 random markers to map">Add 100</button>
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
119
140
|
<table class="leaflet-rotate-info">
|
|
120
|
-
<tr>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<
|
|
135
|
-
|
|
136
|
-
</td>
|
|
137
|
-
<td>
|
|
138
|
-
<button onclick="L.Rotate.randomRotateMarkers(L.Rotate._map);" title="markers.setRotation(Math.random() * Math.PI / 30 + 0.1)">Rotate Markers</button>
|
|
139
|
-
<button onclick="L.Rotate.randomMarkers(L.Rotate._map, 100);" title="add 100 random markers to map">Add Markers</button>
|
|
140
|
-
</td>
|
|
141
|
-
</tr>
|
|
142
|
-
<tr>
|
|
143
|
-
<th>LatLng </th>
|
|
144
|
-
<td id='llx'></td>
|
|
145
|
-
<td id='lly'></td>
|
|
146
|
-
<td class='long'>LatLng of mouse pointer</td>
|
|
147
|
-
</tr>
|
|
148
|
-
<tr>
|
|
149
|
-
<th>Rel to pane </th>
|
|
150
|
-
<td id='lyx'></td>
|
|
151
|
-
<td id='lyy'></td>
|
|
152
|
-
<td class='long'>Mouse pointer pixel coords relative to _rotatePane</td>
|
|
153
|
-
</tr>
|
|
154
|
-
<tr>
|
|
155
|
-
<th>Rel to container</th>
|
|
156
|
-
<td id='cnx'></td>
|
|
157
|
-
<td id='cny'></td>
|
|
158
|
-
<td class='long'>Mouse pointer coords relative to map <div></td>
|
|
159
|
-
</tr>
|
|
160
|
-
<tr>
|
|
161
|
-
<th>Pivot </th>
|
|
162
|
-
<td id='pvx'></td>
|
|
163
|
-
<td id='pvy'></td>
|
|
164
|
-
<td class='long'>Last _rotationPane pivot pixel coords relative to _rotationPane</td>
|
|
165
|
-
</tr>
|
|
166
|
-
<tr>
|
|
167
|
-
<th>Pane offset </th>
|
|
168
|
-
<td id='pox'></td>
|
|
169
|
-
<td id='poy'></td>
|
|
170
|
-
<td class='long'>Pixel offset of _mapPane</td>
|
|
171
|
-
</tr>
|
|
172
|
-
<tr>
|
|
173
|
-
<th>Pixel origin </th>
|
|
174
|
-
<td id='ogx'></td>
|
|
175
|
-
<td id='ogy'></td>
|
|
176
|
-
<td class='long'>Negative pixel coords of the (0,0) CRS point relative to _rotatePane</td>
|
|
177
|
-
</tr>
|
|
178
|
-
<tr>
|
|
179
|
-
<th>Pixel bounds </th>
|
|
180
|
-
<td id='ogbx'></td>
|
|
181
|
-
<td id='ogby'></td>
|
|
182
|
-
<td class='long'>Bounds of the current map view in projected pixel coordinates {min, max}.</td>
|
|
183
|
-
</tr>
|
|
184
|
-
</table>`);
|
|
141
|
+
<tr><th></th><th>x / lng</th><th>y / lat</th></tr>
|
|
142
|
+
${[
|
|
143
|
+
['LatLng', 'LatLng of mouse pointer', 'llx', 'lly'],
|
|
144
|
+
['Rel to pane', 'Mouse pointer pixel coords relative to _rotatePane', 'lyx', 'lyy'],
|
|
145
|
+
['Rel to container', 'Mouse pointer coords relative to map <div>', 'cnx', 'cny'],
|
|
146
|
+
['Pivot', 'Last _rotationPane pivot pixel coords relative to _rotationPane', 'pvx', 'pvy'],
|
|
147
|
+
['Pane offset', 'Pixel offset of _mapPane', 'pox', 'poy'],
|
|
148
|
+
['Pixel origin', 'Negative pixel coords of the (0,0) CRS point relative to _rotatePane', 'ogx', 'ogy'],
|
|
149
|
+
['Pixel bounds (min / max)', 'Bounds of the current map view in projected pixel coordinates {min, max}', 'ogbx', 'ogby'],
|
|
150
|
+
].map(([label, title, x, y]) => `<tr title="${title.replace('<', '<').replace('>', '>')}"><th>${label}</th><td id='${x}'></td><td id='${y}'></td></tr>`).join('')}
|
|
151
|
+
</table>`;
|
|
152
|
+
if (options.container) {
|
|
153
|
+
options.container.insertAdjacentHTML('beforeend', `<details open><summary>Debug</summary>${html}</details>`);
|
|
154
|
+
} else {
|
|
155
|
+
map._container.insertAdjacentHTML('afterend', '<hr>' + html);
|
|
156
|
+
}
|
|
185
157
|
|
|
186
158
|
map.___rotatePixelOrigin = L.DomUtil.create('div', 'pixelorigin', map._rotatePane);
|
|
187
159
|
map.___rotatePivotDot = L.DomUtil.create('div', 'pivot', map._mapPane);
|
|
@@ -260,12 +232,13 @@
|
|
|
260
232
|
},
|
|
261
233
|
|
|
262
234
|
logMouse(e) {
|
|
263
|
-
|
|
264
|
-
document.getElementById('
|
|
265
|
-
document.getElementById('
|
|
266
|
-
document.getElementById('
|
|
267
|
-
document.getElementById('
|
|
268
|
-
document.getElementById('
|
|
235
|
+
const fmt = (n, d) => +n.toFixed(d);
|
|
236
|
+
document.getElementById('llx').innerHTML = fmt(e.latlng.lng, 6);
|
|
237
|
+
document.getElementById('lly').innerHTML = fmt(e.latlng.lat, 6);
|
|
238
|
+
document.getElementById('lyx').innerHTML = fmt(e.layerPoint.x, 1);
|
|
239
|
+
document.getElementById('lyy').innerHTML = fmt(e.layerPoint.y, 1);
|
|
240
|
+
document.getElementById('cnx').innerHTML = fmt(e.containerPoint.x, 1);
|
|
241
|
+
document.getElementById('cny').innerHTML = fmt(e.containerPoint.y, 1);
|
|
269
242
|
},
|
|
270
243
|
|
|
271
244
|
/**
|
|
@@ -288,8 +261,8 @@
|
|
|
288
261
|
*/
|
|
289
262
|
updatePixelOrigin() {
|
|
290
263
|
if (this._pixelOrigin) {
|
|
291
|
-
document.getElementById('ogx').innerHTML = this._pixelOrigin.x;
|
|
292
|
-
document.getElementById('ogy').innerHTML = this._pixelOrigin.y;
|
|
264
|
+
document.getElementById('ogx').innerHTML = +this._pixelOrigin.x.toFixed(1);
|
|
265
|
+
document.getElementById('ogy').innerHTML = +this._pixelOrigin.y.toFixed(1);
|
|
293
266
|
this.___rotatePixelOrigin.style.left = (-this._pixelOrigin.x - 1) + 'px';
|
|
294
267
|
this.___rotatePixelOrigin.style.top = (-this._pixelOrigin.y - 1) + 'px';
|
|
295
268
|
}
|
|
@@ -530,8 +503,9 @@
|
|
|
530
503
|
// ]);
|
|
531
504
|
// // paddedSize = paddedBounds.getSize();
|
|
532
505
|
|
|
533
|
-
|
|
534
|
-
document.getElementById('
|
|
506
|
+
const xy = p => `${+p.x.toFixed(1)}, ${+p.y.toFixed(1)}`;
|
|
507
|
+
document.getElementById('ogbx').innerHTML = xy(pixelBounds.min);
|
|
508
|
+
document.getElementById('ogby').innerHTML = xy(pixelBounds.max);
|
|
535
509
|
},
|
|
536
510
|
|
|
537
511
|
resetHeading(e) {
|
|
@@ -551,4 +525,4 @@
|
|
|
551
525
|
}
|
|
552
526
|
|
|
553
527
|
};
|
|
554
|
-
})();
|
|
528
|
+
})();
|
package/lib/debug.min.js
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
(function(){const e=Object.isExtensible(globalThis.L)?globalThis.L:Object.assign({},globalThis.L);globalThis.L=e,e.Rotate={debug(t,n={}){this._map=t,document.head.insertAdjacentHTML("beforeend",'<style>.leaflet-rotate-info{width:100%}.leaflet-rotate-info td{overflow:hidden;text-overflow:ellipsis}.leaflet-rotate-info td:nth-of-type(3){font-family:Monospace}.leaflet-rotate-info th{text-align:left}div.crosshair{position:absolute;width:1px;height:1px;border:1px solid green;z-index:100;left:50%;right:50%}div.pivot,div.pixelorigin{position:absolute;width:1px;height:1px;border:5px solid yellow;border-radius:100%;z-index:1000}div.pivot:before{left:-1.5em;bottom:-2em;position:absolute;content:"PIVOT";color:#ff0}div.pixelorigin:before{left:-1.75em;top:-2em;position:absolute;content:"ORIGIN";color:#ff0}div.panebounds.main{position:absolute;width:800px;height:600px;box-sizing:border-box;border:1px solid red;border-bottom:1px red dashed;border-right:1px red dashed;z-index:100}div.panebounds.rotate{position:absolute;width:800px;height:600px;box-sizing:border-box;border:1px solid purple;border-bottom:1px purple dashed;border-right:1px purple dashed;z-index:100}div.crosshair.c1{left:calc(50% - 5px);top:50%;height:0;width:10px}div.crosshair.c2{left:50%;top:calc(50% - 5px);width:0;height:10px}#rho_input{vertical-align:middle}.panebounds:before{padding:1ch}.panebounds.rotate:before{float:right;content:"ROTATED"}.panebounds.main:before{float:left;content:"MAIN"}</style>'),t._container.insertAdjacentHTML("afterbegin",'<div class="crosshair c1"></div><div class="crosshair c2"></div>'),t._container.insertAdjacentHTML("afterend",`<hr><table class="leaflet-rotate-info"><tr><td><button onclick="L.Rotate._map.setBearing(0);" title="map.setBearing(0);"> 0</button><button onclick="L.Rotate._map.setBearing(15);" title="map.setBearing(15);">15</button><button onclick="L.Rotate._map.setBearing(30);" title="map.setBearing(30);">30</button><button onclick="L.Rotate._map.setBearing(45);" title="map.setBearing(45);">45</button><button onclick="L.Rotate._map.setBearing(60);" title="map.setBearing(60);">60</button><button onclick="L.Rotate._map.setBearing(75);" title="map.setBearing(75);">75</button><button onclick="L.Rotate._map.setBearing(90);" title="map.setBearing(90);">90</button><button onclick="L.Rotate._map.setBearing(180);" title="map.setBearing(180);">180</button></td><td><input title="increase/decrease rotation angle" type="range" min="0" max="360" step="1" value="0" name="rho" id='rho_input' /><span id='rho'></span></td><td><input title="increase/decrease padding bounds" type="range" min="-0.25" max="0" value="0" step="0.01" name="pad" id='pad_input' /></td><td><button onclick="L.Rotate.randomRotateMarkers(L.Rotate._map);" title="markers.setRotation(Math.random() * Math.PI / 30 + 0.1)">Rotate Markers</button><button onclick="L.Rotate.randomMarkers(L.Rotate._map, 100);" title="add 100 random markers to map">Add Markers</button></td></tr><tr><th>LatLng </th><td id='llx'></td><td id='lly'></td><td class='long'>LatLng of mouse pointer</td></tr><tr><th>Rel to pane </th><td id='lyx'></td><td id='lyy'></td><td class='long'>Mouse pointer pixel coords relative to _rotatePane</td></tr><tr><th>Rel to container</th><td id='cnx'></td><td id='cny'></td><td class='long'>Mouse pointer coords relative to map <div></td></tr><tr><th>Pivot </th><td id='pvx'></td><td id='pvy'></td><td class='long'>Last _rotationPane pivot pixel coords relative to _rotationPane</td></tr><tr><th>Pane offset </th><td id='pox'></td><td id='poy'></td><td class='long'>Pixel offset of _mapPane</td></tr><tr><th>Pixel origin </th><td id='ogx'></td><td id='ogy'></td><td class='long'>Negative pixel coords of the (0,0) CRS point relative to _rotatePane</td></tr><tr><th>Pixel bounds </th><td id='ogbx'></td><td id='ogby'></td><td class='long'>Bounds of the current map view in projected pixel coordinates {min, max}.</td></tr></table>`),t.___rotatePixelOrigin=e.DomUtil.create("div","pixelorigin",t._rotatePane),t.___rotatePivotDot=e.DomUtil.create("div","pivot",t._mapPane),this.___rotateBounds=e.DomUtil.create("div","panebounds rotate",t._rotatePane),this.___mainBounds=e.DomUtil.create("div","panebounds main",t._mapPane),this.updatePaneBounds();const i=document.getElementById("rho_input"),o=document.getElementById("pad_input");if(i.addEventListener("change",e.Rotate.rotate.bind(this)),i.addEventListener("mousemove",e.Rotate.rotate.bind(this)),o.addEventListener("change",e.Rotate.displayCenter.bind(this)),o.addEventListener("mousemove",e.Rotate.displayCenter.bind(this)),o.addEventListener("change",e.Rotate.displayBounds.bind(this)),o.addEventListener("mousemove",e.Rotate.displayBounds.bind(this)),o.addEventListener("change",e.Rotate.displayLayersBounds.bind(this)),o.addEventListener("mousemove",e.Rotate.displayLayersBounds.bind(this)),o.addEventListener("change",e.Rotate.displayPixelBounds.bind(this)),o.addEventListener("mousemove",e.Rotate.displayPixelBounds.bind(this)),t.on("move rotate zoomend",this.updatePanePos),t.on("resize",this.updatePaneBounds.bind(this)),t.on("rotate zoomend load",this.updatePixelOrigin),t.on("rotate",this.updateRhoInput),t.on("rotate",this.updatePivotDot),t.on("moveend zoomend resetview rotate",this.displayCenter.bind(this)),t.on("moveend zoomend resetview rotate",this.displayBounds.bind(this)),t.on("moveend zoomend resetview rotate",this.displayLayersBounds.bind(this)),t.on("moveend zoomend resetview rotate",this.displayPixelBounds.bind(this)),this.updatePixelOrigin.call(t),this.updateRhoInput.call(t),this.updatePivotDot.call(t),this.displayCenter.call(this),this.displayBounds.call(this),this.displayLayersBounds.call(this),this.displayPixelBounds.call(this),e.Browser.mobile&&t.compassBearing.enable(),e.version.startsWith("2.")){const a=t.getContainer();a.addEventListener("mousemove",function(r){const s=a.getBoundingClientRect(),d=new e.Point(r.clientX-s.left-a.clientLeft,r.clientY-s.top-a.clientTop);e.Rotate.logMouse.call(t,{latlng:t.containerPointToLatLng(d),layerPoint:t.containerPointToLayerPoint(d),containerPoint:d})})}else t.on("mousemove",this.logMouse);t.on("locationfound",this.resetHeading),n.log&&t.on("click movestart move moveend zoomstart zoomend locationfound locationerror locationfound",this.logEvent)},logEvent(t){console.log(t,t.type)},logMouse(t){document.getElementById("llx").innerHTML=t.latlng.lng,document.getElementById("lly").innerHTML=t.latlng.lat,document.getElementById("lyx").innerHTML=t.layerPoint.x,document.getElementById("lyy").innerHTML=t.layerPoint.y,document.getElementById("cnx").innerHTML=t.containerPoint.x,document.getElementById("cny").innerHTML=t.containerPoint.y},rotate(t){return(t.type!=="mousemove"||t.buttons!==0)&&this._map.setBearing(t.target.valueAsNumber)},updatePaneBounds(){const t=this._map.getSize();for(const n of[this.___rotateBounds,this.___mainBounds])n.style.width=t.x+"px",n.style.height=t.y+"px"},updatePixelOrigin(){this._pixelOrigin&&(document.getElementById("ogx").innerHTML=this._pixelOrigin.x,document.getElementById("ogy").innerHTML=this._pixelOrigin.y,this.___rotatePixelOrigin.style.left=-this._pixelOrigin.x-1+"px",this.___rotatePixelOrigin.style.top=-this._pixelOrigin.y-1+"px")},updatePivotDot(){this._pivot&&(document.getElementById("pvx").innerHTML=this._pivot.x,document.getElementById("pvy").innerHTML=this._pivot.y,this.___rotatePivotDot.style.left=this._pivot.x-1+"px",this.___rotatePivotDot.style.top=this._pivot.y-1+"px")},updatePanePos(){const t=this._getMapPanePos();document.getElementById("pox").innerHTML=t.x,document.getElementById("poy").innerHTML=t.y},updateRhoInput(){document.getElementById("rho_input").value=this.getBearing()},rotateOneDegree(){const t=document.getElementById("rho_input").valueAsNumber++;this.setBearing(t<360?t:0)},randomRotateMarkers(t){const n=[],i=[];for(const a in t._layers)t._layers[a]instanceof e.Marker&&(n.push(t._layers[a]),i.push(Math.random()*Math.PI/30+.1));function o(a){for(let r=0;r<n.length;r++)n[r].setRotation(n[r].options.rotation+i[r]);--a&&setTimeout(o.bind(null,a),20)}o(25)},randomMarkers(t,n){t=this._map;const i=function(){for(let o=0;o<n;o++){const a=t.getBounds(),r=a.getSouthWest(),s=a.getNorthEast(),d=s.lng-r.lng,l=s.lat-r.lat,c=new e.Marker(new e.LatLng(r.lat+l*Math.random(),r.lng+d*Math.random()));e.Rotate.___cluster.addLayer(c)}};this.___cluster?typeof e.markerClusterGroup=="function"&&i():this.___cluster=import("https://unpkg.com/leaflet.markercluster@1.4.1/src/index.js").then(o=>{document.head.insertAdjacentHTML("beforeend",'<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css"><link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css">'),Object.assign(globalThis.L,o),this.___cluster=e.markerClusterGroup({spiderfyOnMaxZoom:!1,showCoverageOnHover:!1,zoomToBoundsOnClick:!1}).on("clusterclick",a=>a.layer.spiderfy()).addTo(t),i()})},displayCenter(){const t=this._map;this.___centerMarker&&this.___centerMarker.remove(),this.___centerMarker=new e.CircleMarker(t.getCenter(),{radius:3,color:"red"}).addTo(t).bindTooltip("<b>center</b>",{direction:"right",permanent:!0}),this.___centerMarker.getTooltip().getElement().setAttribute("tabindex","-1")},displayBounds(){const t=this._map;this.___bounds&&this.___bounds.remove();const n=document.getElementById("pad_input").valueAsNumber,i=t.getBounds().pad(n);this.___bounds=new e.Rectangle(i,{fill:!1,lineCap:"square",color:"yellow",dashArray:"5, 10",weight:2}).addTo(t)},displayLayersBounds(){for(const t of["path","polygon","circle"]){if(globalThis[t])this[`___${t}bounds`]&&this[`___${t}bounds`].remove();else continue;const i=globalThis[t].getBounds();this[`___${t}bounds`]=new e.FeatureGroup([new e.Rectangle(i,{fill:!1,lineCap:"square",color:"yellow",dashArray:"5, 10",weight:1,opacity:.5}),new e.Marker(i.getNorthWest(),{scale:.5,rotation:-45*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>NW</b>",{direction:"center"}),new e.Marker(i.getNorthEast(),{scale:.5,rotation:45*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>NE</b>",{direction:"center"}),new e.Marker(i.getSouthEast(),{scale:.5,rotation:135*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>SE</b>",{direction:"center"}),new e.Marker(i.getSouthWest(),{scale:.5,rotation:-135*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>SW</b>",{direction:"center"})]).addTo(map).eachLayer(o=>{o.getElement&&o.getElement()&&(o.getElement().style.filter="hue-rotate(150deg)")})}},displayPixelBounds(){const t=this._map;this.___corners&&this.___corners.remove(),this.___pixelbounds&&this.___pixelbounds.remove();const n=t.getSize(),i=t.getPixelBounds(),o=new e.Point(0,0),a=new e.Point(n.x,0),r=new e.Point(n.x,n.y),s=new e.Point(0,n.y),d=new e.Marker(t.containerPointToLatLng(o),{title:"Top Left corner",rotation:-45*(Math.PI/180)}),l=new e.Marker(t.containerPointToLatLng(a),{title:"Top Right corner",rotation:45*(Math.PI/180)}),c=new e.Marker(t.containerPointToLatLng(r),{title:"Bottom Right corner",rotation:135*(Math.PI/180)}),h=new e.Marker(t.containerPointToLatLng(s),{title:"Bottom Left corner",rotation:-135*(Math.PI/180)});this.___corners=new e.FeatureGroup([d,l,c,h]).addTo(t).eachLayer(u=>u.getElement().style.filter="hue-rotate(150deg)"),document.getElementById("ogbx").innerHTML=JSON.stringify(i.min),document.getElementById("ogby").innerHTML=JSON.stringify(i.max)},resetHeading(t){t.heading!==null&&this.setBearing(t.heading)},locate(){this._map.locate({setView:!0,enableHighAccuracy:!0})}}})();
|
|
1
|
+
(function(){const e=Object.isExtensible(globalThis.L)?globalThis.L:Object.assign({},globalThis.L);globalThis.L=e,e.Rotate={debug(t,o={}){this._map=t,document.head.insertAdjacentHTML("beforeend",'<style>.leaflet-rotate-tools{display:flex;flex-wrap:wrap;align-items:center;gap:4px 16px;margin-bottom:6px}.leaflet-rotate-tools input,.leaflet-rotate-tools button{vertical-align:middle}.leaflet-rotate-info{border-collapse:collapse}.leaflet-rotate-info td,.leaflet-rotate-info th{padding:1px 12px 1px 0;text-align:left;white-space:nowrap;vertical-align:top}.leaflet-rotate-info td:nth-of-type(1),.leaflet-rotate-info td:nth-of-type(2){font-family:monospace;min-width:12ch}.leaflet-rotate-info tr[title]{cursor:help}div.crosshair{position:absolute;width:1px;height:1px;border:1px solid green;z-index:100;left:50%;right:50%}div.pivot,div.pixelorigin{position:absolute;width:1px;height:1px;border:5px solid yellow;border-radius:100%;z-index:1000}div.pivot:before{left:-1.5em;bottom:-2em;position:absolute;content:"PIVOT";color:#ff0}div.pixelorigin:before{left:-1.75em;top:-2em;position:absolute;content:"ORIGIN";color:#ff0}div.panebounds.main{position:absolute;width:800px;height:600px;box-sizing:border-box;border:1px solid red;border-bottom:1px red dashed;border-right:1px red dashed;z-index:100}div.panebounds.rotate{position:absolute;width:800px;height:600px;box-sizing:border-box;border:1px solid purple;border-bottom:1px purple dashed;border-right:1px purple dashed;z-index:100}div.crosshair.c1{left:calc(50% - 5px);top:50%;height:0;width:10px}div.crosshair.c2{left:50%;top:calc(50% - 5px);width:0;height:10px}#rho_input{vertical-align:middle}.panebounds:before{padding:1ch}.panebounds.rotate:before{float:right;content:"ROTATED"}.panebounds.main:before{float:left;content:"MAIN"}</style>'),t._container.insertAdjacentHTML("afterbegin",'<div class="crosshair c1"></div><div class="crosshair c2"></div>');const i=`
|
|
2
|
+
<div class="leaflet-rotate-tools">
|
|
3
|
+
<span>Bearing
|
|
4
|
+
${[0,15,30,45,60,75,90,180].map(n=>`<button onclick="L.Rotate._map.setBearing(${n});" title="map.setBearing(${n});">${n}</button>`).join("")}
|
|
5
|
+
<input title="increase/decrease rotation angle" type="range" min="0" max="360" step="1" value="0" name="rho" id='rho_input' /><span id='rho'></span>
|
|
6
|
+
</span>
|
|
7
|
+
<label>Bounds padding <input title="increase/decrease padding bounds" type="range" min="-0.25" max="0" value="0" step="0.01" name="pad" id='pad_input' /></label>
|
|
8
|
+
<span>Markers
|
|
9
|
+
<button onclick="L.Rotate.randomRotateMarkers(L.Rotate._map);" title="markers.setRotation(Math.random() * Math.PI / 30 + 0.1)">Rotate</button>
|
|
10
|
+
<button onclick="L.Rotate.randomMarkers(L.Rotate._map, 100);" title="add 100 random markers to map">Add 100</button>
|
|
11
|
+
</span>
|
|
12
|
+
</div>
|
|
13
|
+
<table class="leaflet-rotate-info">
|
|
14
|
+
<tr><th></th><th>x / lng</th><th>y / lat</th></tr>
|
|
15
|
+
${[["LatLng","LatLng of mouse pointer","llx","lly"],["Rel to pane","Mouse pointer pixel coords relative to _rotatePane","lyx","lyy"],["Rel to container","Mouse pointer coords relative to map <div>","cnx","cny"],["Pivot","Last _rotationPane pivot pixel coords relative to _rotationPane","pvx","pvy"],["Pane offset","Pixel offset of _mapPane","pox","poy"],["Pixel origin","Negative pixel coords of the (0,0) CRS point relative to _rotatePane","ogx","ogy"],["Pixel bounds (min / max)","Bounds of the current map view in projected pixel coordinates {min, max}","ogbx","ogby"]].map(([n,s,d,l])=>`<tr title="${s.replace("<","<").replace(">",">")}"><th>${n}</th><td id='${d}'></td><td id='${l}'></td></tr>`).join("")}
|
|
16
|
+
</table>`;o.container?o.container.insertAdjacentHTML("beforeend",`<details open><summary>Debug</summary>${i}</details>`):t._container.insertAdjacentHTML("afterend","<hr>"+i),t.___rotatePixelOrigin=e.DomUtil.create("div","pixelorigin",t._rotatePane),t.___rotatePivotDot=e.DomUtil.create("div","pivot",t._mapPane),this.___rotateBounds=e.DomUtil.create("div","panebounds rotate",t._rotatePane),this.___mainBounds=e.DomUtil.create("div","panebounds main",t._mapPane),this.updatePaneBounds();const r=document.getElementById("rho_input"),a=document.getElementById("pad_input");if(r.addEventListener("change",e.Rotate.rotate.bind(this)),r.addEventListener("mousemove",e.Rotate.rotate.bind(this)),a.addEventListener("change",e.Rotate.displayCenter.bind(this)),a.addEventListener("mousemove",e.Rotate.displayCenter.bind(this)),a.addEventListener("change",e.Rotate.displayBounds.bind(this)),a.addEventListener("mousemove",e.Rotate.displayBounds.bind(this)),a.addEventListener("change",e.Rotate.displayLayersBounds.bind(this)),a.addEventListener("mousemove",e.Rotate.displayLayersBounds.bind(this)),a.addEventListener("change",e.Rotate.displayPixelBounds.bind(this)),a.addEventListener("mousemove",e.Rotate.displayPixelBounds.bind(this)),t.on("move rotate zoomend",this.updatePanePos),t.on("resize",this.updatePaneBounds.bind(this)),t.on("rotate zoomend load",this.updatePixelOrigin),t.on("rotate",this.updateRhoInput),t.on("rotate",this.updatePivotDot),t.on("moveend zoomend resetview rotate",this.displayCenter.bind(this)),t.on("moveend zoomend resetview rotate",this.displayBounds.bind(this)),t.on("moveend zoomend resetview rotate",this.displayLayersBounds.bind(this)),t.on("moveend zoomend resetview rotate",this.displayPixelBounds.bind(this)),this.updatePixelOrigin.call(t),this.updateRhoInput.call(t),this.updatePivotDot.call(t),this.displayCenter.call(this),this.displayBounds.call(this),this.displayLayersBounds.call(this),this.displayPixelBounds.call(this),e.Browser.mobile&&t.compassBearing.enable(),e.version.startsWith("2.")){const n=t.getContainer();n.addEventListener("mousemove",function(s){const d=n.getBoundingClientRect(),l=new e.Point(s.clientX-d.left-n.clientLeft,s.clientY-d.top-n.clientTop);e.Rotate.logMouse.call(t,{latlng:t.containerPointToLatLng(l),layerPoint:t.containerPointToLayerPoint(l),containerPoint:l})})}else t.on("mousemove",this.logMouse);t.on("locationfound",this.resetHeading),o.log&&t.on("click movestart move moveend zoomstart zoomend locationfound locationerror locationfound",this.logEvent)},logEvent(t){console.log(t,t.type)},logMouse(t){const o=(i,r)=>+i.toFixed(r);document.getElementById("llx").innerHTML=o(t.latlng.lng,6),document.getElementById("lly").innerHTML=o(t.latlng.lat,6),document.getElementById("lyx").innerHTML=o(t.layerPoint.x,1),document.getElementById("lyy").innerHTML=o(t.layerPoint.y,1),document.getElementById("cnx").innerHTML=o(t.containerPoint.x,1),document.getElementById("cny").innerHTML=o(t.containerPoint.y,1)},rotate(t){return(t.type!=="mousemove"||t.buttons!==0)&&this._map.setBearing(t.target.valueAsNumber)},updatePaneBounds(){const t=this._map.getSize();for(const o of[this.___rotateBounds,this.___mainBounds])o.style.width=t.x+"px",o.style.height=t.y+"px"},updatePixelOrigin(){this._pixelOrigin&&(document.getElementById("ogx").innerHTML=+this._pixelOrigin.x.toFixed(1),document.getElementById("ogy").innerHTML=+this._pixelOrigin.y.toFixed(1),this.___rotatePixelOrigin.style.left=-this._pixelOrigin.x-1+"px",this.___rotatePixelOrigin.style.top=-this._pixelOrigin.y-1+"px")},updatePivotDot(){this._pivot&&(document.getElementById("pvx").innerHTML=this._pivot.x,document.getElementById("pvy").innerHTML=this._pivot.y,this.___rotatePivotDot.style.left=this._pivot.x-1+"px",this.___rotatePivotDot.style.top=this._pivot.y-1+"px")},updatePanePos(){const t=this._getMapPanePos();document.getElementById("pox").innerHTML=t.x,document.getElementById("poy").innerHTML=t.y},updateRhoInput(){document.getElementById("rho_input").value=this.getBearing()},rotateOneDegree(){const t=document.getElementById("rho_input").valueAsNumber++;this.setBearing(t<360?t:0)},randomRotateMarkers(t){const o=[],i=[];for(const a in t._layers)t._layers[a]instanceof e.Marker&&(o.push(t._layers[a]),i.push(Math.random()*Math.PI/30+.1));function r(a){for(let n=0;n<o.length;n++)o[n].setRotation(o[n].options.rotation+i[n]);--a&&setTimeout(r.bind(null,a),20)}r(25)},randomMarkers(t,o){t=this._map;const i=function(){for(let r=0;r<o;r++){const a=t.getBounds(),n=a.getSouthWest(),s=a.getNorthEast(),d=s.lng-n.lng,l=s.lat-n.lat,h=new e.Marker(new e.LatLng(n.lat+l*Math.random(),n.lng+d*Math.random()));e.Rotate.___cluster.addLayer(h)}};this.___cluster?typeof e.markerClusterGroup=="function"&&i():this.___cluster=import("https://unpkg.com/leaflet.markercluster@1.4.1/src/index.js").then(r=>{document.head.insertAdjacentHTML("beforeend",'<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css"><link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css">'),Object.assign(globalThis.L,r),this.___cluster=e.markerClusterGroup({spiderfyOnMaxZoom:!1,showCoverageOnHover:!1,zoomToBoundsOnClick:!1}).on("clusterclick",a=>a.layer.spiderfy()).addTo(t),i()})},displayCenter(){const t=this._map;this.___centerMarker&&this.___centerMarker.remove(),this.___centerMarker=new e.CircleMarker(t.getCenter(),{radius:3,color:"red"}).addTo(t).bindTooltip("<b>center</b>",{direction:"right",permanent:!0}),this.___centerMarker.getTooltip().getElement().setAttribute("tabindex","-1")},displayBounds(){const t=this._map;this.___bounds&&this.___bounds.remove();const o=document.getElementById("pad_input").valueAsNumber,i=t.getBounds().pad(o);this.___bounds=new e.Rectangle(i,{fill:!1,lineCap:"square",color:"yellow",dashArray:"5, 10",weight:2}).addTo(t)},displayLayersBounds(){for(const t of["path","polygon","circle"]){if(globalThis[t])this[`___${t}bounds`]&&this[`___${t}bounds`].remove();else continue;const i=globalThis[t].getBounds();this[`___${t}bounds`]=new e.FeatureGroup([new e.Rectangle(i,{fill:!1,lineCap:"square",color:"yellow",dashArray:"5, 10",weight:1,opacity:.5}),new e.Marker(i.getNorthWest(),{scale:.5,rotation:-45*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>NW</b>",{direction:"center"}),new e.Marker(i.getNorthEast(),{scale:.5,rotation:45*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>NE</b>",{direction:"center"}),new e.Marker(i.getSouthEast(),{scale:.5,rotation:135*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>SE</b>",{direction:"center"}),new e.Marker(i.getSouthWest(),{scale:.5,rotation:-135*(Math.PI/180),rotateWithView:!0}).bindTooltip("<b>SW</b>",{direction:"center"})]).addTo(map).eachLayer(r=>{r.getElement&&r.getElement()&&(r.getElement().style.filter="hue-rotate(150deg)")})}},displayPixelBounds(){const t=this._map;this.___corners&&this.___corners.remove(),this.___pixelbounds&&this.___pixelbounds.remove();const o=t.getSize(),i=t.getPixelBounds(),r=new e.Point(0,0),a=new e.Point(o.x,0),n=new e.Point(o.x,o.y),s=new e.Point(0,o.y),d=new e.Marker(t.containerPointToLatLng(r),{title:"Top Left corner",rotation:-45*(Math.PI/180)}),l=new e.Marker(t.containerPointToLatLng(a),{title:"Top Right corner",rotation:45*(Math.PI/180)}),h=new e.Marker(t.containerPointToLatLng(n),{title:"Bottom Right corner",rotation:135*(Math.PI/180)}),u=new e.Marker(t.containerPointToLatLng(s),{title:"Bottom Left corner",rotation:-135*(Math.PI/180)});this.___corners=new e.FeatureGroup([d,l,h,u]).addTo(t).eachLayer(c=>c.getElement().style.filter="hue-rotate(150deg)");const p=c=>`${+c.x.toFixed(1)}, ${+c.y.toFixed(1)}`;document.getElementById("ogbx").innerHTML=p(i.min),document.getElementById("ogby").innerHTML=p(i.max)},resetHeading(t){t.heading!==null&&this.setBearing(t.heading)},locate(){this._map.locate({setView:!0,enableHighAccuracy:!0})}}})();
|
package/package.json
CHANGED
package/src/control/Rotate.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Leaflet control
|
|
3
|
-
* Its modes are:
|
|
2
|
+
* Leaflet control showing the map bearing and pitch, MapLibre-like (`NavigationControl` compass):
|
|
4
3
|
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
4
|
+
* - Click: animate back to north (and to zero pitch when `visualizePitch` is enabled).
|
|
5
|
+
* - Drag: horizontal movement changes the bearing, vertical movement changes the pitch.
|
|
6
|
+
*
|
|
7
|
+
* Touch rotation and compass bearing are configured with the `touchRotate` and `compassBearing`
|
|
8
|
+
* map handlers (eg. `map.compassBearing.enable()`).
|
|
8
9
|
*
|
|
9
10
|
* @class L.Control.Rotate
|
|
10
11
|
*
|
|
11
12
|
* @extends L.Control
|
|
12
13
|
*
|
|
13
14
|
* @property {string} [position='topleft'] Leaflet control position.
|
|
14
|
-
* @property {boolean} [closeOnZeroBearing=true] Hide the control when bearing
|
|
15
|
+
* @property {boolean} [closeOnZeroBearing=true] Hide the control when both bearing and pitch are zero.
|
|
16
|
+
* @property {boolean} [visualizePitch=true] Tilt the icon with the pitch, reset the pitch on click and change it on drag.
|
|
15
17
|
*
|
|
16
18
|
* @since leaflet-rotate (v0.1.0)
|
|
17
19
|
*/
|
|
@@ -20,11 +22,12 @@ L.Control.Rotate = L.Control.extend({
|
|
|
20
22
|
|
|
21
23
|
options: {
|
|
22
24
|
position: 'topleft',
|
|
23
|
-
closeOnZeroBearing: true
|
|
25
|
+
closeOnZeroBearing: true,
|
|
26
|
+
visualizePitch: true,
|
|
24
27
|
},
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
|
-
* Create the control element and subscribe to map rotation.
|
|
30
|
+
* Create the control element and subscribe to map rotation and pitch.
|
|
28
31
|
*
|
|
29
32
|
* @param {L.Map} map
|
|
30
33
|
*
|
|
@@ -35,46 +38,42 @@ L.Control.Rotate = L.Control.extend({
|
|
|
35
38
|
onAdd(map) {
|
|
36
39
|
const container = this._container = L.DomUtil.create('div', 'leaflet-control-rotate leaflet-bar');
|
|
37
40
|
|
|
38
|
-
// this.button = L.Control.Zoom.prototype._createButton.call(this, 'R', 'leaflet-control-rotate', 'leaflet-control-rotate', container, this._toggleLock);
|
|
39
|
-
|
|
40
41
|
const arrow = this._arrow = L.DomUtil.create('span', 'leaflet-control-rotate-arrow');
|
|
41
42
|
|
|
42
43
|
arrow.style.backgroundImage = `url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")`;
|
|
43
|
-
arrow.style.cursor = 'grab';
|
|
44
44
|
arrow.style.display = 'block';
|
|
45
45
|
arrow.style.width = '100%';
|
|
46
46
|
arrow.style.height = '100%';
|
|
47
47
|
arrow.style.backgroundRepeat = 'no-repeat';
|
|
48
48
|
arrow.style.backgroundPosition = '50%';
|
|
49
|
+
arrow.style.pointerEvents = 'none';
|
|
49
50
|
|
|
50
|
-
// Copy-pasted from L.Control.Zoom
|
|
51
51
|
const link = this._link = L.DomUtil.create('a', 'leaflet-control-rotate-toggle', container);
|
|
52
52
|
link.appendChild(arrow);
|
|
53
53
|
link.href = '#';
|
|
54
|
-
link.title = '
|
|
54
|
+
link.title = this.options.visualizePitch ? 'Reset bearing and pitch' : 'Reset bearing to north';
|
|
55
|
+
link.setAttribute('role', 'button');
|
|
56
|
+
link.setAttribute('aria-label', link.title);
|
|
57
|
+
link.style.cursor = 'grab';
|
|
58
|
+
link.style.touchAction = 'none';
|
|
59
|
+
|
|
60
|
+
this._onPointerMoveBound = this._onPointerMove.bind(this);
|
|
61
|
+
this._onPointerUpBound = this._onPointerUp.bind(this);
|
|
55
62
|
|
|
56
63
|
L.DomEvent
|
|
57
|
-
.on(link, 'dblclick', L.DomEvent.
|
|
58
|
-
.on(link, 'mousedown', this._handleMouseDown, this)
|
|
64
|
+
.on(link, 'dblclick contextmenu', L.DomEvent.stop)
|
|
59
65
|
.on(link, 'click', L.DomEvent.stop)
|
|
60
|
-
.on(link, 'click', this.
|
|
66
|
+
.on(link, 'click', this._onClick, this)
|
|
61
67
|
.on(link, 'click', this._refocusOnMap, this);
|
|
68
|
+
link.addEventListener('pointerdown', this._onPointerDown.bind(this));
|
|
62
69
|
|
|
70
|
+
map.on('rotate pitch', this._restyle, this);
|
|
71
|
+
this._map = map;
|
|
63
72
|
this._restyle();
|
|
64
73
|
|
|
65
|
-
map.on('rotate', this._restyle, this);
|
|
66
|
-
|
|
67
|
-
// State flag
|
|
68
|
-
this._follow = false;
|
|
69
|
-
this._canFollow = false;
|
|
70
|
-
|
|
71
|
-
if (this.options.closeOnZeroBearing && map.getBearing() === 0) {
|
|
72
|
-
container.style.display = 'none';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
74
|
return container;
|
|
76
75
|
},
|
|
77
|
-
|
|
76
|
+
|
|
78
77
|
/**
|
|
79
78
|
* Remove the map event listener when the control is detached.
|
|
80
79
|
*
|
|
@@ -83,138 +82,122 @@ L.Control.Rotate = L.Control.extend({
|
|
|
83
82
|
* @since leaflet-rotate (v0.1.0)
|
|
84
83
|
*/
|
|
85
84
|
onRemove(map) {
|
|
86
|
-
|
|
85
|
+
this._onPointerUp();
|
|
86
|
+
map.off('rotate pitch', this._restyle, this);
|
|
87
87
|
},
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* @param {MouseEvent} e Mouse-down event.
|
|
90
|
+
* Reset bearing (and pitch) unless the click ends a drag.
|
|
93
91
|
*
|
|
94
|
-
* @since leaflet-rotate (
|
|
92
|
+
* @since leaflet-rotate (0.5.1)
|
|
95
93
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.
|
|
94
|
+
_onClick() {
|
|
95
|
+
if (this._moved) {
|
|
96
|
+
this._moved = false;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!this._map || !this._map._rotate) { return; }
|
|
100
|
+
if (this.options.visualizePitch) {
|
|
101
|
+
this._map.resetNorthPitch();
|
|
102
|
+
} else {
|
|
103
|
+
this._map.resetNorth();
|
|
104
|
+
}
|
|
104
105
|
},
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
|
-
*
|
|
108
|
+
* Start dragging the control (mouse, pen or touch).
|
|
108
109
|
*
|
|
109
|
-
* @param {
|
|
110
|
+
* @param {PointerEvent} e
|
|
110
111
|
*
|
|
111
112
|
* @since leaflet-rotate (v0.1.0)
|
|
112
113
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
114
|
+
_onPointerDown(e) {
|
|
115
|
+
if (!this._map || !this._map._rotate || e.button > 0 || this._pointerId != null) { return; }
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
e.stopPropagation();
|
|
118
|
+
this._pointerId = e.pointerId;
|
|
119
|
+
this._startX = this._lastX = e.clientX;
|
|
120
|
+
this._startY = this._lastY = e.clientY;
|
|
121
|
+
this._moved = false;
|
|
122
|
+
this._link.style.cursor = 'grabbing';
|
|
123
|
+
document.addEventListener('pointermove', this._onPointerMoveBound);
|
|
124
|
+
document.addEventListener('pointerup', this._onPointerUpBound);
|
|
125
|
+
document.addEventListener('pointercancel', this._onPointerUpBound);
|
|
120
126
|
},
|
|
121
127
|
|
|
122
128
|
/**
|
|
123
|
-
*
|
|
129
|
+
* Horizontal movement changes the bearing, vertical movement the pitch.
|
|
124
130
|
*
|
|
125
|
-
* @param {
|
|
131
|
+
* @param {PointerEvent} e
|
|
126
132
|
*
|
|
127
133
|
* @since leaflet-rotate (v0.1.0)
|
|
128
134
|
*/
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
this.
|
|
135
|
+
_onPointerMove(e) {
|
|
136
|
+
if (e.pointerId !== this._pointerId) { return; }
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
if (!this._moved && Math.hypot(e.clientX - this._startX, e.clientY - this._startY) < 3) { return; }
|
|
139
|
+
if (!this._moved) {
|
|
140
|
+
this._moved = true;
|
|
141
|
+
this._map._stopPitchBearingAnimation();
|
|
142
|
+
}
|
|
143
|
+
const map = this._map,
|
|
144
|
+
pitch = this.options.visualizePitch ? map.getPitch() + (this._lastY - e.clientY) * 0.5 : map.getPitch();
|
|
145
|
+
map._setPitchAndBearing(pitch, map.getBearing() + (e.clientX - this._lastX) * 0.8);
|
|
146
|
+
this._lastX = e.clientX;
|
|
147
|
+
this._lastY = e.clientY;
|
|
133
148
|
},
|
|
134
149
|
|
|
135
150
|
/**
|
|
136
|
-
*
|
|
137
|
-
* Compass mode may require permission from the browser.
|
|
151
|
+
* Stop dragging and remove document listeners.
|
|
138
152
|
*
|
|
139
|
-
* @param {
|
|
153
|
+
* @param {PointerEvent} [e]
|
|
140
154
|
*
|
|
141
155
|
* @since leaflet-rotate (v0.1.0)
|
|
142
156
|
*/
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
157
|
+
_onPointerUp(e) {
|
|
158
|
+
if (e && e.pointerId !== this._pointerId) { return; }
|
|
159
|
+
document.removeEventListener('pointermove', this._onPointerMoveBound);
|
|
160
|
+
document.removeEventListener('pointerup', this._onPointerUpBound);
|
|
161
|
+
document.removeEventListener('pointercancel', this._onPointerUpBound);
|
|
162
|
+
// a drag ending outside the button produces no click
|
|
163
|
+
if (e && this._moved && !this._link.contains(e.target)) { this._moved = false; }
|
|
164
|
+
delete this._pointerId;
|
|
165
|
+
if (this._link) {
|
|
166
|
+
this._link.style.cursor = 'grab';
|
|
167
|
+
this._restyle();
|
|
153
168
|
}
|
|
154
|
-
|
|
155
|
-
// Compass mode
|
|
156
|
-
else if (!map.compassBearing.enabled()) {
|
|
157
|
-
map.touchRotate.disable();
|
|
158
|
-
(
|
|
159
|
-
DeviceOrientationEvent && DeviceOrientationEvent.requestPermission
|
|
160
|
-
? DeviceOrientationEvent.requestPermission() // iOS compass
|
|
161
|
-
: Promise.resolve('granted') // others
|
|
162
|
-
).then(state => "granted" === state && map.compassBearing.enable())
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Locked mode
|
|
166
|
-
else {
|
|
167
|
-
map.compassBearing.disable();
|
|
168
|
-
map.setBearing(0);
|
|
169
|
-
if (this.options.closeOnZeroBearing) {
|
|
170
|
-
map.touchRotate.enable();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
this._restyle();
|
|
174
169
|
},
|
|
175
170
|
|
|
176
171
|
/**
|
|
177
|
-
* Update the
|
|
172
|
+
* Update the icon (bearing and pitch) and the control visibility.
|
|
178
173
|
*
|
|
179
174
|
* @listens L.Map#rotate
|
|
175
|
+
* @listens L.Map#pitch
|
|
180
176
|
*
|
|
181
177
|
* @since leaflet-rotate (v0.1.0)
|
|
182
178
|
*/
|
|
183
179
|
_restyle() {
|
|
184
|
-
|
|
180
|
+
const map = this._map;
|
|
181
|
+
if (!map) { return; }
|
|
182
|
+
if (!map.options.rotate) {
|
|
185
183
|
this._link.classList.add('leaflet-disabled');
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Touch mode
|
|
202
|
-
else if (map.touchRotate.enabled()) {
|
|
203
|
-
this._link.style.backgroundColor = null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Locked mode
|
|
207
|
-
else {
|
|
208
|
-
this._link.style.backgroundColor = 'grey';
|
|
209
|
-
if (0 === bearing && this.options.closeOnZeroBearing) {
|
|
210
|
-
this._container.style.display = 'none';
|
|
211
|
-
}
|
|
212
|
-
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const bearing = map.getBearing(),
|
|
188
|
+
pitch = map.getPitch();
|
|
189
|
+
|
|
190
|
+
this._arrow.style.transform = this.options.visualizePitch && pitch
|
|
191
|
+
? `scale(${1 / Math.sqrt(Math.cos(pitch * Math.PI / 180))}) rotateX(${pitch}deg) rotate(${bearing}deg)`
|
|
192
|
+
: `rotate(${bearing}deg)`;
|
|
193
|
+
|
|
194
|
+
if (this.options.closeOnZeroBearing) {
|
|
195
|
+
const hidden = !bearing && !(this.options.visualizePitch && pitch) && this._pointerId == null;
|
|
196
|
+
this._container.style.display = hidden ? 'none' : 'block';
|
|
213
197
|
}
|
|
214
198
|
},
|
|
215
199
|
|
|
216
200
|
});
|
|
217
|
-
|
|
218
201
|
if (L.control) {
|
|
219
202
|
L.control.rotate = options => new L.Control.Rotate(options);
|
|
220
203
|
}
|
|
@@ -243,3 +226,4 @@ L.Map.addInitHook(function() {
|
|
|
243
226
|
this.addControl(this.rotateControl);
|
|
244
227
|
}
|
|
245
228
|
});
|
|
229
|
+
|
package/src/index.js
CHANGED
|
@@ -55,6 +55,7 @@ export * from './map/handler/CompassBearing';
|
|
|
55
55
|
export * from './map/handler/ContainerMutation';
|
|
56
56
|
export * from './map/handler/TouchGestures';
|
|
57
57
|
export * from './map/handler/TouchRotate';
|
|
58
|
+
export * from './map/handler/TouchPitch';
|
|
58
59
|
export * from './map/handler/ShiftKeyRotate';
|
|
59
60
|
export * from './map/handler/TouchZoom';
|
|
60
61
|
|
|
@@ -27,11 +27,30 @@ L.GridLayer.include({
|
|
|
27
27
|
if (!this._onRotate) {
|
|
28
28
|
this._onRotate = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
|
|
29
29
|
}
|
|
30
|
-
events.rotate = this.
|
|
30
|
+
events.rotate = this._onRotateEvent;
|
|
31
31
|
}
|
|
32
32
|
return events;
|
|
33
33
|
},
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Keep tile levels aligned when a bearing/pitch change shifts the pixel origin
|
|
37
|
+
* (eg. when the rotation pivot is not the view center), then refresh tiles (throttled).
|
|
38
|
+
*
|
|
39
|
+
* @listens L.Map~rotate
|
|
40
|
+
*
|
|
41
|
+
* @since leaflet-rotate (0.5.1)
|
|
42
|
+
*/
|
|
43
|
+
_onRotateEvent() {
|
|
44
|
+
const map = this._map;
|
|
45
|
+
if (!map) { return; }
|
|
46
|
+
const origin = map.getPixelOrigin();
|
|
47
|
+
if (this._rotateOrigin && !this._rotateOrigin.equals(origin) && !map._animatingZoom && this._levels) {
|
|
48
|
+
this._setZoomTransforms(map.getCenter(), map.getZoom());
|
|
49
|
+
}
|
|
50
|
+
this._rotateOrigin = origin;
|
|
51
|
+
this._onRotate();
|
|
52
|
+
},
|
|
53
|
+
|
|
35
54
|
/**
|
|
36
55
|
* Return tile pixel bounds that cover the rotated viewport.
|
|
37
56
|
*
|