view artifacts/src/main/java/org/dive4elements/river/artifacts/charts/CrossSectionApp.java @ 5838:5aa05a7a34b7

Rename modules to more fitting names.
author Sascha L. Teichmann <teichmann@intevation.de>
date Thu, 25 Apr 2013 15:23:37 +0200
parents flys-artifacts/src/main/java/org/dive4elements/river/artifacts/charts/CrossSectionApp.java@bd047b71ab37
children 4897a58c8746
line wrap: on
line source
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