/* 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.spatialoperations.internal.processmanager; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.logging.Logger; import net.refractions.udig.catalog.IGeoResource; import net.refractions.udig.project.ILayer; import net.refractions.udig.project.IMap; import net.refractions.udig.project.internal.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.geotools.data.DataUtilities; import org.geotools.data.FeatureStore; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.filter.FidFilter; import org.geotools.filter.FilterFactory; import org.geotools.filter.FilterFactoryFinder; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.GeometryDescriptor; 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.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import es.axios.udig.spatialoperations.internal.i18n.Messages; import es.axios.udig.spatialoperations.internal.parameters.IClipInExistentLayerParameters; import es.axios.udig.spatialoperations.internal.parameters.IClipInNewLayerParameters; import es.axios.udig.spatialoperations.internal.parameters.IClipParameters; import es.axios.udig.ui.commons.mediator.AppGISMediator; import es.axios.udig.ui.commons.util.GeoToolsUtils; import es.axios.udig.ui.commons.util.LayerUtil; import es.axios.udig.ui.commons.util.MapUtil; /** * Clip Process * <p> * Uses the feature's geometries of clipping collection to intersect the features contained in * clipped collection. * </p> * <p> * This transaction must: * <ul> * <li>create new features using the data of original features, for those features that were * broken. * <li>delete the features included in clip area   * <li>clip geometry of features that intersect. * <ul> * </p> * * @author Mauricio Pazos (www.axios.es) * @author Gabriel Roldan (www.axios.es) * @since 1.1.0 */ final class ClipProcess extends AbstractProcess { private static final Logger LOGGER = Logger .getLogger(ClipProcess.class .getName()); private static final FilterFactory FILTER_FACTORY = FilterFactoryFinder .createFilterFactory(); private IClipInExistentLayerParameters paramsClipInExistentLayer = null; private IClipInNewLayerParameters paramsClipInNewLayer = null; private GeometryDescriptor geomAttrType = null; private ILayer targetLayer; private FeatureStore<SimpleFeatureType, SimpleFeature> targetStore; private ILayer clippingLayer; private ILayer layerToClip; private IMap map; private FeatureCollection<SimpleFeatureType, SimpleFeature> clippingFeatures; private FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToClip; private IGeoResource targetGeoResource; private java.util.Map<String, FeatureTransaction> transactionTrack = new HashMap<String, FeatureTransaction>(); /** * new instance of clip process * * @param paramsClipInExistentLayer */ public ClipProcess( final IClipParameters params ) { if(params instanceof IClipInExistentLayerParameters){ this.paramsClipInExistentLayer = (IClipInExistentLayerParameters)params; } else if (params instanceof IClipInNewLayerParameters){ this.paramsClipInNewLayer = (IClipInNewLayerParameters) params; } else{ assert false; //illegal parameter } } /** * @return the FeatureStore<SimpleFeatureType, SimpleFeature> of target layer */ private FeatureStore<SimpleFeatureType, SimpleFeature> getTargetStore() { assert this.targetStore != null; return this.targetStore; } /** * Runs the clip process * @throws SOProcessException */ @Override public final void run( IProgressMonitor monitor ) throws SOProcessException { // initalization init(monitor); final String msg = MessageFormat.format(Messages.ClipProcess_clipping_with, layerToClip.getName(), clippingLayer.getName()); getMonitor().subTask(msg); getMonitor().worked(1); int count = computeCount(); getMonitor().beginTask(msg, count); // gets the crs of layers and map final CoordinateReferenceSystem clippingCrs = LayerUtil.getCrs(clippingLayer); final CoordinateReferenceSystem mapCrs = MapUtil.getCRS(clippingLayer.getMap()); final CoordinateReferenceSystem featureToClipCrs = LayerUtil.getCrs(layerToClip); FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToClip = this.featuresToClip; // save the name of geometry attribute FeatureCollection<SimpleFeatureType, SimpleFeature> clipping = this.clippingFeatures; FeatureIterator<SimpleFeature> iter = null; try { iter = clipping.features(); while( iter.hasNext() ) { checkCancelation(); SimpleFeature clippingFeature = iter.next(); clipFeatureCollectionUsingClippingFeature( featuresToClip, featureToClipCrs, clippingFeature, clippingCrs, mapCrs); getMonitor().worked(1); } } catch (InterruptedException e) { final String cancelMsg = Messages.ClipProcess_clip_was_canceled; throw new SOProcessException(cancelMsg); } finally { if (iter != null) { clipping.close(iter); } endProcess((Map) this.map, this.targetLayer); final String endMsg = Messages.ClipProcess_successful; monitor.subTask(endMsg); monitor.done(); } } /** * Compute the count of features to process. * * If overflow occur retruns MaxInteger. * * @return the count or Integer.MAX_VALUE by overflow */ private int computeCount() { int count; try{ count = this.clippingFeatures.size() * this.featuresToClip.size(); }catch(ArithmeticException e ){ count = Integer.MAX_VALUE; } return count; } /** * @return true if a new layer is required. */ private final boolean isRequiredCreateNewLayer() { return (this.paramsClipInNewLayer != null) ; } /** * Initializes the clip process * * @param params */ protected void init(final IClipParameters params){ this.clippingLayer = params.getClippingLayer(); assert this.clippingLayer != null; this.layerToClip = params.getLayerToClip(); assert this.layerToClip != null; this.map = this.clippingLayer.getMap(); assert this.map != null; this.clippingFeatures = params.getClippingFeatures(); assert this.clippingFeatures != null; this.featuresToClip = params.getFeaturesToClip(); assert this.featuresToClip != null; } /** * Initialzie the process taking into account if the required target is an * existnet layer or a new layer * * @param monitor */ @Override protected void init( final IProgressMonitor monitor ) throws SOProcessException{ try { super.init(monitor); if (this.paramsClipInExistentLayer != null) { init(this.paramsClipInExistentLayer); this.targetLayer = this.paramsClipInExistentLayer.getTargetLayer(); this.targetStore = getFeatureStore(targetLayer); setGeomAttrTypeToClip(this.targetLayer.getSchema()); } else if (this.paramsClipInNewLayer != null) { init(this.paramsClipInNewLayer); // create new layer (store and resource) with the feature type required SimpleFeatureType type = this.paramsClipInNewLayer.getTargetFeatureType(); this.targetGeoResource = AppGISMediator.createTempGeoResource(type); assert this.targetGeoResource != null; this.targetStore = this.targetGeoResource.resolve(FeatureStore.class, monitor); setGeomAttrTypeToClip(type); this.targetLayer = addLayerToMap(this.map, this.targetGeoResource); } assert this.targetLayer != null; assert this.targetStore != null; } catch (IOException e) { final String msg = MessageFormat .format( Messages.ClipProcess_failed_creating_temporal_store, e.getMessage()); LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * Extracts the attribute type of geometry and sets the instance variable * for this process * * @param featureType */ private final void setGeomAttrTypeToClip( SimpleFeatureType featureType ) { GeometryDescriptor type = featureType.getDefaultGeometry(); assert type != null; this.geomAttrType = type; } private final GeometryDescriptor getGeomAttrTypeToClip(){ assert this.geomAttrType != null; return this.geomAttrType; } /** * Clips the feature collection using the clippingFeature * * @param featureCollectionToClip feature collection to clip * @param featureToClipCrs crs * @param clippingFeature feature used to clip the feature collection * @param clippingCrs crs * @param mapCrs crs * @throws SOProcessException */ private final void clipFeatureCollectionUsingClippingFeature( final FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollectionToClip, final CoordinateReferenceSystem featureToClipCrs, final SimpleFeature clippingFeature, final CoordinateReferenceSystem clippingCrs, final CoordinateReferenceSystem mapCrs ) throws SOProcessException { FeatureIterator<SimpleFeature> iter = null; try { Geometry clippingFeatureGeometry = (Geometry) clippingFeature.getDefaultGeometry(); // iterate for each clipping feature'geometry (general case is collection, particular case only one geometry) for(int i=0; i < clippingFeatureGeometry.getNumGeometries(); i++){ Geometry clippingGeometry = clippingFeatureGeometry.getGeometryN(i); Geometry clippingGeometryOnMap = GeoToolsUtils.reproject(clippingGeometry, clippingCrs, mapCrs); // iterates in the collection to clip and does the geometry clip using the clipping geometry iter = featureCollectionToClip.features(); while( iter.hasNext() ) { checkCancelation(); SimpleFeature featureToClip = iter.next(); // The feature selected to clip could have been changed in previous iteration // then is necessary check the "transaction trak" String fid = featureToClip.getID(); if( this.transactionTrack.containsKey(fid) ){ // the feature was changed before and requires more changes (clipping) clipChangedFeatureUsing( featureToClip, featureToClipCrs, clippingGeometryOnMap, mapCrs); } else { // first time that this feature will be precessed clipFeatureUsing( featureToClip, featureToClipCrs, clippingGeometryOnMap, mapCrs); } getMonitor().worked(1); } } } catch (Exception e) { final String emsg = e.getMessage(); LOGGER.severe(emsg); throw new SOProcessException(emsg); } finally { if (iter != null) { featureCollectionToClip.close(iter); } } } /** * The feature to clip, was changed before and requires more precessing * * @param featureToClip * @param featureToClipCrs * @param clippingGeometryOnMap * * @param mapCrs * @throws SOProcessException */ private void clipChangedFeatureUsing(SimpleFeature featureToClip, CoordinateReferenceSystem featureToClipCrs, Geometry clippingGeometryOnMap, CoordinateReferenceSystem mapCrs ) throws SOProcessException { String fid = featureToClip.getID(); FeatureTransaction tx = this.transactionTrack.get(fid); assert tx != null; switch( tx.getType() ) { case DELETE : // does not process break; case SPLIT : // precesses the fragments List<String> fidList = tx.getListInsertedFeatures(); FeatureCollection<SimpleFeatureType, SimpleFeature> fragmentCollection = findFeatures(this.targetStore, fidList); FeatureIterator<SimpleFeature> fragmentIterator = null; try{ fragmentIterator = fragmentCollection.features(); while( fragmentIterator.hasNext() ) { SimpleFeature fragment = fragmentIterator.next(); clipFeatureUsing( fragment, featureToClipCrs, clippingGeometryOnMap, mapCrs); } } finally { if (fragmentIterator != null) fragmentCollection.close(fragmentIterator); } break; case UPDATE : // the feature require more changes // retrieves the changed feature and applies it the clip fid = tx.getUpdatedFeature(); SimpleFeature updatedFeature = findFeature(this.targetStore, fid); clipFeatureUsing( updatedFeature, featureToClipCrs, clippingGeometryOnMap, mapCrs); break; default: assert false; // impossible case } } /** * Retrieves the features from sotre * * @param store target feature sotore * @param fidList * * @return the feature collection * @throws SOProcessException */ private FeatureCollection<SimpleFeatureType, SimpleFeature> findFeatures( FeatureStore<SimpleFeatureType, SimpleFeature> store, List<String> fidList ) throws SOProcessException { try { FidFilter filter = FILTER_FACTORY.createFidFilter(); filter.addAllFids(fidList); FeatureCollection<SimpleFeatureType, SimpleFeature> features = store.getFeatures(filter); return features; } catch (IOException e) { final String msg = e.getMessage(); LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * Retrieve the feature from store * * @param store * @param fid * @return * @throws SOProcessException */ private SimpleFeature findFeature( FeatureStore<SimpleFeatureType, SimpleFeature> store, String fid) throws SOProcessException { try { FidFilter filter = FILTER_FACTORY.createFidFilter(fid); FeatureCollection<SimpleFeatureType, SimpleFeature> featuresCollection = store.getFeatures(filter); FeatureIterator<SimpleFeature> iter = featuresCollection.features(); assert iter.hasNext(); SimpleFeature feature = iter.next(); return feature; } catch (IOException e) { final String msg = e.getMessage(); LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * * * @param featureCollection * @return feature list */ private List<SimpleFeature> toList( final FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) { List<SimpleFeature> list = new LinkedList<SimpleFeature> (); FeatureIterator<SimpleFeature> iterator = featureCollection.features(); while( iterator.hasNext() ) { SimpleFeature feature = iterator.next(); list.add(feature); } featureCollection.close(iterator); return list; } /** * Clips the feature using the clipping geometry. * * @param featureToClip * @param featureToClipCrs * @param clippingGeometryOnMap * @param mapCrs * * @return the list of features resultant of process. * @throws SOProcessException */ private void clipFeatureUsing(final SimpleFeature featureToClip, final CoordinateReferenceSystem featureToClipCrs, final Geometry clippingGeometryOnMap, final CoordinateReferenceSystem mapCrs) throws SOProcessException { try { Geometry featureGeometryToClip = (Geometry) featureToClip.getDefaultGeometry(); // iterate in the feature's geometries to clip and applies the delete, split or difference operation // using the clipping geometry int resultGeomSize = featureGeometryToClip.getNumGeometries(); List<Geometry> resultClipGeom = new ArrayList<Geometry>(resultGeomSize); for( int i = 0; i < featureGeometryToClip.getNumGeometries(); i++ ) { Geometry simpleGeomToClip = featureGeometryToClip.getGeometryN(i); Geometry featureGeometryOnMapCrs = GeoToolsUtils.reproject(simpleGeomToClip, featureToClipCrs, mapCrs); // Analyses the geometry's positon if (clippingGeometryOnMap.contains(featureGeometryOnMapCrs)) { continue; // it will be deleted from target (if "to clip layer" is equal to target layer) // or does not add this geometry to result } else if (splits(clippingGeometryOnMap, featureGeometryOnMapCrs)) { Geometry splitGeom = computeGeometrySplit(featureGeometryOnMapCrs, clippingGeometryOnMap, mapCrs); resultClipGeom.add(splitGeom); } else if (clippingGeometryOnMap.intersects(featureGeometryOnMapCrs)) { Geometry diffGeom = computeGeometryDifference(featureGeometryOnMapCrs, clippingGeometryOnMap, mapCrs); resultClipGeom.add(diffGeom); } } //postcondition: {resultClipGeom is a Collection with Geometries modified with difference, deleted and splited geom (new geom) } GeometryFactory factory = featureGeometryToClip.getFactory(); GeometryCollection resultCollection = factory.createGeometryCollection(resultClipGeom.toArray(new Geometry[]{})); updateTargetWith(featureToClip, resultCollection); } catch (Exception e) { LOGGER.severe(e.getMessage()); throw new SOProcessException(Messages.ClipProcess_failed_executing_reproject); } } /** * Analyses the geometry result to apply clip method to a feature and update the target store. * <p> * Theses are the different situations to analyse: * * <ul> * <li>resultCollection is empty: the feature must be deleted.</li> * * <li>resultCollection is a GeometryCollection (not empty) and the feature to clip is GeometryCollection * compatible, the method adds the new geometry.</li> * * <li>resultCollection is a GeometryCollection (not empty) and the feature to clip is "simple geometry", * then if the "to Clip" layer and Target layer are equals requires to create a new feature and delete the source feature, * esle only add the new features in the target. * </li> * </ul> * </p> * * @param featureToClip feature that contains the source geometry (to clip geometry) * @param resultCollection resultClipGeom is a Collection with geometries modified with difference, deleted or splited geom (new geom) * @throws SOProcessException */ private void updateTargetWith(final SimpleFeature featureToClip, final GeometryCollection resultCollection) throws SOProcessException{ final CoordinateReferenceSystem mapCrs = MapUtil.getCRS(this.map); final CoordinateReferenceSystem targetCrs = LayerUtil.getCrs(this.targetLayer); Geometry geomResultOnTarget; try { geomResultOnTarget = GeoToolsUtils.reproject(resultCollection, mapCrs, targetCrs); } catch (Exception e) { throw new SOProcessException(e.getMessage()); } String fidToClip = featureToClip.getID(); FeatureTransaction tx; if( geomResultOnTarget.isEmpty() ){ // the feature must be deleted form target (or no apeare in the result) if(this.layerToClip.equals(this.targetLayer)){ deleteFeature(featureToClip); } // else does not add it in target layer //registers the delete transaction in track tx = FeatureTransaction.createDeleteTransaction(fidToClip); this.transactionTrack.put(fidToClip, tx); } else { // resultCollection is not empty Geometry geometryToClip = (Geometry) featureToClip.getDefaultGeometry(); if(geometryToClip instanceof GeometryCollection){ // target must be GeometryCollection compatible too // modified geometries case if(this.layerToClip.equals(this.targetLayer)){ // updates the feature modifyFeatureInStore(featureToClip, geomResultOnTarget, this.targetStore); tx = FeatureTransaction.createUpdateTransaction(fidToClip, fidToClip); this.transactionTrack.put(fidToClip, tx); } else { // creates a new feature in the target layer String newFid = createFeatureInStore(geomResultOnTarget, this.targetStore); tx = FeatureTransaction.createUpdateTransaction(fidToClip, newFid); this.transactionTrack.put(fidToClip, tx); } } else { // geometryToClip is "simple geometry" // creates new features for each new fragment. if toClip layer and target are equals // inserts the fragments and delete the original feature // else only inserts the new fragments List<SimpleFeature> featureList = createFeatureFragments(featureToClip, (GeometryCollection)geomResultOnTarget, true); assert ! featureList.isEmpty(); List<String> newFidList = new LinkedList<String>(); for( SimpleFeature feature : featureList ) { String newFid = insertFeatureInStore(feature, this.targetStore); newFidList.add(newFid); } if( this.layerToClip.equals(this.targetLayer) ){ deleteFeature(featureToClip); } tx = FeatureTransaction.createSplitTransaction(fidToClip, newFidList); this.transactionTrack.put(fidToClip, tx); } } } /** * Updates the target store with the clipped geometry * * @param targetStore * @param featureToClip * @param clippedGeometry * * @throws SOProcessException */ // private void updateTarget(final FeatureStore<SimpleFeatureType, SimpleFeature> targetStore, // final SimpleFeature featureToClip, // final Geometry clippedGeometry ) // throws SOProcessException { // // // if the feature have some geometry updates the store, else the featue // // have not geometry as result of this process then deletes the feature. // try { // if (clippedGeometry.isEmpty()) { // // deleteFeature(featureToClip); // // } else { // // creates the new geometry using the result geometry of clip process // // and sets it in the feature // // Geometry featureGeometry = featureToClip.getDefaultGeometry(); // GeometryFactory geomFac = featureGeometry.getFactory(); // Geometry newClippedGeomery = geomFac.createGeometryCollection( // clippedGeometry.toArray(new Geometry[]{})); // // applies the change in the store // if (requireCreateFeatureForFragment(this.targetStore)) { // // final CoordinateReferenceSystem mapCrs = MapUtil.getCRS(clippingLayer.getMap()); // SimpleFeatureType featureType = featureToClip.getFeatureType(); // final CoordinateReferenceSystem targetLayerCrs = featureType // .getDefaultGeometry() // .getCoordinateSystem(); // updateStoreWithMultiGeom(this.targetStore, targetLayerCrs, featureToClip, // mapCrs, (GeometryCollection) newClippedGeomery); // // } else { // featureToClip.setDefaultGeometry(newClippedGeomery); // if (this.targetLayer.equals(this.layerToClip)) { // // modifies the feature's geometry in the store // modifyFeatureInStore(featureToClip, newClippedGeomery, this.targetStore); // // } else { // create a feature in the new layer with the clipped geometry // // createFeatureInStore(newClippedGeomery, this.targetStore); // } // } // // } // } catch (Exception e) { // // LOGGER.severe(e.getMessage()); // throw new SOProcessException(Messages.ClipProcess_failed); // } // // } /** * Updates the feature in store using the Geometry Collection * * @param store * @param targetLayerCrs * @param feature * @param mapCrs * @param resultGeometry * * @throws OperationNotFoundException * @throws TransformException * @throws SOProcessException */ private void updateStoreWithMultiGeom( final FeatureStore<SimpleFeatureType, SimpleFeature> store, final CoordinateReferenceSystem targetLayerCrs, final SimpleFeature feature, final CoordinateReferenceSystem mapCrs, final GeometryCollection resultGeometry ) throws SOProcessException { Geometry adjustedGeom; if( requiresAdjust(feature, store) ){ adjustedGeom = adjustGeometryAttribute(resultGeometry, feature); } else{ adjustedGeom = resultGeometry; } Geometry finalGeometry; try { finalGeometry = GeoToolsUtils.reproject(adjustedGeom, mapCrs, targetLayerCrs); } catch (Exception e) { throw new SOProcessException(e.getMessage()); } // create the clip geometry and set the feature if (this.layerToClip.equals(this.targetLayer)) { // modifies the feature's geometry in the store modifyFeatureInStore(feature, finalGeometry, store); } else { createFeatureInStore(finalGeometry, store); } } /** * Analyses the target store, then if it was defined as simple geometry then require * a feature for each geometry fragment to store the clip result. * * @return true if the strore requires to create fragments, false in other case */ private final boolean requireCreateFeatureForFragment(FeatureStore<SimpleFeatureType, SimpleFeature> store) { final SimpleFeatureType featureType = store.getSchema(); GeometryDescriptor geomAttr = featureType.getDefaultGeometry(); assert geomAttr != null; Class targetGeom = geomAttr.getType().getBinding(); return Point.class.equals(targetGeom) || LineString.class.equals(targetGeom) || Polygon.class.equals(targetGeom); } /** * Evaluates if clippingGeometry divides or not the feature's geometry. The params featureGeometry cannot be * instance of Geometry Collection (Muli ....) * * @param clippingGeometry * @param featureGeometry * @return true if clippingGeometry divides the feature's geometry, false in other case. */ private final boolean splits( final Geometry clippingGeometry, final Geometry featureGeometry ) { assert ! (featureGeometry instanceof GeometryCollection); if(featureGeometry instanceof Point){ return false; // cannot split a point } // If the result of difference is a multygeometry then the geometry is splitted. Geometry geoDiff = featureGeometry.difference(clippingGeometry); boolean isGeometryCollection = geoDiff instanceof GeometryCollection; boolean intersects = featureGeometry.intersects(clippingGeometry); return isGeometryCollection && intersects; } /** * Makes the geometry difference between the feature's geometry and the clipping geometry. If the * features type is geometry or multigeometry (geometry collection ) then modifies the feature's geometry. * If the feature type has a simple geometry (point, polygon, line) then makes new geometries for each. * * @param featureCrs * @param featureGeometryOnMapCrs * @param clippingGeometryOnMapCrs * @param mapCrs * * @return splited geometry. * * @throws SOProcessException */ private GeometryCollection computeGeometrySplit( final Geometry featureGeometryOnMapCrs, final Geometry clippingGeometryOnMapCrs, final CoordinateReferenceSystem mapCrs ) throws SOProcessException { try { // does the difference Geometry geoDiff = featureGeometryOnMapCrs.difference(clippingGeometryOnMapCrs); assert geoDiff instanceof GeometryCollection; GeometryCollection geoCollection = (GeometryCollection) geoDiff; return geoCollection; } catch (Exception e) { final String msg = e.getMessage(); LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * Creates new features for each geometry fragmens. * * @param featurePrototype prototype used to create the new features * @param geomFragmentCollection geometry fragment (split result) * @param requiresCopyData true to copy the prototype data to the new features * * @throws SOProcessException * * @return list of new SimpleFeature */ private final List<SimpleFeature> createFeatureFragments( final SimpleFeature featurePrototype, final GeometryCollection geomFragmentCollection, final boolean requiresCopyData) throws SOProcessException { List<SimpleFeature> featureList = new LinkedList<SimpleFeature>(); // creates a new feature for each geometry fragment GeometryCollection geomList = geomFragmentCollection; for( int i = 0; i < geomList.getNumGeometries(); i++ ) { Geometry geomFragment = geomList.getGeometryN(i); SimpleFeature newFeature = createFeature(featurePrototype, geomFragment, requiresCopyData); featureList.add(newFeature); } return featureList; } /** * @param featurePrototype * @param newGeometry * @param requiresCopyData * @return * @throws SOProcessException */ private final SimpleFeature createFeature( final SimpleFeature featurePrototype, final Geometry newGeometry, final boolean requiresCopyData) throws SOProcessException { try { // create the new feature and set the geometry fragment SimpleFeature newFeature = DataUtilities.template(featurePrototype.getFeatureType()); // copies the data in the new feature if (requiresCopyData) { GeoToolsUtils.match(featurePrototype, newFeature); } newFeature.setDefaultGeometry(newGeometry); return newFeature; } catch (Exception e) { final String msg = e.getMessage(); LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * Test if the feature geometry requires adjust to the target store geometry. The feature's * geometry only requires adjust if it es a simple geometry (point, linestring, ploygon) and the * target store is a GeometryCollection (Mulipoint, MultiLinestring, MultiPolygon). * * @param feature * @param targetStore * @return true if the feature requires adjust its geometry */ private boolean requiresAdjust( final SimpleFeature feature, final FeatureStore<SimpleFeatureType, SimpleFeature> targetStore ) { Geometry geom = (Geometry) feature.getDefaultGeometry(); boolean featureGeomIsSimple = (geom instanceof Point) || (geom instanceof LineString) || (geom instanceof Polygon); GeometryDescriptor geomAttType = targetStore.getSchema().getDefaultGeometry(); Class targetGeom = geomAttType.getClass(); boolean targetStoreIsMulty = MultiPoint.class.equals(targetGeom) || MultiLineString.class.equals(targetGeom) || MultiPolygon.class.equals(targetGeom); return featureGeomIsSimple && targetStoreIsMulty; } /** * Deletes the feature if the target layer is equal to the layer to clip * * @param feature */ private void deleteFeature(final SimpleFeature feature ) { try { //Deletes only if the target is equal to the layer to clip if(this.layerToClip.equals(this.targetLayer)){ FidFilter filter = FILTER_FACTORY.createFidFilter(feature.getID()); FeatureStore<SimpleFeatureType, SimpleFeature> store = getTargetStore(); store.removeFeatures(filter); } } catch (IOException e) { final String msg = Messages.ClipProcess_failed_deleting; LOGGER.severe(msg); throw (RuntimeException) new RuntimeException(msg).initCause( e ); } } /** * Modifies the geometry of feature doing the difference with the clipping geometry. * precondition: this method supposes that clipping does not contain featrue's geometry but * intersects it. * * <p> * Note: Geometry could contain clipping then feature with hole is required. * This process does not produce hole geometry. * In this case this method returns th original geometry without modifications. * </p> * * @param targetLayerCrs * @param featureGeometryOnMap * @param clippingGeometryOnMap * @param mapCrs * @return Modify Geometry or the original if it require hole * @throws SOProcessException */ private final Geometry computeGeometryDifference( Geometry featureGeometryOnMap, Geometry clippingGeometryOnMap, CoordinateReferenceSystem mapCrs ) throws SOProcessException { // assert: clipping does not contains featrue's geometry but intersect FeatureStore<SimpleFeatureType, SimpleFeature> store = getTargetStore(); final SimpleFeatureType featureType = store.getSchema(); final CoordinateReferenceSystem targetLayerCrs = featureType.getDefaultGeometry() .getCRS(); Geometry resultGeometry = null; try { if (!featureGeometryOnMap.contains(clippingGeometryOnMap)) { // clipping does not contain the feature and features does not contain clipping area // then constructs the following difference: feature'geometry - clipping area Geometry diffGeometry = featureGeometryOnMap.difference(clippingGeometryOnMap); Geometry adjustedGeom = GeoToolsUtils.reproject(diffGeometry, mapCrs, targetLayerCrs); resultGeometry = adjustedGeom; } else { // Note: if the condition is false, feature's geometry could contain clipping // then feature with hole is required. This process does not produce hole geometry. resultGeometry = featureGeometryOnMap; } return resultGeometry; } catch (OperationNotFoundException e) { final String msg = MessageFormat.format(Messages.ClipProcess_failed_executing_reproject, mapCrs.getName(), targetLayerCrs.getName()); LOGGER.severe(msg); throw new SOProcessException(msg); } catch (TransformException e) { final String msg = MessageFormat.format(Messages.ClipProcess_failed_transforming, mapCrs.getName(), targetLayerCrs.getName()); LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * Creates a new feature in the strore using the geometry * * @param finalGeometry * @param store * @return the feature id * @throws SOProcessException */ private String createFeatureInStore( final Geometry finalGeometry, final FeatureStore<SimpleFeatureType, SimpleFeature> store ) throws SOProcessException{ SimpleFeature newFeature; try { newFeature = DataUtilities.template(store.getSchema()); newFeature.setDefaultGeometry(finalGeometry); Set fidSet = store.addFeatures(DataUtilities.collection(new SimpleFeature[]{newFeature})); Iterator iter = fidSet.iterator(); assert iter.hasNext(); String fid = (String)iter.next(); return fid; } catch (Exception e) { final String msg = Messages.ClipProcess_failed_creating_new_feature ; LOGGER.severe(msg); throw new SOProcessException(msg); } } private String insertFeatureInStore( final SimpleFeature feature, final FeatureStore<SimpleFeatureType, SimpleFeature> store ) throws SOProcessException{ try { Set fidSet = store.addFeatures(DataUtilities.collection(new SimpleFeature[]{feature})); Iterator iter = fidSet.iterator(); assert iter.hasNext(); String fid = (String)iter.next(); return fid; } catch (Exception e) { final String msg = Messages.ClipProcess_failed_creating_new_feature ; LOGGER.severe(msg); throw new SOProcessException(msg); } } /** * Sets the new geometry in the feature and register the modification in the store * * @param featureToModify * @param finalGeometry * @param store * @throws SOProcessException */ private final void modifyFeatureInStore(SimpleFeature featureToModify, Geometry finalGeometry, FeatureStore<SimpleFeatureType, SimpleFeature> store ) throws SOProcessException{ // modifies the feature's geometry in the store FidFilter filter = FILTER_FACTORY.createFidFilter(featureToModify.getID()); GeometryDescriptor geomAttr = getGeomAttrTypeToClip(); try { store.modifyFeatures(geomAttr, finalGeometry, filter); } catch (IOException e) { final String msg = e.getMessage(); LOGGER.severe(msg); throw new SOProcessException(msg); } } }