mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Refs #33476 -- Reformatted code with Black.
This commit is contained in:
committed by
Mariusz Felisiak
parent
f68fa8b45d
commit
9c19aff7c7
@@ -1,32 +1,52 @@
|
||||
from django.contrib.gis.db.models.functions import (
|
||||
Area, Distance, Length, Perimeter, Transform, Union,
|
||||
Area,
|
||||
Distance,
|
||||
Length,
|
||||
Perimeter,
|
||||
Transform,
|
||||
Union,
|
||||
)
|
||||
from django.contrib.gis.geos import GEOSGeometry, LineString, Point
|
||||
from django.contrib.gis.measure import D # alias for Distance
|
||||
from django.db import NotSupportedError, connection
|
||||
from django.db.models import (
|
||||
Case, Count, Exists, F, IntegerField, OuterRef, Q, Value, When,
|
||||
Case,
|
||||
Count,
|
||||
Exists,
|
||||
F,
|
||||
IntegerField,
|
||||
OuterRef,
|
||||
Q,
|
||||
Value,
|
||||
When,
|
||||
)
|
||||
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
|
||||
from ..utils import FuncTestMixin
|
||||
from .models import (
|
||||
AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt,
|
||||
SouthTexasInterstate, SouthTexasZipcode,
|
||||
AustraliaCity,
|
||||
CensusZipcode,
|
||||
Interstate,
|
||||
SouthTexasCity,
|
||||
SouthTexasCityFt,
|
||||
SouthTexasInterstate,
|
||||
SouthTexasZipcode,
|
||||
)
|
||||
|
||||
|
||||
class DistanceTest(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def setUp(self):
|
||||
# A point we are testing distances with -- using a WGS84
|
||||
# coordinate that'll be implicitly transformed to that to
|
||||
# the coordinate system of the field, EPSG:32140 (Texas South Central
|
||||
# w/units in meters)
|
||||
self.stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326)
|
||||
self.stx_pnt = GEOSGeometry(
|
||||
"POINT (-95.370401017314293 29.704867409475465)", 4326
|
||||
)
|
||||
# Another one for Australia
|
||||
self.au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326)
|
||||
self.au_pnt = GEOSGeometry("POINT (150.791 -34.4919)", 4326)
|
||||
|
||||
def get_names(self, qs):
|
||||
cities = [c.name for c in qs]
|
||||
@@ -57,8 +77,8 @@ class DistanceTest(TestCase):
|
||||
au_dists = [(0.5, 32000), D(km=32), D(mi=19.884)]
|
||||
|
||||
# Expected cities for Australia and Texas.
|
||||
tx_cities = ['Downtown Houston', 'Southside Place']
|
||||
au_cities = ['Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong']
|
||||
tx_cities = ["Downtown Houston", "Southside Place"]
|
||||
au_cities = ["Mittagong", "Shellharbour", "Thirroul", "Wollongong"]
|
||||
|
||||
# Performing distance queries on two projected coordinate systems one
|
||||
# with units in meters and the other in units of U.S. survey feet.
|
||||
@@ -74,7 +94,9 @@ class DistanceTest(TestCase):
|
||||
self.assertEqual(tx_cities, self.get_names(qs))
|
||||
|
||||
# With a complex geometry expression
|
||||
self.assertFalse(SouthTexasCity.objects.exclude(point__dwithin=(Union('point', 'point'), 0)))
|
||||
self.assertFalse(
|
||||
SouthTexasCity.objects.exclude(point__dwithin=(Union("point", "point"), 0))
|
||||
)
|
||||
|
||||
# Now performing the `dwithin` queries on a geodetic coordinate system.
|
||||
for dist in au_dists:
|
||||
@@ -89,15 +111,20 @@ class DistanceTest(TestCase):
|
||||
dist = dist[0]
|
||||
|
||||
# Creating the query set.
|
||||
qs = AustraliaCity.objects.order_by('name')
|
||||
qs = AustraliaCity.objects.order_by("name")
|
||||
if type_error:
|
||||
# A ValueError should be raised on PostGIS when trying to
|
||||
# pass Distance objects into a DWithin query using a
|
||||
# geodetic field.
|
||||
with self.assertRaises(ValueError):
|
||||
AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, dist)).count()
|
||||
AustraliaCity.objects.filter(
|
||||
point__dwithin=(self.au_pnt, dist)
|
||||
).count()
|
||||
else:
|
||||
self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
|
||||
self.assertEqual(
|
||||
au_cities,
|
||||
self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))),
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_distances_lookups")
|
||||
def test_distance_lookups(self):
|
||||
@@ -108,20 +135,26 @@ class DistanceTest(TestCase):
|
||||
# (thus, Houston and Southside place will be excluded as tested in
|
||||
# the `test02_dwithin` above).
|
||||
for model in [SouthTexasCity, SouthTexasCityFt]:
|
||||
stx_pnt = self.stx_pnt.transform(model._meta.get_field('point').srid, clone=True)
|
||||
stx_pnt = self.stx_pnt.transform(
|
||||
model._meta.get_field("point").srid, clone=True
|
||||
)
|
||||
qs = model.objects.filter(point__distance_gte=(stx_pnt, D(km=7))).filter(
|
||||
point__distance_lte=(stx_pnt, D(km=20)),
|
||||
)
|
||||
cities = self.get_names(qs)
|
||||
self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
|
||||
self.assertEqual(cities, ["Bellaire", "Pearland", "West University Place"])
|
||||
|
||||
# Doing a distance query using Polygons instead of a Point.
|
||||
z = SouthTexasZipcode.objects.get(name='77005')
|
||||
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=275)))
|
||||
self.assertEqual(['77025', '77401'], self.get_names(qs))
|
||||
z = SouthTexasZipcode.objects.get(name="77005")
|
||||
qs = SouthTexasZipcode.objects.exclude(name="77005").filter(
|
||||
poly__distance_lte=(z.poly, D(m=275))
|
||||
)
|
||||
self.assertEqual(["77025", "77401"], self.get_names(qs))
|
||||
# If we add a little more distance 77002 should be included.
|
||||
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
|
||||
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
|
||||
qs = SouthTexasZipcode.objects.exclude(name="77005").filter(
|
||||
poly__distance_lte=(z.poly, D(m=300))
|
||||
)
|
||||
self.assertEqual(["77002", "77025", "77401"], self.get_names(qs))
|
||||
|
||||
@skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic")
|
||||
def test_geodetic_distance_lookups(self):
|
||||
@@ -130,12 +163,18 @@ class DistanceTest(TestCase):
|
||||
"""
|
||||
# Line is from Canberra to Sydney. Query is for all other cities within
|
||||
# a 100km of that line (which should exclude only Hobart & Adelaide).
|
||||
line = GEOSGeometry('LINESTRING(144.9630 -37.8143,151.2607 -33.8870)', 4326)
|
||||
line = GEOSGeometry("LINESTRING(144.9630 -37.8143,151.2607 -33.8870)", 4326)
|
||||
dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100)))
|
||||
expected_cities = [
|
||||
'Batemans Bay', 'Canberra', 'Hillsdale',
|
||||
'Melbourne', 'Mittagong', 'Shellharbour',
|
||||
'Sydney', 'Thirroul', 'Wollongong',
|
||||
"Batemans Bay",
|
||||
"Canberra",
|
||||
"Hillsdale",
|
||||
"Melbourne",
|
||||
"Mittagong",
|
||||
"Shellharbour",
|
||||
"Sydney",
|
||||
"Thirroul",
|
||||
"Wollongong",
|
||||
]
|
||||
if connection.ops.spatialite:
|
||||
# SpatiaLite is less accurate and returns 102.8km for Batemans Bay.
|
||||
@@ -144,141 +183,185 @@ class DistanceTest(TestCase):
|
||||
|
||||
msg = "2, 3, or 4-element tuple required for 'distance_lte' lookup."
|
||||
with self.assertRaisesMessage(ValueError, msg): # Too many params.
|
||||
len(AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4', None)))
|
||||
len(
|
||||
AustraliaCity.objects.filter(
|
||||
point__distance_lte=(
|
||||
"POINT(5 23)",
|
||||
D(km=100),
|
||||
"spheroid",
|
||||
"4",
|
||||
None,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
with self.assertRaisesMessage(ValueError, msg): # Too few params.
|
||||
len(AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)))
|
||||
len(AustraliaCity.objects.filter(point__distance_lte=("POINT(5 23)",)))
|
||||
|
||||
msg = "For 4-element tuples the last argument must be the 'spheroid' directive."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
len(AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
|
||||
len(
|
||||
AustraliaCity.objects.filter(
|
||||
point__distance_lte=("POINT(5 23)", D(km=100), "spheroid", "4")
|
||||
)
|
||||
)
|
||||
|
||||
# Getting all cities w/in 550 miles of Hobart.
|
||||
hobart = AustraliaCity.objects.get(name='Hobart')
|
||||
qs = AustraliaCity.objects.exclude(name='Hobart').filter(point__distance_lte=(hobart.point, D(mi=550)))
|
||||
hobart = AustraliaCity.objects.get(name="Hobart")
|
||||
qs = AustraliaCity.objects.exclude(name="Hobart").filter(
|
||||
point__distance_lte=(hobart.point, D(mi=550))
|
||||
)
|
||||
cities = self.get_names(qs)
|
||||
self.assertEqual(cities, ['Batemans Bay', 'Canberra', 'Melbourne'])
|
||||
self.assertEqual(cities, ["Batemans Bay", "Canberra", "Melbourne"])
|
||||
|
||||
# Cities that are either really close or really far from Wollongong --
|
||||
# and using different units of distance.
|
||||
wollongong = AustraliaCity.objects.get(name='Wollongong')
|
||||
wollongong = AustraliaCity.objects.get(name="Wollongong")
|
||||
d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles.
|
||||
|
||||
# Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
|
||||
gq1 = Q(point__distance_lte=(wollongong.point, d1))
|
||||
gq2 = Q(point__distance_gte=(wollongong.point, d2))
|
||||
qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2)
|
||||
qs1 = AustraliaCity.objects.exclude(name="Wollongong").filter(gq1 | gq2)
|
||||
|
||||
# Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
|
||||
# instead (we should get the same results b/c accuracy variance won't matter
|
||||
# in this test case).
|
||||
querysets = [qs1]
|
||||
if connection.features.has_DistanceSpheroid_function:
|
||||
gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
|
||||
gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
|
||||
qs2 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq3 | gq4)
|
||||
gq3 = Q(point__distance_lte=(wollongong.point, d1, "spheroid"))
|
||||
gq4 = Q(point__distance_gte=(wollongong.point, d2, "spheroid"))
|
||||
qs2 = AustraliaCity.objects.exclude(name="Wollongong").filter(gq3 | gq4)
|
||||
querysets.append(qs2)
|
||||
|
||||
for qs in querysets:
|
||||
cities = self.get_names(qs)
|
||||
self.assertEqual(cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul'])
|
||||
self.assertEqual(cities, ["Adelaide", "Hobart", "Shellharbour", "Thirroul"])
|
||||
|
||||
@skipUnlessDBFeature("supports_distances_lookups")
|
||||
def test_distance_lookups_with_expression_rhs(self):
|
||||
stx_pnt = self.stx_pnt.transform(SouthTexasCity._meta.get_field('point').srid, clone=True)
|
||||
stx_pnt = self.stx_pnt.transform(
|
||||
SouthTexasCity._meta.get_field("point").srid, clone=True
|
||||
)
|
||||
qs = SouthTexasCity.objects.filter(
|
||||
point__distance_lte=(stx_pnt, F('radius')),
|
||||
).order_by('name')
|
||||
point__distance_lte=(stx_pnt, F("radius")),
|
||||
).order_by("name")
|
||||
self.assertEqual(
|
||||
self.get_names(qs),
|
||||
['Bellaire', 'Downtown Houston', 'Southside Place', 'West University Place']
|
||||
[
|
||||
"Bellaire",
|
||||
"Downtown Houston",
|
||||
"Southside Place",
|
||||
"West University Place",
|
||||
],
|
||||
)
|
||||
|
||||
# With a combined expression
|
||||
qs = SouthTexasCity.objects.filter(
|
||||
point__distance_lte=(stx_pnt, F('radius') * 2),
|
||||
).order_by('name')
|
||||
point__distance_lte=(stx_pnt, F("radius") * 2),
|
||||
).order_by("name")
|
||||
self.assertEqual(len(qs), 5)
|
||||
self.assertIn('Pearland', self.get_names(qs))
|
||||
self.assertIn("Pearland", self.get_names(qs))
|
||||
|
||||
# With spheroid param
|
||||
if connection.features.supports_distance_geodetic:
|
||||
hobart = AustraliaCity.objects.get(name='Hobart')
|
||||
hobart = AustraliaCity.objects.get(name="Hobart")
|
||||
AustraliaCity.objects.update(ref_point=hobart.point)
|
||||
for ref_point in [hobart.point, F('ref_point')]:
|
||||
for ref_point in [hobart.point, F("ref_point")]:
|
||||
qs = AustraliaCity.objects.filter(
|
||||
point__distance_lte=(ref_point, F('radius') * 70, 'spheroid'),
|
||||
).order_by('name')
|
||||
self.assertEqual(self.get_names(qs), ['Canberra', 'Hobart', 'Melbourne'])
|
||||
point__distance_lte=(ref_point, F("radius") * 70, "spheroid"),
|
||||
).order_by("name")
|
||||
self.assertEqual(
|
||||
self.get_names(qs), ["Canberra", "Hobart", "Melbourne"]
|
||||
)
|
||||
|
||||
# With a complex geometry expression
|
||||
self.assertFalse(SouthTexasCity.objects.filter(point__distance_gt=(Union('point', 'point'), 0)))
|
||||
self.assertFalse(
|
||||
SouthTexasCity.objects.filter(
|
||||
point__distance_gt=(Union("point", "point"), 0)
|
||||
)
|
||||
)
|
||||
self.assertEqual(
|
||||
SouthTexasCity.objects.filter(point__distance_lte=(Union('point', 'point'), 0)).count(),
|
||||
SouthTexasCity.objects.filter(
|
||||
point__distance_lte=(Union("point", "point"), 0)
|
||||
).count(),
|
||||
SouthTexasCity.objects.count(),
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('supports_distances_lookups')
|
||||
@skipUnlessDBFeature("supports_distances_lookups")
|
||||
def test_distance_annotation_group_by(self):
|
||||
stx_pnt = self.stx_pnt.transform(
|
||||
SouthTexasCity._meta.get_field('point').srid,
|
||||
SouthTexasCity._meta.get_field("point").srid,
|
||||
clone=True,
|
||||
)
|
||||
qs = SouthTexasCity.objects.annotate(
|
||||
relative_distance=Case(
|
||||
When(point__distance_lte=(stx_pnt, D(km=20)), then=Value(20)),
|
||||
default=Value(100),
|
||||
output_field=IntegerField(),
|
||||
),
|
||||
).values('relative_distance').annotate(count=Count('pk'))
|
||||
self.assertCountEqual(qs, [
|
||||
{'relative_distance': 20, 'count': 5},
|
||||
{'relative_distance': 100, 'count': 4},
|
||||
])
|
||||
qs = (
|
||||
SouthTexasCity.objects.annotate(
|
||||
relative_distance=Case(
|
||||
When(point__distance_lte=(stx_pnt, D(km=20)), then=Value(20)),
|
||||
default=Value(100),
|
||||
output_field=IntegerField(),
|
||||
),
|
||||
)
|
||||
.values("relative_distance")
|
||||
.annotate(count=Count("pk"))
|
||||
)
|
||||
self.assertCountEqual(
|
||||
qs,
|
||||
[
|
||||
{"relative_distance": 20, "count": 5},
|
||||
{"relative_distance": 100, "count": 4},
|
||||
],
|
||||
)
|
||||
|
||||
def test_mysql_geodetic_distance_error(self):
|
||||
if not connection.ops.mysql:
|
||||
self.skipTest('This is a MySQL-specific test.')
|
||||
msg = 'Only numeric values of degree units are allowed on geodetic distance queries.'
|
||||
self.skipTest("This is a MySQL-specific test.")
|
||||
msg = "Only numeric values of degree units are allowed on geodetic distance queries."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
AustraliaCity.objects.filter(point__distance_lte=(Point(0, 0), D(m=100))).exists()
|
||||
AustraliaCity.objects.filter(
|
||||
point__distance_lte=(Point(0, 0), D(m=100))
|
||||
).exists()
|
||||
|
||||
@skipUnlessDBFeature('supports_dwithin_lookup')
|
||||
@skipUnlessDBFeature("supports_dwithin_lookup")
|
||||
def test_dwithin_subquery(self):
|
||||
"""dwithin lookup in a subquery using OuterRef as a parameter."""
|
||||
qs = CensusZipcode.objects.annotate(
|
||||
annotated_value=Exists(SouthTexasCity.objects.filter(
|
||||
point__dwithin=(OuterRef('poly'), D(m=10)),
|
||||
))
|
||||
annotated_value=Exists(
|
||||
SouthTexasCity.objects.filter(
|
||||
point__dwithin=(OuterRef("poly"), D(m=10)),
|
||||
)
|
||||
)
|
||||
).filter(annotated_value=True)
|
||||
self.assertEqual(self.get_names(qs), ['77002', '77025', '77401'])
|
||||
self.assertEqual(self.get_names(qs), ["77002", "77025", "77401"])
|
||||
|
||||
@skipUnlessDBFeature('supports_dwithin_lookup', 'supports_dwithin_distance_expr')
|
||||
@skipUnlessDBFeature("supports_dwithin_lookup", "supports_dwithin_distance_expr")
|
||||
def test_dwithin_with_expression_rhs(self):
|
||||
# LineString of Wollongong and Adelaide coords.
|
||||
ls = LineString(((150.902, -34.4245), (138.6, -34.9258)), srid=4326)
|
||||
qs = AustraliaCity.objects.filter(
|
||||
point__dwithin=(ls, F('allowed_distance')),
|
||||
).order_by('name')
|
||||
point__dwithin=(ls, F("allowed_distance")),
|
||||
).order_by("name")
|
||||
self.assertEqual(
|
||||
self.get_names(qs),
|
||||
['Adelaide', 'Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong'],
|
||||
["Adelaide", "Mittagong", "Shellharbour", "Thirroul", "Wollongong"],
|
||||
)
|
||||
|
||||
@skipIfDBFeature('supports_dwithin_distance_expr')
|
||||
@skipIfDBFeature("supports_dwithin_distance_expr")
|
||||
def test_dwithin_with_expression_rhs_not_supported(self):
|
||||
ls = LineString(((150.902, -34.4245), (138.6, -34.9258)), srid=4326)
|
||||
msg = (
|
||||
'This backend does not support expressions for specifying '
|
||||
'distance in the dwithin lookup.'
|
||||
"This backend does not support expressions for specifying "
|
||||
"distance in the dwithin lookup."
|
||||
)
|
||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||
list(AustraliaCity.objects.filter(
|
||||
point__dwithin=(ls, F('allowed_distance')),
|
||||
))
|
||||
list(
|
||||
AustraliaCity.objects.filter(
|
||||
point__dwithin=(ls, F("allowed_distance")),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
'''
|
||||
"""
|
||||
=============================
|
||||
Distance functions on PostGIS
|
||||
=============================
|
||||
@@ -310,20 +393,27 @@ ST_Distance(geom1, geom2, use_ellipsoid=False) | N/A | OK (
|
||||
|
||||
Perimeter(geom1) | OK | :-( (degrees)
|
||||
|
||||
''' # NOQA
|
||||
""" # NOQA
|
||||
|
||||
|
||||
class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
@skipUnlessDBFeature("has_Area_function")
|
||||
def test_area(self):
|
||||
# Reference queries:
|
||||
# SELECT ST_Area(poly) FROM distapp_southtexaszipcode;
|
||||
area_sq_m = [5437908.90234375, 10183031.4389648, 11254471.0073242, 9881708.91772461]
|
||||
area_sq_m = [
|
||||
5437908.90234375,
|
||||
10183031.4389648,
|
||||
11254471.0073242,
|
||||
9881708.91772461,
|
||||
]
|
||||
# Tolerance has to be lower for Oracle
|
||||
tol = 2
|
||||
for i, z in enumerate(SouthTexasZipcode.objects.annotate(area=Area('poly')).order_by('name')):
|
||||
for i, z in enumerate(
|
||||
SouthTexasZipcode.objects.annotate(area=Area("poly")).order_by("name")
|
||||
):
|
||||
self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol)
|
||||
|
||||
@skipUnlessDBFeature("has_Distance_function")
|
||||
@@ -332,14 +422,14 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
Test a simple distance query, with projected coordinates and without
|
||||
transformation.
|
||||
"""
|
||||
lagrange = GEOSGeometry('POINT(805066.295722839 4231496.29461335)', 32140)
|
||||
houston = SouthTexasCity.objects.annotate(dist=Distance('point', lagrange)).order_by('id').first()
|
||||
tol = 2 if connection.ops.oracle else 5
|
||||
self.assertAlmostEqual(
|
||||
houston.dist.m,
|
||||
147075.069813,
|
||||
tol
|
||||
lagrange = GEOSGeometry("POINT(805066.295722839 4231496.29461335)", 32140)
|
||||
houston = (
|
||||
SouthTexasCity.objects.annotate(dist=Distance("point", lagrange))
|
||||
.order_by("id")
|
||||
.first()
|
||||
)
|
||||
tol = 2 if connection.ops.oracle else 5
|
||||
self.assertAlmostEqual(houston.dist.m, 147075.069813, tol)
|
||||
|
||||
@skipUnlessDBFeature("has_Distance_function", "has_Transform_function")
|
||||
def test_distance_projected(self):
|
||||
@@ -347,24 +437,44 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
Test the `Distance` function on projected coordinate systems.
|
||||
"""
|
||||
# The point for La Grange, TX
|
||||
lagrange = GEOSGeometry('POINT(-96.876369 29.905320)', 4326)
|
||||
lagrange = GEOSGeometry("POINT(-96.876369 29.905320)", 4326)
|
||||
# Reference distances in feet and in meters. Got these values from
|
||||
# using the provided raw SQL statements.
|
||||
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 32140))
|
||||
# FROM distapp_southtexascity;
|
||||
m_distances = [147075.069813, 139630.198056, 140888.552826,
|
||||
138809.684197, 158309.246259, 212183.594374,
|
||||
70870.188967, 165337.758878, 139196.085105]
|
||||
m_distances = [
|
||||
147075.069813,
|
||||
139630.198056,
|
||||
140888.552826,
|
||||
138809.684197,
|
||||
158309.246259,
|
||||
212183.594374,
|
||||
70870.188967,
|
||||
165337.758878,
|
||||
139196.085105,
|
||||
]
|
||||
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278))
|
||||
# FROM distapp_southtexascityft;
|
||||
ft_distances = [482528.79154625, 458103.408123001, 462231.860397575,
|
||||
455411.438904354, 519386.252102563, 696139.009211594,
|
||||
232513.278304279, 542445.630586414, 456679.155883207]
|
||||
ft_distances = [
|
||||
482528.79154625,
|
||||
458103.408123001,
|
||||
462231.860397575,
|
||||
455411.438904354,
|
||||
519386.252102563,
|
||||
696139.009211594,
|
||||
232513.278304279,
|
||||
542445.630586414,
|
||||
456679.155883207,
|
||||
]
|
||||
|
||||
# Testing using different variations of parameters and using models
|
||||
# with different projected coordinate systems.
|
||||
dist1 = SouthTexasCity.objects.annotate(distance=Distance('point', lagrange)).order_by('id')
|
||||
dist2 = SouthTexasCityFt.objects.annotate(distance=Distance('point', lagrange)).order_by('id')
|
||||
dist1 = SouthTexasCity.objects.annotate(
|
||||
distance=Distance("point", lagrange)
|
||||
).order_by("id")
|
||||
dist2 = SouthTexasCityFt.objects.annotate(
|
||||
distance=Distance("point", lagrange)
|
||||
).order_by("id")
|
||||
dist_qs = [dist1, dist2]
|
||||
|
||||
# Ensuring expected distances are returned for each distance queryset.
|
||||
@@ -386,10 +496,22 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
# Reference query:
|
||||
# SELECT ST_distance_sphere(point, ST_GeomFromText('LINESTRING(150.9020 -34.4245,150.8700 -34.5789)', 4326))
|
||||
# FROM distapp_australiacity ORDER BY name;
|
||||
distances = [1120954.92533513, 140575.720018241, 640396.662906304,
|
||||
60580.9693849269, 972807.955955075, 568451.8357838,
|
||||
40435.4335201384, 0, 68272.3896586844, 12375.0643697706, 0]
|
||||
qs = AustraliaCity.objects.annotate(distance=Distance('point', ls)).order_by('name')
|
||||
distances = [
|
||||
1120954.92533513,
|
||||
140575.720018241,
|
||||
640396.662906304,
|
||||
60580.9693849269,
|
||||
972807.955955075,
|
||||
568451.8357838,
|
||||
40435.4335201384,
|
||||
0,
|
||||
68272.3896586844,
|
||||
12375.0643697706,
|
||||
0,
|
||||
]
|
||||
qs = AustraliaCity.objects.annotate(distance=Distance("point", ls)).order_by(
|
||||
"name"
|
||||
)
|
||||
for city, distance in zip(qs, distances):
|
||||
with self.subTest(city=city, distance=distance):
|
||||
# Testing equivalence to within a meter (kilometer on SpatiaLite).
|
||||
@@ -406,30 +528,46 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
# SELECT ST_distance_sphere(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326))
|
||||
# FROM distapp_australiacity WHERE (NOT (id = 11)); st_distance_sphere
|
||||
spheroid_distances = [
|
||||
60504.0628957201, 77023.9489850262, 49154.8867574404,
|
||||
90847.4358768573, 217402.811919332, 709599.234564757,
|
||||
640011.483550888, 7772.00667991925, 1047861.78619339,
|
||||
60504.0628957201,
|
||||
77023.9489850262,
|
||||
49154.8867574404,
|
||||
90847.4358768573,
|
||||
217402.811919332,
|
||||
709599.234564757,
|
||||
640011.483550888,
|
||||
7772.00667991925,
|
||||
1047861.78619339,
|
||||
1165126.55236034,
|
||||
]
|
||||
sphere_distances = [
|
||||
60580.9693849267, 77144.0435286473, 49199.4415344719,
|
||||
90804.7533823494, 217713.384600405, 709134.127242793,
|
||||
639828.157159169, 7786.82949717788, 1049204.06569028,
|
||||
60580.9693849267,
|
||||
77144.0435286473,
|
||||
49199.4415344719,
|
||||
90804.7533823494,
|
||||
217713.384600405,
|
||||
709134.127242793,
|
||||
639828.157159169,
|
||||
7786.82949717788,
|
||||
1049204.06569028,
|
||||
1162623.7238134,
|
||||
]
|
||||
# Testing with spheroid distances first.
|
||||
hillsdale = AustraliaCity.objects.get(name='Hillsdale')
|
||||
qs = AustraliaCity.objects.exclude(id=hillsdale.id).annotate(
|
||||
distance=Distance('point', hillsdale.point, spheroid=True)
|
||||
).order_by('id')
|
||||
hillsdale = AustraliaCity.objects.get(name="Hillsdale")
|
||||
qs = (
|
||||
AustraliaCity.objects.exclude(id=hillsdale.id)
|
||||
.annotate(distance=Distance("point", hillsdale.point, spheroid=True))
|
||||
.order_by("id")
|
||||
)
|
||||
for i, c in enumerate(qs):
|
||||
with self.subTest(c=c):
|
||||
self.assertAlmostEqual(spheroid_distances[i], c.distance.m, tol)
|
||||
if connection.ops.postgis or connection.ops.spatialite:
|
||||
# PostGIS uses sphere-only distances by default, testing these as well.
|
||||
qs = AustraliaCity.objects.exclude(id=hillsdale.id).annotate(
|
||||
distance=Distance('point', hillsdale.point)
|
||||
).order_by('id')
|
||||
qs = (
|
||||
AustraliaCity.objects.exclude(id=hillsdale.id)
|
||||
.annotate(distance=Distance("point", hillsdale.point))
|
||||
.order_by("id")
|
||||
)
|
||||
for i, c in enumerate(qs):
|
||||
with self.subTest(c=c):
|
||||
self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol)
|
||||
@@ -437,9 +575,13 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
@skipIfDBFeature("supports_distance_geodetic")
|
||||
@skipUnlessDBFeature("has_Distance_function")
|
||||
def test_distance_function_raw_result(self):
|
||||
distance = Interstate.objects.annotate(
|
||||
d=Distance(Point(0, 0, srid=4326), Point(0, 1, srid=4326)),
|
||||
).first().d
|
||||
distance = (
|
||||
Interstate.objects.annotate(
|
||||
d=Distance(Point(0, 0, srid=4326), Point(0, 1, srid=4326)),
|
||||
)
|
||||
.first()
|
||||
.d
|
||||
)
|
||||
self.assertEqual(distance, 1)
|
||||
|
||||
@skipUnlessDBFeature("has_Distance_function")
|
||||
@@ -449,29 +591,37 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
).filter(d=D(m=1))
|
||||
self.assertTrue(qs.exists())
|
||||
|
||||
@skipUnlessDBFeature('supports_tolerance_parameter')
|
||||
@skipUnlessDBFeature("supports_tolerance_parameter")
|
||||
def test_distance_function_tolerance_escaping(self):
|
||||
qs = Interstate.objects.annotate(
|
||||
d=Distance(
|
||||
Point(500, 500, srid=3857),
|
||||
Point(0, 0, srid=3857),
|
||||
tolerance='0.05) = 1 OR 1=1 OR (1+1',
|
||||
),
|
||||
).filter(d=D(m=1)).values('pk')
|
||||
msg = 'The tolerance parameter has the wrong type'
|
||||
qs = (
|
||||
Interstate.objects.annotate(
|
||||
d=Distance(
|
||||
Point(500, 500, srid=3857),
|
||||
Point(0, 0, srid=3857),
|
||||
tolerance="0.05) = 1 OR 1=1 OR (1+1",
|
||||
),
|
||||
)
|
||||
.filter(d=D(m=1))
|
||||
.values("pk")
|
||||
)
|
||||
msg = "The tolerance parameter has the wrong type"
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
qs.exists()
|
||||
|
||||
@skipUnlessDBFeature('supports_tolerance_parameter')
|
||||
@skipUnlessDBFeature("supports_tolerance_parameter")
|
||||
def test_distance_function_tolerance(self):
|
||||
# Tolerance is greater than distance.
|
||||
qs = Interstate.objects.annotate(
|
||||
d=Distance(
|
||||
Point(0, 0, srid=3857),
|
||||
Point(1, 1, srid=3857),
|
||||
tolerance=1.5,
|
||||
),
|
||||
).filter(d=0).values('pk')
|
||||
qs = (
|
||||
Interstate.objects.annotate(
|
||||
d=Distance(
|
||||
Point(0, 0, srid=3857),
|
||||
Point(1, 1, srid=3857),
|
||||
tolerance=1.5,
|
||||
),
|
||||
)
|
||||
.filter(d=0)
|
||||
.values("pk")
|
||||
)
|
||||
self.assertIs(qs.exists(), True)
|
||||
|
||||
@skipIfDBFeature("supports_distance_geodetic")
|
||||
@@ -480,11 +630,11 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
qs = Interstate.objects.annotate(
|
||||
d=Distance(Point(0, 0, srid=4326), Point(0, 1, srid=4326)),
|
||||
).filter(d=D(m=1))
|
||||
msg = 'Distance measure is supplied, but units are unknown for result.'
|
||||
msg = "Distance measure is supplied, but units are unknown for result."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
list(qs)
|
||||
|
||||
@skipUnlessDBFeature("has_Distance_function", 'has_Transform_function')
|
||||
@skipUnlessDBFeature("has_Distance_function", "has_Transform_function")
|
||||
def test_distance_transform(self):
|
||||
"""
|
||||
Test the `Distance` function used with `Transform` on a geographic field.
|
||||
@@ -493,7 +643,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
# of 77005 to 100m) -- which aren't allowed in geographic distance
|
||||
# queries normally, however our field has been transformed to
|
||||
# a non-geographic system.
|
||||
z = SouthTexasZipcode.objects.get(name='77005')
|
||||
z = SouthTexasZipcode.objects.get(name="77005")
|
||||
|
||||
# Reference query:
|
||||
# SELECT ST_Distance(ST_Transform("distapp_censuszipcode"."poly", 32140),
|
||||
@@ -508,22 +658,29 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
# however.
|
||||
buf1 = z.poly.centroid.buffer(100)
|
||||
buf2 = buf1.transform(4269, clone=True)
|
||||
ref_zips = ['77002', '77025', '77401']
|
||||
ref_zips = ["77002", "77025", "77401"]
|
||||
|
||||
for buf in [buf1, buf2]:
|
||||
qs = CensusZipcode.objects.exclude(name='77005').annotate(
|
||||
distance=Distance(Transform('poly', 32140), buf)
|
||||
).order_by('name')
|
||||
qs = (
|
||||
CensusZipcode.objects.exclude(name="77005")
|
||||
.annotate(distance=Distance(Transform("poly", 32140), buf))
|
||||
.order_by("name")
|
||||
)
|
||||
self.assertEqual(ref_zips, sorted(c.name for c in qs))
|
||||
for i, z in enumerate(qs):
|
||||
self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
|
||||
|
||||
@skipUnlessDBFeature("has_Distance_function")
|
||||
def test_distance_order_by(self):
|
||||
qs = SouthTexasCity.objects.annotate(distance=Distance('point', Point(3, 3, srid=32140))).order_by(
|
||||
'distance'
|
||||
).values_list('name', flat=True).filter(name__in=('San Antonio', 'Pearland'))
|
||||
self.assertSequenceEqual(qs, ['San Antonio', 'Pearland'])
|
||||
qs = (
|
||||
SouthTexasCity.objects.annotate(
|
||||
distance=Distance("point", Point(3, 3, srid=32140))
|
||||
)
|
||||
.order_by("distance")
|
||||
.values_list("name", flat=True)
|
||||
.filter(name__in=("San Antonio", "Pearland"))
|
||||
)
|
||||
self.assertSequenceEqual(qs, ["San Antonio", "Pearland"])
|
||||
|
||||
@skipUnlessDBFeature("has_Length_function")
|
||||
def test_length(self):
|
||||
@@ -537,20 +694,24 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
len_m2 = 4617.668
|
||||
|
||||
if connection.features.supports_length_geodetic:
|
||||
qs = Interstate.objects.annotate(length=Length('path'))
|
||||
qs = Interstate.objects.annotate(length=Length("path"))
|
||||
tol = 2 if connection.ops.oracle else 3
|
||||
self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
|
||||
# TODO: test with spheroid argument (True and False)
|
||||
else:
|
||||
# Does not support geodetic coordinate systems.
|
||||
with self.assertRaises(NotSupportedError):
|
||||
list(Interstate.objects.annotate(length=Length('path')))
|
||||
list(Interstate.objects.annotate(length=Length("path")))
|
||||
|
||||
# Now doing length on a projected coordinate system.
|
||||
i10 = SouthTexasInterstate.objects.annotate(length=Length('path')).get(name='I-10')
|
||||
i10 = SouthTexasInterstate.objects.annotate(length=Length("path")).get(
|
||||
name="I-10"
|
||||
)
|
||||
self.assertAlmostEqual(len_m2, i10.length.m, 2)
|
||||
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))
|
||||
@@ -563,14 +724,21 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
"""
|
||||
# Reference query:
|
||||
# SELECT ST_Perimeter(distapp_southtexaszipcode.poly) FROM distapp_southtexaszipcode;
|
||||
perim_m = [18404.3550889361, 15627.2108551001, 20632.5588368978, 17094.5996143697]
|
||||
perim_m = [
|
||||
18404.3550889361,
|
||||
15627.2108551001,
|
||||
20632.5588368978,
|
||||
17094.5996143697,
|
||||
]
|
||||
tol = 2 if connection.ops.oracle else 7
|
||||
qs = SouthTexasZipcode.objects.annotate(perimeter=Perimeter('poly')).order_by('name')
|
||||
qs = SouthTexasZipcode.objects.annotate(perimeter=Perimeter("poly")).order_by(
|
||||
"name"
|
||||
)
|
||||
for i, z in enumerate(qs):
|
||||
self.assertAlmostEqual(perim_m[i], z.perimeter.m, tol)
|
||||
|
||||
# Running on points; should return 0.
|
||||
qs = SouthTexasCity.objects.annotate(perim=Perimeter('point'))
|
||||
qs = SouthTexasCity.objects.annotate(perim=Perimeter("point"))
|
||||
for city in qs:
|
||||
self.assertEqual(0, city.perim.m)
|
||||
|
||||
@@ -578,28 +746,32 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase):
|
||||
def test_perimeter_geodetic(self):
|
||||
# Currently only Oracle supports calculating the perimeter on geodetic
|
||||
# geometries (without being transformed).
|
||||
qs1 = CensusZipcode.objects.annotate(perim=Perimeter('poly'))
|
||||
qs1 = CensusZipcode.objects.annotate(perim=Perimeter("poly"))
|
||||
if connection.features.supports_perimeter_geodetic:
|
||||
self.assertAlmostEqual(qs1[0].perim.m, 18406.3818954314, 3)
|
||||
else:
|
||||
with self.assertRaises(NotSupportedError):
|
||||
list(qs1)
|
||||
# But should work fine when transformed to projected coordinates
|
||||
qs2 = CensusZipcode.objects.annotate(perim=Perimeter(Transform('poly', 32140))).filter(name='77002')
|
||||
qs2 = CensusZipcode.objects.annotate(
|
||||
perim=Perimeter(Transform("poly", 32140))
|
||||
).filter(name="77002")
|
||||
self.assertAlmostEqual(qs2[0].perim.m, 18404.355, 3)
|
||||
|
||||
@skipUnlessDBFeature("supports_null_geometries", "has_Area_function", "has_Distance_function")
|
||||
@skipUnlessDBFeature(
|
||||
"supports_null_geometries", "has_Area_function", "has_Distance_function"
|
||||
)
|
||||
def test_measurement_null_fields(self):
|
||||
"""
|
||||
Test the measurement functions on fields with NULL values.
|
||||
"""
|
||||
# Creating SouthTexasZipcode w/NULL value.
|
||||
SouthTexasZipcode.objects.create(name='78212')
|
||||
SouthTexasZipcode.objects.create(name="78212")
|
||||
# Performing distance/area queries against the NULL PolygonField,
|
||||
# and ensuring the result of the operations is None.
|
||||
htown = SouthTexasCity.objects.get(name='Downtown Houston')
|
||||
htown = SouthTexasCity.objects.get(name="Downtown Houston")
|
||||
z = SouthTexasZipcode.objects.annotate(
|
||||
distance=Distance('poly', htown.point), area=Area('poly')
|
||||
).get(name='78212')
|
||||
distance=Distance("poly", htown.point), area=Area("poly")
|
||||
).get(name="78212")
|
||||
self.assertIsNone(z.distance)
|
||||
self.assertIsNone(z.area)
|
||||
|
||||
@@ -5,28 +5,37 @@ from django.contrib.gis.gdal import Driver, GDALException
|
||||
|
||||
valid_drivers = (
|
||||
# vector
|
||||
'ESRI Shapefile', 'MapInfo File', 'TIGER', 'S57', 'DGN', 'Memory', 'CSV',
|
||||
'GML', 'KML',
|
||||
"ESRI Shapefile",
|
||||
"MapInfo File",
|
||||
"TIGER",
|
||||
"S57",
|
||||
"DGN",
|
||||
"Memory",
|
||||
"CSV",
|
||||
"GML",
|
||||
"KML",
|
||||
# raster
|
||||
'GTiff', 'JPEG', 'MEM', 'PNG',
|
||||
"GTiff",
|
||||
"JPEG",
|
||||
"MEM",
|
||||
"PNG",
|
||||
)
|
||||
|
||||
invalid_drivers = ('Foo baz', 'clucka', 'ESRI Shp', 'ESRI rast')
|
||||
invalid_drivers = ("Foo baz", "clucka", "ESRI Shp", "ESRI rast")
|
||||
|
||||
aliases = {
|
||||
'eSrI': 'ESRI Shapefile',
|
||||
'TigER/linE': 'TIGER',
|
||||
'SHAPE': 'ESRI Shapefile',
|
||||
'sHp': 'ESRI Shapefile',
|
||||
'tiFf': 'GTiff',
|
||||
'tIf': 'GTiff',
|
||||
'jPEg': 'JPEG',
|
||||
'jpG': 'JPEG',
|
||||
"eSrI": "ESRI Shapefile",
|
||||
"TigER/linE": "TIGER",
|
||||
"SHAPE": "ESRI Shapefile",
|
||||
"sHp": "ESRI Shapefile",
|
||||
"tiFf": "GTiff",
|
||||
"tIf": "GTiff",
|
||||
"jPEg": "JPEG",
|
||||
"jpG": "JPEG",
|
||||
}
|
||||
|
||||
|
||||
class DriverTest(unittest.TestCase):
|
||||
|
||||
def test01_valid_driver(self):
|
||||
"Testing valid GDAL/OGR Data Source Drivers."
|
||||
for d in valid_drivers:
|
||||
@@ -45,15 +54,16 @@ class DriverTest(unittest.TestCase):
|
||||
dr = Driver(alias)
|
||||
self.assertEqual(full_name, str(dr))
|
||||
|
||||
@mock.patch('django.contrib.gis.gdal.driver.vcapi.get_driver_count')
|
||||
@mock.patch('django.contrib.gis.gdal.driver.rcapi.get_driver_count')
|
||||
@mock.patch('django.contrib.gis.gdal.driver.vcapi.register_all')
|
||||
@mock.patch('django.contrib.gis.gdal.driver.rcapi.register_all')
|
||||
@mock.patch("django.contrib.gis.gdal.driver.vcapi.get_driver_count")
|
||||
@mock.patch("django.contrib.gis.gdal.driver.rcapi.get_driver_count")
|
||||
@mock.patch("django.contrib.gis.gdal.driver.vcapi.register_all")
|
||||
@mock.patch("django.contrib.gis.gdal.driver.rcapi.register_all")
|
||||
def test_registered(self, rreg, vreg, rcount, vcount):
|
||||
"""
|
||||
Prototypes are registered only if their respective driver counts are
|
||||
zero.
|
||||
"""
|
||||
|
||||
def check(rcount_val, vcount_val):
|
||||
vreg.reset_mock()
|
||||
rreg.reset_mock()
|
||||
|
||||
@@ -3,12 +3,8 @@ import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from django.contrib.gis.gdal import (
|
||||
DataSource, Envelope, GDALException, OGRGeometry,
|
||||
)
|
||||
from django.contrib.gis.gdal.field import (
|
||||
OFTDateTime, OFTInteger, OFTReal, OFTString,
|
||||
)
|
||||
from django.contrib.gis.gdal import DataSource, Envelope, GDALException, OGRGeometry
|
||||
from django.contrib.gis.gdal.field import OFTDateTime, OFTInteger, OFTReal, OFTString
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
@@ -17,87 +13,105 @@ from ..test_data import TEST_DATA, TestDS, get_ds_file
|
||||
wgs_84_wkt = (
|
||||
'GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",'
|
||||
'6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",'
|
||||
'0.017453292519943295]]'
|
||||
"0.017453292519943295]]"
|
||||
)
|
||||
# Using a regex because of small differences depending on GDAL versions.
|
||||
wgs_84_wkt_regex = r'^GEOGCS\["(GCS_)?WGS[ _](19)?84".*$'
|
||||
|
||||
datetime_format = '%Y-%m-%dT%H:%M:%S'
|
||||
datetime_format = "%Y-%m-%dT%H:%M:%S"
|
||||
|
||||
# List of acceptable data sources.
|
||||
ds_list = (
|
||||
TestDS(
|
||||
'test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile',
|
||||
fields={'dbl': OFTReal, 'int': OFTInteger, 'str': OFTString},
|
||||
"test_point",
|
||||
nfeat=5,
|
||||
nfld=3,
|
||||
geom="POINT",
|
||||
gtype=1,
|
||||
driver="ESRI Shapefile",
|
||||
fields={"dbl": OFTReal, "int": OFTInteger, "str": OFTString},
|
||||
extent=(-1.35011, 0.166623, -0.524093, 0.824508), # Got extent from QGIS
|
||||
srs_wkt=wgs_84_wkt,
|
||||
field_values={
|
||||
'dbl': [float(i) for i in range(1, 6)],
|
||||
'int': list(range(1, 6)),
|
||||
'str': [str(i) for i in range(1, 6)],
|
||||
"dbl": [float(i) for i in range(1, 6)],
|
||||
"int": list(range(1, 6)),
|
||||
"str": [str(i) for i in range(1, 6)],
|
||||
},
|
||||
fids=range(5)
|
||||
fids=range(5),
|
||||
),
|
||||
TestDS(
|
||||
'test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype='Point25D',
|
||||
driver='OGR_VRT',
|
||||
"test_vrt",
|
||||
ext="vrt",
|
||||
nfeat=3,
|
||||
nfld=3,
|
||||
geom="POINT",
|
||||
gtype="Point25D",
|
||||
driver="OGR_VRT",
|
||||
fields={
|
||||
'POINT_X': OFTString,
|
||||
'POINT_Y': OFTString,
|
||||
'NUM': OFTString,
|
||||
"POINT_X": OFTString,
|
||||
"POINT_Y": OFTString,
|
||||
"NUM": OFTString,
|
||||
}, # VRT uses CSV, which all types are OFTString.
|
||||
extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV
|
||||
field_values={
|
||||
'POINT_X': ['1.0', '5.0', '100.0'],
|
||||
'POINT_Y': ['2.0', '23.0', '523.5'],
|
||||
'NUM': ['5', '17', '23'],
|
||||
"POINT_X": ["1.0", "5.0", "100.0"],
|
||||
"POINT_Y": ["2.0", "23.0", "523.5"],
|
||||
"NUM": ["5", "17", "23"],
|
||||
},
|
||||
fids=range(1, 4)
|
||||
fids=range(1, 4),
|
||||
),
|
||||
TestDS(
|
||||
'test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3,
|
||||
driver='ESRI Shapefile',
|
||||
fields={'float': OFTReal, 'int': OFTInteger, 'str': OFTString},
|
||||
"test_poly",
|
||||
nfeat=3,
|
||||
nfld=3,
|
||||
geom="POLYGON",
|
||||
gtype=3,
|
||||
driver="ESRI Shapefile",
|
||||
fields={"float": OFTReal, "int": OFTInteger, "str": OFTString},
|
||||
extent=(-1.01513, -0.558245, 0.161876, 0.839637), # Got extent from QGIS
|
||||
srs_wkt=wgs_84_wkt,
|
||||
),
|
||||
TestDS(
|
||||
'has_nulls', nfeat=3, nfld=6, geom='POLYGON', gtype=3,
|
||||
driver='GeoJSON', ext='geojson',
|
||||
"has_nulls",
|
||||
nfeat=3,
|
||||
nfld=6,
|
||||
geom="POLYGON",
|
||||
gtype=3,
|
||||
driver="GeoJSON",
|
||||
ext="geojson",
|
||||
fields={
|
||||
'uuid': OFTString,
|
||||
'name': OFTString,
|
||||
'num': OFTReal,
|
||||
'integer': OFTInteger,
|
||||
'datetime': OFTDateTime,
|
||||
'boolean': OFTInteger,
|
||||
"uuid": OFTString,
|
||||
"name": OFTString,
|
||||
"num": OFTReal,
|
||||
"integer": OFTInteger,
|
||||
"datetime": OFTDateTime,
|
||||
"boolean": OFTInteger,
|
||||
},
|
||||
extent=(-75.274200, 39.846504, -74.959717, 40.119040), # Got extent from QGIS
|
||||
field_values={
|
||||
'uuid': [
|
||||
'1378c26f-cbe6-44b0-929f-eb330d4991f5',
|
||||
'fa2ba67c-a135-4338-b924-a9622b5d869f',
|
||||
'4494c1f3-55ab-4256-b365-12115cb388d5',
|
||||
"uuid": [
|
||||
"1378c26f-cbe6-44b0-929f-eb330d4991f5",
|
||||
"fa2ba67c-a135-4338-b924-a9622b5d869f",
|
||||
"4494c1f3-55ab-4256-b365-12115cb388d5",
|
||||
],
|
||||
'name': ['Philadelphia', None, 'north'],
|
||||
'num': [1.001, None, 0.0],
|
||||
'integer': [5, None, 8],
|
||||
'boolean': [True, None, False],
|
||||
'datetime': [
|
||||
datetime.strptime('1994-08-14T11:32:14', datetime_format),
|
||||
"name": ["Philadelphia", None, "north"],
|
||||
"num": [1.001, None, 0.0],
|
||||
"integer": [5, None, 8],
|
||||
"boolean": [True, None, False],
|
||||
"datetime": [
|
||||
datetime.strptime("1994-08-14T11:32:14", datetime_format),
|
||||
None,
|
||||
datetime.strptime('2018-11-29T03:02:52', datetime_format),
|
||||
]
|
||||
datetime.strptime("2018-11-29T03:02:52", datetime_format),
|
||||
],
|
||||
},
|
||||
fids=range(3),
|
||||
),
|
||||
)
|
||||
|
||||
bad_ds = (TestDS('foo'),)
|
||||
bad_ds = (TestDS("foo"),)
|
||||
|
||||
|
||||
class DataSourceTest(SimpleTestCase):
|
||||
|
||||
def test01_valid_shp(self):
|
||||
"Testing valid SHP Data Source files."
|
||||
|
||||
@@ -115,15 +129,17 @@ class DataSourceTest(SimpleTestCase):
|
||||
self.assertEqual(source.driver, str(ds.driver))
|
||||
|
||||
# Making sure indexing works
|
||||
msg = 'Index out of range when accessing layers in a datasource: %s.'
|
||||
msg = "Index out of range when accessing layers in a datasource: %s."
|
||||
with self.assertRaisesMessage(IndexError, msg % len(ds)):
|
||||
ds.__getitem__(len(ds))
|
||||
|
||||
with self.assertRaisesMessage(IndexError, 'Invalid OGR layer name given: invalid.'):
|
||||
ds.__getitem__('invalid')
|
||||
with self.assertRaisesMessage(
|
||||
IndexError, "Invalid OGR layer name given: invalid."
|
||||
):
|
||||
ds.__getitem__("invalid")
|
||||
|
||||
def test_ds_input_pathlib(self):
|
||||
test_shp = Path(get_ds_file('test_point', 'shp'))
|
||||
test_shp = Path(get_ds_file("test_point", "shp"))
|
||||
ds = DataSource(test_shp)
|
||||
self.assertEqual(len(ds), 1)
|
||||
|
||||
@@ -162,12 +178,14 @@ class DataSourceTest(SimpleTestCase):
|
||||
self.assertIn(f, source.fields)
|
||||
|
||||
# Negative FIDs are not allowed.
|
||||
with self.assertRaisesMessage(IndexError, 'Negative indices are not allowed on OGR Layers.'):
|
||||
with self.assertRaisesMessage(
|
||||
IndexError, "Negative indices are not allowed on OGR Layers."
|
||||
):
|
||||
layer.__getitem__(-1)
|
||||
with self.assertRaisesMessage(IndexError, 'Invalid feature id: 50000.'):
|
||||
with self.assertRaisesMessage(IndexError, "Invalid feature id: 50000."):
|
||||
layer.__getitem__(50000)
|
||||
|
||||
if hasattr(source, 'field_values'):
|
||||
if hasattr(source, "field_values"):
|
||||
# Testing `Layer.get_fields` (which uses Layer.__iter__)
|
||||
for fld_name, fld_value in source.field_values.items():
|
||||
self.assertEqual(fld_value, layer.get_fields(fld_name))
|
||||
@@ -181,12 +199,16 @@ class DataSourceTest(SimpleTestCase):
|
||||
for fld_name, fld_value in source.field_values.items():
|
||||
self.assertEqual(fld_value[i], feat.get(fld_name))
|
||||
|
||||
msg = 'Index out of range when accessing field in a feature: %s.'
|
||||
msg = (
|
||||
"Index out of range when accessing field in a feature: %s."
|
||||
)
|
||||
with self.assertRaisesMessage(IndexError, msg % len(feat)):
|
||||
feat.__getitem__(len(feat))
|
||||
|
||||
with self.assertRaisesMessage(IndexError, 'Invalid OFT field name given: invalid.'):
|
||||
feat.__getitem__('invalid')
|
||||
with self.assertRaisesMessage(
|
||||
IndexError, "Invalid OFT field name given: invalid."
|
||||
):
|
||||
feat.__getitem__("invalid")
|
||||
|
||||
def test03b_layer_slice(self):
|
||||
"Test indexing and slicing on Layers."
|
||||
@@ -223,7 +245,7 @@ class DataSourceTest(SimpleTestCase):
|
||||
self.assertEqual(source.gtype, lyr.geom_type.num)
|
||||
|
||||
# Same issue for Feature/Field objects, see #18640
|
||||
self.assertEqual(str(lyr[0]['str']), "1")
|
||||
self.assertEqual(str(lyr[0]["str"]), "1")
|
||||
|
||||
def test04_features(self):
|
||||
"Testing Data Source Features."
|
||||
@@ -271,12 +293,12 @@ class DataSourceTest(SimpleTestCase):
|
||||
self.assertEqual(source.gtype, g.geom_type)
|
||||
|
||||
# Making sure the SpatialReference is as expected.
|
||||
if hasattr(source, 'srs_wkt'):
|
||||
if hasattr(source, "srs_wkt"):
|
||||
self.assertIsNotNone(re.match(wgs_84_wkt_regex, g.srs.wkt))
|
||||
|
||||
def test06_spatial_filter(self):
|
||||
"Testing the Layer.spatial_filter property."
|
||||
ds = DataSource(get_ds_file('cities', 'shp'))
|
||||
ds = DataSource(get_ds_file("cities", "shp"))
|
||||
lyr = ds[0]
|
||||
|
||||
# When not set, it should be None.
|
||||
@@ -284,7 +306,7 @@ class DataSourceTest(SimpleTestCase):
|
||||
|
||||
# Must be set a/an OGRGeometry or 4-tuple.
|
||||
with self.assertRaises(TypeError):
|
||||
lyr._set_spatial_filter('foo')
|
||||
lyr._set_spatial_filter("foo")
|
||||
|
||||
# Setting the spatial filter with a tuple/list with the extent of
|
||||
# a buffer centering around Pueblo.
|
||||
@@ -295,19 +317,19 @@ class DataSourceTest(SimpleTestCase):
|
||||
self.assertEqual(OGRGeometry.from_bbox(filter_extent), lyr.spatial_filter)
|
||||
feats = [feat for feat in lyr]
|
||||
self.assertEqual(1, len(feats))
|
||||
self.assertEqual('Pueblo', feats[0].get('Name'))
|
||||
self.assertEqual("Pueblo", feats[0].get("Name"))
|
||||
|
||||
# Setting the spatial filter with an OGRGeometry for buffer centering
|
||||
# around Houston.
|
||||
filter_geom = OGRGeometry(
|
||||
'POLYGON((-96.363151 28.763374,-94.363151 28.763374,'
|
||||
'-94.363151 30.763374,-96.363151 30.763374,-96.363151 28.763374))'
|
||||
"POLYGON((-96.363151 28.763374,-94.363151 28.763374,"
|
||||
"-94.363151 30.763374,-96.363151 30.763374,-96.363151 28.763374))"
|
||||
)
|
||||
lyr.spatial_filter = filter_geom
|
||||
self.assertEqual(filter_geom, lyr.spatial_filter)
|
||||
feats = [feat for feat in lyr]
|
||||
self.assertEqual(1, len(feats))
|
||||
self.assertEqual('Houston', feats[0].get('Name'))
|
||||
self.assertEqual("Houston", feats[0].get("Name"))
|
||||
|
||||
# Clearing the spatial filter by setting it to None. Now
|
||||
# should indicate that there are 3 features in the Layer.
|
||||
@@ -319,14 +341,14 @@ class DataSourceTest(SimpleTestCase):
|
||||
# Using *.dbf from Census 2010 TIGER Shapefile for Texas,
|
||||
# which has land area ('ALAND10') stored in a Real field
|
||||
# with no precision.
|
||||
ds = DataSource(os.path.join(TEST_DATA, 'texas.dbf'))
|
||||
ds = DataSource(os.path.join(TEST_DATA, "texas.dbf"))
|
||||
feat = ds[0][0]
|
||||
# Reference value obtained using `ogrinfo`.
|
||||
self.assertEqual(676586997978, feat.get('ALAND10'))
|
||||
self.assertEqual(676586997978, feat.get("ALAND10"))
|
||||
|
||||
def test_nonexistent_field(self):
|
||||
source = ds_list[0]
|
||||
ds = DataSource(source.ds)
|
||||
msg = 'invalid field name: nonexistent'
|
||||
msg = "invalid field name: nonexistent"
|
||||
with self.assertRaisesMessage(GDALException, msg):
|
||||
ds[0].get_fields('nonexistent')
|
||||
ds[0].get_fields("nonexistent")
|
||||
|
||||
@@ -10,7 +10,6 @@ class TestPoint:
|
||||
|
||||
|
||||
class EnvelopeTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.e = Envelope(0, 0, 5, 5)
|
||||
|
||||
@@ -18,7 +17,7 @@ class EnvelopeTest(unittest.TestCase):
|
||||
"Testing Envelope initialization."
|
||||
e1 = Envelope((0, 0, 5, 5))
|
||||
Envelope(0, 0, 5, 5)
|
||||
Envelope(0, '0', '5', 5) # Thanks to ww for this
|
||||
Envelope(0, "0", "5", 5) # Thanks to ww for this
|
||||
Envelope(e1._envelope)
|
||||
with self.assertRaises(GDALException):
|
||||
Envelope((5, 5, 0, 0))
|
||||
@@ -29,9 +28,9 @@ class EnvelopeTest(unittest.TestCase):
|
||||
with self.assertRaises(GDALException):
|
||||
Envelope(())
|
||||
with self.assertRaises(ValueError):
|
||||
Envelope(0, 'a', 5, 5)
|
||||
Envelope(0, "a", 5, 5)
|
||||
with self.assertRaises(TypeError):
|
||||
Envelope('foo')
|
||||
Envelope("foo")
|
||||
with self.assertRaises(GDALException):
|
||||
Envelope((1, 1, 0, 0))
|
||||
# Shouldn't raise an exception for min_x == max_x or min_y == max_y
|
||||
@@ -47,8 +46,8 @@ class EnvelopeTest(unittest.TestCase):
|
||||
self.assertEqual((0, 0), e.ll)
|
||||
self.assertEqual((2, 3), e.ur)
|
||||
self.assertEqual((0, 0, 2, 3), e.tuple)
|
||||
self.assertEqual('POLYGON((0.0 0.0,0.0 3.0,2.0 3.0,2.0 0.0,0.0 0.0))', e.wkt)
|
||||
self.assertEqual('(0.0, 0.0, 2.0, 3.0)', str(e))
|
||||
self.assertEqual("POLYGON((0.0 0.0,0.0 3.0,2.0 3.0,2.0 0.0,0.0 0.0))", e.wkt)
|
||||
self.assertEqual("(0.0, 0.0, 2.0, 3.0)", str(e))
|
||||
|
||||
def test03_equivalence(self):
|
||||
"Testing Envelope equivalence."
|
||||
|
||||
@@ -2,7 +2,11 @@ import json
|
||||
import pickle
|
||||
|
||||
from django.contrib.gis.gdal import (
|
||||
CoordTransform, GDALException, OGRGeometry, OGRGeomType, SpatialReference,
|
||||
CoordTransform,
|
||||
GDALException,
|
||||
OGRGeometry,
|
||||
OGRGeomType,
|
||||
SpatialReference,
|
||||
)
|
||||
from django.template import Context
|
||||
from django.template.engine import Engine
|
||||
@@ -20,46 +24,48 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
# OGRGeomType should initialize on all these inputs.
|
||||
OGRGeomType(1)
|
||||
OGRGeomType(7)
|
||||
OGRGeomType('point')
|
||||
OGRGeomType('GeometrycollectioN')
|
||||
OGRGeomType('LINearrING')
|
||||
OGRGeomType('Unknown')
|
||||
OGRGeomType("point")
|
||||
OGRGeomType("GeometrycollectioN")
|
||||
OGRGeomType("LINearrING")
|
||||
OGRGeomType("Unknown")
|
||||
|
||||
# Should throw TypeError on this input
|
||||
with self.assertRaises(GDALException):
|
||||
OGRGeomType(23)
|
||||
with self.assertRaises(GDALException):
|
||||
OGRGeomType('fooD')
|
||||
OGRGeomType("fooD")
|
||||
with self.assertRaises(GDALException):
|
||||
OGRGeomType(9)
|
||||
|
||||
# Equivalence can take strings, ints, and other OGRGeomTypes
|
||||
self.assertEqual(OGRGeomType(1), OGRGeomType(1))
|
||||
self.assertEqual(OGRGeomType(7), 'GeometryCollection')
|
||||
self.assertEqual(OGRGeomType('point'), 'POINT')
|
||||
self.assertNotEqual(OGRGeomType('point'), 2)
|
||||
self.assertEqual(OGRGeomType('unknown'), 0)
|
||||
self.assertEqual(OGRGeomType(6), 'MULtiPolyGON')
|
||||
self.assertEqual(OGRGeomType(1), OGRGeomType('point'))
|
||||
self.assertNotEqual(OGRGeomType('POINT'), OGRGeomType(6))
|
||||
self.assertEqual(OGRGeomType(7), "GeometryCollection")
|
||||
self.assertEqual(OGRGeomType("point"), "POINT")
|
||||
self.assertNotEqual(OGRGeomType("point"), 2)
|
||||
self.assertEqual(OGRGeomType("unknown"), 0)
|
||||
self.assertEqual(OGRGeomType(6), "MULtiPolyGON")
|
||||
self.assertEqual(OGRGeomType(1), OGRGeomType("point"))
|
||||
self.assertNotEqual(OGRGeomType("POINT"), OGRGeomType(6))
|
||||
|
||||
# Testing the Django field name equivalent property.
|
||||
self.assertEqual('PointField', OGRGeomType('Point').django)
|
||||
self.assertEqual('GeometryField', OGRGeomType('Geometry').django)
|
||||
self.assertEqual('GeometryField', OGRGeomType('Unknown').django)
|
||||
self.assertIsNone(OGRGeomType('none').django)
|
||||
self.assertEqual("PointField", OGRGeomType("Point").django)
|
||||
self.assertEqual("GeometryField", OGRGeomType("Geometry").django)
|
||||
self.assertEqual("GeometryField", OGRGeomType("Unknown").django)
|
||||
self.assertIsNone(OGRGeomType("none").django)
|
||||
|
||||
# 'Geometry' initialization implies an unknown geometry type.
|
||||
gt = OGRGeomType('Geometry')
|
||||
gt = OGRGeomType("Geometry")
|
||||
self.assertEqual(0, gt.num)
|
||||
self.assertEqual('Unknown', gt.name)
|
||||
self.assertEqual("Unknown", gt.name)
|
||||
|
||||
def test_geomtype_25d(self):
|
||||
"Testing OGRGeomType object with 25D types."
|
||||
wkb25bit = OGRGeomType.wkb25bit
|
||||
self.assertEqual(OGRGeomType(wkb25bit + 1), 'Point25D')
|
||||
self.assertEqual(OGRGeomType('MultiLineString25D'), (5 + wkb25bit))
|
||||
self.assertEqual('GeometryCollectionField', OGRGeomType('GeometryCollection25D').django)
|
||||
self.assertEqual(OGRGeomType(wkb25bit + 1), "Point25D")
|
||||
self.assertEqual(OGRGeomType("MultiLineString25D"), (5 + wkb25bit))
|
||||
self.assertEqual(
|
||||
"GeometryCollectionField", OGRGeomType("GeometryCollection25D").django
|
||||
)
|
||||
|
||||
def test_wkt(self):
|
||||
"Testing WKT output."
|
||||
@@ -69,11 +75,11 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_ewkt(self):
|
||||
"Testing EWKT input/output."
|
||||
for ewkt_val in ('POINT (1 2 3)', 'LINEARRING (0 0,1 1,2 1,0 0)'):
|
||||
for ewkt_val in ("POINT (1 2 3)", "LINEARRING (0 0,1 1,2 1,0 0)"):
|
||||
# First with ewkt output when no SRID in EWKT
|
||||
self.assertEqual(ewkt_val, OGRGeometry(ewkt_val).ewkt)
|
||||
# No test consumption with an SRID specified.
|
||||
ewkt_val = 'SRID=4326;%s' % ewkt_val
|
||||
ewkt_val = "SRID=4326;%s" % ewkt_val
|
||||
geom = OGRGeometry(ewkt_val)
|
||||
self.assertEqual(ewkt_val, geom.ewkt)
|
||||
self.assertEqual(4326, geom.srs.srid)
|
||||
@@ -108,24 +114,26 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
"Testing GeoJSON input/output."
|
||||
for g in self.geometries.json_geoms:
|
||||
geom = OGRGeometry(g.wkt)
|
||||
if not hasattr(g, 'not_equal'):
|
||||
if not hasattr(g, "not_equal"):
|
||||
# Loading jsons to prevent decimal differences
|
||||
self.assertEqual(json.loads(g.json), json.loads(geom.json))
|
||||
self.assertEqual(json.loads(g.json), json.loads(geom.geojson))
|
||||
self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json))
|
||||
# Test input with some garbage content (but valid json) (#15529)
|
||||
geom = OGRGeometry('{"type": "Point", "coordinates": [ 100.0, 0.0 ], "other": "<test>"}')
|
||||
geom = OGRGeometry(
|
||||
'{"type": "Point", "coordinates": [ 100.0, 0.0 ], "other": "<test>"}'
|
||||
)
|
||||
self.assertIsInstance(geom, OGRGeometry)
|
||||
|
||||
def test_points(self):
|
||||
"Testing Point objects."
|
||||
|
||||
OGRGeometry('POINT(0 0)')
|
||||
OGRGeometry("POINT(0 0)")
|
||||
for p in self.geometries.points:
|
||||
if not hasattr(p, 'z'): # No 3D
|
||||
if not hasattr(p, "z"): # No 3D
|
||||
pnt = OGRGeometry(p.wkt)
|
||||
self.assertEqual(1, pnt.geom_type)
|
||||
self.assertEqual('POINT', pnt.geom_name)
|
||||
self.assertEqual("POINT", pnt.geom_name)
|
||||
self.assertEqual(p.x, pnt.x)
|
||||
self.assertEqual(p.y, pnt.y)
|
||||
self.assertEqual((p.x, p.y), pnt.tuple)
|
||||
@@ -135,9 +143,9 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
for mp in self.geometries.multipoints:
|
||||
mgeom1 = OGRGeometry(mp.wkt) # First one from WKT
|
||||
self.assertEqual(4, mgeom1.geom_type)
|
||||
self.assertEqual('MULTIPOINT', mgeom1.geom_name)
|
||||
mgeom2 = OGRGeometry('MULTIPOINT') # Creating empty multipoint
|
||||
mgeom3 = OGRGeometry('MULTIPOINT')
|
||||
self.assertEqual("MULTIPOINT", mgeom1.geom_name)
|
||||
mgeom2 = OGRGeometry("MULTIPOINT") # Creating empty multipoint
|
||||
mgeom3 = OGRGeometry("MULTIPOINT")
|
||||
for g in mgeom1:
|
||||
mgeom2.add(g) # adding each point from the multipoints
|
||||
mgeom3.add(g.wkt) # should take WKT as well
|
||||
@@ -148,16 +156,16 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_linestring(self):
|
||||
"Testing LineString objects."
|
||||
prev = OGRGeometry('POINT(0 0)')
|
||||
prev = OGRGeometry("POINT(0 0)")
|
||||
for ls in self.geometries.linestrings:
|
||||
linestr = OGRGeometry(ls.wkt)
|
||||
self.assertEqual(2, linestr.geom_type)
|
||||
self.assertEqual('LINESTRING', linestr.geom_name)
|
||||
self.assertEqual("LINESTRING", linestr.geom_name)
|
||||
self.assertEqual(ls.n_p, linestr.point_count)
|
||||
self.assertEqual(ls.coords, linestr.tuple)
|
||||
self.assertEqual(linestr, OGRGeometry(ls.wkt))
|
||||
self.assertNotEqual(linestr, prev)
|
||||
msg = 'Index out of range when accessing points of a line string: %s.'
|
||||
msg = "Index out of range when accessing points of a line string: %s."
|
||||
with self.assertRaisesMessage(IndexError, msg % len(linestr)):
|
||||
linestr.__getitem__(len(linestr))
|
||||
prev = linestr
|
||||
@@ -170,11 +178,11 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_multilinestring(self):
|
||||
"Testing MultiLineString objects."
|
||||
prev = OGRGeometry('POINT(0 0)')
|
||||
prev = OGRGeometry("POINT(0 0)")
|
||||
for mls in self.geometries.multilinestrings:
|
||||
mlinestr = OGRGeometry(mls.wkt)
|
||||
self.assertEqual(5, mlinestr.geom_type)
|
||||
self.assertEqual('MULTILINESTRING', mlinestr.geom_name)
|
||||
self.assertEqual("MULTILINESTRING", mlinestr.geom_name)
|
||||
self.assertEqual(mls.n_p, mlinestr.point_count)
|
||||
self.assertEqual(mls.coords, mlinestr.tuple)
|
||||
self.assertEqual(mlinestr, OGRGeometry(mls.wkt))
|
||||
@@ -182,18 +190,18 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
prev = mlinestr
|
||||
for ls in mlinestr:
|
||||
self.assertEqual(2, ls.geom_type)
|
||||
self.assertEqual('LINESTRING', ls.geom_name)
|
||||
msg = 'Index out of range when accessing geometry in a collection: %s.'
|
||||
self.assertEqual("LINESTRING", ls.geom_name)
|
||||
msg = "Index out of range when accessing geometry in a collection: %s."
|
||||
with self.assertRaisesMessage(IndexError, msg % len(mlinestr)):
|
||||
mlinestr.__getitem__(len(mlinestr))
|
||||
|
||||
def test_linearring(self):
|
||||
"Testing LinearRing objects."
|
||||
prev = OGRGeometry('POINT(0 0)')
|
||||
prev = OGRGeometry("POINT(0 0)")
|
||||
for rr in self.geometries.linearrings:
|
||||
lr = OGRGeometry(rr.wkt)
|
||||
# self.assertEqual(101, lr.geom_type.num)
|
||||
self.assertEqual('LINEARRING', lr.geom_name)
|
||||
self.assertEqual("LINEARRING", lr.geom_name)
|
||||
self.assertEqual(rr.n_p, len(lr))
|
||||
self.assertEqual(lr, OGRGeometry(rr.wkt))
|
||||
self.assertNotEqual(lr, prev)
|
||||
@@ -207,14 +215,14 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
p = OGRGeometry.from_bbox(bbox)
|
||||
self.assertEqual(bbox, p.extent)
|
||||
|
||||
prev = OGRGeometry('POINT(0 0)')
|
||||
prev = OGRGeometry("POINT(0 0)")
|
||||
for p in self.geometries.polygons:
|
||||
poly = OGRGeometry(p.wkt)
|
||||
self.assertEqual(3, poly.geom_type)
|
||||
self.assertEqual('POLYGON', poly.geom_name)
|
||||
self.assertEqual("POLYGON", poly.geom_name)
|
||||
self.assertEqual(p.n_p, poly.point_count)
|
||||
self.assertEqual(p.n_i + 1, len(poly))
|
||||
msg = 'Index out of range when accessing rings of a polygon: %s.'
|
||||
msg = "Index out of range when accessing rings of a polygon: %s."
|
||||
with self.assertRaisesMessage(IndexError, msg % len(poly)):
|
||||
poly.__getitem__(len(poly))
|
||||
|
||||
@@ -235,43 +243,45 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
self.assertEqual(len(p.ext_ring_cs), ring.point_count)
|
||||
|
||||
for r in poly:
|
||||
self.assertEqual('LINEARRING', r.geom_name)
|
||||
self.assertEqual("LINEARRING", r.geom_name)
|
||||
|
||||
def test_polygons_templates(self):
|
||||
# Accessing Polygon attributes in templates should work.
|
||||
engine = Engine()
|
||||
template = engine.from_string('{{ polygons.0.wkt }}')
|
||||
template = engine.from_string("{{ polygons.0.wkt }}")
|
||||
polygons = [OGRGeometry(p.wkt) for p in self.geometries.multipolygons[:2]]
|
||||
content = template.render(Context({'polygons': polygons}))
|
||||
self.assertIn('MULTIPOLYGON (((100', content)
|
||||
content = template.render(Context({"polygons": polygons}))
|
||||
self.assertIn("MULTIPOLYGON (((100", content)
|
||||
|
||||
def test_closepolygons(self):
|
||||
"Testing closing Polygon objects."
|
||||
# Both rings in this geometry are not closed.
|
||||
poly = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5), (1 1, 2 1, 2 2, 2 1))')
|
||||
poly = OGRGeometry("POLYGON((0 0, 5 0, 5 5, 0 5), (1 1, 2 1, 2 2, 2 1))")
|
||||
self.assertEqual(8, poly.point_count)
|
||||
with self.assertRaises(GDALException):
|
||||
poly.centroid
|
||||
|
||||
poly.close_rings()
|
||||
self.assertEqual(10, poly.point_count) # Two closing points should've been added
|
||||
self.assertEqual(OGRGeometry('POINT(2.5 2.5)'), poly.centroid)
|
||||
self.assertEqual(
|
||||
10, poly.point_count
|
||||
) # Two closing points should've been added
|
||||
self.assertEqual(OGRGeometry("POINT(2.5 2.5)"), poly.centroid)
|
||||
|
||||
def test_multipolygons(self):
|
||||
"Testing MultiPolygon objects."
|
||||
OGRGeometry('POINT(0 0)')
|
||||
OGRGeometry("POINT(0 0)")
|
||||
for mp in self.geometries.multipolygons:
|
||||
mpoly = OGRGeometry(mp.wkt)
|
||||
self.assertEqual(6, mpoly.geom_type)
|
||||
self.assertEqual('MULTIPOLYGON', mpoly.geom_name)
|
||||
self.assertEqual("MULTIPOLYGON", mpoly.geom_name)
|
||||
if mp.valid:
|
||||
self.assertEqual(mp.n_p, mpoly.point_count)
|
||||
self.assertEqual(mp.num_geom, len(mpoly))
|
||||
msg = 'Index out of range when accessing geometry in a collection: %s.'
|
||||
msg = "Index out of range when accessing geometry in a collection: %s."
|
||||
with self.assertRaisesMessage(IndexError, msg % len(mpoly)):
|
||||
mpoly.__getitem__(len(mpoly))
|
||||
for p in mpoly:
|
||||
self.assertEqual('POLYGON', p.geom_name)
|
||||
self.assertEqual("POLYGON", p.geom_name)
|
||||
self.assertEqual(3, p.geom_type)
|
||||
self.assertEqual(mpoly.wkt, OGRGeometry(mp.wkt).wkt)
|
||||
|
||||
@@ -279,7 +289,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
"Testing OGR Geometries with Spatial Reference objects."
|
||||
for mp in self.geometries.multipolygons:
|
||||
# Creating a geometry w/spatial reference
|
||||
sr = SpatialReference('WGS84')
|
||||
sr = SpatialReference("WGS84")
|
||||
mpoly = OGRGeometry(mp.wkt, sr)
|
||||
self.assertEqual(sr.wkt, mpoly.srs.wkt)
|
||||
|
||||
@@ -307,7 +317,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
self.assertEqual(4326, mpoly.srid)
|
||||
mpoly.srs = SpatialReference(4269)
|
||||
self.assertEqual(4269, mpoly.srid)
|
||||
self.assertEqual('NAD83', mpoly.srs.name)
|
||||
self.assertEqual("NAD83", mpoly.srs.name)
|
||||
|
||||
# Incrementing through the multipolygon after the spatial reference
|
||||
# has been re-assigned.
|
||||
@@ -317,13 +327,13 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
for ring in poly:
|
||||
# Changing each ring in the polygon
|
||||
self.assertEqual(32140, ring.srs.srid)
|
||||
self.assertEqual('NAD83 / Texas South Central', ring.srs.name)
|
||||
self.assertEqual("NAD83 / Texas South Central", ring.srs.name)
|
||||
ring.srs = str(SpatialReference(4326)) # back to WGS84
|
||||
self.assertEqual(4326, ring.srs.srid)
|
||||
|
||||
# Using the `srid` property.
|
||||
ring.srid = 4322
|
||||
self.assertEqual('WGS 72', ring.srs.name)
|
||||
self.assertEqual("WGS 72", ring.srs.name)
|
||||
self.assertEqual(4322, ring.srid)
|
||||
|
||||
# srs/srid may be assigned their own values, even when srs is None.
|
||||
@@ -333,15 +343,15 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_srs_transform(self):
|
||||
"Testing transform()."
|
||||
orig = OGRGeometry('POINT (-104.609 38.255)', 4326)
|
||||
trans = OGRGeometry('POINT (992385.4472045 481455.4944650)', 2774)
|
||||
orig = OGRGeometry("POINT (-104.609 38.255)", 4326)
|
||||
trans = OGRGeometry("POINT (992385.4472045 481455.4944650)", 2774)
|
||||
|
||||
# Using an srid, a SpatialReference object, and a CoordTransform object
|
||||
# or transformations.
|
||||
t1, t2, t3 = orig.clone(), orig.clone(), orig.clone()
|
||||
t1.transform(trans.srid)
|
||||
t2.transform(SpatialReference('EPSG:2774'))
|
||||
ct = CoordTransform(SpatialReference('WGS84'), SpatialReference(2774))
|
||||
t2.transform(SpatialReference("EPSG:2774"))
|
||||
ct = CoordTransform(SpatialReference("WGS84"), SpatialReference(2774))
|
||||
t3.transform(ct)
|
||||
|
||||
# Testing use of the `clone` keyword.
|
||||
@@ -359,8 +369,8 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_transform_dim(self):
|
||||
"Testing coordinate dimension is the same on transformed geometries."
|
||||
ls_orig = OGRGeometry('LINESTRING(-104.609 38.255)', 4326)
|
||||
ls_trans = OGRGeometry('LINESTRING(992385.4472045 481455.4944650)', 2774)
|
||||
ls_orig = OGRGeometry("LINESTRING(-104.609 38.255)", 4326)
|
||||
ls_trans = OGRGeometry("LINESTRING(992385.4472045 481455.4944650)", 2774)
|
||||
|
||||
# Different PROJ versions use different transformations, all are
|
||||
# correct as having a 1 meter accuracy.
|
||||
@@ -379,7 +389,9 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
d1 = OGRGeometry(self.geometries.diff_geoms[i].wkt)
|
||||
d2 = a.difference(b)
|
||||
self.assertTrue(d1.geos.equals(d2.geos))
|
||||
self.assertTrue(d1.geos.equals((a - b).geos)) # __sub__ is difference operator
|
||||
self.assertTrue(
|
||||
d1.geos.equals((a - b).geos)
|
||||
) # __sub__ is difference operator
|
||||
a -= b # testing __isub__
|
||||
self.assertTrue(d1.geos.equals(a.geos))
|
||||
|
||||
@@ -392,7 +404,9 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
self.assertTrue(a.intersects(b))
|
||||
i2 = a.intersection(b)
|
||||
self.assertTrue(i1.geos.equals(i2.geos))
|
||||
self.assertTrue(i1.geos.equals((a & b).geos)) # __and__ is intersection operator
|
||||
self.assertTrue(
|
||||
i1.geos.equals((a & b).geos)
|
||||
) # __and__ is intersection operator
|
||||
a &= b # testing __iand__
|
||||
self.assertTrue(i1.geos.equals(a.geos))
|
||||
|
||||
@@ -404,7 +418,9 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
d1 = OGRGeometry(self.geometries.sdiff_geoms[i].wkt)
|
||||
d2 = a.sym_difference(b)
|
||||
self.assertTrue(d1.geos.equals(d2.geos))
|
||||
self.assertTrue(d1.geos.equals((a ^ b).geos)) # __xor__ is symmetric difference operator
|
||||
self.assertTrue(
|
||||
d1.geos.equals((a ^ b).geos)
|
||||
) # __xor__ is symmetric difference operator
|
||||
a ^= b # testing __ixor__
|
||||
self.assertTrue(d1.geos.equals(a.geos))
|
||||
|
||||
@@ -423,8 +439,8 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
def test_add(self):
|
||||
"Testing GeometryCollection.add()."
|
||||
# Can't insert a Point into a MultiPolygon.
|
||||
mp = OGRGeometry('MultiPolygon')
|
||||
pnt = OGRGeometry('POINT(5 23)')
|
||||
mp = OGRGeometry("MultiPolygon")
|
||||
pnt = OGRGeometry("POINT(5 23)")
|
||||
with self.assertRaises(GDALException):
|
||||
mp.add(pnt)
|
||||
|
||||
@@ -432,9 +448,9 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
# of the same type all child geoms will be added individually) or WKT.
|
||||
for mp in self.geometries.multipolygons:
|
||||
mpoly = OGRGeometry(mp.wkt)
|
||||
mp1 = OGRGeometry('MultiPolygon')
|
||||
mp2 = OGRGeometry('MultiPolygon')
|
||||
mp3 = OGRGeometry('MultiPolygon')
|
||||
mp1 = OGRGeometry("MultiPolygon")
|
||||
mp2 = OGRGeometry("MultiPolygon")
|
||||
mp3 = OGRGeometry("MultiPolygon")
|
||||
|
||||
for poly in mpoly:
|
||||
mp1.add(poly) # Adding a geometry at a time
|
||||
@@ -446,7 +462,7 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
def test_extent(self):
|
||||
"Testing `extent` property."
|
||||
# The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
|
||||
mp = OGRGeometry('MULTIPOINT(5 23, 0 0, 10 50)')
|
||||
mp = OGRGeometry("MULTIPOINT(5 23, 0 0, 10 50)")
|
||||
self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent)
|
||||
# Testing on the 'real world' Polygon.
|
||||
poly = OGRGeometry(self.geometries.polygons[3].wkt)
|
||||
@@ -458,18 +474,18 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_25D(self):
|
||||
"Testing 2.5D geometries."
|
||||
pnt_25d = OGRGeometry('POINT(1 2 3)')
|
||||
self.assertEqual('Point25D', pnt_25d.geom_type.name)
|
||||
pnt_25d = OGRGeometry("POINT(1 2 3)")
|
||||
self.assertEqual("Point25D", pnt_25d.geom_type.name)
|
||||
self.assertEqual(3.0, pnt_25d.z)
|
||||
self.assertEqual(3, pnt_25d.coord_dim)
|
||||
ls_25d = OGRGeometry('LINESTRING(1 1 1,2 2 2,3 3 3)')
|
||||
self.assertEqual('LineString25D', ls_25d.geom_type.name)
|
||||
ls_25d = OGRGeometry("LINESTRING(1 1 1,2 2 2,3 3 3)")
|
||||
self.assertEqual("LineString25D", ls_25d.geom_type.name)
|
||||
self.assertEqual([1.0, 2.0, 3.0], ls_25d.z)
|
||||
self.assertEqual(3, ls_25d.coord_dim)
|
||||
|
||||
def test_pickle(self):
|
||||
"Testing pickle support."
|
||||
g1 = OGRGeometry('LINESTRING(1 1 1,2 2 2,3 3 3)', 'WGS84')
|
||||
g1 = OGRGeometry("LINESTRING(1 1 1,2 2 2,3 3 3)", "WGS84")
|
||||
g2 = pickle.loads(pickle.dumps(g1))
|
||||
self.assertEqual(g1, g2)
|
||||
self.assertEqual(4326, g2.srs.srid)
|
||||
@@ -504,63 +520,114 @@ class OGRGeomTest(SimpleTestCase, TestDataMixin):
|
||||
|
||||
def test_equivalence_regression(self):
|
||||
"Testing equivalence methods with non-OGRGeometry instances."
|
||||
self.assertIsNotNone(OGRGeometry('POINT(0 0)'))
|
||||
self.assertNotEqual(OGRGeometry('LINESTRING(0 0, 1 1)'), 3)
|
||||
self.assertIsNotNone(OGRGeometry("POINT(0 0)"))
|
||||
self.assertNotEqual(OGRGeometry("LINESTRING(0 0, 1 1)"), 3)
|
||||
|
||||
def test_contains(self):
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').contains(OGRGeometry('POINT(0 0)')), True)
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').contains(OGRGeometry('POINT(0 1)')), False)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").contains(OGRGeometry("POINT(0 0)")), True
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").contains(OGRGeometry("POINT(0 1)")), False
|
||||
)
|
||||
|
||||
def test_crosses(self):
|
||||
self.assertIs(OGRGeometry('LINESTRING(0 0, 1 1)').crosses(OGRGeometry('LINESTRING(0 1, 1 0)')), True)
|
||||
self.assertIs(OGRGeometry('LINESTRING(0 0, 0 1)').crosses(OGRGeometry('LINESTRING(1 0, 1 1)')), False)
|
||||
self.assertIs(
|
||||
OGRGeometry("LINESTRING(0 0, 1 1)").crosses(
|
||||
OGRGeometry("LINESTRING(0 1, 1 0)")
|
||||
),
|
||||
True,
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("LINESTRING(0 0, 0 1)").crosses(
|
||||
OGRGeometry("LINESTRING(1 0, 1 1)")
|
||||
),
|
||||
False,
|
||||
)
|
||||
|
||||
def test_disjoint(self):
|
||||
self.assertIs(OGRGeometry('LINESTRING(0 0, 1 1)').disjoint(OGRGeometry('LINESTRING(0 1, 1 0)')), False)
|
||||
self.assertIs(OGRGeometry('LINESTRING(0 0, 0 1)').disjoint(OGRGeometry('LINESTRING(1 0, 1 1)')), True)
|
||||
self.assertIs(
|
||||
OGRGeometry("LINESTRING(0 0, 1 1)").disjoint(
|
||||
OGRGeometry("LINESTRING(0 1, 1 0)")
|
||||
),
|
||||
False,
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("LINESTRING(0 0, 0 1)").disjoint(
|
||||
OGRGeometry("LINESTRING(1 0, 1 1)")
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
def test_equals(self):
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').contains(OGRGeometry('POINT(0 0)')), True)
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').contains(OGRGeometry('POINT(0 1)')), False)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").contains(OGRGeometry("POINT(0 0)")), True
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").contains(OGRGeometry("POINT(0 1)")), False
|
||||
)
|
||||
|
||||
def test_intersects(self):
|
||||
self.assertIs(OGRGeometry('LINESTRING(0 0, 1 1)').intersects(OGRGeometry('LINESTRING(0 1, 1 0)')), True)
|
||||
self.assertIs(OGRGeometry('LINESTRING(0 0, 0 1)').intersects(OGRGeometry('LINESTRING(1 0, 1 1)')), False)
|
||||
self.assertIs(
|
||||
OGRGeometry("LINESTRING(0 0, 1 1)").intersects(
|
||||
OGRGeometry("LINESTRING(0 1, 1 0)")
|
||||
),
|
||||
True,
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("LINESTRING(0 0, 0 1)").intersects(
|
||||
OGRGeometry("LINESTRING(1 0, 1 1)")
|
||||
),
|
||||
False,
|
||||
)
|
||||
|
||||
def test_overlaps(self):
|
||||
self.assertIs(
|
||||
OGRGeometry('POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))').overlaps(
|
||||
OGRGeometry('POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1))')
|
||||
), True
|
||||
OGRGeometry("POLYGON ((0 0, 0 2, 2 2, 2 0, 0 0))").overlaps(
|
||||
OGRGeometry("POLYGON ((1 1, 1 5, 5 5, 5 1, 1 1))")
|
||||
),
|
||||
True,
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").overlaps(OGRGeometry("POINT(0 1)")), False
|
||||
)
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').overlaps(OGRGeometry('POINT(0 1)')), False)
|
||||
|
||||
def test_touches(self):
|
||||
self.assertIs(
|
||||
OGRGeometry('POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))').touches(OGRGeometry('LINESTRING(0 2, 2 0)')), True
|
||||
OGRGeometry("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))").touches(
|
||||
OGRGeometry("LINESTRING(0 2, 2 0)")
|
||||
),
|
||||
True,
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").touches(OGRGeometry("POINT(0 1)")), False
|
||||
)
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').touches(OGRGeometry('POINT(0 1)')), False)
|
||||
|
||||
def test_within(self):
|
||||
self.assertIs(
|
||||
OGRGeometry('POINT(0.5 0.5)').within(OGRGeometry('POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))')), True
|
||||
OGRGeometry("POINT(0.5 0.5)").within(
|
||||
OGRGeometry("POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))")
|
||||
),
|
||||
True,
|
||||
)
|
||||
self.assertIs(
|
||||
OGRGeometry("POINT(0 0)").within(OGRGeometry("POINT(0 1)")), False
|
||||
)
|
||||
self.assertIs(OGRGeometry('POINT(0 0)').within(OGRGeometry('POINT(0 1)')), False)
|
||||
|
||||
def test_from_gml(self):
|
||||
self.assertEqual(
|
||||
OGRGeometry('POINT(0 0)'),
|
||||
OGRGeometry("POINT(0 0)"),
|
||||
OGRGeometry.from_gml(
|
||||
'<gml:Point gml:id="p21" srsName="http://www.opengis.net/def/crs/EPSG/0/4326">'
|
||||
' <gml:pos srsDimension="2">0 0</gml:pos>'
|
||||
'</gml:Point>'
|
||||
"</gml:Point>"
|
||||
),
|
||||
)
|
||||
|
||||
def test_empty(self):
|
||||
self.assertIs(OGRGeometry('POINT (0 0)').empty, False)
|
||||
self.assertIs(OGRGeometry('POINT EMPTY').empty, True)
|
||||
self.assertIs(OGRGeometry("POINT (0 0)").empty, False)
|
||||
self.assertIs(OGRGeometry("POINT EMPTY").empty, True)
|
||||
|
||||
def test_empty_point_to_geos(self):
|
||||
p = OGRGeometry('POINT EMPTY', srs=4326)
|
||||
p = OGRGeometry("POINT EMPTY", srs=4326)
|
||||
self.assertEqual(p.geos.ewkt, p.ewkt)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,11 @@
|
||||
from unittest import skipIf
|
||||
|
||||
from django.contrib.gis.gdal import (
|
||||
GDAL_VERSION, AxisOrder, CoordTransform, GDALException, SpatialReference,
|
||||
GDAL_VERSION,
|
||||
AxisOrder,
|
||||
CoordTransform,
|
||||
GDALException,
|
||||
SpatialReference,
|
||||
SRSException,
|
||||
)
|
||||
from django.contrib.gis.geos import GEOSGeometry
|
||||
@@ -15,7 +19,7 @@ class TestSRS:
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
WGS84_proj = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs '
|
||||
WGS84_proj = "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs "
|
||||
|
||||
# Some Spatial Reference examples
|
||||
srlist = (
|
||||
@@ -25,10 +29,20 @@ srlist = (
|
||||
'PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",'
|
||||
'0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],'
|
||||
'AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]',
|
||||
epsg=4326, projected=False, geographic=True, local=False,
|
||||
lin_name='unknown', ang_name='degree', lin_units=1.0, ang_units=0.0174532925199,
|
||||
auth={'GEOGCS': ('EPSG', '4326'), 'spheroid': ('EPSG', '7030')},
|
||||
attr=(('DATUM', 'WGS_1984'), (('SPHEROID', 1), '6378137'), ('primem|authority', 'EPSG'),),
|
||||
epsg=4326,
|
||||
projected=False,
|
||||
geographic=True,
|
||||
local=False,
|
||||
lin_name="unknown",
|
||||
ang_name="degree",
|
||||
lin_units=1.0,
|
||||
ang_units=0.0174532925199,
|
||||
auth={"GEOGCS": ("EPSG", "4326"), "spheroid": ("EPSG", "7030")},
|
||||
attr=(
|
||||
("DATUM", "WGS_1984"),
|
||||
(("SPHEROID", 1), "6378137"),
|
||||
("primem|authority", "EPSG"),
|
||||
),
|
||||
),
|
||||
TestSRS(
|
||||
'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",'
|
||||
@@ -42,13 +56,23 @@ srlist = (
|
||||
'PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],'
|
||||
'PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],'
|
||||
'AXIS["Easting",EAST],AXIS["Northing",NORTH],AUTHORITY["EPSG","32140"]]',
|
||||
epsg=32140, projected=True, geographic=False, local=False,
|
||||
lin_name='metre', ang_name='degree', lin_units=1.0, ang_units=0.0174532925199,
|
||||
auth={'PROJCS': ('EPSG', '32140'), 'spheroid': ('EPSG', '7019'), 'unit': ('EPSG', '9001')},
|
||||
epsg=32140,
|
||||
projected=True,
|
||||
geographic=False,
|
||||
local=False,
|
||||
lin_name="metre",
|
||||
ang_name="degree",
|
||||
lin_units=1.0,
|
||||
ang_units=0.0174532925199,
|
||||
auth={
|
||||
"PROJCS": ("EPSG", "32140"),
|
||||
"spheroid": ("EPSG", "7019"),
|
||||
"unit": ("EPSG", "9001"),
|
||||
},
|
||||
attr=(
|
||||
('DATUM', 'North_American_Datum_1983'),
|
||||
(('SPHEROID', 2), '298.257222101'),
|
||||
('PROJECTION', 'Lambert_Conformal_Conic_2SP'),
|
||||
("DATUM", "North_American_Datum_1983"),
|
||||
(("SPHEROID", 2), "298.257222101"),
|
||||
("PROJECTION", "Lambert_Conformal_Conic_2SP"),
|
||||
),
|
||||
),
|
||||
TestSRS(
|
||||
@@ -61,18 +85,35 @@ srlist = (
|
||||
'PARAMETER["central_meridian",-99],PARAMETER["standard_parallel_1",28.3833333333333],'
|
||||
'PARAMETER["standard_parallel_2",30.2833333333333],PARAMETER["latitude_of_origin",27.8333333333333],'
|
||||
'UNIT["US survey foot",0.304800609601219],AXIS["Easting",EAST],AXIS["Northing",NORTH]]',
|
||||
epsg=None, projected=True, geographic=False, local=False,
|
||||
lin_name='US survey foot', ang_name='Degree', lin_units=0.3048006096012192, ang_units=0.0174532925199,
|
||||
auth={'PROJCS': (None, None)},
|
||||
attr=(('PROJCS|GeOgCs|spheroid', 'GRS 1980'), (('projcs', 9), 'UNIT'), (('projcs', 11), 'AXIS'),),
|
||||
epsg=None,
|
||||
projected=True,
|
||||
geographic=False,
|
||||
local=False,
|
||||
lin_name="US survey foot",
|
||||
ang_name="Degree",
|
||||
lin_units=0.3048006096012192,
|
||||
ang_units=0.0174532925199,
|
||||
auth={"PROJCS": (None, None)},
|
||||
attr=(
|
||||
("PROJCS|GeOgCs|spheroid", "GRS 1980"),
|
||||
(("projcs", 9), "UNIT"),
|
||||
(("projcs", 11), "AXIS"),
|
||||
),
|
||||
),
|
||||
# This is really ESRI format, not WKT -- but the import should work the same
|
||||
TestSRS(
|
||||
'LOCAL_CS["Non-Earth (Meter)",LOCAL_DATUM["Local Datum",32767],'
|
||||
'UNIT["Meter",1],AXIS["X",EAST],AXIS["Y",NORTH]]',
|
||||
esri=True, epsg=None, projected=False, geographic=False, local=True,
|
||||
lin_name='Meter', ang_name='degree', lin_units=1.0, ang_units=0.0174532925199,
|
||||
attr=(('LOCAL_DATUM', 'Local Datum'),),
|
||||
esri=True,
|
||||
epsg=None,
|
||||
projected=False,
|
||||
geographic=False,
|
||||
local=True,
|
||||
lin_name="Meter",
|
||||
ang_name="degree",
|
||||
lin_units=1.0,
|
||||
ang_units=0.0174532925199,
|
||||
attr=(("LOCAL_DATUM", "Local Datum"),),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -83,8 +124,9 @@ well_known = (
|
||||
'AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],'
|
||||
'PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,'
|
||||
'AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]',
|
||||
wk='WGS84', name='WGS 84',
|
||||
attrs=(('GEOGCS|AUTHORITY', 1, '4326'), ('SPHEROID', 'WGS 84')),
|
||||
wk="WGS84",
|
||||
name="WGS 84",
|
||||
attrs=(("GEOGCS|AUTHORITY", 1, "4326"), ("SPHEROID", "WGS 84")),
|
||||
),
|
||||
TestSRS(
|
||||
'GEOGCS["WGS 72",DATUM["WGS_1972",SPHEROID["WGS 72",6378135,298.26,'
|
||||
@@ -92,8 +134,9 @@ well_known = (
|
||||
'PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],'
|
||||
'UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],'
|
||||
'AUTHORITY["EPSG","4322"]]',
|
||||
wk='WGS72', name='WGS 72',
|
||||
attrs=(('GEOGCS|AUTHORITY', 1, '4322'), ('SPHEROID', 'WGS 72')),
|
||||
wk="WGS72",
|
||||
name="WGS 72",
|
||||
attrs=(("GEOGCS|AUTHORITY", 1, "4322"), ("SPHEROID", "WGS 72")),
|
||||
),
|
||||
TestSRS(
|
||||
'GEOGCS["NAD27",DATUM["North_American_Datum_1927",'
|
||||
@@ -102,8 +145,9 @@ well_known = (
|
||||
'PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],'
|
||||
'UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],'
|
||||
'AUTHORITY["EPSG","4267"]]',
|
||||
wk='NAD27', name='NAD27',
|
||||
attrs=(('GEOGCS|AUTHORITY', 1, '4267'), ('SPHEROID', 'Clarke 1866'))
|
||||
wk="NAD27",
|
||||
name="NAD27",
|
||||
attrs=(("GEOGCS|AUTHORITY", 1, "4267"), ("SPHEROID", "Clarke 1866")),
|
||||
),
|
||||
TestSRS(
|
||||
'GEOGCS["NAD83",DATUM["North_American_Datum_1983",'
|
||||
@@ -112,15 +156,16 @@ well_known = (
|
||||
'PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],'
|
||||
'UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],'
|
||||
'AUTHORITY["EPSG","4269"]]',
|
||||
wk='NAD83', name='NAD83',
|
||||
attrs=(('GEOGCS|AUTHORITY', 1, '4269'), ('SPHEROID', 'GRS 1980')),
|
||||
wk="NAD83",
|
||||
name="NAD83",
|
||||
attrs=(("GEOGCS|AUTHORITY", 1, "4269"), ("SPHEROID", "GRS 1980")),
|
||||
),
|
||||
TestSRS(
|
||||
'PROJCS["NZGD49 / Karamea Circuit",GEOGCS["NZGD49",'
|
||||
'DATUM["New_Zealand_Geodetic_Datum_1949",'
|
||||
'SPHEROID["International 1924",6378388,297,'
|
||||
'AUTHORITY["EPSG","7022"]],'
|
||||
'TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],'
|
||||
"TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],"
|
||||
'AUTHORITY["EPSG","6272"]],PRIMEM["Greenwich",0,'
|
||||
'AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,'
|
||||
'AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4272"]],'
|
||||
@@ -130,13 +175,17 @@ well_known = (
|
||||
'PARAMETER["scale_factor",1],PARAMETER["false_easting",300000],'
|
||||
'PARAMETER["false_northing",700000],'
|
||||
'UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","27216"]]',
|
||||
wk='EPSG:27216', name='NZGD49 / Karamea Circuit',
|
||||
attrs=(('PROJECTION', 'Transverse_Mercator'), ('SPHEROID', 'International 1924')),
|
||||
wk="EPSG:27216",
|
||||
name="NZGD49 / Karamea Circuit",
|
||||
attrs=(
|
||||
("PROJECTION", "Transverse_Mercator"),
|
||||
("SPHEROID", "International 1924"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
bad_srlist = (
|
||||
'Foobar',
|
||||
"Foobar",
|
||||
'OOJCS["NAD83 / Texas South Central",GEOGCS["NAD83",'
|
||||
'DATUM["North_American_Datum_1983",'
|
||||
'SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],'
|
||||
@@ -153,7 +202,6 @@ bad_srlist = (
|
||||
|
||||
|
||||
class SpatialRefTest(SimpleTestCase):
|
||||
|
||||
def test01_wkt(self):
|
||||
"Testing initialization on valid OGC WKT."
|
||||
for s in srlist:
|
||||
@@ -176,14 +224,18 @@ class SpatialRefTest(SimpleTestCase):
|
||||
srs = SpatialReference(s.wkt)
|
||||
# GDAL 3 strips UNIT part in the last occurrence.
|
||||
self.assertEqual(
|
||||
s.wkt.replace(',UNIT["Meter",1]', ''),
|
||||
srs.wkt.replace(',UNIT["Meter",1]', ''),
|
||||
s.wkt.replace(',UNIT["Meter",1]', ""),
|
||||
srs.wkt.replace(',UNIT["Meter",1]', ""),
|
||||
)
|
||||
|
||||
def test04_proj(self):
|
||||
"""PROJ import and export."""
|
||||
proj_parts = [
|
||||
'+proj=longlat', '+ellps=WGS84', '+towgs84=0,0,0,0,0,0,0', '+datum=WGS84', '+no_defs'
|
||||
"+proj=longlat",
|
||||
"+ellps=WGS84",
|
||||
"+towgs84=0,0,0,0,0,0,0",
|
||||
"+datum=WGS84",
|
||||
"+no_defs",
|
||||
]
|
||||
srs1 = SpatialReference(srlist[0].wkt)
|
||||
srs2 = SpatialReference(WGS84_proj)
|
||||
@@ -197,7 +249,7 @@ class SpatialRefTest(SimpleTestCase):
|
||||
srs1 = SpatialReference(s.wkt)
|
||||
srs2 = SpatialReference(s.epsg)
|
||||
srs3 = SpatialReference(str(s.epsg))
|
||||
srs4 = SpatialReference('EPSG:%d' % s.epsg)
|
||||
srs4 = SpatialReference("EPSG:%d" % s.epsg)
|
||||
for srs in (srs1, srs2, srs3, srs4):
|
||||
for attr, expected in s.attr:
|
||||
self.assertEqual(expected, srs[attr])
|
||||
@@ -221,7 +273,7 @@ class SpatialRefTest(SimpleTestCase):
|
||||
def test09_authority(self):
|
||||
"Testing the authority name & code routines."
|
||||
for s in srlist:
|
||||
if hasattr(s, 'auth'):
|
||||
if hasattr(s, "auth"):
|
||||
srs = SpatialReference(s.wkt)
|
||||
for target, tup in s.auth.items():
|
||||
self.assertEqual(tup[0], srs.auth_name(target))
|
||||
@@ -252,21 +304,21 @@ class SpatialRefTest(SimpleTestCase):
|
||||
|
||||
def test12_coordtransform(self):
|
||||
"Testing initialization of a CoordTransform."
|
||||
target = SpatialReference('WGS84')
|
||||
target = SpatialReference("WGS84")
|
||||
CoordTransform(SpatialReference(srlist[0].wkt), target)
|
||||
|
||||
def test13_attr_value(self):
|
||||
"Testing the attr_value() method."
|
||||
s1 = SpatialReference('WGS84')
|
||||
s1 = SpatialReference("WGS84")
|
||||
with self.assertRaises(TypeError):
|
||||
s1.__getitem__(0)
|
||||
with self.assertRaises(TypeError):
|
||||
s1.__getitem__(('GEOGCS', 'foo'))
|
||||
self.assertEqual('WGS 84', s1['GEOGCS'])
|
||||
self.assertEqual('WGS_1984', s1['DATUM'])
|
||||
self.assertEqual('EPSG', s1['AUTHORITY'])
|
||||
self.assertEqual(4326, int(s1['AUTHORITY', 1]))
|
||||
self.assertIsNone(s1['FOOBAR'])
|
||||
s1.__getitem__(("GEOGCS", "foo"))
|
||||
self.assertEqual("WGS 84", s1["GEOGCS"])
|
||||
self.assertEqual("WGS_1984", s1["DATUM"])
|
||||
self.assertEqual("EPSG", s1["AUTHORITY"])
|
||||
self.assertEqual(4326, int(s1["AUTHORITY", 1]))
|
||||
self.assertIsNone(s1["FOOBAR"])
|
||||
|
||||
def test_unicode(self):
|
||||
wkt = (
|
||||
@@ -285,17 +337,17 @@ class SpatialRefTest(SimpleTestCase):
|
||||
srs.import_wkt(wkt)
|
||||
|
||||
for srs in srs_list:
|
||||
self.assertEqual(srs.name, 'DHDN / Soldner 39 Langschoß')
|
||||
self.assertEqual(srs.name, "DHDN / Soldner 39 Langschoß")
|
||||
self.assertEqual(srs.wkt, wkt)
|
||||
self.assertIn('Langschoß', srs.pretty_wkt)
|
||||
self.assertIn('Langschoß', srs.xml)
|
||||
self.assertIn("Langschoß", srs.pretty_wkt)
|
||||
self.assertIn("Langschoß", srs.xml)
|
||||
|
||||
@skipIf(GDAL_VERSION < (3, 0), 'GDAL >= 3.0 is required')
|
||||
@skipIf(GDAL_VERSION < (3, 0), "GDAL >= 3.0 is required")
|
||||
def test_axis_order(self):
|
||||
wgs84_trad = SpatialReference(4326, axis_order=AxisOrder.TRADITIONAL)
|
||||
wgs84_auth = SpatialReference(4326, axis_order=AxisOrder.AUTHORITY)
|
||||
# Coordinate interpretation may depend on the srs axis predicate.
|
||||
pt = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774)
|
||||
pt = GEOSGeometry("POINT (992385.4472045 481455.4944650)", 2774)
|
||||
pt_trad = pt.transform(wgs84_trad, clone=True)
|
||||
self.assertAlmostEqual(pt_trad.x, -104.609, 3)
|
||||
self.assertAlmostEqual(pt_trad.y, 38.255, 3)
|
||||
@@ -308,18 +360,18 @@ class SpatialRefTest(SimpleTestCase):
|
||||
self.assertAlmostEqual(pt_auth.y, -104.609, 3)
|
||||
|
||||
def test_axis_order_invalid(self):
|
||||
msg = 'SpatialReference.axis_order must be an AxisOrder instance.'
|
||||
msg = "SpatialReference.axis_order must be an AxisOrder instance."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
SpatialReference(4326, axis_order='other')
|
||||
SpatialReference(4326, axis_order="other")
|
||||
|
||||
@skipIf(GDAL_VERSION > (3, 0), "GDAL < 3.0 doesn't support authority.")
|
||||
def test_axis_order_non_traditional_invalid(self):
|
||||
msg = 'AxisOrder.AUTHORITY is not supported in GDAL < 3.0.'
|
||||
msg = "AxisOrder.AUTHORITY is not supported in GDAL < 3.0."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
SpatialReference(4326, axis_order=AxisOrder.AUTHORITY)
|
||||
|
||||
def test_esri(self):
|
||||
srs = SpatialReference('NAD83')
|
||||
srs = SpatialReference("NAD83")
|
||||
pre_esri_wkt = srs.wkt
|
||||
srs.to_esri()
|
||||
self.assertNotEqual(srs.wkt, pre_esri_wkt)
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import unittest
|
||||
|
||||
from django.contrib.gis.gdal import (
|
||||
GDAL_VERSION, gdal_full_version, gdal_version,
|
||||
)
|
||||
from django.contrib.gis.gdal import GDAL_VERSION, gdal_full_version, gdal_version
|
||||
|
||||
|
||||
class GDALTest(unittest.TestCase):
|
||||
def test_gdal_version(self):
|
||||
if GDAL_VERSION:
|
||||
self.assertEqual(gdal_version(), ('%s.%s.%s' % GDAL_VERSION).encode())
|
||||
self.assertEqual(gdal_version(), ("%s.%s.%s" % GDAL_VERSION).encode())
|
||||
else:
|
||||
self.assertIn(b'.', gdal_version())
|
||||
self.assertIn(b".", gdal_version())
|
||||
|
||||
def test_gdal_full_version(self):
|
||||
full_version = gdal_full_version()
|
||||
self.assertIn(gdal_version(), full_version)
|
||||
self.assertTrue(full_version.startswith(b'GDAL'))
|
||||
self.assertTrue(full_version.startswith(b"GDAL"))
|
||||
|
||||
@@ -16,7 +16,7 @@ class City3D(NamedModel):
|
||||
pointg = models.PointField(dim=3, geography=True)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
|
||||
class Interstate2D(NamedModel):
|
||||
@@ -27,7 +27,7 @@ class Interstate3D(NamedModel):
|
||||
line = models.LineStringField(dim=3, srid=4269)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
|
||||
class InterstateProj2D(NamedModel):
|
||||
@@ -38,7 +38,7 @@ class InterstateProj3D(NamedModel):
|
||||
line = models.LineStringField(dim=3, srid=32140)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
|
||||
class Polygon2D(NamedModel):
|
||||
@@ -49,11 +49,10 @@ class Polygon3D(NamedModel):
|
||||
poly = models.PolygonField(dim=3, srid=32140)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
|
||||
class SimpleModel(models.Model):
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -66,11 +65,11 @@ class Point3D(SimpleModel):
|
||||
point = models.PointField(dim=3)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
|
||||
class MultiPoint3D(SimpleModel):
|
||||
mpoint = models.MultiPointField(dim=3)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
@@ -3,32 +3,45 @@ import re
|
||||
|
||||
from django.contrib.gis.db.models import Extent3D, Union
|
||||
from django.contrib.gis.db.models.functions import (
|
||||
AsGeoJSON, AsKML, Length, Perimeter, Scale, Translate,
|
||||
AsGeoJSON,
|
||||
AsKML,
|
||||
Length,
|
||||
Perimeter,
|
||||
Scale,
|
||||
Translate,
|
||||
)
|
||||
from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
|
||||
from ..utils import FuncTestMixin
|
||||
from .models import (
|
||||
City3D, Interstate2D, Interstate3D, InterstateProj2D, InterstateProj3D,
|
||||
MultiPoint3D, Point2D, Point3D, Polygon2D, Polygon3D,
|
||||
City3D,
|
||||
Interstate2D,
|
||||
Interstate3D,
|
||||
InterstateProj2D,
|
||||
InterstateProj3D,
|
||||
MultiPoint3D,
|
||||
Point2D,
|
||||
Point3D,
|
||||
Polygon2D,
|
||||
Polygon3D,
|
||||
)
|
||||
|
||||
data_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
|
||||
city_file = os.path.join(data_path, 'cities', 'cities.shp')
|
||||
vrt_file = os.path.join(data_path, 'test_vrt', 'test_vrt.vrt')
|
||||
data_path = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "data"))
|
||||
city_file = os.path.join(data_path, "cities", "cities.shp")
|
||||
vrt_file = os.path.join(data_path, "test_vrt", "test_vrt.vrt")
|
||||
|
||||
# The coordinates of each city, with Z values corresponding to their
|
||||
# altitude in meters.
|
||||
city_data = (
|
||||
('Houston', (-95.363151, 29.763374, 18)),
|
||||
('Dallas', (-96.801611, 32.782057, 147)),
|
||||
('Oklahoma City', (-97.521157, 34.464642, 380)),
|
||||
('Wellington', (174.783117, -41.315268, 14)),
|
||||
('Pueblo', (-104.609252, 38.255001, 1433)),
|
||||
('Lawrence', (-95.235060, 38.971823, 251)),
|
||||
('Chicago', (-87.650175, 41.850385, 181)),
|
||||
('Victoria', (-123.305196, 48.462611, 15)),
|
||||
("Houston", (-95.363151, 29.763374, 18)),
|
||||
("Dallas", (-96.801611, 32.782057, 147)),
|
||||
("Oklahoma City", (-97.521157, 34.464642, 380)),
|
||||
("Wellington", (174.783117, -41.315268, 14)),
|
||||
("Pueblo", (-104.609252, 38.255001, 1433)),
|
||||
("Lawrence", (-95.235060, 38.971823, 251)),
|
||||
("Chicago", (-87.650175, 41.850385, 181)),
|
||||
("Victoria", (-123.305196, 48.462611, 15)),
|
||||
)
|
||||
|
||||
# Reference mapping of city name to its altitude (Z value).
|
||||
@@ -37,32 +50,53 @@ city_dict = {name: coords for name, coords in city_data}
|
||||
# 3D freeway data derived from the National Elevation Dataset:
|
||||
# http://seamless.usgs.gov/products/9arc.php
|
||||
interstate_data = (
|
||||
('I-45',
|
||||
'LINESTRING(-95.3708481 29.7765870 11.339,-95.3694580 29.7787980 4.536,'
|
||||
'-95.3690305 29.7797359 9.762,-95.3691886 29.7812450 12.448,'
|
||||
'-95.3696447 29.7850144 10.457,-95.3702511 29.7868518 9.418,'
|
||||
'-95.3706724 29.7881286 14.858,-95.3711632 29.7896157 15.386,'
|
||||
'-95.3714525 29.7936267 13.168,-95.3717848 29.7955007 15.104,'
|
||||
'-95.3717719 29.7969804 16.516,-95.3717305 29.7982117 13.923,'
|
||||
'-95.3717254 29.8000778 14.385,-95.3719875 29.8013539 15.160,'
|
||||
'-95.3720575 29.8026785 15.544,-95.3721321 29.8040912 14.975,'
|
||||
'-95.3722074 29.8050998 15.688,-95.3722779 29.8060430 16.099,'
|
||||
'-95.3733818 29.8076750 15.197,-95.3741563 29.8103686 17.268,'
|
||||
'-95.3749458 29.8129927 19.857,-95.3763564 29.8144557 15.435)',
|
||||
(11.339, 4.536, 9.762, 12.448, 10.457, 9.418, 14.858,
|
||||
15.386, 13.168, 15.104, 16.516, 13.923, 14.385, 15.16,
|
||||
15.544, 14.975, 15.688, 16.099, 15.197, 17.268, 19.857,
|
||||
15.435),
|
||||
),
|
||||
(
|
||||
"I-45",
|
||||
"LINESTRING(-95.3708481 29.7765870 11.339,-95.3694580 29.7787980 4.536,"
|
||||
"-95.3690305 29.7797359 9.762,-95.3691886 29.7812450 12.448,"
|
||||
"-95.3696447 29.7850144 10.457,-95.3702511 29.7868518 9.418,"
|
||||
"-95.3706724 29.7881286 14.858,-95.3711632 29.7896157 15.386,"
|
||||
"-95.3714525 29.7936267 13.168,-95.3717848 29.7955007 15.104,"
|
||||
"-95.3717719 29.7969804 16.516,-95.3717305 29.7982117 13.923,"
|
||||
"-95.3717254 29.8000778 14.385,-95.3719875 29.8013539 15.160,"
|
||||
"-95.3720575 29.8026785 15.544,-95.3721321 29.8040912 14.975,"
|
||||
"-95.3722074 29.8050998 15.688,-95.3722779 29.8060430 16.099,"
|
||||
"-95.3733818 29.8076750 15.197,-95.3741563 29.8103686 17.268,"
|
||||
"-95.3749458 29.8129927 19.857,-95.3763564 29.8144557 15.435)",
|
||||
(
|
||||
11.339,
|
||||
4.536,
|
||||
9.762,
|
||||
12.448,
|
||||
10.457,
|
||||
9.418,
|
||||
14.858,
|
||||
15.386,
|
||||
13.168,
|
||||
15.104,
|
||||
16.516,
|
||||
13.923,
|
||||
14.385,
|
||||
15.16,
|
||||
15.544,
|
||||
14.975,
|
||||
15.688,
|
||||
16.099,
|
||||
15.197,
|
||||
17.268,
|
||||
19.857,
|
||||
15.435,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Bounding box polygon for inner-loop of Houston (in projected coordinate
|
||||
# system 32140), with elevation values from the National Elevation Dataset
|
||||
# (see above).
|
||||
bbox_data = (
|
||||
'POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,'
|
||||
'942051.75 4208366.38,941527.97 4225693.20))',
|
||||
(21.71, 13.21, 9.12, 16.40, 21.71)
|
||||
"POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,"
|
||||
"942051.75 4208366.38,941527.97 4225693.20))",
|
||||
(21.71, 13.21, 9.12, 16.40, 21.71),
|
||||
)
|
||||
|
||||
|
||||
@@ -83,15 +117,19 @@ class Geo3DLoadingHelper:
|
||||
def _load_city_data(self):
|
||||
for name, pnt_data in city_data:
|
||||
City3D.objects.create(
|
||||
name=name, point=Point(*pnt_data, srid=4326), pointg=Point(*pnt_data, srid=4326),
|
||||
name=name,
|
||||
point=Point(*pnt_data, srid=4326),
|
||||
pointg=Point(*pnt_data, srid=4326),
|
||||
)
|
||||
|
||||
def _load_polygon_data(self):
|
||||
bbox_wkt, bbox_z = bbox_data
|
||||
bbox_2d = GEOSGeometry(bbox_wkt, srid=32140)
|
||||
bbox_3d = Polygon(tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140)
|
||||
Polygon2D.objects.create(name='2D BBox', poly=bbox_2d)
|
||||
Polygon3D.objects.create(name='3D BBox', poly=bbox_3d)
|
||||
bbox_3d = Polygon(
|
||||
tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140
|
||||
)
|
||||
Polygon2D.objects.create(name="2D BBox", poly=bbox_2d)
|
||||
Polygon3D.objects.create(name="3D BBox", poly=bbox_3d)
|
||||
|
||||
|
||||
@skipUnlessDBFeature("supports_3d_storage")
|
||||
@@ -132,7 +170,7 @@ class Geo3DTest(Geo3DLoadingHelper, TestCase):
|
||||
Test the creation of polygon 3D models.
|
||||
"""
|
||||
self._load_polygon_data()
|
||||
p3d = Polygon3D.objects.get(name='3D BBox')
|
||||
p3d = Polygon3D.objects.get(name="3D BBox")
|
||||
self.assertTrue(p3d.poly.hasz)
|
||||
self.assertIsInstance(p3d.poly, Polygon)
|
||||
self.assertEqual(p3d.poly.srid, 32140)
|
||||
@@ -144,8 +182,8 @@ class Geo3DTest(Geo3DLoadingHelper, TestCase):
|
||||
# Import here as GDAL is required for those imports
|
||||
from django.contrib.gis.utils import LayerMapError, LayerMapping
|
||||
|
||||
point_mapping = {'point': 'POINT'}
|
||||
mpoint_mapping = {'mpoint': 'MULTIPOINT'}
|
||||
point_mapping = {"point": "POINT"}
|
||||
mpoint_mapping = {"mpoint": "MULTIPOINT"}
|
||||
|
||||
# The VRT is 3D, but should still be able to map sans the Z.
|
||||
lm = LayerMapping(Point2D, vrt_file, point_mapping, transform=False)
|
||||
@@ -177,12 +215,12 @@ class Geo3DTest(Geo3DLoadingHelper, TestCase):
|
||||
# `SELECT ST_AsText(ST_Union(point)) FROM geo3d_city3d;`
|
||||
self._load_city_data()
|
||||
ref_ewkt = (
|
||||
'SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,'
|
||||
'-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,'
|
||||
'-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)'
|
||||
"SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,"
|
||||
"-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,"
|
||||
"-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)"
|
||||
)
|
||||
ref_union = GEOSGeometry(ref_ewkt)
|
||||
union = City3D.objects.aggregate(Union('point'))['point__union']
|
||||
union = City3D.objects.aggregate(Union("point"))["point__union"]
|
||||
self.assertTrue(union.hasz)
|
||||
# Ordering of points in the resulting geometry may vary between implementations
|
||||
self.assertEqual({p.ewkt for p in ref_union}, {p.ewkt for p in union})
|
||||
@@ -195,14 +233,16 @@ class Geo3DTest(Geo3DLoadingHelper, TestCase):
|
||||
self._load_city_data()
|
||||
# `SELECT ST_Extent3D(point) FROM geo3d_city3d;`
|
||||
ref_extent3d = (-123.305196, -41.315268, 14, 174.783117, 48.462611, 1433)
|
||||
extent = City3D.objects.aggregate(Extent3D('point'))['point__extent3d']
|
||||
extent = City3D.objects.aggregate(Extent3D("point"))["point__extent3d"]
|
||||
|
||||
def check_extent3d(extent3d, tol=6):
|
||||
for ref_val, ext_val in zip(ref_extent3d, extent3d):
|
||||
self.assertAlmostEqual(ref_val, ext_val, tol)
|
||||
|
||||
check_extent3d(extent)
|
||||
self.assertIsNone(City3D.objects.none().aggregate(Extent3D('point'))['point__extent3d'])
|
||||
self.assertIsNone(
|
||||
City3D.objects.none().aggregate(Extent3D("point"))["point__extent3d"]
|
||||
)
|
||||
|
||||
|
||||
@skipUnlessDBFeature("supports_3d_functions")
|
||||
@@ -212,10 +252,12 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
Test KML() function with Z values.
|
||||
"""
|
||||
self._load_city_data()
|
||||
h = City3D.objects.annotate(kml=AsKML('point', precision=6)).get(name='Houston')
|
||||
h = City3D.objects.annotate(kml=AsKML("point", precision=6)).get(name="Houston")
|
||||
# KML should be 3D.
|
||||
# `SELECT ST_AsKML(point, 6) FROM geo3d_city3d WHERE name = 'Houston';`
|
||||
ref_kml_regex = re.compile(r'^<Point><coordinates>-95.363\d+,29.763\d+,18</coordinates></Point>$')
|
||||
ref_kml_regex = re.compile(
|
||||
r"^<Point><coordinates>-95.363\d+,29.763\d+,18</coordinates></Point>$"
|
||||
)
|
||||
self.assertTrue(ref_kml_regex.match(h.kml))
|
||||
|
||||
def test_geojson(self):
|
||||
@@ -223,10 +265,14 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
Test GeoJSON() function with Z values.
|
||||
"""
|
||||
self._load_city_data()
|
||||
h = City3D.objects.annotate(geojson=AsGeoJSON('point', precision=6)).get(name='Houston')
|
||||
h = City3D.objects.annotate(geojson=AsGeoJSON("point", precision=6)).get(
|
||||
name="Houston"
|
||||
)
|
||||
# GeoJSON should be 3D
|
||||
# `SELECT ST_AsGeoJSON(point, 6) FROM geo3d_city3d WHERE name='Houston';`
|
||||
ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
|
||||
ref_json_regex = re.compile(
|
||||
r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$'
|
||||
)
|
||||
self.assertTrue(ref_json_regex.match(h.geojson))
|
||||
|
||||
def test_perimeter(self):
|
||||
@@ -239,9 +285,13 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
ref_perim_3d = 76859.2620451
|
||||
ref_perim_2d = 76859.2577803
|
||||
tol = 6
|
||||
poly2d = Polygon2D.objects.annotate(perimeter=Perimeter('poly')).get(name='2D BBox')
|
||||
poly2d = Polygon2D.objects.annotate(perimeter=Perimeter("poly")).get(
|
||||
name="2D BBox"
|
||||
)
|
||||
self.assertAlmostEqual(ref_perim_2d, poly2d.perimeter.m, tol)
|
||||
poly3d = Polygon3D.objects.annotate(perimeter=Perimeter('poly')).get(name='3D BBox')
|
||||
poly3d = Polygon3D.objects.annotate(perimeter=Perimeter("poly")).get(
|
||||
name="3D BBox"
|
||||
)
|
||||
self.assertAlmostEqual(ref_perim_3d, poly3d.perimeter.m, tol)
|
||||
|
||||
def test_length(self):
|
||||
@@ -256,9 +306,9 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
tol = 3
|
||||
ref_length_2d = 4368.1721949481
|
||||
ref_length_3d = 4368.62547052088
|
||||
inter2d = Interstate2D.objects.annotate(length=Length('line')).get(name='I-45')
|
||||
inter2d = Interstate2D.objects.annotate(length=Length("line")).get(name="I-45")
|
||||
self.assertAlmostEqual(ref_length_2d, inter2d.length.m, tol)
|
||||
inter3d = Interstate3D.objects.annotate(length=Length('line')).get(name='I-45')
|
||||
inter3d = Interstate3D.objects.annotate(length=Length("line")).get(name="I-45")
|
||||
self.assertAlmostEqual(ref_length_3d, inter3d.length.m, tol)
|
||||
|
||||
# Making sure `ST_Length3D` is used on for a projected
|
||||
@@ -267,9 +317,13 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
ref_length_2d = 4367.71564892392
|
||||
# `SELECT ST_Length3D(line) FROM geo3d_interstateproj3d;`
|
||||
ref_length_3d = 4368.16897234101
|
||||
inter2d = InterstateProj2D.objects.annotate(length=Length('line')).get(name='I-45')
|
||||
inter2d = InterstateProj2D.objects.annotate(length=Length("line")).get(
|
||||
name="I-45"
|
||||
)
|
||||
self.assertAlmostEqual(ref_length_2d, inter2d.length.m, tol)
|
||||
inter3d = InterstateProj3D.objects.annotate(length=Length('line')).get(name='I-45')
|
||||
inter3d = InterstateProj3D.objects.annotate(length=Length("line")).get(
|
||||
name="I-45"
|
||||
)
|
||||
self.assertAlmostEqual(ref_length_3d, inter3d.length.m, tol)
|
||||
|
||||
def test_scale(self):
|
||||
@@ -280,7 +334,7 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
# Mapping of City name to reference Z values.
|
||||
zscales = (-3, 4, 23)
|
||||
for zscale in zscales:
|
||||
for city in City3D.objects.annotate(scale=Scale('point', 1.0, 1.0, zscale)):
|
||||
for city in City3D.objects.annotate(scale=Scale("point", 1.0, 1.0, zscale)):
|
||||
self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
|
||||
|
||||
def test_translate(self):
|
||||
@@ -290,5 +344,7 @@ class Geo3DFunctionsTests(FuncTestMixin, Geo3DLoadingHelper, TestCase):
|
||||
self._load_city_data()
|
||||
ztranslations = (5.23, 23, -17)
|
||||
for ztrans in ztranslations:
|
||||
for city in City3D.objects.annotate(translate=Translate('point', 0, 0, ztrans)):
|
||||
for city in City3D.objects.annotate(
|
||||
translate=Translate("point", 0, 0, ztrans)
|
||||
):
|
||||
self.assertEqual(city_dict[city.name][2] + ztrans, city.translate.z)
|
||||
|
||||
@@ -8,7 +8,7 @@ class City(models.Model):
|
||||
point = models.PointField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoadmin'
|
||||
app_label = "geoadmin"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -16,18 +16,18 @@ class City(models.Model):
|
||||
|
||||
class CityAdminCustomWidgetKwargs(admin.GISModelAdmin):
|
||||
gis_widget_kwargs = {
|
||||
'attrs': {
|
||||
'default_lat': 55,
|
||||
'default_lon': 37,
|
||||
"attrs": {
|
||||
"default_lat": 55,
|
||||
"default_lon": 37,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
site = admin.AdminSite(name='gis_admin_modeladmin')
|
||||
site = admin.AdminSite(name="gis_admin_modeladmin")
|
||||
site.register(City, admin.ModelAdmin)
|
||||
|
||||
site_gis = admin.AdminSite(name='gis_admin_gismodeladmin')
|
||||
site_gis = admin.AdminSite(name="gis_admin_gismodeladmin")
|
||||
site_gis.register(City, admin.GISModelAdmin)
|
||||
|
||||
site_gis_custom = admin.AdminSite(name='gis_admin_gismodeladmin')
|
||||
site_gis_custom = admin.AdminSite(name="gis_admin_gismodeladmin")
|
||||
site_gis_custom.register(City, CityAdminCustomWidgetKwargs)
|
||||
|
||||
@@ -4,16 +4,16 @@ from django.test import SimpleTestCase, override_settings
|
||||
from .models import City, site, site_gis, site_gis_custom
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='django.contrib.gis.tests.geoadmin.urls')
|
||||
@override_settings(ROOT_URLCONF="django.contrib.gis.tests.geoadmin.urls")
|
||||
class GeoAdminTest(SimpleTestCase):
|
||||
admin_site = site # ModelAdmin
|
||||
|
||||
def test_widget_empty_string(self):
|
||||
geoadmin = self.admin_site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)({'point': ''})
|
||||
with self.assertRaisesMessage(AssertionError, 'no logs'):
|
||||
with self.assertLogs('django.contrib.gis', 'ERROR'):
|
||||
output = str(form['point'])
|
||||
form = geoadmin.get_changelist_form(None)({"point": ""})
|
||||
with self.assertRaisesMessage(AssertionError, "no logs"):
|
||||
with self.assertLogs("django.contrib.gis", "ERROR"):
|
||||
output = str(form["point"])
|
||||
self.assertInHTML(
|
||||
'<textarea id="id_point" class="vSerializedField required" cols="150"'
|
||||
' rows="10" name="point"></textarea>',
|
||||
@@ -22,9 +22,9 @@ class GeoAdminTest(SimpleTestCase):
|
||||
|
||||
def test_widget_invalid_string(self):
|
||||
geoadmin = self.admin_site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)({'point': 'INVALID()'})
|
||||
with self.assertLogs('django.contrib.gis', 'ERROR') as cm:
|
||||
output = str(form['point'])
|
||||
form = geoadmin.get_changelist_form(None)({"point": "INVALID()"})
|
||||
with self.assertLogs("django.contrib.gis", "ERROR") as cm:
|
||||
output = str(form["point"])
|
||||
self.assertInHTML(
|
||||
'<textarea id="id_point" class="vSerializedField required" cols="150"'
|
||||
' rows="10" name="point"></textarea>',
|
||||
@@ -40,16 +40,16 @@ class GeoAdminTest(SimpleTestCase):
|
||||
def test_widget_has_changed(self):
|
||||
geoadmin = self.admin_site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)()
|
||||
has_changed = form.fields['point'].has_changed
|
||||
has_changed = form.fields["point"].has_changed
|
||||
|
||||
initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326)
|
||||
data_same = 'SRID=3857;POINT(1493879.2754093995 6894592.019687599)'
|
||||
data_almost_same = 'SRID=3857;POINT(1493879.2754093990 6894592.019687590)'
|
||||
data_changed = 'SRID=3857;POINT(1493884.0527237 6894593.8111804)'
|
||||
data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)"
|
||||
data_almost_same = "SRID=3857;POINT(1493879.2754093990 6894592.019687590)"
|
||||
data_changed = "SRID=3857;POINT(1493884.0527237 6894593.8111804)"
|
||||
|
||||
self.assertIs(has_changed(None, data_changed), True)
|
||||
self.assertIs(has_changed(initial, ''), True)
|
||||
self.assertIs(has_changed(None, ''), False)
|
||||
self.assertIs(has_changed(initial, ""), True)
|
||||
self.assertIs(has_changed(None, ""), False)
|
||||
self.assertIs(has_changed(initial, data_same), False)
|
||||
self.assertIs(has_changed(initial, data_almost_same), False)
|
||||
self.assertIs(has_changed(initial, data_changed), True)
|
||||
@@ -61,15 +61,15 @@ class GISAdminTests(GeoAdminTest):
|
||||
def test_default_gis_widget_kwargs(self):
|
||||
geoadmin = self.admin_site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)()
|
||||
widget = form['point'].field.widget
|
||||
self.assertEqual(widget.attrs['default_lat'], 47)
|
||||
self.assertEqual(widget.attrs['default_lon'], 5)
|
||||
self.assertEqual(widget.attrs['default_zoom'], 12)
|
||||
widget = form["point"].field.widget
|
||||
self.assertEqual(widget.attrs["default_lat"], 47)
|
||||
self.assertEqual(widget.attrs["default_lon"], 5)
|
||||
self.assertEqual(widget.attrs["default_zoom"], 12)
|
||||
|
||||
def test_custom_gis_widget_kwargs(self):
|
||||
geoadmin = site_gis_custom._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)()
|
||||
widget = form['point'].field.widget
|
||||
self.assertEqual(widget.attrs['default_lat'], 55)
|
||||
self.assertEqual(widget.attrs['default_lon'], 37)
|
||||
self.assertEqual(widget.attrs['default_zoom'], 12)
|
||||
widget = form["point"].field.widget
|
||||
self.assertEqual(widget.attrs["default_lat"], 55)
|
||||
self.assertEqual(widget.attrs["default_lon"], 37)
|
||||
self.assertEqual(widget.attrs["default_zoom"], 12)
|
||||
|
||||
@@ -2,5 +2,5 @@ from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', include(admin.site.urls)),
|
||||
path("admin/", include(admin.site.urls)),
|
||||
]
|
||||
|
||||
@@ -10,12 +10,12 @@ class City(models.Model):
|
||||
point = models.PointField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoadmini_deprecated'
|
||||
app_label = "geoadmini_deprecated"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
site = admin.AdminSite(name='admin_gis')
|
||||
site = admin.AdminSite(name="admin_gis")
|
||||
with ignore_warnings(category=RemovedInDjango50Warning):
|
||||
site.register(City, admin.OSMGeoAdmin)
|
||||
|
||||
@@ -8,9 +8,8 @@ from .models import City, site
|
||||
|
||||
|
||||
@ignore_warnings(category=RemovedInDjango50Warning)
|
||||
@override_settings(ROOT_URLCONF='django.contrib.gis.tests.geoadmin.urls')
|
||||
@override_settings(ROOT_URLCONF="django.contrib.gis.tests.geoadmin.urls")
|
||||
class GeoAdminTest(SimpleTestCase):
|
||||
|
||||
def test_ensure_geographic_media(self):
|
||||
geoadmin = site._registry[City]
|
||||
admin_js = geoadmin.media.render_js()
|
||||
@@ -20,12 +19,14 @@ class GeoAdminTest(SimpleTestCase):
|
||||
delete_all_btn = """<a href="javascript:geodjango_point.clearFeatures()">Delete all Features</a>"""
|
||||
|
||||
original_geoadmin = site._registry[City]
|
||||
params = original_geoadmin.get_map_widget(City._meta.get_field('point')).params
|
||||
result = original_geoadmin.get_map_widget(City._meta.get_field('point'))(
|
||||
).render('point', Point(-79.460734, 40.18476), params)
|
||||
params = original_geoadmin.get_map_widget(City._meta.get_field("point")).params
|
||||
result = original_geoadmin.get_map_widget(
|
||||
City._meta.get_field("point")
|
||||
)().render("point", Point(-79.460734, 40.18476), params)
|
||||
self.assertIn(
|
||||
"""geodjango_point.layers.base = new OpenLayers.Layer.OSM("OpenStreetMap (Mapnik)");""",
|
||||
result)
|
||||
result,
|
||||
)
|
||||
|
||||
self.assertIn(delete_all_btn, result)
|
||||
|
||||
@@ -33,9 +34,10 @@ class GeoAdminTest(SimpleTestCase):
|
||||
site.register(City, UnmodifiableAdmin)
|
||||
try:
|
||||
geoadmin = site._registry[City]
|
||||
params = geoadmin.get_map_widget(City._meta.get_field('point')).params
|
||||
result = geoadmin.get_map_widget(City._meta.get_field('point'))(
|
||||
).render('point', Point(-79.460734, 40.18476), params)
|
||||
params = geoadmin.get_map_widget(City._meta.get_field("point")).params
|
||||
result = geoadmin.get_map_widget(City._meta.get_field("point"))().render(
|
||||
"point", Point(-79.460734, 40.18476), params
|
||||
)
|
||||
|
||||
self.assertNotIn(delete_all_btn, result)
|
||||
finally:
|
||||
@@ -44,12 +46,14 @@ class GeoAdminTest(SimpleTestCase):
|
||||
|
||||
def test_olmap_WMS_rendering(self):
|
||||
geoadmin = admin.GeoModelAdmin(City, site)
|
||||
result = geoadmin.get_map_widget(City._meta.get_field('point'))(
|
||||
).render('point', Point(-79.460734, 40.18476))
|
||||
result = geoadmin.get_map_widget(City._meta.get_field("point"))().render(
|
||||
"point", Point(-79.460734, 40.18476)
|
||||
)
|
||||
self.assertIn(
|
||||
"""geodjango_point.layers.base = new OpenLayers.Layer.WMS("OpenLayers WMS", """
|
||||
""""http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic', format: 'image/jpeg'});""",
|
||||
result)
|
||||
result,
|
||||
)
|
||||
|
||||
def test_olwidget_has_changed(self):
|
||||
"""
|
||||
@@ -57,7 +61,7 @@ class GeoAdminTest(SimpleTestCase):
|
||||
"""
|
||||
geoadmin = site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)()
|
||||
has_changed = form.fields['point'].has_changed
|
||||
has_changed = form.fields["point"].has_changed
|
||||
|
||||
initial = Point(13.4197458572965953, 52.5194108501149799, srid=4326)
|
||||
data_same = "SRID=3857;POINT(1493879.2754093995 6894592.019687599)"
|
||||
@@ -73,30 +77,30 @@ class GeoAdminTest(SimpleTestCase):
|
||||
|
||||
def test_olwidget_empty_string(self):
|
||||
geoadmin = site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)({'point': ''})
|
||||
with self.assertNoLogs('django.contrib.gis', 'ERROR'):
|
||||
output = str(form['point'])
|
||||
form = geoadmin.get_changelist_form(None)({"point": ""})
|
||||
with self.assertNoLogs("django.contrib.gis", "ERROR"):
|
||||
output = str(form["point"])
|
||||
self.assertInHTML(
|
||||
'<textarea id="id_point" class="vWKTField required" cols="150"'
|
||||
' rows="10" name="point"></textarea>',
|
||||
output
|
||||
output,
|
||||
)
|
||||
|
||||
def test_olwidget_invalid_string(self):
|
||||
geoadmin = site._registry[City]
|
||||
form = geoadmin.get_changelist_form(None)({'point': 'INVALID()'})
|
||||
with self.assertLogs('django.contrib.gis', 'ERROR') as cm:
|
||||
output = str(form['point'])
|
||||
form = geoadmin.get_changelist_form(None)({"point": "INVALID()"})
|
||||
with self.assertLogs("django.contrib.gis", "ERROR") as cm:
|
||||
output = str(form["point"])
|
||||
self.assertInHTML(
|
||||
'<textarea id="id_point" class="vWKTField required" cols="150"'
|
||||
' rows="10" name="point"></textarea>',
|
||||
output
|
||||
output,
|
||||
)
|
||||
self.assertEqual(len(cm.records), 1)
|
||||
self.assertEqual(
|
||||
cm.records[0].getMessage(),
|
||||
"Error creating geometry from value 'INVALID()' (String input "
|
||||
"unrecognized as WKT EWKT, and HEXEWKB.)"
|
||||
"unrecognized as WKT EWKT, and HEXEWKB.)",
|
||||
)
|
||||
|
||||
|
||||
@@ -109,9 +113,9 @@ class DeprecationTests(SimpleTestCase):
|
||||
pass
|
||||
|
||||
msg = (
|
||||
'django.contrib.gis.admin.GeoModelAdmin and OSMGeoAdmin are '
|
||||
'deprecated in favor of django.contrib.admin.ModelAdmin and '
|
||||
'django.contrib.gis.admin.GISModelAdmin.'
|
||||
"django.contrib.gis.admin.GeoModelAdmin and OSMGeoAdmin are "
|
||||
"deprecated in favor of django.contrib.admin.ModelAdmin and "
|
||||
"django.contrib.gis.admin.GISModelAdmin."
|
||||
)
|
||||
with self.assertRaisesMessage(RemovedInDjango50Warning, msg):
|
||||
DeprecatedOSMGeoAdmin(City, site)
|
||||
|
||||
@@ -2,5 +2,5 @@ from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', include(admin.site.urls)),
|
||||
path("admin/", include(admin.site.urls)),
|
||||
]
|
||||
|
||||
@@ -4,14 +4,14 @@ from .models import City
|
||||
|
||||
|
||||
class TestGeoRSS1(feeds.Feed):
|
||||
link = '/city/'
|
||||
title = 'Test GeoDjango Cities'
|
||||
link = "/city/"
|
||||
title = "Test GeoDjango Cities"
|
||||
|
||||
def items(self):
|
||||
return City.objects.all()
|
||||
|
||||
def item_link(self, item):
|
||||
return '/city/%s/' % item.pk
|
||||
return "/city/%s/" % item.pk
|
||||
|
||||
def item_geometry(self, item):
|
||||
return item.point
|
||||
@@ -56,16 +56,17 @@ class TestW3CGeo3(TestGeoRSS1):
|
||||
|
||||
def item_geometry(self, item):
|
||||
from django.contrib.gis.geos import Polygon
|
||||
|
||||
return Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
|
||||
|
||||
|
||||
# The feed dictionary to use for URLs.
|
||||
feed_dict = {
|
||||
'rss1': TestGeoRSS1,
|
||||
'rss2': TestGeoRSS2,
|
||||
'atom1': TestGeoAtom1,
|
||||
'atom2': TestGeoAtom2,
|
||||
'w3cgeo1': TestW3CGeo1,
|
||||
'w3cgeo2': TestW3CGeo2,
|
||||
'w3cgeo3': TestW3CGeo3,
|
||||
"rss1": TestGeoRSS1,
|
||||
"rss2": TestGeoRSS2,
|
||||
"atom1": TestGeoAtom1,
|
||||
"atom2": TestGeoAtom2,
|
||||
"w3cgeo1": TestW3CGeo1,
|
||||
"w3cgeo2": TestW3CGeo2,
|
||||
"w3cgeo3": TestW3CGeo3,
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class City(NamedModel):
|
||||
point = models.PointField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoapp'
|
||||
app_label = "geoapp"
|
||||
|
||||
|
||||
# This is an inherited model from City
|
||||
@@ -34,14 +34,16 @@ class PennsylvaniaCity(City):
|
||||
founded = models.DateTimeField(null=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoapp'
|
||||
app_label = "geoapp"
|
||||
|
||||
|
||||
class State(NamedModel):
|
||||
poly = models.PolygonField(null=gisfield_may_be_null) # Allowing NULL geometries here.
|
||||
poly = models.PolygonField(
|
||||
null=gisfield_may_be_null
|
||||
) # Allowing NULL geometries here.
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoapp'
|
||||
app_label = "geoapp"
|
||||
|
||||
|
||||
class Track(NamedModel):
|
||||
@@ -59,8 +61,8 @@ class UniqueTogetherModel(models.Model):
|
||||
point = models.PointField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('city', 'point')
|
||||
required_db_features = ['supports_geometry_field_unique_index']
|
||||
unique_together = ("city", "point")
|
||||
required_db_features = ["supports_geometry_field_unique_index"]
|
||||
|
||||
|
||||
class Truth(models.Model):
|
||||
@@ -76,7 +78,6 @@ class MinusOneSRID(models.Model):
|
||||
|
||||
|
||||
class NonConcreteField(models.IntegerField):
|
||||
|
||||
def db_type(self, connection):
|
||||
return None
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@ from django.contrib.gis.sitemaps import KMLSitemap, KMZSitemap
|
||||
from .models import City, Country
|
||||
|
||||
sitemaps = {
|
||||
'kml': KMLSitemap([City, Country]),
|
||||
'kmz': KMZSitemap([City, Country]),
|
||||
"kml": KMLSitemap([City, Country]),
|
||||
"kmz": KMZSitemap([City, Country]),
|
||||
}
|
||||
|
||||
@@ -8,24 +8,30 @@ from .models import City, ManyPointModel, MultiFields
|
||||
|
||||
|
||||
class GeoExpressionsTests(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test_geometry_value_annotation(self):
|
||||
p = Point(1, 1, srid=4326)
|
||||
point = City.objects.annotate(p=Value(p, GeometryField(srid=4326))).first().p
|
||||
self.assertEqual(point, p)
|
||||
|
||||
@skipUnlessDBFeature('supports_transform')
|
||||
@skipUnlessDBFeature("supports_transform")
|
||||
def test_geometry_value_annotation_different_srid(self):
|
||||
p = Point(1, 1, srid=32140)
|
||||
point = City.objects.annotate(p=Value(p, GeometryField(srid=4326))).first().p
|
||||
self.assertTrue(point.equals_exact(p.transform(4326, clone=True), 10 ** -5))
|
||||
self.assertTrue(point.equals_exact(p.transform(4326, clone=True), 10**-5))
|
||||
self.assertEqual(point.srid, 4326)
|
||||
|
||||
@skipUnlessDBFeature('supports_geography')
|
||||
@skipUnlessDBFeature("supports_geography")
|
||||
def test_geography_value(self):
|
||||
p = Polygon(((1, 1), (1, 2), (2, 2), (2, 1), (1, 1)))
|
||||
area = City.objects.annotate(a=functions.Area(Value(p, GeometryField(srid=4326, geography=True)))).first().a
|
||||
area = (
|
||||
City.objects.annotate(
|
||||
a=functions.Area(Value(p, GeometryField(srid=4326, geography=True)))
|
||||
)
|
||||
.first()
|
||||
.a
|
||||
)
|
||||
self.assertAlmostEqual(area.sq_km, 12305.1, 0)
|
||||
|
||||
def test_update_from_other_field(self):
|
||||
@@ -37,29 +43,37 @@ class GeoExpressionsTests(TestCase):
|
||||
point3=p2.transform(3857, clone=True),
|
||||
)
|
||||
# Updating a point to a point of the same SRID.
|
||||
ManyPointModel.objects.filter(pk=obj.pk).update(point2=F('point1'))
|
||||
ManyPointModel.objects.filter(pk=obj.pk).update(point2=F("point1"))
|
||||
obj.refresh_from_db()
|
||||
self.assertEqual(obj.point2, p1)
|
||||
# Updating a point to a point with a different SRID.
|
||||
if connection.features.supports_transform:
|
||||
ManyPointModel.objects.filter(pk=obj.pk).update(point3=F('point1'))
|
||||
ManyPointModel.objects.filter(pk=obj.pk).update(point3=F("point1"))
|
||||
obj.refresh_from_db()
|
||||
self.assertTrue(obj.point3.equals_exact(p1.transform(3857, clone=True), 0.1))
|
||||
self.assertTrue(
|
||||
obj.point3.equals_exact(p1.transform(3857, clone=True), 0.1)
|
||||
)
|
||||
|
||||
def test_multiple_annotation(self):
|
||||
multi_field = MultiFields.objects.create(
|
||||
point=Point(1, 1),
|
||||
city=City.objects.get(name='Houston'),
|
||||
city=City.objects.get(name="Houston"),
|
||||
poly=Polygon(((1, 1), (1, 2), (2, 2), (2, 1), (1, 1))),
|
||||
)
|
||||
qs = City.objects.values('name').annotate(
|
||||
distance=Min(functions.Distance('multifields__point', multi_field.city.point)),
|
||||
).annotate(count=Count('multifields'))
|
||||
qs = (
|
||||
City.objects.values("name")
|
||||
.annotate(
|
||||
distance=Min(
|
||||
functions.Distance("multifields__point", multi_field.city.point)
|
||||
),
|
||||
)
|
||||
.annotate(count=Count("multifields"))
|
||||
)
|
||||
self.assertTrue(qs.first())
|
||||
|
||||
@skipUnlessDBFeature('has_Translate_function')
|
||||
@skipUnlessDBFeature("has_Translate_function")
|
||||
def test_update_with_expression(self):
|
||||
city = City.objects.create(point=Point(1, 1, srid=4326))
|
||||
City.objects.filter(pk=city.pk).update(point=functions.Translate('point', 1, 1))
|
||||
City.objects.filter(pk=city.pk).update(point=functions.Translate("point", 1, 1))
|
||||
city.refresh_from_db()
|
||||
self.assertEqual(city.point, Point(2, 2, srid=4326))
|
||||
|
||||
@@ -7,10 +7,10 @@ from django.test import TestCase, modify_settings, override_settings
|
||||
from .models import City
|
||||
|
||||
|
||||
@modify_settings(INSTALLED_APPS={'append': 'django.contrib.sites'})
|
||||
@override_settings(ROOT_URLCONF='gis_tests.geoapp.urls')
|
||||
@modify_settings(INSTALLED_APPS={"append": "django.contrib.sites"})
|
||||
@override_settings(ROOT_URLCONF="gis_tests.geoapp.urls")
|
||||
class GeoFeedTest(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -25,64 +25,87 @@ class GeoFeedTest(TestCase):
|
||||
def test_geofeed_rss(self):
|
||||
"Tests geographic feeds using GeoRSS over RSSv2."
|
||||
# Uses `GEOSGeometry` in `item_geometry`
|
||||
doc1 = minidom.parseString(self.client.get('/feeds/rss1/').content)
|
||||
doc1 = minidom.parseString(self.client.get("/feeds/rss1/").content)
|
||||
# Uses a 2-tuple in `item_geometry`
|
||||
doc2 = minidom.parseString(self.client.get('/feeds/rss2/').content)
|
||||
doc2 = minidom.parseString(self.client.get("/feeds/rss2/").content)
|
||||
feed1, feed2 = doc1.firstChild, doc2.firstChild
|
||||
|
||||
# Making sure the box got added to the second GeoRSS feed.
|
||||
self.assertChildNodes(feed2.getElementsByTagName('channel')[0],
|
||||
['title', 'link', 'description', 'language',
|
||||
'lastBuildDate', 'item', 'georss:box', 'atom:link']
|
||||
)
|
||||
self.assertChildNodes(
|
||||
feed2.getElementsByTagName("channel")[0],
|
||||
[
|
||||
"title",
|
||||
"link",
|
||||
"description",
|
||||
"language",
|
||||
"lastBuildDate",
|
||||
"item",
|
||||
"georss:box",
|
||||
"atom:link",
|
||||
],
|
||||
)
|
||||
|
||||
# Incrementing through the feeds.
|
||||
for feed in [feed1, feed2]:
|
||||
# Ensuring the georss namespace was added to the <rss> element.
|
||||
self.assertEqual(feed.getAttribute('xmlns:georss'), 'http://www.georss.org/georss')
|
||||
chan = feed.getElementsByTagName('channel')[0]
|
||||
items = chan.getElementsByTagName('item')
|
||||
self.assertEqual(
|
||||
feed.getAttribute("xmlns:georss"), "http://www.georss.org/georss"
|
||||
)
|
||||
chan = feed.getElementsByTagName("channel")[0]
|
||||
items = chan.getElementsByTagName("item")
|
||||
self.assertEqual(len(items), City.objects.count())
|
||||
|
||||
# Ensuring the georss element was added to each item in the feed.
|
||||
for item in items:
|
||||
self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'georss:point'])
|
||||
self.assertChildNodes(
|
||||
item, ["title", "link", "description", "guid", "georss:point"]
|
||||
)
|
||||
|
||||
def test_geofeed_atom(self):
|
||||
"Testing geographic feeds using GeoRSS over Atom."
|
||||
doc1 = minidom.parseString(self.client.get('/feeds/atom1/').content)
|
||||
doc2 = minidom.parseString(self.client.get('/feeds/atom2/').content)
|
||||
doc1 = minidom.parseString(self.client.get("/feeds/atom1/").content)
|
||||
doc2 = minidom.parseString(self.client.get("/feeds/atom2/").content)
|
||||
feed1, feed2 = doc1.firstChild, doc2.firstChild
|
||||
|
||||
# Making sure the box got added to the second GeoRSS feed.
|
||||
self.assertChildNodes(feed2, ['title', 'link', 'id', 'updated', 'entry', 'georss:box'])
|
||||
self.assertChildNodes(
|
||||
feed2, ["title", "link", "id", "updated", "entry", "georss:box"]
|
||||
)
|
||||
|
||||
for feed in [feed1, feed2]:
|
||||
# Ensuring the georsss namespace was added to the <feed> element.
|
||||
self.assertEqual(feed.getAttribute('xmlns:georss'), 'http://www.georss.org/georss')
|
||||
entries = feed.getElementsByTagName('entry')
|
||||
self.assertEqual(
|
||||
feed.getAttribute("xmlns:georss"), "http://www.georss.org/georss"
|
||||
)
|
||||
entries = feed.getElementsByTagName("entry")
|
||||
self.assertEqual(len(entries), City.objects.count())
|
||||
|
||||
# Ensuring the georss element was added to each entry in the feed.
|
||||
for entry in entries:
|
||||
self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'georss:point'])
|
||||
self.assertChildNodes(
|
||||
entry, ["title", "link", "id", "summary", "georss:point"]
|
||||
)
|
||||
|
||||
def test_geofeed_w3c(self):
|
||||
"Testing geographic feeds using W3C Geo."
|
||||
doc = minidom.parseString(self.client.get('/feeds/w3cgeo1/').content)
|
||||
doc = minidom.parseString(self.client.get("/feeds/w3cgeo1/").content)
|
||||
feed = doc.firstChild
|
||||
# Ensuring the geo namespace was added to the <feed> element.
|
||||
self.assertEqual(feed.getAttribute('xmlns:geo'), 'http://www.w3.org/2003/01/geo/wgs84_pos#')
|
||||
chan = feed.getElementsByTagName('channel')[0]
|
||||
items = chan.getElementsByTagName('item')
|
||||
self.assertEqual(
|
||||
feed.getAttribute("xmlns:geo"), "http://www.w3.org/2003/01/geo/wgs84_pos#"
|
||||
)
|
||||
chan = feed.getElementsByTagName("channel")[0]
|
||||
items = chan.getElementsByTagName("item")
|
||||
self.assertEqual(len(items), City.objects.count())
|
||||
|
||||
# Ensuring the geo:lat and geo:lon element was added to each item in the feed.
|
||||
for item in items:
|
||||
self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'geo:lat', 'geo:lon'])
|
||||
self.assertChildNodes(
|
||||
item, ["title", "link", "description", "guid", "geo:lat", "geo:lon"]
|
||||
)
|
||||
|
||||
# Boxes and Polygons aren't allowed in W3C Geo feeds.
|
||||
with self.assertRaises(ValueError): # Box in <channel>
|
||||
self.client.get('/feeds/w3cgeo2/')
|
||||
self.client.get("/feeds/w3cgeo2/")
|
||||
with self.assertRaises(ValueError): # Polygons in <entry>
|
||||
self.client.get('/feeds/w3cgeo3/')
|
||||
self.client.get("/feeds/w3cgeo3/")
|
||||
|
||||
@@ -4,9 +4,7 @@ import re
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.gis.db.models import GeometryField, PolygonField, functions
|
||||
from django.contrib.gis.geos import (
|
||||
GEOSGeometry, LineString, Point, Polygon, fromstr,
|
||||
)
|
||||
from django.contrib.gis.geos import GEOSGeometry, LineString, Point, Polygon, fromstr
|
||||
from django.contrib.gis.measure import Area
|
||||
from django.db import NotSupportedError, connection
|
||||
from django.db.models import IntegerField, Sum, Value
|
||||
@@ -23,12 +21,13 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
|
||||
Please keep the tests in function's alphabetic order.
|
||||
"""
|
||||
fixtures = ['initial']
|
||||
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test_asgeojson(self):
|
||||
if not connection.features.has_AsGeoJSON_function:
|
||||
with self.assertRaises(NotSupportedError):
|
||||
list(Country.objects.annotate(json=functions.AsGeoJSON('mpoly')))
|
||||
list(Country.objects.annotate(json=functions.AsGeoJSON("mpoly")))
|
||||
return
|
||||
|
||||
pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
|
||||
@@ -44,32 +43,36 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
'{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},'
|
||||
'"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
|
||||
)
|
||||
if 'crs' in connection.features.unsupported_geojson_options:
|
||||
del houston_json['crs']
|
||||
del chicago_json['crs']
|
||||
if 'bbox' in connection.features.unsupported_geojson_options:
|
||||
del chicago_json['bbox']
|
||||
del victoria_json['bbox']
|
||||
if 'precision' in connection.features.unsupported_geojson_options:
|
||||
chicago_json['coordinates'] = [-87.650175, 41.850385]
|
||||
if "crs" in connection.features.unsupported_geojson_options:
|
||||
del houston_json["crs"]
|
||||
del chicago_json["crs"]
|
||||
if "bbox" in connection.features.unsupported_geojson_options:
|
||||
del chicago_json["bbox"]
|
||||
del victoria_json["bbox"]
|
||||
if "precision" in connection.features.unsupported_geojson_options:
|
||||
chicago_json["coordinates"] = [-87.650175, 41.850385]
|
||||
|
||||
# Precision argument should only be an integer
|
||||
with self.assertRaises(TypeError):
|
||||
City.objects.annotate(geojson=functions.AsGeoJSON('point', precision='foo'))
|
||||
City.objects.annotate(geojson=functions.AsGeoJSON("point", precision="foo"))
|
||||
|
||||
# Reference queries and values.
|
||||
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0)
|
||||
# FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
|
||||
self.assertJSONEqual(
|
||||
pueblo_json,
|
||||
City.objects.annotate(geojson=functions.AsGeoJSON('point')).get(name='Pueblo').geojson
|
||||
City.objects.annotate(geojson=functions.AsGeoJSON("point"))
|
||||
.get(name="Pueblo")
|
||||
.geojson,
|
||||
)
|
||||
|
||||
# SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city"
|
||||
# WHERE "geoapp_city"."name" = 'Houston';
|
||||
# This time we want to include the CRS by using the `crs` keyword.
|
||||
self.assertJSONEqual(
|
||||
City.objects.annotate(json=functions.AsGeoJSON('point', crs=True)).get(name='Houston').json,
|
||||
City.objects.annotate(json=functions.AsGeoJSON("point", crs=True))
|
||||
.get(name="Houston")
|
||||
.json,
|
||||
houston_json,
|
||||
)
|
||||
|
||||
@@ -77,9 +80,9 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
# WHERE "geoapp_city"."name" = 'Houston';
|
||||
# This time we include the bounding box by using the `bbox` keyword.
|
||||
self.assertJSONEqual(
|
||||
City.objects.annotate(
|
||||
geojson=functions.AsGeoJSON('point', bbox=True)
|
||||
).get(name='Victoria').geojson,
|
||||
City.objects.annotate(geojson=functions.AsGeoJSON("point", bbox=True))
|
||||
.get(name="Victoria")
|
||||
.geojson,
|
||||
victoria_json,
|
||||
)
|
||||
|
||||
@@ -88,21 +91,29 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
# Finally, we set every available keyword.
|
||||
# MariaDB doesn't limit the number of decimals in bbox.
|
||||
if connection.ops.mariadb:
|
||||
chicago_json['bbox'] = [-87.650175, 41.850385, -87.650175, 41.850385]
|
||||
chicago_json["bbox"] = [-87.650175, 41.850385, -87.650175, 41.850385]
|
||||
try:
|
||||
self.assertJSONEqual(
|
||||
City.objects.annotate(
|
||||
geojson=functions.AsGeoJSON('point', bbox=True, crs=True, precision=5)
|
||||
).get(name='Chicago').geojson,
|
||||
geojson=functions.AsGeoJSON(
|
||||
"point", bbox=True, crs=True, precision=5
|
||||
)
|
||||
)
|
||||
.get(name="Chicago")
|
||||
.geojson,
|
||||
chicago_json,
|
||||
)
|
||||
except AssertionError:
|
||||
# Give a second chance with different coords rounding.
|
||||
chicago_json['coordinates'][1] = 41.85038
|
||||
chicago_json["coordinates"][1] = 41.85038
|
||||
self.assertJSONEqual(
|
||||
City.objects.annotate(
|
||||
geojson=functions.AsGeoJSON('point', bbox=True, crs=True, precision=5)
|
||||
).get(name='Chicago').geojson,
|
||||
geojson=functions.AsGeoJSON(
|
||||
"point", bbox=True, crs=True, precision=5
|
||||
)
|
||||
)
|
||||
.get(name="Chicago")
|
||||
.geojson,
|
||||
chicago_json,
|
||||
)
|
||||
|
||||
@@ -112,25 +123,29 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
# non-geometry field.
|
||||
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')
|
||||
qs.annotate(gml=functions.AsGML("name"))
|
||||
ptown = City.objects.annotate(gml=functions.AsGML("point", precision=9)).get(
|
||||
name="Pueblo"
|
||||
)
|
||||
|
||||
if connection.ops.oracle:
|
||||
# No precision parameter for 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>'
|
||||
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>'
|
||||
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
|
||||
City.objects.annotate(gml=functions.AsGML("point", version=3))
|
||||
.get(name="Pueblo")
|
||||
.gml,
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("has_AsKML_function")
|
||||
@@ -138,46 +153,68 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
# Should throw a TypeError when trying to obtain KML from a
|
||||
# non-geometry field.
|
||||
with self.assertRaises(TypeError):
|
||||
City.objects.annotate(kml=functions.AsKML('name'))
|
||||
City.objects.annotate(kml=functions.AsKML("name"))
|
||||
|
||||
# Ensuring the KML is as expected.
|
||||
ptown = City.objects.annotate(kml=functions.AsKML('point', precision=9)).get(name='Pueblo')
|
||||
self.assertEqual('<Point><coordinates>-104.609252,38.255001</coordinates></Point>', ptown.kml)
|
||||
ptown = City.objects.annotate(kml=functions.AsKML("point", precision=9)).get(
|
||||
name="Pueblo"
|
||||
)
|
||||
self.assertEqual(
|
||||
"<Point><coordinates>-104.609252,38.255001</coordinates></Point>", ptown.kml
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("has_AsSVG_function")
|
||||
def test_assvg(self):
|
||||
with self.assertRaises(TypeError):
|
||||
City.objects.annotate(svg=functions.AsSVG('point', precision='foo'))
|
||||
City.objects.annotate(svg=functions.AsSVG("point", precision="foo"))
|
||||
# SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
|
||||
svg1 = 'cx="-104.609252" cy="-38.255001"'
|
||||
# Even though relative, only one point so it's practically the same except for
|
||||
# the 'c' letter prefix on the x,y values.
|
||||
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)
|
||||
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_AsWKB_function')
|
||||
@skipUnlessDBFeature("has_AsWKB_function")
|
||||
def test_aswkb(self):
|
||||
wkb = City.objects.annotate(
|
||||
wkb=functions.AsWKB(Point(1, 2, srid=4326)),
|
||||
).first().wkb
|
||||
wkb = (
|
||||
City.objects.annotate(
|
||||
wkb=functions.AsWKB(Point(1, 2, srid=4326)),
|
||||
)
|
||||
.first()
|
||||
.wkb
|
||||
)
|
||||
# WKB is either XDR or NDR encoded.
|
||||
self.assertIn(
|
||||
bytes(wkb),
|
||||
(
|
||||
b'\x00\x00\x00\x00\x01?\xf0\x00\x00\x00\x00\x00\x00@\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00',
|
||||
b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00'
|
||||
b'\x00\x00\x00\x00\x00@',
|
||||
b"\x00\x00\x00\x00\x01?\xf0\x00\x00\x00\x00\x00\x00@\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00",
|
||||
b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0?\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00@",
|
||||
),
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('has_AsWKT_function')
|
||||
@skipUnlessDBFeature("has_AsWKT_function")
|
||||
def test_aswkt(self):
|
||||
wkt = City.objects.annotate(
|
||||
wkt=functions.AsWKT(Point(1, 2, srid=4326)),
|
||||
).first().wkt
|
||||
self.assertEqual(wkt, 'POINT (1.0 2.0)' if connection.ops.oracle else 'POINT(1 2)')
|
||||
wkt = (
|
||||
City.objects.annotate(
|
||||
wkt=functions.AsWKT(Point(1, 2, srid=4326)),
|
||||
)
|
||||
.first()
|
||||
.wkt
|
||||
)
|
||||
self.assertEqual(
|
||||
wkt, "POINT (1.0 2.0)" if connection.ops.oracle else "POINT(1 2)"
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("has_Azimuth_function")
|
||||
def test_azimuth(self):
|
||||
@@ -199,7 +236,9 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
return (4 * num_seg) + 1
|
||||
|
||||
expected_areas = (169, 136) if connection.ops.postgis else (171, 126)
|
||||
qs = Country.objects.annotate(circle=functions.BoundingCircle('mpoly')).order_by('name')
|
||||
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 connection.ops.postgis:
|
||||
@@ -211,8 +250,8 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
for num_seq in tests:
|
||||
with self.subTest(num_seq=num_seq):
|
||||
qs = Country.objects.annotate(
|
||||
circle=functions.BoundingCircle('mpoly', num_seg=num_seq),
|
||||
).order_by('name')
|
||||
circle=functions.BoundingCircle("mpoly", num_seg=num_seq),
|
||||
).order_by("name")
|
||||
if connection.ops.postgis:
|
||||
self.assertGreater(qs[0].circle.area, 168.4, 0)
|
||||
self.assertLess(qs[0].circle.area, 169.5, 0)
|
||||
@@ -225,21 +264,27 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
|
||||
@skipUnlessDBFeature("has_Centroid_function")
|
||||
def test_centroid(self):
|
||||
qs = State.objects.exclude(poly__isnull=True).annotate(centroid=functions.Centroid('poly'))
|
||||
tol = 1.8 if connection.ops.mysql else (0.1 if connection.ops.oracle else 0.00001)
|
||||
qs = State.objects.exclude(poly__isnull=True).annotate(
|
||||
centroid=functions.Centroid("poly")
|
||||
)
|
||||
tol = (
|
||||
1.8 if connection.ops.mysql else (0.1 if connection.ops.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'))
|
||||
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))
|
||||
qs = Country.objects.annotate(diff=functions.Difference("mpoly", geom))
|
||||
# Oracle does something screwy with the Texas geometry.
|
||||
if connection.ops.oracle:
|
||||
qs = qs.exclude(name='Texas')
|
||||
qs = qs.exclude(name="Texas")
|
||||
|
||||
for c in qs:
|
||||
self.assertTrue(c.mpoly.difference(geom).equals(c.diff))
|
||||
@@ -248,16 +293,16 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
def test_difference_mixed_srid(self):
|
||||
"""Testing with mixed SRID (Country has default 4326)."""
|
||||
geom = Point(556597.4, 2632018.6, srid=3857) # Spherical Mercator
|
||||
qs = Country.objects.annotate(difference=functions.Difference('mpoly', geom))
|
||||
qs = Country.objects.annotate(difference=functions.Difference("mpoly", geom))
|
||||
# Oracle does something screwy with the Texas geometry.
|
||||
if connection.ops.oracle:
|
||||
qs = qs.exclude(name='Texas')
|
||||
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'))
|
||||
countries = Country.objects.annotate(envelope=functions.Envelope("mpoly"))
|
||||
for country in countries:
|
||||
self.assertTrue(country.envelope.equals(country.mpoly.envelope))
|
||||
|
||||
@@ -271,8 +316,10 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
((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_polygon_cw=functions.ForcePolygonCW('poly')).get(name='Foo')
|
||||
State.objects.create(name="Foo", poly=Polygon(*rings))
|
||||
st = State.objects.annotate(
|
||||
force_polygon_cw=functions.ForcePolygonCW("poly")
|
||||
).get(name="Foo")
|
||||
self.assertEqual(rhr_rings, st.force_polygon_cw.coords)
|
||||
|
||||
@skipUnlessDBFeature("has_GeoHash_function")
|
||||
@@ -280,16 +327,22 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
# Reference query:
|
||||
# SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston';
|
||||
# SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston';
|
||||
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)])
|
||||
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_GeometryDistance_function')
|
||||
@skipUnlessDBFeature("has_GeometryDistance_function")
|
||||
def test_geometry_distance(self):
|
||||
point = Point(-90, 40, srid=4326)
|
||||
qs = City.objects.annotate(distance=functions.GeometryDistance('point', point)).order_by('distance')
|
||||
qs = City.objects.annotate(
|
||||
distance=functions.GeometryDistance("point", point)
|
||||
).order_by("distance")
|
||||
distances = (
|
||||
2.99091995527296,
|
||||
5.33507274054713,
|
||||
@@ -307,7 +360,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
@skipUnlessDBFeature("has_Intersection_function")
|
||||
def test_intersection(self):
|
||||
geom = Point(5, 23, srid=4326)
|
||||
qs = Country.objects.annotate(inter=functions.Intersection('mpoly', geom))
|
||||
qs = Country.objects.annotate(inter=functions.Intersection("mpoly", geom))
|
||||
for c in qs:
|
||||
if connection.features.empty_intersection_returns_none:
|
||||
self.assertIsNone(c.inter)
|
||||
@@ -316,12 +369,20 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
|
||||
@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()
|
||||
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)
|
||||
|
||||
@@ -329,12 +390,14 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
def test_area_with_regular_aggregate(self):
|
||||
# Create projected country objects, for this test to work on all backends.
|
||||
for c in Country.objects.all():
|
||||
CountryWebMercator.objects.create(name=c.name, mpoly=c.mpoly.transform(3857, clone=True))
|
||||
CountryWebMercator.objects.create(
|
||||
name=c.name, mpoly=c.mpoly.transform(3857, clone=True)
|
||||
)
|
||||
# Test in projected coordinate system
|
||||
qs = CountryWebMercator.objects.annotate(area_sum=Sum(functions.Area('mpoly')))
|
||||
qs = CountryWebMercator.objects.annotate(area_sum=Sum(functions.Area("mpoly")))
|
||||
# Some backends (e.g. Oracle) cannot group by multipolygon values, so
|
||||
# defer such fields in the aggregation query.
|
||||
for c in qs.defer('mpoly'):
|
||||
for c in qs.defer("mpoly"):
|
||||
result = c.area_sum
|
||||
# If the result is a measure object, get value.
|
||||
if isinstance(result, Area):
|
||||
@@ -348,43 +411,68 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
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'))
|
||||
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.'):
|
||||
with self.assertRaisesMessage(
|
||||
ValueError, "AreaField only accepts Area measurement objects."
|
||||
):
|
||||
qs.get(area__lt=500000)
|
||||
|
||||
@skipUnlessDBFeature("has_LineLocatePoint_function")
|
||||
def test_line_locate_point(self):
|
||||
pos_expr = functions.LineLocatePoint(LineString((0, 0), (0, 3), srid=4326), Point(0, 1, srid=4326))
|
||||
self.assertAlmostEqual(State.objects.annotate(pos=pos_expr).first().pos, 0.3333333)
|
||||
pos_expr = functions.LineLocatePoint(
|
||||
LineString((0, 0), (0, 3), srid=4326), Point(0, 1, srid=4326)
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
State.objects.annotate(pos=pos_expr).first().pos, 0.3333333
|
||||
)
|
||||
|
||||
@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()
|
||||
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.assertTrue(invalid.repaired.equals(fromstr('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))', srid=invalid.poly.srid)))
|
||||
self.assertTrue(
|
||||
invalid.repaired.equals(
|
||||
fromstr("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", srid=invalid.poly.srid)
|
||||
)
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('has_MakeValid_function')
|
||||
@skipUnlessDBFeature("has_MakeValid_function")
|
||||
def test_make_valid_multipolygon(self):
|
||||
invalid_geom = fromstr(
|
||||
'POLYGON((0 0, 0 1 , 1 1 , 1 0, 0 0), (10 0, 10 1, 11 1, 11 0, 10 0))'
|
||||
"POLYGON((0 0, 0 1 , 1 1 , 1 0, 0 0), (10 0, 10 1, 11 1, 11 0, 10 0))"
|
||||
)
|
||||
State.objects.create(name="invalid", poly=invalid_geom)
|
||||
invalid = (
|
||||
State.objects.filter(name="invalid")
|
||||
.annotate(
|
||||
repaired=functions.MakeValid("poly"),
|
||||
)
|
||||
.get()
|
||||
)
|
||||
State.objects.create(name='invalid', poly=invalid_geom)
|
||||
invalid = State.objects.filter(name='invalid').annotate(
|
||||
repaired=functions.MakeValid('poly'),
|
||||
).get()
|
||||
self.assertIs(invalid.repaired.valid, True)
|
||||
self.assertTrue(invalid.repaired.equals(fromstr(
|
||||
'MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), '
|
||||
'((10 0, 10 1, 11 1, 11 0, 10 0)))',
|
||||
srid=invalid.poly.srid,
|
||||
)))
|
||||
self.assertTrue(
|
||||
invalid.repaired.equals(
|
||||
fromstr(
|
||||
"MULTIPOLYGON (((0 0, 0 1, 1 1, 1 0, 0 0)), "
|
||||
"((10 0, 10 1, 11 1, 11 0, 10 0)))",
|
||||
srid=invalid.poly.srid,
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(invalid.repaired), 2)
|
||||
|
||||
@skipUnlessDBFeature('has_MakeValid_function')
|
||||
@skipUnlessDBFeature("has_MakeValid_function")
|
||||
def test_make_valid_output_field(self):
|
||||
# output_field is GeometryField instance because different geometry
|
||||
# types can be returned.
|
||||
@@ -396,17 +484,21 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
|
||||
@skipUnlessDBFeature("has_MemSize_function")
|
||||
def test_memsize(self):
|
||||
ptown = City.objects.annotate(size=functions.MemSize('point')).get(name='Pueblo')
|
||||
ptown = City.objects.annotate(size=functions.MemSize("point")).get(
|
||||
name="Pueblo"
|
||||
)
|
||||
# Exact value depends on database and version.
|
||||
self.assertTrue(20 <= ptown.size <= 105)
|
||||
|
||||
@skipUnlessDBFeature("has_NumGeom_function")
|
||||
def test_num_geom(self):
|
||||
# Both 'countries' only have two geometries.
|
||||
for c in Country.objects.annotate(num_geom=functions.NumGeometries('mpoly')):
|
||||
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'))
|
||||
qs = City.objects.filter(point__isnull=False).annotate(
|
||||
num_geom=functions.NumGeometries("point")
|
||||
)
|
||||
for city in qs:
|
||||
# The results for the number of geometries on non-collections
|
||||
# depends on the database.
|
||||
@@ -418,10 +510,10 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
@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'))
|
||||
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'))
|
||||
mpoly_qs = Country.objects.annotate(num_points=functions.NumPoints("mpoly"))
|
||||
if not connection.features.supports_num_points_poly:
|
||||
for c in mpoly_qs:
|
||||
self.assertIsNone(c.num_points)
|
||||
@@ -430,20 +522,24 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
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')):
|
||||
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):
|
||||
qs = Country.objects.annotate(point_on_surface=functions.PointOnSurface('mpoly'))
|
||||
qs = Country.objects.annotate(
|
||||
point_on_surface=functions.PointOnSurface("mpoly")
|
||||
)
|
||||
for country in qs:
|
||||
self.assertTrue(country.mpoly.intersection(country.point_on_surface))
|
||||
|
||||
@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')
|
||||
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)
|
||||
|
||||
@@ -451,7 +547,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
def test_scale(self):
|
||||
xfac, yfac = 2, 3
|
||||
tol = 5 # The low precision tolerance is for SpatiaLite
|
||||
qs = Country.objects.annotate(scaled=functions.Scale('mpoly', xfac, yfac))
|
||||
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):
|
||||
@@ -459,7 +555,9 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
|
||||
self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
|
||||
# Test float/Decimal values
|
||||
qs = Country.objects.annotate(scaled=functions.Scale('mpoly', 1.5, Decimal('2.5')))
|
||||
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")
|
||||
@@ -467,22 +565,24 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
# Let's try and break snap_to_grid() with bad combinations of arguments.
|
||||
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)))):
|
||||
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))
|
||||
Country.objects.annotate(snap=functions.SnapToGrid("mpoly", *bad_args))
|
||||
|
||||
# Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org
|
||||
# from the world borders dataset he provides.
|
||||
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))
|
||||
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))
|
||||
|
||||
# Because floating-point arithmetic isn't exact, we set a tolerance
|
||||
# to pass into GEOS `equals_exact`.
|
||||
@@ -490,60 +590,70 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
|
||||
# SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.1)) FROM "geoapp_country"
|
||||
# WHERE "geoapp_country"."name" = 'San Marino';
|
||||
ref = fromstr('MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))')
|
||||
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
|
||||
Country.objects.annotate(snap=functions.SnapToGrid("mpoly", 0.1))
|
||||
.get(name="San Marino")
|
||||
.snap,
|
||||
tol,
|
||||
)
|
||||
)
|
||||
|
||||
# SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.05, 0.23)) FROM "geoapp_country"
|
||||
# WHERE "geoapp_country"."name" = 'San Marino';
|
||||
ref = fromstr('MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))')
|
||||
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
|
||||
Country.objects.annotate(snap=functions.SnapToGrid("mpoly", 0.05, 0.23))
|
||||
.get(name="San Marino")
|
||||
.snap,
|
||||
tol,
|
||||
)
|
||||
)
|
||||
|
||||
# SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.5, 0.17, 0.05, 0.23)) FROM "geoapp_country"
|
||||
# WHERE "geoapp_country"."name" = 'San Marino';
|
||||
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)))'
|
||||
"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
|
||||
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))
|
||||
qs = Country.objects.annotate(
|
||||
sym_difference=functions.SymDifference("mpoly", geom)
|
||||
)
|
||||
# Oracle does something screwy with the Texas geometry.
|
||||
if connection.ops.oracle:
|
||||
qs = qs.exclude(name='Texas')
|
||||
qs = qs.exclude(name="Texas")
|
||||
for country in qs:
|
||||
self.assertTrue(country.mpoly.sym_difference(geom).equals(country.sym_difference))
|
||||
self.assertTrue(
|
||||
country.mpoly.sym_difference(geom).equals(country.sym_difference)
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("has_Transform_function")
|
||||
def test_transform(self):
|
||||
# Pre-transformed points for Houston and Pueblo.
|
||||
ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
|
||||
ptown = fromstr("POINT(992363.390841912 481455.395105533)", srid=2774)
|
||||
|
||||
# Asserting the result of the transform operation with the values in
|
||||
# the pre-transformed points.
|
||||
h = City.objects.annotate(pt=functions.Transform('point', ptown.srid)).get(name='Pueblo')
|
||||
h = City.objects.annotate(pt=functions.Transform("point", ptown.srid)).get(
|
||||
name="Pueblo"
|
||||
)
|
||||
self.assertEqual(2774, h.pt.srid)
|
||||
# Precision is low due to version variations in PROJ and GDAL.
|
||||
self.assertLess(ptown.x - h.pt.x, 1)
|
||||
@@ -552,7 +662,9 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
@skipUnlessDBFeature("has_Translate_function")
|
||||
def test_translate(self):
|
||||
xfac, yfac = 5, -23
|
||||
qs = Country.objects.annotate(translated=functions.Translate('mpoly', xfac, yfac))
|
||||
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):
|
||||
@@ -563,15 +675,18 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
|
||||
# Some combined function tests
|
||||
@skipUnlessDBFeature(
|
||||
"has_Difference_function", "has_Intersection_function",
|
||||
"has_SymDifference_function", "has_Union_function")
|
||||
"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),
|
||||
difference=functions.Difference("mpoly", geom),
|
||||
sym_difference=functions.SymDifference("mpoly", geom),
|
||||
union=functions.Union("mpoly", geom),
|
||||
intersection=functions.Intersection("mpoly", geom),
|
||||
)
|
||||
|
||||
if connection.ops.oracle:
|
||||
@@ -593,18 +708,36 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
"""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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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")
|
||||
@@ -614,24 +747,28 @@ class GISFunctionsTests(FuncTestMixin, TestCase):
|
||||
geom_3857 = geom.transform(3857, clone=True)
|
||||
tol = 0.001
|
||||
|
||||
for city in City.objects.annotate(union=functions.Union('point', geom_3857)):
|
||||
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')):
|
||||
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.'):
|
||||
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.'
|
||||
msg = "GeoFunc function requires a GeometryField in position 1, got CharField."
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
City.objects.annotate(geo=functions.GeoFunc('name'))
|
||||
City.objects.annotate(geo=functions.GeoFunc("name"))
|
||||
|
||||
msg = 'GeoFunc function requires a geometric argument in position 1.'
|
||||
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')
|
||||
City.objects.annotate(union=functions.GeoFunc(1, "point")).get(
|
||||
name="Dallas"
|
||||
)
|
||||
|
||||
@@ -15,9 +15,9 @@ class SchemaIndexesTests(TransactionTestCase):
|
||||
with connection.cursor() as cursor:
|
||||
constraints = connection.introspection.get_constraints(cursor, table)
|
||||
return {
|
||||
name: constraint['columns']
|
||||
name: constraint["columns"]
|
||||
for name, constraint in constraints.items()
|
||||
if constraint['index']
|
||||
if constraint["index"]
|
||||
}
|
||||
|
||||
def has_spatial_indexes(self, table):
|
||||
@@ -32,31 +32,31 @@ class SchemaIndexesTests(TransactionTestCase):
|
||||
|
||||
def test_using_sql(self):
|
||||
if not connection.ops.postgis:
|
||||
self.skipTest('This is a PostGIS-specific test.')
|
||||
index = Index(fields=['point'])
|
||||
self.skipTest("This is a PostGIS-specific test.")
|
||||
index = Index(fields=["point"])
|
||||
editor = connection.schema_editor()
|
||||
self.assertIn(
|
||||
'%s USING ' % editor.quote_name(City._meta.db_table),
|
||||
"%s USING " % editor.quote_name(City._meta.db_table),
|
||||
str(index.create_sql(City, editor)),
|
||||
)
|
||||
|
||||
@isolate_apps('gis_tests.geoapp')
|
||||
@isolate_apps("gis_tests.geoapp")
|
||||
def test_namespaced_db_table(self):
|
||||
if not connection.ops.postgis:
|
||||
self.skipTest('PostGIS-specific test.')
|
||||
self.skipTest("PostGIS-specific test.")
|
||||
|
||||
class SchemaCity(models.Model):
|
||||
point = models.PointField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'geoapp'
|
||||
app_label = "geoapp"
|
||||
db_table = 'django_schema"."geoapp_schema_city'
|
||||
|
||||
index = Index(fields=['point'])
|
||||
index = Index(fields=["point"])
|
||||
editor = connection.schema_editor()
|
||||
create_index_sql = str(index.create_sql(SchemaCity, editor))
|
||||
self.assertIn(
|
||||
'%s USING ' % editor.quote_name(SchemaCity._meta.db_table),
|
||||
"%s USING " % editor.quote_name(SchemaCity._meta.db_table),
|
||||
create_index_sql,
|
||||
)
|
||||
self.assertIn(
|
||||
@@ -66,12 +66,12 @@ class SchemaIndexesTests(TransactionTestCase):
|
||||
|
||||
def test_index_name(self):
|
||||
if not self.has_spatial_indexes(City._meta.db_table):
|
||||
self.skipTest('Spatial indexes in Meta.indexes are not supported.')
|
||||
index_name = 'custom_point_index_name'
|
||||
index = Index(fields=['point'], name=index_name)
|
||||
self.skipTest("Spatial indexes in Meta.indexes are not supported.")
|
||||
index_name = "custom_point_index_name"
|
||||
index = Index(fields=["point"], name=index_name)
|
||||
with connection.schema_editor() as editor:
|
||||
editor.add_index(City, index)
|
||||
indexes = self.get_indexes(City._meta.db_table)
|
||||
self.assertIn(index_name, indexes)
|
||||
self.assertEqual(indexes[index_name], ['point'])
|
||||
self.assertEqual(indexes[index_name], ["point"])
|
||||
editor.remove_index(City, index)
|
||||
|
||||
@@ -9,20 +9,20 @@ from .models import City, PennsylvaniaCity, State, Truth
|
||||
|
||||
|
||||
class GeoRegressionTests(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test_update(self):
|
||||
"Testing QuerySet.update() (#10411)."
|
||||
pueblo = City.objects.get(name='Pueblo')
|
||||
pueblo = City.objects.get(name="Pueblo")
|
||||
bak = pueblo.point.clone()
|
||||
pueblo.point.y += 0.005
|
||||
pueblo.point.x += 0.005
|
||||
|
||||
City.objects.filter(name='Pueblo').update(point=pueblo.point)
|
||||
City.objects.filter(name="Pueblo").update(point=pueblo.point)
|
||||
pueblo.refresh_from_db()
|
||||
self.assertAlmostEqual(bak.y + 0.005, pueblo.point.y, 6)
|
||||
self.assertAlmostEqual(bak.x + 0.005, pueblo.point.x, 6)
|
||||
City.objects.filter(name='Pueblo').update(point=bak)
|
||||
City.objects.filter(name="Pueblo").update(point=bak)
|
||||
pueblo.refresh_from_db()
|
||||
self.assertAlmostEqual(bak.y, pueblo.point.y, 6)
|
||||
self.assertAlmostEqual(bak.x, pueblo.point.x, 6)
|
||||
@@ -30,45 +30,61 @@ class GeoRegressionTests(TestCase):
|
||||
def test_kmz(self):
|
||||
"Testing `render_to_kmz` with non-ASCII data. See #11624."
|
||||
name = "Åland Islands"
|
||||
places = [{
|
||||
'name': name,
|
||||
'description': name,
|
||||
'kml': '<Point><coordinates>5.0,23.0</coordinates></Point>'
|
||||
}]
|
||||
render_to_kmz('gis/kml/placemarks.kml', {'places': places})
|
||||
places = [
|
||||
{
|
||||
"name": name,
|
||||
"description": name,
|
||||
"kml": "<Point><coordinates>5.0,23.0</coordinates></Point>",
|
||||
}
|
||||
]
|
||||
render_to_kmz("gis/kml/placemarks.kml", {"places": places})
|
||||
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test_extent(self):
|
||||
"Testing `extent` on a table with a single point. See #11827."
|
||||
pnt = City.objects.get(name='Pueblo').point
|
||||
pnt = City.objects.get(name="Pueblo").point
|
||||
ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y)
|
||||
extent = City.objects.filter(name='Pueblo').aggregate(Extent('point'))['point__extent']
|
||||
extent = City.objects.filter(name="Pueblo").aggregate(Extent("point"))[
|
||||
"point__extent"
|
||||
]
|
||||
for ref_val, val in zip(ref_ext, extent):
|
||||
self.assertAlmostEqual(ref_val, val, 4)
|
||||
|
||||
def test_unicode_date(self):
|
||||
"Testing dates are converted properly, even on SpatiaLite. See #16408."
|
||||
founded = datetime(1857, 5, 23)
|
||||
PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)',
|
||||
founded=founded)
|
||||
self.assertEqual(founded, PennsylvaniaCity.objects.datetimes('founded', 'day')[0])
|
||||
self.assertEqual(founded, PennsylvaniaCity.objects.aggregate(Min('founded'))['founded__min'])
|
||||
PennsylvaniaCity.objects.create(
|
||||
name="Mansfield",
|
||||
county="Tioga",
|
||||
point="POINT(-77.071445 41.823881)",
|
||||
founded=founded,
|
||||
)
|
||||
self.assertEqual(
|
||||
founded, PennsylvaniaCity.objects.datetimes("founded", "day")[0]
|
||||
)
|
||||
self.assertEqual(
|
||||
founded, PennsylvaniaCity.objects.aggregate(Min("founded"))["founded__min"]
|
||||
)
|
||||
|
||||
def test_empty_count(self):
|
||||
"Testing that PostGISAdapter.__eq__ does check empty strings. See #13670."
|
||||
# contrived example, but need a geo lookup paired with an id__in lookup
|
||||
pueblo = City.objects.get(name='Pueblo')
|
||||
pueblo = City.objects.get(name="Pueblo")
|
||||
state = State.objects.filter(poly__contains=pueblo.point)
|
||||
cities_within_state = City.objects.filter(id__in=state)
|
||||
|
||||
# .count() should not throw TypeError in __eq__
|
||||
self.assertEqual(cities_within_state.count(), 1)
|
||||
|
||||
@skipUnlessDBFeature('allows_group_by_lob')
|
||||
@skipUnlessDBFeature("allows_group_by_lob")
|
||||
def test_defer_or_only_with_annotate(self):
|
||||
"Regression for #16409. Make sure defer() and only() work with annotate()"
|
||||
self.assertIsInstance(list(City.objects.annotate(Count('point')).defer('name')), list)
|
||||
self.assertIsInstance(list(City.objects.annotate(Count('point')).only('name')), list)
|
||||
self.assertIsInstance(
|
||||
list(City.objects.annotate(Count("point")).defer("name")), list
|
||||
)
|
||||
self.assertIsInstance(
|
||||
list(City.objects.annotate(Count("point")).only("name")), list
|
||||
)
|
||||
|
||||
def test_boolean_conversion(self):
|
||||
"Testing Boolean value conversion with the spatial backend, see #15169."
|
||||
|
||||
@@ -8,7 +8,7 @@ from .models import City, MultiFields, PennsylvaniaCity
|
||||
|
||||
|
||||
class GeoJSONSerializerTests(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test_builtin_serializers(self):
|
||||
"""
|
||||
@@ -17,17 +17,17 @@ class GeoJSONSerializerTests(TestCase):
|
||||
all_formats = set(serializers.get_serializer_formats())
|
||||
public_formats = set(serializers.get_public_serializer_formats())
|
||||
|
||||
self.assertIn('geojson', all_formats),
|
||||
self.assertIn('geojson', public_formats)
|
||||
self.assertIn("geojson", all_formats),
|
||||
self.assertIn("geojson", public_formats)
|
||||
|
||||
def test_serialization_base(self):
|
||||
geojson = serializers.serialize('geojson', City.objects.all().order_by('name'))
|
||||
geojson = serializers.serialize("geojson", City.objects.all().order_by("name"))
|
||||
geodata = json.loads(geojson)
|
||||
self.assertEqual(len(geodata['features']), len(City.objects.all()))
|
||||
self.assertEqual(geodata['features'][0]['geometry']['type'], 'Point')
|
||||
self.assertEqual(geodata['features'][0]['properties']['name'], 'Chicago')
|
||||
first_city = City.objects.all().order_by('name').first()
|
||||
self.assertEqual(geodata['features'][0]['properties']['pk'], str(first_city.pk))
|
||||
self.assertEqual(len(geodata["features"]), len(City.objects.all()))
|
||||
self.assertEqual(geodata["features"][0]["geometry"]["type"], "Point")
|
||||
self.assertEqual(geodata["features"][0]["properties"]["name"], "Chicago")
|
||||
first_city = City.objects.all().order_by("name").first()
|
||||
self.assertEqual(geodata["features"][0]["properties"]["pk"], str(first_city.pk))
|
||||
|
||||
def test_geometry_field_option(self):
|
||||
"""
|
||||
@@ -35,49 +35,56 @@ class GeoJSONSerializerTests(TestCase):
|
||||
can be used to specify the field to use as the 'geometry' key.
|
||||
"""
|
||||
MultiFields.objects.create(
|
||||
city=City.objects.first(), name='Name', point=Point(5, 23),
|
||||
poly=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))
|
||||
city=City.objects.first(),
|
||||
name="Name",
|
||||
point=Point(5, 23),
|
||||
poly=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))),
|
||||
)
|
||||
|
||||
geojson = serializers.serialize('geojson', MultiFields.objects.all())
|
||||
geojson = serializers.serialize("geojson", MultiFields.objects.all())
|
||||
geodata = json.loads(geojson)
|
||||
self.assertEqual(geodata['features'][0]['geometry']['type'], 'Point')
|
||||
self.assertEqual(geodata["features"][0]["geometry"]["type"], "Point")
|
||||
|
||||
geojson = serializers.serialize(
|
||||
'geojson',
|
||||
MultiFields.objects.all(),
|
||||
geometry_field='poly'
|
||||
"geojson", MultiFields.objects.all(), geometry_field="poly"
|
||||
)
|
||||
geodata = json.loads(geojson)
|
||||
self.assertEqual(geodata['features'][0]['geometry']['type'], 'Polygon')
|
||||
self.assertEqual(geodata["features"][0]["geometry"]["type"], "Polygon")
|
||||
|
||||
# geometry_field is considered even if not in fields (#26138).
|
||||
geojson = serializers.serialize(
|
||||
'geojson',
|
||||
"geojson",
|
||||
MultiFields.objects.all(),
|
||||
geometry_field='poly',
|
||||
fields=('city',)
|
||||
geometry_field="poly",
|
||||
fields=("city",),
|
||||
)
|
||||
geodata = json.loads(geojson)
|
||||
self.assertEqual(geodata['features'][0]['geometry']['type'], 'Polygon')
|
||||
self.assertEqual(geodata["features"][0]["geometry"]["type"], "Polygon")
|
||||
|
||||
def test_fields_option(self):
|
||||
"""
|
||||
The fields option allows to define a subset of fields to be present in
|
||||
the 'properties' of the generated output.
|
||||
"""
|
||||
PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
|
||||
PennsylvaniaCity.objects.create(
|
||||
name="Mansfield", county="Tioga", point="POINT(-77.071445 41.823881)"
|
||||
)
|
||||
geojson = serializers.serialize(
|
||||
'geojson', PennsylvaniaCity.objects.all(), fields=('county', 'point'),
|
||||
"geojson",
|
||||
PennsylvaniaCity.objects.all(),
|
||||
fields=("county", "point"),
|
||||
)
|
||||
geodata = json.loads(geojson)
|
||||
self.assertIn('county', geodata['features'][0]['properties'])
|
||||
self.assertNotIn('founded', geodata['features'][0]['properties'])
|
||||
self.assertNotIn('pk', geodata['features'][0]['properties'])
|
||||
self.assertIn("county", geodata["features"][0]["properties"])
|
||||
self.assertNotIn("founded", geodata["features"][0]["properties"])
|
||||
self.assertNotIn("pk", geodata["features"][0]["properties"])
|
||||
|
||||
def test_srid_option(self):
|
||||
geojson = serializers.serialize('geojson', City.objects.all().order_by('name'), srid=2847)
|
||||
geojson = serializers.serialize(
|
||||
"geojson", City.objects.all().order_by("name"), srid=2847
|
||||
)
|
||||
geodata = json.loads(geojson)
|
||||
coordinates = geodata['features'][0]['geometry']['coordinates']
|
||||
coordinates = geodata["features"][0]["geometry"]["coordinates"]
|
||||
# Different PROJ versions use different transformations, all are
|
||||
# correct as having a 1 meter accuracy.
|
||||
self.assertAlmostEqual(coordinates[0], 1564802, -1)
|
||||
@@ -88,4 +95,4 @@ class GeoJSONSerializerTests(TestCase):
|
||||
GeoJSON cannot be deserialized.
|
||||
"""
|
||||
with self.assertRaises(serializers.base.SerializerDoesNotExist):
|
||||
serializers.deserialize('geojson', '{}')
|
||||
serializers.deserialize("geojson", "{}")
|
||||
|
||||
@@ -9,10 +9,11 @@ from django.test import TestCase, modify_settings, override_settings
|
||||
from .models import City, Country
|
||||
|
||||
|
||||
@modify_settings(INSTALLED_APPS={'append': ['django.contrib.sites', 'django.contrib.sitemaps']})
|
||||
@override_settings(ROOT_URLCONF='gis_tests.geoapp.urls')
|
||||
@modify_settings(
|
||||
INSTALLED_APPS={"append": ["django.contrib.sites", "django.contrib.sitemaps"]}
|
||||
)
|
||||
@override_settings(ROOT_URLCONF="gis_tests.geoapp.urls")
|
||||
class GeoSitemapTest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
Site(id=settings.SITE_ID, domain="example.com", name="example.com").save()
|
||||
@@ -25,34 +26,46 @@ class GeoSitemapTest(TestCase):
|
||||
|
||||
def test_geositemap_kml(self):
|
||||
"Tests KML/KMZ geographic sitemaps."
|
||||
for kml_type in ('kml', 'kmz'):
|
||||
doc = minidom.parseString(self.client.get('/sitemaps/%s.xml' % kml_type).content)
|
||||
for kml_type in ("kml", "kmz"):
|
||||
doc = minidom.parseString(
|
||||
self.client.get("/sitemaps/%s.xml" % kml_type).content
|
||||
)
|
||||
|
||||
# Ensuring the right sitemaps namespace is present.
|
||||
urlset = doc.firstChild
|
||||
self.assertEqual(urlset.getAttribute('xmlns'), 'http://www.sitemaps.org/schemas/sitemap/0.9')
|
||||
self.assertEqual(
|
||||
urlset.getAttribute("xmlns"),
|
||||
"http://www.sitemaps.org/schemas/sitemap/0.9",
|
||||
)
|
||||
|
||||
urls = urlset.getElementsByTagName('url')
|
||||
urls = urlset.getElementsByTagName("url")
|
||||
self.assertEqual(2, len(urls)) # Should only be 2 sitemaps.
|
||||
for url in urls:
|
||||
self.assertChildNodes(url, ['loc'])
|
||||
self.assertChildNodes(url, ["loc"])
|
||||
|
||||
# Getting the relative URL since we don't have a real site.
|
||||
kml_url = url.getElementsByTagName('loc')[0].childNodes[0].data.split('http://example.com')[1]
|
||||
kml_url = (
|
||||
url.getElementsByTagName("loc")[0]
|
||||
.childNodes[0]
|
||||
.data.split("http://example.com")[1]
|
||||
)
|
||||
|
||||
if kml_type == 'kml':
|
||||
if kml_type == "kml":
|
||||
kml_doc = minidom.parseString(self.client.get(kml_url).content)
|
||||
elif kml_type == 'kmz':
|
||||
elif kml_type == "kmz":
|
||||
# Have to decompress KMZ before parsing.
|
||||
buf = BytesIO(self.client.get(kml_url).content)
|
||||
with zipfile.ZipFile(buf) as zf:
|
||||
self.assertEqual(1, len(zf.filelist))
|
||||
self.assertEqual('doc.kml', zf.filelist[0].filename)
|
||||
kml_doc = minidom.parseString(zf.read('doc.kml'))
|
||||
self.assertEqual("doc.kml", zf.filelist[0].filename)
|
||||
kml_doc = minidom.parseString(zf.read("doc.kml"))
|
||||
|
||||
# Ensuring the correct number of placemarks are in the KML doc.
|
||||
if 'city' in kml_url:
|
||||
if "city" in kml_url:
|
||||
model = City
|
||||
elif 'country' in kml_url:
|
||||
elif "country" in kml_url:
|
||||
model = Country
|
||||
self.assertEqual(model.objects.count(), len(kml_doc.getElementsByTagName('Placemark')))
|
||||
self.assertEqual(
|
||||
model.objects.count(),
|
||||
len(kml_doc.getElementsByTagName("Placemark")),
|
||||
)
|
||||
|
||||
@@ -4,8 +4,16 @@ from io import StringIO
|
||||
from django.contrib.gis import gdal
|
||||
from django.contrib.gis.db.models import Extent, MakeLine, Union, functions
|
||||
from django.contrib.gis.geos import (
|
||||
GeometryCollection, GEOSGeometry, LinearRing, LineString, MultiLineString,
|
||||
MultiPoint, MultiPolygon, Point, Polygon, fromstr,
|
||||
GeometryCollection,
|
||||
GEOSGeometry,
|
||||
LinearRing,
|
||||
LineString,
|
||||
MultiLineString,
|
||||
MultiPoint,
|
||||
MultiPolygon,
|
||||
Point,
|
||||
Polygon,
|
||||
fromstr,
|
||||
)
|
||||
from django.core.management import call_command
|
||||
from django.db import DatabaseError, NotSupportedError, connection
|
||||
@@ -15,13 +23,20 @@ from django.test.utils import CaptureQueriesContext
|
||||
|
||||
from ..utils import skipUnlessGISLookup
|
||||
from .models import (
|
||||
City, Country, Feature, MinusOneSRID, MultiFields, NonConcreteModel,
|
||||
PennsylvaniaCity, State, Track,
|
||||
City,
|
||||
Country,
|
||||
Feature,
|
||||
MinusOneSRID,
|
||||
MultiFields,
|
||||
NonConcreteModel,
|
||||
PennsylvaniaCity,
|
||||
State,
|
||||
Track,
|
||||
)
|
||||
|
||||
|
||||
class GeoModelTest(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test_fixtures(self):
|
||||
"Testing geographic model initialization from fixtures."
|
||||
@@ -34,13 +49,13 @@ class GeoModelTest(TestCase):
|
||||
"Testing Lazy-Geometry support (using the GeometryProxy)."
|
||||
# Testing on a Point
|
||||
pnt = Point(0, 0)
|
||||
nullcity = City(name='NullCity', point=pnt)
|
||||
nullcity = City(name="NullCity", point=pnt)
|
||||
nullcity.save()
|
||||
|
||||
# Making sure TypeError is thrown when trying to set with an
|
||||
# incompatible type.
|
||||
for bad in [5, 2.0, LineString((0, 0), (1, 1))]:
|
||||
with self.assertRaisesMessage(TypeError, 'Cannot set'):
|
||||
with self.assertRaisesMessage(TypeError, "Cannot set"):
|
||||
nullcity.point = bad
|
||||
|
||||
# Now setting with a compatible GEOS Geometry, saving, and ensuring
|
||||
@@ -54,15 +69,19 @@ class GeoModelTest(TestCase):
|
||||
nullcity.save()
|
||||
|
||||
# Ensuring the point was saved correctly after saving
|
||||
self.assertEqual(new, City.objects.get(name='NullCity').point)
|
||||
self.assertEqual(new, City.objects.get(name="NullCity").point)
|
||||
|
||||
# Setting the X and Y of the Point
|
||||
nullcity.point.x = 23
|
||||
nullcity.point.y = 5
|
||||
# Checking assignments pre & post-save.
|
||||
self.assertNotEqual(Point(23, 5, srid=4326), City.objects.get(name='NullCity').point)
|
||||
self.assertNotEqual(
|
||||
Point(23, 5, srid=4326), City.objects.get(name="NullCity").point
|
||||
)
|
||||
nullcity.save()
|
||||
self.assertEqual(Point(23, 5, srid=4326), City.objects.get(name='NullCity').point)
|
||||
self.assertEqual(
|
||||
Point(23, 5, srid=4326), City.objects.get(name="NullCity").point
|
||||
)
|
||||
nullcity.delete()
|
||||
|
||||
# Testing on a Polygon
|
||||
@@ -71,18 +90,18 @@ class GeoModelTest(TestCase):
|
||||
|
||||
# Creating a State object using a built Polygon
|
||||
ply = Polygon(shell, inner)
|
||||
nullstate = State(name='NullState', poly=ply)
|
||||
nullstate = State(name="NullState", poly=ply)
|
||||
self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
|
||||
nullstate.save()
|
||||
|
||||
ns = State.objects.get(name='NullState')
|
||||
ns = State.objects.get(name="NullState")
|
||||
self.assertEqual(connection.ops.Adapter._fix_polygon(ply), ns.poly)
|
||||
|
||||
# Testing the `ogr` and `srs` lazy-geometry properties.
|
||||
self.assertIsInstance(ns.poly.ogr, gdal.OGRGeometry)
|
||||
self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb)
|
||||
self.assertIsInstance(ns.poly.srs, gdal.SpatialReference)
|
||||
self.assertEqual('WGS 84', ns.poly.srs.name)
|
||||
self.assertEqual("WGS 84", ns.poly.srs.name)
|
||||
|
||||
# Changing the interior ring on the poly attribute.
|
||||
new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
|
||||
@@ -92,7 +111,7 @@ class GeoModelTest(TestCase):
|
||||
ns.save()
|
||||
self.assertEqual(
|
||||
connection.ops.Adapter._fix_polygon(ply),
|
||||
State.objects.get(name='NullState').poly
|
||||
State.objects.get(name="NullState").poly,
|
||||
)
|
||||
ns.delete()
|
||||
|
||||
@@ -100,7 +119,7 @@ class GeoModelTest(TestCase):
|
||||
def test_lookup_insert_transform(self):
|
||||
"Testing automatic transform for lookups and inserts."
|
||||
# San Antonio in 'WGS84' (SRID 4326)
|
||||
sa_4326 = 'POINT (-98.493183 29.424170)'
|
||||
sa_4326 = "POINT (-98.493183 29.424170)"
|
||||
wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
|
||||
# San Antonio in 'WGS 84 / Pseudo-Mercator' (SRID 3857)
|
||||
other_srid_pnt = wgs_pnt.transform(3857, clone=True)
|
||||
@@ -111,13 +130,13 @@ class GeoModelTest(TestCase):
|
||||
tx = Country.objects.get(mpoly__contains=other_srid_pnt)
|
||||
else:
|
||||
tx = Country.objects.get(mpoly__intersects=other_srid_pnt)
|
||||
self.assertEqual('Texas', tx.name)
|
||||
self.assertEqual("Texas", tx.name)
|
||||
|
||||
# Creating San Antonio. Remember the Alamo.
|
||||
sa = City.objects.create(name='San Antonio', point=other_srid_pnt)
|
||||
sa = City.objects.create(name="San Antonio", point=other_srid_pnt)
|
||||
|
||||
# Now verifying that San Antonio was transformed correctly
|
||||
sa = City.objects.get(name='San Antonio')
|
||||
sa = City.objects.get(name="San Antonio")
|
||||
self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6)
|
||||
self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
|
||||
|
||||
@@ -134,23 +153,31 @@ class GeoModelTest(TestCase):
|
||||
|
||||
def test_geometryfield(self):
|
||||
"Testing the general GeometryField."
|
||||
Feature(name='Point', geom=Point(1, 1)).save()
|
||||
Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save()
|
||||
Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save()
|
||||
Feature(name='GeometryCollection',
|
||||
geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)),
|
||||
Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save()
|
||||
Feature(name="Point", geom=Point(1, 1)).save()
|
||||
Feature(name="LineString", geom=LineString((0, 0), (1, 1), (5, 5))).save()
|
||||
Feature(
|
||||
name="Polygon",
|
||||
geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))),
|
||||
).save()
|
||||
Feature(
|
||||
name="GeometryCollection",
|
||||
geom=GeometryCollection(
|
||||
Point(2, 2),
|
||||
LineString((0, 0), (2, 2)),
|
||||
Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))),
|
||||
),
|
||||
).save()
|
||||
|
||||
f_1 = Feature.objects.get(name='Point')
|
||||
f_1 = Feature.objects.get(name="Point")
|
||||
self.assertIsInstance(f_1.geom, Point)
|
||||
self.assertEqual((1.0, 1.0), f_1.geom.tuple)
|
||||
f_2 = Feature.objects.get(name='LineString')
|
||||
f_2 = Feature.objects.get(name="LineString")
|
||||
self.assertIsInstance(f_2.geom, LineString)
|
||||
self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple)
|
||||
|
||||
f_3 = Feature.objects.get(name='Polygon')
|
||||
f_3 = Feature.objects.get(name="Polygon")
|
||||
self.assertIsInstance(f_3.geom, Polygon)
|
||||
f_4 = Feature.objects.get(name='GeometryCollection')
|
||||
f_4 = Feature.objects.get(name="GeometryCollection")
|
||||
self.assertIsInstance(f_4.geom, GeometryCollection)
|
||||
self.assertEqual(f_3.geom, f_4.geom[2])
|
||||
|
||||
@@ -158,11 +185,15 @@ class GeoModelTest(TestCase):
|
||||
def test_inherited_geofields(self):
|
||||
"Database functions on inherited Geometry fields."
|
||||
# Creating a Pennsylvanian city.
|
||||
PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
|
||||
PennsylvaniaCity.objects.create(
|
||||
name="Mansfield", county="Tioga", point="POINT(-77.071445 41.823881)"
|
||||
)
|
||||
|
||||
# All transformation SQL will need to be performed on the
|
||||
# _parent_ table.
|
||||
qs = PennsylvaniaCity.objects.annotate(new_point=functions.Transform('point', srid=32128))
|
||||
qs = PennsylvaniaCity.objects.annotate(
|
||||
new_point=functions.Transform("point", srid=32128)
|
||||
)
|
||||
|
||||
self.assertEqual(1, qs.count())
|
||||
for pc in qs:
|
||||
@@ -171,10 +202,12 @@ class GeoModelTest(TestCase):
|
||||
def test_raw_sql_query(self):
|
||||
"Testing raw SQL query."
|
||||
cities1 = City.objects.all()
|
||||
point_select = connection.ops.select % 'point'
|
||||
cities2 = list(City.objects.raw(
|
||||
'select id, name, %s as point from geoapp_city' % point_select
|
||||
))
|
||||
point_select = connection.ops.select % "point"
|
||||
cities2 = list(
|
||||
City.objects.raw(
|
||||
"select id, name, %s as point from geoapp_city" % point_select
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(cities1), len(cities2))
|
||||
with self.assertNumQueries(0): # Ensure point isn't deferred.
|
||||
self.assertIsInstance(cities2[0].point, Point)
|
||||
@@ -184,18 +217,18 @@ class GeoModelTest(TestCase):
|
||||
Test a dumpdata/loaddata cycle with geographic data.
|
||||
"""
|
||||
out = StringIO()
|
||||
original_data = list(City.objects.all().order_by('name'))
|
||||
call_command('dumpdata', 'geoapp.City', stdout=out)
|
||||
original_data = list(City.objects.all().order_by("name"))
|
||||
call_command("dumpdata", "geoapp.City", stdout=out)
|
||||
result = out.getvalue()
|
||||
houston = City.objects.get(name='Houston')
|
||||
houston = City.objects.get(name="Houston")
|
||||
self.assertIn('"point": "%s"' % houston.point.ewkt, result)
|
||||
|
||||
# Reload now dumped data
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json') as tmp:
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".json") as tmp:
|
||||
tmp.write(result)
|
||||
tmp.seek(0)
|
||||
call_command('loaddata', tmp.name, verbosity=0)
|
||||
self.assertEqual(original_data, list(City.objects.all().order_by('name')))
|
||||
call_command("loaddata", tmp.name, verbosity=0)
|
||||
self.assertEqual(original_data, list(City.objects.all().order_by("name")))
|
||||
|
||||
@skipUnlessDBFeature("supports_empty_geometries")
|
||||
def test_empty_geometries(self):
|
||||
@@ -211,7 +244,7 @@ class GeoModelTest(TestCase):
|
||||
]
|
||||
for klass in geometry_classes:
|
||||
g = klass(srid=4326)
|
||||
feature = Feature.objects.create(name='Empty %s' % klass.__name__, geom=g)
|
||||
feature = Feature.objects.create(name="Empty %s" % klass.__name__, geom=g)
|
||||
feature.refresh_from_db()
|
||||
if klass is LinearRing:
|
||||
# LinearRing isn't representable in WKB, so GEOSGeomtry.wkb
|
||||
@@ -222,21 +255,21 @@ class GeoModelTest(TestCase):
|
||||
|
||||
|
||||
class GeoLookupTest(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test_disjoint_lookup(self):
|
||||
"Testing the `disjoint` lookup type."
|
||||
ptown = City.objects.get(name='Pueblo')
|
||||
ptown = City.objects.get(name="Pueblo")
|
||||
qs1 = City.objects.filter(point__disjoint=ptown.point)
|
||||
self.assertEqual(7, qs1.count())
|
||||
qs2 = State.objects.filter(poly__disjoint=ptown.point)
|
||||
self.assertEqual(1, qs2.count())
|
||||
self.assertEqual('Kansas', qs2[0].name)
|
||||
self.assertEqual("Kansas", qs2[0].name)
|
||||
|
||||
def test_contains_contained_lookups(self):
|
||||
"Testing the 'contained', 'contains', and 'bbcontains' lookup types."
|
||||
# Getting Texas, yes we were a country -- once ;)
|
||||
texas = Country.objects.get(name='Texas')
|
||||
texas = Country.objects.get(name="Texas")
|
||||
|
||||
# Seeing what cities are in Texas, should get Houston and Dallas,
|
||||
# and Oklahoma City because 'contained' only checks on the
|
||||
@@ -244,69 +277,80 @@ class GeoLookupTest(TestCase):
|
||||
if connection.features.supports_contained_lookup:
|
||||
qs = City.objects.filter(point__contained=texas.mpoly)
|
||||
self.assertEqual(3, qs.count())
|
||||
cities = ['Houston', 'Dallas', 'Oklahoma City']
|
||||
cities = ["Houston", "Dallas", "Oklahoma City"]
|
||||
for c in qs:
|
||||
self.assertIn(c.name, cities)
|
||||
|
||||
# Pulling out some cities.
|
||||
houston = City.objects.get(name='Houston')
|
||||
wellington = City.objects.get(name='Wellington')
|
||||
pueblo = City.objects.get(name='Pueblo')
|
||||
okcity = City.objects.get(name='Oklahoma City')
|
||||
lawrence = City.objects.get(name='Lawrence')
|
||||
houston = City.objects.get(name="Houston")
|
||||
wellington = City.objects.get(name="Wellington")
|
||||
pueblo = City.objects.get(name="Pueblo")
|
||||
okcity = City.objects.get(name="Oklahoma City")
|
||||
lawrence = City.objects.get(name="Lawrence")
|
||||
|
||||
# Now testing contains on the countries using the points for
|
||||
# Houston and Wellington.
|
||||
tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
|
||||
nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
|
||||
self.assertEqual('Texas', tx.name)
|
||||
self.assertEqual('New Zealand', nz.name)
|
||||
nz = Country.objects.get(
|
||||
mpoly__contains=wellington.point.hex
|
||||
) # Query w/EWKBHEX
|
||||
self.assertEqual("Texas", tx.name)
|
||||
self.assertEqual("New Zealand", nz.name)
|
||||
|
||||
# Testing `contains` on the states using the point for Lawrence.
|
||||
ks = State.objects.get(poly__contains=lawrence.point)
|
||||
self.assertEqual('Kansas', ks.name)
|
||||
self.assertEqual("Kansas", ks.name)
|
||||
|
||||
# Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
|
||||
# are not contained in Texas or New Zealand.
|
||||
self.assertEqual(len(Country.objects.filter(mpoly__contains=pueblo.point)), 0) # Query w/GEOSGeometry object
|
||||
self.assertEqual(len(Country.objects.filter(mpoly__contains=okcity.point.wkt)), 0) # Query w/WKT
|
||||
self.assertEqual(
|
||||
len(Country.objects.filter(mpoly__contains=pueblo.point)), 0
|
||||
) # Query w/GEOSGeometry object
|
||||
self.assertEqual(
|
||||
len(Country.objects.filter(mpoly__contains=okcity.point.wkt)), 0
|
||||
) # Query w/WKT
|
||||
|
||||
# OK City is contained w/in bounding box of Texas.
|
||||
if connection.features.supports_bbcontains_lookup:
|
||||
qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('Texas', qs[0].name)
|
||||
self.assertEqual("Texas", qs[0].name)
|
||||
|
||||
@skipUnlessDBFeature("supports_crosses_lookup")
|
||||
def test_crosses_lookup(self):
|
||||
Track.objects.create(
|
||||
name='Line1',
|
||||
line=LineString([(-95, 29), (-60, 0)])
|
||||
Track.objects.create(name="Line1", line=LineString([(-95, 29), (-60, 0)]))
|
||||
self.assertEqual(
|
||||
Track.objects.filter(
|
||||
line__crosses=LineString([(-95, 0), (-60, 29)])
|
||||
).count(),
|
||||
1,
|
||||
)
|
||||
self.assertEqual(
|
||||
Track.objects.filter(line__crosses=LineString([(-95, 0), (-60, 29)])).count(),
|
||||
1
|
||||
)
|
||||
self.assertEqual(
|
||||
Track.objects.filter(line__crosses=LineString([(-95, 30), (0, 30)])).count(),
|
||||
0
|
||||
Track.objects.filter(
|
||||
line__crosses=LineString([(-95, 30), (0, 30)])
|
||||
).count(),
|
||||
0,
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_isvalid_lookup")
|
||||
def test_isvalid_lookup(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_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)
|
||||
qs = State.objects.all()
|
||||
if connection.ops.oracle or (connection.ops.mysql and connection.mysql_version < (8, 0, 0)):
|
||||
if connection.ops.oracle or (
|
||||
connection.ops.mysql and connection.mysql_version < (8, 0, 0)
|
||||
):
|
||||
# Kansas has adjacent vertices with distance 6.99244813842e-12
|
||||
# which is smaller than the default Oracle tolerance.
|
||||
# It's invalid on MySQL < 8 also.
|
||||
qs = qs.exclude(name='Kansas')
|
||||
self.assertEqual(State.objects.filter(name='Kansas', poly__isvalid=False).count(), 1)
|
||||
qs = qs.exclude(name="Kansas")
|
||||
self.assertEqual(
|
||||
State.objects.filter(name="Kansas", poly__isvalid=False).count(), 1
|
||||
)
|
||||
self.assertEqual(qs.filter(poly__isvalid=False).count(), 1)
|
||||
self.assertEqual(qs.filter(poly__isvalid=True).count(), qs.count() - 1)
|
||||
|
||||
@skipUnlessGISLookup('left', 'right')
|
||||
@skipUnlessGISLookup("left", "right")
|
||||
def test_left_right_lookups(self):
|
||||
"Testing the 'left' and 'right' lookup types."
|
||||
# Left: A << B => true if xmax(A) < xmin(B)
|
||||
@@ -314,22 +358,28 @@ class GeoLookupTest(TestCase):
|
||||
# See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
|
||||
|
||||
# Getting the borders for Colorado & Kansas
|
||||
co_border = State.objects.get(name='Colorado').poly
|
||||
ks_border = State.objects.get(name='Kansas').poly
|
||||
co_border = State.objects.get(name="Colorado").poly
|
||||
ks_border = State.objects.get(name="Kansas").poly
|
||||
|
||||
# Note: Wellington has an 'X' value of 174, so it will not be considered
|
||||
# to the left of CO.
|
||||
|
||||
# These cities should be strictly to the right of the CO border.
|
||||
cities = ['Houston', 'Dallas', 'Oklahoma City',
|
||||
'Lawrence', 'Chicago', 'Wellington']
|
||||
cities = [
|
||||
"Houston",
|
||||
"Dallas",
|
||||
"Oklahoma City",
|
||||
"Lawrence",
|
||||
"Chicago",
|
||||
"Wellington",
|
||||
]
|
||||
qs = City.objects.filter(point__right=co_border)
|
||||
self.assertEqual(6, len(qs))
|
||||
for c in qs:
|
||||
self.assertIn(c.name, cities)
|
||||
|
||||
# These cities should be strictly to the right of the KS border.
|
||||
cities = ['Chicago', 'Wellington']
|
||||
cities = ["Chicago", "Wellington"]
|
||||
qs = City.objects.filter(point__right=ks_border)
|
||||
self.assertEqual(2, len(qs))
|
||||
for c in qs:
|
||||
@@ -338,9 +388,9 @@ class GeoLookupTest(TestCase):
|
||||
# Note: Wellington has an 'X' value of 174, so it will not be considered
|
||||
# to the left of CO.
|
||||
vic = City.objects.get(point__left=co_border)
|
||||
self.assertEqual('Victoria', vic.name)
|
||||
self.assertEqual("Victoria", vic.name)
|
||||
|
||||
cities = ['Pueblo', 'Victoria']
|
||||
cities = ["Pueblo", "Victoria"]
|
||||
qs = City.objects.filter(point__left=ks_border)
|
||||
self.assertEqual(2, len(qs))
|
||||
for c in qs:
|
||||
@@ -348,32 +398,32 @@ class GeoLookupTest(TestCase):
|
||||
|
||||
@skipUnlessGISLookup("strictly_above", "strictly_below")
|
||||
def test_strictly_above_below_lookups(self):
|
||||
dallas = City.objects.get(name='Dallas')
|
||||
dallas = City.objects.get(name="Dallas")
|
||||
self.assertQuerysetEqual(
|
||||
City.objects.filter(point__strictly_above=dallas.point).order_by('name'),
|
||||
['Chicago', 'Lawrence', 'Oklahoma City', 'Pueblo', 'Victoria'],
|
||||
lambda b: b.name
|
||||
City.objects.filter(point__strictly_above=dallas.point).order_by("name"),
|
||||
["Chicago", "Lawrence", "Oklahoma City", "Pueblo", "Victoria"],
|
||||
lambda b: b.name,
|
||||
)
|
||||
self.assertQuerysetEqual(
|
||||
City.objects.filter(point__strictly_below=dallas.point).order_by('name'),
|
||||
['Houston', 'Wellington'],
|
||||
lambda b: b.name
|
||||
City.objects.filter(point__strictly_below=dallas.point).order_by("name"),
|
||||
["Houston", "Wellington"],
|
||||
lambda b: b.name,
|
||||
)
|
||||
|
||||
def test_equals_lookups(self):
|
||||
"Testing the 'same_as' and 'equals' lookup types."
|
||||
pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
|
||||
pnt = fromstr("POINT (-95.363151 29.763374)", srid=4326)
|
||||
c1 = City.objects.get(point=pnt)
|
||||
c2 = City.objects.get(point__same_as=pnt)
|
||||
c3 = City.objects.get(point__equals=pnt)
|
||||
for c in [c1, c2, c3]:
|
||||
self.assertEqual('Houston', c.name)
|
||||
self.assertEqual("Houston", c.name)
|
||||
|
||||
@skipUnlessDBFeature("supports_null_geometries")
|
||||
def test_null_geometries(self):
|
||||
"Testing NULL geometry support, and the `isnull` lookup type."
|
||||
# Creating a state with a NULL boundary.
|
||||
State.objects.create(name='Puerto Rico')
|
||||
State.objects.create(name="Puerto Rico")
|
||||
|
||||
# Querying for both NULL and Non-NULL values.
|
||||
nullqs = State.objects.filter(poly__isnull=True)
|
||||
@@ -381,7 +431,7 @@ class GeoLookupTest(TestCase):
|
||||
|
||||
# Puerto Rico should be NULL (it's a commonwealth unincorporated territory)
|
||||
self.assertEqual(1, len(nullqs))
|
||||
self.assertEqual('Puerto Rico', nullqs[0].name)
|
||||
self.assertEqual("Puerto Rico", nullqs[0].name)
|
||||
# GeometryField=None is an alias for __isnull=True.
|
||||
self.assertCountEqual(State.objects.filter(poly=None), nullqs)
|
||||
self.assertCountEqual(State.objects.exclude(poly=None), validqs)
|
||||
@@ -389,110 +439,135 @@ class GeoLookupTest(TestCase):
|
||||
# The valid states should be Colorado & Kansas
|
||||
self.assertEqual(2, len(validqs))
|
||||
state_names = [s.name for s in validqs]
|
||||
self.assertIn('Colorado', state_names)
|
||||
self.assertIn('Kansas', state_names)
|
||||
self.assertIn("Colorado", state_names)
|
||||
self.assertIn("Kansas", state_names)
|
||||
|
||||
# Saving another commonwealth w/a NULL geometry.
|
||||
nmi = State.objects.create(name='Northern Mariana Islands', poly=None)
|
||||
nmi = State.objects.create(name="Northern Mariana Islands", poly=None)
|
||||
self.assertIsNone(nmi.poly)
|
||||
|
||||
# Assigning a geometry and saving -- then UPDATE back to NULL.
|
||||
nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))'
|
||||
nmi.poly = "POLYGON((0 0,1 0,1 1,1 0,0 0))"
|
||||
nmi.save()
|
||||
State.objects.filter(name='Northern Mariana Islands').update(poly=None)
|
||||
self.assertIsNone(State.objects.get(name='Northern Mariana Islands').poly)
|
||||
State.objects.filter(name="Northern Mariana Islands").update(poly=None)
|
||||
self.assertIsNone(State.objects.get(name="Northern Mariana Islands").poly)
|
||||
|
||||
@skipUnlessDBFeature('supports_null_geometries', 'supports_crosses_lookup', 'supports_relate_lookup')
|
||||
@skipUnlessDBFeature(
|
||||
"supports_null_geometries", "supports_crosses_lookup", "supports_relate_lookup"
|
||||
)
|
||||
def test_null_geometries_excluded_in_lookups(self):
|
||||
"""NULL features are excluded in spatial lookup functions."""
|
||||
null = State.objects.create(name='NULL', poly=None)
|
||||
null = State.objects.create(name="NULL", poly=None)
|
||||
queries = [
|
||||
('equals', Point(1, 1)),
|
||||
('disjoint', Point(1, 1)),
|
||||
('touches', Point(1, 1)),
|
||||
('crosses', LineString((0, 0), (1, 1), (5, 5))),
|
||||
('within', Point(1, 1)),
|
||||
('overlaps', LineString((0, 0), (1, 1), (5, 5))),
|
||||
('contains', LineString((0, 0), (1, 1), (5, 5))),
|
||||
('intersects', LineString((0, 0), (1, 1), (5, 5))),
|
||||
('relate', (Point(1, 1), 'T*T***FF*')),
|
||||
('same_as', Point(1, 1)),
|
||||
('exact', Point(1, 1)),
|
||||
('coveredby', Point(1, 1)),
|
||||
('covers', Point(1, 1)),
|
||||
("equals", Point(1, 1)),
|
||||
("disjoint", Point(1, 1)),
|
||||
("touches", Point(1, 1)),
|
||||
("crosses", LineString((0, 0), (1, 1), (5, 5))),
|
||||
("within", Point(1, 1)),
|
||||
("overlaps", LineString((0, 0), (1, 1), (5, 5))),
|
||||
("contains", LineString((0, 0), (1, 1), (5, 5))),
|
||||
("intersects", LineString((0, 0), (1, 1), (5, 5))),
|
||||
("relate", (Point(1, 1), "T*T***FF*")),
|
||||
("same_as", Point(1, 1)),
|
||||
("exact", Point(1, 1)),
|
||||
("coveredby", Point(1, 1)),
|
||||
("covers", Point(1, 1)),
|
||||
]
|
||||
for lookup, geom in queries:
|
||||
with self.subTest(lookup=lookup):
|
||||
self.assertNotIn(null, State.objects.filter(**{'poly__%s' % lookup: geom}))
|
||||
self.assertNotIn(
|
||||
null, State.objects.filter(**{"poly__%s" % lookup: geom})
|
||||
)
|
||||
|
||||
def test_wkt_string_in_lookup(self):
|
||||
# Valid WKT strings don't emit error logs.
|
||||
with self.assertNoLogs('django.contrib.gis', 'ERROR'):
|
||||
State.objects.filter(poly__intersects='LINESTRING(0 0, 1 1, 5 5)')
|
||||
with self.assertNoLogs("django.contrib.gis", "ERROR"):
|
||||
State.objects.filter(poly__intersects="LINESTRING(0 0, 1 1, 5 5)")
|
||||
|
||||
@skipUnlessDBFeature("supports_relate_lookup")
|
||||
def test_relate_lookup(self):
|
||||
"Testing the 'relate' lookup type."
|
||||
# To make things more interesting, we will have our Texas reference point in
|
||||
# different SRIDs.
|
||||
pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
|
||||
pnt2 = fromstr('POINT(-98.4919715741052 29.4333344025053)', srid=4326)
|
||||
pnt1 = fromstr("POINT (649287.0363174 4177429.4494686)", srid=2847)
|
||||
pnt2 = fromstr("POINT(-98.4919715741052 29.4333344025053)", srid=4326)
|
||||
|
||||
# Not passing in a geometry as first param raises a TypeError when
|
||||
# initializing the QuerySet.
|
||||
with self.assertRaises(ValueError):
|
||||
Country.objects.filter(mpoly__relate=(23, 'foo'))
|
||||
Country.objects.filter(mpoly__relate=(23, "foo"))
|
||||
|
||||
# Making sure the right exception is raised for the given
|
||||
# bad arguments.
|
||||
for bad_args, e in [((pnt1, 0), ValueError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
|
||||
for bad_args, e in [
|
||||
((pnt1, 0), ValueError),
|
||||
((pnt2, "T*T***FF*", 0), ValueError),
|
||||
]:
|
||||
qs = Country.objects.filter(mpoly__relate=bad_args)
|
||||
with self.assertRaises(e):
|
||||
qs.count()
|
||||
|
||||
contains_mask = 'T*T***FF*'
|
||||
within_mask = 'T*F**F***'
|
||||
intersects_mask = 'T********'
|
||||
contains_mask = "T*T***FF*"
|
||||
within_mask = "T*F**F***"
|
||||
intersects_mask = "T********"
|
||||
# Relate works differently on Oracle.
|
||||
if connection.ops.oracle:
|
||||
contains_mask = 'contains'
|
||||
within_mask = 'inside'
|
||||
contains_mask = "contains"
|
||||
within_mask = "inside"
|
||||
# TODO: This is not quite the same as the PostGIS mask above
|
||||
intersects_mask = 'overlapbdyintersect'
|
||||
intersects_mask = "overlapbdyintersect"
|
||||
|
||||
# Testing contains relation mask.
|
||||
if connection.features.supports_transform:
|
||||
self.assertEqual(
|
||||
Country.objects.get(mpoly__relate=(pnt1, contains_mask)).name,
|
||||
'Texas',
|
||||
"Texas",
|
||||
)
|
||||
self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, contains_mask)).name)
|
||||
self.assertEqual(
|
||||
"Texas", Country.objects.get(mpoly__relate=(pnt2, contains_mask)).name
|
||||
)
|
||||
|
||||
# Testing within relation mask.
|
||||
ks = State.objects.get(name='Kansas')
|
||||
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
|
||||
ks = State.objects.get(name="Kansas")
|
||||
self.assertEqual(
|
||||
"Lawrence", City.objects.get(point__relate=(ks.poly, within_mask)).name
|
||||
)
|
||||
|
||||
# Testing intersection relation mask.
|
||||
if not connection.ops.oracle:
|
||||
if connection.features.supports_transform:
|
||||
self.assertEqual(
|
||||
Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name,
|
||||
'Texas',
|
||||
"Texas",
|
||||
)
|
||||
self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name)
|
||||
self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
|
||||
self.assertEqual(
|
||||
"Texas", Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name
|
||||
)
|
||||
self.assertEqual(
|
||||
"Lawrence",
|
||||
City.objects.get(point__relate=(ks.poly, intersects_mask)).name,
|
||||
)
|
||||
|
||||
# With a complex geometry expression
|
||||
mask = 'anyinteract' if connection.ops.oracle else within_mask
|
||||
self.assertFalse(City.objects.exclude(point__relate=(functions.Union('point', 'point'), mask)))
|
||||
mask = "anyinteract" if connection.ops.oracle else within_mask
|
||||
self.assertFalse(
|
||||
City.objects.exclude(
|
||||
point__relate=(functions.Union("point", "point"), mask)
|
||||
)
|
||||
)
|
||||
|
||||
def test_gis_lookups_with_complex_expressions(self):
|
||||
multiple_arg_lookups = {'dwithin', 'relate'} # These lookups are tested elsewhere.
|
||||
multiple_arg_lookups = {
|
||||
"dwithin",
|
||||
"relate",
|
||||
} # These lookups are tested elsewhere.
|
||||
lookups = connection.ops.gis_operators.keys() - multiple_arg_lookups
|
||||
self.assertTrue(lookups, 'No lookups found')
|
||||
self.assertTrue(lookups, "No lookups found")
|
||||
for lookup in lookups:
|
||||
with self.subTest(lookup):
|
||||
City.objects.filter(**{'point__' + lookup: functions.Union('point', 'point')}).exists()
|
||||
City.objects.filter(
|
||||
**{"point__" + lookup: functions.Union("point", "point")}
|
||||
).exists()
|
||||
|
||||
def test_subquery_annotation(self):
|
||||
multifields = MultiFields.objects.create(
|
||||
@@ -501,18 +576,20 @@ class GeoLookupTest(TestCase):
|
||||
poly=Polygon.from_bbox((0, 0, 2, 2)),
|
||||
)
|
||||
qs = MultiFields.objects.annotate(
|
||||
city_point=Subquery(City.objects.filter(
|
||||
id=OuterRef('city'),
|
||||
).values('point')),
|
||||
city_point=Subquery(
|
||||
City.objects.filter(
|
||||
id=OuterRef("city"),
|
||||
).values("point")
|
||||
),
|
||||
).filter(
|
||||
city_point__within=F('poly'),
|
||||
city_point__within=F("poly"),
|
||||
)
|
||||
self.assertEqual(qs.get(), multifields)
|
||||
|
||||
|
||||
class GeoQuerySetTest(TestCase):
|
||||
# TODO: GeoQuerySet is removed, organize these test better.
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test_extent(self):
|
||||
@@ -522,21 +599,30 @@ class GeoQuerySetTest(TestCase):
|
||||
# Reference query:
|
||||
# `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
|
||||
# => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
|
||||
expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
|
||||
expected = (
|
||||
-96.8016128540039,
|
||||
29.7633724212646,
|
||||
-95.3631439208984,
|
||||
32.782058715820,
|
||||
)
|
||||
|
||||
qs = City.objects.filter(name__in=('Houston', 'Dallas'))
|
||||
extent = qs.aggregate(Extent('point'))['point__extent']
|
||||
qs = City.objects.filter(name__in=("Houston", "Dallas"))
|
||||
extent = qs.aggregate(Extent("point"))["point__extent"]
|
||||
for val, exp in zip(extent, expected):
|
||||
self.assertAlmostEqual(exp, val, 4)
|
||||
self.assertIsNone(City.objects.filter(name=('Smalltown')).aggregate(Extent('point'))['point__extent'])
|
||||
self.assertIsNone(
|
||||
City.objects.filter(name=("Smalltown")).aggregate(Extent("point"))[
|
||||
"point__extent"
|
||||
]
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test_extent_with_limit(self):
|
||||
"""
|
||||
Testing if extent supports limit.
|
||||
"""
|
||||
extent1 = City.objects.all().aggregate(Extent('point'))['point__extent']
|
||||
extent2 = City.objects.all()[:3].aggregate(Extent('point'))['point__extent']
|
||||
extent1 = City.objects.all().aggregate(Extent("point"))["point__extent"]
|
||||
extent2 = City.objects.all()[:3].aggregate(Extent("point"))["point__extent"]
|
||||
self.assertNotEqual(extent1, extent2)
|
||||
|
||||
def test_make_line(self):
|
||||
@@ -545,90 +631,94 @@ class GeoQuerySetTest(TestCase):
|
||||
"""
|
||||
if not connection.features.supports_make_line_aggr:
|
||||
with self.assertRaises(NotSupportedError):
|
||||
City.objects.all().aggregate(MakeLine('point'))
|
||||
City.objects.all().aggregate(MakeLine("point"))
|
||||
return
|
||||
|
||||
# MakeLine on an inappropriate field returns simply None
|
||||
self.assertIsNone(State.objects.aggregate(MakeLine('poly'))['poly__makeline'])
|
||||
self.assertIsNone(State.objects.aggregate(MakeLine("poly"))["poly__makeline"])
|
||||
# Reference query:
|
||||
# SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city;
|
||||
ref_line = GEOSGeometry(
|
||||
'LINESTRING(-95.363151 29.763374,-96.801611 32.782057,'
|
||||
'-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,'
|
||||
'-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)',
|
||||
srid=4326
|
||||
"LINESTRING(-95.363151 29.763374,-96.801611 32.782057,"
|
||||
"-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,"
|
||||
"-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)",
|
||||
srid=4326,
|
||||
)
|
||||
# We check for equality with a tolerance of 10e-5 which is a lower bound
|
||||
# of the precisions of ref_line coordinates
|
||||
line = City.objects.aggregate(MakeLine('point'))['point__makeline']
|
||||
line = City.objects.aggregate(MakeLine("point"))["point__makeline"]
|
||||
self.assertTrue(
|
||||
ref_line.equals_exact(line, tolerance=10e-5),
|
||||
"%s != %s" % (ref_line, line)
|
||||
ref_line.equals_exact(line, tolerance=10e-5), "%s != %s" % (ref_line, line)
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('supports_union_aggr')
|
||||
@skipUnlessDBFeature("supports_union_aggr")
|
||||
def test_unionagg(self):
|
||||
"""
|
||||
Testing the `Union` aggregate.
|
||||
"""
|
||||
tx = Country.objects.get(name='Texas').mpoly
|
||||
tx = Country.objects.get(name="Texas").mpoly
|
||||
# Houston, Dallas -- Ordering may differ depending on backend or GEOS version.
|
||||
union = GEOSGeometry('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
|
||||
union = GEOSGeometry("MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)")
|
||||
qs = City.objects.filter(point__within=tx)
|
||||
with self.assertRaises(ValueError):
|
||||
qs.aggregate(Union('name'))
|
||||
qs.aggregate(Union("name"))
|
||||
# Using `field_name` keyword argument in one query and specifying an
|
||||
# order in the other (which should not be used because this is
|
||||
# an aggregate method on a spatial column)
|
||||
u1 = qs.aggregate(Union('point'))['point__union']
|
||||
u2 = qs.order_by('name').aggregate(Union('point'))['point__union']
|
||||
u1 = qs.aggregate(Union("point"))["point__union"]
|
||||
u2 = qs.order_by("name").aggregate(Union("point"))["point__union"]
|
||||
self.assertTrue(union.equals(u1))
|
||||
self.assertTrue(union.equals(u2))
|
||||
qs = City.objects.filter(name='NotACity')
|
||||
self.assertIsNone(qs.aggregate(Union('point'))['point__union'])
|
||||
qs = City.objects.filter(name="NotACity")
|
||||
self.assertIsNone(qs.aggregate(Union("point"))["point__union"])
|
||||
|
||||
@skipUnlessDBFeature('supports_union_aggr')
|
||||
@skipUnlessDBFeature("supports_union_aggr")
|
||||
def test_geoagg_subquery(self):
|
||||
tx = Country.objects.get(name='Texas')
|
||||
union = GEOSGeometry('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
|
||||
tx = Country.objects.get(name="Texas")
|
||||
union = GEOSGeometry("MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)")
|
||||
# Use distinct() to force the usage of a subquery for aggregation.
|
||||
with CaptureQueriesContext(connection) as ctx:
|
||||
self.assertIs(union.equals(
|
||||
City.objects.filter(point__within=tx.mpoly).distinct().aggregate(
|
||||
Union('point'),
|
||||
)['point__union'],
|
||||
), True)
|
||||
self.assertIn('subquery', ctx.captured_queries[0]['sql'])
|
||||
self.assertIs(
|
||||
union.equals(
|
||||
City.objects.filter(point__within=tx.mpoly)
|
||||
.distinct()
|
||||
.aggregate(
|
||||
Union("point"),
|
||||
)["point__union"],
|
||||
),
|
||||
True,
|
||||
)
|
||||
self.assertIn("subquery", ctx.captured_queries[0]["sql"])
|
||||
|
||||
@skipUnlessDBFeature('supports_tolerance_parameter')
|
||||
@skipUnlessDBFeature("supports_tolerance_parameter")
|
||||
def test_unionagg_tolerance(self):
|
||||
City.objects.create(
|
||||
point=fromstr('POINT(-96.467222 32.751389)', srid=4326),
|
||||
name='Forney',
|
||||
point=fromstr("POINT(-96.467222 32.751389)", srid=4326),
|
||||
name="Forney",
|
||||
)
|
||||
tx = Country.objects.get(name='Texas').mpoly
|
||||
tx = Country.objects.get(name="Texas").mpoly
|
||||
# Tolerance is greater than distance between Forney and Dallas, that's
|
||||
# why Dallas is ignored.
|
||||
forney_houston = GEOSGeometry(
|
||||
'MULTIPOINT(-95.363151 29.763374, -96.467222 32.751389)',
|
||||
"MULTIPOINT(-95.363151 29.763374, -96.467222 32.751389)",
|
||||
srid=4326,
|
||||
)
|
||||
self.assertIs(
|
||||
forney_houston.equals_exact(
|
||||
City.objects.filter(point__within=tx).aggregate(
|
||||
Union('point', tolerance=32000),
|
||||
)['point__union'],
|
||||
Union("point", tolerance=32000),
|
||||
)["point__union"],
|
||||
tolerance=10e-6,
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('supports_tolerance_parameter')
|
||||
@skipUnlessDBFeature("supports_tolerance_parameter")
|
||||
def test_unionagg_tolerance_escaping(self):
|
||||
tx = Country.objects.get(name='Texas').mpoly
|
||||
tx = Country.objects.get(name="Texas").mpoly
|
||||
with self.assertRaises(DatabaseError):
|
||||
City.objects.filter(point__within=tx).aggregate(
|
||||
Union('point', tolerance='0.05))), (((1'),
|
||||
Union("point", tolerance="0.05))), (((1"),
|
||||
)
|
||||
|
||||
def test_within_subquery(self):
|
||||
@@ -637,13 +727,16 @@ class GeoQuerySetTest(TestCase):
|
||||
(#14483).
|
||||
"""
|
||||
tex_cities = City.objects.filter(
|
||||
point__within=Country.objects.filter(name='Texas').values('mpoly')).order_by('name')
|
||||
self.assertEqual(list(tex_cities.values_list('name', flat=True)), ['Dallas', 'Houston'])
|
||||
point__within=Country.objects.filter(name="Texas").values("mpoly")
|
||||
).order_by("name")
|
||||
self.assertEqual(
|
||||
list(tex_cities.values_list("name", flat=True)), ["Dallas", "Houston"]
|
||||
)
|
||||
|
||||
def test_non_concrete_field(self):
|
||||
NonConcreteModel.objects.create(point=Point(0, 0), name='name')
|
||||
NonConcreteModel.objects.create(point=Point(0, 0), name="name")
|
||||
list(NonConcreteModel.objects.all())
|
||||
|
||||
def test_values_srid(self):
|
||||
for c, v in zip(City.objects.all(), City.objects.values()):
|
||||
self.assertEqual(c.point.srid, v['point'].srid)
|
||||
self.assertEqual(c.point.srid, v["point"].srid)
|
||||
|
||||
@@ -7,20 +7,22 @@ from .feeds import feed_dict
|
||||
from .sitemaps import sitemaps
|
||||
|
||||
urlpatterns = [
|
||||
path('feeds/<path:url>/', gis_views.feed, {'feed_dict': feed_dict}),
|
||||
path("feeds/<path:url>/", gis_views.feed, {"feed_dict": feed_dict}),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
path('sitemaps/<section>.xml', sitemap_views.sitemap, {'sitemaps': sitemaps}),
|
||||
path("sitemaps/<section>.xml", sitemap_views.sitemap, {"sitemaps": sitemaps}),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
path(
|
||||
'sitemaps/kml/<label>/<model>/<field_name>.kml',
|
||||
"sitemaps/kml/<label>/<model>/<field_name>.kml",
|
||||
gis_sitemap_views.kml,
|
||||
name='django.contrib.gis.sitemaps.views.kml'),
|
||||
name="django.contrib.gis.sitemaps.views.kml",
|
||||
),
|
||||
path(
|
||||
'sitemaps/kml/<label>/<model>/<field_name>.kmz',
|
||||
"sitemaps/kml/<label>/<model>/<field_name>.kmz",
|
||||
gis_sitemap_views.kmz,
|
||||
name='django.contrib.gis.sitemaps.views.kmz'),
|
||||
name="django.contrib.gis.sitemaps.views.kmz",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -15,7 +15,7 @@ class City(NamedModel):
|
||||
point = models.PointField(geography=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'geogapp'
|
||||
app_label = "geogapp"
|
||||
|
||||
|
||||
class Zipcode(NamedModel):
|
||||
@@ -28,7 +28,7 @@ class County(NamedModel):
|
||||
mpoly = models.MultiPolygonField(geography=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'geogapp'
|
||||
app_label = "geogapp"
|
||||
|
||||
def __str__(self):
|
||||
return ' County, '.join([self.name, self.state])
|
||||
return " County, ".join([self.name, self.state])
|
||||
|
||||
@@ -15,7 +15,7 @@ from .models import City, County, Zipcode
|
||||
|
||||
|
||||
class GeographyTest(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test01_fixture_load(self):
|
||||
"Ensure geography features loaded properly."
|
||||
@@ -24,26 +24,28 @@ class GeographyTest(TestCase):
|
||||
@skipUnlessDBFeature("supports_distances_lookups", "supports_distance_geodetic")
|
||||
def test02_distance_lookup(self):
|
||||
"Testing distance lookup support on non-point geography fields."
|
||||
z = Zipcode.objects.get(code='77002')
|
||||
cities1 = list(City.objects
|
||||
.filter(point__distance_lte=(z.poly, D(mi=500)))
|
||||
.order_by('name')
|
||||
.values_list('name', flat=True))
|
||||
cities2 = list(City.objects
|
||||
.filter(point__dwithin=(z.poly, D(mi=500)))
|
||||
.order_by('name')
|
||||
.values_list('name', flat=True))
|
||||
z = Zipcode.objects.get(code="77002")
|
||||
cities1 = list(
|
||||
City.objects.filter(point__distance_lte=(z.poly, D(mi=500)))
|
||||
.order_by("name")
|
||||
.values_list("name", flat=True)
|
||||
)
|
||||
cities2 = list(
|
||||
City.objects.filter(point__dwithin=(z.poly, D(mi=500)))
|
||||
.order_by("name")
|
||||
.values_list("name", flat=True)
|
||||
)
|
||||
for cities in [cities1, cities2]:
|
||||
self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
|
||||
self.assertEqual(["Dallas", "Houston", "Oklahoma City"], cities)
|
||||
|
||||
def test04_invalid_operators_functions(self):
|
||||
"Ensuring exceptions are raised for operators & functions invalid on geography fields."
|
||||
if not connection.ops.postgis:
|
||||
self.skipTest('This is a PostGIS-specific test.')
|
||||
self.skipTest("This is a PostGIS-specific test.")
|
||||
# Only a subset of the geometry functions & operator are available
|
||||
# to PostGIS geography types. For more information, visit:
|
||||
# http://postgis.refractions.net/documentation/manual-1.5/ch08.html#PostGIS_GeographyFunctions
|
||||
z = Zipcode.objects.get(code='77002')
|
||||
z = Zipcode.objects.get(code="77002")
|
||||
# ST_Within not available.
|
||||
with self.assertRaises(ValueError):
|
||||
City.objects.filter(point__within=z.poly).count()
|
||||
@@ -52,7 +54,7 @@ class GeographyTest(TestCase):
|
||||
City.objects.filter(point__contained=z.poly).count()
|
||||
|
||||
# Regression test for #14060, `~=` was never really implemented for PostGIS.
|
||||
htown = City.objects.get(name='Houston')
|
||||
htown = City.objects.get(name="Houston")
|
||||
with self.assertRaises(ValueError):
|
||||
City.objects.get(point__exact=htown.point)
|
||||
|
||||
@@ -63,22 +65,26 @@ class GeographyTest(TestCase):
|
||||
from django.contrib.gis.utils import LayerMapping
|
||||
|
||||
# Getting the shapefile and mapping dictionary.
|
||||
shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
|
||||
co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
|
||||
shp_path = os.path.realpath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "data")
|
||||
)
|
||||
co_shp = os.path.join(shp_path, "counties", "counties.shp")
|
||||
co_mapping = {
|
||||
'name': 'Name',
|
||||
'state': 'State',
|
||||
'mpoly': 'MULTIPOLYGON',
|
||||
"name": "Name",
|
||||
"state": "State",
|
||||
"mpoly": "MULTIPOLYGON",
|
||||
}
|
||||
# Reference county names, number of polygons, and state names.
|
||||
names = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
||||
names = ["Bexar", "Galveston", "Harris", "Honolulu", "Pueblo"]
|
||||
num_polys = [1, 2, 1, 19, 1] # Number of polygons for each.
|
||||
st_names = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
|
||||
st_names = ["Texas", "Texas", "Texas", "Hawaii", "Colorado"]
|
||||
|
||||
lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269, unique='name')
|
||||
lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269, unique="name")
|
||||
lm.save(silent=True, strict=True)
|
||||
|
||||
for c, name, num_poly, state in zip(County.objects.order_by('name'), names, num_polys, st_names):
|
||||
for c, name, num_poly, state in zip(
|
||||
County.objects.order_by("name"), names, num_polys, st_names
|
||||
):
|
||||
self.assertEqual(4326, c.mpoly.srid)
|
||||
self.assertEqual(num_poly, len(c.mpoly))
|
||||
self.assertEqual(name, c.name)
|
||||
@@ -86,7 +92,7 @@ class GeographyTest(TestCase):
|
||||
|
||||
|
||||
class GeographyFunctionTests(FuncTestMixin, TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
@skipUnlessDBFeature("supports_extent_aggr")
|
||||
def test_cast_aggregate(self):
|
||||
@@ -96,11 +102,16 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
|
||||
"""
|
||||
if not connection.features.supports_geography:
|
||||
self.skipTest("This test needs geography support")
|
||||
expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
|
||||
res = City.objects.filter(
|
||||
name__in=('Houston', 'Dallas')
|
||||
).aggregate(extent=models.Extent(Cast('point', models.PointField())))
|
||||
for val, exp in zip(res['extent'], expected):
|
||||
expected = (
|
||||
-96.8016128540039,
|
||||
29.7633724212646,
|
||||
-95.3631439208984,
|
||||
32.782058715820,
|
||||
)
|
||||
res = City.objects.filter(name__in=("Houston", "Dallas")).aggregate(
|
||||
extent=models.Extent(Cast("point", models.PointField()))
|
||||
)
|
||||
for val, exp in zip(res["extent"], expected):
|
||||
self.assertAlmostEqual(exp, val, 4)
|
||||
|
||||
@skipUnlessDBFeature("has_Distance_function", "supports_distance_geodetic")
|
||||
@@ -119,10 +130,10 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
|
||||
ref_dists = [0, 4899.68, 8081.30, 9115.15]
|
||||
else:
|
||||
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),
|
||||
distance2=Distance(htown.point, 'poly'),
|
||||
distance=Distance("poly", htown.point),
|
||||
distance2=Distance(htown.point, "poly"),
|
||||
)
|
||||
for z, ref in zip(qs, ref_dists):
|
||||
self.assertAlmostEqual(z.distance.m, ref, 2)
|
||||
@@ -135,7 +146,7 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
|
||||
|
||||
if not connection.ops.spatialite:
|
||||
# Distance function combined with a lookup.
|
||||
hzip = Zipcode.objects.get(code='77002')
|
||||
hzip = Zipcode.objects.get(code="77002")
|
||||
self.assertEqual(qs.get(distance__lte=0), hzip)
|
||||
|
||||
@skipUnlessDBFeature("has_Area_function", "supports_area_geodetic")
|
||||
@@ -144,7 +155,7 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
|
||||
Testing that Area calculations work on geography columns.
|
||||
"""
|
||||
# SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002';
|
||||
z = Zipcode.objects.annotate(area=Area('poly')).get(code='77002')
|
||||
z = Zipcode.objects.annotate(area=Area("poly")).get(code="77002")
|
||||
# Round to the nearest thousand as possible values (depending on
|
||||
# the database and geolib) include 5439084, 5439100, 5439101.
|
||||
rounded_value = z.area.sq_m
|
||||
@@ -154,5 +165,7 @@ class GeographyFunctionTests(FuncTestMixin, TestCase):
|
||||
@skipUnlessDBFeature("has_Area_function")
|
||||
@skipIfDBFeature("supports_area_geodetic")
|
||||
def test_geodetic_area_raises_if_not_supported(self):
|
||||
with self.assertRaisesMessage(NotSupportedError, 'Area on geodetic coordinate systems not supported.'):
|
||||
Zipcode.objects.annotate(area=Area('poly')).get(code='77002')
|
||||
with self.assertRaisesMessage(
|
||||
NotSupportedError, "Area on geodetic coordinate systems not supported."
|
||||
):
|
||||
Zipcode.objects.annotate(area=Area("poly")).get(code="77002")
|
||||
|
||||
@@ -3,14 +3,13 @@ from django.test import SimpleTestCase
|
||||
|
||||
|
||||
class GEOSCoordSeqTest(SimpleTestCase):
|
||||
|
||||
def test_getitem(self):
|
||||
coord_seq = LineString([(x, x) for x in range(2)]).coord_seq
|
||||
for i in (0, 1):
|
||||
with self.subTest(i):
|
||||
self.assertEqual(coord_seq[i], (i, i))
|
||||
for i in (-3, 10):
|
||||
msg = 'invalid GEOS Geometry index: %s' % i
|
||||
msg = "invalid GEOS Geometry index: %s" % i
|
||||
with self.subTest(i):
|
||||
with self.assertRaisesMessage(IndexError, msg):
|
||||
coord_seq[i]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,12 @@
|
||||
import unittest
|
||||
|
||||
from django.contrib.gis.geos import (
|
||||
LinearRing, LineString, MultiPoint, Point, Polygon, fromstr,
|
||||
LinearRing,
|
||||
LineString,
|
||||
MultiPoint,
|
||||
Point,
|
||||
Polygon,
|
||||
fromstr,
|
||||
)
|
||||
|
||||
|
||||
@@ -66,8 +71,9 @@ def api_get_length(x):
|
||||
|
||||
|
||||
geos_function_tests = [
|
||||
val for name, val in vars().items()
|
||||
if hasattr(val, '__call__') and name.startswith('api_get_')
|
||||
val
|
||||
for name, val in vars().items()
|
||||
if hasattr(val, "__call__") and name.startswith("api_get_")
|
||||
]
|
||||
|
||||
|
||||
@@ -78,7 +84,7 @@ class GEOSMutationTest(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def test00_GEOSIndexException(self):
|
||||
'Testing Geometry IndexError'
|
||||
"Testing Geometry IndexError"
|
||||
p = Point(1, 2)
|
||||
for i in range(-2, 2):
|
||||
p._checkindex(i)
|
||||
@@ -88,87 +94,124 @@ class GEOSMutationTest(unittest.TestCase):
|
||||
p._checkindex(-3)
|
||||
|
||||
def test01_PointMutations(self):
|
||||
'Testing Point mutations'
|
||||
for p in (Point(1, 2, 3), fromstr('POINT (1 2 3)')):
|
||||
self.assertEqual(p._get_single_external(1), 2.0, 'Point _get_single_external')
|
||||
"Testing Point mutations"
|
||||
for p in (Point(1, 2, 3), fromstr("POINT (1 2 3)")):
|
||||
self.assertEqual(
|
||||
p._get_single_external(1), 2.0, "Point _get_single_external"
|
||||
)
|
||||
|
||||
# _set_single
|
||||
p._set_single(0, 100)
|
||||
self.assertEqual(p.coords, (100.0, 2.0, 3.0), 'Point _set_single')
|
||||
self.assertEqual(p.coords, (100.0, 2.0, 3.0), "Point _set_single")
|
||||
|
||||
# _set_list
|
||||
p._set_list(2, (50, 3141))
|
||||
self.assertEqual(p.coords, (50.0, 3141.0), 'Point _set_list')
|
||||
self.assertEqual(p.coords, (50.0, 3141.0), "Point _set_list")
|
||||
|
||||
def test02_PointExceptions(self):
|
||||
'Testing Point exceptions'
|
||||
"Testing Point exceptions"
|
||||
with self.assertRaises(TypeError):
|
||||
Point(range(1))
|
||||
with self.assertRaises(TypeError):
|
||||
Point(range(4))
|
||||
|
||||
def test03_PointApi(self):
|
||||
'Testing Point API'
|
||||
"Testing Point API"
|
||||
q = Point(4, 5, 3)
|
||||
for p in (Point(1, 2, 3), fromstr('POINT (1 2 3)')):
|
||||
for p in (Point(1, 2, 3), fromstr("POINT (1 2 3)")):
|
||||
p[0:2] = [4, 5]
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(q), f(p), 'Point ' + f.__name__)
|
||||
self.assertEqual(f(q), f(p), "Point " + f.__name__)
|
||||
|
||||
def test04_LineStringMutations(self):
|
||||
'Testing LineString mutations'
|
||||
for ls in (LineString((1, 0), (4, 1), (6, -1)),
|
||||
fromstr('LINESTRING (1 0,4 1,6 -1)')):
|
||||
self.assertEqual(ls._get_single_external(1), (4.0, 1.0), 'LineString _get_single_external')
|
||||
"Testing LineString mutations"
|
||||
for ls in (
|
||||
LineString((1, 0), (4, 1), (6, -1)),
|
||||
fromstr("LINESTRING (1 0,4 1,6 -1)"),
|
||||
):
|
||||
self.assertEqual(
|
||||
ls._get_single_external(1),
|
||||
(4.0, 1.0),
|
||||
"LineString _get_single_external",
|
||||
)
|
||||
|
||||
# _set_single
|
||||
ls._set_single(0, (-50, 25))
|
||||
self.assertEqual(ls.coords, ((-50.0, 25.0), (4.0, 1.0), (6.0, -1.0)), 'LineString _set_single')
|
||||
self.assertEqual(
|
||||
ls.coords,
|
||||
((-50.0, 25.0), (4.0, 1.0), (6.0, -1.0)),
|
||||
"LineString _set_single",
|
||||
)
|
||||
|
||||
# _set_list
|
||||
ls._set_list(2, ((-50.0, 25.0), (6.0, -1.0)))
|
||||
self.assertEqual(ls.coords, ((-50.0, 25.0), (6.0, -1.0)), 'LineString _set_list')
|
||||
self.assertEqual(
|
||||
ls.coords, ((-50.0, 25.0), (6.0, -1.0)), "LineString _set_list"
|
||||
)
|
||||
|
||||
lsa = LineString(ls.coords)
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(lsa), f(ls), 'LineString ' + f.__name__)
|
||||
self.assertEqual(f(lsa), f(ls), "LineString " + f.__name__)
|
||||
|
||||
def test05_Polygon(self):
|
||||
'Testing Polygon mutations'
|
||||
for pg in (Polygon(((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
|
||||
((5, 4), (6, 4), (6, 3), (5, 4))),
|
||||
fromstr('POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))')):
|
||||
self.assertEqual(pg._get_single_external(0),
|
||||
LinearRing((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
|
||||
'Polygon _get_single_external(0)')
|
||||
self.assertEqual(pg._get_single_external(1),
|
||||
LinearRing((5, 4), (6, 4), (6, 3), (5, 4)),
|
||||
'Polygon _get_single_external(1)')
|
||||
"Testing Polygon mutations"
|
||||
for pg in (
|
||||
Polygon(
|
||||
((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
|
||||
((5, 4), (6, 4), (6, 3), (5, 4)),
|
||||
),
|
||||
fromstr("POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))"),
|
||||
):
|
||||
self.assertEqual(
|
||||
pg._get_single_external(0),
|
||||
LinearRing((1, 0), (4, 1), (6, -1), (8, 10), (1, 0)),
|
||||
"Polygon _get_single_external(0)",
|
||||
)
|
||||
self.assertEqual(
|
||||
pg._get_single_external(1),
|
||||
LinearRing((5, 4), (6, 4), (6, 3), (5, 4)),
|
||||
"Polygon _get_single_external(1)",
|
||||
)
|
||||
|
||||
# _set_list
|
||||
pg._set_list(2, (((1, 2), (10, 0), (12, 9), (-1, 15), (1, 2)), ((4, 2), (5, 2), (5, 3), (4, 2))))
|
||||
pg._set_list(
|
||||
2,
|
||||
(
|
||||
((1, 2), (10, 0), (12, 9), (-1, 15), (1, 2)),
|
||||
((4, 2), (5, 2), (5, 3), (4, 2)),
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
pg.coords,
|
||||
(((1.0, 2.0), (10.0, 0.0), (12.0, 9.0), (-1.0, 15.0), (1.0, 2.0)),
|
||||
((4.0, 2.0), (5.0, 2.0), (5.0, 3.0), (4.0, 2.0))),
|
||||
'Polygon _set_list')
|
||||
(
|
||||
((1.0, 2.0), (10.0, 0.0), (12.0, 9.0), (-1.0, 15.0), (1.0, 2.0)),
|
||||
((4.0, 2.0), (5.0, 2.0), (5.0, 3.0), (4.0, 2.0)),
|
||||
),
|
||||
"Polygon _set_list",
|
||||
)
|
||||
|
||||
lsa = Polygon(*pg.coords)
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(lsa), f(pg), 'Polygon ' + f.__name__)
|
||||
self.assertEqual(f(lsa), f(pg), "Polygon " + f.__name__)
|
||||
|
||||
def test06_Collection(self):
|
||||
'Testing Collection mutations'
|
||||
"Testing Collection mutations"
|
||||
points = (
|
||||
MultiPoint(*map(Point, ((3, 4), (-1, 2), (5, -4), (2, 8)))),
|
||||
fromstr('MULTIPOINT (3 4,-1 2,5 -4,2 8)'),
|
||||
fromstr("MULTIPOINT (3 4,-1 2,5 -4,2 8)"),
|
||||
)
|
||||
for mp in points:
|
||||
self.assertEqual(mp._get_single_external(2), Point(5, -4), 'Collection _get_single_external')
|
||||
self.assertEqual(
|
||||
mp._get_single_external(2),
|
||||
Point(5, -4),
|
||||
"Collection _get_single_external",
|
||||
)
|
||||
|
||||
mp._set_list(3, map(Point, ((5, 5), (3, -2), (8, 1))))
|
||||
self.assertEqual(mp.coords, ((5.0, 5.0), (3.0, -2.0), (8.0, 1.0)), 'Collection _set_list')
|
||||
self.assertEqual(
|
||||
mp.coords, ((5.0, 5.0), (3.0, -2.0), (8.0, 1.0)), "Collection _set_list"
|
||||
)
|
||||
|
||||
lsa = MultiPoint(*map(Point, ((5, 5), (3, -2), (8, 1))))
|
||||
for f in geos_function_tests:
|
||||
self.assertEqual(f(lsa), f(mp), 'MultiPoint ' + f.__name__)
|
||||
self.assertEqual(f(lsa), f(mp), "MultiPoint " + f.__name__)
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import binascii
|
||||
|
||||
from django.contrib.gis.geos import (
|
||||
GEOSGeometry, Point, Polygon, WKBReader, WKBWriter, WKTReader, WKTWriter,
|
||||
GEOSGeometry,
|
||||
Point,
|
||||
Polygon,
|
||||
WKBReader,
|
||||
WKBWriter,
|
||||
WKTReader,
|
||||
WKTWriter,
|
||||
)
|
||||
from django.contrib.gis.geos.libgeos import geos_version_tuple
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
|
||||
class GEOSIOTest(SimpleTestCase):
|
||||
|
||||
def test01_wktreader(self):
|
||||
# Creating a WKTReader instance
|
||||
wkt_r = WKTReader()
|
||||
wkt = 'POINT (5 23)'
|
||||
wkt = "POINT (5 23)"
|
||||
|
||||
# read() should return a GEOSGeometry
|
||||
ref = GEOSGeometry(wkt)
|
||||
@@ -26,7 +31,7 @@ class GEOSIOTest(SimpleTestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
wkt_r.read(1)
|
||||
with self.assertRaises(TypeError):
|
||||
wkt_r.read(memoryview(b'foo'))
|
||||
wkt_r.read(memoryview(b"foo"))
|
||||
|
||||
def test02_wktwriter(self):
|
||||
# Creating a WKTWriter instance, testing its ptr property.
|
||||
@@ -34,24 +39,24 @@ class GEOSIOTest(SimpleTestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
wkt_w.ptr = WKTReader.ptr_type()
|
||||
|
||||
ref = GEOSGeometry('POINT (5 23)')
|
||||
ref_wkt = 'POINT (5.0000000000000000 23.0000000000000000)'
|
||||
ref = GEOSGeometry("POINT (5 23)")
|
||||
ref_wkt = "POINT (5.0000000000000000 23.0000000000000000)"
|
||||
self.assertEqual(ref_wkt, wkt_w.write(ref).decode())
|
||||
|
||||
def test_wktwriter_constructor_arguments(self):
|
||||
wkt_w = WKTWriter(dim=3, trim=True, precision=3)
|
||||
ref = GEOSGeometry('POINT (5.34562 23 1.5)')
|
||||
ref = GEOSGeometry("POINT (5.34562 23 1.5)")
|
||||
if geos_version_tuple() > (3, 10):
|
||||
ref_wkt = 'POINT Z (5.346 23 1.5)'
|
||||
ref_wkt = "POINT Z (5.346 23 1.5)"
|
||||
else:
|
||||
ref_wkt = 'POINT Z (5.35 23 1.5)'
|
||||
ref_wkt = "POINT Z (5.35 23 1.5)"
|
||||
self.assertEqual(ref_wkt, wkt_w.write(ref).decode())
|
||||
|
||||
def test03_wkbreader(self):
|
||||
# Creating a WKBReader instance
|
||||
wkb_r = WKBReader()
|
||||
|
||||
hex = b'000000000140140000000000004037000000000000'
|
||||
hex = b"000000000140140000000000004037000000000000"
|
||||
wkb = memoryview(binascii.a2b_hex(hex))
|
||||
ref = GEOSGeometry(hex)
|
||||
|
||||
@@ -72,17 +77,17 @@ class GEOSIOTest(SimpleTestCase):
|
||||
|
||||
# Representations of 'POINT (5 23)' in hex -- one normal and
|
||||
# the other with the byte order changed.
|
||||
g = GEOSGeometry('POINT (5 23)')
|
||||
hex1 = b'010100000000000000000014400000000000003740'
|
||||
g = GEOSGeometry("POINT (5 23)")
|
||||
hex1 = b"010100000000000000000014400000000000003740"
|
||||
wkb1 = memoryview(binascii.a2b_hex(hex1))
|
||||
hex2 = b'000000000140140000000000004037000000000000'
|
||||
hex2 = b"000000000140140000000000004037000000000000"
|
||||
wkb2 = memoryview(binascii.a2b_hex(hex2))
|
||||
|
||||
self.assertEqual(hex1, wkb_w.write_hex(g))
|
||||
self.assertEqual(wkb1, wkb_w.write(g))
|
||||
|
||||
# Ensuring bad byteorders are not accepted.
|
||||
for bad_byteorder in (-1, 2, 523, 'foo', None):
|
||||
for bad_byteorder in (-1, 2, 523, "foo", None):
|
||||
# Equivalent of `wkb_w.byteorder = bad_byteorder`
|
||||
with self.assertRaises(ValueError):
|
||||
wkb_w._set_byteorder(bad_byteorder)
|
||||
@@ -96,17 +101,21 @@ class GEOSIOTest(SimpleTestCase):
|
||||
wkb_w.byteorder = 1
|
||||
|
||||
# Now, trying out the 3D and SRID flags.
|
||||
g = GEOSGeometry('POINT (5 23 17)')
|
||||
g = GEOSGeometry("POINT (5 23 17)")
|
||||
g.srid = 4326
|
||||
|
||||
hex3d = b'0101000080000000000000144000000000000037400000000000003140'
|
||||
hex3d = b"0101000080000000000000144000000000000037400000000000003140"
|
||||
wkb3d = memoryview(binascii.a2b_hex(hex3d))
|
||||
hex3d_srid = b'01010000A0E6100000000000000000144000000000000037400000000000003140'
|
||||
hex3d_srid = (
|
||||
b"01010000A0E6100000000000000000144000000000000037400000000000003140"
|
||||
)
|
||||
wkb3d_srid = memoryview(binascii.a2b_hex(hex3d_srid))
|
||||
|
||||
# Ensuring bad output dimensions are not accepted
|
||||
for bad_outdim in (-1, 0, 1, 4, 423, 'foo', None):
|
||||
with self.assertRaisesMessage(ValueError, 'WKB output dimension must be 2 or 3'):
|
||||
for bad_outdim in (-1, 0, 1, 4, 423, "foo", None):
|
||||
with self.assertRaisesMessage(
|
||||
ValueError, "WKB output dimension must be 2 or 3"
|
||||
):
|
||||
wkb_w.outdim = bad_outdim
|
||||
|
||||
# Now setting the output dimensions to be 3
|
||||
@@ -123,53 +132,73 @@ class GEOSIOTest(SimpleTestCase):
|
||||
def test_wkt_writer_trim(self):
|
||||
wkt_w = WKTWriter()
|
||||
self.assertFalse(wkt_w.trim)
|
||||
self.assertEqual(wkt_w.write(Point(1, 1)), b'POINT (1.0000000000000000 1.0000000000000000)')
|
||||
self.assertEqual(
|
||||
wkt_w.write(Point(1, 1)), b"POINT (1.0000000000000000 1.0000000000000000)"
|
||||
)
|
||||
|
||||
wkt_w.trim = True
|
||||
self.assertTrue(wkt_w.trim)
|
||||
self.assertEqual(wkt_w.write(Point(1, 1)), b'POINT (1 1)')
|
||||
self.assertEqual(wkt_w.write(Point(1.1, 1)), b'POINT (1.1 1)')
|
||||
self.assertEqual(wkt_w.write(Point(1. / 3, 1)), b'POINT (0.3333333333333333 1)')
|
||||
self.assertEqual(wkt_w.write(Point(1, 1)), b"POINT (1 1)")
|
||||
self.assertEqual(wkt_w.write(Point(1.1, 1)), b"POINT (1.1 1)")
|
||||
self.assertEqual(
|
||||
wkt_w.write(Point(1.0 / 3, 1)), b"POINT (0.3333333333333333 1)"
|
||||
)
|
||||
|
||||
wkt_w.trim = False
|
||||
self.assertFalse(wkt_w.trim)
|
||||
self.assertEqual(wkt_w.write(Point(1, 1)), b'POINT (1.0000000000000000 1.0000000000000000)')
|
||||
self.assertEqual(
|
||||
wkt_w.write(Point(1, 1)), b"POINT (1.0000000000000000 1.0000000000000000)"
|
||||
)
|
||||
|
||||
def test_wkt_writer_precision(self):
|
||||
wkt_w = WKTWriter()
|
||||
self.assertIsNone(wkt_w.precision)
|
||||
self.assertEqual(wkt_w.write(Point(1. / 3, 2. / 3)), b'POINT (0.3333333333333333 0.6666666666666666)')
|
||||
self.assertEqual(
|
||||
wkt_w.write(Point(1.0 / 3, 2.0 / 3)),
|
||||
b"POINT (0.3333333333333333 0.6666666666666666)",
|
||||
)
|
||||
|
||||
wkt_w.precision = 1
|
||||
self.assertEqual(wkt_w.precision, 1)
|
||||
self.assertEqual(wkt_w.write(Point(1. / 3, 2. / 3)), b'POINT (0.3 0.7)')
|
||||
self.assertEqual(wkt_w.write(Point(1.0 / 3, 2.0 / 3)), b"POINT (0.3 0.7)")
|
||||
|
||||
wkt_w.precision = 0
|
||||
self.assertEqual(wkt_w.precision, 0)
|
||||
self.assertEqual(wkt_w.write(Point(1. / 3, 2. / 3)), b'POINT (0 1)')
|
||||
self.assertEqual(wkt_w.write(Point(1.0 / 3, 2.0 / 3)), b"POINT (0 1)")
|
||||
|
||||
wkt_w.precision = None
|
||||
self.assertIsNone(wkt_w.precision)
|
||||
self.assertEqual(wkt_w.write(Point(1. / 3, 2. / 3)), b'POINT (0.3333333333333333 0.6666666666666666)')
|
||||
self.assertEqual(
|
||||
wkt_w.write(Point(1.0 / 3, 2.0 / 3)),
|
||||
b"POINT (0.3333333333333333 0.6666666666666666)",
|
||||
)
|
||||
|
||||
with self.assertRaisesMessage(AttributeError, 'WKT output rounding precision must be '):
|
||||
wkt_w.precision = 'potato'
|
||||
with self.assertRaisesMessage(
|
||||
AttributeError, "WKT output rounding precision must be "
|
||||
):
|
||||
wkt_w.precision = "potato"
|
||||
|
||||
def test_empty_point_wkb(self):
|
||||
p = Point(srid=4326)
|
||||
wkb_w = WKBWriter()
|
||||
|
||||
wkb_w.srid = False
|
||||
with self.assertRaisesMessage(ValueError, 'Empty point is not representable in WKB.'):
|
||||
with self.assertRaisesMessage(
|
||||
ValueError, "Empty point is not representable in WKB."
|
||||
):
|
||||
wkb_w.write(p)
|
||||
with self.assertRaisesMessage(ValueError, 'Empty point is not representable in WKB.'):
|
||||
with self.assertRaisesMessage(
|
||||
ValueError, "Empty point is not representable in WKB."
|
||||
):
|
||||
wkb_w.write_hex(p)
|
||||
|
||||
wkb_w.srid = True
|
||||
for byteorder, hex in enumerate([
|
||||
b'0020000001000010E67FF80000000000007FF8000000000000',
|
||||
b'0101000020E6100000000000000000F87F000000000000F87F',
|
||||
]):
|
||||
for byteorder, hex in enumerate(
|
||||
[
|
||||
b"0020000001000010E67FF80000000000007FF8000000000000",
|
||||
b"0101000020E6100000000000000000F87F000000000000F87F",
|
||||
]
|
||||
):
|
||||
wkb_w.byteorder = byteorder
|
||||
self.assertEqual(wkb_w.write_hex(p), hex)
|
||||
self.assertEqual(GEOSGeometry(wkb_w.write_hex(p)), p)
|
||||
@@ -181,14 +210,18 @@ class GEOSIOTest(SimpleTestCase):
|
||||
p_no_srid = Polygon()
|
||||
wkb_w = WKBWriter()
|
||||
wkb_w.srid = True
|
||||
for byteorder, hexes in enumerate([
|
||||
(b'000000000300000000', b'0020000003000010E600000000'),
|
||||
(b'010300000000000000', b'0103000020E610000000000000'),
|
||||
]):
|
||||
for byteorder, hexes in enumerate(
|
||||
[
|
||||
(b"000000000300000000", b"0020000003000010E600000000"),
|
||||
(b"010300000000000000", b"0103000020E610000000000000"),
|
||||
]
|
||||
):
|
||||
wkb_w.byteorder = byteorder
|
||||
for srid, hex in enumerate(hexes):
|
||||
wkb_w.srid = srid
|
||||
self.assertEqual(wkb_w.write_hex(p), hex)
|
||||
self.assertEqual(GEOSGeometry(wkb_w.write_hex(p)), p if srid else p_no_srid)
|
||||
self.assertEqual(
|
||||
GEOSGeometry(wkb_w.write_hex(p)), p if srid else p_no_srid
|
||||
)
|
||||
self.assertEqual(wkb_w.write(p), memoryview(binascii.a2b_hex(hex)))
|
||||
self.assertEqual(GEOSGeometry(wkb_w.write(p)), p if srid else p_no_srid)
|
||||
|
||||
@@ -29,7 +29,7 @@ class UserListA(ListMixin):
|
||||
# this would work:
|
||||
# self._list = self._mytype(items)
|
||||
# but then we wouldn't be testing length parameter
|
||||
itemList = ['x'] * length
|
||||
itemList = ["x"] * length
|
||||
for i, v in enumerate(items):
|
||||
itemList[i] = v
|
||||
|
||||
@@ -59,6 +59,7 @@ class ListMixinTest(unittest.TestCase):
|
||||
Tests base class ListMixin by comparing a list clone which is
|
||||
a ListMixin subclass with a real Python list.
|
||||
"""
|
||||
|
||||
limit = 3
|
||||
listType = UserListA
|
||||
|
||||
@@ -75,57 +76,61 @@ class ListMixinTest(unittest.TestCase):
|
||||
return [*range(-1 - self.limit, 0), *range(1, 1 + self.limit)]
|
||||
|
||||
def test01_getslice(self):
|
||||
'Slice retrieval'
|
||||
"Slice retrieval"
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in self.limits_plus(1):
|
||||
self.assertEqual(pl[i:], ul[i:], 'slice [%d:]' % (i))
|
||||
self.assertEqual(pl[:i], ul[:i], 'slice [:%d]' % (i))
|
||||
self.assertEqual(pl[i:], ul[i:], "slice [%d:]" % (i))
|
||||
self.assertEqual(pl[:i], ul[:i], "slice [:%d]" % (i))
|
||||
|
||||
for j in self.limits_plus(1):
|
||||
self.assertEqual(pl[i:j], ul[i:j], 'slice [%d:%d]' % (i, j))
|
||||
self.assertEqual(pl[i:j], ul[i:j], "slice [%d:%d]" % (i, j))
|
||||
for k in self.step_range():
|
||||
self.assertEqual(pl[i:j:k], ul[i:j:k], 'slice [%d:%d:%d]' % (i, j, k))
|
||||
self.assertEqual(
|
||||
pl[i:j:k], ul[i:j:k], "slice [%d:%d:%d]" % (i, j, k)
|
||||
)
|
||||
|
||||
for k in self.step_range():
|
||||
self.assertEqual(pl[i::k], ul[i::k], 'slice [%d::%d]' % (i, k))
|
||||
self.assertEqual(pl[:i:k], ul[:i:k], 'slice [:%d:%d]' % (i, k))
|
||||
self.assertEqual(pl[i::k], ul[i::k], "slice [%d::%d]" % (i, k))
|
||||
self.assertEqual(pl[:i:k], ul[:i:k], "slice [:%d:%d]" % (i, k))
|
||||
|
||||
for k in self.step_range():
|
||||
self.assertEqual(pl[::k], ul[::k], 'slice [::%d]' % (k))
|
||||
self.assertEqual(pl[::k], ul[::k], "slice [::%d]" % (k))
|
||||
|
||||
def test02_setslice(self):
|
||||
'Slice assignment'
|
||||
"Slice assignment"
|
||||
|
||||
def setfcn(x, i, j, k, L):
|
||||
x[i:j:k] = range(L)
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
for slen in range(self.limit + 1):
|
||||
ssl = nextRange(slen)
|
||||
ul[:] = ssl
|
||||
pl[:] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [:]')
|
||||
self.assertEqual(pl, ul[:], "set slice [:]")
|
||||
|
||||
for i in self.limits_plus(1):
|
||||
ssl = nextRange(slen)
|
||||
ul[i:] = ssl
|
||||
pl[i:] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d:]' % (i))
|
||||
self.assertEqual(pl, ul[:], "set slice [%d:]" % (i))
|
||||
|
||||
ssl = nextRange(slen)
|
||||
ul[:i] = ssl
|
||||
pl[:i] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [:%d]' % (i))
|
||||
self.assertEqual(pl, ul[:], "set slice [:%d]" % (i))
|
||||
|
||||
for j in self.limits_plus(1):
|
||||
ssl = nextRange(slen)
|
||||
ul[i:j] = ssl
|
||||
pl[i:j] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d:%d]' % (i, j))
|
||||
self.assertEqual(pl, ul[:], "set slice [%d:%d]" % (i, j))
|
||||
|
||||
for k in self.step_range():
|
||||
ssl = nextRange(len(ul[i:j:k]))
|
||||
ul[i:j:k] = ssl
|
||||
pl[i:j:k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d:%d:%d]' % (i, j, k))
|
||||
self.assertEqual(pl, ul[:], "set slice [%d:%d:%d]" % (i, j, k))
|
||||
|
||||
sliceLen = len(ul[i:j:k])
|
||||
with self.assertRaises(ValueError):
|
||||
@@ -138,83 +143,86 @@ class ListMixinTest(unittest.TestCase):
|
||||
ssl = nextRange(len(ul[i::k]))
|
||||
ul[i::k] = ssl
|
||||
pl[i::k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [%d::%d]' % (i, k))
|
||||
self.assertEqual(pl, ul[:], "set slice [%d::%d]" % (i, k))
|
||||
|
||||
ssl = nextRange(len(ul[:i:k]))
|
||||
ul[:i:k] = ssl
|
||||
pl[:i:k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [:%d:%d]' % (i, k))
|
||||
self.assertEqual(pl, ul[:], "set slice [:%d:%d]" % (i, k))
|
||||
|
||||
for k in self.step_range():
|
||||
ssl = nextRange(len(ul[::k]))
|
||||
ul[::k] = ssl
|
||||
pl[::k] = ssl
|
||||
self.assertEqual(pl, ul[:], 'set slice [::%d]' % (k))
|
||||
self.assertEqual(pl, ul[:], "set slice [::%d]" % (k))
|
||||
|
||||
def test03_delslice(self):
|
||||
'Delete slice'
|
||||
"Delete slice"
|
||||
for Len in range(self.limit):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[:]
|
||||
del ul[:]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [:]')
|
||||
self.assertEqual(pl[:], ul[:], "del slice [:]")
|
||||
for i in range(-Len - 1, Len + 1):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i:]
|
||||
del ul[i:]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d:]' % (i))
|
||||
self.assertEqual(pl[:], ul[:], "del slice [%d:]" % (i))
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[:i]
|
||||
del ul[:i]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [:%d]' % (i))
|
||||
self.assertEqual(pl[:], ul[:], "del slice [:%d]" % (i))
|
||||
for j in range(-Len - 1, Len + 1):
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i:j]
|
||||
del ul[i:j]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i, j))
|
||||
self.assertEqual(pl[:], ul[:], "del slice [%d:%d]" % (i, j))
|
||||
for k in [*range(-Len - 1, 0), *range(1, Len)]:
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i:j:k]
|
||||
del ul[i:j:k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i, j, k))
|
||||
self.assertEqual(
|
||||
pl[:], ul[:], "del slice [%d:%d:%d]" % (i, j, k)
|
||||
)
|
||||
|
||||
for k in [*range(-Len - 1, 0), *range(1, Len)]:
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[:i:k]
|
||||
del ul[:i:k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [:%d:%d]' % (i, k))
|
||||
self.assertEqual(pl[:], ul[:], "del slice [:%d:%d]" % (i, k))
|
||||
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[i::k]
|
||||
del ul[i::k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i, k))
|
||||
self.assertEqual(pl[:], ul[:], "del slice [%d::%d]" % (i, k))
|
||||
|
||||
for k in [*range(-Len - 1, 0), *range(1, Len)]:
|
||||
pl, ul = self.lists_of_len(Len)
|
||||
del pl[::k]
|
||||
del ul[::k]
|
||||
self.assertEqual(pl[:], ul[:], 'del slice [::%d]' % (k))
|
||||
self.assertEqual(pl[:], ul[:], "del slice [::%d]" % (k))
|
||||
|
||||
def test04_get_set_del_single(self):
|
||||
'Get/set/delete single item'
|
||||
"Get/set/delete single item"
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in self.limits_plus(0):
|
||||
self.assertEqual(pl[i], ul[i], 'get single item [%d]' % i)
|
||||
self.assertEqual(pl[i], ul[i], "get single item [%d]" % i)
|
||||
|
||||
for i in self.limits_plus(0):
|
||||
pl, ul = self.lists_of_len()
|
||||
pl[i] = 100
|
||||
ul[i] = 100
|
||||
self.assertEqual(pl[:], ul[:], 'set single item [%d]' % i)
|
||||
self.assertEqual(pl[:], ul[:], "set single item [%d]" % i)
|
||||
|
||||
for i in self.limits_plus(0):
|
||||
pl, ul = self.lists_of_len()
|
||||
del pl[i]
|
||||
del ul[i]
|
||||
self.assertEqual(pl[:], ul[:], 'del single item [%d]' % i)
|
||||
self.assertEqual(pl[:], ul[:], "del single item [%d]" % i)
|
||||
|
||||
def test05_out_of_range_exceptions(self):
|
||||
'Out of range exceptions'
|
||||
"Out of range exceptions"
|
||||
|
||||
def setfcn(x, i):
|
||||
x[i] = 20
|
||||
|
||||
@@ -223,6 +231,7 @@ class ListMixinTest(unittest.TestCase):
|
||||
|
||||
def delfcn(x, i):
|
||||
del x[i]
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in (-1 - self.limit, self.limit):
|
||||
with self.assertRaises(IndexError): # 'set index %d' % i)
|
||||
@@ -233,39 +242,40 @@ class ListMixinTest(unittest.TestCase):
|
||||
delfcn(ul, i)
|
||||
|
||||
def test06_list_methods(self):
|
||||
'List methods'
|
||||
"List methods"
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.append(40)
|
||||
ul.append(40)
|
||||
self.assertEqual(pl[:], ul[:], 'append')
|
||||
self.assertEqual(pl[:], ul[:], "append")
|
||||
|
||||
pl.extend(range(50, 55))
|
||||
ul.extend(range(50, 55))
|
||||
self.assertEqual(pl[:], ul[:], 'extend')
|
||||
self.assertEqual(pl[:], ul[:], "extend")
|
||||
|
||||
pl.reverse()
|
||||
ul.reverse()
|
||||
self.assertEqual(pl[:], ul[:], 'reverse')
|
||||
self.assertEqual(pl[:], ul[:], "reverse")
|
||||
|
||||
for i in self.limits_plus(1):
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.insert(i, 50)
|
||||
ul.insert(i, 50)
|
||||
self.assertEqual(pl[:], ul[:], 'insert at %d' % i)
|
||||
self.assertEqual(pl[:], ul[:], "insert at %d" % i)
|
||||
|
||||
for i in self.limits_plus(0):
|
||||
pl, ul = self.lists_of_len()
|
||||
self.assertEqual(pl.pop(i), ul.pop(i), 'popped value at %d' % i)
|
||||
self.assertEqual(pl[:], ul[:], 'after pop at %d' % i)
|
||||
self.assertEqual(pl.pop(i), ul.pop(i), "popped value at %d" % i)
|
||||
self.assertEqual(pl[:], ul[:], "after pop at %d" % i)
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
self.assertEqual(pl.pop(), ul.pop(i), 'popped value')
|
||||
self.assertEqual(pl[:], ul[:], 'after pop')
|
||||
self.assertEqual(pl.pop(), ul.pop(i), "popped value")
|
||||
self.assertEqual(pl[:], ul[:], "after pop")
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
|
||||
def popfcn(x, i):
|
||||
x.pop(i)
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
popfcn(ul, self.limit)
|
||||
with self.assertRaises(IndexError):
|
||||
@@ -273,29 +283,30 @@ class ListMixinTest(unittest.TestCase):
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
for val in range(self.limit):
|
||||
self.assertEqual(pl.index(val), ul.index(val), 'index of %d' % val)
|
||||
self.assertEqual(pl.index(val), ul.index(val), "index of %d" % val)
|
||||
|
||||
for val in self.limits_plus(2):
|
||||
self.assertEqual(pl.count(val), ul.count(val), 'count %d' % val)
|
||||
self.assertEqual(pl.count(val), ul.count(val), "count %d" % val)
|
||||
|
||||
for val in range(self.limit):
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.remove(val)
|
||||
ul.remove(val)
|
||||
self.assertEqual(pl[:], ul[:], 'after remove val %d' % val)
|
||||
self.assertEqual(pl[:], ul[:], "after remove val %d" % val)
|
||||
|
||||
def indexfcn(x, v):
|
||||
return x.index(v)
|
||||
|
||||
def removefcn(x, v):
|
||||
return x.remove(v)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
indexfcn(ul, 40)
|
||||
with self.assertRaises(ValueError):
|
||||
removefcn(ul, 40)
|
||||
|
||||
def test07_allowed_types(self):
|
||||
'Type-restricted list'
|
||||
"Type-restricted list"
|
||||
pl, ul = self.lists_of_len()
|
||||
ul._allowed = int
|
||||
ul[1] = 50
|
||||
@@ -303,13 +314,14 @@ class ListMixinTest(unittest.TestCase):
|
||||
|
||||
def setfcn(x, i, v):
|
||||
x[i] = v
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
setfcn(ul, 2, 'hello')
|
||||
setfcn(ul, 2, "hello")
|
||||
with self.assertRaises(TypeError):
|
||||
setfcn(ul, slice(0, 3, 2), ('hello', 'goodbye'))
|
||||
setfcn(ul, slice(0, 3, 2), ("hello", "goodbye"))
|
||||
|
||||
def test08_min_length(self):
|
||||
'Length limits'
|
||||
"Length limits"
|
||||
pl, ul = self.lists_of_len(5)
|
||||
ul._minlength = 3
|
||||
|
||||
@@ -318,12 +330,13 @@ class ListMixinTest(unittest.TestCase):
|
||||
|
||||
def setfcn(x, i):
|
||||
x[:i] = []
|
||||
|
||||
for i in range(len(ul) - ul._minlength + 1, len(ul)):
|
||||
with self.assertRaises(ValueError):
|
||||
delfcn(ul, i)
|
||||
with self.assertRaises(ValueError):
|
||||
setfcn(ul, i)
|
||||
del ul[:len(ul) - ul._minlength]
|
||||
del ul[: len(ul) - ul._minlength]
|
||||
|
||||
ul._maxlength = 4
|
||||
for i in range(0, ul._maxlength - len(ul)):
|
||||
@@ -332,99 +345,102 @@ class ListMixinTest(unittest.TestCase):
|
||||
ul.append(10)
|
||||
|
||||
def test09_iterable_check(self):
|
||||
'Error on assigning non-iterable to slice'
|
||||
"Error on assigning non-iterable to slice"
|
||||
pl, ul = self.lists_of_len(self.limit + 1)
|
||||
|
||||
def setfcn(x, i, v):
|
||||
x[i] = v
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
setfcn(ul, slice(0, 3, 2), 2)
|
||||
|
||||
def test10_checkindex(self):
|
||||
'Index check'
|
||||
"Index check"
|
||||
pl, ul = self.lists_of_len()
|
||||
for i in self.limits_plus(0):
|
||||
if i < 0:
|
||||
self.assertEqual(ul._checkindex(i), i + self.limit, '_checkindex(neg index)')
|
||||
self.assertEqual(
|
||||
ul._checkindex(i), i + self.limit, "_checkindex(neg index)"
|
||||
)
|
||||
else:
|
||||
self.assertEqual(ul._checkindex(i), i, '_checkindex(pos index)')
|
||||
self.assertEqual(ul._checkindex(i), i, "_checkindex(pos index)")
|
||||
|
||||
for i in (-self.limit - 1, self.limit):
|
||||
with self.assertRaises(IndexError):
|
||||
ul._checkindex(i)
|
||||
|
||||
def test_11_sorting(self):
|
||||
'Sorting'
|
||||
"Sorting"
|
||||
pl, ul = self.lists_of_len()
|
||||
pl.insert(0, pl.pop())
|
||||
ul.insert(0, ul.pop())
|
||||
pl.sort()
|
||||
ul.sort()
|
||||
self.assertEqual(pl[:], ul[:], 'sort')
|
||||
self.assertEqual(pl[:], ul[:], "sort")
|
||||
mid = pl[len(pl) // 2]
|
||||
pl.sort(key=lambda x: (mid - x) ** 2)
|
||||
ul.sort(key=lambda x: (mid - x) ** 2)
|
||||
self.assertEqual(pl[:], ul[:], 'sort w/ key')
|
||||
self.assertEqual(pl[:], ul[:], "sort w/ key")
|
||||
|
||||
pl.insert(0, pl.pop())
|
||||
ul.insert(0, ul.pop())
|
||||
pl.sort(reverse=True)
|
||||
ul.sort(reverse=True)
|
||||
self.assertEqual(pl[:], ul[:], 'sort w/ reverse')
|
||||
self.assertEqual(pl[:], ul[:], "sort w/ reverse")
|
||||
mid = pl[len(pl) // 2]
|
||||
pl.sort(key=lambda x: (mid - x) ** 2)
|
||||
ul.sort(key=lambda x: (mid - x) ** 2)
|
||||
self.assertEqual(pl[:], ul[:], 'sort w/ key')
|
||||
self.assertEqual(pl[:], ul[:], "sort w/ key")
|
||||
|
||||
def test_12_arithmetic(self):
|
||||
'Arithmetic'
|
||||
"Arithmetic"
|
||||
pl, ul = self.lists_of_len()
|
||||
al = list(range(10, 14))
|
||||
self.assertEqual(list(pl + al), list(ul + al), 'add')
|
||||
self.assertEqual(type(ul), type(ul + al), 'type of add result')
|
||||
self.assertEqual(list(al + pl), list(al + ul), 'radd')
|
||||
self.assertEqual(type(al), type(al + ul), 'type of radd result')
|
||||
self.assertEqual(list(pl + al), list(ul + al), "add")
|
||||
self.assertEqual(type(ul), type(ul + al), "type of add result")
|
||||
self.assertEqual(list(al + pl), list(al + ul), "radd")
|
||||
self.assertEqual(type(al), type(al + ul), "type of radd result")
|
||||
objid = id(ul)
|
||||
pl += al
|
||||
ul += al
|
||||
self.assertEqual(pl[:], ul[:], 'in-place add')
|
||||
self.assertEqual(objid, id(ul), 'in-place add id')
|
||||
self.assertEqual(pl[:], ul[:], "in-place add")
|
||||
self.assertEqual(objid, id(ul), "in-place add id")
|
||||
|
||||
for n in (-1, 0, 1, 3):
|
||||
pl, ul = self.lists_of_len()
|
||||
self.assertEqual(list(pl * n), list(ul * n), 'mul by %d' % n)
|
||||
self.assertEqual(type(ul), type(ul * n), 'type of mul by %d result' % n)
|
||||
self.assertEqual(list(n * pl), list(n * ul), 'rmul by %d' % n)
|
||||
self.assertEqual(type(ul), type(n * ul), 'type of rmul by %d result' % n)
|
||||
self.assertEqual(list(pl * n), list(ul * n), "mul by %d" % n)
|
||||
self.assertEqual(type(ul), type(ul * n), "type of mul by %d result" % n)
|
||||
self.assertEqual(list(n * pl), list(n * ul), "rmul by %d" % n)
|
||||
self.assertEqual(type(ul), type(n * ul), "type of rmul by %d result" % n)
|
||||
objid = id(ul)
|
||||
pl *= n
|
||||
ul *= n
|
||||
self.assertEqual(pl[:], ul[:], 'in-place mul by %d' % n)
|
||||
self.assertEqual(objid, id(ul), 'in-place mul by %d id' % n)
|
||||
self.assertEqual(pl[:], ul[:], "in-place mul by %d" % n)
|
||||
self.assertEqual(objid, id(ul), "in-place mul by %d id" % n)
|
||||
|
||||
pl, ul = self.lists_of_len()
|
||||
self.assertEqual(pl, ul, 'cmp for equal')
|
||||
self.assertNotEqual(ul, pl + [2], 'cmp for not equal')
|
||||
self.assertGreaterEqual(pl, ul, 'cmp for gte self')
|
||||
self.assertLessEqual(pl, ul, 'cmp for lte self')
|
||||
self.assertGreaterEqual(ul, pl, 'cmp for self gte')
|
||||
self.assertLessEqual(ul, pl, 'cmp for self lte')
|
||||
self.assertEqual(pl, ul, "cmp for equal")
|
||||
self.assertNotEqual(ul, pl + [2], "cmp for not equal")
|
||||
self.assertGreaterEqual(pl, ul, "cmp for gte self")
|
||||
self.assertLessEqual(pl, ul, "cmp for lte self")
|
||||
self.assertGreaterEqual(ul, pl, "cmp for self gte")
|
||||
self.assertLessEqual(ul, pl, "cmp for self lte")
|
||||
|
||||
self.assertGreater(pl + [5], ul, 'cmp')
|
||||
self.assertGreaterEqual(pl + [5], ul, 'cmp')
|
||||
self.assertLess(pl, ul + [2], 'cmp')
|
||||
self.assertLessEqual(pl, ul + [2], 'cmp')
|
||||
self.assertGreater(ul + [5], pl, 'cmp')
|
||||
self.assertGreaterEqual(ul + [5], pl, 'cmp')
|
||||
self.assertLess(ul, pl + [2], 'cmp')
|
||||
self.assertLessEqual(ul, pl + [2], 'cmp')
|
||||
self.assertGreater(pl + [5], ul, "cmp")
|
||||
self.assertGreaterEqual(pl + [5], ul, "cmp")
|
||||
self.assertLess(pl, ul + [2], "cmp")
|
||||
self.assertLessEqual(pl, ul + [2], "cmp")
|
||||
self.assertGreater(ul + [5], pl, "cmp")
|
||||
self.assertGreaterEqual(ul + [5], pl, "cmp")
|
||||
self.assertLess(ul, pl + [2], "cmp")
|
||||
self.assertLessEqual(ul, pl + [2], "cmp")
|
||||
|
||||
pl[1] = 20
|
||||
self.assertGreater(pl, ul, 'cmp for gt self')
|
||||
self.assertLess(ul, pl, 'cmp for self lt')
|
||||
self.assertGreater(pl, ul, "cmp for gt self")
|
||||
self.assertLess(ul, pl, "cmp for self lt")
|
||||
pl[1] = -20
|
||||
self.assertLess(pl, ul, 'cmp for lt self')
|
||||
self.assertGreater(ul, pl, 'cmp for gt self')
|
||||
self.assertLess(pl, ul, "cmp for lt self")
|
||||
self.assertGreater(ul, pl, "cmp for gt self")
|
||||
|
||||
|
||||
class ListMixinTestSingle(ListMixinTest):
|
||||
|
||||
@@ -9,10 +9,12 @@ if connection.features.supports_raster:
|
||||
# PostGIS 3+ requires postgis_raster extension.
|
||||
if pg_version[1:] >= (3,):
|
||||
operations = [
|
||||
CreateExtension('postgis_raster'),
|
||||
CreateExtension("postgis_raster"),
|
||||
]
|
||||
else:
|
||||
operations = []
|
||||
|
||||
else:
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
operations = []
|
||||
|
||||
@@ -3,63 +3,96 @@ from django.db import connection, migrations
|
||||
|
||||
ops = [
|
||||
migrations.CreateModel(
|
||||
name='Neighborhood',
|
||||
name="Neighborhood",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('geom', models.MultiPolygonField(srid=4326)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100, unique=True)),
|
||||
("geom", models.MultiPolygonField(srid=4326)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Household',
|
||||
name="Household",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('neighborhood', models.ForeignKey(
|
||||
'gis_migrations.Neighborhood',
|
||||
models.SET_NULL,
|
||||
to_field='id',
|
||||
null=True,
|
||||
)),
|
||||
('address', models.CharField(max_length=100)),
|
||||
('zip_code', models.IntegerField(null=True, blank=True)),
|
||||
('geom', models.PointField(srid=4326, geography=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"neighborhood",
|
||||
models.ForeignKey(
|
||||
"gis_migrations.Neighborhood",
|
||||
models.SET_NULL,
|
||||
to_field="id",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("address", models.CharField(max_length=100)),
|
||||
("zip_code", models.IntegerField(null=True, blank=True)),
|
||||
("geom", models.PointField(srid=4326, geography=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Family',
|
||||
name="Family",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100, unique=True)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='household',
|
||||
name='family',
|
||||
field=models.ForeignKey('gis_migrations.Family', models.SET_NULL, blank=True, null=True),
|
||||
model_name="household",
|
||||
name="family",
|
||||
field=models.ForeignKey(
|
||||
"gis_migrations.Family", models.SET_NULL, blank=True, null=True
|
||||
),
|
||||
preserve_default=True,
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
if connection.features.supports_raster:
|
||||
ops += [
|
||||
migrations.CreateModel(
|
||||
name='Heatmap',
|
||||
name="Heatmap",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('rast', models.fields.RasterField(srid=4326)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100, unique=True)),
|
||||
("rast", models.fields.RasterField(srid=4326)),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
@@ -69,7 +102,8 @@ class Migration(migrations.Migration):
|
||||
"""
|
||||
Used for gis-specific migration tests.
|
||||
"""
|
||||
|
||||
dependencies = [
|
||||
('gis_migrations', '0001_setup_extensions'),
|
||||
("gis_migrations", "0001_setup_extensions"),
|
||||
]
|
||||
operations = ops
|
||||
|
||||
@@ -7,6 +7,7 @@ class MigrateTests(TransactionTestCase):
|
||||
"""
|
||||
Tests running the migrate command in Geodjango.
|
||||
"""
|
||||
|
||||
available_apps = ["gis_tests.gis_migrations"]
|
||||
|
||||
def get_table_description(self, table):
|
||||
@@ -52,7 +53,10 @@ class MigrateTests(TransactionTestCase):
|
||||
pass
|
||||
else:
|
||||
qs = GeoColumn.objects.filter(
|
||||
**{'%s__in' % GeoColumn.table_name_col(): ["gis_neighborhood", "gis_household"]}
|
||||
**{
|
||||
"%s__in"
|
||||
% GeoColumn.table_name_col(): ["gis_neighborhood", "gis_household"]
|
||||
}
|
||||
)
|
||||
self.assertEqual(qs.count(), 0)
|
||||
# Revert the "unmigration"
|
||||
|
||||
@@ -6,9 +6,7 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import connection, migrations, models
|
||||
from django.db.migrations.migration import Migration
|
||||
from django.db.migrations.state import ProjectState
|
||||
from django.test import (
|
||||
TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature,
|
||||
)
|
||||
from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||
|
||||
try:
|
||||
GeometryColumns = connection.ops.geometry_columns()
|
||||
@@ -18,25 +16,29 @@ except NotImplementedError:
|
||||
|
||||
|
||||
class OperationTestCase(TransactionTestCase):
|
||||
available_apps = ['gis_tests.gis_migrations']
|
||||
get_opclass_query = '''
|
||||
available_apps = ["gis_tests.gis_migrations"]
|
||||
get_opclass_query = """
|
||||
SELECT opcname, c.relname FROM pg_opclass AS oc
|
||||
JOIN pg_index as i on oc.oid = ANY(i.indclass)
|
||||
JOIN pg_class as c on c.oid = i.indexrelid
|
||||
WHERE c.relname = %s
|
||||
'''
|
||||
"""
|
||||
|
||||
def tearDown(self):
|
||||
# Delete table after testing
|
||||
if hasattr(self, 'current_state'):
|
||||
self.apply_operations('gis', self.current_state, [migrations.DeleteModel('Neighborhood')])
|
||||
if hasattr(self, "current_state"):
|
||||
self.apply_operations(
|
||||
"gis", self.current_state, [migrations.DeleteModel("Neighborhood")]
|
||||
)
|
||||
super().tearDown()
|
||||
|
||||
@property
|
||||
def has_spatial_indexes(self):
|
||||
if connection.ops.mysql:
|
||||
with connection.cursor() as cursor:
|
||||
return connection.introspection.supports_spatial_index(cursor, 'gis_neighborhood')
|
||||
return connection.introspection.supports_spatial_index(
|
||||
cursor, "gis_neighborhood"
|
||||
)
|
||||
return True
|
||||
|
||||
def get_table_description(self, table):
|
||||
@@ -50,57 +52,67 @@ class OperationTestCase(TransactionTestCase):
|
||||
self.assertNotIn(column, [c.name for c in self.get_table_description(table)])
|
||||
|
||||
def apply_operations(self, app_label, project_state, operations):
|
||||
migration = Migration('name', app_label)
|
||||
migration = Migration("name", app_label)
|
||||
migration.operations = operations
|
||||
with connection.schema_editor() as editor:
|
||||
return migration.apply(project_state, editor)
|
||||
|
||||
def set_up_test_model(self, force_raster_creation=False):
|
||||
test_fields = [
|
||||
('id', models.AutoField(primary_key=True)),
|
||||
('name', models.CharField(max_length=100, unique=True)),
|
||||
('geom', fields.MultiPolygonField(srid=4326))
|
||||
("id", models.AutoField(primary_key=True)),
|
||||
("name", models.CharField(max_length=100, unique=True)),
|
||||
("geom", fields.MultiPolygonField(srid=4326)),
|
||||
]
|
||||
if connection.features.supports_raster or force_raster_creation:
|
||||
test_fields += [('rast', fields.RasterField(srid=4326, null=True))]
|
||||
operations = [migrations.CreateModel('Neighborhood', test_fields)]
|
||||
self.current_state = self.apply_operations('gis', ProjectState(), operations)
|
||||
test_fields += [("rast", fields.RasterField(srid=4326, null=True))]
|
||||
operations = [migrations.CreateModel("Neighborhood", test_fields)]
|
||||
self.current_state = self.apply_operations("gis", ProjectState(), operations)
|
||||
|
||||
def assertGeometryColumnsCount(self, expected_count):
|
||||
self.assertEqual(
|
||||
GeometryColumns.objects.filter(**{
|
||||
'%s__iexact' % GeometryColumns.table_name_col(): 'gis_neighborhood',
|
||||
}).count(),
|
||||
expected_count
|
||||
GeometryColumns.objects.filter(
|
||||
**{
|
||||
"%s__iexact" % GeometryColumns.table_name_col(): "gis_neighborhood",
|
||||
}
|
||||
).count(),
|
||||
expected_count,
|
||||
)
|
||||
|
||||
def assertSpatialIndexExists(self, table, column, raster=False):
|
||||
with connection.cursor() as cursor:
|
||||
constraints = connection.introspection.get_constraints(cursor, table)
|
||||
if raster:
|
||||
self.assertTrue(any(
|
||||
'st_convexhull(%s)' % column in c['definition']
|
||||
for c in constraints.values()
|
||||
if c['definition'] is not None
|
||||
))
|
||||
self.assertTrue(
|
||||
any(
|
||||
"st_convexhull(%s)" % column in c["definition"]
|
||||
for c in constraints.values()
|
||||
if c["definition"] is not None
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.assertIn([column], [c['columns'] for c in constraints.values()])
|
||||
self.assertIn([column], [c["columns"] for c in constraints.values()])
|
||||
|
||||
def alter_gis_model(self, migration_class, model_name, field_name,
|
||||
blank=False, field_class=None, field_class_kwargs=None):
|
||||
def alter_gis_model(
|
||||
self,
|
||||
migration_class,
|
||||
model_name,
|
||||
field_name,
|
||||
blank=False,
|
||||
field_class=None,
|
||||
field_class_kwargs=None,
|
||||
):
|
||||
args = [model_name, field_name]
|
||||
if field_class:
|
||||
field_class_kwargs = field_class_kwargs or {'srid': 4326, 'blank': blank}
|
||||
field_class_kwargs = field_class_kwargs or {"srid": 4326, "blank": blank}
|
||||
args.append(field_class(**field_class_kwargs))
|
||||
operation = migration_class(*args)
|
||||
old_state = self.current_state.clone()
|
||||
operation.state_forwards('gis', self.current_state)
|
||||
operation.state_forwards("gis", self.current_state)
|
||||
with connection.schema_editor() as editor:
|
||||
operation.database_forwards('gis', editor, old_state, self.current_state)
|
||||
operation.database_forwards("gis", editor, old_state, self.current_state)
|
||||
|
||||
|
||||
class OperationTests(OperationTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.set_up_test_model()
|
||||
@@ -109,8 +121,10 @@ class OperationTests(OperationTestCase):
|
||||
"""
|
||||
Test the AddField operation with a geometry-enabled column.
|
||||
"""
|
||||
self.alter_gis_model(migrations.AddField, 'Neighborhood', 'path', False, fields.LineStringField)
|
||||
self.assertColumnExists('gis_neighborhood', 'path')
|
||||
self.alter_gis_model(
|
||||
migrations.AddField, "Neighborhood", "path", False, fields.LineStringField
|
||||
)
|
||||
self.assertColumnExists("gis_neighborhood", "path")
|
||||
|
||||
# Test GeometryColumns when available
|
||||
if HAS_GEOMETRY_COLUMNS:
|
||||
@@ -118,33 +132,37 @@ class OperationTests(OperationTestCase):
|
||||
|
||||
# Test spatial indices when available
|
||||
if self.has_spatial_indexes:
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'path')
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "path")
|
||||
|
||||
@skipUnless(HAS_GEOMETRY_COLUMNS, "Backend doesn't support GeometryColumns.")
|
||||
def test_geom_col_name(self):
|
||||
self.assertEqual(
|
||||
GeometryColumns.geom_col_name(),
|
||||
'column_name' if connection.ops.oracle else 'f_geometry_column',
|
||||
"column_name" if connection.ops.oracle else "f_geometry_column",
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('supports_raster')
|
||||
@skipUnlessDBFeature("supports_raster")
|
||||
def test_add_raster_field(self):
|
||||
"""
|
||||
Test the AddField operation with a raster-enabled column.
|
||||
"""
|
||||
self.alter_gis_model(migrations.AddField, 'Neighborhood', 'heatmap', False, fields.RasterField)
|
||||
self.assertColumnExists('gis_neighborhood', 'heatmap')
|
||||
self.alter_gis_model(
|
||||
migrations.AddField, "Neighborhood", "heatmap", False, fields.RasterField
|
||||
)
|
||||
self.assertColumnExists("gis_neighborhood", "heatmap")
|
||||
|
||||
# Test spatial indices when available
|
||||
if self.has_spatial_indexes:
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'heatmap', raster=True)
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "heatmap", raster=True)
|
||||
|
||||
def test_add_blank_geom_field(self):
|
||||
"""
|
||||
Should be able to add a GeometryField with blank=True.
|
||||
"""
|
||||
self.alter_gis_model(migrations.AddField, 'Neighborhood', 'path', True, fields.LineStringField)
|
||||
self.assertColumnExists('gis_neighborhood', 'path')
|
||||
self.alter_gis_model(
|
||||
migrations.AddField, "Neighborhood", "path", True, fields.LineStringField
|
||||
)
|
||||
self.assertColumnExists("gis_neighborhood", "path")
|
||||
|
||||
# Test GeometryColumns when available
|
||||
if HAS_GEOMETRY_COLUMNS:
|
||||
@@ -152,96 +170,108 @@ class OperationTests(OperationTestCase):
|
||||
|
||||
# Test spatial indices when available
|
||||
if self.has_spatial_indexes:
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'path')
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "path")
|
||||
|
||||
@skipUnlessDBFeature('supports_raster')
|
||||
@skipUnlessDBFeature("supports_raster")
|
||||
def test_add_blank_raster_field(self):
|
||||
"""
|
||||
Should be able to add a RasterField with blank=True.
|
||||
"""
|
||||
self.alter_gis_model(migrations.AddField, 'Neighborhood', 'heatmap', True, fields.RasterField)
|
||||
self.assertColumnExists('gis_neighborhood', 'heatmap')
|
||||
self.alter_gis_model(
|
||||
migrations.AddField, "Neighborhood", "heatmap", True, fields.RasterField
|
||||
)
|
||||
self.assertColumnExists("gis_neighborhood", "heatmap")
|
||||
|
||||
# Test spatial indices when available
|
||||
if self.has_spatial_indexes:
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'heatmap', raster=True)
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "heatmap", raster=True)
|
||||
|
||||
def test_remove_geom_field(self):
|
||||
"""
|
||||
Test the RemoveField operation with a geometry-enabled column.
|
||||
"""
|
||||
self.alter_gis_model(migrations.RemoveField, 'Neighborhood', 'geom')
|
||||
self.assertColumnNotExists('gis_neighborhood', 'geom')
|
||||
self.alter_gis_model(migrations.RemoveField, "Neighborhood", "geom")
|
||||
self.assertColumnNotExists("gis_neighborhood", "geom")
|
||||
|
||||
# Test GeometryColumns when available
|
||||
if HAS_GEOMETRY_COLUMNS:
|
||||
self.assertGeometryColumnsCount(0)
|
||||
|
||||
@skipUnlessDBFeature('supports_raster')
|
||||
@skipUnlessDBFeature("supports_raster")
|
||||
def test_remove_raster_field(self):
|
||||
"""
|
||||
Test the RemoveField operation with a raster-enabled column.
|
||||
"""
|
||||
self.alter_gis_model(migrations.RemoveField, 'Neighborhood', 'rast')
|
||||
self.assertColumnNotExists('gis_neighborhood', 'rast')
|
||||
self.alter_gis_model(migrations.RemoveField, "Neighborhood", "rast")
|
||||
self.assertColumnNotExists("gis_neighborhood", "rast")
|
||||
|
||||
def test_create_model_spatial_index(self):
|
||||
if not self.has_spatial_indexes:
|
||||
self.skipTest('No support for Spatial indexes')
|
||||
self.skipTest("No support for Spatial indexes")
|
||||
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'geom')
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "geom")
|
||||
|
||||
if connection.features.supports_raster:
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'rast', raster=True)
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "rast", raster=True)
|
||||
|
||||
@skipUnlessDBFeature('supports_3d_storage')
|
||||
@skipUnlessDBFeature("supports_3d_storage")
|
||||
def test_add_3d_field_opclass(self):
|
||||
if not connection.ops.postgis:
|
||||
self.skipTest('PostGIS-specific test.')
|
||||
self.skipTest("PostGIS-specific test.")
|
||||
|
||||
self.alter_gis_model(
|
||||
migrations.AddField,
|
||||
'Neighborhood',
|
||||
'point3d',
|
||||
"Neighborhood",
|
||||
"point3d",
|
||||
field_class=fields.PointField,
|
||||
field_class_kwargs={'dim': 3},
|
||||
field_class_kwargs={"dim": 3},
|
||||
)
|
||||
self.assertColumnExists('gis_neighborhood', 'point3d')
|
||||
self.assertSpatialIndexExists('gis_neighborhood', 'point3d')
|
||||
self.assertColumnExists("gis_neighborhood", "point3d")
|
||||
self.assertSpatialIndexExists("gis_neighborhood", "point3d")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
index_name = 'gis_neighborhood_point3d_113bc868_id'
|
||||
index_name = "gis_neighborhood_point3d_113bc868_id"
|
||||
cursor.execute(self.get_opclass_query, [index_name])
|
||||
self.assertEqual(
|
||||
cursor.fetchall(),
|
||||
[('gist_geometry_ops_nd', index_name)],
|
||||
[("gist_geometry_ops_nd", index_name)],
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('can_alter_geometry_field', 'supports_3d_storage')
|
||||
@skipUnlessDBFeature("can_alter_geometry_field", "supports_3d_storage")
|
||||
def test_alter_geom_field_dim(self):
|
||||
Neighborhood = self.current_state.apps.get_model('gis', 'Neighborhood')
|
||||
Neighborhood = self.current_state.apps.get_model("gis", "Neighborhood")
|
||||
p1 = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
|
||||
Neighborhood.objects.create(name='TestDim', geom=MultiPolygon(p1, p1))
|
||||
Neighborhood.objects.create(name="TestDim", geom=MultiPolygon(p1, p1))
|
||||
# Add 3rd dimension.
|
||||
self.alter_gis_model(
|
||||
migrations.AlterField, 'Neighborhood', 'geom', False,
|
||||
fields.MultiPolygonField, field_class_kwargs={'srid': 4326, 'dim': 3}
|
||||
migrations.AlterField,
|
||||
"Neighborhood",
|
||||
"geom",
|
||||
False,
|
||||
fields.MultiPolygonField,
|
||||
field_class_kwargs={"srid": 4326, "dim": 3},
|
||||
)
|
||||
self.assertTrue(Neighborhood.objects.first().geom.hasz)
|
||||
# Rewind to 2 dimensions.
|
||||
self.alter_gis_model(
|
||||
migrations.AlterField, 'Neighborhood', 'geom', False,
|
||||
fields.MultiPolygonField, field_class_kwargs={'srid': 4326, 'dim': 2}
|
||||
migrations.AlterField,
|
||||
"Neighborhood",
|
||||
"geom",
|
||||
False,
|
||||
fields.MultiPolygonField,
|
||||
field_class_kwargs={"srid": 4326, "dim": 2},
|
||||
)
|
||||
self.assertFalse(Neighborhood.objects.first().geom.hasz)
|
||||
|
||||
@skipUnlessDBFeature('supports_column_check_constraints', 'can_introspect_check_constraints')
|
||||
@skipUnlessDBFeature(
|
||||
"supports_column_check_constraints", "can_introspect_check_constraints"
|
||||
)
|
||||
def test_add_check_constraint(self):
|
||||
Neighborhood = self.current_state.apps.get_model('gis', 'Neighborhood')
|
||||
Neighborhood = self.current_state.apps.get_model("gis", "Neighborhood")
|
||||
poly = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
|
||||
constraint = models.CheckConstraint(
|
||||
check=models.Q(geom=poly),
|
||||
name='geom_within_constraint',
|
||||
name="geom_within_constraint",
|
||||
)
|
||||
Neighborhood._meta.constraints = [constraint]
|
||||
with connection.schema_editor() as editor:
|
||||
@@ -251,21 +281,24 @@ class OperationTests(OperationTestCase):
|
||||
cursor,
|
||||
Neighborhood._meta.db_table,
|
||||
)
|
||||
self.assertIn('geom_within_constraint', constraints)
|
||||
self.assertIn("geom_within_constraint", constraints)
|
||||
|
||||
|
||||
@skipIfDBFeature('supports_raster')
|
||||
@skipIfDBFeature("supports_raster")
|
||||
class NoRasterSupportTests(OperationTestCase):
|
||||
def test_create_raster_model_on_db_without_raster_support(self):
|
||||
msg = 'Raster fields require backends with raster support.'
|
||||
msg = "Raster fields require backends with raster support."
|
||||
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||
self.set_up_test_model(force_raster_creation=True)
|
||||
|
||||
def test_add_raster_field_on_db_without_raster_support(self):
|
||||
msg = 'Raster fields require backends with raster support.'
|
||||
msg = "Raster fields require backends with raster support."
|
||||
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||
self.set_up_test_model()
|
||||
self.alter_gis_model(
|
||||
migrations.AddField, 'Neighborhood', 'heatmap',
|
||||
False, fields.RasterField
|
||||
migrations.AddField,
|
||||
"Neighborhood",
|
||||
"heatmap",
|
||||
False,
|
||||
fields.RasterField,
|
||||
)
|
||||
|
||||
@@ -21,4 +21,4 @@ class Fields3D(models.Model):
|
||||
poly = models.PolygonField(dim=3)
|
||||
|
||||
class Meta:
|
||||
required_db_features = {'supports_3d_storage'}
|
||||
required_db_features = {"supports_3d_storage"}
|
||||
|
||||
@@ -20,94 +20,96 @@ class InspectDbTests(TestCase):
|
||||
"""
|
||||
out = StringIO()
|
||||
call_command(
|
||||
'inspectdb',
|
||||
table_name_filter=lambda tn: tn == 'inspectapp_allogrfields',
|
||||
stdout=out
|
||||
"inspectdb",
|
||||
table_name_filter=lambda tn: tn == "inspectapp_allogrfields",
|
||||
stdout=out,
|
||||
)
|
||||
output = out.getvalue()
|
||||
if connection.features.supports_geometry_field_introspection:
|
||||
self.assertIn('geom = models.PolygonField()', output)
|
||||
self.assertIn('point = models.PointField()', output)
|
||||
self.assertIn("geom = models.PolygonField()", output)
|
||||
self.assertIn("point = models.PointField()", output)
|
||||
else:
|
||||
self.assertIn('geom = models.GeometryField(', output)
|
||||
self.assertIn('point = models.GeometryField(', output)
|
||||
self.assertIn("geom = models.GeometryField(", output)
|
||||
self.assertIn("point = models.GeometryField(", output)
|
||||
|
||||
@skipUnlessDBFeature("supports_3d_storage")
|
||||
def test_3d_columns(self):
|
||||
out = StringIO()
|
||||
call_command(
|
||||
'inspectdb',
|
||||
table_name_filter=lambda tn: tn == 'inspectapp_fields3d',
|
||||
stdout=out
|
||||
"inspectdb",
|
||||
table_name_filter=lambda tn: tn == "inspectapp_fields3d",
|
||||
stdout=out,
|
||||
)
|
||||
output = out.getvalue()
|
||||
if connection.features.supports_geometry_field_introspection:
|
||||
self.assertIn('point = models.PointField(dim=3)', output)
|
||||
self.assertIn("point = models.PointField(dim=3)", output)
|
||||
if connection.features.supports_geography:
|
||||
self.assertIn('pointg = models.PointField(geography=True, dim=3)', output)
|
||||
self.assertIn(
|
||||
"pointg = models.PointField(geography=True, dim=3)", output
|
||||
)
|
||||
else:
|
||||
self.assertIn('pointg = models.PointField(dim=3)', output)
|
||||
self.assertIn('line = models.LineStringField(dim=3)', output)
|
||||
self.assertIn('poly = models.PolygonField(dim=3)', output)
|
||||
self.assertIn("pointg = models.PointField(dim=3)", output)
|
||||
self.assertIn("line = models.LineStringField(dim=3)", output)
|
||||
self.assertIn("poly = models.PolygonField(dim=3)", output)
|
||||
else:
|
||||
self.assertIn('point = models.GeometryField(', output)
|
||||
self.assertIn('pointg = models.GeometryField(', output)
|
||||
self.assertIn('line = models.GeometryField(', output)
|
||||
self.assertIn('poly = models.GeometryField(', output)
|
||||
self.assertIn("point = models.GeometryField(", output)
|
||||
self.assertIn("pointg = models.GeometryField(", output)
|
||||
self.assertIn("line = models.GeometryField(", output)
|
||||
self.assertIn("poly = models.GeometryField(", output)
|
||||
|
||||
|
||||
@modify_settings(
|
||||
INSTALLED_APPS={'append': 'django.contrib.gis'},
|
||||
INSTALLED_APPS={"append": "django.contrib.gis"},
|
||||
)
|
||||
class OGRInspectTest(SimpleTestCase):
|
||||
maxDiff = 1024
|
||||
|
||||
def test_poly(self):
|
||||
shp_file = os.path.join(TEST_DATA, 'test_poly', 'test_poly.shp')
|
||||
model_def = ogrinspect(shp_file, 'MyModel')
|
||||
shp_file = os.path.join(TEST_DATA, "test_poly", "test_poly.shp")
|
||||
model_def = ogrinspect(shp_file, "MyModel")
|
||||
|
||||
expected = [
|
||||
'# This is an auto-generated Django model module created by ogrinspect.',
|
||||
'from django.contrib.gis.db import models',
|
||||
'',
|
||||
'',
|
||||
'class MyModel(models.Model):',
|
||||
' float = models.FloatField()',
|
||||
' int = models.BigIntegerField()',
|
||||
' str = models.CharField(max_length=80)',
|
||||
' geom = models.PolygonField()',
|
||||
"# This is an auto-generated Django model module created by ogrinspect.",
|
||||
"from django.contrib.gis.db import models",
|
||||
"",
|
||||
"",
|
||||
"class MyModel(models.Model):",
|
||||
" float = models.FloatField()",
|
||||
" int = models.BigIntegerField()",
|
||||
" str = models.CharField(max_length=80)",
|
||||
" geom = models.PolygonField()",
|
||||
]
|
||||
|
||||
self.assertEqual(model_def, '\n'.join(expected))
|
||||
self.assertEqual(model_def, "\n".join(expected))
|
||||
|
||||
def test_poly_multi(self):
|
||||
shp_file = os.path.join(TEST_DATA, 'test_poly', 'test_poly.shp')
|
||||
model_def = ogrinspect(shp_file, 'MyModel', multi_geom=True)
|
||||
self.assertIn('geom = models.MultiPolygonField()', model_def)
|
||||
shp_file = os.path.join(TEST_DATA, "test_poly", "test_poly.shp")
|
||||
model_def = ogrinspect(shp_file, "MyModel", multi_geom=True)
|
||||
self.assertIn("geom = models.MultiPolygonField()", model_def)
|
||||
# Same test with a 25D-type geometry field
|
||||
shp_file = os.path.join(TEST_DATA, 'gas_lines', 'gas_leitung.shp')
|
||||
model_def = ogrinspect(shp_file, 'MyModel', multi_geom=True)
|
||||
srid = '-1' if GDAL_VERSION < (2, 3) else '31253'
|
||||
self.assertIn('geom = models.MultiLineStringField(srid=%s)' % srid, model_def)
|
||||
shp_file = os.path.join(TEST_DATA, "gas_lines", "gas_leitung.shp")
|
||||
model_def = ogrinspect(shp_file, "MyModel", multi_geom=True)
|
||||
srid = "-1" if GDAL_VERSION < (2, 3) else "31253"
|
||||
self.assertIn("geom = models.MultiLineStringField(srid=%s)" % srid, model_def)
|
||||
|
||||
def test_date_field(self):
|
||||
shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp')
|
||||
model_def = ogrinspect(shp_file, 'City')
|
||||
shp_file = os.path.join(TEST_DATA, "cities", "cities.shp")
|
||||
model_def = ogrinspect(shp_file, "City")
|
||||
|
||||
expected = [
|
||||
'# This is an auto-generated Django model module created by ogrinspect.',
|
||||
'from django.contrib.gis.db import models',
|
||||
'',
|
||||
'',
|
||||
'class City(models.Model):',
|
||||
' name = models.CharField(max_length=80)',
|
||||
' population = models.BigIntegerField()',
|
||||
' density = models.FloatField()',
|
||||
' created = models.DateField()',
|
||||
' geom = models.PointField()',
|
||||
"# This is an auto-generated Django model module created by ogrinspect.",
|
||||
"from django.contrib.gis.db import models",
|
||||
"",
|
||||
"",
|
||||
"class City(models.Model):",
|
||||
" name = models.CharField(max_length=80)",
|
||||
" population = models.BigIntegerField()",
|
||||
" density = models.FloatField()",
|
||||
" created = models.DateField()",
|
||||
" geom = models.PointField()",
|
||||
]
|
||||
|
||||
self.assertEqual(model_def, '\n'.join(expected))
|
||||
self.assertEqual(model_def, "\n".join(expected))
|
||||
|
||||
def test_time_field(self):
|
||||
# Getting the database identifier used by OGR, if None returned
|
||||
@@ -119,48 +121,60 @@ class OGRInspectTest(SimpleTestCase):
|
||||
try:
|
||||
# Writing shapefiles via GDAL currently does not support writing OGRTime
|
||||
# fields, so we need to actually use a database
|
||||
model_def = ogrinspect(ogr_db, 'Measurement',
|
||||
layer_key=AllOGRFields._meta.db_table,
|
||||
decimal=['f_decimal'])
|
||||
model_def = ogrinspect(
|
||||
ogr_db,
|
||||
"Measurement",
|
||||
layer_key=AllOGRFields._meta.db_table,
|
||||
decimal=["f_decimal"],
|
||||
)
|
||||
except GDALException:
|
||||
self.skipTest("Unable to setup an OGR connection to your database")
|
||||
|
||||
self.assertTrue(model_def.startswith(
|
||||
'# This is an auto-generated Django model module created by ogrinspect.\n'
|
||||
'from django.contrib.gis.db import models\n'
|
||||
'\n'
|
||||
'\n'
|
||||
'class Measurement(models.Model):\n'
|
||||
))
|
||||
self.assertTrue(
|
||||
model_def.startswith(
|
||||
"# This is an auto-generated Django model module created by ogrinspect.\n"
|
||||
"from django.contrib.gis.db import models\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"class Measurement(models.Model):\n"
|
||||
)
|
||||
)
|
||||
|
||||
# The ordering of model fields might vary depending on several factors (version of GDAL, etc.)
|
||||
if connection.vendor == 'sqlite':
|
||||
if connection.vendor == "sqlite":
|
||||
# SpatiaLite introspection is somewhat lacking (#29461).
|
||||
self.assertIn(' f_decimal = models.CharField(max_length=0)', model_def)
|
||||
self.assertIn(" f_decimal = models.CharField(max_length=0)", model_def)
|
||||
else:
|
||||
self.assertIn(' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)', model_def)
|
||||
self.assertIn(' f_int = models.IntegerField()', model_def)
|
||||
self.assertIn(
|
||||
" f_decimal = models.DecimalField(max_digits=0, decimal_places=0)",
|
||||
model_def,
|
||||
)
|
||||
self.assertIn(" f_int = models.IntegerField()", model_def)
|
||||
if not connection.ops.mariadb:
|
||||
# Probably a bug between GDAL and MariaDB on time fields.
|
||||
self.assertIn(' f_datetime = models.DateTimeField()', model_def)
|
||||
self.assertIn(' f_time = models.TimeField()', model_def)
|
||||
if connection.vendor == 'sqlite':
|
||||
self.assertIn(' f_float = models.CharField(max_length=0)', model_def)
|
||||
self.assertIn(" f_datetime = models.DateTimeField()", model_def)
|
||||
self.assertIn(" f_time = models.TimeField()", model_def)
|
||||
if connection.vendor == "sqlite":
|
||||
self.assertIn(" f_float = models.CharField(max_length=0)", model_def)
|
||||
else:
|
||||
self.assertIn(' f_float = models.FloatField()', model_def)
|
||||
max_length = 0 if connection.vendor == 'sqlite' else 10
|
||||
self.assertIn(' f_char = models.CharField(max_length=%s)' % max_length, model_def)
|
||||
self.assertIn(' f_date = models.DateField()', model_def)
|
||||
self.assertIn(" f_float = models.FloatField()", model_def)
|
||||
max_length = 0 if connection.vendor == "sqlite" else 10
|
||||
self.assertIn(
|
||||
" f_char = models.CharField(max_length=%s)" % max_length, model_def
|
||||
)
|
||||
self.assertIn(" f_date = models.DateField()", model_def)
|
||||
|
||||
# Some backends may have srid=-1
|
||||
self.assertIsNotNone(re.search(r' geom = models.PolygonField\(([^\)])*\)', model_def))
|
||||
self.assertIsNotNone(
|
||||
re.search(r" geom = models.PolygonField\(([^\)])*\)", model_def)
|
||||
)
|
||||
|
||||
def test_management_command(self):
|
||||
shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp')
|
||||
shp_file = os.path.join(TEST_DATA, "cities", "cities.shp")
|
||||
out = StringIO()
|
||||
call_command('ogrinspect', shp_file, 'City', stdout=out)
|
||||
call_command("ogrinspect", shp_file, "City", stdout=out)
|
||||
output = out.getvalue()
|
||||
self.assertIn('class City(models.Model):', output)
|
||||
self.assertIn("class City(models.Model):", output)
|
||||
|
||||
def test_mapping_option(self):
|
||||
expected = (
|
||||
@@ -174,10 +188,11 @@ class OGRInspectTest(SimpleTestCase):
|
||||
" 'density': 'Density',\n"
|
||||
" 'created': 'Created',\n"
|
||||
" 'geom': 'POINT',\n"
|
||||
"}\n")
|
||||
shp_file = os.path.join(TEST_DATA, 'cities', 'cities.shp')
|
||||
"}\n"
|
||||
)
|
||||
shp_file = os.path.join(TEST_DATA, "cities", "cities.shp")
|
||||
out = StringIO()
|
||||
call_command('ogrinspect', shp_file, '--mapping', 'City', stdout=out)
|
||||
call_command("ogrinspect", shp_file, "--mapping", "City", stdout=out)
|
||||
self.assertIn(expected, out.getvalue())
|
||||
|
||||
|
||||
@@ -187,19 +202,23 @@ def get_ogr_db_string():
|
||||
GDAL will create its own connection to the database, so we re-use the
|
||||
connection settings from the Django test.
|
||||
"""
|
||||
db = connections.databases['default']
|
||||
db = connections.databases["default"]
|
||||
|
||||
# Map from the django backend into the OGR driver name and database identifier
|
||||
# https://gdal.org/drivers/vector/
|
||||
#
|
||||
# TODO: Support Oracle (OCI).
|
||||
drivers = {
|
||||
'django.contrib.gis.db.backends.postgis': ('PostgreSQL', "PG:dbname='%(db_name)s'", ' '),
|
||||
'django.contrib.gis.db.backends.mysql': ('MySQL', 'MYSQL:"%(db_name)s"', ','),
|
||||
'django.contrib.gis.db.backends.spatialite': ('SQLite', '%(db_name)s', '')
|
||||
"django.contrib.gis.db.backends.postgis": (
|
||||
"PostgreSQL",
|
||||
"PG:dbname='%(db_name)s'",
|
||||
" ",
|
||||
),
|
||||
"django.contrib.gis.db.backends.mysql": ("MySQL", 'MYSQL:"%(db_name)s"', ","),
|
||||
"django.contrib.gis.db.backends.spatialite": ("SQLite", "%(db_name)s", ""),
|
||||
}
|
||||
|
||||
db_engine = db['ENGINE']
|
||||
db_engine = db["ENGINE"]
|
||||
if db_engine not in drivers:
|
||||
return None
|
||||
|
||||
@@ -212,20 +231,21 @@ def get_ogr_db_string():
|
||||
return None
|
||||
|
||||
# SQLite/SpatiaLite in-memory databases
|
||||
if db['NAME'] == ":memory:":
|
||||
if db["NAME"] == ":memory:":
|
||||
return None
|
||||
|
||||
# Build the params of the OGR database connection string
|
||||
params = [db_str % {'db_name': db['NAME']}]
|
||||
params = [db_str % {"db_name": db["NAME"]}]
|
||||
|
||||
def add(key, template):
|
||||
value = db.get(key, None)
|
||||
# Don't add the parameter if it is not in django's settings
|
||||
if value:
|
||||
params.append(template % value)
|
||||
add('HOST', "host='%s'")
|
||||
add('PORT', "port='%s'")
|
||||
add('USER', "user='%s'")
|
||||
add('PASSWORD', "password='%s'")
|
||||
|
||||
add("HOST", "host='%s'")
|
||||
add("PORT", "port='%s'")
|
||||
add("USER", "user='%s'")
|
||||
add("PASSWORD", "password='%s'")
|
||||
|
||||
return param_sep.join(params)
|
||||
|
||||
@@ -25,7 +25,7 @@ class CountyFeat(NamedModel):
|
||||
|
||||
|
||||
class City(NamedModel):
|
||||
name_txt = models.TextField(default='')
|
||||
name_txt = models.TextField(default="")
|
||||
name_short = models.CharField(max_length=5)
|
||||
population = models.IntegerField()
|
||||
density = models.DecimalField(max_digits=7, decimal_places=1)
|
||||
@@ -33,7 +33,7 @@ class City(NamedModel):
|
||||
point = models.PointField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'layermap'
|
||||
app_label = "layermap"
|
||||
|
||||
|
||||
class Interstate(NamedModel):
|
||||
@@ -41,7 +41,7 @@ class Interstate(NamedModel):
|
||||
path = models.LineStringField()
|
||||
|
||||
class Meta:
|
||||
app_label = 'layermap'
|
||||
app_label = "layermap"
|
||||
|
||||
|
||||
# Same as `City` above, but for testing model inheritance.
|
||||
@@ -91,37 +91,37 @@ class DoesNotAllowNulls(models.Model):
|
||||
|
||||
# Mapping dictionaries for the models above.
|
||||
co_mapping = {
|
||||
'name': 'Name',
|
||||
"name": "Name",
|
||||
# ForeignKey's use another mapping dictionary for the _related_ Model (State in this case).
|
||||
'state': {'name': 'State'},
|
||||
'mpoly': 'MULTIPOLYGON', # Will convert POLYGON features into MULTIPOLYGONS.
|
||||
"state": {"name": "State"},
|
||||
"mpoly": "MULTIPOLYGON", # Will convert POLYGON features into MULTIPOLYGONS.
|
||||
}
|
||||
|
||||
cofeat_mapping = {
|
||||
'name': 'Name',
|
||||
'poly': 'POLYGON',
|
||||
"name": "Name",
|
||||
"poly": "POLYGON",
|
||||
}
|
||||
|
||||
city_mapping = {
|
||||
'name': 'Name',
|
||||
'population': 'Population',
|
||||
'density': 'Density',
|
||||
'dt': 'Created',
|
||||
'point': 'POINT',
|
||||
"name": "Name",
|
||||
"population": "Population",
|
||||
"density": "Density",
|
||||
"dt": "Created",
|
||||
"point": "POINT",
|
||||
}
|
||||
|
||||
inter_mapping = {
|
||||
'name': 'Name',
|
||||
'length': 'Length',
|
||||
'path': 'LINESTRING',
|
||||
"name": "Name",
|
||||
"length": "Length",
|
||||
"path": "LINESTRING",
|
||||
}
|
||||
|
||||
has_nulls_mapping = {
|
||||
'geom': 'POLYGON',
|
||||
'uuid': 'uuid',
|
||||
'datetime': 'datetime',
|
||||
'name': 'name',
|
||||
'integer': 'integer',
|
||||
'num': 'num',
|
||||
'boolean': 'boolean',
|
||||
"geom": "POLYGON",
|
||||
"uuid": "uuid",
|
||||
"datetime": "datetime",
|
||||
"name": "name",
|
||||
"integer": "integer",
|
||||
"num": "num",
|
||||
"boolean": "boolean",
|
||||
}
|
||||
|
||||
@@ -7,47 +7,61 @@ from pathlib import Path
|
||||
from django.conf import settings
|
||||
from django.contrib.gis.gdal import DataSource
|
||||
from django.contrib.gis.utils.layermapping import (
|
||||
InvalidDecimal, InvalidString, LayerMapError, LayerMapping,
|
||||
InvalidDecimal,
|
||||
InvalidString,
|
||||
LayerMapError,
|
||||
LayerMapping,
|
||||
MissingForeignKey,
|
||||
)
|
||||
from django.db import connection
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
from .models import (
|
||||
City, County, CountyFeat, DoesNotAllowNulls, HasNulls, ICity1, ICity2,
|
||||
Interstate, Invalid, State, city_mapping, co_mapping, cofeat_mapping,
|
||||
has_nulls_mapping, inter_mapping,
|
||||
City,
|
||||
County,
|
||||
CountyFeat,
|
||||
DoesNotAllowNulls,
|
||||
HasNulls,
|
||||
ICity1,
|
||||
ICity2,
|
||||
Interstate,
|
||||
Invalid,
|
||||
State,
|
||||
city_mapping,
|
||||
co_mapping,
|
||||
cofeat_mapping,
|
||||
has_nulls_mapping,
|
||||
inter_mapping,
|
||||
)
|
||||
|
||||
shp_path = Path(__file__).resolve().parent.parent / 'data'
|
||||
city_shp = shp_path / 'cities' / 'cities.shp'
|
||||
co_shp = shp_path / 'counties' / 'counties.shp'
|
||||
inter_shp = shp_path / 'interstates' / 'interstates.shp'
|
||||
invalid_shp = shp_path / 'invalid' / 'emptypoints.shp'
|
||||
has_nulls_geojson = shp_path / 'has_nulls' / 'has_nulls.geojson'
|
||||
shp_path = Path(__file__).resolve().parent.parent / "data"
|
||||
city_shp = shp_path / "cities" / "cities.shp"
|
||||
co_shp = shp_path / "counties" / "counties.shp"
|
||||
inter_shp = shp_path / "interstates" / "interstates.shp"
|
||||
invalid_shp = shp_path / "invalid" / "emptypoints.shp"
|
||||
has_nulls_geojson = shp_path / "has_nulls" / "has_nulls.geojson"
|
||||
|
||||
# Dictionaries to hold what's expected in the county shapefile.
|
||||
NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
|
||||
NAMES = ["Bexar", "Galveston", "Harris", "Honolulu", "Pueblo"]
|
||||
NUMS = [1, 2, 1, 19, 1] # Number of polygons for each.
|
||||
STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
|
||||
STATES = ["Texas", "Texas", "Texas", "Hawaii", "Colorado"]
|
||||
|
||||
|
||||
class LayerMapTest(TestCase):
|
||||
|
||||
def test_init(self):
|
||||
"Testing LayerMapping initialization."
|
||||
|
||||
# Model field that does not exist.
|
||||
bad1 = copy(city_mapping)
|
||||
bad1['foobar'] = 'FooField'
|
||||
bad1["foobar"] = "FooField"
|
||||
|
||||
# Shapefile field that does not exist.
|
||||
bad2 = copy(city_mapping)
|
||||
bad2['name'] = 'Nombre'
|
||||
bad2["name"] = "Nombre"
|
||||
|
||||
# Nonexistent geographic field type.
|
||||
bad3 = copy(city_mapping)
|
||||
bad3['point'] = 'CURVE'
|
||||
bad3["point"] = "CURVE"
|
||||
|
||||
# Incrementing through the bad mapping dictionaries and
|
||||
# ensuring that a LayerMapError is raised.
|
||||
@@ -57,7 +71,7 @@ class LayerMapTest(TestCase):
|
||||
|
||||
# A LookupError should be thrown for bogus encodings.
|
||||
with self.assertRaises(LookupError):
|
||||
LayerMapping(City, city_shp, city_mapping, encoding='foobar')
|
||||
LayerMapping(City, city_shp, city_mapping, encoding="foobar")
|
||||
|
||||
def test_simple_layermap(self):
|
||||
"Test LayerMapping import of a simple point shapefile."
|
||||
@@ -73,10 +87,10 @@ class LayerMapTest(TestCase):
|
||||
ds = DataSource(city_shp)
|
||||
layer = ds[0]
|
||||
for feat in layer:
|
||||
city = City.objects.get(name=feat['Name'].value)
|
||||
self.assertEqual(feat['Population'].value, city.population)
|
||||
self.assertEqual(Decimal(str(feat['Density'])), city.density)
|
||||
self.assertEqual(feat['Created'].value, city.dt)
|
||||
city = City.objects.get(name=feat["Name"].value)
|
||||
self.assertEqual(feat["Population"].value, city.population)
|
||||
self.assertEqual(Decimal(str(feat["Density"])), city.density)
|
||||
self.assertEqual(feat["Created"].value, city.dt)
|
||||
|
||||
# Comparing the geometries.
|
||||
pnt1, pnt2 = feat.geom, city.point
|
||||
@@ -110,14 +124,14 @@ class LayerMapTest(TestCase):
|
||||
# Only the first two features of this shapefile are valid.
|
||||
valid_feats = ds[0][:2]
|
||||
for feat in valid_feats:
|
||||
istate = Interstate.objects.get(name=feat['Name'].value)
|
||||
istate = Interstate.objects.get(name=feat["Name"].value)
|
||||
|
||||
if feat.fid == 0:
|
||||
self.assertEqual(Decimal(str(feat['Length'])), istate.length)
|
||||
self.assertEqual(Decimal(str(feat["Length"])), istate.length)
|
||||
elif feat.fid == 1:
|
||||
# Everything but the first two decimal digits were truncated,
|
||||
# because the Interstate model's `length` field has decimal_places=2.
|
||||
self.assertAlmostEqual(feat.get('Length'), float(istate.length), 2)
|
||||
self.assertAlmostEqual(feat.get("Length"), float(istate.length), 2)
|
||||
|
||||
for p1, p2 in zip(feat.geom, istate.path):
|
||||
self.assertAlmostEqual(p1[0], p2[0], 6)
|
||||
@@ -145,16 +159,20 @@ class LayerMapTest(TestCase):
|
||||
|
||||
# Specifying the source spatial reference system via the `source_srs` keyword.
|
||||
lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269)
|
||||
lm = LayerMapping(County, co_shp, co_mapping, source_srs='NAD83')
|
||||
lm = LayerMapping(County, co_shp, co_mapping, source_srs="NAD83")
|
||||
|
||||
# Unique may take tuple or string parameters.
|
||||
for arg in ('name', ('name', 'mpoly')):
|
||||
for arg in ("name", ("name", "mpoly")):
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
|
||||
|
||||
# Now test for failures
|
||||
|
||||
# Testing invalid params for the `unique` keyword.
|
||||
for e, arg in ((TypeError, 5.0), (ValueError, 'foobar'), (ValueError, ('name', 'mpolygon'))):
|
||||
for e, arg in (
|
||||
(TypeError, 5.0),
|
||||
(ValueError, "foobar"),
|
||||
(ValueError, ("name", "mpolygon")),
|
||||
):
|
||||
with self.assertRaises(e):
|
||||
LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
|
||||
|
||||
@@ -166,9 +184,9 @@ class LayerMapTest(TestCase):
|
||||
# Passing in invalid ForeignKey mapping parameters -- must be a dictionary
|
||||
# mapping for the model the ForeignKey points to.
|
||||
bad_fk_map1 = copy(co_mapping)
|
||||
bad_fk_map1['state'] = 'name'
|
||||
bad_fk_map1["state"] = "name"
|
||||
bad_fk_map2 = copy(co_mapping)
|
||||
bad_fk_map2['state'] = {'nombre': 'State'}
|
||||
bad_fk_map2["state"] = {"nombre": "State"}
|
||||
with self.assertRaises(TypeError):
|
||||
LayerMapping(County, co_shp, bad_fk_map1, transform=False)
|
||||
with self.assertRaises(LayerMapError):
|
||||
@@ -177,14 +195,14 @@ class LayerMapTest(TestCase):
|
||||
# There exist no State models for the ForeignKey mapping to work -- should raise
|
||||
# a MissingForeignKey exception (this error would be ignored if the `strict`
|
||||
# keyword is not set).
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique="name")
|
||||
with self.assertRaises(MissingForeignKey):
|
||||
lm.save(silent=True, strict=True)
|
||||
|
||||
# Now creating the state models so the ForeignKey mapping may work.
|
||||
State.objects.bulk_create([
|
||||
State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
|
||||
])
|
||||
State.objects.bulk_create(
|
||||
[State(name="Colorado"), State(name="Hawaii"), State(name="Texas")]
|
||||
)
|
||||
|
||||
# If a mapping is specified as a collection, all OGR fields that
|
||||
# are not collections will be converted into them. For example,
|
||||
@@ -199,7 +217,7 @@ class LayerMapTest(TestCase):
|
||||
# appended to the geometry collection of the unique model. Thus,
|
||||
# all of the various islands in Honolulu county will be in in one
|
||||
# database record with a MULTIPOLYGON type.
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique="name")
|
||||
lm.save(silent=True, strict=True)
|
||||
|
||||
# A reference that doesn't use the unique keyword; a new database record will
|
||||
@@ -216,15 +234,15 @@ class LayerMapTest(TestCase):
|
||||
def clear_counties():
|
||||
County.objects.all().delete()
|
||||
|
||||
State.objects.bulk_create([
|
||||
State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
|
||||
])
|
||||
State.objects.bulk_create(
|
||||
[State(name="Colorado"), State(name="Hawaii"), State(name="Texas")]
|
||||
)
|
||||
|
||||
# Initializing the LayerMapping object to use in these tests.
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique="name")
|
||||
|
||||
# Bad feature id ranges should raise a type error.
|
||||
bad_ranges = (5.0, 'foo', co_shp)
|
||||
bad_ranges = (5.0, "foo", co_shp)
|
||||
for bad in bad_ranges:
|
||||
with self.assertRaises(TypeError):
|
||||
lm.save(fid_range=bad)
|
||||
@@ -239,7 +257,7 @@ class LayerMapTest(TestCase):
|
||||
# one model is returned because the `unique` keyword was set.
|
||||
qs = County.objects.all()
|
||||
self.assertEqual(1, qs.count())
|
||||
self.assertEqual('Galveston', qs[0].name)
|
||||
self.assertEqual("Galveston", qs[0].name)
|
||||
|
||||
# Features IDs 5 and beyond for Honolulu County, Hawaii, and
|
||||
# FID 0 is for Pueblo County, Colorado.
|
||||
@@ -250,13 +268,13 @@ class LayerMapTest(TestCase):
|
||||
# Only Pueblo & Honolulu counties should be present because of
|
||||
# the `unique` keyword. Have to set `order_by` on this QuerySet
|
||||
# or else MySQL will return a different ordering than the other dbs.
|
||||
qs = County.objects.order_by('name')
|
||||
qs = County.objects.order_by("name")
|
||||
self.assertEqual(2, qs.count())
|
||||
hi, co = tuple(qs)
|
||||
hi_idx, co_idx = tuple(map(NAMES.index, ('Honolulu', 'Pueblo')))
|
||||
self.assertEqual('Pueblo', co.name)
|
||||
hi_idx, co_idx = tuple(map(NAMES.index, ("Honolulu", "Pueblo")))
|
||||
self.assertEqual("Pueblo", co.name)
|
||||
self.assertEqual(NUMS[co_idx], len(co.mpoly))
|
||||
self.assertEqual('Honolulu', hi.name)
|
||||
self.assertEqual("Honolulu", hi.name)
|
||||
self.assertEqual(NUMS[hi_idx], len(hi.mpoly))
|
||||
|
||||
# Testing the `step` keyword -- should get the same counties
|
||||
@@ -270,11 +288,11 @@ class LayerMapTest(TestCase):
|
||||
def test_model_inheritance(self):
|
||||
"Tests LayerMapping on inherited models. See #12093."
|
||||
icity_mapping = {
|
||||
'name': 'Name',
|
||||
'population': 'Population',
|
||||
'density': 'Density',
|
||||
'point': 'POINT',
|
||||
'dt': 'Created',
|
||||
"name": "Name",
|
||||
"population": "Population",
|
||||
"density": "Density",
|
||||
"point": "POINT",
|
||||
"dt": "Created",
|
||||
}
|
||||
# Parent model has geometry field.
|
||||
lm1 = LayerMapping(ICity1, city_shp, icity_mapping)
|
||||
@@ -289,14 +307,13 @@ class LayerMapTest(TestCase):
|
||||
|
||||
def test_invalid_layer(self):
|
||||
"Tests LayerMapping on invalid geometries. See #15378."
|
||||
invalid_mapping = {'point': 'POINT'}
|
||||
lm = LayerMapping(Invalid, invalid_shp, invalid_mapping,
|
||||
source_srs=4326)
|
||||
invalid_mapping = {"point": "POINT"}
|
||||
lm = LayerMapping(Invalid, invalid_shp, invalid_mapping, source_srs=4326)
|
||||
lm.save(silent=True)
|
||||
|
||||
def test_charfield_too_short(self):
|
||||
mapping = copy(city_mapping)
|
||||
mapping['name_short'] = 'Name'
|
||||
mapping["name_short"] = "Name"
|
||||
lm = LayerMapping(City, city_shp, mapping)
|
||||
with self.assertRaises(InvalidString):
|
||||
lm.save(silent=True, strict=True)
|
||||
@@ -304,15 +321,15 @@ class LayerMapTest(TestCase):
|
||||
def test_textfield(self):
|
||||
"String content fits also in a TextField"
|
||||
mapping = copy(city_mapping)
|
||||
mapping['name_txt'] = 'Name'
|
||||
mapping["name_txt"] = "Name"
|
||||
lm = LayerMapping(City, city_shp, mapping)
|
||||
lm.save(silent=True, strict=True)
|
||||
self.assertEqual(City.objects.count(), 3)
|
||||
self.assertEqual(City.objects.get(name='Houston').name_txt, "Houston")
|
||||
self.assertEqual(City.objects.get(name="Houston").name_txt, "Houston")
|
||||
|
||||
def test_encoded_name(self):
|
||||
""" Test a layer containing utf-8-encoded name """
|
||||
city_shp = shp_path / 'ch-city' / 'ch-city.shp'
|
||||
"""Test a layer containing utf-8-encoded name"""
|
||||
city_shp = shp_path / "ch-city" / "ch-city.shp"
|
||||
lm = LayerMapping(City, city_shp, city_mapping)
|
||||
lm.save(silent=True, strict=True)
|
||||
self.assertEqual(City.objects.count(), 1)
|
||||
@@ -320,10 +337,12 @@ class LayerMapTest(TestCase):
|
||||
|
||||
def test_null_geom_with_unique(self):
|
||||
"""LayerMapping may be created with a unique and a null geometry."""
|
||||
State.objects.bulk_create([State(name='Colorado'), State(name='Hawaii'), State(name='Texas')])
|
||||
hw = State.objects.get(name='Hawaii')
|
||||
hu = County.objects.create(name='Honolulu', state=hw, mpoly=None)
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
|
||||
State.objects.bulk_create(
|
||||
[State(name="Colorado"), State(name="Hawaii"), State(name="Texas")]
|
||||
)
|
||||
hw = State.objects.get(name="Hawaii")
|
||||
hu = County.objects.create(name="Honolulu", state=hw, mpoly=None)
|
||||
lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique="name")
|
||||
lm.save(silent=True, strict=True)
|
||||
hu.refresh_from_db()
|
||||
self.assertIsNotNone(hu.mpoly)
|
||||
@@ -341,9 +360,9 @@ class LayerMapTest(TestCase):
|
||||
"Test LayerMapping import of GeoJSON with a null string value."
|
||||
lm = LayerMapping(HasNulls, has_nulls_geojson, has_nulls_mapping)
|
||||
lm.save()
|
||||
self.assertEqual(HasNulls.objects.filter(name='None').count(), 0)
|
||||
self.assertEqual(HasNulls.objects.filter(name="None").count(), 0)
|
||||
num_empty = 1 if connection.features.interprets_empty_strings_as_nulls else 0
|
||||
self.assertEqual(HasNulls.objects.filter(name='').count(), num_empty)
|
||||
self.assertEqual(HasNulls.objects.filter(name="").count(), num_empty)
|
||||
self.assertEqual(HasNulls.objects.filter(name__isnull=True).count(), 1)
|
||||
|
||||
def test_nullable_boolean_imported(self):
|
||||
@@ -358,15 +377,24 @@ class LayerMapTest(TestCase):
|
||||
"""LayerMapping import of GeoJSON with a nullable date/time value."""
|
||||
lm = LayerMapping(HasNulls, has_nulls_geojson, has_nulls_mapping)
|
||||
lm.save()
|
||||
self.assertEqual(HasNulls.objects.filter(datetime__lt=datetime.date(1994, 8, 15)).count(), 1)
|
||||
self.assertEqual(HasNulls.objects.filter(datetime='2018-11-29T03:02:52').count(), 1)
|
||||
self.assertEqual(
|
||||
HasNulls.objects.filter(datetime__lt=datetime.date(1994, 8, 15)).count(), 1
|
||||
)
|
||||
self.assertEqual(
|
||||
HasNulls.objects.filter(datetime="2018-11-29T03:02:52").count(), 1
|
||||
)
|
||||
self.assertEqual(HasNulls.objects.filter(datetime__isnull=True).count(), 1)
|
||||
|
||||
def test_uuids_imported(self):
|
||||
"""LayerMapping import of GeoJSON with UUIDs."""
|
||||
lm = LayerMapping(HasNulls, has_nulls_geojson, has_nulls_mapping)
|
||||
lm.save()
|
||||
self.assertEqual(HasNulls.objects.filter(uuid='1378c26f-cbe6-44b0-929f-eb330d4991f5').count(), 1)
|
||||
self.assertEqual(
|
||||
HasNulls.objects.filter(
|
||||
uuid="1378c26f-cbe6-44b0-929f-eb330d4991f5"
|
||||
).count(),
|
||||
1,
|
||||
)
|
||||
|
||||
def test_null_number_imported_not_allowed(self):
|
||||
"""
|
||||
@@ -385,7 +413,7 @@ class LayerMapTest(TestCase):
|
||||
|
||||
class OtherRouter:
|
||||
def db_for_read(self, model, **hints):
|
||||
return 'other'
|
||||
return "other"
|
||||
|
||||
def db_for_write(self, model, **hints):
|
||||
return self.db_for_read(model, **hints)
|
||||
@@ -402,9 +430,9 @@ class OtherRouter:
|
||||
|
||||
@override_settings(DATABASE_ROUTERS=[OtherRouter()])
|
||||
class LayerMapRouterTest(TestCase):
|
||||
databases = {'default', 'other'}
|
||||
databases = {"default", "other"}
|
||||
|
||||
@unittest.skipUnless(len(settings.DATABASES) > 1, 'multiple databases required')
|
||||
@unittest.skipUnless(len(settings.DATABASES) > 1, "multiple databases required")
|
||||
def test_layermapping_default_db(self):
|
||||
lm = LayerMapping(City, city_shp, city_mapping)
|
||||
self.assertEqual(lm.using, 'other')
|
||||
self.assertEqual(lm.using, "other")
|
||||
|
||||
@@ -9,10 +9,12 @@ if connection.features.supports_raster:
|
||||
# PostGIS 3+ requires postgis_raster extension.
|
||||
if pg_version[1:] >= (3,):
|
||||
operations = [
|
||||
CreateExtension('postgis_raster'),
|
||||
CreateExtension("postgis_raster"),
|
||||
]
|
||||
else:
|
||||
operations = []
|
||||
|
||||
else:
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
operations = []
|
||||
|
||||
@@ -6,42 +6,67 @@ from django.db.models import deletion
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('rasterapp', '0001_setup_extensions'),
|
||||
("rasterapp", "0001_setup_extensions"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='RasterModel',
|
||||
name="RasterModel",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rast', models.fields.RasterField(
|
||||
blank=True,
|
||||
null=True,
|
||||
srid=4326,
|
||||
verbose_name='A Verbose Raster Name',
|
||||
)),
|
||||
('rastprojected', models.fields.RasterField(
|
||||
null=True,
|
||||
srid=3086,
|
||||
verbose_name='A Projected Raster Table',
|
||||
)),
|
||||
('geom', models.fields.PointField(null=True, srid=4326)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"rast",
|
||||
models.fields.RasterField(
|
||||
blank=True,
|
||||
null=True,
|
||||
srid=4326,
|
||||
verbose_name="A Verbose Raster Name",
|
||||
),
|
||||
),
|
||||
(
|
||||
"rastprojected",
|
||||
models.fields.RasterField(
|
||||
null=True,
|
||||
srid=3086,
|
||||
verbose_name="A Projected Raster Table",
|
||||
),
|
||||
),
|
||||
("geom", models.fields.PointField(null=True, srid=4326)),
|
||||
],
|
||||
options={
|
||||
'required_db_features': ['supports_raster'],
|
||||
"required_db_features": ["supports_raster"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RasterRelatedModel',
|
||||
name="RasterRelatedModel",
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('rastermodel', models.ForeignKey(
|
||||
on_delete=deletion.CASCADE,
|
||||
to='rasterapp.rastermodel',
|
||||
)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"rastermodel",
|
||||
models.ForeignKey(
|
||||
on_delete=deletion.CASCADE,
|
||||
to="rasterapp.rastermodel",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'required_db_features': ['supports_raster'],
|
||||
"required_db_features": ["supports_raster"],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -2,12 +2,14 @@ from django.contrib.gis.db import models
|
||||
|
||||
|
||||
class RasterModel(models.Model):
|
||||
rast = models.RasterField('A Verbose Raster Name', null=True, srid=4326, spatial_index=True, blank=True)
|
||||
rastprojected = models.RasterField('A Projected Raster Table', srid=3086, null=True)
|
||||
rast = models.RasterField(
|
||||
"A Verbose Raster Name", null=True, srid=4326, spatial_index=True, blank=True
|
||||
)
|
||||
rastprojected = models.RasterField("A Projected Raster Table", srid=3086, null=True)
|
||||
geom = models.PointField(null=True)
|
||||
|
||||
class Meta:
|
||||
required_db_features = ['supports_raster']
|
||||
required_db_features = ["supports_raster"]
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
@@ -17,7 +19,7 @@ class RasterRelatedModel(models.Model):
|
||||
rastermodel = models.ForeignKey(RasterModel, models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
required_db_features = ['supports_raster']
|
||||
required_db_features = ["supports_raster"]
|
||||
|
||||
def __str__(self):
|
||||
return str(self.id)
|
||||
|
||||
@@ -16,21 +16,23 @@ from ..data.rasters.textrasters import JSON_RASTER
|
||||
from .models import RasterModel, RasterRelatedModel
|
||||
|
||||
|
||||
@skipUnlessDBFeature('supports_raster')
|
||||
@skipUnlessDBFeature("supports_raster")
|
||||
class RasterFieldTest(TransactionTestCase):
|
||||
available_apps = ['gis_tests.rasterapp']
|
||||
available_apps = ["gis_tests.rasterapp"]
|
||||
|
||||
def setUp(self):
|
||||
rast = GDALRaster({
|
||||
"srid": 4326,
|
||||
"origin": [0, 0],
|
||||
"scale": [-1, 1],
|
||||
"skew": [0, 0],
|
||||
"width": 5,
|
||||
"height": 5,
|
||||
"nr_of_bands": 2,
|
||||
"bands": [{"data": range(25)}, {"data": range(25, 50)}],
|
||||
})
|
||||
rast = GDALRaster(
|
||||
{
|
||||
"srid": 4326,
|
||||
"origin": [0, 0],
|
||||
"scale": [-1, 1],
|
||||
"skew": [0, 0],
|
||||
"width": 5,
|
||||
"height": 5,
|
||||
"nr_of_bands": 2,
|
||||
"bands": [{"data": range(25)}, {"data": range(25, 50)}],
|
||||
}
|
||||
)
|
||||
model_instance = RasterModel.objects.create(
|
||||
rast=rast,
|
||||
rastprojected=rast,
|
||||
@@ -53,19 +55,21 @@ class RasterFieldTest(TransactionTestCase):
|
||||
|
||||
def test_deserialize_with_pixeltype_flags(self):
|
||||
no_data = 3
|
||||
rast = GDALRaster({
|
||||
'srid': 4326,
|
||||
'origin': [0, 0],
|
||||
'scale': [-1, 1],
|
||||
'skew': [0, 0],
|
||||
'width': 1,
|
||||
'height': 1,
|
||||
'nr_of_bands': 1,
|
||||
'bands': [{'data': [no_data], 'nodata_value': no_data}],
|
||||
})
|
||||
rast = GDALRaster(
|
||||
{
|
||||
"srid": 4326,
|
||||
"origin": [0, 0],
|
||||
"scale": [-1, 1],
|
||||
"skew": [0, 0],
|
||||
"width": 1,
|
||||
"height": 1,
|
||||
"nr_of_bands": 1,
|
||||
"bands": [{"data": [no_data], "nodata_value": no_data}],
|
||||
}
|
||||
)
|
||||
r = RasterModel.objects.create(rast=rast)
|
||||
RasterModel.objects.filter(pk=r.pk).update(
|
||||
rast=Func(F('rast'), function='ST_SetBandIsNoData'),
|
||||
rast=Func(F("rast"), function="ST_SetBandIsNoData"),
|
||||
)
|
||||
r.refresh_from_db()
|
||||
band = r.rast.bands[0].data()
|
||||
@@ -96,13 +100,33 @@ class RasterFieldTest(TransactionTestCase):
|
||||
# value is as expected.
|
||||
self.assertEqual(
|
||||
[
|
||||
0.0, 1.0, 2.0, 3.0, 4.0,
|
||||
5.0, 6.0, 7.0, 8.0, 9.0,
|
||||
10.0, 11.0, 12.0, 13.0, 14.0,
|
||||
15.0, 16.0, 17.0, 18.0, 19.0,
|
||||
20.0, 21.0, 22.0, 23.0, 24.0
|
||||
0.0,
|
||||
1.0,
|
||||
2.0,
|
||||
3.0,
|
||||
4.0,
|
||||
5.0,
|
||||
6.0,
|
||||
7.0,
|
||||
8.0,
|
||||
9.0,
|
||||
10.0,
|
||||
11.0,
|
||||
12.0,
|
||||
13.0,
|
||||
14.0,
|
||||
15.0,
|
||||
16.0,
|
||||
17.0,
|
||||
18.0,
|
||||
19.0,
|
||||
20.0,
|
||||
21.0,
|
||||
22.0,
|
||||
23.0,
|
||||
24.0,
|
||||
],
|
||||
band
|
||||
band,
|
||||
)
|
||||
|
||||
def test_implicit_raster_transformation(self):
|
||||
@@ -113,7 +137,7 @@ class RasterFieldTest(TransactionTestCase):
|
||||
# Parse json raster
|
||||
rast = json.loads(JSON_RASTER)
|
||||
# Update srid to another value
|
||||
rast['srid'] = 3086
|
||||
rast["srid"] = 3086
|
||||
# Save model and get it from db
|
||||
r = RasterModel.objects.create(rast=rast)
|
||||
r.refresh_from_db()
|
||||
@@ -121,8 +145,12 @@ class RasterFieldTest(TransactionTestCase):
|
||||
self.assertEqual(r.rast.srs.srid, 4326)
|
||||
# Confirm geotransform is in lat/lon
|
||||
expected = [
|
||||
-87.9298551266551, 9.459646421449934e-06, 0.0, 23.94249275457565,
|
||||
0.0, -9.459646421449934e-06,
|
||||
-87.9298551266551,
|
||||
9.459646421449934e-06,
|
||||
0.0,
|
||||
23.94249275457565,
|
||||
0.0,
|
||||
-9.459646421449934e-06,
|
||||
]
|
||||
for val, exp in zip(r.rast.geotransform, expected):
|
||||
self.assertAlmostEqual(exp, val)
|
||||
@@ -132,8 +160,7 @@ class RasterFieldTest(TransactionTestCase):
|
||||
RasterField should accept a positional verbose name argument.
|
||||
"""
|
||||
self.assertEqual(
|
||||
RasterModel._meta.get_field('rast').verbose_name,
|
||||
'A Verbose Raster Name'
|
||||
RasterModel._meta.get_field("rast").verbose_name, "A Verbose Raster Name"
|
||||
)
|
||||
|
||||
def test_all_gis_lookups_with_rasters(self):
|
||||
@@ -143,13 +170,11 @@ class RasterFieldTest(TransactionTestCase):
|
||||
unprojected coordinate systems. This test just checks that the lookup
|
||||
can be called, but doesn't check if the result makes logical sense.
|
||||
"""
|
||||
from django.contrib.gis.db.backends.postgis.operations import (
|
||||
PostGISOperations,
|
||||
)
|
||||
from django.contrib.gis.db.backends.postgis.operations import PostGISOperations
|
||||
|
||||
# Create test raster and geom.
|
||||
rast = GDALRaster(json.loads(JSON_RASTER))
|
||||
stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326)
|
||||
stx_pnt = GEOSGeometry("POINT (-95.370401017314293 29.704867409475465)", 4326)
|
||||
stx_pnt.transform(3086)
|
||||
|
||||
lookups = [
|
||||
@@ -157,64 +182,83 @@ class RasterFieldTest(TransactionTestCase):
|
||||
for name, lookup in BaseSpatialField.get_lookups().items()
|
||||
if issubclass(lookup, GISLookup)
|
||||
]
|
||||
self.assertNotEqual(lookups, [], 'No lookups found')
|
||||
self.assertNotEqual(lookups, [], "No lookups found")
|
||||
# Loop through all the GIS lookups.
|
||||
for name, lookup in lookups:
|
||||
# Construct lookup filter strings.
|
||||
combo_keys = [
|
||||
field + name for field in [
|
||||
'rast__', 'rast__', 'rastprojected__0__', 'rast__',
|
||||
'rastprojected__', 'geom__', 'rast__',
|
||||
field + name
|
||||
for field in [
|
||||
"rast__",
|
||||
"rast__",
|
||||
"rastprojected__0__",
|
||||
"rast__",
|
||||
"rastprojected__",
|
||||
"geom__",
|
||||
"rast__",
|
||||
]
|
||||
]
|
||||
if issubclass(lookup, DistanceLookupBase):
|
||||
# Set lookup values for distance lookups.
|
||||
combo_values = [
|
||||
(rast, 50, 'spheroid'),
|
||||
(rast, 0, 50, 'spheroid'),
|
||||
(rast, 50, "spheroid"),
|
||||
(rast, 0, 50, "spheroid"),
|
||||
(rast, 0, D(km=1)),
|
||||
(stx_pnt, 0, 500),
|
||||
(stx_pnt, D(km=1000)),
|
||||
(rast, 500),
|
||||
(json.loads(JSON_RASTER), 500),
|
||||
]
|
||||
elif name == 'relate':
|
||||
elif name == "relate":
|
||||
# Set lookup values for the relate lookup.
|
||||
combo_values = [
|
||||
(rast, 'T*T***FF*'),
|
||||
(rast, 0, 'T*T***FF*'),
|
||||
(rast, 0, 'T*T***FF*'),
|
||||
(stx_pnt, 0, 'T*T***FF*'),
|
||||
(stx_pnt, 'T*T***FF*'),
|
||||
(rast, 'T*T***FF*'),
|
||||
(json.loads(JSON_RASTER), 'T*T***FF*'),
|
||||
(rast, "T*T***FF*"),
|
||||
(rast, 0, "T*T***FF*"),
|
||||
(rast, 0, "T*T***FF*"),
|
||||
(stx_pnt, 0, "T*T***FF*"),
|
||||
(stx_pnt, "T*T***FF*"),
|
||||
(rast, "T*T***FF*"),
|
||||
(json.loads(JSON_RASTER), "T*T***FF*"),
|
||||
]
|
||||
elif name == 'isvalid':
|
||||
elif name == "isvalid":
|
||||
# The isvalid lookup doesn't make sense for rasters.
|
||||
continue
|
||||
elif PostGISOperations.gis_operators[name].func:
|
||||
# Set lookup values for all function based operators.
|
||||
combo_values = [
|
||||
rast, (rast, 0), (rast, 0), (stx_pnt, 0), stx_pnt,
|
||||
rast, json.loads(JSON_RASTER)
|
||||
rast,
|
||||
(rast, 0),
|
||||
(rast, 0),
|
||||
(stx_pnt, 0),
|
||||
stx_pnt,
|
||||
rast,
|
||||
json.loads(JSON_RASTER),
|
||||
]
|
||||
else:
|
||||
# Override band lookup for these, as it's not supported.
|
||||
combo_keys[2] = 'rastprojected__' + name
|
||||
combo_keys[2] = "rastprojected__" + name
|
||||
# Set lookup values for all other operators.
|
||||
combo_values = [rast, None, rast, stx_pnt, stx_pnt, rast, json.loads(JSON_RASTER)]
|
||||
combo_values = [
|
||||
rast,
|
||||
None,
|
||||
rast,
|
||||
stx_pnt,
|
||||
stx_pnt,
|
||||
rast,
|
||||
json.loads(JSON_RASTER),
|
||||
]
|
||||
|
||||
# Create query filter combinations.
|
||||
self.assertEqual(
|
||||
len(combo_keys),
|
||||
len(combo_values),
|
||||
'Number of lookup names and values should be the same',
|
||||
"Number of lookup names and values should be the same",
|
||||
)
|
||||
combos = [x for x in zip(combo_keys, combo_values) if x[1]]
|
||||
self.assertEqual(
|
||||
[(n, x) for n, x in enumerate(combos) if x in combos[:n]],
|
||||
[],
|
||||
'There are repeated test lookups',
|
||||
"There are repeated test lookups",
|
||||
)
|
||||
combos = [{k: v} for k, v in combos]
|
||||
|
||||
@@ -236,14 +280,16 @@ class RasterFieldTest(TransactionTestCase):
|
||||
"""
|
||||
# Create test raster and geom.
|
||||
rast = GDALRaster(json.loads(JSON_RASTER))
|
||||
stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326)
|
||||
stx_pnt = GEOSGeometry("POINT (-95.370401017314293 29.704867409475465)", 4326)
|
||||
stx_pnt.transform(3086)
|
||||
|
||||
# Filter raster with different lookup raster formats.
|
||||
qs = RasterModel.objects.filter(rastprojected__dwithin=(rast, D(km=1)))
|
||||
self.assertEqual(qs.count(), 1)
|
||||
|
||||
qs = RasterModel.objects.filter(rastprojected__dwithin=(json.loads(JSON_RASTER), D(km=1)))
|
||||
qs = RasterModel.objects.filter(
|
||||
rastprojected__dwithin=(json.loads(JSON_RASTER), D(km=1))
|
||||
)
|
||||
self.assertEqual(qs.count(), 1)
|
||||
|
||||
qs = RasterModel.objects.filter(rastprojected__dwithin=(JSON_RASTER, D(km=1)))
|
||||
@@ -287,7 +333,10 @@ class RasterFieldTest(TransactionTestCase):
|
||||
self.assertEqual(qs.count(), 1)
|
||||
|
||||
# Filter through conditional statements.
|
||||
qs = RasterModel.objects.filter(Q(rast__dwithin=(rast, 40)) & Q(rastprojected__dwithin=(stx_pnt, D(km=10000))))
|
||||
qs = RasterModel.objects.filter(
|
||||
Q(rast__dwithin=(rast, 40))
|
||||
& Q(rastprojected__dwithin=(stx_pnt, D(km=10000)))
|
||||
)
|
||||
self.assertEqual(qs.count(), 1)
|
||||
|
||||
# Filter through different lookup.
|
||||
@@ -296,35 +345,43 @@ class RasterFieldTest(TransactionTestCase):
|
||||
|
||||
def test_lookup_input_tuple_too_long(self):
|
||||
rast = GDALRaster(json.loads(JSON_RASTER))
|
||||
msg = 'Tuple too long for lookup bbcontains.'
|
||||
msg = "Tuple too long for lookup bbcontains."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
RasterModel.objects.filter(rast__bbcontains=(rast, 1, 2))
|
||||
|
||||
def test_lookup_input_band_not_allowed(self):
|
||||
rast = GDALRaster(json.loads(JSON_RASTER))
|
||||
qs = RasterModel.objects.filter(rast__bbcontains=(rast, 1))
|
||||
msg = 'Band indices are not allowed for this operator, it works on bbox only.'
|
||||
msg = "Band indices are not allowed for this operator, it works on bbox only."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
qs.count()
|
||||
|
||||
def test_isvalid_lookup_with_raster_error(self):
|
||||
qs = RasterModel.objects.filter(rast__isvalid=True)
|
||||
msg = 'IsValid function requires a GeometryField in position 1, got RasterField.'
|
||||
msg = (
|
||||
"IsValid function requires a GeometryField in position 1, got RasterField."
|
||||
)
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
qs.count()
|
||||
|
||||
def test_result_of_gis_lookup_with_rasters(self):
|
||||
# Point is in the interior
|
||||
qs = RasterModel.objects.filter(rast__contains=GEOSGeometry('POINT (-0.5 0.5)', 4326))
|
||||
qs = RasterModel.objects.filter(
|
||||
rast__contains=GEOSGeometry("POINT (-0.5 0.5)", 4326)
|
||||
)
|
||||
self.assertEqual(qs.count(), 1)
|
||||
# Point is in the exterior
|
||||
qs = RasterModel.objects.filter(rast__contains=GEOSGeometry('POINT (0.5 0.5)', 4326))
|
||||
qs = RasterModel.objects.filter(
|
||||
rast__contains=GEOSGeometry("POINT (0.5 0.5)", 4326)
|
||||
)
|
||||
self.assertEqual(qs.count(), 0)
|
||||
# A point on the boundary is not contained properly
|
||||
qs = RasterModel.objects.filter(rast__contains_properly=GEOSGeometry('POINT (0 0)', 4326))
|
||||
qs = RasterModel.objects.filter(
|
||||
rast__contains_properly=GEOSGeometry("POINT (0 0)", 4326)
|
||||
)
|
||||
self.assertEqual(qs.count(), 0)
|
||||
# Raster is located left of the point
|
||||
qs = RasterModel.objects.filter(rast__left=GEOSGeometry('POINT (1 0)', 4326))
|
||||
qs = RasterModel.objects.filter(rast__left=GEOSGeometry("POINT (1 0)", 4326))
|
||||
self.assertEqual(qs.count(), 1)
|
||||
|
||||
def test_lookup_with_raster_bbox(self):
|
||||
@@ -363,7 +420,7 @@ class RasterFieldTest(TransactionTestCase):
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
RasterModel.objects.filter(geom__intersects=obj)
|
||||
# Test with invalid string lookup parameter
|
||||
obj = '00000'
|
||||
obj = "00000"
|
||||
msg = "Couldn't create spatial object from lookup value '%s'." % obj
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
RasterModel.objects.filter(geom__intersects=obj)
|
||||
@@ -378,14 +435,22 @@ class RasterFieldTest(TransactionTestCase):
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
RasterModel.objects.annotate(distance_from_point=Distance("geom", rast))
|
||||
with self.assertRaisesMessage(TypeError, msg):
|
||||
RasterModel.objects.annotate(distance_from_point=Distance("rastprojected", rast))
|
||||
msg = "Distance function requires a GeometryField in position 1, got RasterField."
|
||||
RasterModel.objects.annotate(
|
||||
distance_from_point=Distance("rastprojected", rast)
|
||||
)
|
||||
msg = (
|
||||
"Distance function requires a GeometryField in position 1, got RasterField."
|
||||
)
|
||||
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()
|
||||
|
||||
def test_lhs_with_index_rhs_without_index(self):
|
||||
with CaptureQueriesContext(connection) as queries:
|
||||
RasterModel.objects.filter(rast__0__contains=json.loads(JSON_RASTER)).exists()
|
||||
RasterModel.objects.filter(
|
||||
rast__0__contains=json.loads(JSON_RASTER)
|
||||
).exists()
|
||||
# It's easier to check the indexes in the generated SQL than to write
|
||||
# tests that cover all index combinations.
|
||||
self.assertRegex(queries[-1]['sql'], r'WHERE ST_Contains\([^)]*, 1, [^)]*, 1\)')
|
||||
self.assertRegex(queries[-1]["sql"], r"WHERE ST_Contains\([^)]*, 1, [^)]*, 1\)")
|
||||
|
||||
@@ -36,7 +36,7 @@ class Parcel(SimpleModel):
|
||||
city = models.ForeignKey(City, models.CASCADE)
|
||||
center1 = models.PointField()
|
||||
# Throwing a curveball w/`db_column` here.
|
||||
center2 = models.PointField(srid=2276, db_column='mycenter')
|
||||
center2 = models.PointField(srid=2276, db_column="mycenter")
|
||||
border1 = models.PolygonField()
|
||||
border2 = models.PolygonField(srid=2276)
|
||||
|
||||
@@ -56,7 +56,7 @@ class Article(SimpleModel):
|
||||
|
||||
class Book(SimpleModel):
|
||||
title = models.CharField(max_length=100)
|
||||
author = models.ForeignKey(Author, models.SET_NULL, related_name='books', null=True)
|
||||
author = models.ForeignKey(Author, models.SET_NULL, related_name="books", null=True)
|
||||
|
||||
|
||||
class Event(SimpleModel):
|
||||
|
||||
@@ -5,25 +5,23 @@ from django.test import TestCase, skipUnlessDBFeature
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import (
|
||||
Article, Author, Book, City, DirectoryEntry, Event, Location, Parcel,
|
||||
)
|
||||
from .models import Article, Author, Book, City, DirectoryEntry, Event, Location, Parcel
|
||||
|
||||
|
||||
class RelatedGeoModelTest(TestCase):
|
||||
fixtures = ['initial']
|
||||
fixtures = ["initial"]
|
||||
|
||||
def test02_select_related(self):
|
||||
"Testing `select_related` on geographic models (see #7126)."
|
||||
qs1 = City.objects.order_by('id')
|
||||
qs2 = City.objects.order_by('id').select_related()
|
||||
qs3 = City.objects.order_by('id').select_related('location')
|
||||
qs1 = City.objects.order_by("id")
|
||||
qs2 = City.objects.order_by("id").select_related()
|
||||
qs3 = City.objects.order_by("id").select_related("location")
|
||||
|
||||
# Reference data for what's in the fixtures.
|
||||
cities = (
|
||||
('Aurora', 'TX', -97.516111, 33.058333),
|
||||
('Roswell', 'NM', -104.528056, 33.387222),
|
||||
('Kecksburg', 'PA', -79.460734, 40.18476),
|
||||
("Aurora", "TX", -97.516111, 33.058333),
|
||||
("Roswell", "NM", -104.528056, 33.387222),
|
||||
("Kecksburg", "PA", -79.460734, 40.18476),
|
||||
)
|
||||
|
||||
for qs in (qs1, qs2, qs3):
|
||||
@@ -38,14 +36,18 @@ class RelatedGeoModelTest(TestCase):
|
||||
def test_related_extent_aggregate(self):
|
||||
"Testing the `Extent` aggregate on related geographic models."
|
||||
# This combines the Extent and Union aggregates into one query
|
||||
aggs = City.objects.aggregate(Extent('location__point'))
|
||||
aggs = City.objects.aggregate(Extent("location__point"))
|
||||
|
||||
# One for all locations, one that excludes New Mexico (Roswell).
|
||||
all_extent = (-104.528056, 29.763374, -79.460734, 40.18476)
|
||||
txpa_extent = (-97.516111, 29.763374, -79.460734, 40.18476)
|
||||
e1 = City.objects.aggregate(Extent('location__point'))['location__point__extent']
|
||||
e2 = City.objects.exclude(state='NM').aggregate(Extent('location__point'))['location__point__extent']
|
||||
e3 = aggs['location__point__extent']
|
||||
e1 = City.objects.aggregate(Extent("location__point"))[
|
||||
"location__point__extent"
|
||||
]
|
||||
e2 = City.objects.exclude(state="NM").aggregate(Extent("location__point"))[
|
||||
"location__point__extent"
|
||||
]
|
||||
e3 = aggs["location__point__extent"]
|
||||
|
||||
# The tolerance value is to four decimal places because of differences
|
||||
# between the Oracle and PostGIS spatial backends on the extent calculation.
|
||||
@@ -59,19 +61,19 @@ class RelatedGeoModelTest(TestCase):
|
||||
"""
|
||||
Test annotation with Extent GeoAggregate.
|
||||
"""
|
||||
cities = City.objects.annotate(points_extent=Extent('location__point')).order_by('name')
|
||||
cities = City.objects.annotate(
|
||||
points_extent=Extent("location__point")
|
||||
).order_by("name")
|
||||
tol = 4
|
||||
self.assertAlmostEqual(
|
||||
cities[0].points_extent,
|
||||
(-97.516111, 33.058333, -97.516111, 33.058333),
|
||||
tol
|
||||
cities[0].points_extent, (-97.516111, 33.058333, -97.516111, 33.058333), tol
|
||||
)
|
||||
|
||||
@skipUnlessDBFeature('supports_union_aggr')
|
||||
@skipUnlessDBFeature("supports_union_aggr")
|
||||
def test_related_union_aggregate(self):
|
||||
"Testing the `Union` aggregate on related geographic models."
|
||||
# This combines the Extent and Union aggregates into one query
|
||||
aggs = City.objects.aggregate(Union('location__point'))
|
||||
aggs = City.objects.aggregate(Union("location__point"))
|
||||
|
||||
# These are the points that are components of the aggregate geographic
|
||||
# union that is returned. Each point # corresponds to City PK.
|
||||
@@ -87,11 +89,11 @@ class RelatedGeoModelTest(TestCase):
|
||||
ref_u1 = MultiPoint(p1, p2, p4, p5, p3, srid=4326)
|
||||
ref_u2 = MultiPoint(p2, p3, srid=4326)
|
||||
|
||||
u1 = City.objects.aggregate(Union('location__point'))['location__point__union']
|
||||
u1 = City.objects.aggregate(Union("location__point"))["location__point__union"]
|
||||
u2 = City.objects.exclude(
|
||||
name__in=('Roswell', 'Houston', 'Dallas', 'Fort Worth'),
|
||||
).aggregate(Union('location__point'))['location__point__union']
|
||||
u3 = aggs['location__point__union']
|
||||
name__in=("Roswell", "Houston", "Dallas", "Fort Worth"),
|
||||
).aggregate(Union("location__point"))["location__point__union"]
|
||||
u3 = aggs["location__point__union"]
|
||||
self.assertEqual(type(u1), MultiPoint)
|
||||
self.assertEqual(type(u3), MultiPoint)
|
||||
|
||||
@@ -111,11 +113,11 @@ class RelatedGeoModelTest(TestCase):
|
||||
# Constructing a dummy parcel border and getting the City instance for
|
||||
# assigning the FK.
|
||||
b1 = GEOSGeometry(
|
||||
'POLYGON((-97.501205 33.052520,-97.501205 33.052576,'
|
||||
'-97.501150 33.052576,-97.501150 33.052520,-97.501205 33.052520))',
|
||||
srid=4326
|
||||
"POLYGON((-97.501205 33.052520,-97.501205 33.052576,"
|
||||
"-97.501150 33.052576,-97.501150 33.052520,-97.501205 33.052520))",
|
||||
srid=4326,
|
||||
)
|
||||
pcity = City.objects.get(name='Aurora')
|
||||
pcity = City.objects.get(name="Aurora")
|
||||
|
||||
# First parcel has incorrect center point that is equal to the City;
|
||||
# it also has a second border that is different from the first as a
|
||||
@@ -123,7 +125,9 @@ class RelatedGeoModelTest(TestCase):
|
||||
c1 = pcity.location.point
|
||||
c2 = c1.transform(2276, clone=True)
|
||||
b2 = c2.buffer(100)
|
||||
Parcel.objects.create(name='P1', city=pcity, center1=c1, center2=c2, border1=b1, border2=b2)
|
||||
Parcel.objects.create(
|
||||
name="P1", city=pcity, center1=c1, center2=c2, border1=b1, border2=b2
|
||||
)
|
||||
|
||||
# Now creating a second Parcel where the borders are the same, just
|
||||
# in different coordinate systems. The center points are also the
|
||||
@@ -131,20 +135,26 @@ class RelatedGeoModelTest(TestCase):
|
||||
# actually correspond to the centroid of the border.
|
||||
c1 = b1.centroid
|
||||
c2 = c1.transform(2276, clone=True)
|
||||
b2 = b1 if connection.features.supports_transform else b1.transform(2276, clone=True)
|
||||
Parcel.objects.create(name='P2', city=pcity, center1=c1, center2=c2, border1=b1, border2=b2)
|
||||
b2 = (
|
||||
b1
|
||||
if connection.features.supports_transform
|
||||
else b1.transform(2276, clone=True)
|
||||
)
|
||||
Parcel.objects.create(
|
||||
name="P2", city=pcity, center1=c1, center2=c2, border1=b1, border2=b2
|
||||
)
|
||||
|
||||
# Should return the second Parcel, which has the center within the
|
||||
# border.
|
||||
qs = Parcel.objects.filter(center1__within=F('border1'))
|
||||
qs = Parcel.objects.filter(center1__within=F("border1"))
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('P2', qs[0].name)
|
||||
self.assertEqual("P2", qs[0].name)
|
||||
|
||||
# This time center2 is in a different coordinate system and needs to be
|
||||
# wrapped in transformation SQL.
|
||||
qs = Parcel.objects.filter(center2__within=F('border1'))
|
||||
qs = Parcel.objects.filter(center2__within=F("border1"))
|
||||
if connection.features.supports_transform:
|
||||
self.assertEqual('P2', qs.get().name)
|
||||
self.assertEqual("P2", qs.get().name)
|
||||
else:
|
||||
msg = "This backend doesn't support the Transform function."
|
||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||
@@ -152,14 +162,14 @@ class RelatedGeoModelTest(TestCase):
|
||||
|
||||
# Should return the first Parcel, which has the center point equal
|
||||
# to the point in the City ForeignKey.
|
||||
qs = Parcel.objects.filter(center1=F('city__location__point'))
|
||||
qs = Parcel.objects.filter(center1=F("city__location__point"))
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual('P1', qs[0].name)
|
||||
self.assertEqual("P1", qs[0].name)
|
||||
|
||||
# This time the city column should be wrapped in transformation SQL.
|
||||
qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
|
||||
qs = Parcel.objects.filter(border2__contains=F("city__location__point"))
|
||||
if connection.features.supports_transform:
|
||||
self.assertEqual('P1', qs.get().name)
|
||||
self.assertEqual("P1", qs.get().name)
|
||||
else:
|
||||
msg = "This backend doesn't support the Transform function."
|
||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||
@@ -176,21 +186,21 @@ class RelatedGeoModelTest(TestCase):
|
||||
for m, d, t in zip(gqs, gvqs, gvlqs):
|
||||
# The values should be Geometry objects and not raw strings returned
|
||||
# by the spatial database.
|
||||
self.assertIsInstance(d['point'], GEOSGeometry)
|
||||
self.assertIsInstance(d["point"], GEOSGeometry)
|
||||
self.assertIsInstance(t[1], GEOSGeometry)
|
||||
self.assertEqual(m.point, d['point'])
|
||||
self.assertEqual(m.point, d["point"])
|
||||
self.assertEqual(m.point, t[1])
|
||||
|
||||
@override_settings(USE_TZ=True)
|
||||
def test_07b_values(self):
|
||||
"Testing values() and values_list() with aware datetime. See #21565."
|
||||
Event.objects.create(name="foo", when=timezone.now())
|
||||
list(Event.objects.values_list('when'))
|
||||
list(Event.objects.values_list("when"))
|
||||
|
||||
def test08_defer_only(self):
|
||||
"Testing defer() and only() on Geographic models."
|
||||
qs = Location.objects.all()
|
||||
def_qs = Location.objects.defer('point')
|
||||
def_qs = Location.objects.defer("point")
|
||||
for loc, def_loc in zip(qs, def_qs):
|
||||
self.assertEqual(loc.point, def_loc.point)
|
||||
|
||||
@@ -202,31 +212,33 @@ class RelatedGeoModelTest(TestCase):
|
||||
# ID column is selected instead of ID column for the city.
|
||||
city_ids = (1, 2, 3, 4, 5)
|
||||
loc_ids = (1, 2, 3, 5, 4)
|
||||
ids_qs = City.objects.order_by('id').values('id', 'location__id')
|
||||
ids_qs = City.objects.order_by("id").values("id", "location__id")
|
||||
for val_dict, c_id, l_id in zip(ids_qs, city_ids, loc_ids):
|
||||
self.assertEqual(val_dict['id'], c_id)
|
||||
self.assertEqual(val_dict['location__id'], l_id)
|
||||
self.assertEqual(val_dict["id"], c_id)
|
||||
self.assertEqual(val_dict["location__id"], l_id)
|
||||
|
||||
def test10_combine(self):
|
||||
"Testing the combination of two QuerySets (#10807)."
|
||||
buf1 = City.objects.get(name='Aurora').location.point.buffer(0.1)
|
||||
buf2 = City.objects.get(name='Kecksburg').location.point.buffer(0.1)
|
||||
buf1 = City.objects.get(name="Aurora").location.point.buffer(0.1)
|
||||
buf2 = City.objects.get(name="Kecksburg").location.point.buffer(0.1)
|
||||
qs1 = City.objects.filter(location__point__within=buf1)
|
||||
qs2 = City.objects.filter(location__point__within=buf2)
|
||||
combined = qs1 | qs2
|
||||
names = [c.name for c in combined]
|
||||
self.assertEqual(2, len(names))
|
||||
self.assertIn('Aurora', names)
|
||||
self.assertIn('Kecksburg', names)
|
||||
self.assertIn("Aurora", names)
|
||||
self.assertIn("Kecksburg", names)
|
||||
|
||||
@skipUnlessDBFeature('allows_group_by_lob')
|
||||
@skipUnlessDBFeature("allows_group_by_lob")
|
||||
def test12a_count(self):
|
||||
"Testing `Count` aggregate on geo-fields."
|
||||
# The City, 'Fort Worth' uses the same location as Dallas.
|
||||
dallas = City.objects.get(name='Dallas')
|
||||
dallas = City.objects.get(name="Dallas")
|
||||
|
||||
# Count annotation should be 2 for the Dallas location now.
|
||||
loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id)
|
||||
loc = Location.objects.annotate(num_cities=Count("city")).get(
|
||||
id=dallas.location.id
|
||||
)
|
||||
self.assertEqual(2, loc.num_cities)
|
||||
|
||||
def test12b_count(self):
|
||||
@@ -234,25 +246,33 @@ class RelatedGeoModelTest(TestCase):
|
||||
# Should only be one author (Trevor Paglen) returned by this query, and
|
||||
# the annotation should have 3 for the number of books, see #11087.
|
||||
# Also testing with a values(), see #11489.
|
||||
qs = Author.objects.annotate(num_books=Count('books')).filter(num_books__gt=1)
|
||||
vqs = Author.objects.values('name').annotate(num_books=Count('books')).filter(num_books__gt=1)
|
||||
qs = Author.objects.annotate(num_books=Count("books")).filter(num_books__gt=1)
|
||||
vqs = (
|
||||
Author.objects.values("name")
|
||||
.annotate(num_books=Count("books"))
|
||||
.filter(num_books__gt=1)
|
||||
)
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual(3, qs[0].num_books)
|
||||
self.assertEqual(1, len(vqs))
|
||||
self.assertEqual(3, vqs[0]['num_books'])
|
||||
self.assertEqual(3, vqs[0]["num_books"])
|
||||
|
||||
@skipUnlessDBFeature('allows_group_by_lob')
|
||||
@skipUnlessDBFeature("allows_group_by_lob")
|
||||
def test13c_count(self):
|
||||
"Testing `Count` aggregate with `.values()`. See #15305."
|
||||
qs = Location.objects.filter(id=5).annotate(num_cities=Count('city')).values('id', 'point', 'num_cities')
|
||||
qs = (
|
||||
Location.objects.filter(id=5)
|
||||
.annotate(num_cities=Count("city"))
|
||||
.values("id", "point", "num_cities")
|
||||
)
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual(2, qs[0]['num_cities'])
|
||||
self.assertIsInstance(qs[0]['point'], GEOSGeometry)
|
||||
self.assertEqual(2, qs[0]["num_cities"])
|
||||
self.assertIsInstance(qs[0]["point"], GEOSGeometry)
|
||||
|
||||
def test13_select_related_null_fk(self):
|
||||
"Testing `select_related` on a nullable ForeignKey."
|
||||
Book.objects.create(title='Without Author')
|
||||
b = Book.objects.select_related('author').get(title='Without Author')
|
||||
Book.objects.create(title="Without Author")
|
||||
b = Book.objects.select_related("author").get(title="Without Author")
|
||||
# Should be `None`, and not a 'dummy' model.
|
||||
self.assertIsNone(b.author)
|
||||
|
||||
@@ -266,11 +286,13 @@ class RelatedGeoModelTest(TestCase):
|
||||
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
|
||||
# WHERE "relatedapp_city"."state" = 'TX';
|
||||
ref_geom = GEOSGeometry(
|
||||
'MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,'
|
||||
'-95.363151 29.763374,-96.801611 32.782057)'
|
||||
"MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,"
|
||||
"-95.363151 29.763374,-96.801611 32.782057)"
|
||||
)
|
||||
|
||||
coll = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
|
||||
coll = City.objects.filter(state="TX").aggregate(Collect("location__point"))[
|
||||
"location__point__collect"
|
||||
]
|
||||
# Even though Dallas and Ft. Worth share same point, Collect doesn't
|
||||
# consolidate -- that's why 4 points in MultiPoint.
|
||||
self.assertEqual(4, len(coll))
|
||||
@@ -278,7 +300,7 @@ class RelatedGeoModelTest(TestCase):
|
||||
|
||||
def test15_invalid_select_related(self):
|
||||
"Testing doing select_related on the related name manager of a unique FK. See #13934."
|
||||
qs = Article.objects.select_related('author__article')
|
||||
qs = Article.objects.select_related("author__article")
|
||||
# This triggers TypeError when `get_default_columns` has no `local_only`
|
||||
# keyword. The TypeError is swallowed if QuerySet is actually
|
||||
# evaluated as list generation swallows TypeError in CPython.
|
||||
@@ -286,8 +308,12 @@ class RelatedGeoModelTest(TestCase):
|
||||
|
||||
def test16_annotated_date_queryset(self):
|
||||
"Ensure annotated date querysets work if spatial backend is used. See #14648."
|
||||
birth_years = [dt.year for dt in
|
||||
list(Author.objects.annotate(num_books=Count('books')).dates('dob', 'year'))]
|
||||
birth_years = [
|
||||
dt.year
|
||||
for dt in list(
|
||||
Author.objects.annotate(num_books=Count("books")).dates("dob", "year")
|
||||
)
|
||||
]
|
||||
birth_years.sort()
|
||||
self.assertEqual([1950, 1974], birth_years)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
# Path where reference test data is located.
|
||||
TEST_DATA = os.path.join(os.path.dirname(__file__), 'data')
|
||||
TEST_DATA = os.path.join(os.path.dirname(__file__), "data")
|
||||
|
||||
|
||||
def tuplize(seq):
|
||||
@@ -24,16 +24,14 @@ def strconvert(d):
|
||||
|
||||
|
||||
def get_ds_file(name, ext):
|
||||
return os.path.join(TEST_DATA,
|
||||
name,
|
||||
name + '.%s' % ext
|
||||
)
|
||||
return os.path.join(TEST_DATA, name, name + ".%s" % ext)
|
||||
|
||||
|
||||
class TestObj:
|
||||
"""
|
||||
Base testing object, turns keyword args into attributes.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
@@ -43,7 +41,8 @@ class TestDS(TestObj):
|
||||
"""
|
||||
Object for testing GDAL data sources.
|
||||
"""
|
||||
def __init__(self, name, *, ext='shp', **kwargs):
|
||||
|
||||
def __init__(self, name, *, ext="shp", **kwargs):
|
||||
# Shapefile is default extension, unless specified otherwise.
|
||||
self.name = name
|
||||
self.ds = get_ds_file(name, ext)
|
||||
@@ -55,6 +54,7 @@ class TestGeom(TestObj):
|
||||
Testing object used for wrapping reference geometry data
|
||||
in GEOS/GDAL tests.
|
||||
"""
|
||||
|
||||
def __init__(self, *, coords=None, centroid=None, ext_ring_cs=None, **kwargs):
|
||||
# Converting lists to tuples of certain keyword args
|
||||
# so coordinate test cases will match (JSON has no
|
||||
@@ -71,6 +71,7 @@ class TestGeomSet:
|
||||
"""
|
||||
Each attribute of this object is a list of `TestGeom` instances.
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, [TestGeom(**strconvert(kw)) for kw in value])
|
||||
@@ -81,9 +82,10 @@ class TestDataMixin:
|
||||
Mixin used for GEOS/GDAL test cases that defines a `geometries`
|
||||
property, which returns and/or loads the reference geometry data.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def geometries(self):
|
||||
# Load up the test geometry data from fixture into global.
|
||||
with open(os.path.join(TEST_DATA, 'geometries.json')) as f:
|
||||
with open(os.path.join(TEST_DATA, "geometries.json")) as f:
|
||||
geometries = json.load(f)
|
||||
return TestGeomSet(**strconvert(geometries))
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.test import SimpleTestCase
|
||||
|
||||
|
||||
class FieldsTests(SimpleTestCase):
|
||||
|
||||
def test_area_field_deepcopy(self):
|
||||
field = AreaField(None)
|
||||
self.assertEqual(copy.deepcopy(field), field)
|
||||
@@ -20,21 +19,38 @@ class GeometryFieldTests(SimpleTestCase):
|
||||
def test_deconstruct_empty(self):
|
||||
field = GeometryField()
|
||||
*_, kwargs = field.deconstruct()
|
||||
self.assertEqual(kwargs, {'srid': 4326})
|
||||
self.assertEqual(kwargs, {"srid": 4326})
|
||||
|
||||
def test_deconstruct_values(self):
|
||||
field = GeometryField(
|
||||
srid=4067,
|
||||
dim=3,
|
||||
geography=True,
|
||||
extent=(50199.4814, 6582464.0358, -50000.0, 761274.6247, 7799839.8902, 50000.0),
|
||||
extent=(
|
||||
50199.4814,
|
||||
6582464.0358,
|
||||
-50000.0,
|
||||
761274.6247,
|
||||
7799839.8902,
|
||||
50000.0,
|
||||
),
|
||||
tolerance=0.01,
|
||||
)
|
||||
*_, kwargs = field.deconstruct()
|
||||
self.assertEqual(kwargs, {
|
||||
'srid': 4067,
|
||||
'dim': 3,
|
||||
'geography': True,
|
||||
'extent': (50199.4814, 6582464.0358, -50000.0, 761274.6247, 7799839.8902, 50000.0),
|
||||
'tolerance': 0.01,
|
||||
})
|
||||
self.assertEqual(
|
||||
kwargs,
|
||||
{
|
||||
"srid": 4067,
|
||||
"dim": 3,
|
||||
"geography": True,
|
||||
"extent": (
|
||||
50199.4814,
|
||||
6582464.0358,
|
||||
-50000.0,
|
||||
761274.6247,
|
||||
7799839.8902,
|
||||
50000.0,
|
||||
),
|
||||
"tolerance": 0.01,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -9,11 +9,10 @@ from django.utils.html import escape
|
||||
|
||||
|
||||
class GeometryFieldTest(SimpleTestCase):
|
||||
|
||||
def test_init(self):
|
||||
"Testing GeometryField initialization with defaults."
|
||||
fld = forms.GeometryField()
|
||||
for bad_default in ('blah', 3, 'FoO', None, 0):
|
||||
for bad_default in ("blah", 3, "FoO", None, 0):
|
||||
with self.subTest(bad_default=bad_default):
|
||||
with self.assertRaises(ValidationError):
|
||||
fld.clean(bad_default)
|
||||
@@ -23,7 +22,7 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
# Input that doesn't specify the SRID is assumed to be in the SRID
|
||||
# of the input field.
|
||||
fld = forms.GeometryField(srid=4326)
|
||||
geom = fld.clean('POINT(5 23)')
|
||||
geom = fld.clean("POINT(5 23)")
|
||||
self.assertEqual(4326, geom.srid)
|
||||
# Making the field in a different SRID from that of the geometry, and
|
||||
# asserting it transforms.
|
||||
@@ -31,9 +30,13 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
# Different PROJ versions use different transformations, all are
|
||||
# correct as having a 1 meter accuracy.
|
||||
tol = 1
|
||||
xform_geom = GEOSGeometry('POINT (951640.547328465 4219369.26171664)', srid=32140)
|
||||
xform_geom = GEOSGeometry(
|
||||
"POINT (951640.547328465 4219369.26171664)", srid=32140
|
||||
)
|
||||
# The cleaned geometry is transformed to 32140 (the widget map_srid is 3857).
|
||||
cleaned_geom = fld.clean('SRID=3857;POINT (-10615777.40976205 3473169.895707852)')
|
||||
cleaned_geom = fld.clean(
|
||||
"SRID=3857;POINT (-10615777.40976205 3473169.895707852)"
|
||||
)
|
||||
self.assertEqual(cleaned_geom.srid, 32140)
|
||||
self.assertTrue(xform_geom.equals_exact(cleaned_geom, tol))
|
||||
|
||||
@@ -52,22 +55,31 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
"Testing GeometryField's handling of different geometry types."
|
||||
# By default, all geometry types are allowed.
|
||||
fld = forms.GeometryField()
|
||||
for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'):
|
||||
for wkt in (
|
||||
"POINT(5 23)",
|
||||
"MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))",
|
||||
"LINESTRING(0 0, 1 1)",
|
||||
):
|
||||
with self.subTest(wkt=wkt):
|
||||
# to_python() uses the SRID of OpenLayersWidget if the
|
||||
# converted value doesn't have an SRID.
|
||||
self.assertEqual(GEOSGeometry(wkt, srid=fld.widget.map_srid), fld.clean(wkt))
|
||||
self.assertEqual(
|
||||
GEOSGeometry(wkt, srid=fld.widget.map_srid), fld.clean(wkt)
|
||||
)
|
||||
|
||||
pnt_fld = forms.GeometryField(geom_type='POINT')
|
||||
self.assertEqual(GEOSGeometry('POINT(5 23)', srid=pnt_fld.widget.map_srid), pnt_fld.clean('POINT(5 23)'))
|
||||
pnt_fld = forms.GeometryField(geom_type="POINT")
|
||||
self.assertEqual(
|
||||
GEOSGeometry("POINT(5 23)", srid=pnt_fld.widget.map_srid),
|
||||
pnt_fld.clean("POINT(5 23)"),
|
||||
)
|
||||
# a WKT for any other geom_type will be properly transformed by `to_python`
|
||||
self.assertEqual(
|
||||
GEOSGeometry('LINESTRING(0 0, 1 1)', srid=pnt_fld.widget.map_srid),
|
||||
pnt_fld.to_python('LINESTRING(0 0, 1 1)')
|
||||
GEOSGeometry("LINESTRING(0 0, 1 1)", srid=pnt_fld.widget.map_srid),
|
||||
pnt_fld.to_python("LINESTRING(0 0, 1 1)"),
|
||||
)
|
||||
# but rejected by `clean`
|
||||
with self.assertRaises(ValidationError):
|
||||
pnt_fld.clean('LINESTRING(0 0, 1 1)')
|
||||
pnt_fld.clean("LINESTRING(0 0, 1 1)")
|
||||
|
||||
def test_to_python(self):
|
||||
"""
|
||||
@@ -75,14 +87,14 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
a ValidationError.
|
||||
"""
|
||||
good_inputs = [
|
||||
'POINT(5 23)',
|
||||
'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))',
|
||||
'LINESTRING(0 0, 1 1)',
|
||||
"POINT(5 23)",
|
||||
"MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))",
|
||||
"LINESTRING(0 0, 1 1)",
|
||||
]
|
||||
bad_inputs = [
|
||||
'POINT(5)',
|
||||
'MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))',
|
||||
'BLAH(0 0, 1 1)',
|
||||
"POINT(5)",
|
||||
"MULTI POLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))",
|
||||
"BLAH(0 0, 1 1)",
|
||||
'{"type": "FeatureCollection", "features": ['
|
||||
'{"geometry": {"type": "Point", "coordinates": [508375, 148905]}, "type": "Feature"}]}',
|
||||
]
|
||||
@@ -90,7 +102,10 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
# to_python returns the same GEOSGeometry for a WKT
|
||||
for geo_input in good_inputs:
|
||||
with self.subTest(geo_input=geo_input):
|
||||
self.assertEqual(GEOSGeometry(geo_input, srid=fld.widget.map_srid), fld.to_python(geo_input))
|
||||
self.assertEqual(
|
||||
GEOSGeometry(geo_input, srid=fld.widget.map_srid),
|
||||
fld.to_python(geo_input),
|
||||
)
|
||||
# but raises a ValidationError for any other string
|
||||
for geo_input in bad_inputs:
|
||||
with self.subTest(geo_input=geo_input):
|
||||
@@ -100,21 +115,23 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
def test_to_python_different_map_srid(self):
|
||||
f = forms.GeometryField(widget=OpenLayersWidget)
|
||||
json = '{ "type": "Point", "coordinates": [ 5.0, 23.0 ] }'
|
||||
self.assertEqual(GEOSGeometry('POINT(5 23)', srid=f.widget.map_srid), f.to_python(json))
|
||||
self.assertEqual(
|
||||
GEOSGeometry("POINT(5 23)", srid=f.widget.map_srid), f.to_python(json)
|
||||
)
|
||||
|
||||
def test_field_with_text_widget(self):
|
||||
class PointForm(forms.Form):
|
||||
pt = forms.PointField(srid=4326, widget=forms.TextInput)
|
||||
|
||||
form = PointForm()
|
||||
cleaned_pt = form.fields['pt'].clean('POINT(5 23)')
|
||||
self.assertEqual(cleaned_pt, GEOSGeometry('POINT(5 23)', srid=4326))
|
||||
cleaned_pt = form.fields["pt"].clean("POINT(5 23)")
|
||||
self.assertEqual(cleaned_pt, GEOSGeometry("POINT(5 23)", srid=4326))
|
||||
self.assertEqual(4326, cleaned_pt.srid)
|
||||
with self.assertRaisesMessage(ValidationError, 'Invalid geometry value.'):
|
||||
form.fields['pt'].clean('POINT(5)')
|
||||
with self.assertRaisesMessage(ValidationError, "Invalid geometry value."):
|
||||
form.fields["pt"].clean("POINT(5)")
|
||||
|
||||
point = GEOSGeometry('SRID=4326;POINT(5 23)')
|
||||
form = PointForm(data={'pt': 'POINT(5 23)'}, initial={'pt': point})
|
||||
point = GEOSGeometry("SRID=4326;POINT(5 23)")
|
||||
form = PointForm(data={"pt": "POINT(5 23)"}, initial={"pt": point})
|
||||
self.assertFalse(form.has_changed())
|
||||
|
||||
def test_field_string_value(self):
|
||||
@@ -122,36 +139,39 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
Initialization of a geometry field with a valid/empty/invalid string.
|
||||
Only the invalid string should trigger an error log entry.
|
||||
"""
|
||||
|
||||
class PointForm(forms.Form):
|
||||
pt1 = forms.PointField(srid=4326)
|
||||
pt2 = forms.PointField(srid=4326)
|
||||
pt3 = forms.PointField(srid=4326)
|
||||
|
||||
form = PointForm({
|
||||
'pt1': 'SRID=4326;POINT(7.3 44)', # valid
|
||||
'pt2': '', # empty
|
||||
'pt3': 'PNT(0)', # invalid
|
||||
})
|
||||
form = PointForm(
|
||||
{
|
||||
"pt1": "SRID=4326;POINT(7.3 44)", # valid
|
||||
"pt2": "", # empty
|
||||
"pt3": "PNT(0)", # invalid
|
||||
}
|
||||
)
|
||||
|
||||
with self.assertLogs('django.contrib.gis', 'ERROR') as logger_calls:
|
||||
with self.assertLogs("django.contrib.gis", "ERROR") as logger_calls:
|
||||
output = str(form)
|
||||
|
||||
# The first point can't use assertInHTML() due to non-deterministic
|
||||
# ordering of the rendered dictionary.
|
||||
pt1_serialized = re.search(r'<textarea [^>]*>({[^<]+})<', output)[1]
|
||||
pt1_json = pt1_serialized.replace('"', '"')
|
||||
pt1_expected = GEOSGeometry(form.data['pt1']).transform(3857, clone=True)
|
||||
pt1_serialized = re.search(r"<textarea [^>]*>({[^<]+})<", output)[1]
|
||||
pt1_json = pt1_serialized.replace(""", '"')
|
||||
pt1_expected = GEOSGeometry(form.data["pt1"]).transform(3857, clone=True)
|
||||
self.assertJSONEqual(pt1_json, pt1_expected.json)
|
||||
|
||||
self.assertInHTML(
|
||||
'<textarea id="id_pt2" class="vSerializedField required" cols="150"'
|
||||
' rows="10" name="pt2"></textarea>',
|
||||
output
|
||||
output,
|
||||
)
|
||||
self.assertInHTML(
|
||||
'<textarea id="id_pt3" class="vSerializedField required" cols="150"'
|
||||
' rows="10" name="pt3"></textarea>',
|
||||
output
|
||||
output,
|
||||
)
|
||||
# Only the invalid PNT(0) triggers an error log entry.
|
||||
# Deserialization is called in form clean and in widget rendering.
|
||||
@@ -159,62 +179,74 @@ class GeometryFieldTest(SimpleTestCase):
|
||||
self.assertEqual(
|
||||
logger_calls.records[0].getMessage(),
|
||||
"Error creating geometry from value 'PNT(0)' (String input "
|
||||
"unrecognized as WKT EWKT, and HEXEWKB.)"
|
||||
"unrecognized as WKT EWKT, and HEXEWKB.)",
|
||||
)
|
||||
|
||||
|
||||
class SpecializedFieldTest(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.geometries = {
|
||||
'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
|
||||
'multipoint': GEOSGeometry("SRID=4326;MULTIPOINT("
|
||||
"(13.18634033203125 14.504356384277344),"
|
||||
"(13.207969665527 14.490966796875),"
|
||||
"(13.177070617675 14.454917907714))"),
|
||||
'linestring': GEOSGeometry("SRID=4326;LINESTRING("
|
||||
"-8.26171875 -0.52734375,"
|
||||
"-7.734375 4.21875,"
|
||||
"6.85546875 3.779296875,"
|
||||
"5.44921875 -3.515625)"),
|
||||
'multilinestring': GEOSGeometry("SRID=4326;MULTILINESTRING("
|
||||
"(-16.435546875 -2.98828125,"
|
||||
"-17.2265625 2.98828125,"
|
||||
"-0.703125 3.515625,"
|
||||
"-1.494140625 -3.33984375),"
|
||||
"(-8.0859375 -5.9765625,"
|
||||
"8.525390625 -8.7890625,"
|
||||
"12.392578125 -0.87890625,"
|
||||
"10.01953125 7.646484375))"),
|
||||
'polygon': GEOSGeometry("SRID=4326;POLYGON("
|
||||
"(-1.669921875 6.240234375,"
|
||||
"-3.8671875 -0.615234375,"
|
||||
"5.9765625 -3.955078125,"
|
||||
"18.193359375 3.955078125,"
|
||||
"9.84375 9.4921875,"
|
||||
"-1.669921875 6.240234375))"),
|
||||
'multipolygon': GEOSGeometry("SRID=4326;MULTIPOLYGON("
|
||||
"((-17.578125 13.095703125,"
|
||||
"-17.2265625 10.8984375,"
|
||||
"-13.974609375 10.1953125,"
|
||||
"-13.359375 12.744140625,"
|
||||
"-15.732421875 13.7109375,"
|
||||
"-17.578125 13.095703125)),"
|
||||
"((-8.525390625 5.537109375,"
|
||||
"-8.876953125 2.548828125,"
|
||||
"-5.888671875 1.93359375,"
|
||||
"-5.09765625 4.21875,"
|
||||
"-6.064453125 6.240234375,"
|
||||
"-8.525390625 5.537109375)))"),
|
||||
'geometrycollection': GEOSGeometry("SRID=4326;GEOMETRYCOLLECTION("
|
||||
"POINT(5.625 -0.263671875),"
|
||||
"POINT(6.767578125 -3.603515625),"
|
||||
"POINT(8.525390625 0.087890625),"
|
||||
"POINT(8.0859375 -2.13134765625),"
|
||||
"LINESTRING("
|
||||
"6.273193359375 -1.175537109375,"
|
||||
"5.77880859375 -1.812744140625,"
|
||||
"7.27294921875 -2.230224609375,"
|
||||
"7.657470703125 -1.25244140625))"),
|
||||
"point": GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
|
||||
"multipoint": GEOSGeometry(
|
||||
"SRID=4326;MULTIPOINT("
|
||||
"(13.18634033203125 14.504356384277344),"
|
||||
"(13.207969665527 14.490966796875),"
|
||||
"(13.177070617675 14.454917907714))"
|
||||
),
|
||||
"linestring": GEOSGeometry(
|
||||
"SRID=4326;LINESTRING("
|
||||
"-8.26171875 -0.52734375,"
|
||||
"-7.734375 4.21875,"
|
||||
"6.85546875 3.779296875,"
|
||||
"5.44921875 -3.515625)"
|
||||
),
|
||||
"multilinestring": GEOSGeometry(
|
||||
"SRID=4326;MULTILINESTRING("
|
||||
"(-16.435546875 -2.98828125,"
|
||||
"-17.2265625 2.98828125,"
|
||||
"-0.703125 3.515625,"
|
||||
"-1.494140625 -3.33984375),"
|
||||
"(-8.0859375 -5.9765625,"
|
||||
"8.525390625 -8.7890625,"
|
||||
"12.392578125 -0.87890625,"
|
||||
"10.01953125 7.646484375))"
|
||||
),
|
||||
"polygon": GEOSGeometry(
|
||||
"SRID=4326;POLYGON("
|
||||
"(-1.669921875 6.240234375,"
|
||||
"-3.8671875 -0.615234375,"
|
||||
"5.9765625 -3.955078125,"
|
||||
"18.193359375 3.955078125,"
|
||||
"9.84375 9.4921875,"
|
||||
"-1.669921875 6.240234375))"
|
||||
),
|
||||
"multipolygon": GEOSGeometry(
|
||||
"SRID=4326;MULTIPOLYGON("
|
||||
"((-17.578125 13.095703125,"
|
||||
"-17.2265625 10.8984375,"
|
||||
"-13.974609375 10.1953125,"
|
||||
"-13.359375 12.744140625,"
|
||||
"-15.732421875 13.7109375,"
|
||||
"-17.578125 13.095703125)),"
|
||||
"((-8.525390625 5.537109375,"
|
||||
"-8.876953125 2.548828125,"
|
||||
"-5.888671875 1.93359375,"
|
||||
"-5.09765625 4.21875,"
|
||||
"-6.064453125 6.240234375,"
|
||||
"-8.525390625 5.537109375)))"
|
||||
),
|
||||
"geometrycollection": GEOSGeometry(
|
||||
"SRID=4326;GEOMETRYCOLLECTION("
|
||||
"POINT(5.625 -0.263671875),"
|
||||
"POINT(6.767578125 -3.603515625),"
|
||||
"POINT(8.525390625 0.087890625),"
|
||||
"POINT(8.0859375 -2.13134765625),"
|
||||
"LINESTRING("
|
||||
"6.273193359375 -1.175537109375,"
|
||||
"5.77880859375 -1.812744140625,"
|
||||
"7.27294921875 -2.230224609375,"
|
||||
"7.657470703125 -1.25244140625))"
|
||||
),
|
||||
}
|
||||
|
||||
def assertMapWidget(self, form_instance):
|
||||
@@ -224,15 +256,15 @@ class SpecializedFieldTest(SimpleTestCase):
|
||||
"""
|
||||
self.assertTrue(form_instance.is_valid())
|
||||
rendered = form_instance.as_p()
|
||||
self.assertIn('new MapWidget(options);', rendered)
|
||||
self.assertIn('map_srid: 3857,', rendered)
|
||||
self.assertIn('gis/js/OLMapWidget.js', str(form_instance.media))
|
||||
self.assertIn("new MapWidget(options);", rendered)
|
||||
self.assertIn("map_srid: 3857,", rendered)
|
||||
self.assertIn("gis/js/OLMapWidget.js", str(form_instance.media))
|
||||
|
||||
def assertTextarea(self, geom, rendered):
|
||||
"""Makes sure the wkt and a textarea are in the content"""
|
||||
|
||||
self.assertIn('<textarea ', rendered)
|
||||
self.assertIn('required', rendered)
|
||||
self.assertIn("<textarea ", rendered)
|
||||
self.assertIn("required", rendered)
|
||||
ogr = geom.ogr
|
||||
ogr.transform(3857)
|
||||
self.assertIn(escape(ogr.json), rendered)
|
||||
@@ -243,109 +275,121 @@ class SpecializedFieldTest(SimpleTestCase):
|
||||
class PointForm(forms.Form):
|
||||
p = forms.PointField()
|
||||
|
||||
geom = self.geometries['point']
|
||||
form = PointForm(data={'p': geom})
|
||||
geom = self.geometries["point"]
|
||||
form = PointForm(data={"p": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(PointForm().is_valid())
|
||||
invalid = PointForm(data={'p': 'some invalid geom'})
|
||||
invalid = PointForm(data={"p": "some invalid geom"})
|
||||
self.assertFalse(invalid.is_valid())
|
||||
self.assertIn('Invalid geometry value', str(invalid.errors))
|
||||
self.assertIn("Invalid geometry value", str(invalid.errors))
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'point']:
|
||||
self.assertFalse(PointForm(data={'p': invalid.wkt}).is_valid())
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != "point"]:
|
||||
self.assertFalse(PointForm(data={"p": invalid.wkt}).is_valid())
|
||||
|
||||
def test_multipointfield(self):
|
||||
class PointForm(forms.Form):
|
||||
p = forms.MultiPointField()
|
||||
|
||||
geom = self.geometries['multipoint']
|
||||
form = PointForm(data={'p': geom})
|
||||
geom = self.geometries["multipoint"]
|
||||
form = PointForm(data={"p": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(PointForm().is_valid())
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'multipoint']:
|
||||
self.assertFalse(PointForm(data={'p': invalid.wkt}).is_valid())
|
||||
for invalid in [
|
||||
geo for key, geo in self.geometries.items() if key != "multipoint"
|
||||
]:
|
||||
self.assertFalse(PointForm(data={"p": invalid.wkt}).is_valid())
|
||||
|
||||
def test_linestringfield(self):
|
||||
class LineStringForm(forms.Form):
|
||||
f = forms.LineStringField()
|
||||
|
||||
geom = self.geometries['linestring']
|
||||
form = LineStringForm(data={'f': geom})
|
||||
geom = self.geometries["linestring"]
|
||||
form = LineStringForm(data={"f": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(LineStringForm().is_valid())
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'linestring']:
|
||||
self.assertFalse(LineStringForm(data={'p': invalid.wkt}).is_valid())
|
||||
for invalid in [
|
||||
geo for key, geo in self.geometries.items() if key != "linestring"
|
||||
]:
|
||||
self.assertFalse(LineStringForm(data={"p": invalid.wkt}).is_valid())
|
||||
|
||||
def test_multilinestringfield(self):
|
||||
class LineStringForm(forms.Form):
|
||||
f = forms.MultiLineStringField()
|
||||
|
||||
geom = self.geometries['multilinestring']
|
||||
form = LineStringForm(data={'f': geom})
|
||||
geom = self.geometries["multilinestring"]
|
||||
form = LineStringForm(data={"f": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(LineStringForm().is_valid())
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'multilinestring']:
|
||||
self.assertFalse(LineStringForm(data={'p': invalid.wkt}).is_valid())
|
||||
for invalid in [
|
||||
geo for key, geo in self.geometries.items() if key != "multilinestring"
|
||||
]:
|
||||
self.assertFalse(LineStringForm(data={"p": invalid.wkt}).is_valid())
|
||||
|
||||
def test_polygonfield(self):
|
||||
class PolygonForm(forms.Form):
|
||||
p = forms.PolygonField()
|
||||
|
||||
geom = self.geometries['polygon']
|
||||
form = PolygonForm(data={'p': geom})
|
||||
geom = self.geometries["polygon"]
|
||||
form = PolygonForm(data={"p": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(PolygonForm().is_valid())
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'polygon']:
|
||||
self.assertFalse(PolygonForm(data={'p': invalid.wkt}).is_valid())
|
||||
for invalid in [
|
||||
geo for key, geo in self.geometries.items() if key != "polygon"
|
||||
]:
|
||||
self.assertFalse(PolygonForm(data={"p": invalid.wkt}).is_valid())
|
||||
|
||||
def test_multipolygonfield(self):
|
||||
class PolygonForm(forms.Form):
|
||||
p = forms.MultiPolygonField()
|
||||
|
||||
geom = self.geometries['multipolygon']
|
||||
form = PolygonForm(data={'p': geom})
|
||||
geom = self.geometries["multipolygon"]
|
||||
form = PolygonForm(data={"p": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(PolygonForm().is_valid())
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'multipolygon']:
|
||||
self.assertFalse(PolygonForm(data={'p': invalid.wkt}).is_valid())
|
||||
for invalid in [
|
||||
geo for key, geo in self.geometries.items() if key != "multipolygon"
|
||||
]:
|
||||
self.assertFalse(PolygonForm(data={"p": invalid.wkt}).is_valid())
|
||||
|
||||
def test_geometrycollectionfield(self):
|
||||
class GeometryForm(forms.Form):
|
||||
g = forms.GeometryCollectionField()
|
||||
|
||||
geom = self.geometries['geometrycollection']
|
||||
form = GeometryForm(data={'g': geom})
|
||||
geom = self.geometries["geometrycollection"]
|
||||
form = GeometryForm(data={"g": geom})
|
||||
self.assertTextarea(geom, form.as_p())
|
||||
self.assertMapWidget(form)
|
||||
self.assertFalse(GeometryForm().is_valid())
|
||||
|
||||
for invalid in [geo for key, geo in self.geometries.items() if key != 'geometrycollection']:
|
||||
self.assertFalse(GeometryForm(data={'g': invalid.wkt}).is_valid())
|
||||
for invalid in [
|
||||
geo for key, geo in self.geometries.items() if key != "geometrycollection"
|
||||
]:
|
||||
self.assertFalse(GeometryForm(data={"g": invalid.wkt}).is_valid())
|
||||
|
||||
|
||||
class OSMWidgetTest(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.geometries = {
|
||||
'point': GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
|
||||
"point": GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)"),
|
||||
}
|
||||
|
||||
def test_osm_widget(self):
|
||||
class PointForm(forms.Form):
|
||||
p = forms.PointField(widget=forms.OSMWidget)
|
||||
|
||||
geom = self.geometries['point']
|
||||
form = PointForm(data={'p': geom})
|
||||
geom = self.geometries["point"]
|
||||
form = PointForm(data={"p": geom})
|
||||
rendered = form.as_p()
|
||||
|
||||
self.assertIn("ol.source.OSM()", rendered)
|
||||
@@ -358,11 +402,13 @@ class OSMWidgetTest(SimpleTestCase):
|
||||
|
||||
class PointForm(forms.Form):
|
||||
p = forms.PointField(
|
||||
widget=forms.OSMWidget(attrs={
|
||||
'default_lon': 20,
|
||||
'default_lat': 30,
|
||||
'default_zoom': 17,
|
||||
}),
|
||||
widget=forms.OSMWidget(
|
||||
attrs={
|
||||
"default_lon": 20,
|
||||
"default_lat": 30,
|
||||
"default_zoom": 17,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
form = PointForm()
|
||||
@@ -374,48 +420,49 @@ class OSMWidgetTest(SimpleTestCase):
|
||||
|
||||
|
||||
class GeometryWidgetTests(SimpleTestCase):
|
||||
|
||||
def test_get_context_attrs(self):
|
||||
# The Widget.get_context() attrs argument overrides self.attrs.
|
||||
widget = BaseGeometryWidget(attrs={'geom_type': 'POINT'})
|
||||
context = widget.get_context('point', None, attrs={'geom_type': 'POINT2'})
|
||||
self.assertEqual(context['geom_type'], 'POINT2')
|
||||
widget = BaseGeometryWidget(attrs={"geom_type": "POINT"})
|
||||
context = widget.get_context("point", None, attrs={"geom_type": "POINT2"})
|
||||
self.assertEqual(context["geom_type"], "POINT2")
|
||||
# Widget.get_context() returns expected name for geom_type.
|
||||
widget = BaseGeometryWidget(attrs={'geom_type': 'POLYGON'})
|
||||
context = widget.get_context('polygon', None, None)
|
||||
self.assertEqual(context['geom_type'], 'Polygon')
|
||||
widget = BaseGeometryWidget(attrs={"geom_type": "POLYGON"})
|
||||
context = widget.get_context("polygon", None, None)
|
||||
self.assertEqual(context["geom_type"], "Polygon")
|
||||
# Widget.get_context() returns 'Geometry' instead of 'Unknown'.
|
||||
widget = BaseGeometryWidget(attrs={'geom_type': 'GEOMETRY'})
|
||||
context = widget.get_context('geometry', None, None)
|
||||
self.assertEqual(context['geom_type'], 'Geometry')
|
||||
widget = BaseGeometryWidget(attrs={"geom_type": "GEOMETRY"})
|
||||
context = widget.get_context("geometry", None, None)
|
||||
self.assertEqual(context["geom_type"], "Geometry")
|
||||
|
||||
def test_subwidgets(self):
|
||||
widget = forms.BaseGeometryWidget()
|
||||
self.assertEqual(
|
||||
list(widget.subwidgets('name', 'value')),
|
||||
[{
|
||||
'is_hidden': False,
|
||||
'attrs': {
|
||||
'map_srid': 4326,
|
||||
'map_width': 600,
|
||||
'geom_type': 'GEOMETRY',
|
||||
'map_height': 400,
|
||||
'display_raw': False,
|
||||
},
|
||||
'name': 'name',
|
||||
'template_name': '',
|
||||
'value': 'value',
|
||||
'required': False,
|
||||
}]
|
||||
list(widget.subwidgets("name", "value")),
|
||||
[
|
||||
{
|
||||
"is_hidden": False,
|
||||
"attrs": {
|
||||
"map_srid": 4326,
|
||||
"map_width": 600,
|
||||
"geom_type": "GEOMETRY",
|
||||
"map_height": 400,
|
||||
"display_raw": False,
|
||||
},
|
||||
"name": "name",
|
||||
"template_name": "",
|
||||
"value": "value",
|
||||
"required": False,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def test_custom_serialization_widget(self):
|
||||
class CustomGeometryWidget(forms.BaseGeometryWidget):
|
||||
template_name = 'gis/openlayers.html'
|
||||
template_name = "gis/openlayers.html"
|
||||
deserialize_called = 0
|
||||
|
||||
def serialize(self, value):
|
||||
return value.json if value else ''
|
||||
return value.json if value else ""
|
||||
|
||||
def deserialize(self, value):
|
||||
self.deserialize_called += 1
|
||||
@@ -425,15 +472,15 @@ class GeometryWidgetTests(SimpleTestCase):
|
||||
p = forms.PointField(widget=CustomGeometryWidget)
|
||||
|
||||
point = GEOSGeometry("SRID=4326;POINT(9.052734375 42.451171875)")
|
||||
form = PointForm(data={'p': point})
|
||||
form = PointForm(data={"p": point})
|
||||
self.assertIn(escape(point.json), form.as_p())
|
||||
|
||||
CustomGeometryWidget.called = 0
|
||||
widget = form.fields['p'].widget
|
||||
widget = form.fields["p"].widget
|
||||
# Force deserialize use due to a string value
|
||||
self.assertIn(escape(point.json), widget.render('p', point.json))
|
||||
self.assertIn(escape(point.json), widget.render("p", point.json))
|
||||
self.assertEqual(widget.deserialize_called, 1)
|
||||
|
||||
form = PointForm(data={'p': point.json})
|
||||
form = PointForm(data={"p": point.json})
|
||||
self.assertTrue(form.is_valid())
|
||||
self.assertEqual(form.cleaned_data['p'].srid, 4326)
|
||||
self.assertEqual(form.cleaned_data["p"].srid, 4326)
|
||||
|
||||
@@ -17,11 +17,11 @@ if HAS_GEOIP2:
|
||||
# 'GeoLite2-City.mmdb'.
|
||||
@skipUnless(
|
||||
HAS_GEOIP2 and getattr(settings, "GEOIP_PATH", None),
|
||||
"GeoIP is required along with the GEOIP_PATH setting."
|
||||
"GeoIP is required along with the GEOIP_PATH setting.",
|
||||
)
|
||||
class GeoIPTest(SimpleTestCase):
|
||||
addr = '129.237.192.1'
|
||||
fqdn = 'ku.edu'
|
||||
addr = "129.237.192.1"
|
||||
fqdn = "ku.edu"
|
||||
|
||||
def test01_init(self):
|
||||
"GeoIP initialization."
|
||||
@@ -40,15 +40,15 @@ class GeoIPTest(SimpleTestCase):
|
||||
self.assertTrue(g._city)
|
||||
|
||||
# Only passing in the location of one database.
|
||||
city = os.path.join(path, 'GeoLite2-City.mmdb')
|
||||
cntry = os.path.join(path, 'GeoLite2-Country.mmdb')
|
||||
g4 = GeoIP2(city, country='')
|
||||
city = os.path.join(path, "GeoLite2-City.mmdb")
|
||||
cntry = os.path.join(path, "GeoLite2-Country.mmdb")
|
||||
g4 = GeoIP2(city, country="")
|
||||
self.assertIsNone(g4._country)
|
||||
g5 = GeoIP2(cntry, city='')
|
||||
g5 = GeoIP2(cntry, city="")
|
||||
self.assertIsNone(g5._city)
|
||||
|
||||
# Improper parameters.
|
||||
bad_params = (23, 'foo', 15.23)
|
||||
bad_params = (23, "foo", 15.23)
|
||||
for bad in bad_params:
|
||||
with self.assertRaises(GeoIP2Exception):
|
||||
GeoIP2(cache=bad)
|
||||
@@ -60,19 +60,19 @@ class GeoIPTest(SimpleTestCase):
|
||||
GeoIP2(bad, 0)
|
||||
|
||||
def test_no_database_file(self):
|
||||
invalid_path = os.path.join(os.path.dirname(__file__), 'data')
|
||||
msg = 'Could not load a database from %s.' % invalid_path
|
||||
invalid_path = os.path.join(os.path.dirname(__file__), "data")
|
||||
msg = "Could not load a database from %s." % invalid_path
|
||||
with self.assertRaisesMessage(GeoIP2Exception, msg):
|
||||
GeoIP2(invalid_path)
|
||||
|
||||
def test02_bad_query(self):
|
||||
"GeoIP query parameter checking."
|
||||
cntry_g = GeoIP2(city='<foo>')
|
||||
cntry_g = GeoIP2(city="<foo>")
|
||||
# No city database available, these calls should fail.
|
||||
with self.assertRaises(GeoIP2Exception):
|
||||
cntry_g.city('tmc.edu')
|
||||
cntry_g.city("tmc.edu")
|
||||
with self.assertRaises(GeoIP2Exception):
|
||||
cntry_g.coords('tmc.edu')
|
||||
cntry_g.coords("tmc.edu")
|
||||
|
||||
# Non-string query should raise TypeError
|
||||
with self.assertRaises(TypeError):
|
||||
@@ -80,92 +80,105 @@ class GeoIPTest(SimpleTestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
cntry_g.country_name(GeoIP2)
|
||||
|
||||
@mock.patch('socket.gethostbyname')
|
||||
@mock.patch("socket.gethostbyname")
|
||||
def test03_country(self, gethostbyname):
|
||||
"GeoIP country querying methods."
|
||||
gethostbyname.return_value = '128.249.1.1'
|
||||
g = GeoIP2(city='<foo>')
|
||||
gethostbyname.return_value = "128.249.1.1"
|
||||
g = GeoIP2(city="<foo>")
|
||||
|
||||
for query in (self.fqdn, self.addr):
|
||||
self.assertEqual(
|
||||
'US',
|
||||
"US",
|
||||
g.country_code(query),
|
||||
'Failed for func country_code and query %s' % query
|
||||
"Failed for func country_code and query %s" % query,
|
||||
)
|
||||
self.assertEqual(
|
||||
'United States',
|
||||
"United States",
|
||||
g.country_name(query),
|
||||
'Failed for func country_name and query %s' % query
|
||||
"Failed for func country_name and query %s" % query,
|
||||
)
|
||||
self.assertEqual(
|
||||
{'country_code': 'US', 'country_name': 'United States'},
|
||||
g.country(query)
|
||||
{"country_code": "US", "country_name": "United States"},
|
||||
g.country(query),
|
||||
)
|
||||
|
||||
@mock.patch('socket.gethostbyname')
|
||||
@mock.patch("socket.gethostbyname")
|
||||
def test04_city(self, gethostbyname):
|
||||
"GeoIP city querying methods."
|
||||
gethostbyname.return_value = '129.237.192.1'
|
||||
g = GeoIP2(country='<foo>')
|
||||
gethostbyname.return_value = "129.237.192.1"
|
||||
g = GeoIP2(country="<foo>")
|
||||
|
||||
for query in (self.fqdn, self.addr):
|
||||
# Country queries should still work.
|
||||
self.assertEqual(
|
||||
'US',
|
||||
"US",
|
||||
g.country_code(query),
|
||||
'Failed for func country_code and query %s' % query
|
||||
"Failed for func country_code and query %s" % query,
|
||||
)
|
||||
self.assertEqual(
|
||||
'United States',
|
||||
"United States",
|
||||
g.country_name(query),
|
||||
'Failed for func country_name and query %s' % query
|
||||
"Failed for func country_name and query %s" % query,
|
||||
)
|
||||
self.assertEqual(
|
||||
{'country_code': 'US', 'country_name': 'United States'},
|
||||
g.country(query)
|
||||
{"country_code": "US", "country_name": "United States"},
|
||||
g.country(query),
|
||||
)
|
||||
|
||||
# City information dictionary.
|
||||
d = g.city(query)
|
||||
self.assertEqual('NA', d['continent_code'])
|
||||
self.assertEqual('North America', d['continent_name'])
|
||||
self.assertEqual('US', d['country_code'])
|
||||
self.assertEqual('Lawrence', d['city'])
|
||||
self.assertEqual('KS', d['region'])
|
||||
self.assertEqual('America/Chicago', d['time_zone'])
|
||||
self.assertFalse(d['is_in_european_union'])
|
||||
self.assertEqual("NA", d["continent_code"])
|
||||
self.assertEqual("North America", d["continent_name"])
|
||||
self.assertEqual("US", d["country_code"])
|
||||
self.assertEqual("Lawrence", d["city"])
|
||||
self.assertEqual("KS", d["region"])
|
||||
self.assertEqual("America/Chicago", d["time_zone"])
|
||||
self.assertFalse(d["is_in_european_union"])
|
||||
geom = g.geos(query)
|
||||
self.assertIsInstance(geom, GEOSGeometry)
|
||||
|
||||
for e1, e2 in (geom.tuple, g.coords(query), g.lon_lat(query), g.lat_lon(query)):
|
||||
for e1, e2 in (
|
||||
geom.tuple,
|
||||
g.coords(query),
|
||||
g.lon_lat(query),
|
||||
g.lat_lon(query),
|
||||
):
|
||||
self.assertIsInstance(e1, float)
|
||||
self.assertIsInstance(e2, float)
|
||||
|
||||
def test06_ipv6_query(self):
|
||||
"GeoIP can lookup IPv6 addresses."
|
||||
g = GeoIP2()
|
||||
d = g.city('2002:81ed:c9a5::81ed:c9a5') # IPv6 address for www.nhm.ku.edu
|
||||
self.assertEqual('US', d['country_code'])
|
||||
self.assertEqual('Lawrence', d['city'])
|
||||
self.assertEqual('KS', d['region'])
|
||||
d = g.city("2002:81ed:c9a5::81ed:c9a5") # IPv6 address for www.nhm.ku.edu
|
||||
self.assertEqual("US", d["country_code"])
|
||||
self.assertEqual("Lawrence", d["city"])
|
||||
self.assertEqual("KS", d["region"])
|
||||
|
||||
def test_repr(self):
|
||||
path = settings.GEOIP_PATH
|
||||
g = GeoIP2(path=path)
|
||||
meta = g._reader.metadata()
|
||||
version = '%s.%s' % (meta.binary_format_major_version, meta.binary_format_minor_version)
|
||||
version = "%s.%s" % (
|
||||
meta.binary_format_major_version,
|
||||
meta.binary_format_minor_version,
|
||||
)
|
||||
country_path = g._country_file
|
||||
city_path = g._city_file
|
||||
expected = '<GeoIP2 [v%(version)s] _country_file="%(country)s", _city_file="%(city)s">' % {
|
||||
'version': version,
|
||||
'country': country_path,
|
||||
'city': city_path,
|
||||
}
|
||||
expected = (
|
||||
'<GeoIP2 [v%(version)s] _country_file="%(country)s", _city_file="%(city)s">'
|
||||
% {
|
||||
"version": version,
|
||||
"country": country_path,
|
||||
"city": city_path,
|
||||
}
|
||||
)
|
||||
self.assertEqual(repr(g), expected)
|
||||
|
||||
@mock.patch('socket.gethostbyname', return_value='expected')
|
||||
@mock.patch("socket.gethostbyname", return_value="expected")
|
||||
def test_check_query(self, gethostbyname):
|
||||
g = GeoIP2()
|
||||
self.assertEqual(g._check_query('127.0.0.1'), '127.0.0.1')
|
||||
self.assertEqual(g._check_query('2002:81ed:c9a5::81ed:c9a5'), '2002:81ed:c9a5::81ed:c9a5')
|
||||
self.assertEqual(g._check_query('invalid-ip-address'), 'expected')
|
||||
self.assertEqual(g._check_query("127.0.0.1"), "127.0.0.1")
|
||||
self.assertEqual(
|
||||
g._check_query("2002:81ed:c9a5::81ed:c9a5"), "2002:81ed:c9a5::81ed:c9a5"
|
||||
)
|
||||
self.assertEqual(g._check_query("invalid-ip-address"), "expected")
|
||||
|
||||
@@ -11,28 +11,29 @@ def test_mutation(raises=True):
|
||||
output_field = models.IntegerField()
|
||||
|
||||
def __init__(self):
|
||||
self.attribute = 'initial'
|
||||
super().__init__('initial', ['initial'])
|
||||
self.attribute = "initial"
|
||||
super().__init__("initial", ["initial"])
|
||||
|
||||
def as_sql(self, *args, **kwargs):
|
||||
mutation_func(self)
|
||||
return '', ()
|
||||
return "", ()
|
||||
|
||||
if raises:
|
||||
msg = 'TestFunc Func was mutated during compilation.'
|
||||
msg = "TestFunc Func was mutated during compilation."
|
||||
with test_case_instance.assertRaisesMessage(AssertionError, msg):
|
||||
getattr(TestFunc(), 'as_' + connection.vendor)(None, None)
|
||||
getattr(TestFunc(), "as_" + connection.vendor)(None, None)
|
||||
else:
|
||||
getattr(TestFunc(), 'as_' + connection.vendor)(None, None)
|
||||
getattr(TestFunc(), "as_" + connection.vendor)(None, None)
|
||||
|
||||
return test
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class FuncTestMixinTests(FuncTestMixin, SimpleTestCase):
|
||||
@test_mutation()
|
||||
def test_mutated_attribute(func):
|
||||
func.attribute = 'mutated'
|
||||
func.attribute = "mutated"
|
||||
|
||||
@test_mutation()
|
||||
def test_mutated_expressions(func):
|
||||
@@ -40,11 +41,11 @@ class FuncTestMixinTests(FuncTestMixin, SimpleTestCase):
|
||||
|
||||
@test_mutation()
|
||||
def test_mutated_expression(func):
|
||||
func.source_expressions[0].name = 'mutated'
|
||||
func.source_expressions[0].name = "mutated"
|
||||
|
||||
@test_mutation()
|
||||
def test_mutated_expression_deep(func):
|
||||
func.source_expressions[1].value[0] = 'mutated'
|
||||
func.source_expressions[1].value[0] = "mutated"
|
||||
|
||||
@test_mutation(raises=False)
|
||||
def test_not_mutated(func):
|
||||
|
||||
@@ -46,7 +46,7 @@ class DistanceTest(unittest.TestCase):
|
||||
def test_access_invalid(self):
|
||||
"Testing access in invalid units"
|
||||
d = D(m=100)
|
||||
self.assertFalse(hasattr(d, 'banana'))
|
||||
self.assertFalse(hasattr(d, "banana"))
|
||||
|
||||
def test_addition(self):
|
||||
"Test addition & subtraction"
|
||||
@@ -109,13 +109,13 @@ class DistanceTest(unittest.TestCase):
|
||||
d2 = D(km=1)
|
||||
|
||||
d3 = d1 + d2
|
||||
self.assertEqual(d3._default_unit, 'm')
|
||||
self.assertEqual(d3._default_unit, "m")
|
||||
d4 = d2 + d1
|
||||
self.assertEqual(d4._default_unit, 'km')
|
||||
self.assertEqual(d4._default_unit, "km")
|
||||
d5 = d1 * 2
|
||||
self.assertEqual(d5._default_unit, 'm')
|
||||
self.assertEqual(d5._default_unit, "m")
|
||||
d6 = d1 / 2
|
||||
self.assertEqual(d6._default_unit, 'm')
|
||||
self.assertEqual(d6._default_unit, "m")
|
||||
|
||||
def test_comparisons(self):
|
||||
"Testing comparisons"
|
||||
@@ -133,10 +133,10 @@ class DistanceTest(unittest.TestCase):
|
||||
d1 = D(m=100)
|
||||
d2 = D(km=3.5)
|
||||
|
||||
self.assertEqual(str(d1), '100.0 m')
|
||||
self.assertEqual(str(d2), '3.5 km')
|
||||
self.assertEqual(repr(d1), 'Distance(m=100.0)')
|
||||
self.assertEqual(repr(d2), 'Distance(km=3.5)')
|
||||
self.assertEqual(str(d1), "100.0 m")
|
||||
self.assertEqual(str(d2), "3.5 km")
|
||||
self.assertEqual(repr(d1), "Distance(m=100.0)")
|
||||
self.assertEqual(repr(d2), "Distance(km=3.5)")
|
||||
|
||||
def test_furlong(self):
|
||||
d = D(m=201.168)
|
||||
@@ -144,9 +144,15 @@ class DistanceTest(unittest.TestCase):
|
||||
|
||||
def test_unit_att_name(self):
|
||||
"Testing the `unit_attname` class method"
|
||||
unit_tuple = [('Yard', 'yd'), ('Nautical Mile', 'nm'), ('German legal metre', 'german_m'),
|
||||
('Indian yard', 'indian_yd'), ('Chain (Sears)', 'chain_sears'), ('Chain', 'chain'),
|
||||
('Furrow Long', 'furlong')]
|
||||
unit_tuple = [
|
||||
("Yard", "yd"),
|
||||
("Nautical Mile", "nm"),
|
||||
("German legal metre", "german_m"),
|
||||
("Indian yard", "indian_yd"),
|
||||
("Chain (Sears)", "chain_sears"),
|
||||
("Chain", "chain"),
|
||||
("Furrow Long", "furlong"),
|
||||
]
|
||||
for nm, att in unit_tuple:
|
||||
with self.subTest(nm=nm):
|
||||
self.assertEqual(att, D.unit_attname(nm))
|
||||
@@ -188,7 +194,7 @@ class AreaTest(unittest.TestCase):
|
||||
def test_access_invalid_a(self):
|
||||
"Testing access in invalid units"
|
||||
a = A(sq_m=100)
|
||||
self.assertFalse(hasattr(a, 'banana'))
|
||||
self.assertFalse(hasattr(a, "banana"))
|
||||
|
||||
def test_addition(self):
|
||||
"Test addition & subtraction"
|
||||
@@ -251,13 +257,13 @@ class AreaTest(unittest.TestCase):
|
||||
a2 = A(sq_km=1)
|
||||
|
||||
a3 = a1 + a2
|
||||
self.assertEqual(a3._default_unit, 'sq_m')
|
||||
self.assertEqual(a3._default_unit, "sq_m")
|
||||
a4 = a2 + a1
|
||||
self.assertEqual(a4._default_unit, 'sq_km')
|
||||
self.assertEqual(a4._default_unit, "sq_km")
|
||||
a5 = a1 * 2
|
||||
self.assertEqual(a5._default_unit, 'sq_m')
|
||||
self.assertEqual(a5._default_unit, "sq_m")
|
||||
a6 = a1 / 2
|
||||
self.assertEqual(a6._default_unit, 'sq_m')
|
||||
self.assertEqual(a6._default_unit, "sq_m")
|
||||
|
||||
def test_comparisons(self):
|
||||
"Testing comparisons"
|
||||
@@ -275,10 +281,10 @@ class AreaTest(unittest.TestCase):
|
||||
a1 = A(sq_m=100)
|
||||
a2 = A(sq_km=3.5)
|
||||
|
||||
self.assertEqual(str(a1), '100.0 sq_m')
|
||||
self.assertEqual(str(a2), '3.5 sq_km')
|
||||
self.assertEqual(repr(a1), 'Area(sq_m=100.0)')
|
||||
self.assertEqual(repr(a2), 'Area(sq_km=3.5)')
|
||||
self.assertEqual(str(a1), "100.0 sq_m")
|
||||
self.assertEqual(str(a2), "3.5 sq_km")
|
||||
self.assertEqual(repr(a1), "Area(sq_m=100.0)")
|
||||
self.assertEqual(repr(a2), "Area(sq_km=3.5)")
|
||||
|
||||
def test_hash(self):
|
||||
a1 = A(sq_m=100)
|
||||
|
||||
@@ -6,7 +6,6 @@ from django.test import SimpleTestCase
|
||||
|
||||
|
||||
class CPointerBaseTests(SimpleTestCase):
|
||||
|
||||
def test(self):
|
||||
destructor_mock = mock.Mock()
|
||||
|
||||
@@ -41,10 +40,10 @@ class CPointerBaseTests(SimpleTestCase):
|
||||
# results in a TypeError when trying to assign it to the `ptr` property.
|
||||
# Thus, memory addresses (integers) and pointers of the incorrect type
|
||||
# (in `bad_ptrs`) aren't allowed.
|
||||
bad_ptrs = (5, ctypes.c_char_p(b'foobar'))
|
||||
bad_ptrs = (5, ctypes.c_char_p(b"foobar"))
|
||||
for bad_ptr in bad_ptrs:
|
||||
for fg in (fg1, fg2):
|
||||
with self.assertRaisesMessage(TypeError, 'Incompatible pointer type'):
|
||||
with self.assertRaisesMessage(TypeError, "Incompatible pointer type"):
|
||||
fg.ptr = bad_ptr
|
||||
|
||||
# Object can be deleted without a destructor set.
|
||||
@@ -60,7 +59,7 @@ class CPointerBaseTests(SimpleTestCase):
|
||||
|
||||
# The destructor is called if set.
|
||||
fg = FakeGeom2()
|
||||
ptr = fg.ptr_type(ctypes.c_float(1.))
|
||||
ptr = fg.ptr_type(ctypes.c_float(1.0))
|
||||
fg.ptr = ptr
|
||||
del fg
|
||||
destructor_mock.assert_called_with(ptr)
|
||||
|
||||
@@ -4,20 +4,27 @@ from django.db import connection
|
||||
from django.test import TestCase, skipUnlessDBFeature
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
test_srs = ({
|
||||
'srid': 4326,
|
||||
'auth_name': ('EPSG', True),
|
||||
'auth_srid': 4326,
|
||||
# Only the beginning, because there are differences depending on installed libs
|
||||
'srtext': 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"',
|
||||
# +ellps=WGS84 has been removed in the 4326 proj string in proj-4.8
|
||||
'proj_re': r'\+proj=longlat (\+ellps=WGS84 )?(\+datum=WGS84 |\+towgs84=0,0,0,0,0,0,0 )\+no_defs ?',
|
||||
'spheroid': 'WGS 84', 'name': 'WGS 84',
|
||||
'geographic': True, 'projected': False, 'spatialite': True,
|
||||
# From proj's "cs2cs -le" and Wikipedia (semi-minor only)
|
||||
'ellipsoid': (6378137.0, 6356752.3, 298.257223563),
|
||||
'eprec': (1, 1, 9),
|
||||
'wkt': re.sub(r'[\s+]', '', """
|
||||
test_srs = (
|
||||
{
|
||||
"srid": 4326,
|
||||
"auth_name": ("EPSG", True),
|
||||
"auth_srid": 4326,
|
||||
# Only the beginning, because there are differences depending on installed libs
|
||||
"srtext": 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84"',
|
||||
# +ellps=WGS84 has been removed in the 4326 proj string in proj-4.8
|
||||
"proj_re": r"\+proj=longlat (\+ellps=WGS84 )?(\+datum=WGS84 |\+towgs84=0,0,0,0,0,0,0 )\+no_defs ?",
|
||||
"spheroid": "WGS 84",
|
||||
"name": "WGS 84",
|
||||
"geographic": True,
|
||||
"projected": False,
|
||||
"spatialite": True,
|
||||
# From proj's "cs2cs -le" and Wikipedia (semi-minor only)
|
||||
"ellipsoid": (6378137.0, 6356752.3, 298.257223563),
|
||||
"eprec": (1, 1, 9),
|
||||
"wkt": re.sub(
|
||||
r"[\s+]",
|
||||
"",
|
||||
"""
|
||||
GEOGCS["WGS 84",
|
||||
DATUM["WGS_1984",
|
||||
SPHEROID["WGS 84",6378137,298.257223563,
|
||||
@@ -28,37 +35,42 @@ test_srs = ({
|
||||
UNIT["degree",0.01745329251994328,
|
||||
AUTHORITY["EPSG","9122"]],
|
||||
AUTHORITY["EPSG","4326"]]
|
||||
""")
|
||||
}, {
|
||||
'srid': 32140,
|
||||
'auth_name': ('EPSG', False),
|
||||
'auth_srid': 32140,
|
||||
'srtext': (
|
||||
'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",'
|
||||
'DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"'
|
||||
),
|
||||
'proj_re': r'\+proj=lcc (\+lat_1=30.28333333333333? |\+lat_2=28.38333333333333? |\+lat_0=27.83333333333333? |'
|
||||
r'\+lon_0=-99 ){4}\+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?'
|
||||
r'(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0 )?\+units=m \+no_defs ?',
|
||||
'spheroid': 'GRS 1980', 'name': 'NAD83 / Texas South Central',
|
||||
'geographic': False, 'projected': True, 'spatialite': False,
|
||||
# From proj's "cs2cs -le" and Wikipedia (semi-minor only)
|
||||
'ellipsoid': (6378137.0, 6356752.31414, 298.257222101),
|
||||
'eprec': (1, 5, 10),
|
||||
})
|
||||
""",
|
||||
),
|
||||
},
|
||||
{
|
||||
"srid": 32140,
|
||||
"auth_name": ("EPSG", False),
|
||||
"auth_srid": 32140,
|
||||
"srtext": (
|
||||
'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",'
|
||||
'DATUM["North_American_Datum_1983",SPHEROID["GRS 1980"'
|
||||
),
|
||||
"proj_re": r"\+proj=lcc (\+lat_1=30.28333333333333? |\+lat_2=28.38333333333333? |\+lat_0=27.83333333333333? |"
|
||||
r"\+lon_0=-99 ){4}\+x_0=600000 \+y_0=4000000 (\+ellps=GRS80 )?"
|
||||
r"(\+datum=NAD83 |\+towgs84=0,0,0,0,0,0,0 )?\+units=m \+no_defs ?",
|
||||
"spheroid": "GRS 1980",
|
||||
"name": "NAD83 / Texas South Central",
|
||||
"geographic": False,
|
||||
"projected": True,
|
||||
"spatialite": False,
|
||||
# From proj's "cs2cs -le" and Wikipedia (semi-minor only)
|
||||
"ellipsoid": (6378137.0, 6356752.31414, 298.257222101),
|
||||
"eprec": (1, 5, 10),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@skipUnlessDBFeature("has_spatialrefsys_table")
|
||||
class SpatialRefSysTest(TestCase):
|
||||
|
||||
@cached_property
|
||||
def SpatialRefSys(self):
|
||||
return connection.ops.connection.ops.spatial_ref_sys()
|
||||
|
||||
def test_get_units(self):
|
||||
epsg_4326 = next(f for f in test_srs if f['srid'] == 4326)
|
||||
unit, unit_name = self.SpatialRefSys().get_units(epsg_4326['wkt'])
|
||||
self.assertEqual(unit_name, 'degree')
|
||||
epsg_4326 = next(f for f in test_srs if f["srid"] == 4326)
|
||||
unit, unit_name = self.SpatialRefSys().get_units(epsg_4326["wkt"])
|
||||
self.assertEqual(unit_name, "degree")
|
||||
self.assertAlmostEqual(unit, 0.01745329251994328)
|
||||
|
||||
def test_retrieve(self):
|
||||
@@ -66,40 +78,40 @@ class SpatialRefSysTest(TestCase):
|
||||
Test retrieval of SpatialRefSys model objects.
|
||||
"""
|
||||
for sd in test_srs:
|
||||
srs = self.SpatialRefSys.objects.get(srid=sd['srid'])
|
||||
self.assertEqual(sd['srid'], srs.srid)
|
||||
srs = self.SpatialRefSys.objects.get(srid=sd["srid"])
|
||||
self.assertEqual(sd["srid"], srs.srid)
|
||||
|
||||
# Some of the authority names are borked on Oracle, e.g., SRID=32140.
|
||||
# also, Oracle Spatial seems to add extraneous info to fields, hence the
|
||||
# the testing with the 'startswith' flag.
|
||||
auth_name, oracle_flag = sd['auth_name']
|
||||
auth_name, oracle_flag = sd["auth_name"]
|
||||
# Compare case-insensitively because srs.auth_name is lowercase
|
||||
# ("epsg") on Spatialite.
|
||||
if not connection.ops.oracle or oracle_flag:
|
||||
self.assertIs(srs.auth_name.upper().startswith(auth_name), True)
|
||||
|
||||
self.assertEqual(sd['auth_srid'], srs.auth_srid)
|
||||
self.assertEqual(sd["auth_srid"], srs.auth_srid)
|
||||
|
||||
# No PROJ and different srtext on Oracle.
|
||||
if not connection.ops.oracle:
|
||||
self.assertTrue(srs.wkt.startswith(sd['srtext']))
|
||||
self.assertRegex(srs.proj4text, sd['proj_re'])
|
||||
self.assertTrue(srs.wkt.startswith(sd["srtext"]))
|
||||
self.assertRegex(srs.proj4text, sd["proj_re"])
|
||||
|
||||
def test_osr(self):
|
||||
"""
|
||||
Test getting OSR objects from SpatialRefSys model objects.
|
||||
"""
|
||||
for sd in test_srs:
|
||||
sr = self.SpatialRefSys.objects.get(srid=sd['srid'])
|
||||
self.assertTrue(sr.spheroid.startswith(sd['spheroid']))
|
||||
self.assertEqual(sd['geographic'], sr.geographic)
|
||||
self.assertEqual(sd['projected'], sr.projected)
|
||||
self.assertIs(sr.name.startswith(sd['name']), True)
|
||||
sr = self.SpatialRefSys.objects.get(srid=sd["srid"])
|
||||
self.assertTrue(sr.spheroid.startswith(sd["spheroid"]))
|
||||
self.assertEqual(sd["geographic"], sr.geographic)
|
||||
self.assertEqual(sd["projected"], sr.projected)
|
||||
self.assertIs(sr.name.startswith(sd["name"]), True)
|
||||
# Testing the SpatialReference object directly.
|
||||
if not connection.ops.oracle:
|
||||
srs = sr.srs
|
||||
self.assertRegex(srs.proj, sd['proj_re'])
|
||||
self.assertTrue(srs.wkt.startswith(sd['srtext']))
|
||||
self.assertRegex(srs.proj, sd["proj_re"])
|
||||
self.assertTrue(srs.wkt.startswith(sd["srtext"]))
|
||||
|
||||
def test_ellipsoid(self):
|
||||
"""
|
||||
@@ -107,17 +119,17 @@ class SpatialRefSysTest(TestCase):
|
||||
"""
|
||||
for sd in test_srs:
|
||||
# Getting the ellipsoid and precision parameters.
|
||||
ellps1 = sd['ellipsoid']
|
||||
prec = sd['eprec']
|
||||
ellps1 = sd["ellipsoid"]
|
||||
prec = sd["eprec"]
|
||||
|
||||
# Getting our spatial reference and its ellipsoid
|
||||
srs = self.SpatialRefSys.objects.get(srid=sd['srid'])
|
||||
srs = self.SpatialRefSys.objects.get(srid=sd["srid"])
|
||||
ellps2 = srs.ellipsoid
|
||||
|
||||
for i in range(3):
|
||||
self.assertAlmostEqual(ellps1[i], ellps2[i], prec[i])
|
||||
|
||||
@skipUnlessDBFeature('supports_add_srs_entry')
|
||||
@skipUnlessDBFeature("supports_add_srs_entry")
|
||||
def test_add_entry(self):
|
||||
"""
|
||||
Test adding a new entry in the SpatialRefSys model using the
|
||||
@@ -126,10 +138,8 @@ class SpatialRefSysTest(TestCase):
|
||||
from django.contrib.gis.utils import add_srs_entry
|
||||
|
||||
add_srs_entry(3857)
|
||||
self.assertTrue(
|
||||
self.SpatialRefSys.objects.filter(srid=3857).exists()
|
||||
)
|
||||
self.assertTrue(self.SpatialRefSys.objects.filter(srid=3857).exists())
|
||||
srs = self.SpatialRefSys.objects.get(srid=3857)
|
||||
self.assertTrue(
|
||||
self.SpatialRefSys.get_spheroid(srs.wkt).startswith('SPHEROID[')
|
||||
self.SpatialRefSys.get_spheroid(srs.wkt).startswith("SPHEROID[")
|
||||
)
|
||||
|
||||
@@ -4,19 +4,19 @@ from django.core.exceptions import ImproperlyConfigured
|
||||
from django.db import ProgrammingError
|
||||
|
||||
try:
|
||||
from django.contrib.gis.db.backends.postgis.operations import (
|
||||
PostGISOperations,
|
||||
)
|
||||
from django.contrib.gis.db.backends.postgis.operations import PostGISOperations
|
||||
|
||||
HAS_POSTGRES = True
|
||||
except ImportError:
|
||||
HAS_POSTGRES = False
|
||||
|
||||
|
||||
if HAS_POSTGRES:
|
||||
|
||||
class FakeConnection:
|
||||
def __init__(self):
|
||||
self.settings_dict = {
|
||||
'NAME': 'test',
|
||||
"NAME": "test",
|
||||
}
|
||||
|
||||
class FakePostGISOperations(PostGISOperations):
|
||||
@@ -25,15 +25,15 @@ if HAS_POSTGRES:
|
||||
self.connection = FakeConnection()
|
||||
|
||||
def _get_postgis_func(self, func):
|
||||
if func == 'postgis_lib_version':
|
||||
if func == "postgis_lib_version":
|
||||
if self.version is None:
|
||||
raise ProgrammingError
|
||||
else:
|
||||
return self.version
|
||||
elif func == 'version':
|
||||
elif func == "version":
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError('This function was not expected to be called')
|
||||
raise NotImplementedError("This function was not expected to be called")
|
||||
|
||||
|
||||
@unittest.skipUnless(HAS_POSTGRES, "The psycopg2 driver is needed for these tests")
|
||||
@@ -43,34 +43,34 @@ class TestPostGISVersionCheck(unittest.TestCase):
|
||||
"""
|
||||
|
||||
def test_get_version(self):
|
||||
expect = '1.0.0'
|
||||
expect = "1.0.0"
|
||||
ops = FakePostGISOperations(expect)
|
||||
actual = ops.postgis_lib_version()
|
||||
self.assertEqual(expect, actual)
|
||||
|
||||
def test_version_classic_tuple(self):
|
||||
expect = ('1.2.3', 1, 2, 3)
|
||||
expect = ("1.2.3", 1, 2, 3)
|
||||
ops = FakePostGISOperations(expect[0])
|
||||
actual = ops.postgis_version_tuple()
|
||||
self.assertEqual(expect, actual)
|
||||
|
||||
def test_version_dev_tuple(self):
|
||||
expect = ('1.2.3dev', 1, 2, 3)
|
||||
expect = ("1.2.3dev", 1, 2, 3)
|
||||
ops = FakePostGISOperations(expect[0])
|
||||
actual = ops.postgis_version_tuple()
|
||||
self.assertEqual(expect, actual)
|
||||
|
||||
def test_version_loose_tuple(self):
|
||||
expect = ('1.2.3b1.dev0', 1, 2, 3)
|
||||
expect = ("1.2.3b1.dev0", 1, 2, 3)
|
||||
ops = FakePostGISOperations(expect[0])
|
||||
actual = ops.postgis_version_tuple()
|
||||
self.assertEqual(expect, actual)
|
||||
|
||||
def test_valid_version_numbers(self):
|
||||
versions = [
|
||||
('1.3.0', 1, 3, 0),
|
||||
('2.1.1', 2, 1, 1),
|
||||
('2.2.0dev', 2, 2, 0),
|
||||
("1.3.0", 1, 3, 0),
|
||||
("2.1.1", 2, 1, 1),
|
||||
("2.2.0dev", 2, 2, 0),
|
||||
]
|
||||
|
||||
for version in versions:
|
||||
|
||||
@@ -12,25 +12,30 @@ def skipUnlessGISLookup(*gis_lookups):
|
||||
"""
|
||||
Skip a test unless a database supports all of gis_lookups.
|
||||
"""
|
||||
|
||||
def decorator(test_func):
|
||||
@wraps(test_func)
|
||||
def skip_wrapper(*args, **kwargs):
|
||||
if any(key not in connection.ops.gis_operators for key in gis_lookups):
|
||||
raise unittest.SkipTest(
|
||||
"Database doesn't support all the lookups: %s" % ", ".join(gis_lookups)
|
||||
"Database doesn't support all the lookups: %s"
|
||||
% ", ".join(gis_lookups)
|
||||
)
|
||||
return test_func(*args, **kwargs)
|
||||
|
||||
return skip_wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
|
||||
_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]["ENGINE"].rsplit(".")[-1]
|
||||
# MySQL spatial indices can't handle NULL geometries.
|
||||
gisfield_may_be_null = _default_db != 'mysql'
|
||||
gisfield_may_be_null = _default_db != "mysql"
|
||||
|
||||
|
||||
class FuncTestMixin:
|
||||
"""Assert that Func expressions aren't mutated during their as_sql()."""
|
||||
|
||||
def setUp(self):
|
||||
def as_sql_wrapper(original_as_sql):
|
||||
def inner(*args, **kwargs):
|
||||
@@ -40,9 +45,12 @@ class FuncTestMixin:
|
||||
func.output_field
|
||||
__dict__original = copy.deepcopy(func.__dict__)
|
||||
result = original_as_sql(*args, **kwargs)
|
||||
msg = '%s Func was mutated during compilation.' % func.__class__.__name__
|
||||
msg = (
|
||||
"%s Func was mutated during compilation." % func.__class__.__name__
|
||||
)
|
||||
self.assertEqual(func.__dict__, __dict__original, msg)
|
||||
return result
|
||||
|
||||
return inner
|
||||
|
||||
def __getattribute__(self, name):
|
||||
@@ -51,12 +59,14 @@ class FuncTestMixin:
|
||||
try:
|
||||
as_sql = __getattribute__original(self, vendor_impl)
|
||||
except AttributeError:
|
||||
as_sql = __getattribute__original(self, 'as_sql')
|
||||
as_sql = __getattribute__original(self, "as_sql")
|
||||
return as_sql_wrapper(as_sql)
|
||||
|
||||
vendor_impl = 'as_' + connection.vendor
|
||||
vendor_impl = "as_" + connection.vendor
|
||||
__getattribute__original = Func.__getattribute__
|
||||
self.func_patcher = mock.patch.object(Func, '__getattribute__', __getattribute__)
|
||||
self.func_patcher = mock.patch.object(
|
||||
Func, "__getattribute__", __getattribute__
|
||||
)
|
||||
self.func_patcher.start()
|
||||
super().setUp()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user