Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
CityDoctor
CityDoctor2
Commits
1fbe16e1
Commit
1fbe16e1
authored
Dec 12, 2025
by
Matthias Betz
Browse files
add xml validation to streaming processing
parent
82888d4b
Pipeline
#12286
passed with stage
in 2 minutes and 15 seconds
Changes
10
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CityDoctorParent/CityDoctorCheckResult/src/main/java/de/hft/stuttgart/citydoctor2/checkresult/CheckReport.java
View file @
1fbe16e1
...
...
@@ -22,6 +22,7 @@ import java.io.BufferedReader;
import
java.io.FileReader
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.nio.file.Path
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
...
...
@@ -65,7 +66,11 @@ public class CheckReport {
private
Map
<
String
,
FeatureReport
>
featureReports
;
public
static
CheckReport
load
(
String
file
)
throws
CheckReportParseException
{
try
(
BufferedReader
reader
=
new
BufferedReader
(
new
FileReader
(
file
)))
{
return
load
(
Path
.
of
(
file
));
}
public
static
CheckReport
load
(
Path
path
)
throws
CheckReportParseException
{
try
(
BufferedReader
reader
=
new
BufferedReader
(
new
FileReader
(
path
.
toFile
())))
{
CheckReport
report
=
(
CheckReport
)
getContext
().
createUnmarshaller
().
unmarshal
(
new
InputSource
(
reader
));
enrichReportStructure
(
report
);
return
report
;
...
...
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/parser/CityGmlConsumer.java
View file @
1fbe16e1
...
...
@@ -32,6 +32,5 @@ public interface CityGmlConsumer {
}
// public void endOfFile();
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/parser/CityGmlParser.java
View file @
1fbe16e1
...
...
@@ -102,618 +102,629 @@ import de.hft.stuttgart.quality.QualityADEModule;
*/
public
class
CityGmlParser
{
private
static
final
String
CITY_OBJECT_MEMBER
=
"cityObjectMember"
;
private
static
final
String
WGS_84
=
"EPSG:4326"
;
private
static
final
Logger
logger
=
LogManager
.
getLogger
(
CityGmlParser
.
class
);
private
static
final
CRSFactory
CRS_FACTORY
=
new
CRSFactory
();
// EPSG:31467
private
static
final
Pattern
P_EPSG
=
Pattern
.
compile
(
"^(EPSG:\\d+)$"
);
// urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
// or
// urn:ogc:def:crs,crs:EPSG::28992
private
static
final
Pattern
P_OGC
=
Pattern
.
compile
(
"urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"
);
private
static
final
Pattern
P_OGC2
=
Pattern
.
compile
(
"urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"
);
// urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
// urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
private
static
final
Pattern
P_URN
=
Pattern
.
compile
(
"urn:adv:crs:([^\\*]+)"
);
private
static
final
SAXParserFactory
FACTORY
;
private
static
Proj4FileReader
csReader
=
new
Proj4FileReader
();
private
static
final
Pattern
P_VUNITS
=
Pattern
.
compile
(
"^\\+vunits=.+"
);
private
static
final
Pattern
P_GEOIDGRID
=
Pattern
.
compile
(
"^\\+geoidgrid=.+"
);
private
static
CityGMLContext
context
;
private
static
List
<
QName
>
chunkProperties
=
new
ArrayList
<>();
static
{
System
.
setProperty
(
"javax.xml.transform.TransformerFactory"
,
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"
);
FACTORY
=
SAXParserFactory
.
newInstance
();
try
{
FACTORY
.
setFeature
(
XMLConstants
.
FEATURE_SECURE_PROCESSING
,
false
);
}
catch
(
SAXNotRecognizedException
|
SAXNotSupportedException
|
ParserConfigurationException
e
)
{
logger
.
catching
(
e
);
}
chunkProperties
.
add
(
new
QName
(
CityGMLConstants
.
CITYGML_1_0_CORE_NAMESPACE
,
CITY_OBJECT_MEMBER
));
chunkProperties
.
add
(
new
QName
(
CityGMLConstants
.
CITYGML_2_0_CORE_NAMESPACE
,
CITY_OBJECT_MEMBER
));
chunkProperties
.
add
(
new
QName
(
CityGMLConstants
.
CITYGML_3_0_CORE_NAMESPACE
,
CITY_OBJECT_MEMBER
));
}
private
CityGmlParser
()
{
}
public
static
synchronized
CityGMLContext
getContext
()
{
if
(
context
==
null
)
{
try
{
context
=
CityGMLContext
.
newInstance
(
CityGmlParser
.
class
.
getClassLoader
());
// also setup ades
ADERegistry
adeRegistry
=
ADERegistry
.
getInstance
();
adeRegistry
.
loadADE
(
new
QualityADEContext
());
}
catch
(
CityGMLContextException
e
)
{
logger
.
fatal
(
Localization
.
getText
(
"CityGmlParser.creating4jContextFailed"
),
e
);
throw
new
IllegalStateException
(
"Unable to create citygml4j context"
);
}
catch
(
ADEException
e
)
{
logger
.
fatal
(
Localization
.
getText
(
"CityGmlParser.creatingAdePluginsFailed"
),
e
);
throw
new
IllegalStateException
(
"Unable to add ADE plugins to citygml4j"
);
}
}
return
context
;
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* The nested parse uses an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache}, which
* is not connected to the database.
* <p/>
* Nested parsing should only be used for accessing Features in auxiliary, small CityGML files, e.g. parsing of
* {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject LibraryObject} files.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
nestedCityGmlFileParse
(
String
filePath
,
ParserConfiguration
config
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
null
,
null
,
false
,
true
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
null
,
null
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
,
ProgressListener
l
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
l
,
null
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
,
ProgressListener
l
,
GMLValidationHandler
handler
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
l
,
handler
,
true
,
false
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory upstream cache. Thus, strong references
* to {@link CityObject} are to be avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
* @param verbose If verbosity is enabled the parser will write info logs to the logger
* @param unconnected If true the parser will use an {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache UnconnectedCache}
* to collect the parsed CityObjects
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
private
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
,
ProgressListener
l
,
GMLValidationHandler
handler
,
boolean
verbose
,
boolean
unconnected
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
CityGMLContext
context
=
getContext
();
Path
file
=
Paths
.
get
(
filePath
);
List
<
String
>
validationIssues
=
Collections
.
emptyList
();
if
(
config
.
getValidate
())
{
validationIssues
=
validateFile
(
context
,
handler
,
file
);
}
try
{
parseEpsgCodeFromFile
(
file
,
config
,
verbose
);
CityGMLInputFactory
in
=
context
.
createCityGMLInputFactory
()
.
withChunking
(
ChunkOptions
.
chunkByProperties
(
chunkProperties
).
skipCityModel
(
false
));
try
(
ObservedInputStream
ois
=
new
ObservedInputStream
(
file
.
toFile
()))
{
if
(
l
!=
null
)
{
ois
.
addListener
(
l:
:
updateProgress
);
}
CityDoctorModel
model
=
readAndKeepFeatures
(
config
,
file
,
in
,
ois
,
verbose
,
unconnected
);
for
(
String
xmlError
:
validationIssues
)
{
model
.
addGlobalError
(
new
XMLValidationError
(
xmlError
));
}
return
model
;
}
}
catch
(
CityGMLReadException
|
IOException
e
)
{
throw
new
CityGmlParseException
(
"Failed to read CityGML file"
,
e
);
}
}
public
static
void
streamCityGml
(
String
file
,
ParserConfiguration
config
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGmlParseException
{
Path
f
=
Paths
.
get
(
file
);
streamCityGml
(
f
,
config
,
null
,
cityObjectConsumer
,
outputFile
);
}
public
static
void
streamCityGml
(
File
file
,
ParserConfiguration
parserConfig
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGmlParseException
{
streamCityGml
(
file
.
toPath
(),
parserConfig
,
null
,
cityObjectConsumer
,
outputFile
);
}
public
static
void
streamCityGml
(
Path
file
,
ParserConfiguration
config
,
ProgressListener
l
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGmlParseException
{
parseEpsgCodeFromFile
(
file
,
config
,
true
);
startReadingCityGmlFile
(
file
,
config
,
l
,
cityObjectConsumer
,
outputFile
);
}
public
static
CityModel
parseOnlyCityModel
(
File
inputFile
)
throws
CityGmlParseException
{
try
{
CityGMLInputFactory
inputFactory
=
context
.
createCityGMLInputFactory
()
.
withChunking
(
ChunkOptions
.
chunkByProperties
(
chunkProperties
).
skipCityModel
(
false
));
try
(
CityGMLReader
reader
=
inputFactory
.
createCityGMLReader
(
inputFile
))
{
while
(
reader
.
hasNext
())
{
AbstractFeature
chunk
=
reader
.
next
();
if
(
chunk
instanceof
CityModel
cModel
)
{
cModel
.
setCityObjectMembers
(
null
);
return
cModel
;
}
}
}
}
catch
(
CityGMLReadException
e
)
{
throw
new
CityGmlParseException
(
e
);
}
throw
new
CityGmlParseException
(
"Did not find any CityModel in CityGML file"
);
}
private
static
void
startReadingCityGmlFile
(
Path
file
,
ParserConfiguration
config
,
ProgressListener
l
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
{
try
(
ObservedInputStream
ois
=
new
ObservedInputStream
(
file
.
toFile
()))
{
if
(
l
!=
null
)
{
ois
.
addListener
(
l:
:
updateProgress
);
}
readAndDiscardFeatures
(
file
,
config
,
ois
,
cityObjectConsumer
,
outputFile
);
}
catch
(
IOException
|
CityGMLReadException
e
)
{
logger
.
error
(
Localization
.
getText
(
"CityGmlParser.errorReadingGmlFile"
),
e
.
getMessage
());
logger
.
catching
(
Level
.
ERROR
,
e
);
}
}
private
static
void
readAndDiscardFeatures
(
Path
file
,
ParserConfiguration
config
,
ObservedInputStream
ois
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGMLReadException
{
Citygml3FeatureMapper
mapper
=
new
Citygml3FeatureMapper
(
config
,
file
,
false
);
readAndDiscardModel
(
mapper
,
ois
,
cityObjectConsumer
,
outputFile
);
}
private
static
void
readAndDiscardModel
(
Citygml3FeatureMapper
mapper
,
ObservedInputStream
ois
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGMLReadException
{
getContext
();
CityGMLInputFactory
inputFactory
=
context
.
createCityGMLInputFactory
()
.
withChunking
(
ChunkOptions
.
chunkByProperties
(
chunkProperties
).
skipCityModel
(
false
));
CityGMLChunkWriter
writer
=
null
;
try
(
CityGMLReader
reader
=
inputFactory
.
createCityGMLReader
(
ois
))
{
CityDoctorModel
model
=
mapper
.
getModel
();
boolean
isInitialized
=
false
;
while
(
reader
.
hasNext
())
{
AbstractFeature
chunk
=
reader
.
next
();
if
(
writer
==
null
)
{
writer
=
createCityModelWriter
(
outputFile
,
reader
);
}
if
(!
isInitialized
&&
writer
!=
null
&&
reader
.
getParentInfo
()
!=
null
&&
reader
.
getParentInfo
().
getTypeName
().
getLocalPart
().
equals
(
"CityModel"
))
{
FeatureInfo
parentInfo
=
reader
.
getParentInfo
();
writer
.
withCityModelInfo
(
parentInfo
);
isInitialized
=
true
;
}
if
(
chunk
instanceof
AbstractCityObject
ag
)
{
ag
.
accept
(
mapper
);
drainCityModel
(
model
,
cityObjectConsumer
);
writeAbstractCityObject
(
writer
,
ag
);
}
else
if
(
chunk
instanceof
CityModel
cModel
)
{
cModel
.
setCityObjectMembers
(
null
);
mapper
.
setCityModel
(
cModel
);
cityObjectConsumer
.
accept
(
cModel
);
writeCityModel
(
writer
,
cModel
);
}
else
if
(
writer
!=
null
)
{
writer
.
writeMember
(
chunk
);
}
}
// end of stream
logger
.
debug
(
"End of gml file stream"
);
}
catch
(
CityGMLReadException
e
)
{
logger
.
error
(
Localization
.
getText
(
"CityGmlParser.errorReadingGmlFile"
),
e
.
getMessage
(),
e
);
}
catch
(
CityGMLWriteException
e
)
{
logger
.
error
(
Localization
.
getText
(
"CityGmlParser.errorWritingGmlFile"
),
e
.
getMessage
(),
e
);
}
finally
{
if
(
writer
!=
null
)
{
try
{
writer
.
close
();
}
catch
(
CityGMLWriteException
e
)
{
// ignore
}
}
}
}
private
static
void
writeCityModel
(
CityGMLChunkWriter
writer
,
CityModel
cModel
)
throws
CityGMLWriteException
{
if
(
writer
!=
null
)
{
for
(
AbstractFeatureProperty
featureProp
:
cModel
.
getFeatureMembers
())
{
writer
.
writeMember
(
featureProp
.
getObject
());
}
}
}
private
static
void
writeAbstractCityObject
(
CityGMLChunkWriter
writer
,
AbstractCityObject
ag
)
throws
CityGMLWriteException
{
if
(
writer
!=
null
)
{
writer
.
writeMember
(
ag
);
}
}
private
static
CityGMLChunkWriter
createCityModelWriter
(
String
outputFile
,
CityGMLReader
reader
)
throws
CityGMLWriteException
{
if
(
outputFile
==
null
)
{
return
null
;
}
CityGMLContext
gmlContext
=
CityGmlParser
.
getContext
();
CityGMLVersion
version
=
CityGMLModules
.
getCityGMLVersion
(
reader
.
getName
().
getNamespaceURI
());
CityGMLOutputFactory
factory
=
gmlContext
.
createCityGMLOutputFactory
(
version
);
CityGMLChunkWriter
writer
=
factory
.
createCityGMLChunkWriter
(
new
File
(
outputFile
),
StandardCharsets
.
UTF_8
.
name
());
writer
.
withPrefix
(
"qual"
,
QualityADEModule
.
NAMESPACE_URI
);
writer
.
withSchemaLocation
(
QualityADEModule
.
NAMESPACE_URI
,
QualityADEModule
.
NAMESPACE_URI
+
"/qualityAde.xsd"
);
writer
.
withIndent
(
" "
);
writer
.
withDefaultPrefixes
();
writer
.
withDefaultSchemaLocations
();
return
writer
;
}
private
static
CityDoctorModel
readAndKeepFeatures
(
ParserConfiguration
config
,
Path
file
,
CityGMLInputFactory
inputFactory
,
ObservedInputStream
ois
,
boolean
verbose
,
boolean
unconnected
)
throws
CityGMLReadException
{
return
readAndKeepModel
(
new
Citygml3FeatureMapper
(
config
,
file
,
unconnected
),
inputFactory
,
ois
,
verbose
);
}
private
static
CityDoctorModel
readAndKeepModel
(
Citygml3FeatureMapper
mapper
,
CityGMLInputFactory
inputFactory
,
ObservedInputStream
ois
,
boolean
verbose
)
throws
CityGMLReadException
{
try
(
CityGMLReader
reader
=
inputFactory
.
createCityGMLReader
(
ois
))
{
CityGMLVersion
version
=
null
;
// model is read in chunked mode
// object members are replaced by href in model
// need to remove the refs and re-add unparsed objects
List
<
AbstractCityObject
>
acos
=
new
ArrayList
<>();
CityModel
cgmlModel
=
null
;
CityDoctorModel
cdModel
=
mapper
.
getModel
();
AutoCloseable
parsingContext
=
cdModel
.
getCache
().
startParsingMode
();
try
(
parsingContext
){
List
<
AbstractCityObject
>
queuedObjects
=
new
ArrayList
<>();
while
(
reader
.
hasNext
())
{
AbstractFeature
chunk
=
reader
.
next
();
version
=
CityGMLModules
.
getCityGMLVersion
(
reader
.
getName
().
getNamespaceURI
());
if
(
chunk
instanceof
CityModel
cModel
)
{
cModel
.
setCityObjectMembers
(
null
);
mapper
.
setCityModel
(
cModel
);
mapper
.
setCityGMLVersion
(
version
);
cgmlModel
=
cModel
;
if
(!
queuedObjects
.
isEmpty
())
{
for
(
AbstractCityObject
aco
:
queuedObjects
)
{
if
(!
cdModel
.
containsGmlId
(
aco
.
getId
()))
{
cgmlModel
.
getCityObjectMembers
().
add
(
new
AbstractCityObjectProperty
(
aco
));
}
}
}
}
else
if
(
chunk
instanceof
AbstractCityObject
aco
)
{
acos
.
add
(
aco
);
// getid
String
id
=
aco
.
getId
();
aco
.
accept
(
mapper
);
if
(
cgmlModel
==
null
)
{
// Add object to queue till the CityModel has been parsed
queuedObjects
.
add
(
aco
);
}
else
if
(!
cdModel
.
containsGmlId
(
id
)){
cgmlModel
.
getCityObjectMembers
().
add
(
new
AbstractCityObjectProperty
(
aco
));
}
}
}
if
(
mapper
.
getModel
().
getCityModel
()
==
null
)
{
// file does not contain a city model?
// create it for now
logger
.
warn
(
Localization
.
getText
(
"CityGmlParser.missingCityModel"
));
mapper
.
setCityModel
(
new
CityModel
());
for
(
AbstractCityObject
aco
:
queuedObjects
)
{
if
(!
cdModel
.
containsGmlId
(
aco
.
getId
()))
{
mapper
.
getModel
().
getCityModel
().
getCityObjectMembers
().
add
(
new
AbstractCityObjectProperty
(
aco
));
}
}
}
ImplicitGeometryHolder
.
resolveDeferredObjects
(
mapper
.
getModel
().
getCache
());
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.parsedObjects"
),
mapper
.
getModel
().
getNumberOfFeatures
());
}
mapper
.
checkUnresolvedReferences
();
mapper
.
setCityGMLVersion
(
version
);
return
mapper
.
getModel
();
}
catch
(
Exception
e
)
{
throw
new
CityGMLReadException
(
e
);
}
}
}
private
static
void
parseEpsgCodeFromFile
(
Path
file
,
ParserConfiguration
config
,
boolean
verbose
)
throws
CityGmlParseException
{
try
(
BufferedInputStream
bis
=
new
BufferedInputStream
(
new
FileInputStream
(
file
.
toFile
())))
{
parseEpsgCodeFromStream
(
bis
,
config
,
verbose
);
}
catch
(
ParserConfigurationException
|
SAXException
|
IOException
e
)
{
throw
new
CityGmlParseException
(
"Failed to read CityGML file"
,
e
);
}
}
private
static
void
parseEpsgCodeFromStream
(
InputStream
is
,
ParserConfiguration
config
,
boolean
verbose
)
throws
ParserConfigurationException
,
SAXException
{
SAXParser
parser
=
FACTORY
.
newSAXParser
();
CityGmlHandler
handler
=
new
CityGmlHandler
();
try
{
parser
.
parse
(
new
InputSource
(
is
),
handler
);
}
catch
(
EnvelopeFoundException
e
)
{
try
{
parseCoordinateSystem
(
config
,
handler
,
verbose
);
}
catch
(
Exception
e2
)
{
logEpsgParseError
(
e2
);
}
}
catch
(
Exception
e
)
{
logEpsgParseError
(
e
);
}
if
(
handler
.
getEpsg
()
==
null
&&
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.missingEPSGCode"
));
}
}
private
static
void
logEpsgParseError
(
Exception
e
)
{
logger
.
debug
(
"Exception while parsing for EPSG code"
,
e
);
if
(
logger
.
isWarnEnabled
())
{
logger
.
warn
(
Localization
.
getText
(
"CityGmlParser.failedEPSGParse"
));
}
}
private
static
void
parseCoordinateSystem
(
ParserConfiguration
config
,
CityGmlHandler
handler
,
boolean
verbose
)
{
if
(
handler
.
getEpsg
()
==
null
)
{
return
;
}
CoordinateReferenceSystem
crs
=
crsFromSrsName
(
handler
.
getEpsg
());
if
(
crs
==
null
)
{
// could not find a coordinate system for srsName
// assuming metric system
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.missingEPSGCode"
));
}
return
;
}
if
(
crs
.
getProjection
().
getUnits
()
==
Units
.
METRES
)
{
// coordinate system is in meters, do not convert
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.noConversionNeeded"
));
}
return
;
}
parseMeterConversion
(
config
,
crs
);
Vector3d
low
=
handler
.
getLowerCorner
();
Vector3d
up
=
handler
.
getUpperCorner
();
double
centerLong
=
low
.
getX
()
+
((
up
.
getX
()
-
low
.
getX
())
/
2
);
double
centerLat
=
low
.
getY
()
+
((
up
.
getY
()
-
low
.
getY
())
/
2
);
if
(!
crs
.
getName
().
equals
(
WGS_84
))
{
// need to convert coordinates first to WGS84, then find UTM Zone
CoordinateReferenceSystem
wgs84
=
crsFromSrsName
(
WGS_84
);
ProjCoordinate
p1
=
new
ProjCoordinate
();
p1
.
setValue
(
centerLong
,
centerLat
);
ProjCoordinate
p2
=
new
ProjCoordinate
();
BasicCoordinateTransform
bct
=
new
BasicCoordinateTransform
(
crs
,
wgs84
);
bct
.
transform
(
p1
,
p2
);
centerLong
=
p2
.
x
;
centerLat
=
p2
.
y
;
}
int
zone
=
(
int
)
(
31
+
Math
.
round
(
centerLong
/
6
));
CoordinateReferenceSystem
utm
;
if
(
centerLat
<
0
)
{
// south
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.convertCrsToUtmZoneS"
),
zone
);
}
utm
=
CRS_FACTORY
.
createFromParameters
(
"UTM"
,
"+proj=utm +ellps=WGS84 +units=m +zone="
+
zone
+
" +south"
);
}
else
{
// north
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.convertCrsToUtmZoneN"
),
zone
);
}
utm
=
CRS_FACTORY
.
createFromParameters
(
"UTM"
,
"+proj=utm +ellps=WGS84 +units=m +zone="
+
zone
);
}
config
.
setCoordinateSystem
(
crs
,
utm
);
}
private
static
void
parseMeterConversion
(
ParserConfiguration
config
,
CoordinateReferenceSystem
crs
)
{
Projection
projection
=
crs
.
getProjection
();
double
fromMetres
=
projection
.
getFromMetres
();
if
(
fromMetres
>
0
)
{
// also transform height information
config
.
setFromMeters
(
fromMetres
);
}
else
{
config
.
setFromMeters
(
1.0
);
}
}
/**
* 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.
* <p>
* 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.
* <p>
* 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
*/
private
static
CoordinateReferenceSystem
crsFromSrsName
(
String
srsName
)
{
srsName
=
srsName
.
trim
();
Matcher
mEPSG
=
P_EPSG
.
matcher
(
srsName
);
if
(
mEPSG
.
find
())
{
if
(
"EPSG:4979"
.
contentEquals
(
srsName
))
{
srsName
=
"EPSG:4236"
;
}
else
if
(
"EPSG:7415"
.
contentEquals
(
srsName
))
{
return
CRS_FACTORY
.
createFromParameters
(
"EPSG:7415"
,
"+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs"
);
}
return
CRS_FACTORY
.
createFromName
(
srsName
);
}
Matcher
mOGC
=
P_OGC
.
matcher
(
srsName
);
if
(
mOGC
.
find
())
{
return
getCrsFromOGCSrsCode
(
mOGC
.
group
(
1
));
}
Matcher
mOGC2
=
P_OGC2
.
matcher
(
srsName
);
if
(
mOGC2
.
find
())
{
return
getCrsFromOGCSrsCode
(
mOGC2
.
group
(
1
));
}
Matcher
mURN
=
P_URN
.
matcher
(
srsName
);
// NOTE: Could use a HashMap if the switch/case becomes too long.
if
(
mURN
.
find
())
{
return
switch
(
mURN
.
group
(
1
))
{
case
"DE_DHDN_3GK2"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31466"
);
case
"DE_DHDN_3GK3"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31467"
);
case
"DE_DHDN_3GK4"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31468"
);
case
"DE_DHDN_3GK5"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31469"
);
case
"ETRS89_UTM32"
->
CRS_FACTORY
.
createFromName
(
"EPSG:25832"
);
default
->
null
;
};
}
if
(
srsName
.
equals
(
"http://www.opengis.net/def/crs/EPSG/0/6697"
))
{
return
CRS_FACTORY
.
createFromParameters
(
"EPSG:6697"
,
"+proj=longlat +ellps=GRS80 +no_defs +axis=neu"
);
}
return
null
;
}
private
static
CoordinateReferenceSystem
getCrsFromOGCSrsCode
(
String
srsCode
)
{
String
crsName
=
"EPSG:"
+
srsCode
;
List
<
String
>
params
=
new
ArrayList
<>(
Arrays
.
stream
(
csReader
.
getParameters
(
crsName
)).
toList
());
params
.
removeIf
(
P_VUNITS
.
asPredicate
());
params
.
removeIf
(
P_GEOIDGRID
.
asPredicate
());
return
CRS_FACTORY
.
createFromParameters
(
crsName
,
params
.
toArray
(
new
String
[
0
]));
}
private
static
List
<
String
>
validateFile
(
CityGMLContext
context
,
GMLValidationHandler
handler
,
Path
file
)
throws
CityGmlParseException
{
if
(
handler
==
null
)
{
handler
=
new
GMLValidationHandler
();
}
try
{
SchemaHandler
schemaHandler
=
new
ValidationSchemaHandler
(
context
.
getDefaultSchemaHandler
());
readAdditionalSchemaDefinitions
(
context
,
file
,
schemaHandler
);
Source
[]
schemas
=
schemaHandler
.
getSchemas
();
SchemaFactory
schemaFactory
=
SchemaFactory
.
newInstance
(
XMLConstants
.
W3C_XML_SCHEMA_NS_URI
);
schemaFactory
.
setFeature
(
"http://apache.org/xml/features/disallow-doctype-decl"
,
true
);
Schema
schema
=
schemaFactory
.
newSchema
(
schemas
);
Validator
validator
=
schema
.
newValidator
();
validator
.
setErrorHandler
(
handler
);
validator
.
validate
(
new
StreamSource
(
file
.
toFile
()));
return
handler
.
getMessages
();
}
catch
(
SchemaHandlerException
|
SAXException
|
IOException
e
)
{
throw
new
CityGmlParseException
(
"Failed to validate CityGML file"
,
e
);
}
}
private
static
void
readAdditionalSchemaDefinitions
(
CityGMLContext
context
,
Path
file
,
SchemaHandler
schemaHandler
)
throws
CityGmlParseException
{
try
(
XMLReader
reader
=
XMLReaderFactory
.
newInstance
(
context
.
getXMLObjects
())
.
withSchemaHandler
(
schemaHandler
)
.
createReader
(
file
))
{
reader
.
nextTag
();
}
catch
(
Exception
e
)
{
throw
new
CityGmlParseException
(
"Failed to read file "
+
file
.
toAbsolutePath
()
+
"."
,
e
);
}
}
private
static
void
drainCityModel
(
CityDoctorModel
model
,
CityGmlConsumer
cityObjectConsumer
)
{
List
<
GmlId
>
id
=
new
ArrayList
<>();
model
.
createFeatureStream
().
forEach
(
co
->
{
cityObjectConsumer
.
accept
(
co
);
id
.
add
(
co
.
getGmlId
());
});
id
.
forEach
(
model:
:
removeFeature
);
}
private
static
final
String
CITY_OBJECT_MEMBER
=
"cityObjectMember"
;
private
static
final
String
WGS_84
=
"EPSG:4326"
;
private
static
final
Logger
logger
=
LogManager
.
getLogger
(
CityGmlParser
.
class
);
private
static
final
CRSFactory
CRS_FACTORY
=
new
CRSFactory
();
// EPSG:31467
private
static
final
Pattern
P_EPSG
=
Pattern
.
compile
(
"^(EPSG:\\d+)$"
);
// urn:ogc:def:crs,crs:EPSG:6.12:31467,crs:EPSG:6.12:5783
// or
// urn:ogc:def:crs,crs:EPSG::28992
private
static
final
Pattern
P_OGC
=
Pattern
.
compile
(
"urn:ogc:def:crs,crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"
);
private
static
final
Pattern
P_OGC2
=
Pattern
.
compile
(
"urn:ogc:def:crs:EPSG:[\\d\\.]*:([\\d]+)\\D*"
);
// urn:adv:crs:DE_DHDN_3GK3*DE_DHHN92_NH
// urn:adv:crs:ETRS89_UTM32*DE_DHHN92_NH
private
static
final
Pattern
P_URN
=
Pattern
.
compile
(
"urn:adv:crs:([^\\*]+)"
);
private
static
final
SAXParserFactory
FACTORY
;
private
static
Proj4FileReader
csReader
=
new
Proj4FileReader
();
private
static
final
Pattern
P_VUNITS
=
Pattern
.
compile
(
"^\\+vunits=.+"
);
private
static
final
Pattern
P_GEOIDGRID
=
Pattern
.
compile
(
"^\\+geoidgrid=.+"
);
private
static
CityGMLContext
context
;
private
static
List
<
QName
>
chunkProperties
=
new
ArrayList
<>();
static
{
System
.
setProperty
(
"javax.xml.transform.TransformerFactory"
,
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"
);
FACTORY
=
SAXParserFactory
.
newInstance
();
try
{
FACTORY
.
setFeature
(
XMLConstants
.
FEATURE_SECURE_PROCESSING
,
false
);
}
catch
(
SAXNotRecognizedException
|
SAXNotSupportedException
|
ParserConfigurationException
e
)
{
logger
.
catching
(
e
);
}
chunkProperties
.
add
(
new
QName
(
CityGMLConstants
.
CITYGML_1_0_CORE_NAMESPACE
,
CITY_OBJECT_MEMBER
));
chunkProperties
.
add
(
new
QName
(
CityGMLConstants
.
CITYGML_2_0_CORE_NAMESPACE
,
CITY_OBJECT_MEMBER
));
chunkProperties
.
add
(
new
QName
(
CityGMLConstants
.
CITYGML_3_0_CORE_NAMESPACE
,
CITY_OBJECT_MEMBER
));
}
private
CityGmlParser
()
{
}
public
static
synchronized
CityGMLContext
setupContext
()
{
if
(
context
==
null
)
{
try
{
context
=
CityGMLContext
.
newInstance
(
CityGmlParser
.
class
.
getClassLoader
());
// also setup ades
ADERegistry
adeRegistry
=
ADERegistry
.
getInstance
();
adeRegistry
.
loadADE
(
new
QualityADEContext
());
}
catch
(
CityGMLContextException
e
)
{
logger
.
fatal
(
Localization
.
getText
(
"CityGmlParser.creating4jContextFailed"
),
e
);
throw
new
IllegalStateException
(
"Unable to create citygml4j context"
);
}
catch
(
ADEException
e
)
{
logger
.
fatal
(
Localization
.
getText
(
"CityGmlParser.creatingAdePluginsFailed"
),
e
);
throw
new
IllegalStateException
(
"Unable to add ADE plugins to citygml4j"
);
}
}
return
context
;
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in
* a {@link CityDoctorModel}.
* <p/>
* The nested parse uses an
* {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
* UnconnectedCache}, which is not connected to the database.
* <p/>
* Nested parsing should only be used for accessing Features in auxiliary, small
* CityGML files, e.g. parsing of
* {@link de.hft.stuttgart.citydoctor2.datastructure.LibraryObject
* LibraryObject} files.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
nestedCityGmlFileParse
(
String
filePath
,
ParserConfiguration
config
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
null
,
null
,
false
,
true
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in
* a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory
* upstream cache. Thus, strong references to {@link CityObject} are to be
* avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
null
,
null
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in
* a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory
* upstream cache. Thus, strong references to {@link CityObject} are to be
* avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
,
ProgressListener
l
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
l
,
null
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in
* a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory
* upstream cache. Thus, strong references to {@link CityObject} are to be
* avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
public
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
,
ProgressListener
l
,
GMLValidationHandler
handler
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
return
parseCityGmlFile
(
filePath
,
config
,
l
,
handler
,
true
,
false
);
}
/**
* Parses a given CityGML file and collects the contained CityObject Features in
* a {@link CityDoctorModel}.
* <p/>
* Features are held in a non-persistent, embedded Database with an in-memory
* upstream cache. Thus, strong references to {@link CityObject} are to be
* avoided, to allow for the memory management of the cache.
*
* @param filePath String path to the file
* @param config Configuration of the parser
* @param l Listener hook for tracking of parsing progress
* @param handler Validation handler for GML-file validation
* @param verbose If verbosity is enabled the parser will write info logs to
* the logger
* @param unconnected If true the parser will use an
* {@link de.hft.stuttgart.citydoctor2.database.UnconnectedCache
* UnconnectedCache} to collect the parsed CityObjects
* @return A {@link CityDoctorModel} object with the parsed Features
* @throws CityGmlParseException if parsing of the CityGML-file failed
* @throws InvalidGmlFileException if the GMLValidationHandler found an error
*/
private
static
CityDoctorModel
parseCityGmlFile
(
String
filePath
,
ParserConfiguration
config
,
ProgressListener
l
,
GMLValidationHandler
handler
,
boolean
verbose
,
boolean
unconnected
)
throws
CityGmlParseException
,
InvalidGmlFileException
{
CityGMLContext
context
=
setupContext
();
Path
file
=
Paths
.
get
(
filePath
);
List
<
String
>
validationIssues
=
Collections
.
emptyList
();
if
(
config
.
getValidate
())
{
validationIssues
=
validateFile
(
handler
,
file
);
}
try
{
parseEpsgCodeFromFile
(
file
,
config
,
verbose
);
CityGMLInputFactory
in
=
context
.
createCityGMLInputFactory
()
.
withChunking
(
ChunkOptions
.
chunkByProperties
(
chunkProperties
).
skipCityModel
(
false
));
try
(
ObservedInputStream
ois
=
new
ObservedInputStream
(
file
.
toFile
()))
{
if
(
l
!=
null
)
{
ois
.
addListener
(
l:
:
updateProgress
);
}
CityDoctorModel
model
=
readAndKeepFeatures
(
config
,
file
,
in
,
ois
,
verbose
,
unconnected
);
for
(
String
xmlError
:
validationIssues
)
{
model
.
addGlobalError
(
new
XMLValidationError
(
xmlError
));
}
return
model
;
}
}
catch
(
CityGMLReadException
|
IOException
e
)
{
throw
new
CityGmlParseException
(
"Failed to read CityGML file"
,
e
);
}
}
public
static
void
streamCityGml
(
String
file
,
ParserConfiguration
config
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGmlParseException
{
Path
f
=
Paths
.
get
(
file
);
streamCityGml
(
f
,
config
,
null
,
cityObjectConsumer
,
outputFile
);
}
public
static
void
streamCityGml
(
File
file
,
ParserConfiguration
parserConfig
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGmlParseException
{
streamCityGml
(
file
.
toPath
(),
parserConfig
,
null
,
cityObjectConsumer
,
outputFile
);
}
public
static
void
streamCityGml
(
Path
file
,
ParserConfiguration
config
,
ProgressListener
l
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGmlParseException
{
parseEpsgCodeFromFile
(
file
,
config
,
true
);
startReadingCityGmlFile
(
file
,
config
,
l
,
cityObjectConsumer
,
outputFile
);
}
public
static
CityModel
parseOnlyCityModel
(
File
inputFile
)
throws
CityGmlParseException
{
try
{
CityGMLInputFactory
inputFactory
=
context
.
createCityGMLInputFactory
()
.
withChunking
(
ChunkOptions
.
chunkByProperties
(
chunkProperties
).
skipCityModel
(
false
));
try
(
CityGMLReader
reader
=
inputFactory
.
createCityGMLReader
(
inputFile
))
{
while
(
reader
.
hasNext
())
{
AbstractFeature
chunk
=
reader
.
next
();
if
(
chunk
instanceof
CityModel
cModel
)
{
cModel
.
setCityObjectMembers
(
null
);
return
cModel
;
}
}
}
}
catch
(
CityGMLReadException
e
)
{
throw
new
CityGmlParseException
(
e
);
}
throw
new
CityGmlParseException
(
"Did not find any CityModel in CityGML file"
);
}
private
static
void
startReadingCityGmlFile
(
Path
file
,
ParserConfiguration
config
,
ProgressListener
l
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
{
try
(
ObservedInputStream
ois
=
new
ObservedInputStream
(
file
.
toFile
()))
{
if
(
l
!=
null
)
{
ois
.
addListener
(
l:
:
updateProgress
);
}
readAndDiscardFeatures
(
file
,
config
,
ois
,
cityObjectConsumer
,
outputFile
);
}
catch
(
IOException
|
CityGMLReadException
e
)
{
logger
.
error
(
Localization
.
getText
(
"CityGmlParser.errorReadingGmlFile"
),
e
.
getMessage
());
logger
.
catching
(
Level
.
ERROR
,
e
);
}
}
private
static
void
readAndDiscardFeatures
(
Path
file
,
ParserConfiguration
config
,
ObservedInputStream
ois
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
)
throws
CityGMLReadException
{
Citygml3FeatureMapper
mapper
=
new
Citygml3FeatureMapper
(
config
,
file
,
false
);
readAndDiscardModel
(
mapper
,
ois
,
cityObjectConsumer
,
outputFile
,
config
,
file
);
}
private
static
void
readAndDiscardModel
(
Citygml3FeatureMapper
mapper
,
ObservedInputStream
ois
,
CityGmlConsumer
cityObjectConsumer
,
String
outputFile
,
ParserConfiguration
config
,
Path
file
)
throws
CityGMLReadException
{
setupContext
();
CityGMLInputFactory
inputFactory
=
context
.
createCityGMLInputFactory
()
.
withChunking
(
ChunkOptions
.
chunkByProperties
(
chunkProperties
).
skipCityModel
(
false
));
CityGMLChunkWriter
writer
=
null
;
try
(
CityGMLReader
reader
=
inputFactory
.
createCityGMLReader
(
ois
))
{
CityDoctorModel
model
=
mapper
.
getModel
();
boolean
isInitialized
=
false
;
while
(
reader
.
hasNext
())
{
AbstractFeature
chunk
=
reader
.
next
();
if
(
writer
==
null
)
{
writer
=
createCityModelWriter
(
outputFile
,
reader
);
}
if
(!
isInitialized
&&
writer
!=
null
&&
reader
.
getParentInfo
()
!=
null
&&
reader
.
getParentInfo
().
getTypeName
().
getLocalPart
().
equals
(
"CityModel"
))
{
FeatureInfo
parentInfo
=
reader
.
getParentInfo
();
writer
.
withCityModelInfo
(
parentInfo
);
isInitialized
=
true
;
}
if
(
chunk
instanceof
AbstractCityObject
ag
)
{
ag
.
accept
(
mapper
);
drainCityModel
(
model
,
cityObjectConsumer
);
writeAbstractCityObject
(
writer
,
ag
);
}
else
if
(
chunk
instanceof
CityModel
cModel
)
{
cModel
.
setCityObjectMembers
(
null
);
mapper
.
setCityModel
(
cModel
);
cityObjectConsumer
.
accept
(
cModel
);
writeCityModel
(
writer
,
cModel
);
}
else
if
(
writer
!=
null
)
{
writer
.
writeMember
(
chunk
);
}
}
// end of stream
logger
.
debug
(
"End of gml file stream"
);
}
catch
(
CityGMLReadException
e
)
{
logger
.
error
(
Localization
.
getText
(
"CityGmlParser.errorReadingGmlFile"
),
e
.
getMessage
(),
e
);
}
catch
(
CityGMLWriteException
e
)
{
logger
.
error
(
Localization
.
getText
(
"CityGmlParser.errorWritingGmlFile"
),
e
.
getMessage
(),
e
);
}
finally
{
if
(
writer
!=
null
)
{
try
{
writer
.
close
();
}
catch
(
CityGMLWriteException
e
)
{
// ignore
}
}
}
}
private
static
void
writeCityModel
(
CityGMLChunkWriter
writer
,
CityModel
cModel
)
throws
CityGMLWriteException
{
if
(
writer
!=
null
)
{
for
(
AbstractFeatureProperty
featureProp
:
cModel
.
getFeatureMembers
())
{
writer
.
writeMember
(
featureProp
.
getObject
());
}
}
}
private
static
void
writeAbstractCityObject
(
CityGMLChunkWriter
writer
,
AbstractCityObject
ag
)
throws
CityGMLWriteException
{
if
(
writer
!=
null
)
{
writer
.
writeMember
(
ag
);
}
}
private
static
CityGMLChunkWriter
createCityModelWriter
(
String
outputFile
,
CityGMLReader
reader
)
throws
CityGMLWriteException
{
if
(
outputFile
==
null
)
{
return
null
;
}
CityGMLContext
gmlContext
=
CityGmlParser
.
setupContext
();
CityGMLVersion
version
=
CityGMLModules
.
getCityGMLVersion
(
reader
.
getName
().
getNamespaceURI
());
CityGMLOutputFactory
factory
=
gmlContext
.
createCityGMLOutputFactory
(
version
);
CityGMLChunkWriter
writer
=
factory
.
createCityGMLChunkWriter
(
new
File
(
outputFile
),
StandardCharsets
.
UTF_8
.
name
());
writer
.
withPrefix
(
"qual"
,
QualityADEModule
.
NAMESPACE_URI
);
writer
.
withSchemaLocation
(
QualityADEModule
.
NAMESPACE_URI
,
QualityADEModule
.
NAMESPACE_URI
+
"/qualityAde.xsd"
);
writer
.
withIndent
(
" "
);
writer
.
withDefaultPrefixes
();
writer
.
withDefaultSchemaLocations
();
return
writer
;
}
private
static
CityDoctorModel
readAndKeepFeatures
(
ParserConfiguration
config
,
Path
file
,
CityGMLInputFactory
inputFactory
,
ObservedInputStream
ois
,
boolean
verbose
,
boolean
unconnected
)
throws
CityGMLReadException
{
return
readAndKeepModel
(
new
Citygml3FeatureMapper
(
config
,
file
,
unconnected
),
inputFactory
,
ois
,
verbose
);
}
private
static
CityDoctorModel
readAndKeepModel
(
Citygml3FeatureMapper
mapper
,
CityGMLInputFactory
inputFactory
,
ObservedInputStream
ois
,
boolean
verbose
)
throws
CityGMLReadException
{
try
(
CityGMLReader
reader
=
inputFactory
.
createCityGMLReader
(
ois
))
{
CityGMLVersion
version
=
null
;
// model is read in chunked mode
// object members are replaced by href in model
// need to remove the refs and re-add unparsed objects
List
<
AbstractCityObject
>
acos
=
new
ArrayList
<>();
CityModel
cgmlModel
=
null
;
CityDoctorModel
cdModel
=
mapper
.
getModel
();
AutoCloseable
parsingContext
=
cdModel
.
getCache
().
startParsingMode
();
try
(
parsingContext
)
{
List
<
AbstractCityObject
>
queuedObjects
=
new
ArrayList
<>();
while
(
reader
.
hasNext
())
{
AbstractFeature
chunk
=
reader
.
next
();
version
=
CityGMLModules
.
getCityGMLVersion
(
reader
.
getName
().
getNamespaceURI
());
if
(
chunk
instanceof
CityModel
cModel
)
{
cModel
.
setCityObjectMembers
(
null
);
mapper
.
setCityModel
(
cModel
);
mapper
.
setCityGMLVersion
(
version
);
cgmlModel
=
cModel
;
if
(!
queuedObjects
.
isEmpty
())
{
for
(
AbstractCityObject
aco
:
queuedObjects
)
{
if
(!
cdModel
.
containsGmlId
(
aco
.
getId
()))
{
cgmlModel
.
getCityObjectMembers
().
add
(
new
AbstractCityObjectProperty
(
aco
));
}
}
}
}
else
if
(
chunk
instanceof
AbstractCityObject
aco
)
{
acos
.
add
(
aco
);
// getid
String
id
=
aco
.
getId
();
aco
.
accept
(
mapper
);
if
(
cgmlModel
==
null
)
{
// Add object to queue till the CityModel has been parsed
queuedObjects
.
add
(
aco
);
}
else
if
(!
cdModel
.
containsGmlId
(
id
))
{
cgmlModel
.
getCityObjectMembers
().
add
(
new
AbstractCityObjectProperty
(
aco
));
}
}
}
if
(
mapper
.
getModel
().
getCityModel
()
==
null
)
{
// file does not contain a city model?
// create it for now
logger
.
warn
(
Localization
.
getText
(
"CityGmlParser.missingCityModel"
));
mapper
.
setCityModel
(
new
CityModel
());
for
(
AbstractCityObject
aco
:
queuedObjects
)
{
if
(!
cdModel
.
containsGmlId
(
aco
.
getId
()))
{
mapper
.
getModel
().
getCityModel
().
getCityObjectMembers
()
.
add
(
new
AbstractCityObjectProperty
(
aco
));
}
}
}
ImplicitGeometryHolder
.
resolveDeferredObjects
(
mapper
.
getModel
().
getCache
());
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.parsedObjects"
),
mapper
.
getModel
().
getNumberOfFeatures
());
}
mapper
.
checkUnresolvedReferences
();
mapper
.
setCityGMLVersion
(
version
);
return
mapper
.
getModel
();
}
catch
(
Exception
e
)
{
throw
new
CityGMLReadException
(
e
);
}
}
}
private
static
void
parseEpsgCodeFromFile
(
Path
file
,
ParserConfiguration
config
,
boolean
verbose
)
throws
CityGmlParseException
{
try
(
BufferedInputStream
bis
=
new
BufferedInputStream
(
new
FileInputStream
(
file
.
toFile
())))
{
parseEpsgCodeFromStream
(
bis
,
config
,
verbose
);
}
catch
(
ParserConfigurationException
|
SAXException
|
IOException
e
)
{
throw
new
CityGmlParseException
(
"Failed to read CityGML file"
,
e
);
}
}
private
static
void
parseEpsgCodeFromStream
(
InputStream
is
,
ParserConfiguration
config
,
boolean
verbose
)
throws
ParserConfigurationException
,
SAXException
{
SAXParser
parser
=
FACTORY
.
newSAXParser
();
CityGmlHandler
handler
=
new
CityGmlHandler
();
try
{
parser
.
parse
(
new
InputSource
(
is
),
handler
);
}
catch
(
EnvelopeFoundException
e
)
{
try
{
parseCoordinateSystem
(
config
,
handler
,
verbose
);
}
catch
(
Exception
e2
)
{
logEpsgParseError
(
e2
);
}
}
catch
(
Exception
e
)
{
logEpsgParseError
(
e
);
}
if
(
handler
.
getEpsg
()
==
null
&&
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.missingEPSGCode"
));
}
}
private
static
void
logEpsgParseError
(
Exception
e
)
{
logger
.
debug
(
"Exception while parsing for EPSG code"
,
e
);
if
(
logger
.
isWarnEnabled
())
{
logger
.
warn
(
Localization
.
getText
(
"CityGmlParser.failedEPSGParse"
));
}
}
private
static
void
parseCoordinateSystem
(
ParserConfiguration
config
,
CityGmlHandler
handler
,
boolean
verbose
)
{
if
(
handler
.
getEpsg
()
==
null
)
{
return
;
}
CoordinateReferenceSystem
crs
=
crsFromSrsName
(
handler
.
getEpsg
());
if
(
crs
==
null
)
{
// could not find a coordinate system for srsName
// assuming metric system
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.missingEPSGCode"
));
}
return
;
}
if
(
crs
.
getProjection
().
getUnits
()
==
Units
.
METRES
)
{
// coordinate system is in meters, do not convert
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.noConversionNeeded"
));
}
return
;
}
parseMeterConversion
(
config
,
crs
);
Vector3d
low
=
handler
.
getLowerCorner
();
Vector3d
up
=
handler
.
getUpperCorner
();
double
centerLong
=
low
.
getX
()
+
((
up
.
getX
()
-
low
.
getX
())
/
2
);
double
centerLat
=
low
.
getY
()
+
((
up
.
getY
()
-
low
.
getY
())
/
2
);
if
(!
crs
.
getName
().
equals
(
WGS_84
))
{
// need to convert coordinates first to WGS84, then find UTM Zone
CoordinateReferenceSystem
wgs84
=
crsFromSrsName
(
WGS_84
);
ProjCoordinate
p1
=
new
ProjCoordinate
();
p1
.
setValue
(
centerLong
,
centerLat
);
ProjCoordinate
p2
=
new
ProjCoordinate
();
BasicCoordinateTransform
bct
=
new
BasicCoordinateTransform
(
crs
,
wgs84
);
bct
.
transform
(
p1
,
p2
);
centerLong
=
p2
.
x
;
centerLat
=
p2
.
y
;
}
int
zone
=
(
int
)
(
31
+
Math
.
round
(
centerLong
/
6
));
CoordinateReferenceSystem
utm
;
if
(
centerLat
<
0
)
{
// south
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.convertCrsToUtmZoneS"
),
zone
);
}
utm
=
CRS_FACTORY
.
createFromParameters
(
"UTM"
,
"+proj=utm +ellps=WGS84 +units=m +zone="
+
zone
+
" +south"
);
}
else
{
// north
if
(
logger
.
isInfoEnabled
()
&&
verbose
)
{
logger
.
info
(
Localization
.
getText
(
"CityGmlParser.convertCrsToUtmZoneN"
),
zone
);
}
utm
=
CRS_FACTORY
.
createFromParameters
(
"UTM"
,
"+proj=utm +ellps=WGS84 +units=m +zone="
+
zone
);
}
config
.
setCoordinateSystem
(
crs
,
utm
);
}
private
static
void
parseMeterConversion
(
ParserConfiguration
config
,
CoordinateReferenceSystem
crs
)
{
Projection
projection
=
crs
.
getProjection
();
double
fromMetres
=
projection
.
getFromMetres
();
if
(
fromMetres
>
0
)
{
// also transform height information
config
.
setFromMeters
(
fromMetres
);
}
else
{
config
.
setFromMeters
(
1.0
);
}
}
/**
* 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.
* <p>
* 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.
* <p>
* 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
*/
private
static
CoordinateReferenceSystem
crsFromSrsName
(
String
srsName
)
{
srsName
=
srsName
.
trim
();
Matcher
mEPSG
=
P_EPSG
.
matcher
(
srsName
);
if
(
mEPSG
.
find
())
{
if
(
"EPSG:4979"
.
contentEquals
(
srsName
))
{
srsName
=
"EPSG:4236"
;
}
else
if
(
"EPSG:7415"
.
contentEquals
(
srsName
))
{
return
CRS_FACTORY
.
createFromParameters
(
"EPSG:7415"
,
"+proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.417,50.3319,465.552,-0.398957,0.343988,-1.8774,4.0725 +units=m +no_defs"
);
}
return
CRS_FACTORY
.
createFromName
(
srsName
);
}
Matcher
mOGC
=
P_OGC
.
matcher
(
srsName
);
if
(
mOGC
.
find
())
{
return
getCrsFromOGCSrsCode
(
mOGC
.
group
(
1
));
}
Matcher
mOGC2
=
P_OGC2
.
matcher
(
srsName
);
if
(
mOGC2
.
find
())
{
return
getCrsFromOGCSrsCode
(
mOGC2
.
group
(
1
));
}
Matcher
mURN
=
P_URN
.
matcher
(
srsName
);
// NOTE: Could use a HashMap if the switch/case becomes too long.
if
(
mURN
.
find
())
{
return
switch
(
mURN
.
group
(
1
))
{
case
"DE_DHDN_3GK2"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31466"
);
case
"DE_DHDN_3GK3"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31467"
);
case
"DE_DHDN_3GK4"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31468"
);
case
"DE_DHDN_3GK5"
->
CRS_FACTORY
.
createFromName
(
"EPSG:31469"
);
case
"ETRS89_UTM32"
->
CRS_FACTORY
.
createFromName
(
"EPSG:25832"
);
default
->
null
;
};
}
if
(
srsName
.
equals
(
"http://www.opengis.net/def/crs/EPSG/0/6697"
))
{
return
CRS_FACTORY
.
createFromParameters
(
"EPSG:6697"
,
"+proj=longlat +ellps=GRS80 +no_defs +axis=neu"
);
}
return
null
;
}
private
static
CoordinateReferenceSystem
getCrsFromOGCSrsCode
(
String
srsCode
)
{
String
crsName
=
"EPSG:"
+
srsCode
;
List
<
String
>
params
=
new
ArrayList
<>(
Arrays
.
stream
(
csReader
.
getParameters
(
crsName
)).
toList
());
params
.
removeIf
(
P_VUNITS
.
asPredicate
());
params
.
removeIf
(
P_GEOIDGRID
.
asPredicate
());
return
CRS_FACTORY
.
createFromParameters
(
crsName
,
params
.
toArray
(
new
String
[
0
]));
}
public
static
List
<
String
>
validateFile
(
GMLValidationHandler
handler
,
Path
file
)
throws
CityGmlParseException
{
if
(
handler
==
null
)
{
handler
=
new
GMLValidationHandler
();
}
setupContext
();
try
{
SchemaHandler
schemaHandler
=
new
ValidationSchemaHandler
(
context
.
getDefaultSchemaHandler
());
readAdditionalSchemaDefinitions
(
context
,
file
,
schemaHandler
);
Source
[]
schemas
=
schemaHandler
.
getSchemas
();
SchemaFactory
schemaFactory
=
SchemaFactory
.
newInstance
(
XMLConstants
.
W3C_XML_SCHEMA_NS_URI
);
schemaFactory
.
setFeature
(
"http://apache.org/xml/features/disallow-doctype-decl"
,
true
);
Schema
schema
=
schemaFactory
.
newSchema
(
schemas
);
Validator
validator
=
schema
.
newValidator
();
validator
.
setErrorHandler
(
handler
);
validator
.
validate
(
new
StreamSource
(
file
.
toFile
()));
return
handler
.
getMessages
();
}
catch
(
SchemaHandlerException
|
SAXException
|
IOException
e
)
{
throw
new
CityGmlParseException
(
"Failed to validate CityGML file"
,
e
);
}
}
private
static
void
readAdditionalSchemaDefinitions
(
CityGMLContext
context
,
Path
file
,
SchemaHandler
schemaHandler
)
throws
CityGmlParseException
{
try
(
XMLReader
reader
=
XMLReaderFactory
.
newInstance
(
context
.
getXMLObjects
()).
withSchemaHandler
(
schemaHandler
)
.
createReader
(
file
))
{
reader
.
nextTag
();
}
catch
(
Exception
e
)
{
throw
new
CityGmlParseException
(
"Failed to read file "
+
file
.
toAbsolutePath
()
+
"."
,
e
);
}
}
private
static
void
drainCityModel
(
CityDoctorModel
model
,
CityGmlConsumer
cityObjectConsumer
)
{
List
<
GmlId
>
id
=
new
ArrayList
<>();
model
.
createFeatureStream
().
forEach
(
co
->
{
cityObjectConsumer
.
accept
(
co
);
id
.
add
(
co
.
getGmlId
());
});
id
.
forEach
(
model:
:
removeFeature
);
}
}
CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/writer/CityGMLWriterUtils.java
View file @
1fbe16e1
...
...
@@ -65,7 +65,7 @@ public class CityGMLWriterUtils {
}
public
static
void
writeCityModel
(
String
file
,
CityDoctorModel
model
)
throws
CityDoctorWriteException
{
CityGMLContext
gmlContext
=
CityGmlParser
.
g
etContext
();
CityGMLContext
gmlContext
=
CityGmlParser
.
s
et
up
Context
();
CityModel
cModel
=
model
.
getCityModel
();
CityGMLOutputFactory
factory
=
gmlContext
.
createCityGMLOutputFactory
(
model
.
getCityGMLVersion
());
try
(
CityGMLWriter
writer
=
factory
.
createCityGMLWriter
(
new
File
(
file
)))
{
...
...
CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/parser/CityGml3ParserTest.java
View file @
1fbe16e1
...
...
@@ -37,7 +37,7 @@ public class CityGml3ParserTest {
@Test
public
void
testGetContext
()
{
CityGMLContext
context
=
CityGmlParser
.
g
etContext
();
CityGMLContext
context
=
CityGmlParser
.
s
et
up
Context
();
List
<
ADE
>
ades
=
ADERegistry
.
getInstance
().
getADEs
();
ADE
ade
=
ades
.
get
(
0
);
assertEquals
(
QualityADEContext
.
class
,
ade
.
getClass
());
...
...
CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java
View file @
1fbe16e1
...
...
@@ -65,6 +65,7 @@ import de.hft.stuttgart.citydoctor2.check.error.AttributeInvalidError;
import
de.hft.stuttgart.citydoctor2.check.error.AttributeMissingError
;
import
de.hft.stuttgart.citydoctor2.check.error.AttributeValueWrongError
;
import
de.hft.stuttgart.citydoctor2.check.error.SchematronError
;
import
de.hft.stuttgart.citydoctor2.check.error.XMLValidationError
;
import
de.hft.stuttgart.citydoctor2.checkresult.utility.CheckReportWriteException
;
import
de.hft.stuttgart.citydoctor2.checks.CheckContainer
;
import
de.hft.stuttgart.citydoctor2.checks.CheckPrototype
;
...
...
@@ -918,6 +919,20 @@ public class Checker {
// create reporter if available
XmlStreamReporter
xmlReporter
=
getXmlReporter
(
config
,
xmlBos
,
fileName
);
PdfStreamReporter
pdfReporter
=
getPdfReporter
(
config
,
pdfBos
,
fileName
);
// XML validation
if
(
config
.
isUseStreaming
()
&&
(
xmlReporter
!=
null
||
pdfReporter
!=
null
))
{
List
<
String
>
validationIssues
=
CityGmlParser
.
validateFile
(
null
,
inputFile
.
toPath
());
for
(
String
xmlError
:
validationIssues
)
{
XMLValidationError
validationError
=
new
XMLValidationError
(
xmlError
);
if
(
xmlReporter
!=
null
)
{
xmlReporter
.
reportGlobalError
(
validationError
);
}
if
(
pdfReporter
!=
null
)
{
pdfReporter
.
reportGlobalError
(
validationError
);
}
}
}
// execute schematron first
SvrlContentHandler
handler
=
executeSchematronValidationIfAvailable
(
config
,
inputFile
);
...
...
CityDoctorParent/CityDoctorValidation/src/test/java/de/hft/stuttgart/citydoctor2/check/CheckerTest.java
View file @
1fbe16e1
...
...
@@ -18,6 +18,22 @@
*/
package
de.hft.stuttgart.citydoctor2.check
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
junit
.
Assert
.
fail
;
import
java.io.File
;
import
java.io.IOException
;
import
java.nio.file.Files
;
import
java.nio.file.Path
;
import
org.apache.commons.io.FileUtils
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.junit.rules.TemporaryFolder
;
import
de.hft.stuttgart.citydoctor2.CityDoctorValidation
;
import
de.hft.stuttgart.citydoctor2.checkresult.CheckReport
;
import
de.hft.stuttgart.citydoctor2.checkresult.GlobalErrorStatistics
;
...
...
@@ -31,160 +47,165 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException;
import
de.hft.stuttgart.citydoctor2.parser.CityGmlParser
;
import
de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException
;
import
de.hft.stuttgart.citydoctor2.parser.ParserConfiguration
;
import
org.apache.commons.io.FileUtils
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.junit.rules.TemporaryFolder
;
import
java.io.File
;
import
java.io.IOException
;
import
java.nio.file.Files
;
import
java.nio.file.Path
;
import
static
org
.
junit
.
Assert
.
assertEquals
;
import
static
org
.
junit
.
Assert
.
assertFalse
;
import
static
org
.
junit
.
Assert
.
assertNotNull
;
import
static
org
.
junit
.
Assert
.
assertNull
;
import
static
org
.
junit
.
Assert
.
assertTrue
;
import
static
org
.
junit
.
Assert
.
fail
;
/**
* @author Matthias Betz
*/
public
class
CheckerTest
{
@Rule
public
TemporaryFolder
folder
=
new
TemporaryFolder
();
@Test
public
void
testSchematron
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
getRequirements
().
get
(
Requirement
.
R_SE_BS_ROOF_UNFRAGMENTED
.
toString
()).
setEnabled
(
false
);
config
.
setSchematronFilePathInGlobalParameters
(
"src/test/resources/schematronTest.xml"
);
CityDoctorModel
model
=
CityGmlParser
.
parseCityGmlFile
(
"src/test/resources/SimpleSolid_SrefBS_SchematronTest.gml"
,
config
.
getParserConfiguration
());
Checker
checker
=
new
Checker
(
config
,
model
);
checker
.
runChecks
();
for
(
Building
b
:
model
.
getBuildings
().
toList
())
{
if
(
b
.
getGmlId
().
getGmlString
().
equals
(
"_Simple_BD.1"
))
{
assertTrue
(
b
.
containsAnyError
());
}
else
{
assertFalse
(
b
.
containsAnyError
());
}
}
assertFalse
(
model
.
getGlobalErrors
().
isEmpty
());
}
@Test
public
void
testChecker
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
,
CityDoctorWriteException
{
File
f
=
folder
.
newFile
();
File
f2
=
folder
.
newFile
();
try
{
String
[]
args
=
new
String
[
6
];
args
[
0
]
=
"-in"
;
args
[
1
]
=
"src/test/resources/QA-CS-CONCOMP.gml"
;
args
[
2
]
=
"-xmlReport"
;
args
[
3
]
=
f
.
getAbsolutePath
();
args
[
4
]
=
"-pdfReport"
;
args
[
5
]
=
f2
.
getAbsolutePath
();
CityDoctorValidation
.
main
(
args
);
assertTrue
(
f
.
exists
());
assertTrue
(
f2
.
exists
());
}
finally
{
f
.
delete
();
f2
.
delete
();
}
}
@Test
public
void
testStreaming
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
,
CityDoctorWriteException
{
File
f
=
folder
.
newFile
();
File
f2
=
folder
.
newFile
();
File
f3
=
folder
.
newFile
();
try
{
String
[]
args
=
new
String
[
10
];
args
[
0
]
=
"-in"
;
args
[
1
]
=
"src/test/resources/testarea.gml"
;
args
[
2
]
=
"-config"
;
args
[
3
]
=
"src/test/resources/testConfigWithStreaming.yml"
;
args
[
4
]
=
"-pdfReport"
;
args
[
5
]
=
f
.
getAbsolutePath
();
args
[
6
]
=
"-xmlReport"
;
args
[
7
]
=
f2
.
getAbsolutePath
();
args
[
8
]
=
"-out"
;
args
[
9
]
=
f3
.
getAbsolutePath
();
CityDoctorValidation
.
main
(
args
);
assertTrue
(
f
.
exists
());
assertTrue
(
f2
.
exists
());
assertTrue
(
f3
.
exists
());
}
finally
{
f
.
delete
();
f2
.
delete
();
f3
.
delete
();
}
}
@Test
public
void
testReportCreation
()
throws
IOException
,
CityGmlParseException
,
InvalidGmlFileException
{
Path
tmpDir
=
null
;
try
{
tmpDir
=
Files
.
createTempDirectory
(
"testTmp"
);
tmpDir
.
toFile
().
deleteOnExit
();
Path
xmlPath
=
tmpDir
.
resolve
(
"report.xml"
);
Path
pdfPath
=
tmpDir
.
resolve
(
"report.pdf"
);
File
features
=
new
File
(
"src/test/resources/feature_types.gml"
);
CityDoctorModel
model
=
CityGmlParser
.
nestedCityGmlFileParse
(
features
.
toString
(),
new
ParserConfiguration
(
8
,
false
));
assertNotNull
(
model
);
Checker
checker
=
new
Checker
(
model
);
checker
.
runChecks
();
checker
.
writeXmlReport
(
xmlPath
.
toString
());
checker
.
writePdfReport
(
pdfPath
.
toString
());
assertTrue
(
Files
.
exists
(
xmlPath
));
assertTrue
(
Files
.
exists
(
pdfPath
));
try
{
CheckReport
report
=
CheckReport
.
load
(
xmlPath
.
toString
());
assertNotNull
(
report
);
GlobalStatistics
globalStatistics
=
report
.
getGlobalStatistics
();
assertNotNull
(
globalStatistics
);
ModelStatistics
modelStats
=
globalStatistics
.
getModelStats
();
assertNotNull
(
modelStats
);
assertEquals
(
1
,
modelStats
.
getNumBuildings
());
assertEquals
(
1
,
modelStats
.
getNumVegetation
());
assertEquals
(
1
,
modelStats
.
getNumTransportation
());
assertEquals
(
1
,
modelStats
.
getNumTunnelObjects
());
assertEquals
(
1
,
modelStats
.
getNumBridgeObjects
());
assertEquals
(
1
,
modelStats
.
getNumWaterObjects
());
assertEquals
(
1
,
modelStats
.
getNumLandObjects
());
assertEquals
(
1
,
modelStats
.
getNumCityFurniture
());
assertEquals
(
1
,
modelStats
.
getNumGenericCityObjects
());
GlobalErrorStatistics
globError
=
globalStatistics
.
getGlobalErrorStats
();
assertNotNull
(
globError
);
assertEquals
(
0
,
globError
.
getNumErrorBuildings
());
assertEquals
(
0
,
globError
.
getNumErrorVegetation
());
assertEquals
(
0
,
globError
.
getNumErrorTransportation
());
assertEquals
(
0
,
globError
.
getNumErrorTunnelObjects
());
assertEquals
(
0
,
globError
.
getNumErrorBridgeObjects
());
assertEquals
(
0
,
globError
.
getNumErrorWaterObjects
());
assertEquals
(
0
,
globError
.
getNumErrorLandObjects
());
assertEquals
(
1
,
globError
.
getNumErrorCityFurniture
());
assertEquals
(
1
,
globError
.
getNumErrorGenericCityObjects
());
}
catch
(
CheckReportParseException
e
)
{
fail
(
"Report should be valid file"
);
}
}
finally
{
if
(
tmpDir
!=
null
)
{
FileUtils
.
deleteDirectory
(
tmpDir
.
toFile
());
}
}
}
@Rule
public
TemporaryFolder
folder
=
new
TemporaryFolder
();
@Test
public
void
testSchematron
()
throws
CityGmlParseException
,
InvalidGmlFileException
{
ValidationConfiguration
config
=
ValidationConfiguration
.
loadStandardValidationConfig
();
config
.
getRequirements
().
get
(
Requirement
.
R_SE_BS_ROOF_UNFRAGMENTED
.
toString
()).
setEnabled
(
false
);
config
.
setSchematronFilePathInGlobalParameters
(
"src/test/resources/schematronTest.xml"
);
CityDoctorModel
model
=
CityGmlParser
.
parseCityGmlFile
(
"src/test/resources/SimpleSolid_SrefBS_SchematronTest.gml"
,
config
.
getParserConfiguration
());
Checker
checker
=
new
Checker
(
config
,
model
);
checker
.
runChecks
();
for
(
Building
b
:
model
.
getBuildings
().
toList
())
{
if
(
b
.
getGmlId
().
getGmlString
().
equals
(
"_Simple_BD.1"
))
{
assertTrue
(
b
.
containsAnyError
());
}
else
{
assertFalse
(
b
.
containsAnyError
());
}
}
assertFalse
(
model
.
getGlobalErrors
().
isEmpty
());
}
@Test
public
void
testChecker
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
,
CityDoctorWriteException
{
File
f
=
folder
.
newFile
();
File
f2
=
folder
.
newFile
();
try
{
String
[]
args
=
new
String
[
6
];
args
[
0
]
=
"-in"
;
args
[
1
]
=
"src/test/resources/QA-CS-CONCOMP.gml"
;
args
[
2
]
=
"-xmlReport"
;
args
[
3
]
=
f
.
getAbsolutePath
();
args
[
4
]
=
"-pdfReport"
;
args
[
5
]
=
f2
.
getAbsolutePath
();
CityDoctorValidation
.
main
(
args
);
assertTrue
(
f
.
exists
());
assertTrue
(
f2
.
exists
());
}
finally
{
f
.
delete
();
f2
.
delete
();
}
}
@Test
public
void
testStreaming
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
,
CityDoctorWriteException
{
File
f
=
folder
.
newFile
();
File
f2
=
folder
.
newFile
();
File
f3
=
folder
.
newFile
();
try
{
String
[]
args
=
new
String
[
10
];
args
[
0
]
=
"-in"
;
args
[
1
]
=
"src/test/resources/testarea.gml"
;
args
[
2
]
=
"-config"
;
args
[
3
]
=
"src/test/resources/testConfigWithStreaming.yml"
;
args
[
4
]
=
"-pdfReport"
;
args
[
5
]
=
f
.
getAbsolutePath
();
args
[
6
]
=
"-xmlReport"
;
args
[
7
]
=
f2
.
getAbsolutePath
();
args
[
8
]
=
"-out"
;
args
[
9
]
=
f3
.
getAbsolutePath
();
CityDoctorValidation
.
main
(
args
);
assertTrue
(
f
.
exists
());
assertTrue
(
f2
.
exists
());
assertTrue
(
f3
.
exists
());
}
finally
{
f
.
delete
();
f2
.
delete
();
f3
.
delete
();
}
}
@Test
public
void
testStreamingWithXMLValidation
()
throws
CityGmlParseException
,
IOException
,
InvalidGmlFileException
,
CityDoctorWriteException
,
CheckReportParseException
{
File
f2
=
folder
.
newFile
();
try
{
String
[]
args
=
new
String
[
6
];
args
[
0
]
=
"-in"
;
args
[
1
]
=
"src/test/resources/SimpleSolid_SrefBS_XMLInvalid.gml"
;
args
[
2
]
=
"-config"
;
args
[
3
]
=
"src/test/resources/testConfigWithStreamingWithValidation.yml"
;
args
[
4
]
=
"-xmlReport"
;
args
[
5
]
=
f2
.
getAbsolutePath
();
CityDoctorValidation
.
main
(
args
);
assertTrue
(
f2
.
exists
());
CheckReport
checkReport
=
CheckReport
.
load
(
f2
.
toPath
());
assertEquals
(
1
,
checkReport
.
getValidationResults
().
getGlobalErrors
().
size
());
}
finally
{
f2
.
delete
();
}
}
@Test
public
void
testReportCreation
()
throws
IOException
,
CityGmlParseException
,
InvalidGmlFileException
{
Path
tmpDir
=
null
;
try
{
tmpDir
=
Files
.
createTempDirectory
(
"testTmp"
);
tmpDir
.
toFile
().
deleteOnExit
();
Path
xmlPath
=
tmpDir
.
resolve
(
"report.xml"
);
Path
pdfPath
=
tmpDir
.
resolve
(
"report.pdf"
);
File
features
=
new
File
(
"src/test/resources/feature_types.gml"
);
CityDoctorModel
model
=
CityGmlParser
.
nestedCityGmlFileParse
(
features
.
toString
(),
new
ParserConfiguration
(
8
,
false
));
assertNotNull
(
model
);
Checker
checker
=
new
Checker
(
model
);
checker
.
runChecks
();
checker
.
writeXmlReport
(
xmlPath
.
toString
());
checker
.
writePdfReport
(
pdfPath
.
toString
());
assertTrue
(
Files
.
exists
(
xmlPath
));
assertTrue
(
Files
.
exists
(
pdfPath
));
try
{
CheckReport
report
=
CheckReport
.
load
(
xmlPath
.
toString
());
assertNotNull
(
report
);
GlobalStatistics
globalStatistics
=
report
.
getGlobalStatistics
();
assertNotNull
(
globalStatistics
);
ModelStatistics
modelStats
=
globalStatistics
.
getModelStats
();
assertNotNull
(
modelStats
);
assertEquals
(
1
,
modelStats
.
getNumBuildings
());
assertEquals
(
1
,
modelStats
.
getNumVegetation
());
assertEquals
(
1
,
modelStats
.
getNumTransportation
());
assertEquals
(
1
,
modelStats
.
getNumTunnelObjects
());
assertEquals
(
1
,
modelStats
.
getNumBridgeObjects
());
assertEquals
(
1
,
modelStats
.
getNumWaterObjects
());
assertEquals
(
1
,
modelStats
.
getNumLandObjects
());
assertEquals
(
1
,
modelStats
.
getNumCityFurniture
());
assertEquals
(
1
,
modelStats
.
getNumGenericCityObjects
());
GlobalErrorStatistics
globError
=
globalStatistics
.
getGlobalErrorStats
();
assertNotNull
(
globError
);
assertEquals
(
0
,
globError
.
getNumErrorBuildings
());
assertEquals
(
0
,
globError
.
getNumErrorVegetation
());
assertEquals
(
0
,
globError
.
getNumErrorTransportation
());
assertEquals
(
0
,
globError
.
getNumErrorTunnelObjects
());
assertEquals
(
0
,
globError
.
getNumErrorBridgeObjects
());
assertEquals
(
0
,
globError
.
getNumErrorWaterObjects
());
assertEquals
(
0
,
globError
.
getNumErrorLandObjects
());
assertEquals
(
1
,
globError
.
getNumErrorCityFurniture
());
assertEquals
(
1
,
globError
.
getNumErrorGenericCityObjects
());
}
catch
(
CheckReportParseException
e
)
{
fail
(
"Report should be valid file"
);
}
}
finally
{
if
(
tmpDir
!=
null
)
{
FileUtils
.
deleteDirectory
(
tmpDir
.
toFile
());
}
}
}
}
CityDoctorParent/CityDoctorValidation/src/test/resources/SimpleSolid_SrefBS_XMLInvalid.gml
0 → 100644
View file @
1fbe16e1
<?xml version="1.0" encoding="utf-8"?>
<core:CityModel
xmlns:core=
"http://www.opengis.net/citygml/2.0"
xmlns:gen=
"http://www.opengis.net/citygml/generics/2.0"
xmlns:bldg=
"http://www.opengis.net/citygml/building/2.0"
xmlns:app=
"http://www.opengis.net/citygml/appearance/2.0"
xmlns:dem=
"http://www.opengis.net/citygml/relief/2.0"
xmlns:gml=
"http://www.opengis.net/gml"
xmlns:xlink=
"http://www.w3.org/1999/xlink"
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://www.opengis.net/citygml/building/2.0 http://schemas.opengis.net/citygml/building/2.0/building.xsd http://www.opengis.net/citygml/appearance/2.0 http://schemas.opengis.net/citygml/appearance/2.0/appearance.xsd http://www.opengis.net/citygml/relief/2.0 http://schemas.opengis.net/citygml/relief/2.0/relief.xsd http://www.opengis.net/citygml/2.0 http://schemas.opengis.net/citygml/2.0/cityGMLBase.xsd http://www.opengis.net/citygml/generics/2.0 http://schemas.opengis.net/citygml/generics/2.0/generics.xsd"
>
<!--
Einfaches Gebäude mit Grundriss 3m x 5m und Satteldach, Traufhöhe 3m, Firsthöhe 4,5m
Modelliert mit Begrenzungsflächen (eine Dachfläche, 4 Wandflächen, 1 Grundfläche),
die Gebäudegeometrie als Solid, der auf die Polygone der Begrenzungsflächen referenziert.
Fehler in der Reihenfolge der Attribute.
CityGML 2.0
Gebäudevolumen: 56,25 m3
12.12.2025
Author: V. Coors, HFT Stuttgart
Lizenz:
-->
<core:cityObjectMember>
<bldg:Building
gml:id=
"_Simple_BD.1"
>
<gen:stringAttribute
name=
"averageStoreyHeight"
>
<gen:value>
2.5
</gen:value>
</gen:stringAttribute>
<gml:name>
falsche Reihenfolge
</gml:name>
<bldg:lod2Solid>
<gml:Solid>
<gml:exterior>
<gml:CompositeSurface>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.1"
/>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.2"
/>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.3"
/>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.4"
/>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.5"
/>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.6"
/>
<gml:surfaceMember
xlink:href=
"#_Simple_BD.1_PG.7"
/>
</gml:CompositeSurface>
</gml:exterior>
</gml:Solid>
</bldg:lod2Solid>
<bldg:boundedBy>
<bldg:WallSurface
gml:id=
"_Simple_BD.1_WallSurface_1"
>
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.2"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.2_LR.1"
>
<gml:posList
srsDimension=
"3"
>
13.0 15.0 0.0
13.0 15.0 3.0
13.0 10.0 3.0
13.0 10.0 0.0
13.0 15.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:WallSurface
gml:id=
"_Simple_BD.1_WallSurface_2"
>
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.3"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.3_LR.1"
>
<gml:posList
srsDimension=
"3"
>
10.0 15.0 0.0
10.0 15.0 3.0
11.5 15.0 4.5
13.0 15.0 3.0
13.0 15.0 0.0
10.0 15.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:WallSurface
gml:id=
"_Simple_BD.1_WallSurface_3"
>
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.4"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.4_LR.1"
>
<gml:posList
srsDimension=
"3"
>
10.0 10.0 3.0
10.0 15.0 3.0
10.0 15.0 0.0
10.0 10.0 0.0
10.0 10.0 3.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:WallSurface
gml:id=
"_Simple_BD.1_WallSurface_4"
>
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.5"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.5_LR.1"
>
<gml:posList
srsDimension=
"3"
>
13.0 10.0 0.0
13.0 10.0 3.0
11.5 10.0 4.5
10.0 10.0 3.0
10.0 10.0 0.0
13.0 10.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:WallSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:RoofSurface
gml:id=
"_Simple_BD.1_RoofSurface_1"
>
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.6"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.6_LR.1"
>
<gml:posList
srsDimension=
"3"
>
10.0 10.0 3.0
11.5 10.0 4.5
11.5 15.0 4.5
10.0 15.0 3.0
10.0 10.0 3.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.7"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.7_LR.1"
>
<gml:posList
srsDimension=
"3"
>
11.5 10.0 4.5
13.0 10.0 3.0
13.0 15.0 3.0
11.5 15.0 4.5
11.5 10.0 4.5
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:RoofSurface>
</bldg:boundedBy>
<bldg:boundedBy>
<bldg:GroundSurface
gml:id=
"_Simple_BD.1_GroundSurface_1"
>
<bldg:lod2MultiSurface>
<gml:MultiSurface>
<gml:surfaceMember>
<gml:Polygon
gml:id=
"_Simple_BD.1_PG.1"
>
<gml:exterior>
<gml:LinearRing
gml:id=
"_Simple_BD.1_PG.1_LR.1"
>
<gml:posList
srsDimension=
"3"
>
10.0 10.0 0.0
10.0 15.0 0.0
13.0 15.0 0.0
13.0 10.0 0.0
10.0 10.0 0.0
</gml:posList>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gml:surfaceMember>
</gml:MultiSurface>
</bldg:lod2MultiSurface>
</bldg:GroundSurface>
</bldg:boundedBy>
</bldg:Building>
</core:cityObjectMember>
</core:CityModel>
\ No newline at end of file
CityDoctorParent/CityDoctorValidation/src/test/resources/testConfigWithStreamingWithValidation.yml
0 → 100644
View file @
1fbe16e1
globalParameters
:
numberOfRoundingPlaces
:
8
schematronFilePath
:
src/test/resources/schematronTest.xml
useStreaming
:
true
requirements
:
R_GE_R_TOO_FEW_POINTS
:
enabled
:
true
R_GE_R_NOT_CLOSED
:
enabled
:
true
R_GE_R_CONSECUTIVE_POINTS_SAME
:
enabled
:
true
R_GE_R_SELF_INTERSECTION
:
enabled
:
true
R_GE_S_MULTIPLE_CONNECTED_COMPONENTS
:
enabled
:
true
R_GE_P_INTERIOR_DISCONNECTED
:
enabled
:
true
R_GE_P_INTERSECTING_RINGS
:
enabled
:
true
R_GE_P_NON_PLANAR
:
enabled
:
false
parameters
:
# one of ("distance", "angle", "both")
type
:
distance
distanceTolerance
:
0.01
angleTolerance
:
0.1
R_GE_P_HOLE_OUTSIDE
:
enabled
:
true
R_GE_P_ORIENTATION_RINGS_SAME
:
enabled
:
true
R_GE_P_INNER_RINGS_NESTED
:
enabled
:
true
R_GE_S_TOO_FEW_POLYGONS
:
enabled
:
true
R_GE_S_NOT_CLOSED
:
enabled
:
true
R_GE_S_NON_MANIFOLD_EDGE
:
enabled
:
true
R_GE_S_POLYGON_WRONG_ORIENTATION
:
enabled
:
true
R_GE_S_ALL_POLYGONS_WRONG_ORIENTATION
:
enabled
:
true
R_GE_S_NON_MANIFOLD_VERTEX
:
enabled
:
true
R_GE_S_SELF_INTERSECTION
:
enabled
:
true
\ No newline at end of file
CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java
View file @
1fbe16e1
...
...
@@ -289,7 +289,7 @@ public class Healer {
private
byte
[]
writeCityGml
(
AbstractCityObject
aco
)
throws
CityGMLWriteException
{
CityModel
model
=
new
CityModel
();
model
.
getCityObjectMembers
().
add
(
new
AbstractCityObjectProperty
(
aco
));
CityGMLContext
context
=
CityGmlParser
.
g
etContext
();
CityGMLContext
context
=
CityGmlParser
.
s
et
up
Context
();
CityGMLOutputFactory
outputFactory
=
context
.
createCityGMLOutputFactory
(
CityGMLVersion
.
v2_0
);
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
();
try
(
CityGMLWriter
writer
=
outputFactory
.
createCityGMLWriter
(
out
))
{
...
...
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