view artifacts/src/main/java/org/dive4elements/river/artifacts/charts/CrossSectionApp.java @ 7471:fff862f4ef76

Experimental caching of datacage recommendations. The respective hook is called a lot and running the datacage over and over again when loading data can be expensive. So the generated recommendations are cached for some time. Hopefully this improves the overall speed of loading data from the datacage.
author Sascha L. Teichmann <teichmann@intevation.de>
date Wed, 30 Oct 2013 15:26:21 +0100
parents af13ceeba52a
children
line wrap: on
line source
/* Copyright (C) 2011, 2012, 2013 by Bundesanstalt für Gewässerkunde
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU AGPL (>=v3)
 * and comes with ABSOLUTELY NO WARRANTY! Check out the
 * documentation coming with Dive4Elements River for details.
 */

package org.dive4elements.river.artifacts.charts;

import org.dive4elements.river.backend.SessionFactoryProvider;

import org.dive4elements.river.artifacts.geom.Lines;

import org.dive4elements.river.model.CrossSection;
import org.dive4elements.river.model.CrossSectionLine;
import org.dive4elements.river.model.CrossSectionPoint;

import org.dive4elements.river.utils.Pair;

import org.dive4elements.river.jfree.StableXYDifferenceRenderer;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

import java.math.BigDecimal;
import java.math.MathContext;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import java.awt.Color;
import java.awt.Paint;
import java.awt.TexturePaint;

import java.awt.image.BufferedImage;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;

import javax.swing.table.AbstractTableModel;

import org.hibernate.Query;
import org.hibernate.Session;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;

import org.jfree.chart.axis.NumberAxis;

import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;

import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.chart.renderer.xy.XYItemRenderer;

import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;


/**
 * Standalone tech-demo.
 */
public class CrossSectionApp
extends      ApplicationFrame
{
    public static final String RIVER = System.getProperty("river", "Saar");

    public static final String WATER_LEVEL = System.getProperty("waterlevel");

    public static final String KM = System.getProperty("km");

    public static final double EPSILON = 1e-4;

    protected Session session;

    protected JComboBox crossSectionLinesCB;
    protected JTextField waterlevelTF;

    protected ChartPanel chartPanel;

    protected Double lastWaterLevel;

    protected List<CrossSection> crossSections;
    protected boolean [] drawCrossSection;
    protected boolean [] drawWaterLevel;
    protected boolean [] drawGround;
    protected boolean [] drawFill;

    protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines;

    protected static final Paint TRANSPARENT = createTransparentPaint();

    public class CrossSectionTableModel extends AbstractTableModel {

        @Override
        public String getColumnName(int col) {
            switch (col) {
                case 0: return "Peilungsname";
                case 1: return "Peilung";
                case 2: return "Wasserstand";
                case 3: return "Boden";
                case 4: return "Wasser";
            }
            return "";
        }

        @Override
        public int getColumnCount() {
            return 5;
        }

        @Override
        public int getRowCount() {
            return crossSections != null ? crossSections.size() : 0;
        }

        @Override
        public Object getValueAt(int row, int col) {
            if (crossSections == null) return null;
            switch (col) {
                case 0: return crossSections.get(row).getDescription();
                case 1: return drawCrossSection[row];
                case 2: return drawWaterLevel[row];
                case 3: return drawGround[row];
                case 4: return drawFill[row];
            }
            return null;
        }

        @Override
        public void setValueAt(Object value, int row, int col) {
            switch (col) {
                case 1:
                    if (change(drawCrossSection, row, (Boolean)value)) {
                        fireTableCellUpdated(row, col);
                    }
                    break;
                case 2:
                    if (change(drawWaterLevel, row, (Boolean)value)) {
                        fireTableCellUpdated(row, col);
                    }
                    break;
                case 3:
                    if (change(drawGround, row, (Boolean)value)) {
                        fireTableCellUpdated(row, col);
                    }
                    break;
                case 4:
                    if (change(drawFill, row, (Boolean)value)) {
                        fireTableCellUpdated(row, col);
                    }
                    break;
            }
        }

        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch (columnIndex) {
                case 0: return String.class;
                case 1:
                case 2:
                case 3:
                case 4: return Boolean.class;
            }
            return null;
        }

        @Override
        public boolean isCellEditable(
            int rowIndex,
            int columnIndex
        ) {
            return columnIndex >= 1 && columnIndex <= 4;
        }
    } // class CrossSectionTableModel

    private static boolean change(
        boolean [] values,
        int        index,
        boolean    value
    ) {
        if (values[index] != value) {
            values[index] = value;
            return true;
        }
        return false;
    }

    public static class CrossSectionLineItem {

        Double km;
        List<Pair<CrossSection, CrossSectionLine>> lines;

        public CrossSectionLineItem(
            Double km,
            List<Pair<CrossSection, CrossSectionLine>> lines
        ) {
            this.km    = km;
            this.lines = lines;
        }

        public String toString() {
            return String.valueOf(km);
        }
    } // CrossSectionLineItem

    public CrossSectionApp(String title) {
        super(title);

        session = SessionFactoryProvider
            .createSessionFactory()
            .openSession();

        JPanel content = createContent();
        content.setPreferredSize(new Dimension(800, 480));
        setContentPane(content);
    }


    /** Query and return CrossSections for named river. */
    public List<CrossSection> crossSections(String river) {
        Query query = session.createQuery(
            "from CrossSection where river.name = :river");
        query.setParameter("river", river);
        return query.list();
    }

    protected Map<Double, List<Pair<CrossSection, CrossSectionLine>>>
        loadAllLines(List<CrossSection> crossSections) {
        Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines =
            new TreeMap<Double, List<Pair<CrossSection, CrossSectionLine>>>();
        for (CrossSection cs: crossSections) {
            List<CrossSectionLine> lines = cs.getLines();
            for (CrossSectionLine csl: lines) {
                Double km = Math.round(csl.getKm().doubleValue() * 1000d)/1000d;
                List<Pair<CrossSection, CrossSectionLine>> ls
                    = km2lines.get(km);
                if (ls == null) {
                    ls = new ArrayList<Pair<CrossSection, CrossSectionLine>>(2);
                    km2lines.put(km, ls);
                }
                ls.add(new Pair<CrossSection, CrossSectionLine>(cs, csl));
            }
        }
        return km2lines;
    }

    public JPanel createContent() {
        JPanel panel = new JPanel(new BorderLayout());

        JPanel nav = new JPanel(new FlowLayout());

        crossSections = crossSections(RIVER);
        km2lines = loadAllLines(crossSections);

        int CS = crossSections.size();
        Arrays.fill(drawCrossSection = new boolean[CS], true);
        drawWaterLevel = new boolean[CS];
        drawGround     = new boolean[CS];
        drawFill       = new boolean[CS];

        Object [] clis = createCrossSectionLineItems(km2lines);

        DefaultComboBoxModel dcbm = new DefaultComboBoxModel(clis);

        crossSectionLinesCB = new JComboBox(dcbm);

        if (KM != null) {
            try {
                double km = Double.parseDouble(KM);

                CrossSectionLineItem found = null;

                for (Object o: clis) {
                    CrossSectionLineItem csli = (CrossSectionLineItem)o;
                    if (Math.abs(csli.km - km) < EPSILON) {
                        found = csli;
                        break;
                    }
                }

                if (found != null) {
                    crossSectionLinesCB.setSelectedItem(found);
                }
            }
            catch (NumberFormatException nfe) {
                System.err.println("km is not a number: "
                    + nfe.getMessage());
            }
        }

        nav.add(crossSectionLinesCB);

        crossSectionLinesCB.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent ie) {
                if (ie.getStateChange() == ItemEvent.SELECTED) {
                    updateChart();
                }
            }
        });

        waterlevelTF = new JTextField(5);

        if (WATER_LEVEL != null) {
            try {
                waterlevelTF.setText(
                    (lastWaterLevel = Double.valueOf(WATER_LEVEL)).toString());
            }
            catch (NumberFormatException nfe) {
                System.err.println("Water level not a number: " +
                    nfe.getMessage());
            }
        }

        waterlevelTF.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                waterLevelChanged();
            }
        });

        nav.add(waterlevelTF);

        JButton dump = new JButton("dump");

        dump.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                dumpData();
            }
        });

        nav.add(dump);


        chartPanel = createChartPanel();

        panel.add(chartPanel, BorderLayout.CENTER);


        CrossSectionTableModel cstm = new CrossSectionTableModel();

        cstm.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                updateChart();
            }
        });

        JTable crossTable = new JTable(cstm);

        JPanel west = new JPanel(new BorderLayout());
        JScrollPane scrollPane = new JScrollPane(crossTable);
        west.add(scrollPane);

        west.add(nav, BorderLayout.SOUTH);

        panel.add(west, BorderLayout.WEST);

        return panel;
    }

    protected void waterLevelChanged() {
        String value = waterlevelTF.getText();
        try {
            lastWaterLevel = Double.valueOf(value);
        }
        catch (NumberFormatException nfe) {
            waterlevelTF.setText(
                lastWaterLevel != null ? lastWaterLevel.toString() : "");
            return;
        }
        updateChart();
    }

    protected void updateChart() {

        JFreeChart chart = createChart();

        chartPanel.setChart(chart);
    }

    protected ChartPanel createChartPanel() {

        JFreeChart chart = createChart();

        return new ChartPanel(chart);
    }

    protected void dumpData() {

        CrossSectionLineItem csli =
            (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();

        if (csli == null) {
            return;
        }

        double km = Math.round(csli.km.doubleValue() * 1000d)/1000d;

        String kmS = String.valueOf(km).replace(".", "-");

        int i = 1;
        File file = new File("cross-section-" + kmS + ".txt");
        while (file.exists()) {
            file = new File("cross-section-" + kmS + "[" + (i++) + "].txt");
        }

        System.err.println("dump points to file '" + file + "'");

        PrintWriter out = null;

        MathContext mc = new MathContext(3);

        try {
            out =
                new PrintWriter(
                new FileWriter(file));

            for (Pair<CrossSection, CrossSectionLine> pair: csli.lines) {
                out.println("# " + pair.getA().getDescription());
                for (CrossSectionPoint point: pair.getB().getPoints()) {
                    out.println(
                        new BigDecimal(point.getX()).round(mc) + " " +
                        new BigDecimal(point.getY()).round(mc));
                }
            }

            out.flush();
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
        }
        finally {
            if (out != null) {
                out.close();
            }
        }
    }

    public void generateWaterLevels(
        List<Point2D>                         points,
        List<Pair<XYDataset, XYItemRenderer>> datasets
    ) {
        if (points == null || points.isEmpty() || lastWaterLevel == null) {
            return;
        }

        double [][] data = Lines.createWaterLines(points, lastWaterLevel).points;
        XYSeries series =
            new XYSeries(String.valueOf(lastWaterLevel), false);

        double [] x = data[0];
        double [] y = data[1];
        for (int i = 0; i < x.length; ++i) {
            series.add(x[i], y[i], false);
        }

        datasets.add(new Pair<XYDataset, XYItemRenderer>(
            new XYSeriesCollection(series), null));
    }

    public void generateFill(
        List<Point2D>                         points,
        String                                legend,
        List<Pair<XYDataset, XYItemRenderer>> datasets
    ) {
        if (points == null || points.isEmpty() || lastWaterLevel == null) {
            return;
        }

        double [][] data   = Lines.createWaterLines(points, lastWaterLevel).points;
        double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);

        DefaultXYDataset dataset = new DefaultXYDataset();

        dataset.addSeries(legend + "-Linie", values);
        dataset.addSeries(legend + "-Fl\u00e4che", data);

        datasets.add(new Pair<XYDataset, XYItemRenderer>(
            dataset,
            new StableXYDifferenceRenderer(
                TRANSPARENT, Color.blue, false)));
    }

    public void generateProfile(
        List<Point2D>                         points,
        String                                legend,
        List<Pair<XYDataset, XYItemRenderer>> datasets
    ) {
        if (points == null || points.isEmpty()) {
            return;
        }

        double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);

        XYSeries series = new XYSeries(legend, false);

        double [] x = values[0];
        double [] y = values[1];
        for (int i = 0; i < x.length; ++i) {
            series.add(x[i], y[i], false);
        }

        datasets.add(new Pair<XYDataset, XYItemRenderer>(
            new XYSeriesCollection(series), null));;
    }


    /**
     * @param legend the legend entry.
     */
    public void generateGround(
        List<Point2D>                         points,
        String                                legend,
        List<Pair<XYDataset, XYItemRenderer>> datasets
    ) {
        if (points == null || points.isEmpty()) {
            return;
        }

        double [][] values = CrossSectionLine.fetchCrossSectionProfile(points);

        DefaultXYDataset dataset = new DefaultXYDataset();

        dataset.addSeries(legend, values);

        StableXYDifferenceRenderer renderer =
            new StableXYDifferenceRenderer();

        datasets.add(new Pair<XYDataset, XYItemRenderer>(
            dataset, renderer));
    }

    public List<Pair<XYDataset, XYItemRenderer>> generateDatasets() {

        List<Pair<XYDataset, XYItemRenderer>> datasets =
            new ArrayList<Pair<XYDataset, XYItemRenderer>>();

        CrossSectionLineItem csli =
            (CrossSectionLineItem)crossSectionLinesCB.getSelectedItem();

        for (int i = 0; i < drawCrossSection.length; ++i) {
            List<Point2D> points = null;
            CrossSection cs = crossSections.get(i);

            if (drawGround[i]) {
                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
                    if (csl.getA() == cs) {
                        if (points == null) {
                            points = csl.getB().fetchCrossSectionLinesPoints();
                        }
                        generateGround(
                            points,
                            cs.getDescription() + "/Boden",
                            datasets);
                        break;
                    }
                }
            }

            if (drawFill[i]) {
                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
                    if (csl.getA() == cs) {
                        if (points == null) {
                            points = csl.getB().fetchCrossSectionLinesPoints();
                        }

                        generateFill(
                            points, cs.getDescription(), datasets);
                        break;
                    }
                }
            }

            if (drawCrossSection[i]) {
                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
                    if (csl.getA() == cs) {
                        if (points == null) {
                            points = csl.getB().fetchCrossSectionLinesPoints();
                        }

                        generateProfile(
                            points, cs.getDescription(), datasets);
                        break;
                    }
                }
            }

            if (drawWaterLevel[i]) {
                for (Pair<CrossSection, CrossSectionLine> csl: csli.lines) {
                    if (csl.getA() == cs) {
                        if (points == null) {
                            points = csl.getB().fetchCrossSectionLinesPoints();
                        }
                        generateWaterLevels(points, datasets);
                        break;
                    }
                }
            }

        }

        return datasets;
    }

    protected Object [] createCrossSectionLineItems(
        Map<Double, List<Pair<CrossSection, CrossSectionLine>>> km2lines
    ) {
        Object [] result = new Object[km2lines.size()];
        int i = 0;
        for (Map.Entry<Double, List<Pair<CrossSection, CrossSectionLine>>> entry:
            km2lines.entrySet()) {
            result[i++] = new CrossSectionLineItem(
                entry.getKey(),
                entry.getValue());
        }
        return result;
    }


    public JFreeChart createChart() {
        JFreeChart chart = ChartFactory.createXYLineChart(
            null,
            "Abstand [m]",
            "H\u00f6he [m]",
            null,
            PlotOrientation.VERTICAL,
            true,
            true,
            false);

        List<Pair<XYDataset, XYItemRenderer>> datasets =
            generateDatasets();

        XYPlot plot = chart.getXYPlot();

        for (int i = 0, N = datasets.size(); i < N; ++i) {
            Pair<XYDataset, XYItemRenderer> p = datasets.get(i);
            plot.setDataset(i, p.getA());
            plot.mapDatasetToRangeAxis(i, 0);
            XYItemRenderer renderer = p.getB();
            if (renderer != null) {
                plot.setRenderer(i, renderer);
            }
        }

        NumberAxis yAxis = (NumberAxis)plot.getRangeAxis();
        yAxis.setAutoRangeIncludesZero(false);

        ChartUtilities.applyCurrentTheme(chart);
        return chart;
    }

    protected static Paint createTransparentPaint() {
        BufferedImage texture = new BufferedImage(
            1, 1, BufferedImage.TYPE_4BYTE_ABGR);

        return new TexturePaint(
            texture, new Rectangle2D.Double(0d, 0d, 0d, 0d));
    }

    public static void main(String [] args) {
        CrossSectionApp csa = new CrossSectionApp("Querprofile");
        csa.pack();
        RefineryUtilities.centerFrameOnScreen(csa);
        csa.setVisible(true);
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org