/*-
* 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.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.citygml4j.core.model.CityGMLVersion;
import org.citygml4j.core.model.core.CityModel;
import de.hft.stuttgart.citydoctor2.check.AbstractCheck;
import de.hft.stuttgart.citydoctor2.check.Check;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.exceptions.CityDoctorWriteException;
import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration;
import de.hft.stuttgart.citydoctor2.writer.CityGMLWriterUtils;
import de.hft.stuttgart.quality.model.types.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;
private CityGMLVersion cityGMLVersion;
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 ValidationPlan getValidationPlan() {
return plan;
}
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 CityDoctorWriteException {
if (file.endsWith(".off")) {
exportAsOff(file);
} else {
exportAsGML(file);
}
}
private void exportAsGML(String file) throws CityDoctorWriteException {
CityGMLWriterUtils.writeCityModel(file, this);
}
private void exportAsOff(String file) {
file = file.substring(0, file.lastIndexOf('.'));
File folder = new File(file);
folder.mkdirs();
createFeatureStream().forEach(co -> {
Map> polygonMap = new EnumMap<>(Lod.class);
Check c = new AbstractCheck() {
@Override
public void check(Geometry geom) {
polygonMap.compute(geom.getLod(), (lod, polys) -> {
if (polys == null) {
polys = new HashSet<>();
}
for (Polygon poly : geom.getPolygons()) {
polys.add(poly.getOriginal());
}
return polys;
});
}
};
co.accept(c);
Set highestLod = polygonMap.get(Lod.LOD2);
if (highestLod == null) {
highestLod = polygonMap.get(Lod.LOD1);
}
if (highestLod == null) {
return;
}
Map idMap = new LinkedHashMap<>();
int counter = 0;
for (Polygon poly : highestLod) {
counter = addRing(poly.getExteriorRing(), idMap, counter);
}
try (BufferedWriter writer = Files.newBufferedWriter(folder.toPath().resolve(co.getGmlId() + ".off"))) {
writer.write("OFF\n");
writer.write(Integer.toString(idMap.size()));
writer.write(' ');
writer.write(Integer.toString(highestLod.size()));
writer.write(" 0\n");
for (Vertex v : idMap.keySet()) {
writer.write(Double.toString(v.getX()));
writer.write(' ');
writer.write(Double.toString(v.getY()));
writer.write(' ');
writer.write(Double.toString(v.getZ()));
writer.write('\n');
}
for (Polygon poly : highestLod) {
writer.write(Integer.toString(poly.getExteriorRing().getVertices().size() - 1));
writer.write(' ');
for (int i = 0; i < poly.getExteriorRing().getVertices().size() - 1; i++) {
Vertex v = poly.getExteriorRing().getVertices().get(i);
writer.write(Integer.toString(idMap.get(v)));
writer.write(' ');
}
writer.write('\n');
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
private int addRing(LinearRing ring, Map idMap, int counter) {
for (Vertex v : ring.getVertices()) {
if (!idMap.containsKey(v)) {
idMap.put(v, counter);
counter++;
}
}
return counter;
}
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);
}
}
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 b) {
buildings.add(b);
} else if (co instanceof BridgeObject bo) {
bridges.add(bo);
} else if (co instanceof TransportationObject to) {
roads.add(to);
} else if (co instanceof Vegetation veg) {
vegetation.add(veg);
} else if (co instanceof LandObject lo) {
land.add(lo);
} else if (co instanceof WaterObject wo) {
water.add(wo);
}
}
public void setParsedCityGMLVersion(CityGMLVersion cityGMLVersion) {
this.cityGMLVersion = cityGMLVersion;
}
public CityGMLVersion getCityGMLVersion() {
return cityGMLVersion;
}
}