Exemplo n.º 1
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])
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
class DistanceTest(TestCase):
    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)
        # Another one for Australia
        self.au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326)

    def get_names(self, qs):
        cities = [c.name for c in qs]
        cities.sort()
        return cities

    def test_init(self):
        """
        Test initialization of distance models.
        """
        self.assertEqual(9, SouthTexasCity.objects.count())
        self.assertEqual(9, SouthTexasCityFt.objects.count())
        self.assertEqual(11, AustraliaCity.objects.count())
        self.assertEqual(4, SouthTexasZipcode.objects.count())
        self.assertEqual(4, CensusZipcode.objects.count())
        self.assertEqual(1, Interstate.objects.count())
        self.assertEqual(1, SouthTexasInterstate.objects.count())

    @skipUnlessDBFeature("supports_dwithin_lookup")
    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))))

    @skipUnlessDBFeature("supports_distances_lookups")
    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))

    @skipUnlessDBFeature("supports_distances_lookups",
                         "supports_distance_geodetic")
    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'])

    @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)
        qs = SouthTexasCity.objects.filter(
            point__distance_lte=(stx_pnt, F('radius')), ).order_by('name')
        self.assertEqual(self.get_names(qs), [
            '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')
        self.assertEqual(len(qs), 5)
        self.assertIn('Pearland', self.get_names(qs))

        # With spheroid param
        if connection.features.supports_distance_geodetic:
            hobart = AustraliaCity.objects.get(name='Hobart')
            qs = AustraliaCity.objects.filter(
                point__distance_lte=(hobart.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.assertEqual(
            SouthTexasCity.objects.filter(
                point__distance_lte=(Union('point', 'point'), 0)).count(),
            SouthTexasCity.objects.count(),
        )

    @unittest.skipUnless(mysql, 'This is a MySQL-specific test')
    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()