1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtPositioning module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qgeorectangle.h" |
41 | #include "qgeorectangle_p.h" |
42 | |
43 | #include "qwebmercator_p.h" |
44 | #include "qdoublevector2d_p.h" |
45 | #include "qgeocoordinate.h" |
46 | #include "qnumeric.h" |
47 | #include "qlocationutils_p.h" |
48 | #include <QList> |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | /*! |
52 | \class QGeoRectangle |
53 | \inmodule QtPositioning |
54 | \ingroup QtPositioning-positioning |
55 | \since 5.2 |
56 | |
57 | \brief The QGeoRectangle class defines a rectangular geographic area. |
58 | |
59 | The rectangle is defined in terms of a QGeoCoordinate which specifies the |
60 | top left coordinate of the rectangle and a QGeoCoordinate which specifies |
61 | the bottom right coordinate of the rectangle. |
62 | |
63 | A geo rectangle is considered invalid if the top left or bottom right |
64 | coordinates are invalid or if the top left coordinate is south of the |
65 | bottom right coordinate. |
66 | |
67 | Geo rectangles can never cross the poles. |
68 | |
69 | Several methods behave as though the geo rectangle is defined in terms of a |
70 | center coordinate, the width of the geo rectangle in degrees and the height |
71 | of the geo rectangle in degrees. |
72 | |
73 | If the height or center of a geo rectangle is adjusted such that it would |
74 | cross one of the poles the height is modified such that the geo rectangle |
75 | touches but does not cross the pole and that the center coordinate is still |
76 | in the center of the geo rectangle. |
77 | |
78 | This class is a \l Q_GADGET since Qt 5.5. It can be |
79 | \l{Cpp_value_integration_positioning}{directly used from C++ and QML}. |
80 | */ |
81 | |
82 | /*! |
83 | \property QGeoRectangle::bottomLeft |
84 | \brief This property holds the bottom left coorindate of this geo rectangle. |
85 | |
86 | While this property is introduced in Qt 5.5, the related accessor functions |
87 | exist since the first version of this class. |
88 | |
89 | \since 5.5 |
90 | */ |
91 | |
92 | /*! |
93 | \property QGeoRectangle::bottomRight |
94 | \brief This property holds the bottom right coordinate of this geo rectangle. |
95 | |
96 | While this property is introduced in Qt 5.5, the related accessor functions |
97 | exist since the first version of this class. |
98 | |
99 | \since 5.5 |
100 | */ |
101 | |
102 | /*! |
103 | \property QGeoRectangle::topLeft |
104 | \brief This property holds the top left coordinate of this geo rectangle. |
105 | |
106 | While this property is introduced in Qt 5.5, the related accessor functions |
107 | exist since the first version of this class. |
108 | |
109 | \since 5.5 |
110 | */ |
111 | |
112 | /*! |
113 | \property QGeoRectangle::topRight |
114 | \brief This property holds the top right coordinate of this geo rectangle. |
115 | |
116 | While this property is introduced in Qt 5.5, the related accessor functions |
117 | exist since the first version of this class. |
118 | |
119 | \since 5.5 |
120 | */ |
121 | |
122 | /*! |
123 | \property QGeoRectangle::center |
124 | \brief This property holds the center of this geo rectangle. |
125 | |
126 | While this property is introduced in Qt 5.5, the related accessor functions |
127 | exist since the first version of this class. |
128 | |
129 | \sa QGeoShape::center |
130 | |
131 | \since 5.5 |
132 | */ |
133 | |
134 | /*! |
135 | \property QGeoRectangle::width |
136 | \brief This property holds the width of this geo rectangle in degrees. |
137 | |
138 | The property value is undefined if this geo rectangle is invalid. |
139 | |
140 | If the new width is less than 0.0 or if this geo rectangle is invalid, this |
141 | function does nothing. To set up the values of an invalid |
142 | geo rectangle based on the center, width, and height, you should use |
143 | \l setCenter() first to make the geo rectangle valid. |
144 | |
145 | 360.0 is the width used only if the new width is equal or greater than 360. |
146 | In such cases the leftmost longitude of the geo rectangle is set to -180.0 |
147 | degrees and the rightmost longitude of the geo rectangle is set to 180.0 |
148 | degrees. |
149 | |
150 | While this property is introduced in Qt 5.5, the related accessor functions |
151 | exist since the first version of this class. |
152 | |
153 | \since 5.5 |
154 | */ |
155 | |
156 | /*! |
157 | \property QGeoRectangle::height |
158 | \brief This property holds the height of this geo rectangle in degrees. |
159 | |
160 | The property value is undefined if this geo rectangle is invalid. |
161 | |
162 | If the new height is less than 0.0 or if this geo rectangle is invalid, |
163 | the property is not changed. To set up the values of an invalid |
164 | geo rectangle based on the center, width, and height, you should use |
165 | \l setCenter() first to make the geo rectangle valid. |
166 | |
167 | If the change in height would cause the geo rectangle to cross a pole, |
168 | the height is adjusted such that the geo rectangle only touches the pole. |
169 | |
170 | This change is done such that the center coordinate is still at the |
171 | center of the geo rectangle, which may result in a geo rectangle with |
172 | a smaller height than expected. |
173 | |
174 | 180.0 is the height used only if the new height is greater or equal than 180. |
175 | |
176 | While this property is introduced in Qt 5.5, the related accessor functions |
177 | exist since the first version of this class. |
178 | |
179 | \since 5.5 |
180 | */ |
181 | |
182 | inline QGeoRectanglePrivate *QGeoRectangle::d_func() |
183 | { |
184 | return static_cast<QGeoRectanglePrivate *>(d_ptr.data()); |
185 | } |
186 | |
187 | inline const QGeoRectanglePrivate *QGeoRectangle::d_func() const |
188 | { |
189 | return static_cast<const QGeoRectanglePrivate *>(d_ptr.constData()); |
190 | } |
191 | |
192 | struct RectangleVariantConversions |
193 | { |
194 | RectangleVariantConversions() |
195 | { |
196 | QMetaType::registerConverter<QGeoRectangle, QGeoShape>(); |
197 | QMetaType::registerConverter<QGeoShape, QGeoRectangle>(); |
198 | } |
199 | }; |
200 | |
201 | |
202 | Q_GLOBAL_STATIC(RectangleVariantConversions, initRectangleConversions) |
203 | |
204 | /*! |
205 | Constructs a new, invalid geo rectangle. |
206 | */ |
207 | QGeoRectangle::QGeoRectangle() |
208 | : QGeoShape(new QGeoRectanglePrivate) |
209 | { |
210 | initRectangleConversions(); |
211 | } |
212 | |
213 | /*! |
214 | Constructs a new geo rectangle centered at \a center with a |
215 | width in degrees of \a degreesWidth and a height in degrees of \a degreesHeight. |
216 | |
217 | If \a degreesHeight would take the geo rectangle beyond one of the poles, |
218 | the height of the geo rectangle will be truncated such that the geo rectangle |
219 | only extends up to the pole. The center of the geo rectangle will be |
220 | unchanged, and the height will be adjusted such that the center point is at |
221 | the center of the truncated geo rectangle. |
222 | */ |
223 | QGeoRectangle::QGeoRectangle(const QGeoCoordinate ¢er, double degreesWidth, double degreesHeight) |
224 | { |
225 | initRectangleConversions(); |
226 | d_ptr = new QGeoRectanglePrivate(center, center); |
227 | setWidth(degreesWidth); |
228 | setHeight(degreesHeight); |
229 | } |
230 | |
231 | /*! |
232 | Constructs a new geo rectangle with a top left coordinate \a topLeft and a bottom right |
233 | coordinate \a bottomRight. |
234 | */ |
235 | QGeoRectangle::QGeoRectangle(const QGeoCoordinate &topLeft, const QGeoCoordinate &bottomRight) |
236 | { |
237 | initRectangleConversions(); |
238 | d_ptr = new QGeoRectanglePrivate(topLeft, bottomRight); |
239 | } |
240 | |
241 | /*! |
242 | Constructs a new geo rectangle, of minimum size, containing all of the \a coordinates. |
243 | */ |
244 | QGeoRectangle::QGeoRectangle(const QList<QGeoCoordinate> &coordinates) |
245 | { |
246 | initRectangleConversions(); |
247 | if (coordinates.isEmpty()) { |
248 | d_ptr = new QGeoRectanglePrivate; |
249 | } else { |
250 | const QGeoCoordinate &startCoordinate = coordinates.first(); |
251 | d_ptr = new QGeoRectanglePrivate(startCoordinate, startCoordinate); |
252 | |
253 | foreach (const QGeoCoordinate &coordinate, coordinates) { |
254 | d_ptr->extendShape(coordinate); |
255 | } |
256 | } |
257 | } |
258 | |
259 | /*! |
260 | Constructs a geo rectangle from the contents of \a other. |
261 | */ |
262 | QGeoRectangle::QGeoRectangle(const QGeoRectangle &other) |
263 | : QGeoShape(other) |
264 | { |
265 | initRectangleConversions(); |
266 | } |
267 | |
268 | /*! |
269 | Constructs a geo rectangle from the contents of \a other. |
270 | */ |
271 | QGeoRectangle::QGeoRectangle(const QGeoShape &other) |
272 | : QGeoShape(other) |
273 | { |
274 | initRectangleConversions(); |
275 | if (type() != QGeoShape::RectangleType) |
276 | d_ptr = new QGeoRectanglePrivate; |
277 | } |
278 | |
279 | /*! |
280 | Destroys this geo rectangle. |
281 | */ |
282 | QGeoRectangle::~QGeoRectangle() |
283 | { |
284 | } |
285 | |
286 | /*! |
287 | Assigns \a other to this geo rectangle and returns a reference to this geo rectangle. |
288 | */ |
289 | QGeoRectangle &QGeoRectangle::operator=(const QGeoRectangle &other) |
290 | { |
291 | QGeoShape::operator=(other); |
292 | return *this; |
293 | } |
294 | |
295 | /*! |
296 | Returns whether this geo rectangle is equal to \a other. |
297 | */ |
298 | bool QGeoRectangle::operator==(const QGeoRectangle &other) const |
299 | { |
300 | Q_D(const QGeoRectangle); |
301 | |
302 | return *d == *other.d_func(); |
303 | } |
304 | |
305 | /*! |
306 | Returns whether this geo rectangle is not equal to \a other. |
307 | */ |
308 | bool QGeoRectangle::operator!=(const QGeoRectangle &other) const |
309 | { |
310 | Q_D(const QGeoRectangle); |
311 | |
312 | return !(*d == *other.d_func()); |
313 | } |
314 | |
315 | bool QGeoRectanglePrivate::isValid() const |
316 | { |
317 | return topLeft.isValid() && bottomRight.isValid() && |
318 | topLeft.latitude() >= bottomRight.latitude(); |
319 | } |
320 | |
321 | bool QGeoRectanglePrivate::isEmpty() const |
322 | { |
323 | if (!isValid()) |
324 | return true; |
325 | |
326 | return topLeft.latitude() == bottomRight.latitude() || |
327 | topLeft.longitude() == bottomRight.longitude(); |
328 | } |
329 | |
330 | /*! |
331 | Sets the top left coordinate of this geo rectangle to \a topLeft. |
332 | */ |
333 | void QGeoRectangle::setTopLeft(const QGeoCoordinate &topLeft) |
334 | { |
335 | Q_D(QGeoRectangle); |
336 | |
337 | d->topLeft = topLeft; |
338 | } |
339 | |
340 | /*! |
341 | Returns the top left coordinate of this geo rectangle. |
342 | */ |
343 | QGeoCoordinate QGeoRectangle::topLeft() const |
344 | { |
345 | Q_D(const QGeoRectangle); |
346 | |
347 | return d->topLeft; |
348 | } |
349 | |
350 | /*! |
351 | Sets the top right coordinate of this geo rectangle to \a topRight. |
352 | */ |
353 | void QGeoRectangle::setTopRight(const QGeoCoordinate &topRight) |
354 | { |
355 | Q_D(QGeoRectangle); |
356 | |
357 | d->topLeft.setLatitude(topRight.latitude()); |
358 | d->bottomRight.setLongitude(topRight.longitude()); |
359 | } |
360 | |
361 | /*! |
362 | Returns the top right coordinate of this geo rectangle. |
363 | */ |
364 | QGeoCoordinate QGeoRectangle::topRight() const |
365 | { |
366 | // TODO remove? |
367 | if (!isValid()) |
368 | return QGeoCoordinate(); |
369 | |
370 | Q_D(const QGeoRectangle); |
371 | |
372 | return QGeoCoordinate(d->topLeft.latitude(), d->bottomRight.longitude()); |
373 | } |
374 | |
375 | /*! |
376 | Sets the bottom left coordinate of this geo rectangle to \a bottomLeft. |
377 | */ |
378 | void QGeoRectangle::setBottomLeft(const QGeoCoordinate &bottomLeft) |
379 | { |
380 | Q_D(QGeoRectangle); |
381 | |
382 | d->bottomRight.setLatitude(bottomLeft.latitude()); |
383 | d->topLeft.setLongitude(bottomLeft.longitude()); |
384 | } |
385 | |
386 | /*! |
387 | Returns the bottom left coordinate of this geo rectangle. |
388 | */ |
389 | QGeoCoordinate QGeoRectangle::bottomLeft() const |
390 | { |
391 | // TODO remove? |
392 | if (!isValid()) |
393 | return QGeoCoordinate(); |
394 | |
395 | Q_D(const QGeoRectangle); |
396 | |
397 | return QGeoCoordinate(d->bottomRight.latitude(), d->topLeft.longitude()); |
398 | } |
399 | |
400 | /*! |
401 | Sets the bottom right coordinate of this geo rectangle to \a bottomRight. |
402 | */ |
403 | void QGeoRectangle::setBottomRight(const QGeoCoordinate &bottomRight) |
404 | { |
405 | Q_D(QGeoRectangle); |
406 | |
407 | d->bottomRight = bottomRight; |
408 | } |
409 | |
410 | /*! |
411 | Returns the bottom right coordinate of this geo rectangle. |
412 | */ |
413 | QGeoCoordinate QGeoRectangle::bottomRight() const |
414 | { |
415 | Q_D(const QGeoRectangle); |
416 | |
417 | return d->bottomRight; |
418 | } |
419 | |
420 | /*! |
421 | Sets the center of this geo rectangle to \a center. |
422 | |
423 | If this causes the geo rectangle to cross on of the poles the height of the |
424 | geo rectangle will be truncated such that the geo rectangle only extends up |
425 | to the pole. The center of the geo rectangle will be unchanged, and the |
426 | height will be adjusted such that the center point is at the center of the |
427 | truncated geo rectangle. |
428 | |
429 | */ |
430 | void QGeoRectangle::setCenter(const QGeoCoordinate ¢er) |
431 | { |
432 | Q_D(QGeoRectangle); |
433 | |
434 | if (!isValid()) { |
435 | d->topLeft = center; |
436 | d->bottomRight = center; |
437 | return; |
438 | } |
439 | double width = this->width(); |
440 | double height = this->height(); |
441 | |
442 | double tlLat = center.latitude() + height / 2.0; |
443 | double tlLon = center.longitude() - width / 2.0; |
444 | double brLat = center.latitude() - height / 2.0; |
445 | double brLon = center.longitude() + width / 2.0; |
446 | tlLon = QLocationUtils::wrapLong(lng: tlLon); |
447 | brLon = QLocationUtils::wrapLong(lng: brLon); |
448 | |
449 | if (tlLat > 90.0) { |
450 | brLat = 2 * center.latitude() - 90.0; |
451 | tlLat = 90.0; |
452 | } |
453 | |
454 | if (tlLat < -90.0) { |
455 | brLat = -90.0; |
456 | tlLat = -90.0; |
457 | } |
458 | |
459 | if (brLat > 90.0) { |
460 | tlLat = 90.0; |
461 | brLat = 90.0; |
462 | } |
463 | |
464 | if (brLat < -90.0) { |
465 | tlLat = 2 * center.latitude() + 90.0; |
466 | brLat = -90.0; |
467 | } |
468 | |
469 | if (width == 360.0) { |
470 | tlLon = -180.0; |
471 | brLon = 180.0; |
472 | } |
473 | |
474 | d->topLeft = QGeoCoordinate(tlLat, tlLon); |
475 | d->bottomRight = QGeoCoordinate(brLat, brLon); |
476 | } |
477 | |
478 | /*! |
479 | Returns the center of this geo rectangle. Equivalent to QGeoShape::center(). |
480 | */ |
481 | QGeoCoordinate QGeoRectangle::center() const |
482 | { |
483 | Q_D(const QGeoRectangle); |
484 | |
485 | return d->center(); |
486 | } |
487 | |
488 | /*! |
489 | Sets the width of this geo rectangle in degrees to \a degreesWidth. |
490 | */ |
491 | void QGeoRectangle::setWidth(double degreesWidth) |
492 | { |
493 | if (!isValid()) |
494 | return; |
495 | |
496 | if (degreesWidth < 0.0) |
497 | return; |
498 | |
499 | Q_D(QGeoRectangle); |
500 | |
501 | if (degreesWidth >= 360.0) { |
502 | d->topLeft.setLongitude(-180.0); |
503 | d->bottomRight.setLongitude(180.0); |
504 | return; |
505 | } |
506 | |
507 | double tlLat = d->topLeft.latitude(); |
508 | double brLat = d->bottomRight.latitude(); |
509 | |
510 | QGeoCoordinate c = center(); |
511 | |
512 | double tlLon = c.longitude() - degreesWidth / 2.0; |
513 | tlLon = QLocationUtils::wrapLong(lng: tlLon); |
514 | |
515 | double brLon = c.longitude() + degreesWidth / 2.0; |
516 | brLon = QLocationUtils::wrapLong(lng: brLon); |
517 | |
518 | d->topLeft = QGeoCoordinate(tlLat, tlLon); |
519 | d->bottomRight = QGeoCoordinate(brLat, brLon); |
520 | } |
521 | |
522 | /*! |
523 | Returns the width of this geo rectangle in degrees. |
524 | |
525 | The return value is undefined if this geo rectangle is invalid. |
526 | */ |
527 | double QGeoRectangle::width() const |
528 | { |
529 | if (!isValid()) |
530 | return qQNaN(); |
531 | |
532 | Q_D(const QGeoRectangle); |
533 | |
534 | double result = d->bottomRight.longitude() - d->topLeft.longitude(); |
535 | if (result < 0.0) |
536 | result += 360.0; |
537 | if (result > 360.0) |
538 | result -= 360.0; |
539 | |
540 | return result; |
541 | } |
542 | |
543 | /*! |
544 | Sets the height of this geo rectangle in degrees to \a degreesHeight. |
545 | */ |
546 | void QGeoRectangle::setHeight(double degreesHeight) |
547 | { |
548 | if (!isValid()) |
549 | return; |
550 | |
551 | if (degreesHeight < 0.0) |
552 | return; |
553 | |
554 | if (degreesHeight >= 180.0) { |
555 | degreesHeight = 180.0; |
556 | } |
557 | |
558 | Q_D(QGeoRectangle); |
559 | |
560 | double tlLon = d->topLeft.longitude(); |
561 | double brLon = d->bottomRight.longitude(); |
562 | |
563 | QGeoCoordinate c = center(); |
564 | |
565 | double tlLat = c.latitude() + degreesHeight / 2.0; |
566 | double brLat = c.latitude() - degreesHeight / 2.0; |
567 | |
568 | if (tlLat > 90.0) { |
569 | brLat = 2* c.latitude() - 90.0; |
570 | tlLat = 90.0; |
571 | } |
572 | |
573 | if (tlLat < -90.0) { |
574 | brLat = -90.0; |
575 | tlLat = -90.0; |
576 | } |
577 | |
578 | if (brLat > 90.0) { |
579 | tlLat = 90.0; |
580 | brLat = 90.0; |
581 | } |
582 | |
583 | if (brLat < -90.0) { |
584 | tlLat = 2 * c.latitude() + 90.0; |
585 | brLat = -90.0; |
586 | } |
587 | |
588 | d->topLeft = QGeoCoordinate(tlLat, tlLon); |
589 | d->bottomRight = QGeoCoordinate(brLat, brLon); |
590 | } |
591 | |
592 | /*! |
593 | Returns the height of this geo rectangle in degrees. |
594 | |
595 | The return value is undefined if this geo rectangle is invalid. |
596 | */ |
597 | double QGeoRectangle::height() const |
598 | { |
599 | if (!isValid()) |
600 | return qQNaN(); |
601 | |
602 | Q_D(const QGeoRectangle); |
603 | |
604 | return d->topLeft.latitude() - d->bottomRight.latitude(); |
605 | } |
606 | |
607 | bool QGeoRectanglePrivate::contains(const QGeoCoordinate &coordinate) const |
608 | { |
609 | if (!isValid() || !coordinate.isValid()) |
610 | return false; |
611 | |
612 | double left = topLeft.longitude(); |
613 | double right = bottomRight.longitude(); |
614 | double top = topLeft.latitude(); |
615 | double bottom = bottomRight.latitude(); |
616 | |
617 | double lon = coordinate.longitude(); |
618 | double lat = coordinate.latitude(); |
619 | |
620 | if (lat > top) |
621 | return false; |
622 | if (lat < bottom) |
623 | return false; |
624 | |
625 | if ((lat == 90.0) && (top == 90.0)) |
626 | return true; |
627 | |
628 | if ((lat == -90.0) && (bottom == -90.0)) |
629 | return true; |
630 | |
631 | if (left <= right) { |
632 | if ((lon < left) || (lon > right)) |
633 | return false; |
634 | } else { |
635 | if ((lon < left) && (lon > right)) |
636 | return false; |
637 | } |
638 | |
639 | return true; |
640 | } |
641 | |
642 | QGeoCoordinate QGeoRectanglePrivate::center() const |
643 | { |
644 | if (!isValid()) |
645 | return QGeoCoordinate(); |
646 | |
647 | double cLat = (topLeft.latitude() + bottomRight.latitude()) / 2.0; |
648 | double cLon = (bottomRight.longitude() + topLeft.longitude()) / 2.0; |
649 | |
650 | if (topLeft.longitude() > bottomRight.longitude()) |
651 | cLon = cLon - 180.0; |
652 | |
653 | cLon = QLocationUtils::wrapLong(lng: cLon); |
654 | return QGeoCoordinate(cLat, cLon); |
655 | } |
656 | |
657 | QGeoRectangle QGeoRectanglePrivate::boundingGeoRectangle() const |
658 | { |
659 | return QGeoRectangle(topLeft, bottomRight); |
660 | } |
661 | |
662 | /*! |
663 | Returns whether the geo rectangle \a rectangle is contained within this |
664 | geo rectangle. |
665 | */ |
666 | bool QGeoRectangle::contains(const QGeoRectangle &rectangle) const |
667 | { |
668 | Q_D(const QGeoRectangle); |
669 | |
670 | return (d->contains(coordinate: rectangle.topLeft()) |
671 | && d->contains(coordinate: rectangle.topRight()) |
672 | && d->contains(coordinate: rectangle.bottomLeft()) |
673 | && d->contains(coordinate: rectangle.bottomRight())); |
674 | } |
675 | |
676 | /*! |
677 | Returns whether the geo rectangle \a rectangle intersects this geo rectangle. |
678 | |
679 | If the top or bottom edges of both geo rectangles are at one of the poles |
680 | the geo rectangles are considered to be intersecting, since the longitude |
681 | is irrelevant when the edges are at the pole. |
682 | */ |
683 | bool QGeoRectangle::intersects(const QGeoRectangle &rectangle) const |
684 | { |
685 | Q_D(const QGeoRectangle); |
686 | |
687 | double left1 = d->topLeft.longitude(); |
688 | double right1 = d->bottomRight.longitude(); |
689 | double top1 = d->topLeft.latitude(); |
690 | double bottom1 = d->bottomRight.latitude(); |
691 | |
692 | double left2 = rectangle.d_func()->topLeft.longitude(); |
693 | double right2 = rectangle.d_func()->bottomRight.longitude(); |
694 | double top2 = rectangle.d_func()->topLeft.latitude(); |
695 | double bottom2 = rectangle.d_func()->bottomRight.latitude(); |
696 | |
697 | if (top1 < bottom2) |
698 | return false; |
699 | |
700 | if (bottom1 > top2) |
701 | return false; |
702 | |
703 | if ((top1 == 90.0) && (top1 == top2)) |
704 | return true; |
705 | |
706 | if ((bottom1 == -90.0) && (bottom1 == bottom2)) |
707 | return true; |
708 | |
709 | if (left1 < right1) { |
710 | if (left2 < right2) { |
711 | if ((left1 > right2) || (right1 < left2)) |
712 | return false; |
713 | } else { |
714 | if ((left1 > right2) && (right1 < left2)) |
715 | return false; |
716 | } |
717 | } else { |
718 | if (left2 < right2) { |
719 | if ((left2 > right1) && (right2 < left1)) |
720 | return false; |
721 | } else { |
722 | // if both wrap then they have to intersect |
723 | } |
724 | } |
725 | |
726 | return true; |
727 | } |
728 | |
729 | /*! |
730 | Translates this geo rectangle by \a degreesLatitude northwards and \a |
731 | degreesLongitude eastwards. |
732 | |
733 | Negative values of \a degreesLatitude and \a degreesLongitude correspond to |
734 | southward and westward translation respectively. |
735 | |
736 | If the translation would have caused the geo rectangle to cross a pole the |
737 | geo rectangle will be translated until the top or bottom edge of the geo rectangle |
738 | touches the pole but not further. |
739 | */ |
740 | void QGeoRectangle::translate(double degreesLatitude, double degreesLongitude) |
741 | { |
742 | // TODO handle dlat, dlon larger than 360 degrees |
743 | |
744 | Q_D(QGeoRectangle); |
745 | |
746 | double tlLat = d->topLeft.latitude(); |
747 | double tlLon = d->topLeft.longitude(); |
748 | double brLat = d->bottomRight.latitude(); |
749 | double brLon = d->bottomRight.longitude(); |
750 | |
751 | if (degreesLatitude >= 0.0) |
752 | degreesLatitude = qMin(a: degreesLatitude, b: 90.0 - tlLat); |
753 | else |
754 | degreesLatitude = qMax(a: degreesLatitude, b: -90.0 - brLat); |
755 | |
756 | if ( (tlLon != -180.0) || (brLon != 180.0) ) { |
757 | tlLon = QLocationUtils::wrapLong(lng: tlLon + degreesLongitude); |
758 | brLon = QLocationUtils::wrapLong(lng: brLon + degreesLongitude); |
759 | } |
760 | |
761 | tlLat += degreesLatitude; |
762 | brLat += degreesLatitude; |
763 | |
764 | d->topLeft = QGeoCoordinate(tlLat, tlLon); |
765 | d->bottomRight = QGeoCoordinate(brLat, brLon); |
766 | } |
767 | |
768 | /*! |
769 | Returns a copy of this geo rectangle translated by \a degreesLatitude northwards and \a |
770 | degreesLongitude eastwards. |
771 | |
772 | Negative values of \a degreesLatitude and \a degreesLongitude correspond to |
773 | southward and westward translation respectively. |
774 | |
775 | \sa translate() |
776 | */ |
777 | QGeoRectangle QGeoRectangle::translated(double degreesLatitude, double degreesLongitude) const |
778 | { |
779 | QGeoRectangle result(*this); |
780 | result.translate(degreesLatitude, degreesLongitude); |
781 | return result; |
782 | } |
783 | |
784 | /*! |
785 | Extends the geo rectangle to also cover the coordinate \a coordinate |
786 | |
787 | \since 5.9 |
788 | */ |
789 | void QGeoRectangle::extendRectangle(const QGeoCoordinate &coordinate) |
790 | { |
791 | Q_D(QGeoRectangle); |
792 | d->extendShape(coordinate); |
793 | } |
794 | |
795 | /*! |
796 | Returns the smallest geo rectangle which contains both this geo rectangle and \a rectangle. |
797 | |
798 | If the centers of the two geo rectangles are separated by exactly 180.0 degrees then the |
799 | width is set to 360.0 degrees with the leftmost longitude set to -180.0 degrees and the |
800 | rightmost longitude set to 180.0 degrees. This is done to ensure that the result is |
801 | independent of the order of the operands. |
802 | |
803 | */ |
804 | QGeoRectangle QGeoRectangle::united(const QGeoRectangle &rectangle) const |
805 | { |
806 | QGeoRectangle result(*this); |
807 | if (rectangle.isValid()) |
808 | result |= rectangle; |
809 | return result; |
810 | } |
811 | |
812 | /*! |
813 | Extends the rectangle in the smallest possible way to include \a coordinate in |
814 | the shape. |
815 | |
816 | Both the rectangle and coordinate needs to be valid. If the rectangle already covers |
817 | the coordinate noting happens. |
818 | |
819 | */ |
820 | void QGeoRectanglePrivate::extendShape(const QGeoCoordinate &coordinate) |
821 | { |
822 | if (!isValid() || !coordinate.isValid() || contains(coordinate)) |
823 | return; |
824 | |
825 | double left = topLeft.longitude(); |
826 | double right = bottomRight.longitude(); |
827 | double top = topLeft.latitude(); |
828 | double bottom = bottomRight.latitude(); |
829 | |
830 | double inputLat = coordinate.latitude(); |
831 | double inputLon = coordinate.longitude(); |
832 | |
833 | top = qMax(a: top, b: inputLat); |
834 | bottom = qMin(a: bottom, b: inputLat); |
835 | |
836 | bool wrap = left > right; |
837 | |
838 | if (wrap && inputLon > right && inputLon < left) { |
839 | if (qAbs(t: left - inputLon) < qAbs(t: right - inputLon)) |
840 | left = inputLon; |
841 | else |
842 | right = inputLon; |
843 | } else if (!wrap) { |
844 | if (inputLon < left) { |
845 | if (360 - (right - inputLon) < left - inputLon) |
846 | right = inputLon; |
847 | else |
848 | left = inputLon; |
849 | } else if (inputLon > right) { |
850 | if (360 - (inputLon - left) < inputLon - right) |
851 | left = inputLon; |
852 | else |
853 | right = inputLon; |
854 | } |
855 | } |
856 | topLeft = QGeoCoordinate(top, left); |
857 | bottomRight = QGeoCoordinate(bottom, right); |
858 | } |
859 | |
860 | /*! |
861 | \fn QGeoRectangle QGeoRectangle::operator|(const QGeoRectangle &rectangle) const |
862 | |
863 | Returns the smallest geo rectangle which contains both this geo rectangle and \a rectangle. |
864 | |
865 | If the centers of the two geo rectangles are separated by exactly 180.0 degrees then the |
866 | width is set to 360.0 degrees with the leftmost longitude set to -180.0 degrees and the |
867 | rightmost longitude set to 180.0 degrees. This is done to ensure that the result is |
868 | independent of the order of the operands. |
869 | |
870 | */ |
871 | |
872 | /*! |
873 | Returns the smallest geo rectangle which contains both this geo rectangle and \a rectangle. |
874 | |
875 | If the centers of the two geo rectangles are separated by exactly 180.0 degrees then the |
876 | width is set to 360.0 degrees with the leftmost longitude set to -180.0 degrees and the |
877 | rightmost longitude set to 180.0 degrees. This is done to ensure that the result is |
878 | independent of the order of the operands. |
879 | |
880 | */ |
881 | QGeoRectangle &QGeoRectangle::operator|=(const QGeoRectangle &rectangle) |
882 | { |
883 | // If non-intersecting goes for most narrow box |
884 | |
885 | Q_D(QGeoRectangle); |
886 | |
887 | double left1 = d->topLeft.longitude(); |
888 | double right1 = d->bottomRight.longitude(); |
889 | double top1 = d->topLeft.latitude(); |
890 | double bottom1 = d->bottomRight.latitude(); |
891 | |
892 | double left2 = rectangle.d_func()->topLeft.longitude(); |
893 | double right2 = rectangle.d_func()->bottomRight.longitude(); |
894 | double top2 = rectangle.d_func()->topLeft.latitude(); |
895 | double bottom2 = rectangle.d_func()->bottomRight.latitude(); |
896 | |
897 | double top = qMax(a: top1, b: top2); |
898 | double bottom = qMin(a: bottom1, b: bottom2); |
899 | |
900 | double left = 0.0; |
901 | double right = 0.0; |
902 | |
903 | bool wrap1 = (left1 > right1); |
904 | bool wrap2 = (left2 > right2); |
905 | |
906 | if ((wrap1 && wrap2) || (!wrap1 && !wrap2)) { |
907 | |
908 | double w = qAbs(t: (left1 + right1 - left2 - right2) / 2.0); |
909 | |
910 | if (w < 180.0) { |
911 | left = qMin(a: left1, b: left2); |
912 | right = qMax(a: right1, b: right2); |
913 | } else if (w > 180.0) { |
914 | left = qMax(a: left1, b: left2); |
915 | right = qMin(a: right1, b: right2); |
916 | } else { |
917 | left = -180.0; |
918 | right = 180.0; |
919 | } |
920 | |
921 | } else { |
922 | double wrapLeft = 0.0; |
923 | double wrapRight = 0.0; |
924 | double nonWrapLeft = 0.0; |
925 | double nonWrapRight = 0.0; |
926 | |
927 | if (wrap1) { |
928 | wrapLeft = left1; |
929 | wrapRight = right1; |
930 | nonWrapLeft = left2; |
931 | nonWrapRight = right2; |
932 | } else { |
933 | wrapLeft = left2; |
934 | wrapRight = right2; |
935 | nonWrapLeft = left1; |
936 | nonWrapRight = right1; |
937 | } |
938 | |
939 | bool joinWrapLeft = (nonWrapRight >= wrapLeft); |
940 | bool joinWrapRight = (nonWrapLeft <= wrapRight); |
941 | |
942 | if (wrapLeft <= nonWrapLeft) { // The wrapping rectangle contains the non-wrapping one entirely |
943 | left = wrapLeft; |
944 | right = wrapRight; |
945 | } else { |
946 | if (joinWrapLeft) { |
947 | if (joinWrapRight) { |
948 | left = -180.0; |
949 | right = 180.0; |
950 | } else { |
951 | left = nonWrapLeft; |
952 | right = wrapRight; |
953 | } |
954 | } else { |
955 | if (joinWrapRight) { |
956 | left = wrapLeft; |
957 | right = nonWrapRight; |
958 | } else { |
959 | double wrapRightDistance = nonWrapLeft - wrapRight; |
960 | double wrapLeftDistance = wrapLeft - nonWrapRight; |
961 | |
962 | if (wrapLeftDistance == wrapRightDistance) { |
963 | left = -180.0; |
964 | right = 180.0; |
965 | } else if (wrapLeftDistance < wrapRightDistance) { |
966 | left = nonWrapLeft; |
967 | right = wrapRight; |
968 | } else { |
969 | left = wrapLeft; |
970 | right = nonWrapRight; |
971 | } |
972 | } |
973 | } |
974 | } |
975 | } |
976 | |
977 | if (((left1 == -180) && (right1 == 180.0)) |
978 | || ((left2 == -180) && (right2 == 180.0))) { |
979 | left = -180; |
980 | right = 180; |
981 | } |
982 | |
983 | d->topLeft = QGeoCoordinate(top, left); |
984 | d->bottomRight = QGeoCoordinate(bottom, right); |
985 | |
986 | return *this; |
987 | } |
988 | |
989 | /*! |
990 | Returns the geo rectangle properties as a string. |
991 | |
992 | \since 5.5 |
993 | */ |
994 | QString QGeoRectangle::toString() const |
995 | { |
996 | if (type() != QGeoShape::RectangleType) { |
997 | qWarning(msg: "Not a rectangle a %d\n" , type()); |
998 | return QStringLiteral("QGeoRectangle(not a rectangle)" ); |
999 | } |
1000 | |
1001 | return QStringLiteral("QGeoRectangle({%1, %2}, {%3, %4})" ) |
1002 | .arg(a: topLeft().latitude()) |
1003 | .arg(a: topLeft().longitude()) |
1004 | .arg(a: bottomRight().latitude()) |
1005 | .arg(a: bottomRight().longitude()); |
1006 | } |
1007 | |
1008 | /******************************************************************************* |
1009 | *******************************************************************************/ |
1010 | |
1011 | QGeoRectanglePrivate::QGeoRectanglePrivate() |
1012 | : QGeoShapePrivate(QGeoShape::RectangleType) |
1013 | { |
1014 | } |
1015 | |
1016 | QGeoRectanglePrivate::QGeoRectanglePrivate(const QGeoCoordinate &topLeft, |
1017 | const QGeoCoordinate &bottomRight) |
1018 | : QGeoShapePrivate(QGeoShape::RectangleType), topLeft(topLeft), bottomRight(bottomRight) |
1019 | { |
1020 | } |
1021 | |
1022 | QGeoRectanglePrivate::QGeoRectanglePrivate(const QGeoRectanglePrivate &other) |
1023 | : QGeoShapePrivate(QGeoShape::RectangleType), topLeft(other.topLeft), |
1024 | bottomRight(other.bottomRight) |
1025 | { |
1026 | } |
1027 | |
1028 | QGeoRectanglePrivate::~QGeoRectanglePrivate() {} |
1029 | |
1030 | QGeoShapePrivate *QGeoRectanglePrivate::clone() const |
1031 | { |
1032 | return new QGeoRectanglePrivate(*this); |
1033 | } |
1034 | |
1035 | bool QGeoRectanglePrivate::operator==(const QGeoShapePrivate &other) const |
1036 | { |
1037 | if (!QGeoShapePrivate::operator==(other)) |
1038 | return false; |
1039 | |
1040 | const QGeoRectanglePrivate &otherBox = static_cast<const QGeoRectanglePrivate &>(other); |
1041 | |
1042 | return topLeft == otherBox.topLeft && bottomRight == otherBox.bottomRight; |
1043 | } |
1044 | |
1045 | QT_END_NAMESPACE |
1046 | |
1047 | |