FX Experience Has Gone Read-Only

I've been maintaining FX Experience for a really long time now, and I love hearing from people who enjoy my weekly links roundup. One thing I've noticed recently is that maintaining two sites (FX Experience and JonathanGiles.net) takes more time than ideal, and splits the audience up. Therefore, FX Experience will become read-only for new blog posts, but weekly posts will continue to be published on JonathanGiles.net. If you follow @FXExperience on Twitter, I suggest you also follow @JonathanGiles. This is not the end - just a consolidation of my online presence to make my life a little easier!

tl;dr: Follow me on Twitter and check for the latest news on JonathanGiles.net.

Something that you see more and more with client applications at the moment is embedded map components. This blog will show you how to embed Google, Yahoo or Bing maps in your JavaFX application.

Google Maps in JavaFX 2.0

Download a copy of the application and try it out. The jar file is linked below, just download it and double click. This requires you to have the JavaFX 2.0 beta runtime installed.



Download double clickable jar

Using the Application

The toolbar at the top of the application is all JavaFX controls, it communicates with the web map by calling JavaScript functions. Clicking on the Road,Satellite, Hybrid or Terrain sends commands to the JavaScript map library to change the map style. The Google, Yahoo and Bing buttons change the HTML page to use different map providers. As you type in the location field it navigates to the location, try “London” or “New York” for example. The zoom in/out buttons call JavaScript functions to change the map zoom level.

Yahoo Maps in JavaFX 2.0

Bing Maps in JavaFX 2.0

How it’s Built

There are three parts to the application: the HTML and JavaScript code, the JavaFX application and the css styling.

The HTML Code

<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
  html { height: 100% }
  body { height: 100%; margin: 0px; padding: 0px }
  #map_canvas { height: 100%; background-color: #666970; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
  function initialize() {
    var latlng = new google.maps.LatLng(37.39822, -121.9643936);
    var myOptions = {
      zoom: 14,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      mapTypeControl: false,
      navigationControl: false,
      streetViewControl: false,
      backgroundColor: "#666970"
    };

    document.geocoder = new google.maps.Geocoder();
    document.map = new google.maps.Map(document.getElementById("map_canvas"),myOptions);

    document.zoomIn = function zoomIn() {
    	var zoomLevel = document.map.getZoom();
    	if (zoomLevel <= 20) document.map.setZoom(zoomLevel + 1);
    }

    document.zoomOut = function zoomOut() {
    	var zoomLevel = document.map.getZoom();
    	if (zoomLevel > 0) document.map.setZoom(zoomLevel - 1);
    }

    document.setMapTypeRoad = function setMapTypeRoad() {
    	document.map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
    }
    document.setMapTypeSatellite = function setMapTypeSatellite() {
    	document.map.setMapTypeId(google.maps.MapTypeId.SATELLITE);
    }
    document.setMapTypeHybrid = function setMapTypeHybrid() {
    	document.map.setMapTypeId(google.maps.MapTypeId.HYBRID);
    }
    document.setMapTypeTerrain = function setMapTypeTerrain() {
    	document.map.setMapTypeId(google.maps.MapTypeId.TERRAIN);
    }

    document.goToLocation = function goToLocation(searchString) {
        document.geocoder.geocode( {'address': searchString}, function(results, status) {
      	  if (status == google.maps.GeocoderStatus.OK) {
            document.map.setCenter(results[0].geometry.location);
          } else {
            alert("Geocode was not successful for the following reason: " + status);
          }
        });
    }
  }
</script>
</head>
<body onload="initialize()">
  <div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>

This is the code for using Google Maps API, there is similar code for Yahoo and Bing in the attached Netbeans project below. This is very basic use of the Google Maps API to create a full page map and provide some constant JavaScript functions that the JavaFX application can call. You could have called the Google API directly from JavaFX but it seemed simpler to provide a basic abstraction layer in JavaScript so the only difference the JavaFX app sees is the URL. The other two APIs Yahoo and Bing require that you register to get a API key to be able to use them. The code is provided you just need to insert you API keys into the HTML files.

The JavaFX Code

The most basic application without the toolbar to just show the map would be:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

public class WebMap extends Application {

    @Override public void start(Stage stage) {
        // create web engine and view
        final WebEngine webEngine = new WebEngine(getClass().getResource("googlemap.html").toString());
        final WebView webView = new WebView(webEngine);
        // create scene
        stage.setTitle("Web Map");
        Scene scene = new Scene(webView,1000,700, Color.web("#666970"));
        stage.setScene(scene);
        // show stage
        stage.setVisible(true);
    }

    static { // use system proxy settings when standalone application
        System.setProperty("java.net.useSystemProxies", "true");
    }

    public static void main(String[] args){
        Application.launch(args);
    }
}

The next step is to create the toolbar, this is just a bunch of JavaFX controls in a Toolbar with actions that call the JavaScript functions in the HTML page.

import ....

public class WebMap extends Application {
    private Timeline locationUpdateTimeline;

    @Override public void start(Stage stage) {
        // create web engine and view
        final WebEngine webEngine = new WebEngine(
                getClass().getResource("googlemap.html").toString());
        final WebView webView = new WebView(webEngine);
        // create map type buttons
        final ToggleGroup mapTypeGroup = new ToggleGroup();
        final ToggleButton road = new ToggleButton("Road");
        road.setSelected(true);
        road.setToggleGroup(mapTypeGroup);
        final ToggleButton satellite = new ToggleButton("Satellite");
        satellite.setToggleGroup(mapTypeGroup);
        final ToggleButton hybrid = new ToggleButton("Hybrid");
        hybrid.setToggleGroup(mapTypeGroup);
        final ToggleButton terrain = new ToggleButton("Terrain");
        terrain.setToggleGroup(mapTypeGroup);
        mapTypeGroup.selectedToggleProperty().addListener(
                            new ChangeListener<Toggle>() {
            public void changed(
                    ObservableValue<? extends Toggle> observableValue,
                    Toggle toggle, Toggle toggle1) {
                if (road.isSelected()) {
                    webEngine.executeScript("document.setMapTypeRoad()");
                } else if (satellite.isSelected()) {
                    webEngine.executeScript("document.setMapTypeSatellite()");
                } else if (hybrid.isSelected()) {
                    webEngine.executeScript("document.setMapTypeHybrid()");
                } else if (terrain.isSelected()) {
                    webEngine.executeScript("document.setMapTypeTerrain()");
                }
            }
        });
        // add map source toggles
        ToggleGroup mapSourceGroup = new ToggleGroup();
        final ToggleButton google = new ToggleButton("Google");
        google.setSelected(true);
        google.setToggleGroup(mapSourceGroup);
        final ToggleButton yahoo = new ToggleButton("Yahoo");
        yahoo.setToggleGroup(mapSourceGroup);
        final ToggleButton bing = new ToggleButton("Bing");
        bing.setToggleGroup(mapSourceGroup);
        // listen to selected source
        mapSourceGroup.selectedToggleProperty().addListener(
                            new ChangeListener<Toggle>() {
            public void changed(
                    ObservableValue<? extends Toggle> observableValue,
                    Toggle toggle, Toggle toggle1) {
                terrain.setDisable(true);
                if (google.isSelected()) {
                    terrain.setDisable(false);
                    webEngine.load(
                          getClass().getResource("googlemap.html").toString());
                } else if (yahoo.isSelected()) {
                    webEngine.load(
                          getClass().getResource("bingmap.html").toString());
                } else if (bing.isSelected()) {
                    webEngine.load(
                          getClass().getResource("yahoomap.html").toString());
                }
                mapTypeGroup.selectToggle(road);
            }
        });
        // add search
        final TextBox searchBox = new TextBox("95054");
        searchBox.setColumns(12);
        searchBox.setPromptText("Search");
        searchBox.rawTextProperty().addListener(new ChangeListener<String>() {
            public void changed(
                    ObservableValue<? extends String> observableValue,
                    String s, String s1) {
                // delay location updates to we don't go too fast file typing
                if (locationUpdateTimeline!=null) locationUpdateTimeline.stop();
                locationUpdateTimeline = new Timeline();
                locationUpdateTimeline.getKeyFrames().add(
                    new KeyFrame(new Duration(400),
                            new EventHandler<ActionEvent>() {
                        public void handle(ActionEvent actionEvent) {
                            webEngine.executeScript("document.goToLocation(\""+
                                    searchBox.getRawText()+"\")");
                        }
                    })
                );
                locationUpdateTimeline.play();
            }
        });
        Button zoomIn = new Button("Zoom In");
        zoomIn.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent actionEvent) {
                webEngine.executeScript("document.zoomIn()");
            }
        });
        Button zoomOut = new Button("Zoom Out");
        zoomOut.setOnAction(new EventHandler<ActionEvent>() {
            public void handle(ActionEvent actionEvent) {
                webEngine.executeScript("document.zoomOut()");
            }
        });
        // create toolbar
        ToolBar toolBar = new ToolBar();
        toolBar.getStyleClass().add("map-toolbar");
        toolBar.getItems().addAll(
                road, satellite, hybrid, terrain,
                createSpacer(),
                google, yahoo, bing,
                createSpacer(),
                new Label("Location:"), searchBox, zoomIn, zoomOut);
        // create root
        BorderPane root = new BorderPane();
        root.getStyleClass().add("map");
        root.setCenter(webView);
        root.setTop(toolBar);
        // create scene
        stage.setTitle("Web Map");
        Scene scene = new Scene(root,1000,700, Color.web("#666970"));
        stage.setScene(scene);
        scene.getStylesheets().add("/webmap/WebMap.css");
        // show stage
        stage.setVisible(true);
    }

    private Node createSpacer() {
        Region spacer = new Region();
        HBox.setHgrow(spacer, Priority.ALWAYS);
        return spacer;
    }

    static { // use system proxy settings when standalone application
        System.setProperty("java.net.useSystemProxies", "true");
    }

    public static void main(String[] args){
        Application.launch(args);
    }
}

The CSS Code

The last part of the application is a little css to style the toolbar and make it look dark to stand out from the maps.

.map{
    -fx-background-color: #666970;
}
.map-toolbar .button, .map-toolbar .toggle-button, .map-toolbar .label {
    -fx-text-fill: white;
    -fx-background-radius: 0;
}
.map-toolbar{
    -fx-base: #505359;
    -fx-background: #505359;
    -fx-shadow-highlight-color: transparent;
    -fx-spacing: 5;
    -fx-padding: 4 4 4 4;
    -fx-background-color: linear (0%,0%) to (0%,100%) stops
                (0%,#919398)
                (4%,#919398)
                (4.1%,#666970)
                (66%,#494d53)
                (94%,#3c3f46)
                (94.1%,#515151);
}

Download Source



Download Netbeans Project