| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  | import Script from "../Script" | 
					
						
							|  |  |  | import fs from "fs" | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  | import LinkedDataLoader from "../../src/Logic/Web/LinkedDataLoader" | 
					
						
							|  |  |  | import { Utils } from "../../src/Utils" | 
					
						
							|  |  |  | import { Feature } from "geojson" | 
					
						
							|  |  |  | import { BBox } from "../../src/Logic/BBox" | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  | import { Overpass } from "../../src/Logic/Osm/Overpass" | 
					
						
							|  |  |  | import { RegexTag } from "../../src/Logic/Tags/RegexTag" | 
					
						
							|  |  |  | import { ImmutableStore } from "../../src/Logic/UIEventSource" | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  | import Constants from "../../src/Models/Constants" | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  | class VeloParkToGeojson extends Script { | 
					
						
							|  |  |  |     constructor() { | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         super( | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |             "Downloads the latest Velopark data and converts it to a geojson, which will be saved at the current directory" | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |     private static exportGeojsonTo(filename: string, features: Feature[], extension = ".geojson") { | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         const file = filename + "_" + /*new Date().toISOString() + */ extension | 
					
						
							|  |  |  |         fs.writeFileSync( | 
					
						
							|  |  |  |             file, | 
					
						
							| 
									
										
										
										
											2024-02-15 01:07:50 +01:00
										 |  |  |             JSON.stringify( | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                 extension === ".geojson" | 
					
						
							|  |  |  |                     ? { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |                           type: "FeatureCollection", | 
					
						
							|  |  |  |                           features, | 
					
						
							|  |  |  |                       } | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |                     : features, | 
					
						
							| 
									
										
										
										
											2024-02-15 01:07:50 +01:00
										 |  |  |                 null, | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |                 "    " | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-02-15 01:07:50 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         console.log("Written", file, "(" + features.length, " features)") | 
					
						
							| 
									
										
										
										
											2024-02-15 01:07:50 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |     private static async downloadDataFor(url: string): Promise<Feature[]> { | 
					
						
							|  |  |  |         const cachePath = | 
					
						
							|  |  |  |             "/home/pietervdvn/data/velopark_cache_refined/" + url.replace(/[/:.]/g, "_") | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |         if (fs.existsSync(cachePath)) { | 
					
						
							|  |  |  |             return JSON.parse(fs.readFileSync(cachePath, "utf8")) | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |         console.log("Fetching data for", url) | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const linkedData = await LinkedDataLoader.fetchVeloparkEntry(url) | 
					
						
							|  |  |  |         const allVelopark: Feature[] = [] | 
					
						
							|  |  |  |         for (const sectionId in linkedData) { | 
					
						
							|  |  |  |             const sectionInfo = linkedData[sectionId] | 
					
						
							|  |  |  |             if (Object.keys(sectionInfo).length === 0) { | 
					
						
							|  |  |  |                 console.warn("No result for", url) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             if (!sectionInfo.geometry?.coordinates) { | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |                 throw "Invalid properties!" | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |             allVelopark.push(sectionInfo) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |         fs.writeFileSync(cachePath, JSON.stringify(allVelopark), "utf8") | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         return allVelopark | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |     private static async downloadData() { | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  |         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" | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         const allVeloparkRaw: { url: string }[] = <{ url: string }[]>await Utils.downloadJson(url) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         let failed = 0 | 
					
						
							|  |  |  |         console.log("Got", allVeloparkRaw.length, "items") | 
					
						
							|  |  |  |         const allVelopark: Feature[] = [] | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |         const batchSize = 50 | 
					
						
							|  |  |  |         for (let i = 0; i < allVeloparkRaw.length; i += batchSize) { | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             await Promise.all( | 
					
						
							|  |  |  |                 Utils.TimesT(batchSize, (j) => j).map(async (j) => { | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |                     const f = allVeloparkRaw[i + j] | 
					
						
							|  |  |  |                     if (!f) { | 
					
						
							|  |  |  |                         return | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     try { | 
					
						
							|  |  |  |                         const sections: Feature[] = await VeloParkToGeojson.downloadDataFor(f.url) | 
					
						
							|  |  |  |                         allVelopark.push(...sections) | 
					
						
							|  |  |  |                     } catch (e) { | 
					
						
							|  |  |  |                         console.error("Loading ", f.url, " failed due to", e) | 
					
						
							|  |  |  |                         failed++ | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |                 }) | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         console.log( | 
					
						
							|  |  |  |             "Fetching data done, got ", | 
					
						
							|  |  |  |             allVelopark.length + "/" + allVeloparkRaw.length, | 
					
						
							|  |  |  |             "failed:", | 
					
						
							|  |  |  |             failed | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         VeloParkToGeojson.exportGeojsonTo("velopark_all", allVelopark) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return allVelopark | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |     private static loadFromFile(maxCacheAgeSeconds = 24 * 60 * 60): Feature[] | null { | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         const path = "velopark_all.geojson" | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         if (!fs.existsSync(path)) { | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |             return null | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         // Millis since epoch
 | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         const mtime: number = fs.statSync(path).mtime.getTime() | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         const stalenessSeconds = (new Date().getTime() - mtime) / 1000 | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         if (stalenessSeconds > maxCacheAgeSeconds) { | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |             return null | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return JSON.parse(fs.readFileSync(path, "utf-8")).features | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static exportExtraAmenities(allVelopark: Feature[]) { | 
					
						
							|  |  |  |         const amenities: Record<string, Feature[]> = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const bikeparking of allVelopark) { | 
					
						
							|  |  |  |             const props = bikeparking.properties | 
					
						
							|  |  |  |             if (!props["fixme_nearby_amenity"]) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (props["fixme_nearby_amenity"]?.endsWith("CameraSurveillance")) { | 
					
						
							|  |  |  |                 delete props["fixme_nearby_amenity"] | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const amenity = props["fixme_nearby_amenity"].split("#")[1] | 
					
						
							|  |  |  |             if (!amenities[amenity]) { | 
					
						
							|  |  |  |                 amenities[amenity] = [] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             amenities[amenity].push(bikeparking) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const k in amenities) { | 
					
						
							|  |  |  |             this.exportGeojsonTo("velopark_amenity_" + k + ".geojson", amenities[k]) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static async createDiff(allVelopark: Feature[]) { | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         const bboxBelgium = new BBox([ | 
					
						
							|  |  |  |             [2.51357303225, 49.5294835476], | 
					
						
							| 
									
										
										
										
											2024-06-16 16:06:26 +02:00
										 |  |  |             [6.15665815596, 51.4750237087], | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         ]) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         const alreadyLinkedQuery = new Overpass( | 
					
						
							|  |  |  |             new RegexTag("ref:velopark", /.+/), | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  |             [], | 
					
						
							|  |  |  |             Constants.defaultOverpassUrls[0], | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |             new ImmutableStore(60 * 5), | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |             false | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |         const alreadyLinkedFeatures = (await alreadyLinkedQuery.queryGeoJson(bboxBelgium))[0] | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         const seenIds = new Set<string>( | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |             alreadyLinkedFeatures.features.map((f) => f.properties?.["ref:velopark"]) | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         this.exportGeojsonTo("osm_with_velopark_link", <Feature[]>alreadyLinkedFeatures.features) | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |         console.log("OpenStreetMap contains", seenIds.size, "bicycle parkings with a velopark ref") | 
					
						
							| 
									
										
										
										
											2024-02-15 01:07:50 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         const features: Feature[] = allVelopark.filter( | 
					
						
							|  |  |  |             (f) => !seenIds.has(f.properties["ref:velopark"]) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |         VeloParkToGeojson.exportGeojsonTo("velopark_nonsynced", features) | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const allProperties = new Set<string>() | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |             Object.keys(feature).forEach((k) => allProperties.add(k)) | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |         allProperties.delete("ref:velopark") | 
					
						
							|  |  |  |         for (const feature of features) { | 
					
						
							| 
									
										
										
										
											2024-01-17 18:08:14 +01:00
										 |  |  |             allProperties.forEach((k) => { | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |                 delete feature[k] | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |         this.exportGeojsonTo("velopark_nonsynced_id_only", features) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |     async main(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         const allVelopark = | 
					
						
							|  |  |  |             VeloParkToGeojson.loadFromFile() ?? (await VeloParkToGeojson.downloadData()) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |         console.log("Got", allVelopark.length, " items") | 
					
						
							| 
									
										
										
										
											2024-04-13 01:36:37 +02:00
										 |  |  |         VeloParkToGeojson.exportExtraAmenities(allVelopark) | 
					
						
							| 
									
										
										
										
											2024-04-05 17:49:31 +02:00
										 |  |  |         await VeloParkToGeojson.createDiff(allVelopark) | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         console.log( | 
					
						
							| 
									
										
										
										
											2024-06-14 01:01:41 +02:00
										 |  |  |             "Use vite-node scripts/velopark/compare.ts to compare the results and generate a diff file" | 
					
						
							| 
									
										
										
										
											2024-04-13 02:40:21 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-01-16 04:07:02 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | new VeloParkToGeojson().run() |