diff --git a/src/apps/permits/index.ts b/src/apps/permits/index.ts index 9b9f2fd..a8c24ff 100644 --- a/src/apps/permits/index.ts +++ b/src/apps/permits/index.ts @@ -16,7 +16,7 @@ import '../../plugins/cesium/ngv-plugin-cesium-model-interact'; import '../../plugins/cesium/ngv-plugin-cesium-slicing'; import type {CesiumWidget, DataSourceCollection} from '@cesium/engine'; -import {PrimitiveCollection, CustomDataSource} from '@cesium/engine'; +import {PrimitiveCollection} from '@cesium/engine'; import type {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-cesium-widget.js'; @customElement('ngv-app-permits') @@ -27,7 +27,6 @@ export class NgvAppPermits extends ABaseApp { private uploadedModelsCollection: PrimitiveCollection = new PrimitiveCollection(); private dataSourceCollection: DataSourceCollection; - private slicingDataSource: CustomDataSource = new CustomDataSource(); private storeOptions = { localStoreKey: 'permits-localStoreModels', @@ -79,7 +78,7 @@ export class NgvAppPermits extends ABaseApp { ` : ''} @@ -92,9 +91,6 @@ export class NgvAppPermits extends ABaseApp { this.viewer.scene.primitives.add(this.uploadedModelsCollection); this.dataSourceCollection = evt.detail.dataSourceCollection; this.collections = evt.detail.primitiveCollections; - this.dataSourceCollection - .add(this.slicingDataSource) - .catch((e) => console.error(e)); }} > diff --git a/src/icons/rotate-icon.svg b/src/icons/rotate-icon.svg new file mode 100644 index 0000000..f596d3b --- /dev/null +++ b/src/icons/rotate-icon.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/src/plugins/cesium/draw.ts b/src/plugins/cesium/draw.ts index 1080d74..3e8151a 100644 --- a/src/plugins/cesium/draw.ts +++ b/src/plugins/cesium/draw.ts @@ -1,4 +1,8 @@ -import type {CesiumWidget, ConstantProperty} from '@cesium/engine'; +import type { + CesiumWidget, + ConstantProperty, + CustomDataSource, +} from '@cesium/engine'; import { PositionProperty, Entity, @@ -12,7 +16,6 @@ import { Cartographic, ClassificationType, Color, - CustomDataSource, HeightReference, Intersections2D, JulianDate, @@ -79,9 +82,9 @@ export class CesiumDraw extends EventTarget { private isDoubleClick = false; private singleClickTimer: NodeJS.Timeout | null = null; private segmentsInfo: SegmentInfo[] = []; + private julianDate = new JulianDate(); type: GeometryTypes | undefined; - julianDate = new JulianDate(); - drawingDataSource = new CustomDataSource('drawing'); + drawingDataSource: CustomDataSource; minPointsStop: boolean; moveEntity = false; entityForEdit: Entity | undefined; @@ -90,7 +93,11 @@ export class CesiumDraw extends EventTarget { // todo line options? lineClampToGround: boolean = true; - constructor(viewer: CesiumWidget, dataSource: CustomDataSource, options?: DrawOptions) { + constructor( + viewer: CesiumWidget, + dataSource: CustomDataSource, + options?: DrawOptions, + ) { super(); // todo move default values to constants this.viewer_ = viewer; @@ -208,7 +215,8 @@ export class CesiumDraw extends EventTarget { activateEditing(): void { if (!this.eventHandler_ || !this.entityForEdit) return; this.eventHandler_.setInputAction( - (event: ScreenSpaceEventHandler.PositionedEvent) => this.onLeftDown_(event), + (event: ScreenSpaceEventHandler.PositionedEvent) => + this.onLeftDown_(event), ScreenSpaceEventType.LEFT_DOWN, ); this.eventHandler_.setInputAction( @@ -223,11 +231,16 @@ export class CesiumDraw extends EventTarget { // todo // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - this.entityForEdit.position = new CallbackProperty(() => this.activePoints_[0] || position, false); + this.entityForEdit.position = new CallbackProperty( + () => this.activePoints_[0] || position, + false, + ); break; case 'line': positions = [ - ...(this.entityForEdit.polyline.positions.getValue(this.julianDate)) + ...(( + this.entityForEdit.polyline.positions.getValue(this.julianDate) + )), ]; this.entityForEdit.polyline.positions = new CallbackProperty( () => this.activePoints_, @@ -237,8 +250,9 @@ export class CesiumDraw extends EventTarget { break; case 'polygon': positions = [ - ...(this.entityForEdit.polygon.hierarchy.getValue(this.julianDate) - ).positions, + ...(( + this.entityForEdit.polygon.hierarchy.getValue(this.julianDate) + )).positions, ]; this.entityForEdit.polygon.hierarchy = new CallbackProperty( () => new PolygonHierarchy(this.activePoints_), @@ -248,8 +262,9 @@ export class CesiumDraw extends EventTarget { break; case 'rectangle': positions = [ - ...(this.entityForEdit.polygon.hierarchy.getValue(this.julianDate)) - .positions, + ...(( + this.entityForEdit.polygon.hierarchy.getValue(this.julianDate) + )).positions, ]; this.entityForEdit.polygon.hierarchy = new CallbackProperty( () => new PolygonHierarchy(this.activePoints_), @@ -267,7 +282,7 @@ export class CesiumDraw extends EventTarget { ); }, false), billboard: { - image: './images/rotate-icon.svg', + image: '../../icons/rotate-icon.svg', disableDepthTestDistance: Number.POSITIVE_INFINITY, heightReference: HeightReference.CLAMP_TO_GROUND, }, @@ -744,7 +759,8 @@ export class CesiumDraw extends EventTarget { prevRealSP, this.sketchPoint_, ); - this.sketchPoints_[prevRealSPIndex + 1].position = new ConstantPositionProperty(prevVirtualPosition); + this.sketchPoints_[prevRealSPIndex + 1].position = + new ConstantPositionProperty(prevVirtualPosition); const nextRealSPIndex = ((spLen + idx + 1) * 2) % spLen; const nextRealSP = this.sketchPoints_[nextRealSPIndex]; @@ -752,7 +768,8 @@ export class CesiumDraw extends EventTarget { nextRealSP, this.sketchPoint_, ); - this.sketchPoints_[idx * 2 + 1].position = new ConstantPositionProperty(nextVirtualPosition); + this.sketchPoints_[idx * 2 + 1].position = + new ConstantPositionProperty(nextVirtualPosition); } if (this.type === 'line') { // move virtual SPs @@ -763,9 +780,8 @@ export class CesiumDraw extends EventTarget { prevRealSP, this.sketchPoint_, ); - this.sketchPoints_[(idx - 1) * 2 + 1].position = new ConstantPositionProperty( - prevVirtualPosition - ); + this.sketchPoints_[(idx - 1) * 2 + 1].position = + new ConstantPositionProperty(prevVirtualPosition); } if (idx < this.activePoints_.length - 1) { const nextRealSP = this.sketchPoints_[(idx + 1) * 2]; @@ -773,9 +789,8 @@ export class CesiumDraw extends EventTarget { nextRealSP, this.sketchPoint_, ); - this.sketchPoints_[(idx + 1) * 2 - 1].position = new ConstantPositionProperty( - nextVirtualPosition - ); + this.sketchPoints_[(idx + 1) * 2 - 1].position = + new ConstantPositionProperty(nextVirtualPosition); } } else { const positions = this.activePoints_; @@ -890,10 +905,17 @@ export class CesiumDraw extends EventTarget { onLeftDown_(event: ScreenSpaceEventHandler.PositionedEvent): void { this.leftPressedPixel_ = Cartesian2.clone(event.position); if (this.entityForEdit) { - const objects: any[] = this.viewer_.scene.drillPick(event.position, 5, 5, 5); + const objects: any[] = this.viewer_.scene.drillPick( + event.position, + 5, + 5, + 5, + ); if (objects.length) { - const selectedPoint = <{id: Entity} | undefined>objects.find( - (obj: {id: Entity}) => !!obj.id.point || !!obj.id.billboard, + const selectedPoint = <{id: Entity} | undefined>( + objects.find( + (obj: {id: Entity}) => !!obj.id.point || !!obj.id.billboard, + ) ); if (!selectedPoint) return; const selectedEntity = selectedPoint.id; @@ -918,13 +940,19 @@ export class CesiumDraw extends EventTarget { } halfwayPosition_( - a: Entity | Cartesian3 | PositionProperty, - b: Entity | Cartesian3 | PositionProperty, + a: Entity | Cartesian3 | PositionProperty | ConstantPositionProperty, + b: Entity | Cartesian3 | PositionProperty | ConstantPositionProperty, ): Cartesian3 { a = a instanceof Entity ? a.position : a; b = b instanceof Entity ? b.position : b; - a = a instanceof PositionProperty ? a.getValue(this.julianDate) : a; - b = b instanceof PositionProperty ? b.getValue(this.julianDate) : b; + a = + a instanceof ConstantPositionProperty || a instanceof PositionProperty + ? a.getValue(this.julianDate) + : a; + b = + b instanceof ConstantPositionProperty || b instanceof PositionProperty + ? b.getValue(this.julianDate) + : b; const position = Cartesian3.add(a, b, new Cartesian3()); Cartesian3.divideByScalar(position, 2, position); return position; @@ -1221,7 +1249,7 @@ function rectanglify(coordinates: Cartesian3[]) { function triangulate(positions: Cartesian2[], holes: number[]): number[] { const flattenedPositions: number[] = Cartesian2.packArray(positions); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call + return earcut(flattenedPositions, holes, 2); } diff --git a/src/plugins/cesium/interactionHelpers.ts b/src/plugins/cesium/interactionHelpers.ts index fcc3487..0452edb 100644 --- a/src/plugins/cesium/interactionHelpers.ts +++ b/src/plugins/cesium/interactionHelpers.ts @@ -1,12 +1,12 @@ -import { +import type { Cesium3DTileset, - ClippingPolygonCollection, CustomDataSource, Globe, Model, PrimitiveCollection, Scene, } from '@cesium/engine'; +import {ClippingPolygonCollection} from '@cesium/engine'; import { ArcType, Axis, @@ -623,9 +623,9 @@ export function updateHeightForCartesianPositions( positions: Cartesian3[], height?: number, scene?: Scene, - assignBack: boolean = false + assignBack: boolean = false, ): Cartesian3[] { - return positions.map(p => { + return positions.map((p) => { const cartographicPosition = Cartographic.fromCartesian(p); if (typeof height === 'number' && !isNaN(height)) cartographicPosition.height = height; @@ -633,6 +633,8 @@ export function updateHeightForCartesianPositions( const altitude = scene.globe.getHeight(cartographicPosition) || 0; cartographicPosition.height += altitude; } - return assignBack ? Cartographic.toCartesian(cartographicPosition, Ellipsoid.WGS84, p) : Cartographic.toCartesian(cartographicPosition); + return assignBack + ? Cartographic.toCartesian(cartographicPosition, Ellipsoid.WGS84, p) + : Cartographic.toCartesian(cartographicPosition); }); } diff --git a/src/plugins/cesium/localStore.ts b/src/plugins/cesium/localStore.ts index 479f9e4..e0d2934 100644 --- a/src/plugins/cesium/localStore.ts +++ b/src/plugins/cesium/localStore.ts @@ -154,3 +154,26 @@ export function getStoredModels(storeKey: string): StoredModel[] { return []; } } + +export type StoredClipping = { + name: string; + positions: number[][]; + terrainClipping: boolean; + tilesClipping: boolean; +}; + +const CLIPPING_KEY = 'clipping-polygons'; + +export function updateClippingInLocalStore(clipping: StoredClipping[]): void { + localStorage.setItem(CLIPPING_KEY, JSON.stringify(clipping)); +} + +export function getStoredClipping(): StoredClipping[] { + if (!localStorage.getItem(CLIPPING_KEY)) return []; + try { + return JSON.parse(localStorage.getItem(CLIPPING_KEY)); + } catch (e) { + console.error('Not possible to parse clippings from local storage', e); + return []; + } +} diff --git a/src/plugins/cesium/ngv-cesium-factories.ts b/src/plugins/cesium/ngv-cesium-factories.ts index 33d2c99..09e57f3 100644 --- a/src/plugins/cesium/ngv-cesium-factories.ts +++ b/src/plugins/cesium/ngv-cesium-factories.ts @@ -11,7 +11,6 @@ import { HeadingPitchRoll, Transforms, Ellipsoid, - ClippingPolygon, } from '@cesium/engine'; import type { diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index 10e0385..3902800 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -468,6 +468,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { tilesClipping: this.chosenModel.id.tilesClipping, }, }}" + .showDone=${true} @clippingChange=${(evt: {detail: ClippingChangeDetail}) => { this.chosenModel.id.terrainClipping = evt.detail.terrainClipping; this.chosenModel.id.tilesClipping = evt.detail.tilesClipping; diff --git a/src/plugins/cesium/ngv-plugin-cesium-slicing.ts b/src/plugins/cesium/ngv-plugin-cesium-slicing.ts index b03eec2..80dd82c 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-slicing.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-slicing.ts @@ -1,30 +1,39 @@ import {customElement, property, state} from 'lit/decorators.js'; import {css, html, type HTMLTemplateResult, LitElement} from 'lit'; import type { - Cartesian3, Cesium3DTileset, CesiumWidget, - CustomDataSource, - Entity, + DataSourceCollection, PrimitiveCollection, } from '@cesium/engine'; -import {ClippingPolygon, ClippingPolygonCollection} from '@cesium/engine'; import { - Color, - ConstantProperty, - PolygonHierarchy, + Cartesian3, + ClippingPolygon, + ClippingPolygonCollection, + CustomDataSource, + Entity, + JulianDate, } from '@cesium/engine'; +import {Color, ConstantProperty, PolygonHierarchy} from '@cesium/engine'; import '../ui/ngv-layer-details.js'; import '../ui/ngv-layers-list.js'; import type {ClippingChangeDetail} from '../ui/ngv-layer-details.js'; -import type { DrawEndDetails} from './draw.js'; +import type {DrawEndDetails} from './draw.js'; import {CesiumDraw} from './draw.js'; +import {msg} from '@lit/localize'; +import type {StoredClipping} from './localStore.js'; +import {getStoredClipping, updateClippingInLocalStore} from './localStore.js'; export type ClippingData = { clipping: ClippingPolygon; entity: Entity; }; +export type ClippingEntityProps = { + terrainClipping: boolean; + tilesClipping: boolean; +}; + @customElement('ngv-plugin-cesium-slicing') export class NgvPluginCesiumSlicing extends LitElement { @property({type: Object}) @@ -32,13 +41,17 @@ export class NgvPluginCesiumSlicing extends LitElement { @property({type: Object}) private tiles3dCollection: PrimitiveCollection; @property({type: Object}) - private slicingDataSource: CustomDataSource; + private dataSourceCollection: DataSourceCollection; @state() private clippingPolygons: ClippingData[] = []; @state() private activePolygon: Entity | undefined = undefined; + @state() private editingClipping: ClippingData | undefined = undefined; private draw: CesiumDraw; + private slicingDataSource: CustomDataSource = new CustomDataSource(); + private drawDataSource: CustomDataSource = new CustomDataSource(); + private julianDate = new JulianDate(); static styles = css` button { @@ -70,40 +83,97 @@ export class NgvPluginCesiumSlicing extends LitElement { `; firstUpdated(): void { - this.draw = new CesiumDraw(this.viewer, this.slicingDataSource) - this.draw.type = 'polygon'; - this.draw.addEventListener('drawend', (e) => { - this.draw.active = false; - const details: DrawEndDetails = (>e).detail - const clippingPolygon = new ClippingPolygon({ - positions: details.positions, - }); - if (this.editingClipping) { - this.editingClipping.clipping = clippingPolygon; - this.applyClipping(this.editingClipping); - } else { - this.activePolygon.polygon.hierarchy = new ConstantProperty(new PolygonHierarchy(details.positions)); - const clipping = { - clipping: clippingPolygon, - entity: this.activePolygon, + this.dataSourceCollection + .add(this.slicingDataSource) + .then(() => { + const storedClipping = getStoredClipping(); + storedClipping.forEach((clipping) => { + const positions = clipping.positions.map( + (p) => new Cartesian3(p[0], p[1], p[2]), + ); + const entity = this.drawPolygon(positions, clipping.name, { + terrainClipping: clipping.terrainClipping, + tilesClipping: clipping.tilesClipping, + }); + const clippingData = { + entity, + clipping: new ClippingPolygon({positions}), + }; + this.clippingPolygons.push(clippingData); + this.applyClipping(clippingData); + }); + this.requestUpdate(); + }) + .catch((e) => console.error(e)); + this.dataSourceCollection + .add(this.drawDataSource) + .then((drawDataSource) => { + this.draw = new CesiumDraw(this.viewer, drawDataSource); + this.draw.type = 'polygon'; + this.draw.addEventListener('drawend', (e) => { + this.onDrawEnd((>e).detail); + }); + }) + .catch((e) => console.error(e)); + } + + saveToLocalStore(): void { + const clippingsToStore: StoredClipping[] = + this.slicingDataSource.entities.values.map((e) => { + const properties = ( + e.properties.getValue(this.julianDate) + ); + return { + name: e.name, + positions: (( + e.polygon.hierarchy.getValue(this.julianDate) + )).positions.map((p) => [p.x, p.y, p.z]), + terrainClipping: properties.terrainClipping, + tilesClipping: properties.tilesClipping, }; - this.clippingPolygons.push(clipping); - this.applyClipping(clipping); - } - this.activePolygon = undefined; - }) + }); + updateClippingInLocalStore(clippingsToStore); + } + + onDrawEnd(details: DrawEndDetails): void { + this.draw.active = false; + const clippingPolygon = new ClippingPolygon({ + positions: details.positions, + }); + if (this.editingClipping) { + this.editingClipping.clipping = clippingPolygon; + this.applyClipping(this.editingClipping); + } else { + this.activePolygon.polygon.hierarchy = new ConstantProperty( + new PolygonHierarchy(details.positions), + ); + const clipping = { + clipping: clippingPolygon, + entity: this.activePolygon, + }; + this.clippingPolygons.push(clipping); + this.applyClipping(clipping); + } + this.activePolygon = undefined; + this.saveToLocalStore(); } - drawPolygon(positions: Cartesian3[]): Entity { + drawPolygon( + positions?: Cartesian3[], + name?: string, + properties?: ClippingEntityProps, + ): Entity { const date = new Date(); return this.slicingDataSource.entities.add({ - name: `Polygon ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`, - // show: false, + name: + name || + `Polygon ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`, + show: false, polygon: { - hierarchy: new PolygonHierarchy(positions), + hierarchy: positions || [], material: Color.RED.withAlpha(0.7), }, - properties: { + properties: properties || { terrainClipping: true, tilesClipping: true, }, @@ -112,7 +182,7 @@ export class NgvPluginCesiumSlicing extends LitElement { addClippingPolygon(): void { this.draw.active = true; - this.activePolygon = this.drawPolygon([]) + this.activePolygon = this.drawPolygon(); } applyClipping(clippingData: ClippingData): void { @@ -182,8 +252,12 @@ export class NgvPluginCesiumSlicing extends LitElement { render(): HTMLTemplateResult | string { return html`
- ${this.draw?.active ? html` { this.activePolygon.properties.terrainClipping = new ConstantProperty(evt.detail.terrainClipping); @@ -213,12 +289,36 @@ export class NgvPluginCesiumSlicing extends LitElement { new ConstantProperty(evt.detail.tilesClipping); }} @done="${() => { - // todo + if (this.draw.entityForEdit) { + const positions: Cartesian3[] = (( + this.draw.entityForEdit.polygon.hierarchy.getValue( + this.julianDate, + ) + )).positions; + this.editingClipping.entity.polygon.hierarchy = + new ConstantProperty(new PolygonHierarchy(positions)); + this.editingClipping.clipping = new ClippingPolygon({ + positions, + }); + this.applyClipping(this.editingClipping); + this.draw.active = false; + this.draw.clear(); + this.requestUpdate(); + this.saveToLocalStore(); + } + }}" + @cancel="${() => { + if (this.draw?.entityForEdit) { + this.applyClipping(this.editingClipping); + } + this.draw.active = false; + this.draw.clear(); + this.requestUpdate(); }}" >` : html` Clipping 3D tiles +
@@ -99,16 +104,25 @@ export class NgvLayerDetails extends LitElement { .checked=${this.layer.clippingOptions.terrainClipping} @change=${() => this.onClippingChange()} /> - +
` : ''} + `; }