tim@352: package de.intevation.gnv.state.profile.horizontal; tim@352: sascha@519: import com.vividsolutions.jts.geom.Coordinate; tim@352: tim@352: import de.intevation.artifactdatabase.Config; sascha@357: sascha@519: import de.intevation.artifacts.CallContext; sascha@519: tim@352: import de.intevation.gnv.artifacts.cache.CacheFactory; sascha@357: sascha@519: import de.intevation.gnv.artifacts.context.GNVArtifactContext; sascha@519: sascha@519: import de.intevation.gnv.chart.Chart; sascha@519: import de.intevation.gnv.chart.ChartLabels; sascha@519: import de.intevation.gnv.chart.HorizontalCrossProfileChart; sascha@519: ingo@423: import de.intevation.gnv.geobackend.base.DefaultResult; ingo@423: import de.intevation.gnv.geobackend.base.DefaultResultDescriptor; ingo@423: import de.intevation.gnv.geobackend.base.Result; ingo@423: import de.intevation.gnv.geobackend.base.ResultDescriptor; sascha@519: ingo@423: import de.intevation.gnv.geobackend.base.query.QueryExecutor; ingo@423: import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory; sascha@519: ingo@423: import de.intevation.gnv.geobackend.base.query.exception.QueryException; sascha@519: ingo@423: import de.intevation.gnv.math.Interpolation2D; ingo@423: import de.intevation.gnv.math.LinearMetrics; ingo@423: import de.intevation.gnv.math.Point2d; ingo@365: sascha@440: import de.intevation.gnv.state.InputData; sascha@440: ingo@423: import de.intevation.gnv.utils.DistanceCalculator; sascha@519: import de.intevation.gnv.utils.StringUtils; ingo@420: import de.intevation.gnv.utils.WKTUtils; sascha@439: sascha@519: import java.util.ArrayList; sascha@519: import java.util.Arrays; sascha@519: import java.util.Collection; sascha@519: import java.util.List; sascha@519: import java.util.Locale; sascha@443: sascha@519: import org.apache.log4j.Logger; sascha@357: ingo@365: import org.jfree.chart.ChartTheme; ingo@365: sascha@519: import org.w3c.dom.Node; ingo@423: tim@352: /** ingo@811: * This OutputState is used for 'Horizontalschnitt' products. ingo@811: * sascha@780: * @author Tim Englich sascha@780: * @author Ingo Weinzierl sascha@780: * @author Sascha L. Teichmann tim@352: */ sascha@528: public class HorizontalProfileMeshCrossOutputState sascha@778: extends HorizontalProfileOutputState sascha@528: { tim@352: sascha@528: public static final boolean USE_INDEX_BUFFER = sascha@528: Boolean.getBoolean("gnv.horizontal.profile.mesh.cross.index.buffer"); sascha@528: tim@352: private static final long serialVersionUID = 2205958041745637263L; sascha@778: tim@352: /** tim@352: * the logger, used to log exceptions and additonaly information tim@352: */ sascha@528: private static Logger log = Logger.getLogger( sascha@528: HorizontalProfileMeshCrossOutputState.class); tim@352: tim@352: private String ijkQueryID = null; sascha@778: tim@352: /** tim@352: * Constructor tim@352: */ tim@352: public HorizontalProfileMeshCrossOutputState() { tim@352: super(); tim@352: } sascha@778: ingo@811: tim@352: @Override tim@352: public void setup(Node configuration) { tim@352: super.setup(configuration); tim@352: this.ijkQueryID = Config.getStringXPath(configuration,"queryID-ijk"); sascha@778: tim@352: } tim@352: ingo@365: ingo@811: /** ingo@811: * This method creates a chart and returns it. ingo@811: * ingo@811: * @param chartLables Labels used to decorate the chart. ingo@811: * @param theme The theme used to adjust the look of the chart. ingo@811: * @param parameters A collection with parameters this chart contains. ingo@811: * @param measurements A collection with measurement this chart contains. ingo@811: * @param dates A collection with dates this chart contains. ingo@811: * @param result The data collection used to be displayed in this chart. ingo@811: * @param locale The Locale used to determine the language. ingo@811: * @param uuid The uuid of the current artifact. ingo@811: * @param linesVisible A boolean property to determine the visibility of ingo@811: * lines connecting two points in a chart (not used in this chart type). ingo@811: * @param shapesVisible A boolean property to determine the visiblity of ingo@811: * datapoints in this chart (not used in this chart type). ingo@811: * @param callContext The CallContext object. ingo@811: * @return a HorizontalCrossProfileChart. ingo@811: */ 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@429: Object result, ingo@365: Locale locale, ingo@365: String uuid, ingo@365: boolean linesVisible, sascha@439: boolean shapesVisible, sascha@439: CallContext callContext ingo@365: ) { ingo@365: Chart chart = null; ingo@365: if (CACHE_CHART) { ingo@365: log.info("Try to get horizontalprofilemeshcross chart from cache."); sascha@439: chart = (Chart) getChartFromCache(uuid, callContext); 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@429: (Collection)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: sascha@443: private static int numSamples(CallContext callContext) { sascha@443: GNVArtifactContext context = sascha@443: (GNVArtifactContext)callContext.globalContext(); sascha@443: Integer samples = (Integer)context.get( sascha@443: GNVArtifactContext.HORIZONTAL_CROSS_SECTION_PROFILE_SAMPLES_KEY); sascha@443: return samples != null sascha@443: ? samples.intValue() sascha@443: : GNVArtifactContext.DEFAULT_HORIZONTAL_CROSS_SECTION_PROFILE_SAMPLES; sascha@443: } sascha@443: tim@352: @Override sascha@439: protected Object getChartResult(String uuid, CallContext callContext) { 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{ ingo@423: sascha@440: InputData meshLine = inputData.get("mesh_linestring"); sascha@440: InputData meshId = inputData.get("meshid"); ingo@423: sascha@440: if (meshLine == null) { sascha@440: log.error("mesh_linestring is not defined"); sascha@440: throw new IllegalStateException("missing mesh_linestring"); sascha@440: } ingo@423: sascha@440: if (meshId == null) { sascha@440: log.error("meshid is not defined"); sascha@440: throw new IllegalStateException("missing meshid"); sascha@440: } ingo@423: sascha@440: Coordinate [] coords = WKTUtils.toCoordinates( sascha@440: meshLine.getValue()); sascha@440: sascha@440: if (coords == null) { sascha@440: throw new IllegalStateException("cannot read coordinates"); sascha@440: } sascha@440: sascha@440: try { sascha@528: String additionWhere = USE_INDEX_BUFFER sascha@528: ? WKTUtils.worldCoordinatesToIndex( sascha@440: coords, sascha@440: result, sascha@440: meshId.getValue(), sascha@528: ijkQueryID) sascha@528: : WKTUtils.TRUE_EXPRESSION; sascha@440: sascha@440: String[] addedFilterValues = StringUtils.append( sascha@440: generateFilterValuesFromInputData(), sascha@440: additionWhere); sascha@440: sascha@440: QueryExecutor queryExecutor = QueryExecutorFactory sascha@440: .getInstance() sascha@440: .getQueryExecutor(); sascha@440: sascha@440: result = process( sascha@440: Arrays.asList(coords), sascha@443: numSamples(callContext), sascha@440: queryExecutor.executeQuery( sascha@440: queryID, sascha@440: addedFilterValues)); sascha@440: } sascha@440: catch (QueryException e) { sascha@440: log.error(e,e); tim@352: } ingo@423: tim@352: if (CacheFactory.getInstance().isInitialized()) { tim@352: CacheFactory.getInstance().getCache().put(new net.sf.ehcache.Element(key, result)); tim@352: } ingo@423: tim@352: } tim@352: } tim@352: return result; tim@352: } tim@352: ingo@365: ingo@811: /** ingo@811: * The chart subtitle created by this method is build up of the timeperiod. ingo@811: * ingo@811: * @param locale The Locale used to adjust the language of the subtitle. ingo@811: * @param uuid The UUID of the current artifact. ingo@811: * @return a timeperiod as string. ingo@811: */ 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@423: ingo@423: ingo@811: /** ingo@811: * Prepares the input data for chart creation. ingo@811: * ingo@811: * @param path The coordinates describing the path the data is processed ingo@811: * for. ingo@811: * @param numSamples Number of samples. ingo@811: * @param input The input data. ingo@811: * @return finalized data ready for chart creation. ingo@811: */ ingo@423: public static Collection process( ingo@423: List path, sascha@443: int numSamples, ingo@423: Collection input ingo@423: ) { sascha@443: boolean debug = log.isDebugEnabled(); sascha@443: sascha@443: if (debug) { sascha@443: log.debug("--- number of points before processing: " + input.size()); sascha@443: log.debug(" number samples: " + numSamples); sascha@443: } sascha@443: ingo@423: ArrayList output = new ArrayList(); ingo@423: ingo@423: Result last = null; ingo@423: ingo@423: int [] diffColums = null; ingo@423: ingo@423: SectionHandler sectionHandler = null; ingo@423: ingo@423: for (Result result: input) { ingo@423: ingo@423: if (sectionHandler == null) { ingo@423: ingo@423: ResultDescriptor rd = result.getResultDescriptor(); ingo@423: diffColums = rd.getColumnIndices(DIFF_COLUMS); ingo@423: int columns = rd.getColumnCount(); ingo@423: ingo@423: DefaultResultDescriptor resultDescriptor = ingo@423: new DefaultResultDescriptor(); ingo@423: ingo@423: for (int j = 0; j < columns; ++j) { ingo@423: String columnName = rd.getColumnName(j); sascha@439: if (!StringUtils.contains(COLUMN_BLACKLIST, columnName)) { ingo@423: resultDescriptor.addColumn( ingo@423: columnName, ingo@423: rd.getColumnClassName(j)); ingo@423: } ingo@423: } ingo@423: ingo@423: sectionHandler = new SectionHandler( ingo@423: path, sascha@443: numSamples, ingo@423: output, ingo@423: resultDescriptor); ingo@423: ingo@423: sectionHandler.setPrototyp(result); ingo@423: } ingo@423: ingo@423: if (last != null && WKTUtils.different(last, result, diffColums)) { ingo@423: sectionHandler.finish(); ingo@423: sectionHandler.setPrototyp(result); ingo@423: } ingo@423: ingo@423: sectionHandler.handle(result); ingo@423: ingo@423: last = result; ingo@423: } ingo@423: ingo@423: if (sectionHandler != null) { ingo@423: sectionHandler.finish(); ingo@423: } ingo@423: sascha@443: if (debug) { sascha@443: log.debug("--- number of points after processing: " + output.size()); sascha@443: } ingo@423: ingo@423: return output; ingo@423: } ingo@423: ingo@423: ingo@423: private static final String [] DIFF_COLUMS = { ingo@423: "GROUP1", ingo@423: "GROUP2", ingo@423: "GROUP3" ingo@423: }; ingo@423: ingo@423: private static final String [] COLUMN_BLACKLIST = { ingo@423: "MEDIAN.MESHPOINT.JPOSITION", ingo@423: "MEDIAN.MESHPOINT.IPOSITION" ingo@423: }; ingo@423: ingo@423: public static final double EPSILON = 1e-5d; ingo@423: ingo@423: public static final class SectionHandler ingo@423: implements Interpolation2D.Consumer ingo@423: { ingo@423: private ArrayList points; ingo@423: private List path; ingo@423: private Collection output; ingo@423: private Result prototyp; ingo@423: private ResultDescriptor descriptor; ingo@423: private boolean lastWasSuccess; sascha@443: private int numSamples; ingo@423: ingo@423: public SectionHandler() { ingo@423: } ingo@423: ingo@423: public SectionHandler( ingo@423: List path, sascha@443: int numSamples, ingo@423: Collection output, ingo@423: ResultDescriptor descriptor ingo@423: ) { ingo@423: this.path = path; sascha@443: this.numSamples = numSamples; ingo@423: this.output = output; ingo@423: this.descriptor = descriptor; ingo@423: points = new ArrayList(); ingo@423: lastWasSuccess = true; ingo@423: } ingo@423: ingo@423: public void finish() { ingo@423: if (!points.isEmpty()) { ingo@423: double distance = WKTUtils.toKM( ingo@423: DistanceCalculator.calculateDistance(path)); ingo@423: ingo@423: if (distance > EPSILON) { ingo@423: ingo@423: Interpolation2D.interpolate( ingo@423: path, ingo@423: points, ingo@423: 0d, ingo@423: distance, sascha@443: numSamples, ingo@423: LinearMetrics.INSTANCE, ingo@423: this); ingo@423: } ingo@423: ingo@423: points.clear(); ingo@423: } ingo@423: lastWasSuccess = true; ingo@423: } ingo@423: ingo@423: public void setPrototyp(Result prototyp) { ingo@423: this.prototyp = prototyp; ingo@423: } ingo@423: ingo@423: public void handle(Result result) { ingo@423: Coordinate coordinate = ingo@423: WKTUtils.toCoordinate(result.getString("SHAPE")); ingo@423: double value = result.getDouble("YORDINATE"); sascha@519: int iPos = result.getInteger("IPOSITION"); sascha@519: int jPos = result.getInteger("JPOSITION"); ingo@423: Point2d p = new Point2d( ingo@423: coordinate.x, ingo@423: coordinate.y, ingo@423: value, ingo@423: iPos, jPos); ingo@423: points.add(p); ingo@423: } ingo@423: ingo@423: public void interpolated(Coordinate coordinate, boolean success) { sascha@425: sascha@425: if (!success && !lastWasSuccess) { sascha@425: // only insert null if last was valid. sascha@425: // This prevents flooding the result set with nulls sascha@425: // if interpolating over a large gap. sascha@425: return; sascha@425: } sascha@425: ingo@423: DefaultResult result = new DefaultResult(descriptor); ingo@423: ResultDescriptor pd = prototyp.getResultDescriptor(); ingo@423: ingo@423: int pcolums = pd.getColumnCount(); ingo@423: for (int i = 0, j = 0; i < pcolums; ++i) { ingo@423: String colname = pd.getColumnName(i); sascha@439: if (StringUtils.contains(COLUMN_BLACKLIST, colname)) { ingo@423: continue; ingo@423: } ingo@423: if (colname.equals("SHAPE")) { ingo@423: result.addColumnValue(j, WKTUtils.toWKT(coordinate)); ingo@423: } ingo@423: else if (colname.equals("YORDINATE")) { sascha@425: result.addColumnValue(j, success sascha@425: ? Double.valueOf(coordinate.z) sascha@425: : null); ingo@423: } ingo@423: else { ingo@423: result.addColumnValue(j, prototyp.getObject(i)); ingo@423: } ingo@423: ++j; ingo@423: } ingo@423: output.add(result); ingo@423: lastWasSuccess = success; ingo@423: } ingo@423: } tim@352: } ingo@811: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :