GIS奮闘記

現役GISエンジニアの技術紹介ブログ。主にPythonを使用。

スポンサーリンク

TypeScript を使って Web マップにカスタムウィジェットを配置してみよう!

さて、本日は TypeScript を使って Web マップにカスタムウィジェットを配置してみようと思います。みなさんは TypeScript を使っていますでしょうか?おそらく日本ではまだ馴染みのない言語の一つかもしれません。ただ、近年人気が高まってきており、近い将来はメジャーな言語の一つになるのではないかと思っています。

TypeScript とは

TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースのプログラミング言語である。TypeScript は JavaScriptに対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセットとなっている(Wikipedia より抜粋)。

簡単に言うと JavaScript の進化版といったところでしょうか。そして、TypeScript で書いたコードをコンパイルすると JavaScript のコードに変換することができます。もっと細かいことを知りたい方は以下 Web サイトに記載されているので、読んでみてください。

qiita.com

なぜ TypeScript を使ってみようと思ったのか

以下のように JavaScript → TypeScript への流れに取り残されないようにするためでしょうか。やはりエンジニアの端くれとして新しいことにチャレンジし続けたいですね。

  1. Google が TypeScript を社内の標準言語に採用するなど、TypeScript の存在感は年々高まってきているため
  2. 米国Esri が TypeScript を推しているため(将来的には 全てのコードが TypeScript になるかも?!)
  3. 個人的に将来を見据えて新しいことに挑戦したかったため

準備

まずは環境を準備しなくてはいけませんね。こちらは ESRIジャパンさんのブログなのですが、TypeScript の環境構築から簡単なサンプルコードを載せてくれています。こちらを参考にさせてもらいます。

community.esri.com

また、TypeScript でのカスタムウィジェット作成に関しては、ESRIジャパンさんが公開しているカスタムウィジェット ハンズオンを参考にしてみました。

github.com

今回チャレンジすること

概観図をカスタムウィジェットとして作ってみようと思います。概観図とは以下の画像の右上にある地図のことです。メインのマップの概観を表示させるものですね。

f:id:sanvarie:20200517151318p:plain

概観図の JavaScript 用のサンプルコードが以下に公開されているので、今回はこれを TypeScript に書き換えてみようと思います。 developers.arcgis.com

作ったもの

上記に記載したリソースをもとに以下のような構成でカスタムウィジェットつきのWeb マップを作ってみました。

f:id:sanvarie:20200517151959p:plain

サンプルコード

「OverView.tsx」「index.html」のサンプルコードです。

OverView.tsx

import Map from "esri/Map";
import MapView from "esri/views/MapView";
import Graphic from "esri/Graphic";
import * as watchUtils from "esri/core/watchUtils";
import {subclass, declared, property} from "esri/core/accessorSupport/decorators";
import Widget = require("esri/widgets/Widget");
import { renderable, tsx } from "esri/widgets/support/widget";

@subclass("esri.widgets.OverView")
class OverView extends declared(Widget) {

  constructor() {
    super();
    this._overViewChange = this._overViewChange.bind(this);
  }

  @property()
  @renderable()
  map: Map;

  @property()
  @renderable()
  mainView: MapView;

  @property()
  @renderable()
  mapView: MapView;

  @property()
  @renderable()
  graphic: Graphic;
  
  // postInitialize メソッドの追加
  postInitialize() {

    var overviewMap = new Map({
      basemap: "topo"
    });

    this.mapView = new MapView({
      container: "overviewDiv",
      map: overviewMap,
      center: [139.740286, 35.678601],
      zoom: 10,
      constraints: {
        rotationEnabled: false
      }
    });

    this.mapView.ui.components = [];
    
    var graphicSymbol = {
      type: "simple-fill",
      color: [100, 0, 0, 0.5]
    };
  
    this.graphic = new Graphic({
      geometry: null,
      symbol: graphicSymbol
    });
    
    this.mapView.graphics.add(this.graphic);

    watchUtils.init(this, "mainView.extent", () => this._overViewChange());
  }

  private _overViewChange () {
    if (this.mainView.stationary) {
      this.mapView
        .goTo({
          center: this.mainView.center,
          scale:
          this.mainView.scale *
            2 *
            Math.max(
              this.mainView.width / this.mapView.width,
              this.mainView.height / this.mapView.height
            )
        })
        .catch(function(error) {
          if (error.name != "view:goto-interrupted") {
            console.error(error);
          }
        });
    }

    this.graphic.geometry = this.mainView.extent;
  }

  render() {
    return (
      <div
      bind={this}>
      </div>
    );
  }
}

export = OverView;

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="概観図"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>Overview map - 4.15</title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }

      #overviewDiv {
        position: absolute;
        top: 12px;
        right: 12px;
        width: 600px;
        height: 200px;
        border: 1px solid black;
        z-index: 1;
        overflow: hidden;
      }
    </style>

    <link
      rel="stylesheet"
      href="https://js.arcgis.com/4.15/esri/themes/light/main.css"
    />
    <script>
    /*
    dojoConfig の設定
    */
    var locationPath = location.pathname.replace(/\/[^\/]+$/, "");
    window.dojoConfig = {
        packages: [
        {
            name: "app",
            location: locationPath + "/app"
        }
        ]
    };
    </script>
    <script src="https://js.arcgis.com/4.15/"></script>

    <script>
      require([
        "esri/Map",
        "esri/views/MapView",
        "app/OverView"
      ], function(Map, MapView, OverView) {
        
        var mainMap = new Map({
          basemap: "hybrid"
        });


        var mainView = new MapView({
          container: "viewDiv",
          map: mainMap,
          center: [139.740286, 35.678601],
          zoom: 15
        });

        mainView.when(function() {
          /*
          ウィジェットのインスタンス化
          */

          overView = new OverView({
            map:mainMap,
            mainView:mainView
          });

      mainView.ui.add(overView, "top-right");
      });
    });
    </script>
  </head>
  <body>
    <div id="viewDiv" class="esri-widget"></div>
    <div id="overviewDiv"></div>
  </body>
</html>

TypeScript に関してはほとんど素人同然なので変な書き方をしているかもしれませんが、完成させることができたのでひとまずほっとしております。

以下のように概観図がマップに配置されていることが確認できました。

f:id:sanvarie:20200517153044p:plain

メインのマップを移動させると概観図の赤枠(メインのマップの表示箇所)も移動することがわかります。

f:id:sanvarie:20200517153136p:plain

TypeScript を使ってみた感想

最初は書き方や TypeScript 独特のコンセプトに戸惑うこともあったのですが、慣れの問題かと思います。色々使ってみる中で感じたのは、やはり型が使えるというのが JavaScript に比べて大きなアドバンテージかなと思いました(今回はあまりそのメリットを享受していないのですが)。まだまだ知らないことも多いのですが、色々触りながら勉強してみようと思います。本日は以上です。