我正在使用 GWT 和 App Engine 编写一个网络应用程序。我的应用程序需要根据纬度、经度发布和查询项目。
由于 google 的分布式数据库设计,您不能简单地查询一组不等式。相反,他们建议进行地理散列。该方法在此页面上进行了描述。
http://code.google.com/appengine/articles/geosearch.html
本质上,您预先计算了一个边界框,以便您可以查询已用该边界框标记的项目。
这个过程有一部分我不明白。“切片”属性是什么意思?
谢谢你的帮助!
我正在使用 GWT 和 App Engine 编写一个网络应用程序。我的应用程序需要根据纬度、经度发布和查询项目。
由于 google 的分布式数据库设计,您不能简单地查询一组不等式。相反,他们建议进行地理散列。该方法在此页面上进行了描述。
http://code.google.com/appengine/articles/geosearch.html
本质上,您预先计算了一个边界框,以便您可以查询已用该边界框标记的项目。
这个过程有一部分我不明白。“切片”属性是什么意思?
谢谢你的帮助!
有关 Geomodel 的完整 Java 移植,请参阅http://code.google.com/p/javageomodel/。
有一个演示类向您解释如何使用它。
除了用 4 个坐标(最小和最大纬度、最小和最大经度)来定义边界框,您可以使用框的西北角的坐标和两个参数来定义它:分辨率和切片。分辨率定义了盒子的比例,它被实现为小数点以下的位数。切片是框的宽度和高度,以最低有效数字为单位。
geobox.py中的注释更详细地解释了这一点,并附有很好的例子:
为了查询边界框的成员,我们从一些输入坐标开始,例如 lat=37.78452 long=-122.39532(分辨率均为 5)。然后我们将这些坐标上下四舍五入到最近的“切片”以生成地理框。“切片”是在地理框中划分每个分辨率级别的精细程度。最小切片大小为 1,最大值没有限制,因为较大的切片只会溢出到较低的分辨率(希望示例能够解释)。
一些例子:
分辨率=5,切片=2,纬度=37.78452 长=-122.39532:“37.78452|-122.39532|37.78450|-122.39530”
分辨率=5,切片=10,纬度=37.78452 长=-122.39532:“37.78460|-122.39540|37.78450|-122.39530”
分辨率=5,切片=25,纬度=37.78452 长=-122.39532:“37.78475|-122.39550|37.78450|-122.39525”
我正在做一个 GWT/GAE项目并且遇到了同样的问题。我的解决方案是使用一个Geohash类,我稍作修改以使其对 GWT 友好。这非常适合我的邻近搜索需求。
如果您从未见过 Geohashes 的实际应用,请查看Dave Troy 的JS 演示页面。
在 App Engine 中进行地理空间搜索的另一种方法是 Search Api。您无需担心地理散列或实施细节,您将能够搜索靠近地理点的元素。
I was working on a GAE project with geohashing and this python library did the trick for me: http://mappinghacks.com/code/geohash.py.txt
我还需要一个 Java 版本的 GeoModel。我之前在使用 Geohash,它允许我在给定的边界框中获取位置。但是在排序方面有相当大的限制:为了让 BigTable接受geohash > '" + bottomLeft + "' && geohash < '" + topRight + "'"像geohash使用分页)。同时,除了Java代码之外,我想不出一个解决方案来按距离(从给定的用户位置,即边界框的中心)对结果进行排序。同样,如果您需要分页,这将不起作用。
由于这些问题,我不得不使用不同的方法,而 GeoModel/Geoboxes 似乎就是这样。所以,我将 Python 代码移植到 Java 中,它运行良好!结果如下:
public class Geobox {
private static double roundSlicedown(double coord, double slice) {
double remainder = coord % slice;
if (remainder == Double.NaN) {
return coord;
}
if (coord > 0) {
return coord - remainder + slice;
} else {
return coord - remainder;
}
}
private static double[] computeTuple(double lat, double lng,
int resolution, double slice) {
slice = slice * Math.pow(10, -resolution);
double adjustedLat = roundSlicedown(lat, slice);
double adjustedLng = roundSlicedown(lng, slice);
return new double[] { adjustedLat, adjustedLng - slice,
adjustedLat - slice, adjustedLng };
}
private static String formatTuple(double[] values, int resolution) {
StringBuffer s = new StringBuffer();
String format = String.format("%%.%df", resolution);
for (int i = 0; i < values.length; i++) {
s.append(String.format(format, values[i]).replace(',','.'));
if (i < values.length - 1) {
s.append("|");
}
}
return s.toString();
}
public static String compute(double lat, double lng, int resolution,
int slice) {
return formatTuple(computeTuple(lat, lng, resolution, slice),
resolution);
}
public static List<String> computeSet(double lat, double lng,
int resolution, double slice) {
double[] primaryBox = computeTuple(lat, lng, resolution, slice);
slice = slice * Math.pow(10, -resolution);
List<String> set = new ArrayList<String>();
for (int i = -1; i < 2; i++) {
double latDelta = slice * i;
for (int j = -1; j < 2; j++) {
double lngDelta = slice * j;
double[] adjustedBox = new double[] { primaryBox[0] + latDelta,
primaryBox[1] + lngDelta, primaryBox[2] + latDelta,
primaryBox[3] + lngDelta };
set.add(formatTuple(adjustedBox, resolution));
}
}
return set;
}
}
很抱歉回答迟了,但我有一段时间没有返回此页面。使用 Geobox 方法的 GeoDao 实现可能如下所示:
public class GeoDaoImpl extends DaoImpl<T extends GeoModel> {
// geobox configs are: resolution, slice, use set (1 = true)
private final static int[][] GEOBOX_CONFIGS =
{ { 4, 5, 1 },
{ 3, 2, 1 },
{ 3, 8, 0 },
{ 3, 16, 0 },
{ 2, 5, 0 } };
public GeoDaoImpl(Class<T> persistentClass) {
super(persistentClass);
}
public List<T> findInGeobox(double lat, double lng, int predefinedBox, String filter, String ordering, int offset, int limit) {
return findInGeobox(lat, lng, GEOBOX_CONFIGS[predefinedBox][0], GEOBOX_CONFIGS[predefinedBox][1], filter, ordering, offset, limit);
}
public List<T> findInGeobox(double lat, double lng, int resolution, int slice, String filter, String ordering, int offset, int limit) {
String box = Geobox.compute(lat, lng, resolution, slice);
if (filter == null) {
filter = "";
} else {
filter += " && ";
}
filter += "geoboxes=='" + box + "'";
return super.find(persistentClass, filter, ordering, offset, limit);
}
public List<T> findNearest(final double lat, final double lng, String filter, String ordering, int offset, int limit) {
LinkedHashMap<String, T> uniqueList = new LinkedHashMap<String, T>();
int length = offset + limit;
for (int i = 0; i < GEOBOX_CONFIGS.length; i++) {
List<T> subList = findInGeobox(lat, lng, i, filter, ordering, 0, limit);
for (T model : subList) {
uniqueList.put(model.getId(), model);
}
if (uniqueList.size() >= length) {
break;
}
}
List<T> list = new ArrayList<T>();
int i = 0;
for (String key : uniqueList.keySet()) {
if (i >= offset && i <= length) {
list.add(uniqueList.get(key));
}
i++;
}
Collections.sort(list, new Comparator<T>() {
public int compare(T model1, T model2) {
double distance1 = Geoutils.distFrom(model1.getLatitude(), model1.getLongitude(), lat, lng);
double distance2 = Geoutils.distFrom(model2.getLatitude(), model2.getLongitude(), lat, lng);
return Double.compare(distance1, distance2);
}
});
return list;
}
@Override
public void save(T model) {
preStore(model);
super.save(model);
}
private void preStore(T model) {
// geoboxes are needed to find the nearest entities and sort them by distance
List<String> geoboxes = new ArrayList<String>();
for (int[] geobox : GEOBOX_CONFIGS) {
// use set
if (geobox[2] == 1) {
geoboxes.addAll(Geobox.computeSet(model.getLatitude(), model.getLongitude(), geobox[0], geobox[1]));
} else {
geoboxes.add(Geobox.compute(model.getLatitude(), model.getLongitude(), geobox[0], geobox[1]));
}
}
model.setGeoboxes(geoboxes);
}
}