Angular+Openweathermap+Leafret.js+MongoDBで天気予報WEBアプリを作る ④Leafletで地図を表示する

#この記事でやること
　前回までで都市の登録・天気の取得が可能になった。
　今回はOSS地図の`OpenStreetMap`をWEBアプリに実装できる`Learlet`ライブラリを利用して
　地図上に登録地点の天気を表示してみる
　※過去記事
　　[①Angular環境構築とひな形の生成](https://qiita.com/ryo_1050/items/71f0d5dd56b121c64b84)
　　[②コンポーネントとサービスの追加(登録画面)](https://qiita.com/ryo_1050/items/5ecf0ffa4c1b4d8cf940)
　　[③OpenweathermapのAPIを叩く](https://qiita.com/ryo_1050/items/578903a80653c92aa4cf)

#参考にした記事
[Angular6とLeafletで地図を表示する](https://qiita.com/uedayou/items/30f88d88238f648e8ee6)


#Leafletのインストール

###npm install
　leaflet本体と型定義モジュールを入手する

```powershell:PowerShell
npm install leaflet --save
npm install @types/leaflet --save-dev
```

##angular.jsonの修正
　leafletを使うための設定を行う

```json:angular.json
 :
 :
            "assets": [
              "src/favicon.ico",
              "src/assets",
              {
                "glob": "**/*",
                "input": "./node_modules/leaflet/dist/images",
                "output": "src/assets/"
              }
            ],
            "styles": [
              "src/styles.css",
              "./node_modules/leaflet/dist/leaflet.css"
            ],
 :
 :
```


##Leaflet用サービスの生成
`ng g service`コマンドでcommon/配下にサービスを作成する
地図を利用する画面はこのサービスを呼び出す事とする

```powershell:PowerShell
ng g service common/leaflet
```

　以下のファイルが作成される
　・leaflet.service.spec.ts
　・leaflet.service.ts

`leaflet.service.ts`に地図を生成する処理を実装する

```typescript:src/app/common/leaflet.service.ts

import { Injectable } from '@angular/core';
/* 追加 */
import * as L from 'leaflet';

@Injectable({
  providedIn: 'root'
})
export class LeafletService {
  /* 追加 */
  map:any;

  constructor() { }

  /* 追加 */
  createMap() {
    this.map = L.map('map').setView([33.584261,130.403789], 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);
  }
}
```

##地図の表示
dashboardコンポーネントを編集して地図用タグとLeafletサービスの呼び出し処理を追加

###HTML
`dashboard.component.html`に地図用のタグを追加する

```html:src/app/dashboard/dashboard.component.html
<h1>ダッシュボード</h1>
ここにいろいろ表示する
 :
 :
<!--地図用のdivを追加-->
<div id="map"></div>
```

###css
`dashboard.component.css`にマップのサイズを指定

```css:src/app/dashboard/dashboard.component.css
#map { height:70vh }
```

###component.ts
```typescript:src/app/dashboard/dashboard.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { City } from '../city/city';
import { CityService } from '../city/city.service';
/* 追加 */
import { LeafletService } from '../common/leaflet.service'

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
  cities: City[];

  constructor(
    private router: Router,
    /* サービスが増えたので名前を変える */
    private cityService: CityService,
    /* 追加 */
    private leafletService: LeafletService

  ){ } 

  ngOnInit() {
    this.cities = this.cityService.getCities();

    /* 追加 */
    this.leafletService.createMap();
  }
}

```

###地図表示結果
ダッシュボード画面に地図が表示される
![2019-10-13_09h28_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/79dc7064-397e-f6a0-9017-694fc7b07e7e.png)

(疑問)
`dashboard.component.ts`にはmapプロパティを持っていないのになぜサービスで生成したマップが
`dashboard.component.html`に反映されるのだろう
htmlの`<div id=map>`とimportされた`leaflet.service.ts`のプロパティ`map: any;`が紐づいている？？？

↓こんな感じ？いつかちゃんと調べよう。
![システム構成_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/0bc1ca76-7cc8-ac1c-4afe-c4ecb0f38090.png)



##登録地点にマーカーを表示
OpenstreetmapのAPIのレスポンスから緯度・経度を取り出し、マーカーを描画するために以下を行う
・`city.ts`を編集してクラスに緯度経度のプロパティを追加
・`city.service.ts`を編集してAPIレスポンスから緯度経度を取得
・`LeafletService`へマーカー描画処理の追加
・`dashboard.component.ts`に呼び出し処理を追加

###`city`クラスの修正

```typescript:src/app/city/city.ts
export class City {
    id: number;       /* 連番 */
    name: string;     /* 都市名 */
    weather: string;  /* 天気を取得して格納 */

    /* 緯度経度を格納するプロパティ*/
    location: 
      {lat: number, lon: number}
  }
```
これで緯度経度を保持できるようになった

###`city.service.ts`の編集

```typescript:src/app/city/city.service.ts
 :
 :
  addCity(city: City): void {
    var id: number;
    id = this.cities.length + 1;

    /* idを設定 */
    city.id = id;

    /* OpenweathermapのAPIを呼び出す */
    var apiEndPoint: string = this.environment.baseUrl
    + 'weather?q=' + city.name
    + '&appid=' + this.environment.appId;

    this.service.sendHttpRequest(apiEndPoint)
    .subscribe(res => {
      var weather = res.weather[0].description;
      // 天気を設定
      city.weather = weather;

      // 緯度経度を設定(追加)
      var coord = res.coord;
      city.location = {lat:coord.lat, lon:coord.lon};

      // 配列に追加
      this.cities.push(city);

    }, err => {
      console.log(err);
    }, () => {

    });
  }
 :
 :
```
これで都市登録時に緯度経度が取得できる
ついでにダッシュボードの表示部分も修正

###`dashboard.component.html`の修正

```html:src/app/dashboard/dashboard.component.html
<h1>ダッシュボード</h1>
ここにいろいろ表示する
<div *ngIf="cities.length !== 0">
    <br>
    <table>
        <!-- 緯度経度の表示を追加 -->
        <tr><th>Id</th><th>Name</th><th>Weather</th><th>latitude</th><th>longitude</th></tr>
        <tr *ngFor="let city of cities">
            <td>{{city.id}}</td>
            <td>{{city.name}}</td>
            <td>{{city.weather}}</td>
            <td>{{city.location.lat}}</td>
            <td>{{city.location.lon}}</td>

        </tr>
    </table>
</div>
<!--地図用のdivを追加-->
<div id="map"></div>
```
**画面**
緯度と経度が表示されるようになった
![2019-10-13_10h46_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/db007ba5-c3c4-498d-1299-5878e9e7e7d9.png)

###`leafled.service.ts`の編集
マーカー描画処理を実装する
画面側(dashboard.component.ts)から名前と天気と緯度経度をもらう想定

```typescript:src/app/common/leaflet.service.ts
 :
 :
  createMap() {
    this.map = L.map('map').setView([33.584261,130.403789], 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);
  }

  /* 追加 */
  /* 名前、天気、緯度・経度を受け取ってマーカー描画&吹き出しを付ける*/
  addMarker(name: string, weather: string, lat: number, lon: number) {
    L.marker([lat, lon])
    .bindPopup(name + ':\n' + weather)
    .addTo(this.map);
  }
}
```

cityインスタンスを丸ごと渡した方が早いが、汎用的にマーカー描画に使えるように
すべての値を個別に渡すようにしている

###`dashboard.component.ts`の編集
上記で作成したマーカー描画処理を呼び出す
名前、天気、緯度/経度を渡す

```typescript:src/app/dashboard/dashboard.component.ts
 :
 :
  ngOnInit() {
    this.cities = this.cityService.getCities();

    /* 地図を生成 */
    this.leafletService.createMap();

    /* 登録されている都市ごとにマーカーを作成*/
    this.cities.map(city => {
      this.leafletService.addMarker(city.name, city.weather, city.location.lat, city.location.lon);
    });
  }
 :
 :
```

###画面の動きを確認
`ng serve`でサーバを起動して動作させてみる

①初画面(ダッシュボード)
![2019-10-13_11h03_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/18dde3f6-a08a-5521-23c7-588231dd24d8.png)

②都市登録画面
![2019-10-13_11h03_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/2ed16417-a938-03fb-6c64-a1211fa7cfc8.png)

③初画面(登録後)
![2019-10-13_11h03_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/1d9ed0f4-4660-e798-e183-475878292174.png)

(問題発生)
都市が登録されているが、マーカーが出ない。。。
登録処理とダッシュボードに戻る処理が非同期で実行されているため、
登録処理が終わる前に画面遷移＆地図・マーカーの描画が行われているようだ
※登録処理はOpenweathermapへの問い合わせがあるため遅い

一度別画面に遷移し、戻ってくるとマーカーが描画される
![2019-10-13_11h07_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315491/f5ed33e6-f8f5-d91a-e875-68e5c7189c7f.png)

とりあえず今回はここまで

##次回の予定
　サービスが持っているプロパティ(都市登録)の更新を検知して
　マーカー描画処理を呼び出す仕組みを作る
　(Subjectとか使えばできるはず)

　※関連記事
　　[①Angular環境構築とひな形の生成](https://qiita.com/ryo_1050/items/71f0d5dd56b121c64b84)
　　[②コンポーネントとサービスの追加(登録画面)](https://qiita.com/ryo_1050/items/5ecf0ffa4c1b4d8cf940)
　　[③OpenweathermapのAPIを叩く](https://qiita.com/ryo_1050/items/578903a80653c92aa4cf)
　　④Leafletで地図を表示する
　　[⑤サービスが持つプロパティの更新を検知する](https://qiita.com/ryo_1050/items/eca6a032d714a4e24f8b)