Ответ 1
Для полного java-портажа Geomodel, см. http://code.google.com/p/javageomodel/.
Существует демо-класс, чтобы объяснить вам, как его использовать.
Я пишу веб-приложение с использованием GWT и App Engine. Мое приложение должно будет отправлять и запрашивать элементы в зависимости от их широты, долготы.
В результате разработки распределенной базы данных Google вы не можете просто запросить набор неравенств. Вместо этого они предлагают делать geohashing. Метод описан на этой странице.
http://code.google.com/appengine/articles/geosearch.html
По существу вы предварительно вычисляете ограничивающий прямоугольник, чтобы вы могли запрашивать элементы, помеченные этим ограничивающим прямоугольником.
Есть одна часть процесса, которую я не понимаю. Что означает атрибут "срез"?
Спасибо за вашу помощь!
Для полного java-портажа Geomodel, см. http://code.google.com/p/javageomodel/.
Существует демо-класс, чтобы объяснить вам, как его использовать.
Вместо того, чтобы внедрять сам geohash, вам может быть интересен GeoModel проект с открытым исходным кодом, который реализует систему, похожую на Geohash, в Google App Engine. Вместо того, чтобы понимать все детали, вы можете просто импортировать эту библиотеку и совершать вызовы типа proximity_fetch()
и bounding_box_fetch()
.
Эта более поздняя статья описывает, как она работает, и предоставляет пример, который ее использует.
Вместо определения ограничивающего прямоугольника с 4 координатами (минимальная и максимальная широта, минимальная и максимальная долгота) вы можете определить его с координатами северо-западного угла поля и двумя параметрами: разрешением и срезом. Разрешение определяет масштаб окна, оно реализуется как число цифр ниже десятичной точки. Срез - это ширина и высота окна, используя наименее значимую цифру в качестве своего устройства.
Комментарии в geobox.py объясняют это более подробно, с хорошими примерами:
Чтобы запросить членов ограничивающего блока, мы начнем с некоторых входных координат например lat = 37.78452 long = -122.39532 (оба разрешения 5). Затем мы обходим эти координаты вверх и вниз до ближайшего "среза" для генерации геобокса. Ломтик "как тонко разделить каждый уровень разрешения в геообъектах. Минимум размер среза равен 1, максимум не имеет предела, так как большие фрагменты будут просто перекинуться на более низкие разрешения (надеюсь, примеры объяснят).
Некоторые примеры:
resolution = 5, slice = 2 и lat = 37.78452 long = -122.39532:" 37,78452 | -122,39532 | 37,78450 | -122,39530 "
resolution = 5, slice = 10 и lat = 37.78452 long = -122.39532: " 37,78460 | -122,39540 | 37,78450 | -122,39530 "
resolution = 5, slice = 25 и lat = 37.78452 long = -122.39532: " 37,78475 | -122,39550 | 37,78450 | -122,39525"
Я работаю над проектом GWT/GAE и имел ту же проблему. Мое решение состояло в том, чтобы использовать класс Geohash, который я слегка изменил, чтобы быть удобным для GWT. Это отлично подходит для моих потребностей в поиске близости.
Если вы никогда не видели Geohash в действии, посмотрите демонстрационную страницу Dave Troy JS.
Альтернативой геопространственным поисковым запросам в App Engine является поиск Api. Вам не нужно беспокоиться о деталях геохронизации или реализации, и вы сможете искать элементы, близкие к гео-точке.
Я также нуждался в 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;
}
}
Я работал над проектом GAE с geohashing, и эта библиотека python сделала для меня трюк: http://mappinghacks.com/code/geohash.py.txt
извините за поздний ответ, но я не вернулся на эту страницу в течение некоторого времени. Реализация GeoDao с использованием подхода Geobox может выглядеть так:
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);
}
}