improvement: 增加L7 logo是否显示属性

This commit is contained in:
thinkinggis 2020-02-04 22:37:11 +08:00
parent 7464cda373
commit 86c3327daa
14 changed files with 773 additions and 750 deletions

View File

@ -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

View File

@ -12,6 +12,8 @@ import WarnInfo, { IWarnInfo } from './warnInfo';
*/
const defaultSceneConfig: Partial<ISceneConfig> = {
id: 'map',
logoPosition: 'bottomleft',
logoVisible: true,
};
/**

View File

@ -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 {

View File

@ -10,7 +10,7 @@ export interface ISceneService {
addLayer(layer: ILayer): void;
render(): void;
getSceneContainer(): HTMLDivElement;
ExportMap2Png(): string;
exportPng(): string;
destroy(): void;
}
// scene 事件

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
});
}
}

View File

@ -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 };

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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', () => {