Fetchen en opschonen van RDWData - marcoFijan/frontend-data GitHub Wiki
Inhoud
- Onderzoek van de data
- Ophalen van de data #1
- Probleem #1
- Ophalen van de data #2
- Before-after opschoning
- Probleem #2
- Ophalen van de data #3
Onderzoek van de data
Voor mijn concept had ik 3 hoofd elementen nodig. Ik moest per parkeergarage weten wat de capaciteit was, of deze parkeergarage toegankelijk was en waar deze parkeergarage was.
Voor de capaciteit en toegankelijkheid kon ik makkelijk de RDW Dataset: Specificaties Parkeergebied gebruiken. Hier zat capacity en disabledAccess. Alleen het koppelen met een locatie was een probleem.
In elke dataset zat een algemene 'identifier'-kolom. Deze heette areaManagerId en areaId. Met deze kolommen kon je andere datasets met elkaar linken. Het probleem was alleen dat niet elke dataset dezelfde areaManagerId en areaId. Wanneer ik deze dataset dus zou linken met een andere dataset die ook areaManagerId en areaId had én ook de locatie had, zou ik niet alles kunnen linken en zou er data ongebruikt blijven. Hierdoor hield ik uiteindelijk weinig data over die ik kon gebruiken.
Er was gelukkig nog een andere manier. Er was ook een API beschikbaar die alle parkeergarages had. Al die parkeergarages hadden dan weer een link, staticDataUrl, met daarin alle specifieke data over die parkeergarage. Dat zag er zo uit:
{
"ParkingFacilities": [
{
"name": "P+R Station Appingedam (Appingedam)",
"identifier": "fc749565-1fe9-42f0-920a-3b4e718d62f9",
"staticDataUrl": "https://npropendata.rdw.nl//parkingdata/v2/static/fc749565-1fe9-42f0-920a-3b4e718d62f9",
"limitedAccess": false,
"staticDataLastUpdated": 1601717435
}
]
}
Hier waren op deze manier bijna 8000 parkeergarages aangegeven. Wanneer je naar de staticDataUrl binnen de garage ging, kreeg je iets wat leek op dit:
{
"parkingFacilityInformation": {
"description": "P+R Station Appingedam (Appingedam)",
"identifier": "fc749565-1fe9-42f0-920a-3b4e718d62f9",
"validityStartOfPeriod": 1430784000,
"validityEndOfPeriod": null,
"name": "P+R Station Appingedam (Appingedam)",
"limitedAccess": false,
"specifications": [
{
"validityStartOfPeriod": 1574423959,
"capacity": 22,
"chargingPointCapacity": 0,
"disabledAccess": false,
"minimumHeightInMeters": 0.0,
"usage": "Park & Ride"
}
],
"operator": {
"validityStartOfPeriod": 1430784000,
"validityEndOfPeriod": null,
"name": "Appingedam",
"administrativeAddresses": [
{
"emailAddresses": [
"[email protected]"
],
"streetName": "Wilhelminaweg",
"houseNumber": "14",
"zipcode": "9901CM",
"city": "Appingedam",
"province": "Groningen",
"country": "Nederland",
"phoneNumbers": [
"140596"
]
}
],
"postalAddress": {
"emailAddresses": [
"[email protected]"
],
"streetName": "Postbus",
"houseNumber": "15",
"zipcode": "9900AA",
"city": "Appingedam",
"province": "Groningen",
"country": "Nederland",
"phoneNumbers": [
"140596"
]
Maar dit was dan nog 4 keer zo lang. Het ophalen van al die data zou dus heel erg groot worden. Je moet je voorstellen dat de specificatiedata hierboven 4 keer zo lang is, en dat er 8000 parkeergarages zijn. Dus 8000 keer al die informatie. Toch probeerde ik eerst al die data op te slaan zodat ik hierna daaruit makkelijk gelijk kan filteren.
Ophalen van de data
Ik had me eerst gefocust om alle data op te halen en deze op te slaan in een json. Dit ging vrij makkelijk. Ik gebruikte de code van Laurens Aardloudse als bron om alle data uit de staticDataUrl's op te halen en die op te slaan in een nieuwe array. Hier hoefde ik me nog geen zorgen te maken over opschonen van de data want het enige wat ik deed was alles simpelweg opvragen en opslaan. Eerst haalde ik alleen de url's op in een algemene function
Calling functions
async function setupData(){
const parkingOverview = await getData(overviewRDWUrl)
const combinedData = await combineData(parkingOverview)
download_txt(combinedData)
}
Fetchen van data
Dat doe ik eerst met een simpele getData function
async function getData(url){
const parkingOverview = await d3.json(url)
setTimeout('', 2000) // Dummy timeout for rateLimit
return parkingOverview
}
Ophalen van de staticDataUrl data
Vervolgens, nadat ik de data heb verzameld voer ik de combineData uit. Hier zit eigenlijk het lastige stukje waar ik de staticDataUrl's ophaal en deze bewaar in een array.
async function combineData(parkingOverview){
const parkingOverviewSliced = parkingOverview.ParkingFacilities
const parkingIdentifiers = parkingOverviewSliced.map(garage => garage.identifier)
console.log(parkingIdentifiers)
// const baseUrl = proxyUrl + overviewRDWUrl + 'static/'
const baseUrl = overviewRDWUrl + 'static/'
parkingFacilityArray = parkingIdentifiers.map(identifier => getData(baseUrl+identifier))
console.log(parkingFacilityArray)
const dataCollection = await Promise.all(parkingFacilityArray)
const dataCollectionArray = dataCollection.map(garage => garage.parkingFacilityInformation)
return dataCollectionArray
}
Sla de data op
Vervolgens voer ik deze functie uit die de data opslaat in een tekstdocument. Vervolgens kan ik makkelijk de inhoudt hiervan in een json converter plakken en dan heb ik de volledige json.
function download_txt(data) {
const textToSave = JSON.stringify(data)
const hiddenElement = document.createElement('a')
hiddenElement.href = 'data:attachment/text,' + encodeURI(textToSave)
hiddenElement.target = '_blank'
hiddenElement.download = 'myFile.txt'
hiddenElement.click()
}
Probleem
Na vele 503 networkerrors van de proxyUrl was het me eindelijk gelukt. Maar, dit bestand was zo groot dat alle programma's die dit probeerde om te zetten naar json crashte. Ik begon eerst met verschillende websites om het bestand om te zetten naar JSON. Ik gebruikte websites zoals jsonformatter van curious concept, jsonformatter.io en vele andere. Helaas kon simpelweg de browser en/of de server van de website het niet aan om die code om te zetten. Alleen al uploaden was vaak al een probleem.
Ik probeerde daarom om de inhoud van het tekstdocument te pakken en die simpelweg, zonder eerst mooi op te schonen, te hosten. Maar ook hier ging het fout. Ik had hoge verwachtingen van jsonbin, maar helaas. Ik kreeg het voor elkaar om de code te uploaden, jsonbin herkende de json ook, maar het bestand was simpelweg te groot om vervolgens op te slaan. De server bleef namelijk crashen.
Na 3 uur dit geprobeerd te hebben gaf ik het op en besloot ik om opnieuw alle parkeergarages te fetchen maar dit keer alleen de nodige data op te slaan.
Ophalen van de data #2
Dit keer ging ik voor specifieke data. Zoals ik eerder aangaf heb ik 3 datapunten nodig: capacity, disabledAccess en province.
Ik herschreef het definiëren van 1 variable in mijn combinedData function. Van:
const dataCollectionArray = dataCollection.map(garage => garage.parkingFacilityInformation)
Naar:
const dataCollectionArray = dataCollection.map(garage => {
cleanLocation(garage)
cleanCapacity(garage)
return {
location: garage.parkingFacilityInformation.operator.postalAddress.province,
capacity: garage.parkingFacilityInformation.specifications[0].capacity,
disabledAccess: garage.parkingFacilityInformation.limitedAccess
}
})
Opschonen #1
De data die ik nu ging opvragen was erg specifiek. Het kan dan natuurlijk voorkomen dat het object wat ik opvraag niet bestaat. Daarvoor gebruikte ik de cleanLocation(garage) en cleanCapacity(garage).
Met cleanLocation controleerde ik per garage of de locatie bestaat. Als hij niet bestaat, en als ik dus undefined terugkrijg, geef ik de waarde null mee. Door null mee te geven kan ik in later stadia met if statements makkelijk opzoeken of de location bestaat of niet. Dit kan aangezien null ook een boolean(false) teruggeeft wanneer je deze opvraagt.
const cleanLocation = function(parkingGarage){
if (typeof parkingGarage.parkingFacilityInformation.operator.postalAddress == 'undefined'){
parkingGarage.parkingFacilityInformation.operator.postalAddress = {province: null}
}
}
Voor cleanCapacity deed ik hetzelfde:
const cleanCapacity = function(parkingGarage){
if (typeof parkingGarage.parkingFacilityInformation.specifications[0].capacity == 'undefined'){
parkingGarage.parkingFacilityInformation.specifications[0].capacity = null
}
}
Opschonen #2
Helaas was dit niet voldoende. Er zaten hele specifieke gevallen tussen. Zo had je de array specifications. In sommige gevallen bestond hij wel, maar had hij geen capacity. In andere gevallen bestond specifications alleen als een lege variable. In weer andere gevallen bestond de array specifications, had hij een waarde op index [0], maar was de waarde null. Ik moest hierdoor verschillende if statements schrijven om dit op te vangen.
cleanLocation herschreef ik zo:
const cleanLocation = function(parkingGarage){
if (typeof parkingGarage.parkingFacilityInformation.operator.postalAddress == 'undefined'){
parkingGarage.parkingFacilityInformation.operator.postalAddress = {province: null}
}
}
naar:
const cleanLocation = function(parkingGarage){
if (typeof parkingGarage.parkingFacilityInformation.operator == 'undefined'){
console.log('changing')
parkingGarage.parkingFacilityInformation = {operator: {postalAddress: {province: null}}}
}
else if (typeof parkingGarage.parkingFacilityInformation.operator.postalAddress == 'undefined'){
parkingGarage.parkingFacilityInformation.operator.postalAddress = {province: null}
}
}
cleanCapacity was extra irritant. Hier zaten, doordat je met een array werkt, veel specifieke gevallen tussen. Deze heb ik uiteindelijk herschreven van:
const cleanCapacity = function(parkingGarage){
if (typeof parkingGarage.parkingFacilityInformation.specifications[0].capacity == 'undefined'){
parkingGarage.parkingFacilityInformation.specifications[0].capacity = null
}
}
naar:
const cleanCapacity = function(parkingGarage){
if (typeof parkingGarage.parkingFacilityInformation.specifications == 'undefined'){
parkingGarage.parkingFacilityInformation = {specifications: []}
parkingGarage.parkingFacilityInformation.specifications[0] = {capacity: null}
}
else if (typeof parkingGarage.parkingFacilityInformation.specifications[0] == 'undefined' || null){
parkingGarage.parkingFacilityInformation.specifications[0].capacity = null
}
else if (parkingGarage.parkingFacilityInformation.specifications[0] == null){
parkingGarage.parkingFacilityInformation.specifications[0] = {capacity: null}
}
else if (typeof parkingGarage.parkingFacilityInformation.specifications[0].capacity == 'undefined'){
parkingGarage.parkingFacilityInformation.specifications[0].capacity = null
}
}
Before-after
En na deze functie uit te voeren, had ik een nieuw bestand met hierin alleen de data die ik persoonlijk nodig had.
Uiteindelijk ging ik voor 1 garage van dit:
"parkingFacilityInformation": {
"description": "P+R Station Appingedam (Appingedam)",
"identifier": "fc749565-1fe9-42f0-920a-3b4e718d62f9",
"validityStartOfPeriod": 1430784000,
"validityEndOfPeriod": null,
"name": "P+R Station Appingedam (Appingedam)",
"limitedAccess": false,
"specifications": [
{
"validityStartOfPeriod": 1574423959,
"capacity": 22,
"chargingPointCapacity": 0,
"disabledAccess": false,
"minimumHeightInMeters": 0,
"usage": "Park & Ride"
}
],
"operator": {
"validityStartOfPeriod": 1430784000,
"validityEndOfPeriod": null,
"name": "Appingedam",
"administrativeAddresses": [
{
"emailAddresses": [
"[email protected]"
],
"streetName": "Wilhelminaweg",
"houseNumber": "14",
"zipcode": "9901CM",
"city": "Appingedam",
"province": "Groningen",
"country": "Nederland",
"phoneNumbers": [
"140596"
]
}
],
"postalAddress": {
"emailAddresses": [
"[email protected]"
],
"streetName": "Postbus",
"houseNumber": "15",
"zipcode": "9900AA",
"city": "Appingedam",
"province": "Groningen",
"country": "Nederland",
"phoneNumbers": [
"140596"
]
},
"url": "www.appingedam.nl"
},
"contactPersons": [],
"accessPoints": [
{
"validityStartOfPeriod": 1433116800,
"isVehicleEntrance": true,
"isVehicleExit": true,
"isPedestrianEntrance": false,
"isPedestrianExit": false,
"alias": "",
"accessPointLocation": [
{
"validityStartOfPeriod": 1433116800,
"coordinatesType": "WGS84",
"latitude": 53.325531,
"longitude": 6.86209
}
],
"accessPointAddress": {
"emailAddresses": [
"[email protected]"
],
"streetName": "Stationsweg",
"houseNumber": "36",
"zipcode": "9901CS",
"city": "Appingedam",
"province": "Groningen",
"country": "Nederland",
"phoneNumbers": [
"140596"
]
}
}
],
"openingTimes": [
{
"openAllYear": true,
"periodName": "",
"startOfPeriod": 1433116800,
"exitPossibleAllDay": true,
"entryTimes": []
}
],
"tariffs": [
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Tue"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
},
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Thu"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
},
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Mon"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
},
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Fri"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
},
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Wed"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
},
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Sat"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
},
{
"tariffDescription": "P+R, gratis parkeren",
"periodName": "",
"startOfPeriod": 1430838893,
"endOfPeriod": null,
"validityDays": [
"Sun"
],
"validityFromTime": {
"h": 0,
"m": 0,
"s": 0
},
"validityUntilTime": {
"h": 23,
"m": 59,
"s": 59
},
"intervalRates": [
{
"validityStartOfPeriod": 1430838893,
"validityEndOfPeriod": null,
"charge": 0,
"chargePeriod": 1,
"durationType": "Minutes",
"durationFrom": 0,
"durationUntil": -1
}
]
}
],
"paymentMethods": [],
"specialDays": [],
"parkingRestrictions": []
}
Naar dit:
{
"location":"Groningen",
"capacity":22,
"disabledAccess":false
},
Probleem #2
Doordat ik deze week weinig slaap heb kunnen krijgen en vaak tot middernacht bezig was, heb ik per ongeluk de verkeerde boolean opgehaald voor de toegankelijkheid van de parkeerplaatsen. In plaats van disabledAccess had ik limitedAccess opgehaald. Ik moest daarom nogmaals de data gaan fetchen van de dataset.
Ophalen van de data #3
Eerst herschreef ik het fetchen op de volgende manier:
const dataCollectionArray = dataCollection.map(garage => {
cleanLocation(garage)
cleanCapacity(garage)
return {
location: garage.parkingFacilityInformation.operator.postalAddress.province,
capacity: garage.parkingFacilityInformation.specifications[0].capacity,
disabledAccess: garage.parkingFacilityInformation.limitedAccess
}
})
Naar:
const dataCollectionArray = dataCollection.map(garage => {
return {
location: getLocationIfExist(garage),
capacity: getCapacityIfExist(garage),
disabledAccess: getDisabledAccessIfExist(garage)
}
})
return dataCollectionArray
}
Zoals u kunt zien heb ik dit keer getters geschreven om de data op te halen. Hiervoor filterde ik de data eerst nog apart en sloeg deze op in variabelen. Nu doe ik dat ook, maar pas bij het opvragen. Naast dat dit nu beter met een functional pattern geschreven is, is dit ook veel korter en leesbaarder. De return waarde van getDisabledAccesIfExist() moest ik nog wel aanpassen om de goede boolean waarde te ontvangen. Ik herschreef de return van
return garage.parkingFacilityInformation.limitedAccess
naar:
return garage.parkingFacilityInformation.specifications[0].disabledAccess