ingo@420: package de.intevation.gnv.utils;
ingo@420: 
ingo@420: import com.vividsolutions.jts.geom.Coordinate;
sascha@519: import com.vividsolutions.jts.geom.LineString;
ingo@420: import com.vividsolutions.jts.geom.Point;
sascha@471: import com.vividsolutions.jts.geom.Polygon;
sascha@519: 
ingo@420: import com.vividsolutions.jts.io.ParseException;
ingo@420: import com.vividsolutions.jts.io.WKTReader;
ingo@420: 
sascha@519: import de.intevation.gnv.artifacts.ressource.RessourceFactory;
sascha@519: 
ingo@420: import de.intevation.gnv.geobackend.base.Result;
sascha@519: 
ingo@420: import de.intevation.gnv.geobackend.base.query.QueryExecutor;
ingo@420: import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory;
sascha@519: 
ingo@420: import de.intevation.gnv.geobackend.base.query.exception.QueryException;
sascha@440: 
ingo@420: import de.intevation.gnv.math.LinearFunction;
ingo@420: 
ingo@507: import java.text.MessageFormat;
ingo@507: import java.text.NumberFormat;
sascha@519: 
ingo@420: import java.util.ArrayList;
ingo@420: import java.util.Collection;
ingo@420: import java.util.List;
ingo@507: import java.util.Locale;
ingo@420: 
sascha@519: import org.apache.commons.math.FunctionEvaluationException;
sascha@519: 
ingo@420: import org.apache.commons.math.optimization.OptimizationException;
sascha@519: 
ingo@420: import org.apache.commons.math.optimization.fitting.CurveFitter;
sascha@519: 
ingo@420: import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
ingo@420: 
ingo@420: import org.apache.log4j.Logger;
ingo@420: 
ingo@806: /**
ingo@806:  * A helper class which supports some useful methods to work with wkt strings.
ingo@806:  *
ingo@806:  * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a>
ingo@806:  */
ingo@420: public abstract class WKTUtils {
ingo@420: 
ingo@420:     private static Logger log = Logger.getLogger(WKTUtils.class);
ingo@420: 
ingo@420:     public static final double NAUTICAL_MILE = 1852.216d;
ingo@420:     public static final double KILOMETER     = 1000d;
ingo@420: 
sascha@471:     public static final String I_NAME = "MEDIAN.MESHPOINT.IPOSITION";
sascha@471:     public static final String J_NAME = "MEDIAN.MESHPOINT.JPOSITION";
sascha@471: 
sascha@471:     public static final String TRUE_EXPRESSION = "FEATUREID=FEATUREID";
sascha@471: 
ingo@507:     public static final String[] COORDINATE_OUT_FORMAT = {
ingo@507:         "coordinate.template.northeast",
ingo@507:         "coordinate.template.southeast",
ingo@507:         "coordinate.template.northwest",
ingo@507:         "coordinate.template.southwest"
ingo@507:     };
ingo@507: 
ingo@507:     public static final String DEFAULT_COORDINATE_TEMPLATE =
ingo@507:         "{0}\u00b0N {1}'' {2}\u00b0E {3}''";
ingo@507: 
ingo@806:     /**
ingo@806:      * Checks the difference of two <code>Result</code>s.
ingo@806:      *
ingo@806:      * @param a A Result.
ingo@806:      * @param b Another Result.
ingo@806:      * @param indices Indices to be checked.
ingo@806:      * @return true, if <i>a</i> and <i>b</i> are different - otherwise false.
ingo@806:      */
ingo@423:     public static boolean different(Result a, Result b, int [] indices) {
ingo@420:         for (int i = 0; i < indices.length; ++i) {
ingo@420:             String oa = a.getString(indices[i]);
ingo@420:             String ob = b.getString(indices[i]);
ingo@420: 
ingo@420:             if (oa == null && ob == null)  {
ingo@420:                 continue;
ingo@420:             }
ingo@420: 
ingo@420:             if (oa == null || ob == null) {
ingo@420:                 return true;
ingo@420:             }
ingo@420: 
ingo@420:             if (!oa.equals(ob)) {
ingo@420:                 return true;
ingo@420:             }
ingo@420:         }
ingo@420:         return false;
ingo@420:     }
ingo@420: 
ingo@806:     /**
ingo@806:      * Turns a wkt string into a coordinate.
ingo@806:      *
ingo@806:      * @param shape A wkt string.
ingo@806:      * @return wkt string as coordinate.
ingo@806:      */
ingo@420:     public static Coordinate toCoordinate(String shape) {
ingo@420:         try {
sascha@482:             return shape != null
sascha@482:                 ? ((Point)(new WKTReader().read(shape))).getCoordinate()
sascha@482:                 : null;
ingo@420:         }
ingo@420:         catch (ParseException pe) {
ingo@420:             log.error(pe);
ingo@420:         }
sascha@471: 		catch (ClassCastException cce) {
sascha@471: 			log.error("cannot read WKT point", cce);
sascha@471: 		}
ingo@420:         return null;
ingo@420:     }
ingo@420: 
ingo@806:     /**
ingo@806:      * Turns a wkt string into a polygon.
ingo@806:      *
ingo@806:      * @param shape A wkt string.
ingo@806:      * @return wkt string as polygon.
ingo@806:      */
sascha@471:     public static Polygon toPolygon(String shape) {
sascha@471:         try {
sascha@471:             return (Polygon)new WKTReader().read(shape);
sascha@471:         }
sascha@471:         catch (ParseException pe) {
sascha@471:             log.error(pe);
sascha@471:         }
sascha@471: 		catch (ClassCastException cce) {
sascha@471: 			log.error("cannot read WKT polygon", cce);
sascha@471: 		}
sascha@471:         return null;
sascha@471:     }
ingo@420: 
ingo@806:     /**
ingo@806:      * Returns a distance in km.
ingo@806:      *
ingo@806:      * @param distance A distance.
ingo@806:      * @return distance in km.
ingo@806:      */
ingo@420:     public static final double toKM(double distance) {
ingo@420:         return (distance * NAUTICAL_MILE) / KILOMETER;
ingo@420:     }
ingo@420: 
ingo@420: 
ingo@806:     /**
ingo@806:      * Turns a coordinate into a wkt string.
ingo@806:      *
ingo@806:      * @param coordinate A coordinate.
ingo@806:      * @return the coordinate as wkt string.
ingo@806:      */
ingo@420:     public static String toWKT(Coordinate coordinate) {
ingo@420:         StringBuilder sb = new StringBuilder("POINT(");
ingo@420:         sb.append(coordinate.x)
ingo@420:           .append(' ')
ingo@420:           .append(coordinate.y)
ingo@420:           .append(')');
ingo@420:         return sb.toString();
ingo@420:     }
ingo@420: 
ingo@806: 
sascha@471:     public static final String indexBox(
sascha@778:         java.awt.Point a,
sascha@471:         java.awt.Point b,
sascha@471:         String         iName,
sascha@471:         String         jName
sascha@471:     ) {
sascha@471:         int minI = Math.min(a.x, b.x) - 1;
sascha@471:         int maxI = Math.max(a.x, b.x) + 1;
sascha@471:         int minJ = Math.min(a.y, b.y) - 1;
sascha@471:         int maxJ = Math.max(a.y, b.y) + 1;
sascha@471:         StringBuilder sb = new StringBuilder("(")
sascha@471:                           .append(iName).append(" >= ").append(minI)
sascha@471:           .append(" AND ").append(iName).append(" <= ").append(maxI)
sascha@471:           .append(" AND ").append(jName).append(" >= ").append(minJ)
sascha@471:           .append(" AND ").append(jName).append(" <= ").append(maxJ)
sascha@471:           .append(')');
sascha@471:         return sb.toString();
sascha@471:     }
sascha@471: 
sascha@471:     public static final String diagonalBox(List<java.awt.Point> points) {
sascha@471: 
sascha@471:         if (points.get(0) != null && points.get(2) != null) {
sascha@471:             return indexBox(
sascha@471:                 points.get(0), points.get(2),
sascha@471:                 I_NAME,
sascha@471:                 J_NAME);
sascha@471:         }
sascha@471: 
sascha@471:         if (points.get(1) != null && points.get(3) != null) {
sascha@471:             return indexBox(
sascha@471:                 points.get(1), points.get(3),
sascha@471:                 I_NAME,
sascha@471:                 J_NAME);
sascha@471:         }
sascha@471: 
sascha@471:         return null;
sascha@471:     }
sascha@471: 
tim@468:     public static String worldEnvelopeCoordinatesToIndex(
tim@468:          Coordinate []       coords,
tim@468:          String              meshid,
tim@468:          String              ijkQueryID
tim@468:     )
tim@468:     throws QueryException
tim@468:     {
sascha@471:         List<java.awt.Point> points =
sascha@471:             new ArrayList<java.awt.Point>(coords.length);
sascha@471: 
sascha@471:         ArrayList<Object []> missingPoints =
sascha@471:             new ArrayList<Object []>();
sascha@471: 
tim@468:         createIJKPoints(coords, meshid, ijkQueryID, points, missingPoints);
sascha@778: 
sascha@471:         String additionWhere = null;
sascha@471: 
tim@468:         if (missingPoints.size() == coords.length) {
tim@468:             log.debug("cannot create index buffer");
sascha@471:         }
sascha@471:         else {
sascha@471:             additionWhere = diagonalBox(points);
sascha@471: 
sascha@471:             if (additionWhere == null) {
sascha@471:                 // 3 Points are missing or are on one side of the envelope
sascha@471:                 boolean remainsMissingPoints = calculateIJ4MissingPoints(
sascha@471:                     coords, points, missingPoints);
sascha@471: 
sascha@471:                 if (!remainsMissingPoints) {
sascha@471:                     additionWhere = diagonalBox(points);
tim@468:                 }
tim@468:             }
tim@468:         }
sascha@471: 
sascha@471:         return additionWhere != null
sascha@471:             ? additionWhere
sascha@471:             : TRUE_EXPRESSION;
tim@468:     }
sascha@471: 
ingo@423:     public static String worldCoordinatesToIndex(
sascha@440: 		Coordinate []       coords,
sascha@440:         Collection<Result>  result,
sascha@440: 		String              meshid,
sascha@440:         String              ijkQueryID
sascha@440:     )
sascha@440: 	throws QueryException
ingo@420:     {
ingo@420:         List<java.awt.Point> points = new ArrayList<java.awt.Point>(coords.length);
ingo@420: 
tim@468:         ArrayList<Object []> missingPoints = new ArrayList<Object []>();
ingo@420: 
tim@468:         createIJKPoints(coords, meshid, ijkQueryID, points, missingPoints);
sascha@778: 
sascha@471:         String additionWhere = TRUE_EXPRESSION;
sascha@471: 
tim@468:         if (missingPoints.size() == coords.length) {
tim@468:             log.debug("cannot create index buffer");
tim@468:         }
tim@468:         else { // generate index filter
sascha@471:             boolean remainsMissingPoints = calculateIJ4MissingPoints(
sascha@471:                 coords, points, missingPoints);
ingo@420: 
tim@468:             if (!remainsMissingPoints) {
tim@468:                 // TODO: Make Tablenames and Columns Configurable
tim@468:                 IndexBuffer ib = new IndexBuffer(
sascha@778:                     points,
sascha@778:                     I_NAME,
sascha@471:                     J_NAME );
tim@468:                 additionWhere = ib.toWhereClause();
tim@468:                 log.debug("Additional Where Clause = "+additionWhere);
tim@468:             }
tim@468:         } // if generate index filter
ingo@420: 
tim@468:         return additionWhere;
tim@468:     }
tim@468: 
tim@468: 
tim@468:     /**
tim@468:      * @param coords
tim@468:      * @param points
tim@468:      * @param missingPoints
tim@468:      * @return
tim@468:      */
tim@468:     private static boolean calculateIJ4MissingPoints(
sascha@471:         Coordinate []        coords,
sascha@471:         List<java.awt.Point> points,
sascha@471:         ArrayList<Object[]>  missingPoints
sascha@471:     ) {
tim@468:         boolean remainsMissingPoints = !missingPoints.isEmpty();
tim@468: 
tim@468:         if (remainsMissingPoints) {
tim@468:             // try to guess the missing (i, j)
tim@468:             CurveFitter iFitter = new CurveFitter(new GaussNewtonOptimizer(true));
tim@468:             CurveFitter jFitter = new CurveFitter(new GaussNewtonOptimizer(true));
tim@468: 
tim@468:             for (int i = 0, N = points.size(); i < N; ++i) {
tim@468:                 java.awt.Point p = (java.awt.Point)points.get(i);
tim@468:                 if (p != null) {
tim@468:                     Coordinate coord = coords[i];
tim@468:                     iFitter.addObservedPoint(coord.x, p.x);
tim@468:                     jFitter.addObservedPoint(coord.y, p.y);
tim@468:                 }
tim@468:             }
tim@468:             try {
tim@468:                 // XXX: Assumption: (i, j) are created by componentwise linear function.
tim@468:                 // This is surely not correct because (x, y) are in a ellipsoid projection.
tim@468:                 // TODO: use ellipsoid functions and fit with Levenberg Marquardt.
tim@468:                 double [] iParams = iFitter.fit(
tim@468:                     LinearFunction.INSTANCE, new double [] { 1d, 1d });
tim@468: 
tim@468:                 double [] jParams = jFitter.fit(
tim@468:                     LinearFunction.INSTANCE, new double [] { 1d, 1d });
tim@468: 
tim@468:                 for (int i = missingPoints.size()-1; i >= 0; --i) {
tim@468:                     Object [] a = (Object [])missingPoints.get(i);
tim@468:                     Coordinate coord = (Coordinate)a[1];
tim@468:                     int pi = (int)Math.round(iParams[0]*coord.x + iParams[1]);
tim@468:                     int pj = (int)Math.round(jParams[0]*coord.y + jParams[1]);
tim@468:                     points.set(
tim@468:                         ((Integer)a[0]).intValue(),
tim@468:                         new java.awt.Point(pi, pj));
tim@468:                 }
tim@468: 
tim@468:                 remainsMissingPoints = false; // we filled the gaps
tim@468:             }
tim@468:             catch (FunctionEvaluationException fee) {
tim@468:                 log.error(fee);
tim@468:             }
tim@468:             catch (OptimizationException oe) {
tim@468:                 log.error(oe);
tim@468:             }
tim@468:         }
tim@468:         return remainsMissingPoints;
tim@468:     }
tim@468: 
tim@468: 
tim@468:     /**
tim@468:      * @param coords
tim@468:      * @param meshid
tim@468:      * @param ijkQueryID
tim@468:      * @param points
tim@468:      * @param missingPoints
tim@468:      * @throws QueryException
tim@468:      */
sascha@471:     private static void createIJKPoints(
sascha@471:         Coordinate[]         coords,
sascha@471:         String               meshid,
sascha@471:         String               ijkQueryID,
sascha@471:         List<java.awt.Point> points,
sascha@471:         ArrayList<Object []> missingPoints
sascha@471:     )
sascha@778:     throws QueryException
sascha@471:     {
sascha@471:         boolean debug = log.isDebugEnabled();
sascha@471: 
tim@468:         QueryExecutor queryExecutor = QueryExecutorFactory
tim@468:             .getInstance()
tim@468:             .getQueryExecutor();
sascha@778: 
ingo@420:         for (int i = 0; i < coords.length; i++) {
ingo@420: 
ingo@420:             String wkt = toWKT(coords[i]);
ingo@420: 
sascha@471:             Collection<Result> result = queryExecutor.executeQuery(
sascha@471:                 ijkQueryID,
sascha@471:                 new String [] {meshid, wkt});
sascha@471: 
sascha@471:             if (!result.isEmpty()) {
ingo@420:                 Result resultValue = result.iterator().next();
ingo@420:                 int iPos = resultValue.getInteger(1);
ingo@420:                 int jPos = resultValue.getInteger(0);
sascha@471:                 if (debug) {
sascha@471:                     log.debug("Found Pos "+iPos+"/"+jPos +" for "+wkt);
sascha@471:                 }
ingo@420:                 points.add(i, new java.awt.Point(iPos,jPos));
sascha@471:             }
sascha@471:             else {
sascha@471:                 if (debug) {
sascha@471:                     log.debug("No i/j Pos found for "+wkt);
sascha@471:                 }
ingo@420:                 missingPoints.add(new Object [] { Integer.valueOf(i), coords[i] });
ingo@420:                 points.add(i, null);
ingo@420:                 // Special Case no i,j found for Coordinate
ingo@420:             }
ingo@420:         }
ingo@420:     }
sascha@440: 
sascha@440: 	public static Coordinate [] toCoordinates(String wkt) {
sascha@440: 		try {
sascha@440: 			LineString ls = (LineString)new WKTReader().read(wkt);
sascha@440: 			return ls.getCoordinates();
sascha@440: 		}
sascha@440: 		catch (ParseException pe) {
sascha@440: 			log.error("cannot read WKT line string", pe);
sascha@440: 		}
sascha@440: 		catch (ClassCastException cce) {
sascha@440: 			log.error("cannot read WKT line string", cce);
sascha@440: 		}
sascha@440: 		return null;
sascha@440: 	}
ingo@507: 
ingo@806:     /**
ingo@806:      * Turns a wkt coordinate into a string format using the default locale of
ingo@806:      * the virtual machine.
ingo@806:      *
ingo@806:      * @param wkt A wkt coordinate.
ingo@806:      * @return A formatted coordinate.
ingo@806:      */
ingo@507:     public static String toText(String wkt) {
ingo@507:         return toText(Locale.getDefault(), wkt);
ingo@507:     }
ingo@507: 
ingo@806:     /**
ingo@806:      * Turns a point into a string format using the default locale of the
ingo@806:      * virtual machine.
ingo@806:      *
ingo@806:      * @param p A point.
ingo@806:      * @return A point formatted as string.
ingo@806:      */
ingo@740:     public static String toText(Point p) {
ingo@740:         return toText(Locale.getDefault(), toWKT(p.getCoordinate()));
ingo@740:     }
ingo@740: 
ingo@806:     /**
ingo@806:      * Turns a point into a string format using a given locale.
ingo@806:      *
ingo@806:      * @param locale A locale.
ingo@806:      * @param p A point.
ingo@806:      * @return a point formatted as string.
ingo@806:      */
ingo@740:     public static String toText(Locale locale, Point p) {
ingo@740:         return toText(locale, toWKT(p.getCoordinate()));
ingo@740:     }
ingo@740: 
ingo@806:     /**
ingo@806:      * Turns a wkt coordiante into a formatted text using a given locale.
ingo@806:      *
ingo@806:      * @param locale A locale.
ingo@806:      * @param wkt A wkt coordinate.
ingo@806:      * @return a formatted coordinate.
ingo@806:      */
ingo@507:     public static String toText(Locale locale, String wkt) {
ingo@507:         String formattedCoordinate = null;
ingo@507:         try {
ingo@507:             Point p = (Point)new WKTReader().read(wkt);
ingo@507:             double lat = p.getY();
ingo@507:             double lon =p.getX();
ingo@507: 
ingo@507:             int choice = 0;
ingo@507: 
ingo@507:             if (lat <0 ) {
ingo@507:                 choice += 1;
ingo@507:                 lat=-lat;
ingo@507:             }
ingo@507: 
ingo@507:             if (lon <0 ) {
ingo@507:                 choice += 2;
ingo@507:                 lon=-lon;
ingo@507:             }
ingo@507: 
ingo@507:             RessourceFactory factory = RessourceFactory.getInstance();
ingo@507:             String template = factory.getRessource(
ingo@507:                 locale,
ingo@507:                 COORDINATE_OUT_FORMAT[choice],
ingo@507:                 DEFAULT_COORDINATE_TEMPLATE
ingo@507:             );
ingo@507: 
ingo@507:             NumberFormat minFormatter = NumberFormat.getInstance(locale);
ingo@507:             minFormatter.setMaximumFractionDigits(3);
ingo@507:             minFormatter.setMinimumFractionDigits(3);
ingo@507: 
ingo@507:             String minLat = minFormatter.format(60.*(lat-((int)lat)));
ingo@507:             String minLon = minFormatter.format(60.*(lon-((int)lon)));
ingo@507: 
ingo@507:             NumberFormat degFormatter = NumberFormat.getInstance(locale);
ingo@507:             degFormatter.setMinimumIntegerDigits(2);
ingo@507: 
ingo@507:             String formLat = degFormatter.format((int)lat);
ingo@507:             String formLon = degFormatter.format((int)lon);
ingo@507: 
ingo@507:             MessageFormat formatter = new MessageFormat(template);
ingo@507: 
ingo@507:             Object[] args = {
ingo@507:                 formLat, minLat,
ingo@507:                 formLon, minLon
ingo@507:             };
ingo@507: 
ingo@507:             return formatter.format(args);
ingo@507: 
ingo@507:         }
ingo@507:         catch (ParseException e) {
ingo@507:             log.warn(e,e);
ingo@507:         }
ingo@507: 
ingo@507:         return null;
ingo@507:     }
ingo@420: }
sascha@836: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :