001/*
002 *    GeoTools - The Open Source Java GIS Toolkit
003 *    http://geotools.org
004 *
005 *    (C) 2011, Open Source Geospatial Foundation (OSGeo)
006 *    (C) 2001-2007 TOPP - www.openplans.org.
007 *
008 *    This library is free software; you can redistribute it and/or
009 *    modify it under the terms of the GNU Lesser General Public
010 *    License as published by the Free Software Foundation;
011 *    version 2.1 of the License.
012 *
013 *    This library is distributed in the hope that it will be useful,
014 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
015 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
016 *    Lesser General Public License for more details.
017 */
018package org.kepler.gis.data;
019
020import java.util.NoSuchElementException;
021
022import org.geotools.data.simple.SimpleFeatureCollection;
023import org.geotools.data.simple.SimpleFeatureIterator;
024import org.geotools.feature.AttributeTypeBuilder;
025import org.geotools.feature.collection.DecoratingSimpleFeatureCollection;
026import org.geotools.feature.simple.SimpleFeatureBuilder;
027import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
028import org.geotools.geometry.jts.ReferencedEnvelope;
029import org.geotools.process.ProcessException;
030import org.geotools.process.factory.DescribeParameter;
031import org.geotools.process.factory.DescribeProcess;
032import org.geotools.process.factory.DescribeResult;
033import org.geotools.process.vector.VectorProcess;
034import org.opengis.feature.simple.SimpleFeature;
035import org.opengis.feature.simple.SimpleFeatureType;
036import org.opengis.feature.type.AttributeDescriptor;
037import org.opengis.feature.type.PropertyDescriptor;
038import org.opengis.filter.Filter;
039
040/**
041 * A process providing the union between two feature collections
042 * 
043 * @author Gianni Barrotta - Sinergis
044 * @author Andrea Di Nora - Sinergis
045 * @author Pietro Arena - Sinergis
046 */
047@DescribeProcess(title = "Union Feature Collections", description = "Returns single feature collection containing all features from two input feature collections.  The output attribute schema is a combination of the attributes from the inputs.  Attributes with same name but different types will be converted to strings.")
048public class UnionFeatureCollection implements VectorProcess {
049
050    static final String SCHEMA_NAME = "Union_Layer";
051
052    @DescribeResult(name = "result", description = "Output feature collection")
053    public SimpleFeatureCollection execute(
054            @DescribeParameter(name = "first", description = "First input feature collection") SimpleFeatureCollection firstFeatures,
055            @DescribeParameter(name = "second", description = "Second feature collection") SimpleFeatureCollection secondFeatures)
056            throws ClassNotFoundException {
057        try (SimpleFeatureIterator firstIterator = firstFeatures.features();
058                SimpleFeatureIterator secondIterator = secondFeatures
059                        .features();) {
060            if (!(firstIterator.next().getDefaultGeometry().getClass()
061                    .equals(secondIterator.next().getDefaultGeometry()
062                            .getClass()))) {
063                throw new ProcessException(
064                        "Different default geometries, cannot perform union");
065            }
066        }
067        return new UnitedFeatureCollection(firstFeatures, secondFeatures);
068    }
069
070    static class UnitedFeatureCollection extends DecoratingSimpleFeatureCollection {
071
072        SimpleFeatureCollection features;
073
074        SimpleFeatureType schema;
075
076        public UnitedFeatureCollection(SimpleFeatureCollection delegate,
077                SimpleFeatureCollection features) throws ClassNotFoundException {
078            super(delegate);
079            this.features = features;
080
081            // Create schema containing the attributes from both the feature collections
082            SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
083            for (AttributeDescriptor descriptor : delegate.getSchema().getAttributeDescriptors()) {
084                if (sameNames(features.getSchema(), descriptor)
085                        && !sameTypes(features.getSchema(), descriptor)) {
086                    AttributeTypeBuilder builder = new AttributeTypeBuilder();
087                    builder.setName(descriptor.getLocalName());
088                    builder.setNillable(descriptor.isNillable());
089                    builder.setBinding(String.class);
090                    builder.setMinOccurs(descriptor.getMinOccurs());
091                    builder.setMaxOccurs(descriptor.getMaxOccurs());
092                    builder.setDefaultValue(descriptor.getDefaultValue());
093                    try (SimpleFeatureIterator delegateIterator = this.delegate
094                            .features();) {
095                        builder.setCRS(delegateIterator.next().getFeatureType()
096                                .getCoordinateReferenceSystem());
097                    }
098                    AttributeDescriptor attributeDescriptor = builder.buildDescriptor(descriptor
099                            .getName(), builder.buildType());
100                    tb.add(attributeDescriptor);
101                } else {
102                    tb.add(descriptor);
103                }
104            }
105            for (AttributeDescriptor descriptor : features.getSchema().getAttributeDescriptors()) {
106                if (!sameNames(delegate.getSchema(), descriptor)
107                        && !sameTypes(delegate.getSchema(), descriptor)) {
108                    tb.add(descriptor);
109                }
110            }
111
112            tb.setCRS(delegate.getSchema().getCoordinateReferenceSystem());
113            tb.setNamespaceURI(delegate.getSchema().getName().getNamespaceURI());
114            tb.setName(delegate.getSchema().getName());
115            this.schema = tb.buildFeatureType();
116        }
117
118        @Override
119        public SimpleFeatureIterator features() {
120            return new UnitedFeatureIterator(delegate.features(), delegate, features.features(),
121                    features, getSchema());
122        }
123        
124        @Override
125        public ReferencedEnvelope getBounds() {
126            ReferencedEnvelope envelope = ReferencedEnvelope.create(super.getBounds());
127            envelope.expandToInclude(features.getBounds());
128            return envelope;
129        }
130        
131        @Override
132        public SimpleFeatureType getSchema() {
133            return this.schema;
134        }
135
136        @Override
137        public int size() {
138            return super.size() + features.size(); 
139        }
140
141        @Override
142        public SimpleFeatureCollection subCollection(Filter filter) {
143            try {
144                return new UnitedFeatureCollection(super.subCollection(filter),
145                        features.subCollection(filter));
146            } catch (ClassNotFoundException e) {
147                throw new RuntimeException("ClassNotFoundException", e);
148            }
149        }
150
151        private boolean sameNames(SimpleFeatureType schema, AttributeDescriptor f) {
152            for (AttributeDescriptor descriptor : schema.getAttributeDescriptors()) {
153                if (descriptor.getName().equals(f.getName())) {
154                    return true;
155                }
156            }
157            return false;
158        }
159
160        private boolean sameTypes(SimpleFeatureType schema, AttributeDescriptor f) {
161            for (AttributeDescriptor descriptor : schema.getAttributeDescriptors()) {
162                if (descriptor.getType().equals(f.getType())) {
163                    return true;
164                }
165            }
166            return false;
167        }
168    }
169
170    static class UnitedFeatureIterator implements SimpleFeatureIterator {
171
172        SimpleFeatureIterator firstDelegate;
173
174        SimpleFeatureIterator secondDelegate;
175
176        SimpleFeatureCollection firstCollection;
177
178        SimpleFeatureCollection secondCollection;
179
180        SimpleFeatureBuilder fb;
181
182        SimpleFeature next;
183
184        int iterationIndex = 0;
185
186        public UnitedFeatureIterator(SimpleFeatureIterator firstDelegate,
187                SimpleFeatureCollection firstCollection, SimpleFeatureIterator secondDelegate,
188                SimpleFeatureCollection secondCollection, SimpleFeatureType schema) {
189            this.firstDelegate = firstDelegate;
190            this.secondDelegate = secondDelegate;
191            this.firstCollection = firstCollection;
192            this.secondCollection = secondCollection;
193            fb = new SimpleFeatureBuilder(schema);
194        }
195
196        @Override
197        public void close() {
198            firstDelegate.close();
199            secondDelegate.close();
200        }
201
202        @Override
203        public boolean hasNext() {
204
205            while (next == null && firstDelegate.hasNext()) {
206                SimpleFeature f = firstDelegate.next();
207                for (PropertyDescriptor property : fb.getFeatureType().getDescriptors()) {
208                    fb.set(property.getName(), f.getAttribute(property.getName()));
209
210                }
211                next = fb.buildFeature(Integer.toString(iterationIndex));
212                fb.reset();
213                iterationIndex++;
214            }
215            while (next == null && secondDelegate.hasNext() && !firstDelegate.hasNext()) {
216                SimpleFeature f = secondDelegate.next();
217                for (PropertyDescriptor property : fb.getFeatureType().getDescriptors()) {
218                    fb.set(property.getName(), f.getAttribute(property.getName()));
219                }
220                next = fb.buildFeature(Integer.toString(iterationIndex));
221                fb.reset();
222                iterationIndex++;
223            }
224            return next != null;
225        }
226
227        @Override
228        public SimpleFeature next() throws NoSuchElementException {
229            if (!hasNext()) {
230                throw new NoSuchElementException("hasNext() returned false!");
231            }
232
233            SimpleFeature result = next;
234            next = null;
235            return result;
236        }
237
238    }
239}