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:

lowest zoom level

(the “points” are rendered as triangles)

However, if I zoom in the right corner, this is what I see:

zoom level 1

I don’t expect this blank area in-between the tiles.

And when I zoom even further:

next zoom level

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:

enter image description here

(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:

next zoom level shown by geojson.io

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(),
    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?

1 Answer 1


I fixed the issue by computing the tiles in the Web Mercator projection (EPSG:3857) instead of EPSG:4326. I now make sure my tiles are actual squares in the Web Mercator projection, which was not the case previously. (Note that the point coordinates within the tiles, are still expressed an latitude and longitude, though.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.