All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.citygml4j.cityjson.writer.CityJSONSerializerHelper Maven / Gradle / Ivy

There is a newer version: 3.2.4
Show newest version
/*
 * citygml4j - The Open Source Java API for CityGML
 * https://github.com/citygml4j
 *
 * Copyright 2013-2025 Claus Nagel 
 *
 * 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.citygml4j.cityjson.writer;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.citygml4j.cityjson.CityJSONContext;
import org.citygml4j.cityjson.adapter.Fields;
import org.citygml4j.cityjson.adapter.appearance.serializer.AppearanceSerializer;
import org.citygml4j.cityjson.adapter.core.AbstractSpaceAdapter;
import org.citygml4j.cityjson.adapter.geometry.serializer.GeometrySerializer;
import org.citygml4j.cityjson.adapter.geometry.serializer.SpaceGeometryBuilder;
import org.citygml4j.cityjson.model.CityJSONType;
import org.citygml4j.cityjson.model.CityJSONVersion;
import org.citygml4j.cityjson.model.generics.GenericAttributeTypes;
import org.citygml4j.cityjson.model.geometry.GeometryType;
import org.citygml4j.cityjson.model.geometry.Vertex;
import org.citygml4j.cityjson.model.metadata.Metadata;
import org.citygml4j.cityjson.serializer.CityJSONSerializeException;
import org.citygml4j.cityjson.serializer.JsonObjectSerializer;
import org.citygml4j.cityjson.util.ArrayBuffer;
import org.citygml4j.core.model.ade.ADEProperty;
import org.citygml4j.core.model.common.GeometryInfo;
import org.citygml4j.core.model.core.*;
import org.citygml4j.core.visitor.ObjectWalker;
import org.xmlobjects.gml.model.base.AbstractGML;
import org.xmlobjects.gml.model.basictypes.Code;
import org.xmlobjects.gml.model.feature.FeatureProperty;
import org.xmlobjects.gml.model.geometry.AbstractGeometry;
import org.xmlobjects.gml.model.geometry.GeometryProperty;
import org.xmlobjects.gml.util.id.DefaultIdCreator;
import org.xmlobjects.gml.util.id.IdCreator;
import org.xmlobjects.model.Child;
import org.xmlobjects.util.Properties;

import java.util.*;

public class CityJSONSerializerHelper {
    public static final String SEMANTIC_SURFACE = "org.citygml4j.cityjson.semanticSurface";
    public static final String TEMPORARY_OBJECT = "org.citygml4j.tempObject";

    private final AbstractCityJSONWriter writer;
    private final CityJSONType type;
    private final CityJSONVersion version;
    private final ObjectMapper objectMapper;
    private final CityJSONContext context;
    private final GeometrySerializer geometrySerializer;
    private final AppearanceSerializer appearanceSerializer;
    private final Map, JsonObjectSerializer> serializerCache = new IdentityHashMap<>();

    private IdCreator idCreator = DefaultIdCreator.getInstance();
    private boolean applyTransformation;
    private boolean computeCityModelExtent = true;
    private boolean writeGenericAttributeTypes;
    private Map extensions;
    private Metadata metadata;
    private ObjectNode extraRootProperties;
    private Properties properties;

    CityJSONSerializerHelper(AbstractCityJSONWriter writer, CityJSONVersion version, ObjectMapper objectMapper, CityJSONContext context) {
        this.writer = writer;
        this.version = version;
        this.objectMapper = objectMapper;
        this.context = context;

        type = writer instanceof CityJSONFeatureWriter ? CityJSONType.CITYJSON_FEATURE : CityJSONType.CITYJSON;
        appearanceSerializer = new AppearanceSerializer(this);
        geometrySerializer = new GeometrySerializer(appearanceSerializer, this);
    }

    public CityJSONType getType() {
        return type;
    }

    public CityJSONVersion getVersion() {
        return version;
    }

    public CityJSONContext getContext() {
        return context;
    }

    GeometrySerializer getGeometrySerializer() {
        return geometrySerializer;
    }

    AppearanceSerializer getAppearanceSerializer() {
        return appearanceSerializer;
    }

    public IdCreator getIdCreator() {
        return idCreator;
    }

    void setIdCreator(IdCreator idCreator) {
        this.idCreator = Objects.requireNonNull(idCreator, "The ID creator must not be null.");
    }

    boolean isApplyTransformation() {
        return applyTransformation;
    }

    void setApplyTransformation(boolean applyTransformation) {
        this.applyTransformation = applyTransformation;
    }

    boolean isComputeCityModelExtent() {
        return computeCityModelExtent;
    }

    void setComputeCityModelExtent(boolean computeCityModelExtent) {
        this.computeCityModelExtent = computeCityModelExtent;
    }

    public boolean isWriteGenericAttributeTypes() {
        return writeGenericAttributeTypes;
    }

    void setWriteGenericAttributeTypes(boolean writeGenericAttributeTypes) {
        this.writeGenericAttributeTypes = writeGenericAttributeTypes;
        if (writeGenericAttributeTypes) {
            properties.set(GenericAttributeTypes.class.getName(), new GenericAttributeTypes());
        } else {
            properties.remove(GenericAttributeTypes.class.getName());
        }
    }

    boolean hasExtensions() {
        return extensions != null && !extensions.isEmpty();
    }

    public boolean isSetExternalExtension(String name) {
        return extensions != null && extensions.containsKey(name);
    }

    Map getExternalExtensions() {
        return extensions != null ? extensions : Collections.emptyMap();
    }

    public void addExternalExtension(String name, String url, String version) {
        if (extensions == null) {
            extensions = new HashMap<>();
        }

        ObjectNode node = createObject();
        node.put(Fields.URL, url);
        node.put(Fields.VERSION, version);

        extensions.put(name, node);
    }

    boolean hasMetadata() {
        return metadata != null;
    }

    public Metadata getMetadata() {
        if (metadata == null) {
            metadata = new Metadata();
        }

        return metadata;
    }

    void setMetadata(Metadata metadata) {
        this.metadata = Objects.requireNonNull(metadata, "The metadata must not be null.");
    }

    boolean hasExtraRootProperties() {
        return extraRootProperties != null && !extraRootProperties.isEmpty();
    }

    ObjectNode getExtraRootProperties() {
        if (extraRootProperties == null) {
            extraRootProperties = createObject();
        }

        return extraRootProperties;
    }

    public Properties getProperties() {
        if (properties == null) {
            properties = new Properties();
        }

        return properties;
    }

    void setProperties(Properties properties) {
        this.properties = properties;
    }

    public String getOrCreateId(AbstractGML object) {
        if (object.getId() == null) {
            object.setId(idCreator.createId());
        }

        return object.getId();
    }

    public ObjectNode createObject() {
        return objectMapper.createObjectNode();
    }

    public ObjectNode getOrPutObject(String propertyName, ObjectNode node) {
        JsonNode candidate = node.path(propertyName);
        return candidate.isObject() ?
                (ObjectNode) candidate :
                node.putObject(propertyName);
    }

    public ArrayNode createArray() {
        return objectMapper.createArrayNode();
    }

    public ArrayNode getOrPutArray(String propertyName, ObjectNode node) {
        JsonNode candidate = node.path(propertyName);
        return candidate.isArray() ?
                (ArrayNode) candidate :
                node.putArray(propertyName);
    }

    public void removeIfEmpty(String propertyName, ObjectNode node) {
        if (node.path(propertyName).isEmpty()) {
            node.remove(propertyName);
        }
    }

    public void addGeometry(GeometryProperty property, Number lod, ObjectNode object) {
        addGeometry(property, lod, object, EnumSet.allOf(GeometryType.class));
    }

    public void addGeometry(AbstractGeometry geometry, Number lod, ObjectNode object) {
        addGeometry(geometry, lod, object, EnumSet.allOf(GeometryType.class));
    }

    public void addGeometry(GeometryProperty property, Number lod, ObjectNode object, EnumSet allowedTypes) {
        if (property != null) {
            addGeometry(property.getObject(), lod, object, allowedTypes);
        }
    }

    public void addGeometry(AbstractGeometry geometry, Number lod, ObjectNode object, EnumSet allowedTypes) {
        geometrySerializer.addGeometry(geometry, lod, object, allowedTypes);
    }

    public ObjectNode getGeometry(GeometryProperty property, Number lod) {
        return getGeometry(property, lod, EnumSet.allOf(GeometryType.class));
    }

    public ObjectNode getGeometry(AbstractGeometry geometry, Number lod) {
        return getGeometry(geometry, lod, EnumSet.allOf(GeometryType.class));
    }

    public ObjectNode getGeometry(GeometryProperty property, Number lod, EnumSet allowedTypes) {
        return property != null ?
                getGeometry(property.getObject(), lod, allowedTypes) :
                null;
    }

    public ObjectNode getGeometry(AbstractGeometry geometry, Number lod, EnumSet allowedTypes) {
        return geometrySerializer.getGeometry(geometry, lod, allowedTypes);
    }

    public void addTemplateGeometry(ImplicitGeometryProperty property, Number lod, ObjectNode object, EnumSet allowedTypes) {
        if (property != null) {
            addTemplateGeometry(property.getObject(), lod, object, allowedTypes);
        }
    }

    public void addTemplateGeometry(ImplicitGeometry geometry, Number lod, ObjectNode object, EnumSet allowedTypes) {
        geometrySerializer.addTemplateGeometry(geometry, lod, object, allowedTypes);
    }

    public void addGeometries(AbstractSpace space, ObjectNode object, EnumSet allowedTypes) {
        GeometryInfo geometryInfo = space.getGeometryInfo();
        for (int lod = 0; lod < 4; lod++) {
            for (GeometryProperty property : geometryInfo.getGeometries(lod)) {
                addGeometry(property, lod, object, allowedTypes);
            }
        }

        if (!geometryInfo.hasGeometries(3)) {
            for (GeometryProperty property : geometryInfo.getGeometries(4)) {
                addGeometry(property, 3, object, allowedTypes);
            }
        }

        if (allowedTypes.contains(GeometryType.TEMPLATE_GEOMETRY)) {
            for (int lod = 0; lod < 4; lod++) {
                for (ImplicitGeometryProperty property : geometryInfo.getImplicitGeometries(lod)) {
                    addTemplateGeometry(property, lod, object, allowedTypes);
                }
            }

            if (!geometryInfo.hasImplicitGeometries(3)) {
                for (ImplicitGeometryProperty property : geometryInfo.getImplicitGeometries(4)) {
                    addTemplateGeometry(property, 3, object, allowedTypes);
                }
            }
        }
    }

    public void addGeometries(AbstractThematicSurface boundary, ObjectNode object, EnumSet allowedTypes) {
        GeometryInfo geometryInfo = boundary.getGeometryInfo();
        for (int lod = 0; lod < 4; lod++) {
            for (GeometryProperty property : geometryInfo.getGeometries(lod)) {
                addGeometry(property, lod, object, allowedTypes);
            }
        }

        if (!geometryInfo.hasGeometries(3)) {
            for (GeometryProperty property : geometryInfo.getGeometries(4)) {
                addGeometry(property, 3, object, allowedTypes);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public  void addADEProperty(T property, ObjectNode node) throws CityJSONSerializeException, CityJSONWriteException {
        if (property != null) {
            JsonObjectSerializer serializer = (JsonObjectSerializer) context.getSerializer(property.getClass(), version);
            if (serializer != null) {
                getObjectUsingSerializer(property, node, serializer);
            }
        }
    }

    public void addExtraRootProperty(ADEOfCityModel property) throws CityJSONSerializeException, CityJSONWriteException {
        addADEProperty(property, getExtraRootProperties());
    }

    public void addExtraRootProperty(String propertyName, JsonNode value) {
        getExtraRootProperties().set(propertyName, value);
    }

    public void writeStandardObjectClassifier(StandardObjectClassifier object, ObjectNode node) {
        if (object.getClassifier() != null) {
            node.put("class", object.getClassifier().getValue());
        }

        if (object.isSetFunctions()) {
            for (Code function : object.getFunctions()) {
                if (function.getValue() != null) {
                    node.put("function", function.getValue());
                    break;
                }
            }
        }

        if (object.isSetUsages()) {
            for (Code usage : object.getUsages()) {
                if (usage.getValue() != null) {
                    node.put("usage", usage.getValue());
                    break;
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public  ObjectNode getObject(T object) throws CityJSONSerializeException, CityJSONWriteException {
        if (object != null) {
            JsonObjectSerializer serializer = (JsonObjectSerializer) context.getSerializer(object.getClass(), version);
            if (serializer != null) {
                return getObjectUsingSerializer(object, serializer);
            }
        }

        return null;
    }

    public  ObjectNode getObjectUsingSerializer(T object, Class> type) throws CityJSONSerializeException, CityJSONWriteException {
        return getObjectUsingSerializer(object, getOrCreateSerializer(type));
    }

    public  ObjectNode getObjectUsingSerializer(T object, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        return getObjectUsingSerializer(object, objectMapper.createObjectNode(), serializer);
    }

    private  ObjectNode getObjectUsingSerializer(T object, ObjectNode node, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        if (object != null) {
            String type = serializer.createType(object, version);
            if (type != null) {
                node.put(Fields.TYPE, serializer.createType(object, version));
            }

            serializer.writeObject(object, node, this);
            return node;
        } else {
            return null;
        }
    }

    public  JsonObjectSerializer getOrCreateSerializer(Class> type) throws CityJSONSerializeException {
        JsonObjectSerializer cachedSerializer = serializerCache.get(type);
        if (cachedSerializer != null && type.isAssignableFrom(cachedSerializer.getClass())) {
            return type.cast(cachedSerializer);
        } else {
            try {
                JsonObjectSerializer serializer = type.getDeclaredConstructor().newInstance();
                serializerCache.put(type, serializer);
                return serializer;
            } catch (Exception e) {
                throw new CityJSONSerializeException("The serializer " + type.getName() + " lacks a default constructor.");
            }
        }
    }

    public  void writeCityObject(FeatureProperty property) throws CityJSONSerializeException, CityJSONWriteException {
        if (property != null) {
            writeCityObject(property.getObject());
        }
    }

    @SuppressWarnings("unchecked")
    public  void writeCityObject(T object) throws CityJSONSerializeException, CityJSONWriteException {
        if (object != null) {
            JsonObjectSerializer serializer = (JsonObjectSerializer) context.getSerializer(object.getClass(), version);
            if (serializer != null) {
                writeCityObjectUsingSerializer(object, serializer);
            }
        }
    }

    public  void writeCityObjectUsingSerializer(FeatureProperty property, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        if (property != null) {
            writeCityObjectUsingSerializer(property.getObject(), serializer);
        }
    }

    public  void writeCityObjectUsingSerializer(T object, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        if (object != null) {
            writer.beginTopLevelObject();

            String id = getOrCreateId(object);
            ObjectNode node = getCityObjectUsingSerializer(object, serializer);

            if (!node.path(Fields.TYPE).isNull()) {
                writer.writeCityObject(id, removeEmptyProperties(node));
            }
        }
    }

    public  void writeChildObject(FeatureProperty property, AbstractFeature parent, ObjectNode parentNode) throws CityJSONSerializeException, CityJSONWriteException {
        if (property != null) {
            writeChildObject(property.getObject(), parent, parentNode);
        }
    }

    @SuppressWarnings("unchecked")
    public  void writeChildObject(T child, AbstractFeature parent, ObjectNode parentNode) throws CityJSONSerializeException, CityJSONWriteException {
        if (child != null) {
            JsonObjectSerializer serializer = (JsonObjectSerializer) context.getSerializer(child.getClass(), version);
            if (serializer != null) {
                writeChildObjectUsingSerializer(child, parent, parentNode, serializer);
            }
        }
    }

    public  void writeChildObjectUsingSerializer(FeatureProperty property, AbstractFeature parent, ObjectNode parentNode, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        if (property != null) {
            writeChildObjectUsingSerializer(property.getObject(), parent, parentNode, serializer);
        }
    }

    public  void writeChildObjectUsingSerializer(T child, AbstractFeature parent, ObjectNode parentNode, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        if (child != null && parent != null && parentNode != null) {
            String childId = getOrCreateId(child);
            String parentId = getOrCreateId(parent);

            ObjectNode childNode = getCityObjectUsingSerializer(child, serializer);
            if (!childNode.path(Fields.TYPE).isNull()) {
                getOrPutArray(Fields.PARENTS, childNode).add(parentId);
                getOrPutArray(Fields.CHILDREN, parentNode).add(childId);
                writer.writeChildObject(childId, removeEmptyProperties(childNode));
            }
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private  ObjectNode getCityObjectUsingSerializer(T object, JsonObjectSerializer serializer) throws CityJSONSerializeException, CityJSONWriteException {
        ObjectNode node = objectMapper.createObjectNode();
        node.set(Fields.TYPE, NullNode.getInstance());
        node.set(Fields.GEOGRAPHICAL_EXTENT, createArray());
        node.set(Fields.ATTRIBUTES, createObject());
        node.set(Fields.PARENTS, createArray());
        node.set(Fields.CHILDREN, createArray());
        node.set(Fields.GEOMETRY, createArray());

        SpaceGeometryBuilder builder = null;
        if (object instanceof AbstractSpace space) {
            builder = SpaceGeometryBuilder.newInstance();

            if (serializer instanceof AbstractSpaceAdapter spaceAdapter) {
                builder.withMultiSurfaceProviders(spaceAdapter.getMultiSurfaceProviders(space))
                        .withMultiCurveProviders(spaceAdapter.getMultiCurveProviders(space));
            }

            builder.addUnreferencedBoundaryGeometries(space);
        }

        // populate city object
        getObjectUsingSerializer(object, node, serializer);

        if (builder != null) {
            builder.removeTemporaryInformation((AbstractSpace) object);
        }

        return node;
    }

    private ObjectNode removeEmptyProperties(ObjectNode node) {
        removeIfEmpty(Fields.GEOGRAPHICAL_EXTENT, node);
        removeIfEmpty(Fields.ATTRIBUTES, node);
        removeIfEmpty(Fields.PARENTS, node);
        removeIfEmpty(Fields.CHILDREN, node);

        if (version != CityJSONVersion.v1_0) {
            removeIfEmpty(Fields.GEOMETRY, node);
        }

        return node;
    }

    List computeExtent(ArrayBuffer vertices) {
        Double[] extent = new Double[]{
                Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,
                -Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE};

        for (Vertex vertex : vertices) {
            double x = vertex.getX();
            double y = vertex.getY();
            double z = vertex.getZ();

            if (x < extent[0]) extent[0] = x;
            if (y < extent[1]) extent[1] = y;
            if (z < extent[2]) extent[2] = z;
            if (x > extent[3]) extent[3] = x;
            if (y > extent[4]) extent[4] = y;
            if (z > extent[5]) extent[5] = z;
        }

        return Arrays.asList(extent);
    }

    String getReferenceSystem(AbstractFeature feature) {
        Child parent = feature;
        do {
            if (parent instanceof AbstractFeature tmp) {
                if (tmp.getBoundedBy() != null
                        && tmp.getBoundedBy().getEnvelope() != null
                        && tmp.getBoundedBy().getEnvelope().getSrsName() != null) {
                    return tmp.getBoundedBy().getEnvelope().getSrsName();
                }
            }
        } while ((parent = parent.getParent()) != null);

        String[] reference = new String[1];
        feature.accept(new ObjectWalker() {
            @Override
            public void visit(AbstractFeature feature) {
                if (feature.getBoundedBy() != null
                        && feature.getBoundedBy().getEnvelope() != null
                        && feature.getBoundedBy().getEnvelope().getSrsName() != null) {
                    reference[0] = feature.getBoundedBy().getEnvelope().getSrsName();
                    shouldWalk = false;
                    return;
                }

                super.visit(feature);
            }
        });

        return reference[0];
    }

    void reset() {
        reset(false);
    }

    void reset(boolean keepTemplates) {
        serializerCache.clear();
        geometrySerializer.reset(keepTemplates);
        appearanceSerializer.reset();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy