Mapbox矢量瓦片编码和解码。
项目描述
Mapbox矢量瓦片
安装
mapbox-vector-tile与Python 3.9或更高版本兼容。它在PyPi上注册为mapbox-vector-tile
。推荐的安装方法是使用pip
pip install mapbox-vector-tile
定义了一个额外的依赖项来安装pyproj
。这在编码或解码瓦片时更改坐标参考系统时很有用。
pip install mapbox-vector-tile[proj]
编码
编码方法期望一个层数组或至少一个有效层。一个有效层是一个包含以下键的字典
-
name
:层名称 -
features
:特征数组。一个特征是一个包含以下键的字典geometry
:特征几何形状的WKT、WKB表示,或shapely几何形状。坐标相对于瓦片,缩放在[0, 4096)
范围内。下面是执行必要转换的示例代码。 注意 不支持GeometryCollection
类型,将触发ValueError
。properties
:包含一些键和对应值的字典。
编码操作接受选项,可以使用per_layer_options
参数按层定义。如果在per_layer_options
中缺少层或缺少选项值,则考虑default_options
的选项。最后,使用全局默认值。有关可用选项及其全局默认值的更多详细信息,请参阅encode
方法的文档字符串。
>>> import mapbox_vector_tile
# Using WKT
>>> mapbox_vector_tile.encode([
{
"name": "water",
"features": [
{
"geometry":"POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))",
"properties":{
"uid":123,
"foo":"bar",
"cat":"flew"
}
}
]
},
{
"name": "air",
"features": [
{
"geometry":"LINESTRING(159 3877, -1570 3877)",
"properties":{
"uid":1234,
"foo":"bar",
"cat":"flew"
}
}
]
}
])
b'\x1aH\n\x05water\x12\x18\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03uid\x1a\x03foo\x1a\x03cat"\x02 {"\x05\n\x03bar"\x06\n\x04flew(\x80 x\x02\x1aD\n\x03air\x12\x15\x12\x06\x00\x00\x01\x01\x02\x02\x18\x02"\t\t\xbe\x02\xb6\x03\n\x81\x1b\x00\x1a\x03uid\x1a\x03foo\x1a\x03cat"\x03 \xd2\t"\x05\n\x03bar"\x06\n\x04flew(\x80 x\x02'
# Using WKB
>>> mapbox_vector_tile.encode([
{
"name": "water",
"features": [
{
"geometry":b"\x01\x03\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
"properties":{
"uid":123,
"foo":"bar",
"cat":"flew"
}
}
]
},
{
"name": "air",
"features": [
{
"geometry":b"\x01\x03\x00\x00\x00\x01\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
"properties":{
"uid":1234,
"foo":"bar",
"cat":"flew"
}
}
]
}
])
b'\x1aH\n\x05water\x12\x18\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03uid\x1a\x03foo\x1a\x03cat"\x02 {"\x05\n\x03bar"\x06\n\x04flew(\x80 x\x02\x1aG\n\x03air\x12\x18\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03uid\x1a\x03foo\x1a\x03cat"\x03 \xd2\t"\x05\n\x03bar"\x06\n\x04flew(\x80 x\x02'
编码时的坐标转换
编码器期望几何形状为以下之一
- 瓦片相对坐标,其中左下角为原点,值向上和向右增长,瓦片为4096像素方形。例如,
POINT(0 0)
是瓦片的左下角,POINT(4096, 4096)
是瓦片的右上角。在这种情况下,库不进行投影,坐标按原样编码。 - 另一个坐标系统,瓦片边界由
quantize_bounds
参数给出。在这种情况下,库将缩放坐标,使quantize_bounds
在x
和y
方向上都适合在(0, 4096)范围内。除了仿射变换外,库不执行其他投影。
可以通过在encode()
调用中将参数y_coord_down=True
设置为控制瓦片是否在“y向下”坐标系统中。默认为“y向上”。
可以通过在encode()
调用中设置extents
参数来控制瓦片范围(默认为4096,如上面示例所示)。默认为4096。
如果您有经纬度(EPSG:4326)的几何形状,您可以首先将其投影到球面墨卡托(EPSG:3857),然后计算瓦片内的像素位置。此示例代码使用Django的内置GEOS库对LineString
对象进行转换
SRID_SPHERICAL_MERCATOR = 3857
def linestring_in_tile(tile_bounds, line):
# `mapbox-vector-tile` has a hardcoded tile extent of 4096 units.
MVT_EXTENT = 4096
from django.contrib.gis.geos import LineString
# We need tile bounds in spherical mercator
assert tile_bounds.srid == SRID_SPHERICAL_MERCATOR
# And we need the line to be in a known projection so we can re-project
assert line.srid is not None
line.transform(SRID_SPHERICAL_MERCATOR)
(x0, y0, x_max, y_max) = tile_bounds.extent
x_span = x_max - x0
y_span = y_max - y0
tile_based_coords = []
for x_merc, y_merc in line:
tile_based_coord = (int((x_merc - x0) * MVT_EXTENT / x_span),
int((y_merc - y0) * MVT_EXTENT / y_span))
tile_based_coords.append(tile_based_coord)
return LineString(*tile_based_coords)
瓦片边界可以使用mercantile
找到,所以一个完整的用法示例可能如下所示
from django.contrib.gis.geos import LineString, Polygon
import mercantile
import mapbox_vector_tile
SRID_LNGLAT = 4326
SRID_SPHERICAL_MERCATOR = 3857
tile_xyz = (2452, 3422, 18)
tile_bounds = Polygon.from_bbox(mercantile.bounds(*tile_xyz))
tile_bounds.srid = SRID_LNGLAT
tile_bounds.transform(SRID_SPHERICAL_MERCATOR)
lnglat_line = LineString(((-122.1, 45.1), (-122.2, 45.2)), srid=SRID_LNGLAT)
tile_line = linestring_in_tile(tile_bounds, lnglat_line)
tile_pbf = mapbox_vector_tile.encode({
"name": "my-layer",
"features": [ {
"geometry": tile_line.wkt,
"properties": { "stuff": "things" },
} ]
})
请注意,此示例在渲染时可能瓦片内没有任何可见内容。确保您将正确数据放入瓦片的职责在于您。
请注意,规范允许修改范围,尽管通常按惯例设置为4096。mapbox-vector-tile
假定范围为4096。
import mapbox_vector_tile
from pyproj import Transformer
from shapely.geometry import LineString
SRID_LNGLAT = 4326
SRID_SPHERICAL_MERCATOR = 3857
direct_transformer = Transformer.from_crs(crs_from=SRID_LNGLAT, crs_to=SRID_SPHERICAL_MERCATOR, always_xy=True)
lnglat_line = LineString(((-122.1, 45.1), (-122.2, 45.2)))
# Encode the tile
tile_pbf = mapbox_vector_tile.encode({
"name": "my-layer",
"features": [{
"geometry": lnglat_line.wkt,
"properties": {"stuff": "things"},
}]
},
default_options={"transformer": direct_transformer.transform})
# Decode the tile
reverse_transformer = Transformer.from_crs(crs_from=SRID_SPHERICAL_MERCATOR, crs_to=SRID_LNGLAT, always_xy=True)
mapbox_vector_tile.decode(tile=tile_pbf, default_options={"transformer": reverse_transformer.transform})
{
"my-layer": {
"extent": 4096,
"version": 1,
"features": [
{
"geometry": {
"type": "LineString",
"coordinates": [
[-122.10000156433787, 45.09999871982179],
[-122.20000202176608, 45.20000292038091]
]
},
"properties": {
"stuff": "things"
},
"id": 0,
"type": "Feature"
}
],
"type": "FeatureCollection"
}
}
量化
编码器还具有通过quantize_bounds
选项为您量化数据的选项。在编码时,以形式(minx,miny,maxx,maxy)传递边界,坐标将在编码过程中适当地缩放。
mapbox_vector_tile.encode([
{
"name": "water",
"features": [
{
"geometry":"POINT(15 15)",
"properties":{
"foo":"bar",
}
}
]
}
], default_options={"quantize_bounds": (10.0, 10.0, 20.0, 20.0)})
在此示例中,将被编码的坐标为(2048,2048)
此外,如果数据已经在一个y值向下的坐标系中,编码器支持一个选项y_coord_down
,可以设置为True。这将抑制在编码过程中翻转y坐标值。
自定义范围
编码器还支持传递自定义范围。这些将通过pbf中的层传递,并在量化或翻转y坐标时得到尊重。
mapbox_vector_tile.encode([
{
"name": "water",
"features": [
{
"geometry":"POINT(15 15)",
"properties":{
"foo":"bar",
}
}
]
}
], default_options={"quantize_bounds": (0.0, 0.0, 10.0, 10.0), "extents":50})
解码
解码方法接受一个有效的google.protobuf.message Tile,并返回以下格式的解码字符串
{
layername: {
'extent': 'integer layer extent'
'version': 'integer'
'features': [{
'geometry': 'list of points',
'properties': 'dictionary of key/value pairs',
'id': 'unique id for the given feature within the layer '
}, ...
]
},
layername2: {
# ...
}
}
解码操作接受选项,可以使用per_layer_options
参数按层定义。如果per_layer_options
中缺少层或缺少选项值,则考虑default_options
的选项。最后,使用全局默认值。请参阅decode
方法的文档字符串,了解有关可用选项及其全局默认值的更多详细信息。
>>> import mapbox_vector_tile
>>> mapbox_vector_tile.decode(b'\x1aJ\n\x05water\x12\x1a\x08\x01\x12\x06\x00\x00\x01\x01\x02\x02\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x03cat"\x05\n\x03bar"\x02 {"\x06\n\x04flew(\x80 x\x02\x1aY\n\x03air\x12\x1c\x08\x01\x12\x08\x00\x00\x01\x01\x02\x02\x03\x03\x18\x03"\x0c\t\x00\x80@\x1a\x00\x01\x02\x00\x00\x02\x0f\x1a\x03foo\x1a\x03uid\x1a\x05balls\x1a\x03cat"\x05\n\x03bar"\x03 \xd2\t"\x05\n\x03foo"\x06\n\x04flew(\x80 x\x02')
{
"water": {
"extent": 4096,
"version": 2,
"features": [
{
"geometry": {
"type": "Polygon",
"coordinates": [[[0,0],[0,1],[1,1],[1,0],[0,0]]]
},
"properties": {
"foo": "bar",
"uid": 123,
"cat": "flew"
},
"id": 1,
"type": "Feature"
}
],
"type": "FeatureCollection"
},
"air": {
"extent": 4096,
"version": 2,
"features": [
{
"geometry": {
"type": "Polygon",
"coordinates": [[[0,0],[0,1],[1,1],[1,0],[0,0]]]
},
"properties": {
"foo": "bar",
"uid": 1234,
"balls": "foo",
"cat": "flew"
},
"id": 1,
"type": "Feature"
}
],
"type": "FeatureCollection"
}
}
以下是解码文件中瓦片的方法。
>>> import mapbox_vector_tile
>>> with open('tile.mvt', 'rb') as f:
>>> data = f.read()
>>> decoded_data = mapbox_vector_tile.decode(data)
>>> with open('out.txt', 'w') as f:
>>> f.write(repr(decoded_data))
decode
函数有一个geojson
选项,强制执行GeoJson RFC7946兼容的结果。其默认值为True
。要强制执行版本<2.0.0的行为,请使用geojson=False
。
使用原生protobuf库以获得性能
底层protobuf库的C++实现比纯Python版本性能更好。根据您的操作系统,您可能需要编译C++库或安装它。
自2022年5月6日起,Python profobuf
库基于udp库,因此生成的Python代码需要protoc
3.19.0或更高版本。有关详细信息,请参阅此处。在debian Bullseye上,包注册表中可用的protoc
版本太旧。请从protobuf +GitHub仓库安装。
要编译proto
文件,您必须在运行Python程序之前启用两个环境变量
$ sudo apt-get install libprotoc9 libprotobuf9 protobuf-compiler python-protobuf
然后,您必须在运行Python程序之前启用两个环境变量
$ export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp
$ export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION_VERSION=2
然后
$ protoc -I=mapbox_vector_tile/Mapbox/ --python_out=mapbox_vector_tile/Mapbox/ mapbox_vector_tile/Mapbox/vector_tile.proto
变更日志
点击此处查看各个版本随时间的变化。