diff --git a/examples/index.ts b/examples/index.ts index dde4025..cb14b7e 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,8 +1,14 @@ import CesiumPlot from "../src"; +// import CesiumPlot from "../dist/CesiumPlot"; import * as Cesium from "./cesium/index"; -let raster = new Cesium.ArcGisMapServerImageryProvider({ - url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer", +// let raster = new Cesium.ArcGisMapServerImageryProvider({ +// url: "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer", +// }); + +let keda = new Cesium.UrlTemplateImageryProvider({ + url: "https://10.68.8.41:9043/kmap-server/gridMap/tile/{z}/{y}/{x}", + // url: 'http://10.68.8.58:8080/3d/dom2/{z}/{x}/{y}.png' }); const viewer = new Cesium.Viewer("cesiumContainer", { animation: false, @@ -16,7 +22,8 @@ const viewer = new Cesium.Viewer("cesiumContainer", { timeline: false, navigationHelpButton: false, baseLayerPicker: false, - imageryProvider: raster, + // imageryProvider: raster, + imageryProvider: keda, contextOptions: { requestWebgl2: true, }, diff --git a/src/arrow/fine-arrow.ts b/src/arrow/fine-arrow.ts index 09022cf..344f386 100644 --- a/src/arrow/fine-arrow.ts +++ b/src/arrow/fine-arrow.ts @@ -8,32 +8,33 @@ export default class FineArrow extends Draw { constructor(cesium: any, viewer: any, style: any) { super(cesium, viewer); this.cesium = cesium; - this.onClick(); + this.setState('drawing'); } /** * Add points only on click events */ addPoint(cartesian: Cartesian3) { - this.points.push(cartesian); - if (this.points.length === 1) { + this.points.push(); + if (this.points.length === 0) { + this.points[0] = cartesian; this.onMouseMove(); } if (this.points.length === 2) { const geometryPoints = this.createPolygon(this.points); this.setGeometryPoints(geometryPoints); this.addToMap(); - this.removeClickListener(); this.removeMoveListener(); + this.setState('static'); } } /** * Update the last point as the mouse moves. */ - updateMovingPoint(cartesian: Cartesian3) { - let tempPoints = [...this.points, cartesian]; - const geometryPoints = this.createPolygon(tempPoints); + updateMovingPoint(cartesian: Cartesian3, index: number) { + this.points[index] = cartesian; + const geometryPoints = this.createPolygon(this.points); this.setGeometryPoints(geometryPoints); this.addToMap(); } @@ -57,4 +58,8 @@ export default class FineArrow extends Draw { const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(points); return cartesianPoints; } + + getPoints() { + return this.points; + } } diff --git a/src/assets/circle_red.png b/src/assets/circle_red.png new file mode 100644 index 0000000..653ce8f Binary files /dev/null and b/src/assets/circle_red.png differ diff --git a/src/draw.ts b/src/draw.ts index da49d85..791d22a 100644 --- a/src/draw.ts +++ b/src/draw.ts @@ -1,5 +1,6 @@ import * as Utils from './utils'; import * as CesiumTypeOnly from '../examples/cesium'; +import { State } from './interface'; export default class Draw { cesium: typeof CesiumTypeOnly; @@ -13,7 +14,10 @@ export default class Draw { neckAngle: number; eventHandler: CesiumTypeOnly.ScreenSpaceEventHandler; entity: CesiumTypeOnly.Entity; - geometryPoints: CesiumTypeOnly.Cartesian3[] | undefined; + geometryPoints: CesiumTypeOnly.Cartesian3[] = []; + state: State = 'drawing'; + controlPoints: CesiumTypeOnly.EntityCollection; + controlPointsEventHandler: CesiumTypeOnly.ScreenSpaceEventHandler; constructor(cesium: typeof CesiumTypeOnly, viewer: CesiumTypeOnly.Viewer) { this.cesium = cesium; @@ -25,30 +29,74 @@ export default class Draw { this.neckAngle = Math.PI / 13; this.cartesianToLnglat = this.cartesianToLnglat.bind(this); this.pixelToCartesian = this.pixelToCartesian.bind(this); + this.onClick(); } + /** + * The base class provides a method to change the state, and different logic is implemented based on the state. + * The state is controlled by individual sub-components according to the actual situation. + * @param state + */ + setState(state: State) { + this.state = state; + } + + getState(): State { + return this.state; + } + + /** + * Bind a global click event that responds differently based on the state. When in the drawing state, + * a click will add points for geometric shapes. During editing, selecting a drawn shape puts it in an + * editable state. Clicking on empty space sets it to a static state. + */ onClick() { this.eventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas); this.eventHandler.setInputAction((evt) => { - const cartesian = this.pixelToCartesian(evt.position); - this.addPoint(cartesian); + const pickedObject = this.viewer.scene.pick(evt.position); + const hitEntities = this.cesium.defined(pickedObject) && pickedObject.id instanceof CesiumTypeOnly.Entity; + if (this.state === 'drawing') { + // In the drawing state, the points clicked are key nodes of the shape, and they are saved in this.points. + const cartesian = this.pixelToCartesian(evt.position); + this.addPoint(cartesian); + } else if (this.state === 'edit') { + //In edit mode, exiting the edit state and deleting control points when clicking in the blank area. + if (!hitEntities) { + this.setState('static'); + this.removeControlPoints(); + } + } else if (this.state === 'static') { + if (hitEntities) { + const pickedEntity = pickedObject.id; + if (this.cesium.defined(pickedEntity.polygon)) { + // Hit PolygonGraphics geometry. + this.setState('edit'); + this.addControlPoints(); + } + } + } }, this.cesium.ScreenSpaceEventType.LEFT_CLICK); } onMouseMove() { this.eventHandler.setInputAction((evt) => { - const cartesian = this.pixelToCartesian(evt.endPosition); - const lnglat = this.cartesianToLnglat(cartesian); - const lastPoint = this.cartesianToLnglat(this.points[this.points.length - 1]); - const distance = Utils.MathDistance(lnglat, lastPoint); - if (distance < 0.0001) { - return false; - } - // Synchronize data to subclasses. - this.updateMovingPoint(cartesian); + this.drawingWhileMoving(evt.endPosition, 1); }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE); } + drawingWhileMoving(position: CesiumTypeOnly.Cartesian2, index: number) { + const cartesian = this.pixelToCartesian(position); + const lnglat = this.cartesianToLnglat(cartesian); + const points = this.getPoints(); + const lastPoint = this.cartesianToLnglat(points[points.length - 1]); + const distance = Utils.MathDistance(lnglat, lastPoint); + if (distance < 0.0001) { + return false; + } + // Synchronize data to subclasses. + this.updateMovingPoint(cartesian, index); + } + removeClickListener() { this.eventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_CLICK); } @@ -61,6 +109,10 @@ export default class Draw { this.geometryPoints = geometryPoints; } + getGeometryPoints(): CesiumTypeOnly.Cartesian3[] { + return this.geometryPoints; + } + addToMap() { const callback = () => { return new this.cesium.PolygonHierarchy(this.geometryPoints); @@ -89,4 +141,81 @@ export default class Draw { const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene); return cartesian; } + + /** + * Display key points when creating a shape, allowing dragging of these points to edit and generate new shapes. + */ + addControlPoints() { + const points = this.getPoints(); + this.controlPoints = points.map((position) => { + return this.viewer.entities.add({ + position, + billboard: { + image: './src/assets/circle_red.png', + }, + }); + }); + + let isDragging = false; + let draggedIcon: CesiumTypeOnly.Entity = null; + + this.controlPointsEventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas); + // Listen for left mouse button press events + this.controlPointsEventHandler.setInputAction((clickEvent) => { + const pickedObject = this.viewer.scene.pick(clickEvent.position); + + if (this.cesium.defined(pickedObject)) { + for (let i = 0; i < this.controlPoints.length; i++) { + if (pickedObject.id === this.controlPoints[i]) { + isDragging = true; + draggedIcon = this.controlPoints[i]; + draggedIcon.index = i; //Save the index of dragged points for dynamic updates during movement + break; + } + } + // Disable default camera interaction. + this.viewer.scene.screenSpaceCameraController.enableRotate = false; + } + }, this.cesium.ScreenSpaceEventType.LEFT_DOWN); + + // Listen for mouse movement events + this.controlPointsEventHandler.setInputAction((moveEvent) => { + if (isDragging && draggedIcon) { + const cartesian = this.viewer.camera.pickEllipsoid(moveEvent.endPosition, this.viewer.scene.globe.ellipsoid); + if (cartesian) { + draggedIcon.position.setValue(cartesian); + this.drawingWhileMoving(moveEvent.endPosition, draggedIcon.index); + } + } + }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE); + + // Listen for left mouse button release events + this.controlPointsEventHandler.setInputAction(() => { + isDragging = false; + draggedIcon = null; + this.viewer.scene.screenSpaceCameraController.enableRotate = true; + }, this.cesium.ScreenSpaceEventType.LEFT_UP); + } + + removeControlPoints() { + this.controlPoints.forEach((entity: CesiumTypeOnly.Entity) => { + this.viewer.entities.remove(entity); + }); + this.controlPointsEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_DOWN); + this.controlPointsEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.MOUSE_MOVE); + this.controlPointsEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_UP); + } + + addPoint(cartesian: CesiumTypeOnly.Cartesian3) { + //Abstract method that must be implemented by subclasses. + } + + getPoints(): CesiumTypeOnly.Cartesian3[] { + //Abstract method that must be implemented by subclasses. + return this.cesium.Cartesian3(); + } + + updateMovingPoint(cartesian: CesiumTypeOnly.Cartesian3, index: number) { + //Abstract method that must be implemented by subclasses. + } } diff --git a/src/interface.ts b/src/interface.ts new file mode 100644 index 0000000..1ae5ef4 --- /dev/null +++ b/src/interface.ts @@ -0,0 +1 @@ +export type State = 'drawing' | 'edit' | 'static';