I have a large collection of points to render on a map (~50 thousands in this example, and 1 million in another map). Loading them all in memory slows-down the user experience. Therefore, I am trying to break them down into vector tiles.
The idea is that when I fully zoom out, I show only a subset of the points, and the more I zoom in, the more points I show but only on the specific area that I zoomed in.
My problem is that when I zoom in the map, only a subset of the points that I expect to see are shown.
At the lowest zoom level, the result looks correct:
(the “points” are rendered as triangles)
However, if I zoom in the right corner, this is what I see:
I don’t expect this blank area in-between the tiles.
And when I zoom even further:
As you can see, the features are cropped in a weird way.
My intuition is that something is wrong with the tilegrid.
As a point of comparison, here is what the tile (0, 0, 0) looks like on geojson.io:
(pretty similar to my tile (0, 0, 0).)
However, the tile (1, 1, 0) (which I would expect to see when I zoom in the top-right corner renders like this on geojson.io, proving that it contains more points than what is shown by OpenLayers:
Here is the code I use to load the vector tiles in OpenLayers:
import { VectorTile as VectorTileSource, OSM } from 'ol/source'
import * as grid from 'ol/tilegrid'
import { get as getProjection, transformExtent } from 'ol/proj'
import { GeoJSON } from 'ol/format'
import { VectorTile as VectorTileLayer, WebGLTile as WebGLTileLayer } from 'ol/layer'
import { RegularShape, Style } from 'ol/style'
import View from 'ol/View'
const webMercatorProjection: Projection = getProjection('EPSG:3857');
const windSource = new VectorTileSource({
tileGrid: grid.createForExtent(
transformExtent([5.0487586205341985, 42.66217075979909, 12.55826905774515, 50.17168119701004], getProjection('EPSG:4326'), webMercatorProjection, 3),
4, // maxZoom
1000 // tileSize
),
overlaps: true,
format: new GeoJSON(),
url: 'data/{z}-{x}-{y}.json'
});
const vectorTileLayer = new VectorTileLayer({
source: vectorTileSource,
opacity: 0.6,
minZoom: 5,
style: (point) => {
return new Style({
image: new RegularShape({
points: 3,
radius: 8 + point.get('size'),
rotation: point.get('rotation'),
rotateWithView: true,
fill: new Fill({ color: 'black' })
})
})
}
});
const map = new Map({
target: 'map',
layers: [
new WebGLTileLayer({
source: new OSM(),
}),
vectorTileLayer,
],
view: new View({
projection: webMercatorProjection
})
});
I got the extent I use for the VectorTileSource
by computing the bounding box of the points I need to show on the map. I use transformExtent
to convert from GPS coordinates to the Web Mercator projection.
What did I do wrong? Why are the features cropped?