source: osm/applications/editors/josm/plugins/opendata/includes/org/geotools/data/DataUtilities.java@ 28000

Last change on this file since 28000 was 28000, checked in by donvip, 12 years ago

Import new "opendata" JOSM plugin

File size: 30.5 KB
Line 
1/*
2 * GeoTools - The Open Source Java GIS Toolkit
3 * http://geotools.org
4 *
5 * (C) 2003-2008, Open Source Geospatial Foundation (OSGeo)
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation;
10 * version 2.1 of the License.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 */
17package org.geotools.data;
18
19import java.io.File;
20import java.io.IOException;
21import java.io.UnsupportedEncodingException;
22import java.lang.reflect.Array;
23import java.math.BigDecimal;
24import java.math.BigInteger;
25import java.net.MalformedURLException;
26import java.net.URI;
27import java.net.URISyntaxException;
28import java.net.URL;
29import java.net.URLDecoder;
30import java.sql.Timestamp;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collections;
34import java.util.Comparator;
35import java.util.Date;
36import java.util.HashMap;
37import java.util.Iterator;
38import java.util.LinkedList;
39import java.util.List;
40import java.util.Map;
41
42import org.geotools.factory.CommonFactoryFinder;
43import org.geotools.factory.Hints;
44import org.geotools.feature.AttributeTypeBuilder;
45import org.geotools.feature.FeatureCollection;
46import org.geotools.feature.FeatureIterator;
47import org.geotools.feature.SchemaException;
48import org.geotools.feature.simple.SimpleFeatureBuilder;
49import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
50import org.geotools.filter.visitor.PropertyNameResolvingVisitor;
51import org.geotools.geometry.jts.ReferencedEnvelope;
52import org.geotools.util.Utilities;
53import org.opengis.feature.Feature;
54import org.opengis.feature.FeatureVisitor;
55import org.opengis.feature.IllegalAttributeException;
56import org.opengis.feature.simple.SimpleFeature;
57import org.opengis.feature.simple.SimpleFeatureType;
58import org.opengis.feature.type.AttributeDescriptor;
59import org.opengis.feature.type.FeatureType;
60import org.opengis.feature.type.GeometryDescriptor;
61import org.opengis.filter.Filter;
62import org.opengis.filter.FilterFactory;
63import org.opengis.filter.expression.PropertyName;
64import org.opengis.filter.sort.SortBy;
65import org.opengis.referencing.crs.CoordinateReferenceSystem;
66
67import com.vividsolutions.jts.geom.Coordinate;
68import com.vividsolutions.jts.geom.Envelope;
69import com.vividsolutions.jts.geom.Geometry;
70import com.vividsolutions.jts.geom.GeometryCollection;
71import com.vividsolutions.jts.geom.GeometryFactory;
72import com.vividsolutions.jts.geom.LineString;
73import com.vividsolutions.jts.geom.LinearRing;
74import com.vividsolutions.jts.geom.MultiLineString;
75import com.vividsolutions.jts.geom.MultiPoint;
76import com.vividsolutions.jts.geom.MultiPolygon;
77import com.vividsolutions.jts.geom.Point;
78import com.vividsolutions.jts.geom.Polygon;
79
80/**
81 * Utility functions for use when implementing working with data classes.
82 * <p>
83 * TODO: Move FeatureType manipulation to feature package
84 * </p>
85 *
86 * @author Jody Garnett, Refractions Research
87 *
88 * @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/modules/library/main/src/main/java/org/geotools/data/DataUtilities.java $
89 * http://svn.osgeo.org/geotools/trunk/modules/library/main/src/main/java/org/geotools/
90 * data/DataUtilities.java $
91 */
92public class DataUtilities {
93
94 static Map<String, Class> typeMap = new HashMap<String, Class>();
95
96 static Map<Class, String> typeEncode = new HashMap<Class, String>();
97
98 static FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
99
100 static {
101 typeEncode.put(String.class, "String");
102 typeMap.put("String", String.class);
103 typeMap.put("string", String.class);
104 typeMap.put("\"\"", String.class);
105
106 typeEncode.put(Integer.class, "Integer");
107 typeMap.put("Integer", Integer.class);
108 typeMap.put("int", Integer.class);
109 typeMap.put("0", Integer.class);
110
111 typeEncode.put(Double.class, "Double");
112 typeMap.put("Double", Double.class);
113 typeMap.put("double", Double.class);
114 typeMap.put("0.0", Double.class);
115
116 typeEncode.put(Float.class, "Float");
117 typeMap.put("Float", Float.class);
118 typeMap.put("float", Float.class);
119 typeMap.put("0.0f", Float.class);
120
121 typeEncode.put(Boolean.class, "Boolean");
122 typeMap.put("Boolean", Boolean.class);
123 typeMap.put("true", Boolean.class);
124 typeMap.put("false", Boolean.class);
125
126 typeEncode.put(Geometry.class, "Geometry");
127 typeMap.put("Geometry", Geometry.class);
128
129 typeEncode.put(Point.class, "Point");
130 typeMap.put("Point", Point.class);
131
132 typeEncode.put(LineString.class, "LineString");
133 typeMap.put("LineString", LineString.class);
134
135 typeEncode.put(Polygon.class, "Polygon");
136 typeMap.put("Polygon", Polygon.class);
137
138 typeEncode.put(MultiPoint.class, "MultiPoint");
139 typeMap.put("MultiPoint", MultiPoint.class);
140
141 typeEncode.put(MultiLineString.class, "MultiLineString");
142 typeMap.put("MultiLineString", MultiLineString.class);
143
144 typeEncode.put(MultiPolygon.class, "MultiPolygon");
145 typeMap.put("MultiPolygon", MultiPolygon.class);
146
147 typeEncode.put(GeometryCollection.class, "GeometryCollection");
148 typeMap.put("GeometryCollection", GeometryCollection.class);
149
150 typeEncode.put(Date.class, "Date");
151 typeMap.put("Date", Date.class);
152 }
153
154 /**
155 * A replacement for File.toURI().toURL().
156 * <p>
157 * The handling of file.toURL() is broken; the handling of file.toURI().toURL() is known to be
158 * broken on a few platforms like mac. We have the urlToFile( URL ) method that is able to
159 * untangle both these problems and we use it in the geotools library.
160 * <p>
161 * However occasionally we need to pick up a file and hand it to a third party library like EMF;
162 * this method performs a couple of sanity checks which we can use to prepare a good URL
163 * reference to a file in these situtations.
164 *
165 * @param file
166 * @return URL
167 */
168 public static URL fileToURL(File file) {
169 try {
170 URL url = file.toURI().toURL();
171 String string = url.toExternalForm();
172 if (string.contains("+")) {
173 // this represents an invalid URL created using either
174 // file.toURL(); or
175 // file.toURI().toURL() on a specific version of Java 5 on Mac
176 string = string.replace("+", "%2B");
177 }
178 if (string.contains(" ")) {
179 // this represents an invalid URL created using either
180 // file.toURL(); or
181 // file.toURI().toURL() on a specific version of Java 5 on Mac
182 string = string.replace(" ", "%20");
183 }
184 return new URL(string);
185 } catch (MalformedURLException e) {
186 return null;
187 }
188 }
189
190 /**
191 * Takes a URL and converts it to a File. The attempts to deal with Windows UNC format specific
192 * problems, specifically files located on network shares and different drives.
193 *
194 * If the URL.getAuthority() returns null or is empty, then only the url's path property is used
195 * to construct the file. Otherwise, the authority is prefixed before the path.
196 *
197 * It is assumed that url.getProtocol returns "file".
198 *
199 * Authority is the drive or network share the file is located on. Such as "C:", "E:",
200 * "\\fooServer"
201 *
202 * @param url
203 * a URL object that uses protocol "file"
204 * @return a File that corresponds to the URL's location
205 */
206 public static File urlToFile(URL url) {
207 if (!"file".equals(url.getProtocol())) {
208 return null; // not a File URL
209 }
210 String string = url.toExternalForm();
211 if (string.contains("+")) {
212 // this represents an invalid URL created using either
213 // file.toURL(); or
214 // file.toURI().toURL() on a specific version of Java 5 on Mac
215 string = string.replace("+", "%2B");
216 }
217 try {
218 string = URLDecoder.decode(string, "UTF-8");
219 } catch (UnsupportedEncodingException e) {
220 throw new RuntimeException("Could not decode the URL to UTF-8 format", e);
221 }
222
223 String path3;
224
225 String simplePrefix = "file:/";
226 String standardPrefix = "file://";
227 String os = System.getProperty("os.name");
228
229 if (os.toUpperCase().contains("WINDOWS") && string.startsWith(standardPrefix)) {
230 // win32: host/share reference
231 path3 = string.substring(standardPrefix.length() - 2);
232 } else if (string.startsWith(standardPrefix)) {
233 path3 = string.substring(standardPrefix.length());
234 } else if (string.startsWith(simplePrefix)) {
235 path3 = string.substring(simplePrefix.length() - 1);
236 } else {
237 String auth = url.getAuthority();
238 String path2 = url.getPath().replace("%20", " ");
239 if (auth != null && !auth.equals("")) {
240 path3 = "//" + auth + path2;
241 } else {
242 path3 = path2;
243 }
244 }
245
246 return new File(path3);
247 }
248
249 /**
250 * Performs a deep copy of the provided object.
251 *
252 * @param src Source object
253 * @return copy of source object
254 */
255 public static Object duplicate(Object src) {
256 // JD: this method really needs to be replaced with somethign better
257
258 if (src == null) {
259 return null;
260 }
261
262 //
263 // The following are things I expect
264 // Features will contain.
265 //
266 if (src instanceof String || src instanceof Integer || src instanceof Double
267 || src instanceof Float || src instanceof Byte || src instanceof Boolean
268 || src instanceof Short || src instanceof Long || src instanceof Character
269 || src instanceof Number) {
270 return src;
271 }
272
273 if (src instanceof Date) {
274 return new Date(((Date) src).getTime());
275 }
276
277 if (src instanceof URL || src instanceof URI) {
278 return src; // immutable
279 }
280
281 if (src instanceof Object[]) {
282 Object[] array = (Object[]) src;
283 Object[] copy = new Object[array.length];
284
285 for (int i = 0; i < array.length; i++) {
286 copy[i] = duplicate(array[i]);
287 }
288
289 return copy;
290 }
291
292 if (src instanceof Geometry) {
293 Geometry geometry = (Geometry) src;
294
295 return geometry.clone();
296 }
297
298 if (src instanceof SimpleFeature) {
299 SimpleFeature feature = (SimpleFeature) src;
300 return SimpleFeatureBuilder.copy(feature);
301 }
302
303 //
304 // We are now into diminishing returns
305 // I don't expect Features to contain these often
306 // (eveything is still nice and recursive)
307 //
308 Class<? extends Object> type = src.getClass();
309
310 if (type.isArray() && type.getComponentType().isPrimitive()) {
311 int length = Array.getLength(src);
312 Object copy = Array.newInstance(type.getComponentType(), length);
313 System.arraycopy(src, 0, copy, 0, length);
314
315 return copy;
316 }
317
318 if (type.isArray()) {
319 int length = Array.getLength(src);
320 Object copy = Array.newInstance(type.getComponentType(), length);
321
322 for (int i = 0; i < length; i++) {
323 Array.set(copy, i, duplicate(Array.get(src, i)));
324 }
325
326 return copy;
327 }
328
329 if (src instanceof List) {
330 List list = (List) src;
331 List<Object> copy = new ArrayList<Object>(list.size());
332
333 for (Iterator i = list.iterator(); i.hasNext();) {
334 copy.add(duplicate(i.next()));
335 }
336
337 return Collections.unmodifiableList(copy);
338 }
339
340 if (src instanceof Map) {
341 Map map = (Map) src;
342 Map copy = new HashMap(map.size());
343
344 for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
345 Map.Entry entry = (Map.Entry) i.next();
346 copy.put(entry.getKey(), duplicate(entry.getValue()));
347 }
348
349 return Collections.unmodifiableMap(copy);
350 }
351
352 //
353 // I have lost hope and am returning the orgional reference
354 // Please extend this to support additional classes.
355 //
356 // And good luck getting Cloneable to work
357 throw new IllegalAttributeException(null,"Do not know how to deep copy " + type.getName());
358 }
359
360 /**
361 * Returns a non-null default value for the class that is passed in. This is a helper class an
362 * can't create a default class for any type but it does support:
363 * <ul>
364 * <li>String</li>
365 * <li>Object - will return empty string</li>
366 * <li>Number</li>
367 * <li>Character</li>
368 * <li>JTS Geometries</li>
369 * </ul>
370 *
371 *
372 * @param type
373 * @return
374 */
375 public static Object defaultValue(Class type) {
376 if (type == String.class || type == Object.class) {
377 return "";
378 }
379 if (type == Integer.class) {
380 return new Integer(0);
381 }
382 if (type == Double.class) {
383 return new Double(0);
384 }
385 if (type == Long.class) {
386 return new Long(0);
387 }
388 if (type == Short.class) {
389 return new Short((short) 0);
390 }
391 if (type == Float.class) {
392 return new Float(0.0f);
393 }
394 if (type == BigDecimal.class) {
395 return BigDecimal.valueOf(0);
396 }
397 if (type == BigInteger.class) {
398 return BigInteger.valueOf(0);
399 }
400 if (type == Character.class) {
401 return new Character(' ');
402 }
403 if (type == Boolean.class) {
404 return Boolean.FALSE;
405 }
406 if (type == Timestamp.class)
407 return new Timestamp(System.currentTimeMillis());
408 if (type == java.sql.Date.class)
409 return new java.sql.Date(System.currentTimeMillis());
410 if (type == java.sql.Time.class)
411 return new java.sql.Time(System.currentTimeMillis());
412 if (type == java.util.Date.class)
413 return new java.util.Date();
414
415 GeometryFactory fac = new GeometryFactory();
416 Coordinate coordinate = new Coordinate(0, 0);
417 Point point = fac.createPoint(coordinate);
418
419 if (type == Point.class) {
420 return point;
421 }
422 if (type == MultiPoint.class) {
423 return fac.createMultiPoint(new Point[] { point });
424 }
425 if (type == LineString.class) {
426 return fac.createLineString(new Coordinate[] { coordinate, coordinate, coordinate,
427 coordinate });
428 }
429 LinearRing linearRing = fac.createLinearRing(new Coordinate[] { coordinate, coordinate,
430 coordinate, coordinate });
431 if (type == LinearRing.class) {
432 return linearRing;
433 }
434 if (type == MultiLineString.class) {
435 return fac.createMultiLineString(new LineString[] { linearRing });
436 }
437 Polygon polygon = fac.createPolygon(linearRing, new LinearRing[0]);
438 if (type == Polygon.class) {
439 return polygon;
440 }
441 if (type == MultiPolygon.class) {
442 return fac.createMultiPolygon(new Polygon[] { polygon });
443 }
444
445 throw new IllegalArgumentException(type + " is not supported by this method");
446 }
447
448 /**
449 * Copies the provided fetaures into a List.
450 *
451 * @param featureCollection
452 * @return List of features copied into memory
453 */
454 public static List<SimpleFeature> list(
455 FeatureCollection<SimpleFeatureType, SimpleFeature> featureCollection) {
456 final ArrayList<SimpleFeature> list = new ArrayList<SimpleFeature>();
457 try {
458 featureCollection.accepts(new FeatureVisitor() {
459 public void visit(Feature feature) {
460 list.add((SimpleFeature) feature);
461 }
462 }, null);
463 } catch (IOException ignore) {
464 }
465 return list;
466 }
467
468 /**
469 * Create a derived FeatureType
470 *
471 * <p>
472 * </p>
473 *
474 * @param featureType
475 * @param properties
476 * - if null, every property of the feature type in input will be used
477 * @param override
478 *
479 *
480 * @throws SchemaException
481 */
482 public static SimpleFeatureType createSubType(SimpleFeatureType featureType,
483 String[] properties, CoordinateReferenceSystem override) throws SchemaException {
484 URI namespaceURI = null;
485 if (featureType.getName().getNamespaceURI() != null) {
486 try {
487 namespaceURI = new URI(featureType.getName().getNamespaceURI());
488 } catch (URISyntaxException e) {
489 throw new RuntimeException(e);
490 }
491 }
492
493 return createSubType(featureType, properties, override, featureType.getTypeName(),
494 namespaceURI);
495
496 }
497
498 public static SimpleFeatureType createSubType(SimpleFeatureType featureType,
499 String[] properties, CoordinateReferenceSystem override, String typeName, URI namespace)
500 throws SchemaException {
501
502 if ((properties == null) && (override == null)) {
503 return featureType;
504 }
505
506 if (properties == null) {
507 properties = new String[featureType.getAttributeCount()];
508 for (int i = 0; i < properties.length; i++) {
509 properties[i] = featureType.getDescriptor(i).getLocalName();
510 }
511 }
512
513 String namespaceURI = namespace != null ? namespace.toString() : null;
514 boolean same = featureType.getAttributeCount() == properties.length
515 && featureType.getTypeName().equals(typeName)
516 && Utilities.equals(featureType.getName().getNamespaceURI(), namespaceURI);
517
518 for (int i = 0; (i < featureType.getAttributeCount()) && same; i++) {
519 AttributeDescriptor type = featureType.getDescriptor(i);
520 same = type.getLocalName().equals(properties[i])
521 && (((override != null) && type instanceof GeometryDescriptor) ? assertEquals(
522 override, ((GeometryDescriptor) type).getCoordinateReferenceSystem())
523 : true);
524 }
525
526 if (same) {
527 return featureType;
528 }
529
530 AttributeDescriptor[] types = new AttributeDescriptor[properties.length];
531
532 for (int i = 0; i < properties.length; i++) {
533 types[i] = featureType.getDescriptor(properties[i]);
534
535 if ((override != null) && types[i] instanceof GeometryDescriptor) {
536 AttributeTypeBuilder ab = new AttributeTypeBuilder();
537 ab.init(types[i]);
538 ab.setCRS(override);
539 types[i] = ab.buildDescriptor(types[i].getLocalName(), ab.buildGeometryType());
540 }
541 }
542
543 if (typeName == null)
544 typeName = featureType.getTypeName();
545 if (namespace == null && featureType.getName().getNamespaceURI() != null)
546 try {
547 namespace = new URI(featureType.getName().getNamespaceURI());
548 } catch (URISyntaxException e) {
549 throw new RuntimeException(e);
550 }
551
552 SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
553 tb.setName(typeName);
554 tb.setNamespaceURI(namespace);
555 tb.addAll(types);
556
557 return tb.buildFeatureType();
558 }
559
560 private static boolean assertEquals(Object o1, Object o2) {
561 return o1 == null && o2 == null ? true : (o1 != null ? o1.equals(o2) : false);
562 }
563
564 /**
565 * DOCUMENT ME!
566 *
567 * @param featureType
568 * DOCUMENT ME!
569 * @param properties
570 * DOCUMENT ME!
571 *
572 * @return DOCUMENT ME!
573 *
574 * @throws SchemaException
575 * DOCUMENT ME!
576 */
577 public static SimpleFeatureType createSubType(SimpleFeatureType featureType, String[] properties)
578 throws SchemaException {
579 if (properties == null) {
580 return featureType;
581 }
582
583 boolean same = featureType.getAttributeCount() == properties.length;
584
585 for (int i = 0; (i < featureType.getAttributeCount()) && same; i++) {
586 same = featureType.getDescriptor(i).getLocalName().equals(properties[i]);
587 }
588
589 if (same) {
590 return featureType;
591 }
592
593 SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
594 tb.setName(featureType.getName());
595
596 for (int i = 0; i < properties.length; i++) {
597 tb.add(featureType.getDescriptor(properties[i]));
598 }
599 return tb.buildFeatureType();
600 }
601
602 /**
603 * Factory method to produce Comparator based on provided Query SortBy information.
604 * <p>
605 * This method handles:
606 * <ul>
607 * <li>{@link SortBy#NATURAL_ORDER}: As sorting by FeatureID
608 * </ul>
609 *
610 * @param sortBy
611 * @return Comparator suitable for use with Arrays.sort( SimpleFeature[], comparator )
612 */
613 public static Comparator<SimpleFeature> sortComparator(SortBy sortBy) {
614 if (sortBy == null) {
615 sortBy = SortBy.NATURAL_ORDER;
616 }
617 if (sortBy == SortBy.NATURAL_ORDER) {
618 return new Comparator<SimpleFeature>() {
619 public int compare(SimpleFeature f1, SimpleFeature f2) {
620 return f1.getID().compareTo(f2.getID());
621 }
622 };
623 } else if (sortBy == SortBy.REVERSE_ORDER) {
624 return new Comparator<SimpleFeature>() {
625 public int compare(SimpleFeature f1, SimpleFeature f2) {
626 int compare = f1.getID().compareTo(f2.getID());
627 return -compare;
628 }
629 };
630 } else {
631 final PropertyName PROPERTY = sortBy.getPropertyName();
632 return new Comparator<SimpleFeature>() {
633 @SuppressWarnings("unchecked")
634 public int compare(SimpleFeature f1, SimpleFeature f2) {
635 Object value1 = PROPERTY.evaluate(f1, Comparable.class);
636 Object value2 = PROPERTY.evaluate(f2, Comparable.class);
637 if (value1 == null || value2 == null) {
638 return 0; // cannot perform comparison
639 }
640 if (value1 instanceof Comparable && value1.getClass().isInstance(value2)) {
641 return ((Comparable<Object>) value1).compareTo(value2);
642 } else {
643 return 0; // cannot perform comparison
644 }
645 }
646 };
647 }
648 }
649
650 /**
651 * Takes two {@link Query}objects and produce a new one by mixing the restrictions of both of
652 * them.
653 *
654 * <p>
655 * The policy to mix the queries components is the following:
656 *
657 * <ul>
658 * <li>
659 * typeName: type names MUST match (not checked if some or both queries equals to
660 * <code>Query.ALL</code>)</li>
661 * <li>
662 * handle: you must provide one since no sensible choice can be done between the handles of both
663 * queries</li>
664 * <li>
665 * maxFeatures: the lower of the two maxFeatures values will be used (most restrictive)</li>
666 * <li>
667 * attributeNames: the attributes of both queries will be joined in a single set of attributes.
668 * IMPORTANT: only <b><i>explicitly</i></b> requested attributes will be joint, so, if the
669 * method <code>retrieveAllProperties()</code> of some of the queries returns <code>true</code>
670 * it does not means that all the properties will be joined. You must create the query with the
671 * names of the properties you want to load.</li>
672 * <li>
673 * filter: the filtets of both queries are or'ed</li>
674 * <li>
675 * <b>any other query property is ignored</b> and no guarantees are made of their return values,
676 * so client code shall explicitly care of hints, startIndex, etc., if needed.</li>
677 * </ul>
678 * </p>
679 *
680 * @param firstQuery
681 * Query against this DataStore
682 * @param secondQuery
683 * DOCUMENT ME!
684 * @param handle
685 * DOCUMENT ME!
686 *
687 * @return Query restricted to the limits of definitionQuery
688 *
689 * @throws NullPointerException
690 * if some of the queries is null
691 * @throws IllegalArgumentException
692 * if the type names of both queries do not match
693 */
694 public static Query mixQueries(Query firstQuery, Query secondQuery, String handle) {
695 if ((firstQuery == null) && (secondQuery == null)) {
696 // throw new NullPointerException("Cannot combine two null queries");
697 return Query.ALL;
698 }
699 if (firstQuery == null || firstQuery.equals(Query.ALL)) {
700 return secondQuery;
701 } else if (secondQuery == null || secondQuery.equals(Query.ALL)) {
702 return firstQuery;
703 }
704 if ((firstQuery.getTypeName() != null) && (secondQuery.getTypeName() != null)) {
705 if (!firstQuery.getTypeName().equals(secondQuery.getTypeName())) {
706 String msg = "Type names do not match: " + firstQuery.getTypeName() + " != "
707 + secondQuery.getTypeName();
708 throw new IllegalArgumentException(msg);
709 }
710 }
711
712 // mix versions, if possible
713 String version;
714 if (firstQuery.getVersion() != null) {
715 if (secondQuery.getVersion() != null
716 && !secondQuery.getVersion().equals(firstQuery.getVersion()))
717 throw new IllegalArgumentException(
718 "First and second query refer different versions");
719 version = firstQuery.getVersion();
720 } else {
721 version = secondQuery.getVersion();
722 }
723
724 // none of the queries equals Query.ALL, mix them
725 // use the more restrictive max features field
726 int maxFeatures = Math.min(firstQuery.getMaxFeatures(), secondQuery.getMaxFeatures());
727
728 // join attributes names
729 String[] propNames = joinAttributes(firstQuery.getPropertyNames(), secondQuery
730 .getPropertyNames());
731
732 // join filters
733 Filter filter = firstQuery.getFilter();
734 Filter filter2 = secondQuery.getFilter();
735
736 if ((filter == null) || filter.equals(Filter.INCLUDE)) {
737 filter = filter2;
738 } else if ((filter2 != null) && !filter2.equals(Filter.INCLUDE)) {
739 filter = ff.and(filter, filter2);
740 }
741 Integer start = 0;
742 if (firstQuery.getStartIndex() != null) {
743 start = firstQuery.getStartIndex();
744 }
745 if (secondQuery.getStartIndex() != null) {
746 start += secondQuery.getStartIndex();
747 }
748 // collect all hints
749 Hints hints = new Hints();
750 if(firstQuery.getHints() != null) {
751 hints.putAll(firstQuery.getHints());
752 }
753 if(secondQuery.getHints() != null) {
754 hints.putAll(secondQuery.getHints());
755 }
756 // build the mixed query
757 String typeName = firstQuery.getTypeName() != null ? firstQuery.getTypeName() : secondQuery
758 .getTypeName();
759
760 Query mixed = new Query(typeName, filter, maxFeatures, propNames, handle);
761 mixed.setVersion(version);
762 mixed.setHints(hints);
763 if (start != 0) {
764 mixed.setStartIndex(start);
765 }
766 return mixed;
767 }
768
769 /**
770 * This method changes the query object so that all propertyName references are resolved to
771 * simple attribute names against the schema of the feature source.
772 * <p>
773 * For example, this method ensures that propertyName's such as "gml:name" are rewritten as
774 * simply "name".
775 *</p>
776 */
777 public static Query resolvePropertyNames(Query query, SimpleFeatureType schema) {
778 Filter resolved = resolvePropertyNames(query.getFilter(), schema);
779 if (resolved == query.getFilter()) {
780 return query;
781 }
782 Query newQuery = new Query(query);
783 newQuery.setFilter(resolved);
784 return newQuery;
785 }
786
787 /** Transform provided filter; resolving property names */
788 public static Filter resolvePropertyNames(Filter filter, SimpleFeatureType schema) {
789 if (filter == null || filter == Filter.INCLUDE || filter == Filter.EXCLUDE) {
790 return filter;
791 }
792 return (Filter) filter.accept(new PropertyNameResolvingVisitor(schema), null);
793 }
794
795 /**
796 * Creates a set of attribute names from the two input lists of names, maintaining the order of
797 * the first list and appending the non repeated names of the second.
798 * <p>
799 * In the case where both lists are <code>null</code>, <code>null</code> is returned.
800 * </p>
801 *
802 * @param atts1
803 * the first list of attribute names, who's order will be maintained
804 * @param atts2
805 * the second list of attribute names, from wich the non repeated names will be
806 * appended to the resulting list
807 *
808 * @return Set of attribute names from <code>atts1</code> and <code>atts2</code>
809 */
810 private static String[] joinAttributes(String[] atts1, String[] atts2) {
811 String[] propNames = null;
812
813 if (atts1 == null && atts2 == null) {
814 return null;
815 }
816
817 List<String> atts = new LinkedList<String>();
818
819 if (atts1 != null) {
820 atts.addAll(Arrays.asList(atts1));
821 }
822
823 if (atts2 != null) {
824 for (int i = 0; i < atts2.length; i++) {
825 if (!atts.contains(atts2[i])) {
826 atts.add(atts2[i]);
827 }
828 }
829 }
830
831 propNames = new String[atts.size()];
832 atts.toArray(propNames);
833
834 return propNames;
835 }
836
837 /**
838 * Manually calculates the bounds of a feature collection.
839 *
840 * @param collection
841 * @return
842 */
843 public static Envelope bounds(
844 FeatureCollection<? extends FeatureType, ? extends Feature> collection) {
845 FeatureIterator<? extends Feature> i = collection.features();
846 try {
847 ReferencedEnvelope bounds = new ReferencedEnvelope(collection.getSchema()
848 .getCoordinateReferenceSystem());
849 if (!i.hasNext()) {
850 bounds.setToNull();
851 return bounds;
852 }
853
854 bounds.init(((SimpleFeature) i.next()).getBounds());
855 return bounds;
856 } finally {
857 i.close();
858 }
859 }
860}
Note: See TracBrowser for help on using the repository browser.