Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial public commit for django-geocoding

  • Loading branch information...
commit 474343cab0bc955a324287361a35d35ca23da8e0 0 parents
Alberto García Hierro fiam authored

Showing 3 changed files with 249 additions and 0 deletions. Show diff stats Hide diff stats

  1. +19 0 COPYING
  2. 0  __init__.py
  3. +230 0 models.py
19 COPYING
... ... @@ -0,0 +1,19 @@
  1 +Copyright (c) 2008 Alberto García Hierro <fiam@rm-fr.net>
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +of this software and associated documentation files (the "Software"), to deal
  5 +in the Software without restriction, including without limitation the rights
  6 +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +copies of the Software, and to permit persons to whom the Software is
  8 +furnished to do so, subject to the following conditions:
  9 +
  10 +The above copyright notice and this permission notice shall be included in
  11 +all copies or substantial portions of the Software.
  12 +
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19 +THE SOFTWARE.
0  __init__.py
No changes.
230 models.py
... ... @@ -0,0 +1,230 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +# Copyright (c) 2008 Alberto García Hierro <fiam@rm-fr.net>
  4 +
  5 +# Permission is hereby granted, free of charge, to any person obtaining a copy
  6 +# of this software and associated documentation files (the "Software"), to deal
  7 +# in the Software without restriction, including without limitation the rights
  8 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9 +# copies of the Software, and to permit persons to whom the Software is
  10 +# furnished to do so, subject to the following conditions:
  11 +
  12 +# The above copyright notice and this permission notice shall be included in
  13 +# all copies or substantial portions of the Software.
  14 +
  15 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21 +# THE SOFTWARE.
  22 +
  23 +from django.db import models
  24 +from django.conf import settings
  25 +from django.utils import simplejson
  26 +from django.utils.translation import ugettext as _
  27 +
  28 +from geonames.models import Geoname, Country
  29 +
  30 +from datetime import datetime
  31 +from xml.etree.cElementTree import iterparse
  32 +from urllib2 import urlopen, quote
  33 +from decimal import Decimal, InvalidOperation
  34 +
  35 +GOOGLE_GEOCODE_URI = 'http://maps.google.com/maps/geo?key=%(key)s&' \
  36 + 'oe=utf8&output=xml&q=%(q)s'
  37 +GOOGLE_REVERSE_GEOCODE_URI = 'http://maps.google.com/maps/nav?key=%(key)s&' \
  38 + 'oe=utf8&q=from%%3A%%20%(latitude)s%%2C%(longitude)s%%20to%%3A%%20' \
  39 + '%(latitude)s%%2C%(longitude)s'
  40 +
  41 +class BigIntegerField(models.IntegerField):
  42 + empty_strings_allowed = False
  43 +
  44 + def get_internal_type(self):
  45 + return 'BigIntegerField'
  46 +
  47 + def db_type(self):
  48 + if settings.DATABASE_ENGINE == 'oracle':
  49 + return 'NUMBER(19)'
  50 +
  51 + return 'BIGINT'
  52 +
  53 +class GeocodedPoint(models.Model):
  54 + hash = BigIntegerField(primary_key=True)
  55 + status = models.IntegerField(null=True)
  56 + accuracy = models.IntegerField(null=True)
  57 + latitude = models.DecimalField(max_digits=20, decimal_places=17, null=True)
  58 + longitude = models.DecimalField(max_digits=20, decimal_places=17, null=True)
  59 + altitude = models.DecimalField(max_digits=10, decimal_places=5, null=True)
  60 + address = models.CharField(max_length=300, null=True)
  61 + thoroughfare_name = models.CharField(max_length=200, null=True)
  62 + locality_name = models.CharField(max_length=200, null=True)
  63 + dependent_locality_name = models.CharField(max_length=200, null=True)
  64 + country = models.ForeignKey(Country, null=True)
  65 + near = models.ForeignKey(Geoname, null=True, related_name='near_points')
  66 + location = models.ForeignKey(Geoname, null=True,
  67 + related_name='located_points')
  68 + created = models.DateTimeField(default=datetime.now)
  69 +
  70 + def __unicode__(self):
  71 + return (self.thoroughfare_name or 'near ' + unicode(self.near)) \
  72 + + ' (%s, %s)' % (self.latitude, self.longitude)
  73 +
  74 + def success(self):
  75 + return int(self.status) == 200
  76 +
  77 + def match(self):
  78 + if self.latitude and self.longitude:
  79 + for max_distance in (1, 3, 5, 10, 50, 100):
  80 + nears = Geoname.near_point(self.latitude, self.longitude,
  81 + kms=max_distance)
  82 + if not nears:
  83 + continue
  84 + self.near = nears[0][0]
  85 + self.country_id = nears[0][0].country_id
  86 + if self.dependent_locality_name:
  87 + for near in nears:
  88 + if near[0].name == self.dependent_locality_name:
  89 + self.location = near[0]
  90 + self.save()
  91 + return self
  92 + if self.locality_name:
  93 + for near in nears:
  94 + if near[0].name == self.locality_name:
  95 + self.location = near[0]
  96 + self.save()
  97 + return self
  98 + for near in nears:
  99 + if near[0].population:
  100 + self.location = near[0]
  101 + self.save()
  102 + return self
  103 + self.save()
  104 + return self
  105 +
  106 + @property
  107 + def near_name(self):
  108 + try:
  109 + return self.near.i18n_name
  110 + except AttributeError:
  111 + return u''
  112 +
  113 + @property
  114 + def location_name(self):
  115 + try:
  116 + return self.location.i18n_name
  117 + except AttributeError:
  118 + return u''
  119 +
  120 + @property
  121 + def parent_name(self):
  122 + try:
  123 + return self.near.parent.i18n_name
  124 + except AttributeError:
  125 + return u''
  126 +
  127 + @property
  128 + def country_name(self):
  129 + try:
  130 + return self.country.geoname.i18n_name
  131 + except AttributeError:
  132 + return u''
  133 +
  134 + @property
  135 + def request_id(self):
  136 + return str(self.hash)
  137 +
  138 + @property
  139 + def display_name(self):
  140 + if self.near:
  141 + return _('%(name)s near %(near_name)s in %(location_name)s,' \
  142 + ' %(country_name)s') % \
  143 + {
  144 + 'name': self.thoroughfare_name or _('Somewhere'),
  145 + 'near_name': self.near_name,
  146 + 'location_name': self.location_name or self.parent_name,
  147 + 'country_name': self.country_name,
  148 + }
  149 +
  150 + return _('Somewhere')
  151 +
  152 + @property
  153 + def tz_dst(self):
  154 + try:
  155 + return self.near.timezone.dst_offset
  156 + except AttributeError:
  157 + return None
  158 +
  159 +def direct_geocode(address):
  160 + h = hash(address)
  161 + point, created = GeocodedPoint.objects.get_or_create(hash=h)
  162 + if not created:
  163 + return point
  164 +
  165 + fp = urlopen(GOOGLE_GEOCODE_URI % { 'key': settings.GOOGLE_JS_API_KEY,
  166 + 'q': quote(address.encode('utf8')) })
  167 + for event, element in iterparse(fp):
  168 + if element.tag == '{http://earth.google.com/kml/2.0}code':
  169 + point.status = int(element.text)
  170 + if point.status != 200:
  171 + break
  172 + elif element.tag == '{http://earth.google.com/kml/2.0}address':
  173 + point.address = element.text
  174 + elif element.tag == '{urn:oasis:names:tc:ciq:xsdschema:' \
  175 + 'xAL:2.0}CountryNameCode':
  176 + point.country_id = element.text
  177 + elif element.tag == '{urn:oasis:names:tc:ciq:xsdschema:' \
  178 + 'xAL:2.0}LocalityName':
  179 + point.locality_name = element.text
  180 + elif element.tag == '{urn:oasis:names:tc:ciq:xsdschema:' \
  181 + 'xAL:2.0}DependentLocalityName':
  182 + point.dependent_locality_name = element.text
  183 + elif element.tag == '{urn:oasis:names:tc:ciq:xsdschema:' \
  184 + 'xAL:2.0}ThoroughfareName':
  185 + point.thoroughfare_name = element.text
  186 + elif element.tag == '{urn:oasis:names:tc:ciq:xsdschema:' \
  187 + 'xAL:2.0}AddressDetails':
  188 + point.accuracy = int(element.attrib['Accuracy'])
  189 + elif element.tag == '{http://earth.google.com/kml/2.0}coordinates':
  190 + point.longitude, point.latitude, point.altitude = \
  191 + [Decimal(x) for x in element.text.split(',')]
  192 +
  193 + return point.match()
  194 +
  195 +def reverse_geocode(latitude, longitude):
  196 + if not (-85 < latitude < 85) or not (-180 < longitude < 180):
  197 + return GeocodedPoint(status=400)
  198 + h = hash('%s,%s' % (latitude, longitude))
  199 + point, created = GeocodedPoint.objects.get_or_create(hash=h)
  200 + if not created:
  201 + return point
  202 +
  203 + fp = urlopen(GOOGLE_REVERSE_GEOCODE_URI % { 'key': settings.GOOGLE_JS_API_KEY, 'latitude': latitude, 'longitude': longitude })
  204 + data = simplejson.load(fp)
  205 + fp.close()
  206 + point.status = data['Status']['code']
  207 + if point.status != 200:
  208 + if point.status == 604:
  209 + point.status = 200
  210 + point.latitude, point.longitude = [Decimal(str(x)) for x in (latitude, longitude)]
  211 + return point.match()
  212 +
  213 + point.address = data['Placemark'][0]['address']
  214 + point.accuracy = data['Placemark'][0]['AddressDetails']['Accuracy']
  215 + point.thoroughfare_name = data['Placemark'][0]['AddressDetails']['Thoroughfare']['ThoroughfareName']
  216 + if point.thoroughfare_name == 'Unknown road':
  217 + point.thoroughfare_name = None
  218 + point.latitude, point.longitude = [Decimal(str(x)) for x in (latitude, longitude)]
  219 + point.altitude = Decimal(str(data['Placemark'][0]['Point']['coordinates'][2]))
  220 + #point.longitude, point.latitude, point.altitude = [Decimal(str(x)) for x in data['Placemark'][0]['Point']['coordinates']]
  221 +
  222 + return point.match()
  223 +
  224 +def geocode(query):
  225 + try:
  226 + latitude, longitude = [Decimal(x) for x in query.split(',')]
  227 + return reverse_geocode(latitude, longitude)
  228 + except (ValueError, TypeError, InvalidOperation):
  229 + return direct_geocode(query)
  230 +

0 comments on commit 474343c

Please sign in to comment.
Something went wrong with that request. Please try again.