Skip to content

Commit 43d7fee

Browse files
committed
feat: add multi line support
1 parent a0a46de commit 43d7fee

File tree

10 files changed

+111
-24
lines changed

10 files changed

+111
-24
lines changed

demos/simple/demo.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ function main() {
126126
}
127127
trackManager.addPOI(poiOverlay, onAddListener)
128128
});
129+
130+
document.querySelector('#createNewPart').addEventListener('click', () => {
131+
trackManager.createNewPart();
132+
});
133+
document.querySelector('#changeActivePart').addEventListener('click', () => {
134+
const nextPart = (trackManager.activePart() + 1) % trackManager.partsCount();
135+
trackManager.workOnPart(nextPart);
136+
});
129137
}
130138

131139
main();

demos/simple/simple.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@
5656
<button id="poiCancel">Cancel</button>
5757
<button id="poiSave">Save</button>
5858
</div>
59-
</div>
59+
<br />
60+
<br />
61+
<a href="#" id="createNewPart">Add a new line string</a>
62+
<br />
63+
<a href="#" id="changeActivePart">Change active line string</a>
64+
</div>
6065
<div id="map"></div>
6166
</main>
6267
</body>

demos/simple/style.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const sketchControlPoint = {
1212
export const trackLine = {
1313
"stroke-width": 6,
1414
"stroke-color": "purple",
15+
"text-value": ["concat", "", ["get", "part"]],
16+
"text-fill-color": "#fff",
1517
};
1618

1719
export const trackLineModifying = {
@@ -27,15 +29,15 @@ export const poiPoint = {
2729
"text-font": "bold 11px Inter",
2830
"text-fill-color": "#000",
2931
// use 'concat' to convert number to string
30-
"text-value": ["concat", ["get", "index"], ""],
32+
"text-value": ["concat", "", ["get", "part"]],
3133
};
3234

3335
export const numberedControlPoint = {
3436
...controlPoint,
3537
"circle-fill-color": "#ffffffdd",
3638
"text-color": "blue",
3739
// use 'concat' to convert number to string
38-
"text-value": ["concat", ["get", "index"], ""],
40+
"text-value": ["concat", "", ["get", "part"]],
3941
};
4042

4143
export const snappedTrue = {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@geoblocks/edittrack",
3-
"version": "1.3.7",
3+
"version": "2.0.0-beta.3",
44
"description": "Geoblocks edittrack",
55
"scripts": {
66
"eslint": "eslint src test demos",

src/interaction/TrackData.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,18 @@ interface DeletedControlPoint {
3131
}
3232

3333
export default class TrackData {
34+
private part_: number;
3435
private segments: Feature<LineString>[] = [];
3536
private controlPoints: Feature<Point>[] = [];
3637
private pois: Feature<Point>[] = [];
3738

39+
constructor(part: number) {
40+
this.part_ = part;
41+
}
42+
get part() {
43+
return this.part_;
44+
}
45+
3846
parseFeatures(features: Feature<Point|LineString>[]): ParsedFeatures {
3947
const parsed: ParsedFeatures = {
4048
segments: [],

src/interaction/TrackInteraction.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {Point} from 'ol/geom';
1818
export interface Options {
1919
map: Map;
2020
trackLayer: VectorLayer<VectorSource>
21-
trackData: TrackData
2221
style: StyleLike | FlatStyleLike
2322

2423
/**
@@ -62,6 +61,10 @@ export default class TrackInteraction extends Interaction {
6261
private modifyTrack_: Modify;
6362
private deletePoint_: Select;
6463

64+
setTrackData(trackData: TrackData) {
65+
this.modifyTrack_.setTrackData(trackData);
66+
}
67+
6568
controlPointOrPOIAtPixel(pixel: Pixel): Feature<Point>|false {
6669
return this.getMap().forEachFeatureAtPixel(pixel,
6770
(f) => {
@@ -86,9 +89,8 @@ export default class TrackInteraction extends Interaction {
8689
return draw;
8790
}
8891

89-
createModifyInteraction(trackData: TrackData, source: VectorSource, style: StyleLike | FlatStyleLike, hitTolerance: number): Modify {
92+
createModifyInteraction(source: VectorSource, style: StyleLike | FlatStyleLike, hitTolerance: number): Modify {
9093
const modify = new Modify({
91-
trackData: trackData,
9294
source: source,
9395
style: style,
9496
condition: (event) => !this.deleteCondition_(event),
@@ -146,7 +148,7 @@ export default class TrackInteraction extends Interaction {
146148
source.on('removefeature', () => requestAnimationFrame(() => this.modifyTrack_.updateSketchFeature()));
147149

148150
this.drawTrack_ = this.createDrawInteraction(source);
149-
this.modifyTrack_ = this.createModifyInteraction(options.trackData, source, options.style, options.hitTolerance);
151+
this.modifyTrack_ = this.createModifyInteraction(source, options.style, options.hitTolerance);
150152
this.deletePoint_ = this.createSelectInteraction(options.trackLayer);
151153

152154
this.setActive(false);

src/interaction/TrackInteractionModify.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import LineString from 'ol/geom/LineString.js';
66
import Point from 'ol/geom/Point.js';
77
import Event from 'ol/events/Event.js';
88
import {Geometry} from 'ol/geom';
9-
import TrackData from './TrackData';
9+
import type TrackData from './TrackData';
1010
import {Map, MapBrowserEvent} from 'ol';
1111
import type {StyleLike} from 'ol/style/Style.js';
1212
import type {FlatStyleLike} from 'ol/style/flat.js';
@@ -32,7 +32,6 @@ export class ModifyEvent extends Event {
3232

3333
export interface Options {
3434
source: VectorSource<FeatureLike>;
35-
trackData: TrackData;
3635
style: StyleLike | FlatStyleLike;
3736
condition: (mbe: MapBrowserEvent<UIEvent>) => boolean;
3837
addControlPointCondition: (mbe: MapBrowserEvent<UIEvent>) => boolean;
@@ -65,7 +64,7 @@ export default class Modify extends PointerInteraction {
6564
});
6665
private overlay_: VectorLayer<VectorSource<Feature>>;
6766
private lastPixel_: Pixel = [0, 0];
68-
private trackData_: Options['trackData'];
67+
private trackData_: TrackData;
6968
private pointAtCursorFeature_ = new Feature<Point>({
7069
geometry: new Point([0, 0]),
7170
type: 'sketch',
@@ -103,10 +102,16 @@ export default class Modify extends PointerInteraction {
103102
updateWhileAnimating: true,
104103
updateWhileInteracting: true
105104
});
105+
}
106106

107-
this.trackData_ = options.trackData;
107+
setTrackData(trackData: TrackData) {
108+
this.trackData_ = trackData;
109+
this.overlayFeature.set('part', this.trackData_.part);
108110
}
109111

112+
featureInTrackData(feature: Feature<Geometry> | undefined) {
113+
return feature?.get('part') === this.trackData_.part;
114+
}
110115

111116
setMap(map: Map) {
112117
this.overlay_.setMap(map);
@@ -177,6 +182,11 @@ export default class Modify extends PointerInteraction {
177182

178183
handleEvent(event: MapBrowserEvent<UIEvent>): boolean {
179184
const stop = super.handleEvent(event);
185+
// const feature = this.getFeatureAtPixel(event.pixel);
186+
// if (feature?.get('part') !== this.trackData_.part) {
187+
// return false;
188+
// }
189+
180190
if (this.addControlPointCondition_(event)) {
181191
const feature = this.getFeatureAtPixel(event.pixel);
182192
if (feature && feature.get('type') === 'segment') {
@@ -197,6 +207,10 @@ export default class Modify extends PointerInteraction {
197207
if (!this.feature_) {
198208
return false;
199209
}
210+
if (!this.featureInTrackData(this.feature_)) {
211+
this.feature_ = null;
212+
return false;
213+
}
200214
this.dragStarted = false;
201215
return true;
202216
}

src/interaction/TrackManager.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// FIXME: move pois outside of track data
2+
13
import Feature from 'ol/Feature.js';
24
import Point from 'ol/geom/Point.js';
35

@@ -73,7 +75,7 @@ export default class TrackManager<POIMeta> {
7375
private trackChangeEventListeners_: Function[] = [];
7476
// eslint-disable-next-line @typescript-eslint/ban-types
7577
private trackHoverEventListeners_: Function[] = [];
76-
private trackData_ = new TrackData();
78+
private trackData_: TrackData;
7779
private router_: Router;
7880
get router(): Router {
7981
return this.router_;
@@ -85,6 +87,7 @@ export default class TrackManager<POIMeta> {
8587
private updater_: TrackUpdater;
8688
private interaction_: TrackInteraction;
8789
private historyManager_ = new HistoryManager<Feature<Point|LineString>[]>();
90+
private parts: TrackData[] = [];
8891

8992
constructor(options: Options) {
9093
this.map_ = options.map;
@@ -96,15 +99,14 @@ export default class TrackManager<POIMeta> {
9699

97100
this.router_ = options.router;
98101
this.profiler_ = options.profiler;
102+
99103
this.updater_ = new TrackUpdater({
100104
profiler: this.profiler_,
101105
router: this.router_,
102-
trackData: this.trackData_
103106
});
104107

105108
this.interaction_ = new TrackInteraction({
106109
style: options.style,
107-
trackData: this.trackData_,
108110
trackLayer: this.trackLayer_,
109111
map: this.map_,
110112
deleteCondition: options.deleteCondition,
@@ -113,6 +115,8 @@ export default class TrackManager<POIMeta> {
113115
hitTolerance: this.hitTolerance_,
114116
});
115117

118+
this.createNewPart();
119+
116120
// Hack to test profile synchro
117121
// this.closestPointGeom_ = new Point([0, 0]);
118122
// this.interaction_.modifyTrack_.overlay_.getSource().addFeature(new Feature({
@@ -131,9 +135,12 @@ export default class TrackManager<POIMeta> {
131135
if (!this.snapping) {
132136
feature.set('snapped', false);
133137
}
138+
feature.set('part', this.trackData_.part);
139+
// this is what we want: the new point is added to the current part
134140
const {pointFrom, pointTo, segment} = this.trackData_.pushControlPoint(feature);
135141
if (segment) {
136142
this.source_.addFeature(segment);
143+
segment.set('part', this.trackData_.part);
137144
await this.router_.snapSegment(segment, pointFrom, pointTo);
138145
this.updater_.equalizeCoordinates(pointFrom);
139146
await this.profiler_.computeProfile(segment);
@@ -145,6 +152,7 @@ export default class TrackManager<POIMeta> {
145152

146153
const debouncedMapToProfileUpdater = debounce(
147154
(coordinate: Coordinate, hover: boolean) => {
155+
// FIXME multi lines: check this
148156
if (hover && this.trackData_.getSegments().length > 0) {
149157
const segments = this.trackData_.getSegments().map(feature => feature.get('profile'));
150158
const best = findClosestPointInLines(segments, coordinate, {tolerance: 1, interpolate: true});
@@ -176,22 +184,26 @@ export default class TrackManager<POIMeta> {
176184
const type = event.feature.get('type') as FeatureType;
177185

178186
if (type === 'POI') {
179-
this.trackData_.updatePOIIndexes();
187+
// FIXME multi lines: check this
188+
this.trackData_.updatePOIIndexes();
180189
this.onTrackChanged_();
181190
} else if (type === 'controlPoint') {
182191
const feature = event.feature as Feature<Point>;
183192
await this.updater_.updateAdjacentSegmentsGeometries(feature, this.snapping);
184193
this.updater_.changeAdjacentSegmentsStyling(feature, '');
185194
await this.updater_.computeAdjacentSegmentsProfile(feature);
195+
// FIXME multi lines: check this
186196
this.trackData_.updatePOIIndexes();
187197
this.onTrackChanged_();
188198
} else if (type === 'segment') {
189199
const feature = event.feature as Feature<LineString>;
200+
// FIXME multi lines: check this
190201
const indexOfSegment = this.trackData_.getSegments().indexOf(feature);
191202

192203
console.assert(indexOfSegment >= 0);
193204
const controlPoint = new Feature({
194-
geometry: new Point(event.coordinate)
205+
geometry: new Point(event.coordinate),
206+
part: this.trackData_.part,
195207
});
196208
this.source_.addFeature(controlPoint);
197209
const removed = this.trackData_.insertControlPointAt(controlPoint, indexOfSegment + 1);
@@ -200,11 +212,14 @@ export default class TrackManager<POIMeta> {
200212

201213
const {before, after} = this.trackData_.getAdjacentSegments(controlPoint);
202214
console.assert(!!before && !!after);
215+
before.set('part', this.trackData_.part);
216+
after.set('part', this.trackData_.part);
203217
this.source_.addFeatures([before, after]);
204218

205219
await this.updater_.updateAdjacentSegmentsGeometries(controlPoint, this.snapping);
206220
this.updater_.changeAdjacentSegmentsStyling(controlPoint, '');
207221
await this.updater_.computeAdjacentSegmentsProfile(controlPoint);
222+
// FIXME multi lines: check this
208223
this.trackData_.updatePOIIndexes();
209224
this.onTrackChanged_();
210225
}
@@ -220,11 +235,13 @@ export default class TrackManager<POIMeta> {
220235
console.assert(selected.getGeometry().getType() === 'Point');
221236
const type = selected.get('type') as FeatureType;
222237
if (type === 'POI') {
238+
// FIXME multi lines: check this
223239
this.trackData_.deletePOI(selected);
224240
this.source_.removeFeature(selected);
225241
this.onTrackChanged_();
226242
} else {
227243
// control point
244+
// FIXME multi lines: check this
228245
const {deleted, pointBefore, pointAfter, newSegment} = this.trackData_.deleteControlPoint(selected);
229246

230247
// remove deleted features from source
@@ -329,7 +346,8 @@ export default class TrackManager<POIMeta> {
329346

330347
deleteLastPoint() {
331348
if (this.mode_) {
332-
if (this.trackData_.getControlPoints().length > 0) {
349+
// FIXME multi lines: check this
350+
if (this.trackData_.getControlPoints().length > 0) {
333351
const deletedFeatures = this.trackData_.deleteLastControlPoint();
334352
deletedFeatures.forEach(feature => this.source_.removeFeature(feature));
335353
this.onTrackChanged_();
@@ -364,6 +382,7 @@ export default class TrackManager<POIMeta> {
364382
private clearInternal_() {
365383
this.source_.clear();
366384
this.trackData_.clear();
385+
// FIXME multi lines: remove all parts ?
367386
}
368387

369388
/**
@@ -388,7 +407,6 @@ export default class TrackManager<POIMeta> {
388407
}
389408

390409
async restoreFeatures(features: Feature<Point|LineString>[]): Promise<void> {
391-
this.clearInternal_();
392410
await this.restoreFeaturesInternal_(features);
393411
this.onTrackChanged_();
394412
}
@@ -574,4 +592,32 @@ export default class TrackManager<POIMeta> {
574592
this.source_.changed();
575593
this.shadowTrackLayer_.getSource().changed();
576594
}
577-
}
595+
596+
createNewPart(): number {
597+
this.trackData_ = new TrackData(this.parts.length);
598+
this.parts.push(this.trackData_);
599+
this.updater_.setTrackData(this.trackData_);
600+
this.interaction_.setTrackData(this.trackData_);
601+
602+
return this.trackData_.part;
603+
}
604+
605+
activePart(): number {
606+
return this.trackData_.part;
607+
}
608+
609+
partsCount(): number {
610+
return this.parts.length;
611+
}
612+
613+
workOnPart(index: number) {
614+
this.trackData_ = this.parts[index];
615+
this.updater_.setTrackData(this.trackData_);
616+
this.interaction_.setTrackData(this.trackData_);
617+
}
618+
619+
getParts(): TrackData[] {
620+
return this.parts;
621+
}
622+
623+
}

0 commit comments

Comments
 (0)