| | |
| | | fill: style?.fill || DEFAULT_STYLE.fill, |
| | | outline: style?.outline || DEFAULT_STYLE.outline, |
| | | }) |
| | | const resolveColorWithAlpha = (baseColor, alphaSource) => { |
| | | if (!baseColor) return alphaSource |
| | | const alpha = typeof alphaSource?.alpha === 'number' ? alphaSource.alpha : 1 |
| | | return baseColor.withAlpha(alpha) |
| | | } |
| | | |
| | | const normalizeRing = points => { |
| | | if (!Array.isArray(points)) return [] |
| | | const ring = points.filter(Boolean) |
| | | if (ring.length < 2) return ring |
| | | const first = ring[0] |
| | | const last = ring[ring.length - 1] |
| | | if (Cesium.Cartesian3.equals(first, last)) { |
| | | return ring.slice(0, -1) |
| | | } |
| | | return ring |
| | | } |
| | | |
| | | const isSelfIntersecting = positions => { |
| | | const ring = normalizeRing(positions) |
| | | if (ring.length < 4) return false |
| | | const epsilon = 1e-12 |
| | | const toPoint = cartesian => { |
| | | const carto = Cesium.Cartographic.fromCartesian(cartesian) |
| | | return { x: carto.longitude, y: carto.latitude } |
| | | } |
| | | const pts = ring.map(toPoint) |
| | | const orientation = (a, b, c) => { |
| | | const value = (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y) |
| | | if (Math.abs(value) < epsilon) return 0 |
| | | return value > 0 ? 1 : 2 |
| | | } |
| | | const onSegment = (a, b, c) => |
| | | b.x <= Math.max(a.x, c.x) + epsilon && |
| | | b.x + epsilon >= Math.min(a.x, c.x) && |
| | | b.y <= Math.max(a.y, c.y) + epsilon && |
| | | b.y + epsilon >= Math.min(a.y, c.y) |
| | | const segmentsIntersect = (p1, q1, p2, q2) => { |
| | | const o1 = orientation(p1, q1, p2) |
| | | const o2 = orientation(p1, q1, q2) |
| | | const o3 = orientation(p2, q2, p1) |
| | | const o4 = orientation(p2, q2, q1) |
| | | if (o1 !== o2 && o3 !== o4) return true |
| | | if (o1 === 0 && onSegment(p1, p2, q1)) return true |
| | | if (o2 === 0 && onSegment(p1, q2, q1)) return true |
| | | if (o3 === 0 && onSegment(p2, p1, q2)) return true |
| | | if (o4 === 0 && onSegment(p2, q1, q2)) return true |
| | | return false |
| | | } |
| | | const count = pts.length |
| | | for (let i = 0; i < count; i += 1) { |
| | | const p1 = pts[i] |
| | | const q1 = pts[(i + 1) % count] |
| | | for (let j = i + 1; j < count; j += 1) { |
| | | const isAdjacent = j === i || j === i + 1 || (i === 0 && j === count - 1) |
| | | if (isAdjacent) continue |
| | | const p2 = pts[j] |
| | | const q2 = pts[(j + 1) % count] |
| | | if (segmentsIntersect(p1, q1, p2, q2)) return true |
| | | } |
| | | } |
| | | return false |
| | | } |
| | | |
| | | export class DrawPolygonTool extends ToolBase { |
| | | constructor(viewer, options = {}) { |
| | |
| | | this.floatPosition = null |
| | | this.isDrawing = false |
| | | this.lastMousePosition = null |
| | | this.isIntersecting = false |
| | | this.style = resolveStyle(options?.style) |
| | | } |
| | | |
| | | start() { |
| | | if (!this.tooltip) { |
| | | this.tooltip = new MapTooltip(this.viewer) |
| | | } |
| | | this.dataSource = new Cesium.CustomDataSource('draw-polygon') |
| | | this.viewer.dataSources.add(this.dataSource) |
| | | this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) |
| | |
| | | this.handler.setInputAction(click => this.handleRightClick(click), Cesium.ScreenSpaceEventType.RIGHT_CLICK) |
| | | this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE) |
| | | this.isDrawing = true |
| | | this.tooltip.show(this.getTipText(), { x: 0, y: 0 }) |
| | | this.tooltip.hide() |
| | | } |
| | | |
| | | handleLeftClick(click) { |
| | |
| | | |
| | | const position = this.getPositionFromScreen(click.position) |
| | | if (!position) return |
| | | |
| | | const preview = [...this.positions, position] |
| | | if (isSelfIntersecting(preview)) { |
| | | this.isIntersecting = true |
| | | this.tooltip.show(this.getTipText(), click.position) |
| | | return |
| | | } |
| | | |
| | | this.addPoint(position) |
| | | this.tooltip.show(this.getTipText(), click.position) |
| | |
| | | |
| | | handleMouseMove(movement) { |
| | | if (!this.isDrawing) return |
| | | this.tooltip.show(this.getTipText(), movement.endPosition) |
| | | const tipText = this.getTipText() |
| | | if (!this.tooltip?.isVisible) { |
| | | this.tooltip?.show(tipText, movement.endPosition) |
| | | } else { |
| | | this.tooltip?.show(tipText) |
| | | this.tooltip?.move(movement.endPosition) |
| | | } |
| | | if (this.positions.length === 0) return |
| | | const position = this.getPositionFromScreen(movement.endPosition) |
| | | if (!position) return |
| | |
| | | } |
| | | |
| | | getTipText() { |
| | | const preview = this.getPreviewPositions() |
| | | this.isIntersecting = isSelfIntersecting(preview) |
| | | if (this.isIntersecting) { |
| | | return '区域存在交叉,请调整位置' |
| | | } |
| | | const count = this.positions.length |
| | | if (count === 0) { |
| | | return '单击增加点' |
| | |
| | | } |
| | | } |
| | | |
| | | getPreviewPositions() { |
| | | if (this.positions.length === 0) return [] |
| | | if (!this.isDrawing || !this.floatPosition) return this.positions |
| | | return [...this.positions, this.floatPosition] |
| | | } |
| | | |
| | | createEntities() { |
| | | const getPreviewPositions = () => { |
| | | if (this.positions.length === 0) return [] |
| | | if (!this.isDrawing || !this.floatPosition) return this.positions |
| | | return [...this.positions, this.floatPosition] |
| | | const getIntersecting = () => { |
| | | const preview = this.getPreviewPositions() |
| | | this.isIntersecting = isSelfIntersecting(preview) |
| | | return this.isIntersecting |
| | | } |
| | | |
| | | this.polygonEntity = this.dataSource.entities.add({ |
| | | polygon: { |
| | | hierarchy: new Cesium.CallbackProperty(() => new Cesium.PolygonHierarchy(getPreviewPositions()), false), |
| | | material: this.style.fill, |
| | | hierarchy: new Cesium.CallbackProperty( |
| | | () => new Cesium.PolygonHierarchy(this.getPreviewPositions()), |
| | | false |
| | | ), |
| | | material: new Cesium.ColorMaterialProperty( |
| | | new Cesium.CallbackProperty(() => { |
| | | if (getIntersecting()) return resolveColorWithAlpha(Cesium.Color.RED, this.style.fill) |
| | | return this.style.fill |
| | | }, false) |
| | | ), |
| | | outline: false, |
| | | heightReference: Cesium.HeightReference.CLAMP_TO_GROUND, |
| | | show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 3, false), |
| | | show: new Cesium.CallbackProperty(() => this.getPreviewPositions().length >= 3, false), |
| | | }, |
| | | }) |
| | | |
| | | this.polylineEntity = this.dataSource.entities.add({ |
| | | polyline: { |
| | | positions: new Cesium.CallbackProperty(() => { |
| | | const positions = getPreviewPositions() |
| | | const positions = this.getPreviewPositions() |
| | | if (positions.length < 2) return positions |
| | | if (positions.length >= 3) { |
| | | return [...positions, positions[0]] |
| | |
| | | }, false), |
| | | clampToGround: true, |
| | | width: 2, |
| | | material: this.style.outline, |
| | | show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 2, false), |
| | | material: new Cesium.ColorMaterialProperty( |
| | | new Cesium.CallbackProperty(() => { |
| | | if (getIntersecting()) return resolveColorWithAlpha(Cesium.Color.RED, this.style.outline) |
| | | return this.style.outline |
| | | }, false) |
| | | ), |
| | | show: new Cesium.CallbackProperty(() => this.getPreviewPositions().length >= 2, false), |
| | | }, |
| | | }) |
| | | } |
| | |
| | | if (this.positions.length < 3) return |
| | | this.isDrawing = false |
| | | this.floatPosition = null |
| | | this.isIntersecting = false |
| | | this.notify('getPolygonPositions', this.positions) |
| | | this.tooltip.hide() |
| | | this.tooltip?.hide() |
| | | this.clearPreviewEntities() |
| | | } |
| | | |
| | | setStyle(style) { |
| | | this.style = resolveStyle(style) |
| | | if (this.polygonEntity?.polygon) { |
| | | this.polygonEntity.polygon.material = this.style.fill |
| | | } |
| | | if (this.polylineEntity?.polyline) { |
| | | this.polylineEntity.polyline.material = this.style.outline |
| | | } |
| | | if (this.dataSource) { |
| | | this.dataSource.entities.values |
| | | .filter(entity => entity?.name === POINT_ENTITY_NAME) |
| | |
| | | this.dataSource.entities.removeAll() |
| | | this.polygonEntity = null |
| | | this.polylineEntity = null |
| | | this.isIntersecting = false |
| | | } |
| | | |
| | | getPositionFromScreen(screenPosition) { |
| | | const scene = this.viewer.scene |
| | | const cartesian = scene.pickPosition(screenPosition) |
| | | 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) |
| | | } |
| | |
| | | this.floatPosition = null |
| | | this.isDrawing = false |
| | | this.lastMousePosition = null |
| | | this.isIntersecting = false |
| | | } |
| | | } |