【问题标题】:How to use the PostGIS aggregate function ST_AsMVT with Django ORM如何在 Django ORM 中使用 PostGIS 聚合函数 ST_AsMVT
【发布时间】:2021-04-07 00:51:32
【问题描述】:

问题

我想在 Django 中使用 ORM 创建一个 Mapbox 矢量图块 (MVT)。 在 SQL(PostgreSQL、PostGIS)中,对于 zoom=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, ST_TileEnvelope(8, 137, 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, 137, 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>},...>

现在最后一部分不见了。我想在tile_geometries_mvt 上运行ST_AsMVT

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 解决它。

【问题讨论】:

标签: python django postgresql postgis vector-tiles


【解决方案1】:

我已经尝试将 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.*, '%(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(),
    )
)

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

【讨论】:

  • 你能给出代码示例吗?我认为它会更好地描述你所尝试的。
  • 我在原始答案中添加示例
  • 我很可能误解了这一点。我的AsMVT 模板是"%(function)s(subquery.*)"。我得到了col1col2 等,但添加了.order_by() 以删除任何默认值,并使用.annotate(friendly_name=F('path__to__field'),现在在我的MVT 中获得友好的名称。通过跟踪您突出显示的编译代码并找到它何时重命名为col1 等来做到这一点。但可能完全忽略了这一点 - 如果是这样,请忽略。
  • 我也成功使用了.annotate(friendly_name=Concat('my_field', 'another_field'))等。我没有设法设置ST_AsMVT聚合的feature_id_name参数,或者控制形状在MVT中的标记方式。
  • 您可以向聚合添加注释吗?并将其添加到属性中!?太棒了!
【解决方案2】:

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

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

在 Deck.gl 中使用服务图块的小示例:

getCityTileData = () => (
  new TileLayer({
    stroked: true,
    getLineColor: [0, 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, y, z);
              features.push(feature);
            }
          }
          return features;
        });
    }
  })

【讨论】:

  • 感谢您的建议。我已经看过这个库,它非常有用。我错过了一些额外的自定义过滤。我将查看源代码。希望我能满足我的需要。
  • 不客气。我不确定自定义过滤是什么意思,但您可以在请求中使用通用 django 查询过滤器,例如:city=Ankara 或 avg__gt=5
  • djangorestframework-mvt 只是构建一个 SQL 字符串并执行它。它不使用 Django 的自定义函数。
  • 我无法理解您所说的 Django 自定义函数是什么意思。 Django 的主要属性之一是使用其强大的 ORM 构建和执行 SQL 字符串。
  • 啊,我的意思是从 FuncGeoFunc 继承,就像 OP 一样。 djangorestframework-mvt 没有使用 Django 的 ORM,而是将 SQL 字符串拼接在一起。
猜你喜欢
  • 2021-12-13
  • 2019-11-15
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 2017-07-31
  • 2021-03-04
  • 2019-09-14
  • 2022-01-12
相关资源
最近更新 更多