mirror of https://gitee.com/antv-l7/antv-l7
improvement: 增加L7 logo是否显示属性
This commit is contained in:
parent
7464cda373
commit
86c3327daa
|
@ -76,6 +76,22 @@ const scene = new L7.Scene({
|
|||
|
||||
需传入 dom 容器或者容器 id {domObject || string} [必选]
|
||||
|
||||
### logoPosition
|
||||
|
||||
L7 Logo的显示位置 默认左下角
|
||||
|
||||
- bottomright
|
||||
- topright
|
||||
- bottomleft,
|
||||
- topleft`
|
||||
|
||||
|
||||
### logoVisible
|
||||
|
||||
是否显示L7的Logo {boolean} true
|
||||
|
||||
|
||||
|
||||
### zoom
|
||||
|
||||
地图初始显示级别 {number} (0-22)
|
||||
|
|
|
@ -12,6 +12,8 @@ import WarnInfo, { IWarnInfo } from './warnInfo';
|
|||
*/
|
||||
const defaultSceneConfig: Partial<ISceneConfig> = {
|
||||
id: 'map',
|
||||
logoPosition: 'bottomleft',
|
||||
logoVisible: true,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import Ajv from 'ajv';
|
||||
import { PositionName } from '../component/IControlService';
|
||||
import { ILayerConfig } from '../layer/ILayerService';
|
||||
import { IMapWrapper } from '../map/IMapService';
|
||||
import { IRenderConfig } from '../renderer/IRendererService';
|
||||
|
||||
export interface ISceneConfig extends IRenderConfig {
|
||||
id: string | HTMLDivElement;
|
||||
map: IMapWrapper;
|
||||
logoPosition?: PositionName;
|
||||
logoVisible?: boolean;
|
||||
}
|
||||
|
||||
interface IValidateResult {
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface ISceneService {
|
|||
addLayer(layer: ILayer): void;
|
||||
render(): void;
|
||||
getSceneContainer(): HTMLDivElement;
|
||||
ExportMap2Png(): string;
|
||||
exportPng(): string;
|
||||
destroy(): void;
|
||||
}
|
||||
// scene 事件
|
||||
|
|
|
@ -226,10 +226,11 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
return this.$container as HTMLDivElement;
|
||||
}
|
||||
|
||||
public ExportMap2Png(): string {
|
||||
public exportPng(): string {
|
||||
const renderCanvas = this.$container?.getElementsByTagName('canvas')[0];
|
||||
this.render();
|
||||
const layersPng = renderCanvas?.toDataURL() as string;
|
||||
// this.render();
|
||||
DOM.printCanvas(renderCanvas as HTMLCanvasElement);
|
||||
const layersPng = renderCanvas?.toDataURL('image/png') as string;
|
||||
return layersPng;
|
||||
}
|
||||
|
||||
|
@ -237,11 +238,11 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.emit('destroy');
|
||||
this.inited = false;
|
||||
this.layerService.destroy();
|
||||
this.rendererService.destroy();
|
||||
this.interactionService.destroy();
|
||||
this.controlService.destroy();
|
||||
this.markerService.destroy();
|
||||
this.removeAllListeners();
|
||||
this.rendererService.destroy();
|
||||
this.map.destroy();
|
||||
unbind(this.$container as HTMLDivElement, this.handleWindowResized);
|
||||
window
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { IAMapInstance } from '../../typings/index';
|
||||
import BaseMapWrapper from '../BaseMapWrapper';
|
||||
import AMapService from './index';
|
||||
|
||||
export default class AMapWrapper extends BaseMapWrapper<
|
||||
AMap.Map & IAMapInstance
|
||||
> {
|
||||
protected getServiceConstructor() {
|
||||
return AMapService;
|
||||
}
|
||||
}
|
|
@ -1,393 +1,11 @@
|
|||
/**
|
||||
* AMapService
|
||||
*/
|
||||
import {
|
||||
Bounds,
|
||||
CoordinateSystem,
|
||||
ICoordinateSystemService,
|
||||
IGlobalConfigService,
|
||||
ILngLat,
|
||||
ILogService,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IPoint,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
MapStyle,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { IAMapEvent, IAMapInstance } from '../../typings/index';
|
||||
import { MapTheme } from './theme';
|
||||
import Viewport from './Viewport';
|
||||
let mapdivCount = 0;
|
||||
import { IAMapInstance } from '../../typings/index';
|
||||
import BaseMapWrapper from '../BaseMapWrapper';
|
||||
import AMapService from './map';
|
||||
|
||||
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 AMapService
|
||||
implements IMapService<AMap.Map & IAMapInstance> {
|
||||
/**
|
||||
* 原始地图实例
|
||||
*/
|
||||
public map: AMap.Map & IAMapInstance;
|
||||
|
||||
@inject(TYPES.IGlobalConfigService)
|
||||
private readonly configService: IGlobalConfigService;
|
||||
|
||||
@inject(TYPES.ILogService)
|
||||
private readonly logger: ILogService;
|
||||
|
||||
@inject(TYPES.MapConfig)
|
||||
private readonly config: Partial<IMapConfig>;
|
||||
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
private readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IEventEmitter)
|
||||
private eventEmitter: any;
|
||||
|
||||
private markerContainer: HTMLElement;
|
||||
private $mapContainer: HTMLElement | null;
|
||||
|
||||
private viewport: Viewport;
|
||||
|
||||
private cameraChangedCallback: (viewport: IViewport) => void;
|
||||
|
||||
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 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 {
|
||||
return this.map.setZoom(zoom);
|
||||
}
|
||||
|
||||
public getCenter(): ILngLat {
|
||||
const center = this.map.getCenter();
|
||||
return {
|
||||
lng: center.getLng(),
|
||||
lat: center.getLat(),
|
||||
};
|
||||
}
|
||||
|
||||
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 zoomIn(): void {
|
||||
this.map.zoomIn();
|
||||
}
|
||||
|
||||
public zoomOut(): void {
|
||||
this.map.zoomOut();
|
||||
}
|
||||
|
||||
public panTo(p: [number, number]): void {
|
||||
this.map.panTo(p);
|
||||
}
|
||||
public panBy(pixel: [number, number]): void {
|
||||
this.map.panTo(pixel);
|
||||
}
|
||||
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, center);
|
||||
}
|
||||
public setMapStyle(style: string): void {
|
||||
this.map.setMapStyle(this.getMapStyle(style));
|
||||
}
|
||||
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 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((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.creatAmapContainer(
|
||||
id as string | HTMLDivElement,
|
||||
);
|
||||
|
||||
const map = new AMap.Map(this.$mapContainer, {
|
||||
mapStyle: this.getMapStyle(style as string),
|
||||
zooms: [minZoom, maxZoom],
|
||||
viewMode: '3D',
|
||||
...rest,
|
||||
});
|
||||
// 监听地图相机事件
|
||||
map.on('camerachange', this.handleCameraChanged);
|
||||
// @ts-ignore
|
||||
this.map = map;
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
if (!amapLoaded && !mapInstance) {
|
||||
if (token === AMAP_API_KEY) {
|
||||
this.logger.warn(this.configService.getSceneWarninfo('MapToken'));
|
||||
}
|
||||
amapLoaded = true;
|
||||
plugin.push('Map3D');
|
||||
this.loadAMapScript(
|
||||
`https://webapi.amap.com/maps?v=${AMAP_VERSION}&key=${token}&plugin=${plugin.join(
|
||||
',',
|
||||
)}`,
|
||||
).then(() => {
|
||||
resolveMap();
|
||||
if (pendingResolveQueue.length) {
|
||||
pendingResolveQueue.forEach((r) => r());
|
||||
pendingResolveQueue = [];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if ((amapLoaded && window.AMap) || mapInstance) {
|
||||
resolveMap();
|
||||
} else {
|
||||
pendingResolveQueue.push(resolveMap);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewport = new Viewport();
|
||||
}
|
||||
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();
|
||||
// @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;
|
||||
}
|
||||
|
||||
private handleCameraChanged = (e: IAMapEvent): void => {
|
||||
const {
|
||||
fov,
|
||||
near,
|
||||
far,
|
||||
height,
|
||||
pitch,
|
||||
rotation,
|
||||
aspect,
|
||||
position,
|
||||
} = e.camera;
|
||||
const { lng, lat } = this.getCenter();
|
||||
if (this.cameraChangedCallback) {
|
||||
// resync viewport
|
||||
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],
|
||||
});
|
||||
|
||||
// set coordinate system
|
||||
if (this.viewport.getZoom() > LNGLAT_OFFSET_ZOOM_THRESHOLD) {
|
||||
this.coordinateSystemService.setCoordinateSystem(
|
||||
CoordinateSystem.P20_OFFSET,
|
||||
);
|
||||
} else {
|
||||
this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.P20);
|
||||
}
|
||||
this.cameraChangedCallback(this.viewport);
|
||||
}
|
||||
};
|
||||
|
||||
private getMapStyle(name: string): string {
|
||||
return MapTheme[name] ? MapTheme[name] : name;
|
||||
}
|
||||
private creatAmapContainer(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;
|
||||
}
|
||||
private loadAMapScript(src: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = () => {
|
||||
resolve();
|
||||
};
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
export default class AMapWrapper extends BaseMapWrapper<
|
||||
AMap.Map & IAMapInstance
|
||||
> {
|
||||
protected getServiceConstructor() {
|
||||
return AMapService;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* AMapService
|
||||
*/
|
||||
import {
|
||||
Bounds,
|
||||
CoordinateSystem,
|
||||
ICoordinateSystemService,
|
||||
IGlobalConfigService,
|
||||
ILngLat,
|
||||
ILogService,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IPoint,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
MapStyle,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { IAMapEvent, IAMapInstance } from '../../typings/index';
|
||||
import { MapTheme } from './theme';
|
||||
import Viewport from './Viewport';
|
||||
let mapdivCount = 0;
|
||||
|
||||
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 AMapService
|
||||
implements IMapService<AMap.Map & IAMapInstance> {
|
||||
/**
|
||||
* 原始地图实例
|
||||
*/
|
||||
public map: AMap.Map & IAMapInstance;
|
||||
|
||||
@inject(TYPES.IGlobalConfigService)
|
||||
private readonly configService: IGlobalConfigService;
|
||||
|
||||
@inject(TYPES.ILogService)
|
||||
private readonly logger: ILogService;
|
||||
|
||||
@inject(TYPES.MapConfig)
|
||||
private readonly config: Partial<IMapConfig>;
|
||||
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
private readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IEventEmitter)
|
||||
private eventEmitter: any;
|
||||
|
||||
private markerContainer: HTMLElement;
|
||||
private $mapContainer: HTMLElement | null;
|
||||
|
||||
private viewport: Viewport;
|
||||
|
||||
private cameraChangedCallback: (viewport: IViewport) => void;
|
||||
|
||||
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 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 {
|
||||
return this.map.setZoom(zoom);
|
||||
}
|
||||
|
||||
public getCenter(): ILngLat {
|
||||
const center = this.map.getCenter();
|
||||
return {
|
||||
lng: center.getLng(),
|
||||
lat: center.getLat(),
|
||||
};
|
||||
}
|
||||
|
||||
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 zoomIn(): void {
|
||||
this.map.zoomIn();
|
||||
}
|
||||
|
||||
public zoomOut(): void {
|
||||
this.map.zoomOut();
|
||||
}
|
||||
|
||||
public panTo(p: [number, number]): void {
|
||||
this.map.panTo(p);
|
||||
}
|
||||
public panBy(pixel: [number, number]): void {
|
||||
this.map.panTo(pixel);
|
||||
}
|
||||
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, center);
|
||||
}
|
||||
public setMapStyle(style: string): void {
|
||||
this.map.setMapStyle(this.getMapStyle(style));
|
||||
}
|
||||
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 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((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.creatAmapContainer(
|
||||
id as string | HTMLDivElement,
|
||||
);
|
||||
|
||||
const map = new AMap.Map(this.$mapContainer, {
|
||||
mapStyle: this.getMapStyle(style as string),
|
||||
zooms: [minZoom, maxZoom],
|
||||
viewMode: '3D',
|
||||
...rest,
|
||||
});
|
||||
// 监听地图相机事件
|
||||
map.on('camerachange', this.handleCameraChanged);
|
||||
// @ts-ignore
|
||||
this.map = map;
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
if (!amapLoaded && !mapInstance) {
|
||||
if (token === AMAP_API_KEY) {
|
||||
this.logger.warn(this.configService.getSceneWarninfo('MapToken'));
|
||||
}
|
||||
amapLoaded = true;
|
||||
plugin.push('Map3D');
|
||||
this.loadAMapScript(
|
||||
`https://webapi.amap.com/maps?v=${AMAP_VERSION}&key=${token}&plugin=${plugin.join(
|
||||
',',
|
||||
)}`,
|
||||
).then(() => {
|
||||
resolveMap();
|
||||
if (pendingResolveQueue.length) {
|
||||
pendingResolveQueue.forEach((r) => r());
|
||||
pendingResolveQueue = [];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if ((amapLoaded && window.AMap) || mapInstance) {
|
||||
resolveMap();
|
||||
} else {
|
||||
pendingResolveQueue.push(resolveMap);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.viewport = new Viewport();
|
||||
}
|
||||
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();
|
||||
// @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;
|
||||
}
|
||||
|
||||
private handleCameraChanged = (e: IAMapEvent): void => {
|
||||
const {
|
||||
fov,
|
||||
near,
|
||||
far,
|
||||
height,
|
||||
pitch,
|
||||
rotation,
|
||||
aspect,
|
||||
position,
|
||||
} = e.camera;
|
||||
const { lng, lat } = this.getCenter();
|
||||
if (this.cameraChangedCallback) {
|
||||
// resync viewport
|
||||
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],
|
||||
});
|
||||
|
||||
// set coordinate system
|
||||
if (this.viewport.getZoom() > LNGLAT_OFFSET_ZOOM_THRESHOLD) {
|
||||
this.coordinateSystemService.setCoordinateSystem(
|
||||
CoordinateSystem.P20_OFFSET,
|
||||
);
|
||||
} else {
|
||||
this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.P20);
|
||||
}
|
||||
this.cameraChangedCallback(this.viewport);
|
||||
}
|
||||
};
|
||||
|
||||
private getMapStyle(name: string): string {
|
||||
return MapTheme[name] ? MapTheme[name] : name;
|
||||
}
|
||||
private creatAmapContainer(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;
|
||||
}
|
||||
private loadAMapScript(src: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = () => {
|
||||
resolve();
|
||||
};
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import GaodeMap from './amap/Wrapper';
|
||||
import Mapbox from './mapbox/Wrapper';
|
||||
import GaodeMap from './amap/';
|
||||
import Mapbox from './mapbox/';
|
||||
|
||||
export { GaodeMap, Mapbox };
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { Map } from 'mapbox-gl';
|
||||
import { IMapboxInstance } from '../../typings/index';
|
||||
import BaseMapWrapper from '../BaseMapWrapper';
|
||||
import MapboxService from './index';
|
||||
|
||||
export default class MapboxWrapper extends BaseMapWrapper<
|
||||
Map & IMapboxInstance
|
||||
> {
|
||||
protected getServiceConstructor() {
|
||||
return MapboxService;
|
||||
}
|
||||
}
|
|
@ -1,328 +1,12 @@
|
|||
/**
|
||||
* MapboxService
|
||||
*/
|
||||
import {
|
||||
Bounds,
|
||||
CoordinateSystem,
|
||||
ICoordinateSystemService,
|
||||
IGlobalConfigService,
|
||||
ILngLat,
|
||||
ILogService,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IPoint,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
MapStyle,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import mapboxgl, { IControl, Map } from 'mapbox-gl';
|
||||
import { Map } from 'mapbox-gl';
|
||||
import { IMapboxInstance } from '../../typings/index';
|
||||
import Viewport from './Viewport';
|
||||
const EventMap: {
|
||||
[key: string]: any;
|
||||
} = {
|
||||
mapmove: 'move',
|
||||
camerachange: 'move',
|
||||
};
|
||||
import { MapTheme } from './theme';
|
||||
import BaseMapWrapper from '../BaseMapWrapper';
|
||||
import MapboxService from './map';
|
||||
|
||||
const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12;
|
||||
const MAPBOX_API_KEY =
|
||||
'pk.eyJ1IjoibHp4dWUiLCJhIjoiYnhfTURyRSJ9.Ugm314vAKPHBzcPmY1p4KQ';
|
||||
/**
|
||||
* AMapService
|
||||
*/
|
||||
@injectable()
|
||||
export default class MapboxService
|
||||
implements IMapService<Map & IMapboxInstance> {
|
||||
public map: Map & IMapboxInstance;
|
||||
|
||||
@inject(TYPES.MapConfig)
|
||||
private readonly config: Partial<IMapConfig>;
|
||||
|
||||
@inject(TYPES.IGlobalConfigService)
|
||||
private readonly configService: IGlobalConfigService;
|
||||
|
||||
@inject(TYPES.ILogService)
|
||||
private readonly logger: ILogService;
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
private readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IEventEmitter)
|
||||
private eventEmitter: any;
|
||||
private viewport: Viewport;
|
||||
private markerContainer: HTMLElement;
|
||||
private cameraChangedCallback: (viewport: IViewport) => void;
|
||||
private $mapContainer: HTMLElement | null;
|
||||
|
||||
// init
|
||||
public addMarkerContainer(): void {
|
||||
const container = this.map.getCanvasContainer();
|
||||
this.markerContainer = DOM.create('div', 'l7-marker-container', container);
|
||||
}
|
||||
|
||||
public getMarkerContainer(): HTMLElement {
|
||||
return this.markerContainer;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public getContainer(): HTMLElement | null {
|
||||
return this.map.getContainer();
|
||||
}
|
||||
|
||||
public getSize(): [number, number] {
|
||||
const size = this.map.transform;
|
||||
return [size.width, size.height];
|
||||
}
|
||||
// get mapStatus method
|
||||
|
||||
public getType() {
|
||||
return 'mapbox';
|
||||
}
|
||||
|
||||
public getZoom(): number {
|
||||
return this.map.getZoom();
|
||||
}
|
||||
|
||||
public setZoom(zoom: number) {
|
||||
return this.map.setZoom(zoom);
|
||||
}
|
||||
|
||||
public getCenter(): ILngLat {
|
||||
return this.map.getCenter();
|
||||
}
|
||||
|
||||
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(): void {
|
||||
this.map.zoomIn();
|
||||
}
|
||||
|
||||
public zoomOut(): void {
|
||||
this.map.zoomOut();
|
||||
}
|
||||
|
||||
public panTo(p: [number, number]): void {
|
||||
this.map.panTo(p);
|
||||
}
|
||||
|
||||
public panBy(pixel: [number, number]): void {
|
||||
this.panTo(pixel);
|
||||
}
|
||||
|
||||
public fitBounds(bound: Bounds): void {
|
||||
this.map.fitBounds(bound);
|
||||
}
|
||||
|
||||
public setMaxZoom(max: number): void {
|
||||
this.map.setMaxZoom(max);
|
||||
}
|
||||
|
||||
public setMinZoom(min: number): void {
|
||||
this.map.setMinZoom(min);
|
||||
}
|
||||
|
||||
public setZoomAndCenter(zoom: number, center: [number, number]): void {
|
||||
this.map.flyTo({
|
||||
zoom,
|
||||
center,
|
||||
});
|
||||
}
|
||||
|
||||
public setMapStyle(style: string): void {
|
||||
this.map.setStyle(this.getMapStyle(style));
|
||||
}
|
||||
// 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 async init(): Promise<void> {
|
||||
const {
|
||||
id = 'map',
|
||||
attributionControl = false,
|
||||
style = 'light',
|
||||
token = MAPBOX_API_KEY,
|
||||
rotation = 0,
|
||||
mapInstance,
|
||||
...rest
|
||||
} = this.config;
|
||||
|
||||
this.viewport = new Viewport();
|
||||
|
||||
/**
|
||||
* TODO: 使用 mapbox v0.53.x 版本 custom layer,需要共享 gl context
|
||||
* @see https://github.com/mapbox/mapbox-gl-js/blob/master/debug/threejs.html#L61-L64
|
||||
*/
|
||||
|
||||
// 判断全局 mapboxgl 对象的加载
|
||||
if (!mapInstance && !mapboxgl) {
|
||||
// 用户有时传递进来的实例是继承于 mapbox 实例化的,不一定是 mapboxgl 对象。
|
||||
this.logger.error(this.configService.getSceneWarninfo('SDK'));
|
||||
}
|
||||
|
||||
if (
|
||||
token === MAPBOX_API_KEY &&
|
||||
style !== 'blank' &&
|
||||
!mapboxgl.accessToken &&
|
||||
!mapInstance // 如果用户传递了 mapInstance,应该不去干预实例的 accessToken。
|
||||
) {
|
||||
this.logger.warn(this.configService.getSceneWarninfo('MapToken'));
|
||||
}
|
||||
|
||||
// 判断是否设置了 accessToken
|
||||
if (!mapInstance && !mapboxgl.accessToken) {
|
||||
// 用户有时传递进来的实例是继承于 mapbox 实例化的,不一定是 mapboxgl 对象。
|
||||
mapboxgl.accessToken = token;
|
||||
}
|
||||
|
||||
if (mapInstance) {
|
||||
// @ts-ignore
|
||||
this.map = mapInstance;
|
||||
this.$mapContainer = this.map.getContainer();
|
||||
} else {
|
||||
this.$mapContainer = this.creatAmapContainer(id);
|
||||
// @ts-ignore
|
||||
this.map = new mapboxgl.Map({
|
||||
container: id,
|
||||
style: this.getMapStyle(style),
|
||||
attributionControl,
|
||||
bearing: rotation,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
this.map.on('load', this.handleCameraChanged);
|
||||
this.map.on('move', this.handleCameraChanged);
|
||||
|
||||
// 不同于高德地图,需要手动触发首次渲染
|
||||
this.handleCameraChanged();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.eventEmitter.removeAllListeners();
|
||||
if (this.map) {
|
||||
this.map.remove();
|
||||
this.$mapContainer = null;
|
||||
this.removeLogoControl();
|
||||
}
|
||||
}
|
||||
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 onCameraChanged(callback: (viewport: IViewport) => void): void {
|
||||
this.cameraChangedCallback = callback;
|
||||
}
|
||||
|
||||
private handleCameraChanged = () => {
|
||||
// @see https://github.com/mapbox/mapbox-gl-js/issues/2572
|
||||
const { lat, lng } = this.map.getCenter().wrap();
|
||||
|
||||
// resync
|
||||
this.viewport.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,
|
||||
});
|
||||
|
||||
// set coordinate system
|
||||
if (this.viewport.getZoom() > LNGLAT_OFFSET_ZOOM_THRESHOLD) {
|
||||
this.coordinateSystemService.setCoordinateSystem(
|
||||
CoordinateSystem.LNGLAT_OFFSET,
|
||||
);
|
||||
} else {
|
||||
this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.LNGLAT);
|
||||
}
|
||||
|
||||
this.cameraChangedCallback(this.viewport);
|
||||
};
|
||||
|
||||
private creatAmapContainer(id: string | HTMLDivElement) {
|
||||
let $wrapper = id as HTMLDivElement;
|
||||
if (typeof id === 'string') {
|
||||
$wrapper = document.getElementById(id) as HTMLDivElement;
|
||||
}
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
private removeLogoControl(): void {
|
||||
// @ts-ignore
|
||||
const controls = this.map._controls as IControl[];
|
||||
const logoCtr = controls.find((ctr: IControl) => {
|
||||
if (ctr.hasOwnProperty('_updateLogo')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (logoCtr) {
|
||||
this.map.removeControl(logoCtr);
|
||||
}
|
||||
}
|
||||
|
||||
private getMapStyle(name: MapStyle) {
|
||||
if (typeof name !== 'string') {
|
||||
return name;
|
||||
}
|
||||
return MapTheme[name] ? MapTheme[name] : name;
|
||||
export default class MapboxWrapper extends BaseMapWrapper<
|
||||
Map & IMapboxInstance
|
||||
> {
|
||||
protected getServiceConstructor() {
|
||||
return MapboxService;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
/**
|
||||
* MapboxService
|
||||
*/
|
||||
import {
|
||||
Bounds,
|
||||
CoordinateSystem,
|
||||
ICoordinateSystemService,
|
||||
IGlobalConfigService,
|
||||
ILngLat,
|
||||
ILogService,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IPoint,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
MapStyle,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import mapboxgl, { IControl, Map } from 'mapbox-gl';
|
||||
import { IMapboxInstance } from '../../typings/index';
|
||||
import Viewport from './Viewport';
|
||||
const EventMap: {
|
||||
[key: string]: any;
|
||||
} = {
|
||||
mapmove: 'move',
|
||||
camerachange: 'move',
|
||||
};
|
||||
import { MapTheme } from './theme';
|
||||
|
||||
const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12;
|
||||
const MAPBOX_API_KEY =
|
||||
'pk.eyJ1IjoibHp4dWUiLCJhIjoiYnhfTURyRSJ9.Ugm314vAKPHBzcPmY1p4KQ';
|
||||
/**
|
||||
* AMapService
|
||||
*/
|
||||
@injectable()
|
||||
export default class MapboxService
|
||||
implements IMapService<Map & IMapboxInstance> {
|
||||
public map: Map & IMapboxInstance;
|
||||
|
||||
@inject(TYPES.MapConfig)
|
||||
private readonly config: Partial<IMapConfig>;
|
||||
|
||||
@inject(TYPES.IGlobalConfigService)
|
||||
private readonly configService: IGlobalConfigService;
|
||||
|
||||
@inject(TYPES.ILogService)
|
||||
private readonly logger: ILogService;
|
||||
@inject(TYPES.ICoordinateSystemService)
|
||||
private readonly coordinateSystemService: ICoordinateSystemService;
|
||||
|
||||
@inject(TYPES.IEventEmitter)
|
||||
private eventEmitter: any;
|
||||
private viewport: Viewport;
|
||||
private markerContainer: HTMLElement;
|
||||
private cameraChangedCallback: (viewport: IViewport) => void;
|
||||
private $mapContainer: HTMLElement | null;
|
||||
|
||||
// init
|
||||
public addMarkerContainer(): void {
|
||||
const container = this.map.getCanvasContainer();
|
||||
this.markerContainer = DOM.create('div', 'l7-marker-container', container);
|
||||
}
|
||||
|
||||
public getMarkerContainer(): HTMLElement {
|
||||
return this.markerContainer;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public getContainer(): HTMLElement | null {
|
||||
return this.map.getContainer();
|
||||
}
|
||||
|
||||
public getSize(): [number, number] {
|
||||
const size = this.map.transform;
|
||||
return [size.width, size.height];
|
||||
}
|
||||
// get mapStatus method
|
||||
|
||||
public getType() {
|
||||
return 'mapbox';
|
||||
}
|
||||
|
||||
public getZoom(): number {
|
||||
return this.map.getZoom();
|
||||
}
|
||||
|
||||
public setZoom(zoom: number) {
|
||||
return this.map.setZoom(zoom);
|
||||
}
|
||||
|
||||
public getCenter(): ILngLat {
|
||||
return this.map.getCenter();
|
||||
}
|
||||
|
||||
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(): void {
|
||||
this.map.zoomIn();
|
||||
}
|
||||
|
||||
public zoomOut(): void {
|
||||
this.map.zoomOut();
|
||||
}
|
||||
|
||||
public panTo(p: [number, number]): void {
|
||||
this.map.panTo(p);
|
||||
}
|
||||
|
||||
public panBy(pixel: [number, number]): void {
|
||||
this.panTo(pixel);
|
||||
}
|
||||
|
||||
public fitBounds(bound: Bounds): void {
|
||||
this.map.fitBounds(bound);
|
||||
}
|
||||
|
||||
public setMaxZoom(max: number): void {
|
||||
this.map.setMaxZoom(max);
|
||||
}
|
||||
|
||||
public setMinZoom(min: number): void {
|
||||
this.map.setMinZoom(min);
|
||||
}
|
||||
|
||||
public setZoomAndCenter(zoom: number, center: [number, number]): void {
|
||||
this.map.flyTo({
|
||||
zoom,
|
||||
center,
|
||||
});
|
||||
}
|
||||
|
||||
public setMapStyle(style: string): void {
|
||||
this.map.setStyle(this.getMapStyle(style));
|
||||
}
|
||||
// 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 async init(): Promise<void> {
|
||||
const {
|
||||
id = 'map',
|
||||
attributionControl = false,
|
||||
style = 'light',
|
||||
token = MAPBOX_API_KEY,
|
||||
rotation = 0,
|
||||
mapInstance,
|
||||
...rest
|
||||
} = this.config;
|
||||
|
||||
this.viewport = new Viewport();
|
||||
|
||||
/**
|
||||
* TODO: 使用 mapbox v0.53.x 版本 custom layer,需要共享 gl context
|
||||
* @see https://github.com/mapbox/mapbox-gl-js/blob/master/debug/threejs.html#L61-L64
|
||||
*/
|
||||
|
||||
// 判断全局 mapboxgl 对象的加载
|
||||
if (!mapInstance && !mapboxgl) {
|
||||
// 用户有时传递进来的实例是继承于 mapbox 实例化的,不一定是 mapboxgl 对象。
|
||||
this.logger.error(this.configService.getSceneWarninfo('SDK'));
|
||||
}
|
||||
|
||||
if (
|
||||
token === MAPBOX_API_KEY &&
|
||||
style !== 'blank' &&
|
||||
!mapboxgl.accessToken &&
|
||||
!mapInstance // 如果用户传递了 mapInstance,应该不去干预实例的 accessToken。
|
||||
) {
|
||||
this.logger.warn(this.configService.getSceneWarninfo('MapToken'));
|
||||
}
|
||||
|
||||
// 判断是否设置了 accessToken
|
||||
if (!mapInstance && !mapboxgl.accessToken) {
|
||||
// 用户有时传递进来的实例是继承于 mapbox 实例化的,不一定是 mapboxgl 对象。
|
||||
mapboxgl.accessToken = token;
|
||||
}
|
||||
|
||||
if (mapInstance) {
|
||||
// @ts-ignore
|
||||
this.map = mapInstance;
|
||||
this.$mapContainer = this.map.getContainer();
|
||||
} else {
|
||||
this.$mapContainer = this.creatAmapContainer(id);
|
||||
// @ts-ignore
|
||||
this.map = new mapboxgl.Map({
|
||||
container: id,
|
||||
style: this.getMapStyle(style),
|
||||
attributionControl,
|
||||
bearing: rotation,
|
||||
...rest,
|
||||
});
|
||||
}
|
||||
this.map.on('load', this.handleCameraChanged);
|
||||
this.map.on('move', this.handleCameraChanged);
|
||||
|
||||
// 不同于高德地图,需要手动触发首次渲染
|
||||
this.handleCameraChanged();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.eventEmitter.removeAllListeners();
|
||||
if (this.map) {
|
||||
this.map.remove();
|
||||
this.$mapContainer = null;
|
||||
this.removeLogoControl();
|
||||
}
|
||||
}
|
||||
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 onCameraChanged(callback: (viewport: IViewport) => void): void {
|
||||
this.cameraChangedCallback = callback;
|
||||
}
|
||||
|
||||
private handleCameraChanged = () => {
|
||||
// @see https://github.com/mapbox/mapbox-gl-js/issues/2572
|
||||
const { lat, lng } = this.map.getCenter().wrap();
|
||||
|
||||
// resync
|
||||
this.viewport.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,
|
||||
});
|
||||
|
||||
// set coordinate system
|
||||
if (this.viewport.getZoom() > LNGLAT_OFFSET_ZOOM_THRESHOLD) {
|
||||
this.coordinateSystemService.setCoordinateSystem(
|
||||
CoordinateSystem.LNGLAT_OFFSET,
|
||||
);
|
||||
} else {
|
||||
this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.LNGLAT);
|
||||
}
|
||||
|
||||
this.cameraChangedCallback(this.viewport);
|
||||
};
|
||||
|
||||
private creatAmapContainer(id: string | HTMLDivElement) {
|
||||
let $wrapper = id as HTMLDivElement;
|
||||
if (typeof id === 'string') {
|
||||
$wrapper = document.getElementById(id) as HTMLDivElement;
|
||||
}
|
||||
return $wrapper;
|
||||
}
|
||||
|
||||
private removeLogoControl(): void {
|
||||
// @ts-ignore
|
||||
const controls = this.map._controls as IControl[];
|
||||
const logoCtr = controls.find((ctr: IControl) => {
|
||||
if (ctr.hasOwnProperty('_updateLogo')) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (logoCtr) {
|
||||
this.map.removeControl(logoCtr);
|
||||
}
|
||||
}
|
||||
|
||||
private getMapStyle(name: MapStyle) {
|
||||
if (typeof name !== 'string') {
|
||||
return name;
|
||||
}
|
||||
return MapTheme[name] ? MapTheme[name] : name;
|
||||
}
|
||||
}
|
|
@ -57,7 +57,7 @@ class Scene
|
|||
private container: Container;
|
||||
|
||||
public constructor(config: ISceneConfig) {
|
||||
const { id, map } = config;
|
||||
const { id, map, logoPosition, logoVisible } = config;
|
||||
|
||||
// 创建场景容器
|
||||
const sceneContainer = createSceneContainer();
|
||||
|
@ -93,14 +93,16 @@ class Scene
|
|||
// 初始化 scene
|
||||
this.sceneService.init(config);
|
||||
// TODO: 初始化组件
|
||||
this.addControl(new Logo());
|
||||
if (logoVisible) {
|
||||
this.addControl(new Logo({ position: logoPosition }));
|
||||
}
|
||||
}
|
||||
|
||||
public getMapService(): IMapService<unknown> {
|
||||
return this.mapService;
|
||||
}
|
||||
public ExportMap2Png(): string {
|
||||
return this.sceneService.ExportMap2Png();
|
||||
public exportPng(): string {
|
||||
return this.sceneService.exportPng();
|
||||
}
|
||||
|
||||
public get map() {
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class Point3D extends React.Component {
|
|||
center: [120.19382669582967, 30.258134],
|
||||
pitch: 0,
|
||||
style: 'dark',
|
||||
zoom: 3,
|
||||
zoom: 0,
|
||||
}),
|
||||
});
|
||||
// scene.on('loaded', () => {
|
||||
|
|
Loading…
Reference in New Issue