缓存地理编码数据的最直接方法
Most straightforward way to cache geocoding data
我正在使用 geopy
获取地址列表的 lat/long 坐标。所有文档都指向通过缓存限制服务器查询(实际上这里有很多问题),但很少有人真正给出实用的解决方案。
完成此任务的最佳方法是什么?
这是我正在处理的独立数据处理工作...不涉及应用程序平台。只是试图减少服务器查询,因为我 运行 通过我以前会看到的数据(在我的情况下很可能)。
我的代码如下所示:
from geopy import geocoders
def geocode( address ):
# address ~= "175 5th Avenue NYC"
g = geocoders.GoogleV3()
cache = addressCached( address )
if ( cache != False ):
# We have seen this exact address before,
# return the saved location
return cache
# Otherwise, get a new location from geocoder
location = g.geocode( address )
saveToCache( address, location )
return location
def addressCached( address ):
# What does this look like?
def saveToCache( address, location ):
# What does this look like?
您想要实现缓存的具体方式实际上取决于您的 Python 代码将 运行 运行在哪个平台上。
你想要一个非常持久的 "cache" 因为地址的位置不会经常改变:-),所以数据库(在键值模式下)似乎是最好的。
所以在很多情况下我会选择 sqlite3
,这是一个优秀的、非常轻量级的 SQL 引擎,它是 Python 标准库的一部分。除非我可能更喜欢 MySQL 实例,无论如何我都需要 运行ning,一个优点可能是这将允许不同节点上的多个应用程序 运行ning 共享 "cache" -- 其他数据库,包括 SQL 和非数据库,都适合后者,具体取决于您的限制和偏好。
但是如果我在 运行 上使用 Google App Engine,那么我会使用它包含的数据存储。除非我有特定的理由想要在多个不同的应用程序之间共享 "cache",在这种情况下我可能会考虑替代方案,例如 google 云 sql 和 google 存储,以及作为另一种替代方案,它包含一个专用的 "cache server" GAE 应用程序,我自己提供 RESTful 结果(也许 w/endpoints?)。选择再次!非常非常取决于您的约束和偏好(延迟、每秒查询大小等)。
因此,请说明您使用的是什么平台,以及您对数据库有哪些其他限制和偏好 "cache",然后是可以轻松显示的非常简单的实现代码。但是在你澄清之前展示六种不同的可能性不会很有成效。
补充:由于评论建议 sqlite3
可能是可以接受的,并且代码中最好显示了一些重要的细节(例如,如何序列化和反序列化 geopy.location.Location
into/from a sqlite3
blob -- 其他底层数据库很可能会出现类似的问题,解决方案也类似),我决定最好在代码中显示一个解决方案示例。因此,由于 "geo cache" 显然最好作为自己的模块实现,我写了以下简单的 geocache.py
...:[=20=]
import geopy
import pickle
import sqlite3
class Cache(object):
def __init__(self, fn='cache.db'):
self.conn = conn = sqlite3.connect(fn)
cur = conn.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS '
'Geo ( '
'address STRING PRIMARY KEY, '
'location BLOB '
')')
conn.commit()
def address_cached(self, address):
cur = self.conn.cursor()
cur.execute('SELECT location FROM Geo WHERE address=?', (address,))
res = cur.fetchone()
if res is None: return False
return pickle.loads(res[0])
def save_to_cache(self, address, location):
cur = self.conn.cursor()
cur.execute('INSERT INTO Geo(address, location) VALUES(?, ?)',
(address, sqlite3.Binary(pickle.dumps(location, -1))))
self.conn.commit()
if __name__ == '__main__':
# run a small test in this case
import pprint
cache = Cache('test.db')
address = '1 Murphy St, Sunnyvale, CA'
location = cache.address_cached(address)
if location:
print('was cached: {}\n{}'.format(location, pprint.pformat(location.raw)))
else:
print('was not cached, looking up and caching now')
g = geopy.geocoders.GoogleV3()
location = g.geocode(address)
print('found as: {}\n{}'.format(location, pprint.pformat(location.raw)))
cache.save_to_cache(address, location)
print('... and now cached.')
我希望这里说明的想法足够清楚——每个设计选择都有备选方案,但我尽量使事情简单化(特别是,我使用了一个简单的示例和迷你测试当此模块直接 运行 时,代替一套适当的单元测试...)。
关于序列化 to/from blob 的部分,我选择了 pickle
和 "highest protocol" (-1
) 协议——当然是 cPickle
在 Python 2 中会和 Python 2 一样好(而且更快:-) 但是这些天我尝试编写与 Python 2 或 3 一样好的代码,除非我有特殊原因不这样做:-) .当然,我为测试中使用的 sqlite 数据库使用了不同的文件名 test.db
,因此您可以毫不犹豫地将其删除以测试一些变化,而默认文件名意味着在 "production" 代码中使用的代码保持完整( 是 使用相对文件名的相当可疑的设计选择 - 意思是 "in the current directory" - 但适当的方式来决定放置这样一个文件的位置完全取决于平台,我不想在这里进入这样的奥秘:-)。
如果还有其他问题,请提出(最好在一个单独的新问题上提问,因为这个答案已经变得如此之大!-)。
创建一个存储所有地理编码地址的 list
或 dict
怎么样?那么你可以简单地检查一下。
if address in cached:
//skip
此缓存将从加载模块的那一刻起生效,并且在您使用完此模块后不会保存。您可能希望使用 pickle 将其保存到文件或数据库中,并在下次加载模块时加载它。
from geopy import geocoders
cache = {}
def geocode( address ):
# address ~= "175 5th Avenue NYC"
g = geocoders.GoogleV3()
cache = addressCached( address )
if ( cache != False ):
# We have seen this exact address before,
# return the saved location
return cache
# Otherwise, get a new location from geocoder
location = g.geocode( address )
saveToCache( address, location )
return location
def addressCached( address ):
global cache
if address in cache:
return cache[address]
return None
def saveToCache( address, location ):
global cache
cache[address] = location
这是一个简单的实现,它使用 python shelve
包进行透明和持久缓存:
import geopy
import shelve
import time
class CachedGeocoder:
def __init__(self, source = "Nominatim", geocache = "geocache.db"):
self.geocoder = getattr(geopy.geocoders, source)()
self.db = shelve.open(geocache, writeback = True)
self.ts = time.time()+1.1
def geocode(self, address):
if not address in self.db:
time.sleep(max(1 -(time.time() - self.ts), 0))
self.ts = time.time()
self.db[address] = self.geocoder.geocode(address)
return self.db[address]
geocoder = CachedGeocoder()
print geocoder.geocode("San Francisco, USA")
它存储一个时间戳以确保发出请求的频率不会超过每秒一次(这是 Nominatim 的要求)。一个弱点是不处理来自 Nominatim 的超时响应。
我正在使用 geopy
获取地址列表的 lat/long 坐标。所有文档都指向通过缓存限制服务器查询(实际上这里有很多问题),但很少有人真正给出实用的解决方案。
完成此任务的最佳方法是什么?
这是我正在处理的独立数据处理工作...不涉及应用程序平台。只是试图减少服务器查询,因为我 运行 通过我以前会看到的数据(在我的情况下很可能)。
我的代码如下所示:
from geopy import geocoders
def geocode( address ):
# address ~= "175 5th Avenue NYC"
g = geocoders.GoogleV3()
cache = addressCached( address )
if ( cache != False ):
# We have seen this exact address before,
# return the saved location
return cache
# Otherwise, get a new location from geocoder
location = g.geocode( address )
saveToCache( address, location )
return location
def addressCached( address ):
# What does this look like?
def saveToCache( address, location ):
# What does this look like?
您想要实现缓存的具体方式实际上取决于您的 Python 代码将 运行 运行在哪个平台上。
你想要一个非常持久的 "cache" 因为地址的位置不会经常改变:-),所以数据库(在键值模式下)似乎是最好的。
所以在很多情况下我会选择 sqlite3
,这是一个优秀的、非常轻量级的 SQL 引擎,它是 Python 标准库的一部分。除非我可能更喜欢 MySQL 实例,无论如何我都需要 运行ning,一个优点可能是这将允许不同节点上的多个应用程序 运行ning 共享 "cache" -- 其他数据库,包括 SQL 和非数据库,都适合后者,具体取决于您的限制和偏好。
但是如果我在 运行 上使用 Google App Engine,那么我会使用它包含的数据存储。除非我有特定的理由想要在多个不同的应用程序之间共享 "cache",在这种情况下我可能会考虑替代方案,例如 google 云 sql 和 google 存储,以及作为另一种替代方案,它包含一个专用的 "cache server" GAE 应用程序,我自己提供 RESTful 结果(也许 w/endpoints?)。选择再次!非常非常取决于您的约束和偏好(延迟、每秒查询大小等)。
因此,请说明您使用的是什么平台,以及您对数据库有哪些其他限制和偏好 "cache",然后是可以轻松显示的非常简单的实现代码。但是在你澄清之前展示六种不同的可能性不会很有成效。
补充:由于评论建议 sqlite3
可能是可以接受的,并且代码中最好显示了一些重要的细节(例如,如何序列化和反序列化 geopy.location.Location
into/from a sqlite3
blob -- 其他底层数据库很可能会出现类似的问题,解决方案也类似),我决定最好在代码中显示一个解决方案示例。因此,由于 "geo cache" 显然最好作为自己的模块实现,我写了以下简单的 geocache.py
...:[=20=]
import geopy
import pickle
import sqlite3
class Cache(object):
def __init__(self, fn='cache.db'):
self.conn = conn = sqlite3.connect(fn)
cur = conn.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS '
'Geo ( '
'address STRING PRIMARY KEY, '
'location BLOB '
')')
conn.commit()
def address_cached(self, address):
cur = self.conn.cursor()
cur.execute('SELECT location FROM Geo WHERE address=?', (address,))
res = cur.fetchone()
if res is None: return False
return pickle.loads(res[0])
def save_to_cache(self, address, location):
cur = self.conn.cursor()
cur.execute('INSERT INTO Geo(address, location) VALUES(?, ?)',
(address, sqlite3.Binary(pickle.dumps(location, -1))))
self.conn.commit()
if __name__ == '__main__':
# run a small test in this case
import pprint
cache = Cache('test.db')
address = '1 Murphy St, Sunnyvale, CA'
location = cache.address_cached(address)
if location:
print('was cached: {}\n{}'.format(location, pprint.pformat(location.raw)))
else:
print('was not cached, looking up and caching now')
g = geopy.geocoders.GoogleV3()
location = g.geocode(address)
print('found as: {}\n{}'.format(location, pprint.pformat(location.raw)))
cache.save_to_cache(address, location)
print('... and now cached.')
我希望这里说明的想法足够清楚——每个设计选择都有备选方案,但我尽量使事情简单化(特别是,我使用了一个简单的示例和迷你测试当此模块直接 运行 时,代替一套适当的单元测试...)。
关于序列化 to/from blob 的部分,我选择了 pickle
和 "highest protocol" (-1
) 协议——当然是 cPickle
在 Python 2 中会和 Python 2 一样好(而且更快:-) 但是这些天我尝试编写与 Python 2 或 3 一样好的代码,除非我有特殊原因不这样做:-) .当然,我为测试中使用的 sqlite 数据库使用了不同的文件名 test.db
,因此您可以毫不犹豫地将其删除以测试一些变化,而默认文件名意味着在 "production" 代码中使用的代码保持完整( 是 使用相对文件名的相当可疑的设计选择 - 意思是 "in the current directory" - 但适当的方式来决定放置这样一个文件的位置完全取决于平台,我不想在这里进入这样的奥秘:-)。
如果还有其他问题,请提出(最好在一个单独的新问题上提问,因为这个答案已经变得如此之大!-)。
创建一个存储所有地理编码地址的 list
或 dict
怎么样?那么你可以简单地检查一下。
if address in cached:
//skip
此缓存将从加载模块的那一刻起生效,并且在您使用完此模块后不会保存。您可能希望使用 pickle 将其保存到文件或数据库中,并在下次加载模块时加载它。
from geopy import geocoders
cache = {}
def geocode( address ):
# address ~= "175 5th Avenue NYC"
g = geocoders.GoogleV3()
cache = addressCached( address )
if ( cache != False ):
# We have seen this exact address before,
# return the saved location
return cache
# Otherwise, get a new location from geocoder
location = g.geocode( address )
saveToCache( address, location )
return location
def addressCached( address ):
global cache
if address in cache:
return cache[address]
return None
def saveToCache( address, location ):
global cache
cache[address] = location
这是一个简单的实现,它使用 python shelve
包进行透明和持久缓存:
import geopy
import shelve
import time
class CachedGeocoder:
def __init__(self, source = "Nominatim", geocache = "geocache.db"):
self.geocoder = getattr(geopy.geocoders, source)()
self.db = shelve.open(geocache, writeback = True)
self.ts = time.time()+1.1
def geocode(self, address):
if not address in self.db:
time.sleep(max(1 -(time.time() - self.ts), 0))
self.ts = time.time()
self.db[address] = self.geocoder.geocode(address)
return self.db[address]
geocoder = CachedGeocoder()
print geocoder.geocode("San Francisco, USA")
它存储一个时间戳以确保发出请求的频率不会超过每秒一次(这是 Nominatim 的要求)。一个弱点是不处理来自 Nominatim 的超时响应。