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; either
009: * version 2.1 of the License, or (at your option) any later version.
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.data.ows;
017:
018: import java.util.ArrayList;
019: import java.util.Arrays;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Set;
028: import java.util.WeakHashMap;
029:
030: import org.geotools.geometry.GeneralEnvelope;
031: import org.geotools.geometry.jts.ReferencedEnvelope;
032: import org.geotools.referencing.CRS;
033: import org.geotools.referencing.crs.DefaultGeographicCRS;
034: import org.opengis.referencing.FactoryException;
035: import org.opengis.referencing.NoSuchAuthorityCodeException;
036: import org.opengis.referencing.crs.CoordinateReferenceSystem;
037: import org.opengis.referencing.operation.MathTransform;
038: import org.opengis.referencing.operation.TransformException;
039: import org.opengis.geometry.DirectPosition;
040: import org.opengis.geometry.MismatchedDimensionException;
041:
042: /**
043: * Nested list of zero or more map Layers offered by this server. It contains
044: * only fields for information that we currently find interesting. Feel free
045: * to add your own.
046: *
047: * @author rgould
048: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/plugin/wms/src/main/java/org/geotools/data/ows/Layer.java $
049: */
050: public class Layer implements Comparable {
051: /** A machine-readable (typically one word) identifier */
052: private String name;
053:
054: /** The title is for informative display to a human. */
055: private String title;
056:
057: private String _abstract;
058: private String[] keywords;
059:
060: /** A set of Strings representing SRSs */
061: private Set srs = null;
062: /** the union of the layers's SRSs and the parent's SRSs */
063: private Set allSRSCache = null;
064: /**
065: * A HashMap representings the bounding boxes on each layer. The Key is the
066: * CRS (or SRS) of the bounding box. The Value is the BoundingBox object
067: * itself.
068: */
069: private HashMap boundingBoxes = null;
070:
071: /**
072: * A boundingbox containing the minimum rectangle of the map data in
073: * EPSG:4326
074: */
075: private CRSEnvelope latLonBoundingBox = null;
076:
077: /** A list of type org.opengis.layer.Style */
078: private List styles;
079: private Boolean queryable = null;
080:
081: private double scaleHintMin = Double.NaN;
082: private double scaleHintMax = Double.NaN;
083:
084: private Layer parent;
085: private Layer[] children;
086:
087: private Map envelopeCache = Collections
088: .synchronizedMap(new WeakHashMap());
089:
090: public Layer() {
091:
092: }
093:
094: /**
095: * DOCUMENT ME!
096: *
097: * @param title
098: */
099: public Layer(String title) {
100: this .title = title;
101: }
102:
103: /**
104: * Returns every BoundingBox associated with this layer. The
105: * <code>HashMap</code> returned has each bounding box's CRS/SRS value as
106: * the key, and the value is the <code>BoundingBox</code> object itself.
107: *
108: * Implements inheritance: if this layer's bounding box is null, query ancestors until
109: * the first bounding box is found or no more ancestors
110: *
111: * @return a HashMap of all of this layer's bounding boxes or null if no
112: * bounding boxes found
113: */
114: public HashMap getBoundingBoxes() {
115: if (boundingBoxes == null) {
116: Layer parent = this .getParent();
117: while (parent != null) {
118: HashMap bb = parent.getBoundingBoxes();
119: if (bb != null)
120: return bb;
121: else
122: parent = parent.getParent();
123: }
124: }
125: // May return null. But that is OK since spec says 0 or more may be specified
126: return boundingBoxes;
127: }
128:
129: /**
130: * Sets this layer's bounding boxes. The HashMap must have each
131: * BoundingBox's CRS/SRS value as its key, and the
132: * <code>BoundingBox</code> object as its value.
133: *
134: * @param boundingBoxes a HashMap containing bounding boxes
135: */
136: public void setBoundingBoxes(HashMap boundingBoxes) {
137: this .boundingBoxes = boundingBoxes;
138: }
139:
140: /**
141: * Gets the name of the <code>Layer</code>. It is designed to be machine
142: * readable, and if it is present, this layer is determined to be drawable
143: * and is a valid candidate for use in a GetMap or GetFeatureInfo request.
144: *
145: * @return the machine-readable name of the layer
146: */
147: public String getName() {
148: return name;
149: }
150:
151: /**
152: * Sets the name of this layer. Giving the layer name indicates that it
153: * can be drawn during a GetMap or GetFeatureInfo request.
154: *
155: * @param name the layer's new name
156: */
157: public void setName(String name) {
158: this .name = name;
159: }
160:
161: /**
162: * Accumulates all of the srs/crs specified for this layer and all srs/crs inherited from
163: * its ancestors. No duplicates are returned.
164: *
165: * @return Set of all srs/crs for this layer and its ancestors
166: */
167: public Set getSrs() {
168: synchronized (this ) {
169: if (allSRSCache == null) {
170: allSRSCache = new HashSet(srs);
171: // Get my ancestor's srs/crs
172: Layer parent = this .getParent();
173: if (parent != null) {
174: Set parentSrs = parent.getSrs();
175: if (parentSrs != null) //got something, add to accumulation
176: allSRSCache.addAll(parentSrs);
177: }
178: }
179: // May return an empty list, but spec says at least one must be specified. Perhaps, need
180: // to check and throw exception if set is empty. I'm leaving that out for now since
181: // it changes the method signature and would potentially break existing users of this class
182: return allSRSCache;
183: }
184:
185: }
186:
187: public void setSrs(Set srs) {
188: this .srs = srs;
189: }
190:
191: /**
192: * Accumulates all of the styles specified for this layer and all styles inherited from
193: * its ancestors. No duplicates are returned.
194: *
195: * The List that is returned is of type List<org.opengis.layer.Style>. Before 2.2-RC0
196: * it was of type List<java.lang.String>.
197: *
198: * @return List of all styles for this layer and its ancestors
199: */
200: public List getStyles() {
201: ArrayList allStyles = new ArrayList();
202: // Get my ancestor's styles
203: Layer parent = this .getParent();
204: if (parent != null) {
205: List parentStyles = parent.getStyles();
206: if (parentStyles != null) //got something, add to accumulation
207: allStyles.addAll(parentStyles);
208: }
209: // Now add my styles, if any
210: // Brute force check for duplicates. The spec says duplicates are not allowed:
211: // (para 7.1.4.5.4) "A child shall not redefine a Style with the same Name as one
212: // inherited from a parent. A child may define a new Style with a new Name that is
213: // not available for the parent Layer."
214: if ((styles != null) && !styles.isEmpty()) {
215: for (Iterator iter = styles.iterator(); iter.hasNext();) {
216: Object style = iter.next();
217: if (!allStyles.contains(style))
218: allStyles.add(style);
219: }
220: }
221:
222: // May return an empty list, but that is OK since spec says 0 or more styles may be specified
223: return allStyles;
224: }
225:
226: public void setStyles(List styles) {
227: this .styles = styles;
228: }
229:
230: public String getTitle() {
231: return title;
232: }
233:
234: public void setTitle(String title) {
235: this .title = title;
236: }
237:
238: /**
239: * Determines if this layer is queryable. Implements inheritance: if this layer's
240: * Queryable attribute is null, check ancestors until the first Queryable attribute is found
241: * or no more ancestors. If a Queryable attribute is not found for this layer, it will return
242: * the default value of false.
243: *
244: * @return true is this layer is Queryable
245: */
246: public boolean isQueryable() {
247: if (queryable == null) {
248: Layer parent = this .getParent();
249: while (parent != null) {
250: Boolean q = parent.getQueryable();
251: if (q != null)
252: return q.booleanValue();
253: else
254: parent = parent.getParent();
255: }
256: // At this point a attribute was not found so return default
257: return false;
258: }
259: return queryable.booleanValue();
260: }
261:
262: private Boolean getQueryable() {
263: return queryable;
264: }
265:
266: public void setQueryable(boolean queryable) {
267: this .queryable = new Boolean(queryable);
268: }
269:
270: /* (non-Javadoc)
271: * @see java.lang.Comparable#compareTo(java.lang.Object)
272: */
273: public int compareTo(Object arg0) {
274: Layer layer = (Layer) arg0;
275:
276: if ((this .getName() != null) && (layer.getName() != null)) {
277: return this .getName().compareTo(layer.getName());
278: }
279:
280: return this .getTitle().compareTo(layer.getTitle());
281: }
282:
283: /**
284: * DOCUMENT ME!
285: *
286: * @return Returns the parent.
287: */
288: public Layer getParent() {
289: return parent;
290: }
291:
292: /**
293: * DOCUMENT ME!
294: *
295: * @param parent The parent to set.
296: */
297: public void setParent(Layer parent) {
298: this .parent = parent;
299: }
300:
301: /**
302: * Returns the LatLonBoundingBox for this layer. Implements inheritance: if this layer's
303: * bounding box is null, query ancestors until the first bounding box is found
304: * or no more ancestors.
305: *
306: * @return the LatLonBoundingBox for this layer or null if no lat/lon bounding box is found
307: */
308: public CRSEnvelope getLatLonBoundingBox() {
309: if (latLonBoundingBox == null) {
310: Layer parent = this .getParent();
311: while (parent != null) {
312: CRSEnvelope llbb = parent.getLatLonBoundingBox();
313: if (llbb != null)
314: return llbb;
315: else
316: parent = parent.getParent();
317: }
318: // We should never get to falling out of the while loop w/o a LatLonBoundingBox
319: // being found. The WMS spec says one is required. So perhaps if we don't find one,
320: // then throw an exception. I'm leaving that out for now since it changes the method signature
321: // and would potentially break existing users of this class
322: }
323: // May return null!
324: return latLonBoundingBox;
325: }
326:
327: public void setLatLonBoundingBox(CRSEnvelope latLonBoundingBox) {
328: this .latLonBoundingBox = latLonBoundingBox;
329: }
330:
331: public Layer[] getChildren() {
332: return children;
333: }
334:
335: public void setChildren(Layer[] children) {
336: this .children = children;
337: }
338:
339: /**
340: * The abstract contains human-readable information about this layer
341: * @return Returns the _abstract.
342: */
343: public String get_abstract() {
344: return _abstract;
345: }
346:
347: /**
348: * @param _abstract The _abstract to set.
349: */
350: public void set_abstract(String _abstract) {
351: this ._abstract = _abstract;
352: }
353:
354: /**
355: * Keywords are Strings to be used in searches
356: *
357: * @return Returns the keywords.
358: */
359: public String[] getKeywords() {
360: return keywords;
361: }
362:
363: /**
364: * @param keywords The keywords to set.
365: */
366: public void setKeywords(String[] keywords) {
367: this .keywords = keywords;
368: }
369:
370: public double getScaleHintMax() {
371: return scaleHintMax;
372: }
373:
374: public void setScaleHintMax(double scaleHintMax) {
375: this .scaleHintMax = scaleHintMax;
376: }
377:
378: public double getScaleHintMin() {
379: return scaleHintMin;
380: }
381:
382: public void setScaleHintMin(double scaleHintMin) {
383: this .scaleHintMin = scaleHintMin;
384: }
385:
386: public GeneralEnvelope getEnvelope(CoordinateReferenceSystem crs) {
387: {
388: GeneralEnvelope result = (GeneralEnvelope) envelopeCache
389: .get(crs);
390: if (result != null)
391: return result;
392: }
393: Collection identifiers = crs.getIdentifiers();
394: if (crs == DefaultGeographicCRS.WGS84
395: || crs == DefaultGeographicCRS.WGS84_3D) {
396: identifiers = Arrays.asList(new String[] { "EPSG:4326" }); //$NON-NLS-1$
397: }
398: for (final Iterator i = identifiers.iterator(); i.hasNext();) {
399: String epsgCode = i.next().toString();
400:
401: CRSEnvelope tempBBox = null;
402: Layer parentLayer = this ;
403:
404: //Locate a BBOx if we can
405: while (tempBBox == null && parentLayer != null) {
406: tempBBox = (CRSEnvelope) parentLayer.getBoundingBoxes()
407: .get(epsgCode);
408:
409: parentLayer = parentLayer.getParent();
410: }
411:
412: //Otherwise, locate a LatLon BBOX
413:
414: if (tempBBox == null
415: && ("EPSG:4326".equals(epsgCode.toUpperCase()))) { //$NON-NLS-1$
416: CRSEnvelope latLonBBox = null;
417:
418: parentLayer = this ;
419: while (latLonBBox == null && parentLayer != null) {
420: latLonBBox = parentLayer.getLatLonBoundingBox();
421: if (latLonBBox != null) {
422: try {
423: new GeneralEnvelope(new double[] {
424: latLonBBox.getMinX(),
425: latLonBBox.getMinY() },
426: new double[] {
427: latLonBBox.getMaxX(),
428: latLonBBox.getMaxY() });
429: break;
430: } catch (IllegalArgumentException e) {
431: //TODO LOG here
432: //log("Layer "+layer.getName()+" has invalid bbox declared: "+tempBbox.toString());
433: latLonBBox = null;
434: }
435: }
436: parentLayer = parentLayer.getParent();
437: }
438:
439: if (latLonBBox == null) {
440: //TODO could convert another bbox to latlon?
441: tempBBox = new CRSEnvelope("EPSG:4326", -180, -90,
442: 180, 90);
443: }
444:
445: tempBBox = new CRSEnvelope("EPSG:4326", latLonBBox
446: .getMinX(), latLonBBox.getMinY(), latLonBBox
447: .getMaxX(), latLonBBox.getMaxY());
448: }
449:
450: if (tempBBox == null) {
451: //Haven't found a bbox in the requested CRS. Attempt to transform another bbox
452:
453: String epsg = null;
454: if (getLatLonBoundingBox() != null) {
455: CRSEnvelope latLonBBox = getLatLonBoundingBox();
456: tempBBox = new CRSEnvelope("EPSG:4326", latLonBBox
457: .getMinX(), latLonBBox.getMinY(),
458: latLonBBox.getMaxX(), latLonBBox.getMaxY());
459: epsg = "EPSG:4326";
460: }
461:
462: if (tempBBox == null && getBoundingBoxes() != null
463: && getBoundingBoxes().size() > 0) {
464: tempBBox = (CRSEnvelope) getBoundingBoxes()
465: .values().iterator().next();
466: epsg = tempBBox.getEPSGCode();
467: }
468:
469: if (tempBBox == null) {
470: continue;
471: }
472:
473: GeneralEnvelope env = new GeneralEnvelope(new double[] {
474: tempBBox.getMinX(), tempBBox.getMinY() },
475: new double[] { tempBBox.getMaxX(),
476: tempBBox.getMaxY() });
477:
478: CoordinateReferenceSystem fromCRS = null;
479: try {
480: fromCRS = CRS.decode(epsg);
481:
482: ReferencedEnvelope oldEnv = new ReferencedEnvelope(
483: env.getMinimum(0), env.getMaximum(0), env
484: .getMinimum(1), env.getMaximum(1),
485: fromCRS);
486: ReferencedEnvelope newEnv = oldEnv.transform(crs,
487: true);
488:
489: env = new GeneralEnvelope(
490: new double[] { newEnv.getMinimum(0),
491: newEnv.getMinimum(1) },
492: new double[] { newEnv.getMaximum(0),
493: newEnv.getMaximum(1) });
494: env.setCoordinateReferenceSystem(crs);
495:
496: //success!!
497: envelopeCache.put(crs, env);
498: return env;
499:
500: } catch (NoSuchAuthorityCodeException e) {
501: // TODO Catch e
502: } catch (FactoryException e) {
503: // TODO Catch e
504: } catch (MismatchedDimensionException e) {
505: // TODO Catch e
506: } catch (TransformException e) {
507: // TODO Catch e
508: }
509: }
510:
511: //TODO Attempt to figure out the valid area of the CRS and use that.
512:
513: if (tempBBox != null) {
514: GeneralEnvelope env = new GeneralEnvelope(new double[] {
515: tempBBox.getMinX(), tempBBox.getMinY() },
516: new double[] { tempBBox.getMaxX(),
517: tempBBox.getMaxY() });
518: env.setCoordinateReferenceSystem(crs);
519: return env;
520: }
521:
522: }
523: return null;
524: }
525: }
|