Mapping of the ages of buildings in Seoul
South Korea has been releasing lots of great open data through its National Spatial Data Infrastructure Portal (NSDI). It includes more than 750,000 buildings in Seoul, including their shape, height, neighborhood and approval date. Inspired by built:LA, I decided to make a dynamic map showing the age of buildings in Seoul with Tangram.
When the map is zoomed in, you can see the shape and height of individual buildings, colored by the year of their construction. You can also click on individual building to find out more details. As you zoom out, the map changes to dots, and then shows age data aggregated by neighborhood. Click on a color block in the legend to just show buildings from a specific decade!
Can you guess which part of Seoul is the old downtown area by looking at this map?
FF development log
Post-process of data and custom tiles
The original data from NSDI was in shapefile format. In QGIS, I converted it into a huge GeoJSON file. The projection of the shapefile was slightly off and encoding of the data was euc-kr
, so I re-encoded to utf-8
, and reprojected it when I exported it to GeoJSON.
The exported GeoJSON was over 300mb — too big to be loaded at once — so it had to be tiled. All of these datasets were built into tiles with TileStache, hosted in S3 buckets. To make loading time for each tile quicker, we split it into 3 datasets based on building age.
In case you need inspiration, here is how I configured TileStache to generate the static vector tile set. If your file is not as big as the one used here, but still big enough to be tiled, geojson2mvt looks like a good option.
Looking at the shapes of buildings is interesting, but only above zoom level 15. For zoom 13-14, John Oram and I made centroids of each building. QGIS was surprisingly slow, but thanks to turf.js we were able to quickly generate them and strip out extra fields we didn’t need. (Watch out for null geometries!)
As I played around with the slippy map, it made me want to see how old buildings were by neighborhood. Yet again, NSDI delivers! I grabbed their neighborhood data and aggregated the building age within each “dong”, or neighborhood. There are many ways to do this — I approached the problem using a simple node script.
Colors and interactions
I used the Virdis color palette to have linear, but distinctive colors for each decade. (I found out about this through a kind person on Twitter. Who said Twitter is a waste of life?) These colors could have be added as a global variable in yaml, but they were saved as a hex value array in a separate script so that they could also be used on the javascript side. You can include the script you need with scripts
attribute in Tangram.
sources:
seoul-buildings-2345:
type: GeoJSON
url: https:/tile/address/{z}/{x}/{y}.geojson
scripts: ['./virdisColors.js']
In virdisColors.js
, we define the global variable to which the array of colors is assigned.
/* virdisColors.js */
var viridis = ["#440154", "#482475", "#414287", "#355e8d", "#2a768e", "#218e8d", "#21a585", "#3dbc74", "#70cf57", "#b0dd2f"]
We can also access virdis
outside Tangram by including viridis.js
:
<script src='virdis.js'></script>
<script>
for (var i = viridis.length-1; i > -1; i--) {
var colorBlock = L.DomUtil.create('div');
colorBlock.style.backgroundColor = viridis[i];
}
</script>
Finally, the index number of the array was used to color each decade:
global:
matching:
filter: global.filter
draw:
polygons:
color: |
function () {
var arr = feature['year'].slice(0, 3);
var age = 201 - parseInt(arr);
return viridis[age];
}
layers:
buildings-2345:
data: {source: seoul-buildings-2345}
matching: global.matching
draw:
polygons:
interactive: true
order: 3001
/* This color will be used when the feature is not selected */
color: #333
The interactions between UI components and the map were built using Tangram’s global variable and filtering functions. For example, to highlight a specific decade, the script sets a global variable in Tangram when the color block is clicked on.
colorBlock.addEventListener('click', function () {
scene.config.global.age = this.getAttribute('year');
/* Tangram config needs to be updated */
scene.updateConfig();
})
Then the filter block of yaml (which always returns true
when no age variable is set) starts returning only the features that match the selected decades.
filter: |
function () {
if (!global.age) {
return true;
} else {
/* The date of approval is saved in yyyymmdd string format */
var featureYear = feature['year'].slice(0, 3);
return (featureYear == global.age);
}
}
I made sure all the text we needed was in one JSON file so it could be easily swapped into other languages. You can check out this and all the front-end parts of the map in its repo.
Find building data for your city and use Tangram to make your own map!