Files
qt5-legatus-legacy/Lts/Plugins/SituationMapPlugin/Gui/SituationMap/AdapterDds/building_ownership.cpp
T
2026-08-13 18:33:10 +02:00

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;
}
}