tim@352: /** tim@352: * tim@352: */ tim@352: package de.intevation.gnv.state.profile.horizontal; tim@352: tim@352: import java.util.ArrayList; tim@352: import java.util.Arrays; tim@352: import java.util.Collection; tim@352: import java.util.List; ingo@365: import java.util.Locale; tim@352: tim@352: import org.apache.log4j.Logger; tim@352: import org.w3c.dom.Node; tim@352: tim@352: import com.vividsolutions.jts.geom.Coordinate; sascha@362: import com.vividsolutions.jts.geom.Point; tim@352: import com.vividsolutions.jts.geom.LineString; sascha@362: tim@352: import com.vividsolutions.jts.io.ParseException; tim@352: import com.vividsolutions.jts.io.WKTReader; tim@352: tim@352: import de.intevation.artifactdatabase.Config; sascha@357: tim@352: import de.intevation.gnv.artifacts.cache.CacheFactory; sascha@357: ingo@365: import de.intevation.gnv.chart.Chart; ingo@365: import de.intevation.gnv.chart.ChartLabels; ingo@365: import de.intevation.gnv.chart.HorizontalCrossProfileChart; ingo@365: sascha@362: import de.intevation.gnv.geobackend.base.DefaultResultDescriptor; sascha@362: import de.intevation.gnv.geobackend.base.ResultDescriptor; sascha@362: import de.intevation.gnv.geobackend.base.DefaultResult; tim@352: import de.intevation.gnv.geobackend.base.Result; sascha@362: sascha@362: import de.intevation.gnv.math.Point2d; sascha@362: import de.intevation.gnv.math.Interpolation2D; sascha@362: tim@352: import de.intevation.gnv.geobackend.base.query.QueryExecutor; tim@352: import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory; tim@352: import de.intevation.gnv.geobackend.base.query.exception.QueryException; tim@352: import de.intevation.gnv.geobackend.sde.datasources.ResultSet; sascha@357: sascha@362: tim@352: import de.intevation.gnv.utils.IndexBuffer; sascha@362: import de.intevation.gnv.utils.DistanceCalculator; tim@352: sascha@357: import de.intevation.gnv.math.LinearFunction; sascha@362: import de.intevation.gnv.math.LinearMetrics; sascha@357: sascha@357: import org.apache.commons.math.optimization.fitting.CurveFitter; sascha@357: sascha@357: import org.apache.commons.math.optimization.general.GaussNewtonOptimizer; sascha@357: import org.apache.commons.math.optimization.OptimizationException; sascha@357: sascha@357: import org.apache.commons.math.FunctionEvaluationException; sascha@357: ingo@365: import org.jfree.chart.ChartTheme; ingo@365: tim@352: /** tim@352: * @author Tim Englich tim@352: * tim@352: */ tim@352: public class HorizontalProfileMeshCrossOutputState extends tim@352: HorizontalProfileOutputState { tim@352: tim@352: /** tim@352: * tim@352: */ tim@352: private static final long serialVersionUID = 2205958041745637263L; tim@352: tim@352: /** tim@352: * the logger, used to log exceptions and additonaly information tim@352: */ tim@352: private static Logger log = tim@352: Logger.getLogger(HorizontalProfileMeshCrossOutputState.class); tim@352: tim@352: tim@352: private String ijkQueryID = null; tim@352: tim@352: /** tim@352: * Constructor tim@352: */ tim@352: public HorizontalProfileMeshCrossOutputState() { tim@352: super(); tim@352: } tim@352: tim@352: /** tim@352: * @see de.intevation.gnv.state.timeseries.TimeSeriesOutputState#setup(org.w3c.dom.Node) tim@352: */ tim@352: @Override tim@352: public void setup(Node configuration) { tim@352: super.setup(configuration); tim@352: this.ijkQueryID = Config.getStringXPath(configuration,"queryID-ijk"); tim@352: tim@352: } tim@352: ingo@365: ingo@365: @Override ingo@365: protected Chart getChart( ingo@365: ChartLabels chartLables, ingo@365: ChartTheme theme, ingo@365: Collection parameters, ingo@365: Collection measurements, ingo@365: Collection dates, ingo@365: Collection result, ingo@365: Locale locale, ingo@365: String uuid, ingo@365: boolean linesVisible, ingo@365: boolean shapesVisible ingo@365: ) { ingo@365: Chart chart = null; ingo@365: if (CACHE_CHART) { ingo@365: log.info("Try to get horizontalprofilemeshcross chart from cache."); ingo@365: chart = (Chart) getChartFromCache(uuid); ingo@365: } ingo@365: ingo@365: if (chart != null) ingo@365: return chart; ingo@365: ingo@365: log.info("Chart not in cache yet."); ingo@365: chart = new HorizontalCrossProfileChart( ingo@365: chartLables, ingo@365: theme, ingo@365: parameters, ingo@365: measurements, ingo@365: dates, ingo@365: result, ingo@365: null, ingo@365: locale, ingo@365: linesVisible, ingo@365: shapesVisible ingo@365: ); ingo@365: chart.generateChart(); ingo@365: ingo@365: if (CACHE_CHART) { ingo@365: log.info("Put chart into cache."); ingo@365: purifyChart(chart, uuid); ingo@365: } ingo@365: ingo@365: return chart; ingo@365: } ingo@365: tim@352: @Override tim@352: protected Collection getChartResult(String uuid) { ingo@365: log.debug("HorizontalProfileMeshCrossOutputState.getChartResult"); tim@352: Collection result = null; tim@352: if (CacheFactory.getInstance().isInitialized()) { tim@352: String key = uuid + super.getID(); tim@352: log.debug("Hash for Queryelements: " + key); tim@352: net.sf.ehcache.Element value = CacheFactory.getInstance().getCache().get(key); tim@352: if (value != null) { tim@352: result = (Collection) (value.getObjectValue()); tim@352: }else{ tim@352: tim@352: if (this.inputData.containsKey("mesh_linestring")){ tim@352: tim@352: try { sascha@357: // 1. IJK Anfragen für Stuetzpunkte im Netz ausführen. tim@352: LineString ls = (LineString)new WKTReader().read(this.inputData tim@352: .get("mesh_linestring") tim@352: .getValue()); tim@352: Coordinate[] coords = ls.getCoordinates(); tim@352: tim@352: List points = new ArrayList(coords.length); tim@352: tim@352: String meshid = this.inputData.get("meshid").getValue(); tim@352: QueryExecutor queryExecutor = QueryExecutorFactory tim@352: .getInstance() tim@352: .getQueryExecutor(); sascha@357: sascha@357: ArrayList missingPoints = new ArrayList(); sascha@357: ingo@365: String additionWhere = "FEATUREID=FEATUREID"; sascha@357: sascha@357: for (int i = 0; i < coords.length; i++) { sascha@357: ingo@365: String wkt = toWKT(coords[i]); tim@352: tim@352: result = queryExecutor.executeQuery(this.ijkQueryID, tim@352: new String[]{meshid,wkt}); tim@352: if (!result.isEmpty()){ tim@352: Result resultValue = result.iterator().next(); tim@352: int iPos = resultValue.getInteger(1); tim@352: int jPos = resultValue.getInteger(0); tim@352: log.debug("Found Pos "+iPos+"/"+jPos +" for "+wkt); tim@352: points.add(i, new java.awt.Point(iPos,jPos)); tim@352: }else{ tim@352: log.debug("No i/j Pos found for "+wkt); sascha@357: missingPoints.add(new Object [] { Integer.valueOf(i), coords[i] }); tim@352: points.add(i, null); tim@352: // Special Case no i,j found for Coordinate tim@352: } tim@352: } sascha@357: sascha@357: if (missingPoints.size() == coords.length) { sascha@357: log.debug("cannot create index buffer"); sascha@357: } sascha@357: else { // generate index filter sascha@357: boolean remainsMissingPoints = !missingPoints.isEmpty(); sascha@357: sascha@357: if (remainsMissingPoints) { sascha@357: // try to guess the missing (i, j) sascha@357: CurveFitter iFitter = new CurveFitter(new GaussNewtonOptimizer(true)); sascha@357: CurveFitter jFitter = new CurveFitter(new GaussNewtonOptimizer(true)); sascha@357: sascha@357: for (int i = 0, N = points.size(); i < N; ++i) { sascha@357: java.awt.Point p = (java.awt.Point)points.get(i); sascha@357: if (p != null) { sascha@357: Coordinate coord = coords[i]; sascha@357: iFitter.addObservedPoint(coord.x, p.x); sascha@357: jFitter.addObservedPoint(coord.y, p.y); sascha@357: } sascha@357: } sascha@357: try { sascha@357: // XXX: Assumption: (i, j) are created by componentwise linear function. sascha@357: // This is surely not correct because (x, y) are in a ellipsoid projection. sascha@357: // TODO: use ellipsoid functions and fit with Levenberg Marquardt. sascha@357: double [] iParams = iFitter.fit( sascha@357: LinearFunction.INSTANCE, new double [] { 1d, 1d }); sascha@357: sascha@357: double [] jParams = jFitter.fit( sascha@357: LinearFunction.INSTANCE, new double [] { 1d, 1d }); sascha@357: sascha@357: for (int i = missingPoints.size()-1; i >= 0; --i) { sascha@357: Object [] a = (Object [])missingPoints.get(i); sascha@357: Coordinate coord = (Coordinate)a[1]; sascha@357: int pi = (int)Math.round(iParams[0]*coord.x + iParams[1]); sascha@357: int pj = (int)Math.round(jParams[0]*coord.y + jParams[1]); sascha@357: points.set( sascha@357: ((Integer)a[0]).intValue(), sascha@357: new java.awt.Point(pi, pj)); sascha@357: } sascha@357: sascha@357: remainsMissingPoints = false; // we filled the gaps sascha@357: } sascha@357: catch (FunctionEvaluationException fee) { sascha@357: log.error(fee); sascha@357: } sascha@357: catch (OptimizationException oe) { sascha@357: log.error(oe); sascha@357: } sascha@357: } sascha@357: sascha@357: if (!remainsMissingPoints) { sascha@357: // TODO: Make Tablenames and Columns Configurable sascha@357: IndexBuffer ib = new IndexBuffer( sascha@357: points, sascha@357: "MEDIAN.MESHPOINT.IPOSITION", sascha@357: "MEDIAN.MESHPOINT.JPOSITION" ); sascha@357: additionWhere = ib.toWhereClause(); sascha@357: log.debug("Additional Where Clause = "+additionWhere); sascha@357: // 2. Aus diesen Stuetzpunkten den Resultset generieren. sascha@357: } sascha@357: } // if generate index filter tim@352: tim@352: String[] filterValues = this.generateFilterValuesFromInputData(); tim@352: String[] addedFilterValues = new String[filterValues.length+1]; tim@352: System.arraycopy(filterValues, 0, addedFilterValues, 0, filterValues.length); tim@352: addedFilterValues[filterValues.length] = additionWhere; tim@352: sascha@361: result = process( sascha@362: Arrays.asList(coords), sascha@361: queryExecutor.executeQuery( sascha@361: this.queryID, sascha@361: addedFilterValues)); tim@352: tim@352: } catch (ParseException e) { tim@352: log.error(e,e); tim@352: }catch (QueryException e) { tim@352: log.error(e,e); tim@352: } tim@352: }else{ tim@352: // TODO: definieren was passiert wenn kein linestring vorhanden ist. tim@352: } tim@352: tim@352: if (CacheFactory.getInstance().isInitialized()) { tim@352: CacheFactory.getInstance().getCache().put(new net.sf.ehcache.Element(key, result)); tim@352: } tim@352: tim@352: } tim@352: } tim@352: return result; tim@352: } tim@352: ingo@365: ingo@365: @Override ingo@365: protected String createChartSubtitle(Locale locale, String uuid) { ingo@365: log.debug("create chart subtitle for horizontal crossprofile charts."); ingo@365: String subtitle = createTimePeriod(locale, uuid); ingo@365: ingo@365: return subtitle; ingo@365: } ingo@365: sascha@362: private static final String [] COLUMN_BLACKLIST = { sascha@362: "MEDIAN.MESHPOINT.JPOSITION", sascha@362: "MEDIAN.MESHPOINT.IPOSITION" sascha@362: }; sascha@361: sascha@362: private static final boolean blacklisted(String column) { sascha@362: for (int i = 0; i < COLUMN_BLACKLIST.length; ++i) { sascha@362: if (COLUMN_BLACKLIST.equals(column)) { sascha@362: return true; sascha@362: } sascha@362: } sascha@362: return false; sascha@362: } sascha@361: sascha@362: private static boolean different(Result a, Result b, int [] indices) { sascha@362: for (int i = 0; i < indices.length; ++i) { ingo@365: String oa = a.getString(indices[i]); ingo@365: String ob = b.getString(indices[i]); ingo@365: ingo@365: if (oa == null && ob == null) { ingo@365: continue; ingo@365: } ingo@365: ingo@365: if (oa == null || ob == null) { ingo@365: return true; ingo@365: } ingo@365: ingo@365: if (!oa.equals(ob)) { ingo@365: log.debug("+++++++++++++++ differs ++++++++++++++"); ingo@365: log.debug(" " + oa + " != " + ob); sascha@362: return true; sascha@362: } sascha@362: } sascha@362: return false; sascha@362: } sascha@362: sascha@362: private static final String [] DIFF_COLUMS = { sascha@362: "GROUP1", sascha@362: "GROUP2", sascha@362: "GROUP3" sascha@362: }; sascha@362: sascha@362: public static final class SectionHandler sascha@362: implements Interpolation2D.Consumer sascha@362: { sascha@362: private ArrayList points; sascha@362: private List path; sascha@362: private Collection output; sascha@362: private Result prototyp; sascha@362: private ResultDescriptor descriptor; sascha@362: sascha@362: public SectionHandler() { sascha@362: } sascha@362: sascha@362: public SectionHandler( sascha@362: List path, sascha@362: Collection output, sascha@362: ResultDescriptor descriptor sascha@362: ) { sascha@362: this.path = path; sascha@362: this.output = output; sascha@362: this.descriptor = descriptor; ingo@365: points = new ArrayList(); sascha@362: } sascha@362: sascha@362: public void finish() { sascha@362: if (!points.isEmpty()) { sascha@362: double distance = toKM( sascha@362: DistanceCalculator.calculateDistance(path)); ingo@365: ingo@365: if (distance > EPSILON) { sascha@362: ingo@365: Interpolation2D.interpolate( ingo@365: path, ingo@365: points, ingo@365: 0d, ingo@365: distance, ingo@365: INTERPOLATION_STEPS, ingo@365: LinearMetrics.INSTANCE, ingo@365: this); ingo@365: } sascha@362: sascha@362: points.clear(); sascha@362: } sascha@362: } sascha@362: sascha@362: public void setPrototyp(Result prototyp) { sascha@362: this.prototyp = prototyp; sascha@362: } sascha@362: sascha@362: public void handle(Result result) { sascha@362: Coordinate coordinate = sascha@362: toCoordinate(result.getString("SHAPE")); sascha@362: double value = result.getDouble("YORDINATE"); sascha@362: int iPos = result.getInteger("MEDIAN.MESHPOINT.JPOSITION"); sascha@362: int jPos = result.getInteger("MEDIAN.MESHPOINT.JPOSITION"); sascha@362: Point2d p = new Point2d( sascha@362: coordinate.x, sascha@362: coordinate.y, sascha@362: value, sascha@362: iPos, jPos); sascha@362: points.add(p); sascha@362: } sascha@362: sascha@362: public void interpolated(Coordinate coordinate) { sascha@362: DefaultResult result = new DefaultResult(descriptor); sascha@362: ResultDescriptor pd = prototyp.getResultDescriptor(); sascha@362: sascha@362: int pcolums = pd.getColumnCount(); sascha@362: for (int i = 0, j = 0; i < pcolums; ++i) { sascha@362: String colname = pd.getColumnName(i); sascha@362: if (blacklisted(colname)) { sascha@362: continue; sascha@362: } sascha@362: if (colname.equals("SHAPE")) { sascha@362: result.addColumnValue(j, toWKT(coordinate)); sascha@362: } sascha@362: else if (colname.equals("YORDINATE")) { sascha@362: result.addColumnValue(j, Double.valueOf(coordinate.z)); sascha@362: } ingo@365: else { ingo@365: result.addColumnValue(j, prototyp.getObject(i)); ingo@365: } sascha@362: ++j; sascha@362: } sascha@362: output.add(result); sascha@362: } sascha@362: } // class SectionHandler sascha@362: sascha@362: public static final double NAUTICAL_MILE = 1852.216d; sascha@362: public static final double KILOMETER = 1000d; sascha@362: sascha@362: public static final double toKM(double distance) { sascha@362: return (distance * NAUTICAL_MILE) / KILOMETER; sascha@362: } sascha@362: ingo@365: public static final double EPSILON = 1e-5d; ingo@365: public static final int INTERPOLATION_STEPS = ingo@365: Integer.getInteger("interpolation.steps", 500).intValue(); sascha@362: sascha@362: public static Coordinate toCoordinate(String shape) { sascha@362: try { sascha@362: return ((Point)(new WKTReader().read(shape))).getCoordinate(); sascha@362: } sascha@362: catch (ParseException pe) { sascha@362: log.error(pe); sascha@362: } sascha@362: return null; sascha@362: } sascha@362: sascha@362: public static String toWKT(Coordinate coordinate) { sascha@362: StringBuilder sb = new StringBuilder("POINT("); sascha@362: sb.append(coordinate.x) sascha@362: .append(' ') sascha@362: .append(coordinate.y) sascha@362: .append(')'); sascha@362: return sb.toString(); sascha@362: } sascha@362: sascha@362: protected Collection process( sascha@362: List path, sascha@362: Collection input sascha@362: ) { ingo@365: log.debug("------ number of points before processing: " + input.size()); sascha@362: ArrayList output = new ArrayList(); sascha@362: sascha@362: sascha@362: Result last = null; sascha@362: sascha@362: int [] diffColums = null; sascha@362: sascha@362: SectionHandler sectionHandler = null; sascha@362: sascha@362: for (Result result: input) { sascha@362: sascha@362: if (sectionHandler == null) { sascha@362: sascha@362: ResultDescriptor rd = result.getResultDescriptor(); sascha@362: diffColums = rd.getColumnIndices(DIFF_COLUMS); sascha@362: int columns = rd.getColumnCount(); sascha@362: sascha@362: DefaultResultDescriptor resultDescriptor = sascha@362: new DefaultResultDescriptor(); sascha@362: sascha@362: for (int j = 0; j < columns; ++j) { sascha@362: String columnName = rd.getColumnName(j); sascha@362: if (!blacklisted(columnName)) { sascha@362: resultDescriptor.addColumn( sascha@362: columnName, sascha@362: rd.getColumnClassName(j)); sascha@362: } sascha@362: } sascha@362: sascha@362: sectionHandler = new SectionHandler( sascha@362: path, sascha@362: output, sascha@362: resultDescriptor); sascha@362: sascha@362: sectionHandler.setPrototyp(result); sascha@362: } sascha@362: sascha@362: if (last != null && different(last, result, diffColums)) { sascha@362: sectionHandler.finish(); sascha@362: sectionHandler.setPrototyp(result); sascha@362: } sascha@362: sascha@362: sectionHandler.handle(result); sascha@362: sascha@362: last = result; sascha@362: } sascha@362: sascha@362: if (sectionHandler != null) { sascha@362: sectionHandler.finish(); sascha@362: } sascha@362: ingo@365: log.debug("------ number of points after processing: " + output.size()); ingo@365: sascha@362: return output; sascha@361: } tim@352: }