nl.tue.bimserver.citygml.CityGmlSerializer.java Source code

Java tutorial

Introduction

Here is the source code for nl.tue.bimserver.citygml.CityGmlSerializer.java

Source

package nl.tue.bimserver.citygml;

/******************************************************************************
 * Copyright (C) 2012  Design Systems (www.ds.arch.tue.nl)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/

/* TODO: Units
 */

import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;

import javax.xml.bind.JAXBException;

import org.apache.commons.beanutils.PropertyUtils;
import org.bimserver.emf.IfcModelInterface;
import org.bimserver.models.ifc2x3tc1.IfcBoolean;
import org.bimserver.models.ifc2x3tc1.IfcBuilding;
import org.bimserver.models.ifc2x3tc1.IfcBuildingElement;
import org.bimserver.models.ifc2x3tc1.IfcBuildingStorey;
import org.bimserver.models.ifc2x3tc1.IfcCurtainWall;
import org.bimserver.models.ifc2x3tc1.IfcDoor;
import org.bimserver.models.ifc2x3tc1.IfcElement;
import org.bimserver.models.ifc2x3tc1.IfcObject;
import org.bimserver.models.ifc2x3tc1.IfcObjectDefinition;
import org.bimserver.models.ifc2x3tc1.IfcOpeningElement;
import org.bimserver.models.ifc2x3tc1.IfcPostalAddress;
import org.bimserver.models.ifc2x3tc1.IfcProduct;
import org.bimserver.models.ifc2x3tc1.IfcProject;
import org.bimserver.models.ifc2x3tc1.IfcProperty;
import org.bimserver.models.ifc2x3tc1.IfcPropertySet;
import org.bimserver.models.ifc2x3tc1.IfcPropertySetDefinition;
import org.bimserver.models.ifc2x3tc1.IfcPropertySingleValue;
import org.bimserver.models.ifc2x3tc1.IfcRelContainedInSpatialStructure;
import org.bimserver.models.ifc2x3tc1.IfcRelDecomposes;
import org.bimserver.models.ifc2x3tc1.IfcRelDefines;
import org.bimserver.models.ifc2x3tc1.IfcRelDefinesByProperties;
import org.bimserver.models.ifc2x3tc1.IfcRelFillsElement;
import org.bimserver.models.ifc2x3tc1.IfcRelVoidsElement;
import org.bimserver.models.ifc2x3tc1.IfcRoot;
import org.bimserver.models.ifc2x3tc1.IfcSite;
import org.bimserver.models.ifc2x3tc1.IfcSlab;
import org.bimserver.models.ifc2x3tc1.IfcSlabTypeEnum;
import org.bimserver.models.ifc2x3tc1.IfcSpace;
import org.bimserver.models.ifc2x3tc1.IfcValue;
import org.bimserver.models.ifc2x3tc1.IfcWall;
import org.bimserver.models.ifc2x3tc1.IfcWindow;
import org.bimserver.models.ifc2x3tc1.Tristate;
import org.bimserver.plugins.PluginException;
import org.bimserver.plugins.PluginManager;
import org.bimserver.plugins.ifcengine.IfcEngine;
import org.bimserver.plugins.ifcengine.IfcEngineException;
import org.bimserver.plugins.ifcengine.IfcEngineGeometry;
import org.bimserver.plugins.ifcengine.IfcEngineInstance;
import org.bimserver.plugins.ifcengine.IfcEngineInstanceVisualisationProperties;
import org.bimserver.plugins.ifcengine.IfcEngineModel;
import org.bimserver.plugins.serializers.EmfSerializer;
import org.bimserver.plugins.serializers.ProjectInfo;
import org.bimserver.plugins.serializers.SerializerException;
import org.citygml4j.CityGMLContext;
import org.citygml4j.builder.jaxb.JAXBBuilder;
import org.citygml4j.factory.CityGMLFactory;
import org.citygml4j.factory.GMLFactory;
import org.citygml4j.model.citygml.building.AbstractBoundarySurface;
import org.citygml4j.model.citygml.building.AbstractBuilding;
import org.citygml4j.model.citygml.building.Building;
import org.citygml4j.model.citygml.building.BuildingInstallation;
import org.citygml4j.model.citygml.building.BuildingPart;
import org.citygml4j.model.citygml.building.IntBuildingInstallation;
import org.citygml4j.model.citygml.building.Room;
import org.citygml4j.model.citygml.cityobjectgroup.CityObjectGroup;
import org.citygml4j.model.citygml.core.AbstractCityObject;
import org.citygml4j.model.citygml.core.Address;
import org.citygml4j.model.citygml.core.CityModel;
import org.citygml4j.model.gml.base.AbstractGML;
import org.citygml4j.model.gml.basicTypes.Code;
import org.citygml4j.model.gml.feature.AbstractFeature;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurface;
import org.citygml4j.model.gml.geometry.aggregates.MultiSurfaceProperty;
import org.citygml4j.model.gml.geometry.complexes.CompositeSurface;
import org.citygml4j.model.gml.geometry.primitives.DirectPositionList;
import org.citygml4j.model.gml.geometry.primitives.LinearRing;
import org.citygml4j.model.gml.geometry.primitives.Polygon;
import org.citygml4j.model.module.citygml.CityGMLVersion;
import org.citygml4j.xml.io.CityGMLOutputFactory;
import org.citygml4j.xml.io.reader.CityGMLReadException;
import org.citygml4j.xml.io.writer.CityGMLWriteException;
import org.citygml4j.xml.io.writer.CityGMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CityGmlSerializer extends EmfSerializer {
    private static final Logger LOGGER = LoggerFactory.getLogger(CityGmlSerializer.class);
    private GMLFactory gml;
    private CityGMLFactory citygml;
    private CityGMLContext ctx;
    private IfcEngineModel ifcEngineModel;
    private IfcEngineGeometry geometry;
    private MaterialManager materialManager;
    private ClassMap<Product2InstallationInfo> product2installation;

    @Override
    public void init(IfcModelInterface ifcModel, ProjectInfo projectInfo, PluginManager pluginManager,
            IfcEngine ifcEngine) throws SerializerException {
        super.init(ifcModel, projectInfo, pluginManager, ifcEngine);

        ctx = new CityGMLContext();
        citygml = new CityGMLFactory();
        gml = new GMLFactory();

        product2installation = new ClassMap<Product2InstallationInfo>();
        product2installation.add(org.bimserver.models.ifc2x3tc1.IfcColumn.class,
                new Product2InstallationInfo("Pset_ColumnCommon", "7020", "1050", false));
        product2installation.add(org.bimserver.models.ifc2x3tc1.IfcBeam.class,
                new Product2InstallationInfo("Pset_BeamCommon", "1070", "1070", false)); // TODO: Find good codes for beams
        product2installation.add(org.bimserver.models.ifc2x3tc1.IfcStair.class,
                new Product2InstallationInfo("Pset_StairCommon", "8020", "1060", false));
        product2installation.add(org.bimserver.models.ifc2x3tc1.IfcRailing.class,
                new Product2InstallationInfo("Pset_RailingCommon", "8010", "1070", false));
        product2installation.add(org.bimserver.models.ifc2x3tc1.IfcBuildingElementProxy.class,
                new Product2InstallationInfo(null, "1070", "1070", false)); // Export unknown ifc objects
        product2installation.add(org.bimserver.models.ifc2x3tc1.IfcTransportElement.class,
                new Product2InstallationInfo(null, "1070", "1070", false));

        EmfSerializer serializer = getPluginManager().requireIfcStepSerializer();
        serializer.init(ifcModel, getProjectInfo(), getPluginManager(), ifcEngine);
        try {
            ifcEngine.init();
            ifcEngineModel = ifcEngine.openModel(serializer.getBytes());
            ifcEngineModel.setPostProcessing(true);
            geometry = ifcEngineModel.finalizeModelling(ifcEngineModel.initializeModelling());
        } catch (PluginException e) {
            throw new SerializerException(e);
        }
    }

    private Code createCode(String value) {
        Code code = gml.createCode();
        code.setValue(value);
        return code;
    }

    //   private Code createCode(String value, String codeSpace) {
    //      Code code = gml.createCode();
    //      code.setValue(value);
    //      code.setCodeSpace(codeSpace);
    //      return code;
    //   }

    private List<Code> createCodeList(String... values) {
        List<Code> list = new LinkedList<Code>();
        for (String value : values) {
            list.add(createCode(value));
        }
        return list;
    }

    private void setName(List<Code> name, String value) {
        if (value != null && !value.trim().equals("")) {
            name.add(createCode(value));
        }
    }

    // TODO: Put in utility class (also used in material manager)
    private String hrefTo(AbstractGML abstractGML) {
        if (abstractGML.getId() == null) {
            abstractGML.setId(UUID.randomUUID().toString());
        }
        return "#" + abstractGML.getId();
    }

    /**
     * TODO: What does this do?
     * @param cityObject
     * @param ifcRoot
     */
    private void setGlobalId(AbstractCityObject cityObject, IfcRoot ifcRoot) {
        cityObject.setId(UUID.randomUUID().toString());
    }

    private void assignGuid(AbstractFeature cityObject, IfcRoot ifcRoot) {
        // TODO: Implement this

        // Use the ifc uuid's as gml:id's
        // Original the setGlobalId did something like this, maybe take a look at the
        // old implementation.
        // Or use the xbuilding ADE to assign the uuid's?
        // Should we decode the ifc uuid's to normal onces?

        if (ifcRoot != null && ifcRoot.getGlobalId() != null && ifcRoot.getGlobalId().getWrappedValue() != null) {
            String ifcUuid = ifcRoot.getGlobalId().getWrappedValue();
            System.out.println("UUID: " + ifcUuid);
        }
    }

    @Override
    protected void reset() {
        setMode(Mode.BODY);
    }

    @Override
    public boolean write(OutputStream out) throws SerializerException {
        if (getMode() == Mode.BODY) {
            try {
                CityModel cityModel = buildCityModel();
                writeCityGML(cityModel, out);

                return true;
            } catch (Exception e) {
                throw new SerializerException(e);
            } finally {
                setMode(Mode.FINISHED);
                getIfcEngine().close();
            }
        }

        return false;
    }

    /**
     * Write given citymodel to given OutputStream.
     */
    private void writeCityGML(CityModel cityModel, OutputStream out)
            throws JAXBException, CityGMLReadException, CityGMLWriteException {
        JAXBBuilder builder = ctx.createJAXBBuilder(getClass().getClassLoader());
        CityGMLOutputFactory outputFactory = builder.createCityGMLOutputFactory(CityGMLVersion.v1_0_0);
        PrintWriter writer = new PrintWriter(out);
        CityGMLWriter cityGmlWriter = outputFactory.createCityGMLWriter(writer);

        cityGmlWriter.setPrefixes(CityGMLVersion.v1_0_0);
        cityGmlWriter.setSchemaLocations(CityGMLVersion.v1_0_0);
        cityGmlWriter.setIndentString("  ");
        cityGmlWriter.write(cityModel);
        cityGmlWriter.close();
        writer.flush();
    }

    /**
     * Build up a CityModel from the ifcModel and return it.
     * @throws PluginException 
     * @throws SerializerException 
     */
    private CityModel buildCityModel() throws PluginException, SerializerException {
        IfcModelInterface ifcModel = getModel();

        CityModel cityModel = citygml.createCityModel();
        cityModel.setName(createCodeList(ifcModel.getName()));

        materialManager = new MaterialManager(cityModel, citygml);

        for (IfcProject ifcProject : getModel().getAll(IfcProject.class)) {
            buildCityModel(cityModel, ifcProject);
            break; // Since there is only one ifcProject per ifc file
        }

        return cityModel;
    }

    /**
     * Pre: ifcEngine field must be initialized
     * @param cityModel the base of the citygml data structure
     * @param ifcProject the base of the ifc project
     * @throws SerializerException 
     */
    private void buildCityModel(CityModel cityModel, IfcProject ifcProject) throws SerializerException {
        assignGuid(cityModel, ifcProject);

        for (IfcRelDecomposes ifcRelDecomposes : ifcProject.getIsDecomposedBy()) {
            for (IfcObjectDefinition ifcObjectDefinition : ifcRelDecomposes.getRelatedObjects()) {
                if (ifcObjectDefinition instanceof IfcBuilding) {
                    Building building = buildBuildingOrBuildingPart(cityModel, (IfcBuilding) ifcObjectDefinition,
                            citygml.createBuilding());
                    if (building != null) {
                        cityModel.addCityObjectMember(citygml.createCityObjectMember(building));
                    }
                } else if (ifcObjectDefinition instanceof IfcSite) {
                    CityObjectGroup site = buildSite(cityModel, (IfcSite) ifcObjectDefinition);
                    if (site != null) {
                        cityModel.addCityObjectMember(citygml.createCityObjectMember(site));
                    }
                } else {
                    LOGGER.warn("Unhandled object in city model: " + ifcObjectDefinition);
                }
            }
        }
    }

    private CityObjectGroup buildSite(CityModel cityModel, IfcSite ifcSite) throws SerializerException {
        CityObjectGroup site = citygml.createCityObjectGroup();
        site.setClazz("site");
        setName(site.getName(), ifcSite.getName());
        setGlobalId(site, ifcSite);

        for (IfcRelDecomposes ifcRelDecomposes : ifcSite.getIsDecomposedBy()) {
            for (IfcObjectDefinition ifcObjectDefinition : ifcRelDecomposes.getRelatedObjects()) {
                if (ifcObjectDefinition instanceof IfcBuilding) {
                    Building building = buildBuildingOrBuildingPart(cityModel, (IfcBuilding) ifcObjectDefinition,
                            citygml.createBuilding());
                    if (building != null) {
                        site.addGroupMember(citygml.createCityObjectGroupMember(building));
                    }
                } else if (ifcObjectDefinition instanceof IfcSite) {
                    CityObjectGroup subSite = buildSite(cityModel, (IfcSite) ifcObjectDefinition);
                    if (site != null) {
                        site.addGroupMember(citygml.createCityObjectGroupMember(subSite));
                    }
                } else {
                    //TODO: Handle rooms at this level
                    LOGGER.warn("Unhandled object in site: " + ifcObjectDefinition);
                }
            }
        }

        return site;
    }

    private <T extends AbstractBuilding> T buildBuildingOrBuildingPart(CityModel cityModel, IfcBuilding ifcBuilding,
            T abstractBuilding) throws SerializerException {
        setName(abstractBuilding.getName(), ifcBuilding.getName());
        setGlobalId(abstractBuilding, ifcBuilding);

        // set address
        IfcPostalAddress ifcBuildingAddress = ifcBuilding.getBuildingAddress();
        if (ifcBuildingAddress != null) {
            abstractBuilding.addAddress(citygml.createAddressProperty(buildAddress(ifcBuildingAddress)));
        }

        // Handle all products on this level
        for (IfcRelContainedInSpatialStructure ifcRelContainedInSpatialStructure : ifcBuilding
                .getContainsElements()) {
            for (IfcProduct ifcProduct : ifcRelContainedInSpatialStructure.getRelatedElements()) {
                LOGGER.warn("Unhandled product in building or buildingpart: " + ifcProduct);
            }
        }

        // Continue with the spatial decomposition
        CityObjectGroup buildingStoreys = null;

        for (IfcRelDecomposes ifcRelDecomposes : ifcBuilding.getIsDecomposedBy()) {
            for (IfcObjectDefinition ifcObjectDefinition : ifcRelDecomposes.getRelatedObjects()) {
                if (ifcObjectDefinition instanceof IfcBuildingStorey) {
                    if (buildingStoreys == null) {
                        buildingStoreys = citygml.createCityObjectGroup();
                        buildingStoreys.setClazz("building storeys");
                        buildingStoreys
                                .setGroupParent(citygml.createCityObjectGroupParent(hrefTo(abstractBuilding)));
                        cityModel.addCityObjectMember(citygml.createCityObjectMember(buildingStoreys));
                    }

                    buildBuildingStorey(buildingStoreys, abstractBuilding, (IfcBuildingStorey) ifcObjectDefinition);
                } else if (ifcObjectDefinition instanceof IfcBuilding) {
                    BuildingPart buildingPart = buildBuildingOrBuildingPart(cityModel,
                            (IfcBuilding) ifcObjectDefinition, citygml.createBuildingPart());
                    abstractBuilding.addConsistsOfBuildingPart(citygml.createBuildingPartProperty(buildingPart));
                } else {
                    LOGGER.warn("Unhandled building or builingpart decomposition: " + ifcObjectDefinition);
                }
            }
        }

        return abstractBuilding;
    }

    private Address buildAddress(IfcPostalAddress ifcBuildingAddress) {
        Address address = citygml.createAddress();
        //AddressDetails details = xal.createAddressDetails();
        //TODO: Really create the address here
        return address;
    }

    private void buildBuildingStorey(CityObjectGroup buildingStoreys, AbstractBuilding abstractBuilding,
            IfcBuildingStorey ifcBuildingStorey) throws SerializerException {
        CityObjectGroup buildingSeparation = citygml.createCityObjectGroup();
        buildingSeparation.setClazz("building separation");
        buildingSeparation.addFunction("lod4Strorey");
        setName(buildingSeparation.getName(), ifcBuildingStorey.getName());
        setGlobalId(buildingSeparation, ifcBuildingStorey);

        buildingStoreys.addGroupMember(citygml.createCityObjectGroupMember(buildingSeparation));

        // Loop through spaces (spacial structure)
        for (IfcRelDecomposes ifcRelDecomposes : ifcBuildingStorey.getIsDecomposedBy()) {
            for (IfcObjectDefinition ifcObjectDefinition : ifcRelDecomposes.getRelatedObjects()) {
                if (ifcObjectDefinition instanceof IfcSpace) {
                    IfcSpace ifcSpace = (IfcSpace) ifcObjectDefinition;
                    buildRoom(buildingSeparation, ifcSpace);
                } else {
                    LOGGER.warn("Unknown spacial structure in building storey: " + ifcObjectDefinition);
                }
            }
        }

        // Loop trough the products
        for (IfcRelContainedInSpatialStructure ifcRelContainedInSpatialStructure : ifcBuildingStorey
                .getContainsElements()) {
            for (IfcProduct ifcProduct : ifcRelContainedInSpatialStructure.getRelatedElements()) {
                // Skip any product that is used to fill up a void
                if (isUsedAsVoidFilling(ifcProduct)) {
                    // TODO: Did we skip anything that will never be added?
                    continue;
                }

                // TODO: Handle products here
                AbstractBoundarySurface surface = null;

                if (ifcProduct instanceof IfcSlab) {
                    IfcSlab ifcSlab = (IfcSlab) ifcProduct;

                    if (ifcSlab.getPredefinedType() == IfcSlabTypeEnum.FLOOR) {
                        surface = buildBoundarySurface(ifcSlab, citygml.createFloorSurface());
                    } else if (ifcSlab.getPredefinedType() == IfcSlabTypeEnum.ROOF) {
                        surface = buildBoundarySurface(ifcSlab, citygml.createRoofSurface());
                    } else if (ifcSlab.getPredefinedType() == IfcSlabTypeEnum.BASESLAB
                            || ifcSlab.getPredefinedType() == IfcSlabTypeEnum.LANDING) {
                        surface = buildBoundarySurface(ifcSlab, citygml.createGroundSurface());
                    }
                } else if (ifcProduct instanceof IfcWall) {
                    if (getPropertySingleValue(ifcProduct, "Pset_WallCommon", "IsExternal", true)) {
                        surface = buildBoundarySurface(ifcProduct, citygml.createWallSurface());
                    } else {
                        surface = buildBoundarySurface(ifcProduct, citygml.createInteriorWallSurface());
                    }
                } else if (ifcProduct instanceof IfcCurtainWall) {
                    // TODO: Add information to mark this as a CurtainWall in CityGML (does not exist)
                    if (getPropertySingleValue(ifcProduct, "Pset_CurtainWallCommon", "IsExternal", true)) {
                        surface = buildBoundarySurface(ifcProduct, citygml.createWallSurface());
                    } else {
                        surface = buildBoundarySurface(ifcProduct, citygml.createInteriorWallSurface());
                    }
                }
                // Handle all installation type of conversions (see constructor)
                else if (product2installation.has(ifcProduct)) {
                    Product2InstallationInfo info = product2installation.get(ifcProduct);

                    if ((info.getpSet() == null && info.isDefaultExternal()) || getPropertySingleValue(ifcProduct,
                            info.getpSet(), "IsExternal", info.isDefaultExternal())) {
                        BuildingInstallation buildingInstallation = buildBoundarySurface(ifcProduct,
                                citygml.createBuildingInstallation());
                        buildingInstallation.addFunction(info.getExternalFunction()); // TODO: No good code for beams
                        abstractBuilding.addOuterBuildingInstallation(
                                citygml.createBuildingInstallationProperty(buildingInstallation));
                        buildingSeparation
                                .addGroupMember(citygml.createCityObjectGroupMember(hrefTo(buildingInstallation)));
                    } else {
                        IntBuildingInstallation intBuildingInstallation = buildBoundarySurface(ifcProduct,
                                citygml.createIntBuildingInstallation());
                        intBuildingInstallation.addFunction(info.getInternalFunction()); // TODO: No good code for beams
                        abstractBuilding.addInteriorBuildingInstallation(
                                citygml.createIntBuildingInstallationProperty(intBuildingInstallation));
                        buildingSeparation.addGroupMember(
                                citygml.createCityObjectGroupMember(hrefTo(intBuildingInstallation)));
                    }
                } else if (ifcProduct instanceof IfcOpeningElement) {
                    // Find the fillings of this opening and put that in the model. But we have no wall or other surface to add it too.
                    // Ifc documentation states this should not be part of a structure.
                    // No idea what to do with this.
                    LOGGER.info(
                            "Found IfcOpeningElement in special structure, it should not be here! " + ifcProduct);
                } else {
                    LOGGER.warn("Unhandled product " + ifcProduct);
                }

                // We added a boundary surface, so let's add it and check for voids and add doors and windows
                if (surface != null) {
                    // Adding it to the citygml model
                    abstractBuilding.addBoundedBySurface(citygml.createBoundarySurfaceProperty(surface));
                    buildingSeparation.addGroupMember(citygml.createCityObjectGroupMember(hrefTo(surface)));

                    buildOpeningFillings(surface, ifcProduct);
                }
            }
        }
    }

    private boolean isUsedAsVoidFilling(IfcProduct ifcProduct) {
        if (!(ifcProduct instanceof IfcBuildingElement))
            return false;
        return !((IfcBuildingElement) ifcProduct).getFillsVoids().isEmpty();
    }

    /*
     * Mapping problem:
     * BuildingInstallation is not a boundary surface and can therefore have no openings. In IFC a column can have openings, in CityGML it can not.
     */
    private void buildOpeningFillings(AbstractBoundarySurface surface, IfcProduct ifcProduct)
            throws SerializerException {
        if (!(ifcProduct instanceof IfcBuildingElement)) {
            LOGGER.warn("Encountered a product that is not a building element.");
            return;
        }

        IfcBuildingElement ifcBuildingElement = (IfcBuildingElement) ifcProduct;

        for (IfcRelVoidsElement ifcVoid : ifcBuildingElement.getHasOpenings()) {
            IfcOpeningElement ifcOpeningElement = (IfcOpeningElement) ifcVoid.getRelatedOpeningElement();
            for (IfcRelFillsElement ifcRelFillsElement : ifcOpeningElement.getHasFillings()) {
                IfcElement ifcFilling = ifcRelFillsElement.getRelatedBuildingElement();
                if (ifcFilling instanceof IfcDoor) {
                    surface.addOpening(
                            citygml.createOpeningProperty(buildBoundarySurface(ifcFilling, citygml.createDoor())));
                } else if (ifcFilling instanceof IfcWindow) {
                    surface.addOpening(citygml
                            .createOpeningProperty(buildBoundarySurface(ifcFilling, citygml.createWindow())));
                } else {
                    LOGGER.warn("Found a filling of type I can not handle (not a door or window): " + ifcFilling);
                }
            }
        }
    }

    /**
     * Retreivee a property from an IfcObject.
     * @param propertySetName
     * @param propertyName
     * @return
     * TODO: Determine Exception class for mismatched property class
     */
    private IfcValue getPropertySingleValue(IfcObject ifcObject, String propertySetName, String propertyName) {
        for (IfcRelDefines ifcRelDefines : ifcObject.getIsDefinedBy()) {
            if (ifcRelDefines instanceof IfcRelDefinesByProperties) {
                IfcPropertySetDefinition ifcPropertySetDefinition = ((IfcRelDefinesByProperties) ifcRelDefines)
                        .getRelatingPropertyDefinition();
                if (ifcPropertySetDefinition instanceof IfcPropertySet
                        && ifcPropertySetDefinition.getName().equals(propertySetName)) {
                    IfcPropertySet ifcPropertySet = (IfcPropertySet) ifcPropertySetDefinition;

                    for (IfcProperty ifcProperty : ifcPropertySet.getHasProperties()) {
                        if (ifcProperty.getName().equals(propertyName)) {
                            if (!(ifcProperty instanceof IfcPropertySingleValue))
                                throw new RuntimeException("PropertySingleValue expected");
                            return ((IfcPropertySingleValue) ifcProperty).getNominalValue();
                        }
                    }
                }
            }
        }

        return null;
    }

    /**
     * Returns the result of a boolean property. If the property does not exists the defaultValue will be returned.
     * @param ifcObject
     * @param propertySetName
     * @param propertyName
     * @return
     */
    private boolean getPropertySingleValue(IfcObject ifcObject, String propertySetName, String propertyName,
            boolean defaultValue) {
        IfcValue ifcValue = getPropertySingleValue(ifcObject, propertySetName, propertyName);
        if (ifcValue == null) {
            return defaultValue;
        } else if (ifcValue instanceof IfcBoolean) {
            return ((IfcBoolean) ifcValue).getWrappedValue() == Tristate.TRUE;
        } else {
            throw new RuntimeException("IfcBoolean property expected");
        }
    }

    private Room buildRoom(CityObjectGroup buildingSeparation, IfcSpace ifcSpace) {
        // TODO: Implement generating rooms

        for (IfcRelDecomposes ifcRelDecomposes : ifcSpace.getIsDecomposedBy()) {
            for (IfcObjectDefinition ifcObjectDefinition : ifcRelDecomposes.getRelatedObjects()) {
                System.out.println("* buildRoom: " + ifcObjectDefinition);
            }
        }

        for (IfcRelContainedInSpatialStructure ifcRelContainedInSpatialStructure : ifcSpace.getContainsElements()) {
            for (IfcProduct ifcProduct : ifcRelContainedInSpatialStructure.getRelatedElements()) {
                System.out.println("- buildRoom: " + ifcProduct);
            }
        }

        return null;
    }

    private <T extends AbstractCityObject> T buildBoundarySurface(IfcProduct ifcProduct, T cityObject)
            throws SerializerException {
        setName(cityObject.getName(), ifcProduct.getName());
        setGlobalId(cityObject, ifcProduct);

        MultiSurface multiSurface = gml.createMultiSurface();

        {
            CompositeSurface compositeSurface = gml.createCompositeSurface();
            setGeometry(compositeSurface, ifcProduct);
            materialManager.assign(compositeSurface, ifcProduct);
            multiSurface.addSurfaceMember(gml.createSurfaceProperty(compositeSurface));
        }

        LinkedList<IfcObjectDefinition> decompose = new LinkedList<IfcObjectDefinition>(
                Collections.singletonList(ifcProduct));
        while (!decompose.isEmpty()) {
            for (IfcRelDecomposes ifcRelDecomposes : decompose.removeFirst().getIsDecomposedBy()) {
                for (IfcObjectDefinition ifcObjectDef : ifcRelDecomposes.getRelatedObjects()) {
                    CompositeSurface compositeSurface = gml.createCompositeSurface();
                    setGeometry(compositeSurface, ifcObjectDef);
                    materialManager.assign(compositeSurface, ifcObjectDef);
                    multiSurface.addSurfaceMember(gml.createSurfaceProperty(compositeSurface));
                    decompose.add(ifcObjectDef);
                }
            }
        }

        MultiSurfaceProperty multiSurfaceProperty = gml.createMultiSurfaceProperty(multiSurface);
        try {
            if (PropertyUtils.isWriteable(cityObject, "lod4MultiSurface")) {
                PropertyUtils.setProperty(cityObject, "lod4MultiSurface", multiSurfaceProperty);
            } else {
                PropertyUtils.setProperty(cityObject, "lod4Geometry", multiSurfaceProperty);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return cityObject;
    }

    private void setGeometry(CompositeSurface ms, IfcRoot ifcRootObject) throws SerializerException {
        setGeometry(null, ms, ifcRootObject);
    }

    private void setGeometry(MultiSurface ms1, CompositeSurface ms2, IfcRoot ifcRootObject)
            throws SerializerException {
        try {
            IfcEngineInstance instance = ifcEngineModel.getInstanceFromExpressId((int) ifcRootObject.getOid());
            IfcEngineInstanceVisualisationProperties instanceInModelling = instance.getVisualisationProperties();
            for (int i = instanceInModelling.getStartIndex(); i < instanceInModelling.getPrimitiveCount() * 3
                    + instanceInModelling.getStartIndex(); i += 3) {
                int i1 = geometry.getIndex(i) * 3;
                int i2 = geometry.getIndex(i + 1) * 3;
                int i3 = geometry.getIndex(i + 2) * 3;

                DirectPositionList posList = gml.createDirectPositionList();
                posList.setSrsDimension(3);
                posList.setValue(Arrays.asList(
                        new Double[] { (double) geometry.getVertex(i1 + 0), (double) geometry.getVertex(i1 + 1),
                                (double) geometry.getVertex(i1 + 2), (double) geometry.getVertex(i3 + 0),
                                (double) geometry.getVertex(i3 + 1), (double) geometry.getVertex(i3 + 2),
                                (double) geometry.getVertex(i2 + 0), (double) geometry.getVertex(i2 + 1),
                                (double) geometry.getVertex(i2 + 2), (double) geometry.getVertex(i1 + 0),
                                (double) geometry.getVertex(i1 + 1), (double) geometry.getVertex(i1 + 2) }));

                LinearRing linearRing = gml.createLinearRing();
                linearRing.setPosList(posList);

                Polygon polygon = gml.createPolygon();
                polygon.setExterior(gml.createExterior(linearRing));

                if (ms1 != null) {
                    ms1.addSurfaceMember(gml.createSurfaceProperty(polygon));
                } else if (ms2 != null) {
                    ms2.addSurfaceMember(gml.createSurfaceProperty(polygon));
                }
            }
        } catch (IfcEngineException e) {
            throw new SerializerException("IfcEngineException", e);
        } catch (Exception e) {
            LOGGER.error("", e);
        }
    }

}