/* * Geotools 2 - OpenSource mapping toolkit * (C) 2003, Geotools Project Managment Committee (PMC) * (C) 2002, Institut de Recherche pour le Développement * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.geotools.cs; // J2SE dependencies import java.text.FieldPosition; import java.text.ParseException; import java.text.ParsePosition; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import javax.media.jai.ParameterList; import org.geotools.resources.DescriptorNaming; import org.geotools.resources.WKTElement; import org.geotools.resources.WKTFormat; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.units.Unit; import org.opengis.referencing.FactoryException; /** * Parser for Well Know Text (WKT). * Instances of this class are thread-safe. * * @source $URL$ * @version $Id$ * @author Remi Eve * @author Martin Desruisseaux * * @deprecated Replaced by {@link org.geotools.referencing.wkt.Parser}. */ final class WKTParser extends WKTFormat { /** * The factory to use for creating coordinate systems. */ private CoordinateSystemFactory factory; /** * Construct a parser for the specified locale. * * @param local The locale for parsing and formatting numbers. * @param factory The factory for constructing coordinate systems. */ public WKTParser(final Locale locale, final CoordinateSystemFactory factory) { super(locale); this.factory = factory; } /** * Parses an optional "AUTHORITY" element. * This element has the following pattern: * *
* AUTHORITY["", ""] *
* * @param parent The parent element. * @param parentName The name of the parent object being parsed. * @return The name with the autority code, or parent * if no "AUTHORITY" element has been found. Never null. */ private static CharSequence parseAuthority(final WKTElement parent, final CharSequence parentName) throws ParseException { final WKTElement element = parent.pullOptionalElement("AUTHORITY"); if (element == null) { return parentName; } final String name = element.pullString("name"); final String code = element.pullString("code"); final InfoProperties.Named info = new InfoProperties.Named(parentName.toString()); info.put("authority", name); info.put("authorityCode", code); element.close(); return info; } /** * Parses an "UNIT" element. * This element has the following pattern: * *
* UNIT["", {,}] *
* * @param parent The parent element. * @param unit The contextual unit. Usually {@link Unit#DEGREE} or {@link Unit#METRE}. * @return The "UNIT" element as an {@link Unit} object. * @throws ParseException if the "UNIT" can't be parsed. */ private static Unit parseUnit(final WKTElement parent, final Unit unit) throws ParseException { WKTElement element = parent.pullElement("UNIT"); CharSequence name = element.pullString("name"); double factor = element.pullDouble("factor"); name = parseAuthority(element, name); element.close(); return unit.scale(factor); } /** * Parses an "AXIS" element. * This element has the following pattern: * *
* AXIS["", NORTH | SOUTH | EAST | WEST | UP | DOWN | OTHER] *
* * @param parent The parent element. * @param required true if the axis is mandatory, * or false if it is optional. * @return The "AXIS" element as a {@link AxisInfo} object, or null * if the axis was not required and there is no axis object. * @throws ParseException if the "AXIS" element can't be parsed. */ private AxisInfo parseAxis(final WKTElement parent, final boolean required) throws ParseException { final WKTElement element; if (required) { element = parent.pullElement("AXIS"); } else { element = parent.pullOptionalElement("AXIS"); if (element == null) { return null; } } final String name = element.pullString ("name"); final WKTElement orientation = element.pullVoidElement("orientation"); element.close(); try { return new AxisInfo(name, AxisOrientation.getEnum(orientation.keyword, locale)); } catch (NoSuchElementException exception) { throw element.parseFailed(exception, Errors.format(ErrorKeys.UNKNOW_TYPE_$1, orientation)); } } /** * Parses a "PRIMEM" element. This element has the following pattern: * *
* PRIMEM["", {,}] *
* * @param parent The parent element. * @param unit The contextual unit. * @return The "PRIMEM" element as a {@link PrimeMeridian} object. * @throws ParseException if the "PRIMEM" element can't be parsed. */ private PrimeMeridian parsePrimem(final WKTElement parent, Unit unit) throws ParseException { WKTElement element = parent.pullElement("PRIMEM"); CharSequence name = element.pullString("name"); double longitude = element.pullDouble("longitude"); name = parseAuthority(element, name); element.close(); try { return factory.createPrimeMeridian(name, unit, longitude); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses an optional "TOWGS84" element. * This element has the following pattern: * *
* TOWGS84[, , , , , , ] *
* * @param parent The parent element. * @return The "TOWGS84" element as a {@link WGS84ConversionInfo} object, * or null if no "TOWGS84" has been found. * @throws ParseException if the "TOWGS84" can't be parsed. */ private static WGS84ConversionInfo parseToWGS84(final WKTElement parent) throws ParseException { final WKTElement element = parent.pullOptionalElement("TOWGS84"); if (element == null) { return null; } final WGS84ConversionInfo info = new WGS84ConversionInfo(); info.dx = element.pullDouble("dx"); info.dy = element.pullDouble("dy"); info.dz = element.pullDouble("dz"); info.ex = element.pullDouble("ex"); info.ey = element.pullDouble("ey"); info.ez = element.pullDouble("ez"); info.ppm = element.pullDouble("ppm"); element.close(); return info; } /** * Parses a "SPHEROID" element. This element has the following pattern: * *
* SPHEROID["", , {,}] *
* * @param parent The parent element. * @return The "SPHEROID" element as an {@link Ellipsoid} object. * @throws ParseException if the "SPHEROID" element can't be parsed. */ private Ellipsoid parseSpheroid(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("SPHEROID"); CharSequence name = element.pullString("name"); double semiMajorAxis = element.pullDouble("semiMajorAxis"); double inverseFlattening = element.pullDouble("inverseFlattening"); name = parseAuthority(element, name); element.close(); if (inverseFlattening == 0) { // Inverse flattening nul is an OGC convention for a sphere. inverseFlattening = Double.POSITIVE_INFINITY; } try { return factory.createFlattenedSphere(name, semiMajorAxis, inverseFlattening, Unit.METRE); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "PROJECTION" element. This element has the following pattern: * *
* PROJECTION["" {,}] *
* * @param parent The parent element. * @param ellipsoid The ellipsoid, or null if none. * @param unit The linear unit of the parent PROJCS element, or null if none. * @return The "PROJECTION" element as a {@link Projection} object. * @throws ParseException if the "PROJECTION" element can't be parsed. */ private Projection parseProjection(final WKTElement parent, final Ellipsoid ellipsoid, final Unit unit) throws ParseException { final WKTElement element = parent.pullElement("PROJECTION"); final String classname = element.pullString("name"); final CharSequence name = parseAuthority(element, classname); element.close(); // Set the list of parameters. NOTE: Parameters are defined in // the parent WKTElement (usually a "PROJCS" element), not in // this "PROJECTION" element. final ParameterList parameters = factory.createProjectionParameterList(classname); WKTElement param; while ((param=parent.pullOptionalElement("PARAMETER")) != null) { String paramName = param.pullString("name"); double paramValue = param.pullDouble("value"); Unit paramUnit = DescriptorNaming.getParameterUnit(paramName); if (unit!=null && paramUnit!=null && paramUnit.canConvert(unit)) { paramValue = paramUnit.convert(paramValue, unit); } parameters.setParameter(paramName, paramValue); } if (ellipsoid != null) { final Unit axisUnit = ellipsoid.getAxisUnit(); parameters.setParameter("semi_major", Unit.METRE.convert(ellipsoid.getSemiMajorAxis(), axisUnit)); parameters.setParameter("semi_minor", Unit.METRE.convert(ellipsoid.getSemiMinorAxis(), axisUnit)); } try { return factory.createProjection(name, classname, parameters); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "DATUM" element. This element has the following pattern: * *
* DATUM["", {,} {,}] *
* * @param parent The parent element. * @return The "DATUM" element as a {@link HorizontalDatum} object. * @throws ParseException if the "DATUM" element can't be parsed. */ private HorizontalDatum parseDatum(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("DATUM"); CharSequence name = element.pullString("name"); Ellipsoid ellipsoid = parseSpheroid(element); WGS84ConversionInfo toWGS84 = parseToWGS84(element); // Optional; may be null. name = parseAuthority(element, name); element.close(); try { return factory.createHorizontalDatum(name, DatumType.GEOCENTRIC, ellipsoid, toWGS84); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "VERT_DATUM" element. This element has the following pattern: * *
* VERT_DATUM["", {,}] *
* * @param parent The parent element. * @return The "VERT_DATUM" element as a {@link VerticalDatum} object. * @throws ParseException if the "VERT_DATUM" element can't be parsed. */ private VerticalDatum parseVertDatum(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("VERT_DATUM"); CharSequence name = element.pullString ("name"); final int datum = element.pullInteger("datum"); name = parseAuthority(element, name); element.close(); final DatumType.Vertical type; try { type = (DatumType.Vertical) DatumType.getEnum(datum); } catch (RuntimeException exception) { // Include 'NoSuchElementException' and 'ClassCastException' throw element.parseFailed(exception, Errors.format(ErrorKeys.UNKNOW_TYPE_$1, new Integer(datum))); } try { return factory.createVerticalDatum(name, type); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "LOCAL_DATUM" element. This element has the following pattern: * *
* LOCAL_DATUM["", {,}] *
* * @param parent The parent element. * @return The "LOCAL_DATUM" element as a {@link LocalDatum} object. * @throws ParseException if the "LOCAL_DATUM" element can't be parsed. */ private LocalDatum parseLocalDatum(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("LOCAL_DATUM"); CharSequence name = element.pullString ("name"); final int datum = element.pullInteger("datum"); name = parseAuthority(element, name); element.close(); final DatumType.Local type; try { type = (DatumType.Local) DatumType.getEnum(datum); } catch (RuntimeException exception) { // Include 'NoSuchElementException' and 'ClassCastException' throw element.parseFailed(exception, Errors.format(ErrorKeys.UNKNOW_TYPE_$1, new Integer(datum))); } try { return factory.createLocalDatum(name, type); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "LOCAL_CS" element. * This element has the following pattern: * *
* LOCAL_CS["", , , , {,}* {,}] *
* * @param parent The parent element. * @return The "LOCAL_CS" element as a {@link LocalCoordinateSystem} object. * @throws ParseException if the "LOCAL_CS" element can't be parsed. */ private LocalCoordinateSystem parseLocalCS(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("LOCAL_CS"); CharSequence name = element.pullString("name"); LocalDatum datum = parseLocalDatum(element); Unit unit = parseUnit(element, Unit.METRE); AxisInfo axis = parseAxis(element, true); List list = new ArrayList(); do { list.add(axis); axis = parseAxis(element, false); } while (axis != null); name = parseAuthority(element, name); element.close(); AxisInfo[] array = (AxisInfo[]) list.toArray(new AxisInfo[list.size()]); try { return factory.createLocalCoordinateSystem(name, datum, unit, array); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "GEOCCS" element. * This element has the following pattern: * *
* GEOCCS["", , , * {, , ,} {,}] *
* * @param parent The parent element. * @return The "GEOCCS" element as a {@link GeocentricCoordinateSystem} object. * @throws ParseException if the "GEOCCS" element can't be parsed. */ private GeocentricCoordinateSystem parseGeoCCS(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("GEOCCS"); CharSequence name = element.pullString("name"); HorizontalDatum datum = parseDatum (element); PrimeMeridian meridian = parsePrimem(element, Unit.DEGREE); Unit unit = parseUnit (element, Unit.METRE); AxisInfo[] axes = new AxisInfo[3]; axes[0] = parseAxis(element, false); if (axes[0] != null) { axes[1] = parseAxis(element, true); axes[2] = parseAxis(element, true); } else { axes = GeocentricCoordinateSystem.DEFAULT_AXIS; } name = parseAuthority(element, name); element.close(); try { return factory.createGeocentricCoordinateSystem(name, unit, datum, meridian, axes); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses an optional "VERT_CS" element. * This element has the following pattern: * *
* VERT_CS["", , , {,} {,}] *
* * @param parent The parent element. * @return The "VERT_CS" element as a {@link VerticalCoordinateSystem} object. * @throws ParseException if the "VERT_CS" element can't be parsed. */ private VerticalCoordinateSystem parseVertCS(final WKTElement parent) throws ParseException { final WKTElement element = parent.pullElement("VERT_CS"); if (element == null) { return null; } CharSequence name = element.pullString("name"); VerticalDatum datum = parseVertDatum(element); Unit unit = parseUnit(element, Unit.METRE); AxisInfo axis = parseAxis(element, false); name = parseAuthority(element, name); element.close(); if (axis == null) { axis = AxisInfo.ALTITUDE; } try { return factory.createVerticalCoordinateSystem(name, datum, unit, axis); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "GEOGCS" element. This element has the following pattern: * *
* GEOGCS["", , , {,} {,}] *
* * @param parent The parent element. * @return The "GEOGCS" element as a {@link GeographicCoordinateSystem} object. * @throws ParseException if the "GEOGCS" element can't be parsed. */ private GeographicCoordinateSystem parseGeoGCS(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("GEOGCS"); CharSequence name = element.pullString("name"); HorizontalDatum datum = parseDatum(element); Unit unit = parseUnit(element, Unit.RADIAN); PrimeMeridian meridian = parsePrimem(element, unit); AxisInfo axis0 = parseAxis(element, false); AxisInfo axis1; if (axis0 != null) { axis1 = parseAxis(element, true); } else { axis0 = AxisInfo.LONGITUDE; axis1 = AxisInfo.LATITUDE; } name = parseAuthority(element, name); element.close(); try { return factory.createGeographicCoordinateSystem(name, unit, datum, meridian, axis0, axis1); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "PROJCS" element. * This element has the following pattern: * *
* PROJCS["", , , {,}*, * {,}{,}] *
* * @param parent The parent element. * @return The "PROJCS" element as a {@link ProjectedCoordinateSystem} object. * @throws ParseException if the "GEOGCS" element can't be parsed. */ private ProjectedCoordinateSystem parseProjCS(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("PROJCS"); CharSequence name = element.pullString("name"); GeographicCoordinateSystem gcs = parseGeoGCS(element); Ellipsoid ellipsoid = gcs.getHorizontalDatum().getEllipsoid(); Unit unit = parseUnit(element, Unit.METRE); Projection projection = parseProjection(element, ellipsoid, unit); AxisInfo axis0 = parseAxis(element, false); AxisInfo axis1; if (axis0 != null) { axis1 = parseAxis(element, true); } else { axis0 = AxisInfo.X; axis1 = AxisInfo.Y; } name = parseAuthority(element, name); element.close(); try { return factory.createProjectedCoordinateSystem(name, gcs, projection, unit, axis0, axis1); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a "COMPD_CS" element. * This element has the following pattern: * *
* COMPD_CS["", , {,}] *
* * @param parent The parent element. * @return The "COMPD_CS" element as a {@link CompoundCoordinateSystem} object. * @throws ParseException if the "COMPD_CS" element can't be parsed. */ private CompoundCoordinateSystem parseCompdCS(final WKTElement parent) throws ParseException { WKTElement element = parent.pullElement("COMPD_CS"); CharSequence name = element.pullString("name"); CoordinateSystem headCS = parseCoordinateSystem(element); CoordinateSystem tailCS = parseCoordinateSystem(element); name = parseAuthority(element, name); element.close(); try { return factory.createCompoundCoordinateSystem(name, headCS, tailCS); } catch (FactoryException exception) { throw element.parseFailed(exception, null); } } /** * Parses a coordinate system element. * * @param parent The parent element. * @return The next element as a {@link CoordinateSystem} object. * @throws ParseException if the next element can't be parsed. */ private CoordinateSystem parseCoordinateSystem(final WKTElement element) throws ParseException { final Object key = element.peek(); if (key instanceof WKTElement) { final String keyword = ((WKTElement) key).keyword.trim().toUpperCase(locale); if ( "GEOGCS".equals(keyword)) return parseGeoGCS (element); if ( "PROJCS".equals(keyword)) return parseProjCS (element); if ( "GEOCCS".equals(keyword)) return parseGeoCCS (element); if ( "VERT_CS".equals(keyword)) return parseVertCS (element); if ("LOCAL_CS".equals(keyword)) return parseLocalCS(element); if ("COMPD_CS".equals(keyword)) return parseCompdCS(element); } throw element.parseFailed(null, Errors.format(ErrorKeys.UNKNOW_TYPE_$1, key)); } /** * Parses the next element in the specified Well Know Text (WKT) tree. * * @param element The element to be parsed. * @return The object. * @throws ParseException if the element can't be parsed. */ protected Object parse(final WKTElement element) throws ParseException { final Object key = element.peek(); if (key instanceof WKTElement) { final String keyword = ((WKTElement) key).keyword.trim().toUpperCase(locale); if ( "AXIS".equals(keyword)) return parseAxis (element, true); if ( "PRIMEM".equals(keyword)) return parsePrimem (element, Unit.DEGREE); if ( "TOWGS84".equals(keyword)) return parseToWGS84 (element); if ( "SPHEROID".equals(keyword)) return parseSpheroid (element); if ( "DATUM".equals(keyword)) return parseDatum (element); if ( "VERT_DATUM".equals(keyword)) return parseVertDatum (element); if ("LOCAL_DATUM".equals(keyword)) return parseLocalDatum(element); } return parseCoordinateSystem(element); } /** * Parses a coordinate system element. * * @param text The text to be parsed. * @return The coordinate system. * @throws ParseException if the string can't be parsed. */ public CoordinateSystem parseCoordinateSystem(final String text) throws ParseException { final WKTElement element = getTree(text, new ParsePosition(0)); final CoordinateSystem cs = parseCoordinateSystem(element); element.close(); return cs; } /** * Format the specified object. Current implementation just append {@link Object#toString}, * since the toString() implementation for most {@link org.geotools.cs.Info} * objects is to returns a WKT. * * @task TODO: Provides pacakge private Info.toString(WKTFormat) implementations. * It would allows us to invoke ((Info)obj).toString(this) here. */ public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) { return toAppendTo.append(obj); } }