123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550 |
- import json
- import re
- from decimal import Decimal
- from django.contrib.gis.db.models import functions
- from django.contrib.gis.geos import (
- GEOSGeometry, LineString, Point, Polygon, fromstr,
- )
- from django.contrib.gis.measure import Area
- from django.db import connection
- from django.db.models import Sum
- from django.test import TestCase, skipUnlessDBFeature
- from ..utils import mysql, oracle, postgis, spatialite
- from .models import City, Country, CountryWebMercator, State, Track
- @skipUnlessDBFeature("gis_enabled")
- class GISFunctionsTests(TestCase):
- """
- Testing functions from django/contrib/gis/db/models/functions.py.
- Area/Distance/Length/Perimeter are tested in distapp/tests.
- Please keep the tests in function's alphabetic order.
- """
- fixtures = ['initial']
- def test_asgeojson(self):
-
- if not connection.features.has_AsGeoJSON_function:
- with self.assertRaises(NotImplementedError):
- list(Country.objects.annotate(json=functions.AsGeoJSON('mpoly')))
- return
- pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
- houston_json = (
- '{"type":"Point","crs":{"type":"name","properties":'
- '{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}'
- )
- victoria_json = (
- '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],'
- '"coordinates":[-123.305196,48.462611]}'
- )
- chicago_json = (
- '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},'
- '"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
- )
-
- if mysql:
- houston_json = json.loads(houston_json)
- del houston_json['crs']
- chicago_json = json.loads(chicago_json)
- del chicago_json['crs']
-
- with self.assertRaises(TypeError):
- City.objects.annotate(geojson=functions.AsGeoJSON('point', precision='foo'))
-
-
-
- self.assertJSONEqual(
- pueblo_json,
- City.objects.annotate(geojson=functions.AsGeoJSON('point')).get(name='Pueblo').geojson
- )
-
-
-
- self.assertJSONEqual(
- City.objects.annotate(json=functions.AsGeoJSON('point', crs=True)).get(name='Houston').json,
- houston_json,
- )
-
-
-
- self.assertJSONEqual(
- victoria_json,
- City.objects.annotate(
- geojson=functions.AsGeoJSON('point', bbox=True)
- ).get(name='Victoria').geojson
- )
-
-
-
- self.assertJSONEqual(
- City.objects.annotate(
- geojson=functions.AsGeoJSON('point', bbox=True, crs=True, precision=5)
- ).get(name='Chicago').geojson,
- chicago_json,
- )
- @skipUnlessDBFeature("has_AsGML_function")
- def test_asgml(self):
-
-
- qs = City.objects.all()
- with self.assertRaises(TypeError):
- qs.annotate(gml=functions.AsGML('name'))
- ptown = City.objects.annotate(gml=functions.AsGML('point', precision=9)).get(name='Pueblo')
- if oracle:
-
- gml_regex = re.compile(
- r'^<gml:Point srsName="EPSG:4326" xmlns:gml="http://www.opengis.net/gml">'
- r'<gml:coordinates decimal="\." cs="," ts=" ">-104.60925\d+,38.25500\d+ '
- r'</gml:coordinates></gml:Point>'
- )
- else:
- gml_regex = re.compile(
- r'^<gml:Point srsName="EPSG:4326"><gml:coordinates>'
- r'-104\.60925\d+,38\.255001</gml:coordinates></gml:Point>'
- )
- self.assertTrue(gml_regex.match(ptown.gml))
- self.assertIn(
- '<gml:pos srsDimension="2">',
- City.objects.annotate(gml=functions.AsGML('point', version=3)).get(name='Pueblo').gml
- )
- @skipUnlessDBFeature("has_AsKML_function")
- def test_askml(self):
-
-
- with self.assertRaises(TypeError):
- City.objects.annotate(kml=functions.AsKML('name'))
-
- qs = City.objects.annotate(kml=functions.AsKML('point', precision=9))
- ptown = qs.get(name='Pueblo')
- self.assertEqual('<Point><coordinates>-104.609252,38.255001</coordinates></Point>', ptown.kml)
-
- self.assertEqual(qs.get(name='Pueblo').kml, ptown.kml)
- @skipUnlessDBFeature("has_AsSVG_function")
- def test_assvg(self):
- with self.assertRaises(TypeError):
- City.objects.annotate(svg=functions.AsSVG('point', precision='foo'))
-
- svg1 = 'cx="-104.609252" cy="-38.255001"'
-
-
- svg2 = svg1.replace('c', '')
- self.assertEqual(svg1, City.objects.annotate(svg=functions.AsSVG('point')).get(name='Pueblo').svg)
- self.assertEqual(svg2, City.objects.annotate(svg=functions.AsSVG('point', relative=5)).get(name='Pueblo').svg)
- @skipUnlessDBFeature("has_BoundingCircle_function")
- def test_bounding_circle(self):
- def circle_num_points(num_seg):
-
- return (4 * num_seg) + 1
- expected_areas = (169, 136) if postgis else (171, 126)
- qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly')).order_by('name')
- self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
- self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)
- if postgis:
-
- self.assertEqual(qs[0].circle.num_points, circle_num_points(48))
- self.assertEqual(qs[1].circle.num_points, circle_num_points(48))
- qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly', num_seg=12)).order_by('name')
- if postgis:
- self.assertGreater(qs[0].circle.area, 168.4, 0)
- self.assertLess(qs[0].circle.area, 169.5, 0)
- self.assertAlmostEqual(qs[1].circle.area, 136, 0)
- self.assertEqual(qs[0].circle.num_points, circle_num_points(12))
- self.assertEqual(qs[1].circle.num_points, circle_num_points(12))
- else:
- self.assertAlmostEqual(qs[0].circle.area, expected_areas[0], 0)
- self.assertAlmostEqual(qs[1].circle.area, expected_areas[1], 0)
- @skipUnlessDBFeature("has_Centroid_function")
- def test_centroid(self):
- qs = State.objects.exclude(poly__isnull=True).annotate(centroid=functions.Centroid('poly'))
- tol = 1.8 if mysql else (0.1 if oracle else 0.00001)
- for state in qs:
- self.assertTrue(state.poly.centroid.equals_exact(state.centroid, tol))
- with self.assertRaisesMessage(TypeError, "'Centroid' takes exactly 1 argument (2 given)"):
- State.objects.annotate(centroid=functions.Centroid('poly', 'poly'))
- @skipUnlessDBFeature("has_Difference_function")
- def test_difference(self):
- geom = Point(5, 23, srid=4326)
- qs = Country.objects.annotate(diff=functions.Difference('mpoly', geom))
-
- if oracle:
- qs = qs.exclude(name='Texas')
- for c in qs:
- self.assertTrue(c.mpoly.difference(geom).equals(c.diff))
- @skipUnlessDBFeature("has_Difference_function", "has_Transform_function")
- def test_difference_mixed_srid(self):
- """Testing with mixed SRID (Country has default 4326)."""
- geom = Point(556597.4, 2632018.6, srid=3857)
- qs = Country.objects.annotate(difference=functions.Difference('mpoly', geom))
-
- if oracle:
- qs = qs.exclude(name='Texas')
- for c in qs:
- self.assertTrue(c.mpoly.difference(geom).equals(c.difference))
- @skipUnlessDBFeature("has_Envelope_function")
- def test_envelope(self):
- countries = Country.objects.annotate(envelope=functions.Envelope('mpoly'))
- for country in countries:
- self.assertIsInstance(country.envelope, Polygon)
- @skipUnlessDBFeature("has_ForceRHR_function")
- def test_force_rhr(self):
- rings = (
- ((0, 0), (5, 0), (0, 5), (0, 0)),
- ((1, 1), (1, 3), (3, 1), (1, 1)),
- )
- rhr_rings = (
- ((0, 0), (0, 5), (5, 0), (0, 0)),
- ((1, 1), (3, 1), (1, 3), (1, 1)),
- )
- State.objects.create(name='Foo', poly=Polygon(*rings))
- st = State.objects.annotate(force_rhr=functions.ForceRHR('poly')).get(name='Foo')
- self.assertEqual(rhr_rings, st.force_rhr.coords)
- @skipUnlessDBFeature("has_GeoHash_function")
- def test_geohash(self):
-
-
-
- ref_hash = '9vk1mfq8jx0c8e0386z6'
- h1 = City.objects.annotate(geohash=functions.GeoHash('point')).get(name='Houston')
- h2 = City.objects.annotate(geohash=functions.GeoHash('point', precision=5)).get(name='Houston')
- self.assertEqual(ref_hash, h1.geohash[:len(ref_hash)])
- self.assertEqual(ref_hash[:5], h2.geohash)
- @skipUnlessDBFeature("has_Intersection_function")
- def test_intersection(self):
- geom = Point(5, 23, srid=4326)
- qs = Country.objects.annotate(inter=functions.Intersection('mpoly', geom))
- for c in qs:
- if spatialite or (mysql and not connection.ops.uses_invalid_empty_geometry_collection) or oracle:
-
- expected = None
- else:
- expected = c.mpoly.intersection(geom)
- self.assertEqual(c.inter, expected)
- @skipUnlessDBFeature("has_IsValid_function")
- def test_isvalid(self):
- valid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
- invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))')
- State.objects.create(name='valid', poly=valid_geom)
- State.objects.create(name='invalid', poly=invalid_geom)
- valid = State.objects.filter(name='valid').annotate(isvalid=functions.IsValid('poly')).first()
- invalid = State.objects.filter(name='invalid').annotate(isvalid=functions.IsValid('poly')).first()
- self.assertIs(valid.isvalid, True)
- self.assertIs(invalid.isvalid, False)
- @skipUnlessDBFeature("has_Area_function")
- def test_area_with_regular_aggregate(self):
-
- for c in Country.objects.all():
- CountryWebMercator.objects.create(name=c.name, mpoly=c.mpoly)
-
- qs = CountryWebMercator.objects.annotate(area_sum=Sum(functions.Area('mpoly')))
-
-
- for c in qs.defer('mpoly'):
- result = c.area_sum
-
- if isinstance(result, Area):
- result = result.sq_m
- self.assertAlmostEqual((result - c.mpoly.area) / c.mpoly.area, 0)
- @skipUnlessDBFeature("has_Area_function")
- def test_area_lookups(self):
-
- CountryWebMercator.objects.bulk_create(
- CountryWebMercator(name=c.name, mpoly=c.mpoly.transform(3857, clone=True))
- for c in Country.objects.all()
- )
- qs = CountryWebMercator.objects.annotate(area=functions.Area('mpoly'))
- self.assertEqual(qs.get(area__lt=Area(sq_km=500000)), CountryWebMercator.objects.get(name='New Zealand'))
- with self.assertRaisesMessage(ValueError, 'AreaField only accepts Area measurement objects.'):
- qs.get(area__lt=500000)
- @skipUnlessDBFeature("has_MakeValid_function")
- def test_make_valid(self):
- invalid_geom = fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 1 1, 1 0, 0 0))')
- State.objects.create(name='invalid', poly=invalid_geom)
- invalid = State.objects.filter(name='invalid').annotate(repaired=functions.MakeValid('poly')).first()
- self.assertIs(invalid.repaired.valid, True)
- self.assertEqual(invalid.repaired, fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))', srid=invalid.poly.srid))
- @skipUnlessDBFeature("has_MemSize_function")
- def test_memsize(self):
- ptown = City.objects.annotate(size=functions.MemSize('point')).get(name='Pueblo')
- self.assertTrue(20 <= ptown.size <= 40)
- @skipUnlessDBFeature("has_NumGeom_function")
- def test_num_geom(self):
-
- for c in Country.objects.annotate(num_geom=functions.NumGeometries('mpoly')):
- self.assertEqual(2, c.num_geom)
- qs = City.objects.filter(point__isnull=False).annotate(num_geom=functions.NumGeometries('point'))
- for city in qs:
-
-
- if mysql:
- self.assertIsNone(city.num_geom)
- else:
- self.assertEqual(1, city.num_geom)
- @skipUnlessDBFeature("has_NumPoint_function")
- def test_num_points(self):
- coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)]
- Track.objects.create(name='Foo', line=LineString(coords))
- qs = Track.objects.annotate(num_points=functions.NumPoints('line'))
- self.assertEqual(qs.first().num_points, 2)
- mpoly_qs = Country.objects.annotate(num_points=functions.NumPoints('mpoly'))
- if not connection.features.supports_num_points_poly:
- msg = 'NumPoints can only operate on LineString content on this database.'
- with self.assertRaisesMessage(TypeError, msg):
- list(mpoly_qs)
- return
- for c in mpoly_qs:
- self.assertEqual(c.mpoly.num_points, c.num_points)
- for c in City.objects.annotate(num_points=functions.NumPoints('point')):
- self.assertEqual(c.num_points, 1)
- @skipUnlessDBFeature("has_PointOnSurface_function")
- def test_point_on_surface(self):
-
- if oracle:
-
-
- ref = {'New Zealand': fromstr('POINT (174.616364 -36.100861)', srid=4326),
- 'Texas': fromstr('POINT (-103.002434 36.500397)', srid=4326),
- }
- else:
-
-
- ref = {'New Zealand': Country.objects.get(name='New Zealand').mpoly.point_on_surface,
- 'Texas': Country.objects.get(name='Texas').mpoly.point_on_surface
- }
- qs = Country.objects.annotate(point_on_surface=functions.PointOnSurface('mpoly'))
- for country in qs:
- tol = 0.00001
- self.assertTrue(ref[country.name].equals_exact(country.point_on_surface, tol))
- @skipUnlessDBFeature("has_Reverse_function")
- def test_reverse_geom(self):
- coords = [(-95.363151, 29.763374), (-95.448601, 29.713803)]
- Track.objects.create(name='Foo', line=LineString(coords))
- track = Track.objects.annotate(reverse_geom=functions.Reverse('line')).get(name='Foo')
- coords.reverse()
- self.assertEqual(tuple(coords), track.reverse_geom.coords)
- @skipUnlessDBFeature("has_Scale_function")
- def test_scale(self):
- xfac, yfac = 2, 3
- tol = 5
- qs = Country.objects.annotate(scaled=functions.Scale('mpoly', xfac, yfac))
- for country in qs:
- for p1, p2 in zip(country.mpoly, country.scaled):
- for r1, r2 in zip(p1, p2):
- for c1, c2 in zip(r1.coords, r2.coords):
- self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
- self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
-
- qs = Country.objects.annotate(scaled=functions.Scale('mpoly', 1.5, Decimal('2.5')))
- self.assertGreater(qs[0].scaled.area, qs[0].mpoly.area)
- @skipUnlessDBFeature("has_SnapToGrid_function")
- def test_snap_to_grid(self):
-
- for bad_args in ((), range(3), range(5)):
- with self.assertRaises(ValueError):
- Country.objects.annotate(snap=functions.SnapToGrid('mpoly', *bad_args))
- for bad_args in (('1.0',), (1.0, None), tuple(map(str, range(4)))):
- with self.assertRaises(TypeError):
- Country.objects.annotate(snap=functions.SnapToGrid('mpoly', *bad_args))
-
-
- wkt = ('MULTIPOLYGON(((12.41580 43.95795,12.45055 43.97972,12.45389 43.98167,'
- '12.46250 43.98472,12.47167 43.98694,12.49278 43.98917,'
- '12.50555 43.98861,12.51000 43.98694,12.51028 43.98277,'
- '12.51167 43.94333,12.51056 43.93916,12.49639 43.92333,'
- '12.49500 43.91472,12.48778 43.90583,12.47444 43.89722,'
- '12.46472 43.89555,12.45917 43.89611,12.41639 43.90472,'
- '12.41222 43.90610,12.40782 43.91366,12.40389 43.92667,'
- '12.40500 43.94833,12.40889 43.95499,12.41580 43.95795)))')
- Country.objects.create(name='San Marino', mpoly=fromstr(wkt))
-
-
- tol = 0.000000001
-
-
- ref = fromstr('MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))')
- self.assertTrue(
- ref.equals_exact(
- Country.objects.annotate(
- snap=functions.SnapToGrid('mpoly', 0.1)
- ).get(name='San Marino').snap,
- tol
- )
- )
-
-
- ref = fromstr('MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))')
- self.assertTrue(
- ref.equals_exact(
- Country.objects.annotate(
- snap=functions.SnapToGrid('mpoly', 0.05, 0.23)
- ).get(name='San Marino').snap,
- tol
- )
- )
-
-
- ref = fromstr(
- 'MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))'
- )
- self.assertTrue(
- ref.equals_exact(
- Country.objects.annotate(
- snap=functions.SnapToGrid('mpoly', 0.05, 0.23, 0.5, 0.17)
- ).get(name='San Marino').snap,
- tol
- )
- )
- @skipUnlessDBFeature("has_SymDifference_function")
- def test_sym_difference(self):
- geom = Point(5, 23, srid=4326)
- qs = Country.objects.annotate(sym_difference=functions.SymDifference('mpoly', geom))
-
- if oracle:
- qs = qs.exclude(name='Texas')
- for country in qs:
- self.assertTrue(country.mpoly.sym_difference(geom).equals(country.sym_difference))
- @skipUnlessDBFeature("has_Transform_function")
- def test_transform(self):
-
- ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
- prec = 3
-
-
- h = City.objects.annotate(pt=functions.Transform('point', ptown.srid)).get(name='Pueblo')
- self.assertEqual(2774, h.pt.srid)
- self.assertAlmostEqual(ptown.x, h.pt.x, prec)
- self.assertAlmostEqual(ptown.y, h.pt.y, prec)
- @skipUnlessDBFeature("has_Translate_function")
- def test_translate(self):
- xfac, yfac = 5, -23
- qs = Country.objects.annotate(translated=functions.Translate('mpoly', xfac, yfac))
- for c in qs:
- for p1, p2 in zip(c.mpoly, c.translated):
- for r1, r2 in zip(p1, p2):
- for c1, c2 in zip(r1.coords, r2.coords):
-
- self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
- self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
-
- @skipUnlessDBFeature(
- "has_Difference_function", "has_Intersection_function",
- "has_SymDifference_function", "has_Union_function")
- def test_diff_intersection_union(self):
- geom = Point(5, 23, srid=4326)
- qs = Country.objects.all().annotate(
- difference=functions.Difference('mpoly', geom),
- sym_difference=functions.SymDifference('mpoly', geom),
- union=functions.Union('mpoly', geom),
- intersection=functions.Intersection('mpoly', geom),
- )
- if oracle:
-
-
-
- return
- for c in qs:
- self.assertTrue(c.mpoly.difference(geom).equals(c.difference))
- if not (spatialite or mysql):
- self.assertEqual(c.mpoly.intersection(geom), c.intersection)
- self.assertTrue(c.mpoly.sym_difference(geom).equals(c.sym_difference))
- self.assertTrue(c.mpoly.union(geom).equals(c.union))
- @skipUnlessDBFeature("has_Union_function")
- def test_union(self):
- """Union with all combinations of geometries/geometry fields."""
- geom = Point(-95.363151, 29.763374, srid=4326)
- 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)
- 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')
|