KOSMIndoorMap

scenegeometry.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "scenegeometry_p.h"
8
9#include <QDebug>
10#include <QLineF>
11#include <QPainterPath>
12#include <QPolygonF>
13#include <QRectF>
14
15#include <cmath>
16
17using namespace KOSMIndoorMap;
18
19double SceneGeometry::polylineLength(const QPolygonF &poly)
20{
21 if (poly.size() <= 1) {
22 return 0.0;
23 }
24
25 double lineLength = 0.0;
26 QPointF p1 = poly.at(0);
27 for (auto it = std::next(poly.begin()); it != poly.end(); ++it) {
28 lineLength += QLineF(p1, *it).length();
29 p1 = *it;
30 }
31 return lineLength;
32}
33
34QPointF SceneGeometry::polylineMidPoint(const QPolygonF &poly)
35{
36 const auto lineLength = polylineLength(poly);
37 if (lineLength <= 0.0) {
38 return {};
39 }
40
41 double length = 0.0;
42 auto p1 = poly.at(0);
43 for (auto it = std::next(poly.begin()); it != poly.end(); ++it) {
44 const QLineF line(p1, *it);
45 const auto l = line.length();
46
47 if (length + l < lineLength / 2.0) {
48 length += l;
49 } else {
50 const auto r = ((length + l) - lineLength / 2.0) / l;
51 return line.pointAt(1 - r);
52 }
53
54 p1 = *it;
55 }
56
57 return {};
58}
59
60double SceneGeometry::polylineMidPointAngle(const QPolygonF &path)
61{
62 const auto lineLength = polylineLength(path);
63 if (lineLength <= 0.0) {
64 return 0.0;
65 }
66
67 double length = 0.0;
68 auto p1 = path.at(0);
69 for (auto it = std::next(path.begin()); it != path.end(); ++it) {
70 const QLineF line(p1, *it);
71 const auto l = line.length();
72
73 if (length + l < lineLength / 2.0) {
74 length += l;
75 } else {
76 auto a = - std::remainder(line.angle(), 360.0);
77 if (a < -90.0 || a > 90.0) {
78 a += 180.0;
79 }
80 return a;
81 }
82
83 p1 = *it;
84 }
85
86 return 0.0;
87}
88
89/* the algorithm in here would be pretty simple (see https://en.wikipedia.org/wiki/Polygon#Centroid)
90 * if it weren't for numeric stability. We need something that keeps sufficient precision (~7 digits)
91 * in the range of +/- m * n² for n being the largest coordinate value, and m the polygon size.
92 * To help with that we:
93 * - move the polygon bbox center to 0/0. This works as we usually only look at very small areas.
94 * - scale the value by the bbox size, to enable the use of 64bit integers for the intermediate values.
95 * - use 64 bit integers for the intermediate values, as those contain squares of the coordinates
96 * and thus become very large. As we don't use divisions on the intermediate values, integers work for this.
97 */
98QPointF SceneGeometry::polygonCentroid(const QPolygonF &poly)
99{
100 if (poly.size() < 3) {
101 return {};
102 }
103
104 const auto bbox = poly.boundingRect();
105 const auto offset = bbox.center();
106 const auto scale = 1.0e6 / std::max(bbox.width(), bbox.height());
107
108 int64_t a = 0.0;
109 int64_t cx = 0.0;
110 int64_t cy = 0.0;
111
112 for (int i = 0; i < poly.size(); ++i) {
113 const auto p1 = poly.at(i) - offset;
114 const int64_t x1 = p1.x() * scale;
115 const int64_t y1 = p1.y() * scale;
116
117 const auto p2 = poly.at((i + 1) % poly.size()) - offset;
118 const int64_t x2 = p2.x() * scale;
119 const int64_t y2 = p2.y() * scale;
120
121 a += (x1 * y2) - (x2 * y1);
122 cx += (x1 + x2) * (x1 * y2 - x2 * y1);
123 cy += (y1 + y2) * (x1 * y2 - x2 * y1);
124 }
125
126 if (a == 0) {
127 return {};
128 }
129
130 cx /= 3 * a;
131 cy /= 3 * a;
132
133 return QPointF((double)cx / scale, (double)cy / scale) + offset;
134}
135
136void SceneGeometry::outerPolygonFromPath(const QPainterPath &path, QPolygonF &poly)
137{
138 if (path.isEmpty()) {
139 return;
140 }
141
142 poly.clear();
143 poly.reserve(path.elementCount());
144 poly.push_back(path.elementAt(0));
145 for (int i = 1; i < path.elementCount(); ++i) {
146 const auto e = path.elementAt(i);
147 if (e.type != QPainterPath::LineToElement) {
148 return;
149 }
150 poly.push_back(e);
151 }
152}
153
154double SceneGeometry::distanceToLine(const QLineF &line, QPointF p)
155{
156 const auto len = line.length();
157 if (len == 0.0) {
158 return QLineF(line.p1(), p).length();
159 }
160
161 // project p on a line extending the line segment given by @p line, and clamp to that to the segment
162 const auto r = qBound(0.0, QPointF::dotProduct(p - line.p1(), line.p2() - line.p1()) / (len*len), 1.0);
163 const auto intersection = line.p1() + r * (line.p2() - line.p1());
164 return QLineF(intersection, p).length();
165
166}
167
168bool SceneGeometry::polygonContainsRect(const QPolygonF &polygon, const QRectF &rect)
169{
170 if (!polygon.boundingRect().contains(rect) || polygon.size() < 5 || rect.isEmpty()) {
171 return false;
172 }
173
174 if (!polygon.containsPoint(rect.topLeft(), Qt::OddEvenFill)) {
175 return false;
176 }
177
178 std::array<QLineF, 4> rectLines = { QLineF{ rect.topLeft(), rect.topRight() },
179 QLineF{ rect.topRight(), rect.bottomRight() },
180 QLineF{ rect.bottomRight(), rect.bottomLeft() },
181 QLineF{ rect.bottomLeft(), rect.topLeft() } };
182 for (auto it = std::next(polygon.begin()); it != polygon.end(); ++it) {
183 QLineF line(*std::prev(it), *it);
184 for (const auto rectLine : rectLines) {
185 if (line.intersects(rectLine) == QLineF::BoundedIntersection) {
186 return false;
187 }
188 }
189 }
190
191 return true;
192}
QString path(const QString &relativePath)
OSM-based multi-floor indoor maps for buildings.
BoundedIntersection
IntersectionType intersects(const QLineF &line, QPointF *intersectionPoint) const const
qreal length() const const
QPointF p1() const const
QPointF p2() const const
const_reference at(qsizetype i) const const
iterator begin()
void clear()
iterator end()
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
qreal dotProduct(const QPointF &p1, const QPointF &p2)
qreal x() const const
qreal y() const const
QRectF boundingRect() const const
bool containsPoint(const QPointF &point, Qt::FillRule fillRule) const const
QPointF bottomLeft() const const
QPointF bottomRight() const const
QPointF center() const const
bool contains(const QPointF &point) const const
bool isEmpty() const const
QPointF topLeft() const const
QPointF topRight() const const
const QChar at(qsizetype position) const const
iterator begin()
iterator end()
bool isEmpty() const const
OddEvenFill
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:06:15 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.