From e731c677b920f435c054b00ab7f8d4a69ad27b01 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 16 Jan 2024 04:07:02 +0100 Subject: [PATCH] Scripts: add velopark export script --- scripts/velopark/veloParkToGeojson.ts | 55 +++++++++++++++++++++++++++ src/Logic/Web/VeloparkLoader.ts | 44 ++++++++++++--------- 2 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 scripts/velopark/veloParkToGeojson.ts diff --git a/scripts/velopark/veloParkToGeojson.ts b/scripts/velopark/veloParkToGeojson.ts new file mode 100644 index 000000000..a3226cf39 --- /dev/null +++ b/scripts/velopark/veloParkToGeojson.ts @@ -0,0 +1,55 @@ +import Script from "../Script" +import { Utils } from "../../src/Utils" +import VeloparkLoader, { VeloparkData } from "../../src/Logic/Web/VeloparkLoader" +import fs from "fs" +import OverpassFeatureSource from "../../src/Logic/FeatureSource/Sources/OverpassFeatureSource" +import { Overpass } from "../../src/Logic/Osm/Overpass" +import { RegexTag } from "../../src/Logic/Tags/RegexTag" +import Constants from "../../src/Models/Constants" +import { ImmutableStore } from "../../src/Logic/UIEventSource" +import { BBox } from "../../src/Logic/BBox" +class VeloParkToGeojson extends Script { + constructor() { + super("Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory") + } + + async main(args: string[]): Promise { + console.log("Downloading velopark data") + // Download data for NIS-code 1000. 1000 means: all of belgium + const url = "https://www.velopark.be/api/parkings/1000" + const data = await Utils.downloadJson(url) + + const bboxBelgium = new BBox([[2.51357303225, 49.5294835476],[ 6.15665815596, 51.4750237087]]) + const alreadyLinkedQuery = new Overpass(new RegexTag("ref:velopark", /.+/), + [], + Constants.defaultOverpassUrls[0], + new ImmutableStore(60*5), + false + ) + const alreadyLinkedFeatures = await alreadyLinkedQuery.queryGeoJson(bboxBelgium) + const seenIds = new Set(alreadyLinkedFeatures[0].features.map(f => f.properties["ref:velopark"])) + const features = data.map(f => VeloparkLoader.convert(f)) + .filter(f => !seenIds.has(f.properties["ref:velopark"])) + + const allProperties = new Set() + for (const feature of features) { + Object.keys(feature.properties).forEach(k => allProperties.add(k)) + } + allProperties.delete("ref:velopark") + for (const feature of features) { + allProperties.forEach(k => { + delete feature.properties[k] + }) + } + + fs.writeFileSync("velopark_id_only_export_" + new Date().toISOString() + ".geojson", JSON.stringify({ + "type": "FeatureCollection", + features, + }, null, " ")) + + } + + +} + +new VeloParkToGeojson().run() diff --git a/src/Logic/Web/VeloparkLoader.ts b/src/Logic/Web/VeloparkLoader.ts index 30d9b6268..1101aaaaf 100644 --- a/src/Logic/Web/VeloparkLoader.ts +++ b/src/Logic/Web/VeloparkLoader.ts @@ -1,4 +1,4 @@ -import { Feature, Point } from "geojson" +import { Feature, Geometry, Point } from "geojson" import { OH } from "../../UI/OpeningHours/OpeningHours" import EmailValidator from "../../UI/InputElement/Validators/EmailValidator" import PhoneValidator from "../../UI/InputElement/Validators/PhoneValidator" @@ -18,12 +18,13 @@ export default class VeloparkLoader { private static readonly coder = new CountryCoder( Constants.countryCoderEndpoint, - Utils.downloadJson + Utils.downloadJson, ) - public static convert(veloparkData: VeloparkData): Feature { + public static convert(veloparkData: VeloparkData): Feature { const properties: { + "ref:velopark":string, "operator:email"?: string, "operator:phone"?: string, fee?: string, @@ -31,7 +32,9 @@ export default class VeloparkLoader { access?: string maxstay?: string operator?: string - } = {} + } = { + "ref:velopark": veloparkData["id"] ?? veloparkData["@id"] + } properties.operator = veloparkData.operatedBy?.companyName @@ -53,36 +56,39 @@ export default class VeloparkLoader { } }) - let coordinates: [number, number] = undefined + let geometry = veloparkData.geometry for (const g of veloparkData["@graph"]) { - coordinates = [g.geo[0].longitude, g.geo[0].latitude] + if (g.geo[0]) { + geometry = { type: "Point", coordinates: [g.geo[0].longitude, g.geo[0].latitude] } + } if (g.maximumParkingDuration?.endsWith("D") && g.maximumParkingDuration?.startsWith("P")) { const duration = g.maximumParkingDuration.substring(1, g.maximumParkingDuration.length - 1) properties.maxstay = duration + " days" } properties.access = g.publicAccess ? "yes" : "no" const prefix = "http://schema.org/" - const oh = OH.simplify(g.openingHoursSpecification.map(spec => { - const dayOfWeek = spec.dayOfWeek.substring(prefix.length, prefix.length + 2).toLowerCase() - const startHour = spec.opens - const endHour = spec.closes === "23:59" ? "24:00" : spec.closes - const merged = OH.MergeTimes(OH.ParseRule(dayOfWeek + " " + startHour + "-" + endHour)) - return OH.ToString(merged) - }).join("; ")) - properties.opening_hours = oh - - if (g.priceSpecification[0]) { + if (g.openingHoursSpecification) { + const oh = OH.simplify(g.openingHoursSpecification.map(spec => { + const dayOfWeek = spec.dayOfWeek.substring(prefix.length, prefix.length + 2).toLowerCase() + const startHour = spec.opens + const endHour = spec.closes === "23:59" ? "24:00" : spec.closes + const merged = OH.MergeTimes(OH.ParseRule(dayOfWeek + " " + startHour + "-" + endHour)) + return OH.ToString(merged) + }).join("; ")) + properties.opening_hours = oh + } + if (g.priceSpecification?.[0]) { properties.fee = g.priceSpecification[0].freeOfCharge ? "no" : "yes" } } - - return { type: "Feature", properties, geometry: { type: "Point", coordinates } } + return { type: "Feature", properties, geometry } } } -interface VeloparkData { +export interface VeloparkData { + geometry?: Geometry "@context": any, "@id": string // "https://data.velopark.be/data/NMBS_541", "@type": "BicycleParkingStation",