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.referencing.factory;
017:
018: // J2SE dependencies
019: import java.io.InputStream;
020: import java.io.IOException;
021: import java.net.URL;
022: import java.text.ParseException;
023: import java.util.Collections;
024: import java.util.Iterator;
025: import java.util.Map;
026: import java.util.HashMap;
027: import java.util.Properties;
028: import java.util.Set;
029:
030: // OpenGIS dependencies
031: import org.opengis.metadata.citation.Citation;
032: import org.opengis.referencing.FactoryException;
033: import org.opengis.referencing.IdentifiedObject;
034: import org.opengis.referencing.NoSuchAuthorityCodeException;
035: import org.opengis.referencing.cs.CSAuthorityFactory;
036: import org.opengis.referencing.crs.CRSAuthorityFactory;
037: import org.opengis.referencing.crs.CoordinateReferenceSystem;
038: import org.opengis.referencing.datum.DatumAuthorityFactory;
039: import org.opengis.util.InternationalString;
040: import org.opengis.util.GenericName;
041:
042: // Geotools dependencies
043: import org.geotools.factory.Hints;
044: import org.geotools.referencing.wkt.Symbols;
045: import org.geotools.referencing.NamedIdentifier;
046: import org.geotools.metadata.iso.citation.Citations;
047: import org.geotools.util.DerivedSet;
048: import org.geotools.util.NameFactory;
049: import org.geotools.util.SimpleInternationalString;
050: import org.geotools.resources.i18n.Errors;
051: import org.geotools.resources.i18n.ErrorKeys;
052:
053: /**
054: * Default implementation for a coordinate reference system authority factory
055: * backed by a property file. This gives some of the benificts of using the
056: * {@linkplain org.geotools.referencing.factory.epsg.DirectEpsgFactory EPSG database backed
057: * authority factory} (for example), in a portable property file.
058: * <p>
059: * This factory doesn't cache any result. Any call to a {@code createFoo} method will trig a new
060: * WKT parsing. For caching, this factory should be wrapped in some buffered factory like
061: * {@link BufferedAuthorityFactory}.
062: *
063: * @since 2.1
064: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/PropertyAuthorityFactory.java $
065: * @version $Id: PropertyAuthorityFactory.java 29104 2008-02-06 16:00:14Z aaime $
066: * @author Jody Garnett
067: * @author Rueben Schulz
068: * @author Martin Desruisseaux
069: */
070: public class PropertyAuthorityFactory extends DirectAuthorityFactory
071: implements CRSAuthorityFactory, CSAuthorityFactory,
072: DatumAuthorityFactory {
073: /**
074: * The authority for this factory.
075: */
076: private final Citation authority;
077:
078: /**
079: * Same as {@link #authority}, but may contains more than one elements in some particular
080: * cases.
081: */
082: private final Citation[] authorities;
083:
084: /**
085: * The properties object for our properties file. Keys are the authority
086: * code for a coordinate reference system and the associated value is a
087: * WKT string for the CRS.
088: * <p>
089: * It is technically possible to add or remove elements after they have been
090: * loaded by the constructor. However if such modification are made, then we
091: * should update {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER} accordingly.
092: * It may be an issue since hints are supposed to be immutable after factory
093: * construction. For now, this class to not allow addition of elements.
094: */
095: private final Properties definitions = new Properties();
096:
097: /**
098: * An unmodifiable view of the authority keys. This view is always up to date
099: * even if entries are added or removed in the {@linkplain #definitions} map.
100: */
101: private final Set codes = Collections.unmodifiableSet(definitions
102: .keySet());
103:
104: /**
105: * Views of {@link #codes} for different types. Views will be constructed only when first
106: * needed. View are always up to date even if entries are added or removed in the
107: * {@linkplain #definitions} map.
108: */
109: private transient Map filteredCodes;
110:
111: /**
112: * A WKT parser.
113: */
114: private transient Parser parser;
115:
116: /**
117: * Creates a factory for the specified authority from the specified file.
118: *
119: * @param factories The underlying factories used for objects creation.
120: * @param authority The organization or party responsible for definition and maintenance of
121: * the database.
122: * @param definitions URL to the definition file.
123: * @throws IOException if the definitions can't be read.
124: */
125: public PropertyAuthorityFactory(
126: final ReferencingFactoryContainer factories,
127: final Citation authority, final URL definitions)
128: throws IOException {
129: this (factories, new Citation[] { authority }, definitions);
130: }
131:
132: /**
133: * Creates a factory for the specified authorities from the specified file. More than
134: * one authority may be specified when the CRS to create should have more than one
135: * {@linkplain CoordinateReferenceSystem#getIdentifiers identifier}, each with the same
136: * code but different namespace. For example a
137: * {@linkplain org.geotools.referencing.factory.epsg.EsriExtension factory for CRS defined
138: * by ESRI} uses the {@code "ESRI"} namespace, but also the {@code "EPSG"} namespace
139: * because those CRS are used as extension of the EPSG database. Concequently, the same
140: * CRS can be identified as {@code "ESRI:53001"} and {@code "EPSG:53001"}, where
141: * {@code "53001"} is a unused code in the official EPSG database.
142: *
143: * @param factories The underlying factories used for objects creation.
144: * @param authorities The organizations or party responsible for definition
145: * and maintenance of the database.
146: * @param definitions URL to the definition file.
147: * @throws IOException if the definitions can't be read.
148: *
149: * @since 2.4
150: */
151: public PropertyAuthorityFactory(
152: final ReferencingFactoryContainer factories,
153: final Citation[] authorities, final URL definitions)
154: throws IOException {
155: super (factories, MINIMUM_PRIORITY + 10);
156: // The following hints have no effect on this class behaviour,
157: // but tell to the user what this factory do about axis order.
158: hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS, Boolean.FALSE);
159: hints.put(Hints.FORCE_STANDARD_AXIS_UNITS, Boolean.FALSE);
160: ensureNonNull("authorities", authorities);
161: if (authorities.length == 0) {
162: throw new IllegalArgumentException(Errors
163: .format(ErrorKeys.EMPTY_ARRAY));
164: }
165: this .authorities = (Citation[]) authorities.clone();
166: authority = authorities[0];
167: ensureNonNull("authority", authority);
168: final InputStream in = definitions.openStream();
169: this .definitions.load(in);
170: in.close();
171: /*
172: * If the WKT do not contains any AXIS[...] element, then every CRS will be created with
173: * the default (longitude,latitude) axis order. In such case this factory is insensitive
174: * to the FORCE_LONGITUDE_FIRST_AXIS_ORDER hint (i.e. every CRS to be created by this
175: * instance are invariant under the above-cited hint value) and we can remove it from
176: * the hint map. Removing this hint allow the CRS.decode(..., true) convenience method
177: * to find this factory (GEOT-1175).
178: */
179: final Symbols s = Symbols.DEFAULT;
180: for (final Iterator it = this .definitions.values().iterator(); it
181: .hasNext();) {
182: final String wkt = (String) it.next();
183: if (s.containsAxis(wkt)) {
184: LOGGER
185: .warning("Axis elements found in a wkt definition, the force longitude "
186: + "first axis order hint might not be respected:\n"
187: + wkt);
188: }
189: }
190: }
191:
192: /**
193: * Returns the organization or party responsible for definition and maintenance of the
194: * database.
195: */
196: public Citation getAuthority() {
197: return authority;
198: }
199:
200: /**
201: * Returns the set of authority codes of the given type. The type
202: * argument specify the base class. For example if this factory is
203: * an instance of CRSAuthorityFactory, then:
204: * <p>
205: * <ul>
206: * <li>{@code CoordinateReferenceSystem.class} asks for all authority codes accepted by
207: * {@link #createGeographicCRS createGeographicCRS},
208: * {@link #createProjectedCRS createProjectedCRS},
209: * {@link #createVerticalCRS createVerticalCRS},
210: * {@link #createTemporalCRS createTemporalCRS}
211: * and their friends.</li>
212: * <li>{@code ProjectedCRS.class} asks only for authority codes accepted by
213: * {@link #createProjectedCRS createProjectedCRS}.</li>
214: * </ul>
215: *
216: * The default implementaiton filters the set of codes based on the
217: * {@code "PROJCS"} and {@code "GEOGCS"} at the start of the WKT strings.
218: *
219: * @param type The spatial reference objects type (may be {@code Object.class}).
220: * @return The set of authority codes for spatial reference objects of the given type.
221: * If this factory doesn't contains any object of the given type, then this method
222: * returns an empty set.
223: * @throws FactoryException if access to the underlying database failed.
224: */
225: public Set getAuthorityCodes(final Class type)
226: throws FactoryException {
227: if (type == null
228: || type.isAssignableFrom(IdentifiedObject.class)) {
229: return codes;
230: }
231: if (filteredCodes == null) {
232: filteredCodes = new HashMap();
233: }
234: synchronized (filteredCodes) {
235: Set filtered = (Set) filteredCodes.get(type);
236: if (filtered == null) {
237: filtered = new Codes(definitions, type);
238: filteredCodes.put(type, filtered);
239: }
240: return filtered;
241: }
242: }
243:
244: /**
245: * The set of codes for a specific type of CRS. This set filter the codes set in the
246: * enclosing {@link PropertyAuthorityFactory} in order to keep only the codes for the
247: * specified type. Filtering is performed on the fly. Consequently, this set is cheap
248: * if the user just want to check for the existence of a particular code.
249: */
250: private static final class Codes extends DerivedSet/*<String, String>*/{
251: /**
252: * The spatial reference objects type (may be {@code Object.class}).
253: */
254: private final Class/*<? extends IdentifiedType>*/type;
255:
256: /**
257: * The reference to {@link PropertyAuthorityFactory#definitions}.
258: */
259: private final Map/*<String,String>*/definitions;
260:
261: /**
262: * Constructs a set of codes for the specified type.
263: */
264: public Codes(final Map/*<String,String>*/definitions,
265: final Class/*<? extends IdentifiedType>*/type) {
266: super (definitions.keySet());
267: this .definitions = definitions;
268: this .type = type;
269: }
270:
271: /**
272: * Returns the code if the associated key is of the expected type, or {@code null}
273: * otherwise.
274: */
275: protected/*String*/Object baseToDerived(
276: final/*String*/Object element) {
277: final String key = (String) element;
278: final String wkt = (String) definitions.get(key);
279: final int length = wkt.length();
280: int i = 0;
281: while (i < length
282: && Character.isJavaIdentifierPart(wkt.charAt(i)))
283: i++;
284: Class candidate = Parser.getClassOf(wkt.substring(0, i));
285: if (candidate == null) {
286: candidate = IdentifiedObject.class;
287: }
288: return type.isAssignableFrom(candidate) ? key : null;
289: }
290:
291: /**
292: * Transforms a value in this set to a value in the base set.
293: */
294: protected/*String*/Object derivedToBase(
295: final/*String*/Object element) {
296: return element;
297: }
298: }
299:
300: /**
301: * Returns the Well Know Text from a code.
302: *
303: * @param code Value allocated by authority.
304: * @return The Well Know Text (WKT) for the specified code.
305: * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
306: */
307: public String getWKT(final String code)
308: throws NoSuchAuthorityCodeException {
309: ensureNonNull("code", code);
310: final String wkt = definitions.getProperty(trimAuthority(code));
311: if (wkt == null) {
312: throw noSuchAuthorityCode(IdentifiedObject.class, code);
313: }
314: return wkt.trim();
315: }
316:
317: /**
318: * Gets a description of the object corresponding to a code.
319: *
320: * @param code Value allocated by authority.
321: * @return A description of the object, or {@code null} if the object
322: * corresponding to the specified {@code code} has no description.
323: * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
324: * @throws FactoryException if the query failed for some other reason.
325: */
326: public InternationalString getDescriptionText(final String code)
327: throws NoSuchAuthorityCodeException, FactoryException {
328: final String wkt = getWKT(code);
329: int start = wkt.indexOf('"');
330: if (start >= 0) {
331: final int end = wkt.indexOf('"', ++start);
332: if (end >= 0) {
333: return new SimpleInternationalString(wkt.substring(
334: start, end).trim());
335: }
336: }
337: return null;
338: }
339:
340: /**
341: * Returns the parser.
342: */
343: private Parser getParser() {
344: if (parser == null) {
345: parser = new Parser();
346: }
347: return parser;
348: }
349:
350: /**
351: * Returns an arbitrary object from a code. If the object type is know at compile time, it is
352: * recommended to invoke the most precise method instead of this one.
353: *
354: * @param code Value allocated by authority.
355: * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
356: * @throws FactoryException if the object creation failed for some other reason.
357: */
358: public IdentifiedObject createObject(final String code)
359: throws NoSuchAuthorityCodeException, FactoryException {
360: final String wkt = getWKT(code);
361: final Parser parser = getParser();
362: try {
363: synchronized (parser) {
364: parser.code = code;
365: return (IdentifiedObject) parser.parseObject(wkt);
366: }
367: } catch (ParseException exception) {
368: throw new FactoryException(exception);
369: }
370: }
371:
372: /**
373: * Returns a coordinate reference system from a code. If the object type is know at compile
374: * time, it is recommended to invoke the most precise method instead of this one.
375: *
376: * @param code Value allocated by authority.
377: * @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
378: * @throws FactoryException if the object creation failed for some other reason.
379: */
380: public CoordinateReferenceSystem createCoordinateReferenceSystem(
381: final String code) throws NoSuchAuthorityCodeException,
382: FactoryException {
383: final String wkt = getWKT(code);
384: final Parser parser = getParser();
385: try {
386: synchronized (parser) {
387: parser.code = code;
388: // parseCoordinateReferenceSystem provides a slightly faster path than parseObject.
389: return (CoordinateReferenceSystem) parser
390: .parseCoordinateReferenceSystem(wkt);
391: }
392: } catch (ParseException exception) {
393: throw new FactoryException(exception);
394: }
395: }
396:
397: /**
398: * Trims the authority scope, if present. If more than one authority were given at
399: * {@linkplain #PropertyAuthorityFactory(ReferencingFactoryContainer, Citation[], URL)
400: * construction time}, then any of them may appears as the scope in the supplied code.
401: *
402: * @param code The code to trim.
403: * @return The code without the authority scope.
404: */
405: //@Override
406: protected String trimAuthority(String code) {
407: code = code.trim();
408: final GenericName name = NameFactory.create(code);
409: final GenericName scope = name.getScope();
410: if (scope == null) {
411: return code;
412: }
413: final String candidate = scope.toString();
414: for (int i = 0; i < authorities.length; i++) {
415: if (Citations.identifierMatches(authorities[i], candidate)) {
416: return name.asLocalName().toString().trim();
417: }
418: }
419: return code;
420: }
421:
422: /**
423: * The WKT parser for this authority factory. This parser add automatically the authority
424: * code if it was not explicitly specified in the WKT.
425: */
426: private final class Parser extends
427: org.geotools.referencing.wkt.Parser {
428: /**
429: * The authority code for the WKT to be parsed.
430: */
431: String code;
432:
433: /**
434: * Creates the parser.
435: */
436: public Parser() {
437: super (Symbols.DEFAULT, factories);
438: }
439:
440: /**
441: * Add the authority code to the specified properties, if not already present.
442: */
443: // @Override
444: protected Map alterProperties(Map properties) {
445: Object candidate = properties
446: .get(IdentifiedObject.IDENTIFIERS_KEY);
447: if (candidate == null && code != null) {
448: properties = new HashMap(properties);
449: code = trimAuthority(code);
450: final Object identifiers;
451: if (authorities.length <= 1) {
452: identifiers = new NamedIdentifier(authority, code);
453: } else {
454: final NamedIdentifier[] ids = new NamedIdentifier[authorities.length];
455: for (int i = 0; i < ids.length; i++) {
456: ids[i] = new NamedIdentifier(authorities[i],
457: code);
458: }
459: identifiers = ids;
460: }
461: properties.put(IdentifiedObject.IDENTIFIERS_KEY,
462: identifiers);
463: }
464: return super.alterProperties(properties);
465: }
466: }
467: }
|