Code flow - Jelmerovereem/frontend-data GitHub Wiki

First of all I need to fetch geoData for mapping the Netherlands.

const url = "https://cartomap.github.io/nl/wgs84/gemeente_2020.topojson"; // the geodata for the map

fetch(url)
.then(response => response.json())
.then((data) => {
	var stadData = topojson.feature(data, data.objects.gemeente_2020); // Curran dataviz https://www.youtube.com/watch?v=Qw6uAg3EO64&list=PL9yYRbwpkykvOXrZumtZWbuaXWHvjD8gi&index=15
	renderMap(stadData); // render the mapdata
});

After that I create the projection and pathGenerator:

const projection = d3.geoMercator()
	.center([5.116667, 52.17]) // https://github.com/mbergevoet/frontend-data/blob/master/frontend-data/index.js#L61
	.scale(6000)
	.translate([width/2, height/2]); // center the map based on the width and height from the svg element
const pathGenerator = d3.geoPath().projection(projection);

Now I can render the map through my own function renderMap():

function renderMap(data) {
	/* Create the base map */
	group.selectAll("path")
	.data(data.features)
	.enter()
	.append("path")
	.attr("d", pathGenerator)
	
	/* Add the "gemeente" names */
	group.selectAll("text") //https://stackoverflow.com/questions/13897534/add-names-of-the-states-to-a-map-in-d3-js
	.data(data.features)
	.enter()
	.append("svg:text")
		.text(obj => obj.properties.statnaam)
		.attr("fill", "white")
		.attr("x", (d) => {return pathGenerator.centroid(d)[0]})
		.attr("y", (d) => {return pathGenerator.centroid(d)[1]})
		.attr("text-anchor", "middle")
		.attr("font-size", "1pt")

	/* Add title tooltip */
	group.selectAll("path")
	.append("title")
		.text(obj => obj.properties.statnaam)	
}

Now the map is present!
image

Fetching parking data

Now I have to get the parking data from the RDW.

First I fetch the dataset with the areaId's and area descriptions(the same goes for the capacity dataset):

let garageData;

fetch('https://opendata.rdw.nl/resource/adw6-9hsg.json?$limit=8352&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
	garageData = data;
});

//capacity data
let capacityData;

fetch('https://opendata.rdw.nl/resource/b3us-f26s.json?$limit=1567&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
	capacityData = data;
})

Now it's getting interesting, I want to fetch the data for the parking locations, clean and convert this data.

Before cleaning: POINT (6.8635054 53.325557274)
After cleaning: long:6.8635054, lat: 53.325557274

I first fetch the dataset:

fetch('https://opendata.rdw.nl/resource/nsk3-v9n7.json?$limit=6101&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
	//do something
});

After that I have to loop over the parking spots and clean this data, my classmate(Stan Brankras) has written functions for cleaning this data:
functions for cleaning data (Stan Brankras).

I myself have written code to clean up coordinates in the previous course. But the code from Stan was specifically written for this.

fetch('https://opendata.rdw.nl/resource/nsk3-v9n7.json?$limit=6101&$$app_token=zI34snM8XBhNRzxL50vrTeOLA')
.then(response => response.json())
.then((data) => {
	data.forEach((garage) => {
		let coordinateObj;
		if (garage.areageometryastext != "" && garage.areageometryastext != undefined) {
			if (!Number.isNaN(getCenterCoord(garage.areageometryastext)[0]) || !Number.isNaN(getCenterCoord(garage.areageometryastext)[1])) {
			coordinateObj = { // create object with areaid and coordinates
				areaId: garage.areaid,
				long: getCenterCoord(garage.areageometryastext)[0],
				lat: getCenterCoord(garage.areageometryastext)[1]
			}
			coordinatesArray.push(coordinateObj) // push every object into array
		}
		}			
	});
});

Now I have to combine the data from the 3 separate datasets:

function combineData(garageData, garageLocatieData, variableData) {
  let outcomeData=[];

  garageLocatieData.forEach((garage)=> {
      var result=garageData.find(obj=> {
          return obj.areaid===garage.areaId;
        }

      );

      if (result===undefined) {
        var garageObj= {
          areaId: garage.areaId,
          long: garage.long,
          lat: garage.lat,
          areaDesc: "onbekend"
        }
      }

      else {
        var garageObj= {
          areaId: garage.areaId,
          long: garage.long,
          lat: garage.lat,
          areaDesc: result.areadesc
        }
      }

      outcomeData.push(garageObj);
    }

  );

//check option and edit data
  outcomeData = checkOption(variableData, outcomeData);

  renderPoints(outcomeData)
}
function checkOption(variableData, garagesData) {
	if (variableData.length > 2000) {
		//paid/free dropdown chosen
		garagesData.forEach((garage) => {
			var paidObj = variableData.find(obj => {
				return obj.areaid === garage.areaId;
			})
			if (paidObj === undefined || paidObj.usageid === undefined) {
				garage.paid = "onbekend";
			} else {
				garage.paid = paidObj.usageid;
			}
		})
	} else {
		//capacity dropdown chosen
		garagesData.forEach((garage) => {
			var capacityObj = variableData.find(obj => {
				return obj.areaid === garage.areaId
			})
			if (capacityObj === undefined || capacityObj.capacity === undefined) {
				garage.capacity = "onbekend";	
			} else {
				garage.capacity = capacityObj.capacity;
			}			
		})
	}

	return garagesData;
}

After I have combined the data I can show the parking spots on the map with renderPoints(). With .join() I can easily say what needs to happen when data gets updated through enter and exit:

function renderPoints(coordinates) {
	let circle = group.selectAll("circle");

	circle = circle
		.data(coordinates, d => d.areaId)
		.join(
			enter => enter.append("circle")
				.attr("r", 4)
				.attr("transform", (obj) => {
					return `translate(${projection([obj.long, obj.lat])})`
				})
				.attr("fill", (obj) => {
					if (dropdown.value === "paid/free") {
						if (/VERGUNNING|VERGUNP|VERGUN-ALG|VERGUN-MV/.test(obj.paid)) {
							return "red"
						} else if (/BETAALDP|GARAGEP/.test(obj.paid)) {
							return "orange"
						} else if (/onbekend/.test(obj.paid)) {
							return "grey"
						} else {
							return "green"
						}
					} else if (dropdown.value === "capacity") {
						if (obj.capacity <= 600) {
							return "red"
						} else if (obj.capacity > 600 && obj.capacity <= 1000) {
							return "orange"
						} else if (obj.capacity > 1000) {
							return "green"
						} else {
							return "grey"
						}
					}
				})
				.attr("opacity", (d) => {
					if (dropdown.value === "paid/free") {
						if (/onbekend/.test(d.paid)) {
							return .1
						} else {
							return 1
						}
					} else if (dropdown.value === "capacity") {
						if (d.capacity === "onbekend") {
							return .1
						} else {
							return 1
						}
					}
				}),
				exit => exit
					.attr("fill", "white")
					.call(exit => exit.transition().duration(500)
						.attr("opacity", 0)
						.remove())			
			)
}

After that I add a tooltip:

const tooltip = document.querySelector(".tooltip");
	group.selectAll("circle").on("mouseover", (event, obj) => {
		tooltip.innerHTML = obj.areaDesc + "<br>";
		if (obj.paid === undefined) {
			tooltip.innerHTML += "capacity: " + obj.capacity;	
		} else {
			tooltip.innerHTML += "Paid/free: " + obj.paid;	
		}
		
		

		tooltip.style.left = (event.pageX) + "px";
		tooltip.style.top = (event.pageY + 10) + "px";
		tooltip.classList.add("focus");
		tooltip.style.opacity = "1";

		
	})
	.on("mouseout", () => {
		tooltip.style.opacity = "0";
	});

For the interactivity, I need to change the data on the change of a dropdown:

function updateData() {
	const variable = this.value;
	if (variable === "capacity") {
                /* Set bar */
		document.querySelector(".first").innerText = "0";
		document.querySelector(".last").innerText = "10";
		combineData(garageData, coordinatesArray, capacityData);
	} else if (variable === "paid/free") {
		document.querySelector(".first").innerText = "Betalen";
		document.querySelector(".last").innerText = "Gratis";
		fetch('https://opendata.rdw.nl/resource/adw6-9hsg.json?$limit=8352&$$app_token=zI34snM8XBhNRzxL50vrTeOLA') // fetch the paid/free data
		.then(response => response.json())
		.then((paidData) => {
			combineData(garageData, coordinatesArray, paidData)
		})
	}
}

dropdown.addEventListener("change", updateData);