File indexing completed on 2024-05-05 03:50:39
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2013 Adrian Draghici <draghici.adrian.b@gmail.com> 0004 // 0005 0006 // Self 0007 #include "GroundOverlayFrame.h" 0008 0009 // Marble 0010 #include "GeoDataPlacemark.h" 0011 #include "GeoDataGroundOverlay.h" 0012 #include "GeoDataLinearRing.h" 0013 #include "GeoDataPolygon.h" 0014 #include "GeoPainter.h" 0015 #include "ViewportParams.h" 0016 #include "SceneGraphicsTypes.h" 0017 #include "TextureLayer.h" 0018 #include "MarbleDirs.h" 0019 0020 // Qt 0021 #include <qmath.h> 0022 0023 0024 namespace Marble 0025 { 0026 0027 GroundOverlayFrame::GroundOverlayFrame( GeoDataPlacemark *placemark, 0028 GeoDataGroundOverlay *overlay, 0029 TextureLayer *textureLayer ) : 0030 SceneGraphicsItem( placemark ), 0031 m_overlay( overlay ), 0032 m_textureLayer( textureLayer ), 0033 m_movedHandle( NoRegion ), 0034 m_hoveredHandle( NoRegion ), 0035 m_editStatus( Resize ), 0036 m_editStatusChangeNeeded( false ), 0037 m_previousRotation( 0.0 ), 0038 m_viewport( nullptr ) 0039 { 0040 m_resizeIcons.reserve(16); 0041 // NorthWest 0042 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft.png"))); 0043 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft-active.png"))); 0044 // SouthWest 0045 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright.png"))); 0046 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright-active.png"))); 0047 // SouthEast 0048 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft.png"))); 0049 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft-active.png"))); 0050 // NorthEast 0051 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright.png"))); 0052 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright-active.png"))); 0053 // North 0054 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png"))); 0055 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png"))); 0056 // South 0057 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png"))); 0058 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png"))); 0059 // East 0060 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png"))); 0061 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png"))); 0062 // West 0063 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png"))); 0064 m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png"))); 0065 0066 0067 m_rotateIcons.reserve(16); 0068 // NorthWest 0069 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topleft.png"))); 0070 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topleft-active.png"))); 0071 // SouthWest 0072 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomleft.png"))); 0073 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomleft-active.png"))); 0074 // SouthEast 0075 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomright.png"))); 0076 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomright-active.png"))); 0077 // NorthEast 0078 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topright.png"))); 0079 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topright-active.png"))); 0080 // North 0081 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png"))); 0082 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png"))); 0083 // South 0084 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png"))); 0085 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png"))); 0086 // East 0087 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png"))); 0088 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png"))); 0089 // West 0090 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png"))); 0091 m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png"))); 0092 0093 update(); 0094 setPaintLayers(QStringList() << "GroundOverlayFrame"); 0095 } 0096 0097 void GroundOverlayFrame::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel) 0098 { 0099 Q_UNUSED(layer); 0100 Q_UNUSED(tileZoomLevel); 0101 m_viewport = viewport; 0102 m_regionList.clear(); 0103 0104 painter->save(); 0105 if (const auto polygon = geodata_cast<GeoDataPolygon>(placemark()->geometry())) { 0106 const GeoDataLinearRing &ring = polygon->outerBoundary(); 0107 QVector<GeoDataCoordinates> coordinateList; 0108 coordinateList.reserve(8); 0109 0110 coordinateList.append( ring.at( NorthWest ) ); 0111 coordinateList.append( ring.at( SouthWest ) ); 0112 coordinateList.append( ring.at( SouthEast ) ); 0113 coordinateList.append( ring.at( NorthEast ) ); 0114 0115 GeoDataCoordinates northernHandle = ring.at( NorthEast ).interpolate( ring.at( NorthWest ), 0.5 ); 0116 GeoDataCoordinates southernHandle = ring.at( SouthEast ).interpolate( ring.at( SouthWest ), 0.5 ); 0117 // Special case handle position to take tessellation 0118 // along latitude circles into account 0119 if (m_overlay->latLonBox().rotation() == 0) { 0120 northernHandle.setLatitude(ring.at( NorthEast ).latitude()); 0121 southernHandle.setLatitude(ring.at( SouthEast ).latitude()); 0122 } 0123 coordinateList.append( northernHandle ); 0124 coordinateList.append( southernHandle ); 0125 0126 coordinateList.append( ring.at( NorthEast ).interpolate( ring.at( SouthEast ), 0.5 ) ); 0127 coordinateList.append( ring.at( NorthWest ).interpolate( ring.at( SouthWest ), 0.5 ) ); 0128 0129 m_regionList.reserve(9); 0130 m_regionList.append( painter->regionFromEllipse( coordinateList.at( NorthWest ), 16, 16 ) ); 0131 m_regionList.append( painter->regionFromEllipse( coordinateList.at( SouthWest ), 16, 16 ) ); 0132 m_regionList.append( painter->regionFromEllipse( coordinateList.at( SouthEast ), 16, 16 ) ); 0133 m_regionList.append( painter->regionFromEllipse( coordinateList.at( NorthEast ), 16, 16 ) ); 0134 m_regionList.append( painter->regionFromEllipse( coordinateList.at( North ), 16, 16 ) ); 0135 m_regionList.append( painter->regionFromEllipse( coordinateList.at( South ), 16, 16 ) ); 0136 m_regionList.append( painter->regionFromEllipse( coordinateList.at( East ), 16, 16 ) ); 0137 m_regionList.append( painter->regionFromEllipse( coordinateList.at( West ), 16, 16 ) ); 0138 m_regionList.append( painter->regionFromPolygon( ring, Qt::OddEvenFill ) ); 0139 0140 // Calculate handle icon orientation due to the projection 0141 qreal xNW, yNW, xSW, ySW; 0142 viewport->screenCoordinates(ring.at( NorthWest ), xNW, yNW); 0143 viewport->screenCoordinates(ring.at( SouthWest ), xSW, ySW); 0144 qreal westernAngle = qAtan2(ySW - yNW, xSW - xNW) - M_PI/2; 0145 qreal xNE, yNE, xSE, ySE; 0146 viewport->screenCoordinates(ring.at( NorthEast ), xNE, yNE); 0147 viewport->screenCoordinates(ring.at( SouthEast ), xSE, ySE); 0148 qreal easternAngle = qAtan2(ySE - yNE, xSE - xNE) - M_PI/2; 0149 0150 painter->setPen( Qt::DashLine ); 0151 painter->setBrush( Qt::NoBrush ); 0152 painter->drawPolygon( ring ); 0153 0154 qreal projectedAngle = 0; 0155 0156 for( int i = NorthWest; i != Polygon; ++i ) { 0157 0158 // Assign handle icon orientation due to the projection 0159 if (i == NorthWest || i == West || i == SouthWest) { 0160 projectedAngle = westernAngle; 0161 } 0162 else if (i == NorthEast || i == East || i == SouthEast) { 0163 projectedAngle = easternAngle; 0164 } 0165 else if (i == North || i == South) { 0166 projectedAngle = (westernAngle + easternAngle) / 2; 0167 } 0168 QTransform trans; 0169 trans.rotateRadians( projectedAngle ); 0170 if ( m_editStatus == Resize ){ 0171 if( m_hoveredHandle != i ) { 0172 painter->drawImage( coordinateList.at( i ), 0173 m_resizeIcons.at( 2*i ).transformed( trans, Qt::SmoothTransformation ) ); 0174 } else { 0175 painter->drawImage( coordinateList.at( i ), 0176 m_resizeIcons.at( 2*i + 1 ).transformed( trans, Qt::SmoothTransformation ) ); 0177 } 0178 } else if ( m_editStatus == Rotate ) { 0179 if( m_hoveredHandle != i ) { 0180 painter->drawImage( coordinateList.at( i ), 0181 m_rotateIcons.at( 2*i ).transformed( trans, Qt::SmoothTransformation ) ); 0182 } else { 0183 painter->drawImage( coordinateList.at( i ), 0184 m_rotateIcons.at( 2*i + 1 ).transformed( trans, Qt::SmoothTransformation ) ); 0185 } 0186 } 0187 } 0188 } 0189 painter->restore(); 0190 } 0191 0192 bool GroundOverlayFrame::containsPoint( const QPoint &eventPos ) const 0193 { 0194 for ( const QRegion ®ion: m_regionList ) { 0195 if ( region.contains( eventPos ) ) { 0196 return true; 0197 } 0198 } 0199 0200 // This is a bugfix to handle the events even if they occur outside of this object, 0201 // so when rotating or resizing the mouseReleaseEvent is handled successfully 0202 // TODO: maybe find a better way? 0203 return m_movedHandle != NoRegion || 0204 m_hoveredHandle != NoRegion; 0205 } 0206 0207 void GroundOverlayFrame::dealWithItemChange( const SceneGraphicsItem *other ) 0208 { 0209 Q_UNUSED( other ); 0210 } 0211 0212 void GroundOverlayFrame::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination ) 0213 { 0214 // not implemented yet 0215 Q_UNUSED( source ); 0216 Q_UNUSED( destination ); 0217 } 0218 0219 bool GroundOverlayFrame::mousePressEvent( QMouseEvent *event ) 0220 { 0221 // React to all ellipse as well as to the polygon. 0222 for ( int i = 0; i < m_regionList.size(); ++i ) { 0223 if ( m_regionList.at(i).contains( event->pos() ) ) { 0224 m_movedHandle = i; 0225 0226 qreal lon, lat; 0227 m_viewport->geoCoordinates( event->pos().x(), 0228 event->pos().y(), 0229 lon, lat, 0230 GeoDataCoordinates::Radian ); 0231 m_movedHandleGeoCoordinates.set( lon, lat ); 0232 m_movedHandleScreenCoordinates = event->pos(); 0233 m_previousRotation = m_overlay->latLonBox().rotation(); 0234 0235 if ( m_movedHandle == Polygon ) { 0236 m_editStatusChangeNeeded = true; 0237 } 0238 0239 return true; 0240 } 0241 } 0242 0243 return false; 0244 } 0245 0246 bool GroundOverlayFrame::mouseMoveEvent( QMouseEvent *event ) 0247 { 0248 if ( !m_viewport ) { 0249 return false; 0250 } 0251 0252 // Catch hover events. 0253 if ( m_movedHandle == NoRegion ) { 0254 for ( int i = 0; i < m_regionList.size(); ++i ) { 0255 if ( m_regionList.at(i).contains( event->pos() ) ) { 0256 if ( i == Polygon ) { 0257 setRequest( ChangeCursorOverlayBodyHover ); 0258 } else { 0259 setRequest( ChangeCursorOverlayRotateHover ); 0260 } 0261 m_hoveredHandle = i; 0262 return true; 0263 } 0264 } 0265 m_hoveredHandle = NoRegion; 0266 return true; 0267 } else { 0268 m_editStatusChangeNeeded = false; 0269 } 0270 0271 if (geodata_cast<GeoDataPolygon>(placemark()->geometry())) { 0272 qreal lon, lat; 0273 m_viewport->geoCoordinates( event->pos().x(), 0274 event->pos().y(), 0275 lon, lat, 0276 GeoDataCoordinates::Radian ); 0277 0278 if ( m_editStatus == Resize ) { 0279 0280 GeoDataCoordinates coord(lon, lat); 0281 GeoDataCoordinates rotatedCoord(coord); 0282 0283 if (m_overlay->latLonBox().rotation() != 0) { 0284 rotatedCoord = coord.rotateAround(m_overlay->latLonBox().center(), 0285 -m_overlay->latLonBox().rotation()); 0286 } 0287 0288 if ( m_movedHandle == NorthWest ) { 0289 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() ); 0290 m_overlay->latLonBox().setWest( rotatedCoord.longitude() ); 0291 } else if ( m_movedHandle == SouthWest ) { 0292 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() ); 0293 m_overlay->latLonBox().setWest( rotatedCoord.longitude() ); 0294 } else if ( m_movedHandle == SouthEast ) { 0295 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() ); 0296 m_overlay->latLonBox().setEast( rotatedCoord.longitude() ); 0297 } else if ( m_movedHandle == NorthEast ) { 0298 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() ); 0299 m_overlay->latLonBox().setEast( rotatedCoord.longitude() ); 0300 } else if ( m_movedHandle == North ) { 0301 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() ); 0302 } else if ( m_movedHandle == South ) { 0303 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() ); 0304 } else if ( m_movedHandle == East ) { 0305 m_overlay->latLonBox().setEast( rotatedCoord.longitude() ); 0306 } else if ( m_movedHandle == West ) { 0307 m_overlay->latLonBox().setWest( rotatedCoord.longitude() ); 0308 } 0309 0310 } else if ( m_editStatus == Rotate ) { 0311 if ( m_movedHandle != Polygon ) { 0312 QPoint center = m_regionList.at( Polygon ).boundingRect().center(); 0313 qreal angle1 = qAtan2( event->pos().y() - center.y(), 0314 event->pos().x() - center.x() ); 0315 qreal angle2 = qAtan2( m_movedHandleScreenCoordinates.y() - center.y(), 0316 m_movedHandleScreenCoordinates.x() - center.x() ); 0317 m_overlay->latLonBox().setRotation( angle2 - angle1 + m_previousRotation ); 0318 } 0319 } 0320 0321 if ( m_movedHandle == Polygon ) { 0322 const qreal centerLonDiff = lon - m_movedHandleGeoCoordinates.longitude(); 0323 const qreal centerLatDiff = lat - m_movedHandleGeoCoordinates.latitude(); 0324 0325 m_overlay->latLonBox().setBoundaries( m_overlay->latLonBox().north() + centerLatDiff, 0326 m_overlay->latLonBox().south() + centerLatDiff, 0327 m_overlay->latLonBox().east() + centerLonDiff, 0328 m_overlay->latLonBox().west() + centerLonDiff ); 0329 0330 m_movedHandleGeoCoordinates.set( lon, lat ); 0331 } 0332 0333 update(); 0334 return true; 0335 } 0336 return false; 0337 } 0338 0339 bool GroundOverlayFrame::mouseReleaseEvent( QMouseEvent *event ) 0340 { 0341 Q_UNUSED( event ); 0342 0343 m_movedHandle = NoRegion; 0344 m_textureLayer->reset(); 0345 0346 if( m_editStatusChangeNeeded ) { 0347 if( m_editStatus == Resize ) { 0348 m_editStatus = Rotate; 0349 } else { 0350 m_editStatus = Resize; 0351 } 0352 } 0353 0354 return true; 0355 } 0356 0357 void GroundOverlayFrame::update() 0358 { 0359 GeoDataLatLonBox overlayLatLonBox = m_overlay->latLonBox(); 0360 GeoDataPolygon *poly = dynamic_cast<GeoDataPolygon*>( placemark()->geometry() ); 0361 poly->outerBoundary().clear(); 0362 0363 GeoDataCoordinates rotatedCoord; 0364 0365 GeoDataCoordinates northWest(overlayLatLonBox.west(), overlayLatLonBox.north()); 0366 rotatedCoord = northWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation()); 0367 poly->outerBoundary().append( rotatedCoord ); 0368 0369 GeoDataCoordinates southWest(overlayLatLonBox.west(), overlayLatLonBox.south()); 0370 rotatedCoord = southWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation()); 0371 poly->outerBoundary().append( rotatedCoord ); 0372 0373 GeoDataCoordinates southEast(overlayLatLonBox.east(), overlayLatLonBox.south()); 0374 rotatedCoord = southEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation()); 0375 poly->outerBoundary().append( rotatedCoord ); 0376 0377 GeoDataCoordinates northEast(overlayLatLonBox.east(), overlayLatLonBox.north()); 0378 rotatedCoord = northEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation()); 0379 poly->outerBoundary().append( rotatedCoord ); 0380 } 0381 0382 void GroundOverlayFrame::dealWithStateChange( SceneGraphicsItem::ActionState previousState ) 0383 { 0384 Q_UNUSED( previousState ); 0385 } 0386 0387 const char *GroundOverlayFrame::graphicType() const 0388 { 0389 return SceneGraphicsTypes::SceneGraphicGroundOverlay; 0390 } 0391 0392 0393 }