Добрый день.
Спешу поделиться небольшим опытом использования Google Maps API Web Services.
В этой статья я расскажу как использовать службы для геокодирования, геодекодирования.
Так же как можно находить расстояния и маршруты между точками. И конежно же коснусь решения задачи «куда сходить ближе туда или туда».
Для начала определимся со сторонними библиотеками, помощь которых нам понадобится.
Инструменты
Google Maps API Web Services могут возвращать сообщения с помощью json и xml. Google рекомендует использовать json, он мне тоже больше по душе, так как он меньше и понятнее. Для работы с json будем использовать библиотеку org.json, она не большая и выполняет все задачи, которые мне нужны.
Дополнительно для работы с коллекциями будем использовать библиотеку guava.
Ну и, конечно же, jdk 1.6.
Для обращения в вебсервисами и получения ответа в json, напишем класс JsonReader.
public class JsonReader {
private static String readAll(final Reader rd) throws IOException {
final StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1) {
sb.append((char) cp);
}
return sb.toString();
}
public static JSONObject read(final String url) throws IOException, JSONException {
final InputStream is = new URL(url).openStream();
try {
final BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
final String jsonText = readAll(rd);
final JSONObject json = new JSONObject(jsonText);
return json;
} finally {
is.close();
}
}
}
Для удобства параметры запроса будем хранить в мапе, для того что бы в итоге получать из мапы путь вида key1=value1&key2=value2..., напишем статический метод.
private static String encodeParams(final Map<String, String> params) {
final String paramsUrl = Joiner.on('&').join(// получаем значение вида key1=value1&key2=value2...
Iterables.transform(params.entrySet(), new Function<Entry<String, String>, String>() {
@Override
public String apply(final Entry<String, String> input) {
try {
final StringBuffer buffer = new StringBuffer();
buffer.append(input.getKey());// получаем значение вида key=value
buffer.append('=');
buffer.append(URLEncoder.encode(input.getValue(), "utf-8"));// кодируем строку в соответствии со стандартом HTML 4.01
return buffer.toString();
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}));
return paramsUrl;
}
С инструменами определились, перейдем к сути.
Геокодирование и геодекодирование
Геокодирование – процесс преобразования адресов (таких как 1600 Amphitheatre Parkway, Mountain View, CA) в географические координаты (такие как широта 37,423021 и долгота -122,083739), которые можно использовать для размещения маркеров или позиционирования карты. Служба Google Geocoding API предоставляет прямой доступ к геокодеру посредством HTTP-запроса. Также эта служба позволяет выполнять обратное действие (перевод координат в адреса). Этот процесс называется «обратное геокодирование».
Рассмотрим запрос к службе геокодирования, на примере адреса Россия, Москва, улица Поклонная, 12.
Путь к службе maps.googleapis.com/maps/api/geocode/output?parameters, о структуре запроса и ответа подробно написано здесь и здесь.
public static void main(final String[] args) throws IOException, JSONException {
final String baseUrl = "http://maps.googleapis.com/maps/api/geocode/json";// путь к Geocoding API по HTTP
final Map<String, String> params = Maps.newHashMap();
params.put("sensor", "false");// исходит ли запрос на геокодирование от устройства с датчиком местоположения
params.put("address", "Россия, Москва, улица Поклонная, 12");// адрес, который нужно геокодировать
final String url = baseUrl + '?' + encodeParams(params);// генерируем путь с параметрами
System.out.println(url);// Путь, что бы можно было посмотреть в браузере ответ службы
final JSONObject response = JsonReader.read(url);// делаем запрос к вебсервису и получаем от него ответ
// как правило наиболее подходящий ответ первый и данные о координатах можно получить по пути
// //results[0]/geometry/location/lng и //results[0]/geometry/location/lat
JSONObject location = response.getJSONArray("results").getJSONObject(0);
location = location.getJSONObject("geometry");
location = location.getJSONObject("location");
final double lng = location.getDouble("lng");// долгота
final double lat = location.getDouble("lat");// широта
System.out.println(String.format("%f,%f", lat, lng));// итоговая широта и долгота
}
Консоль:
http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%83%D0%BB%D0%B8%D1%86%D0%B0+%D0%9F%D0%BE%D0%BA%D0%BB%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F%2C+12 55.735893,37.527420
Теперь выполним обратную операцию, заменив в параметрах address, на latlng
public static void main(final String[] args) throws IOException, JSONException {
final String baseUrl = "http://maps.googleapis.com/maps/api/geocode/json";// путь к Geocoding API по HTTP
final Map<String, String> params = Maps.newHashMap();
params.put("language", "ru");// язык данных, на котором мы хотим получить
params.put("sensor", "false");// исходит ли запрос на геокодирование от устройства с датчиком местоположения
// текстовое значение широты/долготы, для которого следует получить ближайший понятный человеку адрес, долгота и
// широта разделяется запятой, берем из предыдущего примера
params.put("latlng", "55.735893,37.527420");
final String url = baseUrl + '?' + encodeParams(params);// генерируем путь с параметрами
System.out.println(url);// Путь, что бы можно было посмотреть в браузере ответ службы
final JSONObject response = JsonReader.read(url);// делаем запрос к вебсервису и получаем от него ответ
// как правило, наиболее подходящий ответ первый и данные об адресе можно получить по пути
// //results[0]/formatted_address
final JSONObject location = response.getJSONArray("results").getJSONObject(0);
final String formattedAddress = location.getString("formatted_address");
System.out.println(formattedAddress);// итоговый адрес
}
Консоль:
http://maps.googleapis.com/maps/api/geocode/json?sensor=false&latlng=55.735893%2C37.527420&language=ru Поклонная ул., 12, Москва, Россия, 121170
Вычисление расстояний между пунктами
Для вычисления расстояния логично было бы получить две точки и посчитать по формуле итог.
private static final double EARTH_RADIUS = 6371.; // Радиус Земли
public static void main(final String[] args) throws IOException, JSONException {
final Point subwayStationPoint = getPoint("Россия, Москва, улица Поклонная, 12");
final Point addressPoint = getPoint("Россия, Москва, станция метро Парк Победы");
// Рассчитываем расстояние между точками
final double dlng = deg2rad(subwayStationPoint.lng - addressPoint.lng);
final double dlat = deg2rad(subwayStationPoint.lat - addressPoint.lat);
final double a = sin(dlat / 2) * sin(dlat / 2) + cos(deg2rad(addressPoint.lat))
* cos(deg2rad(subwayStationPoint.lat)) * sin(dlng / 2) * sin(dlng / 2);
final double c = 2 * atan2(sqrt(a), sqrt(1 - a));
System.out.println("distance: " + c * EARTH_RADIUS); // получаем расстояние в километрах
}
/**
* Класс точки, хранит значения в градусах
*
*/
private static class Point {
public double lat;
public double lng;
public Point(final double lng, final double lat) {
this.lng = lng;
this.lat = lat;
}
@Override
public String toString() {
return lat + "," + lng;
}
}
/**
* Геокодирует адрес
*
* @param address
* @return
* @throws IOException
* @throws JSONException
*/
private static Point getPoint(final String address) throws IOException, JSONException {
final String baseUrl = "http://maps.googleapis.com/maps/api/geocode/json";// путь к Geocoding API по HTTP
final Map<String, String> params = Maps.newHashMap();
params.put("sensor", "false");// указывает, исходит ли запрос на геокодирование от устройства с датчиком
// местоположения
params.put("address", address);// адрес, который нужно геокодировать
final String url = baseUrl + '?' + encodeParams(params);// генерируем путь с параметрами
System.out.println(url);// Можем проверить что вернет этот путь в браузере
final JSONObject response = JsonReader.read(url);// делаем запрос к вебсервису и получаем от него ответ
// как правило наиболее подходящий ответ первый и данные о координатах можно получить по пути
// //results[0]/geometry/location/lng и //results[0]/geometry/location/lat
JSONObject location = response.getJSONArray("results").getJSONObject(0);
location = location.getJSONObject("geometry");
location = location.getJSONObject("location");
final double lng = location.getDouble("lng");// долгота
final double lat = location.getDouble("lat");// широта
final Point point = new Point(lng, lat);
System.out.println(address + " " + point); // выводим адрес и точку для него
return point;
}
/**
* Преобразует значение из градусов в радианы
*
* @param degree
* @return
*/
private static double deg2rad(final double degree) {
return degree * (Math.PI / 180);
}
Консоль:
http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%83%D0%BB%D0%B8%D1%86%D0%B0+%D0%9F%D0%BE%D0%BA%D0%BB%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F%2C+12 Россия, Москва, улица Поклонная, 12 55.7358925,37.5274195 http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%81%D1%82%D0%B0%D0%BD%D1%86%D0%B8%D1%8F+%D0%BC%D0%B5%D1%82%D1%80%D0%BE+%D0%9F%D0%B0%D1%80%D0%BA+%D0%9F%D0%BE%D0%B1%D0%B5%D0%B4%D1%8B Россия, Москва, станция метро Парк Победы 55.736217,37.516838 distance: 0.6634200825814502
Но такой вариант укажет расстояние между только пунктами по прямой, не учитывая преграды. К счастью, Google предлагает службу для получения данных о маршрутах.
Вычисление расстояний между пунктами используя маршруты
Google Directions API – это служба, которая вычисляет маршруты между пунктами с помощью HTTP-запроса. В службе Directions пункты отправления и назначения могут указываться в виде текстовых строк (например, «Чикаго, Иллинойс» или «Дарвин, Новый Южный Уэльс, Австралия») либо как координаты широты и долготы. Служба Directions API может возвращать составные маршруты в виде последовательности путевых точек.
Путь к службе maps.googleapis.com/maps/api/directions/output?parameters, о структуре запроса и ответа подробно написано здесь и здесь.
Например мы хотим знать сколько времени идти от улица Поклонной, 12 до станции метро Парк Победы:
public static void main(final String[] args) throws IOException, JSONException {
final String baseUrl = "http://maps.googleapis.com/maps/api/directions/json";// путь к Geocoding API по
// HTTP
final Map<String, String> params = Maps.newHashMap();
params.put("sensor", "false");// указывает, исходит ли запрос на геокодирование от устройства с датчиком
params.put("language", "ru");// язык данные на котором мы хотим получить
params.put("mode", "walking");// способ перемещения, может быть driving, walking, bicycling
params.put("origin", "Россия, Москва, улица Поклонная, 12");// адрес или текстовое значение широты и
// отправного пункта маршрута
params.put("destination", "Россия, Москва, станция метро Парк Победы");// адрес или текстовое значение широты и
// долготы
// долготы конечного пункта маршрута
final String url = baseUrl + '?' + encodeParams(params);// генерируем путь с параметрами
System.out.println(url); // Можем проверить что вернет этот путь в браузере
final JSONObject response = JsonReader.read(url);// делаем запрос к вебсервису и получаем от него ответ
// как правило наиболее подходящий ответ первый и данные о координатах можно получить по пути
// //results[0]/geometry/location/lng и //results[0]/geometry/location/lat
JSONObject location = response.getJSONArray("routes").getJSONObject(0);
location = location.getJSONArray("legs").getJSONObject(0);
final String distance = location.getJSONObject("distance").getString("text");
final String duration = location.getJSONObject("duration").getString("text");
System.out.println(distance + "n" + duration);
}
Консоль
http://anonymouse.org/cgi-bin/anon-www.cgi/http://maps.googleapis.com/maps/api/directions/json?sensor=false&origin=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%83%D0%BB%D0%B8%D1%86%D0%B0+%D0%94%D0%B5%D0%BD%D0%B8%D1%81%D0%B0+%D0%94%D0%B0%D0%B2%D1%8B%D0%B4%D0%BE%D0%B2%D0%B0%2C+7&language=ru&destination=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%83%D0%BB%D0%B8%D1%86%D0%B0+%D0%9A%D1%83%D0%BB%D1%8C%D0%BD%D0%B5%D0%B2%D0%B0+3&mode=walking 0,9 км 10 мин.
Но что же делать, если мы хотим знать к какой станции метро Парк Победы или станции метро Кутузовская ближе идти от улица Поклонной, 12.
Для решения этой задачи предлагается использование службы Google Distance Matrix API.
Вычисление расстояния и времени пути для матрицы исходных и конечных точек
Google Distance Matrix API –это служба, предоставляющая информацию о расстоянии и времени пути для матрицы исходных и конечных точек. Информация предоставляется на основе данных о рекомендуемом маршруте между начальными и конечными точками, рассчитанном с помощью Google Maps API, и представляет собой строки, содержащие значения duration и distance для каждой пары точек.
Путь к службе maps.googleapis.com/maps/api/distancematrix/output?parameters, о структуре запроса и ответа подробно написано здесь и здесь.
public static void main(final String[] args) throws IOException, JSONException {
final String baseUrl = "http://maps.googleapis.com/maps/api/distancematrix/json";// путь к Geocoding API по HTTP
final Map<String, String> params = Maps.newHashMap();
params.put("sensor", "false");// указывает, исходит ли запрос на геокодирование от устройства с датчиком
params.put("language", "ru");// язык данных
params.put("mode", "walking");// идем пешком, может быть driving, walking, bicycling
// адрес или координаты отправных пунктов
final String[] origins = { "Россия, Москва, улица Поклонная, 12" };
params.put("origins", Joiner.on('|').join(origins));
// адрес или координаты пунктов назначения
final String[] destionations = { //
"Россия, Москва, станция метро Парк Победы", //
"Россия, Москва, станция метро Кутузовская" //
};
// в запросе адреса должны разделяться символом '|'
params.put("destinations", Joiner.on('|').join(destionations));
final String url = baseUrl + '?' + encodeParams(params);// генерируем путь с параметрами
System.out.println(url); // Можем проверить что вернет этот путь в браузере
final JSONObject response = JsonReader.read(url);// делаем запрос к вебсервису и получаем от него ответ
final JSONObject location = response.getJSONArray("rows").getJSONObject(0);
final JSONArray arrays = location.getJSONArray("elements");// Здесь лежат все рассчитанные значения
// Ищем путь на который мы потратим минимум времени
final JSONObject result = Ordering.from(new Comparator<JSONObject>() {
@Override
public int compare(final JSONObject o1, final JSONObject o2) {
final Integer duration1 = getDurationValue(o1);
final Integer duration2 = getDurationValue(o2);
return duration1.compareTo(duration2);// Сравниваем по времени в пути
}
/**
* Возвращает время в пути
*
* @param obj
* @return
*/
private int getDurationValue(final JSONObject obj) {
try {
return obj.getJSONObject("duration").getInt("value");
} catch (final JSONException e) {
throw new RuntimeException(e);
}
}
}).min(new AbstractIterator<JSONObject>() {// К сожалению JSONArray нельзя итереровать, по этому обернем его
private int index = 0;
@Override
protected JSONObject computeNext() {
try {
JSONObject result;
if (index < arrays.length()) {
final String destionation = destionations[index];
result = arrays.getJSONObject(index++);
result.put("address", destionation);// Добавим сразу в структуру и адрес, потому как его нет в
// этом расчёте
} else {
result = endOfData();
}
return result;
} catch (final JSONException e) {
throw new RuntimeException(e);
}
}
});
final String distance = result.getJSONObject("distance").getString("text");// расстояние в километрах
final String duration = result.getJSONObject("duration").getString("text");// время в пути
final String address = result.getString("address");// адрес
System.out.println(address + "n" + distance + "n" + duration);
}
Консоль
http://maps.googleapis.com/maps/api/distancematrix/json?sensor=false&destinations=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%81%D1%82%D0%B0%D0%BD%D1%86%D0%B8%D1%8F+%D0%BC%D0%B5%D1%82%D1%80%D0%BE+%D0%9F%D0%B0%D1%80%D0%BA+%D0%9F%D0%BE%D0%B1%D0%B5%D0%B4%D1%8B%7C%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%81%D1%82%D0%B0%D0%BD%D1%86%D0%B8%D1%8F+%D0%BC%D0%B5%D1%82%D1%80%D0%BE+%D0%9A%D1%83%D1%82%D1%83%D0%B7%D0%BE%D0%B2%D1%81%D0%BA%D0%B0%D1%8F&origins=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C+%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%2C+%D1%83%D0%BB%D0%B8%D1%86%D0%B0+%D0%9F%D0%BE%D0%BA%D0%BB%D0%BE%D0%BD%D0%BD%D0%B0%D1%8F%2C+12&language=ru&mode=walking Россия, Москва, станция метро Кутузовская 0,8 км 9 мин.
Немного усовершенствовав подход можно узнать кому из супругов проще заехать в банк или зайти на почту.
Ограничения на использование
Google один, а нас всех много. Сложно представить сколько запросов принимают эти службы каждый день. Что бы не забоится о нагрузке, корпорация добра просто установила ограничения на использование своих служб.
Сегодня, можно сделать не больше 2500 запросов с одного IP в день, и не больше чем 10 запросов в секунду. Если вы выработаете свой лимит, то результат запроса будет возвращаться пустым со статусом OVER_QUERY_LIMIT.
К тому же количество символов в URL, не должно превышать 2048, по этому лучше всего использовать при прокладке маршрутов координаты.
Ссылки
developers.google.com/maps/documentation/geocoding/?hl=ru
developers.google.com/maps/documentation/directions/?hl=ru
developers.google.com/maps/documentation/distancematrix/?hl=ru
Автор: nestor_by