Convert a Polygon of Points into a Google Encoded Polygon in Perl
Download Zipped
# Google Polygon Encoding algorithm
# Author. John D. Coryat 10/2007 USNaviguide.com and Marcelo Montagna maps.forum.nu
# Adapted from: http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/
##
# If you want to display complicated path data on a Google Map,
# the most efficient way to do so is with an encoded polyline or polygon.
##
package USNaviguide_Google_Encode ;
require 5.003 ;
use USNaviguide_Douglas_Peucker ;
use POSIX ;
use strict ;
BEGIN {
use Exporter ;
use vars qw ( $VERSION @ISA @EXPORT) ;
$VERSION = 1.0 ;
@ISA = qw ( Exporter ) ;
@EXPORT = qw (
Google_Encode
) ;
}
# Call as: (<Encoded Levels String>, <Encoded Points String>) = &Google_Encode(<Reference to array of points>, <tolerance in meters>);
# Points Array Format:
# ([lat1,lng1],[lat2,lng2],...[latn,lngn])
#
sub Google_Encode
{
my $pointsRef = shift;
my $tolerance = shift ;
my @points = @{$pointsRef};
my $encodedPoints = '' ;
my $encodedLevels = '' ;
# Check for tolerance size...
if ( !defined($tolerance) or !$tolerance )
{
$tolerance = 1 ; # Default Value: 1 meter
}
# Run D-P on the points, eliminate redundancies...
@points = &Douglas_Peucker( \@points, $tolerance ) ;
# Encode Points...
$encodedPoints = &createEncodings(\@points);
# Encode Levels...
$encodedLevels = &encodeLevels(\@points, $tolerance);
# Escape backslashes
$encodedPoints =~ s!\\!\\\\!g;
return ($encodedLevels, $encodedPoints);
}
sub encodeLevels
{
my $pointsRef = shift ;
my $tolerance = shift ;
my @points = @{$pointsRef};
my @point = ( ) ;
my %pnthash = ( ) ;
my @pntlev = ( ) ;
my $numLevels = 18 ;
my $zoomFactor = 2 ;
my $en_levels = '' ;
my $lat = 0 ;
my $lng = 0 ;
my $i = 0 ;
my $j = 0 ;
my $k = 0 ;
my $x = '' ;
my $encodelev = &encodeNumber(1) ;
# Build up a point hash to be used to reference original points to their location...
# Mark all points at lowest possible level to start...
for($i=0; $i < scalar(@points); $i++)
{
$pointsRef = $points[$i];
@point = @{$pointsRef};
$lat = $point[0];
$lng = $point[1];
$pnthash{"$lat,$lng"} = $i ;
$pntlev[$i] = $encodelev ;
}
# Iterate through the levels and calculate with an increasing tolerance...
# Each time through, mark all points left with current level...
for($i = 1; $i < $numLevels; $i++)
{
@points = &Douglas_Peucker( \@points, $tolerance * ($zoomFactor ** $i) ) ;
$encodelev = &encodeNumber($i) ;
# Mark Points Still present...
for($j=0; $j < scalar(@points); $j++)
{
$pointsRef = $points[$j];
@point = @{$pointsRef};
$lat = $point[0];
$lng = $point[1];
$k = $pnthash{"$lat,$lng"} ;
$pntlev[$k] = $encodelev ;
}
# Stop when all points are calculated and only 3 are left (line)...
if ( scalar(@points) < 4 )
{
last ;
}
}
# Force first and last point to be highest level...
$encodelev = &encodeNumber($numLevels - 1) ;
$pntlev[0] = $encodelev ;
$pntlev[$#pntlev] = $encodelev ;
# Build up encoded Level string...
foreach $x ( @pntlev )
{
$en_levels .= $x ;
}
return $en_levels;
}
# ############## Numeric subroutines below #############################
# Documentation from Google http://www.google.com/apis/maps/documentation/polylinealgorithm.html
#
# 1. Take the initial signed value:
# -179.9832104
# 2. Take the decimal value and multiply it by 1e5, flooring the result:
# -17998321
sub createEncodings
{
my $pointsRef = shift ;
my @points = @{$pointsRef};
my $encoded_points = '' ;
my $pointRef = '' ;
my @point = ( ) ;
my $plat = 0 ;
my $plng = 0 ;
my $lat = 0 ;
my $lng = 0 ;
my $late5 = 0 ;
my $lnge5 = 0 ;
my $dlat = 0 ;
my $dlng = 0 ;
my $i = 0 ;
for($i=0; $i < scalar(@points); $i++)
{
$pointRef = $points[$i];
@point = @{$pointRef};
$lat = $point[0];
$lng = $point[1];
$late5 = floor($lat * 1e5);
$lnge5 = floor($lng * 1e5);
$dlat = $late5 - $plat;
$dlng = $lnge5 - $plng;
$plat = $late5;
$plng = $lnge5;
$encoded_points .= &encodeSignedNumber($dlat) . &encodeSignedNumber($dlng);
}
return $encoded_points;
}
# 3. Convert the decimal value to binary. Note that a negative value must be inverted
# and provide padded values toward the byte boundary:
# 00000001 00010010 10100001 11110001
# 11111110 11101101 10100001 00001110
# 11111110 11101101 01011110 00001111
# 4. Shift the binary value:
# 11111110 11101101 01011110 00001111 0
# 5. If the original decimal value is negative, invert this encoding:
# 00000001 00010010 10100001 11110000 1
# 6. Break the binary value out into 5-bit chunks (starting from the right hand side):
# 00001 00010 01010 10000 11111 00001
# 7. Place the 5-bit chunks into reverse order:
# 00001 11111 10000 01010 00010 00001
# 8. OR each value with 0x20 if another bit chunk follows:
# 100001 111111 110000 101010 100010 000001
# 9. Convert each value to decimal:
# 33 63 48 42 34 1
# 10. Add 63 to each value:
# 96 126 111 105 97 64
# 11. Convert each value to its ASCII equivalent:
# `~oia@
sub encodeSignedNumber
{
use integer;
my $num = shift;
my $sgn_num = $num << 1;
if ($num < 0)
{
$sgn_num = ~($sgn_num);
}
return &encodeNumber($sgn_num);
}
sub encodeNumber
{
use integer;
my $encodeString = '' ;
my $num = shift;
my $nextValue = 0 ;
my $finalValue = 0 ;
while($num >= 0x20)
{
$nextValue = (0x20 | ($num & 0x1f)) + 63;
$encodeString .= chr($nextValue);
$num >>= 5;
}
$finalValue = $num + 63;
$encodeString .= chr($finalValue);
return $encodeString;
}
1;
__END__
=head1 SYNOPSIS
# Test Program:
# Google Polygon Encoding Test Program
# Author. John D. Coryat 10/2007 USNaviguide.com
use strict;
my $infile = $ARGV[0] ;
my $outfile = $ARGV[1] ;
my $tolerance = $ARGV[2] ;
my @Ipoints = ( ) ;
my $output = '' ;
my $encodedPoints = '' ;
my $encodedLevels = '' ;
if(!$infile or !$outfile)
{
print "Usage: Google_Encode.pl <input file name> <output file name> <tolerance in meters>\n";
print "Data format: (lat,lng)\n" ;
exit ;
}
if ( !(-s $infile) )
{
print "Input File ($infile) not found.\n" ;
exit
}
if (-s $outfile)
{
print "Output File ($outfile) exists.\n" ;
exit
}
open IN, $infile ;
while ( $data = )
{
if ( $data =~ /\((.*),(.*)\)/ )
{
push( @Ipoints, [$1,$2] ) ;
}
}
close IN ;
print "Input: " . $#Ipoints . " Tolerance in Meters: $tolerance\n" ;
# The output is a JSON string containing an array of polylines.
($encodedLevels, $encodedPoints) = &Google_Encode(\@Ipoints, $tolerance);
$output = '' ;
$output .= 'new GPolygon.fromEncoded({';
$output .= ' polylines: [';
$output .= ' {points: "'.$encodedPoints.'",';
$output .= ' levels: "'.$encodedLevels.'",';
$output .= ' color: "#0000ff",';
$output .= ' opacity: 0.7,';
$output .= ' weight: 3,';
$output .= ' numLevels: 18,';
$output .= ' zoomFactor: 2}';
$output .= '],';
$output .= ' fill: true,';
$output .= ' color: "#0000ff",';
$output .= ' opacity: 0.4,';
$output .= ' outline: true';
$output .= '});';
open OUT, ">$outfile" ;
print OUT $output;
close OUT ;
=cut
|
|
Copyright © 1997-2009 USNaviguide.com. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.