@@ -10,7 +10,6 @@ import kotlin.math.PI
10
10
import kotlin.math.atan
11
11
import kotlin.math.cos
12
12
import kotlin.math.ln
13
- import kotlin.math.roundToInt
14
13
import kotlin.math.sinh
15
14
import kotlin.math.tan
16
15
import kotlinx.serialization.Serializable
@@ -36,15 +35,66 @@ data class Tile(val x: Int, val y: Int, val zoom: Int) {
36
35
require(y in 0 .. maxXY) { " y must be between 0 and $maxXY at $zoom " }
37
36
}
38
37
38
+ override fun toString () = " $zoom /$x /$y "
39
+
40
+ val topLeft: PointCoordinates by lazy { topLeft(x = x, y = y, zoom = zoom) }
41
+
42
+ val topRight: PointCoordinates by lazy { topLeft(x = (x + 1 ) % maxXY, y = y, zoom = zoom, fixLonLat = true ) }
43
+
44
+ val bottomLeft: PointCoordinates by lazy { topLeft(x = x, y = (y + 1 ) % maxXY, zoom = zoom) }
45
+
46
+ val bottomRight: PointCoordinates by lazy { topLeft(
47
+ x = (x + 1 ) % maxXY,
48
+ y = (y + 1 ) % maxXY,
49
+ zoom = zoom,
50
+ fixLonLat = true
51
+ ) }
52
+
53
+ val bbox: BoundingBox by lazy {
54
+ if (zoom> 0 ) {
55
+ doubleArrayOf(
56
+ topLeft.longitude,
57
+ bottomRight.latitude,
58
+ bottomRight.longitude,
59
+ topLeft.latitude
60
+ )
61
+ } else {
62
+ doubleArrayOf(- 180.0 , MAX_LATITUDE ,180.0 , MIN_LATITUDE )
63
+ }
64
+ }
65
+
66
+ val east: Tile by lazy { Tile ((x + 1 ) % maxXY, y, zoom) }
67
+
68
+ val west: Tile by lazy { Tile ((x - 1 + maxXY) % maxXY, y, zoom) }
69
+
70
+ val north: Tile by lazy {
71
+ if (y > 0 ) Tile (x, y - 1 , zoom) else Tile (x, 0 , zoom)
72
+ }
73
+
74
+ val south: Tile by lazy {
75
+ val maxTiles = 1 shl zoom
76
+ if (y < maxTiles - 1 ) Tile (x, y + 1 , zoom) else Tile (x, maxTiles - 1 , zoom)
77
+ }
78
+
79
+ val northWest: Tile by lazy { north.west }
80
+
81
+ val southWest: Tile by lazy { south.west }
82
+
83
+ val southEast: Tile by lazy { south.east }
84
+
85
+ val northEast: Tile by lazy { north.east }
86
+
39
87
companion object {
40
88
const val MAX_ZOOM = 22
41
89
const val MIN_LATITUDE = - 85.05112878
42
90
const val MAX_LATITUDE = 85.05112878
43
91
44
92
/* *
45
- * Returns the topLeft corner of the tile.
93
+ * Returns the topLeft corner of the tile. Use [fixLonLat] if you are
94
+ * calculating the topleft of a tile that is North/East of the current one to
95
+ * dodge issues with MIN/MAX latitude and the dateline
46
96
*/
47
- fun topLeft (x : Int , y : Int , zoom : Int ): PointCoordinates {
97
+ fun topLeft (x : Int , y : Int , zoom : Int , fixLonLat : Boolean =false ): PointCoordinates {
48
98
// n is the number of x and y coordinates at a zoom level
49
99
// The shl operation (1 shl zoom) shifts the integer 1 to the left by zoom bits,
50
100
// which is equivalent to calculating 2^zoom
@@ -53,8 +103,17 @@ data class Tile(val x: Int, val y: Int, val zoom: Int) {
53
103
require(x in 0 .. maxCoords) { " x must be between 0 and $maxCoords at $zoom " }
54
104
require(y in 0 .. maxCoords) { " y must be between 0 and $maxCoords at $zoom " }
55
105
val lon = x.toDouble() / maxCoords * 360.0 - 180.0
56
- val lat = atan(sinh(PI * (1 - 2 * y.toDouble() / maxCoords))).toDegrees()
57
- return doubleArrayOf(lon, lat)
106
+ val lat =
107
+ atan(sinh(PI * (1 - 2 * y.toDouble() / maxCoords)))
108
+ .toDegrees()
109
+ .coerceIn(MIN_LATITUDE , MAX_LATITUDE )
110
+
111
+
112
+ return doubleArrayOf(
113
+ if (fixLonLat && lon <= - 180.0 ) 180.0 else lon,
114
+ // nice little rounding error here calculating the bottom latitude
115
+ if (fixLonLat && lat >= 85.051128 ) MIN_LATITUDE else lat
116
+ )
58
117
}
59
118
60
119
/* *
@@ -88,53 +147,20 @@ data class Tile(val x: Int, val y: Int, val zoom: Int) {
88
147
@Deprecated(" use coordinateToTile" , ReplaceWith (" coordinateToTile(p.latitude,p.longitude,zoom)" ))
89
148
fun deg2num (p : PointCoordinates , zoom : Int ) = coordinateToTile(p.latitude, p.longitude, zoom)
90
149
150
+ fun allTilesAt (zoom : Int ): Sequence <Tile > {
151
+ require(zoom in 0 .. MAX_ZOOM ) { " Zoom level must be between 0 and $MAX_ZOOM ." }
152
+ val maxXY = 1 shl zoom
153
+ return sequence {
154
+ for (x in 0 until maxXY) {
155
+ for (y in 0 until maxXY) {
156
+ yield (Tile (x, y, zoom))
157
+ }
158
+ }
159
+ }
160
+ }
91
161
}
92
162
}
93
163
94
- val Tile .topLeft get() = Tile .topLeft(x, y, zoom)
95
- val Tile .topRight: PointCoordinates
96
- get() = Tile .topLeft((x + 1 ) % maxXY, y, zoom)
97
-
98
- val Tile .bottomLeft: PointCoordinates
99
- get() = Tile .topLeft(x, (y + 1 ) % maxXY, zoom)
100
-
101
- val Tile .bottomRight: PointCoordinates
102
- get() = Tile .topLeft((x + 1 ) % maxXY, (y + 1 ) % maxXY, zoom)
103
-
104
- val Tile .bbox: BoundingBox
105
- get() {
106
- return doubleArrayOf(topLeft.longitude, bottomRight.latitude, bottomRight.longitude, topLeft.latitude)
107
- }
108
-
109
- val Tile .east: Tile
110
- get() = Tile ((x + 1 ) % maxXY, y, zoom)
111
-
112
- val Tile .west: Tile
113
- get() = Tile ((x - 1 + maxXY) % maxXY, y, zoom)
114
-
115
- val Tile .north: Tile
116
- get() {
117
- return if (y > 0 ) Tile (x, y - 1 , zoom) else Tile (x, 0 , zoom)
118
- }
119
-
120
- val Tile .south: Tile
121
- get() {
122
- val maxTiles = 1 shl zoom
123
- return if (y < maxTiles - 1 ) Tile (x, y + 1 , zoom) else Tile (x, maxTiles - 1 , zoom)
124
- }
125
-
126
- val Tile .northWest: Tile
127
- get() = north.west
128
-
129
- val Tile .southWest: Tile
130
- get() = south.west
131
-
132
- val Tile .southEast: Tile
133
- get() = south.east
134
-
135
- val Tile .northEast: Tile
136
- get() = north.east
137
-
138
164
fun Tile.parentTiles (): List <Tile > {
139
165
if (zoom == 0 ) return emptyList()
140
166
val parentTiles = mutableListOf<Tile >()
0 commit comments