Это в основном вопрос о запуске пользовательских функций PostGIS внутри кода Django. На этом сайте есть несколько связанных ответов, наиболее близким к моему случаю является этот . Предлагается использовать классы Func()
или даже GeoFunc()
, но там нет примеров для геопространственных функций. Последний ('GeoFunc') даже не работал для меня, выбрасывая исключение st_geofunc does not exist
(Django 2.1.5).
Задача, которую я должен выполнить, - отфильтровать LineStrings
по их расстоянию Фреше до заданной геометрии. Предполагается, что расстояние Фреше рассчитывается с использованием функции ST_FrechetDistance
, предоставляемой PostGIS.
В другом проекте, основанном на SQLAlchemy, я выполняю ту же самую задачу с помощью следующей функции (она работает):
from geoalchemy2 import Geography, Geometry
from sqlalchemy import func, cast
def get_matched_segments(wkt: str, freche_threshold: float = 0.002):
matched_segments = db_session.query(RoadElement).filter(
func.ST_Dwithin(
RoadElement.geom,
cast(wkt, Geography),
10
)
).filter(
(func.ST_FrechetDistance(
cast(RoadElement.geom, Geometry),
cast(wkt, Geometry),
0.1
) < freche_threshold) |
# Frechet Distance is sensitive to geometry direction
(func.ST_FrechetDistance(
cast(RoadElement.geom, Geometry),
func.ST_Reverse(cast(wkt, Geometry)),
0.1
) < freche_threshold)
)
return matched_segments
Как я уже сказал, вышеприведенная функция работает, и я хотел повторно реализовать ее в Django. Мне пришлось добавить дополнительное преобразование геометрии SRS, потому что в проектах на основе SQLite LineStrings были в EPSG: 4326, а в Django они изначально были в EPSG: 3857. Вот что я придумал:
from django.db.models import Func, Value, Q, QuerySet, F
from django.contrib.gis.geos import GEOSGeometry
class HighwayOnlyMotor(models.Model):
geom = LineStringField(srid=3857)
def get_matched_segments(wkt: str, freche_threshold: float = 0.002) -> QuerySet:
linestring = GEOSGeometry(wkt, srid=4326)
transform_ls = linestring.transform(3857, clone=True)
linestring.reverse()
frechet_annotation = HighwayOnlyMotor.objects.filter(
geom__dwithin=(transform_ls, D(m=20))
).annotate(
fre_forward=Func(
Func(F('geom'), Value(4326), function='ST_Transform'),
Value(wkt),
Value(0.1),
function='ST_FrechetDistance'
),
fre_backward=Func(
Func(F('geom'), Value(4326), function='ST_Transform'),
Value(linestring.wkt),
Value(0.1),
function='ST_FrechetDistance'
)
)
matched_segments = frechet_annotation.filter(
Q(fre_forward__lte=freche_threshold) |
Q(fre_backward__lte=freche_threshold)
)
return matched_segments
Это не работает, так как frechet_annotation
QuerySet выдает исключение:
django.db.utils.ProgrammingError: cannot cast type double precision to bytea
LINE 1: ...548 55.717805109,36.825235998 55.717761246)', 0.1)::bytea AS...
^
Кажется, я неправильно определил вычисление ST_FrechetDistance. Как мне это исправить?
UPDATE
Изучил SQL, который написал Django. Это в целом правильно, но попытка привести результат от FrecheDistance
к bytea
портит его ST_FrechetDistance(...)::bytea
. Когда я вручную запускаю запрос без bytea
cast, SQL работает. Так что вопрос в том, как избежать этого броска на bytea
?