com.astronomy.project.Project.java Source code

Java tutorial

Introduction

Here is the source code for com.astronomy.project.Project.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.astronomy.project;

import com.CancelExcepcion;
import com.ProcessException;
import com.Global;
import com.TemporalTaskTemplate;
import static com.astronomy.project.FortuitousProbabilityOption.BERNOULLI;
import static com.astronomy.project.FortuitousProbabilityOption.ROSENFELDT;
import com.chart.AxisChart;
import com.chart.SwingChart;
import com.chart.SimpleSeriesConfiguration;
import com.interfaz.skeleton.MessageDialog;
import com.interfaz.skeleton.ModalDialog;
import com.main.Main;
import static com.main.Main.skeleton;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import static java.lang.Math.PI;
import static java.lang.Math.exp;
import static java.lang.Math.pow;
import static java.lang.Math.sqrt;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Window;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

/**
 * Archaeoastronomy project
 *
 * @author MIGUEL_ANGEL
 */
public class Project extends VBox {

    /**
     * Project name
     */
    private String name;
    /**
     * Work dir
     */
    private String workDir = "";
    /**
     * Observable list of Alignment
     */
    ObservableList data = FXCollections.observableArrayList(new ArrayList<>());
    /**
     * Table view for alignments
     */
    TableView<Alignment> table = new TableView<>(data);
    /**
     * Main parent
     */
    Main parent = null;
    /**
     * Main slipt pane.
     */
    private SplitPane splitPane;
    /**
     * Pane for details of an alignment
     */
    AlignmentPane alignmentPane;

    /**
     * If true a change has been mada and project needs to be saved before
     * closing
     */
    private boolean change = false;
    MenuBar menuBar;

    /**
     * Dialog for creating a new alignment
     *
     * @return Alignment
     */
    public static Alignment inputAlignment() {
        AlignmentDialogInput dlg = new AlignmentDialogInput(null);
        if (dlg.showModal()) {
            return dlg.alignment;
        } else {
            return null;
        }
    }

    /**
     * Dialog for selecting the Gaussian kernel density estimation parameters
     *
     * @param dt Standard deviation
     * @param d Minimum value
     * @param h Maximum value
     * @param i Value increment
     * @return Gaussian kernel density estimation parameters
     * @throws ProcessException Format error
     * @throws CancelExcepcion cancelation by user
     */
    static public final GaussianKernelDensityEstimationParameters inputGaussianKernelDensityEstimationParameters(
            double dt, double d, double h, double i) throws ProcessException, CancelExcepcion {

        TextField tfDesviacionTipica = new TextField(String.valueOf(dt));
        TextField tfDeclinacionDesde = new TextField(String.valueOf(d));
        TextField tfDeclinacionHasta = new TextField(String.valueOf(h));
        TextField tfDeclinacionPaso = new TextField(String.valueOf(i));

        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        gridPane.setPadding(new Insets(10, 10, 10, 10));

        gridPane.add(new Label("Standard deviation"), 0, 0);
        gridPane.add(tfDesviacionTipica, 1, 0);
        gridPane.add(new Label("Minimum value"), 0, 1);
        gridPane.add(tfDeclinacionDesde, 1, 1);
        gridPane.add(new Label("Maximum value"), 0, 2);
        gridPane.add(tfDeclinacionHasta, 1, 2);
        gridPane.add(new Label("Value increment"), 0, 3);
        gridPane.add(tfDeclinacionPaso, 1, 3);

        ModalDialog dialogo = new ModalDialog(skeleton, gridPane, true);
        if (dialogo.showModal()) {
            try {
                return new GaussianKernelDensityEstimationParameters(
                        Double.valueOf(tfDesviacionTipica.getText().replace(",", ".")),
                        Double.valueOf(tfDeclinacionDesde.getText().replace(",", ".")),
                        Double.valueOf(tfDeclinacionHasta.getText().replace(",", ".")),
                        Double.valueOf(tfDeclinacionPaso.getText().replace(",", ".")));
            } catch (NumberFormatException ex) {
                new MessageDialog(skeleton, "Formato de parmetros incorrecto", MessageDialog.MessageType.ERROR)
                        .show();
                return null;
            }
        } else {
            throw new CancelExcepcion("");
        }
    }

    /**
     * Save project
     *
     * @throws IOException Signals that an I/O exception of some sort has
     * occurred. This class is the general class of exceptions produced by
     * failed or interrupted I/O operations
     */
    public void saveProject() throws IOException {
        if (!exists()) {
            DirectoryChooser directoryChooser = new DirectoryChooser();
            File selectedDirectory = directoryChooser.showDialog(null);
            Project.this.save(selectedDirectory.getAbsolutePath());
        } else {
            save();
        }
    }

    /**
     * Export project
     */
    public void export() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Export a CSV file");
        fileChooser.setInitialDirectory(new File(getWorkDir()));
        fileChooser.getExtensionFilters()
                .addAll(new FileChooser.ExtensionFilter("Comma Separated Values", "*.csv"));

        Window w = null;
        try {
            w = skeleton.getScene().getWindow();
        } catch (NullPointerException e) {

        }
        File file = fileChooser.showSaveDialog(w);
        String path = file.getAbsolutePath();

        if (!path.endsWith(".csv")) {
            file = new File(path + ".csv");
        }

        FileWriter fichero;
        PrintWriter pw;
        try {
            pw = new PrintWriter(
                    new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "ISO8859_1")));
            pw.println("Observatory;Foresight;Azimuth;Altitude;Declination;");
            for (Object dato : data) {
                String strRegistro = "";
                Alignment al = (Alignment) dato;
                strRegistro += al.pObservatory.getValue() + ";";
                strRegistro += al.pForesight.getValue() + ";";
                strRegistro += al.pAzimuth.getValue() + ";";
                strRegistro += al.pAltitude.getValue() + ";";
                strRegistro += al.pDeclination.getValue() + ";";
                pw.println(strRegistro);
            }

            pw.close();
        } catch (IOException ex) {
            Global.info.log(ex);
        }
    }

    /**
     * Get project interface
     *
     * @param parent Main parent
     * @return Project as VBox
     */
    public Project getInterface(Main parent) {
        this.parent = parent;
        alignmentPane = new AlignmentPane(parent, this);

        menuBar = new MenuBar();
        Menu menu = new Menu("File");
        menuBar.getMenus().add(menu);
        MenuItem mi = new MenuItem("New alignment");
        mi.setOnAction((ActionEvent event) -> {
            newAlignment();
        });
        menu.getItems().add(mi);
        mi = new MenuItem("Save");
        mi.setOnAction((ActionEvent event) -> {
            try {
                saveProject();
            } catch (IOException ex) {
                Global.info.log(ex);
            }
        });
        menu.getItems().add(mi);
        mi = new MenuItem("Export");
        mi.setOnAction((ActionEvent event) -> {
            export();
        });
        menu.getItems().add(mi);
        menu = new Menu("Data analysis");
        menuBar.getMenus().add(menu);
        Menu submenu = new Menu("Probability of fortuitous astronomical coincidences from a number of alignments");
        menu.getItems().add(submenu);
        mi = new MenuItem("Rosenfeldt");
        mi.setOnAction((ActionEvent event) -> {
            fortuitousProbality(ROSENFELDT);
        });
        submenu.getItems().add(mi);
        mi = new MenuItem("Bernoulli");
        mi.setOnAction((ActionEvent event) -> {
            fortuitousProbality(BERNOULLI);
        });
        submenu.getItems().add(mi);

        mi = new MenuItem("Probability density estimation using a Gaussian kernel");
        mi.setOnAction((ActionEvent event) -> {
            ;
            parent.probabilityDensityEstimation(name, new ArrayList<Alignment>(data).stream()
                    .map(a -> a.getDeclination().getSignedValue()).collect(Collectors.toList()));
        });
        menu.getItems().add(mi);

        VBox vTabla = new VBox(10);
        HBox hpanel = new HBox(10);
        Button b;

        getChildren().addAll(menuBar, hpanel);
        splitPane = new SplitPane();
        splitPane.getItems().addAll(vTabla);
        hpanel.getChildren().add(splitPane);

        HBox.setHgrow(splitPane, Priority.ALWAYS);
        HBox.setHgrow(alignmentPane, Priority.ALWAYS);

        VBox.setVgrow(hpanel, Priority.ALWAYS);
        vTabla.getChildren().addAll(table);
        VBox.setVgrow(table, Priority.ALWAYS);

        TableColumn observatory = new TableColumn("Observatory");
        TableColumn foresight = new TableColumn("Foresight");
        TableColumn azimuth = new TableColumn("Azimuth");
        TableColumn altitude = new TableColumn("Altitude");
        TableColumn declination = new TableColumn("Declination");
        TableColumn comments = new TableColumn("Comments");
        observatory.setCellValueFactory(new PropertyValueFactory("observatory"));
        foresight.setCellValueFactory(new PropertyValueFactory("foresight"));
        azimuth.setCellValueFactory(new PropertyValueFactory("azimuth"));
        altitude.setCellValueFactory(new PropertyValueFactory("altitude"));
        declination.setCellValueFactory(new PropertyValueFactory("declination"));
        comments.setCellValueFactory(new PropertyValueFactory("comments"));
        comments.setCellFactory(TextFieldTableCell.forTableColumn());
        comments.setOnEditCommit(new EventHandler<CellEditEvent<Alignment, String>>() {
            @Override
            public void handle(CellEditEvent<Alignment, String> event) {
                ((Alignment) event.getTableView().getItems().get(event.getTablePosition().getRow()))
                        .setComments(event.getNewValue());
            }

        });
        table.getColumns().addAll(observatory, foresight, azimuth, altitude, declination, comments);
        table.setItems(data);
        table.setEditable(true);

        table.setRowFactory(tv -> {
            TableRow<Alignment> row = new TableRow<>();
            row.setOnMouseClicked((MouseEvent event) -> {
                if (!row.isEmpty()) {
                    if (event.isPopupTrigger()) {
                        Alignment rowData = row.getItem();
                        ContextMenu cm = new ContextMenu();
                        MenuItem mmi = new MenuItem("Delete");
                        mmi.setOnAction((ActionEvent event1) -> {
                            data.remove(rowData);
                            change = true;
                        });
                        cm.getItems().add(mmi);
                        mmi = new MenuItem("Edit");
                        mmi.setOnAction((ActionEvent event1) -> {
                            AlignmentDialogInput dlg = new AlignmentDialogInput(rowData);
                            if (dlg.showModal()) {
                                String img = rowData.getImagenPath();
                                String obs = rowData.getComments();
                                rowData.set(dlg.alignment);
                                rowData.setImagenPath(img);
                                rowData.setComments(obs);
                                setChange(true);
                            }
                        });
                        cm.getItems().add(mmi);
                        mmi = new MenuItem("Copy");
                        mmi.setOnAction((ActionEvent event1) -> {
                            AlignmentDialogInput dlg = new AlignmentDialogInput(rowData);
                            if (dlg.showModal()) {

                                data.add(dlg.alignment);
                            }
                        });
                        cm.getItems().add(mmi);
                        cm.show(table, event.getScreenX(), event.getScreenY());
                    } else if (event.getClickCount() == 1) {
                        if (!splitPane.getItems().contains(alignmentPane)) {
                            splitPane.getItems().add(alignmentPane);
                        }
                        Alignment rowData = row.getItem();
                        alignmentPane.setData(rowData);
                    }

                }

            });
            return row;
        });

        return this;
    }

    /**
     * 
     * @return True if project exists
     */
    public boolean exists() {
        return !workDir.isEmpty();
    }

    /**
     * 
     * @param name Project name
     */
    public Project(String name) {
        super(10);
        this.name = name;
        change = false;
    }

    /**
     * 
     * @param name Project name
     * @param workDir Work dir
     */
    public Project(String name, String workDir) {
        super(10);
        this.name = name;
        if (!workDir.endsWith(System.getProperty("file.separator"))) {
            workDir += System.getProperty("file.separator");
        }
        this.workDir = workDir;
        change = false;
    }

    public Project() {
        super(10);
        this.name = "";
        change = false;
    }

    /**
     * Open project from saved project file 
     * @throws JDOMException XML error
     * @throws IOException IO error
     * @throws ProcessException Format error
     */
    public void open() throws JDOMException, IOException, ProcessException {
        parseXML(new File(getWorkDir() + name + ".awb"));
        change = false;
    }

    /**
     * Save project 
     * @param workDir Work directory
     * @throws IOException IO error
     */
    public void save(String workDir) throws IOException {
        if (!workDir.endsWith(System.getProperty("file.separator"))) {
            workDir += System.getProperty("file.separator");
        }
        this.workDir = workDir;
        toXML(new File(workDir + name + ".awb"));
        change = false;
    }

    /**
     * Save project 
     * @throws IOException IO error
     */
    public void save() throws IOException {
        if (!workDir.isEmpty()) {
            if (!workDir.endsWith(System.getProperty("file.separator"))) {
                workDir += System.getProperty("file.separator");
            }
            toXML(new File(getWorkDir() + name + ".awb"));
            change = false;
        }
    }

    /**
     * 
     * @return Project name
     */
    public String getName() {
        return name;
    }

    /**
     * 
     * @param name Project name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Parse XML project
     * @param file XML file
     * @throws JDOMException XML error
     * @throws IOException IO error
     * @throws ProcessException Format error
     */
    public void parseXML(File file) throws JDOMException, IOException, ProcessException {
        SAXBuilder saxBuilder = new SAXBuilder();
        Document document = saxBuilder.build(file);
        Element raiz = document.getRootElement();
        name = raiz.getAttributeValue("nombre");
        List<Element> aa = raiz.getChild("alineamientos").getChildren();
        data.clear();

        for (Element e : aa) {
            data.add(new Alignment(e));
        }
    }

    /**
     * Save project as XML file
     * @param file XML file
     * @throws FileNotFoundException File not found error
     * @throws IOException IO error
     * @throws NullPointerException Null pointer error 
     */
    public void toXML(File file) throws FileNotFoundException, IOException, NullPointerException {
        Element estudioElemento = new Element("estudio");
        Document doc = new Document(estudioElemento);
        estudioElemento.setAttribute("nombre", name);
        Element aa = new Element("alineamientos");
        estudioElemento.addContent(aa);
        for (int i = 0; i < data.size(); i++) {
            Alignment ali = (Alignment) data.get(i);
            aa.addContent(ali.getXMLElement());
        }

        XMLOutputter xmlOutput = new XMLOutputter();
        xmlOutput.setFormat(Format.getPrettyFormat());
        FileOutputStream out = null;
        try {
            xmlOutput.output(doc, out = new FileOutputStream(file));
        } finally {
            out.close();
        }
    }

    public static void main(String args[]) {
        File f;
        try {
            Project estudio = new Project("prueba");
            estudio.parseXML(new File("estudio.xml"));

        } catch (ProcessException | IOException | NullPointerException | JDOMException ex) {
            Logger.getLogger(Project.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * New alignment
     */
    private void newAlignment() {
        Alignment alignment = inputAlignment();
        boolean found = false;
        for (Object d : data) {
            Alignment a = (Alignment) d;
            if (alignment.equals(a)) {
                found = true;
                break;
            }
        }
        if (!found) {
            data.add(alignment);
            change = true;
        }
    }

    /**
     * @return work directory
     */
    public String getWorkDir() {
        return workDir;
    }

    /**
     * @return If true a change has been mada and project needs to be saved before
     * closing
     */
    public boolean isChange() {
        return change;
    }

    /**
     * @param change change to set
     */
    public void setChange(boolean change) {
        this.change = change;
    }

    /**
     * 
     * @param number
     * @return Factorial of a number
     */
    static private BigDecimal factorial(BigDecimal number) {
        if (number.compareTo(BigDecimal.ZERO) == 1) {
            BigDecimal m = new BigDecimal(number.longValue()).subtract(BigDecimal.ONE);
            BigDecimal r = new BigDecimal(number.longValue());

            while (m.compareTo(BigDecimal.ONE) == 1) {
                r = r.multiply(m);
                m = m.subtract(BigDecimal.ONE);
            }
            return r;
        } else {
            return BigDecimal.ONE;
        }

    }

    /**
     * Fortuitously probality calculation. Number od astronomically meaningful directions = 18
     * @param meaningful number of astronomically meaningful directions
     * @param coincidences Number of coincidences to within +- error of azimuth
     * @param total Number of horizon aligments
     * @param error Azimuth error
     * @return Fortuitously probality of a number of coincidences or more per a total of alignments
     */
    public static double Rosenfeldt(int meaningful, int coincidences, int total, double error) {
        BigDecimal P = new BigDecimal(meaningful);

        BigDecimal N = new BigDecimal(360).divide(new BigDecimal(2).multiply(new BigDecimal(error))); //margen de error
        BigDecimal S = new BigDecimal(total);

        BigDecimal resultado = BigDecimal.ZERO;

        for (int a = coincidences; a <= total; a++) {
            BigDecimal M = new BigDecimal(a);

            BigDecimal num = Project.factorial(P).multiply(Project.factorial(N.subtract(P)))
                    .multiply(Project.factorial(S)).multiply(Project.factorial(N.subtract(S)));
            BigDecimal den = Project.factorial(M).multiply(Project.factorial(P.subtract(M)))
                    .multiply(Project.factorial(S.subtract(M)))
                    .multiply(Project.factorial(N.add(M).subtract(S).subtract(P))).multiply(Project.factorial(N));
            BigDecimal res = num.divide(den, 5, RoundingMode.HALF_UP);
            resultado = resultado.add(res);
        }

        return resultado.doubleValue();
    }

    /**
     * Fortuitously probality calculation by Bernoulli process. 
      * @param meaningful number of astronomically meaningful directions
     * @param coincidences Number of coincidences to within +- error of azimuth
     * @param total Number of horizon aligments
     * @param error Azimuth error
     * @return Fortuitously probality of a number of coincidences or more per a total of alignments
     */
    public static double Bernoulli(int meaningful, int coincidences, int total, double error) {
        BigDecimal P = new BigDecimal(meaningful);
        BigDecimal N = new BigDecimal(360).divide(new BigDecimal(2).multiply(new BigDecimal(error))); //margen de error
        BigDecimal S = new BigDecimal(total);
        BigDecimal probabilidad = P.divide(N);

        BigDecimal resultado = BigDecimal.ZERO;

        for (int a = coincidences; a <= total; a++) {
            BigDecimal M = new BigDecimal(a);

            BigDecimal num = Project.factorial(new BigDecimal(total)).multiply(probabilidad.pow(a))
                    .multiply((BigDecimal.ONE.subtract(probabilidad)).pow(total - a));
            BigDecimal den = Project.factorial(M).multiply(Project.factorial(S.subtract(M)));
            BigDecimal res = num.divide(den, 5, RoundingMode.HALF_UP);
            resultado = resultado.add(res);
        }

        return resultado.doubleValue();
    }

    /**
     * Fortuitously probality calculation
     * @params pc Option Rosenfeldt or Bernoulli
     * @param pc 
     */
    private void fortuitousProbality(FortuitousProbabilityOption pc) {
        int total = 0;
        int coincidences = 0;
        for (int i = 0; i < data.size(); i++) {
            Alignment al = (Alignment) data.get(i);
            total++;
            DatedAstronomicalEvent fd = checkAstronomicalEvent(al.getComments());
            if (fd != null && fd.getAstronomicalEvent() != null) {
                coincidences++;
            }
        }
        double v = 0;
        switch (pc) {
        case BERNOULLI:
            v = Bernoulli(18, coincidences, total, 1);
            break;
        case ROSENFELDT:
            v = Rosenfeldt(18, coincidences, total, 1);
            break;
        }

        new MessageDialog(skeleton, String.format("Fortuitously probality %.2f%%", v * 100),
                MessageDialog.MessageType.INFO).show();

    }

    /**
     * Check astronomical event
     * @param description
     * @return 
     */
    static DatedAstronomicalEvent checkAstronomicalEvent(String description) {
        AstronomicalEvent f;
        int year;
        if (!description.isEmpty()) {
            String[] tDes = description.split(",");
            if (tDes.length == 2) {
                try {
                    year = Integer.valueOf(tDes[1]);
                    f = AstronomicalEvent.fromString(tDes[0]);
                    return new DatedAstronomicalEvent(f, year);
                } catch (NumberFormatException ex) {

                }
            }
        }
        return null;
    }

}