/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.referencing; import java.io.FileReader; import java.io.IOException; import java.io.LineNumberReader; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.ParsePosition; import java.text.ParseException; import java.util.Arrays; import java.util.Locale; import java.util.StringTokenizer; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.CoordinateOperationFactory; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.opengis.referencing.operation.TransformException; import org.opengis.geometry.DirectPosition; import org.opengis.geometry.MismatchedDimensionException; import org.geotools.geometry.GeneralDirectPosition; import org.geotools.io.TableWriter; import org.geotools.measure.Measure; import org.geotools.referencing.crs.AbstractCRS; import org.geotools.referencing.wkt.AbstractConsole; import org.geotools.referencing.wkt.Parser; import org.geotools.referencing.wkt.Preprocessor; import org.geotools.resources.Arguments; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; /** * A console for executing CRS operations from the command line. * Instructions are read from the {@linkplain System#in standard input stream} * and results are sent to the {@linkplain System#out standard output stream}. * Instructions include: * * * * * * * * * * * * * * * * * * * * * * * *
{@code SET} name {@code =} wkt * Set the specified name as a shortcut for the specified Well Know * Text (wkt). This WKT can contains other shortcuts defined previously.
{@code transform = } wkt * Set explicitly a {@linkplain MathTransform math transform} to use for * coordinate transformations. This instruction is a more direct alternative to the usage of * {@code source crs} and {@code target crs} instruction.
{@code source crs = } wkt * Set the source {@linkplain CoordinateReferenceSystem coordinate reference * system} to the specified object. This object can be specified as a Well Know Text * (wkt) or as a shortcut previously set.
{@code target crs = } wkt * Set the target {@linkplain CoordinateReferenceSystem coordinate reference * system} to the specified object. This object can be specified as a Well Know Text * (wkt) or as a shortcut previously set. Once both source and target * CRS are specified a {@linkplain MathTransform math transform} from source to * target CRS is automatically infered.
{@code source pt = } coord * Transforms the specified coordinates from source CRS to target CRS * and prints the result.
{@code target pt = } coord * Inverse transforms the specified coordinates from target CRS to source CRS * and prints the result.
{@code test tolerance = } vector * Set the maximum difference between the transformed source point and the * target point. Once this value is set, every occurence of the {@code target pt} instruction * will trig this comparaison. If a greater difference is found, an exception is thrown or a * message is printed to the error stream.
{@code print set} * Prints the set of shortcuts defined in previous calls to {@code SET} instruction.
{@code print crs} * Prints the source and target {@linkplain CoordinateReferenceSystem coordinate reference system} * {@linkplain MathTransform math transform} and its inverse as Well Know Text (wkt).
{@code print pts} * Prints the source and target points, their transformed points, and the distance between * them.
{@code exit} * Quit the console.
* * @since 2.1 * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public class Console extends AbstractConsole { /** * The locale for number parser. */ private final Locale locale = Locale.US; /** * The number format to use for reading coordinate points. */ private final NumberFormat numberFormat = NumberFormat.getNumberInstance(locale); /** * The number separator in vectors. Usually {@code ,}, but could * also be {@code ;} if the coma is already used as the decimal * separator. */ private final String numberSeparator; /** * The coordinate operation factory to use. */ private final CoordinateOperationFactory factory = ReferencingFactoryFinder.getCoordinateOperationFactory(null); /** * The source and target CRS, or {@code null} if not yet determined. */ private CoordinateReferenceSystem sourceCRS, targetCRS; /** * Source and target coordinate points, or {@code null} if not yet determined. */ private DirectPosition sourcePosition, targetPosition; /** * The math transform, or {@code null} if not yet determined. */ private MathTransform transform; /** * The tolerance value. If non-null, the difference between the computed and the specified * target point will be compared against this tolerance threshold. If it is greater, a message * will be printed. */ private double[] tolerance; /** * The last error thats occured while processing an instruction. * Used in order to print the stack trace on request. */ private transient Exception lastError; /** * Creates a new console instance using {@linkplain System#in standard input stream}, * {@linkplain System#out standard output stream}, {@linkplain System#err error output stream} * and the system default line separator. */ public Console() { super(new Preprocessor(new Parser())); numberSeparator = getNumberSeparator(numberFormat); } /** * Creates a new console instance using the specified input stream. * * @param in The input stream. */ public Console(final LineNumberReader in) { super(new Preprocessor(new Parser()), in); numberSeparator = getNumberSeparator(numberFormat); } /** * Returns the character to use as a number separator. * As a side effect, this method also adjust the minimum and maximum digits. */ private static String getNumberSeparator(final NumberFormat numberFormat) { numberFormat.setGroupingUsed(false); numberFormat.setMinimumFractionDigits(6); numberFormat.setMaximumFractionDigits(6); if (numberFormat instanceof DecimalFormat) { final char decimalSeparator = ((DecimalFormat) numberFormat) .getDecimalFormatSymbols().getDecimalSeparator(); if (decimalSeparator == ',') { return ";"; } } return ","; } /** * Run the console from the command line. Before to process all instructions * from the {@linkplain System#in standard input stream}, this method first * process the following optional command-line arguments: *

* * * * * * * *
-load <filename> Load a definition file before to run instructions from * the standard input stream.
-encoding <code> Set the character encoding.
-locale <language> Set the language for the output (e.g. "fr" for French).
* * @param args the command line arguments */ public static void main(String[] args) { final Arguments arguments = new Arguments(args); final String load = arguments.getOptionalString("-load" ); final String file = arguments.getOptionalString("-file" ); args = arguments.getRemainingArguments(0); Locale.setDefault(arguments.locale); final LineNumberReader input; final Console console; /* * The usual way to execute instructions from a file is to redirect the standard input * stream using the standard DOS/Unix syntax (e.g. "< thefile.txt"). However, we also * accept a "-file" argument for the same purpose. It is easier to debug. On DOS system, * it also use the system default encoding instead of the command-line one. */ if (file == null) { input = null; console = new Console(); } else try { input = new LineNumberReader(new FileReader(file)); console = new Console(input); console.setPrompt(null); } catch (IOException exception) { System.err.println(exception.getLocalizedMessage()); return; } /* * Load predefined shorcuts. The file must be in the form "name = WKT". An example * of such file is the property file used by the property-based authority factory. */ if (load != null) try { final LineNumberReader in = new LineNumberReader(new FileReader(load)); try { console.loadDefinitions(in); } catch (ParseException exception) { console.reportError(exception); in.close(); return; } in.close(); } catch (IOException exception) { console.reportError(exception); return; } /* * Run all instructions and close the stream if it was a file one. */ console.run(); if (input != null) try { input.close(); } catch (IOException exception) { console.reportError(exception); } } /** * Execute the specified instruction. * * @param instruction The instruction to execute. * @throws IOException if an I/O operation failed while writting to the * {@linkplain #out output stream}. * @throws ParseException if a line can't be parsed. * @throws FactoryException If a transform can't be created. * @throws TransformException if a transform failed. */ protected void execute(String instruction) throws IOException, ParseException, FactoryException, TransformException { String value = null; int i = instruction.indexOf('='); if (i >= 0) { value = instruction.substring(i+1).trim(); instruction = instruction.substring(0,i).trim(); } final StringTokenizer keywords = new StringTokenizer(instruction); if (keywords.hasMoreTokens()) { final String key0 = keywords.nextToken(); if (!keywords.hasMoreTokens()) { // ------------------------------- // exit // ------------------------------- if (key0.equalsIgnoreCase("exit")) { if (value != null) { throw unexpectedArgument("exit"); } stop(); return; } // ------------------------------- // stacktrace // ------------------------------- if (key0.equalsIgnoreCase("stacktrace")) { if (value != null) { throw unexpectedArgument("stacktrace"); } if (lastError != null) { lastError.printStackTrace(err); } return; } // ------------------------------- // transform = // ------------------------------- if (key0.equalsIgnoreCase("transform")) { transform = (MathTransform) parseObject(value, MathTransform.class); sourceCRS = null; targetCRS = null; return; } } else { final String key1 = keywords.nextToken(); if (!keywords.hasMoreTokens()) { // ------------------------------- // print definition|crs|points // ------------------------------- if (key0.equalsIgnoreCase("print")) { if (value != null) { throw unexpectedArgument("print"); } if (key1.equalsIgnoreCase("set")) { printDefinitions(); return; } if (key1.equalsIgnoreCase("crs")) { printCRS(); return; } if (key1.equalsIgnoreCase("pts")) { printPts(); return; } } // ------------------------------- // set = // ------------------------------- if (key0.equalsIgnoreCase("set")) { addDefinition(key1, value); return; } // ------------------------------- // test tolerance = // ------------------------------- if (key0.equalsIgnoreCase("test")) { if (key1.equalsIgnoreCase("tolerance")) { tolerance = parseVector(value); return; } } // ------------------------------- // source|target crs = // ------------------------------- if (key1.equalsIgnoreCase("crs")) { if (key0.equalsIgnoreCase("source")) { sourceCRS = (CoordinateReferenceSystem) parseObject(value, CoordinateReferenceSystem.class); transform = null; return; } if (key0.equalsIgnoreCase("target")) { targetCRS = (CoordinateReferenceSystem) parseObject(value, CoordinateReferenceSystem.class); transform = null; return; } } // ------------------------------- // source|target pt = // ------------------------------- if (key1.equalsIgnoreCase("pt")) { if (key0.equalsIgnoreCase("source")) { sourcePosition = new GeneralDirectPosition(parseVector(value)); return; } if (key0.equalsIgnoreCase("target")) { targetPosition = new GeneralDirectPosition(parseVector(value)); if (tolerance!=null && sourcePosition!=null) { update(); if (transform != null) { test(); } } return; } } } } } throw new ParseException(Errors.format(ErrorKeys.ILLEGAL_INSTRUCTION_$1, instruction), 0); } /** * Executes the "{@code print crs}" instruction. */ private void printCRS() throws FactoryException, IOException { final Locale locale = null; final Vocabulary resources = Vocabulary.getResources(locale); final TableWriter table = new TableWriter(out, TableWriter.SINGLE_VERTICAL_LINE); table.setMultiLinesCells(true); char separator = TableWriter.SINGLE_HORIZONTAL_LINE; if (sourceCRS!=null || targetCRS!=null) { table.writeHorizontalSeparator(); table.write(resources.getString(VocabularyKeys.SOURCE_CRS)); table.nextColumn(); table.write(resources.getString(VocabularyKeys.TARGET_CRS)); table.nextLine(); table.writeHorizontalSeparator(); if (sourceCRS != null) { table.write(parser.format(sourceCRS)); } table.nextColumn(); if (targetCRS != null) { table.write(parser.format(targetCRS)); } table.nextLine(); separator = TableWriter.DOUBLE_HORIZONTAL_LINE; } /* * Format the math transform and its inverse, if any. */ update(); if (transform != null) { table.nextLine(separator); table.write(resources.getString(VocabularyKeys.MATH_TRANSFORM)); table.nextColumn(); table.write(resources.getString(VocabularyKeys.INVERSE_TRANSFORM)); table.nextLine(); table.writeHorizontalSeparator(); table.write(parser.format(transform)); table.nextColumn(); try { table.write(parser.format(transform.inverse())); } catch (NoninvertibleTransformException exception) { table.write(exception.getLocalizedMessage()); } table.nextLine(); } table.writeHorizontalSeparator(); table.flush(); } /** * Print the source and target point, and their transforms. * * @throws FactoryException if the transform can't be computed. * @throws TransformException if a transform failed. * @throws IOException if an error occured while writing to the output stream. */ private void printPts() throws FactoryException, TransformException, IOException { update(); DirectPosition transformedSource = null; DirectPosition transformedTarget = null; String targetException = null; if (transform != null) { if (sourcePosition != null) { transformedSource = transform.transform(sourcePosition, null); } if (targetPosition != null) try { transformedTarget = transform.inverse().transform(targetPosition, null); } catch (NoninvertibleTransformException exception) { targetException = exception.getLocalizedMessage(); if (sourcePosition != null) { final GeneralDirectPosition p; transformedTarget = p = new GeneralDirectPosition(sourcePosition.getDimension()); Arrays.fill(p.ordinates, Double.NaN); } } } final Locale locale = null; final Vocabulary resources = Vocabulary.getResources(locale); final TableWriter table = new TableWriter(out, 0); table.setMultiLinesCells(true); table.writeHorizontalSeparator(); table.setAlignment(TableWriter.ALIGN_RIGHT); if (sourcePosition != null) { table.write(resources.getLabel(VocabularyKeys.SOURCE_POINT)); print(sourcePosition, table); print(transformedSource, table); table.nextLine(); } if (targetPosition != null) { table.write(resources.getLabel(VocabularyKeys.TARGET_POINT)); print(transformedTarget, table); print(targetPosition, table); table.nextLine(); } if (sourceCRS!=null && targetCRS!=null) { table.write(resources.getLabel(VocabularyKeys.DISTANCE)); printDistance(sourceCRS, sourcePosition, transformedTarget, table); printDistance(targetCRS, targetPosition, transformedSource, table); table.nextLine(); } table.writeHorizontalSeparator(); table.flush(); if (targetException != null) { out.write(targetException); out.write(lineSeparator); } } /** * Print the specified point to the specified table. * This helper method is for use by {@link #printPts}. * * @param point The point to print, or {@code null} if none. * @throws IOException if an error occured while writting to the output stream. */ private void print(final DirectPosition point, final TableWriter table) throws IOException { if (point != null) { table.nextColumn(); table.write(" ("); final double[] coords = point.getCoordinate(); for (int i=0; i=0;) { table.nextColumn(); } if (position2 != null) { if (crs instanceof AbstractCRS) try { final Measure distance; distance = ((AbstractCRS)crs).distance(position1.getCoordinate(), position2.getCoordinate()); table.setAlignment(TableWriter.ALIGN_RIGHT); table.write(numberFormat.format(distance.doubleValue())); table.write(" "); table.nextColumn(); table.write(String.valueOf(distance.getUnit())); table.setAlignment(TableWriter.ALIGN_LEFT); return; } catch (UnsupportedOperationException ignore) { /* * Underlying CRS do not supports distance computation. * Left the column blank. */ } } table.nextColumn(); } /////////////////////////////////////////////////////////// //////// //////// //////// H E L P E R M E T H O D S //////// //////// //////// /////////////////////////////////////////////////////////// /** * Invoked automatically when the {@code target pt} instruction were executed and a * {@code test tolerance} were previously set. The default implementation compares * the transformed source point with the expected target point. If a mismatch greater than * the tolerance error is found, an exception is thrown. Subclasses may overrides this * method in order to performs more tests. * * @throws TransformException if the source point can't be transformed, or a mistmatch is found. * @throws MismatchedDimensionException if the transformed source point doesn't have the * expected dimension. */ protected void test() throws TransformException, MismatchedDimensionException { final DirectPosition transformedSource = transform.transform(sourcePosition, null); final int sourceDim = transformedSource.getDimension(); final int targetDim = targetPosition.getDimension(); if (sourceDim != targetDim) { throw new MismatchedDimensionException(Errors.format(ErrorKeys.MISMATCHED_DIMENSION_$2, sourceDim, targetDim)); } for (int i=0; i= 1) { if (text.charAt(0)==start && text.charAt(endPos)==end) { text = text.substring(1, endPos).trim(); } } return text; } /** * Parse a vector of values. Vectors are used for coordinate points. * Example: *

     * (46.69439222, 13.91405611, 41.21)
     * 
* * @param text The vector to parse. * @return The vector as floating point numbers. * @throws ParseException if a number can't be parsed. */ private double[] parseVector(String text) throws ParseException { text = removeDelimitors(text, '(', ')'); final StringTokenizer st = new StringTokenizer(text, numberSeparator); final double[] values = new double[st.countTokens()]; for (int i=0; i