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 に比べて大きなアドバンテージかなと思いました(今回はあまりそのメリットを享受していないのですが)。まだまだ知らないことも多いのですが、色々触りながら勉強してみようと思います。本日は以上です。

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

さて、本日はPython で XML を CSV に変換する方法について書いてみようと思います。前回のエントリーでは Python で CSV を XML に変換する方法について書きましたが、今回はその逆ですね。

使用する XML

前回のエントリーで作成した prefectures.xml を使用します。興味のある方はぜひ前回のエントリーを読んでみてください。

f:id:sanvarie:20200411132917p:plain

www.gis-py.com

出力する CSV

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

f:id:sanvarie:20200411121747p:plain

実行環境

Windows 10 64bit
Python 3.6.6

サンプルコード

prefectures.xml を CSV に変換するサンプルです。

# -*- coding: utf-8 -*-
import csv
from xml.etree import ElementTree

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

    # CSV 作成
    f = open(input_file, "w")

    # ヘッダー
    output_string = "公表年,公表月,公表日,都道府県,患者数(2020年3月28日からは感染者数),現在は入院等,退院者,死亡者\n"

    # ヘッダー書き込み
    f.write(output_string)

    # XML 読込
    tree = ElementTree.parse(output_file)

    # ルート
    root = tree.getroot()

    text_list = []

    for r in root:

        if r.tag == "date":
            text_list.append(r.find('year').text)
            text_list.append(r.find('month').text)
            text_list.append(r.find('day').text)

        elif r.tag == "prefecture":
            text_list.append(r.text)

        elif r.tag == "info":
            text_list.append(r.find('cases').text)
            text_list.append(r.find('hospitalized').text)
            text_list.append(r.find('released').text)
            text_list.append(r.find('death').text)

        if len(text_list) == 8:
            # CSV に書き込み
            output_string = text_list[0] \
                            + "," + text_list[1] \
                            + "," + text_list[2] \
                            + "," + text_list[3] \
                            + "," + text_list[4] \
                            + "," + text_list[5] \
                            + "," + text_list[6] \
                            + "," + text_list[7] + "\n"

            f.write(output_string)

            # リストをクリア
            text_list = []

    f.close

if __name__ == '__main__':
    xml2csv()

結果

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

f:id:sanvarie:20200411150128p:plain

さいごに

今回はさくっと終わらせることができました。前回のエントリーで CSV→XML、今回のエントリーでXML→CSV に変換する方法を紹介しました。これで相互変換が必要になっても大丈夫かと思います。前回も使用しましたが、今回も ElementTree を使用しています。Python で XML を扱う際はよく使用するライブラリかと思いますので、ぜひ色々使ってみてください。今回は以上です。

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 を使って地域メッシュコードを緯度経度に変換してみようと思います。

地域メッシュとは

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

本日は以上です。