app/django/contrib/gis/models.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     1 """
       
     2  Imports the SpatialRefSys and GeometryColumns models dependent on the
       
     3  spatial database backend.
       
     4 """
       
     5 import re
       
     6 from django.conf import settings
       
     7 
       
     8 # Checking for the presence of GDAL (needed for the SpatialReference object)
       
     9 from django.contrib.gis.gdal import HAS_GDAL, PYTHON23
       
    10 if HAS_GDAL:
       
    11     from django.contrib.gis.gdal import SpatialReference
       
    12 
       
    13 class SpatialRefSysMixin(object):
       
    14     """
       
    15     The SpatialRefSysMixin is a class used by the database-dependent
       
    16     SpatialRefSys objects to reduce redundnant code.
       
    17     """
       
    18 
       
    19     # For pulling out the spheroid from the spatial reference string. This
       
    20     # regular expression is used only if the user does not have GDAL installed.
       
    21     #  TODO: Flattening not used in all ellipsoids, could also be a minor axis, or 'b'
       
    22     #        parameter.
       
    23     spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
       
    24 
       
    25     # For pulling out the units on platforms w/o GDAL installed.
       
    26     # TODO: Figure out how to pull out angular units of projected coordinate system and
       
    27     # fix for LOCAL_CS types.  GDAL should be highly recommended for performing 
       
    28     # distance queries.
       
    29     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+)"\])?\]$')
       
    30     
       
    31     def srs(self):
       
    32         """
       
    33         Returns a GDAL SpatialReference object, if GDAL is installed.
       
    34         """
       
    35         if HAS_GDAL:
       
    36             if hasattr(self, '_srs'):
       
    37                 # Returning a clone of the cached SpatialReference object.
       
    38                 return self._srs.clone()
       
    39             else:
       
    40                 # Attempting to cache a SpatialReference object.
       
    41 
       
    42                 # Trying to get from WKT first.
       
    43                 try:
       
    44                     self._srs = SpatialReference(self.wkt)
       
    45                     return self.srs
       
    46                 except Exception, msg:
       
    47                     pass
       
    48                 
       
    49                 raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
       
    50         else:
       
    51             raise Exception('GDAL is not installed.')
       
    52     srs = property(srs)
       
    53 
       
    54     def ellipsoid(self):
       
    55         """
       
    56         Returns a tuple of the ellipsoid parameters:
       
    57         (semimajor axis, semiminor axis, and inverse flattening).
       
    58         """
       
    59         if HAS_GDAL:
       
    60             return self.srs.ellipsoid
       
    61         else:
       
    62             m = self.spheroid_regex.match(self.wkt)
       
    63             if m: return (float(m.group('major')), float(m.group('flattening')))
       
    64             else: return None
       
    65     ellipsoid = property(ellipsoid)
       
    66 
       
    67     def name(self):
       
    68         "Returns the projection name."
       
    69         return self.srs.name
       
    70     name = property(name)
       
    71 
       
    72     def spheroid(self):
       
    73         "Returns the spheroid name for this spatial reference."
       
    74         return self.srs['spheroid']
       
    75     spheroid = property(spheroid)
       
    76 
       
    77     def datum(self):
       
    78         "Returns the datum for this spatial reference."
       
    79         return self.srs['datum']
       
    80     datum = property(datum)
       
    81 
       
    82     def projected(self):
       
    83         "Is this Spatial Reference projected?"
       
    84         if HAS_GDAL:
       
    85             return self.srs.projected
       
    86         else:
       
    87             return self.wkt.startswith('PROJCS')
       
    88     projected = property(projected)
       
    89 
       
    90     def local(self):
       
    91         "Is this Spatial Reference local?"
       
    92         if HAS_GDAL:
       
    93             return self.srs.local
       
    94         else:
       
    95             return self.wkt.startswith('LOCAL_CS')
       
    96     local = property(local)
       
    97 
       
    98     def geographic(self):
       
    99         "Is this Spatial Reference geographic?"
       
   100         if HAS_GDAL:
       
   101             return self.srs.geographic
       
   102         else:
       
   103             return self.wkt.startswith('GEOGCS')
       
   104     geographic = property(geographic)
       
   105 
       
   106     def linear_name(self):
       
   107         "Returns the linear units name."
       
   108         if HAS_GDAL:
       
   109             return self.srs.linear_name
       
   110         elif self.geographic: 
       
   111             return None
       
   112         else:
       
   113             m = self.units_regex.match(self.wkt)
       
   114             return m.group('unit_name')
       
   115     linear_name = property(linear_name)
       
   116 
       
   117     def linear_units(self):
       
   118         "Returns the linear units."
       
   119         if HAS_GDAL:
       
   120             return self.srs.linear_units
       
   121         elif self.geographic:
       
   122             return None
       
   123         else:
       
   124             m = self.units_regex.match(self.wkt)
       
   125             return m.group('unit')
       
   126     linear_units = property(linear_units)
       
   127 
       
   128     def angular_name(self):
       
   129         "Returns the name of the angular units."
       
   130         if HAS_GDAL:
       
   131             return self.srs.angular_name
       
   132         elif self.projected:
       
   133             return None
       
   134         else:
       
   135             m = self.units_regex.match(self.wkt)
       
   136             return m.group('unit_name')
       
   137     angular_name = property(angular_name)
       
   138 
       
   139     def angular_units(self):
       
   140         "Returns the angular units."
       
   141         if HAS_GDAL:
       
   142             return self.srs.angular_units
       
   143         elif self.projected:
       
   144             return None
       
   145         else:
       
   146             m = self.units_regex.match(self.wkt)
       
   147             return m.group('unit')
       
   148     angular_units = property(angular_units)
       
   149 
       
   150     def units(self):
       
   151         "Returns a tuple of the units and the name."
       
   152         if self.projected or self.local:
       
   153             return (self.linear_units, self.linear_name)
       
   154         elif self.geographic:
       
   155             return (self.angular_units, self.angular_name)
       
   156         else:
       
   157             return (None, None)
       
   158     units = property(units)
       
   159 
       
   160     def get_units(cls, wkt):
       
   161         """
       
   162         Class method used by GeometryField on initialization to
       
   163         retrive the units on the given WKT, without having to use
       
   164         any of the database fields.
       
   165         """
       
   166         if HAS_GDAL:
       
   167             return SpatialReference(wkt).units
       
   168         else:
       
   169             m = cls.units_regex.match(wkt)
       
   170             return m.group('unit'), m.group('unit_name')
       
   171     get_units = classmethod(get_units)
       
   172 
       
   173     def get_spheroid(cls, wkt, string=True):
       
   174         """
       
   175         Class method used by GeometryField on initialization to
       
   176         retrieve the `SPHEROID[..]` parameters from the given WKT.
       
   177         """
       
   178         if HAS_GDAL:
       
   179             srs = SpatialReference(wkt)
       
   180             sphere_params = srs.ellipsoid
       
   181             sphere_name = srs['spheroid']
       
   182         else:
       
   183             m = cls.spheroid_regex.match(wkt)
       
   184             if m: 
       
   185                 sphere_params = (float(m.group('major')), float(m.group('flattening')))
       
   186                 sphere_name = m.group('name')
       
   187             else: 
       
   188                 return None
       
   189         
       
   190         if not string: 
       
   191             return sphere_name, sphere_params
       
   192         else:
       
   193             # `string` parameter used to place in format acceptable by PostGIS
       
   194             if len(sphere_params) == 3:
       
   195                 radius, flattening = sphere_params[0], sphere_params[2]
       
   196             else:
       
   197                 radius, flattening = sphere_params
       
   198             return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening) 
       
   199     get_spheroid = classmethod(get_spheroid)
       
   200 
       
   201     def __unicode__(self):
       
   202         """
       
   203         Returns the string representation.  If GDAL is installed,
       
   204         it will be 'pretty' OGC WKT.
       
   205         """
       
   206         try:
       
   207             return unicode(self.srs)
       
   208         except:
       
   209             return unicode(self.wkt)
       
   210 
       
   211 # The SpatialRefSys and GeometryColumns models
       
   212 _srid_info = True
       
   213 if not PYTHON23 and settings.DATABASE_ENGINE == 'postgresql_psycopg2':
       
   214     # Because the PostGIS version is checked when initializing the spatial 
       
   215     # backend a `ProgrammingError` will be raised if the PostGIS tables 
       
   216     # and functions are not installed.  We catch here so it won't be raised when 
       
   217     # running the Django test suite.  `ImportError` is also possible if no ctypes.
       
   218     try:
       
   219         from django.contrib.gis.db.backend.postgis.models import GeometryColumns, SpatialRefSys
       
   220     except:
       
   221         _srid_info = False
       
   222 elif not PYTHON23 and settings.DATABASE_ENGINE == 'oracle':
       
   223     # Same thing as above, except the GEOS library is attempted to be loaded for
       
   224     # `BaseSpatialBackend`, and an exception will be raised during the
       
   225     # Django test suite if it doesn't exist.
       
   226     try:
       
   227         from django.contrib.gis.db.backend.oracle.models import GeometryColumns, SpatialRefSys
       
   228     except:
       
   229         _srid_info = False
       
   230 else:
       
   231     _srid_info = False
       
   232 
       
   233 if _srid_info:
       
   234     def get_srid_info(srid):
       
   235         """
       
   236         Returns the units, unit name, and spheroid WKT associated with the
       
   237         given SRID from the `spatial_ref_sys` (or equivalent) spatial database
       
   238         table.  We use a database cursor to execute the query because this
       
   239         function is used when it is not possible to use the ORM (for example,
       
   240         during field initialization).
       
   241         """
       
   242         # SRID=-1 is a common convention for indicating the geometry has no
       
   243         # spatial reference information associated with it.  Thus, we will
       
   244         # return all None values without raising an exception.
       
   245         if srid == -1: return None, None, None
       
   246 
       
   247         # Getting the spatial reference WKT associated with the SRID from the
       
   248         # `spatial_ref_sys` (or equivalent) spatial database table. This query
       
   249         # cannot be executed using the ORM because this information is needed
       
   250         # when the ORM cannot be used (e.g., during the initialization of 
       
   251         # `GeometryField`).
       
   252         from django.db import connection
       
   253         cur = connection.cursor()
       
   254         qn = connection.ops.quote_name
       
   255         stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)'
       
   256         stmt = stmt % {'table' : qn(SpatialRefSys._meta.db_table),
       
   257                        'wkt_col' : qn(SpatialRefSys.wkt_col()),
       
   258                        'srid_col' : qn('srid'),
       
   259                        'srid' : srid,
       
   260                        }
       
   261         cur.execute(stmt)
       
   262         
       
   263         # Fetching the WKT from the cursor; if the query failed raise an Exception.
       
   264         fetched = cur.fetchone()
       
   265         if not fetched:
       
   266             raise ValueError('Failed to find spatial reference entry in "%s" corresponding to SRID=%s.' % 
       
   267                              (SpatialRefSys._meta.db_table, srid))
       
   268         srs_wkt = fetched[0]
       
   269 
       
   270         # Getting metadata associated with the spatial reference system identifier.
       
   271         # Specifically, getting the unit information and spheroid information 
       
   272         # (both required for distance queries).
       
   273         unit, unit_name = SpatialRefSys.get_units(srs_wkt)
       
   274         spheroid = SpatialRefSys.get_spheroid(srs_wkt)
       
   275         return unit, unit_name, spheroid
       
   276 else:
       
   277     def get_srid_info(srid):
       
   278         """
       
   279         Dummy routine for the backends that do not have the OGC required
       
   280         spatial metadata tables (like MySQL).
       
   281         """
       
   282         return None, None, None
       
   283