/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.hale.io.shp.reader.internal; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.google.common.collect.ImmutableMap; import com.vividsolutions.jts.geom.Geometry; import de.fhg.igd.slf4jplus.ALogger; import de.fhg.igd.slf4jplus.ALoggerFactory; import eu.esdihumboldt.hale.common.instance.geometry.CRSDefinitionUtil; import eu.esdihumboldt.hale.common.instance.geometry.CRSProvider; import eu.esdihumboldt.hale.common.instance.geometry.CRSResolveCache; import eu.esdihumboldt.hale.common.instance.geometry.DefaultGeometryProperty; import eu.esdihumboldt.hale.common.instance.geometry.impl.EPSGResolveCache; import eu.esdihumboldt.hale.common.instance.model.Filter; import eu.esdihumboldt.hale.common.instance.model.Instance; import eu.esdihumboldt.hale.common.instance.model.InstanceCollection; import eu.esdihumboldt.hale.common.instance.model.InstanceReference; import eu.esdihumboldt.hale.common.instance.model.InstanceResolver; import eu.esdihumboldt.hale.common.instance.model.MutableInstance; import eu.esdihumboldt.hale.common.instance.model.ResourceIterator; import eu.esdihumboldt.hale.common.instance.model.ext.InstanceCollection2; import eu.esdihumboldt.hale.common.instance.model.ext.InstanceIterator; import eu.esdihumboldt.hale.common.instance.model.impl.DefaultInstance; import eu.esdihumboldt.hale.common.instance.model.impl.FilteredInstanceCollection; import eu.esdihumboldt.hale.common.instance.model.impl.PseudoInstanceReference; import eu.esdihumboldt.hale.common.schema.geometry.CRSDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.io.shp.ShapefileConstants; /** * Instance collection backed by a Shapefile data store. * * @author Simon Templer */ public class ShapesInstanceCollection implements InstanceCollection2 { private static final ALogger log = ALoggerFactory.getLogger(ShapesInstanceCollection.class); /** * Iterates through a shape data store */ private class ShapesIterator implements InstanceIterator { private final SimpleFeatureIterator currentIterator; private final Set<QName> missingProperties = new HashSet<QName>(); /** * Create a new iterator on the data store. * * @throws IOException if reading the data store fails */ public ShapesIterator() throws IOException { super(); currentIterator = source.getFeatures().features(); } @Override public boolean hasNext() { return currentIterator.hasNext(); } @Override public Instance next() { SimpleFeature feature = currentIterator.next(); Instance instance = createInstance(type, feature); if (instance != null) { return instance; } else { log.error("Could not create a data instance from a feature of type " + type.getName()); throw new IllegalStateException(); } } /** * Create an instance from a given feature * * @param type the type definition associated to the feature/instance * @param feature the feature * @return the instance or <code>null</code> if it couldn't be created */ private Instance createInstance(TypeDefinition type, SimpleFeature feature) { MutableInstance instance = new DefaultInstance(type, null); for (Property property : feature.getProperties()) { Object value = property.getValue(); QName propertyName = new QName(property.getName().getNamespaceURI(), property .getName().getLocalPart()); if (type.getChild(propertyName) == null) { if (!missingProperties.contains(propertyName)) { log.warn("Discarding values of property " + propertyName.getLocalPart() + " as it is not contained in the schema type."); missingProperties.add(propertyName); } // only add values for properties contained in the type continue; } // wrap geometry if (value instanceof Geometry) { // try to determine CRS CoordinateReferenceSystem crs = null; // try user data of geometry Object userData = ((Geometry) value).getUserData(); if (userData instanceof CoordinateReferenceSystem) { crs = (CoordinateReferenceSystem) userData; } if (crs == null) { // try CRS associated to geometry descriptor AttributeDescriptor pd = feature.getFeatureType().getDescriptor( property.getName()); if (pd != null && pd instanceof GeometryDescriptor) { crs = ((GeometryDescriptor) pd).getCoordinateReferenceSystem(); } } if (crs == null) { // try CRS associated to feature type crs = feature.getFeatureType().getCoordinateReferenceSystem(); } CRSDefinition crsDef; if (crs != null) { crsDef = CRSDefinitionUtil.createDefinition(crs, crsCache); } else { // ask CRS provider crsDef = crsProvider.getCRS(type, Collections.singletonList(propertyName)); } value = new DefaultGeometryProperty<Geometry>(crsDef, (Geometry) value); } // TODO safe add? in respect to binding, existence of property instance.addProperty(propertyName, value); } // add filename augmented property if (fileName != null) { instance.addProperty(new QName(ShapefileConstants.SHAPEFILE_AUGMENT_NS, ShapefileConstants.AUGMENTED_PROPERTY_FILENAME), fileName); } return instance; } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public void close() { currentIterator.close(); } @Override public TypeDefinition typePeek() { if (hasNext()) { // always the same type return type; } return null; } @Override public boolean supportsTypePeek() { return true; } @Override public void skip() { currentIterator.next(); } } private final CRSProvider crsProvider; private final TypeDefinition type; private final SimpleFeatureSource source; private final String fileName; /** * Cache for resolved CRSs */ protected final CRSResolveCache crsCache = new EPSGResolveCache(); /** * Data store for accessing simple features (from a Shapefile). * * @param features the feature source * @param type the type to use for instances * @param crsProvider CRS provider in case no CRS is specified, may be * <code>null</code> * @param fileName the file name to store in the augmented property */ public ShapesInstanceCollection(SimpleFeatureSource features, TypeDefinition type, CRSProvider crsProvider, String fileName) { this.source = features; this.type = type; this.crsProvider = crsProvider; this.fileName = fileName; } /** * @see InstanceResolver#getReference(Instance) */ @Override public InstanceReference getReference(Instance instance) { // TODO data store based instance reference? return new PseudoInstanceReference(instance); } /** * @see InstanceResolver#getInstance(InstanceReference) */ @Override public Instance getInstance(InstanceReference reference) { // TODO data store based instance reference? if (reference instanceof PseudoInstanceReference) { return ((PseudoInstanceReference) reference).getInstance(); } return null; } /** * @see InstanceCollection#iterator() */ @Override public ResourceIterator<Instance> iterator() { try { return new ShapesIterator(); } catch (IOException e) { throw new IllegalStateException("Could not read shapefile", e); } } /** * @see InstanceCollection#hasSize() */ @Override public boolean hasSize() { return false; } /** * @see InstanceCollection#size() */ @Override public int size() { return UNKNOWN_SIZE; } /** * @see InstanceCollection#isEmpty() */ @Override public boolean isEmpty() { ResourceIterator<Instance> it = iterator(); try { return !it.hasNext(); } finally { it.close(); } } /** * @see InstanceCollection#select(Filter) */ @Override public InstanceCollection select(Filter filter) { return FilteredInstanceCollection.applyFilter(this, filter); } @Override public boolean supportsFanout() { return true; } @Override public Map<TypeDefinition, InstanceCollection> fanout() { return ImmutableMap.<TypeDefinition, InstanceCollection> of(type, this); } }