Commits (6)
......@@ -32,12 +32,12 @@
<artifactId>picocli</artifactId>
<version>4.6.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.osgeo/proj4j -->
<!-- https://mvnrepository.com/artifact/org.locationtech.proj4j/proj4j -->
<!-- More recent than from osgeo -->
<dependency>
<groupId>org.osgeo</groupId>
<groupId>org.locationtech.proj4j</groupId>
<artifactId>proj4j</artifactId>
<version>0.1.0</version>
<version>1.1.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.locationtech.jts/jts-core -->
<dependency>
......
......@@ -8,7 +8,7 @@
import java.nio.file.Paths;
import java.util.Scanner;
import java.util.concurrent.Callable;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import eu.simstadt.regionchooser.RegionChooserCLI.GetVersion;
import picocli.CommandLine;
import picocli.CommandLine.Command;
......
......@@ -15,10 +15,10 @@
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;
import org.locationtech.proj4j.BasicCoordinateTransform;
import org.locationtech.proj4j.CRSFactory;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.ProjCoordinate;
public class RegionChooserUtils
......
......@@ -16,8 +16,8 @@
import com.ximpleware.NavException;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
import eu.simstadt.regionchooser.fast_xml_parser.BuildingXmlNode;
import eu.simstadt.regionchooser.fast_xml_parser.CityGmlIterator;
import eu.simstadt.regionchooser.fast_xml_parser.CityObjectMember;
public class RegionExtractor
......@@ -51,8 +51,9 @@
static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Writer sb,
Path... citygmlPaths) throws ParseException, XPathParseException, NavException, IOException {
int buildingsCount = 0;
int cityObjectsCount = 0;
int foundBuildingsCount = 0;
int foundVegetationCount = 0;
Geometry poly = WKT_READER.read(wktPolygon);
CityGmlIterator citygml = null;
......@@ -61,20 +62,24 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr
LOGGER.info("Parsing " + citygmlPath);
//TODO: Allow citygmlPath for folders too, and iterate over gmls?
citygml = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : citygml) {
if (buildingsCount == 0) {
for (CityObjectMember cityObjectNode : citygml) {
if (cityObjectsCount == 0) {
sb.append(replaceEnvelopeInHeader(citygml.getHeader(), poly.getEnvelopeInternal(), srsName));
}
buildingsCount += 1;
if (buildingXmlNode.hasCoordinates()) {
Coordinate coord = new Coordinate(buildingXmlNode.x, buildingXmlNode.y);
cityObjectsCount += 1;
if (cityObjectNode.hasCoordinates()) {
Coordinate coord = new Coordinate(cityObjectNode.x, cityObjectNode.y);
Point point = GEOMETRY_FACTORY.createPoint(coord);
if (point.within(poly)) {
foundBuildingsCount++;
sb.append(buildingXmlNode.toString());
if (cityObjectNode.isBuilding()) {
foundBuildingsCount++;
} else {
foundVegetationCount++;
}
sb.append(cityObjectNode.toString());
}
}
if (buildingsCount % 1000 == 0) {
if (cityObjectsCount % 1000 == 0) {
LOGGER.info("1000 buildings parsed");
}
}
......@@ -89,6 +94,9 @@ static int selectRegionDirectlyFromCityGML(String wktPolygon, String srsName, Wr
}
LOGGER.info("Buildings found in selected region : " + foundBuildingsCount);
if (foundVegetationCount > 0) {
LOGGER.info("Vegetation found in selected region : " + foundVegetationCount);
}
//NOTE: This could be a problem if header starts with <core:CityModel> and footer ends with </CityModel>
sb.append(citygml.getFooter());
return foundBuildingsCount;
......
......@@ -13,7 +13,7 @@
import com.ximpleware.XPathParseException;
public class CityGmlIterator implements Iterable<BuildingXmlNode>
public class CityGmlIterator implements Iterable<CityObjectMember>
{
private static final Logger LOGGER = Logger.getLogger(CityGmlIterator.class.getName());
......@@ -42,12 +42,12 @@ public CityGmlIterator(Path citygmlPath) throws XPathParseException {
parser.parseFile(citygmlPath.toString(), false);
this.navigator = parser.getNav();
this.buildingsFinder = new AutoPilot(navigator);
buildingsFinder.selectXPath("/CityModel/cityObjectMember[Building]"); //TODO: Check it's the only correct possibility. //FIXME: BuildingPart too!
buildingsFinder.selectXPath(CityObjectMember.XPATH_PATTERN);
}
@Override
public Iterator<BuildingXmlNode> iterator() {
return new Iterator<BuildingXmlNode>() {
public Iterator<CityObjectMember> iterator() {
return new Iterator<CityObjectMember>() {
@Override
public boolean hasNext() {
......@@ -60,12 +60,12 @@ public boolean hasNext() {
}
@Override
public BuildingXmlNode next() {
public CityObjectMember next() {
try {
long offsetAndLength = navigator.getElementFragment();
buildingOffset = (int) offsetAndLength;
buildingLength = (int) (offsetAndLength >> 32);
return new BuildingXmlNode(navigator, buildingOffset, buildingLength);
return new CityObjectMember(navigator, buildingOffset, buildingLength);
} catch (NavException | NumberFormatException | XPathParseException | XPathEvalException ex) {
LOGGER.warning("Error while parsing " + citygmlPath);
}
......
......@@ -7,13 +7,18 @@
import com.ximpleware.XPathParseException;
public class BuildingXmlNode
/**
* XML Node representing a CityObjectMember
*
*/
public class CityObjectMember
{
static final String XPATH_PATTERN = "/CityModel/cityObjectMember[Building or SolitaryVegetationObject or PlantCover]";
private int buildingOffset;
private int buildingLength;
private int nodeOffset;
private int nodeLength;
private VTDNav navigator;
private AutoPilot coordinatesFinder;
public Double x;
public Double xMin;
public Double xMax;
......@@ -22,20 +27,31 @@
public Double y;
private int coordinatesCount = 0;
public BuildingXmlNode(VTDNav navigator, int buildingOffset, int buildingLength)
public CityObjectMember(VTDNav navigator, int nodeOffset, int nodeLength)
throws XPathParseException, XPathEvalException, NavException {
this.navigator = navigator;
this.coordinatesFinder = new AutoPilot(navigator);
this.buildingLength = buildingLength;
this.buildingOffset = buildingOffset;
this.nodeLength = nodeLength;
this.nodeOffset = nodeOffset;
extractCoordinates();
//TODO: Get Building ID too, in order to avoid duplicates?
}
public boolean hasCoordinates() {
return coordinatesCount > 0;
}
public boolean isBuilding() {
try {
this.navigator.push();
AutoPilot checkBuilding = new AutoPilot(this.navigator);
checkBuilding.selectXPath("./Building");
return checkBuilding.evalXPath() != -1;
} catch (XPathEvalException | NavException | XPathParseException ex) {
return false;
} finally {
this.navigator.pop();
}
}
private void extractCoordinates()
throws XPathParseException, XPathEvalException, NavException {
double xTotal = 0;
......@@ -47,6 +63,9 @@ private void extractCoordinates()
double tempYMin = Double.MAX_VALUE;
double tempYMax = Double.MIN_VALUE;
AutoPilot coordinatesFinder = new AutoPilot(navigator);
coordinatesFinder.selectXPath(".//posList|.//pos");
while (coordinatesFinder.evalXPath() != -1) {
long offsetAndLength = navigator.getContentFragment();
......@@ -84,7 +103,7 @@ private void extractCoordinates()
public String toString() {
try {
return navigator.toRawString(buildingOffset, buildingLength);
return navigator.toRawString(nodeOffset, nodeLength);
} catch (NavException ex) {
return "";
}
......
......@@ -16,7 +16,7 @@
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import com.ximpleware.XPathParseException;
import eu.simstadt.regionchooser.RegionChooserUtils;
......@@ -55,7 +55,7 @@ public static Geometry calculateFromCityGML(Path citygmlPath) throws XPathParseE
GeometryFactory geometryFactory = new GeometryFactory();
ArrayList<Coordinate> allPoints = new ArrayList<>();
CityGmlIterator citygml = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : citygml) {
for (CityObjectMember buildingXmlNode : citygml) {
if (buildingXmlNode.hasCoordinates()) {
allPoints.add(new Coordinate(buildingXmlNode.xMin, buildingXmlNode.yMin));
allPoints.add(new Coordinate(buildingXmlNode.xMin, buildingXmlNode.yMax));
......
......@@ -99,7 +99,7 @@ void testExtractRegionFromTwoCitygmlsInWGS84() throws IOException {
assertTrue(err.toString().contains(expectedLog), err.toString() + " should contain " + expectedLog);
assertTrue(Files.exists(outGML));
assertTrue(Files.size(outGML) > 300_000);
assertEquals(22, countBuildings(outGML));
assertEquals(23, countBuildings(outGML));
}
@Test
......
......@@ -9,6 +9,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import org.locationtech.proj4j.CoordinateReferenceSystem;
class RegionExtractorTests
......@@ -164,6 +165,31 @@ void testExtractBuildingsFrom2Citygmls() throws Throwable {
assertEquals(17 + 3, countRegexMatches(gmlFromTwoGMLs, "<bldg:Building gml:id"));
}
@Test
void testExtractBuildingsAndTrees() throws Throwable {
String wktPolygon = "POLYGON((9.944100 49.802694, 9.944092 49.802490, 9.944975 49.802466, 9.944956 49.802689, 9.944100 49.802694))";
Path citygml = TEST_REPOSITORY.resolve("Others.proj/BuildingsAndTrees.gml");
CoordinateReferenceSystem crs = RegionChooserUtils.crsFromCityGMLHeader(citygml);
wktPolygon = RegionChooserUtils.wktPolygonToLocalCRS(wktPolygon, crs);
StringWriter gmlWriter = new StringWriter();
int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, crs.getName(), gmlWriter, citygml);
String gmlWithSomeBuildingAndTrees = gmlWriter.toString();
assertEquals(4, count);
assertEquals(4, countRegexMatches(gmlWithSomeBuildingAndTrees, "<bldg:Building gml:id"));
assertEquals(1, countRegexMatches(gmlWithSomeBuildingAndTrees, "<veg:PlantCover gml:id"));
assertEquals(3, countRegexMatches(gmlWithSomeBuildingAndTrees, "<veg:SolitaryVegetationObject gml:id"));
assertFalse(gmlWithSomeBuildingAndTrees.contains("DEBY_LOD2_605230"),
"Building in another corner should not be included");
assertFalse(gmlWithSomeBuildingAndTrees.contains("e15722a4-42dd"),
"Building in another corner should not be included");
assertTrue(gmlWithSomeBuildingAndTrees.contains("9801a0c9-d792-4b64-9ea1-76d829cc42ea"),
"Tree in corner should be included");
assertTrue(gmlWithSomeBuildingAndTrees.contains("DEBY_LOD2_605227"), "Building in corner should be included");
}
@Test
void testExtractBuildingsAndRemoveOld() throws Throwable {
String wktPolygon = "POLYGON((293229.6831819388 5623753.072371232,293681.22751166753 5623744.274551504,293668.8482257676 5623469.512992135,293197.09954629745 5623504.821467172,293229.6831819388 5623753.072371232))";
......@@ -173,7 +199,6 @@ void testExtractBuildingsAndRemoveOld() throws Throwable {
StringWriter gmlWriter = new StringWriter();
int count = RegionExtractor.selectRegionDirectlyFromCityGML(wktPolygon, "EPSG:25832", gmlWriter, citygmlPath);
String oneAachenBuilding = gmlWriter.toString();
System.out.println(oneAachenBuilding);
assertEquals(1, count);
assertEquals(1, countRegexMatches(oneAachenBuilding, CITY_OBJECT_MEMBER_REGEX));
assertTrue(oneAachenBuilding.contains("DENW39AL10003jfi"));
......
......@@ -9,7 +9,7 @@
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.io.ParseException;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import com.ximpleware.NavException;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
......
......@@ -17,18 +17,19 @@
private void testNoNanInCoordinates(Path citygmlPath) throws XPathParseException {
CityGmlIterator buildingXmlNodes = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : buildingXmlNodes) {
assertTrue(buildingXmlNode.hasCoordinates(), "Buildings should have coordinates");
assertFalse(Double.isNaN(buildingXmlNode.x), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(buildingXmlNode.y), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(buildingXmlNode.xMax), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(buildingXmlNode.yMax), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(buildingXmlNode.xMin), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(buildingXmlNode.yMin), COORDINATE_SHOULD_BE_A_DOUBLE);
assertTrue(buildingXmlNode.xMax > buildingXmlNode.x, COORDINATES_SHOULD_BE_PLAUSIBLE);
assertTrue(buildingXmlNode.yMax > buildingXmlNode.y, COORDINATES_SHOULD_BE_PLAUSIBLE);
assertTrue(buildingXmlNode.xMin < buildingXmlNode.x, COORDINATES_SHOULD_BE_PLAUSIBLE);
assertTrue(buildingXmlNode.yMin < buildingXmlNode.y, COORDINATES_SHOULD_BE_PLAUSIBLE);
for (CityObjectMember cityObjectNode : buildingXmlNodes) {
assertTrue(cityObjectNode.hasCoordinates(), "Building and vegetations should have coordinates");
assertFalse(Double.isNaN(cityObjectNode.x), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(cityObjectNode.y), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(cityObjectNode.xMax), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(cityObjectNode.yMax), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(cityObjectNode.xMin), COORDINATE_SHOULD_BE_A_DOUBLE);
assertFalse(Double.isNaN(cityObjectNode.yMin), COORDINATE_SHOULD_BE_A_DOUBLE);
// Some SolitaryVegetationObjects are defined with a single point.
assertTrue(cityObjectNode.xMax >= cityObjectNode.x, COORDINATES_SHOULD_BE_PLAUSIBLE);
assertTrue(cityObjectNode.yMax >= cityObjectNode.y, COORDINATES_SHOULD_BE_PLAUSIBLE);
assertTrue(cityObjectNode.xMin <= cityObjectNode.x, COORDINATES_SHOULD_BE_PLAUSIBLE);
assertTrue(cityObjectNode.yMin <= cityObjectNode.y, COORDINATES_SHOULD_BE_PLAUSIBLE);
}
}
......@@ -37,6 +38,16 @@ public void testExtractCoordsFromStuttgart() throws XPathParseException {
Path repo = Paths.get(REGION_CHOOSER_TESTDATA, "Stuttgart.proj");
Path citygmlPath = repo.resolve("Stuttgart_LOD0_LOD1_buildings_and_trees.gml");
testNoNanInCoordinates(citygmlPath);
CityGmlIterator buildingXmlNodes = new CityGmlIterator(citygmlPath);
boolean foundBuildings = false;
boolean foundTrees = false;
for (CityObjectMember cityObjectNode : buildingXmlNodes) {
boolean isBuilding = cityObjectNode.isBuilding();
foundBuildings |= isBuilding;
foundTrees |= !isBuilding;
}
assertTrue(foundBuildings, "At least one building should have been found.");
assertTrue(foundTrees, "At least one tree should have been found.");
}
@Test
......@@ -66,7 +77,7 @@ public void testExtractNoCoordsFromEmptyBuilding() throws XPathParseException {
Path citygmlPath = repo.resolve("Stöckach_empty_buildings.gml");
CityGmlIterator buildingXmlNodes = new CityGmlIterator(citygmlPath);
int counter = 0;
for (BuildingXmlNode buildingXmlNode : buildingXmlNodes) {
for (CityObjectMember buildingXmlNode : buildingXmlNodes) {
assertFalse(buildingXmlNode.hasCoordinates(), "Empty Buildings shouldn't have coordinates");
assertTrue(Double.isNaN(buildingXmlNode.x), "Coordinate should be a Nan");
assertTrue(Double.isNaN(buildingXmlNode.y), "Coordinate should be a Nan");
......
This diff is collapsed.