Добрый день, хабровчане.
Спешу поделиться небольшим опытом использования Google Maps API Web Services.
В этой статья я расскажу как использовать службы для геокодирования, геодекодирования.
Так же как можно находить расстояния и маршруты между точками. И конежно же коснусь решения задачи «куда сходить ближе туда или туда».
Для начала определимся со сторонними библиотеками, помощь которых нам понадобится.
Google Maps API Web Services могут возвращать сообщения с помощью json и xml. Google рекомендует использовать json, он мне тоже больше по душе, так как он меньше и понятнее. Для работы с json будем использовать библиотеку org.json, она не большая и выполняет все задачи, которые мне нужны.
Дополнительно для работы с коллекциями будем использовать библиотеку guava.
Ну и, конечно же, jdk 1.6.
Для обращения в вебсервисами и получения ответа в json, напишем класс JsonReader.
Для удобства параметры запроса будем хранить в мапе, для того что бы в итоге получать из мапы путь вида key1=value1&key2=value2..., напишем статический метод.
С инструменами определились, перейдем к сути.
Рассмотрим запрос к службе геокодирования, на примере адреса Россия, Москва, улица Поклонная, 12.
Путь к службе maps.googleapis.com/maps/api/geocode/output?parameters, о структуре запроса и ответа подробно написано здесь и здесь.
Консоль:
Теперь выполним обратную операцию, заменив в параметрах address, на latlng
Консоль:
Для вычисления расстояния логично было бы получить две точки и посчитать по формуле итог.
Консоль:
Но такой вариант укажет расстояние между только пунктами по прямой, не учитывая преграды. К счастью, Google предлагает службу для получения данных о маршрутах.
Путь к службе maps.googleapis.com/maps/api/directions/output?parameters, о структуре запроса и ответа подробно написано здесь и здесь.
Например мы хотим знать сколько времени идти от улица Поклонной, 12 до станции метро Парк Победы:
Консоль
Но что же делать, если мы хотим знать к какой станции метро Парк Победы или станции метро Кутузовская ближе идти от улица Поклонной, 12.
Для решения этой задачи предлагается использование службы Google Distance Matrix API.
Путь к службе maps.googleapis.com/maps/api/distancematrix/output?parameters, о структуре запроса и ответа подробно написано здесь и здесь.
Консоль
Немного усовершенствовав подход можно узнать кому из супругов проще заехать в банк или зайти на почту.
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
UPD: Выложил исходники github.com/nestor-by/map-api-samples
Спешу поделиться небольшим опытом использования 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
UPD: Выложил исходники github.com/nestor-by/map-api-samples
