From dbf73b94d57890d418763438225adc4326d0a5d8 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Mon, 4 Mar 2024 14:59:16 +0100 Subject: [PATCH] feat: add multi line support --- demos/simple/demo.js | 8 +++ demos/simple/simple.html | 7 ++- demos/simple/style.js | 6 ++- package-lock.json | 4 +- package.json | 2 +- src/interaction/TrackData.ts | 8 +++ src/interaction/TrackInteraction.ts | 10 ++-- src/interaction/TrackInteractionModify.ts | 22 ++++++-- src/interaction/TrackManager.ts | 62 ++++++++++++++++++++--- src/interaction/TrackUpdater.ts | 6 ++- 10 files changed, 111 insertions(+), 24 deletions(-) diff --git a/demos/simple/demo.js b/demos/simple/demo.js index 1cb46fec..664daffc 100644 --- a/demos/simple/demo.js +++ b/demos/simple/demo.js @@ -126,6 +126,14 @@ function main() { } trackManager.addPOI(poiOverlay, onAddListener) }); + + document.querySelector('#createNewPart').addEventListener('click', () => { + trackManager.createNewPart(); + }); + document.querySelector('#changeActivePart').addEventListener('click', () => { + const nextPart = (trackManager.activePart() + 1) % trackManager.partsCount(); + trackManager.workOnPart(nextPart); + }); } main(); diff --git a/demos/simple/simple.html b/demos/simple/simple.html index 8deed1f8..2d86e3b8 100644 --- a/demos/simple/simple.html +++ b/demos/simple/simple.html @@ -56,7 +56,12 @@ - +
+
+ Add a new line string +
+ Change active line string +
diff --git a/demos/simple/style.js b/demos/simple/style.js index 5729a70a..fbada9fc 100644 --- a/demos/simple/style.js +++ b/demos/simple/style.js @@ -12,6 +12,8 @@ export const sketchControlPoint = { export const trackLine = { "stroke-width": 6, "stroke-color": "purple", + "text-value": ["concat", "", ["get", "part"]], + "text-fill-color": "#fff", }; export const trackLineModifying = { @@ -27,7 +29,7 @@ export const poiPoint = { "text-font": "bold 11px Inter", "text-fill-color": "#000", // use 'concat' to convert number to string - "text-value": ["concat", ["get", "index"], ""], + "text-value": ["concat", "", ["get", "part"]], }; export const numberedControlPoint = { @@ -35,7 +37,7 @@ export const numberedControlPoint = { "circle-fill-color": "#ffffffdd", "text-color": "blue", // use 'concat' to convert number to string - "text-value": ["concat", ["get", "index"], ""], + "text-value": ["concat", "", ["get", "part"]], }; export const snappedTrue = { diff --git a/package-lock.json b/package-lock.json index d27c48cd..91854ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@geoblocks/edittrack", - "version": "1.3.8", + "version": "2.0.0-beta.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@geoblocks/edittrack", - "version": "1.3.8", + "version": "2.0.0-beta.4", "license": "BSD-3-Clause", "devDependencies": { "@geoblocks/elevation-profile": "0.0.19", diff --git a/package.json b/package.json index b000bd13..15c28829 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@geoblocks/edittrack", - "version": "1.3.8", + "version": "2.0.0-beta.4", "description": "Geoblocks edittrack", "scripts": { "eslint": "eslint src test demos", diff --git a/src/interaction/TrackData.ts b/src/interaction/TrackData.ts index 253b84a0..d73899cb 100644 --- a/src/interaction/TrackData.ts +++ b/src/interaction/TrackData.ts @@ -31,10 +31,18 @@ interface DeletedControlPoint { } export default class TrackData { + private part_: number; private segments: Feature[] = []; private controlPoints: Feature[] = []; private pois: Feature[] = []; + constructor(part: number) { + this.part_ = part; + } + get part() { + return this.part_; + } + parseFeatures(features: Feature[]): ParsedFeatures { const parsed: ParsedFeatures = { segments: [], diff --git a/src/interaction/TrackInteraction.ts b/src/interaction/TrackInteraction.ts index a380dd39..362505e2 100644 --- a/src/interaction/TrackInteraction.ts +++ b/src/interaction/TrackInteraction.ts @@ -18,7 +18,6 @@ import {Point} from 'ol/geom'; export interface Options { map: Map; trackLayer: VectorLayer - trackData: TrackData style: StyleLike | FlatStyleLike /** @@ -62,6 +61,10 @@ export default class TrackInteraction extends Interaction { private modifyTrack_: Modify; private deletePoint_: Select; + setTrackData(trackData: TrackData) { + this.modifyTrack_.setTrackData(trackData); + } + controlPointOrPOIAtPixel(pixel: Pixel): Feature|false { return this.getMap().forEachFeatureAtPixel(pixel, (f) => { @@ -86,9 +89,8 @@ export default class TrackInteraction extends Interaction { return draw; } - createModifyInteraction(trackData: TrackData, source: VectorSource, style: StyleLike | FlatStyleLike, hitTolerance: number): Modify { + createModifyInteraction(source: VectorSource, style: StyleLike | FlatStyleLike, hitTolerance: number): Modify { const modify = new Modify({ - trackData: trackData, source: source, style: style, condition: (event) => !this.deleteCondition_(event), @@ -146,7 +148,7 @@ export default class TrackInteraction extends Interaction { source.on('removefeature', () => requestAnimationFrame(() => this.modifyTrack_.updateSketchFeature())); this.drawTrack_ = this.createDrawInteraction(source); - this.modifyTrack_ = this.createModifyInteraction(options.trackData, source, options.style, options.hitTolerance); + this.modifyTrack_ = this.createModifyInteraction(source, options.style, options.hitTolerance); this.deletePoint_ = this.createSelectInteraction(options.trackLayer); this.setActive(false); diff --git a/src/interaction/TrackInteractionModify.ts b/src/interaction/TrackInteractionModify.ts index de8e00da..3b2f069b 100644 --- a/src/interaction/TrackInteractionModify.ts +++ b/src/interaction/TrackInteractionModify.ts @@ -6,7 +6,7 @@ import LineString from 'ol/geom/LineString.js'; import Point from 'ol/geom/Point.js'; import Event from 'ol/events/Event.js'; import {Geometry} from 'ol/geom'; -import TrackData from './TrackData'; +import type TrackData from './TrackData'; import {Map, MapBrowserEvent} from 'ol'; import type {StyleLike} from 'ol/style/Style.js'; import type {FlatStyleLike} from 'ol/style/flat.js'; @@ -32,7 +32,6 @@ export class ModifyEvent extends Event { export interface Options { source: VectorSource; - trackData: TrackData; style: StyleLike | FlatStyleLike; condition: (mbe: MapBrowserEvent) => boolean; addControlPointCondition: (mbe: MapBrowserEvent) => boolean; @@ -65,7 +64,7 @@ export default class Modify extends PointerInteraction { }); private overlay_: VectorLayer>; private lastPixel_: Pixel = [0, 0]; - private trackData_: Options['trackData']; + private trackData_: TrackData; private pointAtCursorFeature_ = new Feature({ geometry: new Point([0, 0]), type: 'sketch', @@ -103,10 +102,16 @@ export default class Modify extends PointerInteraction { updateWhileAnimating: true, updateWhileInteracting: true }); + } - this.trackData_ = options.trackData; + setTrackData(trackData: TrackData) { + this.trackData_ = trackData; + this.overlayFeature.set('part', this.trackData_.part); } + featureInTrackData(feature: Feature | undefined) { + return feature?.get('part') === this.trackData_.part; + } setMap(map: Map) { this.overlay_.setMap(map); @@ -177,6 +182,11 @@ export default class Modify extends PointerInteraction { handleEvent(event: MapBrowserEvent): boolean { const stop = super.handleEvent(event); + // const feature = this.getFeatureAtPixel(event.pixel); + // if (feature?.get('part') !== this.trackData_.part) { + // return false; + // } + if (this.addControlPointCondition_(event)) { const feature = this.getFeatureAtPixel(event.pixel); if (feature && feature.get('type') === 'segment') { @@ -197,6 +207,10 @@ export default class Modify extends PointerInteraction { if (!this.feature_) { return false; } + if (!this.featureInTrackData(this.feature_)) { + this.feature_ = null; + return false; + } this.dragStarted = false; return true; } diff --git a/src/interaction/TrackManager.ts b/src/interaction/TrackManager.ts index f9662baf..8420ba29 100644 --- a/src/interaction/TrackManager.ts +++ b/src/interaction/TrackManager.ts @@ -1,3 +1,5 @@ +// FIXME: move pois outside of track data + import Feature from 'ol/Feature.js'; import Point from 'ol/geom/Point.js'; @@ -73,7 +75,7 @@ export default class TrackManager { private trackChangeEventListeners_: Function[] = []; // eslint-disable-next-line @typescript-eslint/ban-types private trackHoverEventListeners_: Function[] = []; - private trackData_ = new TrackData(); + private trackData_: TrackData; private router_: Router; get router(): Router { return this.router_; @@ -85,6 +87,7 @@ export default class TrackManager { private updater_: TrackUpdater; private interaction_: TrackInteraction; private historyManager_ = new HistoryManager[]>(); + private parts: TrackData[] = []; constructor(options: Options) { this.map_ = options.map; @@ -96,15 +99,14 @@ export default class TrackManager { this.router_ = options.router; this.profiler_ = options.profiler; + this.updater_ = new TrackUpdater({ profiler: this.profiler_, router: this.router_, - trackData: this.trackData_ }); this.interaction_ = new TrackInteraction({ style: options.style, - trackData: this.trackData_, trackLayer: this.trackLayer_, map: this.map_, deleteCondition: options.deleteCondition, @@ -113,6 +115,8 @@ export default class TrackManager { hitTolerance: this.hitTolerance_, }); + this.createNewPart(); + // Hack to test profile synchro // this.closestPointGeom_ = new Point([0, 0]); // this.interaction_.modifyTrack_.overlay_.getSource().addFeature(new Feature({ @@ -131,9 +135,12 @@ export default class TrackManager { if (!this.snapping) { feature.set('snapped', false); } + feature.set('part', this.trackData_.part); + // this is what we want: the new point is added to the current part const {pointFrom, pointTo, segment} = this.trackData_.pushControlPoint(feature); if (segment) { this.source_.addFeature(segment); + segment.set('part', this.trackData_.part); await this.router_.snapSegment(segment, pointFrom, pointTo); this.updater_.equalizeCoordinates(pointFrom); await this.profiler_.computeProfile(segment); @@ -145,6 +152,7 @@ export default class TrackManager { const debouncedMapToProfileUpdater = debounce( (coordinate: Coordinate, hover: boolean) => { + // FIXME multi lines: check this if (hover && this.trackData_.getSegments().length > 0) { const segments = this.trackData_.getSegments().map(feature => feature.get('profile')); const best = findClosestPointInLines(segments, coordinate, {tolerance: 1, interpolate: true}); @@ -176,22 +184,26 @@ export default class TrackManager { const type = event.feature.get('type') as FeatureType; if (type === 'POI') { - this.trackData_.updatePOIIndexes(); + // FIXME multi lines: check this + this.trackData_.updatePOIIndexes(); this.onTrackChanged_(); } else if (type === 'controlPoint') { const feature = event.feature as Feature; await this.updater_.updateAdjacentSegmentsGeometries(feature, this.snapping); this.updater_.changeAdjacentSegmentsStyling(feature, ''); await this.updater_.computeAdjacentSegmentsProfile(feature); + // FIXME multi lines: check this this.trackData_.updatePOIIndexes(); this.onTrackChanged_(); } else if (type === 'segment') { const feature = event.feature as Feature; + // FIXME multi lines: check this const indexOfSegment = this.trackData_.getSegments().indexOf(feature); console.assert(indexOfSegment >= 0); const controlPoint = new Feature({ - geometry: new Point(event.coordinate) + geometry: new Point(event.coordinate), + part: this.trackData_.part, }); this.source_.addFeature(controlPoint); const removed = this.trackData_.insertControlPointAt(controlPoint, indexOfSegment + 1); @@ -200,11 +212,14 @@ export default class TrackManager { const {before, after} = this.trackData_.getAdjacentSegments(controlPoint); console.assert(!!before && !!after); + before.set('part', this.trackData_.part); + after.set('part', this.trackData_.part); this.source_.addFeatures([before, after]); await this.updater_.updateAdjacentSegmentsGeometries(controlPoint, this.snapping); this.updater_.changeAdjacentSegmentsStyling(controlPoint, ''); await this.updater_.computeAdjacentSegmentsProfile(controlPoint); + // FIXME multi lines: check this this.trackData_.updatePOIIndexes(); this.onTrackChanged_(); } @@ -220,11 +235,13 @@ export default class TrackManager { console.assert(selected.getGeometry().getType() === 'Point'); const type = selected.get('type') as FeatureType; if (type === 'POI') { + // FIXME multi lines: check this this.trackData_.deletePOI(selected); this.source_.removeFeature(selected); this.onTrackChanged_(); } else { // control point + // FIXME multi lines: check this const {deleted, pointBefore, pointAfter, newSegment} = this.trackData_.deleteControlPoint(selected); // remove deleted features from source @@ -329,7 +346,8 @@ export default class TrackManager { deleteLastPoint() { if (this.mode_) { - if (this.trackData_.getControlPoints().length > 0) { + // FIXME multi lines: check this + if (this.trackData_.getControlPoints().length > 0) { const deletedFeatures = this.trackData_.deleteLastControlPoint(); deletedFeatures.forEach(feature => this.source_.removeFeature(feature)); this.onTrackChanged_(); @@ -362,6 +380,7 @@ export default class TrackManager { private clearInternal_() { this.source_.clear(); this.trackData_.clear(); + // FIXME multi lines: remove all parts ? } /** @@ -386,7 +405,6 @@ export default class TrackManager { } async restoreFeatures(features: Feature[]): Promise { - this.clearInternal_(); await this.restoreFeaturesInternal_(features); this.onTrackChanged_(); } @@ -572,4 +590,32 @@ export default class TrackManager { this.source_.changed(); this.shadowTrackLayer_.getSource().changed(); } -} \ No newline at end of file + + createNewPart(): number { + this.trackData_ = new TrackData(this.parts.length); + this.parts.push(this.trackData_); + this.updater_.setTrackData(this.trackData_); + this.interaction_.setTrackData(this.trackData_); + + return this.trackData_.part; + } + + activePart(): number { + return this.trackData_.part; + } + + partsCount(): number { + return this.parts.length; + } + + workOnPart(index: number) { + this.trackData_ = this.parts[index]; + this.updater_.setTrackData(this.trackData_); + this.interaction_.setTrackData(this.trackData_); + } + + getParts(): TrackData[] { + return this.parts; + } + +} diff --git a/src/interaction/TrackUpdater.ts b/src/interaction/TrackUpdater.ts index 2c9ccb7d..45a04253 100644 --- a/src/interaction/TrackUpdater.ts +++ b/src/interaction/TrackUpdater.ts @@ -6,7 +6,6 @@ import type {Profiler} from '../profiler/index'; import {equals} from 'ol/coordinate'; type TrackUpdaterOptions = { - trackData: TrackData; router: Router; profiler: Profiler; }; @@ -21,11 +20,14 @@ export default class TrackUpdater { private router: Router; constructor(options: TrackUpdaterOptions) { - this.trackData = options.trackData; this.profiler = options.profiler; this.router = options.router; } + setTrackData(trackData: TrackData) { + this.trackData = trackData; + } + computeAdjacentSegmentsProfile(modifiedControlPoint: Feature): Promise { const promises = []; if (modifiedControlPoint) {