CityGmlParser.java 22.4 KB
Newer Older
1
/*-
Matthias Betz's avatar
Matthias Betz committed
2
 *  Copyright 2022 Beuth Hochschule für Technik Berlin, Hochschule für Technik Stuttgart
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 * 
 *  This file is part of CityDoctor2.
 *
 *  CityDoctor2 is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  CityDoctor2 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with CityDoctor2.  If not, see <https://www.gnu.org/licenses/>.
 */
package de.hft.stuttgart.citydoctor2.parser;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
Matthias Betz's avatar
Matthias Betz committed
26
27
28
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
29
import java.util.ArrayList;
30
31
32
33
34
35
36
37
38
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
Matthias Betz's avatar
Matthias Betz committed
39
40
41
42
43
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
44
45
46
47

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Matthias Betz's avatar
Matthias Betz committed
48
49
import org.citygml4j.core.ade.ADEException;
import org.citygml4j.core.ade.ADERegistry;
50
import org.citygml4j.core.model.CityGMLVersion;
Matthias Betz's avatar
Matthias Betz committed
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import org.citygml4j.core.model.ade.ADEProperty;
import org.citygml4j.core.model.core.AbstractCityObject;
import org.citygml4j.core.model.core.AbstractCityObjectProperty;
import org.citygml4j.core.model.core.AbstractFeature;
import org.citygml4j.core.model.core.CityModel;
import org.citygml4j.core.util.CityGMLConstants;
import org.citygml4j.xml.CityGMLContext;
import org.citygml4j.xml.CityGMLContextException;
import org.citygml4j.xml.module.citygml.CityGMLModules;
import org.citygml4j.xml.reader.ChunkOptions;
import org.citygml4j.xml.reader.CityGMLInputFactory;
import org.citygml4j.xml.reader.CityGMLReadException;
import org.citygml4j.xml.reader.CityGMLReader;
import org.citygml4j.xml.reader.FeatureInfo;
import org.citygml4j.xml.writer.CityGMLChunkWriter;
import org.citygml4j.xml.writer.CityGMLOutputFactory;
import org.citygml4j.xml.writer.CityGMLWriteException;
68
69
70
71
72
73
import org.locationtech.proj4j.BasicCoordinateTransform;
import org.locationtech.proj4j.CRSFactory;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.ProjCoordinate;
import org.locationtech.proj4j.proj.Projection;
import org.locationtech.proj4j.units.Units;
74
75
76
77
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
78
import org.xmlobjects.schema.SchemaHandler;
Matthias Betz's avatar
Matthias Betz committed
79
import org.xmlobjects.schema.SchemaHandlerException;
80
81
import org.xmlobjects.stream.XMLReader;
import org.xmlobjects.stream.XMLReaderFactory;
82
83
84

import de.hft.stuttgart.citydoctor2.datastructure.CityDoctorModel;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
Matthias Betz's avatar
Matthias Betz committed
85
86
import de.hft.stuttgart.citydoctor2.mapper.citygml3.Citygml3FeatureMapper;
import de.hft.stuttgart.citydoctor2.mapper.citygml3.GMLValidationHandler;
87
import de.hft.stuttgart.citydoctor2.math.Vector3d;
Matthias Betz's avatar
Matthias Betz committed
88
import de.hft.stuttgart.citydoctor2.utils.Localization;
Matthias Betz's avatar
Matthias Betz committed
89
import de.hft.stuttgart.quality.QualityADEContext;
90
import de.hft.stuttgart.quality.QualityADEModule;
91
92
93
94
95
96
97
98
99

/**
 * Utility class to parse CityGML files.
 * 
 * @author Matthias Betz
 *
 */
public class CityGmlParser {

Matthias Betz's avatar
Matthias Betz committed
100
101
	private static final String CITY_OBJECT_MEMBER = "cityObjectMember";

102
103
	private static final String WGS_84 = "EPSG:4326";

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
	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:([^\\*]+)");

Matthias Betz's avatar
Matthias Betz committed
120
	private static final SAXParserFactory FACTORY;
121

Matthias Betz's avatar
Matthias Betz committed
122
123
124
	private static CityGMLContext context;
	private static List<QName> chunkProperties = new ArrayList<>();

125
	static {
126
		System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
Matthias Betz's avatar
Matthias Betz committed
127
		FACTORY = SAXParserFactory.newInstance();
128
		try {
129
			FACTORY.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, false);
130
131
132
		} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
			logger.catching(e);
		}
Matthias Betz's avatar
Matthias Betz committed
133
134
135
136

		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));
137
138
139
	}

	private CityGmlParser() {
Matthias Betz's avatar
Matthias Betz committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
	}

	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("Unable to create citygml4j context", e);
				throw new IllegalStateException("Unable to create citygml4j context");
			} catch (ADEException e) {
				logger.fatal("Unable to add ADE plugins to citygml4j", e);
				throw new IllegalStateException("Unable to add ADE plugins to citygml4j");
			}
		}
		return context;
158
159
160
161
	}

	public static CityDoctorModel parseCityGmlFile(String file, ParserConfiguration config)
			throws CityGmlParseException, InvalidGmlFileException {
162
		return parseCityGmlFile(file, config, null, null);
163
164
	}

165
	public static CityDoctorModel parseCityGmlFile(String file, ParserConfiguration config, ProgressListener l)
166
			throws CityGmlParseException, InvalidGmlFileException {
167
168
169
170
		return parseCityGmlFile(file, config, l, null);
	}

	public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
Matthias Betz's avatar
Matthias Betz committed
171
172
173
174
175
176
177
178
179
180
			GMLValidationHandler handler) throws CityGmlParseException, InvalidGmlFileException {
		CityGMLContext context = getContext();
		Path file = Paths.get(filePath);
		if (config.getValidate()) {
			List<String> messages = validateFile(context, handler, file);
			if (!messages.isEmpty()) {
				throw new InvalidGmlFileException("Invalid GML File. First error: \n" + messages.get(0));
			}
		}

181
182
		try {
			parseEpsgCodeFromFile(file, config);
Matthias Betz's avatar
Matthias Betz committed
183
184
185
			CityGMLInputFactory in = context.createCityGMLInputFactory()
					.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
			try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
186
187
188
				if (l != null) {
					ois.addListener(l::updateProgress);
				}
Matthias Betz's avatar
Matthias Betz committed
189
				return readAndKeepFeatures(config, file, in, ois);
190
			}
Matthias Betz's avatar
Matthias Betz committed
191
192
		} catch (CityGMLReadException | IOException e) {
			throw new CityGmlParseException("Failed to read CityGML file", e);
193
194
195
196
197
		}
	}

	public static void streamCityGml(String file, ParserConfiguration config, CityGmlConsumer cityObjectConsumer,
			String outputFile) throws CityGmlParseException {
Matthias Betz's avatar
Matthias Betz committed
198
		Path f = Paths.get(file);
199
		streamCityGml(f, config, null, cityObjectConsumer, outputFile);
200
201
	}

202
203
	public static void streamCityGml(File file, ParserConfiguration parserConfig, CityGmlConsumer cityObjectConsumer,
			String outputFile) throws CityGmlParseException {
Matthias Betz's avatar
Matthias Betz committed
204
		streamCityGml(file.toPath(), parserConfig, null, cityObjectConsumer, outputFile);
205
206
	}

Matthias Betz's avatar
Matthias Betz committed
207
208
209
210
	public static void streamCityGml(Path file, ParserConfiguration config, ProgressListener l,
			CityGmlConsumer cityObjectConsumer, String outputFile) throws CityGmlParseException {
		parseEpsgCodeFromFile(file, config);
		startReadingCityGml(file, config, l, cityObjectConsumer, outputFile);
211
	}
212

213
214
	public static CityModel parseOnlyCityModel(File inputFile) throws CityGmlParseException {
		try {
Matthias Betz's avatar
Matthias Betz committed
215
216
			CityGMLInputFactory inputFactory = context.createCityGMLInputFactory()
					.withChunking(ChunkOptions.chunkByProperties(chunkProperties).skipCityModel(false));
217
218
			try (CityGMLReader reader = inputFactory.createCityGMLReader(inputFile)) {
				while (reader.hasNext()) {
Matthias Betz's avatar
Matthias Betz committed
219
					AbstractFeature chunk = reader.next();
Matthias Betz's avatar
Matthias Betz committed
220
					if (chunk instanceof CityModel cModel) {
Matthias Betz's avatar
Matthias Betz committed
221
						cModel.setCityObjectMembers(null);
222
223
224
225
						return cModel;
					}
				}
			}
Matthias Betz's avatar
Matthias Betz committed
226
		} catch (CityGMLReadException e) {
227
228
229
230
			throw new CityGmlParseException(e);
		}
		throw new CityGmlParseException("Did not find any CityModel in CityGML file");
	}
231

Matthias Betz's avatar
Matthias Betz committed
232
233
234
235
236
	private static void startReadingCityGml(Path file, ParserConfiguration config, ProgressListener l,
			CityGmlConsumer cityObjectConsumer, String outputFile) {
		try (ObservedInputStream ois = new ObservedInputStream(file.toFile())) {
			if (l != null) {
				ois.addListener(l::updateProgress);
237
			}
Matthias Betz's avatar
Matthias Betz committed
238
239
240
241
			readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile);
		} catch (IOException | CityGMLReadException e) {
			logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage());
			logger.catching(Level.ERROR, e);
242
243
244
		}
	}

Matthias Betz's avatar
Matthias Betz committed
245
246
247
248
249
250
251
252
	private static void readAndDiscardFeatures(Path file, ParserConfiguration config, 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)) {
			Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file);
253
			CityDoctorModel model = mapper.getModel();
254
255
			boolean isInitialized = false;
			while (reader.hasNext()) {
Matthias Betz's avatar
Matthias Betz committed
256
257
258
259
260
261
262
263
				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);
264
265
					isInitialized = true;
				}
Matthias Betz's avatar
Matthias Betz committed
266
				if (chunk instanceof AbstractCityObject ag) {
267
					ag.accept(mapper);
268
269
					drainCityModel(model, cityObjectConsumer);
					writeAbstractCityObject(writer, ag);
Matthias Betz's avatar
Matthias Betz committed
270
				} else if (chunk instanceof CityModel cModel) {
Matthias Betz's avatar
Matthias Betz committed
271
					cModel.setCityObjectMembers(null);
272
					mapper.setCityModel(cModel);
273
					cityObjectConsumer.accept(cModel);
Matthias Betz's avatar
Matthias Betz committed
274
275
276
					writeCityModel(writer, cModel);
				} else if (writer != null) {
					writer.writeMember(chunk);
277
278
279
280
281
				}
			}
			// end of stream
			logger.debug("End of gml file stream");
		} catch (CityGMLReadException e) {
Matthias Betz's avatar
Matthias Betz committed
282
			logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage(), e);
283
284
		} catch (CityGMLWriteException e) {
			logger.error(Localization.getText("CityGmlParser.errorWritingGmlFile"), e.getMessage(), e);
Matthias Betz's avatar
Matthias Betz committed
285
286
287
288
289
290
291
292
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (CityGMLWriteException e) {
					// ignore
				}
			}
293
294
295
		}
	}

Matthias Betz's avatar
Matthias Betz committed
296
	private static void writeCityModel(CityGMLChunkWriter writer, CityModel cModel) {
297
		if (writer != null) {
Matthias Betz's avatar
Matthias Betz committed
298
299
300
			for (ADEProperty genEle : cModel.getADEProperties()) {
				writer.getCityModelInfo().addADEProperty(genEle);
			}
301
302
303
		}
	}

Matthias Betz's avatar
Matthias Betz committed
304
	private static void writeAbstractCityObject(CityGMLChunkWriter writer, AbstractCityObject ag)
305
306
			throws CityGMLWriteException {
		if (writer != null) {
Matthias Betz's avatar
Matthias Betz committed
307
			writer.writeMember(ag);
308
309
310
		}
	}

Matthias Betz's avatar
Matthias Betz committed
311
	private static CityGMLChunkWriter createCityModelWriter(String outputFile, CityGMLReader reader)
312
			throws CityGMLWriteException {
313
314
		if (outputFile == null) {
			return null;
315
		}
Matthias Betz's avatar
Matthias Betz committed
316
		CityGMLContext gmlContext = CityGmlParser.getContext();
317
318
		CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
		CityGMLOutputFactory factory = gmlContext.createCityGMLOutputFactory(version);
Matthias Betz's avatar
Matthias Betz committed
319
320
321
322
323
324
325
		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();
326
		return writer;
327
328
	}

Matthias Betz's avatar
Matthias Betz committed
329
330
331
332
333
334
335
336
337
338
339
340
341
	private static CityDoctorModel readAndKeepFeatures(ParserConfiguration config, Path file,
			CityGMLInputFactory inputFactory, ObservedInputStream ois) throws CityGMLReadException {
		try (CityGMLReader reader = inputFactory.createCityGMLReader(ois)) {
			Citygml3FeatureMapper mapper = new Citygml3FeatureMapper(config, file);
			// 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<>();
			while (reader.hasNext()) {
				AbstractFeature chunk = reader.next();
				if (chunk instanceof CityModel cModel) {
					cModel.setCityObjectMembers(null);
					mapper.setCityModel(cModel);
342
343
					CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
					mapper.setCityGMLVersion(version);
Matthias Betz's avatar
Matthias Betz committed
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
				} else if (chunk instanceof AbstractCityObject aco) {
					acos.add(aco);
					aco.accept(mapper);
				}
			}

			if (mapper.getModel().getCityModel() == null) {
				// file does not contain a city model?
				// create it for now
				mapper.setCityModel(new CityModel());
			}
			CityModel cModel = mapper.getModel().getCityModel();

			// remove those that should have been parsed
			List<AbstractCityObject> parsedCityObjects = mapper.getModel().createFeatureStream()
					.map(CityObject::getGmlObject).toList();
			acos.removeAll(parsedCityObjects);
			// re-add all not parsed objects
			for (AbstractCityObject aco : acos) {
				cModel.getCityObjectMembers().add(new AbstractCityObjectProperty(aco));
			}
			if (logger.isInfoEnabled()) {
				logger.info(Localization.getText("CityGmlParser.parsedObjects"),
						mapper.getModel().getNumberOfFeatures());
			}
			return mapper.getModel();
		}
	}

	private static void parseEpsgCodeFromFile(Path file, ParserConfiguration config) throws CityGmlParseException {
		try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file.toFile()))) {
375
			parseEpsgCodeFromStream(bis, config);
Matthias Betz's avatar
Matthias Betz committed
376
377
		} catch (ParserConfigurationException | SAXException | IOException e) {
			throw new CityGmlParseException("Failed to read CityGML file", e);
378
379
380
381
382
		}
	}

	private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config)
			throws ParserConfigurationException, SAXException {
Matthias Betz's avatar
Matthias Betz committed
383
		SAXParser parser = FACTORY.newSAXParser();
384
385
386
387
388
		CityGmlHandler handler = new CityGmlHandler();
		try {
			parser.parse(new InputSource(is), handler);
		} catch (EnvelopeFoundException e) {
			try {
Matthias Betz's avatar
Matthias Betz committed
389
				parseCoordinateSystem(config, handler);
390
391
			} catch (Exception e2) {
				logger.debug("Exception while parsing for EPSG code", e2);
Matthias Betz's avatar
Matthias Betz committed
392
393
394
				if (logger.isWarnEnabled()) {
					logger.warn(Localization.getText("CityGmlParser.noEPSG"));
				}
395
396
397
			}
		} catch (Exception e) {
			logger.debug("Exception while parsing for EPSG code", e);
Matthias Betz's avatar
Matthias Betz committed
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
			if (logger.isWarnEnabled()) {
				logger.warn(Localization.getText("CityGmlParser.noEPSG"));
			}
		}
	}

	private static void parseCoordinateSystem(ParserConfiguration config, CityGmlHandler handler) {
		if (handler.getEpsg() == null) {
			return;
		}
		CoordinateReferenceSystem crs = crsFromSrsName(handler.getEpsg());
		if (crs == null) {
			// could not find a coordinate system for srsName
			// assuming metric system
			return;
		}
414
		if (crs.getProjection().getUnits() == Units.METRES) {
Matthias Betz's avatar
Matthias Betz committed
415
416
417
418
419
420
421
422
423
424
425
			// coordinate system is in meters, do not convert
			if (logger.isInfoEnabled()) {
				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);
426
		if (!crs.getName().equals(WGS_84)) {
Matthias Betz's avatar
Matthias Betz committed
427
			// need to convert coordinates first to WGS84, then find UTM Zone
428
			CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84);
Matthias Betz's avatar
Matthias Betz committed
429
430
431
432
433
			ProjCoordinate p1 = new ProjCoordinate();
			p1.setValue(centerLong, centerLat);
			ProjCoordinate p2 = new ProjCoordinate();
			BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
			bct.transform(p1, p2);
434
435
			centerLong = p2.x;
			centerLat = p2.y;
Matthias Betz's avatar
Matthias Betz committed
436
437
438
439
440
		}
		int zone = (int) (31 + Math.round(centerLong / 6));
		CoordinateReferenceSystem utm;
		if (centerLat < 0) {
			// south
Matthias Betz's avatar
Matthias Betz committed
441
442
			logger.info("Converting coordiante system to UTM zone {}S", zone);
			utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone + " +south");
Matthias Betz's avatar
Matthias Betz committed
443
444
		} else {
			// north
Matthias Betz's avatar
Matthias Betz committed
445
			logger.info("Converting coordiante system to UTM zone {}N", zone);
446
			utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
447
		}
Matthias Betz's avatar
Matthias Betz committed
448
		config.setCoordinateSystem(crs, utm);
449
450
451
452
453
454
455
	}

	private static void parseMeterConversion(ParserConfiguration config, CoordinateReferenceSystem crs) {
		Projection projection = crs.getProjection();
		double fromMetres = projection.getFromMetres();
		if (fromMetres > 0) {
			// also transform height information
456
			config.setFromMeters(fromMetres);
457
		} else {
458
			config.setFromMeters(1.0);
459
460
461
		}
	}

Matthias Betz's avatar
Matthias Betz committed
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
	/**
	 * 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
	 */
	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 CRS_FACTORY.createFromName("EPSG:" + mOGC.group(1));
		}
		Matcher mOGC2 = P_OGC2.matcher(srsName);
		if (mOGC2.find()) {
			return CRS_FACTORY.createFromName("EPSG:" + mOGC2.group(1));
		}
		Matcher mURN = P_URN.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:
				return 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 List<String> validateFile(CityGMLContext context, GMLValidationHandler handler, Path file)
			throws CityGmlParseException {
		if (handler == null) {
			handler = new GMLValidationHandler();
		}
		try {
531
532
			SchemaHandler schemaHandler = new ValidationSchemaHandler(context.getDefaultSchemaHandler());
            readAdditionalSchemaDefinitions(context, file, schemaHandler);
Matthias Betz's avatar
Matthias Betz committed
533
534
535
536
537
538
539
540
541
542
543
544
545
			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);
		}
	}

546
547
548
549
550
551
552
553
554
555
556
	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);
		}
	}

557
558
559
560
561
562
563
	private static void drainCityModel(CityDoctorModel model, CityGmlConsumer cityObjectConsumer) {
		drainCityObjectList(model.getBuildings(), cityObjectConsumer);
		drainCityObjectList(model.getBridges(), cityObjectConsumer);
		drainCityObjectList(model.getVegetation(), cityObjectConsumer);
		drainCityObjectList(model.getLand(), cityObjectConsumer);
		drainCityObjectList(model.getTransportation(), cityObjectConsumer);
		drainCityObjectList(model.getWater(), cityObjectConsumer);
564
565
	}

566
567
568
	private static void drainCityObjectList(List<? extends CityObject> objects, CityGmlConsumer cityObjectConsumer) {
		for (CityObject co : objects) {
			cityObjectConsumer.accept(co);
569
		}
570
		objects.clear();
571
	}
Matthias Betz's avatar
Matthias Betz committed
572

573
}