mirror of https://github.com/tidwall/tile38.git
156 lines
6.6 KiB
Go
156 lines
6.6 KiB
Go
// https://msdn.microsoft.com/en-us/library/bb259689.aspx
|
|
|
|
package bing
|
|
|
|
import "math"
|
|
|
|
const (
|
|
EarthRadius = 6378137.0 // The radius of the earth
|
|
MinLatitude = -85.05112878 // The min lat
|
|
MaxLatitude = 85.05112878 // The max lat
|
|
MinLongitude = -180.0 // The min lon
|
|
MaxLongitude = 180.0 // The max lon
|
|
TileSize = 256 // The size of a tile
|
|
MaxLevelOfDetail = 38 // The max level of detail
|
|
)
|
|
|
|
// Clips a number to the specified minimum and maximum values.
|
|
// Param 'n' is the number to clip.
|
|
// Param 'minValue' is the minimum allowable value.
|
|
// Param 'maxValue' is the maximum allowable value.
|
|
// Returns the clipped value.
|
|
func clip(n, minValue, maxValue float64) float64 {
|
|
if n < minValue {
|
|
return minValue
|
|
}
|
|
if n > maxValue {
|
|
return maxValue
|
|
}
|
|
return n
|
|
}
|
|
|
|
// MapSize determines the map width and height (in pixels) at a specified level of detail.
|
|
// Param 'levelOfDetail' is the level of detail, from 1 (lowest detail) to N (highest detail).
|
|
// Returns the map width and height in pixels.
|
|
func MapSize(levelOfDetail uint64) uint64 {
|
|
return TileSize << levelOfDetail
|
|
}
|
|
|
|
// // Determines the ground resolution (in meters per pixel) at a specified latitude and level of detail.
|
|
// // Param 'latitude' is the Latitude (in degrees) at which to measure the ground resolution.
|
|
// // Param 'levelOfDetail' is the Level of detail, from 1 (lowest detail) to N (highest detail).
|
|
// // Returns the ground resolution, in meters per pixel.
|
|
// func GroundResolution(latitude float64, levelOfDetail uint64) float64 {
|
|
// latitude = clip(latitude, MinLatitude, MaxLatitude)
|
|
// return math.Cos(latitude*math.Pi/180) * 2 * math.Pi * EarthRadius / float64(MapSize(levelOfDetail))
|
|
// }
|
|
|
|
// // Determines the map scale at a specified latitude, level of detail, and screen resolution.
|
|
// // Param 'latitude' is the latitude (in degrees) at which to measure the map scale.
|
|
// // Param 'levelOfDetail' is the level of detail, from 1 (lowest detail) to N (highest detail).
|
|
// // Param 'screenDpi' is the resolution of the screen, in dots per inch.
|
|
// // Returns the map scale, expressed as the denominator N of the ratio 1 : N.
|
|
// func MapScale(latitude float64, levelOfDetail, screenDpi uint64) float64 {
|
|
// return GroundResolution(latitude, levelOfDetail) * float64(screenDpi) / 0.0254
|
|
// }
|
|
|
|
// LatLongToPixelXY converts a point from latitude/longitude WGS-84 coordinates (in degrees) into pixel XY coordinates at a specified level of detail.
|
|
// Param 'latitude' is the latitude of the point, in degrees.
|
|
// Param 'longitude' is the longitude of the point, in degrees.
|
|
// Param 'levelOfDetail' is the level of detail, from 1 (lowest detail) to N (highest detail).
|
|
// Return value 'pixelX' is the output parameter receiving the X coordinate in pixels.
|
|
// Return value 'pixelY' is the output parameter receiving the Y coordinate in pixels.
|
|
func LatLongToPixelXY(latitude, longitude float64, levelOfDetail uint64) (pixelX, pixelY int64) {
|
|
latitude = clip(latitude, MinLatitude, MaxLatitude)
|
|
longitude = clip(longitude, MinLongitude, MaxLongitude)
|
|
x := (longitude + 180) / 360
|
|
sinLatitude := math.Sin(latitude * math.Pi / 180)
|
|
y := 0.5 - math.Log((1+sinLatitude)/(1-sinLatitude))/(4*math.Pi)
|
|
mapSize := float64(MapSize(levelOfDetail))
|
|
pixelX = int64(clip(x*mapSize+0.5, 0, mapSize-1))
|
|
pixelY = int64(clip(y*mapSize+0.5, 0, mapSize-1))
|
|
return
|
|
}
|
|
|
|
// PixelXYToLatLong converts a pixel from pixel XY coordinates at a specified level of detail into latitude/longitude WGS-84 coordinates (in degrees).
|
|
// Param 'pixelX' is the X coordinate of the point, in pixels.
|
|
// Param 'pixelY' is the Y coordinates of the point, in pixels.
|
|
// Param 'levelOfDetail' is the level of detail, from 1 (lowest detail) to N (highest detail).
|
|
// Return value 'latitude' is the output parameter receiving the latitude in degrees.
|
|
// Return value 'longitude' is the output parameter receiving the longitude in degrees.
|
|
func PixelXYToLatLong(pixelX, pixelY int64, levelOfDetail uint64) (latitude, longitude float64) {
|
|
mapSize := float64(MapSize(levelOfDetail))
|
|
x := (clip(float64(pixelX), 0, mapSize-1) / mapSize) - 0.5
|
|
y := 0.5 - (clip(float64(pixelY), 0, mapSize-1) / mapSize)
|
|
latitude = 90 - 360*math.Atan(math.Exp(-y*2*math.Pi))/math.Pi
|
|
longitude = 360 * x
|
|
return
|
|
}
|
|
|
|
// PixelXYToTileXY converts pixel XY coordinates into tile XY coordinates of the tile containing the specified pixel.
|
|
// Param 'pixelX' is the pixel X coordinate.
|
|
// Param 'pixelY' is the pixel Y coordinate.
|
|
// Return value 'tileX' is the output parameter receiving the tile X coordinate.
|
|
// Return value 'tileY' is the output parameter receiving the tile Y coordinate.
|
|
func PixelXYToTileXY(pixelX, pixelY int64) (tileX, tileY int64) {
|
|
return pixelX >> 8, pixelY >> 8
|
|
}
|
|
|
|
// TileXYToPixelXY converts tile XY coordinates into pixel XY coordinates of the upper-left pixel of the specified tile.
|
|
// Param 'tileX' is the tile X coordinate.
|
|
// Param 'tileY' is the tile Y coordinate.
|
|
// Return value 'pixelX' is the output parameter receiving the pixel X coordinate.
|
|
// Return value 'pixelY' is the output parameter receiving the pixel Y coordinate.
|
|
func TileXYToPixelXY(tileX, tileY int64) (pixelX, pixelY int64) {
|
|
return tileX << 8, tileY << 8
|
|
}
|
|
|
|
/// TileXYToQuadKey converts tile XY coordinates into a QuadKey at a specified level of detail.
|
|
/// Param 'tileX' is the tile X coordinate.
|
|
/// Param 'tileY' is the tile Y coordinate.
|
|
/// Param 'levelOfDetail' is the Level of detail, from 1 (lowest detail) to N (highest detail).
|
|
/// Returns a string containing the QuadKey.
|
|
func TileXYToQuadKey(tileX, tileY int64, levelOfDetail uint64) string {
|
|
quadKey := make([]byte, levelOfDetail)
|
|
for i, j := levelOfDetail, 0; i > 0; i, j = i-1, j+1 {
|
|
mask := int64(1 << (i - 1))
|
|
if (tileX & mask) != 0 {
|
|
if (tileY & mask) != 0 {
|
|
quadKey[j] = '3'
|
|
} else {
|
|
quadKey[j] = '1'
|
|
}
|
|
} else if (tileY & mask) != 0 {
|
|
quadKey[j] = '2'
|
|
} else {
|
|
quadKey[j] = '0'
|
|
}
|
|
}
|
|
return string(quadKey)
|
|
}
|
|
|
|
/// QuadKeyToTileXY converts a QuadKey into tile XY coordinates.
|
|
/// Param 'quadKey' is the quadKey of the tile.
|
|
/// Return value 'tileX' is the output parameter receiving the tile X coordinate.
|
|
/// Return value 'tileY is the output parameter receiving the tile Y coordinate.
|
|
/// Return value 'levelOfDetail' is the output parameter receiving the level of detail.
|
|
func QuadKeyToTileXY(quadKey string) (tileX, tileY int64, levelOfDetail uint64) {
|
|
levelOfDetail = uint64(len(quadKey))
|
|
for i := levelOfDetail; i > 0; i-- {
|
|
mask := int64(1 << (i - 1))
|
|
switch quadKey[levelOfDetail-i] {
|
|
case '0':
|
|
case '1':
|
|
tileX |= mask
|
|
case '2':
|
|
tileY |= mask
|
|
case '3':
|
|
tileX |= mask
|
|
tileY |= mask
|
|
default:
|
|
panic("Invalid QuadKey digit sequence.")
|
|
}
|
|
}
|
|
return
|
|
}
|