1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2013 Adrian Draghici <draghici.adrian.b@gmail.com>
4 //
5 
6 // Self
7 #include "GroundOverlayFrame.h"
8 
9 // Marble
10 #include "GeoDataPlacemark.h"
11 #include "GeoDataGroundOverlay.h"
12 #include "GeoDataLinearRing.h"
13 #include "GeoDataPolygon.h"
14 #include "GeoPainter.h"
15 #include "ViewportParams.h"
16 #include "SceneGraphicsTypes.h"
17 #include "TextureLayer.h"
18 #include "MarbleDirs.h"
19 
20 // Qt
21 #include <qmath.h>
22 
23 
24 namespace Marble
25 {
26 
GroundOverlayFrame(GeoDataPlacemark * placemark,GeoDataGroundOverlay * overlay,TextureLayer * textureLayer)27 GroundOverlayFrame::GroundOverlayFrame( GeoDataPlacemark *placemark,
28                                         GeoDataGroundOverlay *overlay,
29                                         TextureLayer *textureLayer ) :
30     SceneGraphicsItem( placemark ),
31     m_overlay( overlay ),
32     m_textureLayer( textureLayer ),
33     m_movedHandle( NoRegion ),
34     m_hoveredHandle( NoRegion ),
35     m_editStatus( Resize ),
36     m_editStatusChangeNeeded( false ),
37     m_previousRotation( 0.0 ),
38     m_viewport( nullptr )
39 {
40     m_resizeIcons.reserve(16);
41     // NorthWest
42     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft.png")));
43     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft-active.png")));
44     // SouthWest
45     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright.png")));
46     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright-active.png")));
47     // SouthEast
48     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft.png")));
49     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topleft-active.png")));
50     // NorthEast
51     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright.png")));
52     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-diagonal-topright-active.png")));
53     // North
54     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
55     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
56     // South
57     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
58     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
59     // East
60     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
61     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
62     // West
63     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
64     m_resizeIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
65 
66 
67     m_rotateIcons.reserve(16);
68     // NorthWest
69     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topleft.png")));
70     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topleft-active.png")));
71     // SouthWest
72     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomleft.png")));
73     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomleft-active.png")));
74     // SouthEast
75     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomright.png")));
76     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-bottomright-active.png")));
77     // NorthEast
78     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topright.png")));
79     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-rotation-topright-active.png")));
80     // North
81     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
82     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
83     // South
84     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal.png")));
85     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-horizontal-active.png")));
86     // East
87     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
88     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
89     // West
90     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical.png")));
91     m_rotateIcons.append(QImage(MarbleDirs::systemPath() + QLatin1String("/bitmaps/editarrows/arrow-vertical-active.png")));
92 
93     update();
94     setPaintLayers(QStringList() << "GroundOverlayFrame");
95 }
96 
paint(GeoPainter * painter,const ViewportParams * viewport,const QString & layer,int tileZoomLevel)97 void GroundOverlayFrame::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel)
98 {
99     Q_UNUSED(layer);
100     Q_UNUSED(tileZoomLevel);
101     m_viewport = viewport;
102     m_regionList.clear();
103 
104     painter->save();
105     if (const auto polygon = geodata_cast<GeoDataPolygon>(placemark()->geometry())) {
106         const GeoDataLinearRing &ring = polygon->outerBoundary();
107         QVector<GeoDataCoordinates> coordinateList;
108         coordinateList.reserve(8);
109 
110         coordinateList.append( ring.at( NorthWest ) );
111         coordinateList.append( ring.at( SouthWest ) );
112         coordinateList.append( ring.at( SouthEast ) );
113         coordinateList.append( ring.at( NorthEast ) );
114 
115         GeoDataCoordinates northernHandle = ring.at( NorthEast ).interpolate( ring.at( NorthWest ), 0.5 );
116         GeoDataCoordinates southernHandle = ring.at( SouthEast ).interpolate( ring.at( SouthWest ), 0.5 );
117         // Special case handle position to take tessellation
118         // along latitude circles into account
119         if (m_overlay->latLonBox().rotation() == 0) {
120             northernHandle.setLatitude(ring.at( NorthEast ).latitude());
121             southernHandle.setLatitude(ring.at( SouthEast ).latitude());
122         }
123         coordinateList.append( northernHandle );
124         coordinateList.append( southernHandle );
125 
126         coordinateList.append( ring.at( NorthEast ).interpolate( ring.at( SouthEast ), 0.5 ) );
127         coordinateList.append( ring.at( NorthWest ).interpolate( ring.at( SouthWest ), 0.5 ) );
128 
129         m_regionList.reserve(9);
130         m_regionList.append( painter->regionFromEllipse( coordinateList.at( NorthWest ), 16, 16 ) );
131         m_regionList.append( painter->regionFromEllipse( coordinateList.at( SouthWest ), 16, 16 ) );
132         m_regionList.append( painter->regionFromEllipse( coordinateList.at( SouthEast ), 16, 16 ) );
133         m_regionList.append( painter->regionFromEllipse( coordinateList.at( NorthEast ), 16, 16 ) );
134         m_regionList.append( painter->regionFromEllipse( coordinateList.at( North ), 16, 16 ) );
135         m_regionList.append( painter->regionFromEllipse( coordinateList.at( South ), 16, 16 ) );
136         m_regionList.append( painter->regionFromEllipse( coordinateList.at( East ),  16, 16 ) );
137         m_regionList.append( painter->regionFromEllipse( coordinateList.at( West ),  16, 16 ) );
138         m_regionList.append( painter->regionFromPolygon( ring, Qt::OddEvenFill ) );
139 
140         // Calculate handle icon orientation due to the projection
141         qreal xNW, yNW, xSW, ySW;
142         viewport->screenCoordinates(ring.at( NorthWest ), xNW, yNW);
143         viewport->screenCoordinates(ring.at( SouthWest ), xSW, ySW);
144         qreal westernAngle = qAtan2(ySW - yNW, xSW - xNW) - M_PI/2;
145         qreal xNE, yNE, xSE, ySE;
146         viewport->screenCoordinates(ring.at( NorthEast ), xNE, yNE);
147         viewport->screenCoordinates(ring.at( SouthEast ), xSE, ySE);
148         qreal easternAngle = qAtan2(ySE - yNE, xSE - xNE) - M_PI/2;
149 
150         painter->setPen( Qt::DashLine );
151         painter->setBrush( Qt::NoBrush );
152         painter->drawPolygon( ring );
153 
154         qreal projectedAngle = 0;
155 
156         for( int i = NorthWest; i != Polygon; ++i ) {
157 
158             // Assign handle icon orientation due to the projection
159             if (i == NorthWest || i == West || i == SouthWest) {
160                 projectedAngle = westernAngle;
161             }
162             else if (i == NorthEast || i == East || i == SouthEast) {
163                 projectedAngle = easternAngle;
164             }
165             else if (i == North || i == South) {
166                 projectedAngle = (westernAngle + easternAngle) / 2;
167             }
168             QTransform trans;
169             trans.rotateRadians( projectedAngle );
170             if ( m_editStatus == Resize ){
171                 if( m_hoveredHandle != i ) {
172                     painter->drawImage( coordinateList.at( i ),
173                                         m_resizeIcons.at( 2*i ).transformed( trans, Qt::SmoothTransformation ) );
174                 } else {
175                     painter->drawImage( coordinateList.at( i ),
176                                         m_resizeIcons.at( 2*i + 1 ).transformed( trans, Qt::SmoothTransformation ) );
177                 }
178             } else if ( m_editStatus == Rotate ) {
179                 if( m_hoveredHandle != i ) {
180                     painter->drawImage( coordinateList.at( i ),
181                                         m_rotateIcons.at( 2*i ).transformed( trans, Qt::SmoothTransformation ) );
182                 } else {
183                     painter->drawImage( coordinateList.at( i ),
184                                         m_rotateIcons.at( 2*i + 1 ).transformed( trans, Qt::SmoothTransformation ) );
185                 }
186             }
187         }
188     }
189     painter->restore();
190 }
191 
containsPoint(const QPoint & eventPos) const192 bool GroundOverlayFrame::containsPoint( const QPoint &eventPos ) const
193 {
194     for ( const QRegion &region: m_regionList ) {
195         if ( region.contains( eventPos ) ) {
196             return true;
197         }
198     }
199 
200     // This is a bugfix to handle the events even if they occur outside of this object,
201     // so when rotating or resizing the mouseReleaseEvent is handled successfully
202     // TODO: maybe find a better way?
203     return m_movedHandle   != NoRegion ||
204         m_hoveredHandle != NoRegion;
205 }
206 
dealWithItemChange(const SceneGraphicsItem * other)207 void GroundOverlayFrame::dealWithItemChange( const SceneGraphicsItem *other )
208 {
209     Q_UNUSED( other );
210 }
211 
move(const GeoDataCoordinates & source,const GeoDataCoordinates & destination)212 void GroundOverlayFrame::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination )
213 {
214     // not implemented yet
215     Q_UNUSED( source );
216     Q_UNUSED( destination );
217 }
218 
mousePressEvent(QMouseEvent * event)219 bool GroundOverlayFrame::mousePressEvent( QMouseEvent *event )
220 {
221     // React to all ellipse as well as to the polygon.
222     for ( int i = 0; i < m_regionList.size(); ++i ) {
223         if ( m_regionList.at(i).contains( event->pos() ) ) {
224             m_movedHandle = i;
225 
226             qreal lon, lat;
227             m_viewport->geoCoordinates( event->pos().x(),
228                                         event->pos().y(),
229                                         lon, lat,
230                                         GeoDataCoordinates::Radian );
231             m_movedHandleGeoCoordinates.set( lon, lat );
232             m_movedHandleScreenCoordinates = event->pos();
233             m_previousRotation = m_overlay->latLonBox().rotation();
234 
235             if ( m_movedHandle == Polygon ) {
236                 m_editStatusChangeNeeded = true;
237             }
238 
239             return true;
240         }
241     }
242 
243     return false;
244 }
245 
mouseMoveEvent(QMouseEvent * event)246 bool GroundOverlayFrame::mouseMoveEvent( QMouseEvent *event )
247 {
248     if ( !m_viewport ) {
249         return false;
250     }
251 
252     // Catch hover events.
253     if ( m_movedHandle == NoRegion ) {
254         for ( int i = 0; i < m_regionList.size(); ++i ) {
255             if ( m_regionList.at(i).contains( event->pos() ) ) {
256                 if ( i == Polygon ) {
257                     setRequest( ChangeCursorOverlayBodyHover );
258                 } else {
259                     setRequest( ChangeCursorOverlayRotateHover );
260                 }
261                 m_hoveredHandle = i;
262                 return true;
263             }
264         }
265         m_hoveredHandle = NoRegion;
266         return true;
267     } else {
268         m_editStatusChangeNeeded = false;
269     }
270 
271     if (geodata_cast<GeoDataPolygon>(placemark()->geometry())) {
272         qreal lon, lat;
273         m_viewport->geoCoordinates( event->pos().x(),
274                                     event->pos().y(),
275                                     lon, lat,
276                                     GeoDataCoordinates::Radian );
277 
278         if ( m_editStatus == Resize ) {
279 
280             GeoDataCoordinates coord(lon, lat);
281             GeoDataCoordinates rotatedCoord(coord);
282 
283             if (m_overlay->latLonBox().rotation() != 0) {
284                 rotatedCoord = coord.rotateAround(m_overlay->latLonBox().center(),
285                                                  -m_overlay->latLonBox().rotation());
286             }
287 
288             if ( m_movedHandle == NorthWest ) {
289                 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() );
290                 m_overlay->latLonBox().setWest( rotatedCoord.longitude() );
291             } else if ( m_movedHandle == SouthWest ) {
292                 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() );
293                 m_overlay->latLonBox().setWest( rotatedCoord.longitude() );
294             } else if ( m_movedHandle == SouthEast ) {
295                 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() );
296                 m_overlay->latLonBox().setEast( rotatedCoord.longitude() );
297             } else if ( m_movedHandle == NorthEast ) {
298                 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() );
299                 m_overlay->latLonBox().setEast( rotatedCoord.longitude() );
300             } else if ( m_movedHandle == North ) {
301                 m_overlay->latLonBox().setNorth( rotatedCoord.latitude() );
302             } else if ( m_movedHandle == South ) {
303                 m_overlay->latLonBox().setSouth( rotatedCoord.latitude() );
304             } else if ( m_movedHandle == East ) {
305                 m_overlay->latLonBox().setEast( rotatedCoord.longitude() );
306             } else if ( m_movedHandle == West ) {
307                 m_overlay->latLonBox().setWest( rotatedCoord.longitude() );
308             }
309 
310         } else if ( m_editStatus == Rotate ) {
311             if ( m_movedHandle != Polygon ) {
312                 QPoint center = m_regionList.at( Polygon ).boundingRect().center();
313                 qreal angle1 = qAtan2( event->pos().y() - center.y(),
314                                        event->pos().x() - center.x() );
315                 qreal angle2 = qAtan2( m_movedHandleScreenCoordinates.y() - center.y(),
316                                        m_movedHandleScreenCoordinates.x() - center.x() );
317                 m_overlay->latLonBox().setRotation( angle2 - angle1 + m_previousRotation );
318             }
319         }
320 
321         if ( m_movedHandle == Polygon ) {
322             const qreal centerLonDiff = lon - m_movedHandleGeoCoordinates.longitude();
323             const qreal centerLatDiff = lat - m_movedHandleGeoCoordinates.latitude();
324 
325             m_overlay->latLonBox().setBoundaries( m_overlay->latLonBox().north() + centerLatDiff,
326                                                   m_overlay->latLonBox().south() + centerLatDiff,
327                                                   m_overlay->latLonBox().east()  + centerLonDiff,
328                                                   m_overlay->latLonBox().west()  + centerLonDiff );
329 
330             m_movedHandleGeoCoordinates.set( lon, lat );
331         }
332 
333         update();
334         return true;
335     }
336     return false;
337 }
338 
mouseReleaseEvent(QMouseEvent * event)339 bool GroundOverlayFrame::mouseReleaseEvent( QMouseEvent *event )
340 {
341     Q_UNUSED( event );
342 
343     m_movedHandle = NoRegion;
344     m_textureLayer->reset();
345 
346     if( m_editStatusChangeNeeded ) {
347         if( m_editStatus == Resize ) {
348             m_editStatus = Rotate;
349         } else {
350             m_editStatus = Resize;
351         }
352     }
353 
354     return true;
355 }
356 
update()357 void GroundOverlayFrame::update()
358 {
359     GeoDataLatLonBox overlayLatLonBox = m_overlay->latLonBox();
360     GeoDataPolygon *poly = dynamic_cast<GeoDataPolygon*>( placemark()->geometry() );
361     poly->outerBoundary().clear();
362 
363     GeoDataCoordinates rotatedCoord;
364 
365     GeoDataCoordinates northWest(overlayLatLonBox.west(), overlayLatLonBox.north());
366     rotatedCoord = northWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
367     poly->outerBoundary().append( rotatedCoord );
368 
369     GeoDataCoordinates southWest(overlayLatLonBox.west(), overlayLatLonBox.south());
370     rotatedCoord = southWest.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
371     poly->outerBoundary().append( rotatedCoord );
372 
373     GeoDataCoordinates southEast(overlayLatLonBox.east(), overlayLatLonBox.south());
374     rotatedCoord = southEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
375     poly->outerBoundary().append( rotatedCoord );
376 
377     GeoDataCoordinates northEast(overlayLatLonBox.east(), overlayLatLonBox.north());
378     rotatedCoord = northEast.rotateAround(overlayLatLonBox.center(), overlayLatLonBox.rotation());
379     poly->outerBoundary().append( rotatedCoord );
380 }
381 
dealWithStateChange(SceneGraphicsItem::ActionState previousState)382 void GroundOverlayFrame::dealWithStateChange( SceneGraphicsItem::ActionState previousState )
383 {
384     Q_UNUSED( previousState );
385 }
386 
graphicType() const387 const char *GroundOverlayFrame::graphicType() const
388 {
389     return SceneGraphicsTypes::SceneGraphicGroundOverlay;
390 }
391 
392 
393 }
394