Browse files

move geocoding to javascript to avoid hitting google api limit

  • Loading branch information...
1 parent ba48445 commit 0dfdbf75060eb4e8b554917cf50306742cd9bffb @visiblegovernment committed Feb 17, 2011
Showing with 98 additions and 189 deletions.
  1. +19 −49 mainapp/models.py
  2. +0 −49 mainapp/search.py
  3. +35 −35 mainapp/tests/base_cases.py
  4. +19 −33 mainapp/views/main.py
  5. +3 −8 mainapp/views/reports/main.py
  6. +3 −1 templates/base.html
  7. +19 −14 templates/home.html
View
68 mainapp/models.py
@@ -15,8 +15,9 @@
from django.utils.translation import ugettext_lazy, ugettext as _
from contrib.transmeta import TransMeta
from contrib.stdimage import StdImageField
-import libxml2
from django.utils.encoding import iri_to_uri
+from django.contrib.gis.geos import fromstr
+from django.http import Http404
from django.contrib.auth.models import User
# from here: http://www.djangosnippets.org/snippets/630/
@@ -486,54 +487,6 @@ def __init__(self,city):
-class GoogleAddressLookup(object):
-
- """
- Simple Google Geocoder abstraction - supports UTF8
- """
-
- def __init__(self,address ):
- self.query_results = []
- self.match_coords = []
- self.xpathContext = None
- self.url = iri_to_uri(u'http://maps.google.ca/maps/geo?q=%s&output=xml&key=%s&oe=utf-8' % (address, settings.GMAP_KEY) )
-
- def resolve(self):
- try:
- resp = urllib.urlopen(self.url).read()
- doc = libxml2.parseDoc(resp)
- self.xpathContext = doc.xpathNewContext()
- self.xpathContext.xpathRegisterNs('google', 'http://earth.google.com/kml/2.0')
- self.query_results = self.xpathContext.xpathEval("//google:coordinates")
- return( True )
- except:
- return( False )
-
- def exists(self):
- return len(self.query_results) != 0
-
- def matches_multiple(self):
- return len(self.query_results) > 1
-
- def len(self):
- return len(self.query_results)
-
- def lat(self, index ):
- coord = self.query_results[index]
- coord_pair = coord.content.split(',')
- return( coord_pair[1] )
-
- def lon(self, index ):
- coord = self.query_results[index]
- coord_pair = coord.content.split(',')
- return( coord_pair[0] )
-
- def get_match_options(self):
- addr_list = []
- addr_nodes = self.xpathContext.xpathEval("//google:address")
- for i in range(0,len(addr_nodes)):
- addr_list.append(addr_nodes[i].content)
- return ( addr_list )
class SqlQuery(object):
"""
@@ -697,3 +650,20 @@ class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
city = models.ForeignKey(City, null=True)
+
+class DictToPoint():
+
+ def __init__(self, dict ):
+ if not dict.has_key('lat') or not dict.has_key('lon'):
+ raise Http404
+ self.lat = dict['lat']
+ self.lon = dict['lon']
+
+ def __unicode__(self):
+ return ("POINT(" + self.lon + " " + self.lat + ")" )
+
+ def pnt(self, srid = None ):
+ pntstr = self.__unicode__()
+ return( fromstr( pntstr, srid=4326) )
+
+
View
49 mainapp/search.py
@@ -1,49 +0,0 @@
-'''
-module containing search funcions and exceptions for fixmystreet.ca
-'''
-
-from mainapp.models import GoogleAddressLookup, Ward
-
-class SearchException(Exception):
- pass
-
-class SearchAddressException(SearchException):
- pass
-
-class SearchAddressDisambiguateError(SearchAddressException):
- pass
-
-class SearchAddressNotSupported(SearchException):
- pass
-
-def search_address(address, match_index=-1, disambiguate=[]):
- '''
- Tries to parse `address` returning a single point, if the address is
- ambigous `match_index` will be used to pick out the correct address. if
- `match_index` is -1 or not 0 <= `match_index` < len(possible_addresses) the
- disambiguate list is populated with possible addresses and a
- `SearchAddressDisambiguateError` is raised.
- '''
- address_lookup = GoogleAddressLookup( address )
-
- if not address_lookup.resolve():
- raise SearchAddressException("Sorry, we couldn\'t retreive the coordinates of that location, please use the Back button on your browser and try something more specific or include the city name at the end of your search.")
-
- if not address_lookup.exists():
- raise SearchAddressException("Sorry, we couldn\'t find the address you entered. Please try again with another intersection, address or postal code, or add the name of the city to the end of the search.")
-
- if address_lookup.matches_multiple() and match_index == -1:
- disambiguate += address_lookup.get_match_options()
- raise SearchAddressDisambiguateError('Cannot pinpoint given address')
-
- point_str = "POINT(" + address_lookup.lon(match_index) + " " + address_lookup.lat(match_index) + ")"
- return point_str
-
-def search_wards(point_str):
- '''
- returns a list of wards that contain `point_str`
- '''
- wards = Ward.objects.filter(geom__contains=point_str)
- if (len(wards) == 0):
- raise SearchAddressNotSupported("Sorry, we don't yet have that area in our database. Please have your area councillor contact fixmystreet.ca.")
- return wards
View
70 mainapp/tests/base_cases.py
@@ -172,45 +172,45 @@ class TestSearch(BaseCase):
fixtures = ['test_report_basecases.json']
base_url = '/search'
-
- def test_success(self):
- query = 'bank and slater, Ottawa'
- response = self.c.get(self._url(query), follow=True)
- self.assertEquals( response.status_code, 200 )
- self.assertEquals( response.template[0].name, 'search_result.html')
-
- def test_doesnt_resolve(self):
- query = 'nowhere anywhere'
- error = "Sorry, we couldn't find the address you entered."
- response = self._get_error_response(query)
- self.assertEquals( response.context['error_msg'].startswith(error),True)
+
+ # now done in javascript
+# def test_success(self):
+# query = 'bank and slater, Ottawa'
+# response = self.c.get(self._url(query), follow=True)
+# self.assertEquals( response.status_code, 200 )
+# self.assertEquals( response.template[0].name, 'search_result.html')
+
+# def test_doesnt_resolve(self):
+# query = 'nowhere anywhere'
+# error = "Sorry, we couldn't find the address you entered."
+# response = self._get_error_response(query)
+# self.assertContains( response,error)
- def test_ambigous(self):
- query = 'slater street'
- error = "That address returned more than one result."
- response = self._get_error_response(query)
- self.assertNotEquals( response.context['disambiguate'], None )
- self.assertNotEquals(response.content.find(error),-1)
+# def test_ambigous(self):
+# query = 'slater street'
+# error = "That address returned more than one result."
+# response = self._get_error_response(query)
+# self.assertContains(response, error )
# find the link for Ottawa
- ottawa_link = None
- for link,address in response.context['disambiguate'].items():
- if address.find('Ottawa') != -1:
- ottawa_link = link
-
- self.assertNotEquals(ottawa_link,None)
-
- # follow it to make sure it works
- response = self.c.get(ottawa_link, follow=True)
- self.assertEquals( response.status_code, 200 )
- self.assertEquals( response.template[0].name, 'search_result.html')
+# ottawa_link = None
+# for link,address in response.context['disambiguate'].items():
+# if address.find('Ottawa') != -1:
+# ottawa_link = link
+#
+# self.assertNotEquals(ottawa_link,None)
+#
+# # follow it to make sure it works
+# response = self.c.get(ottawa_link, follow=True)
+# self.assertEquals( response.status_code, 200 )
+# self.assertEquals( response.template[0].name, 'search_result.html')
-
- def test_not_in_db(self):
- query = 'moscow, russia'
- error = "Sorry, we don't yet have that area in our database."
- response = self._get_error_response(query)
- self.assertEquals( response.context['error_msg'].startswith(error),True)
+
+# def test_not_in_db(self):
+# query = 'moscow, russia'
+# error = "Sorry, we don't yet have that area in our database."
+# response = self._get_error_response(query)
+# self.assertEquals( response.context['error_msg'].startswith(error),True)
def _get_error_response(self,query):
View
52 mainapp/views/main.py
@@ -1,6 +1,6 @@
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect, Http404
-from mainapp.models import Report, ReportUpdate, Ward, FixMyStreetMap, ReportCountQuery, City, FaqEntry, GoogleAddressLookup
+from mainapp.models import DictToPoint, Report, ReportUpdate, Ward, FixMyStreetMap, ReportCountQuery, City, FaqEntry
from mainapp import search
from django.template import Context, RequestContext
from django.contrib.gis.measure import D
@@ -16,7 +16,8 @@
import urllib
-def home(request, error_msg = None, disambiguate=None):
+def home(request, location = None, error_msg =None):
+
if request.subdomain:
matching_cities = City.objects.filter(name__iexact=request.subdomain)
if matching_cities:
@@ -25,45 +26,30 @@ def home(request, error_msg = None, disambiguate=None):
return render_to_response("home.html",
{"report_counts": ReportCountQuery('1 year'),
"cities": City.objects.all(),
- 'error_msg': error_msg,
- 'disambiguate':disambiguate },
+ 'search_error': error_msg,
+ 'location':location,
+ 'GOOGLE_KEY': settings.GMAP_KEY },
context_instance=RequestContext(request))
+def _search_url(request,years_ago):
+ return('/search?lat=%s;lon=%s;years_ago=%s' % ( request.GET['lat'], request.GET['lon'], years_ago ))
+
def search_address(request):
if request.method == 'POST':
address = iri_to_uri(u'/search?q=%s' % request.POST["q"])
return HttpResponseRedirect( address )
-# address = urllib.urlencode({'x':urlquote(request.POST["q"])})[2:]
-# return HttpResponseRedirect("/search?q=" + address )
-
- address = request.GET["q"]
- address_lookup = GoogleAddressLookup( address )
- if not address_lookup.resolve():
- return home(request, _("Sorry, we couldn\'t retreive the coordinates of that location, please use the Back button on your browser and try something more specific or include the city name at the end of your search."))
-
- if not address_lookup.exists():
- return home( request, _("Sorry, we couldn\'t find the address you entered. Please try again with another intersection, address or postal code, or add the name of the city to the end of the search."))
+ if request.GET.has_key('q'):
+ address = request.GET["q"]
+ return home( request, address, None )
- if address_lookup.matches_multiple() and not request.GET.has_key("index"):
- addrs = address_lookup.get_match_options()
- disambiguate_list = {}
- for i in range(0,len(addrs)):
- link = "/search?q=" + urlquote(address) + "&index=" + str(i)
- disambiguate_list[ link ] = addrs[i]
- return home(request,disambiguate = disambiguate_list )
-
- # otherwise, we have a specific match
- match_index = 0
- if request.GET.has_key("index"):
- match_index = int(request.GET["index"])
- if match_index > address_lookup.len(): raise Http404
-
- point_str = "POINT(" + address_lookup.lon(match_index) + " " + address_lookup.lat(match_index) + ")"
- pnt = fromstr(point_str, srid=4326)
- wards = Ward.objects.filter(geom__contains=point_str)
+ # should have a lat and lon by this time.
+ dict2pt = DictToPoint( request.GET )
+ pnt = dict2pt.pnt()
+ wards = Ward.objects.filter(geom__contains=dict2pt.__unicode__())
if (len(wards) == 0):
- return( home(request, _("Sorry, we don't yet have that area in our database. Please have your area councillor contact fixmystreet.ca.")))
+ return( home(request, None, _("Sorry, we don't yet have that area in our database. Please have your area councillor contact fixmystreet.ca.")))
+
ward = wards[0]
# calculate date range for which to return reports
@@ -78,7 +64,7 @@ def search_address(request):
# do we want to show older reports?
if Report.objects.filter(ward=ward,created_at__lte=date_range_start).count() > 1:
- older_reports_link = "/search?q=%s;index=%i;years_ago=%i" %( urlquote(address),match_index, years_ago + 1)
+ older_reports_link = _search_url(request, years_ago - 1)
else:
older_reports_link = None
View
11 mainapp/views/reports/main.py
@@ -1,22 +1,17 @@
from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect,Http404
-from mainapp.models import Report, ReportUpdate, Ward, FixMyStreetMap, ReportCategory
+from mainapp.models import DictToPoint,Report, ReportUpdate, Ward, FixMyStreetMap, ReportCategory
from mainapp.forms import ReportForm,ReportUpdateForm
from django.template import Context, RequestContext
from django.contrib.gis.geos import *
from fixmystreet import settings
from django.utils.translation import ugettext as _
-def _get_point( dict ):
- if not dict.has_key('lat') or not dict.has_key('lon'):
- raise Http404
- pnt = fromstr("POINT(" + dict["lon"] + " " + dict["lat"] + ")", srid=4326)
- return pnt
def new( request ):
if request.method == "POST":
- pnt = _get_point( request.POST )
+ pnt = DictToPoint( request.POST ).pnt()
report_form = ReportForm( request.POST, request.FILES )
# this checks update is_valid too
if report_form.is_valid():
@@ -25,7 +20,7 @@ def new( request ):
if report:
return( HttpResponseRedirect( report.get_absolute_url() ))
else:
- pnt = _get_point( request.GET )
+ pnt = DictToPoint( request.GET ).pnt()
report_form = ReportForm(initial={ 'lat': request.GET['lat'],
'lon': request.GET['lon'] } )
View
4 templates/base.html
@@ -71,7 +71,9 @@
<div id='middle_container' class='container_12'>
<div class='grid_12'>
{% if error_msg %}
- <div id="error-msg"><p>{{error_msg}}</p></div>
+ <div id="error-msg">
+ <p>{{error_msg}}</p>
+ </div>
{% endif %}
{% block content %}{% endblock %}
</div>
View
33 templates/home.html
@@ -1,5 +1,8 @@
{% extends "base.html" %}
{% load i18n %}
+{% block script %}
+{% include "_search.js" %}
+{% endblock %}
{% block css %}
<style type="text/css" media="screen">
#supported_cities { font-size: 80%; }
@@ -11,18 +14,13 @@
{% endblock %}
</style>
{% block content %}
-
+<div id='search-error'>
+{% if search_error %}
+<div id='error-msg'>{{search_error}}</div>
+{% endif %}
+</div>
<div id="page_content_container">
- {% if disambiguate %}
- <div id='error-msg'>
- <p>{% trans "That address returned more than one result. Please try again, or select an option below:"%}</p>
- <ul>
- {% for link,address in disambiguate.items %}
- <li><a href='{{link}}'>{{address}}</a></li>
- {% endfor %}
- </ul>
- </div>
- {% endif %}
+ <div id='disambiguate'></div>
<div id="front-page">
<div id="intro_text">
<h1>{% trans "report/view/discuss" %}</h1>
@@ -35,11 +33,18 @@
</div>
<div id='start_box'>
- <form name="lookup" action="/search/" method="post">
+ <form onsubmit="return do_search()" >
<p>{% trans "Enter a nearby postal code, or street name and city:" %}</p>
- <input id='search_box' type="text" name="q">
- <input id='search_button' class='big_button' type="submit" name="submit" value="{% trans "Search" %}">
+ <input id='search_box' type="text" name="q"
+ {% if location %}value="{{location}}"{% endif %}
+ >
+ <input id='search_button' class='big_button' type="button" name="submit" onclick="do_search()" value="{% trans "Search" %}">
</form>
+ {% if location %}
+ <div id='searching-progress'>
+ <h2 class="hide-on-load bluegreen"><img src="/images/ajax-loader-bluegreen.gif" width="16" height="16" alt="progress spinner" />&nbsp;searching...</h2>
+ </div>
+ {% endif %}
</div>
<div id="supported_cities">
{% trans "Supported Cities" %}:

0 comments on commit 0dfdbf7

Please sign in to comment.