/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2009-2010, Geomatys * * This library is free software; 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 org.geotoolkit.data; 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 java.io.Closeable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.logging.Logger; import org.apache.sis.feature.FeatureExt; import org.apache.sis.feature.builder.AttributeRole; import org.apache.sis.feature.builder.AttributeTypeBuilder; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.apache.sis.feature.builder.PropertyTypeBuilder; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.memory.MemoryFeatureStore; import org.geotoolkit.data.query.Query; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.data.query.SortByComparator; import org.geotoolkit.data.session.Session; import org.geotoolkit.factory.Hints; import org.apache.sis.geometry.GeneralEnvelope; import static org.apache.sis.util.ArgumentChecks.*; import org.geotoolkit.util.collection.CloseableIterator; import org.apache.sis.util.logging.Logging; import org.geotoolkit.data.memory.GenericMappingFeatureCollection; import org.geotoolkit.data.memory.mapping.DefaultFeatureMapper; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.util.NamesExt; import org.opengis.feature.AttributeType; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.identity.FeatureId; import org.opengis.filter.sort.SortBy; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.util.GenericName; /** * Convinient methods to manipulate FeatureStore and FeatureCollection. * * @author Johann Sorel (Geomatys) * @module */ public class FeatureStoreUtilities { static final Logger LOGGER = Logging.getLogger("org.geotoolkit.data"); private FeatureStoreUtilities() { } public static FeatureCollection collection(final Feature ... features){ final FeatureCollection col = collection("", features[0].getType()); col.addAll(Arrays.asList(features)); return col; } /** * Convinient method to create a featurecollection from a collection of features. * @param type * @param features * @return FeatureCollection */ public static FeatureCollection collection(final FeatureType type, final Collection<? extends Feature> features){ final FeatureCollection col = collection("", type); col.addAll(features); return col; } public static FeatureCollection collection(final String id, FeatureType type){ if(type == null){ //a collection with no defined type, make a generic abstract type //that is possible since feature collection may not always have a type. final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); ftb.setName("null"); ftb.setAbstract(true); type = ftb.build(); } final MemoryFeatureStore ds = new MemoryFeatureStore(type, true); final Session session = ds.createSession(false); FeatureCollection col = session.getFeatureCollection(QueryBuilder.all(type.getName().toString())); ((AbstractFeatureCollection)col).setId(id); return col; } /** * Copy the features from the first collection to the second. * This method takes care of correctly closing interators if source collection * is a FeatureCollection. * @param source : source collection. * @param target : collection to copy features into. */ public static Collection fill(final Collection source, final Collection target){ if(target instanceof FeatureCollection){ //we can safely use the addAll method. target.addAll(source); }else{ //we are not sure that the given collection will take care of closing //the underlying iterator, we better do the iteration ourself. final Iterator ite = source.iterator(); try{ while(ite.hasNext()){ final Object f = ite.next(); target.add(f); } }finally{ //todo must close safely both iterator if(ite instanceof Closeable){ try { ((Closeable) ite).close(); } catch (IOException ex) { throw new FeatureStoreRuntimeException(ex); } } } } return target; } /** * Write the features from the given collection and return the list of generated FeatureID * send by the writer. * * @param writer, writer will not be closed * @param collection * @return List of generated FeatureId * @throws FeatureStoreRuntimeException */ public static List<FeatureId> write(final FeatureWriter writer, final Collection<? extends Feature> collection) throws FeatureStoreRuntimeException{ final List<FeatureId> ids = new ArrayList<>(); final Iterator<? extends Feature> ite = collection.iterator(); try{ while(ite.hasNext()){ final Feature f = ite.next(); final Feature candidate = writer.next(); FeatureExt.copy(f, candidate, false); writer.write(); ids.add(FeatureExt.getId(candidate)); } }finally{ //close reader before the writer to ensure no more read lock might still exist //if we write on the same source FeatureStoreRuntimeException e = null; //todo must close safely both iterator if(ite instanceof Closeable){ try { ((Closeable) ite).close(); } catch (Exception ex) { e = new FeatureStoreRuntimeException(ex); } } if(e != null){ throw e; } } return ids; } /** * Iterate on the given iterator and calculate count. * @throws FeatureStoreRuntimeException */ public static long calculateCount(final CloseableIterator reader) throws FeatureStoreRuntimeException{ long count = 0; try{ while(reader.hasNext()){ reader.next(); count++; } }finally{ reader.close(); } return count; } /** * Iterate on the given iterator and calculate the envelope. * @throws FeatureStoreRuntimeException */ public static Envelope calculateEnvelope(final FeatureIterator iterator) throws FeatureStoreRuntimeException{ ensureNonNull("iterator", iterator); GeneralEnvelope env = null; try{ while(iterator.hasNext()){ final Feature f = iterator.next(); final Envelope bbox = FeatureExt.getEnvelope(f); if(bbox != null){ if(env != null){ env.add(bbox); }else{ env = new GeneralEnvelope(bbox); } } } }finally{ iterator.close(); } return env; } public static FeatureCollection sequence(final String id, final FeatureCollection... collections) { return new FeatureCollectionSequence(id, collections); } public static FeatureIterator sequence(final FeatureIterator ... iterators){ return new FeatureIteratorSequence(iterators); } public static FeatureReader sequence(final FeatureReader ... readers){ return new FeatureReaderSequence(readers); } /** * Combine several FeatureIterator in one and merge them using the sort by orders. * All given iterator must already be sorted. * * @param sorts : sorting orders * @param iterators : iterators to combine * @return FeatureIterator combining all others */ public static FeatureIterator combine(final SortBy[] sorts, final FeatureIterator ... iterators){ return combine(new SortByComparator(sorts), iterators); } /** * Combine several FeatureIterator in one and merge them using the comparator given. * All given iterators must already be sorted. * * @param comparator : comparator * @param iterators : iterators to combine * @return FeatureIterator combining all others */ public static FeatureIterator combine(final Comparator<Feature> comparator, final FeatureIterator ... iterators){ if(iterators == null || iterators.length < 2 || (iterators.length == 1 && iterators[0] == null)){ throw new IllegalArgumentException("There must be at least 2 non null iterators."); } ensureNonNull("comparator", comparator); FeatureIterator ite = iterators[0]; for(int i=1; i<iterators.length; i++){ ite = new FeatureIteratorCombine(comparator, ite, iterators[i]); } return ite; } /** * Split the collection by geometry types. * Multiple feature store can only support a limited number of geometry types. * This method will split the content of the given collection in collections with a * simple geometry type. * * Collection datas are not copied, result collections are filtered collections * * @param col * @param geomClasses * @return splitted collections */ public static FeatureCollection[] decomposeByGeometryType(FeatureCollection col, Class ... geomClasses) throws DataStoreException{ return decomposeByGeometryType(col, FeatureExt.getDefaultGeometryAttribute(col.getFeatureType()).getName(), true, geomClasses); } /** * Split the collection by geometry types. * Multiple feature store can only support a limited number of geometry types. * This method will split the content of the given collection in collections with a * simple geometry type. * * Collection datas are not copied, result collections are filtered collections * * @param col * @param adaptType : ry to map types even if they do not match exactly. * list of adapt operations : * LineString -> MultiLineString * Polygon -> MultiPolygon * Point -> MultiPoint * @param geomClasses * @return splitted collections * @throws org.apache.sis.storage.DataStoreException */ public static FeatureCollection[] decomposeByGeometryType(FeatureCollection col, GenericName geomPropName, boolean adaptType, Class ... geomClasses) throws DataStoreException{ final FilterFactory FF = FactoryFinder.getFilterFactory(null); final FeatureType baseType = col.getFeatureType(); final GenericName name = baseType.getName(); final AttributeType geomDesc = (AttributeType) baseType.getProperty(geomPropName.toString()); final List<Class> lstClasses = Arrays.asList(geomClasses); final FeatureCollection[] cols = new FeatureCollection[geomClasses.length]; for(int i=0; i<geomClasses.length;i++){ final Class geomClass = geomClasses[i]; Filter filter = FF.equals( FF.function("geometryType", FF.property(geomPropName.tip().toString())), FF.literal(geomClass.getSimpleName())); //check if we need to map another type if(adaptType){ if(geomClass == MultiPolygon.class && !lstClasses.contains(Polygon.class)){ filter = FF.or(filter, FF.equals( FF.function("geometryType", FF.property(geomPropName.tip().toString())), FF.literal(Polygon.class.getSimpleName())) ); }else if(geomClass == MultiLineString.class && !lstClasses.contains(LineString.class)){ filter = FF.or(filter, FF.equals( FF.function("geometryType", FF.property(geomPropName.tip().toString())), FF.literal(LineString.class.getSimpleName())) ); }else if(geomClass == MultiPoint.class && !lstClasses.contains(Point.class)){ filter = FF.or(filter, FF.equals( FF.function("geometryType", FF.property(geomPropName.tip().toString())), FF.literal(Point.class.getSimpleName())) ); } } cols[i] = col.subCollection( QueryBuilder.filtered(name.toString(), filter) ); //retype the collection final FeatureTypeBuilder ftb = new FeatureTypeBuilder(baseType); ftb.setName(NamesExt.create(NamesExt.getNamespace(name), name.tip().toString()+"_"+geomClass.getSimpleName())); for(PropertyTypeBuilder ptb : ftb.properties()){ if(ptb.getName().equals(geomPropName)){ final AttributeTypeBuilder atb = (AttributeTypeBuilder) ptb; atb.setValueClass(geomClasses[i]).setCRS(FeatureExt.getCRS(geomDesc)).addRole(AttributeRole.DEFAULT_GEOMETRY); } } cols[i] = new GenericMappingFeatureCollection(cols[i],new DefaultFeatureMapper(baseType, ftb.build())); } return cols; } /** * Provide a collection that link several collections in one. * All collection are appended in the order they are given like a sequence. * This implementation doesn't copy the features, it will call each wraped * collection one after the other. * * @author Johann Sorel (Geomatys) * @module */ private static class FeatureCollectionSequence extends AbstractFeatureCollection { private final FeatureCollection[] wrapped; private FeatureCollectionSequence(final String id, final FeatureCollection[] wrapped) { super(id, wrapped[0].getSource()); if(wrapped.length == 1){ throw new IllegalArgumentException("Sequence of featureCollection must have at least 2 collections."); } this.wrapped = wrapped; } @Override public int size() { int size = 0; for (FeatureCollection c : wrapped) { size += c.size(); } return size; } @Override public FeatureIterator iterator(final Hints hints) throws FeatureStoreRuntimeException { return new SequenceIterator(hints); } @Override public Envelope getEnvelope() throws DataStoreException { CoordinateReferenceSystem crs = null; if (wrapped.length > 0) { crs = wrapped[0].getEnvelope().getCoordinateReferenceSystem(); } GeneralEnvelope bbox = null; for (FeatureCollection c : wrapped) { Envelope e = c.getEnvelope(); if (e != null) { if (bbox != null) { bbox.add(e); } else { bbox = new GeneralEnvelope(e); } } } return bbox; } public static FeatureCollection sequence(final FeatureCollection... cols) { return new FeatureCollectionSequence("collection-1", cols); } public static FeatureCollection sequence(final String id, final FeatureCollection... cols) { return new FeatureCollectionSequence(id, cols); } @Override public boolean isWritable() { return false; } @Override public void update(final Filter filter, final Map values) throws DataStoreException { for(FeatureCollection c : wrapped){ c.update(filter, values); } } @Override public void remove(final Filter filter) throws DataStoreException { for(FeatureCollection c : wrapped){ c.remove(filter); } } @Override public Session getSession() { return null; } @Override public FeatureType getFeatureType() { return wrapped[0].getFeatureType(); } @Override public FeatureCollection subCollection(final Query query) throws DataStoreException { FeatureCollection[] subs = new FeatureCollection[wrapped.length]; for(int i=0;i<subs.length;i++){ subs[i] = wrapped[i].subCollection(query); } return new FeatureCollectionSequence("subid", subs); } private class SequenceIterator implements FeatureIterator { private final Hints hints; private int currentCollection = -1; private FeatureIterator ite = null; public SequenceIterator(final Hints hints) { this.hints = hints; currentCollection = 0; ite = wrapped[currentCollection].iterator(hints); } @Override public void close() { if (ite != null) { ite.close(); } } @Override public boolean hasNext() { if (ite == null) { return false; } if (ite.hasNext()) { return true; } else { ite.close(); } currentCollection++; while (currentCollection < wrapped.length) { ite = wrapped[currentCollection].iterator(hints); if (ite.hasNext()) { return true; } else { ite.close(); } currentCollection++; } return false; } @Override public Feature next() { if (ite == null) { throw new NoSuchElementException("No more elements"); } else { return ite.next(); } } @Override public void remove() { if (ite == null) { throw new NoSuchElementException("No more elements"); } else { ite.remove(); } } } } /** * Provide a way to sequence several featureIterator in one. * * @author Johann Sorel (Geomatys) * @module */ private static class FeatureIteratorSequence implements FeatureIterator { private final FeatureIterator[] wrapped; private int currentIndex = 0; private FeatureIterator active = null; private FeatureIteratorSequence(final FeatureIterator[] wrapped) { if(wrapped == null || wrapped.length == 0 || wrapped[0] == null){ throw new IllegalArgumentException("Iterators can not be empty or null"); } this.wrapped = wrapped; active = wrapped[0]; } @Override public Feature next() { if (active == null) { throw new NoSuchElementException("No more elements"); } else { return active.next(); } } @Override public void close() { for(FeatureIterator ite : wrapped){ ite.close(); } } @Override public boolean hasNext() { if (active == null) { return false; } if (active.hasNext()) { return true; } else { //Do not close it, featurestore often use locks, so the thread who created //the iterator must close it, but the iteration might be done by another. //active.close(); } currentIndex++; while (currentIndex < wrapped.length) { active = wrapped[currentIndex]; if (active.hasNext()) { return true; } else { active.close(); } currentIndex++; } return false; } @Override public void remove() { if(active != null){ active.remove(); } } } /** * Provide a way to sequence several featureReader in one. * * @author Johann Sorel (Geomatys) * @module */ private static class FeatureReaderSequence implements FeatureReader { private final FeatureReader[] wrapped; private int currentIndex = 0; private FeatureReader active = null; private FeatureReaderSequence(final FeatureReader[] wrapped) { if(wrapped == null || wrapped.length == 0 || wrapped[0] == null){ throw new IllegalArgumentException("Readers can not be empty or null"); } this.wrapped = wrapped; active = wrapped[0]; } @Override public Feature next() { if (active == null) { throw new NoSuchElementException("No more elements"); } else { return active.next(); } } @Override public void close() { for(FeatureIterator ite : wrapped){ ite.close(); } } @Override public boolean hasNext() { if (active == null) { return false; } if (active.hasNext()) { return true; } else { active.close(); } currentIndex++; while (currentIndex < wrapped.length) { active = wrapped[currentIndex]; if (active.hasNext()) { return true; } else { active.close(); } currentIndex++; } return false; } @Override public void remove() { if(active != null){ active.remove(); } } @Override public FeatureType getFeatureType() { return wrapped[0].getFeatureType(); } } /** * Combine several FeatureIterator and merge them using the comparator given. * All given iterator must already be ordered this same comparator, otherwise the results * are unpredictable. * * @param <F> extends Feature */ private static class FeatureIteratorCombine implements FeatureIterator{ private final FeatureIterator ite1; private final FeatureIterator ite2; private final Comparator<? super Feature> comparator; private FeatureIterator active = null; private Feature ite1next = null; private Feature ite2next = null; private Feature next = null; private FeatureIteratorCombine(final Comparator<? super Feature> comparator, final FeatureIterator ite1, final FeatureIterator ite2){ ensureNonNull("iterator1", ite1); ensureNonNull("iterator2", ite2); if(comparator == null ){ throw new IllegalArgumentException("comparator can not be null. use sequence if you have no comparator."); } this.comparator = comparator; this.ite1 = ite1; this.ite2 = ite2; } @Override public Feature next() { if(next == null){ hasNext(); } if(next == null){ throw new NoSuchElementException("No more elements."); }else{ Feature candidate = next; next = null; return candidate; } } @Override public void close() { ite1.close(); ite2.close(); } @Override public boolean hasNext() { if(next != null) return true; if(ite1next == null && ite1.hasNext()){ ite1next = ite1.next(); } if(ite2next == null && ite2.hasNext()){ ite2next = ite2.next(); } if (ite1next != null && ite2next != null) { if(comparator.compare(ite1next, ite2next) <= 0){ //ite1next is before next = ite1next; ite1next = null; active = ite1; }else{ next = ite2next; ite2next = null; active = ite2; } } else if (ite1next == null) { next = ite2next; ite2next = null; active = ite2; } else if (ite2next == null) { next = ite1next; ite1next = null; active = ite1; } else { next = null; active = null; } return next != null; } @Override public void remove() { if(active != null){ active.remove(); } } } }