プログラマーのメモ書き

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

Dart の const construtor について

Flutter を使いたいと思い、 Dart 始めました。

リファレンス読んだり、本読んだりして、ある程度概要はつかめたのですが、いまひとつよくわからないのが、 const constructor (定数コンストラクタ)です。

疑問点

公式の説明にあるように、コンパイル時に定数にすることができる、ということなんですが、

class A {
  final number;
  const A(this.number);
}
void main() {
  const a = A(10);  // 定数オブジェクト
  var b = A(20);      // 定数オブジェクトではない

  print(identical(a,b));
}

とすると、

false

となるところまでは理解できるのですが、クラスAのフィールドは final なので、変数 b の中身はどうせ変更できないし、『定数オブジェクトではない』といっても、どういう意味があるんだろうか?もっといえば、 const constructor に対しては常に定数オブジェクトが生成される、というのでいいんじゃないか?と思っていました。

解決

いろいろと調べているうちに、 const constructor の引数として変数を渡すときがあることに気が付きました。 つまり、

  var v = 20;
  // var b = const A(v);  // コンパイルエラー

となるということです。この場合は、

  var b = A(v);

とする必要があります。

なるほど!そういうことなんですね。

普通の constructor と const constructor の両方を定義できないので、 const constructor が定数オブジェクトを生成しない形で使うことも許されている、ということなんですね。

あー、すっきりした。

参考

上記についての直接の言及はないですが、こちらの記事を読んでいるうちに気が付きました。

dart - How does the const constructor actually work? - Stack Overflow

OpenStreetMap のオフラインマップ作成時の不具合の修正 (3/3)

前回までの記事で、オフラインマップで洪水状態になる原因がわかりました。ここでは、それを踏まえて、どうやって修正するかを検討した際のメモを残しておきます。

なお、全部で3つに分かれています

この記事の目次は次の通りです。

対応策

さて、前回までの挙動から対応策を考えてみました。

  • land-polygons-split の伊勢近辺を囲んでいる矩形サイズに切り出す
  • multipolygon を複数の polygon に変換するツールをかませる

この2つぐらいが思いつきます。

前者のほうは、ogr2ogr のオプション指定の際に -clipsrc でland-polygons をぶった切らない範囲を指定するのでたぶんうまくいくと思います。とはいえ、land-polygons-split から kansai 部分だけを切り出したデータを見ると、

f:id:junichim:20200924153455p:plain

こんな感じに、伊勢志摩だけでなく三重県の大部分を含むような範囲になるので、最終的に作成する map ファイルのファイルサイズがかなり大きくなりそうです。 それに、何かの折に land-polygons の分割の仕方が変わるとそれに合わせて追随しないといけないのが、ちょっとイヤです。

なので、後者の方法で、できないか検討してみました。

multipolygon を複数の polygon へ変換(QGIS)

これを調べてみると、まずは QGIS を使えば簡単にできることがわかりました。メニューより、

f:id:junichim:20200924153607p:plain

Vector -> Geometry Tools -> Multipart to Singleparts を選択すると

f:id:junichim:20200924153820p:plain

とダイアログがで表示されるので、対象となる layer と出力先となるシェープファイル名を指定すると、書き出してくれます。

例えば、もともとは

f:id:junichim:20200924154142p:plain

こんな感じで、黄色の部分が1つのPOLYGON(MULTIPOLYGON)だったのですが、この機能を使って分割すると

f:id:junichim:20200924154220p:plain

こんな感じで、一つ一つに分かれます。

multipolygon を複数の polygon へ変換(コマンドラインツール)

いろいろとテストしている分には QGIS でもよいのですが、これをコマンドラインツールでやって、既存の map-creator から呼び出したいと思います。

きっと、 GDAL/OGR 周りで何かあるはずだと思いながら、いろいろと調べたのですがなかなか見つかりません。でも、ネットはすごいですね。それなりに時間とられましたが、最終的に、結構昔のディスカッションで、似た現象のお悩み相談がありました。

GDAL - Dev - Multipart to singlepart

で、頑張って最後のほうまで見ていくと、multipolygon を polyon に変換する python ファイルを gist にアップしてくれてました!

いやー、ありがたいですね。感謝感謝としかいいようがありません。

試しに、ogr2ogr で伊勢近辺に限定して(-clipsrc を指定して)作成したシェープファイルを、このスクリプトに通して、別のシェープファイルに変換して、ogrinfoでみてみると、multipolygon の表記が見事消えていました。

これで動くんじゃないかな?ということで、map-creator を一部修正して、ogr2ogr 呼び出しまでをコメントアウトして、この multipolygon をなくしたファイルを渡す形で shape2osm から動かして mapファイルを作ります。

で、アプリに組み込んで動かすと

f:id:junichim:20200924155108p:plain

問題なしです!

あとは、これを定期更新のスクリプトに組み込めば一件落着です。

ogrinfo と shpdump の出力について(2020/9/28 追記)

参考までに、multipolygon 状態のpolygonを含んだファイルを ogrinfo と shpdump で出力したらどうなるか載せておきます。

ogrinfo で表示してみます。シェープファイル名は land.shp レイヤー名が land の場合です。

osm@map:~/tmp$ ogrinfo land.shp land

こんな感じでファイル全体の情報が出た後に、個々のPOLYGONの情報が出力されます。

INFO: Open of `land.shp'
      using driver `ESRI Shapefile' successful.

Layer name: land
Geometry: Polygon
Feature Count: 1542
Extent: (136.417000, 34.277000) - (137.028000, 34.685000)
Layer SRS WKT:
GEOGCS["GCS_WGS_1984",
    DATUM["WGS_1984",
        SPHEROID["WGS_84",6378137,298.257223563]],
    PRIMEM["Greenwich",0],
    UNIT["Degree",0.017453292519943295]]
FID: Real (11.0)
OGRFeature(land):0
  FID (Real) = 172823
  POLYGON ((136.945087 34.556601,136.945052 34.556614,136.945062 34.556628,136.945095 34.556621,136.945087 34.556601))

OGRFeature(land):1
  FID (Real) = 173390
(略)

Geometry: Polygon の表記からこのファイルに含まれている種類は POLYGON ということがわかります。

でも、MULTIPOLYGONのものについては、

OGRFeature(land):1463
  FID (Real) = 177513
  MULTIPOLYGON (((136.802623666666676 34.277,136.802637 34.277032,136.8026681 34.2770763,136.8026781 34.2770963,136.802686200000011 34.2771317,136.802732
(略)
 

のように MULTIPOLYGON と表示してくれます。

一方、 shpdump の場合は

Shapefile Type: Polygon   # of Shapes: 1542

File Bounds: (136.417,34.277,0,0)
         to  (137.028,34.685,0,0)

Shape:0 (Polygon)  nVertices=5, nParts=1
  Bounds:(136.945052,34.556601, 0)
      to (136.945095,34.556628, 0)
     (136.945087,34.556601, 0) Ring 
     (136.945052,34.556614, 0)  
     (136.945062,34.556628, 0)  
     (136.945095,34.556621, 0)  
     (136.945087,34.556601, 0)  

Shape:1 (Polygon)  nVertices=5, nParts=1
(略)

こんな感じになり、上記と同じ shape を表示させると

Shape:1463 (Polygon)  nVertices=57, nParts=2
  Bounds:(136.802623666667,34.277, 0)
      to (136.804933382124,34.2773938, 0)
     (136.802623666667,34.277, 0) Ring 
     (136.802637,34.277032, 0)  
(略)

となっていて、MULTIPOLYGON ということは一目でわかりません。 nParts = 2 が MULTIPOLYGON の可能性をにおわせていますが、ホールの場合もあり得るので、ちょっとわかりにくいかもしれませんね。

定期更新スクリプトへの組み込み(2020/9/28 追記)

オフラインマップファイルの定期更新についてこちらの記事でまとめています。ここで触れているように、対象領域を絞り込むため、若干 map-creator スクリプトを修正しています。この修正した map-creator の152行目あたりを手直しします。

# Land

ogr2ogr -overwrite -progress -skipfailures -clipsrc $LEFT $BOTTOM $RIGHT $TOP "$WORK_PATH/land.shp" "$DATA_PATH/land-polygons-split-4326/land_polygons.shp"
python shape2osm.py -l "$WORK_PATH/land" "$WORK_PATH/land.shp"

の部分を

# Land

ogr2ogr -overwrite -progress -skipfailures -clipsrc $LEFT $BOTTOM $RIGHT $TOP "$WORK_PATH/land.shp" "$DATA_PATH/land-polygons-split-4326/land_polygons.shp"
# added by Junichi MORI, 2020/9/24
CURRENT=`pwd`
pushd $WORK_PATH
python $CURRENT/multipolygon2polygons.py
popd
# end of added
# modified by Junichi MORI, 2020/9/24
#python shape2osm.py -l "$WORK_PATH/land" "$WORK_PATH/land.shp"
python shape2osm.py -l "$WORK_PATH/land" "$WORK_PATH/polys.shp"

としておきます。

この multipolygon2polygons.py が上で触れた gist にあったスクリプトです(入出力ファイル名だけ変えています)。このスクリプトを正しく呼び出せるようにカレントディレクトリを移動して、呼び出し処理をしているだけです。

全体を通して動かして、きちんとmapファイルが作成されることを確認しました。 いやー、長かったですね。

(参考) 下記の記事にも、別のやり方がありますので、ご参考までに。

qgis - Converting huge multipolygon to polygons - Geographic Information Systems Stack Exchange

OpenStreetMap のオフラインマップ作成時の不具合の修正 (2/3)

前回の記事の続きで、さらに原因調査を行った際のメモ書きです。

なお、全部で3つに分かれています

この記事の目次は次の通りです。

原因調査

前回の記事で、オフラインマップの自動生成と定期更新あたりに問題がありそうだとわかりました。でも、作業環境や関連するツール類はバージョンアップもしていないし、どうやって調べようかと思い悩んでいました。

たまたま、運よくオフラインマップの自動生成を試していた時にダウンロードした land-polygons-split のファイルが残っていることを見つけました。

このファイルを使って、アプリに同梱したmapファイルを作成した際と同じやり方(=現状のオフラインマップの生成と同じ方法)で、map ファイルを作ってみれば、何かわかるのではないかと考えて、まずはそれを試してみました。

古い land-polygons-split-4326 による map ファイル生成

以前ダウンロードした land-polygons-split-4326.zip, 2019/4/22 付け が見つかったので、これを使って、オフラインマップ生成と同じ手順と同じソフトウェアで map ファイルを生成してみました。これを、避難所検索@伊勢 に組み込んで表示してみると、なんと問題なく表示されます!

となると、 land-polygons の新旧が影響しているっポイですね。

land-polygons-split の提供元について

ここで、ちょっと land-polygons-split の提供元について調べてみると、mapsforge-creator のコミットログに 2019/6/10 付けで URI を旧サイトから新サイトに切り替えているのが残っていました。今の land-polygons-split の提供元(https://osmdata.openstreetmap.de/)がいつから稼働しているかまではわかりませんが、このコミットログでの切り替え付近でサイトが切り替わっているように推測してよいかと思います。

一方、apkに組み入れてある ise.map(同梱版)のタイムスタンプは 2019/5/22 付けでした。なので、旧サイトからダウンロードした land-polygons-split を使っている可能性が高いと思います。さらに、前回の記事でオフラインマップファイルの更新チェックを行ったと書いたのですが、この時のオフラインマップファイルは 2019/6/1 に動作させて生成したファイルである可能性が高い(ログファイルおよび自分用の業記録のコメントから推測)ので、利用している land-polygons-split は旧サイトからダウンロードしたものを用いた可能性が高いです。

こうやって推測していくと、新サイトの land-polygons-split を使って、手元の環境で作成したオフラインマップを組み込んで、正しく動作しているものはまだないことがわかります。

mapsforge-map-writer を更新

さて、手元でのオフラインマップ生成に問題がありそうなことはわかりましたが、 mapsforge のダウンロードサーバーからダウンロードした berlin.map は問題なく動作しています。

なので、各種ソフトウェアのバージョンに問題があるのかもしれません。ということで試しました。

まずは、オフラインマップ生成時に用いる map-writer を 0.14.0 に上げてみます。

osm@map:~/tmp/offlinemap_test/bin$ wget https://search.maven.org/remotecontent?filepath=org/mapsforge/mapsforge-map-writer/0.14.0/mapsforge-map-writer-0.14.0-jar-with-dependencies.jar 
 -O mapsforge-map-writer-0.14.0-jar-with-dependencies.jar
--2020-09-20 11:00:00--  https://search.maven.org/remotecontent?filepath=org/mapsforge/mapsforge-map-writer/0.14.0/mapsforge-map-writer-0.14.0-jar-with-dependencies.jar
search.maven.org (search.maven.org) をDNSに問いあわせています... 18.208.45.152, 18.204.165.199
search.maven.org (search.maven.org)|18.208.45.152|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 301 Moved Permanently
場所: https://repo1.maven.org:443/fromsearch?filepath=org/mapsforge/mapsforge-map-writer/0.14.0/mapsforge-map-writer-0.14.0-jar-with-dependencies.jar [続く]
--2020-09-20 11:00:00--  https://repo1.maven.org/fromsearch?filepath=org/mapsforge/mapsforge-map-writer/0.14.0/mapsforge-map-writer-0.14.0-jar-with-dependencies.jar
repo1.maven.org (repo1.maven.org) をDNSに問いあわせています... 151.101.24.209
repo1.maven.org (repo1.maven.org)|151.101.24.209|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 301 Moved Permanently
場所: https://repo1.maven.org/maven2/org/mapsforge/mapsforge-map-writer/0.14.0/mapsforge-map-writer-0.14.0-jar-with-dependencies.jar [続く]
--2020-09-20 11:00:01--  https://repo1.maven.org/maven2/org/mapsforge/mapsforge-map-writer/0.14.0/mapsforge-map-writer-0.14.0-jar-with-dependencies.jar
repo1.maven.org:443 への接続を再利用します。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 6711544 (6.4M) [application/java-archive]
`mapsforge-map-writer-0.14.0-jar-with-dependencies.jar' に保存中

mapsforge-map-writer-0.14.0-jar-with-dependen 100%[=================================================================================================>]   6.40M  2.91MB/s    時間 2.2s  

2020-09-20 11:00:03 (2.91 MB/s) - `mapsforge-map-writer-0.14.0-jar-with-dependencies.jar' へ保存完了 [6711544/6711544]

osm@map:~/tmp/offlinemap_test/bin$ ls
mapsforge-creator  mapsforge-map-writer-0.14.0-jar-with-dependencies.jar

で、これを入れて動作させても、結局結果はかわらずでした。

新しい環境によるオフラインマップの作成

現在のオフラインマップ生成環境だと、タイルサーバーの処理も同居しているので、プラグインとして動作する map-writer 以外を大きく変えるのはちょっと怖いです。

なので、EC2上に新しい環境を構築して、そちらでも試してみます。

  • インスタンス, t3a.medium, 2 vcpu, 4GB メモリ
  • ubuntu 20.04
  • osmosis 0.48.3
  • gdal/ogr 3.0.4
  • map-writer 0.14.0
  • map-creater 最新版
  • オフラインマップを伊勢市周辺に限定するように修正

また、 map-creator に含まれている shape2osm は python2 での動作を前提にしていましたが、 python3 で動くように手直ししています。

で、この環境にて、map-creator 実行時のオプションを変えたり、いろいろと試しましたが、ことごとく状況は変わらず、相変わらず洪水状態でした。

その時、コマンド引数のちょっとした手違いで、 kansai 全体でmapファイルを作ってしまったことがありました。まあ、これもおかしいだろうと思い、一応アプリで表示だけさせてみると、なんと、正しく表示されました!

これはどういうことでしょうか?

kansai全体で正しく表示され、伊勢周辺に絞ると表示がおかしくなるなら、ひょっとすると、伊勢近辺に絞っている処理が影響しているのかもしれません。 とはいえ、kansai全体の場合も領域を指定するファイルの内容が異なるだけで、実行しているコマンドは同じはずです。

なぞは深まるばかりです。

シェープファイルの確認

このままではどこから手を付けてよいのかわからないので、一度、シェープファイル自体を確認してみることにしました。

以前、 OSGeoLive という FOSS4G 関係のソフトウェアをまとめて試せる環境をVM上に作ったことがあったので、そこにあるQGISを使い表示させてみることにしました。QGIS そのものに慣れていないので、結構四苦八苦したのですが、結果は、

2019/4/22, 旧サイトからダウンロードした land-polygons-split-4326

f:id:junichim:20200924155636p:plain

2020/8/16, 新サイトからダウンロードした land-polygons-split-4326

f:id:junichim:20200924155646p:plain

なんと、明らかに分割の仕方が異なっていました!

とはいえ、分割の仕方が異なるからといって、なぜ、正しく表示されないか、やはり疑問です。

QGIS上で、伊勢市の一番大きなポリゴンを選択して FID を表示すると、176993と出てます。 同じものが、map-creator から呼び出された shape2osm により、シェープファイルをOSM形式に変換したファイルにも含まれているはずだと思い検索すると、ありません。 他のポリゴンのFIDは検索してもosmファイルに含まれています。

どういうことだ?

ここからあちこちのソースコードを読んだり、リファレンスを調べたり、試行錯誤したりしたのですが、結論としては、

  • ogr2ogr で -clipsrc で区切ると、指定領域外が除外される
  • この時、一つのポリゴンが複数のポリゴンに分断されることがある(凹型を真ん中近辺のちょっと上あたりで水平に分割して、下側が領域外だとすると、上の2つの四角形が残るような感じです)
  • シェープファイルは1ファイルに1つのシェープタイプ(POLYGONとかPOLYLINEとか)しか指定できない
  • シェープタイプのPOLYGONは、一つのパーツの内部に複数のパーツ(ホール、穴)があるのは認めているが、複数のパーツが交差する形は認められていない
  • 一方、シェープタイプとして複数のPOLYGONを含む MULTIPOLYGON というのはない
  • だけど、POLYGONが分断されてMULTIPOLYGON状態になったものも存在できてしまう(こちらの記事など参照)
  • shape2osm は MULTIPOLYGON 状態のものを書き出さない

ということのようです。

当初からの不具合

どうも当初からこの挙動をしていたようです。

旧サイトからダウンロードした land-polygons-split のファイルから作成した ise.map(同梱版)の場合、伊勢市付近は正しく描画されていましたが、 openstreetmap.org と比較すると、下図の赤丸に囲んだあたりでおかしなところがあることがわかります。

https://www.openstreetmap.org/ での表示

f:id:junichim:20200924163202p:plain

避難所検索@伊勢の画面

f:id:junichim:20200924163002p:plain

ただ、旧サイトからダウンロードしたファイルだと、細かく分割されていて市の中心部で目立たなかったので、気づきにくかっただけのようです。

次は

ここまででやっと原因が分かったので、次はどうやってこれに対応するかを検討します。

参考

シェープファイルの内容をテキストデータで確認するツール

  • ogrinfo
  • shpdump, shaplib パッケージに含まれる