跳转到主要内容

Mapbox矢量瓦片编码和解码。

项目描述

Mapbox矢量瓦片

CI pre-commit Coverage Status

安装

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'

编码时的坐标转换

编码器期望几何形状为以下之一

  1. 瓦片相对坐标,其中左下角为原点,值向上和向右增长,瓦片为4096像素方形。例如,POINT(0 0)是瓦片的左下角,POINT(4096, 4096)是瓦片的右上角。在这种情况下,库不进行投影,坐标按原样编码。
  2. 另一个坐标系统,瓦片边界由quantize_bounds参数给出。在这种情况下,库将缩放坐标,使quantize_boundsxy方向上都适合在(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

变更日志

点击此处查看各个版本随时间的变化。

项目详情


下载文件

下载适合您平台文件。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分布

mapbox_vector_tile-2.1.0.tar.gz (28.0 kB 查看哈希值)

上传时间

构建分布

mapbox_vector_tile-2.1.0-py3-none-any.whl (30.9 kB 查看哈希值)

上传于 Python 3