Использование Google Map в приложении на JavaFX


Хочу рассказать о своем опыте использования Google Maps в приложении на JavaFX. Рассмотрим загрузку карты в приложение и вызов Google Maps JavaScript API v3 для загруженной карты из своего кода на Java.

Реализация

Сначала напишем код инициализации карты на javascript, который оформим ввиде html-страницы map.html. Далее по мере продвижения будем добавлять дополнительный код.

map.html

Теперь у нас есть html-страница с картой. Ее можно открыть в любом браузере. В JavaFX есть замечательный класс для отображения веб-контента: javafx.scene.web.WebView. Воспользуемся им для отображения написанной html-странички. Напишем класс GoogleMap, который будет представлять Google Map в нашем приложении на JavaFX. Чтобы работать с картой как с любым другим JavaFX-нодом (Scene graph node), унаследуемся от класса javafx.scene.Parent.

GoogleMap.java

Простого отображения карты не достаточно. Нам хочется управлять картой и отлавливать события карты. Для обращения к функциям на javascript нам потребуется экземпляр класса netscape.javascript.JSObject, который получим в методе initCommunication(), вызываемом в конструкторе класса GoogleMap. Так же в этом методе закинем в контекст javascript кода GoogleMap.this (экземпляр класса GoogleMap), чтобы иметь возможность вызывать методы класса GoogleMap из кода на javascript.

initCommunication()

Продемонстрируем вызов Java кода из javascript кода на примере отлавливания события клика по карте. Это событие, как ни странно, возникает при нажатии на карту и содержит географические координаты нажатия. Сначала напишем класс события:

MapEvent.java

Теперь напишем методы класса GoogleMap, ответственные за регистрацию обработчика, прием события от javasript кода и вызов обработчика:

методы класса GoogleMap

Осталось только написать обработчик события карты в javascript коде и вызвать в нем метод handle(double lat, double lng) класса GoogleMap:

get_click_position(event)

Теперь напишем метод класса GoogleMap для вызова функций javascript из java кода:

invokeJS(final String 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>

Original : https://habr.com/ru/post/170141/

Добавить комментарий