ダイアログからGPXファイルを選んでルートを表示する(アンドロイド地図アプリの開発 その14)

アンドロイドアプリの開発、ようやく久々の更新です。
ブログでも書いたように、実機での検証でうまくいかずに悩んでいましたが、ようやく解決しました。

alasixosaka.hatenablog.com


前回までで、GPXファイルの読み込みまでできるようになったと思ったのですが、エミュレータではうまくいくのに、実機ではファイルの途中までしか読めないという不具合が発生。これに結構はまりました。
デバッグも試して、さっぱり原因がつかめなかったので、実機(XPERIAZ4)を初期化してみましたが、結果は変わらず。
初心に帰って、ファイルを普通にオープンしてテキストファイルとして1行ずつ読むことをやってみると、ちゃんと読めることが分かった。
これをやっているときに、XMLファイルを読み込む方法を書いた別のブログをみつけて、やってみるとうまくいった。
結局、なぜうまくいかないのかという細かい理由はわからないまま。でも、とりあえず読めたので良しとしよう。

今回やったこと。

まずは、以前にやったダイアログ表示でファイルを指定し、
alasixosaka.hatenablog.com

GPXファイルを読み込む。

ダイアログでファイルリストを表示しファイルを選択する。

これは、以前にやったのと同じ方法です。
拡張子のフィルターをgpxにし、ファイルリストを表示するのはマップファイルと同じフォルダに限定します。
(下位のフォルダに潜っていくことは可能ですが)
FileListDialog.javaという別クラスを用意し、MainActivity.javaを書き換えます。また、レイアウトファイルも変更し、”GPX File Select"というボタンを追加します。
MainActivity.javaのボタンクリックの処理に今回の処理を追記します。

 private class BtListener implements View.OnClickListener {
        @Override
        public void onClick(View view){
            int id = view.getId();

            switch (id) {

                case R.id.btRotationalView:
                    ~省略~
                case R.id.btMapFileSelect:
                   ~省略~
                case R.id.btGPXSelect:
                    preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
                    Path = preferences.getString("path", sdPath);
                    fillter = "gpx";
                    FileListDialog dlg = new FileListDialog(MainActivity.this);
                    dlg.setOnFileListDialogListener(MainActivity.this);
                    dlg.show(Path,Path,fillter);
            }
        }
    }

読み込むGPXファイルは、mapファイルと同じディレクトリにある前提なので、SharedPreferenceからファイルパスを読み込みます。
preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
Path = preferences.getString("path", sdPath);
の部分です。
次に、fillter="gpx"として、拡張子がgpxのファイルを選んで表示するようにします。
FileListDialogのインテントを発行して、リスナーを設定し、表示を行います。

ファイルが選択された時の処理も以前の方法と同じです。

    @Override
    public void onClickFileList(File file) {
        if (file == null) {
            Toast.makeText(this, "ファイルが取得できませんでした", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, file.getName(), Toast.LENGTH_SHORT).show();
            SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = preferences.edit();
            editor.putString(fillter, file.getName());
            //editor.putString("path", file.getPath());
            editor.apply();

        }
    }

ファイル名をトースト表示し、SharedPreferenceにファイル名を書き込みます。
ファイルパスは書き換えるとややこしいので、コメントアウトして書き込まないようにしてあります。

gpxファイルの読み込み

ここが、前回の記事と違う処理をしたところです。
やっていることは基本は同じなのですが。
指定したgpxファイルを読み込んで、経度と緯度を順番に読んでいき、アレイリストに追加していきます。
追加したアレイリストをもとに線をレイヤーに書き込めばルートが表示されます。

SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
GPX_FILE = preferences.getString("gpx", "null.gpx");
Path = preferences.getString("path", sdPath);

として、gpxファイルのパスを指定するところは同じです。

Polyline polyline = new Polyline(Utils.createPaint(
                AndroidGraphicFactory.INSTANCE.createColor(Color.BLUE),
                (int) (4 * mapView.getModel().displayModel.getScaleFactor()),
                Style.STROKE), AndroidGraphicFactory.INSTANCE);

ここで、描画する線を指定します。色は青にしています。

List<LatLong> latLongs = new ArrayList<>();

経度、緯度を入れるためのアレイリストを作成します。

ファイル名を指定して、読み込むところですが、
以前は、

try {
            String listXmlPath = Path + "/"+ GPX_FILE;
            String content = new Scanner(
                    new File(listXmlPath)).useDelimiter("\\z").next();
            //XMLファイルをまとめて読み込み
            XmlPullParser xpp = Xml.newPullParser();

            xpp.setInput(new StringReader(content));
            //解析するXMLファイルの中身を渡す
            int eventType = xpp.getEventType();
      
   ~省略~

        } catch (XmlPullParserException e) {
            Log.e("MainActivity", "XMLの解析失敗.");
        } catch (IOException e) {
            Log.e("MainActivity", "XMLファイルの読み込みに失敗.");
        }

としていましたが、この部分を

        FileInputStream is = null;

        try {
            String listXmlPath = Path + "/"+ GPX_FILE;
            is = new FileInputStream(new File(listXmlPath));
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            //String content = new Scanner(
            //        new File(listXmlPath)).useDelimiter("\\z").next();
            //XMLファイルをまとめて読み込み
            XmlPullParser xpp = Xml.newPullParser();

            xpp.setInput(reader);
            //解析するXMLファイルの中身を渡す
            int eventType = xpp.getEventType();

         ~省略~

        } catch (XmlPullParserException e) {
            Log.e("MainActivity", "XMLの解析失敗.");
        } catch (IOException e) {
            Log.e("MainActivity", "XMLファイルの読み込みに失敗.");
        }

のように、普通にファイルを読み込むときのFileInputStreamを使った処理にすることで、ちゃんと読めるようになりました。
エミュレータでは以前の方法でも問題なく読めたのに、実機で読めなかったのは謎です。
この部分の処理の全文は下記のようになります。

protected void addOverlayLayers(Layers layers) {

        SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
        GPX_FILE = preferences.getString("gpx", "null.gpx");
        Path = preferences.getString("path", sdPath);
        //File file = new File(Path, GPX_FILE);

        Polyline polyline = new Polyline(Utils.createPaint(
                AndroidGraphicFactory.INSTANCE.createColor(Color.BLUE),
                (int) (4 * mapView.getModel().displayModel.getScaleFactor()),
                Style.STROKE), AndroidGraphicFactory.INSTANCE);
        List<LatLong> latLongs = new ArrayList<>();

        try {
            String listXmlPath = Path + "/"+ GPX_FILE;
            String content = new Scanner(
                    new File(listXmlPath)).useDelimiter("\\z").next();
            //XMLファイルをまとめて読み込み
            XmlPullParser xpp = Xml.newPullParser();

            xpp.setInput(new StringReader(content));
            //解析するXMLファイルの中身を渡す
            int eventType = xpp.getEventType();
            while (eventType != XmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_DOCUMENT:
                        Log.i("MainActivity", "ドキュメント開始");
                        break;
                    case XmlPullParser.START_TAG:
                        Log.i("MainActivity", xpp.getName() + "要素開始");
                        int attrCount = xpp.getAttributeCount();
                        for (int i = 0; i < attrCount; ++i) {
                            AtName = xpp.getAttributeName(i);
                            AtValue = xpp.getAttributeValue(i);

                            Log.i("MainActivity", "    " +
                                    i + "番目の属性 = " + xpp.getAttributeName(i));
                            Log.i("MainActivity","    " +
                                    i + "番目の値 = " + xpp.getAttributeValue(i));
                            if(AtName.equals("lat")){
                                lati = parseDouble(AtValue);
                            }
                            if( AtName.equals("lon")) {
                                longi = parseDouble(AtValue);
                                LatLong latLong = new LatLong(lati, longi);
                                latLongs.add(latLong);
                            }


                        }
                        break;
                    case XmlPullParser.TEXT:
                        Log.i("MainActivity", "テキスト = " + xpp.getText());
                        break;
                    case XmlPullParser.END_TAG:
                        Log.i("MainActivity", xpp.getName() + "要素終了");
                        break;
                }
                eventType = xpp.next();
                //次のトークンに進む

            }
            Log.i("MainActivity", "ドキュメント終了");
        } catch (XmlPullParserException e) {
            Log.e("MainActivity", "XMLの解析失敗.");
        } catch (IOException e) {
            Log.e("MainActivity", "XMLファイルの読み込みに失敗.");
        }

        //latLongs.add(latLong1);
        //latLongs.add(latLong2);
        //latLongs.add(latLong3);
        polyline.setPoints(latLongs);

mapsforgeではLatLongという形式で緯度、経度を保持しますので、変換が必要です。LatLongは緯度、経度の2つの引数を持ち、変数の型はDoubleです。
gpxファイルから読み込まれる値はString形式ですので、lati = parseDouble(AtValue)、longi = parseDouble(AtValue)のように、読み込んだ値をDoubleに変換し、lati、longiという変数に代入し、
LatLong latLong = new LatLong(lati, longi);
latLongs.add(latLong);
として、経度、緯度を追記していっています。
実際に表示させると、こんな感じになります。青い線がGPXファイルから読み込んだルートです。

f:id:alasixOsaka:20190929103450p:plain
近所のハニワ工場公園付近にルートを引いてみた。
これでようやくルートが表示できるようになりました。
あとはPOIが表示できるようになれば、当初の目論見のうち、高低図以外の部分は完成です。まあ、高低図が最大の難関なんですが。ぼちぼち開発は続けていきます。