360-gradenfoto’s van de hele stad: op ontdekkingsreis in Amsterdam

Gepubliceerd op 21 maart 2019 door Bert Spaan.

Welke trotse vogel op de Hoogte Kadijk herinnert aan een roemruchte brouwerij die daar ooit stond? Wat staat er in de letterbak op de gevel van de Dubbeltjespanden aan de Mauritskade? Wie houdt braaf de wacht op het Hoofddorpplein? De antwoorden vind je op data.amsterdam.nl, waar je vanaf bijna ieder punt in de stad 360 graden in het rond kunt kijken.

Op data.amsterdam.nl zijn 360-gradenfoto’s van de hele gemeente Amsterdam beschikbaar (en zelfs daarbuiten). Vanaf elke weg, elk fietspad, elke gracht en elk kanaal is gefotografeerd; met je browser ben je zo in Landelijk Noord, in het Amsterdamse Bos, of in de havens van Westpoort tussen de olietankers. Deze panoramafoto’s van Amsterdam en hun metadata zijn beschikbaar als open data. In deze post laten we zien hoe je deze afbeeldingen kunt gebruiken, en, leuker nog, wat je ermee kan maken!

Voor de dagelijkse werkzaamheden van veel gemeentemedewerkers is het van belang om te weten hoe de stad eruit ziet. Samen met kaarten en luchtfoto’s helpen panoramafoto’s, gemaakt vanaf de straat op ooghoogte, bijvoorbeeld bij het beoordelen van vergunningsaanvragen en het herinrichten van straten en kruispunten Ze zouden daarvoor natuurlijk Google Street View kunnen gebruiken. Maar het Google-autootje komt niet in parken, op fiets- en wandelpaden, in stegen, op de grachten of in wijken in aanbouw. Bovendien bepaalt Google zélf wanneer ze ergens nieuwe foto’s maken, en mogen de foto’s niet voor andere doeleinden gebruikt worden.

Sinds 2016 heeft Amsterdam een eigen panoramacamera die bovenop een auto of een schip gemonteerd kan worden. Straat voor straat en gracht voor gracht wordt de stad elk jaar opnieuw vastgelegd. Sinds 2016 zijn er meer dan 2,5 miljoen foto’s gemaakt en is meer dan 14.000 kilometer weg en kade gefotografeerd. Ook de gemeentes Weesp, Almere en Amstelveen doen mee met dit project en maken gebruik van de camera en de foto’s. Na een dag op straat of op het water worden de afbeeldingen en GPS-data uitgelezen waarna ze automatisch op data.amsterdam.nl belanden. Deze methode maakt het mogelijk snel en efficient grote stukken van de stad in één dag te fotograferen.

Panoramafoto van de kerk van Ransdorp in data.amsterdam.nl
Panoramafoto van de kerk van Ransdorp in data.amsterdam.nl.

Al voor de uitvinding van GPS en digitale fotografie was er uiteraard ook behoefte aan vergelijkbare foto’s. De gemeente New York heeft tweemaal, eind jaren dertig en halverwege de jaren tachtig, fotografen op pad gestuurd om elk gebouw en perceel te laten fotograferen. Deze foto’s zijn onlangs gedigitaliseerd en online te bekijken. Zie bijvoorbeeld Every Building on Every Block: A Time Capsule of 1930s New York en 80s.NYC.

Terug naar Amsterdam: we tonen inmiddels een panoramafoto bij elke selectie en zoekopdracht in het dataportaal. Maar omdat de foto’s beschikbaar zijn als open data kunnen we er veel meer mee doen. Zo testen we bijvoorbeeld software om automatisch verkeersborden en andere objecten in de openbare ruimte op de foto’s te herkennen. Maar het leukste is misschien wel om de foto’s te gebruiken om Amsterdam te ontdekken en op plekken te komen waar je anders niet snel komt.

Als je maar lang genoeg met Google Street View de wereld over reist kom je de gekste dingen tegen, zoals arrestaties, ongelukken en ontsnapte dieren. Dit soort Street View-reisreportages zijn populair, zie bijvoorbeeld 9-Eyes en A Series of Unfortunate Events. Ook erg leuk is GeoGuessr, een website die je op een willekeurige plek in de wereld dropt. Aan de hand van het straatbeeld moet je raden waar je bent. Hoe dichterbij, hoe beter. Als je geluk hebt zie je verkeersborden maar soms ben je aangewezen op de vegetatie of bouwstijl van huizen.

In deze post gaan we een Amsterdamse versie van GeoGuessr maken, zonder Google, maar met open source-tools en Amsterdamse open data!

GueGuessr
Raad waar op de wereld je bent: GeoGuessr met Google Street View.

Op ontdekkingsreis in Amsterdam

Aan de slag! Met de webservices van data.amsterdam.nl en wat kennis van webtechnologie en JavaScript-frameworks maken we zo ons eigen online panorama-dropping-spel. Hoe beginnen we?

  1. Ten eerste moeten we een manier vinden om steeds van een willekeurige plek in Amsterdam een panoramafoto te downloaden;
  2. Deze foto kunnen we vervolgens laten zien met Marzipano;
  3. Leaflet zorgt dat spelers kunnen raden waar ze zijn, met een kaartlaag uit het dataportaal;
  4. Met Turf.js kunnen we uitrekenen of de speler in de buurt is van de echte fotolocatie;
  5. Een simpele webapplicatie, gemaakt met Vue, voegt alles samen.
  6. Klaar! We kunnen op ontdekkingsreis in Amsterdam!

De panoramafoto van een willekeurige plek in Amsterdam

De eerste stap is gelijk de moeilijkste. De panoramafoto’s kunnen worden gezocht en benaderd via de REST API op api.data.amsterdam.nl/panorama. Met deze API is het ook mogelijk om de foto te vinden die het dichtst bij een bepaalde geografische locatie ligt. In het voorbeeld hieronder zie je hoe dit gaat met JavaScript en axios, maar het kan uiteraard met elke willekeurige programmeertaal:

const lonLat = [4.96165, 52.38250]
const apiUrl = 'https://api.data.amsterdam.nl/panorama/panoramas/'
const url =  `${apiUrl}?near=${lonLat.join(',')}&srid=4326&radius=250&page_size=1`

return axios.get(url)
  .then((response) => response.data)
  .then((data) => {
    // Als er een foto gevonden is hebben we nu
    // alle metadata en links naar de foto:
    console.log(data)
  })

Je kunt de JSON-data die deze API-call teruggeeft ook bekijken in de browser. Als je met de API wilt spelen en de foto’s en de data direct wilt bekijken kun je ook dit Observable-notebook gebruiken.

Via de Panorama-API is het mogelijk om een foto te zoeken in de buurt van een locatie. Echter, het kiezen van een willekeurige locatie in de gemeente Amsterdam is niet zo eenvoudig. De meest simpele oplossing is om steeds een punt te kiezen in de rechthoek waar de gemeentegrenzen van Amsterdam precies inpassen, grofweg tussen Uitdam, Nederhorst den Berg, Hoofddorp en Assendelft.

Omdat dit gebied veel groter is dan de gemeente zelf, zul je relatief vaak een punt kiezen wat ver buiten de stad ligt. De foto hier het dichtst bij is er dan een van de rand van de stad. Deze methode zal er dus voor zorgen dat je steeds aan de rand van de stad wordt neergezet, middenin het groen.

Ook als we de precieze omtrek van de gemeente gebruiken om een willekeurig punt te kiezen hebben we hetzelfde probleem: er zijn niet overal panoramafoto’s gemaakt, en ook dan zullen de lege gebieden ons te vaak op een afgelegen plek afzetten. We zouden ook een kleine zoekradius kunnen gebruiken, en de zoekopdracht kunnen blijven herhalen tot een foto gevonden is, maar dat is helemaal een onaantrekkelijke oplossing.

Alleen door alle routes te bekijken waarlangs foto’s genomen zijn, en een willekeurig punt te kiezen langs een van deze routes, kunnen we op de correcte manier een willekeurige panoramafoto kiezen. Deze methode levert bovendien ook een mooie datavisualisatie van alle gefotografeerde routes op!

Via de Panorama-API of via de WFS-server kunnen we alle opnamelocaties downloaden. Aan de hand van het ID en het tijdstip van een foto is af te leiden welke foto’s bij elkaar horen en achter elkaar genomen zijn. Voor deze post hebben we speciaal een dataset gemaakt van alle routes. Deze dataset staat op GitHub en bevat GeoJSON-bestanden en vector tiles.

Visualisatie van alle routes waarlangs panoramafoto’s genomen zijn.

Vervolgens kunnen we deze routes openen met QGIS, van de lijnen polygonen maken, deze polygonen samenvoegen en het stuk dat niet binnen de gemeente Amsterdam valt verwijderen. Met QGIS kan dit op de volgende manier:

  1. Download sequences.geojson van GitHub), en open dit bestand met QGIS;
  2. Kies VectorGeoprocessing ToolsBuffer… om een rand van een aantal meter dik om de lijnen te genereren en er zo polygonen van te maken. Hoe dikker deze rand, hoe vaker we in gebieden terecht zullen komen waar geen foto’s gemaakt zijn. Als de rand te dun is worden routes waar meerdere keren foto’s zijn genomen maar niet precies op elkaar liggen niet samengevoegd, en zullen straten die vaker gefotografeerd zijn vaker worden gekozen. In dit voorbeeld hebben we voor een afstand van 200 meter gekozen. Belangrijk: de bewerkingen kunnen het beste worden uitgevoerd in EPSG:28992 (ofwel Rijksdriehoekscoördinaten), alle afstanden zijn dan in meters.
  3. Kies vervolgens VectorGeoprocessing ToolsDissolve… om de losse polygonen samen te voegen tot één vorm.
  4. Open gemeente-amsterdam.geojson (wederom te vinden in het GitHub-project), met VectorGeoprocessing ToolsIntersection… kunnen we het stuk van de samengevoegde polygonen die buiten Amsterdam liggen verwijderen.
Panoramaroutes in QGIS
Alle panorama-routes, samengevoegd, en met een extra rand van 200 meter breed.

We zijn er bijna! Het enige dat nog ontbreekt is een algoritme om een willekeurige punt in een polygoon te selecteren. Voor rechthoeken en driehoeken is dit eenvoudig, maar complexere veelhoeken is er een extra stap nodig. Door de polygoon eerst op te delen in driehoeken, en bij elke zoekopdracht naar een nieuwe panoramafoto eerst een willekeurige driehoek te kiezen en dan een willekeurig punt in die driehoek, hebben we een efficiente en elegante manier om een willekeurige panoramafoto in Amsterdam te vinden. Let wel: we kunnen niet zomaar een willekeurige driehoek te kiezen, maar we moeten grotere driehoeken vaker kiezen dan kleine. Wie meer wil lezen over dit algoritme verwijzen we wederom naar een Observable-notebook.

triangulation
De oppervlakte onderverdeeld in driehoeken met een Delaunay-triangulatie .

Het opdelen van polygonen in driehoeken kan op veel manieren. Het kan in de browser met JavaScript, bijvoorbeeld met Earcut. Wij kozen wederom voor QGIS:

  1. Maak eerst losse punten van de polyoon: VectorGeometry ToolsExtract Vertices….
  2. Daarna kan van deze punten een Delaunay-triangulatie worden berekend: VectorGeometry ToolsDelaunau Triangulation…
  3. Als laatste gebruiken we weer een Intersection om de gaten uit de triangulatie te knippen.

Het resultaat kunnen we nu exporteren als GeoJSON, en gebruiken in de webapplicatie. Kies altijd de projectie EPSG:4326 als je GeoJSON-bestanden exporteert voor gebruik op het web!

Marzipano, Leaflet en Turf.js

De webapplicatie bestaat uit twee onderdelen: de gebruikers zien de panoramafoto zo groot mogelijk in beeld. Rechtsonder in beeld staat een kleine kaart waarmee de locatie van de foto kan worden gezocht. Marzipano kan als volgt worden toegevoegd:

var marzipanoElement = document.getElementById('marzipano')

var viewerOptions = {
  stageType: null,
  stage: {
    preserveDrawingBuffer: true,
    width: 960
  }
}

var viewer = new Marzipano.Viewer(marzipanoElement, viewerOptions)

// panoramaImage bevat een object van de Panorama-API
var source = Marzipano.ImageUrlSource
  .fromString(panoramaImage.cubic_img_pattern, {
    cubeMapPreviewUrl: panoramaImage._links.cubic_img_preview.href
  })

var initialView = {
  yaw: Math.random() * 360,
  fov: 90 * Math.PI / 180,
  pitch: 0,
}

var viewLimiter = Marzipano
  .RectilinearView
  .limit.traditional(12 * 1024, Math.PI / 2)

var view = new Marzipano.RectilinearView(initialView, viewLimiter)

var levelPropertiesList = [{
  tileSize: 256,
  size: 256,
  fallbackOnly: true
}, {
  tileSize: 512,
  size: 512
}, {
  tileSize: 512,
  size: 1024
}, {
  tileSize: 512,
  size: 2048
}]

var scene = this.viewer.createScene({
  source,
  geometry: new Marzipano.CubeGeometry(levelPropertiesList),
  view,
  pinFirstLevel: true
})

scene.switchTo()
Een Amsterdamse panoramafoto, weergegeven met Marzipano
Een Amsterdamse panoramafoto, weergegeven met Marzipano (waar is dit?).

Op het dataportaal zijn naast webservices ook kaarttiles en luchtfoto’s te vinden. Met Leaflet gebruik je deze zo:

var mapElement = document.getElementById('map')
var map = L.map(mapElement).setView([52.369, 4.922], 12)

var tileUrl = 'https://{s}.data.amsterdam.nl/topo_wm_light/{z}/{x}/{y}.png'

L.tileLayer(tileUrl, {
  subdomains: ['t1', 't2', 't3', 't4'],
  maxZoom: 19
}).addTo(map)

Als de speler denkt te weten waar de foto genomen is kunnen we met Turf.js uitrekenen wat de afstand in meters is tussen deze locatie en de plek waar de foto genomen is.

Waar ben ik?

Nu moeten alles wat hierboven beschreven is samenvoegen tot een webapplicatie. Uiteindelijk heeft ons spel ook een goede naam nodig, maar laten we voorlopig Waar ben ik? als naam gebruiken. Voor wie precies wil weten hoe de applicatie, gemaakt met Vue.js, in elkaar zit kan op GitHub kijken.

We hebben al genoeg technologie besproken in deze post, we kunnen ‘t spel beter gaan spelen: bertspaan.nl/waar-ben-ik.

Waar ben ik?
Waar ben ik?

Wie wil meehelpen om van Waar ben ik? een multiplayer-spel te maken? Stuur een bericht op Twitter!