Commit 671dfe38 authored by duminil's avatar duminil
Browse files

RegionChooser: Moving some classes to GeoLibs, for later integration in...

RegionChooser: Moving some classes to GeoLibs, for later integration in Workflows (e.g. ConvexHullCalculators)
parent 6ec8f7ea
......@@ -6,6 +6,5 @@
<classpathentry kind="con" path="org.eclipse.fx.ide.jdt.core.JAVAFX_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry combineaccessrules="false" kind="src" path="/GeoLibs"/>
<classpathentry kind="lib" path="lib/vtd-xml_2_13_1.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
package eu.simstadt.regionchooser;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;
import com.vividsolutions.jts.algorithm.ConvexHull;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.ximpleware.NavException;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
import eu.simstadt.regionchooser.citygml_parser.BuildingXmlNode;
import eu.simstadt.regionchooser.citygml_parser.CityGmlIterator;
public class ConvexHullCalculator
{
public static Geometry calculateFromCityGML(Path citygmlPath)
throws NumberFormatException, XPathParseException, NavException, XPathEvalException, IOException {
GeometryFactory geometryFactory = new GeometryFactory();
ArrayList<Coordinate> allPoints = new ArrayList<Coordinate>();
CityGmlIterator citygml = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : citygml) {
allPoints.add(new Coordinate(buildingXmlNode.xMin, buildingXmlNode.yMin));
allPoints.add(new Coordinate(buildingXmlNode.xMin, buildingXmlNode.yMax));
allPoints.add(new Coordinate(buildingXmlNode.xMax, buildingXmlNode.yMin));
allPoints.add(new Coordinate(buildingXmlNode.xMax, buildingXmlNode.yMax));
}
ConvexHull ch = new com.vividsolutions.jts.algorithm.ConvexHull(
allPoints.toArray(new Coordinate[allPoints.size()]), geometryFactory);
// Convert convex hull in original coordinates to WGS84 coordinates.
// NOTE: It would be much easier with import org.geotools.referencing.CRS; instead of Proj4J
// NOTE: It's faster to convert to WGS84 once the convex hull is calculated, because there are fewer points
Coordinate[] convexHullcoordinates = ch.getConvexHull().getCoordinates();
CRSFactory CRS_FACTORY = new CRSFactory();
CoordinateReferenceSystem wgs84 = CRS_FACTORY.createFromName("EPSG:4326");
CoordinateReferenceSystem originalCRS = citygml.getCRS();
BasicCoordinateTransform transformToWgs84 = new BasicCoordinateTransform(originalCRS, wgs84);
for (int i = 0; i < convexHullcoordinates.length; i++) {
ProjCoordinate wgs84Coordinate = transformToWgs84.transform(
new ProjCoordinate(convexHullcoordinates[i].x, convexHullcoordinates[i].y),
new ProjCoordinate());
convexHullcoordinates[i] = new Coordinate(wgs84Coordinate.x, wgs84Coordinate.y);
}
return geometryFactory.createPolygon(convexHullcoordinates);
}
}
......@@ -14,8 +14,8 @@
import com.ximpleware.NavException;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
import eu.simstadt.regionchooser.citygml_parser.BuildingXmlNode;
import eu.simstadt.regionchooser.citygml_parser.CityGmlIterator;
import eu.simstadt.geo.fast_xml_parser.BuildingXmlNode;
import eu.simstadt.geo.fast_xml_parser.CityGmlIterator;
public class RegionExtractor
......
package eu.simstadt.regionchooser.citygml_parser;
import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDNav;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
public class BuildingXmlNode
{
private int buildingOffset;
private int buildingLength;
private VTDNav navigator;
private AutoPilot coordinatesFinder;
public Double x;
public Double xMin;
public Double xMax;
public Double yMin;
public Double yMax;
public Double y;
public BuildingXmlNode(VTDNav navigator, int buildingOffset, int buildingLength)
throws NumberFormatException, XPathParseException, XPathEvalException, NavException {
this.navigator = navigator;
this.coordinatesFinder = new AutoPilot(navigator);
this.buildingLength = buildingLength;
this.buildingOffset = buildingOffset;
extractCoordinates(); //NOTE: Should it be done lazily? Is there any reason to extract a BuildingXmlNode without coordinates?
}
private void extractCoordinates()
throws XPathParseException, NumberFormatException, XPathEvalException, NavException {
int coordinatesCount = 0;
double xTotal = 0;
double yTotal = 0;
double x, y;
double xMin = Double.MAX_VALUE;
double xMax = Double.MIN_VALUE;
double yMin = Double.MAX_VALUE;
double yMax = Double.MIN_VALUE;
coordinatesFinder.selectXPath(".//posList|.//pos");
while (coordinatesFinder.evalXPath() != -1) {
long offsetAndLength = navigator.getContentFragment();
int coordinatesOffset = (int) offsetAndLength;
int coordinatesLength = (int) (offsetAndLength >> 32);
String posList = navigator.toRawString(coordinatesOffset, coordinatesLength);
String[] coordinates = posList.trim().split("\\s+");
for (int k = 0; k < coordinates.length; k = k + 3) {
coordinatesCount++;
x = Double.valueOf(coordinates[k]);
y = Double.valueOf(coordinates[k + 1]);
if (x < xMin) {
xMin = x;
}
if (y < yMin) {
yMin = y;
}
if (x > xMax) {
xMax = x;
}
if (y > yMax) {
yMax = y;
}
xTotal += x;
yTotal += y;
}
}
this.xMin = xMin;
this.xMax = xMax;
this.yMin = yMin;
this.yMax = yMax;
this.x = xTotal / coordinatesCount;
this.y = yTotal / coordinatesCount;
}
public String toString() {
try {
return navigator.toRawString(buildingOffset, buildingLength);
} catch (NavException ex) {
return null;
}
}
}
package eu.simstadt.regionchooser.citygml_parser;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Optional;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
import eu.simstadt.geo.GeoUtils;
public class CityGmlIterator implements Iterable<BuildingXmlNode>
{
private static final Logger LOGGER = Logger.getLogger(CityGmlIterator.class.getName());
private AutoPilot buildingsFinder;
private VTDNav navigator;
private int buildingOffset = 0;
private int buildingLength = 0;
private Path citygmlPath;
private Pattern srsNamePattern = Pattern.compile("(?i)(?<=srsName=\")[^\"]+(?=\")");
/*
* Simple class to parse a CityGML and extract cityObjectMember XML nodes and their coordinates. Since the
* coordinates are extracted for RegionChooser, it's okay to not be perfectly robust, but it should be fast and not
* use much memory. A SaxParser would use even less memory but might be harder to code and slower to run.
*
* Based on VTD XML, it provides a Building iterator.
*
*/
public CityGmlIterator(Path citygmlPath)
throws XPathParseException, NavException, NumberFormatException, XPathEvalException, IOException {
this.citygmlPath = citygmlPath;
VTDGen parser = new VTDGen();
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!
}
@Override
public Iterator<BuildingXmlNode> iterator() {
Iterator<BuildingXmlNode> it = new Iterator<BuildingXmlNode>() {
@Override
public boolean hasNext() {
try {
return buildingsFinder.evalXPath() != -1;
} catch (XPathEvalException | NavException ex) {
LOGGER.warning("Error while parsing " + citygmlPath);
return false;
}
}
@Override
public BuildingXmlNode next() {
try {
long offsetAndLength = navigator.getElementFragment();
buildingOffset = (int) offsetAndLength;
buildingLength = (int) (offsetAndLength >> 32);
return new BuildingXmlNode(navigator, buildingOffset, buildingLength);
} catch (NavException | NumberFormatException | XPathParseException | XPathEvalException ex) {
LOGGER.warning("Error while parsing " + citygmlPath);
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return it;
}
public String getHeader() throws NavException {
return navigator.toRawString(0, buildingOffset);
}
public Object getFooter() throws IOException, NavException {
int footerOffset = buildingOffset + buildingLength;
int footerLength = (int) (Files.size(citygmlPath) - footerOffset);
return navigator.toRawString(footerOffset, footerLength);
}
/*
* Fast scan of the 50 first lines to look for srsName
*
*/
public CoordinateReferenceSystem getCRS() throws IOException {
Optional<String> line = Files.lines(citygmlPath).limit(50).filter(srsNamePattern.asPredicate()).findFirst();
if (line.isPresent()) {
Matcher matcher = srsNamePattern.matcher(line.get());
matcher.find();
return GeoUtils.crsFromSrsName(matcher.group());
} else {
throw new IllegalArgumentException("No srsName found in the header of " + citygmlPath);
}
}
}
package eu.simstadt.regionchooser.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Test;
import com.ximpleware.NavException;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
import eu.simstadt.regionchooser.citygml_parser.BuildingXmlNode;
import eu.simstadt.regionchooser.citygml_parser.CityGmlIterator;
public class CitygmlParserTests
{
private void testCRSandNoNanInCoordinates(Path citygmlPath, String crsName)
throws NumberFormatException, XPathParseException, NavException, XPathEvalException, IOException {
CityGmlIterator buildingXmlNodes = new CityGmlIterator(citygmlPath);
assertEquals(crsName, buildingXmlNodes.getCRS().toString());
for (BuildingXmlNode buildingXmlNode : buildingXmlNodes) {
assertFalse("Coordinate should be a double", Double.isNaN(buildingXmlNode.x));
assertFalse("Coordinate should be a double", Double.isNaN(buildingXmlNode.y));
assertFalse("Coordinate should be a double", Double.isNaN(buildingXmlNode.xMax));
assertFalse("Coordinate should be a double", Double.isNaN(buildingXmlNode.yMax));
assertFalse("Coordinate should be a double", Double.isNaN(buildingXmlNode.xMin));
assertFalse("Coordinate should be a double", Double.isNaN(buildingXmlNode.yMin));
assertTrue("Coordinates Min/Max should be plausible", buildingXmlNode.xMax > buildingXmlNode.x);
assertTrue("Coordinates Min/Max should be plausible", buildingXmlNode.yMax > buildingXmlNode.y);
assertTrue("Coordinates Min/Max should be plausible", buildingXmlNode.xMin < buildingXmlNode.x);
assertTrue("Coordinates Min/Max should be plausible", buildingXmlNode.yMin < buildingXmlNode.y);
}
}
@Test
public void testExtractCoordsFromStuttgart()
throws NumberFormatException, XPathParseException, NavException, XPathEvalException, IOException {
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_buildings_and_trees.gml");
testCRSandNoNanInCoordinates(citygmlPath, "EPSG:31467");
}
@Test
public void testExtractCoordsFromGruenbuehl() throws Throwable {
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Gruenbuehl.proj/20140218_Gruenbuehl_LOD2_1building.gml");
testCRSandNoNanInCoordinates(citygmlPath, "EPSG:31467");
}
@Test
public void testExtractCoordsFromMunich() throws Throwable {
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Muenchen.proj/Munich_v_1_0_0.gml");
testCRSandNoNanInCoordinates(citygmlPath, "EPSG:32632");
}
@Test
public void testExtractCoordsFromNYC() throws Throwable {
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("NewYork.proj/ManhattanSmall.gml");
testCRSandNoNanInCoordinates(citygmlPath, "EPSG:32118");
}
}
package eu.simstadt.regionchooser.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Test;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import eu.simstadt.regionchooser.ConvexHullCalculator;
public class ConvexHullCalculatorTests
{
private static final GeometryFactory gf = new GeometryFactory();
@Test
public void testExtractConvexHullFromOneBuilding() throws Throwable {
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Gruenbuehl.proj/20140218_Gruenbuehl_LOD2_1building.gml");
Geometry hull = ConvexHullCalculator.calculateFromCityGML(citygmlPath);
assertEquals(hull.getCoordinates().length, 4 + 1); // Convex hull of a building should be a closed rectangle
Point someBuildingPoint = gf.createPoint(new Coordinate(9.216845, 48.878196)); // WGS84
assertTrue("Hull should contain every building point", hull.contains(someBuildingPoint));
}
@Test
public void testExtractConvexHullFromOneSmallRegion() throws Throwable {
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Gruenbuehl.proj/Gruenbuehl_LOD2_ALKIS_1010.gml");
Geometry hull = ConvexHullCalculator.calculateFromCityGML(citygmlPath);
assertTrue(hull.getCoordinates().length > 4); // Convex hull should have at least 4 corners
// Point somewhereBetweenBuildings = gf.createPoint(new Coordinate(3515883.6668538367, 5415843.300640578)); // Original coordinates, GSK3
Point somewhereBetweenBuildings = gf.createPoint(new Coordinate(9.21552249084, 48.87980446)); // WGS84
assertTrue("Hull should contain region between buildings", hull.contains(somewhereBetweenBuildings));
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment