GIS奮闘記

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

スポンサーリンク

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

本日は以上です。

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 について書いていこうと思います。本日は以上です。

ArcGIS Runtime SDK for .NET を使ってみよう!

さて、本日は ArcGIS Runtime SDK for .NET を使ってみようと思います。今まで興味はあったもののなかなか触る機会のなかった SDK なのでとても楽しみです。

ArcGIS Runtime SDK for .NET とは

ArcGIS Runtime SDK for .NET は Windows 及び iOS、Android プラットフォーム上で動作するネイティブ GIS アプリケーションの開発キットであり、Windows デスクトップや Windows タブレット、iOS、Android などの多様なプラットフォーム向けに様々な GIS 機能を持ったアプリケーションを開発することができます。

ArcGIS Runtime SDK for .NET | ESRIジャパン

特徴

ArcGIS Runtime SDK for .NET はファイルジオデータベースを読み込んで SA アプリを開発することもできるのですが、どちらかというと Web のデータにアクセスすることを前提にしている SDK だと思います。GIS は Web 化の傾向にあるので、時代のニーズにあった SDK といえるのではないでしょうか。

Web に重きを置いているので、バリバリデータを編集するようなアプリの開発を検討しているという方は ArcGIS Runtime SDK for .NET よりも ArcGIS Pro SDK を使用した方がいいかと思います。興味のある方は以下のエントリーを読んでみてください。

www.gis-py.com

当ブログでは ArcGIS Pro SDK のサンプルなどは公開していないのですが、以下サイトでサンプルコードを公開しているので、ぜひ参考にしてみてください。

1−ArcGIS Pro SDK for .NET を使用した機能開発 ~マップと... | GeoNet, The Esri Community | GIS and Geospatial Professional Community

開発の前に必要なこと

ArcGIS Developer Subscription というサブスクリプションプログラムに登録する必要があります(無料)。製品詳細 | ESRIジャパン
また、SDK のインストールも行います。https://developers.arcgis.com/downloads/apis-and-sdks

今回やってみること

マップの作成とレイヤーの読み込み(始めてなのでまずは簡単なところから挑戦してみようと思います)

読み込むレイヤーは以下の日本地図です(ArcGIS Online で表示)。 f:id:sanvarie:20200119204052p:plain

実行環境

Windows10 64bit
Visual Studio 2017
ArcGIS Runtime SDK for .NET 100.7.0

サンプル

以下のサイトを参考にしてみました。 Add layers to a map | ArcGIS for Developers

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using Esri.ArcGISRuntime.Data;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Location;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Security;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.Tasks;
using Esri.ArcGISRuntime.UI;

namespace RuntimeTest
{
    /// <summary>
    /// Provides map data to an application
    /// </summary>
    public class MapViewModel : INotifyPropertyChanged
    {
        public MapViewModel()
        {
            CreateNewMap();
        }

        private async void CreateNewMap()
        {
            // オープンストリートマップをベースマップにする
            Map newMap = new Map(Basemap.CreateOpenStreetMap());

            // フィーチャレイヤーを読込
            FeatureLayer featurelayer = new FeatureLayer(new Uri("https://services.arcgis.com/P3ePLMYs2RVChkJx/ArcGIS/rest/services/JPN_Boundaries_ECM/FeatureServer/0"));
            await featurelayer.LoadAsync();

            // フィーチャレイヤーをマップに追加
            newMap.OperationalLayers.Add(featurelayer);

            // 起動時の表示範囲を設定
            newMap.InitialViewpoint = new Viewpoint(featurelayer.FullExtent);

            Map = newMap;
        }

        private Map _map = new Map(Basemap.CreateStreetsVector());

        /// <summary>
        /// Gets or sets the map
        /// </summary>
        public Map Map
        {
            get { return _map; }
            set { _map = value; OnPropertyChanged(); }
        }

        /// <summary>
        /// Raises the <see cref="MapViewModel.PropertyChanged" /> event
        /// </summary>
        /// <param name="propertyName">The name of the property that has changed</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var propertyChangedHandler = PropertyChanged;
            if (propertyChangedHandler != null)
                propertyChangedHandler(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

アプリが起動し、想定通りレイヤーも読み込まれました。ほとんど時間をかけずにここまでできました。本当に素晴らしいですね。自分でやったこととしては CreateNewMap() の作成とそれをコンストラクタで呼び出すことくらいです。あとはデフォルトのままで問題ありません。

f:id:sanvarie:20200119203905p:plain

今回初めて ArcGIS Runtime SDK for .NET を使ってみたのですが、単純にレイヤーを読み込んで少しカスタマイズするくらいだったら一日二日あればそれなりのものが作れてしまうのでは思うくらい簡単に扱える SDK だと思いました。さすが ArcGIS といったところでしょうか。次回以降はもっと機能を作りこもうと思います。本日は以上です。

ezdxf を使って Python で DXF を扱ってみよう!

さて、本日は ezdxf を使って Python で DXF を扱ってみようと思います。

DXF とは

以下エントリーでも少し紹介しているのですが、DXFは、Autodesk社が開発したファイル形式の1つで、CAD 系のデータですね。しかし、GIS の世界でも DXF はよく使われます。

www.gis-py.com

ezdxf とは

DXF を扱うための Python ライブラリです。データの読み書きを行うことができます。

pypi.org

サンプルデータ

ArcGIS Pro で以下のようなデータを用意しました。

1.浄水場名(アノテーション)
2.浄水場(ポイント)
3.河川(ライン)
4.神奈川県(ポリゴン)

f:id:sanvarie:20200118081144p:plain

f:id:sanvarie:20200118081240p:plain

これをジオプロで「ExportCAD.dxf」という名称のDXFに出力します。

f:id:sanvarie:20200118081650p:plain

できあがった DXF を ArcGIS Pro で読み込むとこのようになります。 f:id:sanvarie:20200118082247p:plain

ちなみにですが、「2.浄水場(ポイント) 」「3.河川(ライン) 」は国土数値情報からダウンロードしてきました。

国土数値情報 上水道関連施設データの詳細

国土数値情報 河川データの詳細

ダウンロードの自動化について過去に紹介しているので、興味のある方はぜひ読んでみてください。 www.gis-py.com

また、「1.浄水場名(アノテーション) 」ですが、「2.浄水場(ポイント) 」から作成しました。作成方法は以下のエントリーで紹介しているので、ぜひ読んでみてください。

www.gis-py.com

実行環境

Windows10 64bit
Python3.6.6
ezdxf 0.10.2
Jupyter 6.0.1

サンプルコード

ezdxf の使い方を紹介します。

レイヤー名称一覧を抽出

import ezdxf

doc = ezdxf.readfile(r"D:\python\data\CAD\ExportCAD.dxf")
for layer in doc.layers: 
    print(layer.dxf.name)

このようになります。「0」と「Defpoints」というレイヤーが抽出されていますが、これは フィーチャクラスを DXF に変換する際にできたもので、特に気にする必要はありません。

f:id:sanvarie:20200118084615p:plain

特定のレイヤのデータを抽出する

「河川」レイヤーのデータを抽出します。

import ezdxf

doc = ezdxf.readfile(r"D:\python\data\CAD\ExportCAD.dxf")
msp = doc.modelspace()

rivers = msp.query('*[layer=="河川"]')
for river in rivers:
    print(river)

こんな感じで結果の結果になりました。

f:id:sanvarie:20200118100421p:plain

色々調べてみたのですが、一つ一つのアイテムの属性にアクセスすることはできませんでした。何かやり方があるのかもしれませんが、今回はあきらめます。

DXF を作成する

ラインを一本作図してそれを DXF として出力します。

import ezdxf

doc = ezdxf.new()
msp = doc.modelspace()
doc.layers.new(name='MyLines', dxfattribs={'linetype': 'DASHED', 'color': 7})
msp.add_line((0, 0), (10, 0), dxfattribs={'layer': 'MyLines'})
doc.saveas(r"D:\python\data\CAD\ExportCAD2.dxf")

想定通りラインが作図された状態で DXF を作成することができました。 f:id:sanvarie:20200118101319p:plain

まとめ

使ってみた感想としては、簡易的に DXF を扱う分には十分だけど、細かな操作を行いたい場合はちょっと厳しいかもしれないと思いました。DXF は専用のソフトで を扱うのが一番かと思います。ただ、今回初めて Python で DXF を扱ってみたのでそれ自体はとてもいい経験になったと思います。興味のある方はぜひ ezdxf を使ってみてください。本日は以上です。