JOSMのプラグイン作ろうぜ


OpenStreetMapアドベントカレンダーということで、今日は「マッピングのための道具づくり」の話をするよ!中級以上のマッパーの標準装備となっている編集ソフトウェアJOSMのプラグインを作ってみようという話。

基本的な内容はJOSM公式のドキュメントを読めばわかる。

それに補足するような形で、書かれていない部分やソースをじっくり読まないとわからない部分を書いていく。

ちなみに私は今までに3個作って公開している。

  • MovementAlert : 画面をスクロールするつもりでうっかりオブジェクトを動かしちゃうという超ありがちな事故を防止する
  • EasyPresets : プリセットをさくっと作ってさくっと使えてエクスポートもできちゃう
  • QuickLabel : オブジェクトに表示されるラベルを簡単にカスタムできる

プラグイン開発ってどんな感じなの?

細かい話を始める前に、JOSMプラグイン開発のざっくりした話。これなら出来そうじゃん! と思ったらさっそく何か作ってみよう。

  • 基本的にJavaで開発する。JOSMのJはJavaのJ! JOSMはJavaで書かれており、JOSMのプラグインも同じくJavaで書かれている。プラグインはjarとして提供することになる。
  • 最終的にjarができればビルドツールは何を使ってもいいが、antでビルドすると最初から色々と雛形が用意されていて楽。
  • 拡張ポイントはあるがドキュメントに書かれているのは少ない。core (JOSM本体) のソースコードとJavadocを頼りに色々調べよう。
  • 地図レイヤや画像レイヤなどの中身にも簡単にアクセスし、読み書きすることができる。
  • データレイヤーや画像レイヤなどと同じように自作のレイヤも作って重ねられる。たとえば有名どころならMapillaryプラグインがカスタムレイヤを提供している。
  • GUIはJava Swingで頑張って作る。ただし基本的なダイアログなどはJOSM coreが用意してくれているものをけっこう使えるので中身を作ればOK。
  • 他のプラグインのソースコードが参考資料になる
    • 色々なプラグインを実際に自分で使い、このプラグインでやっているこんなことが参考になりそう! と目星をつけると良い
    • JOSM本家のリポジトリでホスティングしているプラグインは100個以上。IDEに取り込んでパラパラ読むと参考になる

作ってみる

とりあえず最初は本家のドキュメントを参考にsvnでソースコードをチェックアウト。基本的に、core (JOSM本体) とプラグイン、その他諸々が一緒に入っている。
作ったプラグインは同じリポジトリでホスティングすることもできるし、githubなどの外部サイトにソースコード・ドキュメント・jarファイルを置いておくこともできる。私は使い慣れたgithubに置いている。

開発するプラグインのディレクトリは、チェックアウトしたルートディレクトリの中にあるの “plugins” ディレクトリの直下に設置すると面倒が少ない。
最低限必要なのは、

  • build.xmlとクラスファイル (my-plugin-dir/build.xml)
  • プラグインのエントリポイントとなるクラスのjavaソースファイル (org.openstreetmap.josm.plugins.Plugin を継承したもの。my-plugin-dir/com/example/hello/HelloPlugin.javaという具合に、パッケージに合わせて置いておく。

この2ファイル。

<?xml version="1.0" encoding="utf-8"?>
<!--
** This is a template build file for a JOSM  plugin.
**
** Maintaining versions
** ====================
** See README.template
**
** Usage
** =====
** Call "ant help" to get possible build targets.
**
-->
<project name="HelloPlugin" default="dist" basedir=".">

    <!-- enter the SVN commit message -->
    <property name="commit.message" value="Commit message"/>
    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
    <property name="plugin.main.version" value="13170"/>

    <!-- Configure these properties (replace "..." accordingly).
         See https://josm.openstreetmap.de/wiki/DevelopersGuide/DevelopingPlugins
    -->
    <property name="plugin.author" value="Maripo GODA"/>
    <property name="plugin.class" value="com.example.hello.HelloPlugin"/>
    <property name="plugin.description" value="Simple sample"/>
    
    <!-- <property name="plugin.icon" value="..."/> -->
    <property name="plugin.link" value="https://www.example.com"/>
    <!--<property name="plugin.early" value="..."/>-->
    <!--<property name="plugin.requires" value="..."/>-->
    <!--<property name="plugin.stage" value="..."/>-->

    <!-- ** include targets that all plugins have in common ** -->
    <import file="../build-common.xml"/>
  
</project>
package com.example.hello;

import org.openstreetmap.josm.plugins.Plugin;
import org.openstreetmap.josm.plugins.PluginInformation;

public class HelloPlugin extends Plugin {

	public HelloPlugin(PluginInformation info) {
		super(info);
		System.out.println("HelloPlugin is successfully loaded.");
	}
}

これを

ant install

でビルドすると、出来上がったjarがプラグインディレクトリに設置される。

実際に呼び出してもらうためには、JOSMを起動して設定画面で有効化しないといけない。チェックを入れて再起動。次の起動時は実行される。

デバッグの方法

本家ドキュメントにある通り、EclipseでJava Applicationとして実行すればできる。EclipseのConsoleにログを表示できるし、ブレークポイントも使える。普段マッピングして自分で使いながら何かあったときに出力を見たい……という場合は、ターミナルから

java -jar josm-custom.jar

という感じで起動すれば、その後ターミナルに出力が表示される。

その他、

  • レイヤの切り替え・作成・削除
  • オブジェクトの選択
  • データの変更
  • 設定値の変更

などのイベントに対してリスナを登録することができる。そこはドキュメントに書かれているので読めばだいたいわかるので、ここから先は多くの開発者が知りたくなるであろう小ネタを書いていく。

プラグイン開発小ネタ集

レイヤいろいろ

レイヤには色々な種類があり、どれもorg.openstreetmap.josm.gui.layer.Layerクラスを継承している。たとえば
  • 画像レイヤならImageryLayer
  • メモはNoteLayer
  • そして主役の地図データが入っているのはOsmDataLayer
自分でこのLayerクラスを拡張すれば独自レイヤも作れる。LayerChangeListenerに飛んでくるイベントや現在のレイヤ (MainApplication.getLayerManager().getActiveLayer()で取れる) などでは何がとれるかわからないので、特定のタイプのレイヤをいじりたい場合は適宜分岐する。他のプラグインのカスタムレイヤが取れる場合もあるので考慮してコーディングしよう。

地図データに含まれるオブジェクト達を取り扱いたい

OSMのデータを構成するノード・ウェイ・リレーションは、OsmPrimitiveクラスを継承するNode・Way・Relationとなっている。そこについているタグを取得したいならOsmPrimitive#getKeys()を使う。sourceやdescriptionなどのおまけっぽい情報・メタ情報っぽいタグを除いたタグ一覧はOsmPrimitive#getInterestingTags()で取れる。たとえばこんな感じ。

TagMap map = primitive.getKeys();
for (Tag tag: map.getTags()) {
    System.out.println(tag.getKey() + "=" + tag.getValue());
}

メニューに追加したい! ショートカットを割り当てたい!

プラグインの提供する機能をメニューに追加することができる。また、ショートカットを割り当てたり、Preferencesでバーに登録したりすることができるようにすることもできる。JOSM使いならキーボードショートカットで爆速でマッピングしたいもの。頻繁に使うことが想定されている機能ならショートカットをサポートしてあげよう。なるべくJOSM本体のデフォルトショートカットとはかぶらないものを。

たとえばこんな感じ。

        super(MENU_TITLE, MENU_ICON_PATH, MENU_DESCRIPTION,
                Shortcut.registerShortcut(
                        "myPlugin:myAction", ACTION_DESCRIPTION, 
                        KeyEvent.....,
                        Shortcut.ALT_CTRL_SHIFT), true);

設定値の取扱い

key-valueで設定を保存することができる。文字列として保存したい場合は

Main.pref.get(PREF_KEY)

取得する場合は

Main.pref.put(PREF_KEY, PREF_VALUE)

他にもbooleanやintegerなどの型にあわせたメソッドがあるのでそれらを使う。

JOSM画面の右に追加するパネル

JOSMの右側に配置されてて、表示非表示が切り替えられて、折りたたんだり開いたりできるアレ。

アレは、org.openstreetmap.josm.gui.dialogs.ToggleDialogを継承して実装する。プラグインは必ずエントリポイントとしてorg.openstreetmap.josm.plugins.Pluginを継承したクラスを持つが、そのクラスがPlugin#mapFrameInitializedをOverrideすることでパネルを追加することができる。

ダイアログに「次回から表示しない / (このセッションで)再度表示しない」って出るアレ

ExtendedDialog#toggleEnable()を使う。引数として設定キーを渡しておくと、「次回から表示しない / (このセッションで)再度表示しない / 次回もこのダイアログを表示」の機能がダイアログに追加される。

左下にポコッと出る通知

org.openstreetmap.josm.gui.Notificationを使う。

設定値の取扱い

Stringの場合は

Main.pref.get(PREF_KEY)
Main.pref.put(PREF_KEY, PREF_VALUE)

で、保存・取得ができる。他にもbooleanやintegerなどの型にあわせたメソッドがある。

公開してから

公開の方法は公式ドキュメントに詳しいことが書いてあるのでそちらを読めばわかる。その後は、OpenStreetMap Wikiの “JOSM/Plugins” のページにも追加しておくといい。
また、JOSMおよびそのプラグインの開発者向けメーリングリストにも入っておくと、APIの変更スケジュールなど大事な情報が流れてくる。

おわりに

Javaが書けてJOSMを普段から使っているマッパーならそれほど難しくない。JOSMでマッピングをしていると「こんな機能があったらいいな」とか「こういう物を地図に描くときにこういう機能があればいいのに」と思うことは色々出てくるはず。特定の地域に特有の事情、たとえば特定の地域の住所の表記や交通ルールの入力を補助するようなツールも喜ばれている。道路標識や店舗の開店時間など、記法の難しいタグを入れやすくするツールもある。

クリスマスまであと10日、素敵ツールを作って、世界のマッパー達へ、そしてOSMを利用するすべての旅人たちへのクリスマスプレゼントにしよう!!