558 lines
18 KiB
C++
Executable File
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 ¤tFootprint : 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 ¢er, const QPointF &point, const QPolygonF &polygon)
|
|
{
|
|
for (int i = 0; i < polygon.size(); ++i)
|
|
{
|
|
const QPointF ¤tPoint = 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 ¢er, 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 ¤tPosition = 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 ¤tProtectionArea : 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 ¤tPolygon = 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 ¤tPolygon : polygons)
|
|
{
|
|
scaledPolygon.reserve(currentPolygon.size());
|
|
|
|
for (const QPointF ¤tPoint : 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 ¢er, 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 ¤tPolygon = 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
|