フリーダムの日記

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

Node.js で ArcGIS REST JS を使ってみる。

はじめに

本エントリーは Node.js Advent Calendar 2021 5日目 の記事です。

qiita.com

Node.js で Express を使用して ArcGIS が提供している ArcGIS REST JS を使用しました。 ArcGIS REST JS では、クラウドサービスとして ArcGIS Platform (PaaS) を利用して、OAuth 2.0 認証と住所検索としてジオコーディングを実装しましたので、コード含めて紹介していきたいと思います。

そもそも ArcGIS(アークジーアイエス)って何っていう方や、また、初めて聞く方々もいらっしゃるかと思います。
ArcGIS はすぐに利用可能な豊富な地図データや、地理情報を作成、分析、可視化するためのデスクトップ アプリ、モバイル アプリ、Web アプリ、クラウドサービス、サーバー製品等から構成され、システム形態や用途に応じて自由に組み合わせて導入できます。
そして、ArcGIS REST JS は、ArcGIS が提供するロケーション サービス、SaaSArcGIS Online)および オンプレミス環境(ArcGIS Enterprise)の REST API にアクセスするための JavaScript モジュール群です。また、ArcGIS REST JS は、フレームワークに依存しないため、CDN や NodeJS、さらに webpack、Rollup、Parcel などの一般的なモジュールにバンドルして使用することができます。

esri.github.io

今回開発したアプリ(ArcGIS REST JS Express Demo)は GitHub にもアップしました。 Node.js の環境があれば、アプリに必要な情報を config.json に設定して、以下のコマンドでアプリを動かすることができます。

$ npm install
$ npm start

以下はアプリの動作画面です。シンプルですが、Sign In のリンクをクリックすると、ArcGIS のログイン画面が表示されますので、ログインに成功すると以下のような認証情報と住所検索としてジオコーディングを実行するためのリンクが表示されます。

f:id:freedom0625:20211111220458p:plain

github.com

ArcGIS REST JS を利用には

ArcGIS REST JS は無償から利用することができます。ArcGIS Developer というサイトで開発者アカウントを作成することですぐに利用することができます。 ArcGIS が提供するロケーションサービス(ジオコーディングやルート検索)などを利用する場合に料金が発生します。ただし、一定枠までは無料で利用できますので、その枠を超えない限りは無償で利用できます。

developers.arcgis.com

ロケーションサービスは、API キーによる利用と OAuth 2.0 による認証で利用るすることができます。 今回は、両方使用した方法を紹介します。まず、API キーと OAuth 2.0 で認証できるようにするための準備が必要です。

ArcGIS Developer で開発者アカウントを作成後、ダッシュボードが表示されますので、API keys のメニューを選択して、New API key から API キーを作成します。すると以下の画面のように API キーが作成されます。

ArcGIS Platform API キー

次に OAuth 2.0 ですが、同様にダッシュボードで OAuth 2.0 のメニューを選択して、New application からアプリケーションを作成することで、Client ID、Client Secret、Temporary Token が作成されます。また、Redirect URLs の設定も必要になります。このあたりは、OAuth 2.0 を利用するアプリ開発であれば他のクラウドサービスも同じ手順になるかと思います。

ArcGIS Platform OAuth 2.0

その他、Usage の状況から実際に使用したロケーションサービスのリクエスト数も確認することができます。ここでは、 OAuth 2.0 の認証によるロケーションサービスのリクエスト数になります。

ArcGIS Platform 利用状況

Node.js による ArcGIS REST JS の利用

Node.js 本体のインストールや環境構築は、本家サイトを参照していただければと思います。 Node.js の環境が整ったら、ArcGIS REST JS を利用するために必要なライブラリを Pakage.json に設定します。 今回使用する ArcGIS REST JS として、認証ヘルパーの @esri/arcgis-rest-auth 、パッケージの共通メソッドとユーティリティの @esri/arcgis-rest-reques、ジオコーディングヘルパーの @esri/arcgis-rest-geocoding を指定します。フレームワークとして、express も指定します。

{
  "name": "@esri/arcgis-rest-demo-express",
  "version": "3.4.3",
  "private": true,
  "description": "Demo of @esri/arcgis-rest-* packages in an Express server",
  "author": "",
  "license": "Apache-2.0",
  "dependencies": {
    "@esri/arcgis-rest-auth": "^3.4.3",
    "@esri/arcgis-rest-request": "^3.4.3",
    "@esri/arcgis-rest-geocoding": "^3.4.3",
    "cross-fetch": "^3.0.0",
    "express": "^4.16.3",
    "express-session": "^1.17.2",
    "isomorphic-form-data": "^2.0.0",
    "session-file-store": "^1.5.0"
  },
  "scripts": {
    "start": "node server.js"
  }
}

その他、Node.js で ArcGIS REST JS の詳しい使い方については以下のドキュメントが参考になります。

esri.github.io

ArcGIS REST JS Express Demo の実装

まず、ArcGIS の認証の仕組みとして、ArcGIS identity(名前付きユーザとも呼ばれる)は、アプリケーションで利用するユーザーに対して ArcGIS Online または ArcGIS Enterprise のアカウントに認可されたコンテンツおよびサービスへのアクセスを許可する、一時的なアクセストークンを付与します。

この一時的なトークンは、OAuth 2.0 プロトコルを使用して作成されます。このトークンは、ユーザのセキュアなパスワードをアプリケーションに公開することなく、アプリケーションがユーザの代わりに動作することを許可します。

アプリケーションが ArcGIS Platform ユーザーのセキュアなコンテンツにアクセスする場合や、または ArcGIS Marketplace と呼ばれるマーケットプレイスでアプリケーションを配布する場合は、ArcGIS ID 認証を使用する必要があります。

ArcGIS REST JS は、アプリケーションで認証を処理するためのヘルパー メソッドを Node.js のサーバー環境に提供しています。今回は、OAuth2.0 サーバー対応のワークフローを使用します。サーバーサイド認証では、セッションから生成されてサーバー環境に保存されたリフレッシュ トークンを使用して、ユーザーの短命なアクセストークンを取得することができます。 OAuth 2.0 のサーバー対応のワークフローは以下のとおりです。

Server-enabled workflow

developers.arcgis.com

また、ArcGIS REST JS の Express による詳細な実装方法については、以下の Authenticate with an ArcGIS identity (server) をご覧ください。

developers.arcgis.com

server.js 部分の OAuth 2.0 によるワークフローの全体のコードは以下となります。

require("cross-fetch/polyfill");
require("isomorphic-form-data");

const express = require("express");
const session = require("express-session");
const FileStore = require("session-file-store")(session);
const app = express();

const { geocode, bulkGeocode } = require("@esri/arcgis-rest-geocoding");
const { UserSession } = require("@esri/arcgis-rest-auth");
const { ApiKey } = require('@esri/arcgis-rest-auth');
const { CLIENT_ID, SESSION_SECRET, ENCRYPTION_KEY, REDIRECT_URI } = require("./config.json");

const credentials = {
  clientId: CLIENT_ID,
  redirectUri: REDIRECT_URI
};

// API キー
const apiKey = new ApiKey({key: "AAPK4767d2b9b2d9411d9ce0001cb63bd67dIFO1Inj92v630hXNNKs36YHLT7JVLcoPW1sSiRH57oZf4uPXR0LVtHl9ApmuaTx8"});

// セッションを保存する
app.use(
  session({
    name: "ArcGIS REST JS server authentication tutorial",
    secret: SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      maxAge: 2592000000 // 30 days in milliseconds
    },
    // サーバー上のセッションをファイルとして保存するためのFileStoreを作成し、config.jsonファイルで設定した ENCRYPTION_KEY でシークレットを設定
    store: new FileStore({
      ttl: 2592000000 / 1000, // 30 days in seconds
      retries: 1,
      secret: ENCRYPTION_KEY,
      // 暗号化されてディスクに保存される前にセッションオブジェクトをシリアライズするために、sessionObj にエンコーダオプションを設定
      encoder: (sessionObj) => {
        // sessionObj is an object or string representing the session information
        if (typeof sessionObj.userSession !== "string") {
          sessionObj.userSession = sessionObj.userSession.serialize();
        }
        return JSON.stringify(sessionObj);
      },
      // デコーダーは、ユーザーが指定した暗号化キーを使ってsessionContentをデコード
      decoder: (sessionContents) => {
        // sessionContents is the full content of the session on
        if (!sessionContents) {
          return { userSession: null };
        }

        const sessionObj = typeof sessionContents === "string" ? JSON.parse(sessionContents) : sessionContents;
        
        if (typeof sessionObj.userSession === "string") {
          sessionObj.userSession = UserSession.deserialize(sessionObj.userSession);
        }

        return sessionObj;
      }
    })
  })
);

app.get("/sign-in", (req, res) => {
  UserSession.authorize(credentials, res);
});

app.get("/sign-out", (req, res) => {
  req.session.destroy();
  res.redirect("/");
});

app.get("/authenticate", async (req, res) => {
  req.session.userSession = await UserSession.exchangeAuthorizationCode(
    {
      clientId: CLIENT_ID,
      redirectUri: REDIRECT_URI
    },
    req.query.code //The code from the redirect: exchange code for a token in instaniated user session.
  );

  req.session.save((err) => {
    res.redirect("/");
  });

});

app.get("/", (req, res) => {
  //Redirect to homepage.
  if (req.session.userSession) {
    res.send(`
    <h1>Hi ${req.session.userSession.username}<h1>
    <pre><code>${JSON.stringify(req.session.userSession, null, 2)}</code></pre>
    <a href="/addressSearch">住所検索のテスト(東京都千代田区丸の内1丁目で住所検索)<a>
    <br/>
    <a href="/sign-out">Sign Out<a>
  `);
  } else {
    res.send(`<a href="/sign-in">Sign In<a>`);
  }
});

次に住所検索部分です。 ArcGIS のロケーションサービスとして提供している住所検索のジオコーディングの詳しい説明については以下をご覧ください。 developers.arcgis.com

今回試しに利用したジオコーディングの ArcGIS REST API は、バッチジオコーディングの bulkGeocode と geocode を使用しました。bulkGeocode は数件の住所に対してジオコーディング処理を行い、geocode は1件の住所に対してのジオコーディングを行います。

API の詳しい使い方については以下をご覧ください。

bulkGeocode | API Reference | ArcGIS REST JS

geocode | API Reference | ArcGIS REST JS

ジオコーディングの実行に必要となる認証パターンとして、OAuth 2.0 と API キーによる認証のパターンを実装しました。

app.get("/addressSearch", (req, res) => {

  if (req.session.userSession) {

    /****** ジオコーディング  
     * https://esri.github.io/arcgis-rest-js/api/geocoding/geocode/
     * Geocode (non-stored) 20,000 Geocodes free then $0.5 per 1,000 Geocodes
    */
    geocode({
      address: "東京都千代田区丸の内1丁目",
      //authentication: req.session.userSession // OAuth 2.0 の場合
      authentication: apiKey // API キーの場合
    })
      .then((response) => {
        console.log("---------------result-------------")
        console.log(JSON.stringify(response));
        res.send(`${JSON.stringify(response)}`);
        //response.candidates[1].location; // => { x: -77.036533, y: 38.898719, spatialReference: ... }
      });

    /****** バッチジオコーディング  
     * https://esri.github.io/arcgis-rest-js/api/geocoding/bulkGeocode/
     * Geocode (stored) $4 per 1,000 Geocodes 
    */
    /*
    const addresses = [
      { "OBJECTID": 1, "address": "東京都千代田区丸の内1丁目" },
      { "OBJECTID": 2, "address": "東京都世田谷区北沢2-36-14" }
    ];

    bulkGeocode({
      addresses, 
      // authentication: req.session.userSession  // OAuth 2.0 の場合
      authentication: apiKey // API キーの場合
    })
      .then((response) => {
        console.log("---------------result-------------")
        console.log(JSON.stringify(response));
        res.send(`${JSON.stringify(response)}`);
        //response.locations[0].location; // => { x: -117, y: 34, spatialReference: { wkid: 4326 } }
      });
    */
  }

});

app.listen(3000, () => {
  console.log(`Visit http://localhost:3000/ to get started!`);
});

全体のソースは、GitHub にもアップしていますのでご覧いただければと思います。 github.com

さいごに

今回は、Node.js で ArcGIS REST JS の利用した例を紹介しましたが、それ以外にも多くの組み合わせで利用することができます。サーバーサイドではなく、クライアントサイドで完結したい場合にも利用することができますので、システムやアプリの利用用途に応じて利用することができます。 また、ArcGISREST API も提供していますので、直接 REST APIcurl などのコマンドでも利用することができますし、他のプログラム言語で組み込んで利用することも可能です。

developers.arcgis.com

ArcGIS で提供されているロケーションサービスは従量制となっており、各ロケーションサービスの利用料については以下をご覧いただければと思います。

developers.arcgis.com