воскресенье, 25 декабря 2011 г.

Интеграция Google Maps в GWT

     Google Maps API позволяет встраивать карты от Google в web-приложения при помощи JavaScript. Библиотека Google Maps for GWT предоставляет доступ к JavaScript API из Java кода скомпилированного GWT компилятором. 
     Рассмотрим использование Google Maps API и библиотеку для GWT. Создадим приложение на GWT, которое будет определять местоположение пользователя, выводить подробную информацию и показывать место на карте с маркером в центре. 
     Для работы с картами в classpath проекта нужно добавить библиотеку gwt-maps.jar, а также в конфигурационный файл MapGwtApp.gwt.xml дописать следующие строки:
<!-- Include Google Maps API bindings -->
<inherits name='com.google.gwt.maps.Maps' />

<script src="http://maps.google.com/maps/api/js?key=ABQIAAAAxp9dobcDAi8qyMyXsLdS6hT2yXp_ZAY8_ufC3CFXhHIE1NvwkxRiFg5rKqsubGL2ZNYXGXzEg7K5kA&amp;sensor=false" />


* This source code was highlighted with Source Code Highlighter.
     Создадим контейнер AnimatedMapWidget для карты, который позволит ей появляться/скрываться плавно с анимацией.
/**
* @author Dmitry Nikolaenko
*/
    
public class AnimatedMapWidget extends FlowPanel {

    /**
     * how long will take the panel to animate
     */
    private final int ANIMATION_DURATION = 500;

    public AnimatedMapWidget() {
        super();
    
        initWidgetStyle();
    
        show(false);
    
        this.setSize("300px", "250px");
    }
    
    private void initWidgetStyle() {
        this.getElement().getStyle().setPosition(Position.ABSOLUTE);
        this.getElement().getStyle().setBottom(0, Unit.PX);
        this.getElement().getStyle().setZIndex(-1);
        this.getElement().getStyle().setRight(0, Unit.PX);
    }

    @Override
    public void setVisible(final boolean visible) {
        AnimationEffect animationEffect = new AnimationEffect(visible);
    
        animationEffect.run(ANIMATION_DURATION);
    
        if (visible) {
            show(visible);
        } else {
            new Timer() {
                @Override
                public void run() {
                    show(visible);
                }
            }.schedule(ANIMATION_DURATION);
        }
    }

    /**
     * hide or show main panel without animation
     */
    public void show(boolean show) {
        super.setVisible(show);
    }

    /**
     * animation which will change property of the panel depending on the show value
     */
    private class AnimationEffect extends Animation {
    
        private boolean show = true;
    
        AnimationEffect(boolean show) {
            super();
        
            this.show = show;
        }

        @Override
        protected void onUpdate(double progress) {
             double rightValue = progress;
            
             if (!show) {
                 rightValue = 1.0 - progress;
             }
            
             AnimatedMapWidget.this.getElement().getStyle().setRight(-AnimatedMapWidget.this.getOffsetWidth() * rightValue, Unit.PX);
        }
    }
}


* This source code was highlighted with Source Code Highlighter.
     Давайте опишем с помощью enum GoogleMapZoomType тип увеличения для карты:
/**
* @author Dmitry Nikolaenko
*/

public enum GoogleMapZoomType {
    COUNTRY_ZOOM(5),
    REGION_ZOOM(9),
    TOWN_ZOOM(13),
    ADDRESS_ZOOM(17);

    private int zoom;

    private GoogleMapZoomType(int zoom) {
        this.zoom = zoom;
    }

    public int getZoomLevel() {
        return zoom;
    }
}


* This source code was highlighted with Source Code Highlighter.
  Инициализация карты и ее свойств происходит в методах initGoogleMapsWidget() и getGoogleMapsWidget(). Для отображения локации по текущим географическим координатам и установки маркера на карте используются методы displayLocationOnMap() и displayLocationMarker().
/**
* display the current location on the map
* @param geoCoord the geographical coordinates, latitude and longitude
* @param zoomType the zoom level for the map
* @param addressLocation the new address location
*/
private void displayLocationOnMap(HasLatLng geoCoord, GoogleMapZoomType zoomType, String addressLocation) {
    geoRequest.setLatLng(geoCoord);
    geoRequest.setAddress(addressLocation);
    displayLocationMarker(geoRequest, zoomType);
}


* This source code was highlighted with Source Code Highlighter.
/**
* show the marker on the map
*/
public static void displayLocationMarker(final GeocoderRequest request, final GoogleMapZoomType zoomType) {
    new Geocoder().geocode(request, new GeocoderCallback() {
        @Override
        public void callback(List<HasGeocoderResult> responses, String status) {
            if (status.equalsIgnoreCase("ok")) {
                final HasGeocoderResult geoResult = responses.get(0);
                final HasLatLng geoLatLng = geoResult.getGeometry().getLocation();
            
                StringBuilder sb = new StringBuilder();
                for (HasAddressComponent component : geoResult.getAddressComponents()) {
                     sb.append(component.getLongName()).append(", ");
                }
                addressLocationInfo.setText(sb.toString());
            
                mapMarker.setPosition(geoLatLng);
                mapMarker.setDraggable(true);
                mapMarker.setMap(mapWidget.getMap());
            
                mapWidget.getMap().setZoom(zoomType.getZoomLevel());
                mapWidget.getMap().panTo(geoLatLng);
                mapMarker.setMap(mapWidget.getMap());
            } else {
                GWT.log("Geocoder failed with response : " + status);
            }
        }
    });
}


* This source code was highlighted with Source Code Highlighter.
     С помощью JSNI в методе detectCurrentLocation() определяем текущее положение(долготу и ширину).
/**
* JSNI method, try detect current location
*/
private static native void detectCurrentLocation() /*-{
    if ($wnd.navigator.geolocation) {
        $wnd.navigator.geolocation.getCurrentPosition(function(position) {
             var lat = position.coords.latitude;
             var lon = position.coords.longitude;
             @com.dmitrynikol.map.gwt.client.MapGwtApp::currentLocationDetected(DD)(lat, lon);
        }, function() {
             // could not find location
        });
    }
}-*/;


* This source code was highlighted with Source Code Highlighter.
     А если у браузера есть поддержка HTML 5, то для нахождения текущих координат можно воспользоваться методом findCurrentLocation().
/**
* another way to find current location
*/
private void findCurrentLocation() {
    Geolocation.getIfSupported().getCurrentPosition(new Callback<Position, PositionError>() {
        @Override
        public void onSuccess(Position result) {
            double latitide = result.getCoordinates().getLatitude();
            double longitude = result.getCoordinates().getLongitude();
            GWT.log(latitide + " / " + longitude);
        }
    
        @Override
        public void onFailure(PositionError reason) {
            GWT.log(reason.getLocalizedMessage());
        }
    });
}


* This source code was highlighted with Source Code Highlighter.
     Метод refreshGoogleMapsWidget() позволяет возбудить resize событие, обновить и отцентрировать карту.
/**
* refresh google maps widget and preserves the center point
*/
public void refreshGoogleMapsWidget() {
    if (mapWidget != null) {
        HasLatLng center = mapWidget.getMap().getCenter();
        Event.trigger(mapWidget.getMap(), "resize");
        mapWidget.getMap().setCenter(center);
    }
}


* This source code was highlighted with Source Code Highlighter.
     Полный код класса MapGwtApp, где и будет стартовать приложение:
/**
* @author Dmitry Nikolaenko
*/

public class MapGwtApp implements EntryPoint {

    private FlowPanel mainPanel;
    private AnimatedMapWidget animatedMapWidget;
    private Button show;
    private static Label addressLocationInfo;

    private static MapWidget mapWidget;
    private MapOptions options;
    private static GeocoderRequest geoRequest;
    private static Marker mapMarker;

    private boolean showMap = false;

    @Override
    public void onModuleLoad() {
        mainPanel = new FlowPanel();
        animatedMapWidget = new AnimatedMapWidget();
        show = new Button("show map");
        addressLocationInfo = new Label();
    
        show.addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                showMap = !showMap;
                show.setText(showMap ? "hide map" : "show map");
                animatedMapWidget.setVisible(showMap);
            
                if (showMap) {
                    refreshGoogleMapsWidget();
                }
            }
        });
    
        mainPanel.setStyleName("mainPanelStyle");
        mainPanel.add(show);
        mainPanel.add(addressLocationInfo);
        mainPanel.add(animatedMapWidget);
        RootPanel.get().add(mainPanel);
    
        initGoogleMapsWidget();
        detectCurrentLocation();
    }

    private void initGoogleMapsWidget() {
        geoRequest = new GeocoderRequest();
        mapMarker = new Marker();
        options = new MapOptions();
    
        getGoogleMapsWidget();
        displayLocationOnMap(options.getCenter(), GoogleMapZoomType.REGION_ZOOM, "");
        animatedMapWidget.add(mapWidget);
    }

    /**
     * create map widget that presents a viewable Google Map to a user
     */
    public void getGoogleMapsWidget() {
        options.setCenter(new LatLng(39.509, -98.434));
    
        options.setDraggable(true);
        options.setNavigationControl(true);
    
        options.setMapTypeId(new MapTypeId().getRoadmap());
        options.setMapTypeControl(true);
        options.setScrollwheel(true);
    
        mapWidget = new MapWidget(options);
        mapWidget.setSize("100%", "100%");
    }

    /**
     * refresh google maps widget and preserves the center point
     */
    public void refreshGoogleMapsWidget() {
        if (mapWidget != null) {
            HasLatLng center = mapWidget.getMap().getCenter();
            Event.trigger(mapWidget.getMap(), "resize");
            mapWidget.getMap().setCenter(center);
        }
    }

    /**
     * display the current location on the map
     * @param geoCoord the geographical coordinates, latitude and longitude
     * @param zoomType the zoom level for the map
     * @param addressLocation the new address location
     */
    private void displayLocationOnMap(HasLatLng geoCoord, GoogleMapZoomType zoomType, String addressLocation) {
        geoRequest.setLatLng(geoCoord);
        geoRequest.setAddress(addressLocation);
        displayLocationMarker(geoRequest, zoomType);
    }

    /**
     * another way to find current location
     */
    private void findCurrentLocation() {
        Geolocation.getIfSupported().getCurrentPosition(new Callback<Position, PositionError>() {
            @Override
            public void onSuccess(Position result) {
                double latitide = result.getCoordinates().getLatitude();
                double longitude = result.getCoordinates().getLongitude();
                GWT.log(latitide + " / " + longitude);
            }
        
            @Override
            public void onFailure(PositionError reason) {
                GWT.log(reason.getLocalizedMessage());
            }
        });
    }

    /**
     * JSNI method, try detect current location
     */
    private static native void detectCurrentLocation() /*-{
        if ($wnd.navigator.geolocation) {
            $wnd.navigator.geolocation.getCurrentPosition(function(position) {
                 var lat = position.coords.latitude;
                 var lon = position.coords.longitude;
                 @com.dmitrynikol.map.gwt.client.MapGwtApp::currentLocationDetected(DD)(lat, lon);
            }, function() {
                 // could not find location
            });
        }
    }-*/;

    public static void currentLocationDetected(double latitude, double longitude) {
        geoRequest.setLatLng(new LatLng(latitude, longitude));
    
        displayLocationMarker(geoRequest, GoogleMapZoomType.REGION_ZOOM);
    }

    /**
     * show the marker on the map
     */
    public static void displayLocationMarker(final GeocoderRequest request, final GoogleMapZoomType zoomType) {
        new Geocoder().geocode(request, new GeocoderCallback() {
            @Override
            public void callback(List<HasGeocoderResult> responses, String status) {
                if (status.equalsIgnoreCase("ok")) {
                    final HasGeocoderResult geoResult = responses.get(0);
                    final HasLatLng geoLatLng = geoResult.getGeometry().getLocation();
                
                    StringBuilder sb = new StringBuilder();
                    for (HasAddressComponent component : geoResult.getAddressComponents()) {
                         sb.append(component.getLongName()).append(", ");
                    }
                    addressLocationInfo.setText(sb.toString());
                
                    mapMarker.setPosition(geoLatLng);
                    mapMarker.setDraggable(true);
                    mapMarker.setMap(mapWidget.getMap());
                
                    mapWidget.getMap().setZoom(zoomType.getZoomLevel());
                    mapWidget.getMap().panTo(geoLatLng);
                    mapMarker.setMap(mapWidget.getMap());
                } else {
                    GWT.log("Geocoder failed with response : " + status);
                }
            }
        });
    }
}


* This source code was highlighted with Source Code Highlighter.
     Вот так выглядит запущенное приложение: