Write a Firefox extension to rotate images in online mapping applications

Get a different perspective from online mapping tools by changing the view orientation

Learn how to use JavaScript and the Imager Perl module to interface with a Firefox extension for rotating image tiles in Google Maps.

Share:

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
North is up
Figure 2. South is now up
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

DescriptionNameSize
Sample codeos-firefox-rotate-images-mapRotate_0.1.zip11KB

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

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=344201
ArticleTitle=Write a Firefox extension to rotate images in online mapping applications
publish-date=10142008