来自 mapbox-vector-tile-java 的 Vector Tiles 导致在 mapbox-gl 中呈现异常高纬度的特征
Vector Tiles from mapbox-vector-tile-java results in features rendered in mapbox-gl at abnormally high latitudes
我们正在尝试使用 mapbox-vector-tile-java 将矢量切片端点合并到后端框架中,以将地理特征编码到矢量切片中。除了一些奇怪的行为外,我们大部分时间都在工作,在这些行为中,渲染的特征被放置在比它们应该的纬度高得多的地方。放大时,功能将跳到较低的纬度,直到最终在非常低的缩放级别下获得相当好的精度。例如,我的数据是新墨西哥州的县。在初始页面加载时,所有县都呈现在加拿大。当我放大到 2 级时,它们会跳到加拿大南部。我放大得越多,他们就越接近新墨西哥州的实际位置。有谁知道我为什么会看到这种行为?
一些高级细节:
- 数据库中的地理特征在 EPSG:4326。
- JTS 与 mapbox-vector-tile-java 结合使用来处理数据编码。
- 将基于 URL 的 xyz 瓦片位置转换为瓦片边界使用 Mapbox 用于此类转换的精确方程,如 MapBox 的 TileBelt 中所引用。
示例URL
https://localhost:8443/app/location/data?x=0&y=0&z=0&config={一些:json}
构建矢量切片的后端 Java 代码
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();
将图块网格位置转换为图块边界的代码
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)));
}
平铺网格转换结果
A URL with x=0,y=0,z=1 导致 Env[-180.0 : 0.0, 0.0 : 85.0511287798066] 这是一个如预期覆盖北美的图块(我们确定了有效性使用此网站:www.maptiler.org/google-maps-coordinates-tile-bounds-projection).
我们有一些预感(只是猜测)
- 瓦片网格位置到 mapbox-gl 矢量瓦片网格之间的转换与上面的方程不同,尽管我们使用了他们在 TileBelt 中也使用的方程。
- mapbox-vector-tile-java 提供的图块编码不正确。
- 我们错误地设置了 JTS 几何图形的边界。
当然,这是一个显而易见的解决方案,但不知何故我们没有早点发现。我们只需要在编码 Vector Tiles 之前将 JTS 功能投影到 EPSG 3857。
我们正在尝试使用 mapbox-vector-tile-java 将矢量切片端点合并到后端框架中,以将地理特征编码到矢量切片中。除了一些奇怪的行为外,我们大部分时间都在工作,在这些行为中,渲染的特征被放置在比它们应该的纬度高得多的地方。放大时,功能将跳到较低的纬度,直到最终在非常低的缩放级别下获得相当好的精度。例如,我的数据是新墨西哥州的县。在初始页面加载时,所有县都呈现在加拿大。当我放大到 2 级时,它们会跳到加拿大南部。我放大得越多,他们就越接近新墨西哥州的实际位置。有谁知道我为什么会看到这种行为?
一些高级细节:
- 数据库中的地理特征在 EPSG:4326。
- JTS 与 mapbox-vector-tile-java 结合使用来处理数据编码。
- 将基于 URL 的 xyz 瓦片位置转换为瓦片边界使用 Mapbox 用于此类转换的精确方程,如 MapBox 的 TileBelt 中所引用。
示例URL
https://localhost:8443/app/location/data?x=0&y=0&z=0&config={一些:json}
构建矢量切片的后端 Java 代码
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();
将图块网格位置转换为图块边界的代码
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)));
}
平铺网格转换结果
A URL with x=0,y=0,z=1 导致 Env[-180.0 : 0.0, 0.0 : 85.0511287798066] 这是一个如预期覆盖北美的图块(我们确定了有效性使用此网站:www.maptiler.org/google-maps-coordinates-tile-bounds-projection).
我们有一些预感(只是猜测)
- 瓦片网格位置到 mapbox-gl 矢量瓦片网格之间的转换与上面的方程不同,尽管我们使用了他们在 TileBelt 中也使用的方程。
- mapbox-vector-tile-java 提供的图块编码不正确。
- 我们错误地设置了 JTS 几何图形的边界。
当然,这是一个显而易见的解决方案,但不知何故我们没有早点发现。我们只需要在编码 Vector Tiles 之前将 JTS 功能投影到 EPSG 3857。