Cmen
3 years ago
18 changed files with 381 additions and 360 deletions
@ -0,0 +1,203 @@
|
||||
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; |
||||
}; |
||||
|
||||
export class MapEditor extends Emitter implements IMapEditor { |
||||
dom: HTMLDivElement; |
||||
private _map: AMap.Map | undefined; |
||||
private overlayEditors: BaseOverlayEditor<AMapOverlayEditor>[] = []; |
||||
private overlayMap: Record<string, OverlayTemp> = {}; |
||||
private currentOverlayEditor: |
||||
| BaseOverlayEditor<AMapOverlayEditor> |
||||
| 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), |
||||
]; |
||||
} |
||||
|
||||
getEditorByType(overlayType: OverlayTypes) { |
||||
return this.overlayEditors.find( |
||||
(editor) => editor.getType() === overlayType |
||||
); |
||||
} |
||||
|
||||
importProject(options: IMapOptions) { |
||||
this.clearOverlays(); |
||||
const { rectangles, polygons, polylines, circles } = options; |
||||
const rectangleEditor = this.getEditorByType(OverlayTypes.Rectangle); |
||||
const polygonEditor = this.getEditorByType(OverlayTypes.Polygon); |
||||
const polylineEditor = this.getEditorByType(OverlayTypes.Polyline); |
||||
const circleEditor = this.getEditorByType(OverlayTypes.Circle); |
||||
|
||||
const addOverlay = ( |
||||
id: string, |
||||
target: AMap.MapOverlay, |
||||
type: OverlayTypes |
||||
) => { |
||||
this.overlayMap[id] = { |
||||
type, |
||||
target, |
||||
}; |
||||
this.map.add(target); |
||||
}; |
||||
|
||||
rectangles.forEach((rect) => |
||||
addOverlay(rect.id, rectangleEditor!.build(rect), OverlayTypes.Rectangle) |
||||
); |
||||
polygons.forEach((polygon) => |
||||
addOverlay( |
||||
polygon.id, |
||||
polygonEditor!.build(polygon), |
||||
OverlayTypes.Polygon |
||||
) |
||||
); |
||||
polylines.forEach((polyline) => |
||||
addOverlay( |
||||
polyline.id, |
||||
polylineEditor!.build(polyline), |
||||
OverlayTypes.Polyline |
||||
) |
||||
); |
||||
circles.forEach((circle) => |
||||
addOverlay(circle.id, circleEditor!.build(circle), OverlayTypes.Circle) |
||||
); |
||||
} |
||||
|
||||
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); |
||||
} |
||||
this.overlayMap = {}; |
||||
} |
||||
|
||||
createOverlay(type: OverlayTypes) { |
||||
this.currentOverlayEditor = this.getEditorByType(type!); |
||||
this.currentOverlayEditor?.create(); |
||||
this.editorStatus = "creating"; |
||||
} |
||||
|
||||
selectOverlays(ids?: string[]) { |
||||
if (!ids?.length) 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]; |
||||
}); |
||||
} |
||||
|
||||
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.overlayMap[id] = { type, target }; |
||||
} else { |
||||
id = this.selectedIds[0]; |
||||
} |
||||
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); |
||||
} |
||||
target.setOptions( |
||||
type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions |
||||
); |
||||
this.emit(EventTypes.FinishEditOverlay, evt); |
||||
this.editorStatus = null; |
||||
} |
||||
|
||||
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"; |
||||
} |
||||
} |
@ -1,221 +1,2 @@
|
||||
import Emitter from "@finevis/emitter"; |
||||
import "@amap/amap-jsapi-types"; |
||||
|
||||
import { IMapState, IOverlay } from "@store"; |
||||
import { getOverlayPaths, getUID } from "@utils"; |
||||
|
||||
import { |
||||
BaseOverlayEditor, |
||||
RectangleEditor, |
||||
PolygonEditor, |
||||
PolylineEditor, |
||||
CircleEditor, |
||||
} from "./editors"; |
||||
|
||||
import { registerHotkey } from "../utils/hotkeys"; |
||||
import { PolygonOptions, PolylineOptions, SelectedOptions } from "./constants"; |
||||
|
||||
import { Command, EventTypes, OverlayTypes } from "../types/enum"; |
||||
|
||||
type AMapOverlayEditor = |
||||
| AMap.RectangleEditor |
||||
| AMap.PolygonEditor |
||||
| AMap.PolylineEditor |
||||
| AMap.CircleEditor; |
||||
|
||||
type OverlayTemp = { |
||||
type: OverlayTypes; |
||||
target: AMap.MapOverlay; |
||||
}; |
||||
|
||||
export class MapEditor extends Emitter { |
||||
dom: HTMLDivElement; |
||||
private _map: AMap.Map | undefined; |
||||
private overlayEditors: BaseOverlayEditor<AMapOverlayEditor>[] = []; |
||||
private overlayMap: Record<string, OverlayTemp> = {}; |
||||
private _currentOverlayEditor: |
||||
| BaseOverlayEditor<AMapOverlayEditor> |
||||
| 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, |
||||
}); |
||||
} |
||||
|
||||
update(mapState: IMapState) { |
||||
if (this._editorStatus != null) { |
||||
this._finishEditOverlay(); |
||||
} |
||||
const { command } = mapState; |
||||
switch (command) { |
||||
case Command.CreateOverlay: |
||||
this._createOverlay(mapState); |
||||
break; |
||||
case Command.SelectOverlay: |
||||
this._selectOverlays(mapState.selectedIds); |
||||
break; |
||||
case Command.DeleteOverlays: |
||||
this._deleteOverlays(); |
||||
break; |
||||
case Command.OpenProject: |
||||
this.importOverlays(mapState); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
initEditors() { |
||||
const { map } = this; |
||||
this.overlayEditors = [ |
||||
new RectangleEditor(map), |
||||
new PolygonEditor(map), |
||||
new PolylineEditor(map), |
||||
new CircleEditor(map), |
||||
]; |
||||
} |
||||
|
||||
getEditorByType(overlayType: OverlayTypes) { |
||||
return this.overlayEditors.find( |
||||
(editor) => editor.getType() === overlayType |
||||
); |
||||
} |
||||
|
||||
importOverlays(mapState: IMapState) { |
||||
this.clearOverlays(); |
||||
const { rectangles, polygons, polylines, circles } = mapState; |
||||
const rectangleEditor = this.getEditorByType(OverlayTypes.Rectangle); |
||||
const polygonEditor = this.getEditorByType(OverlayTypes.Polygon); |
||||
const polylineEditor = this.getEditorByType(OverlayTypes.Polyline); |
||||
const circleEditor = this.getEditorByType(OverlayTypes.Circle); |
||||
|
||||
const addOverlay = ( |
||||
id: string, |
||||
target: AMap.MapOverlay, |
||||
type: OverlayTypes |
||||
) => { |
||||
this.overlayMap[id] = { |
||||
type, |
||||
target, |
||||
}; |
||||
this.map.add(target); |
||||
}; |
||||
|
||||
rectangles.forEach((rect) => |
||||
addOverlay(rect.id, rectangleEditor!.build(rect), OverlayTypes.Rectangle) |
||||
); |
||||
polygons.forEach((polygon) => |
||||
addOverlay( |
||||
polygon.id, |
||||
polygonEditor!.build(polygon), |
||||
OverlayTypes.Polygon |
||||
) |
||||
); |
||||
polylines.forEach((polyline) => |
||||
addOverlay( |
||||
polyline.id, |
||||
polylineEditor!.build(polyline), |
||||
OverlayTypes.Polyline |
||||
) |
||||
); |
||||
circles.forEach((circle) => |
||||
addOverlay(circle.id, circleEditor!.build(circle), OverlayTypes.Circle) |
||||
); |
||||
} |
||||
|
||||
// 清空所有覆盖物
|
||||
clearOverlays() { |
||||
for (let id in this.overlayMap) { |
||||
this.map.remove(this.overlayMap[id].target); |
||||
} |
||||
this.overlayMap = {}; |
||||
} |
||||
|
||||
_createOverlay(mapState: IMapState) { |
||||
const { overlayType } = mapState; |
||||
this._currentOverlayEditor = this.getEditorByType(overlayType!); |
||||
this._currentOverlayEditor?.create(); |
||||
this._editorStatus = "creating"; |
||||
} |
||||
|
||||
_selectOverlays(ids?: string[]) { |
||||
if (!ids?.length) 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]; |
||||
}); |
||||
} |
||||
|
||||
_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.overlayMap[id] = { type, target }; |
||||
} else { |
||||
id = this._selectedIds[0]; |
||||
} |
||||
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); |
||||
} |
||||
target.setOptions( |
||||
type === OverlayTypes.Polyline ? PolylineOptions : PolygonOptions |
||||
); |
||||
this.emit(EventTypes.FinishEditOverlay, evt); |
||||
this._editorStatus = null; |
||||
} |
||||
|
||||
_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"; |
||||
} |
||||
} |
||||
export * from "./MapEditor"; |
||||
export * from "./type"; |
||||
|
@ -0,0 +1,26 @@
|
||||
import { EventTypes, OverlayTypes } from "@types"; |
||||
|
||||
export interface IMapEditor { |
||||
on(type: EventTypes, evt: any): void; |
||||
init(): Promise<void>; |
||||
createOverlay(type: OverlayTypes): void; |
||||
importProject(options: IMapOptions): void; |
||||
importGeoJSON(geojson: GeoJSON.FeatureCollection): void; |
||||
selectOverlays(ids: string[]): void; |
||||
} |
||||
|
||||
export interface IOverlay { |
||||
id: string; |
||||
name: string; |
||||
type: OverlayTypes; |
||||
lngLat?: GeoJSON.Position; |
||||
path?: GeoJSON.Position[]; |
||||
radius?: number; |
||||
} |
||||
export interface IMapOptions { |
||||
polygons: IOverlay[]; |
||||
polylines: IOverlay[]; |
||||
circles: IOverlay[]; |
||||
rectangles: IOverlay[]; |
||||
selectedIds?: string[]; |
||||
} |
@ -1,3 +1,5 @@
|
||||
import { IEditorState } from "@store"; |
||||
|
||||
export const mapStateSelector = (state: IEditorState) => state.map; |
||||
export const mapOptionsSelector = (state: IEditorState) => state.map; |
||||
export const overlayTypeSelector = (state: IEditorState) => state.overlayType; |
||||
export const statusSelector = (state: IEditorState) => state.status; |
||||
|
Loading…
Reference in new issue