001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.feature;
017:
018: import java.net.URI;
019: import java.net.URISyntaxException;
020: import java.util.Arrays;
021: import java.util.Collections;
022: import java.util.HashSet;
023: import java.util.Set;
024:
025: import org.geotools.factory.FactoryConfigurationError;
026: import org.geotools.filter.LengthFunction;
027: import org.geotools.geometry.jts.JTS;
028: import org.opengis.filter.BinaryComparisonOperator;
029: import org.opengis.filter.Filter;
030: import org.opengis.filter.PropertyIsLessThan;
031: import org.opengis.filter.PropertyIsLessThanOrEqualTo;
032: import org.opengis.filter.expression.Literal;
033: import org.opengis.geometry.MismatchedDimensionException;
034: import org.opengis.referencing.crs.CoordinateReferenceSystem;
035: import org.opengis.referencing.operation.MathTransform;
036: import org.opengis.referencing.operation.TransformException;
037:
038: import com.vividsolutions.jts.geom.Geometry;
039:
040: /**
041: * Utility methods for working against the FeatureType interface.
042: * <p>
043: * Many methods from DataUtilities should be refractored here.
044: * </p>
045: * <p>
046: * Responsibilities:
047: * <ul>
048: * <li>Schema construction from String spec
049: * <li>Schema Force CRS
050: * </ul>
051: *
052: * @author Jody Garnett, Refractions Research
053: * @since 2.1.M3
054: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/feature/FeatureTypes.java $
055: */
056: public class FeatureTypes {
057:
058: /** the default namespace for feature types */
059: //public static final URI = GMLSchema.NAMESPACE;
060: public static final URI DEFAULT_NAMESPACE;
061: static {
062: URI uri;
063: try {
064: uri = new URI("http://www.opengis.net/gml");
065: } catch (URISyntaxException e) {
066: uri = null; //will never happen
067: }
068: DEFAULT_NAMESPACE = uri;
069: }
070:
071: /** represent an unbounded field length */
072: final public static int ANY_LENGTH = -1;
073:
074: /**
075: * This is a 'suitable replacement for extracting the expected field length of an attribute
076: * absed on its "facets" (ie Filter describing type restrictions);
077: * <p>
078: * This code is copied from the ShapefileDataStore where it was written (probably by dzwiers).
079: * Cholmes is providing documentation.
080: * </p>
081: *
082: * @param type the AttributeType
083: *
084: * @return an int indicating the max length of field in characters, or ANY_LENGTH
085: */
086: public static int getFieldLength(AttributeType type) {
087:
088: Class colType = type.getType();
089: String colName = type.getName();
090:
091: int fieldLen = -1;
092: Filter f = type.getRestriction();
093: if (f != null
094: && f != Filter.EXCLUDE
095: && f != Filter.INCLUDE
096: && (f instanceof PropertyIsLessThan || f instanceof PropertyIsLessThanOrEqualTo)) {
097: try {
098: BinaryComparisonOperator cf = (BinaryComparisonOperator) f;
099: if (cf.getExpression1() instanceof LengthFunction) {
100: return Integer.parseInt(((Literal) cf
101: .getExpression2()).getValue().toString());
102: } else if (cf.getExpression2() instanceof LengthFunction) {
103: return Integer.parseInt(((Literal) cf
104: .getExpression1()).getValue().toString());
105: } else {
106: return ANY_LENGTH;
107: }
108: } catch (NumberFormatException e) {
109: return ANY_LENGTH;
110: }
111: } else {
112: return ANY_LENGTH;
113: }
114: }
115:
116: /**
117: * Forces the specified CRS on all geometry attributes
118: * @param schema the original schema
119: * @param crs the forced crs
120: * @return
121: * @throws SchemaException
122: */
123: public static FeatureType transform(FeatureType schema,
124: CoordinateReferenceSystem crs) throws SchemaException {
125: return transform(schema, crs, false);
126: }
127:
128: /**
129: * Forces the specified CRS on geometry attributes (all or some, depends on the parameters).
130: * @param schema the original schema
131: * @param crs the forced crs
132: * @param forceOnlyMissing if true, will force the specified crs only on the attributes that
133: * do miss one
134: * @return
135: * @throws SchemaException
136: */
137: public static FeatureType transform(FeatureType schema,
138: CoordinateReferenceSystem crs, boolean forceOnlyMissing)
139: throws SchemaException {
140: FeatureTypeFactory factory = FeatureTypeFactory
141: .newInstance(schema.getTypeName());
142:
143: factory.setNamespace(schema.getNamespace());
144: factory.setName(schema.getTypeName());
145:
146: GeometryAttributeType defaultGeometryType = null;
147: for (int i = 0; i < schema.getAttributeCount(); i++) {
148: AttributeType attributeType = schema.getAttributeType(i);
149: if (attributeType instanceof GeometryAttributeType) {
150: GeometryAttributeType geometryType = (GeometryAttributeType) attributeType;
151: GeometryAttributeType forced;
152:
153: if (forceOnlyMissing
154: && geometryType.getCoordinateSystem() != null)
155: forced = geometryType;
156: else
157: forced = (GeometryAttributeType) AttributeTypeFactory
158: .newAttributeType(geometryType.getName(),
159: geometryType.getType(),
160: geometryType.isNillable(), 0,
161: geometryType.createDefaultValue(),
162: crs);
163:
164: if (defaultGeometryType == null
165: || geometryType == schema.getDefaultGeometry()) {
166: defaultGeometryType = forced;
167: }
168: factory.addType(forced);
169: } else {
170: factory.addType(attributeType);
171: }
172: }
173: factory.setDefaultGeometry(defaultGeometryType);
174: return factory.getFeatureType();
175: }
176:
177: /**
178: * Applies transform to all geometry attribute.
179: *
180: * @param feature Feature to be transformed
181: * @param schema Schema for target transformation - transform( schema, crs )
182: * @param transform MathTransform used to transform coordinates - reproject( crs, crs )
183: * @return transformed Feature of type schema
184: * @throws TransformException
185: * @throws MismatchedDimensionException
186: * @throws IllegalAttributeException
187: */
188: public static Feature transform(Feature feature,
189: FeatureType schema, MathTransform transform)
190: throws MismatchedDimensionException, TransformException,
191: IllegalAttributeException {
192: feature = schema.create(feature.getAttributes(null), feature
193: .getID());
194:
195: GeometryAttributeType geomType = schema.getDefaultGeometry();
196: Geometry geom = (Geometry) feature.getAttribute(geomType
197: .getName());
198:
199: geom = JTS.transform(geom, transform);
200:
201: try {
202: feature.setAttribute(geomType.getName(), geom);
203: } catch (IllegalAttributeException shouldNotHappen) {
204: // we are expecting the transform to return the same geometry type
205: }
206: return feature;
207: }
208:
209: /**
210: * The most specific way to create a new FeatureType.
211: *
212: * @param types The AttributeTypes to create the FeatureType with.
213: * @param name The typeName of the FeatureType. Required, may not be null.
214: * @param ns The namespace of the FeatureType. Optional, may be null.
215: * @param isAbstract True if this created type should be abstract.
216: * @param superTypes A Collection of types the FeatureType will inherit from. Currently, all
217: * types inherit from feature in the opengis namespace.
218: * @return A new FeatureType created from the given arguments.
219: * @throws FactoryConfigurationError If there are problems creating a factory.
220: * @throws SchemaException If the AttributeTypes provided are invalid in some way.
221: */
222: public static FeatureType newFeatureType(AttributeType[] types,
223: String name, URI ns, boolean isAbstract,
224: FeatureType[] super Types) throws FactoryConfigurationError,
225: SchemaException {
226: return newFeatureType(types, name, ns, isAbstract, super Types,
227: null);
228: }
229:
230: /**
231: * The most specific way to create a new FeatureType.
232: *
233: * @param types The AttributeTypes to create the FeatureType with.
234: * @param name The typeName of the FeatureType. Required, may not be null.
235: * @param ns The namespace of the FeatureType. Optional, may be null.
236: * @param isAbstract True if this created type should be abstract.
237: * @param superTypes A Collection of types the FeatureType will inherit from. Currently, all
238: * types inherit from feature in the opengis namespace.
239: * @return A new FeatureType created from the given arguments.
240: * @throws FactoryConfigurationError If there are problems creating a factory.
241: * @throws SchemaException If the AttributeTypes provided are invalid in some way.
242: */
243: public static FeatureType newFeatureType(AttributeType[] types,
244: String name, URI ns, boolean isAbstract,
245: FeatureType[] super Types, AttributeType defaultGeometry)
246: throws FactoryConfigurationError, SchemaException {
247: FeatureTypeFactory factory = FeatureTypeFactory
248: .newInstance(name);
249: factory.addTypes(types);
250: factory.setNamespace(ns);
251: factory.setAbstract(isAbstract);
252: if (defaultGeometry != null)
253: factory
254: .setDefaultGeometry((GeometryAttributeType) defaultGeometry);
255:
256: if (super Types != null) {
257: factory.setSuperTypes(Arrays.asList(super Types));
258: }
259:
260: return factory.getFeatureType();
261: }
262:
263: /**
264: * The most specific way to create a new FeatureType.
265: *
266: * @param types The AttributeTypes to create the FeatureType with.
267: * @param name The typeName of the FeatureType. Required, may not be null.
268: * @param ns The namespace of the FeatureType. Optional, may be null.
269: * @param isAbstract True if this created type should be abstract.
270: * @param superTypes A Collection of types the FeatureType will inherit from. Currently, all
271: * types inherit from feature in the opengis namespace.
272: * @return A new FeatureType created from the given arguments.
273: * @throws FactoryConfigurationError If there are problems creating a factory.
274: * @throws SchemaException If the AttributeTypes provided are invalid in some way.
275: */
276: public static FeatureType newFeatureType(AttributeType[] types,
277: String name, URI ns, boolean isAbstract,
278: FeatureType[] super Types,
279: GeometryAttributeType defaultGeometry)
280: throws FactoryConfigurationError, SchemaException {
281: FeatureTypeFactory factory = FeatureTypeFactory
282: .newInstance(name);
283: factory.addTypes(types);
284: factory.setNamespace(ns);
285: factory.setAbstract(isAbstract);
286:
287: if (super Types != null) {
288: factory.setSuperTypes(Arrays.asList(super Types));
289: }
290:
291: if (defaultGeometry != null) {
292: factory.setDefaultGeometry(defaultGeometry);
293: }
294:
295: return factory.getFeatureType();
296: }
297:
298: /**
299: * Create a new FeatureType with the given AttributeTypes. A short cut for calling
300: * <code>newFeatureType(types,name,ns,isAbstract,null)</code>.
301: *
302: * @param types The AttributeTypes to create the FeatureType with.
303: * @param name The typeName of the FeatureType. Required, may not be null.
304: * @param ns The namespace of the FeatureType. Optional, may be null.
305: * @param isAbstract True if this created type should be abstract.
306: * @return A new FeatureType created from the given arguments.
307: * @throws FactoryConfigurationError If there are problems creating a factory.
308: * @throws SchemaException If the AttributeTypes provided are invalid in some way.
309: */
310: public static FeatureType newFeatureType(AttributeType[] types,
311: String name, URI ns, boolean isAbstract)
312: throws FactoryConfigurationError, SchemaException {
313: return newFeatureType(types, name, ns, isAbstract, null);
314: }
315:
316: /**
317: * Create a new FeatureType with the given AttributeTypes. A short cut for calling
318: * <code>newFeatureType(types,name,ns,false,null)</code>.
319: *
320: * @param types The AttributeTypes to create the FeatureType with.
321: * @param name The typeName of the FeatureType. Required, may not be null.
322: * @param ns The namespace of the FeatureType. Optional, may be null.
323: * @return A new FeatureType created from the given arguments.
324: * @throws FactoryConfigurationError If there are problems creating a factory.
325: * @throws SchemaException If the AttributeTypes provided are invalid in some way.
326: */
327: public static FeatureType newFeatureType(AttributeType[] types,
328: String name, URI ns) throws FactoryConfigurationError,
329: SchemaException {
330: return newFeatureType(types, name, ns, false);
331: }
332:
333: /**
334: * Create a new FeatureType with the given AttributeTypes. A short cut for calling
335: * <code>newFeatureType(types,name,null,false,null)</code>. Useful for test cases or
336: * datasources which may not allow a namespace.
337: *
338: * @param types The AttributeTypes to create the FeatureType with.
339: * @param name The typeName of the FeatureType. Required, may not be null.
340: * @return A new FeatureType created from the given arguments.
341: * @throws FactoryConfigurationError If there are problems creating a factory.
342: * @throws SchemaException If the AttributeTypes provided are invalid in some way.
343: */
344: public static FeatureType newFeatureType(AttributeType[] types,
345: String name) throws FactoryConfigurationError,
346: SchemaException {
347: return newFeatureType(types, name, DEFAULT_NAMESPACE, false);
348: }
349:
350: /**
351: * A query of the the types ancestor information.
352: * <p>
353: * This utility method may be used as common implementation for
354: * <code>FeatureType.isDecendedFrom( namespace, typeName )</code>, however for specific uses,
355: * such as GML, an implementor may be able to provide a more efficient implemenation based on
356: * prior knolwege.
357: * </p>
358: * <p>
359: * This is a proper check, if the provided FeatureType matches the given namespace and typename
360: * it is <b>not </b> considered to be decended from itself.
361: * </p>
362: *
363: * @param featureType typeName with parentage in question
364: * @param namespace namespace to match against, or null for a "wildcard"
365: * @param typeName typename to match against, or null for a "wildcard"
366: * @return true if featureType is a decendent of the indicated namespace & typeName
367: */
368: public static boolean isDecendedFrom(FeatureType featureType,
369: URI namespace, String typeName) {
370: if (featureType == null)
371: return false;
372: FeatureType ancestors[] = featureType.getAncestors();
373: if (ancestors != null) {
374: TEST: for (int i = 0; i < ancestors.length; i++) {
375: FeatureType ancestor = ancestors[i];
376: if (namespace != null
377: && !namespace.equals(ancestor.getNamespace())) {
378: continue TEST;
379: }
380: if (typeName != null
381: && !namespace.equals(ancestor.getTypeName())) {
382: continue TEST;
383: }
384: return true; // we have a match
385: }
386: }
387: return false;
388: }
389:
390: public static boolean isDecendedFrom(FeatureType featureType,
391: FeatureType isParentType) {
392: if (featureType == null || isParentType == null)
393: return false;
394: FeatureType ancestors[] = featureType.getAncestors();
395: if (ancestors != null) {
396: TEST: for (int i = 0; i < ancestors.length; i++) {
397: FeatureType ancestor = ancestors[i];
398: if (isParentType == ancestor)
399: return true;
400: if (false) {
401: // hack idea #1?
402: if (isParentType.getNamespace().equals(
403: ancestor.getNamespace())) {
404: continue TEST;
405: }
406: if (isParentType.equals(ancestor.getTypeName())) {
407: continue TEST;
408: }
409: return true; // match based on namespace, typeName
410: }
411: }
412: }
413: return false;
414: }
415:
416: /** Exact equality based on typeNames, namespace, attributes and ancestors */
417: public static boolean equals(FeatureType typeA, FeatureType typeB) {
418: if (typeA == typeB)
419: return true;
420:
421: if (typeA == null || typeB == null) {
422: return false;
423: }
424: return equalsId(typeA, typeB)
425: && equals(typeA.getAttributeTypes(), typeB
426: .getAttributeTypes())
427: && equalsAncestors(typeA, typeB);
428: }
429:
430: public static boolean equals(AttributeType attributesA[],
431: AttributeType attributesB[]) {
432: if (attributesA.length != attributesB.length)
433: return false;
434:
435: for (int i = 0, length = attributesA.length; i < length; i++) {
436: if (!equals(attributesA[i], attributesB[i]))
437: return false;
438: }
439: return true;
440: }
441:
442: /**
443: * This method depends on the correct implementation of FeatureType equals
444: * <p>
445: * We may need to write an implementation that can detect cycles,
446: * </p>
447: *
448: * @param typeA
449: * @param typeB
450: */
451: public static boolean equalsAncestors(FeatureType typeA,
452: FeatureType typeB) {
453: return ancestors(typeA).equals(typeB);
454: }
455:
456: public static Set ancestors(FeatureType featureType) {
457: if (featureType == null || featureType.getAncestors() == null
458: || featureType.getAncestors().length == 0) {
459: return Collections.EMPTY_SET;
460: }
461: return new HashSet(Arrays.asList(featureType.getAncestors()));
462: }
463:
464: public static boolean equals(AttributeType a, AttributeType b) {
465: return a == b || (a != null && a.equals(b));
466: }
467:
468: /** Quick check of namespace and typename */
469: public static boolean equalsId(FeatureType typeA, FeatureType typeB) {
470: if (typeA == typeB)
471: return true;
472:
473: if (typeA == null || typeB == null) {
474: return false;
475: }
476:
477: String typeNameA = typeA.getTypeName();
478: String typeNameB = typeB.getTypeName();
479: if (typeNameA == null && typeNameB != null)
480: return false;
481: else if (!typeNameA.equals(typeNameB))
482: return false;
483:
484: URI namespaceA = typeA.getNamespace();
485: URI namespaceB = typeB.getNamespace();
486: if (namespaceA == null && namespaceB != null)
487: return false;
488: else if (!namespaceA.equals(namespaceB))
489: return false;
490:
491: return true;
492: }
493:
494: }
|