ingo@1115: /* ingo@1115: * Copyright (c) 2010 by Intevation GmbH ingo@1115: * ingo@1115: * This program is free software under the LGPL (>=v2.1) ingo@1115: * Read the file LGPL.txt coming with the software for details ingo@1115: * or visit http://www.gnu.org/licenses/ if it does not exist. ingo@1115: */ ingo@1115: 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 Ingo Weinzierl 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 Results. 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 a and b 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 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 points = sascha@471: new ArrayList(coords.length); sascha@471: sascha@471: ArrayList missingPoints = sascha@471: new ArrayList(); 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, sascha@440: String meshid, sascha@440: String ijkQueryID sascha@440: ) sascha@440: throws QueryException ingo@420: { ingo@420: List points = new ArrayList(coords.length); ingo@420: tim@468: ArrayList missingPoints = new ArrayList(); 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 points, sascha@471: ArrayList 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 points, sascha@471: ArrayList 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 = 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 :