K_Reverter的网页开发记录

要么不做,要么就当作艺术品来做!

导航

在GAE之中使用基于地理位置的查询

    当我决定将我的数据上传到GAE上的时候,我就预计到将来的数据调用将会是非常费劲的,果然,我现在仅仅是小小的使用了一下自己的数据就已经把自己折磨的够呛,因为GAE不能支持联合查询,在数据的排序和条件上也有诸多限制,因此,操作起来非常费劲,甚至有时候必须牺牲一些性能或效果。

    本文对那些问题不做详细的表述,仅仅谈谈我进行地理数据查询的实现。

    将地理的数据存储到Google的DataStore之中,可以直接用两个float的字段,代表经纬度,也可以直接使用db.GeoPtProperty,现在看来,直接使用float字段要直观容易的多,可是我当时上传的时候考虑到以后Google可能会在db.GeoPtProperty的基础是那个提供一些方便的功能,因此,就将数据上传为db.GeoPtProperty,可是上传之后遇到问题,原来db.GeoPtProperty不支持基于范围的查询,这就困难了,早知道我用两个float,建立好索引,应该可以实现周边查询等等功能的(后来补充:这样也不行,因为GQL在查询的时候仅仅支持一种"非等于"的条件,也就是说要实现lat>3 and lat<54 and lon>73 and lon<136这种查询也是不行的),很怀恋以前直接使用SQL Server的时候,一个SQL语句,按距离排序就搞定了。

    那么GAE之中的经纬度是按照什么顺序存放的呢?我从网上查了一下,GAE的经纬度是严格按照(lat,lon)的顺序存放的,也就是说基本上是按照纬度排,纬度相同的再按照经度拍,这样的话,查找某一个纬度范围的点是很容易的,不过这个查询在实际应用之中用处不大。

    我从网上查了一下,基本上大家都是考虑用geohash来实现周边查询,这个东西我没有精力去做具体的研究(后来补充:准备研究,专文讲叙),只能大致的猜想:geohash将一个经纬度序列化成为一个字符串,最后能够大致做到“两个越相邻的经纬度,得到字符串开头部分相同的位数就越多”,也就是说将一个平面的2维坐标编码成为了一个线性的序列,在二维坐标之中离的近的点,在这个线性序列之中距离也近。这种算法让我觉得很奇怪,不知道怎么实现的,不过这个问题等有时间再去研究,我现在,且在网站上直接使用这个功能。

    1.首先要使用geohash库,从网上去下一个geohash.py文件,放到自己的应用程序根目录下面

    2.要将线上的数据包含经纬度的表增加一个geohash的字段,并且将geohash编码字符串存进去,这一点我是使用remote_data来实现的,这种方式来处理数据方便快捷的多,比以前一直刷新页面好多了,我的增加这个字段的代码如下:

 


 1# -*- coding: utf-8 -*- #   
 2import code
 3import getpass
 4import sys
 5#下面要改成自己的gae安装路径
 6sys.path.append("D:\Program Files\Google\google_appengine\lib\yaml\lib")
 7sys.path.append("D:\Program Files\Google\google_appengine")
 8#下面是我的应用程序的路径,刚才说过geohash是放在那里的
 9sys.path.append("D:\work\myapp")
10
11import geohash
12from google.appengine.ext.remote_api import remote_api_stub
13from google.appengine.ext import db
14#这是数据表的定义,注意,最下面一行被我加上了geohash字段
15class Train_stations(db.Model):
16  name = db.StringProperty()
17  latlng = db.GeoPtProperty()
18  superior = db.StringProperty()
19  address = db.StringProperty()
20  postcode = db.StringProperty()
21  regionCode = db.StringProperty()
22  level = db.StringProperty()
23  telephone = db.StringProperty()
24  oldName = db.StringProperty()
25  lineCount = db.IntegerProperty()
26  geohash = db.StringProperty()
27
28def auth_func():
29  return raw_input('Username:'), getpass.getpass('Password:')
30#这是应用程序ID
31app_id='myapp'
32host = '%s.appspot.com' % app_id
33
34remote_api_stub.ConfigureRemoteDatastore("myapp"'/remote_api',auth_func)
35
36key=''
37while True:
38    sql="select * from Train_stations"
39    if key:
40        sql+=" where __key__>KEY('%s') "%key
41    sql+=" order by __key__"
42    stations=db.GqlQuery(sql).fetch(100)
43    if len(stations)<=0:
44        break
45    for station in stations:
46        if station.latlng and station.latlng.lat and station.latlng.lon:
47            station.geohash=str(geohash.Geohash((float(station.latlng.lon), float(station.latlng.lat))))
48        else:
49            station.geohash=''
50        print "%s:%s" % (station.name,station.geohash)
51    db.put(stations)
52    key=str(stations[len(stations)-1].key())
53    print key
54print "OK"

 

    上面这段代码在本地运行即可,不需要上传到服务器上去(不过需要注意服务器必须打开了remote_api,并且路径对应),运行的时候,会看到将每行数据经纬度进行geohash之后的数据,等到运行显示"OK",则说明所有的经纬度都hash完毕了,到GAE的后台可以看到数据,例如一个经纬度是"25.6138,109.484",geohash之后是"he6nyfgqsbce4"。

    3.数据处理完毕之后,就可以进行查询了,先理清思路:要查询一个经纬度附近的点,应该先查询大于这个geohash的点之中最小的hash,再查小于这个geohash的点之中最大的hash,因此必须查两遍:

 

    tvs['nearStations']=db.GqlQuery("select * from Train_stations where geohash < :1 order by geohash desc",(hash)).fetch(10)
    tvs[
'nearStations'].extend(db.GqlQuery("select * from Train_stations where geohash > :1 order by geohash",(hash)).fetch(10))

 

    4.通过以上的方式,就可以查出临近的点,这只是geohash的一种使用而已,至少,你还可以通过查询和当前的geohash前几位字母相同的hash值,从而的得到这个经纬度周边一定范围内的所有点,这一点因为我的网站上没有用到,因此,没有相应的代码可以提供,不过从原理上是可以实现的。

    以上就是我对geohash的简单应用,现在,这些应用已经开始运行在http://www.dituren.cn上面,目前来看,效果不错,马上我会仔细研究一下geohash的实现,对于我这个非专业的人来讲,geohash真是太神奇了。

posted on 2009-04-04 00:09  K_Reverter  阅读(3162)  评论(16编辑  收藏  举报