如何在 Django ORM 中使用 PostGIS 聚合函数 ST_AsMVT

问题描述

问题

我想在 Django 中使用 ORM 创建一个 MapBox 矢量瓦片 (MVT)。 在 sql(Postgresql、PostGIS)中,对于缩放=8、x=137、y=83 的图块,sql 查询如下所示:

SELECT ST_AsMVT(tile)
FROM (SELECT id,ST_AsMVTGeom(geometry,ST_TileEnvelope(8,137,83)) AS "mvt_geom"
      FROM geomodel
      WHERE ST_Intersects(geometry,83))
     ) AS tile;

ST_AsMVT 聚合所有行,输出是可以作为响应发送的二进制字段 (bytea)。

由于 GeoDjango 不包含特定的 PostGIS 函数,我为它们创建了自定义函数

class TileEnvelope(Func):
    function = "ST_TileEnvelope"
    arity = 3
    output_field = models.GeometryField()


class AsMVTGeom(GeoFunc):
    function = "ST_AsMVTGeom"
    arity = 2
    output_field = models.GeometryField()

我设法创建了内部子查询并且它有效:

tile_envelope = TileEnvelope(8,83)
tile_geometries = GeoModel.objects.filter(geometry__intersects=tile_envelope)
tile_geometries_mvt = tile_geometries.annotate(mvt_geom=AsMVTGeom("geometry",tile_envelope))
tile_geometries_mvt = tile_geometries_mvt.values("id","mvt_geom")

print(tile_geometries_mvt)
>> <QuerySet [{'id': 165,'mvt_geom': <Point object at 0x7f552f9d3490>},{'id': 166,'mvt_geom': <Point object at 0x7f552f9d3590>},...>

现在缺少最后一部分。我想在 ST_AsMVT 上运行 tile_geometries_mvt

SELECT ST_AsMVT(tile)
FROM 'tile_geometries_mvt' AS tile;

问题

我尝试为 ST_AsMVT 创建自定义聚合函数,但没有成功。 例如,通常像 MAX 这样的聚合函数需要一列作为输入,而 ST_AsMVT 需要一个 anyelement set row

如何将 ST_AsMVT 变成 Django Aggregate(类似于 this SO question)?

我知道,我可以在 Django 中使用 raw_sql 查询,但是这个问题明确地是关于使用 Django ORM 解决它。

解决方法

我已经尝试使 AsMVT 聚合,但似乎无法实现(目前)。

  • ST_ASMVT 设计应该有子查询。*,而不是 (geom,column_1,...),虽然它有效但 ST_ASMVT 不保留列名(重命名为 f1、f2、f3 等)
  • 如果我们在聚合模板中使用 subquery.* ,没关系,ST_ASMVT 保留属性名称.. 但 django orm 重命名子查询中的列.. 所以属性被命名为 __col1,__col2 等。这个机制是在 SQLAggregateCompiler 中定义的,不能被覆盖

示例:

class AsMVT(Aggregate):
    name = "AsMVT"
    function = "ST_ASMVT"
    template = (
        "%(function)s((%(distinct)s%(expressions)s),'%(layer_name)s',%(extent)s)"
    )


features.aggregate(
    tile=AsMVT(
        F("geom_prepared"),F("name"),extent=self.vector_tile_extent,layer_name=self.get_vector_tile_layer_name(),)
)

生成矢量切片,但属性名称被 ST_ASMVT 重命名为 f1。 ST_ASMVT 需要一个真正的行集而不是子查询列表字段

class AsMVT(Aggregate):
    name = "AsMVT"
    function = "ST_ASMVT"
    template = "%(function)s(subquery.*,%(extent)s)"


features.aggregate(
    tile=AsMVT(
        F("geom_prepared"),)
)

生成一个矢量切片,但属性名称在聚合连接中被 django ORM 重命名为 __col1

,

djangorestframework-mvt 已经有一个在 Django 中提供 mapbox 矢量瓦片的解决方案。根据我的经验,它非常有用。 您可以通过 url 查询按字段值进行过滤。

https://github.com/corteva/djangorestframework-mvt

在 Deck.gl 中使用提供的图块的一个小例子:

getCityTileData = () => (
  new TileLayer({
    stroked: true,getLineColor: [0,192],getFillColor: [140,170,180],filled: false,getLineWidth: 1,lineWidthMinPixels: 1,getTileData: ({ x,y,z }) => {
      const mapSource = `${API_URL}/mvt/city?tile=${z}/${x}/${y}&name=ANKARA`;
      return fetch(mapSource)
        .then(response => response.arrayBuffer())
        .then(buffer => {
          const tile = new VectorTile(new Protobuf(buffer));
          const features = [];
          for (const layerName in tile.layers) {
            const vectorTileLayer = tile.layers[layerName];
            for (let i = 0; i < vectorTileLayer.length; i++) {
              const vectorTileFeature = vectorTileLayer.feature(i);
              const feature = vectorTileFeature.toGeoJSON(x,z);
              features.push(feature);
            }
          }
          return features;
        });
    }
  })