/** * 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 #include #include #include #include #include #include namespace CollateralDamageSimulation { namespace Calculations { QHash getBuildingFootprints(const QVector &buildingIds, const std::shared_ptr &ismDtc, const QPointF &impactPosition) { QHash 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 &ismDtc) { if (!ismDtc->isMainFootprintEnabled()) { ismDtc->enableMainFootprint(); ismDtc->waitForHistoricalDataMainFootprint(3000); } if (!ismDtc->isMainStructureElementEnabled()) { ismDtc->enableMainStructureElement(); ismDtc->waitForHistoricalDataMainStructureElement(3000); } QPolygonF footprint; // = absolute positions QVector 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 getProtectionAreas(const QHash &buildingFootprints, double radius, const QPointF &impactPosition) { if (isPointInAnyPolygon(buildingFootprints, impactPosition)) return {AreaEffectSimulation::Polygons::circle(impactPosition, radius)}; QPolygonF currentProtectionArea; QVector 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 &participantIds, const QVector &protectionAreas, const QVector &undergroundBuildingIds, const QVector &directlyHitBuildingIds, const QHash &buildingFootprints, const std::shared_ptr &ppmDtc, const std::shared_ptr &ismDtc) { ParticipantProtectionInfo participantProtectionInfo; QHash 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 &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 mergePolygons(const QVector &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 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 getScaledPolygons(const QVector &polygons, double factor) { QVector 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 getUndergroundBuildingIds(const QVector &buildingIds, const std::shared_ptr &ismDtc) { if (!ismDtc->isMainStructureElementEnabled()) { ismDtc->enableMainStructureElement(); ismDtc->waitForHistoricalDataMainStructureElement(3000); } QVector 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 getGeoRect(const QPointF ¢er, double edgeLength) { QList 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 getDirectlyHitBuildings(const QHash &footprints, const QPointF &impactPosition) { const double angleStep = 0.2; const int minimumHitCount = 12; QVector directlyHitBuildingIds; QHash hitBuildingsCount; // 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 &buildings) { QMap distances; // 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