├── LICENSE.md ├── UpgradeLog.htm ├── README.md ├── .gitignore ├── src └── AwesomeTiles │ ├── AwesomeTiles.csproj │ ├── Properties │ └── AssemblyInfo.cs │ ├── Tile.cs │ └── TileRange.cs └── AwesomeTiles.sln /LICENSE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UpgradeLog.htm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OsmSharp/tiles/HEAD/UpgradeLog.htm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awesome Tiles 2 | 3 | Awesome tiles has two classes: 4 | 5 | - Tile: Represents one tile with a zoom level and x-y coordinate. 6 | - TileRange: Represents a 'rectangular' collection of tiles. 7 | 8 | #### Usage: 9 | 10 | Just include the classes in your code if you need them. 11 | 12 | #### Example: 13 | 14 | ```csharp 15 | // create a tile at a given location. 16 | var tile = Tile.CreateAroundLocation(lat, lon, zoom); 17 | 18 | // get info about tile. 19 | var tileTop = tile.Top; 20 | 21 | // and many more awesome stuff, that's why it's called awesome tiles and not just tiles! 22 | 23 | ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj 2 | [Bb]in 3 | *.user 4 | *.suo 5 | *.[Cc]ache 6 | *.bak 7 | *.ncb 8 | *.log 9 | *.DS_Store 10 | [Tt]humbs.db 11 | _ReSharper.* 12 | *.resharper 13 | Ankh.NoLoad 14 | Core/Output*/ 15 | UI/Output*/ 16 | UI/WinForms/Output*/ 17 | Data/Oracle/Output*/ 18 | Data/PostgreSQL/Output*/ 19 | Data/Redis/Output*/ 20 | Data/SQLServer/Output*/ 21 | Data/SQLite/Output*/ 22 | Core/OsmSharp.UnitTests/test-results*/ 23 | *.userprefs 24 | Output*/ 25 | OutputAndroid*/ 26 | OutputWindowsPhone*/ 27 | *.psess 28 | test-results*/ 29 | *.vsp 30 | .DotSettings 31 | *.vspx 32 | *.patch 33 | TestResults*/ 34 | 35 | packages/* 36 | !packages/repositories.config 37 | .vs/* 38 | 39 | *.routerdb 40 | *.osm.pbf 41 | -------------------------------------------------------------------------------- /src/AwesomeTiles/AwesomeTiles.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard1.6 5 | AwesomeTiles 6 | AwesomeTiles 7 | 1.6.0 8 | $(PackageTargetFallback);dnxcore50 9 | false 10 | false 11 | false 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/AwesomeTiles/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyConfiguration("")] 9 | [assembly: AssemblyCompany("")] 10 | [assembly: AssemblyProduct("AwesomeTiles")] 11 | [assembly: AssemblyTrademark("")] 12 | 13 | // Setting ComVisible to false makes the types in this assembly not visible 14 | // to COM components. If you need to access a type in this assembly from 15 | // COM, set the ComVisible attribute to true on that type. 16 | [assembly: ComVisible(false)] 17 | 18 | // The following GUID is for the ID of the typelib if this project is exposed to COM 19 | [assembly: Guid("fe7497c7-df68-46f4-a7de-e4ffb9e9731c")] 20 | -------------------------------------------------------------------------------- /AwesomeTiles.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.15 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4373FD06-D391-4A50-A34B-C044270F2A05}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FABD819-BCB6-41D5-A4C9-DA7E8C2A38DF}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AwesomeTiles", "src\AwesomeTiles\AwesomeTiles.csproj", "{FE7497C7-DF68-46F4-A7DE-E4FFB9E9731C}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {FE7497C7-DF68-46F4-A7DE-E4FFB9E9731C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {FE7497C7-DF68-46F4-A7DE-E4FFB9E9731C}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {FE7497C7-DF68-46F4-A7DE-E4FFB9E9731C}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {FE7497C7-DF68-46F4-A7DE-E4FFB9E9731C}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | GlobalSection(NestedProjects) = preSolution 27 | {FE7497C7-DF68-46F4-A7DE-E4FFB9E9731C} = {4373FD06-D391-4A50-A34B-C044270F2A05} 28 | EndGlobalSection 29 | GlobalSection(ExtensibilityGlobals) = postSolution 30 | SolutionGuid = {6BA36BCC-F8B3-4E0C-925C-BCB1FCD069C9} 31 | EndGlobalSection 32 | EndGlobal 33 | -------------------------------------------------------------------------------- /src/AwesomeTiles/Tile.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | using System; 24 | 25 | namespace AwesomeTiles 26 | { 27 | /// 28 | /// Represents a tile. 29 | /// 30 | public class Tile 31 | { 32 | private readonly ulong _id; 33 | 34 | /// 35 | /// Creates a new tile from a given id. 36 | /// 37 | /// 38 | public Tile(ulong id) 39 | { 40 | _id = id; 41 | 42 | var tile = Tile.CalculateTile(id); 43 | this.X = tile.X; 44 | this.Y = tile.Y; 45 | this.Zoom = tile.Zoom; 46 | this.CalculateBounds(); 47 | } 48 | 49 | /// 50 | /// Creates a new tile. 51 | /// 52 | public Tile(int x, int y, int zoom) 53 | { 54 | this.X = x; 55 | this.Y = y; 56 | this.Zoom = zoom; 57 | 58 | _id = CalculateTileId(zoom, x, y); 59 | this.CalculateBounds(); 60 | } 61 | 62 | private void CalculateBounds() 63 | { 64 | var n = Math.PI - ((2.0 * Math.PI * this.Y) / Math.Pow(2.0, this.Zoom)); 65 | this.Left = (float)((this.X / Math.Pow(2.0, this.Zoom) * 360.0) - 180.0); 66 | this.Top = (float)(180.0 / Math.PI * Math.Atan(Math.Sinh(n))); 67 | 68 | n = Math.PI - ((2.0 * Math.PI * (this.Y + 1)) / Math.Pow(2.0, this.Zoom)); 69 | this.Right = (float)(((this.X + 1) / Math.Pow(2.0, this.Zoom) * 360.0) - 180.0); 70 | this.Bottom = (float)(180.0 / Math.PI * Math.Atan(Math.Sinh(n))); 71 | 72 | this.CenterLat = (float)((this.Top + this.Bottom) / 2.0); 73 | this.CenterLon = (float)((this.Left + this.Right) / 2.0); 74 | } 75 | 76 | /// 77 | /// The X position of the tile. 78 | /// 79 | public int X { get; private set; } 80 | 81 | /// 82 | /// The Y position of the tile. 83 | /// 84 | public int Y { get; private set; } 85 | 86 | /// 87 | /// The zoom level for this tile. 88 | /// 89 | public int Zoom { get; private set; } 90 | 91 | /// 92 | /// Gets the top. 93 | /// 94 | public float Top { get; private set; } 95 | 96 | /// 97 | /// Get the bottom. 98 | /// 99 | public float Bottom { get; private set; } 100 | 101 | /// 102 | /// Get the left. 103 | /// 104 | public float Left { get; private set; } 105 | 106 | /// 107 | /// Gets the right. 108 | /// 109 | public float Right { get; private set; } 110 | 111 | /// 112 | /// Gets the center lat. 113 | /// 114 | public float CenterLat { get; private set; } 115 | 116 | /// 117 | /// Gets the center lon. 118 | /// 119 | public float CenterLon { get; private set; } 120 | 121 | /// 122 | /// Gets the parent tile. 123 | /// 124 | public Tile Parent => new Tile(this.X / 2, this.Y / 2, this.Zoom - 1); 125 | 126 | /// 127 | /// Returns a hashcode for this tile position. 128 | /// 129 | /// 130 | public override int GetHashCode() 131 | { 132 | return this.X.GetHashCode() ^ 133 | this.Y.GetHashCode() ^ 134 | this.Zoom.GetHashCode(); 135 | } 136 | 137 | /// 138 | /// Returns true if the given object represents the same tile. 139 | /// 140 | /// 141 | /// 142 | public override bool Equals(object obj) 143 | { 144 | var other = (obj as Tile); 145 | if (other != null) 146 | { 147 | return other.X == this.X && 148 | other.Y == this.Y && 149 | other.Zoom == this.Zoom; 150 | } 151 | return false; 152 | } 153 | 154 | /// 155 | /// Returns a description for this tile. 156 | /// 157 | /// 158 | public override string ToString() 159 | { 160 | return string.Format("{0}x-{1}y@{2}z", this.X, this.Y, this.Zoom); 161 | } 162 | 163 | /// 164 | /// Calculates the tile id of the tile at position (0, 0) for the given zoom. 165 | /// 166 | /// 167 | /// 168 | private static ulong CalculateTileId(int zoom) 169 | { 170 | switch (zoom) 171 | { 172 | case 0: // zoom level 0: {0}. 173 | return 0; 174 | case 1: 175 | return 1; 176 | case 2: 177 | return 5; 178 | case 3: 179 | return 21; 180 | case 4: 181 | return 85; 182 | case 5: 183 | return 341; 184 | case 6: 185 | return 1365; 186 | case 7: 187 | return 5461; 188 | case 8: 189 | return 21845; 190 | case 9: 191 | return 87381; 192 | case 10: 193 | return 349525; 194 | case 11: 195 | return 1398101; 196 | case 12: 197 | return 5592405; 198 | case 13: 199 | return 22369621; 200 | case 14: 201 | return 89478485; 202 | case 15: 203 | return 357913941; 204 | case 16: 205 | return 1431655765; 206 | case 17: 207 | return 5726623061; 208 | case 18: 209 | return 22906492245; 210 | } 211 | 212 | var size = (ulong)Math.Pow(2, 2 * (zoom - 1)); 213 | var tileId = Tile.CalculateTileId(zoom - 1) + size; 214 | return tileId; 215 | } 216 | 217 | /// 218 | /// Calculates the tile id of the tile at position (x, y) for the given zoom. 219 | /// 220 | /// 221 | /// 222 | /// 223 | /// 224 | private static ulong CalculateTileId(int zoom, int x, int y) 225 | { 226 | var id = Tile.CalculateTileId(zoom); 227 | var width = (long)System.Math.Pow(2, zoom); 228 | return id + (ulong)x + (ulong)(y * width); 229 | } 230 | 231 | /// 232 | /// Calculate the tile given the id. 233 | /// 234 | /// 235 | /// 236 | private static Tile CalculateTile(ulong id) 237 | { 238 | // find out the zoom level first. 239 | var zoom = 0; 240 | if (id > 0) 241 | { // only if the id is at least at zoom level 1. 242 | while (id >= Tile.CalculateTileId(zoom)) 243 | { 244 | // move to the next zoom level and keep searching. 245 | zoom++; 246 | } 247 | zoom--; 248 | } 249 | 250 | // calculate the x-y. 251 | var local = id - Tile.CalculateTileId(zoom); 252 | var width = (ulong)System.Math.Pow(2, zoom); 253 | var x = (int)(local % width); 254 | var y = (int)(local / width); 255 | 256 | return new Tile(x, y, zoom); 257 | } 258 | 259 | /// 260 | /// Returns the id of this tile. 261 | /// 262 | public ulong Id => _id; 263 | 264 | /// 265 | /// Returns true if this tile is valid. 266 | /// 267 | public bool IsValid 268 | { 269 | get 270 | { 271 | if (this.X >= 0 && 272 | this.Y >= 0 && 273 | this.Zoom >= 0) 274 | { // some are negative. 275 | var size = System.Math.Pow(2, this.Zoom); 276 | return this.X < size && this.Y < size; 277 | } 278 | return false; 279 | } 280 | } 281 | 282 | /// 283 | /// Returns the tile at the given location at the given zoom. 284 | /// 285 | public static Tile CreateAroundLocation(double lat, double lon, int zoom) 286 | { 287 | int x, y; 288 | if (!Tile.CreateAroundLocation(lat, lon, zoom, out x, out y)) 289 | { 290 | return null; 291 | } 292 | return new Tile(x, y, zoom); 293 | } 294 | 295 | /// 296 | /// Returns the tile at the given location at the given zoom. 297 | /// 298 | public static ulong CreateAroundLocationId(double lat, double lon, int zoom) 299 | { 300 | int x, y; 301 | if (!Tile.CreateAroundLocation(lat, lon, zoom, out x, out y)) 302 | { 303 | return ulong.MaxValue; 304 | } 305 | return Tile.CalculateTileId(zoom, x, y); 306 | } 307 | 308 | /// 309 | /// A fast method of calculating x-y without creating a tile object. 310 | /// 311 | public static bool CreateAroundLocation(double lat, double lon, int zoom, out int x, out int y) 312 | { 313 | if (lon == 180) 314 | { 315 | lon = lon - 0.000001; 316 | } 317 | if (lat > 85.0511 || lat < -85.0511) 318 | { 319 | x = 0; 320 | y = 0; 321 | return false; 322 | } 323 | 324 | var n = (int)System.Math.Floor(System.Math.Pow(2, zoom)); 325 | 326 | x = (int)((lon + 180.0) / 360.0 * (1 << zoom)); 327 | var latRad = lat * Math.PI / 180.0; 328 | y = (int)((1.0 - Math.Log(Math.Tan(latRad) + 329 | 1.0 / Math.Cos(latRad)) / Math.PI) / 2.0 * (1 << zoom)); 330 | return true; 331 | } 332 | 333 | /// 334 | /// Gets the tile id the given lat/lon belongs to one zoom level lower. 335 | /// 336 | public ulong GetSubTileIdFor(double lat, double lon) 337 | { 338 | var factor = 2; 339 | var zoom = this.Zoom + 1; 340 | int x = 0, y = 0; 341 | if (lat >= this.CenterLat && lon < this.CenterLon) 342 | { 343 | x = this.X * factor; 344 | y = this.Y * factor; 345 | } 346 | else if(lat >= this.CenterLat && lon >= this.CenterLon) 347 | { 348 | x = this.X * factor + factor - 1; 349 | y = this.Y * factor; 350 | } 351 | else if (lat < this.CenterLat && lon < this.CenterLon) 352 | { 353 | x = this.X * factor; 354 | y = this.Y * factor + factor - 1; 355 | } 356 | else if (lat < this.CenterLat && lon >= this.CenterLon) 357 | { 358 | x = this.X * factor + factor - 1; 359 | y = this.Y * factor + factor - 1; 360 | } 361 | 362 | return Tile.CalculateTileId(zoom, x, y); 363 | } 364 | 365 | /// 366 | /// Returns the subtiles of this tile at the given zoom. 367 | /// 368 | public TileRange GetSubTiles(int zoom) 369 | { 370 | if (this.Zoom > zoom) { throw new ArgumentOutOfRangeException("zoom", "Subtiles can only be calculated for higher zooms."); } 371 | 372 | if (this.Zoom == zoom) 373 | { // just return a range of one tile. 374 | return new TileRange(this.X, this.Y, this.X, this.Y, this.Zoom); 375 | } 376 | 377 | var factor = 1 << (zoom - this.Zoom); 378 | 379 | return new TileRange( 380 | this.X * factor, 381 | this.Y * factor, 382 | this.X * factor + factor - 1, 383 | this.Y * factor + factor - 1, 384 | zoom); 385 | } 386 | 387 | /// 388 | /// Inverts the X-coordinate. 389 | /// 390 | /// 391 | public Tile InvertX() 392 | { 393 | var n = (int)System.Math.Floor(System.Math.Pow(2, this.Zoom)); 394 | 395 | return new Tile(n - this.X - 1, this.Y, this.Zoom); 396 | } 397 | 398 | /// 399 | /// Inverts the Y-coordinate. 400 | /// 401 | /// 402 | public Tile InvertY() 403 | { 404 | var n = (int)System.Math.Floor(System.Math.Pow(2, this.Zoom)); 405 | 406 | return new Tile(this.X, n - this.Y - 1, this.Zoom); 407 | } 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/AwesomeTiles/TileRange.cs: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | 3 | // Copyright (c) 2016 Ben Abelshausen 4 | 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | using System.Collections.Generic; 24 | 25 | namespace AwesomeTiles 26 | { 27 | /// 28 | /// Represents a range of tiles. 29 | /// 30 | public class TileRange : IEnumerable 31 | { 32 | /// 33 | /// Creates a new tile range. 34 | /// 35 | public TileRange(int xMin, int yMin, int xMax, int yMax, int zoom) 36 | { 37 | this.XMin = xMin; 38 | this.XMax = xMax; 39 | this.YMin = yMin; 40 | this.YMax = yMax; 41 | 42 | this.Zoom = zoom; 43 | } 44 | 45 | /// 46 | /// The minimum X of this range. 47 | /// 48 | public int XMin { get; private set; } 49 | 50 | /// 51 | /// The minimum Y of this range. 52 | /// 53 | public int YMin { get; private set; } 54 | 55 | /// 56 | /// The maximum X of this range. 57 | /// 58 | public int XMax { get; private set; } 59 | 60 | /// 61 | /// The maximum Y of this range. 62 | /// 63 | public int YMax { get; private set; } 64 | 65 | /// 66 | /// The zoom of this range. 67 | /// 68 | public int Zoom { get; private set; } 69 | 70 | /// 71 | /// Returns the number of tiles in this range. 72 | /// 73 | public int Count 74 | { 75 | get 76 | { 77 | return System.Math.Abs(this.XMax - this.XMin + 1) * 78 | System.Math.Abs(this.YMax - this.YMin + 1); 79 | } 80 | } 81 | 82 | /// 83 | /// Returns true if the given tile exists in this range. 84 | /// 85 | /// 86 | /// 87 | public bool Contains(Tile tile) 88 | { 89 | return this.XMax >= tile.X && this.XMin <= tile.X && 90 | this.YMax >= tile.Y && this.YMin <= tile.Y; 91 | } 92 | 93 | #region Functions 94 | 95 | /// 96 | /// Returns true if the given tile lies at the border of this range. 97 | /// 98 | /// 99 | /// 100 | /// 101 | /// 102 | public bool IsBorderAt(int x, int y, int zoom) 103 | { 104 | return ((x == this.XMin) || (x == this.XMax) 105 | || (y == this.YMin) || (y == this.YMin)) && 106 | this.Zoom == zoom; 107 | } 108 | 109 | /// 110 | /// Returns true if the given tile lies at the border of this range. 111 | /// 112 | /// 113 | /// 114 | public bool IsBorderAt(Tile tile) 115 | { 116 | return IsBorderAt(tile.X, tile.Y, tile.Zoom); 117 | } 118 | 119 | #endregion 120 | 121 | /// 122 | /// Returns en enumerator of tiles. 123 | /// 124 | /// 125 | public IEnumerator GetEnumerator() 126 | { 127 | return new TileRangeEnumerator(this); 128 | } 129 | 130 | /// 131 | /// Returns en enumerator of tiles. 132 | /// 133 | /// 134 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 135 | { 136 | return this.GetEnumerator(); 137 | } 138 | 139 | /// 140 | /// Simple enumerator. 141 | /// 142 | private class TileRangeEnumerator : IEnumerator 143 | { 144 | private TileRange _range; 145 | 146 | private Tile _current; 147 | 148 | public TileRangeEnumerator(TileRange range) 149 | { 150 | _range = range; 151 | } 152 | 153 | public Tile Current 154 | { 155 | get 156 | { 157 | return _current; 158 | } 159 | } 160 | 161 | public void Dispose() 162 | { 163 | _range = null; 164 | } 165 | 166 | object System.Collections.IEnumerator.Current 167 | { 168 | get { return this.Current; } 169 | } 170 | 171 | public bool MoveNext() 172 | { 173 | if (_current == null) 174 | { 175 | _current = new Tile(_range.XMin, _range.YMin, _range.Zoom); 176 | return true; 177 | } 178 | 179 | int x = _current.X; 180 | int y = _current.Y; 181 | 182 | if (x == _range.XMax) 183 | { 184 | if (y == _range.YMax) 185 | { 186 | return false; 187 | } 188 | y++; 189 | x = _range.XMin; 190 | } 191 | else 192 | { 193 | x++; 194 | } 195 | _current = new Tile(x, y, _current.Zoom); 196 | return true; 197 | } 198 | 199 | public void Reset() 200 | { 201 | _current = null; 202 | } 203 | } 204 | 205 | /// 206 | /// Defines an enumerator that start at the center of the range and moves out in a spiral. 207 | /// 208 | public class TileRangeCenteredEnumerator : IEnumerator 209 | { 210 | /// 211 | /// Holds the range to enumerate. 212 | /// 213 | private TileRange _range; 214 | 215 | /// 216 | /// Holds the current tile. 217 | /// 218 | private Tile _current; 219 | 220 | /// 221 | /// Holds the enumerated tiles. 222 | /// 223 | private HashSet _enumeratedTiles = new HashSet(); 224 | 225 | /// 226 | /// Creates the enumerator. 227 | /// 228 | /// 229 | public TileRangeCenteredEnumerator(TileRange range) 230 | { 231 | _range = range; 232 | } 233 | 234 | /// 235 | /// Returns the current tile. 236 | /// 237 | public Tile Current 238 | { 239 | get 240 | { 241 | return _current; 242 | } 243 | } 244 | 245 | /// 246 | /// Disposes of all resources associated with this object. 247 | /// 248 | public void Dispose() 249 | { 250 | _range = null; 251 | } 252 | 253 | /// 254 | /// Returns the current tile. 255 | /// 256 | object System.Collections.IEnumerator.Current 257 | { 258 | get { return this.Current; } 259 | } 260 | 261 | /// 262 | /// Holds the current desired direction. 263 | /// 264 | private DirectionEnum _direction = DirectionEnum.Up; 265 | 266 | private enum DirectionEnum 267 | { 268 | Up = 0, 269 | Right = 1, 270 | Down = 2, 271 | Left = 3 272 | } 273 | 274 | /// 275 | /// Move to the next tile. 276 | /// 277 | /// 278 | public bool MoveNext() 279 | { 280 | if (_current == null) 281 | { // start with the center tile. 282 | int centerX = (int)System.Math.Floor((_range.XMax + _range.XMin) / 2.0); 283 | int centerY = (int)System.Math.Ceiling((_range.YMax + _range.YMin) / 2.0); 284 | _current = new Tile(centerX, centerY, _range.Zoom); 285 | _enumeratedTiles.Add(_current); 286 | return true; 287 | } 288 | 289 | // check if there are more tiles to be enumerated. 290 | if (_range.Count <= _enumeratedTiles.Count) 291 | { // no more tiles left. 292 | return false; 293 | } 294 | 295 | // try to move in the desired direction. 296 | Tile next = null; 297 | while (next == null) 298 | { // try until a valid tile is found. 299 | switch (_direction) 300 | { 301 | case DirectionEnum.Up: // up 302 | next = new Tile(_current.X, _current.Y - 1, _range.Zoom); 303 | if (_enumeratedTiles.Contains(next)) 304 | { // moving up does not work, try to move left. 305 | _direction = DirectionEnum.Left; 306 | next = null; 307 | } 308 | else 309 | { // moved up, try right. 310 | _direction = DirectionEnum.Right; 311 | } 312 | break; 313 | case DirectionEnum.Left: // left 314 | next = new Tile(_current.X - 1, _current.Y, _range.Zoom); 315 | if (_enumeratedTiles.Contains(next)) 316 | { // moving left does not work, try to move down. 317 | _direction = DirectionEnum.Down; 318 | next = null; 319 | } 320 | else 321 | { // moved left, try up. 322 | _direction = DirectionEnum.Up; 323 | } 324 | break; 325 | case DirectionEnum.Down: // down 326 | next = new Tile(_current.X, _current.Y + 1, _range.Zoom); 327 | if (_enumeratedTiles.Contains(next)) 328 | { // moving down does not work, try to move right. 329 | _direction = DirectionEnum.Right; 330 | next = null; 331 | } 332 | else 333 | { // moved down, try left. 334 | _direction = DirectionEnum.Left; 335 | } 336 | break; 337 | case DirectionEnum.Right: // right 338 | next = new Tile(_current.X + 1, _current.Y, _range.Zoom); 339 | if (_enumeratedTiles.Contains(next)) 340 | { // moving right does not work, try to move up. 341 | _direction = DirectionEnum.Up; 342 | next = null; 343 | } 344 | else 345 | { // moved right, try down. 346 | _direction = DirectionEnum.Down; 347 | } 348 | break; 349 | } 350 | 351 | // test if the next is in range. 352 | if (next != null && !_range.Contains(next)) 353 | { // not in range, do not enumerate but move to next. 354 | _current = next; // pretend the next has been enumerated. 355 | next = null; 356 | } 357 | } 358 | 359 | // ok, next was found. 360 | _current = next; 361 | _enumeratedTiles.Add(_current); 362 | return true; 363 | } 364 | 365 | /// 366 | /// Resets this enumerator. 367 | /// 368 | public void Reset() 369 | { 370 | _current = null; 371 | _enumeratedTiles.Clear(); 372 | } 373 | } 374 | 375 | /// 376 | /// Returns an enumerable that enumerates tiles with the center first. 377 | /// 378 | /// 379 | public IEnumerable EnumerateInCenterFirst() 380 | { 381 | return new TileRangeCenterFirst(this); 382 | } 383 | 384 | /// 385 | /// Tile range center first enumerable. 386 | /// 387 | private class TileRangeCenterFirst : IEnumerable 388 | { 389 | /// 390 | /// Holds the tile range to be enumerated. 391 | /// 392 | private TileRange _tileRange; 393 | 394 | /// 395 | /// Creates a new range center first enumerable. 396 | /// 397 | /// 398 | public TileRangeCenterFirst(TileRange tileRange) 399 | { 400 | _tileRange = tileRange; 401 | } 402 | 403 | /// 404 | /// Returns the enumerator. 405 | /// 406 | /// 407 | public IEnumerator GetEnumerator() 408 | { 409 | return new TileRangeCenteredEnumerator(_tileRange); 410 | } 411 | 412 | /// 413 | /// Returns the enumerator. 414 | /// 415 | /// 416 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 417 | { 418 | return new TileRangeCenteredEnumerator(_tileRange); 419 | } 420 | } 421 | } 422 | } 423 | --------------------------------------------------------------------------------