@orca-fe/x-map
Version:
498 lines (497 loc) • 20.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const mjolnir_js_1 = require("mjolnir.js");
const resize_observer_polyfill_1 = tslib_1.__importDefault(require("resize-observer-polyfill"));
const private_1 = require("../utils/private");
const WebViewport_1 = require("../utils/WebViewport");
const MapControllerEvent_1 = tslib_1.__importDefault(require("./MapControllerEvent"));
const transition_1 = require("../utils/transition");
const defaultBounds = [
[-180, -85],
[180, 85],
];
const defaultViewport = {
lng: 121,
lat: 31,
zoom: 8,
pitch: 0,
rotate: 0,
};
class MapController extends MapControllerEvent_1.default {
constructor(dom, options = {}) {
super();
this.timer = -1;
this.domSize = [100, 100];
/** 标记是否右键点击可用,若不可用则需要用到contextmenu事件 */
this.canRightButtonDown = false;
this.rightButtonDown = false;
this.startPos = null;
this.startRotation = null;
this.startPitch = null;
/** 记录触摸按下的数量 */
this.pointerdown = 0;
this.moving = false;
this.lastPos = { x: Infinity, y: Infinity };
this.handlePointerMoveOrigin = (e) => {
const x = e.clientX;
const y = e.clientY;
if (this.startPos == null) {
return;
}
if (this.lastPos.x === x && this.lastPos.y === y) {
return;
}
this.lastPos = { x, y };
if (!this.moving) {
this.emit('move-start');
this.moving = true;
}
const [lng, lat] = this.startPos;
const vpp = this.getViewportWithProjection();
const [centerLng, centerLat] = vpp.getMapCenterByLngLatPosition({
lngLat: [lng, lat],
pos: [x, y],
});
this.triggerViewportChange({
lng: Number(centerLng.toFixed(6)),
lat: Number(centerLat.toFixed(6)),
});
};
this.wheelDelta = 0;
this.zoomStart = null;
this.wheelTimer = 0;
this.pinchstartCoor = null;
this.pinchOrRotate = null;
this.triggerViewportChangePause = (0, private_1.debounce)((viewport) => {
this.emit('viewport-change-pause', viewport);
}, 300);
this.animTriggerTimer = -1;
const eventManager = new mjolnir_js_1.EventManager(dom);
// dom.addEventListener('mousemove', this.handlePointerMoveOrigin);
eventManager.on({
pointerdown: this.handlePointerDown.bind(this),
pointermove: this.handlePointerMove.bind(this),
pointerup: this.handlePointerUp.bind(this),
wheel: this.handleWheel.bind(this),
contextmenu: this.handleContextmenu.bind(this),
pinchstart: this.handlePinchStart.bind(this),
pinchmove: this.handlePinchMove.bind(this),
pinchend: this.handlePinchEnd.bind(this),
rotatestart: this.handleRotateStart.bind(this),
rotatemove: this.handleRotateMove.bind(this),
rotateend: this.handleRotateEnd.bind(this),
});
const ro = new resize_observer_polyfill_1.default((entries, observer) => {
this.computeSize();
this.triggerViewportChange();
});
ro.observe(dom);
this.ro = ro;
this.on('destroy', () => {
eventManager.destroy();
dom.removeEventListener('mousemove', this.handlePointerMoveOrigin);
this.ro.disconnect();
this.removeAllListeners();
});
const { viewport = defaultViewport, canPitch = true, canRotate = true, projection = 'mercator', maxZoom = 19, minZoom = 2, maxPitch = 60, disableMove = false, disableZoom = false, disableRotate = false, rotateDelay = 6, zoomDelay = 0.1, motion = false, limit = defaultBounds, } = options;
this.viewport = viewport;
this.realViewport = viewport;
this.motion = motion;
this.canPitch = canPitch;
this.canRotate = canRotate;
this.projection = projection;
this.maxZoom = maxZoom;
this.minZoom = minZoom;
this.maxPitch = maxPitch;
this.disableMove = disableMove;
this.disableZoom = disableZoom;
this.disableRotate = disableRotate;
this.rotateDelay = rotateDelay;
this.zoomDelay = zoomDelay;
this.limit = limit;
this.dom = dom;
this.computeSize();
let time = undefined;
const startAnim = () => {
this.timer = requestAnimationFrame((ctime) => {
const timeDiff = ctime - (time !== null && time !== void 0 ? time : ctime);
this.animViewport(timeDiff);
time = ctime;
if (this.motion) {
startAnim();
}
});
};
startAnim();
this.on('destroy', () => {
cancelAnimationFrame(this.timer);
});
}
computeSize() {
if (this.dom) {
this.domSize = [this.dom.clientWidth, this.dom.clientHeight];
}
}
destroy() {
this.emit('destroy');
}
setViewport(viewport) {
this.realViewport = viewport;
this.viewport = viewport;
}
getViewport(real = false) {
const [width, height] = this.domSize;
return Object.assign(Object.assign({}, (real ? this.realViewport : this.viewport)), { width,
height });
}
getViewportWithProjection() {
const wmvp = (0, WebViewport_1.getMapViewport)(this.getViewport(), this.projection);
return wmvp;
}
getRealViewportWithProjection() {
const wmvp = (0, WebViewport_1.getMapViewport)(this.getViewport(true), this.projection);
return wmvp;
}
getLimitViewport(viewport, limitBounds = defaultBounds) {
const wmvp = (0, WebViewport_1.getMapViewport)(Object.assign(Object.assign({}, viewport), { pitch: 0, rotate: 0 }), this.projection);
const { longitude, latitude, width, height } = wmvp;
// 整个地图 左下角的像素
const minMapPixel = wmvp.project(defaultBounds[0]);
// 整个地图 右上角像素
const maxMapPixel = wmvp.project(defaultBounds[1]);
const mapSize = [maxMapPixel[0] - minMapPixel[0], minMapPixel[1] - maxMapPixel[1]];
// limit 左下角的像素
const minBoundsPixel = wmvp.project(limitBounds[0]);
// limit 右上角像素
const maxBoundsPixel = wmvp.project(limitBounds[1]);
const boundsSize = [maxBoundsPixel[0] - minBoundsPixel[0], minBoundsPixel[1] - maxBoundsPixel[1]];
let lng = longitude;
let lat = latitude;
const centerOffset = [0, 0];
if (minBoundsPixel[0] > 0) {
// eslint-disable-next-line prefer-destructuring
centerOffset[0] = minBoundsPixel[0];
}
if (maxBoundsPixel[0] < width) {
centerOffset[0] = maxBoundsPixel[0] - width;
}
if (maxBoundsPixel[1] > 0) {
// eslint-disable-next-line prefer-destructuring
centerOffset[1] = maxBoundsPixel[1];
}
if (minBoundsPixel[1] < height) {
centerOffset[1] = minBoundsPixel[1] - height;
}
if (boundsSize[0] < width) {
const xPosition = minBoundsPixel[0] + 0.5 * (maxBoundsPixel[0] - minBoundsPixel[0]);
centerOffset[0] = xPosition - width / 2;
}
if (boundsSize[1] < height) {
const yPosition = maxBoundsPixel[1] + 0.5 * (minBoundsPixel[1] - maxBoundsPixel[1]);
centerOffset[1] = yPosition - height / 2;
}
centerOffset[0] = Math.max(centerOffset[0], minMapPixel[0]);
centerOffset[0] = Math.min(centerOffset[0], maxMapPixel[0] - width);
centerOffset[1] = Math.max(centerOffset[1], maxMapPixel[1]);
centerOffset[1] = Math.min(centerOffset[1], minMapPixel[1] - height);
if (centerOffset[0] !== 0 || centerOffset[1] !== 0) {
[lng, lat] = wmvp.unproject([width / 2 + centerOffset[0], height / 2 + centerOffset[1]]);
}
if (mapSize[0] < width) {
lng = 0;
}
if (mapSize[1] < height) {
lat = 0;
}
const newViewport = Object.assign(Object.assign({}, viewport), { lng: (0, private_1.toFixedNumber)(lng, 6), lat: (0, private_1.toFixedNumber)(lat, 6) });
return newViewport;
}
handlePointerDown(e) {
if (!this.controllable(e, 'move')) {
return;
}
if (e.rightButton) {
this.canRightButtonDown = true;
this.rightButtonDown = true;
const viewport = this.getViewport();
this.startPitch = viewport.pitch;
this.startRotation = viewport.rotate;
}
/* 如果超过2指,则取消平移效果 */
if (e.pointers.length > 1) {
this.handlePointerUp();
}
else {
this.lastPos = { x: Infinity, y: Infinity };
}
const { x, y } = e.offsetCenter;
// 根据 点击的坐标 记录起始坐标
const vpp = this.getViewportWithProjection();
const [lng, lat] = vpp.unproject([x, y]);
this.startPos = [lng, lat];
}
handleContextmenu(e) {
if (!this.controllable(e, 'move')) {
return;
}
if (!this.canRightButtonDown) {
// 无法通过 pointerdown 事件捕获右键点击的情况
const { x, y } = e.offsetCenter;
const vpp = this.getViewportWithProjection();
const [lng, lat] = vpp.unproject([x, y]);
this.startPos = [lng, lat];
}
e.preventDefault();
}
handlePointerMove(e) {
const { x, y } = e.offsetCenter;
if (this.startPos == null) {
return;
}
if (this.lastPos.x === x && this.lastPos.y === y) {
return;
}
this.lastPos = { x, y };
if (e.leftButton || e.rightButton || e.pointerType === 'touch') {
if (!this.moving) {
this.emit('move-start');
this.moving = true;
}
}
if (e.rightButton) {
if (this.startPitch != null && this.startRotation != null) {
this.triggerViewportChange({
pitch: this.canPitch ? (0, private_1.clamp)(this.startPitch - e.deltaY / 2, 0, this.maxPitch) : 0,
rotate: this.canRotate ? this.startRotation + e.deltaX / 2 : 0,
});
}
}
else if (e.leftButton || (e.pointerType === 'touch' && e.pointers.length === 1)) {
if (this.rightButtonDown) {
this.handlePointerUp();
return;
}
const [lng, lat] = this.startPos;
const vpp = this.getViewportWithProjection();
const [centerLng, centerLat] = vpp.getMapCenterByLngLatPosition({
lngLat: [lng, lat],
pos: [x, y],
});
this.triggerViewportChange({
lng: Number(centerLng.toFixed(6)),
lat: Number(centerLat.toFixed(6)),
});
}
}
handlePointerUp() {
this.startPos = null;
this.startRotation = null;
this.startPitch = null;
this.rightButtonDown = false;
this.pointerdown -= 1;
this.pointerdown = Math.max(0, this.pointerdown);
if (this.moving) {
this.moving = false;
this.emit('move-end');
}
}
handleWheel(e) {
const { maxZoom, minZoom } = this;
const viewport = this.getViewport();
if (!this.controllable(e, 'zoom')) {
return;
}
const { x, y } = e.offsetCenter;
if (Math.sign(this.wheelDelta) !== Math.sign(e.delta)) {
this.wheelDelta = 0;
this.zoomStart = null;
}
if (this.zoomStart == null) {
this.zoomStart = viewport.zoom;
}
this.wheelDelta += (0, private_1.clamp)(e.delta, -20, 20);
const newZoom = Number((0, private_1.clamp)(this.zoomStart + (this.wheelDelta || 0) * 0.01, minZoom, maxZoom).toFixed(2));
// origin center
const [lng, lat] = (0, WebViewport_1.getMapViewport)(viewport).unproject([x, y]);
const vpp = (0, WebViewport_1.getMapViewport)(Object.assign(Object.assign({}, viewport), { zoom: newZoom }), this.projection);
const [centerLng, centerLat] = vpp.getMapCenterByLngLatPosition({
lngLat: [lng, lat],
pos: [x, y],
});
const newViewport = Object.assign(Object.assign({}, viewport), { zoom: newZoom, lng: centerLng, lat: centerLat });
this.triggerViewportChange(newViewport, { x, y });
clearTimeout(this.wheelTimer);
this.wheelTimer = setTimeout(() => {
this.wheelDelta = 0;
this.zoomStart = null;
}, 300);
e.preventDefault();
}
handlePinchStart(e) {
if (!this.controllable(e, 'zoom')) {
return;
}
const viewport = this.getViewport();
const { x, y } = e.offsetCenter;
this.viewport = viewport;
const wmvp = (0, WebViewport_1.getMapViewport)(viewport, this.projection);
const [lng, lat] = wmvp.unproject([x, y]);
this.pinchstartCoor = [lng, lat];
this.pinchOrRotate = null;
}
handlePinchMove(e) {
if (this.pinchstartCoor) {
const [lng, lat] = this.pinchstartCoor;
const { x, y } = e.offsetCenter;
const zoomDiff = Math.log2(e.scale);
if (this.pinchOrRotate == null) {
if (Math.abs(zoomDiff) < this.zoomDelay) {
return;
}
this.pinchOrRotate = 'pinch';
}
else if (this.pinchOrRotate !== 'pinch') {
return;
}
const viewport = this.getViewport();
const newZoom = (0, private_1.clamp)(viewport.zoom + zoomDiff, 3, 18);
const wmvp = this.getViewportWithProjection();
const [centerLng, centerLat] = wmvp.getMapCenterByLngLatPosition({
lngLat: [lng, lat],
pos: [x, y],
});
this.triggerViewportChange({
lng: Number(centerLng.toFixed(6)),
lat: Number(centerLat.toFixed(6)),
zoom: newZoom,
});
}
}
handlePinchEnd() {
this.pinchstartCoor = null;
}
handleRotateStart(e) {
if (!this.controllable(e, 'rotate')) {
return;
}
this.startRotation = e.rotation;
/* 延迟旋转 */
this.pinchOrRotate = null;
}
handleRotateMove(e) {
const { disableRotate, rotateDelay } = this;
if (!disableRotate && this.startRotation != null) {
const rotationDiff = e.rotation - this.startRotation;
if (this.pinchOrRotate == null) {
if (Math.abs(rotationDiff) < rotateDelay) {
return;
}
this.pinchOrRotate = 'rotate';
}
else if (this.pinchOrRotate !== 'rotate') {
return;
}
const { rotate } = this.getViewport();
this.triggerViewportChange({
rotate: rotate - rotationDiff,
});
}
}
handleRotateEnd() {
this.startRotation = null;
}
triggerViewportChange(changedViewport = {}, mousePoint) {
const viewport = this.getViewport();
const newViewport = this.getLimitViewport(Object.assign(Object.assign({}, viewport), changedViewport), this.limit);
newViewport.zoom = Number(newViewport.zoom.toFixed(6));
this.viewport = newViewport;
this.mousePoint = mousePoint;
const isResize = Object.keys(changedViewport).length === 0;
// triggerEvents
if (!this.motion || isResize) {
this.realViewport = this.viewport;
if (viewport.zoom !== newViewport.zoom) {
this.emit('zoom', newViewport.zoom);
}
if (viewport.rotate !== newViewport.rotate) {
this.emit('rotate', newViewport.rotate);
}
if (viewport.pitch !== newViewport.pitch) {
this.emit('pitch', newViewport.pitch);
}
this.emit('viewport-change', newViewport);
this.triggerViewportChangePause(newViewport);
}
}
animViewport(time) {
const targetViewport = this.getViewport();
const viewport = this.realViewport;
if (this.viewport === this.realViewport ||
((0, private_1.isSame)(viewport.zoom, targetViewport.zoom, 0.01) &&
(0, private_1.isSame)(viewport.lng, targetViewport.lng, 0.000001) &&
(0, private_1.isSame)(viewport.lat, targetViewport.lat, 0.000001) &&
(0, private_1.isSame)(viewport.pitch, targetViewport.pitch, 0.1) &&
(0, private_1.isSame)(viewport.rotate, targetViewport.rotate, 0.1))) {
return;
}
const rate = typeof this.motion === 'number' ? this.motion : 0.9;
let r = 1;
for (let i = 0; i < time / 16; i++) {
r *= rate;
}
let newViewport = (0, transition_1.mixObj)(viewport, targetViewport, 1 - r);
// newViewport.zoom = Math.log2(mix(2 ** viewport.zoom, 2 ** targetViewport.zoom, 0.05));
newViewport.zoom = (0, private_1.toFixedNumber)((0, private_1.alignNumber)(newViewport.zoom, targetViewport.zoom, 0.005), 3);
if (this.mousePoint) {
const vp = Object.assign(Object.assign({}, this.getViewport()), viewport);
const { x, y } = this.mousePoint;
const [lng, lat] = (0, WebViewport_1.getMapViewport)(vp).unproject([x, y]);
const vpp = (0, WebViewport_1.getMapViewport)(Object.assign(Object.assign({}, vp), { zoom: newViewport.zoom }), this.projection);
const [centerLng, centerLat] = vpp.getMapCenterByLngLatPosition({
lngLat: [lng, lat],
pos: [x, y],
});
newViewport.lng = centerLng;
newViewport.lat = centerLat;
if (newViewport.zoom === targetViewport.zoom) {
this.viewport.lng = centerLng;
this.viewport.lat = centerLat;
}
}
newViewport.lng = (0, private_1.toFixedNumber)((0, private_1.alignNumber)(newViewport.lng, targetViewport.lng, 0.000001), 6);
newViewport.lat = (0, private_1.toFixedNumber)((0, private_1.alignNumber)(newViewport.lat, targetViewport.lat, 0.000001), 6);
newViewport.rotate = (0, private_1.toFixedNumber)((0, private_1.alignNumber)(newViewport.rotate, targetViewport.rotate, 0.1), 2);
newViewport.pitch = (0, private_1.toFixedNumber)((0, private_1.alignNumber)(newViewport.pitch, targetViewport.pitch, 0.1), 2);
newViewport = this.getLimitViewport(newViewport, this.limit);
this.realViewport = newViewport;
// if (this.animTriggerTimer) {
// clearTimeout(this.animTriggerTimer);
// }
// this.animTriggerTimer = setTimeout(() => {
// this.animTriggerTimer = 0;
if (viewport.zoom !== newViewport.zoom) {
this.emit('zoom', newViewport.zoom);
}
if (viewport.rotate !== newViewport.rotate) {
this.emit('rotate', newViewport.rotate);
}
if (viewport.pitch !== newViewport.pitch) {
this.emit('pitch', newViewport.pitch);
}
this.emit('viewport-change', newViewport);
this.triggerViewportChangePause(newViewport);
// });
}
controllable(e, type) {
const { disableMove, disableZoom, disableRotate } = this;
if (disableMove && type === 'move')
return false;
if (disableZoom && type === 'zoom')
return false;
if (disableRotate && type === 'rotate')
return false;
return true;
}
}
exports.default = MapController;