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); }
Awesome!!! I just love JavaFX!!!
Does it not honour Windows proxy settings? The map area goes from white to grey here. No maps, no errors.
why I can’t run this ?
I’ve just installed JavaFX runtime , but when I run this application , it throws null pointer exception ?
at com.sun.deploy.config.JREInfo.validateHomeJRE_int(Unknown Source
at com.sun.deploy.config.JREInfo.initialize(Unknown Source)
any idea ?
I haven’t seen this one before, but I’m sending it on to our deployment team to see what the deal is. Looks like some install related error (like the JRE is installed somewhere we didn’t expect, or the windows registry key was hosed, or something). If you’d like to email me directly I can get you hooked up with the right team and they’ll be able to help figure it out.
Simply awesome, with few line of code, you can make beautiful things.
But I have one question about threading in Java Fx, we can reuse the same logic like in Java Swing for the loading experience or you must use another paradigm for Java Fx? As you know, map loading can take a lot of time when you customize it with another informations.
Small bug, the yahoo button loads bing maps and bing button loads yahoo maps. But cool program, keep up the good work.
Would be a nice feature if changing the map provider keep the same loaded location.
That works but not very well. In satellite mode and using Google as the source the image has grey blotches of “sorry, no imagery here”. It’s not a temporary thing because it persists after all network activity has stopped. Yet if I switch to, say, hybrid representation, these grey squares get filled.
It also obstinately refuses to move to a new location: it remains stuck in Santa Clara no matter what I type in the Location field and how many I try to refresh.
That sounds like we might have a bug in satellite mode as I can’t see why it should be any different to hybrid mode.
There is a bug that the location is not persisted when switch provider, ie google to yahoo. It always reverts back to santa clara. But it should work typing something like “london” or “new york” into the location box.
Cool stuff. Most of the demos shown on this blog were built on a mac, yet there’s no mac version of the SDK available. What gives?
See http://fxexperience.com/2011/05/is-javafx-2-0-cross-platform/
The need to redeclare the webEngine and webView in the toolbar code is misleading. Without careful re-reading it looks like you are creating a second instance.
Hello is there a way to avoid that fuzzy-look?
Have anyone tried to print this map window area with JavaFX?
I can’t get either the jar or the Netbeans code to work. The jar pops up a “JavaFX Launcher Error” which says “Unable to invoke launch method” and Netbeans can’t find JavaFX despite having the option of using “Default JavaFX Platform”.
I want to use JavaFX with a desktop application but have had no luck in finding a functional example. The JavaFX runtime and SDK have both been installed.
Any idea what could be wrong?
After adding the proper jar to the libraries it now only complains about line 38, 88, and 132.
line 38: final WebView webView = new WebView(webEngine);
Netbeans seems to think WebView doesn’t need an input.
line 88: final TextBox searchBox = new TextBox(“95054”);
Netbeans cannot find this class.
line 132: stage.setVisible(true);
Netbeans can’t find the setVisible method.
all you can do you can tweak the code like this
// create web engine and view
final WebView webView = new WebView();
final WebEngine webEngine = webView.getEngine();
webEngine.load(getClass().getResource(“googlemap.html”).toString());
using .show() instead of setVisible works
i can’t run the jar file “Unable to invoke launch method” , and also i can’t run the Netbeans Files (some missing libraries) … i’m using Netbeans 7.1 and i installed all SDK, and even i updated my JavaFX 2.2 Runtime from
http://www.oracle.com/technetwork/java/javafx/downloads/devpreview-1429449.html
error log :
java.lang.VerifyError: (class: webmap/WebMap, method: signature: ()V) Constructor must call super() or this()
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2442)
at java.lang.Class.getMethod0(Class.java:2685)
at java.lang.Class.getMethod(Class.java:1620)
at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:488)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:480)
Exception in thread “main” Java Result: 1
really cool application… if only it works for me 🙁
any advice to resolve??
i added jfxrt.jar to the library, but still get this error :
Exception in Application start method
Exception in thread “main” java.lang.RuntimeException: Exception in Application start method
at com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source)
at com.sun.javafx.application.LauncherImpl.access$000(Unknown Source)
at com.sun.javafx.application.LauncherImpl$1.run(Unknown Source)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.RuntimeException: Uncompilable source code – constructor WebView in class javafx.scene.web.WebView cannot be applied to given types;
required: no arguments
found: javafx.scene.web.WebEngine
reason: actual and formal argument lists differ in length
at webmap.WebMap.start(WebMap.java:38)
at com.sun.javafx.application.LauncherImpl$5.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl$4.run(Unknown Source)
at com.sun.javafx.application.PlatformImpl$3.run(Unknown Source)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.access$100(Unknown Source)
at com.sun.glass.ui.win.WinApplication$2$1.run(Unknown Source)
… 1 more
Java Result: 1
error in line 92
final TextBox searchBox = new TextBox(“95054”);
cannot find symbol
Symbol: class TextBox
location: class WebMap
I made a blog entry how to implement the way back, so how to read information from the MapView in JavaFX;
http://oldguardprogrammer.blogspot.nl/2013/01/javafx-and-google-maps.html
Error in line 92
final TextBox searchBox = new TextBox(“95054”);
how can solve it ?
When run get this Error
java.lang.reflect.InvocationTargetException
when i run the example i get this error on the console:
A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x638cfccb, pid=2448, tid=1060
#
# JRE version: Java(TM) SE Runtime Environment (7.0_45-b18) (build 1.7.0_45-b18)
# Java VM: Java HotSpot(TM) Client VM (24.45-b08 mixed mode, sharing windows-x86 )
# Problematic frame:
# V [jvm.dll+0xbfccb]
#
# Failed to write core dump. Minidumps are not enabled by default on client versions of Windows
anyone knows why?
also i have errors on
line 38: final WebView webView = new WebView(webEngine);
Netbeans seems to think WebView doesn’t need an input.
line 88: final TextBox searchBox = new TextBox(“95054″);
Netbeans cannot find this class.
line 132: stage.setVisible(true);
Netbeans can’t find the setVisible method.
could I add my own layer on top of map using this controll?
This no longer works with JavaFX 2.2.
final WebEngine webEngine = new WebEngine(getClass().getResource(“googlemap.html”).toString());
final WebView webView = new WebView(webEngine);
WebView now only has a blank constructor.
So how does one do this now?
have you changed the links of the pojects? it doesn’t work. could you tell me where can i download?
Cool it’s amazing
Never thought it that easy 😉
Both the Jar file and netbeans project for download are not available. page says no such page found.
It is show error in: WebView(webEngine)