/*-
* 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 extends CheckError> 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 extends CityObject> 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 extends CityObject> cos) {
for (CityObject co : cos) {
co.collectContainedErrors(errors);
}
}
private FeatureStatistics countValidatedCityObjects(List extends CityObject> 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);
}
}
}