| | | 1 | | using System; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | |
| | | 4 | | namespace Itinero.Network.Tiles; |
| | | 5 | | |
| | | 6 | | internal static class TileStatic |
| | | 7 | | { |
| | | 8 | | private static void ValidateBox(this ((double longitude, double latitude, float? e) topLeft, |
| | | 9 | | (double longitude, double latitude, float? e) bottomRight) box) |
| | 2122 | 10 | | { |
| | 2122 | 11 | | if (box.topLeft.latitude < box.bottomRight.latitude) |
| | 0 | 12 | | { |
| | 0 | 13 | | throw new ArgumentOutOfRangeException($"Top is lower than bottom."); |
| | | 14 | | } |
| | 2122 | 15 | | } |
| | | 16 | | |
| | | 17 | | public static (uint x, uint y) ToTile(int zoom, uint tileId) |
| | 7189650 | 18 | | { |
| | 7189650 | 19 | | var xMax = (ulong)(1 << zoom); |
| | | 20 | | |
| | 7189650 | 21 | | return ((uint)(tileId % xMax), (uint)(tileId / xMax)); |
| | 7189650 | 22 | | } |
| | | 23 | | |
| | | 24 | | public static uint ToLocalId(uint x, uint y, int zoom) |
| | 43447 | 25 | | { |
| | 43447 | 26 | | var xMax = 1 << (int)zoom; |
| | 43447 | 27 | | return (uint)((y * xMax) + x); |
| | 43447 | 28 | | } |
| | | 29 | | |
| | | 30 | | public static uint ToLocalId(double longitude, double latitude, int zoom) |
| | 39 | 31 | | { |
| | 39 | 32 | | var (x, y) = WorldToTile(longitude, latitude, zoom); |
| | 39 | 33 | | return ToLocalId(x, y, zoom); |
| | 39 | 34 | | } |
| | | 35 | | |
| | | 36 | | public static (int x, int y) ToLocalTileCoordinates(int zoom, uint tileId, double longitude, double latitude, |
| | | 37 | | int resolution) |
| | 112423 | 38 | | { |
| | 112423 | 39 | | var tile = ToTile(zoom, tileId); |
| | | 40 | | |
| | 112423 | 41 | | var n = Math.PI - (2.0 * Math.PI * tile.y / Math.Pow(2.0, zoom)); |
| | 112423 | 42 | | var left = (tile.x / Math.Pow(2.0, zoom) * 360.0) - 180.0; |
| | 112423 | 43 | | var top = 180.0 / Math.PI * Math.Atan(Math.Sinh(n)); |
| | | 44 | | |
| | 112423 | 45 | | n = Math.PI - (2.0 * Math.PI * (tile.y + 1) / Math.Pow(2.0, zoom)); |
| | 112423 | 46 | | var right = ((tile.x + 1) / Math.Pow(2.0, zoom) * 360.0) - 180.0; |
| | 112423 | 47 | | var bottom = 180.0 / Math.PI * Math.Atan(Math.Sinh(n)); |
| | | 48 | | |
| | 112423 | 49 | | var latStep = (top - bottom) / resolution; |
| | 112423 | 50 | | var lonStep = (right - left) / resolution; |
| | | 51 | | |
| | 112423 | 52 | | return ((int)((longitude - left) / lonStep), (int)((top - latitude) / latStep)); |
| | 112423 | 53 | | } |
| | | 54 | | |
| | | 55 | | public static void FromLocalTileCoordinates(int zoom, uint tileId, int x, int y, int resolution, |
| | | 56 | | out double longitude, out double latitude) |
| | 7071833 | 57 | | { |
| | 7071833 | 58 | | var tile = ToTile(zoom, tileId); |
| | | 59 | | |
| | 7071833 | 60 | | var n = Math.PI - (2.0 * Math.PI * tile.y / Math.Pow(2.0, zoom)); |
| | 7071833 | 61 | | var left = (tile.x / Math.Pow(2.0, zoom) * 360.0) - 180.0; |
| | 7071833 | 62 | | var top = 180.0 / Math.PI * Math.Atan(Math.Sinh(n)); |
| | | 63 | | |
| | 7071833 | 64 | | n = Math.PI - (2.0 * Math.PI * (tile.y + 1) / Math.Pow(2.0, zoom)); |
| | 7071833 | 65 | | var right = ((tile.x + 1) / Math.Pow(2.0, zoom) * 360.0) - 180.0; |
| | 7071833 | 66 | | var bottom = 180.0 / Math.PI * Math.Atan(Math.Sinh(n)); |
| | | 67 | | |
| | 7071833 | 68 | | var latStep = (top - bottom) / resolution; |
| | 7071833 | 69 | | var lonStep = (right - left) / resolution; |
| | | 70 | | |
| | 7071833 | 71 | | longitude = left + (lonStep * x); |
| | 7071833 | 72 | | latitude = top - (y * latStep); |
| | 7071833 | 73 | | } |
| | | 74 | | |
| | | 75 | | private static int N(int zoom) |
| | 44604 | 76 | | { |
| | 44604 | 77 | | return (int)Math.Floor(Math.Pow(2, zoom)); // replace by bit shifting? |
| | 44604 | 78 | | } |
| | | 79 | | |
| | | 80 | | /// <summary> |
| | | 81 | | /// Returns the geographic bounding box of the tile with the given id at the given zoom level. |
| | | 82 | | /// </summary> |
| | | 83 | | public static (double minLon, double minLat, double maxLon, double maxLat) |
| | | 84 | | GetTileBoundingBox(int zoom, uint tileId) |
| | 5393 | 85 | | { |
| | 5393 | 86 | | var (x, y) = ToTile(zoom, tileId); |
| | 5393 | 87 | | var pow = Math.Pow(2.0, zoom); |
| | | 88 | | |
| | 5393 | 89 | | var nTop = Math.PI - 2.0 * Math.PI * y / pow; |
| | 5393 | 90 | | var nBottom = Math.PI - 2.0 * Math.PI * (y + 1) / pow; |
| | | 91 | | |
| | 5393 | 92 | | var minLon = (x / pow * 360.0) - 180.0; |
| | 5393 | 93 | | var maxLon = ((x + 1) / pow * 360.0) - 180.0; |
| | 5393 | 94 | | var maxLat = 180.0 / Math.PI * Math.Atan(Math.Sinh(nTop)); |
| | 5393 | 95 | | var minLat = 180.0 / Math.PI * Math.Atan(Math.Sinh(nBottom)); |
| | | 96 | | |
| | 5393 | 97 | | return (minLon, minLat, maxLon, maxLat); |
| | 5393 | 98 | | } |
| | | 99 | | |
| | | 100 | | public static (uint x, uint y) WorldToTile(double longitude, double latitude, int zoom) |
| | 42482 | 101 | | { |
| | 42482 | 102 | | var n = N(zoom); |
| | 42482 | 103 | | var rad = latitude / 180d * Math.PI; |
| | | 104 | | |
| | 42482 | 105 | | var x = (uint)((longitude + 180.0f) / 360.0f * n); |
| | 42482 | 106 | | var y = (uint)( |
| | 42482 | 107 | | (1.0f - (Math.Log(Math.Tan(rad) + (1.0f / Math.Cos(rad))) |
| | 42482 | 108 | | / Math.PI)) / 2f * n); |
| | | 109 | | |
| | 42482 | 110 | | return (x, y); |
| | 42482 | 111 | | } |
| | | 112 | | |
| | | 113 | | public static IEnumerable<(uint x, uint y)> TileRange( |
| | | 114 | | this ((double longitude, double latitude, float? e) topLeft, |
| | | 115 | | (double longitude, double latitude, float? e) bottomRight) box, int zoom) |
| | 2122 | 116 | | { |
| | 2122 | 117 | | box.ValidateBox(); |
| | | 118 | | |
| | 2122 | 119 | | var n = N(zoom); |
| | 2122 | 120 | | var topLeft = WorldToTile(box.topLeft.longitude, box.topLeft.latitude, zoom); |
| | 2122 | 121 | | var bottomRight = WorldToTile(box.bottomRight.longitude, box.bottomRight.latitude, zoom); |
| | | 122 | | |
| | 2122 | 123 | | var x = topLeft.x; |
| | 2122 | 124 | | var y = topLeft.y; |
| | 5413 | 125 | | while (true) |
| | 5413 | 126 | | { |
| | 5413 | 127 | | yield return (x, y); |
| | | 128 | | |
| | 5412 | 129 | | if (y == bottomRight.y) |
| | 3216 | 130 | | { |
| | | 131 | | // move on with x. |
| | 3216 | 132 | | if (x == bottomRight.x) |
| | 2121 | 133 | | { |
| | | 134 | | // both x and y have reached the end. |
| | 2121 | 135 | | break; |
| | | 136 | | } |
| | | 137 | | |
| | | 138 | | // reset y. |
| | 1095 | 139 | | y = topLeft.y; |
| | | 140 | | |
| | | 141 | | // move on with x. |
| | 1095 | 142 | | x++; |
| | 1095 | 143 | | if (x == n) |
| | 0 | 144 | | { |
| | 0 | 145 | | x = 0; |
| | 0 | 146 | | } |
| | | 147 | | |
| | 1095 | 148 | | continue; |
| | | 149 | | } |
| | | 150 | | |
| | | 151 | | // move on with y. |
| | 2196 | 152 | | y++; |
| | 2196 | 153 | | } |
| | 2121 | 154 | | } |
| | | 155 | | } |