Created
October 3, 2013 04:25
-
-
Save afresh1/6804965 to your computer and use it in GitHub Desktop.
Translation of Bing Quadkeys helper functions to perl. Original code at http://msdn.microsoft.com/en-us/library/bb259689.aspx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package BingQuadkeys; | |
use Exporter qw(import); | |
use strict; | |
use warnings; | |
our @EXPORT_OK = qw( | |
clip | |
map_size | |
ground_resolution | |
map_scale | |
lat_long_to_pixel_xy | |
pixel_xy_to_lat_long | |
pixel_xy_to_tile_xy | |
tile_xy_to_pixel_xy | |
tile_xy_to_quadkey | |
quadkey_to_tile_xy | |
); | |
use Math::Trig qw( tan atan :pi deg2rad rad2deg ); | |
use List::Util qw( min max ); | |
# A perl translation of Microsoft's sample code at | |
# http://msdn.microsoft.com/en-us/library/bb259689.aspx | |
# Translated by Andrew Fresh <andrew AT afresh1.com> | |
use constant { | |
EARTH_RADIUS => 6378137, | |
MIN_LATITUDE => -85.05112878, | |
MAX_LATITUDE => 85.05112878, | |
MIN_LONGITUDE => -180, | |
MAX_LONGITUDE => 180, | |
}; | |
# Clips a number to the specified minimum and maximum values. | |
sub clip { | |
my ( $n, $min_value, $max_value ) = @_; | |
return min( max( $n, $min_value ), $max_value ); | |
} | |
# Determines the map width and height (in pixels) at a specified level | |
# of detail. | |
sub map_size { | |
my ($level_of_detail) = @_; | |
return 256 << $level_of_detail; | |
} | |
# Determines the ground resolution (in meters per pixel) at a specified | |
# latitude and level of detail. | |
sub ground_resolution { | |
my ( $latitude, $level_of_detail ) = @_; | |
return | |
cos( $latitude * pi / 180 ) | |
* 2 * pi | |
* EARTH_RADIUS | |
/ map_size($level_of_detail); | |
} | |
# Determines the map scale at a specified latitude, level of detail, | |
# and screen resolution. | |
sub map_scale { | |
my ( $latitude, $level_of_detail, $screen_dpi ) = @_; | |
return ground_resolution( $latitude, $level_of_detail ) | |
* $screen_dpi / 0.0254; | |
} | |
# Converts a point from latitude/longitude WGS-84 coordinates (in degrees) | |
# into pixel XY coordinates at a specified level of detail. | |
sub lat_long_to_pixel_xy { | |
my ( $latitude, $longitude, $level_of_detail ) = @_; | |
$latitude = clip( $latitude, MIN_LATITUDE, MAX_LATITUDE ); | |
$longitude = clip( $longitude, MIN_LONGITUDE, MAX_LONGITUDE ); | |
my $x = ( $longitude + 180 ) / 360; | |
my $sin_latitude = sin( $latitude * pi / 180 ); | |
my $y = 0.5 | |
- log( ( 1 + $sin_latitude ) / ( 1 - $sin_latitude ) ) / ( 4 * pi ); | |
my $map_size = map_size($level_of_detail); | |
my $pixel_x = int clip( $x * $map_size + 0.5, 0, $map_size - 1 ); | |
my $pixel_y = int clip( $y * $map_size + 0.5, 0, $map_size - 1 ); | |
return $pixel_x, $pixel_y; | |
} | |
# Converts a pixel from pixel XY coordinates at a specified level of detail | |
# into latitude/longitude WGS-84 coordinates (in degrees). | |
sub pixel_xy_to_lat_long { | |
my ( $pixel_x, $pixel_y, $level_of_detail ) = @_; | |
my $map_size = map_size($level_of_detail); | |
my $x = ( clip( $pixel_x, 0, $map_size - 1 ) / $map_size ) - 0.5; | |
my $y = 0.5 - ( clip( $pixel_y, 0, $map_size - 1 ) / $map_size ); | |
my $latitude = 90 - 360 * atan( exp( -$y * 2 * pi ) ) / pi; | |
my $longitude = 360 * $x; | |
return $latitude, $longitude; | |
} | |
# Converts pixel XY coordinates into tile XY coordinates of the tile containing | |
# the specified pixel. | |
sub pixel_xy_to_tile_xy { | |
my ( $pixel_x, $pixel_y ) = @_; | |
my $tile_x = int $pixel_x / 256; | |
my $tile_y = int $pixel_y / 256; | |
return $tile_x, $tile_y; | |
} | |
# Converts tile XY coordinates into pixel XY coordinates of the upper-left pixel | |
# of the specified tile. | |
sub tile_xy_to_pixel_xy { | |
my ( $tile_x, $tile_y ) = @_; | |
my $pixel_x = $tile_x * 256; | |
my $pixel_y = $tile_y * 256; | |
return $pixel_x, $pixel_y; | |
} | |
# Converts tile XY coordinates into a QuadKey at a specified level of detail. | |
sub tile_xy_to_quadkey { | |
my ( $tile_x, $tile_y, $level_of_detail ) = @_; | |
my $quadkey = ''; | |
for ( my $i = $level_of_detail; $i > 0; $i-- ) { | |
my $digit = 0; | |
my $mask = 1 << ( $i - 1 ); | |
if ( ( $tile_x & $mask ) != 0 ) { | |
$digit++; | |
} | |
if ( ( $tile_y & $mask ) != 0 ) { | |
$digit++; | |
$digit++; | |
} | |
$quadkey .= $digit; | |
} | |
return $quadkey; | |
} | |
# Converts a QuadKey into tile XY coordinates. | |
sub quadkey_to_tile_xy { | |
my ($quadkey) = @_; | |
my $tile_x = my $tile_y = 0; | |
my $level_of_detail = length $quadkey; | |
my @quadkey = split //, $quadkey; | |
for ( my $i = $level_of_detail; $i > 0; $i-- ) { | |
my $mask = 1 << ( $i - 1 ); | |
my $digit = $quadkey[ $level_of_detail - $i ]; | |
if ( $digit == 0 ) { | |
# Do nothing | |
} | |
elsif ( $digit == 1 ) { | |
$tile_x |= $mask; | |
} | |
elsif ( $digit == 2 ) { | |
$tile_y |= $mask; | |
} | |
elsif ( $digit == 3 ) { | |
$tile_x |= $mask; | |
$tile_y |= $mask; | |
} | |
else { | |
die 'Invalid QuadKey digit sequence.'; | |
} | |
} | |
return $tile_x, $tile_y, $level_of_detail; | |
} | |
'Ah, I see you have the machine that goes "Bing!"'; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment