Refactoring: fix rendering of new roads, generated by a split

This commit is contained in:
Pieter Vander Vennet 2023-04-20 01:52:23 +02:00
parent 840990c08b
commit 8eb2c68f79
34 changed files with 443 additions and 333 deletions

View file

@ -83,19 +83,9 @@
snapOnto: snapToWay
});
await state.changes.applyAction(newElementAction);
// The 'changes' should have created a new point, which added this into the 'featureProperties'
const newId = newElementAction.newElementId;
state.newFeatures.features.data.push({
type: "Feature",
properties: {
id: newId,
...TagUtils.KVtoProperties(tags)
},
geometry: {
type: "Point",
coordinates: [location.lon, location.lat]
}
});
state.newFeatures.features.ping();
const tagsStore = state.featureProperties.getStore(newId);
{
// Set some metainfo

View file

@ -58,6 +58,7 @@
])
}
};
// Normally, the 'Changes' will generate the new element. The 'notes' are an exception to this
state.newFeatures.features.data.push(feature);
state.newFeatures.features.ping();
state.selectedElement?.setData(feature);

View file

@ -5,7 +5,7 @@ import { OsmConnection, OsmServiceState } from "../../Logic/Osm/OsmConnection"
import { VariableUiElement } from "../Base/VariableUIElement"
import Loading from "../Base/Loading"
import Translations from "../i18n/Translations"
import { Store } from "../../Logic/UIEventSource"
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import { Translation } from "../i18n/Translation"
@ -13,13 +13,13 @@ class LoginButton extends SubtleButton {
constructor(
text: BaseUIElement | string,
state: {
osmConnection: OsmConnection
osmConnection?: OsmConnection
},
icon?: BaseUIElement | string
) {
super(icon ?? Svg.login_ui(), text)
this.onClick(() => {
state.osmConnection.AttemptLogin()
state.osmConnection?.AttemptLogin()
})
}
}
@ -32,13 +32,16 @@ export class LoginToggle extends VariableUiElement {
* If logging in is not possible for some reason, an appropriate error message is shown
*
* State contains the 'osmConnection' to work with
* @param el: Element to show when logged in
* @param text: To show on the login button. Default: nothing
* @param state: if no osmConnection is given, assumes test situation and will show 'el' as if logged in
*/
constructor(
el: BaseUIElement,
text: BaseUIElement | string,
state: {
readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
readonly osmConnection?: OsmConnection
readonly featureSwitchUserbadge?: Store<boolean>
}
) {
const loading = new Loading("Trying to log in...")
@ -51,14 +54,14 @@ export class LoginToggle extends VariableUiElement {
}
super(
state.osmConnection.loadingStatus.map(
state.osmConnection?.loadingStatus?.map(
(osmConnectionState) => {
if (state.featureSwitchUserbadge.data == false) {
if (state.featureSwitchUserbadge?.data == false) {
// All features to login with are disabled
return undefined
}
const apiState = state.osmConnection.apiIsOnline.data
const apiState = state.osmConnection?.apiIsOnline?.data ?? "online"
const apiTranslation = offlineModes[apiState]
if (apiTranslation !== undefined) {
return new Combine([
@ -77,15 +80,15 @@ export class LoginToggle extends VariableUiElement {
return el
}
// Error!
// Fallback
return new LoginButton(
Translations.t.general.loginFailed,
state,
Svg.invalid_svg()
)
},
[state.featureSwitchUserbadge, state.osmConnection.apiIsOnline]
)
[state.featureSwitchUserbadge, state.osmConnection?.apiIsOnline]
) ?? new ImmutableStore(el) //
)
}
}

View file

@ -2,33 +2,25 @@ import Toggle from "../Input/Toggle"
import Svg from "../../Svg"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton"
import { GeoOperations } from "../../Logic/GeoOperations"
import Combine from "../Base/Combine"
import { Button } from "../Base/Button"
import Translations from "../i18n/Translations"
import SplitAction from "../../Logic/Osm/Actions/SplitAction"
import Title from "../Base/Title"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { BBox } from "../../Logic/BBox"
import split_point from "../../assets/layers/split_point/split_point.json"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import FilteredLayer from "../../Models/FilteredLayer"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { LoginToggle } from "./LoginButton"
import { SpecialVisualizationState } from "../SpecialVisualization"
import SvelteUIElement from "../Base/SvelteUIElement"
import WaySplitMap from "../BigComponents/WaySplitMap.svelte"
import { OsmObject } from "../../Logic/Osm/OsmObject"
import { Feature, Point } from "geojson"
import { WayId } from "../../Models/OsmFeature"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes"
import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
export default class SplitRoadWizard extends Combine {
private static splitLayerStyling = new LayerConfig(
split_point,
"(BUILTIN) SplitRoadWizard.ts",
true
)
public dialogIsOpened: UIEventSource<boolean>
/**
@ -37,20 +29,34 @@ export default class SplitRoadWizard extends Combine {
* @param id: The id of the road to remove
* @param state: the state of the application
*/
constructor(id: string, state: SpecialVisualizationState) {
constructor(
id: WayId,
state: {
layout?: LayoutConfig
osmConnection?: OsmConnection
changes?: Changes
indexedFeatures?: IndexedFeatureSource
selectedElement?: UIEventSource<Feature>
}
) {
const t = Translations.t.split
// Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
const splitPoints = new UIEventSource<{ feature: any; freshness: Date }[]>([])
const splitPoints = new UIEventSource<Feature<Point>[]>([])
const hasBeenSplit = new UIEventSource(false)
// Toggle variable between show split button and map
const splitClicked = new UIEventSource<boolean>(false)
const leafletMap = new UIEventSource<BaseUIElement>(
SplitRoadWizard.setupMapComponent(id, splitPoints, state)
)
const leafletMap = new UIEventSource<BaseUIElement>(undefined)
function initMap() {
SplitRoadWizard.setupMapComponent(id, splitPoints).then((mapComponent) =>
leafletMap.setData(mapComponent.SetClass("w-full h-80"))
)
}
initMap()
// Toggle between splitmap
const splitButton = new SubtleButton(
@ -70,23 +76,19 @@ export default class SplitRoadWizard extends Combine {
splitClicked.setData(false)
const splitAction = new SplitAction(
id,
splitPoints.data.map((ff) => ff.feature.geometry.coordinates),
splitPoints.data.map((ff) => <[number, number]>(<Point>ff.geometry).coordinates),
{
theme: state?.layoutToUse?.id,
theme: state?.layout?.id,
},
5,
(coordinates) => {
state.allElements.ContainingFeatures.get(id).geometry["coordinates"] =
coordinates
}
5
)
await state.changes.applyAction(splitAction)
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))
initMap()
// Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219
ScrollableFullScreen.collapse()
state.selectedElement?.setData(undefined)
})
saveButton.SetClass("btn btn-primary mr-3")
@ -131,95 +133,14 @@ export default class SplitRoadWizard extends Combine {
})
}
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)
// Minimap on which you can select the points to be splitted
const miniMap = Minimap.createMiniMap({
background: state.backgroundLayer,
allowMoving: true,
leafletOptions: {
minZoom: 14,
},
private static async setupMapComponent(
id: WayId,
splitPoints: UIEventSource<Feature[]>
): Promise<BaseUIElement> {
const osmWay = await OsmObject.DownloadObjectAsync(id)
return new SvelteUIElement(WaySplitMap, {
osmWay,
splitPoints,
})
miniMap.SetStyle("width: 100%; height: 24rem").SetClass("rounded-xl overflow-hidden")
miniMap.installBounds(BBox.get(roadElement).pad(0.25), false)
// Define how a cut is displayed on the map
// Datalayer displaying the road and the cut points (if any)
new ShowDataMultiLayer({
features: StaticFeatureSource.fromGeojson([roadElement]),
layers: state.filteredLayers,
leafletMap: miniMap.leafletMap,
zoomToFeatures: true,
state,
})
new ShowDataLayer({
features: new StaticFeatureSource(splitPoints),
leafletMap: miniMap.leafletMap,
zoomToFeatures: false,
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.
* @param coordinates Clicked location, [lon, lat]
*/
function onMapClick(coordinates) {
// First, we check if there is another, already existing point nearby
const points = splitPoints.data
.map((f, i) => [f.feature, i])
.filter(
(p) => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5
)
.map((p) => p[1])
.sort((a, b) => a - b)
.reverse(/*Copy/derived list, inplace reverse is fine*/)
if (points.length > 0) {
for (const point of points) {
splitPoints.data.splice(point, 1)
}
splitPoints.ping()
return
}
// Get nearest point on the road
const pointOnRoad = GeoOperations.nearestPoint(<any>roadElement, coordinates) // pointOnRoad is a geojson
// Update point properties to let it match the layer
pointOnRoad.properties["_split_point"] = "yes"
// Add it to the list of all points and notify observers
splitPoints.data.push({ feature: pointOnRoad, freshness: new Date() }) // show the point on the data layer
splitPoints.ping() // not updated using .setData, so manually ping observers
}
// When clicked, pass clicked location coordinates to onMapClick function
miniMap.leafletMap.addCallbackAndRunD((leafletMap) =>
leafletMap.on("click", (mouseEvent: LeafletMouseEvent) => {
onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat])
})
)
return miniMap
}
}