| | 1 | | using System; |
| | 2 | |
|
| | 3 | | namespace Itinero.IO.Osm.Tiles; |
| | 4 | |
|
| | 5 | | internal class Tile |
| | 6 | | { |
| 2 | 7 | | public Tile(uint x, uint y, int zoom) |
| 2 | 8 | | { |
| 2 | 9 | | this.X = x; |
| 2 | 10 | | this.Y = y; |
| 2 | 11 | | this.Zoom = zoom; |
| | 12 | |
|
| 2 | 13 | | this.CalculateBounds(); |
| 2 | 14 | | } |
| | 15 | |
|
| | 16 | | private void CalculateBounds() |
| 2 | 17 | | { |
| 2 | 18 | | var n = Math.PI - 2.0 * Math.PI * this.Y / Math.Pow(2.0, this.Zoom); |
| 2 | 19 | | this.Left = this.X / Math.Pow(2.0, this.Zoom) * 360.0 - 180.0; |
| 2 | 20 | | this.Top = 180.0 / Math.PI * Math.Atan(Math.Sinh(n)); |
| | 21 | |
|
| 2 | 22 | | n = Math.PI - 2.0 * Math.PI * (this.Y + 1) / Math.Pow(2.0, this.Zoom); |
| 2 | 23 | | this.Right = (this.X + 1) / Math.Pow(2.0, this.Zoom) * 360.0 - 180.0; |
| 2 | 24 | | this.Bottom = 180.0 / Math.PI * Math.Atan(Math.Sinh(n)); |
| 2 | 25 | | } |
| | 26 | |
|
| | 27 | | /// <summary> |
| | 28 | | /// Gets X. |
| | 29 | | /// </summary> |
| 8 | 30 | | public uint X { get; private set; } |
| | 31 | |
|
| | 32 | | /// <summary> |
| | 33 | | /// Gets Y. |
| | 34 | | /// </summary> |
| 8 | 35 | | public uint Y { get; private set; } |
| | 36 | |
|
| | 37 | | /// <summary> |
| | 38 | | /// Gets the zoom level. |
| | 39 | | /// </summary> |
| 12 | 40 | | public int Zoom { get; private set; } |
| | 41 | |
|
| | 42 | | /// <summary> |
| | 43 | | /// Gets the top. |
| | 44 | | /// </summary> |
| 2 | 45 | | public double Top { get; private set; } |
| | 46 | |
|
| | 47 | | /// <summary> |
| | 48 | | /// Get the bottom. |
| | 49 | | /// </summary> |
| 2 | 50 | | public double Bottom { get; private set; } |
| | 51 | |
|
| | 52 | | /// <summary> |
| | 53 | | /// Get the left. |
| | 54 | | /// </summary> |
| 2 | 55 | | public double Left { get; private set; } |
| | 56 | |
|
| | 57 | | /// <summary> |
| | 58 | | /// Gets the right. |
| | 59 | | /// </summary> |
| 2 | 60 | | public double Right { get; private set; } |
| | 61 | |
|
| | 62 | | /// <summary> |
| | 63 | | /// Updates the data in this tile to correspond with the given local tile id. |
| | 64 | | /// </summary> |
| | 65 | | /// <param name="localId">The local tile id.</param> |
| | 66 | | public void UpdateToLocalId(ulong localId) |
| 0 | 67 | | { |
| 0 | 68 | | var xMax = (ulong)(1 << (int)this.Zoom); |
| | 69 | |
|
| 0 | 70 | | this.X = (uint)(localId % xMax); |
| 0 | 71 | | this.Y = (uint)(localId / xMax); |
| 0 | 72 | | } |
| | 73 | |
|
| | 74 | | /// <summary> |
| | 75 | | /// Gets the local tile id. |
| | 76 | | /// </summary> |
| | 77 | | /// <remarks>This is the id relative to the zoom level.</remarks> |
| | 78 | | public uint LocalId |
| | 79 | | { |
| | 80 | | get |
| 1 | 81 | | { |
| 1 | 82 | | var xMax = 1 << (int)this.Zoom; |
| | 83 | |
|
| 1 | 84 | | return (uint)(this.Y * xMax + this.X); |
| 1 | 85 | | } |
| | 86 | | } |
| | 87 | |
|
| | 88 | | /// <summary> |
| | 89 | | /// Returns the tile for the given local id and zoom level. |
| | 90 | | /// </summary> |
| | 91 | | /// <param name="localId">The local id.</param> |
| | 92 | | /// <param name="zoom">The zoom level.</param> |
| | 93 | | /// <returns>The tile.</returns> |
| | 94 | | public static Tile FromLocalId(ulong localId, int zoom) |
| 1 | 95 | | { |
| 1 | 96 | | var xMax = (ulong)(1 << zoom); |
| | 97 | |
|
| 1 | 98 | | return new Tile((uint)(localId % xMax), |
| 1 | 99 | | (uint)(localId / xMax), zoom); |
| 1 | 100 | | } |
| | 101 | |
|
| | 102 | | /// <summary> |
| | 103 | | /// Calculates the maximum local id for the given zoom level. |
| | 104 | | /// </summary> |
| | 105 | | /// <param name="zoom">The zoom level.</param> |
| | 106 | | /// <returns>The maximum local id for the given zoom level</returns> |
| | 107 | | public static ulong MaxLocalId(int zoom) |
| 0 | 108 | | { |
| 0 | 109 | | var xMax = (ulong)(1 << zoom); |
| | 110 | |
|
| 0 | 111 | | return xMax * xMax; |
| 0 | 112 | | } |
| | 113 | |
|
| | 114 | | /// <summary> |
| | 115 | | /// Converts a lat/lon pair to a set of local coordinates. |
| | 116 | | /// </summary> |
| | 117 | | /// <param name="latitude">The latitude.</param> |
| | 118 | | /// <param name="longitude">The longitude.</param> |
| | 119 | | /// <param name="resolution">The resolution.</param> |
| | 120 | | /// <returns>A local coordinate pair.</returns> |
| | 121 | | public (int x, int y) ToLocalCoordinates(double longitude, double latitude, int resolution) |
| 0 | 122 | | { |
| 0 | 123 | | var latStep = (this.Top - this.Bottom) / resolution; |
| 0 | 124 | | var lonStep = (this.Right - this.Left) / resolution; |
| 0 | 125 | | var top = this.Top; |
| 0 | 126 | | var left = this.Left; |
| | 127 | |
|
| 0 | 128 | | return ((int)((longitude - left) / lonStep), (int)((top - latitude) / latStep)); |
| 0 | 129 | | } |
| | 130 | |
|
| | 131 | | /// <summary> |
| | 132 | | /// Converts a set of local coordinates to a lat/lon pair. |
| | 133 | | /// </summary> |
| | 134 | | /// <param name="x"></param> |
| | 135 | | /// <param name="y"></param> |
| | 136 | | /// <param name="resolution"></param> |
| | 137 | | /// <returns>A global coordinate pair.</returns> |
| | 138 | | public (double longitude, double latitude) FromLocalCoordinates(int x, int y, int resolution) |
| 0 | 139 | | { |
| 0 | 140 | | var latStep = (this.Top - this.Bottom) / resolution; |
| 0 | 141 | | var lonStep = (this.Right - this.Left) / resolution; |
| 0 | 142 | | var top = this.Top; |
| 0 | 143 | | var left = this.Left; |
| | 144 | |
|
| 0 | 145 | | return (left + lonStep * x, top - y * latStep); |
| 0 | 146 | | } |
| | 147 | |
|
| | 148 | | /// <summary> |
| | 149 | | /// Gets the tile at the given coordinates for the given zoom level. |
| | 150 | | /// </summary> |
| | 151 | | /// <param name="latitude">The latitude.</param> |
| | 152 | | /// <param name="longitude">The longitude.</param> |
| | 153 | | /// <param name="zoom">The zoom level.</param> |
| | 154 | | /// <returns>The tile a the given coordinates.</returns> |
| | 155 | | public static Tile WorldToTile(double longitude, double latitude, int zoom) |
| 1 | 156 | | { |
| 1 | 157 | | var n = (int)Math.Floor(Math.Pow(2, zoom)); // replace by bitshifting? |
| | 158 | |
|
| 1 | 159 | | var rad = latitude / 180d * Math.PI; |
| | 160 | |
|
| 1 | 161 | | var x = (uint)((longitude + 180.0f) / 360.0f * n); |
| 1 | 162 | | var y = (uint)( |
| 1 | 163 | | (1.0f - Math.Log(Math.Tan(rad) + 1.0f / Math.Cos(rad)) |
| 1 | 164 | | / Math.PI) / 2f * n); |
| | 165 | |
|
| 1 | 166 | | return new Tile(x, y, zoom); |
| 1 | 167 | | } |
| | 168 | |
|
| | 169 | | public bool IsInside(double longitude, double latitude) |
| 0 | 170 | | { |
| 0 | 171 | | return !(this.Top <= latitude || |
| 0 | 172 | | this.Bottom >= latitude || |
| 0 | 173 | | this.Left >= longitude || |
| 0 | 174 | | this.Right <= longitude); |
| 0 | 175 | | } |
| | 176 | |
|
| | 177 | | public override string ToString() |
| 0 | 178 | | { |
| 0 | 179 | | return $"{this.X},{this.Y}@{this.Zoom}"; |
| 0 | 180 | | } |
| | 181 | | } |