Author: jbronn
Date: 2007-09-17 07:09:54 -0500 (Mon, 17 Sep 2007)
New Revision: 6369

Added:
   django/branches/gis/django/contrib/gis/utils/geoip.py
   django/branches/gis/django/contrib/gis/utils/layermapping.py
Removed:
   django/branches/gis/django/contrib/gis/utils/LayerMapping.py
Modified:
   django/branches/gis/django/contrib/gis/utils/__init__.py
Log:
gis: Added the GeoIP utility for IP-based geolocation; got rid of camel case 
and fixed relative imports in utils module.


Deleted: django/branches/gis/django/contrib/gis/utils/LayerMapping.py
===================================================================
--- django/branches/gis/django/contrib/gis/utils/LayerMapping.py        
2007-09-17 04:54:53 UTC (rev 6368)
+++ django/branches/gis/django/contrib/gis/utils/LayerMapping.py        
2007-09-17 12:09:54 UTC (rev 6369)
@@ -1,340 +0,0 @@
-# LayerMapping -- A Django Model/OGR Layer Mapping Utility
-"""
-The LayerMapping class provides a way to map the contents of OGR
- vector files (e.g. SHP files) to Geographic-enabled Django models.
-
-This grew out of my personal needs, specifically the code repetition
- that went into pulling geometries and fields out of an OGR layer,
- converting to another coordinate system (e.g. WGS84), and then inserting
- into a Geographic Django model.
-
-This utility is still in early stages of development, so its usage
- is subject to change -- please report any bugs.
-
-TODO: Unit tests and documentation.
-
-Requirements:  OGR C Library (from GDAL) required.
-
-Usage: 
-  lm = LayerMapping(model, source_file, mapping) where,
-
-  model -- GeoDjango model (not an instance)
-
-  data -- OGR-supported data source file (e.g. a shapefile) or
-          gdal.DataSource instance
-
-  mapping -- A python dictionary, keys are strings corresponding
-             to the GeoDjango model field, and values correspond to
-             string field names for the OGR feature, or if the model field
-             is a geographic then it should correspond to the OGR
-             geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
-
-Keyword Args:
-  layer -- The index of the layer to use from the Data Source (defaults to 0)
-
-  source_srs -- Use this to specify the source SRS manually (for example, 
-                 some shapefiles don't come with a '.prj' file)
-
-Example:
-
- 1. You need a GDAL-supported data source, like a shapefile.
-
-  Assume we're using the test_poly SHP file:
-  >>> from django.contrib.gis.gdal import DataSource
-  >>> ds = DataSource('test_poly.shp')
-  >>> layer = ds[0]
-  >>> print layer.fields # Exploring the fields in the layer, we only want the 
'str' field.
-  ['float', 'int', 'str']
-  >>> print len(layer) # getting the number of features in the layer (should 
be 3)
-  3
-  >>> print layer.geom_type # Should be 3 (a Polygon)
-  3
-  >>> print layer.srs # WGS84
-  GEOGCS["GCS_WGS_1984",
-      DATUM["WGS_1984",
-          SPHEROID["WGS_1984",6378137,298.257223563]],
-      PRIMEM["Greenwich",0],
-      UNIT["Degree",0.017453292519943295]]
-
- 2. Now we define our corresponding Django model (make sure to use syncdb):
-
-  from django.contrib.gis.db import models
-  class TestGeo(models.Model, models.GeoMixin):
-      name = models.CharField(maxlength=25) # corresponds to the 'str' field
-      poly = models.PolygonField(srid=4269) # we want our model in a different 
SRID
-      objects = models.GeoManager()
-      def __str__(self):
-          return 'Name: %s' % self.name
-
- 3. Use LayerMapping to extract all the features and place them in the 
database:
-
-  >>> from django.contrib.gis.utils import LayerMapping
-  >>> from geoapp.models import TestGeo
-  >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' 
layer field.
-                 'poly' : 'POLYGON', # For geometry fields use OGC name.
-                 } # The mapping is a dictionary
-  >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) 
-  >>> lm.save(verbose=True) # Save the layermap, imports the data. 
-  Saved: Name: 1
-  Saved: Name: 2
-  Saved: Name: 3
-
- LayerMapping just transformed the three geometries from the SHP file from 
their
-   source spatial reference system (WGS84) to the spatial reference system of
-   the GeoDjango model (NAD83).  If no spatial reference system is defined for
-   the layer, use the `source_srs` keyword with a SpatialReference object to
-   specify one. Further, data is selectively imported from the given data 
source 
-   fields into the model fields.
-"""
-from types import StringType, TupleType
-from datetime import datetime
-from django.contrib.gis.gdal import \
-     OGRGeometry, OGRGeomType, SpatialReference, CoordTransform, \
-     DataSource, OGRException
-from django.contrib.gis.gdal.field import Field, OFTInteger, OFTReal, 
OFTString, OFTDateTime
-from django.contrib.gis.models import GeometryColumns, SpatialRefSys
-from django.db import connection, transaction
-from django.core.exceptions import ObjectDoesNotExist
-
-# A mapping of given geometry types to their OGR integer type.
-ogc_types = {'POINT' : OGRGeomType('Point'),
-             'LINESTRING' : OGRGeomType('LineString'),
-             'POLYGON' : OGRGeomType('Polygon'),
-             'MULTIPOINT' : OGRGeomType('MultiPoint'),
-             'MULTILINESTRING' : OGRGeomType('MultiLineString'),
-             'MULTIPOLYGON' : OGRGeomType('MultiPolygon'),
-             'GEOMETRYCOLLECTION' : OGRGeomType('GeometryCollection'),
-             }
-
-# The django.contrib.gis model types.
-gis_fields = {'PointField' : 'POINT',
-              'LineStringField': 'LINESTRING',
-              'PolygonField': 'POLYGON',
-              'MultiPointField' : 'MULTIPOINT',
-              'MultiLineStringField' : 'MULTILINESTRING',
-              'MultiPolygonField' : 'MULTIPOLYGON',
-              }
-
-# Acceptable 'base' types for a multi-geometry type.
-multi_types = {'POINT' : OGRGeomType('MultiPoint'),
-               'LINESTRING' : OGRGeomType('MultiLineString'),
-               'POLYGON' : OGRGeomType('MultiPolygon'),
-               }
-
-def map_foreign_key(django_field):
-    from django.db.models.fields.related import ForeignKey
-
-    if not django_field.__class__ is ForeignKey:
-        return django_field.__class__.__name__
-
-     
-    rf=django_field.rel.get_related_field()
-
-    return rf.get_internal_type()
-                
-# The acceptable Django field types that map to OGR fields.
-field_types = {
-               'AutoField' : OFTInteger,
-               'IntegerField' : OFTInteger,
-               'FloatField' : OFTReal,
-               'DateTimeField' : OFTDateTime,
-               'DecimalField' : OFTReal,
-               'CharField' : OFTString,
-               'SmallIntegerField' : OFTInteger,
-               'PositiveSmallIntegerField' : OFTInteger,
-               }
-
-def make_multi(geom_name, model_type):
-    "Determines whether the geometry should be made into a GeometryCollection."
-    if (geom_name in multi_types) and (model_type.startswith('Multi')):
-        return True
-    else:
-        return False
-
-def check_feature(feat, model_fields, mapping):
-    "Checks the OGR layer feature."
-
-    HAS_GEO = False
-
-    # Incrementing through each model_field & ogr_field in the given mapping.
-    for model_field, ogr_field in mapping.items():
-
-        # Making sure the given mapping model field is in the given model 
fields.
-        if model_field in model_fields:
-            model_type = model_fields[model_field]
-        elif model_field[:-3] in model_fields: #foreign key
-            model_type = model_fields[model_field[:-3]]
-        else:
-            raise Exception, 'Given mapping field "%s" not in given Model 
fields!' % model_field
-
-        ## Handling if we get a geometry in the Field ###
-        if ogr_field in ogc_types:
-            # At this time, no more than one geographic field per model =(
-            if HAS_GEO:
-                raise Exception, 'More than one geographic field in mapping 
not allowed (yet).'
-            else:
-                HAS_GEO = ogr_field
-
-            # Making sure this geometry field type is a valid Django GIS field.
-            if not model_type in gis_fields:
-                raise Exception, 'Unknown Django GIS field type "%s"' % 
model_type
-            
-            # Getting the OGRGeometry, it's type (an integer) and it's name (a 
string)
-            geom  = feat.geom
-            gtype = geom.geom_type
-            gname = geom.geom_name
-
-            if make_multi(gname, model_type):
-                # Do we have to 'upsample' into a Geometry Collection?
-                pass
-            elif gtype == ogc_types[gis_fields[model_type]]:
-                # The geometry type otherwise was expected
-                pass
-            else:
-                raise Exception, 'Invalid mapping geometry; model has %s, 
feature has %s' % (model_type, gtype)
-
-        ## Handling other fields 
-        else:
-            # Making sure the model field is
-            if not model_type in field_types:
-                raise Exception, 'Django field type "%s" has no OGR mapping 
(yet).' % model_type
-
-            # Otherwise, we've got an OGR Field.  Making sure that an
-            # index exists for the mapping OGR field.
-            try:
-                fi = feat.index(ogr_field)
-            except:
-                raise Exception, 'Given mapping OGR field "%s" not in given 
OGR layer feature!' % ogr_field
-                    
-def check_layer(layer, fields, mapping):
-    "Checks the OGR layer by incrementing through and checking each feature."
-    # Incrementing through each feature in the layer.
-    for feat in layer:
-        check_feature(feat, fields, mapping)
-
-def check_srs(layer, source_srs):
-    "Checks the compatibility of the given spatial reference object."
-    if isinstance(source_srs, SpatialReference):
-        sr = source_srs
-    elif isinstance(source_srs, SpatialRefSys):
-        sr = source_srs.srs
-    else:
-        sr = layer.srs
-    if not sr:
-        raise Exception, 'No source reference system defined.'
-    else:
-        return sr
-
-class LayerMapping:
-    "A class that maps OGR Layers to Django Models."
-
-    def __init__(self, model, data, mapping, layer=0, source_srs=None):
-        "Takes the Django model, the data source, and the mapping (dictionary)"
-
-        # Getting the field names and types from the model
-        fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)
-        # Getting the DataSource and its Layer
-        if isinstance(data, basestring):
-            self.ds = DataSource(data)
-        else:
-            self.ds = data
-        self.layer = self.ds[layer]
-
-        # Checking the layer -- intitialization of the object will fail if
-        #  things don't check out before hand.
-        check_layer(self.layer, fields, mapping)
-        
-        # Since the layer checked out, setting the fields and the mapping.
-        self.fields = fields
-        self.mapping = mapping
-        self.model = model
-        self.source_srs = check_srs(self.layer, source_srs)
-
-    @transaction.commit_on_success
-    def save(self, verbose=False):
-        "Runs the layer mapping on the given SHP file, and saves to the 
database."
-
-        # Getting the GeometryColumn object.
-        try:
-            geo_col = 
GeometryColumns.objects.get(f_table_name=self.model._meta.db_table)
-        except:
-            raise Exception, 'Geometry column does not exist. (did you run 
syncdb?)'
-        
-        # Getting the coordinate system needed for transformation (with 
CoordTransform)  
-        try:
-            # Getting the target spatial reference system
-            target_srs = SpatialRefSys.objects.get(srid=geo_col.srid).srs
-
-            # Creating the CoordTransform object
-            ct = CoordTransform(self.source_srs, target_srs)
-        except Exception, msg:
-            raise Exception, 'Could not translate between the data source and 
model geometry: %s' % msg
-
-        for feat in self.layer:
-            # The keyword arguments for model construction
-            kwargs = {}
-
-            # Incrementing through each model field and the OGR field in the 
mapping
-            all_prepped = True
-
-            for model_field, ogr_field in self.mapping.items():
-                is_fk = False
-                try:
-                    model_type = self.fields[model_field]
-                except KeyError: #foreign key
-                    model_type = self.fields[model_field[:-3]]
-                    is_fk = True
-
-                if ogr_field in ogc_types:
-                    ## Getting the OGR geometry from the field
-                    geom = feat.geom
-
-                    if make_multi(geom.geom_name, model_type):
-                        # Constructing a multi-geometry type to contain the 
single geometry
-                        multi_type = multi_types[geom.geom_name]
-                        g = OGRGeometry(multi_type)
-                        g.add(geom)
-                    else:
-                        g = geom
-
-                    # Transforming the geometry with our Coordinate 
Transformation object.
-                    g.transform(ct)
-
-                    # Updating the keyword args with the WKT of the 
transformed model.
-                    val = g.wkt
-                else:
-                    ## Otherwise, this is an OGR field type
-                    fi = feat.index(ogr_field)
-                    val = feat[fi].value
-
-                if is_fk:
-                    rel_obj = None
-                    field_name = model_field[:-3]
-                    try:
-                        #FIXME: refactor to efficiently fetch FKs.
-                        #  Requires significant re-work. :-/
-                        rel = self.model._meta.get_field(field_name).rel
-                        rel_obj = rel.to._default_manager.get(**{('%s__exact' 
% rel.field_name):val})
-                    except ObjectDoesNotExist:
-                        all_prepped = False
-                    
-                    kwargs[model_field[:-3]] = rel_obj
-                else:
-                    kwargs[model_field] = val
-
-            # Constructing the model using the constructed keyword args
-            if all_prepped:
-                m = self.model(**kwargs)
-
-                # Saving the model
-                try:
-                    if all_prepped:
-                        m.save()
-                        if verbose: print 'Saved: %s' % str(m)                 
       
-                    else:
-                        print "Skipping %s due to missing relation." % kwargs
-                except SystemExit:
-                    raise
-                except Exception, e:
-                    print "Failed to save %s\n  Continuing" % kwargs
-                    

Modified: django/branches/gis/django/contrib/gis/utils/__init__.py
===================================================================
--- django/branches/gis/django/contrib/gis/utils/__init__.py    2007-09-17 
04:54:53 UTC (rev 6368)
+++ django/branches/gis/django/contrib/gis/utils/__init__.py    2007-09-17 
12:09:54 UTC (rev 6369)
@@ -1,3 +1,10 @@
-from LayerMapping import LayerMapping
-from inspect_data import sample
+from django.contrib.gis.utils.layermapping import LayerMapping
+from django.contrib.gis.utils.inspect_data import sample
+from django.contrib.gis.utils.shortcuts import render_to_kml
 
+# Importing GeoIP
+try:
+    from django.contrib.gis.utils.geoip import GeoIP
+    HAS_GEOIP = True
+except:
+    HAS_GEOIP = False

Added: django/branches/gis/django/contrib/gis/utils/geoip.py
===================================================================
--- django/branches/gis/django/contrib/gis/utils/geoip.py                       
        (rev 0)
+++ django/branches/gis/django/contrib/gis/utils/geoip.py       2007-09-17 
12:09:54 UTC (rev 6369)
@@ -0,0 +1,280 @@
+"""
+ This module houses the GeoIP object, a ctypes wrapper for the MaxMind GeoIP(R)
+  C API (http://www.maxmind.com/app/c).
+
+ GeoIP(R) is a registered trademark of MaxMind, LLC of Boston, Massachusetts.
+
+ For IP-based geolocation, this module requires the GeoLite Country and City
+  datasets, in binary format (CSV will not work!).  The datasets may be 
+  downloaded from MaxMind at http://www.maxmind.com/download/geoip/database/.
+  Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
+  corresponding to settings.GEOIP_PATH.  See the GeoIP docstring and examples
+  below for more details.
+
+ TODO: Verify compatibility with Windows.
+
+ Example:
+
+ >>> from django.contrib.gis.utils import GeoIP
+ >>> g = GeoIP()
+ >>> g.country('google.com')
+ {'country_code': 'US', 'country_name': 'United States'}
+ >>> g.city('72.14.207.99')
+ {'area_code': 650,
+ 'city': 'Mountain View',
+ 'country_code': 'US',
+ 'country_code3': 'USA',
+ 'country_name': 'United States',
+ 'dma_code': 807,
+ 'latitude': 37.419200897216797,
+ 'longitude': -122.05740356445312,
+ 'postal_code': '94043',
+ 'region': 'CA'}
+ >>> g.lat_lon('salon.com')
+ (37.789798736572266, -122.39420318603516)
+ >>> g.lon_lat('uh.edu')
+ (-95.415199279785156, 29.77549934387207) 
+ >>> g.geos('24.124.1.80').wkt
+ 'POINT (-95.2087020874023438 39.0392990112304688)'
+"""
+import os, re
+from ctypes import c_char_p, c_float, c_int, string_at, Structure, CDLL, 
POINTER
+from django.conf import settings
+
+# The Exception class for GeoIP Errors.
+class GeoIPException(Exception): pass
+
+# The shared library for the GeoIP C API.  May be downloaded
+#  from http://www.maxmind.com/download/geoip/api/c/
+if os.name == 'nt':
+    ext = '.dll'
+elif os.name == 'posix':
+    platform = os.uname()[0]
+    if platform in ('Linux', 'SunOS'):
+        ext = '.so'
+    elif platofm == 'Darwin':
+        ext = '.dylib'
+    else:
+        raise GeoIPException('Unknown POSIX platform "%s"' % platform)
+lgeoip = CDLL('libGeoIP' + ext)
+
+# A regular expression for recognizing IP addresses
+ipregex = 
re.compile(r'^(?P<w>\d\d?\d?)\.(?P<x>\d\d?\d?)\.(?P<y>\d\d?\d?)\.(?P<z>\d\d?\d?)$')
+
+# The flags for GeoIP memory caching.
+# GEOIP_STANDARD - read database from filesystem, uses least memory.
+#
+# GEOIP_MEMORY_CACHE - load database into memory, faster performance
+#        but uses more memory
+#
+# GEOIP_CHECK_CACHE - check for updated database.  If database has been 
updated,
+#        reload filehandle and/or memory cache.
+#
+# GEOIP_INDEX_CACHE - just cache
+#        the most frequently accessed index portion of the database, resulting
+#        in faster lookups than GEOIP_STANDARD, but less memory usage than
+#        GEOIP_MEMORY_CACHE - useful for larger databases such as
+#        GeoIP Organization and GeoIP City.  Note, for GeoIP Country, Region
+#        and Netspeed databases, GEOIP_INDEX_CACHE is equivalent to 
GEOIP_MEMORY_CACHE
+#
+cache_options = {0 : c_int(0), # GEOIP_STANDARD
+                 1 : c_int(1), # GEOIP_MEMORY_CACHE
+                 2 : c_int(2), # GEOIP_CHECK_CACHE
+                 4 : c_int(4), # GEOIP_INDEX_CACHE
+                 }
+
+# GeoIPRecord C Structure definition.
+class GeoIPRecord(Structure):
+    _fields_ = [('country_code', c_char_p),
+                ('country_code3', c_char_p),
+                ('country_name', c_char_p),
+                ('region', c_char_p),
+                ('city', c_char_p),
+                ('postal_code', c_char_p),
+                ('latitude', c_float),
+                ('longitude', c_float),
+                ('dma_code', c_int),
+                ('area_code', c_int),
+                ]
+
+# ctypes function prototypes
+record_by_addr = lgeoip.GeoIP_record_by_addr
+record_by_addr.restype = POINTER(GeoIPRecord)
+record_by_name = lgeoip.GeoIP_record_by_name
+record_by_name.restype = POINTER(GeoIPRecord)
+
+# The exception class for GeoIP Errors.
+class GeoIPException(Exception): pass
+
+class GeoIP(object):
+    def __init__(self, path=None, country=None, city=None,
+                 cache=0):
+        """
+        Initializes the GeoIP object, no parameters are required to use default
+         settings.  Keyword arguments may be passed in to customize the 
locations
+         of the GeoIP data sets.
+
+        * path: Base directory where the GeoIP data files (*.dat) are located.
+            Assumes that both the city and country data sets are located in
+            this directory.  Overrides the GEOIP_PATH settings attribute.
+
+        * country: The name of the GeoIP country data file.  Defaults to
+            'GeoIP.dat'; overrides the GEOIP_COUNTRY settings attribute.
+
+        * city: The name of the GeoIP city data file.  Defaults to
+            'GeoLiteCity.dat'; overrides the GEOIP_CITY settings attribute.
+
+        * cache: The cache settings when opening up the GeoIP datasets,
+            and may be an integer in (0, 1, 2, 4).  Defaults to 0, meaning
+            that the data is read from the disk.
+        """
+
+        # Checking the given cache option.
+        if cache in cache_options:
+            self._cache = cache_options[cache]
+        else:
+            raise GeoIPException('Invalid caching option: %s' % cache)
+
+        # Getting the GeoIP data path.
+        if not path:
+            try:
+                self._path = settings.GEOIP_PATH
+            except AttributeError:
+                raise GeoIPException('Must specify GEOIP_PATH in your 
settings.py')
+        else:
+            self._path = path
+        if not os.path.isdir(self._path):
+            raise GeoIPException('GEOIP_PATH must be set to a directory.')
+
+        # Getting the GeoIP country data file.
+        if not country:
+            try:
+                cntry_file = settings.GEOIP_COUNTRY
+            except AttributeError:
+                cntry_file = 'GeoIP.dat'
+        else:
+            cntry_file = country
+        self._country_file = os.path.join(self._path, cntry_file)
+
+        # Getting the GeoIP city data file.
+        if not city:
+            try:
+                city_file = settings.GEOIP_CITY
+            except AttributeError:
+                city_file = 'GeoLiteCity.dat'
+        else:
+            city_file = city
+        self._city_file = os.path.join(self._path, city_file)
+
+        # Opening up the GeoIP country data file.
+        if os.path.isfile(self._country_file):
+            self._country = lgeoip.GeoIP_open(c_char_p(self._country_file), 
self._cache)
+        else:
+            self._country = None
+
+        # Opening the GeoIP city data file.
+        if os.path.isfile(self._city_file):
+            self._city = lgeoip.GeoIP_open(c_char_p(self._city_file), 
self._cache)
+        else:
+            self._city = None
+    
+    def country(self, query):
+        """
+        Returns a dictonary with with the country code and name when given an 
+         IP address or a Fully Qualified Domain Name (FQDN).  For example, both
+         '24.124.1.80' and 'djangoproject.com' are valid parameters.
+        """
+        if self._country is None:
+            raise GeoIPException('Invalid GeoIP country data file: %s' % 
self._country_file)
+
+        if ipregex.match(query):
+            # If an IP address was passed in.
+            code = lgeoip.GeoIP_country_code_by_addr(self._country, 
c_char_p(query))
+            name = lgeoip.GeoIP_country_name_by_addr(self._country, 
c_char_p(query))
+        else:
+            # If a FQDN was passed in.
+            code = lgeoip.GeoIP_country_code_by_name(self._country, 
c_char_p(query))
+            name = lgeoip.GeoIP_country_name_by_name(self._country, 
c_char_p(query))
+
+        # Checking our returned country code and name, setting each to
+        #  None, if pointer is invalid.
+        if bool(code): code = string_at(code)
+        else: code = None
+        if bool(name): name = string_at(name)
+        else: name = None
+
+        # Returning the country code and name
+        return {'country_code' : code, 
+                'country_name' : name,
+                }
+
+    def city(self, query):
+        """
+        Returns a dictionary of city information for the given IP address or 
+         Fully Qualified Domain Name (FQDN).  Some information in the 
dictionary
+         may be undefined (None).
+        """
+        if self._city is None:
+            raise GeoIPException('Invalid GeoIP country data file: %s' % 
self._city_file)
+
+        if ipregex.match(query):
+            # If an IP address was passed in
+            ptr = record_by_addr(self._city, c_char_p(query))
+        else:
+            # If a FQDN was passed in.
+            ptr = record_by_name(self._city, c_char_p(query))
+
+        # Checking the pointer to the C structure, if valid pull out elements
+        #  into a dicionary and return.
+        if bool(ptr):
+            record = ptr.contents
+            return dict((tup[0], getattr(record, tup[0])) for tup in 
record._fields_)
+        else:
+            return None
+
+    #### Coordinate retrieval routines ####
+    def _coords(self, query, ordering):
+        cdict = self.city(query)
+        if cdict is None: return None
+        else: return tuple(cdict[o] for o in ordering)
+    
+    def lon_lat(self, query):
+        "Returns a tuple of the (longitude, latitude) for the given query."
+        return self._coords(query, ('longitude', 'latitude'))
+
+    def lat_lon(self, query):
+        "Returns a tuple of the (latitude, longitude) for the given query."
+        return self._coords(query, ('latitude', 'longitude'))
+
+    def geos(self, query):
+        "Returns a GEOS Point object for the given query."
+        ll = self.lon_lat(query)
+        if ll:
+            from django.contrib.gis.geos import Point
+            return Point(ll, srid=4326)
+        else:
+            return None
+
+    #### GeoIP Database Information Routines ####
+    def country_info(self):
+        "Returns information about the GeoIP country database."
+        if self._country is None:
+            ci = 'No GeoIP Country data in "%s"' % self._country_file
+        else:
+            ci = string_at(lgeoip.GeoIP_database_info(self._country))
+        return ci
+    country_info = property(country_info)
+
+    def city_info(self):
+        "Retuns information about the GeoIP city database."
+        if self._city is None:
+            ci = 'No GeoIP City data in "%s"' % self._city_file
+        else:
+            ci = string_at(lgeoip.GeoIP_database_info(self._city))
+        return ci
+    city_info = property(city_info)
+        
+    def info(self):
+        "Returns information about all GeoIP databases in use."
+        return 'Country:\n\t%s\nCity:\n\t%s' % (self.country_info, 
self.city_info)
+    info = property(info)

Copied: django/branches/gis/django/contrib/gis/utils/layermapping.py (from rev 
6302, django/branches/gis/django/contrib/gis/utils/LayerMapping.py)
===================================================================
--- django/branches/gis/django/contrib/gis/utils/layermapping.py                
                (rev 0)
+++ django/branches/gis/django/contrib/gis/utils/layermapping.py        
2007-09-17 12:09:54 UTC (rev 6369)
@@ -0,0 +1,340 @@
+# LayerMapping -- A Django Model/OGR Layer Mapping Utility
+"""
+The LayerMapping class provides a way to map the contents of OGR
+ vector files (e.g. SHP files) to Geographic-enabled Django models.
+
+This grew out of my personal needs, specifically the code repetition
+ that went into pulling geometries and fields out of an OGR layer,
+ converting to another coordinate system (e.g. WGS84), and then inserting
+ into a Geographic Django model.
+
+This utility is still in early stages of development, so its usage
+ is subject to change -- please report any bugs.
+
+TODO: Unit tests and documentation.
+
+Requirements:  OGR C Library (from GDAL) required.
+
+Usage: 
+  lm = LayerMapping(model, source_file, mapping) where,
+
+  model -- GeoDjango model (not an instance)
+
+  data -- OGR-supported data source file (e.g. a shapefile) or
+          gdal.DataSource instance
+
+  mapping -- A python dictionary, keys are strings corresponding
+             to the GeoDjango model field, and values correspond to
+             string field names for the OGR feature, or if the model field
+             is a geographic then it should correspond to the OGR
+             geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
+
+Keyword Args:
+  layer -- The index of the layer to use from the Data Source (defaults to 0)
+
+  source_srs -- Use this to specify the source SRS manually (for example, 
+                 some shapefiles don't come with a '.prj' file)
+
+Example:
+
+ 1. You need a GDAL-supported data source, like a shapefile.
+
+  Assume we're using the test_poly SHP file:
+  >>> from django.contrib.gis.gdal import DataSource
+  >>> ds = DataSource('test_poly.shp')
+  >>> layer = ds[0]
+  >>> print layer.fields # Exploring the fields in the layer, we only want the 
'str' field.
+  ['float', 'int', 'str']
+  >>> print len(layer) # getting the number of features in the layer (should 
be 3)
+  3
+  >>> print layer.geom_type # Should be 3 (a Polygon)
+  3
+  >>> print layer.srs # WGS84
+  GEOGCS["GCS_WGS_1984",
+      DATUM["WGS_1984",
+          SPHEROID["WGS_1984",6378137,298.257223563]],
+      PRIMEM["Greenwich",0],
+      UNIT["Degree",0.017453292519943295]]
+
+ 2. Now we define our corresponding Django model (make sure to use syncdb):
+
+  from django.contrib.gis.db import models
+  class TestGeo(models.Model, models.GeoMixin):
+      name = models.CharField(maxlength=25) # corresponds to the 'str' field
+      poly = models.PolygonField(srid=4269) # we want our model in a different 
SRID
+      objects = models.GeoManager()
+      def __str__(self):
+          return 'Name: %s' % self.name
+
+ 3. Use LayerMapping to extract all the features and place them in the 
database:
+
+  >>> from django.contrib.gis.utils import LayerMapping
+  >>> from geoapp.models import TestGeo
+  >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str' 
layer field.
+                 'poly' : 'POLYGON', # For geometry fields use OGC name.
+                 } # The mapping is a dictionary
+  >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping) 
+  >>> lm.save(verbose=True) # Save the layermap, imports the data. 
+  Saved: Name: 1
+  Saved: Name: 2
+  Saved: Name: 3
+
+ LayerMapping just transformed the three geometries from the SHP file from 
their
+   source spatial reference system (WGS84) to the spatial reference system of
+   the GeoDjango model (NAD83).  If no spatial reference system is defined for
+   the layer, use the `source_srs` keyword with a SpatialReference object to
+   specify one. Further, data is selectively imported from the given data 
source 
+   fields into the model fields.
+"""
+from types import StringType, TupleType
+from datetime import datetime
+from django.contrib.gis.gdal import \
+     OGRGeometry, OGRGeomType, SpatialReference, CoordTransform, \
+     DataSource, OGRException
+from django.contrib.gis.gdal.field import Field, OFTInteger, OFTReal, 
OFTString, OFTDateTime
+from django.contrib.gis.models import GeometryColumns, SpatialRefSys
+from django.db import connection, transaction
+from django.core.exceptions import ObjectDoesNotExist
+
+# A mapping of given geometry types to their OGR integer type.
+ogc_types = {'POINT' : OGRGeomType('Point'),
+             'LINESTRING' : OGRGeomType('LineString'),
+             'POLYGON' : OGRGeomType('Polygon'),
+             'MULTIPOINT' : OGRGeomType('MultiPoint'),
+             'MULTILINESTRING' : OGRGeomType('MultiLineString'),
+             'MULTIPOLYGON' : OGRGeomType('MultiPolygon'),
+             'GEOMETRYCOLLECTION' : OGRGeomType('GeometryCollection'),
+             }
+
+# The django.contrib.gis model types.
+gis_fields = {'PointField' : 'POINT',
+              'LineStringField': 'LINESTRING',
+              'PolygonField': 'POLYGON',
+              'MultiPointField' : 'MULTIPOINT',
+              'MultiLineStringField' : 'MULTILINESTRING',
+              'MultiPolygonField' : 'MULTIPOLYGON',
+              }
+
+# Acceptable 'base' types for a multi-geometry type.
+multi_types = {'POINT' : OGRGeomType('MultiPoint'),
+               'LINESTRING' : OGRGeomType('MultiLineString'),
+               'POLYGON' : OGRGeomType('MultiPolygon'),
+               }
+
+def map_foreign_key(django_field):
+    from django.db.models.fields.related import ForeignKey
+
+    if not django_field.__class__ is ForeignKey:
+        return django_field.__class__.__name__
+
+     
+    rf=django_field.rel.get_related_field()
+
+    return rf.get_internal_type()
+                
+# The acceptable Django field types that map to OGR fields.
+field_types = {
+               'AutoField' : OFTInteger,
+               'IntegerField' : OFTInteger,
+               'FloatField' : OFTReal,
+               'DateTimeField' : OFTDateTime,
+               'DecimalField' : OFTReal,
+               'CharField' : OFTString,
+               'SmallIntegerField' : OFTInteger,
+               'PositiveSmallIntegerField' : OFTInteger,
+               }
+
+def make_multi(geom_name, model_type):
+    "Determines whether the geometry should be made into a GeometryCollection."
+    if (geom_name in multi_types) and (model_type.startswith('Multi')):
+        return True
+    else:
+        return False
+
+def check_feature(feat, model_fields, mapping):
+    "Checks the OGR layer feature."
+
+    HAS_GEO = False
+
+    # Incrementing through each model_field & ogr_field in the given mapping.
+    for model_field, ogr_field in mapping.items():
+
+        # Making sure the given mapping model field is in the given model 
fields.
+        if model_field in model_fields:
+            model_type = model_fields[model_field]
+        elif model_field[:-3] in model_fields: #foreign key
+            model_type = model_fields[model_field[:-3]]
+        else:
+            raise Exception, 'Given mapping field "%s" not in given Model 
fields!' % model_field
+
+        ## Handling if we get a geometry in the Field ###
+        if ogr_field in ogc_types:
+            # At this time, no more than one geographic field per model =(
+            if HAS_GEO:
+                raise Exception, 'More than one geographic field in mapping 
not allowed (yet).'
+            else:
+                HAS_GEO = ogr_field
+
+            # Making sure this geometry field type is a valid Django GIS field.
+            if not model_type in gis_fields:
+                raise Exception, 'Unknown Django GIS field type "%s"' % 
model_type
+            
+            # Getting the OGRGeometry, it's type (an integer) and it's name (a 
string)
+            geom  = feat.geom
+            gtype = geom.geom_type
+            gname = geom.geom_name
+
+            if make_multi(gname, model_type):
+                # Do we have to 'upsample' into a Geometry Collection?
+                pass
+            elif gtype == ogc_types[gis_fields[model_type]]:
+                # The geometry type otherwise was expected
+                pass
+            else:
+                raise Exception, 'Invalid mapping geometry; model has %s, 
feature has %s' % (model_type, gtype)
+
+        ## Handling other fields 
+        else:
+            # Making sure the model field is
+            if not model_type in field_types:
+                raise Exception, 'Django field type "%s" has no OGR mapping 
(yet).' % model_type
+
+            # Otherwise, we've got an OGR Field.  Making sure that an
+            # index exists for the mapping OGR field.
+            try:
+                fi = feat.index(ogr_field)
+            except:
+                raise Exception, 'Given mapping OGR field "%s" not in given 
OGR layer feature!' % ogr_field
+                    
+def check_layer(layer, fields, mapping):
+    "Checks the OGR layer by incrementing through and checking each feature."
+    # Incrementing through each feature in the layer.
+    for feat in layer:
+        check_feature(feat, fields, mapping)
+
+def check_srs(layer, source_srs):
+    "Checks the compatibility of the given spatial reference object."
+    if isinstance(source_srs, SpatialReference):
+        sr = source_srs
+    elif isinstance(source_srs, SpatialRefSys):
+        sr = source_srs.srs
+    else:
+        sr = layer.srs
+    if not sr:
+        raise Exception, 'No source reference system defined.'
+    else:
+        return sr
+
+class LayerMapping:
+    "A class that maps OGR Layers to Django Models."
+
+    def __init__(self, model, data, mapping, layer=0, source_srs=None):
+        "Takes the Django model, the data source, and the mapping (dictionary)"
+
+        # Getting the field names and types from the model
+        fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)
+        # Getting the DataSource and its Layer
+        if isinstance(data, basestring):
+            self.ds = DataSource(data)
+        else:
+            self.ds = data
+        self.layer = self.ds[layer]
+
+        # Checking the layer -- intitialization of the object will fail if
+        #  things don't check out before hand.
+        check_layer(self.layer, fields, mapping)
+        
+        # Since the layer checked out, setting the fields and the mapping.
+        self.fields = fields
+        self.mapping = mapping
+        self.model = model
+        self.source_srs = check_srs(self.layer, source_srs)
+
+    @transaction.commit_on_success
+    def save(self, verbose=False):
+        "Runs the layer mapping on the given SHP file, and saves to the 
database."
+
+        # Getting the GeometryColumn object.
+        try:
+            geo_col = 
GeometryColumns.objects.get(f_table_name=self.model._meta.db_table)
+        except:
+            raise Exception, 'Geometry column does not exist. (did you run 
syncdb?)'
+        
+        # Getting the coordinate system needed for transformation (with 
CoordTransform)  
+        try:
+            # Getting the target spatial reference system
+            target_srs = SpatialRefSys.objects.get(srid=geo_col.srid).srs
+
+            # Creating the CoordTransform object
+            ct = CoordTransform(self.source_srs, target_srs)
+        except Exception, msg:
+            raise Exception, 'Could not translate between the data source and 
model geometry: %s' % msg
+
+        for feat in self.layer:
+            # The keyword arguments for model construction
+            kwargs = {}
+
+            # Incrementing through each model field and the OGR field in the 
mapping
+            all_prepped = True
+
+            for model_field, ogr_field in self.mapping.items():
+                is_fk = False
+                try:
+                    model_type = self.fields[model_field]
+                except KeyError: #foreign key
+                    model_type = self.fields[model_field[:-3]]
+                    is_fk = True
+
+                if ogr_field in ogc_types:
+                    ## Getting the OGR geometry from the field
+                    geom = feat.geom
+
+                    if make_multi(geom.geom_name, model_type):
+                        # Constructing a multi-geometry type to contain the 
single geometry
+                        multi_type = multi_types[geom.geom_name]
+                        g = OGRGeometry(multi_type)
+                        g.add(geom)
+                    else:
+                        g = geom
+
+                    # Transforming the geometry with our Coordinate 
Transformation object.
+                    g.transform(ct)
+
+                    # Updating the keyword args with the WKT of the 
transformed model.
+                    val = g.wkt
+                else:
+                    ## Otherwise, this is an OGR field type
+                    fi = feat.index(ogr_field)
+                    val = feat[fi].value
+
+                if is_fk:
+                    rel_obj = None
+                    field_name = model_field[:-3]
+                    try:
+                        #FIXME: refactor to efficiently fetch FKs.
+                        #  Requires significant re-work. :-/
+                        rel = self.model._meta.get_field(field_name).rel
+                        rel_obj = rel.to._default_manager.get(**{('%s__exact' 
% rel.field_name):val})
+                    except ObjectDoesNotExist:
+                        all_prepped = False
+                    
+                    kwargs[model_field[:-3]] = rel_obj
+                else:
+                    kwargs[model_field] = val
+
+            # Constructing the model using the constructed keyword args
+            if all_prepped:
+                m = self.model(**kwargs)
+
+                # Saving the model
+                try:
+                    if all_prepped:
+                        m.save()
+                        if verbose: print 'Saved: %s' % str(m)                 
       
+                    else:
+                        print "Skipping %s due to missing relation." % kwargs
+                except SystemExit:
+                    raise
+                except Exception, e:
+                    print "Failed to save %s\n  Continuing" % kwargs
+                    


--~--~---------~--~----~------------~-------~--~----~
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
-~----------~----~----~----~------~----~------~--~---

Reply via email to