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.
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.
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.
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); }