Skip to content

Commit e12d4b5

Browse files
committed
voorbeelden verplaatst
1 parent 1b079e7 commit e12d4b5

File tree

6 files changed

+282
-26
lines changed

6 files changed

+282
-26
lines changed

docs/tiles/Maak een kaart met OGC API - Tiles.md

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@ In deze casus nemen we jou stap voor stap mee:
1515
4. Voeg OGC API - Features toe aan jouw kaart
1616
5. Evalueer het eindresultaat
1717

18-
## Zelf code runnen
19-
20-
Maak gebruik van een platform of IDE naar keuze om code uit te voeren. Hieronder een uitleg voor VSCode, maar je kunt natuurlijk zelf een keuze maken.
21-
22-
- **Fork de Git repository**
23-
- **Clone de Git repository**
24-
2518
## Bekijk een voorbeeld
2619
Leerdoelen:
2720

@@ -30,7 +23,7 @@ Leerdoelen:
3023

3124
We gaan eerst een voorbeeld bekijken.
3225

33-
- **Bekijk** `voorbeelden/tiles_maplibre/index.html`
26+
- **Bekijk** [voorbeelden/tiles/index.html](../../voorbeelden/tiles/index.html)
3427
- **Bekijk de kaart zelf, zoom eens in en uit**
3528

3629
Dit is een web viewer die gemaakt is met de library MapLibre. Deze kaart maakt gebruik van de OGC API – Tiles van de BRT Achtergrondkaart: <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1>
@@ -50,9 +43,7 @@ Merk op dat er onder andere een `main.js` en `https://api.pdok.nl/kadaster/brt-a
5043

5144
- **Zoom eens in en uit**
5245

53-
Merk op dat er nu veel bestanden worden ingeladen, bijvoorbeeld `262?f=mvt`. De volledige URL van is: <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/tiles/WebMercatorQuad/9/168/262?f=mvt>
54-
55-
Dit zijn de tiles (kaarttegels) zelf.
46+
Merk op dat er nu veel bestanden worden ingeladen, bijvoorbeeld `262?f=mvt`. Dit bestand is 1 tile (kaarttegel). De volledige URL van deze tile is: <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/tiles/WebMercatorQuad/9/168/262?f=mvt>
5647

5748
Je kunt nu zien dat deze web viewer de BRT Achtergrondkaart gebruikt, en meer specifiek de WebMercatorQuad TileMatrixSet. Dat zie je aan de URL’s van de tiles. En je ziet dat de standaard style wordt gebruikt voor deze tilematrixset. Dat zie je aan de style URL die na `main.js` werd ingeladen: <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/styles/standaard__webmercatorquad?f=json>
5849

@@ -72,51 +63,59 @@ De URL is als volgt opgebouwd:
7263

7364
### Bekijk nu de code van dichtbij
7465

75-
- **Open de repository in VSCode of een andere IDE**
66+
Maak gebruik van een code editor of IDE naar keuze om code te bekijken en uit te voeren. Hieronder een uitleg voor VSCode, maar je kunt natuurlijk zelf een keuze maken.
67+
68+
- **Fork de Git repository**
69+
- **Clone de Git repository**
70+
- **Open de repository**
7671

7772
Laten we deze code runnen zodat we de applicatie eerst in de browser kunnen bekijken:
7873

79-
- **Start een web server, bijvoorbeeld met python:**
74+
- **Start lokaal een web server, bijvoorbeeld met python:**
8075

8176
```
8277
> python -m http.server
8378
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
8479
```
85-
- **Bekijk nu** `voorbeeld\index.html` **in de browser**
80+
- **Bekijk nu [voorbeelden/tiles/index.html](../../voorbeelden/tiles/index.html) in de browser**
8681

8782
![voorbeeld van kaart die met maplibre is gemaakt](msedge_gjEJAEdbND.png)
8883

89-
Laten we in de code duiken:
84+
Laten we nu eens de code bekijken in een editor:
9085

91-
- **Bekijk** `voorbeeld\index.html`
92-
- **Bekijk** `voorbeeld\main.js`
86+
- **Bekijk** `voorbeelden\tiles\index.html`
87+
- **Bekijk** `voorbeelden\tiles\main.js`
9388
- **Bekijk** <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/styles/standaard__webmercatorquad?f=json>
9489

9590
Als het goed is, zie je in de code `index.html` een `div` met als id `map`.
9691

97-
In `main.js` zie je dat er bij `container` dat er naar diezelfde `map` wordt verwezen. In dit javascript bestand wordt allereerst de `mmaplibre-gl` library geïmporteerd. Daarna wordt de kaart gedefinieerd:
92+
In `main.js` zie je dat er bij `container` dat er naar diezelfde `map` wordt verwezen. In dit javascript bestand wordt allereerst de `mmplibre-gl` library geïmporteerd. Daarna wordt de kaart gedefinieerd:
9893

9994
- `container`: `map` object in `index.html`
100-
- `style`: verwijst naar een `style.json` bestand. Hierin wordt gedefinieerd hoe de tiles gevisualiseerd worden
95+
- `style`: verwijst naar een json-bestand: <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/styles/standaard__webmercatorquad?f=json>. Hierin wordt gedefinieerd hoe de tiles gevisualiseerd worden
10196
- `center`: bepaalt het startmiddenpunt van de kaart (x- en y-coördinaten)
10297
- `zoom`: bepaalt het startzoomlevel van de kaart
10398
- `minZoom`: bepaalt het maximale niveau dat je mag uitzoomen
10499
- `maxZoom`: bepaalt het maximale niveau dat je mag inzoomen
105100

106-
Merk op dat je de URL naar de tegels zelf niet ziet in de `main.js`. Die wordt in de `style.json` aangeroepen. De `main.js` roept de `style.json` aan en die roept vervolgens de tiles aan en bepaalt hoe die tiles weergegeven moeten worden: bijvoorbeeld welke kleuren en diktes de lijnen op de kaart moeten krijgen.
101+
Merk op dat je de URL naar de tegels zelf niet ziet in `main.js`. Die URL wordt namelijk in de `style json` aangeroepen. De `main.js` roept de `style json` aan en die roept vervolgens de bron van van de tiles aan. De `style json` bepaalt ook hoe die tiles weergegeven moeten worden.
102+
De bron van de tiles is in dit geval dus <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/tiles/WebMercatorQuad/{z}/{y}/{x}?f=mvt>
103+
104+
- **Zoek in de** `style json` **de URL van de tiles op.**
107105

108106
!!! note "Wil je hier meer over weten?"
109107

110108
Kijk voor een deep dive op <https://ogcapi-workshop.ogc.org/api-deep-dive/tiles/>
111109

112-
- **Bekijk nog eens** `style.json`
110+
- **Bekijk nog eens** de `style json`: <https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/styles/standaard__webmercatorquad?f=json>
111+
113112

114-
Dit is een erg omvangrijke stijl. Hoe is dit opgebouwd? Dit is een json waarin de bron gedefinieerd wordt en de layers die daar in zitten en hoe die layers getoond moeten worden (kleuren, diktes, etc.)
113+
Dit is een erg omvangrijke stijl. Hoe is dit opgebouwd? Dit is een json waarin de bron gedefinieerd wordt en de layers die daar in zitten en hoe die layers getoond moeten worden (kleuren, diktes, etc.).
115114

116115
- **Bekijk nog eens** `main.js`
117116

118-
In dit geval staat de `style.json` op een externe locatie, maar het kan ook een bestand op je eigen server zijn.
119-
In dit geval is de `style.json` beschikbaar gesteld door PDOK, maar je kunt ook zelf `style.json` bestanden maken. Het voorbeeld is een erg omvangrijke stijl, maar er zijn ook simpelere stijlen mogelijk.
117+
In dit geval staat de `style json` op een externe locatie, maar het kan ook een bestand op je eigen server zijn.
118+
In dit geval is de `style json` beschikbaar gesteld door PDOK, maar je kunt ook zelf `style json` bestanden maken. Het voorbeeld is een erg groot stijlbestand, maar er zijn ook simpelere stijlen mogelijk.
120119

121120
## Maak een kaart
122121
We gaan nu onze eigen web map met een OGC API Tiles achtergrondkaart (BRT) maken, aan de hand van het voorbeeld.

voorbeelden/features/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>maplibre example</title>
6+
<script type="module" src="main.js"></script>
7+
<style>
8+
@import url("https://esm.sh/maplibre-gl/dist/maplibre-gl.css");
9+
10+
body {
11+
margin: 0;
12+
}
13+
14+
#map {
15+
width: 100vw;
16+
height: 100vh;
17+
}
18+
</style>
19+
</head>
20+
<body>
21+
<div id="map"></div>
22+
</body>
23+
</html>

voorbeelden/features/main.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as maplibregl from "https://esm.sh/maplibre-gl";
2+
import OGCFeatureCollection from './mapbox-gl-ogc-feature-collection.patched.js';
3+
4+
const map = new maplibregl.Map({
5+
container: 'map', // container id
6+
style: 'https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/styles/standaard__webmercatorquad?f=json', // style URL
7+
center: [5.44, 52.05], // starting position [lng, lat]
8+
zoom: 7, // starting zoomlevel
9+
minZoom: 6, // minimum zoomlevel zoom out
10+
maxZoom: 14 // maximum zoomlevel zoom in
11+
});
12+
13+
map.on('load', () => {
14+
const provinciegebiedsource = 'collection-src'
15+
16+
new OGCFeatureCollection(provinciegebiedsource, map, {
17+
url: 'https://api.pdok.nl/kadaster/bestuurlijkegebieden/ogc/v1',
18+
collectionId: 'provinciegebied',
19+
limit: 100
20+
})
21+
22+
map.addLayer({
23+
'id': 'provinciegebied-outline',
24+
'source': provinciegebiedsource,
25+
'type': 'line',
26+
'paint': {
27+
'line-color': '#000000',
28+
'line-width': 2
29+
}
30+
});
31+
})
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import * as tilebelt from "https://unpkg.com/@mapbox/[email protected]/dist/esm/index.js";
2+
3+
class OGCFeatureCollection {
4+
constructor(sourceId, map, collectionOptions, geojsonSourceOptions) {
5+
if (!sourceId || !map || !collectionOptions) throw new Error('Source id, map and collectionOptions must be supplied as the first three arguments.')
6+
if (!collectionOptions.url) throw new Error('A url must be supplied as part of the collectionOptions object.')
7+
if (!collectionOptions.collectionId) throw new Error('A collectionId must be supplied as part of the collectionOptions object.')
8+
9+
this.sourceId = sourceId;
10+
this._map = map;
11+
12+
this._tileIndices = new Map();
13+
this._featureIndices = new Map();
14+
this._featureCollections = new Map();
15+
16+
this._collectionServiceOptions = Object.assign({
17+
limit: 5000,
18+
useStaticZoomLevel: false,
19+
minZoom: collectionOptions.useStaticZoomLevel ? 7 : 2
20+
}, collectionOptions);
21+
22+
this.serviceMetadata = null;
23+
this._maxExtent = [-Infinity, Infinity, -Infinity, Infinity];
24+
25+
const gjOptions = !geojsonSourceOptions ? {} : geojsonSourceOptions;
26+
this._map.addSource(sourceId, Object.assign(gjOptions, {
27+
type: 'geojson',
28+
data: this._getBlankFc()
29+
}));
30+
31+
this.enableRequests();
32+
this._clearAndRefreshTiles();
33+
}
34+
35+
destroySource() {
36+
this.disableRequests();
37+
this._map.removeSource(this.sourceId);
38+
}
39+
40+
_getBlankFc() {
41+
return {
42+
type: 'FeatureCollection',
43+
features: []
44+
}
45+
}
46+
47+
disableRequests() {
48+
this._map.off('moveend', this._boundEvent);
49+
}
50+
51+
enableRequests() {
52+
this._boundEvent = this._findAndMapData.bind(this);
53+
this._map.on('moveend', this._boundEvent);
54+
}
55+
56+
_clearAndRefreshTiles() {
57+
this._tileIndices = new Map();
58+
this._featureIndices = new Map();
59+
this._featureCollections = new Map();
60+
this._findAndMapData();
61+
}
62+
63+
_createOrGetTileIndex(zoomLevel) {
64+
const existingZoomIndex = this._tileIndices.get(zoomLevel);
65+
if (existingZoomIndex) return existingZoomIndex
66+
const newIndex = new Map();
67+
this._tileIndices.set(zoomLevel, newIndex);
68+
return newIndex
69+
}
70+
71+
_createOrGetFeatureCollection(zoomLevel) {
72+
const existingZoomIndex = this._featureCollections.get(zoomLevel);
73+
if (existingZoomIndex) return existingZoomIndex
74+
const fc = this._getBlankFc();
75+
this._featureCollections.set(zoomLevel, fc);
76+
return fc
77+
}
78+
79+
_createOrGetFeatureIdIndex(zoomLevel) {
80+
const existingFeatureIdIndex = this._featureIndices.get(zoomLevel);
81+
if (existingFeatureIdIndex) return existingFeatureIdIndex
82+
const newFeatureIdIndex = new Map();
83+
this._featureIndices.set(zoomLevel, newFeatureIdIndex);
84+
return newFeatureIdIndex
85+
}
86+
87+
async _findAndMapData() {
88+
const z = this._map.getZoom();
89+
90+
if (z < this._collectionServiceOptions.minZoom) {
91+
return
92+
}
93+
const bounds = this._map.getBounds().toArray();
94+
const primaryTile = tilebelt.bboxToTile([bounds[0][0], bounds[0][1], bounds[1][0], bounds[1][1]]);
95+
96+
// If we're not using a static zoom level we'll round to the nearest even zoom level
97+
// This means we don't need to request new data for every zoom level allowing us to reuse the previous levels data
98+
const zoomLevel = this._collectionServiceOptions.useStaticZoomLevel ? this._collectionServiceOptions.minZoom : 2 * Math.floor(z / 2);
99+
const zoomLevelIndex = this._createOrGetTileIndex(zoomLevel);
100+
const featureIdIndex = this._createOrGetFeatureIdIndex(zoomLevel);
101+
const fc = this._createOrGetFeatureCollection(zoomLevel);
102+
103+
const tilesToRequest = [];
104+
105+
if (primaryTile[2] < zoomLevel) {
106+
let candidateTiles = tilebelt.getChildren(primaryTile);
107+
let minZoomOfCandidates = candidateTiles[0][2];
108+
while (minZoomOfCandidates < zoomLevel) {
109+
const newCandidateTiles = [];
110+
candidateTiles.forEach(t => newCandidateTiles.push(...tilebelt.getChildren(t)));
111+
candidateTiles = newCandidateTiles;
112+
minZoomOfCandidates = candidateTiles[0][2];
113+
}
114+
115+
for (let index = 0; index < candidateTiles.length; index++) {
116+
if (this._doesTileOverlapBbox(candidateTiles[index], bounds)) {
117+
tilesToRequest.push(candidateTiles[index]);
118+
}
119+
}
120+
} else {
121+
tilesToRequest.push(primaryTile);
122+
}
123+
124+
for (let index = 0; index < tilesToRequest.length; index++) {
125+
const quadKey = tilebelt.tileToQuadkey(tilesToRequest[index]);
126+
if (zoomLevelIndex.has(quadKey)) {
127+
tilesToRequest.splice(index, 1);
128+
index--;
129+
} else zoomLevelIndex.set(quadKey, true);
130+
}
131+
132+
if (tilesToRequest.length === 0) {
133+
this._updateFcOnMap(fc);
134+
return
135+
}
136+
137+
// This tolerance will be used to inform the quantization/simplification of features
138+
const mapWidth = Math.abs(bounds[1][0] - bounds[0][0]);
139+
const tolerance = (mapWidth / this._map.getCanvas().width) * this._collectionServiceOptions.simplifyFactor;
140+
await this._loadTiles(tilesToRequest, tolerance, featureIdIndex, fc);
141+
this._updateFcOnMap(fc);
142+
}
143+
144+
async _loadTiles(tilesToRequest, tolerance, featureIdIndex, fc) {
145+
return new Promise((resolve) => {
146+
const promises = tilesToRequest.map(t => this._getTile(t, tolerance));
147+
Promise.all(promises).then((featureCollections) => {
148+
featureCollections.forEach((tileFc) => {
149+
if (tileFc) this._iterateItems(tileFc, featureIdIndex, fc);
150+
});
151+
resolve();
152+
});
153+
})
154+
}
155+
156+
_iterateItems(tileFc, featureIdIndex, fc) {
157+
if (tileFc.features != null){
158+
tileFc.features.forEach((feature) => {
159+
if (!featureIdIndex.has(feature.id)) {
160+
fc.features.push(feature);
161+
featureIdIndex.set(feature.id);
162+
}
163+
});
164+
}
165+
}
166+
167+
_getTile(tile) {
168+
const tileBounds = tilebelt.tileToBBOX(tile);
169+
170+
let urlParams = `limit=${this._collectionServiceOptions.limit}&bbox=${tileBounds[0]},${tileBounds[1]},${tileBounds[2]},${tileBounds[3]}`;
171+
172+
let blacklist_parameters = ['limit','url','useStaticZoomLevel','collectionId','minZoom'];
173+
174+
for(let option in this._collectionServiceOptions){
175+
if (! blacklist_parameters.includes(option)){
176+
urlParams += `&${option}=${this._collectionServiceOptions[option]}`;
177+
}
178+
}
179+
180+
return new Promise((resolve) => {
181+
fetch(`${`${this._collectionServiceOptions.url}/collections/${this._collectionServiceOptions.collectionId}/items?${urlParams}`}`, this._collectionServiceOptions.fetchOptions)
182+
.then(response => (response.json()))
183+
.then((data) => {
184+
resolve(data);
185+
});
186+
})
187+
}
188+
189+
_updateFcOnMap(fc) {
190+
this._map.getSource(this.sourceId).setData(fc);
191+
}
192+
193+
_doesTileOverlapBbox(tile, bbox) {
194+
const tileBounds = tile.length === 4 ? tile : tilebelt.tileToBBOX(tile);
195+
if (tileBounds[2] < bbox[0][0]) return false
196+
if (tileBounds[0] > bbox[1][0]) return false
197+
if (tileBounds[3] < bbox[0][1]) return false
198+
if (tileBounds[1] > bbox[1][1]) return false
199+
return true
200+
}
201+
}
202+
203+
export { OGCFeatureCollection as default };
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import * as maplibregl from "https://esm.sh/maplibre-gl"; //import
1+
import * as maplibregl from "https://esm.sh/maplibre-gl";
22

33
const map = new maplibregl.Map({
44
container: 'map', // container id
55
style: 'https://api.pdok.nl/kadaster/brt-achtergrondkaart/ogc/v1/styles/standaard__webmercatorquad?f=json', // style URL
6-
center: [5.4407, 52.0518], // starting position [lng, lat]
6+
center: [5.44, 52.05], // starting position [lng, lat]
77
zoom: 7, // starting zoomlevel
88
minZoom: 6, // minimum zoomlevel zoom out
99
maxZoom: 14 // maximum zoomlevel zoom in

0 commit comments

Comments
 (0)