I would like to start working on a JavaFX desktop app that will work heavily (if all goes well) with the Google Maps APIs. I\'ve been having a more difficult time getting st
You could use some of the API's without a webview; just use the Google client libraries...
Add these dependencies:
And for example, using the Places API:
And you can get this:
Just make sure you add your Google API key and enable billing... otherwise it won't let you do more than 1 poll a day.
private static final String API_KEY = add API KEY HERE;
Main.java
import com.google.maps.model.AddressComponentType;
import com.google.maps.model.PlaceDetails;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.apache.commons.lang3.StringUtils;
import test.AutoCompleteAddressField.AddressPrediction;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
AutoCompleteAddressField text = new AutoCompleteAddressField();
TextField streetField = new TextField();
streetField.setPromptText("Street");
TextField postalField = new TextField();
postalField.setPromptText("PostalCode");
TextField cityField = new TextField();
cityField.setPromptText("City");
TextField provinceField = new TextField();
provinceField.setPromptText("Province");
TextField countryField = new TextField();
countryField.setPromptText("Country");
Button clearButton = new Button("Clear");
clearButton.setOnAction(e ->
{
text.clear();
text.getEntries().clear();
streetField.clear();
postalField.clear();
cityField.clear();
provinceField.clear();
countryField.clear();
});
text.getEntryMenu().setOnAction((ActionEvent e) ->
{
((MenuItem) e.getTarget()).addEventHandler(Event.ANY, (Event event) ->
{
if (text.getLastSelectedObject() != null)
{
text.setText(text.getLastSelectedObject().toString());
PlaceDetails place = AutoCompleteAddressField.getPlace((AddressPrediction) text.getLastSelectedObject());
if (place != null)
{
streetField.setText(
StringUtils.join(
AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.STREET_NUMBER),
" ",
AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.ROUTE))
);
postalField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.POSTAL_CODE));
cityField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.LOCALITY));
provinceField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.ADMINISTRATIVE_AREA_LEVEL_1));
countryField.setText(AutoCompleteAddressField.getComponentLongName(place.addressComponents, AddressComponentType.COUNTRY));
} else
{
streetField.clear();
postalField.clear();
cityField.clear();
provinceField.clear();
countryField.clear();
}
}
});
});
VBox root = new VBox();
root.getChildren().addAll(text, new Label(), streetField, postalField, provinceField, countryField, clearButton);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Test Google Places API");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
AutoCompleteAddressField.java
import com.google.maps.GeoApiContext;
import com.google.maps.PlaceDetailsRequest;
import com.google.maps.PlacesApi;
import com.google.maps.QueryAutocompleteRequest;
import com.google.maps.errors.ApiException;
import com.google.maps.model.AddressComponent;
import com.google.maps.model.AddressComponentType;
import com.google.maps.model.AutocompletePrediction;
import com.google.maps.model.PlaceDetails;
import java.io.IOException;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
public class AutoCompleteAddressField extends AutoCompleteTextField
{
private static final String API_KEY = add API KEY HERE;
public AutoCompleteAddressField()
{
super(new TreeSet<>((AddressPrediction o1, AddressPrediction o2) -> o1.toString().compareTo(o2.toString())));
textProperty().addListener((ObservableValue<? extends String> o, String oldValue, String newValue) ->
{
if (newValue != null && !newValue.isEmpty())
{
new Thread(() ->
{
AutocompletePrediction[] predictions = getPredictions(getText());
Platform.runLater(() ->
{
getEntries().clear();
for (AutocompletePrediction prediction : predictions)
{
getEntries().add(new AddressPrediction(prediction));
}
});
}).start();
}
});
}
public class AddressPrediction
{
private final AutocompletePrediction prediction;
public AddressPrediction(AutocompletePrediction prediction)
{
this.prediction = prediction;
}
@Override
public String toString()
{
return prediction.description;
}
protected AutocompletePrediction getPrediction()
{
return this.prediction;
}
}
public static PlaceDetails getPlace(AddressPrediction prediction)
{
if (prediction != null && prediction.getPrediction() != null && !prediction.getPrediction().placeId.isEmpty())
{
PlaceDetailsRequest query = PlacesApi.placeDetails(new GeoApiContext.Builder().apiKey(API_KEY).build(), prediction.getPrediction().placeId);
return query.awaitIgnoreError();
}
return null;
}
public static AutocompletePrediction[] getPredictions(String userInput)
{
QueryAutocompleteRequest query = PlacesApi.queryAutocomplete(new GeoApiContext.Builder()
.apiKey(API_KEY)
.build(), userInput);
try
{
return query.await();
} catch (ApiException | InterruptedException | IOException ex)
{
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
return new AutocompletePrediction[0];
}
public static String getComponentLongName(AddressComponent[] components, AddressComponentType type)
{
for (AddressComponent component : components)
{
for (AddressComponentType types : component.types)
{
if (types.equals(type))
{
return component.longName;
}
}
}
return "";
}
}
AutoCompleteTextField.java
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.geometry.Side;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.CustomMenuItem;
import javafx.scene.control.TextField;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
/**
* This class is a TextField which implements an "autocomplete" functionality,
* based on a supplied list of entries.<p>
*
* If the entered text matches a part of any of the supplied entries these are
* going to be displayed in a popup. Further the matching part of the entry is
* going to be displayed in a special style, defined by
* {@link #textOccurenceStyle textOccurenceStyle}. The maximum number of
* displayed entries in the popup is defined by
* {@link #maxEntries maxEntries}.<br>
* By default the pattern matching is not case-sensitive. This behaviour is
* defined by the {@link #caseSensitive caseSensitive}
* .<p>
*
* The AutoCompleteTextField also has a List of
* {@link #filteredEntries filteredEntries} that is equal to the search results
* if search results are not empty, or {@link #filteredEntries filteredEntries}
* is equal to {@link #entries entries} otherwise. If
* {@link #popupHidden popupHidden} is set to true no popup is going to be
* shown. This list can be used to bind all entries to another node (a ListView
* for example) in the following way:
* <pre>
* <code>
* AutoCompleteTextField auto = new AutoCompleteTextField(entries);
* auto.setPopupHidden(true);
* SimpleListProperty filteredEntries = new SimpleListProperty(auto.getFilteredEntries());
* listView.itemsProperty().bind(filteredEntries);
* </code>
* </pre>
*
* @author Caleb Brinkman
* @author Fabian Ochmann
* @param <S>
*/
public class AutoCompleteTextField<S> extends TextField
{
private final ObjectProperty<S> lastSelectedItem = new SimpleObjectProperty<>();
/**
* The existing autocomplete entries.
*/
private final SortedSet<S> entries;
/**
* The set of filtered entries:<br>
* Equal to the search results if search results are not empty, equal to
* {@link #entries entries} otherwise.
*/
private ObservableList<S> filteredEntries
= FXCollections.observableArrayList();
/**
* The popup used to select an entry.
*/
private ContextMenu entriesPopup;
/**
* Indicates whether the search is case sensitive or not. <br>
* Default: false
*/
private boolean caseSensitive = false;
/**
* Indicates whether the Popup should be hidden or displayed. Use this if
* you want to filter an existing list/set (for example values of a
* {@link javafx.scene.control.ListView ListView}). Do this by binding
* {@link #getFilteredEntries() getFilteredEntries()} to the list/set.
*/
private boolean popupHidden = false;
/**
* The CSS style that should be applied on the parts in the popup that match
* the entered text. <br>
* Default: "-fx-font-weight: bold; -fx-fill: red;"
* <p>
* Note: This style is going to be applied on an
* {@link javafx.scene.text.Text Text} instance. See the <i>JavaFX CSS
* Reference Guide</i> for available CSS Propeties.
*/
private String textOccurenceStyle = "-fx-font-weight: bold; "
+ "-fx-fill: rgb(66,139,202);";
/**
* The maximum Number of entries displayed in the popup.<br>
* Default: 10
*/
private int maxEntries = 10;
/**
* Construct a new AutoCompleteTextField.
*
* @param entrySet
*/
public AutoCompleteTextField(SortedSet<S> entrySet)
{
super();
this.entries = (entrySet == null ? new TreeSet<>() : entrySet);
this.filteredEntries.addAll(entries);
entriesPopup = new ContextMenu();
textProperty().addListener((ObservableValue<? extends String> observableValue, String s, String s2) ->
{
if (getText() == null || getText().length() == 0)
{
filteredEntries.clear();
filteredEntries.addAll(entries);
entriesPopup.hide();
} else
{
LinkedList<S> searchResult = new LinkedList<>();
//Check if the entered Text is part of some entry
String text1 = getText();
Pattern pattern;
if (isCaseSensitive())
{
pattern = Pattern.compile(".*" + text1 + ".*");
} else
{
pattern = Pattern.compile(".*" + text1 + ".*", Pattern.CASE_INSENSITIVE);
}
for (S entry : entries)
{
Matcher matcher = pattern.matcher(entry.toString());
if (matcher.matches())
{
searchResult.add(entry);
}
}
if (!entries.isEmpty())
{
filteredEntries.clear();
filteredEntries.addAll(searchResult);
//Only show popup if not in filter mode
if (!isPopupHidden())
{
populatePopup(searchResult, text1);
if (!entriesPopup.isShowing())
{
entriesPopup.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0);
}
}
} else
{
entriesPopup.hide();
}
}
});
focusedProperty().addListener((ObservableValue<? extends Boolean> observableValue, Boolean aBoolean, Boolean aBoolean2) ->
{
entriesPopup.hide();
});
}
/**
* Get the existing set of autocomplete entries.
*
* @return The existing autocomplete entries.
*/
public SortedSet<S> getEntries()
{
return entries;
}
/**
* Populate the entry set with the given search results. Display is limited
* to 10 entries, for performance.
*
* @param searchResult The set of matching strings.
*/
private void populatePopup(List<S> searchResult, String text)
{
List<CustomMenuItem> menuItems = new LinkedList<>();
int count = Math.min(searchResult.size(), getMaxEntries());
for (int i = 0; i < count; i++)
{
final String result = searchResult.get(i).toString();
final S itemObject = searchResult.get(i);
int occurence;
if (isCaseSensitive())
{
occurence = result.indexOf(text);
} else
{
occurence = result.toLowerCase().indexOf(text.toLowerCase());
}
if (occurence < 0)
{
continue;
}
//Part before occurence (might be empty)
Text pre = new Text(result.substring(0, occurence));
//Part of (first) occurence
Text in = new Text(result.substring(occurence, occurence + text.length()));
in.setStyle(getTextOccurenceStyle());
//Part after occurence
Text post = new Text(result.substring(occurence + text.length(), result.length()));
TextFlow entryFlow = new TextFlow(pre, in, post);
CustomMenuItem item = new CustomMenuItem(entryFlow, true);
item.setOnAction((ActionEvent actionEvent) ->
{
lastSelectedItem.set(itemObject);
entriesPopup.hide();
});
menuItems.add(item);
}
entriesPopup.getItems().clear();
entriesPopup.getItems().addAll(menuItems);
}
public S getLastSelectedObject()
{
return lastSelectedItem.get();
}
public ContextMenu getEntryMenu()
{
return entriesPopup;
}
public boolean isCaseSensitive()
{
return caseSensitive;
}
public String getTextOccurenceStyle()
{
return textOccurenceStyle;
}
public void setCaseSensitive(boolean caseSensitive)
{
this.caseSensitive = caseSensitive;
}
public void setTextOccurenceStyle(String textOccurenceStyle)
{
this.textOccurenceStyle = textOccurenceStyle;
}
public boolean isPopupHidden()
{
return popupHidden;
}
public void setPopupHidden(boolean popupHidden)
{
this.popupHidden = popupHidden;
}
public ObservableList<S> getFilteredEntries()
{
return filteredEntries;
}
public int getMaxEntries()
{
return maxEntries;
}
public void setMaxEntries(int maxEntries)
{
this.maxEntries = maxEntries;
}
}
I am just asking that somebody please provide an example of some code that would simply load the map and pin a few locations on it.
One of the ways you can do this is using a combination of HTML, Javascript and JavaFX. Create a google maps instance in html and javascript then call the URL in your JavaFX program using WebEngine class. WebEngine loads Web pages, creates their document models, applies styles as necessary, and runs JavaScript on pages. After that you'll use WebView to display the google map content like:
final WebEngine webEngine = new WebEngine(getClass().getResource("googlemap.html").toString());
final WebView webView = new WebView(webEngine);
Here's a link with sample codes to get you started.