Location-based software in JavaFX

This is the first part in a series of posts on location-based software using JavaFX. In this entry, we will show how you can easily provide basic map functionality in a JavaFX application, by using a mapserver that renders map tiles. We build a desktop application that allows users to zoom in and out, and to drag the map. Tiles will be loaded when required.

My very first presentation at JavaOne was about map software for the JavaFX 1 Platform. Jim Weaver and I explained how you can create software for rendering maps using JavaFX Script, and we showed a few examples rendering location-based maps as well as topic maps. The code for these demos was based on a Swing-based map application from James Gosling. The code in the current project is still based on that Swing application.
Today, we can use JavaFX with its powerful concepts of Binding and InvalidationListener. Combine this with the Java 8 Lambda's, and you have a recipe for success.

Ever since I worked for telematics provider Acunia in the late nineties I have been interested in location-based software. The possibilities with location-based software are endless and still growing. In this post, we only show some basic location functionality using JavaFX. In a next post, we will show how you can use the LeapMotion device in order to navigate through the map intuitively. After that, we will demonstrate the functionality on the Android Platform. With a growing tendency towards mobile computing, and with the increasing number of devices that are equipped with a GPS, the potential for location-based software using JavaFX on Mobile is huge.

The location-based functionality is demonstrated using an open-source project, hosted at https://bitbucket.org/lodgon/openmapfx. As this project will move on, we created a tag "b1" that reflects the project at the time of this writing. I created a Gradle project for this, and you can easily open it in Netbeans if you have the Gradle plugin installed. You can immediately run the project, and a map will be shown. You can drag the map, and zoom in and out.

The main class for this JavaFX Application is the class MapView, in the org.lodgon.openmapfx.desktop package. The code for the start method in this class is very simple:

    public void start(Stage stage) throws Exception {
        LayeredMap map = new LayeredMap();
        Scene scene = new Scene(map, 800, 600);
        map.setCenter(4.2, 50.2);

As you can see, we create a LayeredMap instance, and put it on the Scene. We set the initial zoom level to 4, and the initial center of the map is somewhere in Belgium.
The LayeredMap class that we instantiated is provided in the org.lodgon.openmapfx.core package. This package contains the classes that we consider common functionality across a number of location-based applications. The reason we named it "LayeredMap" is because we will add some layers on top of each other in a next blog post. Think of layers as location-based functionality, e.g. the location of your friends, or thumbnails of pictures of a specific location,... The LayeredMap takes care of the dragging and zooming, by defining some lambda expressions:

        setOnMousePressed(t -> {
            x0 = t.getSceneX();
            y0 = t.getSceneY();
        setOnMouseDragged(t -> {
            x0 = t.getSceneX();
            y0 = t.getSceneY();
        setOnScroll(t -> mapArea.zoom(t.getDeltaY(), t.getSceneX(), t.getSceneY()) );

A LayeredMap needs at least some basic geo-map. This is provided by the MapArea class in the same package. As you can see from the previous snippet, requests for dragging and zooming are delegated to this MapArea instance. The MapArea is a JavaFX Group, containing the map tiles that need to be rendered. The Map Tiles are obtained from the OpenStreetMap project, which --- amongst other services --- provides a webservice which returns map tiles as small images (256 by 256 pixels). OpenStreetMap, and other map providers, provide different images for different zoom levels. At zoomlevel 0, there is only a single tile. At zoomlevel 1, the world is divided in 2 by 2 tiles, and at zoomlevel n, there are 2^n by 2^n tiles. For each tile, we create an instance of the MapTile class. The MapTile contains an image, which is loaded from the OpenStreetMap server:

    String url = TILESERVER + zoom + "/" + i + "/" + j + ".png";
    Image image = new Image(url, true);
    ImageView iv = new ImageView(image);

The URL containing the request to OpenStreetMap contains the requested zoom-level and the x and y indices containing the tile coordinates.

The position and scale of the tiles is dependent on the current zoomfactor and on the translation of the map. In order to make the zooming a bit more fluent, we use a double to indicate the active zoom --- actually, we use a DoubleProperty since we want the MapTile instances to use invalidation listeners that will recalculate their behavior when the zoom level changes. We scale the image up or down depending on how near the active zoom is to the real zoom factor of the image.

The MapArea controls which MapTile instances should be visible at a given time. A MapTile will be visible when its zoom level matches the active zoom level, or when the tiles for a finer zoom level are still loading. In that case, the covering() method on the tile will return true, indicating that this tile covers the same area as at least one tile with a higher zoom level that is still loading.

There is more functionality in the code that we didn't cover, and there is more functionality that will be added to the project later. In the meantime, I am very open to questions, pull requests, suggestions, proposals. I really believe there is a huge potential for JavaFX based location software.

written on 09 May 2014 15:59.


Author: John Childress
Date: 09 May 2014 23:41

This is awesome. Thank you very much.

Author: Joerg Heimbuchner
Date: 10 May 2014 08:28

That is impressiv how easy this can be done with JavaFX. I am looking forward for further development and your next posts. Thank you very much.

Author: Michael Newcomb
Date: 23 Jun 2014 02:42

What license is OpenMapFX under? Thanks!

Author: Geoff Matrangola
Date: 22 Jun 2015 14:29

Thank you for the article and library. Impressive library. It's a very elegant design. I was able to get it to work just fine with the default map provider with OSM maps. However, I'd like to provide a stand-alone (off internet) capability for a little demo app. Would you be so kind as to help out with a little guidance on how to use the file FileProvider class? This is what I've tried so far. First, I downloaded map tiles from an OSM map source. Then, I created a subclass of DefaultBaseMapProvider like this... public class FileBaseMapProfider extends DefaultBaseMapProvider { public FileBaseMapProfider() { TileProvider fileProvider = getTileProviders().get(3); TileType defaultType = fileProvider.getDefaultType(); tileProviderProperty().setValue(fileProvider); tileTypeProperty().setValue(defaultType); } } Finally, I set up the file provider property before constructing the Layered Map. System.setProperty("fileProvider", "file://home/username/data/test/maps/BaseDcAtlas/"); Unfortunately, map appears blank. I've hooked it up to the debugger and it looks like the constructor for MapTile is finding the local .png file. But because everything is set up as binders etc. I'm getting a little lost as to what should happen after the image is loaded. So I can't quite tell how to complete the set up to use the FileProvider class. Thanks, Geoff

Author: Martin Trgina
Date: 02 Jan 2016 23:23

Hi, Very impressive.I am also curious to solve the problem mentioned above by Geoff Matrangola. Thanks. Martin.

Create comment