/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2003-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.data; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLDecoder; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.collection.CollectionDataStore; import org.geotools.data.collection.CollectionFeatureSource; import org.geotools.data.collection.ListFeatureCollection; import org.geotools.data.collection.SpatialIndexFeatureCollection; import org.geotools.data.collection.SpatialIndexFeatureSource; import org.geotools.data.collection.TreeSetFeatureCollection; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureLocking; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.simple.SimpleFeatureStore; import org.geotools.data.store.ArrayDataStore; import org.geotools.factory.CommonFactoryFinder; import org.geotools.factory.Hints; import org.geotools.feature.AttributeTypeBuilder; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureCollections; import org.geotools.feature.FeatureIterator; import org.geotools.feature.FeatureTypes; import org.geotools.feature.NameImpl; import org.geotools.feature.SchemaException; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.feature.type.AttributeDescriptorImpl; import org.geotools.feature.type.AttributeTypeImpl; import org.geotools.feature.type.GeometryDescriptorImpl; import org.geotools.feature.type.GeometryTypeImpl; import org.geotools.filter.FilterAttributeExtractor; import org.geotools.filter.visitor.PropertyNameResolvingVisitor; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.metadata.iso.citation.Citations; import org.geotools.referencing.CRS; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.util.Converters; import org.geotools.util.Utilities; import org.opengis.coverage.grid.GridCoverage; import org.opengis.feature.Feature; import org.opengis.feature.FeatureVisitor; import org.opengis.feature.IllegalAttributeException; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.AttributeType; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.GeometryType; import org.opengis.feature.type.Name; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory2; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.sort.SortBy; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; 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 org.geotools.data.view.DefaultView; /** * Utility functions for use when implementing working with data classes. * <p> * TODO: Move FeatureType manipulation to feature package * </p> * * @author Jody Garnett, Refractions Research * * @source $URL$ * http://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/ * data/DataUtilities.java $ */ public class DataUtilities { static Map<String, Class> typeMap = new HashMap<String, Class>(); static Map<Class, String> typeEncode = new HashMap<Class, String>(); static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); static { typeEncode.put(String.class, "String"); typeMap.put("String", String.class); typeMap.put("string", String.class); typeMap.put("\"\"", String.class); typeEncode.put(Integer.class, "Integer"); typeMap.put("Integer", Integer.class); typeMap.put("int", Integer.class); typeMap.put("0", Integer.class); typeEncode.put(Double.class, "Double"); typeMap.put("Double", Double.class); typeMap.put("double", Double.class); typeMap.put("0.0", Double.class); typeEncode.put(Float.class, "Float"); typeMap.put("Float", Float.class); typeMap.put("float", Float.class); typeMap.put("0.0f", Float.class); typeEncode.put(Boolean.class, "Boolean"); typeMap.put("Boolean", Boolean.class); typeMap.put("true", Boolean.class); typeMap.put("false", Boolean.class); typeEncode.put(Geometry.class, "Geometry"); typeMap.put("Geometry", Geometry.class); typeEncode.put(Point.class, "Point"); typeMap.put("Point", Point.class); typeEncode.put(LineString.class, "LineString"); typeMap.put("LineString", LineString.class); typeEncode.put(Polygon.class, "Polygon"); typeMap.put("Polygon", Polygon.class); typeEncode.put(MultiPoint.class, "MultiPoint"); typeMap.put("MultiPoint", MultiPoint.class); typeEncode.put(MultiLineString.class, "MultiLineString"); typeMap.put("MultiLineString", MultiLineString.class); typeEncode.put(MultiPolygon.class, "MultiPolygon"); typeMap.put("MultiPolygon", MultiPolygon.class); typeEncode.put(GeometryCollection.class, "GeometryCollection"); typeMap.put("GeometryCollection", GeometryCollection.class); typeEncode.put(Date.class, "Date"); typeMap.put("Date", Date.class); } /** * Retrieve the attributeNames defined by the featureType * * @param featureType * @return array of simple attribute names */ public static String[] attributeNames(SimpleFeatureType featureType) { String[] names = new String[featureType.getAttributeCount()]; final int count = featureType.getAttributeCount(); for (int i = 0; i < count; i++) { names[i] = featureType.getDescriptor(i).getLocalName(); } return names; } /** * A replacement for File.toURI().toURL(). * <p> * The handling of file.toURL() is broken; the handling of file.toURI().toURL() is known to be * broken on a few platforms like mac. We have the urlToFile( URL ) method that is able to * untangle both these problems and we use it in the geotools library. * <p> * However occasionally we need to pick up a file and hand it to a third party library like EMF; * this method performs a couple of sanity checks which we can use to prepare a good URL * reference to a file in these situtations. * * @param file * @return URL */ public static URL fileToURL(File file) { try { URL url = file.toURI().toURL(); String string = url.toExternalForm(); if (string.contains("+")) { // this represents an invalid URL created using either // file.toURL(); or // file.toURI().toURL() on a specific version of Java 5 on Mac string = string.replace("+", "%2B"); } if (string.contains(" ")) { // this represents an invalid URL created using either // file.toURL(); or // file.toURI().toURL() on a specific version of Java 5 on Mac string = string.replace(" ", "%20"); } return new URL(string); } catch (MalformedURLException e) { return null; } } /** * Takes a URL and converts it to a File. The attempts to deal with Windows UNC format specific * problems, specifically files located on network shares and different drives. * * If the URL.getAuthority() returns null or is empty, then only the url's path property is used * to construct the file. Otherwise, the authority is prefixed before the path. * * It is assumed that url.getProtocol returns "file". * * Authority is the drive or network share the file is located on. Such as "C:", "E:", * "\\fooServer" * * @param url * a URL object that uses protocol "file" * @return a File that corresponds to the URL's location */ public static File urlToFile(URL url) { if (!"file".equals(url.getProtocol())) { return null; // not a File URL } String string = url.toExternalForm(); if (string.contains("+")) { // this represents an invalid URL created using either // file.toURL(); or // file.toURI().toURL() on a specific version of Java 5 on Mac string = string.replace("+", "%2B"); } try { string = URLDecoder.decode(string, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Could not decode the URL to UTF-8 format", e); } String path3; String simplePrefix = "file:/"; String standardPrefix = "file://"; String os = System.getProperty("os.name"); if (os.toUpperCase().contains("WINDOWS") && string.startsWith(standardPrefix)) { // win32: host/share reference path3 = string.substring(standardPrefix.length() - 2); } else if (string.startsWith(standardPrefix)) { path3 = string.substring(standardPrefix.length()); } else if (string.startsWith(simplePrefix)) { path3 = string.substring(simplePrefix.length() - 1); } else { String auth = url.getAuthority(); String path2 = url.getPath().replace("%20", " "); if (auth != null && !auth.equals("")) { path3 = "//" + auth + path2; } else { path3 = path2; } } return new File(path3); } /** * Traverses the filter and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static String[] attributeNames(Filter filter, final SimpleFeatureType featureType) { if (filter == null) { return new String[0]; } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); filter.accept(attExtractor, null); String[] attributeNames = attExtractor.getAttributeNames(); return attributeNames; } /** * Traverses the filter and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static Set<PropertyName> propertyNames(Filter filter, final SimpleFeatureType featureType) { if (filter == null) { return Collections.emptySet(); } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); filter.accept(attExtractor, null); Set<PropertyName> propertyNames = attExtractor.getPropertyNameSet(); return propertyNames; } /** * Traverses the filter and returns any encountered property names. */ public static String[] attributeNames(Filter filter) { return attributeNames(filter, null); } /** * Traverses the filter and returns any encountered property names. */ public static Set<PropertyName> propertyNames(Filter filter) { return propertyNames(filter, null); } /** * Traverses the expression and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static String[] attributeNames(Expression expression, final SimpleFeatureType featureType) { if (expression == null) { return new String[0]; } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); expression.accept(attExtractor, null); String[] attributeNames = attExtractor.getAttributeNames(); return attributeNames; } /** * Traverses the expression and returns any encountered property names. * <p> * The feature type is supplied as contexts used to lookup expressions in cases where the * attributeName does not match the actual name of the type. * </p> */ public static Set<PropertyName> propertyNames(Expression expression, final SimpleFeatureType featureType) { if (expression == null) { return Collections.emptySet(); } FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType); expression.accept(attExtractor, null); Set<PropertyName> propertyNames = attExtractor.getPropertyNameSet(); return propertyNames; } /** * Traverses the expression and returns any encountered property names. */ public static String[] attributeNames(Expression expression) { return attributeNames(expression, null); } /** * Traverses the expression and returns any encountered property names. */ public static Set<PropertyName> propertyNames(Expression expression) { return propertyNames(expression, null); } /** * Compare operation for FeatureType. * * <p> * Results in: * </p> * * <ul> * <li> * 1: if typeA is a sub type/reorder/renamespace of typeB</li> * <li> * 0: if typeA and typeB are the same type</li> * <li> * -1: if typeA is not subtype of typeB</li> * </ul> * * <p> * Comparison is based on AttributeTypes, an IOException is thrown if the AttributeTypes are not * compatiable. * </p> * * <p> * Namespace is not considered in this opperations. You may still need to reType to get the * correct namesapce, or reorder. * </p> * * @param typeA * FeatureType beind compared * @param typeB * FeatureType being compared against * */ public static int compare(SimpleFeatureType typeA, SimpleFeatureType typeB) { if (typeA == typeB) { return 0; } if (typeA == null) { return -1; } if (typeB == null) { return -1; } int countA = typeA.getAttributeCount(); int countB = typeB.getAttributeCount(); if (countA > countB) { return -1; } // may still be the same featureType // (Perhaps they differ on namespace?) AttributeDescriptor a; // may still be the same featureType // (Perhaps they differ on namespace?) int match = 0; for (int i = 0; i < countA; i++) { a = typeA.getDescriptor(i); if (isMatch(a, typeB.getDescriptor(i))) { match++; } else if (isMatch(a, typeB.getDescriptor(a.getLocalName()))) { // match was found in a different position } else { // cannot find any match for Attribute in typeA return -1; } } if ((countA == countB) && (match == countA)) { // all attributes in typeA agreed with typeB // (same order and type) // if (typeA.getNamespace() == null) { // if(typeB.getNamespace() == null) { // return 0; // } else { // return 1; // } // } else if(typeA.getNamespace().equals(typeB.getNamespace())) { // return 0; // } else { // return 1; // } return 0; } return 1; } /** * Quickly check if two descriptors are at all compatible. * <p> * This method checks the descriptors name and class binding to see if the values have any * chance of being compatible. * * @param a * descriptor to compare * @param b * descriptor to compare * * @return true to the descriptors name and binding class match */ public static boolean isMatch(AttributeDescriptor a, AttributeDescriptor b) { if (a == b) { return true; } if (b == null) { return false; } if (a == null) { return false; } if (a.equals(b)) { return true; } if (a.getLocalName().equals(b.getLocalName()) && a.getClass().equals(b.getClass())) { return true; } return false; } /** * Creates duplicate of feature adjusted to the provided featureType. * <p> * Please note this implementation provides "deep copy" using {@link #duplicate(Object)} to copy * each attribute. * * @param featureType * FeatureType requested * @param feature * Origional Feature from DataStore * * @return An instance of featureType based on feature * * @throws IllegalAttributeException * If opperation could not be performed */ public static SimpleFeature reType(SimpleFeatureType featureType, SimpleFeature feature) throws IllegalAttributeException { SimpleFeatureType origional = feature.getFeatureType(); if (featureType.equals(origional)) { return SimpleFeatureBuilder.copy(feature); } String id = feature.getID(); int numAtts = featureType.getAttributeCount(); Object[] attributes = new Object[numAtts]; String xpath; for (int i = 0; i < numAtts; i++) { AttributeDescriptor curAttType = featureType.getDescriptor(i); xpath = curAttType.getLocalName(); attributes[i] = duplicate(feature.getAttribute(xpath)); } return SimpleFeatureBuilder.build(featureType, attributes, id); } /** * Retypes the feature to match the provided featureType. * <p> * The duplicate parameter indicates how the new feature is to be formed: * <ul> * <li>dupliate is true: A "deep copy" is made of each attribute resulting in a safe * "copy"Adjusts the attribute order to match the provided featureType.</li> * <li>duplicate is false: the attributes are simply reordered and are actually the same * instances as those in the origional feature</li> * </ul> * In the future this method may simply return a "wrapper" when duplicate is false. * <p> * * @param featureType * @param feature * @param duplicate * True to perform {@link #duplicate(Object)} on each attribute * @return * @throws IllegalAttributeException */ public static SimpleFeature reType(SimpleFeatureType featureType, SimpleFeature feature, boolean duplicate) throws IllegalAttributeException { if (duplicate) { return reType(featureType, feature); } FeatureType origional = feature.getFeatureType(); if (featureType.equals(origional)) { return feature; } String id = feature.getID(); int numAtts = featureType.getAttributeCount(); Object[] attributes = new Object[numAtts]; String xpath; for (int i = 0; i < numAtts; i++) { AttributeDescriptor curAttType = featureType.getDescriptor(i); attributes[i] = feature.getAttribute(curAttType.getLocalName()); } return SimpleFeatureBuilder.build(featureType, attributes, id); } /** * Performs a deep copy of the provided object. * * @param src * Source object * @return copy of source object */ public static Object duplicate(Object src) { // JD: this method really needs to be replaced with somethign better if (src == null) { return null; } // // The following are things I expect // Features will contain. // if (src instanceof String || src instanceof Integer || src instanceof Double || src instanceof Float || src instanceof Byte || src instanceof Boolean || src instanceof Short || src instanceof Long || src instanceof Character || src instanceof Number) { return src; } if (src instanceof Date) { return new Date(((Date) src).getTime()); } if (src instanceof URL || src instanceof URI) { return src; // immutable } if (src instanceof Object[]) { Object[] array = (Object[]) src; Object[] copy = new Object[array.length]; for (int i = 0; i < array.length; i++) { copy[i] = duplicate(array[i]); } return copy; } if (src instanceof Geometry) { Geometry geometry = (Geometry) src; return geometry.clone(); } if (src instanceof SimpleFeature) { SimpleFeature feature = (SimpleFeature) src; return SimpleFeatureBuilder.copy(feature); } // // We are now into diminishing returns // I don't expect Features to contain these often // (eveything is still nice and recursive) // Class<? extends Object> type = src.getClass(); if (type.isArray() && type.getComponentType().isPrimitive()) { int length = Array.getLength(src); Object copy = Array.newInstance(type.getComponentType(), length); System.arraycopy(src, 0, copy, 0, length); return copy; } if (type.isArray()) { int length = Array.getLength(src); Object copy = Array.newInstance(type.getComponentType(), length); for (int i = 0; i < length; i++) { Array.set(copy, i, duplicate(Array.get(src, i))); } return copy; } if (src instanceof List) { List list = (List) src; List<Object> copy = new ArrayList<Object>(list.size()); for (Iterator i = list.iterator(); i.hasNext();) { copy.add(duplicate(i.next())); } return Collections.unmodifiableList(copy); } if (src instanceof Map) { Map map = (Map) src; Map copy = new HashMap(map.size()); for (Iterator i = map.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); copy.put(entry.getKey(), duplicate(entry.getValue())); } return Collections.unmodifiableMap(copy); } if (src instanceof GridCoverage) { return src; // inmutable } // // I have lost hope and am returning the orgional reference // Please extend this to support additional classes. // // And good luck getting Cloneable to work throw new IllegalAttributeException(null, "Do not know how to deep copy " + type.getName()); } /** * Constructs an empty feature to use as a Template for new content. * * <p> * We may move this functionality to FeatureType.create( null )? * </p> * * @param featureType * Type of feature we wish to create * * @return A new Feature of type featureType * * @throws IllegalAttributeException * if we could not create featureType instance with acceptable default values */ public static SimpleFeature template(SimpleFeatureType featureType) throws IllegalAttributeException { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType), null); } /** * DOCUMENT ME! * * @param featureType * DOCUMENT ME! * @param featureID * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws IllegalAttributeException * DOCUMENT ME! */ public static SimpleFeature template(SimpleFeatureType featureType, String featureID) throws IllegalAttributeException { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType), featureID); } /** * Produce a set of default values for the provided FeatureType * * @param featureType * @return Array of values, that are good starting point for data entry * * @throws IllegalAttributeException */ public static Object[] defaultValues(SimpleFeatureType featureType) throws IllegalAttributeException { return defaultValues(featureType, null); } /** * Create a new feature from the provided values, using appropriate default values for any nulls * provided. * * @param featureType * @param atts * provided attributes * * @return newly created feature * * @throws IllegalAttributeException */ public static SimpleFeature template(SimpleFeatureType featureType, Object[] atts) throws IllegalAttributeException { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType, atts), null); } /** * Create a new feature from the provided values, using appropriate default values for any nulls * provided. * * @param featureType * @param featureID * @param atts * provided attributes * * @return newly created feature * * @throws IllegalAttributeException */ public static SimpleFeature template(SimpleFeatureType featureType, String featureID, Object[] atts) throws IllegalAttributeException { return SimpleFeatureBuilder.build(featureType, defaultValues(featureType, atts), featureID); } /** * Create default values matching the provided feature type. * * @param featureType * @param values * * @return set of default values * * @throws IllegalAttributeException * @throws ArrayIndexOutOfBoundsException */ public static Object[] defaultValues(SimpleFeatureType featureType, Object[] values) throws IllegalAttributeException { if (values == null) { values = new Object[featureType.getAttributeCount()]; } else if (values.length != featureType.getAttributeCount()) { throw new ArrayIndexOutOfBoundsException("values"); } for (int i = 0; i < featureType.getAttributeCount(); i++) { values[i] = defaultValue(featureType.getDescriptor(i)); } return values; } /** * Provides a defautlValue for attributeType. * * <p> * Will return null if attributeType isNillable(), or attempt to use Reflection, or * attributeType.parse( null ) * </p> * * @param attributeType * * @return null for nillable attributeType, attempt at reflection * * @throws IllegalAttributeException * If value cannot be constructed for attribtueType */ public static Object defaultValue(AttributeDescriptor attributeType) throws IllegalAttributeException { Object value = attributeType.getDefaultValue(); if (value == null && !attributeType.isNillable()) { return null; // sometimes there is no valid default value :-( // throw new IllegalAttributeException("Got null default value for non-null type."); } return value; } /** * Returns a non-null default value for the class that is passed in. This is a helper class an * can't create a default class for any type but it does support: * <ul> * <li>String</li> * <li>Object - will return empty string</li> * <li>Number</li> * <li>Character</li> * <li>JTS Geometries</li> * </ul> * * * @param type * @return */ public static Object defaultValue(Class type) { if (type == String.class || type == Object.class) { return ""; } if (type == Integer.class) { return new Integer(0); } if (type == Double.class) { return new Double(0); } if (type == Long.class) { return new Long(0); } if (type == Short.class) { return new Short((short) 0); } if (type == Float.class) { return new Float(0.0f); } if (type == BigDecimal.class) { return BigDecimal.valueOf(0); } if (type == BigInteger.class) { return BigInteger.valueOf(0); } if (type == Character.class) { return new Character(' '); } if (type == Boolean.class) { return Boolean.FALSE; } if (type == Timestamp.class) return new Timestamp(System.currentTimeMillis()); if (type == java.sql.Date.class) return new java.sql.Date(System.currentTimeMillis()); if (type == java.sql.Time.class) return new java.sql.Time(System.currentTimeMillis()); if (type == java.util.Date.class) return new java.util.Date(); GeometryFactory fac = new GeometryFactory(); Coordinate coordinate = new Coordinate(0, 0); Point point = fac.createPoint(coordinate); if (type == Point.class) { return point; } if (type == MultiPoint.class) { return fac.createMultiPoint(new Point[] { point }); } if (type == LineString.class) { return fac.createLineString(new Coordinate[] { coordinate, coordinate, coordinate, coordinate }); } LinearRing linearRing = fac.createLinearRing(new Coordinate[] { coordinate, coordinate, coordinate, coordinate }); if (type == LinearRing.class) { return linearRing; } if (type == MultiLineString.class) { return fac.createMultiLineString(new LineString[] { linearRing }); } Polygon polygon = fac.createPolygon(linearRing, new LinearRing[0]); if (type == Polygon.class) { return polygon; } if (type == MultiPolygon.class) { return fac.createMultiPolygon(new Polygon[] { polygon }); } throw new IllegalArgumentException(type + " is not supported by this method"); } /** * Creates a FeatureReader<SimpleFeatureType, SimpleFeature> for testing. * * @param features * Array of features * * @return FeatureReader<SimpleFeatureType, SimpleFeature> spaning provided feature array * * @throws IOException * If provided features Are null or empty * @throws NoSuchElementException * DOCUMENT ME! */ public static FeatureReader<SimpleFeatureType, SimpleFeature> reader( final SimpleFeature[] features) throws IOException { if ((features == null) || (features.length == 0)) { throw new IOException("Provided features where empty"); } return new FeatureReader<SimpleFeatureType, SimpleFeature>() { SimpleFeature[] array = features; int offset = -1; public SimpleFeatureType getFeatureType() { return features[0].getFeatureType(); } public SimpleFeature next() { if (!hasNext()) { throw new NoSuchElementException("No more features"); } return array[++offset]; } public boolean hasNext() { return (array != null) && (offset < (array.length - 1)); } public void close() { array = null; offset = -1; } }; } /** * Wrap up an array of features as a FeatureSource. * * @param featureArray * Array of features * @return FeatureSource * * @throws IOException * @throws RuntimeException */ public static SimpleFeatureSource source(final SimpleFeature[] featureArray) { final SimpleFeatureType featureType; if ((featureArray == null) || (featureArray.length == 0)) { featureType = FeatureTypes.EMPTY; } else { featureType = featureArray[0].getFeatureType(); } DataStore arrayStore = new ArrayDataStore(featureArray); String typeName = featureType.getTypeName(); try { return arrayStore.getFeatureSource(typeName); } catch (IOException e) { throw new IllegalStateException("Unable to find "+typeName+" ArrayDataStore in an inconsistent" + "state", e); } } /** * Wraps up the provided feature collection in as a SimpleFeatureSource. * <p> * This is usually done for use by the renderer; allowing it to query the feature collection as * required. * * @param collection * Feature collection providing content * @return FeatureSource used to wrap the content * * @throws NullPointerException * @throws RuntimeException */ public static SimpleFeatureSource source( final FeatureCollection<SimpleFeatureType, SimpleFeature> collection) { if (collection == null) { throw new NullPointerException("No content provided"); } if (collection instanceof ListFeatureCollection) { ListFeatureCollection list = (ListFeatureCollection) collection; CollectionFeatureSource source = new CollectionFeatureSource(list); return source; } if (collection instanceof SpatialIndexFeatureCollection) { SpatialIndexFeatureCollection indexed = (SpatialIndexFeatureCollection) collection; SpatialIndexFeatureSource source = new SpatialIndexFeatureSource(indexed); return source; } if (collection instanceof TreeSetFeatureCollection) { TreeSetFeatureCollection tree = (TreeSetFeatureCollection) collection; CollectionFeatureSource source = new CollectionFeatureSource(tree); return source; } // if( collection instanceof SimpleFeatureCollection ){ // SimpleFeatureCollection simpleFeatureCollection = simple( collection ); // CollectionFeatureSource source = new CollectionFeatureSource(simpleFeatureCollection); // // return source; // } CollectionDataStore store = new CollectionDataStore(collection); try { return store.getFeatureSource(store.getTypeNames()[0]); } catch (IOException e) { throw new RuntimeException("Something is wrong with the geotools code, " + "this exception should not happen", e); } } /** * Return a 'view' of the given {@code DataStore} constrained by a {@code Query}. * * @param store * the data store * @param query * the query * * @return the constrained view * * @throws IOException * if the data store cannot be accessed * @throws SchemaException * if the query is incompatible with the store's contents */ public static SimpleFeatureSource createView(final DataStore store, final Query query) throws IOException, SchemaException { return new DefaultView(store.getFeatureSource(query.getTypeName()), query); } public static SimpleFeatureCollection results(SimpleFeature[] featureArray) { return results(collection(featureArray)); } /** * Returns collection if non empty. * * @param collection * * @return provided collection * * @throws IOException * Raised if collection was empty */ public static SimpleFeatureCollection results(final SimpleFeatureCollection collection) { if (collection.size() == 0) { // throw new IOException("Provided collection was empty"); } return collection; } public static <T extends FeatureType, F extends Feature> FeatureCollection<T, F> results( final FeatureCollection<T, F> collection) { if (collection.size() == 0) { // throw new IOException("Provided collection was empty"); } return collection; } /** * Adapt a collection to a reader for use with FeatureStore.setFeatures( reader ). * * @param collection * Collection of SimpleFeature * * @return FeatureRedaer over the provided contents * @throws IOException * IOException if there is any problem reading the content. */ public static FeatureReader<SimpleFeatureType, SimpleFeature> reader( Collection<SimpleFeature> collection) throws IOException { return reader(collection.toArray(new SimpleFeature[collection.size()])); } /** * Adapt a collection to a reader for use with FeatureStore.setFeatures( reader ). * * @param collection * Collection of SimpleFeature * * @return FeatureRedaer over the provided contents * @throws IOException * IOException if there is any problem reading the content. */ public static FeatureReader<SimpleFeatureType, SimpleFeature> reader( FeatureCollection<SimpleFeatureType, SimpleFeature> collection) throws IOException { return reader(collection.toArray(new SimpleFeature[collection.size()])); } /** * Copies the provided features into a FeatureCollection. * <p> * Often used when gathering features for FeatureStore: * * <pre> * <code> * featureStore.addFeatures( DataUtilities.collection(array)); * </code> * </pre> * * @param features * Array of features * @return FeatureCollection */ public static SimpleFeatureCollection collection(SimpleFeature[] features) { SimpleFeatureCollection collection = FeatureCollections.newCollection(); final int length = features.length; for (int i = 0; i < length; i++) { collection.add(features[i]); } return collection; } /** * Copies the provided features into a FeatureCollection. * <p> * Often used when gathering a SimpleFeatureCollection into memory. * * @param SimpleFeatureCollection * the features to add to a new feature collection. * @return FeatureCollection */ public static DefaultFeatureCollection collection( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) { return new DefaultFeatureCollection(featureCollection); } /** * A safe cast to SimpleFeatureCollection; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureCollection<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureCollection * if you possibly can. * <p> * So when are you stuck using this class?: * <ul> * <li>Offering backwards compatible constructors for legacy code prior to 2.7</li> * <li>implementing FeatureStore.addFeatures(...) since we cannot type narrow parameters</li> * </ul> * * @param featureCollection * will be returned as a SimpleFeatureCollection and wrapped only if needed * @return SimpleFeatureCollection * @since 2.7 */ public static SimpleFeatureCollection simple( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) { if (featureCollection instanceof SimpleFeatureCollection) { return (SimpleFeatureCollection) featureCollection; } if (featureCollection.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureCollectionBridge(featureCollection); } throw new IllegalArgumentException( "The provided feature collection contains complex features, cannot be bridged to a simple one"); } /** * A safe cast to SimpleFeatureSource; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureSource<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureSource if you * possibly can. * * @since 2.7 */ public static SimpleFeatureSource simple(FeatureSource source) { if (source instanceof FeatureLocking) { return simple((FeatureLocking) source); } else if (source instanceof FeatureStore) { return simple((FeatureStore) source); } if (source instanceof SimpleFeatureSource) { return (SimpleFeatureSource) source; } if (source.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureSourceBridge(source); } throw new IllegalArgumentException( "The provided feature source contains complex features, cannot be bridged to a simple one"); } /** * A safe cast to SimpleFeatureStore; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureStore<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureStore if you * possibly can. * * @since 2.7 */ public static SimpleFeatureStore simple(FeatureStore store) { if (store instanceof FeatureLocking) { return simple((FeatureLocking) store); } if (store instanceof SimpleFeatureStore) { return (SimpleFeatureStore) store; } if (store.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureStoreBridge(store); } throw new IllegalArgumentException( "The provided feature store contains complex features, cannot be bridged to a simple one"); } /** * Go through FeatureType description and convert to a SimpleFeatureType. Also ignores * AbstractFeatureType contributions such as name etc... * * @param featureType * FeatureType being converted * @return SimpleFeatureType created by stripping any complicated content from the provided * featureType * @throws DataSourceException */ public static SimpleFeatureType simple(final FeatureType featureType) throws DataSourceException { if (featureType == null) { return null; } // go through the attributes and strip out compicated contents // List<PropertyDescriptor> attributes; Collection<PropertyDescriptor> descriptors = featureType.getDescriptors(); attributes = new ArrayList<PropertyDescriptor>(descriptors); List<String> simpleProperties = new ArrayList<String>(); List<AttributeDescriptor> simpleAttributes = new ArrayList<AttributeDescriptor>(); // HACK HACK!! the parser sets no namespace to the properties so we're // doing a hardcode property name black list final List<String> ignoreList = Arrays.asList(new String[] { "location", "metaDataProperty", "description", "name", "boundedBy" }); for (Iterator<PropertyDescriptor> it = attributes.iterator(); it.hasNext();) { PropertyDescriptor property = it.next(); if (!(property instanceof AttributeDescriptor)) { continue; } AttributeDescriptor descriptor = (AttributeDescriptor) property; Name name = descriptor.getName(); if (ignoreList.contains(name.getLocalPart())) { it.remove(); } } // / HACK END for (PropertyDescriptor descriptor : attributes) { Class<?> binding = descriptor.getType().getBinding(); int maxOccurs = descriptor.getMaxOccurs(); Name name = descriptor.getName(); if (Object.class.equals(binding)) { continue; // skip complex } if (maxOccurs > 1) { continue; // skip multi value } if ("http://www.opengis.net/gml".equals(name.getNamespaceURI())) { continue; // skip AbstractFeature stuff } if (descriptor instanceof AttributeDescriptor) { AttributeDescriptor attribute = (AttributeDescriptor) descriptor; simpleAttributes.add(attribute); simpleProperties.add(attribute.getLocalName()); } } String[] properties = simpleProperties.toArray(new String[simpleProperties.size()]); SimpleFeatureType simpleFeatureType; try { if (featureType instanceof SimpleFeature) { simpleFeatureType = DataUtilities.createSubType((SimpleFeatureType) featureType, properties); } else { SimpleFeatureTypeBuilder build = new SimpleFeatureTypeBuilder(); build.setName(featureType.getName()); build.setAttributes(simpleAttributes); build.setDefaultGeometry(featureType.getGeometryDescriptor().getLocalName()); simpleFeatureType = build.buildFeatureType(); } } catch (SchemaException e) { throw new DataSourceException(e); } return simpleFeatureType; } /** * A safe cast to SimpleFeatureLocking; that will introduce a wrapper if it has to. * <p> * Please keep the use of this class to a minimum; if you are expecting a * FeatureLocking<SimpleFeatureType,SimpleFeature> please make use of SimpleFeatureLocking if * you possibly can. * * @since 2.7 */ public static SimpleFeatureLocking simple(FeatureLocking locking) { if (locking instanceof SimpleFeatureLocking) { return (SimpleFeatureLocking) locking; } if (locking.getSchema() instanceof SimpleFeatureType) { return new SimpleFeatureLockingBridge(locking); } throw new IllegalArgumentException( "The provided feature store contains complex features, cannot be bridged to a simple one"); } /** * Copies the provided fetaures into a List. * * @param featureCollection * @return List of features copied into memory */ public static List<SimpleFeature> list( FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) { final ArrayList<SimpleFeature> list = new ArrayList<SimpleFeature>(); try { featureCollection.accepts(new FeatureVisitor() { public void visit(Feature feature) { list.add((SimpleFeature) feature); } }, null); } catch (IOException ignore) { } return list; } /** * Copies the feature ids from each and every feature into a set. * <p> * This method can be slurp an in memory record of the contents of a * * @param featureCollection * @return */ public static Set<String> fidSet(FeatureCollection<?, ?> featureCollection) { final HashSet<String> fids = new HashSet<String>(); try { featureCollection.accepts(new FeatureVisitor() { public void visit(Feature feature) { fids.add(feature.getIdentifier().getID()); } }, null); } catch (IOException ignore) { } return fids; } /** * Copies the provided features into a FeatureCollection. * <p> * Often used when gathering a SimpleFeatureCollection into memory. * * @param list * features to add to a new FeatureCollection * @return FeatureCollection */ public static SimpleFeatureCollection collection(List<SimpleFeature> list) { SimpleFeatureCollection collection = FeatureCollections.newCollection(); for (SimpleFeature feature : list) { collection.add(feature); } return collection; } /** * Copies the provided features into a FeatureCollection. * <p> * Often used when gathering features for FeatureStore: * * <pre> * <code> * featureStore.addFeatures( DataUtilities.collection(feature)); * </code> * </pre> * * @param feature * a feature to add to a new collection * @return FeatureCollection */ public static SimpleFeatureCollection collection(SimpleFeature feature) { SimpleFeatureCollection collection = FeatureCollections.newCollection(); collection.add(feature); return collection; } /** * Copies the provided reader into a FeatureCollection, reader will be closed. * <p> * Often used when gathering features for FeatureStore: * * <pre> * <code> * featureStore.addFeatures( DataUtilities.collection(reader)); * </code> * </pre> * * @return FeatureCollection */ public static SimpleFeatureCollection collection( FeatureReader<SimpleFeatureType, SimpleFeature> reader) throws IOException { SimpleFeatureCollection collection = FeatureCollections.newCollection(); try { while (reader.hasNext()) { try { collection.add(reader.next()); } catch (NoSuchElementException e) { throw (IOException) new IOException("EOF").initCause(e); } catch (IllegalAttributeException e) { throw (IOException) new IOException().initCause(e); } } } finally { reader.close(); } return collection; } /** * Copies the provided reader into a FeatureCollection, reader will be closed. * <p> * Often used when gathering features for FeatureStore: * * <pre> * <code> * featureStore.addFeatures( DataUtilities.collection(reader)); * </code> * </pre> * * @return FeatureCollection */ public static SimpleFeatureCollection collection(SimpleFeatureIterator reader) throws IOException { SimpleFeatureCollection collection = FeatureCollections.newCollection(); try { while (reader.hasNext()) { try { collection.add(reader.next()); } catch (NoSuchElementException e) { throw (IOException) new IOException("EOF").initCause(e); } } } finally { reader.close(); } return collection; } /** * Used to compare if two values are equal. * <p> * This method is here to work around the fact that JTS Geometry requires a specific method to * be called rather than object.equals. * * This method uses: * * <ul> * <li>Object.equals( Object )</li> * <li>Geometry.equals( Geometry )</li> * </ul> * * @param att * Attribute value * @param otherAtt * Other value * * @return True if the values are equal */ public static boolean attributesEqual(Object att, Object otherAtt) { if (att == null) { if (otherAtt != null) { return false; } } else { // Note: for JTS Geometry objects this is equivalent // to equalsExact( Geometry ) if (!att.equals(otherAtt)) { return false; } } return true; } /** * Create a derived FeatureType * * <p> * </p> * * @param featureType * @param properties * - if null, every property of the feature type in input will be used * @param override * * * @throws SchemaException */ public static SimpleFeatureType createSubType(SimpleFeatureType featureType, String[] properties, CoordinateReferenceSystem override) throws SchemaException { URI namespaceURI = null; if (featureType.getName().getNamespaceURI() != null) { try { namespaceURI = new URI(featureType.getName().getNamespaceURI()); } catch (URISyntaxException e) { throw new RuntimeException(e); } } return createSubType(featureType, properties, override, featureType.getTypeName(), namespaceURI); } public static SimpleFeatureType createSubType(SimpleFeatureType featureType, String[] properties, CoordinateReferenceSystem override, String typeName, URI namespace) throws SchemaException { if ((properties == null) && (override == null)) { return featureType; } if (properties == null) { properties = new String[featureType.getAttributeCount()]; for (int i = 0; i < properties.length; i++) { properties[i] = featureType.getDescriptor(i).getLocalName(); } } String namespaceURI = namespace != null ? namespace.toString() : null; boolean same = featureType.getAttributeCount() == properties.length && featureType.getTypeName().equals(typeName) && Utilities.equals(featureType.getName().getNamespaceURI(), namespaceURI); for (int i = 0; (i < featureType.getAttributeCount()) && same; i++) { AttributeDescriptor type = featureType.getDescriptor(i); same = type.getLocalName().equals(properties[i]) && (((override != null) && type instanceof GeometryDescriptor) ? assertEquals( override, ((GeometryDescriptor) type).getCoordinateReferenceSystem()) : true); } if (same) { return featureType; } AttributeDescriptor[] types = new AttributeDescriptor[properties.length]; for (int i = 0; i < properties.length; i++) { types[i] = featureType.getDescriptor(properties[i]); if ((override != null) && types[i] instanceof GeometryDescriptor) { AttributeTypeBuilder ab = new AttributeTypeBuilder(); ab.init(types[i]); ab.setCRS(override); types[i] = ab.buildDescriptor(types[i].getLocalName(), ab.buildGeometryType()); } } if (typeName == null) typeName = featureType.getTypeName(); if (namespace == null && featureType.getName().getNamespaceURI() != null) try { namespace = new URI(featureType.getName().getNamespaceURI()); } catch (URISyntaxException e) { throw new RuntimeException(e); } SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setName(typeName); tb.setNamespaceURI(namespace); tb.setCRS(null); // not interested in warnings from this simple method tb.addAll(types); return tb.buildFeatureType(); } private static boolean assertEquals(Object o1, Object o2) { return o1 == null && o2 == null ? true : (o1 != null ? o1.equals(o2) : false); } /** * Create a type limited to the named properties provided. * * @param featureType * @param properties * * @return type limited to the named properties provided * * @throws SchemaException */ public static SimpleFeatureType createSubType(SimpleFeatureType featureType, String[] properties) throws SchemaException { if (properties == null) { return featureType; } boolean same = featureType.getAttributeCount() == properties.length; for (int i = 0; (i < featureType.getAttributeCount()) && same; i++) { same = featureType.getDescriptor(i).getLocalName().equals(properties[i]); } if (same) { return featureType; } SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setName(featureType.getName()); tb.setCRS(null); // not interested in warnings from this simple method for (int i = 0; i < properties.length; i++) { tb.add(featureType.getDescriptor(properties[i])); } return tb.buildFeatureType(); } /** * Utility method for FeatureType construction. * <p> * Will parse a String of the form: <i>"name:Type,name2:Type2,..."</i> * </p> * * <p> * Where <i>Type</i> is defined by createAttribute. * </p> * * <p> * You may indicate the default Geometry with an astrix: "*geom:Geometry". You may also indicate * the srid (used to look up a EPSG code). * </p> * * <p> * Examples: * <ul> * <li><code>name:"",age:0,geom:Geometry,centroid:Point,url:java.io.URL"</code> * <li><code>id:String,polygonProperty:Polygon:srid=32615</code> * </ul> * </p> * * @param identification * identification of FeatureType: (<i>namesapce</i>).<i>typeName</i> * @param typeSpec * Specification for FeatureType * * * @throws SchemaException */ public static SimpleFeatureType createType(String identification, String typeSpec) throws SchemaException { int split = identification.lastIndexOf('.'); String namespace = (split == -1) ? null : identification.substring(0, split); String typeName = (split == -1) ? identification : identification.substring(split + 1); return createType(namespace, typeName, typeSpec); } /** * Utility method for FeatureType construction. * <p> * Will parse a String of the form: <i>"name:Type,name2:Type2,..."</i> * </p> * * <p> * Where <i>Type</i> is defined by createAttribute. * </p> * * <p> * You may indicate the default Geometry with an astrix: "*geom:Geometry". You may also indicate * the srid (used to look up a EPSG code). * </p> * * <p> * Examples: * <ul> * <li><code>name:"",age:0,geom:Geometry,centroid:Point,url:java.io.URL"</code> * <li><code>id:String,polygonProperty:Polygon:srid=32615</code> * </ul> * </p> * * @param identification * identification of FeatureType: (<i>namesapce</i>).<i>typeName</i> * @param typeSpec * Specification for FeatureType * * * @throws SchemaException */ public static SimpleFeatureType createType(String namespace, String typeName, String typeSpec) throws SchemaException { SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder(); builder.setName(typeName); builder.setNamespaceURI(namespace); String[] types = typeSpec.split(","); AttributeDescriptor attributeType; builder.setCRS(null); // not interested in warnings from this simple method for (int i = 0; i < types.length; i++) { boolean defaultGeometry = types[i].startsWith("*"); if (types[i].startsWith("*")) { types[i] = types[i].substring(1); } attributeType = createAttribute(types[i]); builder.add(attributeType); if (defaultGeometry) { builder.setDefaultGeometry(attributeType.getLocalName()); } } return builder.buildFeatureType(); } /** * Uses Converers to parse the provided text into the correct values to create a feature. * * @param type * FeatureType * @param fid * Feature ID for new feature * @param text * Text representation of values * * @return newly created feature * * @throws IllegalAttributeException */ public static SimpleFeature parse(SimpleFeatureType type, String fid, String[] text) throws IllegalAttributeException { Object[] attributes = new Object[text.length]; for (int i = 0; i < text.length; i++) { AttributeType attType = type.getDescriptor(i).getType(); attributes[i] = Converters.convert(text[i], attType.getBinding()); } return SimpleFeatureBuilder.build(type, attributes, fid); } /** * A "quick" String representation of a FeatureType. * <p> * This string representation may be used with createType( name, spec ). * </p> * * @param featureType * FeatureType to represent * * @return The string "specification" for the featureType */ public static String spec(FeatureType featureType) { Collection<PropertyDescriptor> types = featureType.getDescriptors(); StringBuffer buf = new StringBuffer(); for (PropertyDescriptor type : types) { buf.append(type.getName().getLocalPart()); buf.append(":"); buf.append(typeMap(type.getType().getBinding())); if (type instanceof GeometryDescriptor) { GeometryDescriptor gd = (GeometryDescriptor) type; if (gd.getCoordinateReferenceSystem() != null && gd.getCoordinateReferenceSystem().getIdentifiers() != null) { for (Iterator<ReferenceIdentifier> it = gd.getCoordinateReferenceSystem() .getIdentifiers().iterator(); it.hasNext();) { ReferenceIdentifier id = it.next(); if ((id.getAuthority() != null) && id.getAuthority().getTitle().equals(Citations.EPSG.getTitle())) { buf.append(":srid=" + id.getCode()); break; } } } } buf.append(","); } buf.delete(buf.length() - 1, buf.length()); // remove last "," return buf.toString(); } static Class type(String typeName) throws ClassNotFoundException { if (typeMap.containsKey(typeName)) { return typeMap.get(typeName); } return Class.forName(typeName); } static String typeMap(Class type) { if (typeEncode.containsKey(type)) { return typeEncode.get(type); } /* * SortedSet<String> choose = new TreeSet<String>(); for (Iterator i = * typeMap.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Entry) i.next(); * * if (entry.getValue().equals(type)) { choose.add( (String) entry.getKey() ); } } if( * !choose.isEmpty() ){ return choose.last(); } */ return type.getName(); } /** * Factory method to produce Comparator based on provided Query SortBy information. * <p> * This method handles: * <ul> * <li>{@link SortBy#NATURAL_ORDER}: As sorting by FeatureID * </ul> * * @param sortBy * @return Comparator suitable for use with Arrays.sort( SimpleFeature[], comparator ) */ public static Comparator<SimpleFeature> sortComparator(SortBy sortBy) { if (sortBy == null) { sortBy = SortBy.NATURAL_ORDER; } if (sortBy == SortBy.NATURAL_ORDER) { return new Comparator<SimpleFeature>() { public int compare(SimpleFeature f1, SimpleFeature f2) { return f1.getID().compareTo(f2.getID()); } }; } else if (sortBy == SortBy.REVERSE_ORDER) { return new Comparator<SimpleFeature>() { public int compare(SimpleFeature f1, SimpleFeature f2) { int compare = f1.getID().compareTo(f2.getID()); return -compare; } }; } else { final PropertyName PROPERTY = sortBy.getPropertyName(); return new Comparator<SimpleFeature>() { @SuppressWarnings("unchecked") public int compare(SimpleFeature f1, SimpleFeature f2) { Object value1 = PROPERTY.evaluate(f1, Comparable.class); Object value2 = PROPERTY.evaluate(f2, Comparable.class); if (value1 == null || value2 == null) { return 0; // cannot perform comparison } if (value1 instanceof Comparable && value1.getClass().isInstance(value2)) { return ((Comparable<Object>) value1).compareTo(value2); } else { return 0; // cannot perform comparison } } }; } } /** * Takes two {@link Query}objects and produce a new one by mixing the restrictions of both of * them. * * <p> * The policy to mix the queries components is the following: * * <ul> * <li> * typeName: type names MUST match (not checked if some or both queries equals to * <code>Query.ALL</code>)</li> * <li> * handle: you must provide one since no sensible choice can be done between the handles of both * queries</li> * <li> * maxFeatures: the lower of the two maxFeatures values will be used (most restrictive)</li> * <li> * attributeNames: the attributes of both queries will be joined in a single set of attributes. * IMPORTANT: only <b><i>explicitly</i></b> requested attributes will be joint, so, if the * method <code>retrieveAllProperties()</code> of some of the queries returns <code>true</code> * it does not means that all the properties will be joined. You must create the query with the * names of the properties you want to load.</li> * <li> * filter: the filtets of both queries are or'ed</li> * <li> * <b>any other query property is ignored</b> and no guarantees are made of their return values, * so client code shall explicitly care of hints, startIndex, etc., if needed.</li> * </ul> * </p> * * @param firstQuery * Query against this DataStore * @param secondQuery * DOCUMENT ME! * @param handle * DOCUMENT ME! * * @return Query restricted to the limits of definitionQuery * * @throws NullPointerException * if some of the queries is null * @throws IllegalArgumentException * if the type names of both queries do not match */ public static Query mixQueries(Query firstQuery, Query secondQuery, String handle) { if ((firstQuery == null) && (secondQuery == null)) { // throw new NullPointerException("Cannot combine two null queries"); return Query.ALL; } if (firstQuery == null || firstQuery.equals(Query.ALL)) { return secondQuery; } else if (secondQuery == null || secondQuery.equals(Query.ALL)) { return firstQuery; } if ((firstQuery.getTypeName() != null) && (secondQuery.getTypeName() != null)) { if (!firstQuery.getTypeName().equals(secondQuery.getTypeName())) { String msg = "Type names do not match: " + firstQuery.getTypeName() + " != " + secondQuery.getTypeName(); throw new IllegalArgumentException(msg); } } // mix versions, if possible String version; if (firstQuery.getVersion() != null) { if (secondQuery.getVersion() != null && !secondQuery.getVersion().equals(firstQuery.getVersion())) throw new IllegalArgumentException( "First and second query refer different versions"); version = firstQuery.getVersion(); } else { version = secondQuery.getVersion(); } // none of the queries equals Query.ALL, mix them // use the more restrictive max features field int maxFeatures = Math.min(firstQuery.getMaxFeatures(), secondQuery.getMaxFeatures()); // join attributes names List<PropertyName> propNames = joinAttributes(firstQuery.getProperties(), secondQuery.getProperties()); // join filters Filter filter = firstQuery.getFilter(); Filter filter2 = secondQuery.getFilter(); if ((filter == null) || filter.equals(Filter.INCLUDE)) { filter = filter2; } else if ((filter2 != null) && !filter2.equals(Filter.INCLUDE)) { filter = ff.and(filter, filter2); } Integer start = 0; if (firstQuery.getStartIndex() != null) { start = firstQuery.getStartIndex(); } if (secondQuery.getStartIndex() != null) { start += secondQuery.getStartIndex(); } // collect all hints Hints hints = new Hints(); if (firstQuery.getHints() != null) { hints.putAll(firstQuery.getHints()); } if (secondQuery.getHints() != null) { hints.putAll(secondQuery.getHints()); } // build the mixed query String typeName = firstQuery.getTypeName() != null ? firstQuery.getTypeName() : secondQuery .getTypeName(); Query mixed = new Query(typeName, filter, maxFeatures, propNames, handle); mixed.setVersion(version); mixed.setHints(hints); if (start != 0) { mixed.setStartIndex(start); } return mixed; } /** * This method changes the query object so that all propertyName references are resolved to * simple attribute names against the schema of the feature source. * <p> * For example, this method ensures that propertyName's such as "gml:name" are rewritten as * simply "name". * </p> */ public static Query resolvePropertyNames(Query query, SimpleFeatureType schema) { Filter resolved = resolvePropertyNames(query.getFilter(), schema); if (resolved == query.getFilter()) { return query; } Query newQuery = new Query(query); newQuery.setFilter(resolved); return newQuery; } /** Transform provided filter; resolving property names */ public static Filter resolvePropertyNames(Filter filter, SimpleFeatureType schema) { if (filter == null || filter == Filter.INCLUDE || filter == Filter.EXCLUDE) { return filter; } return (Filter) filter.accept(new PropertyNameResolvingVisitor(schema), null); } /** * Creates a set of attribute names from the two input lists of names, maintaining the order of * the first list and appending the non repeated names of the second. * <p> * In the case where both lists are <code>null</code>, <code>null</code> is returned. * </p> * * @param atts1 * the first list of attribute names, who's order will be maintained * @param atts2 * the second list of attribute names, from wich the non repeated names will be * appended to the resulting list * * @return Set of attribute names from <code>atts1</code> and <code>atts2</code> */ private static List<PropertyName> joinAttributes(List<PropertyName> atts1, List<PropertyName> atts2) { if (atts1 == null && atts2 == null) { return null; } List<PropertyName> atts = new LinkedList<PropertyName>(); if (atts1 != null) { atts.addAll(atts1); } if (atts2 != null) { for (int i = 0; i < atts2.size(); i++) { if (!atts.contains(atts2.get(i))) { atts.add(atts2.get(i)); } } } return atts; } /** * Returns a list of properties of a simple feature type, including all properties from a given * list, and all mandatory (minoccurs > 0) added. * * @param type * feature type * @param propNames * given list of properties * @return list of properties including all mandatory properties * @throws IOException */ public static List<PropertyName> addMandatoryProperties(SimpleFeatureType type, List<PropertyName> oldProps) { Iterator<PropertyDescriptor> ii = type.getDescriptors().iterator(); List<PropertyName> properties = new ArrayList<PropertyName>(); while (ii.hasNext()) { PropertyDescriptor descr = ii.next(); PropertyName propName = ff.property(descr.getName()); if (oldProps != null && oldProps.contains(propName)) { properties.add(propName); } else if (((descr.getMinOccurs() > 0) && (descr.getMaxOccurs() != 0))) { // mandatory, add it properties.add(propName); } } return properties; } /** * Returns AttributeType based on String specification (based on UML). * * <p> * Will parse a String of the form: <i>"name:Type:hint"</i> * </p> * * <p> * Where <i>Type</i> is: * </p> * * <ul> * <li> * 0,Interger,int: represents Interger</li> * <li> * 0.0, Double, double: represents Double</li> * <li> * "",String,string: represents String</li> * <li> * Geometry: represents Geometry</li> * <li> * <i>full.class.path</i>: represents java type</li> * </ul> * * <p> * Where <i>hint</i> is "hint1;hint2;...;hintN", in which "hintN" is one of: * <ul> * <li><code>nillable</code></li> * <li><code>srid=<#></code></li> * </ul> * </p> * * @param typeSpec * * * @throws SchemaException * If typeSpect could not be interpreted */ static AttributeDescriptor createAttribute(String typeSpec) throws SchemaException { int split = typeSpec.indexOf(":"); String name; String type; String hint = null; if (split == -1) { name = typeSpec; type = "String"; } else { name = typeSpec.substring(0, split); int split2 = typeSpec.indexOf(":", split + 1); if (split2 == -1) { type = typeSpec.substring(split + 1); } else { type = typeSpec.substring(split + 1, split2); hint = typeSpec.substring(split2 + 1); } } try { boolean nillable = true; CoordinateReferenceSystem crs = null; if (hint != null) { StringTokenizer st = new StringTokenizer(hint, ";"); while (st.hasMoreTokens()) { String h = st.nextToken(); h = h.trim(); // nillable? // JD: i am pretty sure this hint is useless since the // default is to make attributes nillable if (h.equals("nillable")) { nillable = true; } // spatial reference identieger? if (h.startsWith("srid=")) { String srid = h.split("=")[1]; Integer.parseInt(srid); try { crs = CRS.decode("EPSG:" + srid); } catch (Exception e) { String msg = "Error decoding srs: " + srid; throw new SchemaException(msg, e); } } } } Class clazz = type(type); if (Geometry.class.isAssignableFrom(clazz)) { GeometryType at = new GeometryTypeImpl(new NameImpl(name), clazz, crs, false, false, Collections.EMPTY_LIST, null, null); return new GeometryDescriptorImpl(at, new NameImpl(name), 0, 1, nillable, null); } else { AttributeType at = new AttributeTypeImpl(new NameImpl(name), clazz, false, false, Collections.EMPTY_LIST, null, null); return new AttributeDescriptorImpl(at, new NameImpl(name), 0, 1, nillable, null); } } catch (ClassNotFoundException e) { throw new SchemaException("Could not type " + name + " as:" + type, e); } } /** * Manually calculates the bounds of a feature collection. * * @param collection * @return */ public static Envelope bounds( FeatureCollection<? extends FeatureType, ? extends Feature> collection) { FeatureIterator<? extends Feature> i = collection.features(); try { ReferencedEnvelope bounds = new ReferencedEnvelope(collection.getSchema() .getCoordinateReferenceSystem()); if (!i.hasNext()) { bounds.setToNull(); return bounds; } bounds.init(((SimpleFeature) i.next()).getBounds()); return bounds; } finally { i.close(); } } /** * Changes the ending (e.g. ".sld") of a {@link URL} * * @param url * {@link URL} like <code>file:/sds/a.bmp</code> or * <code>http://www.some.org/foo/bar.shp</code> * @param postfix * New file extension for the {@link URL} without <code>.</code> * * @return A new {@link URL} with new extension. * * @throws {@link MalformedURLException} if the new {@link URL} can not be created. */ public static URL changeUrlExt(final URL url, final String postfix) throws IllegalArgumentException { String a = url.toExternalForm(); final int lastDotPos = a.lastIndexOf('.'); if (lastDotPos >= 0) a = a.substring(0, lastDotPos); a = a + "." + postfix; try { return new URL(a); } catch (final MalformedURLException e) { throw new IllegalArgumentException("can't create a new URL for " + url + " with new extension " + postfix, e); } } /** * The function is supposed to be equivalent to {@link File}.getParent(). The {@link URL} is * converted to a String, truncated to the last / and then recreated as a new URL. * * @throws {@link MalformedURLException} if the parent {@link URL} can not be created. */ public static URL getParentUrl(final URL url) throws MalformedURLException { String a = url.toExternalForm(); final int lastDotPos = a.lastIndexOf('/'); if (lastDotPos >= 0) a = a.substring(0, lastDotPos); /** * The parent of jar:file:some!/bar.file is jar:file:some!/, not jar:file:some! */ if (a.endsWith("!")) a += "/"; return new URL(a); } /** * Extends an {@link URL}. * * @param base * Has to be a {@link URL} pointing to a directory. If it doesn't end with a * <code>/</code> it will be added automatically. * @param extension * The part that will be added to the {@link URL} * * @throws MalformedURLException * if the new {@link URL} can not be created. */ public static URL extendURL(URL base, String extension) throws MalformedURLException { if (base == null) throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, "base")); if (extension == null) throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, "extension")); String a = base.toExternalForm(); if (!a.endsWith("/")) a += "/"; a += extension; return new URL(a); } /** * Checks that a {@link File} is a real file, exists and is readable. * * @param file * the {@link File} instance to check. Must not be null. * @param logger * an optional {@link Logger} (can be null) where to log detailed info about the file * properties (path/readable/hidden/writable) * * @return {@code true} in case the file is a real file, exists and is readable; {@code false} * otherwise. */ public static boolean checkFileReadable(final File file, final Logger logger) { if (logger != null && logger.isLoggable(Level.FINE)) { final StringBuilder builder = new StringBuilder("Checking file:") .append(file.getAbsolutePath()).append("\n").append("canRead:") .append(file.canRead()).append("\n").append("isHidden:") .append(file.isHidden()).append("\n").append("isFile").append(file.isFile()) .append("\n").append("canWrite").append(file.canWrite()).append("\n"); logger.fine(builder.toString()); } if (!file.exists() || !file.canRead() || !file.isFile()) return false; return true; } /** * Checks that the provided directory path refers to an existing/readable directory. Finally, * return it as a normalized directory path (removing double and single dot path steps if any) * followed by the separator char if missing ({@code '/'} On UNIX systems; {@code '\\} on * Microsoft Windows systems. * * @param directoryPath * the input directory path. Must not be null. * @return the re-formatted directory path. * @throws IllegalArgumentException * in case the specified path doesn't rely on a existing/readable directory. */ public static File checkDirectory(File file) throws IllegalArgumentException { String directoryPath = file.getPath(); File inDir = file; if (!inDir.isDirectory()) { throw new IllegalArgumentException("Not a directory: " + directoryPath); } if (!inDir.canRead()) { throw new IllegalArgumentException("Not a writable directory: " + directoryPath); } try { directoryPath = inDir.getCanonicalPath(); } catch (IOException e) { throw new IllegalArgumentException(e); } /* * directoryPath = FilenameUtils.normalize(directoryPath); if * (!directoryPath.endsWith(File.separator)){ directoryPath = directoryPath + * File.separator; } */ // test to see if things are still good inDir = new File(directoryPath); if (!inDir.isDirectory()) { throw new IllegalArgumentException("Not a directory: " + directoryPath); } if (!inDir.canRead()) { throw new IllegalArgumentException("Not a writable directory: " + directoryPath); } return new File(directoryPath); } /** * Returns a {@link IOFileFilter} obtained by excluding from the first input filter argument, * the additional filter arguments. * * @param inputFilter * the initial filter from which to exclude other ones. * @param filters * additional filters to be excluded * * @return the updated {@link IOFileFilter} */ public static FilenameFilter excludeFilters(final FilenameFilter inputFilter, final FilenameFilter... filters) { return new FilenameFilter() { public boolean accept(File dir, String name) { if (inputFilter.accept(dir, name)) { for (FilenameFilter exclude : filters) { if (exclude.accept(dir, name)) { return false; } } return true; } return false; } }; } /** * Returns a {@link IOFileFilter} obtained by adding to the first input filter argument, the * additional filter arguments. * * @param inputFilter * the initial filter to which to add other ones. * @param filters * additional filters to be included in the main filter. * * @return the updated {@link IOFileFilter} */ public static FilenameFilter includeFilters(final FilenameFilter inputFilter, final FilenameFilter... filters) { return new FilenameFilter() { public boolean accept(File dir, String name) { if (inputFilter.accept(dir, name)) { return true; } for (FilenameFilter include : filters) { if (include.accept(dir, name)) { return true; } } return false; } }; } }