Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

@@ -0,0 +1,664 @@
/**
* @RDE_HEADER@
* Interface
* $Id$
* $HeadURL$
* $Author$
*/
// Own
#include "building_contamination.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 <functional>
#define USE_BUFFER_APPROACH
#ifdef USE_BUFFER_APPROACH
# include <arcgis/PolygonBuilder.h>
# include <arcgis/GeometryEngine.h>
#endif // USE_BUFFER_APPROACH
namespace SituationMap
{
//------------------------------------------------------------------------------
class BuildingContamination::Private
{
public:
explicit Private() = default;
~Private() = default;
// buildingId, BuildingContamination
QMap<int32_t, Buildings::BuildingState> m_buildingStateData;
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 BuildingContamination
//------------------------------------------------------------------------------
BuildingContamination::BuildingContamination(int drawOrder, QObject *parent)
: BaseClass(parent)
, m_logger(getLogger("BuildingContamination"))
, d(new Private())
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
d->m_drawOrder = drawOrder;
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
BuildingContamination::~BuildingContamination()
{
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 BuildingContamination::init(Map *map, PluginProvider *pluginProvider)
{
d->m_map = map;
if (!d->m_map)
return false;
if (!pluginProvider ||
!pluginProvider->cesDtc() ||
!pluginProvider->ismDtc())
return false;
m_bldDtc = pluginProvider->bldDtc();
m_ismDtc = pluginProvider->ismDtc();
return true;
}
void BuildingContamination::removeAllObjects()
{
d->removeCube(); // will remove all stored MapObjects/Cubes
}
bool BuildingContamination::run()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
if (!d->m_map)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " no m_map";
return false;
}
if (!m_bldDtc)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " no m_bldDtc";
return false;
}
m_bldDtc->disconnect(this);
qRegisterMetaType<Buildings::BuildingState>("Buildings::BuildingState");
connect(m_bldDtc.get(), &Buildings::Interface::Dtc::buildingStateChanged,
this, &BuildingContamination::onBuildingStateChanged, Qt::QueuedConnection);
// get:
m_bldDtc->buildingStates([this](const Buildings::BuildingState & data)
{
onBuildingStateChanged(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 BuildingContamination::stop()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
m_bldDtc->disconnect(this);
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
bool BuildingContamination::isVisible() const
{
return d->m_visible;
}
bool BuildingContamination::setVisible(bool visible)
{
d->m_visible = visible;
bool succ = d->updateVisibility();
return succ;
}
void BuildingContamination::onBuildingStateChanged(const Buildings::BuildingState &data, DataModification modType)
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
const int buildingId = data.id();
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 BuildingContamination::onResetBuildingState()
{
TRACE_LOG(m_logger) << LOGFUNCTION << " START";
d->removeCube();
TRACE_LOG(m_logger) << LOGFUNCTION << " END";
}
void BuildingContamination::updateBuilding(int buildingId)
{
DEBUG_LOG(m_logger) << "update Building " << buildingId;
// retrieve data for complete building:
const Buildings::BuildingState &bos = m_bldDtc->buildingState(buildingId);
// update own data:
d->m_buildingStateData[buildingId] = bos;
const auto contaminations = bos.contaminationStates();
DEBUG_LOG(m_logger) << "Statecount " << contaminations.size();
// accessable by floorId:
QSet<int> contaminatedFloors;
for (const Buildings::ContaminationState &contaminationState : contaminations)
{
//we might get multiple contaminations so first make a Set of rooms:
QSet<int> rooms;
for (const auto &room : contaminationState.rooms())
{
rooms.insert(room);
}
//now check for floors
DEBUG_LOG(m_logger) << "contaminated rooms " << rooms.size();
for (int room : rooms)
{
int floorIdContamination = findFloorIdByRoomId(room);
if ( floorIdContamination == -1)
continue;
contaminatedFloors.insert(floorIdContamination);
}
}
DEBUG_LOG(m_logger) << "contaminated floors " << contaminatedFloors.size();
// qCritical() << "floor2Party" << floor2Party;
// retrieve height of all floors:
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) contamination:
// set them to "false", if not existing:
const bool isContaminaed = contaminatedFloors.contains(floorId);
DEBUG_LOG(m_logger) << LOGFUNCTION
<< " floorId=" << floorId
<< " height=" << height
<< " partyId=" << isContaminaed;
// 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(-1);
if(isContaminaed)
{
cube->setPartyId(-2);
QColor col(Qt::yellow);
cube->setForceMajorColor(col.name());
}
cube->setPositions(absPositionsGeo);
cube->setHeight(height);
if (!cube->updateView())
ERROR_LOG(m_logger) << LOGFUNCTION << " cube->updateView() failed";
} // while
}
//void BuildingContamination::updateBuildingVisiblity(int buildingId)
//{
//}
int BuildingContamination::findFloorIdByRoomId(int roomId) noexcept(false)
{
// get the floor by getting the parent of the room
auto roomToFloorCache = m_roomToFloorCache.constFind(roomId); // try to find the floor in the cache first
if (roomToFloorCache != m_roomToFloorCache.constEnd())
{
//qWarning() << ":) cache hit for room" << roomId;
return roomToFloorCache.value();
}
//qWarning() << ":( cache miss for room" << roomId;
InfrastructureManagement::StructureElement roomSe;
if (!m_ismDtc->getStructureElementById(roomId, roomSe))
{
ERROR_LOG(m_logger) << "getStructureElementById roomId=" << roomId << " not found";
return -1;
}
const std::vector<int32_t> path = roomSe.path();
for (const int parentSeId : path)
{
InfrastructureManagement::StructureElement parentSe;
if (!getRoom(parentSeId, parentSe))
continue;
//qWarning() << "seId" << seId << "type" << structureElement2.structureElementTypeId().underlying();
if (parentSe.structureElementTypeId().underlying() != InfrastructureManagement::StructureElementType_def::Floor)
continue;
// there can only be one floor per room (duh), so we can break after that and finally know the se id for the floor the participant is in
m_roomToFloorCache.insert(roomId, parentSeId);
return parentSeId;
}
throw QStringLiteral("findFloorId: floor id for room %1 not found").arg(roomSe.id());
}
bool BuildingContamination::getRoom(int roomSeId, InfrastructureManagement::StructureElement &roomSe)
{
auto it = m_roomStructureElementCache.find(roomSeId);
if (it != m_roomStructureElementCache.end())
{
//qWarning() << "getRoom: cache hit!";
roomSe = *it;
return true;
}
if (m_ismDtc->getStructureElementById(roomSeId, roomSe))
{
//qWarning() << "getRoom: cache miss - adding to cache";
m_roomStructureElementCache.insert(roomSeId, roomSe);
return true;
}
return false; // not found :((((
}
PositionCont BuildingContamination::retrieveBuildingGeometry(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
for (const InfrastructureManagement::FootprintElement &element : mainFootprint.elements())
{
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();
INFO_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> BuildingContamination::retrieveBuildingAbsHeight(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 'BuildingContamination'.
// 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;
}
}