HERE Maps API for JavaScriptでGeoJSONのポリゴンを表示させる / はじめましてHERE

この記事では[HERE Maps API for JavaScript](https://developer.here.com/develop/javascript-api)を使って地図にポリゴンを表示させてみようと思います。

私が所属しているマップマーケティングではエリアマーケティングで利用するポリゴンや統計データの作成を行っています。ポリゴンは行政界(都道府県～町丁目)やメッシュを扱うことが多いです。[TerraMap API](https://www.mapmarketing.co.jp/terramap-api?grid=qiita&utm_source=qiita.com&utm_medium=referral&utm_campaign=qiita_here_geojson_76207ebde9f2816c682f_01)というWeb APIでデータの配信も行っています。
これらのデータを使って地図アプリケーションを作成する際にポリゴンの表示がほぼほぼ必須になります。HERE Maps API for JavaScriptでGeoJSONの町丁目ポリゴンの表示方法を知りたいというのが今回試してみた動機です。

今回は地図の表示から始まり、GeoJSONのポリゴンを描画するところまでをゴールとします。私はこれまでHEREを使ったことがありません。はじめてのHEREです。

## HEREについて
HEREさんのオーガニゼーションがあるのでそちらを見てもらうのがはやいと思います。

https://qiita.com/organizations/heremaps

こちらも見ていただくとより分かりやすいです。

https://qiita.com/kekomat/items/b381f134cd1faf5b156c

### HERE Maps API for JavaScript
HERE Maps API for JavaScriptはHEREマップを使ってブラウザでアプリケーション開発を行うためのJavaScriptのAPIです。

### 利用の流れ
HEREマップを使って開発を行うにはアカウント登録が必要です。
アカウント登録　→　HEREプラットフォームで Maps API for JavaScript用のアプリ追加（APIキーゲット）　→　コード実装
の流れになります。

アカウント登録やアプリ追加については以下の記事が詳しいです。（私自身こちらの記事に助けられました）

https://qiita.com/kekomat/items/d2e29bac84a27c1ec521

https://qiita.com/asahina820/items/5e28cb30a3d0a89438e4

## 地図を表示させる
何はともあれ地図を表示させないことには始まりません。
公式のドキュメントをベースに書いてみます。[クイックスタート](https://jp.developer.here.com/documentation/maps/3.1.45.1/dev_guide/topics/quick-start.html)のページのコード例をベースにしてみました。
``` html
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    <script src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
    type="text/javascript" charset="utf-8"></script>
    <script src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
    type="text/javascript" charset="utf-8"></script>
  </head>
  <body>
    <div style="width: 640px; height: 480px" id="mapContainer"></div>
    <script>
      // Initialize the platform object
      const platform = new H.service.Platform({
        'apikey': 'YOUR-API-KEY'
      });

      // Obtain the default map types from the platform object
      const maptypes = platform.createDefaultLayers();

      // Instantiate (and display) the map
      const map = new H.Map(
        document.getElementById('mapContainer'),
        maptypes.vector.normal.map,
        {
          zoom: 10,
          center: {lat: 35.68216, lng: 139.76593}
        });
    </script>
  </body>
</html>

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/e30967b7-d21c-ae94-0535-e70399318ed4.png)

### 日本向けの地図にする
[日本のデータを使い始める](https://jp.developer.here.com/documentation/maps/3.1.45.1/dev_guide/topics/get-started-japan.html)のページを参考にして日本向けの地図にしてみました。
ついでにズームやパンなどの地図イベントの有効化とズームコントロールの表示も行いました。
``` html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Here Map API for JavaScript サンプル</title>
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script>
    <!-- マップイベント用のjs -->
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script>
    <!-- UIコントロール用のjs -->
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-ui.js" ></script>
    <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" />     
  </head>
  <body>
    <div id="mapContainer"></div>
    <script type="module" src="/main.js"></script>
  </body>
</html>
```
``` js
import './style.css'

const platform = new H.service.Platform({
  'apikey': 'YOUR-API-KEY'
});

// 日本に対応した地図にする
const omvService = platform.getOMVService({path:  'v2/vectortiles/core/mc'});
const baseUrl = 'https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/';
const style = new H.map.Style(`${baseUrl}normal.day.yaml`, baseUrl);
const omvProvider = new H.service.omv.Provider(omvService, style);
const omvlayer = new H.map.layer.TileLayer(omvProvider, {max: 22});

// mapオブジェクト生成と地図表示
const map = new H.Map(
  document.getElementById('mapContainer'),
  omvlayer,
  {
    zoom: 15,
    center: {lat: 35.68216, lng: 139.76593}
  });

// パンやズームなどの地図イベントを有効にする
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

// ズームコントロールをセット
const ui = H.ui.UI.createDefault(map, omvlayer);

```
すごく見やすい地図になりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/df2bd2b7-e603-5de0-8531-ec5913c2be00.png)

## GeoJSONのポリゴンを表示させる その1
ここからが本題です。GeoJSONのポリゴンを表示させていきます。
[Display GeoJSON Data](https://developer.here.com/documentation/examples/maps-js/data/display-geojson-on-map)を参考にして、東京駅周辺の町丁目ポリゴンを表示させてみました。
[H.data.geojson.Readerクラス](https://www.here.com/docs/bundle/maps-api-for-javascript-api-reference/page/H.data.geojson.Reader.html)を使ってGeoJSONのファイルを読み込んで表示させる方法です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/9eca7c2d-e364-f957-dd3f-adbbd72ddeff.png)

``` html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Here Map API for JavaScript サンプル</title>
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script>
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-ui.js" ></script>
    <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" />
    <!-- mapsjs-data.jsを追加 -->
    <script type="text/javascript" src="https://js.api.here.com/v3/3.1/mapsjs-data.js"></script>
  </head>
  <body>
    <div id="mapContainer"></div>
    <script type="module" src="/main.js"></script>
  </body>
</html>
```
``` js
import './style.css'

const platform = new H.service.Platform({
  'apikey': 'YOUR-API-KEY'
});

// 日本に対応した地図にする
const omvService = platform.getOMVService({path:  'v2/vectortiles/core/mc'});
const baseUrl = 'https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/';
const style = new H.map.Style(`${baseUrl}normal.day.yaml`, baseUrl);
const omvProvider = new H.service.omv.Provider(omvService, style);
const omvlayer = new H.map.layer.TileLayer(omvProvider, {max: 22});

// mapオブジェクト生成と地図表示
const map = new H.Map(
  document.getElementById('mapContainer'),
  omvlayer,
  {
    zoom: 15,
    center: {lat: 35.68216, lng: 139.76593}
  });

// パンやズームなどの地図イベントを有効にする
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

// ズームコントロールをセット
const ui = H.ui.UI.createDefault(map, omvlayer);

// H.data.geojson.ReaderでGeoJSONファイルを読み込む
const reader = new H.data.geojson.Reader('area.json', {
  style: function (mapObject) {
    if (mapObject instanceof H.map.Polygon) {
      mapObject.setStyle({
        fillColor: 'rgba(255, 0, 0, 0.3)',
        strokeColor: 'rgba(0, 0, 255, 0.2)',
        lineWidth: 3
      });
    }
  },
  // マルチポリゴンを表示するにはdisableLegacyModeをセット！
  disableLegacyMode: true
});

// GeoJSONファイルをパースする
reader.parse();

// GeoJSONレイヤーを追加
map.addLayer(reader.getLayer());
```

## GeoJSONのポリゴンを表示させる その2
H.data.geojson.ReaderでGeoJSONのファイルを読み込む方法を試しましたが、そのほかの方法も見てみます。
- GeoJSONをJavaScriptのオブジェクトに格納したデータ
- 外部APIから取得したGeoJSONデータ
を表示させるようなケースを想定したものです。

外部APIを呼び出す際にH.data.geojson.Readerでfetchできないようなこともあると思います。
外部APIから受け取ったGeoJSONのデータをURL.createObjectURL()でURLに変換してH.data.geojson.Readerにセットしてみます。
こちらのページを参考にしました。

https://zenn.dev/mapbox_japan/articles/c7d08d14c4ed73#geojson

クリック地点から半径1km円の範囲と共有部分がある町丁目ポリゴンをGeoJSON形式で取得して表示させています。

``` js
import './style.css'

const platform = new H.service.Platform({
  'apikey': 'YOUR-API-KEY'
});

// 日本に対応した地図にする
const omvService = platform.getOMVService({path:  'v2/vectortiles/core/mc'});
const baseUrl = 'https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/';
const style = new H.map.Style(`${baseUrl}normal.day.yaml`, baseUrl);
const omvProvider = new H.service.omv.Provider(omvService, style);
const omvlayer = new H.map.layer.TileLayer(omvProvider, {max: 22});

// mapオブジェクト生成と地図表示
const map = new H.Map(
  document.getElementById('mapContainer'),
  omvlayer,
  {
    zoom: 15,
    center: {lat: 35.68216, lng: 139.76593}
  });

// パンやズームなどの地図イベントを有効にする
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));

// ズームコントロールをセット
const ui = H.ui.UI.createDefault(map, omvlayer);

// click地点から半径1000mの円と共有部分がある町丁目を取得して表示する
map.addEventListener('tap', function (evt) {
  // click地点の座標
  const coord = map.screenToGeo(evt.currentPointer.viewportX, evt.currentPointer.viewportY);
  // 円半径1000mに設定
  const radius = 1000;
  const params = {
    lat: coord.lat,
    lng: coord.lng,
    radius: radius,
  };
  const queryParams = new URLSearchParams(params);

  // 外部APIにリクエストしてGeoJSONフォーマットでレスポンスされる場合を想定
  fetch(`http://127.0.0.1:3300/?${queryParams}`)
  .then((response) => response.blob())
  .then((blob) => {
      // BlobオブジェクトをURLに変換する
      const url = URL.createObjectURL(blob);
      
      // H.data.geojson.ReaderでURLのGeoJSONを読み込む
      const reader = new H.data.geojson.Reader(url, {
        style: function (mapObject) {
          if (mapObject instanceof H.map.Polygon) {
            mapObject.setStyle({
              fillColor: 'rgba(255, 0, 0, 0.3)',
              strokeColor: 'rgba(0, 0, 255, 0.2)',
              lineWidth: 3
            });
          }
        },
        // マルチポリゴンを表示するにはdisableLegacyModeをセット！
        disableLegacyMode: true
      });

      // GeoJSONファイルをパースする
      reader.parse();

      // GeoJSONレイヤーを追加
      map.addLayer(reader.getLayer());
      URL.revokeObjectURL(url)

      // 指定した円ラインを描画する
      const circle = new H.map.Circle({lat: coord.lat, lng: coord.lng}, radius);
      circle.setStyle({
        fillColor: 'rgba(0, 0, 0, 0)', // 塗りつぶさない方法がわからなかったので完全に透過させる
        strokeColor: 'rgba(255, 0, 0, 0.5)',
        lineWidth: 3
      });
      map.addObject(circle);
    })
    .catch((error) => {
      console.log(error);
    });
});
```
![here-map.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2325378/0cb6676a-cb97-5eed-a695-b341b9992fbd.gif)

## やってみて
日本に対応した地図があることはすごく良いと思いました。よく見ると町丁目の区画も地図に表現されていますし、小田原市曽我別所あたりの細かい飛び地も表現されているようでした。
詳しくは触れてませんが、APIキーの生成などを行うHEREプラットフォームのサイトも使いやすかったです。

## 今後理解を深めたいところ
初めてHERE Maps API for JavaScriptを触ってみて、まだまだわからないことがいっぱいです。HERE Maps API for JavaScriptでやる上での基本概念の理解もできていないです。~~H.data.geojson.ReaderのdisableLegacyModeについてもわかってないので理解したいと思います。~~ ← マルチポリゴンを描画するにはdisableLegacyModeが重要でした！
また、GeoJSONのポリゴンを描画する方法としてH.data.geojson.Readerを使わない方法もあると思います。
GeoJSONをパース → H.geo.MultiPolygonを生成 → H.geo.MultiPolygonからH.map.Polygonを生成 → mapに追加
の流れでもGeoJSONのポリゴンを描画することができそうな感じがします。GeoJSONからH.geo.MultiPolygonを生成する部分でスマートな方法が思いつかなかったので今回はやめました。

### 追記
disableLegacyModeについて書きました。

https://qiita.com/4gou/items/4d75d0196d35c924e508