forked from MapComplete/MapComplete
Furhter improvements to velopark: better icons, improvements to loading
This commit is contained in:
parent
5a48a2e19c
commit
8bcc8820ac
10 changed files with 560 additions and 381 deletions
|
@ -1,4 +1,4 @@
|
|||
import { Feature, Geometry, Point } from "geojson"
|
||||
import { Feature, Geometry } from "geojson"
|
||||
import { OH } from "../../UI/OpeningHours/OpeningHours"
|
||||
import EmailValidator from "../../UI/InputElement/Validators/EmailValidator"
|
||||
import PhoneValidator from "../../UI/InputElement/Validators/PhoneValidator"
|
||||
|
@ -12,39 +12,49 @@ import { Utils } from "../../Utils"
|
|||
* Reads a velopark-json, converts it to a geojson
|
||||
*/
|
||||
export default class VeloparkLoader {
|
||||
|
||||
private static readonly emailReformatting = new EmailValidator()
|
||||
private static readonly phoneValidator = new PhoneValidator()
|
||||
|
||||
private static readonly coder = new CountryCoder(
|
||||
Constants.countryCoderEndpoint,
|
||||
Utils.downloadJson,
|
||||
Utils.downloadJson
|
||||
)
|
||||
|
||||
public static convert(veloparkData: VeloparkData): Feature {
|
||||
|
||||
console.log("Converting", veloparkData)
|
||||
const properties: {
|
||||
"ref:velopark":string,
|
||||
"operator:email"?: string,
|
||||
"operator:phone"?: string,
|
||||
fee?: string,
|
||||
"ref:velopark": string
|
||||
"operator:email"?: string
|
||||
"operator:phone"?: string
|
||||
fee?: string
|
||||
opening_hours?: string
|
||||
access?: string
|
||||
maxstay?: string
|
||||
operator?: string
|
||||
} = {
|
||||
"ref:velopark": veloparkData["id"] ?? veloparkData["@id"]
|
||||
"ref:velopark": veloparkData["id"] ?? veloparkData["@id"],
|
||||
}
|
||||
|
||||
for (const k of ["_id", "url", "dateModified", "name", "address"]) {
|
||||
delete veloparkData[k]
|
||||
}
|
||||
|
||||
VeloparkLoader.cleanup(veloparkData["properties"])
|
||||
VeloparkLoader.cleanupEmtpy(veloparkData)
|
||||
|
||||
properties.operator = veloparkData.operatedBy?.companyName
|
||||
|
||||
if (veloparkData.contactPoint?.email) {
|
||||
properties["operator:email"] = VeloparkLoader.emailReformatting.reformat(veloparkData.contactPoint?.email)
|
||||
properties["operator:email"] = VeloparkLoader.emailReformatting.reformat(
|
||||
veloparkData.contactPoint?.email
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if (veloparkData.contactPoint?.telephone) {
|
||||
properties["operator:phone"] = VeloparkLoader.phoneValidator.reformat(veloparkData.contactPoint?.telephone, () => "be")
|
||||
properties["operator:phone"] = VeloparkLoader.phoneValidator.reformat(
|
||||
veloparkData.contactPoint?.telephone,
|
||||
() => "be"
|
||||
)
|
||||
}
|
||||
|
||||
veloparkData.photos?.forEach((p, i) => {
|
||||
|
@ -52,130 +62,198 @@ export default class VeloparkLoader {
|
|||
properties["image"] = p.image
|
||||
} else {
|
||||
properties["image:" + i] = p.image
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
let geometry = veloparkData.geometry
|
||||
for (const g of veloparkData["@graph"]) {
|
||||
VeloparkLoader.cleanup(g)
|
||||
VeloparkLoader.cleanupEmtpy(g)
|
||||
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)
|
||||
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/"
|
||||
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("; "))
|
||||
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"
|
||||
}
|
||||
const types = {
|
||||
"https://data.velopark.be/openvelopark/terms#RegularBicycle": "_",
|
||||
"https://data.velopark.be/openvelopark/terms#ElectricBicycle":
|
||||
"capacity:electric_bicycle",
|
||||
"https://data.velopark.be/openvelopark/terms#CargoBicycle": "capacity:cargo_bike",
|
||||
}
|
||||
let totalCapacity = 0
|
||||
for (let i = (g.allows ?? []).length - 1; i >= 0; i--) {
|
||||
const capacity = g.allows[i]
|
||||
const type: string = capacity["@type"]
|
||||
if (type === undefined) {
|
||||
console.warn("No type found for", capacity.bicycleType)
|
||||
continue
|
||||
}
|
||||
const count = capacity["amount"]
|
||||
if (!isNaN(count)) {
|
||||
totalCapacity += Number(count)
|
||||
} else {
|
||||
console.warn("Not a valid number while loading velopark data:", count)
|
||||
}
|
||||
if (type !== "_") {
|
||||
// properties[type] = count
|
||||
}
|
||||
g.allows.splice(i, 1)
|
||||
}
|
||||
if (totalCapacity > 0) {
|
||||
properties["capacity"] = totalCapacity
|
||||
}
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(properties, null, " "))
|
||||
|
||||
return { type: "Feature", properties, geometry }
|
||||
}
|
||||
|
||||
private static cleanup(data: any) {
|
||||
if (!data?.attributes) {
|
||||
return
|
||||
}
|
||||
for (const k of ["NIS_CODE", "name_NL", "name_DE", "name_EN", "name_FR"]) {
|
||||
delete data.attributes[k]
|
||||
}
|
||||
VeloparkLoader.cleanupEmtpy(data)
|
||||
}
|
||||
|
||||
private static cleanupEmtpy(data: any) {
|
||||
for (const key in data) {
|
||||
if (data[key] === null) {
|
||||
delete data[key]
|
||||
continue
|
||||
}
|
||||
if (Object.keys(data[key]).length === 0) {
|
||||
delete data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface VeloparkData {
|
||||
geometry?: Geometry
|
||||
"@context": any,
|
||||
"@context": any
|
||||
"@id": string // "https://data.velopark.be/data/NMBS_541",
|
||||
"@type": "BicycleParkingStation",
|
||||
"dateModified": string,
|
||||
"identifier": number,
|
||||
"name": [
|
||||
"@type": "BicycleParkingStation"
|
||||
dateModified: string
|
||||
identifier: number
|
||||
name: [
|
||||
{
|
||||
"@value": string,
|
||||
"@value": string
|
||||
"@language": "nl"
|
||||
}
|
||||
],
|
||||
"ownedBy": {
|
||||
"@id": string,
|
||||
"@type": "BusinessEntity",
|
||||
"companyName": string
|
||||
},
|
||||
"operatedBy": {
|
||||
"@type": "BusinessEntity",
|
||||
"companyName": string
|
||||
},
|
||||
"address": any,
|
||||
"hasMap": any,
|
||||
"contactPoint": {
|
||||
"@type": "ContactPoint",
|
||||
"email": string,
|
||||
"telephone": string
|
||||
},
|
||||
"photos": {
|
||||
"@type": "Photograph",
|
||||
"image": string
|
||||
}[],
|
||||
"interactionService": {
|
||||
"@type": "WebSite",
|
||||
"url": string
|
||||
},
|
||||
]
|
||||
ownedBy: {
|
||||
"@id": string
|
||||
"@type": "BusinessEntity"
|
||||
companyName: string
|
||||
}
|
||||
operatedBy: {
|
||||
"@type": "BusinessEntity"
|
||||
companyName: string
|
||||
}
|
||||
address: any
|
||||
hasMap: any
|
||||
contactPoint: {
|
||||
"@type": "ContactPoint"
|
||||
email: string
|
||||
telephone: string
|
||||
}
|
||||
photos: {
|
||||
"@type": "Photograph"
|
||||
image: string
|
||||
}[]
|
||||
interactionService: {
|
||||
"@type": "WebSite"
|
||||
url: string
|
||||
}
|
||||
/**
|
||||
* Contains various extra pieces of data, e.g. services or opening hours
|
||||
*/
|
||||
"@graph": [
|
||||
{
|
||||
"@type": "https://data.velopark.be/openvelopark/terms#PublicBicycleParking",
|
||||
"openingHoursSpecification": {
|
||||
"@type": "OpeningHoursSpecification",
|
||||
"@type": "https://data.velopark.be/openvelopark/terms#PublicBicycleParking"
|
||||
openingHoursSpecification: {
|
||||
"@type": "OpeningHoursSpecification"
|
||||
/**
|
||||
* Ends with 'Monday', 'Tuesday', ...
|
||||
*/
|
||||
"dayOfWeek": "http://schema.org/Monday"
|
||||
dayOfWeek:
|
||||
| "http://schema.org/Monday"
|
||||
| "http://schema.org/Tuesday"
|
||||
| "http://schema.org/Wednesday"
|
||||
| "http://schema.org/Thursday"
|
||||
| "http://schema.org/Friday"
|
||||
| "http://schema.org/Saturday"
|
||||
| "http://schema.org/Sunday",
|
||||
| "http://schema.org/Sunday"
|
||||
/**
|
||||
* opens: 00:00 and closes 23:59 for the entire day
|
||||
*/
|
||||
"opens": string,
|
||||
"closes": string
|
||||
}[],
|
||||
opens: string
|
||||
closes: string
|
||||
}[]
|
||||
/**
|
||||
* P30D = 30 days
|
||||
*/
|
||||
"maximumParkingDuration": "P30D",
|
||||
"publicAccess": true,
|
||||
"totalCapacity": 110,
|
||||
"allows": [
|
||||
maximumParkingDuration: "P30D"
|
||||
publicAccess: true
|
||||
totalCapacity: 110
|
||||
allows: [
|
||||
{
|
||||
"@type": "AllowedBicycle",
|
||||
"@type": "AllowedBicycle"
|
||||
/* TODO is cargo bikes etc also available?*/
|
||||
"bicycleType": "https://data.velopark.be/openvelopark/terms#RegularBicycle",
|
||||
"bicyclesAmount": number
|
||||
bicycleType:
|
||||
| string
|
||||
| "https://data.velopark.be/openvelopark/terms#RegularBicycle"
|
||||
bicyclesAmount: number
|
||||
}
|
||||
],
|
||||
"geo": [
|
||||
]
|
||||
geo: [
|
||||
{
|
||||
"@type": "GeoCoordinates",
|
||||
"latitude": number,
|
||||
"longitude": number
|
||||
"@type": "GeoCoordinates"
|
||||
latitude: number
|
||||
longitude: number
|
||||
}
|
||||
],
|
||||
"priceSpecification": [
|
||||
]
|
||||
priceSpecification: [
|
||||
{
|
||||
"@type": "PriceSpecification",
|
||||
"freeOfCharge": boolean
|
||||
"@type": "PriceSpecification"
|
||||
freeOfCharge: boolean
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue