Let locationInput deal with multipolygon correctly
This commit is contained in:
parent
28fc81c96f
commit
603fecb107
1 changed files with 64 additions and 15 deletions
|
@ -19,6 +19,9 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||||
import FilteredLayer from "../../Models/FilteredLayer";
|
import FilteredLayer from "../../Models/FilteredLayer";
|
||||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||||
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
|
||||||
|
import {RelationId, WayId} from "../../Models/OsmFeature";
|
||||||
|
import {Feature, LineString, Polygon} from "geojson";
|
||||||
|
import {OsmObject, OsmWay} from "../../Logic/Osm/OsmObject";
|
||||||
|
|
||||||
export default class LocationInput
|
export default class LocationInput
|
||||||
extends BaseUIElement
|
extends BaseUIElement
|
||||||
|
@ -29,7 +32,7 @@ export default class LocationInput
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined)
|
public readonly snappedOnto: UIEventSource<Feature & { properties : { id : WayId} }> = new UIEventSource(undefined)
|
||||||
public readonly _matching_layer: LayerConfig
|
public readonly _matching_layer: LayerConfig
|
||||||
public readonly leafletMap: UIEventSource<any>
|
public readonly leafletMap: UIEventSource<any>
|
||||||
public readonly bounds
|
public readonly bounds
|
||||||
|
@ -40,7 +43,13 @@ export default class LocationInput
|
||||||
* The features to which the input should be snapped
|
* The features to which the input should be snapped
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private readonly _snapTo: Store<{ feature: any }[]>
|
private readonly _snapTo: Store< (Feature<LineString | Polygon> & {properties: {id : WayId}})[]>
|
||||||
|
/**
|
||||||
|
* The features to which the input should be snapped without cleanup of relations and memberships
|
||||||
|
* Used for rendering
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly _snapToRaw: Store< {feature: Feature}[]>
|
||||||
private readonly _value: Store<Loc>
|
private readonly _value: Store<Loc>
|
||||||
private readonly _snappedPoint: Store<any>
|
private readonly _snappedPoint: Store<any>
|
||||||
private readonly _maxSnapDistance: number
|
private readonly _maxSnapDistance: number
|
||||||
|
@ -57,10 +66,41 @@ export default class LocationInput
|
||||||
readonly allElements: ElementStorage
|
readonly allElements: ElementStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of geojson-features, will prepare these features to be snappable:
|
||||||
|
* - points are removed
|
||||||
|
* - LineStrings are passed as-is
|
||||||
|
* - Multipolygons are decomposed into their member ways by downloading them
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private static async prepareSnapOnto(features: Feature[]): Promise<(Feature<LineString | Polygon> & {properties : {id: WayId}})[]> {
|
||||||
|
const linesAndPolygon : Feature<LineString | Polygon>[] = <any> features.filter(f => f.geometry.type !== "Point")
|
||||||
|
// Clean the features: multipolygons are split into their it's members
|
||||||
|
const linestrings : (Feature<LineString | Polygon> & {properties: {id: WayId}})[] = []
|
||||||
|
for (const feature of linesAndPolygon) {
|
||||||
|
if(feature.properties.id.startsWith("way")){
|
||||||
|
// A normal way - we continue
|
||||||
|
linestrings.push(<any> feature)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a multipolygon, thus: a relation
|
||||||
|
// Download the members
|
||||||
|
const relation = await OsmObject.DownloadObjectAsync(<RelationId> feature.properties.id, 60 * 60)
|
||||||
|
const members: OsmWay[] = await Promise.all(relation.members
|
||||||
|
.filter(m => m.type === "way")
|
||||||
|
.map(m => OsmObject.DownloadObjectAsync(<WayId> ("way/"+m.ref), 60 * 60)))
|
||||||
|
linestrings.push(...members.map(m => m.asGeoJson()))
|
||||||
|
}
|
||||||
|
return linestrings
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
constructor(options?: {
|
constructor(options?: {
|
||||||
minZoom?: number
|
minZoom?: number
|
||||||
mapBackground?: UIEventSource<BaseLayer>
|
mapBackground?: UIEventSource<BaseLayer>
|
||||||
snapTo?: UIEventSource<{ feature: any }[]>
|
snapTo?: UIEventSource<{ feature: Feature }[]>
|
||||||
maxSnapDistance?: number
|
maxSnapDistance?: number
|
||||||
snappedPointTags?: any
|
snappedPointTags?: any
|
||||||
requiresSnapping?: boolean
|
requiresSnapping?: boolean
|
||||||
|
@ -75,9 +115,8 @@ export default class LocationInput
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
super()
|
super()
|
||||||
this._snapTo = options?.snapTo?.map((features) =>
|
this._snapToRaw = options?.snapTo?.map(feats => feats.filter(f => f.feature.geometry.type !== "Point"))
|
||||||
features?.filter((feat) => feat.feature.geometry.type !== "Point")
|
this._snapTo = options?.snapTo?.bind((features) => UIEventSource.FromPromise(LocationInput.prepareSnapOnto(features.map(f => f.feature))))?.map(f => f ?? [])
|
||||||
)
|
|
||||||
this._maxSnapDistance = options?.maxSnapDistance
|
this._maxSnapDistance = options?.maxSnapDistance
|
||||||
this._centerLocation = options?.centerLocation ?? new UIEventSource<Loc>({
|
this._centerLocation = options?.centerLocation ?? new UIEventSource<Loc>({
|
||||||
lat: 0, lon: 0, zoom: 0
|
lat: 0, lon: 0, zoom: 0
|
||||||
|
@ -105,36 +144,39 @@ export default class LocationInput
|
||||||
this._matching_layer = LocationInput.matchLayer
|
this._matching_layer = LocationInput.matchLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate the location of the point based by snapping it onto a way
|
||||||
|
// As a side-effect, the actual snapped-onto way (if any) is saved into 'snappedOnto'
|
||||||
this._snappedPoint = this._centerLocation.map(
|
this._snappedPoint = this._centerLocation.map(
|
||||||
(loc) => {
|
(loc) => {
|
||||||
if (loc === undefined) {
|
if (loc === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// We reproject the location onto every 'snap-to-feature' and select the closest
|
// We reproject the location onto every 'snap-to-feature' and select the closest
|
||||||
|
|
||||||
let min = undefined
|
let min = undefined
|
||||||
let matchedWay = undefined
|
let matchedWay: Feature<LineString | Polygon> & {properties : {id : WayId}} = undefined
|
||||||
for (const feature of self._snapTo.data ?? []) {
|
for (const feature of self._snapTo.data ?? []) {
|
||||||
try {
|
try {
|
||||||
const nearestPointOnLine = GeoOperations.nearestPoint(feature.feature, [
|
const nearestPointOnLine = GeoOperations.nearestPoint(feature, [
|
||||||
loc.lon,
|
loc.lon,
|
||||||
loc.lat,
|
loc.lat,
|
||||||
])
|
])
|
||||||
if (min === undefined) {
|
if (min === undefined) {
|
||||||
min = nearestPointOnLine
|
min = nearestPointOnLine
|
||||||
matchedWay = feature.feature
|
matchedWay = feature
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (min.properties.dist > nearestPointOnLine.properties.dist) {
|
if (min.properties.dist > nearestPointOnLine.properties.dist) {
|
||||||
min = nearestPointOnLine
|
min = nearestPointOnLine
|
||||||
matchedWay = feature.feature
|
matchedWay = feature
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(
|
console.log(
|
||||||
"Snapping to a nearest point failed for ",
|
"Snapping to a nearest point failed for ",
|
||||||
feature.feature,
|
feature,
|
||||||
"due to ",
|
"due to ",
|
||||||
e
|
e
|
||||||
)
|
)
|
||||||
|
@ -145,6 +187,7 @@ export default class LocationInput
|
||||||
if (options?.requiresSnapping) {
|
if (options?.requiresSnapping) {
|
||||||
return undefined
|
return undefined
|
||||||
} else {
|
} else {
|
||||||
|
// No match found - the original coordinates are returned as is
|
||||||
return {
|
return {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
properties: options?.snappedPointTags ?? min.properties,
|
properties: options?.snappedPointTags ?? min.properties,
|
||||||
|
@ -153,7 +196,13 @@ export default class LocationInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
min.properties = options?.snappedPointTags ?? min.properties
|
min.properties = options?.snappedPointTags ?? min.properties
|
||||||
self.snappedOnto.setData(matchedWay)
|
if(matchedWay.properties.id.startsWith("relation/")){
|
||||||
|
// We matched a relation instead of a way
|
||||||
|
console.log("Snapping onto a relation. The relation is", matchedWay)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
self.snappedOnto.setData(<any> matchedWay)
|
||||||
return min
|
return min
|
||||||
},
|
},
|
||||||
[this._snapTo]
|
[this._snapTo]
|
||||||
|
@ -216,11 +265,11 @@ export default class LocationInput
|
||||||
this.clickLocation.addCallbackAndRunD((location) =>
|
this.clickLocation.addCallbackAndRunD((location) =>
|
||||||
this._centerLocation.setData(location)
|
this._centerLocation.setData(location)
|
||||||
)
|
)
|
||||||
if (this._snapTo !== undefined) {
|
if (this._snapToRaw !== undefined) {
|
||||||
// Show the lines to snap to
|
// Show the lines to snap to
|
||||||
console.log("Constructing the snap-to layer", this._snapTo)
|
console.log("Constructing the snap-to layer", this._snapToRaw)
|
||||||
new ShowDataMultiLayer({
|
new ShowDataMultiLayer({
|
||||||
features: StaticFeatureSource.fromDateless(this._snapTo),
|
features: StaticFeatureSource.fromDateless(this._snapToRaw),
|
||||||
zoomToFeatures: false,
|
zoomToFeatures: false,
|
||||||
leafletMap: this.map.leafletMap,
|
leafletMap: this.map.leafletMap,
|
||||||
layers: this._state.filteredLayers,
|
layers: this._state.filteredLayers,
|
||||||
|
|
Loading…
Reference in a new issue