@orca-fe/x-map
Version:
274 lines (273 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const d3_ease_1 = require("d3-ease");
const gl_matrix_1 = require("gl-matrix");
const defs_1 = require("../defs");
const private_1 = require("../utils/private");
const MapController_1 = tslib_1.__importDefault(require("./MapController"));
const transition_1 = tslib_1.__importDefault(require("../utils/transition"));
const AMapInstance_1 = tslib_1.__importDefault(require("../instance/AMapInstance"));
const bezier_1 = require("../utils/bezier");
const coord_1 = require("../utils/coord");
const viewport_1 = require("../utils/viewport");
const commonPanFn = d3_ease_1.easeCubicInOut;
class Map {
constructor(id, options) {
this.layers = new Set();
this.destroy = () => {
this.controller.destroy();
this.layers.forEach((layer) => {
layer.destroy();
});
this.mapInstance.destroy();
this.layersContainer.innerHTML = '';
};
this.updateLayers = (viewport = this.controller.getViewport(true)) => {
this.mapInstance.setViewport(viewport);
requestAnimationFrame(() => {
// traverse all layers
this.layers.forEach((layer) => {
layer.updatePosition();
});
});
};
this.registerLayerRenderer = () => {
if (this.mapInstance instanceof AMapInstance_1.default) {
// 高德地图特殊处理
// 需要根据高德地图的 viewport 动态计算出 three.js 的 viewport
// 所以controller不直接更新 layer,只更新 instance
// 当高德地图视野变化后,再在 高德地图 的特殊事件 camerachange 更新 layer
this.controller.on('viewport-change', (viewport) => {
this.mapInstance.setViewport(viewport);
});
this.mapInstance.on('camerachange', () => {
// this.updateLayers(this.controller.getViewport());
this.layers.forEach((layer) => {
layer.updatePosition();
});
});
}
else {
this.controller.on('viewport-change', (viewport) => {
this.updateLayers(viewport);
});
}
};
this.add = (layer) => {
this.layers.add(layer);
this.layersContainer.appendChild(layer.dom);
layer.setMap(this);
};
this.remove = (layer) => {
this.layers.delete(layer);
if (this.layersContainer.contains(layer.dom)) {
this.layersContainer.removeChild(layer.dom);
}
};
/* IMap implements */
this.lnglatToPixel = (pos) => {
const pixel = this.mapInstance.lnglatToPixel(pos);
return [Math.round(pixel[0]), Math.round(pixel[1])];
};
this.pixelToLnglat = (pixel) => this.mapInstance.pixelToLnglat(pixel);
this.getViewport = () => this.controller.getRealViewportWithProjection();
this.setViewport = (viewport) => {
const vp = this.getViewport();
const newViewport = Object.assign(Object.assign({}, (0, viewport_1.toSimpleViewport)(vp)), viewport);
this.controller.setViewport(newViewport);
this.updateLayers(newViewport);
};
this.panTo = (pos) => {
var _a;
const startViewport = this.controller.getViewport();
const targetViewport = {
lng: pos[0],
lat: pos[1],
};
if ((_a = this.transition) === null || _a === void 0 ? void 0 : _a.running) {
this.transition.cancel();
}
return new Promise((resolve, reject) => {
const t = (0, transition_1.default)({
duration: 1000,
from: startViewport,
to: targetViewport,
easing: commonPanFn,
}).onInterval((_, viewport) => {
if (viewport) {
this.controller.setViewport(viewport);
this.updateLayers(viewport);
}
});
this.transition = t;
const interupt = () => {
this.controller.removeListener('viewport-change', interupt);
if (t.running)
t.stop();
if (this.transition === t) {
this.transition = undefined;
}
};
t.on('finish', () => {
interupt();
resolve();
});
t.on('cancel', () => {
interupt();
});
// 如果在移动期间有移动地图,则中断动画
this.controller.addListener('viewport-change', interupt);
}).then(() => {
// 触发事件
const viewport = this.controller.getViewport();
this.controller.emit('viewport-change', viewport);
this.controller.emit('viewport-change-pause', viewport);
});
};
// 控制地图飞行到任意位置
this.flyTo = (target, duration) => {
var _a;
if ((_a = this.transition) === null || _a === void 0 ? void 0 : _a.running) {
this.transition.cancel();
}
const startViewport = this.controller.getViewport();
const targetViewport = Object.assign(Object.assign({}, startViewport), target);
// 修正旋转角度(使前后差距小于180)
let rotateDiff = targetViewport.rotate - startViewport.rotate;
rotateDiff %= 360;
rotateDiff += 360;
rotateDiff %= 360;
if (rotateDiff > 180)
rotateDiff -= 360;
startViewport.rotate = targetViewport.rotate - rotateDiff;
// 计算两地的 mercator 距离,用于计算飞行中的 zoom
const mercatorStart = (0, coord_1.lonLat2Mercator)([startViewport.lng, startViewport.lat]);
const mercatorTarget = (0, coord_1.lonLat2Mercator)([targetViewport.lng, targetViewport.lat]);
const mercatorDistance = Math.hypot(mercatorStart[0] - mercatorTarget[0], mercatorStart[1] - mercatorTarget[1]);
// 飞行中 zoom 不得大于起始、结束 zoom
const flyZoom = Math.min(startViewport.zoom, targetViewport.zoom, 2 + Math.log2(coord_1.MercatorMax / mercatorDistance));
let _duration = duration;
if (_duration == null) {
// 自动设置飞行时长(根据缩放级别的差异,级别差异越大,飞行时间越长)
_duration = 1000 + Math.min(6, Math.max(startViewport.zoom, targetViewport.zoom) - flyZoom) * 300;
}
return new Promise((resolve, reject) => {
// 专用的地图缩放函数(缓动效果)
const zoomBezier = (0, bezier_1.bezierMapZoom)(startViewport.zoom, flyZoom, targetViewport.zoom);
const t = (0, transition_1.default)({
duration: _duration || 1000,
from: startViewport,
to: targetViewport,
// easing: commonPanFn,
easing: (0, bezier_1.bezierMapPan)(),
}).onInterval((_, viewport, linearRate) => {
const zoom = Number(zoomBezier(linearRate).toFixed(6));
const newViewport = Object.assign(Object.assign({}, viewport), { zoom });
this.controller.setViewport(newViewport);
this.updateLayers(newViewport);
});
this.transition = t;
const interupt = () => {
this.controller.removeListener('viewport-change', interupt);
if (t.running)
t.stop();
if (this.transition === t) {
this.transition = undefined;
}
};
t.on('finish', () => {
interupt();
resolve();
});
t.on('cancel', () => {
interupt();
});
// 如果在移动期间有移动地图,则中断动画
this.controller.addListener('viewport-change', interupt);
}).then(() => {
// 触发事件
const viewport = this.controller.getViewport();
this.controller.emit('viewport-change', viewport);
this.controller.emit('viewport-change-pause', viewport);
});
};
this.getThreeMatrix = (classical = false) => {
const matrix = this.mapInstance.getMatrix();
// 中心平移矩阵
const translateMatrix = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.translate(translateMatrix, defs_1.matrixWebMercator, gl_matrix_1.vec3.fromValues(this.threeCenter[0], this.threeCenter[1], 0));
if (matrix && !classical) {
const [projectionMatrix, viewMatrix] = matrix;
const vmat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.translate(vmat, viewMatrix, gl_matrix_1.vec3.fromValues(this.threeCenter[0], this.threeCenter[1], 0));
return [projectionMatrix, vmat];
}
// 默认情况 使用WebMercatorViewport 获取矩阵
const vp = this.getViewport();
const mat = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.multiply(mat, [...vp.projectionMatrix], translateMatrix);
const mat1 = gl_matrix_1.mat4.create();
gl_matrix_1.mat4.multiply(mat1, [...vp.viewMatrix], translateMatrix);
// console.log(mat1);
return [vp.projectionMatrix, mat1];
// return [mat, mat1] as [Mat4, Mat4];
};
this.on = (...args) => {
this.controller.on(...args);
return this;
};
this.off = (...args) => {
this.controller.off(...args);
return this;
};
const dom = (0, private_1.getDom)(id);
if (!dom) {
throw new Error('Map container not exists.');
}
// instance
const { mapInstance, defaultViewport, threeCenter = [0, 0] } = options, controllerOptions = tslib_1.__rest(options, ["mapInstance", "defaultViewport", "threeCenter"]);
this.threeCenter = (0, coord_1.lonLat2Mercator)(threeCenter);
dom.style.position = 'relative';
dom.style.minHeight = '40px';
dom.style.overflow = 'hidden';
const controller = new MapController_1.default(dom, Object.assign(Object.assign({}, controllerOptions), { viewport: defaultViewport }));
const mapDiv = document.createElement('div');
mapDiv.style.width = '100%';
mapDiv.style.height = '100%';
mapDiv.style.pointerEvents = 'none';
dom.appendChild(mapDiv);
mapInstance.init(mapDiv, {
viewport: controller.getViewport(),
});
this.layersContainer = dom;
this.controller = controller;
this.mapInstance = mapInstance;
this.registerLayerRenderer();
}
getSize() {
return this.controller.domSize;
}
getBounds() {
const wmvp = this.getViewport();
return wmvp.getBounds();
}
setBounds(bounds, options) {
const vp = this.fromBounds(bounds, options);
this.setViewport(vp);
}
fromBounds(bounds, options) {
const wmvp = this.getViewport();
const targetViewport = wmvp.fitBounds(bounds, options);
const { zoom, center } = targetViewport;
const [lng, lat] = targetViewport.unprojectFlat(center);
return {
rotate: wmvp.bearing,
pitch: wmvp.pitch,
lng,
lat,
zoom,
};
}
}
exports.default = Map;