File indexing completed on 2024-05-05 04:22:10
0001 // SPDX-FileCopyrightText: 2019-2021 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0002 // 0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "GeoCluster.h" 0006 #include "GeoCoordinates.h" 0007 #include "Logging.h" 0008 0009 #include <DB/ImageInfo.h> 0010 #include <kpathumbnails/ThumbnailCache.h> 0011 0012 #include <KLocalizedString> 0013 #include <QPen> 0014 #include <QSize> 0015 #include <marble/GeoDataLatLonBox.h> 0016 #include <marble/GeoPainter.h> 0017 #include <marble/ViewportParams.h> 0018 0019 namespace 0020 { 0021 // when the angular resolution is smaller than fineResolution, all details should be shown 0022 constexpr qreal FINE_RESOLUTION = 0.000001; 0023 // the scale factor of the bounding box compared to the bounding box as drawn on the map 0024 constexpr qreal BOUNDING_BOX_SCALEFACTOR = 1.2; 0025 0026 /** 0027 * @brief screenSize computes the screen size of a geographical region in pixels. 0028 * If one of the bounding box edges is not visible, a null QizeF is retunred. 0029 * @param viewPortParams the parameters of the current view port 0030 * @param box the geographical region 0031 * @return the size in pixels, or a null size 0032 */ 0033 QSizeF screenSize(const Marble::ViewportParams &viewPortParams, const Marble::GeoDataLatLonBox &box, bool debug = false) 0034 { 0035 qreal east; 0036 qreal north; 0037 qreal west; 0038 qreal south; 0039 // if a point is not visible on screen, screenCoordinates() returns false 0040 // the result is still usable, though 0041 bool onScreen; 0042 onScreen = viewPortParams.screenCoordinates(box.east(Marble::GeoDataCoordinates::Radian), 0043 box.north(Marble::GeoDataCoordinates::Radian), 0044 east, north); 0045 onScreen &= viewPortParams.screenCoordinates(box.west(Marble::GeoDataCoordinates::Radian), 0046 box.south(Marble::GeoDataCoordinates::Radian), 0047 west, south); 0048 if (debug) { 0049 qCDebug(MapLog) << "coordinates" << east << "-" << west << "," << north << "-" << south << "are" << (onScreen ? "on screen" : "not (fully) on screen"); 0050 } 0051 return QSizeF { qAbs(east - west), qAbs(north - south) }; 0052 } 0053 0054 /** 0055 * @brief screenRegion translates a bounding region into screen coordinates for drawing it. 0056 * The region has the size of what is actually drawn, not the size of the bounding region itself. 0057 * @param viewPortParams 0058 * @param region 0059 * @param minSizePx 0060 * @return the region in screen coordinates 0061 */ 0062 QRectF screenRegion(const Marble::ViewportParams &viewPortParams, const Marble::GeoDataLatLonBox region, int minSizePx) 0063 { 0064 const QSizeF areaSizePx = screenSize(viewPortParams, region); 0065 // drawing a larger area gets nicer results on average: 0066 const qreal heightPx = qMax(BOUNDING_BOX_SCALEFACTOR * areaSizePx.height(), (qreal)minSizePx); 0067 const qreal widthPx = qMax(BOUNDING_BOX_SCALEFACTOR * areaSizePx.width(), (qreal)minSizePx); 0068 qreal left; 0069 qreal top; 0070 viewPortParams.screenCoordinates(region.west(Marble::GeoDataCoordinates::Radian), 0071 region.north(Marble::GeoDataCoordinates::Radian), 0072 left, top); 0073 return QRectF(left, top, widthPx, heightPx); 0074 } 0075 } // namespace 0076 0077 Marble::GeoDataLatLonAltBox Map::GeoCluster::boundingRegion() const 0078 { 0079 if (m_boundingRegion.isEmpty()) { 0080 for (const auto &subCluster : m_subClusters) { 0081 m_boundingRegion |= subCluster->boundingRegion(); 0082 } 0083 } 0084 return m_boundingRegion; 0085 } 0086 0087 Marble::GeoDataCoordinates Map::GeoCluster::center() const 0088 { 0089 return boundingRegion().center(); 0090 } 0091 0092 const Map::GeoCluster *Map::GeoCluster::regionForPoint(QPoint pos) const 0093 { 0094 // only check child items if the cluster was not drawn on the map: 0095 if (m_renderedRegion.isEmpty()) { 0096 for (const auto &subCluster : m_subClusters) { 0097 auto cluster = subCluster->regionForPoint(pos); 0098 if (cluster && !cluster->isEmpty()) 0099 return cluster; 0100 } 0101 } else if (m_renderedRegion.contains(pos)) { 0102 qCDebug(MapLog) << "GeoCluster containing" << size() << "images matches point."; 0103 return this; 0104 } 0105 return nullptr; 0106 } 0107 0108 void Map::GeoCluster::render(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const ThumbnailParams &thumbs, Map::MapStyle style) const 0109 { 0110 if (style == MapStyle::ForceShowThumbnails || size() == 1 0111 || viewPortParams.resolves(boundingRegion(), thumbs.thumbnailSizePx) 0112 || (viewPortParams.angularResolution() < FINE_RESOLUTION)) { 0113 m_renderedRegion = {}; 0114 // if the region takes up enough screen space, we should display the subclusters individually. 0115 // if all images have the same coordinates (null bounding region), this will never happen 0116 // -> in this case, show the images when we're zoomed in enough 0117 renderSubItems(painter, viewPortParams, thumbs, style); 0118 } else { 0119 qCDebug(MapLog) << "GeoCluster has" << size() << "images."; 0120 QPen pen = painter->pen(); 0121 const auto opacity = painter->opacity(); 0122 painter->setOpacity(0.5); 0123 const QRectF screenRect = screenRegion(viewPortParams, boundingRegion(), thumbs.thumbnailSizePx); 0124 painter->drawRect(center(), screenRect.width(), screenRect.height(), false); 0125 m_renderedRegion = painter->regionFromRect(center(), screenRect.width(), screenRect.height(), false); 0126 #if MARBLE_VERSION < QT_VERSION_CHECK(21, 04, 0) 0127 // adjust region to match up with drawn region (see Marble bug https://bugs.kde.org/show_bug.cgi?id=431466): 0128 m_renderedRegion.translate(static_cast<int>(-0.5 * screenRect.width()), static_cast<int>(-0.5 * screenRect.height())); 0129 #endif 0130 #ifdef MARBLE_DEBUG_GEOPAINTER 0131 // draw clickable region for visual inspection: 0132 painter->setPen(Qt::green); 0133 painter->drawRect(*m_renderedRegion.begin()); 0134 #endif 0135 painter->setOpacity(opacity); 0136 painter->setPen(QPen(Qt::black)); 0137 painter->drawText(center(), i18nc("The number of images in an area of the map", "%1", size()), -0.5 * thumbs.thumbnailSizePx, 0.5 * thumbs.thumbnailSizePx, thumbs.thumbnailSizePx, thumbs.thumbnailSizePx, QTextOption(Qt::AlignCenter)); 0138 painter->setPen(pen); 0139 } 0140 } 0141 0142 int Map::GeoCluster::size() const 0143 { 0144 if (m_size == 0) { 0145 for (const auto &subCluster : m_subClusters) { 0146 m_size += subCluster->size(); 0147 } 0148 } 0149 return m_size; 0150 } 0151 0152 bool Map::GeoCluster::isEmpty() const 0153 { 0154 return (size() == 0); 0155 } 0156 0157 void Map::GeoCluster::renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const ThumbnailParams &thumbs, Map::MapStyle style) const 0158 { 0159 for (const auto &subCluster : m_subClusters) { 0160 subCluster->render(painter, viewPortParams, thumbs, style); 0161 } 0162 } 0163 0164 Map::GeoCluster::GeoCluster(int lvl) 0165 : m_level(lvl) 0166 { 0167 } 0168 0169 void Map::GeoCluster::addSubCluster(const Map::GeoCluster *subCluster) 0170 { 0171 m_subClusters.append(subCluster); 0172 } 0173 0174 Map::GeoBin::GeoBin() 0175 : GeoCluster(0) 0176 , m_thumbnailSizePx(0) 0177 { 0178 } 0179 0180 void Map::GeoBin::addImage(DB::ImageInfoPtr image) 0181 { 0182 m_images.append(image); 0183 extendGeoDataLatLonBox(m_boundingRegion, image->coordinates()); 0184 } 0185 0186 Marble::GeoDataLatLonAltBox Map::GeoBin::boundingRegion() const 0187 { 0188 return m_boundingRegion; 0189 } 0190 0191 int Map::GeoBin::size() const 0192 { 0193 return m_images.size(); 0194 } 0195 0196 void Map::GeoBin::renderSubItems(Marble::GeoPainter *painter, const Marble::ViewportParams &viewPortParams, const ThumbnailParams &thumbs, Map::MapStyle style) const 0197 { 0198 const auto viewPort = viewPortParams.viewLatLonAltBox(); 0199 qCDebug(MapLog) << "GeoBin: drawing" << m_images.count() << "individual images"; 0200 for (const DB::ImageInfoPtr &image : m_images) { 0201 const Marble::GeoDataCoordinates pos(image->coordinates().lon(), image->coordinates().lat(), 0202 image->coordinates().alt(), 0203 Marble::GeoDataCoordinates::Degree); 0204 if (viewPort.contains(pos)) { 0205 if (style == MapStyle::ShowPins) { 0206 painter->drawPixmap(pos, thumbs.alternatePixmap); 0207 } else { 0208 if (thumbs.thumbnailSizePx != m_thumbnailSizePx) { 0209 m_scaledThumbnailCache.clear(); 0210 m_thumbnailSizePx = thumbs.thumbnailSizePx; 0211 } 0212 if (!m_scaledThumbnailCache.contains(image)) { 0213 QPixmap thumb = thumbs.cache->lookup(image->fileName()).scaled(QSize(thumbs.thumbnailSizePx, thumbs.thumbnailSizePx), Qt::KeepAspectRatio); 0214 m_scaledThumbnailCache.insert(image, thumb); 0215 } 0216 painter->drawPixmap(pos, m_scaledThumbnailCache.value(image)); 0217 } 0218 } 0219 } 0220 } 0221 0222 void Map::extendGeoDataLatLonBox(Marble::GeoDataLatLonBox &box, const Map::GeoCoordinates &coords) 0223 { 0224 Marble::GeoDataLatLonBox addition { coords.lat(), coords.lat(), coords.lon(), coords.lon(), Marble::GeoDataCoordinates::Degree }; 0225 // let GeoDataLatLonBox::united() take care of the edge cases 0226 box |= addition; 0227 }