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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
The result should look as follows:
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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.
That’s it! For completeness I’ll include the full source code of the example below.
Scene.fxml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
MainApp.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
FXMLController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); | |
} | |
} |
Pingback: JavaFX links of the week, August 8 | JavaFX News, Demos and Insight // FX Experience
Pingback: Java desktop links of the week, August 8 « Jonathan Giles