Django

Code

Show
Ignore:
Timestamp:
06/15/08 14:48:57 (7 months ago)
Author:
jbronn
Message:

gis: Refactor of the GeoQuerySet; new features include:

(1) Creation of internal API that eases generation of GeoQuerySet methods.
(2) GeoQuerySet.distance now returns Distance objects instead of floats.
(3) Added the new GeoQuerySet methods: area, centroid, difference, envelope, intersection, length, make_line, mem_size, num_geom, num_points, perimeter, point_on_surface, scale, svg, sym_difference, translate, union.
(4) The model_att keyword may be used to customize the attribute that GeoQuerySet methods attach output to.
(5) Geographic distance lookups and GeoQuerySet.distance calls now use ST_distance_sphere by default (performance benefits far outweigh small loss in accuracy); ST_distance_spheroid may still be used by specifying an option.
(6) GeoQuerySet methods may now operate accross ForeignKey? relations specified via the field_name keyword (but this does not work on Oracle).
(7) Area now has the same units of measure as Distance.

Backward Incompatibilites:

  • The aggregate union method is now known as unionagg.
  • The field_name keyword used for GeoQuerySet methods may no longer be specified via positional arguments.
  • Distance objects returned instead of floats from GeoQuerySet.distance.
  • ST_Distance_sphere used by default for geographic distance calculations.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis/django/contrib/gis/db/backend/__init__.py

    r7512 r7641  
    44 Specifically, this module will import the correct routines and modules 
    55 needed for GeoDjango to interface with the spatial database. 
    6   
    7  Some of the more important classes and routines from the spatial backend 
    8  include: 
    9  
    10  (1) `GeoBackEndField`, a base class needed for GeometryField. 
    11  (2) `get_geo_where_clause`, a routine used by `GeoWhereNode`. 
    12  (3) `GIS_TERMS`, a listing of all valid GeoDjango lookup types. 
    13  (4) `SpatialBackend`, a container object for information specific to the 
    14      spatial backend. 
    156""" 
    167from django.conf import settings 
    17 from django.db.models.sql.query import QUERY_TERMS 
    188from django.contrib.gis.db.backend.util import gqn 
    19  
    20 # These routines (needed by GeoManager), default to False. 
    21 ASGML, ASKML, DISTANCE, DISTANCE_SPHEROID, EXTENT, TRANSFORM, UNION, VERSION = tuple(False for i in range(8)) 
    22  
    23 # Lookup types in which the rest of the parameters are not 
    24 # needed to be substitute in the WHERE SQL (e.g., the 'relate' 
    25 # operation on Oracle does not need the mask substituted back 
    26 # into the query SQL.). 
    27 LIMITED_WHERE = [] 
    289 
    2910# Retrieving the necessary settings from the backend. 
    3011if settings.DATABASE_ENGINE == 'postgresql_psycopg2': 
    31     from django.contrib.gis.db.backend.postgis.adaptor import \ 
    32         PostGISAdaptor as GeoAdaptor 
    33     from django.contrib.gis.db.backend.postgis.field import \ 
    34         PostGISField as GeoBackendField 
    35     from django.contrib.gis.db.backend.postgis.creation import create_spatial_db 
    36     from django.contrib.gis.db.backend.postgis.query import \ 
    37         get_geo_where_clause, POSTGIS_TERMS as GIS_TERMS, \ 
    38         ASGML, ASKML, DISTANCE, DISTANCE_SPHEROID, DISTANCE_FUNCTIONS, \ 
    39         EXTENT, GEOM_SELECT, TRANSFORM, UNION, \ 
    40         MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 
    41     # PostGIS version info is needed to determine calling order of some 
    42     # stored procedures (e.g., AsGML()). 
    43     VERSION = (MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2) 
    44     SPATIAL_BACKEND = 'postgis' 
     12    from django.contrib.gis.db.backend.postgis import create_spatial_db, get_geo_where_clause, SpatialBackend 
    4513elif settings.DATABASE_ENGINE == 'oracle': 
    46     from django.contrib.gis.db.backend.adaptor import WKTAdaptor as GeoAdaptor 
    47     from django.contrib.gis.db.backend.oracle.field import \ 
    48         OracleSpatialField as GeoBackendField 
    49     from django.contrib.gis.db.backend.oracle.creation import create_spatial_db 
    50     from django.contrib.gis.db.backend.oracle.query import \ 
    51         get_geo_where_clause, ORACLE_SPATIAL_TERMS as GIS_TERMS, \ 
    52         ASGML, DISTANCE, DISTANCE_FUNCTIONS, GEOM_SELECT, TRANSFORM, UNION 
    53     SPATIAL_BACKEND = 'oracle' 
    54     LIMITED_WHERE = ['relate'] 
     14    from django.contrib.gis.db.backend.oracle import create_spatial_db, get_geo_where_clause, SpatialBackend 
    5515elif settings.DATABASE_ENGINE == 'mysql': 
    56     from django.contrib.gis.db.backend.adaptor import WKTAdaptor as GeoAdaptor 
    57     from django.contrib.gis.db.backend.mysql.field import \ 
    58         MySQLGeoField as GeoBackendField 
    59     from django.contrib.gis.db.backend.mysql.creation import create_spatial_db 
    60     from django.contrib.gis.db.backend.mysql.query import \ 
    61         get_geo_where_clause, MYSQL_GIS_TERMS as GIS_TERMS, GEOM_SELECT 
    62     DISTANCE_FUNCTIONS = {} 
    63     SPATIAL_BACKEND = 'mysql' 
     16    from django.contrib.gis.db.backend.mysql import create_spatial_db, get_geo_where_clause, SpatialBackend 
    6417else: 
    6518    raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_ENGINE) 
    66  
    67 class SpatialBackend(object): 
    68     "A container for properties of the SpatialBackend." 
    69     # Stored procedure names used by the `GeoManager`. 
    70     as_kml = ASKML 
    71     as_gml = ASGML 
    72     distance = DISTANCE 
    73     distance_spheroid = DISTANCE_SPHEROID 
    74     extent = EXTENT 
    75     name = SPATIAL_BACKEND 
    76     select = GEOM_SELECT 
    77     transform = TRANSFORM 
    78     union = UNION 
    79      
    80     # Version information, if defined. 
    81     version = VERSION 
    82      
    83     # All valid GIS lookup terms, and distance functions. 
    84     gis_terms = GIS_TERMS 
    85     distance_functions = DISTANCE_FUNCTIONS 
    86      
    87     # Lookup types where additional WHERE parameters are excluded. 
    88     limited_where = LIMITED_WHERE 
    89  
    90     # Shortcut booleans. 
    91     mysql = SPATIAL_BACKEND == 'mysql' 
    92     oracle = SPATIAL_BACKEND == 'oracle' 
    93     postgis = SPATIAL_BACKEND == 'postgis' 
    94  
    95     # Class for the backend field. 
    96     Field = GeoBackendField 
    97  
    98     # Adaptor class used for quoting GEOS geometries in the database. 
    99     Adaptor = GeoAdaptor 
  • django/branches/gis/django/contrib/gis/db/backend/mysql/__init__.py

    r7104 r7641  
     1__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend'] 
    12 
     3from django.contrib.gis.db.backend.base import BaseSpatialBackend 
     4from django.contrib.gis.db.backend.adaptor import WKTAdaptor 
     5from django.contrib.gis.db.backend.mysql.creation import create_spatial_db 
     6from django.contrib.gis.db.backend.mysql.field import MySQLGeoField 
     7from django.contrib.gis.db.backend.mysql.query import * 
     8 
     9SpatialBackend = BaseSpatialBackend(name='mysql', mysql=True, 
     10                                    gis_terms=MYSQL_GIS_TERMS, 
     11                                    select=GEOM_SELECT, 
     12                                    Adaptor=WKTAdaptor, 
     13                                    Field=MySQLGeoField) 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/__init__.py

    r7104 r7641  
     1__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend'] 
     2 
     3from django.contrib.gis.db.backend.base import BaseSpatialBackend 
     4from django.contrib.gis.db.backend.oracle.adaptor import OracleSpatialAdaptor 
     5from django.contrib.gis.db.backend.oracle.creation import create_spatial_db 
     6from django.contrib.gis.db.backend.oracle.field import OracleSpatialField 
     7from django.contrib.gis.db.backend.oracle.query import * 
     8 
     9SpatialBackend = BaseSpatialBackend(name='oracle', oracle=True, 
     10                                    area=AREA, 
     11                                    centroid=CENTROID, 
     12                                    difference=DIFFERENCE, 
     13                                    distance=DISTANCE, 
     14                                    distance_functions=DISTANCE_FUNCTIONS, 
     15                                    gis_terms=ORACLE_SPATIAL_TERMS, 
     16                                    gml=ASGML, 
     17                                    intersection=INTERSECTION, 
     18                                    length=LENGTH, 
     19                                    limited_where = {'relate' : None}, 
     20                                    num_geom=NUM_GEOM, 
     21                                    num_points=NUM_POINTS, 
     22                                    perimeter=LENGTH, 
     23                                    point_on_surface=POINT_ON_SURFACE, 
     24                                    select=GEOM_SELECT, 
     25                                    sym_difference=SYM_DIFFERENCE, 
     26                                    transform=TRANSFORM, 
     27                                    unionagg=UNIONAGG, 
     28                                    union=UNION, 
     29                                    Adaptor=OracleSpatialAdaptor, 
     30                                    Field=OracleSpatialField, 
     31                                    ) 
  • django/branches/gis/django/contrib/gis/db/backend/oracle/query.py

    r7483 r7641  
    1616 
    1717# The GML, distance, transform, and union procedures. 
     18AREA = 'SDO_GEOM.SDO_AREA' 
    1819ASGML = 'SDO_UTIL.TO_GMLGEOMETRY' 
     20CENTROID = 'SDO_GEOM.SDO_CENTROID' 
     21DIFFERENCE = 'SDO_GEOM.SDO_DIFFERENCE' 
    1922DISTANCE = 'SDO_GEOM.SDO_DISTANCE' 
     23EXTENT = 'SDO_AGGR_MBR' 
     24INTERSECTION = 'SDO_GEOM.SDO_INTERSECTION' 
     25LENGTH = 'SDO_GEOM.SDO_LENGTH' 
     26NUM_GEOM = 'SDO_UTIL.GETNUMELEM' 
     27NUM_POINTS = 'SDO_UTIL.GETNUMVERTICES' 
     28POINT_ON_SURFACE = 'SDO_GEOM.SDO_POINTONSURFACE' 
     29SYM_DIFFERENCE = 'SDO_GEOM.SDO_XOR' 
    2030TRANSFORM = 'SDO_CS.TRANSFORM' 
    21 UNION = 'SDO_AGGR_UNION' 
     31UNION = 'SDO_GEOM.SDO_UNION' 
     32UNIONAGG = 'SDO_AGGR_UNION' 
    2233 
    2334# We want to get SDO Geometries as WKT because it is much easier to  
  • django/branches/gis/django/contrib/gis/db/backend/postgis/field.py

    r7104 r7641  
    11from django.db import connection 
    22from django.db.models.fields import Field # Django base Field class 
    3 from django.contrib.gis.geos import GEOSGeometry 
    43from django.contrib.gis.db.backend.util import gqn 
    54from django.contrib.gis.db.backend.postgis.query import TRANSFORM 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/__init__.py

    r7104 r7641  
     1__all__ = ['create_spatial_db', 'get_geo_where_clause', 'SpatialBackend'] 
     2 
     3from django.contrib.gis.db.backend.base import BaseSpatialBackend 
     4from django.contrib.gis.db.backend.postgis.adaptor import PostGISAdaptor 
     5from django.contrib.gis.db.backend.postgis.creation import create_spatial_db 
     6from django.contrib.gis.db.backend.postgis.field import PostGISField 
     7from django.contrib.gis.db.backend.postgis.query import * 
     8 
     9SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True, 
     10                                    area=AREA, 
     11                                    centroid=CENTROID, 
     12                                    difference=DIFFERENCE, 
     13                                    distance=DISTANCE, 
     14                                    distance_functions=DISTANCE_FUNCTIONS, 
     15                                    distance_sphere=DISTANCE_SPHERE, 
     16                                    distance_spheroid=DISTANCE_SPHEROID, 
     17                                    envelope=ENVELOPE, 
     18                                    extent=EXTENT, 
     19                                    gis_terms=POSTGIS_TERMS, 
     20                                    gml=ASGML, 
     21                                    intersection=INTERSECTION, 
     22                                    kml=ASKML, 
     23                                    length=LENGTH, 
     24                                    length_spheroid=LENGTH_SPHEROID, 
     25                                    make_line=MAKE_LINE, 
     26                                    mem_size=MEM_SIZE, 
     27                                    num_geom=NUM_GEOM, 
     28                                    num_points=NUM_POINTS, 
     29                                    perimeter=PERIMETER, 
     30                                    point_on_surface=POINT_ON_SURFACE, 
     31                                    scale=SCALE, 
     32                                    select=GEOM_SELECT, 
     33                                    svg=ASSVG, 
     34                                    sym_difference=SYM_DIFFERENCE, 
     35                                    transform=TRANSFORM, 
     36                                    translate=TRANSLATE, 
     37                                    union=UNION, 
     38                                    unionagg=UNIONAGG, 
     39                                    version=(MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2), 
     40                                    Adaptor=PostGISAdaptor, 
     41                                    Field=PostGISField, 
     42                                    ) 
  • django/branches/gis/django/contrib/gis/db/backend/postgis/query.py

    r7456 r7641  
    2222# Versions of PostGIS >= 1.2.2 changed their naming convention to be 
    2323#  'SQL-MM-centric' to conform with the ISO standard. Practically, this 
    24 #  means that 'ST_' is prefixes geometry function names. 
     24#  means that 'ST_' prefixes geometry function names. 
    2525GEOM_FUNC_PREFIX = '' 
    2626if MAJOR_VERSION >= 1: 
     
    3131    def get_func(func): return '%s%s' % (GEOM_FUNC_PREFIX, func) 
    3232 
    33     # Custom selection not needed for PostGIS since GEOS geometries may b
     33    # Custom selection not needed for PostGIS because GEOS geometries ar
    3434    # instantiated directly from the HEXEWKB returned by default.  If 
    3535    # WKT is needed for some reason in the future, this value may be changed, 
    36     # 'AsText(%s)' 
     36    # e.g,, 'AsText(%s)'. 
    3737    GEOM_SELECT = None 
    3838 
    3939    # Functions used by the GeoManager & GeoQuerySet 
     40    AREA = get_func('Area') 
    4041    ASKML = get_func('AsKML') 
    4142    ASGML = get_func('AsGML') 
     43    ASSVG = get_func('AsSVG') 
     44    CENTROID = get_func('Centroid') 
     45    DIFFERENCE = get_func('Difference') 
    4246    DISTANCE = get_func('Distance') 
     47    DISTANCE_SPHERE = get_func('distance_sphere') 
    4348    DISTANCE_SPHEROID = get_func('distance_spheroid') 
     49    ENVELOPE = get_func('Envelope') 
    4450    EXTENT = get_func('extent') 
    4551    GEOM_FROM_TEXT = get_func('GeomFromText') 
    4652    GEOM_FROM_WKB = get_func('GeomFromWKB') 
     53    INTERSECTION = get_func('Intersection') 
     54    LENGTH = get_func('Length') 
     55    LENGTH_SPHEROID = get_func('length_spheroid') 
     56    MAKE_LINE = get_func('MakeLine') 
     57    MEM_SIZE = get_func('mem_size') 
     58    NUM_GEOM = get_func('NumGeometries') 
     59    NUM_POINTS = get_func('npoints') 
     60    PERIMETER = get_func('Perimeter') 
     61    POINT_ON_SURFACE = get_func('PointOnSurface') 
     62    SCALE = get_func('Scale') 
     63    SYM_DIFFERENCE = get_func('SymDifference') 
    4764    TRANSFORM = get_func('Transform') 
     65    TRANSLATE = get_func('Translate') 
    4866 
    4967    # Special cases for union and KML methods. 
    5068    if MINOR_VERSION1 < 3: 
    51         UNION = 'GeomUnion' 
     69        UNIONAGG = 'GeomUnion' 
     70        UNION = 'Union' 
    5271    else: 
     72        UNIONAGG = 'ST_Union' 
    5373        UNION = 'ST_Union' 
    5474 
     
    81101                                              operator=operator, result='%%s') 
    82102 
     103class PostGISSpheroidDistance(PostGISFunction): 
     104    "For PostGIS spherical distance operations (using the spheroid)." 
     105    dist_func = 'distance_spheroid' 
     106    def __init__(self, operator): 
     107        # An extra parameter in `end_subst` is needed for the spheroid string. 
     108        super(PostGISSpheroidDistance, self).__init__(self.dist_func,  
     109                                                      beg_subst='%s(%s, %%s, %%s',  
     110                                                      end_subst=') %s %s', 
     111                                                      operator=operator, result='%%s') 
     112 
    83113class PostGISSphereDistance(PostGISFunction): 
    84114    "For PostGIS spherical distance operations." 
    85     dist_func = 'distance_spheroid' 
    86     def __init__(self, operator): 
    87         # An extra parameter in `end_subst` is needed for the spheroid string. 
    88         super(PostGISSphereDistance, self).__init__(self.dist_func,  
    89                                                     beg_subst='%s(%s, %%s, %%s',  
    90                                                     end_subst=') %s %s', 
     115    dist_func = 'distance_sphere' 
     116    def __init__(self, operator): 
     117        super(PostGISSphereDistance, self).__init__(self.dist_func, end_subst=') %s %s', 
    91118                                                    operator=operator, result='%%s') 
    92  
     119                                                     
    93120class PostGISRelate(PostGISFunctionParam): 
    94121    "For PostGIS Relate(<geom>, <pattern>) calls." 
     
    165192def get_dist_ops(operator): 
    166193    "Returns operations for both regular and spherical distances." 
    167     return (PostGISDistance(operator), PostGISSphereDistance(operator)
     194    return (PostGISDistance(operator), PostGISSphereDistance(operator), PostGISSpheroidDistance(operator)
    168195DISTANCE_FUNCTIONS = { 
    169196    'distance_gt' : (get_dist_ops('>'), dtypes), 
     
    193220POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull') 
    194221POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable 
     222 
     223# For checking tuple parameters -- not very pretty but gets job done. 
     224def exactly_two(val): return val == 2 
     225def two_to_three(val): return val >= 2 and val <=3 
     226def num_params(lookup_type, val): 
     227    if lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin': return two_to_three(val) 
     228    else: return exactly_two(val) 
    195229 
    196230#### The `get_geo_where_clause` function for PostGIS. #### 
     
    217251            if not isinstance(value, (tuple, list)):  
    218252                raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) 
    219             if len(value) != 2: 
    220                 raise ValueError('2-element tuple required or `%s` lookup type.' % lookup_type) 
     253            # Number of valid tuple parameters depends on the lookup type. 
     254            nparams = len(value) 
     255            if not num_params(lookup_type, nparams): 
     256                raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type) 
    221257             
    222258            # Ensuring the argument type matches what we expect. 
     
    235271                    if value[0].geom_typeid != 0: 
    236272                        raise TypeError('PostGIS geometry distance parameter is required to be of type Point.') 
    237                     op = op[1] 
     273                    # Setting up the geodetic operation appropriately. 
     274                    if nparams == 3 and value[2] == 'spheroid': op = op[2] 
     275                    else: op = op[1] 
    238276                else: 
    239277                    op = op[0] 
  • django/branches/gis/django/contrib/gis/db/models/fields/__init__.py

    r7482 r7641  
    44# GeometryProxy, GEOS, Distance, and oldforms imports. 
    55from django.contrib.gis.db.models.proxy import GeometryProxy 
    6 from django.contrib.gis.geos import GEOSException, GEOSGeometry 
    76from django.contrib.gis.measure import Distance 
    87from django.contrib.gis.oldforms import WKTField 
    98 
    10 # Attempting to get the spatial reference system. 
    11 try: 
    12     from django.contrib.gis.models import SpatialRefSys 
    13 except ImportError: 
    14     SpatialRefSys = None 
     9# The `get_srid_info` function gets SRID information from the spatial 
     10# reference system table w/o using the ORM. 
     11from django.contrib.gis.models import get_srid_info 
    1512 
    1613#TODO: Flesh out widgets; consider adding support for OGR Geometry proxies. 
     
    4845        # easily available in the field instance for distance queries. 
    4946        self._srid = srid 
    50         if SpatialRefSys: 
    51             # Getting the spatial reference WKT associated with the SRID from the 
    52             # `spatial_ref_sys` (or equivalent) spatial database table. 
    53             # 
    54             # The following doesn't work: SpatialRefSys.objects.get(srid=srid) 
    55             # Why?  `syncdb` fails to recognize installed geographic models when there's 
    56             # an ORM query instantiated within a model field. 
    57             cur = connection.cursor() 
    58             qn = connection.ops.quote_name 
    59             stmt = 'SELECT %(table)s.%(wkt_col)s FROM %(table)s WHERE (%(table)s.%(srid_col)s = %(srid)s)' 
    60             stmt = stmt % {'table' : qn(SpatialRefSys._meta.db_table), 
    61                            'wkt_col' : qn(SpatialRefSys.wkt_col()), 
    62                            'srid_col' : qn('srid'), 
    63                            'srid' : srid, 
    64                            } 
    65             cur.execute(stmt) 
    66             srs_wkt = cur.fetchone()[0] 
    67              
    68             # Getting metadata associated with the spatial reference system identifier. 
    69             # Specifically, getting the unit information and spheroid information  
    70             # (both required for distance queries). 
    71             self._unit, self._unit_name = SpatialRefSys.get_units(srs_wkt) 
    72             self._spheroid = SpatialRefSys.get_spheroid(srs_wkt) 
     47        self._unit, self._unit_name, self._spheroid = get_srid_info(srid) 
    7348 
    7449        # Setting the dimension of the geometry field. 
     
    8055    @property 
    8156    def geodetic(self): 
     57        """ 
     58        Returns true if this field's SRID corresponds with a coordinate 
     59        system that uses non-projected units (e.g., latitude/longitude). 
     60        """ 
    8261        return self._unit_name in self.geodetic_units 
    8362 
    84     def get_distance(self, dist, lookup_type): 
     63    def get_distance(self, dist_val, lookup_type): 
    8564        """ 
    8665        Returns a distance number in units of the field.  For example, if  
     
    8867        then 1000 would be returned. 
    8968        """ 
    90         postgis = SpatialBackend.name == 'postgis' 
     69        # Getting the distance parameter and any options. 
     70        if len(dist_val) == 1: dist, option = dist_val[0], None 
     71        else: dist, option = dist_val 
     72 
    9173        if isinstance(dist, Distance): 
    9274            if self.geodetic: 
    9375                # Won't allow Distance objects w/DWithin lookups on PostGIS. 
    94                 if postgis and lookup_type == 'dwithin': 
     76                if SpatialBackend.postgis and lookup_type == 'dwithin': 
    9577                    raise TypeError('Only numeric values of degree units are allowed on geographic DWithin queries.') 
    9678                # Spherical distance calculation parameter should be in meters. 
     
    10183            # Assuming the distance is in the units of the field. 
    10284            dist_param = dist 
    103  
    104         # Sphereical distance query; returning meters. 
    105         if postgis and self.geodetic and lookup_type != 'dwithin': 
     85        
     86        if SpatialBackend.postgis and self.geodetic and lookup_type != 'dwithin' and option == 'spheroid': 
     87            # On PostGIS, by default `ST_distance_sphere` is used; but if the  
     88            # accuracy of `ST_distance_spheroid` is needed than the spheroid  
     89            # needs to be passed to the SQL stored procedure. 
    10690            return [gqn(self._spheroid), dist_param] 
    10791        else: 
     
    120104        # When the input is not a GEOS geometry, attempt to construct one 
    121105        # from the given string input. 
    122         if isinstance(geom, GEOSGeometry): 
     106        if isinstance(geom, SpatialBackend.Geometry): 
    123107            pass 
    124108        elif isinstance(geom, basestring): 
    125109            try: 
    126                 geom = GEOSGeometry(geom) 
    127             except GEOSException: 
     110                geom = SpatialBackend.Geometry(geom) 
     111            except SpatialBackend.GeometryException: 
    128112                raise ValueError('Could not create geometry from lookup value: %s' % str(value)) 
    129113        else: 
     
    149133        super(GeometryField, self).contribute_to_class(cls, name) 
    150134         
    151         # Setup for lazy-instantiated GEOSGeometry object. 
    152         setattr(cls, self.attname, GeometryProxy(GEOSGeometry, self)) 
     135        # Setup for lazy-instantiated Geometry object. 
     136        setattr(cls, self.attname, GeometryProxy(SpatialBackend.Geometry, self)) 
    153137 
    154138    def get_db_prep_lookup(self, lookup_type, value): 
     
    167151 
    168152            # Getting the WHERE clause list and the associated params list. The params  
    169             # list is populated with the Adaptor wrapping the GEOSGeometry for the  
     153            # list is populated with the Adaptor wrapping the Geometry for the  
    170154            # backend.  The WHERE clause list contains the placeholder for the adaptor 
    171155            # (e.g. any transformation SQL). 
     
    176160                if lookup_type in SpatialBackend.distance_functions: 
    177161                    # Getting the distance parameter in the units of the field. 
    178                     where += self.get_distance(value[1], lookup_type) 
     162                    where += self.get_distance(value[1:], lookup_type) 
    179163                elif lookup_type in SpatialBackend.limited_where: 
    180164                    pass 
     
    188172    def get_db_prep_save(self, value): 
    189173        "Prepares the value for saving in the database." 
    190         if isinstance(value, GEOSGeometry): 
     174        if isinstance(value, SpatialBackend.Geometry): 
    191175            return SpatialBackend.Adaptor(value) 
    192176        elif value is None: 
    193177            return None 
    194178        else: 
    195             raise TypeError('Geometry Proxy should only return GEOSGeometry objects or None.') 
     179            raise TypeError('Geometry Proxy should only return Geometry objects or None.') 
    196180 
    197181    def get_manipulator_field_objs(self): 
    198         "Using the WKTField (defined above) to be our manipulator." 
     182        "Using the WKTField (oldforms) to be our manipulator." 
    199183        return [WKTField] 
    200184 
  • django/branches/gis/django/contrib/gis/db/models/manager.py

    r7028 r7641  
    88        return GeoQuerySet(model=self.model) 
    99 
     10    def area(self, *args, **kwargs): 
     11        return self.get_query_set().area(*args, **kwargs) 
     12 
     13    def centroid(self, *args, **kwargs): 
     14        return self.get_query_set().centroid(*args, **kwargs) 
     15 
     16    def difference(self, *args, **kwargs): 
     17        return self.get_query_set().difference(*args, **kwargs) 
     18 
    1019    def distance(self, *args, **kwargs): 
    1120        return self.get_query_set().distance(*args, **kwargs) 
     21 
     22    def envelope(self, *args, **kwargs): 
     23        return self.get_query_set().envelope(*args, **kwargs) 
    1224 
    1325    def extent(self, *args, **kwargs): 
     
    1729        return self.get_query_set().gml(*args, **kwargs) 
    1830 
     31    def intersection(self, *args, **kwargs): 
     32        return self.get_query_set().intersection(*args, **kwargs) 
     33 
    1934    def kml(self, *args, **kwargs): 
    2035        return self.get_query_set().kml(*args, **kwargs) 
     36 
     37    def length(self, *args, **kwargs): 
     38        return self.get_query_set().length(*args, **kwargs) 
     39 
     40    def make_line(self, *args, **kwargs): 
     41        return self.get_query_set().make_line(*args, **kwargs) 
     42     
     43    def mem_size(self, *args, **kwargs): 
     44        return self.get_query_set().mem_size(*args, **kwargs) 
     45 
     46    def num_geom(self, *args, **kwargs): 
     47        return self.get_query_set().num_geom(*args, **kwargs) 
     48 
     49    def num_points(self, *args, **kwargs): 
     50        return self.get_query_set().num_points(*args, **kwargs) 
     51 
     52    def perimeter(self, *args, **kwargs): 
     53        return self.get_query_set().perimeter(*args, **kwargs) 
     54 
     55    def point_on_surface(self, *args, **kwargs): 
     56        return self.get_query_set().point_on_surface(*args, **kwargs) 
     57 
     58    def scale(self, *args, **kwargs): 
     59        return self.get_query_set().scale(*args, **kwargs) 
     60 
     61    def svg(self, *args, **kwargs): 
     62        return self.get_query_set().svg(*args, **kwargs) 
     63 
     64    def sym_difference(self, *args, **kwargs): 
     65        return self.get_query_set().sym_difference(*args, **kwargs) 
    2166 
    2267    def transform(self, *args, **kwargs): 
    2368        return self.get_query_set().transform(*args, **kwargs) 
    2469 
     70    def translate(self, *args, **kwargs): 
     71        return self.get_query_set().translate(*args, **kwargs) 
     72 
    2573    def union(self, *args, **kwargs): 
    2674        return self.get_query_set().union(*args, **kwargs) 
     75 
     76    def unionagg(self, *args, **kwargs): 
     77        return self.get_query_set().unionagg(*args, **kwargs) 
  • django/branches/gis/django/contrib/gis/db/models/proxy.py

    r6508 r7641  
    11""" 
    22 The GeometryProxy object, allows for lazy-geometries.  The proxy uses 
    3   Python descriptors for instantiating and setting GEOS Geometry objects 
    4   corresponding to geographic model fields. 
     3 Python descriptors for instantiating and setting Geometry objects 
     4 corresponding to geographic model fields. 
    55 
    66 Thanks to Robert Coup for providing this functionality (see #4322). 
     
    3232            geom = None 
    3333        else:  
    34             # Otherwise, a GEOSGeometry object is built using the field's contents, 
    35             # and the model's corresponding attribute is set. 
     34            # Otherwise, a Geometry object is built using the field's contents, 
     35            # and the model's corresponding attribute is set. 
    3636            geom = self._klass(geom_value) 
    3737            setattr(obj, self._field.attname, geom)  
  • django/branches/gis/django/contrib/gis/db/models/query.py

    r7512 r7641  
    1 from itertools import izip 
    21from django.core.exceptions import ImproperlyConfigured 
    32from django.db import connection 
     
    65from django.contrib.gis.db.backend import SpatialBackend 
    76from django.contrib.gis.db.models.fields import GeometryField, PointField 
    8 from django.contrib.gis.db.models.sql import GeoQuery, GeoWhereNode 
    9 from django.contrib.gis.geos import GEOSGeometry, Point 
     7from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode 
     8from django.contrib.gis.measure import Area, Distance 
     9from django.contrib.gis.models import get_srid_info 
    1010qn = connection.ops.quote_name 
    1111 
     
    2929        self.query = query or GeoQuery(self.model, connection) 
    3030 
    31     def distance(self, *args, **kwargs): 
     31    def area(self, tolerance=0.05, **kwargs): 
     32        """ 
     33        Returns the area of the geographic field in an `area` attribute on  
     34        each element of this GeoQuerySet. 
     35        """ 
     36        # Peforming setup here rather than in `_spatial_attribute` so that 
     37        # we can get the units for `AreaField`. 
     38        procedure_args, geo_field = self._spatial_setup('area', field_name=kwargs.get('field_name', None)) 
     39        s = {'procedure_args' : procedure_args, 
     40             'geo_field' : geo_field, 
     41             'setup' : False, 
     42             } 
     43        if SpatialBackend.oracle: 
     44            s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s' 
     45            s['procedure_args']['tolerance'] = tolerance 
     46            s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters. 
     47        elif SpatialBackend.postgis: 
     48            if not geo_field.geodetic: 
     49                # Getting the area units of the geographic field. 
     50                s['select_field'] = AreaField(Area.unit_attname(geo_field._unit_name)) 
     51            else: 
     52                # TODO: Do we want to support raw number areas for geodetic fields? 
     53                raise Exception('Area on geodetic coordinate systems not supported.') 
     54        return self._spatial_attribute('area', s, **kwargs) 
     55 
     56    def centroid(self, **kwargs): 
     57        """ 
     58        Returns the centroid of the geographic field in a `centroid` 
     59        attribute on each element of this GeoQuerySet. 
     60        """ 
     61        return self._geom_attribute('centroid', **kwargs) 
     62 
     63    def difference(self, geom, **kwargs): 
     64        """ 
     65        Returns the spatial difference of the geographic field in a `difference` 
     66        attribute on each element of this GeoQuerySet. 
     67        """ 
     68        return self._geomset_attribute('difference', geom, **kwargs) 
     69 
     70    def distance(self, geom, **kwargs): 
    3271        """ 
    3372        Returns the distance from the given geographic field name to the 
    3473        given geometry in a `distance` attribute on each element of the 
    3574        GeoQuerySet. 
    36         """ 
    37         DISTANCE = SpatialBackend.distance 
    38         if not DISTANCE: 
    39             raise ImproperlyConfigured('Distance() stored proecedure not available.') 
    40  
    41         # Getting the geometry field and GEOSGeometry object to base distance 
    42         # calculations from. 
    43         nargs = len(args) 
    44         if nargs == 1: 
    45             field_name = None 
    46             geom = args[0] 
    47         elif nargs == 2: 
    48             field_name, geom = args 
    49         else: 
    50             raise ValueError('Maximum two arguments allowed for `distance` aggregate.') 
    51  
    52         # Getting the GeometryField and quoted column. 
    53         geo_field = self.query._geo_field(field_name) 
    54         if not geo_field: 
    55             raise TypeError('Distance output only available on GeometryFields.') 
    56         geo_col = self.query._field_column(geo_field) 
    57  
    58         # Using the field's get_db_prep_lookup() to get any needed 
    59         # transformation SQL -- we pass in a 'dummy' `contains` 
    60         # `distance_lte` lookup type. 
    61         where, params = geo_field.get_db_prep_lookup('distance_lte', (geom, 0)) 
    62         if SpatialBackend.oracle: 
    63             # The `tolerance` keyword may be used for Oracle; the tolerance is  
    64             # in meters -- a default of 5 centimeters is used. 
    65             tolerance = kwargs.get('tolerance', 0.05) 
    66             dist_select = {'distance' : '%s(%s, %s, %s)' % (DISTANCE, geo_col, where[0], tolerance)} 
    67         else: 
    68             if len(where) == 3: 
    69                 # Spherical distance calculation was requested (b/c spheroid  
    70                 # parameter was attached) However, the PostGIS ST_distance_spheroid()  
    71                 # procedure may only do queries from point columns to point geometries 
    72                 # some error checking is required. 
    73                 if not isinstance(geo_field, PointField):  
    74                     raise TypeError('Spherical distance calculation only supported on PointFields.') 
    75                 if not isinstance(GEOSGeometry(buffer(params[0].wkb)), Point): 
    76                     raise TypeError('Spherical distance calculation only supported with Point Geometry parameters') 
    77  
    78                 # Call to distance_spheroid() requires the spheroid as well. 
    79                 dist_sql = '%s(%s, %s, %s)' % (SpatialBackend.distance_spheroid, geo_col, where[0], where[1]) 
    80             else: 
    81                 dist_sql = '%s(%s, %s)' % (DISTANCE, geo_col, where[0]) 
    82             dist_select = {'distance' : dist_sql} 
    83         return self.extra(select=dist_select, select_params=params) 
    84  
    85     def extent(self, field_name=None): 
     75 
     76        Keyword Arguments: 
     77         `spheroid`  => If the geometry field is geodetic and PostGIS is 
     78                        the spatial database, then the more accurate  
     79                        spheroid calculation will be used instead of the 
     80                        quicker sphere calculation. 
     81                         
     82         `tolerance` => Used only for Oracle. The tolerance is  
     83                        in meters -- a default of 5 centimeters (0.05)  
     84                        is used. 
     85        """ 
     86        return self._distance_attribute('distance', geom, **kwargs) 
     87 
     88    def envelope(self, **kwargs): 
     89        """ 
     90        Returns a Geometry representing the bounding box of the  
     91        Geometry field in an `envelope` attribute on each element of 
     92        the GeoQuerySet.  
     93        """ 
     94        return self._geom_attribute('envelope', **kwargs) 
     95 
     96    def extent(self, **kwargs): 
    8697        """ 
    8798        Returns the extent (aggregate) of the features in the GeoQuerySet.  The 
    8899        extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax). 
    89100        """ 
    90         EXTENT = SpatialBackend.extent 
    91         if not EXTENT: 
    92             raise ImproperlyConfigured('Extent stored procedure not available.') 
    93  
    94         # Getting the GeometryField and quoted column. 
    95         geo_field = self.query._geo_field(field_name) 
    96         if not geo_field: 
    97             raise TypeError('Extent information only available on GeometryFields.') 
    98         geo_col = self.query._field_column(geo_field) 
    99  
    100         # Constructing the query that will select the extent. 
    101         extent_sql = '%s(%s)' % (EXTENT, geo_col) 
    102  
    103         self.query.select = [GeomSQL(extent_sql)] 
    104         self.query.select_fields = [None] 
    105         try: 
    106             esql, params = self.query.as_sql() 
    107         except sql.datastructures.EmptyResultSet: 
    108             return None         
    109  
    110         # Getting a cursor, executing the query, and extracting the returned 
    111         # value from the extent function. 
    112         cursor = connection.cursor() 
    113         cursor.execute(esql, params) 
    114         box = cursor.fetchone()[0] 
    115  
    116         if box:  
    117             # TODO: Parsing of BOX3D, Oracle support (patches welcome!) 
    118             #  Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)";  
    119             #  parsing out and returning as a 4-tuple. 
    120             ll, ur = box[4:-1].split(',') 
    121