org.sigmah.client.page.entry.SiteMap.java Source code

Java tutorial

Introduction

Here is the source code for org.sigmah.client.page.entry.SiteMap.java

Source

/*
 * All Sigmah code is released under the GNU General Public License v3
 * See COPYRIGHT.txt and LICENSE.txt.
 */

package org.sigmah.client.page.entry;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.sigmah.client.EventBus;
import org.sigmah.client.dispatch.Dispatcher;
import org.sigmah.client.dispatch.monitor.MaskingAsyncMonitor;
import org.sigmah.client.event.SiteEvent;
import org.sigmah.client.i18n.I18N;
import org.sigmah.client.icon.IconImageBundle;
import org.sigmah.client.map.AdminBoundsHelper;
import org.sigmah.client.map.GcIconFactory;
import org.sigmah.client.map.MapApiLoader;
import org.sigmah.client.map.MapTypeFactory;
import org.sigmah.client.page.common.Shutdownable;
import org.sigmah.shared.command.GetSitePoints;
import org.sigmah.shared.command.result.SitePointList;
import org.sigmah.shared.dao.Filter;
import org.sigmah.shared.dto.ActivityDTO;
import org.sigmah.shared.dto.BoundingBoxDTO;
import org.sigmah.shared.dto.CountryDTO;
import org.sigmah.shared.dto.SiteDTO;
import org.sigmah.shared.dto.SitePointDTO;

import com.ebessette.maps.core.client.overlay.MarkerManagerImpl;
import com.ebessette.maps.core.client.overlay.OverlayManagerOptions;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.dnd.DropTarget;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.DNDEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MenuEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.Record;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Html;
import com.extjs.gxt.ui.client.widget.Status;
import com.extjs.gxt.ui.client.widget.layout.CenterLayout;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.extjs.gxt.ui.client.widget.menu.MenuItem;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.google.gwt.maps.client.MapType;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.SmallMapControl;
import com.google.gwt.maps.client.event.MapClickHandler;
import com.google.gwt.maps.client.event.MapRightClickHandler;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.geom.LatLngBounds;
import com.google.gwt.maps.client.geom.Point;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.maps.client.overlay.MarkerOptions;
import com.google.gwt.maps.client.overlay.Overlay;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;

/**
 * A map panel that serves a counterpart to the SiteGrid, and
 * a drop target for <code>SiteDTO</code>.
 * <p/>
 *
 * @author Alex Bertram (akbertram@gmail.com)
 */
public class SiteMap extends ContentPanel implements Shutdownable {

    private final EventBus eventBus;
    private final Dispatcher service;
    private Filter filter;

    private MapWidget map = null;
    private LatLngBounds pendingZoom = null;

    /**
     * Efficiently handles a large number of markers
     */
    private MarkerManagerImpl markerMgr;

    /**
     * Maps markers to site ids
     */
    private Map<Marker, Integer> markerIds;

    /**
     * Maps siteIds to markers
     */
    private Map<Integer, Marker> sites;

    private Listener<SiteEvent> siteListener;

    private Marker currentHighlightedMarker;
    private Marker highlitMarker;

    private Menu contextMenu;

    private CountryDTO country;

    private Status status;

    @Inject
    public SiteMap(EventBus eventBus, Dispatcher service) {
        this.eventBus = eventBus;
        this.service = service;

        setHeaderVisible(false);

        status = new Status();
        ToolBar statusBar = new ToolBar();
        statusBar.add(status);
        setBottomComponent(statusBar);
    }

    public void loadSites(ActivityDTO activity) {
        loadSites(activity.getDatabase().getCountry(), Filter.filter().onActivity(activity.getId()));
    }

    /**
     * Loads the sites for the given Activity
     * 
     * @param activity
     */
    public void loadSites(CountryDTO country, Filter filter) {
        this.country = country;
        this.filter = filter;
        if (map == null) {
            loadMap();
        } else {
            doLoadSites();
        }
    }

    private void onSiteChanged(SiteDTO site) {

    }

    private void onSiteCreated(SiteDTO site) {

    }

    private void onSiteSelected(SiteEvent se) {
        if (se.getSource() != this) {
            if (se.getEntity() != null && !se.getEntity().hasCoords()) {
                BoundingBoxDTO bounds = AdminBoundsHelper.calculate(country, se.getEntity());
                LatLngBounds llBounds = llBoundsForBounds(bounds);

                if (!llBounds.containsBounds(map.getBounds())) {
                    zoomToBounds(llBounds);
                }
            } else {
                highlightSite(se.getSiteId(), true);
            }
        }
    }

    private CountryDTO getCountry() {
        return country;
    }

    public void shutdown() {
        eventBus.removeListener(SiteEvent.SELECTED, siteListener);
        eventBus.removeListener(SiteEvent.CREATED, siteListener);
        eventBus.removeListener(SiteEvent.UPDATED, siteListener);
    }

    public void onSiteDropped(Record record, double lat, double lng) {
        record.set("x", lng);
        record.set("y", lat);
        updateSiteCoords(((SiteDTO) record.getModel()).getId(), lat, lng);

    }

    public BoundingBoxDTO getSiteBounds(SiteDTO site) {
        return AdminBoundsHelper.calculate(getCountry(), site);
    }

    private void loadMap() {
        status.setBusy(I18N.CONSTANTS.loadingGoogleMaps());
        MapApiLoader.load(new MaskingAsyncMonitor(this, I18N.CONSTANTS.loadingComponent()),
                new AsyncCallback<Void>() {

                    @Override
                    public void onFailure(Throwable throwable) {
                        removeAll();
                        setLayout(new CenterLayout());
                        add(new Html(I18N.CONSTANTS.connectionProblem()));
                        layout();
                    }

                    @Override
                    public void onSuccess(Void result) {
                        removeAll();

                        BoundingBoxDTO countryBounds = country.getBounds();
                        LatLng boundsFromActivity = LatLng.newInstance(countryBounds.getCenterY(),
                                countryBounds.getCenterX());
                        map = new MapWidget(boundsFromActivity, 8);

                        MapType adminMap = MapTypeFactory.createLocalisationMapType(country);
                        map.addMapType(adminMap);
                        map.setCurrentMapType(adminMap);
                        map.addControl(new SmallMapControl());

                        setLayout(new FitLayout());
                        add(map);

                        map.addMapClickHandler(new MapClickHandler() {
                            @Override
                            public void onClick(MapClickEvent event) {
                                if (event.getOverlay() != null) {
                                    int siteId = siteIdFromOverlay(event.getOverlay());
                                    highlightSite(siteId, false);
                                }
                            }
                        });
                        map.addMapRightClickHandler(new MapRightClickHandler() {
                            public void onRightClick(MapRightClickEvent event) {
                                if (event.getOverlay() != null) {
                                    showContextMenu(event);
                                }
                            }
                        });

                        // Listen for when this component is resized/layed out
                        // to assure that map widget is properly restated
                        Listener<BaseEvent> resizeListener = new Listener<BaseEvent>() {

                            @Override
                            public void handleEvent(BaseEvent be) {

                                map.checkResizeAndCenter();

                                if (pendingZoom != null) {
                                    zoomToBounds(pendingZoom);
                                }
                            }
                        };

                        addListener(Events.AfterLayout, resizeListener);
                        addListener(Events.Resize, resizeListener);

                        new MapDropTarget(SiteMap.this);

                        layout();

                        doLoadSites();
                    }
                });
    }

    private void doLoadSites() {
        status.setBusy(I18N.CONSTANTS.loading());
        service.execute(new GetSitePoints(filter), null, new AsyncCallback<SitePointList>() {
            @Override
            public void onFailure(Throwable throwable) {
                status.clearStatus(I18N.CONSTANTS.serverError());
            }

            @Override
            public void onSuccess(SitePointList points) {
                status.clearStatus(I18N.MESSAGES.siteLoadStatus(Integer.toString(points.getPoints().size()),
                        Integer.toString(points.getWithoutCoordinates())));

                addSitesToMap(points);

                siteListener = new Listener<SiteEvent>() {
                    public void handleEvent(SiteEvent be) {
                        if (be.getType() == SiteEvent.SELECTED) {
                            onSiteSelected(be);
                        } else if (be.getType() == SiteEvent.CREATED) {
                            onSiteCreated(be.getEntity());
                        } else if (be.getType() == SiteEvent.UPDATED) {
                            onSiteChanged(be.getEntity());
                        }
                    }
                };

                eventBus.addListener(SiteEvent.SELECTED, siteListener);
                eventBus.addListener(SiteEvent.CREATED, siteListener);
                eventBus.addListener(SiteEvent.UPDATED, siteListener);
            }
        });
    }

    private int siteIdFromOverlay(Overlay overlay) {
        if (overlay == highlitMarker) {
            return markerIds.get(currentHighlightedMarker);
        } else {
            return markerIds.get(overlay);
        }
    }

    /**
     * Attempts to pan to the center of the bounds and
     * zoom to the necessary zoom level. If the map widget is not
     * rendered or is in a funk because of resizing, the zoom
     * is deferred until a Resize or AfterLayout event is received.
     *
     * @param bounds
     */
    private void zoomToBounds(LatLngBounds bounds) {

        int zoomLevel = map.getBoundsZoomLevel(bounds);
        if (zoomLevel == 0) {
            pendingZoom = bounds;
        } else {
            map.setCenter(bounds.getCenter(), zoomLevel);
            pendingZoom = null;
        }
    }

    public void addSitesToMap(final SitePointList points) {

        if (markerMgr == null) {
            OverlayManagerOptions options = new OverlayManagerOptions();
            options.setMaxZoom(map.getCurrentMapType().getMaximumResolution());

            markerMgr = new MarkerManagerImpl(map, options);
        } else {
            for (Marker marker : markerIds.keySet()) {
                markerMgr.removeMarker(marker);
            }
        }
        markerIds = new HashMap<Marker, Integer>();
        sites = new HashMap<Integer, Marker>();

        List<Marker> markers = new ArrayList<Marker>(points.getPoints().size());

        for (SitePointDTO point : points.getPoints()) {

            Marker marker = new Marker(LatLng.newInstance(point.getY(), point.getX()));
            markerIds.put(marker, point.getSiteId());
            sites.put(point.getSiteId(), marker);
            markers.add(marker);
        }

        markerMgr.addOverlays(markers, 0);
        DeferredCommand.addCommand(new Command() {

            @Override
            public void execute() {
                markerMgr.refresh();

                if (points.getPoints().size() == 1) {
                    SitePointDTO p = points.getPoints().get(0);
                    map.setCenter(LatLng.newInstance(p.getY(), p.getX()));
                } else {
                    zoomToBounds(llBoundsForBounds(points.getBounds()));
                }
            }
        });

    }

    private LatLngBounds llBoundsForBounds(BoundingBoxDTO bounds) {
        LatLngBounds llbounds = LatLngBounds.newInstance(LatLng.newInstance(bounds.getY2(), bounds.getX1()),
                LatLng.newInstance(bounds.getY1(), bounds.getX2()));
        return llbounds;
    }

    public void updateSiteCoords(int siteId, double lat, double lng) {
        Marker marker = sites.get(siteId);
        if (marker != null) {
            // update existing site
            marker.setLatLng(LatLng.newInstance(lat, lng));
        } else {
            // create new marker
            marker = new Marker(LatLng.newInstance(lat, lng));
            markerIds.put(marker, siteId);
            sites.put(siteId, marker);
            markerMgr.addOverlay(marker, 0);
        }
    }

    public void highlightSite(int siteId, boolean panTo) {
        Marker marker = sites.get(siteId);
        if (marker != null) {

            // we can't change the icon ( I don't think )
            // so we'll bring in a ringer for the selected site

            if (highlitMarker == null) {
                GcIconFactory iconFactory = new GcIconFactory();
                iconFactory.primaryColor = "#0000FF";
                MarkerOptions opts = MarkerOptions.newInstance();
                opts.setIcon(iconFactory.createMarkerIcon());
                highlitMarker = new Marker(marker.getLatLng(), opts);
                map.addOverlay(highlitMarker);
            } else {
                // make sure this marker is on top
                map.removeOverlay(highlitMarker);
                highlitMarker.setLatLng(marker.getLatLng());
                map.addOverlay(highlitMarker);
            }

            if (currentHighlightedMarker != null) {
                currentHighlightedMarker.setVisible(true);
            }
            currentHighlightedMarker = marker;
            currentHighlightedMarker.setVisible(false);

            if (!map.getBounds().containsLatLng(marker.getLatLng())) {
                map.panTo(marker.getLatLng());
            }
        } else {
            // no coords, un highlight existing marker
            if (currentHighlightedMarker != null) {
                currentHighlightedMarker.setVisible(true);
            }
            if (highlitMarker != null) {
                map.removeOverlay(highlitMarker);
                highlitMarker = null;
            }
        }
    }

    private void showContextMenu(MapRightClickHandler.MapRightClickEvent event) {
        if (contextMenu == null) {

            contextMenu = new Menu();
            contextMenu.add(new MenuItem(I18N.CONSTANTS.showInGrid(), IconImageBundle.ICONS.table(),
                    new SelectionListener<MenuEvent>() {
                        @Override
                        public void componentSelected(MenuEvent ce) {

                        }
                    }));
        }
        Marker marker = (Marker) event.getOverlay();
        contextMenu.show(event.getElement(), "tr");

    }

    private class MapDropTarget extends DropTarget {

        BoundingBoxDTO bounds;
        String boundsName;

        private MapDropTarget(Component target) {
            super(target);
        }

        @Override
        protected void onDragEnter(DNDEvent event) {

            SiteDTO site = getSite(event);
            if (site == null) {
                bounds = null;
            } else {
                bounds = AdminBoundsHelper.calculate(getCountry(), site);
                boundsName = AdminBoundsHelper.name(getCountry(), bounds, site);
            }
            updateDragStatus(event);
        }

        @Override
        protected void onDragMove(DNDEvent event) {
            if (bounds != null) {
                updateDragStatus(event);
            }
        }

        private void updateDragStatus(DNDEvent event) {

            if (bounds == null) {
                // not a site that's being dragged
                event.setCancelled(true);
                event.getStatus().setStatus(false);
            } else {
                LatLng latlng = getLatLng(event);
                if (bounds.contains(latlng.getLongitude(), latlng.getLatitude())) {
                    // a site that's within its bounds
                    event.setCancelled(false);
                    event.getStatus().setStatus(true);
                    event.getStatus().update(GXT.MESSAGES.messageBox_ok());
                } else {
                    // a site that's outside it's bounds
                    event.setCancelled(true);
                    event.getStatus().setStatus(false);
                    event.getStatus().update(I18N.MESSAGES.coordOutsideBounds(boundsName));
                }
            }
        }

        @Override
        protected void onDragDrop(DNDEvent event) {

            if (bounds != null) {
                LatLng latlng = getLatLng(event);
                if (bounds.contains(latlng.getLongitude(), latlng.getLatitude())) {
                    event.setCancelled(false);
                    onSiteDropped((Record) event.getData(), latlng.getLatitude(), latlng.getLongitude());
                }
            }
        }

        private LatLng getLatLng(DNDEvent event) {
            int x = event.getClientX() - map.getElement().getAbsoluteLeft();
            int y = event.getClientY() - map.getElement().getAbsoluteTop();
            return map.convertContainerPixelToLatLng(Point.newInstance(x, y));
        }

        private SiteDTO getSite(DNDEvent event) {
            if (event.getData() instanceof Record) {
                Record record = (Record) event.getData();
                if (record.getModel() instanceof SiteDTO) {
                    return (SiteDTO) record.getModel();
                }
            }
            return null;
        }

    }

}