/* * 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.nio.charset.Charset; import java.text.MessageFormat; import java.util.Collection; import java.util.HashMap; import java.util.Map; import javax.xml.namespace.QName; import org.geotools.data.shapefile.ShapefileDataStore; import org.geotools.data.simple.SimpleFeatureSource; import org.opengis.feature.type.Name; import eu.esdihumboldt.hale.common.core.io.IOProvider; import eu.esdihumboldt.hale.common.core.io.IOProviderConfigurationException; import eu.esdihumboldt.hale.common.core.io.ProgressIndicator; import eu.esdihumboldt.hale.common.core.io.Value; import eu.esdihumboldt.hale.common.core.io.impl.AbstractIOProvider; import eu.esdihumboldt.hale.common.core.io.report.IOReport; import eu.esdihumboldt.hale.common.core.io.report.IOReporter; import eu.esdihumboldt.hale.common.core.io.report.impl.IOMessageImpl; import eu.esdihumboldt.hale.common.core.parameter.AbstractParameterValueDescriptor; import eu.esdihumboldt.hale.common.instance.io.InstanceReader; import eu.esdihumboldt.hale.common.instance.io.impl.AbstractInstanceReader; import eu.esdihumboldt.hale.common.instance.model.InstanceCollection; import eu.esdihumboldt.hale.common.instance.model.ext.impl.PerTypeInstanceCollection; import eu.esdihumboldt.hale.common.schema.model.ChildDefinition; import eu.esdihumboldt.hale.common.schema.model.DefinitionUtil; import eu.esdihumboldt.hale.common.schema.model.PropertyDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeDefinition; import eu.esdihumboldt.hale.common.schema.model.TypeIndex; import eu.esdihumboldt.hale.io.shp.ShapefileConstants; import eu.esdihumboldt.hale.io.shp.internal.Messages; import eu.esdihumboldt.util.Pair; /** * Reads instances from a shapefile. * * @author Thorsten Reitz * @author Simon Templer */ public class ShapeInstanceReader extends AbstractInstanceReader implements ShapefileConstants { private InstanceCollection instances; /** * Default constructor. */ public ShapeInstanceReader() { super(); addSupportedParameter(PARAM_TYPENAME); } @SuppressWarnings("javadoc") public static class TypenameParameterDescriptor extends AbstractParameterValueDescriptor { public TypenameParameterDescriptor() { super(null, Value.of(new QName("namespace", "localname").toString())); } @Override public String getSampleDescription() { return "The type name is represented like in the given example, with the namespace in curly braces."; } } /** * @see IOProvider#isCancelable() */ @Override public boolean isCancelable() { return false; } /** * @see AbstractIOProvider#execute(ProgressIndicator, IOReporter) */ @Override protected IOReport execute(ProgressIndicator progress, IOReporter reporter) throws IOProviderConfigurationException, IOException { progress.begin(Messages.getString("ShapeSchemaProvider.1"), ProgressIndicator.UNKNOWN); //$NON-NLS-1$ // DataStore store = new ShapefileDataStoreFactory().createDataStore(location.toURL()); // DataStore store = FileDataStoreFinder.getDataStore(getSource().getLocation().toURL()); ShapefileDataStore store = new ShapefileDataStore(getSource().getLocation().toURL()); store.setCharset(getCharset()); progress.setCurrentTask("Extracting shape instances"); String typename = getParameter(PARAM_TYPENAME).as(String.class); TypeDefinition defaultType = null; if (typename != null && !typename.isEmpty()) { try { defaultType = getSourceSchema().getType(QName.valueOf(typename)); } catch (Exception e) { // ignore } } if (defaultType == null) { // check if typename was supplied w/o namespace try { defaultType = getSourceSchema().getType( new QName(ShapefileConstants.SHAPEFILE_NS, typename)); } catch (Exception e) { // ignore // TODO report? } } if (defaultType == null) { reporter.info(new IOMessageImpl( "No type name supplied as parameter, trying to auto-detect the schema type.", null)); TypeDefinition dataType = ShapeSchemaReader.readShapeType(getSource()); if (dataType == null) { throw new IOException("Could not read shapefile structure information"); } String preferredName = null; Name name = store.getNames().iterator().next(); if (name != null) { preferredName = name.getLocalPart(); } Pair<TypeDefinition, Integer> tp = getMostCompatibleShapeType(getSourceSchema(), dataType, preferredName); if (tp == null) { throw new IOProviderConfigurationException( "No schema type specified and auto-detection failed"); } defaultType = tp.getFirst(); reporter.info(new IOMessageImpl(MessageFormat.format( "Auto-deteted {0} as schema type, with a {1}% compatibility rating.", defaultType.getName(), tp.getSecond()), null)); } Map<TypeDefinition, InstanceCollection> collections = new HashMap<>(); // create a collection for each type for (Name name : store.getNames()) { SimpleFeatureSource features = store.getFeatureSource(name); TypeDefinition type = defaultType; if (type == null) { QName typeName = new QName(ShapefileConstants.SHAPEFILE_NS, name.getLocalPart()); type = getSourceSchema().getType(typeName); } collections.put(type, new ShapesInstanceCollection(features, type, getCrsProvider(), name.getLocalPart())); } instances = new PerTypeInstanceCollection(collections); reporter.setSuccess(true); return reporter; } @Override protected Charset getDefaultCharset() { // default charset: ISO-8859-1 return Charset.forName("ISO-8859-1"); } /** * Determine the type out of the the mapping relevant types in the given * type index, that matches the given data type best. * * @param types the type index * @param dataType the Shapefile data type * @param preferredName the name of the preferred type * @return the most compatible type found together with is compatibility * rating or <code>null</code> if there is no type that at least has * one matching property * * @see #checkCompatibility(TypeDefinition, TypeDefinition) */ public static Pair<TypeDefinition, Integer> getMostCompatibleShapeType(TypeIndex types, TypeDefinition dataType, String preferredName) { int maxCompatibility = -1; TypeDefinition maxType = null; // check preferred name first TypeDefinition preferredType = types.getType(new QName(ShapefileConstants.SHAPEFILE_NS, preferredName)); if (preferredType != null) { int comp = checkCompatibility(preferredType, dataType); if (comp >= 100) { // return an exact match directly return new Pair<TypeDefinition, Integer>(preferredType, 100); } else { maxType = preferredType; maxCompatibility = comp; } } for (TypeDefinition schemaType : types.getMappingRelevantTypes()) { if (ShapefileConstants.SHAPEFILE_NS.equals(schemaType.getName().getNamespaceURI())) { // is a shapefile type int comp = checkCompatibility(schemaType, dataType); if (comp >= 100) { // return an exact match directly return new Pair<TypeDefinition, Integer>(schemaType, 100); } else if (comp > maxCompatibility) { maxType = schemaType; maxCompatibility = comp; } else if (maxCompatibility > 0 && comp == maxCompatibility) { // TODO debug message? possible duplicate? } } } if (maxType != null && maxCompatibility > 0) { // return the type with the maximum compatibility rating return new Pair<TypeDefinition, Integer>(maxType, maxCompatibility); } return null; } /** * Determines if the compatibility rating between the two Shapefile type * definitions. * * @param schemaType the type to test for compatibility * @param dataType the type representing the data to read * @return the percentage of compatibility (value from <code>0</code> to * <code>100</code>), where <code>100</code> represents an exact * match and <code>0</code> no compatibility */ public static int checkCompatibility(TypeDefinition schemaType, TypeDefinition dataType) { // Shapefile types are flat, so only regard properties Collection<? extends PropertyDefinition> children = DefinitionUtil .getAllProperties(dataType); int count = children.size(); int schemaCount = DefinitionUtil.getAllProperties(schemaType).size(); // check for every property if it exists with the schema, with the same // name int num = 0; for (PropertyDefinition property : children) { ChildDefinition<?> child = schemaType.getChild(property.getName()); if (child != null && child.asProperty() != null) { num++; } } if (num == count && count == schemaCount) { // exact match return 100; } else { int percentage = (int) Math.round((double) (num * 100) / (double) count); if (percentage > 1) { // reduce value by one, to ensure 100 is not returned, but only // return zero if there actually is no match percentage -= 1; } // compatibility measure with a max of 99 return percentage; } } /** * @see AbstractIOProvider#getDefaultTypeName() */ @Override protected String getDefaultTypeName() { return ShapefileConstants.DEFAULT_TYPE_NAME; } /** * @see InstanceReader#getInstances() */ @Override public InstanceCollection getInstances() { return instances; } }