Ticket #6715: distance_sphere.diff

File distance_sphere.diff, 10.0 KB (added by jbronn, 8 years ago)

Patch for using ST_distance_sphere by default.

  • django/contrib/gis/tests/distapp/models.py

     
    1010class AustraliaCity(models.Model):
    1111    "City model for Australia, using WGS84."
    1212    name = models.CharField(max_length=30)
    13     point = models.PointField()
     13    point = models.PointField(distance_spheroid=True)
    1414    objects = models.GeoManager()
    1515    def __unicode__(self): return self.name
    1616
  • django/contrib/gis/db/models/fields/__init__.py

     
    2525    # Geodetic units.
    2626    geodetic_units = ('Decimal Degree', 'degree')
    2727
    28     def __init__(self, srid=4326, spatial_index=True, dim=2, **kwargs):
     28    def __init__(self, srid=4326, spatial_index=True, dim=2,
     29                 distance_spheroid=False, **kwargs):
    2930        """
    3031        The initialization function for geometry fields.  Takes the following
    3132        as keyword arguments:
     
    4142                 
    4243        dim:
    4344         The number of dimensions for this geometry.  Defaults to 2.
     45
     46        distance_spheroid:
     47         When geodetic distance calculations are performed on the geometries
     48         for this field, the `ST_distance_spheroid` function will be used instead
     49         of `ST_distance_sphere`.  In other words, geodetic distance operations
     50         will be more accurate at the expense of speed. Only affects PostGIS
     51         spatial backends.
    4452        """
    4553
    4654        # Setting the index flag with the value of the `spatial_index` keyword.
     
    7280            # (both required for distance queries).
    7381            self._unit, self._unit_name = SpatialRefSys.get_units(srs_wkt)
    7482            self._spheroid = SpatialRefSys.get_spheroid(srs_wkt)
     83            self._distance_spheroid = distance_spheroid
     84        else:
     85            self._unit, self._unit_name, self._sphere_distance = None, None, None
    7586
    7687        # Setting the dimension of the geometry field.
    7788        self._dim = dim
     
    7990        super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
    8091
    8192    ### Routines specific to GeometryField ###
     93    @property
     94    def geodetic(self):
     95        """
     96        Property that tells if the coordinate system for this GeometryField
     97        is geographic in nature (e.g., uses lat/lon instead of projected
     98        units).
     99        """
     100        return self._unit_name in self.geodetic_units
     101       
    82102    def get_distance(self, dist):
    83103        """
    84104        Returns a distance number in units of the field.  For example, if
     
    87107        """
    88108       
    89109        if isinstance(dist, Distance):
    90             if self._unit_name in self.geodetic_units:
     110            if self.geodetic:
    91111                # Spherical distance calculation parameter should be in meters.
    92112                dist_param = dist.m
    93113            else:
     
    97117            dist_param = dist
    98118
    99119        # Sphereical distance query; returning meters.
    100         if SpatialBackend.name == 'postgis' and self._unit_name in self.geodetic_units:
     120        if (SpatialBackend.name == 'postgis' and self.geodetic and self._distance_spheroid):
    101121            return [gqn(self._spheroid), dist_param]
    102122        else:
    103123            return [dist_param]
  • django/contrib/gis/db/models/query.py

     
    284284            tolerance = kwargs.get('tolerance', 0.05)
    285285            dist_select = {'distance' : '%s(%s, %s, %s)' % (DISTANCE, geo_col, where[0], tolerance)}
    286286        else:
    287             if len(where) == 3:
     287            if geo_field.geodetic:
    288288                # Spherical distance calculation was requested (b/c spheroid
    289289                # parameter was attached) However, the PostGIS ST_distance_spheroid()
    290290                # procedure may only do queries from point columns to point geometries
     
    295295                    raise TypeError('Spherical distance calculation only supported with Point Geometry parameters')
    296296
    297297                # Call to distance_spheroid() requires the spheroid as well.
    298                 dist_sql = '%s(%s, %s, %s)' % (SpatialBackend.distance_spheroid, geo_col, where[0], where[1])
     298                if len(where) == 3:
     299                    dist_sql = '%s(%s, %s, %s)' % (SpatialBackend.distance_spheroid, geo_col, where[0], where[1])
     300                else:
     301                    dist_sql = '%s(%s, %s)' % (SpatialBackend.distance_sphere, geo_col, where[0])
    299302            else:
    300303                dist_sql = '%s(%s, %s)' % (DISTANCE, geo_col, where[0])
    301304            dist_select = {'distance' : dist_sql}
  • django/contrib/gis/db/backend/__init__.py

     
    2222from django.contrib.gis.db.backend.util import gqn
    2323
    2424# These routines (needed by GeoManager), default to False.
    25 ASGML, ASKML, DISTANCE, DISTANCE_SPHEROID, EXTENT, TRANSFORM, UNION, VERSION = tuple(False for i in range(8))
     25ASGML, ASKML, DISTANCE, DISTANCE_SPHERE, DISTANCE_SPHEROID, EXTENT, TRANSFORM, UNION, VERSION = tuple(False for i in range(9))
    2626
    2727# Lookup types in which the rest of the parameters are not
    2828# needed to be substitute in the WHERE SQL (e.g., the 'relate'
     
    3939    from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
    4040    from django.contrib.gis.db.backend.postgis.query import \
    4141        get_geo_where_clause, POSTGIS_TERMS as GIS_TERMS, \
    42         ASGML, ASKML, DISTANCE, DISTANCE_SPHEROID, DISTANCE_FUNCTIONS, \
    43         EXTENT, GEOM_SELECT, TRANSFORM, UNION, \
     42        DISTANCE, DISTANCE_SPHERE, DISTANCE_SPHEROID, DISTANCE_FUNCTIONS, \
     43        ASGML, ASKML, EXTENT, GEOM_SELECT, TRANSFORM, UNION, \
    4444        MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2
    4545    # PostGIS version info is needed to determine calling order of some
    4646    # stored procedures (e.g., AsGML()).
     
    7676    as_kml = ASKML
    7777    as_gml = ASGML
    7878    distance = DISTANCE
     79    distance_sphere = DISTANCE_SPHERE
    7980    distance_spheroid = DISTANCE_SPHEROID
    8081    extent = EXTENT
    8182    name = SPATIAL_BACKEND
  • django/contrib/gis/db/backend/postgis/query.py

     
    4040    ASKML = get_func('AsKML')
    4141    ASGML = get_func('AsGML')
    4242    DISTANCE = get_func('Distance')
     43    DISTANCE_SPHERE = get_func('distance_sphere')
    4344    DISTANCE_SPHEROID = get_func('distance_spheroid')
    4445    EXTENT = get_func('extent')
    4546    GEOM_FROM_TEXT = get_func('GeomFromText')
     
    8283
    8384class PostGISSphereDistance(PostGISFunction):
    8485    "For PostGIS spherical distance operations."
    85     dist_func = 'distance_spheroid'
     86    dist_func = 'distance_sphere'
    8687    def __init__(self, operator):
    8788        # 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',
     89        super(PostGISSphereDistance, self).__init__(self.dist_func, end_subst=') %s %s',
    9190                                                    operator=operator, result='%%s')
    9291
     92class PostGISSpheroidDistance(PostGISFunction):
     93    "For PostGIS spherical distance operations."
     94    dist_func = 'distance_spheroid'
     95    def __init__(self, operator):
     96        # An extra parameter in `end_subst` is needed for the spheroid string.
     97        super(PostGISSpheroidDistance, self).__init__(self.dist_func,
     98                                                      beg_subst='%s(%s, %%s, %%s',
     99                                                      end_subst=') %s %s',
     100                                                      operator=operator, result='%%s')
     101
    93102class PostGISRelate(PostGISFunctionParam):
    94103    "For PostGIS Relate(<geom>, <pattern>) calls."
    95104    pattern_regex = re.compile(r'^[012TF\*]{9}$')
     
    162171
    163172# Valid distance types and substitutions
    164173dtypes = (Decimal, Distance, float, int, long)
    165 def get_dist_ops(operator):
     174def get_dist_ops(op):
    166175    "Returns operations for both regular and spherical distances."
    167     return (PostGISDistance(operator), PostGISSphereDistance(operator))
     176    return (PostGISDistance(op), PostGISSphereDistance(op), PostGISSpheroidDistance(op))
    168177DISTANCE_FUNCTIONS = {
    169178    'distance_gt' : (get_dist_ops('>'), dtypes),
    170179    'distance_gte' : (get_dist_ops('>='), dtypes),
     
    228237            if lookup_type == 'relate':
    229238                op = op(value[1])
    230239            elif lookup_type in DISTANCE_FUNCTIONS and lookup_type != 'dwithin':
    231                 if field._unit_name == 'degree':
     240                if field.geodetic:
    232241                    # Geodetic distances are only availble from Points to PointFields.
    233242                    if field._geom != 'POINT':
    234243                        raise TypeError('PostGIS spherical operations are only valid on PointFields.')
    235244                    if value[0].geom_typeid != 0:
    236245                        raise TypeError('PostGIS geometry distance parameter is required to be of type Point.')
    237                     op = op[1]
     246                   
     247                    # If the GeometryField `distance_spheroid` keyword is set, then
     248                    # `ST_distance_spheroid` is used instead.
     249                    if field._distance_spheroid: op = op[2]
     250                    else: op = op[1]
    238251                else:
    239252                    op = op[0]
    240253        else:
Back to Top