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

2 thoughts on “Mapping an Address with JavaFX using the GMapsFX Geocoding API

  1. Pingback: JavaFX links of the week, August 8 | JavaFX News, Demos and Insight // FX Experience

  2. Pingback: Java desktop links of the week, August 8 « Jonathan Giles

Leave a Reply