parent
c8971a1cbe
commit
8f51dd8d64
4 changed files with 123 additions and 70 deletions
|
@ -16,6 +16,7 @@ export default class SplitAction extends OsmChangeAction {
|
|||
private readonly _splitPointsCoordinates: [number, number][] // lon, lat
|
||||
private _meta: { theme: string; changeType: "split" }
|
||||
private _toleranceInMeters: number
|
||||
private _withNewCoordinates: (coordinates: [number, number][]) => void
|
||||
|
||||
/**
|
||||
* Create a changedescription for splitting a point.
|
||||
|
@ -24,17 +25,20 @@ export default class SplitAction extends OsmChangeAction {
|
|||
* @param splitPointCoordinates: lon, lat
|
||||
* @param meta
|
||||
* @param toleranceInMeters: if a splitpoint closer then this amount of meters to an existing point, the existing point will be used to split the line instead of a new point
|
||||
* @param withNewCoordinates: an optional callback which will leak the new coordinates of the original way
|
||||
*/
|
||||
constructor(
|
||||
wayId: string,
|
||||
splitPointCoordinates: [number, number][],
|
||||
meta: { theme: string },
|
||||
toleranceInMeters = 5
|
||||
toleranceInMeters = 5,
|
||||
withNewCoordinates?: (coordinates: [number, number][]) => void
|
||||
) {
|
||||
super(wayId, true)
|
||||
this.wayId = wayId
|
||||
this._splitPointsCoordinates = splitPointCoordinates
|
||||
this._toleranceInMeters = toleranceInMeters
|
||||
this._withNewCoordinates = withNewCoordinates
|
||||
this._meta = { ...meta, changeType: "split" }
|
||||
}
|
||||
|
||||
|
@ -59,7 +63,7 @@ export default class SplitAction extends OsmChangeAction {
|
|||
const originalElement = <OsmWay>await OsmObject.DownloadObjectAsync(this.wayId)
|
||||
const originalNodes = originalElement.nodes
|
||||
|
||||
// First, calculate splitpoints and remove points close to one another
|
||||
// First, calculate the splitpoints and remove points close to one another
|
||||
const splitInfo = this.CalculateSplitCoordinates(originalElement, this._toleranceInMeters)
|
||||
// Now we have a list with e.g.
|
||||
// [ { originalIndex: 0}, {originalIndex: 1, doSplit: true}, {originalIndex: 2}, {originalIndex: undefined, doSplit: true}, {originalIndex: 3}]
|
||||
|
@ -90,7 +94,7 @@ export default class SplitAction extends OsmChangeAction {
|
|||
}
|
||||
|
||||
const changeDescription: ChangeDescription[] = []
|
||||
// Let's create the new points as needed
|
||||
// Let's create the new nodes as needed
|
||||
for (const element of splitInfo) {
|
||||
if (element.originalIndex >= 0) {
|
||||
continue
|
||||
|
@ -114,17 +118,21 @@ export default class SplitAction extends OsmChangeAction {
|
|||
for (const wayPart of wayParts) {
|
||||
let isOriginal = wayPart === longest
|
||||
if (isOriginal) {
|
||||
// We change the actual element!
|
||||
// We change the existing way
|
||||
const nodeIds = wayPart.map((p) => p.originalIndex)
|
||||
const newCoordinates = wayPart.map((p) => p.lngLat)
|
||||
changeDescription.push({
|
||||
type: "way",
|
||||
id: originalElement.id,
|
||||
changes: {
|
||||
coordinates: wayPart.map((p) => p.lngLat),
|
||||
coordinates: newCoordinates,
|
||||
nodes: nodeIds,
|
||||
},
|
||||
meta: this._meta,
|
||||
})
|
||||
if (this._withNewCoordinates) {
|
||||
this._withNewCoordinates(newCoordinates)
|
||||
}
|
||||
allWayIdsInOrder.push(originalElement.id)
|
||||
allWaysNodesInOrder.push(nodeIds)
|
||||
} else {
|
||||
|
@ -141,6 +149,10 @@ export default class SplitAction extends OsmChangeAction {
|
|||
kv.push({ k: k, v: originalElement.tags[k] })
|
||||
}
|
||||
const nodeIds = wayPart.map((p) => p.originalIndex)
|
||||
if (nodeIds.length <= 1) {
|
||||
console.error("Got a segment with only one node - skipping")
|
||||
continue
|
||||
}
|
||||
changeDescription.push({
|
||||
type: "way",
|
||||
id: id,
|
||||
|
|
|
@ -22,8 +22,10 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
|||
import { ElementStorage } from "../../Logic/ElementStorage"
|
||||
import BaseLayer from "../../Models/BaseLayer"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
|
||||
export default class SplitRoadWizard extends Toggle {
|
||||
export default class SplitRoadWizard extends Combine {
|
||||
// @ts-ignore
|
||||
private static splitLayerStyling = new LayerConfig(
|
||||
split_point,
|
||||
|
@ -63,6 +65,106 @@ export default class SplitRoadWizard extends Toggle {
|
|||
|
||||
// Toggle variable between show split button and map
|
||||
const splitClicked = new UIEventSource<boolean>(false)
|
||||
|
||||
const leafletMap = new UIEventSource<BaseUIElement>(
|
||||
SplitRoadWizard.setupMapComponent(id, splitPoints, state)
|
||||
)
|
||||
|
||||
// Toggle between splitmap
|
||||
const splitButton = new SubtleButton(
|
||||
Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"),
|
||||
new Toggle(
|
||||
t.splitAgain.Clone().SetClass("text-lg font-bold"),
|
||||
t.inviteToSplit.Clone().SetClass("text-lg font-bold"),
|
||||
hasBeenSplit
|
||||
)
|
||||
)
|
||||
splitButton.onClick(() => {
|
||||
splitClicked.setData(true)
|
||||
})
|
||||
|
||||
// Only show the splitButton if logged in, else show login prompt
|
||||
const loginBtn = t.loginToSplit
|
||||
.Clone()
|
||||
.onClick(() => state.osmConnection.AttemptLogin())
|
||||
.SetClass("login-button-friendly")
|
||||
const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn)
|
||||
|
||||
// Save button
|
||||
const saveButton = new Button(t.split.Clone(), async () => {
|
||||
hasBeenSplit.setData(true)
|
||||
splitClicked.setData(false)
|
||||
const splitAction = new SplitAction(
|
||||
id,
|
||||
splitPoints.data.map((ff) => ff.feature.geometry.coordinates),
|
||||
{
|
||||
theme: state?.layoutToUse?.id,
|
||||
},
|
||||
5,
|
||||
(coordinates) => {
|
||||
state.allElements.ContainingFeatures.get(id).geometry["coordinates"] =
|
||||
coordinates
|
||||
}
|
||||
)
|
||||
await state.changes.applyAction(splitAction)
|
||||
// We throw away the old map and splitpoints, and create a new map from scratch
|
||||
splitPoints.setData([])
|
||||
leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state))
|
||||
})
|
||||
|
||||
saveButton.SetClass("btn btn-primary mr-3")
|
||||
const disabledSaveButton = new Button("Split", undefined)
|
||||
disabledSaveButton.SetClass("btn btn-disabled mr-3")
|
||||
// Only show the save button if there are split points defined
|
||||
const saveToggle = new Toggle(
|
||||
disabledSaveButton,
|
||||
saveButton,
|
||||
splitPoints.map((data) => data.length === 0)
|
||||
)
|
||||
|
||||
const cancelButton = Translations.t.general.cancel
|
||||
.Clone() // Not using Button() element to prevent full width button
|
||||
.SetClass("btn btn-secondary mr-3")
|
||||
.onClick(() => {
|
||||
splitPoints.setData([])
|
||||
splitClicked.setData(false)
|
||||
})
|
||||
|
||||
cancelButton.SetClass("btn btn-secondary block")
|
||||
|
||||
const splitTitle = new Title(t.splitTitle)
|
||||
|
||||
const mapView = new Combine([
|
||||
splitTitle,
|
||||
new VariableUiElement(leafletMap),
|
||||
new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"),
|
||||
])
|
||||
mapView.SetClass("question")
|
||||
super([
|
||||
Toggle.If(hasBeenSplit, () =>
|
||||
t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full")
|
||||
),
|
||||
new Toggle(mapView, splitToggle, splitClicked),
|
||||
])
|
||||
this.dialogIsOpened = splitClicked
|
||||
}
|
||||
|
||||
private static setupMapComponent(
|
||||
id: string,
|
||||
splitPoints: UIEventSource<{ feature: any; freshness: Date }[]>,
|
||||
state: {
|
||||
filteredLayers: UIEventSource<FilteredLayer[]>
|
||||
backgroundLayer: UIEventSource<BaseLayer>
|
||||
featureSwitchIsTesting: UIEventSource<boolean>
|
||||
featureSwitchIsDebugging: UIEventSource<boolean>
|
||||
featureSwitchShowAllQuestions: UIEventSource<boolean>
|
||||
osmConnection: OsmConnection
|
||||
featureSwitchUserbadge: UIEventSource<boolean>
|
||||
changes: Changes
|
||||
layoutToUse: LayoutConfig
|
||||
allElements: ElementStorage
|
||||
}
|
||||
): BaseUIElement {
|
||||
// Load the road with given id on the minimap
|
||||
const roadElement = state.allElements.ContainingFeatures.get(id)
|
||||
|
||||
|
@ -96,7 +198,6 @@ export default class SplitRoadWizard extends Toggle {
|
|||
layerToShow: SplitRoadWizard.splitLayerStyling,
|
||||
state,
|
||||
})
|
||||
|
||||
/**
|
||||
* Handles a click on the overleaf map.
|
||||
* Finds the closest intersection with the road and adds a point there, ready to confirm the cut.
|
||||
|
@ -137,67 +238,6 @@ export default class SplitRoadWizard extends Toggle {
|
|||
onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat])
|
||||
})
|
||||
)
|
||||
|
||||
// Toggle between splitmap
|
||||
const splitButton = new SubtleButton(
|
||||
Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"),
|
||||
t.inviteToSplit.Clone().SetClass("text-lg font-bold")
|
||||
)
|
||||
splitButton.onClick(() => {
|
||||
splitClicked.setData(true)
|
||||
})
|
||||
|
||||
// Only show the splitButton if logged in, else show login prompt
|
||||
const loginBtn = t.loginToSplit
|
||||
.Clone()
|
||||
.onClick(() => state.osmConnection.AttemptLogin())
|
||||
.SetClass("login-button-friendly")
|
||||
const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn)
|
||||
|
||||
// Save button
|
||||
const saveButton = new Button(t.split.Clone(), () => {
|
||||
hasBeenSplit.setData(true)
|
||||
state.changes.applyAction(
|
||||
new SplitAction(
|
||||
id,
|
||||
splitPoints.data.map((ff) => ff.feature.geometry.coordinates),
|
||||
{
|
||||
theme: state?.layoutToUse?.id,
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
saveButton.SetClass("btn btn-primary mr-3")
|
||||
const disabledSaveButton = new Button("Split", undefined)
|
||||
disabledSaveButton.SetClass("btn btn-disabled mr-3")
|
||||
// Only show the save button if there are split points defined
|
||||
const saveToggle = new Toggle(
|
||||
disabledSaveButton,
|
||||
saveButton,
|
||||
splitPoints.map((data) => data.length === 0)
|
||||
)
|
||||
|
||||
const cancelButton = Translations.t.general.cancel
|
||||
.Clone() // Not using Button() element to prevent full width button
|
||||
.SetClass("btn btn-secondary mr-3")
|
||||
.onClick(() => {
|
||||
splitPoints.setData([])
|
||||
splitClicked.setData(false)
|
||||
})
|
||||
|
||||
cancelButton.SetClass("btn btn-secondary block")
|
||||
|
||||
const splitTitle = new Title(t.splitTitle)
|
||||
|
||||
const mapView = new Combine([
|
||||
splitTitle,
|
||||
miniMap,
|
||||
new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"),
|
||||
])
|
||||
mapView.SetClass("question")
|
||||
const confirm = new Toggle(mapView, splitToggle, splitClicked)
|
||||
super(t.hasBeenSplit.Clone(), confirm, hasBeenSplit)
|
||||
this.dialogIsOpened = splitClicked
|
||||
return miniMap
|
||||
}
|
||||
}
|
||||
|
|
|
@ -913,6 +913,7 @@
|
|||
"inviteToSplit": "Split this road in smaller segments. This allows to give different properties to parts of the road.",
|
||||
"loginToSplit": "You must be logged in to split a road",
|
||||
"split": "Split",
|
||||
"splitAgain": "Split this road again",
|
||||
"splitTitle": "Choose on the map where the properties of this road change"
|
||||
},
|
||||
"translations": {
|
||||
|
|
|
@ -7594,7 +7594,7 @@ describe("GenerateCache", () => {
|
|||
}
|
||||
mkdirSync(dir + "np-cache")
|
||||
initDownloads(
|
||||
"(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22man_made%22%3D%22watermill%22%5D%3Bnwr%5B%22selected%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*foot.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*hiking.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*bycicle.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*horse.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B"
|
||||
"(nwr%5B%22amenity%22%3D%22toilets%22%5D%3Bnwr%5B%22amenity%22%3D%22parking%22%5D%3Bnwr%5B%22amenity%22%3D%22bench%22%5D%3Bnwr%5B%22id%22%3D%22location_track%22%5D%3Bnwr%5B%22id%22%3D%22gps%22%5D%3Bnwr%5B%22information%22%3D%22board%22%5D%3Bnwr%5B%22leisure%22%3D%22picnic_table%22%5D%3Bnwr%5B%22selected%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Ahome%22%3D%22yes%22%5D%3Bnwr%5B%22user%3Alocation%22%3D%22yes%22%5D%3Bnwr%5B%22leisure%22%3D%22nature_reserve%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22boundary%22%3D%22protected_area%22%5D%5B%22protect_class%22!%3D%2298%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22visitor_centre%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22information%22%3D%22office%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*foot.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*hiking.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*bycicle.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22route%22~%22%5E(.*horse.*)%24%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22leisure%22%3D%22bird_hide%22%5D%5B%22operator%22~%22%5E(.*%5BnN%5Datuurpunt.*)%24%22%5D%3Bnwr%5B%22amenity%22%3D%22drinking_water%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3Bnwr%5B%22drinking_water%22%3D%22yes%22%5D%5B%22man_made%22!%3D%22reservoir_covered%22%5D%5B%22access%22!%3D%22permissive%22%5D%5B%22access%22!%3D%22private%22%5D%3B)%3Bout%20body%3Bout%20meta%3B%3E%3Bout%20skel%20qt%3B"
|
||||
)
|
||||
await main([
|
||||
"natuurpunt",
|
||||
|
|
Loading…
Reference in a new issue