diff --git a/docs/api/scene.zh.md b/docs/api/scene.zh.md index 4e8a03871a..30675b7f2e 100644 --- a/docs/api/scene.zh.md +++ b/docs/api/scene.zh.md @@ -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) diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index 7025ab6dd4..cdbffd5c0e 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -12,6 +12,8 @@ import WarnInfo, { IWarnInfo } from './warnInfo'; */ const defaultSceneConfig: Partial = { id: 'map', + logoPosition: 'bottomleft', + logoVisible: true, }; /** diff --git a/packages/core/src/services/config/IConfigService.ts b/packages/core/src/services/config/IConfigService.ts index 9b09e2a052..c7aa5ca400 100644 --- a/packages/core/src/services/config/IConfigService.ts +++ b/packages/core/src/services/config/IConfigService.ts @@ -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 { diff --git a/packages/core/src/services/scene/ISceneService.ts b/packages/core/src/services/scene/ISceneService.ts index e6a6cf95a9..50bc35ab01 100644 --- a/packages/core/src/services/scene/ISceneService.ts +++ b/packages/core/src/services/scene/ISceneService.ts @@ -10,7 +10,7 @@ export interface ISceneService { addLayer(layer: ILayer): void; render(): void; getSceneContainer(): HTMLDivElement; - ExportMap2Png(): string; + exportPng(): string; destroy(): void; } // scene 事件 diff --git a/packages/core/src/services/scene/SceneService.ts b/packages/core/src/services/scene/SceneService.ts index 45776b2e57..e60adb1692 100644 --- a/packages/core/src/services/scene/SceneService.ts +++ b/packages/core/src/services/scene/SceneService.ts @@ -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 diff --git a/packages/maps/src/amap/Wrapper.ts b/packages/maps/src/amap/Wrapper.ts deleted file mode 100644 index 7114e93c1d..0000000000 --- a/packages/maps/src/amap/Wrapper.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/maps/src/amap/index.ts b/packages/maps/src/amap/index.ts index 59b2af238f..245811aad8 100644 --- a/packages/maps/src/amap/index.ts +++ b/packages/maps/src/amap/index.ts @@ -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 { - /** - * 原始地图实例 - */ - 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; - - @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 { - 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; } } diff --git a/packages/maps/src/amap/map.ts b/packages/maps/src/amap/map.ts new file mode 100644 index 0000000000..59b2af238f --- /dev/null +++ b/packages/maps/src/amap/map.ts @@ -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 { + /** + * 原始地图实例 + */ + 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; + + @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 { + 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); + }); + } +} diff --git a/packages/maps/src/index.ts b/packages/maps/src/index.ts index 980d870217..920d74d4a4 100644 --- a/packages/maps/src/index.ts +++ b/packages/maps/src/index.ts @@ -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 }; diff --git a/packages/maps/src/mapbox/Wrapper.ts b/packages/maps/src/mapbox/Wrapper.ts deleted file mode 100644 index b3fe8faec1..0000000000 --- a/packages/maps/src/mapbox/Wrapper.ts +++ /dev/null @@ -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; - } -} diff --git a/packages/maps/src/mapbox/index.ts b/packages/maps/src/mapbox/index.ts index b8659c1716..dc6e5f2259 100644 --- a/packages/maps/src/mapbox/index.ts +++ b/packages/maps/src/mapbox/index.ts @@ -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 { - public map: Map & IMapboxInstance; - - @inject(TYPES.MapConfig) - private readonly config: Partial; - - @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 { - 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; } } diff --git a/packages/maps/src/mapbox/map.ts b/packages/maps/src/mapbox/map.ts new file mode 100644 index 0000000000..b8659c1716 --- /dev/null +++ b/packages/maps/src/mapbox/map.ts @@ -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 { + public map: Map & IMapboxInstance; + + @inject(TYPES.MapConfig) + private readonly config: Partial; + + @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 { + 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; + } +} diff --git a/packages/scene/src/index.ts b/packages/scene/src/index.ts index a6f7ec64ac..d066ba0739 100644 --- a/packages/scene/src/index.ts +++ b/packages/scene/src/index.ts @@ -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 { return this.mapService; } - public ExportMap2Png(): string { - return this.sceneService.ExportMap2Png(); + public exportPng(): string { + return this.sceneService.exportPng(); } public get map() { diff --git a/stories/Layers/components/Point.tsx b/stories/Layers/components/Point.tsx index 6cdba83922..75d2949200 100644 --- a/stories/Layers/components/Point.tsx +++ b/stories/Layers/components/Point.tsx @@ -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', () => {