Ejemplo n.º 1
0
    def get_geometry(self, value):
        """
        Retrieves the geometry, setting the default SRID from the given
        lookup parameters.
        """
        if isinstance(value, (tuple, list)):
            geom = value[0]
        else:
            geom = value

        # When the input is not a GEOS geometry, attempt to construct one
        # from the given string input.
        if isinstance(geom, SpatialBackend.Geometry):
            pass
        elif isinstance(geom, basestring):
            try:
                geom = SpatialBackend.Geometry(geom)
            except SpatialBackend.GeometryException:
                raise ValueError(
                    'Could not create geometry from lookup value: %s' %
                    str(value))
        else:
            raise TypeError(
                'Cannot use parameter of `%s` type as lookup parameter.' %
                type(value))

        # Assigning the SRID value.
        geom.srid = self.get_srid(geom)

        return geom
Ejemplo n.º 2
0
    def get_db_prep_lookup(self, lookup_type, value):
        """
        Returns the spatial WHERE clause and associated parameters for the
        given lookup type and value.  The value will be prepared for database
        lookup (e.g., spatial transformation SQL will be added if necessary).
        """
        if lookup_type in SpatialBackend.gis_terms:
            # special case for isnull lookup
            if lookup_type == 'isnull': return [], []

            # Get the geometry with SRID; defaults SRID to that of the field
            # if it is None.
            geom = self.get_geometry(value)

            # Getting the WHERE clause list and the associated params list. The params
            # list is populated with the Adaptor wrapping the Geometry for the
            # backend.  The WHERE clause list contains the placeholder for the adaptor
            # (e.g. any transformation SQL).
            where = [self.get_placeholder(geom)]
            params = [SpatialBackend.Adaptor(geom)]

            if isinstance(value, (tuple, list)):
                if lookup_type in SpatialBackend.distance_functions:
                    # Getting the distance parameter in the units of the field.
                    where += self.get_distance(value[1:], lookup_type)
                elif lookup_type in SpatialBackend.limited_where:
                    pass
                else:
                    # Otherwise, making sure any other parameters are properly quoted.
                    where += map(gqn, value[1:])
            return where, params
        else:
            raise TypeError("Field has invalid lookup: %s" % lookup_type)
Ejemplo n.º 3
0
    def clean(self, value):
        """
        Validates that the input value can be converted to a Geometry
        object (which is returned).  A ValidationError is raised if
        the value cannot be instantiated as a Geometry.
        """
        if not value:
            if self.null:
                # The geometry column allows NULL, return None.
                return None
            else:
                raise forms.ValidationError(self.error_messages['no_geom'])

        try:
            # Trying to create a Geometry object from the form value.
            geom = SpatialBackend.Geometry(value)
        except:
            raise forms.ValidationError(self.error_messages['invalid_geom'])

        # Ensuring that the geometry is of the correct type (indicated
        # using the OGC string label).
        if str(geom.geom_type).upper(
        ) != self.geom_type and not self.geom_type == 'GEOMETRY':
            raise forms.ValidationError(
                self.error_messages['invalid_geom_type'])

        return geom
Ejemplo n.º 4
0
 def get_db_prep_save(self, value):
     "Prepares the value for saving in the database."
     if isinstance(value, SpatialBackend.Geometry):
         return SpatialBackend.Adaptor(value)
     elif value is None:
         return None
     else:
         raise TypeError('Geometry Proxy should only return Geometry objects or None.')
Ejemplo n.º 5
0
 def convert_extent(clob):
     if clob:
         # Oracle returns a polygon for the extent, we construct
         # the 4-tuple from the coordinates in the polygon.
         poly = SpatialBackend.Geometry(clob.read())
         shell = poly.shell
         ll, ur = shell[0], shell[2]
         xmin, ymin = ll
         xmax, ymax = ur
         return (xmin, ymin, xmax, ymax)
     else:
         return None
Ejemplo n.º 6
0
 def convert_values(self, value, field):
     """
     Using the same routines that Oracle does we can convert our
     extra selection objects into Geometry and Distance objects.
     TODO: Laziness.
     """
     if SpatialBackend.oracle:
         # Running through Oracle's first.
         value = super(GeoQuery, self).convert_values(value, field)
     if isinstance(field, DistanceField):
         # Using the field's distance attribute, can instantiate
         # `Distance` with the right context.
         value = Distance(**{field.distance_att: value})
     elif isinstance(field, AreaField):
         value = Area(**{field.area_att: value})
     elif isinstance(field, GeomField):
         value = SpatialBackend.Geometry(value)
     return value
Ejemplo n.º 7
0
 def convert_extent(clob):
     if clob:
         # Generally, Oracle returns a polygon for the extent -- however,
         # it can return a single point if there's only one Point in the
         # table.
         ext_geom = SpatialBackend.Geometry(clob.read())
         gtype = str(ext_geom.geom_type)
         if gtype == 'Polygon':
             # Construct the 4-tuple from the coordinates in the polygon.
             shell = ext_geom.shell
             ll, ur = shell[0][:2], shell[2][:2]
         elif gtype == 'Point':
             ll = ext_geom.coords[:2]
             ur = ll
         else:
             raise Exception('Unexpected geometry type returned for extent: %s' % gtype)
         xmin, ymin = ll
         xmax, ymax = ur
         return (xmin, ymin, xmax, ymax)
     else:
         return None
Ejemplo n.º 8
0
    def convert_values(self, value, field):
        """
        Using the same routines that Oracle does we can convert our
        extra selection objects into Geometry and Distance objects.
        TODO: Make converted objects 'lazy' for less overhead.
        """
        if SpatialBackend.oracle:
            # Running through Oracle's first.
            value = super(GeoQuery,
                          self).convert_values(value, field or GeomField())

        if value is None:
            # Output from spatial function is NULL (e.g., called
            # function on a geometry field with NULL value).
            pass
        elif isinstance(field, DistanceField):
            # Using the field's distance attribute, can instantiate
            # `Distance` with the right context.
            value = Distance(**{field.distance_att: value})
        elif isinstance(field, AreaField):
            value = Area(**{field.area_att: value})
        elif isinstance(field, (GeomField, GeometryField)) and value:
            value = SpatialBackend.Geometry(value)
        return value
Ejemplo n.º 9
0
    def _distance_attribute(self,
                            func,
                            geom=None,
                            tolerance=0.05,
                            spheroid=False,
                            **kwargs):
        """
        DRY routine for GeoQuerySet distance attribute routines.
        """
        # Setting up the distance procedure arguments.
        procedure_args, geo_field = self._spatial_setup(func,
                                                        field_name=kwargs.get(
                                                            'field_name',
                                                            None))

        # If geodetic defaulting distance attribute to meters (Oracle and
        # PostGIS spherical distances return meters).  Otherwise, use the
        # units of the geometry field.
        if geo_field.geodetic:
            dist_att = 'm'
        else:
            dist_att = Distance.unit_attname(geo_field.units_name)

        # Shortcut booleans for what distance function we're using.
        distance = func == 'distance'
        length = func == 'length'
        perimeter = func == 'perimeter'
        if not (distance or length or perimeter):
            raise ValueError('Unknown distance function: %s' % func)

        # The field's get_db_prep_lookup() is used to get any
        # extra distance parameters.  Here we set up the
        # parameters that will be passed in to field's function.
        lookup_params = [geom or 'POINT (0 0)', 0]

        # If the spheroid calculation is desired, either by the `spheroid`
        # keyword or when calculating the length of geodetic field, make
        # sure the 'spheroid' distance setting string is passed in so we
        # get the correct spatial stored procedure.
        if spheroid or (SpatialBackend.postgis and geo_field.geodetic
                        and length):
            lookup_params.append('spheroid')
        where, params = geo_field.get_db_prep_lookup('distance_lte',
                                                     lookup_params)

        # The `geom_args` flag is set to true if a geometry parameter was
        # passed in.
        geom_args = bool(geom)

        if SpatialBackend.oracle:
            if distance:
                procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
            elif length or perimeter:
                procedure_fmt = '%(geo_col)s,%(tolerance)s'
            procedure_args['tolerance'] = tolerance
        else:
            # Getting whether this field is in units of degrees since the field may have
            # been transformed via the `transform` GeoQuerySet method.
            if self.query.transformed_srid:
                u, unit_name, s = get_srid_info(self.query.transformed_srid)
                geodetic = unit_name in geo_field.geodetic_units
            else:
                geodetic = geo_field.geodetic

            if SpatialBackend.spatialite and geodetic:
                raise ValueError(
                    'SQLite does not support linear distance calculations on geodetic coordinate systems.'
                )

            if distance:
                if self.query.transformed_srid:
                    # Setting the `geom_args` flag to false because we want to handle
                    # transformation SQL here, rather than the way done by default
                    # (which will transform to the original SRID of the field rather
                    #  than to what was transformed to).
                    geom_args = False
                    procedure_fmt = '%s(%%(geo_col)s, %s)' % (
                        SpatialBackend.transform, self.query.transformed_srid)
                    if geom.srid is None or geom.srid == self.query.transformed_srid:
                        # If the geom parameter srid is None, it is assumed the coordinates
                        # are in the transformed units.  A placeholder is used for the
                        # geometry parameter.  `GeomFromText` constructor is also needed
                        # to wrap geom placeholder for SpatiaLite.
                        if SpatialBackend.spatialite:
                            procedure_fmt += ', %s(%%%%s, %s)' % (
                                SpatialBackend.from_text,
                                self.query.transformed_srid)
                        else:
                            procedure_fmt += ', %%s'
                    else:
                        # We need to transform the geom to the srid specified in `transform()`,
                        # so wrapping the geometry placeholder in transformation SQL.
                        # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
                        # constructor.
                        if SpatialBackend.spatialite:
                            procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (
                                SpatialBackend.transform,
                                SpatialBackend.from_text, geom.srid,
                                self.query.transformed_srid)
                        else:
                            procedure_fmt += ', %s(%%%%s, %s)' % (
                                SpatialBackend.transform,
                                self.query.transformed_srid)
                else:
                    # `transform()` was not used on this GeoQuerySet.
                    procedure_fmt = '%(geo_col)s,%(geom)s'

                if geodetic:
                    # Spherical distance calculation is needed (because the geographic
                    # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
                    # procedures may only do queries from point columns to point geometries
                    # some error checking is required.
                    if not isinstance(geo_field, PointField):
                        raise ValueError(
                            'Spherical distance calculation only supported on PointFields.'
                        )
                    if not str(
                            SpatialBackend.Geometry(buffer(
                                params[0].wkb)).geom_type) == 'Point':
                        raise ValueError(
                            'Spherical distance calculation only supported with Point Geometry parameters'
                        )
                    # The `function` procedure argument needs to be set differently for
                    # geodetic distance calculations.
                    if spheroid:
                        # Call to distance_spheroid() requires spheroid param as well.
                        procedure_fmt += ',%(spheroid)s'
                        procedure_args.update({
                            'function': SpatialBackend.distance_spheroid,
                            'spheroid': where[1]
                        })
                    else:
                        procedure_args.update(
                            {'function': SpatialBackend.distance_sphere})
            elif length or perimeter:
                procedure_fmt = '%(geo_col)s'
                if geodetic and length:
                    # There's no `length_sphere`
                    procedure_fmt += ',%(spheroid)s'
                    procedure_args.update({
                        'function': SpatialBackend.length_spheroid,
                        'spheroid': where[1]
                    })

        # Setting up the settings for `_spatial_attribute`.
        s = {
            'select_field': DistanceField(dist_att),
            'setup': False,
            'geo_field': geo_field,
            'procedure_args': procedure_args,
            'procedure_fmt': procedure_fmt,
        }
        if geom_args:
            s['geom_args'] = ('geom', )
            s['procedure_args']['geom'] = geom
        elif geom:
            # The geometry is passed in as a parameter because we handled
            # transformation conditions in this routine.
            s['select_params'] = [SpatialBackend.Adaptor(geom)]
        return self._spatial_attribute(func, s, **kwargs)
Ejemplo n.º 10
0
 def get_db_prep_save(self, value):
     "Prepares the value for saving in the database."
     if value is None:
         return None
     else:
         return SpatialBackend.Adaptor(self.get_geometry(value))
Ejemplo n.º 11
0
 def convert_geom(wkt, geo_field):
     if wkt:
         return SpatialBackend.Geometry(wkt, geo_field.srid)
     else:
         return None
Ejemplo n.º 12
0
 def convert_geom(clob, geo_field):
     if clob:
         return SpatialBackend.Geometry(clob.read(), geo_field.srid)
     else:
         return None
Ejemplo n.º 13
0
 def convert_geom(hex, geo_field):
     if hex: return SpatialBackend.Geometry(hex)
     else: return None