/*- * Copyright 2020 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart * * This file is part of CityDoctor2. * * CityDoctor2 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CityDoctor2 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CityDoctor2. If not, see . */ package de.hft.stuttgart.citydoctor2.reporting.pdf; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import de.hft.stuttgart.citydoctor2.check.RequirementConfiguration; import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.CheckId; import de.hft.stuttgart.citydoctor2.check.CheckResult; import de.hft.stuttgart.citydoctor2.check.ErrorId; import de.hft.stuttgart.citydoctor2.check.ResultStatus; import de.hft.stuttgart.citydoctor2.check.ValidationConfiguration; import de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException; import de.hft.stuttgart.citydoctor2.datastructure.BoundarySurface; import de.hft.stuttgart.citydoctor2.datastructure.BridgeObject; import de.hft.stuttgart.citydoctor2.datastructure.Building; import de.hft.stuttgart.citydoctor2.datastructure.BuildingPart; import de.hft.stuttgart.citydoctor2.datastructure.CityObject; import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.LandObject; import de.hft.stuttgart.citydoctor2.datastructure.LinearRing; import de.hft.stuttgart.citydoctor2.datastructure.Opening; import de.hft.stuttgart.citydoctor2.datastructure.Polygon; import de.hft.stuttgart.citydoctor2.datastructure.TransportationObject; import de.hft.stuttgart.citydoctor2.datastructure.Vegetation; import de.hft.stuttgart.citydoctor2.datastructure.WaterObject; import de.hft.stuttgart.citydoctor2.reporting.StreamReporter; import de.hft.stuttgart.citydoctor2.utils.Localization; /** * Reporter to create a PDF report out of a stream of feature results * * @author Matthias Betz * */ public class PdfStreamReporter implements StreamReporter { private static final Logger logger = LogManager.getLogger(PdfStreamReporter.class); private static final String ERROR_COLOR = "red"; @SuppressWarnings("unused") private static final String WARNING_COLOR = "yellow"; private static final String OK_COLOR = "green"; private PdfReport report; private OutputStream outFile; private Map errorStatistics; private ValidationConfiguration config; private Section statistics; private Section vr; private Section buildings; private int numErrorBuildings; private int numOkBuildings; private Section vegetation; private int numErrorVegetation; private int numOkVegetation; private Section trans; private int numErrorTrans; private int numOkTrans; private Section bridge; private int numErrorBridge; private int numOkBridge; private Section water; private int numErrorWater; private int numOkWater; private Section land; private int numErrorLand; private int numOkLand; private Section globalErrors; private Map sectionMap = new HashMap<>(); public PdfStreamReporter(OutputStream pdfOutputFile, String fileName, ValidationConfiguration config) { this.config = config; errorStatistics = new HashMap<>(); outFile = pdfOutputFile; report = new PdfReport(); report.writeSourceFileName(fileName); writeEnvironment(); statistics = report.createSection("Statistics", true); statistics.addTextElement("Object distribution:"); writeValidationPlan(); vr = report.createSection("Validation Results", true); } private void writeValidationPlan() { Section vp = report.createSection("Validation Plan", true); vp.addTextElement("numberOfRoundingPlaces = " + config.getNumberOfRoundingPlaces()); vp.addTextElement("minVertexDistance = " + config.getMinVertexDistance()); if (config.getSchematronFilePath() != null && !config.getSchematronFilePath().isEmpty()) { vp.addTextElement("Schematron file: " + config.getSchematronFilePath()); } for (Entry e : config.getRequirements().entrySet()) { String text = e.getKey(); String color; if (e.getValue().isEnabled()) { text = text + " = enabled"; color = OK_COLOR; } else { text = text + " = disabled"; color = ERROR_COLOR; } vp.addTextElement(text, color); for (Entry parameter : e.getValue().getParameters().entrySet()) { vp.add10PtTextElement(parameter.getKey() + " = " + parameter.getValue(), 10); } } } private void writeEnvironment() { Section env = report.createSection("Environment"); env.addTextElement("The checks were executed under the following environment:"); Table t = new Table(2); t.setTitle("Name", "Value"); t.addRow("City Doctor Version", Localization.getText(Localization.VERSION)); t.addRow("Java VM-Version", System.getProperties().getProperty("java.vm.version")); t.addRow("Java VM-Vendor", System.getProperties().getProperty("java.vm.vendor")); t.addRow("Java Version", System.getProperties().getProperty("java.version")); t.addRow("OS Name", System.getProperties().getProperty("os.name")); t.addRow("OS Architecture", System.getProperties().getProperty("os.arch")); env.addTable(t); } @Override public void report(CityObject co) { List errorList = new ArrayList<>(); co.collectContainedErrors(errorList); Set errors = new HashSet<>(errorList); for (CheckError e : errors) { AtomicInteger errorCount = errorStatistics.computeIfAbsent(e.getErrorId(), k -> new AtomicInteger(0)); errorCount.incrementAndGet(); } boolean hasError = containsError(co); if (co instanceof Building) { reportBuilding(co, hasError); } else if (co instanceof Vegetation) { reportVegetation(co, hasError); } else if (co instanceof TransportationObject) { reportTrans(co, hasError); } else if (co instanceof BridgeObject) { reportBridge(co, hasError); } else if (co instanceof WaterObject) { reportWater(co, hasError); } else if (co instanceof LandObject) { reportLand(co, hasError); } else { throw new IllegalStateException("Unknown City Object found: " + co.getClass()); } } private void reportLand(CityObject co, boolean hasError) { if (land == null) { land = vr.createSubSection("Land Objects"); } Section lSection = land.createSubSection(co.getGmlId().getGmlString()); sectionMap.put(co.getGmlId().getGmlString(), lSection); if (hasError) { numErrorLand++; lSection.setHeadlineColor(ERROR_COLOR); } else { numOkLand++; lSection.setHeadlineColor(OK_COLOR); } writeErrorForCityObject(co, lSection); } private void reportWater(CityObject co, boolean hasError) { if (water == null) { water = vr.createSubSection("Water Objects"); } Section wSection = water.createSubSection(co.getGmlId().getGmlString()); sectionMap.put(co.getGmlId().getGmlString(), wSection); if (hasError) { numErrorWater++; wSection.setHeadlineColor(ERROR_COLOR); } else { numOkWater++; wSection.setHeadlineColor(OK_COLOR); } writeErrorForCityObject(co, wSection); } private void reportBridge(CityObject co, boolean hasError) { if (bridge == null) { bridge = vr.createSubSection("Bridge Objects"); } Section bSection = bridge.createSubSection(co.getGmlId().getGmlString()); sectionMap.put(co.getGmlId().getGmlString(), bSection); if (hasError) { numErrorBridge++; bSection.setHeadlineColor(ERROR_COLOR); } else { numOkBridge++; bSection.setHeadlineColor(OK_COLOR); } writeErrorForCityObject(co, bSection); } private void reportTrans(CityObject co, boolean hasError) { if (trans == null) { trans = vr.createSubSection("Transportation Objects"); } Section tSection = trans.createSubSection(co.getGmlId().getGmlString()); sectionMap.put(co.getGmlId().getGmlString(), tSection); if (hasError) { numErrorTrans++; tSection.setHeadlineColor(ERROR_COLOR); } else { numOkTrans++; tSection.setHeadlineColor(OK_COLOR); } writeErrorForCityObject(co, tSection); TransportationObject to = (TransportationObject) co; for (TransportationObject transO : to.getChildren()) { writeCheckResultForTransportationObject(transO, tSection); } } private void reportVegetation(CityObject co, boolean hasError) { if (vegetation == null) { vegetation = vr.createSubSection("Vegetation"); } Section vSection = vegetation.createSubSection(co.getGmlId().getGmlString()); sectionMap.put(co.getGmlId().getGmlString(), vSection); if (hasError) { numErrorVegetation++; vSection.setHeadlineColor(ERROR_COLOR); } else { numOkVegetation++; vSection.setHeadlineColor(OK_COLOR); } writeErrorForCityObject(co, vSection); } private void reportBuilding(CityObject co, boolean hasError) { if (buildings == null) { buildings = vr.createSubSection("Buildings"); } Section bSection = buildings.createSubSection(co.getGmlId().getGmlString()); sectionMap.put(co.getGmlId().getGmlString(), bSection); if (hasError) { bSection.setHeadlineColor(ERROR_COLOR); } else { bSection.setHeadlineColor(OK_COLOR); } writeErrorForCityObject(co, bSection); Building b = (Building) co; for (BuildingPart bp : b.getBuildingParts()) { sectionMap.put(bp.getGmlId().getGmlString(), bSection); writeCheckResultForBuildingPart(bp, bSection); } for (BoundarySurface bs : b.getBoundarySurfaces()) { writeCheckResultForBoundarySurface(bs, bSection); } } private void writeCheckResultForTransportationObject(TransportationObject to, Section root) { Map results = to.getAllCheckResults(); writeCheckResults(results.values(), root); for (Geometry geom : to.getGeometries()) { writeCheckResultForGeometry(geom, root); } } private void writeCheckResultForBuildingPart(BuildingPart bp, Section root) { Map results = bp.getAllCheckResults(); writeCheckResults(results.values(), root); for (Geometry geom : bp.getGeometries()) { writeCheckResultForGeometry(geom, root); } for (BoundarySurface bs : bp.getBoundarySurfaces()) { writeCheckResultForBoundarySurface(bs, root); } } private void writeErrorForCityObject(CityObject co, Section section) { Map results = co.getAllCheckResults(); writeCheckResults(results.values(), section); for (Geometry geom : co.getGeometries()) { writeCheckResultForGeometry(geom, section); } } private void writeCheckResultForBoundarySurface(BoundarySurface bs, Section root) { Map results = bs.getAllCheckResults(); writeCheckResults(results.values(), root); for (Opening o : bs.getOpenings()) { writeCheckResultForOpening(o, root); } } private void writeCheckResultForOpening(Opening o, Section root) { Map results = o.getAllCheckResults(); writeCheckResults(results.values(), root); } private void writeCheckResultForGeometry(Geometry geom, Section root) { Map results = geom.getAllCheckResults(); writeCheckResults(results.values(), root); for (Polygon poly : geom.getPolygons()) { writeCheckResultForPolygon(poly, root); } } private void writeCheckResultForPolygon(Polygon poly, Section root) { Map results = poly.getAllCheckResults(); writeCheckResults(results.values(), root); writeCheckResultForLinearRing(poly.getExteriorRing(), root); for (LinearRing ring : poly.getInnerRings()) { writeCheckResultForLinearRing(ring, root); } } private void writeCheckResultForLinearRing(LinearRing ring, Section root) { Map results = ring.getAllCheckResults(); writeCheckResults(results.values(), root); } private void writeCheckResults(Collection results, Section root) { for (CheckResult cr : results) { if (cr.getResultStatus() == ResultStatus.ERROR) { writeError(root, cr.getError()); } } } private void writeError(Section root, CheckError error) { String text = "Error " + error.getErrorId().toString() + ":"; root.add12PtTextElement(text, 0); PdfErrorHandler handler = new PdfErrorHandler(root, config.getParserConfiguration()); error.report(handler); root.addError(error); } private boolean containsError(CityObject co) { return co.containsAnyError(); } @Override public void finishReport() throws CheckReportWriteException { for (Section s : buildings.getSubSections()) { if (!s.hasErrors()) { numOkBuildings++; // building has no errors, no table continue; } numErrorBuildings++; Table t = new Table(2); t.setTableColumnWidth(75, 25); t.setTitle("Error", "Count"); for (Entry e : s.getStats().getErrorCounts().entrySet()) { t.addRow(e.getKey(), e.getValue().toString()); } s.addTable(1, t); } int numBuildings = numErrorBuildings + numOkBuildings; if (numBuildings > 0) { statistics.addDistributionBar("Buildings", numErrorBuildings, numOkBuildings); } int numVegetation = numErrorVegetation + numOkVegetation; if (numVegetation > 0) { statistics.addDistributionBar("Vegetation", numErrorVegetation, numOkVegetation); } int numtr = numErrorTrans + numOkTrans; if (numtr > 0) { statistics.addDistributionBar("Transportation Objects", numErrorTrans, numOkTrans); } int numbri = numErrorBridge + numOkBridge; if (numbri > 0) { statistics.addDistributionBar("Bridge Objects", numErrorBridge, numOkBridge); } int numWater = numErrorWater + numOkWater; if (numWater > 0) { statistics.addDistributionBar("Water Objects", numErrorWater, numOkWater); } int numLand = numErrorLand + numOkLand; if (numLand > 0) { statistics.addDistributionBar("Land Objects", numErrorLand, numOkLand); } statistics.addTextElement("Error Statistics:"); for (Entry e : errorStatistics.entrySet()) { statistics.add10PtTextElement(e.getKey().toString() + ": " + e.getValue().intValue(), 10); } report.save(outFile); } @Override public void reportGlobalError(CheckError err) { AtomicInteger errorCount = errorStatistics.computeIfAbsent(err.getErrorId(), k -> new AtomicInteger(0)); errorCount.incrementAndGet(); if (globalErrors == null) { globalErrors = vr.createSubSection("Global Errors"); } String text = "Global error " + err.getErrorId(); globalErrors.add10PtTextElement(text, 10); PdfErrorHandler handler = new PdfErrorHandler(globalErrors, config.getParserConfiguration()); err.report(handler); } @Override public void addError(String gmlId, CheckError err) { AtomicInteger errorCount = errorStatistics.computeIfAbsent(err.getErrorId(), k -> new AtomicInteger(0)); errorCount.incrementAndGet(); Section section = sectionMap.get(gmlId); if (section == null) { logger.warn("No section found for gml id: {}, adding to global errors instead", gmlId); reportGlobalError(err); } else { section.setHeadlineColor(ERROR_COLOR); writeError(section, err); section.addError(err); } } }