CityGmlParser.java 21.8 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
68
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.schema.CityGMLSchemaHandler;
import org.citygml4j.xml.writer.CityGMLChunkWriter;
import org.citygml4j.xml.writer.CityGMLOutputFactory;
import org.citygml4j.xml.writer.CityGMLWriteException;
69
70
71
72
73
74
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;
75
76
77
78
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
Matthias Betz's avatar
Matthias Betz committed
79
import org.xmlobjects.schema.SchemaHandlerException;
80
81
82

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

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

Matthias Betz's avatar
Matthias Betz committed
98
99
	private static final String CITY_OBJECT_MEMBER = "cityObjectMember";

100
101
	private static final String WGS_84 = "EPSG:4326";

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
	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
118
	private static final SAXParserFactory FACTORY;
119

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

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

		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));
135
136
137
	}

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

	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;
156
157
158
159
	}

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

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

	public static CityDoctorModel parseCityGmlFile(String filePath, ParserConfiguration config, ProgressListener l,
Matthias Betz's avatar
Matthias Betz committed
169
170
171
172
173
174
175
176
177
178
			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));
			}
		}

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

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

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

Matthias Betz's avatar
Matthias Betz committed
205
206
207
208
	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);
209
	}
210

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

Matthias Betz's avatar
Matthias Betz committed
230
231
232
233
234
	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);
235
			}
Matthias Betz's avatar
Matthias Betz committed
236
237
238
239
			readAndDiscardFeatures(file, config, ois, cityObjectConsumer, outputFile);
		} catch (IOException | CityGMLReadException e) {
			logger.error(Localization.getText("CityGmlParser.errorReadingGmlFile"), e.getMessage());
			logger.catching(Level.ERROR, e);
240
241
242
		}
	}

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

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

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

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

Matthias Betz's avatar
Matthias Betz committed
327
328
329
330
331
332
333
334
335
336
337
338
339
	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);
340
341
					CityGMLVersion version = CityGMLModules.getCityGMLVersion(reader.getName().getNamespaceURI());
					mapper.setCityGMLVersion(version);
Matthias Betz's avatar
Matthias Betz committed
342
343
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
				} 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()))) {
373
			parseEpsgCodeFromStream(bis, config);
Matthias Betz's avatar
Matthias Betz committed
374
375
		} catch (ParserConfigurationException | SAXException | IOException e) {
			throw new CityGmlParseException("Failed to read CityGML file", e);
376
377
378
379
380
		}
	}

	private static void parseEpsgCodeFromStream(InputStream is, ParserConfiguration config)
			throws ParserConfigurationException, SAXException {
Matthias Betz's avatar
Matthias Betz committed
381
		SAXParser parser = FACTORY.newSAXParser();
382
383
384
385
386
		CityGmlHandler handler = new CityGmlHandler();
		try {
			parser.parse(new InputSource(is), handler);
		} catch (EnvelopeFoundException e) {
			try {
Matthias Betz's avatar
Matthias Betz committed
387
				parseCoordinateSystem(config, handler);
388
389
			} catch (Exception e2) {
				logger.debug("Exception while parsing for EPSG code", e2);
Matthias Betz's avatar
Matthias Betz committed
390
391
392
				if (logger.isWarnEnabled()) {
					logger.warn(Localization.getText("CityGmlParser.noEPSG"));
				}
393
394
395
			}
		} catch (Exception e) {
			logger.debug("Exception while parsing for EPSG code", e);
Matthias Betz's avatar
Matthias Betz committed
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
			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;
		}
412
		if (crs.getProjection().getUnits() == Units.METRES) {
Matthias Betz's avatar
Matthias Betz committed
413
414
415
416
417
418
419
420
421
422
423
			// 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);
424
		if (!crs.getName().equals(WGS_84)) {
Matthias Betz's avatar
Matthias Betz committed
425
			// need to convert coordinates first to WGS84, then find UTM Zone
426
			CoordinateReferenceSystem wgs84 = crsFromSrsName(WGS_84);
Matthias Betz's avatar
Matthias Betz committed
427
428
429
430
431
			ProjCoordinate p1 = new ProjCoordinate();
			p1.setValue(centerLong, centerLat);
			ProjCoordinate p2 = new ProjCoordinate();
			BasicCoordinateTransform bct = new BasicCoordinateTransform(crs, wgs84);
			bct.transform(p1, p2);
432
433
			centerLong = p2.x;
			centerLat = p2.y;
Matthias Betz's avatar
Matthias Betz committed
434
435
436
437
438
		}
		int zone = (int) (31 + Math.round(centerLong / 6));
		CoordinateReferenceSystem utm;
		if (centerLat < 0) {
			// south
Matthias Betz's avatar
Matthias Betz committed
439
440
			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
441
442
		} else {
			// north
Matthias Betz's avatar
Matthias Betz committed
443
			logger.info("Converting coordiante system to UTM zone {}N", zone);
444
			utm = CRS_FACTORY.createFromParameters("UTM", "+proj=utm +ellps=WGS84 +units=m +zone=" + zone);
445
		}
Matthias Betz's avatar
Matthias Betz committed
446
		config.setCoordinateSystem(crs, utm);
447
448
449
450
451
452
453
454
455
456
457
458
459
	}

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

Matthias Betz's avatar
Matthias Betz committed
460
461
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
531
532
533
534
535
536
537
538
539
540
541
542
	/**
	 * 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 {
			CityGMLSchemaHandler schemaHandler = context.getDefaultSchemaHandler();
			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);
		}
	}

543
544
545
546
547
548
549
	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);
550
551
	}

552
553
554
	private static void drainCityObjectList(List<? extends CityObject> objects, CityGmlConsumer cityObjectConsumer) {
		for (CityObject co : objects) {
			cityObjectConsumer.accept(co);
555
		}
556
		objects.clear();
557
	}
Matthias Betz's avatar
Matthias Betz committed
558

559
}