All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotoolkit.io.wkt.WKTFormat Maven / Gradle / Ivy

Go to download

Implementations of Coordinate Reference Systems (CRS), conversion and transformation services derived from ISO 19111.

There is a newer version: 3.20-geoapi-3.0
Show newest version
/*
 *    Geotoolkit.org - An Open Source Java GIS Toolkit
 *    http://www.geotoolkit.org
 *
 *    (C) 2002-2011, Open Source Geospatial Foundation (OSGeo)
 *    (C) 2009-2011, Geomatys
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotoolkit.io.wkt;

import java.util.Map;
import java.util.Locale;
import java.util.LinkedHashMap;
import java.io.Reader;
import java.io.Writer;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.EOFException;
import java.text.Format;
import java.text.NumberFormat;
import java.text.FieldPosition;
import java.text.ParseException;
import java.text.ParsePosition;

import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.cs.CoordinateSystemAxis;

import org.geotoolkit.factory.Hints;
import org.geotoolkit.util.Strings;
import org.geotoolkit.util.NullArgumentException;
import org.geotoolkit.util.collection.XCollections;
import org.geotoolkit.referencing.datum.BursaWolfParameters;
import org.geotoolkit.io.ContentFormatException;
import org.geotoolkit.resources.Errors;


/**
 * Parser and formatter for Well Known Text (WKT) objects. This format handles a
 * pair of {@link Parser} and {@link Formatter}, to be used by {@code parse} and {@code format}
 * methods respectively.
 * 

* {@code WKTFormat} objects allow the following configuration: *

*

    *
  • The {@linkplain Symbols symbols} to use (curly braces or brackets, etc.)
  • *
  • The preferred authority of {@linkplain IdentifiedObject#getName() object name} to * format (see {@link Formatter#getName(IdentifiedObject)} for more information)
  • *
  • Whatever ANSI X3.64 colors are allowed or not (default is not)
  • *
  • The indentation
  • *
* * {@section String expansion} * Because the strings to be parsed by this class are long and tend to contain repetitive * substrings, {@code WKTFormat} provides a mechanism for performing string substitutions * before the parsing take place. Long strings can be assigned short names by calls to the * {@linkplain #definitions()}.put(key,value) method. * After definitions have been added, any call to a parsing method will replace all occurrences * of a short name by the associated long string. *

* The short names must comply with the rules of Java identifiers. It is recommended, but not * required, to prefix the names by some symbol like {@code "$"} in order to avoid ambiguity. * Note however that this class doesn't replace occurrences between quoted text, so string * expansion still relatively safe even when used with non-prefixed identifiers. *

* In the example below, the {@code $WGS84} substring which appear in the argument given to the * {@code parseObject} method will be expanded into the full {@code GEOGCS["WGS84", ...]} string * before the parsing proceed. * *

* {@linkplain #definitions()}.put("$WGS84", * "GEOGCS[\"WGS84\", DATUM[ ...etc... ]]);
* Object crs = {@linkplain #parseObject(String) parseObject}("PROJCS[\"Mercator_1SP\", * $WGS84, PROJECTION[
...etc... ]]"); *
* * {@section Synchronization} * {@code WKTFormat}s are not synchronized. It is recommended to create separate format instances * for each thread. If multiple threads access a format concurrently, it must be synchronized * externally. * * @author Martin Desruisseaux (IRD, Geomatys) * @author Rémi Eve (IRD) * @version 3.00 * * @since 3.00 * @module */ public class WKTFormat extends Format { /** * For cross-version compatibility. */ private static final long serialVersionUID = -2909110214650709560L; /** * The mapping between WKT element name and the object class to be created. * Keys must be upper case. */ private static final Map> TYPES; static { final Map> map = new LinkedHashMap>(25); map.put( "GEOGCS", GeographicCRS.class); map.put( "PROJCS", ProjectedCRS.class); map.put( "GEOCCS", GeocentricCRS.class); map.put( "VERT_CS", VerticalCRS.class); map.put( "LOCAL_CS", EngineeringCRS.class); map.put( "COMPD_CS", CompoundCRS.class); map.put( "FITTED_CS", DerivedCRS.class); map.put( "AXIS", CoordinateSystemAxis.class); map.put( "PRIMEM", PrimeMeridian.class); map.put( "TOWGS84", BursaWolfParameters.class); map.put( "SPHEROID", Ellipsoid.class); map.put( "VERT_DATUM", VerticalDatum.class); map.put( "LOCAL_DATUM", EngineeringDatum.class); map.put( "DATUM", GeodeticDatum.class); map.put( "PARAM_MT", MathTransform.class); map.put( "CONCAT_MT", MathTransform.class); map.put( "INVERSE_MT", MathTransform.class); map.put("PASSTHROUGH_MT", MathTransform.class); TYPES = map; } /** * The symbols to use for this formatter. The same object is also referenced in * the {@linkplain #parser} and {@linkplain #formatter}. But it appears here * for serialization purpose. */ private Symbols symbols = Symbols.DEFAULT; /** * The colors to use for this formatter, or {@code null} for no syntax coloring. * The same object is also referenced in the {@linkplain #formatter}. It appears * here for serialization purpose. */ private Colors colors = null; /** * The preferred authority for objects or parameter names. The same object is also * referenced in the {@linkplain #formatter}. It appears here for serialization purpose. */ private Citation authority = FormattableObject.OGC; /** * The amount of spaces to use in indentation, or * {@value org.geotoolkit.io.wkt.FormattableObject#SINGLE_LINE} if indentation is disabled. * The same value is also stored in the {@linkplain #formatter}. It appears here for * serialization purpose. */ private int indentation = FormattableObject.getDefaultIndentation(); /** * The map of definitions. Will be created only if used. */ private Definitions definitions; /** * The parser. Will be created when first needed. */ private transient Parser parser; /** * A formatter using the same symbols than the {@linkplain #parser}. * Will be created by the {@link #format} method when first needed. */ private transient Formatter formatter; /** * Constructs a format using the default factories. */ public WKTFormat() { } /** * Constructs a format using the given factories for creating parsed objects. * * @param hints The hints to be used for fetching the factories, or * {@code null} for the system-wide default hints. */ public WKTFormat(final Hints hints) { if (!XCollections.isNullOrEmpty(hints)) { parser = new ReferencingParser(symbols, hints); } } /** * Returns the symbols used for parsing and formatting WKT. * * @return The current set of symbols used for parsing and formatting WKT. */ public Symbols getSymbols() { return symbols; } /** * Sets the symbols used for parsing and formatting WKT. * * @param symbols The new set of symbols to use for parsing and formatting WKT. */ public void setSymbols(final Symbols symbols) { if (symbols == null) { throw new NullArgumentException(Errors.format( Errors.Keys.NULL_ARGUMENT_$1, "symbols")); } if (!symbols.equals(this.symbols)) { this.symbols = symbols; formatter = null; if (parser != null) { parser.setSymbols(symbols); } if (definitions != null) { definitions.quote = symbols.quote; } } } /** * Returns the set of colors to use for syntax coloring, or {@code null} if none. * If non-null, the set of colors are escape sequences for ANSI X3.64 (aka ECMA-48 * and ISO/IEC 6429) compatible terminal. By default there is no syntax coloring. * * @return The set of colors for syntax coloring, or {@code null} if none. */ public Colors getColors() { return colors; } /** * Sets the colors to use for syntax coloring on ANSI X3.64 (aka ECMA-48 and ISO/IEC 6429) * compatible terminal. This apply only when formatting text. *

* Newly created {@code WKTFormat} have no syntax coloring. If the {@link Colors#DEFAULT DEFAULT} * set of colors is given to this method, then the {@link #format format} method tries to highlight * most of the elements that are relevant to {@link org.geotoolkit.referencing.CRS#equalsIgnoreMetadata}. * * @param colors The set of colors for syntax coloring, or {@code null} if none. */ public void setColors(final Colors colors) { this.colors = colors; if (formatter != null) { formatter.colors = colors; } } /** * Returns the preferred authority for formatting WKT entities. The {@link #format format} * method will use the name specified by this authority, if available. See the * {@link Formatter#getName(IdentifiedObject) Formatter} javadoc for more information. * * @return The expected authority. * * @see Formatter#getName(IdentifiedObject) */ public Citation getAuthority() { return authority; } /** * Sets the preferred authority for formatting WKT entities. The {@link #format format} * method will use the name specified by this authority, if available. See the * {@link Formatter#getName(IdentifiedObject) Formatter} javadoc for more information. * * @param authority The new authority. * * @see Formatter#getName(IdentifiedObject) */ public void setAuthority(final Citation authority) { if (authority == null) { throw new NullArgumentException(Errors.format( Errors.Keys.NULL_ARGUMENT_$1, "authority")); } this.authority = authority; if (formatter != null) { formatter.authority = authority; } } /** * Returns the current indentation to be used for formatting objects. The * {@value org.geotoolkit.io.wkt.FormattableObject#SINGLE_LINE} value means * that the whole WKT is to be formatted on a single line. * * @return The current indentation. */ public int getIndentation() { return indentation; } /** * Sets a new indentation to be used for formatting objects. The * {@value org.geotoolkit.io.wkt.FormattableObject#SINGLE_LINE} value * means that the whole WKT is to be formatted on a single line. * * @param indentation The new indentation to use. */ public void setIndentation(final int indentation) { this.indentation = indentation; if (formatter != null) { formatter.indentation = indentation; } } /** * Returns a map of short identifiers to substitute by WKT string before parsing. See the * "String expansion" section in class javadoc * for details. *

* Entries added in the definitions map will have immediate effect in this {@code WKTFormat} * object. They must obey the following constraints: *

*

    *
  • Keys must be valid identifiers according Java rules.
  • *
  • Values must be valid WKT strings - they will be parsed.
  • *
*

* Any attempt to put an illegal key or value in the definitions map will result in * an {@link IllegalArgumentException} being thrown. * * @return A live map of (identifiers, WKT) entries. */ public Map definitions() { if (definitions == null) { definitions = new Definitions(this); definitions.quote = symbols.quote; } return definitions; } /** * Prints to the specified stream a table of all {@linkplain #definitions() definitions}. * The table content is inferred from the values added to the definitions map. This method * does nothing if the definitions map is empty. * * @param out writer The output stream where to write the table. * @throws IOException if an error occurred while writing to the output stream. */ public void printDefinitions(final Writer out) throws IOException { if (!XCollections.isNullOrEmpty(definitions)) { definitions.print(out, colors != null); } } /** * Parses the specified text and ensures that the resulting object is of the specified type. * If the given text is a valid identifier and this identifier was registered in the * {@linkplain #definitions() definitions} map, then the associated object will be returned. * Otherwise the given text is parsed as a WKT. * * @param The expected type of the object to be parsed. * @param text The WKT to parse, or an identifier given to the * {@linkplain #definitions() definitions} map. * @param offset The index of the first character to parse in the given text. This * information is explicitly given instead than expecting the caller to compute * {@code text.substring(offset)} in order to provide more accurate error offset * in case of {@link ParseException}. * @param type The expected type of the object to be parsed (usually a * {@linkplain CoordinateReferenceSystem}.class or * {@linkplain MathTransform}.class). * @return The parsed object. * @throws ParseException if the string can't be parsed. */ public T parse(String text, final int offset, final Class type) throws ParseException { Object value; text = text.substring(offset); if (Definitions.isIdentifier(text)) { if (definitions == null || (value = definitions.getParsed(text)) == null) { throw new ParseException(Errors.format( Errors.Keys.NO_SUCH_AUTHORITY_CODE_$2, type, text), 0); } } else { if (definitions != null) { text = definitions.substitute(text); } final Parser parser = getParser(); try { if (MathTransform.class.isAssignableFrom(type) && parser instanceof MathTransformParser) { value = ((MathTransformParser) parser).parseMathTransform(text); } else if (CoordinateReferenceSystem.class.isAssignableFrom(type) && parser instanceof ReferencingParser) { value = ((ReferencingParser) parser).parseCoordinateReferenceSystem(text); } else { value = parser.parseObject(text); } } catch (ParseException exception) { if (definitions != null) { exception = definitions.adjustErrorOffset(exception, offset); } throw exception; } } final Class actualType = value.getClass(); if (type.isAssignableFrom(actualType)) { return type.cast(value); } throw new ParseException(Errors.format( Errors.Keys.ILLEGAL_CLASS_$2, actualType, type), 0); } /** * Parses the specified Well Know Text (WKT). The default implementation delegates * the work to {@link #parse(String,int,Class) parse}(text, 0, Object.class). * * @param text The text to be parsed. * @return The parsed object. * @throws ParseException if the string can't be parsed. */ @Override public Object parseObject(final String text) throws ParseException { return parse(text, 0, Object.class); } /** * Parses the specified Well Know Text starting at the specified position. * The default implementation delegates the work to {@link #parseObject(String) * parseObject}(wkt.substring(position.getIndex())). * * {@note The other way around (parseObject(String) invoking * parseObject(String,ParsePosition) as in the default * Format implementation) is not pratical in the context * of WKTFormat. Among other problems, it doesn't provide * any accurate error message.} * * @param text The text to parse. * @param position The index of the first character to parse. * @return The parsed object, or {@code null} in case of failure. */ @Override public Object parseObject(final String text, final ParsePosition position) { final int start = position.getIndex(); try { return parseObject(text.substring(start)); } catch (ParseException exception) { position.setIndex(start); position.setErrorIndex(exception.getErrorOffset() + start); return null; } } /** * Returns the parser, creating it if needed. */ private Parser getParser() { if (parser == null) { parser = new ReferencingParser(symbols, (Hints) null); } return parser; } /** * Returns the formatter, creating it if needed. */ private Formatter getFormatter() { if (formatter == null) { if (Parser.SCIENTIFIC_NOTATION) { // We do not want to expose the "scientific notation hack" to the formatter. // TODO: Remove this block if some future version of J2SE provides something // like 'allowScientificNotationParsing(true)' in DecimalFormat. formatter = new Formatter(symbols, (NumberFormat) symbols.numberFormat.clone()); } else { formatter = getParser().formatter(); } formatter.colors = colors; formatter.authority = authority; formatter.indentation = indentation; } return formatter; } /** * Formats the specified object as a Well Know Text. * Formatting will uses the same set of symbols than the one used for parsing. * * @param object The object to format. * @param toAppendTo Where the text is to be appended. * @param pos An identification of a field in the formatted text. * * @see #getWarning */ @Override public StringBuffer format(final Object object, final StringBuffer toAppendTo, final FieldPosition pos) { final Formatter formatter = getFormatter(); try { formatter.clear(); formatter.buffer = toAppendTo; formatter.bufferBase = toAppendTo.length(); if (object instanceof MathTransform) { formatter.append((MathTransform) object); } else if (object instanceof GeneralParameterValue) { // Special processing for parameter values, which is formatted // directly in 'Formatter'. Note that in GeoAPI, this interface // doesn't share the same parent interface than other interfaces. formatter.append((GeneralParameterValue) object); } else { formatter.append((IdentifiedObject) object); } return toAppendTo; } finally { formatter.buffer = null; } } /** * Reads WKT strings from an input stream and reformats them to the specified * output stream. WKT strings are read until the the end-of-stream, or until * an unparsable WKT has been hit. In this later case, an error message is * formatted to the specified error stream. *

* This method is useful for {@linkplain #setIndentation changing the indentation}, rewriting * the WKT using parameter names specified by a {@linkplain #setAuthority different authority}, * for {@linkplain #setColors adding syntax coloring}, expanding the WKT strings according the * {@linkplain #definitions() definitions}, etc. * * @param in The input stream. * @param out The output stream. * @param err The error stream. * @throws IOException if an error occurred while reading from the input stream * or writing to the output stream. */ public void reformat(final Reader in, final Writer out, final PrintWriter err) throws IOException { final StringBuilder buffer = new StringBuilder(); final String lineSeparator = System.getProperty("line.separator", "\n"); final Symbols symbols = getSymbols(); final char[] openingBrackets = symbols.openingBrackets; final char[] closingBrackets = symbols.closingBrackets; final int [] bracketCount = new int[Math.max(openingBrackets.length, closingBrackets.length)]; while (true) { /* * Skips whitespaces, stopping the method if EOF is reached. */ char c; do { final int ci = in.read(); if (ci < 0) { return; } c = (char) ci; } while (Character.isWhitespace(c)); /* * Copies next characters until the first opening bracket. Count the bracket * that we open, and stop copying characters after we have closed all of them. */ buffer.setLength(0); copy: while (true) { final int ci = in.read(); if (ci < 0) { throw new EOFException(Errors.format(Errors.Keys.UNEXPECTED_END_OF_STRING)); } c = (char) ci; buffer.append(c); for (int i=0; i{@linkplain ProjectedCRS}.class for element "{@code PROJCS}". *

* This method is the converse of {@link #getNameOf}. * * @param element The WKT element name. * @return The GeoAPI class of the specified element, or {@code null} if unknown. */ public static Class getClassOf(String element) { if (element == null) { return null; } element = element.trim().toUpperCase(Locale.US); final Class type = TYPES.get(element); assert type == null || type.equals(MathTransform.class) || element.equals(getNameOf(type)) : type; return type; } /** * Returns the WKT name of the specified object type. For example this method returns * "{@code PROJCS}" for type {@linkplain ProjectedCRS}.class. *

* This method is the converse of {@link #getClassOf}. * * @param type The GeoAPI class of the specified element. * @return The WKT element name, or {@code null} if unknown. */ public static String getNameOf(final Class type) { if (type != null) { for (final Map.Entry> entry : TYPES.entrySet()) { final Class candidate = entry.getValue(); if (candidate.isAssignableFrom(type)) { return entry.getKey(); } } } return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy