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:
- Geographic features from the database are in EPSG:4326.
- JTS is used in conjunction with mapbox-vector-tile-java to handle encoding of data.
- 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)
- 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.
- mapbox-vector-tile-java is providing tiles that are encoded incorrectly.
- We are setting the bounds of the JTS geometries incorrectly.