Commit eca22a54 authored by Volker Krause's avatar Volker Krause
Browse files

Implement pole of inaccessibility algorithm

We use this for placing labels on polygons now, which ensures they are
always placed inside, even on concave shapes.

So far this only considers the outer shape though, but this could handle
complex polygons too should we need it.
parent 0f6c059a
Pipeline #79511 passed with stage
in 18 seconds
......@@ -47,6 +47,7 @@ target_sources(KOSMIndoorMap PRIVATE
scene/openinghourscache.cpp
scene/overlaysource.cpp
scene/penwidthutil.cpp
scene/poleofinaccessibilityfinder.cpp
scene/scenecontroller.cpp
scene/scenegeometry.cpp
scene/scenegraph.cpp
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "poleofinaccessibilityfinder_p.h"
#include "scenegeometry_p.h"
#include <QDebug>
#include <QLineF>
#include <QPolygonF>
#include <cmath>
#include <limits>
using namespace KOSMIndoorMap;
/** Signed distance from @p point to @p polygon, sign indicates inside/outside distance. */
static double pointToPolygonDistance(const QPointF &point, const QPolygonF &poly)
{
auto dist = std::numeric_limits<double>::max();
for (auto i = 0; i < poly.size(); ++i) {
const auto p1 = poly.at(i);
const auto p2 = poly.at((i + 1) % poly.size());
dist = std::min(dist, SceneGeometry::distanceToLine(QLineF(p1, p2), point));
}
return poly.containsPoint(point, Qt::OddEvenFill) ? dist : -dist;
}
void PoleOfInaccessibilityFinder::CellPriorityQueue::clear()
{
c.clear();
}
PoleOfInaccessibilityFinder::Cell::Cell(const QPointF &_center, double _size, const QPolygonF &poly)
: center(_center)
, size(_size)
, distance(pointToPolygonDistance(_center, poly))
{
}
double PoleOfInaccessibilityFinder::Cell::maximumDistance() const
{
return distance + size * std::sqrt(2.0);
}
bool PoleOfInaccessibilityFinder::Cell::operator<(const PoleOfInaccessibilityFinder::Cell &other) const
{
return maximumDistance() < other.maximumDistance();
}
PoleOfInaccessibilityFinder::PoleOfInaccessibilityFinder() = default;
PoleOfInaccessibilityFinder::~PoleOfInaccessibilityFinder() = default;
QPointF PoleOfInaccessibilityFinder::find(const QPolygonF &poly)
{
const auto boundingBox = poly.boundingRect();
const auto cellSize = std::min(boundingBox.width(), boundingBox.height());
auto h = cellSize / 2.0;
if (cellSize == 0.0) {
return boundingBox.center();
}
// cover polygon with initial cells
for (auto x = boundingBox.left(); x < boundingBox.right(); x += cellSize) {
for (auto y = boundingBox.top(); y < boundingBox.bottom(); y += cellSize) {
m_queue.push(Cell(QPointF(x + h, y + h), h, poly));
}
}
// initial guesses
auto bestCell = Cell(SceneGeometry::polygonCentroid(poly), 0, poly);
const auto bboxCell = Cell(boundingBox.center(), 0, poly);
if (bboxCell.distance > bestCell.distance) {
bestCell = bboxCell;
}
while (!m_queue.empty()) {
auto cell = m_queue.top();
m_queue.pop();
if (cell.distance > bestCell.distance) {
bestCell = cell;
}
// don't recurse into cells that can't provide a better result
if (cell.maximumDistance() - bestCell.distance <= 0.00002) {
continue;
}
h = cell.size / 2.0;
m_queue.push(Cell(QPointF(cell.center.x() - h, cell.center.y() - h), h, poly));
m_queue.push(Cell(QPointF(cell.center.x() + h, cell.center.y() - h), h, poly));
m_queue.push(Cell(QPointF(cell.center.x() - h, cell.center.y() + h), h, poly));
m_queue.push(Cell(QPointF(cell.center.x() + h, cell.center.y() + h), h, poly));
}
m_queue.clear();
return bestCell.center;
}
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KOSMINDOORMAP_POLEOFINACCESSIBILITYFINDER_H
#define KOSMINDOORMAP_POLEOFINACCESSIBILITYFINDER_H
#include <QPointF>
#include <queue>
class QPolygonF;
namespace KOSMIndoorMap {
/** Computes the Pole Of Inaccessibility of a polygon.
* That is, the point furthest away from the polygon outline,
* which is where we usually want to place a label.
*
* @see https://github.com/mapbox/polylabel
*/
class PoleOfInaccessibilityFinder
{
public:
explicit PoleOfInaccessibilityFinder();
~PoleOfInaccessibilityFinder();
QPointF find(const QPolygonF &poly);
private:
struct Cell {
explicit Cell(const QPointF &_center, double _size, const QPolygonF &poly);
bool operator<(const Cell &other) const;
double maximumDistance() const;
QPointF center;
double size;
double distance;
};
class CellPriorityQueue : public std::priority_queue<Cell, std::vector<Cell>>
{
public:
void clear();
};
CellPriorityQueue m_queue;
};
}
#endif // KOSMINDOORMAP_POLEOFINACCESSIBILITYFINDER_H
......@@ -10,6 +10,7 @@
#include "iconloader_p.h"
#include "penwidthutil_p.h"
#include "poleofinaccessibilityfinder_p.h"
#include "scenegeometry_p.h"
#include "openinghourscache_p.h"
#include "../style/mapcssdeclaration_p.h"
......@@ -46,6 +47,7 @@ public:
QPolygonF m_labelPlacementPath;
IconLoader m_iconLoader;
OpeningHoursCache m_openingHours;
PoleOfInaccessibilityFinder m_piaFinder;
OSM::TagKey m_layerTag;
OSM::TagKey m_typeTag;
......@@ -321,7 +323,12 @@ void SceneController::updateElement(OSM::Element e, int level, SceneGraph &sg, c
if (item->pos.isNull()) {
if (result.hasAreaProperties()) {
item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
// for simple enough shapes we can use the faster centroid rather than the expensive PIA
if (d->m_labelPlacementPath.size() > 6) {
item->pos = d->m_piaFinder.find(d->m_labelPlacementPath);
} else {
item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
}
} else if (result.hasLineProperties()) {
item->pos = SceneGeometry::polylineMidPoint(d->m_labelPlacementPath);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment