diff --git a/citydb-database/src/main/java/org/citydb/database/util/SrsHelper.java b/citydb-database/src/main/java/org/citydb/database/util/SrsHelper.java index 3612c43e..b17bf985 100644 --- a/citydb-database/src/main/java/org/citydb/database/util/SrsHelper.java +++ b/citydb-database/src/main/java/org/citydb/database/util/SrsHelper.java @@ -127,7 +127,7 @@ public int parseSRID(String identifier) throws SrsException { throw UncheckedException.wrap(e); } }); - } catch (RuntimeException e) { + } catch (Exception e) { throw UncheckedException.unwrap(e, SrsException.class); } } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityGMLAdapterContext.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityGMLAdapterContext.java index bd8de108..cdae07b6 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityGMLAdapterContext.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityGMLAdapterContext.java @@ -17,8 +17,6 @@ import org.citydb.model.common.Name; import org.citygml4j.core.ade.ADEException; import org.citygml4j.core.ade.ADERegistry; -import org.citygml4j.xml.CityGMLContext; -import org.citygml4j.xml.CityGMLContextException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -28,15 +26,13 @@ import java.util.concurrent.ConcurrentHashMap; public class CityGMLAdapterContext { - private final CityGMLContext context; private final Map builders = new ConcurrentHashMap<>(); private final Map> serializers = new ConcurrentHashMap<>(); CityGMLAdapterContext(ClassLoader loader) throws IOAdapterException { try { ADERegistry.getInstance().loadADEs(loader); - context = CityGMLContext.newInstance(loader); - } catch (ADEException | CityGMLContextException e) { + } catch (ADEException e) { throw new IOAdapterException("Failed to create CityGML context.", e); } @@ -44,10 +40,6 @@ public class CityGMLAdapterContext { loadSerializers(loader); } - public CityGMLContext getCityGMLContext() { - return context; - } - @SuppressWarnings("unchecked") public ModelBuilder getBuilder(Class sourceType, Class targetType) { BuilderInfo info = builders.get(sourceType.getName()); diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityJSONAdapter.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityJSONAdapter.java index a4dde0c1..c927617f 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityJSONAdapter.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/CityJSONAdapter.java @@ -20,8 +20,6 @@ import org.citydb.io.writer.FeatureWriter; import org.citydb.io.writer.WriteException; import org.citydb.io.writer.WriteOptions; -import org.citygml4j.cityjson.CityJSONContext; -import org.citygml4j.cityjson.CityJSONContextException; import java.io.InputStream; import java.nio.charset.Charset; @@ -32,17 +30,11 @@ mediaType = "application/city+json", fileExtensions = {".json", ".jsonl"}) public class CityJSONAdapter implements IOAdapter { - private CityGMLAdapterContext adapterContext; - private CityJSONContext cityJSONContext; + private CityGMLAdapterContext context; @Override public void initialize(ClassLoader loader) throws IOAdapterException { - adapterContext = new CityGMLAdapterContext(loader); - try { - cityJSONContext = CityJSONContext.newInstance(loader); - } catch (CityJSONContextException e) { - throw new IOAdapterException("Failed to create CityJSON context.", e); - } + context = new CityGMLAdapterContext(loader); } @Override @@ -67,12 +59,12 @@ public boolean canRead(InputFile file) { @Override public FeatureReader createReader(InputFile file, ReadOptions options) throws ReadException { - return new CityJSONReader(file, options, adapterContext, cityJSONContext); + return new CityJSONReader(file, options, context); } @Override public FeatureWriter createWriter(OutputFile file, WriteOptions options) throws WriteException { - return new CityJSONWriter(file, options, adapterContext, cityJSONContext); + return new CityJSONWriter(file, options, context); } @Override diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceBuilder.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceBuilder.java index 34976fa8..d7c613ca 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceBuilder.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceBuilder.java @@ -38,7 +38,7 @@ public void build(org.citygml4j.core.model.appearance.Appearance source, Appeara if (property != null) { if (property.isSetInlineObject()) { AbstractSurfaceData object = property.getObject(); - if (helper.lookupAndPut(object)) { + if (appearanceHelper.lookupAndPut(object)) { target.getSurfaceData().add(SurfaceDataProperty.of(object.getId())); } else { SurfaceDataAdapter, AbstractSurfaceData> builder = helper.getContext() diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceHelper.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceHelper.java index 59d79a9c..c3111e7d 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceHelper.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/appearance/builder/AppearanceHelper.java @@ -41,6 +41,7 @@ public class AppearanceHelper { private final Map> surfaceData = new IdentityHashMap<>(); private final Map>>> surfaces = new IdentityHashMap<>(); private final Map>> rings = new IdentityHashMap<>(); + private final Set surfaceDataIdCache = new HashSet<>(); private boolean processAppearances = true; private Set themes; @@ -59,6 +60,10 @@ public AppearanceHelper initialize(FormatOptions formatOptions) { return this; } + public boolean lookupAndPut(AbstractSurfaceData surfaceData) { + return surfaceData.getId() != null && !surfaceDataIdCache.add(surfaceData.getId()); + } + public org.citydb.model.appearance.Appearance getAppearance(AbstractAppearance source) throws ModelBuildException { if (processAppearances && source instanceof Appearance appearance @@ -89,10 +94,10 @@ public void addTarget(LinearRing ring, AbstractGeometry source, GeometryProperty } } - public void processTargets(AbstractFeature feature) { + public void processTargets(VisitableObject object) { if (processAppearances && !surfaceData.isEmpty()) { AppearanceCollector collector = new AppearanceCollector(); - for (AppearanceCollector.Context context : collector.collect(feature)) { + for (AppearanceCollector.Context context : collector.collect(object)) { for (Appearance appearance : context.appearances) { processor.process(appearance, context.properties); } @@ -104,13 +109,14 @@ public void reset() { surfaceData.clear(); surfaces.clear(); rings.clear(); + surfaceDataIdCache.clear(); } private static class AppearanceCollector extends ObjectWalker { private final Map contexts = new IdentityHashMap<>(); - Collection collect(AbstractFeature feature) { - feature.accept(this); + Collection collect(VisitableObject object) { + object.accept(this); return contexts.values(); } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/AbstractFeatureAdapter.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/AbstractFeatureAdapter.java index b1d191dc..9b346de1 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/AbstractFeatureAdapter.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/AbstractFeatureAdapter.java @@ -28,17 +28,21 @@ public abstract class AbstractFeatureAdapter extends @Override public void build(T source, Feature target, ModelBuilderHelper helper) throws ModelBuildException { super.build(source, target, helper); + boolean isTopLevel = source.getParent() == null; - if (helper.isComputeEnvelopes() + boolean computeEnvelope = (isTopLevel && helper.isComputeEnvelopes()) || source.getBoundedBy() == null - || !source.getBoundedBy().isSetEnvelope()) { + || !source.getBoundedBy().isSetEnvelope(); + if (computeEnvelope) { source.computeEnvelope(EnvelopeOptions.defaults().setEnvelopeOnFeatures(true)); - } else { - // make sure implicit geometries are included in envelope + } + + // make sure implicit geometries are included in envelope + if (!computeEnvelope || helper.hasImplicitGeometries()) { source.accept(new ObjectWalker() { @Override public void visit(ImplicitGeometry implicitGeometry) { - source.getBoundedBy().getEnvelope().include(implicitGeometry.computeEnvelope()); + source.getBoundedBy().getEnvelope().include(helper.computeEnvelope(implicitGeometry)); } }); } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/ImplicitGeometryAdapter.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/ImplicitGeometryAdapter.java index 0f4d2c1d..9738f03a 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/ImplicitGeometryAdapter.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/core/ImplicitGeometryAdapter.java @@ -11,8 +11,6 @@ import org.citydb.io.citygml.serializer.ModelSerializeException; import org.citydb.io.citygml.serializer.ModelSerializer; import org.citydb.io.citygml.writer.ModelSerializerHelper; -import org.citydb.model.common.Name; -import org.citydb.model.common.Namespaces; import org.citydb.model.geometry.ImplicitGeometry; import org.citydb.model.geometry.Point; import org.citydb.model.property.AppearanceProperty; @@ -33,13 +31,6 @@ public void build(org.citygml4j.core.model.core.ImplicitGeometry source, Implici if (source.getReferencePoint() != null) { target.setReferencePoint(helper.getPointGeometry(source.getReferencePoint().getObject(), Point.class)); } - - ImplicitGeometry implicitGeometry = target.getObject().orElse(null); - if (implicitGeometry != null && source.isSetAppearances()) { - for (AbstractAppearanceProperty property : source.getAppearances()) { - helper.addAppearance(Name.of("appearance", Namespaces.CORE), property, implicitGeometry); - } - } } @Override diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/GeometryHelper.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/GeometryHelper.java index 0660b4b1..d4961972 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/GeometryHelper.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/GeometryHelper.java @@ -8,17 +8,10 @@ import org.citydb.io.citygml.adapter.appearance.builder.AppearanceHelper; import org.citydb.io.citygml.builder.ModelBuildException; import org.citydb.io.citygml.reader.ModelBuilderHelper; -import org.citydb.model.common.ExternalFile; -import org.citydb.model.common.Name; import org.citydb.model.geometry.Geometry; -import org.citydb.model.geometry.ImplicitGeometry; -import org.citydb.model.property.ImplicitGeometryProperty; -import org.slf4j.event.Level; import org.xmlobjects.gml.model.geometry.AbstractGeometry; import org.xmlobjects.gml.model.geometry.GeometryProperty; -import java.io.IOException; - public class GeometryHelper { private final ModelBuilderHelper helper; private final PointGeometryBuilder pointGeometryBuilder; @@ -127,42 +120,4 @@ private Geometry buildGeometry(AbstractGeometry source, boolean force2D, Geom return null; } - - public ImplicitGeometryProperty getImplicitGeometry(org.citygml4j.core.model.core.ImplicitGeometry source, Name name, boolean force2D) throws ModelBuildException { - if (source != null) { - if (source.getRelativeGeometry() != null) { - if (source.getRelativeGeometry().getObject() != null) { - AbstractGeometry template = source.getRelativeGeometry().getObject(); - if (helper.lookupAndPut(template)) { - return ImplicitGeometryProperty.of(name, template.getId()); - } else { - Geometry geometry = getGeometry(template, force2D); - if (geometry != null) { - return ImplicitGeometryProperty.of(name, ImplicitGeometry.of(geometry - .setSrsIdentifier(template.getSrsName()))); - } - } - } else if (source.getRelativeGeometry().getHref() != null) { - return ImplicitGeometryProperty.of(name, - helper.getIdFromReference(source.getRelativeGeometry().getHref())); - } - } else if (source.getLibraryObject() != null) { - try { - ExternalFile libraryObject = helper.getExternalFile(source.getLibraryObject()); - return helper.lookupAndPut(libraryObject) ? - ImplicitGeometryProperty.of(name, libraryObject.getOrCreateObjectId()) : - ImplicitGeometryProperty.of(name, ImplicitGeometry.of(libraryObject)); - } catch (IOException e) { - helper.logOrThrow(Level.ERROR, helper.formatMessage(source, "Failed to read library object file " + - source.getLibraryObject() + "."), e); - } - } - } - - return null; - } - - public ImplicitGeometryProperty getImplicitGeometry(org.citygml4j.core.model.core.ImplicitGeometryProperty source, Name name, boolean force2D) throws ModelBuildException { - return source != null ? getImplicitGeometry(source.getObject(), name, force2D) : null; - } } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/ImplicitGeometryHelper.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/ImplicitGeometryHelper.java new file mode 100644 index 00000000..cf467a67 --- /dev/null +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/adapter/geometry/builder/ImplicitGeometryHelper.java @@ -0,0 +1,112 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright virtualcitysystems GmbH + */ + +package org.citydb.io.citygml.adapter.geometry.builder; + +import org.citydb.io.citygml.adapter.appearance.builder.AppearanceHelper; +import org.citydb.io.citygml.builder.ModelBuildException; +import org.citydb.io.citygml.reader.ModelBuilderHelper; +import org.citydb.io.citygml.reader.preprocess.ImplicitGeometryResolver; +import org.citydb.io.citygml.reader.util.FeatureHelper; +import org.citydb.model.appearance.Appearance; +import org.citydb.model.common.ExternalFile; +import org.citydb.model.common.Name; +import org.citydb.model.common.Namespaces; +import org.citydb.model.geometry.Geometry; +import org.citydb.model.geometry.ImplicitGeometry; +import org.citydb.model.property.AppearanceProperty; +import org.citydb.model.property.ImplicitGeometryProperty; +import org.citygml4j.core.model.core.AbstractAppearanceProperty; +import org.slf4j.event.Level; +import org.xmlobjects.gml.model.geometry.AbstractGeometry; +import org.xmlobjects.gml.model.geometry.Envelope; + +import java.io.IOException; + +public class ImplicitGeometryHelper { + private final ImplicitGeometryResolver resolver; + private final ModelBuilderHelper helper; + + public ImplicitGeometryHelper(ImplicitGeometryResolver resolver, ModelBuilderHelper helper) { + this.resolver = resolver; + this.helper = helper; + } + + public boolean hasImplicitGeometries() { + return resolver.hasImplicitGeometries(); + } + + public Envelope computeEnvelope(org.citygml4j.core.model.core.ImplicitGeometry implicitGeometry) { + return resolver.computeEnvelope(implicitGeometry); + } + + public ImplicitGeometryProperty getImplicitGeometry(Name name, org.citygml4j.core.model.core.ImplicitGeometry source, boolean force2D) throws ModelBuildException { + if (source != null) { + if (source.getRelativeGeometry() != null) { + if (source.getRelativeGeometry().getHref() != null) { + String objectId = FeatureHelper.getIdFromReference(source.getRelativeGeometry().getHref()); + if (helper.lookupAndPut(source.getRelativeGeometry())) { + return ImplicitGeometryProperty.of(name, objectId); + } else { + ImplicitGeometry target = resolver.getOrConvert(objectId, + implicitGeometry -> buildImplicitGeometry(implicitGeometry, force2D)); + if (target != null) { + return ImplicitGeometryProperty.of(name, target); + } + } + } else { + ImplicitGeometry target = buildImplicitGeometry(source, force2D); + if (target != null) { + return ImplicitGeometryProperty.of(name, target); + } + } + } else if (source.getLibraryObject() != null) { + try { + ExternalFile libraryObject = helper.getExternalFile(source.getLibraryObject()); + return helper.lookupAndPut(libraryObject) ? + ImplicitGeometryProperty.of(name, libraryObject.getOrCreateObjectId()) : + ImplicitGeometryProperty.of(name, ImplicitGeometry.of(libraryObject)); + } catch (IOException e) { + helper.logOrThrow(Level.ERROR, helper.formatMessage(source, "Failed to read library object file " + + source.getLibraryObject() + "."), e); + } + } + } + + return null; + } + + private ImplicitGeometry buildImplicitGeometry(org.citygml4j.core.model.core.ImplicitGeometry source, boolean force2D) throws ModelBuildException { + if (source.getRelativeGeometry() != null && source.getRelativeGeometry().getObject() != null) { + AbstractGeometry template = source.getRelativeGeometry().getObject(); + AppearanceHelper appearanceHelper = new AppearanceHelper(helper); + GeometryHelper geometryHelper = new GeometryHelper(appearanceHelper, helper); + + Geometry geometry = geometryHelper.getGeometry(template, force2D); + if (geometry != null) { + ImplicitGeometry target = ImplicitGeometry.of(geometry.setSrsIdentifier(template.getSrsName())); + if (source.isSetAppearances()) { + for (AbstractAppearanceProperty property : source.getAppearances()) { + if (property != null && property.getObject() != null) { + Appearance appearance = appearanceHelper.getAppearance(property.getObject()); + if (appearance != null) { + target.addAppearance(AppearanceProperty.of( + Name.of("appearance", Namespaces.CORE), appearance)); + } + } + } + + if (target.hasAppearances()) { + appearanceHelper.processTargets(source); + } + } + + return target; + } + } + + return null; + } +} diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityGMLReader.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityGMLReader.java index c702ae9d..22416b1a 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityGMLReader.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityGMLReader.java @@ -13,6 +13,7 @@ import org.citydb.io.citygml.CityGMLAdapterContext; import org.citydb.io.citygml.reader.preprocess.CityGMLPreprocessor; import org.citydb.io.citygml.reader.util.FileMetadata; +import org.citydb.io.citygml.reader.util.TemplateReferenceBuilder; import org.citydb.io.reader.FeatureReader; import org.citydb.io.reader.ReadException; import org.citydb.io.reader.ReadOptions; @@ -20,10 +21,14 @@ import org.citydb.model.feature.Feature; import org.citygml4j.core.model.cityobjectgroup.CityObjectGroup; import org.citygml4j.core.model.core.AbstractFeature; +import org.citygml4j.xml.CityGMLContext; +import org.citygml4j.xml.CityGMLContextException; +import org.citygml4j.xml.module.citygml.CoreModule; import org.citygml4j.xml.reader.CityGMLChunk; import org.citygml4j.xml.reader.CityGMLInputFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.xmlobjects.XMLObjectsException; import java.io.IOException; import java.util.Iterator; @@ -36,6 +41,7 @@ public class CityGMLReader implements FeatureReader { private final InputFile file; private final ReadOptions options; private final CityGMLAdapterContext context; + private final CityGMLContext cityGMLContext; private final CityGMLReaderFactory factory; private final CityGMLFormatOptions formatOptions; private final PersistentMapStore store; @@ -49,7 +55,13 @@ public class CityGMLReader implements FeatureReader { public CityGMLReader(InputFile file, ReadOptions options, CityGMLAdapterContext context) throws ReadException { this.file = Objects.requireNonNull(file, "The input file must not be null."); this.options = Objects.requireNonNull(options, "The read options must not be null."); - this.context = Objects.requireNonNull(context, "CityGML adapter context must not be null."); + this.context = Objects.requireNonNull(context, "The CityGML adapter context must not be null."); + + try { + cityGMLContext = CityGMLContext.newInstance(context.getClass().getClassLoader()); + } catch (CityGMLContextException e) { + throw new ReadException("Failed to create CityGML context.", e); + } try { formatOptions = options.getFormatOptions() @@ -67,7 +79,7 @@ public CityGMLReader(InputFile file, ReadOptions options, CityGMLAdapterContext throw new ReadException("Failed to initialize local cache.", e); } - factory = CityGMLReaderFactory.newInstance(context.getCityGMLContext(), options, formatOptions); + factory = CityGMLReaderFactory.newInstance(cityGMLContext, options, formatOptions); filter = options.getFilter().orElseGet(Filter::acceptAll); preprocessor = new CityGMLPreprocessor() .resolveCrossLodReferences(formatOptions.isResolveCrossLodReferences()) @@ -83,6 +95,7 @@ public void read(Consumer consumer) throws ReadException { shouldRun = true; if (!isPreprocessed) { preprocess(); + readTemplatesAsReference(); } CityGMLInputFactory inputFactory = factory.createInputFactory(); @@ -93,11 +106,13 @@ public void read(Consumer consumer) throws ReadException { ExecutorService service = ExecutorHelper.newFixedAndBlockingThreadPool(threads); CountLatch countLatch = new CountLatch(); - try (org.citygml4j.xml.reader.CityGMLReader reader = factory.createReader(file, inputFactory)) { + try (org.citygml4j.xml.reader.CityGMLReader reader = factory.createReader(file, inputFactory, + "CityObjectGroup", "Appearance")) { int featureId = 0; FileMetadata metadata = FileMetadata.of(reader); ThreadLocal helpers = ThreadLocal.withInitial(() -> - new ModelBuilderHelper(file, store, context).initialize(metadata, options, formatOptions)); + new ModelBuilderHelper(file, preprocessor.getImplicitGeometryResolver(), store, context) + .initialize(metadata, options, formatOptions, cityGMLContext)); while (shouldRun && reader.hasNext()) { CityGMLChunk chunk = reader.nextChunk(); @@ -166,6 +181,19 @@ private void preprocess() throws ReadException { logger.debug("Reading global objects and resolving global references..."); preprocessor.processGlobalObjects(file, factory); isPreprocessed = true; + logger.debug("Finished processing global objects and references."); + } + + private void readTemplatesAsReference() throws ReadException { + try { + TemplateReferenceBuilder builder = new TemplateReferenceBuilder(); + cityGMLContext.getXMLObjects() + .registerBuilder(builder, CoreModule.v3_0.getNamespaceURI(), "ImplicitGeometry") + .registerBuilder(builder, CoreModule.v2_0.getNamespaceURI(), "ImplicitGeometry") + .registerBuilder(builder, CoreModule.v1_0.getNamespaceURI(), "ImplicitGeometry"); + } catch (XMLObjectsException e) { + throw new ReadException("Failed to register template reference builder.", e); + } } @Override diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityJSONReader.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityJSONReader.java index dde14ee1..2ebb3660 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityJSONReader.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/CityJSONReader.java @@ -19,6 +19,7 @@ import org.citydb.io.reader.filter.Filter; import org.citydb.model.feature.Feature; import org.citygml4j.cityjson.CityJSONContext; +import org.citygml4j.cityjson.CityJSONContextException; import org.citygml4j.core.model.core.AbstractFeature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,7 +33,7 @@ public class CityJSONReader implements FeatureReader { private final Logger logger = LoggerFactory.getLogger(CityJSONReader.class); private final InputFile file; private final ReadOptions options; - private final CityGMLAdapterContext adapterContext; + private final CityGMLAdapterContext context; private final CityJSONReaderFactory factory; private final CityJSONFormatOptions formatOptions; private final PersistentMapStore store; @@ -42,11 +43,17 @@ public class CityJSONReader implements FeatureReader { private volatile boolean shouldRun = true; private Throwable exception; - public CityJSONReader(InputFile file, ReadOptions options, CityGMLAdapterContext adapterContext, CityJSONContext cityJSONContext) throws ReadException { + public CityJSONReader(InputFile file, ReadOptions options, CityGMLAdapterContext context) throws ReadException { this.file = Objects.requireNonNull(file, "The input file must not be null."); this.options = Objects.requireNonNull(options, "The read options must not be null."); - this.adapterContext = Objects.requireNonNull(adapterContext, "CityGML adapter context must not be null."); - Objects.requireNonNull(cityJSONContext, "CityJSON context must not be null."); + this.context = Objects.requireNonNull(context, "The CityGML adapter context must not be null."); + + CityJSONContext cityJSONContext; + try { + cityJSONContext = CityJSONContext.newInstance(context.getClass().getClassLoader()); + } catch (CityJSONContextException e) { + throw new ReadException("Failed to create CityJSON context.", e); + } try { formatOptions = options.getFormatOptions() @@ -81,7 +88,8 @@ public void read(Consumer consumer) throws ReadException { try (org.citygml4j.cityjson.reader.CityJSONReader reader = factory.createReader(file)) { FileMetadata metadata = FileMetadata.of(reader); ThreadLocal helpers = ThreadLocal.withInitial(() -> - new ModelBuilderHelper(file, store, adapterContext).initialize(metadata, options, formatOptions)); + new ModelBuilderHelper(file, preprocessor.getImplicitGeometryResolver(), store, context) + .initialize(metadata, options, formatOptions)); while (shouldRun && reader.hasNext()) { AbstractFeature feature = reader.next(); diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/ModelBuilderHelper.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/ModelBuilderHelper.java index b797e9d1..32365ca4 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/ModelBuilderHelper.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/ModelBuilderHelper.java @@ -13,11 +13,13 @@ import org.citydb.io.citygml.adapter.appearance.builder.AppearanceHelper; import org.citydb.io.citygml.adapter.core.ImplicitGeometryAdapter; import org.citydb.io.citygml.adapter.geometry.builder.GeometryHelper; +import org.citydb.io.citygml.adapter.geometry.builder.ImplicitGeometryHelper; import org.citydb.io.citygml.adapter.geometry.builder.Lod; import org.citydb.io.citygml.adapter.gml.CodeAdapter; import org.citydb.io.citygml.builder.ModelBuildException; import org.citydb.io.citygml.builder.ModelBuilder; import org.citydb.io.citygml.reader.options.FormatOptions; +import org.citydb.io.citygml.reader.preprocess.ImplicitGeometryResolver; import org.citydb.io.citygml.reader.util.FeatureHelper; import org.citydb.io.citygml.reader.util.FileMetadata; import org.citydb.io.reader.ReadOptions; @@ -29,14 +31,12 @@ import org.citydb.model.common.Name; import org.citydb.model.feature.Feature; import org.citydb.model.geometry.Geometry; -import org.citydb.model.geometry.ImplicitGeometry; import org.citydb.model.property.*; +import org.citydb.model.property.AddressProperty; +import org.citydb.model.property.ImplicitGeometryProperty; import org.citygml4j.core.model.CityGMLVersion; -import org.citygml4j.core.model.appearance.AbstractSurfaceData; -import org.citygml4j.core.model.core.AbstractAppearance; -import org.citygml4j.core.model.core.AbstractAppearanceProperty; -import org.citygml4j.core.model.core.AbstractFeature; -import org.citygml4j.core.model.core.StandardObjectClassifier; +import org.citygml4j.core.model.core.*; +import org.citygml4j.xml.CityGMLContext; import org.citygml4j.xml.module.Module; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +48,7 @@ import org.xmlobjects.gml.model.base.ResolvableAssociation; import org.xmlobjects.gml.model.basictypes.Code; import org.xmlobjects.gml.model.geometry.AbstractGeometry; +import org.xmlobjects.gml.model.geometry.Envelope; import org.xmlobjects.gml.model.geometry.SRSReference; import org.xmlobjects.stream.XMLWriter; import org.xmlobjects.stream.XMLWriterFactory; @@ -66,9 +67,9 @@ public class ModelBuilderHelper { private final CityGMLAdapterContext context; private final AppearanceHelper appearanceHelper; private final GeometryHelper geometryHelper; + private final ImplicitGeometryHelper implicitGeometryHelper; private final Map, ModelBuilder> builderCache = new IdentityHashMap<>(); private final Set featureIdCache = new HashSet<>(); - private final Set surfaceDataIdCache = new HashSet<>(); private final Set geometryIdCache = new HashSet<>(); private final Set addressIdCache = new HashSet<>(); private final Set externalFileIdCache = new HashSet<>(); @@ -80,17 +81,19 @@ public class ModelBuilderHelper { private boolean computeEnvelopes; private boolean includeXALSource; private ImplicitGeometryScope implicitGeometryScope; + private XMLObjects xmlObjects; - ModelBuilderHelper(InputFile file, PersistentMapStore store, CityGMLAdapterContext context) { + ModelBuilderHelper(InputFile file, ImplicitGeometryResolver resolver, PersistentMapStore store, CityGMLAdapterContext context) { this.file = Objects.requireNonNull(file, "The input file must not be null."); this.store = Objects.requireNonNull(store, "The persistent map store must not be null."); - this.context = Objects.requireNonNull(context, "CityGML adapter context must not be null."); + this.context = Objects.requireNonNull(context, "The CityGML adapter context must not be null."); appearanceHelper = new AppearanceHelper(this); geometryHelper = new GeometryHelper(appearanceHelper, this); + implicitGeometryHelper = new ImplicitGeometryHelper(resolver, this); } - private ModelBuilderHelper doInitialize(FileMetadata metadata, ReadOptions options, FormatOptions formatOptions) { + private ModelBuilderHelper doInitialize(FileMetadata metadata, ReadOptions options, FormatOptions formatOptions, CityGMLContext context) { version = metadata.getVersion() != null ? metadata.getVersion() : CityGMLVersion.v3_0; encoding = metadata.getEncoding() != null ? metadata.getEncoding() : StandardCharsets.UTF_8.name(); rootSrsName = metadata.getSrsName(); @@ -98,16 +101,18 @@ private ModelBuilderHelper doInitialize(FileMetadata metadata, ReadOptions optio computeEnvelopes = options.isComputeEnvelopes(); implicitGeometryScope = options.getImplicitGeometryScope(); appearanceHelper.initialize(formatOptions); + xmlObjects = context != null ? context.getXMLObjects() : null; return this; } - ModelBuilderHelper initialize(FileMetadata metadata, ReadOptions options, CityGMLFormatOptions formatOptions) { + ModelBuilderHelper initialize(FileMetadata metadata, ReadOptions options, CityGMLFormatOptions formatOptions, CityGMLContext context) { includeXALSource = formatOptions.isIncludeXALSource(); - return doInitialize(metadata, options, formatOptions); + return doInitialize(metadata, options, formatOptions, context); } ModelBuilderHelper initialize(FileMetadata metadata, ReadOptions options, CityJSONFormatOptions formatOptions) { - return doInitialize(metadata, options, formatOptions); + includeXALSource = false; + return doInitialize(metadata, options, formatOptions, null); } public InputFile getInputFile() { @@ -179,16 +184,22 @@ public ExternalFile getExternalFile(String location) throws IOException { return null; } - public boolean lookupAndPut(AbstractGML feature) { - return feature.getId() != null && !featureIdCache.add(feature.getId()); + public boolean hasImplicitGeometries() { + return implicitGeometryHelper.hasImplicitGeometries(); + } + + public Envelope computeEnvelope(ImplicitGeometry implicitGeometry) { + return implicitGeometryHelper.computeEnvelope(implicitGeometry); } - public boolean lookupAndPut(AbstractSurfaceData surfaceData) { - return surfaceData.getId() != null && !surfaceDataIdCache.add(surfaceData.getId()); + public boolean lookupAndPut(AbstractGML feature) { + return feature.getId() != null && !featureIdCache.add(feature.getId()); } - public boolean lookupAndPut(AbstractGeometry geometry) { - String objectId = geometry.getId(); + public boolean lookupAndPut(org.xmlobjects.gml.model.geometry.GeometryProperty property) { + String objectId = property.getObject() != null + ? property.getObject().getId() + : FeatureHelper.getIdFromReference(property.getHref()); if (objectId != null) { if (implicitGeometryScope == ImplicitGeometryScope.GLOBAL) { return store.getOrCreateMap("implicit-geometries").putIfAbsent(objectId, true) != null; @@ -278,10 +289,6 @@ public void addAppearance(Name name, AbstractAppearanceProperty source, Attribut addProperty(getAppearanceProperty(name, source), attribute::addProperty); } - public void addAppearance(Name name, AbstractAppearanceProperty source, ImplicitGeometry geometry) throws ModelBuildException { - addProperty(getAppearanceProperty(name, source), geometry::addAppearance); - } - public Appearance getAppearance(AbstractAppearance source) throws ModelBuildException { return appearanceHelper.getAppearance(source); } @@ -463,24 +470,21 @@ public ImplicitGeometryProperty getImplicitGeometryProperty(Name name, org.cityg return getImplicitGeometryProperty(name, source, lod, false); } - public ImplicitGeometryProperty getImplicitGeometryProperty(Name name, org.citygml4j.core.model.core.ImplicitGeometry source, Lod lod, boolean force2D) throws ModelBuildException { - return source != null ? - getImplicitGeometryProperty(source, geometryHelper.getImplicitGeometry(source, name, force2D), lod) : - null; - } - public ImplicitGeometryProperty getImplicitGeometryProperty(Name name, org.citygml4j.core.model.core.ImplicitGeometry source, Lod lod) throws ModelBuildException { return getImplicitGeometryProperty(name, source, lod, false); } - private ImplicitGeometryProperty getImplicitGeometryProperty(org.citygml4j.core.model.core.ImplicitGeometry source, ImplicitGeometryProperty target, Lod lod) throws ModelBuildException { - if (source != null && target != null) { - target = buildObject(source, target, getOrCreateBuilder(ImplicitGeometryAdapter.class)); - if (lod != null && lod != Lod.NONE) { - target.setLod(lod.getValue()); - } + public ImplicitGeometryProperty getImplicitGeometryProperty(Name name, org.citygml4j.core.model.core.ImplicitGeometry source, Lod lod, boolean force2D) throws ModelBuildException { + if (source != null) { + ImplicitGeometryProperty target = implicitGeometryHelper.getImplicitGeometry(name, source, force2D); + if (target != null) { + target = buildObject(source, target, getOrCreateBuilder(ImplicitGeometryAdapter.class)); + if (lod != null && lod != Lod.NONE) { + target.setLod(lod.getValue()); + } - return target; + return target; + } } return null; @@ -653,7 +657,7 @@ public String getFeatureReference(ResolvableAssociation association) { private String getReference(ResolvableAssociation association) { return association != null && association.getHref() != null ? - getIdFromReference(association.getHref()) : + FeatureHelper.getIdFromReference(association.getHref()) : null; } @@ -707,8 +711,7 @@ private R buildObject(T source, R target, ModelBuilder getCityObjectGroups() { return cityObjectGroups; } @@ -106,12 +111,17 @@ public void processGlobalObjects(InputFile file, CityGMLReaderFactory factory) t try { try (CityGMLReader reader = factory.createReader(file, inputFactory)) { List appearances = Collections.synchronizedList(new ArrayList<>()); - ImplicitGeometryCollector collector = new ImplicitGeometryCollector(); int featureId = 0; while (shouldRun && reader.hasNext()) { CityGMLChunk chunk = reader.nextChunk(); - chunk.getLocalProperties().set("featureId", featureId++); + + QName name = chunk.getFirstElement(); + if ((!"Appearance".equals(name.getLocalPart()) + && !"CityObjectGroup".equals(name.getLocalPart())) + || !CityGMLModules.isCityGMLNamespace(name.getNamespaceURI())) { + chunk.getLocalProperties().set("featureId", featureId++); + } countLatch.increment(); service.execute(() -> { @@ -122,7 +132,7 @@ public void processGlobalObjects(InputFile file, CityGMLReaderFactory factory) t } else if (feature instanceof CityObjectGroup group) { cityObjectGroups.add(group); } else { - feature.accept(collector); + implicitGeometryResolver.collectImplicitGeometries(feature); globalReferenceResolver.processGeometryReferences(feature, (int) chunk.getLocalProperties().get("featureId")); } @@ -196,15 +206,13 @@ public void processGlobalObjects(InputFile file, CityGMLReaderFactory factory) t } public boolean process(AbstractFeature feature, int featureId) { - if (!shouldRun - || feature instanceof Appearance - || feature instanceof CityObjectGroup) { + if (!shouldRun) { return false; } + implicitGeometryResolver.removeTemplateGeometries(feature); appearanceConverter.convertGlobalAppearance(feature); referenceResolver.resolveReferences(feature); - implicitGeometryResolver.resolveImplicitGeometries(feature); globalReferenceResolver.resolveGeometryReferences(feature, featureId); if (resolveCrossLodReferences || !propertiesProcessor.isUseLod4AsLod3()) { crossLodResolver.resolveCrossLodReferences(feature); @@ -222,11 +230,4 @@ public void postprocess() { public void cancel() { shouldRun = false; } - - private class ImplicitGeometryCollector extends ObjectWalker { - @Override - public void visit(ImplicitGeometry implicitGeometry) { - implicitGeometryResolver.addImplicitGeometry(implicitGeometry); - } - } } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CityJSONPreprocessor.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CityJSONPreprocessor.java index 38b2e63f..99aec514 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CityJSONPreprocessor.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CityJSONPreprocessor.java @@ -6,36 +6,27 @@ package org.citydb.io.citygml.reader.preprocess; import org.citygml4j.core.model.core.AbstractFeature; -import org.citygml4j.core.model.core.ImplicitGeometry; import org.citygml4j.core.util.reference.DefaultReferenceResolver; -import org.citygml4j.core.visitor.ObjectWalker; -import org.xmlobjects.copy.Copier; -import org.xmlobjects.copy.CopierBuilder; import org.xmlobjects.gml.util.reference.ReferenceResolver; public class CityJSONPreprocessor { private final ImplicitGeometryResolver implicitGeometryResolver; private final ReferenceResolver referenceResolver = DefaultReferenceResolver.newInstance(); - private final ImplicitGeometryCollector collector = new ImplicitGeometryCollector(); public CityJSONPreprocessor() { - Copier copier = CopierBuilder.newCopier(); - implicitGeometryResolver = new ImplicitGeometryResolver(copier); + implicitGeometryResolver = new ImplicitGeometryResolver(); } - public void processGlobalObjects(AbstractFeature abstractFeature) { - abstractFeature.accept(collector); + public ImplicitGeometryResolver getImplicitGeometryResolver() { + return implicitGeometryResolver; } - public void process(AbstractFeature feature) { - referenceResolver.resolveReferences(feature); - implicitGeometryResolver.resolveImplicitGeometries(feature); + public void processGlobalObjects(AbstractFeature feature) { + implicitGeometryResolver.collectImplicitGeometries(feature); } - private class ImplicitGeometryCollector extends ObjectWalker { - @Override - public void visit(ImplicitGeometry implicitGeometry) { - implicitGeometryResolver.addImplicitGeometry(implicitGeometry); - } + public void process(AbstractFeature feature) { + referenceResolver.resolveReferences(feature); + implicitGeometryResolver.removeTemplateGeometries(feature); } } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CrossLodReferenceResolver.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CrossLodReferenceResolver.java index 5dcae793..f3fe313c 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CrossLodReferenceResolver.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/CrossLodReferenceResolver.java @@ -7,6 +7,7 @@ import org.citygml4j.core.model.common.GeometryInfo; import org.citygml4j.core.model.core.AbstractFeature; +import org.citygml4j.core.model.core.ImplicitGeometry; import org.citygml4j.core.visitor.ObjectWalker; import org.xmlobjects.copy.CopySession; import org.xmlobjects.gml.model.GMLObject; @@ -105,13 +106,20 @@ Map>>> collect(AbstractF return references; } + @Override + public void visit(ImplicitGeometry implicitGeometry) { + } + @Override public void visit(GeometryProperty property) { if (property.isSetReferencedObject() && property.getHref() != null) { Integer lod = getLod(property); - Integer targetLod = getLod(property.getObject().getParent(GeometryProperty.class)); + if (lod == null) { + return; + } - if (lod != null && targetLod != null && !lod.equals(targetLod)) { + Integer targetLod = getLod(property.getObject().getParent(GeometryProperty.class)); + if (targetLod != null && !lod.equals(targetLod)) { if (mode == Mode.RESOLVE || (mode == Mode.REMOVE_LOD4_REFERENCES && targetLod == 4)) { references.computeIfAbsent(lod, k -> new LinkedHashMap<>()) @@ -119,9 +127,9 @@ public void visit(GeometryProperty property) { .add(property); } } + } else { + super.visit(property); } - - super.visit(property); } private Integer getLod(GeometryProperty property) { diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/DeprecatedPropertiesProcessor.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/DeprecatedPropertiesProcessor.java index b3a2c400..9c75776b 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/DeprecatedPropertiesProcessor.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/DeprecatedPropertiesProcessor.java @@ -16,10 +16,7 @@ import org.citygml4j.core.model.common.GeometryInfo; import org.citygml4j.core.model.construction.AbstractFillingSurface; import org.citygml4j.core.model.construction.RoofSurface; -import org.citygml4j.core.model.core.AbstractFeature; -import org.citygml4j.core.model.core.AbstractSpace; -import org.citygml4j.core.model.core.AbstractSpaceBoundaryProperty; -import org.citygml4j.core.model.core.AbstractThematicSurface; +import org.citygml4j.core.model.core.*; import org.citygml4j.core.model.deprecated.bridge.*; import org.citygml4j.core.model.deprecated.building.DeprecatedPropertiesOfAbstractBuilding; import org.citygml4j.core.model.deprecated.building.DeprecatedPropertiesOfBuildingFurniture; @@ -674,6 +671,14 @@ public void visit(WaterBody waterBody) { super.visit(waterBody); } + @Override + public void visit(ImplicitGeometry implicitGeometry) { + } + + @Override + public void visit(GeometryProperty property) { + } + private void processLod1MultiSurface(MultiSurfaceProperty property, AbstractSpace object) { GenericThematicSurface thematicSurface = new GenericThematicSurface(); thematicSurface.setLod1MultiSurface(property); diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/GlobalAppearanceConverter.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/GlobalAppearanceConverter.java index 70694908..a4760779 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/GlobalAppearanceConverter.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/GlobalAppearanceConverter.java @@ -29,6 +29,7 @@ public class GlobalAppearanceConverter { private final Copier copier; private final Map> targets = new ConcurrentHashMap<>(); + private final Map> textureAssociations = new ConcurrentHashMap<>(); private Mode mode = Mode.TOPLEVEL; @@ -89,47 +90,51 @@ public void visit(ParameterizedTexture texture) { } } + String textureId = FeatureHelper.getOrCreateId(texture); for (TextureAssociationProperty property : texture.getTextureParameterizations()) { GeometryReference reference = getGeometryReference(property); if (reference != null && reference.getHref() != null) { - targets.computeIfAbsent( - FeatureHelper.getIdFromReference(reference.getHref()), - v -> new ArrayList<>()).add(texture); + String geometryId = FeatureHelper.getIdFromReference(reference.getHref()); + targets.computeIfAbsent(geometryId, v -> new ArrayList<>()).add(texture); + textureAssociations.computeIfAbsent(textureId, v -> new ConcurrentHashMap<>()) + .put(geometryId, property); } } + texture.setTextureParameterizations(null); super.visit(texture); } @Override public void visit(GeoreferencedTexture texture) { - for (GeometryReference reference : texture.getTargets()) { - if (reference.getHref() != null) { - targets.computeIfAbsent( - FeatureHelper.getIdFromReference(reference.getHref()), - v -> new ArrayList<>()).add(texture); - } - } - + addTargets(texture, texture.getTargets()); + texture.setTargets(null); super.visit(texture); } @Override public void visit(X3DMaterial material) { - for (GeometryReference reference : material.getTargets()) { + addTargets(material, material.getTargets()); + material.setTargets(null); + super.visit(material); + } + + private void addTargets(AbstractSurfaceData surfaceData, List references) { + for (GeometryReference reference : references) { if (reference.getHref() != null) { targets.computeIfAbsent( FeatureHelper.getIdFromReference(reference.getHref()), - v -> new ArrayList<>()).add(material); + v -> new ArrayList<>()).add(surfaceData); } } - - super.visit(material); } }; DefaultReferenceResolver.newInstance().resolveReferences(appearances); - appearances.forEach(preprocessor::visit); + appearances.forEach(appearance -> { + appearance.accept(preprocessor); + appearance.setSurfaceData(null); + }); } } @@ -150,9 +155,9 @@ private void process(VisitableObject object, boolean removeTargets) { } private GeometryReference getGeometryReference(TextureAssociationProperty property) { - return property.getObject() != null && property.getObject().getTarget() != null ? - property.getObject().getTarget() : - null; + return property.getObject() != null && property.getObject().getTarget() != null + ? property.getObject().getTarget() + : null; } private class AppearanceProcessor extends ObjectWalker { @@ -163,9 +168,9 @@ private class AppearanceProcessor extends ObjectWalker { AppearanceProcessor(VisitableObject object, CopySession session, boolean removeTargets) { this.session = session; this.removeTargets = removeTargets; - topLevelFeature = object instanceof AbstractCityObject cityObject ? - cityObject : - object.getParent(AbstractCityObject.class); + topLevelFeature = object instanceof AbstractCityObject cityObject + ? cityObject + : object.getParent(AbstractCityObject.class); } @Override @@ -207,13 +212,16 @@ private void convertAppearance(AbstractGML target, AbstractSurfaceData source, A Appearance appearance = source.getParent(Appearance.class); AbstractSurfaceData surfaceData = getOrCreateSurfaceData(target, appearance, source); if (surfaceData instanceof ParameterizedTexture targetTexture) { - ParameterizedTexture texture = (ParameterizedTexture) source; - for (TextureAssociationProperty property : texture.getTextureParameterizations()) { - GeometryReference reference = getGeometryReference(property); - if (reference != null - && reference.getHref() != null - && FeatureHelper.getIdFromReference(reference.getHref()).equals(geometry.getId())) { + Map properties = textureAssociations.get(source.getId()); + if (properties != null) { + TextureAssociationProperty property = removeTargets + ? properties.remove(geometry.getId()) + : properties.get(geometry.getId()); + if (property != null) { targetTexture.getTextureParameterizations().add(property); + if (removeTargets && properties.isEmpty()) { + textureAssociations.remove(source.getId()); + } } } } else if (surfaceData instanceof X3DMaterial material) { @@ -229,14 +237,12 @@ private Appearance getOrCreateAppearance(AbstractGML target, Appearance globalAp return appearance; } - List appearances = target instanceof AbstractCityObject cityObject ? - cityObject.getAppearances() : - ((ImplicitGeometry) target).getAppearances(); + List appearances = target instanceof AbstractCityObject cityObject + ? cityObject.getAppearances() + : ((ImplicitGeometry) target).getAppearances(); appearance = copier.shallowCopy(globalAppearance, session); appearance.setId(target instanceof ImplicitGeometry ? FeatureHelper.createId() : null); - appearance.setSurfaceData(null); - appearance.setLocalProperties(null); appearances.add(new AbstractAppearanceProperty(appearance)); return appearance; @@ -250,16 +256,6 @@ private AbstractSurfaceData getOrCreateSurfaceData(AbstractGML target, Appearanc surfaceData = copier.shallowCopy(globalSurfaceData, session); surfaceData.setId(null); - surfaceData.setLocalProperties(null); - - if (surfaceData instanceof ParameterizedTexture texture) { - texture.setTextureParameterizations(null); - } else if (surfaceData instanceof X3DMaterial material) { - material.setTargets(null); - } else if (surfaceData instanceof GeoreferencedTexture texture) { - texture.setTargets(null); - } - Appearance appearance = getOrCreateAppearance(target, globalAppearance); appearance.getSurfaceData().add(new AbstractSurfaceDataProperty(surfaceData)); diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/ImplicitGeometryResolver.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/ImplicitGeometryResolver.java index faf7177d..cd9593f4 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/ImplicitGeometryResolver.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/preprocess/ImplicitGeometryResolver.java @@ -5,28 +5,34 @@ package org.citydb.io.citygml.reader.preprocess; +import org.citydb.core.exception.UncheckedException; +import org.citydb.core.function.CheckedFunction; +import org.citydb.io.citygml.builder.ModelBuildException; import org.citydb.io.citygml.reader.util.FeatureHelper; import org.citygml4j.core.model.core.AbstractAppearanceProperty; import org.citygml4j.core.model.core.AbstractFeature; import org.citygml4j.core.model.core.ImplicitGeometry; import org.citygml4j.core.visitor.ObjectWalker; -import org.xmlobjects.copy.Copier; +import org.xmlobjects.gml.model.geometry.AbstractGeometry; +import org.xmlobjects.gml.model.geometry.Envelope; import org.xmlobjects.gml.model.geometry.GeometryProperty; +import org.xmlobjects.gml.util.Matrices; +import org.xmlobjects.gml.util.matrix.Matrix; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ImplicitGeometryResolver { - private final Copier copier; private final Map implicitGeometries = new ConcurrentHashMap<>(); - private final ResolverProcessor processor = new ResolverProcessor(); + private final Map converted = new ConcurrentHashMap<>(); + private final Map envelopes = new ConcurrentHashMap<>(); - ImplicitGeometryResolver(Copier copier) { - this.copier = copier; + ImplicitGeometryResolver() { } - boolean hasImplicitGeometries() { + public boolean hasImplicitGeometries() { return !implicitGeometries.isEmpty(); } @@ -34,52 +40,103 @@ Collection getImplicitGeometries() { return implicitGeometries.values(); } - void addImplicitGeometry(ImplicitGeometry implicitGeometry) { + public org.citydb.model.geometry.ImplicitGeometry getOrConvert(String objectId, Converter converter) throws ModelBuildException { + try { + return converted.computeIfAbsent(objectId, k -> { + try { + ImplicitGeometry implicitGeometry = implicitGeometries.remove(k); + return implicitGeometry != null + ? converter.apply(implicitGeometry) + : null; + } catch (Exception e) { + throw UncheckedException.wrap(e); + } + }); + } catch (UncheckedException e) { + throw UncheckedException.unwrap(e, ModelBuildException.class); + } + } + + public Envelope computeEnvelope(ImplicitGeometry implicitGeometry) { + Envelope envelope = new Envelope(); if (implicitGeometry.getRelativeGeometry() != null - && implicitGeometry.getRelativeGeometry().isSetInlineObject() - && implicitGeometry.getRelativeGeometry().getObject().getId() != null) { - ImplicitGeometry template = new ImplicitGeometry(implicitGeometry.getRelativeGeometry()); - if (implicitGeometry.isSetAppearances()) { - implicitGeometry.getAppearances().stream() - .filter(AbstractAppearanceProperty::isSetInlineObject) - .forEach(template.getAppearances()::add); - } + && implicitGeometry.getRelativeGeometry().getHref() != null) { + String objectId = FeatureHelper.getIdFromReference(implicitGeometry.getRelativeGeometry().getHref()); + Envelope relative = envelopes.get(objectId); + + if (relative != null + && implicitGeometry.getTransformationMatrix() != null + && implicitGeometry.getReferencePoint() != null + && implicitGeometry.getReferencePoint().getObject() != null) { + Matrix matrix = implicitGeometry.getTransformationMatrix().getValue().copy(); - implicitGeometries.put(implicitGeometry.getRelativeGeometry().getObject().getId(), template); + List point = implicitGeometry.getReferencePoint().getObject().toCoordinateList3D(); + if (!point.isEmpty()) { + matrix.set(0, 3, matrix.get(0, 3) + point.get(0)); + matrix.set(1, 3, matrix.get(1, 3) + point.get(1)); + matrix.set(2, 3, matrix.get(2, 3) + point.get(2)); + } + + envelope.include(Matrices.transform3D(relative.getLowerCorner(), matrix)); + envelope.include(Matrices.transform3D(relative.getUpperCorner(), matrix)); + } else if (implicitGeometry.getReferencePoint() != null + && implicitGeometry.getReferencePoint().getObject() != null) { + List point = implicitGeometry.getReferencePoint().getObject().toCoordinateList3D(); + if (!point.isEmpty()) { + if (implicitGeometry.getTransformationMatrix() != null) { + Matrix matrix = implicitGeometry.getTransformationMatrix().getValue(); + point.set(0, point.get(0) + matrix.get(0, 3)); + point.set(1, point.get(1) + matrix.get(1, 3)); + point.set(2, point.get(2) + matrix.get(2, 3)); + } + + envelope.include(point); + } + } } - } - void resolveImplicitGeometries(AbstractFeature feature) { - feature.accept(processor); + return envelope; } - private class ResolverProcessor extends ObjectWalker { - @Override - public void visit(ImplicitGeometry implicitGeometry) { - GeometryProperty property = implicitGeometry.getRelativeGeometry(); - if (property != null) { - boolean inline = property.isSetInlineObject() && property.getObject().getId() != null; - String id = inline - ? property.getObject().getId() - : FeatureHelper.getIdFromReference(property.getHref()); - - ImplicitGeometry template = id != null ? implicitGeometries.get(id) : null; - if (template != null) { - if (inline) { - property.setInlineObjectIfValid(template.getRelativeGeometry().getObject()); - if (template.isSetAppearances()) { - implicitGeometry.setAppearances(template.getAppearances()); - } - } else { - property.setReferencedObjectIfValid(template.getRelativeGeometry().getObject()); - if (template.isSetAppearances()) { - implicitGeometry.setAppearances(template.getAppearances().stream() - .map(copier::shallowCopy) - .toList()); - } + void collectImplicitGeometries(AbstractFeature feature) { + feature.accept(new ObjectWalker() { + @Override + public void visit(ImplicitGeometry implicitGeometry) { + if (implicitGeometry.getRelativeGeometry() != null + && implicitGeometry.getRelativeGeometry().isSetInlineObject() + && implicitGeometry.getRelativeGeometry().getObject().getId() != null) { + AbstractGeometry geometry = implicitGeometry.getRelativeGeometry().getObject(); + ImplicitGeometry template = new ImplicitGeometry(new GeometryProperty<>(geometry)); + if (implicitGeometry.isSetAppearances()) { + implicitGeometry.getAppearances().stream() + .filter(AbstractAppearanceProperty::isSetInlineObject) + .forEach(template.getAppearances()::add); } + + implicitGeometries.put(geometry.getId(), template); + envelopes.put(geometry.getId(), geometry.computeEnvelope()); } } - } + }); + } + + void removeTemplateGeometries(AbstractFeature feature) { + feature.accept(new ObjectWalker() { + @Override + public void visit(ImplicitGeometry implicitGeometry) { + if (implicitGeometry.getRelativeGeometry() != null + && implicitGeometry.getRelativeGeometry().isSetInlineObject() + && implicitGeometry.getRelativeGeometry().getObject().getId() != null) { + String objectId = FeatureHelper.getOrCreateId(implicitGeometry.getRelativeGeometry().getObject()); + implicitGeometry.getRelativeGeometry().setHref("#" + objectId); + implicitGeometry.getRelativeGeometry().setInlineObject(null); + implicitGeometry.getRelativeGeometry().setReferencedObject(null); + } + } + }); + } + + @FunctionalInterface + public interface Converter extends CheckedFunction { } } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/util/TemplateReferenceBuilder.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/util/TemplateReferenceBuilder.java new file mode 100644 index 00000000..6ffcdb56 --- /dev/null +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/reader/util/TemplateReferenceBuilder.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright virtualcitysystems GmbH + */ + +package org.citydb.io.citygml.reader.util; + +import org.citygml4j.core.model.core.ImplicitGeometry; +import org.citygml4j.xml.adapter.CityGMLBuilderHelper; +import org.citygml4j.xml.adapter.core.ImplicitGeometryAdapter; +import org.xmlobjects.builder.ObjectBuildException; +import org.xmlobjects.gml.adapter.GMLBuilderHelper; +import org.xmlobjects.gml.model.geometry.AbstractGeometry; +import org.xmlobjects.gml.model.geometry.GeometryProperty; +import org.xmlobjects.gml.util.GMLConstants; +import org.xmlobjects.stream.EventType; +import org.xmlobjects.stream.XMLReadException; +import org.xmlobjects.stream.XMLReader; +import org.xmlobjects.xml.Attributes; + +import javax.xml.namespace.QName; + +public class TemplateReferenceBuilder extends ImplicitGeometryAdapter { + + @Override + public void buildChildObject(ImplicitGeometry object, QName name, Attributes attributes, XMLReader reader) throws ObjectBuildException, XMLReadException { + if (CityGMLBuilderHelper.isCoreNamespace(name.getNamespaceURI()) + && (name.getLocalPart().equals("relativeGeometry") + || name.getLocalPart().equals("relativeGMLGeometry"))) { + GeometryProperty property = new GeometryProperty<>(); + GMLBuilderHelper.buildAssociationAttributes(property, attributes); + + if (property.getHref() == null + && reader.hasNext() + && reader.nextTag() == EventType.START_ELEMENT) { + reader.getAttributes().getValue(GMLConstants.GML_3_1_NAMESPACE, "id") + .ifPresent(id -> property.setHref("#" + id)); + reader.getAttributes().getValue(GMLConstants.GML_3_2_NAMESPACE, "id") + .ifPresent(id -> property.setHref("#" + id)); + } + + object.setRelativeGeometry(property.getHref() != null + ? property + : new GeometryProperty<>(reader.getObject(AbstractGeometry.class))); + } else { + super.buildChildObject(object, name, attributes, reader); + } + } +} diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityGMLWriter.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityGMLWriter.java index 946017a8..ddee7614 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityGMLWriter.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityGMLWriter.java @@ -17,6 +17,8 @@ import org.citydb.io.writer.WriteOptions; import org.citydb.model.feature.Feature; import org.citygml4j.core.model.core.AbstractFeature; +import org.citygml4j.xml.CityGMLContext; +import org.citygml4j.xml.CityGMLContextException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmlobjects.util.xml.SAXBuffer; @@ -39,7 +41,14 @@ public class CityGMLWriter implements FeatureWriter, GlobalFeatureWriter { public CityGMLWriter(OutputFile file, WriteOptions options, CityGMLAdapterContext context) throws WriteException { Objects.requireNonNull(file, "The output file must not be null."); Objects.requireNonNull(options, "The write options must not be null."); - Objects.requireNonNull(context, "CityGML adapter context must not be null."); + Objects.requireNonNull(context, "The CityGML adapter context must not be null."); + + CityGMLContext cityGMLContext; + try { + cityGMLContext = CityGMLContext.newInstance(context.getClass().getClassLoader()); + } catch (CityGMLContextException e) { + throw new WriteException("Failed to create CityGML context.", e); + } CityGMLFormatOptions formatOptions; try { @@ -49,7 +58,7 @@ public CityGMLWriter(OutputFile file, WriteOptions options, CityGMLAdapterContex throw new WriteException("Failed to get CityGML format options from config.", e); } - writer = CityGMLWriterFactory.newInstance(context.getCityGMLContext(), options, formatOptions) + writer = CityGMLWriterFactory.newInstance(cityGMLContext, options, formatOptions) .createWriter(file); try { @@ -63,7 +72,7 @@ public CityGMLWriter(OutputFile file, WriteOptions options, CityGMLAdapterContex service = ExecutorHelper.newFixedAndBlockingThreadPool(1, 100); helpers = ThreadLocal.withInitial(() -> new ModelSerializerHelper(this, store, context) - .initialize(options, formatOptions)); + .initialize(options, formatOptions, cityGMLContext)); countLatch = new CountLatch(); } diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityJSONWriter.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityJSONWriter.java index 0e6b30ea..5471a8f2 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityJSONWriter.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/CityJSONWriter.java @@ -18,6 +18,7 @@ import org.citydb.model.feature.Feature; import org.citydb.model.geometry.ImplicitGeometry; import org.citygml4j.cityjson.CityJSONContext; +import org.citygml4j.cityjson.CityJSONContextException; import org.citygml4j.cityjson.writer.AbstractCityJSONWriter; import org.citygml4j.core.model.appearance.Appearance; import org.citygml4j.core.model.core.AbstractAppearanceProperty; @@ -42,11 +43,17 @@ public class CityJSONWriter implements FeatureWriter, GlobalFeatureWriter { private volatile boolean shouldRun = true; private Throwable exception; - public CityJSONWriter(OutputFile file, WriteOptions options, CityGMLAdapterContext adapterContext, CityJSONContext cityJSONContext) throws WriteException { + public CityJSONWriter(OutputFile file, WriteOptions options, CityGMLAdapterContext context) throws WriteException { Objects.requireNonNull(file, "The output file must not be null."); Objects.requireNonNull(options, "The write options must not be null."); - Objects.requireNonNull(adapterContext, "CityGML adapter context must not be null."); - Objects.requireNonNull(cityJSONContext, "CityJSON context must not be null."); + Objects.requireNonNull(context, "The CityGML adapter context must not be null."); + + CityJSONContext cityJSONContext; + try { + cityJSONContext = CityJSONContext.newInstance(context.getClass().getClassLoader()); + } catch (CityJSONContextException e) { + throw new WriteException("Failed to create CityJSON context.", e); + } CityJSONFormatOptions formatOptions; try { @@ -69,7 +76,7 @@ public CityJSONWriter(OutputFile file, WriteOptions options, CityGMLAdapterConte } service = ExecutorHelper.newFixedAndBlockingThreadPool(1, 100); - helpers = ThreadLocal.withInitial(() -> new ModelSerializerHelper(this, store, adapterContext) + helpers = ThreadLocal.withInitial(() -> new ModelSerializerHelper(this, store, context) .initialize(options, formatOptions)); countLatch = new CountLatch(); diff --git a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/ModelSerializerHelper.java b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/ModelSerializerHelper.java index 2a4efb1d..d9006bca 100644 --- a/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/ModelSerializerHelper.java +++ b/citydb-io-citygml/src/main/java/org/citydb/io/citygml/writer/ModelSerializerHelper.java @@ -36,6 +36,7 @@ import org.citygml4j.core.model.CityGMLVersion; import org.citygml4j.core.model.core.*; import org.citygml4j.core.model.core.AddressProperty; +import org.citygml4j.xml.CityGMLContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; @@ -77,6 +78,7 @@ public class ModelSerializerHelper { private boolean mapLod0RoofEdge; private boolean mapLod1MultiSurfaces; private AddressMode addressMode; + private XMLObjects xmlObjects; ModelSerializerHelper(GlobalFeatureWriter writer, PersistentMapStore store, CityGMLAdapterContext context) { this.writer = Objects.requireNonNull(writer, "The global feature writer must not be null."); @@ -88,7 +90,7 @@ public class ModelSerializerHelper { preprocessor = new Preprocessor(); } - ModelSerializerHelper initialize(WriteOptions options, CityGMLFormatOptions formatOptions) { + ModelSerializerHelper initialize(WriteOptions options, CityGMLFormatOptions formatOptions, CityGMLContext context) { failFast = options.isFailFast(); version = formatOptions.getVersion(); versionHelper = CityGMLVersionHelper.of(version); @@ -97,6 +99,7 @@ ModelSerializerHelper initialize(WriteOptions options, CityGMLFormatOptions form mapLod1MultiSurfaces = version == CityGMLVersion.v3_0 && formatOptions.isMapLod1MultiSurfaces(); preprocessor.checkForDeprecatedLod4Geometry(version == CityGMLVersion.v3_0 && formatOptions.isUseLod4AsLod3()); addressMode = formatOptions.getAddressMode(); + xmlObjects = context != null ? context.getXMLObjects() : null; return this; } @@ -106,7 +109,7 @@ ModelSerializerHelper initialize(WriteOptions options, CityJSONFormatOptions for .setMapLod0RoofEdge(true) .setMapLod1MultiSurfaces(true) .setUseLod4AsLod3(formatOptions.isUseLod4AsLod3()) - .setAddressMode(AddressMode.COLUMNS_FIRST)); + .setAddressMode(AddressMode.COLUMNS_FIRST), null); } public CityGMLAdapterContext getContext() { @@ -551,8 +554,7 @@ private R buildObject(T source, R target, ModelSerializer T fromXML(String source, Class type) { - if (source != null) { - XMLObjects xmlObjects = context.getCityGMLContext().getXMLObjects(); + if (xmlObjects != null && source != null) { try (XMLReader reader = getOrCreateXMLReaderFactory(xmlObjects) .createReader(new StringReader(source))) { return xmlObjects.fromXML(reader, type); diff --git a/citydb-model/src/main/java/org/citydb/model/common/Shareable.java b/citydb-model/src/main/java/org/citydb/model/common/Shareable.java new file mode 100644 index 00000000..bd195db9 --- /dev/null +++ b/citydb-model/src/main/java/org/citydb/model/common/Shareable.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright virtualcitysystems GmbH + */ + +package org.citydb.model.common; + +import java.util.Optional; + +public abstract class Shareable extends Child { + + @Override + public Optional getParent() { + return Optional.empty(); + } + + @Override + void setParent(Child parent) { + // do nothing + } +} diff --git a/citydb-model/src/main/java/org/citydb/model/geometry/ImplicitGeometry.java b/citydb-model/src/main/java/org/citydb/model/geometry/ImplicitGeometry.java index c1a47773..2e770074 100644 --- a/citydb-model/src/main/java/org/citydb/model/geometry/ImplicitGeometry.java +++ b/citydb-model/src/main/java/org/citydb/model/geometry/ImplicitGeometry.java @@ -15,7 +15,7 @@ import java.util.Objects; import java.util.Optional; -public class ImplicitGeometry extends Child implements Referencable, Visitable { +public class ImplicitGeometry extends Shareable implements Referencable, Visitable { private Geometry geometry; private ExternalFile libraryObject; private String objectId; diff --git a/citydb-model/src/main/java/org/citydb/model/property/ImplicitGeometryProperty.java b/citydb-model/src/main/java/org/citydb/model/property/ImplicitGeometryProperty.java index 725df74b..8a66b065 100644 --- a/citydb-model/src/main/java/org/citydb/model/property/ImplicitGeometryProperty.java +++ b/citydb-model/src/main/java/org/citydb/model/property/ImplicitGeometryProperty.java @@ -27,7 +27,7 @@ public class ImplicitGeometryProperty extends Property private ImplicitGeometryProperty(Name name, ImplicitGeometry implicitGeometry) { super(name, DataType.IMPLICIT_GEOMETRY_PROPERTY); Objects.requireNonNull(implicitGeometry, "The implicit geometry must not be null."); - this.implicitGeometry = asChild(implicitGeometry); + this.implicitGeometry = implicitGeometry; } private ImplicitGeometryProperty(Name name, String reference) { @@ -57,7 +57,7 @@ public Optional getObject() { @Override public ImplicitGeometryProperty setObject(ImplicitGeometry implicitGeometry) { if (implicitGeometry != null) { - this.implicitGeometry = asChild(implicitGeometry); + this.implicitGeometry = implicitGeometry; reference = null; }