Files
qt5-legatus-legacy/Lts/Plugins/CollateralDamageSimulation/Gui/CreateSimulation/cds_calculations.cpp
T
2026-08-13 18:33:10 +02:00

558 lines
18 KiB
C++
Executable File

/**
* Namespace for calculations, needed by CollateralDamageSimulation
* @RDE_HEADER@
* $Id: ppm_msdl_edit.h 52239 2020-10-05 11:33:12Z thoden $
* $HeadURL: svn://192.168.133.21/TDS/branches/product/features/3dws_devel/Lts/Plugins/ParticipantManagement/Gui/MSDL/ppm_msdl_edit.h $
* $Author: thoden $
* Created by Hannes Fritsche, ESZS, 2024-09-03
*/
#include "cds_calculations.h"
#include <InfrastructureManagement/ism_dtc.h>
#include <ParticipantManagement/ppm_dtc.h>
#include <LtsCoordTrans/lts_coord_trans.h>
#include <QPolygonF>
#include <QLineF>
#include <MathUtils/simple_helper.h>
#include <AreaEffectSimulation/Utils/polygons.h>
namespace CollateralDamageSimulation
{
namespace Calculations
{
QHash<int, QPolygonF> getBuildingFootprints(const QVector<int> &buildingIds, const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc, const QPointF &impactPosition)
{
QHash<int, QPolygonF> footprints;
if (buildingIds.isEmpty())
return footprints;
footprints.reserve(buildingIds.size());
for (int buildingId : buildingIds)
{
const QPolygonF &buildingFootprint = getBuildingFootprint(buildingId, ismDtc);
// if the impact position is within a building footprint, the building has to be ignored
if (buildingFootprint.containsPoint(impactPosition, Qt::OddEvenFill))
continue;
footprints.insert(buildingId, buildingFootprint);
}
return footprints;
}
QPolygonF getBuildingFootprint(int buildingId, const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc)
{
if (!ismDtc->isMainFootprintEnabled())
{
ismDtc->enableMainFootprint();
ismDtc->waitForHistoricalDataMainFootprint(3000);
}
if (!ismDtc->isMainStructureElementEnabled())
{
ismDtc->enableMainStructureElement();
ismDtc->waitForHistoricalDataMainStructureElement(3000);
}
QPolygonF footprint; // = absolute positions
QVector<QPointF> relPositions;
const InfrastructureManagement::MainFootprint &mainFootprint = ismDtc->mainFootprint(buildingId);
relPositions.reserve(mainFootprint.elements().size());
for (const InfrastructureManagement::FootprintElement &element : mainFootprint.elements())
{
const Core::RelativePosition &relPos = element.relPosition();
relPositions.append(QPointF(relPos.offsetX(), relPos.offsetY()));
}
const InfrastructureManagement::MainStructureElement &mainStructureElement = ismDtc->mainStructureElement(buildingId);
const Core::GeoPosition &geoPosition = mainStructureElement.geoPosition();
const double angleRad = mainStructureElement.angle() * LtsCoordTrans::deg2rad();
const QPointF referencePointGeo(geoPosition.lon(), geoPosition.lat());
const LtsCoordTrans::UtmCoordinate referencePointUTM = LtsCoordTrans::UtmCoordinate::fromGeographic(referencePointGeo);
const QPointF center;
footprint.reserve(relPositions.size());
for (const auto &relPosition : qAsConst(relPositions))
{
const QPointF relPositionRotate = SituationMap::MathUtils::rotate(relPosition, center, angleRad);
const QPointF geoPositionRotate = referencePointUTM.geographic(relPositionRotate);
footprint.append(geoPositionRotate);
}
return footprint;
}
QVector<QPolygonF> getProtectionAreas(const QHash<int, QPolygonF> &buildingFootprints, double radius, const QPointF &impactPosition)
{
if (isPointInAnyPolygon(buildingFootprints, impactPosition))
return {AreaEffectSimulation::Polygons::circle(impactPosition, radius)};
QPolygonF currentProtectionArea;
QVector<QPolygonF> protectionAreas;
protectionAreas.reserve(buildingFootprints.size());
for (const QPolygonF &currentFootprint : buildingFootprints)
{
currentProtectionArea.clear();
currentProtectionArea.reserve(currentFootprint.size() + 360);
// take the visible points from the center
for (const QPointF currentPoint : currentFootprint)
{
if (isPointVisible(impactPosition, currentPoint, currentFootprint))
currentProtectionArea.append(currentPoint);
}
// order the points by angle
// each of the two sorting algorithms has a weakness if a point is present which is either directly left or right
// of the impact position, so we have to choose one
if (currentProtectionArea.boundingRect().center().x() < impactPosition.x())
{
// does not sort properly if there is a point directly right to center point
std::sort(currentProtectionArea.begin(), currentProtectionArea.end(),
[&impactPosition](const QPointF &a, const QPointF &b)
{
double angleA = QLineF(impactPosition, a).angle();
double angleB = QLineF(impactPosition, b).angle();
return angleA > angleB;
});
}
else
{
// does not sort properly if there is a point directly left to center point
std::sort(currentProtectionArea.begin(), currentProtectionArea.end(), [impactPosition](const QPointF &a, const QPointF &b)
{
double angleA = std::atan2(a.y() - impactPosition.y(), a.x() - impactPosition.x());
double angleB = std::atan2(b.y() - impactPosition.y(), b.x() - impactPosition.x());
return angleA < angleB;
});
}
addOuterBounds(currentProtectionArea, impactPosition, radius);
protectionAreas.append(currentProtectionArea);
}
return mergePolygons(protectionAreas);
}
bool isPointVisible(const QPointF &center, const QPointF &point, const QPolygonF &polygon)
{
for (int i = 0; i < polygon.size(); ++i)
{
const QPointF &currentPoint = polygon.at(i);
const QPointF &nextPoint = polygon.at((i + 1) % polygon.size());
if (point == currentPoint || point == nextPoint)
continue;
if (doLinesIntersect(center, point, currentPoint, nextPoint))
return false;
}
return true;
}
bool doLinesIntersect(const QPointF &a1, const QPointF &a2, const QPointF &b1, const QPointF &b2)
{
auto ccw = [](const QPointF &A, const QPointF &B, const QPointF &C)
{
return ((C.y() - A.y()) * (B.x() - A.x())) > ((B.y() - A.y()) * (C.x() - A.x()));
};
return ccw(a1, b1, b2) != ccw(a2, b1, b2) && ccw(a1, a2, b1) != ccw(a1, a2, b2);
}
void addOuterBounds(QPolygonF &polygon, const QPointF &center, double radius)
{
QPolygonF circle = AreaEffectSimulation::Polygons::circle(center, radius);
LtsCoordTrans::UtmCoordinate startUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(getStartPosForOuterBounds(polygon, circle));
LtsCoordTrans::UtmCoordinate endUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(getEndPosForOuterBounds(polygon, circle));
LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(center);
double startRad = std::atan2(startUtm.y - centerUtm.y, startUtm.x - centerUtm.x);
double endRad = std::atan2(endUtm.y - centerUtm.y, endUtm.x - centerUtm.x);
double angleStep = 5.0 * M_PI / 180.0;
if (startRad > endRad)
endRad += 2 * M_PI;
for (double angle = startRad; angle < endRad; angle += angleStep)
{
double x = centerUtm.x + radius * cos(angle);
double y = centerUtm.y + radius * sin(angle);
polygon.prepend(LtsCoordTrans::UtmCoordinate(centerUtm.zone, x, y).geographic());
}
if (endRad > startRad && fmod(endRad - startRad, angleStep) != 0)
{
double x = centerUtm.x + radius * cos(endRad);
double y = centerUtm.y + radius * sin(endRad);
polygon.prepend(LtsCoordTrans::UtmCoordinate(centerUtm.zone, x, y).geographic());
}
}
ParticipantProtectionInfo getParticipantProtectionInfo(const QVector<int> &participantIds, const QVector<QPolygonF> &protectionAreas, const QVector<int> &undergroundBuildingIds, const QVector<int> &directlyHitBuildingIds, const QHash<int, QPolygonF> &buildingFootprints, const std::shared_ptr<ParticipantManagement::Interface::Dtc> &ppmDtc, const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc)
{
ParticipantProtectionInfo participantProtectionInfo;
QHash<int, QPolygonF> directlyHitBuildingFootprints;
directlyHitBuildingFootprints.reserve(directlyHitBuildingIds.size());
for (int buildingId : directlyHitBuildingIds)
directlyHitBuildingFootprints.insert(buildingId, buildingFootprints.value(buildingId));
participantProtectionInfo.withoutProtection.reserve(participantIds.size());
participantProtectionInfo.withProtection.reserve(participantIds.size());
participantProtectionInfo.participantsInUnderground.reserve(participantIds.size());
if (!ppmDtc->isParticipantPositionEnabled())
{
ppmDtc->enableParticipantPosition();
ppmDtc->waitForHistoricalDataParticipantPosition(3000);
}
if (!ismDtc->isMainStructureElementEnabled())
{
ismDtc->enableMainStructureElement();
ismDtc->waitForHistoricalDataMainStructureElement(3000);
}
for (int currentParticipantId : participantIds)
{
const ParticipantManagement::ParticipantPosition &currentPosition = ppmDtc->participantPosition(currentParticipantId);
if (undergroundBuildingIds.contains(currentPosition.buildingId()))
{
participantProtectionInfo.participantsInUnderground.append(currentParticipantId);
continue;
}
if (directlyHitBuildingIds.contains(currentPosition.buildingId()))
{
participantProtectionInfo.withoutProtection.append(currentParticipantId);
continue;
}
const QPointF geoPosition(currentPosition.pos().lon(), currentPosition.pos().lat());
if (isPointInAnyPolygon(directlyHitBuildingFootprints, geoPosition))
{
participantProtectionInfo.withoutProtection.append(currentParticipantId);
continue;
}
// participants in buildings are always protected
if (currentPosition.roomId() != 0)
{
participantProtectionInfo.withProtection.append(currentParticipantId);
continue;
}
// check if a participant is in a protection area
for (const QPolygonF &currentProtectionArea : protectionAreas)
{
if (currentProtectionArea.containsPoint(geoPosition, Qt::OddEvenFill))
{
participantProtectionInfo.withProtection.append(currentParticipantId);
break;
}
}
if (participantProtectionInfo.withProtection.contains(currentParticipantId))
continue;
// no protection
participantProtectionInfo.withoutProtection.append(currentParticipantId);
}
participantProtectionInfo.withoutProtection.squeeze();
participantProtectionInfo.withProtection.squeeze();
participantProtectionInfo.participantsInUnderground.squeeze();
return participantProtectionInfo;
}
QPointF getStartPosForOuterBounds(const QPolygonF &polygon, const QPolygonF &circle)
{
if (circle.containsPoint(polygon.first(), Qt::OddEvenFill))
return polygon.first();
QPointF firstInnerPoint = polygon.first();
int index = 0;
while (!circle.containsPoint(firstInnerPoint, Qt::OddEvenFill))
{
if (index >= polygon.size() - 1)
return QPointF();
firstInnerPoint = polygon.at(++index);
}
QPointF lastOuterPoint = polygon.at(index - 1);
QLineF intersectionLine(firstInnerPoint, lastOuterPoint);
return firstIntersection(intersectionLine, circle);
}
QPointF getEndPosForOuterBounds(const QPolygonF &polygon, const QPolygonF &circle)
{
if (circle.containsPoint(polygon.last(), Qt::OddEvenFill))
return polygon.last();
QPointF firstInnerPoint = polygon.last();
int index = polygon.size();
while (!circle.containsPoint(firstInnerPoint, Qt::OddEvenFill))
{
if (index <= 0)
return QPointF();
firstInnerPoint = polygon.at(--index);
}
QPointF lastOuterPoint = polygon.at(index + 1);
QLineF intersectionLine(firstInnerPoint, lastOuterPoint);
return firstIntersection(intersectionLine, circle);
}
QPointF firstIntersection(const QLineF &line, const QPolygonF &circle)
{
QPointF prevPoint = circle.last();
QPointF intersectionPoint;
for (int index = 0; index < circle.size(); ++index)
{
const QLineF edge(prevPoint, circle.at(index));
if (edge.intersects(line, &intersectionPoint) == QLineF::BoundedIntersection)
return intersectionPoint;
prevPoint = circle.at(index);
}
return QPointF();
}
bool isPointInAnyPolygon(const QHash<int, QPolygonF> &polygons, const QPointF &point)
{
for (auto it = polygons.constBegin(); it != polygons.constEnd(); ++it)
{
const QPolygonF &currentPolygon = it.value();
if (currentPolygon.containsPoint(point, Qt::OddEvenFill))
return true;
}
return false;
}
QVector<QPolygonF> mergePolygons(const QVector<QPolygonF> &geoPolygons)
{
if (geoPolygons.size() <= 1)
return geoPolygons;
const double scaleFactor = 100000.0;
// since geocoordinates are very small the result of QPolygonF::united is very imprecise.
// the polygons therefore must be scaled up and later be scaled down again
QVector<QPolygonF> mergedPolygons = getScaledPolygons(geoPolygons,scaleFactor);
bool hasIntersection = true;
while (hasIntersection)
{
hasIntersection = false;
for (int outerIndex = 0; outerIndex < mergedPolygons.size(); ++outerIndex)
{
for (int innerIndex = outerIndex + 1; innerIndex < mergedPolygons.size(); ++innerIndex)
{
if (mergedPolygons.at(outerIndex).intersects(mergedPolygons.at(innerIndex)))
{
const QPolygonF mergedPolygon = mergedPolygons.at(outerIndex).united(mergedPolygons.at(innerIndex));
mergedPolygons[outerIndex] = mergedPolygon;
mergedPolygons.removeAt(innerIndex);
hasIntersection = true;
break;
}
if (hasIntersection)
break;
}
}
}
return getScaledPolygons(mergedPolygons, 1.0 / scaleFactor);
}
QVector<QPolygonF> getScaledPolygons(const QVector<QPolygonF> &polygons, double factor)
{
QVector<QPolygonF> scaledPolygons;
QPolygonF scaledPolygon;
scaledPolygons.reserve(polygons.size());
for (const QPolygonF &currentPolygon : polygons)
{
scaledPolygon.reserve(currentPolygon.size());
for (const QPointF &currentPoint : currentPolygon)
scaledPolygon.append(currentPoint * factor);
scaledPolygons.append(scaledPolygon);
scaledPolygon.clear();
}
return scaledPolygons;
}
QVector<int> getUndergroundBuildingIds(const QVector<int> &buildingIds, const std::shared_ptr<InfrastructureManagement::Interface::Dtc> &ismDtc)
{
if (!ismDtc->isMainStructureElementEnabled())
{
ismDtc->enableMainStructureElement();
ismDtc->waitForHistoricalDataMainStructureElement(3000);
}
QVector<int> undergroundBuildingIds;
undergroundBuildingIds.reserve(buildingIds.size());
// since there is no indicator for underground buildings, we have to parse the buildings name and description
// --> known underground buildings area Kanalisation and U-Bahn Tunnel
const QString kanal = QStringLiteral("kanaele");
const QString uBahn = QStringLiteral("bahn");
for (int currentBuildingId : buildingIds)
{
const InfrastructureManagement::MainStructureElement &building = ismDtc->mainStructureElement(currentBuildingId);
const QString description = QString::fromStdString(building.description());
const QString name = QString::fromStdString(building.name());
if (!(description.contains(kanal, Qt::CaseInsensitive) ||
description.contains(uBahn, Qt::CaseInsensitive) ||
name.contains(kanal, Qt::CaseInsensitive) ||
name.contains(uBahn, Qt::CaseInsensitive)))
continue;
undergroundBuildingIds.append(currentBuildingId);
}
return undergroundBuildingIds;
}
QList<QPointF> getGeoRect(const QPointF &center, double edgeLength)
{
QList<QPointF> geoRect;
const LtsCoordTrans::UtmCoordinate utmCenter = LtsCoordTrans::UtmCoordinate::fromGeographic(center.x(), center.y());
const QPointF topLeft(utmCenter.geographic(-(edgeLength / 2.0), edgeLength / 2.0));
const QPointF topRight(utmCenter.geographic(edgeLength / 2.0, edgeLength / 2.0));
const QPointF bottomRight(utmCenter.geographic(edgeLength / 2.0, -(edgeLength / 2.0)));
const QPointF bottomLeft(utmCenter.geographic(-(edgeLength / 2.0), -(edgeLength / 2.0)));
geoRect.reserve(4);
geoRect.append(topLeft);
geoRect.append(topRight);
geoRect.append(bottomRight);
geoRect.append(bottomLeft);
return geoRect;
}
QVector<int> getDirectlyHitBuildings(const QHash<int, QPolygonF> &footprints, const QPointF &impactPosition)
{
const double angleStep = 0.2;
const int minimumHitCount = 12;
QVector<int> directlyHitBuildingIds;
QHash<int, int> hitBuildingsCount; // <buildingId, hit count>
directlyHitBuildingIds.reserve(footprints.size());
QLineF ray(impactPosition, {impactPosition.x() + 1.0, impactPosition.y()});
for (double currentAngle = 0.0; currentAngle < 360; currentAngle += angleStep)
{
ray.setAngle(currentAngle);
int buildingId = getNearestBlockedBuildingId(ray, footprints);
if (buildingId == -1)
continue;
if (!hitBuildingsCount.contains(buildingId))
hitBuildingsCount.insert(buildingId, 0);
++hitBuildingsCount[buildingId];
}
for (auto it = hitBuildingsCount.constBegin(); it != hitBuildingsCount.constEnd(); ++it)
{
if (it.value() >= minimumHitCount)
directlyHitBuildingIds.append(it.key());
}
directlyHitBuildingIds.squeeze();
return directlyHitBuildingIds;
}
int getNearestBlockedBuildingId(const QLineF &ray, const QHash<int, QPolygonF> &buildings)
{
QMap<double, int> distances; // <distance, buildingId>
for (auto it = buildings.constBegin(); it != buildings.constEnd(); ++it)
{
const QPolygonF &currentPolygon = it.value();
for (int i = 0; i < currentPolygon.count(); ++i)
{
const QPointF p1 = currentPolygon.at(i);
const QPointF p2 = currentPolygon.at((i + 1) % currentPolygon.count());
const QLineF currentEdge(p1, p2);
QPointF intersectionPoint;
if (ray.intersects(currentEdge, &intersectionPoint) != QLineF::BoundedIntersection)
continue;
QLineF distanceLine(ray.p1(), intersectionPoint);
distances.insert(distanceLine.length(), it.key());
}
}
if (distances.isEmpty())
return -1;
return distances.first();
}
} // namespace Calculations
} // namespace CollateralDamageSimulation