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 ®ion: 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