GIS奮闘記

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

スポンサーリンク

Python で CSV を XML に変換する方法

さて、本日は Python で CSV を XML に変換する方法について書いてみようと思います。自粛ムードが高まってきていてなかなか外出はできないかと思いますが、休日にコーディングをするチャンスでもありますね。自分ももっと勉強しなければならないので、これを機にコーディングする時間を増やせればと思います。

使用する CSV

前回エントリーで使用した prefectures.csv を使ってみようと思います。興味のある方はぜひ前回のエントリーを読んでみてください。

f:id:sanvarie:20200411121747p:plain

www.gis-py.com

出力する XML

以下のような形で XML を出力しようと思います。

<root>
    <date>
        <year>2020</year>
        <month>4</month>
        <day>11</day>
    </date>
    <prefecture>東京都</prefecture>
    <info>
        <cases>500</cases>
        <hospitalized>100</hospitalized>
        <released>200</released>
        <death>10</death>
    </info>
</root>

使用するライブラリ

Pandas と ElementTree を使用します。

実行環境

Windows10 64bit
Python 3.6.6

サンプルコード

prefectures.csv を XML に変換するサンプルです。

# -*- coding: utf-8 -*-
import pandas as pd
import xml.etree.ElementTree as et

def csv2xml():
    input_file = r"D:\data\csv\prefectures.csv"
    output_file = r"D:\data\csv\prefectures.xml"

    # CSV 読込
    df = pd.read_csv(input_file)

    # ルートを作成
    root = et.Element("root")
    for i, row in df.iterrows():

        # 公表年、公表月、公表日の要素を作成
        child_root_date = et.SubElement(root, "date")
        year = et.SubElement(child_root_date, 'year')
        month = et.SubElement(child_root_date, 'month')
        day = et.SubElement(child_root_date, 'day')

        # 都道府県の要素を作成
        child_root_prefecture = et.SubElement(root, "prefecture")

        # 患者数(2020年3月28日からは感染者数)、現在は入院等、退院者、志望者の要素を作成
        child_root_info = et.SubElement(root, "info")
        cases = et.SubElement(child_root_info, 'cases')
        hospitalized = et.SubElement(child_root_info, 'hospitalized')
        released = et.SubElement(child_root_info, 'released')
        death = et.SubElement(child_root_info, 'death')

        # 各要素に値をセットする
        for iCol, column in df.iteritems():

            if iCol == "公表年":
                year.text = str(row[iCol])
            elif iCol == "公表月":
                month.text = str(row[iCol])
            elif iCol == "公表日":
                day.text = str(row[iCol])
            elif iCol == "都道府県":
                child_root_prefecture.text = str(row[iCol])
            elif iCol == "患者数(2020年3月28日からは感染者数)":
                cases.text = str(row[iCol])
            elif iCol == "現在は入院等":
                hospitalized.text = str(row[iCol])
            elif iCol == "退院者":
                released.text = str(row[iCol])
            elif iCol == "死亡者":
                death.text = str(row[iCol])

    # XMLを出力
    et.ElementTree(root).write(output_file, encoding="utf-8")

if __name__ == '__main__':
    csv2xml()

結果

想定通りの形で XML が出力されました。

f:id:sanvarie:20200411132917p:plain

さいごに

最初は少してこずったのですが、それ以外は意外とすんなりできました。ただ、もっとスマートなやり方があるような気がしてなりません。まだまだ勉強不足ですね。CSV から XML に変換するということはそんなに頻繁に行うことではないと思いますが、もし興味がある場合はぜひ試してみてください。また、今回のケースに限らず Pandas と ElementTree はよく使うライブラリですので、Python を勉強している、もしくは、勉強したいという方は別のケースでもぜひ使ってみてください。本日は以上です。

ArcGIS Pro を使ってコロナウイルスの感染拡大状況を時系列でみる方法

さて、大変な世の中になってきましたね。コロナウイルスが猛威を振るっていますが、今回はその拡大状況を可視化してみようと思います。実現するには色々な方法がとれると思うのですが、私は ArcGIS Pro を一押しします。

使用するデータ

①コロナウイルス関連のデータ
東洋経済オンラインさんが github で感染状況を CSV として公開していますので、こちらを使用させてもらいます。

github.com

公開されている CSV の中身

  1. data.json・・・json で公開しているデータ
  2. demography.csv・・・年代ごとの感染者の統計
  3. prefectures-2.csv・・・日ごとの各都道府県の陽性者数と検査人数の累計
  4. prefectures.csv・・・日ごとの各都道府県の患者数(2020年3月28日からは感染者数)の累計など
  5. summary.csv・・・日ごとのPCR検査陽性者の累計など

※今回はprefectures.csvを使用します。

②日本地図
ESRIジャパンさんが公開している「全国市区町村界データ」を使用します。

www.esrij.com

実行環境

Windows 10 64 bit
ArcGIS Pro 2.4

データ作成手順

  1. prefectures.csv を FGDB にインポート
  2. 「1」のデータを加工
  3. 全国市区町村界データを FGDB にインポート
  4. 全国市区町村界データを加工
  5. 「2」「4」のデータをテーブル結合
  6. 「5」のデータに対してシンボル設定

1. prefectures.csv を FGDB にインポート

テーブル → テーブルのジオプロを使用します。

f:id:sanvarie:20200409202213p:plain

テーブルが作成されました。 f:id:sanvarie:20200409202252p:plain

2. 「1」のデータを加工

当該データには「公表年」「公表月」「公表日」というカラムがありますが、これらを一つのカラムにまとめます。

①「年月日」という日付型のカラムを作成します。

f:id:sanvarie:20200409202715p:plain

②フィールド演算で「公表年」「公表月」「公表日」カラムの値を「年月日」カラムにセットします

f:id:sanvarie:20200409202737p:plain

③「年月日」カラムに値が設定されていることを確認してください

f:id:sanvarie:20200409202800p:plain

3. 全国市区町村界データを FGDB にインポート

フィーチャークラス → フィーチャークラスのジオプロを使用します。

f:id:sanvarie:20200409203105p:plain

全国市区町村界データがインポートされました

f:id:sanvarie:20200409203137p:plain

4. 全国市区町村界データを加工

当該データは市区町村単位なので、県ごとのポリゴンに加工します。ディゾルブのジオプロを使用します。

f:id:sanvarie:20200409203502p:plain

県ごとのポリゴンになりました。

f:id:sanvarie:20200409203530p:plain

以下エントリーで同じことを ArcPy で実行しています。興味のある方はぜひ読んでみてください。

www.gis-py.com

5. 「2」「4」のデータをテーブル結合

テーブル結合のジオプロを使用します。

f:id:sanvarie:20200409204017p:plain

二つのデータが結合されました。

f:id:sanvarie:20200409204129p:plain

6. 「5」のデータに対してシンボル設定

「5」のデータに対して以下のように等級色でシンボルを設定します。

f:id:sanvarie:20200409220634p:plain

作成したデータを時系列でみる手順

  1. 作成したデータに対して時間設定
  2. 時間の詳細設定

1. 作成したデータに対して時間設定

作成したデータ(レイヤー)を右クリック>プロパティをクリックしてください。

f:id:sanvarie:20200409220726p:plain

時間タブを以下の画像のように設定します。

f:id:sanvarie:20200409204939p:plain

2. 時間の詳細設定

時間タブ内の設定を以下の画像のようにします。

f:id:sanvarie:20200409230437p:plain

このような感じで感染が拡大していく様子がわかります。岩手県など、感染者数がゼロ(2020/4/6時点)の県もあるようですね。

f:id:sanvarie:20200409230040g:plain

薄い青・・・感染者数が少ない県
濃い青・・・感染者数が多い県

さいごに

ArcGIS Pro を使えば、簡単にコロナウイルスの感染拡大状況を時系列でみることができます。東洋経済オンラインさんが公開している他のデータを利用すれば、より詳細にデータを可視化・分析することも可能ですね。今後、ArcGIS Pro だけではなく、他の手段でも同じことができるか試してみようと思います。本日は以上です。

Python と Tesseract OCR を使って文字認識をしてみよう!

本日は Python と Tesseract OCR を使って文字認識をしてみようと思います。みなさんは OCR と聞いてピンときますか?実は私たちの周りは OCR を使用したテクノロジーで溢れかえっています。

OCR とは?

OCR(Optical Character Recognition/Reader、オーシーアール、光学的文字認識)とは、手書きや印刷された文字を、イメージスキャナやデジタルカメラによって読みとり、コンピュータが利用できるデジタルの文字コードに変換する技術のことです。この技術を利用することによって、例えば、紙に書かれている情報を毎回パソコンで手入力しなければいけない、ということはよくあるかと思います。OCR を使うことによってこの作業を自動化することができるようになります。

最近はフリーの OCR エンジン が普及していており、プログラミング で OCR を扱うことが容易になってきました。

Tesseract OCR とは?

Google が公開したオープンソースの文字認識エンジンです。こんな便利なものを公開してくれるなんて本当にありがたいですね。

Pythonで Tesseract OCRを扱うためには?

PyOCR というライブラリを使用します。pip install pyorc でダウンロードできます。

Tesseract OCR を Windows にインストールしてみる

以下のサイトで Windows 用のインストーラーをダウンロードすることができます。32 bit版と64bit 版がありますが、今回は64 bit版をダウンロードします。

github.com

インストール方法は以下のサイトを参考にしてみました。

hei.techblog.jp

環境変数の設定

環境変数にTesseract OCR のパスをセットします。「C:\Program Files\Tesseract-OCR」のような感じで追加してください(ここの設定は Tesseract OCR のインストール先によります)。

※この手順は必須ではありません。

Tesseract OCR を使ってみる

インストール後、コマンドプロンプト上 で Tesseract OCR を実行することができるようになります。

使用するデータ

前回のエントリーの一部を画像化してみました。

f:id:sanvarie:20200322105124p:plain

エントリーの内容に興味のある方はぜひ読んでみてください。

www.gis-py.com

実行方法

tesseract D:\mesh.png D:\mesh-out -l jpn

のような感じでコマンドを実行します。-l jpn は日本語で認識させるためのオプションです。

実行後、以下のようなテキストが出力されます。多少の誤認識はありますが、大体の文字はきちんと認識されていることがわかります。

f:id:sanvarie:20200321201933p:plain

ページセグメンテーションモード -psm

文字の配置の仕方は画像によって様々かと思います。ページセグメンテーションモード を指定することによってより正確に文字が認識される可能性があります。デフォルトでは psm 3が指定されます。

0 Orientation and script detection (OSD) only.
1 Automatic page segmentation with OSD.
2 Automatic page segmentation, but no OSD, or OCR.
3 Fully automatic page segmentation, but no OSD. (Default)
4 Assume a single column of text of variable sizes.
5 Assume a single uniform block of vertically aligned text.
6 Assume a single uniform block of text.
7 Treat the image as a single text line.
8 Treat the image as a single word.
9 Treat the image as a single word in a circle.
10 Treat the image as a single character.
11 Sparse text. Find as much text as possible in no particular order.
12 Sparse text with OSD.
13 Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.

例えば、画像を1行のテキストとして扱いたい場合は以下のようにします。

tesseract D:\mesh.png D:\mesh-out -l jpn --psm 7

PyOCR を使ってみる

それでは準備が整いましたので、PyOCR を使ってみようと思います。

実行環境

Windows 10 64bit
Pyton 3.6.6
Tesseract OCR 5.0.0
PyOCR 0.7.2

サンプルコード

読み込んだ画像の文字をテキストにして出力するサンプルです。

# -*- coding: utf-8 -*-
from PIL import Image
import pyocr

# OCR エンジン取得
tools = pyocr.get_available_tools()
tool = tools[0]

# 使用する画像を指定してOCRを実行
txt = tool.image_to_string(
    Image.open(r"D:\mesh.png"),
    lang="jpn",
    builder=pyocr.builders.TextBuilder()
)

# 結果をテキストで出力
with open(r"D:\ocr-result.txt", "w") as f:
    print(txt, file=f)

実行後、テキストが出力されたことが確認できました。

f:id:sanvarie:20200321220711p:plain

画像の一部のみを対象にして処理を実行する

実際に業務でこのような技術を使用する場合、認識させたいのは一部の箇所だけというケースは多いかと思います。今回はその方法も紹介します。

抽出する箇所

赤枠で囲った箇所を抽出します。

f:id:sanvarie:20200322102643p:plain

画像の特定の箇所を指定する方法

座標(左上のXY, 右下のXY)を指定することで抽出する箇所を設定します。 座標の確認はペイント上で行うことができます。

f:id:sanvarie:20200322102836p:plain

赤枠の箇所を指定する場合、以下の座標を設定する必要があります。

f:id:sanvarie:20200322103049p:plain

サンプルコード

一部の箇所を抽出したい場合は crop() メソッドを使用します。最初のサンプルとの違いはそこだけです。簡単ですね。

# -*- coding: utf-8 -*-
from PIL import Image
import pyocr

# OCR エンジン取得
tools = pyocr.get_available_tools()
tool = tools[0]

# 使用する画像を指定してOCRを実行
txt = tool.image_to_string(
    Image.open(r"D:\mesh.png").crop((20,380, 130,410)),
    lang="jpn",
    builder=pyocr.builders.TextBuilder()
)

# 結果をテキストで出力
with open(r"D:\ocr-result.txt", "w") as f:
    print(txt, file=f)

結果を確認すると狙った箇所がしっかり認識されていることがわかります。

f:id:sanvarie:20200322103219p:plain

最後に

今回、Tesseract OCR を色々使ってみたのですが、すごく簡単に使えるのに機能的にも非常に優秀な OCR エンジンだなという印象を受けました。ただ、実際に業務で使うといった場合は課題も多いのではないかと思いました。例えば、「緯度」を「旨度」と認識してしまったり、数字の扱いもあまり得意ではないことがわかりました。この辺を工夫する必要があるかと思います。また、手書きの文字を認識するのも難しいです。そのため、認識率を上げるには機械学習などと組み合わせて使用する必要が出てくるかと思います。使い方次第ではあるのですが、うまく使いこなせばすごく面白いことができそうですね。興味のある方はぜひ使ってみてください。本日は以上です。

Python で地域メッシュコードを緯度経度に変換する方法

最近は ArcGIS API for JavaScript の話が続いてしまっていたので、今回は気分を変えて Python を使って地域メッシュコードを緯度経度に変換してみようと思います。緯度経度を地域メッシュコードに変換したい方は以下のエントリーを参考にしてみてください。

www.gis-py.com

地域メッシュとは

地域メッシュ(ちいきメッシュ)とは、統計に利用するために、緯度・経度に基づいて地域をほぼ同じ大きさの網の目(メッシュ)に分けたものである。メッシュを識別するためのコードを地域メッシュコードと言う。(地域メッシュ - Wikipedia

地域メッシュは区分の方法により、大きさの異なるいくつかの区画が定められています。

・1次メッシュ: 一辺約80km.
・2次メッシュ: 一辺約10km. 1次メッシュを縦横8分割したもの。
・3次メッシュ: 一辺約1km. 2次メッシュを縦横10分割したもの。
・4次メッシュ: 一辺約500m. 3次メッシュを縦横2分割したもの。
・5次メッシュ: 一辺約250m. 4次メッシュを縦横2分割したもの。
・6次メッシュ: 一辺約125m. 5次メッシュを縦横2分割したもの。

ESRIジャパンさんが詳しく説明してくれているので、気になる方はぜひ以下を読んでみてください。

標準地域メッシュ | ESRIジャパン

実行環境

Windows 10
Python 3.6.6

地域メッシュから緯度経度を割り出すサンプルコード

1~3次メッシュまで対応させたサンプルコードを作成しました。

def get_latlon(meshCode):

    # 文字列に変換
    meshCode = str(meshCode)

    # 1次メッシュ用計算
    code_first_two = meshCode[0:2]
    code_last_two = meshCode[2:4]
    code_first_two = int(code_first_two)
    code_last_two = int(code_last_two)
    lat  = code_first_two * 2 / 3
    lon = code_last_two + 100

    if len(meshCode) > 4:
        # 2次メッシュ用計算
        if len(meshCode) >= 6:
            code_fifth = meshCode[4:5]
            code_sixth = meshCode[5:6]
            code_fifth = int(code_fifth)
            code_sixth = int(code_sixth)
            lat += code_fifth * 2 / 3 / 8
            lon += code_sixth / 8

        # 3次メッシュ用計算
        if len(meshCode) == 8:
            code_seventh = meshCode[6:7]
            code_eighth = meshCode[7:8]
            code_seventh = int(code_seventh)
            code_eighth = int(code_eighth)
            lat += code_seventh * 2 / 3 / 8 / 10
            lon += code_eighth / 8 / 10

    print(lat, lon)

if __name__ == '__main__':
    get_latlon(52396594)

例えば、52396594 を引数にして実行すると「35.24166666666667 139.675」という形で緯度経度を取得することができます。これがどこかといいますと、横須賀のとある地域ですね。

f:id:sanvarie:20200229221902p:plain

それではこの結果が本当に正しいか確認してみます。以下のサイトで地域メッシュコードの一覧を入手することができます。

統計局ホームページ/市区町村別メッシュ・コード一覧

地名までは載っていないのですが、52396594 は横須賀市ということがわかります。

f:id:sanvarie:20200229222512p:plain

意外と簡単に地域メッシュコードを緯度経度に変換することができるということがわかりました。今度は地域メッシュコードからその範囲のポリゴンを作成させたり、あとは、緯度経度から地域メッシュコードに変換したりしてみたいですね。本日は以上です。

ArcGIS API for JavaScript でレイヤーにフィルターをかける方法

最近 ArcGIS API for JavaScript について書くことが多いのですが、本日も ArcGIS API for JavaScript について書いてみようと思います。前回のエントリーでは レイヤーのセレクトボックスを選択した際にそのレイヤーのフィールドを別のセレクトボックスに格納する方法について書いてみました。今回はその続きです。選択したレイヤーとフィールドを使用して、レイヤーにフィルターをかける方法について書いてみようと思います。前回のエントリーに興味がある方はぜひ読んでみてください。

www.gis-py.com

完成イメージ

ちょっと見づらいのですが、実行ボタンを押下すると「神奈川」レイヤーの「SIKUCHOSON」フィールドが「横須賀市」のフィーチャが抽出されます。

f:id:sanvarie:20200211215003p:plain

f:id:sanvarie:20200211215212p:plain

フィルターを解除する場合は、「フィルター」項目を空白にして実行ボタンを押下します。

f:id:sanvarie:20200211215305p:plain

実行環境

ArcGIS API for JavaScript 4.14
Google Chrome 最新版(2020/2/11 時点)

フィルター処理について解説

FeatureLayer クラスの definitionExpression プロパティを使用してフィルター処理を行います。getActiveField() メソッドで選択しているフィールドのオブジェクトを取得し、それを利用してフィルター用の式を作成しています。

// 選択したフィールドを取得
function getActiveField() {
    var ddFieldList = document.getElementById("ddFieldList");
    var selectedIndex = ddFieldList.options.selectedIndex;
    return ddFieldList.options[selectedIndex].field;
}

// フィルター処理
function filterFeatures() {
    var txtSearch = document.getElementById("txtSearch");
    var featureLayer = getActiveLayer();

    // 空だったらフィルター解除
    if (txtSearch.value == "") {
        featureLayer.definitionExpression = txtSearch.value
    } else {
        var activeField = getActiveField();
        var type = activeField.type;
        var expression;

        if (type = "string"){
            expression = activeField.name + "=" + "'" + txtSearch.value + "'";
        } else {
            expression = activeField.name + "=" + txtSearch.value;
        }
        featureLayer.definitionExpression = expression;
    }      
}

サンプルコード

以下に全コードを記載します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <style>
    html,
    body,
    #divMapView {
        padding: 0;
        margin: 0;
        width:100%;
        height:100%;
    }
    #divToolbar {
        top:0;
        width:100%;
        position: fixed;
        background:rgba(0,0,0,0.6);
        color: white;
    }
    #searchBox {
        width:100px;
        right:0;
        position: absolute;
        background-color: rgba(255,255,255);
        color: black;
    }

    </style>
    <title>レイヤー一覧</title>
    <link rel="stylesheet" href="https://js.arcgis.com/4.14/esri/css/main.css">
    <script src="https://js.arcgis.com/4.14/"></script> 
    <script>
    
     // WebMapのレイヤーをセレクトボックスに格納する
     function loadLayers(layers) {
      var ddLayerList = document.getElementById("ddLayerList");
       layers.forEach(l => {
            let o = document.createElement("option");
            o.textContent = l.title;
            o.layer = l;
            ddLayerList.appendChild(o)
       });

       // 最初は未選択の状態にする
       ddLayerList.selectedIndex = -1;
     }

     // レイヤーセレクトボックス変更時の処理
     function onLayerChange(e) {
        var layer = getActiveLayer(); 
        getFieldName(layer);
     }

     // レイヤーのカラムを取得
     function getFieldName(layer) {
        var ddFieldList = document.getElementById("ddFieldList");

        // セレクトボックスの要素を削除
        removeOptions(ddFieldList);

        // セレクトボックスに要素を追加
        layer.fields.forEach(function(field){
            let o = document.createElement("option");
            o.textContent = field.name;
            o.field = field;
            ddFieldList.appendChild(o);
      });
     }

     // セレクトボックスの要素削除
     function removeOptions(selectBox){
        var i;
        for(i = selectBox.options.length - 1 ; i >= 0 ; i--)
        {
            selectBox.remove(i);
        }
     }

     // 選択したレイヤーを取得
     function getActiveLayer() {
        var ddLayerList = document.getElementById("ddLayerList");
        var selectedIndex = ddLayerList.options.selectedIndex;
        return ddLayerList.options[selectedIndex].layer;
     }

     // 選択したフィールドを取得
     function getActiveField() {
        var ddFieldList = document.getElementById("ddFieldList");
        var selectedIndex = ddFieldList.options.selectedIndex;
        return ddFieldList.options[selectedIndex].field;
     }

     // フィルター処理
     function filterFeatures() {
        var txtSearch = document.getElementById("txtSearch");
        var featureLayer = getActiveLayer();

        // 空だったらフィルター解除
        if (txtSearch.value == "") {
            featureLayer.definitionExpression = txtSearch.value
        } else {
            var activeField = getActiveField();
            var type = activeField.type;
            var expression;

            if (type = "string"){
                expression = activeField.name + "=" + "'" + txtSearch.value + "'";
            } else {
                expression = activeField.name + "=" + txtSearch.value;
            }
            featureLayer.definitionExpression = expression;
        }      
     }
      
     require (["esri/WebMap", "esri/views/MapView"],
     (WebMap,MapView) => {
        const map = new WebMap({
            "portalItem" : {
                "id" : "adfcbd5d28174451b61ec8c0195af577"
            }
        });
        const mapView = new MapView({ 
            "container" : "divMapView",
            "map" : map
        })
        
        map.when(() => {
            loadLayers(map.layers);
            var ddLayerList = document.getElementById("ddLayerList");
            ddLayerList.addEventListener("change", onLayerChange);
        })
     })  
    </script>
</head>
<body>
    <div id = 'divMapView'></div>
    <div id = 'divToolbar'>
        <label>レイヤー</label>
        <select id = 'ddLayerList'>
        </select>
        <label>フィールド</label>
        <select id = 'ddFieldList'>
        </select>
        <label>フィルター</label>
        <input type = 'text' id = 'txtSearch' class = 'searchBox'>
        <button id="executeFilter" type="button" onClick="filterFeatures()">実行</button>
    </div>
</body>
</html>

他のレイヤーにもフィルターをかけてみました。

f:id:sanvarie:20200211220406p:plain

f:id:sanvarie:20200211220510p:plain

いい感じですね!

機能的には大したことはないと思いますが、地道に勉強してみようと思います。やはりこれからは Web GIS の時代なので、ArcGIS API for JavaScript について覚えておいて損は無いかと思います。本ブログでは ArcGIS API for JavaScript について色々紹介しているので、興味のある方は以下エントリーをぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

www.gis-py.com

本日は以上です。

ArcGIS API for JavaScript でレイヤーのフィールドをセレクトボックスに格納する方法

さて、本日も ArcGIS API for JavaScript について書いてみようと思います。前回のエントリーでは Web Map のレイヤーをセレクトボックスに格納する方法について書いてみました。今回はその続きです。レイヤーのセレクトボックスを選択した際にそのレイヤーのフィールドを別のセレクトボックスに格納するというものです。前回のエントリーに興味がある方はぜひ読んでみてください。

www.gis-py.com

完成イメージ

以下のように「レイヤー」「フィールド」セレクトボックスを作成します。

f:id:sanvarie:20200202200006p:plain

「レイヤー」セレクトボックスの選択時に「フィールド」セレクトボックスにそのレイヤーのフィールドを格納します。 f:id:sanvarie:20200202200059p:plain

f:id:sanvarie:20200202200241p:plain

実行環境

ArcGIS API for JavaScript 4.14
Google Chrome 最新版(2020/2/2 時点)

ポイントとなる処理

  1. Web Mapのレイヤーをセレクトボックスに格納する
  2. レイヤーセレクトボックス変更時の処理
  3. レイヤーのカラムを取得

WebMapのレイヤーをセレクトボックスに格納する

こちらは前回のエントリーでも紹介しましたが、大事な部分なので本エントリーでも紹介します。マップ読み込み時に map.when が実行され、その中で loadLayers() が実行され、「レイヤー」セレクトボックスにレイヤー名が格納されます。

// マップ読み込み時
map.when(() => {
    loadLayers(map.layers);
    var ddLayerList = document.getElementById("ddLayerList");
    ddLayerList.addEventListener("change", onLayerChange);
})

function loadLayers(layers) {
  var ddLayerList = document.getElementById("ddLayerList");
  layers.forEach(l => {
       let o = document.createElement("option");
       o.textContent = l.title;
       o.layer = l;
       ddLayerList.appendChild(o)
  });

  // 最初は未選択の状態にする
  ddLayerList.selectedIndex = -1;
}

レイヤーセレクトボックス変更時の処理

レイヤーセレクトボックス変更時に onLayerChange() が実行されるように設定します。設定は上記ソース参照(map.when でマップ読み込み時に addEventListener() で設定)

// レイヤーセレクトボックス変更時の処理
function onLayerChange(e) {
  var layer = getActiveLayer(); 
  getFieldName(layer);
}

// 選択したレイヤーを取得
function getActiveLayer() {
  var ddLayerList = document.getElementById("ddLayerList");
  var selectedIndex = ddLayerList.options.selectedIndex;
  return ddLayerList.options[selectedIndex].layer;
}

レイヤーのカラムを取得

最初に removeOptions() でセレクトボックスの要素を削除してから要素の追加を行います。

// レイヤーのカラムを取得
function getFieldName(layer) {
   var ddFieldList = document.getElementById("ddFieldList");
   // セレクトボックスの要素を削除
   removeOptions(ddFieldList);
   // セレクトボックスに要素を追加
   layer.fields.forEach(function(field){
       let o = document.createElement("option");
       o.textContent = field.name;
       ddFieldList.appendChild(o);
  });
}
// セレクトボックスの要素削除
function removeOptions(selectBox){
   var i;
   for(i = selectBox.options.length - 1 ; i >= 0 ; i--)
   {
       selectBox.remove(i);
   }
}

サンプルコード

以下に全コードを記載します。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <style>
    html,
    body,
    #divMapView {
        padding: 0;
        margin: 0;
        width:100%;
        height:100%;
    }
    #divToolbar {
        top:0;
        width:100%;
        position: fixed;
        background:rgba(0,0,0,0.6);
        color: white;
    }

    </style>
    <title>レイヤー一覧</title>
    <link rel="stylesheet" href="https://js.arcgis.com/4.14/esri/css/main.css">
    <script src="https://js.arcgis.com/4.14/"></script> 
    <script>
    
     // WebMapのレイヤーをセレクトボックスに格納する
     function loadLayers(layers) {
      var ddLayerList = document.getElementById("ddLayerList");
       layers.forEach(l => {
            let o = document.createElement("option");
            o.textContent = l.title;
            o.layer = l;
            ddLayerList.appendChild(o)
       });

       // 最初は未選択の状態にする
       ddLayerList.selectedIndex = -1;
     }

     // レイヤーセレクトボックス変更時の処理
     function onLayerChange(e) {
        var layer = getActiveLayer(); 
        getFieldName(layer);
     }

     // レイヤーのカラムを取得
     function getFieldName(layer) {
        var ddFieldList = document.getElementById("ddFieldList");

        // セレクトボックスの要素を削除
        removeOptions(ddFieldList);

        // セレクトボックスに要素を追加
        layer.fields.forEach(function(field){
            let o = document.createElement("option");
            o.textContent = field.name;
            ddFieldList.appendChild(o);
      });
     }

     // セレクトボックスの要素削除
     function removeOptions(selectBox){
        var i;
        for(i = selectBox.options.length - 1 ; i >= 0 ; i--)
        {
            selectBox.remove(i);
        }
     }

     // 選択したレイヤーを取得
     function getActiveLayer() {
        var ddLayerList = document.getElementById("ddLayerList");
        var selectedIndex = ddLayerList.options.selectedIndex;
        return ddLayerList.options[selectedIndex].layer;
     }
      
     require (["esri/WebMap", "esri/views/MapView"],
     (WebMap,MapView) => {
        const map = new WebMap({
            "portalItem" : {
                "id" : "adfcbd5d28174451b61ec8c0195af577"
            }
        });
        const mapView = new MapView({ 
            "container" : "divMapView",
            "map" : map
        })
        
        map.when(() => {
            loadLayers(map.layers);
            var ddLayerList = document.getElementById("ddLayerList");
            ddLayerList.addEventListener("change", onLayerChange);
        })
     })  
    </script>
</head>
<body>
    <div id = 'divMapView'></div>
    <div id = 'divToolbar'>
        <label>レイヤー</label>
        <select id = 'ddLayerList'>
        </select>
        <label>フィールド</label>
        <select id = 'ddFieldList'>
        </select>
    </div>
</body>
</html>

動きを確認してみると以下のようになるかと思います。

f:id:sanvarie:20200202201458p:plain

f:id:sanvarie:20200202201527p:plain

f:id:sanvarie:20200202201558p:plain

意外とすんなり完成しました。私は正直 Web プログラミングは得意ではないので、あまり複雑なことはできないのですが、拙いながらも一応目的は達成することができました。今後も ArcGIS API for JavaScript を使っていき、Web GIS の時代から取り残されないように頑張ろうと思います。本ブログでは ArcGIS API for JavaScript について色々紹介しているので、興味のある方は以下エントリーをぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

www.gis-py.com

本日は以上です。

ArcGIS API for JavaScript でWeb Map にあるレイヤーをセレクトボックスに格納する方法

さて、本日は ArcGIS API for JavaScript を使ってみたいと思います。レイヤーをArcGIS Online に共有することはよくあると思いますが、それと同じくらい マップを 共有することもあるかと思います。今回は Web Map として共有したマップに含まれているレイヤーをセレクトボックスに格納する方法について紹介します。

実行環境

ArcGIS API for JavaScript 4.14
Chrome

データ

以下エントリーで使用した神奈川県のデータを使用しました。

www.gis-py.com

上記データを Web Map としてArcGIS Online に共有しました。

f:id:sanvarie:20200201224639p:plain

f:id:sanvarie:20200201230012p:plain

ポイント

セレクトボックスの作成

以下のようにセレクトボックスを作成します。

<div id = 'divToolbar'>
    <label>レイヤー</label>
    <select id = 'ddLayerList'>
    </select>
</div>

Web Map の読み込み

以下のように WebMap クラスを使用します。そして、作成した Web Map の ID を設定してください。

const map = new WebMap({
    "portalItem" : {
        "id" : "adfcbd5d28174451b61ec8c0195af577"
    }
});

Web Map に格納されているレイヤーの取得

loadLayers というメソッドを作成します。その中でセレクトボックスに各レイヤー名称を格納しています。

map.when(() => loadLayers(map.layers))

function loadLayers(layers) {
  const ddLayerList = document.getElementById("ddLayerList");
   layers.forEach(l => {
        let o = document.createElement("option");
        o.textContent = l.title;
        o.layer = l;
        ddLayerList.appendChild(o)
   });
 }

サンプルコード

こちらがすべてのコードです。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <style>
    html,
    body,
    #divMapView {
        padding: 0;
        margin: 0;
        width:100%;
        height:100%;
    }
    #divToolbar {
        top:0;
        width:100%;
        position: fixed;
        background:rgba(0,0,0,0.6);
        color: white;
    }

    </style>
    <title>レイヤー一覧</title>
    <link rel="stylesheet" href="https://js.arcgis.com/4.14/esri/css/main.css">
    <script src="https://js.arcgis.com/4.14/"></script> 
    <script>
    
     function loadLayers(layers) {
      const ddLayerList = document.getElementById("ddLayerList");
       layers.forEach(l => {
            let o = document.createElement("option");
            o.textContent = l.title;
            o.layer = l;
            ddLayerList.appendChild(o)
       });
     }
     
     require (["esri/WebMap", "esri/views/MapView"],
     (WebMap,MapView) => {
        const map = new WebMap({
            "portalItem" : {
                "id" : "adfcbd5d28174451b61ec8c0195af577"
            }
        });
        const mapView = new MapView({ 
            "container" : "divMapView",
            "map" : map
        })
        
        map.when(() => loadLayers(map.layers))
          
     })  
    </script>
</head>
<body>
    <div id = 'divMapView'></div>
    <div id = 'divToolbar'>
        <label>レイヤー</label>
        <select id = 'ddLayerList'>
        </select>
    </div>
</body>
</html>

ばっちりレイヤー名がセレクトボックスに格納されました!○○名というレイヤーは実はポイントレイヤーだったりと少々わかりづらいところがあるデータですが、今回は各レイヤー名の取得にフォーカスを置いているのでとりあえずよしとします。 f:id:sanvarie:20200201231015p:plain

Web GIS が主流になりつつあるため、今後ますます ArcGIS API for JavaScript の重要性が高まるかと思います。自分ももう少し色々勉強しなければならないと感じています。これからもどんどん ArcGIS API for JavaScript について書いていこうと思います。本日は以上です。