フリーダムの日記

GIS(地理情報システム)を中心に技術的なことを書いています。

Vue 3 で ArcGIS API for JavaScript(地図アプリ API)を使用してみる。

はじめに

本エントリーは Vue Advent Calendar 2022 3日目の記事です。

qiita.com

今回は VueJS と地図アプリ開発APIArcGIS API for JavaScript を使用して簡単な地図アプリと印刷機能を作成しました。Vue と ArcGIS API for JavaScript については EsriArcGIS API for JavaScript の開発者でもある Rene Rubalcava 氏のブログで以下の記事を参考にしました。

odoe.net

ArcGIS API for JavaScriptES モジュールとして提供されていますので、Vue や React などの Web フレームワークと一緒に使用することができます。また、今回は Web ページ デザイン用のコンポーネント群の Calcite Design System や sass も利用してみました。

Vuejs And ArcGIS API for JavaScript

作成したサンプルアプリは、GitHub でも公開していますので、興味のある方は触ってみてください。

開発環境の構築

はじめに VueJS を利用するために vue-cli を使用してインストールを行います。

npm install -g @vue/cli
# OR
yarn global add @vue/cli

インストールが完了したら次にプロジェクトを作成します。

vue create my-project
# OR
vue ui

Vue のプロジェクトを作成する場合に Vue のバージョンを聞かれるので、今回は Vue 3 を選択しました。

Vue CLI v5.0.8
? Please pick a preset: (Use arrow keys)
> Default ([Vue 3] babel, eslint)       
  Default ([Vue 2] babel, eslint)       
  Manually select features

問題がなければ Vue のプロジェクトが作成されます。 作成したプロジェクトに移動して npm run serve で実行するとデフォルトの Vue のプロジェクトが表示されます。これで Vue の環境構築は完了です。

 $ cd my-project
 $ npm run serve

Vue

最後に ArcGIS API for JavaScript や Calcite Design System などをインストールします。

npm i @arcgis/core @esri/calcite-components copy-webpack-plugin sass sass-loader resolve-url-loader

これですべての開発環境の準備ができました。 いよいよ ArcGIS 側の実装に入っていきます。

ArcGIS 側の実装

地図部分の実装は、ArcGIS API for JavaScript を使用します。src/data/map.js を作成し、独自のモジュールとして処理するように作成します。ArcGIS API for JavaScript の実装方法については、以下のチュートリアルを参照して下さい。

developers.arcgis.com

また、カスタムの sass を使用するため、ArcGIS API for JavaScript 用のアセットをローカルにコピーする必要があります。通常は CDN からスタイルやアセットを読み込めがいいのですが、今回はローカスから参照するようにしました。

ArcGIS API for JavaScript による src/data/map.js のコードは、以下になります。

// src/data/map.js
import config from '@arcgis/core/config';
import MapView from '@arcgis/core/views/MapView';
import WebMap from '@arcgis/core/WebMap';
import Expand from '@arcgis/core/widgets/Expand';
import Legend from '@arcgis/core/widgets/Legend';
import Print from '@arcgis/core/widgets/Print';

config.assetsPath = './assets';

export const webmap = new WebMap({
    portalItem: {
        id: 'f2e9b762544945f390ca4ac3671cfa72'
    }
});

export const view = new MapView({
    container: 'viewDiv',
    map: webmap
});

export const legend = new Expand({
    content: new Legend({
        view,
        style: 'card'
    }),
    view,
    expanded: true
});
view.ui.add(legend, 'bottom-left');

/**
 * Assigns the container element to the View
 * @param container
 */
export const initialize = (container) => {
    view.container = container;
    view.when()
        .then(() => {
            console.log('Map and View are ready');
        })
        .catch(error => {
            console.warn('An error in creating the map occurred:', error);
        });
};

export function initializePrint(container) {
    const print = new Print({ view, container });
    return print;
}

次に WebMap.vue ですが、map で実装した地図を表示するために使用される要素を取得する initialize() メソッドを使用します。そして、このモジュールは VueJS コンポーネント内で使用することができます。

// src/components/WebMap.vue
<template>
    <div class="map-div"></div>
</template>

<script>
export default {
    name: 'WebMap',
    async mounted() {
        const app = await import('../data/map');
        app.initialize(this.$el);
    }
};
</script>

<style lang="scss">
</style>

印刷機能の表示は Print.vue にて実装しています。

<template>
    <div class="print-div"></div>
</template>

<script>
export default {
    name: 'Print',
    async mounted() {
        const app = await import('../data/map');
        app.initializePrint(this.$el);
    }
};
</script>

<style lang="scss">
.print-div {
    position: absolute;
    right: 0;
    z-index: 999;
    width: 300px;
    height: 450px;
}
</style>

Sass

ここで sass を触っていきます。独自のカスタム sass を構築することの素晴らしい点は、アプリケーションに必要な sass の部分を決定できることです。ArcGIS API for JavaScript の各ウィジェットには、独自の sass ファイルが付属しています。また、これらのファイルをビルドに含めるかどうかを決定するために、一連のフラグを設定することができます。つまり、ウィジェットを使用しない場合、ビルドの CSS からそのスタイルを除外することができます。

// src/components/WebMap.vue
<style lang="scss">
// Widgets (sorted alphabetically)
$include_AreaMeasurement2D: false !default;
$include_AreaMeasurement3D: false !default;
$include_Attachments: false !default;
$include_BasemapGallery: false !default;
$include_BasemapLayerList: false !default;
$include_BasemapToggle: false !default;
$include_BinaryColorSizeSlider: false !default;
$include_Bookmarks: false !default;
$include_BuildingExplorer: false !default;
$include_ButtonMenu: false !default;
$include_ClassedColorSlider: false !default;
$include_ClassedSizeSlider: false !default;
$include_Compass: false !default;
$include_ColorPicker: false !default;
$include_ColorSizeSlider: false !default;
$include_ColorSlider: false !default;
$include_CoordinateConversion: false !default;
$include_DatePicker: false !default;
$include_Daylight: false !default;
$include_Directions: false !default;
$include_DirectLineMeasurement3D: false !default;
$include_DistanceMeasurement2D: false !default;
$include_Editor: false !default;
$include_ElevationProfile: false !default;
$include_Feature: false !default;
$include_FeatureContent: false !default;
$include_FeatureMedia: false !default;
$include_FeatureForm: false !default;
$include_FeatureTable: false !default;
$include_FeatureTemplates: false !default;
$include_FloorFilter: false !default;
$include_Grid: false !default;
$include_HeatmapSlider: false !default;
$include_Histogram: false !default;
$include_HistogramRangeSlider: false !default;
$include_IdentityForm: false !default;
$include_IdentityModal: false !default;
$include_ItemList: false !default;
$include_LayerList: false !default;
$include_LineOfSight: false !default;
$include_Measurement: false !default;
$include_NavigationToggle: false !default;
$include_OpacitySlider: false !default;
$include_Print: false !default;
$include_ScaleBar: false !default;
$include_ScaleRangeSlider: false !default;
$include_Search: false !default;
$include_ShadowAccumulation: false !default;
$include_SizeSlider: false !default;
$include_Sketch: false !default;
$include_Slice: false !default;
$include_Slider: false !default;
$include_SnappingControls: false !default;
$include_Spinner: false !default;
$include_Swipe: false !default;
$include_TableList: false !default;
$include_TimePicker: false !default;
$include_TimeSlider: false !default;

$icomoon-font-path: "./assets/esri/themes/base/icons/fonts" !default;
$calcite-fonts-path: "./assets/esri/themes/base/fonts/fonts/" !default;
@import "~@arcgis/core/assets/esri/themes/light/main.scss";

.map-div {
    padding: 0;
    margin: 0;
    width: 100%;
    height: 100%;
}
</style>

API には沢山のウィジェットと呼ばれる機能群があります。その他、いくつかの重要な変数がここにリストアップされています。これらは、フォントがコピーされたときにフォントを探す場所を指定します。

$icomoon-font-path: "./assets/esri/themes/base/icons/fonts" !default;
$calcite-fonts-path: "./assets/esri/themes/base/fonts/fonts/" !default;

これで、このコンポーネントを別のコンポーネントで使用することができます。このコンポーネントは Calcite レイアウトコンポーネントを使用し、マップを囲みます。たくさんのコンポーネントがあります。

<template>
  <calcite-shell>
    <slot name="shell-header">
      <header class="header">
        <calcite-icon icon="beaker" scale="m" aria-hidden="true"></calcite-icon>
        <h2 class="heading">ArcGIS Vue and Calcite</h2>
      </header>
    </slot>
    <WebMap />
  </calcite-shell>
</template>

<script>
import {
  applyPolyfills,
  defineCustomElements
} from "@esri/calcite-components/dist/loader";
import WebMap from './components/WebMap.vue'

applyPolyfills().then(() => {
  defineCustomElements(window);
});

export default {
  name: 'App',
  components: {
    WebMap
  }
}
</script>
<style lang="scss">
@import "~@esri/calcite-colors/dist/colors"; // calcite colors
</style>

注意点としては、VueJS は slot 属性を好まないのですが、slot を要素のように扱うことができます。calcite-components は Stencil で作られた Web コンポーネントなので、いくつかの polyfills と、それを読み込むための loader が存在します。ここではすべての sass スタイルを追加していませんが、calcite の sass 変数を使用したい場合は、色の変数をインポートします。

ArcGIS API for JavaScript と calcite で VueJS を使用するには、これでほぼ完了です。すべての sass を正しくコピーしてビルドするために、もう少し設定が必要です。

Build (ビルド) の設定

VueJS は、内部で webpack を使用しています。他の cli ツールとは異なり、webpack のビルドにフックし、config によって拡張する方法を提供しています。使用したい webpack プラグインconfigureWebpack で追加することができます。

// vue.config.js
const CopyPlugin = require('copy-webpack-plugin');

const jsapi = '@arcgis/core';

module.exports = {
  configureWebpack: {
    plugins: [
      new CopyPlugin({
        patterns: [
          // calcite assets
          {
            context: 'node_modules',
            from: '@esri/calcite-components/dist/calcite',
            to: './',
          },
        // arcgis assets
        {
          context: 'node_modules',
          from: `${jsapi}/assets`,
          to: './assets',
          globOptions: {
            // ignore the webscene spec folder, sass files,
            ignore: ['**/webscene/spec/**', '**/*.scss', '**/*.css'],
          },
        },
        ]
      }),
    ]
  },
  chainWebpack: (config) => {
    ['vue-modules', 'vue', 'normal-modules', 'normal'].forEach((rule) => {
      config.module
        .rule('scss')
        .oneOf(rule)
        .use("resolve-url-loader")
        .loader("resolve-url-loader")
        .options({
          sourceMap: true,
          // eslint-disable-next-line no-unused-vars
          join: (_rul_uri, _rul_base) => {
            // args must be included
            return (arg) => {
              return arg.bases.value.includes("@arcgis/core")
                ? arg.uri.replace("../", "./assets/esri/themes/")
                : arg.uri;
            };
          },
        })
        .before("sass-loader")
        .end()
        .use("sass-loader")
        .loader("sass-loader")
        .tap((options) => ({ ...options, sourceMap: true }))
        .end()
        .use('css-loader')
        .loader('css-loader')
        .tap((options) => ({ ...options, url: false, importLoaders: 2 }))
        .end()
    });
  },
  css: {
    extract: {
      filename: '[name].css',
      chunkFilename: '[name].css',
    },
  },
};

ArcGIS 側の実装はこれで完了ですので、以下のコマンドで地図の動きが確認できるかと思います。

 $ npm run serve

最後は Web アプリをデプロイするためにビルドを実行します。

 $ npm run build

そうすると dist フォルダ内にビルドされたコンテンツが生成されますので、 その dist を任意の静的ファイル サーバーにデプロイすることができます。詳細については、以下の Vue のガイドが参考になります。

cli.vuejs.org

ビルドの前に vue.config.js で publicPath を設定する必要もあります。今回 GitHubリポジトリが vuejs で作成しているため、以下のように vuejs を設定しています。

  publicPath: process.env.NODE_ENV === 'production'
    ? '/vuejs/'
    : '/',

簡単ですが、GitHub Pages に公開するため GitHub へは以下のコマンドでデプロイしました。

cd dist  
git init
git add -A
git commit -m 'deploy'
git branch -M main
git remote add origin https://github.com/valuecreation/vuejs.git
git push -u origin main

実はデプロイ後に GitHub Pages で確認すると CSS のパスが間違っており、アイコンなどの参照で 404 エラーが発生していました。そのため、以下のように app.css ファイルで assets で始める場所のパスをすべて修正しました。

url(../../assets/esri/themes/base/fonts/fonts/e388ac99-8c6a-4451-8690-1d15b4d45adb.woff) 

↓ 修正

url(./assets/esri/themes/base/fonts/fonts/e388ac99-8c6a-4451-8690-1d15b4d45adb.woff) 

今回作成したプロジェクトは以下にまとめてあります。 github.com

まとめ

Vue は初めて触ってみました。見よう見まねで触ってみて、ビルドとデプロイまではできました。実際に Vue を使って Web アプリを開発する場合は、Vue に関してはまだ理解できていないので、Vue そのものの理解は必要だなと感じました。Vue に関しては日本語でもガイドは多く提供されていますので、時間を見つけて勉強していきたいと思います。

ArcGIS API for JavaScript は ES モジュールとして提供されていますので、Vue に限らず他のフレームワークでも利用することができます。以下のガイドや GitHub にも公開されていますので、ぜひ、触ってみていただけるとうれしいです。

developers.arcgis.com

github.com