Kaydet (Commit) bde86ce9 authored tarafından Sergey Fedoseev's avatar Sergey Fedoseev Kaydeden (comit) Tim Graham

Fixed #25605 -- Made GIS DB functions accept geometric expressions, not only…

Fixed #25605 -- Made GIS DB functions accept geometric expressions, not only values, in all positions.
üst e487ffd3
...@@ -76,6 +76,8 @@ class Lookup: ...@@ -76,6 +76,8 @@ class Lookup:
def process_lhs(self, compiler, connection, lhs=None): def process_lhs(self, compiler, connection, lhs=None):
lhs = lhs or self.lhs lhs = lhs or self.lhs
if hasattr(lhs, 'resolve_expression'):
lhs = lhs.resolve_expression(compiler.query)
return compiler.compile(lhs) return compiler.compile(lhs)
def process_rhs(self, compiler, connection): def process_rhs(self, compiler, connection):
......
...@@ -429,6 +429,9 @@ class DistanceFunctionsTests(TestCase): ...@@ -429,6 +429,9 @@ class DistanceFunctionsTests(TestCase):
self.assertTrue( self.assertTrue(
SouthTexasInterstate.objects.annotate(length=Length('path')).filter(length__gt=4000).exists() SouthTexasInterstate.objects.annotate(length=Length('path')).filter(length__gt=4000).exists()
) )
# Length with an explicit geometry value.
qs = Interstate.objects.annotate(length=Length(i10.path))
self.assertAlmostEqual(qs.first().length.m, len_m2, 2)
@skipUnlessDBFeature("has_Perimeter_function") @skipUnlessDBFeature("has_Perimeter_function")
def test_perimeter(self): def test_perimeter(self):
......
...@@ -2,7 +2,9 @@ import re ...@@ -2,7 +2,9 @@ import re
from decimal import Decimal from decimal import Decimal
from django.contrib.gis.db.models import functions from django.contrib.gis.db.models import functions
from django.contrib.gis.geos import LineString, Point, Polygon, fromstr from django.contrib.gis.geos import (
GEOSGeometry, LineString, Point, Polygon, fromstr,
)
from django.contrib.gis.measure import Area from django.contrib.gis.measure import Area
from django.db import connection from django.db import connection
from django.db.models import Sum from django.db.models import Sum
...@@ -494,7 +496,48 @@ class GISFunctionsTests(TestCase): ...@@ -494,7 +496,48 @@ class GISFunctionsTests(TestCase):
@skipUnlessDBFeature("has_Union_function") @skipUnlessDBFeature("has_Union_function")
def test_union(self): def test_union(self):
"""Union with all combinations of geometries/geometry fields."""
geom = Point(-95.363151, 29.763374, srid=4326) geom = Point(-95.363151, 29.763374, srid=4326)
ptown = City.objects.annotate(union=functions.Union('point', geom)).get(name='Dallas')
union = City.objects.annotate(union=functions.Union('point', geom)).get(name='Dallas').union
expected = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)', srid=4326) expected = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)', srid=4326)
self.assertTrue(expected.equals(ptown.union)) self.assertTrue(expected.equals(union))
union = City.objects.annotate(union=functions.Union(geom, 'point')).get(name='Dallas').union
self.assertTrue(expected.equals(union))
union = City.objects.annotate(union=functions.Union('point', 'point')).get(name='Dallas').union
expected = GEOSGeometry('POINT(-96.801611 32.782057)', srid=4326)
self.assertTrue(expected.equals(union))
union = City.objects.annotate(union=functions.Union(geom, geom)).get(name='Dallas').union
self.assertTrue(geom.equals(union))
@skipUnlessDBFeature("has_Union_function", "has_Transform_function")
def test_union_mixed_srid(self):
"""The result SRID depends on the order of parameters."""
geom = Point(61.42915, 55.15402, srid=4326)
geom_3857 = geom.transform(3857, clone=True)
tol = 0.001
for city in City.objects.annotate(union=functions.Union('point', geom_3857)):
expected = city.point | geom
self.assertTrue(city.union.equals_exact(expected, tol))
self.assertEqual(city.union.srid, 4326)
for city in City.objects.annotate(union=functions.Union(geom_3857, 'point')):
expected = geom_3857 | city.point.transform(3857, clone=True)
self.assertTrue(expected.equals_exact(city.union, tol))
self.assertEqual(city.union.srid, 3857)
def test_argument_validation(self):
with self.assertRaisesMessage(ValueError, 'SRID is required for all geometries.'):
City.objects.annotate(geo=functions.GeoFunc(Point(1, 1)))
msg = 'GeoFunc function requires a GeometryField in position 1, got CharField.'
with self.assertRaisesMessage(TypeError, msg):
City.objects.annotate(geo=functions.GeoFunc('name'))
msg = 'GeoFunc function requires a geometric argument in position 1.'
with self.assertRaisesMessage(TypeError, msg):
City.objects.annotate(union=functions.GeoFunc(1, 'point')).get(name='Dallas')
...@@ -120,9 +120,19 @@ class GeographyFunctionTests(TestCase): ...@@ -120,9 +120,19 @@ class GeographyFunctionTests(TestCase):
else: else:
ref_dists = [0, 4891.20, 8071.64, 9123.95] ref_dists = [0, 4891.20, 8071.64, 9123.95]
htown = City.objects.get(name='Houston') htown = City.objects.get(name='Houston')
qs = Zipcode.objects.annotate(distance=Distance('poly', htown.point)) qs = Zipcode.objects.annotate(
distance=Distance('poly', htown.point),
distance2=Distance(htown.point, 'poly'),
)
for z, ref in zip(qs, ref_dists): for z, ref in zip(qs, ref_dists):
self.assertAlmostEqual(z.distance.m, ref, 2) self.assertAlmostEqual(z.distance.m, ref, 2)
if postgis:
# PostGIS casts geography to geometry when distance2 is calculated.
ref_dists = [0, 4899.68, 8081.30, 9115.15]
for z, ref in zip(qs, ref_dists):
self.assertAlmostEqual(z.distance2.m, ref, 2)
if not spatialite: if not spatialite:
# Distance function combined with a lookup. # Distance function combined with a lookup.
hzip = Zipcode.objects.get(code='77002') hzip = Zipcode.objects.get(code='77002')
......
...@@ -271,7 +271,7 @@ class RasterFieldTest(TransactionTestCase): ...@@ -271,7 +271,7 @@ class RasterFieldTest(TransactionTestCase):
def test_isvalid_lookup_with_raster_error(self): def test_isvalid_lookup_with_raster_error(self):
qs = RasterModel.objects.filter(rast__isvalid=True) qs = RasterModel.objects.filter(rast__isvalid=True)
msg = 'Geometry functions not supported for raster fields.' msg = 'IsValid function requires a GeometryField in position 1, got RasterField.'
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
qs.count() qs.count()
...@@ -336,11 +336,11 @@ class RasterFieldTest(TransactionTestCase): ...@@ -336,11 +336,11 @@ class RasterFieldTest(TransactionTestCase):
""" """
point = GEOSGeometry("SRID=3086;POINT (-697024.9213808845 683729.1705516104)") point = GEOSGeometry("SRID=3086;POINT (-697024.9213808845 683729.1705516104)")
rast = GDALRaster(json.loads(JSON_RASTER)) rast = GDALRaster(json.loads(JSON_RASTER))
msg = "Please provide a geometry object." msg = "Distance function requires a geometric argument in position 2."
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
RasterModel.objects.annotate(distance_from_point=Distance("geom", rast)) RasterModel.objects.annotate(distance_from_point=Distance("geom", rast))
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
RasterModel.objects.annotate(distance_from_point=Distance("rastprojected", rast)) RasterModel.objects.annotate(distance_from_point=Distance("rastprojected", rast))
msg = "Geometry functions not supported for raster fields." msg = "Distance function requires a GeometryField in position 1, got RasterField."
with self.assertRaisesMessage(TypeError, msg): with self.assertRaisesMessage(TypeError, msg):
RasterModel.objects.annotate(distance_from_point=Distance("rastprojected", point)).count() RasterModel.objects.annotate(distance_from_point=Distance("rastprojected", point)).count()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment