Author: jdunck
Date: 2007-03-23 11:32:00 -0500 (Fri, 23 Mar 2007)
New Revision: 4785
Added:
django/branches/gis/django/contrib/gis/manager.py
django/branches/gis/django/contrib/gis/postgis.py
django/branches/gis/django/contrib/gis/query.py
Modified:
django/branches/gis/django/contrib/gis/__init__.py
Log:
djgis: checked in latest from jbronn from Mar 19.
Modified: django/branches/gis/django/contrib/gis/__init__.py
===================================================================
--- django/branches/gis/django/contrib/gis/__init__.py 2007-03-23 14:05:31 UTC
(rev 4784)
+++ django/branches/gis/django/contrib/gis/__init__.py 2007-03-23 16:32:00 UTC
(rev 4785)
@@ -1,71 +0,0 @@
-from geos import geomFromWKT, geomToWKT
-from decimal import Decimal
-from django.db import models
-from django.db.models.query import QuerySet
-
-
-class GeoQuerySet(QuerySet):
- # The list of valid query terms
- # override the local QUERY_TERMS in the namespace
- # not sure how to do that locals() hackery
- # possibly in the init change its locals() variables
- # not sure if that will work
-
- QUERY_TERMS = (
- 'exact', 'iexact', 'contains', 'icontains', 'overlaps',
- 'gt', 'gte', 'lt', 'lte', 'in',
- 'startswith', 'istartswith', 'endswith', 'iendswith',
- 'range', 'year', 'month', 'day', 'isnull', 'search',
-)
-
-
-def dprint(arg):
- import re
- import inspect
- print re.match("^\s*dprint\(\s*(.+)\s*\)",
inspect.stack()[1][4][0]).group(1) + ": " + repr(arg)
-
-
-
-class GeometryManager(models.Manager):
- #def filter(self, *args, **kwargs):
- # super(Manager, self).filter(*args, **kwargs)
- # return self.get_query_set().filter(*args, **kwargs)
-
- def get_query_set(self):
- return GeoQuerySet(self.model)
-
-
-
-
-class BoundingBox:
-
- def _geom(self):
- return geomToWKT(self._g)
-
- geom = property(_geom)
-
- def _area(self):
- return self._g.area()
-
- area = property(_area)
-
-
- def __init__(self, ne, sw):
- """
- Create a bounding box using two points
- This points come from a JSON request, so they are strings
- """
- ne = [Decimal(i.strip(' ')) for i in ne[1:-1].split(',')]
- sw = [Decimal(i.strip(' ')) for i in sw[1:-1].split(',')]
- ne_lat = ne[0]
- ne_lng = ne[1]
- sw_lat = sw[0]
- sw_lng = sw[1]
- bb = 'POLYGON(('
- bb += str(ne_lng) + " " + str(ne_lat) + ","
- bb += str(ne_lng) + " " + str(sw_lat) + ","
- bb += str(sw_lng) + " " + str(sw_lat) + ","
- bb += str(sw_lng) + " " + str(ne_lat) + ","
- bb += str(ne_lng) + " " + str(ne_lat)
- bb += '))'
- self._g = geomFromWKT(bb)
Added: django/branches/gis/django/contrib/gis/manager.py
===================================================================
--- django/branches/gis/django/contrib/gis/manager.py
(rev 0)
+++ django/branches/gis/django/contrib/gis/manager.py 2007-03-23 16:32:00 UTC
(rev 4785)
@@ -0,0 +1,13 @@
+from django.db.models.manager import Manager
+from django.contrib.gis.db.models.query import GeoQuerySet
+
+class GeoManager(Manager):
+
+ def get_query_set(self):
+ return GeoQuerySet(model=self.model)
+
+ def geo_filter(self, *args, **kwargs):
+ return self.get_query_set().geo_filter(*args, **kwargs)
+
+ def geo_exclude(self, *args, **kwargs):
+ return self.get_query_set().geo_exclude(*args, **kwargs)
Property changes on: django/branches/gis/django/contrib/gis/manager.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: django/branches/gis/django/contrib/gis/postgis.py
===================================================================
--- django/branches/gis/django/contrib/gis/postgis.py
(rev 0)
+++ django/branches/gis/django/contrib/gis/postgis.py 2007-03-23 16:32:00 UTC
(rev 4785)
@@ -0,0 +1,302 @@
+# This module is meant to re-define the helper routines used by the
+# django.db.models.query objects to be customized for PostGIS.
+from copy import copy
+from django.db import backend
+from django.db.models.query import \
+ LOOKUP_SEPARATOR, QUERY_TERMS, \
+ find_field, FieldFound, get_where_clause
+from django.utils.datastructures import SortedDict
+
+# PostGIS-specific operators. The commented descriptions of these
+# operators come from Section 6.2.2 of the official PostGIS documentation.
+POSTGIS_OPERATORS = {
+ # The "&<" operator returns true if A's bounding box overlaps or is to the
left of B's bounding box.
+ 'overlapsleft' : '&< %s',
+ # The "&>" operator returns true if A's bounding box overlaps or is to the
right of B's bounding box.
+ 'overlapsright' : '&> %s',
+ # The "<<" operator returns true if A's bounding box is strictly to the
left of B's bounding box.
+ 'left' : '<< %s',
+ # The ">>" operator returns true if A's bounding box is strictly to the
right of B's bounding box.
+ 'right' : '>> %s',
+ # The "&<|" operator returns true if A's bounding box overlaps or is below
B's bounding box.
+ 'overlapsbelow' : '&<| %s',
+ # The "|&>" operator returns true if A's bounding box overlaps or is above
B's bounding box.
+ 'overlapsabove' : '|&> %s',
+ # The "<<|" operator returns true if A's bounding box is strictly below
B's bounding box.
+ 'strictlybelow' : '<<| %s',
+ # The "|>>" operator returns true if A's bounding box is strictly above
B's bounding box.
+ 'strictlyabove' : '|>> %s',
+ # The "~=" operator is the "same as" operator. It tests actual geometric
equality of two features. So if
+ # A and B are the same feature, vertex-by-vertex, the operator returns
true.
+ 'sameas' : '~= %s',
+ # The "@" operator returns true if A's bounding box is completely
contained by B's bounding box.
+ 'contained' : '@ %s',
+ # The "~" operator returns true if A's bounding box completely contains
B's bounding box.
+ 'bbcontains' : '~ %s',
+ # The "&&" operator is the "overlaps" operator. If A's bounding boux
overlaps B's bounding box the
+ # operator returns true.
+ 'bboverlaps' : '&& %s',
+ }
+
+# PostGIS Geometry Functions -- most of these use GEOS.
+POSTGIS_GEOMETRY_FUNCTIONS = {
+ 'distance' : 'Distance',
+ 'equals' : 'Equals',
+ 'disjoint' : 'Disjoint',
+ 'intersects' : 'Intersects',
+ 'touches' : 'Touches',
+ 'crosses' : 'Crosses',
+ 'within' : 'Within',
+ 'overlaps' : 'Overlaps',
+ 'contains' : 'Contains',
+ 'intersects' : 'Intersects',
+ 'relate' : 'Relate',
+ }
+
+# These are the PostGIS-customized QUERY_TERMS, combines both the operators
+# and the geometry functions.
+POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first
+POSTGIS_TERMS.extend(list(POSTGIS_GEOMETRY_FUNCTIONS.keys())) # Adding on the
Geometry Functions
+
+def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
+ if table_prefix.endswith('.'):
+ table_prefix = backend.quote_name(table_prefix[:-1])+'.'
+ field_name = backend.quote_name(field_name)
+
+ # See if a PostGIS operator matches the lookup type first
+ try:
+ return '%s%s %s' % (table_prefix, field_name,
(POSTGIS_OPERATORS[lookup_type] % '%s'))
+ except KeyError:
+ pass
+
+ # See if a PostGIS Geometry function matches the lookup type next
+ try:
+ return '%s(%s%s, %%s)' % (POSTGIS_GEOMETRY_FUNCTIONS[lookup_type],
table_prefix, field_name)
+ except KeyError:
+ pass
+
+ # For any other lookup type
+ if lookup_type == 'isnull':
+ return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and
'NOT ' or ''))
+
+ raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
+
+def geo_parse_lookup(kwarg_items, opts):
+ # Helper function that handles converting API kwargs
+ # (e.g. "name__exact": "tom") to SQL.
+ # Returns a tuple of (tables, joins, where, params).
+
+ # 'joins' is a sorted dictionary describing the tables that must be joined
+ # to complete the query. The dictionary is sorted because creation order
+ # is significant; it is a dictionary to ensure uniqueness of alias names.
+ #
+ # Each key-value pair follows the form
+ # alias: (table, join_type, condition)
+ # where
+ # alias is the AS alias for the joined table
+ # table is the actual table name to be joined
+ # join_type is the type of join (INNER JOIN, LEFT OUTER JOIN, etc)
+ # condition is the where-like statement over which narrows the join.
+ # alias will be derived from the lookup list name.
+ #
+ # At present, this method only every returns INNER JOINs; the option is
+ # there for others to implement custom Q()s, etc that return other join
+ # types.
+ joins, where, params = SortedDict(), [], []
+
+ for kwarg, value in kwarg_items:
+ path = kwarg.split(LOOKUP_SEPARATOR)
+ # Extract the last elements of the kwarg.
+ # The very-last is the lookup_type (equals, like, etc).
+ # The second-last is the table column on which the lookup_type is
+ # to be performed. If this name is 'pk', it will be substituted with
+ # the name of the primary key.
+ # If there is only one part, or the last part is not a query
+ # term, assume that the query is an __exact
+ lookup_type = path.pop()
+ if lookup_type == 'pk':
+ lookup_type = 'exact'
+ path.append(None)
+ elif len(path) == 0 or lookup_type not in POSTGIS_TERMS:
+ path.append(lookup_type)
+ lookup_type = 'exact'
+
+ if len(path) < 1:
+ raise TypeError, "Cannot parse keyword query %r" % kwarg
+
+ if value is None:
+ # Interpret '__exact=None' as the sql '= NULL'; otherwise, reject
+ # all uses of None as a query value.
+ if lookup_type != 'exact':
+ raise ValueError, "Cannot use None as a query value"
+
+ joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts,
opts.db_table, None)
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ return joins, where, params
+
+def lookup_inner(path, lookup_type, value, opts, table, column):
+ qn = backend.quote_name
+ joins, where, params = SortedDict(), [], []
+ current_opts = opts
+ current_table = table
+ current_column = column
+ intermediate_table = None
+ join_required = False
+
+ name = path.pop(0)
+ # Has the primary key been requested? If so, expand it out
+ # to be the name of the current class' primary key
+ if name is None or name == 'pk':
+ name = current_opts.pk.name
+
+ # Try to find the name in the fields associated with the current class
+ try:
+ # Does the name belong to a defined many-to-many field?
+ field = find_field(name, current_opts.many_to_many, False)
+ if field:
+ new_table = current_table + '__' + name
+ new_opts = field.rel.to._meta
+ new_column = new_opts.pk.column
+
+ # Need to create an intermediate table join over the m2m table
+ # This process hijacks current_table/column to point to the
+ # intermediate table.
+ current_table = "m2m_" + new_table
+ intermediate_table = field.m2m_db_table()
+ join_column = field.m2m_reverse_name()
+ intermediate_column = field.m2m_column_name()
+
+ raise FieldFound
+
+ # Does the name belong to a reverse defined many-to-many field?
+ field = find_field(name,
current_opts.get_all_related_many_to_many_objects(), True)
+ if field:
+ new_table = current_table + '__' + name
+ new_opts = field.opts
+ new_column = new_opts.pk.column
+
+ # Need to create an intermediate table join over the m2m table.
+ # This process hijacks current_table/column to point to the
+ # intermediate table.
+ current_table = "m2m_" + new_table
+ intermediate_table = field.field.m2m_db_table()
+ join_column = field.field.m2m_column_name()
+ intermediate_column = field.field.m2m_reverse_name()
+
+ raise FieldFound
+
+ # Does the name belong to a one-to-many field?
+ field = find_field(name, current_opts.get_all_related_objects(), True)
+ if field:
+ new_table = table + '__' + name
+ new_opts = field.opts
+ new_column = field.field.column
+ join_column = opts.pk.column
+
+ # 1-N fields MUST be joined, regardless of any other conditions.
+ join_required = True
+
+ raise FieldFound
+
+ # Does the name belong to a one-to-one, many-to-one, or regular field?
+ field = find_field(name, current_opts.fields, False)
+ if field:
+ if field.rel: # One-to-One/Many-to-one field
+ new_table = current_table + '__' + name
+ new_opts = field.rel.to._meta
+ new_column = new_opts.pk.column
+ join_column = field.column
+ raise FieldFound
+ elif path:
+ # For regular fields, if there are still items on the path,
+ # an error has been made. We munge "name" so that the error
+ # properly identifies the cause of the problem.
+ name += LOOKUP_SEPARATOR + path[0]
+ else:
+ raise FieldFound
+
+ except FieldFound: # Match found, loop has been shortcut.
+ pass
+ else: # No match found.
+ raise TypeError, "Cannot resolve keyword '%s' into field" % name
+
+ # Check whether an intermediate join is required between current_table
+ # and new_table.
+ if intermediate_table:
+ joins[qn(current_table)] = (
+ qn(intermediate_table), "LEFT OUTER JOIN",
+ "%s.%s = %s.%s" % (qn(table), qn(current_opts.pk.column),
qn(current_table), qn(intermediate_column))
+ )
+
+ if path:
+ # There are elements left in the path. More joins are required.
+ if len(path) == 1 and path[0] in (new_opts.pk.name, None) \
+ and lookup_type in ('exact', 'isnull') and not join_required:
+ # If the next and final name query is for a primary key,
+ # and the search is for isnull/exact, then the current
+ # (for N-1) or intermediate (for N-N) table can be used
+ # for the search. No need to join an extra table just
+ # to check the primary key.
+ new_table = current_table
+ else:
+ # There are 1 or more name queries pending, and we have ruled out
+ # any shortcuts; therefore, a join is required.
+ joins[qn(new_table)] = (
+ qn(new_opts.db_table), "INNER JOIN",
+ "%s.%s = %s.%s" % (qn(current_table), qn(join_column),
qn(new_table), qn(new_column))
+ )
+ # If we have made the join, we don't need to tell subsequent
+ # recursive calls about the column name we joined on.
+ join_column = None
+
+ # There are name queries remaining. Recurse deeper.
+ joins2, where2, params2 = lookup_inner(path, lookup_type, value,
new_opts, new_table, join_column)
+
+ joins.update(joins2)
+ where.extend(where2)
+ params.extend(params2)
+ else:
+ # No elements left in path. Current element is the element on which
+ # the search is being performed.
+
+ if join_required:
+ # Last query term is a RelatedObject
+ if field.field.rel.multiple:
+ # RelatedObject is from a 1-N relation.
+ # Join is required; query operates on joined table.
+ column = new_opts.pk.name
+ joins[qn(new_table)] = (
+ qn(new_opts.db_table), "INNER JOIN",
+ "%s.%s = %s.%s" % (qn(current_table), qn(join_column),
qn(new_table), qn(new_column))
+ )
+ current_table = new_table
+ else:
+ # RelatedObject is from a 1-1 relation,
+ # No need to join; get the pk value from the related object,
+ # and compare using that.
+ column = current_opts.pk.name
+ elif intermediate_table:
+ # Last query term is a related object from an N-N relation.
+ # Join from intermediate table is sufficient.
+ column = join_column
+ elif name == current_opts.pk.name and lookup_type in ('exact',
'isnull') and current_column:
+ # Last query term is for a primary key. If previous iterations
+ # introduced a current/intermediate table that can be used to
+ # optimize the query, then use that table and column name.
+ column = current_column
+ else:
+ # Last query term was a normal field.
+ column = field.column
+
+ # If the field is a geometry field, then the WHERE clause will need to
be obtained
+ # with the get_geo_where_clause()
+ if hasattr(field, '_geom'):
+ where.append(get_geo_where_clause(lookup_type, current_table +
'.', column, value))
+ else:
+ raise TypeError, 'Field "%s" (%s) is not a Geometry Field.' %
(column, field.__class__.__name__)
+ params.extend(field.get_db_prep_lookup(lookup_type, value))
+
+ return joins, where, params
+
Property changes on: django/branches/gis/django/contrib/gis/postgis.py
___________________________________________________________________
Name: svn:eol-style
+ native
Added: django/branches/gis/django/contrib/gis/query.py
===================================================================
--- django/branches/gis/django/contrib/gis/query.py
(rev 0)
+++ django/branches/gis/django/contrib/gis/query.py 2007-03-23 16:32:00 UTC
(rev 4785)
@@ -0,0 +1,36 @@
+from django.db.models.query import *
+from django.contrib.gis.db.models.postgis import geo_parse_lookup
+
+class GeoQ(Q):
+ "Geographical query encapsulation object."
+
+ def get_sql(self, opts):
+ "Overloaded to use the geo_parse_lookup() function instead of
parse_lookup()"
+ return geo_parse_lookup(self.kwargs.items(), opts)
+
+class GeoQuerySet(QuerySet):
+ "Geographical-enabled QuerySet object."
+
+ def geo_filter(self, *args, **kwargs):
+ "Returns a new GeoQuerySet instance with the args ANDed to the
existing set."
+ return self._geo_filter_or_exclude(None, *args, **kwargs)
+
+ def geo_exclude(self, *args, **kwargs):
+ "Returns a new GeoQuerySet instance with NOT (args) ANDed to the
existing set."
+ return self._geo_filter_or_exclude(QNot, *args, **kwargs)
+
+ def _geo_filter_or_exclude(self, mapper, *args, **kwargs):
+ # mapper is a callable used to transform Q objects,
+ # or None for identity transform
+ if mapper is None:
+ mapper = lambda x: x
+ if len(args) > 0 or len(kwargs) > 0:
+ assert self._limit is None and self._offset is None, \
+ "Cannot filter a query once a slice has been taken."
+
+ clone = self._clone()
+ if len(kwargs) > 0:
+ clone._filters = clone._filters & mapper(GeoQ(**kwargs)) # Using
the GeoQ object for our filters instead
+ if len(args) > 0:
+ clone._filters = clone._filters & reduce(operator.and_,
map(mapper, args))
+ return clone
Property changes on: django/branches/gis/django/contrib/gis/query.py
___________________________________________________________________
Name: svn:eol-style
+ native
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---