/* * Copyright (C) 2012 Addition, Lda. (addition at addition dot pt) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even 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. If not, see http://www.gnu.org/licenses/. */ package org.addition.epanet.ui; import org.addition.epanet.Constants; import org.addition.epanet.hydraulic.io.AwareStep; import org.addition.epanet.hydraulic.io.HydraulicReader; import org.addition.epanet.msx.ENToolkit2; import org.addition.epanet.msx.EpanetMSX; import org.addition.epanet.msx.MsxReader; import org.addition.epanet.msx.Structures.Species; import org.addition.epanet.network.FieldsMap; import org.addition.epanet.network.Network; import org.addition.epanet.network.PropertiesMap; import org.addition.epanet.network.structures.Link; import org.addition.epanet.network.structures.Node; import org.addition.epanet.quality.QualityReader; import org.addition.epanet.util.ENException; import org.addition.epanet.util.Utilities; import org.addition.epanet.util.XLSXWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.util.Arrays; import java.util.Iterator; /** * This class handles the XLSX generation from the binary files created by Epanet and MSX simulations, the reported * fields are configured via the ReportOptions class. */ public class ReportGenerator { /** * Hydraulic report fields. */ public enum HydVariable { HYDR_VARIABLE_HEAD(0, "Node head", true), HYDR_VARIABLE_DEMANDS(1, "Node actual demand", true), HYDR_VARIABLE_PRESSURE(2, "Node pressure", true), HYDR_VARIABLE_FLOWS(3, "Link flows", false), HYDR_VARIABLE_VELOCITY(4, "Link velocity", false), HYDR_VARIABLE_HEADLOSS(5, "Link unit headloss", false), HYDR_VARIABLE_FRICTION(6, "Link friction factor", false); public final boolean isNode; public final int id; public final String name; HydVariable(int val, String text, boolean node) { id = val; name = text; isNode = node; } public static String[] getNames() { String[] ret = new String[values().length]; for (int i = 0; i < HydVariable.values().length; i++) ret[i] = HydVariable.values()[i].name; return ret; } } /** * Quality report fields. */ public enum QualVariable { QUAL_VARIABLE_NODES(0, "Node quality", true), QUAL_VARIABLE_LINKS(1, "Link quality", false); //QUAL_VARIABLE_RATE (2,"Link reaction rate",false); public final boolean isNode; public final int id; public final String name; QualVariable(int val, String text, boolean node) { id = val; name = text; isNode = node; } public static String[] getNames() { String[] ret = new String[values().length]; for (int i = 0; i < QualVariable.values().length; i++) ret[i] = QualVariable.values()[i].name; return ret; } } private XLSXWriter sheet; private File xlsxFile; /** * Current report time. */ private long Rtime; public ReportGenerator(File xlsxFile) { this.xlsxFile = xlsxFile; sheet = new XLSXWriter(); } /** * Set excel cells transposition mode. * * @param value */ public void setTransposedMode(boolean value) { sheet.setTransposedMode(value); } /** * Get current report time progress. * * @return */ public long getRtime() { return Rtime; } /** * Generate hydraulic report. * * @param hydBinFile Abstract representation of the hydraulic simulation output file. * @param net Hydraulic network. * @param values Variables report flag. * @throws IOException * @throws org.addition.epanet.util.ENException * */ public void createHydReport(File hydBinFile, Network net, boolean[] values) throws IOException, ENException { Rtime = 0; HydraulicReader dseek = new HydraulicReader(new RandomAccessFile(hydBinFile, "r")); int reportCount = (int) ((net.getPropertiesMap().getDuration() - net.getPropertiesMap().getRstart()) / net.getPropertiesMap().getRstep()) + 1; Object[] nodesHead = new String[dseek.getNodes() + 1]; if (sheet.getTransposedMode()) nodesHead[0] = "Node/Time"; else nodesHead[0] = "Time/Node"; int count = 1; for (Node node : net.getNodes()) { nodesHead[count++] = node.getId(); } Object[] linksHead = new String[dseek.getLinks() + 1]; if (sheet.getTransposedMode()) linksHead[0] = "Link/Time"; else linksHead[0] = "Time/Link"; count = 1; for (Link link : net.getLinks()) { linksHead[count++] = link.getId(); } XLSXWriter.Spreadsheet resultSheets[] = new XLSXWriter.Spreadsheet[HydVariable.values().length]; Arrays.fill(resultSheets, null); for (HydVariable var : HydVariable.values()) { if (values == null || values[var.id]) { resultSheets[var.id] = sheet.newSpreadsheet(var.name); if (var.isNode) { if (sheet.getTransposedMode()) resultSheets[var.id].prepareTranspose(nodesHead.length, reportCount + 1); resultSheets[var.id].addRow(nodesHead); } else { if (sheet.getTransposedMode()) resultSheets[var.id].prepareTranspose(linksHead.length, reportCount + 1); resultSheets[var.id].addRow(linksHead); } } } Object nodeRow[] = new Object[dseek.getNodes() + 1]; Object linkRow[] = new Object[dseek.getLinks() + 1]; for (long time = net.getPropertiesMap().getRstart(); time <= net.getPropertiesMap().getDuration(); time += net.getPropertiesMap().getRstep()) { AwareStep step = dseek.getStep(time); if (step != null) { nodeRow[0] = Utilities.getClockTime(time); linkRow[0] = Utilities.getClockTime(time); int i; // NODES HEADS if (resultSheets[HydVariable.HYDR_VARIABLE_HEAD.id] != null) { i = 0; for (Node node : net.getNodes()) nodeRow[i + 1] = (double) step.getNodeHead(i++, node, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_HEAD.id].addRow(nodeRow); } // NODES DEMANDS if (resultSheets[HydVariable.HYDR_VARIABLE_DEMANDS.id] != null) { i = 0; for (Node node : net.getNodes()) nodeRow[i + 1] = (double) step.getNodeDemand(i++, node, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_DEMANDS.id].addRow(nodeRow); } // NODES PRESSURE if (resultSheets[HydVariable.HYDR_VARIABLE_PRESSURE.id] != null) { i = 0; for (Node node : net.getNodes()) nodeRow[i + 1] = (double) step.getNodePressure(i++, node, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_PRESSURE.id].addRow(nodeRow); } // LINK FLOW if (resultSheets[HydVariable.HYDR_VARIABLE_FLOWS.id] != null) { i = 0; for (Link link : net.getLinks()) linkRow[i + 1] = (double) step.getLinkFlow(i++, link, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_FLOWS.id].addRow(linkRow); } // LINK VELOCITY if (resultSheets[HydVariable.HYDR_VARIABLE_VELOCITY.id] != null) { i = 0; for (Link link : net.getLinks()) linkRow[i + 1] = (double) step.getLinkVelocity(i++, link, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_VELOCITY.id].addRow(linkRow); } // LINK HEADLOSS if (resultSheets[HydVariable.HYDR_VARIABLE_HEADLOSS.id] != null) { i = 0; for (Link link : net.getLinks()) linkRow[i + 1] = (double) step.getLinkHeadLoss(i++, link, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_HEADLOSS.id].addRow(linkRow); } // LINK FRICTION if (resultSheets[HydVariable.HYDR_VARIABLE_FRICTION.id] != null) { i = 0; for (Link link : net.getLinks()) linkRow[i + 1] = (double) step.getLinkFriction(i++, link, net.getFieldsMap()); resultSheets[HydVariable.HYDR_VARIABLE_FRICTION.id].addRow(linkRow); } } Rtime = time; } dseek.close(); } /** * Generate quality report. * * @param qualFile Abstract representation of the quality simulation output file. * @param net Hydraulic network. * @param nodes Show nodes quality flag. * @param links Show links quality flag. * @throws IOException * @throws ENException */ public void createQualReport(File qualFile, Network net, boolean nodes, boolean links) throws IOException, ENException { Rtime = 0; QualityReader dseek = new QualityReader(net.getFieldsMap()); int reportCount = (int) ((net.getPropertiesMap().getDuration() - net.getPropertiesMap().getRstart()) / net.getPropertiesMap().getRstep()) + 1; dseek.open(qualFile); Object[] nodesHead = new String[dseek.getNodes() + 1]; if (sheet.getTransposedMode()) nodesHead[0] = "Node/Time"; else nodesHead[0] = "Time/Node"; int count = 1; for (Node node : net.getNodes()) { nodesHead[count++] = node.getId(); } Object[] linksHead = new String[dseek.getLinks() + 1]; if (sheet.getTransposedMode()) linksHead[0] = "Link/Time"; else linksHead[0] = "Time/Link"; count = 1; for (Link link : net.getLinks()) { linksHead[count++] = link.getId(); } XLSXWriter.Spreadsheet resultSheets[] = new XLSXWriter.Spreadsheet[HydVariable.values().length]; Arrays.fill(resultSheets, null); for (QualVariable var : QualVariable.values()) { if (var.isNode && nodes || !var.isNode && links) { resultSheets[var.id] = sheet.newSpreadsheet(var.name); if (var.isNode) { if (sheet.getTransposedMode()) resultSheets[var.id].prepareTranspose(nodesHead.length, reportCount + 1); resultSheets[var.id].addRow(nodesHead); } else { if (sheet.getTransposedMode()) resultSheets[var.id].prepareTranspose(linksHead.length, reportCount + 1); resultSheets[var.id].addRow(linksHead); } } } Object nodeRow[] = new Object[dseek.getNodes() + 1]; Object linkRow[] = new Object[dseek.getLinks() + 1]; Iterator<QualityReader.Step> qIt = dseek.iterator(); for (long time = net.getPropertiesMap().getRstart(); time <= net.getPropertiesMap().getDuration(); time += net.getPropertiesMap().getRstep()) { if (!qIt.hasNext()) { return; } QualityReader.Step step = qIt.next(); if (step != null) { nodeRow[0] = Utilities.getClockTime(time); linkRow[0] = Utilities.getClockTime(time); if (resultSheets[QualVariable.QUAL_VARIABLE_NODES.id] != null) { for (int i = 0; i < dseek.getNodes(); i++) nodeRow[i + 1] = (double) step.getNodeQuality(i); resultSheets[HydVariable.HYDR_VARIABLE_HEAD.id].addRow(nodeRow); } if (resultSheets[QualVariable.QUAL_VARIABLE_LINKS.id] != null) { for (int i = 0; i < dseek.getLinks(); i++) linkRow[i + 1] = (double) step.getLinkQuality(i); resultSheets[HydVariable.HYDR_VARIABLE_DEMANDS.id].addRow(linkRow); } Rtime = time; } } dseek.close(); } /** * Generate multi-species quality report. * * @param msxBin Abstract representation of the MSX simulation output file. * @param net Hydraulic network. * @param netMSX MSX network. * @param tk2 Hydraulic network - MSX bridge. * @param values Species report flag. * @throws IOException * @throws ENException */ public void createMSXReport(File msxBin, Network net, EpanetMSX netMSX, ENToolkit2 tk2, boolean[] values) throws IOException, ENException { Rtime = 0; org.addition.epanet.msx.Structures.Node[] nodes = netMSX.getNetwork().getNodes(); org.addition.epanet.msx.Structures.Link[] links = netMSX.getNetwork().getLinks(); String[] nSpecies = netMSX.getSpeciesNames(); int reportCount = (int) ((net.getPropertiesMap().getDuration() - net.getPropertiesMap().getRstart()) / net.getPropertiesMap().getRstep()) + 1; MsxReader reader = new MsxReader(nodes.length - 1, links.length - 1, nSpecies.length, netMSX.getResultsOffset()); int totalSpecies; if (values != null) { totalSpecies = 0; for (boolean b : values) if (b) totalSpecies++; } else totalSpecies = nSpecies.length; reader.open(msxBin); Object[] nodesHead = new String[nSpecies.length + 1]; if (sheet.getTransposedMode()) nodesHead[0] = "Node/Time"; else nodesHead[0] = "Time/Node"; Object[] linksHead = new String[nSpecies.length + 1]; if (sheet.getTransposedMode()) linksHead[0] = "Link/Time"; else linksHead[0] = "Time/Link"; int count = 1; for (int i = 0; i < nSpecies.length; i++) if (values == null || values[i]) { nodesHead[count] = nSpecies[i]; linksHead[count++] = nSpecies[i]; } Object nodeRow[] = new Object[totalSpecies + 1]; for (int i = 1; i < nodes.length; i++) { if (nodes[i].getRpt()) { XLSXWriter.Spreadsheet spr = sheet.newSpreadsheet("Node<<" + tk2.ENgetnodeid(i) + ">>"); if (sheet.getTransposedMode()) spr.prepareTranspose(nodesHead.length, reportCount + 1); spr.addRow(nodesHead); for (long time = net.getPropertiesMap().getRstart(), period = 0; time <= net.getPropertiesMap().getDuration(); time += net.getPropertiesMap().getRstep(), period++) { nodeRow[0] = Utilities.getClockTime(time); for (int j = 0, ji = 0; j < nSpecies.length; j++) { if (values == null || values[j]) nodeRow[ji++ + 1] = reader.getNodeQual((int) period, i, j + 1); } spr.addRow(nodeRow); } } } Object linkRow[] = new Object[totalSpecies + 1]; for (int i = 1; i < links.length; i++) { if (links[i].getRpt()) { XLSXWriter.Spreadsheet spr = sheet.newSpreadsheet("Link<<" + tk2.ENgetlinkid(i) + ">>"); if (sheet.getTransposedMode()) spr.prepareTranspose(linksHead.length, reportCount + 1); spr.addRow(linksHead); for (long time = net.getPropertiesMap().getRstart(), period = 0; time <= net.getPropertiesMap().getDuration(); time += net.getPropertiesMap().getRstep(), period++) { linkRow[0] = Utilities.getClockTime(time); for (int j = 0, ji = 0; j < nSpecies.length; j++) { if (values == null || values[j]) linkRow[ji++ + 1] = reader.getLinkQual((int) period, i, j + 1); } spr.addRow(linkRow); } } } reader.close(); } /** * Write the final worksheet. * * @throws IOException */ public void writeWorksheet() throws IOException { sheet.save(new FileOutputStream(xlsxFile)); sheet.finish(); } /** * Write simulation summary to one worksheet. * * @param inpFile Hydraulic network file. * @param net Hydraulic network. * @param msxFile MSX file. * @param msx MSX solver. * @throws IOException */ public void writeSummary(File inpFile, Network net, File msxFile, EpanetMSX msx) throws IOException { XLSXWriter.Spreadsheet sh = sheet.newSpreadsheet("Summary"); try { PropertiesMap pMap = net.getPropertiesMap(); FieldsMap fMap = net.getFieldsMap(); if (net.getTitleText() != null) for (int i = 0; i < net.getTitleText().size() && i < 3; i++) { if (net.getTitleText().get(i).length() > 0) { if (net.getTitleText().get(i).length() <= 70) sh.addRow(net.getTitleText().get(i)); } } sh.addRow("\n"); sh.addRow(Utilities.getText("FMT19"), inpFile.getName()); sh.addRow(Utilities.getText("FMT20"), net.getJunctions().size()); int nReservoirs = 0; int nTanks = 0; for (org.addition.epanet.network.structures.Tank tk : net.getTanks()) if (tk.getArea() == 0) nReservoirs++; else nTanks++; int nValves = net.getValves().size(); int nPumps = net.getPumps().size(); int nPipes = net.getLinks().size() - nPumps - nValves; sh.addRow(Utilities.getText("FMT21a"), nReservoirs); sh.addRow(Utilities.getText("FMT21b"), nTanks); sh.addRow(Utilities.getText("FMT22"), nPipes); sh.addRow(Utilities.getText("FMT23"), nPumps); sh.addRow(Utilities.getText("FMT24"), nValves); sh.addRow(Utilities.getText("FMT25"), pMap.getFormflag().parseStr); sh.addRow(Utilities.getText("FMT26"), Utilities.getClockTime(pMap.getHstep())); sh.addRow(Utilities.getText("FMT27"), pMap.getHacc()); sh.addRow(Utilities.getText("FMT27a"), pMap.getCheckFreq()); sh.addRow(Utilities.getText("FMT27b"), pMap.getMaxCheck()); sh.addRow(Utilities.getText("FMT27c"), pMap.getDampLimit()); sh.addRow(Utilities.getText("FMT28"), pMap.getMaxIter()); if (pMap.getQualflag() == PropertiesMap.QualType.NONE || pMap.getDuration() == 0.0) sh.addRow(Utilities.getText("FMT29"), "None"); else if (pMap.getQualflag() == PropertiesMap.QualType.CHEM) sh.addRow(Utilities.getText("FMT30"), pMap.getChemName()); else if (pMap.getQualflag() == PropertiesMap.QualType.TRACE) sh.addRow(Utilities.getText("FMT31"), "Trace From Node", net.getNode(pMap.getTraceNode()).getId()); else if (pMap.getQualflag() == PropertiesMap.QualType.AGE) sh.addRow(Utilities.getText("FMT32"), "Age"); if (pMap.getQualflag() != PropertiesMap.QualType.NONE && pMap.getDuration() > 0) { sh.addRow(Utilities.getText("FMT33"), "Time Step", Utilities.getClockTime(pMap.getQstep())); sh.addRow(Utilities.getText("FMT34"), "Tolerance", fMap.revertUnit(FieldsMap.Type.QUALITY, pMap.getCtol()), fMap.getField(FieldsMap.Type.QUALITY).getUnits()); } sh.addRow(Utilities.getText("FMT36"), pMap.getSpGrav()); sh.addRow(Utilities.getText("FMT37a"), pMap.getViscos() / Constants.VISCOS); sh.addRow(Utilities.getText("FMT37b"), pMap.getDiffus() / Constants.DIFFUS); sh.addRow(Utilities.getText("FMT38"), pMap.getDmult()); sh.addRow(Utilities.getText("FMT39"), fMap.revertUnit(FieldsMap.Type.TIME, pMap.getDuration()), fMap.getField(FieldsMap.Type.TIME).getUnits()); if (msxFile != null && msx != null) { sh.addRow(""); sh.addRow("MSX data file", msxFile.getName()); sh.addRow("Species"); Species[] spe = msx.getNetwork().getSpecies(); for (int i = 1; i < msx.getNetwork().getSpecies().length; i++) sh.addRow(spe[i].getId(), spe[i].getUnits()); } } catch (IOException e) { } catch (ENException e) { e.printStackTrace(); } } }