import Emitter from "@finevis/emitter"; import "@amap/amap-jsapi-types"; import { getOverlayPaths, getUID, registerHotkey } from "@utils"; import { BaseOverlayEditor, RectangleEditor, PolygonEditor, PolylineEditor, CircleEditor, } from "./editors"; import { IMapOptions, IMapEditor } from "./type"; import { PolygonOptions, PolylineOptions, SelectedOptions } from "./constants"; import { EventTypes, OverlayTypes } from "../types/enum"; type AMapOverlayEditor = | AMap.RectangleEditor | AMap.PolygonEditor | AMap.PolylineEditor | AMap.CircleEditor; type OverlayTemp = { type: OverlayTypes; target: AMap.MapOverlay; }; const getOverlayOptions = (type: OverlayTypes) => type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions; export class MapEditor extends Emitter implements IMapEditor { dom: HTMLDivElement; private _map: AMap.Map | undefined; private overlayEditors: BaseOverlayEditor[] = []; private overlayMap: Record = {}; private imageLayerMap: Record = {}; private currentOverlayEditor: | BaseOverlayEditor | undefined; private selectedIds: string[] = []; private editorStatus: "editing" | "creating" | null = null; constructor(dom: HTMLDivElement) { super(); this.dom = dom; } get map() { return this._map!; } async init() { await AMapLoader.load({ key: "a4171ad2d7df42823b4de7d25c8c35ee", version: "2.0", plugins: [ "AMap.RectangleEditor", "AMap.PolylineEditor", "AMap.PolygonEditor", "AMap.CircleEditor", "AMap.PlaceSearch", "AMap.AutoComplete", ], }); this._map = new AMap.Map(this.dom); this.initEditors(); // Space的key是空字符串, 这就离谱. registerHotkey(" ", { callback: this.finishEditOverlay.bind(this) }); registerHotkey("e", { callback: this.editSelectedOverlay.bind(this), alt: true, }); } initEditors() { const { map } = this; this.overlayEditors = [ new RectangleEditor(map), new PolygonEditor(map), new PolylineEditor(map), new CircleEditor(map), ]; } getCenter() { const { lng, lat } = this.map.getCenter(); return [lng, lat]; } getZoom() { return this.map.getZoom(); } getEditorByType(overlayType: OverlayTypes) { return this.overlayEditors.find( (editor) => editor.getType() === overlayType )!; } importProject(options: IMapOptions) { this.clearOverlays(); const { overlays } = options; const overlayEditors: Record< OverlayTypes, BaseOverlayEditor > = { [OverlayTypes.Rectangle]: this.getEditorByType(OverlayTypes.Rectangle), [OverlayTypes.Polygon]: this.getEditorByType(OverlayTypes.Polygon), [OverlayTypes.Polyline]: this.getEditorByType(OverlayTypes.Polyline), [OverlayTypes.Circle]: this.getEditorByType(OverlayTypes.Circle), }; if (options.center) { const [lng, lat] = options.center; this.map.setCenter(new AMap.LngLat(lng, lat)); } if (options.zoom != null) { this.map.setZoom(options.zoom); } overlays.forEach((overlay) => { const { type, id } = overlay; const target = overlayEditors[type].build(overlay); this._addOverlay(id, { type, target }); target.setOptions( type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions ); this.map.add(target); if (overlay.backgroundImage) { this.updateOverlayBackground(id, overlay.backgroundImage); } }); } importGeoJSON(geojson: GeoJSON.FeatureCollection): void { throw new Error("Method not implemented."); } // 清空所有覆盖物 clearOverlays() { for (let id in this.overlayMap) { this.map.remove(this.overlayMap[id].target); } for (let id in this.imageLayerMap) { this.map.remove(this.imageLayerMap[id]); } this.overlayMap = {}; this.imageLayerMap = {}; this.selectedIds = []; } updateOverlayBackground(id: string, src?: string) { if (this.overlayMap[id] == null) return; const overlay = this.overlayMap[id]; // 只支持矩形 if (overlay.type !== OverlayTypes.Rectangle) return; const rect = overlay.target as AMap.Rectangle; const rectBounds = rect.getBounds()!; if (this.imageLayerMap[id] == null && src) { const { southWest, northEast } = rectBounds; this.imageLayerMap[id] = new AMap.ImageLayer({ url: src, // 居然不一样..... bounds: [southWest.lng, southWest.lat, northEast.lng, northEast.lat], zIndex: 0, }); this.map.add(this.imageLayerMap[id]); } this.imageLayerMap[id]?.setBounds(rectBounds); src && this.imageLayerMap[id]?.setImageUrl(src); } createOverlay(type: OverlayTypes) { this.currentOverlayEditor = this.getEditorByType(type!); this.currentOverlayEditor?.create(); this.editorStatus = "creating"; } selectOverlays(ids?: string[]) { this.selectedIds?.forEach((id) => { const { target, type } = this.overlayMap[id]; target.setOptions(getOverlayOptions(type)); }); if (ids == null) return; this.selectedIds = ids; ids.forEach((id) => { const { target } = this.overlayMap[id]; target.setOptions(SelectedOptions); }); } deleteOverlays() { if (this.selectedIds?.length === 0) return; this.selectedIds.forEach((id) => { const { target } = this.overlayMap[id]; this.map.remove(target); delete this.overlayMap[id]; if (this.imageLayerMap[id]) { this.map.remove(this.imageLayerMap[id]); delete this.imageLayerMap[id]; } }); this.selectedIds = []; } finishEditOverlay() { if (this.currentOverlayEditor == null) return; const target = this.currentOverlayEditor.finish(); if (target == null) return; const type = this.currentOverlayEditor.getType(); const isCreatingOverlay = this.editorStatus === "creating"; let id = getUID(); if (isCreatingOverlay) { this._addOverlay(id, { type, target }); target.setOptions( type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions ); } else { id = this.selectedIds[0]; } this._updateOverlay(id); this.editorStatus = null; } _updateOverlay(id: string) { if (this.overlayMap[id] == null) return; const { type, target } = this.overlayMap[id]; const evt: any = { id, type }; if (type === OverlayTypes.Circle) { const circle = target as AMap.Circle; const { lng, lat } = circle.getCenter(); evt.lngLat = [lng, lat]; evt.radius = circle.getRadius(); } else { evt.path = getOverlayPaths(target, type); } this.emit(EventTypes.FinishEditOverlay, evt); this.updateOverlayBackground(id); } editSelectedOverlay() { if (this.selectedIds.length !== 1) return; const [id] = this.selectedIds; const { target, type } = this.overlayMap[id]; this.currentOverlayEditor = this.getEditorByType(type); this.currentOverlayEditor?.edit(target); this.editorStatus = "editing"; } onOverlayDragEnd(id: string) { this._updateOverlay(id); } _addOverlay(id: string, overlay: OverlayTemp) { this.overlayMap[id] = overlay; overlay.target.on("dragend", () => this.onOverlayDragEnd(id)); } }