org.bimserver.charting.SupportFunctions.java Source code

Java tutorial

Introduction

Here is the source code for org.bimserver.charting.SupportFunctions.java

Source

package org.bimserver.charting;

/******************************************************************************
 * Copyright (C) 2009-2015  BIMserver.org
 * 
 * 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/>.
 *****************************************************************************/

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.mutable.MutableInt;
import org.bimserver.charting.Charts.Chart;
import org.bimserver.charting.Export.IfcObjectWithTrace;
import org.bimserver.charting.Export.StackTrace;
import org.bimserver.emf.IdEObject;
import org.bimserver.emf.IfcModelInterface;
import org.bimserver.models.geometry.GeometryInfo;
import org.bimserver.models.geometry.Vector3f;
import org.bimserver.models.ifc2x3tc1.IfcClassification;
import org.bimserver.models.ifc2x3tc1.IfcClassificationNotation;
import org.bimserver.models.ifc2x3tc1.IfcClassificationNotationFacet;
import org.bimserver.models.ifc2x3tc1.IfcClassificationNotationSelect;
import org.bimserver.models.ifc2x3tc1.IfcClassificationReference;
import org.bimserver.models.ifc2x3tc1.IfcMaterial;
import org.bimserver.models.ifc2x3tc1.IfcMaterialLayer;
import org.bimserver.models.ifc2x3tc1.IfcMaterialLayerSet;
import org.bimserver.models.ifc2x3tc1.IfcMaterialLayerSetUsage;
import org.bimserver.models.ifc2x3tc1.IfcMaterialList;
import org.bimserver.models.ifc2x3tc1.IfcMaterialSelect;
import org.bimserver.models.ifc2x3tc1.IfcObject;
import org.bimserver.models.ifc2x3tc1.IfcObjectDefinition;
import org.bimserver.models.ifc2x3tc1.IfcProduct;
import org.bimserver.models.ifc2x3tc1.IfcProject;
import org.bimserver.models.ifc2x3tc1.IfcRelAssociatesClassification;
import org.bimserver.models.ifc2x3tc1.IfcRelAssociatesMaterial;
import org.bimserver.models.ifc2x3tc1.IfcRelContainedInSpatialStructure;
import org.bimserver.models.ifc2x3tc1.IfcRelDecomposes;
import org.bimserver.models.ifc2x3tc1.IfcRoot;
import org.bimserver.models.ifc2x3tc1.IfcSIUnit;
import org.bimserver.models.ifc2x3tc1.IfcSpatialStructureElement;
import org.bimserver.models.ifc2x3tc1.IfcUnit;
import org.bimserver.models.ifc2x3tc1.IfcUnitAssignment;
import org.bimserver.models.ifc2x3tc1.IfcUnitEnum;
import org.bimserver.models.store.SIPrefix;
import org.eclipse.emf.common.util.EList;
import org.openmali.vecmath2.Vector3d;

public class SupportFunctions {

    public static ArrayList<LinkedHashMap<String, Object>> getTreeStructureWithAreaFromIFCData(
            IfcModelInterface model, Chart chart) {
        return getDataWithTreeStructure("hierarchy", model, chart);
    }

    public static ArrayList<LinkedHashMap<String, Object>> getClusterStructureWithAreaFromIFCData(
            IfcModelInterface model, Chart chart) {
        return getDataWithTreeStructure("clusters", model, chart);
    }

    public static ArrayList<LinkedHashMap<String, Object>> getDataWithTreeStructure(String structureKeyword,
            IfcModelInterface model, Chart chart) {
        ArrayList<LinkedHashMap<String, Object>> rawData = new ArrayList<>();
        // Get units.
        String units = "units";
        SIPrefix prefix = SupportFunctions.getLengthUnitPrefix(model);
        if (prefix != null)
            units = prefix.getLiteral();
        // Prepare for static iteration.
        int maxDepth = 0;
        LinkedList<IfcObjectWithTrace> leaves = new LinkedList<>();
        LinkedList<IfcObjectWithTrace> parts = new LinkedList<>();
        // Iterate, but start with projects.
        for (IfcProject ifcProject : model.getAll(IfcProject.class))
            parts.add(new IfcObjectWithTrace(ifcProject));
        // Iterate the IFC going 1 level at a time (ex: Projects -> Sites, then Sites -> Buildings, then Buildings -> IfcProducts, then IfcProducts -> IfcProducts).
        while (parts.size() > 0) {
            IfcObjectWithTrace entry = parts.pop();
            StackTrace traceAtThisPoint = entry.Key;
            IfcObject parentObject = entry.Value;
            // Get name to be added to stack.
            int parentId = parentObject.getExpressId();
            String ifcParentName = (parentId >= 0)
                    ? String.format("%s (%d)", parentObject.getName(), parentObject.getExpressId())
                    : parentObject.getName();
            // Make the stack trace.
            StackTrace traceAtChildren = new StackTrace(traceAtThisPoint);
            traceAtChildren.add(ifcParentName);
            // Track the children that are getting put into the raw data at this point.
            LinkedList<IfcObjectWithTrace> childrenInThisPass = new LinkedList<>();
            // Walk the relationship from the parent to its child objects.
            for (IfcRelDecomposes ifcRelDecomposes : parentObject.getIsDecomposedBy()) {
                // Iterate what the object decomposes into.
                for (IfcObjectDefinition definition : ifcRelDecomposes.getRelatedObjects())
                    childrenInThisPass.add(new IfcObjectWithTrace(traceAtChildren, (IfcObject) definition));
            }
            // If IfcObject happens to be something like an IfcBuildingStorey, go looking through its structure.
            if (parentObject instanceof IfcSpatialStructureElement) {
                IfcSpatialStructureElement ifcSpatialStructureElement = (IfcSpatialStructureElement) parentObject;
                for (IfcRelContainedInSpatialStructure ifcRelContainedInSpatialStructure : ifcSpatialStructureElement
                        .getContainsElements())
                    for (IfcProduct ifcProduct : ifcRelContainedInSpatialStructure.getRelatedElements()) {
                        Double area = getRoughAreaEstimateFromIfcProduct(ifcProduct);
                        childrenInThisPass.add(new IfcObjectWithTrace(traceAtChildren, ifcProduct, area));
                    }
            }
            // Test if this node is a leaf. If it is, keep it.
            if (childrenInThisPass.size() == 0) {
                leaves.add(entry);
                // Update depth.
                int depthAtThisPoint = traceAtThisPoint.size() + 1;
                if (depthAtThisPoint > maxDepth)
                    maxDepth = depthAtThisPoint;
            } else
                parts.addAll(childrenInThisPass);
        }
        // Derive the column names.
        ArrayList<String> hierarchyColumnNames = new ArrayList<>();
        for (int i = 0; i < maxDepth; i++)
            hierarchyColumnNames.add(String.format("%s%d", structureKeyword, i + 1));
        // Update the chart configuration.
        chart.setDimensionLookupKeys(structureKeyword, hierarchyColumnNames);
        chart.setDimensionLookupKey("size", "size");
        chart.setDimensionLookupKey("label", "label");
        chart.setDimensionLookupKey("color", hierarchyColumnNames.get(Math.max(0, maxDepth - 2)));
        // Iterate the leaf nodes.
        for (IfcObjectWithTrace leaf : leaves) {
            StackTrace traceAtThisPoint = leaf.Key;
            IfcObject leafObject = leaf.Value;
            // Prepare to store this raw data entry.
            LinkedHashMap<String, Object> leafDataEntry = new LinkedHashMap<>();
            // Prepare to iterate backwards along column names (ex. hierarchy10, ..., hierarchy1).
            int leafDepthIndex = maxDepth - 1;
            int sizeOfStack = traceAtThisPoint.size();
            int stackUpperBound = leafDepthIndex - 1;
            int stackLowerRange = stackUpperBound - sizeOfStack;
            // Iterate backwards along column names.
            for (int i = leafDepthIndex; i >= 0; i--) {
                String column = hierarchyColumnNames.get(i);
                String value;
                if (i == leafDepthIndex) {
                    value = String.format("%s (%d)", leafObject.getName(), leafObject.getOid());
                    if (units != null && leaf.Size != null) {
                        if (leaf.Size > 0)
                            value += String.format(" ~%s %s\u00B2", leaf.Size.intValue(), units);
                        else
                            value += String.format(" %s %s\u00B2", leaf.Size, units);
                    }
                    leafDataEntry.put("label", leafObject.getName());
                    leafDataEntry.put("size", leaf.Size);
                } else if (stackLowerRange < i && i <= stackUpperBound) {
                    int index = sizeOfStack - (stackUpperBound - i) - 1;
                    value = traceAtThisPoint.get(index);
                } else
                    value = null;
                // Add column.
                leafDataEntry.put(column, value);
            }
            // Add the data.
            rawData.add(leafDataEntry);
        }
        // Send it all back.
        return rawData;
    }

    public static Double getRoughAreaEstimateFromIfcProduct(IfcProduct ifcProduct) {
        GeometryInfo geometry = ifcProduct.getGeometry();
        Double area = null;
        if (geometry != null) {
            Vector3f min = geometry.getMinBounds();
            Vector3f max = geometry.getMaxBounds();
            Vector3d delta = new Vector3d(max.getX() - min.getX(), max.getY() - min.getY(),
                    max.getZ() - min.getZ());
            // A (of rectangular prism boundary) = 2(wl + hl + hw) 
            area = 2 * (delta.x() * delta.y() + delta.z() * delta.y() + delta.z() * delta.x());
        }
        return area;
    }

    public static Double getRoughVolumeEstimateFromIfcProduct(IfcProduct ifcProduct) {
        GeometryInfo geometry = ifcProduct.getGeometry();
        Double volume = null;
        if (geometry != null) {
            Vector3f min = geometry.getMinBounds();
            Vector3f max = geometry.getMaxBounds();
            Vector3d delta = new Vector3d(max.getX() - min.getX(), max.getY() - min.getY(),
                    max.getZ() - min.getZ());
            // V (of rectangular prism boundary) = whl 
            volume = delta.x() * delta.y() * delta.z();
        }
        return volume;
    }

    public static ArrayList<LinkedHashMap<String, Object>> getIfcDataByClassWithTreeStructure(
            String structureKeyword, IfcModelInterface model, Chart chart, int superClassesToStepBackwardsThrough) {
        ArrayList<LinkedHashMap<String, Object>> rawData = new ArrayList<>();
        // Prepare for static iteration.
        LinkedHashMap<Class<? extends IfcProduct>, Integer> ifcProductClassCounts = new LinkedHashMap<>();
        // Iterate only the products.
        for (IfcProduct ifcProduct : model.getAllWithSubTypes(IfcProduct.class)) {
            Class<? extends IfcProduct> key = ifcProduct.getClass();
            Integer value = 0;
            if (ifcProductClassCounts.containsKey(key))
                value = ifcProductClassCounts.get(key);
            ifcProductClassCounts.put(key, value + 1);
        }
        // Derive the column names.
        ArrayList<String> hierarchyColumnNames = new ArrayList<>();
        superClassesToStepBackwardsThrough = Math.max(0, superClassesToStepBackwardsThrough);
        String leafColumnName = String.format("%s%d", structureKeyword, superClassesToStepBackwardsThrough + 1);
        for (int i = 0; i < superClassesToStepBackwardsThrough + 1; i++)
            hierarchyColumnNames.add(String.format("%s%d", structureKeyword, i + 1));
        // Update the chart configuration.
        chart.setDimensionLookupKeys(structureKeyword, hierarchyColumnNames);
        chart.setDimensionLookupKey("size", "size");
        chart.setDimensionLookupKey("label", "label");
        chart.setDimensionLookupKey("color", "size");
        // Add each entry.
        for (Entry<Class<? extends IfcProduct>, Integer> countedEntry : ifcProductClassCounts.entrySet()) {
            // Prepare to store this raw data entry.
            LinkedHashMap<String, Object> dataEntry = new LinkedHashMap<>();
            //
            Integer count = countedEntry.getValue();
            Class<? extends IfcProduct> productClass = countedEntry.getKey();
            // Sanitize.
            String className = getSanitizedSimpleClassName(productClass);
            // Name the group.
            String name = String.format("%s (%s)", className, count);
            dataEntry.put(leafColumnName, name);
            // Step back through the inheritance of the IfcProduct.
            if (superClassesToStepBackwardsThrough > 0) {
                Class<?> childClass = productClass;
                for (int i = superClassesToStepBackwardsThrough - 1; i >= 0; i--) {
                    String thisColumnName = String.format("%s%d", structureKeyword, i + 1);
                    Class<?> superClass = childClass.getSuperclass();
                    String enclosingClassName = getSanitizedSimpleClassName(superClass);
                    dataEntry.put(thisColumnName, enclosingClassName);
                    // Update pointer.
                    childClass = superClass;
                }
            }
            dataEntry.put("size", count);
            dataEntry.put("label", name);
            // Push the entry into the data pool.
            rawData.add(dataEntry);
        }
        // Send it all back.
        return rawData;
    }

    public static Comparator<Double> sortSmallerValuesToFront = new Comparator<Double>() {
        public int compare(Double p1, Double p2) {
            //
            Double a = (p1 != null) ? p1 : 0;
            Double b = (p2 != null) ? p2 : 0;
            // Smaller values at start, larger values at end.
            return a.compareTo(b);
        }
    };

    public static ArrayList<LinkedHashMap<String, Object>> getIfcMaterialsByClassWithTreeStructure(
            String structureKeyword, IfcModelInterface model, Chart chart, MutableInt subChartCount) {
        // Derive the column name.
        String leafColumnName = structureKeyword;
        // Update the chart configuration.
        chart.setDimensionLookupKey(structureKeyword, leafColumnName);
        chart.setDimensionLookupKey("date", "date");
        chart.setDimensionLookupKey("size", "size");
        // Prepare to iterate the relationships.
        LinkedHashMap<String, ArrayList<Double>> materialNameWithSizes = new LinkedHashMap<>();
        // Iterate only the relationships.
        for (IfcRelAssociatesMaterial ifcRelAssociatesMaterial : model
                .getAllWithSubTypes(IfcRelAssociatesMaterial.class)) {
            // IfcMaterialSelect: IfcMaterial, IfcMaterialList, IfcMaterialLayerSetUsage, IfcMaterialLayerSet, IfcMaterialLayer.
            IfcMaterialSelect materialLike = ifcRelAssociatesMaterial.getRelatingMaterial();
            // If there was a material-like object, sum it across X.
            if (materialLike != null) {
                // Get material name, like: Brick (000000), Air (000001); or, Concrete (0000000).
                String materialName = getNameOfMaterialsFromMaterialLike(materialLike, true, true);
                // Use material name if available. Otherwise, use OID of top-level material-like object.
                String name = (materialName != null) ? materialName : String.format("%d", materialLike.getOid());
                // Add entry if it doesn't exist.
                if (!materialNameWithSizes.containsKey(name))
                    materialNameWithSizes.put(name, new ArrayList<Double>());
                // Get existing size data.
                ArrayList<Double> sizes = materialNameWithSizes.get(name);
                // Iterate objects.
                EList<IfcRoot> ifcRoots = ifcRelAssociatesMaterial.getRelatedObjects();
                for (IfcRoot ifcRoot : ifcRoots) {
                    Double size = 0.0;
                    if (ifcRoot instanceof IfcObjectDefinition) {
                        IfcObjectDefinition ifcObjectDefinition = (IfcObjectDefinition) ifcRoot;
                        if (ifcObjectDefinition instanceof IfcObject) {
                            IfcObject ifcObject = (IfcObject) ifcObjectDefinition;
                            if (ifcObject instanceof IfcProduct) {
                                IfcProduct ifcProduct = (IfcProduct) ifcObject;
                                Double volume = getRoughVolumeEstimateFromIfcProduct(ifcProduct);
                                size = volume;
                            }
                        }
                    }
                    if (size != null && size > 0)
                        sizes.add(size);
                }
            }
        }
        //
        subChartCount.setValue(materialNameWithSizes.size());
        //
        ArrayList<LinkedHashMap<String, Object>> rawData = new ArrayList<>();
        //
        for (Entry<String, ArrayList<Double>> entry : materialNameWithSizes.entrySet()) {
            String name = entry.getKey();
            // Get existing size data.
            ArrayList<Double> sizes = materialNameWithSizes.get(name);
            // Sort, value ascending.
            Collections.sort(sizes, sortSmallerValuesToFront);
            sizes.add(0, 0.0);
            if (sizes.size() == 1)
                sizes.add(0, 0.0);
            // Count including empty first entry.
            double count = Math.max(1, sizes.size() - 1);
            double step = 10000.0 / count;
            double runningSize = 0.0;
            // Add sum of zero at entry zero.
            int i = 0;
            // Iterate objects, summing them across 0 to 10000 (an arbitrary range, a way to relate to other sums along X).
            for (Double size : sizes) {
                double someMeasurement = (size != null) ? size : 0.0;
                runningSize += someMeasurement;
                // Prepare to store this raw data entry.
                LinkedHashMap<String, Object> dataEntry = new LinkedHashMap<>();
                // Name the group.
                dataEntry.put(leafColumnName, name);
                dataEntry.put("date", i * step);
                dataEntry.put("size", runningSize);
                // Push the entry into the data pool.
                rawData.add(dataEntry);
                //
                i += 1;
            }
        }
        // Send it all back.
        return rawData;
    }

    public static String getNameOfMaterialsFromMaterialLike(IfcMaterialSelect materialLike,
            boolean includeIfcMaterialOID, boolean includeLayerPercentages) {
        String materialName = null;
        if (materialLike instanceof IfcMaterial) {
            IfcMaterial ifcMaterial = (IfcMaterial) materialLike;
            materialName = ifcMaterial.getName();
            if (includeIfcMaterialOID)
                materialName += String.format(" (%d)", ifcMaterial.getOid());
        } else if (materialLike instanceof IfcMaterialList) {
            IfcMaterialList ifcMaterialList = (IfcMaterialList) materialLike;
            ArrayList<String> materials = new ArrayList<>();
            for (IfcMaterial ifcMaterial : ifcMaterialList.getMaterials()) {
                String thisName = getNameOfMaterialsFromMaterialLike(ifcMaterial, includeIfcMaterialOID,
                        includeLayerPercentages);
                materials.add(thisName);
            }
            materialName = StringUtils.join(materials, ", ");
        } else if (materialLike instanceof IfcMaterialLayerSetUsage) {
            IfcMaterialLayerSetUsage ifcMaterialLayerSetUsage = (IfcMaterialLayerSetUsage) materialLike;
            IfcMaterialLayerSet ifcMaterialLayerSet = ifcMaterialLayerSetUsage.getForLayerSet();
            return getNameOfMaterialsFromMaterialLike(ifcMaterialLayerSet, includeIfcMaterialOID,
                    includeLayerPercentages);
        } else if (materialLike instanceof IfcMaterialLayerSet) {
            IfcMaterialLayerSet ifcMaterialLayerSet = (IfcMaterialLayerSet) materialLike;
            ArrayList<String> materials = new ArrayList<>();
            ArrayList<Double> thicknesses = new ArrayList<>();
            double thicknessSum = 0.0;
            for (IfcMaterialLayer ifcMaterialLayer : ifcMaterialLayerSet.getMaterialLayers()) {
                String thisName = getNameOfMaterialsFromMaterialLike(ifcMaterialLayer.getMaterial(),
                        includeIfcMaterialOID, includeLayerPercentages);
                materials.add(thisName);
                //
                if (includeLayerPercentages) {
                    double layerThickness = ifcMaterialLayer.getLayerThickness();
                    thicknesses.add(layerThickness);
                    thicknessSum += layerThickness;
                }
            }
            // Calculate percentages and add them to names.
            if (includeLayerPercentages && thicknessSum > 0)
                for (int i = 0; i < materials.size(); i++) {
                    double thisPercent = 100 * thicknesses.get(i) / thicknessSum;
                    String newMaterialName = String.format("%.2f%% %s", thisPercent, materials.get(i));
                    materials.set(i, newMaterialName);
                }
            // Create the joined name.
            materialName = StringUtils.join(materials, ", ");
        } else if (materialLike instanceof IfcMaterialLayer) {
            IfcMaterialLayer ifcMaterialLayer = (IfcMaterialLayer) materialLike;
            return getNameOfMaterialsFromMaterialLike(ifcMaterialLayer.getMaterial(), includeIfcMaterialOID,
                    includeLayerPercentages);
        }
        return materialName;
    }

    public static ArrayList<LinkedHashMap<String, Object>> getIfcMaterialsByNameWithTreeStructure(
            String structureKeyword, IfcModelInterface model, Chart chart, MutableInt subChartCount) {
        // Derive the column name.
        String leafColumnName = structureKeyword;
        // Update the chart configuration.
        chart.setDimensionLookupKey(structureKeyword, leafColumnName);
        chart.setDimensionLookupKey("date", "date");
        chart.setDimensionLookupKey("size", "size");
        // Prepare to iterate the relationships.
        LinkedHashMap<String, ArrayList<Double>> materialNameWithSizes = new LinkedHashMap<>();
        // Iterate only the relationships.
        for (IfcRelAssociatesMaterial ifcRelAssociatesMaterial : model
                .getAllWithSubTypes(IfcRelAssociatesMaterial.class)) {
            // IfcMaterialSelect: IfcMaterial, IfcMaterialList, IfcMaterialLayerSetUsage, IfcMaterialLayerSet, IfcMaterialLayer.
            IfcMaterialSelect materialLike = ifcRelAssociatesMaterial.getRelatingMaterial();
            // If there was a material-like object, sum the names of what it decomposes into across X individually.
            if (materialLike != null) {
                // First, get size data from IFC products.
                ArrayList<Double> sizes = new ArrayList<>();
                // Iterate objects.
                EList<IfcRoot> ifcRoots = ifcRelAssociatesMaterial.getRelatedObjects();
                for (IfcRoot ifcRoot : ifcRoots) {
                    Double size = 0.0;
                    if (ifcRoot instanceof IfcObjectDefinition) {
                        IfcObjectDefinition ifcObjectDefinition = (IfcObjectDefinition) ifcRoot;
                        if (ifcObjectDefinition instanceof IfcObject) {
                            IfcObject ifcObject = (IfcObject) ifcObjectDefinition;
                            if (ifcObject instanceof IfcProduct) {
                                IfcProduct ifcProduct = (IfcProduct) ifcObject;
                                Double volume = getRoughVolumeEstimateFromIfcProduct(ifcProduct);
                                size = volume;
                            }
                        }
                    }
                    if (size != null && size > 0)
                        sizes.add(size);
                }
                // Get material names with percentages, like: Material Name -> 0.5
                LinkedHashMap<String, Double> materials = getNameOfMaterialsFromMaterialLikeWithPercents(
                        materialLike, false);
                // Second, iterate materials, realizing the percentage of the sizes onto the collection of sizes for each material name.
                for (Entry<String, Double> materialEntry : materials.entrySet()) {
                    String materialName = materialEntry.getKey();
                    Double percent = materialEntry.getValue();
                    // Use material name if available. Otherwise, use OID of top-level material-like object.
                    String name = (materialName != null) ? materialName
                            : String.format("%d", materialLike.getOid());
                    // Add entry if it doesn't exist.
                    if (!materialNameWithSizes.containsKey(name))
                        materialNameWithSizes.put(name, new ArrayList<Double>());
                    ArrayList<Double> theseSizes = materialNameWithSizes.get(name);
                    // Get existing size data.
                    if (percent != null && percent > 0) {
                        // If not alteration is required, clone into the stack.
                        if (percent == 1.0)
                            theseSizes.addAll(sizes);
                        // Otherwise, realize the percent of the size.
                        else
                            for (Double size : sizes)
                                theseSizes.add(size * percent);
                    }
                }
            }
        }
        //
        subChartCount.setValue(materialNameWithSizes.size());
        //
        ArrayList<LinkedHashMap<String, Object>> rawData = new ArrayList<>();
        //
        for (Entry<String, ArrayList<Double>> entry : materialNameWithSizes.entrySet()) {
            String name = entry.getKey();
            // Get existing size data.
            ArrayList<Double> sizes = materialNameWithSizes.get(name);
            // Sort, value ascending.
            Collections.sort(sizes, sortSmallerValuesToFront);
            sizes.add(0, 0.0);
            if (sizes.size() == 1)
                sizes.add(0, 0.0);
            // Count including empty first entry.
            double count = Math.max(1, sizes.size() - 1);
            double step = 10000.0 / count;
            double runningSize = 0.0;
            // Add sum of zero at entry zero.
            int i = 0;
            // Iterate objects, summing them across 0 to 10000 (an arbitrary range, a way to relate to other sums along X).
            for (Double size : sizes) {
                double someMeasurement = (size != null) ? size : 0.0;
                runningSize += someMeasurement;
                // Prepare to store this raw data entry.
                LinkedHashMap<String, Object> dataEntry = new LinkedHashMap<>();
                // Name the group.
                dataEntry.put(leafColumnName, name);
                dataEntry.put("date", i * step);
                dataEntry.put("size", runningSize);
                // Push the entry into the data pool.
                rawData.add(dataEntry);
                //
                i += 1;
            }
        }
        // Send it all back.
        return rawData;
    }

    public static LinkedHashMap<String, Double> getNameOfMaterialsFromMaterialLikeWithPercents(
            IfcMaterialSelect materialLike, boolean includeIfcMaterialOID) {
        // Prepare a place to store materials, like: Material Name -> 1.0
        LinkedHashMap<String, Double> parts = new LinkedHashMap<>();
        //
        if (materialLike instanceof IfcMaterial) {
            IfcMaterial ifcMaterial = (IfcMaterial) materialLike;
            String materialName = ifcMaterial.getName();
            if (includeIfcMaterialOID)
                materialName += String.format(" (%d)", ifcMaterial.getOid());
            // Material is 100% of the composition.
            parts.put(materialName, 1.0);
        } else if (materialLike instanceof IfcMaterialList) {
            IfcMaterialList ifcMaterialList = (IfcMaterialList) materialLike;
            for (IfcMaterial ifcMaterial : ifcMaterialList.getMaterials()) {
                String thisName = getNameOfMaterialsFromMaterialLike(ifcMaterial, includeIfcMaterialOID, false);
                // Pretend each material is 100% of the composition, because it was intentionally not specified. 
                parts.put(thisName, 1.0);
            }
        } else if (materialLike instanceof IfcMaterialLayerSetUsage) {
            IfcMaterialLayerSetUsage ifcMaterialLayerSetUsage = (IfcMaterialLayerSetUsage) materialLike;
            IfcMaterialLayerSet ifcMaterialLayerSet = ifcMaterialLayerSetUsage.getForLayerSet();
            return getNameOfMaterialsFromMaterialLikeWithPercents(ifcMaterialLayerSet, includeIfcMaterialOID);
        } else if (materialLike instanceof IfcMaterialLayerSet) {
            IfcMaterialLayerSet ifcMaterialLayerSet = (IfcMaterialLayerSet) materialLike;
            ArrayList<String> materials = new ArrayList<>();
            ArrayList<Double> thicknesses = new ArrayList<>();
            double thicknessSum = 0.0;
            for (IfcMaterialLayer ifcMaterialLayer : ifcMaterialLayerSet.getMaterialLayers()) {
                String thisName = getNameOfMaterialsFromMaterialLike(ifcMaterialLayer.getMaterial(),
                        includeIfcMaterialOID, false);
                materials.add(thisName);
                //
                double layerThickness = ifcMaterialLayer.getLayerThickness();
                thicknesses.add(layerThickness);
                thicknessSum += layerThickness;
            }
            // Calculate percentages and add the materials.
            for (int i = 0; i < materials.size(); i++) {
                double thisPercent = (thicknessSum > 0) ? thicknesses.get(i) / thicknessSum : 0;
                parts.put(materials.get(i), thisPercent);
            }
        } else if (materialLike instanceof IfcMaterialLayer) {
            IfcMaterialLayer ifcMaterialLayer = (IfcMaterialLayer) materialLike;
            return getNameOfMaterialsFromMaterialLikeWithPercents(ifcMaterialLayer.getMaterial(),
                    includeIfcMaterialOID);
        }
        return parts;
    }

    public static ArrayList<LinkedHashMap<String, Object>> getIfcByClassificationReferenceWithTreeStructure(
            String structureKeyword, IfcModelInterface model, Chart chart, boolean includeClassificationSystem) {
        ArrayList<LinkedHashMap<String, Object>> rawData = new ArrayList<>();
        // Prepare for static iteration.
        LinkedHashMap<IfcRelAssociatesClassification, Integer> ifcClassificationWithCounts = new LinkedHashMap<>();
        // Iterate only the products.
        for (IfcRelAssociatesClassification ifcRelAssociatesClassification : model
                .getAllWithSubTypes(IfcRelAssociatesClassification.class)) {
            IfcRelAssociatesClassification key = ifcRelAssociatesClassification;
            Integer value = 0;
            if (ifcClassificationWithCounts.containsKey(key))
                value = ifcClassificationWithCounts.get(key);
            // Count.
            EList<IfcRoot> a = ifcRelAssociatesClassification.getRelatedObjects();
            ifcClassificationWithCounts.put(key, value + a.size());
        }
        // Derive the column names.
        ArrayList<String> hierarchyColumnNames = new ArrayList<>();
        int extraColumns = (includeClassificationSystem) ? 1 : 0;
        String leafColumnName = String.format("%s%d", structureKeyword, extraColumns + 1);
        for (int i = 0; i < extraColumns + 1; i++)
            hierarchyColumnNames.add(String.format("%s%d", structureKeyword, i + 1));
        // Update the chart configuration.
        chart.setDimensionLookupKeys(structureKeyword, hierarchyColumnNames);
        chart.setDimensionLookupKey("size", "size");
        chart.setDimensionLookupKey("label", "label");
        chart.setDimensionLookupKey("color", "size");
        // Add each entry.
        for (Entry<IfcRelAssociatesClassification, Integer> countedEntry : ifcClassificationWithCounts.entrySet()) {
            //
            Integer count = countedEntry.getValue();
            IfcRelAssociatesClassification ifcRelAssociatesClassification = countedEntry.getKey();
            //
            LinkedList<String> itemReferenceNames = new LinkedList<>();
            String classificationSystem = (ifcRelAssociatesClassification.isSetName())
                    ? ifcRelAssociatesClassification.getName()
                    : "(no name)";
            //
            IfcClassificationNotationSelect notationOrReference = ifcRelAssociatesClassification
                    .getRelatingClassification();
            if (notationOrReference instanceof IfcClassificationNotation) {
                // Get notation.
                IfcClassificationNotation notation = (IfcClassificationNotation) notationOrReference;
                // Go through the facets of this annotation.
                for (IfcClassificationNotationFacet facet : notation.getNotationFacets()) {
                    String notationValue = facet.getNotationValue();
                    itemReferenceNames.add(notationValue);
                }
                // TODO: Look up notation in classification. No inverse lookup is available.
            } else if (notationOrReference instanceof IfcClassificationReference) {
                // Get reference.
                IfcClassificationReference reference = (IfcClassificationReference) notationOrReference;
                // Get the reference name.
                String itemReferenceName = reference.getItemReference();
                itemReferenceNames.add(itemReferenceName);
                // Get the classification the reference links out to.
                IfcClassification classification = reference.getReferencedSource();
                // Use it.
                if (classification != null)
                    classificationSystem = classification.getName();
            }
            //
            while (itemReferenceNames.size() > 0) {
                String itemReferenceName = itemReferenceNames.pop();
                // Prepare to store this raw data entry.
                LinkedHashMap<String, Object> dataEntry = new LinkedHashMap<>();
                // Name the group.
                String name = String.format("%s (%s)", itemReferenceName, count);
                dataEntry.put(leafColumnName, name);
                if (includeClassificationSystem)
                    dataEntry.put(hierarchyColumnNames.get(0), classificationSystem);
                dataEntry.put("size", count);
                dataEntry.put("label", name);
                // Push the entry into the data pool.
                rawData.add(dataEntry);
            }
        }
        // Send it all back.
        return rawData;
    }

    /**
     * @param enclosingClass
     */
    public static String getSanitizedSimpleClassName(Class<?> enclosingClass) {
        String enclosingClassName = (enclosingClass != null) ? enclosingClass.getSimpleName() : "";
        if (enclosingClassName.endsWith("Impl"))
            enclosingClassName = enclosingClassName.substring(0, enclosingClassName.length() - "Impl".length());
        if (enclosingClassName.startsWith("Ifc"))
            enclosingClassName = enclosingClassName.substring("Ifc".length(), enclosingClassName.length());
        return StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(enclosingClassName), ' ');
    }

    public static SIPrefix getLengthUnitPrefix(IfcModelInterface model) {
        SIPrefix lengthUnitPrefix = null;
        boolean prefixFound = false;
        Map<Long, IdEObject> objects = model.getObjects();
        for (IdEObject object : objects.values()) {
            if (object instanceof IfcProject) {
                IfcUnitAssignment unitsInContext = ((IfcProject) object).getUnitsInContext();
                if (unitsInContext != null) {
                    EList<IfcUnit> units = unitsInContext.getUnits();
                    for (IfcUnit unit : units) {
                        if (unit instanceof IfcSIUnit) {
                            IfcSIUnit ifcSIUnit = (IfcSIUnit) unit;
                            IfcUnitEnum unitType = ifcSIUnit.getUnitType();
                            if (unitType == IfcUnitEnum.LENGTHUNIT) {
                                prefixFound = true;
                                switch (ifcSIUnit.getPrefix()) {
                                case EXA:
                                    lengthUnitPrefix = SIPrefix.EXAMETER;
                                    break;
                                case PETA:
                                    lengthUnitPrefix = SIPrefix.PETAMETER;
                                    break;
                                case TERA:
                                    lengthUnitPrefix = SIPrefix.TERAMETER;
                                    break;
                                case GIGA:
                                    lengthUnitPrefix = SIPrefix.GIGAMETER;
                                    break;
                                case MEGA:
                                    lengthUnitPrefix = SIPrefix.MEGAMETER;
                                    break;
                                case KILO:
                                    lengthUnitPrefix = SIPrefix.KILOMETER;
                                    break;
                                case HECTO:
                                    lengthUnitPrefix = SIPrefix.HECTOMETER;
                                    break;
                                case DECA:
                                    lengthUnitPrefix = SIPrefix.DECAMETER;
                                    break;
                                case DECI:
                                    lengthUnitPrefix = SIPrefix.DECIMETER;
                                    break;
                                case CENTI:
                                    lengthUnitPrefix = SIPrefix.CENTIMETER;
                                    break;
                                case MILLI:
                                    lengthUnitPrefix = SIPrefix.MILLIMETER;
                                    break;
                                case MICRO:
                                    lengthUnitPrefix = SIPrefix.MICROMETER;
                                    break;
                                case NANO:
                                    lengthUnitPrefix = SIPrefix.NANOMETER;
                                    break;
                                case PICO:
                                    lengthUnitPrefix = SIPrefix.PICOMETER;
                                    break;
                                case FEMTO:
                                    lengthUnitPrefix = SIPrefix.FEMTOMETER;
                                    break;
                                case ATTO:
                                    lengthUnitPrefix = SIPrefix.ATTOMETER;
                                    break;
                                case NULL:
                                    lengthUnitPrefix = SIPrefix.METER;
                                    break;
                                }
                                break;
                            }
                        }
                    }
                }
            }
            if (prefixFound)
                break;
        }
        return lengthUnitPrefix;
    }
}