/**
   * Analyzes the feature list, take the features that are neighbour and if they meet the
   * requirements, add a vertex to them.
   *
   * @return The builder instance.
   * @throws SplitFeatureBuilderFailException
   */
  public SplitFeatureBuilder buildNeighbours() throws SplitFeatureBuilderFailException {

    this.neighbourResultList = new ArrayList<SimpleFeature>();
    try {
      for (SimpleFeature feature : featureList) {

        Geometry geom = (Geometry) feature.getDefaultGeometry();
        CoordinateReferenceSystem featureCRS =
            feature.getFeatureType().getCoordinateReferenceSystem();
        geom = GeoToolsUtils.reproject(geom, featureCRS, desiredCRS);

        if (!canSplit(geom) && (requireVertex(geom))) {

          Geometry geomWithAddedVertex = addVertexToNeighbour(geom);
          geomWithAddedVertex =
              GeoToolsUtils.reproject(geomWithAddedVertex, desiredCRS, featureCRS);
          feature.setDefaultGeometry(geomWithAddedVertex);
          this.neighbourResultList.add(feature);
        }
      }
    } catch (Exception e) {
      throw makeFailException(e);
    }
    return this;
  }
  /**
   * Creates the resultant features.
   *
   * @param splitGeometries List with the new geometries.
   * @param feature The old feature.
   * @throws OperationNotFoundException
   * @throws TransformException
   */
  private List<SimpleFeature> createSplitFeatures(
      final List<Geometry> splitGeometries, final SimpleFeature feature)
      throws OperationNotFoundException, TransformException {

    final SimpleFeatureType featureType = feature.getFeatureType();
    final CoordinateReferenceSystem featureCrs = featureType.getCoordinateReferenceSystem();

    Class<? extends Geometry> geometryType =
        (Class<? extends Geometry>) featureType.getGeometryDescriptor().getType().getBinding();

    List<SimpleFeature> splitFeatureList = new LinkedList<SimpleFeature>();
    for (Geometry splittedPart : splitGeometries) {

      splittedPart = GeoToolsUtils.reproject(splittedPart, desiredCRS, featureCrs);

      splittedPart = GeometryUtil.adapt(splittedPart, geometryType);
      SimpleFeature newFeature = DataUtilities.template(featureType);
      GeoToolsUtils.copyAttributes(feature, newFeature);
      newFeature.setDefaultGeometry(splittedPart);

      splitFeatureList.add(newFeature);
    }

    return splitFeatureList;
  }
  /**
   * Analyze the feature list, and for those features that can suffer split operation, they'll be
   * split.
   *
   * @return The builder instance.
   * @throws SplitFeatureBuilderFailException if the operation fail
   * @throws CannotSplitException if the split line cannot divide the feature's geometry
   */
  public SplitFeatureBuilder buildSplit()
      throws SplitFeatureBuilderFailException, CannotSplitException {

    try {
      this.splitResultList = new LinkedList<SimpleFeature>();
      boolean existSplit = false;
      for (SimpleFeature feature : this.featureList) {

        Geometry geomToSplit = (Geometry) feature.getDefaultGeometry();
        assert geomToSplit.isValid() : "No Valid Geometry: " + geomToSplit.toText(); // $NON-NLS-1$
        CoordinateReferenceSystem featureCrs =
            feature.getFeatureType().getCoordinateReferenceSystem();
        geomToSplit = GeoToolsUtils.reproject(geomToSplit, featureCrs, this.desiredCRS);

        if (canSplit(geomToSplit)) {
          existSplit = true;
          this.featuresThatSufferedSplit.add(feature);

          List<Geometry> splitGeometriesResult = split(geomToSplit);
          this.splitResultList.addAll(createSplitFeatures(splitGeometriesResult, feature));
        }
      }
      if (!existSplit) {
        throw new CannotSplitException("The split line cannot split any features"); // $NON-NLS-1$
      }
    } catch (OperationNotFoundException e) {
      throw makeFailException(e);
    } catch (TransformException e) {
      throw makeFailException(e);
    }
    return this;
  }
  /**
   * Create a new instance of the builder giving the split line, the feature list and the CRS which
   * the operation will be based on.
   *
   * @param featureList A non empty list of features.
   * @param splitLine The split line.
   * @param desiredCRS the crs where the split operation is done it
   * @return a new instance of the builder.
   */
  public static SplitFeatureBuilder newInstance(
      final List<SimpleFeature> featureList,
      final LineString splitLine,
      final CoordinateReferenceSystem desiredCRS)
      throws SplitFeatureBuilderFailException {

    if (featureList == null)
      throw new NullPointerException("The source feature list is required"); // $NON-NLS-1$
    if (splitLine == null)
      throw new NullPointerException("The split line is required"); // $NON-NLS-1$
    if (desiredCRS == null)
      throw new NullPointerException("The desired crs is required, i.e: map crs"); // $NON-NLS-1$
    if (!(splitLine.getUserData() instanceof CoordinateReferenceSystem))
      throw new IllegalArgumentException(
          "Set the crs of the line before calling newInstance (splitLine.setUserData(CoordinateReferenceSystem))"); //$NON-NLS-1$

    LOGGER.fine("featureList parameter:" + prettyPrint(featureList)); // $NON-NLS-1$
    LOGGER.fine("splitLine parameter:" + splitLine.toText()); // $NON-NLS-1$
    LOGGER.fine(((CoordinateReferenceSystem) splitLine.getUserData()).toWKT());

    try {
      LineString splitReprojected =
          (LineString)
              GeoToolsUtils.reproject(
                  splitLine, (CoordinateReferenceSystem) splitLine.getUserData(), desiredCRS);
      SplitFeatureBuilder sfb = new SplitFeatureBuilder();
      sfb.splitLine = splitReprojected;
      sfb.splitStrategy = new SplitStrategy(splitLine);
      sfb.splitResultList = null;
      sfb.originalGeometryList = new LinkedList<Geometry>();
      sfb.featuresThatSufferedSplit = new LinkedList<SimpleFeature>();
      sfb.featureList = featureList;
      sfb.desiredCRS = desiredCRS;

      return sfb;

    } catch (Exception e) {
      throw makeFailException(e);
    }
  }