package org.activityinfo.ui.client.page.entry.location; /* * #%L * ActivityInfo Server * %% * Copyright (C) 2009 - 2013 UNICEF * %% * 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/gpl-3.0.html>. * #L% */ import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.store.StoreEvent; import com.extjs.gxt.ui.client.store.StoreListener; import com.extjs.gxt.ui.client.widget.Html; import com.google.common.collect.Lists; import com.google.gwt.resources.client.ImageResource; import org.activityinfo.model.type.geo.AiLatLng; import org.activityinfo.legacy.shared.Log; import org.activityinfo.legacy.shared.model.LocationDTO; import org.activityinfo.legacy.shared.reports.content.MapboxLayers; import org.activityinfo.legacy.shared.reports.util.mapping.Extents; import org.activityinfo.ui.client.page.entry.form.resources.SiteFormResources; import org.activityinfo.ui.client.util.LeafletUtil; import org.discotools.gwt.leaflet.client.LeafletResourceInjector; import org.discotools.gwt.leaflet.client.Options; import org.discotools.gwt.leaflet.client.crs.epsg.EPSG3857; import org.discotools.gwt.leaflet.client.events.Event; import org.discotools.gwt.leaflet.client.events.MouseEvent; import org.discotools.gwt.leaflet.client.events.handler.EventHandler; import org.discotools.gwt.leaflet.client.events.handler.EventHandlerManager; import org.discotools.gwt.leaflet.client.layers.ILayer; import org.discotools.gwt.leaflet.client.layers.others.LayerGroup; import org.discotools.gwt.leaflet.client.layers.raster.TileLayer; import org.discotools.gwt.leaflet.client.map.Map; import org.discotools.gwt.leaflet.client.map.MapOptions; import org.discotools.gwt.leaflet.client.marker.Marker; import org.discotools.gwt.leaflet.client.types.*; import java.util.List; public class LocationMap extends Html { private final LocationSearchPresenter searchPresenter; private final NewLocationPresenter newLocationPresenter; private LayerGroup markerLayer; private Marker newLocationMarker; private Map map; public LocationMap(LocationSearchPresenter presenter, NewLocationPresenter newLocationPresenter) { super(); this.searchPresenter = presenter; this.newLocationPresenter = newLocationPresenter; LeafletResourceInjector.ensureInjected(); setStyleName("gwt-Map"); setHtml("<div style=\"width:100%; height: 100%; position: relative;\"></div>"); } @Override protected void afterRender() { super.afterRender(); Extents countryBounds = searchPresenter.getCountryBounds(); MapOptions mapOptions = new MapOptions(); mapOptions.setCenter(new LatLng(countryBounds.getCenterY(), countryBounds.getCenterX())); mapOptions.setZoom(6); mapOptions.setProperty("crs", new EPSG3857()); TileLayer baseLayer = new TileLayer(MapboxLayers.MAPBOX_STREETS, new Options()); markerLayer = new LayerGroup(new ILayer[0]); map = new Map(getElement().getElementsByTagName("div").getItem(0), mapOptions); map.addLayer(baseLayer); map.addLayer(markerLayer); bindEvents(); } @Override protected void onResize(int width, int height) { super.onResize(width, height); map.invalidateSize(false); } private void bindEvents() { searchPresenter.getStore().addStoreListener(new StoreListener<LocationDTO>() { @Override public void storeDataChanged(StoreEvent<LocationDTO> event) { updateSearchMarkers(); } }); searchPresenter.addListener(Events.Select, new Listener<LocationEvent>() { @Override public void handleEvent(LocationEvent event) { if (event.getSource() != LocationMap.this) { onLocationSelected(event.getLocation()); } } }); newLocationPresenter.addListener(NewLocationPresenter.ACTIVE_STATE_CHANGED, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { onModeChanged(); } }); newLocationPresenter.addListener(NewLocationPresenter.POSITION_CHANGED, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { onNewLocationPosChanged(); } }); newLocationPresenter.addListener(NewLocationPresenter.BOUNDS_CHANGED, new Listener<BaseEvent>() { @Override public void handleEvent(BaseEvent be) { if (newLocationPresenter.isActive()) { LatLngBounds newBounds = LeafletUtil.newLatLngBounds(newLocationPresenter.getBounds()); map.fitBounds(newBounds); } } }); } LatLngBounds previousBounds = null; /** * IMPORTANT : if call update map with the same bounds map will NOT look the same. It leads to destructive user experience. * For now just workaround the issue with checking whether the same bounds * was already set. * * @param bounds bounds */ private void updateMap(final LatLngBounds bounds) { //GWT.log("===> Bounds: " + bounds + ", eq:" + LeafletUtil.equals(bounds, previousBounds)); if (!LeafletUtil.equals(bounds, previousBounds)) { int effectiveZoom = Math.min(8, map.getBoundsZoom(bounds, false)); map.setView(bounds.getCenter(), effectiveZoom, false); map.fitBounds(bounds); previousBounds = new LatLngBounds(bounds.getJSObject()); } } private void updateSearchMarkers() { markerLayer.clearLayers(); List<LocationDTO> locations = Lists.reverse(searchPresenter.getStore().getModels()); LatLngBounds bounds = new LatLngBounds(); boolean empty = true; for (LocationDTO location : locations) { if (location.hasCoordinates()) { LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); Marker marker = createMarker(latLng, location.getMarker()); markerLayer.addLayer(marker); bounds.extend(latLng); bindClickEvent(location, marker); empty = false; } } if (!empty) { updateMap(bounds); } } private void bindClickEvent(final LocationDTO location, Marker marker) { EventHandlerManager.addEventHandler(marker, org.discotools.gwt.leaflet.client.events.handler.EventHandler.Events.click, new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { searchPresenter.select(this, location); } }); } private Marker createMarker(LatLng latLng, String label) { DivIcon icon = createIcon(label); Options markerOptions = new Options(); markerOptions.setProperty("icon", icon); Marker marker = new Marker(latLng, markerOptions); return marker; } private DivIcon createIcon(String label) { ImageResource markerImage = SiteFormResources.INSTANCE.blankMarker(); DivIconOptions iconOptions = new DivIconOptions(); iconOptions.setClassName(SiteFormResources.INSTANCE.style().locationMarker()); iconOptions.setIconSize(new Point(markerImage.getWidth(), markerImage.getHeight())); iconOptions.setIconAnchor(new Point(markerImage.getWidth() / 2, markerImage.getHeight())); iconOptions.setHtml(label); DivIcon icon = new DivIcon(iconOptions); return icon; } private void onLocationSelected(LocationDTO location) { if (location != null && location.hasCoordinates()) { map.panTo(new LatLng(location.getLatitude(), location.getLongitude())); } } private void onModeChanged() { if (newLocationPresenter.isActive()) { if (newLocationMarker == null) { createNewLocationMarker(); } newLocationMarker.setOpacity(1); panToNewLocation(); } else if (newLocationMarker != null) { newLocationMarker.setOpacity(0); } } private void createNewLocationMarker() { DivIcon icon = createIcon(""); Options markerOptions = new Options(); markerOptions.setProperty("icon", icon); markerOptions.setProperty("draggable", true); newLocationMarker = new Marker(newLatLng(newLocationPresenter.getLatLng()), markerOptions); EventHandlerManager.addEventHandler(newLocationMarker, org.discotools.gwt.leaflet.client.events.handler.EventHandler.Events.dragend, new EventHandler<Event>() { @Override public void handle(Event event) { newLocationPresenter.setLatLng(new AiLatLng(newLocationMarker.getLatLng().lat(), newLocationMarker.getLatLng().lng())); } }); map.addLayer(newLocationMarker); } private LatLng newLatLng(AiLatLng latLng) { return new LatLng(latLng.getLat(), latLng.getLng()); } private void onNewLocationPosChanged() { if (newLocationMarker != null) { Log.debug("New marker pos: " + newLocationPresenter.getLatLng()); newLocationMarker.setLatLng(newLatLng(newLocationPresenter.getLatLng())); } } private void panToNewLocation() { if (!map.getBounds().contains(newLocationMarker.getLatLng())) { map.panTo(newLocationMarker.getLatLng()); } } }