Mercurial > dive4elements > gnv-client
view gnv-artifacts/src/main/java/de/intevation/gnv/state/profile/verticalcrosssection/VerticalCrossSectionOutputState.java @ 1082:3c0fd3c2fd6b
Enabled histogram creation for vectorial horizontalprofiles.
gnv-artifacts/trunk@1184 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Ingo Weinzierl <ingo.weinzierl@intevation.de> |
---|---|
date | Wed, 09 Jun 2010 09:58:38 +0000 |
parents | cc4ec127d666 |
children | 51f3edc9d743 |
line wrap: on
line source
package de.intevation.gnv.state.profile.verticalcrosssection; import au.com.bytecode.opencsv.CSVWriter; import java.awt.Color; import java.awt.Dimension; import java.awt.Paint; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; import org.apache.log4j.Logger; import org.jfree.chart.ChartTheme; import com.vividsolutions.jts.geom.Coordinate; import de.intevation.artifacts.CallContext; import de.intevation.gnv.artifacts.cache.CacheFactory; import de.intevation.gnv.artifacts.context.GNVArtifactContext; import de.intevation.gnv.artifacts.ressource.RessourceFactory; import de.intevation.gnv.chart.Chart; import de.intevation.gnv.chart.ChartLabels; import de.intevation.gnv.chart.VerticalCrossSectionChart; import de.intevation.gnv.exports.DefaultProfile; import de.intevation.gnv.exports.Export; import de.intevation.gnv.exports.VerticalCrossODVExport; import de.intevation.gnv.geobackend.base.Result; import de.intevation.gnv.geobackend.base.ResultDescriptor; import de.intevation.gnv.geobackend.base.query.QueryExecutor; import de.intevation.gnv.geobackend.base.query.QueryExecutorFactory; import de.intevation.gnv.geobackend.base.query.exception.QueryException; import de.intevation.gnv.geobackend.sde.datasources.RasterObject; import de.intevation.gnv.jfreechart.PolygonDataset; import de.intevation.gnv.jfreechart.PolygonSeries; import de.intevation.gnv.math.AttributedXYColumns; import de.intevation.gnv.math.HeightValue; import de.intevation.gnv.math.IJKey; import de.intevation.gnv.math.Interpolation3D; import de.intevation.gnv.math.LinearMetrics; import de.intevation.gnv.math.QueriedXYDepth; import de.intevation.gnv.math.XYColumn; import de.intevation.gnv.raster.Filter; import de.intevation.gnv.raster.IsoAttributeGenerator; import de.intevation.gnv.raster.IsoPolygonSeriesProducer; import de.intevation.gnv.raster.Palette; import de.intevation.gnv.raster.PaletteManager; import de.intevation.gnv.raster.PolygonDatasetProducer; import de.intevation.gnv.raster.Raster; import de.intevation.gnv.raster.Vectorizer; import de.intevation.gnv.state.InputData; import de.intevation.gnv.state.describedata.KeyValueDescibeData; import de.intevation.gnv.state.exception.StateException; import de.intevation.gnv.state.timeseries.TimeSeriesOutputState; import de.intevation.gnv.statistics.Statistics; import de.intevation.gnv.statistics.VerticalCrossSectionStatistics; import de.intevation.gnv.utils.DistanceCalculator; import de.intevation.gnv.utils.StringUtils; import de.intevation.gnv.utils.WKTUtils; /** * @author <a href="mailto:tim.englich@intevation.de">Tim Englich</a> * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a> */ public class VerticalCrossSectionOutputState extends TimeSeriesOutputState { public static final String CHART_TYPE = "verticalcrosssection"; public static final Integer GROUND_FILL_INDEX = Integer.valueOf(-2); public static final boolean USE_INDEX_BUFFER = Boolean.getBoolean("gnv.vertical.cross.section.index.buffer"); public static final String[] ATTRIBUTE_LIST = { "SHAPE", "Z", "YORDINATE", "IPOSITION", "JPOSITION", "KPOSITION" }; private static Logger log = Logger.getLogger( VerticalCrossSectionOutputState.class); private String ijkQueryID = "horizontalprofile_meshpoint_cross_ij"; private String rangeLabel; /** * The UID of this Class */ private static final long serialVersionUID = 3233620652465061860L; /** * Constructor */ public VerticalCrossSectionOutputState() { super(); super.domainLable = "chart.verticalcrosssection.title.xaxis"; this.rangeLabel = "chart.verticalcrosssection.title.yaxis"; } @Override public void initialize(String uuid, CallContext callContext) throws StateException { super.initialize(uuid, callContext); getChartResult(uuid, callContext); } @Override protected ChartLabels createChartLabels(Locale locale, String uuid) { RessourceFactory factory = RessourceFactory.getInstance(); InputData input = inputData.get(parameterValuesName); String parameterName = input.getDescription()[0]; if (parameterName == null) parameterName = "parameterid"; return new ChartLabels( createChartTitle(locale, uuid), createChartSubtitle(locale, uuid), factory.getRessource(locale, domainLable, domainLable), factory.getRessource(locale, rangeLabel, rangeLabel), parameterName ); } @Override protected String createChartSubtitle(Locale locale, String uuid) { InputData data = inputData.get(dateValueName); String date = data.getDescription()[0]; if (date == null) date = "dateid"; RessourceFactory factory = RessourceFactory.getInstance(); String chartType = factory.getRessource( locale, CHART_TYPE, CHART_TYPE); return chartType + ": " + date; } @Override protected Object getChartResult(String uuid, CallContext callContext) { log.debug("VerticalCrossSectionOutputState.getChartResult"); String key = getHash(); CacheFactory factory = CacheFactory.getInstance(); if (factory.isInitialized()) { log.info("Using a cachce - key: " + key); Cache cache = factory.getCache(); Element element = cache.get(key); if (element != null) return element.getObjectValue(); log.debug("No results in cache yet."); Object obj = getData(uuid, callContext); cache.put(new Element(key, obj)); return obj; } else { log.info("Not using a cache."); return getData(uuid, callContext); } } /** * Retrieves the data used to create a VerticalCrossProfileChart. * * @param uuid The UUID of the current artifact. * @param callContext The CallContext object. * @return the data used to create a VerticalCrossProfileChart. */ protected Object getData(String uuid, CallContext callContext) { Collection<Result> result = null; InputData meshLine = inputData.get("mesh_linestring"); InputData meshId = inputData.get("meshid"); if (meshLine == null) { log.error("mesh_linestring is not defined"); throw new IllegalStateException("missing mesh_linestring"); } if (meshId == null) { log.error("meshid is not defined"); throw new IllegalStateException("missing meshid"); } Coordinate [] coords = WKTUtils.toCoordinates( meshLine.getValue()); if (coords == null) { throw new IllegalStateException("cannot read coordinates"); } try { String additionWhere = USE_INDEX_BUFFER ? WKTUtils.worldCoordinatesToIndex( coords, result, meshId.getValue(), ijkQueryID) : WKTUtils.TRUE_EXPRESSION; String[] addedFilterValues = StringUtils.append( generateFilterValuesFromInputData(), additionWhere); QueryExecutor exec = QueryExecutorFactory .getInstance() .getQueryExecutor(); result = exec.executeQuery(queryID, addedFilterValues); } catch (QueryException qe) { log.error(qe, qe); } Object obj = process( Arrays.asList(coords), preProcess(result), callContext); return obj; } @Override protected String getSelectedInputDataName(String uuid, String id) { Collection values = getCollection(id, uuid); if (values != null) { Iterator it = values.iterator(); while (it.hasNext()) { KeyValueDescibeData data = (KeyValueDescibeData) it.next(); if (data.isSelected()) { return data.getValue(); } } } return null; } private static int getGroundInterpolation(CallContext callContext) { GNVArtifactContext context = (GNVArtifactContext)callContext.globalContext(); String interpolation = (String)context.get( GNVArtifactContext.VERTICAL_CROSS_SECTION_GROUND_INTERPOLATION_KEY); return RasterObject.getInterpolationType(interpolation); } private static Dimension getRasterSize(CallContext callContext) { GNVArtifactContext context = (GNVArtifactContext)callContext.globalContext(); Dimension size = (Dimension)context.get( GNVArtifactContext.VERTICAL_CROSS_SECTION_SAMPLES_KEY); return size != null ? size : GNVArtifactContext.DEFAULT_VERTICAL_CROSS_SECTION_SAMPLES; } private static List<Filter.Factory> getFilterFactories( CallContext callContext ) { GNVArtifactContext context = (GNVArtifactContext)callContext.globalContext(); List<Filter.Factory> factories = (List<Filter.Factory>)context.get( GNVArtifactContext.VERTICAL_CROSS_SECTION_FILTER_FACTORIES_KEY); return factories != null ? factories : new ArrayList<Filter.Factory>(); } private static Map<Integer, PaletteManager> getPalettes( CallContext callContext ) { GNVArtifactContext context = (GNVArtifactContext)callContext.globalContext(); Map<Integer, PaletteManager> palettes = (Map<Integer, PaletteManager>)context.get( GNVArtifactContext.PALETTES_KEY); return palettes != null ? palettes : new HashMap<Integer, PaletteManager>(); } private static Paint getGroundFill(CallContext callContext) { GNVArtifactContext context = (GNVArtifactContext)callContext.globalContext(); Paint fill = (Paint)context.get( GNVArtifactContext.VERTICAL_CROSS_SECTION_GROUND_FILL_KEY); return fill != null ? fill : GNVArtifactContext.DEFAULT_VERTICAL_CROSS_SECTION_GROUND_FILL; } public static final double EPSILON = 1e-5d; /** * Finalize the data used for chart generation. Isolines are added, colors * are assigned to polygons and the seabed is added. * * @param path The path which have been inserted while parameterization. * @param columns The data used to be displayed in a 2D chart. * @param callContext The CallContext object. * @return the finalized data ready for chart generation. */ protected Object process( List<Coordinate> path, AttributedXYColumns columns, CallContext callContext ) { Integer parameterId = (Integer)columns.getAttribute("GROUP1"); // XXX: hardcoded if (parameterId == null) { log.error("missing parameter id"); return null; } Map<Integer, PaletteManager> paletteManagers = getPalettes(callContext); PaletteManager paletteManager = paletteManagers.get(parameterId); if (paletteManager == null) { log.error("no palette found for parameter id " + parameterId); return null; } boolean debug = log.isDebugEnabled(); if (debug) { log.debug("using palette '" + paletteManager.getName() + "'"); } Dimension rasterSize = getRasterSize(callContext); List<Filter.Factory> filterFactories = getFilterFactories(callContext); Interpolation3D interpolation = new Interpolation3D(rasterSize); double distance = DistanceCalculator.calculateDistance(path); if (distance < EPSILON) { log.warn("distance too short for interpolation"); return null; } boolean success = interpolation.interpolate( path, columns.getXYColumns(), 0d, distance, LinearMetrics.INSTANCE, new QueriedXYDepth( getGroundInterpolation(callContext))); if (!success) { log.warn("interpolation failed"); return null; } // Do the post processing Raster raster = new Raster( interpolation.getRaster(), rasterSize.width); for (Filter.Factory factory: filterFactories) { raster = factory.create().filter(raster); } if (debug) { log.debug("to indexed raster"); } // scan for regions with base palette Palette basePalette = paletteManager.getBase(); int [] intRaster = raster.toIndexed(basePalette); // produce JFreeChart compatible polygons if (debug) { log.debug("vectorize indexed raster"); } double maxDepth = interpolation.getMaxDepth(); PolygonDatasetProducer pdsp = new PolygonDatasetProducer( 0, 0, distance, maxDepth); int numRegions = new Vectorizer(intRaster, rasterSize.width) .process(pdsp); PolygonDataset pds = pdsp.getPolygonDataset(); // Count number of colors before generating seabed // because its used to determine the number of iso lines. int numColors = pds.getSeriesCount(); if (debug) { log.debug("number of regions: " + numRegions); log.debug("number of colors: " + numColors); } // generate seabed polygon PolygonSeries seabed = OutputHelper.createSeabedPolygon( interpolation, GROUND_FILL_INDEX); if (seabed != null) { pds.addSeries(seabed); } // generate iso lines int numIso; if (numColors < 5) { numIso = 5; } else if (numColors < 10) { numIso = 2; } else { numIso = 0; } Palette isoPalette; if (numIso == 0) { // same palette isoPalette = basePalette; /* intRaster = intRaster; */ } else { isoPalette = paletteManager.getLevel(numIso); intRaster = raster.toIndexed(isoPalette); } IsoPolygonSeriesProducer ipsp = new IsoPolygonSeriesProducer( 0, 0, distance, maxDepth); numRegions = new Vectorizer(false, intRaster, rasterSize.width) .process(ipsp); IsoAttributeGenerator iag = new IsoAttributeGenerator(isoPalette); Collection<PolygonSeries> ps = ipsp.getSeries(iag); ipsp.clear(); if (debug) { log.debug("num of iso regions: " + numRegions); log.debug("num of iso series: " + ps.size()); } pds.addAllSeries(ps); columns.setInterpolation(interpolation); columns.setPolygonDataset(pds); return columns; } /** * Pre-process the data returned by database query. The resulting data is * not ready for chart creation! * * @param results Data returned by database. * @return Pre-processed data which is not ready for chart creation yet. */ protected AttributedXYColumns preProcess(Collection results) { AttributedXYColumns attColumns = new AttributedXYColumns(); Map<IJKey, XYColumn> map = new HashMap<IJKey, XYColumn>(1013); Iterator iter = results.iterator(); int sIdx = -1; int iIdx = -1; int jIdx = -1; int kIdx = -1; int vIdx = -1; int zIdx = -1; while (iter.hasNext()) { Result result = (Result) iter.next(); if (sIdx == -1) { ResultDescriptor rd = result.getResultDescriptor(); int columnCount = rd.getColumnCount(); sIdx = rd.getColumnIndex("SHAPE"); iIdx = rd.getColumnIndex("IPOSITION"); jIdx = rd.getColumnIndex("JPOSITION"); kIdx = rd.getColumnIndex("KPOSITION"); vIdx = rd.getColumnIndex("YORDINATE"); zIdx = rd.getColumnIndex("Z"); for (int i = 0; i < columnCount; i++) { String colName = rd.getColumnName(i); if (!StringUtils.contains(ATTRIBUTE_LIST, colName)) { attColumns.setAttribute( colName, result.getObject(colName)); } } } double v = result.getDouble(vIdx); double z = result.getDouble(zIdx); int i = result.getInteger(iIdx); int j = result.getInteger(jIdx); int k = result.getInteger(kIdx); IJKey key = new IJKey(i, j); XYColumn col = (XYColumn)map.get(key); if (col == null) { Coordinate coord = WKTUtils.toCoordinate(result.getString(sIdx)); if (coord == null) coord = new Coordinate(); col = new XYColumn(coord.x, coord.y, i, j); map.put(key, col); } col.add(new HeightValue(z, v, k)); } ArrayList<XYColumn> cols = new ArrayList<XYColumn>(map.values()); attColumns.setXYColumns(cols); return attColumns; } /** * This <code>getChart</code> method returns a 2D VerticalCrossSectionChart * displaying polygon data with isolines and a legend describing the colors * used in that chart. * * @param chartLables Labels used to decorate the chart. * @param theme The theme used to adjust the look of the chart. * @param parameters A collection with parameters this chart contains. * @param measurements A collection with measurement this chart contains. * @param dates A collection with dates this chart contains. * @param result The data collection used to be displayed in this chart. * @param locale The Locale used to determine the language. * @param uuid The uuid of the current artifact. * @param linesVisible A boolean property to determine the visibility of * lines connecting two points in a chart (not used in this chart type). * @param shapesVisible A boolean property to determine the visiblity of * datapoints in this chart (not used in this chart type). * @param callContext The CallContext object. * @return a 2D chart representing the data as polygons. */ @Override protected Chart getChart( ChartLabels chartLables, ChartTheme theme, Collection parameters, Collection measurements, Collection dates, Object result, Locale locale, String uuid, boolean linesVisible, boolean shapesVisible, CallContext callContext ) { Chart chart = null; if (CACHE_CHART) { log.info("Try to get verticalcrosssection chart from cache."); chart = (Chart) getChartFromCache(uuid, callContext); } if (chart != null) return chart; log.info("Chart not in cache yet."); if (!(result instanceof AttributedXYColumns)) { log.error("result of wrong type"); return null; } AttributedXYColumns columns = (AttributedXYColumns)result; Integer parameterId = (Integer)columns.getAttribute("GROUP1"); // XXX: hardcoded if (parameterId == null) { log.error("missing parameter id"); return null; } Map<Integer, PaletteManager> paletteManagers = getPalettes(callContext); PaletteManager paletteManager = paletteManagers.get(parameterId); if (paletteManager == null) { log.error("no palette found for parameter id " + parameterId); return null; } HashMap<Integer, Paint> special = new HashMap<Integer, Paint>(); special.put(GROUND_FILL_INDEX, getGroundFill(callContext)); chart = new VerticalCrossSectionChart( columns, paletteManager.getBase(), special, locale, chartLables); chart.generateChart(); ((VerticalCrossSectionChart)chart).setBackgroundPaint(Color.WHITE); if (CACHE_CHART) { log.info("Put chart into cache."); purifyChart(chart, uuid); } return chart; } @Override protected Statistics getStatisticsGenerator() { return new VerticalCrossSectionStatistics(); } /** * Nothing happens here. <b>This method should never be called</b> until * there is a wise implementation of a csv representation of the polygon * data. * * @param outputStream The output stream used to write the csv file to. * @param chartResult The data used to be written to csv file. * @throws UnsupportedEncodingException if the encoding is not supported. * @throws IOException if an error occured while writing to output stream. * @throws StateException if an error occured while csv file creation. */ @Override protected void createCSV( OutputStream outputStream, Collection<Result> chartResult ) throws UnsupportedEncodingException, IOException, StateException { // TODO: Implement a substitution which makes sense. } @Override protected void createODV( OutputStream outputStream, String uuid, CallContext callContext) throws IOException, StateException { // 'Profilschnitte' contain one parameter only Collection tmp = getParameters(uuid); KeyValueDescibeData param = (KeyValueDescibeData) tmp.toArray()[0]; String [] COLUMN_HEADER = { "Cruise", "Station", "Type", "yyyy-mm-dd hh:mm", "Lon (°E)", "Lat (°N)", "Bot. Depth [m]", "Depth [m]", "QF", param.getValue() }; Export.Profile ODV_PROFILE = new DefaultProfile( COLUMN_HEADER, '\t', CSVWriter.NO_QUOTE_CHARACTER, CSVWriter.NO_ESCAPE_CHARACTER, "ODV", "ISO-8859-1"); Object chartResult = getChartResult(uuid, callContext); if (chartResult == null) { log.error("No data for export found."); return; } List result = new ArrayList(1); result.add(chartResult); InputData data = inputData.get(dateValueName); String date = data.getDescription()[0]; Interpolation3D interpol = ((AttributedXYColumns) chartResult).getInterpolation(); Coordinate[] coords = interpol.getCoordinates(); double[] depth = interpol.getDepths(); double[] raster = interpol.getRaster(); Export export = new VerticalCrossODVExport( interpol.getCoordinates(), interpol.getCellHeight(), interpol.getCellWidth(), interpol.getRaster(), date, interpol.getWidth(), interpol.getHeight()); export.create(ODV_PROFILE, outputStream, null); } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :