見出し画像

Angular と leaflet (OpenStreet Map) で Geo Tracker を作る

Angular で leaflet を使う という記事で Angularから leaflet(OpenStreet Map) を使う方法をまとめました。
今回は Geolocation API を利用して定期的に現在位置を leaflet の地図上にマッピングしていく Geo Tracker を作成してみます。

Leaflet で 位置情報のトラッキング

Geolocation API を利用することでブラウザだけで現在位置のトラッカーが簡単に作れます。
この記事に使っているソースコードは Github で公開しています。

Leaflet のインストール

leaflet のインストールについては Angular で leaflet を使う を参考にしてください。

Geolocation API

angular service から watchPosition という Geolocatopn API を利用しており、位置に変化があるとその変更後の位置情報を subscribe して取得できるようにします。

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable({
 providedIn: 'root'
})

export class GeolocationService {
 positionOptions: PositionOptions = {
   enableHighAccuracy: true,
   maximumAge: 0, // キャッシュは行わない
   timeout: 100000 // ミリ秒でタイムアウト値を指定する。結構取れないので長めにする。
 };

 constructor() { }

 // https://github.com/angular/angular/issues/27597
 createWatchPosition(): Observable<Position> {
   return new Observable((observer) => {
     let watchId;

     const onSuccess: PositionCallback = (pos: Position) => {
       observer.next(pos);
     };

     const onError: PositionErrorCallback | any = (error) => {
       observer.error(error);
     };

     if ('geolocation' in navigator) {
       watchId = navigator.geolocation.watchPosition(onSuccess, onError, this.positionOptions);
     } else {
       onError('Geolocation not available');
     }

     return { unsubscribe() { navigator.geolocation.clearWatch(watchId); } };
   });
 }

}

component から subscribe するわけですが debounceTime(1000) を設定して、データ通信頻度を調整しています。
subscribe しているので OnDestroy から unsubscribeしています。

import { Component, OnInit, OnDestroy, AfterViewInit, ViewChild, ElementRef } from '@angular/core';
import { GeolocationService } from '../services/geolocation.service';

import { debounceTime } from 'rxjs/operators';

import * as L from 'leaflet';

 ~省略~

export class GeolocationComponent implements OnInit, AfterViewInit, OnDestroy {
 map: any;

 constructor(
   private geolocationService: GeolocationService,
 ) { }

 // ngOnInit を利用すると <div id="map"></div> が描画される前に map を参照してエラーになる場合がある
 ngAfterViewInit() {
   this.subscribeGeolocation();
 }

 // OnDestroy しないとページ遷移してもsubscribeしてしまうので必要なければDestroyした方が良い
 ngOnDestroy() {
   if (this.locationsSubscription) {
     this.locationsSubscription.unsubscribe();
   }
 }

 subscribeGeolocation() {
   this.locationsSubscription = this.geolocationService.createWatchPosition()
   .pipe(
     // debounceTimeを設定してデータ量を節約している
     debounceTime(1000)
   ).subscribe(
     (value: Position) => {
       //  value.coords の中に緯度経度とタイムスタンプが返ってくる
       this.geoLocation = {} as GeoLocations;
       this.geoLocation.lat = value.coords.latitude;
       this.geoLocation.lon = value.coords.longitude;
       this.geoLocation.timestamp = value.timestamp;

       // geoLocationArray に位置情報を格納する。見やすさを考慮して直近10点だけを利用する。
       if (this.geoLocationArray.length < 10) {
         this.geoLocationArray.push(this.geoLocation);
       } else {
         this.geoLocationArray.shift();
         this.geoLocationArray.push(this.geoLocation);
       }

       if (!this.initialize) {
         this.initMap(this.geoLocation.lat, this.geoLocation.lon);
       }

       this.setMaker();

     },
     error => {
       console.log('error:', error);
     }
   );
 }

 // 地図を初期化
 initMap(lat: number, lon: number) {
   this.map = L.map('map').setView([lat, lon], 13);
   L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
       attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
   }).addTo(this.map);
   this.initialize = true;
 }

 // マーカーをプロットしpolylineでつなぐ
 setMaker() {
   const polylineArray = [];

   if (this.geoLocationArray) {
     for (let i = 0; this.geoLocationArray.length > i; i++ ) {
       const date = new Date(this.geoLocationArray[i].timestamp);

       L.marker(
         [this.geoLocationArray[i].lat, this.geoLocationArray[i].lon]
       ).bindPopup(
         '<b>Hello!!</b><br>  ' + (date.getMonth() + 1) + '/' + date.getDate()
       ).addTo(this.map);

       this.map.flyTo([this.geoLocationArray[i].lat, this.geoLocationArray[i].lon], 14, { duration: 2 });

       polylineArray.push( [this.geoLocationArray[i].lat, this.geoLocationArray[i].lon]);

       L.polyline([polylineArray], {color: '#FF0000', weight: 5, opacity: 0.6})
         .addTo(this.map);
     }
   }
 }

あとは html で描画するだけです。

<div class="map" #map id="map" style="width: 100%; height: 532.2px;"></div>

以上


いいなと思ったら応援しよう!