// Greasemap
// version 0.6 BETA!
// 2005-07-18
// Copyright (c) 2005, Vinq, LLC
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// IF YOU ARE UPGRADING FROM A PREVIOUS VERSION OF GREASEMAP, go to
// Tools/Manage User Scripts and manually uninstall the previous
// version before installing this one.  Sorry, this is a limitation
// of Greasemonkey.
// 
// To uninstall, go to Tools/Manage User Scripts,
// select "Greasemap", and click Uninstall.
//
// --------------------------------------------------------------------
// WHAT IT DOES:
//
// On any web page that doesn't match *vinq.com*
// - searches for addresses, zip codes, and geolocation tags within the page
// - if it finds any,
//   - injects a Google map using the new Google Maps API at the top
//     of the page, including credit to Google and to Vinq Greasemap
// --------------------------------------------------------------------
// CHANGELOG:
// 0.6 2005-07-21      Removed GM_ functions (menu choice) from script, due to greasemonkey problem
//
// 0.5 2005-07-18      Don't add DHTML output until onload time.
//                     (As suggested in the first tip at:
//                      http://greasemonkey.mozdev.org/authoring.html .)
//                     (Jeff Dairiki)
//                     Make address matching more forgiving, working also for
//                     zipcode-less addresses with no comma before the state abbrev
//
// 0.4.gtd1 2005-07-18 DOM-ify the DHTML generation code.
//
//                     Fix some bugs which express themselves when
//                     other greasemonkey scripts are also adding
//                     content to the top of the page: the
//                     close-map X box wasn't position properly;
//                     clicking the close-map X box sometimes deleted
//                     the other scripts output rather than the greasemap.
//                     (Jeff Dairiki)
//
//                     DOM-ify the meta-tag parsing code.  (Also
//                     remove exact duplicates from the spots list,
//                     so that if a page has both a geo.position and
//                     and ICBM tag, it doesn't get counted twice.)
//
// 0.4 2005-07-14 8pm  New link to show the same map in a new, big window
//                     On big map, addresses link to show infowindow on that pin
//                     On big map, infowindow shows address + directions form
//                     Parse geo:lat and geo:lon tags from Flickr + other sites
//                     Parse U.S. addresses which lack a zip code, as long as the state
//                       abbreviation is valid; use case http://www.google.com/search?q=movie%3A+movie+crash&sc=1&near=350+Lexington+Ave%2C+New+York%2C+NY&rl=1
// 0.3 2005-07-02 4pm  Version tag 0.3 in iframe URL, so map frame can notify users to upgrade if their version is lower
//                     New links to show + hide the map, hidden map loads faster (doesn't hit vinq.com)
//                     New menu items to toggle whether map should be shown immediately
//                     Rewrite ; to , within lat;long points, for better matching
// 0.2 2005-06-30 4pm  Added support for geo.position and GeoURL meta tags
// 0.1 2005-06-30 3am  Initial version -- Mark Torrance www.marktorrance.com
//
// ==UserScript==
// @name            Greasemap
// @namespace       http://www.vinq.com/greasemap/
// @description     Embed Google Map at top of pages containing georeferences
// @include         *
// @exclude         http://maps.google.com/*
// ==/UserScript==

(function() {
    var Greasemap = {
       findLocations: function(c) {
         var l = [];
         // NOTE: Each regex pattern here must also be recognized by the greasemap.html script at vinq.com
         // Look for addresses with zipcodes
	 var addr_re = /\b(One|\d+)\s+(<br>|[A-Za-z0-9\-.,'\s])+?\s+[A-Z][A-Z]\s+\d\d\d\d\d\b/;
	 var addr = addr_re.exec(c);
	 var pos = 0;
	 while (addr) {
	   l.push(addr[0]);
	   pos += addr.index + addr[0].length;
	   addr = addr_re.exec(c.substr(pos));
         }

	 // Also look for addresses without zipcodes; state abbrev must be valid and must be preceded by comma
	 // Use case: http://www.google.com/search?q=movie%3A+movie+crash&sc=1&near=350+Lexington+Ave%2C+New+York%2C+NY&rl=1
	 addr_re = /\b(\d+)\s+(<br>|[A-Za-z0-9\-.,'\s])+\s+([A-Z][A-Z])\s+/;
	 addr = addr_re.exec(c);
	 pos = 0;
         var isState = {AL:1,AK:1,AZ:1,AR:1,CA:1,CO:1,CT:1,DE:1,DC:1,FL:1,GA:1,HI:1,ID:1,IL:1,IN:1,IA:1,KS:1,KY:1,LA:1,ME:1,MD:1,MA:1,MI:1,MN:1,MS:1,MO:1,MT:1,NE:1,NV:1,NH:1,NJ:1,NM:1,NY:1,NC:1,ND:1,OH:1,OK:1,OR:1,PA:1,RI:1,SC:1,SD:1,TN:1,TX:1,UT:1,VT:1,VA:1,WA:1,WV:1,WI:1,WY:1};
	 while (addr) {
           // Accept this address if the state abbreviation is valid
	   if (addr[3] && isState[addr[3]]) { l.push(addr[0]); }
	   pos += addr.index + addr[0].length;
	   addr = addr_re.exec(c.substr(pos));
         }
	 return l;
       },

       insertMap: function(locs,spots,num) {
	 // locs are addresses, semicolon-separated.  spots are lat+long pairs, comma-separated.

	 var mapurl = 'http://www.vinq.com/greasemap.html?v=0.6&spots='
                      + encodeURIComponent(spots)
                      + '&locs=' + encodeURIComponent(locs);

	 var gmd = document.createElement("div");
 	 document.body.insertBefore(gmd, document.body.firstChild);

         function show_map () {
           if (!gmd)
             return;
           var f = document.createElement('iframe');
           f.setAttribute('width', '100%');
           f.setAttribute('height', '150');
           f.setAttribute('src', mapurl);
           gmd.appendChild(f);

           gmd.style.position = 'relative';
           var i = document.createElement('img');
           i.setAttribute('src',
                          'http://www.vinq.com/greasemap/closebox.png');
           i.style.position = 'absolute';
           i.style.top = '4px';
           i.style.right = '16px';
           i.addEventListener('click', function () {
             if (gmd) {
               document.body.removeChild(gmd);
               gmd = null;
             }
           }, false);
           gmd.appendChild(i);
         }

           var note = document.createElement('div');
           note.style.backgroundColor = '#def';
           note.style.padding = '2px 8px 2px 10px';
           note.style.fontFamily = 'arial, sans-serif';
           note.style.fontSize = '8pt';
  	   var plural = num > 1 ? 's' : '';
           note.appendChild(document.createTextNode(
             'Greasemap found ' + num + ' location' + plural + ' in this page.'
             + ' Click here to map them.'));
           gmd.appendChild(note);
           note.addEventListener('click', function () {
             if (gmd && note) {
               gmd.removeChild(note);
               note = null;
             }
             show_map();
           }, false);
       }
    }

    var href = window.location.href;

    var spots = [];
    spots.contains = function (value) {
      for (var i = 0; i < this.length; i++)
        if (this[i] == value)
          return true;
      return false;
    };
    {
      var metas = document.getElementsByTagName('meta');
      for (var i = 0; i < metas.length; i++) {
        var meta_name = metas[i].getAttribute('name');
        if (meta_name == 'geo.position' || meta_name == 'ICBM') {
          var pos = metas[i].getAttribute('content').replace(/;/g,',');
          if (!spots.contains(pos))
            spots.push(pos);
        }
      }
    }

    var content = document.body.innerHTML;
    content = content.replace(/\n/g,' ');
    content = content.replace(/<b>/ig,'');
    content = content.replace(/<\/?font[^>]*>/ig,'');
    content = content.replace(/<\/b>/ig,'');
    content = content.replace(/&nbsp;/g,' ');
    content = content.replace(new RegExp("<br ?/?>","ig"),'<br>');
    content = content.replace(/\s+/g,' ');
    if (!href.match(/vinq.com\/greasemap.?\.html/)) {
      var latlong_re = /\b(N|S) [\d\.]+. ([\d\.]+\s+)?\s*(W|E) [\d\.]+. ([\d\.]+)?/;
      var latlong = latlong_re.exec(content);
      var pos = 0;
      while (latlong) {
        spots.push(latlong[0]);
        pos += latlong.index + latlong[0].length;
        latlong = latlong_re.exec(content.substr(pos));
      }
      latlong_re = /\bgeo:lat=([\-\d\.]+).*geo:lon=([\-\d\.]+)\b/;
      var latlong = latlong_re.exec(content);
      var pos = 0;
      while (latlong) {
        if (latlong[1] && latlong[2]) { spots.push(latlong[1]+','+latlong[2]); }
        pos += latlong.index + latlong[0].length;
        latlong = latlong_re.exec(content.substr(pos));
      }
      latlong_re = /\bgeo:lon=([\-\d\.]+).*geo:lat=([\-\d\.]+)\b/;
      var latlong = latlong_re.exec(content);
      var pos = 0;
      while (latlong) {
        if (latlong[1] && latlong[2]) { spots.push(latlong[2]+','+latlong[1]); }
        pos += latlong.index + latlong[0].length;
        latlong = latlong_re.exec(content.substr(pos));
      }

      // Call the internal function, defined above, to search for addresses
      var locs = Greasemap.findLocations(content);

      if ((spots && spots.length > 0) || (locs && locs.length > 0)) {
         window.addEventListener("load", function(e) {
           Greasemap.insertMap(locs.join(';'), spots.join(';'),
                               locs.length+spots.length);
         }, false);
      }
    }
})();

// END FILE

