UNPKG

@orca-fe/x-map

Version:
271 lines (270 loc) 12.2 kB
import { __rest } from "tslib"; import { easeCubicInOut } from 'd3-ease'; import { mat4, vec3 } from 'gl-matrix'; import { matrixWebMercator } from '../defs'; import { getDom } from '../utils/private'; import MapController from './MapController'; import transition from '../utils/transition'; import AMapInstance from '../instance/AMapInstance'; import { bezierMapPan, bezierMapZoom } from '../utils/bezier'; import { lonLat2Mercator, MercatorMax } from '../utils/coord'; import { toSimpleViewport } from '../utils/viewport'; const commonPanFn = easeCubicInOut; export default 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) { // 高德地图特殊处理 // 需要根据高德地图的 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({}, 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 = transition({ 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 = lonLat2Mercator([startViewport.lng, startViewport.lat]); const mercatorTarget = 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(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 = bezierMapZoom(startViewport.zoom, flyZoom, targetViewport.zoom); const t = transition({ duration: _duration || 1000, from: startViewport, to: targetViewport, // easing: commonPanFn, easing: 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 = mat4.create(); mat4.translate(translateMatrix, matrixWebMercator, vec3.fromValues(this.threeCenter[0], this.threeCenter[1], 0)); if (matrix && !classical) { const [projectionMatrix, viewMatrix] = matrix; const vmat = mat4.create(); mat4.translate(vmat, viewMatrix, vec3.fromValues(this.threeCenter[0], this.threeCenter[1], 0)); return [projectionMatrix, vmat]; } // 默认情况 使用WebMercatorViewport 获取矩阵 const vp = this.getViewport(); const mat = mat4.create(); mat4.multiply(mat, [...vp.projectionMatrix], translateMatrix); const mat1 = mat4.create(); 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 = getDom(id); if (!dom) { throw new Error('Map container not exists.'); } // instance const { mapInstance, defaultViewport, threeCenter = [0, 0] } = options, controllerOptions = __rest(options, ["mapInstance", "defaultViewport", "threeCenter"]); this.threeCenter = lonLat2Mercator(threeCenter); dom.style.position = 'relative'; dom.style.minHeight = '40px'; dom.style.overflow = 'hidden'; const controller = new MapController(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, }; } }