5
5
from django.contrib.gis.gdal import DataSource
6
6
from django.contrib.gis.geos import GEOSGeometry, Point, LineString
7
7
from django.contrib.gis.measure import D # alias for Distance
8
from django.contrib.gis.db.models import GeoQ
9
from django.contrib.gis.tests.utils import oracle, postgis, no_oracle
8
from django.contrib.gis.tests.utils import oracle, postgis, spatialite, no_oracle, no_spatialite
11
from models import AustraliaCity, Interstate, SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
12
from data import au_cities, interstates, stx_cities, stx_zips
10
from models import AustraliaCity, Interstate, SouthTexasInterstate, \
11
SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
12
from data import au_cities, interstates, stx_interstates, stx_cities, stx_zips
14
14
class DistanceTest(unittest.TestCase):
32
32
# Loading up the cities.
33
33
def load_cities(city_model, data_tup):
34
34
for name, x, y in data_tup:
35
c = city_model(name=name, point=Point(x, y, srid=4326))
35
city_model(name=name, point=Point(x, y, srid=4326)).save()
37
def load_interstates(imodel, data_tup):
38
for name, wkt in data_tup:
39
imodel(name=name, path=wkt).save()
38
41
load_cities(SouthTexasCity, stx_cities)
39
42
load_cities(SouthTexasCityFt, stx_cities)
40
43
load_cities(AustraliaCity, au_cities)
42
45
self.assertEqual(9, SouthTexasCity.objects.count())
43
46
self.assertEqual(9, SouthTexasCityFt.objects.count())
44
47
self.assertEqual(11, AustraliaCity.objects.count())
46
49
# Loading up the South Texas Zip Codes.
47
50
for name, wkt in stx_zips:
48
51
poly = GEOSGeometry(wkt, srid=4269)
52
55
self.assertEqual(4, CensusZipcode.objects.count())
54
57
# Loading up the Interstates.
55
for name, wkt in interstates:
56
Interstate(name=name, line=GEOSGeometry(wkt, srid=4326)).save()
58
load_interstates(Interstate, interstates)
59
load_interstates(SouthTexasInterstate, stx_interstates)
57
61
self.assertEqual(1, Interstate.objects.count())
62
self.assertEqual(1, SouthTexasInterstate.objects.count())
59
65
def test02_dwithin(self):
60
66
"Testing the `dwithin` lookup type."
61
67
# Distances -- all should be equal (except for the
64
70
tx_dists = [(7000, 22965.83), D(km=7), D(mi=4.349)]
65
71
au_dists = [(0.5, 32000), D(km=32), D(mi=19.884)]
67
73
# Expected cities for Australia and Texas.
68
74
tx_cities = ['Downtown Houston', 'Southside Place']
69
75
au_cities = ['Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong']
86
92
if isinstance(dist, tuple):
87
93
if oracle: dist = dist[1]
88
94
else: dist = dist[0]
90
96
# Creating the query set.
91
97
qs = AustraliaCity.objects.order_by('name')
93
99
# A TypeError should be raised on PostGIS when trying to pass
94
# Distance objects into a DWithin query using a geodetic field.
100
# Distance objects into a DWithin query using a geodetic field.
95
101
self.assertRaises(TypeError, AustraliaCity.objects.filter, point__dwithin=(self.au_pnt, dist))
97
103
self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
99
105
def test03a_distance_method(self):
100
106
"Testing the `distance` GeoQuerySet method on projected coordinate systems."
101
107
# The point for La Grange, TX
102
108
lagrange = GEOSGeometry('POINT(-96.876369 29.905320)', 4326)
103
# Reference distances in feet and in meters. Got these values from
109
# Reference distances in feet and in meters. Got these values from
104
110
# using the provided raw SQL statements.
105
111
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 32140)) FROM distapp_southtexascity;
106
112
m_distances = [147075.069813, 139630.198056, 140888.552826,
107
113
138809.684197, 158309.246259, 212183.594374,
108
114
70870.188967, 165337.758878, 139196.085105]
109
115
# SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278)) FROM distapp_southtexascityft;
116
# Oracle 11 thinks this is not a projected coordinate system, so it's s
110
118
ft_distances = [482528.79154625, 458103.408123001, 462231.860397575,
111
119
455411.438904354, 519386.252102563, 696139.009211594,
112
120
232513.278304279, 542445.630586414, 456679.155883207]
115
123
# with different projected coordinate systems.
116
124
dist1 = SouthTexasCity.objects.distance(lagrange, field_name='point')
117
125
dist2 = SouthTexasCity.objects.distance(lagrange) # Using GEOSGeometry parameter
118
dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
119
dist4 = SouthTexasCityFt.objects.distance(lagrange)
126
if spatialite or oracle:
127
dist_qs = [dist1, dist2]
129
dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
130
dist4 = SouthTexasCityFt.objects.distance(lagrange)
131
dist_qs = [dist1, dist2, dist3, dist4]
121
133
# Original query done on PostGIS, have to adjust AlmostEqual tolerance
126
138
# Ensuring expected distances are returned for each distance queryset.
127
for qs in [dist1, dist2, dist3, dist4]:
128
140
for i, c in enumerate(qs):
129
141
self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
130
142
self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
132
145
def test03b_distance_method(self):
133
146
"Testing the `distance` GeoQuerySet method on geodetic coordnate systems."
134
147
if oracle: tol = 2
140
153
# PostGIS is limited to disance queries only to/from point geometries,
141
154
# ensuring a TypeError is raised if something else is put in.
142
self.assertRaises(TypeError, AustraliaCity.objects.distance, 'LINESTRING(0 0, 1 1)')
143
self.assertRaises(TypeError, AustraliaCity.objects.distance, LineString((0, 0), (1, 1)))
155
self.assertRaises(ValueError, AustraliaCity.objects.distance, 'LINESTRING(0 0, 1 1)')
156
self.assertRaises(ValueError, AustraliaCity.objects.distance, LineString((0, 0), (1, 1)))
145
158
# Got the reference distances using the raw SQL statements:
146
159
# SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11));
163
176
"Testing the `distance` GeoQuerySet method used with `transform` on a geographic field."
164
177
# Normally you can't compute distances from a geometry field
165
178
# that is not a PointField (on PostGIS).
166
self.assertRaises(TypeError, CensusZipcode.objects.distance, self.stx_pnt)
179
self.assertRaises(ValueError, CensusZipcode.objects.distance, self.stx_pnt)
168
181
# We'll be using a Polygon (created by buffering the centroid
169
182
# of 77005 to 100m) -- which aren't allowed in geographic distance
170
183
# queries normally, however our field has been transformed to
183
196
buf1 = z.poly.centroid.buffer(100)
184
197
buf2 = buf1.transform(4269, clone=True)
198
ref_zips = ['77002', '77025', '77401']
185
200
for buf in [buf1, buf2]:
186
201
qs = CensusZipcode.objects.exclude(name='77005').transform(32140).distance(buf)
187
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
202
self.assertEqual(ref_zips, self.get_names(qs))
188
203
for i, z in enumerate(qs):
189
204
self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
194
209
# (thus, Houston and Southside place will be excluded as tested in
195
210
# the `test02_dwithin` above).
196
211
qs1 = SouthTexasCity.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
197
qs2 = SouthTexasCityFt.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
213
# Can't determine the units on SpatiaLite from PROJ.4 string, and
214
# Oracle 11 incorrectly thinks it is not projected.
215
if spatialite or oracle:
218
qs2 = SouthTexasCityFt.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
199
222
cities = self.get_names(qs)
200
223
self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
206
229
# If we add a little more distance 77002 should be included.
207
230
qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
208
231
self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
210
234
def test05_geodetic_distance_lookups(self):
211
235
"Testing distance lookups on geodetic coordinate systems."
216
240
self.assertRaises(TypeError,
217
241
AustraliaCity.objects.filter(point__distance_lte=(mp, D(km=100))))
218
242
# Too many params (4 in this case) should raise a ValueError.
219
self.assertRaises(ValueError,
243
self.assertRaises(ValueError,
220
244
AustraliaCity.objects.filter, point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4'))
222
246
# Not enough params should raise a ValueError.
235
259
d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles.
237
261
# Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
238
gq1 = GeoQ(point__distance_lte=(wollongong.point, d1))
239
gq2 = GeoQ(point__distance_gte=(wollongong.point, d2))
262
gq1 = Q(point__distance_lte=(wollongong.point, d1))
263
gq2 = Q(point__distance_gte=(wollongong.point, d2))
240
264
qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2)
242
266
# Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
243
267
# instead (we should get the same results b/c accuracy variance won't matter
244
# in this test case). Using `Q` instead of `GeoQ` to be different (post-qsrf
245
# it doesn't matter).
268
# in this test case).
247
270
gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
248
271
gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
270
293
"Testing the `length` GeoQuerySet method."
271
294
# Reference query (should use `length_spheroid`).
272
295
# SELECT ST_length_spheroid(ST_GeomFromText('<wkt>', 4326) 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]');
273
len_m = 473504.769553813
274
qs = Interstate.objects.length()
277
self.assertAlmostEqual(len_m, qs[0].length.m, tol)
296
len_m1 = 473504.769553813
300
# Does not support geodetic coordinate systems.
301
self.assertRaises(ValueError, Interstate.objects.length)
303
qs = Interstate.objects.length()
306
self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
308
# Now doing length on a projected coordinate system.
309
i10 = SouthTexasInterstate.objects.length().get(name='I-10')
310
self.assertAlmostEqual(len_m2, i10.length.m, 2)
279
313
def test08_perimeter(self):
280
314
"Testing the `perimeter` GeoQuerySet method."
281
315
# Reference query: