Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Eric Duminil
RegionChooser
Commits
706f5731
Commit
706f5731
authored
Oct 30, 2019
by
Eric Duminil
Browse files
RegionChooser: Wild test to see if it could run as a standalone project.
parent
41fcf416
Changes
7
Show whitespace changes
Inline
Side-by-side
.classpath
View file @
706f5731
...
...
@@ -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=
"/GeoLibs/lib/proj4j-0.1.0.jar"
sourcepath=
"/GeoLibs/lib/proj4j-0.1.0-sources.jar"
/>
<classpathentry
kind=
"lib"
path=
"/GeoLibs/lib/citygml4j-2.10.2.jar"
sourcepath=
"/GeoLibs/lib/citygml4j-2.10.2.zip"
/>
<classpathentry
kind=
"lib"
path=
"/GeoLibs/lib/vtd-xml_2_13_1.jar"
/>
<classpathentry
kind=
"lib"
path=
"/GeoLibs/lib/jts-core-1.16.1.jar"
sourcepath=
"/GeoLibs/lib/jts-core-1.16.1-sources.jar"
/>
<classpathentry
kind=
"output"
path=
"bin"
/>
</classpath>
src/eu/simstadt/geo/GeoUtils.java
0 → 100644
View file @
706f5731
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"
));
}
}
src/eu/simstadt/geo/fast_xml_parser/BuildingXmlNode.java
0 → 100644
View file @
706f5731
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
""
;
}
}
}
src/eu/simstadt/geo/fast_xml_parser/CityGmlIterator.java
0 → 100644
View file @
706f5731
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
);
}
}
src/eu/simstadt/geo/fast_xml_parser/ConvexHullCalculator.java
0 → 100644
View file @
706f5731
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
);
}
}
test/eu/simstadt/geo/fast_xml_parser/CitygmlParserTests.java
0 → 100644
View file @
706f5731
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.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
;
public
class
CitygmlParserTests
{
private
void
testNoNanInCoordinates
(
Path
citygmlPath
)
throws
NumberFormatException
,
XPathParseException
,
NavException
,
XPathEvalException
,
IOException
{
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 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"
);
testNoNanInCoordinates
(
citygmlPath
);
}
@Test
public
void
testExtractCoordsFromGruenbuehl
()
throws
Throwable
{
Path
repo
=
Paths
.
get
(
"../TestRepository"
);
Path
citygmlPath
=
repo
.
resolve
(
"Gruenbuehl.proj/20140218_Gruenbuehl_LOD2_1building.gml"
);
testNoNanInCoordinates
(
citygmlPath
);
}
@Test
public
void
testExtractCoordsFromMunich
()
throws
Throwable
{
Path
repo
=
Paths
.
get
(
"../TestRepository"
);
Path
citygmlPath
=
repo
.
resolve
(
"Muenchen.proj/Munich_v_1_0_0.gml"
);
testNoNanInCoordinates
(
citygmlPath
);
}
@Test
public
void
testExtractCoordsFromNYC
()
throws
Throwable
{
Path
repo
=
Paths
.
get
(
"../TestRepository"
);
Path
citygmlPath
=
repo
.
resolve
(
"NewYork.proj/ManhattanSmall.gml"
);
testNoNanInCoordinates
(
citygmlPath
);
}
@Test
public
void
testExtractNoCoordsFromEmptyBuilding
()
throws
Throwable
{
Path
repo
=
Paths
.
get
(
"../TestRepository"
);
Path
citygmlPath
=
repo
.
resolve
(
"Ensource.proj/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
);
}
}
test/eu/simstadt/geo/fast_xml_parser/ConvexHullCalculatorTests.java
0 → 100644
View file @
706f5731
package
eu.simstadt.geo.fast_xml_parser
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
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
eu.simstadt.geo.GeoUtils
;
public
class
ConvexHullCalculatorTests
{
private
static
final
GeometryFactory
gf
=
new
GeometryFactory
();
private
static
final
Path
repository
=
Paths
.
get
(
"../TestRepository"
);
@Test
public
void
testExtractConvexHullFromOneBuilding
()
throws
Throwable
{
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
Throwable
{
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
Throwable
{
Path
citygmlPath
=
repository
.
resolve
(
"Ensource.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
Throwable
{
int
minHullCount
=
70
;
//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
);
}
}
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment