From 552e7f4c3c095cdd7cc0f758eb402b5e0fe2cc8f Mon Sep 17 00:00:00 2001 From: Claus Nagel Date: Thu, 2 Jan 2025 19:48:32 +0100 Subject: [PATCH] added affine transformation options to import and export --- .../model/encoding/Matrix3x4Reader.java | 50 +++++++++++++++++++ .../model/encoding/Matrix3x4Writer.java | 48 ++++++++++++++++++ .../model/geometry/ImplicitGeometry.java | 5 +- .../citydb/model/util/AffineTransformer.java | 19 ++++--- .../operation/exporter/ExportOptions.java | 14 ++++++ .../exporter/util/Postprocessor.java | 12 +++++ .../operation/importer/ImportHelper.java | 7 +++ .../operation/importer/ImportOptions.java | 16 ++++++ 8 files changed, 159 insertions(+), 12 deletions(-) create mode 100644 citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Reader.java create mode 100644 citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Writer.java diff --git a/citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Reader.java b/citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Reader.java new file mode 100644 index 00000000..a8fbb2b5 --- /dev/null +++ b/citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Reader.java @@ -0,0 +1,50 @@ +/* + * citydb-tool - Command-line tool for the 3D City Database + * https://www.3dcitydb.org/ + * + * Copyright 2022-2025 + * virtualcitysystems GmbH, Germany + * https://vc.systems/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.citydb.model.encoding; + +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.reader.ObjectReader; +import org.citydb.model.common.Matrix3x4; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class Matrix3x4Reader implements ObjectReader { + @Override + public Matrix3x4 readObject(JSONReader jsonReader, Type type, Object o, long l) { + if (jsonReader.isArray()) { + List values = new ArrayList<>(); + for (Object value : jsonReader.readArray()) { + if (value instanceof Number number) { + values.add(number.doubleValue()); + } + } + + if (values.size() > 11) { + return Matrix3x4.ofRowMajor(values); + } + } + + return null; + } +} diff --git a/citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Writer.java b/citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Writer.java new file mode 100644 index 00000000..f600db8c --- /dev/null +++ b/citydb-model/src/main/java/org/citydb/model/encoding/Matrix3x4Writer.java @@ -0,0 +1,48 @@ +/* + * citydb-tool - Command-line tool for the 3D City Database + * https://www.3dcitydb.org/ + * + * Copyright 2022-2025 + * virtualcitysystems GmbH, Germany + * https://vc.systems/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.citydb.model.encoding; + +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.writer.ObjectWriter; +import org.citydb.model.common.Matrix3x4; + +import java.lang.reflect.Type; +import java.util.List; + +public class Matrix3x4Writer implements ObjectWriter { + @Override + public void write(JSONWriter jsonWriter, Object o, Object o1, Type type, long l) { + if (o instanceof Matrix3x4 matrix) { + jsonWriter.startArray(); + List values = matrix.toRowMajor(); + for (int i = 0; i < values.size(); i++) { + if (i != 0) { + jsonWriter.writeComma(); + } + jsonWriter.writeDouble(values.get(i)); + } + jsonWriter.endArray(); + } else { + jsonWriter.writeNull(); + } + } +} 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 66e3ff43..85a3efce 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 @@ -122,11 +122,12 @@ public Envelope getEnvelope(Matrix4x4 transformationMatrix, Point referencePoint if (transformationMatrix != null && referencePoint != null) { Envelope envelope; if (geometry != null) { - envelope = AffineTransformer.of(transformationMatrix.plus(new Matrix(4, 4) + envelope = geometry.getEnvelope(); + AffineTransformer.of(transformationMatrix.plus(new Matrix(4, 4) .set(0, 3, referencePoint.getCoordinate().getX()) .set(1, 3, referencePoint.getCoordinate().getY()) .set(2, 3, referencePoint.getCoordinate().getZ()))) - .transform(geometry.getEnvelope()); + .transform(envelope); } else { envelope = Envelope.empty().include(Point.of(Coordinate.of( referencePoint.getCoordinate().getX() + transformationMatrix.get(0, 3), diff --git a/citydb-model/src/main/java/org/citydb/model/util/AffineTransformer.java b/citydb-model/src/main/java/org/citydb/model/util/AffineTransformer.java index 34a250c9..f2c637d1 100644 --- a/citydb-model/src/main/java/org/citydb/model/util/AffineTransformer.java +++ b/citydb-model/src/main/java/org/citydb/model/util/AffineTransformer.java @@ -26,6 +26,7 @@ import org.citydb.model.common.Matrix2x2; import org.citydb.model.common.Matrix3x4; import org.citydb.model.common.Matrix4x4; +import org.citydb.model.common.Visitable; import org.citydb.model.feature.Feature; import org.citydb.model.geometry.*; import org.citydb.model.property.AppearanceProperty; @@ -61,7 +62,7 @@ public static AffineTransformer ofRowMajor(List values, int rows) { return of(new Matrix(values, rows)); } - public Coordinate transform(Coordinate coordinate) { + public void transform(Coordinate coordinate) { Matrix transformed = matrix.times(new Matrix(new double[][]{ {coordinate.getX()}, {coordinate.getY()}, @@ -74,27 +75,25 @@ public Coordinate transform(Coordinate coordinate) { if (coordinate.getDimension() == 3) { coordinate.setZ(transformed.get(2, 0)); } - - return coordinate; } - public Feature transform(Feature feature) { + public void transform(Feature feature) { feature.accept(processor); - return feature; } - public Geometry transform(Geometry geometry) { + public void transform(Geometry geometry) { geometry.accept(processor); - return geometry; } - public Envelope transform(Envelope envelope) { + public void transform(Visitable visitable) { + visitable.accept(processor); + } + + public void transform(Envelope envelope) { if (!envelope.isEmpty()) { transform(envelope.getLowerCorner()); transform(envelope.getUpperCorner()); } - - return envelope; } private class Processor extends ModelWalker { diff --git a/citydb-operation/src/main/java/org/citydb/operation/exporter/ExportOptions.java b/citydb-operation/src/main/java/org/citydb/operation/exporter/ExportOptions.java index 48b7d89d..c8fcab32 100644 --- a/citydb-operation/src/main/java/org/citydb/operation/exporter/ExportOptions.java +++ b/citydb-operation/src/main/java/org/citydb/operation/exporter/ExportOptions.java @@ -27,6 +27,9 @@ import org.citydb.core.concurrent.LazyCheckedInitializer; import org.citydb.core.file.OutputFile; import org.citydb.core.file.output.RegularOutputFile; +import org.citydb.model.common.Matrix3x4; +import org.citydb.model.encoding.Matrix3x4Reader; +import org.citydb.model.encoding.Matrix3x4Writer; import org.citydb.operation.exporter.options.AppearanceOptions; import org.citydb.operation.exporter.options.LodOptions; @@ -47,6 +50,8 @@ public class ExportOptions { private SrsReference targetSrs; private LodOptions lodOptions; private AppearanceOptions appearanceOptions; + @JSONField(serializeUsing = Matrix3x4Writer.class, deserializeUsing = Matrix3x4Reader.class) + private Matrix3x4 affineTransform; public OutputFile getOutputFile() { if (outputFile == null) { @@ -105,4 +110,13 @@ public ExportOptions setAppearanceOptions(AppearanceOptions appearanceOptions) { this.appearanceOptions = appearanceOptions; return this; } + + public Optional getAffineTransform() { + return Optional.ofNullable(affineTransform); + } + + public ExportOptions setAffineTransform(Matrix3x4 affineTransform) { + this.affineTransform = affineTransform; + return this; + } } diff --git a/citydb-operation/src/main/java/org/citydb/operation/exporter/util/Postprocessor.java b/citydb-operation/src/main/java/org/citydb/operation/exporter/util/Postprocessor.java index 6d349290..9f81d411 100644 --- a/citydb-operation/src/main/java/org/citydb/operation/exporter/util/Postprocessor.java +++ b/citydb-operation/src/main/java/org/citydb/operation/exporter/util/Postprocessor.java @@ -31,6 +31,7 @@ import org.citydb.model.property.FeatureProperty; import org.citydb.model.property.ImplicitGeometryProperty; import org.citydb.model.property.Property; +import org.citydb.model.util.AffineTransformer; import org.citydb.model.walker.ModelWalker; import org.citydb.operation.exporter.ExportHelper; @@ -43,6 +44,7 @@ public class Postprocessor { private final ExportHelper helper; private final EnvelopeHelper envelopeHelper; private final AppearanceHelper appearanceHelper; + private final AffineTransformer transformer; private final Comparator> comparator = Comparator.comparingLong( property -> property.getDescriptor() .map(DatabaseDescriptor::getId) @@ -52,6 +54,7 @@ public Postprocessor(ExportHelper helper) { this.helper = helper; appearanceHelper = new AppearanceHelper(helper); envelopeHelper = new EnvelopeHelper(helper); + transformer = helper.getOptions().getAffineTransform().map(AffineTransformer::of).orElse(null); } public void process(Feature feature) { @@ -75,11 +78,20 @@ public void process(Feature feature) { envelopeHelper.updateEnvelope(feature); } + if (transformer != null) { + transformer.transform(feature); + } + sortAttributes(feature); } public void process(Visitable visitable) { appearanceHelper.assignSurfaceData(visitable, helper.getSurfaceDataMapper()); + + if (transformer != null) { + transformer.transform(visitable); + } + sortAttributes(visitable); } diff --git a/citydb-operation/src/main/java/org/citydb/operation/importer/ImportHelper.java b/citydb-operation/src/main/java/org/citydb/operation/importer/ImportHelper.java index ce18b58d..df7562e1 100644 --- a/citydb-operation/src/main/java/org/citydb/operation/importer/ImportHelper.java +++ b/citydb-operation/src/main/java/org/citydb/operation/importer/ImportHelper.java @@ -29,6 +29,7 @@ import org.citydb.model.common.Visitable; import org.citydb.model.feature.Feature; import org.citydb.model.feature.FeatureDescriptor; +import org.citydb.model.util.AffineTransformer; import org.citydb.operation.importer.common.DatabaseImporter; import org.citydb.operation.importer.feature.FeatureImporter; import org.citydb.operation.importer.reference.CacheType; @@ -51,6 +52,7 @@ public class ImportHelper { private final SchemaMapping schemaMapping; private final TableHelper tableHelper; private final SequenceHelper sequenceHelper; + private final AffineTransformer transformer; private final Map caches = new EnumMap<>(CacheType.class); private final List logEntries = new ArrayList<>(); private final Importer.TransactionMode transactionMode; @@ -70,6 +72,7 @@ public class ImportHelper { schemaMapping = adapter.getSchemaAdapter().getSchemaMapping(); tableHelper = new TableHelper(this); sequenceHelper = new SequenceHelper(this); + transformer = options.getAffineTransform().map(AffineTransformer::of).orElse(null); batchSize = options.getBatchSize() > 0 ? Math.min(options.getBatchSize(), adapter.getSchemaAdapter().getMaximumBatchSize()) : ImportOptions.DEFAULT_BATCH_SIZE; @@ -110,6 +113,10 @@ public FileLocator getFileLocator(ExternalFile file) { FeatureDescriptor importFeature(Feature feature) throws ImportException { try { + if (transformer != null) { + transformer.transform(feature); + } + generateSequenceValues(feature); FeatureDescriptor descriptor = tableHelper.getOrCreateImporter(FeatureImporter.class).doImport(feature); diff --git a/citydb-operation/src/main/java/org/citydb/operation/importer/ImportOptions.java b/citydb-operation/src/main/java/org/citydb/operation/importer/ImportOptions.java index 5472255b..bc5dd701 100644 --- a/citydb-operation/src/main/java/org/citydb/operation/importer/ImportOptions.java +++ b/citydb-operation/src/main/java/org/citydb/operation/importer/ImportOptions.java @@ -21,10 +21,15 @@ package org.citydb.operation.importer; +import com.alibaba.fastjson2.annotation.JSONField; import org.citydb.config.SerializableConfig; import org.citydb.core.CoreConstants; +import org.citydb.model.common.Matrix3x4; +import org.citydb.model.encoding.Matrix3x4Reader; +import org.citydb.model.encoding.Matrix3x4Writer; import java.nio.file.Path; +import java.util.Optional; @SerializableConfig(name = "importOptions") public class ImportOptions { @@ -33,6 +38,8 @@ public class ImportOptions { private String tempDirectory; private int numberOfThreads; private int batchSize = DEFAULT_BATCH_SIZE; + @JSONField(serializeUsing = Matrix3x4Writer.class, deserializeUsing = Matrix3x4Reader.class) + private Matrix3x4 affineTransform; public Path getTempDirectory() { return tempDirectory != null ? CoreConstants.WORKING_DIR.resolve(tempDirectory) : null; @@ -60,4 +67,13 @@ public ImportOptions setBatchSize(int batchSize) { this.batchSize = batchSize; return this; } + + public Optional getAffineTransform() { + return Optional.ofNullable(affineTransform); + } + + public ImportOptions setAffineTransform(Matrix3x4 affineTransform) { + this.affineTransform = affineTransform; + return this; + } }