/** * @RDE_HEADER@ * Interface * $Id$ * $HeadURL$ * $Author$ */ // Own #include "building_contamination.h" #include #include #include #include #include #include #define USE_BUFFER_APPROACH #ifdef USE_BUFFER_APPROACH # include # include #endif // USE_BUFFER_APPROACH namespace SituationMap { //------------------------------------------------------------------------------ class BuildingContamination::Private { public: explicit Private() = default; ~Private() = default; // buildingId, BuildingContamination QMap m_buildingStateData; Map *m_map = nullptr; // MapObjects QMap, UniqueMapId> m_bldgFlr2UniqueMapId; bool m_visible = false; int m_drawOrder = 0; //------------------------------------------------------------------------------ static QPair createKey(int buildingId, int floorId) { return {buildingId, floorId}; } static int buildingIdOfKey(const QPair &keyData) { return keyData.first; } RefPointer getCreateCube(int buildingId, int floorId) { const QPair &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 = m_map->objectFactory().create(layernameForCubes, key_debugInfo); if (cube.valid()) m_bldgFlr2UniqueMapId[key] = cube->uniqueMapId(); return cube; } return m_map->objectFactory().get(uniqueMapId); } void removeCube(int buildingId = -1) { QMutableMapIterator, UniqueMapId> mapIt(m_bldgFlr2UniqueMapId); while (mapIt.hasNext()) { mapIt.next(); const QPair &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, UniqueMapId> mapIt(m_bldgFlr2UniqueMapId); while (mapIt.hasNext()) { mapIt.next(); const QPair &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"); 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 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 floorHeights = retrieveBuildingAbsHeight(buildingId); // cubes: RefPointer 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 contaminatedFloors; for (const Buildings::ContaminationState &contaminationState : contaminations) { //we might get multiple contaminations so first make a Set of rooms: QSet 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 floorHeights = retrieveBuildingAbsHeight(buildingId); // create/update one Cube (MapObject) per floor: QMapIterator 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 = 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 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 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 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 BuildingContamination::retrieveBuildingAbsHeight(int buildingId) const { QMap 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 mapIt(floorHeights); while (mapIt.hasNext()) { QMap::iterator curFloor = mapIt.next(); if (mapIt.hasNext()) { QMap::iterator upperFloor = mapIt.peekNext(); // neighboring floor curFloor.value() = upperFloor.value(); } else curFloor.value() += 2.0; // add default height of 2 [meter] } } return floorHeights; } }