Add module to fetch data (via a proxy) from the website with jsonld
This commit is contained in:
parent
1b06eee15b
commit
352414b29d
17 changed files with 388 additions and 351 deletions
|
@ -90,11 +90,8 @@
|
||||||
"id": "show-data-velopark",
|
"id": "show-data-velopark",
|
||||||
"render": {
|
"render": {
|
||||||
"special": {
|
"special": {
|
||||||
"type": "compare_data",
|
"type": "linked_data_from_website",
|
||||||
"url": "ref:velopark",
|
"key": "ref:velopark"
|
||||||
"host": "https://data.velopark.be",
|
|
||||||
"postprocessing": "velopark",
|
|
||||||
"readonly": "yes"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -338,10 +335,8 @@
|
||||||
},
|
},
|
||||||
"render": {
|
"render": {
|
||||||
"special": {
|
"special": {
|
||||||
"type": "compare_data",
|
"type": "linked_data_from_website",
|
||||||
"url": "ref:velopark",
|
"key": "ref:velopark"
|
||||||
"host": "https://data.velopark.be",
|
|
||||||
"postprocessing": "velopark"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,9 @@
|
||||||
"weblate-fix-heavy": "git fetch weblate-hosted-layers; git fetch weblate-hosted-core; git merge weblate-hosted-layers/master weblate-hosted-core/master ",
|
"weblate-fix-heavy": "git fetch weblate-hosted-layers; git fetch weblate-hosted-core; git merge weblate-hosted-layers/master weblate-hosted-core/master ",
|
||||||
"housekeeping": "git pull && npx update-browserslist-db@latest && npm run weblate-fix-heavy && npm run generate && npm run generate:docs && npm run generate:contributor-list && vite-node scripts/fetchLanguages.ts && npm run format && git add assets/ langs/ Docs/ **/*.ts Docs/* src/* && git commit -m 'chore: automated housekeeping...'",
|
"housekeeping": "git pull && npx update-browserslist-db@latest && npm run weblate-fix-heavy && npm run generate && npm run generate:docs && npm run generate:contributor-list && vite-node scripts/fetchLanguages.ts && npm run format && git add assets/ langs/ Docs/ **/*.ts Docs/* src/* && git commit -m 'chore: automated housekeeping...'",
|
||||||
"reuse-compliance": "reuse lint",
|
"reuse-compliance": "reuse lint",
|
||||||
"backup:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/"
|
"backup:images": "vite-node scripts/generateImageAnalysis.ts -- ~/data/imgur-image-backup/",
|
||||||
|
"dloadVelopark": "vite-node scripts/velopark/veloParkToGeojson.ts ",
|
||||||
|
"scrapeWebsites": "vite-node scripts/importscripts/compareWebsiteData.ts -- ~/Downloads/ShopsWithWebsiteNodes.csv ~/data/scraped_websites/\n"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"OpenStreetMap",
|
"OpenStreetMap",
|
||||||
|
|
|
@ -148,7 +148,16 @@ export default class ScriptUtils {
|
||||||
const data = await ScriptUtils.Download(url, headers)
|
const data = await ScriptUtils.Download(url, headers)
|
||||||
return JSON.parse(data["content"])
|
return JSON.parse(data["content"])
|
||||||
}
|
}
|
||||||
|
public static async DownloadFetch(
|
||||||
|
url: string,
|
||||||
|
headers?: any
|
||||||
|
): Promise<{ content: string } | { redirect: string }> {
|
||||||
|
console.log("Fetching", url)
|
||||||
|
const req = await fetch(url, {headers})
|
||||||
|
const data= await req.text()
|
||||||
|
console.log("Fetched", url,data)
|
||||||
|
return {content: data}
|
||||||
|
}
|
||||||
public static Download(
|
public static Download(
|
||||||
url: string,
|
url: string,
|
||||||
headers?: any
|
headers?: any
|
||||||
|
|
80
scripts/importscripts/compareWebsiteData.ts
Normal file
80
scripts/importscripts/compareWebsiteData.ts
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import fs from "fs"
|
||||||
|
// import readline from "readline"
|
||||||
|
import Script from "../Script"
|
||||||
|
import LinkedDataLoader from "../../src/Logic/Web/LinkedDataLoader"
|
||||||
|
import UrlValidator from "../../src/UI/InputElement/Validators/UrlValidator"
|
||||||
|
// vite-node scripts/importscripts/compareWebsiteData.ts -- ~/Downloads/ShopsWithWebsiteNodes.csv ~/data/scraped_websites/
|
||||||
|
/*
|
||||||
|
class CompareWebsiteData extends Script {
|
||||||
|
constructor() {
|
||||||
|
super("Given a csv file with 'id', 'tags' and 'website', attempts to fetch jsonld and compares the attributes. Usage: csv-file datadir")
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly urlFormatter = new UrlValidator()
|
||||||
|
async getWithCache(cachedir : string, url: string): Promise<any>{
|
||||||
|
const filename= cachedir+"/"+encodeURIComponent(url)
|
||||||
|
if(fs.existsSync(filename)){
|
||||||
|
return JSON.parse(fs.readFileSync(filename, "utf-8"))
|
||||||
|
}
|
||||||
|
const jsonLd = await LinkedDataLoader.fetchJsonLdWithProxy(url)
|
||||||
|
console.log("Got:", jsonLd)
|
||||||
|
fs.writeFileSync(filename, JSON.stringify(jsonLd))
|
||||||
|
return jsonLd
|
||||||
|
}
|
||||||
|
async handleEntry(line: string, cachedir: string, targetfile: string) : Promise<boolean>{
|
||||||
|
const id = JSON.parse(line.split(",")[0])
|
||||||
|
let tags = line.substring(line.indexOf("{") - 1)
|
||||||
|
tags = tags.substring(1, tags.length - 1)
|
||||||
|
tags = tags.replace(/""/g, "\"")
|
||||||
|
const data = JSON.parse(tags)
|
||||||
|
|
||||||
|
const website = data.website //this.urlFormatter.reformat(data.website)
|
||||||
|
if(!website.startsWith("https://stores.delhaize.be")){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
console.log(website)
|
||||||
|
const jsonld = await this.getWithCache(cachedir, website)
|
||||||
|
console.log(jsonld)
|
||||||
|
if(Object.keys(jsonld).length === 0){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const diff = LinkedDataLoader.removeDuplicateData(jsonld, data)
|
||||||
|
fs.appendFileSync(targetfile, id +", "+ JSON.stringify(diff)+"\n")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async main(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
throw "Not enough arguments"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const readInterface = readline.createInterface({
|
||||||
|
input: fs.createReadStream(args[0]),
|
||||||
|
})
|
||||||
|
|
||||||
|
let handled = 0
|
||||||
|
let diffed = 0
|
||||||
|
const targetfile = "diff.csv"
|
||||||
|
fs.writeFileSync(targetfile, "id, diff-json\n")
|
||||||
|
for await (const line of readInterface) {
|
||||||
|
try {
|
||||||
|
if(line.startsWith("\"id\"")){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const madeComparison = await this.handleEntry(line, args[1], targetfile)
|
||||||
|
handled ++
|
||||||
|
diffed = diffed + (madeComparison ? 1 : 0)
|
||||||
|
if(handled % 1000 == 0){
|
||||||
|
// console.log("Handled ",handled," got ",diffed,"diff results")
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new CompareWebsiteData().run()
|
||||||
|
*/
|
0
scripts/scrapeOsm.ts
Normal file
0
scripts/scrapeOsm.ts
Normal file
|
@ -15,6 +15,7 @@ class ServerLdScrape extends Script {
|
||||||
mimetype: "application/ld+json",
|
mimetype: "application/ld+json",
|
||||||
async handle(content, searchParams: URLSearchParams) {
|
async handle(content, searchParams: URLSearchParams) {
|
||||||
const url = searchParams.get("url")
|
const url = searchParams.get("url")
|
||||||
|
console.log("Fetching", url)
|
||||||
if (cache[url]) {
|
if (cache[url]) {
|
||||||
return JSON.stringify(cache[url])
|
return JSON.stringify(cache[url])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,42 @@
|
||||||
import Script from "../Script"
|
import Script from "../Script"
|
||||||
import { Utils } from "../../src/Utils"
|
|
||||||
import VeloparkLoader, { VeloparkData } from "../../src/Logic/Web/VeloparkLoader"
|
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import { Overpass } from "../../src/Logic/Osm/Overpass"
|
import { Overpass } from "../../src/Logic/Osm/Overpass"
|
||||||
import { RegexTag } from "../../src/Logic/Tags/RegexTag"
|
import { RegexTag } from "../../src/Logic/Tags/RegexTag"
|
||||||
import Constants from "../../src/Models/Constants"
|
import Constants from "../../src/Models/Constants"
|
||||||
import { ImmutableStore } from "../../src/Logic/UIEventSource"
|
import { ImmutableStore } from "../../src/Logic/UIEventSource"
|
||||||
import { BBox } from "../../src/Logic/BBox"
|
import { BBox } from "../../src/Logic/BBox"
|
||||||
|
import LinkedDataLoader from "../../src/Logic/Web/LinkedDataLoader"
|
||||||
|
|
||||||
class VeloParkToGeojson extends Script {
|
class VeloParkToGeojson extends Script {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(
|
super(
|
||||||
"Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory"
|
"Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exportTo(filename: string, features) {
|
exportTo(filename: string, features) {
|
||||||
fs.writeFileSync(
|
features = features.slice(0,25) // TODO REMOVE
|
||||||
filename + "_" + new Date().toISOString() + ".geojson",
|
const file = filename + "_" + /*new Date().toISOString() + */".geojson"
|
||||||
|
fs.writeFileSync(file,
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
{
|
{
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
|
"#":"Only 25 features are shown!", // TODO REMOVE
|
||||||
features,
|
features,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" "
|
" ",
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
console.log("Written",file)
|
||||||
}
|
}
|
||||||
|
|
||||||
async main(args: string[]): Promise<void> {
|
async main(args: string[]): Promise<void> {
|
||||||
console.log("Downloading velopark data")
|
console.log("Downloading velopark data")
|
||||||
// Download data for NIS-code 1000. 1000 means: all of belgium
|
// Download data for NIS-code 1000. 1000 means: all of belgium
|
||||||
const url = "https://www.velopark.be/api/parkings/1000"
|
const url = "https://www.velopark.be/api/parkings/1000"
|
||||||
const data = <VeloparkData[]>await Utils.downloadJson(url)
|
const allVelopark = await LinkedDataLoader.fetchJsonLd(url, { country: "be" })
|
||||||
|
this.exportTo("velopark_all", allVelopark)
|
||||||
|
|
||||||
const bboxBelgium = new BBox([
|
const bboxBelgium = new BBox([
|
||||||
[2.51357303225, 49.5294835476],
|
[2.51357303225, 49.5294835476],
|
||||||
|
@ -44,15 +47,13 @@ class VeloParkToGeojson extends Script {
|
||||||
[],
|
[],
|
||||||
Constants.defaultOverpassUrls[0],
|
Constants.defaultOverpassUrls[0],
|
||||||
new ImmutableStore(60 * 5),
|
new ImmutableStore(60 * 5),
|
||||||
false
|
false,
|
||||||
)
|
)
|
||||||
const alreadyLinkedFeatures = await alreadyLinkedQuery.queryGeoJson(bboxBelgium)
|
const alreadyLinkedFeatures = await alreadyLinkedQuery.queryGeoJson(bboxBelgium)
|
||||||
const seenIds = new Set<string>(
|
const seenIds = new Set<string>(
|
||||||
alreadyLinkedFeatures[0].features.map((f) => f.properties["ref:velopark"])
|
alreadyLinkedFeatures[0].features.map((f) => f.properties["ref:velopark"]),
|
||||||
)
|
)
|
||||||
console.log("OpenStreetMap contains", seenIds.size, "bicycle parkings with a velopark ref")
|
console.log("OpenStreetMap contains", seenIds.size, "bicycle parkings with a velopark ref")
|
||||||
const allVelopark = data.map((f) => VeloparkLoader.convert(f))
|
|
||||||
this.exportTo("velopark_all", allVelopark)
|
|
||||||
|
|
||||||
const features = allVelopark.filter((f) => !seenIds.has(f.properties["ref:velopark"]))
|
const features = allVelopark.filter((f) => !seenIds.has(f.properties["ref:velopark"]))
|
||||||
|
|
||||||
|
|
|
@ -237,8 +237,11 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
|
|
||||||
public bindD<X>(f: (t: Exclude<T, undefined | null>) => Store<X>): Store<X> {
|
public bindD<X>(f: (t: Exclude<T, undefined | null>) => Store<X>): Store<X> {
|
||||||
return this.bind((t) => {
|
return this.bind((t) => {
|
||||||
if (t === undefined || t === null) {
|
if(t=== null){
|
||||||
return <undefined | null>t
|
return null
|
||||||
|
}
|
||||||
|
if (t === undefined ) {
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
return f(<Exclude<T, undefined | null>>t)
|
return f(<Exclude<T, undefined | null>>t)
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,11 @@ import PhoneValidator from "../../UI/InputElement/Validators/PhoneValidator"
|
||||||
import EmailValidator from "../../UI/InputElement/Validators/EmailValidator"
|
import EmailValidator from "../../UI/InputElement/Validators/EmailValidator"
|
||||||
import { Validator } from "../../UI/InputElement/Validator"
|
import { Validator } from "../../UI/InputElement/Validator"
|
||||||
import UrlValidator from "../../UI/InputElement/Validators/UrlValidator"
|
import UrlValidator from "../../UI/InputElement/Validators/UrlValidator"
|
||||||
|
import Constants from "../../Models/Constants"
|
||||||
|
|
||||||
|
interface JsonLdLoaderOptions {
|
||||||
|
country?: string
|
||||||
|
}
|
||||||
export default class LinkedDataLoader {
|
export default class LinkedDataLoader {
|
||||||
private static readonly COMPACTING_CONTEXT = {
|
private static readonly COMPACTING_CONTEXT = {
|
||||||
name: "http://schema.org/name",
|
name: "http://schema.org/name",
|
||||||
|
@ -43,6 +47,10 @@ export default class LinkedDataLoader {
|
||||||
"http://schema.org/contactPoint",
|
"http://schema.org/contactPoint",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
private static ignoreTypes = [
|
||||||
|
"Breadcrumblist"
|
||||||
|
]
|
||||||
|
|
||||||
static async geoToGeometry(geo): Promise<Geometry> {
|
static async geoToGeometry(geo): Promise<Geometry> {
|
||||||
const context = {
|
const context = {
|
||||||
lat: {
|
lat: {
|
||||||
|
@ -102,15 +110,30 @@ export default class LinkedDataLoader {
|
||||||
return OH.ToString(OH.MergeTimes(allRules))
|
return OH.ToString(OH.MergeTimes(allRules))
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fetchJsonLd(url: string, country?: string): Promise<Record<string, any>> {
|
static async fetchJsonLdWithProxy(url: string, options?: JsonLdLoaderOptions): Promise<any> {
|
||||||
const proxy = "http://127.0.0.1:2346/extractgraph" // "https://cache.mapcomplete.org/extractgraph"
|
const urlWithProxy = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
|
||||||
const data = await Utils.downloadJson(`${proxy}?url=${url}`)
|
return await this.fetchJsonLd(urlWithProxy, options)
|
||||||
const compacted = await jsonld.compact(data, LinkedDataLoader.COMPACTING_CONTEXT)
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "content": "{\"@context\":\"http://schema.org\",\"@type\":\"LocalBusiness\",\"@id\":\"http://stores.delhaize.be/nl/ad-delhaize-munsterbilzen\",\"name\":\"AD Delhaize Munsterbilzen\",\"url\":\"http://stores.delhaize.be/nl/ad-delhaize-munsterbilzen\",\"logo\":\"https://stores.delhaize.be/build/images/web/shop/delhaize-be/favicon.ico\",\"image\":\"http://stores.delhaize.be/image/mobilosoft-testing?apiPath=rehab/delhaize-be/images/location/ad%20delhaize%20image%20ge%CC%81ne%CC%81rale%20%281%29%201652787176865&imageSize=h_500\",\"email\":\"\",\"telephone\":\"+3289413520\",\"address\":{\"@type\":\"PostalAddress\",\"streetAddress\":\"Waterstraat, 18\",\"addressLocality\":\"Bilzen\",\"postalCode\":\"3740\",\"addressCountry\":\"BE\"},\"geo\":{\"@type\":\"GeoCoordinates\",\"latitude\":50.8906898,\"longitude\":5.5260586},\"openingHoursSpecification\":[{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Tuesday\",\"opens\":\"08:00\",\"closes\":\"18:30\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Wednesday\",\"opens\":\"08:00\",\"closes\":\"18:30\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Thursday\",\"opens\":\"08:00\",\"closes\":\"18:30\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Friday\",\"opens\":\"08:00\",\"closes\":\"18:30\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Saturday\",\"opens\":\"08:00\",\"closes\":\"18:30\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Sunday\",\"opens\":\"08:00\",\"closes\":\"12:00\"},{\"@type\":\"OpeningHoursSpecification\",\"dayOfWeek\":\"Monday\",\"opens\":\"12:00\",\"closes\":\"18:30\"}],\"@base\":\"https://stores.delhaize.be/nl/ad-delhaize-munsterbilzen\"}"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
private static async compact(data: any, options?: JsonLdLoaderOptions): Promise<any>{
|
||||||
|
console.log("Compacting",data)
|
||||||
|
if(Array.isArray(data)) {
|
||||||
|
return await Promise.all(data.map(d => LinkedDataLoader.compact(d)))
|
||||||
|
}
|
||||||
|
const country = options?.country
|
||||||
|
const compacted = await jsonld.compact(data, <any> LinkedDataLoader.COMPACTING_CONTEXT)
|
||||||
compacted["opening_hours"] = await LinkedDataLoader.ohToOsmFormat(
|
compacted["opening_hours"] = await LinkedDataLoader.ohToOsmFormat(
|
||||||
compacted["opening_hours"]
|
compacted["opening_hours"]
|
||||||
)
|
)
|
||||||
if (compacted["openingHours"]) {
|
if (compacted["openingHours"]) {
|
||||||
const ohspec: string[] = compacted["openingHours"]
|
const ohspec: string[] = <any> compacted["openingHours"]
|
||||||
compacted["opening_hours"] = OH.simplify(
|
compacted["opening_hours"] = OH.simplify(
|
||||||
ohspec.map((r) => LinkedDataLoader.ohStringToOsmFormat(r)).join("; ")
|
ohspec.map((r) => LinkedDataLoader.ohStringToOsmFormat(r)).join("; ")
|
||||||
)
|
)
|
||||||
|
@ -138,5 +161,39 @@ export default class LinkedDataLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <any>compacted
|
return <any>compacted
|
||||||
|
|
||||||
|
}
|
||||||
|
static async fetchJsonLd(url: string, options?: JsonLdLoaderOptions): Promise<any> {
|
||||||
|
const data = await Utils.downloadJson(url)
|
||||||
|
return await LinkedDataLoader.compact(data, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only returns different items
|
||||||
|
* @param externalData
|
||||||
|
* @param currentData
|
||||||
|
*/
|
||||||
|
static removeDuplicateData(externalData: Record<string, string>, currentData: Record<string, string>) : Record<string, string>{
|
||||||
|
const d = { ...externalData }
|
||||||
|
delete d["@context"]
|
||||||
|
for (const k in d) {
|
||||||
|
const v = currentData[k]
|
||||||
|
if (!v) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (k === "opening_hours") {
|
||||||
|
const oh = [].concat(...v.split(";").map(r => OH.ParseRule(r) ?? []))
|
||||||
|
const merged = OH.ToString(OH.MergeTimes(oh ?? []))
|
||||||
|
if (merged === d[k]) {
|
||||||
|
delete d[k]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (v === d[k]) {
|
||||||
|
delete d[k]
|
||||||
|
}
|
||||||
|
delete d.geo
|
||||||
|
}
|
||||||
|
return d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
{
|
{
|
||||||
theme: state.layout.id,
|
theme: state.layout.id,
|
||||||
changeType: "import",
|
changeType: "import",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
await state.changes.applyChanges(await change.CreateChangeDescriptions())
|
await state.changes.applyChanges(await change.CreateChangeDescriptions())
|
||||||
currentStep = "done"
|
currentStep = "done"
|
||||||
|
@ -43,6 +43,9 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>{key}</b></td>
|
<td><b>{key}</b></td>
|
||||||
|
|
||||||
|
{#if $tags[key]}
|
||||||
|
{$tags[key]}
|
||||||
|
{/if}
|
||||||
<td>
|
<td>
|
||||||
{#if externalProperties[key].startsWith("http")}
|
{#if externalProperties[key].startsWith("http")}
|
||||||
<a href={externalProperties[key]} target="_blank">
|
<a href={externalProperties[key]} target="_blank">
|
||||||
|
|
|
@ -26,23 +26,21 @@
|
||||||
let externalKeys: string[] = Object.keys(externalProperties).sort()
|
let externalKeys: string[] = Object.keys(externalProperties).sort()
|
||||||
|
|
||||||
const imageKeyRegex = /image|image:[0-9]+/
|
const imageKeyRegex = /image|image:[0-9]+/
|
||||||
console.log("Calculating knwon images")
|
|
||||||
let knownImages = new Set(
|
let knownImages = new Set(
|
||||||
Object.keys(osmProperties)
|
Object.keys(osmProperties)
|
||||||
.filter((k) => k.match(imageKeyRegex))
|
.filter((k) => k.match(imageKeyRegex))
|
||||||
.map((k) => osmProperties[k])
|
.map((k) => osmProperties[k])
|
||||||
)
|
)
|
||||||
console.log("Known images are:", knownImages)
|
|
||||||
let unknownImages = externalKeys
|
let unknownImages = externalKeys
|
||||||
.filter((k) => k.match(imageKeyRegex))
|
.filter((k) => k.match(imageKeyRegex))
|
||||||
.map((k) => externalProperties[k])
|
.map((k) => externalProperties[k])
|
||||||
.filter((i) => !knownImages.has(i))
|
.filter((i) => !knownImages.has(i))
|
||||||
|
|
||||||
let propertyKeysExternal = externalKeys.filter((k) => k.match(imageKeyRegex) === null)
|
let propertyKeysExternal = externalKeys.filter((k) => k.match(imageKeyRegex) === null)
|
||||||
let missing = propertyKeysExternal.filter((k) => osmProperties[k] === undefined)
|
let missing = propertyKeysExternal.filter((k) => osmProperties[k] === undefined && typeof externalProperties[k] === "string")
|
||||||
let same = propertyKeysExternal.filter((key) => osmProperties[key] === externalProperties[key])
|
let same = propertyKeysExternal.filter((key) => osmProperties[key] === externalProperties[key])
|
||||||
let different = propertyKeysExternal.filter(
|
let different = propertyKeysExternal.filter(
|
||||||
(key) => osmProperties[key] !== undefined && osmProperties[key] !== externalProperties[key]
|
(key) => osmProperties[key] !== undefined && osmProperties[key] !== externalProperties[key] && typeof externalProperties[key] === "string"
|
||||||
)
|
)
|
||||||
|
|
||||||
let currentStep: "init" | "applying_all" | "all_applied" = "init"
|
let currentStep: "init" | "applying_all" | "all_applied" = "init"
|
||||||
|
@ -68,11 +66,7 @@
|
||||||
<th>External</th>
|
<th>External</th>
|
||||||
</tr>
|
</tr>
|
||||||
{#each different as key}
|
{#each different as key}
|
||||||
<tr>
|
<ComparisonAction {key} {state} {tags} {externalProperties} {layer} {feature} {readonly} />
|
||||||
<td>{key}</td>
|
|
||||||
<td>{osmProperties[key]}</td>
|
|
||||||
<td>{externalProperties[key]}</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
{/each}
|
||||||
</table>
|
</table>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -3,63 +3,32 @@
|
||||||
* The comparison tool loads json-data from a speficied URL, eventually post-processes it
|
* The comparison tool loads json-data from a speficied URL, eventually post-processes it
|
||||||
* and compares it with the current object
|
* and compares it with the current object
|
||||||
*/
|
*/
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { Utils } from "../../Utils"
|
|
||||||
import VeloparkLoader from "../../Logic/Web/VeloparkLoader"
|
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import ComparisonTable from "./ComparisonTable.svelte"
|
import ComparisonTable from "./ComparisonTable.svelte"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson"
|
||||||
import type { OsmTags } from "../../Models/OsmFeature"
|
import type { OsmTags } from "../../Models/OsmFeature"
|
||||||
|
|
||||||
export let url: string
|
export let externalData: Store<{ success: {content: Record<string, string> } } | { error: string } | undefined | null /* null if no URL is found, undefined if loading*/>
|
||||||
export let postprocessVelopark: boolean
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let tags: UIEventSource<OsmTags>
|
export let tags: UIEventSource<OsmTags>
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
let data: any = undefined
|
|
||||||
let error: any = undefined
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const _url = tags.data[url]
|
|
||||||
if (!_url) {
|
|
||||||
error = "No URL found in attribute" + url
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
console.log("Attempting to download", _url)
|
|
||||||
const downloaded = await Utils.downloadJsonAdvanced(_url)
|
|
||||||
if (downloaded["error"]) {
|
|
||||||
console.error(downloaded)
|
|
||||||
error = downloaded["error"]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (postprocessVelopark) {
|
|
||||||
data = VeloparkLoader.convert(downloaded["content"])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data = downloaded["content"]
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
error = "" + e
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
{#if $externalData === null}
|
||||||
{#if error !== undefined}
|
<!-- empty block -->
|
||||||
|
{:else if $externalData === undefined}
|
||||||
|
<Loading>{$externalData}</Loading>
|
||||||
|
{:else if $externalData["error"] !== undefined}
|
||||||
<div class="alert">
|
<div class="alert">
|
||||||
Something went wrong: {error}
|
Something went wrong: {$externalData["error"]}
|
||||||
</div>
|
</div>
|
||||||
{:else if data === undefined}
|
{:else if $externalData["success"] !== undefined}
|
||||||
<Loading>
|
|
||||||
Loading {$tags[url]}
|
|
||||||
</Loading>
|
|
||||||
{:else if data.properties !== undefined}
|
|
||||||
<ComparisonTable
|
<ComparisonTable
|
||||||
externalProperties={data.properties}
|
externalProperties={$externalData["success"]}
|
||||||
osmProperties={$tags}
|
osmProperties={$tags}
|
||||||
{state}
|
{state}
|
||||||
{feature}
|
{feature}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
|
||||||
export let image: ProvidedImage
|
export let image: Partial<ProvidedImage>
|
||||||
let fallbackImage: string = undefined
|
let fallbackImage: string = undefined
|
||||||
if (image.provider === Mapillary.singleton) {
|
if (image.provider === Mapillary.singleton) {
|
||||||
fallbackImage = "./assets/svg/blocked.svg"
|
fallbackImage = "./assets/svg/blocked.svg"
|
||||||
|
|
|
@ -41,6 +41,11 @@ export default class UrlValidator extends Validator {
|
||||||
"AdGroup",
|
"AdGroup",
|
||||||
"TargetId",
|
"TargetId",
|
||||||
"msclkid",
|
"msclkid",
|
||||||
|
"pk_source",
|
||||||
|
"pk_medium",
|
||||||
|
"pk_campaign",
|
||||||
|
"pk_content",
|
||||||
|
"pk_kwd"
|
||||||
]
|
]
|
||||||
for (const dontLike of blacklistedTrackingParams) {
|
for (const dontLike of blacklistedTrackingParams) {
|
||||||
url.searchParams.delete(dontLike.toLowerCase())
|
url.searchParams.delete(dontLike.toLowerCase())
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
/**
|
|
||||||
* Shows attributes that are loaded via linked data and which are suitable for import
|
|
||||||
*/
|
|
||||||
import type { SpecialVisualizationState } from "./SpecialVisualization"
|
|
||||||
import type { Store } from "../Logic/UIEventSource"
|
|
||||||
import { Stores, UIEventSource } from "../Logic/UIEventSource"
|
|
||||||
|
|
||||||
import type { Feature, Geometry } from "geojson"
|
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
|
||||||
import LinkedDataLoader from "../Logic/Web/LinkedDataLoader"
|
|
||||||
import Loading from "./Base/Loading.svelte"
|
|
||||||
import { GeoOperations } from "../Logic/GeoOperations"
|
|
||||||
import { OH } from "./OpeningHours/OpeningHours"
|
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
|
||||||
export let tagsSource: UIEventSource<Record<string, string>>
|
|
||||||
export let argument: string[]
|
|
||||||
export let feature: Feature
|
|
||||||
export let layer: LayerConfig
|
|
||||||
export let key: string
|
|
||||||
|
|
||||||
|
|
||||||
let url = tagsSource.mapD(tags => {
|
|
||||||
if (!tags._country || !tags[key] || tags[key] === "undefined") {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
return ({ url: tags[key], country: tags._country })
|
|
||||||
})
|
|
||||||
let dataWithErr = url.bindD(({ url, country }) => {
|
|
||||||
return Stores.FromPromiseWithErr(LinkedDataLoader.fetchJsonLd(url, country))
|
|
||||||
})
|
|
||||||
|
|
||||||
let error = dataWithErr.mapD(d => d["error"])
|
|
||||||
let data = dataWithErr.mapD(d => d["success"])
|
|
||||||
|
|
||||||
let distanceToFeature: Store<string> = data.mapD(d => d.geo).mapD(geo => {
|
|
||||||
const dist = Math.round(GeoOperations.distanceBetween(
|
|
||||||
GeoOperations.centerpointCoordinates(<Geometry>geo), GeoOperations.centerpointCoordinates(feature)))
|
|
||||||
return dist + "m"
|
|
||||||
})
|
|
||||||
let dataCleaned = data.mapD(d => {
|
|
||||||
const featureTags = tagsSource.data
|
|
||||||
console.log("Downloaded data is", d)
|
|
||||||
d = { ...d }
|
|
||||||
delete d["@context"]
|
|
||||||
for (const k in d) {
|
|
||||||
const v = featureTags[k]
|
|
||||||
if (!v) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (k === "opening_hours") {
|
|
||||||
const oh = [].concat(...v.split(";").map(r => OH.ParseRule(r) ?? []))
|
|
||||||
const merged = OH.ToString(OH.MergeTimes(oh ?? []))
|
|
||||||
if (merged === d[k]) {
|
|
||||||
delete d[k]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (featureTags[k] === d[k]) {
|
|
||||||
delete d[k]
|
|
||||||
}
|
|
||||||
delete d.geo
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}, [tagsSource])
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{#if $error}
|
|
||||||
<div class="alert">
|
|
||||||
{$error}
|
|
||||||
</div>
|
|
||||||
{:else if $url}
|
|
||||||
<div class="flex flex-col border border-dashed border-gray-500 p-1">
|
|
||||||
{#if $dataCleaned !== undefined && Object.keys($dataCleaned).length === 0}
|
|
||||||
No new data from website
|
|
||||||
{:else if !$data}
|
|
||||||
<Loading />
|
|
||||||
{:else}
|
|
||||||
{$distanceToFeature}
|
|
||||||
<ul>
|
|
||||||
{#each Object.keys($dataCleaned) as k}
|
|
||||||
<li>
|
|
||||||
<b>{k}</b>: {JSON.stringify($dataCleaned[k])} {$tagsSource[k]} {($dataCleaned[k]) === $tagsSource[k]}
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
|
@ -459,7 +459,7 @@ class LineRenderingLayer {
|
||||||
} else {
|
} else {
|
||||||
const tags = this._fetchStore(id)
|
const tags = this._fetchStore(id)
|
||||||
this._listenerInstalledOn.add(id)
|
this._listenerInstalledOn.add(id)
|
||||||
tags.addCallbackAndRunD((properties) => {
|
tags?.addCallbackAndRunD((properties) => {
|
||||||
// Make sure to use 'getSource' here, the layer names are different!
|
// Make sure to use 'getSource' here, the layer names are different!
|
||||||
try {
|
try {
|
||||||
if (map.getSource(this._layername) === undefined) {
|
if (map.getSource(this._layername) === undefined) {
|
||||||
|
|
|
@ -3,11 +3,7 @@ import { FixedUiElement } from "./Base/FixedUiElement"
|
||||||
import BaseUIElement from "./BaseUIElement"
|
import BaseUIElement from "./BaseUIElement"
|
||||||
import Title from "./Base/Title"
|
import Title from "./Base/Title"
|
||||||
import Table from "./Base/Table"
|
import Table from "./Base/Table"
|
||||||
import {
|
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
|
||||||
RenderingSpecification,
|
|
||||||
SpecialVisualization,
|
|
||||||
SpecialVisualizationState,
|
|
||||||
} from "./SpecialVisualization"
|
|
||||||
import { HistogramViz } from "./Popup/HistogramViz"
|
import { HistogramViz } from "./Popup/HistogramViz"
|
||||||
import { MinimapViz } from "./Popup/MinimapViz"
|
import { MinimapViz } from "./Popup/MinimapViz"
|
||||||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
||||||
|
@ -93,7 +89,7 @@ import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
|
||||||
import LoginButton from "./Base/LoginButton.svelte"
|
import LoginButton from "./Base/LoginButton.svelte"
|
||||||
import Toggle from "./Input/Toggle"
|
import Toggle from "./Input/Toggle"
|
||||||
import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte"
|
import ImportReviewIdentity from "./Reviews/ImportReviewIdentity.svelte"
|
||||||
import LinkedDataDisplay from "./LinkedDataDisplay.svelte"
|
import LinkedDataLoader from "../Logic/Web/LinkedDataLoader"
|
||||||
|
|
||||||
class NearbyImageVis implements SpecialVisualization {
|
class NearbyImageVis implements SpecialVisualization {
|
||||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||||
|
@ -120,7 +116,7 @@ class NearbyImageVis implements SpecialVisualization {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const isOpen = args[0] === "open"
|
const isOpen = args[0] === "open"
|
||||||
const readonly = args[1] === "readonly"
|
const readonly = args[1] === "readonly"
|
||||||
|
@ -187,7 +183,7 @@ class StealViz implements SpecialVisualization {
|
||||||
selectedElement: otherFeature,
|
selectedElement: otherFeature,
|
||||||
state,
|
state,
|
||||||
layer,
|
layer,
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (elements.length === 1) {
|
if (elements.length === 1) {
|
||||||
|
@ -195,8 +191,8 @@ class StealViz implements SpecialVisualization {
|
||||||
}
|
}
|
||||||
return new Combine(elements).SetClass("flex flex-col")
|
return new Combine(elements).SetClass("flex flex-col")
|
||||||
},
|
},
|
||||||
[state.indexedFeatures.featuresById]
|
[state.indexedFeatures.featuresById],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +231,7 @@ export class QuestionViz implements SpecialVisualization {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const labels = args[0]
|
const labels = args[0]
|
||||||
?.split(";")
|
?.split(";")
|
||||||
|
@ -278,7 +274,7 @@ export default class SpecialVisualizations {
|
||||||
defaultArg = "_empty string_"
|
defaultArg = "_empty string_"
|
||||||
}
|
}
|
||||||
return [arg.name, defaultArg, arg.doc]
|
return [arg.name, defaultArg, arg.doc]
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
new Title("Example usage of " + viz.funcName, 4),
|
new Title("Example usage of " + viz.funcName, 4),
|
||||||
|
@ -288,21 +284,21 @@ export default class SpecialVisualizations {
|
||||||
viz.funcName +
|
viz.funcName +
|
||||||
"(" +
|
"(" +
|
||||||
viz.args.map((arg) => arg.defaultValue).join(",") +
|
viz.args.map((arg) => arg.defaultValue).join(",") +
|
||||||
")}`"
|
")}`",
|
||||||
).SetClass("literal-code"),
|
).SetClass("literal-code"),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
public static constructSpecification(
|
public static constructSpecification(
|
||||||
template: string,
|
template: string,
|
||||||
extraMappings: SpecialVisualization[] = []
|
extraMappings: SpecialVisualization[] = [],
|
||||||
): RenderingSpecification[] {
|
): RenderingSpecification[] {
|
||||||
return SpecialVisualisationUtils.constructSpecification(template, extraMappings)
|
return SpecialVisualisationUtils.constructSpecification(template, extraMappings)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HelpMessage() {
|
public static HelpMessage() {
|
||||||
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
const helpTexts = SpecialVisualizations.specialVisualizations.map((viz) =>
|
||||||
SpecialVisualizations.DocumentationFor(viz)
|
SpecialVisualizations.DocumentationFor(viz),
|
||||||
)
|
)
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
|
@ -336,10 +332,10 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" "
|
" ",
|
||||||
)
|
),
|
||||||
).SetClass("code"),
|
).SetClass("code"),
|
||||||
'In other words: use `{ "before": ..., "after": ..., "special": {"type": ..., "argname": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)',
|
"In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)",
|
||||||
]).SetClass("flex flex-col"),
|
]).SetClass("flex flex-col"),
|
||||||
...helpTexts,
|
...helpTexts,
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col")
|
||||||
|
@ -348,7 +344,7 @@ export default class SpecialVisualizations {
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
public static renderExampleOfSpecial(
|
public static renderExampleOfSpecial(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
s: SpecialVisualization
|
s: SpecialVisualization,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const examples =
|
const examples =
|
||||||
s.structuredExamples === undefined
|
s.structuredExamples === undefined
|
||||||
|
@ -359,7 +355,7 @@ export default class SpecialVisualizations {
|
||||||
new UIEventSource<Record<string, string>>(e.feature.properties),
|
new UIEventSource<Record<string, string>>(e.feature.properties),
|
||||||
e.args,
|
e.args,
|
||||||
e.feature,
|
e.feature,
|
||||||
undefined
|
undefined,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
return new Combine([new Title(s.funcName), s.docs, ...examples])
|
||||||
|
@ -401,7 +397,7 @@ export default class SpecialVisualizations {
|
||||||
assignTo: state.userRelatedState.language,
|
assignTo: state.userRelatedState.language,
|
||||||
availableLanguages: state.layout.language,
|
availableLanguages: state.layout.language,
|
||||||
preferredLanguages: state.osmConnection.userDetails.map(
|
preferredLanguages: state.osmConnection.userDetails.map(
|
||||||
(ud) => ud.languages
|
(ud) => ud.languages,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -426,7 +422,7 @@ export default class SpecialVisualizations {
|
||||||
|
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource
|
tagSource
|
||||||
|
@ -436,7 +432,7 @@ export default class SpecialVisualizations {
|
||||||
return new SplitRoadWizard(<WayId>id, state)
|
return new SplitRoadWizard(<WayId>id, state)
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -450,7 +446,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
if (feature.geometry.type !== "Point") {
|
if (feature.geometry.type !== "Point") {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -473,7 +469,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
if (!layer.deletion) {
|
if (!layer.deletion) {
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -501,7 +497,7 @@ export default class SpecialVisualizations {
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature
|
feature: Feature,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
|
||||||
return new SvelteUIElement(CreateNewNote, {
|
return new SvelteUIElement(CreateNewNote, {
|
||||||
|
@ -565,7 +561,7 @@ export default class SpecialVisualizations {
|
||||||
.map((tags) => tags[args[0]])
|
.map((tags) => tags[args[0]])
|
||||||
.map((wikidata) => {
|
.map((wikidata) => {
|
||||||
wikidata = Utils.NoEmpty(
|
wikidata = Utils.NoEmpty(
|
||||||
wikidata?.split(";")?.map((wd) => wd.trim()) ?? []
|
wikidata?.split(";")?.map((wd) => wd.trim()) ?? [],
|
||||||
)[0]
|
)[0]
|
||||||
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
const entry = Wikidata.LoadWikidataEntry(wikidata)
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -575,9 +571,9 @@ export default class SpecialVisualizations {
|
||||||
}
|
}
|
||||||
const response = <WikidataResponse>e["success"]
|
const response = <WikidataResponse>e["success"]
|
||||||
return Translation.fromMap(response.labels)
|
return Translation.fromMap(response.labels)
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
new MapillaryLinkVis(),
|
new MapillaryLinkVis(),
|
||||||
|
@ -591,7 +587,7 @@ export default class SpecialVisualizations {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
_,
|
_,
|
||||||
__,
|
__,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
) => new SvelteUIElement(AllTagsPanel, { tags, layer }),
|
) => new SvelteUIElement(AllTagsPanel, { tags, layer }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -613,7 +609,7 @@ export default class SpecialVisualizations {
|
||||||
return new ImageCarousel(
|
return new ImageCarousel(
|
||||||
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
AllImageProviders.LoadImagesFor(tags, imagePrefixes),
|
||||||
tags,
|
tags,
|
||||||
state
|
state,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -669,7 +665,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(StarsBarIcon, {
|
return new SvelteUIElement(StarsBarIcon, {
|
||||||
score: reviews.average,
|
score: reviews.average,
|
||||||
|
@ -702,7 +698,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
return new SvelteUIElement(ReviewForm, { reviews, state, tags, feature, layer })
|
||||||
},
|
},
|
||||||
|
@ -734,7 +730,7 @@ export default class SpecialVisualizations {
|
||||||
{
|
{
|
||||||
nameKey: nameKey,
|
nameKey: nameKey,
|
||||||
fallbackName,
|
fallbackName,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
return new SvelteUIElement(AllReviews, { reviews, state, tags, feature, layer })
|
||||||
},
|
},
|
||||||
|
@ -754,7 +750,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const [text] = argument
|
const [text] = argument
|
||||||
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
return new SvelteUIElement(ImportReviewIdentity, { state, text })
|
||||||
|
@ -813,7 +809,7 @@ export default class SpecialVisualizations {
|
||||||
tags: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): SvelteUIElement {
|
): SvelteUIElement {
|
||||||
const keyToUse = args[0]
|
const keyToUse = args[0]
|
||||||
const prefix = args[1]
|
const prefix = args[1]
|
||||||
|
@ -850,17 +846,17 @@ export default class SpecialVisualizations {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const allUnits: Unit[] = [].concat(
|
const allUnits: Unit[] = [].concat(
|
||||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? []),
|
||||||
)
|
)
|
||||||
const unit = allUnits.filter((unit) =>
|
const unit = allUnits.filter((unit) =>
|
||||||
unit.isApplicableToKey(key)
|
unit.isApplicableToKey(key),
|
||||||
)[0]
|
)[0]
|
||||||
if (unit === undefined) {
|
if (unit === undefined) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
const getCountry = () => tagSource.data._country
|
const getCountry = () => tagSource.data._country
|
||||||
return unit.asHumanLongValue(value, getCountry)
|
return unit.asHumanLongValue(value, getCountry)
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -877,7 +873,7 @@ export default class SpecialVisualizations {
|
||||||
new Combine([
|
new Combine([
|
||||||
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
|
||||||
t.downloadGeoJsonHelper.SetClass("subtle"),
|
t.downloadGeoJsonHelper.SetClass("subtle"),
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col"),
|
||||||
)
|
)
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
console.log("Exporting as Geojson")
|
console.log("Exporting as Geojson")
|
||||||
|
@ -890,7 +886,7 @@ export default class SpecialVisualizations {
|
||||||
title + "_mapcomplete_export.geojson",
|
title + "_mapcomplete_export.geojson",
|
||||||
{
|
{
|
||||||
mimetype: "application/vnd.geo+json",
|
mimetype: "application/vnd.geo+json",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.SetClass("w-full")
|
.SetClass("w-full")
|
||||||
|
@ -926,7 +922,7 @@ export default class SpecialVisualizations {
|
||||||
constr: (state) => {
|
constr: (state) => {
|
||||||
return new SubtleButton(
|
return new SubtleButton(
|
||||||
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
Svg.delete_icon_svg().SetStyle("height: 1.5rem"),
|
||||||
Translations.t.general.removeLocationHistory
|
Translations.t.general.removeLocationHistory,
|
||||||
).onClick(() => {
|
).onClick(() => {
|
||||||
state.historicalUserLocations.features.setData([])
|
state.historicalUserLocations.features.setData([])
|
||||||
state.selectedElement.setData(undefined)
|
state.selectedElement.setData(undefined)
|
||||||
|
@ -964,10 +960,10 @@ export default class SpecialVisualizations {
|
||||||
.filter((c) => c.text !== "")
|
.filter((c) => c.text !== "")
|
||||||
.map(
|
.map(
|
||||||
(c, i) =>
|
(c, i) =>
|
||||||
new NoteCommentElement(c, state, i, comments.length)
|
new NoteCommentElement(c, state, i, comments.length),
|
||||||
)
|
),
|
||||||
).SetClass("flex flex-col")
|
).SetClass("flex flex-col")
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1001,7 +997,7 @@ export default class SpecialVisualizations {
|
||||||
tagsSource: UIEventSource<Record<string, string>>,
|
tagsSource: UIEventSource<Record<string, string>>,
|
||||||
_: string[],
|
_: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
) =>
|
) =>
|
||||||
new VariableUiElement(
|
new VariableUiElement(
|
||||||
tagsSource.map((tags) => {
|
tagsSource.map((tags) => {
|
||||||
|
@ -1019,7 +1015,7 @@ export default class SpecialVisualizations {
|
||||||
feature,
|
feature,
|
||||||
layer,
|
layer,
|
||||||
}).SetClass("px-1")
|
}).SetClass("px-1")
|
||||||
})
|
}),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1035,8 +1031,8 @@ export default class SpecialVisualizations {
|
||||||
let challenge = Stores.FromPromise(
|
let challenge = Stores.FromPromise(
|
||||||
Utils.downloadJsonCached(
|
Utils.downloadJsonCached(
|
||||||
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
`${Maproulette.defaultEndpoint}/challenge/${parentId}`,
|
||||||
24 * 60 * 60 * 1000
|
24 * 60 * 60 * 1000,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1061,7 +1057,7 @@ export default class SpecialVisualizations {
|
||||||
} else {
|
} else {
|
||||||
return [title, new List(listItems)]
|
return [title, new List(listItems)]
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.",
|
docs: "Fetches the metadata of MapRoulette campaign that this task is part of and shows those details (namely `title`, `description` and `instruction`).\n\nThis reads the property `mr_challengeId` to detect the parent campaign.",
|
||||||
|
@ -1075,15 +1071,15 @@ export default class SpecialVisualizations {
|
||||||
"\n" +
|
"\n" +
|
||||||
"```json\n" +
|
"```json\n" +
|
||||||
"{\n" +
|
"{\n" +
|
||||||
' "id": "mark_duplicate",\n' +
|
" \"id\": \"mark_duplicate\",\n" +
|
||||||
' "render": {\n' +
|
" \"render\": {\n" +
|
||||||
' "special": {\n' +
|
" \"special\": {\n" +
|
||||||
' "type": "maproulette_set_status",\n' +
|
" \"type\": \"maproulette_set_status\",\n" +
|
||||||
' "message": {\n' +
|
" \"message\": {\n" +
|
||||||
' "en": "Mark as not found or false positive"\n' +
|
" \"en\": \"Mark as not found or false positive\"\n" +
|
||||||
" },\n" +
|
" },\n" +
|
||||||
' "status": "2",\n' +
|
" \"status\": \"2\",\n" +
|
||||||
' "image": "close"\n' +
|
" \"image\": \"close\"\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
" }\n" +
|
" }\n" +
|
||||||
"}\n" +
|
"}\n" +
|
||||||
|
@ -1163,8 +1159,8 @@ export default class SpecialVisualizations {
|
||||||
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
const fsBboxed = new BBoxFeatureSourceForLayer(fs, bbox)
|
||||||
return new StatisticsPanel(fsBboxed)
|
return new StatisticsPanel(fsBboxed)
|
||||||
},
|
},
|
||||||
[state.mapProperties.bounds]
|
[state.mapProperties.bounds],
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1230,7 +1226,7 @@ export default class SpecialVisualizations {
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[]
|
args: string[],
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
let [text, href, classnames, download, ariaLabel] = args
|
let [text, href, classnames, download, ariaLabel] = args
|
||||||
if (download === "") {
|
if (download === "") {
|
||||||
|
@ -1244,14 +1240,14 @@ export default class SpecialVisualizations {
|
||||||
text: Utils.SubstituteKeys(text, tags),
|
text: Utils.SubstituteKeys(text, tags),
|
||||||
href: Utils.SubstituteKeys(href, tags).replaceAll(
|
href: Utils.SubstituteKeys(href, tags).replaceAll(
|
||||||
/ /g,
|
/ /g,
|
||||||
"%20"
|
"%20",
|
||||||
) /* Chromium based browsers eat the spaces */,
|
) /* Chromium based browsers eat the spaces */,
|
||||||
classnames,
|
classnames,
|
||||||
download: Utils.SubstituteKeys(download, tags),
|
download: Utils.SubstituteKeys(download, tags),
|
||||||
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
ariaLabel: Utils.SubstituteKeys(ariaLabel, tags),
|
||||||
newTab,
|
newTab,
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1273,7 +1269,7 @@ export default class SpecialVisualizations {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
" "
|
" ",
|
||||||
) +
|
) +
|
||||||
"\n```",
|
"\n```",
|
||||||
args: [
|
args: [
|
||||||
|
@ -1297,7 +1293,7 @@ export default class SpecialVisualizations {
|
||||||
featureTags: UIEventSource<Record<string, string>>,
|
featureTags: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
) {
|
) {
|
||||||
const [key, tr, classesRaw] = args
|
const [key, tr, classesRaw] = args
|
||||||
let classes = classesRaw ?? ""
|
let classes = classesRaw ?? ""
|
||||||
|
@ -1322,7 +1318,7 @@ export default class SpecialVisualizations {
|
||||||
elements.push(subsTr)
|
elements.push(subsTr)
|
||||||
}
|
}
|
||||||
return elements
|
return elements
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1342,7 +1338,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource.map((tags) => {
|
tagSource.map((tags) => {
|
||||||
|
@ -1354,7 +1350,7 @@ export default class SpecialVisualizations {
|
||||||
console.error("Cannot create a translation for", v, "due to", e)
|
console.error("Cannot create a translation for", v, "due to", e)
|
||||||
return JSON.stringify(v)
|
return JSON.stringify(v)
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1374,7 +1370,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0]
|
const key = argument[0]
|
||||||
const validator = new FediverseValidator()
|
const validator = new FediverseValidator()
|
||||||
|
@ -1384,7 +1380,7 @@ export default class SpecialVisualizations {
|
||||||
.map((fediAccount) => {
|
.map((fediAccount) => {
|
||||||
fediAccount = validator.reformat(fediAccount)
|
fediAccount = validator.reformat(fediAccount)
|
||||||
const [_, username, host] = fediAccount.match(
|
const [_, username, host] = fediAccount.match(
|
||||||
FediverseValidator.usernameAtServer
|
FediverseValidator.usernameAtServer,
|
||||||
)
|
)
|
||||||
|
|
||||||
const normalLink = new SvelteUIElement(Link, {
|
const normalLink = new SvelteUIElement(Link, {
|
||||||
|
@ -1399,7 +1395,7 @@ export default class SpecialVisualizations {
|
||||||
]
|
]
|
||||||
console.log(
|
console.log(
|
||||||
"LoggedinContributorMastodon",
|
"LoggedinContributorMastodon",
|
||||||
loggedInContributorMastodon
|
loggedInContributorMastodon,
|
||||||
)
|
)
|
||||||
if (!loggedInContributorMastodon) {
|
if (!loggedInContributorMastodon) {
|
||||||
return normalLink
|
return normalLink
|
||||||
|
@ -1415,7 +1411,7 @@ export default class SpecialVisualizations {
|
||||||
newTab: true,
|
newTab: true,
|
||||||
}).SetClass("button"),
|
}).SetClass("button"),
|
||||||
])
|
])
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1435,7 +1431,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new FixedUiElement("{" + args[0] + "}")
|
return new FixedUiElement("{" + args[0] + "}")
|
||||||
},
|
},
|
||||||
|
@ -1456,7 +1452,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0] ?? "value"
|
const key = argument[0] ?? "value"
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1476,10 +1472,10 @@ export default class SpecialVisualizations {
|
||||||
"Could not parse this tag: " +
|
"Could not parse this tag: " +
|
||||||
JSON.stringify(value) +
|
JSON.stringify(value) +
|
||||||
" due to " +
|
" due to " +
|
||||||
e
|
e,
|
||||||
).SetClass("alert")
|
).SetClass("alert")
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1500,7 +1496,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const giggityUrl = argument[0]
|
const giggityUrl = argument[0]
|
||||||
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
return new SvelteUIElement(Giggity, { tags: tagSource, state, giggityUrl })
|
||||||
|
@ -1516,12 +1512,12 @@ export default class SpecialVisualizations {
|
||||||
_: UIEventSource<Record<string, string>>,
|
_: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const tags = (<ThemeViewState>(
|
const tags = (<ThemeViewState>(
|
||||||
state
|
state
|
||||||
)).geolocation.currentUserLocation.features.map(
|
)).geolocation.currentUserLocation.features.map(
|
||||||
(features) => features[0]?.properties
|
(features) => features[0]?.properties,
|
||||||
)
|
)
|
||||||
return new Combine([
|
return new Combine([
|
||||||
new SvelteUIElement(OrientationDebugPanel, {}),
|
new SvelteUIElement(OrientationDebugPanel, {}),
|
||||||
|
@ -1543,7 +1539,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(MarkAsFavourite, {
|
return new SvelteUIElement(MarkAsFavourite, {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
|
@ -1563,7 +1559,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(MarkAsFavouriteMini, {
|
return new SvelteUIElement(MarkAsFavouriteMini, {
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
|
@ -1583,7 +1579,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
return new SvelteUIElement(DirectionIndicator, { state, feature })
|
||||||
},
|
},
|
||||||
|
@ -1598,7 +1594,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
tagSource
|
tagSource
|
||||||
|
@ -1620,9 +1616,9 @@ export default class SpecialVisualizations {
|
||||||
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
`${window.location.protocol}//${window.location.host}${window.location.pathname}?${layout}lat=${lat}&lon=${lon}&z=15` +
|
||||||
`#${id}`
|
`#${id}`
|
||||||
return new Img(new Qr(url).toImageElement(75)).SetStyle(
|
return new Img(new Qr(url).toImageElement(75)).SetStyle(
|
||||||
"width: 75px"
|
"width: 75px",
|
||||||
)
|
)
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1642,7 +1638,7 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
const key = args[0] === "" ? "_direction:centerpoint" : args[0]
|
||||||
return new VariableUiElement(
|
return new VariableUiElement(
|
||||||
|
@ -1653,11 +1649,11 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
.mapD((value) => {
|
.mapD((value) => {
|
||||||
const dir = GeoOperations.bearingToHuman(
|
const dir = GeoOperations.bearingToHuman(
|
||||||
GeoOperations.parseBearing(value)
|
GeoOperations.parseBearing(value),
|
||||||
)
|
)
|
||||||
console.log("Human dir", dir)
|
console.log("Human dir", dir)
|
||||||
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
return Translations.t.general.visualFeedback.directionsAbsolute[dir]
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1675,11 +1671,6 @@ export default class SpecialVisualizations {
|
||||||
required: true,
|
required: true,
|
||||||
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. ",
|
doc: "The domain name(s) where data might be fetched from - this is needed to set the CSP. A domain must include 'https', e.g. 'https://example.com'. For multiple domains, separate them with ';'. If you don't know the possible domains, use '*'. ",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "postprocessing",
|
|
||||||
required: false,
|
|
||||||
doc: "Apply some postprocessing. Currently, only 'velopark' is allowed as value",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "readonly",
|
name: "readonly",
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -1692,19 +1683,19 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const url = args[0]
|
const url = args[0]
|
||||||
const postprocessVelopark = args[2] === "velopark"
|
|
||||||
const readonly = args[3] === "yes"
|
const readonly = args[3] === "yes"
|
||||||
|
const externalData = Stores.FromPromiseWithErr(Utils.downloadJson(url))
|
||||||
return new SvelteUIElement(ComparisonTool, {
|
return new SvelteUIElement(ComparisonTool, {
|
||||||
url,
|
url,
|
||||||
postprocessVelopark,
|
|
||||||
state,
|
state,
|
||||||
tags: tagSource,
|
tags: tagSource,
|
||||||
layer,
|
layer,
|
||||||
feature,
|
feature,
|
||||||
readonly,
|
readonly,
|
||||||
|
externalData,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1718,12 +1709,12 @@ export default class SpecialVisualizations {
|
||||||
tagSource: UIEventSource<Record<string, string>>,
|
tagSource: UIEventSource<Record<string, string>>,
|
||||||
args: string[],
|
args: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
return new Toggle(
|
return new Toggle(
|
||||||
undefined,
|
undefined,
|
||||||
new SvelteUIElement(LoginButton),
|
new SvelteUIElement(LoginButton),
|
||||||
state.osmConnection.isLoggedIn
|
state.osmConnection.isLoggedIn,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1740,19 +1731,36 @@ export default class SpecialVisualizations {
|
||||||
needsUrls: [Constants.linkedDataProxy],
|
needsUrls: [Constants.linkedDataProxy],
|
||||||
constr(
|
constr(
|
||||||
state: SpecialVisualizationState,
|
state: SpecialVisualizationState,
|
||||||
tagsSource: UIEventSource<Record<string, string>>,
|
tags: UIEventSource<Record<string, string>>,
|
||||||
argument: string[],
|
argument: string[],
|
||||||
feature: Feature,
|
feature: Feature,
|
||||||
layer: LayerConfig
|
layer: LayerConfig,
|
||||||
): BaseUIElement {
|
): BaseUIElement {
|
||||||
const key = argument[0] ?? "website"
|
const key = argument[0] ?? "website"
|
||||||
return new SvelteUIElement(LinkedDataDisplay, {
|
let url = tags.mapD(tags => {
|
||||||
|
if (!tags._country || !tags[key] || tags[key] === "undefined") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return ({ url: tags[key], country: tags._country })
|
||||||
|
})
|
||||||
|
const externalData: Store<{ success: { content: any } } | {
|
||||||
|
error: string
|
||||||
|
} | undefined | null> = url.bindD(({
|
||||||
|
url,
|
||||||
|
country,
|
||||||
|
}) => Stores.FromPromiseWithErr(LinkedDataLoader.fetchJsonLdWithProxy(url, { country })))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return new Toggle(
|
||||||
|
new SvelteUIElement(ComparisonTool, {
|
||||||
feature,
|
feature,
|
||||||
state,
|
state,
|
||||||
tagsSource,
|
tags,
|
||||||
key,
|
|
||||||
layer,
|
layer,
|
||||||
})
|
externalData,
|
||||||
|
}), undefined, url.map(url => !!url),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -1766,7 +1774,7 @@ export default class SpecialVisualizations {
|
||||||
throw (
|
throw (
|
||||||
"Invalid special visualisation found: funcName is undefined for " +
|
"Invalid special visualisation found: funcName is undefined for " +
|
||||||
invalid.map((sp) => sp.i).join(", ") +
|
invalid.map((sp) => sp.i).join(", ") +
|
||||||
'. Did you perhaps type \n funcName: "funcname" // type declaration uses COLON\ninstead of:\n funcName = "funcName" // value definition uses EQUAL'
|
". Did you perhaps type \n funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n funcName = \"funcName\" // value definition uses EQUAL"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue