Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.2.0 - Added geometry editing #2

Merged
merged 4 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ map.addInteraction(interaction);
- `waySource?: VectorSource<Feature<LineString>>` - Ways source for snapping (default to a new instance of OSMOverpassWaySource)
- `createAndAddWayLayer?: boolean` - Create a new way layer from way source (if provided) and add to map (default: true)
- `wrapX?: boolean` - WrapX
- `allowCreate?: boolean` - Whether to allow creating a new feature (default: true)
- `allowEdit?: boolean` - Whether allow geometry edition to start when clicking on an existing feature, the option can be later configured in allowEdit property of OSMWaySnap class (default: true)

If `waySource` is not provided, `OSMOverpass` will be used as source for snapping, so the constructor options for `OSMWaySnap` will be extended to include [thoses options from `OSMOverpassSourceBase`](https://github.com/ponlawat-w/ol-osmoverpass#constructor-options).

Expand Down
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ol-osmwaysnap",
"type": "module",
"version": "0.1.3",
"version": "0.2.0",
"description": "OpenLayers Interaction Extension for Snapping Ways from OSM using Overpass API",
"keywords": [
"osm",
Expand Down Expand Up @@ -41,6 +41,6 @@
"webpack-cli": "^4.10.0"
},
"dependencies": {
"ol-osmoverpass": "^0.2.0"
"ol-osmoverpass": "^0.2.1"
}
}
148 changes: 118 additions & 30 deletions src/interaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ type SnapOptions = {
createAndAddWayLayer?: boolean,

/** WrapX */
wrapX?: boolean
wrapX?: boolean,

/** Whether to allow creating a new feature (default: true) */
allowCreate?: boolean,

/** Whether allow geometry edition to start when clicking on an existing feature, the option can be later configured in allowEdit property of OSMWaySnap class (default: true) */
allowEdit?: boolean
};

type SnapOptionsOSMOverpassWaySource = Omit<SnapOptions, 'waySource'> & {
Expand All @@ -48,6 +54,15 @@ export type OSMWaySnapOptions = SnapOptions | SnapOptionsOSMOverpassWaySource;
* This is designed to use with Snap interaction.
*/
export default class OSMWaySnap extends PointerInteraction {
/** Whether to allow geometry edition to start when clicking on an existing feature */
public allowEdit: boolean = true;
/** Whether to allow creating a new feature */
public allowCreate: boolean = true
/** True to automatically fit map view to next candidantes. */
public autoFocus: boolean = true;
/** Used with autoFocus, specify number to add padding to view fitting. */
public focusPadding: number = 50;

/** Coordinates of active linestring */
private coordinates: Coordinate[] = [];
/** Feature that is being edited */
Expand All @@ -59,23 +74,19 @@ export default class OSMWaySnap extends PointerInteraction {
private sketchPoint?: Feature<Point> = undefined;
/** Sketch point for placeholder on map */
private sketchLine?: Feature<LineString> = undefined;
/** Partial line of the original geometry from active feature that will be either removed or merged by editing */
private draftOriginalLine?: Feature<LineString> = undefined;
/** Candidate points for selection on map */
private candidatePoints: Feature<Point>[] = [];
/** Candidate lines for selection on map */
private candidateLines: Feature<LineString>[] = [];
/** Indicate if sketchLine is merging to draft original line */
private mergingDraftOriginalLine: boolean = false;

/** True to automatically fit map view to next candidantes. */
private autoFocus: boolean;
/** Used with autoFocus, specify number to add padding to view fitting. */
private focusPadding: number;
/** Target source of edition */
private source: VectorSource<Feature<LineString>>;
/** Ways source for snapping */
private waySource: VectorSource<Feature<LineString>>;
/** Create a new way layer from way source (if provided) and add to map */
private createAndAddWayLayer: boolean;
/** WrapX */
private wrapX: boolean|undefined = undefined;

/** Map */
private map: Map|undefined = undefined;
Expand All @@ -91,8 +102,10 @@ export default class OSMWaySnap extends PointerInteraction {
*/
public constructor(options: OSMWaySnapOptions) {
super();
this.autoFocus = options.autoFocus === undefined ? true: options.autoFocus;
this.focusPadding = options.focusPadding ?? 50;
if (options.allowCreate !== undefined) this.allowCreate = options.allowCreate;
if (options.allowEdit !== undefined) this.allowEdit = options.allowEdit;
if (options.autoFocus !== undefined) this.autoFocus = options.autoFocus;
if (options.focusPadding !== undefined) this.focusPadding = options.focusPadding;
this.source = options.source;
this.waySource = options.waySource ?? new OSMOverpassWaySource({
cachedFeaturesCount: options.cachedFeaturesCount ?? undefined,
Expand All @@ -101,17 +114,15 @@ export default class OSMWaySnap extends PointerInteraction {
overpassQuery: options.overpassQuery ?? '(way["highway"];>;);',
overpassEndpointURL: options.overpassEndpointURL ?? undefined
});
this.createAndAddWayLayer = options.createAndAddWayLayer === undefined ? true : options.createAndAddWayLayer;
this.wrapX = options.wrapX;
this.snapInteraction = new Snap({ source: this.waySource });

if (this.createAndAddWayLayer) {
if (options.createAndAddWayLayer ?? true) {
this.wayLayer = new VectorLayer({ source: this.waySource, style: OSMOverpassWaySource.getDefaultStyle() });
}
this.overlayLayer = new VectorLayer({
source: new VectorSource({ useSpatialIndex: false, wrapX: this.wrapX }),
source: new VectorSource({ useSpatialIndex: false, wrapX: options.wrapX ?? true }),
updateWhileInteracting: true,
style: options.sketchStyle ?? OSMWaySnap.getDefaultSketchStyle()
style: options.sketchStyle ?? OSMWaySnap.getDefaultOverlayStyle()
});

this.addChangeListener('active', this.activeChanged.bind(this));
Expand Down Expand Up @@ -163,12 +174,20 @@ export default class OSMWaySnap extends PointerInteraction {

/** Called when the editing is finished, clear all the sketching and candidates. */
public finishEditing() {
if (this.autoFocus && this.map && this.activeFeature && this.coordinates.length > 1) {
this.map.getView().fit(this.activeFeature.getGeometry()!.getExtent(), { padding: Array(4).fill(this.focusPadding) });
}
this.activeFeature = undefined;
this.coordinates = [];
this.removeSketchPoint();
this.removeSketchLine();
this.removeCandidateLines();
this.removeCandidatePoints();
if (this.draftOriginalLine && this.waySource.hasFeature(this.draftOriginalLine)) {
this.waySource.removeFeature(this.draftOriginalLine);
}
this.draftOriginalLine = undefined;
this.mergingDraftOriginalLine = false;
}

/**
Expand All @@ -181,9 +200,20 @@ export default class OSMWaySnap extends PointerInteraction {
public handleEvent(event: MapBrowserEvent<MouseEvent>): boolean {
if (event.type !== 'click') return super.handleEvent(event);
if (!this.coordinates.length) {
this.coordinates = [this.sketchPoint?.getGeometry()!.getCoordinates() ?? event.coordinate];
this.updateFeature();
return false;
if (this.allowEdit) {
const overlappedFeatures = this.source.getFeatures().filter(f => f.getGeometry()?.intersectsCoordinate(event.coordinate) ?? false);
if (overlappedFeatures.length) {
this.enterEditMode(overlappedFeatures[0], event.coordinate);
this.updateFeature();
return false;
}
}
if (this.allowCreate) {
this.coordinates = [this.sketchPoint?.getGeometry()!.getCoordinates() ?? event.coordinate];
this.updateFeature();
return false;
}
return super.handleEvent(event);
}
const lastCoors = this.activeFeature!.getGeometry()!.getLastCoordinate();
if (lastCoors[0] === event.coordinate[0] && lastCoors[1] === event.coordinate[1]) {
Expand All @@ -193,6 +223,7 @@ export default class OSMWaySnap extends PointerInteraction {
if (this.sketchLine) {
this.coordinates.push(...this.sketchLine.getGeometry()!.getCoordinates().slice(1));
this.updateFeature();
if (this.mergingDraftOriginalLine) this.finishEditing();
return false;
}
return super.handleEvent(event);
Expand All @@ -210,6 +241,27 @@ export default class OSMWaySnap extends PointerInteraction {
return super.handleMoveEvent(event);
}

/**
* Start editing on the selected feature
* @param feature Feature to edit
* @param fromCoordinate Coordinate on feature geometry to starts altering, the original vertices after this coordinate will go to draft in overlay layer
*/
private enterEditMode(feature: Feature<LineString>, fromCoordinate: Coordinate) {
const originalCoordinates = feature.getGeometry()!.getCoordinates();
const fromCoordinateIdx = originalCoordinates.findIndex(c => c[0] === fromCoordinate[0] && c[1] === fromCoordinate[1]);
this.coordinates = originalCoordinates.slice(0, fromCoordinateIdx + 1);
this.draftOriginalLine = new Feature({
draft: true,
geometry: new LineString(
originalCoordinates.slice(fromCoordinateIdx)
)
});
this.waySource.addFeature(this.draftOriginalLine);
feature.getGeometry()!.setCoordinates(this.coordinates);
this.activeFeature = feature;
this.mergingDraftOriginalLine = false;
}

/**
* Create or update active feature from editing coordinates.
*/
Expand Down Expand Up @@ -268,11 +320,12 @@ export default class OSMWaySnap extends PointerInteraction {
}

/**
* Update the sketch layer displayed.
* Update the overlay layer.
*/
private updateSketchLayer() {
private updateOverlayLayer() {
const source = this.overlayLayer.getSource()!;
source.clear(true);
if (this.draftOriginalLine) source.addFeature(this.draftOriginalLine);
if (this.sketchLine) source.addFeature(this.sketchLine);
source.addFeatures(this.candidateLines);
source.addFeatures(this.candidatePoints);
Expand Down Expand Up @@ -328,12 +381,34 @@ export default class OSMWaySnap extends PointerInteraction {
* @param coordinate Coordinate
*/
private createOrUpdateSketchPoint(coordinate: Coordinate) {
if (!this.allowCreate && !this.activeFeature) {
return this.removeSketchPoint();
}
if (this.sketchPoint) {
this.sketchPoint.getGeometry()!.setCoordinates(coordinate);
} else {
this.sketchPoint = new Feature(new Point(coordinate));
}
this.updateSketchLayer();
this.updateOverlayLayer();
}

/**
* This method alters array in place.
* If the last coordinate is on draftOriginalLine, extend it from the last coordinate until the remaining of the draftOriginalLine
* @param coordinates Original sketch line coordinates to get extended
*/
private extendSketchLineCoorsToDraftOriginal(coordinates: Coordinate[]) {
const lastCoordinate = coordinates[coordinates.length - 1];
if (!this.draftOriginalLine || !this.draftOriginalLine.getGeometry()!.intersectsCoordinate(lastCoordinate)) return;
let lineToExtend = this.draftOriginalLine.getGeometry()!;
let idx = this.draftOriginalLine.getGeometry()!.getCoordinates()
.findIndex(c => c[0] === lastCoordinate[0] && c[1] === lastCoordinate[1]);
if (idx < 0) {
lineToExtend = LineStringUtils.split(lineToExtend, lastCoordinate);
}
const extendFromIdx = lineToExtend.getCoordinates().findIndex(c => c[0] === lastCoordinate[0] && c[1] === lastCoordinate[1]);
coordinates.push(...lineToExtend.getCoordinates().slice(extendFromIdx + 1));
this.mergingDraftOriginalLine = true;
}

/**
Expand All @@ -359,38 +434,40 @@ export default class OSMWaySnap extends PointerInteraction {
return this.removeSketchLine();
}

if (this.allowEdit) {
this.extendSketchLineCoorsToDraftOriginal(sketchCoors);
}

if (this.sketchLine) {
this.sketchLine.getGeometry()!.setCoordinates(sketchCoors);
} else {
this.sketchLine = new Feature(new LineString(sketchCoors));
}
this.updateSketchLayer();
this.updateOverlayLayer();
}

/** Remove sketch point. */
private removeSketchPoint() {
if (!this.sketchPoint) return;
this.sketchPoint = undefined;
this.updateSketchLayer();
this.updateOverlayLayer();
}

/** Remove sketch line. */
private removeSketchLine() {
if (!this.sketchLine) return;
this.sketchLine = undefined;
this.updateSketchLayer();
this.updateOverlayLayer();
}

/** Remove all the candidate lines. */
private removeCandidateLines() {
this.candidateLines = [];
this.updateSketchLayer();
this.updateOverlayLayer();
}

/** Remove all the candidate points. */
private removeCandidatePoints() {
this.candidatePoints = [];
this.updateSketchLayer();
this.updateOverlayLayer();
}

/**
Expand All @@ -401,7 +478,11 @@ export default class OSMWaySnap extends PointerInteraction {
this.calculateCandidates(false);
}

private static getDefaultSketchStyle(): StyleLike {
/**
*
* @returns
*/
private static getDefaultOverlayStyle(): StyleLike {
return feature => {
if (feature.getProperties().candidate) {
return feature.getGeometry()!.getType() === 'Point' ?
Expand All @@ -418,6 +499,13 @@ export default class OSMWaySnap extends PointerInteraction {
color: 'rgba(245,128,2,0.5)'
})
});
} else if (feature.getProperties().draft) {
return new Style({
stroke: new Stroke({
width: 4,
color: 'rgba(60,60,60,0.7)'
})
});
} else if (feature.getGeometry()!.getType() === 'Point') {
return createEditingStyle()['Point'];
}
Expand Down
Loading