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 | */
|
---|
17 | package org.geotools.data;
|
---|
18 |
|
---|
19 | import java.io.File;
|
---|
20 | import java.io.IOException;
|
---|
21 | import java.io.UnsupportedEncodingException;
|
---|
22 | import java.lang.reflect.Array;
|
---|
23 | import java.math.BigDecimal;
|
---|
24 | import java.math.BigInteger;
|
---|
25 | import java.net.MalformedURLException;
|
---|
26 | import java.net.URI;
|
---|
27 | import java.net.URISyntaxException;
|
---|
28 | import java.net.URL;
|
---|
29 | import java.net.URLDecoder;
|
---|
30 | import java.sql.Timestamp;
|
---|
31 | import java.util.ArrayList;
|
---|
32 | import java.util.Arrays;
|
---|
33 | import java.util.Collections;
|
---|
34 | import java.util.Comparator;
|
---|
35 | import java.util.Date;
|
---|
36 | import java.util.HashMap;
|
---|
37 | import java.util.Iterator;
|
---|
38 | import java.util.LinkedList;
|
---|
39 | import java.util.List;
|
---|
40 | import java.util.Map;
|
---|
41 |
|
---|
42 | import org.geotools.factory.CommonFactoryFinder;
|
---|
43 | import org.geotools.factory.Hints;
|
---|
44 | import org.geotools.feature.AttributeTypeBuilder;
|
---|
45 | import org.geotools.feature.FeatureCollection;
|
---|
46 | import org.geotools.feature.FeatureIterator;
|
---|
47 | import org.geotools.feature.SchemaException;
|
---|
48 | import org.geotools.feature.simple.SimpleFeatureBuilder;
|
---|
49 | import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
|
---|
50 | import org.geotools.filter.visitor.PropertyNameResolvingVisitor;
|
---|
51 | import org.geotools.geometry.jts.ReferencedEnvelope;
|
---|
52 | import org.geotools.util.Utilities;
|
---|
53 | import org.opengis.feature.Feature;
|
---|
54 | import org.opengis.feature.FeatureVisitor;
|
---|
55 | import org.opengis.feature.IllegalAttributeException;
|
---|
56 | import org.opengis.feature.simple.SimpleFeature;
|
---|
57 | import org.opengis.feature.simple.SimpleFeatureType;
|
---|
58 | import org.opengis.feature.type.AttributeDescriptor;
|
---|
59 | import org.opengis.feature.type.FeatureType;
|
---|
60 | import org.opengis.feature.type.GeometryDescriptor;
|
---|
61 | import org.opengis.filter.Filter;
|
---|
62 | import org.opengis.filter.FilterFactory;
|
---|
63 | import org.opengis.filter.expression.PropertyName;
|
---|
64 | import org.opengis.filter.sort.SortBy;
|
---|
65 | import org.opengis.referencing.crs.CoordinateReferenceSystem;
|
---|
66 |
|
---|
67 | import com.vividsolutions.jts.geom.Coordinate;
|
---|
68 | import com.vividsolutions.jts.geom.Envelope;
|
---|
69 | import com.vividsolutions.jts.geom.Geometry;
|
---|
70 | import com.vividsolutions.jts.geom.GeometryCollection;
|
---|
71 | import com.vividsolutions.jts.geom.GeometryFactory;
|
---|
72 | import com.vividsolutions.jts.geom.LineString;
|
---|
73 | import com.vividsolutions.jts.geom.LinearRing;
|
---|
74 | import com.vividsolutions.jts.geom.MultiLineString;
|
---|
75 | import com.vividsolutions.jts.geom.MultiPoint;
|
---|
76 | import com.vividsolutions.jts.geom.MultiPolygon;
|
---|
77 | import com.vividsolutions.jts.geom.Point;
|
---|
78 | import 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 | */
|
---|
92 | public 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 | }
|
---|