/* Spatial Operations & Editing Tools for uDig
*
* Axios Engineering under a funding contract with:
* Diputación Foral de Gipuzkoa, Ordenación Territorial
*
* http://b5m.gipuzkoa.net
* http://www.axios.es
*
* (C) 2006, Diputación Foral de Gipuzkoa, Ordenación Territorial (DFG-OT).
* DFG-OT agrees to licence under Lesser General Public License (LGPL).
*
* You can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software
* Foundation; version 2.1 of the License.
*
* This library 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
* Lesser General Public License for more details.
*/
package es.axios.udig.ui.editingtools.internal.commands;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.refractions.udig.project.ILayer;
import net.refractions.udig.project.IMap;
import net.refractions.udig.project.command.AbstractCommand;
import net.refractions.udig.project.command.UndoableComposite;
import net.refractions.udig.project.command.UndoableMapCommand;
import net.refractions.udig.project.command.factory.EditCommandFactory;
import net.refractions.udig.project.internal.Map;
import net.refractions.udig.tools.edit.EditPlugin;
import net.refractions.udig.tools.edit.EditState;
import net.refractions.udig.tools.edit.EditToolHandler;
import net.refractions.udig.tools.edit.support.EditBlackboard;
import net.refractions.udig.tools.edit.support.GeometryCreationUtil;
import net.refractions.udig.tools.edit.support.PrimitiveShape;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.geotools.data.FeatureStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.filter.IllegalFilterException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.spatial.Intersects;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;
import es.axios.udig.ui.commons.mediator.AppGISMediator;
import es.axios.udig.ui.commons.util.GeoToolsUtils;
import es.axios.udig.ui.commons.util.GeometryUtil;
import es.axios.udig.ui.commons.util.LayerUtil;
import es.axios.udig.ui.commons.util.MapUtil;
import es.axios.udig.ui.editingtools.internal.geometryoperations.split.SplitStrategy;
import es.axios.udig.ui.editingtools.internal.i18n.Messages;
/**
* Undoable map command that splits a collection of Features with a given Line.
* <p>
* The splitting line is taken from the EditToolHandler's {@link EditToolHandler#getCurrentShape()}
* </p>
* <p>
* That line will then be used to cut the selected layer's geometries that intersect it. If a
* selection is set on the layer, it will be respected, and thus the command will apply to those
* features that are either selected and intersect the splitting line.
* </p>
* <p>
* For those SimpleFeature geometries that were splitted, the original SimpleFeature will be deleted and as many
* new Features as geometries resulted of the split operation will be created, maintaining the
* original SimpleFeature's attributes other than the default geometry.
* </p>
*
* @author Gabriel Roldan (www.axios.es)
* @author Mauricio Pazos (www.axios.es)
* @since 1.1.0
* @see SplitStrategy
*/
final class SplitFeaturesCommand extends AbstractCommand implements UndoableMapCommand {
private EditToolHandler handler;
private ILayer selectedLayer;
/**
* Composite command used to aggregate the set of feature delete and create commands
*/
private UndoableComposite composite;
/**
* Creates a new split command to split the features of the <code>handler</code>'s selected
* layer with the line present in the handler's
* {@link EditToolHandler#getCurrentShape() current shape}.
*
* @param handler an EditToolHandler containing the context for the command to run. For
* instance, the selected layer and the line shape drawn by the user.
*/
public SplitFeaturesCommand( EditToolHandler handler ) {
final ILayer selectedLayer = handler.getContext().getSelectedLayer();
assert selectedLayer.getSchema() != null;
Class<?> geometryBinding = selectedLayer.getSchema().getDefaultGeometry().getType().getBinding();
assert geometryBinding != Point.class;
assert geometryBinding != MultiPoint.class;
assert selectedLayer.hasResource(FeatureStore.class);
this.handler = handler;
this.selectedLayer = selectedLayer;
}
public String getName() {
return "Split Features Command"; //$NON-NLS-1$
}
/**
* Runs the split operation over the provided features and builds this command's state as an
* {@link UndoableComposite} with the list of commands needed to remove the splitted features
* and create the new ones, then delegates the execution to that {@link UndoableComposite} which
* is maintained to be reused by {@link #rollback(IProgressMonitor)}.
*/
public void run( IProgressMonitor monitor ) throws Exception {
if( monitor == null ) {
monitor = new NullProgressMonitor();
}
IProgressMonitor prepMonitor = SubMonitor.convert( monitor, 80 );
final List<UndoableMapCommand> undoableCommands = new ArrayList<UndoableMapCommand>();
LineString splitter;
FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToSplit;
monitor.beginTask("Splitting features", 3); //$NON-NLS-1$
try {
splitter = getSplittingLineInMapCRS(handler);
assert splitter.getUserData() instanceof CoordinateReferenceSystem;
prepMonitor.worked(1);
featuresToSplit = getFeaturesToSplit(splitter);
prepMonitor.worked(2);
} catch (OperationNotFoundException e) {
throw new IllegalStateException( e.getMessage(), e );
} catch (IOException e) {
throw new IllegalStateException( e.getMessage(), e );
} catch (TransformException e) {
throw new IllegalStateException( e.getMessage(), e );
}
try {
List<UndoableMapCommand> commands = buildCommandList(featuresToSplit, splitter);
undoableCommands.addAll(commands);
} catch (Exception e) {
throw (RuntimeException) new RuntimeException("Difficulity splitting "+featuresToSplit.size()+" feature(s)").initCause(e); //$NON-NLS-1$ //$NON-NLS-2$
}
prepMonitor.worked(3);
prepMonitor.done();
if (undoableCommands.size() == 0) {
throw new IllegalArgumentException(
Messages.SplitFeaturesCommand_did_not_apply_to_any_feature);
}
IProgressMonitor splitMonitor = SubMonitor.convert( monitor, 20 );
composite = new UndoableComposite(undoableCommands);
// cascade setMap on the aggregate commands
Map map = getMap();
composite.setMap(map);
try {
composite.run(splitMonitor);
} catch (Exception e) {
throw new IllegalStateException( e.getLocalizedMessage(), e );
}
handler.setCurrentShape(null);
handler.setCurrentState(EditState.NONE);
final ILayer selectedLayer = handler.getContext().getSelectedLayer();
EditBlackboard editBlackboard = handler.getEditBlackboard(selectedLayer);
editBlackboard.clear();
handler.repaint();
}
/**
* Returns the line drawn as the splitting line, transformed to JTS LineString in the current
* {@link IMap map}'s CRS.
*
* @param handler the {@link EditToolHandler} from where to grab the current shape (the one
* drawn as the cutting line)
* @param layerCrs the {@link CoordinateReferenceSystem} of the Layer being edited, which is the
* CRS the editing line is being stored in the {@link EditToolHandler}. Expected as
* argument as I don't know how to obtain the layer's CRS from the handler itself.
* @return
* @throws TransformException
* @throws OperationNotFoundException
*/
public static LineString getSplittingLineInMapCRS( EditToolHandler handler ) throws OperationNotFoundException, TransformException {
final ILayer selectedLayer = handler.getContext().getSelectedLayer();
final CoordinateReferenceSystem layerCrs = LayerUtil.getCrs(selectedLayer);
assert handler.getCurrentShape() != null;
assert layerCrs != null;
final PrimitiveShape currentShape = handler.getCurrentShape();
final LineString lineInLayerCrs = GeometryCreationUtil.createGeom(LineString.class,currentShape,true);
final CoordinateReferenceSystem mapCrs = MapUtil.getCRS(handler.getContext().getMap());
LineString splittingLine = (LineString) GeoToolsUtils.reproject(lineInLayerCrs, layerCrs,
mapCrs);
splittingLine.setUserData(mapCrs);
return splittingLine;
}
/**
* Returns the features to be splitted by <code>splittingLine</code>.
* <p>
* To aquire the featuers, <code>splittingLine</code> is transformed to the layer's CRS and an
* Intersects filter is made with the result.
* </p>
*
* @param splittingLine
* @return
* @throws IOException
* @throws OperationNotFoundException
* @throws TransformException
*/
FeatureCollection<SimpleFeatureType, SimpleFeature> getFeaturesToSplit( LineString splittingLine ) throws IOException, OperationNotFoundException, TransformException {
Filter extraFilter = createSplittingLineFilter(splittingLine);
FeatureCollection<SimpleFeatureType, SimpleFeature> selection = LayerUtil.getSelectedFeatures(selectedLayer, extraFilter);
return selection;
}
Filter createSplittingLineFilter( LineString splittingLine ) throws OperationNotFoundException, TransformException {
Filter filter = selectedLayer.getFilter();
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
Intersects intersectsFilter;
try {
final CoordinateReferenceSystem geomCrs;
geomCrs = (CoordinateReferenceSystem) splittingLine.getUserData();
final CoordinateReferenceSystem layerCrs = LayerUtil.getCrs(selectedLayer);
final Geometry literal = GeoToolsUtils.reproject(splittingLine, geomCrs, layerCrs);
SimpleFeatureType schema = selectedLayer.getSchema();
String geomName = schema.getDefaultGeometry().getLocalName();
intersectsFilter = ff.intersects( ff.property(geomName), ff.literal(literal));
} catch (IllegalFilterException e) {
throw new IllegalStateException( e.getMessage(), e );
}
if (Filter.EXCLUDE.equals(filter)) {
filter = intersectsFilter;
} else {
filter = ff.and( filter, intersectsFilter );
}
return filter;
}
/**
* Creates the list of commands that are going to be executed on {@link #run(IProgressMonitor)}
* <p>
* NOTE: visible only to be accessed by unit tests, framework uses
* {@link #run(IProgressMonitor)} and you should never see this class from client code.
* </p>
*
* @return
* @throws OperationNotFoundException
* @throws TransformException
* @throws IllegalAttributeException
*/
List<UndoableMapCommand> buildCommandList( FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToSplit,
LineString splitterInMapCrs ) throws OperationNotFoundException, TransformException, IllegalAttributeException {
EditPlugin.trace("Splitter in map crs:" + splitterInMapCrs, null); //$NON-NLS-1$
final EditCommandFactory cmdFac = AppGISMediator.getEditCommandFactory();
final FeatureIterator<SimpleFeature> iterator = featuresToSplit.features();
final List<UndoableMapCommand> undoableCommands = new ArrayList<UndoableMapCommand>();
final SplitStrategy splitOp = new SplitStrategy(splitterInMapCrs);
final CoordinateReferenceSystem layerCrs = LayerUtil.getCrs(selectedLayer);
final CoordinateReferenceSystem splitterCrs;
splitterCrs = (CoordinateReferenceSystem) splitterInMapCrs.getUserData();
try {
Geometry originalGeometry;
Geometry splitted;
UndoableMapCommand command;
while( iterator.hasNext() ) {
final SimpleFeature feature = iterator.next();
final SimpleFeatureType featureType = feature.getFeatureType();
originalGeometry = (Geometry) feature.getDefaultGeometry();
EditPlugin.trace("originalGeometry=" + originalGeometry,null); //$NON-NLS-1$
originalGeometry = GeoToolsUtils.reproject(originalGeometry, layerCrs, splitterCrs);
EditPlugin.trace("originalGeometry projected to Map CRS =" + originalGeometry, null); //$NON-NLS-1$
splitted = splitOp.split(originalGeometry);
EditPlugin.trace("split result =" + splitted,null); //$NON-NLS-1$
splitted = GeoToolsUtils.reproject(splitted, splitterCrs, layerCrs);
EditPlugin.trace("splitted back projected to layerCrs=" + splitted,null); //$NON-NLS-1$
final int numGeometries = splitted.getNumGeometries();
switch( numGeometries ) {
case 0:
throw new IllegalStateException(
Messages.SplitFeaturesCommand_no_geometry_were_created);
case 1:
// do nothing, same as input
break;
default:
command = cmdFac.createDeleteFeature(feature, selectedLayer);
undoableCommands.add(command);
for( int i = 0; i < numGeometries; i++ ) {
Geometry splittedPart = splitted.getGeometryN(i);
Class<?> geometryBinding =
featureType.getDefaultGeometry().getType().getBinding();
splittedPart = GeometryUtil.adapt(splittedPart, (Class<? extends Geometry>) geometryBinding);
SimpleFeature newFeature = SimpleFeatureBuilder.template( featureType, null );
GeoToolsUtils.match(feature, newFeature);
newFeature.setDefaultGeometry(splittedPart);
command = cmdFac.createAddFeatureCommand(newFeature, selectedLayer);
undoableCommands.add(command);
}
}
}
} finally {
featuresToSplit.close(iterator);
}
return undoableCommands;
}
/**
* Rolls back the split operation by delegating to the {@link UndoableComposite} created in
* {@link #run(IProgressMonitor)}, if any. Exits gracefully otherwise.
*/
public void rollback( IProgressMonitor monitor ) throws Exception {
if (composite != null) {
// handler.setCurrentState(EditState.NONE);
// handler.setCurrentShape(currentShape);
composite.rollback(monitor);
}
}
}