プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

openstreetmap-carto が用いるshapfileについて

OpenStreetMap のタイルサーバーのセットアップの際に、地図描画のスタイルとして openstreetmap-carto を利用します。

こちらの記事に書いたように、タイルサーバーを再セットアップした際に、 openstreetmap-carto が利用するシェープファイルデータのダウンロード先が変わってました。

どうして、いろいろと変更があったのかちょっと興味があったので、調べてみたことをメモっておきます。

利用するファイルの比較

セットアップ時点

最初にセットアップした際は、v4.6.0 に当たるコミット ( 2017/12/18 ac7d4fb )でした。

この時は、データソースとして

を用いていました。

2020/9/9 再セットアップ時点

再セットアップでは 2020/9/7 b10aef3 のコミットを用いており、ダウンロードするファイルは、 以下になっていました。

ダウンロードしているファイルの内容

それぞれのファイルの内容をサイトの説明とファイル名から推測すると

ファイル名 内容
simplified-land-polygons-complete-3857 大陸や土地のポリゴン, 低ズームレベル用の単純化版, 分割なし
land-polygons-split-3857 大陸や土地のポリゴン, 分割あり
simplified-water-polygons-split-3857 海のポリゴン, 低ズームレベル用の単純化版, 分割あり
water-polygons-split-3857 海のポリゴン, 分割あり
antarctica-icesheet-polygons-3857 南極のポリゴン
antarctica-icesheet-outlines-3857 南極のアウトライン
ne_110m_admin_0_boundary_lines_land 国境に関するデータ ?
world_boundaries-spherical ?

となっているようです。

背景描画方法の変更

その後、 2019/3/25 b5938d6 のコミットにて、描画方式が、

  • 海の背景上に大陸(土地部分)のポリゴンを描画

から

  • 土地の背景上に海のポリゴンを描画

に変更になっています。

その理由としては、 PR #3694

This will allow solving several issues related to mud flats, mangroves, wetlands and areas of parkland at the coast. It also makes it possible to use different colors for rivers and lakes, if desired. It also makes it easier to render maritime borders less prominently

とあります。意訳すると

干潟、マングローブ林、湿地、海岸の緑地などに関連する問題をいくつか解決しいます。また、川や湖に異なる色を使うことを可能にします。また、海岸線を目立たないように描画するのが簡単になります。

ということのようです。

ですが、残念ながらレンダリングの中身まで追いかけてないので、この説明がピンとこないですね(あと、議論全部が追えてないので、誤解があるかもしれませんがご容赦ください)。

ダウンロード先サーバーを切り替え

2019/3/31 080b8ea のコミットで、シェープファイルをダウンロードするサーバーを切り替えています。これは単に既存のサーバーの運用停止に伴うもののようです。

world-boundaries を不使用

さらに、2019/4/22 2bff971 のコミットで、world_boundaries-spherical.tgz を利用しなくなります(特に理由が見当たらないのでなぜ急に不要になったかは不明です)。

シェープファイルの内容をdbで管理

そのうえで、再セットアップの際にあったように、2019/4/27 6782400 のコミットで、シェープファイルの扱いが変わり、DB内のテーブルに格納するようになったみたいです。

シェープファイルセットアップ前のgisデータベースのテーブルとして、タイルサーバー再セットアップ時に調べた以前のテーブル構成を見てみると、

osm@map:~$ psql -d gis
psql (9.5.23)
Type "help" for help.

gis=> \d
               List of relations
 Schema |        Name        | Type  |  Owner   
--------+--------------------+-------+----------
 public | geography_columns  | view  | postgres
 public | geometry_columns   | view  | osm
 public | planet_osm_line    | table | osm
 public | planet_osm_nodes   | table | osm
 public | planet_osm_point   | table | osm
 public | planet_osm_polygon | table | osm
 public | planet_osm_rels    | table | osm
 public | planet_osm_roads   | table | osm
 public | planet_osm_ways    | table | osm
 public | raster_columns     | view  | postgres
 public | raster_overviews   | view  | postgres
 public | spatial_ref_sys    | table | osm
(12 rows)

gis=> 

ですが、シェープファイルセットアップ後は、

mor@map:/home/osm/src$ sudo -u postgres -i
[sudo] mor のパスワード: 
postgres@map:~$ 
postgres@map:~$ psql gis
psql (9.5.23)
Type "help" for help.

gis=# \d
                        List of relations
 Schema |                Name                 | Type  |  Owner   
--------+-------------------------------------+-------+----------
 public | external_data                       | table | osm
 public | geography_columns                   | view  | postgres
 public | geometry_columns                    | view  | osm
 public | icesheet_outlines                   | table | osm
 public | icesheet_polygons                   | table | osm
 public | ne_110m_admin_0_boundary_lines_land | table | osm
 public | planet_osm_line                     | table | osm
 public | planet_osm_nodes                    | table | osm
 public | planet_osm_point                    | table | osm
 public | planet_osm_polygon                  | table | osm
 public | planet_osm_rels                     | table | osm
 public | planet_osm_roads                    | table | osm
 public | planet_osm_ways                     | table | osm
 public | raster_columns                      | view  | postgres
 public | raster_overviews                    | view  | postgres
 public | simplified_water_polygons           | table | osm
 public | spatial_ref_sys                     | table | osm
 public | water_polygons                      | table | osm
(18 rows)

gis=# 

となりました。確かに、シェープファイル名に対応するテーブルが追加されているのがわかります。

このように変更した理由は特に明記されていませんが、コミット時のコメントを見ると、

  • 運用中でもデータの更新ができる
  • ダウンロードするデータの日付けを見てダウンロードする

といったことが書いてあるので、データ更新を容易にするための変更ではないかと推測しています。

雑感

地図データの描画なんて、安定していて、データの更新以外はそうそう変更があるように思ってなかったんですが、意外といろいろと変わっていくものなんですね。

海岸線は、オフラインマップの生成でもかかわるので、機会を見つけていろいろと調べようと思います。

【Android】 NFC を試しました

以前から興味はあったのですが、ようやく仕事で使う機会に恵まれそうなので、 NFC を試してみました。

既にネットにいろいろと情報がありますが、自分なりに気づいた点をメモしておきます。

なお、検証は Nexus 6P, Android 8.1.0 で行いました。

NFC 全般について

最初はネットの記事などをいろいろと呼んでいたのですが、今一つ腑に落ちません。ということで、ちょっと昔の本になりますが、一冊 Amazon で買いました。

NFC Hacks ―プロが教えるテクニック & ツール

NFC Hacks ―プロが教えるテクニック & ツール

私の場合、やっぱり、何か新しいものを理解しようとするときは、本で概要をつかむほうが手っ取り早いです。 NFC Forum Tag の仕様なども載っていて、NFC 触るなら手元に一冊あってもいい感じだと思います。

タグの購入

NFCのテスト用に Amazon で買いました。

NXP Ntag 213 チップ, Type-2 タグ。シールタイプ。

にしても、手軽に買えるようになりましたね。

読み込みサンプル

大きく分けて、NFC の読み込みを行う際は、

  • スマホで NFC タグにタッチした際に、アプリを起動させる
  • アプリを起動している際に、 NFC にタッチして読み込む

の2パターンがあると思います。

前者を実現するには、 AndroidManifest にインテントフィルタ(暗黙的インテント)を定義してやります。後者に対応するには、アプリ内で NfcAdapter#enableForegroundDispatch を呼び出す必要があります(NfcAdapter#enableReaderMode でもできると思いますが、今回は試していません)。

個人的には後者でやるほうが、動きがわかりやすいように思います。

なお、今回作成した NFC の読み取りと(この記事では別段書き込みに触れてるところはないですが)書き込みサンプルをそれぞれ Gist に上げておきます。何かの参考にしてください。

NFC 読み取りサンプル · GitHub

NFC 書き込みサンプル · GitHub

インテントの指定方法

Android のドキュメントには、NFC を読み込んだ際、3種類のインテントが発行されるとあります。この際、ACTION_TAG_DISCOVERED 以外のインテントにはインテント名だけでなく、インテントフィルタの定義に追加情報が必要です。

インテント 追加情報
ACTION_NDEF_DISCOVERED mimetype
ACTION_TECH_DISCOVERED tech list
ACTION_TAG_DISCOVERED

追加情報がない場合、インテントフィルタを定義していてもその、正しくインテントの呼び出しがおこなわれませんでした(追加情報なしだからといって、ワイルドカード的にはならないようです)。以下に、具体例を示しておきます。

(参考) ACTION_TECH_DISCOVERED に tech list が必要という話

Android NFC Intent-filter for all type - Stack Overflow

ACTION_NDEF_DISCOVERED の例

NDEF に text/plain の情報が書き込まれているNFCタグを読み取る場合を想定します。

AndroidManifest での定義

例えば、AndroidManifest にインテントフィルタを定義して、スマホでNFCタグにタッチした際に、アプリを起動させてみると、

指定した mimeType アプリ起動
text/plain
image/jpeg ×
なし ×

となります。

mimeType 指定が text/plain を指定

        <activity
            android:name=".MainActivity"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/plain" />
            </intent-filter>

mimeType 指定なし

        <activity
            android:name=".MainActivity"
            android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.nfc.action.NDEF_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
NfcAdapter#enableForegroundDispatch による指定

今度は、NfcAdapter#enableForegroundDispatch を呼び出し、アプリを起動中に、NFCタグにタッチして正しく読み込めるか試すと、

mimeType 指定 読み込み(画面表示)
text/plain
image/jpeg ×
なし ×

となりました。

mimeType 指定が text/plain の場合

    @Override
    protected void onResume() {
        super.onResume();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);
        IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);

        try {
            ndefFilter.addDataType("text/plain");
        } catch (IntentFilter.MalformedMimeTypeException e) {
            e.printStackTrace();
        }

        IntentFilter[] intentFilters = new IntentFilter[] {ndefFilter};
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null);
    }

mimeType 指定がなし

    @Override
    protected void onResume() {
        super.onResume();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);
        IntentFilter ndefFilter = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);

        IntentFilter[] intentFilters = new IntentFilter[] {ndefFilter};
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null);
    }

ACTION_TECH_DISCOVERED の例

次に、購入直後のNFCタグ(一度も書き込みを行っていないタグ, Type-2タグ)を読み取る場合を想定します。

AndroidManifest での定義

上記と同じく、AndroidManifest にインテントフィルタを定義して、スマホでNFCタグにタッチした際に、アプリを起動させてみると、

tech list 指定 アプリ起動
tech list あり
なし ×

となりました(下記に示すように tech list として xml/nfc_tect_list.xml を作成して、その中で Ndef を指定しています)。

AndroidManifest.xml

            <intent-filter>
                <action android:name="android.nfc.action.TECH_DISCOVERED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <meta-data
                android:name="android.nfc.action.TECH_DISCOVERED"
                android:resource="@xml/nfc_tech_filter" />
 

xml/nfc_tect_list.xml の中身

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.Ndef</tech>
    </tech-list>
</resources>
NfcAdapter#enableForegroundDispatch による指定

例えば、NfcAdapter#enableForegroundDispatch を呼び出し、アプリを起動中に、NFCタグにタッチして正しく読み込めるか試すと、

tech list 指定 アプリ起動
tech list あり
なし ×

となります。

tech list がある場合の NfcAdapter#enableForegroundDispatch 呼び出し

    @Override
    protected void onResume() {
        super.onResume();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);
        IntentFilter techFilter = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);

        IntentFilter[] intentFilters = new IntentFilter[] {techFilter};

        String tech = Ndef.class.getName();
        String techlist[] = new String[]{tech};           // and 条件
        String techLists[][] = new String[][] {techlist}; // or 条件

        // TECH_DISCOVERED, tech list 必須
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, techLists);
    }

tech list がない場合の NfcAdapter#enableForegroundDispatch 呼び出し

    @Override
    protected void onResume() {
        super.onResume();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);
        IntentFilter techFilter = new IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED);

        IntentFilter[] intentFilters = new IntentFilter[] {techFilter};

        // TECH_DISCOVERED, tech list 必須
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFilters, null);
    }

ACTION_NDEF_DISCOVERED の例外

ドキュメントによると NfcAdapter#enableForegroundDispatch のインテントフィルタと tech list 引数の両方に null を渡した場合、 ACTION_TAG_DISCOVERED を取得できるとあります。

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()), 0);

        // すべてを受け付ける
        mNfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null);

でも、実はこの時、 ACTION_TAG_DISCOVERED だけでなく、 ACTION_NDEF_DISCOVERED にも反応していました。この時は、上記と異なり、mimeType の指定がなくてもです。

AndroidManifest でのインテントフィルタと NfcAdapter#enableForegroundDispatch のインテントフィルタは同じように指定できると思っていたのですが、この部分については両者で異なるようです。

なお、 NfcAdapter#enableForegroundDispatch を呼び出す際に、 ACTION_TAG_DISCOVERED をインテントフィルタで明示的に指定しても、取得することができました。

ACTION_TAG_DISCOVERED に反応するアプリについて

なお、今回実機でいろいろと試していたら、下記の画面が表示されることがありました。

f:id:junichim:20200914184257p:plain

これは、購入してまだ書き込みを行っていない NFC タグを読み取ったときに出てきました。

どうもこれは、 ACTION_TECH_DISCOVERED および ACTION_TAG_DISCOVERED に対応するアプリがインストールされていないときに、おそらくシステムが用意しているアプリが反応したのだと思われます。似たような話が下記にありました。

「収集された新しいタグ」プロンプトが頻繁に表示されます | HUAWEI サポート 公式サイト

また、自分で作成したNFC読み取りサンプルが ACTION_TECH_DISCOVERED または ACTION_TAG_DISCOVERED に対応させたときは、このアプリの表示もなくなったので、ほぼあたっているかと思います。特に問題があるわけではありませんが、こういうものがありました、というメモです。

NDEFメッセージについて

Gist の処理を見るとわかりますが、 ACTION_NDEF_DISCOVERED の場合に、 NdefMessage を取得しようとすると、配列の形式で取得します。 でも、 NFC (NDEF) の仕様を眺めていても、Ndefメッセージは一つだけのようです。これはなぜなんでしょうか?

調べてみると、やはり、基本的には1つのNFCタグには1つのNDEFメッセージのようです(参考までに、1つのNDEFメッセージには複数のNDEFレコードを入れることができるようです)。

1つのNFCタグに2つのNDEFメッセージ/レコード - Android

Android のドキュメントにも通常一つのNDEFメッセージとあります。でも、将来の拡張性のため配列にして扱ってる、と書いてます。このため、 NdefMessage を取得する時、配列で戻ってきているようです。

NfcAdapter  |  Android デベロッパー  |  Android Developers

これで、だいたい感触がつかめたので、引き続きいろいろと試そうと思います。

OpenStreetMap タイルサーバーの https 化

OpenStreetmap のタイルサーバーの地図データを再セットアップしたので、ついでに https にも対応させました。

サーバーの https 化は何度かやってますが、作業記録としてメモっときます。あ、 SSL 証明書は毎度おなじみの Let's Encrypt を使います。

準備

元々、このタイルサーバーは公開にあたりポート番号を 80 から変更していました(こちらの記事参照)。また、それに合わせて ufw も設定していました。なので、 Let's Encrypt を使うにあたり、 80 と 443 でアクセスできるように修正しておきます。

まずは、 apache のポート番号を 80 に戻しておきます。

/etc/apache2/ports.conf

mor@map:/etc/apache2$ cat /etc/apache2/ports.conf
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 80


        Listen 443



        Listen 443


# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
mor@map:/etc/apache2$ 

バーチャルホスト設定も併せて変更します。

/etc/apache2/sites-available/000-default.conf


(略)

設定ができたら、apache2 を再起動しておきます。

mor@map:~$ sudo service apache2 restart

次は、 ufw の再設定を行います。『Apache for Public』 という名前で公開するポート番号を定義していたので、これを 80 と 443 に変更しておきます。

/etc/ufw/applications.d/apache2-utils.ufw.profile.for-public

[Apache for Public]
title=Web Server
description=Apache v2 is the next generation of the omnipresent Apache web server.
ports=80,443/tcp

このルールを適用します。

mor@map:/etc/ufw/applications.d$ sudo ufw app update "Apache for Public"
プロファイル 'Apache for Public' を規定するルールが更新されました
ファイアウォールの再読込を飛ばします
mor@map:/etc/ufw/applications.d$ sudo ufw reload
ファイアウォールを再読込しました

あと、このサーバーへアクセスするため、ルータの設定を変更します。ルータの設定は、基本的にIPマスカレード設定をしますが、下記の記事で書いたように、

blog.mori-soft.com

NVR510 の場合、不正アクセス検知機能を有効にしているときは、内部から外部へ出ていくパケットの静的フィルタルールを追記してあげる必要があります。

ここまでの設定ができれば、 http(80番ポート)でタイルサーバーに接続できることを確認しておきます。

certbot のインストール

ちょっと前まで、 Ubuntu で Let's Encrypt 使おうとすると、 ppa リポジトリを追加して、 apt で certbot をインストールして、という流れでしたが、今は snap を使ってインストールするそうです。

Certbot - Ubuntuxenial Apache

なので、やってみます。

certbot インストールしてないけど、一応削除

mor@map:~$ sudo apt remove certbot
[sudo] mor のパスワード: 
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
パッケージ 'certbot' はインストールされていないため削除もされません
アップグレード: 0 個、新規インストール: 0 個、削除: 0 個、保留: 99 個。
mor@map:~$ 

certbot を snap でインストールします。

mor@map:~$ sudo snap install --classic certbot
2020-09-08T15:10:00+09:00 INFO Waiting for automatic snapd restart...
certbot 1.7.0 from Certbot Project (certbot-eff✓) installed
mor@map:~$ 

certbot の設定

あとはいつもの certbot の設定をします。今回は下記コマンドにより apache 側の設定も変更することにしました。 画面の指示に従って、必要事項を入力していけば簡単に設定できます。

mor@map:~$ sudo certbot --apache
(略)

こちらなどが参考になるかと思います。 Ubuntu18.04 で Certbot を使った SSL 証明書の発行と更新 | 株式会社TKS2

今回の設定では http --> https のリダイレクトを設定するようにしました。

あとは、 https でタイルサーバーにアクセスできること、 http でアクセスすると https にリダイレクトされることが確認できればOKです。

なお、 certbot をインストールすると renew のための設定も自動で追加されるようなので、これも操作不要です。心配なら、下記でテストだけしておけばよさそうです。

mor@map:~$ sudo certbot renew --dry-run
(略)

ちなみに、この方法だと Apache の設定ファイルの書き換えもやってくれるというので、今回初めて試したのですが、変更前のファイルを残してくれないんですね。なので個人的にはちょっともやもやしています。ということで certonly をつけて、設定ファイルの変更は自分でやるほうが好みかもしれません。また次の機会に試してみようと思います。