org.n52.oxf.render.sos.AnimatedMapBarChartRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.n52.oxf.render.sos.AnimatedMapBarChartRenderer.java

Source

/**
 * Copyright (C) 2012
 * by 52 North Initiative for Geospatial Open Source Software GmbH
 *
 * Contact: Andreas Wytzisk
 * 52 North Initiative for Geospatial Open Source Software GmbH
 * Martin-Luther-King-Weg 24
 * 48155 Muenster, Germany
 * info@52north.org
 *
 * This program is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation.
 *
 * This program is distributed WITHOUT ANY WARRANTY; even without the implied
 * WARRANTY OF MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program (see gnu-gpl v2.txt). If not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or
 * visit the Free Software Foundation web page, http://www.fsf.org.
 */

package org.n52.oxf.render.sos;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.renderer.category.BarRenderer3D;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.n52.oxf.OXFException;
import org.n52.oxf.adapter.ParameterContainer;
import org.n52.oxf.adapter.ParameterShell;
import org.n52.oxf.context.ContextBoundingBox;
import org.n52.oxf.feature.OXFFeature;
import org.n52.oxf.feature.OXFFeatureCollection;
import org.n52.oxf.feature.sos.ObservationSeriesCollection;
import org.n52.oxf.feature.sos.ObservedValueTuple;
import org.n52.oxf.ows.capabilities.IBoundingBox;
import org.n52.oxf.render.AnimatedVisualization;
import org.n52.oxf.render.IFeatureDataRenderer;
import org.n52.oxf.valueDomains.time.ITimePosition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vividsolutions.jts.geom.Point;

public class AnimatedMapBarChartRenderer implements IFeatureDataRenderer {

    private static final Logger LOGGER = LoggerFactory.getLogger(AnimatedMapBarChartRenderer.class);

    protected static final int X_SHIFT = -10;
    protected static final int Y_SHIFT = -10;

    protected static final float CHART_WIDTH = 60;
    protected static final float CHART_WIDTH_MULTIPLICATOR = 80; // + 100 per observedProperty
    protected static final float CHART_HEIGHT = 150;

    protected static final String[] SUPPORTED_RESULT_TYPES = new String[] { "Measurement", "Observation" };

    private ChartCache chartCache = null;
    private ObservationSeriesCollection obsValues4FOI = null;
    private String[] observedProperties;

    private Set<OXFFeature> featuresWithCharts = null;

    /**
     * 
     * @param featuresWithCharts
     *        the Features of Interest for which a chart shall be renderered.
     */
    public AnimatedMapBarChartRenderer() {
        super();
        chartCache = new ChartCache();
    }

    /**
     * Requirements:<br>
     * 1. Observations are of type OXFMeasurementType. <br>
     * 2. Number of observedProperties: min=1 max=n <br>
     * 3. To allow "caching": observationCollection is the same between different renderLayer()-executions.
     * 
     * @param observationCollection
     *        contains the Observations
     * @param paramCon
     * @param screenW
     * @param screenH
     * @param bbox
     * @param featuresWithCharts
     *        the Features of Interest for which a chart shall be rendered.
     * @return
     */
    public AnimatedVisualization renderLayer(OXFFeatureCollection observationCollection,
            ParameterContainer paramCon, int screenW, int screenH, IBoundingBox bbox,
            Set<OXFFeature> selectedFeatures) throws OXFException {
        if (featuresWithCharts == null) {
            featuresWithCharts = selectedFeatures;
        }

        // before starting to render --> run garbageCollection
        Runtime.getRuntime().gc();
        LOGGER.info("Garbage Collection done.");
        // --

        // correct resultModel used?:
        String resultModel = (String) paramCon.getParameterShellWithServiceSidedName("resultModel")
                .getSpecifiedValue();
        if (!isResultModelSupported(resultModel)) {
            throw new OXFException("Renderer does not support the specified resultModel '" + resultModel + "'");
        }

        // which observedProperties have been used?:
        ParameterShell observedPropertyPS = paramCon.getParameterShellWithServiceSidedName("observedProperty");
        if (observedPropertyPS.hasMultipleSpecifiedValues()) {
            observedProperties = observedPropertyPS.getSpecifiedTypedValueArray(String[].class);
            //observedProperties = new String[] {"urn:ogc:def:phenomenon:OGC:1.0.30:dryBulbTemp", "urn:ogc:def:phenomenon:OGC:1.0.30:barometricPressure"]};
        } else {
            throw new IllegalArgumentException("no observedProperties found.");
        }

        // find tuples:
        if (obsValues4FOI == null) {
            obsValues4FOI = new ObservationSeriesCollection(observationCollection, featuresWithCharts,
                    observedProperties, true);
        }

        //
        // render Images for each time stamp (frame) and add them to the resultVis:
        //
        AnimatedVisualization resultVis = new AnimatedVisualization();

        ITimePosition[] sortedArray = obsValues4FOI.getSortedTimeArray();
        for (int i = 0; i < sortedArray.length; i++) {
            resultVis.addFrame(
                    renderFrame(sortedArray, i, screenW, screenH, bbox, featuresWithCharts, obsValues4FOI));
        }

        return resultVis;
    }

    protected Image renderFrame(ITimePosition[] sortedTimeArray, int currentTimeIndex, int screenW, int screenH,
            IBoundingBox bbox, Set<OXFFeature> featuresWithCharts, ObservationSeriesCollection tupleFinder) {
        ContextBoundingBox contextBBox = new ContextBoundingBox(bbox);

        BufferedImage image = new BufferedImage(screenW, screenH, BufferedImage.TYPE_INT_RGB);

        Graphics2D g = image.createGraphics();

        // draw white background:
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, screenW, screenH);

        g.setColor(Color.BLACK);

        ITimePosition currentTimePos = sortedTimeArray[currentTimeIndex];

        for (OXFFeature chartFeature : featuresWithCharts) {

            //
            // create Plot for each "chart feature" and add it to the cache:
            //
            if (!chartCache.contains(currentTimePos, chartFeature)) {

                // if there is a data tuple for the current time position -> create a new plot
                if (tupleFinder.getTuple(chartFeature, currentTimePos) != null) {
                    CategoryPlot plot = drawChart4FOI(chartFeature.getID(), currentTimePos.toString(),
                            tupleFinder.getTuple(chartFeature, currentTimePos));
                    chartCache.add(currentTimePos, chartFeature, plot);
                }
            }

            CategoryPlot plot = (CategoryPlot) chartCache.get(currentTimePos, chartFeature);

            // if there wasn't a plot for the current time position go backwards through the sortedTimeArray and take the most recent one:
            int j = currentTimeIndex - 1;
            while (plot == null && j >= 0) {
                plot = (CategoryPlot) chartCache.get(sortedTimeArray[j], chartFeature);
                j--;
            }

            //
            // draw the plots into the image at the georeferenced position of the corresponding chartFeature:
            //
            Point pRealWorld = (Point) chartFeature.getGeometry();

            java.awt.Point pScreen = ContextBoundingBox.realworld2Screen(contextBBox.getActualBBox(), screenW,
                    screenH, new Point2D.Double(pRealWorld.getCoordinate().x, pRealWorld.getCoordinate().y));
            // if an appropriate plot was found -> draw it
            if (plot != null) {
                for (int i = 0; i < observedProperties.length; i++) {
                    plot.getRangeAxis(i).setRange((Double) tupleFinder.getMinimum(i) - 5,
                            (Double) tupleFinder.getMaximum(i));
                    if (i > 0) {
                        plot.setRangeAxisLocation(1, AxisLocation.BOTTOM_OR_RIGHT);
                    }
                }

                plot.draw(g,
                        new Rectangle2D.Float(pScreen.x + X_SHIFT + 10, pScreen.y + Y_SHIFT,
                                CHART_WIDTH + observedProperties.length * CHART_WIDTH_MULTIPLICATOR, CHART_HEIGHT),
                        null, null, null);
            }
            // otherwise draw "no data available"
            else {
                g.drawString("No data available", pScreen.x + X_SHIFT, pScreen.y + Y_SHIFT);
            }

            // draw point of feature:
            g.fillOval(pScreen.x - (FeatureGeometryRenderer.DOT_SIZE_POINT / 2),
                    pScreen.y - (FeatureGeometryRenderer.DOT_SIZE_POINT / 2),
                    FeatureGeometryRenderer.DOT_SIZE_POINT, FeatureGeometryRenderer.DOT_SIZE_POINT);
        }

        return image;
    }

    protected CategoryPlot drawChart4FOI(String featureID, String timeString, ObservedValueTuple tuple) {

        BarRenderer3D barRenderer = new BarRenderer3D();

        CategoryPlot plot = new CategoryPlot();
        plot.setDomainAxis(new CategoryAxis(featureID));

        for (int i = 0; i < observedProperties.length; i++) {
            plot.setDataset(i, createDataset(i, timeString, tuple));
            plot.setRangeAxis(i, new NumberAxis(
                    tuple.getPhenomenonNames()[i].split(":")[tuple.getPhenomenonNames()[i].split(":").length - 1]));
            plot.setRenderer(i, barRenderer);
            plot.mapDatasetToRangeAxis(i, i);
        }

        return plot;
    }

    private CategoryDataset createDataset(int indexOfProperty, String timeString, ObservedValueTuple tuple) {
        String category = timeString;

        DefaultCategoryDataset dataset = new DefaultCategoryDataset();

        for (int j = 0; j < observedProperties.length; j++) {
            if (j == indexOfProperty) {
                dataset.addValue((Number) tuple.getValue(indexOfProperty), "Series " + j, category);
            } else {
                dataset.addValue(null, "Series " + j, category);
            }
        }
        return dataset;
    }

    private boolean isResultModelSupported(String specifiedResultModel) {
        for (String supportedResultModel : SUPPORTED_RESULT_TYPES) {
            if (specifiedResultModel.equals(supportedResultModel)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return a plain text description of this Renderer.
     */
    public String getDescription() {
        return "AnimatedMapBarChartRenderer - visualizes a temporal animation of bar charts into a map";
    }

    public String toString() {
        return getDescription();
    }

    /**
     * @return the type of the service whose data can be rendered with this ServiceRenderer. In this case
     *         "OGC:SOS" will be returned.
     */
    public String getServiceType() {
        return "OGC:SOS";
    }

    /**
     * @return the versions of the services whose data can be rendered with this ServiceRenderer. In this case
     *         {"1.0.0"} will be returned.
     */
    public String[] getSupportedVersions() {
        return new String[] { "0.0.0" };
    }

    private class ChartCache {

        private Map<ITimePosition, Map<OXFFeature, Plot>> timePosMap;

        public ChartCache() {
            super();
            timePosMap = new HashMap<ITimePosition, Map<OXFFeature, Plot>>();
        }

        public void add(ITimePosition timePos, OXFFeature feature, CategoryPlot plot) {
            Map<OXFFeature, Plot> featureMap = timePosMap.get(timePos);
            if (featureMap == null) {
                featureMap = new HashMap<OXFFeature, Plot>();
                timePosMap.put(timePos, featureMap);
            }
            featureMap.put(feature, plot);
        }

        public Plot get(ITimePosition timePos, OXFFeature feature) {
            Map<OXFFeature, Plot> featureMap = timePosMap.get(timePos);
            if (featureMap != null) {
                return featureMap.get(feature);
            } else {
                return null;
            }
        }

        public boolean contains(ITimePosition timePos, OXFFeature feature) {
            Map<OXFFeature, Plot> featureMap = timePosMap.get(timePos);
            if (featureMap != null) {
                return featureMap.containsKey(feature);
            } else {
                return false;
            }
        }

    }
}