В этой статье мы интегрируем Google Maps с приложением AngularDart. Само приложение будет очень простым: оно рассчитывает расстояние по большому кругу (кратчайшее расстояние на поверхности сферы) между двумя выбранными маркерами на карте.
По пути вы:
- Зарегистрируйте свой собственный ключ API Карт Google.
- Создайте базовое веб-приложение на Angular.
- Интегрируйте Dart с Google Maps JavaScript API и управляйте взаимодействием с картами.
- Изучите несколько советов по полировке компонента Angular.
Эта статья примерно в четыре раза длиннее кода. Если хотите, просто посмотрите полный исходный код и последнюю демонстрацию.
Google Maps JavaScript API
Google Maps JavaScript API позволяет разработчикам встраивать и интегрировать Google Maps на свои сайты. Вы можете настроить отображение отображаемой карты и обработку ваших собственных изображений, данных и обработки.
Чтобы начать разработку с помощью Google Maps API, вы должны зарегистрировать бесплатный ключ API, который позволяет использовать его в разумных пределах. По мере того, как ваше приложение набирает обороты, вы можете перейти на платный план.
Посетите страницу JavaScript API и вверху страницы нажмите GET A KEY
. Придумайте название для своего проекта и нажмите CREATE AND ENABLE API
:
Ваш ключ будет включен и готов к использованию через пару секунд:
Запишите свой ключ API. Мы будем использовать его при загрузке библиотеки JavaScript Карт:
https://maps.googleapis.com/maps/api/js?key=KEY_GOES_HERE
В JavaScript, чтобы создать экземпляр карты и разместить на нем маркер, мы будем использовать следующий код:
var hostElement = document.getElementById('map-id'); var map = new google.maps.Map(hostElement, { zoom: 2, center: {lat: 47.4979, lng: 19.0402} }); var marker = new google.maps.Marker({ position: {lat: 47.4979, lng: 19.0402}, map: map, label: 'A' });
Как вы увидите позже, код Dart будет очень похож (со всеми дополнительными преимуществами Dart).
Приложение AngularDart
Самый простой способ начать работу с приложением AngularDart - следовать руководству Начало работы и создать новый проект в WebStorm или в Community Edition IntelliJ IDEA.
Если вы используете другой редактор или просто хотите следовать структуре нашего примера кода, вот минимум.
В файле pubspec pubspec.yaml
:
name: google_maps_angular_dart version: 0.0.1 description: Angular application with Google Maps integration environment: sdk: '>=1.19.0 <2.0.0' dependencies: angular2: ^2.2.0 dev_dependencies: dart_to_js_script_rewriter: ^1.0.1 transformers: - angular2: platform_directives: - 'package:angular2/common.dart#COMMON_DIRECTIVES' platform_pipes: - 'package:angular2/common.dart#COMMON_PIPES' entry_points: web/main.dart - dart_to_js_script_rewriter
На главной странице web/index.html
:
<!DOCTYPE html> <html> <head> <!-- other headers --> <script defer src="main.dart" type="application/dart"></script> </head> <body> <map-control></map-control> </body> </html>
В точке входа приложения web/main.dart
:
import 'package:angular2/platform/browser.dart'; import 'package:google_maps_angular_dart/component/map_control.dart'; void main() { bootstrap(MapControl); }
В файле Dart для компонента <map-control>
lib/component/map_control.dart
:
import 'package:angular2/core.dart'; @Component( selector: 'map-control', template: '{{distance}}', ) class MapControl { String distance = 'no distance yet'; }
Приведенный выше код мало что делает, но это только начало, и вы можете запустить его, используя pub serve
, а затем открыв страницу в Dartium:
$ pub serve Loading source assets... Loading angular2 and dart_to_js_script_rewriter transformers... Serving google_maps_angular_dart web on http://localhost:8080 Build completed successfully
Интеграция с Google Maps
Чтобы начать интеграцию с Google Maps, поместите следующий тег скрипта в свой web/index.html
. Обратите внимание, что вам нужно установить свой ключ API:
<script src="https://maps.googleapis.com/maps/api/js?key=[YOUR_KEY_HERE]"></script>
К счастью, в pub есть готовый к использованию пакет Google Maps Dart. Добавьте его в свой pubspec.yaml
:
dependencies: angular2: ^2.2.0 google_maps: ^3.0.0
Затем запустите pub get
, чтобы загрузить пакет.
Нам нужно создать основной элемент для области карты в нашем шаблоне компонента. Мы начнем с базового стиля и на следующем шаге будем использовать привязку #mapArea
для идентификации элемента:
<div style="width: 300px; height: 300px" #mapArea>[map]</div>
В код компонента мы можем вставить ссылку на элемент следующим образом:
@ViewChild('mapArea') ElementRef mapAreaRef;
Ссылка на элемент недоступна сразу после создания класса MapControl, поэтому нам нужно подключиться к обратным вызовам жизненного цикла Angular:
class MapControl implements AfterViewInit { @override void ngAfterViewInit() { // mapAreaRef is available now } }
Совет: используйте IDE, чтобы написать за вас тела методов. Например, в IntelliJ нажмите CMD + N
(или CTRL + N
) и выберите пункт меню «Реализовать методы…». Это позволит вам выбрать недостающие методы, и вам нужно будет беспокоиться только о теле метода:
Как показано в следующем коде, Dart API очень похож на API JavaScript, с дополнительным преимуществом, заключающимся в проверке типа.
class MapControl implements AfterViewInit { // ... @override void ngAfterViewInit() { GMap map = new GMap( mapAreaRef.nativeElement, new MapOptions() ..zoom = 2 ..center = new LatLng(47.4979, 19.0402) // ^ Budapest, Hungary ); new Marker(new MarkerOptions() ..map = map ..position = new LatLng(47.4979, 19.0402) ..label = 'A'); } }
Одним из примеров преимуществ проверки типов является то, что, когда мы отслеживаем события, нам не нужно думать о том, как получить доступ к свойствам. Например, IDE может помочь нам найти свойство MouseEvent (latLng
), которое содержит местоположение маркера.
Вот код, который реагирует на перетаскивание маркера:
marker.onDrag.listen((MouseEvent event) { print('New location while dragging: ${event.latLng}'); });
Захват событий щелчка на карте аналогичен:
map.onClick.listen((MouseEvent event) { print('User clicked on position: ${event.latLng}'); });
Собираем все вместе
Чтобы измерить расстояние между двумя координатами, мы будем отслеживать два маркера на карте. Пользователь может перетаскивать маркеры или размещать их, щелкая.
Нам нужно отслеживать карту и маркеры как поля:
GMap _map; Marker _aMarker; Marker _bMarker;
Обновите инициализацию, чтобы сохранить ссылку на карту и зарегистрировать обработчик кликов. Обработчик кликов обновляет позиции маркеров и расстояние:
@override void ngAfterViewInit() { _map = new GMap( mapAreaRef.nativeElement, new MapOptions() ..zoom = 2 ..center = new LatLng(47.4979, 19.0402) // ^ Budapest, Hungary ); _map.onClick.listen((MouseEvent event) { _updatePosition(event.latLng); _updateDistance(); }); }
Вот код первой части обработчика кликов:
void _updatePosition(LatLng position) { if (_aMarker == null) { _aMarker = _createMarker(_map, 'A', position); } else if (_bMarker == null) { _bMarker = _createMarker(_map, 'B', position); } else { _aMarker.position = _bMarker.position; _bMarker.position = position; } }
Код для создания экземпляра маркера аналогичен предыдущему примеру:
Marker _createMarker(GMap map, String label, LatLng position) { final Marker marker = new Marker(new MarkerOptions() ..map = map ..draggable = true ..label = label ..position = position); marker.onDrag.listen((MouseEvent event) { _updateDistance(); }); return marker; }
С помощью вспомогательных функций из dart:math
мы можем разобраться в математике вычисления расстояния большого круга и установить значение в нашем поле distance
:
/// Radius of the earth in km. const int radiusOfEarth = 6371; double _toRadian(num degree) => degree * PI / 180.0; void _updateDistance() { if (_aMarker == null || _bMarker == null) return; LatLng a = _aMarker.position; LatLng b = _bMarker.position; double dLat = _toRadian(b.lat - a.lat); double sLat = pow(sin(dLat / 2), 2); double dLng = _toRadian(b.lng - a.lng); double sLng = pow(sin(dLng / 2), 2); double cosALat = cos(_toRadian(a.lat)); double cosBLat = cos(_toRadian(b.lat)); double x = sLat + cosALat * cosBLat * sLng; double d = 2 * atan2(sqrt(x), sqrt(1 - x)) * radiusOfEarth; distance = '${d.round()} km'; }
Вы когда-нибудь задумывались, как далеко вы живете от родственника, известного места или достопримечательности? Пришло время опробовать приложение и проверить его на себе. Оказывается, мой дом находится в 9815 км от штаб-квартиры Google в Маунтин-Вью:
Полировка приложения
На этом интеграция Google Maps в наше приложение Angular завершена. Мы слушаем и реагируем на события карты и создаем объекты на карте. В этом последнем разделе мы реализуем функции, которые добавят красивую отделку нашей демонстрации.
Отдельный шаблон и стиль
Рекомендуется помещать сложный шаблон пользовательского интерфейса в отдельный файл .html. То же самое можно сделать и со стилями CSS:
@Component( selector: 'map-control', templateUrl: 'map_control.html', styleUrls: const <String>['map_control.css']) class MapControl implements AfterViewInit {
Элемент карты имеет класс CSS map-area:
<div class="map-area" #mapArea>[map]</div>
Наш файл CSS может быть таким простым:
.map-area { width: 500px; height: 400px; margin: 10px; }
Обработка единицы расстояния
Некоторые люди свободно конвертируют км ⟷ мили, но остальные хотели бы раскрывающийся список, в котором мы могли бы выбрать единицу измерения расстояния. В шаблоне HTML раскрывающийся список может быть простым элементом <SELECT>
с привязкой модели к полю unit
.
<label>Unit:</label> <select [(ngModel)]="unit"> <option value="km">km</option> <option value="miles">miles</option> </select>
В коде Dart мы хотим сохранить единицу и обновлять расстояние при каждом обновлении единицы:
String _unit = 'km'; String get unit => _unit; set unit(String value) { _unit = value; _updateDistance(); }
И не забудьте обновить ранее жестко запрограммированный km
при расчете расстояния:
/// Const value to convert from km to miles. const double milesPerKm = 0.621371; // ... same code as earlier if (unit == 'miles') { d *= milesPerKm; } distance = '${d.round()} $unit';
Форматирование координат
Что, если мы заинтересованы в публикации положения наших маркеров? Самый простой способ - показать значения позиции в Dart, чтобы мы могли использовать {{a}}
и {{b}}
в нашем шаблоне:
// Expose the position values. LatLng get a => _aMarker?.position; LatLng get b => _bMarker?.position;
Шаблон может скрыть метку, когда маркер еще не инициализирован, что предотвращает потенциальные проблемы с нулевыми значениями:
<div *ngIf="a != null">A: {{a}}</div> <div *ngIf="b != null">B: {{b}}</div>
Однако {{a}}
будет преобразовываться в вызов LatLng.toString()
и даст нам два действительно длинных двойных значения, тогда как несколько цифр вполне подойдут. Одно из решений - использовать в шаблоне трубы:
<div *ngIf="a != null">A: {{a.lat | number : '1.4-4'}}, {{a.lng | number : '1.4-4'}}</div> <div *ngIf="b != null">B: {{b.lat | number : '1.4-4'}}, {{b.lng | number : '1.4-4'}}</div>
Руководство по синтаксису шаблонов предлагает разместить эту логику в классе контроллера для лучшего тестирования:
/// Formatted position of the 'A' marker. String get aPosition => _formatPosition(a); /// Formatted position of the 'B' marker. String get bPosition => _formatPosition(b); String _formatPosition(LatLng pos) { if (pos == null) return null; return '${pos.lat.toStringAsFixed(4)}, ' '${pos.lng.toStringAsFixed(4)}'; }
При этом шаблон может быть намного проще:
<div *ngIf="a != null">A: {{aPosition}}</div> <div *ngIf="b != null">B: {{bPosition}}</div>
Очистите шаблон
В качестве последнего шага переместите все оставшиеся части кода из шаблона в контроллер:
/// Whether the 'A' marker's positions should be shown bool get showA => a != null; /// Whether the 'B' marker's positions should be shown bool get showB => b != null; /// Whether the 'distance' label should be shown bool get showDistance => distance != null;
При этом шаблон ссылается только на геттеры:
<div *ngIf="showA">A: {{aPosition}}</div> <div *ngIf="showB">B: {{bPosition}}</div> <p *ngIf="showDistance"> Distance: {{distance}}<br/> </p>
Заключительные примечания
Как видите, реализовать двустороннюю интеграцию с Google Maps просто: исходный код чистый и читаемый. Благодаря полной поддержке набора инструментов Dart его легко расширять, не беспокоясь о том, что мы можем сломать в других местах.
В качестве упражнения для читателя вы можете добавить на карту визуализацию тепловой карты, используя тот же API в Dart. Ознакомьтесь со всеми возможностями пакета google_maps.