/* * This is part of Geomajas, a GIS framework, http://www.geomajas.org/. * * Copyright 2008-2015 Geosparc nv, http://www.geosparc.com/, Belgium. * * The program is available in open source according to the GNU Affero * General Public License. All contributions in this program are covered * by the Geomajas Contributors License Agreement. For full licensing * details, see LICENSE.txt in the project root. */ package org.geomajas.gwt.client.controller; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import org.geomajas.command.dto.SearchByLocationRequest; import org.geomajas.command.dto.SearchByLocationResponse; import org.geomajas.gwt.client.action.menu.DeselectAllAction; import org.geomajas.gwt.client.action.menu.ToggleSelectionAction; import org.geomajas.gwt.client.command.AbstractCommandCallback; import org.geomajas.gwt.client.command.GwtCommand; import org.geomajas.gwt.client.command.GwtCommandDispatcher; import org.geomajas.gwt.client.map.MapModel; import org.geomajas.gwt.client.map.feature.Feature; import org.geomajas.gwt.client.map.layer.Layer; import org.geomajas.gwt.client.map.layer.VectorLayer; import org.geomajas.gwt.client.spatial.Bbox; import org.geomajas.gwt.client.spatial.geometry.Polygon; import org.geomajas.gwt.client.util.GeometryConverter; import org.geomajas.gwt.client.widget.MapWidget; import org.geomajas.gwt.client.widget.MapWidget.RenderGroup; import org.geomajas.gwt.client.widget.MapWidget.RenderStatus; import com.google.gwt.event.dom.client.MouseUpEvent; import com.smartgwt.client.widgets.menu.Menu; /** * Allow selection of features, either by clicking on individual features, or by dragging a rectangle and selecting all * features which (for a certain percentage) fall into the indicated area. * * @author Joachim Van der Auwera * @author Pieter De Graef */ public class SelectionController extends AbstractRectangleController { private boolean shiftOrCtrl; private Menu menu; private int clickTimeout; // timeout in milliseconds for handling as click versus dragging private float coverageRatio; // coverage percentage which is used to determine a feature as selected private boolean priorityToSelectedLayer; // give priority to the selected layer on when toggling private int pixelTolerance; // ------------------------------------------------------------------------- // Constructors: // ------------------------------------------------------------------------- /** * Create a new selection controller, that controls selection on the given map through user interaction. * * @param mapWidget * The map onto whom you want to control selection. * @param clickTimeout * Timeout in milliseconds for handling as click versus dragging. * @param coverageRatio * Coverage percentage which is used to determine a feature as selected. This is only used when dragging * a rectangle to select in. Must be a floating value between 0 and 1. * @param priorityToSelectedLayer * Activate or disable priority to the selected layer. This works only if there is a selected layer, and * that selected layer is a {@link VectorLayer}. In all other cases, the selection toggle will occur on * the first object that is encountered. In other words it will depend on the layer drawing order, * starting at the top. * @param pixelTolerance * Number of pixels that describes the tolerance allowed when trying to select features. */ public SelectionController(MapWidget mapWidget, int clickTimeout, float coverageRatio, boolean priorityToSelectedLayer, int pixelTolerance) { super(mapWidget); this.clickTimeout = clickTimeout; this.coverageRatio = coverageRatio; this.priorityToSelectedLayer = priorityToSelectedLayer; this.pixelTolerance = pixelTolerance; } // ------------------------------------------------------------------------- // GraphicsController implementation: // ------------------------------------------------------------------------- @Override public void onActivate() { menu = new Menu(); menu.addItem(new ToggleSelectionAction(mapWidget, pixelTolerance)); menu.addItem(new DeselectAllAction(mapWidget.getMapModel())); mapWidget.setContextMenu(menu); } @Override public void onDeactivate() { super.onDeactivate(); onDoubleClick(null); menu.destroy(); menu = null; mapWidget.setContextMenu(null); if (dragging) { dragging = false; mapWidget.render(rectangle, RenderGroup.SCREEN, RenderStatus.DELETE); } } /** * First mouse button: publish a toggle event on the selection topic for the referenced MapWidget. Second mouse * button opens a menu. * * @param event * event */ @Override public void onMouseUp(MouseUpEvent event) { // handle click if any? if (dragging) { // shift or CTRL is used when depressed either at beginning or end: shiftOrCtrl = (event.isShiftKeyDown() || event.isControlKeyDown()); if (timestamp + clickTimeout > new Date().getTime() && (bounds.getWidth() < pixelTolerance || bounds.getHeight() < pixelTolerance)) { stopDragging(); // click behavior instead of drag ToggleSelectionAction action = new ToggleSelectionAction(mapWidget, priorityToSelectedLayer, pixelTolerance); action.toggle(getScreenPosition(event), !shiftOrCtrl); return; } } // normal "rectangle" handling super.onMouseUp(event); } // ------------------------------------------------------------------------- // Getters and setters: // ------------------------------------------------------------------------- /** * Coverage percentage which is used to determine a feature as selected. This is only used when dragging a rectangle * to select in. Must be a floating value between 0 and 1. * * @return coverage ratio */ public float getCoverageRatio() { return coverageRatio; } /** * Set a new coverage percentage which is used to determine a feature as selected. This is only used when dragging a * rectangle to select in. Must be a floating value between 0 and 1. * * @param coverageRatio coverage ratio */ public void setCoverageRatio(float coverageRatio) { this.coverageRatio = coverageRatio; } /** * Will priority be given to the selected layer? This works only if there is a selected layer, and that selected * layer is a {@link VectorLayer}. In all other cases, the selection toggle will occur on the first object that is * encountered. In other words it will depend on the layer drawing order, starting at the top. * * @return true when selected layer has priority */ public boolean isPriorityToSelectedLayer() { return priorityToSelectedLayer; } /** * Activate or disable priority to the selected layer. This works only if there is a selected layer, and that * selected layer is a {@link VectorLayer}. In all other cases, the selection toggle will occur on the first object * that is encountered. In other words it will depend on the layer drawing order, starting at the top. * * @param priorityToSelectedLayer should selected layer have priority */ public void setPriorityToSelectedLayer(boolean priorityToSelectedLayer) { this.priorityToSelectedLayer = priorityToSelectedLayer; } /** * Number of pixels that describes the tolerance allowed when trying to select features. * * @return pixel tolerance */ public int getPixelTolerance() { return pixelTolerance; } /** * Number of pixels that describes the tolerance allowed when trying to select features. * * @param pixelTolerance * The new value. */ public void setPixelTolerance(int pixelTolerance) { this.pixelTolerance = pixelTolerance; } // ------------------------------------------------------------------------- // Private and protected methods: // ------------------------------------------------------------------------- @Override protected void selectRectangle(Bbox selectedArea) { // we can clear here ! if (!shiftOrCtrl) { MapModel mapModel = mapWidget.getMapModel(); mapModel.clearSelectedFeatures(); } GwtCommand commandRequest = new GwtCommand(SearchByLocationRequest.COMMAND); SearchByLocationRequest request = new SearchByLocationRequest(); request.setLayerIds(getSelectionLayerIds()); for (Layer<?> layer : mapWidget.getMapModel().getLayers()) { if (layer.isShowing() && layer instanceof VectorLayer) { request.setFilter(layer.getServerLayerId(), ((VectorLayer) layer).getFilter()); } } Polygon polygon = mapWidget.getMapModel().getGeometryFactory().createPolygon(selectedArea); request.setLocation(GeometryConverter.toDto(polygon)); request.setCrs(mapWidget.getMapModel().getCrs()); request.setQueryType(SearchByLocationRequest.QUERY_INTERSECTS); request.setRatio(coverageRatio); request.setSearchType(SearchByLocationRequest.SEARCH_ALL_LAYERS); request.setFeatureIncludes(GwtCommandDispatcher.getInstance().getLazyFeatureIncludesSelect()); commandRequest.setCommandRequest(request); GwtCommandDispatcher.getInstance().execute(commandRequest, new AbstractCommandCallback<SearchByLocationResponse>() { public void execute(SearchByLocationResponse response) { Map<String, List<org.geomajas.layer.feature.Feature>> featureMap = response.getFeatureMap(); for (String layerId : featureMap.keySet()) { selectFeatures(layerId, featureMap.get(layerId)); } } }); } private String[] getSelectionLayerIds() { Layer<?> layer = mapWidget.getMapModel().getSelectedLayer(); List<String> ids = new ArrayList<String>(); if (layer != null && layer instanceof VectorLayer) { ids.add(layer.getServerLayerId()); } else { for (VectorLayer v : mapWidget.getMapModel().getVectorLayers()) { if (v.isShowing()) { ids.add(v.getServerLayerId()); } } } return ids.toArray(new String[ids.size()]); } private void selectFeatures(String serverLayerId, List<org.geomajas.layer.feature.Feature> orgFeatures) { List<VectorLayer> layers = mapWidget.getMapModel().getVectorLayersByServerId(serverLayerId); Layer<?> selectedLayer = mapWidget.getMapModel().getSelectedLayer(); for (VectorLayer vectorLayer : layers) { if (isSelectionTargetLayer(vectorLayer, selectedLayer)) { for (org.geomajas.layer.feature.Feature orgFeature : orgFeatures) { Feature feature = new Feature(orgFeature, vectorLayer); vectorLayer.getFeatureStore().addFeature(feature); vectorLayer.selectFeature(feature); } } } } private boolean isSelectionTargetLayer(VectorLayer targetLayer, Layer<?> selectedLayer) { if (!targetLayer.isShowing()) { return false; } else { return !(priorityToSelectedLayer && null != selectedLayer) || (selectedLayer.equals(targetLayer)); } } }