First fully working version

This commit is contained in:
Pieter Vander Vennet 2024-02-19 15:38:46 +01:00
parent 995f48b97a
commit 1fa66e50f8
8 changed files with 73 additions and 88 deletions

View file

@ -47,6 +47,10 @@ HP ProLiant DL360 G7 (1U): 2Rx4 DDR3-memory (PC3)
Intel Xeon X56** Intel Xeon X56**
## Updating data
`osm2pgsql-replication update -d postgresql://user:password@localhost:5444/osm-poi -- -O flex -S build_db.lua -s --flat-nodes=import-help-file`
## Deploying a tile server ## Deploying a tile server

View file

@ -15,11 +15,12 @@
"point", "point",
"centroid" "centroid"
], ],
"iconSize": "25,25", "iconSize": "40,40",
"label": { "label": {
"render": "{total}" "render": "{total}"
}, },
"labelCssClasses": "bg-white w-6 h-6 text-lg rounded-full" "labelCss": "background: #ffffffbb",
"labelCssClasses": "w-12 text-lg rounded-xl p-1 px-2"
} }
] ]
} }

View file

@ -21,7 +21,7 @@
"oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg", "oauth_secret": "NBWGhWDrD3QDB35xtVuxv4aExnmIt4FA_WgeLtwxasg",
"url": "https://www.openstreetmap.org" "url": "https://www.openstreetmap.org"
}, },
"mvt_layer_server": "http://cache.mapcomplete.org/mvt/public.{type}_{layer}/{z}/{x}/{y}.pbf", "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf",
"disabled:oauth_credentials": { "disabled:oauth_credentials": {
"##": "DEV", "##": "DEV",
"#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/", "#": "This client-id is registered by 'MapComplete' on https://master.apis.dev.openstreetmap.org/",

View file

@ -4,12 +4,13 @@ import { BBox } from "../../BBox"
import { FeatureSource } from "../FeatureSource" import { FeatureSource } from "../FeatureSource"
import FeatureSourceMerger from "../Sources/FeatureSourceMerger" import FeatureSourceMerger from "../Sources/FeatureSourceMerger"
/*** /***
* A tiled source which dynamically loads the required tiles at a fixed zoom level. * A tiled source which dynamically loads the required tiles at a fixed zoom level.
* A single featureSource will be initialized for every tile in view; which will later be merged into this featureSource * A single featureSource will be initialized for every tile in view; which will later be merged into this featureSource
*/ */
export default class DynamicTileSource<Src extends FeatureSource = FeatureSource> extends FeatureSourceMerger<Src> { export default class DynamicTileSource<
Src extends FeatureSource = FeatureSource
> extends FeatureSourceMerger<Src> {
/** /**
* *
* @param zoomlevel If {z} is specified in the source, the 'zoomlevel' will be used as zoomlevel to download from * @param zoomlevel If {z} is specified in the source, the 'zoomlevel' will be used as zoomlevel to download from
@ -28,10 +29,12 @@ export default class DynamicTileSource<Src extends FeatureSource = FeatureSource
}, },
options?: { options?: {
isActive?: Store<boolean> isActive?: Store<boolean>
}, zDiff?: number
}
) { ) {
super() super()
const loadedTiles = new Set<number>() const loadedTiles = new Set<number>()
const zDiff = options?.zDiff ?? 0
const neededTiles: Store<number[]> = Stores.ListStabilized( const neededTiles: Store<number[]> = Stores.ListStabilized(
mapProperties.bounds mapProperties.bounds
.mapD( .mapD(
@ -43,32 +46,32 @@ export default class DynamicTileSource<Src extends FeatureSource = FeatureSource
if (mapProperties.zoom.data < minzoom) { if (mapProperties.zoom.data < minzoom) {
return undefined return undefined
} }
const z = Math.floor(zoomlevel.data) const z = Math.floor(zoomlevel.data) + zDiff
const tileRange = Tiles.TileRangeBetween( const tileRange = Tiles.TileRangeBetween(
z, z,
bounds.getNorth(), bounds.getNorth(),
bounds.getEast(), bounds.getEast(),
bounds.getSouth(), bounds.getSouth(),
bounds.getWest(), bounds.getWest()
) )
if (tileRange.total > 500) { if (tileRange.total > 500) {
console.warn( console.warn(
"Got a really big tilerange, bounds and location might be out of sync", "Got a really big tilerange, bounds and location might be out of sync"
) )
return undefined return undefined
} }
const needed = Tiles.MapRange(tileRange, (x, y) => const needed = Tiles.MapRange(tileRange, (x, y) =>
Tiles.tile_index(z, x, y), Tiles.tile_index(z, x, y)
).filter((i) => !loadedTiles.has(i)) ).filter((i) => !loadedTiles.has(i))
if (needed.length === 0) { if (needed.length === 0) {
return undefined return undefined
} }
return needed return needed
}, },
[options?.isActive, mapProperties.zoom], [options?.isActive, mapProperties.zoom]
) )
.stabilized(250), .stabilized(250)
) )
neededTiles.addCallbackAndRunD((neededIndexes) => { neededTiles.addCallbackAndRunD((neededIndexes) => {
@ -79,5 +82,3 @@ export default class DynamicTileSource<Src extends FeatureSource = FeatureSource
}) })
} }
} }

View file

@ -24,12 +24,13 @@ export class SummaryTileSource extends DynamicTileSource {
} }
) { ) {
const layersSummed = layers.join("+") const layersSummed = layers.join("+")
const zDiff = 2
super( super(
zoomRounded, zoomRounded,
0, // minzoom 0, // minzoom
(tileIndex) => { (tileIndex) => {
const [z, x, y] = Tiles.tile_from_index(tileIndex) const [z, x, y] = Tiles.tile_from_index(tileIndex)
const coordinates = Tiles.centerPointOf(z, x, y) let coordinates = Tiles.centerPointOf(z, x, y)
const count = UIEventSource.FromPromiseWithErr( const count = UIEventSource.FromPromiseWithErr(
Utils.downloadJson(`${cacheserver}/${layersSummed}/${z}/${x}/${y}.json`) Utils.downloadJson(`${cacheserver}/${layersSummed}/${z}/${x}/${y}.json`)
@ -50,6 +51,21 @@ export class SummaryTileSource extends DynamicTileSource {
if (counts === undefined || counts["total"] === 0) { if (counts === undefined || counts["total"] === 0) {
return SummaryTileSource.empty return SummaryTileSource.empty
} }
const lat = counts["lat"]
const lon = counts["lon"]
const total = Utils.numberWithMetrixPrefix(Number(counts["total"]))
const tileBbox = new BBox(Tiles.tile_bounds_lon_lat(z, x, y))
if (!tileBbox.contains([lon, lat])) {
console.error(
"Average coordinate is outside of bbox!?",
lon,
lat,
tileBbox,
counts
)
} else {
coordinates = [lon, lat]
}
return [ return [
{ {
type: "Feature", type: "Feature",
@ -57,6 +73,7 @@ export class SummaryTileSource extends DynamicTileSource {
id: "summary_" + tileIndex, id: "summary_" + tileIndex,
summary: "yes", summary: "yes",
...counts, ...counts,
total,
layers: layersSummed, layers: layersSummed,
}, },
geometry: { geometry: {
@ -69,7 +86,8 @@ export class SummaryTileSource extends DynamicTileSource {
return new StaticFeatureSource( return new StaticFeatureSource(
features.map( features.map(
(f) => { (f) => {
if (z !== zoomRounded.data) { console.log("z, zdiff, rounded:", z, zDiff, zoomRounded.data)
if (z - zDiff !== zoomRounded.data) {
return SummaryTileSource.empty return SummaryTileSource.empty
} }
return f return f
@ -79,7 +97,7 @@ export class SummaryTileSource extends DynamicTileSource {
) )
}, },
mapProperties, mapProperties,
options { ...options, zDiff }
) )
} }
} }

View file

@ -145,7 +145,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/ */
public readonly visualFeedback: UIEventSource<boolean> = new UIEventSource<boolean>(false) public readonly visualFeedback: UIEventSource<boolean> = new UIEventSource<boolean>(false)
constructor(layout: LayoutConfig) { constructor(layout: LayoutConfig, mvtAvailableLayers: Set<string>) {
Utils.initDomPurify() Utils.initDomPurify()
this.layout = layout this.layout = layout
this.featureSwitches = new FeatureSwitchState(layout) this.featureSwitches = new FeatureSwitchState(layout)
@ -240,6 +240,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.mapProperties, this.mapProperties,
this.osmConnection.Backend(), this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed, (id) => self.layerState.filteredLayers.get(id).isDisplayed,
mvtAvailableLayers,
this.fullNodeDatabase this.fullNodeDatabase
) )
@ -662,10 +663,11 @@ export default class ThemeViewState implements SpecialVisualizationState {
Constants.priviliged_layers.indexOf(<any>l.id) < 0 && Constants.priviliged_layers.indexOf(<any>l.id) < 0 &&
l.source.geojsonSource === undefined l.source.geojsonSource === undefined
) )
const url = new URL(Constants.VectorTileServer)
return new SummaryTileSource( return new SummaryTileSource(
"http://127.0.0.1:2345", url.protocol + "//" + url.host + "/summary",
layers.map((l) => l.id), layers.map((l) => l.id),
this.mapProperties.zoom.map((z) => Math.max(Math.ceil(z) + 1, 0)), this.mapProperties.zoom.map((z) => Math.max(Math.ceil(z), 0)),
this.mapProperties this.mapProperties
) )
} }

View file

@ -1128,42 +1128,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
element.click() element.click()
} }
public static ColourNameToHex(color: string): string {
return colors[color.toLowerCase()] ?? color
}
public static HexToColourName(hex: string): string {
hex = hex.toLowerCase()
if (!hex.startsWith("#")) {
return hex
}
const c = Utils.color(hex)
let smallestDiff = Number.MAX_VALUE
let bestColor = undefined
for (const color in colors) {
if (!colors.hasOwnProperty(color)) {
continue
}
const foundhex = colors[color]
if (typeof foundhex !== "string") {
continue
}
if (foundhex === hex) {
return color
}
const diff = this.colorDiff(Utils.color(foundhex), c)
if (diff > 50) {
continue
}
if (diff < smallestDiff) {
smallestDiff = diff
bestColor = color
}
}
return bestColor ?? hex
}
/** /**
* Reorders an object: creates a new object where the keys have been added alphabetically * Reorders an object: creates a new object where the keys have been added alphabetically
* *
@ -1204,33 +1168,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return hours + ":" + Utils.TwoDigits(minutes) + ":" + Utils.TwoDigits(seconds) return hours + ":" + Utils.TwoDigits(minutes) + ":" + Utils.TwoDigits(seconds)
} }
public static DisableLongPresses() {
// Remove all context event listeners on mobile to prevent long presses
window.addEventListener(
"contextmenu",
(e) => {
// Not compatible with IE < 9
if (e.target["nodeName"] === "INPUT") {
return
}
e.preventDefault()
return false
},
false
)
}
public static preventDefaultOnMouseEvent(event: any) {
event?.originalEvent?.preventDefault()
event?.originalEvent?.stopPropagation()
event?.originalEvent?.stopImmediatePropagation()
if (event?.originalEvent) {
// This is a total workaround, as 'preventDefault' and everything above seems to be not working
event.originalEvent["dismissed"] = true
}
}
public static HomepageLink(): string { public static HomepageLink(): string {
if (typeof window === "undefined") { if (typeof window === "undefined") {
return "https://mapcomplete.org" return "https://mapcomplete.org"
@ -1711,4 +1648,19 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
) { ) {
return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b) return Math.abs(c0.r - c1.r) + Math.abs(c0.g - c1.g) + Math.abs(c0.b - c1.b)
} }
private static readonly _metrixPrefixes = ["", "k", "M", "G", "T", "P", "E"]
/**
* Converts a big number (e.g. 1000000) into a rounded postfixed verion (e.g. 1M)
*
* Supported metric prefixes are: [k, M, G, T, P, E]
*/
public static numberWithMetrixPrefix(n: number) {
let index = 0
while (n > 1000) {
n = Math.round(n / 1000)
index++
}
return n + Utils._metrixPrefixes[index]
}
} }

View file

@ -20,9 +20,15 @@ function webgl_support() {
return false return false
} }
} }
async function availableLayers(): Promise<Set<string>> { async function getAvailableLayers(): Promise<Set<string>> {
const status = await Utils.downloadJson(Constants.VectorTileServer + "/status.json") try {
const host = new URL(Constants.VectorTileServer).host
const status = await Utils.downloadJson("https://" + host + "/summary/status.json")
return new Set<string>(status.layers) return new Set<string>(status.layers)
} catch (e) {
console.error("Could not get MVT available layers due to", e)
return new Set<string>()
}
} }
async function main() { async function main() {
// @ts-ignore // @ts-ignore
@ -32,9 +38,10 @@ async function main() {
} }
const [layout, availableLayers] = await Promise.all([ const [layout, availableLayers] = await Promise.all([
DetermineLayout.GetLayout(), DetermineLayout.GetLayout(),
await availableLayers(), await getAvailableLayers(),
]) ])
const state = new ThemeViewState(layout) console.log("The available layers on server are", Array.from(availableLayers))
const state = new ThemeViewState(layout, availableLayers)
const main = new SvelteUIElement(ThemeViewGUI, { state }) const main = new SvelteUIElement(ThemeViewGUI, { state })
main.AttachTo("maindiv") main.AttachTo("maindiv")
} catch (err) { } catch (err) {