Skip to content

Commit

Permalink
Add loopTrail feature
Browse files Browse the repository at this point in the history
  • Loading branch information
dtrucs committed Apr 1, 2021
1 parent 1abf43c commit 5702629
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 30 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ Linestring can be a line between points or a phantom junction line

- lineString - `Feature<LineString> | undefined`

A feature of Linestring type (only applied if `featureCollection` parameter is not set)
Some properties can describe section of points and phantom junction
- `path: string[]` : a collection of string describing each junction. Values can be `free`, `direction` or `junction`
A feature of Linestring type (only applied if `featureCollection` parameter is not set)
Some properties can describe section of points and phantom junction

- `path: string[]` : a collection of string describing each junction. Values can be `free`, `direction` or `junction`
- `points: number[]` : a collection of all coordinates points between each junction
If there are no properties, Mapbox-gl-path create two points at the edge of the lineString and determine if the path is following direction if `directionsTheme` is defined and `isFollowingDirections` is equal at `true`.
If there are no properties, Mapbox-gl-path create two points at the edge of the lineString and determine if the path is following direction if `directionsTheme` is defined and `isFollowingDirections` is equal at `true`.

Point

Expand Down Expand Up @@ -116,6 +117,12 @@ getPathByCoordinates function return a object of type DirectionsThemeResponse wi

### METHODS

#### setLoopTrail

This is not applied if the number of points is less than 3.

#### setOneWayTrail

#### clearFeatureCollection

#### getFeatureCollection
Expand All @@ -133,6 +140,7 @@ return `Feature<LineString>`
featureCollection - `GeoJSON.FeatureCollection<GeoJSON.Geometry>`

#### setLineString

##### Parameter

lineString - `Feature<LineString>`
Expand Down
2 changes: 2 additions & 0 deletions src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ export const defaultLocales = {
"gl-pathControl.createPoint": "Create point",
"gl-pathControl.createIntermediatePoint": "Create intermediate point",
"gl-pathControl.deletePoint": "Delete point",
"gl-pathControl.loopPoint": "Round trip",
"gl-pathControl.oneWayPoint": "One way",
};
210 changes: 184 additions & 26 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Feature, Point, LineString } from "geojson";
import { Map, IControl, MapMouseEvent, GeoJSONSource, Popup } from "mapbox-gl";
import { point, lineString } from "@turf/helpers";
import nearestPointOnLine from "@turf/nearest-point-on-line";
import pointToLineDistance from "@turf/point-to-line-distance";
import lineSplit from "@turf/line-split";
import debounce from "lodash.debounce";
import {
Expand Down Expand Up @@ -45,6 +46,7 @@ interface Parameters {
featureCollection: GeoJSON.FeatureCollection<GeoJSON.Geometry> | undefined;
lineString: GeoJSON.Feature<LineString> | undefined;
directionsThemes: DirectionsTheme[] | undefined;
isLoopTrail: boolean | undefined;
translate: Function | undefined;
}

Expand Down Expand Up @@ -87,6 +89,7 @@ export default class MapboxPathControl implements IControl {
className: "mapbox-gl-path-popup",
});
private isFollowingDirections = false;
private isLoopTrail = false;
private layersCustomisation: LayersCustomisation | undefined;
private directionsThemes: DirectionsTheme[] | undefined;
private selectedDirectionsTheme: DirectionsTheme | undefined;
Expand Down Expand Up @@ -336,6 +339,47 @@ export default class MapboxPathControl implements IControl {

if (!referencePointOrLineIsUnderMouse) {
const newPointCoordinates = getLngLat(event.lngLat);

if (this.isLoopTrail) {
const newPoint: Feature<Point> = point(getLngLat(event.lngLat));

let nearestLineString = this.linesBetweenReferencePoints[0];
this.linesBetweenReferencePoints.slice(1).forEach((line) => {
const currentDistance = pointToLineDistance(
newPoint,
nearestLineString
);
const newDistance = pointToLineDistance(newPoint, line);
if (newDistance < currentDistance) {
nearestLineString = line;
}
});

const nearestPointInLineString: Feature<Point> = nearestPointOnLine(
nearestLineString,
newPoint
);

const newLines = lineSplit(nearestLineString, nearestPointInLineString);

this.selectedReferencePointIndex =
nearestLineString.properties!.index + 1;

this.createNewPointAndLine(
nearestPointInLineString.geometry.coordinates,
newPoint.properties!.isFollowingDirections,
newLines.features[0].geometry!.coordinates,
newLines.features[1].geometry!.coordinates,
nearestLineString?.properties!.index
);

this.movePointHandler(newPointCoordinates);

this.syncIndex();
this.updateSource();

return;
}
const previousReferencePoint: Feature<Point> | null =
this.referencePoints.length > 0
? this.referencePoints[this.referencePoints.length - 1]
Expand Down Expand Up @@ -383,10 +427,40 @@ export default class MapboxPathControl implements IControl {
textContent: this.translate("gl-pathControl.deletePoint"),
});

const actionsPanelContainer = document.createElement("div");
actionsPanelContainer.append(deleteButton);

if (
referencePointsUnderMouse.find(
({ properties }) =>
properties!.index === 0 ||
properties!.index === this.referencePoints.length - 1
) &&
this.referencePoints.length > 2
) {
const loopOrOneWayButton = createElement(
"button",
this.isLoopTrail
? {
className:
"mapbox-gl-path-popup-button mapbox-gl-path-popup-oneWay",
onclick: () => this.setOneWayTrail(),
textContent: this.translate("gl-pathControl.oneWayPoint"),
}
: {
className:
"mapbox-gl-path-popup-button mapbox-gl-path-popup-loop",
onclick: () => this.setLoopTrail(),
textContent: this.translate("gl-pathControl.loopPoint"),
}
);
actionsPanelContainer.append(loopOrOneWayButton);
}

this.selectedReferencePointIndex = referencePointsUnderMouse[0].properties!.index;
this.actionsPanel
.setLngLat(event.lngLat)
.setDOMContent(deleteButton)
.setDOMContent(actionsPanelContainer)
.addTo(this.map!);
}
}
Expand Down Expand Up @@ -452,13 +526,24 @@ export default class MapboxPathControl implements IControl {
}

private movePointHandler(coordinates: number[]): void {
const previousLine = this.linesBetweenReferencePoints[
let previousLine = this.linesBetweenReferencePoints[
this.selectedReferencePointIndex! - 1
];
const nextLine = this.linesBetweenReferencePoints[

if (!previousLine && this.isLoopTrail) {
previousLine = this.linesBetweenReferencePoints[
this.referencePoints.length - 1
];
}

let nextLine = this.linesBetweenReferencePoints[
this.selectedReferencePointIndex!
];

if (!nextLine && this.isLoopTrail) {
nextLine = this.linesBetweenReferencePoints[0];
}

this.handleMapCursor("grabbing");

if (this.actionsPanel.isOpen()) {
Expand Down Expand Up @@ -681,13 +766,26 @@ export default class MapboxPathControl implements IControl {
this.map!.fire("MapboxPathControl.delete", {
deletedPoint: this.referencePoints[this.selectedReferencePointIndex!],
});
const previousLine = this.linesBetweenReferencePoints[

let previousLine = this.linesBetweenReferencePoints[
this.selectedReferencePointIndex! - 1
];
const nextLine = this.linesBetweenReferencePoints[

if (!previousLine && this.isLoopTrail) {
previousLine = this.linesBetweenReferencePoints[
this.linesBetweenReferencePoints.length - 1
];
}

let nextLine = this.linesBetweenReferencePoints[
this.selectedReferencePointIndex!
];
if (this.selectedReferencePointIndex! === 0) {

if (!nextLine && this.isLoopTrail) {
nextLine = this.linesBetweenReferencePoints[0];
}

if (!previousLine) {
this.referencePoints.shift();
if (this.referencePoints.length > 0) {
this.linesBetweenReferencePoints.shift();
Expand All @@ -696,30 +794,34 @@ export default class MapboxPathControl implements IControl {
phantomJunctionLine.properties!.index !== nextLine.properties!.index
);
}
this.syncIndex();
} else if (
this.selectedReferencePointIndex! ===
this.referencePoints.length - 1
) {
} else if (!nextLine) {
this.referencePoints.splice(this.selectedReferencePointIndex!, 1);
this.linesBetweenReferencePoints.splice(
previousLine.properties!.index,
1
);
this.syncIndex();
this.phantomJunctionLines = this.phantomJunctionLines.filter(
(phantomJunctionLine) =>
phantomJunctionLine.properties!.index !==
previousLine.properties!.index
);
} else {
const previousPoint = this.referencePoints[
let previousPoint = this.referencePoints[
this.selectedReferencePointIndex! - 1
];
const nextPoint = this.referencePoints[

if (!previousPoint && this.isLoopTrail) {
previousPoint = this.referencePoints[this.referencePoints.length - 1];
}

let nextPoint = this.referencePoints[
this.selectedReferencePointIndex! + 1
];

if (!nextPoint && this.isLoopTrail) {
nextPoint = this.referencePoints[0];
}

this.phantomJunctionLines = this.phantomJunctionLines.filter(
(phantomJunctionLine) =>
phantomJunctionLine.properties!.index !==
Expand Down Expand Up @@ -778,9 +880,52 @@ export default class MapboxPathControl implements IControl {
}
this.referencePoints.splice(this.selectedReferencePointIndex!, 1);
this.linesBetweenReferencePoints.splice(nextLine.properties!.index, 1);
this.syncIndex();

// Below 3 points, a lineString can no longer be looped
if (this.referencePoints.length < 3 && this.isLoopTrail) {
this.linesBetweenReferencePoints.splice(
previousLine.properties!.index -
Number(
this.selectedReferencePointIndex !== this.referencePoints.length
),
1
);
}
}

this.syncIndex();
this.updateSource();
this.actionsPanel.remove();
}

public async setLoopTrail(): Promise<void> {
if (this.referencePoints.length < 3) {
return;
}
const firstPoint = this.referencePoints[this.referencePoints.length - 1];
const lastPoint = this.referencePoints[0];

this.isLoopTrail = true;

await this.drawNewLine(
firstPoint.geometry.coordinates,
lastPoint.geometry.coordinates
);

this.referencePoints = this.referencePoints.slice(0, -1);
this.updateSource();

this.actionsPanel.remove();
}

public async setOneWayTrail(): Promise<void> {
this.createNewPointAndLine(this.referencePoints[0].geometry.coordinates);

this.selectedReferencePointIndex = this.referencePoints.length - 1;
this.isLoopTrail = false;

this.deletePoint();

this.updateSource();
this.actionsPanel.remove();
}
Expand Down Expand Up @@ -814,6 +959,9 @@ export default class MapboxPathControl implements IControl {
this.referencePoints.forEach(
(point, index) => (point.properties!.index = index)
);
if (this.referencePoints.length < 3) {
this.isLoopTrail = false;
}
this.linesBetweenReferencePoints.forEach((line, index) => {
if (line.properties!.index !== index) {
this.phantomJunctionLines.forEach((phantomJunctionLine) => {
Expand All @@ -834,7 +982,10 @@ export default class MapboxPathControl implements IControl {
): Promise<void> {
let coordinates: number[][] | undefined = [];
const previousPoint = this.referencePoints[line.properties!.index];
const nextPoint = this.referencePoints[line.properties!.index + 1];
let nextPoint = this.referencePoints[line.properties!.index + 1];
if (!nextPoint && this.isLoopTrail) {
nextPoint = this.referencePoints[0];
}
if (line.properties!.isFollowingDirections && !forceDirections) {
coordinates = [
previousPoint.geometry.coordinates,
Expand Down Expand Up @@ -906,21 +1057,28 @@ export default class MapboxPathControl implements IControl {
"Point"
);

this.linesBetweenReferencePoints = this.filterFeaturesByTypeAndSortByIndex<
LineString
>(
features.filter(({ properties }) => !properties!.isPhantomJunction) as [],
const lines = this.filterFeaturesByTypeAndSortByIndex<LineString>(
features as [],
"LineString"
);

this.phantomJunctionLines = this.filterFeaturesByTypeAndSortByIndex<
LineString
>(
features.filter(({ properties }) => properties!.isPhantomJunction) as [],
"LineString"
this.linesBetweenReferencePoints = lines.filter(
({ properties }) => !properties!.isPhantomJunction
);

// In case the featureCollection contains only one LineString, it needs to set two Point at edges
this.phantomJunctionLines = lines.filter(
({ properties }) => properties!.isPhantomJunction
);

if (
this.referencePoints.length > 2 &&
lines[lines.length - 1].geometry.coordinates[1].join() ===
this.referencePoints[0].geometry.coordinates.join()
) {
this.isLoopTrail = true;
}

// In case of @the featureCollection contains only one LineString, it needs to set two Point at edges
if (!this.referencePoints.length && !this.phantomJunctionLines.length) {
this.referencePoints = getLineEnds(
this.linesBetweenReferencePoints[0].geometry.coordinates
Expand Down

0 comments on commit 5702629

Please # to comment.