org.geomajas.gwt2.client.controller.FeatureSelectionController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of geomajas-client-gwt2-server-extension Show documentation
Show all versions of geomajas-client-gwt2-server-extension Show documentation
Geomajas GWT2 client: Main - Server Extension
/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2014 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.gwt2.client.controller;
import java.util.List;
import java.util.Map;
import org.geomajas.annotation.Api;
import org.geomajas.geometry.Bbox;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.Geometry;
import org.geomajas.geometry.service.GeometryService;
import org.geomajas.geometry.service.MathService;
import org.geomajas.gwt.client.map.RenderSpace;
import org.geomajas.gwt2.client.GeomajasServerExtension;
import org.geomajas.gwt2.client.map.MapPresenter;
import org.geomajas.gwt2.client.map.feature.Feature;
import org.geomajas.gwt2.client.map.feature.FeatureMapFunction;
import org.geomajas.gwt2.client.map.feature.ServerFeatureService.QueryType;
import org.geomajas.gwt2.client.map.feature.ServerFeatureService.SearchLayerType;
import org.geomajas.gwt2.client.map.layer.FeaturesSupported;
import com.google.gwt.event.dom.client.HumanInputEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseOutEvent;
/**
*
* Controller for selecting and deselecting features on the map. This controller extends the
* {@link NavigationController} and will therefore allow some forms of navigation, depending on the chosen
* {@link SelectionMethod}.
*
*
* Zooming through the mouse wheel or by double clicking as supported through the {@link NavigationController}. Other
* forms of navigation are dependent upon the {@link SelectionMethod}. The following modes are supported:
*
* - CLICK_ONLY: Click will select or deselect features. Dragging will pan the map. By pressing the SHIFT
* button additional features can be selected.
* - CLICK_AND_DRAG: This mode will allow the user to also draw a rectangle on the map (by dragging). When the
* user lets go of the mouse, all features with a ratio of 50% (can be changed) within the rectangle will be selected,
* all other deselected. If the SHIFT button is pressed, the features within the rectangle will be selected upon the
* current selection.
*
*
*
* @author Pieter De Graef
* @since 2.0.0
*/
@Api(allMethods = true)
public class FeatureSelectionController extends NavigationController {
/**
* Enumeration that decides how selection should behave.
*
* @author Pieter De Graef
*/
public enum SelectionMethod {
/**
* The user can select features only by clicking on the map. Selection by dragging a rectangle is not supported.
* Instead when dragging, the map will pan.
*/
CLICK_ONLY,
/**
* Support both clicking an selection by dragging a rectangle. Dragging will no longer pan the map.
*/
CLICK_AND_DRAG,
/** Allow only a single feature to be selected. */
SINGLE_SELECTION
}
private final SelectionRectangleController selectionRectangleController;
// Selection options:
private SelectionMethod selectionMethod = SelectionMethod.CLICK_ONLY;
private SearchLayerType searchLayerType = SearchLayerType.TOP_LAYER_ONLY;
private float intersectionRatio = 0.5f;
private int pixelTolerance = 5;
// Keeping track of panning versus clicking:
private double clickDelta = 2;
private Coordinate onDownLocation;
// ------------------------------------------------------------------------
// Constructor:
// ------------------------------------------------------------------------
/** Create a controller. */
public FeatureSelectionController() {
this(SelectionMethod.CLICK_ONLY);
}
/**
* Create this controller, using the given selection method.
*
* @param selectionMethod
* The preferred selection method.
*/
public FeatureSelectionController(SelectionMethod selectionMethod) {
super();
selectionRectangleController = new SelectionRectangleController();
setSelectionMethod(selectionMethod);
}
// ------------------------------------------------------------------------
// MapController implementation:
// ------------------------------------------------------------------------
@Override
public void onActivate(MapPresenter mapPresenter) {
// Activate all 3 controllers:
super.onActivate(mapPresenter);
this.mapPresenter = mapPresenter;
selectionRectangleController.onActivate(mapPresenter);
}
@Override
public void onMouseDown(MouseDownEvent event) {
switch (selectionMethod) {
case CLICK_AND_DRAG:
onDownLocation = getLocation(event, RenderSpace.SCREEN);
selectionRectangleController.onMouseDown(event);
break;
default:
super.onMouseDown(event);
}
}
@Override
public void onDown(HumanInputEvent> event) {
switch (selectionMethod) {
case CLICK_AND_DRAG:
selectionRectangleController.onDown(event);
break;
default:
onDownLocation = getLocation(event, RenderSpace.SCREEN);
if (!event.isShiftKeyDown() && !event.isControlKeyDown()) {
super.onDown(event);
}
}
}
@Override
public void onMouseMove(MouseMoveEvent event) {
switch (selectionMethod) {
case CLICK_AND_DRAG:
selectionRectangleController.onMouseMove(event);
break;
default:
if (!isDownPosition(event)) {
super.onMouseMove(event);
}
}
}
@Override
public void onDrag(HumanInputEvent> event) {
switch (selectionMethod) {
case CLICK_AND_DRAG:
selectionRectangleController.onDrag(event);
break;
default:
super.onDrag(event);
}
}
@Override
public void onUp(HumanInputEvent> event) {
switch (selectionMethod) {
case CLICK_AND_DRAG:
if (isDownPosition(event)) {
searchAtLocation(getLocation(event, RenderSpace.WORLD), event.isShiftKeyDown());
selectionRectangleController.cleanup();
} else {
selectionRectangleController.onUp(event);
}
break;
case CLICK_ONLY:
stopPanning(null);
if (!event.isShiftKeyDown() && !event.isControlKeyDown()) {
super.onUp(event);
}
if (isDownPosition(event)) {
searchAtLocation(getLocation(event, RenderSpace.WORLD), event.isShiftKeyDown());
}
break;
default:
stopPanning(null);
if (!event.isShiftKeyDown() && !event.isControlKeyDown()) {
super.onUp(event);
}
if (isDownPosition(event)) {
searchAtLocation(getLocation(event, RenderSpace.WORLD), false);
}
}
}
@Override
public void onMouseOut(MouseOutEvent event) {
super.onMouseOut(event);
if (selectionMethod == SelectionMethod.CLICK_AND_DRAG) {
selectionRectangleController.onMouseOut(event);
}
}
// ------------------------------------------------------------------------
// Getters and setters:
// ------------------------------------------------------------------------
/**
* Get the current selection method. This determines how the user can select features on the map.
*
* @return The current selection method.
*/
public SelectionMethod getSelectionMethod() {
return selectionMethod;
}
/**
* Change the way selection should occur.
*
* @param selectionMethod
* The new selection method to apply.
*/
public void setSelectionMethod(SelectionMethod selectionMethod) {
this.selectionMethod = selectionMethod;
}
// ------------------------------------------------------------------------
// Private methods:
// ------------------------------------------------------------------------
/**
* Is the event at the same location as the "down" event?
*
* @param event
* The event to check.
* @return true or false.
*/
private boolean isDownPosition(HumanInputEvent> event) {
if (onDownLocation != null) {
Coordinate location = getLocation(event, RenderSpace.SCREEN);
if (MathService.distance(onDownLocation, location) < clickDelta) {
return true;
}
}
return false;
}
/**
* Search for features at a certain location.
*
* @param location
* The location to check.
* @param isShift
* Is the shift button pressed down?
*/
private void searchAtLocation(Coordinate location, boolean isShift) {
Geometry point = new Geometry(Geometry.POINT, 0, -1);
point.setCoordinates(new Coordinate[] { location });
GeomajasServerExtension
.getInstance()
.getServerFeatureService()
.search(mapPresenter, point, pixelsToUnits(pixelTolerance), QueryType.INTERSECTS, searchLayerType, -1,
new SelectionCallback(isShift, false));
}
/**
* Transform a pixel-length into a real-life distance expressed in map CRS. This depends on the current map scale.
*
* @param pixels
* The number of pixels to calculate the distance for.
* @return The distance the given number of pixels entails.
*/
private double pixelsToUnits(int pixels) {
Coordinate c1 = mapPresenter.getViewPort().getTransformationService()
.transform(new Coordinate(0, 0), RenderSpace.SCREEN, RenderSpace.WORLD);
Coordinate c2 = mapPresenter.getViewPort().getTransformationService()
.transform(new Coordinate(pixels, 0), RenderSpace.SCREEN, RenderSpace.WORLD);
return MathService.distance(c1, c2);
}
// ------------------------------------------------------------------------
// Private classes:
// ------------------------------------------------------------------------
/**
* Internal selection by rectangle controller.
*
* @author Pieter De Graef
*/
private class SelectionRectangleController extends AbstractRectangleController {
public void execute(Bbox worldBounds) {
GeomajasServerExtension
.getInstance()
.getServerFeatureService()
.search(mapPresenter, GeometryService.toPolygon(worldBounds), 0, QueryType.INTERSECTS,
searchLayerType, intersectionRatio, new SelectionCallback(shift, true));
}
public void cleanup() {
if (dragging) {
dragging = false;
if (container != null) {
container.remove(rectangle);
}
}
}
}
/**
* Callback for feature searches that actually selects or deselects the features involved.
*
* @author Pieter De Graef
*/
private class SelectionCallback implements FeatureMapFunction {
private final boolean isShift;
private final boolean bulk;
public SelectionCallback(boolean isShift, boolean bulk) {
this.isShift = isShift;
this.bulk = bulk;
}
public void execute(Map> featureMap) {
if (bulk) {
for (FeaturesSupported layer : featureMap.keySet()) {
List features = featureMap.get(layer);
if (features != null) {
if (!isShift) {
layer.clearSelectedFeatures();
}
for (Feature feature : features) {
if (!layer.isFeatureSelected(feature.getId())) {
layer.selectFeature(feature);
}
}
}
}
} else {
for (FeaturesSupported layer : featureMap.keySet()) {
List features = featureMap.get(layer);
if (features != null) {
if (isShift) {
// Add to selection, unless already selected:
if (layer.isFeatureSelected(features.get(0).getId())) {
layer.deselectFeature(features.get(0));
} else {
layer.selectFeature(features.get(0));
}
} else {
// No shift: if selected deselect, otherwise make sure it's the only selection:
if (layer.isFeatureSelected(features.get(0).getId())) {
layer.clearSelectedFeatures();
} else {
layer.clearSelectedFeatures();
layer.selectFeature(features.get(0));
}
}
}
}
}
}
}
}