import * as Cesium from 'cesium' import { MapTooltip } from '@ztzf/utils' import { ToolBase } from '../ToolBase' import { getBoundsFromLngLats, rectanglePositionsFromBounds } from '../shapeUtils' const DEFAULT_STYLE = { fill: Cesium.Color.fromBytes(45, 140, 240, 99), outline: Cesium.Color.fromBytes(45, 140, 240, 255), } const resolveStyle = style => ({ fill: style?.fill || DEFAULT_STYLE.fill, outline: style?.outline || DEFAULT_STYLE.outline, }) const normalizePoints = points => (points || []).map(point => ({ lng: point.lng ?? point.longitude, lat: point.lat ?? point.latitude, })) export class EditRectangleTool extends ToolBase { constructor(viewer, options = {}) { super(viewer) this.tooltip = new MapTooltip(viewer) this.dataSource = null this.bounds = null this.rectangleEntity = null this.outlineEntity = null this.cornerEntities = [] this.dragIndex = null this.fixedCorner = null this.style = resolveStyle(options?.style) } start(points = []) { const normalized = normalizePoints(points) this.bounds = getBoundsFromLngLats(normalized) if (!this.bounds) return this.dataSource = new Cesium.CustomDataSource('edit-rectangle') this.viewer.dataSources.add(this.dataSource) this.createEntities() this.initHandler() } initHandler() { this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN) this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE) this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP) this.tooltip.hide() } handleLeftDown(evt) { const picked = this.viewer.scene.pick(evt.position)?.id if (!picked || picked.customType !== 'rect-corner') return this.dragIndex = picked.customData.index const corners = this.getCornerCartographics() this.fixedCorner = corners[(this.dragIndex + 2) % 4] this.disableMapControl() } handleMouseMove(evt) { if (!this.tooltip?.isVisible) { this.tooltip.show('拖动顶点编辑', evt.endPosition) } else { this.tooltip.show('拖动顶点编辑') this.tooltip.move(evt.endPosition) } if (this.dragIndex == null) return const position = this.getPositionFromScreen(evt.endPosition) if (!position) return const moved = Cesium.Cartographic.fromCartesian(position) this.bounds = { west: Math.min(moved.longitude, this.fixedCorner.longitude), east: Math.max(moved.longitude, this.fixedCorner.longitude), south: Math.min(moved.latitude, this.fixedCorner.latitude), north: Math.max(moved.latitude, this.fixedCorner.latitude), } this.updateCorners() } handleLeftUp() { if (this.dragIndex == null) return this.dragIndex = null this.fixedCorner = null this.enableMapControl() const positions = rectanglePositionsFromBounds(this.bounds) this.notify('getPolygonPositions', positions) } createEntities() { const hierarchy = new Cesium.CallbackProperty(() => { const positions = rectanglePositionsFromBounds(this.bounds) return new Cesium.PolygonHierarchy(positions) }, false) this.rectangleEntity = this.dataSource.entities.add({ polygon: { hierarchy, material: this.style.fill, outline: false, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, }, }) this.outlineEntity = this.dataSource.entities.add({ polyline: { positions: new Cesium.CallbackProperty(() => { const positions = rectanglePositionsFromBounds(this.bounds) return positions.length ? [...positions, positions[0]] : [] }, false), clampToGround: true, width: 2, material: this.style.outline, }, }) this.createCorners() } createCorners() { const positions = rectanglePositionsFromBounds(this.bounds) this.cornerEntities = positions.map((position, index) => this.dataSource.entities.add({ position, point: { pixelSize: 12, color: Cesium.Color.WHITE, heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, }, customType: 'rect-corner', customData: { index }, }) ) } updateCorners() { const positions = rectanglePositionsFromBounds(this.bounds) this.cornerEntities.forEach((entity, index) => { entity.position = positions[index] }) } getPositions() { return rectanglePositionsFromBounds(this.bounds) } getCornerCartographics() { return rectanglePositionsFromBounds(this.bounds).map(position => Cesium.Cartographic.fromCartesian(position)) } getPositionFromScreen(screenPosition) { const scene = this.viewer.scene const ray = scene.camera.getPickRay(screenPosition) const cartesian = ray ? scene.globe.pick(ray, scene) : null if (cartesian) return cartesian return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid) } setStyle(style) { this.style = resolveStyle(style) if (this.rectangleEntity?.polygon) { this.rectangleEntity.polygon.material = this.style.fill } if (this.outlineEntity?.polyline) { this.outlineEntity.polyline.material = this.style.outline } } disableMapControl() { const controller = this.viewer.scene.screenSpaceCameraController controller.enableRotate = false controller.enableTranslate = false controller.enableZoom = false } enableMapControl() { const controller = this.viewer.scene.screenSpaceCameraController controller.enableRotate = true controller.enableTranslate = true controller.enableZoom = true } destroy() { if (this.dataSource) { this.dataSource.entities.removeAll() this.viewer.dataSources.remove(this.dataSource) this.dataSource = null } if (this.handler) { this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN) this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE) this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP) this.handler.destroy() this.handler = null } this.tooltip?.destroy() this.tooltip = null this.bounds = null this.dragIndex = null this.fixedCorner = null this.cornerEntities = [] } }