/* Copyright 2013 The jeo project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jeo.proj.wkt;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import io.jeo.proj.Proj;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.Registry;
import org.osgeo.proj4j.datum.Datum;
import org.osgeo.proj4j.datum.Ellipsoid;
import org.osgeo.proj4j.proj.LongLatProjection;
import org.osgeo.proj4j.proj.Projection;
import org.osgeo.proj4j.units.Unit;
import org.osgeo.proj4j.units.Units;
import static org.osgeo.proj4j.parser.Proj4Keyword.*;
public class ProjWKTParser {
static enum Param {
central_meridian(lon_0),
latitude_of_origin(lat_0),
scale_factor(k_0),
false_easting(x_0),
false_northing(y_0);
String proj4;
Param(String proj4) {
this.proj4 = proj4;
}
}
final static String NAME_KEY = "name";
final static String IDENTIFIERS_KEY = "identifiers";
public CoordinateReferenceSystem parse(String wkt) throws ParseException {
return parseCRS(parseTree(wkt));
}
Element parseTree(String wkt) throws ParseException {
return new Element(wkt, new ParsePosition(0));
}
CoordinateReferenceSystem parseCRS(Element e) throws ParseException {
final Object key = e;
if (key instanceof Element) {
final String keyword = ((Element) key).keyword.trim().toUpperCase(e.symbols.locale);
try {
if ( "GEOGCS".equals(keyword)) return parseGeoGCS (e);
if ( "PROJCS".equals(keyword)) return parseProjCS (e);
if ( "GEOCCS".equals(keyword)) return parseGeoCCS (e);
//if ( "VERT_CS".equals(keyword)) return r=parseVertCS (e);
//if ( "LOCAL_CS".equals(keyword)) return r=parseLocalCS (e);
//if ( "COMPD_CS".equals(keyword)) return r=parseCompdCS (e);
//if ("FITTED_CS".equals(keyword)) return r=parseFittedCS(e);
} finally {
// Work around for simulating post-conditions in Java.
//assert isValid(r, keyword) : element;
}
}
throw e.parseFailed(null, String.format(Locale.ROOT,"Type \"%s\" is unknown in this context.", key));
}
CoordinateReferenceSystem parseGeoGCS(Element element) throws ParseException {
//Element element = parent.pullElement("GEOGCS");
String name = element.pullString("name");
Map<String,?> properties = parseAuthority(element, name);
Unit angularUnit = parseUnit (element, Units.RADIANS);
Object meridian = parsePrimem (element, angularUnit);
Datum datum = parseDatum(element, meridian);
//String[] params = new String[]{Proj4Keyword.units, angularUnit.abbreviation};
LongLatProjection proj = new LongLatProjection();
proj.initialize();
return new CoordinateReferenceSystem(name, null, datum, proj);
/*CoordinateSystemAxis axis0 = parseAxis (element, angularUnit, false);
CoordinateSystemAxis axis1;
CoordinateSystemAxis axis2 = null;
try {
if (axis0 != null) {
axis1 = parseAxis(element, angularUnit, true);
if(axis1 != null) {
axis2 = parseAxis(element, SI.METER, false);
}
} else {
// Those default values are part of WKT specification.
axis0 = createAxis(null, "Lon", AxisDirection.EAST, angularUnit);
axis1 = createAxis(null, "Lat", AxisDirection.NORTH, angularUnit);
}
element.close();
EllipsoidalCS ellipsoidalCS;
if(axis2 != null) {
ellipsoidalCS = csFactory.createEllipsoidalCS(properties, axis0, axis1, axis2);
} else {
ellipsoidalCS = csFactory.createEllipsoidalCS(properties, axis0, axis1);
}
return crsFactory.createGeographicCRS(properties, datum,
ellipsoidalCS);
} catch (FactoryException exception) {
throw element.parseFailed(exception, null);
}*/
}
Map<String,Object> parseAuthority(final Element parent, final String name)
throws ParseException {
final boolean isRoot = parent.isRoot();
final Element element = parent.pullOptionalElement("AUTHORITY");
Map<String,Object> properties;
if (element == null) {
if (isRoot) {
properties = new HashMap<String,Object>(4);
properties.put(NAME_KEY, name);
} else {
properties = Collections.singletonMap(NAME_KEY, (Object) name);
}
} else {
final String auth = element.pullString("name");
// the code can be annotation marked but could be a number to
String code = element.pullOptionalString("code");
if (code == null) {
int codeNumber = element.pullInteger("code");
code = String.valueOf(codeNumber);
}
element.close();
//final Citation authority = Citations.fromName(auth);
properties = new HashMap<String,Object>(4);
properties.put(NAME_KEY, auth + ":" + name);
properties.put(IDENTIFIERS_KEY, auth + ":" + code);
}
if (isRoot) {
//properties = alterProperties(properties);
}
return properties;
}
Unit parseUnit(final Element parent, final Unit unit) throws ParseException {
final Element element = parent.pullElement("UNIT");
final String name = element.pullString("name");
final double factor = element.pullDouble("factor");
final Map<String,?> properties = parseAuthority(element, name);
element.close();
if (name != null) {
Unit u = Units.findUnits(name.toLowerCase(Locale.ROOT));
if (u != null) {
return u;
}
}
return (factor != 1) ? times(unit, factor) : unit;
}
Unit times(Unit u, double factor) {
throw new UnsupportedOperationException();
}
Object parsePrimem(final Element parent, final Unit angularUnit)
throws ParseException
{
final Element element = parent.pullElement("PRIMEM");
final String name = element.pullString("name");
final double longitude = element.pullDouble("longitude");
final Map<String,?> properties = parseAuthority(element, name);
element.close();
return null;
}
Datum parseDatum(final Element parent, final Object meridian) throws ParseException {
Element element = parent.pullElement("DATUM");
String name = element.pullString("name");
Ellipsoid ellipsoid = parseSpheroid(element);
double[] toWGS84 = parseToWGS84(element); // Optional; may be
// null.
Map<String, Object> properties = parseAuthority(element, name);
if (true/*ALLOW_ORACLE_SYNTAX*/ && (toWGS84 == null)
&& (element.peek() instanceof Number)) {
toWGS84 = new double[7];
toWGS84[0] = element.pullDouble("dx");
toWGS84[1] = element.pullDouble("dy");
toWGS84[2] = element.pullDouble("dz");
toWGS84[3] = element.pullDouble("ex");
toWGS84[4] = element.pullDouble("ey");
toWGS84[5] = element.pullDouble("ez");
toWGS84[6] = element.pullDouble("ppm");
}
element.close();
return new Datum(name, toWGS84, ellipsoid, name);
}
Ellipsoid parseSpheroid(final Element parent) throws ParseException {
Element element = parent.pullElement("SPHEROID");
String name = element.pullString("name");
double semiMajorAxis = element.pullDouble("semiMajorAxis");
double inverseFlattening = element.pullDouble("inverseFlattening");
Map<String,?> properties = parseAuthority(element, name);
element.close();
if (inverseFlattening == 0) {
// Inverse flattening null is an OGC convention for a sphere.
inverseFlattening = Double.POSITIVE_INFINITY;
}
return new Ellipsoid(name, semiMajorAxis, 0, inverseFlattening, name);
}
double[] parseToWGS84(final Element parent) throws ParseException {
final Element element = parent.pullOptionalElement("TOWGS84");
if (element == null) {
return null;
}
double dx = element.pullDouble("dx");
double dy = element.pullDouble("dy");
double dz = element.pullDouble("dz");
try {
if (element.peek() != null) {
double ex = element.pullDouble("ex");
double ey = element.pullDouble("ey");
double ez = element.pullDouble("ez");
double ppm = element.pullDouble("ppm");
return new double[]{dx, dy, dz, ex, ey, ez, ppm};
}
else {
return new double[]{dx, dy, dz};
}
}
finally {
element.close();
}
}
void parseAxis(Element e) throws ParseException {
e.pullOptionalElement("AXIS");
}
String parseAuthCode(Element e) throws ParseException {
Element a = e.pullOptionalElement("AUTHORITY");
if (a != null) {
return a.pullString("name") + ":" + a.pullString("code");
}
return null;
}
CoordinateReferenceSystem parseProjCS(Element e) throws ParseException {
String authCode = parseAuthCode(e);
if (authCode != null) {
CoordinateReferenceSystem crs = Proj.crs(authCode);
if (crs != null) {
return crs;
}
}
//parse manually
String name = e.pullString("name");
CoordinateReferenceSystem geo = parseGeoGCS(e.pullElement("GEOGCS"));
Projection proj = parseProjection(e);
String[] params = parseParameters(e);
//TODO:
/*
Unit unit = parseUnit(e, Units.METRES);
parseAxis(e);
parseAxis(e);
*/
return new CoordinateReferenceSystem(name, params, geo.getDatum(), proj);
}
Projection parseProjection(Element e) throws ParseException {
Element p = e.pullElement("PROJECTION");
String name = p.pullString("name");
Projection proj = new Registry().getProjection(name);
if (proj == null) {
throw new IllegalArgumentException("Unsupported projection: " + name);
}
return proj;
}
String[] parseParameters(Element e) throws ParseException {
Element p = null;
List<String> params = new ArrayList<String>();
while ((p = e.pullOptionalElement("PARAMETER")) != null) {
String key = p.pullString("name");
Double val = p.pullDouble("value");
Param param = Param.valueOf(key);
if (param == null) {
throw new IllegalArgumentException("Unsupported projection parameter: " + key);
}
params.add(String.format(Locale.ROOT,"%s=%f", param.proj4, val));
}
return params.toArray(new String[params.size()]);
}
CoordinateReferenceSystem parseGeoCCS(Element e) throws ParseException {
throw new UnsupportedOperationException();
}
}