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

@ -1,6 +1,9 @@
import { Changes } from "../Osm/Changes"
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore";
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
/**
* Applies tag changes onto the featureStore
*/
export default class ChangeToElementsActor {
constructor(changes: Changes, allElements: FeaturePropertiesStore) {
changes.pendingChanges.addCallbackAndRun((changes) => {

View file

@ -55,12 +55,13 @@ class SingleTileSaver {
*/
export default class SaveFeatureSourceToLocalStorage {
constructor(
backend: string,
layername: string,
zoomlevel: number,
features: FeatureSource,
featureProperties: FeaturePropertiesStore
) {
const storage = TileLocalStorage.construct<Feature[]>(layername)
const storage = TileLocalStorage.construct<Feature[]>(backend, layername)
const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>()
features.features.addCallbackAndRunD((features) => {
const sliced = GeoOperations.slice(zoomlevel, features)

View file

@ -17,14 +17,15 @@ export default class TileLocalStorage<T> {
this._layername = layername
}
public static construct<T>(layername: string): TileLocalStorage<T> {
const cached = TileLocalStorage.perLayer[layername]
public static construct<T>(backend: string, layername: string): TileLocalStorage<T> {
const key = backend + "_" + layername
const cached = TileLocalStorage.perLayer[key]
if (cached) {
return cached
}
const tls = new TileLocalStorage<T>(layername)
TileLocalStorage.perLayer[layername] = tls
const tls = new TileLocalStorage<T>(key)
TileLocalStorage.perLayer[key] = tls
return tls
}
@ -46,7 +47,7 @@ export default class TileLocalStorage<T> {
return src
}
private async SetIdb(tileIndex: number, data): Promise<void> {
private async SetIdb(tileIndex: number, data: any): Promise<void> {
try {
await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data)
} catch (e) {

View file

@ -3,22 +3,18 @@
*/
import { Changes } from "../../Osm/Changes"
import { UIEventSource } from "../../UIEventSource"
import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource"
import FilteredLayer from "../../../Models/FilteredLayer"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription"
import { Feature } from "geojson"
export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
public readonly features: UIEventSource<Feature[]> =
new UIEventSource<Feature[]>([])
public readonly layer: FilteredLayer
export default class ChangeGeometryApplicator implements FeatureSource {
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
private readonly source: IndexedFeatureSource
private readonly changes: Changes
constructor(source: IndexedFeatureSource & FeatureSourceForLayer, changes: Changes) {
constructor(source: IndexedFeatureSource, changes: Changes) {
this.source = source
this.changes = changes
this.layer = source.layer
this.features = new UIEventSource<Feature[]>(undefined)
@ -30,10 +26,10 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
private update() {
const upstreamFeatures = this.source.features.data
const upstreamIds = this.source.containedIds.data
const upstreamIds = this.source.featuresById.data
const changesToApply = this.changes.allChanges.data?.filter(
(ch) =>
// Does upsteram have this element? If not, we skip
// Does upstream have this element? If not, we skip
upstreamIds.has(ch.type + "/" + ch.id) &&
// Are any (geometry) changes defined?
ch.changes !== undefined &&
@ -61,7 +57,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer {
for (const feature of upstreamFeatures) {
const changesForFeature = changesPerId.get(feature.properties.id)
if (changesForFeature === undefined) {
// No changes for this element
// No changes for this element - simply pass it along to downstream
newFeatures.push(feature)
continue
}

View file

@ -1,5 +1,5 @@
import { Store, UIEventSource } from "../../UIEventSource"
import { FeatureSource , IndexedFeatureSource } from "../FeatureSource"
import { FeatureSource, IndexedFeatureSource } from "../FeatureSource"
import { Feature } from "geojson"
import { Utils } from "../../../Utils"
@ -19,6 +19,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
this._featuresById = new UIEventSource<Map<string, Feature>>(undefined)
this.featuresById = this._featuresById
const self = this
sources = Utils.NoNull(sources)
for (let source of sources) {
source.features.addCallback(() => {
self.addData(sources.map((s) => s.features.data))
@ -28,7 +29,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource {
this._sources = sources
}
protected addSource(source: FeatureSource) {
public addSource(source: FeatureSource) {
this._sources.push(source)
source.features.addCallbackAndRun(() => {
this.addData(this._sources.map((s) => s.features.data))

View file

@ -4,13 +4,14 @@ import { FeatureSource } from "../FeatureSource"
import { Or } from "../../Tags/Or"
import FeatureSwitchState from "../../State/FeatureSwitchState"
import OverpassFeatureSource from "./OverpassFeatureSource"
import { ImmutableStore, Store } from "../../UIEventSource"
import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
import OsmFeatureSource from "./OsmFeatureSource"
import FeatureSourceMerger from "./FeatureSourceMerger"
import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
import { BBox } from "../../BBox"
import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource"
import StaticFeatureSource from "./StaticFeatureSource"
import { OsmPreferences } from "../../Osm/OsmPreferences"
/**
* This source will fetch the needed data from various sources for the given layout.
@ -18,15 +19,14 @@ import StaticFeatureSource from "./StaticFeatureSource"
* Note that special layers (with `source=null` will be ignored)
*/
export default class LayoutSource extends FeatureSourceMerger {
private readonly _isLoading: UIEventSource<boolean> = new UIEventSource<boolean>(false)
/**
* Indicates if a data source is loading something
* TODO fixme
*/
public readonly isLoading: Store<boolean> = new ImmutableStore(false)
public readonly isLoading: Store<boolean> = this._isLoading
constructor(
layers: LayerConfig[],
featureSwitches: FeatureSwitchState,
newAndChangedElements: FeatureSource,
mapProperties: { bounds: Store<BBox>; zoom: Store<number> },
backend: string,
isDisplayed: (id: string) => Store<boolean>
@ -39,7 +39,7 @@ export default class LayoutSource extends FeatureSourceMerger {
const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined)
const fromCache = osmLayers.map(
(l) =>
new LocalStorageFeatureSource(l.id, 15, mapProperties, {
new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
isActive: isDisplayed(l.id),
})
)
@ -56,7 +56,17 @@ export default class LayoutSource extends FeatureSourceMerger {
)
const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? []))
super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources, ...fromCache)
super(overpassSource, osmApiSource, ...geojsonSources, ...fromCache)
const self = this
function setIsLoading() {
const loading = overpassSource?.runningQuery?.data && osmApiSource?.isRunning?.data
self._isLoading.setData(loading)
}
overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading())
osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading())
}
private static setupGeojsonSource(
@ -83,9 +93,9 @@ export default class LayoutSource extends FeatureSourceMerger {
zoom: Store<number>,
backend: string,
featureSwitches: FeatureSwitchState
): FeatureSource {
): OsmFeatureSource | undefined {
if (osmLayers.length == 0) {
return new StaticFeatureSource(new ImmutableStore([]))
return undefined
}
const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom))
const isActive = zoom.mapD((z) => {
@ -115,9 +125,9 @@ export default class LayoutSource extends FeatureSourceMerger {
bounds: Store<BBox>,
zoom: Store<number>,
featureSwitches: FeatureSwitchState
): FeatureSource {
): OverpassFeatureSource | undefined {
if (osmLayers.length == 0) {
return new StaticFeatureSource(new ImmutableStore([]))
return undefined
}
const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom))
const isActive = zoom.mapD((z) => {

View file

@ -1,13 +1,12 @@
import { Changes } from "../../Osm/Changes"
import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject"
import { FeatureSource } from "../FeatureSource"
import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource"
import { UIEventSource } from "../../UIEventSource"
import { ChangeDescription } from "../../Osm/Actions/ChangeDescription"
import { ElementStorage } from "../../ElementStorage"
import { OsmId, OsmTags } from "../../../Models/OsmFeature"
import { Feature } from "geojson"
export class NewGeometryFromChangesFeatureSource implements FeatureSource {
export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource {
// This class name truly puts the 'Java' into 'Javascript'
/**
@ -18,7 +17,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
*/
public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) {
constructor(changes: Changes, allElementStorage: IndexedFeatureSource, backendUrl: string) {
const seenChanges = new Set<ChangeDescription>()
const features = this.features.data
const self = this
@ -53,7 +52,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
// In _most_ of the cases, this means that this _isn't_ a new object
// However, when a point is snapped to an already existing point, we have to create a representation for this point!
// For this, we introspect the change
if (allElementStorage.has(change.type + "/" + change.id)) {
if (allElementStorage.featuresById.data.has(change.type + "/" + change.id)) {
// The current point already exists, we don't have to do anything here
continue
}
@ -65,7 +64,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource {
feat.tags[kv.k] = kv.v
}
const geojson = feat.asGeoJson()
allElementStorage.addOrGetElement(geojson)
self.features.data.push(geojson)
self.features.ping()
})

View file

@ -41,7 +41,6 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
this.isActive = options.isActive ?? new ImmutableStore(true)
this._backend = options.backend ?? "https://www.openstreetmap.org"
this._bounds.addCallbackAndRunD((bbox) => this.loadData(bbox))
console.log("Allowed tags are:", this.allowedTags)
}
private async loadData(bbox: BBox) {
@ -108,7 +107,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
}
private async LoadTile(z, x, y): Promise<void> {
console.log("OsmFeatureSource: loading ", z, x, y)
console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend)
if (z >= 22) {
throw "This is an absurd high zoom level"
}

View file

@ -22,13 +22,18 @@ export interface SnappingOptions {
* The resulting snap coordinates will be written into this UIEventSource
*/
snapLocation?: UIEventSource<{ lon: number; lat: number }>
/**
* If the projected point is within `reusePointWithin`-meter of an already existing point
*/
reusePointWithin?: number
}
export default class SnappingFeatureSource implements FeatureSource {
public readonly features: Store<Feature<Point>[]>
private readonly _snappedTo: UIEventSource<string>
/*Contains the id of the way it snapped to*/
public readonly snappedTo: Store<string>
private readonly _snappedTo: UIEventSource<string>
constructor(
snapTo: FeatureSource,

View file

@ -7,6 +7,7 @@ import StaticFeatureSource from "../Sources/StaticFeatureSource"
export default class LocalStorageFeatureSource extends DynamicTileSource {
constructor(
backend: string,
layername: string,
zoomlevel: number,
mapProperties: {
@ -17,7 +18,7 @@ export default class LocalStorageFeatureSource extends DynamicTileSource {
isActive?: Store<boolean>
}
) {
const storage = TileLocalStorage.construct<Feature[]>(layername)
const storage = TileLocalStorage.construct<Feature[]>(backend, layername)
super(
zoomlevel,
(tileIndex) =>

View file

@ -294,6 +294,10 @@ export class GeoOperations {
* Mostly used as helper for 'nearestPoint'
* @param way
*/
public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString>
public static forceLineString(
way: Feature<MultiLineString | MultiPolygon>
): Feature<MultiLineString>
public static forceLineString(
way: Feature<LineString | MultiLineString | Polygon | MultiPolygon>
): Feature<LineString | MultiLineString> {
@ -972,4 +976,9 @@ export class GeoOperations {
},
}
}
static centerpointCoordinatesObj(geojson: Feature) {
const [lon, lat] = GeoOperations.centerpointCoordinates(geojson)
return { lon, lat }
}
}

View file

@ -4,6 +4,7 @@ import { GeoOperations } from "../../GeoOperations"
import OsmChangeAction from "./OsmChangeAction"
import { ChangeDescription } from "./ChangeDescription"
import RelationSplitHandler from "./RelationSplitHandler"
import { Feature, LineString } from "geojson"
interface SplitInfo {
originalIndex?: number // or negative for new elements
@ -14,9 +15,9 @@ interface SplitInfo {
export default class SplitAction extends OsmChangeAction {
private readonly wayId: string
private readonly _splitPointsCoordinates: [number, number][] // lon, lat
private _meta: { theme: string; changeType: "split" }
private _toleranceInMeters: number
private _withNewCoordinates: (coordinates: [number, number][]) => void
private readonly _meta: { theme: string; changeType: "split" }
private readonly _toleranceInMeters: number
private readonly _withNewCoordinates: (coordinates: [number, number][]) => void
/**
* Create a changedescription for splitting a point.
@ -197,7 +198,7 @@ export default class SplitAction extends OsmChangeAction {
* If another point is closer then ~5m, we reuse that point
*/
private CalculateSplitCoordinates(osmWay: OsmWay, toleranceInM = 5): SplitInfo[] {
const wayGeoJson = osmWay.asGeoJson()
const wayGeoJson = <Feature<LineString>>osmWay.asGeoJson()
// Should be [lon, lat][]
const originalPoints: [number, number][] = osmWay.coordinates.map((c) => [c[1], c[0]])
const allPoints: {

View file

@ -18,10 +18,6 @@ import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesSto
* Needs an authenticator via OsmConnection
*/
export class Changes {
/**
* All the newly created features as featureSource + all the modified features
*/
public readonly features = new UIEventSource<{ feature: any; freshness: Date }[]>([])
public readonly pendingChanges: UIEventSource<ChangeDescription[]> =
LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)

View file

@ -213,7 +213,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger {
move("changeset", "_last_edit:changeset")
move("timestamp", "_last_edit:timestamp")
move("version", "_version_number")
feature.properties._backend = "https://openstreetmap.org"
feature.properties._backend = feature.properties._backend ?? "https://openstreetmap.org"
return movedSomething
}
}

View file

@ -38,8 +38,9 @@ export class IdbLocalStorage {
return src
}
public static SetDirectly(key: string, value): Promise<void> {
return idb.set(key, value)
public static SetDirectly(key: string, value: any): Promise<void> {
const copy = Utils.Clone(value)
return idb.set(key, copy)
}
static GetDirectly(key: string): Promise<void> {

View file

@ -43,6 +43,7 @@ export default class Constants {
public static readonly no_include = [
"conflation",
"split_point",
"split_road",
"current_view",
"matchpoint",
"import_candidate",

View file

@ -596,6 +596,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
id: "split-button",
render: { "*": "{split_button()}" },
})
delete json.allowSplit
}
if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) {
@ -611,7 +612,16 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
})
}
if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "all_tags")) {
if (
json.source !== "special" &&
json.source !== "special:library" &&
json.tagRenderings &&
!json.tagRenderings.some((tr) => tr["id"] === "last_edit")
) {
json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit"))
}
if (!ValidationUtils.hasSpecialVisualisation(json, "all_tags")) {
const trc: TagRenderingConfigJson = {
id: "all-tags",
render: { "*": "{all_tags()}" },
@ -623,16 +633,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
],
},
}
json.tagRenderings.push(trc)
}
if (
json.source !== "special" &&
json.source !== "special:library" &&
json.tagRenderings &&
!json.tagRenderings.some((tr) => tr["id"] === "last_edit")
) {
json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit"))
json.tagRenderings?.push(trc)
}
return { result: json }

View file

@ -26,7 +26,6 @@ import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"
import ShowDataLayer from "../UI/Map/ShowDataLayer"
import TitleHandler from "../Logic/Actors/TitleHandler"
@ -39,9 +38,10 @@ import Hotkeys from "../UI/Base/Hotkeys"
import Translations from "../UI/i18n/Translations"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource"
import { MenuState } from "./MenuState"
import MetaTagging from "../Logic/MetaTagging"
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"
import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
/**
*
@ -65,7 +65,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
readonly selectedElement: UIEventSource<Feature>
readonly mapProperties: MapProperties & ExportableMap
readonly dataIsLoading: Store<boolean> // TODO
readonly dataIsLoading: Store<boolean>
readonly guistate: MenuState
readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO
@ -80,6 +80,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
readonly geolocation: GeoLocationHandler
readonly lastClickObject: WritableFeatureSource
constructor(layout: LayoutConfig) {
this.layout = layout
this.guistate = new MenuState(layout.id)
@ -121,49 +122,69 @@ export default class ThemeViewState implements SpecialVisualizationState {
const self = this
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
this.newFeatures = new SimpleFeatureSource(undefined)
const layoutSource = new LayoutSource(
layout.layers,
this.featureSwitches,
this.newFeatures,
this.mapProperties,
this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed
)
this.indexedFeatures = layoutSource
this.dataIsLoading = layoutSource.isLoading
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
this.mapProperties.lastClickLocation,
this.layout
))
const indexedElements = this.indexedFeatures
this.featureProperties = new FeaturePropertiesStore(indexedElements)
const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter(
(l) => l.layerDef?.source !== null
),
indexedElements,
{
constructStore: (features, layer) => new GeoIndexedStoreForLayer(features, layer),
handleLeftovers: (features) => {
console.warn(
"Got ",
features.length,
"leftover features, such as",
features[0].properties
)
},
}
)
this.perLayer = perLayer.perLayer
{
/* Setup the layout source
* A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too
*/
const layoutSource = new LayoutSource(
layout.layers,
this.featureSwitches,
this.mapProperties,
this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed
)
this.indexedFeatures = layoutSource
this.dataIsLoading = layoutSource.isLoading
const indexedElements = this.indexedFeatures
this.featureProperties = new FeaturePropertiesStore(indexedElements)
this.changes = new Changes(
{
dryRun: this.featureSwitches.featureSwitchIsTesting,
allElements: indexedElements,
featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection,
historicalUserLocations: this.geolocation.historicalUserLocations,
},
layout?.isLeftRightSensitive() ?? false
)
this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes,
indexedElements,
this.osmConnection.Backend()
)
layoutSource.addSource(this.newFeatures)
const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter(
(l) => l.layerDef?.source !== null
),
new ChangeGeometryApplicator(this.indexedFeatures, this.changes),
{
constructStore: (features, layer) =>
new GeoIndexedStoreForLayer(features, layer),
handleLeftovers: (features) => {
console.warn(
"Got ",
features.length,
"leftover features, such as",
features[0].properties
)
},
}
)
this.perLayer = perLayer.perLayer
}
this.perLayer.forEach((fs) => {
new SaveFeatureSourceToLocalStorage(
/* TODO enable new SaveFeatureSourceToLocalStorage(
this.osmConnection.Backend(),
fs.layer.layerDef.id,
15,
fs,
this.featureProperties
)
)//*/
const filtered = new FilteringFeatureSource(
fs.layer,
@ -187,16 +208,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
})
})
this.changes = new Changes(
{
dryRun: this.featureSwitches.featureSwitchIsTesting,
allElements: indexedElements,
featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection,
historicalUserLocations: this.geolocation.historicalUserLocations,
},
layout?.isLeftRightSensitive() ?? false
)
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
this.mapProperties.lastClickLocation,
this.layout
))
this.initActors()
this.drawSpecialLayers(lastClick)
@ -211,9 +226,13 @@ export default class ThemeViewState implements SpecialVisualizationState {
private miscSetup() {
this.userRelatedState.markLayoutAsVisited(this.layout)
this.selectedElement.addCallbackAndRunD(() => {
// As soon as we have a selected element, we clear it
this.selectedElement.addCallbackAndRunD((feature) => {
// As soon as we have a selected element, we clear the selected element
// This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
// The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
if (feature.properties.id === "last_click") {
return
}
this.lastClickObject.features.setData([])
})
}

View file

@ -11,7 +11,6 @@ import { Utils } from "../../Utils"
import { MapillaryLink } from "./MapillaryLink"
import { OpenIdEditor, OpenJosm } from "./CopyrightPanel"
import Toggle from "../Input/Toggle"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { DefaultGuiState } from "../DefaultGuiState"
export class BackToThemeOverview extends Toggle {
@ -78,14 +77,6 @@ export class ActionButtons extends Combine {
new OpenIdEditor(state, iconStyle),
new MapillaryLink(state, iconStyle),
new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"),
new SubtleButton(
Svg.translate_ui().SetStyle(iconStyle),
Translations.t.translations.activateButton
).onClick(() => {
ScrollableFullScreen.collapse()
state.defaultGuiState.userInfoIsOpened.setData(true)
state.defaultGuiState.userInfoFocusedQuestion.setData("translation-mode")
}),
])
this.SetClass("block w-full link-no-underline")
}

View file

@ -0,0 +1,104 @@
<script lang="ts">
/**
* This component shows a map which focuses on a single OSM-Way (linestring) feature.
* Clicking the map will add a new 'scissor' point, projected on the linestring (and possible snapped to an already existing node within the linestring;
* clicking this point again will remove it.
* The bound 'value' will contain the location of these projected points.
* Points are not coalesced with already existing nodes within the way; it is up to the code actually splitting the way to decide to reuse an existing point or not
*
* This component is _not_ responsible for the rest of the flow, e.g. the confirm button
*/
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson";
import split_point from "../../assets/layers/split_point/split_point.json";
import split_road from "../../assets/layers/split_road/split_road.json";
import { UIEventSource } from "../../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl";
import type { MapProperties } from "../../Models/MapProperties";
import { MapLibreAdaptor } from "../Map/MapLibreAdaptor";
import MaplibreMap from "../Map/MaplibreMap.svelte";
import { OsmWay } from "../../Logic/Osm/OsmObject";
import ShowDataLayer from "../Map/ShowDataLayer";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import { GeoOperations } from "../../Logic/GeoOperations";
import { BBox } from "../../Logic/BBox";
import type { Feature, LineString, Point } from "geojson";
const splitpoint_style = new LayerConfig(
<LayerConfigJson>split_point,
"(BUILTIN) SplitRoadWizard.ts",
true
) as const;
const splitroad_style = new LayerConfig(
<LayerConfigJson>split_road,
"(BUILTIN) SplitRoadWizard.ts",
true
) as const;
/**
* The way to focus on
*/
export let osmWay: OsmWay
/**
* How to render this layer.
* A default is given
*/
export let layer: LayerConfig = splitroad_style
/**
* Optional: use these properties to set e.g. background layer
*/
export let mapProperties: undefined | Partial<MapProperties> = undefined;
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
let adaptor = new MapLibreAdaptor(map, mapProperties);
const wayGeojson: Feature<LineString> = GeoOperations.forceLineString( osmWay.asGeoJson())
adaptor.location.setData(GeoOperations.centerpointCoordinatesObj(wayGeojson))
adaptor.bounds.setData(BBox.get(wayGeojson).pad(2))
adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2))
new ShowDataLayer(map, {
features: new StaticFeatureSource([wayGeojson]),
drawMarkers: false,
layer: layer
})
export let splitPoints: UIEventSource< Feature<
Point,
{
id: number
index: number
dist: number
location: number
}
>[]> = new UIEventSource([])
const splitPointsFS = new StaticFeatureSource(splitPoints)
new ShowDataLayer(map, {
layer: splitpoint_style,
features: splitPointsFS,
onClick: (clickedFeature: Feature) => {
console.log("Clicked feature is", clickedFeature, splitPoints.data)
const i = splitPoints.data.findIndex(f => f === clickedFeature)
if(i < 0){
return
}
splitPoints.data.splice(i, 1)
splitPoints.ping()
}
})
let id = 0
adaptor.lastClickLocation.addCallbackD(({lon, lat}) => {
const projected = GeoOperations.nearestPoint(wayGeojson, [lon, lat])
projected.properties["id"] = id
id++
splitPoints.data.push(<any> projected)
splitPoints.ping()
})
</script>
<div class="w-full h-full">
<MaplibreMap {map}></MaplibreMap>
</div>

View file

@ -90,6 +90,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setAllowMoving(self.allowMoving.data)
self.setAllowZooming(self.allowZooming.data)
self.setMinzoom(self.minzoom.data)
self.setBounds(self.bounds.data)
})
self.MoveMapToCurrentLoc(self.location.data)
self.SetZoom(self.zoom.data)
@ -97,6 +98,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setAllowMoving(self.allowMoving.data)
self.setAllowZooming(self.allowZooming.data)
self.setMinzoom(self.minzoom.data)
self.setBounds(self.bounds.data)
this.updateStores()
map.on("moveend", () => this.updateStores())
map.on("click", (e) => {
@ -238,18 +240,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
container.style.height = document.documentElement.clientHeight + "px"
}
const markerCanvas: HTMLCanvasElement = await html2canvas(
await html2canvas(
map.getCanvasContainer(),
{
backgroundColor: "#00000000",
canvas: drawOn,
}
)
const markers = await new Promise<Blob>((resolve) =>
markerCanvas.toBlob((data) => resolve(data))
)
console.log("Markers:", markers, markerCanvas)
// destinationCtx.drawImage(markerCanvas, 0, 0)
} catch (e) {
console.error(e)
} finally {
@ -429,7 +426,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
private setBounds(bounds: BBox) {
const map = this._maplibreMap.data
if (map === undefined) {
if (map === undefined || bounds === undefined) {
return
}
const oldBounds = map.getBounds()

View file

@ -24,7 +24,7 @@
$map.resize();
});
});
const styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=GvoVAJgu46I5rZapJuAy";
const styleUrl = "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key=GvoVAJgu46I5rZapJuAy";
</script>
<main>
<Map bind:center={center}

View file

@ -197,7 +197,7 @@ class LineRenderingLayer {
this._fetchStore = fetchStore
this._onClick = onClick
const self = this
features.features.addCallbackAndRunD((features) => self.update(features))
features.features.addCallbackAndRunD(() => self.update(features.features))
}
private calculatePropsFor(
@ -229,13 +229,23 @@ class LineRenderingLayer {
return calculatedProps
}
private async update(features: Feature[]) {
private currentSourceData
private async update(featureSource: Store<Feature[]>) {
const map = this._map
while (!map.isStyleLoaded()) {
await Utils.waitFor(100)
}
// After waiting 'till the map has loaded, the data might have changed already
// As such, we only now read the features from the featureSource and compare with the previously set data
const features = featureSource.data
const src = <GeoJSONSource>map.getSource(this._layername)
if (this.currentSourceData === features) {
// Already up to date
return
}
if (src === undefined) {
this.currentSourceData = features
map.addSource(this._layername, {
type: "geojson",
data: {
@ -262,7 +272,6 @@ class LineRenderingLayer {
})
map.on("click", linelayer, (e) => {
console.log("Click", e)
e.originalEvent["consumed"] = true
this._onClick(e.features[0])
})
@ -297,9 +306,10 @@ class LineRenderingLayer {
}
})
} else {
this.currentSourceData = features
src.setData({
type: "FeatureCollection",
features,
features: this.currentSourceData,
})
}
@ -345,10 +355,21 @@ export default class ShowDataLayer {
"ShowDataLayer.ts:range.json"
)
private readonly _map: Store<MlMap>
private readonly _options: ShowDataLayerOptions & { layer: LayerConfig }
private readonly _options: ShowDataLayerOptions & {
layer: LayerConfig
drawMarkers?: true | boolean
drawLines?: true | boolean
}
private readonly _popupCache: Map<string, ScrollableFullScreen>
constructor(map: Store<MlMap>, options: ShowDataLayerOptions & { layer: LayerConfig }) {
constructor(
map: Store<MlMap>,
options: ShowDataLayerOptions & {
layer: LayerConfig
drawMarkers?: true | boolean
drawLines?: true | boolean
}
) {
this._map = map
this._options = options
this._popupCache = new Map()
@ -405,28 +426,31 @@ export default class ShowDataLayer {
selectedElement?.setData(feature)
selectedLayer?.setData(this._options.layer)
})
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
const lineRenderingConfig = this._options.layer.lineRendering[i]
new LineRenderingLayer(
map,
features,
this._options.layer.id + "_linerendering_" + i,
lineRenderingConfig,
doShowLayer,
fetchStore,
onClick
)
if (this._options.drawLines !== false) {
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
const lineRenderingConfig = this._options.layer.lineRendering[i]
new LineRenderingLayer(
map,
features,
this._options.layer.id + "_linerendering_" + i,
lineRenderingConfig,
doShowLayer,
fetchStore,
onClick
)
}
}
for (const pointRenderingConfig of this._options.layer.mapRendering) {
new PointRenderingLayer(
map,
features,
pointRenderingConfig,
doShowLayer,
fetchStore,
onClick
)
if (this._options.drawMarkers !== false) {
for (const pointRenderingConfig of this._options.layer.mapRendering) {
new PointRenderingLayer(
map,
features,
pointRenderingConfig,
doShowLayer,
fetchStore,
onClick
)
}
}
features.features.addCallbackAndRunD((_) => this.zoomToCurrentFeatures(map))
}

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
}
}

View file

@ -1,18 +1,18 @@
import { Store, UIEventSource } from "../Logic/UIEventSource"
import BaseUIElement from "./BaseUIElement"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { Changes } from "../Logic/Osm/Changes"
import { ExportableMap, MapProperties } from "../Models/MapProperties"
import LayerState from "../Logic/State/LayerState"
import { Feature, Geometry } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { MangroveIdentity } from "../Logic/Web/MangroveReviews"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import { MenuState } from "../Models/MenuState"
import { Store, UIEventSource } from "../Logic/UIEventSource";
import BaseUIElement from "./BaseUIElement";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource";
import { OsmConnection } from "../Logic/Osm/OsmConnection";
import { Changes } from "../Logic/Osm/Changes";
import { ExportableMap, MapProperties } from "../Models/MapProperties";
import LayerState from "../Logic/State/LayerState";
import { Feature, Geometry } from "geojson";
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource";
import { MangroveIdentity } from "../Logic/Web/MangroveReviews";
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import { MenuState } from "../Models/MenuState";
/**
* The state needed to render a special Visualisation.

View file

@ -77,8 +77,9 @@ import Lazy from "./Base/Lazy"
import { CheckBox } from "./Input/Checkboxes"
import Slider from "./Input/Slider"
import DeleteWizard from "./Popup/DeleteWizard"
import { OsmId, OsmTags } from "../Models/OsmFeature"
import { OsmId, OsmTags, WayId } from "../Models/OsmFeature"
import MoveWizard from "./Popup/MoveWizard"
import SplitRoadWizard from "./Popup/SplitRoadWizard"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -532,16 +533,17 @@ export default class SpecialVisualizations {
args: [],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
tagSource: UIEventSource<Record<string, string>>
): BaseUIElement {
return new VariableUiElement(
// TODO
tagSource
.map((tags) => tags.id)
.map((id) => new FixedUiElement("TODO: enable splitting")) // new SplitRoadWizard(id, state))
.map((id) => {
if (id.startsWith("way/")) {
return new SplitRoadWizard(<WayId>id, state)
}
return undefined
})
)
},
},

View file

@ -0,0 +1,21 @@
{
"id": "split_road",
"description": "Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible",
"minzoom": 1,
"source": "special",
"name": null,
"title": null,
"mapRendering": [
{
"location": [
"point"
],
"icon": "bug",
"iconSize": "30,30,center"
},
{
"width": "8",
"color": "black"
}
]
}

View file

@ -379,7 +379,7 @@
],
"overrideAll": {
"allowSplit": true,
"tagRenderings+": [
"+tagRenderings": [
{
"id": "is_cyclestreet",
"question": {

View file

@ -1061,6 +1061,10 @@ video {
height: 10rem;
}
.h-80 {
height: 20rem;
}
.max-h-20vh {
max-height: 20vh;
}
@ -1081,6 +1085,10 @@ video {
min-height: 8rem;
}
.w-full {
width: 100%;
}
.w-8 {
width: 2rem;
}
@ -1105,10 +1113,6 @@ video {
width: 1.5rem;
}
.w-full {
width: 100%;
}
.w-screen {
width: 100vw;
}

View file

@ -18,7 +18,7 @@
</head>
<body>
<div id="maindiv">'maindiv' not attached</div>
<div id="maindiv" class="w-full h-full">'maindiv' not attached</div>
<div id="extradiv">'extradiv' not attached</div>
<script type="module" src="./test.ts"></script>

11
test.ts
View file

@ -9,6 +9,10 @@ import { UIEventSource } from "./Logic/UIEventSource"
import { VariableUiElement } from "./UI/Base/VariableUIElement"
import { FixedUiElement } from "./UI/Base/FixedUiElement"
import Title from "./UI/Base/Title"
import WaySplitMap from "./UI/BigComponents/WaySplitMap.svelte"
import SvelteUIElement from "./UI/Base/SvelteUIElement"
import { OsmObject } from "./Logic/Osm/OsmObject"
import SplitRoadWizard from "./UI/Popup/SplitRoadWizard"
function testspecial() {
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
@ -41,5 +45,10 @@ function testinput() {
}
new Combine(els).SetClass("flex flex-col").AttachTo("maindiv")
}
testinput()
async function testWaySplit() {
new SplitRoadWizard("way/28717919", {}).SetClass("w-full h-full").AttachTo("maindiv")
}
testWaySplit().then((_) => console.log("inited"))
//testinput()
// testspecial()