File indexing completed on 2024-12-22 04:27:03

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "scenegeometry_p.h"
0008 
0009 #include <QDebug>
0010 #include <QLineF>
0011 #include <QPainterPath>
0012 #include <QPolygonF>
0013 
0014 #include <cmath>
0015 
0016 using namespace KOSMIndoorMap;
0017 
0018 double SceneGeometry::polylineLength(const QPolygonF &poly)
0019 {
0020     if (poly.size() <= 1) {
0021         return 0.0;
0022     }
0023 
0024     double lineLength = 0.0;
0025     QPointF p1 = poly.at(0);
0026     for (auto it = std::next(poly.begin()); it != poly.end(); ++it) {
0027         lineLength += QLineF(p1, *it).length();
0028         p1 = *it;
0029     }
0030     return lineLength;
0031 }
0032 
0033 QPointF SceneGeometry::polylineMidPoint(const QPolygonF &poly)
0034 {
0035     const auto lineLength = polylineLength(poly);
0036     if (lineLength <= 0.0) {
0037         return {};
0038     }
0039 
0040     double length = 0.0;
0041     auto p1 = poly.at(0);
0042     for (auto it = std::next(poly.begin()); it != poly.end(); ++it) {
0043         const QLineF line(p1, *it);
0044         const auto l = line.length();
0045 
0046         if (length + l < lineLength / 2.0) {
0047             length += l;
0048         } else {
0049             const auto r = ((length + l) - lineLength / 2.0) / l;
0050             return line.pointAt(1 - r);
0051         }
0052 
0053         p1 = *it;
0054     }
0055 
0056     return {};
0057 }
0058 
0059 double SceneGeometry::polylineMidPointAngle(const QPolygonF &path)
0060 {
0061     const auto lineLength = polylineLength(path);
0062     if (lineLength <= 0.0) {
0063         return 0.0;
0064     }
0065 
0066     double length = 0.0;
0067     auto p1 = path.at(0);
0068     for (auto it = std::next(path.begin()); it != path.end(); ++it) {
0069         const QLineF line(p1, *it);
0070         const auto l = line.length();
0071 
0072         if (length + l < lineLength / 2.0) {
0073             length += l;
0074         } else {
0075             auto a = - std::remainder(line.angle(), 360.0);
0076             if (a < -90.0 || a > 90.0) {
0077                 a += 180.0;
0078             }
0079             return a;
0080         }
0081 
0082         p1 = *it;
0083     }
0084 
0085     return 0.0;
0086 }
0087 
0088 /* the algorithm in here would be pretty simple (see https://en.wikipedia.org/wiki/Polygon#Centroid)
0089  * if it weren't for numeric stability. We need something that keeps sufficient precision (~7 digits)
0090  * in the range of +/- m * n² for n being the largest coordinate value, and m the polygon size.
0091  * To help with that we:
0092  * - move the polygon bbox center to 0/0. This works as we usually only look at very small areas.
0093  * - scale the value by the bbox size, to enable the use of 64bit integers for the intermediate values.
0094  * - use 64 bit integers for the intermediate values, as those contain squares of the coordinates
0095  *   and thus become very large. As we don't use divisions on the intermediate values, integers work for this.
0096  */
0097 QPointF SceneGeometry::polygonCentroid(const QPolygonF &poly)
0098 {
0099     if (poly.size() < 3) {
0100         return {};
0101     }
0102 
0103     const auto bbox = poly.boundingRect();
0104     const auto offset = bbox.center();
0105     const auto scale = 1.0e6 / std::max(bbox.width(), bbox.height());
0106 
0107     int64_t a = 0.0;
0108     int64_t cx = 0.0;
0109     int64_t cy = 0.0;
0110 
0111     for (int i = 0; i < poly.size(); ++i) {
0112         const auto p1 = poly.at(i) - offset;
0113         const int64_t x1 = p1.x() * scale;
0114         const int64_t y1 = p1.y() * scale;
0115 
0116         const auto p2 = poly.at((i + 1) % poly.size()) - offset;
0117         const int64_t x2 = p2.x() * scale;
0118         const int64_t y2 = p2.y() * scale;
0119 
0120         a += (x1 * y2) - (x2 * y1);
0121         cx += (x1 + x2) * (x1 * y2 - x2 * y1);
0122         cy += (y1 + y2) * (x1 * y2 - x2 * y1);
0123     }
0124 
0125     if (a == 0) {
0126         return {};
0127     }
0128 
0129     cx /= 3 * a;
0130     cy /= 3 * a;
0131 
0132     return QPointF((double)cx / scale, (double)cy / scale) + offset;
0133 }
0134 
0135 void SceneGeometry::outerPolygonFromPath(const QPainterPath &path, QPolygonF &poly)
0136 {
0137     if (path.isEmpty()) {
0138         return;
0139     }
0140 
0141     poly.clear();
0142     poly.reserve(path.elementCount());
0143     poly.push_back(path.elementAt(0));
0144     for (int i = 1; i < path.elementCount(); ++i) {
0145         const auto e = path.elementAt(i);
0146         if (e.type != QPainterPath::LineToElement) {
0147             return;
0148         }
0149         poly.push_back(e);
0150     }
0151 }
0152 
0153 double SceneGeometry::distanceToLine(const QLineF &line, QPointF p)
0154 {
0155     const auto len = line.length();
0156     if (len == 0.0) {
0157         return QLineF(line.p1(), p).length();
0158     }
0159 
0160     // project p on a line extending the line segment given by @p line, and clamp to that to the segment
0161     const auto r = qBound(0.0, QPointF::dotProduct(p - line.p1(), line.p2() - line.p1()) / (len*len), 1.0);
0162     const auto intersection = line.p1() + r * (line.p2() - line.p1());
0163     return QLineF(intersection, p).length();
0164 
0165 }