web/lib/django/contrib/gis/db/backends/base.py
changeset 29 cc9b7e14412b
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
       
     1 """
       
     2 Base/mixin classes for the spatial backend database operations and the
       
     3 `SpatialRefSys` model the backend.
       
     4 """
       
     5 import re
       
     6 from django.conf import settings
       
     7 from django.contrib.gis import gdal
       
     8 
       
     9 class BaseSpatialOperations(object):
       
    10     """
       
    11     This module holds the base `BaseSpatialBackend` object, which is
       
    12     instantiated by each spatial database backend with the features
       
    13     it has.
       
    14     """
       
    15     distance_functions = {}
       
    16     geometry_functions = {}
       
    17     geometry_operators = {}
       
    18     geography_operators = {}
       
    19     geography_functions = {}
       
    20     gis_terms = {}
       
    21     truncate_params = {}
       
    22 
       
    23     # Quick booleans for the type of this spatial backend, and
       
    24     # an attribute for the spatial database version tuple (if applicable)
       
    25     postgis = False
       
    26     spatialite = False
       
    27     mysql = False
       
    28     oracle = False
       
    29     spatial_version = None
       
    30 
       
    31     # How the geometry column should be selected.
       
    32     select = None
       
    33 
       
    34     # Does the spatial database have a geography type?
       
    35     geography = False
       
    36 
       
    37     area = False
       
    38     centroid = False
       
    39     difference = False
       
    40     distance = False
       
    41     distance_sphere = False
       
    42     distance_spheroid = False
       
    43     envelope = False
       
    44     force_rhr = False
       
    45     mem_size = False
       
    46     bounding_circle = False
       
    47     num_geom = False
       
    48     num_points = False
       
    49     perimeter = False
       
    50     perimeter3d = False
       
    51     point_on_surface = False
       
    52     polygonize = False
       
    53     reverse = False
       
    54     scale = False
       
    55     snap_to_grid = False
       
    56     sym_difference = False
       
    57     transform = False
       
    58     translate = False
       
    59     union = False
       
    60 
       
    61     # Aggregates
       
    62     collect = False
       
    63     extent = False
       
    64     extent3d = False
       
    65     make_line = False
       
    66     unionagg = False
       
    67 
       
    68     # Serialization
       
    69     geohash = False
       
    70     geojson = False
       
    71     gml = False
       
    72     kml = False
       
    73     svg = False
       
    74 
       
    75     # Constructors
       
    76     from_text = False
       
    77     from_wkb = False
       
    78 
       
    79     # Default conversion functions for aggregates; will be overridden if implemented
       
    80     # for the spatial backend.
       
    81     def convert_extent(self, box):
       
    82         raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
       
    83 
       
    84     def convert_extent3d(self, box):
       
    85         raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
       
    86 
       
    87     def convert_geom(self, geom_val, geom_field):
       
    88         raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
       
    89 
       
    90     # For quoting column values, rather than columns.
       
    91     def geo_quote_name(self, name):
       
    92         if isinstance(name, unicode):
       
    93             name = name.encode('ascii')
       
    94         return "'%s'" % name
       
    95 
       
    96     # GeometryField operations
       
    97     def geo_db_type(self, f):
       
    98         """
       
    99         Returns the database column type for the geometry field on
       
   100         the spatial backend.
       
   101         """
       
   102         raise NotImplementedError
       
   103 
       
   104     def get_distance(self, f, value, lookup_type):
       
   105         """
       
   106         Returns the distance parameters for the given geometry field,
       
   107         lookup value, and lookup type.
       
   108         """
       
   109         raise NotImplementedError('Distance operations not available on this spatial backend.')
       
   110 
       
   111     def get_geom_placeholder(self, f, value):
       
   112         """
       
   113         Returns the placeholder for the given geometry field with the given
       
   114         value.  Depending on the spatial backend, the placeholder may contain a
       
   115         stored procedure call to the transformation function of the spatial
       
   116         backend.
       
   117         """
       
   118         raise NotImplementedError
       
   119 
       
   120     # Spatial SQL Construction
       
   121     def spatial_aggregate_sql(self, agg):
       
   122         raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
       
   123 
       
   124     def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
       
   125         raise NotImplmentedError
       
   126 
       
   127     # Routines for getting the OGC-compliant models.
       
   128     def geometry_columns(self):
       
   129         raise NotImplementedError
       
   130 
       
   131     def spatial_ref_sys(self):
       
   132         raise NotImplementedError
       
   133 
       
   134 class SpatialRefSysMixin(object):
       
   135     """
       
   136     The SpatialRefSysMixin is a class used by the database-dependent
       
   137     SpatialRefSys objects to reduce redundnant code.
       
   138     """
       
   139     # For pulling out the spheroid from the spatial reference string. This
       
   140     # regular expression is used only if the user does not have GDAL installed.
       
   141     # TODO: Flattening not used in all ellipsoids, could also be a minor axis,
       
   142     # or 'b' parameter.
       
   143     spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
       
   144 
       
   145     # For pulling out the units on platforms w/o GDAL installed.
       
   146     # TODO: Figure out how to pull out angular units of projected coordinate system and
       
   147     # fix for LOCAL_CS types.  GDAL should be highly recommended for performing
       
   148     # distance queries.
       
   149     units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$')
       
   150 
       
   151     @property
       
   152     def srs(self):
       
   153         """
       
   154         Returns a GDAL SpatialReference object, if GDAL is installed.
       
   155         """
       
   156         if gdal.HAS_GDAL:
       
   157             # TODO: Is caching really necessary here?  Is complexity worth it?
       
   158             if hasattr(self, '_srs'):
       
   159                 # Returning a clone of the cached SpatialReference object.
       
   160                 return self._srs.clone()
       
   161             else:
       
   162                 # Attempting to cache a SpatialReference object.
       
   163 
       
   164                 # Trying to get from WKT first.
       
   165                 try:
       
   166                     self._srs = gdal.SpatialReference(self.wkt)
       
   167                     return self.srs
       
   168                 except Exception, msg:
       
   169                     pass
       
   170 
       
   171                 try:
       
   172                     self._srs = gdal.SpatialReference(self.proj4text)
       
   173                     return self.srs
       
   174                 except Exception, msg:
       
   175                     pass
       
   176 
       
   177                 raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
       
   178         else:
       
   179             raise Exception('GDAL is not installed.')
       
   180 
       
   181     @property
       
   182     def ellipsoid(self):
       
   183         """
       
   184         Returns a tuple of the ellipsoid parameters:
       
   185         (semimajor axis, semiminor axis, and inverse flattening).
       
   186         """
       
   187         if gdal.HAS_GDAL:
       
   188             return self.srs.ellipsoid
       
   189         else:
       
   190             m = self.spheroid_regex.match(self.wkt)
       
   191             if m: return (float(m.group('major')), float(m.group('flattening')))
       
   192             else: return None
       
   193 
       
   194     @property
       
   195     def name(self):
       
   196         "Returns the projection name."
       
   197         return self.srs.name
       
   198 
       
   199     @property
       
   200     def spheroid(self):
       
   201         "Returns the spheroid name for this spatial reference."
       
   202         return self.srs['spheroid']
       
   203 
       
   204     @property
       
   205     def datum(self):
       
   206         "Returns the datum for this spatial reference."
       
   207         return self.srs['datum']
       
   208 
       
   209     @property
       
   210     def projected(self):
       
   211         "Is this Spatial Reference projected?"
       
   212         if gdal.HAS_GDAL:
       
   213             return self.srs.projected
       
   214         else:
       
   215             return self.wkt.startswith('PROJCS')
       
   216 
       
   217     @property
       
   218     def local(self):
       
   219         "Is this Spatial Reference local?"
       
   220         if gdal.HAS_GDAL:
       
   221             return self.srs.local
       
   222         else:
       
   223             return self.wkt.startswith('LOCAL_CS')
       
   224 
       
   225     @property
       
   226     def geographic(self):
       
   227         "Is this Spatial Reference geographic?"
       
   228         if gdal.HAS_GDAL:
       
   229             return self.srs.geographic
       
   230         else:
       
   231             return self.wkt.startswith('GEOGCS')
       
   232 
       
   233     @property
       
   234     def linear_name(self):
       
   235         "Returns the linear units name."
       
   236         if gdal.HAS_GDAL:
       
   237             return self.srs.linear_name
       
   238         elif self.geographic:
       
   239             return None
       
   240         else:
       
   241             m = self.units_regex.match(self.wkt)
       
   242             return m.group('unit_name')
       
   243 
       
   244     @property
       
   245     def linear_units(self):
       
   246         "Returns the linear units."
       
   247         if gdal.HAS_GDAL:
       
   248             return self.srs.linear_units
       
   249         elif self.geographic:
       
   250             return None
       
   251         else:
       
   252             m = self.units_regex.match(self.wkt)
       
   253             return m.group('unit')
       
   254 
       
   255     @property
       
   256     def angular_name(self):
       
   257         "Returns the name of the angular units."
       
   258         if gdal.HAS_GDAL:
       
   259             return self.srs.angular_name
       
   260         elif self.projected:
       
   261             return None
       
   262         else:
       
   263             m = self.units_regex.match(self.wkt)
       
   264             return m.group('unit_name')
       
   265 
       
   266     @property
       
   267     def angular_units(self):
       
   268         "Returns the angular units."
       
   269         if gdal.HAS_GDAL:
       
   270             return self.srs.angular_units
       
   271         elif self.projected:
       
   272             return None
       
   273         else:
       
   274             m = self.units_regex.match(self.wkt)
       
   275             return m.group('unit')
       
   276 
       
   277     @property
       
   278     def units(self):
       
   279         "Returns a tuple of the units and the name."
       
   280         if self.projected or self.local:
       
   281             return (self.linear_units, self.linear_name)
       
   282         elif self.geographic:
       
   283             return (self.angular_units, self.angular_name)
       
   284         else:
       
   285             return (None, None)
       
   286 
       
   287     @classmethod
       
   288     def get_units(cls, wkt):
       
   289         """
       
   290         Class method used by GeometryField on initialization to
       
   291         retrive the units on the given WKT, without having to use
       
   292         any of the database fields.
       
   293         """
       
   294         if gdal.HAS_GDAL:
       
   295             return gdal.SpatialReference(wkt).units
       
   296         else:
       
   297             m = cls.units_regex.match(wkt)
       
   298             return m.group('unit'), m.group('unit_name')
       
   299 
       
   300     @classmethod
       
   301     def get_spheroid(cls, wkt, string=True):
       
   302         """
       
   303         Class method used by GeometryField on initialization to
       
   304         retrieve the `SPHEROID[..]` parameters from the given WKT.
       
   305         """
       
   306         if gdal.HAS_GDAL:
       
   307             srs = gdal.SpatialReference(wkt)
       
   308             sphere_params = srs.ellipsoid
       
   309             sphere_name = srs['spheroid']
       
   310         else:
       
   311             m = cls.spheroid_regex.match(wkt)
       
   312             if m:
       
   313                 sphere_params = (float(m.group('major')), float(m.group('flattening')))
       
   314                 sphere_name = m.group('name')
       
   315             else:
       
   316                 return None
       
   317 
       
   318         if not string:
       
   319             return sphere_name, sphere_params
       
   320         else:
       
   321             # `string` parameter used to place in format acceptable by PostGIS
       
   322             if len(sphere_params) == 3:
       
   323                 radius, flattening = sphere_params[0], sphere_params[2]
       
   324             else:
       
   325                 radius, flattening = sphere_params
       
   326             return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
       
   327 
       
   328     def __unicode__(self):
       
   329         """
       
   330         Returns the string representation.  If GDAL is installed,
       
   331         it will be 'pretty' OGC WKT.
       
   332         """
       
   333         try:
       
   334             return unicode(self.srs)
       
   335         except:
       
   336             return unicode(self.wkt)