mirror of https://gitee.com/antv-l7/antv-l7
feat: 合并代码
This commit is contained in:
parent
f2c501ef0d
commit
6c883689f8
|
@ -0,0 +1,366 @@
|
|||
/**
|
||||
* MapboxService
|
||||
*/
|
||||
import {
|
||||
Bounds,
|
||||
CoordinateSystem,
|
||||
ICoordinateSystemService,
|
||||
IGlobalConfigService,
|
||||
ILngLat,
|
||||
IMapCamera,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IMercator,
|
||||
IPoint,
|
||||
IStatusOptions,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
MapStyle,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { Map } from '@antv/l7-map';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import 'reflect-metadata';
|
||||
import { ISimpleMapCoord, SimpleMapCoord } from './simpleMapCoord';
|
||||
import { Version } from '../version';
|
||||
const EventMap: {
|
||||
[key: string]: any;
|
||||
} = {
|
||||
mapmove: 'move',
|
||||
camerachange: 'move',
|
||||
zoomchange: 'zoom',
|
||||
dragging: 'drag',
|
||||
};
|
||||
import { MapTheme } from './theme';
|
||||
|
||||
const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12;
|
||||
/**
|
||||
* AMapService
|
||||
*/
|
||||
@injectable()
|
||||
export default abstract class BaseMapService<T>
|
||||
implements IMapService<Map & T> {
|
||||
public version: string = Version.L7MAP;
|
||||
public map: Map & T;
|
||||
protected viewport: IViewport | unknown;
|
||||
public simpleMapCoord: ISimpleMapCoord = new SimpleMapCoord();
|
||||
// 背景色
|
||||
public bgColor: string = 'rgba(0.0, 0.0, 0.0, 0.0)';
|
||||
|
||||
@inject(TYPES.MapConfig)
|
||||
protected readonly config: Partial<IMapConfig>;
|
||||
|
||||
@inject(TYPES.IGlobalConfigService)
|
||||
protected readonly configService: IGlobalConfigService;
|
||||
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
protected readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IEventEmitter)
|
||||
protected eventEmitter: any;
|
||||
|
||||
protected markerContainer: HTMLElement;
|
||||
protected cameraChangedCallback: (viewport: IViewport) => void;
|
||||
protected $mapContainer: HTMLElement | null;
|
||||
public setBgColor(color: string) {
|
||||
this.bgColor = color;
|
||||
}
|
||||
|
||||
// init
|
||||
public addMarkerContainer(): void {
|
||||
const container = this.map.getCanvasContainer();
|
||||
this.markerContainer = DOM.create('div', 'l7-marker-container', container);
|
||||
this.markerContainer.setAttribute('tabindex', '-1');
|
||||
}
|
||||
|
||||
public getMarkerContainer(): HTMLElement {
|
||||
return this.markerContainer;
|
||||
}
|
||||
public getOverlayContainer(): HTMLElement | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// map event
|
||||
public on(type: string, handle: (...args: any[]) => void): void {
|
||||
if (MapServiceEvent.indexOf(type) !== -1) {
|
||||
this.eventEmitter.on(type, handle);
|
||||
} else {
|
||||
// 统一事件名称
|
||||
this.map.on(EventMap[type] || type, handle);
|
||||
}
|
||||
}
|
||||
public off(type: string, handle: (...args: any[]) => void): void {
|
||||
this.map.off(EventMap[type] || type, handle);
|
||||
this.eventEmitter.off(type, handle);
|
||||
}
|
||||
|
||||
public getContainer(): HTMLElement | null {
|
||||
return this.map.getContainer();
|
||||
}
|
||||
|
||||
public getMapCanvasContainer(): HTMLElement {
|
||||
return this.map.getCanvasContainer() as HTMLElement;
|
||||
}
|
||||
|
||||
public getSize(): [number, number] {
|
||||
if (this.version === Version.SIMPLE) {
|
||||
return this.simpleMapCoord.getSize();
|
||||
}
|
||||
const size = this.map.transform;
|
||||
|
||||
return [size.width, size.height];
|
||||
}
|
||||
// get mapStatus method
|
||||
|
||||
public getType() {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
public getZoom(): number {
|
||||
return this.map.getZoom();
|
||||
}
|
||||
|
||||
public setZoom(zoom: number) {
|
||||
return this.map.setZoom(zoom);
|
||||
}
|
||||
|
||||
public getCenter(): ILngLat {
|
||||
return this.map.getCenter();
|
||||
}
|
||||
|
||||
public setCenter(lnglat: [number, number]): void {
|
||||
this.map.setCenter(lnglat);
|
||||
}
|
||||
|
||||
public getPitch(): number {
|
||||
return this.map.getPitch();
|
||||
}
|
||||
|
||||
public getRotation(): number {
|
||||
return this.map.getBearing();
|
||||
}
|
||||
|
||||
public getBounds(): Bounds {
|
||||
return this.map.getBounds().toArray() as Bounds;
|
||||
}
|
||||
|
||||
public getMinZoom(): number {
|
||||
return this.map.getMinZoom();
|
||||
}
|
||||
|
||||
public getMaxZoom(): number {
|
||||
return this.map.getMaxZoom();
|
||||
}
|
||||
|
||||
public setRotation(rotation: number): void {
|
||||
this.map.setBearing(rotation);
|
||||
}
|
||||
|
||||
public zoomIn(option?: any, eventData?: any): void {
|
||||
this.map.zoomIn(option, eventData);
|
||||
}
|
||||
public zoomOut(option?: any, eventData?: any): void {
|
||||
this.map.zoomOut(option, eventData);
|
||||
}
|
||||
public setPitch(pitch: number) {
|
||||
return this.map.setPitch(pitch);
|
||||
}
|
||||
|
||||
public panTo(p: [number, number]): void {
|
||||
this.map.panTo(p);
|
||||
}
|
||||
|
||||
public panBy(x: number = 0, y: number = 0): void {
|
||||
this.panTo([x, y]);
|
||||
}
|
||||
|
||||
public fitBounds(bound: Bounds, fitBoundsOptions?: any): void {
|
||||
this.map.fitBounds(bound, fitBoundsOptions);
|
||||
}
|
||||
|
||||
public setMaxZoom(max: number): void {
|
||||
this.map.setMaxZoom(max);
|
||||
}
|
||||
|
||||
public setMinZoom(min: number): void {
|
||||
this.map.setMinZoom(min);
|
||||
}
|
||||
public setMapStatus(option: Partial<IStatusOptions>): void {
|
||||
if (option.doubleClickZoom === true) {
|
||||
this.map.doubleClickZoom.enable();
|
||||
}
|
||||
if (option.doubleClickZoom === false) {
|
||||
this.map.doubleClickZoom.disable();
|
||||
}
|
||||
if (option.dragEnable === false) {
|
||||
this.map.dragPan.disable();
|
||||
}
|
||||
if (option.dragEnable === true) {
|
||||
this.map.dragPan.enable();
|
||||
}
|
||||
if (option.rotateEnable === false) {
|
||||
this.map.dragRotate.disable();
|
||||
}
|
||||
if (option.dragEnable === true) {
|
||||
this.map.dragRotate.enable();
|
||||
}
|
||||
if (option.keyboardEnable === false) {
|
||||
this.map.keyboard.disable();
|
||||
}
|
||||
if (option.keyboardEnable === true) {
|
||||
this.map.keyboard.enable();
|
||||
}
|
||||
if (option.zoomEnable === false) {
|
||||
this.map.scrollZoom.disable();
|
||||
}
|
||||
if (option.zoomEnable === true) {
|
||||
this.map.scrollZoom.enable();
|
||||
}
|
||||
}
|
||||
|
||||
public setZoomAndCenter(zoom: number, center: [number, number]): void {
|
||||
this.map.flyTo({
|
||||
zoom,
|
||||
center,
|
||||
});
|
||||
}
|
||||
|
||||
public setMapStyle(style: any): void {
|
||||
this.map.setStyle(this.getMapStyle(style));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public meterToCoord(center: [number, number], outer: [number, number]) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// TODO: 计算像素坐标
|
||||
public pixelToLngLat(pixel: [number, number]): ILngLat {
|
||||
return this.map.unproject(pixel);
|
||||
}
|
||||
|
||||
public lngLatToPixel(lnglat: [number, number]): IPoint {
|
||||
return this.map.project(lnglat);
|
||||
}
|
||||
|
||||
public containerToLngLat(pixel: [number, number]): ILngLat {
|
||||
return this.map.unproject(pixel);
|
||||
}
|
||||
|
||||
public lngLatToContainer(lnglat: [number, number]): IPoint {
|
||||
return this.map.project(lnglat);
|
||||
}
|
||||
public abstract lngLatToMercator(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
): IMercator;
|
||||
|
||||
public abstract getModelMatrix(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
rotate: [number, number, number],
|
||||
scale: [number, number, number],
|
||||
origin: IMercator,
|
||||
): number[];
|
||||
|
||||
public abstract init(): Promise<void>;
|
||||
|
||||
public destroy() {
|
||||
this.eventEmitter.removeAllListeners();
|
||||
if (this.map) {
|
||||
this.map.remove();
|
||||
this.$mapContainer = null;
|
||||
}
|
||||
}
|
||||
public emit(name: string, ...args: any[]) {
|
||||
this.eventEmitter.emit(name, ...args);
|
||||
}
|
||||
public once(name: string, ...args: any[]) {
|
||||
this.eventEmitter.once(name, ...args);
|
||||
}
|
||||
|
||||
public getMapContainer() {
|
||||
return this.$mapContainer;
|
||||
}
|
||||
|
||||
public exportMap(type: 'jpg' | 'png'): string {
|
||||
const renderCanvas = this.map.getCanvas();
|
||||
const layersPng =
|
||||
type === 'jpg'
|
||||
? (renderCanvas?.toDataURL('image/jpeg') as string)
|
||||
: (renderCanvas?.toDataURL('image/png') as string);
|
||||
return layersPng;
|
||||
}
|
||||
public onCameraChanged(callback: (viewport: IViewport) => void): void {
|
||||
this.cameraChangedCallback = callback;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected handleCameraChanged = (e?: any) => {
|
||||
const { lat, lng } = this.map.getCenter();
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
// resync
|
||||
(this.viewport as IViewport).syncWithMapCamera({
|
||||
bearing: this.map.getBearing(),
|
||||
center: [lng, lat],
|
||||
viewportHeight: this.map.transform.height,
|
||||
pitch: this.map.getPitch(),
|
||||
viewportWidth: this.map.transform.width,
|
||||
zoom: this.map.getZoom(),
|
||||
// mapbox 中固定相机高度为 viewport 高度的 1.5 倍
|
||||
cameraHeight: 0,
|
||||
});
|
||||
|
||||
this.updateCoordinateSystemService();
|
||||
this.cameraChangedCallback(this.viewport as IViewport);
|
||||
};
|
||||
|
||||
protected creatMapContainer(id: string | HTMLDivElement) {
|
||||
let $wrapper = id as HTMLDivElement;
|
||||
if (typeof id === 'string') {
|
||||
$wrapper = document.getElementById(id) as HTMLDivElement;
|
||||
}
|
||||
return $wrapper;
|
||||
}
|
||||
public updateView(viewOption: Partial<IMapCamera>) {
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
// resync
|
||||
(this.viewport as IViewport).syncWithMapCamera({
|
||||
bearing: viewOption.bearing,
|
||||
center: viewOption.center,
|
||||
viewportHeight: viewOption.viewportHeight,
|
||||
pitch: viewOption.pitch,
|
||||
viewportWidth: viewOption.viewportWidth,
|
||||
zoom: viewOption.zoom,
|
||||
// mapbox 中固定相机高度为 viewport 高度的 1.5 倍
|
||||
cameraHeight: 0,
|
||||
});
|
||||
this.updateCoordinateSystemService();
|
||||
this.cameraChangedCallback(this.viewport as IViewport);
|
||||
}
|
||||
|
||||
protected updateCoordinateSystemService() {
|
||||
const { offsetCoordinate = true } = this.config;
|
||||
// set coordinate system
|
||||
if (
|
||||
(this.viewport as IViewport).getZoom() > LNGLAT_OFFSET_ZOOM_THRESHOLD &&
|
||||
offsetCoordinate
|
||||
) {
|
||||
this.coordinateSystemService.setCoordinateSystem(
|
||||
CoordinateSystem.LNGLAT_OFFSET,
|
||||
);
|
||||
} else {
|
||||
this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.LNGLAT);
|
||||
}
|
||||
}
|
||||
|
||||
protected getMapStyle(name: MapStyle) {
|
||||
if (typeof name !== 'string') {
|
||||
return name;
|
||||
}
|
||||
return MapTheme[name] ? MapTheme[name] : name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,559 @@
|
|||
/**
|
||||
* AMapService
|
||||
*/
|
||||
import AMapLoader from '@amap/amap-jsapi-loader';
|
||||
import {
|
||||
Bounds,
|
||||
CoordinateSystem,
|
||||
ICameraOptions,
|
||||
ICoordinateSystemService,
|
||||
IGlobalConfigService,
|
||||
ILngLat,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IMercator,
|
||||
IPoint,
|
||||
IStatusOptions,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
TYPES,
|
||||
IMapCamera,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { mat4, vec3 } from 'gl-matrix';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import 'reflect-metadata';
|
||||
import { IAMapEvent, IAMapInstance } from '../../../typings/index';
|
||||
import { ISimpleMapCoord, SimpleMapCoord } from '../simpleMapCoord';
|
||||
import { toPaddingOptions } from '../utils';
|
||||
import { Version } from '../../version';
|
||||
import Viewport from '../Viewport';
|
||||
import './logo.css';
|
||||
import { MapTheme } from './theme';
|
||||
let mapdivCount = 0;
|
||||
// @ts-ignore
|
||||
window.forceWebGL = true;
|
||||
|
||||
const AMAP_API_KEY: string = '15cd8a57710d40c9b7c0e3cc120f1200';
|
||||
const AMAP_VERSION: string = '1.4.15';
|
||||
/**
|
||||
* 确保多个场景只引入一个高德地图脚本
|
||||
*/
|
||||
const AMAP_SCRIPT_ID: string = 'amap-script';
|
||||
/**
|
||||
* 高德地图脚本是否加载完毕
|
||||
*/
|
||||
let amapLoaded = false;
|
||||
/**
|
||||
* 高德地图脚本加载成功等待队列,成功之后依次触发
|
||||
*/
|
||||
let pendingResolveQueue: Array<() => void> = [];
|
||||
const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12; // 暂时关闭 fix 统一不同坐标系,不同底图的高度位置
|
||||
|
||||
/**
|
||||
* AMapService
|
||||
*/
|
||||
@injectable()
|
||||
export default class AMapBaseService
|
||||
implements IMapService<AMap.Map & IAMapInstance> {
|
||||
public version: string = Version['GAODE1.x'];
|
||||
public simpleMapCoord: ISimpleMapCoord = new SimpleMapCoord();
|
||||
/**
|
||||
* 原始地图实例
|
||||
*/
|
||||
public map: AMap.Map & IAMapInstance;
|
||||
|
||||
// 背景色
|
||||
public bgColor: string = 'rgba(0, 0, 0, 0)';
|
||||
|
||||
@inject(TYPES.IGlobalConfigService)
|
||||
protected readonly configService: IGlobalConfigService;
|
||||
|
||||
@inject(TYPES.MapConfig)
|
||||
protected readonly config: Partial<IMapConfig>;
|
||||
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
protected readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IEventEmitter)
|
||||
protected eventEmitter: any;
|
||||
|
||||
protected markerContainer: HTMLElement;
|
||||
protected $mapContainer: HTMLElement | null;
|
||||
|
||||
protected viewport: IViewport;
|
||||
|
||||
protected cameraChangedCallback: (viewport: IViewport) => void;
|
||||
public setBgColor(color: string) {
|
||||
this.bgColor = color;
|
||||
}
|
||||
|
||||
public addMarkerContainer(): void {
|
||||
const mapContainer = this.map.getContainer();
|
||||
if (mapContainer !== null) {
|
||||
const amap = mapContainer.getElementsByClassName(
|
||||
'amap-maps',
|
||||
)[0] as HTMLElement;
|
||||
this.markerContainer = DOM.create('div', 'l7-marker-container', amap);
|
||||
}
|
||||
}
|
||||
public getMarkerContainer(): HTMLElement {
|
||||
return this.markerContainer;
|
||||
}
|
||||
|
||||
// map event
|
||||
public on(type: string, handler: (...args: any[]) => void): void {
|
||||
if (MapServiceEvent.indexOf(type) !== -1) {
|
||||
this.eventEmitter.on(type, handler);
|
||||
} else {
|
||||
this.map.on(type, handler);
|
||||
}
|
||||
}
|
||||
public off(type: string, handler: (...args: any[]) => void): void {
|
||||
if (MapServiceEvent.indexOf(type) !== -1) {
|
||||
this.eventEmitter.off(type, handler);
|
||||
} else {
|
||||
this.map.off(type, handler);
|
||||
}
|
||||
}
|
||||
|
||||
public getContainer(): HTMLElement | null {
|
||||
return this.map.getContainer();
|
||||
}
|
||||
|
||||
public getMapCanvasContainer(): HTMLElement {
|
||||
return this.map
|
||||
.getContainer()
|
||||
?.getElementsByClassName('amap-maps')[0] as HTMLElement;
|
||||
}
|
||||
|
||||
public getSize(): [number, number] {
|
||||
const size = this.map.getSize();
|
||||
return [size.getWidth(), size.getHeight()];
|
||||
}
|
||||
|
||||
public getType() {
|
||||
return 'amap';
|
||||
}
|
||||
public getZoom(): number {
|
||||
// 统一返回 Mapbox 缩放等级
|
||||
return this.map.getZoom() - 1;
|
||||
}
|
||||
|
||||
public setZoom(zoom: number): void {
|
||||
// 统一设置 Mapbox 缩放等级
|
||||
return this.map.setZoom(zoom + 1);
|
||||
}
|
||||
|
||||
public getCenter(options?: ICameraOptions): ILngLat {
|
||||
if (options?.padding) {
|
||||
const originCenter = this.getCenter();
|
||||
const [w, h] = this.getSize();
|
||||
const padding = toPaddingOptions(options.padding);
|
||||
const px = this.lngLatToPixel([originCenter.lng, originCenter.lat]);
|
||||
const offsetPx = [
|
||||
(padding.right - padding.left) / 2,
|
||||
(padding.bottom - padding.top) / 2,
|
||||
];
|
||||
|
||||
const newCenter = this.pixelToLngLat([
|
||||
px.x - offsetPx[0],
|
||||
px.y - offsetPx[1],
|
||||
]);
|
||||
return newCenter;
|
||||
}
|
||||
const center = this.map.getCenter();
|
||||
return {
|
||||
lng: center.getLng(),
|
||||
lat: center.getLat(),
|
||||
};
|
||||
}
|
||||
public setCenter(lnglat: [number, number], options?: ICameraOptions): void {
|
||||
if (options?.padding) {
|
||||
const padding = toPaddingOptions(options.padding);
|
||||
const px = this.lngLatToPixel(lnglat);
|
||||
const offsetPx = [
|
||||
(padding.right - padding.left) / 2,
|
||||
(padding.bottom - padding.top) / 2,
|
||||
];
|
||||
const newCenter = this.pixelToLngLat([
|
||||
px.x + offsetPx[0],
|
||||
px.y + offsetPx[1],
|
||||
]);
|
||||
this.map.setCenter([newCenter.lng, newCenter.lat]);
|
||||
} else {
|
||||
this.map.setCenter(lnglat);
|
||||
}
|
||||
}
|
||||
public getPitch(): number {
|
||||
return this.map.getPitch();
|
||||
}
|
||||
|
||||
public getRotation(): number {
|
||||
// 统一返回逆时针旋转角度
|
||||
return 360 - this.map.getRotation();
|
||||
}
|
||||
|
||||
public getBounds(): Bounds {
|
||||
// @ts-ignore
|
||||
const amapBound = this.map.getBounds().toBounds();
|
||||
const NE = amapBound.getNorthEast();
|
||||
const SW = amapBound.getSouthWest();
|
||||
const center = this.getCenter();
|
||||
const maxlng =
|
||||
center.lng > NE.getLng() || center.lng < SW.getLng()
|
||||
? 180 - NE.getLng()
|
||||
: NE.getLng();
|
||||
const minlng = center.lng < SW.getLng() ? SW.getLng() - 180 : SW.getLng();
|
||||
// 兼容 Mapbox,统一返回西南、东北
|
||||
return [
|
||||
[minlng, SW.getLat()],
|
||||
[maxlng, NE.getLat()],
|
||||
];
|
||||
}
|
||||
|
||||
public getMinZoom(): number {
|
||||
const zooms = this.map.get('zooms') as [number, number];
|
||||
return zooms[0] - 1;
|
||||
}
|
||||
public getMaxZoom(): number {
|
||||
const zooms = this.map.get('zooms') as [number, number];
|
||||
return zooms[1] - 1;
|
||||
}
|
||||
public setRotation(rotation: number): void {
|
||||
return this.map.setRotation(rotation);
|
||||
}
|
||||
|
||||
public setPitch(pitch: number) {
|
||||
return this.map.setPitch(pitch);
|
||||
}
|
||||
public zoomIn(): void {
|
||||
this.map.zoomIn();
|
||||
}
|
||||
|
||||
public zoomOut(): void {
|
||||
this.map.zoomOut();
|
||||
}
|
||||
|
||||
public panTo(p: [number, number]): void {
|
||||
this.map.panTo(p);
|
||||
}
|
||||
|
||||
public panBy(x: number = 0, y: number = 0): void {
|
||||
this.map.panBy(x, y);
|
||||
}
|
||||
|
||||
public fitBounds(extent: Bounds): void {
|
||||
this.map.setBounds(
|
||||
new AMap.Bounds([extent[0][0], extent[0][1], extent[1][0], extent[1][1]]),
|
||||
);
|
||||
}
|
||||
|
||||
public setZoomAndCenter(zoom: number, center: [number, number]): void {
|
||||
this.map.setZoomAndCenter(zoom + 1, center);
|
||||
}
|
||||
|
||||
public setMapStyle(style: string): void {
|
||||
this.map.setMapStyle(this.getMapStyle(style));
|
||||
}
|
||||
|
||||
public setMapStatus(option: Partial<IStatusOptions>): void {
|
||||
this.map.setStatus(option);
|
||||
}
|
||||
public pixelToLngLat(pixel: [number, number]): ILngLat {
|
||||
const lngLat = this.map.pixelToLngLat(new AMap.Pixel(pixel[0], pixel[1]));
|
||||
return { lng: lngLat.getLng(), lat: lngLat.getLat() };
|
||||
}
|
||||
public lngLatToPixel(lnglat: [number, number]): IPoint {
|
||||
const p = this.map.lnglatToPixel(new AMap.LngLat(lnglat[0], lnglat[1]));
|
||||
return {
|
||||
x: p.getX(),
|
||||
y: p.getY(),
|
||||
};
|
||||
}
|
||||
public containerToLngLat(pixel: [number, number]): ILngLat {
|
||||
const ll = new AMap.Pixel(pixel[0], pixel[1]);
|
||||
const lngLat = this.map.containerToLngLat(ll);
|
||||
return {
|
||||
lng: lngLat?.getLng(),
|
||||
lat: lngLat?.getLat(),
|
||||
};
|
||||
}
|
||||
public lngLatToContainer(lnglat: [number, number]): IPoint {
|
||||
const ll = new AMap.LngLat(lnglat[0], lnglat[1]);
|
||||
const pixel = this.map.lngLatToContainer(ll);
|
||||
return {
|
||||
x: pixel.getX(),
|
||||
y: pixel.getY(),
|
||||
};
|
||||
}
|
||||
|
||||
public lngLatToCoord(lnglat: [number, number]): any {
|
||||
// @ts-ignore
|
||||
const { x, y } = this.map.lngLatToGeodeticCoord(lnglat);
|
||||
return [x, -y];
|
||||
}
|
||||
|
||||
public lngLatToMercator(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
): IMercator {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
};
|
||||
}
|
||||
|
||||
public getModelMatrix(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
rotate: [number, number, number],
|
||||
scale: [number, number, number] = [1, 1, 1],
|
||||
|
||||
): number[] {
|
||||
const flat = this.viewport.projectFlat(lnglat);
|
||||
// @ts-ignore
|
||||
const modelMatrix = mat4.create();
|
||||
|
||||
mat4.translate(
|
||||
modelMatrix,
|
||||
modelMatrix,
|
||||
vec3.fromValues(flat[0], flat[1], altitude),
|
||||
);
|
||||
mat4.scale(
|
||||
modelMatrix,
|
||||
modelMatrix,
|
||||
vec3.fromValues(scale[0], scale[1], scale[2]),
|
||||
);
|
||||
|
||||
mat4.rotateX(modelMatrix, modelMatrix, rotate[0]);
|
||||
mat4.rotateY(modelMatrix, modelMatrix, rotate[1]);
|
||||
mat4.rotateZ(modelMatrix, modelMatrix, rotate[2]);
|
||||
|
||||
return (modelMatrix as unknown) as number[];
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
const {
|
||||
id,
|
||||
style = 'light',
|
||||
minZoom = 0,
|
||||
maxZoom = 18,
|
||||
token = AMAP_API_KEY,
|
||||
mapInstance,
|
||||
plugin = [],
|
||||
...rest
|
||||
} = this.config;
|
||||
// 高德地图创建独立的container;
|
||||
// tslint:disable-next-line:typedef
|
||||
await new Promise<void>((resolve) => {
|
||||
const resolveMap = () => {
|
||||
if (mapInstance) {
|
||||
this.map = mapInstance as AMap.Map & IAMapInstance;
|
||||
this.$mapContainer = this.map.getContainer();
|
||||
setTimeout(() => {
|
||||
this.map.on('camerachange', this.handleCameraChanged);
|
||||
resolve();
|
||||
}, 30);
|
||||
} else {
|
||||
this.$mapContainer = this.creatMapContainer(
|
||||
id as string | HTMLDivElement,
|
||||
);
|
||||
const mapConstructorOptions = {
|
||||
mapStyle: this.getMapStyle(style as string),
|
||||
zooms: [minZoom, maxZoom],
|
||||
viewMode: '3D',
|
||||
...rest,
|
||||
};
|
||||
if (mapConstructorOptions.zoom) {
|
||||
// TODO: 高德地图在相同大小下需要比 MapBox 多一个 zoom 层级
|
||||
mapConstructorOptions.zoom += 1;
|
||||
}
|
||||
// @ts-ignore
|
||||
const map = new AMap.Map(this.$mapContainer, mapConstructorOptions);
|
||||
// 监听地图相机事件
|
||||
map.on('camerachange', this.handleCameraChanged);
|
||||
// Tip: 为了兼容开启 MultiPassRender 的情况
|
||||
// 修复 MultiPassRender 在高德地图 1.x 的情况下,缩放地图改变 zoom 时存在可视化层和底图不同步的现象
|
||||
map.on('camerachange', () => {
|
||||
setTimeout(() => this.handleAfterMapChange());
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.map = map;
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
if (!amapLoaded && !mapInstance) {
|
||||
if (token === AMAP_API_KEY) {
|
||||
console.warn(this.configService.getSceneWarninfo('MapToken'));
|
||||
}
|
||||
amapLoaded = true;
|
||||
plugin.push('Map3D');
|
||||
AMapLoader.load({
|
||||
key: token, // 申请好的Web端开发者Key,首次调用 load 时必填
|
||||
version: AMAP_VERSION, // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
|
||||
plugins: plugin, // 需要使用的的插件列表,如比例尺'AMap.Scale'等
|
||||
})
|
||||
.then((AMap) => {
|
||||
resolveMap();
|
||||
|
||||
if (pendingResolveQueue.length) {
|
||||
pendingResolveQueue.forEach((r) => r());
|
||||
pendingResolveQueue = [];
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
} else {
|
||||
if ((amapLoaded && window.AMap) || mapInstance) {
|
||||
resolveMap();
|
||||
} else {
|
||||
pendingResolveQueue.push(resolveMap);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewport = new Viewport();
|
||||
}
|
||||
|
||||
public meterToCoord(center: [number, number], outer: [number, number]) {
|
||||
// 统一根据经纬度来转化
|
||||
// Tip: 实际米距离 unit meter
|
||||
const meterDis = AMap.GeometryUtil.distance(
|
||||
new AMap.LngLat(...center),
|
||||
new AMap.LngLat(...outer),
|
||||
);
|
||||
|
||||
// Tip: 三维世界坐标距离
|
||||
const [x1, y1] = this.lngLatToCoord(center);
|
||||
const [x2, y2] = this.lngLatToCoord(outer);
|
||||
const coordDis = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
|
||||
|
||||
return coordDis / meterDis;
|
||||
}
|
||||
|
||||
public updateView(viewOption: Partial<IMapCamera>): void {}
|
||||
public getOverlayContainer(): HTMLElement | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public exportMap(type: 'jpg' | 'png'): string {
|
||||
const renderCanvas = this.getContainer()?.getElementsByClassName(
|
||||
'amap-layer',
|
||||
)[0] as HTMLCanvasElement;
|
||||
const layersPng =
|
||||
type === 'jpg'
|
||||
? (renderCanvas?.toDataURL('image/jpeg') as string)
|
||||
: (renderCanvas?.toDataURL('image/png') as string);
|
||||
return layersPng;
|
||||
}
|
||||
|
||||
public emit(name: string, ...args: any[]) {
|
||||
this.eventEmitter.emit(name, ...args);
|
||||
}
|
||||
|
||||
public once(name: string, ...args: any[]) {
|
||||
this.eventEmitter.once(name, ...args);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.map.destroy();
|
||||
|
||||
// TODO: 销毁地图可视化层的容器
|
||||
this.$mapContainer?.parentNode?.removeChild(this.$mapContainer);
|
||||
|
||||
// @ts-ignore
|
||||
delete window.initAMap;
|
||||
const $jsapi = document.getElementById(AMAP_SCRIPT_ID);
|
||||
if ($jsapi) {
|
||||
document.head.removeChild($jsapi);
|
||||
}
|
||||
}
|
||||
|
||||
public getMapContainer() {
|
||||
return this.$mapContainer;
|
||||
}
|
||||
|
||||
public onCameraChanged(callback: (viewport: IViewport) => void): void {
|
||||
this.cameraChangedCallback = callback;
|
||||
}
|
||||
|
||||
protected handleAfterMapChange() {
|
||||
this.emit('mapAfterFrameChange');
|
||||
}
|
||||
|
||||
protected handleCameraChanged = (e: IAMapEvent): void => {
|
||||
const {
|
||||
fov,
|
||||
near,
|
||||
far,
|
||||
height,
|
||||
pitch,
|
||||
rotation,
|
||||
aspect,
|
||||
position,
|
||||
} = e.camera;
|
||||
const { lng, lat } = this.getCenter();
|
||||
// Tip: 触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
|
||||
if (this.cameraChangedCallback) {
|
||||
// resync viewport
|
||||
// console.log('cameraHeight', height)
|
||||
// console.log('pitch', pitch)
|
||||
// console.log('rotation', rotation)
|
||||
// console.log('zoom', this.map.getZoom())
|
||||
this.viewport.syncWithMapCamera({
|
||||
aspect,
|
||||
// AMap 定义 rotation 为顺时针方向,而 Mapbox 为逆时针
|
||||
// @see https://docs.mapbox.com/mapbox-gl-js/api/#map#getbearing
|
||||
bearing: 360 - rotation,
|
||||
far,
|
||||
fov,
|
||||
cameraHeight: height,
|
||||
near,
|
||||
pitch,
|
||||
// AMap 定义的缩放等级 与 Mapbox 相差 1
|
||||
zoom: this.map.getZoom() - 1,
|
||||
center: [lng, lat],
|
||||
offsetOrigin: [position.x, position.y],
|
||||
});
|
||||
const { offsetZoom = LNGLAT_OFFSET_ZOOM_THRESHOLD } = this.config;
|
||||
// console.log('this.viewport', this.viewport)
|
||||
// set coordinate system
|
||||
if (this.viewport.getZoom() > offsetZoom) {
|
||||
this.coordinateSystemService.setCoordinateSystem(
|
||||
CoordinateSystem.P20_OFFSET,
|
||||
);
|
||||
} else {
|
||||
this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.P20);
|
||||
}
|
||||
this.cameraChangedCallback(this.viewport);
|
||||
}
|
||||
};
|
||||
|
||||
protected getMapStyle(name: string): string {
|
||||
return MapTheme[name] ? MapTheme[name] : name;
|
||||
}
|
||||
protected creatMapContainer(id: string | HTMLDivElement) {
|
||||
let $wrapper = id as HTMLDivElement;
|
||||
if (typeof id === 'string') {
|
||||
$wrapper = document.getElementById(id) as HTMLDivElement;
|
||||
}
|
||||
const $amapdiv = document.createElement('div');
|
||||
$amapdiv.style.cssText += `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
$amapdiv.id = 'l7_amap_div' + mapdivCount++;
|
||||
$wrapper.appendChild($amapdiv);
|
||||
return $amapdiv;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import { IMapCamera, IViewport } from '@antv/l7-core';
|
||||
import { mat4, vec3 } from 'gl-matrix';
|
||||
|
||||
const DEGREES_TO_RADIANS = Math.PI / 180;
|
||||
|
||||
export default class Viewport implements IViewport {
|
||||
private projectionMatrix: mat4 = mat4.create();
|
||||
private viewMatrix: mat4 = mat4.create();
|
||||
private viewProjectionMatrix: mat4 = mat4.create();
|
||||
private ViewProjectionMatrixUncentered: mat4 = mat4.create();
|
||||
private viewUncenteredMatrix: mat4 = mat4.create();
|
||||
private zoom: number;
|
||||
private center: number[];
|
||||
|
||||
public syncWithMapCamera(mapCamera: Partial<IMapCamera>) {
|
||||
const {
|
||||
zoom = 1,
|
||||
pitch = 0,
|
||||
bearing = 0,
|
||||
center = [0, 0],
|
||||
offsetOrigin = [0, 0],
|
||||
cameraHeight = 1,
|
||||
aspect = 1,
|
||||
near = 0.1,
|
||||
far = 1000,
|
||||
fov = 0,
|
||||
} = mapCamera;
|
||||
this.zoom = zoom;
|
||||
this.center = center;
|
||||
|
||||
const pitchInRadians = pitch * DEGREES_TO_RADIANS;
|
||||
const rotationInRadians = (360 - bearing) * DEGREES_TO_RADIANS;
|
||||
|
||||
// 计算透视投影矩阵 projectionMatrix
|
||||
mat4.perspective(this.projectionMatrix, fov, aspect, near, far);
|
||||
// 计算相机矩阵 viewMatrix
|
||||
const eye = vec3.fromValues(
|
||||
cameraHeight * Math.sin(pitchInRadians) * Math.sin(rotationInRadians),
|
||||
-cameraHeight * Math.sin(pitchInRadians) * Math.cos(rotationInRadians),
|
||||
cameraHeight * Math.cos(pitchInRadians),
|
||||
);
|
||||
const up = vec3.fromValues(
|
||||
-Math.cos(pitchInRadians) * Math.sin(rotationInRadians),
|
||||
Math.cos(pitchInRadians) * Math.cos(rotationInRadians),
|
||||
Math.sin(pitchInRadians),
|
||||
);
|
||||
mat4.lookAt(this.viewMatrix, eye, vec3.fromValues(0, 0, 0), up);
|
||||
this.viewUncenteredMatrix = mat4.clone(this.viewMatrix);
|
||||
|
||||
// 移动相机位置
|
||||
mat4.translate(
|
||||
this.viewMatrix,
|
||||
this.viewMatrix,
|
||||
vec3.fromValues(-offsetOrigin[0], offsetOrigin[1], 0),
|
||||
);
|
||||
|
||||
mat4.multiply(
|
||||
this.viewProjectionMatrix,
|
||||
this.projectionMatrix,
|
||||
this.viewMatrix,
|
||||
);
|
||||
mat4.multiply(
|
||||
this.ViewProjectionMatrixUncentered,
|
||||
this.projectionMatrix,
|
||||
this.viewMatrix,
|
||||
);
|
||||
}
|
||||
|
||||
public getZoom(): number {
|
||||
return this.zoom;
|
||||
}
|
||||
|
||||
public getZoomScale(): number {
|
||||
// 512 尺寸下的缩放:2 ^ 19
|
||||
return 524288;
|
||||
}
|
||||
|
||||
public getCenter(): [number, number] {
|
||||
const [lng, lat] = this.center;
|
||||
return [lng, lat];
|
||||
}
|
||||
|
||||
public getProjectionMatrix(): number[] {
|
||||
// @ts-ignore
|
||||
return this.projectionMatrix;
|
||||
}
|
||||
|
||||
public getModelMatrix(): number[] {
|
||||
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
}
|
||||
|
||||
public getViewMatrix(): number[] {
|
||||
// @ts-ignore
|
||||
return this.viewMatrix;
|
||||
}
|
||||
|
||||
public getViewMatrixUncentered(): number[] {
|
||||
// @ts-ignore
|
||||
return this.viewUncenteredMatrix;
|
||||
}
|
||||
public getViewProjectionMatrix(): number[] {
|
||||
// @ts-ignore
|
||||
return this.viewProjectionMatrix;
|
||||
}
|
||||
|
||||
public getViewProjectionMatrixUncentered(): number[] {
|
||||
// @ts-ignore
|
||||
return this.ViewProjectionMatrixUncentered;
|
||||
}
|
||||
|
||||
public getFocalDistance() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* P20 坐标系,固定 scale
|
||||
*/
|
||||
public projectFlat(lngLat: [number, number]): [number, number] {
|
||||
const maxs = 85.0511287798;
|
||||
const lat = Math.max(Math.min(maxs, lngLat[1]), -maxs);
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const zoomScale = 256 << 20;
|
||||
let d = Math.PI / 180;
|
||||
let x = lngLat[0] * d;
|
||||
let y = lat * d;
|
||||
y = Math.log(Math.tan(Math.PI / 4 + y / 2));
|
||||
const a = 0.5 / Math.PI;
|
||||
const b = 0.5;
|
||||
const c = -0.5 / Math.PI;
|
||||
d = 0.5;
|
||||
x = zoomScale * (a * x + b) - 215440491;
|
||||
y = -(zoomScale * (c * y + d) - 106744817);
|
||||
return [x, y];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.amap-logo{
|
||||
display: none !important;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
export const MapTheme: {
|
||||
[key: string]: any;
|
||||
} = {
|
||||
dark: 'amap://styles/c9f1d10cae34f8ab05e425462c5a58d7?isPublic=true',
|
||||
light: 'amap://styles/c422f5c0cfced5be9fe3a83f05f28a68?isPublic=true',
|
||||
normal: 'amap://styles/normal',
|
||||
blank: 'amap://styles/07c17002b38775b32a7a76c66cf90e99?isPublic=true',
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
import Viewport from './Viewport';
|
||||
import BaseMapWrapper from './BaseMapWrapper';
|
||||
import BaseMapService from './BaseMapService';
|
||||
export { Viewport, BaseMapWrapper, BaseMapService };
|
|
@ -0,0 +1,23 @@
|
|||
export const MapTheme: {
|
||||
[key: string]: any;
|
||||
} = {
|
||||
light: 'mapbox://styles/zcxduo/ck2ypyb1r3q9o1co1766dex29',
|
||||
dark: 'mapbox://styles/zcxduo/ck241p6413s0b1cpayzldv7x7',
|
||||
normal: 'mapbox://styles/mapbox/streets-v11',
|
||||
blank: {
|
||||
version: 8,
|
||||
// sprite: 'https://lzxue.github.io/font-glyphs/sprite/sprite',
|
||||
// glyphs:
|
||||
// 'https://gw.alipayobjects.com/os/antvdemo/assets/mapbox/glyphs/{fontstack}/{range}.pbf',
|
||||
sources: {},
|
||||
layers: [
|
||||
{
|
||||
id: 'background',
|
||||
type: 'background',
|
||||
layout: {
|
||||
visibility: 'none',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue