 |
Getting started with the Aerith Mapping Component
Posted by joshy on July 11, 2006 at 11:14 AM | Comments (21)
A few days ago we released the code to Aerith, our JavaOne demo that combines photos, mapdata, and 3d effects. We worked very hard to get the code out to you and let you see how everything works. However, if you've downloaded the code you may have noticed that the code for the map parts is missing. Only the binaries are provided in the JXMapViewer.jar file. That's because the map component has a brighter future than just a JavaOne demo. It is now the first component in our new SwingLabs project: The Swing Web Services components, or SwingX-WS.
I'm really excited about this. Doing interesting things with web services has always been our dream for SwingLabs, and Desktop Java in general. Now we have a place to put it all. And the JXMapViewer is only the beginning. We want to have all sorts of components in there for things like Flickr, Del.icio.us, and RSS/Atom feeds. It's a good time to be a client developer!
Okay, so on to more immediate things. How do you use the map viewer? Just add it to your panel and you are ready to go. If you add it to your NetBeans palette you can just drag it into your form, hit F6, and see it come up. It'll look something like this:

What? Where's the map?!
Okay, I lied. So there is a bit more you have to do. The JXMapViewer uses a TileFactory implementation to retrieve the actual tiles. By default it uses an EmptyTileFactory which gives you the result you see here, which isn't very useful but can be helpful when testing code. For something more interesting you can provide your own TileFactory implementation, or use the DefaultTileFactory.
The DefaultTileFactory implements the basic tile loading and geometric transformation behavior required for your typical tile based map server like Google Maps and Yahoo Maps (and presumably others, though I haven't tested it). You just need to provide information about the server to the DefaultTileFactory and it will take care of the rest. You do this using an instance of TileProviderInfo. For example, you could connect to an internal tile map server using something like this:
TileProviderInfo info = new TileProviderInfo(
0, // minimum zoom
15, // maximum zoom
17, // total zoom levels (only 0 - 15 can be viewed)
256, // square tile size - 256x256
true, // x increase to the right
true, // y origin is at north pole (not the equator)
"http://myserver.com/getmap.jsp?" // tile server base URL
"x","y","z" // x,y and zoom URL parameter names
);
TileFactory fact = new DefaultTileFactory(info);
JXMapViewer map = new JXMapViewer();
map.setFactory(fact);
map.setZoom(7);
map.setAddressLocation(new GeoPosition(37.392137,-121.950431));
The first four numbers to the TileProviderInfo constructor represent the minimum zoom, the maximum zoom, the total zoom levels, and the tile size. The two booleans are used to indicate if the x coordinates go left to right or right to left and if the y coordinate goes
from top to bottom or out from the equator. The rest of the parameters are for the base
url and the name of the http parameters in the get request to fetch tiles. Once the TileProviderInfo is create you can build a DefaultTileFactory, install it on a new JXMapViewer, then set the starting zoom level and location. The GeoPosition above is the latitude and longitude coordinates of my office (give or take a few hundred feet. :).
That's pretty much all you need, but the zoom levels may take some explaining. A tile based map is essentially a pyramid of square bitmaps. At the top level you have a single bitmap with a certain size, in this case 256 x 256 pixels. In the level below that there are four tiles, also 256 x 256 each. On the third level there are four tiles for each tile in the second level, for a total of sixteen. When you continue on down the number of tiles grows by a factor of four. As you can imagine this creates a lot of tiles! The total zoom levels is the number of levels in your tile pyramid. The minimum and maximum zoom levels are the limits placed on the user navigating within the levels, since you might not want the user to go all the way to the top or bottom (maybe because you don't have real tiles there). In any case, you can customize these to match your own tile server and then let the DefaultTileFactory handle the details.
At JavaOne we demonstrated the JXMapViewer, as part of the Aerith demo,
with a TileProviderInfo class that pointed at Google's tile server.
This was relatively easy to do, since the REST web service they
provide is straightforward. We have not published the settings for using
that server because we have not yet received formal approval for doing
so from Google. JXMapViewer is a generally useful component for
displaying large tiled images from any source. We didn't want to
suspend its release until we'd gained approval for demonstrating it
with Google's tile server, however we hope to do so in the future.
The World of Warcraft maps
Here's a working example. The guys over at MapWow.com have created a map of the World of Warcraft using data from actual players. It uses an api similar to Google Maps so it's very easy to plug into:
//wowmap version
TileProviderInfo info = new TileProviderInfo(0,8,9, // 9 levels, navigate from 0 to 8
256, true, true, // tile size is 256 and x/y orientation is normal
"http://mapwow.com/gmap/zoom",
"x","y","z") {
public String getTileUrl(int zoom, TilePoint tilePoint) {
return this.baseURL + zoom + "maps/"+
tilePoint.getX()+"_"+tilePoint.getY()+"_"+zoom+".jpg";
}
};
map.setZoom(6);
map.setAddressLocation(new GeoPosition(50,-80));
The code above looks pretty similar to the previous example except that I have to override getTileUrl with a custom version. That's because the MapWow urls don't use a format compatible with default version. Their urls use the zoom variable in two places so I create a two line method to generate the proper url. When you run the map it will look like this:
WoW Map
Or you can run it right now if you have Java 5 or higher installed.

Using a single tile
Now lets supposed instead of a big remote map composed of a bunch of tiles you'd like to just use a single image stored locally, say a satellite photo from Nasa. You can do that too using a different TileProviderInfo. You just need to override getTileUrl() to return the
base url without any parameters. Then set all of the zoom parameters to 1 and the tile size to the size of your image.
//single square file version
TileProviderInfo info = new TileProviderInfo(1, 1, 1, 640, true, true,
"file:/Users/josh/Desktop/world.small.square.jpg",
"x","y","z") {
public String getTileUrl(int zoom, TilePoint tilePoint) {
return this.baseURL;
}
};
 Single 640px NASA image of earth
Making your own tile images from NASA photos
Lets say you have a gigantic NASA image that you'd like to navigate through. (There are tons of great images here.) First you'll need to convert the image into a pyramid of tiles and then construct a TileProviderInfo to load the images from disk. To convert the images you need to chop up the main image into 256x256 tiles, save them to a directory according to a particular naming scheme, scale the image to half the size, split *that* image into tiles, and repeat until you have only a single tile left. It'd sure be nice to have a program that does this for you. As it happens I created just such a program.
The ImageTileCutter
The tile generator will take any image readable by ImageIO, scale and pad it to become a square, and then start generating tiles. Most of the parameters are hard coded right now (tile size, directory names, etc) but that could be changed in the future. Using this program I was able to create a tile set that looks like this:
the top most image of the cloudless earth image stack
I should mention that this program is horribly naive and not efficient at all. If you deal with large images you will need to increase the memory you give it on the command line with something like: -Xms600m -Xmx600m. I was able to chop down a NASA photo at 5400x2700 pixels in about 30 seconds on my dual-core 2ghz MacBook. I haven't tried anything larger yet but I'm sure the task will scale exponentially. In the future I'd like to see a toolset that uses ImageMagick or Java Advanced Imaging for fast non-interactive scaling. (perhaps there's a java.net project idea in there?!)
You can get the source to the tile generator as a netbeans project here
Once you have a directory structure full of tiles you need to load them into your map. My set uses 5 levels of tiles stored in the format: basedir/tiles/zoomlevel/outputimage.zoom-x-y.jpg. The TileProviderInfo to load them looks like:
//local 5 level tile set of the globe produced by ImageTileCutter
TileProviderInfo info = new TileProviderInfo(
0, 5, 5, 256, true, true,
"file:/Users/joshy/projects/java.net/ImageTileCutter/tiles", "x","y","z") {
public String getTileUrl(int zoom, TilePoint tilePoint) {
return this.baseURL + "/"+zoom+"/"+"outputimage."+zoom+"-" + tilePoint.getX() + "-"+tilePoint.getY()+".jpg";
}
};
and the finished map looks like this:
If you'd like to try it online you can connect to some tiles that Hans Muller created from a Hubble image at http://download.java.net/javadesktop/hubble/tile_row_column.jpg:
// hans tiles
TileProviderInfo info = new TileProviderInfo(1, 1, 7, 200, true, true,
"http://download.java.net/javadesktop/hubble/tile_",
"x","y","z") {
public String getTileUrl(int zoom, TilePoint tilePoint) {
return this.baseURL + tilePoint.getY() + "_" +
tilePoint.getX()+".jpg";
}
};
map.setZoom(1);
With tiles that look like this:

Conclusion
The JXMapViewer is a powerful component that can be modified to talk to any map server. In this blog I have only scratched the surface of what this component can do. In a future blog I will dig into the Overlay API that lets you add waypoints, paths, and draw on top of the map, and the Yahoo Geocoder that converts street addresses into lat/long coordinates.
the JXMapViewer is functional but it has a lot of bugs and limitations. In particular the viewer assumes tiles are square, there are repaint glitches, the threading code is overly complicated, and the lat/lon conversion only works with certain kinds of mercator projections. Please join the new SwingX-WS project to play with the components, help fix bugs, add features, and contribute new components.
Links
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Very interesting! Wow! I am going to be going on a 2500 mile road trip next month. I figure I have plenty of time to build Aerith and give it a test drive. But I cant wait to give it a shot on my road trip!
Posted by: suryad on July 11, 2006 at 03:11 PM
-
Josh, I've just tried to launch it with the WebStart, but crashes consistently the VM on my Mac OS X Intel.
The code downloaded and compiled locally works fine. I hope that the guys at Google won't take too long for their decision... :-)
Posted by: fabriziogiudici on July 11, 2006 at 04:35 PM
-
Hm. I compiled and ran it on my MacBook. Could you tell me what the error is?
Posted by: joshy on July 11, 2006 at 04:44 PM
-
I've sent you the crash log. BTW the webstart worked fine on Windows XP.
Posted by: fabriziogiudici on July 11, 2006 at 05:31 PM
-
I think I figured it out. Could you try it again, making sure to clear you webstart cache first?
Thanks.
Posted by: joshy on July 12, 2006 at 12:18 AM
-
Worked perfectly now. Even without clearing the webstart cache.
Posted by: fabriziogiudici on July 12, 2006 at 01:21 AM
-
PS Joshua, the link to the image splitter is not set: [link to the zip file]
Posted by: fabriziogiudici on July 12, 2006 at 04:01 AM
-
Is there an explicit assumption that the component is only working with raster data? While it's a valid assumption for topographic maps, not all GIS data comes in raster. Some of it comes in vector format (hydrography, roads etc). Do you happen to know how it is handled in GoogleMaps API and does your component support vector layers?
Posted by: kirillcool on July 12, 2006 at 08:56 AM
-
fabriziogiudici: Great. It seems that if the icon element has an href value of 'default' (which is the default value created by the JNLP module for NetBeans) then webstart will silently crash on the Mac. This doesn't happen with Mustang, however, which is why I didn't notice it. Sorry guys.
fabriziogiudici: I've fixed the link.
kirillcool: the component assumes it is working with a tile data source. However, you can use the Overlay api or subclassing to draw whatever you want on top, which would include doing your own vector layers. This does not work through Google's Javascript API, so you wouldn't have access to their directions service, but it does hook into a Yahoo geocoder which will turn street addresses into lat/long coordinates. I'm going to do another blog soon that delves further into using the overlays and working with coordinates.
I should mention that the lat/long to pixel equations currently assume the modified mercator projection that Google uses. Thus you wouldn't get the right coordinate conversion on the Nasa photo. In the future I want to make the conversion equations handle different projections. I"m not the best when it comes to mapping equations so I'd greatly appreciate help from anyone in the mapping world. Thanks.
Posted by: joshy on July 12, 2006 at 10:24 AM
-
I have now spent hours trying to get Aerith to work.
The JNLP version dies with exceptions printed to the console.
I'm fed up with trying to install the source code: Java 6, fine. Swing X (fine, had to do a bunch of cvs configuration -- ugh)? Netbeans. Netbeans SVN support... 'svn' is not recognized as an internal or external command'...
Why is this so hard?
Why can't you just publish a zip file with all the jars, and a zip file with all the sources?
Posted by: liminal on July 12, 2006 at 11:23 AM
-
liminal, NetBeans has only very beta support for SVN, but the quickest thing you can do is download a command line SVN client from subversion.tigris.org
I was able to integrate JXMapViewer into my application in a matter of minutes - it's a great piece of software.
Posted by: fabriziogiudici on July 12, 2006 at 03:09 PM
-
Hi Josh--this seems great! One question is--how are you balancing the need/desire for quick scrolling around rendered tiles with the need to garbage collect them at some point? When are the references released? Regards, Patrick
Posted by: pdoubleya on July 13, 2006 at 08:08 AM
-
The tiles are placed into a disk cache and also an in memory cache. The memory cache uses weak references so that they will be collected when memory is low and then we will fall back to the disk cache. If that's empty then we fall back to the network. So far it works pretty well, but the caching could be more intelligent and predictive (ex: you might want to pre-cache the tiles in other zoom levels, which we do right now but not very well)
- J
Posted by: joshy on July 15, 2006 at 11:43 AM
-
JXMapViewer would be a fantastic utility if it was decoupled from the fat swingx.jar. I just can't see downloading a 2M jar containing unneeded stuff just so I can use the map viewer smaller jar. The application needs to be more lightweight.
Posted by: ryustconversant on August 09, 2006 at 06:05 PM
-
You can easily take out the classes you don't need. The core JXMapViewer should be very small. We will eventually add it to the SwingLabs.org store which will build custom jars for you.
Thanks,
Josh
Posted by: joshy on August 09, 2006 at 08:27 PM
-
Not being intimate with the class dependency of JXMapViewer, how would one know which classes to remove? A core minimal map viewer jar would go a long way to allowing others to integrate JXMapViewer into their applications. I am surprised that the swingx-ws project was even separated from the swingx project when one is fully dependent on the other.
Posted by: ryustconversant on August 10, 2006 at 07:23 AM
-
Nice article.
Can you post an example for using Yahoo maps?
Posted by: greenido on September 12, 2006 at 03:09 PM
-
I don't know where to turn with this problem if someone knows the place please inform me of it, however: I got some troubles with this map demo. I don't seem to get a picture to load at all. It must be some trouble with the filepath (I try to get the single square file version to work, look beneath).
//single square file version
TileProviderInfo info = new TileProviderInfo(1, 1, 1, 640, true, true,
"file:/Users/josh/Desktop/world.small.square.jpg",
"x","y","z") {
public String getTileUrl(int zoom, TilePoint tilePoint) {
return this.baseURL;
}
};
I wonder what kind of filepath I'm suppose to send into the TileProviderInfo constructor if I have the image in "C:\picturename.jpg". I tried many different filepaths but no picture appears when I run the demo, and I don't get any errormessages that the picture couldn't be found or whatever.
regards
MB
Posted by: lillbiff on January 11, 2008 at 04:44 AM
-
The file path must be valid. Rather than trying to create the right string by hand, why don't you create a File object and use the toURL method. Something like this:
new File("C:\\picturename.jpg").toURL().toString();
Posted by: joshy on January 11, 2008 at 08:50 AM
-
Thanks, now it works fine!
Posted by: lillbiff on January 15, 2008 at 07:17 AM
-
Hi, this is a really great component! A job well done!
I've been trying for the past few weeks to display a map using UTM projection, unfortunately without any luck. In my first attempt I was trying to convert the lat/long coordinates to UTM to get the tiles from WMS, this was partially a success, but the tiles did not math eachother. So now I'm trying to create tiles without the use of lat/long, basically converting the XY to UTM coordinates, but since I'm no expert at this I'm not able to crack it. The tilecache I'm using (tilecache.org) has several zoom levels that I have to use to get the correct tiles from the server, but since I'm not able to convert the XY coordinates to UTM, It's not working. Do you have a pointer as to how I can use the JXMapViewer with WMS and tilecache?. thanks!.
Posted by: desibel on May 19, 2008 at 04:15 AM
|