/* 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.tasks; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import org.geotools.data.DataUtilities; import org.geotools.data.FeatureEvent; import org.geotools.data.FeatureEvent.Type; import org.geotools.data.FeatureListener; import org.geotools.data.FeatureStore; import org.geotools.data.Query; import org.geotools.data.Transaction; import org.geotools.data.simple.SimpleFeatureStore; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.geotools.referencing.CRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.Id; import org.opengis.filter.identity.FeatureId; import org.opengis.filter.spatial.BBOX; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import es.axios.geotools.util.FeatureUtil; import es.axios.geotools.util.GeoToolsUtils; import es.axios.lib.geometry.util.GeometryUtil; import es.axios.udig.spatialoperations.internal.i18n.Messages; /** * Implements the common behavior for spatial operations task. * * @author Mauricio Pazos (www.axios.es) * @author Aritz Davila (www.axios.es) * */ abstract class AbstractSpatialOperationTask<V> { private static final Logger LOGGER = Logger.getLogger(AbstractSpatialOperationTask.class .getName()); protected SimpleFeatureStore targetStore = null; protected static final FilterFactory FILTER_FACTORY = CommonFactoryFinder .getFilterFactory(null); public V call() throws Exception { assert targetStore != null : "target store is null!"; //$NON-NLS-1$ addListeners(); perform(); return getResult(); } /** * Performs the algorithm. The subclass must provide an implementation. */ protected abstract void perform() throws SpatialOperationException; /** * @return the task result. The subclass must provide an implementation. */ protected abstract V getResult(); /** * Standard exception handle. * * @param e * @return SpatialDataProcessException */ protected SpatialOperationException createException(Exception e) { LOGGER.severe(e.getMessage()); final String msg = Messages.AbstractSpatialOperationTask_failed_inserting; SpatialOperationException processException = new SpatialOperationException(msg); return processException; } /** * Standard exception handle. * * @param msg * @return SpatialDataProcessException */ protected SpatialOperationException createException(final String msg) { LOGGER.severe(msg); return new SpatialOperationException(msg); } /** * Logs the exception message and creates an * {@link SpatialOperationException} * * @param e * * @return {@link SpatialOperationException} */ protected SpatialOperationException makeException(final Exception e) { final String msg = e.getMessage(); LOGGER.severe(msg); e.printStackTrace(); return new SpatialOperationException(e); } /** * Logs the user message and build the {@link SpatialOperationException} * * @param e * @param messageToUser * @return {@link SpatialOperationException} */ protected SpatialOperationException makeException(final Exception e, final String messageToUser) { final String msg = e.getMessage(); LOGGER.severe(msg); e.printStackTrace(); SpatialOperationException ex = new SpatialOperationException(messageToUser); LOGGER.severe(msg); return ex; } /** * Creates a new feature in the store using the geometry. This method copies * the data present in the "to clip feature" as source to create the new * feature. * * @param store * @param feature * @return the new feature * @throws SpatialOperationException */ // TODO REFACTOR. protected SimpleFeature createFeatureInStore(final FeatureStore<SimpleFeatureType, SimpleFeature> store, final SimpleFeature feature, final Class<? extends Geometry> expectedClass, final boolean isCreatingNewLayer) throws SpatialOperationException { Transaction transaction = store.getTransaction(); List<FeatureId> fidSet; try { // project the feature geometry on store's CRS Geometry geomProjected = GeoToolsUtils.reproject( (Geometry) feature.getDefaultGeometry(), feature.getFeatureType().getCoordinateReferenceSystem(), store.getSchema().getCoordinateReferenceSystem()); SimpleFeature newFeature = DataUtilities.template(store.getSchema()); Geometry adaptedGeometry = GeometryUtil.adapt(geomProjected, expectedClass); newFeature.setDefaultGeometry(adaptedGeometry); if (isCreatingNewLayer) { newFeature = FeatureUtil.copyAttributes(feature, newFeature); } // clear the list. addedID.clear(); committedID.clear(); fidSet = store.addFeatures(DataUtilities.collection(new SimpleFeature[] { newFeature })); transaction.commit(); retrieveAfterCommit(); String fid; if (addedID.size() != 0) { fid = getCommittedFID(); } else { FeatureId id = fidSet.iterator().next(); fid = id.getID(); } addedID.clear(); committedID.clear(); SimpleFeature storedFeature = findFeature(store, fid); assert storedFeature != null; return storedFeature; } catch (Exception e) { try { transaction.rollback(); } catch (IOException e1) { e1.printStackTrace(); throw makeException(e1); } final String msg = Messages.AbstractCommonTask_failed_creating_layer; LOGGER.severe(msg); throw makeException(e, msg); } finally { try { transaction.close(); } catch (IOException e) { throw makeException(e); } } } /** * Retrieves the feature from store. The feature must exist in the store. * * @param store * @param requestedFid * id of an existent feature * * @return the found feature * @throws SpatialOperationException */ protected SimpleFeature findFeature(final FeatureStore<SimpleFeatureType, SimpleFeature> store, final String requestedFid) throws SpatialOperationException { FeatureCollection<SimpleFeatureType, SimpleFeature> featuresCollection = null; FeatureIterator<SimpleFeature> iter = null; try { Id filter = getFilterId(requestedFid); featuresCollection = store.getFeatures(filter); iter = featuresCollection.features(); assert iter.hasNext() : "feature not found :" + requestedFid; //$NON-NLS-1$ SimpleFeature feature = iter.next(); return feature; } catch (IOException e) { final String msg = e.getMessage(); LOGGER.severe(msg); throw makeException(e, msg); } finally { if (iter != null) { iter.close(); } } } protected SimpleFeature insertFeature(FeatureStore<SimpleFeatureType, SimpleFeature> store, SimpleFeature newFeature) throws SpatialOperationException { Transaction transaction = store.getTransaction(); List<FeatureId> newIds; try { // clear the list. addedID.clear(); committedID.clear(); newIds = store.addFeatures(DataUtilities.collection(new SimpleFeature[] { newFeature })); if (newIds.size() != 1) { final String msg = Messages.DissolveTask_failed_inserting; throw createException(msg); } transaction.commit(); retrieveAfterCommit(); String fid; if (addedID.size() != 0) { fid = getCommittedFID(); } else { FeatureId id = newIds.iterator().next(); fid = id.getID(); } addedID.clear(); committedID.clear(); SimpleFeature insertedFeature = findFeature(store, fid); return insertedFeature; } catch (IOException e) { try { transaction.rollback(); } catch (IOException e1) { throw createException(e1); } throw createException(e); } finally { try { transaction.close(); } catch (IOException e) { e.printStackTrace(); throw createException(e); } } } /** * Adds the features in the store. * * @param features * @param store * * @return set of new identifier for the created features * * @throws SpatialOperationException */ protected Set<String> insertFeaturesInStore(final Set<SimpleFeature> features, final FeatureStore<SimpleFeatureType, SimpleFeature> store) throws SpatialOperationException { Transaction transaction = store.getTransaction(); try { SimpleFeature[] featuresArray = features.toArray(new SimpleFeature[features.size()]); // clear the list. addedID.clear(); committedID.clear(); List<FeatureId> fidSet = store.addFeatures(DataUtilities.collection(featuresArray)); transaction.commit(); retrieveAfterCommit(); Set<String> fid = new HashSet<String>(); if (addedID.size() != 0) { fid = getCommittedFIDs(); } else { FeatureId id = fidSet.iterator().next(); fid.add(id.getID()); } addedID.clear(); committedID.clear(); // Set<String> insertedID = new HashSet<String>(); // for (FeatureId id : fidSet) { // insertedID.add(id.getID()); // } return fid; } catch (Exception e) { try { transaction.rollback(); } catch (IOException e1) { e1.printStackTrace(); } e.printStackTrace(); final String msg = e.getMessage(); LOGGER.severe(msg); throw makeException(e, msg); } finally { try { transaction.close(); } catch (IOException e) { e.printStackTrace(); throw makeException(e); } } } /** * Will use a query created when features were added to the store to * retrieve the same features but whit different ID because they were * committed. * * @throws IOException */ protected void retrieveAfterCommit() throws IOException { if (!addedID.isEmpty()) { assert query != null : "query musn't be null."; //$NON-NLS-1$ FeatureCollection<SimpleFeatureType, SimpleFeature> collection = null; FeatureIterator<SimpleFeature> iter = null; try { collection = targetStore.getFeatures(query); iter = collection.features(); while (iter.hasNext()) { SimpleFeature feature = iter.next(); committedID.add(feature.getID()); } } finally { if (iter != null) { iter.close(); } } } } /** * Search for the ID contained on the committedID set that isn't contained * on the addedID set. * * @return The ID of the feature added after being committed. */ private String getCommittedFID() { for (String idAfter : committedID) { // we assume this ID is the new one. boolean newID = true; for (String idBefore : addedID) { if (idBefore.equals(idAfter)) { newID = false; break; } } if (newID) { return idAfter; } } // instead of returning null return addedID.iterator().next(); } private Set<String> getCommittedFIDs() { Set<String> newIDs = new HashSet<String>(); for (String idAfter : committedID) { // we assume this ID is the new one. boolean newID = true; for (String idBefore : addedID) { if (idBefore.equals(idAfter)) { newID = false; break; } } if (newID) { newIDs.add(idAfter); } } return newIDs; } /** * insert the feature in the target store * * @param newFeature * @throws SpatialOperationException */ protected void insert(FeatureStore<SimpleFeatureType, SimpleFeature> targetStore, SimpleFeature newFeature) throws SpatialOperationException { Transaction transaction = targetStore.getTransaction(); try { List<FeatureId> newIds = targetStore.addFeatures(DataUtilities .collection(new SimpleFeature[] { newFeature })); if (newIds.size() != 1) { final String msg = Messages.AbstractTask_failed_inserting_feature; throw createException(msg); } transaction.commit(); } catch (IOException e) { try { transaction.rollback(); } catch (IOException e1) { throw makeException(e1); } throw makeException(e); } finally { try { transaction.close(); } catch (IOException e) { e.printStackTrace(); throw makeException(e); } } } protected void addListeners() { // initialize the listener. FeatureListener targetListener = new FeatureListener() { public void changed(FeatureEvent featureEvent) { Type eventType = featureEvent.getType(); switch (eventType) { case ADDED: featuresAdded(featureEvent.getBounds()); break; case COMMIT: // TODO this will work on uDig trunk, right now the commit // event doesn't notify. // TEST IT NOW! featuresChanged(featureEvent.getBounds()); break; case REMOVED: break; default: break; } } }; assert targetListener != null : "listener is null"; //$NON-NLS-1$ targetStore.addFeatureListener(targetListener); } private Set<String> addedID = new HashSet<String>(); private Set<String> committedID = new HashSet<String>(); private Query query = null; protected void featuresAdded(Envelope bounds) { FeatureCollection<SimpleFeatureType, SimpleFeature> collection = null; FeatureIterator<SimpleFeature> iter = null; try { collection = getFeatureCollection(bounds); iter = collection.features(); while (iter.hasNext()) { SimpleFeature feature = iter.next(); addedID.add(feature.getID()); } } finally { if (iter != null) { iter.close(); } } } protected void featuresChanged(Envelope bounds) { FeatureCollection<SimpleFeatureType, SimpleFeature> collection = null; FeatureIterator<SimpleFeature> iter = null; try { collection = getFeatureCollection(bounds); iter = collection.features(); while (iter.hasNext()) { SimpleFeature feature = iter.next(); committedID.add(feature.getID()); } } finally { if (iter != null) { iter.close(); } } } private FeatureCollection<SimpleFeatureType, SimpleFeature> getFeatureCollection(Envelope bounds) { SimpleFeatureType schema = targetStore.getSchema(); final List<String> queryAtts = obtainQueryAttributesForFeatureTable(schema); final Query query = new Query(schema.getTypeName(), Filter.EXCLUDE, queryAtts .toArray(new String[0])); // TODO TEST IT. BBOX bboxFilter; String name = schema.getGeometryDescriptor().getName().getLocalPart(); FeatureCollection<SimpleFeatureType, SimpleFeature> collection = null; try { double minx = bounds.getMinX(); double miny = bounds.getMinY(); double maxx = bounds.getMaxX(); double maxy = bounds.getMaxY(); String srs = CRS.lookupIdentifier(schema.getCoordinateReferenceSystem(), false); bboxFilter = FILTER_FACTORY.bbox(name, minx, miny, maxx, maxy, srs); query.setFilter(bboxFilter); collection = targetStore.getFeatures(query); } catch (Exception e) { e.printStackTrace(); } // set the value of the query. this.query = query; return collection; } private List<String> obtainQueryAttributesForFeatureTable(final SimpleFeatureType schema) { final List<String> queryAtts = new ArrayList<String>(); for (int i = 0; i < schema.getAttributeCount(); i++) { AttributeDescriptor attr = schema.getDescriptor(i); if (!(attr instanceof GeometryDescriptor)) { queryAtts.add(attr.getName().getLocalPart()); } } return queryAtts; } /** * Get a filter id from a feature id. * * @param requestedFid * feature id name * @return The Id of the filter */ protected Id getFilterId(String requestedFid) { FeatureId fid = FILTER_FACTORY.featureId(requestedFid); Set<FeatureId> ids = new HashSet<FeatureId>(1); ids.add(fid); Id filter = FILTER_FACTORY.id(ids); return filter; } }