gov.wa.wsdot.mobile.client.activities.trafficmap.TrafficMapViewGwtImpl.java Source code

Java tutorial

Introduction

Here is the source code for gov.wa.wsdot.mobile.client.activities.trafficmap.TrafficMapViewGwtImpl.java

Source

/*
 * Copyright (c) 2015 Washington State Department of Transportation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 */

package gov.wa.wsdot.mobile.client.activities.trafficmap;

import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapTypeId;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.base.LatLngBounds;
import com.google.gwt.maps.client.base.Point;
import com.google.gwt.maps.client.controls.MapTypeStyle;
import com.google.gwt.maps.client.events.MapEventType;
import com.google.gwt.maps.client.events.MapHandlerRegistration;
import com.google.gwt.maps.client.events.click.ClickMapEvent;
import com.google.gwt.maps.client.events.click.ClickMapHandler;
import com.google.gwt.maps.client.events.idle.IdleMapEvent;
import com.google.gwt.maps.client.events.idle.IdleMapHandler;
import com.google.gwt.maps.client.events.resize.ResizeMapEvent;
import com.google.gwt.maps.client.events.resize.ResizeMapHandler;
import com.google.gwt.maps.client.layers.TrafficLayer;
import com.google.gwt.maps.client.maptypes.MapTypeStyleElementType;
import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType;
import com.google.gwt.maps.client.maptypes.MapTypeStyler;
import com.google.gwt.maps.client.overlays.*;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.storage.client.Storage;
import com.google.gwt.storage.client.StorageMap;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.googlecode.gwtphonegap.client.geolocation.Position;
import com.googlecode.mgwt.dom.client.event.tap.TapEvent;
import com.googlecode.mgwt.ui.client.MGWT;
import com.googlecode.mgwt.ui.client.widget.button.image.RefreshImageButton;
import com.googlecode.mgwt.ui.client.widget.buttonbar.ButtonBar;
import com.googlecode.mgwt.ui.client.widget.header.HeaderTitle;
import com.googlecode.mgwt.ui.client.widget.panel.flex.FlexSpacer;
import com.googlecode.mgwt.ui.client.widget.progress.ProgressIndicator;
import gov.wa.wsdot.mobile.client.css.AppBundle;
import gov.wa.wsdot.mobile.client.util.ParserUtils;
import gov.wa.wsdot.mobile.client.widget.button.image.*;
import gov.wa.wsdot.mobile.shared.CalloutItem;
import gov.wa.wsdot.mobile.shared.CameraItem;
import gov.wa.wsdot.mobile.shared.HighwayAlertItem;
import gov.wa.wsdot.mobile.shared.RestAreaItem;

import java.util.*;
import java.util.Map.Entry;

public class TrafficMapViewGwtImpl extends Composite implements TrafficMapView {

    /**
     * The UiBinder interface.
     */
    interface TrafficMapViewGwtImplUiBinder extends UiBinder<Widget, TrafficMapViewGwtImpl> {
    }

    /**
     * The UiBinder used to generate the view.
     */
    private static TrafficMapViewGwtImplUiBinder uiBinder = GWT.create(TrafficMapViewGwtImplUiBinder.class);

    @UiField
    HeaderTitle heading;

    @UiField
    BackImageButton backButton;

    @UiField
    FlexSpacer leftFlexSpacer;

    @UiField
    FlowPanel flowPanel;

    @UiField
    ProgressIndicator progressIndicator;

    @UiField
    ButtonBar buttonBar;

    @UiField
    Camera2ImageButton cameraButton;

    @UiField
    MenuImageButton menuButton;

    @UiField
    WarningImageButton alertsButton;

    @UiField
    NavigationImageButton navigationButton;

    @UiField
    StarImageButton starButton;

    @UiField
    RefreshImageButton refreshButton;

    private Presenter presenter;
    private MyMapWidget mapWidget;
    private Marker cameraMarker;
    private Marker alertMarker;
    private Marker restAreaMarker;
    private Marker calloutMarker;
    private Marker myLocationMarker;
    private Circle myLocationError;
    private static List<Marker> cameraMarkers = new ArrayList<Marker>();
    private static List<Marker> alertMarkers = new ArrayList<Marker>();
    private static List<Marker> restAreaMarkers = new ArrayList<Marker>();
    private static List<Marker> calloutMarkers = new ArrayList<Marker>();

    private static Storage localStorage = Storage.getLocalStorageIfSupported();
    private static StorageMap storageMap;

    public TrafficMapViewGwtImpl() {

        initWidget(uiBinder.createAndBindUi(this));

        accessibilityPrepare();

        if (MGWT.getOsDetection().isAndroid()) {
            leftFlexSpacer.setVisible(false);
        }

        if (localStorage != null) {
            storageMap = new StorageMap(localStorage);
            if (!storageMap.containsKey("KEY_SHOW_CAMERAS")) {
                localStorage.setItem("KEY_SHOW_CAMERAS", "true"); // Set initial default value
            }
            if (!storageMap.containsKey("KEY_SHOW_RESTAREAS")) {
                localStorage.setItem("KEY_SHOW_RESTAREAS", "false"); // Set initial default value
            }

            // Set initial default location and zoom to Seattle area.
            if (!storageMap.containsKey("KEY_MAP_LAT")) {
                localStorage.setItem("KEY_MAP_LAT", "47.5990");
            }
            if (!storageMap.containsKey("KEY_MAP_LON")) {
                localStorage.setItem("KEY_MAP_LON", "-122.3350");
            }
            if (!storageMap.containsKey("KEY_MAP_ZOOM")) {
                localStorage.setItem("KEY_MAP_ZOOM", "12");
            }
        }

        final TrafficLayer trafficLayer = TrafficLayer.newInstance();

        LatLng center = LatLng.newInstance(Double.valueOf(localStorage.getItem("KEY_MAP_LAT")),
                Double.valueOf(localStorage.getItem("KEY_MAP_LON")));

        MapOptions opts = MapOptions.newInstance();
        opts.setMapTypeId(MapTypeId.ROADMAP);
        opts.setCenter(center);
        opts.setZoom(Integer.valueOf(localStorage.getItem("KEY_MAP_ZOOM"), 10));
        opts.setPanControl(false);
        opts.setZoomControl(false);
        opts.setMapTypeControl(false);
        opts.setScaleControl(false);
        opts.setStreetViewControl(false);
        opts.setOverviewMapControl(false);

        // Custom map style to remove all "Points of Interest" labels from map
        MapTypeStyle style1 = MapTypeStyle.newInstance();
        style1.setFeatureType(MapTypeStyleFeatureType.POI);
        style1.setElementType(MapTypeStyleElementType.LABELS);
        style1.setStylers(new MapTypeStyler[] { MapTypeStyler.newVisibilityStyler("off") });
        MapTypeStyle[] styles = { style1 };

        opts.setMapTypeStyles(styles);

        mapWidget = new MyMapWidget(opts);
        trafficLayer.setMap(mapWidget);
        flowPanel.add(mapWidget);

        mapWidget.setSize(Window.getClientWidth() + "px",
                (Window.getClientHeight() - ParserUtils.windowUI()) + "px");

        Window.addResizeHandler(new ResizeHandler() {
            @Override
            public void onResize(ResizeEvent event) {
                MapHandlerRegistration.trigger(mapWidget, MapEventType.RESIZE);

            }
        });

        mapWidget.addResizeHandler(new ResizeMapHandler() {
            @Override
            public void onEvent(ResizeMapEvent event) {
                mapWidget.setSize(Window.getClientWidth() + "px",
                        (Window.getClientHeight() - ParserUtils.windowUI()) + "px");
            }
        });

        mapWidget.addIdleHandler(new IdleMapHandler() {

            @Override
            public void onEvent(IdleMapEvent event) {
                localStorage.setItem("KEY_MAP_LAT", String.valueOf(mapWidget.getCenter().getLatitude()));
                localStorage.setItem("KEY_MAP_LON", String.valueOf(mapWidget.getCenter().getLongitude()));
                localStorage.setItem("KEY_MAP_ZOOM", String.valueOf(mapWidget.getZoom()));

                if (presenter != null) {
                    presenter.onMapIsIdle();
                }
            }
        });
    }

    @UiHandler("backButton")
    protected void onBackButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onBackButtonPressed();
        }
    }

    @UiHandler("cameraButton")
    protected void onCameraButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onCameraButtonPressed(Boolean.valueOf(localStorage.getItem("KEY_SHOW_CAMERAS")));
        }
    }

    @UiHandler("menuButton")
    protected void onMenuButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onMenuButtonPressed();
        }
    }

    @UiHandler("alertsButton")
    protected void onAlertsButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onTrafficAlertsButtonPressed(getViewportBounds());
        }
    }

    @UiHandler("navigationButton")
    protected void onLocateButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onLocateButtonPressed();
        }
    }

    @UiHandler("starButton")
    protected void onStarButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onStarButtonPressed();
        }
    }

    @UiHandler("refreshButton")
    protected void onRefreshMapButtonPressed(TapEvent event) {
        if (presenter != null) {
            presenter.onRefreshMapButtonPressed();
        }
    }

    @Override
    public void showProgressIndicator() {
        progressIndicator.setVisible(true);
    }

    @Override
    public void hideProgressIndicator() {
        progressIndicator.setVisible(false);
    }

    @Override
    public void setPresenter(Presenter presenter) {
        this.presenter = presenter;
    }

    @Override
    public LatLngBounds getViewportBounds() {
        return mapWidget.getBounds();
    }

    @Override
    public void drawCameras(final List<CameraItem> cameras) {

        deleteCameras();

        for (final CameraItem camera : cameras) {
            final LatLng loc = LatLng.newInstance(camera.getLatitude(), camera.getLongitude());
            MarkerOptions options = MarkerOptions.newInstance();
            options.setPosition(loc);

            MarkerImage icon;

            if (camera.getHasVideo() == 1) {
                icon = MarkerImage.newInstance(AppBundle.INSTANCE.cameraVideoPNG().getSafeUri().asString());
            } else {
                icon = MarkerImage.newInstance(AppBundle.INSTANCE.cameraPNG().getSafeUri().asString());
            }

            options.setIcon(icon);

            cameraMarker = Marker.newInstance(options);
            cameraMarker.addClickHandler(new ClickMapHandler() {

                @Override
                public void onEvent(ClickMapEvent event) {
                    presenter.onCameraSelected(camera.getCameraId());
                }
            });

            cameraMarkers.add(cameraMarker);
        }

        if (Boolean.valueOf(localStorage.getItem("KEY_SHOW_CAMERAS"))) {
            showCameras();
        }
    }

    @Override
    public void drawAlerts(List<HighwayAlertItem> alerts) {

        // Types of categories which result in one icon or another being displayed. 
        String[] eventClosure = { "closed", "closure" };
        String[] eventConstruction = { "construction", "maintenance", "lane closure" };

        HashMap<String, String[]> eventCategories = new HashMap<String, String[]>();
        eventCategories.put("closure", eventClosure);
        eventCategories.put("construction", eventConstruction);

        deleteAlerts();

        for (final HighwayAlertItem alert : alerts) {
            LatLng loc = LatLng.newInstance(alert.getStartLatitude(), alert.getStartLongitude());
            MarkerOptions options = MarkerOptions.newInstance();
            options.setPosition(loc);

            // Determine correct icon for alert type
            ImageResource imageResource = getCategoryIcon(eventCategories, alert.getEventCategory(),
                    alert.getPriority());

            MarkerImage icon = MarkerImage.newInstance(imageResource.getSafeUri().asString());
            options.setIcon(icon);

            alertMarker = Marker.newInstance(options);
            alertMarker.addClickHandler(new ClickMapHandler() {

                @Override
                public void onEvent(ClickMapEvent event) {
                    presenter.onAlertSelected(alert.getAlertId());
                }
            });

            alertMarkers.add(alertMarker);

        }

        showAlerts();
    }

    @Override
    public void drawRestAreas(List<RestAreaItem> restAreas) {

        ImageResource noDumpSiteIcon = AppBundle.INSTANCE.restAreaPNG();
        ImageResource dumpSiteIcon = AppBundle.INSTANCE.restAreaDumpPNG();

        deleteRestAreas();

        for (final RestAreaItem restArea : restAreas) {
            LatLng loc = LatLng.newInstance(Double.valueOf(restArea.getLatitude()),
                    Double.valueOf(restArea.getLongitude()));
            MarkerOptions options = MarkerOptions.newInstance();
            options.setPosition(loc);

            // Determine correct icon for restarea type
            ImageResource imageResource = restArea.hasDump() ? dumpSiteIcon : noDumpSiteIcon;

            MarkerImage icon = MarkerImage.newInstance(imageResource.getSafeUri().asString());
            options.setIcon(icon);

            restAreaMarker = Marker.newInstance(options);
            restAreaMarker.addClickHandler(new ClickMapHandler() {
                @Override
                public void onEvent(ClickMapEvent event) {
                    presenter.onRestAreaSelected(restArea.getId());
                }
            });
            restAreaMarkers.add(restAreaMarker);
        }

        if (Boolean.valueOf(localStorage.getItem("KEY_SHOW_RESTAREAS"))) {
            showRestAreas();
        }
    }

    @Override
    public void drawCallouts(List<CalloutItem> callouts) {
        deleteCallouts();

        for (final CalloutItem callout : callouts) {
            LatLng loc = LatLng.newInstance(callout.getLatitude(), callout.getLongitude());
            MarkerOptions options = MarkerOptions.newInstance();
            options.setPosition(loc);

            MarkerImage icon = MarkerImage.newInstance(AppBundle.INSTANCE.jblmPNG().getSafeUri().asString());

            options.setIcon(icon);

            calloutMarker = Marker.newInstance(options);
            calloutMarker.addClickHandler(new ClickMapHandler() {
                @Override
                public void onEvent(ClickMapEvent event) {
                    presenter.onCalloutSelected(callout.getImageUrl());
                }
            });

            calloutMarkers.add(calloutMarker);
        }

        showCallouts();
    }

    @Override
    public void hideCameras() {
        for (Marker marker : cameraMarkers) {
            marker.setMap((MapWidget) null);
        }

        localStorage.setItem("KEY_SHOW_CAMERAS", "false");
    }

    @Override
    public void showCameras() {
        for (Marker marker : cameraMarkers) {
            marker.setMap(mapWidget);
        }

        localStorage.setItem("KEY_SHOW_CAMERAS", "true");
    }

    @Override
    public void hideRestAreas() {
        for (Marker marker : restAreaMarkers) {
            marker.setMap((MapWidget) null);
        }

        localStorage.setItem("KEY_SHOW_RESTAREAS", "false");
    }

    @Override
    public void showRestAreas() {
        for (Marker marker : restAreaMarkers) {
            marker.setMap(mapWidget);
        }

        localStorage.setItem("KEY_SHOW_RESTAREAS", "true");
    }

    @Override
    public MapWidget getMapWidget() {
        return mapWidget;
    }

    @Override
    public void deleteCameras() {
        for (Marker marker : cameraMarkers) {
            marker.setMap((MapWidget) null);
        }

        cameraMarkers.clear();
    }

    @Override
    public void setMapLocation(double latitude, double longitude, int zoom) {
        LatLng latLng = LatLng.newInstance(latitude, longitude);

        mapWidget.panTo(latLng);
        mapWidget.setZoom(zoom);
    }

    @Override
    public void setMapLocation() {
        LatLng center = LatLng.newInstance(Double.valueOf(localStorage.getItem("KEY_MAP_LAT")),
                Double.valueOf(localStorage.getItem("KEY_MAP_LON")));

        int zoom = Integer.valueOf(localStorage.getItem("KEY_MAP_ZOOM"), 10);

        mapWidget.panTo(center);
        mapWidget.setZoom(zoom);
    }

    @Override
    public void addMapMarker(Position position) {

        if (myLocationMarker != null) {
            myLocationMarker.setMap((MapWidget) null);
        }

        if (myLocationError != null) {
            myLocationError.setMap(null);
        }

        LatLng center = LatLng.newInstance(position.getCoordinates().getLatitude(),
                position.getCoordinates().getLongitude());
        MarkerOptions options = MarkerOptions.newInstance();
        options.setPosition(center);
        MarkerImage icon = MarkerImage.newInstance(AppBundle.INSTANCE.myLocationPNG().getSafeUri().asString());
        icon.setAnchor(Point.newInstance(11, 11));
        options.setOptimized(true);
        options.setIcon(icon);
        myLocationMarker = Marker.newInstance(options);
        myLocationMarker.setMap(mapWidget);

        // create a circle the size of the error
        CircleOptions circleOptions = CircleOptions.newInstance();
        circleOptions.setFillOpacity(0.1);
        circleOptions.setFillColor("#1a75ff");
        circleOptions.setStrokeOpacity(0.12);
        circleOptions.setStrokeWeight(1);
        circleOptions.setStrokeColor("#1a75ff");
        myLocationError = Circle.newInstance(circleOptions);
        myLocationError.setCenter(center);
        myLocationError.setRadius(position.getCoordinates().getAccuracy());
        myLocationError.setMap(mapWidget);

    }

    @Override
    public void hideAlerts() {
        for (Marker marker : alertMarkers) {
            marker.setMap((MapWidget) null);
        }
    }

    @Override
    public void showAlerts() {
        for (Marker marker : alertMarkers) {
            marker.setMap(mapWidget);
        }
    }

    @Override
    public void deleteAlerts() {
        for (Marker marker : alertMarkers) {
            marker.setMap((MapWidget) null);
        }

        alertMarkers.clear();
    }

    @Override
    public void deleteRestAreas() {
        for (Marker marker : restAreaMarkers) {
            marker.setMap((MapWidget) null);
        }

        restAreaMarkers.clear();
    }

    @Override
    public void deleteCallouts() {
        for (Marker marker : calloutMarkers) {
            marker.setMap((MapWidget) null);
        }

        calloutMarkers.clear();
    }

    @Override
    public void showCallouts() {
        for (Marker marker : calloutMarkers) {
            marker.setMap(mapWidget);
        }
    }

    /**
     * Refresh the map to update the traffic layer.
     * 
     * Force the traffic layer to update by zooming in and then out
     * of the map.
     */
    @Override
    public void refreshMap() {
        mapWidget.setZoom(mapWidget.getZoom() + 1);
        new Timer() {

            @Override
            public void run() {
                mapWidget.setZoom(mapWidget.getZoom() - 1);
            }
        }.schedule(1);
    }

    /**
     * Get the correct icon given the priority and category of alert.
     * 
     * @param eventCategories
     * @param category
     * @param priority
     * @return
     */
    private ImageResource getCategoryIcon(HashMap<String, String[]> eventCategories, String category,
            String priority) {

        ImageResource alertClosed = AppBundle.INSTANCE.closedPNG();
        ImageResource alertHighest = AppBundle.INSTANCE.alertHighestPNG();
        ImageResource alertHigh = AppBundle.INSTANCE.alertHighPNG();
        ImageResource alertMedium = AppBundle.INSTANCE.alertModeratePNG();
        ImageResource alertLow = AppBundle.INSTANCE.alertLowPNG();
        ImageResource constructionHighest = AppBundle.INSTANCE.constructionHighestPNG();
        ImageResource constructionHigh = AppBundle.INSTANCE.constructionHighPNG();
        ImageResource constructionMedium = AppBundle.INSTANCE.constructionModeratePNG();
        ImageResource constructionLow = AppBundle.INSTANCE.constructionLowPNG();
        ImageResource defaultAlertImage = alertHighest;

        Set<Entry<String, String[]>> set = eventCategories.entrySet();
        Iterator<Entry<String, String[]>> i = set.iterator();

        if (category.equals(""))
            return defaultAlertImage;

        while (i.hasNext()) {
            Entry<String, String[]> me = i.next();
            for (String phrase : (String[]) me.getValue()) {
                String patternStr = phrase;
                RegExp pattern = RegExp.compile(patternStr, "i");
                MatchResult matcher = pattern.exec(category);
                boolean matchFound = matcher != null;

                if (matchFound) {
                    String keyWord = me.getKey();

                    if (keyWord.equalsIgnoreCase("closure")) {
                        return alertClosed;
                    } else if (keyWord.equalsIgnoreCase("construction")) {
                        if (priority.equalsIgnoreCase("highest")) {
                            return constructionHighest;
                        } else if (priority.equalsIgnoreCase("high")) {
                            return constructionHigh;
                        } else if (priority.equalsIgnoreCase("medium")) {
                            return constructionMedium;
                        } else if (priority.equalsIgnoreCase("low") || priority.equalsIgnoreCase("lowest")) {
                            return constructionLow;
                        }
                    }
                }
            }
        }

        // If we arrive here, it must be an accident or alert item.
        if (priority.equalsIgnoreCase("highest")) {
            return alertHighest;
        } else if (priority.equalsIgnoreCase("high")) {
            return alertHigh;
        } else if (priority.equalsIgnoreCase("medium")) {
            return alertMedium;
        } else if (priority.equalsIgnoreCase("low") || priority.equalsIgnoreCase("lowest")) {
            return alertLow;
        }

        return defaultAlertImage;
    }

    /**
     * Custom MapWidget for map not resizing issue.
     * 
     * @author Kostas Patakas https://plus.google.com/112436396950239118116
     *
     * When the onAttach occurs and animation exists meaning that the div started
     * smaller and increases in size, but the map has already attached. At the end
     * of the animation, the map doesn't auto resize.
     *
     */
    private class MyMapWidget extends MapWidget {

        public MyMapWidget(MapOptions options) {
            super(options);
        }

        @Override
        protected void onAttach() {
            super.onAttach();
            Timer timer = new Timer() {

                @Override
                public void run() {
                    resize();
                }
            };
            timer.schedule(5);
        }

        /**
         * This method is called to fix the Map loading issue when opening
         * multiple instances of maps in different tabs
         * Triggers a resize event to be consumed by google api in order to resize view
         * after attach.
         *
         */
        public void resize() {
            LatLng center = this.getCenter();
            MapHandlerRegistration.trigger(this, MapEventType.RESIZE);
            this.setCenter(center);
        }
    }

    private void accessibilityPrepare() {

        // Add ARIA roles for accessibility
        Roles.getButtonRole().set(backButton.getElement());
        Roles.getButtonRole().setAriaLabelProperty(backButton.getElement(), "back");

        Roles.getButtonRole().set(menuButton.getElement());
        Roles.getButtonRole().setAriaLabelProperty(menuButton.getElement(), "more options");
        Roles.getButtonRole().set(alertsButton.getElement());
        Roles.getButtonRole().setAriaLabelProperty(alertsButton.getElement(), "alerts");

        Roles.getHeadingRole().set(heading.getElement());
    }
}