@geoblocks/edittrack
Advanced tools
Comparing version 1.1.3 to 1.1.4
{ | ||
"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 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Misc. License Issues
License(Experimental) A package's licensing information has fine-grained problems.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
0
1
7
48
74530
20
26
2057
5