Commit 79c85e26 authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'standalone_regionchooser' into refactor

parents e875874a 3cc00bf0
......@@ -5,6 +5,9 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<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/proj4j-0.1.0.jar" sourcepath="lib/proj4j-0.1.0-sources.jar"/>
<classpathentry kind="lib" path="lib/citygml4j-2.10.2.jar" sourcepath="lib/citygml4j-2.10.2.zip"/>
<classpathentry kind="lib" path="lib/vtd-xml_2_13_1.jar"/>
<classpathentry kind="lib" path="lib/jts-core-1.16.1.jar" sourcepath="lib/jts-core-1.16.1-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
This script performs the common tasks required for compiling and deploying one or more Eclipse projects given
in ${projects} into one jar file as well as corresponding javadoc and sources zip files. ${target.path} must hold the
path and base-name (without extension) of the generated artifacts relative to the deployment directory given by
${deploy.dir.path}.
Thus, a correct usage from an ant script located in, say, ProjectOne would look like this:
<project default="deploy" basedir="..">
<property name="deploy.dir.path" location="${user.home}/Desktop/FooBar" />
<property name="target.path" value="lib/artefact" />
<property name="projects" value="ProjectOne,ProjectTwo,ProjectThree" />
<import file="deploy-common.xml" />
... specific tasks follow here ...
This script expects Java sources, properties files, fxml files and other resources to reside in the "src" directory of
each project, while all required jar libraries, if any, are expected directly under directory "lib" in each project.
Note that a version string has to be provided in "simstadt.version" below.
Note also that this script requires Ant version 1.9.4 or higher.
-->
<project name="CommonDeploy" xmlns:if="ant:if" xmlns:unless="ant:unless">
<property name="simstadt.version" value="0.9.1-SNAPSHOT" />
<property name="stage.dir" location="tmpdeploy/${ant.project.name}" />
<property name="src.dir" location="${stage.dir}/src" />
<dirname property="simstadt.src.dir" file="${ant.file.CommonDeploy}" />
<property name="test.dir" location="${stage.dir}/test" />
<property name="doc.dir" location="${stage.dir}/doc" />
<property name="classes.dir" location="${stage.dir}/classes" />
<property name="testclasses.dir" location="${stage.dir}/testclasses" />
<property name="report.dir" location="SimStadtTestReports" />
<property name="reports.dir" location="${report.dir}/${target.path}" />
<property name="deploy.dir.path" location="${user.home}/Desktop/SimStadt" />
<property name="deploy.dir" location="${deploy.dir.path} ${simstadt.version}" />
<property name="lib.dir" location="${deploy.dir}/lib" />
<property name="workflows.dir" location="${deploy.dir}/workflows" />
<property name="target.lib.prefix" location="${deploy.dir}/${target.path}-${simstadt.version}" /> <!-- How to define and use specific lib version? -->
<!-- <property name="doNotTest" value="true"/> Uncomment in order to disable tests, reports and coverage reports -->
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="SimStadt/lib/test/jacocoant.jar" />
</taskdef>
<path id="classpath">
<fileset dir="${lib.dir}">
<include name="*.jar" />
</fileset>
<fileset dir="${workflows.dir}/">
<include name="*.jar" />
</fileset>
<pathelement path="${classes.dir}" />
<pathelement path="${testclasses.dir}" />
<pathelement path="${simstadt.src.dir}/lib/test/junit.jar" />
</path>
<target name="print-classpath">
<property name="myclasspath" refid="classpath" />
<echo message="Classpath = ${myclasspath}" />
</target>
<target name="delete-deploy-dir">
<delete dir="${deploy.dir}" />
</target>
<target name="setup">
<fail message="Ant 1.9.4 or higher required">
<condition>
<not>
<antversion atleast="1.9.4" />
</not>
</condition>
</fail>
<!-- setup stage dir -->
<delete dir="${stage.dir}" />
<mkdir dir="${stage.dir}" />
<mkdir dir="${workflows.dir}" />
<!-- copy jar libraries -->
<mkdir dir="${lib.dir}" />
<copy todir="${deploy.dir}">
<multirootfileset id="project.lib.dirs" basedirs="${projects}">
<patternset>
<include name="**/lib/*.jar" />
<exclude name="**/lib/*-javadoc.jar" />
<exclude name="**/lib/*-sources.jar" />
</patternset>
</multirootfileset>
</copy>
<!-- copy project sources (without tests) -->
<mkdir dir="${src.dir}" />
<copy toDir="${stage.dir}">
<multirootfileset basedirs="${projects}">
<include name="**/src/**/*" />
</multirootfileset>
</copy>
<!-- copy project tests -->
<mkdir dir="${test.dir}" />
<copy toDir="${stage.dir}">
<multirootfileset basedirs="${projects}">
<include name="**/test/**/*" />
</multirootfileset>
</copy>
</target>
<target name="compile" depends="setup">
<mkdir dir="${classes.dir}" />
<mkdir dir="${testclasses.dir}" />
<javac includeantruntime="false" source="1.8" target="1.8" debug="true" srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath" encoding="UTF-8" />
<javac includeantruntime="false" source="1.8" target="1.8" debug="true" srcdir="${test.dir}" destdir="${testclasses.dir}" classpathref="classpath" encoding="UTF-8" />
</target>
<target name="deploy-common" depends="compile, git.revision">
<!-- copy all non Java resource files -->
<copy todir="${classes.dir}">
<fileset dir="${src.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
<!-- copy all non Java resource test files -->
<copy todir="${testclasses.dir}">
<fileset dir="${test.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
<!-- create jar with classes and resources from all modules -->
<jar destfile="${target.lib.prefix}.jar">
<fileset dir="${classes.dir}" />
<manifest>
<attribute name="Implementation-Title" value="${ant.project.name}" />
<attribute name="Implementation-Version" value="${simstadt.version} (${git.branch}, rev. ${git.revision}, ${git.date})" />
<attribute name="Implementation-URL" value="http://simstadt.hft-stuttgart.de/" />
</manifest>
</jar>
<!-- create javadoc, without error messages and at most 1 warning. -->
<javadoc access="protected" destdir="${doc.dir}" charset="UTF-8" additionalparam="-Xdoclint:none -Xmaxwarns 1" unless:set="doNotDoc">
<sourcepath path="${src.dir}" />
<classpath refid="classpath" />
</javadoc>
<!-- zip javadoc and sources -->
<zip destfile="${target.lib.prefix}-javadoc.zip" basedir="${doc.dir}" unless:set="doNotDoc"/>
<zip destfile="${target.lib.prefix}-sources.zip" basedir="${src.dir}" unless:set="doNotDoc"/>
</target>
<!-- Defines classpath for junit tests -->
<target name="test-common" depends="deploy-common">
<delete dir="${reports.dir}" />
<mkdir dir="${reports.dir}" />
<path id="test-classpath">
<fileset dir="${lib.dir}">
<include name="*.jar" />
</fileset>
<fileset dir="${workflows.dir}">
<include name="*.jar" />
</fileset>
<pathelement path="${java.class.path}" />
<pathelement path="${classes.dir}" />
<pathelement path="${testclasses.dir}" />
<pathelement path="${simstadt.src.dir}/lib/test/junit.jar" />
<pathelement path="${simstadt.src.dir}/lib/test/hamcrest-core-1.3.jar" />
</path>
</target>
<!-- Extract date and hash for the last GIT commit in master. Useful for manifest and deployed zip.
Properties are set to YYYY-MM-DD and XXXXX if they cannot be extracted.
-->
<available file=".git" type="dir" property="git.present" />
<target name="git.revision" description="Store git revision in ${repository.version}" if="git.present">
<exec executable="git" outputproperty="git.revision" failifexecutionfails="false" errorproperty="">
<arg value="log" />
<arg value="-n1" />
<arg value="--format=%h" />
</exec>
<exec executable="git" outputproperty="git.date" failifexecutionfails="false" errorproperty="">
<arg value="log" />
<arg value="-1" />
<arg value="--format=%cd" />
<arg value="--date=short" />
</exec>
<exec executable="git" outputproperty="git.branch" failifexecutionfails="false" errorproperty="">
<arg value="rev-parse" />
<arg value="--abbrev-ref" />
<arg value="HEAD" />
</exec>
<!-- Set default for git.revision -->
<condition property="git.revision" value="${git.revision}" else="XXXXXX">
<and>
<isset property="git.revision" />
<length string="${git.revision}" trim="yes" length="0" when="greater" />
</and>
</condition>
<!-- Set default for git.branch -->
<condition property="git.branch" value="${git.branch}" else="XXXXXX">
<and>
<isset property="git.branch" />
<length string="${git.branch}" trim="yes" length="0" when="greater" />
</and>
</condition>
<!-- Set default for git.date -->
<condition property="git.date" value="${git.date}" else="YYYY-MM-DD">
<and>
<isset property="git.date" />
<length string="${git.date}" trim="yes" length="0" when="greater" />
</and>
</condition>
<loadresource property="git.YYYYMMDD">
<propertyresource name="git.date" />
<filterchain>
<tokenfilter>
<filetokenizer />
<replacestring from="-" to="" />
</tokenfilter>
</filterchain>
</loadresource>
</target>
</project>
......@@ -10,8 +10,8 @@ Before executing check that all required modules/projects are enumerated in pro
Create a Jar file with RegionChooser libraries and executables
</description>
<property name="target.path" value="lib/region-chooser" />
<property name="projects" value="RegionChooser,GeoLibs" />
<import file="../SimStadt/deploy-common.xml" />
<property name="projects" value="RegionChooser" />
<import file="deploy-common.xml" />
<target name="deploy" depends="unit-test">
<echo file="${deploy.dir}/RegionChooser.bat">
java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.properties eu.simstadt.regionchooser.RegionChooserFX&#13;
......@@ -28,17 +28,18 @@ java -classpath lib/* -Xms512m -Xmx2g -Djava.util.logging.config.file=logging.pr
</target>
<target name="unit-test" depends="test-common" unless="doNotTest">
<jacoco:coverage destfile="${reports.dir}/${ant.project.name}.exec" xmlns:jacoco="antlib:org.jacoco.ant">
<junit printsummary="yes" haltonfailure="yes" fork="true">
<junit printsummary="yes" haltonfailure="yes" fork="true">
<classpath refid="test-classpath" />
<formatter type="xml" usefile="true" />
<formatter type="plain" usefile="true" />
<classpath refid="test-classpath" />
<formatter type="xml" usefile="true" />
<formatter type="plain" usefile="true" />
<test name="eu.simstadt.geo.fast_xml_parser.CitygmlParserTests" todir="${reports.dir}" />
<test name="eu.simstadt.geo.fast_xml_parser.ConvexHullCalculatorTests" todir="${reports.dir}" />
<!-- RegionExtractor -->
<test name="eu.simstadt.regionchooser.RegionExtractorTests" haltonfailure="no" todir="${reports.dir}" />
<test name="eu.simstadt.regionchooser.RegionExtractorWithDifferentInputTests" haltonfailure="yes" todir="${reports.dir}" />
</junit>
</jacoco:coverage>
<!-- RegionExtractor -->
<test name="eu.simstadt.regionchooser.RegionExtractorTests" haltonfailure="no" todir="${reports.dir}" />
<test name="eu.simstadt.regionchooser.RegionExtractorWithDifferentInputTests" haltonfailure="yes" todir="${reports.dir}" />
</junit>
</target>
</project>
\ No newline at end of file
</project>
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
package eu.simstadt.geo;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.osgeo.proj4j.BasicCoordinateTransform;
import org.osgeo.proj4j.CRSFactory;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.osgeo.proj4j.ProjCoordinate;
public class GeoUtils
{
private static final CRSFactory CRS_FACTORY = new CRSFactory();
public static final CoordinateReferenceSystem WGS84 = CRS_FACTORY.createFromName("EPSG:4326");
private static final Pattern srsNamePattern = Pattern.compile("(?i)(?<=srsName=[\"'])[^\"']+(?=[\"'])");
private GeoUtils() {
// only static use
}
/**
* Writes down the input coordinates in a string, without "." or "-". E.g. "N48_77__E9_18" for (48.77, 9.18) and
* "S12_345__W6_789" for (-12.345, -6.789).
*
* Useful for caching geolocated information. The process should be reversible.
*
* WARNING: LATITUDE FIRST!
*
* @param latitude
* @param longitude
* @return Filename friendly string for given coordinates
*/
public static String coordinatesForFilename(double latitude, double longitude) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance();
symbols.setDecimalSeparator('.');
DecimalFormat df = new DecimalFormat("+#.######;-#.######", symbols);
String lat = df.format(latitude).replace('-', 'S').replace('+', 'N');
String lon = df.format(longitude).replace('-', 'W').replace('+', 'E');
return (lat + "__" + lon).replace('.', '_');
}
/**
* The srsName (The name by which this reference system is identified) inside the CityGML file can have multiple
* formats. This method tries to parse the string and detect the corresponding reference system. If it is found, it
* returns a proj4j.CoordinateReferenceSystem. It throws an IllegalArgumentException otherwise.
*
* This method should be able to parse any EPSG id : e.g. "EPSG:1234". German Citygmls might also have "DE_DHDN_3GK3"
* or "ETRS89_UTM32" as srsName, so those are also included. It isn't guaranteed that those formats are correctly
* parsed, though.
*
* The EPSG ids and parameters are defined in resources ('nad/epsg') inside proj4j-0.1.0.jar. Some EPSG ids are
* missing though, e.g. 7415
*
* @param srsName
* @return CoordinateReferenceSystem
*/
public static CoordinateReferenceSystem crsFromSrsName(String srsName) {
// EPSG:31467
Pattern pEPSG = Pattern.compile("^(EPSG:\\d+)$");
Matcher mEPSG = pEPSG.matcher(srsName);
if (mEPSG.find()) {
return CRS_FACTORY.createFromName(srsName);
}
// urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
// or
// urn:ogc:def:crs,crs:EPSG::28992
Pattern pOGC = Pattern.compile("urn:ogc:def:crs(?:,crs)?:EPSG:[\\d\\.]*:([\\d]+)\\D*");
Matcher mOGC = pOGC.matcher(srsName);
if (mOGC.find()) {
return CRS_FACTORY.createFromName("EPSG:" + mOGC.group(1));
}
// urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
// urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
Pattern pURN = Pattern.compile("urn:adv:crs:([^\\*]+)");
Matcher mURN = pURN.matcher(srsName);
//NOTE: Could use a HashMap if the switch/case becomes too long.
if (mURN.find()) {
switch (mURN.group(1)) {
case "DE_DHDN_3GK2":
return CRS_FACTORY.createFromName("EPSG:31466");
case "DE_DHDN_3GK3":
return CRS_FACTORY.createFromName("EPSG:31467");
case "DE_DHDN_3GK4":
return CRS_FACTORY.createFromName("EPSG:31468");
case "DE_DHDN_3GK5":
return CRS_FACTORY.createFromName("EPSG:31469");
case "ETRS89_UTM32":
return CRS_FACTORY.createFromName("EPSG:25832");
default:
// nothing found
}
}
throw new IllegalArgumentException("Unknown srsName format: " + srsName);
}
/**
* Converts a jts.geom.Polygon from one CoordinateReferenceSystem to another.
*
* NOTE: It would be easier with org.geotools.referencing.CRS instead of Proj4J
*
* @param polygonInOriginalCRS
* @param originalCRS
* @param newCRS
* @return
*/
public static Polygon changePolygonCRS(Polygon polygonInOriginalCRS, CoordinateReferenceSystem originalCRS,
CoordinateReferenceSystem newCRS) {
GeometryFactory geometryFactory = new GeometryFactory();
Coordinate[] convexHullcoordinates = polygonInOriginalCRS.getCoordinates();
BasicCoordinateTransform transformToWgs84 = new BasicCoordinateTransform(originalCRS, newCRS);
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);
}
/**
*
* Fast scan of the 50 first lines of a Citygml file to look for srsName. It might not be as reliable as parsing the
* whole CityGML, but it should be much faster and use much less memory. For a more reliable solution, use
* GeoCoordinatesAccessor. This solution can be convenient for Web services, RegionChooser or HullExtractor.
*
* @param citygmlPath
* @return
* @throws IOException
*/
public static CoordinateReferenceSystem crsFromCityGMLHeader(Path citygmlPath) 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 crsFromSrsName(matcher.group());
} else {
throw new IllegalArgumentException("No srsName found in the header of " + citygmlPath);
}
}
/**
* The haversine formula determines the great-circle distance between two points on a sphere given their longitudes
* and latitudes. https://en.wikipedia.org/wiki/Haversine_formula
*
* @param latitude1
* @param longitude1
* @param latitude2
* @param longitude2
* @return distance in kilometer
*/
public static Double greatCircleDistance(Double latitude1, Double longitude1, Double latitude2, Double longitude2) {
final int earthRadius = 6371; // [km]. Radius of the earth
double latDistance = Math.toRadians(latitude2 - latitude1);
double lonDistance = Math.toRadians(longitude2 - longitude1);
double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + Math.cos(Math.toRadians(latitude1))
* Math.cos(Math.toRadians(latitude2)) * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadius * c;
}
/**
* Finds every CityGML in every .proj folder in a repository.
*
* @param repository
* @return a stream of CityGML Paths
* @throws IOException
*/
public static Stream<Path> everyCityGML(Path repository) throws IOException {
return Files.walk(repository, 2)
.sorted()
.filter(
p -> Files.isRegularFile(p) &&
p.getParent().toString().endsWith(".proj") &&
p.toString().toLowerCase().endsWith(".gml"));
}
}
package eu.simstadt.geo.fast_xml_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;
private int coordinatesCount = 0;
public BuildingXmlNode(VTDNav navigator, int buildingOffset, int buildingLength)
throws 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?
}
public boolean hasCoordinates() {
return coordinatesCount > 0;
}
private void extractCoordinates()
throws XPathParseException, XPathEvalException, NavException {
double xTotal = 0;
double yTotal = 0;
double tempX;
double tempY;
double tempXMin = Double.MAX_VALUE;
double tempXMax = Double.MIN_VALUE;
double tempYMin = Double.MAX_VALUE;
double tempYMax = 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++;
tempX = Double.valueOf(coordinates[k]);
tempY = Double.valueOf(coordinates[k + 1]);
if (tempX < tempXMin) {
tempXMin = tempX;
}
if (tempY < tempYMin) {
tempYMin = tempY;
}
if (tempX > tempXMax) {
tempXMax = tempX;
}
if (tempY > tempYMax) {
tempYMax = tempY;
}
xTotal += tempX;
yTotal += tempY;
}
}
this.xMin = tempXMin;
this.xMax = tempXMax;
this.yMin = tempYMin;
this.yMax = tempYMax;
this.x = xTotal / coordinatesCount;
this.y = yTotal / coordinatesCount;
}
public String toString() {
try {
return navigator.toRawString(buildingOffset, buildingLength);
} catch (NavException ex) {
return "";
}
}
}
package eu.simstadt.geo.fast_xml_parser;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.logging.Logger;
import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
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;
/*
* Simple class to parse a CityGML and extract cityObjectMember XML nodes and their coordinates. No other attribute
* is extracted. Not suitable for building simulation, perfect for quick and dirty extract of coordinates (e.g. for
* RegionChooser or HullExtractor). It should be fast and not use much memory. A SaxParser would use even less memory
* but might be harder to code and possibly slower to run.
*
* For a more complete and more robust (but slower) implementation, use eu.simstadt.geo.GeoCoordinatesAccessor
*
* Based on VTD XML, it provides a Building iterator.
*
*/
public CityGmlIterator(Path citygmlPath) throws XPathParseException {
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() {
return 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();
}
};
}
/**
* Returns Header of the Citygml, from the start of the file to the current building. This method needs to be called
* directly after the first building has been found.
*
* @return Citygml header
* @throws NavException
*/
public String getHeader() throws NavException {
return navigator.toRawString(0, buildingOffset);
}
/**
* Returns footer of the Citygml, from the end of the last building to the end of file. This method needs to be
* called after the last building has been found.
*
* @return Citygml footer
* @throws NavException
*/
public Object getFooter() throws IOException, NavException {
int footerOffset = buildingOffset + buildingLength;
int footerLength = (int) (Files.size(citygmlPath) - footerOffset);
return navigator.toRawString(footerOffset, footerLength);
}
}
package eu.simstadt.geo.fast_xml_parser;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.logging.Logger;
import org.locationtech.jts.algorithm.ConvexHull;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Polygon;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import com.ximpleware.XPathParseException;
import eu.simstadt.geo.GeoUtils;
public class ConvexHullCalculator
{
private static final Logger LOGGER = Logger.getLogger(ConvexHullCalculator.class.getName());
/**
* Relatively fast method to extract a convex hull in WGS84 for any Citygml file for which CRS is known and whose
* size is less than 2GB (VTD XML limitation). It iterates over every building, gets the bounding box as 4
* coordinates and calculates a convex hull of all those coordinates at the end of the iteration. It finally converts
* this convex hull to WGS84 coordinates.
*
* The convex hull is needed for RegionChooser. No SimstadtModel or CityDoctor model is needed, thanks to
* eu.simstadt.geo.fast_xml_parser.CityGmlIterator.
*
* E.g. : POLYGON ((9.219282617376651 48.876828283254675, 9.2175568365387 48.87695546490524, 9.213228008654541
* 48.87741235218009, 9.21293830332426 48.8774437308139, 9.212628150503749 48.87995232037036, 9.21263222062228
* 48.880912500705406, 9.213601288895058 48.88138432918416, 9.213701498914048 48.8813841435154, 9.217160380858063
* 48.88092123714512, 9.217396359933812 48.88085245173012, 9.217558989421159 48.880784074008496, 9.219328059150042
* 48.879894270949485, 9.219367632516049 48.876847095121995, 9.219367549551574 48.87682812171015, 9.219282617376651
* 48.876828283254675))
*
* for "20140218_Gruenbuehl_LOD2.gml"
*
* @param citygmlPath
* @return convex Hull as jts.geom.Geometry, with the originalCRS written in userData.
*/
public static Geometry calculateFromCityGML(Path citygmlPath) throws XPathParseException, IOException {
GeometryFactory geometryFactory = new GeometryFactory();
ArrayList<Coordinate> allPoints = new ArrayList<>();
CityGmlIterator citygml = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : citygml) {
if (buildingXmlNode.hasCoordinates()) {
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));
}
}
Coordinate[] pointsArray = allPoints.toArray(new Coordinate[allPoints.size()]);
ConvexHull ch = new org.locationtech.jts.algorithm.ConvexHull(pointsArray, geometryFactory);
// Convert convex hull in original coordinates to WGS84 coordinates.
// NOTE: It's faster to convert to WGS84 once the convex hull is calculated, because there are fewer points
Polygon originalConvexHull = (Polygon) ch.getConvexHull();
CoordinateReferenceSystem originalCRS = GeoUtils.crsFromCityGMLHeader(citygmlPath);
Polygon convexHull = GeoUtils.changePolygonCRS(originalConvexHull, originalCRS, GeoUtils.WGS84);
convexHull.setUserData(originalCRS.toString());
return convexHull;
}
/**
* Finds every CityGML in a folder recursively, extract their convex hull and send them to callback. Used by
* RegionChooser Javascript to display the CityGMLs.
*
* If hull has been extracted, get it from cache, calculate it otherwise.
*
* @param repository
* @param callback
* @throws IOException
*/
public static void extractHullsForEveryCityGML(Path repository, Consumer<String> callback) throws IOException {
LOGGER.info("Parsing " + repository);
GeoUtils.everyCityGML(repository)
.map(gmlPath -> {
LOGGER.fine("Importing " + repository.relativize(gmlPath));
try {
Path kmlPath = getHullPath(repository, gmlPath);
if (Files.exists(kmlPath)) {
LOGGER.fine("Using cache from " + repository.relativize(kmlPath));
return new String(Files.readAllBytes(kmlPath), StandardCharsets.UTF_8);
} else {
LOGGER.info("No hull found in cache for " + repository.relativize(gmlPath) + ". Creating it!");
return calculateConvexHullPlacemark(gmlPath, kmlPath, false);
}
} catch (IOException ex) {
ex.printStackTrace();
}
return null;
})
.filter(Objects::nonNull)
.forEach(hullKML -> {
if (hullKML.contains("<LinearRing>")) {
callback.accept(hullKML);
} else {
LOGGER.warning("Cache is empty!");
}
});
}
/**
* Writes a KML cache file for the convex hull of a CityGML file and returns the KML content as string. Depending on
* throwIfError, the method will either ignore any problem (and write the message inside the KML file (preventing the
* process from happening again next time) or stop the process, without returning or caching anything.
*
* @param gmlPath
* @param kmlPath
* @param throwIfError
* @return the KML content as a String
*/
public static String calculateConvexHullPlacemark(Path gmlPath, Path kmlPath, boolean throwIfError) {
String result;
try {
Geometry convexHull = ConvexHullCalculator.calculateFromCityGML(gmlPath);
//NOTE: Header and footer with <kml><Document> don't seem to be needed. They're easier to concatenate without.
StringBuilder kmlPlacemark = new StringBuilder();
kmlPlacemark.append(" <Placemark>\n");
kmlPlacemark.append(" <name>");
kmlPlacemark.append(gmlPath.getFileName());
kmlPlacemark.append("</name>\n");
kmlPlacemark.append(" <ExtendedData>\n");
kmlPlacemark.append(" <Data name=\"project\">\n");
kmlPlacemark.append(" <value>");
kmlPlacemark.append(gmlPath.getParent().getFileName().toString().replace(".proj", ""));
kmlPlacemark.append("</value>\n");
kmlPlacemark.append(" </Data>\n");
kmlPlacemark.append(" <Data name=\"srsName\">\n");
kmlPlacemark.append(" <value>");
kmlPlacemark.append(convexHull.getUserData());
kmlPlacemark.append("</value>\n");
kmlPlacemark.append(" </Data>\n");
kmlPlacemark.append(" </ExtendedData>\n");
kmlPlacemark.append(" <Polygon>\n");
kmlPlacemark.append(" <outerBoundaryIs>\n");
kmlPlacemark.append(" <LinearRing>\n");
kmlPlacemark.append(" <tessellate>1</tessellate>\n");
kmlPlacemark.append(" <coordinates>");
for (Coordinate coordinate : convexHull.getCoordinates()) {
kmlPlacemark.append(coordinate.x);
kmlPlacemark.append(",");
kmlPlacemark.append(coordinate.y);
kmlPlacemark.append(",0 ");
}
kmlPlacemark.append("</coordinates>\n");
kmlPlacemark.append(" </LinearRing>\n");
kmlPlacemark.append(" </outerBoundaryIs>\n");
kmlPlacemark.append(" </Polygon>\n");
kmlPlacemark.append(" </Placemark>\n");
result = kmlPlacemark.toString();
} catch (Exception ex) {
// Something went wrong.
LOGGER.severe("Couldn't calculate hull : " + ex.getMessage());
// Either throw again:
if (throwIfError) {
throw new RuntimeException(ex);
}
// Or write the error message inside a comment in the KML cache file. It could be dangerous (the file doesn't contain any geometry)
// but will prevent the same process from happening next time again. This could be desirable if the Error happens at the end of a 2GB file.
StringWriter errors = new StringWriter();
ex.printStackTrace(new PrintWriter(errors));
result = "<kml>\n<!--\n" + errors.toString() + "\n-->\n</kml>";
}
try (BufferedWriter writer = Files.newBufferedWriter(kmlPath)) {
writer.write(result);
} catch (IOException ex) {
ex.printStackTrace();
}
LOGGER.info("Convex hull written to cache.");
return result;
}
/**
* For a given CityGML Path, returns the Path at which KML hull should be cached. If needed it creates the '.cache'
* folder and hides it. The '.cache' folder isn't specific to the project: every kml cache file is written inside the
* same repository '.cache' folder.
*
* @param repository
* @param citygmlPath
*
* @return KML Path
* @throws IOException
*/
private static Path getHullPath(Path repository, Path citygmlPath) throws IOException {
String kmlFilename = citygmlPath.getFileName().toString().replaceAll("(?i)\\.gml$", ".kml");
Path cacheFolder = repository.resolve(".cache/");
Path hullsFolder = cacheFolder.resolve("hulls/");
Files.createDirectories(hullsFolder);
try {
Files.setAttribute(cacheFolder, "dos:hidden", true);
} catch (UnsupportedOperationException ex) {
// do nothing for non-windows os
}
return hullsFolder.resolve(kmlFilename);
}
}
package eu.simstadt.geo.fast_xml_parser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.Test;
import com.ximpleware.XPathParseException;
public class CitygmlParserTests
{
private static final String REGION_CHOOSER_TESTDATA = "../RegionChooser/test/testdata";
private static final String COORDINATES_SHOULD_BE_PLAUSIBLE = "Min/Max Coordinates should be plausible";
private static final String COORDINATE_SHOULD_BE_A_DOUBLE = "Coordinate should be a double";
private void testNoNanInCoordinates(Path citygmlPath) throws XPathParseException {
CityGmlIterator buildingXmlNodes = new CityGmlIterator(citygmlPath);
for (BuildingXmlNode buildingXmlNode : buildingXmlNodes) {
assertTrue("Buildings should have coordinates", buildingXmlNode.hasCoordinates());
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_SHOULD_BE_PLAUSIBLE, buildingXmlNode.xMax > buildingXmlNode.x);
assertTrue(COORDINATES_SHOULD_BE_PLAUSIBLE, buildingXmlNode.yMax > buildingXmlNode.y);
assertTrue(COORDINATES_SHOULD_BE_PLAUSIBLE, buildingXmlNode.xMin < buildingXmlNode.x);
assertTrue(COORDINATES_SHOULD_BE_PLAUSIBLE, buildingXmlNode.yMin < buildingXmlNode.y);
}
}
@Test
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);
}
@Test
public void testExtractCoordsFromGruenbuehl() throws XPathParseException {
Path repo = Paths.get(REGION_CHOOSER_TESTDATA, "Gruenbuehl.proj");
Path citygmlPath = repo.resolve("20140218_Gruenbuehl_LOD2_1building.gml");
testNoNanInCoordinates(citygmlPath);
}
@Test
public void testExtractCoordsFromMunich() throws XPathParseException {
Path repo = Paths.get(REGION_CHOOSER_TESTDATA, "Muenchen.proj");
Path citygmlPath = repo.resolve("Munich_v_1_0_0.gml");
testNoNanInCoordinates(citygmlPath);
}
@Test
public void testExtractCoordsFromNYC() throws XPathParseException {
Path repo = Paths.get(REGION_CHOOSER_TESTDATA, "NewYork.proj");
Path citygmlPath = repo.resolve("ManhattanSmall.gml");
testNoNanInCoordinates(citygmlPath);
}
@Test
public void testExtractNoCoordsFromEmptyBuilding() throws XPathParseException {
Path repo = Paths.get(REGION_CHOOSER_TESTDATA, "Stuttgart.proj");
Path citygmlPath = repo.resolve("Stöckach_empty_buildings.gml");
CityGmlIterator buildingXmlNodes = new CityGmlIterator(citygmlPath);
int counter = 0;
for (BuildingXmlNode buildingXmlNode : buildingXmlNodes) {
assertFalse("Empty Buildings shouldn't have coordinates", buildingXmlNode.hasCoordinates());
assertTrue("Coordinate should be a Nan", Double.isNaN(buildingXmlNode.x));
assertTrue("Coordinate should be a Nan", Double.isNaN(buildingXmlNode.y));
counter++;
}
assertEquals("3 buildings should have been analyzed", counter, 3);
}
}
package eu.simstadt.geo.fast_xml_parser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import com.ximpleware.XPathParseException;
import eu.simstadt.geo.GeoUtils;
public class ConvexHullCalculatorTests
{
private static final GeometryFactory gf = new GeometryFactory();
private static final Path repository = Paths.get("../RegionChooser/test/testdata");
@Test
public void testExtractConvexHullFromOneBuilding() throws IOException, XPathParseException {
Path citygmlPath = repository.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 IOException, XPathParseException {
Path citygmlPath = repository.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));
}
@Test
public void testExtractConvexHullFromStoeckachNoBuildingPart() throws IOException, XPathParseException {
Path citygmlPath = repository.resolve("Stuttgart.proj/Stöckach_überarbeitete GML-NoBuildingPart.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(9.195212, 48.789062)); // WGS84
assertTrue("Hull should contain region between buildings", hull.contains(somewhereBetweenBuildings));
}
@Test
public void testExtractConvexHullFromEveryCitygmlInRepository() throws IOException {
int minHullCount = 6;
//NOTE: Should cache be deleted first?
// Files.walk(repository.resolve(".cache/hulls"), 1).forEach(System.out::println);
long gmlCount = GeoUtils.everyCityGML(repository).count();
AtomicInteger hullCount = new AtomicInteger(0);
ConvexHullCalculator.extractHullsForEveryCityGML(repository, kmlHull -> {
assertTrue("KML hull should contain project name", kmlHull.contains("Data name=\"project\""));
assertTrue("KML hull should contain srs name", kmlHull.contains("Data name=\"srsName\""));
assertTrue("KML hull should contain epsg id", kmlHull.contains("<value>EPSG:"));
assertTrue("KML hull should contain coordinates", kmlHull.contains("<coordinates>"));
hullCount.getAndIncrement();
});
assertTrue("At least " + minHullCount + " citygmls should be present in repository", gmlCount >= minHullCount);
assertTrue("At least " + minHullCount + " hulls should have been calculated", hullCount.get() >= minHullCount);
}
}
......@@ -12,6 +12,14 @@
public class RegionExtractorTests
{
private static final String EPSG_32118 = "EPSG:32118";
private static final String CITY_OBJECT_MEMBER_REGEX = "<(core:)?cityObjectMember";
private static final String CITY_MODEL_HEADER = "<CityModel";
private static final String CORE_CITY_MODEL_HEADER = "<core:CityModel";
private static final String CITY_MODEL_FOOTER = "</CityModel";
private static final String CORE_CITY_MODEL_FOOTER = "</core:CityModel";
private static final Path TEST_REPOSITORY = Paths.get("../RegionChooser/test/testdata/");
public static int countRegexMatches(String str, String subStr) {
Pattern pattern = Pattern.compile(subStr);
Matcher matcher = pattern.matcher(str);
......@@ -26,17 +34,16 @@ public static int countRegexMatches(String str, String subStr) {
public void testExtract3BuildingsFromGSK3Model() throws Throwable {
//NOTE: Small region around Martinskirche in Grünbühl
String wktPolygon = "POLYGON((3515848.896028535 5415823.108586172,3515848.9512289143 5415803.590347393,3515829.0815150724 5415803.338023346,3515830.9784850604 5415793.437034622,3515842.0946056456 5415793.272282251,3515843.3515515197 5415766.204935087,3515864.1064344468 5415766.557899496,3515876.489172751 5415805.433782301,3515876.343844858 5415822.009293416,3515848.896028535 5415823.108586172))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Gruenbuehl.proj/20140218_Gruenbuehl_LOD2.gml");
Path citygmlPath = TEST_REPOSITORY.resolve("Gruenbuehl.proj/20140218_Gruenbuehl_LOD2.gml");
String churchGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:31467")
.toString();
assertEquals(countRegexMatches(churchGMLString, "<(core:)?cityObjectMember"), 3);
assertEquals(countRegexMatches(churchGMLString, CITY_OBJECT_MEMBER_REGEX), 3);
assertTrue(churchGMLString.contains("Donaustr"));
assertTrue(churchGMLString.contains("DEBW_LOD2_203056"));
assertTrue(churchGMLString.contains("DEBW_LOD2_2869"));
assertTrue(churchGMLString.contains("DEBW_LOD2_2909"));
assertTrue(churchGMLString.contains("<core:CityModel")); // Header
assertTrue(churchGMLString.contains("</core:CityModel")); // Footer
assertTrue(churchGMLString.contains(CORE_CITY_MODEL_HEADER));
assertTrue(churchGMLString.contains(CORE_CITY_MODEL_FOOTER));
assertTrue("The exported CityGML should contain a new envelope with the correct EPSG", churchGMLString
.contains("<gml:Envelope srsName=\"EPSG:31467\" srsDimension=\"3\">"));
assertTrue("The exported CityGML should contain a new envelope", churchGMLString
......@@ -49,16 +56,15 @@ public void testExtract3BuildingsFromGSK3Model() throws Throwable {
public void testExtractBuildingsWithoutCommentsInBetween() throws Throwable {
//NOTE: Small region around WashingtonSquare
String wktPolygon = "POLYGON((300259.78663489706 62835.835907766595,300230.33294975647 62792.0482567884,300213.5667431851 62770.83143720031,300183.6592861123 62730.20347659383,300252.9947486632 62676.938468840905,300273.3862256562 62701.767105345614,300257.5250407747 62715.760413539596,300308.2754543957 62805.14198211394,300259.78663489706 62835.835907766595))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("NewYork.proj/ManhattanSmall.gml");
String archGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:32118")
Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
String archGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, EPSG_32118)
.toString();
assertEquals(countRegexMatches(archGMLString, "<(core:)?cityObjectMember"), 2);
assertEquals(countRegexMatches(archGMLString, CITY_OBJECT_MEMBER_REGEX), 2);
assertTrue(archGMLString.contains("WASHINGTON SQUARE"));
assertTrue(archGMLString.contains("uuid_c0980a6e-05ea-4d09-bc83-efab226945a1"));
assertTrue(archGMLString.contains("uuid_0985cebb-922d-4b3e-95e5-15dc6089cd28"));
assertTrue(archGMLString.contains("<CityModel")); // Header
assertTrue(archGMLString.contains("</CityModel")); // Footer
assertTrue(archGMLString.contains(CITY_MODEL_HEADER));
assertTrue(archGMLString.contains(CITY_MODEL_FOOTER));
assertFalse("Comments between buildings shouldn't be extracted",
archGMLString.contains("comment between buildings")); // Comment
assertFalse("Comments after buildings shouldn't be extracted",
......@@ -68,11 +74,10 @@ public void testExtractBuildingsWithoutCommentsInBetween() throws Throwable {
@Test
public void testExtractBuildingsAndChangeEnvelope() throws Throwable {
String wktPolygon = "POLYGON((299761.8123557725 61122.68126771413,299721.46983062755 61058.11626595352,299780.84627343423 61021.99295737501,299823.9079725632 61083.3979344517,299761.8123557725 61122.68126771413))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("NewYork.proj/FamilyCourt_LOD2_with_PLUTO_attributes.gml");
Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/FamilyCourt_LOD2_with_PLUTO_attributes.gml");
String familyCourtBuilding = RegionExtractor
.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:32118").toString();
assertEquals(countRegexMatches(familyCourtBuilding, "<(core:)?cityObjectMember"), 1);
.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, EPSG_32118).toString();
assertEquals(countRegexMatches(familyCourtBuilding, CITY_OBJECT_MEMBER_REGEX), 1);
assertTrue(familyCourtBuilding.contains("Bldg_12210021066"));
assertFalse("The exported CityGML shouldn't contain the original envelope", familyCourtBuilding
.contains("<gml:lowerCorner>298393.46959639067 59277.34021543693 -11.892070104139751</gml:lowerCorner>"));
......@@ -90,46 +95,42 @@ public void testExtractBuildingsAndChangeEnvelope() throws Throwable {
public void testExtract0BuildingsWithWrongCoordinates() throws Throwable {
//NOTE: Small region, far away from NYC
String wktPolygon = "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("NewYork.proj/ManhattanSmall.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:32118")
Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/ManhattanSmall.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, EPSG_32118)
.toString();
assertEquals(countRegexMatches(emptyGMLString, "<(core:)?cityObjectMember"), 0);
assertTrue(emptyGMLString.contains("<CityModel")); // Header
assertTrue(emptyGMLString.contains("</CityModel")); // Footer
assertEquals(countRegexMatches(emptyGMLString, CITY_OBJECT_MEMBER_REGEX), 0);
assertTrue(emptyGMLString.contains(CITY_MODEL_HEADER));
assertTrue(emptyGMLString.contains(CITY_MODEL_FOOTER));
}
@Test
public void testExtract0BuildingsFromEmptyGML() throws Throwable {
//NOTE: Small region, with too many spaces between coordinates
String wktPolygon = "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("NewYork.proj/empty_model.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:32118")
Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/empty_model.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, EPSG_32118)
.toString();
assertEquals(countRegexMatches(emptyGMLString, "<(core:)?cityObjectMember"), 0);
assertTrue(emptyGMLString.contains("<core:CityModel")); // Header
assertTrue(emptyGMLString.contains("</core:CityModel")); // Footer
assertEquals(countRegexMatches(emptyGMLString, CITY_OBJECT_MEMBER_REGEX), 0);
assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_HEADER));
assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_FOOTER));
}
@Test
public void testExtract0BuildingsFromWeirdGML() throws Throwable {
//NOTE: Small region, with too many spaces between coordinates
String wktPolygon = "POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("NewYork.proj/broken_nyc_lod2.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:32118")
Path citygmlPath = TEST_REPOSITORY.resolve("NewYork.proj/broken_nyc_lod2.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, EPSG_32118)
.toString();
assertEquals(countRegexMatches(emptyGMLString, "<(core:)?cityObjectMember"), 0);
assertTrue(emptyGMLString.contains("<core:CityModel")); // Header
assertTrue(emptyGMLString.contains("</core:CityModel")); // Footer
assertEquals(countRegexMatches(emptyGMLString, CITY_OBJECT_MEMBER_REGEX), 0);
assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_HEADER));
assertTrue(emptyGMLString.contains(CORE_CITY_MODEL_FOOTER));
}
@Test
public void testExtractBuildingsFromCitygmlWithoutZinEnvelope() throws Throwable {
String wktPolygon = "POLYGON((3512683.1280912133 5404783.732132129,3512719.1608604863 5404714.627650777,3512831.40076119 5404768.344155442,3512790.239106708 5404838.614891164,3512683.1280912133 5404783.732132129))";
Path repo = Paths.get("../TestRepository");
Path citygmlPath = repo.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
Path citygmlPath = TEST_REPOSITORY.resolve("Stuttgart.proj/Stuttgart_LOD0_LOD1_small.gml");
String emptyGMLString = RegionExtractor.selectRegionDirectlyFromCityGML(citygmlPath, wktPolygon, "EPSG:31463")
.toString();
assertEquals(countRegexMatches(emptyGMLString, "<bldg:Building gml:id"), 2);
......
Markdown is supported
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