/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.feature; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.geotools.factory.FactoryRegistryException; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.feature.simple.SimpleFeatureTypeImpl; import org.geotools.filter.LengthFunction; import org.geotools.geometry.jts.JTS; import org.geotools.util.Utilities; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.feature.type.PropertyType; import org.opengis.filter.BinaryComparisonOperator; import org.opengis.filter.Filter; import org.opengis.filter.PropertyIsGreaterThan; import org.opengis.filter.PropertyIsGreaterThanOrEqualTo; import org.opengis.filter.PropertyIsLessThan; import org.opengis.filter.PropertyIsLessThanOrEqualTo; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import com.vividsolutions.jts.geom.Geometry; /** * Utility methods for working against the FeatureType interface. * <p> * Many methods from DataUtilities should be refractored here. * </p> * <p> * Responsibilities: * <ul> * <li>Schema construction from String spec * <li>Schema Force CRS * </ul> * * @author Jody Garnett, Refractions Research * @since 2.1.M3 * * @source $URL$ */ public class FeatureTypes { /** the default namespace for feature types */ //public static final URI = GMLSchema.NAMESPACE; public static final URI DEFAULT_NAMESPACE; static { URI uri; try { uri = new URI( "http://www.opengis.net/gml" ); } catch (URISyntaxException e) { uri = null; //will never happen } DEFAULT_NAMESPACE = uri; } /** abstract base type for all feature types */ public final static SimpleFeatureType ABSTRACT_FEATURE_TYPE; static { SimpleFeatureType featureType = null; try { featureType = FeatureTypes.newFeatureType(null, "Feature",new URI("http://www.opengis.net/gml"), true); } catch(Exception e ) { //shold not happen } ABSTRACT_FEATURE_TYPE = featureType; } /** default feature collection name */ final public static NameImpl DEFAULT_TYPENAME = new NameImpl( "AbstractFeatureCollectionType", DEFAULT_NAMESPACE.toString() ); /** represent an unbounded field length */ final public static int ANY_LENGTH = -1; /** An feature type with no attributes */ public static final SimpleFeatureType EMPTY = new SimpleFeatureTypeImpl( new NameImpl( "Empty" ), Collections.EMPTY_LIST, null, false, Collections.EMPTY_LIST, null, null ); /** * This is a 'suitable replacement for extracting the expected field length of an attribute * absed on its "facets" (ie Filter describing type restrictions); * <p> * This code is copied from the ShapefileDataStore where it was written (probably by dzwiers). * Cholmes is providing documentation. * </p> * * @param type the AttributeType * * @return an int indicating the max length of field in characters, or ANY_LENGTH */ public static int getFieldLength( PropertyDescriptor descriptor ) { PropertyType type = descriptor.getType(); Integer length = null; while( type != null ){ // TODO: We should really go through all the restrictions and find // the minimum of all the length restrictions; for now we assume an // override behavior. for ( Filter f : type.getRestrictions()) { Integer filterLength = null; try { if(f == null) { continue; } if (f instanceof PropertyIsLessThan) { BinaryComparisonOperator cf = (BinaryComparisonOperator) f; if (cf.getExpression1() instanceof LengthFunction) { filterLength = cf.getExpression2().evaluate(null, Integer.class) - 1; } } else if (f instanceof PropertyIsLessThanOrEqualTo) { BinaryComparisonOperator cf = (BinaryComparisonOperator) f; if (cf.getExpression1() instanceof LengthFunction) { filterLength = cf.getExpression2().evaluate(null, Integer.class); } } else if(f instanceof PropertyIsGreaterThan) { BinaryComparisonOperator cf = (BinaryComparisonOperator) f; if (cf.getExpression2() instanceof LengthFunction) { filterLength = cf.getExpression1().evaluate(null, Integer.class) - 1; } } else if (f instanceof PropertyIsGreaterThanOrEqualTo) { BinaryComparisonOperator cf = (BinaryComparisonOperator) f; if (cf.getExpression2() instanceof LengthFunction) { filterLength = cf.getExpression1().evaluate(null, Integer.class); } } } catch (NullPointerException e) { // was not an integer eh? Continue, worst case we'll return ANY_LENGTH } if(filterLength != null) { if(length == null || filterLength < length) { length = filterLength; } } } type = type.getSuper(); } return length != null ? length : ANY_LENGTH; } /** * Forces the specified CRS on all geometry attributes * @param schema the original schema * @param crs the forced crs * @return * @throws SchemaException */ public static SimpleFeatureType transform( SimpleFeatureType schema, CoordinateReferenceSystem crs ) throws SchemaException { return transform(schema, crs, false); } /** * Forces the specified CRS on geometry attributes (all or some, depends on the parameters). * @param schema the original schema * @param crs the forced crs * @param forceOnlyMissing if true, will force the specified crs only on the attributes that * do miss one * @return * @throws SchemaException */ public static SimpleFeatureType transform( SimpleFeatureType schema, CoordinateReferenceSystem crs, boolean forceOnlyMissing) throws SchemaException { SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setName(schema.getTypeName()); tb.setNamespaceURI( schema.getName().getNamespaceURI() ); tb.setAbstract(schema.isAbstract()); GeometryDescriptor defaultGeometryType = null; for( int i = 0; i < schema.getAttributeCount(); i++ ) { AttributeDescriptor attributeType = schema.getDescriptor(i); if (attributeType instanceof GeometryDescriptor) { GeometryDescriptor geometryType = (GeometryDescriptor) attributeType; AttributeDescriptor forced; tb.descriptor( geometryType ); if ( !forceOnlyMissing || geometryType.getCoordinateReferenceSystem() == null ) { tb.crs( crs ); } tb.add( geometryType.getLocalName(), geometryType.getType().getBinding() ); } else { tb.add(attributeType); } } if (schema.getGeometryDescriptor() != null) { tb.setDefaultGeometry(schema.getGeometryDescriptor().getLocalName()); } tb.setSuperType((SimpleFeatureType) schema.getSuper()); return tb.buildFeatureType(); } /** * Applies transform to all geometry attribute. * * @param feature Feature to be transformed * @param schema Schema for target transformation - transform( schema, crs ) * @param transform MathTransform used to transform coordinates - reproject( crs, crs ) * @return transformed Feature of type schema * @throws TransformException * @throws MismatchedDimensionException * @throws IllegalAttributeException */ public static SimpleFeature transform( SimpleFeature feature, SimpleFeatureType schema, MathTransform transform ) throws MismatchedDimensionException, TransformException, IllegalAttributeException { feature = SimpleFeatureBuilder.copy(feature); GeometryDescriptor geomType = schema.getGeometryDescriptor(); Geometry geom = (Geometry) feature.getAttribute(geomType.getLocalName()); geom = JTS.transform(geom, transform); feature.setAttribute(geomType.getLocalName(), geom); return feature; } /** * The most specific way to create a new FeatureType. * * @param types The AttributeTypes to create the FeatureType with. * @param name The typeName of the FeatureType. Required, may not be null. * @param ns The namespace of the FeatureType. Optional, may be null. * @param isAbstract True if this created type should be abstract. * @param superTypes A Collection of types the FeatureType will inherit from. Currently, all * types inherit from feature in the opengis namespace. * @return A new FeatureType created from the given arguments. * @throws FactoryRegistryException If there are problems creating a factory. * @throws SchemaException If the AttributeTypes provided are invalid in some way. */ public static SimpleFeatureType newFeatureType( AttributeDescriptor[] types, String name, URI ns, boolean isAbstract, SimpleFeatureType[] superTypes ) throws FactoryRegistryException, SchemaException { return newFeatureType(types, name, ns, isAbstract, superTypes, null); } /** * The most specific way to create a new FeatureType. * * @param types The AttributeTypes to create the FeatureType with. * @param name The typeName of the FeatureType. Required, may not be null. * @param ns The namespace of the FeatureType. Optional, may be null. * @param isAbstract True if this created type should be abstract. * @param superTypes A Collection of types the FeatureType will inherit from. Currently, all * types inherit from feature in the opengis namespace. * @return A new FeatureType created from the given arguments. * @throws FactoryRegistryException If there are problems creating a factory. * @throws SchemaException If the AttributeTypes provided are invalid in some way. */ public static SimpleFeatureType newFeatureType( AttributeDescriptor[] types, String name, URI ns, boolean isAbstract, SimpleFeatureType[] superTypes, AttributeDescriptor defaultGeometry ) throws FactoryRegistryException, SchemaException { SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setName(name); tb.setNamespaceURI(ns); tb.setAbstract(isAbstract); if(types != null) { tb.addAll(types); } if ( defaultGeometry != null ) { //make sure that the default geometry was one of the types specified boolean add = true; for ( int i = 0; i < types.length; i++ ) { if (types[i] == defaultGeometry) { add = false; break; } } if ( add ) { tb.add(defaultGeometry); } tb.setDefaultGeometry(defaultGeometry.getLocalName()); } if ( superTypes != null && superTypes.length > 0) { if ( superTypes.length > 1 ) { throw new SchemaException("Can only specify a single super type"); } tb.setSuperType(superTypes[0]); } else { //use the default super type tb.setSuperType(ABSTRACT_FEATURE_TYPE); } return (SimpleFeatureType) tb.buildFeatureType(); } /** * The most specific way to create a new FeatureType. * * @param types The AttributeTypes to create the FeatureType with. * @param name The typeName of the FeatureType. Required, may not be null. * @param ns The namespace of the FeatureType. Optional, may be null. * @param isAbstract True if this created type should be abstract. * @param superTypes A Collection of types the FeatureType will inherit from. Currently, all * types inherit from feature in the opengis namespace. * @return A new FeatureType created from the given arguments. * @throws FactoryRegistryException If there are problems creating a factory. * @throws SchemaException If the AttributeTypes provided are invalid in some way. */ public static SimpleFeatureType newFeatureType( AttributeDescriptor[] types, String name, URI ns, boolean isAbstract, SimpleFeatureType[] superTypes, GeometryDescriptor defaultGeometry ) throws FactoryRegistryException, SchemaException { return newFeatureType(types,name,ns,isAbstract,superTypes,(AttributeDescriptor)defaultGeometry); } /** * Create a new FeatureType with the given AttributeTypes. A short cut for calling * <code>newFeatureType(types,name,ns,isAbstract,null)</code>. * * @param types The AttributeTypes to create the FeatureType with. * @param name The typeName of the FeatureType. Required, may not be null. * @param ns The namespace of the FeatureType. Optional, may be null. * @param isAbstract True if this created type should be abstract. * @return A new FeatureType created from the given arguments. * @throws FactoryRegistryException If there are problems creating a factory. * @throws SchemaException If the AttributeTypes provided are invalid in some way. */ public static SimpleFeatureType newFeatureType( AttributeDescriptor[] types, String name, URI ns, boolean isAbstract ) throws FactoryRegistryException, SchemaException { return newFeatureType(types, name, ns, isAbstract, null); } /** * Create a new FeatureType with the given AttributeTypes. A short cut for calling * <code>newFeatureType(types,name,ns,false,null)</code>. * * @param types The AttributeTypes to create the FeatureType with. * @param name The typeName of the FeatureType. Required, may not be null. * @param ns The namespace of the FeatureType. Optional, may be null. * @return A new FeatureType created from the given arguments. * @throws FactoryRegistryException If there are problems creating a factory. * @throws SchemaException If the AttributeTypes provided are invalid in some way. */ public static SimpleFeatureType newFeatureType( AttributeDescriptor[] types, String name, URI ns ) throws FactoryRegistryException, SchemaException { return newFeatureType(types, name, ns, false); } /** * Create a new FeatureType with the given AttributeTypes. A short cut for calling * <code>newFeatureType(types,name,null,false,null)</code>. Useful for test cases or * datasources which may not allow a namespace. * * @param types The AttributeTypes to create the FeatureType with. * @param name The typeName of the FeatureType. Required, may not be null. * @return A new FeatureType created from the given arguments. * @throws FactoryRegistryException If there are problems creating a factory. * @throws SchemaException If the AttributeTypes provided are invalid in some way. */ public static SimpleFeatureType newFeatureType( AttributeDescriptor[] types, String name ) throws FactoryRegistryException, SchemaException { return newFeatureType(types, name, DEFAULT_NAMESPACE, false); } /** * Walks up the type hierarchy of the feature returning all super types of the specified feature * type. The search terminates when a non-FeatureType or null is found. The original featureType * is not included as an ancestor, only its strict ancestors. */ public static List<FeatureType> getAncestors(FeatureType featureType) { List<FeatureType> ancestors = new ArrayList<FeatureType>(); while (featureType.getSuper() instanceof FeatureType) { FeatureType superType = (FeatureType) featureType.getSuper(); ancestors.add(superType); featureType = superType; } return ancestors; } /** * A query of the the types ancestor information. * <p> * This utility method may be used as common implementation for * <code>FeatureType.isDecendedFrom( namespace, typeName )</code>, however for specific uses, * such as GML, an implementor may be able to provide a more efficient implemenation based on * prior knolwege. * </p> * <p> * This is a proper check, if the provided FeatureType matches the given namespace and typename * it is <b>not </b> considered to be decended from itself. * </p> * * @param featureType * typeName with parentage in question * @param namespace * namespace to match against, or null for a "wildcard" * @param typeName * typename to match against, or null for a "wildcard" * @return true if featureType is a decendent of the indicated namespace & typeName */ public static boolean isDecendedFrom(FeatureType featureType, URI namespace, String typeName) { if (featureType == null) return false; List<FeatureType> ancestors = getAncestors(featureType); for (FeatureType superType : ancestors) { if (namespace == null) { // dont match on namespace if (Utilities.equals(superType.getName().getLocalPart(), typeName)) { return true; } } else { if (Utilities.equals(superType.getName().getNamespaceURI(), namespace.toString()) && Utilities.equals(superType.getName().getLocalPart(), typeName)) { return true; } } } return false; } public static boolean isDecendedFrom(FeatureType featureType, FeatureType isParentType) { try { return isDecendedFrom(featureType, new URI(isParentType.getName().getNamespaceURI()), isParentType.getName().getLocalPart()); } catch (URISyntaxException e) { throw new RuntimeException(e); } } /** Exact equality based on typeNames, namespace, attributes and ancestors */ public static boolean equals( SimpleFeatureType typeA, SimpleFeatureType typeB ) { return equals(typeA, typeB, false); } /** Exact equality based on typeNames, namespace, attributes and ancestors, including the user maps contents */ public static boolean equalsExact( SimpleFeatureType typeA, SimpleFeatureType typeB ) { return equals(typeA, typeB, true); } /** Exact equality based on typeNames, namespace, attributes and ancestors */ static boolean equals( SimpleFeatureType typeA, SimpleFeatureType typeB, boolean compareUserMaps) { if (typeA == typeB) return true; if (typeA == null || typeB == null) { return false; } if(compareUserMaps) { if(!equals(typeA.getUserData(), typeB.getUserData())) return false; } return equalsId(typeA, typeB) && equals(typeA.getAttributeDescriptors(), typeB.getAttributeDescriptors(), compareUserMaps) && equalsAncestors( typeA, typeB ); } static boolean equals( List attributesA, List attributesB, boolean compareUserMaps) { return equals( (AttributeDescriptor[]) attributesA.toArray(new AttributeDescriptor[attributesA.size()]), (AttributeDescriptor[]) attributesB.toArray(new AttributeDescriptor[attributesB.size()]), compareUserMaps); } public static boolean equals( List attributesA, List attributesB) { return equals(attributesA, attributesB, false); } public static boolean equalsExact( List attributesA, List attributesB) { return equals(attributesA, attributesB, true); } public static boolean equals( AttributeDescriptor attributesA[], AttributeDescriptor attributesB[] ) { return equals(attributesA, attributesB, false); } public static boolean equalsExact( AttributeDescriptor attributesA[], AttributeDescriptor attributesB[] ) { return equals(attributesA, attributesB, true); } static boolean equals( AttributeDescriptor attributesA[], AttributeDescriptor attributesB[], boolean compareUserMaps ) { if (attributesA.length != attributesB.length) return false; for( int i = 0, length = attributesA.length; i < length; i++ ) { if (!equals(attributesA[i], attributesB[i], compareUserMaps)) return false; } return true; } /** * This method depends on the correct implementation of FeatureType equals * <p> * We may need to write an implementation that can detect cycles, * </p> * * @param typeA * @param typeB */ public static boolean equalsAncestors( SimpleFeatureType typeA, SimpleFeatureType typeB ) { return ancestors( typeA ).equals( ancestors(typeB) ); } public static Set ancestors( SimpleFeatureType featureType ) { if (featureType == null || getAncestors(featureType).isEmpty()) { return Collections.EMPTY_SET; } return new HashSet(getAncestors(featureType)); } public static boolean equals( AttributeDescriptor a, AttributeDescriptor b) { return equals(a, b, false); } public static boolean equalsExact( AttributeDescriptor a, AttributeDescriptor b) { return equals(a, b, true); } static boolean equals( AttributeDescriptor a, AttributeDescriptor b, boolean compareUserMaps) { if(a == b) return true; if(a == null) return true; if(!a.equals(b)) return false; if(compareUserMaps) { if(!equals(a.getUserData(), b.getUserData())) return false; if(!equals(a.getType().getUserData(), b.getType().getUserData())) return false; } return true; } /** * Tolerant map comparison. Two maps are considered to be equal if they express the * same content. So for example two null maps are equal, but also a null and an * empty one are */ static boolean equals(Map a, Map b) { if(a == b) return true; // null == null handled above if(a == null || b == null) return false; if(a != null && a.size() == 0 && b == null) return true; if(b != null && b.size() == 0 && a == null) return true; return a.equals(b); } /** Quick check of namespace and typename */ public static boolean equalsId( SimpleFeatureType typeA, SimpleFeatureType typeB ) { if (typeA == typeB) return true; if (typeA == null || typeB == null) { return false; } String typeNameA = typeA.getTypeName(); String typeNameB = typeB.getTypeName(); if (typeNameA == null && typeNameB != null) return false; else if (!typeNameA.equals(typeNameB)) return false; String namespaceA = typeA.getName().getNamespaceURI(); String namespaceB = typeB.getName().getNamespaceURI(); if(namespaceA == null && namespaceB == null) return true; if (namespaceA == null && namespaceB != null) return false; else if (!namespaceA.equals(namespaceB)) return false; return true; } }