-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Andrew Jennings
committed
Mar 13, 2012
0 parents
commit 2dd7065
Showing
15 changed files
with
1,533 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
*.pyc | ||
*.swp | ||
*.py~ | ||
build | ||
dist | ||
python_omgeo.egg-info | ||
docs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
v1.0, 2012-03-13 -- Initial Release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
The MIT License (MIT) | ||
Copyright (c) 2012 Azavea, Inc. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
this software and associated documentation files (the "Software"), to deal in | ||
the Software without restriction, including without limitation the rights to | ||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
of the Software, and to permit persons to whom the Software is furnished to do | ||
so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
include README.rst | ||
include LICENSE.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
**The Oatmeal Geocoder - Python Edition** | ||
|
||
``python-omgeo`` is a geocoding abstraction layer written in python. Currently | ||
supported geocoders: | ||
|
||
* Bing | ||
* ESRI's North American locator | ||
* ESRI's European address locator | ||
* Nominatim | ||
|
||
|
||
See the source for more info. Here's a quick example. | ||
|
||
>>> from omgeo import Geocoder | ||
>>> from omgeo.places import PlaceQuery | ||
>>> g = Geocoder() | ||
>>> you_are_here = PlaceQuery('340 N 12th St Philadelphia PA') | ||
>>> candidates = g.geocode(you_are_here) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import copy | ||
from omgeo.processors.postprocessors import DupePicker | ||
|
||
class Geocoder(): | ||
""" | ||
The base geocode class. This class can be initialized with settings | ||
for each geocoder and/or settings for the geocoder itself. | ||
Arguments: | ||
========== | ||
sources -- a dictionary of GeocodeServiceConfig() parameters, | ||
keyed by module name for the GeocodeService to use | ||
ex: {'esri_na':{}, | ||
'bing': { | ||
'settings': {}, | ||
'preprocessors': [], | ||
'postprocessors': []}, ...} | ||
preprocessors -- list of universal preprocessors to use | ||
postprocessors -- list of universal postprocessors to use | ||
""" | ||
_sources = [] | ||
""" | ||
A list of classes representing the geocode services that will be used | ||
to find addresses for the given locations | ||
""" | ||
_preprocessors = [] | ||
""" | ||
Preprocessor instances to apply to each address requested | ||
""" | ||
_postprocessors = [] | ||
""" | ||
Postprocessor instances to apply to each result obtained from the geocoders | ||
""" | ||
_settings = {} | ||
""" | ||
Reserved for future use. | ||
""" | ||
|
||
DEFAULT_SOURCES = [['omgeo.services.EsriNA', {}], | ||
['omgeo.services.EsriEU', {}], | ||
['omgeo.services.Nominatim', {}]] | ||
DEFAULT_PREPROCESSORS = [] | ||
DEFAULT_POSTPROCESSORS = [ | ||
DupePicker('match_addr', 'locator', ['rooftop', 'parcel', 'interpolation_offset', 'interpolation']), | ||
] | ||
|
||
|
||
def _get_service_by_name(self, service_name): | ||
module, separator, class_name = service_name.rpartition('.') | ||
m = __import__( module ) | ||
path = service_name.split('.')[1:] | ||
for p in path: | ||
m = getattr(m, p) | ||
return m | ||
|
||
def add_source(self, source): | ||
geocode_service = self._get_service_by_name(source[0]) | ||
self._sources.append(geocode_service(**source[1])) | ||
|
||
def remove_source(self, source): | ||
geocode_service = self._get_service_by_name(source[0]) | ||
self._sources.remove(geocode_service(**source[1])) | ||
|
||
def set_sources(self, sources): | ||
""" | ||
Creates GeocodeServiceConfigs from each str source | ||
Argument: | ||
========= | ||
sources -- list of source-settings pairs | ||
ex. "[['EsriNA', {}], ['Nominatim', {}]]" | ||
""" | ||
if len(sources) == 0: | ||
raise Exception('Must declare at least one source for a geocoder') | ||
self._sources = [] | ||
for source in sources: # iterate through a list of sources | ||
self.add_source(source) | ||
|
||
|
||
def __init__(self, | ||
sources=DEFAULT_SOURCES, | ||
preprocessors=DEFAULT_PREPROCESSORS, | ||
postprocessors=DEFAULT_POSTPROCESSORS): | ||
|
||
self.set_sources(sources) | ||
self._preprocessors = preprocessors | ||
self._postprocessors = postprocessors | ||
|
||
def geocode(self, pq, waterfall=_settings.get('waterfall', False)): | ||
""" | ||
Returns a list of Candidate objects | ||
Arguments: | ||
========== | ||
pq -- A PlaceQuery object (required). | ||
waterfall -- Boolean set to True if all geocoders listed should | ||
be used to find results, instead of stopping after | ||
the first geocoding service with valid candidates | ||
(default False). | ||
""" | ||
processed_pq = copy.copy(pq) | ||
for p in self._preprocessors: # apply universal address preprocessing | ||
processed_pq = p.process(processed_pq) | ||
if processed_pq == False: return [] | ||
|
||
processed_candidates = [] | ||
for gs in self._sources: # iterate through each GeocodeService | ||
candidates = gs.geocode(processed_pq) | ||
processed_candidates += candidates # merge lists | ||
if waterfall is False and len(processed_candidates) > 0: | ||
break # if we have >= 1 good candidate, don't go to next geocoder | ||
|
||
for p in self._postprocessors: # apply universal candidate postprocessing | ||
processed_candidates = p.process(processed_candidates) | ||
|
||
return processed_candidates |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
class Viewbox(): | ||
""" | ||
Class representing a bounding box. | ||
Defaults to maximum bounds for WKID 4326. | ||
Arguments: | ||
========== | ||
left -- Minimum X value (default -180) | ||
top -- Maximum Y value (default 90) | ||
right -- Maximum X value (default 180) | ||
bottom -- Minimum Y value (default -90) | ||
wkid -- Well-known ID for spatial reference system (default 4326) | ||
""" | ||
def _validate(self): | ||
""" | ||
Return True if WKID is found and Viewbox is within maximum bounds. | ||
Return True if WKID is not found. | ||
Otherwise raise error. | ||
""" | ||
return True #TODO: Find max bounds from WKID in PostGIS database | ||
|
||
def convert_srs(self, new_wkid): | ||
""" | ||
Return a new Viewbox object with the specified SRS. | ||
""" | ||
return self # for now | ||
|
||
def __init__(self, left=-180, top=90, right=180, bottom=-90, wkid=4326): | ||
for k in locals().keys(): | ||
if k != 'self': setattr(self, k, locals()[k]) | ||
self._validate() | ||
|
||
def to_bing_str(self): | ||
""" | ||
Convert Viewbox object to a string that can be used by Bing | ||
as a query parameter. | ||
""" | ||
vb = self.convert_srs(4326) | ||
return '%s,%s,%s,%s' % (vb.bottom, vb.left, vb.top, vb.right) | ||
|
||
def to_mapquest_str(self): | ||
""" | ||
Convert Viewbox object to a string that can be used by | ||
MapQuest as a query parameter. | ||
""" | ||
vb = self.convert_srs(4326) | ||
return '%s,%s,%s,%s' % (vb.left, vb.top, vb.right, vb.bottom) | ||
|
||
class PlaceQuery(): | ||
""" | ||
Class representing an address or place passed to geocoders. | ||
Arguments: | ||
========== | ||
query -- A string containing the query to parse | ||
and match to a coordinate on the map. | ||
*ex: "340 N 12th St Philadelphia PA 19107" | ||
or "Wolf Building, Philadelphia"* | ||
address -- A string for the street line of an address. | ||
*ex: "340 N 12th St"* | ||
city -- A string specifying the populated place for the address. | ||
This commonly refers to a city, but may refer to a suburb | ||
or neighborhood in certain countries. | ||
state -- A string for the state, province, territory, etc. | ||
postal -- A string for the postal / ZIP Code | ||
country -- A string for the country or region. Because the geocoder | ||
uses the country to determine which geocoding service to use, | ||
this is strongly recommended for efficency. ISO alpha-2 is | ||
preferred, and is required by some geocoder services. | ||
viewbox -- A Viewbox object indicating the preferred area | ||
to find search results (default None) | ||
bounded -- Boolean indicating whether or not to only | ||
return candidates within the given Viewbox (default False) | ||
Keyword Arguments: | ||
================== | ||
user_lat -- A float representing the Latitude of the end-user. | ||
user_lon -- A float representing the Longitude of the end-user. | ||
user_ip -- A string representing the IP address of the end-user. | ||
culture -- Culture code to be used for the request (used by Bing). | ||
For example, if set to 'de', the country for a U.S. address | ||
would be returned as "Vereinigte Staaten Von Amerika" | ||
instead of "United States". | ||
""" | ||
def __init__(self, query='', address='', city='', state='', postal='', country='', | ||
viewbox=None, bounded=False, **kwargs): | ||
for k in locals().keys(): | ||
if k not in ['self', 'kwargs']: setattr(self, k, locals()[k]) | ||
if query == '' and address == '' and city == '' and state == '' and postal == '': | ||
raise Exception('Must provide query or one or more of address, city, state, and postal.') | ||
for k in kwargs: | ||
setattr(self, k, kwargs[k]) | ||
|
||
class Candidate(): | ||
""" | ||
Class representing a candidate address returned from geocoders. | ||
Accepts arguments defined below, plus informal keyword arguments. | ||
Arguments: | ||
========== | ||
locator -- Locator used for geocoding (default '') | ||
score -- Standardized score (default 0) | ||
match_addr -- Address returned by geocoder (default '') | ||
x -- X-coordinate (longitude for lat-lon SRS) (default None) | ||
y -- Y-coordinate (latitude for lat-lon SRS) (default None) | ||
wkid -- Well-known ID for spatial reference system (default 4326) | ||
entity -- Used by Bing (default '') | ||
confidence -- Used by Bing (default '') | ||
geoservice -- GeocodeService used for geocoding (default '') | ||
Usage Example: | ||
============== | ||
c = Candidate('US_RoofTop', 91.5, '340 N 12th St, Philadelphia, PA, 19107', | ||
'-75.16', '39.95', some_extra_data='yellow') | ||
""" | ||
def __init__(self, locator='', score=0, match_addr='', x=None, y=None, | ||
wkid=4326, entity='', confidence='', **kwargs): | ||
for k in locals().keys(): | ||
if k not in ['self', 'kwargs']: setattr(self, k, locals()[k]) | ||
for k in kwargs: | ||
setattr(self, k, kwargs[k]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
class _Processor(): | ||
def _init_helper(self, vars_): | ||
"""Overwrite defaults (if they exist) with arguments passed to constructor""" | ||
for k in vars_: | ||
if k == 'kwargs': | ||
for kwarg in vars_[k]: | ||
setattr(self, kwarg, vars_[k][kwarg]) | ||
elif k != 'self': | ||
setattr(self, k, vars_[k]) | ||
|
||
def __init__(self, **kwargs): | ||
""" | ||
Constructor for Processor. | ||
In a subclass, arguments may be formally defined to avoid the use of keywords | ||
(and to throw errors when bogus keyword arguments are passed): | ||
def __init__(self, arg1='foo', arg2='bar') | ||
""" | ||
self._init_helper(vars()) | ||
|
||
class PreProcessor(_Processor): | ||
"""Takes, processes, and returns a geocoding.places.PlaceQuery object.""" | ||
def process(self, pq): | ||
raise NotImplementedError( | ||
'PreProcessor subclasses must implement process().') | ||
|
||
class PostProcessor(_Processor): | ||
"""Takes, processes, and returns list of geocoding.places.Candidate objects.""" | ||
def process(self, candidates): | ||
raise NotImplementedError( | ||
'PostProcessor subclasses must implement process().') |
Oops, something went wrong.