tile38/internal/bing/bing.go

163 lines
6.7 KiB
Go

// https://msdn.microsoft.com/en-us/library/bb259689.aspx
package bing
import "math"
const (
// EarthRadius is the radius of the earth
EarthRadius = 6378137.0
// MinLatitude is the min lat
MinLatitude = -85.05112878
// MaxLatitude is the max lat
MaxLatitude = 85.05112878
// MinLongitude is the min lon
MinLongitude = -180.0
// MaxLongitude is the max lon
MaxLongitude = 180.0
// TileSize is the size of a tile
TileSize = 256
// MaxLevelOfDetail is the max level of detail
MaxLevelOfDetail = 38
)
// 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
}