def testAddition(self): "Test addition & subtraction" d1 = D(m=100) d2 = D(m=200) d3 = d1 + d2 self.assertEqual(d3.m, 300) d3 += d1 self.assertEqual(d3.m, 400) d4 = d1 - d2 self.assertEqual(d4.m, -100) d4 -= d1 self.assertEqual(d4.m, -200) with self.assertRaises(TypeError): d1 + 1 with self.assertRaises(TypeError): d1 - 1 with self.assertRaises(TypeError): d1 += 1 with self.assertRaises(TypeError): d1 -= 1
def test_dwithin(self): """ Test the `dwithin` lookup type. """ # Distances -- all should be equal (except for the # degree/meter pair in au_cities, that's somewhat # approximate). tx_dists = [(7000, 22965.83), D(km=7), D(mi=4.349)] 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'] # Performing distance queries on two projected coordinate systems one # with units in meters and the other in units of U.S. survey feet. for dist in tx_dists: if isinstance(dist, tuple): dist1, dist2 = dist else: dist1 = dist2 = dist qs1 = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist1)) qs2 = SouthTexasCityFt.objects.filter(point__dwithin=(self.stx_pnt, dist2)) for qs in qs1, qs2: with self.subTest(dist=dist, qs=qs): self.assertEqual(tx_cities, self.get_names(qs)) # With a complex geometry expression 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: with self.subTest(dist=dist): type_error = isinstance(dist, D) and not oracle if isinstance(dist, tuple): if oracle or spatialite: # Result in meters dist = dist[1] else: # Result in units of the field dist = dist[0] # Creating the query set. 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() else: self.assertEqual( au_cities, self.get_names( qs.filter(point__dwithin=(self.au_pnt, dist))))
def testMultiplication(self): "Test multiplication & division" d1 = D(m=100) d3 = d1 * 2 self.assertEqual(d3.m, 200) d3 = 2 * d1 self.assertEqual(d3.m, 200) d3 *= 5 self.assertEqual(d3.m, 1000) d4 = d1 / 2 self.assertEqual(d4.m, 50) d4 /= 5 self.assertEqual(d4.m, 10) d5 = d1 / D(m=2) self.assertEqual(d5, 50) a5 = d1 * D(m=10) self.assertIsInstance(a5, Area) self.assertEqual(a5.sq_m, 100 * 10) with self.assertRaises(TypeError): d1 *= D(m=1) with self.assertRaises(TypeError): d1 /= D(m=1)
def test_distance_lookups(self): """ Test the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types. """ # Retrieving the cities within a 20km 'donut' w/a 7km radius 'hole' # (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) 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']) # 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)) # 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))
def testUnitsStr(self): "Testing conversion to strings" 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)')
def testComparisons(self): "Testing comparisons" d1 = D(m=100) d2 = D(km=1) d3 = D(km=0) self.assertGreater(d2, d1) self.assertEqual(d1, d1) self.assertLess(d1, d2) self.assertFalse(d3)
def testUnitConversions(self): "Testing default units during maths" d1 = D(m=100) d2 = D(km=1) d3 = d1 + d2 self.assertEqual(d3._default_unit, 'm') d4 = d2 + d1 self.assertEqual(d4._default_unit, 'km') d5 = d1 * 2 self.assertEqual(d5._default_unit, 'm') d6 = d1 / 2 self.assertEqual(d6._default_unit, 'm')
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)) for cities in [cities1, cities2]: self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
def testUnitAttName(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')] for nm, att in unit_tuple: with self.subTest(nm=nm): self.assertEqual(att, D.unit_attname(nm))
def testInit(self): "Testing initialization from valid units" d = Distance(m=100) self.assertEqual(d.m, 100) d1, d2, d3 = D(m=100), D(meter=100), D(metre=100) for d in (d1, d2, d3): self.assertEqual(d.m, 100) d = D(nm=100) self.assertEqual(d.m, 185200) y1, y2, y3 = D(yd=100), D(yard=100), D(Yard=100) for d in (y1, y2, y3): self.assertEqual(d.yd, 100) mm1, mm2 = D(millimeter=1000), D(MiLLiMeTeR=1000) for d in (mm1, mm2): self.assertEqual(d.m, 1.0) self.assertEqual(d.mm, 1000.0)
def test_all_gis_lookups_with_rasters(self): """ Evaluate all possible lookups for all input combinations (i.e. raster-raster, raster-geom, geom-raster) and for projected and unprojected coordinate systems. This test just checks that the lookup can be called, but doesn't check if the result makes logical sense. """ from djmodels.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.transform(3086) lookups = [(name, lookup) for name, lookup in BaseSpatialField.get_lookups().items() if issubclass(lookup, GISLookup)] 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__', ] ] if issubclass(lookup, DistanceLookupBase): # Set lookup values for distance lookups. combo_values = [ (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': # 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*'), ] 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) ] else: # Override band lookup for these, as it's not supported. 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) ] # Create query filter combinations. self.assertEqual( len(combo_keys), len(combo_values), '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', ) combos = [{k: v} for k, v in combos] for combo in combos: # Apply this query filter. qs = RasterModel.objects.filter(**combo) # Evaluate normal filter qs. self.assertIn(qs.count(), [0, 1]) # Evaluate on conditional Q expressions. qs = RasterModel.objects.filter(Q(**combos[0]) & Q(**combos[1])) self.assertIn(qs.count(), [0, 1])
def test_dwithin_gis_lookup_ouptut_with_rasters(self): """ Check the logical functionality of the dwithin lookup for different input parameters. """ # Create test raster and geom. rast = GDALRaster(json.loads(JSON_RASTER)) 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))) self.assertEqual(qs.count(), 1) qs = RasterModel.objects.filter(rastprojected__dwithin=(JSON_RASTER, D(km=1))) self.assertEqual(qs.count(), 1) # Filter in an unprojected coordinate system. qs = RasterModel.objects.filter(rast__dwithin=(rast, 40)) self.assertEqual(qs.count(), 1) # Filter with band index transform. qs = RasterModel.objects.filter(rast__1__dwithin=(rast, 1, 40)) self.assertEqual(qs.count(), 1) qs = RasterModel.objects.filter(rast__1__dwithin=(rast, 40)) self.assertEqual(qs.count(), 1) qs = RasterModel.objects.filter(rast__dwithin=(rast, 1, 40)) self.assertEqual(qs.count(), 1) # Filter raster by geom. qs = RasterModel.objects.filter(rast__dwithin=(stx_pnt, 500)) self.assertEqual(qs.count(), 1) qs = RasterModel.objects.filter(rastprojected__dwithin=(stx_pnt, D(km=10000))) self.assertEqual(qs.count(), 1) qs = RasterModel.objects.filter(rast__dwithin=(stx_pnt, 5)) self.assertEqual(qs.count(), 0) qs = RasterModel.objects.filter(rastprojected__dwithin=(stx_pnt, D(km=100))) self.assertEqual(qs.count(), 0) # Filter geom by raster. qs = RasterModel.objects.filter(geom__dwithin=(rast, 500)) self.assertEqual(qs.count(), 1) # Filter through related model. qs = RasterRelatedModel.objects.filter( rastermodel__rast__dwithin=(rast, 40)) self.assertEqual(qs.count(), 1) # Filter through related model with band index transform qs = RasterRelatedModel.objects.filter( rastermodel__rast__1__dwithin=(rast, 40)) 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)))) self.assertEqual(qs.count(), 1) # Filter through different lookup. qs = RasterModel.objects.filter(rastprojected__bbcontains=rast) self.assertEqual(qs.count(), 1)
def test_mysql_geodetic_distance_error(self): 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()
def test_geodetic_distance_lookups(self): """ Test distance lookups on geodetic coordinate systems. """ # 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) dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100))) expected_cities = [ 'Batemans Bay', 'Canberra', 'Hillsdale', 'Melbourne', 'Mittagong', 'Shellharbour', 'Sydney', 'Thirroul', 'Wollongong', ] if spatialite: # SpatiaLite is less accurate and returns 102.8km for Batemans Bay. expected_cities.pop(0) self.assertEqual(expected_cities, self.get_names(dist_qs)) 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))) with self.assertRaisesMessage(ValueError, msg): # Too few params. 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'))) # 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))) cities = self.get_names(qs) 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') 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) # 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) querysets.append(qs2) for qs in querysets: cities = self.get_names(qs) self.assertEqual( cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul'])
def testAccessInvalid(self): "Testing access in invalid units" d = D(m=100) self.assertFalse(hasattr(d, 'banana'))
def testAccess(self): "Testing access in different units" d = D(m=100) self.assertEqual(d.km, 0.1) self.assertAlmostEqual(d.ft, 328.084, 3)
def testInitInvalid(self): "Testing initialization from invalid units" with self.assertRaises(AttributeError): D(banana=100)
def test_distance_function_d_lookup(self): qs = Interstate.objects.annotate(d=Distance(Point( 0, 0, srid=3857), Point(0, 1, srid=3857)), ).filter(d=D(m=1)) self.assertTrue(qs.exists())
def test_distance_function_raw_result_d_lookup(self): 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.' with self.assertRaisesMessage(ValueError, msg): list(qs)