data/MapProjection.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "MapProjection.h" | ||
| 2 | |||
| 3 | #include <QList> | ||
| 4 | #include <QPoint> | ||
| 5 | #include <QtMath> | ||
| 6 | #include <algorithm> | ||
| 7 | #include <cmath> | ||
| 8 | |||
| 9 | namespace MapProjection { | ||
| 10 | |||
| 11 | 123101 | QPointF project(double latitude, double longitude) { | |
| 12 |
1/1✓ Branch 2 → 3 taken 123101 times.
|
123101 | const double lat = std::clamp(latitude, -MaxLatitude, MaxLatitude); |
| 13 | 123101 | const double x = (longitude + 180.0) / 360.0; | |
| 14 | 123101 | const double sinLat = std::sin(qDegreesToRadians(lat)); | |
| 15 | 123101 | const double y = 0.5 - std::log((1.0 + sinLat) / (1.0 - sinLat)) / (4.0 * M_PI); | |
| 16 | 123101 | return {x, y}; | |
| 17 | } | ||
| 18 | |||
| 19 | 13 | QPointF unproject(const QPointF &world) { | |
| 20 | 13 | const double longitude = world.x() * 360.0 - 180.0; | |
| 21 | 13 | const double n = M_PI - 2.0 * M_PI * world.y(); | |
| 22 | 13 | const double latitude = qRadiansToDegrees(std::atan(std::sinh(n))); | |
| 23 | 13 | return {longitude, latitude}; | |
| 24 | } | ||
| 25 | |||
| 26 | 13 | QPoint tileOf(const QPointF &world, int zoom) { | |
| 27 | 13 | const double tiles = std::ldexp(1.0, zoom); | |
| 28 | 13 | const int max = int(tiles) - 1; | |
| 29 |
2/2✓ Branch 3 → 4 taken 13 times.
✓ Branch 5 → 6 taken 13 times.
|
13 | return {std::clamp(int(std::floor(world.x() * tiles)), 0, max), std::clamp(int(std::floor(world.y() * tiles)), 0, max)}; |
| 30 | } | ||
| 31 | |||
| 32 | 5576 | double Viewport::worldPixels() const { return TileSize * std::exp2(zoom); } | |
| 33 | |||
| 34 | 4940 | QPointF Viewport::toScreen(const QPointF &world) const { | |
| 35 | 4940 | const double scale = worldPixels(); | |
| 36 | 4940 | return {(world.x() - center.x()) * scale + size.width() / 2.0, (world.y() - center.y()) * scale + size.height() / 2.0}; | |
| 37 | } | ||
| 38 | |||
| 39 | 35 | QPointF Viewport::toWorld(const QPointF &screen) const { | |
| 40 | 35 | const double scale = worldPixels(); | |
| 41 | 35 | return {center.x() + (screen.x() - size.width() / 2.0) / scale, | |
| 42 | 35 | center.y() + (screen.y() - size.height() / 2.0) / scale}; | |
| 43 | } | ||
| 44 | |||
| 45 | 326 | QRectF Viewport::visibleWorld() const { | |
| 46 | 326 | const double scale = worldPixels(); | |
| 47 | 326 | const double w = size.width() / scale; | |
| 48 | 326 | const double h = size.height() / scale; | |
| 49 | 326 | return {center.x() - w / 2.0, center.y() - h / 2.0, w, h}; | |
| 50 | } | ||
| 51 | |||
| 52 | namespace { | ||
| 53 | |||
| 54 | // Keeps the world filling the canvas vertically and wraps east–west. | ||
| 55 | 48 | Viewport normalised(Viewport v) { | |
| 56 | 48 | v.center.setX(v.center.x() - std::floor(v.center.x())); | |
| 57 | 48 | const double half = v.size.height() / 2.0 / v.worldPixels(); | |
| 58 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 48 times.
|
48 | if (half >= 0.5) |
| 59 | ✗ | v.center.setY(0.5); | |
| 60 | else | ||
| 61 |
1/1✓ Branch 10 → 11 taken 48 times.
|
48 | v.center.setY(std::clamp(v.center.y(), half, 1.0 - half)); |
| 62 | 48 | return v; | |
| 63 | } | ||
| 64 | |||
| 65 | } // namespace | ||
| 66 | |||
| 67 | 7 | Viewport Viewport::panned(const QPointF &screenDelta) const { | |
| 68 | 7 | Viewport v = *this; | |
| 69 | 7 | const double scale = worldPixels(); | |
| 70 | 7 | v.center -= QPointF(screenDelta.x() / scale, screenDelta.y() / scale); | |
| 71 |
1/1✓ Branch 7 → 8 taken 7 times.
|
14 | return normalised(v); |
| 72 | } | ||
| 73 | |||
| 74 | 24 | Viewport Viewport::zoomed(double steps, const QPointF &anchor) const { | |
| 75 | 24 | Viewport v = *this; | |
| 76 | 24 | const QPointF anchorWorld = toWorld(anchor); | |
| 77 |
3/3✓ Branch 3 → 4 taken 24 times.
✓ Branch 5 → 6 taken 24 times.
✓ Branch 6 → 7 taken 24 times.
|
24 | v.zoom = std::clamp(zoom + steps, minZoomFor(size), std::max(maxZoom, minZoomFor(size))); |
| 78 | // Keep anchorWorld under the anchor: centre = anchorWorld - offset/scale. | ||
| 79 | 24 | const double scale = v.worldPixels(); | |
| 80 | 24 | v.center = anchorWorld - QPointF((anchor.x() - size.width() / 2.0) / scale, (anchor.y() - size.height() / 2.0) / scale); | |
| 81 |
1/1✓ Branch 14 → 15 taken 24 times.
|
48 | return normalised(v); |
| 82 | } | ||
| 83 | |||
| 84 | 168 | double minZoomFor(const QSizeF &size) { | |
| 85 | 168 | const double longest = std::max(size.width(), 1.0); | |
| 86 |
1/1✓ Branch 4 → 5 taken 168 times.
|
168 | return std::clamp(std::log2(longest / TileSize), MinZoom, MaxZoom); |
| 87 | } | ||
| 88 | |||
| 89 | 17 | Viewport fit(const QList<QPointF> &worldPoints, const QSizeF &size, double margin, double singleZoom) { | |
| 90 | 17 | Viewport v; | |
| 91 | 17 | v.size = size; | |
| 92 |
1/1✓ Branch 2 → 3 taken 17 times.
|
17 | v.zoom = minZoomFor(size); |
| 93 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 7 taken 16 times.
|
17 | if (worldPoints.isEmpty()) |
| 94 |
1/1✓ Branch 5 → 6 taken 1 time.
|
1 | return normalised(v); |
| 95 | 16 | double minX = 1, minY = 1, maxX = 0, maxY = 0; | |
| 96 |
2/2✓ Branch 20 → 9 taken 892 times.
✓ Branch 20 → 21 taken 16 times.
|
908 | for (const QPointF &p : worldPoints) { |
| 97 | 892 | minX = std::min(minX, p.x()); | |
| 98 | 892 | maxX = std::max(maxX, p.x()); | |
| 99 | 892 | minY = std::min(minY, p.y()); | |
| 100 | 892 | maxY = std::max(maxY, p.y()); | |
| 101 | } | ||
| 102 | 16 | v.center = {(minX + maxX) / 2.0, (minY + maxY) / 2.0}; | |
| 103 | 16 | const double w = std::max(maxX - minX, 1e-9); | |
| 104 | 16 | const double h = std::max(maxY - minY, 1e-9); | |
| 105 | 16 | const double usableW = std::max(size.width() - 2 * margin, 1.0); | |
| 106 | 16 | const double usableH = std::max(size.height() - 2 * margin, 1.0); | |
| 107 | 16 | const double scale = std::min(usableW / w, usableH / h); | |
| 108 |
3/6✓ Branch 30 → 31 taken 15 times.
✓ Branch 30 → 34 taken 1 time.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 15 times.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
|
16 | v.zoom = worldPoints.size() == 1 || (w < 1e-6 && h < 1e-6) ? singleZoom : std::log2(scale / TileSize); |
| 109 |
2/2✓ Branch 35 → 36 taken 16 times.
✓ Branch 36 → 37 taken 16 times.
|
16 | v.zoom = std::clamp(v.zoom, minZoomFor(size), MaxZoom); |
| 110 |
1/1✓ Branch 37 → 38 taken 16 times.
|
16 | return normalised(v); |
| 111 | } | ||
| 112 | |||
| 113 | } // namespace MapProjection | ||
| 114 |