Skip to content

Commit 26a097d

Browse files
committed
feat: add multi line support
1 parent e35d4fc commit 26a097d

File tree

10 files changed

+112
-27
lines changed

10 files changed

+112
-27
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.6",
3+
"version": "2.0.0-beta.1",
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: 19 additions & 8 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,11 +64,8 @@ export default class Modify extends PointerInteraction {
6564
});
6665
private overlay_: VectorLayer<VectorSource<Feature>>;
6766
private lastPixel_ = [0, 0];
68-
private trackData_: Options['trackData'];
69-
/**
70-
* @type {Feature<Point>}
71-
*/
72-
private pointAtCursorFeature_ = new Feature({
67+
private trackData_: TrackData;
68+
private pointAtCursorFeature_ = new Feature<Point>({
7369
geometry: new Point([0, 0]),
7470
type: 'sketch',
7571
subtype: '',
@@ -106,10 +102,16 @@ export default class Modify extends PointerInteraction {
106102
updateWhileAnimating: true,
107103
updateWhileInteracting: true
108104
});
105+
}
109106

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

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

114116
setMap(map: Map) {
115117
this.overlay_.setMap(map);
@@ -182,6 +184,11 @@ export default class Modify extends PointerInteraction {
182184

183185
handleEvent(event: MapBrowserEvent<UIEvent>): boolean {
184186
const stop = super.handleEvent(event);
187+
// const feature = this.getFeatureAtPixel(event.pixel);
188+
// if (feature?.get('part') !== this.trackData_.part) {
189+
// return false;
190+
// }
191+
185192
if (this.addControlPointCondition_(event)) {
186193
const feature = this.getFeatureAtPixel(event.pixel);
187194
if (feature && feature.get('type') === 'segment') {
@@ -202,6 +209,10 @@ export default class Modify extends PointerInteraction {
202209
if (!this.feature_) {
203210
return false;
204211
}
212+
if (!this.featureInTrackData(this.feature_)) {
213+
this.feature_ = null;
214+
return false;
215+
}
205216
this.dragStarted = false;
206217
return true;
207218
}

src/interaction/TrackManager.ts

Lines changed: 54 additions & 7 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});
@@ -175,22 +183,26 @@ export default class TrackManager<POIMeta> {
175183
const type = event.feature.get('type') as FeatureType;
176184

177185
if (type === 'POI') {
178-
this.trackData_.updatePOIIndexes();
186+
// FIXME multi lines: check this
187+
this.trackData_.updatePOIIndexes();
179188
this.onTrackChanged_();
180189
} else if (type === 'controlPoint') {
181190
const feature = event.feature as Feature<Point>;
182191
await this.updater_.updateAdjacentSegmentsGeometries(feature, this.snapping);
183192
this.updater_.changeAdjacentSegmentsStyling(feature, '');
184193
await this.updater_.computeAdjacentSegmentsProfile(feature);
194+
// FIXME multi lines: check this
185195
this.trackData_.updatePOIIndexes();
186196
this.onTrackChanged_();
187197
} else if (type === 'segment') {
188198
const feature = event.feature as Feature<LineString>;
199+
// FIXME multi lines: check this
189200
const indexOfSegment = this.trackData_.getSegments().indexOf(feature);
190201

191202
console.assert(indexOfSegment >= 0);
192203
const controlPoint = new Feature({
193-
geometry: new Point(event.coordinate)
204+
geometry: new Point(event.coordinate),
205+
part: this.trackData_.part,
194206
});
195207
this.source_.addFeature(controlPoint);
196208
const removed = this.trackData_.insertControlPointAt(controlPoint, indexOfSegment + 1);
@@ -199,11 +211,14 @@ export default class TrackManager<POIMeta> {
199211

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

204218
await this.updater_.updateAdjacentSegmentsGeometries(controlPoint, this.snapping);
205219
this.updater_.changeAdjacentSegmentsStyling(controlPoint, '');
206220
await this.updater_.computeAdjacentSegmentsProfile(controlPoint);
221+
// FIXME multi lines: check this
207222
this.trackData_.updatePOIIndexes();
208223
this.onTrackChanged_();
209224
}
@@ -218,11 +233,13 @@ export default class TrackManager<POIMeta> {
218233
console.assert(selected.getGeometry().getType() === 'Point');
219234
const type = selected.get('type') as FeatureType;
220235
if (type === 'POI') {
236+
// FIXME multi lines: check this
221237
this.trackData_.deletePOI(selected);
222238
this.source_.removeFeature(selected);
223239
this.onTrackChanged_();
224240
} else {
225241
// control point
242+
// FIXME multi lines: check this
226243
const {deleted, pointBefore, pointAfter, newSegment} = this.trackData_.deleteControlPoint(selected);
227244

228245
// remove deleted features from source
@@ -327,7 +344,8 @@ export default class TrackManager<POIMeta> {
327344

328345
deleteLastPoint() {
329346
if (this.mode_) {
330-
if (this.trackData_.getControlPoints().length > 0) {
347+
// FIXME multi lines: check this
348+
if (this.trackData_.getControlPoints().length > 0) {
331349
const deletedFeatures = this.trackData_.deleteLastControlPoint();
332350
deletedFeatures.forEach(feature => this.source_.removeFeature(feature));
333351
this.onTrackChanged_();
@@ -362,6 +380,7 @@ export default class TrackManager<POIMeta> {
362380
private clearInternal_() {
363381
this.source_.clear();
364382
this.trackData_.clear();
383+
// FIXME multi lines: remove all parts ?
365384
}
366385

367386
/**
@@ -568,4 +587,32 @@ export default class TrackManager<POIMeta> {
568587
this.source_.changed();
569588
this.shadowTrackLayer_.getSource().changed();
570589
}
571-
}
590+
591+
createNewPart(): number {
592+
this.trackData_ = new TrackData(this.parts.length);
593+
this.parts.push(this.trackData_);
594+
this.updater_.setTrackData(this.trackData_);
595+
this.interaction_.setTrackData(this.trackData_);
596+
597+
return this.trackData_.part;
598+
}
599+
600+
activePart(): number {
601+
return this.trackData_.part;
602+
}
603+
604+
partsCount(): number {
605+
return this.parts.length;
606+
}
607+
608+
workOnPart(index: number) {
609+
this.trackData_ = this.parts[index];
610+
this.updater_.setTrackData(this.trackData_);
611+
this.interaction_.setTrackData(this.trackData_);
612+
}
613+
614+
getParts(): TrackData[] {
615+
return this.parts;
616+
}
617+
618+
}

0 commit comments

Comments
 (0)