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

地図アプリMap開発記録

2026.5.15 大きな湖名が描画されなくなった

座標データ領域を Renderer 管理の配列に移動したとき、中心座標計算にバグが入り込んだようだ。

xc, yc が共に 0 である。間違って初期化しているのかも知れない。

あるいは、極座標を平面座標に変更したとき、中心座標のセットが漏れたのかも知れない。


ParserXY#procRelation で極座標での center は例えば 601614120934100811 となっているが、 それを 平面座標に変換した結果が (0, 0) になっている。polygon の場合は正しいようだ。

double En = Rank=='h' ? (1<<30) : Rank=='m' ? (1<<25) : (1<<20); が漏れていた。 メソッドの外に En の宣言があったため、コンパイルエラーとならず、En = 0 となっていた。

2026.5.15 交差判定修正

航路(drawLine)および行政境界線にそって書く行政名(drawTextMulti)でのスキップ処理に 誤りがあった。線分を矩形として、矩形と矩形の交差判定に修正した。判定時間は調べた限りでは全て 1mS未満であった。

交差判定を空間検索の後処理に移せば、レンダリング対象のレコードからも除外でき、 メモリ使用量を減らすことができる。

2026.5.12 natural=shrubbery[解決]

実行結果だけを見ると natural=shrubbery;というレコードが一つだけのR-Treeノードが頻発している。 osm_id がない。場所的には福島県と栃木県の境界付近。例えば、大島ダム貯水池付近。

現在は、レンダリングしないので、とりあえずは無視する。


陸地ポリゴンであった。natural=shrubberyは今年の3月に追加したものであり、陸地ポリゴンパーサ に反映していなかった。このため、natural=land としていたのが、natural=shrubbery に変わった。 陸地ポリゴンの描画ではタグは使っていない。また、osm_id はゼロである。 つまり、このレコードは削除してはならず、このままで、正常に描画される。

2026.5.12 OSMタグによる絞り込みも正常に行われるようになった。

大量となる、もしくは、サイズの大きいレコードだけを除外するだけでよい。

2026.5.12 boundary=administrative のname(大字、小字相当)が描画されなくなった

境界線の破線が実線に変わった。TagやRecordの共用が関係している可能性がある。

他の修正でこのエラーが消えた。再発したとき、調査する。

2026.5.11 Record、Tag配列

1タイルレンダリング用Record、Tagを固定配列で割り当て、再利用するようにした。 いずれ、座標値列データ、文字列も同じ方法で再利用したい。

2026.5.10 Tagパースを先に行う

zoom 13以上をハイズームとしている。 このため、zoom 13では描画しない建物レコードがzoom 13の検索結果に含まれる。

このように、この zoom では描画しないレコードを事前に除去すれば、メモリ使用量は少なくなり、 レンダリング時間も短縮される。

2026.5.9 endAllActiveAnimators on … が頻発する

invalidate()の発行が多すぎるのか?

スレッドから invalidate() を発行しているのが間違いでは?

postInvalidate() に変えたが、やはり、endAllActiveAnimators on … が発生する。

どうやらメニュー操作が関係しているようだ。AI検索結果を以下に示す。

D/OpenGLRenderer: endAllActiveAnimators on MenuPopupWindow$MenuDropDownListView は、
Androidアプリケーションでポップアップメニューやコンテキストメニューが閉じられる際に、
描画システム(OpenGLRenderer)が関連するアニメーションを終了させたことを示すデバッグログです。
このメッセージの詳細は以下の通りです。
何が起きているか: メニュー(MenuDropDownListView)の表示や非表示に伴うアニメーションが終了した。
危険性: 通常、デバッグログ(D)であり、エラーではないため無視して問題ありません。
発生箇所: アプリがメニュー(ポップアップ、スピナー、ドロップダウン)を使用する際に、
Androidシステムが内部的に発生させます。

2026.5.9 低ズームのレンダリングが不安定である

まず、ダイレクトByteBuffer を止めて、ヒープByteBuuferにしてみる。

低ズームでメモリの使用量が多すぎるようだ。 恐らく、レコード数よりも、サイズの大きいレコード数が多いのが原因であろう。

低ズームのレンダリングはスレッドひとつに限定した。

2026.5.9 小さな line/polygon/multipolygon

以前は、空間検索結果から微小な line/polygon/multipolygon をつぎのようにして除去していた。 世界XY平面座標ではまだこの処理を入れていない。 このため、低ズームではレコード数が膨大になる可能性がある。また、微小なpolygonはもはや正確な閉曲線とは 呼べないものも多数生まれるだろう。これが低ズームでのレンダリングがうまくいかない原因かも知れない。

  float bbox = bw * bh / Map.Scale / Map.Scale;
  if (type >= 2 && bbox < 0.2 && rank != 'h') continue;
  if (type == 1 && bbox < 0.03 && rank != 'h') continue;

上の bbox は境界ボックスの面積をタイルサイズを 256x256 としたときの値に変換して、上限を決めている。 0.2 とか 0.03 は多分、試行錯誤で決めたものであろう。

世界XY平面座標ではどうなるか? 低ズーム(zoom <= 7)では bw、bh は  世界全体サイズ= 1 << 20 に対する値である。 あるzoomのタイルサイズは縦横 1 << (20-zoom) である。 bw がタイルに占める比率は bw / (1<<(20-zoom)) である。 タイルが256x256画素の場合、画素単位では bw / (1<<(12-zoom)) である。

低ズームだけを対象とすると、次のようになる。

float bbox2 = bbox / (1<<(12-r.zoom)) / (1<<(12-r.zoom)); 
if (type >= 2 && bbox2 < 0.2 && r.zoom <= 7) return false; 
if (type == 1 && bbox2 < 0.03 && r.zoom <= 7) return false; 

以前と同じように、中ズームも対象とするには、以下のようにする。

double bbox2 = bbox*r.Rn*r.Rn; 
if (type >= 2 && bbox2 < 0.2 && r.rank != 'h') return false; 
if (type == 1 && bbox2 < 0.03 && r.rank != 'h') return false; 

2026.5.8 低ズームでは最大バイト長が 31.7MB

低ズームのレンダリングが不安定になった。ダイレクトByteBuffer や channel が問題なのではなく、 一つのタイルのレンダリングの候補となるバイナリレコードを全部ひとまとめしてからパースするのが よくない。葉ノード単位で ByteBuffer に読み込み、short配列に変換して、パースして、結果を List<Record> に蓄えて行くべきである。

2026.5.8 バイナリレコード読み込みに ダイレクトByteBuffer を使う

ByteBuffer は Renderer に一つ(static)で、シリアル利用。読み込み後、スレッド毎の short配列に移して、レンダリングはマルチスレッドとした。

当面、サイズは 20MB とした。実際の使用最大サイズがいくらになるかは、計測して、 sharedPreference として保存している。これまでの最大サイズは zoom 7, 13MB であった。

中低ズームの更新頻度は少なくていいので、レンダリングは1スレッドでもよい。 スレッド毎の short配列は一つだけ大きくして、他は小さくすることも可能である。

リーフキャッシュは止めた。

ByteBuffer 20MB + short配列 20MBx4スレッド=100MB : 固定的に100MB が使われるが、シンプルでバイナリレコード読み込みによる GC は起きない。

いずれ、Record にも固定領域を割り当て、GC が起きないようにする。

2026.5.7 バイナリレコード読み込みに ダイレクトByteBuffer を使う

パフォーマンス上問題なければ Leafキャッシュを止める。

まだ、途中段階であるが、概ねダイレクトByteBufferの使い方は分かった。

2026.5.7 RTree#select で使う List を Renderer のメンバーとして再利用するように変更した

RTree#select で使う List<byte[]> listBytes、List<Long> listFound 、List<Record> listRecord は Renderer のメンバーとして、 再利用するように変更した。

2026.5.6 Recordオブジェクトの再利用

Recordオブジェクトはレンダリングスレッド毎に予め数万レコード分のインスタンスを生成しておき、 レコードのパースで値を設定する。

int[] points、int[][] polys については、まずは、これまでどおりとする。 10ノード以下が7、8割を占めると思われるので、int[] points については、10ノード以下のときは、 予め確保した配列にセットする案もある。

Record のメモリサイズを 300バイト、用意する総インスタンス数を 20万とすると、 所要メモリは 6000万バイト=60MB バイトとなる。これには、動的に確保する int配列は含まない。

OSMタグについては、ひとまずは、現状通りとする。

int[] points、int[][] polys、Tag[] が現状のままでは、GC の対象は精々4、5割減であろう。 大幅に減らすには、工夫が必要であり、プログラムを分かりにくくする可能性が高い。

通常の地図利用では現状で、パフォーマンスの問題はないため、GCを減らすために複雑化するのは 得策ではない。

無駄を省く程度でよい。

2026.5.6 RTree葉ノードのメモリ共用[保留]

葉ノードのサイズはざっと調べた限りでは 数10KB が多かった。現在の ByteArrayPool では 要求サイズを 10KB の倍数として、最大を 100KB としている。したがって、このままの仕様で ByteArrayPool が使える。

メモリの獲得は new byte[length] を ByteArrayPool.get(length) に変えるだけでよい。

葉ノードの byte[] はパースして Recordリストになるが、全て、LRUCache に置かれる。 LRUCache に長く残り、溢れて、キャッシュから削除するときに、ByteArrayPoolに戻す。

ByteArrayPool.get(length)で得た byte[] のサイズは length とぴったり一致するわけではなく、 length 以上となるため、この配列の使い方が少し面倒になる。

サイズがピッタリ一致するものを再利用しようとすると、使われないものがプールにたまり メモリ使用量が大きくなってしまう。サイズが不揃いの場合、こうするしかない。 その分、プログラムは少し分かりにくくなってしまう。

RTree の場合、獲得した配列をゼロクリアして使う。レコードの先頭はレコード長なので、 ここがゼロの場合、レコードの終わりと判断する。


なぜか、正常にレンダリングされないケースがある。今のところ、原因がつかめない。

2026.5.6 キャッシュは難しい

キャッシュに置かれているのは通常はポインタであり、実体はメモリ上のどこかにある。 キャッシュやプールから排他でポインタを受け取り、直ちに実体をコピーして複製を作るのであれば 問題はない。通常は、ポインタを受け取り、実体を参照して使う。キャッシュがあふれたとき、 ポインタを削除するだけならば問題ないが、Tile.Cache では、Bitmap や byte[] をプールに返している。 したがって、キャッシュから削除した途端に、その直前に受け取った Tile は無効になる。

Tile.Cache は LRUキャッシュであり、十分な容量があるため、長らく使用していないものが削除される。 したがって、キャッシュから受け取ったタイルを描画前とか描画中に削除されてしまうことはないため、 問題は起きない。

キャッシュを自前で作成する場合、このエントリが使われていないことを確認して削除する仕組みが望まれる。 もしくは、キャッシュからいつ消えてもいいように複製を作る。複製を作るには、その分メモリがいるので、 通常は、現実的ではない。一つの実体をそのまま何度も使ってこそのキャッシュである。

2026.5.6 Rectの共通利用

void query(Collection<Long> results, Rect box, int ix) のループ処理での Rect boxChild = new Rect(a,b,c,d); について、boxChild をメソッド外で生成、 boxChild.set(a,b,c,d); に変更した。

2026.5.6 GCを減らしたいが、再利用は慎重に

PC版からスマホに移行したとき、再利用にこだわった。これが、プログラムを分かりにくくした。

必要な所で new で生成するのが一番分かりやすい。この new を避ける方法は様々である。 効果が大きいものに限定して、分かりやすいものが見つかったとき、それを採用したい。

OSMバイナリレコードでは、サイズ100バイト前後のものが7割程度を占めるであろう。 サイズ毎の byte配列プールによる再利用が有効であろう。

一方、都道府県境界などを表す巨大なレコードは数少ないが、 多くのタイルのレンダリングで同じものが空間検索結果として得られる。 これらに対しては、あるタイルのレンダリングでパースした Recordオブジェクト(インスタンス)を レコード単位でキャッシュしたものが有効であろう。レコードサイズが大きいとパースにも時間が かかるため、パース結果の再利用は効果が大きい。

両者の中間に当たるものは、後回しでもよい。byte配列プールとしては、サイズは百~数百バイト単位に 丸めたものを使う。

一つの単純な方法での効果的な再利用は不可能である。したがって、時間(期間)をかけて、少しずつ実施しよう。

2026.5.5 Recordキャッシュに誤りがあった

パースしてあるタイルの境界ボックスで絞り込んだ結果(Record)を葉ノードのオフセットをキーとして キャッシュしていた。これは、別のタイルのレンダリングには使えない。 同じ葉ノードの読み込みを避けるためのキャッシュであれば、葉ノードの byte[] または short[] を キャッシュするか、タイルの境界ボックスでの絞り込みをしない全Recordをキャッシュしなければならない。

キャッシュを使わなくても、短期間に同じ葉ノードを読み込む場合、OSのディスクキャッシュ効果により、 高速読み込みが可能である。

葉ノードキャッシュは値を byte[] とした。

2026.5.5 GCを減らしたい: Recordの構造体をシンプルにする[検討続行]

結局のところ、Record構造体は残した方がいいようだ。 ただし、Rect、int[]、int[][]、Tag[] などはメンバーとはせず、 メンバー変数は int、float などプリミティブ型に限定する。

キャッシュは LRUCache<Long,short[]> とする。キーは葉ノードのoffset、 値は、バイナリレコード列をそのまま short配列としたものとする。

空間検索では、short[] をパースして Record とする。ベースになるメンバー変数は レコードの始まりとなるshort配列の添え字 ixBgn である。 必要に応じて、レコードの長さ length または、終わりを表す ixEnd をメンバー変数とする。

レンダリング対象となる Record[] は、Rendrerスレッド毎に共有する。つまり、 class Renderer のメンバーに Record[] osms、int size(有効長)を含む。

下に、OSMバイナリレコードフォーマットを示す。

上で述べたように、レコード始まりを表す ixBgn が最も重要なメンバー変数となる。 これに続いて ixEnd(=ixBgn + length)をメンバー変数とする。

head は type と reversed フラグからなる。Rendererでは、int type、boolean fReversed とする。 サイズ的には short type、byte type でもいいが、そこまでメモリを気にせず、分かりやすさを重視すべきであろう。

osm_id、uid、time はレンダリングには使わないため、 long osm_id()、int uid()、int time() というメソッドを置くだけでよい。

境界ボックス bbox はレコードの絞り込み(空間検索の最終段)で使うため、 int xmin, int ymin, int xmax, int ymax を Renderer のメンバー変数にしてもよいが、レンダリング自体では 使わないはずなので、その場合はメンバー変数にしない。

num_polys はバイナリファイルでは short 型であるが、将来的には int 型にする。メンバー変数としては、 int num_polys とする。point/line/polygon では 0 とする。

point/line/polygon の num_nds(num_nodes、nodesでもよい)はメンバー変数とする。 multipolygon の場合は {num_nds:4}* の合計値とする。個々の値 int[] はメンバーとしない。 int num_nodes(int i) でポリゴン #i のノード数を得られるようにする。

タグ部の先頭は ixBgn と num_polys と num_nodes から算出されるが、int ixTags をメンバー変数とする。 ixEnd がタグ部の終わりであると共に、レコードの終わりである。

レンダリングでは ポリゴンの面積や zorder の値によるレコードの並び替えが必要となる。 zorder の値の算出にはOSMタグのパースが必要となる。

このOSMタグの扱いについて、まだ、いい解が思いつかない。現在のタグ処理は多様で煩雑である。

いい解を思いつくまでは、現状で、少しずつ、GCの発生頻度を下げるようにしよう。 例えば、効果は少ないが、Record に、ixBgn を導入して、メンバー変数 osm_id、uid、time を long osm_id()、int uid()、int time()に変えるのは簡単である。

続いて、int[] points; を int points(int i) に変えるなどに取り組む。 しかし、GC は減っても、CPU負荷は増えるので、やはり、見送る。 int[] points は残し、IntArrayPool を導入する方がいいであろう。

その次には int[][] polys; はやめて、int[] points; に一本化する。 ただし、座標値列データの前に、ポリゴン毎のノード数 {num_nds:4}* を置く。全体としては int配列となるため、 IntArrayPool の対象とする。ただし、line/point ではnode数が 10未満が多いのに対して、multipolygon の node数は巨大なものが多い。IntArrayPool では、サイズを丸めるが、建物レコードが多いことを考えると、ノード数 は 10, 20, 40, 80, 160, 320, ... といった丸め方がいいだろう。非常に巨大なものは頻繁には現れないため、 上限を設け、これを超えるものは IntArrayPool の対象とはしない。使うとき new で生成して、使用を終わったとき、 GC の対象とする。

簡単には 2を底とする対数[log(n)/log(2)] を求めて、整数に切り上げればよい ((int)Math.ceil(Math.log(n)/Math.log(2)))。

バイナリレコードとしては、形式上、point は line/polygon に合わせたが、Record では point に対しては int[] は使わない。Record としては int xc, yc; を使い、int[] points は null とする。

length:4, head:4, osm_id:8, uid:4, time:4, bbox:16, num_nds:4, {x:4/2,y:4/2}*, {key:2,val:2*}* -- point/line/polygon
length:4, ~同上~ time:4, bbox:16, num_polys:2, {num_nds:4}*, {x:4/2,y:4/2}*, {key:2,val:2*}* -- multipolygon
bbox: xmin, ymin, xmax, ymax
{x:4/2,y:4/2}* = (x:4,y:4),{dx:2,dy:2}*  差分コード

現在の Record ではプールによる再利用が難しい。キャッシュは Record ではなく int[] とする。 num_poly は int にする。座標値は差分を元に戻せば、x、y 座標とも int になる。 差分のままでも、x と y をペアにすれば int になる。タグ部は長さが 4 の倍数にならない場合は末尾にnull 2バイトを 付け加えて4の倍数にする。タグのパースをするときに、short配列に戻す。

        // 変換後のint配列(長さは半分になる)
        int[] iArray = new int[sArray.length / 2];
        for (int i = 0; i < iArray.length; i++) {
            // shortをintにキャストする際、符号拡張を防ぐために 0xFFFF と & する
            int high = (sArray[i * 2] << 16);
            int low = (sArray[i * 2 + 1] & 0xFFFF);
            iArray[i] = high | low;
        }

	int配列をshort配列に戻すときには (short)(i >> 16), (short)(i & 0xFFFF) となる。

2026.5.4 GCを減らしたい: Recordの構造体を止める

Recordは構造体とした方が分かりやすいが、バイナリレコードの平均サイズは100バイトにもみたず、 一方、1タイルのレンダリングに関するレコード数は膨大なため、構造体(class Record)とした場合、 恐らく、2、3倍を超えるメモリを消費するものと思われる。動的に生成すると、GCが頻発する大きな要因 となる。

構造体(メンバーに Rect、int[]、int[][]、Tag[] などを含む)でも、new を使わず、構造体をプールして使うことは 可能であるが、メンバーに様々な配列があるため、再利用処理が煩雑になる。

そこで、構造体に展開せず、レコード全体を一つの int 配列としたい。 ファイル上のOSMバイナリレコードは short 配列としているので、メモリ上も、レコードをそのまま short 配列とする 案もあるが、レコード中の個々のデータを取り出すのに少し処理がかかる。 int 配列にすると、所要メモリは 1.4~1.5倍になる見込みであるが、個々のデータの取り出しが簡単になる。

ファイル上は4、5割増加は避けたいが、メモリ上は全体のごく一部のみに限られるため、4,5割の増加は許容される。 よって、レコードキャッシュは LRUCache<Long, int[]> とする。 int[] は IntArrayPool によって、再利用する。

レコードの int[] 化で少し手がかかるのは OSMタグである。ファイル上は、キーは2バイト、値は2バイトの倍数 である。課題は文字列(現在は UTF-16)の扱いである。レコードのパースで現れた全文字列を一つの short[] で 表し、レコードデータとしては その配列の添え字をOSMタグの値とすれば、int型で済む。

レコード自体は LRUCache からスワップアウトされることがあるが、文字列配列からの削除はない。アプリを起動してから 登場した全ての文字列の保持を続ける。メモリ容量的には問題はない。short[] のほか HashMap を使う。

2文字分を int で表す案もある。文字部分については、int[] を short[] に変換して、文字列を取り出す。

レコードをデータ部とタグ部に分けて、前者を LRUCache<Long, int[]> 、後者を LRUCache<Long, short[]> で管理する案もある。

class Record { int[] data; short[] tags; } として、LRUCache<Long, Record[]> で管理する案もある。 この場合、 int[]、short[]、Record[] の再利用を考える必要がある。

レコード全体を int[] として、タグ部だけを short[] に戻す場合、int[] の再利用だけで済むので、 この案がいいであろう。

まずは、その前に、LRUCache<Long, short[]> とする。 境界ボックスなど int 変数を取り込むのに少し手間がかかるが、osm_id(8バイト)、uid(4バイト)、time(4バイト) はレンダリングには使わないので、メソッドを用意しておくだけで良い。

int[] points、int[][] polys は使わない。

ここで、大きな抜けに気づいた。レンダリングでは、レコードを面積や zorder で並び替えが必要である。 short[] (あるいは int[]) に置いたままでは、レコード実体の並び替えは行えない。 レコード(short配列)のインデクス(添え字)だけを格納するint配列を作り、この中身を並び替える必要がある。 レコード自体に比べれば、この int配列のサイズは小さい。Renderer に大きめの int配列を用意して、 これを共有すればよいので、int配列プールは要らない。

実体の並び替えではなく、ポインタの並び替えのため、現在のプログラムコードがそのままでは使えない。 これまでの並び替えも実際はオブジェクトのポインタの並び替えであるから、 多分、殆ど同じであろうと思うが、未経験である。

2026.5.3 レンダリング時間の調査

空間検索時間は100mS, 200mSを超えることもあるが、平均的には10ミリ秒程度であった。

行政名のレンダリング時間は数十ミリ秒、境界線自体は数ミリ秒、道路レンダリングは数十ミリ秒であった。

全体は100mS~500mSである。レンダリングが続くと数秒を超えることもある。多分、待ち時間の影響であろう。

2026.5.2 行政境界名描画スキップ処理

行政境界線はライン描画であるが、行政境界名はポリゴンの境界線として描画するため、 オーバヘッドが大きい。タイルを横切らないものは除外しているが、これをタイル座標ではなく、 世界XY平面座標で判定するように変更した。

2026.4.30 行政境界線描画

wayにタグがなかったとき、relation オブジェクトのタグを与える必要がある。 極座標からXY平面に変更した際、バグが入り込んだか、あるいは、 以前から不備があったのかも知れない。そうではなかった。

中ズームで市区町村の行政境界線を出力していないのではないか? 現在は admin_level <= 5 としている。これを admin_level <= 8 に変更した。 これで解決した。

標準OSM地図では zoom >= 10 で市区町村の境界線を描画している。

2026.4.30 行政境界線に沿って描く文字に誤りがある

多摩川の行政境界線について、神奈川側に、東京都や狛江市がある。

同様の誤りが他でも散見される。reversedフラグが関係しているかも知れない。

行政境界線については reversedフラグは false で、問題はなかった。

極座標をXY平面座標に変更したが、そこで、この誤りが混入していた。

2026.4.26 エリアについた名称が描画されていない[解決]

パソコン側のパーサーが中心を計算しているが、この座標が極座標のままかも知れない。

単独wayの場合、平面座標に変換していたが、その値を出力せず、元の極座標を出力していた。

2026.4.26 検索時のタイルマージンは上下左右に 20%とした

タイル境界線をまたぐアイコン、文字列がほぼ全て正常に描画されている。 ごく稀に、描画の途切れがあるが、解消を急ぐ必要はない。

タイル原点座標はマージンなしで求める。タイルRectと検索用Rectは別物とする。

2026.4.25 pointレコードの xc, yc が極座標のままのようだ

この修正でパッと見た時の地図はほぼ完成した。使いながら直せばいい。

2026.4.24 キャッシュの導入

R-Treeの葉ノードをキャッシュするようにした。キーは src, range, offset とした。

2026.4.23 極座標を世界XY平面座標に変換

修正点が多岐にわたるため、デバッグには時間がかかるであろう。

まずは、kanto でデバッグする。kanto_*.dat、kanto_*.obr ファイルのサイズは、 極座標の場合とほぼ同じ大きさとなった。

まず、建物のレンダリングだけを試みた。コンパイルエラーがとれたら、意外とあっさりと建物の レンダリングを確認できた。

2026.4.6 256x256画素の BitmapPool256 を廃止した

国土地理院地図の場合、タイルは 256x256画素であるが、高精細スマホでは 768x768画素に拡大表示している。

タイルキャッシュ上の全てのタイルは webp形式の圧縮画像データを持っており、 表示直前に Bitmap に変換している。国土地理院地図の場合、まず、256x256画素の Bitmap に変換して、そのあと 768x768画素の Bitmap に拡大する。

当初、この webp から 256x256画素の Bitmap への変換で BitmapPool256 を使おうとしたが、 エラーが出てうまく行かなかった。

256x256画素の Bitmap は一時的にしか存在しない。 一時的にしか使われないものは必ずしもガーベージコレクションの対象とはならず、 スタックを使うなど、効率的な方法が使われるようである。

よって、この256x256画素の BitmapPool256 を廃止した。

現在、地図アプリでは通常、ガーベージコレクションは殆ど起きない。 OSM地図でレンダリングが発生した場合は多少、ガーベージコレクションが起きる。 しかし、パフォーマンスに影響するほどのものではない。

処理時間がかかるのは OS地図Mのレンダリングだけである。アーカイブが増えれば、 大抵の場合、タイルがあるため、まずは、これを表示して、必要に応じて、バックグラウンドで レンダリングを行い、更新があったことが確認できた場合、新画像を表示している。 このため、更新に多少時間がかかっても問題はない。

もちろん、レンダリング時間が短いに越したことはないので、無理のないところで、 高速化に努めたい。

2026.4.5 zip を arc(Archiver: タイル画像用 read/write) および pak(Packer: OSMバイナリレコード用)に置き換えた。Mortonコードを使用

当初は id として zoom、x、y をこの順で組み合わせた long 値を用いていた。

その後、空間検索について調べて、 x と y をビット毎にインタリーブしたMortonコードを知った。 これまでの単純な id をこのモートンコードに置き換えた。Packer はリードオンリーであることと 最初からモートンコードとしたため、簡単であった。

タイル画像のアーカイブについては、既に、単純な id によるアーカイブファイルがあったため、 これをモートンコードに置き換えるための一時使用のプログラムも必要となった。 また、read/write や再構成など、readオンリーの Packer よりはるかにプログラムが大きいため、 モートンコードへの置き換えに多少手間取った。

2026.4.1 タイル画像について、zip から arc への変換完了

japan.arc(768x768 webp) は 110,057タイル 4.53GB となった。

gsi.arc (256x256 webp) は 101,668タイル 1.91GB となった。

2026.3.27 数時間後にチェックしたら id = 0 や id 中に zoom = 0 がある

エントリの更新や新規追加があると、エントリ管理部の id に異常がある。

エントリ管理部読み込みをシンプルにするとこのエラーは消えた。

2026.3.25 gsi に ort のタイル画像が書き込まれているところがあった

多分、ort から gsi に切り替えたとき、キャッシュに残っていた ort の画像データを 書き込んだのであろう。

地図ソースを切り替えたとき、Loader や Updater のリクエストキューは残っている。 load や update する時点では Source は Map の Source と異なっている。 LoaderやUpdater が map.src を使っていることはないか?

ba2bmpでのエラーともども、再利用に問題がありそう。

tileキャッシュから削除し、byte[]、Bitmap をプールに入れた後、tile.ba、tile.bmp に null を セットした。Tileオブジェクト自体は再利用していないので、関係ないとは思うが。


gsi と ort の振り分けを行わず、書き込みは全て gsi.arc に行っていた。 ort.arc も用意する。

gsi は問題なかったが、ort を表示するといくつかのタイルは正常に表示されたが、 tile.size が異常というエラーが多くでた。そのあと、gsi に戻すと、一つが ort のタイル画像に変わっていた。

まずはタイルサイズの異常がなぜ起きるかを調べたい。


ba2bmpでの Bitmap の再利用を止めると、エラーは起きなくなった。 アプリ全体のメモリ使用量は特に増える様子もない。

2026.3.25 ba2bmpでのエラー対策

下のエラーは依然として残っている。

java.lang.IllegalArgumentException: Problem decoding into existing bitmap
   	at android.graphics.BitmapFactory.decodeByteArray(BitmapFactory.java:671)
   	at com.example.map.Map.ba2bmp(Map.java:318)

フォーマットまたはサイズに誤りがあるときはエラーメッセージを出して変換をやめた。 しかし、このチェックにはかからず、ダウンが起きた。

このエラーは地図ソースを切り替えた時に起こるようだ。このとき、複数スレッドが一斉に動く。 排他制御に問題があるとか、使用中の画像メモリをプールに入れてぶつかりが起こる、 といったことが考えられる。

2026.3.25 Archiver: CRC32によるチェック

国土地理院地図の利用では、サーバー側の時刻とスマホに保存した時刻を比較して、 更新があったかどうか判断しているが、実際には、ある範囲の一部が変わっただけで、 大半のタイル画像は不変である。保存タイル画像の サイズとCRC32 と新タイル画像の サイズとCRC32 の いずれかが変わったときだけ、新タイル画像の書き込みを行い、同じだったときは、アーカイブファイル上の エントリ最終更新時刻のみを更新するようにした。

実際のところ、CRC32の変化はなく、時刻更新だけで済むことが多いことを確認できた。


タイルサイズが非常に大きい (tile.size=1380533830)というエラーが頻発する。 CRC32の追加など、仕様を変更したため、バグが混入したかも知れない。

画面(地図)は正常に表示されているが、このエラーが頻発している。無駄で不正な読み込み指示があるようだ。

ローカルヘッダにアクセスカウンターを追加したが、これがトラブルの原因かも知れない。

アクセスカウンタを入れた最新のアーカイブファイルがスマホに転送されていなかった。また、Archiver のプログラム の一部に不備があった。

このアクセスカウンタはタイル画像の保存量が膨大になった場合、再構築時に保存価値の少ないものを破棄するときに 使うためのものである。

以上の修正により、このエラーは解決した。

2026.3.24 Archiverの書き込みテスト

予め空きエリアと予備エントリを持って置きここに書き込むテストを行った。 読み書きともタイル当たりの処理時間は通常、 数ミリ秒のため、読み書きはシリアライズした。

読み込みは並行処理が可能とすることもできるが、パフォーマンスは変わらない。 tile.size=1380533830

2026.3.24 時々、次のエラーでダウンする

java.lang.IllegalArgumentException: Problem decoding into existing bitmap
   	at android.graphics.BitmapFactory.decodeByteArray(BitmapFactory.java:671)
   	at com.example.map.Map.ba2bmp(Map.java:318)

入り口でフォーマットやサイズをチェックしよう。

2026.3.22 独自アーカイブ方式

大規模Zipファイルから頻繁にタイル画像を読みだすと、使用メモリが大きく、GCが増えることが分かった。

試しに 国土地理院標準地図タイルのアーカイブファイルを作成した。

2026.3.21 256x256Bitmap用Pool導入

異なるサイズを一つのPoolでサポートすることを試みたがうまく行かなかった。

スマートではないが、プログラムは小さいので、Bitmap Pool を二つ使うことにした。

GSIではほかに大きなメモリがないはずだが、ほかに、メモリ使用が大きいものが残っているようである。 ZIPからの読み込みでかなりのメモリが使われる可能性はある。

巨大なzipファイルから頻繁にデータを読み込むには負担が大きいのであろう。 やはり、自前アーカイブを考えるべきか?

100万タイルまでなら一つのアーカイブで対応できる。それを超える場合には zoom 別とする。 全体の管理部はエントリ毎に id(8か7バイト)、オフセット(6か5バイト)、基準日からの経過日数(2か3バイト)をもつ。

管理部全体をメモリ常駐とするため、なるべく小さくしたい。管理部におく時刻情報は更新の有無を判断するときだけ 使うものであるので、精度は日で十分である。正確に日である必要はないので、例えば、UNIXタイムの上位2バイトとすれば 簡単である。正確なUNIXタイムはローカルヘッダにおく。

id は zoom(1バイト)、x(3バイト)、y(3バイト)とする。エントリは昇順として、バイナリ検索でエントリのオフセットを求める。

上限を100万(1M)タイルとすれば、管理部ば最大 16MB となる。余裕を持たせて id 8バイト、オフセット 8バイト、時刻 4 バイトとすれば 管理部ば最大 20MB となる。

2026.3.21 表示タイルを間違えている

国土地理院標準地図の表示で、異なるタイルが表示されることがある。一つの画面で同じタイルが二か所で表示されたり、 ズームを変えたとき、前のズームタイルが表示されたりする。現時点では、タイルの再利用はしていないし、同じ画面で 二か所で表示されるのは再利用では起こりえないであろう。複数のスレッドで並行読み込みを行っているので、 並行処理に問題が含まれているものと推測される。

static は間違い。

    static boolean load(Tile tile) throws Exception {

static をとった。多分、これで解決するだろう。

2026.3.20 webpへの変換

国土地理院は png、jpg である。ダウンロードファイルの webp への変換が漏れていた。

2026.3.20 ByteArrayPool を導入した

レンダリング用のPoolは BitmapPool、ByteArrayPool をしばらく使いこなしてからとする。

2026.3.19 BitmapPoolに release しないものがあるようだ[解決]

BitmapPoolに戻さず、GC の対象になっているものがある。

レンダリングで release が漏れていた。

しばらく動かした段階で再利用率は 99%であった。しかし、release もれが皆無かどうかはまだ検証していない。 もし漏れが残っているとしてもパフォーマンス上は問題ない。

次には、タイル画像(webp)のバイナリデータの再利用を考えたい。サイズにばらつきがあるため、ロスが一番少ない配列を割り当てる。 たまたまサイズの大きいファイルが続いた場合、サイズが大きい配列が長時間使われないという問題がある。 同様に、小さいものも使われにくい。新たにプールに返すものをリストの最後に置けば、使われないものはリストの先頭に固まるため、 配列数や合計サイズが上限を超えたときは、リストの先頭から削除してゆけばいい。

2026.3.18 BitmapPoolを導入した

Bitmapの再利用のために BitmapPool を導入した。 現在のところ、再利用率は 60%前後である。BitmapPoolに戻さず、GC の対象になっているものが まだ多いのは何故か?

更新のとき BitmapPool に戻していなかった。この修正で再利用率は80%前後になった。

2026.3.10 logcat風モニタを実装した

2026.3.9 タイル画像形式を WEBP とする。

タイル地図の画像形式は全て webp、品質85% とする。

国土地理院地図では標準地図が png、航空地図(ORT)が jpeg であるが、 これらも webp に変換して保存する。

japan768.zip の場合、png形式では 16.09GB であったが、webp(品質85%) では 3.65GB(23%) になった。

gsi.zip の場合、png形式では 2.02GB であったが、webp(品質85%) では 1.20GB(59%) になった。

ort.zip の場合、jpeg形式では 220MB であったが、webp(品質85%) では 135MB(61%) になった。

OSMの場合、1/4 以下に大幅な縮小効果が得られた。国土地理院地図では 40%程度の縮小効果であるが、 プログラムが分かりやすくなるため、WEBP形式に統一する。

2026.3.7 アプリが落ちた

直近のプログラム修正は Blockキャッシュ、Tileキャッシュのサイズを変更しただけ。

Android Studio を更新したばかり。これが関係しているかも知れない。

Requestがあった #1 新規 /storage/9C33-6BBD/Map/GSI/gsi/16/57694/25569.png
Shutting down VM
--------- beginning of crash
FATAL EXCEPTION: main
Process: com.example.map, PID: 14619
java.lang.IllegalStateException: The specified message queue synchronization  barrier token has not been posted or has already been removed.
	at android.os.MessageQueue.removeSyncBarrier(MessageQueue.java:531)
  	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2132)
   	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8686)
   	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1120)
   	at android.view.Choreographer.doCallbacks(Choreographer.java:926)
   	at android.view.Choreographer.doFrame(Choreographer.java:859)
   	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1105)
   	at android.os.Handler.handleCallback(Handler.java:938)
   	at android.os.Handler.dispatchMessage(Handler.java:99)
   	at android.os.Looper.loopOnce(Looper.java:346)
   	at android.os.Looper.loop(Looper.java:475)
   	at android.app.ActivityThread.main(ActivityThread.java:7889)
   	at java.lang.reflect.Method.invoke(Native Method)
   	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
   	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1009)
 Sending signal. PID: 14619 SIG: 9
------------------------

2026.3.4 ストレージからのタイル画像読み込み専用の Loader を Updater の前段におく[Map68]

これまで、Updater で ストレージからの読み込みとレンダリング/ダウンロードを実行していた。 この場合、全てのスレッドが時間のかかるレンダリングに従事すると、ストレージにタイル画像が あっても、読み込み処理が行えなくなり、長時間表示されないタイルが生まれる。 Loader、Updater をタンデムにつなぐと優先的に Loaderスレッドが現存するタイルの表示を行う。

Loader は LIFOキュー、Updater は FIFOキュー動作をさせる。

Loaderはメモリ使用量が小さいので、スレッド数を8にした。プログラムは分かりやすくなり、 パフォーマンスも向上した。

2026.3.4 ZIP採用で 128GB SDカード使用率は 約80%から66%に減少した

旧地図アプリ(階層ディレクトリ)も動かしており、これによるメモリ使用量が大きい。当面、動かし続け、 不具合が出たとき削除する。そのときは大幅に使用率が下がる。

2026.3.4 ガベージコレクションが増えた

ZIPファイルからの画像データ読み出しでガーベージコレクション(GC)が増えたようだ。

バックグラウンドで動くが、やはり、表示が少し遅くなった気がする。しかし、大量のタイル画像を保存するためには、 階層ディレクトリでは対応できないのでやむ得ないだろう。

BitmapFactory.Optionsには色々ある。GCを減らす方法もあるかも知れない。無理はせず、時間をかけて調べてみよう。

例えば、 options.inBitmap = reuseBitmap; を使えば、Bitmap エリアは再利用できる。 デフォルトでは新たにエリアが使われるため、いずれはGCの対象になる。

しかし、GCを減らそうとするとプログラムが複雑になってくる。うかつに取り組んではならない。


Background young concurrent copying GC freed 5266KB AllocSpace bytes, 2(40KB) LOS objects, 3% free, 281MB/290MB, paused 327us,147us total 238.691ms

Background concurrent copying GC freed 52MB AllocSpace bytes, 24(70MB) LOS objects, 28% free, 235MB/331MB, paused 165us,36us total 998.699ms

2026.3.4 やはり、書き換え可能なBitmapに変更した

Bitmap bmp = BitmapFactory.decodeStream(bis) では上書きできない。

Optionsをつかって mutable な Bitmap にした。 これで、等高線を上書きできた。

  BitmapFactory.Options options = new  BitmapFactory.Options();
  options.inMutable = true;
  Bitmap bmp = BitmapFactory.decodeStream(bis, null, options);

2026.3.3 タイル画像ファイルのアーカイブにZIPを使う[Map66]

まず、GSIから始めた。読み込み時間は階層ディレクトリよりも少しはかかるようになった感じがあるが、 気になるほどではない。

ORTについてもサポートした[Map67]。

OSMについてもサポートした[Map68]。

mutable については対応できていない可能性がある。

2026.2.28 タイルファイルの更新

ZIPファイル(japan.zipやkanto,zip)とタイルファイルの更新時間を比較して、 前者の方が大きいときはタイル更新の候補とする。

低ズームなほど更新の必要性は低いので、これを考慮して実際に更新するかどうかを決めている。

2026.2.27 OSMバイナリレコードは zip 化した

地図アプリとしてはリードオンリーのファイルは適宜コンパクトにしたい。

2026.2.27 森林が描画されない[解決]

多分、バス路線データ出力を TSV からOSMバイナリレコード形式に変更した際、 Parser.java にエラーが混入したものと推測される。 2, 3日前と比較すると high9 0.98GB -> 826MB、high12 4.64GB -> 4.62GB、mid9 635MB -> 571MB、 low3 635MB -> 571MB となった。恐らく、リレーション処理に誤りが混入したものと思われる。 誤って、何行か消してしまったのかも知れない。 まずは、Parser.java について、変更前と変更後をチェックしよう。

森林だけでなく、マルチポリゴンが全て消えているようだ。

datファイルの出力先を変更したが、リレーション処理結果についての出力先が変更されていなかった。

2026.2.23 PC版での異常[未解決]

パソコン版ではないが、経験したことのないレンダリングエラーがある。 ひとつのタイルではなく、タイルをまたいでおり、しかも正方形エリアの表示がほぼかけている。

昨年12月中旬にOSMバイナリレコードに reversedフラグを追加したが、パソコン版では未対応である。 しかし、一部の natural=cliff のトゲの方向が逆になるだけで、本件のエラーとは無関係である。

PC版は主にOSMマッピングに使っているため、このエラーは当面差支えないので、 解決を急ぐ必要はない。

2026.2.22 等高線描画実装

zoom 15以上で等高線描画をサポートした。

2026.2.20 タグエラー

parseTgas: error 280484/280669 tags_length=1050
280484/280669 length=1050 type=1 osm_id=319532000 ikey=96 18_233361_102603
  `?`?`??`??`??`
parseTgas: error 615903/616088 tags_length=1050
615903/616088 length=1050 type=1 osm_id=1123063412 ikey=96 18_233363_102603
`?`?`??`??`??`

ikey=96 はOSMタグではなく、bus_route_id である。 bus_route_japan.tsv を更新していなかった。

highway	trunk
lanes	4
lanes:backward	2
lanes:forward	2
maxspeed	40
name	銀杏坂
name:en	Ichozaka Avenue
name:ja	銀杏坂
official_name	国道50号
official_name:en	National Highway Route 50
official_name:ja	国道50号
ref	50
sidewalk	separate
surface	asphalt
turn:lanes:backward	through|through|right

2026.2.19 素早くスクロールしたときキューにリクエストが入らないときがあるか?

タイルキャッシュには登録されているようだ。

Tile.queRequest.offerFirst(tile); は他スレッドとぶつかりがあると待たずに false を返す。 待つためには putFirst とする必要がある。

2026.2.19 LinkedBlockingDeque

昨日は日中に一度アプリがダウンした。

今朝はアプリが固まっていた。 これはLinkedBlockingDequeをタイムアウトで抜けるようにすれば解決するかも知れない。

対策は、1日~数日様子を見て同じ現象が起きたときに考える。

2026.2.18 タイル画像読み込み、レンダリング/ダウンロードを大幅に簡単化した

LinkedBlockingDequeに待ちがたまった状態になっている。仕様が理解できていないようだ。

しかし、10分ほどたっと所望の動作をするようになった。OSの設定か何かに時間がかかった のかもしれない。

2026.2.16 画面更新で画面がちらつく

まず、古いタイル画面を表示、一旦消して描画、新タイルを表示しているのではなかろうか? キャッシュから削除した状態での表示を止める必要がある。

ファイル更新後、再読み込みさせず、直接 cache にセットさせるとちらつきはおさまった。

Tile.cache.put(tile.key, tile);//Tile.cache.remove(tile.key);
map.invalidate();

2026.2.16 タイル描画が途中で終わっていることがある[未解決]

描画の最中にBitmap更新のために tile.bmp が消されるのかもしれない。 この間ロックしてみる。

  synchronized (Tile.cache) { // 2026.2.16
      drawBitmap(canvas, tile.bmp, PX * (tx - bx) - offX,
              PX * (tile.y - by) - offY);
  }

  synchronized (Tile.cache) { // 2026.2.16
     tile.bmp = loadBitmap(tile.path());
  }
skia com.example.map  D  --- Failed to create image decoder with message 'incomplete input'

書き込まれたpngファイルには異常はない。

念のため、スマホを再起動したが、やはり、新規ファイルで起きた。

2026.2.14 行政境界線に沿っての行政名が描画されない[解決]

境界線は way で、名称は relation で描く。relation は RAdminに届いているが文字が 描画されない。

これを機会に描画方法を見直したい。

文字ごとならば次のようになる。

canvas.save() で現在の状態を保存。
canvas.rotate(角度, 中心X, 中心Y) でキャンバス自体を回転。
canvas.drawText() で文字を描画。
canvas.restore() で状態を元に戻す。 
canvas.save();
// 回転角と回転の中心点を指定して回転する
canvas.rotate(180f, canvas.getWidth(), canvas.getHeight() / 2);
canvas.drawText("兵", canvas.getWidth(), canvas.getHeight() / 2 - paint.ascent() / 2 - paint.descent() / 2, paint);
canvas.restore();

しかし、文字列の場合、Path に沿う方が、角度計算も自動化され楽であろう。 少なくとも当面は、他の描画との重なりは考えなくてもいいだろう。

canvas.drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

hOffset: パスの開始点からの水平距離(文字の開始位置をずらす)

vOffset: パスからの垂直距離(パスの上下にテキストを配置)


大字、小字レベルではうまくいく。都道府県堺は巨大なポリゴンまたはマルチポリゴンであり、 各タイルには少なくとも一つは含まれる。 しかし、境界線がタイルを横切ることはまれであり、パスのセットや空描画は全て無駄になる。 したがって、境界線全体でパスを作るのではなく、横切る場合に限り、その部分だけをパスに登録するなど の工夫が必要になる。

工夫により、パフォーマンス上は問題なくなった。乱数で、なるべく名称が重ならないようにした。 いずれ、もう少し改善して、行政名での重なりはなくしたい。

急ぐ必要はないので、少しずつ改良していきたい。


シンプルなケースでは drawTextOnPath が簡単でいい。しかし、 OSM地図では、境界線がタイルを横切ることは稀であり、無駄を極力省くことが必要である。 また、境界線が極端に折れ曲がっているような位置には行政境界名をうまく描けない。

行政境界名の場合は縁取り文字でなくてもよいが、特に川名の場合には文字の色が水の色に近いため、 文字を白色で縁取ることが望まれる。理解する限りでは drawTextOnPath では縁取り文字を描画できない。

結局、煩雑ではあるが、これまでの描画を使い続けることにした。 あらかじめ、描画位置を決めておき、その部分だけのラインレコードをOSMバイナリレコードとして出力して おけば、レンダリング時間は短縮できるが、このような手を使わずに高速化できる方が望ましい。

2026.2.10 LinkedBlockingDeque

wait/notify をやめて LinkedBlockingDeque に変えた。 これでアプリが止まることがなくなった。

2026.2.8 OSMバイナリデータ更新

行政境界線が正常に描画されるようになったので、OSMデータを更新した。

建物 27454756 / 全地物 46216883 = 59.4%

2026.2.8 行政境界線の描画

次の行政境界線(way id=1375320805)がなぜ描画されなくなったか調べよう。

relationのメンバーではあるが、wayレコードとしても出力され、境界線は、wayレコードで描画する仕組みである。

<way id="1375320805" visible="true" version="1" changeset="164593256" timestamp="2025-04-06T14:10:13Z" user="Rokomo35" uid="19071241">
<nd ref="6818087660"/>
<nd ref="3405224735"/>
<nd ref="12734117083"/>
<tag k="admin_level" v="4"/>
<tag k="boundary" v="administrative"/>
<tag k="maritime" v="yes"/>
</way>

この wayレコードは存在し、RAdminクラスの描画メソッドに届いている。

RAdmin 14_14557_6454 osm_id=1375320805, type=1, admin_level=4

次に osm.drawLine(canvas, r, paint4) をチェックする。

タイルサイズは 876x876 であるから、座標値に異常はない。 ノード数は差分を2バイトで表すために、ノード追加が起きたため増えたようで問題はない。

574.6582, 91.28107
527.336, 183.33154
480.01373, 275.38388
475.8404, 373.3639
471.66708, 471.3411

他の描画をやめて、白地図に行政境界線だけを描画する場合は正しく描画される。 例え、行政境界線描画を最後にしてもうまく行かない。

テーマパークや国立公園などの境界線(二重線)の描画をやめると、行政境界線が正常に描画されるようになった。


二重線描画を canvas.save()、canvas.restore() でくくることにより解決した。

2026.2.7 リレーションboundary=administrative処理の見直し【取りやめ】

relationメンバーの way に boundary=administrative や admin_level のタグ がなかった場合、親relation のタグが適用される。 複数の relation が親になる場合、最小のadmin_level(正の値)が適用される。

これまでのプログラムではこの処理を省いていた。 境界線が描画されなくなったことにこれが関与しているかもしれない。 別に原因があるかもしれないが、リレーション処理を見直そう。

Tag[] tags を HashMap<Key,Tag> tags に変更する。 この段階で生成されるOSMバイナリレコードファイルに変わりはないかを調べる。

2026.2.7 OSMバイナリレコードファイルサイズ

パソコンのgisをコンパイルし直すと、全体のzipサイズが少し変わった。

元のディレクトリ毎のサイズは変わらない。 4ディレクトリの圧縮順序が変わると、圧縮ファイルのサイズは少し変わるようだ。

              後        前
kanto-high9  343MB   343MB
kanto-high12 971MB      971MB
kanto-mid9   109MB      109MB
kanto-low3  12.2MB     12.2MB

2026.2.5 先日OSMデータを更新したが、県境線が描画されなくなった【解決】

全都道府県ではなく、描画される場所もある。全体としては描画されないものが多い。

OSMバイナリデータ全ファイルのzipのサイズは 昨年12月13日が 3,815MB に対して今回は 3,692MB と小さくなっている。

この間、プログラムは修正していないはずだが。もっと前の変更が関係しているのかもしれない。

関東地方のOSMデータをダウンロードして確かめよう。関東でも一部が描画され、大半が描画されなかった。

都道府県境界だけでなく、adminラインの殆どが描画されていない。しかし、所々に限り描画される。 タイル境界線で切れているため、OSMバイナリレコード自体には問題はないと思われる。

構成要素のway(type=1)はOSMバイナリデータにある。

Outer Polygonが複数の場合、Outer Polygon別のレコードに分けている。 内部に他県、他市の飛び地があれば Multipolygon(type=3)になる。そうでなければ Polygon(type=2)になる。

RAdminでチェックすると下記のレコードが存在した。しかし、一部のタイルに含まれるだけであり、 大抵のタイルのレンダリングにはこれらが全く含まれていないようだ。

RAdmin osm_id=-2679957, type=2, 千葉県
RAdmin osm_id=-2689487, type=3, 神奈川県
RAdmin osm_id=-2689476, type=3, 川崎市
RAdmin osm_id=-2689482, type=3, 横浜市
RAdmin osm_id=-2689481, type=2, 相模原市
RAdmin osm_id=-2689466, type=2, 緑区
RAdmin osm_id=-5301639, type=2, 八王子市
RAdmin osm_id=-5100276, type=2, 町田市
RAdmin osm_id=-1759474, type=2, 世田谷区
RAdmin osm_id=-2689451, type=2, 南区
RAdmin osm_id=-2689449, type=2, 中央区
RAdmin osm_id=-2689471, type=2, 青葉区
RAdmin osm_id=-2689461, type=2, 港北区
RAdmin osm_id=-2689474, type=3, 麻生区
RAdmin osm_id=-2689469, type=2, 都筑区

昨年12月半ばに reversedフラグを導入した。これが関係しているのだろうか?

中低ズームで boundary=administrative が出力されていないのではないか?

RAdmin 9_454_203 osm_id=269179477, type=1, admin_level=2, null
RAdmin 9_454_203 osm_id=269179483, type=1, admin_level=2, null
RAdmin 9_454_203 osm_id=355231075, type=1, admin_level=2, null
RAdmin 9_454_203 osm_id=355233089, type=1, admin_level=2, null
RAdmin 9_454_203 osm_id=262103468, type=1, admin_level=2, null

RAdmin 9_454_202 osm_id=269179483, type=1, admin_level=2, null

二重線描画を canvas.save()、canvas.restore() でくくることにより解決した。

2026.2.4 OSMタイル更新でメインスレッドが高負荷でアプリが止まることがある【解決】

一斉に全タイル更新ではなく、確率的に更新を減らしたときは起こりにくい。

国土地理院地図はサーバーからのダウンロードであり、この現象は起きない。 OSMタイル更新処理はスレッドで行っているので、メインスレッドの負荷にはならないはずだが。

タイル更新時のロック(synchronized)が原因かもしれない。

2026.2.4 GPSログが200メートルほど途絶えた。旧アプリでは正常に記録されている【未解決】

メインスレッドの高負荷が関係しているかもしれない。

2026.2.2 更新処理 Thread.sleep をやめて wait/notifify に戻した[Map55]

2026.1.31 WalkServiceを LocationServiceに統合した[Map54]

タイマー起動のスレッドによる監視がなくなった。今日の動作は問題なかったが、 1、2か月使ってみないと、これで問題ないかどうかは判断できない。

2026.1.30 GPSログが取られていない

室内自転車を踏んでいたときの記録はあるが、午後の散歩の記録がない。

たまたま、GPS追跡はオフにしていたが、GPS追跡のオン・オフは地図画面表示だけに関するものであり、これをオフにしても GPSログファイルへの書き込みとデータベースへの登録は行うはず。前アプリではGBS追跡が行われていた。

原因は室内ではGBSをオフにして、動きを感知したときオンにしているが、このオンにする操作に問題があるようだ。

前アプリでは一つのServiceで GBSログとステップカウンタログの処理を行っていたが、 新アプリでは LocationService と WalkService に分けた。前者はフォアグラウンドサービスにしているが、 WalkServiceはそうしていない。メインActivityと同様にスリープしてしまうようだ。 そうすると、動きが検出できない。

まずは、まずは現状のLocationServiceとWalkServiceの動作を比較する。


スレッドはスリープさせられることがある。AI検索では 「バックグラウンドでの定期的・長期的な処理は WorkManager に任せる」、 「バックグラウンドで確実に処理を継続させたい場合は、Foreground Service の利用や、 WorkManager によるジョブのスケジューリングを検討するのが一般的です」となった。

WalkServiceをやめて、LocationServiceに一体化した方が簡単かもしれない。


Serviceは Activityと同じプロセスで動くので、重い仕事をさせてはならない。

2026.1.29 テーマパーク等の境界線

テーマパーク(tourism=theme_park)等の境界線は見栄えのよい二重線で描画する。 C#では使いやすい関数があるが、Android Javaではこれまでは手の込んだ方法で二重線を描画していた。 しかし、Canvas#setClipPathメソッドを使えば簡単に二重線を引けることが分かった。

2026.1.16 GPS過去ログ表示

多少のバグが残っているようだが、概ね完成した。

2026.1.13 データベース編集ポップアップがメニュー操作で消える[未解決]

キーボード表示のため、バス時刻表などとは異なる方法でポップアップしている。 バス時刻表などでは問題がないが、この新しい方法ではポップアップがメニュー操作やoutsideタッチで消える。

setOutsideTouchable(false);としているがダメ。

注意すればデータベースの編集には問題がないため、保留する。そのうち、解決策が見つかるだろう。

2026.1.7 SQLiteデータベースのオンライン修正

これまでの SQLiteデータベースの修正はパソコン経由のため手間がかかる。

とりあえず、万歩計データのUpdate は新アプリから直接実行できるようにする。

データベースは GIS/sqlite3/gis.db と Map/sqlite3/walklog.db の二つ。

update テーブル set 項目 = 値 where date = 日付

2026.1.7 Pedometer#changed()微修正

日中での shutdown にはこれでいいだろう。

2026.1.1 gpslogデータベース

データベースへの登録をやり直した。ORDER BY date DESCは正常に動作した。 データベース異常で地図アプリが動かなくならないようにしたい。

2025.12.31 gpslogデータベースへの書き込みに問題か

DB Browser で直近のデータが確認できない。ORDER BY date DESC がうまく動作しない。

来年、step by step でチェックしよう。

変換プログラムで db.close() が漏れていたのが原因かもしれない。

2025.12.30 gpslogファイルへの書き込み完了[Map40]

これまでと同じ形式での出力を完成した。

GBSログ表示は gbslogデータベースを使う。

2025.12.28 Applicationクラスの使い方を間違えていた

インスタンスは自動生成される一つを共有する App app = new App(); としてはならない。 必ず、App app = (App) getApplication() とする。 Activity、Service からでないと App のインスタンスを得られない。

2025.12.27 万歩計はシャットダウン処理を除いて完了した

最初に walklog オフライン訂正した。

update walklog set steps=10198, cum_steps=83450 where date = 20251226; 
insert into walklog values(20251227,5511,88961);

地図に表示する今日の歩数およびリスト&グラフ(日別、月別、DBテーブル)が正常に動作することを確認した。

シャットダウン時の処理はもう少し見直したい。 また、シャットダウンさせると、現在使っている地図アプリでDBのオフライン修正が必要になる。 頻繁なテストが面倒であるため、この動作確認は新アプリの完成が近くなったときまで保留する。

2025.12.24 GPSログをSQLiteデータベースに[Map38]

冗長なレコードを除けばGPSログ全体をひとつのSQLiteデータベースとしても、 十分な速さで空間検索を行えることが分かった。これにより、過去のGPS軌跡表示が簡単になる。 新アプリの運用開始はもっと先でよいので、時間をかけて細部を詰めたい。

歩数計については、前システムから、SQLiteを使用しているが、項目を見直し、 シャットダウン対策をシンプルにしたい。

2025.12.24 GPSログをSQLiteデータベースに[Map36]

ここ1年強のデータを登録した。スマホでの2箇所での空間検索時間は「350行, 実行時間 19ミリ秒」、 「9862行, 実行時間 38ミリ秒」となった。

過去のGBSログ表示では、歩行は10秒間隔で十分であろう。 しかし、日別のログファイルをやめる場合には、現状のデータをそのまま年単位のデータベースとするのが 望ましい。空間検索用データベースは別途用意する。

項目は UNIXタイムを date(20251224)、time (102113) に分けた方がいいであろう。 インデックスは date だけに付与する。

実際上の空間検索では、日付 date だけを抽出する。そのあと、この日付ごとの全レコードを抽出して 地図に、GPS軌跡を表示する。

理想としては、現在のGBSログを一つの SQLite データベースにすることである。 可能かどうかはもう少し時間をかけて検討する。

2025.12.23 WalkService と LocationService の実装開始

前システムでは一体化していたサービスを二つに分けた。 分かりやすくなったが、WalkService から LocationService へ(またはその逆)のデータ引き渡しには 少し手間がいる。

停止からの起動では画面全体が一旦水色(海)になってから、所定の地図が表示されることがある。

activity_main.xmlにエラーがある。これが原因とは思えないがこのエラーをとろう。

zoom in/out ボタンのパラメータにそれぞれ一つずつのエラーがあるようだ。 そのほか com.example.map.Map にも一つのエラーがある。

エラーとは関係ないようだが、

    <com.example.map.Map
        android:id="@+id/map_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop = "50dp"/>
としていた最後の行が
        tools:layout_editor_absoluteX="72dp"
        tools:layout_editor_absoluteY="50dp"
に置き換わっていた。

アプリ起動には時間がかかる。水色になったので違和感を感じるようになったのかもしれない。 他のアプリでは真っ白な画面である。


最初の zoom、x、y が海になっており、そのあと、GPSの位置情報取得で、現在地に変わるのではなかろうか。 LocationService導入でどこかにミスが入り込んだのではなかろうか。

この予想は当たった。最初は onDraw 10-512-512 で、そのあと onDraw 10-908-403 に変わった。

最初に無効な移動を行っていた。実行条件を tracking から tracking & time > 0 に変更することにより解決した。

2025.12.21 交差のある多角形

例えば余呉湖を取り巻く琵琶湖国定公園はハイズームでは交差のない閉ループであるが、 中低ズームで単純のノードを間引くと、自己交差が生まれる。 このため、多角形の少し内側の境界線を求めるときにエラーが起きる。

例えば、PostgreSQL/PostGIS では、自己交差が生じないように間引きを行うことができる。 たしか自己交差で生まれた小さい閉ループを切り取っているはずである。

しかし、自作地図ではPostgreSQL/PostGISを使わない。 似たことを自作プログラムで行うのは面倒である。

一番簡単なのは、中低ズームでは二重線の描画をやめて、単純な実線を引くことである。 例え自己交差があっても実線は問題なく描画できる。

境界内側の線を求めるのに失敗したときだけ内側の線を引くのをやめてみたが、 これではうまく行かなかった。

中低ズーム(zoom 13以下)では単純な実線にするのが一番簡単である。簡単な方法が見つからない限り、 こうしておく。

Windows Javaや C# の場合、二重線が簡単に描画できる。Android Javaのグラフィックスもそのうち 進歩する可能性もあるので、ここは無理はしない方が無難であろう。

2025.12.21 Map31: OSMタイルの更新機能を実装した

国土地理院地図と同様に、低ズームほど更新確率を小さくした。 これまでの地図アプリとはかなり異なるため、しばらくは様子見としたい。

2025.12.21 これまでと同様に zoom in/outボタンを実装した

2025.12.21 国土地理院地図の低中ズームタイルの更新頻度を下げた

zoom 18を基準として、zoom が1小さくなるにつれて、更新確率を半減した。 これにより、更新による画面のちらつきは感じられなくなった。

2025.12.20 オフラインレンダリング

gsiにあるタイルをosmにも含まれるように一括レンダリングを行った。

通常の地図表示では殆どのタイルが存在するため、表示は素早くなった。

スマホ   gsi   1.85GB   62,584tiles
スマホ   osm  11.09GB   68,016tiles
tablet   gsi   1.88GB   63,536tiles
tablet   osm   4.56GB   66,060tiles

低中ズームタイルの更新は殆どいらない。

例えば、zoom 18以上は確率1.0、zoom 17は確率 1/2、zoom 16は確率 1/4、 zoom 15は確率 1/8、zoom 14は確率 1/16、zoom 13は確率 1/32、... という風に 低ズームほど更新確率を下げるのが望ましいであろう。 これによって、通常は、パフォーマンスを損なうことなく更新が行われるであろう。

画面表示範囲の全タイルを更新するモードも用意しておきたい。

2025.12.19 Renderer::renderの引数 map を削除した

Map の static 変数は使用するが、オブジェクトの変数は使わない。 バッググラウンド更新でも同じ renderメソッドを使う。

2025.12.17 国土地理院地図タイル更新

まず、保存されたタイルを表示する。 現時点では半年以上たっていたら、サーバーの更新時間をチェックして、更新されていた場合 ダウンロード更新する。更新されていなかった場合、保存タイルの時刻だけを更新しておく。 これにより、再び半年たったとき、更新処理を行う。

サーバー上のタイルの更新時間が変わっていても実際は中身は同じケースが多い。 同じでも、描画をしなおすと画面がちらつく。 したがって、バイナリチェックを行い、変化がない場合、更新時間の変更だけでもよい。 しかし、日常利用が OSM地図が中心のため、当面、この対策は保留する。

2025.12.17 タイル更新、表示機能は概ね完成した

パフォーマンスについては5~10万タイルほどで判断する。

スマホ   gsi   1.83GB   62,413tiles
スマホ   osm   1.09MB    5,110tiles
tablet   gsi   1.87GB   63,405tiles
tablet   osm    571MB    5,899tiles

2025.12.17 更新(download/rendering)は最後に要求したものを先に実行するように変更した

画面からはずれたタイルは後回しでよい。

2025.12.15 町田の芹が谷公園の東南で、崖(閉ループ)のアイコンの向きが逆になっている。

標準OSM地図は正しいので、データの誤りではない。閉ループでない場所は正しく描画されている。

全ての閉ループを機械的に並び替えているのが誤りである。 natural=wood のようなエリアタグの場合は閉ループにする必要があるが、natural=cliffのような ラインタグの場合は例え閉ループでも polygon(type=2) とはせず、line(type=1)とする。

以前はこのようにしていたと思うが、現時点のプログラムではこの例外処理が漏れているのであろう。

Parser.java の修正がいる。現在は、最初に polygon 扱いにしてからタグ処理をしている。 polygon処理の前にタグ処理を移す。natural=cliff、man_made=embankment などは閉ループであっても、type=1 とする。

地図表示システムも 閉ループを polygon扱いとしており、修正は簡単ではない。 反転したことをフラグで記憶しておく方法もある。時間をかけて対策を考える。


reversedフラグを導入した。Parser.javaでは、polygon を反転した場合、この reversed フラグをオンにしておく。 地図レンダリングアプリではこのフラグがオンのときは、180度回転したアイコンを並べて描画するように 変更した。 この方法では Parser.java、地図アプリとも修正はシンプルで済んだ。

2025.12.12 zoomを変えたときレンダリングが起きないことがある

tile.zoom が更新されていないようだ。例えば map.zoom = 16、tile.zoom = 15。

zoom 14 から、すばやく 15、16 と変えたとき、map.zoom = 16、tile.zoom = 15 が起きるので、 このことは異常ではない。zoom 16 のタイルが描画されないのは問題であるが、最近はこの現象はあまり経験して いないので、様子見とする。

2025.12.11 森林の一部が描画されない

気づいたのは山梨県と神奈川県の境界付近の一部だけ。まずは、その領域の osm_id を調べよう。

osm_idは 967900(relation: natural=wood, type=multipolygon) であることが分かった。

OSM Inspectorにより、outer polygon の一部の way の role が inner になっていた。 これを outer に訂正した。これによって、正しく描画された。

2025.12.09 道路の描画順序が間違えていた

見たところ、ソーティングを忘れたわけではなさそう。

zorderの初期設定をコメントしていた。

2025.12.09 森林が一部しか描画されない

マルチポリゴンが描画されていないようだ。

順に実装しており、マルチポリゴンの一部は未実装のため、コメントアウトしていた。 この関係を実装して解決した。

2025.11.14 開発に着手した

リファレンス