2

I'm using Mapbox GL JS to display a map using my own tile-server (which just serves openmaptiles pbf files in a Z/X/Y directory structure).

The loading of tiles is done automatically, based on the current center coordinates.

I want to pre-load the surrounding tiles for an offline solution. I know how to get the current zoom-level with map.getZoom()

How can I get the X and Y indexes of the current tile URL ?

MapTiler
  • 1,754
  • 14
  • 22
Dylan
  • 9,129
  • 20
  • 96
  • 153

4 Answers4

6

I suggest framing the problem in terms of geographic coordinates:

  • You can get the viewport geographic coordinates from Map#getBounds)
  • You can convert geographic coordinates to tile coordinates with some light math
Lucas Wojciechowski
  • 3,695
  • 16
  • 19
1

Also, if you need to do some calculations in bash scripts, you can use mercantile library or CLI utility to transform lon/lan coordinates to tiles, find parent or children tiles and get some other information. Another advanced tool is supermercado.

unibasil
  • 488
  • 8
  • 18
1

@mapbox/tile-cover can be used to figure out what tiles are included in a given geometry. Once you get the bounds of the map with Map#getBounds(), you can then figure out the tile coverage using @mapbox/tile-cover. I also use @turf/bbox-polygon to create a geometry from the given map bounds. For example:

import cover from '@mapbox/tile-cover';
import bboxPolygon from '@turf/bbox-polygon';
import { flatten } from 'lodash';

const zoom = Math.floor(map.getZoom());
const tiles = cover.tiles(
  bboxPolygon(flatten(map.getBounds().toArray()) as BBox).geometry,
  {
    min_zoom: zoom,
    max_zoom: zoom,
  }
);
mrozema
  • 11
  • 2
-1

This worked for me:

It's a React.js implementation.

const lon2tile = (lon: any, zoom: any) => {
  return Math.floor(((lon + 180) / 360) * Math.pow(2, zoom))
}
const lat2tile = (lat: any, zoom: any) => {
  return Math.floor(
    ((1 -
      Math.log(Math.tan((lat * Math.PI) / 180) + 1 / Math.cos((lat * Math.PI) / 180)) / Math.PI) /
      2) *
      Math.pow(2, zoom)
  )
}

const zoomToUse = Math.floor(mapZoom)
const currentX = mapBounds?.ne && lon2tile((mapBounds.ne as TLngLatLike).lng, zoomToUse)
const currentY = mapBounds?.sw && lat2tile(Math.abs((mapBounds.sw as TLngLatLike).lat), zoomToUse)
const currentZ = zoomToUse

And this is how I call the tiles API to check first if valid tiles are returned for the current x, y, z:

  let tilesURLWithCoordinates = tiles.replace("{z}", zoomToUse.toString())
  tilesURLWithCoordinates = tilesURLWithCoordinates.replace("{x}", currentX.toString())
  tilesURLWithCoordinates = tilesURLWithCoordinates.replace("{y}", currentY.toString())

  const response = await fetch(tilesURLWithCoordinates, {
    method: "GET",
  })