等高線付きの地図を表示する(アンドロイド地図アプリの開発 その19)

前回までで、このアプリ開発を一旦総括しました。
alasixosaka.hatenablog.com

さて、今回は地図に等高線を表示させてみようという話です。総括のところで、今後の機能についても書いてみましたが、実はもう一つやってみたいことがあって、それが等高線付きの地図を表示するというものです。
mapsforgeの地図データは、Mapsforgeのサーバーに行けばダウンロードできるのですが、この地図には等高線がついていません。
Mapsforge Download Server
ただ、トレランとかに使うことを考えると等高線付きの地図も見たいということもあって調べてみました。mapsforgeの地図形式はベクターという形式らしいのですが、実はLocusMapというアプリがあって、同じ形式の地図を使えるようになっているらしい、また、OpenAndroMapsというのもあって、ここで等高線付きの地図がダウンロードできるらしい。
blogs.da-cha.jp
また、自分で等高線付きの地図を作っている人もおられて、作った地図も公開してくださっている。
yueno.net
ここのやり方を見れば自分でも地図を作ることができそうだが、とりあえずサクッと表示するためにありものの地図データをダウンロードしてやってみた。

地図ファイルを入れ替えるだけではうまくいかない

ところが、地図ファイルをただ入れ替えるだけでは等高線は表示されなかった。

f:id:alasixOsaka:20200315135117j:plain
そのまま地図ファイルだけ入れ替えてもダメだった
そうは簡単に問屋はおろしてくれないということか。
実は、OpenAndroMapsのサイトには気になることが書いてあって、”The maps from this site need a Rendertheme for nice and correct rendering on screen. (This have to be done only once for each device)"と書いてある。つまり、専用のRenderthemeを使わないとちゃんと表示されませんよということらしい。そして、Renderthemeもダウンロードできるようになっている。問題はそこからだった。

mapsforgeのRenderthemeを変更する。

検索すると、mapsforgeのサイトの中にRenderthemeに関する記述があった。
mapsforge/Rendertheme.md at master · mapsforge/mapsforge · GitHub
ここには、Renderthemeとして外部ファイルを使う場合のやり方として、"External render-theme files are also supported and can be activated via the tileRendererLayer.setXmlRenderTheme(new ExternalRenderTheme(File)) method at runtime."と書いてある。しかし、これだけでは全然やり方がわからない。
さらに調べると、こんなサイトがあった。
www.programcreek.com

ここには、外部ファイルのRenderthemeを使うときのサンプルコードがいくつか書いてあるのでこれを参考にした。
次の問題は、自分のアプリのどの部分をいじれば良いのかということだった。
結論から書くと、DefautltTheme.javaというクラスがあって、ここを変更すると良いということが分かった。
そこで、先ほどのサイトの記事を参考に、こんな感じに変更してみた。Elevate.xmlがOpenAndroMapsから落としてきたRendertheme。storage/emulated/0/Elevate.xmlはフルパスの記述で、アプリからgetRenderthemeが呼ばれた時に、オリジナルは、 InternalRenderTheme.DEFAULT(つまり内部に持っているデフォルトのRendertheme)を返すようになっていたのを、ダウンロードしてきた外部ファイルを返すようにしてみた。もし、該当の外部ファイルがなかったら、デフォルトを返すようにしている。

public class DefaultTheme extends SamplesBaseActivity  {

    /**
     * This MapViewer uses the built-in default theme.
     *
     * @return the render theme to use
     */
    @Override
    protected XmlRenderTheme getRenderTheme() {
        //return InternalRenderTheme.DEFAULT;
        try {
            return  new ExternalRenderTheme("storage/emulated/0/Elevate.xml");
        } catch (Exception e) {
            // just return the default theme
        }
        return InternalRenderTheme.DEFAULT;
    }

}

やってみると少し見た目は変わったが今度はうまく等高線が表示できた。

f:id:alasixOsaka:20200315140801j:plain
等高線が表示された地図

テーマを適当に入れ替えれるようにする。

シチュエーションによっては等高線があったほうが良いこともあれば、ない方が見やすいこともあるので、テーマを選んで入れ替えれるようにしてみたい。
そうすると、またメニュー画面が追加になるが、もう一つサブ画面を作って、テーマを選ぶ画面を追加してみた。
まず、メニュー画面にボタンを一つ追加し、"Select Theme"とした。また、"Theme Select.java"というjava class を一つ追加し、ボタンをクリックしたら、呼び出すようにした。Theme Select.javaではリストビューを表示し、リストからテーマを選択するようにした。現在は、"default"と"Locus"の2つが選択できる。将来的に増やしたい時は、リストビューに追加できるようにしている。ちなみにLocusにあまり意味はないです。最初に等高線付きの地図を見つけたのがLocusMapのサイトだったので敬意を表してLocusとしています。

MainActivityの変更点は下記の通りです。

   private void clickWait(){

        ~省略~
        Button btselth = findViewById(R.id.btSelectTheme);
        ~省略~
        btselth.setOnClickListener(listener);
  ~省略~
        }
    }
    private class BtListener implements View.OnClickListener {
        @Override
        public void onClick(View view){
            //Intent SMV = new Intent(getApplicationContext(),SimplestMapViewer.class);
            //startActivity(SMV);
            int id = view.getId();

            switch (id) {

                ~省略~
                case R.id.btSelectTheme:
                    Intent selth = new Intent(MainActivity.this,ThemeSelect.class);
                    startActivity(selth);
                    break;
            }
        }
    }

clickwait()のところに、ボタンのidを追加し、リスナーに渡しています。リスナーの方では、SelectThemeのボタンがタップされたらThemeSelect.classのインテントを発行して処理を引き継ぎます。
また、レイアウトファイルActivity_main.xmlにボタンを追加します。

<Button
        android:id="@+id/btSelectTheme"
        android:layout_width="202dp"
        android:layout_height="38dp"
        android:layout_alignParentStart="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginStart="84dp"
        android:layout_marginLeft="84dp"
        android:layout_marginTop="400dp"
        android:text="@string/select_theme" />

ThemeSelect.javaソースコードは下記の通り。

public class ThemeSelect extends Activity {

    private ListView lv;
    private static String MapTheme ="";

    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.select_theme);
        ArrayAdapter<String> adapter =
                new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
        adapter.add("default");
        adapter.add("Locus");
        lv = (ListView) findViewById(R.id.themeselect);
        lv.setAdapter(adapter);
        lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
                preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
                MapTheme = preferences.getString("theme","default");
                switch (position){
                    case 0:
                        MapTheme = "default";
                        break;
                    case 1:
                        MapTheme = "Locus";
                }
                SharedPreferences.Editor editor = preferences.edit();
                editor.putString("theme", MapTheme);
                //editor.putString("path", file.getPath());
                editor.apply();
                finish();
            }
        });
    }
}

リストビューでdefaultとLocusを表示して、どちらをタップしたかでSwitch文で分岐して、MapThemeを決定しています。決定したMapThemeはSharedPreferenceに書き込んで、DefaultTheme.javaで選択するようにします。
SelectThemeのリストビューselect_theme.xmlは下記の通りです。リストビューを一つ表示するシンプルなものです。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ListView
        android:id="@+id/themeselect"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

    </ListView>

</LinearLayout>

DefaultTheme.javaの方は次のように書き換えます。

public class DefaultTheme extends SamplesBaseActivity  {

    /**
     * This MapViewer uses the built-in default theme.
     *
     * @return the render theme to use
     */
    private static String MapTheme ="";
    private static String Path ="";
    String sdPath = Environment.getExternalStorageDirectory().getPath();
    @Override
    protected XmlRenderTheme getRenderTheme() {
        //return InternalRenderTheme.DEFAULT;
        SharedPreferences preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
        preferences = getSharedPreferences("DATA", Context.MODE_PRIVATE);
        MapTheme = preferences.getString("theme","default");
        Path = preferences.getString("path", sdPath);
        try {
            if (MapTheme.equals("Locus")){
                return  new ExternalRenderTheme(Path + "Elevate.xml");
            }

        } catch (Exception e) {
            // just return the default theme
        }
        return InternalRenderTheme.DEFAULT;
    }

}

元々、InternalRenderTheme.DEFAULTをシンプルに返すようになっていたのを、SharedPreferenceからthemeを読みだして返すようにしています。ただ、Locusを選んだ時に該当のファイルElevate.xmlがない場合はデフォルトを返すようにしてエラーの発生を防いでいます。また、RenderThemeのファイルは地図ファイルと同じディレクトリに置いておく必要があります。そうしないとエラーになってデフォルトのテーマが選ばれます。


そして、Intent で呼び出すjava class を追加したのでmanifest に追記が必要になります。始めはこれを忘れていてエラーになって、何故なのか結構悩んでしまった。ここで、mpf_rotation8は今回のプロジェクト名です。

<activity android:name="com.example.mpf_rotation8.ThemeSelect"></activity>

ちなみに、default とLocus の違いは等高線の有り無し以外に見た目も結構変わります。また、テーマにLocus を選んでも地図データに等高線が含まれてないと当然、等高線は表示されず見た目だけが変わります。
次回は残りの修正点、画面をオフにしない、軌跡を残す、GPSをバックグランドで動かすといったことに取り組んでみたいと思います。

参考にしたサイト
HikingでOpenStreetMapを活用
Downloads - openandromaps
mapsforge/Rendertheme.md at master · mapsforge/mapsforge · GitHub
Java Code Examples org.mapsforge.map.rendertheme.ExternalRenderTheme
上野家のホームページ - PC/地図/Locus map用ベクター地図Locus Map - 資料室
map フォーマットファイルを自分で作成する方法について - プログラマーのメモ書き