Package org.locationtech.udig.tools.split

Source Code of org.locationtech.udig.tools.split.SplitFeaturesCommand

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2012, Refractions Research Inc.
* (C) 2006, Axios Engineering S.L. (Axios)
* (C) 2006, County Council of Gipuzkoa, Department of Environment and Planning
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Axios BSD
* License v1.0 (http://udig.refractions.net/files/asd3-v10.html).
*/
package org.locationtech.udig.tools.split;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.project.IMap;
import org.locationtech.udig.project.command.AbstractCommand;
import org.locationtech.udig.project.command.UndoableComposite;
import org.locationtech.udig.project.command.UndoableMapCommand;
import org.locationtech.udig.project.command.factory.EditCommandFactory;
import org.locationtech.udig.project.internal.ProjectPlugin;
import org.locationtech.udig.tools.edit.EditState;
import org.locationtech.udig.tools.edit.EditToolHandler;
import org.locationtech.udig.tools.edit.support.EditBlackboard;
import org.locationtech.udig.tools.edit.support.GeometryCreationUtil;
import org.locationtech.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.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 com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.Point;

import org.locationtech.udig.tools.feature.split.CannotSplitException;
import org.locationtech.udig.tools.feature.split.SplitFeatureBuilder;
import org.locationtech.udig.tools.feature.split.SplitFeatureBuilderFailException;
import org.locationtech.udig.tools.feature.util.GeoToolsUtils;
import org.locationtech.udig.tools.internal.i18n.Messages;
import org.locationtech.udig.tools.internal.ui.util.LayerUtil;
import org.locationtech.udig.tools.internal.ui.util.MapUtil;

/**
* 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 Feature geometries that were split, the original Feature will be
* deleted and as many new Features as geometries resulted of the split
* operation will be created, maintaining the original Feature's attributes
* other than the default geometry.
* </p>
*
* @author Mauricio Pazos (www.axios.es)
* @author Aritz Davila (www.axios.es)
* @since 1.3.0
* @see SplitFeatureBuilder
*/
final class SplitFeaturesCommand extends AbstractCommand implements UndoableMapCommand {

  private static final String className = SplitFeaturesCommand.class.getName();
   
  /* Edit tool handler used by the tools. */
  private EditToolHandler    handler;
  /* The selected layer. */
  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().getGeometryDescriptor().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 split 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);
    prepMonitor.beginTask("Splitting features", 1); //$NON-NLS-1$

    LineString splitter = getSplittingLineInMapCRS(handler);
    assert splitter.getUserData() instanceof CoordinateReferenceSystem;

    FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToSplit = getFeaturesToSplit(splitter);
    prepMonitor.worked(2);

    final List<UndoableMapCommand> undoableCommands = new ArrayList<UndoableMapCommand>();

    List<UndoableMapCommand> commands = buildCommandList(featuresToSplit, splitter);
    undoableCommands.addAll(commands);
    prepMonitor.worked(3);

    prepMonitor.done();

    if (undoableCommands.size() == 0) {
            ProjectPlugin.log("The split did not apply to any feature"); //$NON-NLS-1$
       
      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
    composite.setMap(getMap());
    composite.run(splitMonitor);

    repaint();
  }

  private void repaint() {
    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)
   * @return The split line transformed to the map CRS.
   *
   * @throws SplitFeaturesCommandException
   */
  private  LineString getSplittingLineInMapCRS(final EditToolHandler handler)
    throws SplitFeaturesCommandException {

      final PrimitiveShape currentShape = handler.getCurrentShape();
      final LineString lineInLayerCrs = GeometryCreationUtil.createGeom(LineString.class, currentShape, false);
      final CoordinateReferenceSystem mapCrs = MapUtil.getCRS(handler.getContext().getMap());

      LineString splittingLine = reprojectSplitLine(lineInLayerCrs, mapCrs);

      return splittingLine;
  }

  /**
   * Reprojects the split line to the target CRS
   *
   * @param lineInLayerCrs
   * @param targetCrs
   * @return the split line in the target crs
   * @throws SplitFeaturesCommandException
   */
  private  LineString reprojectSplitLine(final LineString lineInLayerCrs, final CoordinateReferenceSystem targetCrs) throws SplitFeaturesCommandException {
     
      try{
         
            CoordinateReferenceSystem splitLineCrs = (CoordinateReferenceSystem) lineInLayerCrs.getUserData();
         
          if(splitLineCrs == null){
             
              final ILayer selectedLayer = this.handler.getContext().getSelectedLayer();
              final CoordinateReferenceSystem layerCrs = LayerUtil.getCrs(selectedLayer);

              assert this.handler.getCurrentShape() != null;
              assert layerCrs != null;
             
              splitLineCrs = layerCrs;
          }
         
            LineString splitlineInMapCRS = (LineString) GeoToolsUtils.reproject(lineInLayerCrs, splitLineCrs, targetCrs);
           
            splitlineInMapCRS.setUserData(targetCrs);

         
            return splitlineInMapCRS;
      } catch (Exception e) {
       
            e.printStackTrace();
            ProjectPlugin.log("It does not possible transform the split line to the map crs", e); //$NON-NLS-1$
            throw new SplitFeaturesCommandException(Messages.SplitFeaturesCommand_cannot_transform_the_splitline_crs)
      }
  }

  /**
   * Returns the features to be split by <code>splittingLine</code>.
   * <p>
   * To get the features, <code>splittingLine</code> is transformed to the
   * layer's CRS and an Intersects filter is made with the result.
   * </p>
   *
   * @param splittingLine  The split line.
   *
   * @return The features that are under the influence of the split line.
   *
   * @throws SplitFeaturesCommandException
   */
  private FeatureCollection<SimpleFeatureType, SimpleFeature> getFeaturesToSplit(final LineString splittingLine)
    throws SplitFeaturesCommandException {

      try{
          Filter extraFilter = createSplittingLineFilter(splittingLine);
          FeatureCollection<SimpleFeatureType, SimpleFeature> selection = LayerUtil.getSelectedFeatures(selectedLayer,
                      extraFilter);
          return selection;
         
        } catch (Exception e) {
            e.printStackTrace();
            ProjectPlugin.log("The split tool can not retrieve the features to be split", e); //$NON-NLS-1$
            throw new SplitFeaturesCommandException(Messages.SplitFeaturesCommand_cannot_retrieve_features_to_be_split)
      }
  }

  /**
   * Creates the filter based on the layer filter and the split line. It would
   * be an intersect filter between the split line and the layer, taking into
   * account the own features that are selected in the layer.
   *
   * @param splittingLine
   *            The split line.
   * @return The intersect filter between the split line and the layer.
   *
   * @throws SplitFeaturesCommandException
   */
  private Filter createSplittingLineFilter(LineString splittingLine)
    throws SplitFeaturesCommandException {

    try {

          Filter filter = selectedLayer.getFilter();
          FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
          Intersects intersectsFilter;

      final CoordinateReferenceSystem layerCrs = LayerUtil.getCrs(selectedLayer);
      final Geometry splitLineGeom = reprojectSplitLine(splittingLine, layerCrs);

      SimpleFeatureType schema = selectedLayer.getSchema();
      String geomName = schema.getGeometryDescriptor().getLocalName();

      intersectsFilter = ff.intersects(ff.property(geomName), ff.literal(splitLineGeom));

      if (Filter.EXCLUDE.equals(filter)) {
              filter = intersectsFilter;
          } else {
              filter = ff.and(filter, intersectsFilter);
          }
          return filter;
         
        } catch (IllegalFilterException e) {
            e.printStackTrace();
            ProjectPlugin.log("The split command found an illegal filter", e); //$NON-NLS-1$
            throw new SplitFeaturesCommandException(Messages.SplitFeaturesCommand_fail);
           
        }
  }

  /**
   * 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 A command list will all the operations that the split tool will
   *         do.
   * @throws SplitFeaturesCommandException
   * @throws IOException
   */
  private List<UndoableMapCommand> buildCommandListFeatureCollection<SimpleFeatureType, SimpleFeature> featuresToSplit,
                            LineString splitterInMapCrs)
    throws SplitFeaturesCommandException {

    ProjectPlugin.log(className + " - Split original: " + splitterInMapCrs.toText()); //$NON-NLS-1$
 
    final EditCommandFactory cmdFac = EditCommandFactory.getInstance();
    final FeatureIterator<SimpleFeature> featureToSplitIterator = featuresToSplit.features();
    final CoordinateReferenceSystem mapCRS = handler.getContext().getCRS();

    try {
     
      List<SimpleFeature> originalFeatureList = asList(featureToSplitIterator);
     
            if(originalFeatureList.isEmpty()){
                throw new SplitFeaturesCommandException(Messages.SplitFeaturesCommand_did_not_apply_to_any_feature);
            }
          ProjectPlugin.log(className + " - Split using CRS: " + mapCRS.toString()); //$NON-NLS-1$

      SplitFeatureBuilder builder = SplitFeatureBuilder.newInstance(originalFeatureList, splitterInMapCrs, mapCRS);
      try {
        builder.buildSplit();
      } catch (CannotSplitException e) {
        throw new SplitFeaturesCommandException(e.getMessage());
      }

      // make the requires list of commands to update the affected features
      final List<UndoableMapCommand> undoableCommands = new LinkedList<UndoableMapCommand>();
     
      // delete the features that suffered split
      List<SimpleFeature> featuresThatSufferedSplit = builder.getFeaturesThatSufferedSplit();

      for (SimpleFeature feature : featuresThatSufferedSplit) {

          UndoableMapCommand command = cmdFac.createDeleteFeature(feature, selectedLayer);
         
        undoableCommands.add(command);
        ProjectPlugin.log(className + " - Delete original feature: " + ((Geometry) feature.getDefaultGeometry()).toText()); //$NON-NLS-1$
      }
      // add the new features
      List<SimpleFeature> splitResult = builder.getSplitResult();
      for (SimpleFeature feature : splitResult) {
          UndoableMapCommand command = cmdFac.createAddFeatureCommand(feature, selectedLayer);
        undoableCommands.add(command);
        ProjectPlugin.log(className + " - Split result: " + ((Geometry) feature.getDefaultGeometry()).toText()); //$NON-NLS-1$
      }
      // modify the neighbor features.
      builder.buildNeighbours();
      List<SimpleFeature> modifiedNeighbour = builder.getNeighbourResult();
      for (SimpleFeature neighbor : modifiedNeighbour) {

          for( SimpleFeature original : originalFeatureList ) {
                    if(original.getID().equals(neighbor.getID())){
                        UndoableMapCommand command = new ModifyGeometryFeatureCommand(original.getID(), (Geometry)neighbor.getDefaultGeometry(), (Geometry)original.getDefaultGeometry(), this.selectedLayer);
                        undoableCommands.add(command);

                        ProjectPlugin.log(className + " - Neighbour result: " + ((Geometry) neighbor.getDefaultGeometry()).toText()); //$NON-NLS-1$
                    }
                }
      }
          return undoableCommands;

    } catch (SplitFeatureBuilderFailException sfbe) {
      sfbe.printStackTrace();
      ProjectPlugin.log("This split transaction is not possible.", sfbe); //$NON-NLS-1$
      throw new SplitFeaturesCommandException(Messages.SplitFeaturesCommand_split_transaction_failed);

    } finally {
      if (featureToSplitIterator != null){
        featureToSplitIterator.close();
      }
    }
  }

        /**
         * Transforms the feature collection to a List
         *
         * @param featureIterator
         * @return the feature list
         * @throws SplitFeaturesCommandException
         */
        private List<SimpleFeature> asList(
          final FeatureIterator<SimpleFeature> featureIterator)
          throws SplitFeaturesCommandException {
   
            List<SimpleFeature> featureList = new ArrayList<SimpleFeature>();
       
            while (featureIterator.hasNext()) {
       
              SimpleFeature feature = featureIterator.next();
       
              Geometry defaultGeometry = (Geometry) feature.getDefaultGeometry();
              ProjectPlugin.log(className
                + "- Original feature: " + defaultGeometry.toText()); //$NON-NLS-1$
       
              if (!defaultGeometry.isValid()) {
            ProjectPlugin
              .log(className
                + "- Original feature has invalid geometry: " + defaultGeometry.toText()); //$NON-NLS-1$
            throw new SplitFeaturesCommandException(
              Messages.SplitFeaturesCommand_the_feature_has_invalid_geometry);
              }
              featureList.add(feature);
       
            }
            return featureList;
        }

  /**
   * 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) {

      composite.rollback(monitor);
    }
  }

}
TOP

Related Classes of org.locationtech.udig.tools.split.SplitFeaturesCommand

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.