/*
* 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.FileFilter;
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.UUID;
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.SimpleFeatureReader;
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.AttributeImpl;
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.GeometryAttributeImpl;
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.geometry.jts.WKTReader2;
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.FeatureFactory;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.IllegalAttributeException;
import org.opengis.feature.Property;
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.geometry.BoundingBox;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.FactoryException;
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 with GeoTools with data classes.
*
* These methods fall into several categories:
*
* Conversion between common data structures.
*
*
{@link #collection} methods: creating/converting a {@link SimpleFeatureCollection} from a range of input.
*
{@link #simple} methods: adapting from generic Feature use to SimpleFeature. Used to convert to SimpleFeature,
* SimpleFeatureCollection,SimpleFeatureSource
*
{@link #list} to quickly copy features into a memory based list
*
{@link #reader} methods to convert to FeatureReader
*
{@link #source} setup a FeatureSource wrapper around the provided data
*
*
*
* SimpleFeatureType and SimpleFeature encoding/decoding from String as used by the PropertyDataStore tutorials.
*
*
{@link #createType} methods: to create a SimpleFeatureType from a one line text string
*
{@link #encodeType}: text string representation of a SimpleFeaturerType
*
{@link #createFeature}: create a SimpleFeature from a one line text String
*
{@link #encodeFeature}: encode a feature as a single line text string
*
*
*
* Working with SimpleFeatureType (this class is immutable so we always have to make a modified copy):
*
*
{@link #createSubType(SimpleFeatureType, String[])} methods return a modified copy of an origional feature type. Used to cut down an exsiting
* feature type to reflect only the attributes requested when using {@link SimpleFeatureSource#getFeatures(Filter)}.
*
{@link #compare} and {@link #isMatch(AttributeDescriptor, AttributeDescriptor)} are used to check for types compatible with
* {@link #createSubType} used to verify that feature values can be copied across
*
*
*
* Manipulating individual features and data values:
*
*
{@link #reType} generates a cut down version of an original feature in the same manners as {@link #createSubType}
*
{@link #template} and {@link #defaultValue} methods which uses {@link AttributeDescriptor#getDefaultValue()} when creating new empty features
*
{@link #duplicate(Object)} used for deep copy of feature data
*
*
*
* And a grab bag of helpful utility methods for those implementing a DataStore:
*
*
{@link #urlToFile(URL)} and {@link #fileToURL(File)} and {@link #getParentUrl(URL)} used to work with files across platforms
*
{@link #includeFilters} and {@link #excludeFilters} work as a compound {@link FileFilter} making {@link File#listFiles} easier to use
*
{@link #propertyNames}, {@link #fidSet}, {@link #attributeNames} methods are used to double check a provided query and ensure
* it can be correctly handed.
* {@link #sortComparator}, {@link #resolvePropertyNames} and {@link #mixQueries} are used to prep a {@link Query} prior to use
*
*
* @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 {
/** Typemap used by {@link #createType(String, String)} methods */
static Map typeMap = new HashMap();
/** Reverse type map used by {@link #encodeType(FeatureType)} */
static Map typeEncode = new HashMap();
static FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2();
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(UUID.class, "UUID");
typeMap.put("UUID", UUID.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().
*
* 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.
*
* 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.
*
* 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.
*
*/
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.
*
* 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.
*
*/
public static Set propertyNames(Filter filter, final SimpleFeatureType featureType) {
if (filter == null) {
return Collections.emptySet();
}
FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType);
filter.accept(attExtractor, null);
Set 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 propertyNames(Filter filter) {
return propertyNames(filter, null);
}
/**
* Traverses the expression and returns any encountered property names.
*
* 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.
*
*/
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.
*
* 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.
*
*/
public static Set propertyNames(Expression expression,
final SimpleFeatureType featureType) {
if (expression == null) {
return Collections.emptySet();
}
FilterAttributeExtractor attExtractor = new FilterAttributeExtractor(featureType);
expression.accept(attExtractor, null);
Set 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 propertyNames(Expression expression) {
return propertyNames(expression, null);
}
/**
* Compare attribute coverage between two feature types (allowing the identification of subTypes).
*
* The comparison results in a number with the following meaning:
*
*
*
*
* 1: if typeA is a sub type/reorder/renamespace of typeB
*
* 0: if typeA and typeB are the same type
*
* -1: if typeA is not subtype of typeB
*
*
*
* Comparison is based on {@link AttributeDescriptor} - the {@link #isMatch(AttributeDescriptor, AttributeDescriptor)}
* method is used to quickly confirm that the local name and java binding are compatible.
*
*
*
* Namespace is not considered in this opperations. You may still need to reType to get the
* correct namesapce, or reorder.
*
*
* Please note this method will not result in a stable sort if used in a {@link Comparator}
* as -1 is used to indicate incompatiblity (rather than simply "before").
*
* @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;
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)
return 0;
}
return 1;
}
/**
* Quickly check if two descriptors are at all compatible.
*
* 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.
*
* 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.
*
* The duplicate parameter indicates how the new feature is to be formed:
*
*
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.
*
duplicate is false: the attributes are simply reordered and are actually the same
* instances as those in the origional feature
*
* In the future this method may simply return a "wrapper" when duplicate is false.
*
*
* @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.
*
* A number of tricks are used to make this as fast as possible:
*
*
Simple or Immutable types are copied as is (String, Integer, Float, URL, etc..)
*
JTS Geometry objects are cloned
*
Arrays and the Collection classes are duplicated element by element
*
* This function is used recusively for (in order to handle complext features) no attempt
* is made to detect cycles at this time so your milage may vary.
*
* @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