663 lines
20 KiB
C++
Executable File
663 lines
20 KiB
C++
Executable File
/**
|
|
* @RDE_HEADER@
|
|
* Interface
|
|
* $Id$
|
|
* $HeadURL$
|
|
* $Author$
|
|
*/
|
|
|
|
// Own
|
|
#include "building_ownership.h"
|
|
#include <Map/map.h>
|
|
#include <Map/object_factory.h>
|
|
#include <MapObjects/cube.h>
|
|
|
|
#include <LtsCoordTrans/lts_coord_trans.h>
|
|
#include <MathUtils/simple_helper.h>
|
|
|
|
#include <unordered_map>
|
|
#include <functional>
|
|
|
|
#define USE_BUFFER_APPROACH
|
|
#ifdef USE_BUFFER_APPROACH
|
|
# include <arcgis/PolygonBuilder.h>
|
|
# include <arcgis/GeometryEngine.h>
|
|
#endif // USE_BUFFER_APPROACH
|
|
|
|
namespace SituationMap
|
|
{
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// PositionCont BuildingOwnership::retrieveBuildingGeometry(int buildingId, int floorId) const
|
|
// QMap<int, double> BuildingOwnership::retrieveBuildingAbsHeight(int buildingId) const
|
|
|
|
//------------------------------------------------------------------------------
|
|
// class BuildingGeometryCache
|
|
//------------------------------------------------------------------------------
|
|
class BuildingGeometryCache
|
|
{
|
|
//------------------------------------------------------------------------------
|
|
// struct KEY_T
|
|
//------------------------------------------------------------------------------
|
|
struct KEY_T
|
|
{
|
|
KEY_T(int buildingId = -1, int floorId = -1)
|
|
: m_buildingId(buildingId), m_floorId(floorId)
|
|
{}
|
|
|
|
int m_buildingId{-1};
|
|
int m_floorId{-1};
|
|
|
|
bool operator<(const KEY_T &other) const
|
|
{
|
|
return m_buildingId < other.m_buildingId
|
|
|| ((m_buildingId == other.m_buildingId) && (m_floorId < other.m_floorId));
|
|
}
|
|
};
|
|
|
|
public:
|
|
BuildingGeometryCache() = default;
|
|
~BuildingGeometryCache() = default;
|
|
|
|
void init(BuildingOwnership *buildingOwnership)
|
|
{
|
|
m_buildingOwnership = buildingOwnership;
|
|
}
|
|
|
|
PositionCont retrieveBuildingGeometry(int buildingId, int floorId) const
|
|
{
|
|
const KEY_T key(buildingId, floorId);
|
|
loadGeometry(key);
|
|
return m_cacheGeometry[key];
|
|
};
|
|
|
|
QMap<int, double> retrieveBuildingAbsHeight(int buildingId) const
|
|
{
|
|
loadAbsHeight(buildingId);
|
|
return m_cacheAbsHeight[buildingId];
|
|
};
|
|
|
|
|
|
private:
|
|
void loadGeometry(const KEY_T &key) const
|
|
{
|
|
if (m_cacheGeometry.contains(key))
|
|
return;
|
|
m_cacheGeometry[key] = m_buildingOwnership->retrieveBuildingGeometryInternal(key.m_buildingId, key.m_floorId);
|
|
}
|
|
|
|
void loadAbsHeight(int buildingId) const
|
|
{
|
|
if (m_cacheAbsHeight.contains(buildingId))
|
|
return;
|
|
m_cacheAbsHeight[buildingId] = m_buildingOwnership->retrieveBuildingAbsHeightInternal(buildingId);
|
|
}
|
|
|
|
private:
|
|
BuildingOwnership *m_buildingOwnership{nullptr};
|
|
mutable QMap<KEY_T, PositionCont> m_cacheGeometry;
|
|
mutable QMap<int, QMap<int, double>> m_cacheAbsHeight; // key == buildingId
|
|
};
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
class BuildingOwnership::Private
|
|
{
|
|
public:
|
|
explicit Private() = default;
|
|
|
|
~Private() = default;
|
|
|
|
// buildingId, BuildingOwnership
|
|
QMap<int32_t, CentralEffectSimulation::BuildingOwnership> m_ownershipData;
|
|
|
|
BuildingGeometryCache m_buildingGeometryCache;
|
|
|
|
Map *m_map = nullptr;
|
|
|
|
// MapObjects
|
|
QMap<QPair<int, int>, UniqueMapId> m_bldgFlr2UniqueMapId;
|
|
|
|
bool m_visible = false;
|
|
int m_drawOrder = 0;
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
static QPair<int, int> createKey(int buildingId, int floorId)
|
|
{
|
|
return {buildingId, floorId};
|
|
}
|
|
|
|
static int buildingIdOfKey(const QPair<int, int> &keyData)
|
|
{
|
|
return keyData.first;
|
|
}
|
|
|
|
|
|
RefPointer<Cube> getCreateCube(int buildingId, int floorId)
|
|
{
|
|
const QPair<int, int> &key = createKey(buildingId, floorId);
|
|
const UniqueMapId uniqueMapId = m_bldgFlr2UniqueMapId.value(key, INVALID_MAP_ID);
|
|
|
|
if (!SituationMap::isUniqueMapIdValid(uniqueMapId))
|
|
{
|
|
const QString &layernameForCubes = m_map->layerFactory().layernameForCubes();
|
|
const QString &key_debugInfo = QStringLiteral("bldg_%1-flr%2").arg(buildingId).arg(floorId);
|
|
|
|
RefPointer<Cube> cube =
|
|
m_map->objectFactory().create<Cube>(layernameForCubes, key_debugInfo);
|
|
|
|
if (cube.valid())
|
|
m_bldgFlr2UniqueMapId[key] = cube->uniqueMapId();
|
|
return cube;
|
|
}
|
|
return m_map->objectFactory().get<Cube>(uniqueMapId);
|
|
}
|
|
|
|
void removeCube(int buildingId = -1)
|
|
{
|
|
QMutableMapIterator<QPair<int, int>, UniqueMapId> mapIt(m_bldgFlr2UniqueMapId);
|
|
|
|
while (mapIt.hasNext())
|
|
{
|
|
mapIt.next();
|
|
|
|
const QPair<int, int> &key = mapIt.key();
|
|
int curBuildingId = buildingIdOfKey(key);
|
|
|
|
if ((buildingId < 0) ||
|
|
(buildingId == curBuildingId)
|
|
)
|
|
{
|
|
UniqueMapId uMapId = mapIt.value();
|
|
SituationMap::AbstractMapObject *mapObj = m_map->objectFactory().idToMapObject(uMapId);
|
|
if (mapObj)
|
|
mapObj->removeMySelf();
|
|
|
|
// Will remove that pair from map:
|
|
mapIt.remove();
|
|
}
|
|
} // while
|
|
}
|
|
|
|
bool updateVisibility(int buildingId = -1)
|
|
{
|
|
bool success = true;
|
|
QMutableMapIterator<QPair<int, int>, UniqueMapId> mapIt(m_bldgFlr2UniqueMapId);
|
|
while (mapIt.hasNext())
|
|
{
|
|
mapIt.next();
|
|
|
|
const QPair<int, int> &key = mapIt.key();
|
|
int curBuildingId = buildingIdOfKey(key);
|
|
|
|
if ((buildingId < 0) ||
|
|
(buildingId == curBuildingId)
|
|
)
|
|
{
|
|
UniqueMapId uMapId = mapIt.value();
|
|
SituationMap::AbstractMapObject *mapObj = m_map->objectFactory().idToMapObject(uMapId);
|
|
if (mapObj)
|
|
{
|
|
mapObj->setVisibility(m_visible);
|
|
if (!mapObj->updateView())
|
|
success = false;
|
|
}
|
|
}
|
|
} // while
|
|
return success;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// class BuildingOwnership
|
|
//------------------------------------------------------------------------------
|
|
BuildingOwnership::BuildingOwnership(int drawOrder, QObject *parent)
|
|
: BaseClass(parent)
|
|
, m_logger(getLogger("BuildingOwnership"))
|
|
, d(new Private())
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
|
|
d->m_drawOrder = drawOrder;
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
}
|
|
|
|
BuildingOwnership::~BuildingOwnership()
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
|
|
this->stop();
|
|
// this->removeAllObjects(); // will be done by dtor on Map
|
|
delete d;
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
}
|
|
|
|
bool BuildingOwnership::init(Map *map, PluginProvider *pluginProvider)
|
|
{
|
|
d->m_map = map;
|
|
|
|
if (!d->m_map)
|
|
return false;
|
|
|
|
if (!pluginProvider ||
|
|
!pluginProvider->cesDtc() ||
|
|
!pluginProvider->ismDtc())
|
|
return false;
|
|
|
|
m_cesDtc = pluginProvider->cesDtc();
|
|
m_ismDtc = pluginProvider->ismDtc();
|
|
|
|
d->m_buildingGeometryCache.init(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
void BuildingOwnership::removeAllObjects()
|
|
{
|
|
d->removeCube(); // will remove all stored MapObjects/Cubes
|
|
}
|
|
|
|
bool BuildingOwnership::run()
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
|
|
|
|
if (!d->m_map)
|
|
{
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " no m_map";
|
|
return false;
|
|
}
|
|
|
|
if (!m_cesDtc)
|
|
{
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " no m_cesDtc";
|
|
return false;
|
|
}
|
|
|
|
m_cesDtc->disconnect(this);
|
|
|
|
qRegisterMetaType<CentralEffectSimulation::BuildingOwnership>("CentralEffectSimulation::BuildingOwnership");
|
|
|
|
connect(m_cesDtc.get(), &CentralEffectSimulation::Interface::Dtc::buildingOwnershipChanged,
|
|
this, &BuildingOwnership::onBuildingOwnershipChanged, Qt::QueuedConnection);
|
|
|
|
// get:
|
|
m_cesDtc->buildingOwnerships([this](const CentralEffectSimulation::BuildingOwnership & data)
|
|
{
|
|
onBuildingOwnershipChanged(data, DataModification::eAdded);
|
|
});
|
|
|
|
|
|
// Trigger manually :
|
|
if (false)
|
|
{
|
|
std::function<void(int, int, int)> fTriggerBuilding = [this](int partyId, int buildingId, int floorId)
|
|
{
|
|
// V100.1: Renderingmode::Dynamic -> cube shows height
|
|
// V100.1: Renderingmode::Static -> cube is flat
|
|
|
|
// retrieve geometry:
|
|
const PositionCont &absPositionsGeo = retrieveBuildingGeometry(buildingId, floorId);
|
|
|
|
// retrieve height:
|
|
QMap<int, double> floorHeights = retrieveBuildingAbsHeight(buildingId);
|
|
|
|
// cubes:
|
|
RefPointer<Cube> cube = d->getCreateCube(buildingId, floorId);
|
|
if (!cube)
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " no cube created";
|
|
|
|
cube->setVisibility(d->m_visible);
|
|
cube->setDrawOrder(d->m_drawOrder);
|
|
|
|
cube->setPartyId(partyId); // means: set partyId of owner
|
|
cube->setPositions(absPositionsGeo);
|
|
const double height = floorHeights.value(floorId, 2.5 * floorId);
|
|
cube->setHeight(height);
|
|
|
|
bool success = cube->updateView();
|
|
INFO_LOG(m_logger) << LOGFUNCTION << " cube->updateView " << success;
|
|
};
|
|
|
|
// partyId, bldId, floorid
|
|
fTriggerBuilding(24, 5, 1);
|
|
fTriggerBuilding(-1, 5, 2);
|
|
fTriggerBuilding(89, 5, 3);
|
|
|
|
}
|
|
// : Trigger manually
|
|
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
return true;
|
|
}
|
|
|
|
void BuildingOwnership::stop()
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
|
|
m_cesDtc->disconnect(this);
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
}
|
|
|
|
bool BuildingOwnership::isVisible() const
|
|
{
|
|
return d->m_visible;
|
|
}
|
|
bool BuildingOwnership::setVisible(bool visible)
|
|
{
|
|
d->m_visible = visible;
|
|
bool succ = d->updateVisibility();
|
|
return succ;
|
|
}
|
|
|
|
void BuildingOwnership::onBuildingOwnershipChanged(const CentralEffectSimulation::BuildingOwnership &data, DataModification modType)
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
|
|
|
|
const int buildingId = data.buildingId();
|
|
|
|
switch (modType)
|
|
{
|
|
case DataModification::eAdded:
|
|
case DataModification::eModified:
|
|
// retrieve geometry from ISM and ownership and finally create cubes
|
|
updateBuilding(buildingId);
|
|
break;
|
|
case DataModification::eRemoved:
|
|
// reset ownership -> remove cubes:
|
|
d->removeCube(buildingId);
|
|
break;
|
|
|
|
} // switch
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
}
|
|
|
|
void BuildingOwnership::onResetBuildingOwnership()
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
|
|
d->removeCube();
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
}
|
|
|
|
void BuildingOwnership::updateBuilding(int buildingId)
|
|
{
|
|
// retrieve data for complete building:
|
|
const CentralEffectSimulation::BuildingOwnership &bos = m_cesDtc->buildingOwnership(buildingId);
|
|
|
|
// update own data:
|
|
d->m_ownershipData[buildingId] = bos;
|
|
|
|
const auto &ownerships = bos.ownerships();
|
|
|
|
// accessable by floorId:
|
|
QMap<int, int> floor2Party;
|
|
for (const CentralEffectSimulation::Ownership &ownership : ownerships)
|
|
// BuildingOwnership uses a '0' to indicate an invalid partyId.... :-(
|
|
if (ownership.partyId() > 0)
|
|
floor2Party[ownership.floorId()] = ownership.partyId();
|
|
|
|
|
|
// qCritical() << "floor2Party" << floor2Party;
|
|
|
|
|
|
// retrieve height of all floors:
|
|
const QMap<int, double> &floorHeights = retrieveBuildingAbsHeight(buildingId);
|
|
|
|
|
|
// create/update one Cube (MapObject) per floor:
|
|
QMapIterator<int, double> mapIt(floorHeights);
|
|
int floorCounter = 0;
|
|
while (mapIt.hasNext())
|
|
{
|
|
mapIt.next();
|
|
|
|
// a) floorId
|
|
const int floorId = mapIt.key();
|
|
|
|
// b) height
|
|
const double height = mapIt.value();
|
|
|
|
// c) partyId:
|
|
// set them to "unknown", if not existing:
|
|
const int partyId = floor2Party.value(floorId, -1); // invalid partyId -> unknown -> white color
|
|
|
|
DEBUG_LOG(m_logger) << LOGFUNCTION
|
|
<< " floorId=" << floorId
|
|
<< " height=" << height
|
|
<< " partyId=" << partyId;
|
|
|
|
|
|
// retrieve geometry:
|
|
const PositionCont &absPositionsGeo = retrieveBuildingGeometry(buildingId, ++floorCounter);
|
|
|
|
// cubes
|
|
RefPointer<Cube> cube = d->getCreateCube(buildingId, floorId);
|
|
|
|
cube->setVisibility(d->m_visible);
|
|
cube->setDrawOrder(d->m_drawOrder);
|
|
|
|
cube->setPartyId(partyId); // means: set partyId of owner
|
|
cube->setPositions(absPositionsGeo);
|
|
cube->setHeight(height);
|
|
|
|
if (!cube->updateView())
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " cube->updateView() failed";
|
|
} // while
|
|
}
|
|
|
|
PositionCont BuildingOwnership::retrieveBuildingGeometry(int buildingId, int floorId) const
|
|
{
|
|
return d->m_buildingGeometryCache.retrieveBuildingGeometry(buildingId, floorId);
|
|
}
|
|
|
|
PositionCont BuildingOwnership::retrieveBuildingGeometryInternal(int buildingId, int floorId) const
|
|
{
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START"
|
|
<< " buildingId=" << buildingId
|
|
<< " floorId=" << floorId;
|
|
if (!m_ismDtc)
|
|
{
|
|
ERROR_LOG(m_logger) << LOGFUNCTION << " no m_ismDtc";
|
|
return PositionCont();
|
|
}
|
|
|
|
|
|
//
|
|
// retrieve relative points of footprint:
|
|
//
|
|
QVector<QPointF> relPositions;
|
|
{
|
|
const InfrastructureManagement::MainFootprint &mainFootprint = m_ismDtc->mainFootprint(buildingId);
|
|
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " START"
|
|
<< " mainFootprint.elements().size()=" << mainFootprint.elements().size();
|
|
|
|
relPositions.reserve(mainFootprint.elements().size());
|
|
// sorted per definition
|
|
const auto &fpElements = mainFootprint.elements();
|
|
for (const InfrastructureManagement::FootprintElement &element : fpElements)
|
|
{
|
|
const auto &relPos = element.relPosition();
|
|
relPositions += QPointF(relPos.offsetX(), relPos.offsetY());
|
|
}
|
|
}
|
|
// DEBUG_LOG(m_logger) << "footprintOrg = " << relPositions;
|
|
|
|
//
|
|
// expand the polyline depending on the floor:
|
|
//
|
|
|
|
#ifndef USE_BUFFER_APPROACH
|
|
// calculate the mass/center point of the outline/polygon:
|
|
{
|
|
// assume that there will be not more than 100 floors:
|
|
const double distance = 0.5 + (100 - floorId) * 0.01;
|
|
|
|
const QPolygonF &massPoly = relPositions.toVector();
|
|
const QPointF &massCenterPoint = massPoly.boundingRect().center();
|
|
|
|
// increase distance to massCenterPoint (depending on the quadrant)
|
|
const double massCenterPointX = massCenterPoint.x();
|
|
const double massCenterPointY = massCenterPoint.y();
|
|
QMutableListIterator<QPointF> listIt(relPositions);
|
|
while (listIt.hasNext())
|
|
{
|
|
QPointF &relPosition = listIt.next();
|
|
// X
|
|
if (relPosition.x() < massCenterPointX)
|
|
relPosition.rx() -= distance;
|
|
else
|
|
relPosition.rx() += distance;
|
|
|
|
// Y
|
|
if (relPosition.y() < massCenterPointY)
|
|
relPosition.ry() -= distance;
|
|
else
|
|
relPosition.ry() += distance;
|
|
} // for
|
|
// DEBUG_LOG(m_logger) << "footprintBlown = " << relPositions;
|
|
}
|
|
#endif // USE_BUFFER_APPROACH
|
|
|
|
|
|
//
|
|
// translate to absolute
|
|
//
|
|
PositionCont absPositionsGeo;
|
|
|
|
const InfrastructureManagement::MainStructureElement &mse = m_ismDtc->mainStructureElement(buildingId);
|
|
|
|
const Core::GeoPosition &geoPosition = mse.geoPosition();
|
|
const QPointF &referencePointGeo = QPointF(geoPosition.lon(), geoPosition.lat());
|
|
const LtsCoordTrans::UtmCoordinate referencePointUTM = LtsCoordTrans::UtmCoordinate::fromGeographic(referencePointGeo);
|
|
const double angleRad = mse.angle() * LtsCoordTrans::deg2rad();
|
|
|
|
// add relPos to referencePoint:
|
|
const QPointF center;
|
|
absPositionsGeo.reserve(relPositions.size());
|
|
for (const auto &relPosition : qAsConst(relPositions))
|
|
{
|
|
const QPointF &relPositionRotate = MathUtils::rotate(relPosition, center, angleRad); // rotate relPosition by angleRad around 0,0
|
|
const QPointF &pGeo = referencePointUTM.geographic(relPositionRotate); // add relPosition to referencePointUTM
|
|
absPositionsGeo.append(Point3D::fromQPointF(pGeo));
|
|
} // for
|
|
|
|
// DEBUG_LOG(m_logger) << "absPositionsGeo = " << absPositionsGeo;
|
|
|
|
#ifdef USE_BUFFER_APPROACH
|
|
{
|
|
//
|
|
// This works better for building for building with a shape like a 'H'...
|
|
//
|
|
|
|
// assume that there will be not more than 100 floors:
|
|
const double distance = (100 - floorId) * 0.01;
|
|
|
|
// create EsriGeometry:
|
|
|
|
Esri::ArcGISRuntime::PolygonBuilder polygonBuilder(Esri::ArcGISRuntime::SpatialReference::wgs84());
|
|
constexpr double rad2deg = LtsCoordTrans::rad2deg();
|
|
for (const Point3D &point : qAsConst(absPositionsGeo))
|
|
polygonBuilder.addPoint(Esri::ArcGISRuntime::Point(point.x() * rad2deg,
|
|
point.y() * rad2deg,
|
|
point.z()));
|
|
const Esri::ArcGISRuntime::Geometry &esriGeometry = polygonBuilder.toGeometry();
|
|
|
|
const Esri::ArcGISRuntime::Polygon polyline =
|
|
Esri::ArcGISRuntime::GeometryEngine::bufferGeodetic(esriGeometry,
|
|
distance,
|
|
Esri::ArcGISRuntime::LinearUnitId::Meters,
|
|
0.1,
|
|
Esri::ArcGISRuntime::GeodeticCurveType::Geodesic);
|
|
|
|
if (polyline.parts().isEmpty())
|
|
return absPositionsGeo;
|
|
|
|
const Esri::ArcGISRuntime::ImmutablePart &immutablePart = polyline.parts().part(0);
|
|
const Esri::ArcGISRuntime::ImmutablePointCollection &pointCollection = immutablePart.points();
|
|
const int size = pointCollection.size();
|
|
|
|
// NOTICE_LOG(m_logger) << " pointCollection.size = " << pointCollection.size()
|
|
// << " - buildingId = " << buildingId
|
|
// << ", mse.name = " << mse.name();
|
|
|
|
absPositionsGeo.clear();
|
|
absPositionsGeo.reserve(size);
|
|
|
|
for (const Esri::ArcGISRuntime::Point &esriP : pointCollection)
|
|
{
|
|
absPositionsGeo.append(Point3D(esriP.x() * LtsCoordTrans::deg2rad(),
|
|
esriP.y() * LtsCoordTrans::deg2rad(),
|
|
esriP.z()));
|
|
} // for;
|
|
}
|
|
#endif // USE_BUFFER_APPROACH
|
|
|
|
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
|
|
return absPositionsGeo;
|
|
}
|
|
|
|
QMap<int, double> BuildingOwnership::retrieveBuildingAbsHeight(int buildingId) const
|
|
{
|
|
return d->m_buildingGeometryCache.retrieveBuildingAbsHeight(buildingId);
|
|
}
|
|
|
|
QMap<int, double> BuildingOwnership::retrieveBuildingAbsHeightInternal(int buildingId) const
|
|
{
|
|
QMap<int, double> floorHeights;
|
|
|
|
const InfrastructureManagement::MainStructureElement &mse = m_ismDtc->mainStructureElement(buildingId);
|
|
|
|
// move from dds to QVector:
|
|
for (const InfrastructureManagement::StructureElement &element : mse.elements())
|
|
{
|
|
if (element.structureElementTypeId() != InfrastructureManagement::StructureElementType::Floor)
|
|
continue;
|
|
|
|
const int floorId = element.id();
|
|
|
|
//
|
|
// Cubes are using the UniqueValueRenderer of layer 'BuildingOwnership'.
|
|
// To reduce the dependency to the elevation source, choose
|
|
// ExtrusionMode::AbsoluteHeight @ UniqueValueRenderer
|
|
// Additionally, the top is flat (and not according base surface.)
|
|
//
|
|
// If UniqueValueRenderer uses: Esri::ArcGISRuntime::ExtrusionMode::AbsoluteHeight
|
|
// ExtrusionMode::AbsoluteHeight has a flat top.
|
|
double height = element.geoPosition().z();
|
|
|
|
// If UniqueValueRenderer uses: Esri::ArcGISRuntime::ExtrusionMode::BaseHeight
|
|
// This is the only extrusion mode that doesn't have a flat top.
|
|
// The extruded height, we need the relative,
|
|
// double height = element.relPosition().offsetZ();
|
|
floorHeights[floorId] = height;
|
|
}
|
|
|
|
// this is the height of the bottom, not the ceiling
|
|
// therefore we have take value of topwards neighboring floor:
|
|
{
|
|
QMutableMapIterator<int, double> mapIt(floorHeights);
|
|
while (mapIt.hasNext())
|
|
{
|
|
QMap<int, double>::iterator curFloor = mapIt.next();
|
|
if (mapIt.hasNext())
|
|
{
|
|
QMap<int, double>::iterator upperFloor = mapIt.peekNext(); // neighboring floor
|
|
curFloor.value() = upperFloor.value();
|
|
}
|
|
else
|
|
curFloor.value() += 2.0; // add default height of 2 [meter]
|
|
}
|
|
}
|
|
|
|
return floorHeights;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|