问题描述
问题
我想在 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;
});
}
})