首页 > 解决方案 > Django Rest Framework是否可以通过外键使用geometry_field从查询中序列化geojson?

问题描述

我有两种模型:一种用于维护点网格,另一种用于保存地理空间数据

from django.contrib.gis.db import models as geomodels
from django.db import models

class Grid(geomodels.Model):

    geom = geomodels.PointField(srid=4326, unique=True)
    elevation = geomodels.FloatField(blank=True, null=True)

class SpData(models.Model):

    mtime = models.DateTimeField()
    grid = models.ForeignKey(Grid)
    measure = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        blank=True,
        null=True,
    )

使用 django rest 框架,可以很容易地为具有几何字段作为我的网格模型的模型获取 geojson 文件,并且以下视图提供了一个不错的 geojson,其中 geom 作为几何字段并在属性中具有“高程”。

from django.http import HttpResponse
from django.core.serializers import serialize
from rest_framework.views import APIView
from .models import Grid

class GridView(APIView):

    def get(self, request):
        result = serialize(
            "geojson",
            Grid.objects.all(),
            geometry_field="geom",
            srid=4326,
            fields=(
                "elevation",
            ),
        )

        return HttpResponse(result)

现在我想使用同一个网格来显示链接到我的网格模型的 SpData 记录以显示“测量”值,但我没有成功向序列化程序指示我想使用网格表的几何形状。我认为这样优雅的代码可以工作:

class SpDataView(APIView):

    def get(self, request):
        geodata = SpData.objects.filter(mtime=today())
        fields = ("measure", "grid",)
        result = serialize(
            "geojson",
            geodata,
            geometry_field="grid.geom",
            srid=4326,
            fields=fields,
            use_natural_foreign_keys=True,
        )

        return HttpResponse(result)

但事实并非如此。因此,我尝试向我的 SpData 模型添加一个属性字段,但它也不起作用:

class SpData(models.Model):

    mtime = models.DateTimeField()
    grid = models.ForeignKey(Grid)
    measure = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        blank=True,
        null=True,
    )

    @property
    def geom(self):
        return self.grid.geom


class SpDataView(APIView):

    def get(self, request):
        geodata = SpData.objects.filter(mtime=today())
        fields = ("measure", "geom",)
        result = serialize(
            "geojson",
            geodata,
            geometry_field="geom",
            srid=4326,
            fields=fields,
        )

        return HttpResponse(result)

我尝试使用原始 SQL 查询,但序列化程序仅考虑已完成原始 sql 的模型的字段。

strsql ='SELECT sp.pk, sp.measure, g.geom '
strsql += ' FROM SpData AS sp'
strsql += ' INNER JOIN Grid AS g'
strsql += ' ON sp.grid = g.pk'
strsql += ' WHERE sp.mtime = today()'

geodata = SpData.objects.raw(strsql)
fields = ("measure", "geom",)
result = serialize(
    "geojson",
    geodata,
    geometry_field="geom",
    srid=4326,
    fields=fields,
)

return HttpResponse(result)

但是我生成的geojson中的几何等于null。因此,还原 SQL 将提供几何,但不会将字段“测量”添加到属性中:

strsql ='SELECT g.pk, sp.measure, g.geom '
strsql += ' FROM Grid AS g'
strsql += ' INNER JOIN SpData AS sp'
strsql += ' ON sp.grid = g.pk'
strsql += ' WHERE sp.mtime = today()'

geodata = Grid.objects.raw(strsql)

我错过了什么?这超出了当前 geodjango 序列化程序的范围吗?

标签: django-rest-frameworkforeign-keysgeojsongeodjango

解决方案


感谢相关列中出现的新链接,我明白了我的假设出了什么问题,最后我找到了两个解决方案:第一个创建专用序列化程序,第二个使用 sqlview 作为模型。

A)创建一个专用的序列化程序,重新定义 get_properties 方法以用所需的措施填充它们:

from rest_framework_gis.serializers import GeoFeatureModelSerializer
from django.db import connection

def dictfetchall(cursor):
    """
    When using a django.db.connection for raw sql,
    returns all rows from a cursor as a dict
    """
    columns = [col[0] for col in cursor.description]
    return [
        dict(zip(columns, row))
        for row in cursor.fetchall()
    ]

class SpDataSerializer(GeoFeatureModelSerializer):
    """
    Geojson Serializer for SpData
    """

    def get_properties(self, instance, fields):
        """
        Retrieve the measures linked to each grid point
        """
        strsql ='SELECT sp.measure '
        strsql += ' FROM SpData AS sp'
        strsql += ' WHERE sp.grid_id = %s'
        strsql += ' AND sp.mtime = today()'

        with connection.cursor() as cursor:
            cursor.execute(strsql, [instance.id])
            properties = dictfetchall(cursor)

        return properties[0]

    class Meta:
        model = Grid
        geo_field = "geom"
        fields = ("id", "elevation")

然后是一个使用它来生成geojson的视图:

from rest_framework.generics import ListAPIView

class SpDataView(ListAPIView):

    serializer_class = SpatialWalSerializer

    def get_queryset(self):
        # get the grid
        queryset = Grid.objects.all()

        return queryset

这个优雅的解决方案的限制是完成的查询量(网格模型中选择的每个几何图形)以及使用时间。

B)为了获得性能,我已经实现了 GridView 解决方案(请参阅我的第一个问题),但将其应用于基于https://blog.rescale.com/using-database-views-in-启发的数据库 VIEW 的模型django-orm/

BEGIN
;
DROP VIEW IF EXISTS app_viewspdata
;
CREATE OR REPLACE VIEW app_viewspdata AS
    SELECT sp.id
    , g.geom
    , g.elevation
    , sp.mtime
    , sp.measure
    FROM app_spdata AS sp
    INNER JOIN app_grid AS g
    ON g.id = sp.grid_id
;
COMMIT
;

然后我创建了一个新模型:

class ViewSpData(geomodels.Model):

    geom = geomodels.PointField(srid=4326, unique=True)
    elevation = geomodels.FloatField(blank=True, null=True)
    mtime = models.DateTimeField()
    measure = models.DecimalField(
        max_digits=6,
        decimal_places=1,
        blank=True,
        null=True,
    )

    class Meta:
            managed = False
            db_table = 'app_viewspdata'

该模型可用于生成这样的geojson:

from django.http import HttpResponse
from django.core.serializers import serialize
from rest_framework.views import APIView
from .models import ViewSpData

class ViewSpDataView(APIView):

    def get(self, request):
        result = serialize(
            "geojson",
            ViewSpData.objects.filter(mtime=today()),
            geometry_field="geom",
            srid=4326,
            fields=(
                "measure",
                "elevation",
            ),
        )

        return HttpResponse(result)

而已。


推荐阅读