forked from MapComplete/MapComplete
refactoring: split all the states
This commit is contained in:
parent
4d48b1cf2b
commit
8e2f04c0d0
32 changed files with 411 additions and 395 deletions
|
@ -148,7 +148,7 @@ export default class OverpassFeatureSource implements FeatureSource {
|
|||
if (typeof layer === "string") {
|
||||
throw "A layer was not expanded!"
|
||||
}
|
||||
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||
if (layer.source === undefined) {
|
||||
continue
|
||||
}
|
||||
if (this.state.locationControl.data.zoom < layer.minzoom) {
|
||||
|
|
|
@ -7,14 +7,9 @@ import { ElementStorage } from "../ElementStorage"
|
|||
import { Utils } from "../../Utils"
|
||||
|
||||
export default class TitleHandler {
|
||||
constructor(state: {
|
||||
selectedElement: Store<any>
|
||||
layoutToUse: LayoutConfig
|
||||
allElements: ElementStorage
|
||||
}) {
|
||||
const currentTitle: Store<string> = state.selectedElement.map(
|
||||
constructor(selectedElement: Store<any>, layout: LayoutConfig, allElements: ElementStorage) {
|
||||
const currentTitle: Store<string> = selectedElement.map(
|
||||
(selected) => {
|
||||
const layout = state.layoutToUse
|
||||
const defaultTitle = layout?.title?.txt ?? "MapComplete"
|
||||
|
||||
if (selected === undefined) {
|
||||
|
@ -28,8 +23,7 @@ export default class TitleHandler {
|
|||
}
|
||||
if (layer.source.osmTags.matchesProperties(tags)) {
|
||||
const tagsSource =
|
||||
state.allElements.getEventSourceById(tags.id) ??
|
||||
new UIEventSource<any>(tags)
|
||||
allElements.getEventSourceById(tags.id) ?? new UIEventSource<any>(tags)
|
||||
const title = new TagRenderingAnswer(tagsSource, layer.title, {})
|
||||
return (
|
||||
new Combine([defaultTitle, " | ", title]).ConstructElement()
|
||||
|
|
|
@ -189,7 +189,7 @@ export class BBox {
|
|||
public asGeoJson<T>(properties: T): Feature<Polygon, T> {
|
||||
return {
|
||||
type: "Feature",
|
||||
properties: properties,
|
||||
properties,
|
||||
geometry: this.asGeometry(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ export default class FeaturePipeline {
|
|||
public readonly relationTracker: RelationsTracker
|
||||
/**
|
||||
* Keeps track of all raw OSM-nodes.
|
||||
* Only initialized if 'type_node' is defined as layer
|
||||
* Only initialized if `ReplaceGeometryAction` is needed somewhere
|
||||
*/
|
||||
public readonly fullNodeDatabase?: FullNodeDatabaseSource
|
||||
private readonly overpassUpdater: OverpassFeatureSource
|
||||
|
@ -132,14 +132,6 @@ export default class FeaturePipeline {
|
|||
// We do not mark as visited here, this is the responsability of the code near the actual loader (e.g. overpassLoader and OSMApiFeatureLoader)
|
||||
}
|
||||
|
||||
function handlePriviligedFeatureSource(src: FeatureSourceForLayer & Tiled) {
|
||||
// Passthrough to passed function, except that it registers as well
|
||||
handleFeatureSource(src)
|
||||
src.features.addCallbackAndRunD((fs) => {
|
||||
fs.forEach((ff) => state.allElements.addOrGetElement(<any>ff))
|
||||
})
|
||||
}
|
||||
|
||||
for (const filteredLayer of state.filteredLayers.data) {
|
||||
const id = filteredLayer.layerDef.id
|
||||
const source = filteredLayer.layerDef.source
|
||||
|
@ -160,36 +152,6 @@ export default class FeaturePipeline {
|
|||
continue
|
||||
}
|
||||
|
||||
if (id === "selected_element") {
|
||||
handlePriviligedFeatureSource(state.selectedElementsLayer)
|
||||
continue
|
||||
}
|
||||
|
||||
if (id === "gps_location") {
|
||||
handlePriviligedFeatureSource(state.currentUserLocation)
|
||||
continue
|
||||
}
|
||||
|
||||
if (id === "gps_location_history") {
|
||||
handlePriviligedFeatureSource(state.historicalUserLocations)
|
||||
continue
|
||||
}
|
||||
|
||||
if (id === "gps_track") {
|
||||
handlePriviligedFeatureSource(state.historicalUserLocationsTrack)
|
||||
continue
|
||||
}
|
||||
|
||||
if (id === "home_location") {
|
||||
handlePriviligedFeatureSource(state.homeLocation)
|
||||
continue
|
||||
}
|
||||
|
||||
if (id === "current_view") {
|
||||
handlePriviligedFeatureSource(state.currentView)
|
||||
continue
|
||||
}
|
||||
|
||||
const localTileSaver = new SaveTileToLocalStorageActor(filteredLayer)
|
||||
this.localStorageSavers.set(filteredLayer.layerDef.id, localTileSaver)
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
|
|||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
|
||||
|
||||
export default class FeaturePipelineState extends MapState {
|
||||
export default class FeaturePipelineState {
|
||||
/**
|
||||
* The piece of code which fetches data from various sources and shows it on the background map
|
||||
*/
|
||||
|
@ -27,8 +27,6 @@ export default class FeaturePipelineState extends MapState {
|
|||
>()
|
||||
|
||||
constructor(layoutToUse: LayoutConfig) {
|
||||
super(layoutToUse)
|
||||
|
||||
const clustering = layoutToUse?.clustering
|
||||
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this)
|
||||
const clusterCounter = this.featureAggregator
|
||||
|
|
145
Logic/State/LayerState.ts
Normal file
145
Logic/State/LayerState.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
import { UIEventSource } from "../UIEventSource"
|
||||
import { GlobalFilter } from "../../Models/GlobalFilter"
|
||||
import FilteredLayer, { FilterState } from "../../Models/FilteredLayer"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
|
||||
/**
|
||||
* The layer state keeps track of:
|
||||
* - Which layers are enabled
|
||||
* - Which filters are used, including 'global' filters
|
||||
*/
|
||||
export default class LayerState {
|
||||
/**
|
||||
* Filters which apply onto all layers
|
||||
*/
|
||||
public readonly globalFilters: UIEventSource<GlobalFilter[]> = new UIEventSource(
|
||||
[],
|
||||
"globalFilters"
|
||||
)
|
||||
|
||||
/**
|
||||
* Which layers are enabled in the current theme and what filters are applied onto them
|
||||
*/
|
||||
public readonly filteredLayers: Map<string, FilteredLayer>
|
||||
private readonly osmConnection: OsmConnection
|
||||
|
||||
/**
|
||||
*
|
||||
* @param osmConnection
|
||||
* @param layers
|
||||
* @param context: the context, probably the name of the theme. Used to disambiguate the upstream user preference
|
||||
*/
|
||||
constructor(osmConnection: OsmConnection, layers: LayerConfig[], context: string) {
|
||||
this.osmConnection = osmConnection
|
||||
this.filteredLayers = new Map()
|
||||
for (const layer of layers) {
|
||||
this.filteredLayers.set(layer.id, this.initFilteredLayer(layer, context))
|
||||
}
|
||||
layers.forEach((l) => this.linkFilterStates(l))
|
||||
}
|
||||
|
||||
private static getPref(
|
||||
osmConnection: OsmConnection,
|
||||
key: string,
|
||||
layer: LayerConfig
|
||||
): UIEventSource<boolean> {
|
||||
return osmConnection.GetPreference(key, layer.shownByDefault + "").sync(
|
||||
(v) => {
|
||||
if (v === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return v === "true"
|
||||
},
|
||||
[],
|
||||
(b) => {
|
||||
if (b === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return "" + b
|
||||
}
|
||||
)
|
||||
}
|
||||
/**
|
||||
* INitializes a filtered layer for the given layer.
|
||||
* @param layer
|
||||
* @param context: probably the theme-name. This is used to disambiguate the user settings; e.g. when using the same layer in different contexts
|
||||
* @private
|
||||
*/
|
||||
private initFilteredLayer(layer: LayerConfig, context: string): FilteredLayer | undefined {
|
||||
let isDisplayed: UIEventSource<boolean>
|
||||
const osmConnection = this.osmConnection
|
||||
if (layer.syncSelection === "local") {
|
||||
isDisplayed = LocalStorageSource.GetParsed(
|
||||
context + "-layer-" + layer.id + "-enabled",
|
||||
layer.shownByDefault
|
||||
)
|
||||
} else if (layer.syncSelection === "theme-only") {
|
||||
isDisplayed = LayerState.getPref(
|
||||
osmConnection,
|
||||
context + "-layer-" + layer.id + "-enabled",
|
||||
layer
|
||||
)
|
||||
} else if (layer.syncSelection === "global") {
|
||||
isDisplayed = LayerState.getPref(osmConnection, "layer-" + layer.id + "-enabled", layer)
|
||||
} else {
|
||||
isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
||||
"layer-" + layer.id,
|
||||
layer.shownByDefault,
|
||||
"Wether or not layer " + layer.id + " is shown"
|
||||
)
|
||||
}
|
||||
|
||||
const flayer: FilteredLayer = {
|
||||
isDisplayed,
|
||||
layerDef: layer,
|
||||
appliedFilters: new UIEventSource<Map<string, FilterState>>(
|
||||
new Map<string, FilterState>()
|
||||
),
|
||||
}
|
||||
layer.filters?.forEach((filterConfig) => {
|
||||
const stateSrc = filterConfig.initState()
|
||||
|
||||
stateSrc.addCallbackAndRun((state) =>
|
||||
flayer.appliedFilters.data.set(filterConfig.id, state)
|
||||
)
|
||||
flayer.appliedFilters
|
||||
.map((dict) => dict.get(filterConfig.id))
|
||||
.addCallback((state) => stateSrc.setData(state))
|
||||
})
|
||||
|
||||
return flayer
|
||||
}
|
||||
|
||||
/**
|
||||
* Some layers copy the filter state of another layer - this is quite often the case for 'sibling'-layers,
|
||||
* (where two variations of the same layer are used, e.g. a specific type of shop on all zoom levels and all shops on high zoom).
|
||||
*
|
||||
* This methods links those states for the given layer
|
||||
*/
|
||||
private linkFilterStates(layer: LayerConfig) {
|
||||
if (layer.filterIsSameAs === undefined) {
|
||||
return
|
||||
}
|
||||
const toReuse = this.filteredLayers.get(layer.filterIsSameAs)
|
||||
if (toReuse === undefined) {
|
||||
throw (
|
||||
"Error in layer " +
|
||||
layer.id +
|
||||
": it defines that it should be use the filters of " +
|
||||
layer.filterIsSameAs +
|
||||
", but this layer was not loaded"
|
||||
)
|
||||
}
|
||||
console.warn(
|
||||
"Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs
|
||||
)
|
||||
this.filteredLayers.set(layer.id, {
|
||||
isDisplayed: toReuse.isDisplayed,
|
||||
layerDef: layer,
|
||||
appliedFilters: toReuse.appliedFilters,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,31 +1,19 @@
|
|||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
import Attribution from "../../UI/BigComponents/Attribution"
|
||||
import BaseUIElement from "../../UI/BaseUIElement"
|
||||
import FilteredLayer, { FilterState } from "../../Models/FilteredLayer"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer"
|
||||
import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import TitleHandler from "../Actors/TitleHandler"
|
||||
import { BBox } from "../BBox"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import FeatureSource, { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
|
||||
import StaticFeatureSource, {
|
||||
TiledStaticFeatureSource,
|
||||
} from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
import { Feature } from "geojson"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { GlobalFilter } from "../../Models/GlobalFilter"
|
||||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
|
||||
|
||||
/**
|
||||
* Contains all the leaflet-map related state
|
||||
*/
|
||||
export default class MapState {
|
||||
|
||||
|
||||
/**
|
||||
* Last location where a click was registered
|
||||
*/
|
||||
|
@ -46,21 +34,6 @@ export default class MapState {
|
|||
*/
|
||||
public selectedElementsLayer: FeatureSourceForLayer & Tiled
|
||||
|
||||
public readonly mainMapObject: BaseUIElement
|
||||
|
||||
/**
|
||||
* Which layers are enabled in the current theme and what filters are applied onto them
|
||||
*/
|
||||
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>(
|
||||
[],
|
||||
"filteredLayers"
|
||||
)
|
||||
|
||||
/**
|
||||
* Filters which apply onto all layers
|
||||
*/
|
||||
public globalFilters: UIEventSource<GlobalFilter[]> = new UIEventSource([], "globalFilters")
|
||||
|
||||
/**
|
||||
* Which overlays are shown
|
||||
*/
|
||||
|
@ -80,16 +53,6 @@ export default class MapState {
|
|||
this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer)
|
||||
this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id))
|
||||
|
||||
// Will write into this.leafletMap
|
||||
this.mainMapObject = Minimap.createMiniMap({
|
||||
background: this.backgroundLayer,
|
||||
location: this.locationControl,
|
||||
leafletMap: this.leafletMap,
|
||||
bounds: this.currentBounds,
|
||||
attribution: attr,
|
||||
lastClickLocation: this.LastClickLocation,
|
||||
})
|
||||
|
||||
this.overlayToggles =
|
||||
this.layoutToUse?.tileLayerSources
|
||||
?.filter((c) => c.name !== undefined)
|
||||
|
@ -101,16 +64,11 @@ export default class MapState {
|
|||
"Wether or not the overlay " + c.id + " is shown"
|
||||
),
|
||||
})) ?? []
|
||||
this.filteredLayers = new UIEventSource<FilteredLayer[]>(
|
||||
MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection)
|
||||
)
|
||||
|
||||
this.AddAllOverlaysToMap(this.leafletMap)
|
||||
|
||||
this.initCurrentView()
|
||||
this.initSelectedElement()
|
||||
|
||||
new TitleHandler(this)
|
||||
}
|
||||
|
||||
public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) {
|
||||
|
@ -128,48 +86,23 @@ export default class MapState {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private initCurrentView() {
|
||||
let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter(
|
||||
(l) => l.layerDef.id === "current_view"
|
||||
)[0]
|
||||
|
||||
if (currentViewLayer === undefined) {
|
||||
// This layer is not needed by the theme and thus unloaded
|
||||
return
|
||||
}
|
||||
|
||||
private static initCurrentView(mapproperties: MapProperties): FeatureSource {
|
||||
let i = 0
|
||||
const self = this
|
||||
const features: Store<Feature[]> = this.currentBounds.map((bounds) => {
|
||||
const features: Store<Feature[]> = mapproperties.bounds.map((bounds) => {
|
||||
if (bounds === undefined) {
|
||||
return []
|
||||
}
|
||||
i++
|
||||
const feature = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
return [
|
||||
bounds.asGeoJson({
|
||||
id: "current_view-" + i,
|
||||
current_view: "yes",
|
||||
zoom: "" + self.locationControl.data.zoom,
|
||||
},
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [
|
||||
[
|
||||
[bounds.maxLon, bounds.maxLat],
|
||||
[bounds.minLon, bounds.maxLat],
|
||||
[bounds.minLon, bounds.minLat],
|
||||
[bounds.maxLon, bounds.minLat],
|
||||
[bounds.maxLon, bounds.maxLat],
|
||||
],
|
||||
],
|
||||
},
|
||||
}
|
||||
return [feature]
|
||||
zoom: "" + mapproperties.zoom.data,
|
||||
}),
|
||||
]
|
||||
})
|
||||
|
||||
this.currentView = new TiledStaticFeatureSource(features, currentViewLayer)
|
||||
return new StaticFeatureSource(features)
|
||||
}
|
||||
|
||||
private initSelectedElement() {
|
||||
|
@ -197,113 +130,4 @@ export default class MapState {
|
|||
})
|
||||
this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef)
|
||||
}
|
||||
|
||||
private static getPref(
|
||||
osmConnection: OsmConnection,
|
||||
key: string,
|
||||
layer: LayerConfig
|
||||
): UIEventSource<boolean> {
|
||||
return osmConnection.GetPreference(key, layer.shownByDefault + "").sync(
|
||||
(v) => {
|
||||
if (v === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return v === "true"
|
||||
},
|
||||
[],
|
||||
(b) => {
|
||||
if (b === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return "" + b
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public static InitializeFilteredLayers(
|
||||
layoutToUse: { layers: LayerConfig[]; id: string },
|
||||
osmConnection: OsmConnection
|
||||
): FilteredLayer[] {
|
||||
if (layoutToUse === undefined) {
|
||||
return []
|
||||
}
|
||||
const flayers: FilteredLayer[] = []
|
||||
for (const layer of layoutToUse.layers) {
|
||||
let isDisplayed: UIEventSource<boolean>
|
||||
if (layer.syncSelection === "local") {
|
||||
isDisplayed = LocalStorageSource.GetParsed(
|
||||
layoutToUse.id + "-layer-" + layer.id + "-enabled",
|
||||
layer.shownByDefault
|
||||
)
|
||||
} else if (layer.syncSelection === "theme-only") {
|
||||
isDisplayed = MapState.getPref(
|
||||
osmConnection,
|
||||
layoutToUse.id + "-layer-" + layer.id + "-enabled",
|
||||
layer
|
||||
)
|
||||
} else if (layer.syncSelection === "global") {
|
||||
isDisplayed = MapState.getPref(
|
||||
osmConnection,
|
||||
"layer-" + layer.id + "-enabled",
|
||||
layer
|
||||
)
|
||||
} else {
|
||||
isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
||||
"layer-" + layer.id,
|
||||
layer.shownByDefault,
|
||||
"Wether or not layer " + layer.id + " is shown"
|
||||
)
|
||||
}
|
||||
|
||||
const flayer: FilteredLayer = {
|
||||
isDisplayed,
|
||||
layerDef: layer,
|
||||
appliedFilters: new UIEventSource<Map<string, FilterState>>(
|
||||
new Map<string, FilterState>()
|
||||
),
|
||||
}
|
||||
layer.filters.forEach((filterConfig) => {
|
||||
const stateSrc = filterConfig.initState()
|
||||
|
||||
stateSrc.addCallbackAndRun((state) =>
|
||||
flayer.appliedFilters.data.set(filterConfig.id, state)
|
||||
)
|
||||
flayer.appliedFilters
|
||||
.map((dict) => dict.get(filterConfig.id))
|
||||
.addCallback((state) => stateSrc.setData(state))
|
||||
})
|
||||
|
||||
flayers.push(flayer)
|
||||
}
|
||||
|
||||
for (const layer of layoutToUse.layers) {
|
||||
if (layer.filterIsSameAs === undefined) {
|
||||
continue
|
||||
}
|
||||
const toReuse = flayers.find((l) => l.layerDef.id === layer.filterIsSameAs)
|
||||
if (toReuse === undefined) {
|
||||
throw (
|
||||
"Error in layer " +
|
||||
layer.id +
|
||||
": it defines that it should be use the filters of " +
|
||||
layer.filterIsSameAs +
|
||||
", but this layer was not loaded"
|
||||
)
|
||||
}
|
||||
console.warn(
|
||||
"Linking filter and isDisplayed-states of " +
|
||||
layer.id +
|
||||
" and " +
|
||||
layer.filterIsSameAs
|
||||
)
|
||||
const selfLayer = flayers.findIndex((l) => l.layerDef.id === layer.id)
|
||||
flayers[selfLayer] = {
|
||||
isDisplayed: toReuse.isDisplayed,
|
||||
layerDef: layer,
|
||||
appliedFilters: toReuse.appliedFilters,
|
||||
}
|
||||
}
|
||||
|
||||
return flayers
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import Locale from "../../UI/i18n/Locale"
|
|||
import { Changes } from "../Osm/Changes"
|
||||
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import FeatureSource from "../FeatureSource/FeatureSource"
|
||||
import { Feature } from "geojson"
|
||||
|
||||
/**
|
||||
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
|
||||
|
@ -182,7 +183,7 @@ export default class UserRelatedState {
|
|||
|
||||
private initHomeLocation(): FeatureSource {
|
||||
const empty = []
|
||||
const feature = Stores.ListStabilized(
|
||||
const feature: Store<Feature[]> = Stores.ListStabilized(
|
||||
this.osmConnection.userDetails.map((userDetails) => {
|
||||
if (userDetails === undefined) {
|
||||
return undefined
|
||||
|
@ -198,21 +199,18 @@ export default class UserRelatedState {
|
|||
return empty
|
||||
}
|
||||
return [
|
||||
{
|
||||
feature: {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "home",
|
||||
"user:home": "yes",
|
||||
_lon: homeLonLat[0],
|
||||
_lat: homeLonLat[1],
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: homeLonLat,
|
||||
},
|
||||
<Feature>{
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "home",
|
||||
"user:home": "yes",
|
||||
_lon: homeLonLat[0],
|
||||
_lat: homeLonLat[1],
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: homeLonLat,
|
||||
},
|
||||
freshness: new Date(),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
|
|
@ -26,31 +26,30 @@ export default class Constants {
|
|||
// Doesn't support nwr: "https://overpass.openstreetmap.fr/api/interpreter"
|
||||
]
|
||||
|
||||
public static readonly added_by_default: string[] = [
|
||||
public static readonly added_by_default = [
|
||||
"selected_element",
|
||||
"gps_location",
|
||||
"gps_location_history",
|
||||
"home_location",
|
||||
"gps_track",
|
||||
]
|
||||
public static readonly no_include: string[] = [
|
||||
"range",
|
||||
] as const
|
||||
/**
|
||||
* Special layers which are not included in a theme by default
|
||||
*/
|
||||
public static readonly no_include = [
|
||||
"conflation",
|
||||
"left_right_style",
|
||||
"split_point",
|
||||
"current_view",
|
||||
"matchpoint",
|
||||
]
|
||||
] as const
|
||||
/**
|
||||
* Layer IDs of layers which have special properties through built-in hooks
|
||||
*/
|
||||
public static readonly priviliged_layers: string[] = [
|
||||
public static readonly priviliged_layers = [
|
||||
...Constants.added_by_default,
|
||||
"type_node",
|
||||
"note",
|
||||
"import_candidate",
|
||||
"direction",
|
||||
...Constants.no_include,
|
||||
]
|
||||
] as const
|
||||
|
||||
// The user journey states thresholds when a new feature gets unlocked
|
||||
public static userJourney = {
|
||||
|
|
|
@ -255,7 +255,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
|
|||
const creator = new CreateNoteImportLayer()
|
||||
for (let i1 = 0; i1 < allLayers.length; i1++) {
|
||||
const layer = allLayers[i1]
|
||||
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||
if (layer.source === undefined) {
|
||||
// Priviliged layers are skipped
|
||||
continue
|
||||
}
|
||||
|
@ -600,7 +600,7 @@ class PreparePersonalTheme extends DesugaringStep<LayoutConfigJson> {
|
|||
// All other preparations are done by the 'override-all'-block in personal.json
|
||||
|
||||
json.layers = Array.from(this._state.sharedLayers.keys())
|
||||
.filter((l) => Constants.priviliged_layers.indexOf(l) < 0)
|
||||
.filter((l) => this._state.sharedLayers.get(l).source !== null)
|
||||
.filter((l) => this._state.publicLayers.has(l))
|
||||
return {
|
||||
result: json,
|
||||
|
|
|
@ -845,7 +845,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
|
||||
if (json.description === undefined) {
|
||||
if (Constants.priviliged_layers.indexOf(json.id) >= 0) {
|
||||
if (typeof json.source === null) {
|
||||
errors.push(context + ": A priviliged layer must have a description")
|
||||
} else {
|
||||
warnings.push(context + ": A builtin layer should have a description")
|
||||
|
@ -882,6 +882,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
|
|||
}
|
||||
|
||||
if (json.presets !== undefined) {
|
||||
if (typeof json.source === "string") {
|
||||
throw "A special layer cannot have presets"
|
||||
}
|
||||
// Check that a preset will be picked up by the layer itself
|
||||
const baseTags = TagUtils.Tag(json.source.osmTags)
|
||||
for (let i = 0; i < json.presets.length; i++) {
|
||||
|
|
|
@ -77,4 +77,15 @@ export default interface PointRenderingConfigJson {
|
|||
* A snippet of css-classes. They can be space-separated
|
||||
*/
|
||||
cssClasses?: string | TagRenderingConfigJson
|
||||
|
||||
/**
|
||||
* If the map is pitched, the marker will stay parallel to the screen.
|
||||
* Set to 'map' if you want to put it flattened on the map
|
||||
*/
|
||||
pitchAlignment?: "canvas" | "map" | TagRenderingConfigJson
|
||||
|
||||
/**
|
||||
* If the map is rotated, the icon will still point to the north if no rotation was applied
|
||||
*/
|
||||
rotationAlignment?: "map" | "canvas" | TagRenderingConfigJson
|
||||
}
|
||||
|
|
|
@ -86,9 +86,9 @@ export default class LayerConfig extends WithContextLoader {
|
|||
throw "Layer " + this.id + " does not define a source section (" + context + ")"
|
||||
}
|
||||
|
||||
if(json.source === "special" || json.source === "special:library"){
|
||||
if (json.source === "special" || json.source === "special:library") {
|
||||
this.source = null
|
||||
}else if (json.source.osmTags === undefined) {
|
||||
} else if (json.source.osmTags === undefined) {
|
||||
throw (
|
||||
"Layer " +
|
||||
this.id +
|
||||
|
@ -105,7 +105,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
throw `${context}: The id of a layer should match [a-z0-9-_]*: ${json.id}`
|
||||
}
|
||||
|
||||
this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30
|
||||
if (
|
||||
json.syncSelection !== undefined &&
|
||||
LayerConfig.syncSelectionAllowed.indexOf(json.syncSelection) < 0
|
||||
|
@ -120,13 +119,28 @@ export default class LayerConfig extends WithContextLoader {
|
|||
)
|
||||
}
|
||||
this.syncSelection = json.syncSelection ?? "no"
|
||||
const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags")
|
||||
if (typeof json.source !== "string") {
|
||||
this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30
|
||||
const osmTags = TagUtils.Tag(json.source.osmTags, context + "source.osmTags")
|
||||
if (osmTags.isNegative()) {
|
||||
throw (
|
||||
context +
|
||||
"The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" +
|
||||
osmTags.asHumanString(false, false, {})
|
||||
)
|
||||
}
|
||||
|
||||
if (Constants.priviliged_layers.indexOf(this.id) < 0 && osmTags.isNegative()) {
|
||||
throw (
|
||||
context +
|
||||
"The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" +
|
||||
osmTags.asHumanString(false, false, {})
|
||||
this.source = new SourceConfig(
|
||||
{
|
||||
osmTags: osmTags,
|
||||
geojsonSource: json.source["geoJson"],
|
||||
geojsonSourceLevel: json.source["geoJsonZoomLevel"],
|
||||
overpassScript: json.source["overpassScript"],
|
||||
isOsmCache: json.source["isOsmCache"],
|
||||
mercatorCrs: json.source["mercatorCrs"],
|
||||
idKey: json.source["idKey"],
|
||||
},
|
||||
json.id
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -138,20 +152,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
throw context + "Use 'geoJson' instead of 'geojson' (the J is a capital letter)"
|
||||
}
|
||||
|
||||
this.source = new SourceConfig(
|
||||
{
|
||||
osmTags: osmTags,
|
||||
geojsonSource: json.source["geoJson"],
|
||||
geojsonSourceLevel: json.source["geoJsonZoomLevel"],
|
||||
overpassScript: json.source["overpassScript"],
|
||||
isOsmCache: json.source["isOsmCache"],
|
||||
mercatorCrs: json.source["mercatorCrs"],
|
||||
idKey: json.source["idKey"],
|
||||
},
|
||||
Constants.priviliged_layers.indexOf(this.id) > 0,
|
||||
json.id
|
||||
)
|
||||
|
||||
this.allowSplit = json.allowSplit ?? false
|
||||
this.name = Translations.T(json.name, translationContext + ".name")
|
||||
if (json.units !== undefined && !Array.isArray(json.units)) {
|
||||
|
@ -250,7 +250,7 @@ export default class LayerConfig extends WithContextLoader {
|
|||
| "osmbasedmap"
|
||||
| "historicphoto"
|
||||
| string
|
||||
)[]
|
||||
)[]
|
||||
if (typeof pr.preciseInput.preferredBackground === "string") {
|
||||
preferredBackground = [pr.preciseInput.preferredBackground]
|
||||
} else {
|
||||
|
@ -597,7 +597,7 @@ export default class LayerConfig extends WithContextLoader {
|
|||
}
|
||||
|
||||
let overpassLink: BaseUIElement = undefined
|
||||
if (Constants.priviliged_layers.indexOf(this.id) < 0) {
|
||||
if (this.source !== undefined) {
|
||||
try {
|
||||
overpassLink = new Link(
|
||||
"Execute on overpass",
|
||||
|
|
|
@ -12,6 +12,7 @@ import Img from "../../UI/Base/Img"
|
|||
import Combine from "../../UI/Base/Combine"
|
||||
import { VariableUiElement } from "../../UI/Base/VariableUIElement"
|
||||
import { OsmTags } from "../OsmFeature"
|
||||
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
|
||||
|
||||
export default class PointRenderingConfig extends WithContextLoader {
|
||||
private static readonly allowed_location_codes = new Set<string>([
|
||||
|
@ -32,6 +33,8 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
public readonly rotation: TagRenderingConfig
|
||||
public readonly cssDef: TagRenderingConfig
|
||||
public readonly cssClasses?: TagRenderingConfig
|
||||
public readonly pitchAlignment?: TagRenderingConfig
|
||||
public readonly rotationAlignment?: TagRenderingConfig
|
||||
|
||||
constructor(json: PointRenderingConfigJson, context: string) {
|
||||
super(json, context)
|
||||
|
@ -88,6 +91,14 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
this.iconSize = this.tr("iconSize", "40,40,center")
|
||||
this.label = this.tr("label", undefined)
|
||||
this.rotation = this.tr("rotation", "0")
|
||||
if (json.pitchAlignment) {
|
||||
console.log("Got a pitch alignment!", json.pitchAlignment)
|
||||
}
|
||||
this.pitchAlignment = this.tr("pitchAlignment", "canvas")
|
||||
this.rotationAlignment = this.tr(
|
||||
"rotationAlignment",
|
||||
json.pitchAlignment === "map" ? "map" : "canvas"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,7 +20,6 @@ export default class SourceConfig {
|
|||
geojsonSourceLevel?: number
|
||||
idKey?: string
|
||||
},
|
||||
isSpecialLayer: boolean,
|
||||
context?: string
|
||||
) {
|
||||
let defined = 0
|
||||
|
@ -51,7 +50,7 @@ export default class SourceConfig {
|
|||
throw `Source defines a geojson-zoomLevel, but does not specify {x} nor {y} (or equivalent), this is probably a bug (in context ${context})`
|
||||
}
|
||||
}
|
||||
if (params.osmTags !== undefined && !isSpecialLayer) {
|
||||
if (params.osmTags !== undefined) {
|
||||
const optimized = params.osmTags.optimize()
|
||||
if (optimized === false) {
|
||||
throw (
|
||||
|
|
|
@ -228,7 +228,7 @@ export class DownloadPanel extends Toggle {
|
|||
new Set(neededLayers)
|
||||
)
|
||||
for (const tile of featureList) {
|
||||
if (Constants.priviliged_layers.indexOf(tile.layer) >= 0) {
|
||||
if (tile.layer !== undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ export class GeolocationControl extends VariableUiElement {
|
|||
return false
|
||||
}
|
||||
const timeDiff = (new Date().getTime() - date.getTime()) / 1000
|
||||
console.log("Timediff", timeDiff)
|
||||
return timeDiff <= Constants.zoomToLocationTimeout
|
||||
}
|
||||
)
|
||||
|
|
|
@ -59,7 +59,7 @@ export class MapPreview
|
|||
}
|
||||
|
||||
const availableLayers = AllKnownLayouts.AllPublicLayers().filter(
|
||||
(l) => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0
|
||||
(l) => l.name !== undefined && l.source !== undefined
|
||||
)
|
||||
const layerPicker = new DropDown(
|
||||
t.selectLayer,
|
||||
|
|
|
@ -14,12 +14,12 @@ import Constants from "../../Models/Constants"
|
|||
*/
|
||||
export class MapLibreAdaptor implements MapProperties {
|
||||
private static maplibre_control_handlers = [
|
||||
"scrollZoom",
|
||||
"boxZoom",
|
||||
// "scrollZoom",
|
||||
// "boxZoom",
|
||||
// "doubleClickZoom",
|
||||
"dragRotate",
|
||||
"dragPan",
|
||||
"keyboard",
|
||||
"doubleClickZoom",
|
||||
"touchZoomRotate",
|
||||
]
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
|
|
|
@ -8,12 +8,13 @@ import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig"
|
|||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
import FeatureSource from "../../Logic/FeatureSource/FeatureSource"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import { Feature } from "geojson"
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
|
||||
import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
import * as range_layer from "../../assets/layers/range/range.json"
|
||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
|
||||
class PointRenderingLayer {
|
||||
private readonly _config: PointRenderingConfig
|
||||
private readonly _fetchStore?: (id: string) => Store<OsmTags>
|
||||
|
@ -44,6 +45,16 @@ class PointRenderingLayer {
|
|||
const unseenKeys = new Set(cache.keys())
|
||||
for (const location of this._config.location) {
|
||||
for (const feature of features) {
|
||||
if (feature?.geometry === undefined) {
|
||||
console.warn(
|
||||
"Got an invalid feature:",
|
||||
features,
|
||||
" while rendering",
|
||||
location,
|
||||
"of",
|
||||
this._config
|
||||
)
|
||||
}
|
||||
const loc = GeoOperations.featureToCoordinateWithRenderingType(
|
||||
<any>feature,
|
||||
location
|
||||
|
@ -102,7 +113,14 @@ class PointRenderingLayer {
|
|||
})
|
||||
}
|
||||
|
||||
return new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map)
|
||||
const marker = new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map)
|
||||
store
|
||||
.map((tags) => this._config.pitchAlignment.GetRenderValue(tags).Subs(tags).txt)
|
||||
.addCallbackAndRun((pitchAligment) => marker.setPitchAlignment(pitchAligment))
|
||||
store
|
||||
.map((tags) => this._config.rotationAlignment.GetRenderValue(tags).Subs(tags).txt)
|
||||
.addCallbackAndRun((pitchAligment) => marker.setRotationAlignment(pitchAligment))
|
||||
return marker
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,13 +136,17 @@ class LineRenderingLayer {
|
|||
"offset",
|
||||
"fill",
|
||||
"fillColor",
|
||||
]
|
||||
] as const
|
||||
|
||||
private static readonly lineConfigKeysColor = ["color", "fillColor"] as const
|
||||
private static readonly lineConfigKeysNumber = ["width", "offset"] as const
|
||||
private readonly _map: MlMap
|
||||
private readonly _config: LineRenderingConfig
|
||||
private readonly _visibility?: Store<boolean>
|
||||
private readonly _fetchStore?: (id: string) => Store<OsmTags>
|
||||
private readonly _onClick?: (id: string) => void
|
||||
private readonly _layername: string
|
||||
private readonly _listenerInstalledOn: Set<string> = new Set<string>()
|
||||
|
||||
constructor(
|
||||
map: MlMap,
|
||||
|
@ -145,6 +167,39 @@ class LineRenderingLayer {
|
|||
features.features.addCallbackAndRunD((features) => self.update(features))
|
||||
}
|
||||
|
||||
private calculatePropsFor(
|
||||
properties: Record<string, string>
|
||||
): Partial<Record<typeof LineRenderingLayer.lineConfigKeys[number], string>> {
|
||||
const calculatedProps = {}
|
||||
const config = this._config
|
||||
|
||||
for (const key of LineRenderingLayer.lineConfigKeys) {
|
||||
const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
calculatedProps[key] = v
|
||||
}
|
||||
for (const key of LineRenderingLayer.lineConfigKeysColor) {
|
||||
let v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
if (v === undefined) {
|
||||
continue
|
||||
}
|
||||
console.log("Color", v)
|
||||
if (v.length == 9 && v.startsWith("#")) {
|
||||
// This includes opacity
|
||||
calculatedProps[key + "-opacity"] = parseInt(v.substring(7), 16) / 256
|
||||
v = v.substring(0, 7)
|
||||
console.log("Color >", v, calculatedProps[key + "-opacity"])
|
||||
}
|
||||
calculatedProps[key] = v
|
||||
}
|
||||
for (const key of LineRenderingLayer.lineConfigKeysNumber) {
|
||||
const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
calculatedProps[key] = Number(v)
|
||||
}
|
||||
|
||||
console.log("Calculated props:", calculatedProps, "for", properties.id)
|
||||
return calculatedProps
|
||||
}
|
||||
|
||||
private async update(features: Feature[]) {
|
||||
const map = this._map
|
||||
while (!map.isStyleLoaded()) {
|
||||
|
@ -158,31 +213,14 @@ class LineRenderingLayer {
|
|||
},
|
||||
promoteId: "id",
|
||||
})
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
const feature = features[i]
|
||||
const id = feature.properties.id ?? "" + i
|
||||
const tags = this._fetchStore(id)
|
||||
tags.addCallbackAndRunD((properties) => {
|
||||
const config = this._config
|
||||
|
||||
const calculatedProps = {}
|
||||
for (const key of LineRenderingLayer.lineConfigKeys) {
|
||||
const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
calculatedProps[key] = v
|
||||
}
|
||||
|
||||
map.setFeatureState({ source: this._layername, id }, calculatedProps)
|
||||
})
|
||||
}
|
||||
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
id: this._layername + "_line",
|
||||
type: "line",
|
||||
filter: ["in", ["geometry-type"], ["literal", ["LineString", "MultiLineString"]]],
|
||||
layout: {},
|
||||
paint: {
|
||||
"line-color": ["feature-state", "color"],
|
||||
"line-opacity": ["feature-state", "color-opacity"],
|
||||
"line-width": ["feature-state", "width"],
|
||||
"line-offset": ["feature-state", "offset"],
|
||||
},
|
||||
|
@ -205,12 +243,49 @@ class LineRenderingLayer {
|
|||
layout: {},
|
||||
paint: {
|
||||
"fill-color": ["feature-state", "fillColor"],
|
||||
"fill-opacity": 0.1,
|
||||
},
|
||||
})
|
||||
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
const feature = features[i]
|
||||
const id = feature.properties.id ?? feature.id
|
||||
console.log("ID is", id)
|
||||
if (id === undefined) {
|
||||
console.trace(
|
||||
"Got a feature without ID; this causes rendering bugs:",
|
||||
feature,
|
||||
"from"
|
||||
)
|
||||
continue
|
||||
}
|
||||
if (this._listenerInstalledOn.has(id)) {
|
||||
continue
|
||||
}
|
||||
if (this._fetchStore === undefined) {
|
||||
map.setFeatureState(
|
||||
{ source: this._layername, id },
|
||||
this.calculatePropsFor(feature.properties)
|
||||
)
|
||||
} else {
|
||||
const tags = this._fetchStore(id)
|
||||
this._listenerInstalledOn.add(id)
|
||||
tags.addCallbackAndRunD((properties) => {
|
||||
map.setFeatureState(
|
||||
{ source: this._layername, id },
|
||||
this.calculatePropsFor(properties)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class ShowDataLayer {
|
||||
private static rangeLayer = new LayerConfig(
|
||||
<LayerConfigJson>range_layer,
|
||||
"ShowDataLayer.ts:range.json"
|
||||
)
|
||||
private readonly _map: Store<MlMap>
|
||||
private readonly _options: ShowDataLayerOptions & { layer: LayerConfig }
|
||||
private readonly _popupCache: Map<string, ScrollableFullScreen>
|
||||
|
@ -223,11 +298,6 @@ export default class ShowDataLayer {
|
|||
map.addCallbackAndRunD((map) => self.initDrawFeatures(map))
|
||||
}
|
||||
|
||||
private static rangeLayer = new LayerConfig(
|
||||
<LayerConfigJson>range_layer,
|
||||
"ShowDataLayer.ts:range.json"
|
||||
)
|
||||
|
||||
public static showRange(
|
||||
map: Store<MlMap>,
|
||||
features: FeatureSource,
|
||||
|
@ -241,6 +311,9 @@ export default class ShowDataLayer {
|
|||
}
|
||||
|
||||
private openOrReusePopup(id: string): void {
|
||||
if (!this._popupCache || !this._options.fetchStore) {
|
||||
return
|
||||
}
|
||||
if (this._popupCache.has(id)) {
|
||||
this._popupCache.get(id).Activate()
|
||||
return
|
||||
|
@ -267,11 +340,12 @@ export default class ShowDataLayer {
|
|||
private initDrawFeatures(map: MlMap) {
|
||||
const { features, doShowLayer, fetchStore, buildPopup } = this._options
|
||||
const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id)
|
||||
for (const lineRenderingConfig of this._options.layer.lineRendering) {
|
||||
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
|
||||
const lineRenderingConfig = this._options.layer.lineRendering[i]
|
||||
new LineRenderingLayer(
|
||||
map,
|
||||
features,
|
||||
"test",
|
||||
this._options.layer.id + "_linerendering_" + i,
|
||||
lineRenderingConfig,
|
||||
doShowLayer,
|
||||
fetchStore,
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
import Svg from "../Svg";
|
||||
import If from "./Base/If.svelte";
|
||||
import { GeolocationControl } from "./BigComponents/GeolocationControl.js";
|
||||
import FeaturePipeline from "../Logic/FeatureSource/FeaturePipeline";
|
||||
import { BBox } from "../Logic/BBox";
|
||||
import ShowDataLayer from "./Map/ShowDataLayer";
|
||||
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource";
|
||||
|
||||
import type FeatureSource from "../Logic/FeatureSource/FeatureSource";
|
||||
import LayerState from "../Logic/State/LayerState";
|
||||
import Constants from "../Models/Constants";
|
||||
import type { Feature } from "geojson";
|
||||
export let layout: LayoutConfig;
|
||||
|
||||
const maplibremap: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined);
|
||||
|
@ -46,7 +48,7 @@
|
|||
osmConfiguration: <"osm" | "osm-test">featureSwitches.featureSwitchApiURL.data
|
||||
});
|
||||
const userRelatedState = new UserRelatedState(osmConnection, layout?.language);
|
||||
const selectedElement = new UIEventSource<any>(undefined, "Selected element");
|
||||
const selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element");
|
||||
const geolocation = new GeoLocationHandler(geolocationState, selectedElement, mapproperties, userRelatedState.gpsLocationHistoryRetentionTime);
|
||||
|
||||
const allElements = new ElementStorage();
|
||||
|
@ -55,16 +57,19 @@
|
|||
osmConnection,
|
||||
historicalUserLocations: geolocation.historicalUserLocations
|
||||
}, layout?.isLeftRightSensitive() ?? false);
|
||||
|
||||
Map
|
||||
|
||||
console.log("Setting up layerstate...")
|
||||
const layerState = new LayerState(osmConnection, layout.layers, layout.id)
|
||||
{
|
||||
// Various actors that we don't need to reference
|
||||
// TODO enable new TitleHandler(selectedElement,layout,allElements)
|
||||
new ChangeToElementsActor(changes, allElements);
|
||||
new PendingChangesUploader(changes, selectedElement);
|
||||
new SelectedElementTagsUpdater({
|
||||
allElements, changes, selectedElement, layoutToUse: layout, osmConnection
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Various initial setup
|
||||
userRelatedState.markLayoutAsVisited(layout);
|
||||
if(layout?.lockLocation){
|
||||
|
@ -76,7 +81,37 @@
|
|||
featureSwitches.featureSwitchIsTesting
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
type AddedByDefaultTypes = typeof Constants.added_by_default[number]
|
||||
/**
|
||||
* A listing which maps the layerId onto the featureSource
|
||||
*/
|
||||
const empty = []
|
||||
const specialLayers : Record<AddedByDefaultTypes | "current_view", FeatureSource> = {
|
||||
"home_location": userRelatedState.homeLocation,
|
||||
gps_location: geolocation.currentUserLocation,
|
||||
gps_location_history: geolocation.historicalUserLocations,
|
||||
gps_track: geolocation.historicalUserLocationsTrack,
|
||||
selected_element: new StaticFeatureSource(selectedElement.map(f => f === undefined ? empty : [f])),
|
||||
range: new StaticFeatureSource(mapproperties.maxbounds.map(bbox => bbox === undefined ? empty : <Feature[]> [bbox.asGeoJson({id:"range"})])) ,
|
||||
current_view: new StaticFeatureSource(mapproperties.bounds.map(bbox => bbox === undefined ? empty : <Feature[]> [bbox.asGeoJson({id:"current_view"})])),
|
||||
}
|
||||
layerState.filteredLayers.get("range")?.isDisplayed?.syncWith(featureSwitches.featureSwitchIsTesting, true)
|
||||
console.log("RAnge fs", specialLayers.range)
|
||||
specialLayers.range.features.addCallbackAndRun(fs => console.log("Range.features:", JSON.stringify(fs)))
|
||||
layerState.filteredLayers.forEach((flayer) => {
|
||||
const features = specialLayers[flayer.layerDef.id]
|
||||
if(features === undefined){
|
||||
return
|
||||
}
|
||||
new ShowDataLayer(maplibremap, {
|
||||
features,
|
||||
doShowLayer: flayer.isDisplayed,
|
||||
layer: flayer.layerDef,
|
||||
selectedElement
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -93,15 +128,12 @@
|
|||
</div>
|
||||
|
||||
<div class="absolute bottom-0 right-0 mb-4 mr-4">
|
||||
|
||||
<If condition={mapproperties.allowMoving}>
|
||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}>
|
||||
<ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte>
|
||||
</MapControlButton>
|
||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}>
|
||||
<ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte>
|
||||
</MapControlButton>
|
||||
</If>
|
||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}>
|
||||
<ToSvelte class="w-7 h-7 block" construct={Svg.plus_ui}></ToSvelte>
|
||||
</MapControlButton>
|
||||
<MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}>
|
||||
<ToSvelte class="w-7 h-7 block" construct={Svg.min_ui}></ToSvelte>
|
||||
</MapControlButton>
|
||||
<If condition={featureSwitches.featureSwitchGeolocation}>
|
||||
<MapControlButton>
|
||||
<ToSvelte construct={() => new GeolocationControl(geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
]
|
||||
},
|
||||
"iconSize": "40,40,center",
|
||||
"pitchAlignment": "map",
|
||||
"rotation": {
|
||||
"render": "0deg",
|
||||
"mappings": [
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "home_location",
|
||||
"description": "Meta layer showing the home location of the user. The home location can be set in the [profile settings](https://www.openstreetmap.org/profile/edit) of OpenStreetMap.",
|
||||
"minzoom": 0,
|
||||
"source":"special",
|
||||
"source": "special",
|
||||
"mapRendering": [
|
||||
{
|
||||
"icon": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "import_candidate",
|
||||
"description": "Layer used in the importHelper",
|
||||
"description": "Layer used as template in the importHelper",
|
||||
"source":"special",
|
||||
"mapRendering": [
|
||||
{
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
"name": null,
|
||||
"mapRendering": [
|
||||
{
|
||||
"width": 4,
|
||||
"width": 3,
|
||||
"fill": "no",
|
||||
"color": "#ff000088"
|
||||
"color": "#cc00cc"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
"id": "split_point",
|
||||
"description": "Layer rendering the little scissors for the minimap in the 'splitRoadWizard'",
|
||||
"minzoom": 1,
|
||||
"source": {
|
||||
"osmTags": "_split_point=yes"
|
||||
},
|
||||
"source": "special",
|
||||
"name": "Split point",
|
||||
"title": "Split point",
|
||||
"mapRendering": [
|
||||
|
@ -17,4 +15,4 @@
|
|||
"iconSize": "30,30,center"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"id": "type_node",
|
||||
"description": "This is a priviliged meta_layer which exports _every_ point in OSM. This only works if zoomed below the point that the full tile is loaded (and not loaded via Overpass). Note that this point will also contain a property `parent_ways` which contains all the ways this node is part of as a list. This is mainly used for extremely specialized themes, which do advanced conflations. Expert use only.",
|
||||
"minzoom": 18,
|
||||
"source": "special",
|
||||
"mapRendering": null,
|
||||
"name": "All OSM Nodes",
|
||||
"title": "OSM node {id}",
|
||||
"tagRendering": []
|
||||
}
|
|
@ -28,29 +28,6 @@
|
|||
"minzoom": 19
|
||||
},
|
||||
"layers": [
|
||||
{
|
||||
"builtin": "type_node",
|
||||
"override": {
|
||||
"calculatedTags": [
|
||||
"_is_part_of_building=feat.get('parent_ways')?.some(p => p.building !== undefined && p.building !== '') ?? false",
|
||||
"_is_part_of_grb_building=feat.get('parent_ways')?.some(p => p['source:geometry:ref'] !== undefined) ?? false",
|
||||
"_is_part_of_building_passage=feat.get('parent_ways')?.some(p => p.tunnel === 'building_passage') ?? false",
|
||||
"_is_part_of_highway=!feat.get('is_part_of_building_passage') && (feat.get('parent_ways')?.some(p => p.highway !== undefined && p.highway !== '') ?? false)",
|
||||
"_is_part_of_landuse=feat.get('parent_ways')?.some(p => (p.landuse !== undefined && p.landuse !== '') || (p.natural !== undefined && p.natural !== '')) ?? false",
|
||||
"_moveable=feat.get('_is_part_of_building') && !feat.get('_is_part_of_grb_building')"
|
||||
],
|
||||
"mapRendering": [
|
||||
{
|
||||
"icon": "square:#cc0",
|
||||
"iconSize": "5,5,center",
|
||||
"location": [
|
||||
"point"
|
||||
]
|
||||
}
|
||||
],
|
||||
"passAllFeatures": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "osm-buildings",
|
||||
"name": "All OSM-buildings",
|
||||
|
@ -771,4 +748,4 @@
|
|||
"overpassMaxZoom": 17,
|
||||
"osmApiTileSize": 17,
|
||||
"credits": "Pieter Vander Vennet"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ function GenLayerOverviewText(): BaseUIElement {
|
|||
}
|
||||
|
||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||
(layer) => Constants.priviliged_layers.indexOf(layer.id) < 0
|
||||
(layer) => layer.source === null
|
||||
)
|
||||
|
||||
const builtinLayerIds: Set<string> = new Set<string>()
|
||||
|
@ -183,7 +183,7 @@ function GenOverviewsForSingleLayer(
|
|||
callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void
|
||||
): void {
|
||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||
(layer) => Constants.priviliged_layers.indexOf(layer.id) < 0
|
||||
(layer) => layer.source !== null
|
||||
)
|
||||
const builtinLayerIds: Set<string> = new Set<string>()
|
||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||
|
@ -195,7 +195,7 @@ function GenOverviewsForSingleLayer(
|
|||
}
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||
if (layer.source === null) {
|
||||
continue
|
||||
}
|
||||
if (builtinLayerIds.has(layer.id)) {
|
||||
|
|
|
@ -18,7 +18,7 @@ async function main(includeTags = true) {
|
|||
if (layer.source["geoJson"] !== undefined && !layer.source["isOsmCache"]) {
|
||||
continue
|
||||
}
|
||||
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||
if (layer.source == null || typeof layer.source === "string") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ function generateLayerUsage(layer: LayerConfig, layout: LayoutConfig): any[] {
|
|||
function generateTagInfoEntry(layout: LayoutConfig): any {
|
||||
const usedTags = []
|
||||
for (const layer of layout.layers) {
|
||||
if (Constants.priviliged_layers.indexOf(layer.id) >= 0) {
|
||||
if (layer.source === null) {
|
||||
continue
|
||||
}
|
||||
if (layer.source.geojsonSource !== undefined && layer.source.isOsmCacheLayer !== true) {
|
||||
|
|
7
test.ts
7
test.ts
|
@ -3,11 +3,12 @@ import ThemeViewGUI from "./UI/ThemeViewGUI.svelte"
|
|||
import { FixedUiElement } from "./UI/Base/FixedUiElement"
|
||||
import { QueryParameters } from "./Logic/Web/QueryParameters"
|
||||
import { AllKnownLayoutsLazy } from "./Customizations/AllKnownLayouts"
|
||||
|
||||
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"
|
||||
import * as benches from "./assets/generated/themes/benches.json"
|
||||
async function main() {
|
||||
new FixedUiElement("Determining layout...").AttachTo("maindiv")
|
||||
const qp = QueryParameters.GetQueryParameter("layout", "benches")
|
||||
const layout = new AllKnownLayoutsLazy().get(qp.data)
|
||||
const qp = QueryParameters.GetQueryParameter("layout", "")
|
||||
const layout = new LayoutConfig(<any>benches, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
|
||||
console.log("Using layout", layout.id)
|
||||
new SvelteUIElement(ThemeViewGUI, { layout }).AttachTo("maindiv")
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue