| Level: Intermediate Nathan Harrington, Programmer, IBM
14 Oct 2008 Learn how to use JavaScript and the Imager Perl module to interface with a Firefox extension for rotating image tiles in Google Maps.
Most online mapping applications assume that the desired view is always north at the
top of the image. This article presents tools and code that show how to replace the
map image with an inverted copy, where south is at the top. Using a Firefox extension
and the Imager Perl module, each tile that comprises the full image is extracted,
rotated, and placed back in the image at the appropriate spot.
Hardware and software requirements
The rotation of images and extension processing requires little CPU power. Anything
manufactured after 2000 should provide plenty of muscle for running the code presented here.
In addition to Firefox, you'll need the Imager Perl module, as well as Perl itself (see
Resources). Although tested exclusively on Firefox V3 and
Ubuntu V7.10, the code should work on older versions of Firefox and any operating system that supports Perl.
Description of rotation process
Rotating a handheld map is an simple way to more easily match your orientation with the
navigational interface. The extension described below provides an interface to take a
traditional map, like that shown in Figure 1, and rotate it 180 degrees, like that shown in Figure 2.
Figure 1. North is up
Figure 2. South is now up
The first step in building the mapRotate extension is to extract the framework used
in the developerWorks article "Integrate encryption into Google Calendar with Firefox
extensions" (see Resources). Specifically, download the
source code archive and extract it to a directory of your choice. This article uses the /home/nathan/mapRotate directory.
Modifying the existing extension
Unpack the code file from the "Integrate encryption into Google Calendar with Firefox
extensions" article into the directory /home/nathan/mapRotate and replace the contents
of the install.rdf file with that shown below.
Listing 1. New install.rdf contents
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>mapRotate@devWorks_IBM.com</em:id>
<em:name>180 degree google map rotation</em:name>
<em:version>1.0.0</em:version>
<em:description>180 degree google map rotation</em:description>
<em:creator>Nathan Harrington</em:creator>
<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>1.5</em:minVersion>
<em:maxVersion>3.0.1</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>
|
After the extension description data has been updated in install.rdf, changing the
extension's functionality is performed by editing overlay.xul and overlay.js. Change to
the chrome/content subdirectory and edit the overlay.xul file. Replace lines 8 and 9 with the code in Listing 2.
Listing 2. overlay.xul key-press grabbers
<key id="quickgooglecalendar-key" modifiers="control" key="'" \
oncommand="exportImages(event)" />
<key id="quickgooglecalendar-key" modifiers="control" key="." \
oncommand="resetImages(event)" />
|
Note that the backslashes are to indicate line continuation and should not actually be
placed in the overlay.xul file. Line 1 in the listing above (line 8 in overlay.js) will
start the exportImages subroutine when the user presses the Ctrl+. key
combination, which will reset the images to their initial state. Save and close the
overlay.xul file, and at the beginning of overlay.js, insert the lines shown in Listing
3.
Listing 3. overlay.js variables, exportImages function
var origImgs = []; // record the original filenames before rotation
var inverted = 0; // current rotation mode
var tileDir = "/home/nathan/Desktop/"; // change to your directory
function exportImages()
{
maxImg = 0;
var allImgs = content.document.getElementsByTagName("IMG");
for (var n = 0; n < allImgs.length; n++){
if( allImgs[n].src.indexOf( "v=nq.83" ) > 0 )
{
saveURL( allImgs[n].src, null, null, false, true, null );
origImgs[n] = allImgs[n].src;
maxImg++;
}//if image name match
}//for all image tags
// if enough images to process
if( maxImg > 3 )
{
var waitTim = maxImg * 200
setTimeout('delayRotate()', waitTim );
}//if enough images to process
}//exportImages
|
Note that you'll need to modify the tileDir parameter to
specify the location of files downloaded by Firefox. For most instances, this is the
Desktop directory. The exportImages function as called by
the Ctrl+. key combination loops over every image in the Google Maps page. If
the image contains the v=nq.83 string, which signifies a Map
tile, the image is copied from the cache to the tileDir directory. By changing this
string that is searched for, other image tiles — such as satellite and
terrain — can be processed. Different strings can also be inserted in the
indexOf function call to support other mapping applications,
such as Yahoo! or Live maps.
After each image is saved, a check for a minimum number of images to process will
trigger a timeout-enabled call to the delayRotate function.
The number of milliseconds to wait before calling the delayRotate function is determined by simply waiting 200
milliseconds for each image. For large numbers of tile images on high-resolution
screens, the saveURL function can cause a substantial delay
as each image is copied from the cache. This setTimeout
approach with a simple delay computation is an easy way to make sure the image-rotation
step is not performed before all the images are ready. Listing 4 details the delayRotate function.
Listing 4. overlay.js delayRotate function
function delayRotate()
{
var fileExe = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
fileExe.initWithPath( tileDir + "tileRotate.pl");
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(fileExe);
var args = [];
args[0] = maxImg;
args[1] = tileDir;
process.run(true, args, args.length);
var allImgs = content.document.getElementsByTagName("IMG");
for (var n = 0; n < allImgs.length; n++){
if( allImgs[n].src.indexOf( "v=nq.83" ) > 0 )
{
allImgs[n].src = "file://" + tileDir +
"rotated.out/" + allImgs[n].src.substr(25) + ".png";
}//if image name match
}//for all image tags
inverted =1;
}//delayRotate
|
After defining an nsIProcess to call the external program
and specifying the number of images and directory they are saved in, the
tileDir/tileRotate.pl program is run. When the blocking process run is complete, each
image is then loaded from the local file system at the appropriate location.
Programmers familiar with Firefox will remember that this loading of local files from a
document retrieved elsewhere is not permitted by Firefox's security systems. See Firefox modifications, loading the extension to learn a simple approach
to enable this particular Web site to load local files.
Listing 5 continues with the resetImages function, added
below the exportImages and delayRotate functions.
Listing 5. overlay.js resetImages function
function resetImages()
{
if( inverted == 1 )
{
inverted =0;
var allImgs = content.document.getElementsByTagName("IMG");
for (var n = 0; n < allImgs.length; n++)
{
if( allImgs[n].src.indexOf( "v=nq.83" ) > 0 )
{
allImgs[n].src = origImgs[n];
}//if image to set back to
}//for all image tags
}// if in inverted mode
}//resetImages
|
If the current mode is inverted, the resetImages function
loops through every tile image and resets the URL back to the original, which
immediately reverts the image to the nonrotated version in the cache.
tileRotate.pl program — Rotating exported tiles
With the modifications to the existing extension complete, the next step is to create
the tileRotate.pl Perl program that handles the rotation and translation of the image
tiles. Change to the directory specified above as the tileDir variable
(/home/nathan/Desktop/ in this example). Create a file called tileRotate.pl with the
code in Listing 6.
Listing 6. tileRotate.pl validity checks, variable declaration
#!/usr/bin/perl -w
# tileRotate.pl - rotate and translate locations of Google Maps tiles
use strict;
use Imager;
die "specify number of tiles, directory " unless @ARGV == 2;
my $numTiles = $ARGV[0];
my $tileDir = $ARGV[1];
# remove and rotated originals
my $res = `rm $tileDir/rotated.out/*.png`;
my @origTiles = `ls $tileDir/v\=nq.83*`;
if( ($#origTiles+1) != $numTiles )
{
# remove originals for easier problem solving
$res = `rm $tileDir/v\=nq*`;
die "number of images does not match\n";
}#if number of tiles does not match
my $row = 0; # temporary row tracker
my %vals = (); # filename for a given col,row tile
my $maxCol = 0; # maximum columns
my $maxRow = 0; # maximum rows
my $lastXval = -1; # temporary tracker to increment the row
|
As shown above, the tileRotate.pl script requires a number of tiles and a
tile directory as input variables. After removing any existing rotated images, the
number of tiles saved on disk is read. If your processing continuously dies at this
point, it's likely not enough time is being given between the nonblocking saveURL calls and the time the tileRotate.pl process is called.
Consider increasing the millisecond multiplier from 200 to 400 to give your system more
time to save the image tiles before the rotate process begins. If the number of tiles
on disk matches the expected number, processing continues. The tileRotate.pl program
continues below.
Listing 7. tileRotate.pl filename to column and row mapping
for my $tile( @origTiles )
{
# get the x and y values from the filename
chomp($tile);
my @vals = split '=', $tile;
my $xVal = substr($vals[3],0,index( $vals[3], '&') );
my $yVal = substr($vals[4],0,index( $vals[4], '&') );
if( $lastXval == -1 )
{
# set the current lastXval if it's the first one
$lastXval = $xVal;
}elsif( $lastXval ne $xVal )
{
# wrap the row, and increase the total number of columns
$row = 0;
$lastXval = $xVal;
$maxCol++;
}else
{
# normal increase of row, record the maximum number of rows
$row++;
if( $row > $maxRow ){ $maxRow = $row }
}
# set the filename to the current computed column,row position
$vals{ $maxCol }{ $row } = $tile;
}#for each tile
|
Each filename/tile recorded in the @origTiles array is saved
to disk by the extension. These files can have nonsequential x= parameters, or jump by a large quantity between columns. The
for loop in Listing 7 translates their filename vagaries into
simple X,Y (column and row) coordinates, and stores their values in the %vals hash. For example, the code above will record a tile with a
filename like v=w2.80&hl=en&x=228&y=4&zoom=15&s=Gal to be at
position 0,4 in the (column,row) hash. This development of a
filename tied to a coordinate is required to accurately translate the rotated tiles to
their proper post-rotation position. Listing 8 demonstrates the rotation and translation using the Imager module.
Listing 8. tileRotate.pl rotation and translation of the image tiles
for( my $colCount = $maxCol; $colCount >= 0; $colCount-- )
{
for( my $rowCount = $maxRow; $rowCount >= 0; $rowCount-- )
{
# read the original tile assigned those coordinates
my $chunk = Imager->new();
$chunk->read( file=> "$vals{ $colCount }{ $rowCount }" ) or
die "can't read image tile ";
$chunk->flip(dir=>"v");
# write the tile out to it's appropriately translated position (filename)
my $fname = substr($vals{ $colCount }{ $maxRow-$rowCount }, 21);
$chunk->write( file=>"$tileDir/rotated.out/$fname.png") or
die "can't write chunk ", $chunk->errstr, "\n";
}#for rows
}#for columsn
# remove originals
$res = `rm $tileDir/v\=nq*`;
|
Across each row and down each column, the appropriate tile is read from those saved in
the tileDir . These tiles are then rotated 180 degrees and
saved with the filename of its appropriately translated position. Using the same
filename suffix makes transitioning from rotated and back easier in the JavaScript
resetImages function.
Firefox modifications, loading the extension
As mentioned, you need to modify the Firefox security settings to allow loading of
local file information by a Web page loaded from maps.google.com. After closing all
Firefox processes, add the lines in Listing 9 to the prefs.js in your Mozilla profile
directory.
Listing 9. Modify Firefox security settings
user_pref("capability.policy.localfilelinks.checkloaduri.enabled","allAccess");
user_pref("capability.policy.localfilelinks.sites", "http://maps.google.com");
user_pref("capability.policy.policynames", "localfilelinks");
|
Note that it's possible maps.google.com can try to access files on your local machine
if you enable this option. Consult other extensions and the Mozilla Developer Center
for information on how to temporarily enable these options during a call for rotation.
You'll also need to add the local mapRotate extension to your
Firefox extensions library. Both the "Integrate encryption into Google Calendar with
Firefox extensions" and Mozilla developers resources provide documentation about adding
a local extension to your Firefox environment. See Listing 10 for the basics on
adding the mapRotate extension.
Listing 10. Commands to add the mapRotate extension to Firefox
cd ~/.mozilla/firefox/m717dved/extensions
cat > mapRotate@devWorks_IBM.com
(then paste the line:)
/home/nathan/mapRotate/
|
Usage
Change to the directory specified by the tileDir
parameter and create a directory called rotated.out. In the example above, the directory created is /home/nathan/Desktop/rotated.out/.
Start Firefox and make sure the extension is loaded. Adding options to your
environment — such as the "Extension Developers Extension" — are very useful for
diagnosing problems or other issues you may have with your setup. If you're confident
the extension is loaded correctly, view a street address on maps.google.com and press
Ctrl+.. Wait a few seconds (depending on the number of tiles to be processed),
and you will see the map rotated 180 degrees to give you a new perspective on the geography.
Before moving the map, make sure you press Ctrl+. to reset the tiles to their
original URLs. This is critical to the effective redisplaying of rotated tiles after the map has been moved.
Conclusion, further examples
With the tools and code presented above, you can create a rotated map within the
existing interface. Similar to the maps.live.com bird's-eye view rotation, such
displays create a unique view of the environment and a new perspective on the
surrounding area. Consider modifying the code to support each cardinal direction
of rotation, add satellite imagery rotation, or expand the acceptable URL strings to support other mapping applications.
Download Description | Name | Size | Download method |
---|
Sample code | os-firefox-rotate-images-mapRotate_0.1.zip | 11KB | HTTP |
---|
Resources Learn
Get products and technologies
-
Download the Imager module from CPAN.
-
UNIX® and Linux® users: If you're new to installing Perl modules,
install Andreas J. Konig's CPAN
module, which automates the installation of other modules.
-
Download Perl or read about Perl at Perl.org.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
About the author | | | Nathan Harrington is a programmer at IBM currently working with Linux and resource-locating technologies. |
Rate this page
| |