Merge branch 'develop'

This commit is contained in:
Pieter Vander Vennet 2024-06-16 13:06:42 +02:00
commit cf3ca610b0
41 changed files with 334 additions and 195 deletions

View file

@ -1,14 +1,4 @@
cache.mapcomplete.org { cache.mapcomplete.org {
reverse_proxy /summary/* { reverse_proxy /summary/* 127.0.0.1:2345
to http://127.0.0.1:2345 reverse_proxy /* 127.0.0.1:7800
}
reverse_proxy /extractgraph {
to http://127.0.0.1:2346
}
reverse_proxy /* {
to http://127.0.0.1:7800
}
} }

View file

@ -10,6 +10,6 @@ https://dynamicdns.park-your-domain.com/update?host=cache&domain=mapcomplete.org
## Setup ## Setup
See SettingUpPSQL.md See [SettingUpPSQL.md](../SettingUpPSQL.md)

View file

@ -3,7 +3,6 @@ hosted.mapcomplete.org {
file_server file_server
header { header {
+Permissions-Policy "interest-cohort=()" +Permissions-Policy "interest-cohort=()"
+Report-To `\{"group":"csp-endpoint", "max_age": 86400,"endpoints": [\{"url": "https://report.mapcomplete.org/csp"}], "include_subdomains": true}`
} }
} }
@ -11,12 +10,11 @@ countrycoder.mapcomplete.org {
root * tiles/ root * tiles/
file_server file_server
header { header {
+Permissions-Policy "interest-cohort=()" +Permissions-Policy "interest-cohort=()"
+Access-Control-Allow-Origin https://hosted.mapcomplete.org https://dev.mapcomplete.org https://mapcomplete.org +Access-Control-Allow-Origin https://hosted.mapcomplete.org https://dev.mapcomplete.org https://mapcomplete.org
} }
} }
report.mapcomplete.org { report.mapcomplete.org {
reverse_proxy { reverse_proxy {
to http://127.0.0.1:2600 to http://127.0.0.1:2600
@ -30,25 +28,7 @@ studio.mapcomplete.org {
} }
lod.mapcomplete.org { lod.mapcomplete.org {
reverse_proxy /extractgraph { reverse_proxy /extractgraph {
to http://127.0.0.1:2346 to http://127.0.0.1:2346
} }
}
proxy.mapcomplete.org {
reverse_proxy {
to http://127.0.0.1:1237
}
}
bounce.mapcomplete.org {
reverse_proxy {
to http://127.0.0.1:1236
}
}
cache.mapcomplete.org {
reverse_proxy /summary/* {
to http://127.0.0.1:2345
}
} }

View file

@ -59,11 +59,13 @@ HP ProLiant DL360 G7 (1U): 2Rx4 DDR3-memory (PC3)
## Deploying a tile server ## Deploying a tile server
pg_tileserv kan hier gedownload worden: https://github.com/CrunchyData/pg_tileserv pg_tileserv can be downloaded here: https://github.com/CrunchyData/pg_tileserv
In the directory where it is downloaded (e.g. `~/data`), run
```` ````
export DATABASE_URL=postgresql://user:password@localhost:5444/osm-poi export DATABASE_URL=postgresql://user:password@localhost:5444/osm-poi
nohup ./pg_tileserv & nohup ./pg_tileserv > pg_tileserv.log &
```` ````
Tiles are available at: Tiles are available at:
@ -74,6 +76,10 @@ map.addSource("drinking_water", {
}) })
```` ````
# Starting the summary server
`npm run summary-server` in the git repo
# Rebooting: # Rebooting:
-> Restart the docker container -> Restart the docker container

View file

@ -201,7 +201,7 @@
}, },
{ {
"#": "ignore-image-in-then", "#": "ignore-image-in-then",
"if": "context:website~*", "if": "contact:website~*",
"then": "<a href='{contact:website}' target='_blank' rel='noopener'><img textmode='🌐' alt='website' src='./assets/layers/icons/website.svg'/></a>" "then": "<a href='{contact:website}' target='_blank' rel='noopener'><img textmode='🌐' alt='website' src='./assets/layers/icons/website.svg'/></a>"
} }
] ]

View file

@ -364,5 +364,8 @@
} }
} }
], ],
"allowMove": true "allowMove": true,
"deletion": {
"neededChangesets": 0
}
} }

View file

@ -426,6 +426,36 @@
} }
] ]
}, },
{
"id": "copyshop-binding",
"condition": {
"or": [
"shop~.*copyshop.*",
"shop~.*stationery.*",
"service:print=yes"
]
},
"question": {
"en": "Does this shop offer a binding service?"
},
"questionHint": {
"en": "Does this shop bind a bundle of pages into a small book, e.g. with a comb, a spiral, wire or by gluing?"
},
"mappings": [
{
"if": "service:binding=yes",
"then": {
"en": "This shop binds papers into a booklet"
}
},
{
"if": "service:binding=no",
"then": {
"en": "This shop does bind books"
}
}
]
},
{ {
"id": "key_cutter", "id": "key_cutter",
"question": { "question": {

56
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.42.6", "version": "0.43.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mapcomplete", "name": "mapcomplete",
"version": "0.42.6", "version": "0.43.1",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@comunica/core": "^3.0.1", "@comunica/core": "^3.0.1",
@ -7682,10 +7682,11 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"license": "MIT", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -9756,8 +9757,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"license": "MIT", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -10770,6 +10772,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-path-inside": { "node_modules/is-path-inside": {
"version": "3.0.3", "version": "3.0.3",
"dev": true, "dev": true,
@ -17710,7 +17720,8 @@
}, },
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"license": "MIT", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },
@ -17718,13 +17729,6 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/to-regex-range/node_modules/is-number": {
"version": "7.0.0",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/topojson-client": { "node_modules/topojson-client": {
"version": "3.1.0", "version": "3.1.0",
"license": "ISC", "license": "ISC",
@ -24931,9 +24935,11 @@
} }
}, },
"braces": { "braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"requires": { "requires": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
} }
}, },
"brorand": { "brorand": {
@ -26246,7 +26252,9 @@
} }
}, },
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"requires": { "requires": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
} }
@ -26866,6 +26874,11 @@
"define-properties": "^1.1.3" "define-properties": "^1.1.3"
} }
}, },
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-path-inside": { "is-path-inside": {
"version": "3.0.3", "version": "3.0.3",
"dev": true "dev": true
@ -31326,13 +31339,10 @@
}, },
"to-regex-range": { "to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"requires": { "requires": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
},
"dependencies": {
"is-number": {
"version": "7.0.0"
}
} }
}, },
"topojson-client": { "topojson-client": {

View file

@ -22,6 +22,8 @@
"url": "https://www.openstreetmap.org" "url": "https://www.openstreetmap.org"
}, },
"mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf", "mvt_layer_server": "https://cache.mapcomplete.org/public.{type}_{layer}/{z}/{x}/{y}.pbf",
"#summary_server": "Should be the endpoint; appending status.json should work",
"summary_server": "https://cache.mapcomplete.org/",
"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

@ -5,6 +5,7 @@ import Script from "./Script"
import { GeoOperations } from "../src/Logic/GeoOperations" import { GeoOperations } from "../src/Logic/GeoOperations"
import { Feature, Polygon } from "geojson" import { Feature, Polygon } from "geojson"
import { Tiles } from "../src/Models/TileRange" import { Tiles } from "../src/Models/TileRange"
import { BBox } from "../src/Logic/BBox"
class StatsDownloader { class StatsDownloader {
private readonly urlTemplate = private readonly urlTemplate =
@ -123,7 +124,7 @@ class StatsDownloader {
Referer: Referer:
"https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D", "https://osmcha.org/?filters=%7B%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22mapcomplete%22%2C%22value%22%3A%22mapcomplete%22%7D%5D%7D",
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: "Token 6e422e2afedb79ef66573982012000281f03dc91", Authorization: "Token 9cc11ad2868778272eadbb1a423ebb507184bc04",
DNT: "1", DNT: "1",
Connection: "keep-alive", Connection: "keep-alive",
TE: "Trailers", TE: "Trailers",
@ -135,7 +136,7 @@ class StatsDownloader {
ScriptUtils.erasableLog( ScriptUtils.erasableLog(
`Downloading stats for ${year}-${month}-${day}, page ${page} ${url}` `Downloading stats for ${year}-${month}-${day}, page ${page} ${url}`
) )
const result = await Utils.downloadJson(url, headers) const result = await Utils.downloadJson<{features: [], next: string}>(url, headers)
page++ page++
allFeatures.push(...result.features) allFeatures.push(...result.features)
if (result.features === undefined) { if (result.features === undefined) {
@ -201,11 +202,11 @@ class GenerateSeries extends Script {
const targetDir = args[0] ?? "../../git/MapComplete-data" const targetDir = args[0] ?? "../../git/MapComplete-data"
await this.downloadStatistics(targetDir + "/changeset-metadata") await this.downloadStatistics(targetDir + "/changeset-metadata")
await this.generateCenterPoints( this.generateCenterPoints(
targetDir + "/changeset-metadata", targetDir + "/changeset-metadata",
targetDir + "/mapcomplete-changes/", targetDir + "/mapcomplete-changes/",
{ {
zoomlevel: 8, zoomlevel: 8
} }
) )
} }
@ -248,10 +249,8 @@ class GenerateSeries extends Script {
const allPaths = readdirSync(sourceDir).filter( const allPaths = readdirSync(sourceDir).filter(
(p) => p.startsWith("stats.") && p.endsWith(".json") (p) => p.startsWith("stats.") && p.endsWith(".json")
) )
let allFeatures: ChangeSetData[] = [].concat( let allFeatures: ChangeSetData[] = allPaths.flatMap(
...allPaths.map(
(path) => JSON.parse(readFileSync(sourceDir + "/" + path, "utf-8")).features (path) => JSON.parse(readFileSync(sourceDir + "/" + path, "utf-8")).features
)
) )
allFeatures = allFeatures.filter( allFeatures = allFeatures.filter(
(f) => (f) =>
@ -270,8 +269,24 @@ class GenerateSeries extends Script {
f.properties.editor.toLowerCase().startsWith("mapcomplete")) f.properties.editor.toLowerCase().startsWith("mapcomplete"))
) )
allFeatures = allFeatures.filter((f) => f.properties.metadata?.theme !== "EMPTY CS") allFeatures = allFeatures.filter((f) => f.properties.metadata?.theme !== "EMPTY CS" && f.geometry.coordinates.length > 0)
const centerpoints = allFeatures.map((f) => GeoOperations.centerpoint(f)) const centerpointsAll = allFeatures.map((f) => {
const centerpoint = GeoOperations.centerpoint(f)
const c = centerpoint.geometry.coordinates
// OsmCha doesn't adhere to the Geojson standard and uses `lat` `lon` as coordinates instead of `lon`, `lat`
centerpoint.geometry.coordinates = [c[1], c[0]]
return centerpoint
})
const centerpoints = centerpointsAll.filter(p => {
const bbox= BBox.get(p)
if(bbox.minLat === -90 && bbox.maxLat === -90){
// Due to some bug somewhere, those invalid bboxes might appear if the latitude is < 90
// This crashes the 'spreadIntoBBoxes
// As workaround, we simply ignore them for now
return false
}
return true
})
console.log("Found", centerpoints.length, " changesets in total") console.log("Found", centerpoints.length, " changesets in total")
const perBbox = GeoOperations.spreadIntoBboxes(centerpoints, options.zoomlevel) const perBbox = GeoOperations.spreadIntoBboxes(centerpoints, options.zoomlevel)

View file

@ -392,6 +392,8 @@ class GenerateLayouts extends Script {
} }
} }
hosts.add("http://www.schema.org") // Schema.org is _not_ encrypted and thus needs an exception
if (hosts.has("*")) { if (hosts.has("*")) {
throw "* is not allowed as connect-src" throw "* is not allowed as connect-src"
} }

View file

@ -65,6 +65,10 @@ class Compare extends Script {
async main(args: string[]): Promise<void> { async main(args: string[]): Promise<void> {
let [velopark, osm, key] = args let [velopark, osm, key] = args
if(velopark === undefined || osm === undefined){
console.log("Needed argument: velopark.geojson osm.geojson [key]\nThe key is optional and will be `ref:velopark` by default\nUse overpass to get a geojson with ref:velopark")
return
}
key ??= "ref:velopark" key ??= "ref:velopark"
const veloparkData: FeatureCollection = JSON.parse(fs.readFileSync(velopark, "utf-8")) const veloparkData: FeatureCollection = JSON.parse(fs.readFileSync(velopark, "utf-8"))
const osmData: FeatureCollection = JSON.parse(fs.readFileSync(osm, "utf-8")) const osmData: FeatureCollection = JSON.parse(fs.readFileSync(osm, "utf-8"))
@ -82,6 +86,7 @@ class Compare extends Script {
console.error("No velopark entry found for", veloId) console.error("No velopark entry found for", veloId)
continue continue
} }
console.log("Veloparking is", veloparking)
diffs.push(this.compare(veloId, parking, veloparking)) diffs.push(this.compare(veloId, parking, veloparking))
} }
console.log( console.log(

View file

@ -0,0 +1,63 @@
import Script from "../Script"
import { readFileSync, writeFileSync } from "fs"
import { OsmId } from "../../src/Models/OsmFeature"
import { Utils } from "../../src/Utils"
interface DiffItem {
/**
* Velopark-id
*/
"ref": string,
"osmid": OsmId,
"distance": number,
"diffs": {
key: string,
/**
* The value in OpenStreetMap
* Might be undefined if OSM doesn't have an appropriate value
*/
osm?: string,
velopark: string | number } []
}
export class DiffToCsv extends Script {
constructor() {
super("Converts a 'report.diff' to a CSV file for people who prefer LibreOffice Calc (or other Spreadsheet Software)")
}
async main(args: string[]): Promise<void> {
const file = args[0] ?? "report_diff.json"
const json = <{diffs:DiffItem[], distanceBinds: number[]}> JSON.parse(readFileSync(file, "utf8"))
const diffs = json.diffs
const allKeys = Utils.Dedup(diffs.flatMap(item => item.diffs.map(d => d.key)))
allKeys.sort()
const header = ["osm_id","velopark_id", "distance",...allKeys.flatMap(k => ["osm:"+k, "velopark:"+k])]
const lines = [header]
for (const diffItem of diffs) {
const line = []
lines.push(line)
line.push(diffItem.osmid)
line.push(diffItem.ref)
line.push(diffItem.distance)
const d = diffItem.diffs
for (const k of allKeys) {
const found = d.find(i => i.key === k)
if(!found){
line.push("","")
continue
}
line.push(found.osm, found.velopark)
}
}
const path = "report_diff.csv"
writeFileSync(path,
lines.map(l => l.join(",")).join("\n")
,"utf8")
console.log("Written", path)
}
}
new DiffToCsv().run()

View file

@ -23,9 +23,9 @@ class VeloParkToGeojson extends Script {
JSON.stringify( JSON.stringify(
extension === ".geojson" extension === ".geojson"
? { ? {
type: "FeatureCollection", type: "FeatureCollection",
features, features
} }
: features, : features,
null, null,
" " " "
@ -34,34 +34,14 @@ class VeloParkToGeojson extends Script {
console.log("Written", file, "(" + features.length, " features)") console.log("Written", file, "(" + features.length, " features)")
} }
public static sumProperties(data: object, addTo: Record<string, Set<string>>) {
delete data["@context"]
for (const k in data) {
if (k === "@graph") {
for (const obj of data["@graph"]) {
this.sumProperties(obj, addTo)
}
continue
}
if (addTo[k] === undefined) {
addTo[k] = new Set<string>()
}
addTo[k].add(data[k])
}
}
private static async downloadDataFor( private static async downloadDataFor(
url: string, url: string
allProperties: Record<string, Set<string>>
): Promise<Feature[]> { ): Promise<Feature[]> {
const cachePath = "/home/pietervdvn/data/velopark_cache/" + url.replace(/[/:.]/g, "_") const cachePath = "/home/pietervdvn/data/velopark_cache_refined/" + url.replace(/[/:.]/g, "_")
if (!fs.existsSync(cachePath)) { if (fs.existsSync(cachePath)) {
const data = await Utils.downloadJson(url) return JSON.parse(fs.readFileSync(cachePath, "utf8"))
fs.writeFileSync(cachePath, JSON.stringify(data), "utf-8")
console.log("Saved a backup to", cachePath)
} }
console.log("Fetching data for", url)
this.sumProperties(JSON.parse(fs.readFileSync(cachePath, "utf-8")), allProperties)
const linkedData = await LinkedDataLoader.fetchVeloparkEntry(url) const linkedData = await LinkedDataLoader.fetchVeloparkEntry(url)
const allVelopark: Feature[] = [] const allVelopark: Feature[] = []
@ -70,9 +50,12 @@ class VeloParkToGeojson extends Script {
if (Object.keys(sectionInfo).length === 0) { if (Object.keys(sectionInfo).length === 0) {
console.warn("No result for", url) console.warn("No result for", url)
} }
sectionInfo["ref:velopark"] = [sectionId ?? url] if(!sectionInfo.geometry?.coordinates){
throw "Invalid properties!"
}
allVelopark.push(sectionInfo) allVelopark.push(sectionInfo)
} }
fs.writeFileSync(cachePath, JSON.stringify(allVelopark), "utf8")
return allVelopark return allVelopark
} }
@ -85,20 +68,24 @@ class VeloParkToGeojson extends Script {
let failed = 0 let failed = 0
console.log("Got", allVeloparkRaw.length, "items") console.log("Got", allVeloparkRaw.length, "items")
const allVelopark: Feature[] = [] const allVelopark: Feature[] = []
const allProperties = {} const batchSize = 50
for (let i = 0; i < allVeloparkRaw.length; i++) { for (let i = 0; i < allVeloparkRaw.length; i += batchSize) {
const f = allVeloparkRaw[i] await Promise.all(Utils.TimesT(batchSize, j => j).map(
console.log("Handling", i + "/" + allVeloparkRaw.length) async j => {
try { const f = allVeloparkRaw[i + j]
const sections: Feature[] = await VeloParkToGeojson.downloadDataFor( if (!f) {
f.url, return
allProperties }
) try {
allVelopark.push(...sections) const sections: Feature[] = await VeloParkToGeojson.downloadDataFor(f.url)
} catch (e) { allVelopark.push(...sections)
console.error("Loading ", f.url, " failed due to", e) } catch (e) {
failed++ console.error("Loading ", f.url, " failed due to", e)
} failed++
}
}
))
} }
console.log( console.log(
"Fetching data done, got ", "Fetching data done, got ",
@ -107,11 +94,6 @@ class VeloParkToGeojson extends Script {
failed failed
) )
VeloParkToGeojson.exportGeojsonTo("velopark_all", allVelopark) VeloParkToGeojson.exportGeojsonTo("velopark_all", allVelopark)
for (const k in allProperties) {
allProperties[k] = Array.from(allProperties[k])
}
fs.writeFileSync("all_properties_mashup.json", JSON.stringify(allProperties, null, " "))
return allVelopark return allVelopark
} }
@ -158,7 +140,7 @@ class VeloParkToGeojson extends Script {
private static async createDiff(allVelopark: Feature[]) { private static async createDiff(allVelopark: Feature[]) {
const bboxBelgium = new BBox([ const bboxBelgium = new BBox([
[2.51357303225, 49.5294835476], [2.51357303225, 49.5294835476],
[6.15665815596, 51.4750237087], [6.15665815596, 51.4750237087]
]) ])
const alreadyLinkedQuery = new Overpass( const alreadyLinkedQuery = new Overpass(
@ -201,7 +183,7 @@ class VeloParkToGeojson extends Script {
VeloParkToGeojson.exportExtraAmenities(allVelopark) VeloParkToGeojson.exportExtraAmenities(allVelopark)
await VeloParkToGeojson.createDiff(allVelopark) await VeloParkToGeojson.createDiff(allVelopark)
console.log( console.log(
"Use vite-node script/velopark/compare to compare the results and generate a diff file" "Use vite-node scripts/velopark/compare.ts to compare the results and generate a diff file"
) )
} }
} }

View file

@ -16,6 +16,18 @@ export class BBox {
/*** /***
* Coordinates should be [[lon, lat],[lon, lat]] * Coordinates should be [[lon, lat],[lon, lat]]
* @param coordinates * @param coordinates
*
* const bb = new BBox([[5,6]])
* bb.minLat // => 6
* bb.minLon // => 5
* bb.maxLat // => 6
* bb.maxLon // => 5
*
* const bb = new BBox([[-100,-100]])
* bb.minLat // => -90
* bb.minLon // => -100
* bb.maxLat // => -90
* bb.maxLon // => -100
*/ */
constructor(coordinates: [number, number][]) { constructor(coordinates: [number, number][]) {
this.maxLat = -90 this.maxLat = -90
@ -45,13 +57,24 @@ export class BBox {
]) ])
} }
/**
* Gets the bbox from a feature and caches it (by monkeypatching) the relevant feature
*
*
* const f = {type:"Feature",properties: {}, geometry: {type: "Point", coordinates: [-100,45]}}
* const bb = BBox.get(f)
* bb.minLat // => 45
* bb.minLon // => -100
*
*/
static get(feature: Feature): BBox { static get(feature: Feature): BBox {
const f = <any>feature const f = <any>feature
if (f.bbox?.overlapsWith === undefined) { if (f.bbox?.overlapsWith === undefined) {
const turfBbox: number[] = turf.bbox(feature) const [minX, minY, maxX, maxY]: number[] = turf.bbox(feature)
// Note: x is longitude
f["bbox"] = new BBox([ f["bbox"] = new BBox([
[turfBbox[0], turfBbox[1]], [minX, minY],
[turfBbox[2], turfBbox[3]], [maxX, maxY],
]) ])
} }
return f["bbox"] return f["bbox"]

View file

@ -26,7 +26,6 @@ export default class LayoutSource extends FeatureSourceMerger {
private readonly supportsForceDownload: UpdatableFeatureSource[] private readonly supportsForceDownload: UpdatableFeatureSource[]
private readonly fromCache: Map<string, LocalStorageFeatureSource>
public static readonly fromCacheZoomLevel = 15 public static readonly fromCacheZoomLevel = 15
constructor( constructor(
layers: LayerConfig[], layers: LayerConfig[],

View file

@ -734,7 +734,6 @@ export class GeoOperations {
const splitted: Feature<LineString>[] = [].concat(...lines) const splitted: Feature<LineString>[] = [].concat(...lines)
const kept: Feature<LineString>[] = [] const kept: Feature<LineString>[] = []
for (const f of splitted) { for (const f of splitted) {
console.log("Checking", f)
if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) { if (!GeoOperations.inside(GeoOperations.centerpointCoordinates(f), boundary)) {
continue continue
} }

View file

@ -316,14 +316,16 @@ export default class LinkedDataLoader {
input: Record<string, Set<string>> input: Record<string, Set<string>>
): Record<string, string[]> { ): Record<string, string[]> {
const output: Record<string, string[]> = {} const output: Record<string, string[]> = {}
console.log("Input for patchVelopark:", input)
for (const k in input) { for (const k in input) {
output[k] = Array.from(input[k]) output[k] = Array.from(input[k])
} }
if (output["type"][0] === "https://data.velopark.be/openvelopark/terms#BicycleLocker") { if (output["type"]?.[0] === "https://data.velopark.be/openvelopark/terms#BicycleLocker") {
output["bicycle_parking"] = ["lockers"] output["bicycle_parking"] = ["lockers"]
} }
if(output["type"] === undefined){
console.error("No type given for", output)
}
delete output["type"] delete output["type"]
function on(key: string, applyF: (s: string) => string) { function on(key: string, applyF: (s: string) => string) {
@ -506,7 +508,6 @@ export default class LinkedDataLoader {
" ?parking a <http://schema.mobivoc.org/BicycleParkingStation>", " ?parking a <http://schema.mobivoc.org/BicycleParkingStation>",
"?parking " + property + " " + (variable ?? "") "?parking " + property + " " + (variable ?? "")
) )
console.log("Fetching a velopark property gave", property, results)
return results return results
} }

View file

@ -164,6 +164,7 @@ export default class Constants {
*/ */
public static VectorTileServer: string | undefined = Constants.config.mvt_layer_server public static VectorTileServer: string | undefined = Constants.config.mvt_layer_server
public static readonly maptilerApiKey = "GvoVAJgu46I5rZapJuAy" public static readonly maptilerApiKey = "GvoVAJgu46I5rZapJuAy"
public static readonly SummaryServer: string = Constants.config.summary_server
private static isRetina(): boolean { private static isRetina(): boolean {
if (Utils.runningFromConsole) { if (Utils.runningFromConsole) {

View file

@ -80,11 +80,14 @@ export class AvailableRasterLayers {
return GeoOperations.inside(lonlat, eliPolygon) return GeoOperations.inside(lonlat, eliPolygon)
}) })
matching.unshift(AvailableRasterLayers.osmCarto) matching.unshift(AvailableRasterLayers.osmCarto)
matching.push(AvailableRasterLayers.defaultBackgroundLayer)
if (enableBing?.data) { if (enableBing?.data) {
matching.push(AvailableRasterLayers.bing) matching.push(AvailableRasterLayers.bing)
} }
matching.push(...AvailableRasterLayers.globalLayers) matching.push(...AvailableRasterLayers.globalLayers)
if(!matching.some(l => l.id === AvailableRasterLayers.defaultBackgroundLayer.properties.id)){
matching.push(AvailableRasterLayers.defaultBackgroundLayer)
}
return matching return matching
}, },
[enableBing] [enableBing]

View file

@ -690,9 +690,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
l.source.geojsonSource === undefined && l.source.geojsonSource === undefined &&
l.doCount l.doCount
) )
const url = new URL(Constants.VectorTileServer)
const summaryTileSource = new SummaryTileSource( const summaryTileSource = new SummaryTileSource(
url.protocol + "//" + url.host + "/summary", Constants.SummaryServer,
layers.map((l) => l.id), layers.map((l) => l.id),
this.mapProperties.zoom.map((z) => Math.max(Math.floor(z), 0)), this.mapProperties.zoom.map((z) => Math.max(Math.floor(z), 0)),
this.mapProperties, this.mapProperties,

View file

@ -1,4 +1,5 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement"
import StylesheetTestGui from "./UI/StylesheetTestGui.svelte" import StylesheetTestGui from "./UI/StylesheetTestGui.svelte"
new SvelteUIElement(StylesheetTestGui, {}).AttachTo("main") new StylesheetTestGui({
target: document.getElementById("maindiv")
})

View file

@ -65,6 +65,7 @@
} }
</script> </script>
<main>
<div class="m-4 flex flex-col"> <div class="m-4 flex flex-col">
<LanguagePicker <LanguagePicker
clss="self-end max-w-full" clss="self-end max-w-full"
@ -165,3 +166,4 @@
v{Constants.vNumber} v{Constants.vNumber}
</div> </div>
</div> </div>
</main>

View file

@ -16,6 +16,10 @@
* If set, 'loading' will act as if we are already logged in. * If set, 'loading' will act as if we are already logged in.
*/ */
export let ignoreLoading: boolean = false export let ignoreLoading: boolean = false
/**
* Only show the 'successful' state, don't show loading or error messages
*/
export let silentFail : boolean = false
let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in") let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in")
let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true) let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true)
const t = Translations.t.general const t = Translations.t.general
@ -30,11 +34,11 @@
</script> </script>
{#if $badge} {#if $badge}
{#if !ignoreLoading && $loadingStatus === "loading"} {#if !ignoreLoading && !silentFail && $loadingStatus === "loading"}
<slot name="loading"> <slot name="loading">
<Loading /> <Loading />
</slot> </slot>
{:else if $loadingStatus === "error"} {:else if !silentFail && $loadingStatus === "error"}
<slot name="error"> <slot name="error">
<div class="alert max-w-64 flex items-center"> <div class="alert max-w-64 flex items-center">
<Invalid class="m-2 h-8 w-8 shrink-0" /> <Invalid class="m-2 h-8 w-8 shrink-0" />
@ -43,7 +47,7 @@
</slot> </slot>
{:else if $loadingStatus === "logged-in"} {:else if $loadingStatus === "logged-in"}
<slot /> <slot />
{:else if $loadingStatus === "not-attempted"} {:else if !silentFail && $loadingStatus === "not-attempted"}
<slot name="not-logged-in" /> <slot name="not-logged-in" />
{/if} {/if}
{/if} {/if}

View file

@ -13,6 +13,7 @@
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
import AttributedImage from "./AttributedImage.svelte" import AttributedImage from "./AttributedImage.svelte"
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte" import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
import LoginToggle from "../Base/LoginToggle.svelte"
export let tags: UIEventSource<OsmTags> export let tags: UIEventSource<OsmTags>
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
@ -68,10 +69,12 @@
previewedImage={state.previewedImage} previewedImage={state.previewedImage}
/> />
</div> </div>
<LoginToggle {state} silentFail={true} >
{#if linkable} {#if linkable}
<label> <label>
<input bind:checked={$isLinked} type="checkbox" /> <input bind:checked={$isLinked} type="checkbox" />
<SpecialTranslation t={t.link} {tags} {state} {layer} {feature} /> <SpecialTranslation t={t.link} {tags} {state} {layer} {feature} />
</label> </label>
{/if} {/if}
</LoginToggle>
</div> </div>

View file

@ -30,7 +30,7 @@
lon, lon,
lat, lat,
allowSpherical: new UIEventSource<boolean>(false), allowSpherical: new UIEventSource<boolean>(false),
blacklist: AllImageProviders.LoadImagesFor(tags), blacklist: AllImageProviders.LoadImagesFor(tags)
}, },
state.indexedFeatures state.indexedFeatures
) )
@ -39,26 +39,24 @@
let allDone = imagesProvider.allDone let allDone = imagesProvider.allDone
</script> </script>
<LoginToggle {state}> <div class="interactive border-interactive rounded-2xl p-2">
<div class="interactive border-interactive rounded-2xl p-2"> <div class="flex justify-between">
<div class="flex justify-between"> <h4>
<h4> <Tr t={Translations.t.image.nearby.title} />
<Tr t={Translations.t.image.nearby.title} /> </h4>
</h4> <slot name="corner" />
<slot name="corner" /> </div>
</div> {#if !$allDone}
{#if !$allDone} <Loading />
<Loading /> {:else if $images.length === 0}
{:else if $images.length === 0} <Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" />
<Tr t={Translations.t.image.nearby.noNearbyImages} cls="alert" /> {:else}
{:else} <div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity">
<div class="flex w-full space-x-1 overflow-x-auto" style="scroll-snap-type: x proximity"> {#each $images as image (image.pictureUrl)}
{#each $images as image (image.pictureUrl)}
<span class="w-fit shrink-0" style="scroll-snap-align: start"> <span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} /> <LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
</span> </span>
{/each} {/each}
</div> </div>
{/if} {/if}
</div> </div>
</LoginToggle>

View file

@ -25,7 +25,6 @@
let expanded = false let expanded = false
</script> </script>
<LoginToggle {state}>
<div class="my-4"> <div class="my-4">
{#if expanded} {#if expanded}
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer}> <NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer}>
@ -54,4 +53,3 @@
</button> </button>
{/if} {/if}
</div> </div>
</LoginToggle>

View file

@ -30,7 +30,7 @@
} }
> = UIEventSource.FromPromise(Utils.downloadJsonCached(source)) > = UIEventSource.FromPromise(Utils.downloadJsonCached(source))
</script> </script>
<main>
<h1>Contributed images with MapComplete: leaderboard</h1> <h1>Contributed images with MapComplete: leaderboard</h1>
{#if $data} {#if $data}
@ -67,3 +67,4 @@
<div> <div>
Logged in as {$loggedInContributor} Logged in as {$loggedInContributor}
</div> </div>
</main>

View file

@ -5,6 +5,7 @@
console.log("???") console.log("???")
</script> </script>
<main>
<div class="flex flex-col"> <div class="flex flex-col">
<Tr t={Translations.t.general["404"]} /> <Tr t={Translations.t.general["404"]} />
<BackButton <BackButton
@ -18,3 +19,4 @@
</div> </div>
</BackButton> </BackButton>
</div> </div>
</main>

View file

@ -63,7 +63,7 @@
} }
</script> </script>
<span {lang}> {#if lang === "*"}
{#each specs as specpart} {#each specs as specpart}
{#if typeof specpart === "string"} {#if typeof specpart === "string"}
<span> <span>
@ -74,4 +74,17 @@
<ToSvelte construct={() => createVisualisation(specpart)} /> <ToSvelte construct={() => createVisualisation(specpart)} />
{/if} {/if}
{/each} {/each}
</span> {:else}
<span {lang}>
{#each specs as specpart}
{#if typeof specpart === "string"}
<span>
{@html Utils.purify(Utils.SubstituteKeys(specpart, $tags)) }
<WeblateLink context={t.context} />
</span>
{:else if $tags !== undefined}
<ToSvelte construct={() => createVisualisation(specpart)} />
{/if}
{/each}
</span>
{/if}

View file

@ -16,7 +16,7 @@
userRelatedState: new UserRelatedState(osmConnection) userRelatedState: new UserRelatedState(osmConnection)
} }
</script> </script>
<main>
<div class="flex h-screen flex-col overflow-hidden px-4"> <div class="flex h-screen flex-col overflow-hidden px-4">
<div class="flex justify-between"> <div class="flex justify-between">
<h2 class="flex items-center"> <h2 class="flex items-center">
@ -33,3 +33,4 @@
<Tr t={Translations.t.general.backToIndex} /> <Tr t={Translations.t.general.backToIndex} />
</a> </a>
</div> </div>
</main>

View file

@ -6,6 +6,7 @@
import { UIEventSource } from "../Logic/UIEventSource" import { UIEventSource } from "../Logic/UIEventSource"
</script> </script>
<main>
<div> <div>
<h1>Stylesheet testing grounds</h1> <h1>Stylesheet testing grounds</h1>
@ -167,3 +168,4 @@
</select> </select>
</div> </div>
</div> </div>
</main>

View file

@ -1,16 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Translation } from "./i18n/Translation"
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
import Tr from "./Base/Tr.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
const m = new Map<string, string>()
m.set("nl", "Nederlands")
const tr = Translation.fromMap(m, true)
</script> </script>
<LanguagePicker/> <main>
<h1>Svelte native</h1> </main>
<Tr t={tr}/>
<h1>ToSvelte</h1>
<ToSvelte construct={tr}/>

View file

@ -203,6 +203,7 @@
</script> </script>
<main>
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden"> <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
<MaplibreMap map={maplibremap} mapProperties={mapproperties} /> <MaplibreMap map={maplibremap} mapProperties={mapproperties} />
</div> </div>
@ -688,3 +689,4 @@
<CloseAnimation isOpened={selectedElement.map(sl => sl !== undefined && sl?.properties?.id === LastClickFeatureSource.newPointElementId)} moveTo={openNewElementButton} debug="newElement"/> <CloseAnimation isOpened={selectedElement.map(sl => sl !== undefined && sl?.properties?.id === LastClickFeatureSource.newPointElementId)} moveTo={openNewElementButton} debug="newElement"/>
<CloseAnimation isOpened={state.guistate.filtersPanelIsOpened} moveTo={openFilterButton} debug="filter"/> <CloseAnimation isOpened={state.guistate.filtersPanelIsOpened} moveTo={openFilterButton} debug="filter"/>
<CloseAnimation isOpened={state.guistate.backgroundLayerSelectionIsOpened} moveTo={openBackgroundButton} debug="bg"/> <CloseAnimation isOpened={state.guistate.backgroundLayerSelectionIsOpened} moveTo={openBackgroundButton} debug="bg"/>
</main>

View file

@ -28,4 +28,6 @@ if (layout !== "") {
) )
} }
new SvelteUIElement(AllThemesGui, {}).AttachTo("main") new AllThemesGui({
target: document.getElementById("main")
})

View file

@ -28,7 +28,7 @@ async function timeout(timeMS: number): Promise<{ layers: string[] }> {
async function getAvailableLayers(): Promise<Set<string>> { async function getAvailableLayers(): Promise<Set<string>> {
try { try {
const host = new URL(Constants.VectorTileServer).host const host = new URL(Constants.SummaryServer).host
const status: { layers: string[] } = await Promise.any([ const status: { layers: string[] } = await Promise.any([
Utils.downloadJson<{layers}>("https://" + host + "/summary/status.json"), Utils.downloadJson<{layers}>("https://" + host + "/summary/status.json"),
timeout(2500), timeout(2500),
@ -52,8 +52,10 @@ async function main() {
]) ])
console.log("The available layers on server are", Array.from(availableLayers)) console.log("The available layers on server are", Array.from(availableLayers))
const state = new ThemeViewState(layout, availableLayers) const state = new ThemeViewState(layout, availableLayers)
const main = new SvelteUIElement(ThemeViewGUI, { state }) new ThemeViewGUI({
main.AttachTo("maindiv") target: document.getElementById("maindiv"),
props: {state}
})
Array.from(document.getElementsByClassName("delete-on-load")).forEach((el) => { Array.from(document.getElementsByClassName("delete-on-load")).forEach((el) => {
el.parentElement.removeChild(el) el.parentElement.removeChild(el)
}) })

View file

@ -1,5 +1,4 @@
import ThemeViewState from "./src/Models/ThemeViewState" import ThemeViewState from "./src/Models/ThemeViewState"
import SvelteUIElement from "./src/UI/Base/SvelteUIElement"
import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte" import ThemeViewGUI from "./src/UI/ThemeViewGUI.svelte"
import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig"; import LayoutConfig from "./src/Models/ThemeConfig/LayoutConfig";
import MetaTagging from "./src/Logic/MetaTagging"; import MetaTagging from "./src/Logic/MetaTagging";
@ -47,8 +46,10 @@ async function main() {
MetaTagging.setThemeMetatagging(new ThemeMetaTagging()) MetaTagging.setThemeMetatagging(new ThemeMetaTagging())
// LAYOUT.ADD_LAYERS // LAYOUT.ADD_LAYERS
const state = new ThemeViewState(new LayoutConfig(<any> layout), availableLayers) const state = new ThemeViewState(new LayoutConfig(<any> layout), availableLayers)
const main = new SvelteUIElement(ThemeViewGUI, { state }) new ThemeViewGUI({
main.AttachTo("maindiv") target: document.getElementById("maindiv"),
props: {state}
})
Array.from(document.getElementsByClassName("delete-on-load")).forEach(el => { Array.from(document.getElementsByClassName("delete-on-load")).forEach(el => {
el.parentElement.removeChild(el) el.parentElement.removeChild(el)
}) })

View file

@ -1,4 +1,5 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement"
import Leaderboard from "./UI/Leaderboard.svelte" import Leaderboard from "./UI/Leaderboard.svelte"
new SvelteUIElement(Leaderboard, {}).AttachTo("main") new Leaderboard({
target: document.getElementById("main")
})

View file

@ -1,4 +1,5 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement"
import NotFound from "./UI/NotFound.svelte" import NotFound from "./UI/NotFound.svelte"
new SvelteUIElement(NotFound, {}).AttachTo("maindiv") new NotFound({
target: document.getElementById("maindiv")
})

View file

@ -1,3 +1,4 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement"
import PrivacyGui from "./UI/PrivacyGui.svelte" import PrivacyGui from "./UI/PrivacyGui.svelte"
new SvelteUIElement(PrivacyGui, {}).AttachTo("main") new PrivacyGui({
target: document.getElementById("main")
})

View file

@ -1,4 +1,6 @@
import SvelteUIElement from "./UI/Base/SvelteUIElement" import SvelteUIElement from "./UI/Base/SvelteUIElement"
import Test from "./UI/Test.svelte" import Test from "./UI/Test.svelte"
new SvelteUIElement(Test).AttachTo("maindiv") new Test({
target: document.getElementById("maindiv")
})