forked from MapComplete/MapComplete
More tweaks to the linked data loader
This commit is contained in:
parent
684932aebd
commit
734be4a702
5 changed files with 76 additions and 55 deletions
|
@ -2,14 +2,14 @@ import Script from "./Script"
|
||||||
import LinkedDataLoader from "../src/Logic/Web/LinkedDataLoader"
|
import LinkedDataLoader from "../src/Logic/Web/LinkedDataLoader"
|
||||||
import { writeFileSync } from "fs"
|
import { writeFileSync } from "fs"
|
||||||
|
|
||||||
export default class DownloadLinkedDataList extends Script {
|
class DownloadLinkedDataList extends Script {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("Downloads the localBusinesses from the given location. Usage: url [--no-proxy]")
|
super("Downloads the localBusinesses from the given location. Usage: url [--no-proxy]")
|
||||||
}
|
}
|
||||||
|
|
||||||
async main([url, noProxy]: string[]): Promise<void> {
|
async main([url, noProxy]: string[]): Promise<void> {
|
||||||
const useProxy = noProxy !== "--no-proxy"
|
const useProxy = noProxy !== "--no-proxy"
|
||||||
const data = await LinkedDataLoader.fetchJsonLd(url, {}, useProxy)
|
const data = await LinkedDataLoader.fetchJsonLd(url, {}, useProxy ? "proxy" : "fetch-lod")
|
||||||
const path = "linked_data_" + url.replace(/[^a-zA-Z0-9_]/g, "_") + ".jsonld"
|
const path = "linked_data_" + url.replace(/[^a-zA-Z0-9_]/g, "_") + ".jsonld"
|
||||||
writeFileSync(path, JSON.stringify(data), "utf8")
|
writeFileSync(path, JSON.stringify(data), "utf8")
|
||||||
console.log("Written", path)
|
console.log("Written", path)
|
||||||
|
|
|
@ -17,7 +17,7 @@ class CompareWebsiteData extends Script {
|
||||||
if (fs.existsSync(filename)) {
|
if (fs.existsSync(filename)) {
|
||||||
return JSON.parse(fs.readFileSync(filename, "utf-8"))
|
return JSON.parse(fs.readFileSync(filename, "utf-8"))
|
||||||
}
|
}
|
||||||
const jsonLd = await LinkedDataLoader.fetchJsonLd(url, undefined, true)
|
const jsonLd = await LinkedDataLoader.fetchJsonLd(url, undefined, "proxy")
|
||||||
console.log("Got:", jsonLd)
|
console.log("Got:", jsonLd)
|
||||||
fs.writeFileSync(filename, JSON.stringify(jsonLd))
|
fs.writeFileSync(filename, JSON.stringify(jsonLd))
|
||||||
return jsonLd
|
return jsonLd
|
||||||
|
|
|
@ -27,23 +27,23 @@ export default class LinkedDataLoader {
|
||||||
opening_hours: { "@id": "http://schema.org/openingHoursSpecification" },
|
opening_hours: { "@id": "http://schema.org/openingHoursSpecification" },
|
||||||
openingHours: { "@id": "http://schema.org/openingHours", "@container": "@set" },
|
openingHours: { "@id": "http://schema.org/openingHours", "@container": "@set" },
|
||||||
geo: { "@id": "http://schema.org/geo" },
|
geo: { "@id": "http://schema.org/geo" },
|
||||||
alt_name: { "@id": "http://schema.org/alternateName" },
|
alt_name: { "@id": "http://schema.org/alternateName" }
|
||||||
}
|
}
|
||||||
private static COMPACTING_CONTEXT_OH = {
|
private static COMPACTING_CONTEXT_OH = {
|
||||||
dayOfWeek: { "@id": "http://schema.org/dayOfWeek", "@container": "@set" },
|
dayOfWeek: { "@id": "http://schema.org/dayOfWeek", "@container": "@set" },
|
||||||
closes: {
|
closes: {
|
||||||
"@id": "http://schema.org/closes",
|
"@id": "http://schema.org/closes",
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#time",
|
"@type": "http://www.w3.org/2001/XMLSchema#time"
|
||||||
},
|
},
|
||||||
opens: {
|
opens: {
|
||||||
"@id": "http://schema.org/opens",
|
"@id": "http://schema.org/opens",
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#time",
|
"@type": "http://www.w3.org/2001/XMLSchema#time"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
private static formatters: Record<"phone" | "email" | "website", Validator> = {
|
private static formatters: Record<"phone" | "email" | "website", Validator> = {
|
||||||
phone: new PhoneValidator(),
|
phone: new PhoneValidator(),
|
||||||
email: new EmailValidator(),
|
email: new EmailValidator(),
|
||||||
website: new UrlValidator(undefined, undefined, true),
|
website: new UrlValidator(undefined, undefined, true)
|
||||||
}
|
}
|
||||||
private static ignoreKeys = [
|
private static ignoreKeys = [
|
||||||
"http://schema.org/logo",
|
"http://schema.org/logo",
|
||||||
|
@ -56,7 +56,7 @@ export default class LinkedDataLoader {
|
||||||
"http://schema.org/description",
|
"http://schema.org/description",
|
||||||
"http://schema.org/hasMap",
|
"http://schema.org/hasMap",
|
||||||
"http://schema.org/priceRange",
|
"http://schema.org/priceRange",
|
||||||
"http://schema.org/contactPoint",
|
"http://schema.org/contactPoint"
|
||||||
]
|
]
|
||||||
|
|
||||||
private static shapeToPolygon(str: string): Polygon {
|
private static shapeToPolygon(str: string): Polygon {
|
||||||
|
@ -69,8 +69,8 @@ export default class LinkedDataLoader {
|
||||||
.trim()
|
.trim()
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((n) => Number(n))
|
.map((n) => Number(n))
|
||||||
),
|
)
|
||||||
],
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,18 +92,18 @@ export default class LinkedDataLoader {
|
||||||
const context = {
|
const context = {
|
||||||
lat: {
|
lat: {
|
||||||
"@id": "http://schema.org/latitude",
|
"@id": "http://schema.org/latitude",
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#double",
|
"@type": "http://www.w3.org/2001/XMLSchema#double"
|
||||||
},
|
},
|
||||||
lon: {
|
lon: {
|
||||||
"@id": "http://schema.org/longitude",
|
"@id": "http://schema.org/longitude",
|
||||||
"@type": "http://www.w3.org/2001/XMLSchema#double",
|
"@type": "http://www.w3.org/2001/XMLSchema#double"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
const flattened = await jsonld.compact(geo, context)
|
const flattened = await jsonld.compact(geo, context)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: "Point",
|
type: "Point",
|
||||||
coordinates: [Number(flattened.lon), Number(flattened.lat)],
|
coordinates: [Number(flattened.lon), Number(flattened.lat)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,13 +236,28 @@ export default class LinkedDataLoader {
|
||||||
static async fetchJsonLd(
|
static async fetchJsonLd(
|
||||||
url: string,
|
url: string,
|
||||||
options?: JsonLdLoaderOptions,
|
options?: JsonLdLoaderOptions,
|
||||||
useProxy: boolean = false
|
mode: "fetch-lod" | "fetch-raw" | "proxy"
|
||||||
): Promise<object> {
|
): Promise<object> {
|
||||||
if (useProxy) {
|
if (mode === "proxy") {
|
||||||
url = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
|
url = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
|
||||||
}
|
}
|
||||||
const data = await Utils.downloadJson(url)
|
if (mode !== "fetch-raw") {
|
||||||
return await LinkedDataLoader.compact(data, options)
|
const data = await Utils.downloadJson(url)
|
||||||
|
return await LinkedDataLoader.compact(data, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let htmlContent = await Utils.download(url)
|
||||||
|
const div = document.createElement("div")
|
||||||
|
div.innerHTML = htmlContent
|
||||||
|
const script = Array.from(div.getElementsByTagName("script"))
|
||||||
|
.find(script => script.type === "application/ld+json")
|
||||||
|
|
||||||
|
|
||||||
|
const snippet = JSON.parse(script.textContent)
|
||||||
|
snippet["@base"] = url
|
||||||
|
return await LinkedDataLoader.compact(snippet, options)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -293,7 +308,7 @@ export default class LinkedDataLoader {
|
||||||
if (properties["latitude"] && properties["longitude"]) {
|
if (properties["latitude"] && properties["longitude"]) {
|
||||||
geometry = {
|
geometry = {
|
||||||
type: "Point",
|
type: "Point",
|
||||||
coordinates: [Number(properties["longitude"]), Number(properties["latitude"])],
|
coordinates: [Number(properties["longitude"]), Number(properties["latitude"])]
|
||||||
}
|
}
|
||||||
delete properties["latitude"]
|
delete properties["latitude"]
|
||||||
delete properties["longitude"]
|
delete properties["longitude"]
|
||||||
|
@ -305,7 +320,7 @@ export default class LinkedDataLoader {
|
||||||
const geo: GeoJSON = {
|
const geo: GeoJSON = {
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
properties,
|
properties,
|
||||||
geometry,
|
geometry
|
||||||
}
|
}
|
||||||
delete linkedData.geo
|
delete linkedData.geo
|
||||||
delete properties.shape
|
delete properties.shape
|
||||||
|
@ -423,7 +438,7 @@ export default class LinkedDataLoader {
|
||||||
"brede publiek",
|
"brede publiek",
|
||||||
"iedereen",
|
"iedereen",
|
||||||
"bezoekers",
|
"bezoekers",
|
||||||
"iedereen - vooral bezoekers gemeentehuis of bibliotheek.",
|
"iedereen - vooral bezoekers gemeentehuis of bibliotheek."
|
||||||
].indexOf(audience.toLowerCase()) >= 0
|
].indexOf(audience.toLowerCase()) >= 0
|
||||||
) {
|
) {
|
||||||
return "yes"
|
return "yes"
|
||||||
|
@ -506,7 +521,7 @@ export default class LinkedDataLoader {
|
||||||
mv: "http://schema.mobivoc.org/",
|
mv: "http://schema.mobivoc.org/",
|
||||||
gr: "http://purl.org/goodrelations/v1#",
|
gr: "http://purl.org/goodrelations/v1#",
|
||||||
vp: "https://data.velopark.be/openvelopark/vocabulary#",
|
vp: "https://data.velopark.be/openvelopark/vocabulary#",
|
||||||
vpt: "https://data.velopark.be/openvelopark/terms#",
|
vpt: "https://data.velopark.be/openvelopark/terms#"
|
||||||
},
|
},
|
||||||
[url],
|
[url],
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -527,7 +542,7 @@ export default class LinkedDataLoader {
|
||||||
mv: "http://schema.mobivoc.org/",
|
mv: "http://schema.mobivoc.org/",
|
||||||
gr: "http://purl.org/goodrelations/v1#",
|
gr: "http://purl.org/goodrelations/v1#",
|
||||||
vp: "https://data.velopark.be/openvelopark/vocabulary#",
|
vp: "https://data.velopark.be/openvelopark/vocabulary#",
|
||||||
vpt: "https://data.velopark.be/openvelopark/terms#",
|
vpt: "https://data.velopark.be/openvelopark/terms#"
|
||||||
},
|
},
|
||||||
[url],
|
[url],
|
||||||
"g",
|
"g",
|
||||||
|
@ -670,20 +685,20 @@ export default class LinkedDataLoader {
|
||||||
const withProxyUrl = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
|
const withProxyUrl = Constants.linkedDataProxy.replace("{url}", encodeURIComponent(url))
|
||||||
const optionalPaths: Record<string, string | Record<string, string>> = {
|
const optionalPaths: Record<string, string | Record<string, string>> = {
|
||||||
"schema:interactionService": {
|
"schema:interactionService": {
|
||||||
"schema:url": "website",
|
"schema:url": "website"
|
||||||
},
|
},
|
||||||
"mv:operatedBy": {
|
"mv:operatedBy": {
|
||||||
"gr:legalName": "operator",
|
"gr:legalName": "operator"
|
||||||
},
|
},
|
||||||
"schema:contactPoint": {
|
"schema:contactPoint": {
|
||||||
"schema:email": "email",
|
"schema:email": "email",
|
||||||
"schema:telephone": "phone",
|
"schema:telephone": "phone"
|
||||||
},
|
},
|
||||||
"schema:dateModified": "_last_edit_timestamp",
|
"schema:dateModified": "_last_edit_timestamp"
|
||||||
}
|
}
|
||||||
if (includeExtras) {
|
if (includeExtras) {
|
||||||
optionalPaths["schema:address"] = {
|
optionalPaths["schema:address"] = {
|
||||||
"schema:streetAddress": "addr",
|
"schema:streetAddress": "addr"
|
||||||
}
|
}
|
||||||
optionalPaths["schema:name"] = "name"
|
optionalPaths["schema:name"] = "name"
|
||||||
optionalPaths["schema:description"] = "description"
|
optionalPaths["schema:description"] = "description"
|
||||||
|
@ -701,19 +716,19 @@ export default class LinkedDataLoader {
|
||||||
"schema:geo": {
|
"schema:geo": {
|
||||||
"schema:latitude": "latitude",
|
"schema:latitude": "latitude",
|
||||||
"schema:longitude": "longitude",
|
"schema:longitude": "longitude",
|
||||||
"schema:polygon": "shape",
|
"schema:polygon": "shape"
|
||||||
},
|
},
|
||||||
"schema:priceSpecification": {
|
"schema:priceSpecification": {
|
||||||
"mv:freeOfCharge": "fee",
|
"mv:freeOfCharge": "fee",
|
||||||
"schema:price": "charge",
|
"schema:price": "charge"
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const extra = [
|
const extra = [
|
||||||
"schema:priceSpecification [ mv:dueForTime [ mv:timeStartValue ?chargeStart; mv:timeEndValue ?chargeEnd; mv:timeUnit ?timeUnit ] ]",
|
"schema:priceSpecification [ mv:dueForTime [ mv:timeStartValue ?chargeStart; mv:timeEndValue ?chargeEnd; mv:timeUnit ?timeUnit ] ]",
|
||||||
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#CargoBicycle>; vp:bicyclesAmount ?capacityCargobike; vp:bicycleType ?cargoBikeType]",
|
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#CargoBicycle>; vp:bicyclesAmount ?capacityCargobike; vp:bicycleType ?cargoBikeType]",
|
||||||
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#ElectricBicycle>; vp:bicyclesAmount ?capacityElectric; vp:bicycleType ?electricBikeType]",
|
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#ElectricBicycle>; vp:bicyclesAmount ?capacityElectric; vp:bicycleType ?electricBikeType]",
|
||||||
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#TandemBicycle>; vp:bicyclesAmount ?capacityTandem; vp:bicycleType ?tandemBikeType]",
|
"vp:allows [vp:bicycleType <https://data.velopark.be/openvelopark/terms#TandemBicycle>; vp:bicyclesAmount ?capacityTandem; vp:bicycleType ?tandemBikeType]"
|
||||||
]
|
]
|
||||||
|
|
||||||
const unpatched = await this.fetchEntry(
|
const unpatched = await this.fetchEntry(
|
||||||
|
|
|
@ -66,24 +66,22 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class:interactive={!readonly} class="flex w-full justify-between py-1 px-2">
|
<div class:interactive={!readonly} class="flex flex-col items-end py-1 px-2">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col w-full">
|
||||||
<div>
|
{#if renderingExternal}
|
||||||
{#if renderingExternal}
|
<TagRenderingAnswer
|
||||||
<TagRenderingAnswer
|
tags={new UIEventSource(mockPropertiesExternal)}
|
||||||
tags={new UIEventSource(mockPropertiesExternal)}
|
selectedElement={feature}
|
||||||
selectedElement={feature}
|
config={renderingExternal}
|
||||||
config={renderingExternal}
|
{layer}
|
||||||
{layer}
|
{state}
|
||||||
{state}
|
/>
|
||||||
/>
|
{:else}
|
||||||
{:else}
|
<div class="flex items-center gap-x-1">
|
||||||
<div class="flex items-center gap-x-1">
|
<b>{key}</b>
|
||||||
<b>{key}</b>
|
{externalProperties[key]}
|
||||||
{externalProperties[key]}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !readonly && ($isTesting || $isDebug || $showTags === "yes" || $showTags === "always" || $showTags === "full")}
|
{#if !readonly && ($isTesting || $isDebug || $showTags === "yes" || $showTags === "always" || $showTags === "full")}
|
||||||
<div class="subtle text-sm">
|
<div class="subtle text-sm">
|
||||||
|
@ -103,7 +101,7 @@
|
||||||
{#if !readonly}
|
{#if !readonly}
|
||||||
{#if currentStep === "init"}
|
{#if currentStep === "init"}
|
||||||
<button
|
<button
|
||||||
class="small"
|
class="w-fit"
|
||||||
on:click={() => apply(key)}
|
on:click={() => apply(key)}
|
||||||
on:mouseover={() => (onOverwrite = true)}
|
on:mouseover={() => (onOverwrite = true)}
|
||||||
on:focus={() => (onOverwrite = true)}
|
on:focus={() => (onOverwrite = true)}
|
||||||
|
|
|
@ -1833,8 +1833,15 @@ export default class SpecialVisualizations {
|
||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return Stores.FromPromiseWithErr(
|
return Stores.FromPromiseWithErr((async () => {
|
||||||
LinkedDataLoader.fetchJsonLd(url, { country }, useProxy)
|
try {
|
||||||
|
|
||||||
|
return await LinkedDataLoader.fetchJsonLd(url, { country }, useProxy ? "proxy" : "fetch-lod")
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Could not get with proxy/download LOD, attempting to download directly. Error for ",url,"is",e)
|
||||||
|
return await LinkedDataLoader.fetchJsonLd(url, { country }, "fetch-raw")
|
||||||
|
}
|
||||||
|
})()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1850,7 +1857,8 @@ export default class SpecialVisualizations {
|
||||||
layer,
|
layer,
|
||||||
externalData,
|
externalData,
|
||||||
sourceUrl,
|
sourceUrl,
|
||||||
readonly
|
readonly,
|
||||||
|
collapsed: isClosed
|
||||||
}),
|
}),
|
||||||
undefined,
|
undefined,
|
||||||
url.map((url) => !!url)
|
url.map((url) => !!url)
|
||||||
|
|
Loading…
Reference in a new issue