Как стать автором
Обновить

Примеры работы с Google Map API, используя Java

Время на прочтение 12 мин
Количество просмотров 70K
Добрый день, хабровчане.

Спешу поделиться небольшим опытом использования 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
Теги:
Хабы:
+13
Комментарии 5
Комментарии Комментарии 5

Публикации

Истории

Работа

Java разработчик
356 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн