NASA Maps in your Swing App

Short short version:

map_not_awesome.png Not Awesome

map_awesome.png Awesome!

Short version

You can now embed some NASA map servers into your own Swing apps using the JXMapViewer component in SwingX-WS.

Long version

For the Aerith demo we showed at JavaOne we build a component that can embed Google Maps into a Swing application. Using the Google service in the way we were doing violates their terms of service and though Google would like to work with us it would be difficult to change the licensing on the map data to fit our needs. This is why, in my previous blog, we shipped the JXMapViewer component with a dummy TileFactory and showed how to plug in your own local data. This is interesting but not very useful. There is no conversion between local tiles and lat/long coordinates so you can't draw waypoints over the map in any meaningful way. Shipping a component which doesn't do anything useful out of the box does not make for a compelling library. It's time for that to change.

I have been investigating how to hook into one of NASA's map servers for quite some time. They use a completely different mechanism for retrieving images and, quite frankly, I don't really understand how all of this mapping stuff works. Mapping involves a lot of complicated equations that project real world data, ie: maps and photos of the earth, onto a flat surface of some kind. We created this component to specifically hide the complexity of mapping, but someone has to write that component and those people were Rich, Romain, and I. I created the mapping equations for the original demo by looking at examples on the web. I copied and translated the code but I didn't really understand them. Finally, last weekend, I figured it out. Thanks to these two great weblog entries by Kyle Mulka and Charlie Savage I can now hook NASA's server into the JXMapViewer component.

How it works

Google's map server divides the entire world up into equal sized tiles which you reference with an x/y coordinate. This is Google's own design and it makes the client side of mapping very easy, pushing all of the hard work onto their servers (I've heard they have quite a few of them:). NASA uses a map server specification called Web Map Service or WMS. Instead of using tiles, WMS lets you request a rectangular image of the world expressed as two lat/long coordinates. The JXMapViewer thinks in terms of tiles, so if you can convert between the two systems you can show the NASA data in the map viewer. And that is exactly what the new classes below do:

WMSService wms = new WMSService();
wms.setLayer("BMNG");
wms.setBaseUrl("http://wms.jpl.nasa.gov/wms.cgi?");
TileFactory fact = new WMSTileFactory(wms);
map.setFactory(fact);

The WMS web service is represented, creatively, by the WMSService class. You can set the BaseUrl for the web service and the layer with properties on the WMSService instance. The BaseURL is the URL where the service resides minus any parameters. The layer represents a particular portion of the data available from this map service. In general, these two pieces of information are all you need to connect to a WMS server.

Most WMS servers have several layers, usually a single base layer and several data layers. The base layer usually covers the entire world and the data layers are transparent data sets which can overlaid on top of the base layer. This demo just uses the NASA base layer, often called the Blue Marble. In a future version of the API we will add support for multiple layers at a time.

Once you have the WMSService instance you can wrap it in a new WMSTileFactory and set it on the map. That's it! The WMSTileFactory knows all about the Mercator projection that will convert WMS requests into the tile based coordinate system that JXMapViewer understands.

If you want to try it out take a look at this webstart app.

Just before I posted this app I started having problems connecting to NASA's server. I will continue working on the problem, but in the mean time I have set it to connect to the map server at wms.lizardtech.com, which contains the same data set

In this application I have loaded up the NASA map and then overlaid three waypoints. Each waypoint represents a weather station. Using a library I wrote a while back (available in the weatherlib project on Java.net) you can easily get the weather from any weather station in NOAA's weather.gov network. There XML feeds also provide the lat/long of most weather stations, which makes it easy to draw them on the map. In this example application I have added a waypoint for the airport weather stations in Atlanta, Georgia; San Francisco, California; and Eugene, Oregon. The code looks like this:

    WaypointMapOverlay overlay;

    /** Creates new form MainFrame */
    public MainFrame() {
		 // .... set up the gui here
        overlay = new WaypointMapOverlay() {
            protected void paintBackground(Graphics2D g, JXMapViewer component) {
            }
            protected void paintWaypointSummary(Graphics2D g, 
					JXMapViewer map, Waypoint waypoint) {
            }
        };
        map.setMapOverlay(overlay);
        try {
            addWeatherWaypoint("KEUG");
            addWeatherWaypoint("KSFO");
            addWeatherWaypoint("KATL");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    class WeatherWaypoint implements Waypoint {
        private Weather weather;
        public WeatherWaypoint(Weather weather) {
            this.weather = weather;
        }
        public GeoPosition getPosition() {
            return new GeoPosition(weather.getLatitude(), weather.getLongitude());
        }
        public Weather getWeather() {
            return weather;
        }
    }
    
    private void addWeatherWaypoint(String station) throws Exception {
        final Weather weather;
        weather = WeatherFactory.newInstance().getWeather(station);
        overlay.getWaypoints().add(new WeatherWaypoint(weather));
        map.repaint();
    }

In the code above there are two things to notice. First, I have created an instance of a WaypointMapOverlay which adds nothing to the drawing, but inherits the default drawing code for waypoints. (I admit this is cumbersome. The overlay API needs an overhaul). The other thing to notice is the addWeatherWaypoint(String station) method which creates a new waypoint for the requested station. To get the weather it just calls WeatherFactory.newInstance().getWeather(station), where station is the four character name of the weather station. (see this page for a full list of available stations. Note that some stations don't have lat/long data available).

With this code it will draw a circle for each weather station. Just drawing a circle isn't very interesting, however. It would be much nicer to actually show the current temperature with a transparent overlay. You can do this by adding a WaypointRenderer:

        overlay.setRenderer(new WaypointRenderer() {
            public boolean paintWaypoint(Graphics2D g, JXMapViewer map, Waypoint waypoint) {
                // make a copy of the graphics object
                g = (Graphics2D) g.create();
                // convert to map space
                Point2D center = map.getTileFactory().getBitmapCoordinate(
                        waypoint.getPosition(), map.getZoom());
                Rectangle bounds = map.getViewportBounds();
     ��          // translate 0,0 to be the center of the waypoint
                g.translate(center.getX()-bounds.getX(), center.getY()-bounds.getY());
                
                // get the weather
                Weather weather = ((WeatherWaypoint)waypoint).getWeather();
                // draw the waypoint
                g.setColor(new Color(255,255,255,175));
                g.fillOval(-20,-20,40,40);
                g.setColor(Color.BLACK);
                g.drawString(""+weather.getTempF(),-15,5);
                return false;
            }
        });

Caveats and known issues.

First some caveats about NASA's map server. This particular server only has data within a certain range. If you zoom in too far you will start to see pixelation. Also the component itself and the WMS conversion still need some work. The tiles are slow to load and sometimes fail completely. There is some distortion at the top-most zoom level and the tiles along the edges of the map fail due to some rounding errors. Clearly there is more work to do, but this is a good start. (hint hint, consider joining in and contributing patches). Most importantly, we can ship a component that does something useful right out of the box.

So what is it good for?

In addition to the photo demo we created in Aerith, there are lots of cool things you can do with easy to use geo-mapping. The overlay API lets you draw anything you want on top of the map, including waypoints and geo-polygons, so we can imagine applications like:

  • show the timezones of the world graphically
  • show the trails that you have hiked with your gps receiver
  • manually draw a trail on the map and produce a list of geopositions out of it
  • draw zipcode and area code boundaries of your local area
  • show where photos were taken and make it searchable to show by date or keyword (I think that Fabrizio is working on something like this)
  • combine with census data to show population by zip across the US
  • show active volcanoes and black bear popuplations. (yes, you really can download datasets for this stuff.)

Basically any set of lat/long coordinates can be drawn on top of the map. With the power of Java2D at your fingertips you can make rollovers, translucent region overlays, or any other crazy visualization imaginable. And because the WMSService class works with any WMS map server you can hook it up to many different map sources. Some creative Googling turns up lots and lots and lots of WMS servers out their, each with their own data. I can imagine some very interesting applications made by combining several WMS sources with other kinds of webservices.

We help and ideas

Don't just download the demo. Try out the component in your own apps and let us know what you think. If you make something interesting then please send us a link and let us know if we can mention it on our website or feature it on Java.net. Please also join the forum and mailing list. We need patches, feedback, and new ideas.

You can find the JXMapViewer and other great Swing tools for working with web services at the SwingX-WS project. The new WMS support is in CVS on the new_tile_provider branch.

Talk to me about it on Twitter

Posted October 12th, 2006

Tagged: java.net