// vc_id = "$Id: map.js 12679 2009-11-12 20:58:45Z robert $"
// This library represents fairly generic mapping functions.
// More specialized uses of these methods should be placed in
// a separate library.
// @author Robert D. Rice

// Constructor for a GeoPoint object
// @param double x
// @param double y
function GeoPoint( pointX,
                   pointY ) {
  // set attributes
  this.x = pointX;
  this.y = pointY;
  this.target = null;

  // set methods
  this.toString = GeoPointToString;
  this.diff = GeoPointDiff;
}

// to string method for a GeoPoint
// @return string representation
function GeoPointToString( ) {
   var toString = "";
   toString = toString + "( " + this.x;
   toString = toString + ", " + this.y + " )";
   return toString;
}

// reduce this point by
// one that is passed in.
// @param GeoPoint to subtract
function GeoPointDiff( point ) {
   this.x = this.x - point.x;
   this.y = this.y - point.y; 
}

// Add two geopoints together
// @param point 1
// @param point 2
// @return sum
function GeoPointSum( point1, point2 ) {
   return new GeoPoint( point1.x + point2.x,
                        point1.y + point2.y );
}


// Constructor for a GeoRectangle object
// @param double top
// @param double left
// @param double bottom
// @param double right
function GeoRectangle( extentTop,
                       extentLeft,
                       extentBottom,
                       extentRight ) {
  // set attributes
  this.top = extentTop;
  this.left = extentLeft;
  this.bottom = extentBottom;
  this.right = extentRight;

  // the start point
  this.start = null;

  // the html element representing this box
  this.html = null;

  // set methods
  this.toString = GeoRectangleToString;
  this.union = GeoRectangleUnion;
  this.grow = GeoRectangleGrow;
  this.border = GeoRectangleBorder;
  this.center = GeoRectangleCenter;
  this.isInside = GeoRectangleIsInside;
}

// to string method for a GeoRectangle
// @return string representation
function GeoRectangleToString( ) {
   var toString = "";
   toString = toString + "{ left:" + this.left;
   toString = toString + " right:" + this.right;
   toString = toString + " top:" + this.top;
   toString = toString + " bottom:" + this.bottom + " }";
   return toString;
}

// this method unions the box with a point
// thus potentially increasing ( or reducing ) the size of the box
// this isn't a true union, as it doesn't keep growing ( necessarily ).
// initially, this method will look to the start point to decide what
// to do.  we could enhance this method to only grow, if start point
// isn't set ( or some other property ).
// @param point object
function GeoRectangleUnion( point ) {
  if ( point.x <= this.start.x ) {
    this.left = point.x;
    this.right = this.start.x;
  }
  if ( point.x >= this.start.x ) {
    this.left = this.start.x;
    this.right = point.x;
  }

  if ( point.y <= this.start.y ) {
     this.top = point.y;
     this.bottom = this.start.y;
  }
  if ( point.y >= this.start.y ) {
     this.top = this.start.y;
     this.bottom = point.y;
  }
}

// this method unions the box with a point
// the rectangle will grow if the point is outside
// @param point object
// @param true if screen coordinates, false otherwise
function GeoRectangleGrow( point, screen ) {
  var botL = screen ? this.bottom : point.y;
  var botR = screen ? point.y : this.bottom;
  var topL = screen ? this.top : point.y;
  var topR = screen ? point.y : this.top;

  if ( point.x < this.left ) {
    this.left = point.x;
  }
  if ( point.x > this.right ) {
    this.right = point.x;
  }

  if ( botL < botR ) {
      this.bottom = point.y;
  }
  if ( topL > topR ) {
      this.top = point.y;
  }
}

// this method adds a border to the rectangle
// @param nteger representing percentage of the width/height
function GeoRectangleBorder( border ) {
    var hundred = 100;
    var widthBorder =
	( this.right - this.left ) * 
	( border / hundred );
    var heightBorder = 
	( this.top - this.bottom ) *
	( border / hundred );

    this.top = this.top + heightBorder;
    this.left = this.left - widthBorder;
    this.bottom = this.bottom - heightBorder;
    this.right = this.right + widthBorder;
}

// this method will find the center point
// @return GeoPoint representing center
function GeoRectangleCenter( ) {
    return new GeoPoint( ( ( this.left + this.right ) / 2 ), 
			 ( ( this.top + this.bottom ) / 2 ) );
}

// method to determine if the position
// is in the rectangle
// @param a GeoPoint object
// @param true if screen coordinates, false otherwise
// @return true if position inside the box
function GeoRectangleIsInside( position,
                               screen ) {
  var inside = false;
  var minX = this.left;
  var maxX = this.right;
  var minY = screen ? this.top : this.bottom;
  var maxY = screen ? this.bottom : this.top;
  
  if ( position.x >= minX &&
       position.x <= maxX &&
       position.y >= minY &&
       position.y <= maxY ) {
    inside = true;
  }

  return inside;
}

// Constructor for a Map object
// This class implements the positionChange method to satisfy 
// the PositionChangeListener interface.  It does so by updating the bounds.
// @param identifier for this map
// @param GeoRectangle representing the map in map coords
// @param GeoPoint representing the map center in map coords
// @param GeoRectangle representing the map in screen coords
// @param int zoomLevel
function Map( mapID,
              mapExtent,
              mapCenter,
              mapScreen,
              mapZoomLevel ) {
  // set attributes
  this.id = mapID;
  this.extent = mapExtent; // map coords
  this.center = mapCenter; // map coords
  this.screen = mapScreen; // screen coords
  this.zoomLevel = mapZoomLevel;
  this.zoomLevelMax = 9;

  // the bounds for this map ( user draws box )
  // these are in screen coordinates
  this.bounds = null;

  // set methods
  this.toString = MapToString;
  this.fromScreenCoordinates = MapFromScreenCoordinates;
  this.toScreenCoordinates = MapToScreenCoordinates;
  this.positionChange = MapPositionChange;

  // holder for the current position event
  // makes it easier to initialize the bounds
  this.positionEvent = null;
}

// to string method for Map
// @return string representation
function MapToString( ) {
   var toString = "";
   toString = toString + "Map: " + this.id;
   toString = toString + "\nExtent: " + this.extent;
   toString = toString + "\nCenter: " + this.center;
   toString = toString + "\nScreen: " + this.screen;
   toString = toString + "\nZoom: " + this.zoomLevel;
   return toString;
}

// method to convert a position in screen coordinates
// to map coordinates. this method will accept either a GeoPoint or a 
// GeoRectangle and do the correct thing.
// @param GeoPoint or GeoRectangle in screen coordinates
// @return GeoPoint or GeoRectangle in map coordinates
function MapFromScreenCoordinates( screen ) {
    if ( screen.top ) {
	// must be a GeoRectangle
	var neCorner = this.fromScreenCoordinates( new GeoPoint( screen.right, screen.top ) );
	var swCorner = this.fromScreenCoordinates( new GeoPoint( screen.left, screen.bottom ) );
	return new GeoRectangle( neCorner.y, swCorner.x, swCorner.y, neCorner.x );
    } else {
	var mapX = this.extent.left + 
	    ( ( ( this.extent.right - this.extent.left ) / 
		( this.screen.right - this.screen.left ) ) * 
	      ( screen.x - this.screen.left ) );
	var mapY = this.extent.top - 
	    ( ( ( this.extent.top - this.extent.bottom ) / 
		( this.screen.bottom - this.screen.top ) ) * 
	      ( screen.y - this.screen.top ) );
	return new GeoPoint( mapX, mapY );
    }
}

// method to convert a position in map coordinates
// to screen coordinates. this method will accept either a GeoPoint or a 
// GeoRectangle and do the correct thing.
// @param GeoPoint or GeoRectangle in map coordinates
// @return GeoPoint or GeoRectangle in screen coordinates
function MapToScreenCoordinates( extent ) {
  if ( extent.top ) {
   // must be a GeoRectangle
   var neCorner = this.toScreenCoordinates( 
     new GeoPoint( extent.right, extent.top ) );
   var swCorner = this.toScreenCoordinates( 
     new GeoPoint( extent.left, extent.bottom ) );
   return new GeoRectangle( neCorner.y, swCorner.x,
                            swCorner.y, neCorner.x );
  } else {
   var mapX = this.screen.left + 
    ( ( ( this.screen.right - this.screen.left ) / 
        ( this.extent.right - this.extent.left ) ) * 
      ( extent.x - this.extent.left ) );
   var mapY = this.screen.top - 
    ( ( ( this.screen.top - this.screen.bottom ) / 
        ( this.extent.bottom - this.extent.top ) ) * 
      ( extent.y - this.extent.top ) );
   return new GeoPoint( mapX, mapY );
  }
}

// PositionChangeListener impl method
// Update the bounds.
// @param PositionChangeEvent or a GeoPoint object
//  representing the screen for backwards compatibility
function MapPositionChange( positionevent ) {
    // save this for later, we can use it to initialize the bounds
    this.positionEvent = positionevent;
  var position = 
   positionevent.screen ? positionevent.screen : positionevent;

  if ( this.bounds != null &&
       this.screen != null ) {
    // here we must assure that the position
    // is on the map, or we don't update.
    if ( this.screen.isInside( position,
                               true ) ) {
      this.bounds.union( position );
      if ( this.bounds.html != null ) {
        displayBounds( this.bounds,
                       this.bounds.html );
      }
    }
  }
}

// The maps array
var MAPS = new Object( );

// method to add a map
// @param map object, or an id for a map object
// @param GeoRectangle representing the map in map coords
// @param GeoPoint representing the map in map coords
// @param GeoRectangle representing the map in screen coords
// @param int zoomLevel
function addMap( map,
                 mapExtent,
                 mapCenter,
                 mapScreen,
                 mapZoomLevel ) {
    if ( map.id ) { 
	MAPS[ map.id ] = map;
    } else {
	MAPS[ map ] = new Map( map,
			       mapExtent,
			       mapCenter,
			       mapScreen,
			       mapZoomLevel );
    }
}

// method to update a map
// @param identifier for this map
// @param GeoRectangle representing the map in map coords
// @param GeoPoint representing the map in map coords
// @param GeoRectangle representing the map in screen coords
// @param int zoomLevel
function updateMap( mapID,
		    mapExtent,
		    mapCenter,
		    mapScreen,
		    mapZoomLevel ) {
    var map = retrieveMap( mapID );
    map.extent = mapExtent;
    map.center = mapCenter;
    map.screen = mapScreen;
    map.zoomLevel = mapZoomLevel;
}

// method to save a map, adds or updates as appropriate
// @param identifier for this map
// @param GeoRectangle representing the map in map coords
// @param GeoPoint representing the map in map coords
// @param GeoRectangle representing the map in screen coords
// @param int zoomLevel
function saveMap( mapID,
		  mapExtent,
		  mapCenter,
		  mapScreen,
		  mapZoomLevel ) {
    if ( retrieveMap( mapID ) ) {
	updateMap( mapID, mapExtent, mapCenter, mapScreen, mapZoomLevel );
    } else {
	addMap( mapID, mapExtent, mapCenter, mapScreen, mapZoomLevel );
    }
}

// method to retrieve a map
// @param identifier for this map
// @return Map object or null
function retrieveMap( mapID ) {
  return MAPS[ mapID ];
}

// method to display a map
// @param identifier for this map
// @return string representing the map or empty
function displayMap( id ) {
   var toString = "";
   var map = retrieveMap( id );
   if ( map != null )
     toString = map;
   return toString;
}

// method to convert the screen coordinates 
// to map units. this method will accept either a GeoPoint or a 
// GeoRectangle and do the correct thing.
// @param id of the map
// @param GeoPoint or GeoRectangle in screen coordinates
// @return GeoPoint or GeoRectangle representing the map coordinates
function MapCoordinatesfromScreenCoordinates( id, position ) {
   var map = retrieveMap( id );
   if ( map != null )
     return map.fromScreenCoordinates( position );
   else
     return new GeoPoint( null, null );
}

// method to initialize a point, in screen coordinates
// this method will initialize a point based on the event,
// as taken from the page origin.
// @param event
// @return a GeoPoint object ( in screen coordinates )
function ScreenCoordinatesOnPageFromEvent( e ) {
  var target = e.target || e.srcElement ;
  var xPosition = e.pageX || e.clientX;
  var yPosition = e.pageY || e.clientY;
  var point = new GeoPoint( xPosition, yPosition );
  point.target = target;
  return point;
}

// method to initialize a point, in screen coordinates
// this method will initialize a point based on the event,
// as taken from the element origin.
// this method accepts an optional origin, to use rather
// than the one found from the event.  this allows us to 
// find the position of an event on a reference element.
// @param event
// @param origin ( optional )
// @return a GeoPoint object ( in screen coordinates )
function ScreenCoordinatesOnElementFromEvent( e, origin ) {
  var point = null;

  if ( origin ) {
    var point = ScreenCoordinatesOnPageFromEvent( e );
    point.diff( origin );
  } else {
    var target = e.target || e.srcElement ;
    var xPosition = e.layerX || e.offsetX ;
    var yPosition = e.layerY || e.offsetY ;
    point = new GeoPoint( xPosition, yPosition );
    point.target = target;
  }

  return point;
}

// method to initialize a point, in screen coordinates
// this method will initialize a point based on the event,
// as the element origin.
// @param event
// @return a GeoPoint object ( in screen coordinates )
function ScreenCoordinatesElementOriginFromEvent( e ) {
  var page = ScreenCoordinatesOnPageFromEvent( e );
  var element = ScreenCoordinatesOnElementFromEvent( e );
  page.diff( element );
  return page;
}

// method to initialize the bounds
// this method will initialize the bounds based on a point
// object in screen coordinates.
// initially, the top and bottom will be the same, as will
// the left and right extents.
// @param map id
// @param a GeoPoint object ( in screen coordinates ), if null, the previously
//   held position event will be used.  relies on being a PositionChangeListener
// @param an optional html element that represents this bounds
function initializeBounds( id,
                           position,
                           htmlElement ) {
  var map = retrieveMap( id );
  if ( map != null ) {
      position = position == null ? map.positionEvent.screen : position;
    var box =  new GeoRectangle( position.y, position.x, position.y, position.x );
    box.start = position;
    if ( htmlElement )
      box.html = htmlElement;
    map.bounds = box;
  }
}

// method to update a bounds
// this method will update the correct extent(s)
// @param map id
// @param a GeoPoint object ( in screen coordinates )
function updateBounds( id,
	               position ) {
  var map = retrieveMap( id );
  if ( map != null ) {
    map.positionChange( position );
  }
}

// method to finalize the bounds
// this method mostly assures the bounding box stops resizing
// on the screen, until another initialize is seen, i.e. it
// nulls out the html attibute.
// @param map id
function finalizeBounds( id ) {
  var map = retrieveMap( id );
  if ( map != null &&
      map.bounds != null ) {
      if ( map.bounds.html != null ) {
	  hideBounds( map.bounds.html );
	  map.bounds.html = null;
      }
      map.bounds = null;
  }
}

// method to display the bounds
// this method will update a css-p
// element based on the bounds
// @param GeoRectangle representing the bounds
//  in screen coordinates
// @param html
function displayBounds( box,
                        html ) {
  html.style.visibility = "visible";
  html.style.left = box.left + "px";
  html.style.top = box.top + "px";
  html.style.width = ( box.right - box.left ) + "px";
  html.style.height = ( box.bottom - box.top ) + "px";
}

// method to hide the bounds
// this method will update a css-p
// @param html
function hideBounds( html ) {
  html.style.visibility = "hidden";
}

// method to determine if the position
// is in the rectangle
// @param a GeoRectangle object
// @param a GeoPoint object
// @param true if screen coordinates, false otherwise
// @return true if position inside the box
function isPointInRectangle( box,
                             position,
                             screen ) {
  return box.isInside( position, screen );
}

// Constructor for a Location Fix
// @param fix found boolean
// @param String fix scheme
// @param String fix provider
// @param double fix map position x
// @param double fix map position y
// @param double fix latitude
// @param double fix longitude
function LocationFix( fixID,
                      fixFound, fixScheme, fixProvider, 
                      fixPositionX, fixPositionY, 
                      fixLatitude, fixLongitude,
                      locPid,
                      locAddress,
                      locBedBath,
                      locPrice,
		      locPtyp,
		      locUrl,
		      locFull,
		      locThumb,
		      locAttributes,
		      locMls, locCompany ) {
  // set attributes

  // fix attributes
  this.id = fixID;
  this.found = fixFound;
  this.scheme = fixScheme;
  this.provider = fixProvider;
  this.position = new GeoPoint( fixPositionX, fixPositionY );
  this.earth = new GeoPoint( fixLongitude, fixLatitude );

  // property attributes
  this.pid = locPid;
  this.address = locAddress;
  this.bedbath = locBedBath;
  this.price = locPrice;
  this.ptyp = locPtyp;
  this.url = locUrl;
  this.full = locFull;
  this.thumb = locThumb;
  this.attributes = locAttributes;
  this.mls = locMls;
  this.company = locCompany;

  // location information
  this.address = locAddress;
  this.city = null;
  this.state = null;
  this.zip = null;
  this.county = null;
  this.house = null;
  this.street = null;
  this.cross = null;
  this.parcel = null;
  this.country = "USA";
 
  // build the bounding box for PositionChangeSupport
  this.bounds = null;
  this.screen = null;

  // set methods
  this.toString = LocationFixToString;
  this.isNearby = LocationFixIsNearby;
  this.initializeBounds = LocationFixInitializeBounds;
  this.credit = LocationFixCredit;
  this.parseAddressAsStreet = LocationFixParseAddressAsStreet;
  this.parseAddressAsStreetHouse = LocationFixParseAddressAsStreetHouse;
  this.zip5digit = LocationFixZip5Digit;
}

// method to initialize the bounds for the location fix.
// this call, sets up the screen "fuzziness" around the fix, and
// allows us to find fixes that are close.  without such
// "fuzziness", one would have to precisely find a location.
// @param map in earth coords, used for conversion to screen coords
// @param GeoRectangle delta, to build bounding box for
//  help with PositionChangeSupport.  creates a "fuzziness" around
//  the location fixes, defaults to 5 pixels offset.  
// These are in screen coords, thus y axis is backwards.
function LocationFixInitializeBounds( earth, delta ) {
  var offset = delta ? delta : LOCATION_FIX_DELTA_DEFAULT;
  var scrn = earth.toScreenCoordinates( this.earth );

  this.screen = new GeoPoint( Math.round( scrn.x ), Math.round( scrn.y ) );
  this.bounds = new GeoRectangle( scrn.y + offset.top, 
                                  scrn.x + offset.left,
                                  scrn.y + offset.bottom,
                                  scrn.x + offset.right );
}

// to string method for LocationFix
// @return string representation
function LocationFixToString( ) {
   var toString = "";
   toString = toString + "Location: " + this.id;
   toString = toString + "\nFound: " + this.found;
   toString = toString + "\nScheme: " + this.scheme;
   toString = toString + "\nProvider: " + this.provider;
   toString = toString + "\nProjection: " + this.position;
   toString = toString + "\nEarth: " + this.earth;
   toString = toString + "\nScreen: " + this.screen;
   toString = toString + "\nPid: " + this.pid;
   toString = toString + "\nAddress: " + this.address;
   toString = toString + "\nCity: " + this.city;
   toString = toString + "\nState: " + this.state;
   toString = toString + "\nZip: " + this.zip;
   toString = toString + "\nBed/Bath: " + this.bedbath;
   toString = toString + "\nPrice: " + this.price;
   toString = toString + "\nPtyp: " + this.ptyp;
   return toString;
}

// is the passed in position near to this location fix
// @param PositionChangeEvent
// @return true if close by, utilizes bounding box for "fuzziness"
function LocationFixIsNearby( position ) {
  var rval = false;

  if ( this.found && 
       this.bounds.isInside( position.screen, true ) ) {
    rval = true;
  }

  return rval;
}

// get the location fix credit, computed property
// @return credit or empty string if there is nothing to compute
function LocationFixCredit( ) {		
    return ( this.mls != null && this.company != null ) ? this.mls.displayMLSCredit( this.company ) : "";
}

/**
 * parse the address into a street and possibly cross
 */
function LocationFixParseAddressAsStreet( ) {
    var streets = this.address.split( " & " );
    if ( streets.length == 2 ) {
	this.street = streets[0];
	this.cross = streets[1];
    } else {	
	this.street = this.address;
    }
}

/**
 * parse the address into a street and house number
 */
function LocationFixParseAddressAsStreetHouse( ) {
    var tokens = this.address.split( " " );
    if ( tokens.length >= 2 ) {
	this.house = tokens.shift( );
	this.street = tokens.join( " " );
    }
}

/**
 * fetch the first five digits of a zip code
 * @return 5 digit zip
 */
function LocationFixZip5Digit( ) {
    return this.zip != null ? this.zip.split("-")[0] : null;
}

// The location fixes array
var LOCATION_FIXES = new Object( );
var LOCATION_FIX_DELTA_DEFAULT = new GeoRectangle( -5, -5, 5, 5 );
var LOCATION_FIX_SCHEME = {ADDRESS:1,STREET_INTERSECTION:2,STREET:3,STREET_HOUSE:4,
			   STREET_LOT:5,STATE:6,ZIP:7,CITY:8,
			   COUNTY:9,LATITUDE_LONGITUDE:10,X_Y:11,NULL:12,
			   MANUAL:13,ROOFTOP:14,PARCEL:15,COUNTRY:16};
var LOCATION_FIX_PROVIDER = {RMIMS:1,MWS:2,MLS:3,WRE:4,VEWS:5,GMAP:6,MSVE:7};

// method to cleaer the location fixes.  
// currently clears all, but could be made to accept an id
function clearLocationFix( ) {
    LOCATION_FIXES = new Object( );
}

// method to add a LocationFix
function addLocationFix( id,
                         fixFound, fixScheme, fixProvider,
                         fixPositionX, fixPositionY, 
                         fixLatitude, fixLongitude,
                         locPid, locAddress, locBedBath, locPrice, 
			 locPtyp, locUrl, locFull, locThumb, locAttributes, locMls, locCompany ) {
   LOCATION_FIXES[ id ] = new LocationFix( id,
                                           fixFound, fixScheme, fixProvider, 
                                           fixPositionX, fixPositionY, 
                                           fixLatitude, fixLongitude,
                                           locPid, locAddress, locBedBath, 
                                           locPrice, locPtyp, locUrl, locFull, locThumb, locAttributes, locMls, locCompany );
}

// retrieve the location fix
// @param id, if id exists, return the one fix
//  otherwise return the entire fix collection
function retrieveLocationFix( id ) {
    if ( id ) {
	return LOCATION_FIXES[ id ];
    } else {
	return LOCATION_FIXES;
    }
}

// retrieve the location fix by property id
// @param property id
// @return first found fix, or null if not found
function retrieveLocationFixByPID( id ) {
    var fix = null;
    for ( prop in LOCATION_FIXES ) {
	if ( LOCATION_FIXES[ prop ].pid == id ) {
	    fix = LOCATION_FIXES[ prop ];
	    break;
	}
    }
    return fix;
}

// count the number of location fixes
// @return count
function countLocationFix( ) {
    var count = 0;
    for ( prop in LOCATION_FIXES ) { count++; }
    return count;
}

// method to display a location fix
function displayLocationFix( id ) {
   var toString = "";
   var fix = retrieveLocationFix( id );
   if ( fix != null )
     toString = fix;
   return toString;
}

// method to get nearby location fix ids.
// @param a PositionChangeEvent object
// @return an array of location fix ids
//  the array will be zero length if none are found
function getNearbyLocationFixIDs( position ) {
   var matches = new Array( );   
     
   for ( prop in LOCATION_FIXES ) {
     var loc = LOCATION_FIXES[ prop ];
     if ( loc.isNearby( position ) ) {
        matches[ matches.length ] = loc.id;
     }
   }

   return matches;
}

// method to initialize all the location fix bounds.
// @param map in earth coords, used for conversion to screen coords
// @param GeoRectangle delta, to build bounding box for
//  help with PositionChangeSupport.  creates a "fuzziness" around
//  the location fixes, defaults to 5 pixels offset.  
// These are in screen coords, thus y axis is backwards.
function initializeLocationFixBounds( earth, delta ) {
   var matches = new Array( );   
     
   for ( prop in LOCATION_FIXES ) {
     var loc = LOCATION_FIXES[ prop ];
     loc.initializeBounds( earth, delta );
   }

   return matches;
}

// method to get a region that would properly encapsulate
// the location fixes.  this method will walk the known fixes
// and attempt to build the proper GeoRectangle.
// this method currently returns a map in earth coordinates (fix.earth).
// we could choose to make this conditional on an argument to 
// return a map in projected coordinates (fix.position) 
// or screen coordinates (fix.screen).
// if we find that we need to provide more view information, we
// should instead build a map ( getLocationFixMap ) making use of this method.
// currently, this method is basically building a GeoRectangle based upon
// an "array" of GeoPoints, the core of which might become a GeoRectangle method,
//  GeoPointsToGeoRectangle
// @return region, GeoRectangle:  
//   for 0 fixes, region is null
//   for 1  fix, region is a point
function getLocationFixRegion( ) {
    var region = null;

    for ( prop in LOCATION_FIXES ) {
	var fix = LOCATION_FIXES[ prop ];
        if ( fix.earth ) {
 	    if ( region == null ) {
		region = new GeoRectangle( fix.earth.y, fix.earth.x, fix.earth.y, fix.earth.x );
	    } else {
		region.grow( fix.earth );
	    }
	}
    }

    return region;
}
