| New file |
| | |
| | | import * as Cesium from 'cesium' |
| | | import { MapTooltip } from '../Tooltip' |
| | | 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.show('拖动顶点编辑', { x: 0, y: 0 }) |
| | | } |
| | | |
| | | 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) { |
| | | 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 cartesian = scene.pickPosition(screenPosition) |
| | | 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 = [] |
| | | } |
| | | } |