GMapsFX 2.12.0 Released – Mac OSX Garbled Text Fixed!

The latest version of GMapsFX has been released and includes a major bug fix and a few enhancements.

Bug Fix

Last month I reported a bug that has been affecting users of GMapsFX on Mac OSX where the text appeared ‘garbled’.  An example image is below.
Screen Shot 2017-03-29 at 2.15.45 PM

The underlying issue is that the JavaFx WebView component on Mac OSX is rendering icons rather than letters for some websites, including Google Maps.

I receive a tweet from @ggeorgopoulos1 with a proposed work around that involves injecting CSS programatically into the page with the correct font.  I’ve incorporated the code into the latest GMapsFX library and I am happy to say that it is working once again on Mac OSX!

 

Screen Shot 2016-03-18 at 3.40.46 PM


Enhancements

Added a setKey() method to the GoogleMap compoment

This will allow a key to be set in the FXML and will eliminate the need to programatically set a key on the map object at runtime.

 

Support for Clustered Markers

Added ability to cluster markers by utilizing the Google Maps Marker Clustering API.  https://developers.google.com/maps/documentation/javascript/marker-clustering

clusteredMarkers

Ability to Set Route Colors

Route colors can now be specified rather than having to rely on the default blue that Google Maps uses.

ColorRoute

 

 

Garbled Text in GMapsFX on Mac OSX

 

I have been receiving a lot of messages the last couple of months regarding the text in GMapsFX applications looking “garbled”, as illustrated in the screenshot below.

 

Screen Shot 2017-03-29 at 2.15.45 PM

 

This is appears to only affect applications running on Mac OSX.  GMapsFX makes use of the JavaFX WebView component under the hood, so I created a simple test app that loads Google Maps into a WebView to confirm the issue was the WebView component.

Screen Shot 2017-03-01 at 5.09.09 PM.png

I filed a bug report with Oracle, but apparently this is a known issue that affects only some websites in WebView on Mac. Linux and Windows applications using the WebView component are unaffected.

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8088205

The issue has been open for a couple of years now which leads me to think that a recent Mac update may be contributing to the problem becoming more widespread.

Unfortunately a fix for this won’t be available until Java 9, and there is no work around that I know of.

 

 

Get the Lat/Long of a Mouse Click in a GMapsFX Map

A new API has been created for mouse events in the GMapsFX API which will begin to make it easier to obtain information about mouse events occurring within the Google Map without having to interact with the underlying Javascript API.

So now getting the Lat/Long of a mouse click is a relatively straightforward process.


GoogleMap map = googleMapView.createMap(mapOptions, false);
map.addMouseEventHandler(UIEventType.click, (GMapMouseEvent event) -> {
LatLong latLong = event.getLatLong();
System.out.println("Latitude: " + latLong.getLatitude());
System.out.println("Longitude: " + latLong.getLongitude());
});

view raw

LatLong.java

hosted with ❤ by GitHub

We tell the map we want to add a click UI event listener and pass in an event handler which handles a GMapMouseEvent event object.  From that object the latitude and longitude of the event can be determined.

Currently the Lat/Long are the only properties available on the GMapMouseEvent object, but additional properties will be added as demand warrants.

Below is a screenshot of one of the example applications included with the GMapsFX project that illustrates how to capture the lat/long of a mouse click.

 

latlongscreenshot.png

 

How to Import the GMapsFX Component into SceneBuilder

I recently received a question regarding how to add the GMapsFX component to SceneBuilder, so it could be dragged and dropped onto the UI as its being constructed.

I thought I would address the question here since the good folks at Gluon have made it extremely easy to import custom components to SceneBuilder.

The first step is to click the small ‘gear’ icon just to the right of the Library search box and select the “JAR/FXML Manager” menu item from the popup menu.

screen-shot-2016-09-30-at-2-49-38-pm

 

 

Since the GMapsFX binaries are in the Maven Central repository SceneBuilder can directly download and install the component from there.  Click the “Search repository” link in the Library Manager dialog box.

screen-shot-2016-09-30-at-2-52-03-pm

 

Next, enter “GMapsFX” into the Group or artifact ID text box, and click the “Search” button.  You should get a search result with the com.lynden:GMapsFX artifact. Select the result and click “Add JAR”.

screen-shot-2016-09-30-at-2-52-24-pm

 

The GoogleMapView should be the only component available in the Jar file, select it and click the “Import Component” button.

screen-shot-2016-09-30-at-2-52-33-pm

 

Finally, you should get a confirmation that the library was imported into SceneBuilder.

screen-shot-2016-09-30-at-2-52-41-pm

 

At this point the GoogleMapView component should be visible in the “Custom” component section of the pallette, and ready to be dragged and dropped onto your UI.  Due to the way the component is constructed, a map will not display in SceneBuilder or the SceneBuilder preview tool, but the proper FXML will be generated and the map will display when the program is run.

screen-shot-2016-09-30-at-2-52-50-pm

 

Feel free to tweet me at:  @RobTerpilowski with any questions.

 

Mapping Directions with JavaFX using the GMapsFX Directions API

Mapping directions in a JavaFX application is easy with the Directions API that was recently introduced in GMapsFX.  In this blog post I’ll walk through an example of setting up an application with a map and a couple of text fields, one which will be used for the trip origin and the second which will be used for the trip destination.  When the user hits ‘Enter’ in the destination text field, the map will display the directions.

Starting off with the FXML file, we have an AnchorPane which contains the GoogleMapView and 2 TextFields.  The AnchorPane has a controller assigned to it named FXMLController, and both components have an FX ID associated with them so they will be accessible from the FXMLController class.  Also, the destination TextField has an action, “toTextFieldAction” associated with it, so this method will be called when the user hits the ‘Enter’ key in the TextField.


<?xml version="1.0" encoding="UTF-8"?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="443.0" prefWidth="711.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.lynden.gmaps.directions.FXMLController">
<children>
<GoogleMapView fx:id="mapView" layoutX="-311.0" layoutY="-244.0" prefWidth="490.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<TextField fx:id="fromTextField" prefHeight="27.0" prefWidth="222.0" promptText="From:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0" />
<TextField fx:id="toTextField" layoutX="10.0" layoutY="10.0" onAction="#toTextFieldAction" prefHeight="27.0" prefWidth="222.0" promptText="To:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="50.0" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

The result should look as follows:

Screen Shot 2016-08-19 at 2.56.27 PM

 

Next, I’ve cut up the relevant parts of the FXMLController class.  The MapComponentInitializedListener interface needs to be implemented by the controller since the underlying GoogleMap doesn’t get initialized immediately.  The DirectionsServiceCallback interface also needs to be implemented, although in this example I won’t be doing anything with it.

The GoogleMapView and the TextFields components from the FXML file are defined below and annotated with @FXML.

There is also a reference to the Directions Service as well as StringProperties to represent the ‘to’ and ‘from’ endpoints that the user will enter.

 


public class FXMLController implements Initializable, MapComponentInitializedListener, DirectionsServiceCallback {
protected StringProperty from = new SimpleStringProperty();
protected StringProperty to = new SimpleStringProperty();
@FXML
protected GoogleMapView mapView;
@FXML
protected TextField fromTextField;
@FXML
protected TextField toTextField;

 

After the controller is created, its initialize method is called which will set the MapView’s initialization listener to the FXMLController as well as bind the ‘to’ and ‘from’ String properties to the TextProperties of their respective TextFields.


@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
to.bindBidirectional(toTextField.textProperty());
from.bindBidirectional(fromTextField.textProperty());
}

 

Once the map has been initialized, the DirectionService can be instantiated as well as a MapOptions object to set various attributes about the map.  The options are then configured and a GoogleMap object can be instantiated from the map view.  The directionsPane is a component which can be used to render the step by step direction text, in this example however, it won’t be displayed.


@Override
public void mapInitialized() {
MapOptions options = new MapOptions();
options.center(new LatLong(47.606189, –122.335842))
.zoomControl(true)
.zoom(12)
.overviewMapControl(false)
.mapType(MapTypeIdEnum.ROADMAP);
GoogleMap map = mapView.createMap(options);
directionsService = new DirectionsService();
directionsPane = mapView.getDirec();
}

Finally, the action method defined in the FXML file when the user hits ‘Enter’ in the TextField is below.  The method will call the getRoute() method on the DirectionsService class, passing in a boolean value which will define whether the route can be modified by dragging it, the map object, and the DirectionsRequest object.


@FXML
private void toTextFieldAction(ActionEvent event) {
DirectionsRequest request = new DirectionsRequest(from.get(), to.get(), TravelModes.DRIVING);
directionsService.getRoute(request, this, new DirectionsRenderer(true, mapView.getMap(), directionsPane));
}

 

Below is an example when the user enters directions from Seattle to Redmond

 

Screen Shot 2016-08-19 at 3.37.38 PM

That’s it!  For completeness I’ll include the full source code of the example below.

 

 

Scene.fxml


<?xml version="1.0" encoding="UTF-8"?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<AnchorPane id="AnchorPane" prefHeight="443.0" prefWidth="711.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.lynden.gmaps.directions.FXMLController">
<children>
<GoogleMapView fx:id="mapView" layoutX="-311.0" layoutY="-244.0" prefWidth="490.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<TextField fx:id="fromTextField" prefHeight="27.0" prefWidth="222.0" promptText="From:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="10.0" />
<TextField fx:id="toTextField" layoutX="10.0" layoutY="10.0" onAction="#toTextFieldAction" prefHeight="27.0" prefWidth="222.0" promptText="To:" AnchorPane.leftAnchor="10.0" AnchorPane.topAnchor="50.0" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

MainApp.java


package com.lynden.gmaps.directions;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
Scene scene = new Scene(root);
scene.getStylesheets().add("/styles/Styles.css");
stage.setTitle("JavaFX and Maven");
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}

 

FXMLController.java


package com.lynden.gmaps.directions;
import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.*;
import com.lynden.gmapsfx.service.directions.*;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextField;
public class FXMLController implements Initializable, MapComponentInitializedListener, DirectionsServiceCallback {
protected DirectionsService directionsService;
protected DirectionsPane directionsPane;
protected StringProperty from = new SimpleStringProperty();
protected StringProperty to = new SimpleStringProperty();
@FXML
protected GoogleMapView mapView;
@FXML
protected TextField fromTextField;
@FXML
protected TextField toTextField;
@FXML
private void toTextFieldAction(ActionEvent event) {
DirectionsRequest request = new DirectionsRequest(from.get(), to.get(), TravelModes.DRIVING);
directionsService.getRoute(request, this, new DirectionsRenderer(true, mapView.getMap(), directionsPane));
}
@Override
public void directionsReceived(DirectionsResult results, DirectionStatus status) {
}
@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
to.bindBidirectional(toTextField.textProperty());
from.bindBidirectional(fromTextField.textProperty());
}
@Override
public void mapInitialized() {
MapOptions options = new MapOptions();
options.center(new LatLong(47.606189, –122.335842))
.zoomControl(true)
.zoom(12)
.overviewMapControl(false)
.mapType(MapTypeIdEnum.ROADMAP);
GoogleMap map = mapView.createMap(options);
directionsService = new DirectionsService();
directionsPane = mapView.getDirec();
}
}

GMapsFX 2.0.9 Released

The latest version of GMapsFX has been released which contains a fix for a bug that was preventing the GoogleMapView component from being added as a custom component to SceneBuilder.

The fix will allow the MapView to be added as a custom component.  In a future blog post I will detail how to do this.

 

Screen Shot 2016-08-05 at 4.26.39 PM.png

Mapping an Address with JavaFX using the GMapsFX Geocoding API

Mapping an address in a JavaFX application is extremely easy with the Geocoding API that was recently introduced in GMapsFX.  In this blog post I’ll walk through an example of setting up an application with a map and a text field.  The map will recenter itself at whatever address or place the user types in the text field.

Starting off with the FXML file, we have an AnchorPane which contains the GoogleMapView and a TextField.  The AnchorPane has a controller assigned to it named FXMLController, and both components have an FX ID associated with them so they will be accessible from the FXMLController class.  Also, the TextField has an action, “addressTextFieldAction” associated with it, so this method will be called when the user hits the ‘Enter’ key in the TextField.


<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.*?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<AnchorPane id="AnchorPane" prefHeight="500" prefWidth="750" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.mycompany.gmapstestproject.FXMLController">
<children>
<GoogleMapView fx:id="mapView" prefHeight="500.0" prefWidth="700.0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"/>
<TextField fx:id="addressTextField" onAction="#addressTextFieldAction" prefHeight="27.0" prefWidth="274.0" promptText="Address" AnchorPane.leftAnchor="10" AnchorPane.topAnchor="10" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

The result should look as follows:

Screen Shot 2016-07-29 at 3.12.18 PM.png

 

Next, I’ve cut up the relevant parts of the FXMLController class.  The MapComponentInitializedListener interface needs to be implemented by the controller since the underlying GoogleMap doesn’t get initialized immediately.  The GoogleMapView and TextField components from the FXML file are defined below and annotated with @FXML.

There is also a reference to the GeocodingService as well as a StringProperty to represent the address the user enters.

 


public class FXMLController implements Initializable, MapComponentInitializedListener {
@FXML
private GoogleMapView mapView;
@FXML
private TextField addressTextField;
private GoogleMap map;
private GeocodingService geocodingService;
private StringProperty address = new SimpleStringProperty();

 

After the controller is created its initialize method is called which will set the MapView’s initialization listener to the FXMLController as well as bind the address property to the address TextField’s text property.


@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
address.bind(addressTextField.textProperty());
}

 

Once the map has been initialized, the GeocodingService can be instantiated as well as a MapOptions object to set various attributes about the map.  Once the options are configured, a GoogleMap object can be instantiated from the map view.


@Override
public void mapInitialized() {
geocodingService = new GeocodingService();
MapOptions mapOptions = new MapOptions();
mapOptions.center(new LatLong(47.6097, –122.3331))
.mapType(MapTypeIdEnum.ROADMAP)
.overviewMapControl(false)
.panControl(false)
.rotateControl(false)
.scaleControl(false)
.streetViewControl(false)
.zoomControl(false)
.zoom(12);
map = mapView.createMap(mapOptions);
}

 

Finally, the action method defined in the FXML file when the user hits ‘Enter’ in the TextField is below.  The method will call the geocode() method on the GeocodeService class, passing in the value of the Address property as well as a callback method.

The callback will check the status of the results, and based on the outcome, will recenter the map at the latitude/longitude the user had entered.


@FXML
public void addressTextFieldAction(ActionEvent event) {
geocodingService.geocode(address.get(), (GeocodingResult[] results, GeocoderStatus status) -> {
LatLong latLong = null;
if( status == GeocoderStatus.ZERO_RESULTS) {
Alert alert = new Alert(Alert.AlertType.ERROR, "No matching address found");
alert.show();
return;
} else if( results.length > 1 ) {
Alert alert = new Alert(Alert.AlertType.WARNING, "Multiple results found, showing the first one.");
alert.show();
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
} else {
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
}
map.setCenter(latLong);
});
}

 

Below is an example when the user enters New York City as the address.

Screen Shot 2016-07-29 at 4.27.56 PM

 

That’s it!  For completeness I’ll include the full source code of the example below.

 

 

Scene.fxml


<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.control.*?>
<?import com.lynden.gmapsfx.GoogleMapView?>
<AnchorPane id="AnchorPane" prefHeight="500" prefWidth="750" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.65" fx:controller="com.mycompany.gmapstestproject.FXMLController">
<children>
<GoogleMapView fx:id="mapView" prefHeight="500.0" prefWidth="700.0" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0.0"/>
<TextField fx:id="addressTextField" onAction="#addressTextFieldAction" prefHeight="27.0" prefWidth="274.0" promptText="Address" AnchorPane.leftAnchor="10" AnchorPane.topAnchor="10" />
</children>
</AnchorPane>

view raw

Scene.fxml

hosted with ❤ by GitHub

 

MainApp.java


package com.mycompany.gmapstestproject;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml"));
Scene scene = new Scene(root);
stage.setTitle("JavaFX Geocode Example");
stage.setScene(scene);
stage.show();
}
/**
* The main() method is ignored in correctly deployed JavaFX application.
* main() serves only as fallback in case the application can not be
* launched through deployment artifacts, e.g., in IDEs with limited FX
* support. NetBeans ignores main().
*
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}

view raw

MainApp.java

hosted with ❤ by GitHub

 

FXMLController.java


package com.mycompany.gmapstestproject;
import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.*;
import com.lynden.gmapsfx.service.geocoding.GeocoderStatus;
import com.lynden.gmapsfx.service.geocoding.GeocodingResult;
import com.lynden.gmapsfx.service.geocoding.GeocodingService;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.TextField;
public class FXMLController implements Initializable, MapComponentInitializedListener {
@FXML
private GoogleMapView mapView;
@FXML
private TextField addressTextField;
private GoogleMap map;
private GeocodingService geocodingService;
private StringProperty address = new SimpleStringProperty();
@Override
public void initialize(URL url, ResourceBundle rb) {
mapView.addMapInializedListener(this);
address.bind(addressTextField.textProperty());
}
@Override
public void mapInitialized() {
geocodingService = new GeocodingService();
MapOptions mapOptions = new MapOptions();
mapOptions.center(new LatLong(47.6097, –122.3331))
.mapType(MapTypeIdEnum.ROADMAP)
.overviewMapControl(false)
.panControl(false)
.rotateControl(false)
.scaleControl(false)
.streetViewControl(false)
.zoomControl(false)
.zoom(12);
map = mapView.createMap(mapOptions);
}
@FXML
public void addressTextFieldAction(ActionEvent event) {
geocodingService.geocode(address.get(), (GeocodingResult[] results, GeocoderStatus status) -> {
LatLong latLong = null;
if( status == GeocoderStatus.ZERO_RESULTS) {
Alert alert = new Alert(Alert.AlertType.ERROR, "No matching address found");
alert.show();
return;
} else if( results.length > 1 ) {
Alert alert = new Alert(Alert.AlertType.WARNING, "Multiple results found, showing the first one.");
alert.show();
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
} else {
latLong = new LatLong(results[0].getGeometry().getLocation().getLatitude(), results[0].getGeometry().getLocation().getLongitude());
}
map.setCenter(latLong);
});
}
}

GMapsFX 2.0.7 Released

A new version of GMapsFX has been released to bintray and Maven Central.  The main feature in this version is to allow the use of custom marker/pin images, rather than relying on the default Google images.

A future blog post will demonstrate how to add custom markers to your GMapsFX application.

 

Screen Shot 2016-05-20 at 2.25.33 PM

 

GMapsFX 2.0.6 Released

A new version of GMapsFX has been released to bintray and Maven Central which contains

  • additional bug fixes related to hiding/showing the directions pane at runtime.
  • Ability to pass the map/directions language to the Google map at runtime, eliminating the need to hardcode it.

Screen Shot 2016-05-20 at 2.25.33 PM.png

<dependency> 
  <groupId>com.lynden</groupId> 
  <artifactId>GMapsFX</artifactId> 
  <version>2.0.6</version> 
</dependency>

 

GMapsFX Home: http://rterp.github.io/GMapsFX/
twitter: @RobTerpilowski

GMapsFX 2.0.5 Released

GMapsFX 2.0.5 has been released and is now available via bintray and maven central.

This version contains a major bugfix where the directions pane was always enabled.  The framework has been updated to make the direction pane an option that can be toggled on or off at runtime as it is needed.

GMapsFX is a Java library that makes it extremely easy to add a Google Map to a JavaFX application.

 

<dependency> 
  <groupId>com.lynden</groupId> 
  <artifactId>GMapsFX</artifactId> 
  <version>2.0.5</version> 
  <type>pom</type> 
</dependency>

 

GMaps Home: http://rterp.github.io/GMapsFX/
twitter: @RobTerpilowski