トップ地図アプリMap > 地図アプリMap4開発記録

地図アプリMap4開発記録

2024.8.30 ある relationで表現された natural=water,water=river が描画されなくなった[解決]

ある inner polygon[#987687349] を natural=shingle としたことが関係しているのかも知れない。 標準OSM地図では正しく描画される。

それ以外の修正が関係しているかも知れない。


relationを追ってみたが、修正範囲内では問題は見つからなかった。 チェック範囲でな二つの inner polygon に natural=shingle を追加していた。 自作プログラムの relation(#13266291)処理をチェックした方がいいようだ。 JOSMでこのrelationを読み込んだ限りでは異常は見つからなかった。


タグの追加が反映されていなかったのが原因のようだった。

2024.8.30 タグに aquaculture, leaf_type を追加した[map442]

maputil/enum.javaおよび enum Key に aquaculture、leaf_type を enum Val に aquaculture、 shrimp, fish, mussels、broadleaved, needleleaved, mixed, leafless を追加した。

2024.6.14 バス路線図

Paintインスタンスの共用に問題があった。共有をやめてシンプルにした。

2024.6.11 橋名が描画されない

一度はうまく描画できていたが、いつの間にか描画されなくなった。

描画されるようになったが、プログラムが結構複雑化している。

2024.6.10 森林のアイコンが描画されない

SDカードのマウントには時間がかかる。直ちに地図アプリを起動すると、 ビットマップファイルが初期化では読み込みに失敗する。

SDカード読み込み準備が出来てから初期化を行う必要がある。

2024.6.4 aerialway=stationのアイコン(■)が意図したより小さすぎる

256x256画素タイルでは railwayでは station.png は 9x9 、station_small は 4x4 である。 aerialwayでは station.png は 5x5 、station_small は 2x2 にしていた。

これは小さすぎる。 station.png は 7x7 、station_small は 3x3 にしてみる。

2024.6.3 鉄道のzoomm 12, 13 のトンネルが破線になっていない

中ズームレコードにトンネルを加えた。これでトンネルが破線でレンダリングされるようになった。

2024.6.3 OSMタグエラーへの対応

京都宝ヶ池の東の山(森)が laduse=forest;natural=peak となっていた。この場合、natural=peakを無視するだけでよい。

2024.5.26 ixエラー

座標値データをパースした直後のチェックで検出されたエラーである。 type > 0 で num_nodes = 0 はおかしい。

 render 10_878_406 ixOsm=28 陸地:48+検索:24+作図:9=81mS
 getOSMs: ix-offPnts[2] != num_nodes*2[0]
 render 10_878_408 ixOsm=18 陸地:25+検索:55+作図:8=88mS
 if (type > 0 && ix - osm.offPnts != osm.num_nodes*2) {
    // 上記は、ここで出力されたエラーメッセージである
 }

別のタイルでも同じエラーが起きた。type=1 で num_nodes=0 はおかしい。

 :10_886_407
 :10_885_407
 getOSMs: ix-offPnts[2] != num_nodes*2[0] type=1
 Block.get: /storage/36C5-1401/Map4/dat/japan-mid9/444/204.dat time=24mS
 type=1 num_nodes=0

ほかのエラーも起きるが、上のエラーは数回起きている。


short配列の長さとバイト配列の長さを混同していた。 これが、この数日のエラー原因だったのであろう。 しかし、まだ、どこかにエラーが残っているかもしれない。

2024.5.26 中ズームで natural=water;water=river のnameが描画される

water=river があれば描画を回避している。Divider.java でタグを絞り込んでいるが、 water=river はタグに含めているはず。


mid に対する処理だけでいいと思っていたが、low に対する処理も必要だった。

2024.5.26 中ズームに railway=station, aerialway=station を加えた

2024.5.26 中ズームに railway を加えた

これまでは railway=rail だけを中ズームに入れていた。モノレールや登山鉄道なども新規に追加した。 ファイルサイズの増加は japan では1%にも満たない。

2024.5.25 中ズームに aerialway を加えた

2024.5.24 タグパースでエラー検出

多分、先日と同じようなエラーであろう。偶然かも知れないが、ファイルの末尾に近いところでエラーが起きている。 滅多に起きるわけでないが、最終レコード判定に問題はないだろうか? エラーが起きたファイルは 79,236バイト、short配列では 39,618要素である。最終レコードではなさそう。

tags_length は非常に大きい。まだ、差分座標値の可能性がある。

バス路線relation id を道路レコードに含めているため、 japan-high13 では tags_length の最大長は 2454 バイトになる。 3152バイトは誤りである。


ジャンプ命令では タイル 15_28632_12988 が問題なく描画された。バイナリレコードファイルに問題があるのでなく、 再利用とか初期化に問題があるのであろう。再利用では、リセット忘れで前のデータがエラーを引き起こすことが考えられる。

 readAll: size=0.1MB time=5mS /storage/36C5-1401/Map4/dat/japan-high13/7157/3247.dat
 Block.get: /storage/36C5-1401/Map4/dat/japan-high13/7157/3247.dat time=18mS
 parseTgas: error 36387/37962 tags_length=3152
 36387/37962 length=3152 type=1 osm_id=0 ikey=-55 15_28632_12988
 ￉қﷶ኎籠㴹Œ॰ﴯዽʢᕍᔛ┨⨱㰕
 java.lang.ArrayIndexOutOfBoundsException: length=96; index=-55
     at com.example.map4.Tag.parseTags(Tag.java:43)
     at com.example.map4.Block.getOSMs(Block.java:344)
     at com.example.map4.Renderer.renderTile(Renderer.java:331)

2024.5.22 タグの長さが偶数バイトでない

おそらく、タグの前の処理に問題があるのであろう。 タブレットでのファイルサイズは 1,852,428バイトであるから、 末尾に近いレコードでエラーが起きている。 エラーが起きたレコードの末尾は 850080 で、エラーが検出されたのは 849880 であるから、 tags_length は 402 であろう。

再利用のクリアなどが関係しているかも知れない。簡単には再現できないエラーのようだ。 エラー時の出力を増やした。しばらく、これで様子を見よう。

 Block.get: /storage/36C5-1401/Map4/dat/japan-high13/7271/3230.dat time=36mS
 parseTags: error 849880/850080 tags_length=403
 849880/850080 length=403 osm.type=2
 ﶖﶠﻸ:ᄅﺂz︲˃,Ȁ<匬꺲
 readAll: size=2MB time=39mS /storage/36C5-1401/Map4/dat/japan-high13/7271/3229.dat
 Block.get: /storage/36C5-1401/Map4/dat/japan-high13/7271/3229.dat time=41mS
 java.lang.ArrayIndexOutOfBoundsException: length=96; index=-618
     at com.example.map4.Tag.parseTags(Tag.java:42)
     at com.example.map4.Block.getOSMs(Block.java:329)
     at com.example.map4.Renderer.renderTile(Renderer.java:346)
     at com.example.map4.Renderer.render(Renderer.java:251)
     at com.example.map4.RenderThread.run(RenderThread.java:42)

2024.5.21 Thread.sleep() を wait/notify に変更した

2024.5.20 中ズームで森林公園などの名称が描画されない

xc, yc が設定されていないようだ。

Divider.java で low、mid の type >= 2 レコードで nameタグを含めるときは xc, yc を含めるように変更した。

これで、中低ズームでも広域ポリゴンの名称が描画されるようになった。

2024.5.19 landuse=military のname が描画されない

昨日の修正が不完全であったことと現在のOSMバイナリレコードには way_area が設定されていないことが要因であった。 way_area は境界ボックスの面積で代用することにした。殆どの Val.unknown は null に変更した。

OSMバイナリレコードでタグの値が 0 の場合には Val.unknown を表し、この場合は有効である。 タグに指定されたキー key が存在しないときは getVal(Key key) に返す値は null である。

2024.5.18 プログラム上の Key.unknown を null に変更

レンダリングに使用しないキーは無視している。enum Key に含まれないタグは無視している。

一方、Val.unknown はタグのキーが enum Key に含まれるが、 タグの値が enum Val の unknown 以外に該当しないことを表す。 バイナリレコードのタグには Key.unknown は存在しないが、Val.unknown は存在する。

Val getVal(Key key) では、key が enum Key に含まれない場合、および tags配列に含まれない場合 null を返すように変更した。 これまでは Val.unknown を返していた。

実際上、レンダリングに変更は起きないが、分かりやすさのためにこうした。 万一、OSMデータにタグのキーが unknown となっていた場合、 そのレコードは今も前もそのレコードはバイナリレコードに含まれるが、レンダリングでは無視される。

Key の unknown を削除することも可能であるが、正常なキーでは、序数が 0 になることを避けるために、残しておく。 ただし、Val が unknown のときは序数が 0 であるから、Key と Val で事情が異なる。

2024.5.17 tags_lengthの値が異常

多分、multipolygon関連であろう。

Relation処理で inner polygon のノード出力にバグがあった。

2024.5.16 ペデストリアンデッキのmultipolygonでのエラー

見直しで枝葉を払ってシンプルにしたことが影響しているかもしれない。multipolygon が line レコードのように扱われている 場所がある。

パソコン側のバイナリレコード作成プログラムにバグが入り込む type = 3 が type = 1 とか type = 2 に間違えているのではないか?

どうやら、Relation処理 inner polygon が無視されて、outer polygon だけが type = 2 で出力されているようだ。 あるいは inner polygon も type=2 として出力されているかもしれない。

ここまで変更した記憶はないが、何かのはずみで誤りが入り込んだのであろう。


Relation の role を utf-16 にしてしまったことが原因のようだ。 OSMタグは utf-16 でよいが、Relation member の role は utf-8 としたい。

2024.5.15 道路名、川名などが描画されない

橋名の描画を調べると、事前チェックで2(描画できない)が返されている。

座標値の取り出し方を間違えていた。この修正で描画されるようになった。

2024.5.15 convertでエラー

変換先はワークエリアを確保している。ソース側で配列オーバーが起きている。 どこかにアドレス計算ミスがあるのだろう。

    java.lang.ArrayIndexOutOfBoundsException: length=2097152; index=2215836
        at com.example.map4.OSM.convert(OSM.java:624)
        at com.example.map4.OSM.drawLine(OSM.java:293)

ノード座標はX座標(4byte)とY座標(4byte)の二つである。offPnts は 4*offPntsバイトか 8*offPntsバイトか? 両者を混同している可能性がある。

ixPoints および offPnts はノードで二つ進む。

  float[] dst = convert(r, r.points, offPnts+from, offPnts+to, dy);
は、誤りであろう。
  float[] dst = convert(r, r.points, offPnts/2+from, offPnts/2+to, dy);

2024.5.15 Deviderバグ修正

低、中ズームの分割にエラーがあった。陸地ポリゴンの低ズームは分割なしのため、影響なし。

2024.5.15 Javaエラー

今日も Java の更新があった。そのせいか、次のエラーが出た。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode kanto
Exception in thread "main" java.lang.UnsupportedClassVersionError: 
OSMUtil has been compiled by a more recent version of the Java Runtime (class file version 55.0),
 this version of the Java Runtime only recognizes class file versions up to 52.0

java の環境変数のパス設定を変更して、三行目がトップになるようにすればいいだろう。

c:\map>where javac
C:\Program Files\Microsoft\jdk-11.0.12.7-hotspot\bin\javac.exe

c:\map>where java
C:\Program Files (x86)\Common Files\Oracle\Java\java8path\java.exe
C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe
C:\Program Files\Microsoft\jdk-11.0.12.7-hotspot\bin\java.exe

c:\map>java -version
java version "1.8.0_411"
Java(TM) SE Runtime Environment (build 1.8.0_411-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.411-b09, mixed mode)

c:\map>javac -version
javac 11.0.12

トップに移した。これで実行エラーが出なくなった。

c:\map>where java
C:\Program Files\Microsoft\jdk-11.0.12.7-hotspot\bin\java.exe
C:\Program Files (x86)\Common Files\Oracle\Java\java8path\java.exe
C:\Program Files (x86)\Common Files\Oracle\Java\javapath\java.exe

2024.5.15 OSMバイナリレコード描画再開

陸地ポリゴンのレンダリングで Devider や BBox を修正したので、デバッグをやり直す。

プログラムを分かりにくくする些末な高速化はやめる。

低ズームのバイナリレコードファイルがおかしい。境界ボックスに誤りが入り込んだと思われる。

高ズームでは起動時、一部のタイルで建物の描画が一切ない。

2024.5.15 陸地ポリゴンの描画時間

 render Lands 5_28_11 ixOsm=36 time=247ms
 render Lands 5_28_13 ixOsm=38 time=228ms
 render Lands 5_28_10 ixOsm=59 time=256ms
 render Lands 5_28_12 ixOsm=284 time=255ms

2024.5.14 陸地ポリゴン

フォーマット変更に伴い、 LandParser を修正した。

日本地図は問題なく、描画された。しかし、ユーラシア大陸の東部と北部が描画されていない。 北米、中米、南米、アフリカ、オーストラリア大陸などは描画されている。 カスピ海付近は描画されているので、multipolygon がおかしいわけではない。

エラーメッセージは出ていない。

高ズームは描画される。いつでも、最初はこんなものである。

描画されていないタイルでは、該当マルチポリゴンが抽出されていないようである。

因みに全レコードを抽出すると、正確に世界地図が描画された。

要するに、空間検索に問題があることになる。

低ズームではマルチポリゴンはユーラシア大陸だけである。そのレコードの境界ボックスは つぎのようになった。低ズームでは極座標に 100,000 をかけて整数化している。 北半球であるから、top、bottom が正であるのはいいとして、left は負でなければならない。 レコードの境界ボックスの算出に問題があるようだ。

type=3 (4673944,3657204) (5413888,4698587)

境界ボックスの誤りはユーラシア大陸だけのようだ。以前のプログラムではこのようなことはなかった。

【解決】

マルチポリゴンの場合、境界ボックスは outer polygon だけで求める必要がある。 そこを間違えていた。修正した結果下のようになった。

type=3 (-950060,126576) (17999998,7772291)

これで陸地ポリゴンが正確に描画された。

2024.5.13 zoom 19で大きくくずれる

場所によっては zoom 17 でもくずれる。zoom 14 でエラー、zoom 15でOKというケースもある。 render.points配列から誤まった区間を取り出しておれば起こるようなエラーである。 差分から座標値復元を誤っても似たようなエラーになるかも知れない。

並列レンダリングをやめても同じである。

狭い範囲のデータについて、鉄道だけを表示してみた。この場合、鉄道名も描画された。 表示後、高ズームでスクロールしてみると、表示が壊れた。 しかし、そこで、終了して再起動すると、正しく描画された。

要するに、どこかに再利用時の初期化忘れがあるのであろう。


タイル画像用 Bitmap を再利用している。陸地ポリゴン描画を後回しにしたときの不手際で、 前のタイルへの上書きが起きていた。

2024.5.13 ノード内挿エラー

ノードの内挿エラーは解決した。

2024.5.13 道路番号描画位置のずれ

zoom にもよるが、建物の描画位置がずれていることがある。道路番号が大きくずれた位置にある。 道路や川名がないなど、ずれがあるようだ。しかし、正しいものも多い。

場所にもよるかもしれないが、小さな建物は zoom 20 でずれた。しかし、大きい建物や森林はずれない。 way と relation の違いかも知れない。

これまで座標列データを OSM で管理していたが、今回、Renderer での一括管理とした。 修正箇所は多かったので、まずは、このあたりを調べよう。

zoom 19 以下でおかしいのはまず道路番号が目に付く。

この修正ミスはすぐに分かった。

2024.5.13 一部のエラーを無視すると地図が描画された

タグのキーが 512 というエラーは osm.type=2 で起きた。 type=river というタグがあるので、relation処理で生成されたのであろう。

centerは Devider.java がタグの末尾に追加している。

natural=water;type=river;water=weir;center=(1.39678106E9,3.54913056E8);

バグの原因は /2 を忘れていたことであった。

  int end = ix + length/2; 
GPSLog.appendGPSLog: no file=/storage/36C5-1401/Map4/AT/gpslogs/202405**.csv

2024.5.13 空間検索で抽出されるのは point レコードだけである

ファイル上の境界ボックスは x, y, w, h のようだ。しかし、h の値に異状がある。

type=2 (1395031724,355379353) (998,-1039651488)

間違いはすぐに分かった。

誤: dos.writeInt(maxlat-minlon);     // #8, #9
正: dos.writeInt(maxlat-minlat);     // #8, #9

2024.5.13 pointデータが表示された

lineやpolygonは描画されないが、pointレコードは概ね正しく処理されるようになったようだ。

2024.5.12 スキャンはだいぶ進んだところで System.exit(0)

length = 0 があったようだ。 取りあえずは、head == 0 でレコード終了とする。

誤  block.vals[n/2] = (short)((buff[n]<<8) + buff[n+1]&0xff);
正  block.vals[n/2] = (short)((buff[n]<<8) | buff[n+1]&0xff);

2024.5.12 レコードスキャンエラー

PCプログラムで境界ボックス追加で close() が漏れていた。

/storage/36C5-1401/Map4/dat/japan-high10/908/403.dat time=252mS
 0, 32
 0, 1573119
 0, 5963823

ファイルをチェックすると、次のようになっている。長さ 0x20(32) は正しい。 次のレコードの長さは 0x46(70) であるので、パースがおかしい。その後のlon: 53 1E は妥当だが、lat: B1 7A はおかしい。 最初のレコードの lat: 3F B9 は妥当である。

 0:  00 00 00 20 53 1F 3F B9 ...
32:  00 00 00 46 53 1E B1 7A ...

length の単位は byte のため、 nextOff = off + length/2; とする必要があった。

2024.5.12 レコード読み込みエラー

最初から can't alloc Block. length=9447032 となる。16MBバッファがあるが、それを取得していない。 32MBバッファも用意したらこのエラーは取れた。単位違いがエラー原因だった。

2024.5.12 レコード読み込みエラー

vals(short配列) か buff(byte配列)で添え字エラー。

レコード長が 9447032 となっていた。最適なバッファサイズを得ていなかった。

    for (int n=0; n < length; n+=2) {
        block.vals[n/2] = (short)((buff[n]<<8) + buff[n+1]&0xff);
    }   // 割り当てられたメモリに short配列としてコピーする

2024.5.12 全面見直し

空間検索を簡素化したい。Block については、バイナリレコード格納short配列を共用する。

大幅な書き換えを行った。まず、 java.lang.ExceptionInInitializerError が起きた。 多分 static { ... } でエラーが起きたのであろう。

Blockインスタンスが作られていないようだ。

添え字を間違えていた。

2024.4.29 単純な建物では中心座標を省いた

中心座標はアイコンや名称を描画するときに使う。タグが building=*** の場合 不要である。

変更前 japan-high7 269MB、japan-high12 2.80GB

変更後 japan-high7 269MB、japan-high12 2.59GB

2024.4.29 陸地ポリゴンのcenterおをびway_areaを省いた

変更前 low0 10.3MB、splits1 1.59MB、splits5 330MB

変更後 low0 9.35MB、splits1 1.56MB、splits5 317MB

2024.4.28 陸地ポリゴンのレンダリングで出るエラーであった

描画は問題ないが、エラーメッセージが出るようだ。

タグの enum を変更したのが原因だった。

2024.4.27 箱根駒ヶ岳ロープウェイ付近表示でエラー

半月ほど前に気づいたエラー。

Tag.parseTags: type=2 zorder[85] ix=1309 tags_length=14 num_tags=0 num_nodes=19253 wid=0 zorder=0
parseTag: error ikey=20419, ixTag=2

osm_idが分からないため、デバッグが難しい。 取りあえず、レンダリングに使うOSM tag数が1を超える場合、Encoderでタグ部に osm_id を追加する。

relationに限り osm_id を負の値にした。これにより、relationで生まれた line/polygon と wayオブジェクトの line/polygon かの区別ができる。

未解決エラーの正体は芦ノ湖であった。しかし、芦ノ湖はレンダリングされているので、ごみレコードのようだ。

 parseTag: ikey=11
 natural=rock
 parseTag: ikey=85
 Tag.parseTags: type=2 zorder[85] ix=38 tags_length=14 num_tags=0 num_nodes=19253 wid=0 osm_id=-10349338
 parseTag: ikey=20419
 parseTag: error ikey=20419, ixAll=3
 readAll開始 /storage/36C5-1401/Map4/byte/lands-split1/1/0.dat
 readAll: size=0MB time=4mS
 readAll開始 /storage/36C5-1401/Map4/byte/lands-split5/28/12.dat
 readAll: size=3MB time=10mS
 Tag.parseTags: type=2 zorder[85] ix=255 tags_length=14 num_nodes=19253 wid=0 osm_id=0
 parseTag: ikey=20419
 ikey=20419, ixAll=3
 Tag.parseTags: type=2 zorder[85] ix=256 tags_length=14 num_nodes=3081 wid=0 osm_id=0
 parseTag: ikey=20496
 parseTag: error ikey=20496, ixAll=3
 render Lands ixOsm=2 time=52ms
 Tag.parseTags: type=2 zorder[85] ix=42 tags_length=14 num_nodes=19253 wid=0 osm_id=0
 parseTag: ikey=20419
 parseTag: error ikey=20419, ixAll=3
 Tag.parseTags: type=2 zorder[85] ix=28 tags_length=14 num_nodes=3081 wid=0 osm_id=0
 parseTag: ikey=20496
 parseTag: error ikey=20496, ixAll=3
 Tag.parseTags: type=2 zorder[85] ix=44 tags_length=14 num_nodes=19253 wid=0 osm_id=0
 parseTag: ikey=20419
 parseTag: error ikey=20419, ixAll=3
 Tag.parseTags: type=2 zorder[85] ix=30 tags_length=14 num_nodes=3081 wid=0 osm_id=0
 parseTag: ikey=20496
 parseTag: error ikey=20496, ixAll=3
 Tag.parseTags: type=2 zorder[85] ix=41 tags_length=14 num_nodes=19253 wid=0 osm_id=0
 parseTag: ikey=20419
 parseTag: error ikey=20419, ixAll=3
 Tag.parseTags: type=2 zorder[85] ix=26 tags_length=14 num_nodes=3081 wid=0 osm_id=0
 parseTag: ikey=20496
 parseTag: error ikey=20496, ixAll=3

2024.4.26 golf=green;hole;tee;pin;path のレンダリングを追加した

2024.4.20 佐渡島の一部の森林が描画されない(2)[解決]

zoom 16 では描画された。レコード自体は含まれているが、highway=track の存在から、 zoom 15以下では描画しなかったようだ。

wid による除外条件に type==1 を追加することにより、正しく描画されるようになった。

 highway[7]=track[23]
 natural[11]=wood[362]
 type[75]=multipolygon[667]
 way_area=6.8150008E7;
 xc=1.38321805E9 yc=3.8101104E8;

2024.4.19 佐渡島の一部の森林が描画されない(1)

Relation #15921663は outer(1439ノード)、inner にエラーはない模様。しかし、 highway=track という無効タグが加わっている。type=multipolygon、natural=wood はあるので、 無視すればよい。 しかし、描画されない。

Parserを調べると、このレコードは出力されているようである。

しかし、最終的に ByteCoder で出力されたかどうかは osm_id でチェックできない。 highway=trackタグの存在により、どこかで捨てられた可能性が否定できない。

レンダリング上は osm_id は要らないが、デバッグではレコードに osm_id がほしい。

2024.4.19 陸地ポリゴンエラー[解決]

仕様変更にまだ追従できていないようである。LandParserの変更が必要なのかも知れない。

管理部の長さが極端に大きい。 lenIndexes=100663360

まず、以下は古い仕様のため削除しなければならない。

   head |= 0x04;                                   // 絶対座標

これを削除して以降の処理をやり直した。これで、陸地ポリゴンが正常に描画されるようになった。

2024.4.19 タグの値のエラー[解決]

enum Val は現在 716個。

 java.lang.ArrayIndexOutOfBoundsException: length=716; index=20419
   at com.example.map4.Tag.parseTags(Tag.java:83)

レコードに osm_id を含めていないので、デバッグがやりにくい。

タグのキーが不正な値 84 である。どこで混入したかを調べる。

最近のプログラム修正が原因か、以前からあったものかは不明である。

Parserではタグ中に不正なキーはない。

Devider でも見つからなかった。

ByteEncoder でも見つからなかった。

OSMバイナリレコードに不正なタグが混入したのではなく、 Windows と Android のタグ解釈の不一致または Android の地図アプリのバグの可能性がある。

しかし、タグ部には OSMタグではないものも含んでおり、このチェックは行っていない。

パフォーマンスに拘り、タグのパースを複雑にしすぎたきらいがある。 主要タグが一つの場合には skey、sval に値をセットしている。 Tag[] tags には全OSMタグを含んでいる。これには、OSMタグではないものは含まない。

OSMレコードは形式的にはシンプルになったが、その分、タグ部が複雑になったのが問題かもしれない。

84 がタグの不正な値、ix はそのタイルで何番目のレコードだったかを表す。num_tags=0 なのは、 空間検索の最終処理で、タグをパース中にエラーが検出されたため、まだ値が設定されていないため。

tags_lengthは 14 なので、結構多くのタグがあるようだ。

num_nodes=19253でレコードを手掛かりとして、ここレコードを特定してみよう。 偶然、複数あったとしてもそれらを全部調べればよい。

type=2 zorder[84] ix=95 tags_length=14 num_tags=0 num_nodes=19253 wid=0 zorder=0

Parserで調べると Way では num_nodes==19253 が見つからなかった。 Relation によって生まれたレコードであろう。市区町村境界線か何かであろう。

Devider で調べてみる。これでも見つからない。num_nodes=19253 に間違いがあるのか。

結局、陸地ポリゴン natural=land でのエラーと分かった。 何らかの仕様変更を陸地ポリゴンデータに対して適用していなかったかもしれない。

LandParser.java の最終更新日時は 2024年2月3日である。 データの作り直しは4月1日を行っている。

2024.4.18 tower_type と tower:type

これまでは tower_type としていたが、tower:type があった。これが正しく、tower_type は ないのかもしれないが、当面、両方は存在するものとした。

Encodeからやり直す必要がある。

lapan-high.dat は 2,996,684KBから2,996,714KB に増加した。

この修正でスマホの基地局(アンテナ塔アイコン)が正しく描画されるようになった。

2024.4.18 フリーワード検索完成

キー入力は初めてのため、多少、日時がかかったが、一応の完成をみた。 プログラム行数は200行弱、通常のレスポンスタイムは2秒以内となった。 実際に使ってみて、必要に応じて、改良する。

2024.4.17 AND検索にエラーがある

「備中松山城」で検索した場合、9レコードが該当した。

 備中松山城
 [-2, -1, 80, -103, 78, 45, 103, 126, 92, 113, 87, -50]
 1: 1336330833,348135379,備中松山城展望台
 2: 1336215664,348056762,史跡 備中松山城跡(国指定)
 3: 1336220319,348083233,備中松山城復古図
 4: 1336218812,348079956,国指定史跡 備中松山城跡
 5: 1336222002,348082677,高梁観光百選 備中松山城
 6: 1336219008,348081440,備中松山城
 7: 1336220416,348085216,備中松山城 二の丸
 8: 1336222720,348143648,史跡 備中松山城跡
 9: 1336330880,348135360,備中松山城展望台

「備中 松山城」で検索した場合、「備中」と「松山城」の AND検索となる。 「備中松山城」と同じかより多くの結果が得られるべきである。下記のように 「備中松山城」と「高梁観光百選 備中松山城」が漏れている。

AND検索のどこかにバグがあるのであろう。

 備中 松山城
 [-2, -1, 80, -103, 78, 45]
 [-2, -1, 103, 126, 92, 113, 87, -50]
 1: 1336330833,348135379,備中松山城展望台
 2: 1336215664,348056762,史跡 備中松山城跡(国指定)
 3: 1336220319,348083233,備中松山城復古図
 4: 1336218812,348079956,国指定史跡 備中松山城跡
 5: 1336220416,348085216,備中松山城 二の丸
 6: 1336222720,348143648,史跡 備中松山城跡
 7: 1336330880,348135360,備中松山城展望台
 1396mS

「松山城 備中」としても「松山城」で不一致となる。

単語が一つのときは問題ないが、二つ以上では、丁度末尾まで一致するとき、 うまく検出できないようである。

レコード上の文字列の範囲を for (int n = 10; n < length - 8; n++)  としていたのが間違い。for (int n = 10; n < length - 2; n++) に訂正した。 10 は lon、 lat、-2、-1 の合計バイト数、 length - 2 の 2 は次のレコードの長さを表す2バイトである。

2024.4.13 natural=cliffのアイコンを変更した

標準OSM地図では精密なレンダリングソフトが使われている。 自作地図アプリでは専用ソフトは使わないので、標準OSM地図用アイコンでは natural=cliffのレンダリングの見栄えが悪くなる。 自前アイコンにより見栄えを良くした。

2024.4.12 高尾エコーリフトは描画されない

何度もretryを繰り返し、最終的に描画不能が戻っている。

ライン描画上のアイコンと競合しているようだ。

線上に並べるアイコンは競合対象から除外することにより、解決した。

2024.4.12 行政境界線に沿って描く文字を見やすくした

例えば、東京と埼玉のほぼ水平方向の境界線があった場合、 東京は見やすいが、埼玉は逆向きであった。これを 180度回転して見やすくした。

2024.4.10 行政境界線lineレコードを復活する

ポリゴン描画では二度以上の描画起き、鎖線が崩れる。lineレコードを復活して、 描画を1回に限定する。

変更前 japan-high.dat 2.83GB、japan-mid.dat 301MB、japan-low.dat 23.7MB

変更後 japan-high.dat 2.85GB、japan-mid.dat 307MB、japan-low.dat 25.6MB

一つの lineレコードに全ての行政境界realtionの osm_id を持たせる方法が ファイルサイズは最小になるが、効果は限定的なので、プログラムがシンプルな方を選択した。

特に、高ズームでは multipolygon/polygon レコードは必須ではなく、 行政境界線に沿って、行政名を描画するのに必要な情報(文字の描画位置と角度)だけが あればよいので、必要に迫られれば、レコードサイズを小さくできる。

2024.4.10 ferry_terminalの色を変更した

2024.4.9 線分が長い時一方通行の矢印が二つ接近して描画される

20_930391_412825

drawIconMultiに変更したら解決した。drawBitmapMulti は少々煩雑すぎる。いずれ見直したい。

2024.4.9 17_116320_51658/51659 上のタイルでプールアイコンと学校名が競合

上が描画されず、下だけ描画されている。

nameタグに対するマージンは大きくしたが、アイコンだけについてはマージンが小さいことが原因であろう。

type 2 のマージンを 0.02 から 0.2 に上げたら、下だけの描画が無くなった。

高ファイルのマージンは 0.01 から 0.02に上げた。zoom 12分割のため、zoom 16では 0.32のマージンに相当する。 japan-high7 242MB、japan-high12 2.66GB から japan-high7 244MB、japan-high12 2.71GB に増加した。

2024.4.9 何も競合していないがタイル境界で道路名が途切れる[不完全な描画消えた]

前のタイルでは信号機や道路端に駐車場がある。17_116318_51685

この原因を掴みたい。

要所でチェック結果を表示しようとしていたら、半分かけた道路名描画が消えた。 何か初期化忘れがあるのかも知れない。

2024.4.9 道路番号がタイル境界で途切れることがある

15-29050/29051-12917 で 413 の 4 が切れている。 マージンが効いていないか、全く別の原因と思われる。森林の中であり、付近には何もマッピングされていない。

レコードの始端か終端に近い。最初の offset が関係しているかも知れない。

タイル境界をまたぐような描画は行わないようにした。

2024.4.9 道路番号描画に関連して道路のマージンをふやす

道路幅に対するマージンは以前から盛り込み済みであるが、道路番号表示ではそれよりも 大きいマージンが必要である。通常 0.03 の所、中高ズームの幹線道路は 0.1 に変更した。

マージン変更前のOSMバイナリレコードファイルのサイズは japan-mid7 234MB、japan-high7 238MB、japan-high12 2.65GB から japan-mid7 241MB、japan-high7 242MB、japan-high12 2.66GB に増加した。

空間検索でのマージンも増やした。

2024.4.9 中ズーム用レコードに refタグを含める

OSMバイナリレコードファイルjapan-mid7 は 231MB から 234MB に増加した。

2024.4.8 道路番号(refタグ)の描画

自前レンダリングに変えてから、道路番号(refタグの値)を描画してこなかったが、 今回、描画した。今後、多少の修正はあると思うが、予想以上に簡単であった。

2024.4.6 道路名などの描画:リトライをやめた

オフセットを少し変更してのリトライはタイルをまたぐ描画のエラーを生むやすいと思われる。 描画(候補)位置を確定的になるように変えた。

2024.4.6 一方通行の矢印を文字からアイコンに変更した

2024.4.5 タイル境界での途切れがなくならない

じゃまものはないのに、描画されないことがある。

2024.4.5 曲線に沿って描く文字

緩やかなカーブの場合は全く問題はない。曲線が極端にくねくね折れ曲がっている場合、うまく、線に沿わない。 線分と線分の接点に文字を描くときの位置と角度をいくらにするかがポイントとなる。 角度差が大きい場合は文字列を描かない。

とりあえず、角度差が十分小さくなければ描画しないことにした。

2024.4.5 市の境界線が描画されない

地方の一部をチェックしただけであるが、例えば 17-114554-51879 では姫路市と宍粟市の境界線および市名が 正しく描画されるが zoom 16では市名は描画されるが、境界線はごく一部しか描画されない。 zoom 15では境界線はほとんど描画されない。zoom 14 では境界線はところどころ描画されるだけである。 東京でも森林が描画されているところでは同様である。

森林は無関係である。バックに何も描画されていない八王子と昭島の河原での境界線も同じ症状がある。 ハイズームでは描画されるため、データの異常ではないであろう。

Pathを使っての描画は問題ないようだ。Canvas#drawLineメソッドで不具合が出る。線分の長さが短すぎて、 線分単位の描画では、うまく鎖線が描画できないのであろう。 Pathの場合、線分をつなぎ合わせた状態で鎖線を描画するのであろう。 敢えて、drawLineを使うには、境界線の鎖線をシンプルにして、これに似たことを行う必要がある。

境界線は二度以上描画している。元の境界線wayレコードも出力して、境界線はこれを使えば1回の描画で済む。 バス路線のように、元の境界線wayレコードにrelation番号列を持たせれば、ファイルサイズも少し小さくなる。

2024.4.5 崖(natural=cliff)のアイコンの数が多すぎる

5個以上になるようなときは問題ないが、少ない時が間隔が詰まりすぎ。 1個描ける幅がなければ描画をやめるべき。

とりあえず、アイコンの描画を減らした。根本的には線分単位の処理が問題である。 zoom が小さくなると線分が短くなる。 繋ぎ合わせて、1線分の長さをある値以上にするなどの対策が望まれる。

2024.4.5 飛び地(inner polygon)の境界線エラー

埼玉県にある東京都練馬区の飛び地「西大泉町」はタイル 17-116350-51576 では境界線が正しく描画されている。 zoom 16, 15, 14 でも正しく描画されている。しかし、zoom 13以下で で東京都と埼玉県の境界線のある場所まで 線が伸びている。飛び地(inner polygon)が inner polygon として扱われず、全体を outer polygon として しまったものと思われる。zoom 13以下では、inner polygon を無視して outer polygon だけを描画したかったものと 思われる。

multipolygon を下のプログラムで描画しているのが問題である。

    void drawLine(Canvas canvas, Renderer r, Paint paint) {
        drawLine(canvas, r, paint, 0, 0, num_nodes);
    }

multipolygon は outer と inner に分けて描画する必要がある。 以下のように変更して、境界線が正しく描画されることを確認した。

    void drawLine(Canvas canvas, Renderer r, Paint paint) {
        if (type == 1 || type == 2) {
            drawLine(canvas, r, paint, 0, 0, num_nodes);
        } else if (type == 3) {
            int offset = 0;
            for (int n = 0; n < num_polys; n++) {
                int nodes = poly_nodes[n];
                drawLine(canvas, r, paint, offset, offset + nodes);
                offset += nodes;
            }
        }
    }

2024.4.4 route=ferryのレンダリング時間が異常に長い

やはり、以前と同じく、route=ferryのレンダリング時間が異常に大きいことが分かった。

route=ferryとかノード数の多いlineの描画では Graphics.Path を使わないようにした。 これで、レンダリング時間が格段に短くなった。この結果、レンダリング時間よりも 空間検索時間(可変長バイトコードの復号化および座標変換時間を含む)の方が大きくなることもある。

2024.4.3 高ズーム boundary=administrative

polygon/multipolygon出力をやめ、個々の wayオブジェクト単位で出力する。閉ループは type=2 とした。

japan-high.dat はレコード分割により 2.82MB から 2.83GB に微増した。これは想定内である。

japan-high12 は 3.30GB から 3.34GBに、japan-high7 は 324MBから 266MBに変わった。 境界ボックスが小さくなったことにより、high12 に収まるものが増え、 それ以上に high7 が減ったのは複数ブロックへの格納が減ったことによる。

2024.4.3 boundary=administrativeの lineレコード出力をやめる

変更前 japan-high.dat 2.85GB、mid 301MB、low 23.7MB

変更後 japan-high.dat 2.82GB、mid 301MB、low 23.7MB

2024.4.3 一方通行の矢印の方向が間違っている

文字単位描画に変えたときから、← と → を例外扱いする処理が漏れていた。

例外的に反転を禁止するだけで解決した。

2024.4.3 タイル境界での描画[解決]

一方通行矢印の影響で次のタイルで文字が一つ消えたと思われる例が見つかった。

同じ renderName で同じ drawTextMulti メソッドを使っているが、一方通行を優先している。

タイル境界とは無関係に、一つの道路レコードについて、矢印と道路名の位置が決まるはずであるから、 次のタイルでは矢印なしで道路名描画が始まるのはおかしい。

矢印描画を止めると道路名が正しく描画された。

文字列描画を優先すると、道路名が正常に描画された。取りあえずこれで様子を見る。

2024.4.3 行政境界線のレンダリング負荷

行政境界線は現在マルチポリゴンしている。このレンダリング負荷が高い場合、 高ズームはラインレコードに分割すれば、個々のタイルのレンダリングで抽出されるものが小さくなる。

現状では、zoom 17 で全体の 10%前後のようだ。 これには、行政境界線(マルチ)ポリゴンの可変長バイトコード復号化時間と座標変換時間は含まれていない。 小さくはないが、極端に大きいわけでもない。 無理して、ラインレコードに分割する必要はないであろう。

多分、現在はレンダリングには要らない境界線ラインレコードも含まれているであろう。 全ての境界線が閉ループとしてRelationで管理されているならば、このレコードは要らない。

境界線Relationを見直すときがあれば、そのとき、検討する。

2024.4.2 小さな橋名が表示されなくなった[解決]

ここ数日の修正が影響していると思われる。

原因はすぐに分かった。

2024.4.2 道路名の途切れ[未解決]

競合の影響とは思えない途切れが見つかった。近くに2か所あり、ひとつは文字列と文字列の間隔が小さい。 バグかも知れない。zoom 17,116331,51663

同じ道路に対する描画位置がタイルによってずれたか。

チェックでは描画可能となっているが、描画処理が行われていない。描画で絞り込みを行っていないか?

2024.4.1 陸地ポリゴンレコード更新

ファイルフォーマットの変更は陸地ポリゴンにも適用しなければならない。

tags_length の追加の前には低中ズームの精度を変更した。他にも変更したかもしれない。


修正はtags_length の追加だけだったようだ。

2024.4.1 2タイルマージンの無駄を省く(2)

次に、ByteCoderでの対応を考えてみる。

しかし、polygon/multipolygon として塗りつぶしがいるかどうかは、 地図アプリでの空間検索でしか判断できないのではないかということに気づいた。

現状のファイル形式では可変長バイトコードの復号化だけは行わないとタグ部だけを 取り出せない。

可変長バイトコード化でデータ長の次にタグ部の長さ(可変長バイトコード)を追加するのが 修正は一番小さいであろう。平均的には1バイト強の増加で済む。レコード末尾から計算すれば タグ部の先頭が分かる。


まず、ByteCoder でレコードに tags_length を追加した。

地図アプリでは取りあえず、この値を確認するだけとした。

調べた範囲では、疑似pointの対象はさほど多くなく、平均ノード数は 100前後だった。 気にするほどの無駄はないかもしれない。


地図アプリで疑似pointレコードとするのは簡単ではない。バグが入り込む危険性が高いので、 簡単な方法を思いつかない限り、保留する。

2024.4.1 2タイルマージンの無駄を省く(1)

一部レコードの2タイルファイルサイズ増は小さく、負担増は小さい。 しかし、地図アプリでの負荷は事情が異なる。

ファイルサイズ増は小さいため、空間検索のスキャン処理の増加は小さい。

polygon/multipoloygonレコードの2タイルマージンは重なり判定の為で、使うのは中心座標だけであり、 座標値列データの可変長バイトコードの復号化および座標変換、描画処理(空振り)は要らない。

polygon/multipoloygonレコードの空間検索では、小さいマージンで抽出されたものはこれまで通りの扱いで、 それを外れて、2タイルマージンにより抽出された場合には、pointレコードに変更して、抽出結果リストに加える。

ノード座標値列データは要らないがタグデータはいる。 現在のバイナリレコードはタグが末尾にあるため、タグデータの先頭位置と長さの算出が必要になる。

地図アプリの空検索やレンダリング処理は変更せず、PCでのデータ作成で対応する方が楽かもしれない。 この場合は、通常のマージンで今までと同じレコード分割を行い、そこから漏れたレコードに対して、 2タイルマージン内に入るレコードについて、type を 0 に変え、中心座標の point の座標として、 タグデータはそのまま出力すればよい。元の polygon/multipoligon の中心座標はタグ部に含まれており、 pointレコードとしての座標とダブっている無駄はあるが、タグ部からこの無駄を省くにはそのプログラムがいる。 この無駄は小さいので、当面、そのままとする。

現在のプログラムを下に示す。修正がいるのは赤字部分である。上とは少し異なる対策がいる。 赤字部分の先頭でこのレコードの境界ボックスとタイル境界(微小マージンを含む)を比較する。 境界ボックスがタイル境界と交差する場合、そのまま赤字部分を実行する。

交差しないときは2タイルマージンに含まれるレコードであるから、pointレコードに変更して出力する。 center(中心座標)の出力はいらない(上で述べた無駄はなくなる)が、 way_area は文字列描画フォントのサイズに反映される。そのため、純粋な point レコードと区別がいる。 現在の type は point[0]/line[1]/polygon[2]/multipolygon[3] であるが、これに pseudo-point[4](疑似ポイント)を 付加するなど、何らかの対策が必要となる。

type の変更はあちこちに影響する。本来の point レコードには way_area は含まれないので、 way_area の有無で本来のpointレコードかpoint疑似レコードかを区別することにする。

head は length を含むため、この値は変わる。 nodes_buff は出力しない。代わりに、最後の writeLong(center) をここに移す。 tags_buff 以降の出力は同じである。

ByteCoderはそのままでいいはず。

マージンを小さくして minX, minY, maxX, maxY を求めて、マージンが大きいの差をもとめればいいのであろうが、 高ズームでは zoom 12とzoom 7に分かれるために、厄介である。

空間検索よりも Deviderでの対応が楽かと思ったがそうでもなさそうである。

空間検索では小さいマージンで検出したあと、nameタグをもつ polygon/multipolygon に対して、 マージンを大きくして、抽出されたレコードを pointレコードに変換して登録する方が簡単かもしれない。 しかし、バイナリレコードの仕様を少し変更して、ノード座標値列データを飛ばして、 タグデータだけを取り出せるようにする必要がある。

  for (int x = minX; x < maxX; x++) {     
      String dirX = dirDst + x + "/";
      if (!setDir.contains(dirX)) {
          setDir.add(dirX);
          File dir = new File(dirX);
          if (!dir.exists()) dir.mkdirs();
      }
      for (int y = minY; y < maxY; y++) {
          String path = dirX + y + ".dat";
          DataOutputStream out;
          if (mapOUT.containsKey(path)) {
              out = mapOUT.get(path);
          } else {
              out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(path, true)));
              mapOUT.put(path, out);
          }
          if (wayarea == 0) {
              out.writeInt(head);
              out.write(nodes_buff);
              out.write(tags_buff);
              dstSize += 4 + nodes_buff.length + tags_buff.length;
          } else {
              out.writeInt(head + 16);
              out.write(nodes_buff);
              out.write(tags_buff);
              out.writeShort(Key.wayarea.ordinal());  // 2
              out.writeFloat(wayarea);                // 4
              out.writeShort(Key.center.ordinal());   // 2
              out.writeLong(center);                  // 8
              dstSize += 4 + nodes_buff.length + tags_buff.length + 16;
          }
      }
  }

2024.4.1 boundary=administrative を 2タイルマージン対象からはずす

都道府県や市区町村境界を表すpolygon/multipoloygonレコード については、nameタグは境界線に沿って、内側に繰り返し描画するのには使用するが、 都道府県名や市区町村名は pointレコードで描画しているため、2タイルマージンの対象から除外できる

Dividerの出力は japan-low3 13.6MB、japan-mid7 306MB、japan-high12 3.30GB、japan-high7 324MB となった。

2024.3.31 管理データに nameフラグを追加する

管理データの最初の4バイトは、最上位バイトが フラグと type であり、後の3バイトがレコード長である。

type は 0、1 bit、4 bit(0x10) は境界ボックスの width、height が16ビットか32ビットかの区別、 5 bit(0x20)はレコードに nameタグがあるかないかの区別を表す。現在は 8ビット中、4ビットを使用中である。

全てのpointレコードおよびnameタグをもつpolygon/multipoloygonレコードのマージンは上下左右2タイルという 巨大なものにした。

これは文字列描画に関するものである。 高ズームのOSMバイナリレコードファイルはzoom 12とzoom 7で分割している。地名を除き、zoom 16未満での 描画は皆無またはそれに近いため、zoom 16で 2タイルのマージンは、zoom 12 では 2/16タイル分となり、 zoom 7 では微々たる大きさとなる。

以上から、2タイルマージンによるファイルサイズ増は全体として小さく、 プログラムの追加修正もほんのわずかであった。

2024.3.31 道路名の描画が欠ける

二つのタイルをまたぐ道路名の一方が文字列との重なりを避けて、描画されていない。

描画しているタイル側では、隣のタイルで描画できないことを検知して、描画を止めるべきであるが、 そのように動作していない。

少なくともチェック段階では、隣のタイルでの描画もチェックする。重なり検出用矩形リストには、 マージンの働きで、隣のタイルで描画する文字列矩形も登録しておく。 道路レコードが隣のタイルにも伸びておれば、タイルをまたがる道路名描画が起こりうる。 隣のタイルでも描画できることを確認した上で、描画可能判断がなされるはずだが、そうなっていない。 どこかに思い違いがあるようだ。

pointレコードではなく polygonレコードであった。polygonレコードに対するマージンは小さいため、 隣のタイルでの描画がチェックされない。

単純にマージンを増やすとファイルサイズが大きく増大する恐れがある。 pointレコードは数が少なく、レコードサイズが小さいため、マージンを大きくしても負担は少ないが、 polygon、multipolygon はそうは行かない。 エリアの中心点を独立したpointレコードにする案もある。

高ズームはzoom 12、zoom 7で分割している。店名などの描画は zoom 16、17以上が主である。 zoom 16で2タイル分のマージンはzoom 12では 2/16タイル分のマージンでよい。

空間検索では、polygon/multipolygon がレンダリング対象範囲を外れている時は、座標列データは不要で、 中心点座標だけでよい。pointレコードとして抽出するだけでよい。

そのようにしても、zoom 16でのマージンを 2タイル分とすると相当な負荷が増える。 大きなマージンなしで、描画の途切れをなくせないものだろうか?

プログラム的に簡単なのはnameタグをもつpolygon/multipolygonの高ズームのマージンを zoom 12 で 2/16タイル分にすることである。

ただし、boundary=administrative は除外してもよい。しかし、境界ボックスが巨大な場合、 2/16タイルの分のマージンは小さいため、ファイルサイズの増加は小さいかも知れない。

変更前のサイズは japan-high7 314MB、japan-high12 3.29GBであり、 変更後のサイズは japan-high7 327MB、japan-high12 3.30GBとなった。 思ったより、増加は小さかった。

空間検索では無駄を気にしないならば、 nameタグをもつpolygon/multipolygonのマージンを 2タイルに変更するだけでよい。

無駄を省くには、通常のマージンではレンダリング対象外となったpolygon/multipolygonレコードについて、 マージンを2タイル分としたとき、対象レコードとなる場合、pointレコードとして、抽出結果に加える。

いずれにせよ、問題はnameタグを持つか否かが空間検索の初期段階では分からないことである。 全てのpolygon/multipolygonのマージンを 2タイル分として抽出して、その後、 nameタグの有無を調べて、なければ破棄することにする。

まずは、この破棄も考えず、単にマージンを 2タイル分に増やしてみる。

変更前にあった道路名の途切れはこれでなくなったことを確認した。

この状態では、名前なしの建物など実際にはレンダリングには影響のないレコードが多数含まれることになる。

2024.3.30 メッシュ(ブロック)分割時のマージン

pointマージンを 2 という非常に大きな値にしたとき、全体のファイルサイズがどう変わるか調べる。

現在のサイズは可変長バイトコード変換前では japan-high7 232MB、japan-high12 3.40GB、japan-mid7 304MB、 japan-low3 12.9MBである。

変更後のサイズは japan-high7 2.35GB、japan-high12 3.29GB、japan-mid7 306MB、 japan-low3 13.6MB となった。

japan-high7 が約10倍もの大きさになった。pointマージンを 2 としたため、 同じpointレコードが (1+2+2)*(1+2+2)=25 タイルに置かれることになる。 japan-mid7 はほんのわずかしか増加していない。

現在の分割プログラムに問題がありそうである。

high7 には同じレコードが 25回書き込まれているのであろう。

high12 は約 110MB減少している。恐らく、これが全pointレコードのサイズであろう。 これが 25倍になれば、約3GBである。

マージン2は上下左右2タイルを意味する。しかし、例えば zoom 12 での 5x5 タイルは、 zoom 7 では大抵の場合、同じ一つのタイルに含まれる。

多分、この無駄は高ズームだけで起きるのかも知れない。いずれにせよ、分割プログラムの見直しがいる。

現在の分割プログラムのマージンの利用方法を下に示す。highZoom におけるマージンは妥当としても、 lowZoom でのマージンに問題がある。高ズーム用は現在 highZoom=12、lowZoom=7 としている。 lowZoom でのマージンは margin/(1<<highZoom-lowZoom)) とすべきであろう。

    // 修正前
    boolean high = true;    // デフォルトは highZoom
    int minX = (int)(Lib.lon2X(minLon/E7, highZoom) - left_margin);
    int maxX = (int)(Lib.lon2X(maxLon/E7, highZoom) + right_margin) + 1;
    int minY = (int)(Lib.lat2Y(maxLat/E7, highZoom) - top_margin);
    int maxY = (int)(Lib.lat2Y(minLat/E7, highZoom) + bottom_margin) + 1;
    int overlap = (maxX - minX) * (maxY - minY);
    if (overlap >= 4) {	
        high = false;    // lowZoom におく
        minX = (int)(Lib.lon2X(minLon/E7, lowZoom) - left_margin);
        maxX = (int)(Lib.lon2X(maxLon/E7, lowZoom) + right_margin) + 1;
        minY = (int)(Lib.lat2Y(maxLat/E7, lowZoom) - top_margin);
        maxY = (int)(Lib.lat2Y(minLat/E7, lowZoom) + bottom_margin) + 1;
    }

変更後のサイズは japan-high7 314MB、japan-high12 3.29GBとなった。 pointレコードが全て high7 に移った。 また、line、polygon、multipolygon の high7 でのマージンが減ったため、その分ファイルサイズが小さくなった。

2024.3.30 空間検索時のマージン

現在のマージンは次の通りである。line マージンは以前は 0.05 としていたが、 最近、道路名の途切れは lineマージン不足かと思い、大きくした。

道路名途切れ抑止に必要なのは lineマージンではなく、pointマージンである。 pointマージンで bottom_marginだけを他より大きくしているのは、 店名などの長い文字列を折り曲げて描画するとき、文字列はアイコンの下に描くため、下に延びることが多いためである。 道路名に対するマージンは4方向いずれも同じでよい。よって、lineマージンは以前の 0.05 に戻し、 pointマージンを全て 0.50 に増やしてみる。

実際に確認してみると、非常に長い道路名があり、三つのタイルにまたがるケースがあった。 point マージンを 2 とすれば、このような長い道路名も描画できた。ただし、ハイズームでないと、 信号機やバス停があり、道路名を描く場所が見つからず、道路名が描画されない区間が続く。 プログラムで、道路名を簡略した方がよい。

道路名表現を2つに分けて、交互に表示するなどの工夫が必要である。

                                           point line polygon multipolygon
    final static double[] left_margin   = {0.20, 0.15, 0.05, 0.01};
    final static double[] top_margin    = {0.20, 0.15, 0.05, 0.01};
    final static double[] right_margin  = {0.20, 0.15, 0.05, 0.01};
    final static double[] bottom_margin = {0.30, 0.15, 0.05, 0.01};

2024.3.30 道路名はアイコンや店名などより先に描画する

道路名は道路のレンダリング直後に行う。すでに川名は道路より先に描画しているため、橋の上書きにより、 川名の一部が消えることがあるが、さほど気にならない。 道路レコードは交差点から始まることが多いため、道路名は先頭に少しスペースを空けてから描画を開始する。 これにより、交差点名や信号機アイコンとの重なりを避ける。 それでも重なりを完全になくせるとは限らない。上書きを避けるか、川名のように上書きを許すかは、 実際の描画結果を観察してから決定する。


描画結果を見たところ、上書きは望ましくない。道路レコードの途中に交差点や信号機があるケースも多そうである。 また、バス停やバス停名が道路名と重なるケースが多い。

やはり、道路名よりも信号機、バス停アイコン、交差点、バス停名を優先すべきであろう。

京都市内をざっと見たところ、道路名の末尾が欠けたのが1か所だけあった。 標準OSM地図では見た範囲では欠けたものがなかった。

現在のタイルでは描画できても、隣のタイルでは交差点やバス停があって道路名が描画できないかも知れない。 マージンを十分に大きくすれば、隣のタイルの情報も含むことになるが、レコードが多くなり、パフォーマンスは低下する。 長い道路名があるため、マージンによる負担が大きい。

しかし、このマージンはラインレコードに対するマージンではない。交差点、バス停はポイントデータである。 これを誤解して、ラインレコードのマージンを大きくしていた。ラインレコードのマージンは元に戻して、 ポイントレコードのマージンを大きくしてみよう。ポイントレコードは全体の数も少ないし、一つのレコードのサイズも小さい。 このマージンを大きくしても全体に占める比重は小さい。

このマージンは分割時と空間検索時に必要である。分割時のマージンについては分割のやり直しが必要なため、 後回しとして、まずは、空間検索時のマージンを変更する。

2024.3.29 natural=cliff、man_made=embankmentの反転を避ける

閉ループであっても、 natural=cliff、man_made=embankment は lineデータであり、 反転があってはならない。

まず、kantoデータで修正結果を確認した。natural=cliffの向きが正しく描画された。

引き続き、japanファイルも更新してnatural=cliffの向きが正しく描画されることを確認した。

2024.3.28 Paint#getTextBoundsの返す値

getTextBoundsの返す幅と高さを下に示す。「一」の高さはわずか2である。 回転、移動を考えると、横幅は文字により変動させるとしても、縦幅はほぼ同じにすべきであろう。

数十文字調べた範囲では幅、高さ 19, 19 が最大であった。

15, 2 一
15, 14 丁
11, 15 目
19, 19 横

とりあえず、「一」は上下のマージンにより描画されるようにした。 根本的には、Rect の幅と高さだけではなく、left、top を考量して、それにマージンを加えるべきであろう。

2024.3.27 ポリゴン描画で paint が null である

次のようにしているので null はありうる。

bmpIntermittentWaterが null だったのが引き金になっている。

  osm.fillPolygon(canvas, r, fIntermittent ? null : paintWater,
        fIntermittent ? bmpIntermittentWater : null);

画素数が3倍のときに下のようなケアレスミスがあった。「!」は要らない。

    if (Files.exists(Paths.get(path3))) {
    //if (!Files.exists(Paths.get(path3))) {

スマホでも境界線の「一」は消えていることが分かった。 水平方向の地名では「一丁目」はきちんと描画されるので、何か対策はあるだろう。

2024.3.27 Androidタブレットでは境界線に「一丁目」の「一」が描画されない

「二」、「三」は描画される。「一」だけが全く描画されない。

高精細なスマホでも同じかどうか確かめる。 USBケーブル接続でファイルを転送した。

2024.3.27 選択したバス路線は赤色で表示されない

仕様変更(簡略化)後の処理が未完成であった。Map3とは抜本的に仕組みを変えている。 道路などラインレコードおよびバス停ポイントレコードには該当する路線番号配列を持たせており、 空間検索ではその値を OSMインスタンスの bus_route_ids配列にセットしている。

バスが走るかどうかは bus_route_ids配列の有無だけで決める。バス路線の場合、細い青線を描画する。

バス路線表示モードにすると、その時点の画面内に含まれるバス路線だけが選択され mapBusRoutes に登録される。同時に路線選択ボタンが画面下に表示される。 mpaBusRoutesに登録された路線は太い青線になる。選択ボタンにより路線を選択すると、 その路線は赤い太線描画となる。

バス路線一覧にある場合 BusRoute br = mapBusRoutes.get(id) で BusRouteインスタンス br が 取り出せる。選択されている場合 br.selected が true である。

修正した。しかし、いずれ、地の地図の描画とアイコン、店名、バス路線などのレンダリングは分離する。 地の地図のタイル画像はストレージに保存して、再利用する。 アイコン、店名、バス路線などは選択でレンダリングが変わる。このとき、バス路線のレンダリングも再考する。

2024.3.27 ディレクトリ名、ファイル名変更

OSMバイナリレコードファイルについては既に japan と kanto では区別しているが、 最近導入したタイル画像の保存ディレクトリ名は区別していなかった。 osm384 とか osm768 など(数値はタイルの画素数)は japan384、japan768 あるいは kanto384、kanto768 などに変更する。

プログラム上の修正は "osm" としていたところを tile.src に変更するだけである。

バス路線ID(バス路線relationのosm_id)、バス路線番号、バス路線名レコードファイル bus_route.tsv は bus_route_japan.tsv、bus_route_kanto.tsv に変更する。

現在のプログラムは初期化処理で bus_route.tsv を読み込み、それを使用している。 アプリ起動後の kanto から japan への切り替えなどに対応していない。 ファイルサイズは japan でも 850KB に過ぎないので、バス路線図を表示するときに読込むように変更した。

読み込んだ時、japan とか kanto は記録しておき、連続的に同じデータを使うときは、 ファイル読み込みは初回だけとした。

2024.3.26 japan.osmデータを更新する

Map4に一区切りがついた。ここで japan.osm、kanto.osm を久しぶりに更新した。

ファイルサイズは前回より1割ほど増加した。隣の市では建物のマッピングが進んだ。 我が家の近辺では区の下の字の境界線も新たに登録されていた。 地方はチェックした範囲では大きな変化は感じられなかった。

2024.3.26 バス路線が描画されていない[解決]

バス路線が描画されていないことに気づいた。多分、何かの修正で消えたのであろう。

Paintインスタンスの取得メソッドを変更したときに描画処理をコメントアウトしたままになっていた。

2024.3.26 タイルをまたがる描画について

道路名などに限らず、店名、地名、アイコンなどあらゆる描画がタイルをまたがることがある。 タイル単位のレンダリング(描画)であるから、一方のタイルでは描画され、 他方のタイルでは描画されないことが必ず存在する。 タイル領域にマージンを持たせることにより、不完全な描画は大幅に軽減するが、皆無にはならない。

例えば地名描画の場合、事前にタイル境界のない地図で仮想的に描画して、 描画する地名を確定しておけば、完璧な描画が可能である。

道路名などは何度も繰り返し描画するため、タイルをまたがる描画を止めてもよい。 しかし、Matrix操作で描画する場合、描画領域を正確に把握することは簡単ではない。 時間がかかりすぎるのも問題である。

現時点では、アイコン、文字列のタイルをまたがる描画はまだ完璧なものではない。

地名についても、一時は完璧を期したが、今はプログラムの簡単さから マージンだけで対応しているため、ごくわずかであるが、地名の描画が不完全なこともありえるであろう。

無理はせず、簡単な方法で少しずつ、不完全な描画を減らしていきたい。

2024.3.25 線分と線分の角度が30度を超えるときは描画を避けた[Map420]

2024.3.25 橋など2ノードで短い場合中央に描画する

赤字部分の追加で大半の橋名は中央に描画されることを確認した。

    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs, float dy) {
        r.ixNode = 0;
        r.offset = 0;   // ixNodeからのオフセット。複数線分になることもある。
        if (num_nodes == 2) {
            float width = getWidth(chrs);
            float dist = (float)distance(pnts[0], pnts[1], pnts[2], pnts[3]);
            if (dist > width && dist < width*5) {
                r.offset = (dist - width) / 2;
            }
        }
        Bitmap[] reversed = reverse(chrs);
        while (true) {
            int rtn = drawBitmapMulti(canvas, r, chrs, dy, 0, true);   // チェック
            if (rtn == 2)  {
                break;    // 終了(これ以上描画できない)
            } else if (rtn == 1) {
                r.offset += Map.PX * 0.05f; // 再試行 スペース(狭い)を空ける
                // r.ixNodeは更新されていない。
            } else {
                Val boundary = getVal(Key.boundary);
                boolean fAdmin = (boundary == Val.administrative);
                int addDegree = fAdmin ? 0 : rtn;
                drawBitmapMulti(canvas, r, addDegree==0 ? chrs : reversed,
                                dy, addDegree, false);    // 描画実行
                r.offset = Map.PX * 0.5f;
                // r.ixNodeが更新されている。offset は ixNode の先頭から。
            }
        }
    }

2024.3.25 文字を読みやすいように必要に応じて 180度回転する

道路名等の描画に関して、恐らく最後と思われる課題に取り組む。

その他の課題として、細かい調整は残っている。(1)線分の角度が大きく変わる所には描画しない。 (2)文字列レコードが長い場合、文字列は繰り返し描画するので、タイルをまたがる所は避ける。 (3)橋など、一度しか描けない場合、なるべく、中間に描くなど。

ほほ水平の文字が上下が逆に表示されることがある。このような場合、 個々の文字を180度回転させる。同時に文字列の後ろから順に描くように変更する。

一方通行を表す矢印(→)も文字である。この矢印に限り、180度回転させてはならない。

これまでの地図アプリでは次のようにしている。

チェック段階では複数の文字の角度の平均値を算出して、この平均値により、0、-180、+180 を決める。 したがって、チェックでの戻り値は現在の 0, 1, 2 を変更する必要がある (とりあえず 0, -180, 180, 1, 2 でいいだろう)。 また、この角度はメソッドの引数(addDegree)として追加する必要がある。 一方通行の矢印の場合はこの回転度数は無条件に 0 とする。

-180 と +180 よって、何か変える必要あるかどうかはやってみないと分からない。

  if (!text.equals("→") && !text.equals("←")) {
      if (deg > 90 || deg < -90) {
          if (deg > 90) deg -= 180;
          else deg += 180;
      }   // deg は -90 ~ 90
  }

次のようにすることにより、道路名などが見やすくなった。 境界線の内側に描く都道府県名についてはこのまま回転させると、境界線の内側でなくなってしまう。 このため、当面は境界線に文字の下が接する形での描画とする。 Matrixが自由自在に使うこなせるようになったとき再トライしたい。

    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs, float dy) {
        r.ixNode = 0;
        r.offset = 0;   // ixNodeからのオフセット。複数線分になることもある。
        Bitmap[] reversed = reverse(chrs);
        while (true) {
            int rtn = drawBitmapMulti(canvas, r, chrs, dy, 0, true);   // チェック
            if (rtn == 2)  {
                break;    // 終了
            } else if (rtn == 1) {
                r.offset += Map.PX * 0.05f; // 再試行 スペース(狭い)を空ける
            } else {
                Val boundary = getVal(Key.boundary);
                boolean fAdmin = (boundary == Val.administrative);
                int addDegree = fAdmin ? 0 : rtn;
                drawBitmapMulti(canvas, r, addDegree==0 ? chrs : reversed, dy, addDegree, false);    // 描画実行
                r.offset = Map.PX * 0.5f;
            }
        }
    }

    static Bitmap[] reverse(Bitmap[] v) {
        Bitmap[] reversed = new Bitmap[v.length];
        for (int i = 0; i < v.length; i++) {
            reversed[v.length - i - 1] = v[i];
        }
        return reversed;
    }

2024.3.25 都道府県境界線の描画

通常の車道や川名はラインの中心に描画するが、都道府県境界線の場合、境界線の内側に都道府県名を描画する。

Matrix操作でラインとは垂直方向にずらせるはずであるが、慣れていないので試行錯誤となる。

プログラム上は dx = (xe - xb)/num、dy = (ye - yb)/num が線分方向で、 垂直方向は dx と dy が入れ替わるはず、どちらかの符号が変わったかも知れない。

線分単位の処理で、線分(xb,yb)-(xe,ye) を垂直方向の平行移動させてから、 現在のプログラムを実行すればよいが、現在の平行移動プログラムでは三角関数を使っているので時間がかかる。

都道府県境界線レコードのノード数は膨大なため、Matrixを使う方が効率的であろう。

現在のMatrix操作は次の2行である。これは水平方向の w * h画素のビットマップを deg だけ回転させた後、 平行移動させたものである。 (xb, yb)は線分の始点である。

  // ラインの中心に文字を描く
  matrix.preRotate(deg, w/2, h/2);
  matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);

道路のようにラインの中心に描くか、境界線の内側に描く二つとする。 線から離す場合には、ビットマップの上下にマージンを置くことにする。

試行錯誤の結果、境界線の内側に描くには例えば、次のようにすればよいことが分かった。 これで十分というより、これがベストと言える。

  // 文字はラインと交差しないように少し離して描く
  matrix.postTranslate(xb, yb);
  matrix.postRotate(deg, xb, yb);

上記二つのタイプのいずれかは float dy によって決める。dy = 0 のときは、車道名のようにラインの中心に描く。 都道府県境界線などでは、実質上は0となるような、十分に小さい値を引数とする。 中心線から規定値より少し離したい場合には、その値を設定する。中心線の上(左)か下(右)かは符号による。

  void drawTextMulti(Canvas canvas, Renderer r, String text, int color, float size, float dy, float halo)

ラインの中心に文字を描く場合、その領域は得ることができた。 ライン中心が離した場合、その領域がまだ掴めていない。

しかし、中心線上に描く場合と中心線と交わらないように描く差は文字の高さ半分の違いに過ぎない。 重なり回避に関しては、中心線上に描くと仮定して決定した結果を使っても、実際上の差は殆ど起きない。 従って、当面はその方法をとる。

2024.3.25 線分のつなぎ目の調整

線分の末尾が半文字未満のときは描画を止める。 従って、末尾には半文字分未満の空きがあるケースとはみ出しがあるケースがある。 次の線分の描画ではこれを考慮して、先頭より前から描きだすケースと 先頭から少しずらして描き始めるケースがある。

次の線分を基準として、前の線分の文字がはみ出した分を正の値で表す。前の線分の末尾に空きがある場合、 その幅をマイナスで表す。

まず、プログラムを簡単にするため、少しでもはみ出そうなときは描画をやめ、次の線分とした。 従って、次の線分は offset が正になることはない。

以下(赤字)の追加修正で道路名、川名の線分継ぎ目での不自然さはほぼなくなった。

市区町村境界線の場合、入り組んでいるため、線分と線分の角度の角度が大きい場所への描画を避けたい。

            if (dist < w) {     // w/2
                // 次の文字(幅は同じと仮定した)はこの線分には描けない
                if (n >= num_nodes * 2) {
                    return 2;   // このレコードには全文字は描けない
                }
                float rest = dist;  // 前の線分の末尾の空き
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = (float) distance(xb, yb, xe, ye);
                deg = (float) degree(xb, yb, xe, ye);
                if (rest != 0) {
                    num = dist / rest;    // 空きの何倍のおおきさか
                    xb -= ((xe - xb) / num);   // 空き描画で進める大きさ
                    yb -= ((ye - yb) / num);
                    dist += rest;
                }
            }

2024.3.24 橋名が描画されていない[解決]

1、2日前には描画されていたが、また、描画されなくなった。

1線分だけのレコードが除外されていた。

2024.3.24 他の地物のアイコンや文字との重なり回避機能を入れた[Map419]

まだ、線分のつなぎ目で改善の余地はあるが、アイコンや他の文字列との重なり回避機能を入れた。

重なり判定では、文字の回転を考慮していないが、矩形領域を描画してみて、 概ね文字が矩形領域に包含されていることを確認した。重なり判定はこれで十分とみられる。 必要に応じて、矩形にマージンを加えればよい。

    /* Renderer のメンバー変数
    int ixNode;         // 先頭の線分は #ixNode - #ixNode+1 である
    float offset;       // 文字列描画のスタート位置
     */
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        r.ixNode = 0;
        r.offset = 0;   // ixNodeからのオフセット。複数線分になることもある。
        while (true) {
            int rtn = drawBitmapMulti(canvas, r, chrs, true);   // チェック
            if (rtn == 2)  {
                break;    // 終了
            } else if (rtn == 0) {
                drawBitmapMulti(canvas, r, chrs, false);    // 描画実行
                r.offset = Map.PX * 0.5f;
            } else {   // rtn == 1: 再試行、重なりで描画できない
                r.offset += Map.PX * 0.1f; // 再試行 スペース(狭い)を空ける
            }
        }
    }

    // chrs[] をラインに沿って描画する
    int drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs, boolean check) {
        float dist = 0, deg = 0, num;
        float xb = 0, yb = 0, xe = 0, ye = 0;

        int n = r.ixNode * 2;  // 線分を指す
        xe = pnts[n++];   // 最初の始点座標にセットされる
        ye = pnts[n++];   // 同上
        float offset = r.offset;
        for ( ; offset >= 0 && n < num_nodes * 2; offset -= dist) {
            xb = xe;            // 線分の始点x座標
            yb = ye;            // 線分の始点y座標
            xe = pnts[n++];     // 線分の終点x座標
            ye = pnts[n++];     // 線分の終点y座標
            dist = (float) distance(xb, yb, xe, ye);
            deg = (float) degree(xb, yb, xe, ye);
            if (dist > offset) break;      // この線分から描画できる
        }
        //if (n >= num_nodes * 2) {
        //    return 2; // 1文字も描けない
        //}  これは間違い。1線分だけでも文字列が描けることが多い。橋は1線分が多い。
        if (offset > 0) {
            num = dist / offset;
            xb += (xe - xb) / num;
            yb += (ye - yb) / num;
            dist -= offset;
        }   // 残りのoffsetなし。(xb, yb)から文字を描く

        Matrix matrix = r.matrixMultiIcon;
        for (Bitmap chr : chrs) {
            float w = chr.getWidth();
            float h = chr.getHeight();
            num = dist / w;    // 何文字描けるか
            float dx = ((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = ((ye - yb) / num);
            float x1 = xb-w/2+dx/2;
            float y1 = yb-h/2+dy/2;
            float x2 = xb+w/2+dx/2;
            float y2 = yb+h/2+dy/2;
            if (check) {
                //canvas.drawRect(x1, y1, x2, y2, r.paintRedPen);
                if (r.intersects(x1, y1, x2, y2)) {
                    return 1;   // 交差有り
                }
            } else {
                //==== 一文字描画する ====
                matrix.reset();
                matrix.preRotate((float) deg, w / 2, h / 2);
                matrix.postTranslate(x1, y1);
                canvas.drawBitmap(chr, matrix, null);

                if (rectTile120.intersects(x1, y1, x2, y2)) {
                    RectF rect = r.rects[r.num_rects++];
                    rect.set(x1, y1, x2, y2);   // 追加登録
                }
            }
            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 yeに向かって進む
            dist -= w;
            if (dist < w/2) {
                // 次の文字はこの線分には描けない
                if (n >= num_nodes * 2) {
                    return 2;   // このレコードには全文字は描けない
                }
                float rest = dist - w;
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = (float) distance(xb, yb, xe, ye);
                deg = (float) degree(xb, yb, xe, ye);
            }
        }
        n -= 2;
        if (!check) {
            r.ixNode = n/2;
            //r.offset
        }
        // 最後の線分の後ろに dist 分のスペースがある
        return 0;   // 全文字の処理を終わった
    }

2024.3.23 つなぎ目での重なりは殆どなくなった。

線分のつなぎ目で文字間隔が空くことがある。重なりも皆無ではない。

元に戻れるように細かく記録を残しておく。

文字単位の描画は自作地図アプリでは初めての試みのため、急がず、 十二分に時間をかけて、より分かりやすいプログラムを目指す。

できれば、プログラムをもっとシンプルにしたい。また、継ぎ目の処理を分かりやすくしたい。 継ぎ目で角度差が大きい時は、描画を止めた方がいいだろう。角度差が小さい時は継ぎ目でのスペースを もっと小さくしたい。

drawBitmapMultiのチェック時の戻り値は成功、再試行、失敗の三つとしたい。線分の状態を引数とする。 状態としては、線分番号、線分の長さ、線分オフセットとする。チェックモードでは変更しないが、 描画モードではこれらの値を先に進める。再試行は、現在の線分番号、オフセットでは重なりが発生するが、 まだ、残りの線分に可能性があるときである。失敗は一部の文字が描けるとしても全部は描けない場合である。

dx、dy については当初、三角関数がいるかと思ったが、簡単に算出できた。

Matrix#postTranslateメソッドの引数は分かりにくいが、試行錯誤で、正解らしきものにたどり着いた。 記録を取りながら、試行錯誤で、少しずつ、ゴールに近づけばよい。

現在のプログラムは連続的に複数回文字列を描画するようになっているが、これを1回限りに変更する。 別途、親プログラムを作り、戻り値が失敗となるまで、これを繰り返し実行する。

線分番号、線分の長さ、線分オフセットは新たなclassの導入はひとまず見送り、 Renderer のメンバー変数とする。

    // chrs[] をラインに沿って繰り返し描画する
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
        int k = 0;  // 文字を指す
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        double dist = distance(xb, yb, xe, ye);
        double deg = degree(xb, yb, xe, ye);
        // 最初の線分は先頭から描画を始める
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
            double num = dist / w;    // 何文字描けるか
            float dx = (float)((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = (float)((ye - yb) / num);

            //==== 一文字描画する ====
            matrix.reset();
            matrix.preRotate((float)deg, w / 2, h / 2);
            matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);
            canvas.drawBitmap(chrs[k], matrix, null);
            //canvas.drawRect(xb, yb, xb+2, yb+2, r.paintRedPen);
            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 xeに向かって進む
            dist -= w;
            if (dist < w) {
                // 次の文字は描けない
                if (n >= num_nodes * 2) {
                    break;  // 線分終了
                }
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = distance(xb, yb, xe, ye);
                deg = degree(xb, yb, xe, ye);
            }

            if (++k >= chrs.length) {
                // 全文字描き終わった
                k = 0;
                float space = Map.PX * 0.3f;
                if (dist > space + w) {
                    // まだ、この線分に描ける可能性がある。
                    num = dist / space;
                    dx = (float)((xe - xb) / num);  // space分の移動量
                    dy = (float)((ye - yb) / num);
                    dist -= space;
                    xb += dx;
                    yb += dy;
                } else if (n < num_nodes * 2) {
                    // スペースの確保を次の線分に引き継ぐ
                    space -= dist;  // 残りのスペース
                    while (n < num_nodes * 2) {
                        xb = xe;
                        yb = ye;
                        xe = pnts[n++];   // 線分の終点x座標
                        ye = pnts[n++];   // 線分の終点y座標
                        dist = distance(xb, yb, xe, ye);
                        deg = degree(xb, yb, xe, ye);
                        if (dist < space) {
                            space -= dist;  // まだ、空きが足りない
                        } else {
                            num = dist / space;
                            dx = (float)((xe - xb) / num);  // space分の移動量
                            dy = (float)((ye - yb) / num);
                            xb += dx;
                            yb += dy;
                            dist -= space;
                            break;  // 文字描画を始める
                        }
                    }
                } else {
                    break;  // 全線分終了
                }
            }
        }
    }

2024.3.23 プログラム修正

線分のつなぎ目で文字が重なることがある。

    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
        int k = 0;  // 文字を指す
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        double dist = distance(xb, yb, xe, ye);
        double deg = degree(xb, yb, xe, ye);
        // 最初の線分は先頭から描画を始める
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
            double num = dist / w;    // 何文字描けるか
            float dx = (float)((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = (float)((ye - yb) / num);
            // 一文字描画する
            matrix.reset();
            matrix.preRotate((float)deg, w / 2, h / 2);
            matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);
            canvas.drawBitmap(chrs[k], matrix, null);
            //canvas.drawRect(xb, yb, xb+2, yb+2, r.paintRedPen);

            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 xeに向かって進む
            dist -= w;
            if (dist < 0) {
                // 次の文字は描けない
                if (n >= num_nodes * 2) {
                    break;  // 線分終了
                }
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = distance(xb, yb, xe, ye);
                deg = degree(xb, yb, xe, ye);
            }

            if (++k >= chrs.length) {
                // 全文字描き終わった
                k = 0;
                float space = Map.PX * 0.3f;
                if (dist >= space) {
                    // まだ、この線分に描ける可能性がある。
                    num = dist / space;
                    dx = (float)((xe - xb) / num);  // space分の移動量
                    dy = (float)((ye - yb) / num);
                    dist -= space;
                    xb += dx;
                    yb += dy;
                } else if (n < num_nodes * 2) {
                    // スペースの確保を次の線分に引き継ぐ
                    space -= dist;  // 残りのスペース
                    while (n < num_nodes * 2) {
                        xb = xe;
                        yb = ye;
                        xe = pnts[n++];   // 線分の終点x座標
                        ye = pnts[n++];   // 線分の終点y座標
                        dist = distance(xb, yb, xe, ye);
                        deg = degree(xb, yb, xe, ye);
                        if (dist < space) {
                            space -= dist;  // まだ、空きが足りない
                        } else {
                            num = dist / space;
                            dx = (float)((xe - xb) / num);  // space分の移動量
                            dy = (float)((ye - yb) / num);
                            xb += dx;
                            yb += dy;
                            dist -= space;
                            break;  // 文字描画を始める
                        }
                    }
                } else {
                    break;  // 全線分終了
                }
            }
        }
    }

2024.3.23 一方通行の矢印がスペースなく描画されている

2024.3.23 マルチポリゴンによるペデストリアンデッキの枠線が太すぎる。

multipolygonの除外をとった。

2024.3.23 highway=service, area=yesエラー

一度修正したエラーが元に戻っている。

なぜか、コメントアウトしていた。解除すると、正常に描画された。

2024.3.23 ゴールに近づいた

線分が変わったところで更新するのが distance(線分の長さ)と degree(線分の角度)であり、 文字が変わったところで更新するのが w、h、dx、dy である。

Matrixで難しいのは postTranslate の引数である。多分、回転前の文字ビットマップの中心座標であろう。 (xb-w/2、yb-h/2)とした場合、先頭文字の半分が線分の先頭より前に出てしまう。

先頭をピッタリ合わせるには (xb-w/2+dx/2、yb-h/2+dy/2) となる。 (xb-w/2+dx、yb-h/2+dy) とすれば、先頭一文字の半分が前に空く。

ここはどちらでもいいが、先頭に文字半分のスペースを空ける場合、distance から w/2 を引いておく必要がある。

分かりにくいのは回転前の w/2 と h/2 と回転後の dx、dy は同居している点である。 しかし、描画結果ではこれが一番良好な結果となっている。

下のプログラムでは線分の末尾に1文字未満のスペースが生まれる。 次の線分はこれを考慮しないため、このスペースが描画結果に現れる。

文字列間のスペースについても、前の線分で十分なスペースが確保できなかった場合、 次の線分の先頭で残りのスペースを確保してから描画を始める必要がある。この処理は未完成である。

「○○通り」で「通」と「り」の間にスペースが入ることはなくなった。 しかし、「り」の後ろが少し切れる。これは自分のプログラムのせいではない可能性が高い。 とりあえず、例外処理で目立たなくした。

頻度は少ないが、線分のつなぎ目で文字が中心からずれることがあるが、まずはこれでよしとして、 次のフェーズに移る。

大きくは似た処理を二度行う。一度目は実際の描画は行わず、 文字列中の全文字が重なりなく描けるかどうかを調べる。描けることが分かれば、二度目の処理で描画を行う。

全線分が終わって描けなかった場合はそれで終わり。 そうでないときは、適当なスペースを空けて、同じことを行う。

メソッドにチェックフェーズか描画フェーズかを区別するフラグ引数 boolean check を追加する。 check が true のときは、重なり判定のみ行い、描画は行わない。check が false のときは 描画を行い、 文字Bitmapの矩形を登録する。回転した矩形は登録できないため、とりあえず、対角線を1辺とする正方形を登録する。 中心座標は (xb-w/2+dx/2, yb-h/2+dy/2) である。

    // chrs[] をラインに沿って繰り返し描画する
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
        int k = 0;  // 文字を指す
        //float rest = 0;
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        double dist = distance(xb, yb, xe, ye);
        double deg = degree(xb, yb, xe, ye);
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
            double num = dist / w;    // 何文字描けるか
            float dx = (float)((xe - xb) / num);   // 一文字描画で進める大きさ
            float dy = (float)((ye - yb) / num);
            if (dist < w) {
                // この線分には描けない
                if (n >= num_nodes * 2) {
                    break;  // 全線分終了
                }
                // 新たな線分を取り出す
                //rest = w - dist;    // 前の線分の末尾のスペース
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
                dist = distance(xb, yb, xe, ye);
                deg = degree(xb, yb, xe, ye);
            }
            // 一文字描画する

            matrix.reset();
            matrix.preRotate((float)deg, w / 2, h / 2);
            matrix.postTranslate(xb-w/2+dx/2, yb-h/2+dy/2);
            canvas.drawBitmap(chrs[k], matrix, null);

            xb += dx;    // 一文字分 xeに向かって進む
            yb += dy;    // 一文字分 yeに向かって進む
            dist -= w;
            if (++k >= chrs.length) {
                k = 0;
                float space = Map.PX * 0.3f;
                while (n < num_nodes * 2) {
                    if (dist >= space) {
                        num = dist / space;
                        dx = (float)((xe - xb) / num);  // space分の移動量
                        dy = (float)((ye - yb) / num);
                        dist -= space;
                        xb += dx;
                        yb += dy;
                        break;      // スペースが現在の線分で確保できた
                    } else {
                        // スペースの確保を次の線分に引き継ぐ
                        space -= dist;  // 残りのスペース
                        xb = xe;
                        yb = ye;
                        xe = pnts[n++];   // 線分の終点x座標
                        ye = pnts[n++];   // 線分の終点y座標
                        dist = distance(xb, yb, xe, ye);
                        deg = degree(xb, yb, xe, ye);
                    }
                }
            }
        }
    }

2024.3.23 文字の先頭が線分の始まりより前にある

文字の中心が先頭に一致している。最初に xb += dx; yb += dy; を実行してから文字を描くことにしたい。

長い橋には橋名が二度以上かけることもあるが、通常は1回である。1回の場合、中間に描くのが見栄えがよい。

2024.3.22 現アプリとの折衷案はどうか

文字列全体が一つの線分内に描画できるときは文字毎の描画ではなく、一括して描くことにしてはどうか。 文字間隔はバランスが良い。

二つの線分に分かれるときは、二組または両端と折れ曲がる所の1文字に分ける案もある。

一文字ずつでもこれと同等の結果が簡単に得られるならばそれでよいが。

2024.3.22 個々の文字間隔が不適切

道路の中心から文字がずれることはなくなった。文字列が一つの線分に描ける場合、ほぼ問題はなくなった。 ただし、幅の狭い開かな交じりのとき、文字間隔が空きすぎる。

文字の Bitmap を作るとき日本語の文字幅は固定長の方がいいかも知れない。縁取りを考慮してマージンを加えたが、 これが大きすぎるかも知れない。 しかし、文字間隔が不揃いである。dx、dy に問題がある。一文字ずれているかも知れない。 postTranslateの引数に問題がある可能性が高い。

Paint#getTextBoundsで得られる矩形を文字と共に描画してみたが、「こ」とか「り」のとき良くないようだ。 他に適切なメソッドが見つからない場合、個別に補正するのがいいだろう。 漢字に対しては適切な結果が得られている。

また「通り」の場合、「通」と「り」の間に他より大きいスペースが空くことが多い。これは自作プログラムの不具合であろう。

2024.3.22 道路名の描画(2)

今回、初めての試みのため、そう簡単にはいかない。String から Bitmap配列 を生成する方は単純であり、 概ね問題はないようだ。ただ、縁取りがうまく働いていないようだ。

道路上に、道路名が概ねうまく描けているところと大きく外れているところが半々くらいである。 プログラムにちょっとしたバグがあるのであろう。

Matrix演算を次のようにしているが、実のところこれが良く分かっていない。 postRotateはBitmapの中心で回転させるのでこれでいいのであろう。問題は postTranslate の引数である。 X, Y軸の移動量はこれでいいのだろうか? -dx/2、-dy/2 はいるかいらないか?

   matrix.postRotate(deg, w/2, h/2);
   matrix.postTranslate(xb-dx/2-w/2, yb-dy/2-h/2);

単純に matrix.postTranslate(xb, yb); としても、所々、道路から大きくずれる文字がある。 xb、yb の算出に問題が残っているのだろう。

一文字ごとに、xb、yb を次式で変更している。線分には傾きがあるため、一文字の移動量 dx、dy を次式で決めている。 dist は線分の長さである。w は文字ビットマップの幅である。xb、yb はだんだん xe、ye に近づいていくので、 これでいいように思う。

  float num = dist / w;    // 何文字描けるか
  float dx = (xe - xb) / num;   // 一文字描画で進める大きさ
  float dy = (ye - yb) / num;
  xb += dx;
  yb += dy;

Matrixの動作が予想とは違っている可能性もある。 回転はさせなくてもいいので、Matrixを使わずに、Bitmap を (xb, yb) の位置にコピーしてみょう。 要するに、Matrix操作がおかしいのか、(dx、dy) がおかしいのか切り分けたい。 うまく行けば、Matrix操作は回転だけに限定してもよい。

あるいは上のプログラムで xb、yb を変化させ、小さな点か十字線だけを描いてみる。 要するには、上の計算が正しいかどうかを確かめたい。


xb、yb の位置確認は次の一行で行えた。Matrix演算の前に、この座標計算に問題があることが判明した。

文字列と文字列間のスペースをやめると、この赤点がライン中心を外れることは殆どなくなった。 極まれに少しずれているのは、線分と線分のつなぎ目近辺と思われる。 従って、バグは文字列と文字列間のスペースにある。また、一つの文字が二つの線分をまたぐことがあるので、その処理に注意がいる。

文字列と文字列間のスペースは線分をまたぐこともあるため、思ったより厄介である。 スペース文字を描く形式の方がプログラムは楽かも知れない。

一つの文字が線分をまたぐケースが厄介である。傾きは前の線分のものでいいとしても、(dx、dy)が二つにわかれることになる。 前の線分で何%分か終わったとして、残りの分は後の線分の角度は前とは異なるために、(dx、dy)の算出をやり直す必要がある。 文字が線分をまたぐと計算が面倒である。

線分の残りが文字幅の半分以上であれば描く、半分未満であれは書かない。描いた場合、はみ出した幅(ドット単位)、 描かなかった場合、残りの幅を次の線分に引き継ぐ。

次の線分では、線分の先頭から文字を描くのではなく、はみ出し幅を空けて描き始める。 残り幅があれば、その分先頭より前から、文字を描き始める。

このような細かい調整を行わなかった場合、線分をまたぐところで、文字間隔が広がったり、少し文字が重なったりするであろう。

 canvas.drawRect(xb, yb, xb+2, yb+2, r.paintRedPen);

スペースで dy とすべきを dx としていたミスがあった。道路中心からのはみ出しはこれが原因だったようだ。

2024.3.21 道路名の描画(1)

カーブに沿って描く道路名などの描画はアイコンをカーブ沿って並べるのに似ている。 特に、一方通行を表す矢印アイコンの場合、それに近い。

一般に道路などは複数の線分で構成される。カーブしている道路に道路名を描く場合、 全ての文字を同じ線分上に描くとは限らず、途中で文字の傾きが変わることもある。

しかし、名前の場合、これよりは色々と面倒である。複数の文字をセットとする必要がある。 矢印のように道路の進行方向に合わせて文字列を並べるわけではない。 例えば、東西に走る上下線が分かれた道路の場合、上りと下りは向きが逆であるが、 描画は同じで、左から右に文字列を並べる。

アイコンの場合、下地が何であっても無条件に描きならべるが、道路名の場合、 すでに別の文字やアイコンが描画されている場所は避ける。 全ての文字が重なることなく描画できる場合にのみ描画する。

まずは、重なりは気にせず、下のアイコン描画プログラムを参考にして 文字を描きならべてみよう。

個々の文字は Bitmap で表せるので、とりあえず drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) メソッドとしてみる。文字は反転(180度回転)させることがある。 そのときは、文字は後ろから順に描くことになるだろう。 これらについては追って考える。

下のプログラムは線分毎の処理で、一つの線分には 複数のアイコンが並べているため、二つの for 文になっている。

    // iconをラインに沿って繰り返し描画する
    void drawIconMulti(Canvas canvas, Renderer r, Bitmap bmp, float space) {
        if (bmp == null) return;
        float width = bmp.getWidth();
        float height = bmp.getHeight();
        Matrix matrix = r.matrixMultiIcon;
        float prv_x = pnts[0], prv_y = pnts[1];
        for (int k = 1; k < num_nodes; k++) {
            float x = pnts[k*2];
            float y = pnts[k*2+1];
            float dist = distance(x, y, prv_x, prv_y);
            int num_icons = (int)((dist + 0.9f)/(width+space));
            float dx = (x - prv_x) / num_icons;
            float dy = (y - prv_y) / num_icons;
            float deg = degree(prv_x, prv_y, x, y);
            float x1 = x, y1 = y;
            for (int n = 0; n < num_icons; n++, x1 -= dx, y1 -= dy) {
                matrix.reset();
                matrix.postRotate(deg, width/2, height/2);
                matrix.postTranslate(x1-dx/2-width/2, y1-dy/2-height/2);
                canvas.drawBitmap(bmp, matrix, null);
            }
            prv_x = x;
            prv_y = y;
        }
    }

一般に一つの道路等のレコードに複数回(0回、1回を含む)道路名等を描画することから、 全体が while文(またはfor文)とする。

必ずしも線分を区切りとはしないため、線分での for 文はない。 文字列が線分をまたがることがあるため、文字ごとの for文もやめる。

    // chrs[] をラインに沿って繰り返し描画する
    void drawBitmapMulti(Canvas canvas, Renderer r, Bitmap[] chrs) {
        Matrix matrix = r.matrixMultiIcon;
        int n = 0;  // 線分を指す
	int k = 0;  // 文字を指す
        float xb = pnts[n++];   // 線分の始点x座標
        float yb = pnts[n++];   // 線分の始点y座標
        float xe = pnts[n++];   // 線分の終点x座標
        float ye = pnts[n++];   // 線分の終点y座標
        float dist = distance(xb, yb, xe, ye);
        float deg = degree(xb, yb, xe, ye);
        while (true) {
            float w = chrs[k].getWidth();
            float h = chrs[k].getHeight();
      if (dist < w) {
                if (n >= num_nodes*2) {
                    break;  // 全線分終了
                }
                xb = xe;
                yb = ye;
                xe = pnts[n++];   // 線分の終点x座標
                ye = pnts[n++];   // 線分の終点y座標
        	dist = distance(xb, yb, xe, ye);
        	deg = degree(xb, yb, xe, ye);
            } else {
                // 一文字描画する
                matrix.reset();
                matrix.postRotate(deg, w/2, h/2);
                matrix.postTranslate(x-w/2, y-h/2);
                canvas.drawBitmap(bmp, matrix, null);
        	float num = dist/chrs[k].getWidth();	// 何文字描けるか
        	float dx = (xe - xb)/num;   // 一文字描画で進める大きさ
        	float dy = (ye - yb)/num;
                xb += dx;	// 一文字分 xeに向かって進む
                yb += dy;	// 一文字分 xeに向かって進む
                dist -= w;
                if (++k >= chrs.length) {
                    k = 0;
		    float space = Map.PX * 0.3f;
		    if (dist >= space) {
			dist -= space;
		    } else {
	            }
                    
                }  
            }
        }
    }

まだ、修正点は多く残っているが、道路名が道路に沿って描画されるようになった。

2024.3.20 アイコン、文字列を重なりや欠けることなく描画するのは難しい[Map419]

タイル境界の存在や文字列の傾きから適切な描画が難しい。 重なりだけを避けるはできるが、描画が減ってしまう。 完璧は難しいが、なるべく、標準OSM並みの描画を実現したい。 一から考え直す必要があるようだ。

カーブに沿った道路名などはこれまでずっと文字列ごとの描画であった。 Matrixを使って描画すると、実際の描画位置が良く分からない。 また、文字やアイコンの重なり判定を矩形で行っているため、傾いた矩形が扱えない。 したがって、実際の描画領域よりもはるかに大きい矩形領域を登録していた。

精度を上げるため、道路や都道府県境界線などに沿って描く文字列は、文字ごとの描画に変更する。 傾きのある矩形は扱いにくいため、重なり判定用としては、 傾きのある矩形を概ね包含するような水平方向の矩形を描画した領域とするが、 文字列毎に比べれば、格段に精度が上がる。


水平に文字を描いてそれを回転させる。水平線の上に文字を描く場合でも、それを180度回転させる場合には、 文字列上の文字は前から書くのではなく、後ろから順に書く必要がある。 多分、最初の線分の傾きで前か後ろかが決まるであろう。

2024.3.19 広域森林などマルチポリゴンの描画が消えた[解決]

ここ1、2日の変更が関係していると思われる。

マルチポリゴン描画の最後の処理をコメントアウトしていた。なぜかは、覚えていない。

2024.3.19 高尾山のエコーリフトの名前が描画されない

wayデータは始点、中間点、終点の3点だけであった。ノード数が少ないために、描画されなかったのであろう。 登山鉄道の場合、始点、終点近くを除くと、ノードが両端だけの way である。

ケーブルカーやロープウェイは通常の道路や鉄道と違う処理が必要なようである。

タイル境界内に限定していた。これを解除すると描画された。

ひょっとすると、道路や鉄道ではタイル内に限定した方が良いかもしれない。

2024.3.19 Map.Scaleはレンダリングメソッドで対応する

2024.3.19 drawLine簡素化

Paint引数をColor(int型)引数に変更。修正箇所は膨大であるが、簡素化を果たす。

2024.3.19 drawLine修正

Pathを使わず、線分毎に描画する方が高速であるが、鉄道の場合、カーブでは一つの線分の長さが短いため、 破線描画がうまく行かない。Pathを使うと見栄えががぜん良くなった。

低中ズームは高速描画になった。高ズームも近々この方法に変えたいが、レンダリングを修正した場合、 保存したタイル画像が無効になる。

しかし、レンダリングの修正は急がず、気が付いた時点で、描画を確認しながら行っているので、 こちらは長期戦である。

レストランとかコンビニだけなど、いずれ、特定のものだけ描画する機能を追加したいので、 このレンダリングとバス路線のレンダリングなど一部のレンダリングだけを切り離して、 地図のベースとなるタイル画像は一度作成したものをストレージに保存して、再利用する。 総合的な描画時間は 1/10~1/100 に短縮する。

レンダリング時間の短縮に拘ってプログラムを分かりにくくしているところは、少しずつシンプルにしていく。

2024.3.18 国立公園がレンダリングされていない

Relation処理で抽出されていなかった。

いくつかの修正を経て、所望のレンダリングが行われるようになった。

2024.3.18 低中ズームのタイル画像のストレージ保存開始[Map418]

やはり、タイル画像のストレージ保存が圧倒的に高パフォーマンスである。

動的変更がない低中ズームのタイル保存を開始した。

低中ズームはレンダリング時間を気にせず、見栄えを追求できる。

バス路線やアイコンなどをダイナミックに変更する場合、ベースとなるレンダリングと動的に変わる 部分のレンダリングを分離して、ベースの部分をタイル保存すれば大幅なパフォーマンス向上が図れる。 しばらくしてからこれを考えたい。

2024.3.18 低ズームのレンダリング時間

zoom 6では、時間のかかるタイルで、レコード数は6~10万、レンダリング時間は5~7秒である。 ラインレコードを空間検索で全て除外した場合、レコード数は 4,000~6,000 で レンダリング時間は4、5秒となる。レコード数は1/10 になるが、描画時間は2割ほど速くなるだけである。

道路等のライン描画にかかる時間はわずかで、大半が、巨大なポリゴン、マルチポリゴンの描画時間である。 都道府県境界線は現在は type = 1 レコードではなく、 Relation処理による ポリゴン/マルチポリゴンレコードで描画している。境界線にそって描画する都道府県名は Relation処理が必要であるが、境界線自体は way レコードでもいいはずなので、そうすれば少しは速くなる。

低ズームのレンダリング時間はレコード数に依存するのではなく、巨大なポリゴン/マルチポリゴンによる。 陸地ポリゴンも巨大であるが、数が少ない。やはり、一度レンダリングしたタイル画像をストレージに保存して おき、再利用するのは最も簡単で高速であろう。タイルのレンダリング時間を1秒未満にするのは簡単ではない。

バス路線など、タイルのレンダリングを動的に変更するのは、高ズーム(zoom 12または13以上)でよい。 高ズームではタイル画像のストレージ保存はしない。

2024.3.17 中低ズームのラインレコードを接続してレコード数を減らす[中止]

これは、即座に実行するのではなく、できるだけ簡単な方法が見つかるまで、熟慮する。

可変長バイトコード化も当初は実行時間がかかることから実施を見送っていたが、 熟慮の末、復号化にあまり時間がかからない方法を思いつくに至った。

中低ズームはレコード数が非常に多い。間引きによりレコード当たりのノード数は少ない。

リレーション route=train;subway;road を使って接続する案と、 ByteCoderの前処理で同じファイル内の鉄道、地下鉄、道路の終点座標と始点座標の比較で接続する案がある。 プログラム的には後者の方が簡単であろう。鉄道、地下鉄、道路毎に、タグの値が一致し、終点と始点が一致する場合、 つなぎ合わせる。しかし、交差点や鉄道のポイントである終点と始点が一致するものが二つ以上のケースがあるかも 知れない。最初に見つかったものをつなぐことにする。どれをつなぐかにより最終レコード数が変わることがあっても 問題ではない。

現在は osm_id をレコードに含めていないが、含める場合、先頭の osm_id をつなぎ合わせたレコードの osm_id とする。

中低ズームではバス路線番号リストを持っていないが、これを持つ場合には、バス路線番号リストもタグと同じように、 完全に一致する場合、繋ぎ合わせる。一部でも違いがあるものは繋ぎ合わせない。

境界ボックスは当然繋ぎ合わせた結果について求める。type = 1 だけを対象とするため、way_area は持たない。


レコードをつなぎ合わせる目途はついた。しかし、効果があまり期待できない。

ファイルサイズ上は中ズームでは type=1 レコードは少ない。(低ズームではtype=1が多い。)

また、道路等をつなぎ合わせると、zoom によっては実際には描画されない範囲についても 可変長バイトコードの復号化や座標変換を行う無駄が生じる。

レコード数が減っても余分な処理が増え、逆効果になる恐れもある。実際、どこに時間がかかっているか、 状況を掴んでからにする。

2024.3.17 マルチポリゴンで inner polygon のプールが塗りつぶされていない

アイコンは描画されている。inner (プール) が二つあり、一つは描画されている。

どうやら num_polysの値が 2 になっているようだ。

別のビルでは、inner polygon が6つのところが、3つしか穴あきになっていない。 1、3、5 が inner polygon になり、2 と 4 が無視されているようだ。 データ作成の Relation処理の誤りの可能性がある。

多分、listInnerPolygon.remove(i);によって、偶数番号の inner polygon が捨てられてしまうのであろう。

ループ内で要素を削除するのは良くない。outer polygon同士の重なりはないので、 一つの inner が二つ以上の outer polygon に含まれることはない。削除しなくても問題はない。

この結果、マルチポリゴンの inner polygon が全て描画されるようになった。

// inner polygon を outer polygon に所属させる
for (Polygon outer : listOuterPolygon) {
  for (int i = 0; i < listInnerPolygon.size(); i++) {
    Polygon inner = listInnerPolygon.get(i);
    if (outer.bbox.contains(inner.bbox)) {// 暫定 いずれポリゴン内点判定に変更
      listInnerPolygon.remove(i);
      outer.listInner.add(inner);
    }
  }
  if (outer.listInner.size() == 0) {
    //System.out.printf("id=%d no inner\n", rel.id);
  }
}

2024.3.16 車道がタイル境界線付近で描画されていない

空間検索ではマージンをいれている。ブロック分割にも以前と同じマージンを加えた。 これで描画された。

2024.3.16 ポリゴン/マルチポリゴンの先頭ノードと最終ノードの座標が一致しない[解決]

差が小さい場合、レンダリングでは気がつかないが、殆どの場合、わずかな差がある。 符号化か復号化のどちらに誤りがあるようだ。境界線の場合、ノード数が多いため、目立つようだ。 ノード数が少ない場合、差は小さいようである。下の例では、+2 ~ +4 である。

type=2 (1394989335, 355543050) (1394989337, 355543052)     (+2, +2)
type=2 (1395069981, 355530949) (1395069984, 355530953)     (+3, +4)

符号化の -1 が関係しているのであろう。復号化ではこれに相当するものを入れていない。 これに合わせた結果エラーがなくなった。

    static void ConvertSLong(ByteBuffer buf, long v) {
        long u = v<0 ? ((-v)<<1) - 1 : v<<1;
        ConvertULong(buf, u);
    }

2024.3.16 タイル4-8-3の陸地が描画されない[解決]

陸地ポリゴンの低ズーム分割を zoom 1 から zoom 0 に変更したが、 zoom 0 の分割ファイルをパソコン側で作っただけで、タブレットにコピーしていなかった。

2024.3.16 境界線に沿って描く県名や市名の位置が大きく(数十メートル)ずれている[解決]

可変長バイトコード化前はおかしくなかった。

町田市と横浜市の境界線は同時に東京都と神奈川県の境界線であるが、 リレーションで作られるレコードは異なる。

描画では県境と市境がずれている。経度、緯度差を可変長バイトコード化している。整数のため、 誤差の累積は起きない。どこかの1点で誤りがあり、それが全体のずれを生んだものと思われる。

大抵は1,2バイトコードであるが、稀に、3、4バイトになるであろう。1、2バイトの復号化にはバグはなく、 3バイトか4バイトの復号化に誤差があれば、このような現象が起こりうる。

また、符号化のエラーの可能性は皆無とは断定できない。 しばしば起こるエラーならば、もっと色んな所にでるので、そうではない。

レンダリングスレッド数を1にしても、同じであった。

int getUInt() は結果が非負の範囲であれば問題ないが、負のときは要注意。例えば long v = getUint() & 0xffffffff; として正の値に変化する必要がある。元々 int 型で非負の数値を 符号なし可変長バイトコードに符号化しているはず。 int getSInt() は問題ないはず。

境界線以外でも getSInt() の結果が4バイトのケースは多くある。しかし、今のところ境界線以外では異常は見つかっていない。 なお、結果が5バイトは見つかっていない。getUInt()では、結果が4バイト以上はなかった。

エラー原因は可変長バイトコードの復号化ではないかも知れない。

境界線エラーはいろんな場所で頻発している。

zoom 15 で高圧線と思われるものが道路ほどの太い線になったことが二度ある。再現性はない。 境界線のずれとは無関係であろうが、これもバグであろう。

マルチポリゴンについても、森林は正しく描画されているし、巨大なユーラシア大陸、 その inner polygon であるカスピ海にも異常はない。

しかし、ユーラシア大陸の北側でレンダリングされないタイルがあった。zoom 4 で2か所が描画されない。 レンダリングするのは同じユーラシア大陸レコードであり、場所によってレンダリングされないことが起きる。[解決]

境界線のずれは復号化処理で -1 が漏れていたのが原因だった。

2024.3.15 widをバイナリレコードに追加した

バイナリレコードの先頭バイトはこれまで type だけであったのを上位6ビットを wid とした。

小さいポリゴン/マルチポリゴンレコードは way_area ではなく、 境界ボックスの width、height を使うことにする。

zoom 0 では width、height が1度程度のものは無視してもよいだろう。zoom 10では 0.001度、 一般に 1/(1<2024.3.15 中ズームの抽出レコードを絞り込む

中ズームのレンダリング時間はタブレットで1~2秒である。レコード数は数万が多い。最大は20万越え。 中ズームファイルは現在は zoom 10と11 で使っている。使用範囲は試行錯誤で変えている。

zoom 10や11では使わないレコードも含まれている。完全に使用範囲が固まれば、 そのようなレコードは含むべきではないが、試行錯誤段階では、バイナリレコードファイルの取り直し 頻繁に行うには時間がかかるため、中低ズームにはどちらかといえばやや多めで余分なレコードを含んでいるといえる。 zoom 11 では使うが、zoom 10 では使わないレコードもある。

したがって、レコード抽出では空間検索だけでなく、タグによる絞り込みも行った方がよい。 元々レンダリングでは除外されているレコードのため、正味のレンダリング時間は変わらないが、 無駄な可変長バイトコードの復号化や座標変換がなくなり、使用メモリも少なくなる。


これを実現するには、バイナリレコードの仕様変更が必要となる。タグデータはレコードの末尾にあり、 その前にある可変長バイトコードを復号化しないとタグ部の先頭が分からない。

以前は、タグ部を前に置いていたが、OSMデータに合わせて後ろに移動させた。

座標値データの前にあるのはノード数であって、座標値データの長さではない。 ノード数を長さに変えることもできるが、マルチポリゴンではポリゴンの長さを合計する必要がある。

以前のように座標値データを後ろに移動し、前に移動したタグ部の先頭にはタグ部の長さをおく方が分かりやすい であろう。

しかし、バイナリレコードの仕様は軽々に変更すべきではない。以前は道路種別を表すIDである wid(1バイト) を バイナリレコードに持っていた。空間検索段階の絞り込みは道路などラインレコードではこの wid、 ポリゴンレコードでは way_area となるであろう。ポイントレコードは少ないので絞り込みは要らない。

現在は、wid は直接的にはバイナリレコードにはない。タグから算出している。way_area もタグ扱いとしている。 これにより、レコードフォーマットがシンプルになっている。パフォーマンスを考えれば、wid、way_area を バイナリレコードの表に出した方がよい。wid は type と合わせて1バイトあるいは2バイトとする。 way_area は type=2、type=3 で存在する独立要素(float型)とする方がプログラムの修正は少なく、 パフォーマンス上もよい。

2024.3.15 滑走路を中ズームに追加する

標準OSM地図では滑走路は zoom 11 から描画されているようである。 これに合わせるには、中ズームに加える必要がある。

この追加で japan-mid.dat は 286,748MB から 286,759MB に微増した。

zoom 11から羽田空港の滑走路が描画されることを確認した。

2024.3.14 滑走路が描画されていない

MapX から renderAeroway をコピーした。

2024.3.14 中ズームのレコード数が非常に大きい

zoom 8 で35万レコードを超えた。できれば10万レコードくらいに抑えたい。

並列スレッド数を落として、動かした。最大レコード数は 383350 であった。

zoom 9までを低ズームに変えた。zoom 10 での最大レコード数は 206959 であった。 MAX_OSM を 250000 に設定した。パフォーマンスは大幅に改善した。

2024.3.14 OSMバイナリレコードによるレンダリング

まず、OSMバイナリレコードの可変長バイトコード化を行う。

2024.3.14 シンプルに

極力 BitmapFctory の使用を減らしてみたが、実行時間は変わらないか少し増える。

ファイルの読み込みに時間がかかる。海や陸のデコードは速い。また、メモリ獲得が起きても、 直ちに解放するので、ガーベージコレクションの負荷は気にするほどではない。

        if (new File(pathLands).exists()) {
            byte[] img = read(pathLands);
            if (Arrays.equals(img, land)) {
                OSM.fillRect(canvas, paintLand);
            } else if (Arrays.equals(img, sea)) {
                OSM.fillRect(canvas, paintWater);
            } else if (img != null) {
                Bitmap bmp = BitmapFactory.decodeByteArray(img, 0, img.length);
                canvas.drawBitmap(bmp, 0, 0, null);
                bmp.recycle();   //System.arraycopy(tile.bmp, 0, bmp, 0, bmp.length);
            }

2024.3.14 メモリの再利用ができない

ひとまず

   tile.bmp = loadBitmap(pathLands);
としたが、これではメモリの再利用ができない。loadBitmapの中心は以下であるが、 decodeStreamは動的に新たなメモリを確保して、そこに解凍したビットマップをセットする。
  return BitmapFactory.decodeStream(new BufferedInputStream(stream));

ざっと調べた限りでは、引数で格納先を与えてそこにビットマップをセットするようはメソッドの使い方はできないようだ。 その場合、このタイル画像が要らなくなったときはガーベージコレクションの対象となる。

少なくとも当面は BitmapFactoryで作られたメモリの再利用は断念する。再利用 tile.bmp にコピーして、 ガーベージコレクション対象とする。

decodeStream の代わりに decodeByteArray(byte[] data, int offset, int length) を使う。 byte配列として読み込み、600バイトであれば陸地タイルの600バイトと比較して、陸地と分かれば、Bitmapへの解凍はせず、 tile.bmpにCaavasを通して、矩形描画を行う。海と陸が交じり合ったタイルのみ decodeByteArrayを使う。

2024.3.14 陸地ポリゴンの精度を上げる

これまで zoom 9以下で精度の低い陸地ポリゴンデータを使っていたが、 この低ズームファイルの利用は zoom 7以下で使うように変更した。zoom 8、9 の精度が上がった。

2024.3.14 画像フォーマットをWebPとする

陸の単色タイルは384x384画素で 600B になった。

読み込み時間は Androidタブレットで平均的 5、6 mS となった。 zoom 16 で日本地図の陸地を表示した場合、全タイルが陸地タイルとなり、平均時間は 4mS となった。

全体的なレンダリング&描画時間は少なくとも 100~300 mS となることから、 陸地ポリゴンにかかる時間は十分に小さくなった。これまでは 100~200mS を超えていたと思われる。 ただし、陸地かどうかを予め調べて、それをデータとしてメモリに読込んでいたときがある。 その方法では、通常は1mS 未満であろう。しかし、海近くでは 100mS 以上かかる。

新たな方法は内陸では 4mS、海岸線近くでも 10mS 未満であり、また、プログラムが分かりやすい。 Map4では、この方法を採用する。

平均的なファイルサイズはそれほど変わらないかも知れないが、通常は陸地を表示しているため、 陸地タイル画像ファイルが小さい方が平均パフォーマンスが向上する。

陸の単色タイルは256x256画素でも同じ 600B になった。また 768x768画素でも同じ 600Bになった。

陸地ポリゴンの平均的なレンダリングは 0.1~0.2秒程度なので、陸地ポリゴンについては、 一度レンダリングしたものをストレージに保存し、再利用する方式とする。

一般のOSMバイナリレコードによるレンダリングについても zoom 10~12 以下では、 レンダリング結果が変わることはないので、レンダリング結果の再利用によるパフォーマンス向上を考えてもいいであろう。

レンダリングの見直しが多いため、陸地ポリゴンとは分離しておく。上位のタイル画像は陸地ポリゴンを含んでいるため、 上位のタイル画像があれば、これを表示するだけ、なければ陸地ポリゴンタイルを読み込んでレンダリングとなる。 陸地ポリゴンタイルもなければ、陸地ポリゴンのレンダリングから行うこととなる。

しばらくは、陸地ポリゴンのタイル画像保存のみとする。安定した段階で、上位タイル画像の保存を考える。

2024.3.14 海、陸タイルの保存について

単純に pngファイルにすると、384x384画素では1.46KBになった。Windowsの場合、非常に小さかった。 Windowsの場合、確か、非常に小さいファイルは、ファルダに置かれ、ファイル自体は作られなかったように思う。 ひよっとすると、フォルダ当たり一つの画像について、だったかも知れない。 Android にもファイルサイズ0(ファイル実体なし)のときフォルダだけにファイル情報が置かれるのであれば、 陸地のときは 123land.png、海の時は 123sea.png としてもよい。

画像ファイルを作らず、データで持つ方法もあるが、画像ファイルで統一したのが分かりやすい。

いずれにせよ、急ぐ話ではないので、試行錯誤してみよう。WebPという新しい画像フォーマットも試してみる。

2024.3.13 陸地ポリゴンレンダリング&描画時間

zoom 7で日本地図を表示した場合、タイル当たりの描画時間はタブレットで 0.2~0.25秒であった。 一度作成したファイルをストレージに保存しておけば、読み出し時間は半分くらいになるだろうが、 桁違いに速くなるわけではない。

本州ポリゴンは大きいため、それぞれのスレッドが同じポリゴンレコード を復号化して、座標変換している。ある一つのスレッドが復号化&座標変換したものを 他のスレッドが利用すれば、効率的である。複数のタイルにまたがる大きなレコードについてのみ このようなことができないものでろうか?

復号化は同じとしても、座標変換の結果はタイルの原点をベースにしたものであるから、 全く同じではなく、平行移動した値となる。また、異なるzoomで使う場合には拡大か縮小が必要となる。

しかし、縮小・平行移動を行うとすれば、バイナリレコードの座標を極座標ではなく平面座標としたのと同じである。 以前は極座標としていたものを一時はXY平面座標に変えた。しかし、分かりやすさから極座標に戻した経緯がある。

大陸、本州や北海道など特に大きな数少ないポリゴンに限定すれば以前とは異なるものが考えられる。 例えば、少数の大きなポリゴンに限り、一度読み込んだデータをメモリに常駐させる。 低ズームユーラシア大陸は巨大といっても 184,053ノードに過ぎない。0.5~1.0MBに過ぎない。

アクセス頻度の高いものはアプリ起動時にバックグラウンドでこれらのレコードを読み込めば、 使用時のレスポンスが大幅に改善するであろう。低ズーム用陸地ポリゴンデータは zoom 9用を常駐させた場合、 zoom 9のタイル描画では減算のみ、zoom 8以下では 0.5、0.25、0.125、... の乗算と減算で描画用 x, y 座標を求める。

これらのレコードは ByteCoder が分離し、特別なファイルに格納する。同じように空間検索により抽出するため、 ID は必要としない。ファイル上では可変長バイトコードであるが、メモリに常駐させるときに、 例えば 9/0/0 タイルのレンダリング用 x、y 座標に変換した値で格納する。

これにより、陸地ポリゴンのレンダリング時間がどれほど高速化されるかはやってみないと分からない。 一部の描画のみ、あるいは実際には何も描画されなくても、座標値データをPathに設定するだけでも時間がかかる。

Pathに設定したデータをMatrix演算で迅速に縮小・移動ができるならば、Pathに設定したデータを共用した方がよい。


一度レンダリングしたタイル画像をpngファイルとしてしまっておくのが簡単である。 しかし、タイル単位では、読み込みに時間がかかり、期待したほどのレスポンス向上が図れない可能性がある。

圧縮率が高ければ多くのタイルをメモリに載せられる。どの程度のサイズになるか、やってみないと分からない。

上記のプログラムを実装した結果、Androidタブレットで読み込み時間は10mS以下であった。 ファイルサイズは単色でも 1.46KB であった。 単色(完全に陸地または海)タイルはデータでの管理の方が簡単である。

2024.3.13 並列処理でエラー[解決]

ここまでは一旦レンダリングスレッド数を1としていた。これを2に変えるとエラーが起きた。

複数のスレッドが同じメモリにアクセスしている可能性がある。

まず、次の static byte[] buffer は同時に複数のスレッドで使えない。 I/Oの並列処理は難しいので、readAll はシリアル処理にするか、または、buffer を Renderer に移す必要がある。 この場合、総メモリサイズは 4MB x スレッド数に増える。

とりあえず、ストレージI/Oは並列処理効果が上げにくいことと、メモリ使用量を抑えるために、排他制御とする。

    static byte[] buffer = new byte[4*1024*1024];
    static Block readAll(String file, Block block) {
        // buffer を使う処理
    }

もっと厄介なのは可変長バイトコードの復号化である。ByteBuffer はオーバヘッドが大きいので、 復号化メソッドの引数とはせず、static byte[] ba、static int off をメソッドの外に置いていた。 復号化は並列処理が必須なので、排他制御は使えない。

メモリに置いた可変長バイトコード自体は変更を加えないので、複数のスレッドが排他制御なしに同時アクセスできる。 配列をさす byte[] ba や off はスレッド毎に必要となる。

現在は Block.getSInt() などは Renderer#getSInt() に移動する。

Block.getSInt(Renderer r) とすることもできる。

2024.3.13 タイル9/440/213の空間検索の吟味

まず、奄美大島が描画されている9/439/214タイルを調べる。 多分、num_nodes=342が奄美大島、num_nodes=117が徳之島であろう。 これらの境界ボックスを調べる。

ixOsm=10
 type=2 x=439 y=214 num_nodes=13
 type=2 x=439 y=214 num_nodes=117
 type=2 x=439 y=214 num_nodes=5
 type=2 x=439 y=214 num_nodes=16
 type=2 x=439 y=214 num_nodes=342
 type=2 x=439 y=214 num_nodes=19
 type=2 x=439 y=214 num_nodes=5
 type=2 x=439 y=214 num_nodes=60
 type=2 x=439 y=214 num_nodes=6
 type=3 x=439 y=214 num_nodes=184053
 type=2 x=439 y=214 (12917846,2828226) (12922174,2830709)
 type=2 x=439 y=214 (12917575,2806215) (12935476,2820129)
 type=2 x=439 y=214 (12927173,2801962) (12927935,2802696)
 type=2 x=439 y=214 (12914376,2801380) (12917886,2806937)
 type=2 x=439 y=214 (12913390,2810937) (12906676,2853076)
 type=2 x=439 y=214 (12920487,2800550) (12926916,2805506)
 type=2 x=439 y=214 (12916559,2819200) (12917275,2819830)
 type=2 x=439 y=214 (12887888,2766104) (12903795,2789395)
 type=2 x=439 y=214 (12915937,2810987) (12917653,2812345)
 type=3 x=439 y=214 (-950059,126576) (18000000,7772291)

赤字が奄美大島と思われるが、maxlon が minlon よりも小さくなっている。明らかに誤りである。 恐らく、width、height がそれぞれ 16ビット以下で表現できることから合わせて 32ビットとしたのであろう。 分割したとき、width が 16ビットで、負の値になったのであろう。

赤字の部分を加えることにより、奄美大島が正しく描画されるようになった。めでたしめでたし。

    width = (wh >> 16) & 0xffff;

2024.3.13 ブロック分割および空間検索

分割プログラムおよび空間検索に問題があることがわかった。 カスピ海が多くのファイルに重複しておかれている。 また、奄美大島近辺の空間検索で抽出されるのもおかしい。

日本地図領域では経度、緯度とも正の値であるが、世界地図では -180~180 である。 負の値の処理に問題がないかチェックしたい。


これは誤解だった。

大きなマルチポリゴンはユーラシア大陸であり、カスピ海はその inner polygon である。 ユーラシア大陸の境界ボックスは巨大なため、多くの分割ファイルに含まれる。 日本全体がユーラシア大陸の境界ボックスに含まれるため、低ズームでは、全てのタイルのレンダリング対象となる。

実際には描画が起きない、しかし、可変長バイトコードを復号して、座標変換を行う 無駄がある。 少数の巨大なポリゴンを例外処理で除外すれば、無駄が省ける。しかし、ユーラシア大陸の一部は描画したいことも あるため、例外処理が細かくなる。

また、低ズームの陸地ポリゴンファイルの分割は非効率なため、やめた方がよい。形式上 zoom 0 分割に変更した。

2024.3.13 奄美大島の描画がかける[解決]

奄美大島の描画が低ズームではタイル境界で切れている。例えば、9-440-213 および 9-440-214 で描画されない。 喜界島は描画されている。

空間検索のレコード数は2となっている。多分、奄美大島と喜界島であろう。 しかし、ノード数は 41 と 184053 であった。41 は小さすぎる。

低ズームでのマルチポリゴンはカスピ海だけのはず。 type 2の小さなポリゴンは二つのタイルのレンダリングにまたがる可能性は極めて低い。

 type=2 x=440 y=213 num_nodes=41
 type=3 x=440 y=213 num_nodes=184053
 type=2 x=440 y=214 num_nodes=117
 type=2 x=440 y=214 num_nodes=41
 type=3 x=440 y=214 num_nodes=184053

ByteCoderで type=3 のレコードをチェックしてみる。

どうやら、num_nodes=184053 は低ズームでただ一つのカスピ海のようだ。 そもそも低ズームで小さな島が巨大なノード数になることはない。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -bytecode lands low 1
type 3: c:\map\data4\lands-low1\0 0.dat 184053
type 3: c:\map\data4\lands-low1\0 1.dat 184053
type 3: c:\map\data4\lands-low1\1 0.dat 184053
type 3: c:\map\data4\lands-low1\1 1.dat 184053
type 3: c:\map\data4\lands-low1\2 0.dat 184053
type 3: c:\map\data4\lands-low1\2 1.dat 184053
type 3: c:\map\data4\lands-low1\3 0.dat 184053
type 3: c:\map\data4\lands-low1\3 1.dat 184053

このレコードはカスピ海ではなく、ユーラシア大陸である。その inner polygon がカスピ海である。 ユーラシア大陸は強大なため、分割した場合、複数のブロックに含まれる。したがって、上の結果は正常である。

空間検索結果ではnum_nodes=41は喜界島であろう。9/440/213 では奄美大島ポリゴンが漏れているのであろう。

なぜ漏れたかを詳しく調べたい。

2024.3.12 可変長バイトコードの採用

1週間余りの準備を経て、可変長バイトコードの採用に踏み切った。 まず、陸地ポリゴンのレンダリングを確認した。概ね、正しく描画されるようになったが、

2024.3.4 Paintインスタンスの再利用[Map415]

ほぼ一通りのレンダリングを終えた。問題は、予め、無数の Paintインスタンスを生成していることである。 パフォーマンス上は問題ないとしても、プログラム行数が嵩み、わかりにくい。

色や線の幅は線を描画する直前で設定すればよいので、Paintインスタンスの再利用が簡単である。 点線、破線、鎖線のときには、float配列が必要となる。ライン描画ごとに配列を生成するのを避けるために、

現在は、事前に float 配列を生成しておく。この配列は enum { d20_30, d20_15_20_25, ... } を { 2.0f, 3.0f}, { 2.0f, 1.5f, 2.0f, 2.5 f}, ... といった float配列に変換することにより初期処理で生成している。

これをもう一歩すすめ、enum の要素ごとの Paintインスタンスにしておく。

現在のプログラムは永続的に使うインスタンス作成用である。

    final static int RoundCap  = 0x01;
    final static int RoundJoin = 0x02;
    final static int ButtCap   = 0x10;
    final static int Rounds = RoundCap | RoundJoin;

    static Paint getPaint(int color, float width, int flags, Dash dash) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        if (dash != Dash.d0) {
            float[] dashes = Resource.dashes[dash.ordinal()];
            paint.setPathEffect(new DashPathEffect(dashes, 0));
        }
        if (flags != 0) {
            if ((flags & RoundCap) != 0) paint.setStrokeCap(Paint.Cap.ROUND);
            if ((flags & ButtCap) != 0) paint.setStrokeCap(Paint.Cap.BUTT);
            if ((flags & RoundJoin) != 0) paint.setStrokeJoin(Paint.Join.ROUND);
        }
        paint.setColor(color);
        paint.setStrokeWidth(width * Map.Scale);
        return paint;
    }
これを、次のように修正する。new は存在しないので、動的なメモリ獲得はない。 初期化で、setPathEffect を施した Paintインスタンス配列を準備しておく。 Color、StrokeWidth、StrokeCap、StrokeJoin はライン描画毎に引数で与えられた値を使う。 StrokeCap、StrokeJoin は Round のときは必ず指定する。指定しなかったときは Cap.BUTT、Join.METERとみなす。
    final static int ButtCap   = 0x01;
    final static int RoundCap  = 0x02;
    final static int MiterJoin = 0x10;
    final static int RoundJoin = 0x20;

    // ライン描画用Paintインスタンスの内容を変更する
    static Paint allocPaint(Paint paint, int color, float width, int flags, Dash dash) {
        Paint paint = Resource.paints[dash.ordinal()];
        paint.setColor(color);
        paint.setStrokeWidth(width * Map.Scale);
        paint.setStrokeCap((flags & RoundCap) != 0 ? Paint.Cap.ROUND : Paint.Cap.BUTT);
        paint.setStrokeJoin((flags & RoundJoin) != 0 ? Paint.Join.ROUND : Paint.Join.MITER);
        return paint;
    }

試みに数か所のラインレンダリングを修正し、問題のないことを確認した。 全体の修正は一挙には行わず、描画を確認しながら少しずつ置き換えていく。

2024.3.3 位置情報取得サービスを実装[Map415]

ひとまず、Map3のプログラムをコピーした。

2024.3.2 バス時刻表動作確認

これまでと同じものとした。できればシンプルにしたいが、使い勝手を考えると、 ある程度の複雑さは避けられないであろう。

2024.3.2 バス路線表示完成[Map414]

MapXとはバス路線データの入手方法を根本的に変更した。レンダリングによってバス路線データを入手する方法とした。 入手完了はメインスレッドで検出するようにした。Thread.sleep() ではなく、Handlerを使って完了待ちをした。

レンダリングThreadでの待ちも Thread.sleep()の使用をやめたいが、Handlerはメインスレッドでないと使えないようである。

特にメインスレッドでの Thread.sleep() は避けなければならないようであるが、 レンダリングThreadで何も仕事がないときは、 Thread.sleep() を使っても問題ないようである。

2024.3.1 最近の修正で intermittent が描画されなくなった[解決]

以前は

            } else if (landuse == Val.basin || natural == Val.water) {
                Val intermittent = osm.getVal(Key.intermittent);
                if (zoom >= 15 && (intermittent!=Val.unknown && intermittent!=Val.no)) {
                    osm.fillPolygon(canvas, r, paintWater, bmpIntermittentWater);
                } else {
                    osm.fillPolygon(canvas, r, paintWater);
                }
            }
としていた。intermittent は共通のため前に移動、landuse と natural は分離した。
     case basin:
         osm.fillPolygon(canvas, r, paintWater,
              fIntermittent ? bmpIntermittentWater : null);
         break;

分離前の Map412 から正常に描画されなくなった。つまり、Map411 から Map412 への変更でエラーが入った。 このときから倍率を 2倍から 1.5倍に変えた。2倍に戻してみたが、エラーは解決しなかった。別の変更が原因のようだ。

森林描画などで PorterDuff関係を少しいじったので、これが関係していた。

2024.2.28 ある historic=wayside_shrineの名前が zoom 18 で描画されない

プログラム上は zoom 17のはずだが。他は描画されている。

タイル境界線が関係しているのだろうか。アイコンは描画されるのでレコードは抽出されている。

アイコンの描画を止めても文字は描画されない。

道路の描画を止めると文字が描画された。道路名は描画していないが、バグがあり、 矩形登録が起きているのかもしれない。

【解決】

道路名描画にバグがあり、表示がなくても矩形登録が行われたり、誤った表示(描画)が行われるようになっていた。

2024.2.28 バス路線情報を道路およびバス停レコードに追加した

バス路線CSVファイル(relation id, ref(路線番号), name(バス路線名))は未出力。

まず、バス路線には道路中央に細い青線を描画した。これにはバス路線データは要らない。

バス路線CSVファイルを用いず、relation idのほかに、 bus_route_ref、bus_route_name を含める案もあるが、個々のレコードに含めると バイナリレコードファイルのサイズが大きくなる。 バス路線やバス停を含むブロックファイルに一つといった工夫がいる。プログラムが簡単であればそうしたいが、 複雑になるようなら、一つの独立したバス路線CSVファイルとして、起動時にメモリに読込む。

2024.2.28 座標値の異常が検出されなかった

昨日は座標値に (0x80000000, 0) という異常値があった。 Divider.java でこの異常値があったとき表示するようにしたが、OSMUtil -devide 12 7 japan-high で現れなかった。 昨日は当初、japan-low や japan-mid でもこのエラーがあり、その後収まった。 その後も japan-high ではこのエラーが続いた。昨日、応急処理で、正常範囲の値に修正する処理を入れて実行したが、 この処理をおこなったかどうかを表示せず実行した。 本日、この表示を入れて実行したところ、この異常処理は実行されなかった。二度実行したが問題なかった。

昨日のデータと今日のデータの圧縮ファイルのサイズは同じであるが、 バイナリチェックすると違いがあるため、修正処理が働いたと思われる。

再現性のないエラーであるが、異常値は (0x80000000, 0) という特別な値である。 再び、この異常値が現れたとき、原因を追究する。

2024.2.27 relationのエラーチェックを緩和する

広域森林の描画は大幅に改善した。しかし、依然として、描画されない森林が残っている。

relation処理のバグの可能性がある。

バグが一つ見つかったが、この修正の影響か、japan では relation によるレコードの座標値が異常な値を含むようになった。

2024.2.27 所々、広域森林が描画されていないことに気づいた。かなり、以前のバージョンでも同様であった

現在使用中の地図アプリMapでは問題ないので、Map4の Relation処理に問題があるのであろう。 誤まって、エラーで破棄しているマルチポリゴンの osm_id をチェックしてみよう。

例えば 11753091(1年前に編集)には閉じていないウェイがある。 しかし、軽微なため、標準OSM地図では森林が表示されている。 以前の Relation処理ではこのようなエラーを救済したのかも知れない。 自分自身も建物のレンダリングなどで、つい、軽微なエラーが入り込むことがあった。 このため、軽微なエラーを救済していたかも知れない。 閉ループが完成したあと、もう一つ余分なクリックをすると、余分なノードが一つ加わり、 始点と終点が一致しなくなり、厳密には閉ループでなくなる。一つ前のノードで閉ループが完成しているので、 余分なノードを削除するのが救済策である。あるいは、終点と始点が一致しないが、極めて近ければ、 あと一つノードを追加して閉ループを完成させるといった救済も考えられる。

このようなOSMデータ自体の軽微なエラーではなく、自作のプログラム自体にバグがあるかも知れない。

2024.2.27 zoomが変わった場合、そのタイルのレンダリングを中止するようにした[Map413]

zoom を立て続けに zooom 16 ⇒ zoom 15 ⇒ zoom 14 というように変更した時、最後までレンダリングせず、 途中でうちきるようにした。

2024.2.26 森林描画が way(polygon) と relation(multipolygon) では葉っぱアイコンの描画が異なっている

multipolygonの方が望ましい。

次の修正で polygon と multipolygon が一致した。

    //fillPolygon(r.canvasWork, r, paintWhite);
    fillPolygon(r.canvasWork, r, paint);

2024.2.26 amenity=cinema の名前 109シネマズ が描画されない

グランベリーパークの109シネマズが描画されない。回りにアイコンや文字はないのに。 海老名のToHOシネマズは描画される。

タイルの境界線上にあった。境界を超える描画を禁止していたことが原因だった。 これを解除すると描画されるようになった。

こうした場合、タイルをまたがる文字列は、まれに一方のタイルだけ描画されることになる。 中低ズームでは文字は主に地名となり、不完全な表示になると目立つため、これまで通り、境界を超える描画を禁止した。

2024.2.26 境界線にそって表示する都道府県や市区町村名など

現在は、境界線をベースとする表示になっている。境界線が水平な場合、北側はいいが、 南側は見にくい。南側は逆転したほうがいい。Matrix演算に慣れていないため、それがうまく行かない。

県名と市名などの重なりも回避しているが、やはり、Matrix演算が関係しており、 所々で重なりが発生する。

Matrix演算を完全に理解することにより、これら二つの問題を解決したい。

2024.2.26 Androidタブレットのタイルの倍率を 1.5倍 とする

高精細でなく、パソコン並みの画素数であるが、パソコンより画面サイズが小さいため、 タイルの画素数を 256x256 とすると、文字が小さすぎる。 2倍の 512x512画素では文字が大きすぎる。1.5倍ぐらいがちょうどいい大きさとなる。

レンダリングは問題なかったが、国土地理院地図に対しては整数倍にしか対応していなかった。 256x256画素のイメージを拡大するMatrixを共用していたので、これをやめると対応できた。

Matrixの共用はトラブルのもとになるのでやめることにした。

アイコンについては、256x256画素用を縦横1.5倍する。 SVGアイコンのサイズをプログラムで1.5倍してPNGファイルに変換できるようになれば、 その方がアイコンは綺麗になる。

2024.2.25 あらかたのレンダリングは終わった[Map411]

細かい調整が多く残っているため、数か月かけて分かりやすいものにしたい。 長時間、広い範囲で地図の表示を続けるとメモリ使用量が増加する。 メモリを解放するようにしているが、うまく機能していないようである。

2024.2.25 OSMの int[] pnts を float[] pnts に変更する

Map410までは int[] pnts、Map411から float[] pnts とする。 空間検索では極座標配列を作らず、直接、float[] pnts を生成する。 プログラムはシンプルになった。

境界線より少し内側の閉曲線に問題はなくなった。

2024.2.23 境界線より少し内側の閉曲線計算にバグがある

中ズームでは現れれないが、高ズームでエラーが出る。

以前(1,2年前?)にも経験したが、解決方法の記憶がない。

元の境界線データが閉ループであれば、 二線分の交点座標が計算できる。しかし、元の閉曲線に冗長なデータがあり、二つの線分が一直線であれば、交点が求めらない。 また、2点がダブっているときも交点計算ができない。

2024.2.22 行政境界について

行政境界線に沿っての名称の位置にしばしば誤りがある。

行政境界線自体は wayレコードで描画するのが効率がよい。

名称描画では県名、市名などが重なる。この競合調整は地図アプリで行う方がいいであろう。 境界線は少なくとも2地域で共有されるため、行政境界ポリゴンとするとノード数では全体で2倍以上になる。 また、境界ボックスが大きくなるため、同じ行政境界ポリゴンを複数ブロックに含める必要から、 全体のレコード数が多くなる。塗りつぶしが必要な場合は、ポリゴンレコードがいるが、名称描画だけであれば、 描画位置と角度だけのポイントレコードでよい。間隔に乱数でばらつきを持たせておけば、ぶつかりは減らせる。

2024.2.20 葉っぱアイコンが薄くなるタイルがかなりある[2.26に解決]

森林のアイコンパターンの表示が所々異なっている。現在のプログラムでは、閉ループとなっている森林way が relation の outer polygon としても登録されている場合は、同じポリゴンが出力される。

relationのタグとwayのタグのチェックが必要である。 way の主タグが natural=wood あるいは landuse=forest だけで、 relation に natural=wood か landuse=forest が含まれている場合、way単独での polygonレコードの出力は要らない。

Map3 まではこの重複を排除していなかったため、これを排除するには一工夫がいる。 これまでのプログラムではrelationメンバーであることだけしか分からない。 way のパースで、親relation を知る必要がある。森林の場合、親は一つであろう。一般には親relationは一つとは限らない。

現在は HashMap<Long,Way> を使っているが、入力 HashMap<Long, List<Relation>> と 出力 HashMap<Long,Way> に分けた方が分かりやすいであろう。

入力は森林の場合、relation に natural=wood あるいは landuse=forest があるかどうかだけでよいので HashMap<Long, Integer> とする。将来、森林以外も対象とするかも知れないので、Integer にしておく。 当面、relation に natural=wood あるいは landuse=forest があれば 1 なければ 0 とする。

閉ループではなく、outer polygon の部分 way に対して、タグが存在した場合、wayセクションで単独レコードとしても 出力している。レンダリングで使われることのない無駄レコードとなる。 もし、このような無駄レコードが多いようならば対策が望まれる。


上記の重複は僅かであった。描画上の違いは別の原因かもしれない。例えば、OSMデータ自体に森林の重なりがあるなど。

やはり、葉っぱアイコンが薄くなるタイルがかなりある。共用や排他制御に不備があるのかもしれない。

2024.2.20 地名の描画を実装した

2024.2.20 ポリゴンの中心座標 center を Devider でバイナリレコードに追加

ひとまず、全ての polygon、multipolygon を対象とした。 実際に中心座標を必要とするレコードはごく一部なので、将来はこれらのレコードに限定するかも知れない。

2024.2.20 幅のある道路の描画を詳細化した

レンダリング時間が数日前に比べると、少なくとも3、4割は増えた。 プログラムを複雑にしない範囲で高速化も図りたい。

2024.2.19 Ferryルートを描画した

細部はこれからだが、主要な道路の描画も終わった。 急ぐとプログラムが汚くなりやすいので、ゆっくり進めよう。

2024.2.19 zorder算出

Map3 とは異なり、Map4アプリの空間検索で設定した。 橋やトンネルの情報はレンダリングでも使うため、実質上の算出オーバヘッドは僅少である。

2024.2.19 way_areaを Deviderでバイナリレコードに追加

way_areaを空間検索で算出すると、レンダリング時間が2割ほど増加するため、 バイナリレコードに含めることにした。形式的にはタグと同じ扱いとした。 OSMでは Tag配列には含めず、独立メンバー変数とした。

2024.2.18 skey、svalの導入[Map408]

試行錯誤の結果、タグの取り出し方が決まった。主タグが一つの場合に限り、 それをOSMの Key skey、Val sval にセットする。主タグが一つのケースが多いため、 そうでないときだけ、getVal(Key key)メソッドでタグの値を取り出す。 これにより、タグ取り出しの平均時間を大幅に短縮できた。

2024.2.17 境界線描画エラーか[解決]

建物に薄い紫の境界線が描画されることがある。何もないところに線が描画されることもある。 共用化にバグが入り込んだのであろう。

タグの例外処理には関係がない。中ズームで森林などにも多くの境界線が現れる。 タグ処理により誤まったタグが混入しているのかも知れない。

やはり、タグに問題があった。OSM#getInt()、OSM#getVal() で無効なタグの値を返していた。 探索範囲を 0~num_tags に変えることにより解決した。

2024.2.17 タグパースを簡単化した

buiding=yesなど標準タグが一つのレコードが多いので、例外的に簡単にタグ設定を行った。

2024.2.16 OSMのインスタンス、Paintを共用化[Map407]

タグについては見直す余地がある。

プログラムはさほど複雑化はせず、パフォーマンスは十分に改善した。

OSMの配列は極力再利用する。極端にサイズが大きくなることは稀であるから、 この場合は解放して、次回に必要なサイズを確保する。

2024.2.16 レンダリングメソッドの引数を Paint に変える

プログラムをさほど複雑にしない範囲で new の発行を減らしたい。 レンダリングメソッドの引数は int color の方が使いやすいが、 実際のレンダリングでは Paint が必要になる。HashMapで管理すれば、new は減らせるが、 取り出しにオーバヘッドが加わる。Map3と同様に、引数を Paint に変える。

2024.2.13 中間ズーム修正[Map406]

間引きを緩める(0.0003 → 0.0001)。道路の描画はもっと減らす(primary → trunk)。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  7 7 japan-mid
レコード数=2058320, 最大レコード長=1039104, 平均レコード長=81.3, 平均タグ数=1.1, 平均タグ長=4.2B

2024.2.13 Scale が 2 のとき森林が1/4しか塗りつぶされない

琵琶湖は正常に描画される。

bmpWork、bmpHolesのサイズを 256x256画素固定にしていた。PX x PX 画素に変えると解決した。

2024.2.13 Scale が 2 とか 3 の時バグがある

   java.lang.RuntimeException: Canvas: trying to draw too large(191102976bytes) bitmap.
        at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:266)
        at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:94)
        at com.example.map4.Map.drawBitmap(Map.java:292)

Scale=3のとき、レンダリングでは 256x256画素が GSIに変えると、768x768、2304x2304、6912x6912 のように どんどん大きくなる。明らかに異常であるが、レンダリングでは 256x256 ではなく、768x768 になるべきでは。

まず、matrix.postScale(Map.Scale, Map.Scale); は一度実行するだけでよい。毎回実行していたため、 倍率がどんどん大きくなっていた。

倍率を変えてもタイルサイズが同じ。Map3は正しく動作する。比較して、どこで間違えたか調べよう。

Map3ではタイル座標をセーブ/リカバリしていた。

        editor.putFloat("TX", ((float)CX)/PX);
        editor.putFloat("TY", ((float)CY)/PX);

Map4では CX, CY のセーブ/リカバリに変えた。致命的なエラーは生まないが、Scaleを変えてのデバッグがやりにくい。 これを Map3方式に戻す。

        editor.putInt("CX", CX);
        editor.putInt("CY", CY);
【解決】

Scaleだけ変えて、PX を変更していなかった。 連動するように修正した。これで全て解決した。

        Scale = fSP ? 3 : 2;
        //PX = fSP ? 768 : 256;
        PX = (int)(256 * Scale);

2024.2.13 タグの値を Renderer から OSM に戻した[Map405]

タグの呼び出しは少なくともレンダリングレイヤの数だけ行われる。 レイヤの先頭でタグのパースを行うのはオーバヘッド上問題になる。 Tag[] tags を OSM に置くことにした。

陸地ポリゴンは形式的にタグを持たせているが、レンダリングではタグを使わない。

buildingがレコードの過半数を占めるが、他のレコードと同じく、全てのレンダリングレイヤでタグチェックの対象となる。 buildingやhighwayなど出現頻度の高いタグだけ OSM に独立した変数で持たせることは容易であるが、 タグチェックの負担軽減効果はあまりない。陸地ポリゴンのように、buildingレコードだけのファイルを設ければ、 タグチェックの負担は半減するが、そこまでする必要性はない。 タグチェックの負担が重いようならば、タグに1ビットを割り当てたMap3方式の方がいいだろう。

まだ、多くのレンダリングが残っているが、今のところ、パフォーマンスは Map3より少し劣っているかも知れない。 ただし、タグ呼び出しのオーバヘッドとは限らない。当面はパフォーマンスよりプログラムの分かりやすさに重点をおく。

空間検索 Block.getOSMs() で、出力配列を建物タグのみとその他に分けるのは簡単である。 レンダリングでのタグチェックの負担は半減する。ただし、その採用はタグチェック時間を計測して、 全体に占める比重が高いと分かった場合に限る。

2024.2.12 admin-levelがない

Devider でチェックしたが、ここでは admin_level がタグに含まれている。 Map4アプリでは Tag.parseTags では admin_level が存在する。 RAdmin では admin_levelがない。
【原因判明】

admin_level はint型タグである。標準タグとしてパースしていた。 この修正で所期の結果が得られた。

2024.2.12 タグの値を OSM から Renderer に移した[Map404]

パフォーマンス上は OSM の変数の方がよいが、OSMタグの数が多いため、OSMに置くと、メモリ使用が多くなる。

2024.2.12 間引き誤差修正

// 1度=約100km ⇒ 1m=約0.00001度
double epsilon = Rank=='l' ? 0.005 : 0.0003;
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  7 7 japan-mid
レコード数=1648904, 最大レコード長=487192, 平均レコード長=62.8, 平均タグ数=1.0, 平均タグ長=4.1B
128MB 実行時間: 0.11分

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  3 3 japan-low
レコード数=276765, 最大レコード長=95336, 平均レコード長=37.7, 平均タグ数=1.1, 平均タグ長=4.2B
24.3MB 実行時間: 0.03分

2024.2.11 経験のないエラーのため、2.7時点に戻した。

Can not extract resource from com.android.aaptcompiler.ParsedResource@55823481.

時々バージョン(プロジェクト名)を変えることにする。

2時間ほどかかったが、ほぼ直前まで戻った。 基本的には、まだ、解決すべき課題があるので、これでよいだろう。

2024.2.10 一部の森林が描画されない

面積による絞り込みを緩めたが改善されない。原因は他にあるようだ。

wayで面積が負の場合、reverseした。

procRelation の > 1 を > 0 に変えると正常になった。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide  7 7 japan-mid
レコード数=2688637, 最大レコード長=1039100, 平均レコード長=63.5, 平均タグ数=0.8, 平均タグ長=3.2B
実行時間: 0.16分

2024.2.10 Divider.java: 絞り込みタグを増やしたら、描画しなくなった

レコード数が増えたため、オーバフローが起きていた。

2024.2.10 OSMとGSIの切替不調[解決]

タブレットの倍率を 1.0 とした場合は問題ないが、倍率を2とか3にすると、正常に表示されない。 次のようなエラーで止まることもある。

表示回りは Map3 と同じはずだが、どこか違うところがあるのかもしれない。

    java.lang.RuntimeException: Canvas: trying to draw too large(191102976bytes) bitmap.
        at android.graphics.RecordingCanvas.throwIfCannotDraw(RecordingCanvas.java:266)
        at android.graphics.BaseRecordingCanvas.drawBitmap(BaseRecordingCanvas.java:94)
        at com.example.map4.Map.drawBitmap(Map.java:289)
        at com.example.map4.Map.onDraw(Map.java:217)

2024.2.8 低中ズーム用バイナリファイル作成および描画確認

Map3ではパースに時間がかかったため、高ズーム用バイナリファイルから低中ズーム用 バイナリファイルを作成していたが、Map4ではパース時間は短くなったため、 パースで直接、低中ズーム用ファイルを作成できるようにした。 プログラム的にはこの方が分かりやすく簡単である。

細かい調整には月日をかけたいが、大雑把なところでは描画まで確認した。

2024.2.7 道路の実線描画確認

道路の実線描画を確認した。この先のことは急がず、分かりやすさを追求する。

2024.2.7 標高表示

特に問題はないので、Map3のプログラムをコピーした。

2024.2.7 MultiPolygonレコードの描画確認

Relation処理を終了し、穴あき森林の描画を確認した。

これまでのところ、Map3よりかなり簡単化できた。

2024.2.7 Divider.javaの修正

< とするところを <= としていたため、総ファイルサイズが4倍近くになっていた。

2024.2.7 Polygonレコードの確認

建物等の描画でPolygonレコードを確認した。

2024.2.6 Pointレコードの確認

数種類のアイコン描画でPointレコードを確認した。

2024.2.6 全地物のレンダリングに着手した

新しい方法について見通しが着いたので、Pointレコードの描画からスタートする。

2024.2.4/5 タグ部にエラー

まず、Divider でチェックしてエラーが起きることが分かった。 kanto の場合、110,788番目のレコードで初めてエラーが起きた。

Parser でも同じ結果を得た。

Encoder では最初のレコードエラーが起きる。 Parser では、ずっと先のレコードでエラーが起きる。 最初のタグは highway=motorway のはずが、ref='Z03' になっている。 node に ref='Z03' があるが、nodeセクションの最後の ref ではない。

ここでの ref は 'C1' である。なぜ、ref='Z03'となるのか分からない。
【解決】 Encoder.java のタグパースで赤字の部分をなぜかコメントアウトしていた。 これがないと、key なしで val のみとなり、エラーとなる。

  } else if (ikey <= Key.endFloatKey.ordinal()) {
     val = val.replace("m", "");
     try {
         float v = Float.parseFloat(val);
         bbTags.putShort(ikey);
         bbTags.putFloat(v);
     } catch (NumberFormatException e) { 
         try {
             Key s_key = Key.valueOf("s_" + skey);
             bbTags.putShort((short)s_key.ordinal());
             putString(bbTags, val);
         } catch (Exception ex) {
             numTags--;
             System.out.println("Error FloatTag: " + key + "=" + val);
         }
     }

2024.2.4 陸地ポリゴン描画完成

2024.2.4 Block.get()修正

type が 3のときの処理が漏れていた。これにより、低ズームの陸地は見た目では正常になった。

2024.2.3 type が 3 でポリゴン数が1のケースがある(継続)

LandParser ではポリゴン数が2以上のときに、type = 3 としているので、どこかに誤りがある。

LandParser では以下の通りである。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -land low
63912: head=7 polys.length=2

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -land high
214964: head=7 polys.length=4
450524: head=7 polys.length=2
744795: head=7 polys.length=3

Devider で outer/inner polygon のノード数を確認すると以下のようになった。 low zoom用はレコードを間引いているので、high zoom用と一致しなくて不思議はない。 low用の inner polygonのノード数の方が大きいのは意外。簡単化の仕組みで複数のポリゴンがつながって、 隙間に inner polygon が生まれたのであろう。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 1 1 lands-low
nRec=63912, #0 poly_nodes=180831
nRec=63912, #1 poly_nodes=3222

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 5 1 lands-split
nRec=214964, #0 poly_nodes=30385
nRec=214964, #1 poly_nodes=13
nRec=214964, #2 poly_nodes=16
nRec=214964, #3 poly_nodes=23
nRec=450524, #0 poly_nodes=72477
nRec=450524, #1 poly_nodes=11
nRec=744795, #0 poly_nodes=14325
nRec=744795, #1 poly_nodes=26
nRec=744795, #2 poly_nodes=38

2024.2.3 地図ソース変更エラー

OSM地図から国土地理院地図に移り、OSM地図に戻ろうとしたとき、 国土地理院タイル地図がばらばらに表示される。おそらく、うまくレンダリングできず、 古いビットマップが残っており、それが返されるのであろう。

頻繁ではないが、まれに起こる。まだ、LandParserかレンダリングのどこかにバグがある。

2024.2.3 java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor

OSM地図から国土地理院地図に移り、OSM地図に戻ろうとしたとき Immutable bitmap passed to Canvas constructorエラーが起きた。

Tile で Canvasオブジェクトを生成し、それを再利用するように変更した。

2024.2.3 タブレットのMapデータをSDカードに移動する

スマホと同様にタブレットのMapデータをSDカードに移動した。

2024.2.3 parseTagsエラー[保留]

zoom 5で陸地ポリゴンを描画させようとしてエラーが起きた。レコード数は 878 のようだが、 parseTagsが延々と続き、数万回に一回程度の割合で下のエラーがおき、やがて、ストップする。

parseTagsが延々と続くのがおかしい。 陸地ポリゴン描画では、parseTagsはいらないので、止めた。

完全ではないが、陸地が描画された。LandParser.java は概ねバグがとれたようだ。

 80235: natural=land;
 java.lang.ArrayIndexOutOfBoundsException: length=88; index=9424
System.err:     at com.example.map4.Tag.parseTags(Tag.java:44)
System.err:     at com.example.map4.Tag.parseTags(Tag.java:32)
System.err:     at com.example.map4.Block.parseTags(Block.java:307)
System.out: 878/51610 time=275mS

2024.2.1 Polygon描画確認

建物、公園、水域等、一部のポリゴン描画を確認した。

2024.1.31 空間検索でエラー

数レコードのタグのパースを成功しているようだが、 やがて、java.nio.BufferUnderflowExceptionが起きた。

文字列タグのパースでエラーが起きる。

2024.1.31 Encoderの出力フォーマットを変更する

 
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
出力レコード数=6954785, Wayレコード=7026273, 出力レコードの平均タグ数=6.614819, 出力レコードの平均Node数=7.2623243
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
出力レコード数=31616319, Wayレコード=32023242, 出力レコードの平均タグ数=6.5017314, 出力レコードの平均Node数=8.089884

Encoderの出力フォーマットを次のように変更する。 文字コードは UTF-8 とする。

 
length, osm_id, lon, lat, {key, val}*                         ----- Node   
length, osm_id, num_nds, {lon, lat}*, {key, val}*             ----- Way  
length, osm_id, num_members, {type, ref, role}*, {key, val}*  ----- Relation       
 
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode kanto
maxId=2822F0E7C, nodes=2A6D42B, ways=6B3661, rels=8715, 総サイズ=0.57GB 実行時間: 3.19分
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode japan
maxId=28230265A, nodes=DF15625, ways=1E8A2CA, rels=1E074, 総サイズ=2.77GB 実行時間: 18.70分

2024.1.31 タグを持つ全Wayレコードを出力

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
出力レコード数=6954785, Wayレコード=7026273, 出力レコードの平均タグ数=1.2490556, 出力レコードの平均Node数=7.2623243
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
出力レコード数=31616319, Wayレコード=32023242, 出力レコードの平均タグ数=1.2263029, 出力レコードの平均Node数=8.089884

kanto.dat   建物 175,730KB   全て    325,274KB
japan.dat   建物 764,356KB   全て  1,584,594KB(50B/レコード)

2024.1.30 建物描画テスト成功!!

タブレットで十分なパフォーマンスが得られた。東京の描画でメモリ使用量は 137MBとなった。 class OSM には int[] pnts を持つ。空間検索で、最初に、整数化した lon、lat 列データを格納する。 必要に応じて、面積など、極座標による計算を行う。 その後、レンダリング対象タイル上の相対XY座標(画素単位)の変換して、pnts配列に格納する。 このデータを使ってポリゴンやラインの描画を行う。

2024.1.30 空間検索中間結果

まだ、途中段階であるが、スマホの場合、空間検索時間は 10mS 前後であるので、 まずは問題ない。まだ、レンダリングは行っていない。

 レンダリング開始
 kanto bx=3637 by=1612 z=16
 1255/450578 time=6mS
 レンダリング終了
 RenderThread: tile #8 ready
 tile #10 renderring
 レンダリング開始
 kanto bx=3637 by=1612 z=16
 595/450578 time=12mS
 レンダリング終了

2024.1.30 空間検索を極座標系に戻す

最近はOSMバイナリレコードの座標系は世界XY平面座標としていたが、 これを極座標に戻してみる。

空間検索で絞り込んだレコードについて、必要に応じて、ポリゴンの面積などを求めてから 極座標をその zoom における平面座標に変換する。

記号上は原点(西北端)を (x0, y0)、東南端を (x1, y1) で表す。

空間検索の対象となるタイル座標(左上)を Zoom、X、Yとしたとき、右下(東南端)の座標は Zoom、X+1、Y+1 となる。左上、右下 の極座標は次のメソッドで得られる。

    static double X2Lon(double x, int zoom) {
        return x / Math.pow(2.0, zoom) * 360.0 - 180.0;
    }

    static double Y2Lat(double y, int zoom) {
        double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, zoom);
        return 180.0 / Math.PI * Math.atan(Math.sinh(n));
    }

2024.1.29 Blockで管理データを生成した

思ったほど時間がかからなかった。

 scan 392698records, 130ms
 #1 /storage/emulated/0/Map/kanto12/3638/1612.dat 159ms 15637KB

2024.1.29 Deviderでエラー検出---文字列コード修正

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 12 kanto
error nRec=1 last nds_length=-1
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 12 hot
error nRec=1 last nds_length=-6
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide 12 japan
error nRec=1 last nds_length=-1

まず、Parser.java にバグがある。length は2の倍数のはず。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
1: 47, 差分コード=true,length=254
2: 58, 差分コード=true,length=365
建物レコード数=4331819, Wayレコード=7026273, 建物レコードの平均タグ数=1.0707476, 建物レコードの平均Node数=6.527187

文字コードが utf-16 ではなく utf-8 になっている。

半角文字が多いことから、utf-8の方がファイルサイズは少し小さくなる。しかし、サイズが2の倍数になることから、 文字コードは UTF-16としたい。

getBytes("UTF-16")とすべきだった。ただし、末尾に2バイト(多分\x00)が付くようである。 Encoderについても修正した。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode kanto
maxId=2822F0E7C, nodes=2A6D42B, ways=6B3661, rels=8715, 総サイズ=0.56GB 実行時間: 3.75分
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -encode japan
maxId=28230265A, nodes=DF15625, ways=1E8A2CA, rels=1E074, 総サイズ=2.72GB 実行時間: 30.99分
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
1: 47, 差分コード=true,length=312
nds_length=192, tags_length=118
2: 58, 差分コード=true,length=462
nds_length=236, tags_length=224
建物レコード数=4331819, Wayレコード=7026273, 建物レコードの平均タグ数=1.0707476, 建物レコードの平均Node数=6.527187

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
1: 47, 差分コード=true,length=312
nds_length=192, tags_length=118
2: 58, 差分コード=true,length=462
nds_length=236, tags_length=224
建物レコード数=19373553, Wayレコード=32023242, 建物レコードの平均タグ数=1.0443347, 建物レコードの平均Node数=6.330969

num_nodes を長さと勘違いしていた。

2024.1.29 バイナリレコード形式修正

バイナリレコード形式を下のように変更した。

head(4),  num_nds(0/2/4), {lon,lat}*, {key,val}*  ... point/line/polygon 
head(4), {num_nds(2/4)}*, {lon,lat}*, {key,val}*  ... multipolygon
head:
  第0,1bit(0x03) 0: point、   1: line、    2: polygon、 3: multipolygon
  第2bit(0x04)   0: 差分座標、1: 絶対座標
  第3bit(0x08)   0: num_nds2バイト、 1: num_nodes4バイト
  下位3バイト   レコード長

これに沿って Parser.java を修正した。Wayによる 建物レコード(polygon)のみ。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse kanto
建物レコード数=4331819, Wayレコード=7026273, 建物レコードの平均タグ数=1.0707476, 建物レコードの平均Node数=6.527187
c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -parse japan
建物レコード数=19373553, Wayレコード=32023242, 建物レコードの平均タグ数=1.0443347, 建物レコードの平均Node数=6.330969

2024.1.27 建物レコード分割完了

とりあえず zoom 12 で分割した。この場合、全ファイルをオープンのまま、 レコード書き込みが行えるため、プログラムは簡単である。

zip圧縮してもパソコンからタブレットへの転送に時間がかかる。

2024.1.24 Dividerでエラー検出

先頭レコードは hot.osm をチェックすると 10ノードであった。 nds_length は 8+4*9 = 44 であるべきが 46 となっている。 Divider.java か前段の Parser.java にエラーがある。あるいは Tag.java にエラーがあるのかもしれない。

c:\map>java -Dfile.encoding=UTF-8 -Xmx5g -classpath ./class OSMUtil -devide hot
path=c:/map/dat/hot.dat
record length=118
nds_length=46
1394914034,355306637
1394915761,355307513
1394916984,355306064
1394917975,355305066
1394918378,355305283
1394918781,355304949
1394917987,355304516
1394918106,355304252
1394916727,355303400
1394914034,355306637
1394914064,355306638
error last nds_length=-2

タグ数よりもタグ部の長さの方が分かりやすいので、Parserの出力を下のように変更した。 これで今回のエラーは取れた。

Encoder の出力形式と合わない。num_nds、num_members とも合わない。

valに文字列があり、roleも文字列のため、全て、長さにした方がよいだろうか。

{key,val}* の中身は不要で、座標値列データが欲しい場合、 len_tags の方が処理は速い。

もう少し、先に進めてから、決定したい。

length(2/4), len_tags(2), {key,val}*, {lon,lat}*                 ... point/line/polygon 
length(2/4), len_tags(2), {key,val}*, {num_nds(4)}*, {lon,lat}*  ... multipolygon

リファレンス