Android 地図アプリの修正

少し前に書いたように、開発した地図アプリに不具合があったので修正を試みました。
alasixosaka.hatenablog.com

今回はソースコードの修正部分だけを書いています。ソースコードの全体は過去の記事を見て下さい。
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com
alasixosaka.hatenablog.com

POIファイル選択時にテーマ選択画面がオーバーラップして表示される

今回初めてPOIファイルを本格的に活用したのですが、メイン画面から”SELECT POI FILE"をタップしてファイル選択画面を出すと、引き続きテーマ選択画面が表示されるようになっていました。
これは単純なミスで、メイン画面で各種設定選択画面でタップするところの処理でBreakが抜けていたためでした。具体的には、”MainActivity.java”の”public void onClick(View view){”以下のところで下記のように、case R.id.btPOISelect:のところの最後にBreakを追加しました。

case  R.id.btPOISelect:
                    preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
                    Path = preferences.getString("path", sdPath);
                    fillter = "poi";
                    FileListDialog dlg2 = new FileListDialog(MainActivity.this);
                    dlg2.setOnFileListDialogListener(MainActivity.this);
                    dlg2.show(Path,Path,fillter);

                    break;

頻繁にアプリが再起動する

前に書いたように、この症状が一番困ったことでした。アプリが頻繁に再起動して地図もまともに読めないし、再起動すると軌跡が消えてしまう、更に再起動を繰り返しているうちにアプリが落ちてしまうということまで起こった。端末を再起動すると症状は少し改善したが、結局この日は、あまり長い時間地図を見ずにだましだまし使うことでなんとか対処した。帰ってから少し調べて原因は端末が向きを検知して画面を回転させているためだと推定した。地図アプリ専用端末にするなら端末側の設定で回転を固定してしまえば問題ないが、他のアプリも使う場合はアプリ側で回転しないように固定してやる必要がある。
設定は、"android.manifest.xml"を書き換えることで行う。"activity"の部分で元のファイルでは特に方向指定をしていなかった。

   <activity android:name="com.example.mpf_rotationE.RotateMapViewer" />
        <activity android:name="com.example.mpf_rotationE.RotateMapViewer2"/>
        <activity android:name="com.example.mpf_rotationE.ThemeSelect"></activity>
        <activity android:name="com.example.mpf_rotationE.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.mpf_rotationE.SimplestMapViewer"
            android:configChanges="keyboardHidden|orientation|screenSize" />
        <activity
            android:name="com.example.mpf_rotationE.OverlayMapViewer"
            android:configChanges="keyboardHidden|orientation|screenSize" />
    </application>

回転してしまって困るのは、地図を表示する部分なので、初めの2つ、"RotateMapViewer"と”RotateMapViewer2"にスクリーン方向を指定するandroid:screenOrientation="portrait"を追記した。

<activity
            android:name="com.example.mpf_rotationE.RotateMapViewer"
            android:screenOrientation="portrait" />
        <activity
            android:name="com.example.mpf_rotationE.RotateMapViewer2"
            android:screenOrientation="portrait"/>
        <activity android:name="com.example.mpf_rotationE.ThemeSelect"></activity>
        <activity android:name="com.example.mpf_rotationE.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.mpf_rotationE.SimplestMapViewer"
            android:configChanges="keyboardHidden|orientation|screenSize" />
        <activity
            android:name="com.example.mpf_rotationE.OverlayMapViewer"
            android:configChanges="keyboardHidden|orientation|screenSize" />
    </application>

現在地がずれる

現在地のマーカーが何故だかずれていて、ズレはそれほど大きくなかったのでそれほど大きな影響はなかったが、なんとなく気持ち悪いので調べてみた。
どうやら、Androidの仕様が9以降で変更になっているみたいで、Android StudioエミュレータでAndoroi8では正しく表示されるのに、Android9ではずれることが確認できた。地図の縮尺も、キャプチャのサイズも違ってちょっと不細工だが、下の図で比較すると、Android8では正しく現在地の円が表示されているのに対し、Android9では精度の円の中心が正しい現在地なのだが、現在地表示の円は右下にずれている。

f:id:alasixOsaka:20210306131823j:plain
Andoriod9では現在地がずれている。

このずれの原因はどうやらAndroidの仕様変更に伴うようで(調べてもよくわからんかったが)、図形を描写するときにAndroid8以前であれば、図形の中心を描写する中心点にして描いていたものが、変わっているようだ。
ちなみに、現在地を示すマーカーは42×42のグラフィックスで描かれているので、オフセットの位置を調整して42ポイントずらすようにしたらよいかと思ってやってみたら、ずらしすぎになってしまい35ポイントずらすと丁度良い位置に表示された、何故35ポイントなのかは謎。
修正ポイントは"OverlayMapViewer.java"の”protected void createLayers(){"以下のMakerを指定する部分のMarker marker=...でMarkerを指定するところで、以前は最後のオフセット値を0,0としていた(一番下の行)。

protected void createLayers() {
        super.createLayers();
        //setGPS=(SamplesApplication)getApplication();
        globals=(SamplesApplication)getApplication();

        // we just add a few more overlays
        //Layers y = mapView.getLayerManager().getLayers();
        addOverlayLayers(mapView.getLayerManager().getLayers());
        //Layers z =mapView.getLayerManager().getLayers();
        Drawable drawable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDrawable(R.drawable.ic_maps_indicator_current_position) : getResources().getDrawable(R.drawable.ic_maps_indicator_current_position);
        Marker marker = new Marker(null, AndroidGraphicFactory.convertToBitmap(drawable), 0, 0);

この部分をAndroidAPIを判断して、27以下ならオフセットを0、28以上ならオフセットを-35とした。オフセットの値は変数ofsetに持たせて、if文でAPIを判断して代入している。

protected void createLayers() {
        int ofset = 0;
        super.createLayers();
        //setGPS=(SamplesApplication)getApplication();
        globals=(SamplesApplication)getApplication();

        // we just add a few more overlays
        //Layers y = mapView.getLayerManager().getLayers();
        addOverlayLayers(mapView.getLayerManager().getLayers());
        //Layers z =mapView.getLayerManager().getLayers();
        Drawable drawable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDrawable(R.drawable.ic_maps_indicator_current_position) : getResources().getDrawable(R.drawable.ic_maps_indicator_current_position);
        if (Build.VERSION.SDK_INT <= 27){
            ofset = 0;
        }else if (Build.VERSION.SDK_INT >= 28) {
            ofset = -35;
        }
        Marker marker = new Marker(null, AndroidGraphicFactory.convertToBitmap(drawable), ofset, ofset);

POIの追加

今まではPOIとしては矢印だけだったのですが、この際なので少しマークを追加してみました。
追加したのは、トイレ、レストラン、水場、チェックポイントの4つです。コンビニも入れようかと思いましたが、まあレストランマークで代用できるので良しとしました。
ただ、これらのマーク(以前からある矢印も含めて)やっぱり、Android9ではずれるので対策が必要でした。
以前にも書いたように、POIを表示させるためのファイルはカシミール3Dで作成しています。したがって、カシミール3D上のアイコンと地図アプリのアイコンに相関を持たせる必要があります。
alasixosaka.hatenablog.com

まずは、マークの追加から。カシミール3D側はもともとあるものを使っています。

カシミール3Dでのアイコンの決定

トイレはGarmin GPS カラーアイコンのアウトドアの中にあるトイレです、GPXファイルに書き出した場合の記号は952010でした。
レストランは同じくGarmin GPS カラーアイコンのスポットの中にあるレストランです、GPXファイルに書き出した場合の記号は956005でした。
水場は同じくGarmin GPS  カラーアイコンのアウトドアにある水場です、GPXファイルに書き出した場合の記号は959037でした。
チェックポイントは同じくGarmin GPS カラーアイコンのマークの赤旗、GPXファイルに書き出した場合の記号は951003でした。

f:id:alasixOsaka:20210307101720j:plain
カシミール上でPOIアイコンを配置した様子。右からトイレ、レストラン、チェックポイント、水場
Androidアプリ上で表示するための画像を作成

次に、Androidアプリ上で表示するための画像を作成します。
ネット上で適当に無料素材をダウンロードして使いました。
ダウンロード素材は背景が透明の場合もありますが、背景が白だったりすることもあるのでその場合は、少し加工が必要です。
今回はPowerPointを背景を透明にしてみました。まず、PowerPointで新規ファイルを作成します。
ここに、ダウンロードした画像を挿入します。挿入→画像→このデバイスからファイルを選択して挿入します。
挿入した画像をクリックし、書式タブを選択、色をクリックして、透明色指定をクリックします。画像の背景の部分をクリックすれば背景が透明になります。
出来た画像は右クリックして、図として保存、保存形式はpngです。画像サイズは適当に調整してください。30-50ピクセル四方くらいが適当なサイズだと思います。
ちょっとめんどくさいですがこうすれば背景は透明にできます。

Android Studioで画像の読み込み

出来た画像をAndroid Studioに読み込みます。
Android Studioでプロジェクトを開き、左のペインのdrawableを選んだら、左端の縦タブのResource Managerをクリックします。ここでタブがdrawableになっていることを確認し、左上の「+」をクリック、import drawableをクリックします。するとファイル選択画面が表示されるので作成した画像ファイルを選択します。背景が透明になっていれば、グレーのチェッカー模様が背景になっているのでここで確認できます。

プログラムの修正

POIで配置するアイコンもやはりAndroid9ではずれることが分かったので、修正が必要です。アイコンの位置を決めている部分は、現在地を表示している”OverlayMapViewer.java"と同じファイル内にあり、”addOverlayLayers(Layers layers) {”の中に書いてあります。ここではGPXファイルを解析してルートを表示し、POIファイルを解析してアイコンを表示しています。めちゃめちゃ長いですが、下の方のPOIファイルを解析する部分の下に”POIname"でswitch~ case でそれぞれのアイコンに対する画像を読み込んでmarkaersにaddしています。例えば、左折の矢印1番の場合case"1001009"以下の部分で処理を行っています。

try {
            String listXmlPath = Path + "/"+ POI_FILE;
            is = new FileInputStream(new File(listXmlPath));
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
           ~省略~
           
                    case XmlPullParser.TEXT:
                        POIname = xpp.getText();
                        Log.i("MainActivity", "テキスト = " + POIname);

                        if (XPname.equals("icon")){
                            switch (POIname) {
                                case "1001009":
                                    markers.add( Utils.createTappableMarker(this, R.drawable.left1, latLongX));
                                    break;
                                case "1001010":
                                    markers.add( Utils.createTappableMarker(this, R.drawable.left2, latLongX));

ところが、この処理の部分”markers.add( Utils.createTappableMarker(this, R.drawable.left1, latLongX));”では、オフセットを設定することができないので、この部分を下記のように変更します。

Drawable drawable = getResources().getDrawable(R.drawable.left1);
Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(drawable);
Marker poimarker = new Marker(latLongX, bitmap, ofset, ofset);
markers.add( poimarker);

ちょっとめんどくさいですが、一旦画像をdrawableに読み込んで、ビットマップに変換し、poimarkerに位置とビットマップとオフセットを設定してからmarkersにaddしています。こうすることでオフセットが設定できます。オフセットの値"ofset"は現在地表示の時と同様にandroidAPIで判断して”0"か"-40"を設定するようにしています。本当は画像ファイルのサイズがそれぞれ微妙に違うので正確に表示するためには個別に設定したほうが良いのですが、処理が煩雑になることと、POIの位置についてはそれほど厳密でなくても実用上差し支えないと判断して一律の値にしています。

int ofset =0;
if (Build.VERSION.SDK_INT <= 27){
   ofset = 0;
}else if (Build.VERSION.SDK_INT >= 28) {
   ofset = -40;
}

実際の地図上にはこんな感じで表示されます。

f:id:alasixOsaka:20210307113314j:plain
エミュレータ上でPOIのアイコンを表示

実機(AQUOS SENSE PLUS)でも問題ないことが確認できました。

参考にしたサイト
[Android] Activity のライフサイクルと画面の回転
AndroidOSのバージョン(APIレベル)によって処理を変える方法 | 一番かんたんなJava入門
パワーポイントで簡単に画像の背景を透過するかんたんな方法│BtoBのデジタルマーケティング戦略ラボ
mapsforge でポップアップするマーカーを試す - プログラマーのメモ書き
【2021年度版】無料&商用利用できるフリーアイコン配布サイト14選! | S.Design.Labo