Version 23 (modified by 17 years ago) ( diff ) | ,
---|
This page gives a short introduction for developers how to create, deploy and develop plugins for JOSM. Any Questions left? Ask Imi.
Setting up the environment
- Get the complete osm - trunk from http://svn.openstreetmap.org. This checks out everything into a directory called osm
svn co http://svn.openstreetmap.org osm
- create a new plugin-directory osm/applications/editors/josm/plugins/yourpluginname
- copy an ant script (build.xml) from an other plugin (TODO: Someone familar with ant could be so nice and setup a template plugin with an almost complete configured ant script and then change this here)
- The important thing of your build script is, that it places some attributes into the MANIFEST.MF of the jar file. See below.
- Also not, that JOSM is compiled into Java5 compatible class files, so if you are using Java6 or Java7, specify "-target 1.5" as parameter to javac or your plugin will not be usable with the official josm builds.
The Plugin class
After setting everything up, you need to create the plugin main class specified in the jar's manifest. The only prerequisite of this class is, that it must have a standard constructor taking no arguments. The constructor of the plugin class will be called during the JOSM startup if the plugin is registered. Here, you have full access to the object tree within JOSM.
It is recommended (but not required), that you subclass org.openstreetmap.josm.plugins.Plugin. This way you get some helper functions and always have the latest callback function signatures (see below) as hints in your baseclass to override them ;)
Callbacks
Most of your plugin work will probably be tweaking the JOSM object tree at startup time, but there are a few callbacks that get automatically called on every plugin (whether you subclass Plugin or not, just the signature matters).
mapFrameInitialized
This is called after the first data has been loaded and the mapFrame is initialized. Use this to change something on the new MapFrame (or more common: the MapView (THE map ;) behind the frame)- As example, you could add yourself as LayerChangeListener to newMapFrame.mapView (please don't forget to remove yourself from oldMapFrame, if newMapFrame is null). This way you can capture changes of the active layer or the addition/removal of layers.
getPreferenceSetting
This will be called to retrieve an creator for GUI elements for the preferences dialog. The return is an interface with two methods: one to add the GUI elements to the dialog and on that is called when the preferences dialog finishes with ok. Be sure not to write preferences in your GUI code, but remember settings and only write them in the ok() method.
addDownloadSelection
This will be called whenever the user pops up the download dialog. The download dialog is prepared by first assembling a list of objects implementing the DownloadSelection interface, and after that giving each of them, via a call to their addGui method, the chance of adding a tab to the download dialog's tabpane. The DownloadSelection interface also has a method that is called by the download dialog if one of the other tabs changes the bounding box, and in turn the GUI elements created by addGui are expected to call boundingBoxChanged on the download dialog if they want to communicate a change to other tabs. The name addDownloadSelection is not 100% accurate as a plugin is also allowed to modify the existing list of DownloadSelection objects. For example, a plugin might want to replace the existing Bookmark handler; it can do so by finding the BookmarkSelection object in the list passed to addDownloadSelection and remove it.
Some tips about the JOSM object tree or What is where?
First some words about my style of accessing public variables. Most people find this annoying and bad coding style in Java. If this would be an enterprise project, where most of the code is glue code and had to work with objects in a generic way, I would agree with them. But as JOSM is not, I like to keep the classes as simple as possible, which includes, that I don't add standard getter/setter but make the variable public. Also, there are no or very few so-called singleton-factories in JOSM that became popular in the past years. I use to reference singleton objects as global statics. This is unusual but equivalent to having stuff like Dependency Injection or Factory Methods (except you want to make complex things like auto-distributing stuff as seen in some enterprise programs).
Most objects are accessible through the class org.openstreetmap.josm.Main. There are numerous entry points for various parts of JOSM here.
Main.parent
This is the parent of all GUI elements. Use this as first parameter to JOptionPane.show* if you want to popup a message.
Main.pref
This is the (ONLY!) access to the global preferences file, located in .josm/preferences. Use Main.pref.get(...) and Main.pref.put(...) to access the preferences. They will be saved immediately after a put, so don't put anything you dont want to have there ;).
Please, prefix custom plugin preferences with your plugin name.
Main.ds
The data layer of the current edit layer, which is usually the current selected layer in the layer list.
Main.proj
If you want to translate between Lat/Lon and East/North, use Main.proj.latlon2eastnorth and Main.proj.eastnorth2latlon.
Main.map.mapView
This is the GUI class of the map. You usually access this to call stuff like getCenter, getScale or zoomTo.
BEWARE: Main.map can be null, in which case no data is loaded yet.
Main.main.editLayer()
Call this to get the current OsmDataLayer. If there is no such layer, an empty will be created, so you never get null returned from this.
Plugin.getPluginDir() and Plugin.copy()
If you subclass from the plugin class (see above), then you can call this functions to get your plugin directory or copy data into your plugin directory. This is a directory where you can freely store/read data from. It is located at ~/.josm/plugins/youpluginname.
The MANIFEST.MF
You have to put some information into the manifest file of your jar. If you use ant, you can set these values within the build.xml file.
- Plugin-Class (required)
- Points to the main plugin file
- Plugin-Description (required)
- Gives the description of the plugin visible in the preferences page. For line breaks, you have to use <br>, not <br/> (blame Swing ;).
- Plugin-Author
- An Email-Adress of the author of this plugin. This is used in the error report window, if an error is detected within the plugin code (there is a strange heuristic to guess whether an exception comes from a plugin).
- Plugin-Early
-
Can be set to
true
, in which case the plugin is loaded as early as possible, more specific before the GUI classes are loaded. This is usefull if your plugin alters the GUI or the JOSM-startup process in any way. - Plugin-Stage
- An number of the order relative to other plugins, when the plugin is loaded. Smaller numbers gets loaded first, so if you have conflicts with other plugins, you can increase or decrease this number to get some control on the loading order. Defaults to 50.
- Plugin-Version
- Just mapped to the string PluginInformation.version. Use this as example to pass versioning informations to other plugins.
- Class-Path
- An space-seperated list of additional classpaths your plugin wants to use when looking for ressources and classes. The plugin itself is added automatically. Don't forget to provide the additional jar's as well, if you add dependencies here. Note that all loaded plugins are in the class-path automatically, so don't specify plugin-dependencies here.
Versioning and Dependencies
JOSM does not support declarative dependency checking - you have to do it by hand. But don't be scared, it's not that hard. You can request information about other installed plugins via PluginInformation.getLoaded("pluginname")
. As example if your plugin is named "secondwaltz" and you depend on the plugin "schostakowitsch" to be loaded, do the following things:
- make sure you get loaded after "schostakowitsch" (by incrementing your Plugin-Stage. See above)
- check for "schostakowitsch" to be loaded in your plugin's class constructor:
class SecondWaltz { // constructor called by JOSM to initialize the plugin public SecondWaltz() { PluginInformation schostakowitsch = PluginInformation.getLoaded("schostakowitsch"); if (schostakowitsch == null || !"2".equals(schostakowitsch.version)) { JOptionPane.showMessageDialog(Main.parent, tr("Please install plugin 'schostakowitsch' version 2")); return; } // initialize the plugin ... } }
- Of course, you may initialize the parts of your plugin that don't need "schostakowitsch" to be loaded or react in some other way to the missing plugin (e.g. by using a build-in version of "schostakowitsch").
Legal stuff (Imis opinion)
Just because I have been asked: JOSM is licensed under GPL and if any code is a "derived work" of JOSM, then it has to be under GPL too. It is my believe, that any JOSM-Plugin is a derived work of JOSM, so GPL is the only possible license for a JOSM-Plugin. If you want to include non-GPL code into a plugin, it has to be seperated from the classes that use JOSM. "Use" as in "import org.openstreetmap.josm...". See the 'Class-Path' - MANIFEST.MF attribute for a way to include other jar files.