1
0
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:
django-bot
2022-02-03 20:24:19 +01:00
committed by Mariusz Felisiak
parent f68fa8b45d
commit 9c19aff7c7
1992 changed files with 139577 additions and 96284 deletions

View File

@@ -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)

View File

@@ -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()

View File

@@ -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")

View File

@@ -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."

View File

@@ -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

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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"}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)),
]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)),
]

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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]),
}

View File

@@ -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))

View File

@@ -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/")

View File

@@ -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"
)

View File

@@ -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)

View File

@@ -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."

View File

@@ -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", "{}")

View File

@@ -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")),
)

View File

@@ -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)

View File

@@ -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",
),
]

View File

@@ -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])

View File

@@ -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")

View File

@@ -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

View File

@@ -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__)

View File

@@ -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)

View File

@@ -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):

View File

@@ -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 = []

View File

@@ -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

View File

@@ -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"

View File

@@ -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,
)

View File

@@ -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"}

View File

@@ -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)

View File

@@ -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",
}

View File

@@ -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")

View File

@@ -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 = []

View File

@@ -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"],
},
),
]

View File

@@ -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)

View File

@@ -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\)")

View File

@@ -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):

View File

@@ -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)

View File

@@ -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))

View File

@@ -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,
},
)

View File

@@ -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('&quot;', '"')
pt1_expected = GEOSGeometry(form.data['pt1']).transform(3857, clone=True)
pt1_serialized = re.search(r"<textarea [^>]*>({[^<]+})<", output)[1]
pt1_json = pt1_serialized.replace("&quot;", '"')
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)

View File

@@ -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")

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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[")
)

View File

@@ -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:

View File

@@ -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()