Автор: Сергей Матвеенко
Однажды ко мне пришел инвестор одного проекта и сказал: «Давай сделаем планировщик путешествий по картам Google!» Я согласился. Тогда инвестор стал рассказывать, как техдиректор представлял себе архитектуру этого планировщика: он говорил что-то про связь с сервером, про ключ API, про запросы в Google, про деньги за запросы, которых будет много и т. д. Все выглядело сложно и красиво. Однако затем мы стали внимательно читать документацию Google API и вдруг поняли, что на самом деле нам сервер не нужен. Вообще! Весь планировщик можно сделать на клиенте. А самое интересное — мы можем обойтись даже без API-ключа (при условии, что мы будем использовать JS API). В итоге я за два дня смог написать такой планировщик, с логикой на стороне клиента, на основе Google API, без использования сервера. Все оказалось очень просто.
Я расскажу, как можно сделать простейший планировщик такого рода буквально за пару часов. Конечно, за это время можно собрать только прототип, но главное — он будет работать! Его главной функцией будет прокладка оптимального маршрута между достопримечательностями в интересующем нас городе; может присутствовать разбивка плана поездки по дням. Все будет сделано на AngularJS с использованием Google Maps/Places API. Я расскажу об особенностях работы с этим API и о некоторых его возможностях, которые не указаны в документации. Также мы поговорим о выделении логики в клиентские приложения.
Итак, как именно будет выглядеть такой планировщик? Это будет просто строка поиска в браузере, в которую мы будем вводить название интересующего нас города или места. В ответ на такой поисковый запрос нам будет выдаваться это место на карте Google, список достопримечательностей в этом месте и ближайших окрестностях с описанием, фотографиями и отзывами. И, конечно, между достопримечательностями будет прокладываться оптимальный маршрут по картам Google c указанием примерного времени перемещения между ними. Также можно будет легко добавить возможность разбить план посещения достопримечательностей по дням.
Что нам понадобится?
Во-первых, нам понадобится библиотека Google Places. Это самое главное, что обеспечит работу планировщика:
<script src="https://maps.googleapis.com/maps/api/is?libraries=places&language=en"></script>
— это Autocomplete, очень полезный проект для AngularJS, который оборачивает библиотеку Google Places и работу с автозаполнением. Rawgit — обвязка вокруг GitHub, у которой тоже есть CDN.
<script src="https://rawgit.com/kuhnza/angular-google-places-autocomplete/0.2.7/src/autocomplete.js"></script>
— это собственно AngularJS.
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
— это Bootstrap.
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
— это jQuery, который нужен Bootstrap’у.
<link rel="stylesheet"
href=https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet"
href=https://rawgit.com/kuhnza/angular-google-places-autocomplete/master/dist/autocomplete.min.css">
— это таблицы стилей от Autocomplete и Bootstrap.
Вот и все внешнее, что у нас есть. Все это загружено с CDN — сервер нам не нужен вообще!
Ищем место на карте
Теперь попробуем сделать поиск интересующего нас города на карте. Вот как мы используем Autocomplete:
<input type="text" class="form-control input-lg"
g-places-autocomplete ng-model="destinationData"
placeholder="Enter destination">
<button ng-click="showDestination = !showDestination"
class="btn btn-block btn-default btn-xs">destinationData</button>
<pre ng-show="showDestination">{{destination|json}}</pre>
<div id="map" style="height: 200px;"></div>
Как мы видим, тут у нас один input:
<input type="text" class="form-control input-lg"
g-places-autocomplete ng-model="destinationData"
placeholder="Enter destination">
Таким образом, благодаря Angular-проекту Google Places Autocomplete мы получаем работающее автозаполнение. Это значит, что мы можем начать вводить название места в форму поиска (даже с ошибками), и нам в поиске будут предлагаться подходящие варианты:
В итоге у нас получился поиск места с отображением его местоположения на карте Google:
Что дальше?
Ищем ближайшие достопримечательности
Раньше (до версии 3) поиск по местам в Google API осуществлялся одним методом — там было очень много параметров, и все было очень сложно. Теперь поиск поделен на три метода. В частности, появился выделенный radar search — поиск по ближайшим интересным местам. Раньше такие места приходилось дополнительно фильтровать, т. к. в результатах поиска попадались города и страны. А сейчас все просто.
Как он работает?
Google обычно предлагает сразу показывать результаты поиска на карте. Но, на самом деле, нам это сейчас не нужно, ведь мы просто хотим получить список ближайших мест. Поэтому мы используем radar search (service.radarSearch
) без карты в радиусе 50 км — это обычный радиус города и окрестностей. Мы ищем обычные туристические места вроде музеев, церквей, ночных клубов, зоопарков и т. д., которые мы перечисляем как типы Google (types
).
$scope.destinationOptions = {
location : destination.geometry.location,
radius : 50000
};
$scope.popularPoints = [];
$scope.map = new google.maps.Map(document.getElementById('map'), {
center : $scope.destinationOptions.location,
zoom : 5
});
var service = new google.maps.places.PlacesService($scope.map);
var radarOptions = {
location : $scope.destinationOptions.location,
radius : $scope.destinationOptions.radius,
types : [ 'airport', 'amusement_park', 'aquarium', 'art_gallery', 'casino', 'church',
'city_hall', 'courthouse', 'hindu_temple', 'library', 'museum', 'night_club',
'park', 'stadium', 'synagogue', 'university', 'zoo' ]
};
service.radarSearch(radarOptions, function(points) {
points.slice(0, 8).forEach(function(point) {
service.getDetails({
placeId : point.place_id
}, function(details) {
$scope.popularPoints.push(details);
});
});
$scope.$digest();
});
Итак, мы получаем запрос к радар-поиску и вместо того, чтобы использовать рендерер и начать работать с картой, забираем все в Angular, и поиск выдает нам восемь первых мест (а возвращает он 200 результатов). Сразу же мы с помощью того же сервиса запрашиваем подробности о местах и добавляем к себе в ангуляровский scope: $scope.popularPoints.push(details)
.
Так мы сделали поиск — когда находим место на карте, тут же отображается и список всех достопримечательностей в радиусе 50 км. Также, если это нужно, мы сможем раскрыть этот список и увидеть изображения этого места, а также разные подробности и комментарии пользователей с рейтингом.
Прокладываем маршрут
Но если бы я просто показал самые лучшие достопримечательности в результатах поиска, инвестор был бы недоволен, ведь хочется еще спланировать наилучший маршрут по этим местам. Поэтому под результатами поиска у нас будет кнопка, которая сможет показать нам этот маршрут. Как она работает?
Оказывается, и тут тоже Google всё сделал за нас — он предоставляет полный интерфейс к механизмам Google Maps по планированию и прокладке маршрутов — вплоть до промежуточных точек. Но, как любят говорить некоторые мои знакомые, API тут сделан «чужими для хищников». Потому что, если у меня есть список точек, я просто хочу передать их вместе и сказать, что собираюсь по ним поездить. Но оказывается, что так не бывает, а бывает так, что мы начинаем только в каком-то одном месте и заканчиваем в каком-то другом конкретном месте, и внутри нужно выцепить slice’ом промежуточные точки.
$scope.tripCalc = function() {
var directionsService = new google.maps.DirectionsService();
directionsService.route({
origin : $scope.points[0].geometry.location,
destination : $scope.points[$scope.points.length - 1].geometry.location,
waypoints : $scope.points.slice(1, $scope.points.length - 1).map(function(point) {
return {
location : point.geometry.location
};
}),
travelMode: google.maps.TravelMode.DRIVING
}, function(result) {
$scope.route = result;
$scope.$digest();
});
}
Однако мы нашли выход — замыкали точки начала (origin
) и конца (destination
) маршрута на отель, в котором отдыхает человек. Также мы потом сделали и разбиение по дням — с учетом, что ночевать мы можем в разных отелях. Т. ч. можно сделать и так, например, что начальная точка — аэропорт, вторая — пункт аренды автомобилей, а конечная — отель первой ночи. Еще мы можем проложить маршрут так, чтобы искать в середине дня рестораны, чтобы пообедать. Все эти точки можно получить с помощью указания соответствующих типов в запросе к API Google Maps (параметр types
). На самом деле, это простая задача программирования — выстроить массив объектов и отразить его в waypoints
. Так мы получаем готовый маршрут.
Самое интересное и самое приятное — возможность показывать дистанцию и время, которое будет потрачено на то, чтобы переместиться между двумя точками маршрута. Это значит, что, если мы, например, едем между точками шесть часов — это подсказка, чтобы подыскать ресторан по пути, а если едем через ночь — значит, хорошо бы подсказать и отель. Все это делается на клиенте.
Нужен ли сервер?
И тут мы подходим к интересной дискуссии, которая возникла у нас, когда мы делали этот планировщик путешествий. А действительно ли мы хотим сохранять все это на сервере? Нужно ли нам знать, что искал пользователь?
На самом деле, нам интересно только одно: чтобы пользователь заказал у нас то, что мы хотим продать. Не буду говорить, что мы хотели продать пользователю, но, допустим, это могут быть комнаты в отелях, которые он выбрал в этом планировщике. По большому счету, этот планировщик — просто added value, добавленная стоимость, всего лишь удобный сервис на сайте, который продает комнаты в отелях. Т. ч. нам на сервере сохранять ничего не нужно — все можно делать на клиенте. Таким образом, мы можем просто прикрутить кнопки для бронирования номеров в отелях там, где эти отели отображаются при планировании маршрута.
Конечно, какая-то аналитика нам все же может понадобиться, но и тут мы можем обойтись без сервера, если используем Google Analytics. Так, например, если нам нужно знать, какие места больше всего искали пользователи, текст поиска может без проблем прыгать через HTML5 API в URL, и Google Analytics это подхватит.
Все остальное нас уже не очень волнует. Мы можем теперь рекламировать страницу, а Google пусть справляется с нагрузкой — при этом будет ограничено только количество запросов с одного IP, т. е. от пользователя. Мы можем выложить страницу на Amazon S3 и тогда сможем вообще не думать о нагрузке и о том, что наша рекламная кампания может положить наши серверы. Если же мы добавим еще отзывы от пользователей, пользователь начнет завязываться на какой-то интерактив на сайте, который, на самом деле, весь загружен снаружи.
Так что планировщик мы практически сделали — еще немного, и его можно продавать.
На самом деле, именно такие способы организации интерфейса эффективнее всего. Сейчас большие поставщики действительно сделали все, чтобы нам ни о чем не надо было беспокоиться. Например, последние два года, когда мои знакомые спрашивали, как им сделать сайт, я не мог ответить им ничего лучше, кроме как: «Идите на blogspot». Ведь там все есть! Так и Google Maps предоставляет все, что нужно. Но на каких условиях?
Условия использования GoogleMaps API
Если бегло читать официальные инструкции к Google Maps API, может показаться, что все обязательно должно происходить где-то на картах Google — мы должны что-то нарисовать и ничего с этим не делать. Но, если читать внимательней, оказывается, что, по условиям Google, все, что мы обязаны делать — это, грубо говоря, нарисовать в нижнем углу логотип Google и ссылки на всякие условия использования. Получается, что Google дал нам все необходимые данные, и мы на их основе можем хоть собственную карту рисовать и вообще распихивать эти данные, как нам угодно, лишь бы не забыть написать, что данные получены от Google. Поэтому вы можете делать на сайте все, что угодно на JavaScript, и без всякого сервера.
Исходник проекта
Автор: DataArt