private FeatureCollection convexHhull(TaskMonitor monitor, FeatureCollection fc) {
    monitor.allowCancellationRequests();
    monitor.report(I18N.get("ui.plugin.analysis.ConvexHullPlugIn.Computing-Convex-Hull") + "...");

    int size = fc.size();
    GeometryFactory geomFact = null;

    if (size == 0) {
      return null;
    }
    int count = 0;
    Geometry[] geoms = new Geometry[size];

    for (Iterator i = fc.iterator(); i.hasNext(); ) {
      Feature f = (Feature) i.next();
      Geometry geom = f.getGeometry();
      if (geom == null) {
        continue;
      }
      if (geomFact == null) {
        geomFact = geom.getFactory();
      }

      geoms[count++] = geom;
    }
    GeometryCollection gc = geomFact.createGeometryCollection(geoms);
    Geometry hull = gc.convexHull();
    List hullList = new ArrayList();
    hullList.add(hull);

    return FeatureDatasetFactory.createFromGeometry(hullList);
  }
  /**
   * Maps one Coordinate to another.
   *
   * @param c a Coordinate which must be inside one of the triangle keys passed into the constructor
   * @return the transformed Coordinate
   */
  public Coordinate transform(Coordinate c) {
    monitor.report(++coordinatesTransformed, -1, "coordinates");

    Triangle sourceTriangle = sourceTriangle(c);
    Assert.isTrue(sourceTriangle != null, "Unable to determine source triangle for " + c);

    Triangle destTriangle = destTriangle(sourceTriangle);

    return destTriangle.toEuclideanCoordinate(sourceTriangle.toSimplicialCoordinate(c));
  }
  /**
   * @param sourceFeatures
   * @param targetFeatures
   * @param attributeName
   * @param attributeOp
   * @param spatialRelation
   * @param bufferRadius
   * @return a feature dataset
   */
  public static FeatureDataset joinAttributes(
      Collection sourceFeatures,
      Collection targetFeatures,
      String attributeName,
      int attributeOp,
      int spatialRelation,
      double bufferRadius,
      TaskMonitor monitor) {
    /*
    System.out.println("Join Attributes --- attribute op:" + attributeOp + " - " +
            AttributeOp.getName(attributeOp) + " --- spatial op: " + spatialRelation + " - " +
            SpatialRelationOp.getName(spatialRelation));
    */
    FeatureDataset fd = null;
    AttributeType newAttributeType = AttributeType.DOUBLE;
    String newAttributeName = attributeName + "_" + AttributeOp.getName(attributeOp);
    if (attributeOp == AttributeOp.COUNT) {
      newAttributeName = AttributeOp.getName(attributeOp);
    }

    // -- put all in a tree
    Quadtree fqTree = new Quadtree();
    FeatureSchema sourceFS = null;
    int count = 0;
    for (Iterator iter = sourceFeatures.iterator(); iter.hasNext(); ) {
      Feature pt = (Feature) iter.next();
      fqTree.insert(pt.getGeometry().getEnvelopeInternal(), pt);
      if (count == 0) {
        sourceFS = pt.getSchema();
      }
      count++;
    }
    // -- get AttributeType
    AttributeType at = null;
    try {
      at = sourceFS.getAttributeType(attributeName);
    } catch (Exception e) {
      at = AttributeType.GEOMETRY;
      attributeName = sourceFS.getAttributeName(0);
      System.out.println(
          "JoinAttributes.joinAttributes: replace unknown attribute name by geometry");
    }
    //
    ArrayList outPolys = new ArrayList();
    int size = targetFeatures.size();
    FeatureSchema targetFSnew = null;
    count = 0;
    Iterator iterp = targetFeatures.iterator();
    while (iterp.hasNext()) {
      count = count + 1;
      if (monitor != null) {
        monitor.report("item: " + count + " of " + size);
      }
      Feature p = (Feature) iterp.next();
      if (count == 1) {
        FeatureSchema targetFs = p.getSchema();
        targetFSnew = copyFeatureSchema(targetFs);
        if (targetFSnew.hasAttribute(newAttributeName)) {
          // attribute will be overwriten
        } else {
          // add attribute
          targetFSnew.addAttribute(newAttributeName, newAttributeType);
        }
      }
      // -- evaluate value for every polygon
      double value =
          evaluateSinglePolygon(
              p.getGeometry(), fqTree, attributeName, attributeOp, spatialRelation, bufferRadius);
      Feature fcopy = copyFeature(p, targetFSnew);
      fcopy.setAttribute(newAttributeName, new Double(value));
      outPolys.add(fcopy);
    }
    fd = new FeatureDataset(targetFSnew);
    fd.addAll(outPolys);
    return fd;
  }
 /**
  * Creates a RubberSheetTransform using the given triangulation.
  *
  * @param triangleMap a map of source Triangle to destination Triangle
  */
 public BilinearInterpolatedTransform(Map triangleMap, TaskMonitor monitor) {
   this.triangleMap = triangleMap;
   this.monitor = monitor;
   monitor.report("Transforming...");
 }