/*
* 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.List;
import org.geomajas.geometry.Coordinate;
import org.geomajas.geometry.service.GeometryService;
import org.geomajas.gwt.client.action.MenuAction;
import org.geomajas.gwt.client.action.menu.ToggleSnappingAction;
import org.geomajas.gwt.client.controller.MeasureDistanceHandler.State;
import org.geomajas.gwt.client.gfx.paintable.GfxGeometry;
import org.geomajas.gwt.client.gfx.style.ShapeStyle;
import org.geomajas.gwt.client.i18n.I18nProvider;
import org.geomajas.gwt.client.map.layer.Layer;
import org.geomajas.gwt.client.map.layer.VectorLayer;
import org.geomajas.gwt.client.spatial.geometry.Geometry;
import org.geomajas.gwt.client.spatial.geometry.GeometryFactory;
import org.geomajas.gwt.client.spatial.geometry.operation.InsertCoordinateOperation;
import org.geomajas.gwt.client.util.GeometryConverter;
import org.geomajas.gwt.client.util.WidgetLayout;
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.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.menu.MenuItem;
import com.smartgwt.client.widgets.menu.MenuItemIfFunction;
import com.smartgwt.client.widgets.menu.events.MenuItemClickEvent;
/**
* <p>
* Controller that measures distances on the map, by clicking points. The actual distances are displayed in a label at
* the top left of the map.
* </p>
*
* @author Pieter De Graef
* @author Oliver May
* @author Jan De Moerloose
*
*/
public class MeasureDistanceController extends AbstractSnappingController {
private static final ShapeStyle LINE_STYLE_1 = new ShapeStyle("#FFFFFF", 0, "#FF9900", 1, 2);
private static final ShapeStyle LINE_STYLE_2 = new ShapeStyle("#FFFFFF", 0, "#FF5500", 1, 2);
private GfxGeometry distanceLine;
private GfxGeometry lineSegment;
private VLayout panel;
private GeometryFactory factory;
private float tempLength;
private Menu menu;
private GeometryFactory geometryFactory;
private List<MeasureDistanceHandler> handlers = new ArrayList<MeasureDistanceHandler>();
private MeasureDistanceContext context = new MeasureDistanceContextImpl();
// -------------------------------------------------------------------------
// Constructor:
// -------------------------------------------------------------------------
/**
* Construct a measureDistanceController. Default is to display the total distance and last line distance.
*
* @param mapWidget the mapwidget where the distance is measured on.
*/
public MeasureDistanceController(MapWidget mapWidget) {
this(mapWidget, false, false);
}
/**
* Construct a measureDistanceController.
*
* @param mapWidget the mapwidget where the distance is measured on.
* @param showArea true if the area should be displayed
* @param displayCoordinates the if the coordinates should be displayed.
*/
public MeasureDistanceController(MapWidget mapWidget, boolean showArea, boolean displayCoordinates) {
super(mapWidget);
distanceLine = new GfxGeometry("measureDistanceLine");
distanceLine.setStyle(LINE_STYLE_1);
lineSegment = new GfxGeometry("measureDistanceLineSegment");
lineSegment.setStyle(LINE_STYLE_2);
geometryFactory = new GeometryFactory(mapWidget.getMapModel().getPrecision(),
mapWidget.getMapModel().getSrid());
addHandler(new MeasureDistancePanel(mapWidget, showArea, displayCoordinates));
}
public void addHandler(MeasureDistanceHandler handler) {
handlers.add(handler);
}
public void removeHandler(MeasureDistanceHandler handler) {
handlers.remove(handler);
}
public void clearHandlers() {
handlers.clear();
}
// -------------------------------------------------------------------------
// GraphicsController interface:
// -------------------------------------------------------------------------
/** Create the context menu for this controller. */
public void onActivate() {
menu = new Menu();
menu.addItem(new CancelMeasuringAction(this));
Layer selectedLayer = mapWidget.getMapModel().getSelectedLayer();
if (selectedLayer instanceof VectorLayer) {
menu.addItem(new ToggleSnappingAction((VectorLayer) selectedLayer, this));
}
mapWidget.setContextMenu(menu);
}
/** Clean everything up. */
public void onDeactivate() {
onDoubleClick(null);
menu.destroy();
menu = null;
mapWidget.setContextMenu(null);
mapWidget.unregisterWorldPaintable(distanceLine);
mapWidget.unregisterWorldPaintable(lineSegment);
}
/** Set a new point on the distance-line. */
public void onMouseUp(MouseUpEvent event) {
if (event.getNativeButton() != NativeEvent.BUTTON_RIGHT) {
Coordinate coordinate = getWorldPosition(event);
if (distanceLine.getOriginalLocation() == null) {
distanceLine.setGeometry(getFactory().createLineString(new Coordinate[] { coordinate }));
mapWidget.registerWorldPaintable(distanceLine);
mapWidget.registerWorldPaintable(lineSegment);
dispatchState(State.START);
} else {
Geometry geometry = (Geometry) distanceLine.getOriginalLocation();
InsertCoordinateOperation op = new InsertCoordinateOperation(geometry.getNumPoints(), coordinate);
geometry = op.execute(geometry);
distanceLine.setGeometry(geometry);
tempLength = (float) geometry.getLength();
updateMeasure(event, true);
dispatchState(State.CLICK);
}
mapWidget.render(mapWidget.getMapModel(), RenderGroup.VECTOR, RenderStatus.UPDATE);
}
}
/** Update the drawing while moving the mouse. */
public void onMouseMove(MouseMoveEvent event) {
if (isMeasuring() && distanceLine.getOriginalLocation() != null) {
updateMeasure(event, false);
dispatchState(State.MOVE);
}
}
/** Stop the measuring, and remove all graphics from the map. */
public void onDoubleClick(DoubleClickEvent event) {
tempLength = 0;
mapWidget.unregisterWorldPaintable(distanceLine);
mapWidget.unregisterWorldPaintable(lineSegment);
distanceLine.setGeometry(null);
lineSegment.setGeometry(null);
if (panel != null) {
panel.destroy();
}
dispatchState(State.STOP);
}
protected void updateMeasure(MouseEvent event, boolean complete) {
Geometry lastClickedLineGeometry = (Geometry) distanceLine.getOriginalLocation();
Coordinate lastClickedCoordinate = lastClickedLineGeometry.getCoordinates()[distanceLine.getGeometry()
.getNumPoints() - 1];
Coordinate mouseCoordinate = getWorldPosition(event);
lineSegment.setGeometry(getFactory().createLineString(
new Coordinate[] { lastClickedCoordinate, mouseCoordinate }));
mapWidget.render(mapWidget.getMapModel(), RenderGroup.VECTOR, RenderStatus.UPDATE);
}
// -------------------------------------------------------------------------
// Private methods:
// -------------------------------------------------------------------------
private void dispatchState(State state) {
for (MeasureDistanceHandler handler : handlers) {
handler.onChange(state, context);
}
}
private boolean isMeasuring() {
return distanceLine.getGeometry() != null;
}
/**
* The factory can only be used after the MapModel has initialized, that is why this getter exists...
*
* @return geometry factory
*/
protected GeometryFactory getFactory() {
if (factory == null) {
factory = mapWidget.getMapModel().getGeometryFactory();
}
return factory;
}
/**
* Context to pass to our handlers.
*
* @author Jan De Moerloose
*
*/
class MeasureDistanceContextImpl implements MeasureDistanceContext {
@Override
public Geometry getGeometry() {
return distanceLine == null ? null : (Geometry) distanceLine.getOriginalLocation();
}
@Override
public double getPreviousDistance() {
return tempLength;
}
@Override
public double getCurrentDistance() {
return tempLength + getRadius();
}
@Override
public double getPreviousArea() {
return getGeometry() == null ? 0 : getArea(getGeometry());
}
@Override
public double getCurrentArea() {
Geometry geometry = getGeometry();
if (geometry != null) {
InsertCoordinateOperation op = new InsertCoordinateOperation(geometry.getNumPoints(),
getCurrentCoordinate());
geometry = op.execute(geometry);
return getArea(getGeometry());
} else {
return 0;
}
}
@Override
public double getRadius() {
if (lineSegment.getOriginalLocation() != null) {
return (float) ((Geometry) lineSegment.getOriginalLocation()).getLength();
} else {
return 0;
}
}
@Override
public Coordinate getPreviousCoordinate() {
if (getLastSegment() != null && getLastSegment().getNumPoints() > 0) {
return getLastSegment().getCoordinates()[0];
} else {
return null;
}
}
@Override
public Coordinate getCurrentCoordinate() {
if (getLastSegment() != null && getLastSegment().getNumPoints() > 1) {
return getLastSegment().getCoordinates()[1];
} else {
return null;
}
}
protected Geometry getLastSegment() {
return lineSegment == null ? null : ((Geometry) lineSegment.getOriginalLocation());
}
protected double getArea(Geometry geometry) {
return GeometryService.getArea(GeometryConverter.toDto(geometryFactory.createLinearRing(geometry
.getCoordinates())));
}
}
/**
* Menu item that stop the measuring.
*
* @author Pieter De Graef
*/
protected class CancelMeasuringAction extends MenuAction {
private final MeasureDistanceController controller;
public CancelMeasuringAction(final MeasureDistanceController controller) {
super(I18nProvider.getMenu().cancelMeasuring(), WidgetLayout.iconQuit);
this.controller = controller;
setEnableIfCondition(new MenuItemIfFunction() {
public boolean execute(Canvas target, Menu menu, MenuItem item) {
return controller.isMeasuring();
}
});
}
public void onClick(MenuItemClickEvent event) {
controller.onDoubleClick(null);
}
}
}