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 }