6#include "BuildingGraphicsItem.h"
8#include "MarbleDebug.h"
10#include "GeoDataPlacemark.h"
11#include "GeoDataLinearRing.h"
12#include "GeoDataPolygon.h"
13#include "GeoDataBuilding.h"
14#include "GeoDataMultiGeometry.h"
15#include "GeoDataPolyStyle.h"
16#include "OsmPlacemarkData.h"
17#include "GeoPainter.h"
20#include <QApplication>
25BuildingGraphicsItem::BuildingGraphicsItem(
const GeoDataPlacemark *placemark,
const GeoDataBuilding *building)
26 : AbstractGeoPolygonGraphicsItem(placemark, building)
28 if (
const auto ring = geodata_cast<GeoDataLinearRing>(&building->multiGeometry()->at(0))) {
30 }
else if (
const auto poly = geodata_cast<GeoDataPolygon>(&building->multiGeometry()->at(0))) {
34 setZValue(building->height());
35 Q_ASSERT(building->height() > 0.0);
38 paintLayers << QStringLiteral(
"Polygon/Building/frame")
39 << QStringLiteral(
"Polygon/Building/roof");
40 setPaintLayers(paintLayers);
43BuildingGraphicsItem::~BuildingGraphicsItem()
45 qDeleteAll(m_cachedOuterPolygons);
46 qDeleteAll(m_cachedInnerPolygons);
47 qDeleteAll(m_cachedOuterRoofPolygons);
48 qDeleteAll(m_cachedInnerRoofPolygons);
51void BuildingGraphicsItem::initializeBuildingPainting(
const GeoPainter* painter,
const ViewportParams *viewport,
52 bool &drawAccurate3D,
bool &isCameraAboveBuilding )
const
54 drawAccurate3D =
false;
55 isCameraAboveBuilding =
false;
58 double const physicalSize = 1.0;
59 int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM));
61 QPointF offsetAtCorner = buildingOffset(
QPointF(0, 0), viewport, &isCameraAboveBuilding);
62 qreal maxOffset = qMax( qAbs( offsetAtCorner.
x() ), qAbs( offsetAtCorner.
y() ) );
63 drawAccurate3D = painter->mapQuality() ==
HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize;
67static void normalizeWindingOrder(
QPolygonF *polygon)
70 for (
int i = 0; i <polygon->
size(); ++i) {
71 c += (polygon->
at((i + 1) % polygon->
size()).x() - polygon->
at(i).x()) * (polygon->
at(i).y() + polygon->
at((i + 1) % polygon->
size()).y());
74 std::reverse(polygon->
begin(), polygon->
end());
78void BuildingGraphicsItem::updatePolygons(
const ViewportParams &viewport,
81 bool &hasInnerBoundaries)
const
86 hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().
isEmpty() :
false;
88 if (hasInnerBoundaries) {
89 screenPolygons(viewport, polygon(), innerPolygons, outerPolygons);
92 viewport.screenCoordinates(polygon()->outerBoundary(), outerPolygons);
95 viewport.screenCoordinates(*ring(), outerPolygons);
97 for (
auto *polygon : outerPolygons) {
98 normalizeWindingOrder(polygon);
100 for (
auto *polygon : innerPolygons) {
101 normalizeWindingOrder(polygon);
105QPointF BuildingGraphicsItem::centroid(
const QPolygonF &polygon,
double &area)
107 auto centroid =
QPointF(0.0, 0.0);
109 for (
auto i=0, n=polygon.
size(); i<n; ++i) {
110 auto const x0 = polygon[i].x();
111 auto const y0 = polygon[i].y();
112 auto const j = i == n-1 ? 0 : i+1;
113 auto const x1 = polygon[j].x();
114 auto const y1 = polygon[j].y();
115 auto const a = x0*y1 - x1*y0;
117 centroid.rx() += (x0 + x1)*a;
118 centroid.ry() += (y0 + y1)*a;
122 return area != 0 ? centroid / (6.0*area) : polygon.boundingRect().
center();
125QPointF BuildingGraphicsItem::buildingOffset(
const QPointF &point,
const ViewportParams *viewport,
bool* isCameraAboveBuilding)
const
127 qreal
const cameraFactor = 0.5 * tan(0.5 * 110 * DEG2RAD);
128 Q_ASSERT(building()->height() > 0.0);
129 qreal
const buildingFactor = building()->height() / EARTH_RADIUS;
131 qreal
const cameraHeightPixel = viewport->width() * cameraFactor;
132 qreal buildingHeightPixel = viewport->radius() * buildingFactor;
133 qreal
const cameraDistance = cameraHeightPixel-buildingHeightPixel;
135 if (isCameraAboveBuilding) {
136 *isCameraAboveBuilding = cameraDistance > 0;
139 qreal
const cc = cameraDistance * cameraHeightPixel;
140 qreal
const cb = cameraDistance * buildingHeightPixel;
148 qreal
const offsetX = point.
x() - viewport->width() / 2.0;
149 qreal
const offsetY = point.
y() - viewport->height() / 2.0;
151 qreal
const shiftX = offsetX * cb / (cc + offsetX);
152 qreal
const shiftY = offsetY * cb / (cc + offsetY);
154 return QPointF(shiftX, shiftY);
157void BuildingGraphicsItem::paint(GeoPainter* painter,
const ViewportParams* viewport,
const QString &layer,
int tileZoomLevel)
160 if (tileZoomLevel == 17) {
163 AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel );
167 setZValue(building()->height());
171 qDeleteAll(m_cachedOuterPolygons);
172 qDeleteAll(m_cachedInnerPolygons);
173 qDeleteAll(m_cachedOuterRoofPolygons);
174 qDeleteAll(m_cachedInnerRoofPolygons);
175 m_cachedOuterPolygons.clear();
176 m_cachedInnerPolygons.clear();
177 m_cachedOuterRoofPolygons.clear();
178 m_cachedInnerRoofPolygons.clear();
179 updatePolygons(*viewport, m_cachedOuterPolygons,
180 m_cachedInnerPolygons,
181 m_hasInnerBoundaries);
182 if (m_cachedOuterPolygons.isEmpty()) {
185 paintFrame(painter, viewport);
187 if (m_cachedOuterPolygons.isEmpty()) {
190 paintRoof(painter, viewport);
192 mDebug() <<
"Didn't expect to have to paint layer " << layer <<
", ignoring it.";
196void BuildingGraphicsItem::paintRoof(GeoPainter* painter,
const ViewportParams* viewport)
199 bool isCameraAboveBuilding;
200 initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
201 if (!isCameraAboveBuilding) {
206 if (s_previousStyle != style().data()) {
207 isValid = configurePainter(painter, *viewport);
209 QFont font = painter->font();
212 painter->setFont(font);
215 s_previousStyle = style().data();
217 if (!isValid)
return;
221 if ( drawAccurate3D) {
222 if (m_hasInnerBoundaries) {
224 QPen const currentPen = painter->pen();
228 painter->createFillPolygons( m_cachedOuterRoofPolygons,
229 m_cachedInnerRoofPolygons );
231 for(
const QPolygonF* fillPolygon: fillPolygons ) {
232 painter->drawPolygon(*fillPolygon);
235 painter->setPen(currentPen);
237 for(
const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) {
238 painter->drawPolyline( *outerRoof );
240 for(
const QPolygonF* innerRoof: m_cachedInnerRoofPolygons ) {
241 painter->drawPolyline( *innerRoof );
243 qDeleteAll(fillPolygons);
246 for(
const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) {
247 painter->drawPolygon( *outerRoof );
252 QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().
center(), viewport);
253 painter->translate(offset);
255 if (m_hasInnerBoundaries) {
257 QPen const currentPen = painter->pen();
261 m_cachedInnerPolygons );
263 for(
const QPolygonF* fillPolygon: fillPolygons ) {
264 painter->drawPolygon(*fillPolygon);
267 painter->setPen(currentPen);
269 for(
const QPolygonF* outerPolygon: m_cachedOuterPolygons ) {
270 painter->drawPolyline( *outerPolygon );
272 for(
const QPolygonF* innerPolygon: m_cachedInnerPolygons ) {
273 painter->drawPolyline( *innerPolygon );
275 qDeleteAll(fillPolygons);
278 for(
const QPolygonF* outerPolygon: m_cachedOuterPolygons ) {
279 painter->drawPolygon( *outerPolygon );
282 painter->translate(-offset);
288 double maxArea = 0.0;
290 for (
int i = 0; i < m_cachedOuterRoofPolygons.size(); ++i) {
291 const QPolygonF *outerRoof = m_cachedOuterRoofPolygons[i];
296 if (!building()->
name().isEmpty() || !building()->entries().isEmpty()) {
298 qreal size = polygonSize.
width() * polygonSize.
height();
299 if (size > maxSize) {
302 roofCenter = centroid(*outerRoof, area);
303 maxArea = qMax(area, maxArea);
308 if (drawAccurate3D && !building()->
name().isEmpty() && !roofCenter.
isNull()) {
309 double const w2 = 0.5 * painter->fontMetrics().horizontalAdvance(building()->
name());
310 double const ascent = painter->fontMetrics().ascent();
311 double const descent = painter->fontMetrics().descent();
312 double const a2 = 0.5 * painter->fontMetrics().ascent();
319 painter->drawTextFragment(roofCenter.
toPoint(), building()->
name(),
320 painter->font().pointSize(), painter->brush().color());
326 if (!building()->entries().isEmpty() && maxArea > 1600 * building()->entries().size()) {
327 for(
const auto &entry: building()->entries()) {
329 viewport->screenCoordinates(entry.point, x, y);
331 point += buildingOffset(point, viewport);
332 painter->drawTextFragment(point.
toPoint(),
333 building()->
name(), painter->font().pointSize(), painter->brush().color(),
334 GeoPainter::RoundFrame);
339void BuildingGraphicsItem::paintFrame(GeoPainter *painter,
const ViewportParams *viewport)
342 if (building()->height() == 0.0) {
346 if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4))
347 || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) {
352 bool isCameraAboveBuilding;
353 initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
356 if (s_previousStyle != style().data()) {
357 isValid = configurePainterForFrame(painter);
359 s_previousStyle = style().data();
361 if (!isValid)
return;
363 if ( drawAccurate3D && isCameraAboveBuilding ) {
364 for (
const QPolygonF *outline: m_cachedOuterPolygons) {
365 if (outline->isEmpty()) {
369 int const size = outline->size();
371 outerRoof->
reserve(outline->size());
373 QPointF shiftA = a + buildingOffset(a, viewport);
374 outerRoof->
append(shiftA);
375 for (
int i=1; i<size; ++i) {
376 QPointF const & b = (*outline)[i];
377 QPointF const shiftB = b + buildingOffset(b, viewport);
379 bool backface = (b.
x() - a.
x()) * (shiftA.
y() - a.
y())
380 - (b.
y() - a.
y()) * (shiftA.
x() - a.
x()) >= 0;
384 buildingSide << a << shiftA << shiftB << b;
385 painter->drawPolygon(buildingSide);
389 outerRoof->
append(shiftA);
391 m_cachedOuterRoofPolygons.append(outerRoof);
393 for (
const QPolygonF *outline: m_cachedInnerPolygons) {
394 if (outline->isEmpty()) {
398 int const size = outline->size();
400 innerRoof->
reserve(outline->size());
402 QPointF shiftA = a + buildingOffset(a, viewport);
403 innerRoof->
append(shiftA);
404 for (
int i=1; i<size; ++i) {
405 QPointF const & b = (*outline)[i];
406 QPointF const shiftB = b + buildingOffset(b, viewport);
408 bool backface = (b.
x() - a.
x()) * (shiftA.
y() - a.
y())
409 - (b.
y() - a.
y()) * (shiftA.
x() - a.
x()) >= 0;
413 buildingSide << a << shiftA << shiftB << b;
414 painter->drawPolygon(buildingSide);
418 innerRoof->
append(shiftA);
420 m_cachedInnerRoofPolygons.append(innerRoof);
425 m_cachedInnerPolygons );
427 for(
QPolygonF* fillPolygon: fillPolygons ) {
428 painter->drawPolygon(*fillPolygon);
430 qDeleteAll(fillPolygons);
434void BuildingGraphicsItem::screenPolygons(
const ViewportParams &viewport,
const GeoDataPolygon *polygon,
441 viewport.screenCoordinates(polygon->outerBoundary(), outerPolygons);
444 for (
const GeoDataLinearRing &innerBoundary: innerBoundaries) {
446 viewport.screenCoordinates(innerBoundary, innerPolygonsPerBoundary);
448 innerPolygons.
reserve(innerPolygons.
size() + innerPolygonsPerBoundary.
size());
449 for(
QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) {
450 innerPolygons << innerPolygonPerBoundary;
455bool BuildingGraphicsItem::contains(
const QPoint &screenPosition,
const ViewportParams *viewport)
const
457 if (m_cachedOuterPolygons.isEmpty()) {
459 return AbstractGeoPolygonGraphicsItem::contains(screenPosition, viewport);
462 QPointF const point = screenPosition;
463 for (
auto polygon: m_cachedOuterRoofPolygons) {
465 for (
auto polygon: m_cachedInnerRoofPolygons) {
473 for (
auto polygon: m_cachedOuterPolygons) {
475 for (
auto polygon: m_cachedInnerPolygons) {
486bool BuildingGraphicsItem::configurePainterForFrame(GeoPainter *painter)
const
488 QPen currentPen = painter->pen();
490 GeoDataStyle::ConstPtr style = this->style();
492 painter->setPen(
QPen() );
495 const GeoDataPolyStyle& polyStyle = style->polyStyle();
501 if (!polyStyle.fill()) {
505 const QColor paintedColor = polyStyle.paintedColor().
darker(150);
506 if (painter->brush().color() != paintedColor) {
507 painter->setBrush(paintedColor);
This file contains the headers for ViewportParams.
bool isValid(QStringView ifopt)
QString name(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
@ HighQuality
High quality (e.g. antialiasing for lines)
QColor darker(int factor) const const
int pointSize() const const
void setPointSize(int pointSize)
QList< QScreen * > screens()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
Qt::PenStyle style() const const
bool isNull() const const
QPoint toPoint() const const
QRectF boundingRect() const const
bool containsPoint(const QPointF &point, Qt::FillRule fillRule) const const
QSizeF size() const const
qreal height() const const
qreal width() const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QTextStream & center(QTextStream &stream)