Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@geoblocks/edittrack

Package Overview
Dependencies
Maintainers
5
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@geoblocks/edittrack - npm Package Compare versions

Comparing version 1.1.3 to 1.1.4

LICENSE

70

package.json
{
"name": "@geoblocks/edittrack",
"version": "1.1.3",
"version": "1.1.4",
"description": "Geoblocks edittrack",
"module": "src/index.js",
"scripts": {
"eslint": "eslint src test",
"eslint-fix": "eslint src test --fix",
"start": "web-dev-server --watch --node-resolve --open /demos/",
"eslint": "eslint src test demos",
"start": "parcel serve demos/*/*.html",
"build-demo:simple": "parcel build --target demo demos/simple/simple.html",
"build-demo:schm": "parcel build --target demo demos/schm/schm.html",
"build-demo": "npm run build-demo:simple && npm run build-demo:schm",
"test": "mocha --exit",
"typecheck": "tsc --pretty",
"typecheck": "tsc --pretty --noEmit",
"lint": "npm run eslint && npm run typecheck",
"doc": "typedoc"
"doc": "typedoc",
"gh-pages": "rm -rf docs && npm run doc && npm run build-demo && gh-pages -d dist"
},
"author": "",
"license": "bsd",
"license": "BSD-3-Clause",
"repository": "github:geoblocks/edittrack",
"publishConfig": {
"access": "public"
},
"files": [
"src"
],
"type": "module",
"targets": {
"demo": {
"context": "browser",
"publicUrl": "./"
}
},
"devDependencies": {
"@geoblocks/d3profile": "^0.0.6",
"@geoblocks/proj": "^0.4.6",
"@geoblocks/sources": "^0.3.1",
"@types/chai": "^4.3.3",
"@types/mocha": "^9.1.1",
"@types/ol": "^6.5.3",
"@types/proj4": "^2.5.2",
"@web/dev-server": "^0.1.34",
"chai": "^4.3.6",
"eslint": "^8.22.0",
"eslint-config-openlayers": "^16.2.3",
"mocha": "^10.0.0",
"ol": "^7.0.0",
"proj4": "^2.8.0",
"tsconfig-paths": "^4.1.0",
"typedoc": "^0.23.10",
"typescript": "^4.7.4"
"@geoblocks/d3profile": "0.0.6",
"@geoblocks/proj": "0.4.6",
"@geoblocks/sources": "0.3.1",
"@types/chai": "4.3.5",
"@types/mocha": "10.0.1",
"@types/proj4": "2.5.2",
"@typescript-eslint/eslint-plugin": "6.5.0",
"@typescript-eslint/parser": "6.5.0",
"chai": "4.3.8",
"eslint": "8.48.0",
"eslint-config-openlayers": "18.0.0",
"gh-pages": "6.0.0",
"mocha": "10.2.0",
"ol": "7.5.2",
"parcel": "2.9.3",
"proj4": "2.9.0",
"ts-node": "10.9.1",
"tsconfig-paths": "4.2.0",
"typedoc": "0.25.0",
"typescript": "5.2.2"
},

@@ -42,4 +60,4 @@ "optionalDependencies": {

"ol": "^6 || ^7",
"proj4": "^2.4.4"
"proj4": "^2"
}
}
export as namespace geoblocks;
export * from './profiler/profiler';
export * from './router/router';
export * from './profiler/profiler.d.ts';
export * from './router/router.d.ts';
import Interaction from 'ol/interaction/Interaction.js';
import Draw from 'ol/interaction/Draw.js';
import Select from 'ol/interaction/Select.js';
import Modify from './TrackInteractionModify.js';
import {click} from 'ol/events/condition.js';
import DrawPoint from './DrawPoint.ts';
import {FALSE} from 'ol/functions';

@@ -12,3 +13,3 @@ /** @typedef {import('ol/source/Vector').default<any>} VectorSource */

/** @typedef {import("ol/layer/Vector").default<VectorSource>} VectorLayer */
/** @typedef {import('ol/Feature.js').default<any>} Feature */
/** @typedef {import('ol/Feature.js').FeatureLike} FeatureLike */

@@ -22,3 +23,6 @@ /**

* @property {StyleFunction} style
* @property {function(MapBrowserEvent): boolean} [deleteCondition]
* @property {function(MapBrowserEvent, string): boolean} [deleteCondition] Default is to delete control points and pois on click
* @property {function(MapBrowserEvent): boolean} [addLastPointCondition] Default is to add a new point on click
* @property {function(MapBrowserEvent): boolean} [addControlPointCondition] In addition to the drag sequence, an optional condition to add a new control point to the track. Default is never.
* @property {number} hitTolerance Pixel tolerance for considering the pointer close enough to a segment for snapping.
*/

@@ -32,9 +36,9 @@

* @param {import("ol/pixel.js").Pixel} pixel
* @return {Feature}
* @return {FeatureLike|false}
*/
controlPointAtPixel(pixel) {
// @ts-ignore false cast error
controlPointOrPOIAtPixel(pixel) {
return this.getMap().forEachFeatureAtPixel(pixel,
(f) => {
if (f.get('type') === 'controlPoint') {
const t = f.get('type');
if (t === 'controlPoint' || t === 'POI') {
return f;

@@ -50,15 +54,10 @@ }

* @param {VectorSource} source
* @param {StyleFunction} style
* @return {Draw}
* @return {DrawPoint}
*/
createDrawInteraction(source, style) {
const draw = new Draw({
type: 'Point',
createDrawInteraction(source) {
const draw = new DrawPoint({
source: source,
style: style,
// don't draw when deleteCondition is true
// without condition, don't draw then there is a control point at this pixel
condition: (event) => this.deleteCondition_ ?
!this.deleteCondition_(event) : !this.controlPointAtPixel(event.pixel) // FIXME: analyze performance
condition: (event) => this.userAddLastPointCondition_(event) && !this.controlPointOrPOIAtPixel(event.pixel)
});
// @ts-ignore too complicate to declare proper events
draw.on('drawend', (evt) => this.dispatchEvent(evt));

@@ -73,5 +72,6 @@ return draw;

* @param {StyleFunction} style
* @param {number} hitTolerance
* @return {Modify}
*/
createModifyInteraction(trackData, source, style) {
createModifyInteraction(trackData, source, style, hitTolerance) {
const modify = new Modify({

@@ -81,6 +81,15 @@ trackData: trackData,

style: style,
condition: (event) => !this.deleteCondition_ || !this.deleteCondition_(event),
condition: (event) => !this.deleteCondition_(event),
addControlPointCondition: (event) => this.userAddControlPointCondition_(event),
hitTolerance: hitTolerance,
});
// @ts-ignore too complicate to declare proper events
modify.on('modifyend', (evt) => this.dispatchEvent(evt));
modify.on('modifystart', () => {
source.addFeature(modify.overlayFeature);
});
// @ts-ignore too complicate to declare proper events
modify.on('modifyend', (evt) => {
source.removeFeature(modify.overlayFeature);
this.dispatchEvent(evt);
});
return modify;

@@ -96,8 +105,8 @@ }

const select = new Select({
// only delete if alt-key is being pressed while clicking
condition: (mapBrowserEvent) =>
click(mapBrowserEvent) &&
(!this.deleteCondition_ || this.deleteCondition_(mapBrowserEvent)),
condition: (event) => this.deleteCondition_(event),
layers: [trackLayer],
filter: (feature) => feature.get('type') === 'controlPoint',
filter: (feature) => {
const t = feature.get('type');
return t === 'controlPoint' || t === 'POI';
},
});

@@ -109,2 +118,14 @@ select.on('select', (evt) => this.dispatchEvent(evt));

/**
* @param {MapBrowserEvent} event
* @return {boolean}
*/
deleteCondition_(event) {
const point = this.controlPointOrPOIAtPixel(event.pixel);
if (point) {
return this.userDeleteCondition_(event, point.get('type'));
}
return false;
}
/**
*

@@ -117,4 +138,7 @@ * @param {Options} options

this.trackLayer_ = options.trackLayer;
this.deleteCondition_ = options.deleteCondition;
this.userDeleteCondition_ = options.deleteCondition === undefined ? click : options.deleteCondition;
this.userAddLastPointCondition_ = options.addLastPointCondition === undefined ? click : options.addLastPointCondition;
this.userAddControlPointCondition_ = options.addControlPointCondition === undefined ? FALSE : options.addControlPointCondition;
const source = options.trackLayer.getSource();

@@ -128,3 +152,3 @@ // FIXME should debounce

*/
this.drawTrack_ = this.createDrawInteraction(source, options.style);
this.drawTrack_ = this.createDrawInteraction(source);

@@ -134,3 +158,3 @@ /**

*/
this.modifyTrack_ = this.createModifyInteraction(options.trackData, source, options.style);
this.modifyTrack_ = this.createModifyInteraction(options.trackData, source, options.style, options.hitTolerance);

@@ -147,7 +171,9 @@ /**

// otherwise clicking on an existing segment or point doesn't add a new point
// The delete interaction must be added after the draw otherwise the draw is not passing
// the double click event to the delete interaction.
const map = options.map;
this.setMap(map);
map.addInteraction(this.deletePoint_);
map.addInteraction(this.modifyTrack_);
map.addInteraction(this.drawTrack_);
map.addInteraction(this.deletePoint_);
}

@@ -154,0 +180,0 @@

@@ -10,4 +10,3 @@ import PointerInteraction from 'ol/interaction/Pointer.js';

/**
* @template UIEvent
* @typedef {import('ol/MapBrowserEvent.js').default<UIEvent>} MapBrowserEvent<UIEvent>
* @typedef {import('ol/MapBrowserEvent.js').default<UIEvent>} MapBrowserEvent
*/

@@ -37,3 +36,3 @@

/**
* The coordinate of the pointer when modification occured.
* The coordinate of the pointer when modification occurred.
* @type {import("ol/coordinate.js").Coordinate}

@@ -54,3 +53,5 @@ */

* @property {StyleFunction} style
* @property {function(MapBrowserEvent<any>): boolean} condition
* @property {function(MapBrowserEvent): boolean} condition
* @property {function(MapBrowserEvent): boolean} addControlPointCondition
* @property {number} hitTolerance Pixel tolerance for considering the pointer close enough to a segment for snapping.
*/

@@ -78,4 +79,8 @@

this.addControlPointCondition_ = options.addControlPointCondition;
this.source_ = options.source;
this.hitTolerance_ = options.hitTolerance;
/**

@@ -90,3 +95,3 @@ * The feature being modified.

*/
this.overlayFeature_ = new Feature({
this.overlayFeature = new Feature({
type: 'segment'

@@ -102,5 +107,3 @@ });

}),
style: (feature, resolution) => {
return options.style(feature, resolution);
},
style: (feature, resolution) => options.style(feature, resolution),
updateWhileAnimating: true,

@@ -127,2 +130,4 @@ updateWhileInteracting: true

this.overlayLineString_ = null;
this.scratchPoint_ = new Point([0, 0]);
}

@@ -139,35 +144,55 @@

/**
* Get the first feature at pixel, favor points over lines
* @param {import("ol/pixel").Pixel} pixel
* @return {Feature<LineString|Point>|undefined}
*/
getFeatureAtPixel(pixel) {
const features = /** @type {Feature<LineString|Point>[]} */ (this.getMap().getFeaturesAtPixel(pixel, {
layerFilter: (l) => l.getSource() === this.source_,
hitTolerance: this.hitTolerance_
}));
// get the first point feature
const feature = features.find((f) => f.getGeometry().getType() === 'Point');
if (feature) {
return feature;
}
return features[0];
}
updateSketchFeature() {
const f = /** @type {Feature<LineString|Point>} */ (this.getMap().forEachFeatureAtPixel(
this.lastPixel_,
(f) => f, {
layerFilter: (l) => l.getSource() === this.source_,
hitTolerance: 20
})
);
let subtype = '';
if (f) {
switch (f.get('type')) {
case 'controlPoint':
subtype = 'cp';
break;
case 'segment':
if (!this.dragStarted) {
subtype = 'segment';
}
break;
default:
// empty
const feature = this.getFeatureAtPixel(this.lastPixel_);
// Adds hit geometries to the hit feature and the sketch feature.
// The geometry is either the closest point on a line or the point itself
this.source_.forEachFeature((f) => f.set('sketchHitGeometry', undefined));
this.pointAtCursorFeature_.setProperties({
'sketchHitGeometry': undefined,
'subtype': undefined,
});
if (feature) {
const type = feature.get('type');
const sketchGeometry = this.pointAtCursorFeature_.getGeometry();
const featureGeometry = feature.getGeometry();
let hitGeometry = null;
if (type === 'segment') {
this.scratchPoint_.setCoordinates(featureGeometry.getClosestPoint(sketchGeometry.getCoordinates()));
hitGeometry = this.scratchPoint_;
} else {
hitGeometry = featureGeometry;
}
feature.set('sketchHitGeometry', hitGeometry);
this.pointAtCursorFeature_.setProperties({
'sketchHitGeometry': sketchGeometry,
'subtype': type,
});
}
if (subtype !== this.pointAtCursorFeature_.get('subtype')) {
this.pointAtCursorFeature_.set('subtype', subtype);
}
}
/**
* @param {MapBrowserEvent<any>} event
* @param {MapBrowserEvent} event
*/
handleMoveEvent(event) {
if (event.dragging) {
return;
}
this.pointAtCursorFeature_.getGeometry().setCoordinates(event.coordinate);

@@ -179,5 +204,21 @@ this.lastPixel_ = event.pixel;

/**
* @param {MapBrowserEvent<any>} event
* @param {MapBrowserEvent} event
* @return {boolean}
*/
handleEvent(event) {
const stop = super.handleEvent(event);
if (this.addControlPointCondition_(event)) {
const feature = this.getFeatureAtPixel(event.pixel);
if (feature && feature.get('type') === 'segment') {
this.dispatchEvent(new ModifyEvent('modifyend', feature, event.coordinate));
return false
}
}
return stop;
}
/**
* @param {MapBrowserEvent} event
* @return {boolean}
*/
handleDownEvent(event) {

@@ -188,9 +229,3 @@ if (!this.condition_(event)) {

console.assert(!this.feature_);
this.feature_ = /** @type {Feature<LineString|Point>} */ (event.map.forEachFeatureAtPixel(
event.pixel,
(f) => f, {
layerFilter: (l) => l.getSource() === this.source_,
hitTolerance: 20
})
);
this.feature_ = this.getFeatureAtPixel(event.pixel);

@@ -205,3 +240,3 @@ if (!this.feature_) {

/**
* @param {MapBrowserEvent<any>} event
* @param {MapBrowserEvent} event
*/

@@ -213,2 +248,3 @@ handleDragEvent(event) {

if (!this.dragStarted) {
this.dispatchEvent(new ModifyEvent('modifystart', this.feature_, event.coordinate));
this.dragStarted = true;

@@ -218,2 +254,3 @@ this.overlayLineString_ = null;

case 'segment': {
this.overlayFeature.set('dragging', true);
// we create a 3 points linestring

@@ -223,3 +260,4 @@ const geometry = this.feature_.getGeometry();

const g = /** @type {LineString} */ (geometry);
this.overlayLineString_ = new LineString([g.getFirstCoordinate(), event.coordinate, g.getLastCoordinate()])
this.overlayLineString_ = new LineString([g.getFirstCoordinate(), event.coordinate, g.getLastCoordinate()]);
this.overlayFeature.set('sketchHitGeometry', new Point(event.coordinate));
this.involvedFeatures_ = [this.feature_];

@@ -229,2 +267,3 @@ break;

case 'controlPoint': {
this.feature_.set('dragging', true);
// we create a 3 points linestring, doubled if end points clicked

@@ -246,2 +285,7 @@ const f = /** @type {Feature<Point>} */ (this.feature_);

}
case 'POI': {
this.feature_.set('dragging', true);
this.involvedFeatures_ = [this.feature_];
break;
}
default:

@@ -251,12 +295,11 @@ throw new Error('unknown feature');

if (this.overlayLineString_ ) {
this.overlayFeature_.setGeometry(this.overlayLineString_ );
this.overlay_.getSource().addFeature(this.overlayFeature_);
if (this.overlayLineString_) {
this.overlayFeature.setGeometry(this.overlayLineString_);
}
this.involvedFeatures_.forEach(f => {
f?.get('type') === 'segment' && f?.set('subtype', 'modifying')
f?.get('type') === 'segment' && f.set('subtype', 'modifying')
});
}
if (this.overlayLineString_ ) {
if (this.overlayLineString_) {
// update sketch linestring

@@ -267,5 +310,10 @@ const coordinates = this.overlayLineString_.getCoordinates();

this.overlayLineString_.setCoordinates(coordinates);
const sketchHitGeometry = this.overlayFeature.get('sketchHitGeometry');
if (sketchHitGeometry) {
sketchHitGeometry.setCoordinates(event.coordinate);
}
}
if (type === 'controlPoint') {
if (type === 'controlPoint' || type === 'POI') {
const g = /** @type {Point} */ (this.feature_.getGeometry());

@@ -278,3 +326,3 @@ g.setCoordinates(event.coordinate);

*
* @param {MapBrowserEvent<any>} event
* @param {MapBrowserEvent} event
* @return {boolean}

@@ -287,10 +335,12 @@ */

}
this.dispatchEvent(new ModifyEvent('modifyend', this.feature_, event.coordinate));
this.dragStarted = false;
this.overlayFeature.setGeometry(null);
this.overlayFeature.set('dragging', false);
this.overlayFeature.set('sketchHitGeometry', undefined);
this.involvedFeatures_.forEach(f => {
f?.get('type') === 'segment' && f?.set('subtype', undefined)
f?.get('type') === 'segment' && f.set('subtype', undefined)
});
this.dispatchEvent(new ModifyEvent('modifyend', this.feature_, event.coordinate));
if (this.overlayLineString_) {
this.overlay_.getSource().removeFeature(this.overlayFeature_);
}
this.feature_ = null;

@@ -297,0 +347,0 @@ return false;

import Feature from 'ol/Feature.js';
import Point from 'ol/geom/Point.js';
import TrackData from './TrackData.js';
import TrackData from './TrackData.ts';
import TrackUpdater from './TrackUpdater.js';
import TrackInteraction from './TrackInteraction.js';
import HistoryManager from './HistoryManager.js';
import HistoryManager from './HistoryManager.ts';
import {findClosestPointInLines} from './closestfinder.js';
import {findClosestPointInLines} from './closestfinder.ts';
import {debounce, setZ} from './util.js';
import {debounce, setZ} from './util.ts';

@@ -32,3 +32,6 @@

* @property {StyleFunction} style
* @property {function(MapBrowserEvent): boolean} [deleteCondition]
* @property {function(MapBrowserEvent, string): boolean} [deleteCondition] Condition to remove a point (control point or POI). Default is click.
* @property {function(MapBrowserEvent): boolean} [addLastPointCondition] Condition to add a new last point to the track. Default is click.
* @property {function(MapBrowserEvent): boolean} [addControlPointCondition] In addition to the drag sequence, an optional condition to add a new control point to the track. Default is never.
* @property {number} [hitTolerance=20] Pixel tolerance for considering the pointer close enough to a segment for snapping.
*/

@@ -69,2 +72,8 @@

/**
* @type {number}
* @private
*/
this.hitTolerance_ = options.hitTolerance !== undefined ? options.hitTolerance : 20;
/**
* @type {boolean}

@@ -126,2 +135,5 @@ */

deleteCondition: options.deleteCondition,
addLastPointCondition: options.addLastPointCondition,
addControlPointCondition: options.addControlPointCondition,
hitTolerance: this.hitTolerance_,
});

@@ -140,20 +152,4 @@

*/
this.history_ = new HistoryManager();
this.historyManager_ = new HistoryManager();
/**
* @type {boolean}
*/
this.historyChanged_ = false;
this.addTrackChangeEventListener(() => {
const segments = this.getSegments();
if (!this.historyChanged_ && segments.length > 0) {
const controlPoints = this.getControlPoints();
this.history_.add([...segments, ...controlPoints]);
} else {
// skip the update, it originated from a undo or redo.
this.historyChanged_ = false;
}
});
// @ts-ignore too complicate to declare proper events

@@ -165,3 +161,3 @@ this.interaction_.on('drawend',

*/
(event) => {
async (event) => {
console.assert(event.feature.getGeometry().getType() === 'Point');

@@ -172,31 +168,14 @@ const feature = /** @type {Feature<Point>} */ (event.feature);

this.source_.addFeature(segment);
let snapped = false;
if (this.snapping) {
this.router_.snapSegment(segment, pointFrom, pointTo)
.then(() => {
// compute profile for routed segment
this.profiler_.computeProfile(segment).then(() => {
const {before} = this.trackData_.getAdjacentSegments(pointFrom);
if (before && !before.get('snapped')) {
// move the last point of the previous straight segment
const geometry = /** @type {LineString} */ (before.getGeometry());
const coordinates = geometry.getCoordinates();
console.assert(coordinates.length === 2, 'Previous segment is not a straight line');
geometry.setCoordinates([coordinates[0], segment.getGeometry().getFirstCoordinate()]);
}
this.onTrackChanged_();
});
});
} else {
// compute profile for straight segment and set the elevation to the points
this.profiler_.computeProfile(segment).then(() => {
/**
* @type {[number, number, number, number][]}
*/
const segmentProfile = segment.get('profile');
if (segmentProfile) {
setZ(segment, segmentProfile[0][2], segmentProfile[segmentProfile.length - 1][2]);
}
this.onTrackChanged_();
});
snapped = await this.router_.snapSegment(segment, pointFrom, pointTo);
}
await this.profiler_.computeProfile(segment);
if (!snapped) {
const segmentProfile = segment.get('profile');
if (segmentProfile) {
setZ(segment, segmentProfile[0][2], segmentProfile[segmentProfile.length - 1][2]);
}
}
this.onTrackChanged_();
}

@@ -227,7 +206,6 @@ });

// @ts-ignore
this.map_.on('pointermove', (event) => {
const hover = this.map_.hasFeatureAtPixel(event.pixel, {
layerFilter: l => l === options.trackLayer,
hitTolerance: 20,
hitTolerance: this.hitTolerance_,
});

@@ -243,17 +221,22 @@ const cursor = (this.interaction_.getActive() && hover) ? 'pointer' : '';

// @ts-ignore too complicate to declare proper events
this.interaction_.on('modifyend',
/**
*
* @param {import ('./TrackInteractionModify').ModifyEvent} event
*/
(event) => {
const feature = event.feature;
const type = feature.get('type');
this.interaction_.on(
// @ts-ignore too complicate to declare proper events
'modifyend',
/**
*
* @param {import ('./TrackInteractionModify').ModifyEvent} event
*/
async (event) => {
const feature = event.feature;
const type = feature.get('type');
if (type === 'controlPoint') {
this.updater_.updateAdjacentSegmentsGeometries(feature).then(() => {
this.updater_.changeAdjacentSegmentsStyling(feature, '');
this.updater_.computeAdjacentSegmentsProfile(feature).then(() => this.onTrackChanged_());
});
if (type === 'POI') {
this.trackData_.updatePOIIndexes();
this.onTrackChanged_();
} else if (type === 'controlPoint') {
await this.updater_.updateAdjacentSegmentsGeometries(feature);
this.updater_.changeAdjacentSegmentsStyling(feature, '');
await this.updater_.computeAdjacentSegmentsProfile(feature);
this.trackData_.updatePOIIndexes();
this.onTrackChanged_();
} else if (type === 'segment') {

@@ -263,5 +246,5 @@ const indexOfSegment = this.trackData_.getSegments().indexOf(feature);

console.assert(indexOfSegment >= 0);
const controlPoint = /** @type {Feature<Point>} */ (new Feature({
const controlPoint = new Feature({
geometry: new Point(event.coordinate)
}));
});
this.source_.addFeature(controlPoint);

@@ -276,49 +259,59 @@ const removed = this.trackData_.insertControlPointAt(controlPoint, indexOfSegment + 1);

this.updater_.updateAdjacentSegmentsGeometries(controlPoint).then(() => {
this.updater_.changeAdjacentSegmentsStyling(controlPoint, '');
this.updater_.computeAdjacentSegmentsProfile(controlPoint).then(() => this.onTrackChanged_());
});
await this.updater_.updateAdjacentSegmentsGeometries(controlPoint);
this.updater_.changeAdjacentSegmentsStyling(controlPoint, '');
await this.updater_.computeAdjacentSegmentsProfile(controlPoint);
this.trackData_.updatePOIIndexes();
this.onTrackChanged_();
}
});
// @ts-ignore too complicate to declare proper events
this.interaction_.on('select',
/**
*
* @param {import ('ol/interaction/Select').SelectEvent} event
*/
(event) => {
const selected = /** @type {Feature<Point>} */ (event.selected[0]);
console.assert(selected.getGeometry().getType() === 'Point');
const {deleted, pointBefore, pointAfter, newSegment} = this.trackData_.deleteControlPoint(selected);
this.interaction_.on(
// @ts-ignore too complicate to declare proper events
'select',
/**
*
* @param {import ('ol/interaction/Select').SelectEvent} event
*/
(event) => {
const selected = /** @type {Feature<Point>} */ (event.selected[0]);
console.assert(selected.getGeometry().getType() === 'Point');
const type = selected.get('type');
if (type === 'POI') {
this.trackData_.deletePOI(selected);
this.source_.removeFeature(selected);
this.onTrackChanged_();
} else {
// control point
const {deleted, pointBefore, pointAfter, newSegment} = this.trackData_.deleteControlPoint(selected);
// remove deleted features from source
deleted.forEach(f => this.source_.removeFeature(f));
// remove deleted features from source
deleted.forEach(f => this.source_.removeFeature(f));
// add newly created segment to source
if (newSegment) {
this.source_.addFeature(newSegment);
}
// add newly created segment to source
if (newSegment) {
this.source_.addFeature(newSegment);
}
// update adjacent points
if (pointBefore || pointAfter) {
const geometryUpdates = [];
if (pointBefore) {
geometryUpdates.push(this.updater_.updateAdjacentSegmentsGeometries(pointBefore));
}
if (pointAfter) {
geometryUpdates.push(this.updater_.updateAdjacentSegmentsGeometries(pointAfter));
}
Promise.all(geometryUpdates).then(() => {
const segmentUpdates = [];
// update adjacent points
if (pointBefore || pointAfter) {
const geometryUpdates = [];
if (pointBefore) {
segmentUpdates.push(this.updater_.computeAdjacentSegmentsProfile(pointBefore));
geometryUpdates.push(this.updater_.updateAdjacentSegmentsGeometries(pointBefore));
}
if (pointAfter) {
segmentUpdates.push(this.updater_.computeAdjacentSegmentsProfile(pointAfter));
geometryUpdates.push(this.updater_.updateAdjacentSegmentsGeometries(pointAfter));
}
Promise.all(segmentUpdates).then(() => {
this.onTrackChanged_();
Promise.all(geometryUpdates).then(() => {
const segmentUpdates = [];
if (pointBefore) {
segmentUpdates.push(this.updater_.computeAdjacentSegmentsProfile(pointBefore));
}
if (pointAfter) {
segmentUpdates.push(this.updater_.computeAdjacentSegmentsProfile(pointAfter));
}
Promise.all(segmentUpdates).then(() => {
this.onTrackChanged_();
});
});
});
}
}

@@ -332,2 +325,12 @@

/**
* @private
*/
pushNewStateToHistoryManager_() {
const segments = this.getSegments();
const controlPoints = this.getControlPoints();
const pois = this.getPOIs();
this.historyManager_.add([...segments, ...controlPoints, ...pois]);
}
/**
* @return {TrackMode} mode

@@ -351,3 +354,3 @@ */

} else {
this.history_.clear();
this.historyManager_.clear();
if (this.shadowTrackLayer_) {

@@ -379,5 +382,7 @@ this.shadowTrackLayer_.getSource().clear();

if (this.mode_) {
const deletedFeatures = this.trackData_.deleteLastControlPoint();
deletedFeatures.forEach(feature => this.source_.removeFeature(feature));
this.onTrackChanged_();
if (this.trackData_.getControlPoints().length > 0) {
const deletedFeatures = this.trackData_.deleteLastControlPoint();
deletedFeatures.forEach(feature => this.source_.removeFeature(feature));
this.onTrackChanged_();
}
}

@@ -387,2 +392,5 @@ }

reverse() {
if (!this.trackData_.getSegments().length) {
return;
}
this.trackData_.reverse();

@@ -393,6 +401,8 @@ const points = this.trackData_.getControlPoints();

this.updater_.changeAdjacentSegmentsStyling(point, 'modifying');
this.updater_.updateAdjacentSegmentsGeometries(point).then(() => {
this.updater_.updateAdjacentSegmentsGeometries(point)
.then(() => {
this.updater_.changeAdjacentSegmentsStyling(point, '');
this.updater_.computeAdjacentSegmentsProfile(point).then(() => this.onTrackChanged_());
});
this.updater_.computeAdjacentSegmentsProfile(point);
})
.then(() => this.onTrackChanged_());
}

@@ -402,30 +412,56 @@ }

/**
* This function does not trigger track changed events.
* @private
*/
clear() {
clearInternal_() {
this.source_.clear();
this.trackData_.clear();
this.onTrackChanged_();
}
/**
*/
clear() {
if (this.trackData_.hasData()) {
this.clearInternal_()
this.onTrackChanged_();
}
}
/**
* This function does not trigger track changed events.
* @private
* @param {Array<Feature<Point|LineString>>} features
* @return {Promise<any>}
*/
restoreFeatures(features) {
this.clear();
this.trackData_.restoreFeatures(features);
async restoreFeaturesInternal_(features) {
// should parse features first, compute profile, and then replace the trackdata and add history
const parsedFeatures = this.trackData_.parseFeatures(features);
this.source_.addFeatures(features);
const segments = this.trackData_.getSegments();
const profileRequests = segments.map(segment => this.profiler_.computeProfile(segment));
return Promise.all(profileRequests).then(() => {
this.onTrackChanged_();
});
const profileRequests = parsedFeatures.segments.map(segment => this.profiler_.computeProfile(segment));
await Promise.all(profileRequests);
this.trackData_.restoreParsedFeatures(parsedFeatures);
}
/**
* @param {Array<Feature<Point|LineString>>} features
* @return {Promise<any>}
*/
async restoreFeatures(features) {
await this.restoreFeaturesInternal_(features);
this.onTrackChanged_();
}
/**
* @return {Feature<Point>[]}
*/
getPOIs() {
return this.trackData_.getPOIs().map(point => point.clone());
}
/**
* @return {Feature<Point>[]}
*/
getControlPoints() {
return this.trackData_.getControlPoints().map((point, index) => {
const clone = /** @type {Feature<Point>} */ (point.clone());
const clone = point.clone();
clone.set('index', index);

@@ -441,3 +477,3 @@ return clone;

return this.trackData_.getSegments().map((segment, index) => {
const clone = /** @type {Feature<LineString>} */ (segment.clone());
const clone = segment.clone();
clone.set('index', index);

@@ -474,5 +510,9 @@ return clone;

* @private
* @param {boolean} notifyHistory Whether to notify history manager
*/
notifyTrackChangeEventListeners_() {
notifyTrackChangeEventListeners_(notifyHistory = true) {
this.trackChangeEventListeners_.forEach(handler => handler());
if (notifyHistory) {
this.pushNewStateToHistoryManager_();
}
}

@@ -504,15 +544,14 @@

/**
* Undo one drawing step
*/
undo() {
undo() {
if (this.mode === 'edit') {
const features = this.history_.undo();
const features = this.historyManager_.undo();
if (features) {
this.restoreFeatures(features.map(feature => feature.clone()));
this.historyChanged_ = true;
this.restoreFeaturesInternal_(features.map(feature => feature.clone()));
} else {
this.clear();
this.clearInternal_();
}
this.notifyTrackChangeEventListeners_(false);
}

@@ -526,6 +565,6 @@ }

if (this.mode === 'edit') {
const features = this.history_.redo();
const features = this.historyManager_.redo();
if (features) {
this.restoreFeatures(features.map(feature => feature.clone()));
this.historyChanged_ = true;
this.restoreFeaturesInternal_(features.map(feature => feature.clone()));
this.notifyTrackChangeEventListeners_(false);
}

@@ -532,0 +571,0 @@ }

/**
* @typedef {import("./TrackData.js").default} TrackData
* @typedef {import("./TrackData").default} TrackData
* @typedef {import('ol/geom/Point').default} Point

@@ -16,2 +16,5 @@ * @typedef {import('ol/geom/LineString').default} LineString

/**
* Drive the chosen router and profiler to update the segment geometries.
*/
class TrackUpdater {

@@ -39,18 +42,3 @@

/**
* @private
* @param {import('ol/Feature').default<LineString>} segment
* @param {import('ol/Feature').default<Point>} pointFrom
* @param {import('ol/Feature').default<Point>} pointTo
*/
updateStraightLineSegmentGeometry_(segment, pointFrom, pointTo) {
console.assert(!segment.get('snapped'));
segment.getGeometry().setCoordinates([
pointFrom.getGeometry().getCoordinates(),
pointTo.getGeometry().getCoordinates()
]);
}
/**
* @param {import('ol/Feature').default<Point>} modifiedControlPoint

@@ -93,7 +81,4 @@ * @return {Promise<any>}

*/
updateAdjacentSegmentsGeometries(modifiedControlPoint) {
const routedSegments = [];
/** @type {function[]} */
const straightSegments = [];
async updateAdjacentSegmentsGeometries(modifiedControlPoint) {
// FIXME: use snapping property from manager
if (modifiedControlPoint) {

@@ -103,20 +88,11 @@ const {before, after} = this.trackData_.getAdjacentSegments(modifiedControlPoint);

const pointFrom = this.trackData_.getControlPointBefore(modifiedControlPoint);
if (before.get('snapped')) {
routedSegments.push(() => this.router_.snapSegment(before, pointFrom, modifiedControlPoint));
} else {
straightSegments.push(() => this.updateStraightLineSegmentGeometry_(before, pointFrom, modifiedControlPoint));
}
await this.router_.snapSegment(before, pointFrom, modifiedControlPoint);
await this.profiler_.computeProfile(before);
}
if (after) {
const pointTo = this.trackData_.getControlPointAfter(modifiedControlPoint);
if (after.get('snapped')) {
routedSegments.push(() => this.router_.snapSegment(after, modifiedControlPoint, pointTo));
} else {
straightSegments.push(() => this.updateStraightLineSegmentGeometry_(after, modifiedControlPoint, pointTo));
}
await this.router_.snapSegment(after, modifiedControlPoint, pointTo);
await this.profiler_.computeProfile(after);
}
}
return Promise.all(routedSegments.map(fn => fn())).then(() => {
straightSegments.forEach(fn => fn());
});
}

@@ -123,0 +99,0 @@ }

@@ -39,3 +39,3 @@ import GeoJSONFormat from 'ol/format/GeoJSON.js';

const proj = getProjection('EPSG:2056');
console.assert(proj, 'Register projection first');
console.assert(!!proj, 'Register projection first');

@@ -42,0 +42,0 @@ /**

import {toLonLat} from 'ol/proj.js';
import PolyLineXYZMFormat from './PolylineXYZM.js';
import PolyLineXYZMFormat from './PolylineXYZM.ts';
import RouterBase from './RouterBase.ts';

@@ -9,8 +10,9 @@ /** @typedef {import('ol/geom/LineString').default} LineString */

* @typedef {Object} Options
* @property {import("ol/proj").ProjectionLike} mapProjection
* @property {import("ol/Map").default} map
* @property {string} url
* @property {number} [maxRoutingTolerance=Infinity]
*/
export default class GraphHopper {
export default class GraphHopper extends RouterBase {

@@ -21,2 +23,4 @@ /**

constructor(options) {
super(options);
/**

@@ -30,8 +34,2 @@ * @private

* @private
* @type {import("ol/proj").ProjectionLike}
*/
this.mapProjection_ = options.mapProjection;
/**
* @private
* @type {PolyLineXYZMFormat}

@@ -46,35 +44,42 @@ */

* @param {import("ol/Feature").default<Point>} pointTo
* @return {Promise<void>}
* @return {Promise<boolean>}
*/
snapSegment(segment, pointFrom, pointTo) {
const pointFromGeometry = /** @type {import("ol/geom/Point").default} */ (pointFrom.getGeometry());
const pointToGeometry = /** @type {import("ol/geom/Point").default} */ (pointTo.getGeometry());
async snapSegment(segment, pointFrom, pointTo) {
const mapProjection = this.map.getView().getProjection();
const pointFromGeometry = pointFrom.getGeometry();
const pointToGeometry = pointTo.getGeometry();
const pointFromCoordinates = pointFromGeometry.getCoordinates();
const pointToCoordinates = pointToGeometry.getCoordinates();
const coordinates = [pointFromCoordinates, pointToCoordinates].map(cc => toLonLat(cc.slice(0, 2), this.mapProjection_));
const coordinates = [pointFromCoordinates, pointToCoordinates].map(cc => toLonLat(cc.slice(0, 2), mapProjection));
const coordinateString = coordinates.map(c => `point=${c.reverse().join(',')}`).join('&');
return fetch(`${this.url_}&${coordinateString}`)
.then(response => response.json())
.then(json => {
if (json.paths) {
const path = json.paths[0];
const resultGeometry = /** @type {import("ol/geom/LineString").default} */ (this.polylineFormat_.readGeometry(path.points, {
featureProjection: this.mapProjection_
}));
const resultCoordinates = resultGeometry.getCoordinates();
const segmentGeometry = /** @type {import("ol/geom/LineString").default} */ (segment.getGeometry());
segmentGeometry.setCoordinates(resultCoordinates, 'XYZM');
const response = await fetch(`${this.url_}&${coordinateString}`);
const json = await response.json();
if (json.paths) {
const path = json.paths[0];
const resultGeometry = /** @type {import("ol/geom/LineString").default} */ (this.polylineFormat_.readGeometry(path.points, {
featureProjection: mapProjection
}));
const resultCoordinates = resultGeometry.getCoordinates();
const resultFromCoordinates = resultCoordinates[0].slice(0, 2);
const resultToCoordinates = resultCoordinates[resultCoordinates.length - 1].slice(0, 2);
segment.setProperties({
snapped: true
});
pointFromGeometry.setCoordinates(resultCoordinates[0].slice(0, 2));
pointToGeometry.setCoordinates(resultCoordinates[resultCoordinates.length - 1].slice(0, 2));
pointFrom.set('snapped', true);
pointTo.set('snapped', true);
}
});
pointFrom.set('snapped', this.isInTolerance(pointFromCoordinates, resultFromCoordinates));
pointTo.set('snapped', this.isInTolerance(pointToCoordinates, resultToCoordinates));
const snapped = pointFrom.get('snapped') && pointTo.get('snapped');
if (snapped) {
segment.getGeometry().setCoordinates(resultCoordinates, 'XYZM');
pointFromGeometry.setCoordinates(resultFromCoordinates);
pointToGeometry.setCoordinates(resultToCoordinates);
} else {
segment.getGeometry().setCoordinates([pointFromCoordinates, pointToCoordinates], 'XY');
}
segment.set('snapped', snapped);
return snapped;
}
return false;
}
}
import {fromLonLat, toLonLat} from 'ol/proj.js';
import RouterBase from './RouterBase.ts';

@@ -12,3 +13,4 @@ /**

* @typedef {Object} Options
* @property {ProjectionLike} mapProjection
* @property {import("ol/Map").default} map
* @property {number} [maxRoutingTolerance=Infinity]
* @property {string} url The URL profile prefix to use, see *_PROFILE_URL.

@@ -31,3 +33,3 @@ * @property {string} extraParams Parameters like access token.

export default class OSRMRouter {
export default class OSRMRouter extends RouterBase {

@@ -38,2 +40,4 @@ /**

constructor(options) {
super(options);
/**

@@ -53,8 +57,2 @@ * @private

* @private
* @type {ProjectionLike}
*/
this.mapProjection_ = options.mapProjection;
/**
* @private
* @type {string}

@@ -69,8 +67,9 @@ */

* @param {import('ol/Feature').default<Point>} pointTo
* @return {Promise<void>}
* @return {Promise<boolean>}
*/
snapSegment(segment, pointFrom, pointTo) {
const pointFromGeometry = /** @type {Point} */ (pointFrom.getGeometry());
const pointToGeometry = /** @type {Point} */ (pointTo.getGeometry());
const coordinates = [pointFromGeometry.getCoordinates(), pointToGeometry.getCoordinates()].map(cc => toLonLat(cc.slice(0, 2), this.mapProjection_));
async snapSegment(segment, pointFrom, pointTo) {
const mapProjection = this.map.getView().getProjection();
const pointFromGeometry = pointFrom.getGeometry();
const pointToGeometry = pointTo.getGeometry();
const coordinates = [pointFromGeometry.getCoordinates(), pointToGeometry.getCoordinates()].map(cc => toLonLat(cc.slice(0, 2), mapProjection));

@@ -85,23 +84,18 @@ // [ [a,b] , [c,d] ] -> 'a,b;c,d'

}
return fetch(url)
.then(response => response.json())
.then((jsonResponse) => {
console.assert(jsonResponse.code === 'Ok');
console.assert(jsonResponse.routes.length === 1);
const route = jsonResponse.routes[0];
const segmentCoordinates = /** @type {import('ol/coordinate').Coordinate[]} */
(route.geometry.coordinates).map(cc => fromLonLat(cc, this.mapProjection_));
const segmentGeometry = segment.getGeometry();
const response = await fetch(url);
const jsonResponse = await response.json();
console.assert(jsonResponse.code === 'Ok');
console.assert(jsonResponse.routes.length === 1);
const route = jsonResponse.routes[0];
const segmentCoordinates = /** @type {import('ol/coordinate').Coordinate[]} */ (route.geometry.coordinates).map(cc => fromLonLat(cc, mapProjection));
const segmentGeometry = segment.getGeometry();
segmentGeometry.setCoordinates(segmentCoordinates);
segment.setProperties({
snapped: true
});
pointFromGeometry.setCoordinates(segmentCoordinates[0]);
pointToGeometry.setCoordinates(segmentCoordinates[segmentCoordinates.length - 1]);
segment.set('snapped', true);
pointFrom.set('snapped', true);
pointTo.set('snapped', true);
segmentGeometry.setCoordinates(segmentCoordinates);
pointFromGeometry.setCoordinates(segmentCoordinates[0]);
pointToGeometry.setCoordinates(segmentCoordinates[segmentCoordinates.length - 1]);
pointFrom.set('snapped', true);
pointTo.set('snapped', true);
});
return true;
}

@@ -108,0 +102,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc