Хочу рассказать о своем опыте использования Google Maps в приложении на JavaFX. Рассмотрим загрузку карты в приложение и вызов Google Maps JavaScript API v3 для загруженной карты из своего кода на Java.
Реализация
Сначала напишем код инициализации карты на javascript, который оформим ввиде html-страницы map.html. Далее по мере продвижения будем добавлять дополнительный код.
map.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key=***&sensor=false">
</script>
<script type="text/javascript">
var map;
var marker;
function initialize() {
var defLatLng = new google.maps.LatLng(59.95632093391832, 30.309906005859375);
var mapOptions = {
center: defLatLng,
zoom: 3,
mapTypeId: google.maps.MapTypeId.ROADMAP,
disableDefaultUI: true,
panControl: false
};
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
marker = new google.maps.Marker({
position: defLatLng,
map: map,
icon: "img/Pin.png"
});
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>
Теперь у нас есть html-страница с картой. Ее можно открыть в любом браузере. В JavaFX есть замечательный класс для отображения веб-контента: javafx.scene.web.WebView. Воспользуемся им для отображения написанной html-странички. Напишем класс GoogleMap, который будет представлять Google Map в нашем приложении на JavaFX. Чтобы работать с картой как с любым другим JavaFX-нодом (Scene graph node), унаследуемся от класса javafx.scene.Parent.
GoogleMap.java
public class GoogleMap extends Parent {
public GoogleMap() {
initMap();
getChildren().add(webView);
}
private void initMap()
{
webView = new WebView();
webEngine = webView.getEngine();
webEngine.load(getClass().getResource("map.html").toExternalForm());
ready = false;
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>()
{
@Override
public void changed(final ObservableValue<? extends Worker.State> observableValue,
final Worker.State oldState,
final Worker.State newState)
{
if (newState == Worker.State.SUCCEEDED)
{
ready = true;
}
}
});
}
private WebView webView;
private WebEngine webEngine;
private boolean ready;
}
Простого отображения карты не достаточно. Нам хочется управлять картой и отлавливать события карты. Для обращения к функциям на javascript нам потребуется экземпляр класса netscape.javascript.JSObject, который получим в методе initCommunication(), вызываемом в конструкторе класса GoogleMap. Так же в этом методе закинем в контекст javascript кода GoogleMap.this (экземпляр класса GoogleMap), чтобы иметь возможность вызывать методы класса GoogleMap из кода на javascript.
initCommunication()
private void initCommunication() {
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>()
{
@Override
public void changed(final ObservableValue<? extends Worker.State> observableValue,
final Worker.State oldState,
final Worker.State newState)
{
if (newState == Worker.State.SUCCEEDED)
{
doc = (JSObject) webEngine.executeScript("window");
doc.setMember("app", GoogleMap.this);
}
}
});
}
private JSObject doc;
Продемонстрируем вызов Java кода из javascript кода на примере отлавливания события клика по карте. Это событие, как ни странно, возникает при нажатии на карту и содержит географические координаты нажатия. Сначала напишем класс события:
MapEvent.java
public class MapEvent extends Event {
public MapEvent(GoogleMap map, double lat, double lng) {
super(map, Event.NULL_SOURCE_TARGET, Event.ANY);
this.lat = lat;
this.lng = lng;
}
public double getLat() {
return this.lat;
}
public double getLng() {
return this.lng;
}
private double lat;
private double lng;
}
Теперь напишем методы класса GoogleMap, ответственные за регистрацию обработчика, прием события от javasript кода и вызов обработчика:
методы класса GoogleMap
public void setOnMapLatLngChanged(EventHandler<MapEvent> eventHandler) {
onMapLatLngChanged = eventHandler;
}
public void handle(double lat, double lng) {
if(onMapLatLngChanged != null) {
MapEvent event = new MapEvent(this, lat, lng);
onMapLatLngChanged.handle(event);
}
}
private EventHandler<MapEvent> onMapLatLngChanged;
Осталось только написать обработчик события карты в javascript коде и вызвать в нем метод handle(double lat, double lng) класса GoogleMap:
get_click_position(event)
function get_click_position(event){
var location = event.latLng;
var lat = location.lat();
var lng = location.lng();
app.handle(lat, lng);
}
Теперь напишем метод класса GoogleMap для вызова функций javascript из java кода:
invokeJS(final String str)
private void invokeJS(final String str) {
if(ready) {
doc.eval(str);
}
else {
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>()
{
@Override
public void changed(final ObservableValue<? extends Worker.State> observableValue,
final Worker.State oldState,
final Worker.State newState)
{
if (newState == Worker.State.SUCCEEDED)
{
doc.eval(str);
}
}
});
}
}
Результат
Не буду отдельно пояснять методы установки типа карты, положения центра карты, положения курсора. Все эти методы есть в полных исходниках:
GoogleMap.java
public class GoogleMap extends Parent {
public GoogleMap() {
initMap();
initCommunication();
getChildren().add(webView);
setMarkerPosition(0,0);
setMapCenter(0, 0);
switchTerrain();
}
private void initMap()
{
webView = new WebView();
webEngine = webView.getEngine();
webEngine.load(getClass().getResource("resources/map.html").toExternalForm());
ready = false;
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>()
{
@Override
public void changed(final ObservableValue<? extends Worker.State> observableValue,
final Worker.State oldState,
final Worker.State newState)
{
if (newState == Worker.State.SUCCEEDED)
{
ready = true;
}
}
});
}
private void initCommunication() {
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>()
{
@Override
public void changed(final ObservableValue<? extends Worker.State> observableValue,
final Worker.State oldState,
final Worker.State newState)
{
if (newState == Worker.State.SUCCEEDED)
{
doc = (JSObject) webEngine.executeScript("window");
doc.setMember("app", GoogleMap.this);
}
}
});
}
private void invokeJS(final String str) {
if(ready) {
doc.eval(str);
}
else {
webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>()
{
@Override
public void changed(final ObservableValue<? extends Worker.State> observableValue,
final Worker.State oldState,
final Worker.State newState)
{
if (newState == Worker.State.SUCCEEDED)
{
doc.eval(str);
}
}
});
}
}
public void setOnMapLatLngChanged(EventHandler<MapEvent> eventHandler) {
onMapLatLngChanged = eventHandler;
}
public void handle(double lat, double lng) {
if(onMapLatLngChanged != null) {
MapEvent event = new MapEvent(this, lat, lng);
onMapLatLngChanged.handle(event);
}
}
public void setMarkerPosition(double lat, double lng) {
String sLat = Double.toString(lat);
String sLng = Double.toString(lng);
invokeJS("setMarkerPosition(" + sLat + ", " + sLng + ")");
}
public void setMapCenter(double lat, double lng) {
String sLat = Double.toString(lat);
String sLng = Double.toString(lng);
invokeJS("setMapCenter(" + sLat + ", " + sLng + ")");
}
public void switchSatellite() {
invokeJS("switchSatellite()");
}
public void switchRoadmap() {
invokeJS("switchRoadmap()");
}
public void switchHybrid() {
invokeJS("switchHybrid()");
}
public void switchTerrain() {
invokeJS("switchTerrain()");
}
public void startJumping() {
invokeJS("startJumping()");
}
public void stopJumping() {
invokeJS("stopJumping()");
}
public void setHeight(double h) {
webView.setPrefHeight(h);
}
public void setWidth(double w) {
webView.setPrefWidth(w);
}
public ReadOnlyDoubleProperty widthProperty() {
return webView.widthProperty();
}
private JSObject doc;
private EventHandler<MapEvent> onMapLatLngChanged;
private WebView webView;
private WebEngine webEngine;
private boolean ready;
}
map.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key=***&sensor=false">
</script>
<script type="text/javascript">
var map;
var marker;
function get_click_position(event){
var location = event.latLng;
var lat = location.lat();
var lng = location.lng();
setMarkerPosition(lat, lng);
app.handle(lat, lng);
}
function setMarkerPosition(lat, lng) {
var clickLatLng = new google.maps.LatLng(lat, lng);
marker.setPosition(clickLatLng);
}
function startJumping(){
marker.setAnimation(google.maps.Animation.BOUNCE);
}
function stopJumping(){
marker.setAnimation(google.maps.Animation.BOUNCE);
}
function setMapCenter(lat, lng) {
var latlng = new google.maps.LatLng(lat, lng);
map.setCenter(latlng);
}
function switchSatellite() {
var mapOptions = {
mapTypeId: google.maps.MapTypeId.SATELLITE
};
map.setOptions(mapOptions);
setLightMarkerIcon();
}
function switchRoadmap() {
var mapOptions = {
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map.setOptions(mapOptions);
setDarkMarkerIcon();
}
function switchHybrid() {
var mapOptions = {
mapTypeId: google.maps.MapTypeId.HYBRID
};
map.setOptions(mapOptions);
setLightMarkerIcon();
}
function switchTerrain() {
var mapOptions = {
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map.setOptions(mapOptions);
setDarkMarkerIcon();
}
function initialize() {
var defLatLng = new google.maps.LatLng(59.95632093391832, 30.309906005859375);
var mapOptions = {
center: defLatLng,
zoom: 3,
mapTypeId: google.maps.MapTypeId.ROADMAP,
disableDefaultUI: true,
panControl: false
};
map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
google.maps.event.addListener(map, 'click', get_click_position);
marker = new google.maps.Marker({
position: defLatLng,
map: map,
icon: "img/Pin.png"
});
app.handle(0, 0);
}
function setDarkMarkerIcon() {
marker.setIcon("img/Pin.png");
}
function setLightMarkerIcon() {
marker.setIcon("img/Pin_s.png");
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>
Как сказал Вольтер:
«Я могу быть не согласным с Вашим мнением, но я готов отдать жизнь за Ваше право высказывать его.»