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
|
@@ -537,10 +537,30 @@ var leafletRotate = (() => {
|
|
|
537
537
|
if (!this._onRotate) {
|
|
538
538
|
this._onRotate = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
|
|
539
539
|
}
|
|
540
|
-
events.rotate = this.
|
|
540
|
+
events.rotate = this._onRotateEvent;
|
|
541
541
|
}
|
|
542
542
|
return events;
|
|
543
543
|
},
|
|
544
|
+
/**
|
|
545
|
+
* Keep tile levels aligned when a bearing/pitch change shifts the pixel origin
|
|
546
|
+
* (eg. when the rotation pivot is not the view center), then refresh tiles (throttled).
|
|
547
|
+
*
|
|
548
|
+
* @listens L.Map~rotate
|
|
549
|
+
*
|
|
550
|
+
* @since leaflet-rotate (0.5.1)
|
|
551
|
+
*/
|
|
552
|
+
_onRotateEvent() {
|
|
553
|
+
const map = this._map;
|
|
554
|
+
if (!map) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const origin = map.getPixelOrigin();
|
|
558
|
+
if (this._rotateOrigin && !this._rotateOrigin.equals(origin) && !map._animatingZoom && this._levels) {
|
|
559
|
+
this._setZoomTransforms(map.getCenter(), map.getZoom());
|
|
560
|
+
}
|
|
561
|
+
this._rotateOrigin = origin;
|
|
562
|
+
this._onRotate();
|
|
563
|
+
},
|
|
544
564
|
/**
|
|
545
565
|
* Return tile pixel bounds that cover the rotated viewport.
|
|
546
566
|
*
|
|
@@ -578,7 +598,10 @@ var leafletRotate = (() => {
|
|
|
578
598
|
return Object.assign(events, { rotate: this._onRotate });
|
|
579
599
|
},
|
|
580
600
|
/**
|
|
581
|
-
*
|
|
601
|
+
* Refresh renderer bounds after bearing or pitch changes.
|
|
602
|
+
*
|
|
603
|
+
* Paths are reprojected only when the pixel origin actually changed (eg. fractional centers
|
|
604
|
+
* after an off-center zoom), since reprojection is the main cost with long polylines.
|
|
582
605
|
*
|
|
583
606
|
* @since leaflet-rotate (v0.1.0)
|
|
584
607
|
*/
|
|
@@ -586,7 +609,32 @@ var leafletRotate = (() => {
|
|
|
586
609
|
if (!this._map._rotate) {
|
|
587
610
|
return;
|
|
588
611
|
}
|
|
589
|
-
this.
|
|
612
|
+
const origin = this._map.getPixelOrigin();
|
|
613
|
+
if (!this._rotateOrigin || !this._rotateOrigin.equals(origin)) {
|
|
614
|
+
this._rotateOrigin = origin;
|
|
615
|
+
clearTimeout(this._rotateTimer);
|
|
616
|
+
this._onZoomEnd();
|
|
617
|
+
this._updateAfterRotate();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (this._bounds && this._bounds.contains(this._map._getPaddedPixelBounds(0))) {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
clearTimeout(this._rotateTimer);
|
|
624
|
+
const wait = (this._lastRotateUpdate || 0) + 100 - performance.now();
|
|
625
|
+
if (wait > 0) {
|
|
626
|
+
this._rotateTimer = setTimeout(() => this._map && this._updateAfterRotate(), wait);
|
|
627
|
+
} else {
|
|
628
|
+
this._updateAfterRotate();
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
/**
|
|
632
|
+
* Recalculate renderer bounds and redraw after bearing or pitch changes.
|
|
633
|
+
*
|
|
634
|
+
* @since leaflet-rotate (0.5.1)
|
|
635
|
+
*/
|
|
636
|
+
_updateAfterRotate() {
|
|
637
|
+
this._lastRotateUpdate = performance.now();
|
|
590
638
|
if (L.version.startsWith("2.")) {
|
|
591
639
|
this._onMoveEnd();
|
|
592
640
|
} else {
|
|
@@ -610,46 +658,25 @@ var leafletRotate = (() => {
|
|
|
610
658
|
},
|
|
611
659
|
/**
|
|
612
660
|
* Update the renderer transform during zoom animation, accounting for rotated coordinates.
|
|
613
|
-
*
|
|
614
|
-
* @FIXME layer drifts on `map.setZoom()` (eg. zoom during animation)
|
|
615
|
-
*
|
|
616
|
-
* the main cause seems to be related to `this._updateTransform(path._center, path._zoom))`
|
|
617
|
-
* and `this._topLeft = this._map.layerPointToLatLng(this._bounds.min);`
|
|
618
|
-
*
|
|
619
|
-
* @example
|
|
620
|
-
* map.setZoom(2);
|
|
621
|
-
* path._renderer._update();
|
|
622
|
-
* path._renderer._updateTransform(path._renderer._center, path._renderer._zoom);
|
|
623
661
|
*
|
|
624
662
|
* @see https://github.com/Leaflet/Leaflet/pull/8794
|
|
625
663
|
* @see https://github.com/Leaflet/Leaflet/pull/8103
|
|
626
664
|
* @see https://github.com/Leaflet/Leaflet/issues/7466
|
|
627
665
|
*
|
|
628
|
-
* @TODO recheck these changes against leaflet@v1.9.3
|
|
629
|
-
*
|
|
630
|
-
* @see https://github.com/Leaflet/Leaflet/compare/v1.7.0...v1.9.3
|
|
631
|
-
*
|
|
632
666
|
* @param {L.LatLng} center Target map center.
|
|
633
667
|
* @param {number} zoom Target zoom level.
|
|
634
668
|
*
|
|
635
669
|
* @since leaflet-rotate (v0.1.0)
|
|
636
670
|
*/
|
|
637
671
|
_updateTransform(center, zoom) {
|
|
638
|
-
|
|
639
|
-
if (this._map._rotate) {
|
|
640
|
-
const scale2 = this._map.getZoomScale(zoom, this._zoom), offset2 = this._topLeftPoint.multiplyBy(scale2).subtract(this._map._getNewPixelOrigin(center, zoom));
|
|
641
|
-
L.DomUtil.setTransform(this._container, offset2, scale2);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
return rendererBase._updateTransform.apply(this, arguments);
|
|
645
|
-
}
|
|
672
|
+
const v2 = L.version.startsWith("2.");
|
|
646
673
|
if (!this._map._rotate) {
|
|
647
|
-
return rendererProto._updateTransform.apply(this, arguments);
|
|
674
|
+
return (v2 ? rendererBase : rendererProto)._updateTransform.apply(this, arguments);
|
|
648
675
|
}
|
|
649
|
-
if (zoom === this._zoom) {
|
|
676
|
+
if (!v2 && zoom === this._zoom) {
|
|
650
677
|
return;
|
|
651
678
|
}
|
|
652
|
-
const scale = this._map.getZoomScale(zoom, this._zoom), offset = this.
|
|
679
|
+
const scale = this._map.getZoomScale(zoom, this._zoom), offset = this._topLeftPoint.multiplyBy(scale).subtract(this._map._getNewPixelOrigin(center, zoom));
|
|
653
680
|
L.DomUtil.setTransform(this._container, offset, scale);
|
|
654
681
|
},
|
|
655
682
|
/**
|
|
@@ -734,6 +761,7 @@ var leafletRotate = (() => {
|
|
|
734
761
|
this._topLeft = this._map.layerPointToLatLng(this._bounds.min);
|
|
735
762
|
this._center = this._map.getCenter();
|
|
736
763
|
this._zoom = this._map.getZoom();
|
|
764
|
+
this._topLeftPoint = this._map._getNewPixelOrigin(this._center, this._zoom).add(this._bounds.min);
|
|
737
765
|
}
|
|
738
766
|
});
|
|
739
767
|
|
|
@@ -757,7 +785,15 @@ var leafletRotate = (() => {
|
|
|
757
785
|
*
|
|
758
786
|
* @since leaflet-rotate (0.5.0)
|
|
759
787
|
*/
|
|
760
|
-
pitch: 0
|
|
788
|
+
pitch: 0,
|
|
789
|
+
/**
|
|
790
|
+
* @type {number} Maximum distance of the pitched map plane from the map center, in multiples
|
|
791
|
+
* of the map height. Tiles, vectors and `getBounds()` stop there (and always
|
|
792
|
+
* before the horizon); a `.leaflet-pitch-sky` element fills the area above.
|
|
793
|
+
*
|
|
794
|
+
* @since leaflet-rotate (0.5.1)
|
|
795
|
+
*/
|
|
796
|
+
pitchMaxDistance: 3
|
|
761
797
|
});
|
|
762
798
|
L.Map.include({
|
|
763
799
|
/**
|
|
@@ -795,6 +831,10 @@ var leafletRotate = (() => {
|
|
|
795
831
|
mapProto.initialize.apply(this, arguments);
|
|
796
832
|
if (this.options.rotate) {
|
|
797
833
|
this.on("resize", this._updatePitchTransform, this);
|
|
834
|
+
this.on("movestart dragstart", this._startPitchPan, this);
|
|
835
|
+
this.on("move", this._updatePitchPan, this);
|
|
836
|
+
this.on("moveend", this._endPitchPan, this);
|
|
837
|
+
this.on("move", this._updatePitchSky, this);
|
|
798
838
|
this.setBearing(this.options.bearing);
|
|
799
839
|
this.setPitch(this.options.pitch);
|
|
800
840
|
}
|
|
@@ -914,7 +954,8 @@ var leafletRotate = (() => {
|
|
|
914
954
|
/**
|
|
915
955
|
* Return the geographic bounds of the current viewport, accounting for rotation and pitch.
|
|
916
956
|
*
|
|
917
|
-
* The result
|
|
957
|
+
* The result encloses the four corners of the visible ground rectangle. With pitch,
|
|
958
|
+
* the rectangle stops at the configured far plane, excluding the sky.
|
|
918
959
|
*
|
|
919
960
|
* @returns {L.LatLngBounds} Geographic bounds visible in the map viewport.
|
|
920
961
|
*
|
|
@@ -924,18 +965,60 @@ var leafletRotate = (() => {
|
|
|
924
965
|
if (!this._rotate) {
|
|
925
966
|
return mapProto.getBounds.apply(this, arguments);
|
|
926
967
|
}
|
|
927
|
-
const
|
|
968
|
+
const [min, max] = this._getPitchedContainerBounds(0);
|
|
928
969
|
return new L.LatLngBounds([
|
|
929
|
-
this.containerPointToLatLng([
|
|
970
|
+
this.containerPointToLatLng([min.x, min.y]),
|
|
930
971
|
// topleft
|
|
931
|
-
this.containerPointToLatLng([
|
|
972
|
+
this.containerPointToLatLng([max.x, min.y]),
|
|
932
973
|
// topright
|
|
933
|
-
this.containerPointToLatLng([
|
|
974
|
+
this.containerPointToLatLng([max.x, max.y]),
|
|
934
975
|
// bottomright
|
|
935
|
-
this.containerPointToLatLng([
|
|
976
|
+
this.containerPointToLatLng([min.x, max.y])
|
|
936
977
|
// bottomleft
|
|
937
978
|
]);
|
|
938
979
|
},
|
|
980
|
+
/**
|
|
981
|
+
* Return the container rectangle covering the visible map plane, optionally padded by a
|
|
982
|
+
* fraction of the map size. When pitched, its top is cut at the far plane (see `pitchMaxDistance`)
|
|
983
|
+
* so that it never reaches the horizon, where the plane would extend to infinity.
|
|
984
|
+
*
|
|
985
|
+
* @param {number} padding Padding as a fraction of the map size.
|
|
986
|
+
*
|
|
987
|
+
* @returns {L.Point[]} Top-left and bottom-right container points.
|
|
988
|
+
*
|
|
989
|
+
* @since leaflet-rotate (0.5.1)
|
|
990
|
+
*/
|
|
991
|
+
_getPitchedContainerBounds(padding) {
|
|
992
|
+
const size = this.getSize(), min = size.multiplyBy(-padding), max = size.multiplyBy(1 + padding);
|
|
993
|
+
if (this._pitch) {
|
|
994
|
+
min.y = Math.min(Math.max(min.y, this._getPitchFarY()), max.y);
|
|
995
|
+
}
|
|
996
|
+
return [min, max];
|
|
997
|
+
},
|
|
998
|
+
/**
|
|
999
|
+
* Return the container y coordinate of the far plane: points above it are farther than
|
|
1000
|
+
* `pitchMaxDistance` from the map center, or beyond the horizon.
|
|
1001
|
+
*
|
|
1002
|
+
* @returns {number} Container y coordinate (`-Infinity` when the whole map is close enough).
|
|
1003
|
+
*
|
|
1004
|
+
* @since leaflet-rotate (0.5.1)
|
|
1005
|
+
*/
|
|
1006
|
+
_getPitchFarY() {
|
|
1007
|
+
const size = this.getSize(), inverse = this._pitchInverseTransform, panePos = this._getMapPanePos(), x = size.x / 2 - panePos.x, centerY = size.y / 2 - panePos.y, maxDistance = this.options.pitchMaxDistance * size.y, w = (y) => inverse.g * x + inverse.h * y + inverse.i, centerW = w(centerY), center = this._transformPitchPoint([x, centerY], inverse), isNear = (y) => w(y) * centerW > 0 && this._transformPitchPoint([x, y], inverse).distanceTo(center) <= maxDistance;
|
|
1008
|
+
let far = -size.y - panePos.y, near = centerY;
|
|
1009
|
+
if (isNear(far)) {
|
|
1010
|
+
return -Infinity;
|
|
1011
|
+
}
|
|
1012
|
+
for (let i = 0; i < 20; i++) {
|
|
1013
|
+
const mid = (far + near) / 2;
|
|
1014
|
+
if (isNear(mid)) {
|
|
1015
|
+
near = mid;
|
|
1016
|
+
} else {
|
|
1017
|
+
far = mid;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return near + panePos.y;
|
|
1021
|
+
},
|
|
939
1022
|
/**
|
|
940
1023
|
* Returns the bounds of the current map view in projected pixel
|
|
941
1024
|
* coordinates (sometimes useful in layer and overlay implementations).
|
|
@@ -972,37 +1055,92 @@ var leafletRotate = (() => {
|
|
|
972
1055
|
return;
|
|
973
1056
|
}
|
|
974
1057
|
if (options && options.animate) {
|
|
975
|
-
this.
|
|
976
|
-
const animationId = this._bearingAnimationId;
|
|
977
|
-
if (this._bearingAnimationFrame) {
|
|
978
|
-
cancelAnimationFrame(this._bearingAnimationFrame);
|
|
979
|
-
}
|
|
980
|
-
const startBearing = this.getBearing(), endBearing = L.Util.wrapNum(theta, [0, 360]), delta = (endBearing - startBearing + 540) % 360 - 180, duration = Math.max(0, options.duration == null ? 0.25 : options.duration) * 1e3, easeLinearity = Math.max(options.easeLinearity || 0.5, 0.2), startTime = performance.now();
|
|
981
|
-
const animate = function() {
|
|
982
|
-
if (!this._mapPane) {
|
|
983
|
-
delete this._bearingAnimationFrame;
|
|
984
|
-
return;
|
|
985
|
-
}
|
|
986
|
-
const progress = duration ? Math.min((performance.now() - startTime) / duration, 1) : 1, eased = 1 - Math.pow(1 - progress, 1 / easeLinearity);
|
|
987
|
-
this._setBearing(progress === 1 ? endBearing : startBearing + delta * eased);
|
|
988
|
-
if (this._bearingAnimationId !== animationId) {
|
|
989
|
-
return;
|
|
990
|
-
}
|
|
991
|
-
if (progress < 1) {
|
|
992
|
-
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
993
|
-
} else {
|
|
994
|
-
delete this._bearingAnimationFrame;
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
|
-
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
1058
|
+
this._animatePitchBearing(this.getPitch(), theta, options);
|
|
998
1059
|
return;
|
|
999
1060
|
}
|
|
1061
|
+
this._stopPitchBearingAnimation();
|
|
1062
|
+
this._setBearing(theta);
|
|
1063
|
+
},
|
|
1064
|
+
/**
|
|
1065
|
+
* Animate pitch and bearing together (bearing follows the shortest path).
|
|
1066
|
+
*
|
|
1067
|
+
* @param {number} pitch Target pitch in degrees.
|
|
1068
|
+
* @param {number} bearing Target bearing in degrees.
|
|
1069
|
+
* @param {Object} [options] Animation options.
|
|
1070
|
+
* @param {number} [options.duration=0.25] Animation duration in seconds.
|
|
1071
|
+
* @param {number} [options.easeLinearity=0.5] Easing curve linearity.
|
|
1072
|
+
*
|
|
1073
|
+
* @since leaflet-rotate (0.5.1)
|
|
1074
|
+
*/
|
|
1075
|
+
_animatePitchBearing(pitch, bearing, options) {
|
|
1076
|
+
this._stopPitchBearingAnimation();
|
|
1077
|
+
const animationId = this._bearingAnimationId;
|
|
1078
|
+
const startBearing = this.getBearing(), startPitch = this.getPitch(), endBearing = L.Util.wrapNum(bearing, [0, 360]), endPitch = Math.max(0, Math.min(pitch, 85)), delta = (endBearing - startBearing + 540) % 360 - 180, duration = Math.max(0, options.duration == null ? 0.25 : options.duration) * 1e3, easeLinearity = Math.max(options.easeLinearity || 0.5, 0.2), startTime = performance.now();
|
|
1079
|
+
const animate = function() {
|
|
1080
|
+
if (!this._mapPane || this._bearingAnimationId !== animationId) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
const progress = duration ? Math.min((performance.now() - startTime) / duration, 1) : 1, eased = 1 - Math.pow(1 - progress, 1 / easeLinearity);
|
|
1084
|
+
if (progress < 1) {
|
|
1085
|
+
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
1086
|
+
} else {
|
|
1087
|
+
delete this._bearingAnimationFrame;
|
|
1088
|
+
}
|
|
1089
|
+
this._setPitchAndBearing(
|
|
1090
|
+
progress === 1 ? endPitch : startPitch + (endPitch - startPitch) * eased,
|
|
1091
|
+
progress === 1 ? endBearing : startBearing + delta * eased
|
|
1092
|
+
);
|
|
1093
|
+
};
|
|
1094
|
+
this._bearingAnimationFrame = requestAnimationFrame(animate.bind(this));
|
|
1095
|
+
},
|
|
1096
|
+
/**
|
|
1097
|
+
* Cancel a running pitch/bearing animation.
|
|
1098
|
+
*
|
|
1099
|
+
* @since leaflet-rotate (0.5.1)
|
|
1100
|
+
*/
|
|
1101
|
+
_stopPitchBearingAnimation() {
|
|
1000
1102
|
this._bearingAnimationId = (this._bearingAnimationId || 0) + 1;
|
|
1001
1103
|
if (this._bearingAnimationFrame) {
|
|
1002
1104
|
cancelAnimationFrame(this._bearingAnimationFrame);
|
|
1003
1105
|
delete this._bearingAnimationFrame;
|
|
1004
1106
|
}
|
|
1005
|
-
|
|
1107
|
+
},
|
|
1108
|
+
/**
|
|
1109
|
+
* Rotate the map back to north (bearing 0), animated by default.
|
|
1110
|
+
*
|
|
1111
|
+
* @param {Object} [options] Animation options (`animate`, `duration`, `easeLinearity`).
|
|
1112
|
+
*
|
|
1113
|
+
* @returns {L.Map} This map instance.
|
|
1114
|
+
*
|
|
1115
|
+
* @since leaflet-rotate (0.5.1)
|
|
1116
|
+
*/
|
|
1117
|
+
resetNorth(options) {
|
|
1118
|
+
options = Object.assign({ animate: true, duration: 0.5 }, options);
|
|
1119
|
+
if (options.animate) {
|
|
1120
|
+
this._animatePitchBearing(this.getPitch(), 0, options);
|
|
1121
|
+
} else {
|
|
1122
|
+
this.setBearing(0);
|
|
1123
|
+
}
|
|
1124
|
+
return this;
|
|
1125
|
+
},
|
|
1126
|
+
/**
|
|
1127
|
+
* Rotate the map back to north and remove the pitch, animated by default.
|
|
1128
|
+
*
|
|
1129
|
+
* @param {Object} [options] Animation options (`animate`, `duration`, `easeLinearity`).
|
|
1130
|
+
*
|
|
1131
|
+
* @returns {L.Map} This map instance.
|
|
1132
|
+
*
|
|
1133
|
+
* @since leaflet-rotate (0.5.1)
|
|
1134
|
+
*/
|
|
1135
|
+
resetNorthPitch(options) {
|
|
1136
|
+
options = Object.assign({ animate: true, duration: 0.5 }, options);
|
|
1137
|
+
if (options.animate) {
|
|
1138
|
+
this._animatePitchBearing(0, 0, options);
|
|
1139
|
+
} else {
|
|
1140
|
+
this._stopPitchBearingAnimation();
|
|
1141
|
+
this._setPitchAndBearing(0, 0);
|
|
1142
|
+
}
|
|
1143
|
+
return this;
|
|
1006
1144
|
},
|
|
1007
1145
|
/**
|
|
1008
1146
|
* Apply a bearing immediately and update the rotated pane and map coordinates.
|
|
@@ -1036,10 +1174,12 @@ var leafletRotate = (() => {
|
|
|
1036
1174
|
this.fire("rotate");
|
|
1037
1175
|
},
|
|
1038
1176
|
/**
|
|
1039
|
-
* Transform a point with a
|
|
1177
|
+
* Transform a ground-plane point with a DOMMatrix, or reverse its screen projection.
|
|
1178
|
+
* Both branches divide by the homogeneous coordinate after applying the matrix.
|
|
1040
1179
|
*
|
|
1041
1180
|
* @param {L.Point|number[]} point Input pixel coordinate.
|
|
1042
|
-
* @param {DOMMatrix|Object} matrix Forward or inverse
|
|
1181
|
+
* @param {DOMMatrix|Object} matrix Forward CSS matrix or inverse homography with
|
|
1182
|
+
* row-major coefficients `a` through `i`.
|
|
1043
1183
|
*
|
|
1044
1184
|
* @returns {L.Point} Transformed pixel coordinate.
|
|
1045
1185
|
*
|
|
@@ -1067,9 +1207,23 @@ var leafletRotate = (() => {
|
|
|
1067
1207
|
return;
|
|
1068
1208
|
}
|
|
1069
1209
|
const pivot = this._getPixelCenter(), size = this.getSize(), perspective = Math.max(1, size.y * 1.5), position = this._rotatePane._leaflet_pos || new L.Point(0, 0), transform = `translate3d(${pivot.x}px,${pivot.y}px,0) perspective(${perspective}px) rotateX(${this.getPitch()}deg) rotateZ(${this.getBearing()}deg) translate3d(${position.x - pivot.x}px,${position.y - pivot.y}px,0)`;
|
|
1210
|
+
this._setPitchTransform(transform);
|
|
1211
|
+
},
|
|
1212
|
+
/**
|
|
1213
|
+
* Apply the CSS transform and cache matching forward/inverse point conversions.
|
|
1214
|
+
*
|
|
1215
|
+
* Ground points have z = 0, so the 4x4 CSS matrix reduces to the homography
|
|
1216
|
+
* [a b c; d e f; g h i]. Its inverse maps screen points back onto that plane;
|
|
1217
|
+
* inverting the full 4x4 matrix at screen z = 0 would not undo perspective.
|
|
1218
|
+
*
|
|
1219
|
+
* @param {string} transform CSS transform list, including a serialized DOMMatrix.
|
|
1220
|
+
*
|
|
1221
|
+
* @since leaflet-rotate (0.5.2)
|
|
1222
|
+
*/
|
|
1223
|
+
_setPitchTransform(transform) {
|
|
1070
1224
|
this._rotatePane.style.transformOrigin = "0 0";
|
|
1071
1225
|
this._rotatePane.style.transform = transform;
|
|
1072
|
-
const matrix = this._pitchTransform = new DOMMatrix(
|
|
1226
|
+
const matrix = this._pitchTransform = new DOMMatrix(transform), a = matrix.m11, b = matrix.m21, c = matrix.m41, d = matrix.m12, e = matrix.m22, f = matrix.m42, g = matrix.m14, h = matrix.m24, i = matrix.m44, determinant = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
|
1073
1227
|
this._pitchInverseTransform = {
|
|
1074
1228
|
a: (e * i - f * h) / determinant,
|
|
1075
1229
|
b: (c * h - b * i) / determinant,
|
|
@@ -1081,11 +1235,109 @@ var leafletRotate = (() => {
|
|
|
1081
1235
|
h: (b * g - a * h) / determinant,
|
|
1082
1236
|
i: (a * e - b * d) / determinant
|
|
1083
1237
|
};
|
|
1238
|
+
this._updatePitchSky();
|
|
1239
|
+
},
|
|
1240
|
+
/**
|
|
1241
|
+
* Capture a fixed projection baseline for a pitched drag or programmatic pan.
|
|
1242
|
+
*
|
|
1243
|
+
* `movestart` uses the viewport center; `dragstart` replaces it with the original
|
|
1244
|
+
* pointer position. Store both the screen anchor and its ground-plane coordinate
|
|
1245
|
+
* so subsequent frames can keep the grabbed geographic point under the pointer.
|
|
1246
|
+
* Pane positions are in screen pixels and the baseline matrices stay unchanged.
|
|
1247
|
+
*
|
|
1248
|
+
* @param {L.Event} e Leaflet `movestart` or `dragstart` event.
|
|
1249
|
+
*
|
|
1250
|
+
* @since leaflet-rotate (0.5.2)
|
|
1251
|
+
*/
|
|
1252
|
+
_startPitchPan(e) {
|
|
1253
|
+
if (!this._pitch || !this._loaded) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
const draggable = this.dragging && this.dragging._draggable, anchor = e.type === "dragstart" && draggable ? (this.pointerEventToContainerPoint || this.mouseEventToContainerPoint).call(this, {
|
|
1257
|
+
clientX: draggable._startPoint.x,
|
|
1258
|
+
clientY: draggable._startPoint.y
|
|
1259
|
+
}) : this.getSize().divideBy(2), panePos = this._getMapPanePos().clone();
|
|
1260
|
+
this._pitchPan = {
|
|
1261
|
+
anchor,
|
|
1262
|
+
panePos,
|
|
1263
|
+
lastPanePos: panePos,
|
|
1264
|
+
matrix: this._pitchTransform,
|
|
1265
|
+
inverse: this._pitchInverseTransform,
|
|
1266
|
+
layerAnchor: this.containerPointToLayerPoint(anchor),
|
|
1267
|
+
zoom: this._zoom
|
|
1268
|
+
};
|
|
1269
|
+
},
|
|
1270
|
+
/**
|
|
1271
|
+
* Translate the ground plane without moving the camera's vanishing point.
|
|
1272
|
+
*
|
|
1273
|
+
* For pane displacement delta, invert the displaced anchor with the baseline
|
|
1274
|
+
* projection to find the required layer-space shift. Compose
|
|
1275
|
+
* T(-delta) * baseline * T(shift): the left translation cancels Leaflet's outer
|
|
1276
|
+
* pane movement, while the right translation moves geography before perspective.
|
|
1277
|
+
* Always derive this from the baseline, not the previous frame, to avoid drift.
|
|
1278
|
+
* Zoom frames are handled by Leaflet's zoom/origin updates instead.
|
|
1279
|
+
*
|
|
1280
|
+
* @since leaflet-rotate (0.5.2)
|
|
1281
|
+
*/
|
|
1282
|
+
_updatePitchPan() {
|
|
1283
|
+
const pan = this._pitchPan;
|
|
1284
|
+
if (!this._pitch || !pan || this._animatingZoom || this._zoom !== pan.zoom) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const panePos = this._getMapPanePos();
|
|
1288
|
+
if (panePos.equals(pan.lastPanePos)) {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
const delta = panePos.subtract(pan.panePos), target = this._transformPitchPoint(pan.anchor.add(delta).subtract(pan.panePos), pan.inverse), shift = target.subtract(pan.layerAnchor);
|
|
1292
|
+
const matrix = new DOMMatrix().translate(-delta.x, -delta.y).multiply(pan.matrix).translate(shift.x, shift.y);
|
|
1293
|
+
this._setPitchTransform(matrix.toString());
|
|
1294
|
+
pan.lastPanePos = panePos.clone();
|
|
1295
|
+
delete this._lastCenter;
|
|
1296
|
+
},
|
|
1297
|
+
/**
|
|
1298
|
+
* Discard the baseline only at `moveend`, after any drag inertia has completed.
|
|
1299
|
+
* Keep the final transform and pixel origin: resetting the view here would
|
|
1300
|
+
* reproject layers abruptly and cause a visible jump on release.
|
|
1301
|
+
*
|
|
1302
|
+
* @since leaflet-rotate (0.5.2)
|
|
1303
|
+
*/
|
|
1304
|
+
_endPitchPan() {
|
|
1305
|
+
delete this._pitchPan;
|
|
1306
|
+
},
|
|
1307
|
+
/**
|
|
1308
|
+
* Fill the area above the far plane (where no tiles are loaded) with a sky gradient.
|
|
1309
|
+
* Style it through the `.leaflet-pitch-sky` class.
|
|
1310
|
+
*
|
|
1311
|
+
* @since leaflet-rotate (0.5.1)
|
|
1312
|
+
*/
|
|
1313
|
+
_updatePitchSky() {
|
|
1314
|
+
const farY = this._pitch && this._pitchInverseTransform ? this._getPitchFarY() : -Infinity;
|
|
1315
|
+
if (farY <= 0) {
|
|
1316
|
+
if (this._pitchSky) {
|
|
1317
|
+
this._pitchSky.style.display = "none";
|
|
1318
|
+
}
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
if (!this._pitchSky) {
|
|
1322
|
+
this._pitchSky = L.DomUtil.create("div", "leaflet-pitch-sky", this._container);
|
|
1323
|
+
Object.assign(this._pitchSky.style, {
|
|
1324
|
+
position: "absolute",
|
|
1325
|
+
left: 0,
|
|
1326
|
+
top: 0,
|
|
1327
|
+
width: "100%",
|
|
1328
|
+
zIndex: 0,
|
|
1329
|
+
pointerEvents: "none",
|
|
1330
|
+
background: "linear-gradient(#9ec5e8, #dfe9f0)"
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
this._pitchSky.style.display = "";
|
|
1334
|
+
this._pitchSky.style.height = `${Math.ceil(farY) + 1}px`;
|
|
1084
1335
|
},
|
|
1085
1336
|
/**
|
|
1086
1337
|
* Set the map pitch in degrees. Values are clamped to the range 0–85.
|
|
1087
1338
|
*
|
|
1088
1339
|
* @param {number} pitch Pitch angle in degrees; clamped to [0, 85].
|
|
1340
|
+
* @param {Object} [options] Animation options (`animate`, `duration`, `easeLinearity`).
|
|
1089
1341
|
*
|
|
1090
1342
|
* @returns {L.Map} This map instance.
|
|
1091
1343
|
*
|
|
@@ -1093,7 +1345,12 @@ var leafletRotate = (() => {
|
|
|
1093
1345
|
*
|
|
1094
1346
|
* @since leaflet-rotate (0.5.0)
|
|
1095
1347
|
*/
|
|
1096
|
-
setPitch(pitch) {
|
|
1348
|
+
setPitch(pitch, options) {
|
|
1349
|
+
if (options && options.animate) {
|
|
1350
|
+
this._animatePitchBearing(pitch, this.getBearing(), options);
|
|
1351
|
+
return this;
|
|
1352
|
+
}
|
|
1353
|
+
this._stopPitchBearingAnimation();
|
|
1097
1354
|
this._setPitchAndBearing(pitch, this.getBearing());
|
|
1098
1355
|
return this;
|
|
1099
1356
|
},
|
|
@@ -1124,6 +1381,9 @@ var leafletRotate = (() => {
|
|
|
1124
1381
|
}
|
|
1125
1382
|
this._setBearing(bearing, mapCenter);
|
|
1126
1383
|
if (pitchChanged) {
|
|
1384
|
+
if (!this._pitch) {
|
|
1385
|
+
this._updatePitchSky();
|
|
1386
|
+
}
|
|
1127
1387
|
this.fire("pitch");
|
|
1128
1388
|
}
|
|
1129
1389
|
},
|
|
@@ -1137,6 +1397,31 @@ var leafletRotate = (() => {
|
|
|
1137
1397
|
getPitch() {
|
|
1138
1398
|
return (this._pitch || 0) * (180 / Math.PI);
|
|
1139
1399
|
},
|
|
1400
|
+
/**
|
|
1401
|
+
* Zoom while keeping the given point fixed on screen, also when the map is pitched.
|
|
1402
|
+
*
|
|
1403
|
+
* Leaflet interpolates in container space, which is not linear under perspective;
|
|
1404
|
+
* interpolating in layer space keeps the point under the cursor.
|
|
1405
|
+
*
|
|
1406
|
+
* @param {L.LatLng|L.Point} latlng Geographic point or container point to keep fixed.
|
|
1407
|
+
* @param {number} zoom Target zoom level.
|
|
1408
|
+
* @param {Object} [options] Zoom options.
|
|
1409
|
+
*
|
|
1410
|
+
* @returns {L.Map} This map instance.
|
|
1411
|
+
*
|
|
1412
|
+
* @since leaflet-rotate (0.5.1)
|
|
1413
|
+
*/
|
|
1414
|
+
setZoomAround(latlng, zoom, options) {
|
|
1415
|
+
if (!this._pitch) {
|
|
1416
|
+
return mapProto.setZoomAround.apply(this, arguments);
|
|
1417
|
+
}
|
|
1418
|
+
const scale = this.getZoomScale(zoom), size = this.getSize(), center = this.containerPointToLayerPoint(size.divideBy(2)), containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng), point = this.containerPointToLayerPoint(containerPoint), newCenter = this.layerPointToLatLng(center.add(point.subtract(center).multiplyBy(1 - 1 / scale)));
|
|
1419
|
+
options = Object.assign({}, options);
|
|
1420
|
+
if (options.animate === void 0 && new L.Bounds(new L.Point(0, 0), size).contains(containerPoint)) {
|
|
1421
|
+
options.animate = true;
|
|
1422
|
+
}
|
|
1423
|
+
return this.setView(newCenter, zoom, { zoom: options });
|
|
1424
|
+
},
|
|
1140
1425
|
/**
|
|
1141
1426
|
* Get the current map bearing. Enable the `rotate` map option before calling.
|
|
1142
1427
|
*
|
|
@@ -1367,23 +1652,26 @@ var leafletRotate = (() => {
|
|
|
1367
1652
|
// return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
|
|
1368
1653
|
// },
|
|
1369
1654
|
/**
|
|
1370
|
-
* Return the
|
|
1655
|
+
* Return the screen-space pan offset needed to center the given geographic point.
|
|
1656
|
+
* Under perspective, projecting a negated layer offset is not equivalent to
|
|
1657
|
+
* negating its screen projection. Reflect the target about the layer center
|
|
1658
|
+
* to match the opposite anchor displacement performed by Leaflet's `panBy`.
|
|
1371
1659
|
*
|
|
1372
1660
|
* @param {L.LatLng} latlng Geographic point.
|
|
1373
1661
|
*
|
|
1374
|
-
* @returns {L.Point} Offset in
|
|
1662
|
+
* @returns {L.Point} Offset in container pixels (the map pane is panned in screen space), adjusted for bearing and pitch.
|
|
1375
1663
|
*
|
|
1376
1664
|
* @since leaflet-rotate (v0.1.0)
|
|
1377
1665
|
*/
|
|
1378
1666
|
_getCenterOffset(latlng) {
|
|
1379
|
-
|
|
1667
|
+
if (!this._rotate) {
|
|
1668
|
+
return mapProto._getCenterOffset.apply(this, arguments);
|
|
1669
|
+
}
|
|
1380
1670
|
if (this._pitch) {
|
|
1381
|
-
const center = this.getSize().
|
|
1382
|
-
|
|
1383
|
-
} else if (this._rotate) {
|
|
1384
|
-
centerOffset = centerOffset.rotate(this._bearing);
|
|
1671
|
+
const center = this.getSize().divideBy(2), layerCenter = this.containerPointToLayerPoint(center), opposite = layerCenter.multiplyBy(2).subtract(this.project(latlng).subtract(this._pixelOrigin));
|
|
1672
|
+
return center.subtract(this.layerPointToContainerPoint(opposite));
|
|
1385
1673
|
}
|
|
1386
|
-
return
|
|
1674
|
+
return this.latLngToContainerPoint(latlng)._subtract(this.getSize()._divideBy(2));
|
|
1387
1675
|
},
|
|
1388
1676
|
/**
|
|
1389
1677
|
* Return the current position of the rotated pane.
|
|
@@ -1410,11 +1698,10 @@ var leafletRotate = (() => {
|
|
|
1410
1698
|
* @since leaflet-rotate (v0.1.0)
|
|
1411
1699
|
*/
|
|
1412
1700
|
_getNewPixelOrigin(center, zoom) {
|
|
1413
|
-
if (!this._rotate
|
|
1701
|
+
if (!this._rotate) {
|
|
1414
1702
|
return mapProto._getNewPixelOrigin.apply(this, arguments);
|
|
1415
1703
|
}
|
|
1416
|
-
|
|
1417
|
-
return this.project(center, zoom).rotate(this._bearing)._subtract(viewHalf)._add(this._getMapPanePos())._add(this._getRotatePanePos()).rotate(-this._bearing)._round();
|
|
1704
|
+
return this.project(center, zoom)._subtract(this.containerPointToLayerPoint(this.getSize()._divideBy(2)))._round();
|
|
1418
1705
|
},
|
|
1419
1706
|
/**
|
|
1420
1707
|
* Calculate the pixel bounds needed to cover the transformed viewport.
|
|
@@ -1434,18 +1721,21 @@ var leafletRotate = (() => {
|
|
|
1434
1721
|
if (!this._rotate && mapProto._getNewPixelBounds) {
|
|
1435
1722
|
return mapProto._getNewPixelBounds.apply(this, arguments);
|
|
1436
1723
|
}
|
|
1437
|
-
const mapZoom = this._animatingZoom ? Math.max(this._animateToZoom, this.getZoom()) : this.getZoom(), scale = this.getZoomScale(mapZoom, zoom), pixelCenter = this.project(center, zoom).floor(),
|
|
1438
|
-
|
|
1439
|
-
this.containerPointToLayerPoint(
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1724
|
+
const mapZoom = this._animatingZoom ? Math.max(this._animateToZoom, this.getZoom()) : this.getZoom(), scale = this.getZoomScale(mapZoom, zoom), pixelCenter = this.project(center, zoom).floor(), [min, max] = this._getPitchedContainerBounds(0), corners = [[min.x, min.y], [max.x, min.y], [min.x, max.y], [max.x, max.y]].map((point) => this.containerPointToLayerPoint(point));
|
|
1725
|
+
if (this._pitch) {
|
|
1726
|
+
const viewCenter = this.containerPointToLayerPoint(this.getSize()._divideBy(2));
|
|
1727
|
+
return new L.Bounds(corners.map((point) => pixelCenter.add(point.subtract(viewCenter).divideBy(scale))));
|
|
1728
|
+
}
|
|
1729
|
+
const halfSize = new L.Bounds(corners.map((point) => point.floor())).getSize().divideBy(scale * 2);
|
|
1443
1730
|
return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
|
|
1444
1731
|
},
|
|
1445
1732
|
/**
|
|
1733
|
+
* Return the viewport center before the outer map-pane translation.
|
|
1734
|
+
* Used as the bearing/pitch pivot in map-pane coordinates, not layer coordinates.
|
|
1735
|
+
*
|
|
1446
1736
|
* @since leaflet-rotate (v0.2.0)
|
|
1447
1737
|
*
|
|
1448
|
-
* @returns {L.Point} Map pivot
|
|
1738
|
+
* @returns {L.Point} Map pivot in map-pane pixels.
|
|
1449
1739
|
*/
|
|
1450
1740
|
_getPixelCenter() {
|
|
1451
1741
|
if (!this._rotate && mapProto._getPixelCenter) {
|
|
@@ -1454,6 +1744,9 @@ var leafletRotate = (() => {
|
|
|
1454
1744
|
return this.getSize()._divideBy(2)._subtract(this._getMapPanePos());
|
|
1455
1745
|
},
|
|
1456
1746
|
/**
|
|
1747
|
+
* Convert the padded visible ground rectangle to layer bounds for vector renderers.
|
|
1748
|
+
* Clip its far edge before reversing perspective to avoid unbounded render areas.
|
|
1749
|
+
*
|
|
1457
1750
|
* @see src\layer\vector\Renderer::_update()
|
|
1458
1751
|
*
|
|
1459
1752
|
* @param {number} padding Bounds padding as a fraction of map size.
|
|
@@ -1466,7 +1759,7 @@ var leafletRotate = (() => {
|
|
|
1466
1759
|
if (!this._rotate && mapProto._getPaddedPixelBounds) {
|
|
1467
1760
|
return mapProto._getPaddedPixelBounds.apply(this, arguments);
|
|
1468
1761
|
}
|
|
1469
|
-
const
|
|
1762
|
+
const [padMin, padMax] = this._getPitchedContainerBounds(padding);
|
|
1470
1763
|
return new L.Bounds([
|
|
1471
1764
|
this.containerPointToLayerPoint([padMin.x, padMin.y]).floor(),
|
|
1472
1765
|
this.containerPointToLayerPoint([padMin.x, padMax.y]).floor(),
|
|
@@ -1541,12 +1834,18 @@ var leafletRotate = (() => {
|
|
|
1541
1834
|
},
|
|
1542
1835
|
/**
|
|
1543
1836
|
* Subscribe to the supported device-orientation event, if available.
|
|
1837
|
+
* On iOS, permission is requested first (call `enable()` from a user gesture).
|
|
1544
1838
|
*
|
|
1545
1839
|
* @since leaflet-rotate (v0.1.0)
|
|
1546
1840
|
*/
|
|
1547
1841
|
addHooks() {
|
|
1548
1842
|
if (this._map._rotate && this.__deviceOrientationEvent) {
|
|
1549
1843
|
L.DomEvent.on(window, this.__deviceOrientationEvent, this._throttled, this);
|
|
1844
|
+
const DOE = window.DeviceOrientationEvent;
|
|
1845
|
+
if (DOE && DOE.requestPermission) {
|
|
1846
|
+
DOE.requestPermission().then((state) => "denied" === state && this.enabled() && this.disable()).catch(() => {
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1550
1849
|
} else {
|
|
1551
1850
|
this.disable();
|
|
1552
1851
|
}
|
|
@@ -1677,6 +1976,7 @@ var leafletRotate = (() => {
|
|
|
1677
1976
|
this._map = map;
|
|
1678
1977
|
this.rotate = !!this._map.options.touchRotate;
|
|
1679
1978
|
this.zoom = !!this._map.options.touchZoom;
|
|
1979
|
+
this.pitch = !!this._map.options.touchPitch;
|
|
1680
1980
|
},
|
|
1681
1981
|
/**
|
|
1682
1982
|
* Listen for the start of two-finger gestures on the map container.
|
|
@@ -1703,10 +2003,18 @@ var leafletRotate = (() => {
|
|
|
1703
2003
|
*/
|
|
1704
2004
|
_onTouchStart(e) {
|
|
1705
2005
|
const map = this._map;
|
|
1706
|
-
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating) {
|
|
2006
|
+
if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming || this._rotating || this._gesture) {
|
|
1707
2007
|
return;
|
|
1708
2008
|
}
|
|
1709
2009
|
const p1 = touchToContainerPoint(map, e.touches[0]), p2 = touchToContainerPoint(map, e.touches[1]), vector = p1.subtract(p2);
|
|
2010
|
+
const canPitch = this.pitch && map._rotate && Math.abs(vector.x) > Math.abs(vector.y);
|
|
2011
|
+
if (!canPitch && !this.zoom && !this.rotate) {
|
|
2012
|
+
return;
|
|
2013
|
+
}
|
|
2014
|
+
this._gesture = canPitch ? "pending" : "pinch";
|
|
2015
|
+
this._startP1 = p1;
|
|
2016
|
+
this._startP2 = p2;
|
|
2017
|
+
map._stopPitchBearingAnimation();
|
|
1710
2018
|
this._centerPoint = map.getSize()._divideBy(2);
|
|
1711
2019
|
this._startLatLng = map.containerPointToLatLng(this._centerPoint);
|
|
1712
2020
|
if (this.zoom) {
|
|
@@ -1742,11 +2050,36 @@ var leafletRotate = (() => {
|
|
|
1742
2050
|
* @since leaflet-rotate (v0.1.0)
|
|
1743
2051
|
*/
|
|
1744
2052
|
_onTouchMove(e) {
|
|
1745
|
-
if (!e.touches || e.touches.length !== 2 || !
|
|
2053
|
+
if (!e.touches || e.touches.length !== 2 || !this._gesture) {
|
|
1746
2054
|
return;
|
|
1747
2055
|
}
|
|
1748
2056
|
const map = this._map, p1 = touchToContainerPoint(map, e.touches[0]), p2 = touchToContainerPoint(map, e.touches[1]), vector = p1.subtract(p2), scale = p1.distanceTo(p2) / this._startDist;
|
|
1749
2057
|
let delta;
|
|
2058
|
+
if (this._gesture === "pending") {
|
|
2059
|
+
const d1 = p1.subtract(this._startP1), d2 = p2.subtract(this._startP2);
|
|
2060
|
+
if (Math.max(d1.x * d1.x + d1.y * d1.y, d2.x * d2.x + d2.y * d2.y) < 25) {
|
|
2061
|
+
L.DomEvent.preventDefault(e);
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
const vertical = (d) => Math.abs(d.y) > Math.abs(d.x);
|
|
2065
|
+
if (vertical(d1) && vertical(d2) && Math.sign(d1.y) === Math.sign(d2.y)) {
|
|
2066
|
+
this._gesture = "pitch";
|
|
2067
|
+
this._lastPitchY = (this._startP1.y + this._startP2.y) / 2;
|
|
2068
|
+
this._zooming = this._rotating = false;
|
|
2069
|
+
} else {
|
|
2070
|
+
this._gesture = "pinch";
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
if (this._gesture === "pitch") {
|
|
2074
|
+
const y = (p1.y + p2.y) / 2;
|
|
2075
|
+
map._setPitchAndBearing(map.getPitch() - (y - this._lastPitchY) * 0.5, map.getBearing());
|
|
2076
|
+
this._lastPitchY = y;
|
|
2077
|
+
L.DomEvent.preventDefault(e);
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
if (!(this._zooming || this._rotating)) {
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
1750
2083
|
if (this._rotating) {
|
|
1751
2084
|
const theta = Math.atan(vector.x / vector.y);
|
|
1752
2085
|
let bearingDelta = (theta - this._startTheta) * (180 / Math.PI);
|
|
@@ -1797,8 +2130,12 @@ var leafletRotate = (() => {
|
|
|
1797
2130
|
* @since leaflet-rotate (v0.1.0)
|
|
1798
2131
|
*/
|
|
1799
2132
|
_onTouchEnd() {
|
|
1800
|
-
|
|
2133
|
+
const gesture = this._gesture;
|
|
2134
|
+
delete this._gesture;
|
|
2135
|
+
if (gesture === "pitch" || gesture === "pending" || !this._moved || !(this._zooming || this._rotating)) {
|
|
1801
2136
|
this._zooming = false;
|
|
2137
|
+
this._rotating = false;
|
|
2138
|
+
L.DomEvent.off(document, "touchmove", this._onTouchMove, this).off(document, "touchend touchcancel", this._onTouchEnd, this);
|
|
1802
2139
|
return;
|
|
1803
2140
|
}
|
|
1804
2141
|
this._zooming = false;
|
|
@@ -1847,6 +2184,38 @@ var leafletRotate = (() => {
|
|
|
1847
2184
|
});
|
|
1848
2185
|
L.Map.addInitHook("addHandler", "touchRotate", L.Map.TouchRotate);
|
|
1849
2186
|
|
|
2187
|
+
// src/map/handler/TouchPitch.js
|
|
2188
|
+
L.Map.mergeOptions({
|
|
2189
|
+
/**
|
|
2190
|
+
* Whether the map can be pitched by dragging two fingers vertically
|
|
2191
|
+
*
|
|
2192
|
+
* @type {Boolean}
|
|
2193
|
+
*
|
|
2194
|
+
* @since leaflet-rotate (0.5.1)
|
|
2195
|
+
*/
|
|
2196
|
+
touchPitch: false
|
|
2197
|
+
});
|
|
2198
|
+
L.Map.TouchPitch = L.Handler.extend({
|
|
2199
|
+
/**
|
|
2200
|
+
* Enable pitch handling in the map's shared touch gesture handler.
|
|
2201
|
+
*
|
|
2202
|
+
* @since leaflet-rotate (0.5.1)
|
|
2203
|
+
*/
|
|
2204
|
+
addHooks() {
|
|
2205
|
+
this._map.touchGestures.enable();
|
|
2206
|
+
this._map.touchGestures.pitch = true;
|
|
2207
|
+
},
|
|
2208
|
+
/**
|
|
2209
|
+
* Disable pitch handling without affecting pinch zoom and rotation.
|
|
2210
|
+
*
|
|
2211
|
+
* @since leaflet-rotate (0.5.1)
|
|
2212
|
+
*/
|
|
2213
|
+
removeHooks() {
|
|
2214
|
+
this._map.touchGestures.pitch = false;
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
L.Map.addInitHook("addHandler", "touchPitch", L.Map.TouchPitch);
|
|
2218
|
+
|
|
1850
2219
|
// src/map/handler/ShiftKeyRotate.js
|
|
1851
2220
|
L.Map.mergeOptions({
|
|
1852
2221
|
/**
|
|
@@ -1858,7 +2227,7 @@ var leafletRotate = (() => {
|
|
|
1858
2227
|
*/
|
|
1859
2228
|
shiftKeyRotate: true,
|
|
1860
2229
|
/**
|
|
1861
|
-
* Whether Ctrl+drag changes bearing and pitch.
|
|
2230
|
+
* Whether Ctrl+drag (or right-button drag) changes bearing and pitch.
|
|
1862
2231
|
*
|
|
1863
2232
|
* @type {Boolean}
|
|
1864
2233
|
*
|
|
@@ -1877,6 +2246,8 @@ var leafletRotate = (() => {
|
|
|
1877
2246
|
this._onMouseDownBound = this._onMouseDown.bind(this);
|
|
1878
2247
|
this._onMouseMoveBound = this._onMouseMove.bind(this);
|
|
1879
2248
|
this._onMouseUpBound = this._onMouseUp.bind(this);
|
|
2249
|
+
this._onContextMenuBound = this._onContextMenu.bind(this);
|
|
2250
|
+
this._applyBound = this._apply.bind(this);
|
|
1880
2251
|
},
|
|
1881
2252
|
/**
|
|
1882
2253
|
* Attach enabled wheel and pointer/mouse gesture listeners.
|
|
@@ -1911,6 +2282,7 @@ var leafletRotate = (() => {
|
|
|
1911
2282
|
}
|
|
1912
2283
|
this._pitchEvents = L.version.startsWith("2.") ? ["pointerdown", "pointermove", "pointerup pointercancel"] : ["mousedown", "mousemove", "mouseup"];
|
|
1913
2284
|
this._map._container.addEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
|
|
2285
|
+
this._map._container.addEventListener("contextmenu", this._onContextMenuBound, true);
|
|
1914
2286
|
this._pitchHooksAdded = true;
|
|
1915
2287
|
},
|
|
1916
2288
|
/**
|
|
@@ -1923,6 +2295,7 @@ var leafletRotate = (() => {
|
|
|
1923
2295
|
return;
|
|
1924
2296
|
}
|
|
1925
2297
|
this._map._container.removeEventListener(this._pitchEvents[0], this._onMouseDownBound, true);
|
|
2298
|
+
this._map._container.removeEventListener("contextmenu", this._onContextMenuBound, true);
|
|
1926
2299
|
this._onMouseUp();
|
|
1927
2300
|
delete this._pitchEvents;
|
|
1928
2301
|
this._pitchHooksAdded = false;
|
|
@@ -1944,14 +2317,18 @@ var leafletRotate = (() => {
|
|
|
1944
2317
|
}
|
|
1945
2318
|
},
|
|
1946
2319
|
/**
|
|
1947
|
-
* Start Ctrl+drag and capture subsequent movement outside the map.
|
|
2320
|
+
* Start Ctrl+drag (or right-button drag) and capture subsequent movement outside the map.
|
|
1948
2321
|
*
|
|
1949
2322
|
* @param {MouseEvent|PointerEvent} e Gesture start event.
|
|
1950
2323
|
*
|
|
1951
2324
|
* @since leaflet-rotate (0.5.0)
|
|
1952
2325
|
*/
|
|
1953
2326
|
_onMouseDown(e) {
|
|
1954
|
-
if (
|
|
2327
|
+
if (this._dragging) {
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
this._moved = false;
|
|
2331
|
+
if (!this._map._rotate || !this._map.options.pitchDrag || !(e.button === 2 || e.ctrlKey && e.button === 0)) {
|
|
1955
2332
|
return;
|
|
1956
2333
|
}
|
|
1957
2334
|
const scrollWheelZoom = this._map.scrollWheelZoom;
|
|
@@ -1964,42 +2341,82 @@ var leafletRotate = (() => {
|
|
|
1964
2341
|
e.preventDefault();
|
|
1965
2342
|
e.stopPropagation();
|
|
1966
2343
|
e.stopImmediatePropagation();
|
|
2344
|
+
this._map._stopPitchBearingAnimation();
|
|
2345
|
+
this._dragging = true;
|
|
1967
2346
|
this._lastX = e.clientX;
|
|
1968
2347
|
this._lastY = e.clientY;
|
|
2348
|
+
this._pitchDelta = this._bearingDelta = 0;
|
|
1969
2349
|
document.addEventListener(this._pitchEvents[1], this._onMouseMoveBound);
|
|
1970
2350
|
for (const event of this._pitchEvents[2].split(" ")) {
|
|
1971
2351
|
document.addEventListener(event, this._onMouseUpBound);
|
|
1972
2352
|
}
|
|
1973
2353
|
},
|
|
1974
2354
|
/**
|
|
1975
|
-
*
|
|
2355
|
+
* Accumulate pointer movement as pitch (vertical) and bearing (horizontal).
|
|
2356
|
+
* Far from the horizontal center line the bearing follows the pointer angle
|
|
2357
|
+
* around the map center.
|
|
1976
2358
|
*
|
|
1977
2359
|
* @param {MouseEvent|PointerEvent} e Gesture movement event.
|
|
1978
2360
|
*
|
|
1979
2361
|
* @since leaflet-rotate (0.5.0)
|
|
1980
2362
|
*/
|
|
1981
2363
|
_onMouseMove(e) {
|
|
1982
|
-
this._map.
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
this.
|
|
1987
|
-
this.
|
|
2364
|
+
const rect = this._map._container.getBoundingClientRect(), cx = rect.left + rect.width / 2, cy = rect.top + rect.height / 2, x = e.clientX, y = e.clientY;
|
|
2365
|
+
this._pitchDelta += (this._lastY - y) * 0.5;
|
|
2366
|
+
this._bearingDelta += Math.abs(cy - this._lastY) > 100 ? (Math.atan2(y - cy, x - cx) - Math.atan2(y - cy, this._lastX - cx)) * (180 / Math.PI) : (x - this._lastX) * 0.8 * (y < cy ? 1 : -1);
|
|
2367
|
+
this._lastX = x;
|
|
2368
|
+
this._lastY = y;
|
|
2369
|
+
this._moved = true;
|
|
2370
|
+
if (!this._frame) {
|
|
2371
|
+
this._frame = requestAnimationFrame(this._applyBound);
|
|
2372
|
+
}
|
|
1988
2373
|
e.preventDefault();
|
|
1989
2374
|
},
|
|
1990
2375
|
/**
|
|
1991
|
-
*
|
|
2376
|
+
* Apply accumulated movement once per animation frame.
|
|
2377
|
+
*
|
|
2378
|
+
* @since leaflet-rotate (0.5.1)
|
|
2379
|
+
*/
|
|
2380
|
+
_apply() {
|
|
2381
|
+
this._frame = null;
|
|
2382
|
+
if (!this._pitchDelta && !this._bearingDelta) {
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
this._map._setPitchAndBearing(this._map.getPitch() + this._pitchDelta, this._map.getBearing() + this._bearingDelta);
|
|
2386
|
+
this._pitchDelta = this._bearingDelta = 0;
|
|
2387
|
+
},
|
|
2388
|
+
/**
|
|
2389
|
+
* Suppress the context menu that follows a right-button drag.
|
|
2390
|
+
*
|
|
2391
|
+
* @param {MouseEvent} e Context menu event.
|
|
2392
|
+
*
|
|
2393
|
+
* @since leaflet-rotate (0.5.1)
|
|
2394
|
+
*/
|
|
2395
|
+
_onContextMenu(e) {
|
|
2396
|
+
if (this._dragging || this._moved) {
|
|
2397
|
+
e.preventDefault();
|
|
2398
|
+
e.stopPropagation();
|
|
2399
|
+
}
|
|
2400
|
+
},
|
|
2401
|
+
/**
|
|
2402
|
+
* End Ctrl+drag, flush pending movement and clear document-level listeners.
|
|
1992
2403
|
*
|
|
1993
2404
|
* @since leaflet-rotate (0.5.0)
|
|
1994
2405
|
*/
|
|
1995
2406
|
_onMouseUp() {
|
|
1996
|
-
if (!this._pitchEvents) {
|
|
2407
|
+
if (!this._pitchEvents || !this._dragging) {
|
|
1997
2408
|
return;
|
|
1998
2409
|
}
|
|
1999
2410
|
document.removeEventListener(this._pitchEvents[1], this._onMouseMoveBound);
|
|
2000
2411
|
for (const event of this._pitchEvents[2].split(" ")) {
|
|
2001
2412
|
document.removeEventListener(event, this._onMouseUpBound);
|
|
2002
2413
|
}
|
|
2414
|
+
cancelAnimationFrame(this._frame);
|
|
2415
|
+
if (this._map._mapPane) {
|
|
2416
|
+
this._apply();
|
|
2417
|
+
}
|
|
2418
|
+
this._frame = null;
|
|
2419
|
+
this._dragging = false;
|
|
2003
2420
|
delete this._lastX;
|
|
2004
2421
|
delete this._lastY;
|
|
2005
2422
|
}
|
|
@@ -2073,10 +2490,11 @@ var leafletRotate = (() => {
|
|
|
2073
2490
|
L.Control.Rotate = L.Control.extend({
|
|
2074
2491
|
options: {
|
|
2075
2492
|
position: "topleft",
|
|
2076
|
-
closeOnZeroBearing: true
|
|
2493
|
+
closeOnZeroBearing: true,
|
|
2494
|
+
visualizePitch: true
|
|
2077
2495
|
},
|
|
2078
2496
|
/**
|
|
2079
|
-
* Create the control element and subscribe to map rotation.
|
|
2497
|
+
* Create the control element and subscribe to map rotation and pitch.
|
|
2080
2498
|
*
|
|
2081
2499
|
* @param {L.Map} map
|
|
2082
2500
|
*
|
|
@@ -2088,24 +2506,27 @@ var leafletRotate = (() => {
|
|
|
2088
2506
|
const container = this._container = L.DomUtil.create("div", "leaflet-control-rotate leaflet-bar");
|
|
2089
2507
|
const arrow = this._arrow = L.DomUtil.create("span", "leaflet-control-rotate-arrow");
|
|
2090
2508
|
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")`;
|
|
2091
|
-
arrow.style.cursor = "grab";
|
|
2092
2509
|
arrow.style.display = "block";
|
|
2093
2510
|
arrow.style.width = "100%";
|
|
2094
2511
|
arrow.style.height = "100%";
|
|
2095
2512
|
arrow.style.backgroundRepeat = "no-repeat";
|
|
2096
2513
|
arrow.style.backgroundPosition = "50%";
|
|
2514
|
+
arrow.style.pointerEvents = "none";
|
|
2097
2515
|
const link = this._link = L.DomUtil.create("a", "leaflet-control-rotate-toggle", container);
|
|
2098
2516
|
link.appendChild(arrow);
|
|
2099
2517
|
link.href = "#";
|
|
2100
|
-
link.title = "
|
|
2101
|
-
|
|
2518
|
+
link.title = this.options.visualizePitch ? "Reset bearing and pitch" : "Reset bearing to north";
|
|
2519
|
+
link.setAttribute("role", "button");
|
|
2520
|
+
link.setAttribute("aria-label", link.title);
|
|
2521
|
+
link.style.cursor = "grab";
|
|
2522
|
+
link.style.touchAction = "none";
|
|
2523
|
+
this._onPointerMoveBound = this._onPointerMove.bind(this);
|
|
2524
|
+
this._onPointerUpBound = this._onPointerUp.bind(this);
|
|
2525
|
+
L.DomEvent.on(link, "dblclick contextmenu", L.DomEvent.stop).on(link, "click", L.DomEvent.stop).on(link, "click", this._onClick, this).on(link, "click", this._refocusOnMap, this);
|
|
2526
|
+
link.addEventListener("pointerdown", this._onPointerDown.bind(this));
|
|
2527
|
+
map.on("rotate pitch", this._restyle, this);
|
|
2528
|
+
this._map = map;
|
|
2102
2529
|
this._restyle();
|
|
2103
|
-
map.on("rotate", this._restyle, this);
|
|
2104
|
-
this._follow = false;
|
|
2105
|
-
this._canFollow = false;
|
|
2106
|
-
if (this.options.closeOnZeroBearing && map.getBearing() === 0) {
|
|
2107
|
-
container.style.display = "none";
|
|
2108
|
-
}
|
|
2109
2530
|
return container;
|
|
2110
2531
|
},
|
|
2111
2532
|
/**
|
|
@@ -2116,102 +2537,119 @@ var leafletRotate = (() => {
|
|
|
2116
2537
|
* @since leaflet-rotate (v0.1.0)
|
|
2117
2538
|
*/
|
|
2118
2539
|
onRemove(map) {
|
|
2119
|
-
|
|
2540
|
+
this._onPointerUp();
|
|
2541
|
+
map.off("rotate pitch", this._restyle, this);
|
|
2120
2542
|
},
|
|
2121
2543
|
/**
|
|
2122
|
-
*
|
|
2544
|
+
* Reset bearing (and pitch) unless the click ends a drag.
|
|
2123
2545
|
*
|
|
2124
|
-
* @
|
|
2125
|
-
*
|
|
2126
|
-
* @since leaflet-rotate (v0.1.0)
|
|
2546
|
+
* @since leaflet-rotate (0.5.1)
|
|
2127
2547
|
*/
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2548
|
+
_onClick() {
|
|
2549
|
+
if (this._moved) {
|
|
2550
|
+
this._moved = false;
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (!this._map || !this._map._rotate) {
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if (this.options.visualizePitch) {
|
|
2557
|
+
this._map.resetNorthPitch();
|
|
2558
|
+
} else {
|
|
2559
|
+
this._map.resetNorth();
|
|
2560
|
+
}
|
|
2134
2561
|
},
|
|
2135
2562
|
/**
|
|
2136
|
-
*
|
|
2563
|
+
* Start dragging the control (mouse, pen or touch).
|
|
2137
2564
|
*
|
|
2138
|
-
* @param {
|
|
2565
|
+
* @param {PointerEvent} e
|
|
2139
2566
|
*
|
|
2140
2567
|
* @since leaflet-rotate (v0.1.0)
|
|
2141
2568
|
*/
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2569
|
+
_onPointerDown(e) {
|
|
2570
|
+
if (!this._map || !this._map._rotate || e.button > 0 || this._pointerId != null) {
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
e.preventDefault();
|
|
2574
|
+
e.stopPropagation();
|
|
2575
|
+
this._pointerId = e.pointerId;
|
|
2576
|
+
this._startX = this._lastX = e.clientX;
|
|
2577
|
+
this._startY = this._lastY = e.clientY;
|
|
2578
|
+
this._moved = false;
|
|
2579
|
+
this._link.style.cursor = "grabbing";
|
|
2580
|
+
document.addEventListener("pointermove", this._onPointerMoveBound);
|
|
2581
|
+
document.addEventListener("pointerup", this._onPointerUpBound);
|
|
2582
|
+
document.addEventListener("pointercancel", this._onPointerUpBound);
|
|
2146
2583
|
},
|
|
2147
2584
|
/**
|
|
2148
|
-
*
|
|
2585
|
+
* Horizontal movement changes the bearing, vertical movement the pitch.
|
|
2149
2586
|
*
|
|
2150
|
-
* @param {
|
|
2587
|
+
* @param {PointerEvent} e
|
|
2151
2588
|
*
|
|
2152
2589
|
* @since leaflet-rotate (v0.1.0)
|
|
2153
2590
|
*/
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2591
|
+
_onPointerMove(e) {
|
|
2592
|
+
if (e.pointerId !== this._pointerId) {
|
|
2156
2593
|
return;
|
|
2157
2594
|
}
|
|
2158
|
-
|
|
2159
|
-
this.
|
|
2595
|
+
e.preventDefault();
|
|
2596
|
+
if (!this._moved && Math.hypot(e.clientX - this._startX, e.clientY - this._startY) < 3) {
|
|
2597
|
+
return;
|
|
2598
|
+
}
|
|
2599
|
+
if (!this._moved) {
|
|
2600
|
+
this._moved = true;
|
|
2601
|
+
this._map._stopPitchBearingAnimation();
|
|
2602
|
+
}
|
|
2603
|
+
const map = this._map, pitch = this.options.visualizePitch ? map.getPitch() + (this._lastY - e.clientY) * 0.5 : map.getPitch();
|
|
2604
|
+
map._setPitchAndBearing(pitch, map.getBearing() + (e.clientX - this._lastX) * 0.8);
|
|
2605
|
+
this._lastX = e.clientX;
|
|
2606
|
+
this._lastY = e.clientY;
|
|
2160
2607
|
},
|
|
2161
2608
|
/**
|
|
2162
|
-
*
|
|
2163
|
-
* Compass mode may require permission from the browser.
|
|
2609
|
+
* Stop dragging and remove document listeners.
|
|
2164
2610
|
*
|
|
2165
|
-
* @param {
|
|
2611
|
+
* @param {PointerEvent} [e]
|
|
2166
2612
|
*
|
|
2167
2613
|
* @since leaflet-rotate (v0.1.0)
|
|
2168
2614
|
*/
|
|
2169
|
-
|
|
2170
|
-
if (
|
|
2615
|
+
_onPointerUp(e) {
|
|
2616
|
+
if (e && e.pointerId !== this._pointerId) {
|
|
2171
2617
|
return;
|
|
2172
2618
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
map.touchRotate.enable();
|
|
2184
|
-
}
|
|
2619
|
+
document.removeEventListener("pointermove", this._onPointerMoveBound);
|
|
2620
|
+
document.removeEventListener("pointerup", this._onPointerUpBound);
|
|
2621
|
+
document.removeEventListener("pointercancel", this._onPointerUpBound);
|
|
2622
|
+
if (e && this._moved && !this._link.contains(e.target)) {
|
|
2623
|
+
this._moved = false;
|
|
2624
|
+
}
|
|
2625
|
+
delete this._pointerId;
|
|
2626
|
+
if (this._link) {
|
|
2627
|
+
this._link.style.cursor = "grab";
|
|
2628
|
+
this._restyle();
|
|
2185
2629
|
}
|
|
2186
|
-
this._restyle();
|
|
2187
2630
|
},
|
|
2188
2631
|
/**
|
|
2189
|
-
* Update the
|
|
2632
|
+
* Update the icon (bearing and pitch) and the control visibility.
|
|
2190
2633
|
*
|
|
2191
2634
|
* @listens L.Map#rotate
|
|
2635
|
+
* @listens L.Map#pitch
|
|
2192
2636
|
*
|
|
2193
2637
|
* @since leaflet-rotate (v0.1.0)
|
|
2194
2638
|
*/
|
|
2195
2639
|
_restyle() {
|
|
2196
|
-
|
|
2640
|
+
const map = this._map;
|
|
2641
|
+
if (!map) {
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
if (!map.options.rotate) {
|
|
2197
2645
|
this._link.classList.add("leaflet-disabled");
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
if (map.compassBearing.enabled()) {
|
|
2206
|
-
this._link.style.backgroundColor = "orange";
|
|
2207
|
-
} else if (map.touchRotate.enabled()) {
|
|
2208
|
-
this._link.style.backgroundColor = null;
|
|
2209
|
-
} else {
|
|
2210
|
-
this._link.style.backgroundColor = "grey";
|
|
2211
|
-
if (0 === bearing && this.options.closeOnZeroBearing) {
|
|
2212
|
-
this._container.style.display = "none";
|
|
2213
|
-
}
|
|
2214
|
-
}
|
|
2646
|
+
return;
|
|
2647
|
+
}
|
|
2648
|
+
const bearing = map.getBearing(), pitch = map.getPitch();
|
|
2649
|
+
this._arrow.style.transform = this.options.visualizePitch && pitch ? `scale(${1 / Math.sqrt(Math.cos(pitch * Math.PI / 180))}) rotateX(${pitch}deg) rotate(${bearing}deg)` : `rotate(${bearing}deg)`;
|
|
2650
|
+
if (this.options.closeOnZeroBearing) {
|
|
2651
|
+
const hidden = !bearing && !(this.options.visualizePitch && pitch) && this._pointerId == null;
|
|
2652
|
+
this._container.style.display = hidden ? "none" : "block";
|
|
2215
2653
|
}
|
|
2216
2654
|
}
|
|
2217
2655
|
});
|