0
votes

We are trying to incorporate vector tile endpoints into a back-end framework using mapbox-vector-tile-java to encode geographic features into Vector Tiles. We have it all mostly working except for some odd behavior where the rendered features are placed at latitudes way higher than they should be. When zooming in the features will jump to lower latitudes until finally getting to pretty good accuracy at really low zoom levels. For example, my data is of New Mexico counties. On initial page load the counties all render in Canada. When I zoom in to zoom level 2 they will jump down to Southern Canada. The more I zoom in the more close they get to where New Mexico actually is. Does anyone know why I'm seeing this behavior?

Some high level details:

  1. Geographic features from the database are in EPSG:4326.
  2. JTS is used in conjunction with mapbox-vector-tile-java to handle encoding of data.
  3. Converting URL based xyz tile positions to tile bounds uses the exact equation that Mapbox uses for such conversions as referenced in MapBox's TileBelt.

Example URL

https://localhost:8443/app/location/data?x=0&y=0&z=0&config={some:json}

Back-end Java code building the Vector Tiles

  Envelope tileEnvelope = this.getTileBounds(x, y, zoom);

  GeometryFactory geomFactory = new GeometryFactory();
  IGeometryFilter acceptAllGeomFilter = geometry -> true;

  MvtLayerParams layerParams = new MvtLayerParams();      

  TileGeomResult tileGeom = JtsAdapter.createTileGeom(geometries, tileEnvelope, geomFactory, layerParams, acceptAllGeomFilter);

  final VectorTile.Tile.Builder tileBuilder = VectorTile.Tile.newBuilder();

  // Create MVT layer
  final MvtLayerProps layerProps = new MvtLayerProps();
  final IUserDataConverter ignoreUserData = new UserDataConverter();

  // MVT tile geometry to MVT features
  final List<VectorTile.Tile.Feature> features = JtsAdapter.toFeatures(tileGeom.mvtGeoms, layerProps, ignoreUserData);

  final VectorTile.Tile.Layer.Builder layerBuilder = MvtLayerBuild.newLayerBuilder(layerName, layerParams);
  layerBuilder.addAllFeatures(features);

  MvtLayerBuild.writeProps(layerBuilder, layerProps);

  // Build MVT layer
  final VectorTile.Tile.Layer layer = layerBuilder.build();

  // Add built layer to MVT
  tileBuilder.addLayers(layer);

  /// Build MVT
  Tile mvt = tileBuilder.build();

  return mvt.toByteArray();

Code converting tile grid position to tile bounds

public Envelope getTileBounds(int x, int y, int zoom)
{
    return new Envelope(this.getLong(x, zoom), this.getLong(x + 1, zoom), this.getLat(y, zoom), this.getLat(y + 1, zoom));
}

public double getLong(int x, int zoom)
{
    return ( x / Math.pow(2, zoom) * 360 - 180 );
}

public double getLat(int y, int zoom)
{
    double r2d = 180 / Math.PI;
    double n = Math.PI - 2 * Math.PI * y / Math.pow(2, zoom);
    return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}

Tile grid conversion results

A URL with x=0,y=0,z=1 results in Env[-180.0 : 0.0, 0.0 : 85.0511287798066] which is a tile that covers north america as expected (We determined validity with this web-site: www.maptiler.org/google-maps-coordinates-tile-bounds-projection ).

A couple hunches we have (just guessing)

  1. The conversion between tile grid position to mapbox-gl vector tile grid is different from the above equation despite our using an equation they also use in TileBelt.
  2. mapbox-vector-tile-java is providing tiles that are encoded incorrectly.
  3. We are setting the bounds of the JTS geometries incorrectly.
1

1 Answers

0
votes

Of course this turned out to be an obvious solution that somehow we didn't catch earlier. We just needed to project the JTS features to EPSG 3857 before encoding the Vector Tiles.