/*- * 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.datastructure; import java.io.File; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.citygml4j.CityGMLContext; import org.citygml4j.builder.jaxb.CityGMLBuilder; import org.citygml4j.builder.jaxb.CityGMLBuilderException; import org.citygml4j.factory.GMLGeometryFactory; import org.citygml4j.model.citygml.ade.ADEException; import org.citygml4j.model.citygml.ade.binding.ADEContext; import org.citygml4j.model.citygml.core.CityModel; import org.citygml4j.model.citygml.core.CityObjectMember; import org.citygml4j.model.module.citygml.CityGMLVersion; import org.citygml4j.xml.io.CityGMLOutputFactory; import org.citygml4j.xml.io.writer.CityGMLWriteException; import org.citygml4j.xml.io.writer.CityGMLWriter; import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.ErrorId; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import de.hft.stuttgart.citydoctor2.utils.Localization; import de.hft.stuttgart.citydoctor2.utils.QualityADEUtils; import de.hft.stuttgart.quality.QualityADEModule; import de.hft.stuttgart.quality.model.Validation; import de.hft.stuttgart.quality.model.jaxb.ErrorStatistics; import de.hft.stuttgart.quality.model.jaxb.FeatureStatistics; import de.hft.stuttgart.quality.model.jaxb.Statistics; import de.hft.stuttgart.quality.model.jaxb.ValidationPlan; /** * The complete CityGML model containing all features that are used in * CityDoctor * * @author Matthias Betz * */ public class CityDoctorModel { private static final String COULD_NOT_FIND_FEATURE = "Could not find feature: "; private List buildings; private List vegetation; private List bridges; private List land; private List roads; private List water; private CityModel cModel; private ParserConfiguration config; private String fileName; private File file; private List globalErrors; private boolean isValidated = false; private ValidationPlan plan; public CityDoctorModel(ParserConfiguration config, File file) { if (config == null) { throw new IllegalArgumentException("Parser configuration may not be null"); } this.fileName = file.getName(); this.config = config; this.file = file; buildings = new ArrayList<>(); vegetation = new ArrayList<>(); bridges = new ArrayList<>(); land = new ArrayList<>(); roads = new ArrayList<>(); water = new ArrayList<>(); globalErrors = new ArrayList<>(); } public boolean isValidated() { return isValidated; } public void setValidated(ValidationPlan plan) { this.plan = plan; this.isValidated = true; } public void addGlobalError(CheckError err) { globalErrors.add(err); } public void addGlobalErrors(List generalErrors) { globalErrors.addAll(generalErrors); } public List getGlobalErrors() { return globalErrors; } public File getFile() { return file; } public Stream createFeatureStream() { return Stream.of(buildings.stream(), vegetation.stream(), bridges.stream(), land.stream(), roads.stream(), water.stream()).flatMap(co -> co); } public void saveAs(String file) throws CityGMLBuilderException, CityGMLWriteException, ADEException { CityGMLContext gmlContext = CityGMLContext.getInstance(); // setup energy ade stuff, so the parser doesn't crash on encountering this if (!gmlContext.hasADEContexts()) { for (ADEContext adeContext : ServiceLoader.load(ADEContext.class)) { gmlContext.registerADEContext(adeContext); } } CityGMLBuilder builder = gmlContext.createCityGMLBuilder(); CityGMLOutputFactory factory = builder.createCityGMLOutputFactory(); try (CityGMLWriter writer = factory.createCityGMLWriter(new File(file))) { if (isValidated) { writer.setPrefix("qual", QualityADEModule.NAMESPACE_URI); writer.setSchemaLocation(QualityADEModule.NAMESPACE_URI, QualityADEModule.NAMESPACE_URI + "qualityAde.xsd"); // remove old model if available QualityADEUtils.removeValidation(cModel); // create new quality ade validation datastructures Validation val = createValidation(); // add to city model cModel.addGenericApplicationPropertyOfCityModel(val); } writer.setIndentString(" "); writer.setPrefixes(CityGMLVersion.DEFAULT); writer.setSchemaLocations(CityGMLVersion.DEFAULT); GMLGeometryFactory gmlFactory = new GMLGeometryFactory(); storeCityObjects(buildings, gmlFactory); storeCityObjects(vegetation, gmlFactory); storeCityObjects(bridges, gmlFactory); storeCityObjects(land, gmlFactory); storeCityObjects(roads, gmlFactory); storeCityObjects(water, gmlFactory); writer.write(cModel); cModel.unsetCityObjectMember(); } } private void storeCityObjects(List cos, GMLGeometryFactory gmlFactory) { for (CityObject co : cos) { QualityADEUtils.removeValidationResult(co); if (isValidated) { QualityADEUtils.writeQualityAde(co); } co.reCreateGeometries(gmlFactory, config); cModel.addCityObjectMember(new CityObjectMember(co.getGmlObject())); } } private Validation createValidation() { Validation val = new Validation(); val.setValidationDate(ZonedDateTime.now()); val.setValidationSoftware("CityDoctor " + Localization.getText(Localization.VERSION)); Statistics statistics = new Statistics(); Set errors = collectErrors(); Map errorCount = new HashMap<>(); for (CheckError e : errors) { errorCount.compute(e.getErrorId(), (k, v) -> { if (v == null) { return new AtomicInteger(1); } v.incrementAndGet(); return v; }); } for (Entry e : errorCount.entrySet()) { ErrorStatistics stats = new ErrorStatistics(); stats.setAmount(e.getValue().get()); stats.setName(QualityADEUtils.mapErrorIdToAdeId(e.getKey())); statistics.getErrorStatistics().add(stats); } statistics.setNumErrorBuildings(countValidatedCityObjects(buildings)); statistics.setNumErrorBridgeObjects(countValidatedCityObjects(bridges)); statistics.setNumErrorLandObjects(countValidatedCityObjects(land)); statistics.setNumErrorTransportation(countValidatedCityObjects(roads)); statistics.setNumErrorVegetation(countValidatedCityObjects(vegetation)); statistics.setNumErrorWaterObjects(countValidatedCityObjects(water)); val.setStatistics(statistics); val.setValidationPlan(plan); return val; } public Set collectErrors() { List errors = new ArrayList<>(); collectErrorsFromList(errors, buildings); collectErrorsFromList(errors, vegetation); collectErrorsFromList(errors, bridges); collectErrorsFromList(errors, land); collectErrorsFromList(errors, roads); collectErrorsFromList(errors, water); return new HashSet<>(errors); } private void collectErrorsFromList(List errors, List cos) { for (CityObject co : cos) { co.collectContainedErrors(errors); } } private FeatureStatistics countValidatedCityObjects(List cos) { int numChecked = 0; int numError = 0; for (CityObject co : cos) { if (co.isValidated()) { numChecked++; if (co.containsAnyError()) { numError++; } } } return new FeatureStatistics(numChecked, numError); } public String getFileName() { return fileName; } public void addBridge(BridgeObject coBridge) { bridges.add(coBridge); } public void addBuilding(Building coBuilding) { buildings.add(coBuilding); } public List getBuildings() { return buildings; } public List getBridges() { return bridges; } public void setCityModel(CityModel cModel) { this.cModel = cModel; } public CityModel getCityModel() { return cModel; } public List getTransportation() { return roads; } public List getWater() { return water; } public List getLand() { return land; } public List getVegetation() { return vegetation; } public void addWater(WaterObject wo) { water.add(wo); } public void addLand(LandObject lo) { land.add(lo); } public void addVegetation(Vegetation veg) { vegetation.add(veg); } public void addTransportation(TransportationObject to) { roads.add(to); } public int getNumberOfFeatures() { return buildings.size() + vegetation.size() + bridges.size() + land.size() + roads.size() + water.size(); } public ParserConfiguration getParserConfig() { return config; } public void replaceFeature(CityObject currentFeature, CityObject nextFeature) { if (nextFeature instanceof Building) { replaceBuilding(currentFeature, nextFeature); } else if (nextFeature instanceof BridgeObject) { replaceBridge(currentFeature, nextFeature); } else if (nextFeature instanceof TransportationObject) { replaceTransportationObject(currentFeature, nextFeature); } else if (nextFeature instanceof Vegetation) { replaceVegetation(currentFeature, nextFeature); } else if (nextFeature instanceof LandObject) { replaceLandObject(currentFeature, nextFeature); } else if (nextFeature instanceof WaterObject) { replaceWaterObject(currentFeature, nextFeature); } } private void replaceWaterObject(CityObject currentFeature, CityObject nextFeature) { int index = water.indexOf(currentFeature); if (index == -1) { throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in water objects"); } water.set(index, (WaterObject) nextFeature); } private void replaceLandObject(CityObject currentFeature, CityObject nextFeature) { int index = land.indexOf(currentFeature); if (index == -1) { throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in land objects"); } land.set(index, (LandObject) nextFeature); } private void replaceVegetation(CityObject currentFeature, CityObject nextFeature) { int index = vegetation.indexOf(currentFeature); if (index == -1) { throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in vegetation"); } vegetation.set(index, (Vegetation) nextFeature); } private void replaceTransportationObject(CityObject currentFeature, CityObject nextFeature) { int index = roads.indexOf(currentFeature); if (index == -1) { throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in transportation objects"); } roads.set(index, (TransportationObject) nextFeature); } private void replaceBridge(CityObject currentFeature, CityObject nextFeature) { int index = bridges.indexOf(currentFeature); if (index == -1) { throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in bridges"); } bridges.set(index, (BridgeObject) nextFeature); } private void replaceBuilding(CityObject currentFeature, CityObject nextFeature) { int index = buildings.indexOf(currentFeature); if (index == -1) { throw new IllegalStateException(COULD_NOT_FIND_FEATURE + currentFeature + " in buildings"); } buildings.set(index, (Building) nextFeature); } public void addCityObject(CityObject co) { if (co instanceof Building) { buildings.add((Building) co); } else if (co instanceof BridgeObject) { bridges.add((BridgeObject) co); } else if (co instanceof TransportationObject) { roads.add((TransportationObject) co); } else if (co instanceof Vegetation) { vegetation.add((Vegetation) co); } else if (co instanceof LandObject) { land.add((LandObject) co); } else if (co instanceof WaterObject) { water.add((WaterObject) co); } } }