Map Editor - Plugins

JH Labs Home

Introduction

Installation
Quick Start
Importing
Navigation
Layers

Feature Layer
Image Layer
Graticule Layer
Daytime Layer
Scalar Grid
Vector Grid
Styles
Effects
Drop Shadow
Blur
Lighting

Rendering Maps
Coordinate Systems
Projections

Tools
Plugins

Many of the features of the editor can be extended using the plugin mechanism. This allows new tools, menu items, projections, units, coordinate systems, file formats and many other features to be added. If, for example, you want to import map data in a file format which the editor doesn't handle, you can write a plugin to handle the new format.

A map viewer plugin

Plugins are easy to use: all plugins are stored in a directory called "plugins". When the editor starts up, all plugins found will be loaded and added to the appropriate place. Menu items are added to the menu bar, coordinate systems will be available in all appropriate dialogs, file exporters will appear in the File menu and so on.

To write a plugin you need to write a Java bean of the appropriate class (see below) and then package it. The plugin classes must be placed into a JAR file which is then placed into the plugins directory. A plugin can have as many classes as it needs and multiple plugins may be placed into the JAR file. In addition, it is possible to write a plugin which generates new plugins when loaded.

Plugins can be organised into plugin suites, which are groups of plugins which belong together. A plugin suite has a version number and it is possible to express dependencies between suites to state that a particular plugin requires a particular version of some other plugin to be loaded.

In order for the editor to know which classes are plugins you must add a file to the JAR describing the plugins. This is an XML file which must be in a directory named "config" in the JAR file. There should only be one such file in the JAR. A typical config file might look like this:

<plugins>
        <plugin-suite id="com.jhlabs.map.myplugins" name="Sample Plugins" version="1.0">
               <plugin bean="com.jhlabs.map.ImportMyFileFormat"/>
                <plugin bean="com.jhlabs.map.MyCoordinateSystem"/> 
        </plugin-suite>
</plugins>

This declares two plugins in the JAR file, a coordinate system and a file importer. These are contained in a suite called "Sample Plugins". The id is the internal name of of the suite and should be unique. For this reason, it should follow the Java convention of placing a domain name at the start.

The editor can also load serialized Java Beans as plugins. To do this, just place the serialized bean file into the plugins folder.

  • For details on how to write specific plugin types, see the API reference.
  • For details on existing plugins, see the plugins page.

Writing a File Importer

File importers are one of the most important plugins for the viewer, allowing the import of map data from variaous file formats. Typically a file importer will read in a file or stream and create one layer to hold the map data, although importers can perform other tasks such as creating styles or symbols. A user can ask for a layer to be reloaded from its original source in which case the importer will be called again, but be passed the layer into which to import. For an importer which supports this type of working, it is important that it checks the given layer. If it's null, it should create a new layer, if not it should reuse the layer, provided it is of an appropriate type - you can't import images into a Shapefile layer for example.

A file importer must implement the MapImport interface. This has a method for allowing the viewer to recognise which files can be imported by the importer and a method for actually importing the data. Like all plugins, the importer should have a toString() method which returns a human-readable name for the plugin.

public interface MapImport {
        public String[] getMimeTypes();
        public void importData(ImportExportParams data) throws IOException;
}


The (slightly misnamed) getMimeTypes method should return a list of MIME types and/or file extensions which the importer recognises. [Note: this will be extended in the future to allow the importers to recognise files from the first few bytes of the file and from Macintosh file types]. Thus importers generally recognise files by their extensions.

The importData() method is the most important part of an importer. It takes one parameter of type ImportExportParams which containes details of the file to be imported. The ImportExportParams class has a number of methods which return objects likely to be useful to an importer, such as the file or URL, an input stream onto the data, the map and layer to import into, and the viewer application itself.

Here's a sample file importer for importing AWT images: It's fairly simple, the main complications being the logic to load the image and to decide whether to create a new layer or not. This importer creates a RasterLayer for its images. It could have decided to create a FeatureLayer containing an ImageFeature, or it could ask the user at import time what to do with the data. [Note: at present, if two importers exists for the same file type the last one loaded will take precedence. In the future, the viewer will ask the user which to use].

/*
 * Copyright (C) Jerry Huxtable 1998-2001. All rights reserved.
 */
package com.jhlabs.map.io;

import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import com.jhlabs.app.*;
import com.jhlabs.map.*;
import com.jhlabs.map.layer.*;

public class ImportImage implements MapImport {

       public String[] getMimeTypes() {
               return new String[] { "image/gif", "image/jpeg", ".gif", ".jpg" };
        }

       public void importData(ImportExportParams iep) throws IOException {
               Map map = iep.getMap();
               StatusDisplay status = iep.getStatusDisplay();

               Image image;
               URL url = null;
               if (iep.getFile() != null) {
                       url = iep.getFile().toURL();
                       image = iep.getFrame().getToolkit().getImage(iep.getFile().getPath());
               } else {
                       url = iep.getURL();
                       image = iep.getFrame().getToolkit().getImage(iep.getURL());
               }
               MediaTracker tracker = new MediaTracker(iep.getFrame());
               tracker.addImage(image, 0);
               try {
                       status.showMessage("Loading image...");
                       tracker.waitForID(0);
               }
               catch (InterruptedException e) {
               }

               Layer layer = iep.getLayer();
               RasterLayer rasterLayer;
               if (layer != null) {
                       if (!(layer instanceof RasterLayer))
                               throw new IllegalArgumentException("Images cannot be imported into this layer");
                       rasterLayer = (RasterLayer)layer;
               } else
                       rasterLayer = new RasterLayer(iep.getName());
               rasterLayer.setDataURL(url);
               ImageObserver io = iep.getApplication().getFrame();
               BufferedImage bufferedImage = new BufferedImage(image.getWidth(io), image.getHeight(io), BufferedImage.TYPE_INT_ARGB);
               Graphics2D g = bufferedImage.createGraphics();
               g.drawImage(image, 0, 0, io);
               g.dispose();
               rasterLayer.setLayerImage(bufferedImage);
               if (layer == null)
                       map.addLayer(rasterLayer);
        }

       public String toString() {
               return "GIF/JPEG Image";
        }
        
}


Writing a Menu Action

Another important plugin type is an Action. This is a plugin which appears as a menu item or toolbar button, allowing you to add new commands to the viewer. Actions should extend the MapAction class which provides a lot of useful methods for accessing objects such as the current view, map and selection. At present, all added actions go into an "Extras" submenu under the "Edit" menu. In the future, there will be a mechanism to allow users and plugins to reorganise the menus. For the moment, the menus can be reorganised by changing the <menubar> element in the viewer configuration file, or in the user's preferences file.

To write an action, you need to subclass MapAction, provide a name and icon (optional) to the constructor and override the actionPerformed method. You may also provide an isEnabled method if your action is not always enabled.

Here's a sample action plugin:

public class ZoomToSelectionAction extends MapAction {
        public ZoomToSelectionAction() {
               super("zoomToSelection", null, "ZoomToSelection24.gif);
               putValue(SHORT_DESCRIPTION, "Zoom to Selection");
               putValue(LONG_DESCRIPTION, "Zooms the view to fit the selected features");
        }
        
        public boolean isEnabled() {
               Map map = getMap();
               return map != null && map.numSelected() != 0;
        }

       public void actionPerformed(ActionEvent e) {
               getMapView().zoomToSelection();
               enableCommands();
        }
}