001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2004, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.referencing;
018:
019: // J2SE dependencies
020: import java.io.FileReader;
021: import java.io.IOException;
022: import java.io.LineNumberReader;
023: import java.text.DecimalFormat;
024: import java.text.NumberFormat;
025: import java.text.ParsePosition;
026: import java.text.ParseException;
027: import java.util.Arrays;
028: import java.util.Locale;
029: import java.util.StringTokenizer;
030:
031: // OpenGIS dependencies
032: import org.opengis.referencing.FactoryException;
033: import org.opengis.referencing.crs.CoordinateReferenceSystem;
034: import org.opengis.referencing.operation.CoordinateOperationFactory;
035: import org.opengis.referencing.operation.MathTransform;
036: import org.opengis.referencing.operation.NoninvertibleTransformException;
037: import org.opengis.referencing.operation.TransformException;
038: import org.opengis.geometry.DirectPosition;
039: import org.opengis.geometry.MismatchedDimensionException;
040:
041: // Geotools dependencies
042: import org.geotools.geometry.GeneralDirectPosition;
043: import org.geotools.io.TableWriter;
044: import org.geotools.measure.Measure;
045: import org.geotools.referencing.crs.AbstractCRS;
046: import org.geotools.referencing.wkt.AbstractConsole;
047: import org.geotools.referencing.wkt.Parser;
048: import org.geotools.referencing.wkt.Preprocessor;
049: import org.geotools.resources.Arguments;
050: import org.geotools.resources.i18n.Errors;
051: import org.geotools.resources.i18n.ErrorKeys;
052: import org.geotools.resources.i18n.Vocabulary;
053: import org.geotools.resources.i18n.VocabularyKeys;
054:
055: /**
056: * A console for executing CRS operations from the command line.
057: * Instructions are read from the {@linkplain System#in standard input stream}
058: * and results are sent to the {@linkplain System#out standard output stream}.
059: * Instructions include:
060: *
061: * <table>
062: * <tr><td nowrap valign="top">{@code SET} <var>name</var> {@code =} <var>wkt</var></td><td>
063: * Set the specified <var>name</var> as a shortcut for the specified Well Know
064: * Text (<var>wkt</var>). This WKT can contains other shortcuts defined previously.</td></tr>
065: *
066: * <tr><td nowrap valign="top">{@code transform = } <var>wkt</var></td><td>
067: * Set explicitly a {@linkplain MathTransform math transform} to use for
068: * coordinate transformations. This instruction is a more direct alternative to the usage of
069: * {@code source crs} and {@code target crs} instruction.</td></tr>
070: *
071: * <tr><td nowrap valign="top">{@code source crs = } <var>wkt</var></td><td>
072: * Set the source {@linkplain CoordinateReferenceSystem coordinate reference
073: * system} to the specified object. This object can be specified as a Well Know Text
074: * (<var>wkt</var>) or as a shortcut previously set.</td></tr>
075: *
076: * <tr><td nowrap valign="top">{@code target crs = } <var>wkt</var></td><td>
077: * Set the target {@linkplain CoordinateReferenceSystem coordinate reference
078: * system} to the specified object. This object can be specified as a Well Know Text
079: * (<var>wkt</var>) or as a shortcut previously set. Once both source and target
080: * CRS are specified a {@linkplain MathTransform math transform} from source to
081: * target CRS is automatically infered.</td></tr>
082: *
083: * <tr><td nowrap valign="top">{@code source pt = } <var>coord</var></td><td>
084: * Transforms the specified coordinates from source CRS to target CRS
085: * and prints the result.</td></tr>
086: *
087: * <tr><td nowrap valign="top">{@code target pt = } <var>coord</var></td><td>
088: * Inverse transforms the specified coordinates from target CRS to source CRS
089: * and prints the result.</td></tr>
090: *
091: * <tr><td nowrap valign="top">{@code test tolerance = } <var>vector</var></td><td>
092: * Set the maximum difference between the transformed source point and the
093: * target point. Once this value is set, every occurence of the {@code target pt} instruction
094: * will trig this comparaison. If a greater difference is found, an exception is thrown or a
095: * message is printed to the error stream.</td></tr>
096: *
097: * <tr><td nowrap valign="top">{@code print set}</td><td>
098: * Prints the set of shortcuts defined in previous calls to {@code SET} instruction.</td></tr>
099: *
100: * <tr><td nowrap valign="top">{@code print crs}</td><td>
101: * Prints the source and target {@linkplain CoordinateReferenceSystem coordinate reference system}
102: * {@linkplain MathTransform math transform} and its inverse as Well Know Text (wkt).</td></tr>
103: *
104: * <tr><td nowrap valign="top">{@code print pts}</td><td>
105: * Prints the source and target points, their transformed points, and the distance between
106: * them.</td></tr>
107: *
108: * <tr><td nowrap valign="top">{@code exit}</td><td>
109: * Quit the console.</td></tr>
110: * </table>
111: *
112: * @since 2.1
113: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/Console.java $
114: * @version $Id: Console.java 25050 2007-04-06 00:41:49Z jgarnett $
115: * @author Martin Desruisseaux
116: */
117: public class Console extends AbstractConsole {
118: /**
119: * The locale for number parser.
120: */
121: private final Locale locale = Locale.US;
122:
123: /**
124: * The number format to use for reading coordinate points.
125: */
126: private final NumberFormat numberFormat = NumberFormat
127: .getNumberInstance(locale);
128:
129: /**
130: * The number separator in vectors. Usually {@code ,}, but could
131: * also be {@code ;} if the coma is already used as the decimal
132: * separator.
133: */
134: private final String numberSeparator;
135:
136: /**
137: * The coordinate operation factory to use.
138: */
139: private final CoordinateOperationFactory factory = ReferencingFactoryFinder
140: .getCoordinateOperationFactory(null);
141:
142: /**
143: * The source and target CRS, or {@code null} if not yet determined.
144: */
145: private CoordinateReferenceSystem sourceCRS, targetCRS;
146:
147: /**
148: * Source and target coordinate points, or {@code null} if not yet determined.
149: */
150: private DirectPosition sourcePosition, targetPosition;
151:
152: /**
153: * The math transform, or {@code null} if not yet determined.
154: */
155: private MathTransform transform;
156:
157: /**
158: * The tolerance value. If non-null, the difference between the computed and the specified
159: * target point will be compared against this tolerance threshold. If it is greater, a message
160: * will be printed.
161: */
162: private double[] tolerance;
163:
164: /**
165: * The last error thats occured while processing an instruction.
166: * Used in order to print the stack trace on request.
167: */
168: private transient Exception lastError;
169:
170: /**
171: * Creates a new console instance using {@linkplain System#in standard input stream},
172: * {@linkplain System#out standard output stream}, {@linkplain System#err error output stream}
173: * and the system default line separator.
174: */
175: public Console() {
176: super (new Preprocessor(new Parser()));
177: numberSeparator = getNumberSeparator(numberFormat);
178: }
179:
180: /**
181: * Creates a new console instance using the specified input stream.
182: *
183: * @param in The input stream.
184: */
185: public Console(final LineNumberReader in) {
186: super (new Preprocessor(new Parser()), in);
187: numberSeparator = getNumberSeparator(numberFormat);
188: }
189:
190: /**
191: * Returns the character to use as a number separator.
192: * As a side effect, this method also adjust the minimum and maximum digits.
193: */
194: private static String getNumberSeparator(
195: final NumberFormat numberFormat) {
196: numberFormat.setGroupingUsed(false);
197: numberFormat.setMinimumFractionDigits(6);
198: numberFormat.setMaximumFractionDigits(6);
199: if (numberFormat instanceof DecimalFormat) {
200: final char decimalSeparator = ((DecimalFormat) numberFormat)
201: .getDecimalFormatSymbols().getDecimalSeparator();
202: if (decimalSeparator == ',') {
203: return ";";
204: }
205: }
206: return ",";
207: }
208:
209: /**
210: * Run the console from the command line. Before to process all instructions
211: * from the {@linkplain System#in standard input stream}, this method first
212: * process the following optional command-line arguments:
213: * <P>
214: * <TABLE CELLPADDING='0' CELLSPACING='0'>
215: * <TR><TD NOWRAP><CODE>-load</CODE> <VAR><filename></VAR></TD>
216: * <TD> Load a definition file before to run instructions from
217: * the standard input stream.</TD></TR>
218: * <TR><TD NOWRAP><CODE>-encoding</CODE> <VAR><code></VAR></TD>
219: * <TD> Set the character encoding.</TD></TR>
220: * <TR><TD NOWRAP><CODE>-locale</CODE> <VAR><language></VAR></TD>
221: * <TD> Set the language for the output (e.g. "fr" for French).</TD></TR>
222: * </TABLE>
223: *
224: * @param args the command line arguments
225: */
226: public static void main(String[] args) {
227: final Arguments arguments = new Arguments(args);
228: final String load = arguments.getOptionalString("-load");
229: final String file = arguments.getOptionalString("-file");
230: args = arguments.getRemainingArguments(0);
231: Locale.setDefault(arguments.locale);
232: final LineNumberReader input;
233: final Console console;
234: /*
235: * The usual way to execute instructions from a file is to redirect the standard input
236: * stream using the standard DOS/Unix syntax (e.g. "< thefile.txt"). However, we also
237: * accept a "-file" argument for the same purpose. It is easier to debug. On DOS system,
238: * it also use the system default encoding instead of the command-line one.
239: */
240: if (file == null) {
241: input = null;
242: console = new Console();
243: } else
244: try {
245: input = new LineNumberReader(new FileReader(file));
246: console = new Console(input);
247: console.setPrompt(null);
248: } catch (IOException exception) {
249: System.err.println(exception.getLocalizedMessage());
250: return;
251: }
252: /*
253: * Load predefined shorcuts. The file must be in the form "name = WKT". An example
254: * of such file is the property file used by the property-based authority factory.
255: */
256: if (load != null)
257: try {
258: final LineNumberReader in = new LineNumberReader(
259: new FileReader(load));
260: try {
261: console.loadDefinitions(in);
262: } catch (ParseException exception) {
263: console.reportError(exception);
264: in.close();
265: return;
266: }
267: in.close();
268: } catch (IOException exception) {
269: console.reportError(exception);
270: return;
271: }
272: /*
273: * Run all instructions and close the stream if it was a file one.
274: */
275: console.run();
276: if (input != null)
277: try {
278: input.close();
279: } catch (IOException exception) {
280: console.reportError(exception);
281: }
282: }
283:
284: /**
285: * Execute the specified instruction.
286: *
287: * @param instruction The instruction to execute.
288: * @throws IOException if an I/O operation failed while writting to the
289: * {@linkplain #out output stream}.
290: * @throws ParseException if a line can't be parsed.
291: * @throws FactoryException If a transform can't be created.
292: * @throws TransformException if a transform failed.
293: */
294: protected void execute(String instruction) throws IOException,
295: ParseException, FactoryException, TransformException {
296: String value = null;
297: int i = instruction.indexOf('=');
298: if (i >= 0) {
299: value = instruction.substring(i + 1).trim();
300: instruction = instruction.substring(0, i).trim();
301: }
302: final StringTokenizer keywords = new StringTokenizer(
303: instruction);
304: if (keywords.hasMoreTokens()) {
305: final String key0 = keywords.nextToken();
306: if (!keywords.hasMoreTokens()) {
307: // -------------------------------
308: // exit
309: // -------------------------------
310: if (key0.equalsIgnoreCase("exit")) {
311: if (value != null) {
312: throw unexpectedArgument("exit");
313: }
314: stop();
315: return;
316: }
317: // -------------------------------
318: // stacktrace
319: // -------------------------------
320: if (key0.equalsIgnoreCase("stacktrace")) {
321: if (value != null) {
322: throw unexpectedArgument("stacktrace");
323: }
324: if (lastError != null) {
325: lastError.printStackTrace(err);
326: }
327: return;
328: }
329: // -------------------------------
330: // transform = <the transform>
331: // -------------------------------
332: if (key0.equalsIgnoreCase("transform")) {
333: transform = (MathTransform) parseObject(value,
334: MathTransform.class);
335: sourceCRS = null;
336: targetCRS = null;
337: return;
338: }
339: } else {
340: final String key1 = keywords.nextToken();
341: if (!keywords.hasMoreTokens()) {
342: // -------------------------------
343: // print definition|crs|points
344: // -------------------------------
345: if (key0.equalsIgnoreCase("print")) {
346: if (value != null) {
347: throw unexpectedArgument("print");
348: }
349: if (key1.equalsIgnoreCase("set")) {
350: printDefinitions();
351: return;
352: }
353: if (key1.equalsIgnoreCase("crs")) {
354: printCRS();
355: return;
356: }
357: if (key1.equalsIgnoreCase("pts")) {
358: printPts();
359: return;
360: }
361: }
362: // -------------------------------
363: // set <name> = <wkt>
364: // -------------------------------
365: if (key0.equalsIgnoreCase("set")) {
366: addDefinition(key1, value);
367: return;
368: }
369: // -------------------------------
370: // test tolerance = <vector>
371: // -------------------------------
372: if (key0.equalsIgnoreCase("test")) {
373: if (key1.equalsIgnoreCase("tolerance")) {
374: tolerance = parseVector(value);
375: return;
376: }
377: }
378: // -------------------------------
379: // source|target crs = <wkt>
380: // -------------------------------
381: if (key1.equalsIgnoreCase("crs")) {
382: if (key0.equalsIgnoreCase("source")) {
383: sourceCRS = (CoordinateReferenceSystem) parseObject(
384: value,
385: CoordinateReferenceSystem.class);
386: transform = null;
387: return;
388: }
389: if (key0.equalsIgnoreCase("target")) {
390: targetCRS = (CoordinateReferenceSystem) parseObject(
391: value,
392: CoordinateReferenceSystem.class);
393: transform = null;
394: return;
395: }
396: }
397: // -------------------------------
398: // source|target pt = <coords>
399: // -------------------------------
400: if (key1.equalsIgnoreCase("pt")) {
401: if (key0.equalsIgnoreCase("source")) {
402: sourcePosition = new GeneralDirectPosition(
403: parseVector(value));
404: return;
405: }
406: if (key0.equalsIgnoreCase("target")) {
407: targetPosition = new GeneralDirectPosition(
408: parseVector(value));
409: if (tolerance != null
410: && sourcePosition != null) {
411: update();
412: if (transform != null) {
413: test();
414: }
415: }
416: return;
417: }
418: }
419: }
420: }
421: }
422: throw new ParseException(Errors.format(
423: ErrorKeys.ILLEGAL_INSTRUCTION_$1, instruction), 0);
424: }
425:
426: /**
427: * Executes the "{@code print crs}" instruction.
428: */
429: private void printCRS() throws FactoryException, IOException {
430: final Locale locale = null;
431: final Vocabulary resources = Vocabulary.getResources(locale);
432: final TableWriter table = new TableWriter(out, " \u2502 ");
433: table.setMultiLinesCells(true);
434: char separator = '\u2500';
435: if (sourceCRS != null || targetCRS != null) {
436: table.writeHorizontalSeparator();
437: table.write(resources.getString(VocabularyKeys.SOURCE_CRS));
438: table.nextColumn();
439: table.write(resources.getString(VocabularyKeys.TARGET_CRS));
440: table.nextLine();
441: table.writeHorizontalSeparator();
442: if (sourceCRS != null) {
443: table.write(parser.format(sourceCRS));
444: }
445: table.nextColumn();
446: if (targetCRS != null) {
447: table.write(parser.format(targetCRS));
448: }
449: table.nextLine();
450: separator = '\u2550';
451: }
452: /*
453: * Format the math transform and its inverse, if any.
454: */
455: update();
456: if (transform != null) {
457: table.nextLine(separator);
458: table.write(resources
459: .getString(VocabularyKeys.MATH_TRANSFORM));
460: table.nextColumn();
461: table.write(resources
462: .getString(VocabularyKeys.INVERSE_TRANSFORM));
463: table.nextLine();
464: table.writeHorizontalSeparator();
465: table.write(parser.format(transform));
466: table.nextColumn();
467: try {
468: table.write(parser.format(transform.inverse()));
469: } catch (NoninvertibleTransformException exception) {
470: table.write(exception.getLocalizedMessage());
471: }
472: table.nextLine();
473: }
474: table.writeHorizontalSeparator();
475: table.flush();
476: }
477:
478: /**
479: * Print the source and target point, and their transforms.
480: *
481: * @throws FactoryException if the transform can't be computed.
482: * @throws TransformException if a transform failed.
483: * @throws IOException if an error occured while writing to the output stream.
484: */
485: private void printPts() throws FactoryException,
486: TransformException, IOException {
487: update();
488: DirectPosition transformedSource = null;
489: DirectPosition transformedTarget = null;
490: String targetException = null;
491: if (transform != null) {
492: if (sourcePosition != null) {
493: transformedSource = transform.transform(sourcePosition,
494: null);
495: }
496: if (targetPosition != null)
497: try {
498: transformedTarget = transform.inverse().transform(
499: targetPosition, null);
500: } catch (NoninvertibleTransformException exception) {
501: targetException = exception.getLocalizedMessage();
502: if (sourcePosition != null) {
503: final GeneralDirectPosition p;
504: transformedTarget = p = new GeneralDirectPosition(
505: sourcePosition.getDimension());
506: Arrays.fill(p.ordinates, Double.NaN);
507: }
508: }
509: }
510: final Locale locale = null;
511: final Vocabulary resources = Vocabulary.getResources(locale);
512: final TableWriter table = new TableWriter(out, 0);
513: table.setMultiLinesCells(true);
514: table.writeHorizontalSeparator();
515: table.setAlignment(TableWriter.ALIGN_RIGHT);
516: if (sourcePosition != null) {
517: table
518: .write(resources
519: .getLabel(VocabularyKeys.SOURCE_POINT));
520: print(sourcePosition, table);
521: print(transformedSource, table);
522: table.nextLine();
523: }
524: if (targetPosition != null) {
525: table
526: .write(resources
527: .getLabel(VocabularyKeys.TARGET_POINT));
528: print(transformedTarget, table);
529: print(targetPosition, table);
530: table.nextLine();
531: }
532: if (sourceCRS != null && targetCRS != null) {
533: table.write(resources.getLabel(VocabularyKeys.DISTANCE));
534: printDistance(sourceCRS, sourcePosition, transformedTarget,
535: table);
536: printDistance(targetCRS, targetPosition, transformedSource,
537: table);
538: table.nextLine();
539: }
540: table.writeHorizontalSeparator();
541: table.flush();
542: if (targetException != null) {
543: out.write(targetException);
544: out.write(lineSeparator);
545: }
546: }
547:
548: /**
549: * Print the specified point to the specified table.
550: * This helper method is for use by {@link #printPts}.
551: *
552: * @param point The point to print, or {@code null} if none.
553: * @throws IOException if an error occured while writting to the output stream.
554: */
555: private void print(final DirectPosition point,
556: final TableWriter table) throws IOException {
557: if (point != null) {
558: table.nextColumn();
559: table.write(" (");
560: final double[] coords = point.getCoordinates();
561: for (int i = 0; i < coords.length; i++) {
562: if (i != 0) {
563: table.write(", ");
564: }
565: table.nextColumn();
566: table.write(numberFormat.format(coords[i]));
567: }
568: table.write(')');
569: }
570: }
571:
572: /**
573: * Print the distance between two points using the specified CRS.
574: */
575: private void printDistance(final CoordinateReferenceSystem crs,
576: final DirectPosition position1,
577: final DirectPosition position2, final TableWriter table)
578: throws IOException {
579: if (position1 == null) {
580: // Note: 'position2' is checked below, *after* blank columns insertion.
581: return;
582: }
583: for (int i = crs.getCoordinateSystem().getDimension(); --i >= 0;) {
584: table.nextColumn();
585: }
586: if (position2 != null) {
587: if (crs instanceof AbstractCRS)
588: try {
589: final Measure distance;
590: distance = ((AbstractCRS) crs).distance(position1
591: .getCoordinates(), position2
592: .getCoordinates());
593: table.setAlignment(TableWriter.ALIGN_RIGHT);
594: table.write(numberFormat.format(distance
595: .doubleValue()));
596: table.write(" ");
597: table.nextColumn();
598: table.write(String.valueOf(distance.getUnit()));
599: table.setAlignment(TableWriter.ALIGN_LEFT);
600: return;
601: } catch (UnsupportedOperationException ignore) {
602: /*
603: * Underlying CRS do not supports distance computation.
604: * Left the column blank.
605: */
606: }
607: }
608: table.nextColumn();
609: }
610:
611: ///////////////////////////////////////////////////////////
612: //////// ////////
613: //////// H E L P E R M E T H O D S ////////
614: //////// ////////
615: ///////////////////////////////////////////////////////////
616:
617: /**
618: * Invoked automatically when the {@code target pt} instruction were executed and a
619: * {@code test tolerance} were previously set. The default implementation compares
620: * the transformed source point with the expected target point. If a mismatch greater than
621: * the tolerance error is found, an exception is thrown. Subclasses may overrides this
622: * method in order to performs more tests.
623: *
624: * @throws TransformException if the source point can't be transformed, or a mistmatch is found.
625: * @throws MismatchedDimensionException if the transformed source point doesn't have the
626: * expected dimension.
627: */
628: protected void test() throws TransformException,
629: MismatchedDimensionException {
630: final DirectPosition transformedSource = transform.transform(
631: sourcePosition, null);
632: final int sourceDim = transformedSource.getDimension();
633: final int targetDim = targetPosition.getDimension();
634: if (sourceDim != targetDim) {
635: throw new MismatchedDimensionException(Errors.format(
636: ErrorKeys.MISMATCHED_DIMENSION_$2, new Integer(
637: sourceDim), new Integer(targetDim)));
638: }
639: for (int i = 0; i < sourceDim; i++) {
640: // Use '!' for catching NaN.
641: if (!(Math.abs(transformedSource.getOrdinate(i)
642: - targetPosition.getOrdinate(i)) <= tolerance[Math
643: .min(i, tolerance.length - 1)])) {
644: throw new TransformException(Errors
645: .format(ErrorKeys.UNEXPECTED_TRANSFORM_RESULT));
646: }
647: }
648: }
649:
650: /**
651: * Check if the specified string start and end with the specified delimitors,
652: * and returns the string without the delimitors.
653: *
654: * @param text The string to check.
655: * @param start The delimitor required at the string begining.
656: * @param end The delimitor required at the string end.
657: */
658: private static String removeDelimitors(String text,
659: final char start, final char end) {
660: text = text.trim();
661: final int endPos = text.length() - 1;
662: if (endPos >= 1) {
663: if (text.charAt(0) == start && text.charAt(endPos) == end) {
664: text = text.substring(1, endPos).trim();
665: }
666: }
667: return text;
668: }
669:
670: /**
671: * Parse a vector of values. Vectors are used for coordinate points.
672: * Example:
673: * <pre>
674: * (46.69439222, 13.91405611, 41.21)
675: * </pre>
676: *
677: * @param text The vector to parse.
678: * @return The vector as floating point numbers.
679: * @throws ParseException if a number can't be parsed.
680: */
681: private double[] parseVector(String text) throws ParseException {
682: text = removeDelimitors(text, '(', ')');
683: final StringTokenizer st = new StringTokenizer(text,
684: numberSeparator);
685: final double[] values = new double[st.countTokens()];
686: for (int i = 0; i < values.length; i++) {
687: // Note: we need to convert the number to upper-case because
688: // NumberParser seems to accepts "1E-10" but not "1e-10".
689: final String token = st.nextToken().trim().toUpperCase(
690: locale);
691: final ParsePosition position = new ParsePosition(0);
692: final Number result = numberFormat.parse(token, position);
693: if (position.getIndex() != token.length()) {
694: throw new ParseException(Errors.format(
695: ErrorKeys.UNPARSABLE_NUMBER_$1, token),
696: position.getErrorIndex());
697: }
698: values[i] = result.doubleValue();
699: }
700: return values;
701: }
702:
703: /**
704: * Update the internal state after a change, before to apply transformation.
705: * The most important change is to update the math transform, if needed.
706: */
707: private void update() throws FactoryException {
708: if (transform == null && sourceCRS != null && targetCRS != null) {
709: transform = factory.createOperation(sourceCRS, targetCRS)
710: .getMathTransform();
711: }
712: }
713:
714: /**
715: * Constructs an exception saying that an argument was unexpected.
716: *
717: * @param instruction The instruction name.
718: * @return The exception to throws.
719: */
720: private static ParseException unexpectedArgument(
721: final String instruction) {
722: return new ParseException(Errors.format(
723: ErrorKeys.UNEXPECTED_ARGUMENT_FOR_INSTRUCTION_$1,
724: instruction), 0);
725: }
726:
727: /**
728: * {@inheritDoc}
729: *
730: * @param exception The exception to report.
731: */
732: protected void reportError(final Exception exception) {
733: super.reportError(exception);
734: lastError = exception;
735: }
736: }
|