Skip to content

Instantly share code, notes, and snippets.

@afresh1
Created October 3, 2013 04:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afresh1/6804965 to your computer and use it in GitHub Desktop.
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
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