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());
}
}
}