Marble

GeoDataLatLonBox.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2007 Andrew Manson <g.real.ate@gmail.com>
4// SPDX-FileCopyrightText: 2008-2009 Torsten Rahn <rahn@kde.org>
5//
6
7#include "GeoDataLatLonBox.h"
8
9#include "GeoDataLineString.h"
10#include "MarbleDebug.h"
11
12#include "GeoDataTypes.h"
13
14#include <QDataStream>
15
16namespace Marble
17{
18
19const GeoDataLatLonBox GeoDataLatLonBox::empty = GeoDataLatLonBox();
20
21class GeoDataLatLonBoxPrivate
22{
23public:
24 GeoDataLatLonBoxPrivate()
25 : m_north(0.0)
26 , m_south(0.0)
27 , m_east(0.0)
28 , m_west(0.0)
29 , m_rotation(0.0)
30 {
31 }
32
33 qreal m_north;
34 qreal m_south;
35 qreal m_east;
36 qreal m_west;
37 qreal m_rotation; // NOT implemented yet!
38};
39
40bool operator==(GeoDataLatLonBox const &lhs, GeoDataLatLonBox const &rhs)
41{
42 return lhs.d->m_west == rhs.d->m_west && lhs.d->m_east == rhs.d->m_east && lhs.d->m_north == rhs.d->m_north && lhs.d->m_south == rhs.d->m_south
43 && lhs.d->m_rotation == rhs.d->m_rotation;
44}
45
46bool operator!=(GeoDataLatLonBox const &lhs, GeoDataLatLonBox const &rhs)
47{
48 return !(lhs == rhs);
49}
50
51GeoDataLatLonBox::GeoDataLatLonBox()
52 : GeoDataObject()
53 , d(new GeoDataLatLonBoxPrivate)
54{
55}
56
57GeoDataLatLonBox::GeoDataLatLonBox(qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit)
58 : GeoDataObject()
59 , d(new GeoDataLatLonBoxPrivate)
60{
61 setBoundaries(north, south, east, west, unit);
62}
63
64GeoDataLatLonBox::GeoDataLatLonBox(const GeoDataLatLonBox &other)
65 : GeoDataObject(other)
66 , d(new GeoDataLatLonBoxPrivate(*other.d))
67{
68}
69
70GeoDataLatLonBox::~GeoDataLatLonBox()
71{
72 delete d;
73}
74
75const char *GeoDataLatLonBox::nodeType() const
76{
77 return GeoDataTypes::GeoDataLatLonBoxType;
78}
79
80qreal GeoDataLatLonBox::north(GeoDataCoordinates::Unit unit) const
81{
82 if (unit == GeoDataCoordinates::Degree) {
83 return d->m_north * RAD2DEG;
84 }
85 return d->m_north;
86}
87
88void GeoDataLatLonBox::setNorth(const qreal north, GeoDataCoordinates::Unit unit)
89{
90 switch (unit) {
91 default:
92 case GeoDataCoordinates::Radian:
93 d->m_north = GeoDataCoordinates::normalizeLat(north);
94 break;
95 case GeoDataCoordinates::Degree:
96 d->m_north = GeoDataCoordinates::normalizeLat(north * DEG2RAD);
97 break;
98 }
99}
100
101qreal GeoDataLatLonBox::south(GeoDataCoordinates::Unit unit) const
102{
103 if (unit == GeoDataCoordinates::Degree) {
104 return d->m_south * RAD2DEG;
105 }
106 return d->m_south;
107}
108
109void GeoDataLatLonBox::setSouth(const qreal south, GeoDataCoordinates::Unit unit)
110{
111 switch (unit) {
112 default:
113 case GeoDataCoordinates::Radian:
114 d->m_south = GeoDataCoordinates::normalizeLat(south);
115 break;
116 case GeoDataCoordinates::Degree:
117 d->m_south = GeoDataCoordinates::normalizeLat(south * DEG2RAD);
118 break;
119 }
120}
121
122qreal GeoDataLatLonBox::east(GeoDataCoordinates::Unit unit) const
123{
124 if (unit == GeoDataCoordinates::Degree) {
125 return d->m_east * RAD2DEG;
126 }
127 return d->m_east;
128}
129
130void GeoDataLatLonBox::setEast(const qreal east, GeoDataCoordinates::Unit unit)
131{
132 switch (unit) {
133 default:
134 case GeoDataCoordinates::Radian:
135 d->m_east = GeoDataCoordinates::normalizeLon(east);
136 break;
137 case GeoDataCoordinates::Degree:
138 d->m_east = GeoDataCoordinates::normalizeLon(east * DEG2RAD);
139 break;
140 }
141}
142
143qreal GeoDataLatLonBox::west(GeoDataCoordinates::Unit unit) const
144{
145 if (unit == GeoDataCoordinates::Degree) {
146 return d->m_west * RAD2DEG;
147 }
148 return d->m_west;
149}
150
151void GeoDataLatLonBox::setWest(const qreal west, GeoDataCoordinates::Unit unit)
152{
153 switch (unit) {
154 default:
155 case GeoDataCoordinates::Radian:
156 d->m_west = GeoDataCoordinates::normalizeLon(west);
157 break;
158 case GeoDataCoordinates::Degree:
159 d->m_west = GeoDataCoordinates::normalizeLon(west * DEG2RAD);
160 break;
161 }
162}
163
164void GeoDataLatLonBox::setRotation(const qreal rotation, GeoDataCoordinates::Unit unit)
165{
166 switch (unit) {
167 default:
168 case GeoDataCoordinates::Radian:
169 d->m_rotation = rotation;
170 break;
171 case GeoDataCoordinates::Degree:
172 d->m_rotation = rotation * DEG2RAD;
173 break;
174 }
175}
176
177qreal GeoDataLatLonBox::rotation(GeoDataCoordinates::Unit unit) const
178{
179 if (unit == GeoDataCoordinates::Degree) {
180 return d->m_rotation * RAD2DEG;
181 }
182 return d->m_rotation;
183}
184
185void GeoDataLatLonBox::boundaries(qreal &north, qreal &south, qreal &east, qreal &west, GeoDataCoordinates::Unit unit) const
186{
187 switch (unit) {
188 default:
189 case GeoDataCoordinates::Radian:
190 north = d->m_north;
191 south = d->m_south;
192 east = d->m_east;
193 west = d->m_west;
194 break;
195 case GeoDataCoordinates::Degree:
196 north = d->m_north * RAD2DEG;
197 south = d->m_south * RAD2DEG;
198 east = d->m_east * RAD2DEG;
199 west = d->m_west * RAD2DEG;
200 break;
201 }
202}
203
204void GeoDataLatLonBox::setBoundaries(qreal north, qreal south, qreal east, qreal west, GeoDataCoordinates::Unit unit)
205{
206 switch (unit) {
207 default:
208 case GeoDataCoordinates::Radian:
209 d->m_north = GeoDataCoordinates::normalizeLat(north);
210 d->m_south = GeoDataCoordinates::normalizeLat(south);
211 d->m_east = GeoDataCoordinates::normalizeLon(east);
212 d->m_west = GeoDataCoordinates::normalizeLon(west);
213 break;
214 case GeoDataCoordinates::Degree:
215 d->m_north = GeoDataCoordinates::normalizeLat(north * DEG2RAD);
216 d->m_south = GeoDataCoordinates::normalizeLat(south * DEG2RAD);
217 d->m_east = GeoDataCoordinates::normalizeLon(east * DEG2RAD);
218 d->m_west = GeoDataCoordinates::normalizeLon(west * DEG2RAD);
219 break;
220 }
221}
222
223void GeoDataLatLonBox::scale(qreal verticalFactor, qreal horizontalFactor) const
224{
225 GeoDataCoordinates const middle = center();
226 qreal const deltaY = 0.5 * height() * verticalFactor;
227 qreal const deltaX = 0.5 * width() * horizontalFactor;
228 d->m_north = qMin((middle.latitude() + deltaY), static_cast<qreal>(M_PI / 2));
229 d->m_south = qMax((middle.latitude() - deltaY), static_cast<qreal>(-M_PI / 2));
230 if (deltaX > 180 * DEG2RAD) {
231 d->m_east = M_PI;
232 d->m_west = -M_PI;
233 } else {
234 d->m_east = GeoDataCoordinates::normalizeLon(middle.longitude() + deltaX);
235 d->m_west = GeoDataCoordinates::normalizeLon(middle.longitude() - deltaX);
236 }
237}
238
239GeoDataLatLonBox GeoDataLatLonBox::scaled(qreal verticalFactor, qreal horizontalFactor) const
240{
241 GeoDataLatLonBox result = *this;
242 result.scale(verticalFactor, horizontalFactor);
243 return result;
244}
245
246qreal GeoDataLatLonBox::width(GeoDataCoordinates::Unit unit) const
247{
248 return GeoDataLatLonBox::width(d->m_east, d->m_west, unit);
249}
250
251qreal GeoDataLatLonBox::width(qreal east, qreal west, GeoDataCoordinates::Unit unit)
252{
253 qreal width = fabs((qreal)(GeoDataLatLonBox::crossesDateLine(east, west) ? 2 * M_PI - west + east : east - west));
254
255 // This also covers the case where this bounding box covers the whole
256 // longitude range ( -180 <= lon <= + 180 ).
257 if (width > 2 * M_PI) {
258 width = 2 * M_PI;
259 }
260
261 if (unit == GeoDataCoordinates::Degree) {
262 return width * RAD2DEG;
263 }
264
265 return width;
266}
267
268qreal GeoDataLatLonBox::height(GeoDataCoordinates::Unit unit) const
269{
270 return GeoDataLatLonBox::height(d->m_north, d->m_south, unit);
271}
272
273qreal GeoDataLatLonBox::height(qreal north, qreal south, GeoDataCoordinates::Unit unit)
274{
275 qreal height = fabs((qreal)(south - north));
276
277 if (unit == GeoDataCoordinates::Degree) {
278 return height * RAD2DEG;
279 }
280
281 return height;
282}
283
284bool GeoDataLatLonBox::crossesDateLine() const
285{
286 return GeoDataLatLonBox::crossesDateLine(d->m_east, d->m_west);
287}
288
289bool GeoDataLatLonBox::crossesDateLine(qreal east, qreal west)
290{
291 return east < west || (east == M_PI && west == -M_PI);
292}
293
294GeoDataCoordinates GeoDataLatLonBox::center() const
295{
296 if (isEmpty())
297 return {};
298
299 if (crossesDateLine())
300 return {GeoDataCoordinates::normalizeLon(east() + 2 * M_PI - (east() + 2 * M_PI - west()) / 2), north() - (north() - south()) / 2};
301 else
302 return {east() - (east() - west()) / 2, north() - (north() - south()) / 2};
303}
304
305bool GeoDataLatLonBox::containsPole(Pole pole) const
306{
307 switch (pole) {
308 case NorthPole:
309 return (2 * north() == +M_PI);
310 case SouthPole:
311 return (2 * south() == -M_PI);
312 default:
313 case AnyPole:
314 return (2 * north() == +M_PI || 2 * south() == -M_PI);
315 }
316
317 mDebug() << "Invalid pole";
318 return false;
319}
320
321bool GeoDataLatLonBox::contains(qreal lon, qreal lat) const
322{
323 if (lat < d->m_south || lat > d->m_north) {
324 return false;
325 }
326
327 // We need to take care of the normal case ...
328 if (((lon < d->m_west || lon > d->m_east) && (d->m_west < d->m_east)) ||
329 // ... and the case where the bounding box crosses the date line:
330 ((lon < d->m_west && lon > d->m_east) && (d->m_west > d->m_east)))
331 return false;
332
333 return true;
334}
335
336bool GeoDataLatLonBox::contains(const GeoDataCoordinates &point) const
337{
338 qreal lon, lat;
339
340 point.geoCoordinates(lon, lat);
341
342 return contains(lon, lat);
343}
344
345bool GeoDataLatLonBox::contains(const GeoDataLatLonBox &other) const
346{
347 // check the contain criterion for the latitude first as this is trivial:
348
349 if (d->m_north >= other.north() && d->m_south <= other.south()) {
350 if (!crossesDateLine()) {
351 if (!other.crossesDateLine()) {
352 // "Normal" case: both bounding boxes don't cross the date line
353 if (d->m_west <= other.west() && d->m_east >= other.east()) {
354 return true;
355 }
356 } else {
357 // The other bounding box crosses the date line, "this" one does not:
358 // So the date line splits the other bounding box in two parts.
359 // Hence "this" bounding box could be fully contained by one of them.
360 // So for both cases we are able to ignore the "overhanging" portion
361 // and thereby basically reduce the problem to the "normal" case:
362
363 if ((other.west() <= d->m_west && d->m_east <= +M_PI) || (other.east() >= d->m_east && d->m_west >= -M_PI)) {
364 return true;
365 }
366 }
367 } else {
368 if (other.crossesDateLine()) {
369 // Other "Simple" case: both bounding boxes cross the date line
370 if (d->m_west <= other.west() && d->m_east >= other.east()) {
371 return true;
372 }
373 } else {
374 // "This" bounding box crosses the date line, the other one does not.
375 // So the date line splits "this" bounding box in two parts.
376 // Hence the other bounding box could be fully contained by one of them.
377 // So for both cases we are able to ignore the "overhanging" portion
378 // and thereby basically reduce the problem to the "normal" case:
379
380 if ((d->m_west <= other.west() && other.east() <= +M_PI) || (d->m_east >= other.east() && other.west() >= -M_PI)) {
381 return true;
382 }
383
384 // if this bounding box covers the whole longitude range ( -180 <= lon <= + 180 )
385 // then of course the "inner" bounding box is "inside"
386 if (d->m_west == -M_PI && d->m_east == +M_PI) {
387 return true;
388 }
389 }
390 }
391 }
392
393 return false;
394}
395
396bool GeoDataLatLonBox::intersects(const GeoDataLatLonBox &other) const
397{
398 if (isEmpty() || other.isEmpty()) {
399 return false;
400 }
401
402 // check the intersection criterion for the latitude first:
403
404 // Case 1: northern boundary of other box intersects:
405 if ((d->m_north >= other.d->m_north && d->m_south <= other.d->m_north)
406 // Case 2: northern boundary of this box intersects:
407 || (other.d->m_north >= d->m_north && other.d->m_south <= d->m_north)
408 // Case 3: southern boundary of other box intersects:
409 || (d->m_north >= other.d->m_south && d->m_south <= other.d->m_south)
410 // Case 4: southern boundary of this box intersects:
411 || (other.d->m_north >= d->m_south && other.d->m_south <= d->m_south)) {
412 if (!crossesDateLine()) {
413 if (!other.crossesDateLine()) {
414 // "Normal" case: both bounding boxes don't cross the date line
415 // Case 1: eastern boundary of other box intersects:
416 if ((d->m_east >= other.d->m_east && d->m_west <= other.d->m_east)
417 // Case 2: eastern boundary of this box intersects:
418 || (other.d->m_east >= d->m_east && other.d->m_west <= d->m_east)
419 // Case 3: western boundary of other box intersects:
420 || (d->m_east >= other.d->m_west && d->m_west <= other.d->m_west)
421 // Case 4: western boundary of this box intersects:
422 || (other.d->m_east >= d->m_west && other.d->m_west <= d->m_west)) {
423 return true;
424 }
425 } else {
426 // The other bounding box crosses the date line, "this" one does not:
427 // So the date line splits the other bounding box in two parts.
428
429 if (d->m_west <= other.d->m_east || d->m_east >= other.d->m_west) {
430 return true;
431 }
432 }
433 } else {
434 if (other.crossesDateLine()) {
435 // The trivial case: both bounding boxes cross the date line and intersect
436 return true;
437 } else {
438 // "This" bounding box crosses the date line, the other one does not.
439 // So the date line splits "this" bounding box in two parts.
440 //
441 // This also covers the case where this bounding box covers the whole
442 // longitude range ( -180 <= lon <= + 180 ).
443 if (other.d->m_west <= d->m_east || other.d->m_east >= d->m_west) {
444 return true;
445 }
446 }
447 }
448 }
449
450 return false;
451}
452
453GeoDataLatLonBox GeoDataLatLonBox::united(const GeoDataLatLonBox &other) const
454{
455 if (isEmpty()) {
456 return other;
457 }
458
459 if (other.isEmpty()) {
460 return *this;
461 }
462
463 GeoDataLatLonBox result;
464
465 // use the position of the centers of the boxes to determine the "smallest"
466 // box (i.e. should the total box go through IDL or not). this
467 // determination does not depend on one box or the other crossing IDL too
468 GeoDataCoordinates c1 = center();
469 GeoDataCoordinates c2 = other.center();
470
471 // do latitude first, quite simple
472 result.setNorth(qMax(d->m_north, other.north()));
473 result.setSouth(qMin(d->m_south, other.south()));
474
475 qreal w1 = d->m_west;
476 qreal w2 = other.west();
477 qreal e1 = d->m_east;
478 qreal e2 = other.east();
479
480 bool const idl1 = d->m_east < d->m_west;
481 bool const idl2 = other.d->m_east < other.d->m_west;
482
483 if (idl1) {
484 w1 += 2 * M_PI;
485 e1 += 2 * M_PI;
486 }
487 if (idl2) {
488 w2 += 2 * M_PI;
489 e2 += 2 * M_PI;
490 }
491
492 // in the usual case, we take the maximum of east bounds, and
493 // the minimum of west bounds. The exceptions are:
494 // - centers of boxes are more than 180 apart
495 // (so the smallest box should go around the IDL)
496 //
497 // - 1 but not 2 boxes are crossing IDL
498 if (fabs(c2.longitude() - c1.longitude()) > M_PI || (idl1 ^ idl2)) {
499 // exceptions, we go the unusual way:
500 // min of east, max of west
501 result.setEast(qMin(e1, e2));
502 result.setWest(qMax(w1, w2));
503 } else {
504 // normal case, max of east, min of west
505 result.setEast(qMax(e1, e2));
506 result.setWest(qMin(w1, w2));
507 }
508 return result;
509}
510
511GeoDataLatLonBox GeoDataLatLonBox::toCircumscribedRectangle() const
512{
513 QList<GeoDataCoordinates> coordinates;
514 coordinates.reserve(4);
515
516 coordinates.append(GeoDataCoordinates(west(), north()));
517 coordinates.append(GeoDataCoordinates(west(), south()));
518 coordinates.append(GeoDataCoordinates(east() + (crossesDateLine() ? 2 * M_PI : 0), north()));
519 coordinates.append(GeoDataCoordinates(east() + (crossesDateLine() ? 2 * M_PI : 0), south()));
520
521 const qreal cosRotation = cos(rotation());
522 const qreal sinRotation = sin(rotation());
523
524 qreal centerLat = center().latitude();
525 qreal centerLon = center().longitude();
526 if (GeoDataLatLonBox(0, 0, center().longitude(), west()).crossesDateLine()) {
527 if (!centerLon)
528 centerLon += M_PI;
529 else
530 centerLon += 2 * M_PI;
531 }
532
534
535 bool northSet = false;
536 bool southSet = false;
537 bool eastSet = false;
538 bool westSet = false;
539
540 for (const GeoDataCoordinates &coord : coordinates) {
541 const qreal lon = coord.longitude();
542 const qreal lat = coord.latitude();
543
544 const qreal rotatedLon = (lon - centerLon) * cosRotation - (lat - centerLat) * sinRotation + centerLon;
545 const qreal rotatedLat = (lon - centerLon) * sinRotation + (lat - centerLat) * cosRotation + centerLat;
546
547 if (!northSet || rotatedLat > box.north()) {
548 northSet = true;
549 box.setNorth(rotatedLat);
550 }
551
552 if (!southSet || rotatedLat < box.south()) {
553 southSet = true;
554 box.setSouth(rotatedLat);
555 }
556
557 if (!westSet || rotatedLon < box.west()) {
558 westSet = true;
559 box.setWest(rotatedLon);
560 }
561
562 if (!eastSet || rotatedLon > box.east()) {
563 eastSet = true;
564 box.setEast(rotatedLon);
565 }
566 }
567
568 box.setBoundaries(box.north(), box.south(), box.east(), box.west());
569
570 return box;
571}
572
573GeoDataLatLonBox &GeoDataLatLonBox::operator=(const GeoDataLatLonBox &other)
574{
575 GeoDataObject::operator=(other);
576
577 *d = *other.d;
578 return *this;
579}
580
581GeoDataLatLonBox GeoDataLatLonBox::operator|(const GeoDataLatLonBox &other) const
582{
583 return united(other);
584}
585
586GeoDataLatLonBox &GeoDataLatLonBox::operator|=(const GeoDataLatLonBox &other)
587{
588 *this = united(other);
589 return *this;
590}
591
592void GeoDataLatLonBox::pack(QDataStream &stream) const
593{
594 GeoDataObject::pack(stream);
595
596 stream << d->m_north << d->m_south << d->m_east << d->m_west << d->m_rotation;
597}
598
599void GeoDataLatLonBox::unpack(QDataStream &stream)
600{
601 GeoDataObject::unpack(stream);
602
603 stream >> d->m_north >> d->m_south >> d->m_east >> d->m_west >> d->m_rotation;
604}
605
606GeoDataLatLonBox GeoDataLatLonBox::fromLineString(const GeoDataLineString &lineString)
607{
608 // If the line string is empty return an empty boundingbox
609 if (lineString.isEmpty()) {
610 return {};
611 }
612
613 qreal lon, lat;
614 lineString.first().geoCoordinates(lon, lat);
615 GeoDataCoordinates::normalizeLonLat(lon, lat);
616
617 qreal north = lat;
618 qreal south = lat;
619 qreal west = lon;
620 qreal east = lon;
621
622 // If there's only a single node stored then the boundingbox only contains that point
623 if (lineString.size() == 1)
624 return {north, south, east, west};
625
626 // Specifies whether the polygon crosses the IDL
627 bool idlCrossed = false;
628
629 // "idlCrossState" specifies the state concerning IDL crossage.
630 // This is needed in order to create optimal bounding boxes in case of covering the IDL
631 // Every time the IDL gets crossed from east to west the idlCrossState value gets
632 // increased by one.
633 // Every time the IDL gets crossed from west to east the idlCrossState value gets
634 // decreased by one.
635
636 int idlCrossState = 0;
637 int idlMaxCrossState = 0;
638 int idlMinCrossState = 0;
639
640 // Holds values for east and west while idlCrossState != 0
641 qreal otherWest = lon;
642 qreal otherEast = lon;
643
644 qreal previousLon = lon;
645
646 int currentSign = (lon < 0) ? -1 : +1;
647 int previousSign = currentSign;
648
651
652 bool processingLastNode = false;
653
654 while (it != itEnd) {
655 // Get coordinates and normalize them to the desired range.
656 (it)->geoCoordinates(lon, lat);
657 GeoDataCoordinates::normalizeLonLat(lon, lat);
658
659 // Determining the maximum and minimum latitude
660 if (lat > north) {
661 north = lat;
662 } else if (lat < south) {
663 south = lat;
664 }
665
666 currentSign = (lon < 0) ? -1 : +1;
667
668 // Once the polyline crosses the dateline the covered bounding box
669 // would cover the whole [-M_PI; M_PI] range.
670 // When looking separately at the longitude range that gets covered
671 // east and west from the IDL we get two bounding boxes (we prefix
672 // the resulting longitude range on the "other side" with "other").
673 // By picking the "inner" range values we get a more appropriate
674 // optimized single bounding box.
675
676 // IDL check
677 if (previousSign != currentSign && fabs(previousLon) + fabs(lon) > M_PI) {
678 // Initialize values for otherWest and otherEast
679 if (idlCrossed == false) {
680 otherWest = lon;
681 otherEast = lon;
682 idlCrossed = true;
683 }
684
685 // Determine the new IDL Cross State
686 if (previousLon < 0) {
687 idlCrossState++;
688 if (idlCrossState > idlMaxCrossState) {
689 idlMaxCrossState = idlCrossState;
690 }
691 } else {
692 idlCrossState--;
693 if (idlCrossState < idlMinCrossState) {
694 idlMinCrossState = idlCrossState;
695 }
696 }
697 }
698
699 if (idlCrossState == 0) {
700 if (lon > east)
701 east = lon;
702 if (lon < west)
703 west = lon;
704 } else {
705 if (lon > otherEast)
706 otherEast = lon;
707 if (lon < otherWest)
708 otherWest = lon;
709 }
710
711 previousLon = lon;
712 previousSign = currentSign;
713
714 if (processingLastNode) {
715 break;
716 }
717 ++it;
718
719 if (lineString.isClosed() && it == itEnd) {
720 it = lineString.constBegin();
721 processingLastNode = true;
722 }
723 }
724
725 if (idlCrossed) {
726 if (idlMinCrossState < 0) {
727 east = otherEast;
728 }
729 if (idlMaxCrossState > 0) {
730 west = otherWest;
731 }
732 if ((idlMinCrossState < 0 && idlMaxCrossState > 0) || idlMinCrossState < -1 || idlMaxCrossState > 1 || west <= east) {
733 east = +M_PI;
734 west = -M_PI;
735 // if polygon fully in south hemisphere, contain south pole
736 if (north < 0) {
737 south = -M_PI / 2;
738 } else {
739 north = M_PI / 2;
740 }
741 }
742 }
743
744 return {north, south, east, west};
745}
746
747bool GeoDataLatLonBox::isNull() const
748{
749 return d->m_north == d->m_south && d->m_east == d->m_west;
750}
751
752bool GeoDataLatLonBox::isEmpty() const
753{
754 return *this == empty;
755}
756
757bool GeoDataLatLonBox::fuzzyCompare(const GeoDataLatLonBox &lhs, const GeoDataLatLonBox &rhs, const qreal factor)
758{
759 bool equal = true;
760
761 // Check the latitude for approximate equality
762
763 double latDelta = lhs.height() * factor;
764
765 if (fabs(lhs.north() - rhs.north()) > latDelta)
766 equal = false;
767 if (fabs(lhs.south() - rhs.south()) > latDelta)
768 equal = false;
769
770 // Check the longitude for approximate equality
771
772 double lonDelta = lhs.width() * factor;
773
774 double lhsEast = lhs.east();
775 double rhsEast = rhs.east();
776
777 if (!GeoDataLatLonBox::crossesDateLine(lhsEast, rhsEast)) {
778 if (fabs(lhsEast - rhsEast) > lonDelta)
779 equal = false;
780 } else {
781 if (lhsEast < 0 && rhsEast > 0) {
782 lhsEast += 2 * M_PI;
783 if (fabs(lhsEast - rhsEast) > lonDelta)
784 equal = false;
785 }
786 if (lhsEast > 0 && rhsEast < 0) {
787 rhsEast += 2 * M_PI;
788 if (fabs(lhsEast - rhsEast) > lonDelta)
789 equal = false;
790 }
791 }
792
793 double lhsWest = lhs.west();
794 double rhsWest = rhs.west();
795
796 if (!GeoDataLatLonBox::crossesDateLine(lhsWest, rhsWest)) {
797 if (fabs(lhsWest - rhsWest) > lonDelta)
798 equal = false;
799 } else {
800 if (lhsWest < 0 && rhsWest > 0) {
801 lhsWest += 2 * M_PI;
802 if (fabs(lhsWest - rhsWest) > lonDelta)
803 equal = false;
804 }
805 if (lhsWest > 0 && rhsWest < 0) {
806 rhsWest += 2 * M_PI;
807 if (fabs(lhsWest - rhsWest) > lonDelta)
808 equal = false;
809 }
810 }
811
812 return equal;
813}
814
815void GeoDataLatLonBox::clear()
816{
817 *this = empty;
818}
819}
A 3d point representation.
Unit
enum used constructor to specify the units used
void geoCoordinates(qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit) const
use this function to get the longitude and latitude with one call - use the unit parameter to switch ...
A class that defines a 2D bounding box for geographic data.
void scale(qreal verticalFactor, qreal horizontalFactor) const
Changes the differences between the boundaries and the center by the given factor,...
qreal north(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the northern boundary of the bounding box.
qreal east(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the eastern boundary of the bounding box.
qreal height(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the height of the latitude interval.
virtual bool isEmpty() const
Indicates whether the bounding box is not initialised (and contains nothing).
qreal width(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the width of the longitude interval.
qreal west(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the western boundary of the bounding box.
qreal south(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the southern boundary of the bounding box.
virtual GeoDataCoordinates center() const
returns the center of this box
A LineString that allows to store a contiguous set of line segments.
virtual bool isClosed() const
Returns whether a LineString is a closed polygon.
bool isEmpty() const
Returns whether the LineString has no nodes at all.
GeoDataCoordinates & first()
Returns a reference to the first node in the LineString. This method detaches the returned coordinate...
int size() const
Returns the number of nodes in a LineString.
QList< GeoDataCoordinates >::ConstIterator constBegin() const
Returns a const iterator that points to the begin of the LineString.
QList< GeoDataCoordinates >::ConstIterator constEnd() const
Returns a const iterator that points to the end of the LineString.
KIOCORE_EXPORT bool operator!=(const UDSEntry &entry, const UDSEntry &other)
bool operator==(const StyleDelim &l, const StyleDelim &r)
Binds a QML item to a specific geodetic location in screen coordinates.
@ SouthPole
Only South Pole.
@ NorthPole
Only North Pole.
@ AnyPole
Any pole.
void append(QList< T > &&value)
void reserve(qsizetype size)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.