/**
   * Creates a new SimpleFeature for <code>targetType</code> that holds the common attributes from
   * <code>sourceFeature</code> and the buffered geometry.
   *
   * @param sourceFeature the original SimpleFeature from which to extract matching attributes for
   *     the new SimpleFeature, or <code>null</code> if the new SimpleFeature has to have empty
   *     attributes other than the default geometry.
   * @param targetType
   * @param bufferedGeometry the product geometry of running {@link BufferOp} over the default
   *     geometry of <code>sourceFeature</code> with the parameters provided to this operation.
   * @return a new SimpleFeature of type <code>targetType</code> holding the common attributes with
   *     <code>sourceFeature</code> and <code>bufferedGeometry</code> as the feature's default
   *     geometry
   * @throws SOProcessException
   */
  @SuppressWarnings("unchecked")
  private SimpleFeature createBufferedFeature(
      SimpleFeature sourceFeature, SimpleFeatureType targetType, Geometry bufferedGeometry)
      throws SOProcessException {

    SimpleFeature newFeature;
    try {
      newFeature = DataUtilities.template(targetType);
      final GeometryDescriptor targetGeometryType = targetType.getDefaultGeometry();

      if (sourceFeature != null) {
        GeoToolsUtils.match(sourceFeature, newFeature);
      }

      final String attName = targetGeometryType.getLocalName();
      final Class geomClass = targetGeometryType.getType().getBinding();
      bufferedGeometry = GeometryUtil.adapt(bufferedGeometry, geomClass);

      newFeature.setAttribute(attName, bufferedGeometry);

    } catch (IllegalAttributeException e) {
      throw new SOProcessException(e.getMessage(), e);
    }
    return newFeature;
  }
  /** @return the target feature type */
  private SimpleFeatureType buildFeatureType(final String layerName) {

    final CoordinateReferenceSystem crs = getCurrentMapCrs();
    assert crs != null;
    SimpleFeatureTypeBuilder typeBuilder = GeoToolsUtils.createDefaultFeatureType(layerName, crs);
    SimpleFeatureType newFeatureType = null;
    try {
      newFeatureType = typeBuilder.buildFeatureType();

    } catch (IllegalArgumentException e) {
      Message message =
          new Message(
              Messages.IntersectComposite_can_not_create_targetFeatureType, Message.Type.ERROR);

      this.getController().setMessage(message);
    }
    return newFeatureType;
  }
  /**
   * Traverses the FeatureCollection<SimpleFeatureType, SimpleFeature> <code>selection</code>
   * creating a buffered geometry on each SimpleFeature's default geometry and stores the result in
   * a new SimpleFeature on the <code>target</code> FeatureStore.
   *
   * <p>Note the buffer computation is made with a slightly {@link BufferOp modified version} of the
   * JTS <code>BufferOp</code> in order to allow the operation to be cancelled while inside the
   * buffer computation.
   *
   * @param params buffer parameters
   * @param selection source layer's selected features in its native CRS
   * @throws SOProcessException if {@link #createBufferedFeature(SimpleFeature, SimpleFeatureType,
   *     Geometry)} fails
   * @throws IOException if it is thrown while adding the resulting features to the target
   *     FeatureStore<SimpleFeatureType, SimpleFeature> or while commiting the transaction
   * @throws InterruptedException if the user cancelled the operation
   */
  private void performBuffer(
      IBufferParameters params, FeatureCollection<SimpleFeatureType, SimpleFeature> selection)
      throws SOProcessException, InterruptedException {

    assert selection != null;

    final int featureCount = selection.size();

    final ILayer sourceLayer = this.sourceLayer;

    final CoordinateReferenceSystem sourceCrs = LayerUtil.getCrs(sourceLayer);

    final CoordinateReferenceSystem mapCrs = MapUtil.getCRS(sourceLayer.getMap());

    final CoordinateReferenceSystem targetCrs =
        this.targetStore.getSchema().getDefaultGeometry().getCRS();

    final Unit sourceUnits = GeoToolsUtils.getDefaultCRSUnit(mapCrs);
    final Unit targetUnits = params.getUnitsOfMeasure();
    final int quadSegments = params.getQuadrantSegments().intValue();

    final Double width = params.getWidth().doubleValue();

    SimpleFeature sourceFeature = null;
    // the one to use if params.isMergeGeometry() == true
    Geometry mergedGeometry = null;

    FeatureIterator<SimpleFeature> iterator = null;

    try {
      int processingCount = 0;
      Geometry geometry;

      String subTaskName;

      iterator = selection.features();
      while (iterator.hasNext()) {

        processingCount++;
        subTaskName =
            MessageFormat.format(
                Messages.BufferProcess_subTask_BufferingFeatureN, processingCount, featureCount);

        getMonitor().subTask(subTaskName);

        checkCancelation();

        sourceFeature = iterator.next();

        geometry = (Geometry) sourceFeature.getDefaultGeometry();
        geometry = GeoToolsUtils.reproject(geometry, sourceCrs, mapCrs);
        geometry =
            makeBufferGeometry(
                geometry, width, sourceUnits, targetUnits, quadSegments, getMonitor());
        geometry = GeoToolsUtils.reproject(geometry, mapCrs, targetCrs);

        checkCancelation();

        if (params.isMergeGeometries()) {
          if (mergedGeometry == null) {
            mergedGeometry = geometry;
          } else {
            mergedGeometry = mergedGeometry.union(geometry);
          }
        } else {
          createAndStoreBufferedFeature(sourceFeature, geometry, this.targetStore);
        }
        getMonitor().worked(1);
      }
      checkCancelation();
      if (params.isMergeGeometries()) {
        createAndStoreBufferedFeature(null, mergedGeometry, this.targetStore);
      }

      getMonitor().subTask(Messages.BufferProcess_subtastCommittingTransaction);

    } catch (OperationNotFoundException e) {
      String message =
          MessageFormat.format(
              Messages.BufferProcess_failed_transforming, sourceFeature.getID(), e.getMessage());
      throw new SOProcessException(message, e);
    } catch (TransformException e) {
      String message =
          MessageFormat.format(
              Messages.BufferProcess_failed_transforming_feature_to_crs,
              sourceFeature.getID(),
              e.getMessage());
      throw new SOProcessException(message, e);
    } finally {

      if (iterator != null) iterator.close();

      getMonitor().done();
    }
  }
  public UndoableMapCommand getCommand(EditToolHandler handler) {
    final PrimitiveShape currentShape = handler.getCurrentShape();
    final ILayer editLayer = handler.getEditLayer();

    // need to use map coordinates in order to avoid
    // possible inconsistencies between what the user
    // drawn and the projected result
    GeometryFactory gf = new GeometryFactory();
    CoordinateReferenceSystem layerCrs = LayerUtil.getCrs(editLayer);
    CoordinateReferenceSystem mapCrs = editLayer.getMap().getViewportModel().getCRS();
    Point p1 = gf.createPoint(currentShape.getCoord(0));
    Point p2 = gf.createPoint(currentShape.getCoord(1));
    Point p3 = gf.createPoint(currentShape.getCoord(2));

    try {
      p1 = (Point) GeoToolsUtils.reproject(p1, layerCrs, mapCrs);
      p2 = (Point) GeoToolsUtils.reproject(p2, layerCrs, mapCrs);
      p3 = (Point) GeoToolsUtils.reproject(p3, layerCrs, mapCrs);
    } catch (OperationNotFoundException onfe) {
      throw new IllegalStateException(
          "Could not reproject to map crs:" + onfe.getLocalizedMessage(), onfe);
    } catch (TransformException te) {
      throw new IllegalStateException(
          "Could not reproject to map crs:" + te.getLocalizedMessage(), te);
    }

    ArcBuilder builder = new ArcBuilder();
    builder.setPoints(p1.getX(), p1.getY(), p2.getX(), p2.getY(), p3.getX(), p3.getY());

    // TODO: especificar la cantidad de segmentos por cuadrante
    // mediante una preferencia
    Geometry geom = builder.getGeometry(15);
    if (geom == null) {
      throw new IllegalStateException("null geom");
    }

    // backproject resulting geom from map crs to layer's crs
    try {
      geom = GeoToolsUtils.reproject(geom, mapCrs, layerCrs);
    } catch (OperationNotFoundException onfe) {
      throw new IllegalStateException(
          "Could not reproject back to data crs:" + onfe.getLocalizedMessage(), onfe);
    } catch (TransformException te) {
      throw new IllegalStateException(
          "Could not reproject back to data crs:" + te.getLocalizedMessage(), te);
    }

    EditCommandFactory editCmdFac = AppGISMediator.getEditCommandFactory();

    ILayer layer = editLayer;
    SimpleFeatureType schema = layer.getSchema();
    SimpleFeature feature;
    try {
      feature = SimpleFeatureBuilder.build(schema, (Object[]) null, null);
      Class type = schema.getDefaultGeometry().getType().getBinding();
      geom = GeometryUtil.adapt(geom, type);
      feature.setDefaultGeometry(geom);
    } catch (IllegalAttributeException e) {
      // consider using the bubble feedback
      throw new IllegalStateException("Could not create Arc:" + e, e);
    }
    UndoableMapCommand command = editCmdFac.createAddFeatureCommand(feature, layer);

    handler.setCurrentShape(null);
    handler.setCurrentState(EditState.NONE);

    return command;
  }