Ignore:
Timestamp:
Oct 12, 2011, 1:13:15 PM (11 years ago)
Author:
djay
Message:

Fix tickets #63, #64 and #68

Location:
trunk/zoo-project/zoo-api/js
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/zoo-project/zoo-api/js/ZOO-api.js

    r278 r342  
    331331    return ZOO.String.isNumeric(value) ? parseFloat(value) : value;
    332332  }
     333};
     334
     335/**
     336 * Class: ZOO.Class
     337 * Object for creating CLASS
     338 */
     339ZOO.Class = function() {
     340  var len = arguments.length;
     341  var P = arguments[0];
     342  var F = arguments[len-1];
     343  var C = typeof F.initialize == "function" ?
     344    F.initialize :
     345    function(){ P.prototype.initialize.apply(this, arguments); };
     346
     347  if (len > 1) {
     348    var newArgs = [C, P].concat(
     349          Array.prototype.slice.call(arguments).slice(1, len-1), F);
     350    ZOO.inherit.apply(null, newArgs);
     351  } else {
     352    C.prototype = F;
     353  }
     354  return C;
     355};
     356/**
     357 * Function: create
     358 * Function for creating CLASS
     359 */
     360ZOO.Class.create = function() {
     361  return function() {
     362    if (arguments && arguments[0] != ZOO.Class.isPrototype) {
     363      this.initialize.apply(this, arguments);
     364    }
     365  };
     366};
     367/**
     368 * Function: inherit
     369 * Function for inheriting CLASS
     370 */
     371ZOO.Class.inherit = function (P) {
     372  var C = function() {
     373   P.call(this);
     374  };
     375  var newArgs = [C].concat(Array.prototype.slice.call(arguments));
     376  ZOO.inherit.apply(null, newArgs);
     377  return C.prototype;
     378};
     379/**
     380 * Function: inherit
     381 * Function for inheriting CLASS
     382 */
     383ZOO.inherit = function(C, P) {
     384  var F = function() {};
     385  F.prototype = P.prototype;
     386  C.prototype = new F;
     387  var i, l, o;
     388  for(i=2, l=arguments.length; i<l; i++) {
     389    o = arguments[i];
     390    if(typeof o === "function") {
     391      o = o.prototype;
     392    }
     393    ZOO.Util.extend(C.prototype, o);
     394  }
     395};
     396/**
     397 * Class: ZOO.Util
     398 * Object for utilities
     399 */
     400ZOO.Util = ZOO.Util || {};
     401/**
     402 * Function: extend
     403 * Function for extending object
     404 */
     405ZOO.Util.extend = function(destination, source) {
     406  destination = destination || {};
     407  if (source) {
     408    for (var property in source) {
     409      var value = source[property];
     410      if (value !== undefined) {
     411        destination[property] = value;
     412      }
     413    }
     414  }
     415  return destination;
    333416};
    334417
     
    61246207     */
    61256208    'complex': function(identifier,data) {
    6126       var input = new XML('<wps:Input xmlns:wps="'+this.namespaces['wps']+'"><ows:Identifier xmlns:ows="'+this.namespaces['ows']+'">'+identifier+'</ows:Identifier><wps:Data><wps:ComplexData><![CDATA['+data.value+']]></wps:ComplexData></wps:Data></wps:Input>');
    6127       input.*::Data.*::ComplexData.@mimeType = data.mimetype ? data.mimetype : 'application/json';
     6209      var input = new XML('<wps:Input xmlns:wps="'+this.namespaces['wps']+'"><ows:Identifier xmlns:ows="'+this.namespaces['ows']+'">'+identifier+'</ows:Identifier>'+(data.value?'<wps:Data><wps:ComplexData><![CDATA['+data.value+']]></wps:ComplexData></wps:Data>':(data.xlink?'<wps:Reference xmlns:xlink="'+this.namespaces['xlink']+'" xlink:href="'+data.xlink+'" mimeType="'+data.mimeType+'" />':''))+'</wps:Input>');
     6210      if(data.xlink)
     6211        input.*::Reference.@mimeType = data.mimetype ? data.mimetype : 'application/json';
     6212      else
     6213        input.*::Data.*::ComplexData.@mimeType = data.mimetype ? data.mimetype : 'application/json';
    61286214      if (data.encoding)
    61296215        input.*::Data.*::ComplexData.@encoding = data.encoding;
  • trunk/zoo-project/zoo-api/js/ZOO-proj4js.js

    r144 r342  
    2323 */
    2424
    25 /**
    26  * Author:       Mike Adair madairATdmsolutions.ca
    27  *               Richard Greenwood rich@greenwoodmap.com
    28  * License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html
    29  * $Id: Proj.js 2956 2007-07-09 12:17:52Z steven $
    30  */
     25/*
     26  proj4js.js -- Javascript reprojection library.
     27 
     28  Authors:      Mike Adair madairATdmsolutions.ca
     29                Richard Greenwood richATgreenwoodmap.com
     30                Didier Richard didier.richardATign.fr
     31                Stephen Irons
     32  License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html
     33                Note: This program is an almost direct port of the C library
     34                Proj4.
     35*/
    3136
    32 /**
    33  * Class: ZOO
    34  */
    35 ZOO = {
    36   /**
    37    * Constant: SERVICE_ACCEPTED
    38    * {Integer} used for
    39    */
    40   SERVICE_ACCEPTED: 0,
    41   /**
    42    * Constant: SERVICE_STARTED
    43    * {Integer} used for
    44    */
    45   SERVICE_STARTED: 1,
    46   /**
    47    * Constant: SERVICE_PAUSED
    48    * {Integer} used for
    49    */
    50   SERVICE_PAUSED: 2,
    51   /**
    52    * Constant: SERVICE_SUCCEEDED
    53    * {Integer} used for
    54    */
    55   SERVICE_SUCCEEDED: 3,
    56   /**
    57    * Constant: SERVICE_FAILED
    58    * {Integer} used for
    59    */
    60   SERVICE_FAILED: 4,
    61   /**
    62    * Function: removeItem
    63    * Remove an object from an array. Iterates through the array
    64    *     to find the item, then removes it.
    65    *
    66    * Parameters:
    67    * array - {Array}
    68    * item - {Object}
    69    *
    70    * Return
    71    * {Array} A reference to the array
    72    */
    73   removeItem: function(array, item) {
    74     for(var i = array.length - 1; i >= 0; i--) {
    75         if(array[i] == item) {
    76             array.splice(i,1);
    77         }
    78     }
    79     return array;
     37Proj4js={defaultDatum:'WGS84',transform:function(source,dest,point){if(!source.readyToUse||!dest.readyToUse){this.reportError("Proj4js initialization for "+source.srsCode+" not yet complete");return point;}
     38if((source.srsProjNumber=="900913"&&dest.datumCode!="WGS84")||(dest.srsProjNumber=="900913"&&source.datumCode!="WGS84")){var wgs84=Proj4js.WGS84;this.transform(source,wgs84,point);source=wgs84;}
     39if(source.projName=="longlat"){point.x*=Proj4js.common.D2R;point.y*=Proj4js.common.D2R;}else{if(source.to_meter){point.x*=source.to_meter;point.y*=source.to_meter;}
     40source.inverse(point);}
     41if(source.from_greenwich){point.x+=source.from_greenwich;}
     42point=this.datum_transform(source.datum,dest.datum,point);if(dest.from_greenwich){point.x-=dest.from_greenwich;}
     43if(dest.projName=="longlat"){point.x*=Proj4js.common.R2D;point.y*=Proj4js.common.R2D;}else{dest.forward(point);if(dest.to_meter){point.x/=dest.to_meter;point.y/=dest.to_meter;}}
     44return point;},datum_transform:function(source,dest,point){if(source.compare_datums(dest)){return point;}
     45if(source.datum_type==Proj4js.common.PJD_NODATUM||dest.datum_type==Proj4js.common.PJD_NODATUM){return point;}
     46if(source.datum_type==Proj4js.common.PJD_GRIDSHIFT)
     47{alert("ERROR: Grid shift transformations are not implemented yet.");}
     48if(dest.datum_type==Proj4js.common.PJD_GRIDSHIFT)
     49{alert("ERROR: Grid shift transformations are not implemented yet.");}
     50if(source.es!=dest.es||source.a!=dest.a||source.datum_type==Proj4js.common.PJD_3PARAM||source.datum_type==Proj4js.common.PJD_7PARAM||dest.datum_type==Proj4js.common.PJD_3PARAM||dest.datum_type==Proj4js.common.PJD_7PARAM)
     51{source.geodetic_to_geocentric(point);if(source.datum_type==Proj4js.common.PJD_3PARAM||source.datum_type==Proj4js.common.PJD_7PARAM){source.geocentric_to_wgs84(point);}
     52if(dest.datum_type==Proj4js.common.PJD_3PARAM||dest.datum_type==Proj4js.common.PJD_7PARAM){dest.geocentric_from_wgs84(point);}
     53dest.geocentric_to_geodetic(point);}
     54if(dest.datum_type==Proj4js.common.PJD_GRIDSHIFT)
     55{alert("ERROR: Grid shift transformations are not implemented yet.");}
     56return point;},reportError:function(msg){},extend:function(destination,source){destination=destination||{};if(source){for(var property in source){var value=source[property];if(value!==undefined){destination[property]=value;}}}
     57return destination;},Class:function(){var Class=function(){this.initialize.apply(this,arguments);};var extended={};var parent;for(var i=0;i<arguments.length;++i){if(typeof arguments[i]=="function"){parent=arguments[i].prototype;}else{parent=arguments[i];}
     58Proj4js.extend(extended,parent);}
     59Class.prototype=extended;return Class;},bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},scriptName:"proj4js-compressed.js",defsLookupService:'http://spatialreference.org/ref',libPath:null,getScriptLocation:function(){if(this.libPath)return this.libPath;var scriptName=this.scriptName;var scriptNameLen=scriptName.length;var scripts=document.getElementsByTagName('script');for(var i=0;i<scripts.length;i++){var src=scripts[i].getAttribute('src');if(src){var index=src.lastIndexOf(scriptName);if((index>-1)&&(index+scriptNameLen==src.length)){this.libPath=src.slice(0,-scriptNameLen);break;}}}
     60return this.libPath||"";},
     61  loadScript:function(url,onload,onfail,loadCheck){
     62    var script = [];
     63  var script = ZOORequest('GET',url);
     64  try {
     65    eval(script);
     66    onload();
     67  } catch (e) {
     68    onfail();
     69  }
     70    /*
     71    var script=document.createElement('script');
     72    script.defer=false;
     73    script.type="text/javascript";
     74    script.id=url;
     75    script.src=url;
     76    script.onload=onload;
     77    script.onerror=onfail;
     78    script.loadCheck=loadCheck;
     79    if(/MSIE/.test(navigator.userAgent)){script.onreadystatechange=this.checkReadyState;}
     80    document.getElementsByTagName('head')[0].appendChild(script);
     81    */
    8082  },
    81   /**
    82    * Function: indexOf
    83    *
    84    * Parameters:
    85    * array - {Array}
    86    * obj - {Object}
    87    *
    88    * Returns:
    89    * {Integer} The index at, which the first object was found in the array.
    90    *           If not found, returns -1.
    91    */
    92   indexOf: function(array, obj) {
    93     for(var i=0, len=array.length; i<len; i++) {
    94       if (array[i] == obj)
    95         return i;
    96     }
    97     return -1;   
     83  checkReadyState:function(){if(this.readyState=='loaded'){if(!this.loadCheck()){this.onerror();}else{this.onload();}}}};Proj4js.Proj=Proj4js.Class({readyToUse:false,title:null,projName:null,units:null,datum:null,x0:0,y0:0,initialize:function(srsCode){this.srsCodeInput=srsCode;if(srsCode.indexOf('urn:')==0){var urn=srsCode.split(':');if((urn[1]=='ogc'||urn[1]=='x-ogc')&&(urn[2]=='def')&&(urn[3]=='crs')){srsCode=urn[4]+':'+urn[urn.length-1];}}else if(srsCode.indexOf('http://')==0){var url=srsCode.split('#');if(url[0].match(/epsg.org/)){srsCode='EPSG:'+url[1];}else if(url[0].match(/RIG.xml/)){srsCode='IGNF:'+url[1];}}
     84this.srsCode=srsCode.toUpperCase();if(this.srsCode.indexOf("EPSG")==0){this.srsCode=this.srsCode;this.srsAuth='epsg';this.srsProjNumber=this.srsCode.substring(5);}else if(this.srsCode.indexOf("IGNF")==0){this.srsCode=this.srsCode;this.srsAuth='IGNF';this.srsProjNumber=this.srsCode.substring(5);}else if(this.srsCode.indexOf("CRS")==0){this.srsCode=this.srsCode;this.srsAuth='CRS';this.srsProjNumber=this.srsCode.substring(4);}else{this.srsAuth='';this.srsProjNumber=this.srsCode;}
     85this.loadProjDefinition();},
     86  loadProjDefinition:function(){
     87    if(Proj4js.defs[this.srsCode]){this.defsLoaded();return;}
     88    this.loadFromService();
     89  /*
     90    var url=Proj4js.getScriptLocation()+'defs/'+this.srsAuth.toUpperCase()+this.srsProjNumber+'.js';
     91    Proj4js.loadScript(url,Proj4js.bind(this.defsLoaded,this),Proj4js.bind(this.loadFromService,this),Proj4js.bind(this.checkDefsLoaded,this));
     92    */
    9893  },
    99   /**
    100    * Function: extend
    101    * Copy all properties of a source object to a destination object. Modifies
    102    *     the passed in destination object.  Any properties on the source object
    103    *     that are set to undefined will not be (re)set on the destination object.
    104    *
    105    * Parameters:
    106    * destination - {Object} The object that will be modified
    107    * source - {Object} The object with properties to be set on the destination
    108    *
    109    * Returns:
    110    * {Object} The destination object.
    111    */
    112   extend: function(destination, source) {
    113     destination = destination || {};
    114     if(source) {
    115       for(var property in source) {
    116         var value = source[property];
    117         if(value !== undefined)
    118           destination[property] = value;
    119       }
    120     }
    121     return destination;
     94  loadFromService:function(){var url=Proj4js.defsLookupService+'/'+this.srsAuth+'/'+this.srsProjNumber+'/proj4js/';Proj4js.loadScript(url,Proj4js.bind(this.defsLoaded,this),Proj4js.bind(this.defsFailed,this),Proj4js.bind(this.checkDefsLoaded,this));},defsLoaded:function(){this.parseDefs();this.loadProjCode(this.projName);},checkDefsLoaded:function(){if(Proj4js.defs[this.srsCode]){return true;}else{return false;}},defsFailed:function(){Proj4js.reportError('failed to load projection definition for: '+this.srsCode);Proj4js.defs[this.srsCode]=Proj4js.defs['WGS84'];this.defsLoaded();},
     95  loadProjCode:function(projName){
     96    if(Proj4js.Proj[projName]){this.initTransforms();return;}
     97    var url=Proj4js.getScriptLocation()+'projCode/'+projName+'.js';
     98    Proj4js.loadScript(url,Proj4js.bind(this.loadProjCodeSuccess,this,projName),Proj4js.bind(this.loadProjCodeFailure,this,projName),Proj4js.bind(this.checkCodeLoaded,this,projName));
    12299  },
    123   /**
    124    * Function: rad
    125    *
    126    * Parameters:
    127    * x - {Float}
    128    *
    129    * Returns:
    130    * {Float}
    131    */
    132   rad: function(x) {return x*Math.PI/180;},
    133   /**
    134    * Function: distVincenty
    135    * Given two objects representing points with geographic coordinates, this
    136    *     calculates the distance between those points on the surface of an
    137    *     ellipsoid.
    138    *
    139    * Parameters:
    140    * p1 - {<ZOO.Geometry.Point>} (or any object with both .x, .y properties)
    141    * p2 - {<ZOO.Geometry.Point>} (or any object with both .x, .y properties)
    142    *
    143    * Returns:
    144    * {Float} The distance (in km) between the two input points as measured on an
    145    *     ellipsoid.  Note that the input point objects must be in geographic
    146    *     coordinates (decimal degrees) and the return distance is in kilometers.
    147    */
    148   distVincenty: function(p1, p2) {
    149     var a = 6378137, b = 6356752.3142,  f = 1/298.257223563;
    150     var L = ZOO.rad(p2.x - p1.y);
    151     var U1 = Math.atan((1-f) * Math.tan(ZOO.rad(p1.y)));
    152     var U2 = Math.atan((1-f) * Math.tan(ZOO.rad(p2.y)));
    153     var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
    154     var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
    155     var lambda = L, lambdaP = 2*Math.PI;
    156     var iterLimit = 20;
    157     while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
    158         var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
    159         var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
    160         (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
    161         if (sinSigma==0) {
    162             return 0;  // co-incident points
    163         }
    164         var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
    165         var sigma = Math.atan2(sinSigma, cosSigma);
    166         var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
    167         var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
    168         var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
    169         var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
    170         lambdaP = lambda;
    171         lambda = L + (1-C) * f * Math.sin(alpha) *
    172         (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
    173     }
    174     if (iterLimit==0) {
    175         return NaN;  // formula failed to converge
    176     }
    177     var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
    178     var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
    179     var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
    180     var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
    181         B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
    182     var s = b*A*(sigma-deltaSigma);
    183     var d = s.toFixed(3)/1000; // round to 1mm precision
    184     return d;
    185   },
    186   /**
    187    * Function: Class
    188    * Method used to create ZOO classes. Includes support for
    189    *     multiple inheritance.
    190    */
    191   Class: function() {
    192     var Class = function() {
    193       this.initialize.apply(this, arguments);
    194     };
    195     var extended = {};
    196     var parent;
    197     for(var i=0; i<arguments.length; ++i) {
    198       if(typeof arguments[i] == "function") {
    199         // get the prototype of the superclass
    200         parent = arguments[i].prototype;
    201       } else {
    202         // in this case we're extending with the prototype
    203         parent = arguments[i];
    204       }
    205       ZOO.extend(extended, parent);
    206     }
    207     Class.prototype = extended;
    208 
    209     return Class;
    210   },
    211   /**
    212    * Function: UpdateStatus
    213    * Method used to update the status of the process
    214    *
    215    * Parameters:
    216    * env - {Object} The environment object
    217    * value - {Float} the status value between 0 to 100
    218    */
    219   UpdateStatus: function(env,value) {
    220     return ZOOUpdateStatus(env,value);
    221   }
    222 };
    223 
    224 /**
    225  * Class: ZOO.String
    226  * Contains convenience methods for string manipulation
    227  */
    228 ZOO.String = {
    229   /**
    230    * Function: startsWith
    231    * Test whether a string starts with another string.
    232    *
    233    * Parameters:
    234    * str - {String} The string to test.
    235    * sub - {Sring} The substring to look for.
    236    * 
    237    * Returns:
    238    * {Boolean} The first string starts with the second.
    239    */
    240   startsWith: function(str, sub) {
    241     return (str.indexOf(sub) == 0);
    242   },
    243   /**
    244    * Function: contains
    245    * Test whether a string contains another string.
    246    *
    247    * Parameters:
    248    * str - {String} The string to test.
    249    * sub - {String} The substring to look for.
    250    *
    251    * Returns:
    252    * {Boolean} The first string contains the second.
    253    */
    254   contains: function(str, sub) {
    255     return (str.indexOf(sub) != -1);
    256   },
    257   /**
    258    * Function: trim
    259    * Removes leading and trailing whitespace characters from a string.
    260    *
    261    * Parameters:
    262    * str - {String} The (potentially) space padded string.  This string is not
    263    *     modified.
    264    *
    265    * Returns:
    266    * {String} A trimmed version of the string with all leading and
    267    *     trailing spaces removed.
    268    */
    269   trim: function(str) {
    270     return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    271   },
    272   /**
    273    * Function: camelize
    274    * Camel-case a hyphenated string.
    275    *     Ex. "chicken-head" becomes "chickenHead", and
    276    *     "-chicken-head" becomes "ChickenHead".
    277    *
    278    * Parameters:
    279    * str - {String} The string to be camelized.  The original is not modified.
    280    *
    281    * Returns:
    282    * {String} The string, camelized
    283    *
    284    */
    285   camelize: function(str) {
    286     var oStringList = str.split('-');
    287     var camelizedString = oStringList[0];
    288     for (var i=1, len=oStringList.length; i<len; i++) {
    289       var s = oStringList[i];
    290       camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    291     }
    292     return camelizedString;
    293   },
    294   /**
    295    * Property: tokenRegEx
    296    * Used to find tokens in a string.
    297    * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
    298    */
    299   tokenRegEx:  /\$\{([\w.]+?)\}/g,
    300   /**
    301    * Property: numberRegEx
    302    * Used to test strings as numbers.
    303    */
    304   numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
    305   /**
    306    * Function: isNumeric
    307    * Determine whether a string contains only a numeric value.
    308    *
    309    * Examples:
    310    * (code)
    311    * ZOO.String.isNumeric("6.02e23") // true
    312    * ZOO.String.isNumeric("12 dozen") // false
    313    * ZOO.String.isNumeric("4") // true
    314    * ZOO.String.isNumeric(" 4 ") // false
    315    * (end)
    316    *
    317    * Returns:
    318    * {Boolean} String contains only a number.
    319    */
    320   isNumeric: function(value) {
    321     return ZOO.String.numberRegEx.test(value);
    322   },
    323   /**
    324    * Function: numericIf
    325    * Converts a string that appears to be a numeric value into a number.
    326    *
    327    * Returns
    328    * {Number|String} a Number if the passed value is a number, a String
    329    *     otherwise.
    330    */
    331   numericIf: function(value) {
    332     return ZOO.String.isNumeric(value) ? parseFloat(value) : value;
    333   }
    334 };
    335 
    336 /**
    337  * Class: ZOO.Request
    338  * Contains convenience methods for working with ZOORequest which
    339  *     replace XMLHttpRequest. Because of we are not in a browser
    340  *     JavaScript environment, ZOO Project provides a method to
    341  *     query servers which is based on curl : ZOORequest.
    342  */
    343 ZOO.Request = {
    344   /**
    345    * Function: GET
    346    * Send an HTTP GET request.
    347    *
    348    * Parameters:
    349    * url - {String} The URL to request.
    350    * params - {Object} Params to add to the url
    351    *
    352    * Returns:
    353    * {String} Request result.
    354    */
    355   Get: function(url,params) {
    356     var paramsArray = [];
    357     for (var key in params) {
    358       var value = params[key];
    359       if ((value != null) && (typeof value != 'function')) {
    360         var encodedValue;
    361         if (typeof value == 'object' && value.constructor == Array) {
    362           /* value is an array; encode items and separate with "," */
    363           var encodedItemArray = [];
    364           for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
    365             encodedItemArray.push(encodeURIComponent(value[itemIndex]));
    366           }
    367           encodedValue = encodedItemArray.join(",");
    368         }
    369         else {
    370           /* value is a string; simply encode */
    371           encodedValue = encodeURIComponent(value);
    372         }
    373         paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
    374       }
    375     }
    376     var paramString = paramsArray.join("&");
    377     if(paramString.length > 0) {
    378       var separator = (url.indexOf('?') > -1) ? '&' : '?';
    379       url += separator + paramString;
    380     }
    381     return ZOORequest('GET',url);
    382   },
    383   /**
    384    * Function: POST
    385    * Send an HTTP POST request.
    386    *
    387    * Parameters:
    388    * url - {String} The URL to request.
    389    * body - {String} The request's body to send.
    390    * headers - {Object} A key-value object of headers to push to
    391    *     the request's head
    392    *
    393    * Returns:
    394    * {String} Request result.
    395    */
    396   Post: function(url,body,headers) {
    397     if(!(headers instanceof Array)) {
    398       var headersArray = [];
    399       for (var name in headers) {
    400         headersArray.push(name+': '+headers[name]);
    401       }
    402       headers = headersArray;
    403     }
    404     return ZOORequest('POST',url,body,headers);
    405   }
    406 };
    407 
    408 /**
    409  * Class: ZOO.Bounds
    410  * Instances of this class represent bounding boxes.  Data stored as left,
    411  *     bottom, right, top floats. All values are initialized to null,
    412  *     however, you should make sure you set them before using the bounds
    413  *     for anything.
    414  */
    415 ZOO.Bounds = ZOO.Class({
    416   /**
    417    * Property: left
    418    * {Number} Minimum horizontal coordinate.
    419    */
    420   left: null,
    421   /**
    422    * Property: bottom
    423    * {Number} Minimum vertical coordinate.
    424    */
    425   bottom: null,
    426   /**
    427    * Property: right
    428    * {Number} Maximum horizontal coordinate.
    429    */
    430   right: null,
    431   /**
    432    * Property: top
    433    * {Number} Maximum vertical coordinate.
    434    */
    435   top: null,
    436   /**
    437    * Constructor: ZOO.Bounds
    438    * Construct a new bounds object.
    439    *
    440    * Parameters:
    441    * left - {Number} The left bounds of the box.  Note that for width
    442    *        calculations, this is assumed to be less than the right value.
    443    * bottom - {Number} The bottom bounds of the box.  Note that for height
    444    *          calculations, this is assumed to be more than the top value.
    445    * right - {Number} The right bounds.
    446    * top - {Number} The top bounds.
    447    */
    448   initialize: function(left, bottom, right, top) {
    449     if (left != null)
    450       this.left = parseFloat(left);
    451     if (bottom != null)
    452       this.bottom = parseFloat(bottom);
    453     if (right != null)
    454       this.right = parseFloat(right);
    455     if (top != null)
    456       this.top = parseFloat(top);
    457   },
    458   /**
    459    * Method: clone
    460    * Create a cloned instance of this bounds.
    461    *
    462    * Returns:
    463    * {<ZOO.Bounds>} A fresh copy of the bounds
    464    */
    465   clone:function() {
    466     return new ZOO.Bounds(this.left, this.bottom,
    467                           this.right, this.top);
    468   },
    469   /**
    470    * Method: equals
    471    * Test a two bounds for equivalence.
    472    *
    473    * Parameters:
    474    * bounds - {<ZOO.Bounds>}
    475    *
    476    * Returns:
    477    * {Boolean} The passed-in bounds object has the same left,
    478    *           right, top, bottom components as this.  Note that if bounds
    479    *           passed in is null, returns false.
    480    */
    481   equals:function(bounds) {
    482     var equals = false;
    483     if (bounds != null)
    484         equals = ((this.left == bounds.left) &&
    485                   (this.right == bounds.right) &&
    486                   (this.top == bounds.top) &&
    487                   (this.bottom == bounds.bottom));
    488     return equals;
    489   },
    490   /**
    491    * Method: toString
    492    *
    493    * Returns:
    494    * {String} String representation of bounds object.
    495    *          (ex.<i>"left-bottom=(5,42) right-top=(10,45)"</i>)
    496    */
    497   toString:function() {
    498     return ( "left-bottom=(" + this.left + "," + this.bottom + ")"
    499               + " right-top=(" + this.right + "," + this.top + ")" );
    500   },
    501   /**
    502    * APIMethod: toArray
    503    *
    504    * Returns:
    505    * {Array} array of left, bottom, right, top
    506    */
    507   toArray: function() {
    508     return [this.left, this.bottom, this.right, this.top];
    509   },
    510   /**
    511    * Method: toBBOX
    512    *
    513    * Parameters:
    514    * decimal - {Integer} How many significant digits in the bbox coords?
    515    *                     Default is 6
    516    *
    517    * Returns:
    518    * {String} Simple String representation of bounds object.
    519    *          (ex. <i>"5,42,10,45"</i>)
    520    */
    521   toBBOX:function(decimal) {
    522     if (decimal== null)
    523       decimal = 6;
    524     var mult = Math.pow(10, decimal);
    525     var bbox = Math.round(this.left * mult) / mult + "," +
    526                Math.round(this.bottom * mult) / mult + "," +
    527                Math.round(this.right * mult) / mult + "," +
    528                Math.round(this.top * mult) / mult;
    529     return bbox;
    530   },
    531   /**
    532    * Method: toGeometry
    533    * Create a new polygon geometry based on this bounds.
    534    *
    535    * Returns:
    536    * {<ZOO.Geometry.Polygon>} A new polygon with the coordinates
    537    *     of this bounds.
    538    */
    539   toGeometry: function() {
    540     return new ZOO.Geometry.Polygon([
    541       new ZOO.Geometry.LinearRing([
    542         new ZOO.Geometry.Point(this.left, this.bottom),
    543         new ZOO.Geometry.Point(this.right, this.bottom),
    544         new ZOO.Geometry.Point(this.right, this.top),
    545         new ZOO.Geometry.Point(this.left, this.top)
    546       ])
    547     ]);
    548   },
    549   /**
    550    * Method: getWidth
    551    *
    552    * Returns:
    553    * {Float} The width of the bounds
    554    */
    555   getWidth:function() {
    556     return (this.right - this.left);
    557   },
    558   /**
    559    * Method: getHeight
    560    *
    561    * Returns:
    562    * {Float} The height of the bounds (top minus bottom).
    563    */
    564   getHeight:function() {
    565     return (this.top - this.bottom);
    566   },
    567   /**
    568    * Method: add
    569    *
    570    * Parameters:
    571    * x - {Float}
    572    * y - {Float}
    573    *
    574    * Returns:
    575    * {<ZOO.Bounds>} A new bounds whose coordinates are the same as
    576    *     this, but shifted by the passed-in x and y values.
    577    */
    578   add:function(x, y) {
    579     if ( (x == null) || (y == null) )
    580       return null;
    581     return new ZOO.Bounds(this.left + x, this.bottom + y,
    582                                  this.right + x, this.top + y);
    583   },
    584   /**
    585    * Method: extend
    586    * Extend the bounds to include the point, lonlat, or bounds specified.
    587    *     Note, this function assumes that left < right and bottom < top.
    588    *
    589    * Parameters:
    590    * object - {Object} Can be Point, or Bounds
    591    */
    592   extend:function(object) {
    593     var bounds = null;
    594     if (object) {
    595       // clear cached center location
    596       switch(object.CLASS_NAME) {
    597         case "ZOO.Geometry.Point":
    598           bounds = new ZOO.Bounds(object.x, object.y,
    599                                          object.x, object.y);
    600           break;
    601         case "ZOO.Bounds":   
    602           bounds = object;
    603           break;
    604       }
    605       if (bounds) {
    606         if ( (this.left == null) || (bounds.left < this.left))
    607           this.left = bounds.left;
    608         if ( (this.bottom == null) || (bounds.bottom < this.bottom) )
    609           this.bottom = bounds.bottom;
    610         if ( (this.right == null) || (bounds.right > this.right) )
    611           this.right = bounds.right;
    612         if ( (this.top == null) || (bounds.top > this.top) )
    613           this.top = bounds.top;
    614       }
    615     }
    616   },
    617   /**
    618    * APIMethod: contains
    619    *
    620    * Parameters:
    621    * x - {Float}
    622    * y - {Float}
    623    * inclusive - {Boolean} Whether or not to include the border.
    624    *     Default is true.
    625    *
    626    * Returns:
    627    * {Boolean} Whether or not the passed-in coordinates are within this
    628    *     bounds.
    629    */
    630   contains:function(x, y, inclusive) {
    631      //set default
    632      if (inclusive == null)
    633        inclusive = true;
    634      if (x == null || y == null)
    635        return false;
    636      x = parseFloat(x);
    637      y = parseFloat(y);
    638 
    639      var contains = false;
    640      if (inclusive)
    641        contains = ((x >= this.left) && (x <= this.right) &&
    642                    (y >= this.bottom) && (y <= this.top));
    643      else
    644        contains = ((x > this.left) && (x < this.right) &&
    645                    (y > this.bottom) && (y < this.top));
    646      return contains;
    647   },
    648   /**
    649    * Method: intersectsBounds
    650    * Determine whether the target bounds intersects this bounds.  Bounds are
    651    *     considered intersecting if any of their edges intersect or if one
    652    *     bounds contains the other.
    653    *
    654    * Parameters:
    655    * bounds - {<ZOO.Bounds>} The target bounds.
    656    * inclusive - {Boolean} Treat coincident borders as intersecting.  Default
    657    *     is true.  If false, bounds that do not overlap but only touch at the
    658    *     border will not be considered as intersecting.
    659    *
    660    * Returns:
    661    * {Boolean} The passed-in bounds object intersects this bounds.
    662    */
    663   intersectsBounds:function(bounds, inclusive) {
    664     if (inclusive == null)
    665       inclusive = true;
    666     var intersects = false;
    667     var mightTouch = (
    668         this.left == bounds.right ||
    669         this.right == bounds.left ||
    670         this.top == bounds.bottom ||
    671         this.bottom == bounds.top
    672     );
    673     if (inclusive || !mightTouch) {
    674       var inBottom = (
    675           ((bounds.bottom >= this.bottom) && (bounds.bottom <= this.top)) ||
    676           ((this.bottom >= bounds.bottom) && (this.bottom <= bounds.top))
    677           );
    678       var inTop = (
    679           ((bounds.top >= this.bottom) && (bounds.top <= this.top)) ||
    680           ((this.top > bounds.bottom) && (this.top < bounds.top))
    681           );
    682       var inLeft = (
    683           ((bounds.left >= this.left) && (bounds.left <= this.right)) ||
    684           ((this.left >= bounds.left) && (this.left <= bounds.right))
    685           );
    686       var inRight = (
    687           ((bounds.right >= this.left) && (bounds.right <= this.right)) ||
    688           ((this.right >= bounds.left) && (this.right <= bounds.right))
    689           );
    690       intersects = ((inBottom || inTop) && (inLeft || inRight));
    691     }
    692     return intersects;
    693   },
    694   /**
    695    * Method: containsBounds
    696    * Determine whether the target bounds is contained within this bounds.
    697    *
    698    * bounds - {<ZOO.Bounds>} The target bounds.
    699    * partial - {Boolean} If any of the target corners is within this bounds
    700    *     consider the bounds contained.  Default is false.  If true, the
    701    *     entire target bounds must be contained within this bounds.
    702    * inclusive - {Boolean} Treat shared edges as contained.  Default is
    703    *     true.
    704    *
    705    * Returns:
    706    * {Boolean} The passed-in bounds object is contained within this bounds.
    707    */
    708   containsBounds:function(bounds, partial, inclusive) {
    709     if (partial == null)
    710       partial = false;
    711     if (inclusive == null)
    712       inclusive = true;
    713     var bottomLeft  = this.contains(bounds.left, bounds.bottom, inclusive);
    714     var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
    715     var topLeft  = this.contains(bounds.left, bounds.top, inclusive);
    716     var topRight = this.contains(bounds.right, bounds.top, inclusive);
    717     return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
    718                      : (bottomLeft && bottomRight && topLeft && topRight);
    719   },
    720   CLASS_NAME: 'ZOO.Bounds'
    721 });
    722 
    723 /**
    724  * Class: ZOO.Projection
    725  * Class for coordinate transforms between coordinate systems.
    726  *     Depends on the zoo-proj4js library. zoo-proj4js library
    727  *     is loaded by the ZOO Kernel with zoo-api.
    728  */
    729 ZOO.Projection = ZOO.Class({
    730   /**
    731    * Property: proj
    732    * {Object} Proj4js.Proj instance.
    733    */
    734   proj: null,
    735   /**
    736    * Property: projCode
    737    * {String}
    738    */
    739   projCode: null,
    740   /**
    741    * Constructor: ZOO.Projection
    742    * This class offers several methods for interacting with a wrapped
    743    *     zoo-pro4js projection object.
    744    *
    745    * Parameters:
    746    * projCode - {String} A string identifying the Well Known Identifier for
    747    *    the projection.
    748    * options - {Object} An optional object to set additional properties.
    749    *
    750    * Returns:
    751    * {<ZOO.Projection>} A projection object.
    752    */
    753   initialize: function(projCode, options) {
    754     ZOO.extend(this, options);
    755     this.projCode = projCode;
    756     if (Proj4js) {
    757       this.proj = new Proj4js.Proj(projCode);
    758     }
    759   },
    760   /**
    761    * Method: getCode
    762    * Get the string SRS code.
    763    *
    764    * Returns:
    765    * {String} The SRS code.
    766    */
    767   getCode: function() {
    768     return this.proj ? this.proj.srsCode : this.projCode;
    769   },
    770   /**
    771    * Method: getUnits
    772    * Get the units string for the projection -- returns null if
    773    *     zoo-proj4js is not available.
    774    *
    775    * Returns:
    776    * {String} The units abbreviation.
    777    */
    778   getUnits: function() {
    779     return this.proj ? this.proj.units : null;
    780   },
    781   /**
    782    * Method: toString
    783    * Convert projection to string (getCode wrapper).
    784    *
    785    * Returns:
    786    * {String} The projection code.
    787    */
    788   toString: function() {
    789     return this.getCode();
    790   },
    791   /**
    792    * Method: equals
    793    * Test equality of two projection instances.  Determines equality based
    794    *     soley on the projection code.
    795    *
    796    * Returns:
    797    * {Boolean} The two projections are equivalent.
    798    */
    799   equals: function(projection) {
    800     if (projection && projection.getCode)
    801       return this.getCode() == projection.getCode();
    802     else
    803       return false;
    804   },
    805   /* Method: destroy
    806    * Destroy projection object.
    807    */
    808   destroy: function() {
    809     this.proj = null;
    810     this.projCode = null;
    811   },
    812   CLASS_NAME: 'ZOO.Projection'
    813 });
    814 /**
    815  * Method: transform
    816  * Transform a point coordinate from one projection to another.  Note that
    817  *     the input point is transformed in place.
    818  *
    819  * Parameters:
    820  * point - {{ZOO.Geometry.Point> | Object} An object with x and y
    821  *     properties representing coordinates in those dimensions.
    822  * sourceProj - {ZOO.Projection} Source map coordinate system
    823  * destProj - {ZOO.Projection} Destination map coordinate system
    824  *
    825  * Returns:
    826  * point - {object} A transformed coordinate.  The original point is modified.
    827  */
    828 ZOO.Projection.transform = function(point, source, dest) {
    829     if (source.proj && dest.proj)
    830         point = Proj4js.transform(source.proj, dest.proj, point);
    831     return point;
    832 };
    833 
    834 /**
    835  * Class: ZOO.Format
    836  * Base class for format reading/writing a variety of formats. Subclasses
    837  *     of ZOO.Format are expected to have read and write methods.
    838  */
    839 ZOO.Format = ZOO.Class({
    840   /**
    841    * Property: options
    842    * {Object} A reference to options passed to the constructor.
    843    */
    844   options:null,
    845   /**
    846    * Property: externalProjection
    847    * {<ZOO.Projection>} When passed a externalProjection and
    848    *     internalProjection, the format will reproject the geometries it
    849    *     reads or writes. The externalProjection is the projection used by
    850    *     the content which is passed into read or which comes out of write.
    851    *     In order to reproject, a projection transformation function for the
    852    *     specified projections must be available. This support is provided
    853    *     via zoo-proj4js.
    854    */
    855   externalProjection: null,
    856   /**
    857    * Property: internalProjection
    858    * {<ZOO.Projection>} When passed a externalProjection and
    859    *     internalProjection, the format will reproject the geometries it
    860    *     reads or writes. The internalProjection is the projection used by
    861    *     the geometries which are returned by read or which are passed into
    862    *     write.  In order to reproject, a projection transformation function
    863    *     for the specified projections must be available. This support is
    864    *     provided via zoo-proj4js.
    865    */
    866   internalProjection: null,
    867   /**
    868    * Property: data
    869    * {Object} When <keepData> is true, this is the parsed string sent to
    870    *     <read>.
    871    */
    872   data: null,
    873   /**
    874    * Property: keepData
    875    * {Object} Maintain a reference (<data>) to the most recently read data.
    876    *     Default is false.
    877    */
    878   keepData: false,
    879   /**
    880    * Constructor: ZOO.Format
    881    * Instances of this class are not useful.  See one of the subclasses.
    882    *
    883    * Parameters:
    884    * options - {Object} An optional object with properties to set on the
    885    *           format
    886    *
    887    * Valid options:
    888    * keepData - {Boolean} If true, upon <read>, the data property will be
    889    *     set to the parsed object (e.g. the json or xml object).
    890    *
    891    * Returns:
    892    * An instance of ZOO.Format
    893    */
    894   initialize: function(options) {
    895     ZOO.extend(this, options);
    896     this.options = options;
    897   },
    898   /**
    899    * Method: destroy
    900    * Clean up.
    901    */
    902   destroy: function() {
    903   },
    904   /**
    905    * Method: read
    906    * Read data from a string, and return an object whose type depends on the
    907    * subclass.
    908    *
    909    * Parameters:
    910    * data - {string} Data to read/parse.
    911    *
    912    * Returns:
    913    * Depends on the subclass
    914    */
    915   read: function(data) {
    916   },
    917   /**
    918    * Method: write
    919    * Accept an object, and return a string.
    920    *
    921    * Parameters:
    922    * object - {Object} Object to be serialized
    923    *
    924    * Returns:
    925    * {String} A string representation of the object.
    926    */
    927   write: function(data) {
    928   },
    929   CLASS_NAME: 'ZOO.Format'
    930 });
    931 /**
    932  * Class: ZOO.Format.WKT
    933  * Class for reading and writing Well-Known Text. Create a new instance
    934  * with the <ZOO.Format.WKT> constructor.
    935  *
    936  * Inherits from:
    937  *  - <ZOO.Format>
    938  */
    939 ZOO.Format.WKT = ZOO.Class(ZOO.Format, {
    940   /**
    941    * Constructor: ZOO.Format.WKT
    942    * Create a new parser for WKT
    943    *
    944    * Parameters:
    945    * options - {Object} An optional object whose properties will be set on
    946    *           this instance
    947    *
    948    * Returns:
    949    * {<ZOO.Format.WKT>} A new WKT parser.
    950    */
    951   initialize: function(options) {
    952     this.regExes = {
    953       'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
    954       'spaces': /\s+/,
    955       'parenComma': /\)\s*,\s*\(/,
    956       'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/,  // can't use {2} here
    957       'trimParens': /^\s*\(?(.*?)\)?\s*$/
    958     };
    959     ZOO.Format.prototype.initialize.apply(this, [options]);
    960   },
    961   /**
    962    * Method: read
    963    * Deserialize a WKT string and return a vector feature or an
    964    *     array of vector features.  Supports WKT for POINT,
    965    *     MULTIPOINT, LINESTRING, MULTILINESTRING, POLYGON,
    966    *     MULTIPOLYGON, and GEOMETRYCOLLECTION.
    967    *
    968    * Parameters:
    969    * wkt - {String} A WKT string
    970    *
    971    * Returns:
    972    * {<ZOO.Feature.Vector>|Array} A feature or array of features for
    973    *     GEOMETRYCOLLECTION WKT.
    974    */
    975   read: function(wkt) {
    976     var features, type, str;
    977     var matches = this.regExes.typeStr.exec(wkt);
    978     if(matches) {
    979       type = matches[1].toLowerCase();
    980       str = matches[2];
    981       if(this.parse[type]) {
    982         features = this.parse[type].apply(this, [str]);
    983       }
    984       if (this.internalProjection && this.externalProjection) {
    985         if (features &&
    986             features.CLASS_NAME == "ZOO.Feature") {
    987           features.geometry.transform(this.externalProjection,
    988                                       this.internalProjection);
    989         } else if (features &&
    990             type != "geometrycollection" &&
    991             typeof features == "object") {
    992           for (var i=0, len=features.length; i<len; i++) {
    993             var component = features[i];
    994             component.geometry.transform(this.externalProjection,
    995                                          this.internalProjection);
    996           }
    997         }
    998       }
    999     }   
    1000     return features;
    1001   },
    1002   /**
    1003    * Method: write
    1004    * Serialize a feature or array of features into a WKT string.
    1005    *
    1006    * Parameters:
    1007    * features - {<ZOO.Feature.Vector>|Array} A feature or array of
    1008    *            features
    1009    *
    1010    * Returns:
    1011    * {String} The WKT string representation of the input geometries
    1012    */
    1013   write: function(features) {
    1014     var collection, geometry, type, data, isCollection;
    1015     if(features.constructor == Array) {
    1016       collection = features;
    1017       isCollection = true;
    1018     } else {
    1019       collection = [features];
    1020       isCollection = false;
    1021     }
    1022     var pieces = [];
    1023     if(isCollection)
    1024       pieces.push('GEOMETRYCOLLECTION(');
    1025     for(var i=0, len=collection.length; i<len; ++i) {
    1026       if(isCollection && i>0)
    1027         pieces.push(',');
    1028       geometry = collection[i].geometry;
    1029       type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
    1030       if(!this.extract[type])
    1031         return null;
    1032       if (this.internalProjection && this.externalProjection) {
    1033         geometry = geometry.clone();
    1034         geometry.transform(this.internalProjection,
    1035                           this.externalProjection);
    1036       }                       
    1037       data = this.extract[type].apply(this, [geometry]);
    1038       pieces.push(type.toUpperCase() + '(' + data + ')');
    1039     }
    1040     if(isCollection)
    1041       pieces.push(')');
    1042     return pieces.join('');
    1043   },
    1044   /**
    1045    * Property: extract
    1046    * Object with properties corresponding to the geometry types.
    1047    * Property values are functions that do the actual data extraction.
    1048    */
    1049   extract: {
    1050     /**
    1051      * Return a space delimited string of point coordinates.
    1052      * @param {<ZOO.Geometry.Point>} point
    1053      * @returns {String} A string of coordinates representing the point
    1054      */
    1055     'point': function(point) {
    1056       return point.x + ' ' + point.y;
    1057     },
    1058     /**
    1059      * Return a comma delimited string of point coordinates from a multipoint.
    1060      * @param {<ZOO.Geometry.MultiPoint>} multipoint
    1061      * @returns {String} A string of point coordinate strings representing
    1062      *                  the multipoint
    1063      */
    1064     'multipoint': function(multipoint) {
    1065       var array = [];
    1066       for(var i=0, len=multipoint.components.length; i<len; ++i) {
    1067         array.push(this.extract.point.apply(this, [multipoint.components[i]]));
    1068       }
    1069       return array.join(',');
    1070     },
    1071     /**
    1072      * Return a comma delimited string of point coordinates from a line.
    1073      * @param {<ZOO.Geometry.LineString>} linestring
    1074      * @returns {String} A string of point coordinate strings representing
    1075      *                  the linestring
    1076      */
    1077     'linestring': function(linestring) {
    1078       var array = [];
    1079       for(var i=0, len=linestring.components.length; i<len; ++i) {
    1080         array.push(this.extract.point.apply(this, [linestring.components[i]]));
    1081       }
    1082       return array.join(',');
    1083     },
    1084     /**
    1085      * Return a comma delimited string of linestring strings from a multilinestring.
    1086      * @param {<ZOO.Geometry.MultiLineString>} multilinestring
    1087      * @returns {String} A string of of linestring strings representing
    1088      *                  the multilinestring
    1089      */
    1090     'multilinestring': function(multilinestring) {
    1091       var array = [];
    1092       for(var i=0, len=multilinestring.components.length; i<len; ++i) {
    1093         array.push('(' +
    1094             this.extract.linestring.apply(this, [multilinestring.components[i]]) +
    1095             ')');
    1096       }
    1097       return array.join(',');
    1098     },
    1099     /**
    1100      * Return a comma delimited string of linear ring arrays from a polygon.
    1101      * @param {<ZOO.Geometry.Polygon>} polygon
    1102      * @returns {String} An array of linear ring arrays representing the polygon
    1103      */
    1104     'polygon': function(polygon) {
    1105       var array = [];
    1106       for(var i=0, len=polygon.components.length; i<len; ++i) {
    1107         array.push('(' +
    1108             this.extract.linestring.apply(this, [polygon.components[i]]) +
    1109             ')');
    1110       }
    1111       return array.join(',');
    1112     },
    1113     /**
    1114      * Return an array of polygon arrays from a multipolygon.
    1115      * @param {<ZOO.Geometry.MultiPolygon>} multipolygon
    1116      * @returns {Array} An array of polygon arrays representing
    1117      *                  the multipolygon
    1118      */
    1119     'multipolygon': function(multipolygon) {
    1120       var array = [];
    1121       for(var i=0, len=multipolygon.components.length; i<len; ++i) {
    1122         array.push('(' +
    1123             this.extract.polygon.apply(this, [multipolygon.components[i]]) +
    1124             ')');
    1125       }
    1126       return array.join(',');
    1127     }
    1128   },
    1129   /**
    1130    * Property: parse
    1131    * Object with properties corresponding to the geometry types.
    1132    *     Property values are functions that do the actual parsing.
    1133    */
    1134   parse: {
    1135     /**
    1136      * Method: parse.point
    1137      * Return point feature given a point WKT fragment.
    1138      *
    1139      * Parameters:
    1140      * str - {String} A WKT fragment representing the point
    1141      * Returns:
    1142      * {<ZOO.Feature>} A point feature
    1143      */
    1144     'point': function(str) {
    1145        var coords = ZOO.String.trim(str).split(this.regExes.spaces);
    1146             return new ZOO.Feature(
    1147                 new ZOO.Geometry.Point(coords[0], coords[1])
    1148             );
    1149     },
    1150     /**
    1151      * Method: parse.multipoint
    1152      * Return a multipoint feature given a multipoint WKT fragment.
    1153      *
    1154      * Parameters:
    1155      * str - {String} A WKT fragment representing the multipoint
    1156      *
    1157      * Returns:
    1158      * {<ZOO.Feature>} A multipoint feature
    1159      */
    1160     'multipoint': function(str) {
    1161        var points = ZOO.String.trim(str).split(',');
    1162        var components = [];
    1163        for(var i=0, len=points.length; i<len; ++i) {
    1164          components.push(this.parse.point.apply(this, [points[i]]).geometry);
    1165        }
    1166        return new ZOO.Feature(
    1167            new ZOO.Geometry.MultiPoint(components)
    1168            );
    1169     },
    1170     /**
    1171      * Method: parse.linestring
    1172      * Return a linestring feature given a linestring WKT fragment.
    1173      *
    1174      * Parameters:
    1175      * str - {String} A WKT fragment representing the linestring
    1176      *
    1177      * Returns:
    1178      * {<ZOO.Feature>} A linestring feature
    1179      */
    1180     'linestring': function(str) {
    1181       var points = ZOO.String.trim(str).split(',');
    1182       var components = [];
    1183       for(var i=0, len=points.length; i<len; ++i) {
    1184         components.push(this.parse.point.apply(this, [points[i]]).geometry);
    1185       }
    1186       return new ZOO.Feature(
    1187           new ZOO.Geometry.LineString(components)
    1188           );
    1189     },
    1190     /**
    1191      * Method: parse.multilinestring
    1192      * Return a multilinestring feature given a multilinestring WKT fragment.
    1193      *
    1194      * Parameters:
    1195      * str - {String} A WKT fragment representing the multilinestring
    1196      *
    1197      * Returns:
    1198      * {<ZOO.Feature>} A multilinestring feature
    1199      */
    1200     'multilinestring': function(str) {
    1201       var line;
    1202       var lines = ZOO.String.trim(str).split(this.regExes.parenComma);
    1203       var components = [];
    1204       for(var i=0, len=lines.length; i<len; ++i) {
    1205         line = lines[i].replace(this.regExes.trimParens, '$1');
    1206         components.push(this.parse.linestring.apply(this, [line]).geometry);
    1207       }
    1208       return new ZOO.Feature(
    1209           new ZOO.Geometry.MultiLineString(components)
    1210           );
    1211     },
    1212     /**
    1213      * Method: parse.polygon
    1214      * Return a polygon feature given a polygon WKT fragment.
    1215      *
    1216      * Parameters:
    1217      * str - {String} A WKT fragment representing the polygon
    1218      *
    1219      * Returns:
    1220      * {<ZOO.Feature>} A polygon feature
    1221      */
    1222     'polygon': function(str) {
    1223        var ring, linestring, linearring;
    1224        var rings = ZOO.String.trim(str).split(this.regExes.parenComma);
    1225        var components = [];
    1226        for(var i=0, len=rings.length; i<len; ++i) {
    1227          ring = rings[i].replace(this.regExes.trimParens, '$1');
    1228          linestring = this.parse.linestring.apply(this, [ring]).geometry;
    1229          linearring = new ZOO.Geometry.LinearRing(linestring.components);
    1230          components.push(linearring);
    1231        }
    1232        return new ZOO.Feature(
    1233            new ZOO.Geometry.Polygon(components)
    1234            );
    1235     },
    1236     /**
    1237      * Method: parse.multipolygon
    1238      * Return a multipolygon feature given a multipolygon WKT fragment.
    1239      *
    1240      * Parameters:
    1241      * str - {String} A WKT fragment representing the multipolygon
    1242      *
    1243      * Returns:
    1244      * {<ZOO.Feature>} A multipolygon feature
    1245      */
    1246     'multipolygon': function(str) {
    1247       var polygon;
    1248       var polygons = ZOO.String.trim(str).split(this.regExes.doubleParenComma);
    1249       var components = [];
    1250       for(var i=0, len=polygons.length; i<len; ++i) {
    1251         polygon = polygons[i].replace(this.regExes.trimParens, '$1');
    1252         components.push(this.parse.polygon.apply(this, [polygon]).geometry);
    1253       }
    1254       return new ZOO.Feature(
    1255           new ZOO.Geometry.MultiPolygon(components)
    1256           );
    1257     },
    1258     /**
    1259      * Method: parse.geometrycollection
    1260      * Return an array of features given a geometrycollection WKT fragment.
    1261      *
    1262      * Parameters:
    1263      * str - {String} A WKT fragment representing the geometrycollection
    1264      *
    1265      * Returns:
    1266      * {Array} An array of ZOO.Feature
    1267      */
    1268     'geometrycollection': function(str) {
    1269       // separate components of the collection with |
    1270       str = str.replace(/,\s*([A-Za-z])/g, '|$1');
    1271       var wktArray = ZOO.String.trim(str).split('|');
    1272       var components = [];
    1273       for(var i=0, len=wktArray.length; i<len; ++i) {
    1274         components.push(ZOO.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
    1275       }
    1276       return components;
    1277     }
    1278   },
    1279   CLASS_NAME: 'ZOO.Format.WKT'
    1280 });
    1281 /**
    1282  * Class: ZOO.Format.JSON
    1283  * A parser to read/write JSON safely. Create a new instance with the
    1284  *     <ZOO.Format.JSON> constructor.
    1285  *
    1286  * Inherits from:
    1287  *  - <ZOO.Format>
    1288  */
    1289 ZOO.Format.JSON = ZOO.Class(ZOO.Format, {
    1290   /**
    1291    * Property: indent
    1292    * {String} For "pretty" printing, the indent string will be used once for
    1293    *     each indentation level.
    1294    */
    1295   indent: "    ",
    1296   /**
    1297    * Property: space
    1298    * {String} For "pretty" printing, the space string will be used after
    1299    *     the ":" separating a name/value pair.
    1300    */
    1301   space: " ",
    1302   /**
    1303    * Property: newline
    1304    * {String} For "pretty" printing, the newline string will be used at the
    1305    *     end of each name/value pair or array item.
    1306    */
    1307   newline: "\n",
    1308   /**
    1309    * Property: level
    1310    * {Integer} For "pretty" printing, this is incremented/decremented during
    1311    *     serialization.
    1312    */
    1313   level: 0,
    1314   /**
    1315    * Property: pretty
    1316    * {Boolean} Serialize with extra whitespace for structure.  This is set
    1317    *     by the <write> method.
    1318    */
    1319   pretty: false,
    1320   /**
    1321    * Constructor: ZOO.Format.JSON
    1322    * Create a new parser for JSON.
    1323    *
    1324    * Parameters:
    1325    * options - {Object} An optional object whose properties will be set on
    1326    *     this instance.
    1327    */
    1328   initialize: function(options) {
    1329     ZOO.Format.prototype.initialize.apply(this, [options]);
    1330   },
    1331   /**
    1332    * Method: read
    1333    * Deserialize a json string.
    1334    *
    1335    * Parameters:
    1336    * json - {String} A JSON string
    1337    * filter - {Function} A function which will be called for every key and
    1338    *     value at every level of the final result. Each value will be
    1339    *     replaced by the result of the filter function. This can be used to
    1340    *     reform generic objects into instances of classes, or to transform
    1341    *     date strings into Date objects.
    1342    *     
    1343    * Returns:
    1344    * {Object} An object, array, string, or number .
    1345    */
    1346   read: function(json, filter) {
    1347     /**
    1348      * Parsing happens in three stages. In the first stage, we run the text
    1349      *     against a regular expression which looks for non-JSON
    1350      *     characters. We are especially concerned with '()' and 'new'
    1351      *     because they can cause invocation, and '=' because it can cause
    1352      *     mutation. But just to be safe, we will reject all unexpected
    1353      *     characters.
    1354      */
    1355     try {
    1356       if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
    1357                           replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
    1358                           replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
    1359         /**
    1360          * In the second stage we use the eval function to compile the
    1361          *     text into a JavaScript structure. The '{' operator is
    1362          *     subject to a syntactic ambiguity in JavaScript - it can
    1363          *     begin a block or an object literal. We wrap the text in
    1364          *     parens to eliminate the ambiguity.
    1365          */
    1366         var object = eval('(' + json + ')');
    1367         /**
    1368          * In the optional third stage, we recursively walk the new
    1369          *     structure, passing each name/value pair to a filter
    1370          *     function for possible transformation.
    1371          */
    1372         if(typeof filter === 'function') {
    1373           function walk(k, v) {
    1374             if(v && typeof v === 'object') {
    1375               for(var i in v) {
    1376                 if(v.hasOwnProperty(i)) {
    1377                   v[i] = walk(i, v[i]);
    1378                 }
    1379               }
    1380             }
    1381             return filter(k, v);
    1382           }
    1383           object = walk('', object);
    1384         }
    1385         if(this.keepData) {
    1386           this.data = object;
    1387         }
    1388         return object;
    1389       }
    1390     } catch(e) {
    1391       // Fall through if the regexp test fails.
    1392     }
    1393     return null;
    1394   },
    1395   /**
    1396    * Method: write
    1397    * Serialize an object into a JSON string.
    1398    *
    1399    * Parameters:
    1400    * value - {String} The object, array, string, number, boolean or date
    1401    *     to be serialized.
    1402    * pretty - {Boolean} Structure the output with newlines and indentation.
    1403    *     Default is false.
    1404    *
    1405    * Returns:
    1406    * {String} The JSON string representation of the input value.
    1407    */
    1408   write: function(value, pretty) {
    1409     this.pretty = !!pretty;
    1410     var json = null;
    1411     var type = typeof value;
    1412     if(this.serialize[type]) {
    1413       try {
    1414         json = this.serialize[type].apply(this, [value]);
    1415       } catch(err) {
    1416         //OpenLayers.Console.error("Trouble serializing: " + err);
    1417       }
    1418     }
    1419     return json;
    1420   },
    1421   /**
    1422    * Method: writeIndent
    1423    * Output an indentation string depending on the indentation level.
    1424    *
    1425    * Returns:
    1426    * {String} An appropriate indentation string.
    1427    */
    1428   writeIndent: function() {
    1429     var pieces = [];
    1430     if(this.pretty) {
    1431       for(var i=0; i<this.level; ++i) {
    1432         pieces.push(this.indent);
    1433       }
    1434     }
    1435     return pieces.join('');
    1436   },
    1437   /**
    1438    * Method: writeNewline
    1439    * Output a string representing a newline if in pretty printing mode.
    1440    *
    1441    * Returns:
    1442    * {String} A string representing a new line.
    1443    */
    1444   writeNewline: function() {
    1445     return (this.pretty) ? this.newline : '';
    1446   },
    1447   /**
    1448    * Method: writeSpace
    1449    * Output a string representing a space if in pretty printing mode.
    1450    *
    1451    * Returns:
    1452    * {String} A space.
    1453    */
    1454   writeSpace: function() {
    1455     return (this.pretty) ? this.space : '';
    1456   },
    1457   /**
    1458    * Property: serialize
    1459    * Object with properties corresponding to the serializable data types.
    1460    *     Property values are functions that do the actual serializing.
    1461    */
    1462   serialize: {
    1463     /**
    1464      * Method: serialize.object
    1465      * Transform an object into a JSON string.
    1466      *
    1467      * Parameters:
    1468      * object - {Object} The object to be serialized.
    1469      *
    1470      * Returns:
    1471      * {String} A JSON string representing the object.
    1472      */
    1473     'object': function(object) {
    1474        // three special objects that we want to treat differently
    1475        if(object == null)
    1476          return "null";
    1477        if(object.constructor == Date)
    1478          return this.serialize.date.apply(this, [object]);
    1479        if(object.constructor == Array)
    1480          return this.serialize.array.apply(this, [object]);
    1481        var pieces = ['{'];
    1482        this.level += 1;
    1483        var key, keyJSON, valueJSON;
    1484 
    1485        var addComma = false;
    1486        for(key in object) {
    1487          if(object.hasOwnProperty(key)) {
    1488            // recursive calls need to allow for sub-classing
    1489            keyJSON = ZOO.Format.JSON.prototype.write.apply(this,
    1490                                                            [key, this.pretty]);
    1491            valueJSON = ZOO.Format.JSON.prototype.write.apply(this,
    1492                                                              [object[key], this.pretty]);
    1493            if(keyJSON != null && valueJSON != null) {
    1494              if(addComma)
    1495                pieces.push(',');
    1496              pieces.push(this.writeNewline(), this.writeIndent(),
    1497                          keyJSON, ':', this.writeSpace(), valueJSON);
    1498              addComma = true;
    1499            }
    1500          }
    1501        }
    1502        this.level -= 1;
    1503        pieces.push(this.writeNewline(), this.writeIndent(), '}');
    1504        return pieces.join('');
    1505     },
    1506     /**
    1507      * Method: serialize.array
    1508      * Transform an array into a JSON string.
    1509      *
    1510      * Parameters:
    1511      * array - {Array} The array to be serialized
    1512      *
    1513      * Returns:
    1514      * {String} A JSON string representing the array.
    1515      */
    1516     'array': function(array) {
    1517       var json;
    1518       var pieces = ['['];
    1519       this.level += 1;
    1520       for(var i=0, len=array.length; i<len; ++i) {
    1521         // recursive calls need to allow for sub-classing
    1522         json = ZOO.Format.JSON.prototype.write.apply(this,
    1523                                                      [array[i], this.pretty]);
    1524         if(json != null) {
    1525           if(i > 0)
    1526             pieces.push(',');
    1527           pieces.push(this.writeNewline(), this.writeIndent(), json);
    1528         }
    1529       }
    1530       this.level -= 1;   
    1531       pieces.push(this.writeNewline(), this.writeIndent(), ']');
    1532       return pieces.join('');
    1533     },
    1534     /**
    1535      * Method: serialize.string
    1536      * Transform a string into a JSON string.
    1537      *
    1538      * Parameters:
    1539      * string - {String} The string to be serialized
    1540      *
    1541      * Returns:
    1542      * {String} A JSON string representing the string.
    1543      */
    1544     'string': function(string) {
    1545       var m = {
    1546                 '\b': '\\b',
    1547                 '\t': '\\t',
    1548                 '\n': '\\n',
    1549                 '\f': '\\f',
    1550                 '\r': '\\r',
    1551                 '"' : '\\"',
    1552                 '\\': '\\\\'
    1553       };
    1554       if(/["\\\x00-\x1f]/.test(string)) {
    1555         return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
    1556             var c = m[b];
    1557             if(c)
    1558               return c;
    1559             c = b.charCodeAt();
    1560             return '\\u00' +
    1561             Math.floor(c / 16).toString(16) +
    1562             (c % 16).toString(16);
    1563         }) + '"';
    1564       }
    1565       return '"' + string + '"';
    1566     },
    1567     /**
    1568      * Method: serialize.number
    1569      * Transform a number into a JSON string.
    1570      *
    1571      * Parameters:
    1572      * number - {Number} The number to be serialized.
    1573      *
    1574      * Returns:
    1575      * {String} A JSON string representing the number.
    1576      */
    1577     'number': function(number) {
    1578       return isFinite(number) ? String(number) : "null";
    1579     },
    1580     /**
    1581      * Method: serialize.boolean
    1582      * Transform a boolean into a JSON string.
    1583      *
    1584      * Parameters:
    1585      * bool - {Boolean} The boolean to be serialized.
    1586      *
    1587      * Returns:
    1588      * {String} A JSON string representing the boolean.
    1589      */
    1590     'boolean': function(bool) {
    1591       return String(bool);
    1592     },
    1593     /**
    1594      * Method: serialize.date
    1595      * Transform a date into a JSON string.
    1596      *
    1597      * Parameters:
    1598      * date - {Date} The date to be serialized.
    1599      *
    1600      * Returns:
    1601      * {String} A JSON string representing the date.
    1602      */
    1603     'date': function(date) {   
    1604       function format(number) {
    1605         // Format integers to have at least two digits.
    1606         return (number < 10) ? '0' + number : number;
    1607       }
    1608       return '"' + date.getFullYear() + '-' +
    1609         format(date.getMonth() + 1) + '-' +
    1610         format(date.getDate()) + 'T' +
    1611         format(date.getHours()) + ':' +
    1612         format(date.getMinutes()) + ':' +
    1613         format(date.getSeconds()) + '"';
    1614     }
    1615   },
    1616   CLASS_NAME: 'ZOO.Format.JSON'
    1617 });
    1618 /**
    1619  * Class: ZOO.Format.GeoJSON
    1620  * Read and write GeoJSON. Create a new parser with the
    1621  *     <ZOO.Format.GeoJSON> constructor.
    1622  *
    1623  * Inherits from:
    1624  *  - <ZOO.Format.JSON>
    1625  */
    1626 ZOO.Format.GeoJSON = ZOO.Class(ZOO.Format.JSON, {
    1627   /**
    1628    * Constructor: ZOO.Format.GeoJSON
    1629    * Create a new parser for GeoJSON.
    1630    *
    1631    * Parameters:
    1632    * options - {Object} An optional object whose properties will be set on
    1633    *     this instance.
    1634    */
    1635   initialize: function(options) {
    1636     ZOO.Format.JSON.prototype.initialize.apply(this, [options]);
    1637   },
    1638   /**
    1639    * Method: read
    1640    * Deserialize a GeoJSON string.
    1641    *
    1642    * Parameters:
    1643    * json - {String} A GeoJSON string
    1644    * type - {String} Optional string that determines the structure of
    1645    *     the output.  Supported values are "Geometry", "Feature", and
    1646    *     "FeatureCollection".  If absent or null, a default of
    1647    *     "FeatureCollection" is assumed.
    1648    * filter - {Function} A function which will be called for every key and
    1649    *     value at every level of the final result. Each value will be
    1650    *     replaced by the result of the filter function. This can be used to
    1651    *     reform generic objects into instances of classes, or to transform
    1652    *     date strings into Date objects.
    1653    *
    1654    * Returns:
    1655    * {Object} The return depends on the value of the type argument. If type
    1656    *     is "FeatureCollection" (the default), the return will be an array
    1657    *     of <ZOO.Feature>. If type is "Geometry", the input json
    1658    *     must represent a single geometry, and the return will be an
    1659    *     <ZOO.Geometry>.  If type is "Feature", the input json must
    1660    *     represent a single feature, and the return will be an
    1661    *     <ZOO.Feature>.
    1662    */
    1663   read: function(json, type, filter) {
    1664     type = (type) ? type : "FeatureCollection";
    1665     var results = null;
    1666     var obj = null;
    1667     if (typeof json == "string")
    1668       obj = ZOO.Format.JSON.prototype.read.apply(this,[json, filter]);
    1669     else
    1670       obj = json;
    1671     if(!obj) {
    1672       //ZOO.Console.error("Bad JSON: " + json);
    1673     } else if(typeof(obj.type) != "string") {
    1674       //ZOO.Console.error("Bad GeoJSON - no type: " + json);
    1675     } else if(this.isValidType(obj, type)) {
    1676       switch(type) {
    1677         case "Geometry":
    1678           try {
    1679             results = this.parseGeometry(obj);
    1680           } catch(err) {
    1681             //ZOO.Console.error(err);
    1682           }
    1683           break;
    1684         case "Feature":
    1685           try {
    1686             results = this.parseFeature(obj);
    1687             results.type = "Feature";
    1688           } catch(err) {
    1689             //ZOO.Console.error(err);
    1690           }
    1691           break;
    1692         case "FeatureCollection":
    1693           // for type FeatureCollection, we allow input to be any type
    1694           results = [];
    1695           switch(obj.type) {
    1696             case "Feature":
    1697               try {
    1698                 results.push(this.parseFeature(obj));
    1699               } catch(err) {
    1700                 results = null;
    1701                 //ZOO.Console.error(err);
    1702               }
    1703               break;
    1704             case "FeatureCollection":
    1705               for(var i=0, len=obj.features.length; i<len; ++i) {
    1706                 try {
    1707                   results.push(this.parseFeature(obj.features[i]));
    1708                 } catch(err) {
    1709                   results = null;
    1710                   //ZOO.Console.error(err);
    1711                 }
    1712               }
    1713               break;
    1714             default:
    1715               try {
    1716                 var geom = this.parseGeometry(obj);
    1717                 results.push(new ZOO.Feature(geom));
    1718               } catch(err) {
    1719                 results = null;
    1720                 //ZOO.Console.error(err);
    1721               }
    1722           }
    1723           break;
    1724       }
    1725     }
    1726     return results;
    1727   },
    1728   /**
    1729    * Method: isValidType
    1730    * Check if a GeoJSON object is a valid representative of the given type.
    1731    *
    1732    * Returns:
    1733    * {Boolean} The object is valid GeoJSON object of the given type.
    1734    */
    1735   isValidType: function(obj, type) {
    1736     var valid = false;
    1737     switch(type) {
    1738       case "Geometry":
    1739         if(ZOO.indexOf(
    1740               ["Point", "MultiPoint", "LineString", "MultiLineString",
    1741               "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
    1742               obj.type) == -1) {
    1743           // unsupported geometry type
    1744           //ZOO.Console.error("Unsupported geometry type: " +obj.type);
    1745         } else {
    1746           valid = true;
    1747         }
    1748         break;
    1749       case "FeatureCollection":
    1750         // allow for any type to be converted to a feature collection
    1751         valid = true;
    1752         break;
    1753       default:
    1754         // for Feature types must match
    1755         if(obj.type == type) {
    1756           valid = true;
    1757         } else {
    1758           //ZOO.Console.error("Cannot convert types from " +obj.type + " to " + type);
    1759         }
    1760     }
    1761     return valid;
    1762   },
    1763   /**
    1764    * Method: parseFeature
    1765    * Convert a feature object from GeoJSON into an
    1766    *     <ZOO.Feature>.
    1767    *
    1768    * Parameters:
    1769    * obj - {Object} An object created from a GeoJSON object
    1770    *
    1771    * Returns:
    1772    * {<ZOO.Feature>} A feature.
    1773    */
    1774   parseFeature: function(obj) {
    1775     var feature, geometry, attributes, bbox;
    1776     attributes = (obj.properties) ? obj.properties : {};
    1777     bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
    1778     try {
    1779       geometry = this.parseGeometry(obj.geometry);
    1780     } catch(err) {
    1781       // deal with bad geometries
    1782       throw err;
    1783     }
    1784     feature = new ZOO.Feature(geometry, attributes);
    1785     if(bbox)
    1786       feature.bounds = ZOO.Bounds.fromArray(bbox);
    1787     if(obj.id)
    1788       feature.fid = obj.id;
    1789     return feature;
    1790   },
    1791   /**
    1792    * Method: parseGeometry
    1793    * Convert a geometry object from GeoJSON into an <ZOO.Geometry>.
    1794    *
    1795    * Parameters:
    1796    * obj - {Object} An object created from a GeoJSON object
    1797    *
    1798    * Returns:
    1799    * {<ZOO.Geometry>} A geometry.
    1800    */
    1801   parseGeometry: function(obj) {
    1802     if (obj == null)
    1803       return null;
    1804     var geometry, collection = false;
    1805     if(obj.type == "GeometryCollection") {
    1806       if(!(obj.geometries instanceof Array)) {
    1807         throw "GeometryCollection must have geometries array: " + obj;
    1808       }
    1809       var numGeom = obj.geometries.length;
    1810       var components = new Array(numGeom);
    1811       for(var i=0; i<numGeom; ++i) {
    1812         components[i] = this.parseGeometry.apply(
    1813             this, [obj.geometries[i]]
    1814             );
    1815       }
    1816       geometry = new ZOO.Geometry.Collection(components);
    1817       collection = true;
    1818     } else {
    1819       if(!(obj.coordinates instanceof Array)) {
    1820         throw "Geometry must have coordinates array: " + obj;
    1821       }
    1822       if(!this.parseCoords[obj.type.toLowerCase()]) {
    1823         throw "Unsupported geometry type: " + obj.type;
    1824       }
    1825       try {
    1826         geometry = this.parseCoords[obj.type.toLowerCase()].apply(
    1827             this, [obj.coordinates]
    1828             );
    1829       } catch(err) {
    1830         // deal with bad coordinates
    1831         throw err;
    1832       }
    1833     }
    1834         // We don't reproject collections because the children are reprojected
    1835         // for us when they are created.
    1836     if (this.internalProjection && this.externalProjection && !collection) {
    1837       geometry.transform(this.externalProjection,
    1838           this.internalProjection);
    1839     }                       
    1840     return geometry;
    1841   },
    1842   /**
    1843    * Property: parseCoords
    1844    * Object with properties corresponding to the GeoJSON geometry types.
    1845    *     Property values are functions that do the actual parsing.
    1846    */
    1847   parseCoords: {
    1848     /**
    1849      * Method: parseCoords.point
    1850      * Convert a coordinate array from GeoJSON into an
    1851      *     <ZOO.Geometry.Point>.
    1852      *
    1853      * Parameters:
    1854      * array - {Object} The coordinates array from the GeoJSON fragment.
    1855      *
    1856      * Returns:
    1857      * {<ZOO.Geometry.Point>} A geometry.
    1858      */
    1859     "point": function(array) {
    1860       if(array.length != 2) {
    1861         throw "Only 2D points are supported: " + array;
    1862       }
    1863       return new ZOO.Geometry.Point(array[0], array[1]);
    1864     },
    1865     /**
    1866      * Method: parseCoords.multipoint
    1867      * Convert a coordinate array from GeoJSON into an
    1868      *     <ZOO.Geometry.MultiPoint>.
    1869      *
    1870      * Parameters:
    1871      * array - {Object} The coordinates array from the GeoJSON fragment.
    1872      *
    1873      * Returns:
    1874      * {<ZOO.Geometry.MultiPoint>} A geometry.
    1875      */
    1876     "multipoint": function(array) {
    1877       var points = [];
    1878       var p = null;
    1879       for(var i=0, len=array.length; i<len; ++i) {
    1880         try {
    1881           p = this.parseCoords["point"].apply(this, [array[i]]);
    1882         } catch(err) {
    1883           throw err;
    1884         }
    1885         points.push(p);
    1886       }
    1887       return new ZOO.Geometry.MultiPoint(points);
    1888     },
    1889     /**
    1890      * Method: parseCoords.linestring
    1891      * Convert a coordinate array from GeoJSON into an
    1892      *     <ZOO.Geometry.LineString>.
    1893      *
    1894      * Parameters:
    1895      * array - {Object} The coordinates array from the GeoJSON fragment.
    1896      *
    1897      * Returns:
    1898      * {<ZOO.Geometry.LineString>} A geometry.
    1899      */
    1900     "linestring": function(array) {
    1901       var points = [];
    1902       var p = null;
    1903       for(var i=0, len=array.length; i<len; ++i) {
    1904         try {
    1905           p = this.parseCoords["point"].apply(this, [array[i]]);
    1906         } catch(err) {
    1907           throw err;
    1908         }
    1909         points.push(p);
    1910       }
    1911       return new ZOO.Geometry.LineString(points);
    1912     },
    1913     /**
    1914      * Method: parseCoords.multilinestring
    1915      * Convert a coordinate array from GeoJSON into an
    1916      *     <ZOO.Geometry.MultiLineString>.
    1917      *
    1918      * Parameters:
    1919      * array - {Object} The coordinates array from the GeoJSON fragment.
    1920      *
    1921      * Returns:
    1922      * {<ZOO.Geometry.MultiLineString>} A geometry.
    1923      */
    1924     "multilinestring": function(array) {
    1925       var lines = [];
    1926       var l = null;
    1927       for(var i=0, len=array.length; i<len; ++i) {
    1928         try {
    1929           l = this.parseCoords["linestring"].apply(this, [array[i]]);
    1930         } catch(err) {
    1931           throw err;
    1932         }
    1933         lines.push(l);
    1934       }
    1935       return new ZOO.Geometry.MultiLineString(lines);
    1936     },
    1937     /**
    1938      * Method: parseCoords.polygon
    1939      * Convert a coordinate array from GeoJSON into an
    1940      *     <ZOO.Geometry.Polygon>.
    1941      *
    1942      * Parameters:
    1943      * array - {Object} The coordinates array from the GeoJSON fragment.
    1944      *
    1945      * Returns:
    1946      * {<ZOO.Geometry.Polygon>} A geometry.
    1947      */
    1948     "polygon": function(array) {
    1949       var rings = [];
    1950       var r, l;
    1951       for(var i=0, len=array.length; i<len; ++i) {
    1952         try {
    1953           l = this.parseCoords["linestring"].apply(this, [array[i]]);
    1954         } catch(err) {
    1955           throw err;
    1956         }
    1957         r = new ZOO.Geometry.LinearRing(l.components);
    1958         rings.push(r);
    1959       }
    1960       return new ZOO.Geometry.Polygon(rings);
    1961     },
    1962     /**
    1963      * Method: parseCoords.multipolygon
    1964      * Convert a coordinate array from GeoJSON into an
    1965      *     <ZOO.Geometry.MultiPolygon>.
    1966      *
    1967      * Parameters:
    1968      * array - {Object} The coordinates array from the GeoJSON fragment.
    1969      *
    1970      * Returns:
    1971      * {<ZOO.Geometry.MultiPolygon>} A geometry.
    1972      */
    1973     "multipolygon": function(array) {
    1974       var polys = [];
    1975       var p = null;
    1976       for(var i=0, len=array.length; i<len; ++i) {
    1977         try {
    1978           p = this.parseCoords["polygon"].apply(this, [array[i]]);
    1979         } catch(err) {
    1980           throw err;
    1981         }
    1982         polys.push(p);
    1983       }
    1984       return new ZOO.Geometry.MultiPolygon(polys);
    1985     },
    1986     /**
    1987      * Method: parseCoords.box
    1988      * Convert a coordinate array from GeoJSON into an
    1989      *     <ZOO.Geometry.Polygon>.
    1990      *
    1991      * Parameters:
    1992      * array - {Object} The coordinates array from the GeoJSON fragment.
    1993      *
    1994      * Returns:
    1995      * {<ZOO.Geometry.Polygon>} A geometry.
    1996      */
    1997     "box": function(array) {
    1998       if(array.length != 2) {
    1999         throw "GeoJSON box coordinates must have 2 elements";
    2000       }
    2001       return new ZOO.Geometry.Polygon([
    2002           new ZOO.Geometry.LinearRing([
    2003             new ZOO.Geometry.Point(array[0][0], array[0][1]),
    2004             new ZOO.Geometry.Point(array[1][0], array[0][1]),
    2005             new ZOO.Geometry.Point(array[1][0], array[1][1]),
    2006             new ZOO.Geometry.Point(array[0][0], array[1][1]),
    2007             new Z0O.Geometry.Point(array[0][0], array[0][1])
    2008           ])
    2009       ]);
    2010     }
    2011   },
    2012   /**
    2013    * Method: write
    2014    * Serialize a feature, geometry, array of features into a GeoJSON string.
    2015    *
    2016    * Parameters:
    2017    * obj - {Object} An <ZOO.Feature>, <ZOO.Geometry>,
    2018    *     or an array of features.
    2019    * pretty - {Boolean} Structure the output with newlines and indentation.
    2020    *     Default is false.
    2021    *
    2022    * Returns:
    2023    * {String} The GeoJSON string representation of the input geometry,
    2024    *     features, or array of features.
    2025    */
    2026   write: function(obj, pretty) {
    2027     var geojson = {
    2028       "type": null
    2029     };
    2030     if(obj instanceof Array) {
    2031       geojson.type = "FeatureCollection";
    2032       var numFeatures = obj.length;
    2033       geojson.features = new Array(numFeatures);
    2034       for(var i=0; i<numFeatures; ++i) {
    2035         var element = obj[i];
    2036         if(!element instanceof ZOO.Feature) {
    2037           var msg = "FeatureCollection only supports collections " +
    2038             "of features: " + element;
    2039           throw msg;
    2040         }
    2041         geojson.features[i] = this.extract.feature.apply(this, [element]);
    2042       }
    2043     } else if (obj.CLASS_NAME.indexOf("ZOO.Geometry") == 0) {
    2044       geojson = this.extract.geometry.apply(this, [obj]);
    2045     } else if (obj instanceof ZOO.Feature) {
    2046       geojson = this.extract.feature.apply(this, [obj]);
    2047       /*
    2048       if(obj.layer && obj.layer.projection) {
    2049         geojson.crs = this.createCRSObject(obj);
    2050       }
    2051       */
    2052     }
    2053     return ZOO.Format.JSON.prototype.write.apply(this,
    2054                                                  [geojson, pretty]);
    2055   },
    2056   /**
    2057    * Method: createCRSObject
    2058    * Create the CRS object for an object.
    2059    *
    2060    * Parameters:
    2061    * object - {<ZOO.Feature>}
    2062    *
    2063    * Returns:
    2064    * {Object} An object which can be assigned to the crs property
    2065    * of a GeoJSON object.
    2066    */
    2067   createCRSObject: function(object) {
    2068     //var proj = object.layer.projection.toString();
    2069     var proj = object.projection.toString();
    2070     var crs = {};
    2071     if (proj.match(/epsg:/i)) {
    2072       var code = parseInt(proj.substring(proj.indexOf(":") + 1));
    2073       if (code == 4326) {
    2074         crs = {
    2075           "type": "OGC",
    2076           "properties": {
    2077             "urn": "urn:ogc:def:crs:OGC:1.3:CRS84"
    2078           }
    2079         };
    2080       } else {   
    2081         crs = {
    2082           "type": "EPSG",
    2083           "properties": {
    2084             "code": code
    2085           }
    2086         };
    2087       }   
    2088     }
    2089     return crs;
    2090   },
    2091   /**
    2092    * Property: extract
    2093    * Object with properties corresponding to the GeoJSON types.
    2094    *     Property values are functions that do the actual value extraction.
    2095    */
    2096   extract: {
    2097     /**
    2098      * Method: extract.feature
    2099      * Return a partial GeoJSON object representing a single feature.
    2100      *
    2101      * Parameters:
    2102      * feature - {<ZOO.Feature>}
    2103      *
    2104      * Returns:
    2105      * {Object} An object representing the point.
    2106      */
    2107     'feature': function(feature) {
    2108       var geom = this.extract.geometry.apply(this, [feature.geometry]);
    2109       return {
    2110         "type": "Feature",
    2111         "id": feature.fid == null ? feature.id : feature.fid,
    2112         "properties": feature.attributes,
    2113         "geometry": geom
    2114       };
    2115     },
    2116     /**
    2117      * Method: extract.geometry
    2118      * Return a GeoJSON object representing a single geometry.
    2119      *
    2120      * Parameters:
    2121      * geometry - {<ZOO.Geometry>}
    2122      *
    2123      * Returns:
    2124      * {Object} An object representing the geometry.
    2125      */
    2126     'geometry': function(geometry) {
    2127       if (geometry == null)
    2128         return null;
    2129       if (this.internalProjection && this.externalProjection) {
    2130         geometry = geometry.clone();
    2131         geometry.transform(this.internalProjection,
    2132             this.externalProjection);
    2133       }                       
    2134       var geometryType = geometry.CLASS_NAME.split('.')[2];
    2135       var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
    2136       var json;
    2137       if(geometryType == "Collection")
    2138         json = {
    2139           "type": "GeometryCollection",
    2140           "geometries": data
    2141         };
    2142       else
    2143         json = {
    2144           "type": geometryType,
    2145           "coordinates": data
    2146         };
    2147       return json;
    2148     },
    2149     /**
    2150      * Method: extract.point
    2151      * Return an array of coordinates from a point.
    2152      *
    2153      * Parameters:
    2154      * point - {<ZOO.Geometry.Point>}
    2155      *
    2156      * Returns:
    2157      * {Array} An array of coordinates representing the point.
    2158      */
    2159     'point': function(point) {
    2160       return [point.x, point.y];
    2161     },
    2162     /**
    2163      * Method: extract.multipoint
    2164      * Return an array of coordinates from a multipoint.
    2165      *
    2166      * Parameters:
    2167      * multipoint - {<ZOO.Geometry.MultiPoint>}
    2168      *
    2169      * Returns:
    2170      * {Array} An array of point coordinate arrays representing
    2171      *     the multipoint.
    2172      */
    2173     'multipoint': function(multipoint) {
    2174       var array = [];
    2175       for(var i=0, len=multipoint.components.length; i<len; ++i) {
    2176         array.push(this.extract.point.apply(this, [multipoint.components[i]]));
    2177       }
    2178       return array;
    2179     },
    2180     /**
    2181      * Method: extract.linestring
    2182      * Return an array of coordinate arrays from a linestring.
    2183      *
    2184      * Parameters:
    2185      * linestring - {<ZOO.Geometry.LineString>}
    2186      *
    2187      * Returns:
    2188      * {Array} An array of coordinate arrays representing
    2189      *     the linestring.
    2190      */
    2191     'linestring': function(linestring) {
    2192       var array = [];
    2193       for(var i=0, len=linestring.components.length; i<len; ++i) {
    2194         array.push(this.extract.point.apply(this, [linestring.components[i]]));
    2195       }
    2196       return array;
    2197     },
    2198     /**
    2199      * Method: extract.multilinestring
    2200      * Return an array of linestring arrays from a linestring.
    2201      *
    2202      * Parameters:
    2203      * multilinestring - {<ZOO.Geometry.MultiLineString>}
    2204      *
    2205      * Returns:
    2206      * {Array} An array of linestring arrays representing
    2207      *     the multilinestring.
    2208      */
    2209     'multilinestring': function(multilinestring) {
    2210       var array = [];
    2211       for(var i=0, len=multilinestring.components.length; i<len; ++i) {
    2212         array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
    2213       }
    2214       return array;
    2215     },
    2216     /**
    2217      * Method: extract.polygon
    2218      * Return an array of linear ring arrays from a polygon.
    2219      *
    2220      * Parameters:
    2221      * polygon - {<ZOO.Geometry.Polygon>}
    2222      *
    2223      * Returns:
    2224      * {Array} An array of linear ring arrays representing the polygon.
    2225      */
    2226     'polygon': function(polygon) {
    2227       var array = [];
    2228       for(var i=0, len=polygon.components.length; i<len; ++i) {
    2229         array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
    2230       }
    2231       return array;
    2232     },
    2233     /**
    2234      * Method: extract.multipolygon
    2235      * Return an array of polygon arrays from a multipolygon.
    2236      *
    2237      * Parameters:
    2238      * multipolygon - {<ZOO.Geometry.MultiPolygon>}
    2239      *
    2240      * Returns:
    2241      * {Array} An array of polygon arrays representing
    2242      *     the multipolygon
    2243      */
    2244     'multipolygon': function(multipolygon) {
    2245       var array = [];
    2246       for(var i=0, len=multipolygon.components.length; i<len; ++i) {
    2247         array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
    2248       }
    2249       return array;
    2250     },
    2251     /**
    2252      * Method: extract.collection
    2253      * Return an array of geometries from a geometry collection.
    2254      *
    2255      * Parameters:
    2256      * collection - {<ZOO.Geometry.Collection>}
    2257      *
    2258      * Returns:
    2259      * {Array} An array of geometry objects representing the geometry
    2260      *     collection.
    2261      */
    2262     'collection': function(collection) {
    2263       var len = collection.components.length;
    2264       var array = new Array(len);
    2265       for(var i=0; i<len; ++i) {
    2266         array[i] = this.extract.geometry.apply(
    2267             this, [collection.components[i]]
    2268             );
    2269       }
    2270       return array;
    2271     }
    2272   },
    2273   CLASS_NAME: 'ZOO.Format.GeoJSON'
    2274 });
    2275 /**
    2276  * Class: ZOO.Format.KML
    2277  * Read/Write KML. Create a new instance with the <ZOO.Format.KML>
    2278  *     constructor.
    2279  *
    2280  * Inherits from:
    2281  *  - <ZOO.Format>
    2282  */
    2283 ZOO.Format.KML = ZOO.Class(ZOO.Format, {
    2284   /**
    2285    * Property: kmlns
    2286    * {String} KML Namespace to use. Defaults to 2.2 namespace.
    2287    */
    2288   kmlns: "http://www.opengis.net/kml/2.2",
    2289   /**
    2290    * Property: foldersName
    2291    * {String} Name of the folders.  Default is "ZOO export".
    2292    *          If set to null, no name element will be created.
    2293    */
    2294   foldersName: "ZOO export",
    2295   /**
    2296    * Property: foldersDesc
    2297    * {String} Description of the folders. Default is "Exported on [date]."
    2298    *          If set to null, no description element will be created.
    2299    */
    2300   foldersDesc: "Created on " + new Date(),
    2301   /**
    2302    * Property: placemarksDesc
    2303    * {String} Name of the placemarks.  Default is "No description available".
    2304    */
    2305   placemarksDesc: "No description available",
    2306   /**
    2307    * Property: extractAttributes
    2308    * {Boolean} Extract attributes from KML.  Default is true.
    2309    *           Extracting styleUrls requires this to be set to true
    2310    */
    2311   extractAttributes: true,
    2312   /**
    2313    * Constructor: ZOO.Format.KML
    2314    * Create a new parser for KML.
    2315    *
    2316    * Parameters:
    2317    * options - {Object} An optional object whose properties will be set on
    2318    *     this instance.
    2319    */
    2320   initialize: function(options) {
    2321     // compile regular expressions once instead of every time they are used
    2322     this.regExes = {
    2323            trimSpace: (/^\s*|\s*$/g),
    2324            removeSpace: (/\s*/g),
    2325            splitSpace: (/\s+/),
    2326            trimComma: (/\s*,\s*/g),
    2327            kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
    2328            kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
    2329            straightBracket: (/\$\[(.*?)\]/g)
    2330     };
    2331     // KML coordinates are always in longlat WGS84
    2332     this.externalProjection = new ZOO.Projection("EPSG:4326");
    2333     ZOO.Format.prototype.initialize.apply(this, [options]);
    2334   },
    2335   /**
    2336    * APIMethod: read
    2337    * Read data from a string, and return a list of features.
    2338    *
    2339    * Parameters:
    2340    * data    - {String} data to read/parse.
    2341    *
    2342    * Returns:
    2343    * {Array(<ZOO.Feature>)} List of features.
    2344    */
    2345   read: function(data) {
    2346     this.features = [];
    2347     data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
    2348     data = new XML(data);
    2349     var placemarks = data..*::Placemark;
    2350     this.parseFeatures(placemarks);
    2351     return this.features;
    2352   },
    2353   /**
    2354    * Method: parseFeatures
    2355    * Loop through all Placemark nodes and parse them.
    2356    * Will create a list of features
    2357    *
    2358    * Parameters:
    2359    * nodes    - {Array} of {E4XElement} data to read/parse.
    2360    * options  - {Object} Hash of options
    2361    *
    2362    */
    2363   parseFeatures: function(nodes) {
    2364     var features = new Array(nodes.length());
    2365     for(var i=0, len=nodes.length(); i<len; i++) {
    2366       var featureNode = nodes[i];
    2367       var feature = this.parseFeature.apply(this,[featureNode]) ;
    2368       features[i] = feature;
    2369     }
    2370     this.features = this.features.concat(features);
    2371   },
    2372   /**
    2373    * Method: parseFeature
    2374    * This function is the core of the KML parsing code in ZOO.
    2375    *     It creates the geometries that are then attached to the returned
    2376    *     feature, and calls parseAttributes() to get attribute data out.
    2377    *
    2378    * Parameters:
    2379    * node - {E4XElement}
    2380    *
    2381    * Returns:
    2382    * {<ZOO.Feature>} A vector feature.
    2383    */
    2384   parseFeature: function(node) {
    2385     // only accept one geometry per feature - look for highest "order"
    2386     var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
    2387     var type, nodeList, geometry, parser;
    2388     for(var i=0, len=order.length; i<len; ++i) {
    2389       type = order[i];
    2390       nodeList = node.descendants(QName(null,type));
    2391       if (nodeList.length()> 0) {
    2392         var parser = this.parseGeometry[type.toLowerCase()];
    2393         if(parser) {
    2394           geometry = parser.apply(this, [nodeList[0]]);
    2395           if (this.internalProjection && this.externalProjection) {
    2396             geometry.transform(this.externalProjection,
    2397                                this.internalProjection);
    2398           }                       
    2399         }
    2400         // stop looking for different geometry types
    2401         break;
    2402       }
    2403     }
    2404     // construct feature (optionally with attributes)
    2405     var attributes;
    2406     if(this.extractAttributes) {
    2407       attributes = this.parseAttributes(node);
    2408     }
    2409     var feature = new ZOO.Feature(geometry, attributes);
    2410     var fid = node.@id || node.@name;
    2411     if(fid != null)
    2412       feature.fid = fid;
    2413     return feature;
    2414   },
    2415   /**
    2416    * Property: parseGeometry
    2417    * Properties of this object are the functions that parse geometries based
    2418    *     on their type.
    2419    */
    2420   parseGeometry: {
    2421     /**
    2422      * Method: parseGeometry.point
    2423      * Given a KML node representing a point geometry, create a ZOO
    2424      *     point geometry.
    2425      *
    2426      * Parameters:
    2427      * node - {E4XElement} A KML Point node.
    2428      *
    2429      * Returns:
    2430      * {<ZOO.Geometry.Point>} A point geometry.
    2431      */
    2432     'point': function(node) {
    2433       var coordString = node.*::coordinates.toString();
    2434       coordString = coordString.replace(this.regExes.removeSpace, "");
    2435       coords = coordString.split(",");
    2436       var point = null;
    2437       if(coords.length > 1) {
    2438         // preserve third dimension
    2439         if(coords.length == 2) {
    2440           coords[2] = null;
    2441         }
    2442         point = new ZOO.Geometry.Point(coords[0], coords[1], coords[2]);
    2443       }
    2444       return point;
    2445     },
    2446     /**
    2447      * Method: parseGeometry.linestring
    2448      * Given a KML node representing a linestring geometry, create a
    2449      *     ZOO linestring geometry.
    2450      *
    2451      * Parameters:
    2452      * node - {E4XElement} A KML LineString node.
    2453      *
    2454      * Returns:
    2455      * {<ZOO.Geometry.LineString>} A linestring geometry.
    2456      */
    2457     'linestring': function(node, ring) {
    2458       var line = null;
    2459       var coordString = node.*::coordinates.toString();
    2460       coordString = coordString.replace(this.regExes.trimSpace,
    2461           "");
    2462       coordString = coordString.replace(this.regExes.trimComma,
    2463           ",");
    2464       var pointList = coordString.split(this.regExes.splitSpace);
    2465       var numPoints = pointList.length;
    2466       var points = new Array(numPoints);
    2467       var coords, numCoords;
    2468       for(var i=0; i<numPoints; ++i) {
    2469         coords = pointList[i].split(",");
    2470         numCoords = coords.length;
    2471         if(numCoords > 1) {
    2472           if(coords.length == 2) {
    2473             coords[2] = null;
    2474           }
    2475           points[i] = new ZOO.Geometry.Point(coords[0],
    2476                                              coords[1],
    2477                                              coords[2]);
    2478         }
    2479       }
    2480       if(numPoints) {
    2481         if(ring) {
    2482           line = new ZOO.Geometry.LinearRing(points);
    2483         } else {
    2484           line = new ZOO.Geometry.LineString(points);
    2485         }
    2486       } else {
    2487         throw "Bad LineString coordinates: " + coordString;
    2488       }
    2489       return line;
    2490     },
    2491     /**
    2492      * Method: parseGeometry.polygon
    2493      * Given a KML node representing a polygon geometry, create a
    2494      *     ZOO polygon geometry.
    2495      *
    2496      * Parameters:
    2497      * node - {E4XElement} A KML Polygon node.
    2498      *
    2499      * Returns:
    2500      * {<ZOO.Geometry.Polygon>} A polygon geometry.
    2501      */
    2502     'polygon': function(node) {
    2503       var nodeList = node..*::LinearRing;
    2504       var numRings = nodeList.length();
    2505       var components = new Array(numRings);
    2506       if(numRings > 0) {
    2507         // this assumes exterior ring first, inner rings after
    2508         var ring;
    2509         for(var i=0, len=nodeList.length(); i<len; ++i) {
    2510           ring = this.parseGeometry.linestring.apply(this,
    2511                                                      [nodeList[i], true]);
    2512           if(ring) {
    2513             components[i] = ring;
    2514           } else {
    2515             throw "Bad LinearRing geometry: " + i;
    2516           }
    2517         }
    2518       }
    2519       return new ZOO.Geometry.Polygon(components);
    2520     },
    2521     /**
    2522      * Method: parseGeometry.multigeometry
    2523      * Given a KML node representing a multigeometry, create a
    2524      *     ZOO geometry collection.
    2525      *
    2526      * Parameters:
    2527      * node - {E4XElement} A KML MultiGeometry node.
    2528      *
    2529      * Returns:
    2530      * {<ZOO.Geometry.Collection>} A geometry collection.
    2531      */
    2532     'multigeometry': function(node) {
    2533       var child, parser;
    2534       var parts = [];
    2535       var children = node.*::*;
    2536       for(var i=0, len=children.length(); i<len; ++i ) {
    2537         child = children[i];
    2538         var type = child.localName();
    2539         var parser = this.parseGeometry[type.toLowerCase()];
    2540         if(parser) {
    2541           parts.push(parser.apply(this, [child]));
    2542         }
    2543       }
    2544       return new ZOO.Geometry.Collection(parts);
    2545     }
    2546   },
    2547   /**
    2548    * Method: parseAttributes
    2549    *
    2550    * Parameters:
    2551    * node - {E4XElement}
    2552    *
    2553    * Returns:
    2554    * {Object} An attributes object.
    2555    */
    2556   parseAttributes: function(node) {
    2557     var attributes = {};
    2558     var edNodes = node.*::ExtendedData;
    2559     if (edNodes.length() > 0) {
    2560       attributes = this.parseExtendedData(edNodes[0])
    2561     }
    2562     var child, grandchildren;
    2563     var children = node.*::*;
    2564     for(var i=0, len=children.length(); i<len; ++i) {
    2565       child = children[i];
    2566       grandchildren = child..*::*;
    2567       if(grandchildren.length() == 1) {
    2568         var name = child.localName();
    2569         var value = child.toString();
    2570         if (value) {
    2571           value = value.replace(this.regExes.trimSpace, "");
    2572           attributes[name] = value;
    2573         }
    2574       }
    2575     }
    2576     return attributes;
    2577   },
    2578   /**
    2579    * Method: parseExtendedData
    2580    * Parse ExtendedData from KML. Limited support for schemas/datatypes.
    2581    *     See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
    2582    *     for more information on extendeddata.
    2583    *
    2584    * Parameters:
    2585    * node - {E4XElement}
    2586    *
    2587    * Returns:
    2588    * {Object} An attributes object.
    2589    */
    2590   parseExtendedData: function(node) {
    2591     var attributes = {};
    2592     var dataNodes = node.*::Data;
    2593     for (var i = 0, len = dataNodes.length(); i < len; i++) {
    2594       var data = dataNodes[i];
    2595       var key = data.@name;
    2596       var ed = {};
    2597       var valueNode = data.*::value;
    2598       if (valueNode.length() > 0)
    2599         ed['value'] = valueNode[0].toString();
    2600       var nameNode = data.*::displayName;
    2601       if (nameNode.length() > 0)
    2602         ed['displayName'] = valueNode[0].toString();
    2603       attributes[key] = ed;
    2604     }
    2605     return attributes;
    2606   },
    2607   /**
    2608    * Method: write
    2609    * Accept Feature Collection, and return a string.
    2610    *
    2611    * Parameters:
    2612    * features - {Array(<ZOO.Feature>} An array of features.
    2613    *
    2614    * Returns:
    2615    * {String} A KML string.
    2616    */
    2617   write: function(features) {
    2618     if(!(features instanceof Array))
    2619       features = [features];
    2620     var kml = new XML('<kml xmlns="'+this.kmlns+'"></kml>');
    2621     var folder = kml.Document.Folder;
    2622     folder.name = this.foldersName;
    2623     folder.description = this.foldersDesc;
    2624     for(var i=0, len=features.length; i<len; ++i) {
    2625       folder.Placemark[i] = this.createPlacemark(features[i]);
    2626     }
    2627     return kml.toXMLString();
    2628   },
    2629   /**
    2630    * Method: createPlacemark
    2631    * Creates and returns a KML placemark node representing the given feature.
    2632    *
    2633    * Parameters:
    2634    * feature - {<ZOO.Feature>}
    2635    *
    2636    * Returns:
    2637    * {E4XElement}
    2638    */
    2639   createPlacemark: function(feature) {
    2640     var placemark = new XML('<Placemark xmlns="'+this.kmlns+'"></Placemark>');
    2641     placemark.name = (feature.attributes.name) ?
    2642                     feature.attributes.name : feature.id;
    2643     placemark.description = (feature.attributes.description) ?
    2644                              feature.attributes.description : this.placemarksDesc;
    2645     if(feature.fid != null)
    2646       placemark.@id = feature.fid;
    2647     placemark.*[2] = this.buildGeometryNode(feature.geometry);
    2648     return placemark;
    2649   },
    2650   /**
    2651    * Method: buildGeometryNode
    2652    * Builds and returns a KML geometry node with the given geometry.
    2653    *
    2654    * Parameters:
    2655    * geometry - {<ZOO.Geometry>}
    2656    *
    2657    * Returns:
    2658    * {E4XElement}
    2659    */
    2660   buildGeometryNode: function(geometry) {
    2661     if (this.internalProjection && this.externalProjection) {
    2662       geometry = geometry.clone();
    2663       geometry.transform(this.internalProjection,
    2664                          this.externalProjection);
    2665     }
    2666     var className = geometry.CLASS_NAME;
    2667     var type = className.substring(className.lastIndexOf(".") + 1);
    2668     var builder = this.buildGeometry[type.toLowerCase()];
    2669     var node = null;
    2670     if(builder) {
    2671       node = builder.apply(this, [geometry]);
    2672     }
    2673     return node;
    2674   },
    2675   /**
    2676    * Property: buildGeometry
    2677    * Object containing methods to do the actual geometry node building
    2678    *     based on geometry type.
    2679    */
    2680   buildGeometry: {
    2681     /**
    2682      * Method: buildGeometry.point
    2683      * Given a ZOO point geometry, create a KML point.
    2684      *
    2685      * Parameters:
    2686      * geometry - {<ZOO.Geometry.Point>} A point geometry.
    2687      *
    2688      * Returns:
    2689      * {E4XElement} A KML point node.
    2690      */
    2691     'point': function(geometry) {
    2692       var kml = new XML('<Point xmlns="'+this.kmlns+'"></Point>');
    2693       kml.coordinates = this.buildCoordinatesNode(geometry);
    2694       return kml;
    2695     },
    2696     /**
    2697      * Method: buildGeometry.multipoint
    2698      * Given a ZOO multipoint geometry, create a KML
    2699      *     GeometryCollection.
    2700      *
    2701      * Parameters:
    2702      * geometry - {<ZOO.Geometry.MultiPoint>} A multipoint geometry.
    2703      *
    2704      * Returns:
    2705      * {E4XElement} A KML GeometryCollection node.
    2706      */
    2707     'multipoint': function(geometry) {
    2708       return this.buildGeometry.collection.apply(this, [geometry]);
    2709     },
    2710     /**
    2711      * Method: buildGeometry.linestring
    2712      * Given a ZOO linestring geometry, create a KML linestring.
    2713      *
    2714      * Parameters:
    2715      * geometry - {<ZOO.Geometry.LineString>} A linestring geometry.
    2716      *
    2717      * Returns:
    2718      * {E4XElement} A KML linestring node.
    2719      */
    2720     'linestring': function(geometry) {
    2721       var kml = new XML('<LineString xmlns="'+this.kmlns+'"></LineString>');
    2722       kml.coordinates = this.buildCoordinatesNode(geometry);
    2723       return kml;
    2724     },
    2725     /**
    2726      * Method: buildGeometry.multilinestring
    2727      * Given a ZOO multilinestring geometry, create a KML
    2728      *     GeometryCollection.
    2729      *
    2730      * Parameters:
    2731      * geometry - {<ZOO.Geometry.MultiLineString>} A multilinestring geometry.
    2732      *
    2733      * Returns:
    2734      * {E4XElement} A KML GeometryCollection node.
    2735      */
    2736     'multilinestring': function(geometry) {
    2737       return this.buildGeometry.collection.apply(this, [geometry]);
    2738     },
    2739     /**
    2740      * Method: buildGeometry.linearring
    2741      * Given a ZOO linearring geometry, create a KML linearring.
    2742      *
    2743      * Parameters:
    2744      * geometry - {<ZOO.Geometry.LinearRing>} A linearring geometry.
    2745      *
    2746      * Returns:
    2747      * {E4XElement} A KML linearring node.
    2748      */
    2749     'linearring': function(geometry) {
    2750       var kml = new XML('<LinearRing xmlns="'+this.kmlns+'"></LinearRing>');
    2751       kml.coordinates = this.buildCoordinatesNode(geometry);
    2752       return kml;
    2753     },
    2754     /**
    2755      * Method: buildGeometry.polygon
    2756      * Given a ZOO polygon geometry, create a KML polygon.
    2757      *
    2758      * Parameters:
    2759      * geometry - {<ZOO.Geometry.Polygon>} A polygon geometry.
    2760      *
    2761      * Returns:
    2762      * {E4XElement} A KML polygon node.
    2763      */
    2764     'polygon': function(geometry) {
    2765       var kml = new XML('<Polygon xmlns="'+this.kmlns+'"></Polygon>');
    2766       var rings = geometry.components;
    2767       var ringMember, ringGeom, type;
    2768       for(var i=0, len=rings.length; i<len; ++i) {
    2769         type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
    2770         ringMember = new XML('<'+type+' xmlns="'+this.kmlns+'"></'+type+'>');
    2771         ringMember.LinearRing = this.buildGeometry.linearring.apply(this,[rings[i]]);
    2772         kml.*[i] = ringMember;
    2773       }
    2774       return kml;
    2775     },
    2776     /**
    2777      * Method: buildGeometry.multipolygon
    2778      * Given a ZOO multipolygon geometry, create a KML
    2779      *     GeometryCollection.
    2780      *
    2781      * Parameters:
    2782      * geometry - {<ZOO.Geometry.Point>} A multipolygon geometry.
    2783      *
    2784      * Returns:
    2785      * {E4XElement} A KML GeometryCollection node.
    2786      */
    2787     'multipolygon': function(geometry) {
    2788       return this.buildGeometry.collection.apply(this, [geometry]);
    2789     },
    2790     /**
    2791      * Method: buildGeometry.collection
    2792      * Given a ZOO geometry collection, create a KML MultiGeometry.
    2793      *
    2794      * Parameters:
    2795      * geometry - {<ZOO.Geometry.Collection>} A geometry collection.
    2796      *
    2797      * Returns:
    2798      * {E4XElement} A KML MultiGeometry node.
    2799      */
    2800     'collection': function(geometry) {
    2801       var kml = new XML('<MultiGeometry xmlns="'+this.kmlns+'"></MultiGeometry>');
    2802       var child;
    2803       for(var i=0, len=geometry.components.length; i<len; ++i) {
    2804         kml.*[i] = this.buildGeometryNode.apply(this,[geometry.components[i]]);
    2805       }
    2806       return kml;
    2807     }
    2808   },
    2809   /**
    2810    * Method: buildCoordinatesNode
    2811    * Builds and returns the KML coordinates node with the given geometry
    2812    *     <coordinates>...</coordinates>
    2813    *
    2814    * Parameters:
    2815    * geometry - {<ZOO.Geometry>}
    2816    *
    2817    * Return:
    2818    * {E4XElement}
    2819    */
    2820   buildCoordinatesNode: function(geometry) {
    2821     var cooridnates = new XML('<coordinates xmlns="'+this.kmlns+'"></coordinates>');
    2822     var points = geometry.components;
    2823     if(points) {
    2824       // LineString or LinearRing
    2825       var point;
    2826       var numPoints = points.length;
    2827       var parts = new Array(numPoints);
    2828       for(var i=0; i<numPoints; ++i) {
    2829         point = points[i];
    2830         parts[i] = point.x + "," + point.y;
    2831       }
    2832       coordinates = parts.join(" ");
    2833     } else {
    2834       // Point
    2835       coordinates = geometry.x + "," + geometry.y;
    2836     }
    2837     return coordinates;
    2838   },
    2839   CLASS_NAME: 'ZOO.Format.KML'
    2840 });
    2841 /**
    2842  * Class: ZOO.Format.GML
    2843  * Read/Write GML. Create a new instance with the <ZOO.Format.GML>
    2844  *     constructor.  Supports the GML simple features profile.
    2845  *
    2846  * Inherits from:
    2847  *  - <ZOO.Format>
    2848  */
    2849 ZOO.Format.GML = ZOO.Class(ZOO.Format, {
    2850   /**
    2851    * Property: schemaLocation
    2852    * {String} Schema location for a particular minor version.
    2853    */
    2854   schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
    2855   /**
    2856    * Property: namespaces
    2857    * {Object} Mapping of namespace aliases to namespace URIs.
    2858    */
    2859   namespaces: {
    2860     ogr: "http://ogr.maptools.org/",
    2861     gml: "http://www.opengis.net/gml",
    2862     xlink: "http://www.w3.org/1999/xlink",
    2863     xsi: "http://www.w3.org/2001/XMLSchema-instance",
    2864     wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
    2865   },
    2866   /**
    2867    * Property: defaultPrefix
    2868    */
    2869   defaultPrefix: 'ogr',
    2870   /**
    2871    * Property: collectionName
    2872    * {String} Name of featureCollection element.
    2873    */
    2874   collectionName: "FeatureCollection",
    2875   /*
    2876    * Property: featureName
    2877    * {String} Element name for features. Default is "sql_statement".
    2878    */
    2879   featureName: "sql_statement",
    2880   /**
    2881    * Property: geometryName
    2882    * {String} Name of geometry element.  Defaults to "geometryProperty".
    2883    */
    2884   geometryName: "geometryProperty",
    2885   /**
    2886    * Property: xy
    2887    * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
    2888    * Changing is not recommended, a new Format should be instantiated.
    2889    */
    2890   xy: true,
    2891   /**
    2892    * Constructor: ZOO.Format.GML
    2893    * Create a new parser for GML.
    2894    *
    2895    * Parameters:
    2896    * options - {Object} An optional object whose properties will be set on
    2897    *     this instance.
    2898    */
    2899   initialize: function(options) {
    2900     // compile regular expressions once instead of every time they are used
    2901     this.regExes = {
    2902       trimSpace: (/^\s*|\s*$/g),
    2903       removeSpace: (/\s*/g),
    2904       splitSpace: (/\s+/),
    2905       trimComma: (/\s*,\s*/g)
    2906     };
    2907     ZOO.Format.prototype.initialize.apply(this, [options]);
    2908   },
    2909   /**
    2910    * Method: read
    2911    * Read data from a string, and return a list of features.
    2912    *
    2913    * Parameters:
    2914    * data - {String} data to read/parse.
    2915    *
    2916    * Returns:
    2917    * {Array(<ZOO.Feature>)} An array of features.
    2918    */
    2919   read: function(data) {
    2920     this.features = [];
    2921     data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
    2922     data = new XML(data);
    2923 
    2924     var gmlns = Namespace(this.namespaces['gml']);
    2925     var featureNodes = data..gmlns::featureMember;
    2926     var features = [];
    2927     for(var i=0,len=featureNodes.length(); i<len; i++) {
    2928       var feature = this.parseFeature(featureNodes[i]);
    2929       if(feature) {
    2930         features.push(feature);
    2931       }
    2932     }
    2933     return features;
    2934   },
    2935   /**
    2936    * Method: parseFeature
    2937    * This function is the core of the GML parsing code in ZOO.
    2938    *    It creates the geometries that are then attached to the returned
    2939    *    feature, and calls parseAttributes() to get attribute data out.
    2940    *   
    2941    * Parameters:
    2942    * node - {E4XElement} A GML feature node.
    2943    */
    2944   parseFeature: function(node) {
    2945     // only accept one geometry per feature - look for highest "order"
    2946     var gmlns = Namespace(this.namespaces['gml']);
    2947     var order = ["MultiPolygon", "Polygon",
    2948                  "MultiLineString", "LineString",
    2949                  "MultiPoint", "Point", "Envelope", "Box"];
    2950     var type, nodeList, geometry, parser;
    2951     for(var i=0; i<order.length; ++i) {
    2952       type = order[i];
    2953       nodeList = node.descendants(QName(gmlns,type));
    2954       if (nodeList.length() > 0) {
    2955         var parser = this.parseGeometry[type.toLowerCase()];
    2956         if(parser) {
    2957           geometry = parser.apply(this, [nodeList[0]]);
    2958           if (this.internalProjection && this.externalProjection) {
    2959             geometry.transform(this.externalProjection,
    2960                                this.internalProjection);
    2961           }                       
    2962         }
    2963         // stop looking for different geometry types
    2964         break;
    2965       }
    2966     }
    2967     var attributes;
    2968     if(this.extractAttributes) {
    2969       attributes = this.parseAttributes(node);
    2970     }
    2971     var feature = new ZOO.Feature(geometry, attributes);
    2972     return feature;
    2973   },
    2974   /**
    2975    * Property: parseGeometry
    2976    * Properties of this object are the functions that parse geometries based
    2977    *     on their type.
    2978    */
    2979   parseGeometry: {
    2980     /**
    2981      * Method: parseGeometry.point
    2982      * Given a GML node representing a point geometry, create a ZOO
    2983      *     point geometry.
    2984      *
    2985      * Parameters:
    2986      * node - {E4XElement} A GML node.
    2987      *
    2988      * Returns:
    2989      * {<ZOO.Geometry.Point>} A point geometry.
    2990      */
    2991     'point': function(node) {
    2992       /**
    2993        * Three coordinate variations to consider:
    2994        * 1) <gml:pos>x y z</gml:pos>
    2995        * 2) <gml:coordinates>x, y, z</gml:coordinates>
    2996        * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
    2997        */
    2998       var nodeList, coordString;
    2999       var coords = [];
    3000       // look for <gml:pos>
    3001       var nodeList = node..*::pos;
    3002       if(nodeList.length() > 0) {
    3003         coordString = nodeList[0].toString();
    3004         coordString = coordString.replace(this.regExes.trimSpace, "");
    3005         coords = coordString.split(this.regExes.splitSpace);
    3006       }
    3007       // look for <gml:coordinates>
    3008       if(coords.length == 0) {
    3009         nodeList = node..*::coordinates;
    3010         if(nodeList.length() > 0) {
    3011           coordString = nodeList[0].toString();
    3012           coordString = coordString.replace(this.regExes.removeSpace,"");
    3013           coords = coordString.split(",");
    3014         }
    3015       }
    3016       // look for <gml:coord>
    3017       if(coords.length == 0) {
    3018         nodeList = node..*::coord;
    3019         if(nodeList.length() > 0) {
    3020           var xList = nodeList[0].*::X;
    3021           var yList = nodeList[0].*::Y;
    3022           if(xList.length() > 0 && yList.length() > 0)
    3023             coords = [xList[0].toString(),
    3024                       yList[0].toString()];
    3025         }
    3026       }
    3027       // preserve third dimension
    3028       if(coords.length == 2)
    3029         coords[2] = null;
    3030       if (this.xy)
    3031         return new ZOO.Geometry.Point(coords[0],coords[1],coords[2]);
    3032       else
    3033         return new ZOO.Geometry.Point(coords[1],coords[0],coords[2]);
    3034     },
    3035     /**
    3036      * Method: parseGeometry.multipoint
    3037      * Given a GML node representing a multipoint geometry, create a
    3038      *     ZOO multipoint geometry.
    3039      *
    3040      * Parameters:
    3041      * node - {E4XElement} A GML node.
    3042      *
    3043      * Returns:
    3044      * {<ZOO.Geometry.MultiPoint>} A multipoint geometry.
    3045      */
    3046     'multipoint': function(node) {
    3047       var nodeList = node..*::Point;
    3048       var components = [];
    3049       if(nodeList.length() > 0) {
    3050         var point;
    3051         for(var i=0, len=nodeList.length(); i<len; ++i) {
    3052           point = this.parseGeometry.point.apply(this, [nodeList[i]]);
    3053           if(point)
    3054             components.push(point);
    3055         }
    3056       }
    3057       return new ZOO.Geometry.MultiPoint(components);
    3058     },
    3059     /**
    3060      * Method: parseGeometry.linestring
    3061      * Given a GML node representing a linestring geometry, create a
    3062      *     ZOO linestring geometry.
    3063      *
    3064      * Parameters:
    3065      * node - {E4XElement} A GML node.
    3066      *
    3067      * Returns:
    3068      * {<ZOO.Geometry.LineString>} A linestring geometry.
    3069      */
    3070     'linestring': function(node, ring) {
    3071       /**
    3072        * Two coordinate variations to consider:
    3073        * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
    3074        * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
    3075        */
    3076       var nodeList, coordString;
    3077       var coords = [];
    3078       var points = [];
    3079       // look for <gml:posList>
    3080       nodeList = node..*::posList;
    3081       if(nodeList.length() > 0) {
    3082         coordString = nodeList[0].toString();
    3083         coordString = coordString.replace(this.regExes.trimSpace, "");
    3084         coords = coordString.split(this.regExes.splitSpace);
    3085         var dim = parseInt(nodeList[0].@dimension);
    3086         var j, x, y, z;
    3087         for(var i=0; i<coords.length/dim; ++i) {
    3088           j = i * dim;
    3089           x = coords[j];
    3090           y = coords[j+1];
    3091           z = (dim == 2) ? null : coords[j+2];
    3092           if (this.xy)
    3093             points.push(new ZOO.Geometry.Point(x, y, z));
    3094           else
    3095             points.push(new Z0O.Geometry.Point(y, x, z));
    3096         }
    3097       }
    3098       // look for <gml:coordinates>
    3099       if(coords.length == 0) {
    3100         nodeList = node..*::coordinates;
    3101         if(nodeList.length() > 0) {
    3102           coordString = nodeList[0].toString();
    3103           coordString = coordString.replace(this.regExes.trimSpace,"");
    3104           coordString = coordString.replace(this.regExes.trimComma,",");
    3105           var pointList = coordString.split(this.regExes.splitSpace);
    3106           for(var i=0; i<pointList.length; ++i) {
    3107             coords = pointList[i].split(",");
    3108             if(coords.length == 2)
    3109               coords[2] = null;
    3110             if (this.xy)
    3111               points.push(new ZOO.Geometry.Point(coords[0],coords[1],coords[2]));
    3112             else
    3113               points.push(new ZOO.Geometry.Point(coords[1],coords[0],coords[2]));
    3114           }
    3115         }
    3116       }
    3117       var line = null;
    3118       if(points.length != 0) {
    3119         if(ring)
    3120           line = new ZOO.Geometry.LinearRing(points);
    3121         else
    3122           line = new ZOO.Geometry.LineString(points);
    3123       }
    3124       return line;
    3125     },
    3126     /**
    3127      * Method: parseGeometry.multilinestring
    3128      * Given a GML node representing a multilinestring geometry, create a
    3129      *     ZOO multilinestring geometry.
    3130      *
    3131      * Parameters:
    3132      * node - {E4XElement} A GML node.
    3133      *
    3134      * Returns:
    3135      * {<ZOO.Geometry.MultiLineString>} A multilinestring geometry.
    3136      */
    3137     'multilinestring': function(node) {
    3138       var nodeList = node..*::LineString;
    3139       var components = [];
    3140       if(nodeList.length() > 0) {
    3141         var line;
    3142         for(var i=0, len=nodeList.length(); i<len; ++i) {
    3143           line = this.parseGeometry.linestring.apply(this, [nodeList[i]]);
    3144           if(point)
    3145             components.push(point);
    3146         }
    3147       }
    3148       return new ZOO.Geometry.MultiLineString(components);
    3149     },
    3150     /**
    3151      * Method: parseGeometry.polygon
    3152      * Given a GML node representing a polygon geometry, create a
    3153      *     ZOO polygon geometry.
    3154      *
    3155      * Parameters:
    3156      * node - {E4XElement} A GML node.
    3157      *
    3158      * Returns:
    3159      * {<ZOO.Geometry.Polygon>} A polygon geometry.
    3160      */
    3161     'polygon': function(node) {
    3162       nodeList = node..*::LinearRing;
    3163       var components = [];
    3164       if(nodeList.length() > 0) {
    3165         // this assumes exterior ring first, inner rings after
    3166         var ring;
    3167         for(var i=0, len = nodeList.length(); i<len; ++i) {
    3168           ring = this.parseGeometry.linestring.apply(this,[nodeList[i], true]);
    3169           if(ring)
    3170             components.push(ring);
    3171         }
    3172       }
    3173       return new ZOO.Geometry.Polygon(components);
    3174     },
    3175     /**
    3176      * Method: parseGeometry.multipolygon
    3177      * Given a GML node representing a multipolygon geometry, create a
    3178      *     ZOO multipolygon geometry.
    3179      *
    3180      * Parameters:
    3181      * node - {E4XElement} A GML node.
    3182      *
    3183      * Returns:
    3184      * {<ZOO.Geometry.MultiPolygon>} A multipolygon geometry.
    3185      */
    3186     'multipolygon': function(node) {
    3187       var nodeList = node..*::Polygon;
    3188       var components = [];
    3189       if(nodeList.length() > 0) {
    3190         var polygon;
    3191         for(var i=0, len=nodeList.length(); i<len; ++i) {
    3192           polygon = this.parseGeometry.polygon.apply(this, [nodeList[i]]);
    3193           if(polygon)
    3194             components.push(polygon);
    3195         }
    3196       }
    3197       return new ZOO.Geometry.MultiPolygon(components);
    3198     },
    3199     /**
    3200      * Method: parseGeometry.polygon
    3201      * Given a GML node representing an envelope, create a
    3202      *     ZOO polygon geometry.
    3203      *
    3204      * Parameters:
    3205      * node - {E4XElement} A GML node.
    3206      *
    3207      * Returns:
    3208      * {<ZOO.Geometry.Polygon>} A polygon geometry.
    3209      */
    3210     'envelope': function(node) {
    3211       var components = [];
    3212       var coordString;
    3213       var envelope;
    3214       var lpoint = node..*::lowerCorner;
    3215       if (lpoint.length() > 0) {
    3216         var coords = [];
    3217         if(lpoint.length() > 0) {
    3218           coordString = lpoint[0].toString();
    3219           coordString = coordString.replace(this.regExes.trimSpace, "");
    3220           coords = coordString.split(this.regExes.splitSpace);
    3221         }
    3222         if(coords.length == 2)
    3223           coords[2] = null;
    3224         if (this.xy)
    3225           var lowerPoint = new ZOO.Geometry.Point(coords[0], coords[1],coords[2]);
    3226         else
    3227           var lowerPoint = new ZOO.Geometry.Point(coords[1], coords[0],coords[2]);
    3228       }
    3229       var upoint = node..*::upperCorner;
    3230       if (upoint.length() > 0) {
    3231         var coords = [];
    3232         if(upoint.length > 0) {
    3233           coordString = upoint[0].toString();
    3234           coordString = coordString.replace(this.regExes.trimSpace, "");
    3235           coords = coordString.split(this.regExes.splitSpace);
    3236         }
    3237         if(coords.length == 2)
    3238           coords[2] = null;
    3239         if (this.xy)
    3240           var upperPoint = new ZOO.Geometry.Point(coords[0], coords[1],coords[2]);
    3241         else
    3242           var upperPoint = new ZOO.Geometry.Point(coords[1], coords[0],coords[2]);
    3243       }
    3244       if (lowerPoint && upperPoint) {
    3245         components.push(new ZOO.Geometry.Point(lowerPoint.x, lowerPoint.y));
    3246         components.push(new ZOO.Geometry.Point(upperPoint.x, lowerPoint.y));
    3247         components.push(new ZOO.Geometry.Point(upperPoint.x, upperPoint.y));
    3248         components.push(new ZOO.Geometry.Point(lowerPoint.x, upperPoint.y));
    3249         components.push(new ZOO.Geometry.Point(lowerPoint.x, lowerPoint.y));
    3250         var ring = new ZOO.Geometry.LinearRing(components);
    3251         envelope = new ZOO.Geometry.Polygon([ring]);
    3252       }
    3253       return envelope;
    3254     }
    3255   },
    3256   /**
    3257    * Method: parseAttributes
    3258    *
    3259    * Parameters:
    3260    * node - {<E4XElement>}
    3261    *
    3262    * Returns:
    3263    * {Object} An attributes object.
    3264    */
    3265   parseAttributes: function(node) {
    3266     var attributes = {};
    3267     // assume attributes are children of the first type 1 child
    3268     var childNode = node.*::*[0];
    3269     var child, grandchildren;
    3270     var children = childNode.*::*;
    3271     for(var i=0, len=children.length(); i<len; ++i) {
    3272       child = children[i];
    3273       grandchildren = child..*::*;
    3274       if(grandchildren.length() == 1) {
    3275         var name = child.localName();
    3276         var value = child.toString();
    3277         if (value) {
    3278           value = value.replace(this.regExes.trimSpace, "");
    3279           attributes[name] = value;
    3280         } else
    3281           attributes[name] = null;
    3282       }
    3283     }
    3284     return attributes;
    3285   },
    3286   /**
    3287    * Method: write
    3288    * Generate a GML document string given a list of features.
    3289    *
    3290    * Parameters:
    3291    * features - {Array(<ZOO.Feature>)} List of features to
    3292    *     serialize into a string.
    3293    *
    3294    * Returns:
    3295    * {String} A string representing the GML document.
    3296    */
    3297   write: function(features) {
    3298     if(!(features instanceof Array)) {
    3299       features = [features];
    3300     }
    3301     var pfx = this.defaultPrefix;
    3302     var name = pfx+':'+this.collectionName;
    3303     var gml = new XML('<'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'" xmlns:gml="'+this.namespaces['gml']+'" xmlns:xsi="'+this.namespaces['xsi']+'" xsi:schemaLocation="'+this.schemaLocation+'"></'+name+'>');
    3304     for(var i=0; i<features.length; i++) {
    3305       gml.*::*[i] = this.createFeature(features[i]);
    3306     }
    3307     return gml.toXMLString();
    3308   },
    3309   /**
    3310    * Method: createFeature
    3311    * Accept an ZOO.Feature, and build a GML node for it.
    3312    *
    3313    * Parameters:
    3314    * feature - {<ZOO.Feature>} The feature to be built as GML.
    3315    *
    3316    * Returns:
    3317    * {E4XElement} A node reprensting the feature in GML.
    3318    */
    3319   createFeature: function(feature) {
    3320     var pfx = this.defaultPrefix;
    3321     var name = pfx+':'+this.featureName;
    3322     var fid = feature.fid || feature.id;
    3323     var gml = new XML('<gml:featureMember xmlns:gml="'+this.namespaces['gml']+'"><'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'" fid="'+fid+'"></'+name+'></gml:featureMember>');
    3324     var geometry = feature.geometry;
    3325     gml.*::*[0].*::* = this.buildGeometryNode(geometry);
    3326     for(var attr in feature.attributes) {
    3327       var attrNode = new XML('<'+pfx+':'+attr+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'">'+feature.attributes[attr]+'</'+pfx+':'+attr+'>');
    3328       gml.*::*[0].appendChild(attrNode);
    3329     }
    3330     return gml;
    3331   },
    3332   /**
    3333    * Method: buildGeometryNode
    3334    *
    3335    * Parameters:
    3336    * geometry - {<ZOO.Geometry>} The geometry to be built as GML.
    3337    *
    3338    * Returns:
    3339    * {E4XElement} A node reprensting the geometry in GML.
    3340    */
    3341   buildGeometryNode: function(geometry) {
    3342     if (this.externalProjection && this.internalProjection) {
    3343       geometry = geometry.clone();
    3344       geometry.transform(this.internalProjection,
    3345           this.externalProjection);
    3346     }   
    3347     var className = geometry.CLASS_NAME;
    3348     var type = className.substring(className.lastIndexOf(".") + 1);
    3349     var builder = this.buildGeometry[type.toLowerCase()];
    3350     var pfx = this.defaultPrefix;
    3351     var name = pfx+':'+this.geometryName;
    3352     var gml = new XML('<'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'"></'+name+'>');
    3353     if (builder)
    3354       gml.*::* = builder.apply(this, [geometry]);
    3355     return gml;
    3356   },
    3357   /**
    3358    * Property: buildGeometry
    3359    * Object containing methods to do the actual geometry node building
    3360    *     based on geometry type.
    3361    */
    3362   buildGeometry: {
    3363     /**
    3364      * Method: buildGeometry.point
    3365      * Given a ZOO point geometry, create a GML point.
    3366      *
    3367      * Parameters:
    3368      * geometry - {<ZOO.Geometry.Point>} A point geometry.
    3369      *
    3370      * Returns:
    3371      * {E4XElement} A GML point node.
    3372      */
    3373     'point': function(geometry) {
    3374       var gml = new XML('<gml:Point xmlns:gml="'+this.namespaces['gml']+'"></gml:Point>');
    3375       gml.*::*[0] = this.buildCoordinatesNode(geometry);
    3376       return gml;
    3377     },
    3378     /**
    3379      * Method: buildGeometry.multipoint
    3380      * Given a ZOO multipoint geometry, create a GML multipoint.
    3381      *
    3382      * Parameters:
    3383      * geometry - {<ZOO.Geometry.MultiPoint>} A multipoint geometry.
    3384      *
    3385      * Returns:
    3386      * {E4XElement} A GML multipoint node.
    3387      */
    3388     'multipoint': function(geometry) {
    3389       var gml = new XML('<gml:MultiPoint xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiPoint>');
    3390       var points = geometry.components;
    3391       var pointMember;
    3392       for(var i=0; i<points.length; i++) {
    3393         pointMember = new XML('<gml:pointMember xmlns:gml="'+this.namespaces['gml']+'"></gml:pointMember>');
    3394         pointMember.*::* = this.buildGeometry.point.apply(this,[points[i]]);
    3395         gml.*::*[i] = pointMember;
    3396       }
    3397       return gml;           
    3398     },
    3399     /**
    3400      * Method: buildGeometry.linestring
    3401      * Given a ZOO linestring geometry, create a GML linestring.
    3402      *
    3403      * Parameters:
    3404      * geometry - {<ZOO.Geometry.LineString>} A linestring geometry.
    3405      *
    3406      * Returns:
    3407      * {E4XElement} A GML linestring node.
    3408      */
    3409     'linestring': function(geometry) {
    3410       var gml = new XML('<gml:LineString xmlns:gml="'+this.namespaces['gml']+'"></gml:LineString>');
    3411       gml.*::*[0] = this.buildCoordinatesNode(geometry);
    3412       return gml;
    3413     },
    3414     /**
    3415      * Method: buildGeometry.multilinestring
    3416      * Given a ZOO multilinestring geometry, create a GML
    3417      *     multilinestring.
    3418      *
    3419      * Parameters:
    3420      * geometry - {<ZOO.Geometry.MultiLineString>} A multilinestring
    3421      *     geometry.
    3422      *
    3423      * Returns:
    3424      * {E4XElement} A GML multilinestring node.
    3425      */
    3426     'multilinestring': function(geometry) {
    3427       var gml = new XML('<gml:MultiLineString xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiLineString>');
    3428       var lines = geometry.components;
    3429       var lineMember;
    3430       for(var i=0; i<lines.length; i++) {
    3431         lineMember = new XML('<gml:lineStringMember xmlns:gml="'+this.namespaces['gml']+'"></gml:lineStringMember>');
    3432         lineMember.*::* = this.buildGeometry.linestring.apply(this,[lines[i]]);
    3433         gml.*::*[i] = lineMember;
    3434       }
    3435       return gml;           
    3436     },
    3437     /**
    3438      * Method: buildGeometry.linearring
    3439      * Given a ZOO linearring geometry, create a GML linearring.
    3440      *
    3441      * Parameters:
    3442      * geometry - {<ZOO.Geometry.LinearRing>} A linearring geometry.
    3443      *
    3444      * Returns:
    3445      * {E4XElement} A GML linearring node.
    3446      */
    3447     'linearring': function(geometry) {
    3448       var gml = new XML('<gml:LinearRing xmlns:gml="'+this.namespaces['gml']+'"></gml:LinearRing>');
    3449       gml.*::*[0] = this.buildCoordinatesNode(geometry);
    3450       return gml;
    3451     },
    3452     /**
    3453      * Method: buildGeometry.polygon
    3454      * Given an ZOO polygon geometry, create a GML polygon.
    3455      *
    3456      * Parameters:
    3457      * geometry - {<ZOO.Geometry.Polygon>} A polygon geometry.
    3458      *
    3459      * Returns:
    3460      * {E4XElement} A GML polygon node.
    3461      */
    3462     'polygon': function(geometry) {
    3463       var gml = new XML('<gml:Polygon xmlns:gml="'+this.namespaces['gml']+'"></gml:Polygon>');
    3464       var rings = geometry.components;
    3465       var ringMember, type;
    3466       for(var i=0; i<rings.length; ++i) {
    3467         type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
    3468         var ringMember = new XML('<gml:'+type+' xmlns:gml="'+this.namespaces['gml']+'"></gml:'+type+'>');
    3469         ringMember.*::* = this.buildGeometry.linearring.apply(this,[rings[i]]);
    3470         gml.*::*[i] = ringMember;
    3471       }
    3472       return gml;
    3473     },
    3474     /**
    3475      * Method: buildGeometry.multipolygon
    3476      * Given a ZOO multipolygon geometry, create a GML multipolygon.
    3477      *
    3478      * Parameters:
    3479      * geometry - {<ZOO.Geometry.MultiPolygon>} A multipolygon
    3480      *     geometry.
    3481      *
    3482      * Returns:
    3483      * {E4XElement} A GML multipolygon node.
    3484      */
    3485     'multipolygon': function(geometry) {
    3486       var gml = new XML('<gml:MultiPolygon xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiPolygon>');
    3487       var polys = geometry.components;
    3488       var polyMember;
    3489       for(var i=0; i<polys.length; i++) {
    3490         polyMember = new XML('<gml:polygonMember xmlns:gml="'+this.namespaces['gml']+'"></gml:polygonMember>');
    3491         polyMember.*::* = this.buildGeometry.polygon.apply(this,[polys[i]]);
    3492         gml.*::*[i] = polyMember;
    3493       }
    3494       return gml;           
    3495     }
    3496   },
    3497   /**
    3498    * Method: buildCoordinatesNode
    3499    * builds the coordinates XmlNode
    3500    * (code)
    3501    * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
    3502    * (end)
    3503    * Parameters:
    3504    * geometry - {<ZOO.Geometry>}
    3505    *
    3506    * Returns:
    3507    * {E4XElement} created E4XElement
    3508    */
    3509   buildCoordinatesNode: function(geometry) {
    3510     var parts = [];
    3511     if(geometry instanceof ZOO.Bounds){
    3512       parts.push(geometry.left + "," + geometry.bottom);
    3513       parts.push(geometry.right + "," + geometry.top);
    3514     } else {
    3515       var points = (geometry.components) ? geometry.components : [geometry];
    3516       for(var i=0; i<points.length; i++) {
    3517         parts.push(points[i].x + "," + points[i].y);               
    3518       }           
    3519     }
    3520     return new XML('<gml:coordinates xmlns:gml="'+this.namespaces['gml']+'" decimal="." cs=", " ts=" ">'+parts.join(" ")+'</gml:coordinates>');
    3521   },
    3522   CLASS_NAME: 'ZOO.Format.GML'
    3523 });
    3524 /**
    3525  * Class: ZOO.Format.WPS
    3526  * Read/Write WPS. Create a new instance with the <ZOO.Format.WPS>
    3527  *     constructor. Supports only parseExecuteResponse.
    3528  *
    3529  * Inherits from:
    3530  *  - <ZOO.Format>
    3531  */
    3532 ZOO.Format.WPS = ZOO.Class(ZOO.Format, {
    3533   /**
    3534    * Property: schemaLocation
    3535    * {String} Schema location for a particular minor version.
    3536    */
    3537   schemaLocation: "http://www.opengis.net/wps/1.0.0/../wpsExecute_request.xsd",
    3538   /**
    3539    * Property: namespaces
    3540    * {Object} Mapping of namespace aliases to namespace URIs.
    3541    */
    3542   namespaces: {
    3543     ows: "http://www.opengis.net/ows/1.1",
    3544     wps: "http://www.opengis.net/wps/1.0.0",
    3545     xlink: "http://www.w3.org/1999/xlink",
    3546     xsi: "http://www.w3.org/2001/XMLSchema-instance",
    3547   },
    3548   /**
    3549    * Method: read
    3550    *
    3551    * Parameters:
    3552    * data - {String} A WPS xml document
    3553    *
    3554    * Returns:
    3555    * {Object} Execute response.
    3556    */
    3557   read:function(data) {
    3558     data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
    3559     data = new XML(data);
    3560     switch (data.localName()) {
    3561       case 'ExecuteResponse':
    3562         return this.parseExecuteResponse(data);
    3563       default:
    3564         return null;
    3565     }
    3566   },
    3567   /**
    3568    * Method: parseExecuteResponse
    3569    *
    3570    * Parameters:
    3571    * node - {E4XElement} A WPS ExecuteResponse document
    3572    *
    3573    * Returns:
    3574    * {Object} Execute response.
    3575    */
    3576   parseExecuteResponse: function(node) {
    3577     var outputs = node.*::ProcessOutputs.*::Output;
    3578     if (outputs.length() > 0) {
    3579       var data = outputs[0].*::Data.*::*[0];
    3580       var builder = this.parseData[data.localName().toLowerCase()];
    3581       if (builder)
    3582         return builder.apply(this,[data]);
    3583       else
    3584         return null;
    3585     } else
    3586       return null;
    3587   },
    3588   /**
    3589    * Property: parseData
    3590    * Object containing methods to analyse data response.
    3591    */
    3592   parseData: {
    3593     /**
    3594      * Method: parseData.complexdata
    3595      * Given an Object representing the WPS complex data response.
    3596      *
    3597      * Parameters:
    3598      * node - {E4XElement} A WPS node.
    3599      *
    3600      * Returns:
    3601      * {Object} A WPS complex data response.
    3602      */
    3603     'complexdata': function(node) {
    3604       var result = {value:node.toString()};
    3605       if (node.@mimeType.length()>0)
    3606         result.mimeType = node.@mimeType;
    3607       if (node.@encoding.length()>0)
    3608         result.encoding = node.@encoding;
    3609       if (node.@schema.length()>0)
    3610         result.schema = node.@schema;
    3611       return result;
    3612     },
    3613     /**
    3614      * Method: parseData.literaldata
    3615      * Given an Object representing the WPS literal data response.
    3616      *
    3617      * Parameters:
    3618      * node - {E4XElement} A WPS node.
    3619      *
    3620      * Returns:
    3621      * {Object} A WPS literal data response.
    3622      */
    3623     'literaldata': function(node) {
    3624       var result = {value:node.toString()};
    3625       if (node.@dataType.length()>0)
    3626         result.dataType = node.@dataType;
    3627       if (node.@uom.length()>0)
    3628         result.uom = node.@uom;
    3629       return result;
    3630     }
    3631   },
    3632   CLASS_NAME: 'ZOO.Format.WPS'
    3633 });
    3634 
    3635 /**
    3636  * Class: ZOO.Feature
    3637  * Vector features use the ZOO.Geometry classes as geometry description.
    3638  * They have an 'attributes' property, which is the data object
    3639  */
    3640 ZOO.Feature = ZOO.Class({
    3641   /**
    3642    * Property: fid
    3643    * {String}
    3644    */
    3645   fid: null,
    3646   /**
    3647    * Property: geometry
    3648    * {<ZOO.Geometry>}
    3649    */
    3650   geometry: null,
    3651   /**
    3652    * Property: attributes
    3653    * {Object} This object holds arbitrary properties that describe the
    3654    *     feature.
    3655    */
    3656   attributes: null,
    3657   /**
    3658    * Property: bounds
    3659    * {<ZOO.Bounds>} The box bounding that feature's geometry, that
    3660    *     property can be set by an <ZOO.Format> object when
    3661    *     deserializing the feature, so in most cases it represents an
    3662    *     information set by the server.
    3663    */
    3664   bounds: null,
    3665   /**
    3666    * Constructor: ZOO.Feature
    3667    * Create a vector feature.
    3668    *
    3669    * Parameters:
    3670    * geometry - {<ZOO.Geometry>} The geometry that this feature
    3671    *     represents.
    3672    * attributes - {Object} An optional object that will be mapped to the
    3673    *     <attributes> property.
    3674    */
    3675   initialize: function(geometry, attributes) {
    3676     this.geometry = geometry ? geometry : null;
    3677     this.attributes = {};
    3678     if (attributes)
    3679       this.attributes = ZOO.extend(this.attributes,attributes);
    3680   },
    3681   /**
    3682    * Method: destroy
    3683    * nullify references to prevent circular references and memory leaks
    3684    */
    3685   destroy: function() {
    3686     this.geometry = null;
    3687   },
    3688   /**
    3689    * Method: clone
    3690    * Create a clone of this vector feature.  Does not set any non-standard
    3691    *     properties.
    3692    *
    3693    * Returns:
    3694    * {<ZOO.Feature>} An exact clone of this vector feature.
    3695    */
    3696   clone: function () {
    3697     return new ZOO.Feature(this.geometry ? this.geometry.clone() : null,
    3698             this.attributes);
    3699   },
    3700   /**
    3701    * Method: move
    3702    * Moves the feature and redraws it at its new location
    3703    *
    3704    * Parameters:
    3705    * x - {Float}
    3706    * y - {Float}
    3707    */
    3708   move: function(x, y) {
    3709     if(!this.geometry.move)
    3710       return;
    3711 
    3712     this.geometry.move(x,y);
    3713     return this.geometry;
    3714   },
    3715   CLASS_NAME: 'ZOO.Feature'
    3716 });
    3717 
    3718 /**
    3719  * Class: ZOO.Geometry
    3720  * A Geometry is a description of a geographic object. Create an instance
    3721  * of this class with the <ZOO.Geometry> constructor. This is a base class,
    3722  * typical geometry types are described by subclasses of this class.
    3723  */
    3724 ZOO.Geometry = ZOO.Class({
    3725   /**
    3726    * Property: id
    3727    * {String} A unique identifier for this geometry.
    3728    */
    3729   id: null,
    3730   /**
    3731    * Property: parent
    3732    * {<ZOO.Geometry>}This is set when a Geometry is added as component
    3733    * of another geometry
    3734    */
    3735   parent: null,
    3736   /**
    3737    * Property: bounds
    3738    * {<ZOO.Bounds>} The bounds of this geometry
    3739    */
    3740   bounds: null,
    3741   /**
    3742    * Constructor: ZOO.Geometry
    3743    * Creates a geometry object. 
    3744    */
    3745   initialize: function() {
    3746     //generate unique id
    3747   },
    3748   /**
    3749    * Method: destroy
    3750    * Destroy this geometry.
    3751    */
    3752   destroy: function() {
    3753     this.id = null;
    3754     this.bounds = null;
    3755   },
    3756   /**
    3757    * Method: clone
    3758    * Create a clone of this geometry.  Does not set any non-standard
    3759    *     properties of the cloned geometry.
    3760    *
    3761    * Returns:
    3762    * {<ZOO.Geometry>} An exact clone of this geometry.
    3763    */
    3764   clone: function() {
    3765     return new ZOO.Geometry();
    3766   },
    3767   /**
    3768    * Method: extendBounds
    3769    * Extend the existing bounds to include the new bounds.
    3770    * If geometry's bounds is not yet set, then set a new Bounds.
    3771    *
    3772    * Parameters:
    3773    * newBounds - {<ZOO.Bounds>}
    3774    */
    3775   extendBounds: function(newBounds){
    3776     var bounds = this.getBounds();
    3777     if (!bounds)
    3778       this.setBounds(newBounds);
    3779     else
    3780       this.bounds.extend(newBounds);
    3781   },
    3782   /**
    3783    * Set the bounds for this Geometry.
    3784    *
    3785    * Parameters:
    3786    * bounds - {<ZOO.Bounds>}
    3787    */
    3788   setBounds: function(bounds) {
    3789     if (bounds)
    3790       this.bounds = bounds.clone();
    3791   },
    3792   /**
    3793    * Method: clearBounds
    3794    * Nullify this components bounds and that of its parent as well.
    3795    */
    3796   clearBounds: function() {
    3797     this.bounds = null;
    3798     if (this.parent)
    3799       this.parent.clearBounds();
    3800   },
    3801   /**
    3802    * Method: getBounds
    3803    * Get the bounds for this Geometry. If bounds is not set, it
    3804    * is calculated again, this makes queries faster.
    3805    *
    3806    * Returns:
    3807    * {<ZOO.Bounds>}
    3808    */
    3809   getBounds: function() {
    3810     if (this.bounds == null) {
    3811       this.calculateBounds();
    3812     }
    3813     return this.bounds;
    3814   },
    3815   /**
    3816    * Method: calculateBounds
    3817    * Recalculate the bounds for the geometry.
    3818    */
    3819   calculateBounds: function() {
    3820     // This should be overridden by subclasses.
    3821     return this.bounds;
    3822   },
    3823   distanceTo: function(geometry, options) {
    3824   },
    3825   getVertices: function(nodes) {
    3826   },
    3827   getLength: function() {
    3828     return 0.0;
    3829   },
    3830   getArea: function() {
    3831     return 0.0;
    3832   },
    3833   getCentroid: function() {
    3834     return null;
    3835   },
    3836   /**
    3837    * Method: toString
    3838    * Returns the Well-Known Text representation of a geometry
    3839    *
    3840    * Returns:
    3841    * {String} Well-Known Text
    3842    */
    3843   toString: function() {
    3844     return ZOO.Format.WKT.prototype.write(
    3845         new ZOO.Feature(this)
    3846     );
    3847   },
    3848   CLASS_NAME: 'ZOO.Geometry'
    3849 });
    3850 /**
    3851  * Function: OpenLayers.Geometry.fromWKT
    3852  * Generate a geometry given a Well-Known Text string.
    3853  *
    3854  * Parameters:
    3855  * wkt - {String} A string representing the geometry in Well-Known Text.
    3856  *
    3857  * Returns:
    3858  * {<ZOO.Geometry>} A geometry of the appropriate class.
    3859  */
    3860 ZOO.Geometry.fromWKT = function(wkt) {
    3861   var format = arguments.callee.format;
    3862   if(!format) {
    3863     format = new ZOO.Format.WKT();
    3864     arguments.callee.format = format;
    3865   }
    3866   var geom;
    3867   var result = format.read(wkt);
    3868   if(result instanceof ZOO.Feature) {
    3869     geom = result.geometry;
    3870   } else if(result instanceof Array) {
    3871     var len = result.length;
    3872     var components = new Array(len);
    3873     for(var i=0; i<len; ++i) {
    3874       components[i] = result[i].geometry;
    3875     }
    3876     geom = new ZOO.Geometry.Collection(components);
    3877   }
    3878   return geom;
    3879 };
    3880 ZOO.Geometry.segmentsIntersect = function(seg1, seg2, options) {
    3881   var point = options && options.point;
    3882   var tolerance = options && options.tolerance;
    3883   var intersection = false;
    3884   var x11_21 = seg1.x1 - seg2.x1;
    3885   var y11_21 = seg1.y1 - seg2.y1;
    3886   var x12_11 = seg1.x2 - seg1.x1;
    3887   var y12_11 = seg1.y2 - seg1.y1;
    3888   var y22_21 = seg2.y2 - seg2.y1;
    3889   var x22_21 = seg2.x2 - seg2.x1;
    3890   var d = (y22_21 * x12_11) - (x22_21 * y12_11);
    3891   var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
    3892   var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
    3893   if(d == 0) {
    3894     // parallel
    3895     if(n1 == 0 && n2 == 0) {
    3896       // coincident
    3897       intersection = true;
    3898     }
    3899   } else {
    3900     var along1 = n1 / d;
    3901     var along2 = n2 / d;
    3902     if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
    3903       // intersect
    3904       if(!point) {
    3905         intersection = true;
    3906       } else {
    3907         // calculate the intersection point
    3908         var x = seg1.x1 + (along1 * x12_11);
    3909         var y = seg1.y1 + (along1 * y12_11);
    3910         intersection = new ZOO.Geometry.Point(x, y);
    3911       }
    3912     }
    3913   }
    3914   if(tolerance) {
    3915     var dist;
    3916     if(intersection) {
    3917       if(point) {
    3918         var segs = [seg1, seg2];
    3919         var seg, x, y;
    3920         // check segment endpoints for proximity to intersection
    3921         // set intersection to first endpoint within the tolerance
    3922         outer: for(var i=0; i<2; ++i) {
    3923           seg = segs[i];
    3924           for(var j=1; j<3; ++j) {
    3925             x = seg["x" + j];
    3926             y = seg["y" + j];
    3927             dist = Math.sqrt(
    3928                 Math.pow(x - intersection.x, 2) +
    3929                 Math.pow(y - intersection.y, 2)
    3930             );
    3931             if(dist < tolerance) {
    3932               intersection.x = x;
    3933               intersection.y = y;
    3934               break outer;
    3935             }
    3936           }
    3937         }
    3938       }
    3939     } else {
    3940       // no calculated intersection, but segments could be within
    3941       // the tolerance of one another
    3942       var segs = [seg1, seg2];
    3943       var source, target, x, y, p, result;
    3944       // check segment endpoints for proximity to intersection
    3945       // set intersection to first endpoint within the tolerance
    3946       outer: for(var i=0; i<2; ++i) {
    3947         source = segs[i];
    3948         target = segs[(i+1)%2];
    3949         for(var j=1; j<3; ++j) {
    3950           p = {x: source["x"+j], y: source["y"+j]};
    3951           result = ZOO.Geometry.distanceToSegment(p, target);
    3952           if(result.distance < tolerance) {
    3953             if(point) {
    3954               intersection = new ZOO.Geometry.Point(p.x, p.y);
    3955             } else {
    3956               intersection = true;
    3957             }
    3958             break outer;
    3959           }
    3960         }
    3961       }
    3962     }
    3963   }
    3964   return intersection;
    3965 };
    3966 ZOO.Geometry.distanceToSegment = function(point, segment) {
    3967   var x0 = point.x;
    3968   var y0 = point.y;
    3969   var x1 = segment.x1;
    3970   var y1 = segment.y1;
    3971   var x2 = segment.x2;
    3972   var y2 = segment.y2;
    3973   var dx = x2 - x1;
    3974   var dy = y2 - y1;
    3975   var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
    3976                (Math.pow(dx, 2) + Math.pow(dy, 2));
    3977   var x, y;
    3978   if(along <= 0.0) {
    3979     x = x1;
    3980     y = y1;
    3981   } else if(along >= 1.0) {
    3982     x = x2;
    3983     y = y2;
    3984   } else {
    3985     x = x1 + along * dx;
    3986     y = y1 + along * dy;
    3987   }
    3988   return {
    3989     distance: Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)),
    3990     x: x, y: y
    3991   };
    3992 };
    3993 /**
    3994  * Class: OpenLayers.Geometry.Collection
    3995  * A Collection is exactly what it sounds like: A collection of different
    3996  * Geometries. These are stored in the local parameter <components> (which
    3997  * can be passed as a parameter to the constructor).
    3998  *
    3999  * As new geometries are added to the collection, they are NOT cloned.
    4000  * When removing geometries, they need to be specified by reference (ie you
    4001  * have to pass in the *exact* geometry to be removed).
    4002  *
    4003  * The <getArea> and <getLength> functions here merely iterate through
    4004  * the components, summing their respective areas and lengths.
    4005  *
    4006  * Create a new instance with the <ZOO.Geometry.Collection> constructor.
    4007  *
    4008  * Inerhits from:
    4009  *  - <ZOO.Geometry>
    4010  */
    4011 ZOO.Geometry.Collection = ZOO.Class(ZOO.Geometry, {
    4012   /**
    4013    * Property: components
    4014    * {Array(<ZOO.Geometry>)} The component parts of this geometry
    4015    */
    4016   components: null,
    4017   /**
    4018    * Property: componentTypes
    4019    * {Array(String)} An array of class names representing the types of
    4020    * components that the collection can include.  A null value means the
    4021    * component types are not restricted.
    4022    */
    4023   componentTypes: null,
    4024   /**
    4025    * Constructor: ZOO.Geometry.Collection
    4026    * Creates a Geometry Collection -- a list of geoms.
    4027    *
    4028    * Parameters:
    4029    * components - {Array(<ZOO.Geometry>)} Optional array of geometries
    4030    *
    4031    */
    4032   initialize: function (components) {
    4033     ZOO.Geometry.prototype.initialize.apply(this, arguments);
    4034     this.components = [];
    4035     if (components != null) {
    4036       this.addComponents(components);
    4037     }
    4038   },
    4039   /**
    4040    * Method: destroy
    4041    * Destroy this geometry.
    4042    */
    4043   destroy: function () {
    4044     this.components.length = 0;
    4045     this.components = null;
    4046   },
    4047   /**
    4048    * Method: clone
    4049    * Clone this geometry.
    4050    *
    4051    * Returns:
    4052    * {<ZOO.Geometry.Collection>} An exact clone of this collection
    4053    */
    4054   clone: function() {
    4055     var geometry = eval("new " + this.CLASS_NAME + "()");
    4056     for(var i=0, len=this.components.length; i<len; i++) {
    4057       geometry.addComponent(this.components[i].clone());
    4058     }
    4059     return geometry;
    4060   },
    4061   /**
    4062    * Method: getComponentsString
    4063    * Get a string representing the components for this collection
    4064    *
    4065    * Returns:
    4066    * {String} A string representation of the components of this geometry
    4067    */
    4068   getComponentsString: function(){
    4069     var strings = [];
    4070     for(var i=0, len=this.components.length; i<len; i++) {
    4071       strings.push(this.components[i].toShortString());
    4072     }
    4073     return strings.join(",");
    4074   },
    4075   /**
    4076    * Method: calculateBounds
    4077    * Recalculate the bounds by iterating through the components and
    4078    * calling calling extendBounds() on each item.
    4079    */
    4080   calculateBounds: function() {
    4081     this.bounds = null;
    4082     if ( this.components && this.components.length > 0) {
    4083       this.setBounds(this.components[0].getBounds());
    4084       for (var i=1, len=this.components.length; i<len; i++) {
    4085         this.extendBounds(this.components[i].getBounds());
    4086       }
    4087     }
    4088     return this.bounds
    4089   },
    4090   /**
    4091    * APIMethod: addComponents
    4092    * Add components to this geometry.
    4093    *
    4094    * Parameters:
    4095    * components - {Array(<ZOO.Geometry>)} An array of geometries to add
    4096    */
    4097   addComponents: function(components){
    4098     if(!(components instanceof Array))
    4099       components = [components];
    4100     for(var i=0, len=components.length; i<len; i++) {
    4101       this.addComponent(components[i]);
    4102     }
    4103   },
    4104   /**
    4105    * Method: addComponent
    4106    * Add a new component (geometry) to the collection.  If this.componentTypes
    4107    * is set, then the component class name must be in the componentTypes array.
    4108    *
    4109    * The bounds cache is reset.
    4110    *
    4111    * Parameters:
    4112    * component - {<ZOO.Geometry>} A geometry to add
    4113    * index - {int} Optional index into the array to insert the component
    4114    *
    4115    * Returns:
    4116    * {Boolean} The component geometry was successfully added
    4117    */
    4118   addComponent: function(component, index) {
    4119     var added = false;
    4120     if(component) {
    4121       if(this.componentTypes == null ||
    4122           (ZOO.indexOf(this.componentTypes,
    4123                        component.CLASS_NAME) > -1)) {
    4124         if(index != null && (index < this.components.length)) {
    4125           var components1 = this.components.slice(0, index);
    4126           var components2 = this.components.slice(index,
    4127                                                   this.components.length);
    4128           components1.push(component);
    4129           this.components = components1.concat(components2);
    4130         } else {
    4131           this.components.push(component);
    4132         }
    4133         component.parent = this;
    4134         this.clearBounds();
    4135         added = true;
    4136       }
    4137     }
    4138     return added;
    4139   },
    4140   /**
    4141    * Method: removeComponents
    4142    * Remove components from this geometry.
    4143    *
    4144    * Parameters:
    4145    * components - {Array(<ZOO.Geometry>)} The components to be removed
    4146    */
    4147   removeComponents: function(components) {
    4148     if(!(components instanceof Array))
    4149       components = [components];
    4150     for(var i=components.length-1; i>=0; --i) {
    4151       this.removeComponent(components[i]);
    4152     }
    4153   },
    4154   /**
    4155    * Method: removeComponent
    4156    * Remove a component from this geometry.
    4157    *
    4158    * Parameters:
    4159    * component - {<ZOO.Geometry>}
    4160    */
    4161   removeComponent: function(component) {     
    4162     ZOO.removeItem(this.components, component);
    4163     // clearBounds() so that it gets recalculated on the next call
    4164     // to this.getBounds();
    4165     this.clearBounds();
    4166   },
    4167   /**
    4168    * Method: getLength
    4169    * Calculate the length of this geometry
    4170    *
    4171    * Returns:
    4172    * {Float} The length of the geometry
    4173    */
    4174   getLength: function() {
    4175     var length = 0.0;
    4176     for (var i=0, len=this.components.length; i<len; i++) {
    4177       length += this.components[i].getLength();
    4178     }
    4179     return length;
    4180   },
    4181   /**
    4182    * APIMethod: getArea
    4183    * Calculate the area of this geometry. Note how this function is
    4184    * overridden in <ZOO.Geometry.Polygon>.
    4185    *
    4186    * Returns:
    4187    * {Float} The area of the collection by summing its parts
    4188    */
    4189   getArea: function() {
    4190     var area = 0.0;
    4191     for (var i=0, len=this.components.length; i<len; i++) {
    4192       area += this.components[i].getArea();
    4193     }
    4194     return area;
    4195   },
    4196   /**
    4197    * APIMethod: getGeodesicArea
    4198    * Calculate the approximate area of the polygon were it projected onto
    4199    *     the earth.
    4200    *
    4201    * Parameters:
    4202    * projection - {<ZOO.Projection>} The spatial reference system
    4203    *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
    4204    *     assumed.
    4205    *
    4206    * Reference:
    4207    * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
    4208    *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
    4209    *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
    4210    *
    4211    * Returns:
    4212    * {float} The approximate geodesic area of the geometry in square meters.
    4213    */
    4214   getGeodesicArea: function(projection) {
    4215     var area = 0.0;
    4216     for(var i=0, len=this.components.length; i<len; i++) {
    4217       area += this.components[i].getGeodesicArea(projection);
    4218     }
    4219     return area;
    4220   },
    4221   /**
    4222    * Method: getCentroid
    4223    *
    4224    * Returns:
    4225    * {<ZOO.Geometry.Point>} The centroid of the collection
    4226    */
    4227   getCentroid: function() {
    4228     return this.components.length && this.components[0].getCentroid();
    4229   },
    4230   /**
    4231    * Method: getGeodesicLength
    4232    * Calculate the approximate length of the geometry were it projected onto
    4233    *     the earth.
    4234    *
    4235    * projection - {<ZOO.Projection>} The spatial reference system
    4236    *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
    4237    *     assumed.
    4238    *
    4239    * Returns:
    4240    * {Float} The appoximate geodesic length of the geometry in meters.
    4241    */
    4242   getGeodesicLength: function(projection) {
    4243     var length = 0.0;
    4244     for(var i=0, len=this.components.length; i<len; i++) {
    4245       length += this.components[i].getGeodesicLength(projection);
    4246     }
    4247     return length;
    4248   },
    4249   /**
    4250    * Method: move
    4251    * Moves a geometry by the given displacement along positive x and y axes.
    4252    *     This modifies the position of the geometry and clears the cached
    4253    *     bounds.
    4254    *
    4255    * Parameters:
    4256    * x - {Float} Distance to move geometry in positive x direction.
    4257    * y - {Float} Distance to move geometry in positive y direction.
    4258    */
    4259   move: function(x, y) {
    4260     for(var i=0, len=this.components.length; i<len; i++) {
    4261       this.components[i].move(x, y);
    4262     }
    4263   },
    4264   /**
    4265    * Method: rotate
    4266    * Rotate a geometry around some origin
    4267    *
    4268    * Parameters:
    4269    * angle - {Float} Rotation angle in degrees (measured counterclockwise
    4270    *                 from the positive x-axis)
    4271    * origin - {<ZOO.Geometry.Point>} Center point for the rotation
    4272    */
    4273   rotate: function(angle, origin) {
    4274     for(var i=0, len=this.components.length; i<len; ++i) {
    4275       this.components[i].rotate(angle, origin);
    4276     }
    4277   },
    4278   /**
    4279    * Method: resize
    4280    * Resize a geometry relative to some origin.  Use this method to apply
    4281    *     a uniform scaling to a geometry.
    4282    *
    4283    * Parameters:
    4284    * scale - {Float} Factor by which to scale the geometry.  A scale of 2
    4285    *                 doubles the size of the geometry in each dimension
    4286    *                 (lines, for example, will be twice as long, and polygons
    4287    *                 will have four times the area).
    4288    * origin - {<ZOO.Geometry.Point>} Point of origin for resizing
    4289    * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
    4290    *
    4291    * Returns:
    4292    * {ZOO.Geometry} - The current geometry.
    4293    */
    4294   resize: function(scale, origin, ratio) {
    4295     for(var i=0; i<this.components.length; ++i) {
    4296       this.components[i].resize(scale, origin, ratio);
    4297     }
    4298     return this;
    4299   },
    4300   distanceTo: function(geometry, options) {
    4301     var edge = !(options && options.edge === false);
    4302     var details = edge && options && options.details;
    4303     var result, best;
    4304     var min = Number.POSITIVE_INFINITY;
    4305     for(var i=0, len=this.components.length; i<len; ++i) {
    4306       result = this.components[i].distanceTo(geometry, options);
    4307       distance = details ? result.distance : result;
    4308       if(distance < min) {
    4309         min = distance;
    4310         best = result;
    4311         if(min == 0)
    4312           break;
    4313       }
    4314     }
    4315     return best;
    4316   },
    4317   /**
    4318    * Method: equals
    4319    * Determine whether another geometry is equivalent to this one.  Geometries
    4320    *     are considered equivalent if all components have the same coordinates.
    4321    *
    4322    * Parameters:
    4323    * geom - {<ZOO.Geometry>} The geometry to test.
    4324    *
    4325    * Returns:
    4326    * {Boolean} The supplied geometry is equivalent to this geometry.
    4327    */
    4328   equals: function(geometry) {
    4329     var equivalent = true;
    4330     if(!geometry || !geometry.CLASS_NAME ||
    4331        (this.CLASS_NAME != geometry.CLASS_NAME))
    4332       equivalent = false;
    4333     else if(!(geometry.components instanceof Array) ||
    4334              (geometry.components.length != this.components.length))
    4335       equivalent = false;
    4336     else
    4337       for(var i=0, len=this.components.length; i<len; ++i) {
    4338         if(!this.components[i].equals(geometry.components[i])) {
    4339           equivalent = false;
    4340           break;
    4341         }
    4342       }
    4343     return equivalent;
    4344   },
    4345   /**
    4346    * Method: transform
    4347    * Reproject the components geometry from source to dest.
    4348    *
    4349    * Parameters:
    4350    * source - {<ZOO.Projection>}
    4351    * dest - {<ZOO.Projection>}
    4352    *
    4353    * Returns:
    4354    * {<ZOO.Geometry>}
    4355    */
    4356   transform: function(source, dest) {
    4357     if (source && dest) {
    4358       for (var i=0, len=this.components.length; i<len; i++) { 
    4359         var component = this.components[i];
    4360         component.transform(source, dest);
    4361       }
    4362       this.bounds = null;
    4363     }
    4364     return this;
    4365   },
    4366   /**
    4367    * Method: intersects
    4368    * Determine if the input geometry intersects this one.
    4369    *
    4370    * Parameters:
    4371    * geometry - {<ZOO.Geometry>} Any type of geometry.
    4372    *
    4373    * Returns:
    4374    * {Boolean} The input geometry intersects this one.
    4375    */
    4376   intersects: function(geometry) {
    4377     var intersect = false;
    4378     for(var i=0, len=this.components.length; i<len; ++ i) {
    4379       intersect = geometry.intersects(this.components[i]);
    4380       if(intersect)
    4381         break;
    4382     }
    4383     return intersect;
    4384   },
    4385   /**
    4386    * Method: getVertices
    4387    * Return a list of all points in this geometry.
    4388    *
    4389    * Parameters:
    4390    * nodes - {Boolean} For lines, only return vertices that are
    4391    *     endpoints.  If false, for lines, only vertices that are not
    4392    *     endpoints will be returned.  If not provided, all vertices will
    4393    *     be returned.
    4394    *
    4395    * Returns:
    4396    * {Array} A list of all vertices in the geometry.
    4397    */
    4398   getVertices: function(nodes) {
    4399     var vertices = [];
    4400     for(var i=0, len=this.components.length; i<len; ++i) {
    4401       Array.prototype.push.apply(
    4402           vertices, this.components[i].getVertices(nodes)
    4403           );
    4404     }
    4405     return vertices;
    4406   },
    4407   CLASS_NAME: 'ZOO.Geometry.Collection'
    4408 });
    4409 /**
    4410  * Class: ZOO.Geometry.Point
    4411  * Point geometry class.
    4412  *
    4413  * Inherits from:
    4414  *  - <ZOO.Geometry>
    4415  */
    4416 ZOO.Geometry.Point = ZOO.Class(ZOO.Geometry, {
    4417   /**
    4418    * Property: x
    4419    * {float}
    4420    */
    4421   x: null,
    4422   /**
    4423    * Property: y
    4424    * {float}
    4425    */
    4426   y: null,
    4427   /**
    4428    * Constructor: ZOO.Geometry.Point
    4429    * Construct a point geometry.
    4430    *
    4431    * Parameters:
    4432    * x - {float}
    4433    * y - {float}
    4434    *
    4435    */
    4436   initialize: function(x, y) {
    4437     ZOO.Geometry.prototype.initialize.apply(this, arguments);
    4438     this.x = parseFloat(x);
    4439     this.y = parseFloat(y);
    4440   },
    4441   /**
    4442    * Method: clone
    4443    *
    4444    * Returns:
    4445    * {<ZOO.Geometry.Point>} An exact clone of this ZOO.Geometry.Point
    4446    */
    4447   clone: function(obj) {
    4448     if (obj == null)
    4449       obj = new ZOO.Geometry.Point(this.x, this.y);
    4450     // catch any randomly tagged-on properties
    4451     // ZOO.Util.applyDefaults(obj, this);
    4452     return obj;
    4453   },
    4454   /**
    4455    * Method: calculateBounds
    4456    * Create a new Bounds based on the x/y
    4457    */
    4458   calculateBounds: function () {
    4459     this.bounds = new ZOO.Bounds(this.x, this.y,
    4460                                         this.x, this.y);
    4461   },
    4462   distanceTo: function(geometry, options) {
    4463     var edge = !(options && options.edge === false);
    4464     var details = edge && options && options.details;
    4465     var distance, x0, y0, x1, y1, result;
    4466     if(geometry instanceof ZOO.Geometry.Point) {
    4467       x0 = this.x;
    4468       y0 = this.y;
    4469       x1 = geometry.x;
    4470       y1 = geometry.y;
    4471       distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
    4472       result = !details ?
    4473         distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
    4474     } else {
    4475       result = geometry.distanceTo(this, options);
    4476       if(details) {
    4477         // switch coord order since this geom is target
    4478         result = {
    4479           x0: result.x1, y0: result.y1,
    4480           x1: result.x0, y1: result.y0,
    4481           distance: result.distance
    4482         };
    4483       }
    4484     }
    4485     return result;
    4486   },
    4487   /**
    4488    * Method: equals
    4489    * Determine whether another geometry is equivalent to this one.  Geometries
    4490    *     are considered equivalent if all components have the same coordinates.
    4491    *
    4492    * Parameters:
    4493    * geom - {<ZOO.Geometry.Point>} The geometry to test.
    4494    *
    4495    * Returns:
    4496    * {Boolean} The supplied geometry is equivalent to this geometry.
    4497    */
    4498   equals: function(geom) {
    4499     var equals = false;
    4500     if (geom != null)
    4501       equals = ((this.x == geom.x && this.y == geom.y) ||
    4502                 (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
    4503     return equals;
    4504   },
    4505   /**
    4506    * Method: toShortString
    4507    *
    4508    * Returns:
    4509    * {String} Shortened String representation of Point object.
    4510    *         (ex. <i>"5, 42"</i>)
    4511    */
    4512   toShortString: function() {
    4513     return (this.x + ", " + this.y);
    4514   },
    4515   /**
    4516    * Method: move
    4517    * Moves a geometry by the given displacement along positive x and y axes.
    4518    *     This modifies the position of the geometry and clears the cached
    4519    *     bounds.
    4520    *
    4521    * Parameters:
    4522    * x - {Float} Distance to move geometry in positive x direction.
    4523    * y - {Float} Distance to move geometry in positive y direction.
    4524    */
    4525   move: function(x, y) {
    4526     this.x = this.x + x;
    4527     this.y = this.y + y;
    4528     this.clearBounds();
    4529   },
    4530   /**
    4531    * Method: rotate
    4532    * Rotate a point around another.
    4533    *
    4534    * Parameters:
    4535    * angle - {Float} Rotation angle in degrees (measured counterclockwise
    4536    *                 from the positive x-axis)
    4537    * origin - {<ZOO.Geometry.Point>} Center point for the rotation
    4538    */
    4539   rotate: function(angle, origin) {
    4540         angle *= Math.PI / 180;
    4541         var radius = this.distanceTo(origin);
    4542         var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
    4543         this.x = origin.x + (radius * Math.cos(theta));
    4544         this.y = origin.y + (radius * Math.sin(theta));
    4545         this.clearBounds();
    4546   },
    4547   /**
    4548    * Method: getCentroid
    4549    *
    4550    * Returns:
    4551    * {<ZOO.Geometry.Point>} The centroid of the collection
    4552    */
    4553   getCentroid: function() {
    4554     return new ZOO.Geometry.Point(this.x, this.y);
    4555   },
    4556   /**
    4557    * Method: resize
    4558    * Resize a point relative to some origin.  For points, this has the effect
    4559    *     of scaling a vector (from the origin to the point).  This method is
    4560    *     more useful on geometry collection subclasses.
    4561    *
    4562    * Parameters:
    4563    * scale - {Float} Ratio of the new distance from the origin to the old
    4564    *                 distance from the origin.  A scale of 2 doubles the
    4565    *                 distance between the point and origin.
    4566    * origin - {<ZOO.Geometry.Point>} Point of origin for resizing
    4567    * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
    4568    *
    4569    * Returns:
    4570    * {ZOO.Geometry} - The current geometry.
    4571    */
    4572   resize: function(scale, origin, ratio) {
    4573     ratio = (ratio == undefined) ? 1 : ratio;
    4574     this.x = origin.x + (scale * ratio * (this.x - origin.x));
    4575     this.y = origin.y + (scale * (this.y - origin.y));
    4576     this.clearBounds();
    4577     return this;
    4578   },
    4579   /**
    4580    * Method: intersects
    4581    * Determine if the input geometry intersects this one.
    4582    *
    4583    * Parameters:
    4584    * geometry - {<ZOO.Geometry>} Any type of geometry.
    4585    *
    4586    * Returns:
    4587    * {Boolean} The input geometry intersects this one.
    4588    */
    4589   intersects: function(geometry) {
    4590     var intersect = false;
    4591     if(geometry.CLASS_NAME == "ZOO.Geometry.Point") {
    4592       intersect = this.equals(geometry);
    4593     } else {
    4594       intersect = geometry.intersects(this);
    4595     }
    4596     return intersect;
    4597   },
    4598   /**
    4599    * Method: transform
    4600    * Translate the x,y properties of the point from source to dest.
    4601    *
    4602    * Parameters:
    4603    * source - {<ZOO.Projection>}
    4604    * dest - {<ZOO.Projection>}
    4605    *
    4606    * Returns:
    4607    * {<ZOO.Geometry>}
    4608    */
    4609   transform: function(source, dest) {
    4610     if ((source && dest)) {
    4611       ZOO.Projection.transform(
    4612           this, source, dest);
    4613       this.bounds = null;
    4614     }       
    4615     return this;
    4616   },
    4617   /**
    4618    * Method: getVertices
    4619    * Return a list of all points in this geometry.
    4620    *
    4621    * Parameters:
    4622    * nodes - {Boolean} For lines, only return vertices that are
    4623    *     endpoints.  If false, for lines, only vertices that are not
    4624    *     endpoints will be returned.  If not provided, all vertices will
    4625    *     be returned.
    4626    *
    4627    * Returns:
    4628    * {Array} A list of all vertices in the geometry.
    4629    */
    4630   getVertices: function(nodes) {
    4631     return [this];
    4632   },
    4633   CLASS_NAME: 'ZOO.Geometry.Point'
    4634 });
    4635 /**
    4636  * Class: ZOO.Geometry.Surface
    4637  * Surface geometry class.
    4638  *
    4639  * Inherits from:
    4640  *  - <ZOO.Geometry>
    4641  */
    4642 ZOO.Geometry.Surface = ZOO.Class(ZOO.Geometry, {
    4643   initialize: function() {
    4644     ZOO.Geometry.prototype.initialize.apply(this, arguments);
    4645   },
    4646   CLASS_NAME: "ZOO.Geometry.Surface"
    4647 });
    4648 /**
    4649  * Class: ZOO.Geometry.MultiPoint
    4650  * MultiPoint is a collection of Points. Create a new instance with the
    4651  * <ZOO.Geometry.MultiPoint> constructor.
    4652  *
    4653  * Inherits from:
    4654  *  - <ZOO.Geometry.Collection>
    4655  */
    4656 ZOO.Geometry.MultiPoint = ZOO.Class(
    4657   ZOO.Geometry.Collection, {
    4658   /**
    4659    * Property: componentTypes
    4660    * {Array(String)} An array of class names representing the types of
    4661    * components that the collection can include.  A null value means the
    4662    * component types are not restricted.
    4663    */
    4664   componentTypes: ["ZOO.Geometry.Point"],
    4665   /**
    4666    * Constructor: ZOO.Geometry.MultiPoint
    4667    * Create a new MultiPoint Geometry
    4668    *
    4669    * Parameters:
    4670    * components - {Array(<ZOO.Geometry.Point>)}
    4671    *
    4672    * Returns:
    4673    * {<ZOO.Geometry.MultiPoint>}
    4674    */
    4675   initialize: function(components) {
    4676     ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
    4677   },
    4678   /**
    4679    * Method: addPoint
    4680    * Wrapper for <ZOO.Geometry.Collection.addComponent>
    4681    *
    4682    * Parameters:
    4683    * point - {<ZOO.Geometry.Point>} Point to be added
    4684    * index - {Integer} Optional index
    4685    */
    4686   addPoint: function(point, index) {
    4687     this.addComponent(point, index);
    4688   },
    4689   /**
    4690    * Method: removePoint
    4691    * Wrapper for <ZOO.Geometry.Collection.removeComponent>
    4692    *
    4693    * Parameters:
    4694    * point - {<ZOO.Geometry.Point>} Point to be removed
    4695    */
    4696   removePoint: function(point){
    4697     this.removeComponent(point);
    4698   },
    4699   CLASS_NAME: "ZOO.Geometry.MultiPoint"
    4700 });
    4701 /**
    4702  * Class: ZOO.Geometry.Curve
    4703  * A Curve is a MultiPoint, whose points are assumed to be connected. To
    4704  * this end, we provide a "getLength()" function, which iterates through
    4705  * the points, summing the distances between them.
    4706  *
    4707  * Inherits:
    4708  *  - <ZOO.Geometry.MultiPoint>
    4709  */
    4710 ZOO.Geometry.Curve = ZOO.Class(ZOO.Geometry.MultiPoint, {
    4711   /**
    4712    * Property: componentTypes
    4713    * {Array(String)} An array of class names representing the types of
    4714    *                 components that the collection can include.  A null
    4715    *                 value means the component types are not restricted.
    4716    */
    4717   componentTypes: ["ZOO.Geometry.Point"],
    4718   /**
    4719    * Constructor: ZOO.Geometry.Curve
    4720    *
    4721    * Parameters:
    4722    * point - {Array(<ZOO.Geometry.Point>)}
    4723    */
    4724   initialize: function(points) {
    4725     ZOO.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);
    4726   },
    4727   /**
    4728    * Method: getLength
    4729    *
    4730    * Returns:
    4731    * {Float} The length of the curve
    4732    */
    4733   getLength: function() {
    4734     var length = 0.0;
    4735     if ( this.components && (this.components.length > 1)) {
    4736       for(var i=1, len=this.components.length; i<len; i++) {
    4737         length += this.components[i-1].distanceTo(this.components[i]);
    4738       }
    4739     }
    4740     return length;
    4741   },
    4742   /**
    4743      * APIMethod: getGeodesicLength
    4744      * Calculate the approximate length of the geometry were it projected onto
    4745      *     the earth.
    4746      *
    4747      * projection - {<ZOO.Projection>} The spatial reference system
    4748      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
    4749      *     assumed.
    4750      *
    4751      * Returns:
    4752      * {Float} The appoximate geodesic length of the geometry in meters.
    4753      */
    4754     getGeodesicLength: function(projection) {
    4755       var geom = this;  // so we can work with a clone if needed
    4756       if(projection) {
    4757         var gg = new ZOO.Projection("EPSG:4326");
    4758         if(!gg.equals(projection)) {
    4759           geom = this.clone().transform(projection, gg);
    4760        }
    4761      }
    4762      var length = 0.0;
    4763      if(geom.components && (geom.components.length > 1)) {
    4764        var p1, p2;
    4765        for(var i=1, len=geom.components.length; i<len; i++) {
    4766          p1 = geom.components[i-1];
    4767          p2 = geom.components[i];
    4768         // this returns km and requires x/y properties
    4769         length += ZOO.distVincenty(p1,p2);
    4770       }
    4771     }
    4772     // convert to m
    4773     return length * 1000;
    4774   },
    4775   CLASS_NAME: "ZOO.Geometry.Curve"
    4776 });
    4777 /**
    4778  * Class: ZOO.Geometry.LineString
    4779  * A LineString is a Curve which, once two points have been added to it, can
    4780  * never be less than two points long.
    4781  *
    4782  * Inherits from:
    4783  *  - <ZOO.Geometry.Curve>
    4784  */
    4785 ZOO.Geometry.LineString = ZOO.Class(ZOO.Geometry.Curve, {
    4786   /**
    4787    * Constructor: ZOO.Geometry.LineString
    4788    * Create a new LineString geometry
    4789    *
    4790    * Parameters:
    4791    * points - {Array(<ZOO.Geometry.Point>)} An array of points used to
    4792    *          generate the linestring
    4793    *
    4794    */
    4795   initialize: function(points) {
    4796     ZOO.Geometry.Curve.prototype.initialize.apply(this, arguments);       
    4797   },
    4798   /**
    4799    * Method: removeComponent
    4800    * Only allows removal of a point if there are three or more points in
    4801    * the linestring. (otherwise the result would be just a single point)
    4802    *
    4803    * Parameters:
    4804    * point - {<ZOO.Geometry.Point>} The point to be removed
    4805    */
    4806   removeComponent: function(point) {
    4807     if ( this.components && (this.components.length > 2))
    4808       ZOO.Geometry.Collection.prototype.removeComponent.apply(this,arguments);
    4809   },
    4810   /**
    4811    * Method: intersects
    4812    * Test for instersection between two geometries.  This is a cheapo
    4813    *     implementation of the Bently-Ottmann algorigithm.  It doesn't
    4814    *     really keep track of a sweep line data structure.  It is closer
    4815    *     to the brute force method, except that segments are sorted and
    4816    *     potential intersections are only calculated when bounding boxes
    4817    *     intersect.
    4818    *
    4819    * Parameters:
    4820    * geometry - {<ZOO.Geometry>}
    4821    *
    4822    * Returns:
    4823    * {Boolean} The input geometry intersects this geometry.
    4824    */
    4825   intersects: function(geometry) {
    4826     var intersect = false;
    4827     var type = geometry.CLASS_NAME;
    4828     if(type == "ZOO.Geometry.LineString" ||
    4829        type == "ZOO.Geometry.LinearRing" ||
    4830        type == "ZOO.Geometry.Point") {
    4831       var segs1 = this.getSortedSegments();
    4832       var segs2;
    4833       if(type == "ZOO.Geometry.Point")
    4834         segs2 = [{
    4835           x1: geometry.x, y1: geometry.y,
    4836           x2: geometry.x, y2: geometry.y
    4837         }];
    4838       else
    4839         segs2 = geometry.getSortedSegments();
    4840       var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
    4841           seg2, seg2y1, seg2y2;
    4842       // sweep right
    4843       outer: for(var i=0, len=segs1.length; i<len; ++i) {
    4844          seg1 = segs1[i];
    4845          seg1x1 = seg1.x1;
    4846          seg1x2 = seg1.x2;
    4847          seg1y1 = seg1.y1;
    4848          seg1y2 = seg1.y2;
    4849          inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
    4850            seg2 = segs2[j];
    4851            if(seg2.x1 > seg1x2)
    4852              break;
    4853            if(seg2.x2 < seg1x1)
    4854              continue;
    4855            seg2y1 = seg2.y1;
    4856            seg2y2 = seg2.y2;
    4857            if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2))
    4858              continue;
    4859            if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2))
    4860              continue;
    4861            if(ZOO.Geometry.segmentsIntersect(seg1, seg2)) {
    4862              intersect = true;
    4863              break outer;
    4864            }
    4865          }
    4866       }
    4867     } else {
    4868       intersect = geometry.intersects(this);
    4869     }
    4870     return intersect;
    4871   },
    4872   /**
    4873    * Method: getSortedSegments
    4874    *
    4875    * Returns:
    4876    * {Array} An array of segment objects.  Segment objects have properties
    4877    *     x1, y1, x2, and y2.  The start point is represented by x1 and y1.
    4878    *     The end point is represented by x2 and y2.  Start and end are
    4879    *     ordered so that x1 < x2.
    4880    */
    4881   getSortedSegments: function() {
    4882     var numSeg = this.components.length - 1;
    4883     var segments = new Array(numSeg);
    4884     for(var i=0; i<numSeg; ++i) {
    4885       point1 = this.components[i];
    4886       point2 = this.components[i + 1];
    4887       if(point1.x < point2.x)
    4888         segments[i] = {
    4889           x1: point1.x,
    4890           y1: point1.y,
    4891           x2: point2.x,
    4892           y2: point2.y
    4893         };
    4894       else
    4895         segments[i] = {
    4896           x1: point2.x,
    4897           y1: point2.y,
    4898           x2: point1.x,
    4899           y2: point1.y
    4900         };
    4901     }
    4902     // more efficient to define this somewhere static
    4903     function byX1(seg1, seg2) {
    4904       return seg1.x1 - seg2.x1;
    4905     }
    4906     return segments.sort(byX1);
    4907   },
    4908   /**
    4909    * Method: splitWithSegment
    4910    * Split this geometry with the given segment.
    4911    *
    4912    * Parameters:
    4913    * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
    4914    *     segment endpoint coordinates.
    4915    * options - {Object} Properties of this object will be used to determine
    4916    *     how the split is conducted.
    4917    *
    4918    * Valid options:
    4919    * edge - {Boolean} Allow splitting when only edges intersect.  Default is
    4920    *     true.  If false, a vertex on the source segment must be within the
    4921    *     tolerance distance of the intersection to be considered a split.
    4922    * tolerance - {Number} If a non-null value is provided, intersections
    4923    *     within the tolerance distance of one of the source segment's
    4924    *     endpoints will be assumed to occur at the endpoint.
    4925    *
    4926    * Returns:
    4927    * {Object} An object with *lines* and *points* properties.  If the given
    4928    *     segment intersects this linestring, the lines array will reference
    4929    *     geometries that result from the split.  The points array will contain
    4930    *     all intersection points.  Intersection points are sorted along the
    4931    *     segment (in order from x1,y1 to x2,y2).
    4932    */
    4933   splitWithSegment: function(seg, options) {
    4934     var edge = !(options && options.edge === false);
    4935     var tolerance = options && options.tolerance;
    4936     var lines = [];
    4937     var verts = this.getVertices();
    4938     var points = [];
    4939     var intersections = [];
    4940     var split = false;
    4941     var vert1, vert2, point;
    4942     var node, vertex, target;
    4943     var interOptions = {point: true, tolerance: tolerance};
    4944     var result = null;
    4945     for(var i=0, stop=verts.length-2; i<=stop; ++i) {
    4946       vert1 = verts[i];
    4947       points.push(vert1.clone());
    4948       vert2 = verts[i+1];
    4949       target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
    4950       point = ZOO.Geometry.segmentsIntersect(seg, target, interOptions);
    4951       if(point instanceof ZOO.Geometry.Point) {
    4952         if((point.x === seg.x1 && point.y === seg.y1) ||
    4953            (point.x === seg.x2 && point.y === seg.y2) ||
    4954             point.equals(vert1) || point.equals(vert2))
    4955           vertex = true;
    4956         else
    4957           vertex = false;
    4958         if(vertex || edge) {
    4959           // push intersections different than the previous
    4960           if(!point.equals(intersections[intersections.length-1]))
    4961             intersections.push(point.clone());
    4962           if(i === 0) {
    4963             if(point.equals(vert1))
    4964               continue;
    4965           }
    4966           if(point.equals(vert2))
    4967             continue;
    4968           split = true;
    4969           if(!point.equals(vert1))
    4970             points.push(point);
    4971           lines.push(new ZOO.Geometry.LineString(points));
    4972           points = [point.clone()];
    4973         }
    4974       }
    4975     }
    4976     if(split) {
    4977       points.push(vert2.clone());
    4978       lines.push(new ZOO.Geometry.LineString(points));
    4979     }
    4980     if(intersections.length > 0) {
    4981       // sort intersections along segment
    4982       var xDir = seg.x1 < seg.x2 ? 1 : -1;
    4983       var yDir = seg.y1 < seg.y2 ? 1 : -1;
    4984       result = {
    4985         lines: lines,
    4986         points: intersections.sort(function(p1, p2) {
    4987            return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
    4988         })
    4989       };
    4990     }
    4991     return result;
    4992   },
    4993   /**
    4994    * Method: split
    4995    * Use this geometry (the source) to attempt to split a target geometry.
    4996    *
    4997    * Parameters:
    4998    * target - {<ZOO.Geometry>} The target geometry.
    4999    * options - {Object} Properties of this object will be used to determine
    5000    *     how the split is conducted.
    5001    *
    5002    * Valid options:
    5003    * mutual - {Boolean} Split the source geometry in addition to the target
    5004    *     geometry.  Default is false.
    5005    * edge - {Boolean} Allow splitting when only edges intersect.  Default is
    5006    *     true.  If false, a vertex on the source must be within the tolerance
    5007    *     distance of the intersection to be considered a split.
    5008    * tolerance - {Number} If a non-null value is provided, intersections
    5009    *     within the tolerance distance of an existing vertex on the source
    5010    *     will be assumed to occur at the vertex.
    5011    *
    5012    * Returns:
    5013    * {Array} A list of geometries (of this same type as the target) that
    5014    *     result from splitting the target with the source geometry.  The
    5015    *     source and target geometry will remain unmodified.  If no split
    5016    *     results, null will be returned.  If mutual is true and a split
    5017    *     results, return will be an array of two arrays - the first will be
    5018    *     all geometries that result from splitting the source geometry and
    5019    *     the second will be all geometries that result from splitting the
    5020    *     target geometry.
    5021    */
    5022   split: function(target, options) {
    5023     var results = null;
    5024     var mutual = options && options.mutual;
    5025     var sourceSplit, targetSplit, sourceParts, targetParts;
    5026     if(target instanceof ZOO.Geometry.LineString) {
    5027       var verts = this.getVertices();
    5028       var vert1, vert2, seg, splits, lines, point;
    5029       var points = [];
    5030       sourceParts = [];
    5031       for(var i=0, stop=verts.length-2; i<=stop; ++i) {
    5032         vert1 = verts[i];
    5033         vert2 = verts[i+1];
    5034         seg = {
    5035           x1: vert1.x, y1: vert1.y,
    5036           x2: vert2.x, y2: vert2.y
    5037         };
    5038         targetParts = targetParts || [target];
    5039         if(mutual)
    5040           points.push(vert1.clone());
    5041         for(var j=0; j<targetParts.length; ++j) {
    5042           splits = targetParts[j].splitWithSegment(seg, options);
    5043           if(splits) {
    5044             // splice in new features
    5045             lines = splits.lines;
    5046             if(lines.length > 0) {
    5047               lines.unshift(j, 1);
    5048               Array.prototype.splice.apply(targetParts, lines);
    5049               j += lines.length - 2;
    5050             }
    5051             if(mutual) {
    5052               for(var k=0, len=splits.points.length; k<len; ++k) {
    5053                 point = splits.points[k];
    5054                 if(!point.equals(vert1)) {
    5055                   points.push(point);
    5056                   sourceParts.push(new ZOO.Geometry.LineString(points));
    5057                   if(point.equals(vert2))
    5058                     points = [];
    5059                   else
    5060                     points = [point.clone()];
    5061                 }
    5062               }
    5063             }
    5064           }
    5065         }
    5066       }
    5067       if(mutual && sourceParts.length > 0 && points.length > 0) {
    5068         points.push(vert2.clone());
    5069         sourceParts.push(new ZOO.Geometry.LineString(points));
    5070       }
    5071     } else {
    5072       results = target.splitWith(this, options);
    5073     }
    5074     if(targetParts && targetParts.length > 1)
    5075       targetSplit = true;
    5076     else
    5077       targetParts = [];
    5078     if(sourceParts && sourceParts.length > 1)
    5079       sourceSplit = true;
    5080     else
    5081       sourceParts = [];
    5082     if(targetSplit || sourceSplit) {
    5083       if(mutual)
    5084         results = [sourceParts, targetParts];
    5085       else
    5086         results = targetParts;
    5087     }
    5088     return results;
    5089   },
    5090   /**
    5091    * Method: splitWith
    5092    * Split this geometry (the target) with the given geometry (the source).
    5093    *
    5094    * Parameters:
    5095    * geometry - {<ZOO.Geometry>} A geometry used to split this
    5096    *     geometry (the source).
    5097    * options - {Object} Properties of this object will be used to determine
    5098    *     how the split is conducted.
    5099    *
    5100    * Valid options:
    5101    * mutual - {Boolean} Split the source geometry in addition to the target
    5102    *     geometry.  Default is false.
    5103    * edge - {Boolean} Allow splitting when only edges intersect.  Default is
    5104    *     true.  If false, a vertex on the source must be within the tolerance
    5105    *     distance of the intersection to be considered a split.
    5106    * tolerance - {Number} If a non-null value is provided, intersections
    5107    *     within the tolerance distance of an existing vertex on the source
    5108    *     will be assumed to occur at the vertex.
    5109    *
    5110    * Returns:
    5111    * {Array} A list of geometries (of this same type as the target) that
    5112    *     result from splitting the target with the source geometry.  The
    5113    *     source and target geometry will remain unmodified.  If no split
    5114    *     results, null will be returned.  If mutual is true and a split
    5115    *     results, return will be an array of two arrays - the first will be
    5116    *     all geometries that result from splitting the source geometry and
    5117    *     the second will be all geometries that result from splitting the
    5118    *     target geometry.
    5119    */
    5120   splitWith: function(geometry, options) {
    5121     return geometry.split(this, options);
    5122   },
    5123   /**
    5124    * Method: getVertices
    5125    * Return a list of all points in this geometry.
    5126    *
    5127    * Parameters:
    5128    * nodes - {Boolean} For lines, only return vertices that are
    5129    *     endpoints.  If false, for lines, only vertices that are not
    5130    *     endpoints will be returned.  If not provided, all vertices will
    5131    *     be returned.
    5132    *
    5133    * Returns:
    5134    * {Array} A list of all vertices in the geometry.
    5135    */
    5136   getVertices: function(nodes) {
    5137     var vertices;
    5138     if(nodes === true)
    5139       vertices = [
    5140         this.components[0],
    5141         this.components[this.components.length-1]
    5142       ];
    5143     else if (nodes === false)
    5144       vertices = this.components.slice(1, this.components.length-1);
    5145     else
    5146       vertices = this.components.slice();
    5147     return vertices;
    5148   },
    5149   distanceTo: function(geometry, options) {
    5150     var edge = !(options && options.edge === false);
    5151     var details = edge && options && options.details;
    5152     var result, best = {};
    5153     var min = Number.POSITIVE_INFINITY;
    5154     if(geometry instanceof ZOO.Geometry.Point) {
    5155       var segs = this.getSortedSegments();
    5156       var x = geometry.x;
    5157       var y = geometry.y;
    5158       var seg;
    5159       for(var i=0, len=segs.length; i<len; ++i) {
    5160         seg = segs[i];
    5161         result = ZOO.Geometry.distanceToSegment(geometry, seg);
    5162         if(result.distance < min) {
    5163           min = result.distance;
    5164           best = result;
    5165           if(min === 0)
    5166             break;
    5167         } else {
    5168           // if distance increases and we cross y0 to the right of x0, no need to keep looking.
    5169           if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2)))
    5170             break;
    5171         }
    5172       }
    5173       if(details)
    5174         best = {
    5175           distance: best.distance,
    5176           x0: best.x, y0: best.y,
    5177           x1: x, y1: y
    5178         };
    5179       else
    5180         best = best.distance;
    5181     } else if(geometry instanceof ZOO.Geometry.LineString) {
    5182       var segs0 = this.getSortedSegments();
    5183       var segs1 = geometry.getSortedSegments();
    5184       var seg0, seg1, intersection, x0, y0;
    5185       var len1 = segs1.length;
    5186       var interOptions = {point: true};
    5187       outer: for(var i=0, len=segs0.length; i<len; ++i) {
    5188         seg0 = segs0[i];
    5189         x0 = seg0.x1;
    5190         y0 = seg0.y1;
    5191         for(var j=0; j<len1; ++j) {
    5192           seg1 = segs1[j];
    5193           intersection = ZOO.Geometry.segmentsIntersect(seg0, seg1, interOptions);
    5194           if(intersection) {
    5195             min = 0;
    5196             best = {
    5197               distance: 0,
    5198               x0: intersection.x, y0: intersection.y,
    5199               x1: intersection.x, y1: intersection.y
    5200             };
    5201             break outer;
    5202           } else {
    5203             result = ZOO.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
    5204             if(result.distance < min) {
    5205               min = result.distance;
    5206               best = {
    5207                 distance: min,
    5208                 x0: x0, y0: y0,
    5209                 x1: result.x, y1: result.y
    5210               };
    5211             }
    5212           }
    5213         }
    5214       }
    5215       if(!details)
    5216         best = best.distance;
    5217       if(min !== 0) {
    5218         // check the final vertex in this line's sorted segments
    5219         if(seg0) {
    5220           result = geometry.distanceTo(
    5221               new ZOO.Geometry.Point(seg0.x2, seg0.y2),
    5222               options
    5223               );
    5224           var dist = details ? result.distance : result;
    5225           if(dist < min) {
    5226             if(details)
    5227               best = {
    5228                 distance: min,
    5229                 x0: result.x1, y0: result.y1,
    5230                 x1: result.x0, y1: result.y0
    5231               };
    5232             else
    5233               best = dist;
    5234           }
    5235         }
    5236       }
    5237     } else {
    5238       best = geometry.distanceTo(this, options);
    5239       // swap since target comes from this line
    5240       if(details)
    5241         best = {
    5242           distance: best.distance,
    5243           x0: best.x1, y0: best.y1,
    5244           x1: best.x0, y1: best.y0
    5245         };
    5246     }
    5247     return best;
    5248   },
    5249   CLASS_NAME: "ZOO.Geometry.LineString"
    5250 });
    5251 /**
    5252  * Class: ZOO.Geometry.LinearRing
    5253  *
    5254  * A Linear Ring is a special LineString which is closed. It closes itself
    5255  * automatically on every addPoint/removePoint by adding a copy of the first
    5256  * point as the last point.
    5257  *
    5258  * Also, as it is the first in the line family to close itself, a getArea()
    5259  * function is defined to calculate the enclosed area of the linearRing
    5260  *
    5261  * Inherits:
    5262  *  - <OpenLayers.Geometry.LineString>
    5263  */
    5264 ZOO.Geometry.LinearRing = ZOO.Class(
    5265   ZOO.Geometry.LineString, {
    5266   /**
    5267    * Property: componentTypes
    5268    * {Array(String)} An array of class names representing the types of
    5269    *                 components that the collection can include.  A null
    5270    *                 value means the component types are not restricted.
    5271    */
    5272   componentTypes: ["ZOO.Geometry.Point"],
    5273   /**
    5274    * Constructor: OpenLayers.Geometry.LinearRing
    5275    * Linear rings are constructed with an array of points.  This array
    5276    *     can represent a closed or open ring.  If the ring is open (the last
    5277    *     point does not equal the first point), the constructor will close
    5278    *     the ring.  If the ring is already closed (the last point does equal
    5279    *     the first point), it will be left closed.
    5280    *
    5281    * Parameters:
    5282    * points - {Array(<ZOO.Geometry.Point>)} points
    5283    */
    5284   initialize: function(points) {
    5285     ZOO.Geometry.LineString.prototype.initialize.apply(this,arguments);
    5286   },
    5287   /**
    5288    * Method: addComponent
    5289    * Adds a point to geometry components.  If the point is to be added to
    5290    *     the end of the components array and it is the same as the last point
    5291    *     already in that array, the duplicate point is not added.  This has
    5292    *     the effect of closing the ring if it is not already closed, and
    5293    *     doing the right thing if it is already closed.  This behavior can
    5294    *     be overridden by calling the method with a non-null index as the
    5295    *     second argument.
    5296    *
    5297    * Parameter:
    5298    * point - {<ZOO.Geometry.Point>}
    5299    * index - {Integer} Index into the array to insert the component
    5300    *
    5301    * Returns:
    5302    * {Boolean} Was the Point successfully added?
    5303    */
    5304   addComponent: function(point, index) {
    5305     var added = false;
    5306     //remove last point
    5307     var lastPoint = this.components.pop();
    5308     // given an index, add the point
    5309     // without an index only add non-duplicate points
    5310     if(index != null || !point.equals(lastPoint))
    5311       added = ZOO.Geometry.Collection.prototype.addComponent.apply(this,arguments);
    5312     //append copy of first point
    5313     var firstPoint = this.components[0];
    5314     ZOO.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);
    5315     return added;
    5316   },
    5317   /**
    5318    * APIMethod: removeComponent
    5319    * Removes a point from geometry components.
    5320    *
    5321    * Parameters:
    5322    * point - {<ZOO.Geometry.Point>}
    5323    */
    5324   removeComponent: function(point) {
    5325     if (this.components.length > 4) {
    5326       //remove last point
    5327       this.components.pop();
    5328       //remove our point
    5329       ZOO.Geometry.Collection.prototype.removeComponent.apply(this,arguments);
    5330       //append copy of first point
    5331       var firstPoint = this.components[0];
    5332       ZOO.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);
    5333     }
    5334   },
    5335   /**
    5336    * Method: move
    5337    * Moves a geometry by the given displacement along positive x and y axes.
    5338    *     This modifies the position of the geometry and clears the cached
    5339    *     bounds.
    5340    *
    5341    * Parameters:
    5342    * x - {Float} Distance to move geometry in positive x direction.
    5343    * y - {Float} Distance to move geometry in positive y direction.
    5344    */
    5345   move: function(x, y) {
    5346     for(var i = 0, len=this.components.length; i<len - 1; i++) {
    5347       this.components[i].move(x, y);
    5348     }
    5349   },
    5350   /**
    5351    * Method: rotate
    5352    * Rotate a geometry around some origin
    5353    *
    5354    * Parameters:
    5355    * angle - {Float} Rotation angle in degrees (measured counterclockwise
    5356    *                 from the positive x-axis)
    5357    * origin - {<ZOO.Geometry.Point>} Center point for the rotation
    5358    */
    5359   rotate: function(angle, origin) {
    5360     for(var i=0, len=this.components.length; i<len - 1; ++i) {
    5361       this.components[i].rotate(angle, origin);
    5362     }
    5363   },
    5364   /**
    5365    * Method: resize
    5366    * Resize a geometry relative to some origin.  Use this method to apply
    5367    *     a uniform scaling to a geometry.
    5368    *
    5369    * Parameters:
    5370    * scale - {Float} Factor by which to scale the geometry.  A scale of 2
    5371    *                 doubles the size of the geometry in each dimension
    5372    *                 (lines, for example, will be twice as long, and polygons
    5373    *                 will have four times the area).
    5374    * origin - {<ZOO.Geometry.Point>} Point of origin for resizing
    5375    * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
    5376    *
    5377    * Returns:
    5378    * {ZOO.Geometry} - The current geometry.
    5379    */
    5380   resize: function(scale, origin, ratio) {
    5381     for(var i=0, len=this.components.length; i<len - 1; ++i) {
    5382       this.components[i].resize(scale, origin, ratio);
    5383     }
    5384     return this;
    5385   },
    5386   /**
    5387    * Method: transform
    5388    * Reproject the components geometry from source to dest.
    5389    *
    5390    * Parameters:
    5391    * source - {<ZOO.Projection>}
    5392    * dest - {<ZOO.Projection>}
    5393    *
    5394    * Returns:
    5395    * {<ZOO.Geometry>}
    5396    */
    5397   transform: function(source, dest) {
    5398     if (source && dest) {
    5399       for (var i=0, len=this.components.length; i<len - 1; i++) {
    5400         var component = this.components[i];
    5401         component.transform(source, dest);
    5402       }
    5403       this.bounds = null;
    5404     }
    5405     return this;
    5406   },
    5407   /**
    5408    * Method: getCentroid
    5409    *
    5410    * Returns:
    5411    * {<ZOO.Geometry.Point>} The centroid of the ring
    5412    */
    5413   getCentroid: function() {
    5414     if ( this.components && (this.components.length > 2)) {
    5415       var sumX = 0.0;
    5416       var sumY = 0.0;
    5417       for (var i = 0; i < this.components.length - 1; i++) {
    5418         var b = this.components[i];
    5419         var c = this.components[i+1];
    5420         sumX += (b.x + c.x) * (b.x * c.y - c.x * b.y);
    5421         sumY += (b.y + c.y) * (b.x * c.y - c.x * b.y);
    5422       }
    5423       var area = -1 * this.getArea();
    5424       var x = sumX / (6 * area);
    5425       var y = sumY / (6 * area);
    5426     }
    5427     return new ZOO.Geometry.Point(x, y);
    5428   },
    5429   /**
    5430    * Method: getArea
    5431    * Note - The area is positive if the ring is oriented CW, otherwise
    5432    *         it will be negative.
    5433    *
    5434    * Returns:
    5435    * {Float} The signed area for a ring.
    5436    */
    5437   getArea: function() {
    5438     var area = 0.0;
    5439     if ( this.components && (this.components.length > 2)) {
    5440       var sum = 0.0;
    5441       for (var i=0, len=this.components.length; i<len - 1; i++) {
    5442         var b = this.components[i];
    5443         var c = this.components[i+1];
    5444         sum += (b.x + c.x) * (c.y - b.y);
    5445       }
    5446       area = - sum / 2.0;
    5447     }
    5448     return area;
    5449   },
    5450   /**
    5451    * Method: getGeodesicArea
    5452    * Calculate the approximate area of the polygon were it projected onto
    5453    *     the earth.  Note that this area will be positive if ring is oriented
    5454    *     clockwise, otherwise it will be negative.
    5455    *
    5456    * Parameters:
    5457    * projection - {<ZOO.Projection>} The spatial reference system
    5458    *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
    5459    *     assumed.
    5460    *
    5461    * Reference:
    5462    * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
    5463    *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
    5464    *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
    5465    *
    5466    * Returns:
    5467    * {float} The approximate signed geodesic area of the polygon in square
    5468    *     meters.
    5469    */
    5470   getGeodesicArea: function(projection) {
    5471     var ring = this;  // so we can work with a clone if needed
    5472     if(projection) {
    5473       var gg = new ZOO.Projection("EPSG:4326");
    5474       if(!gg.equals(projection)) {
    5475         ring = this.clone().transform(projection, gg);
    5476       }
    5477     }
    5478     var area = 0.0;
    5479     var len = ring.components && ring.components.length;
    5480     if(len > 2) {
    5481       var p1, p2;
    5482       for(var i=0; i<len-1; i++) {
    5483         p1 = ring.components[i];
    5484         p2 = ring.components[i+1];
    5485         area += ZOO.rad(p2.x - p1.x) *
    5486                 (2 + Math.sin(ZOO.rad(p1.y)) +
    5487                 Math.sin(ZOO.rad(p2.y)));
    5488       }
    5489       area = area * 6378137.0 * 6378137.0 / 2.0;
    5490     }
    5491     return area;
    5492   },
    5493   /**
    5494    * Method: containsPoint
    5495    * Test if a point is inside a linear ring.  For the case where a point
    5496    *     is coincident with a linear ring edge, returns 1.  Otherwise,
    5497    *     returns boolean.
    5498    *
    5499    * Parameters:
    5500    * point - {<ZOO.Geometry.Point>}
    5501    *
    5502    * Returns:
    5503    * {Boolean | Number} The point is inside the linear ring.  Returns 1 if
    5504    *     the point is coincident with an edge.  Returns boolean otherwise.
    5505    */
    5506   containsPoint: function(point) {
    5507     var approx = OpenLayers.Number.limitSigDigs;
    5508     var digs = 14;
    5509     var px = approx(point.x, digs);
    5510     var py = approx(point.y, digs);
    5511     function getX(y, x1, y1, x2, y2) {
    5512       return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2);
    5513     }
    5514     var numSeg = this.components.length - 1;
    5515     var start, end, x1, y1, x2, y2, cx, cy;
    5516     var crosses = 0;
    5517     for(var i=0; i<numSeg; ++i) {
    5518       start = this.components[i];
    5519       x1 = approx(start.x, digs);
    5520       y1 = approx(start.y, digs);
    5521       end = this.components[i + 1];
    5522       x2 = approx(end.x, digs);
    5523       y2 = approx(end.y, digs);
    5524 
    5525       /**
    5526        * The following conditions enforce five edge-crossing rules:
    5527        *    1. points coincident with edges are considered contained;
    5528        *    2. an upward edge includes its starting endpoint, and
    5529        *    excludes its final endpoint;
    5530        *    3. a downward edge excludes its starting endpoint, and
    5531        *    includes its final endpoint;
    5532        *    4. horizontal edges are excluded; and
    5533        *    5. the edge-ray intersection point must be strictly right
    5534        *    of the point P.
    5535        */
    5536       if(y1 == y2) {
    5537         // horizontal edge
    5538         if(py == y1) {
    5539           // point on horizontal line
    5540           if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
    5541               x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
    5542             // point on edge
    5543             crosses = -1;
    5544             break;
    5545           }
    5546         }
    5547         // ignore other horizontal edges
    5548         continue;
    5549       }
    5550       cx = approx(getX(py, x1, y1, x2, y2), digs);
    5551       if(cx == px) {
    5552         // point on line
    5553         if(y1 < y2 && (py >= y1 && py <= y2) || // upward
    5554             y1 > y2 && (py <= y1 && py >= y2)) { // downward
    5555           // point on edge
    5556           crosses = -1;
    5557           break;
    5558         }
    5559       }
    5560       if(cx <= px) {
    5561         // no crossing to the right
    5562         continue;
    5563       }
    5564       if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
    5565         // no crossing
    5566         continue;
    5567       }
    5568       if(y1 < y2 && (py >= y1 && py < y2) || // upward
    5569           y1 > y2 && (py < y1 && py >= y2)) { // downward
    5570         ++crosses;
    5571       }
    5572     }
    5573     var contained = (crosses == -1) ?
    5574       // on edge
    5575       1 :
    5576       // even (out) or odd (in)
    5577       !!(crosses & 1);
    5578 
    5579     return contained;
    5580   },
    5581   intersects: function(geometry) {
    5582     var intersect = false;
    5583     if(geometry.CLASS_NAME == "ZOO.Geometry.Point")
    5584       intersect = this.containsPoint(geometry);
    5585     else if(geometry.CLASS_NAME == "ZOO.Geometry.LineString")
    5586       intersect = geometry.intersects(this);
    5587     else if(geometry.CLASS_NAME == "ZOO.Geometry.LinearRing")
    5588       intersect = ZOO.Geometry.LineString.prototype.intersects.apply(
    5589           this, [geometry]
    5590           );
    5591     else
    5592       for(var i=0, len=geometry.components.length; i<len; ++ i) {
    5593         intersect = geometry.components[i].intersects(this);
    5594         if(intersect)
    5595           break;
    5596       }
    5597     return intersect;
    5598   },
    5599   getVertices: function(nodes) {
    5600     return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
    5601   },
    5602   CLASS_NAME: "ZOO.Geometry.LinearRing"
    5603 });
    5604 /**
    5605  * Class: ZOO.Geometry.MultiLineString
    5606  * A MultiLineString is a geometry with multiple <ZOO.Geometry.LineString>
    5607  * components.
    5608  *
    5609  * Inherits from:
    5610  *  - <ZOO.Geometry.Collection>
    5611  */
    5612 ZOO.Geometry.MultiLineString = ZOO.Class(
    5613   ZOO.Geometry.Collection, {
    5614   componentTypes: ["ZOO.Geometry.LineString"],
    5615   /**
    5616    * Constructor: ZOO.Geometry.MultiLineString
    5617    * Constructor for a MultiLineString Geometry.
    5618    *
    5619    * Parameters:
    5620    * components - {Array(<ZOO.Geometry.LineString>)}
    5621    *
    5622    */
    5623   initialize: function(components) {
    5624     ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);       
    5625   },
    5626   split: function(geometry, options) {
    5627     var results = null;
    5628     var mutual = options && options.mutual;
    5629     var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
    5630     var sourceParts = [];
    5631     var targetParts = [geometry];
    5632     for(var i=0, len=this.components.length; i<len; ++i) {
    5633       sourceLine = this.components[i];
    5634       sourceSplit = false;
    5635       for(var j=0; j < targetParts.length; ++j) {
    5636         splits = sourceLine.split(targetParts[j], options);
    5637         if(splits) {
    5638           if(mutual) {
    5639             sourceLines = splits[0];
    5640             for(var k=0, klen=sourceLines.length; k<klen; ++k) {
    5641               if(k===0 && sourceParts.length)
    5642                 sourceParts[sourceParts.length-1].addComponent(
    5643                   sourceLines[k]
    5644                 );
    5645               else
    5646                 sourceParts.push(
    5647                   new ZOO.Geometry.MultiLineString([
    5648                     sourceLines[k]
    5649                     ])
    5650                 );
    5651             }
    5652             sourceSplit = true;
    5653             splits = splits[1];
    5654           }
    5655           if(splits.length) {
    5656             // splice in new target parts
    5657             splits.unshift(j, 1);
    5658             Array.prototype.splice.apply(targetParts, splits);
    5659             break;
    5660           }
    5661         }
    5662       }
    5663       if(!sourceSplit) {
    5664         // source line was not hit
    5665         if(sourceParts.length) {
    5666           // add line to existing multi
    5667           sourceParts[sourceParts.length-1].addComponent(
    5668               sourceLine.clone()
    5669               );
    5670         } else {
    5671           // create a fresh multi
    5672           sourceParts = [
    5673             new ZOO.Geometry.MultiLineString(
    5674                 sourceLine.clone()
    5675                 )
    5676             ];
    5677         }
    5678       }
    5679     }
    5680     if(sourceParts && sourceParts.length > 1)
    5681       sourceSplit = true;
    5682     else
    5683       sourceParts = [];
    5684     if(targetParts && targetParts.length > 1)
    5685       targetSplit = true;
    5686     else
    5687       targetParts = [];
    5688     if(sourceSplit || targetSplit) {
    5689       if(mutual)
    5690         results = [sourceParts, targetParts];
    5691       else
    5692         results = targetParts;
    5693     }
    5694     return results;
    5695   },
    5696   splitWith: function(geometry, options) {
    5697     var results = null;
    5698     var mutual = options && options.mutual;
    5699     var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
    5700     if(geometry instanceof ZOO.Geometry.LineString) {
    5701       targetParts = [];
    5702       sourceParts = [geometry];
    5703       for(var i=0, len=this.components.length; i<len; ++i) {
    5704         targetSplit = false;
    5705         targetLine = this.components[i];
    5706         for(var j=0; j<sourceParts.length; ++j) {
    5707           splits = sourceParts[j].split(targetLine, options);
    5708           if(splits) {
    5709             if(mutual) {
    5710               sourceLines = splits[0];
    5711               if(sourceLines.length) {
    5712                 // splice in new source parts
    5713                 sourceLines.unshift(j, 1);
    5714                 Array.prototype.splice.apply(sourceParts, sourceLines);
    5715                 j += sourceLines.length - 2;
    5716               }
    5717               splits = splits[1];
    5718               if(splits.length === 0) {
    5719                 splits = [targetLine.clone()];
    5720               }
    5721             }
    5722             for(var k=0, klen=splits.length; k<klen; ++k) {
    5723               if(k===0 && targetParts.length) {
    5724                 targetParts[targetParts.length-1].addComponent(
    5725                     splits[k]
    5726                     );
    5727               } else {
    5728                 targetParts.push(
    5729                     new ZOO.Geometry.MultiLineString([
    5730                       splits[k]
    5731                       ])
    5732                     );
    5733               }
    5734             }
    5735             targetSplit = true;                   
    5736           }
    5737         }
    5738         if(!targetSplit) {
    5739           // target component was not hit
    5740           if(targetParts.length) {
    5741             // add it to any existing multi-line
    5742             targetParts[targetParts.length-1].addComponent(
    5743                 targetLine.clone()
    5744                 );
    5745           } else {
    5746             // or start with a fresh multi-line
    5747             targetParts = [
    5748               new ZOO.Geometry.MultiLineString([
    5749                   targetLine.clone()
    5750                   ])
    5751               ];
    5752           }
    5753 
    5754         }
    5755       }
    5756     } else {
    5757       results = geometry.split(this);
    5758     }
    5759     if(sourceParts && sourceParts.length > 1)
    5760       sourceSplit = true;
    5761     else
    5762       sourceParts = [];
    5763     if(targetParts && targetParts.length > 1)
    5764       targetSplit = true;
    5765     else
    5766       targetParts = [];
    5767     if(sourceSplit || targetSplit) {
    5768       if(mutual)
    5769         results = [sourceParts, targetParts];
    5770       else
    5771         results = targetParts;
    5772     }
    5773     return results;
    5774   },
    5775   CLASS_NAME: "ZOO.Geometry.MultiLineString"
    5776 });
    5777 /**
    5778  * Class: ZOO.Geometry.Polygon
    5779  * Polygon is a collection of <ZOO.Geometry.LinearRing>.
    5780  *
    5781  * Inherits from:
    5782  *  - <ZOO.Geometry.Collection>
    5783  */
    5784 ZOO.Geometry.Polygon = ZOO.Class(
    5785   ZOO.Geometry.Collection, {
    5786   componentTypes: ["ZOO.Geometry.LinearRing"],
    5787   /**
    5788    * Constructor: OpenLayers.Geometry.Polygon
    5789    * Constructor for a Polygon geometry.
    5790    * The first ring (this.component[0])is the outer bounds of the polygon and
    5791    * all subsequent rings (this.component[1-n]) are internal holes.
    5792    *
    5793    *
    5794    * Parameters:
    5795    * components - {Array(<ZOO.Geometry.LinearRing>)}
    5796    */
    5797   initialize: function(components) {
    5798     ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
    5799   },
    5800   /**
    5801    * Method: getArea
    5802    * Calculated by subtracting the areas of the internal holes from the
    5803    *   area of the outer hole.
    5804    *
    5805    * Returns:
    5806    * {float} The area of the geometry
    5807    */
    5808   getArea: function() {
    5809     var area = 0.0;
    5810     if ( this.components && (this.components.length > 0)) {
    5811       area += Math.abs(this.components[0].getArea());
    5812       for (var i=1, len=this.components.length; i<len; i++) {
    5813         area -= Math.abs(this.components[i].getArea());
    5814       }
    5815     }
    5816     return area;
    5817   },
    5818   /**
    5819    * APIMethod: getGeodesicArea
    5820    * Calculate the approximate area of the polygon were it projected onto
    5821    *     the earth.
    5822    *
    5823    * Parameters:
    5824    * projection - {<ZOO.Projection>} The spatial reference system
    5825    *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
    5826    *     assumed.
    5827    *
    5828    * Reference:
    5829    * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
    5830    *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
    5831    *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
    5832    *
    5833    * Returns:
    5834    * {float} The approximate geodesic area of the polygon in square meters.
    5835    */
    5836   getGeodesicArea: function(projection) {
    5837     var area = 0.0;
    5838     if(this.components && (this.components.length > 0)) {
    5839       area += Math.abs(this.components[0].getGeodesicArea(projection));
    5840       for(var i=1, len=this.components.length; i<len; i++) {
    5841           area -= Math.abs(this.components[i].getGeodesicArea(projection));
    5842       }
    5843     }
    5844     return area;
    5845   },
    5846   /**
    5847    * Method: containsPoint
    5848    * Test if a point is inside a polygon.  Points on a polygon edge are
    5849    *     considered inside.
    5850    *
    5851    * Parameters:
    5852    * point - {<ZOO.Geometry.Point>}
    5853    *
    5854    * Returns:
    5855    * {Boolean | Number} The point is inside the polygon.  Returns 1 if the
    5856    *     point is on an edge.  Returns boolean otherwise.
    5857    */
    5858   containsPoint: function(point) {
    5859     var numRings = this.components.length;
    5860     var contained = false;
    5861     if(numRings > 0) {
    5862     // check exterior ring - 1 means on edge, boolean otherwise
    5863       contained = this.components[0].containsPoint(point);
    5864       if(contained !== 1) {
    5865         if(contained && numRings > 1) {
    5866           // check interior rings
    5867           var hole;
    5868           for(var i=1; i<numRings; ++i) {
    5869             hole = this.components[i].containsPoint(point);
    5870             if(hole) {
    5871               if(hole === 1)
    5872                 contained = 1;
    5873               else
    5874                 contained = false;
    5875               break;
    5876             }
    5877           }
    5878         }
    5879       }
    5880     }
    5881     return contained;
    5882   },
    5883   intersects: function(geometry) {
    5884     var intersect = false;
    5885     var i, len;
    5886     if(geometry.CLASS_NAME == "ZOO.Geometry.Point") {
    5887       intersect = this.containsPoint(geometry);
    5888     } else if(geometry.CLASS_NAME == "ZOO.Geometry.LineString" ||
    5889               geometry.CLASS_NAME == "ZOO.Geometry.LinearRing") {
    5890       // check if rings/linestrings intersect
    5891       for(i=0, len=this.components.length; i<len; ++i) {
    5892         intersect = geometry.intersects(this.components[i]);
    5893         if(intersect) {
    5894           break;
    5895         }
    5896       }
    5897       if(!intersect) {
    5898         // check if this poly contains points of the ring/linestring
    5899         for(i=0, len=geometry.components.length; i<len; ++i) {
    5900           intersect = this.containsPoint(geometry.components[i]);
    5901           if(intersect) {
    5902             break;
    5903           }
    5904         }
    5905       }
    5906     } else {
    5907       for(i=0, len=geometry.components.length; i<len; ++ i) {
    5908         intersect = this.intersects(geometry.components[i]);
    5909         if(intersect)
    5910           break;
    5911       }
    5912     }
    5913     // check case where this poly is wholly contained by another
    5914     if(!intersect && geometry.CLASS_NAME == "ZOO.Geometry.Polygon") {
    5915       // exterior ring points will be contained in the other geometry
    5916       var ring = this.components[0];
    5917       for(i=0, len=ring.components.length; i<len; ++i) {
    5918         intersect = geometry.containsPoint(ring.components[i]);
    5919         if(intersect)
    5920           break;
    5921       }
    5922     }
    5923     return intersect;
    5924   },
    5925   distanceTo: function(geometry, options) {
    5926     var edge = !(options && options.edge === false);
    5927     var result;
    5928     // this is the case where we might not be looking for distance to edge
    5929     if(!edge && this.intersects(geometry))
    5930       result = 0;
    5931     else
    5932       result = ZOO.Geometry.Collection.prototype.distanceTo.apply(
    5933           this, [geometry, options]
    5934           );
    5935     return result;
    5936   },
    5937   CLASS_NAME: "ZOO.Geometry.Polygon"
    5938 });
    5939 /**
    5940  * Method: createRegularPolygon
    5941  * Create a regular polygon around a radius. Useful for creating circles
    5942  * and the like.
    5943  *
    5944  * Parameters:
    5945  * origin - {<ZOO.Geometry.Point>} center of polygon.
    5946  * radius - {Float} distance to vertex, in map units.
    5947  * sides - {Integer} Number of sides. 20 approximates a circle.
    5948  * rotation - {Float} original angle of rotation, in degrees.
    5949  */
    5950 OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) { 
    5951     var angle = Math.PI * ((1/sides) - (1/2));
    5952     if(rotation) {
    5953         angle += (rotation / 180) * Math.PI;
    5954     }
    5955     var rotatedAngle, x, y;
    5956     var points = [];
    5957     for(var i=0; i<sides; ++i) {
    5958         rotatedAngle = angle + (i * 2 * Math.PI / sides);
    5959         x = origin.x + (radius * Math.cos(rotatedAngle));
    5960         y = origin.y + (radius * Math.sin(rotatedAngle));
    5961         points.push(new ZOO.Geometry.Point(x, y));
    5962     }
    5963     var ring = new ZOO.Geometry.LinearRing(points);
    5964     return new ZOO.Geometry.Polygon([ring]);
    5965 };
    5966 /**
    5967  * Class: ZOO.Geometry.MultiPolygon
    5968  * MultiPolygon is a geometry with multiple <ZOO.Geometry.Polygon>
    5969  * components.  Create a new instance with the <ZOO.Geometry.MultiPolygon>
    5970  * constructor.
    5971  *
    5972  * Inherits from:
    5973  *  - <ZOO.Geometry.Collection>
    5974  */
    5975 ZOO.Geometry.MultiPolygon = ZOO.Class(
    5976   ZOO.Geometry.Collection, {
    5977   componentTypes: ["ZOO.Geometry.Polygon"],
    5978   /**
    5979    * Constructor: OpenLayers.Geometry.MultiPolygon
    5980    * Create a new MultiPolygon geometry
    5981    *
    5982    * Parameters:
    5983    * components - {Array(<ZOO.Geometry.Polygon>)} An array of polygons
    5984    *              used to generate the MultiPolygon
    5985    *
    5986    */
    5987   initialize: function(components) {
    5988     ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
    5989   },
    5990   CLASS_NAME: "ZOO.Geometry.MultiPolygon"
    5991 });
    5992 
    5993 ZOO.Process = ZOO.Class({
    5994   schemaLocation: "http://www.opengis.net/wps/1.0.0/../wpsExecute_request.xsd",
    5995   namespaces: {
    5996     ows: "http://www.opengis.net/ows/1.1",
    5997     wps: "http://www.opengis.net/wps/1.0.0",
    5998     xlink: "http://www.w3.org/1999/xlink",
    5999     xsi: "http://www.w3.org/2001/XMLSchema-instance",
    6000   },
    6001   url: 'http://localhost/zoo',
    6002   identifier: null,
    6003   initialize: function(url,identifier) {
    6004     this.url = url;
    6005     this.identifier = identifier;
    6006   },
    6007   Execute: function(inputs) {
    6008     if (this.identifier == null)
    6009       return null;
    6010     var body = new XML('<wps:Execute service="WPS" version="1.0.0" xmlns:wps="'+this.namespaces['wps']+'" xmlns:ows="'+this.namespaces['ows']+'" xmlns:xlink="'+this.namespaces['xlink']+'" xmlns:xsi="'+this.namespaces['xsi']+'" xsi:schemaLocation="'+this.schemaLocation+'"><ows:Identifier>'+this.identifier+'</ows:Identifier>'+this.buildDataInputsNode(inputs)+'</wps:Execute>');
    6011     body = body.toXMLString();
    6012     var response = ZOO.Request.Post(this.url,body,['Content-Type: text/xml; charset=UTF-8']);
    6013     return response;
    6014   },
    6015   buildInput: {
    6016     'complex': function(identifier,data) {
    6017       var input = new XML('<wps:Input xmlns:wps="'+this.namespaces['wps']+'"><ows:Identifier xmlns:ows="'+this.namespaces['ows']+'">'+identifier+'</ows:Identifier><wps:Data><wps:ComplexData>'+data.value+'</wps:ComplexData></wps:Data></wps:Input>');
    6018       input.*::Data.*::ComplexData.@mimeType = data.mimetype ? data.mimetype : 'text/plain';
    6019       if (data.encoding)
    6020         input.*::Data.*::ComplexData.@encoding = data.encoding;
    6021       if (data.schema)
    6022         input.*::Data.*::ComplexData.@schema = data.schema;
    6023       input = input.toXMLString();
    6024       return input;
    6025     },
    6026     'reference': function(identifier,data) {
    6027       return '<wps:Input xmlns:wps="'+this.namespaces['wps']+'"><ows:Identifier xmlns:ows="'+this.namespaces['ows']+'">'+identifier+'</ows:Identifier><wps:Reference xmlns:xlink="'+this.namespaces['xlink']+'" xlink:href="'+data.value.replace('&','&amp;','gi')+'"/></wps:Input>';
    6028     },
    6029     'literal': function(identifier,data) {
    6030       var input = new XML('<wps:Input xmlns:wps="'+this.namespaces['wps']+'"><ows:Identifier xmlns:ows="'+this.namespaces['ows']+'">'+identifier+'</ows:Identifier><wps:Data><wps:LiteralData>'+data.value+'</wps:LiteralData></wps:Data></wps:Input>');
    6031       if (data.type)
    6032         input.*::Data.*::LiteralData.@dataType = data.type;
    6033       if (data.uom)
    6034         input.*::Data.*::LiteralData.@uom = data.uom;
    6035       input = input.toXMLString();
    6036       return input;
    6037     }
    6038   },
    6039   buildDataInputsNode:function(inputs){
    6040     var data, builder, inputsArray=[];
    6041     for (var attr in inputs) {
    6042       data = inputs[attr];
    6043       if (data.mimetype || data.type == 'complex')
    6044         builder = this.buildInput['complex'];
    6045       else if (data.type == 'reference' || data.type == 'url')
    6046         builder = this.buildInput['reference'];
    6047       else
    6048         builder = this.buildInput['literal'];
    6049       inputsArray.push(builder.apply(this,[attr,data]));
    6050     }
    6051     return '<wps:DataInputs xmlns:wps="'+this.namespaces['wps']+'">'+inputsArray.join('\n')+'</wps:DataInputs>';
    6052   },
    6053   CLASS_NAME: "ZOO.Process"
    6054 });
     100  loadProjCodeSuccess:function(projName){if(Proj4js.Proj[projName].dependsOn){this.loadProjCode(Proj4js.Proj[projName].dependsOn);}else{this.initTransforms();}},loadProjCodeFailure:function(projName){Proj4js.reportError("failed to find projection file for: "+projName);},checkCodeLoaded:function(projName){if(Proj4js.Proj[projName]){return true;}else{return false;}},initTransforms:function(){Proj4js.extend(this,Proj4js.Proj[this.projName]);this.init();this.readyToUse=true;},parseDefs:function(){this.defData=Proj4js.defs[this.srsCode];var paramName,paramVal;if(!this.defData){return;}
     101var paramArray=this.defData.split("+");for(var prop=0;prop<paramArray.length;prop++){var property=paramArray[prop].split("=");paramName=property[0].toLowerCase();paramVal=property[1];switch(paramName.replace(/\s/gi,"")){case"":break;case"title":this.title=paramVal;break;case"proj":this.projName=paramVal.replace(/\s/gi,"");break;case"units":this.units=paramVal.replace(/\s/gi,"");break;case"datum":this.datumCode=paramVal.replace(/\s/gi,"");break;case"nadgrids":this.nagrids=paramVal.replace(/\s/gi,"");break;case"ellps":this.ellps=paramVal.replace(/\s/gi,"");break;case"a":this.a=parseFloat(paramVal);break;case"b":this.b=parseFloat(paramVal);break;case"rf":this.rf=parseFloat(paramVal);break;case"lat_0":this.lat0=paramVal*Proj4js.common.D2R;break;case"lat_1":this.lat1=paramVal*Proj4js.common.D2R;break;case"lat_2":this.lat2=paramVal*Proj4js.common.D2R;break;case"lat_ts":this.lat_ts=paramVal*Proj4js.common.D2R;break;case"lon_0":this.long0=paramVal*Proj4js.common.D2R;break;case"alpha":this.alpha=parseFloat(paramVal)*Proj4js.common.D2R;break;case"lonc":this.longc=paramVal*Proj4js.common.D2R;break;case"x_0":this.x0=parseFloat(paramVal);break;case"y_0":this.y0=parseFloat(paramVal);break;case"k_0":this.k0=parseFloat(paramVal);break;case"k":this.k0=parseFloat(paramVal);break;case"r_a":this.R_A=true;break;case"zone":this.zone=parseInt(paramVal);break;case"south":this.utmSouth=true;break;case"towgs84":this.datum_params=paramVal.split(",");break;case"to_meter":this.to_meter=parseFloat(paramVal);break;case"from_greenwich":this.from_greenwich=paramVal*Proj4js.common.D2R;break;case"pm":paramVal=paramVal.replace(/\s/gi,"");this.from_greenwich=Proj4js.PrimeMeridian[paramVal]?Proj4js.PrimeMeridian[paramVal]:parseFloat(paramVal);this.from_greenwich*=Proj4js.common.D2R;break;case"no_defs":break;default:}}
     102this.deriveConstants();},deriveConstants:function(){if(this.nagrids=='@null')this.datumCode='none';if(this.datumCode&&this.datumCode!='none'){var datumDef=Proj4js.Datum[this.datumCode];if(datumDef){this.datum_params=datumDef.towgs84?datumDef.towgs84.split(','):null;this.ellps=datumDef.ellipse;this.datumName=datumDef.datumName?datumDef.datumName:this.datumCode;}}
     103if(!this.a){var ellipse=Proj4js.Ellipsoid[this.ellps]?Proj4js.Ellipsoid[this.ellps]:Proj4js.Ellipsoid['WGS84'];Proj4js.extend(this,ellipse);}
     104if(this.rf&&!this.b)this.b=(1.0-1.0/this.rf)*this.a;if(Math.abs(this.a-this.b)<Proj4js.common.EPSLN){this.sphere=true;this.b=this.a;}
     105this.a2=this.a*this.a;this.b2=this.b*this.b;this.es=(this.a2-this.b2)/this.a2;this.e=Math.sqrt(this.es);if(this.R_A){this.a*=1.-this.es*(Proj4js.common.SIXTH+this.es*(Proj4js.common.RA4+this.es*Proj4js.common.RA6));this.a2=this.a*this.a;this.b2=this.b*this.b;this.es=0.;}
     106this.ep2=(this.a2-this.b2)/this.b2;if(!this.k0)this.k0=1.0;this.datum=new Proj4js.datum(this);}});Proj4js.Proj.longlat={init:function(){},forward:function(pt){return pt;},inverse:function(pt){return pt;}};Proj4js.defs={'WGS84':"+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",'EPSG:4326':"+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees",'EPSG:4269':"+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees",'EPSG:3785':"+title= Google Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"};Proj4js.defs['GOOGLE']=Proj4js.defs['EPSG:3785'];Proj4js.defs['EPSG:900913']=Proj4js.defs['EPSG:3785'];Proj4js.defs['EPSG:102113']=Proj4js.defs['EPSG:3785'];Proj4js.common={PI:3.141592653589793238,HALF_PI:1.570796326794896619,TWO_PI:6.283185307179586477,FORTPI:0.78539816339744833,R2D:57.29577951308232088,D2R:0.01745329251994329577,SEC_TO_RAD:4.84813681109535993589914102357e-6,EPSLN:1.0e-10,MAX_ITER:20,COS_67P5:0.38268343236508977,AD_C:1.0026000,PJD_UNKNOWN:0,PJD_3PARAM:1,PJD_7PARAM:2,PJD_GRIDSHIFT:3,PJD_WGS84:4,PJD_NODATUM:5,SRS_WGS84_SEMIMAJOR:6378137.0,SIXTH:.1666666666666666667,RA4:.04722222222222222222,RA6:.02215608465608465608,RV4:.06944444444444444444,RV6:.04243827160493827160,msfnz:function(eccent,sinphi,cosphi){var con=eccent*sinphi;return cosphi/(Math.sqrt(1.0-con*con));},tsfnz:function(eccent,phi,sinphi){var con=eccent*sinphi;var com=.5*eccent;con=Math.pow(((1.0-con)/(1.0+con)),com);return(Math.tan(.5*(this.HALF_PI-phi))/con);},phi2z:function(eccent,ts){var eccnth=.5*eccent;var con,dphi;var phi=this.HALF_PI-2*Math.atan(ts);for(i=0;i<=15;i++){con=eccent*Math.sin(phi);dphi=this.HALF_PI-2*Math.atan(ts*(Math.pow(((1.0-con)/(1.0+con)),eccnth)))-phi;phi+=dphi;if(Math.abs(dphi)<=.0000000001)return phi;}
     107alert("phi2z has NoConvergence");return(-9999);},qsfnz:function(eccent,sinphi){var con;if(eccent>1.0e-7){con=eccent*sinphi;return((1.0-eccent*eccent)*(sinphi/(1.0-con*con)-(.5/eccent)*Math.log((1.0-con)/(1.0+con))));}else{return(2.0*sinphi);}},asinz:function(x){if(Math.abs(x)>1.0){x=(x>1.0)?1.0:-1.0;}
     108return Math.asin(x);},e0fn:function(x){return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));},e1fn:function(x){return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));},e2fn:function(x){return(0.05859375*x*x*(1.0+0.75*x));},e3fn:function(x){return(x*x*x*(35.0/3072.0));},mlfn:function(e0,e1,e2,e3,phi){return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));},srat:function(esinp,exp){return(Math.pow((1.0-esinp)/(1.0+esinp),exp));},sign:function(x){if(x<0.0)return(-1);else return(1);},adjust_lon:function(x){x=(Math.abs(x)<this.PI)?x:(x-(this.sign(x)*this.TWO_PI));return x;},adjust_lat:function(x){x=(Math.abs(x)<this.HALF_PI)?x:(x-(this.sign(x)*this.PI));return x;},latiso:function(eccent,phi,sinphi){if(Math.abs(phi)>this.HALF_PI)return+Number.NaN;if(phi==this.HALF_PI)return Number.POSITIVE_INFINITY;if(phi==-1.0*this.HALF_PI)return-1.0*Number.POSITIVE_INFINITY;var con=eccent*sinphi;return Math.log(Math.tan((this.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0;},fL:function(x,L){return 2.0*Math.atan(x*Math.exp(L))-this.HALF_PI;},invlatiso:function(eccent,ts){var phi=this.fL(1.0,ts);var Iphi=0.0;var con=0.0;do{Iphi=phi;con=eccent*Math.sin(Iphi);phi=this.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts)}while(Math.abs(phi-Iphi)>1.0e-12);return phi;},sinh:function(x)
     109{var r=Math.exp(x);r=(r-1.0/r)/2.0;return r;},cosh:function(x)
     110{var r=Math.exp(x);r=(r+1.0/r)/2.0;return r;},tanh:function(x)
     111{var r=Math.exp(x);r=(r-1.0/r)/(r+1.0/r);return r;},asinh:function(x)
     112{var s=(x>=0?1.0:-1.0);return s*(Math.log(Math.abs(x)+Math.sqrt(x*x+1.0)));},acosh:function(x)
     113{return 2.0*Math.log(Math.sqrt((x+1.0)/2.0)+Math.sqrt((x-1.0)/2.0));},atanh:function(x)
     114{return Math.log((x-1.0)/(x+1.0))/2.0;},gN:function(a,e,sinphi)
     115{var temp=e*sinphi;return a/Math.sqrt(1.0-temp*temp);}};Proj4js.datum=Proj4js.Class({initialize:function(proj){this.datum_type=Proj4js.common.PJD_WGS84;if(proj.datumCode&&proj.datumCode=='none'){this.datum_type=Proj4js.common.PJD_NODATUM;}
     116if(proj&&proj.datum_params){for(var i=0;i<proj.datum_params.length;i++){proj.datum_params[i]=parseFloat(proj.datum_params[i]);}
     117if(proj.datum_params[0]!=0||proj.datum_params[1]!=0||proj.datum_params[2]!=0){this.datum_type=Proj4js.common.PJD_3PARAM;}
     118if(proj.datum_params.length>3){if(proj.datum_params[3]!=0||proj.datum_params[4]!=0||proj.datum_params[5]!=0||proj.datum_params[6]!=0){this.datum_type=Proj4js.common.PJD_7PARAM;proj.datum_params[3]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[4]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[5]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[6]=(proj.datum_params[6]/1000000.0)+1.0;}}}
     119if(proj){this.a=proj.a;this.b=proj.b;this.es=proj.es;this.ep2=proj.ep2;this.datum_params=proj.datum_params;}},compare_datums:function(dest){if(this.datum_type!=dest.datum_type){return false;}else if(this.a!=dest.a||Math.abs(this.es-dest.es)>0.000000000050){return false;}else if(this.datum_type==Proj4js.common.PJD_3PARAM){return(this.datum_params[0]==dest.datum_params[0]&&this.datum_params[1]==dest.datum_params[1]&&this.datum_params[2]==dest.datum_params[2]);}else if(this.datum_type==Proj4js.common.PJD_7PARAM){return(this.datum_params[0]==dest.datum_params[0]&&this.datum_params[1]==dest.datum_params[1]&&this.datum_params[2]==dest.datum_params[2]&&this.datum_params[3]==dest.datum_params[3]&&this.datum_params[4]==dest.datum_params[4]&&this.datum_params[5]==dest.datum_params[5]&&this.datum_params[6]==dest.datum_params[6]);}else if(this.datum_type==Proj4js.common.PJD_GRIDSHIFT){return strcmp(pj_param(this.params,"snadgrids").s,pj_param(dest.params,"snadgrids").s)==0;}else{return true;}},geodetic_to_geocentric:function(p){var Longitude=p.x;var Latitude=p.y;var Height=p.z?p.z:0;var X;var Y;var Z;var Error_Code=0;var Rn;var Sin_Lat;var Sin2_Lat;var Cos_Lat;if(Latitude<-Proj4js.common.HALF_PI&&Latitude>-1.001*Proj4js.common.HALF_PI){Latitude=-Proj4js.common.HALF_PI;}else if(Latitude>Proj4js.common.HALF_PI&&Latitude<1.001*Proj4js.common.HALF_PI){Latitude=Proj4js.common.HALF_PI;}else if((Latitude<-Proj4js.common.HALF_PI)||(Latitude>Proj4js.common.HALF_PI)){Proj4js.reportError('geocent:lat out of range:'+Latitude);return null;}
     120if(Longitude>Proj4js.common.PI)Longitude-=(2*Proj4js.common.PI);Sin_Lat=Math.sin(Latitude);Cos_Lat=Math.cos(Latitude);Sin2_Lat=Sin_Lat*Sin_Lat;Rn=this.a/(Math.sqrt(1.0e0-this.es*Sin2_Lat));X=(Rn+Height)*Cos_Lat*Math.cos(Longitude);Y=(Rn+Height)*Cos_Lat*Math.sin(Longitude);Z=((Rn*(1-this.es))+Height)*Sin_Lat;p.x=X;p.y=Y;p.z=Z;return Error_Code;},geocentric_to_geodetic:function(p){var genau=1.E-12;var genau2=(genau*genau);var maxiter=30;var P;var RR;var CT;var ST;var RX;var RK;var RN;var CPHI0;var SPHI0;var CPHI;var SPHI;var SDPHI;var At_Pole;var iter;var X=p.x;var Y=p.y;var Z=p.z?p.z:0.0;var Longitude;var Latitude;var Height;At_Pole=false;P=Math.sqrt(X*X+Y*Y);RR=Math.sqrt(X*X+Y*Y+Z*Z);if(P/this.a<genau){At_Pole=true;Longitude=0.0;if(RR/this.a<genau){Latitude=Proj4js.common.HALF_PI;Height=-this.b;return;}}else{Longitude=Math.atan2(Y,X);}
     121CT=Z/RR;ST=P/RR;RX=1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST);CPHI0=ST*(1.0-this.es)*RX;SPHI0=CT*RX;iter=0;do
     122{iter++;RN=this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0);Height=P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0);RK=this.es*RN/(RN+Height);RX=1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST);CPHI=ST*(1.0-RK)*RX;SPHI=CT*RX;SDPHI=SPHI*CPHI0-CPHI*SPHI0;CPHI0=CPHI;SPHI0=SPHI;}
     123while(SDPHI*SDPHI>genau2&&iter<maxiter);Latitude=Math.atan(SPHI/Math.abs(CPHI));p.x=Longitude;p.y=Latitude;p.z=Height;return p;},geocentric_to_geodetic_noniter:function(p){var X=p.x;var Y=p.y;var Z=p.z?p.z:0;var Longitude;var Latitude;var Height;var W;var W2;var T0;var T1;var S0;var S1;var Sin_B0;var Sin3_B0;var Cos_B0;var Sin_p1;var Cos_p1;var Rn;var Sum;var At_Pole;X=parseFloat(X);Y=parseFloat(Y);Z=parseFloat(Z);At_Pole=false;if(X!=0.0)
     124{Longitude=Math.atan2(Y,X);}
     125else
     126{if(Y>0)
     127{Longitude=Proj4js.common.HALF_PI;}
     128else if(Y<0)
     129{Longitude=-Proj4js.common.HALF_PI;}
     130else
     131{At_Pole=true;Longitude=0.0;if(Z>0.0)
     132{Latitude=Proj4js.common.HALF_PI;}
     133else if(Z<0.0)
     134{Latitude=-Proj4js.common.HALF_PI;}
     135else
     136{Latitude=Proj4js.common.HALF_PI;Height=-this.b;return;}}}
     137W2=X*X+Y*Y;W=Math.sqrt(W2);T0=Z*Proj4js.common.AD_C;S0=Math.sqrt(T0*T0+W2);Sin_B0=T0/S0;Cos_B0=W/S0;Sin3_B0=Sin_B0*Sin_B0*Sin_B0;T1=Z+this.b*this.ep2*Sin3_B0;Sum=W-this.a*this.es*Cos_B0*Cos_B0*Cos_B0;S1=Math.sqrt(T1*T1+Sum*Sum);Sin_p1=T1/S1;Cos_p1=Sum/S1;Rn=this.a/Math.sqrt(1.0-this.es*Sin_p1*Sin_p1);if(Cos_p1>=Proj4js.common.COS_67P5)
     138{Height=W/Cos_p1-Rn;}
     139else if(Cos_p1<=-Proj4js.common.COS_67P5)
     140{Height=W/-Cos_p1-Rn;}
     141else
     142{Height=Z/Sin_p1+Rn*(this.es-1.0);}
     143if(At_Pole==false)
     144{Latitude=Math.atan(Sin_p1/Cos_p1);}
     145p.x=Longitude;p.y=Latitude;p.z=Height;return p;},geocentric_to_wgs84:function(p){if(this.datum_type==Proj4js.common.PJD_3PARAM)
     146{p.x+=this.datum_params[0];p.y+=this.datum_params[1];p.z+=this.datum_params[2];}
     147else if(this.datum_type==Proj4js.common.PJD_7PARAM)
     148{var Dx_BF=this.datum_params[0];var Dy_BF=this.datum_params[1];var Dz_BF=this.datum_params[2];var Rx_BF=this.datum_params[3];var Ry_BF=this.datum_params[4];var Rz_BF=this.datum_params[5];var M_BF=this.datum_params[6];var x_out=M_BF*(p.x-Rz_BF*p.y+Ry_BF*p.z)+Dx_BF;var y_out=M_BF*(Rz_BF*p.x+p.y-Rx_BF*p.z)+Dy_BF;var z_out=M_BF*(-Ry_BF*p.x+Rx_BF*p.y+p.z)+Dz_BF;p.x=x_out;p.y=y_out;p.z=z_out;}},geocentric_from_wgs84:function(p){if(this.datum_type==Proj4js.common.PJD_3PARAM)
     149{p.x-=this.datum_params[0];p.y-=this.datum_params[1];p.z-=this.datum_params[2];}
     150else if(this.datum_type==Proj4js.common.PJD_7PARAM)
     151{var Dx_BF=this.datum_params[0];var Dy_BF=this.datum_params[1];var Dz_BF=this.datum_params[2];var Rx_BF=this.datum_params[3];var Ry_BF=this.datum_params[4];var Rz_BF=this.datum_params[5];var M_BF=this.datum_params[6];var x_tmp=(p.x-Dx_BF)/M_BF;var y_tmp=(p.y-Dy_BF)/M_BF;var z_tmp=(p.z-Dz_BF)/M_BF;p.x=x_tmp+Rz_BF*y_tmp-Ry_BF*z_tmp;p.y=-Rz_BF*x_tmp+y_tmp+Rx_BF*z_tmp;p.z=Ry_BF*x_tmp-Rx_BF*y_tmp+z_tmp;}}});Proj4js.Point=Proj4js.Class({initialize:function(x,y,z){if(typeof x=='object'){this.x=x[0];this.y=x[1];this.z=x[2]||0.0;}else if(typeof x=='string'){var coords=x.split(',');this.x=parseFloat(coords[0]);this.y=parseFloat(coords[1]);this.z=parseFloat(coords[2])||0.0;}else{this.x=x;this.y=y;this.z=z||0.0;}},clone:function(){return new Proj4js.Point(this.x,this.y,this.z);},toString:function(){return("x="+this.x+",y="+this.y);},toShortString:function(){return(this.x+", "+this.y);}});Proj4js.PrimeMeridian={"greenwich":0.0,"lisbon":-9.131906111111,"paris":2.337229166667,"bogota":-74.080916666667,"madrid":-3.687938888889,"rome":12.452333333333,"bern":7.439583333333,"jakarta":106.807719444444,"ferro":-17.666666666667,"brussels":4.367975,"stockholm":18.058277777778,"athens":23.7163375,"oslo":10.722916666667};Proj4js.Ellipsoid={"MERIT":{a:6378137.0,rf:298.257,ellipseName:"MERIT 1983"},"SGS85":{a:6378136.0,rf:298.257,ellipseName:"Soviet Geodetic System 85"},"GRS80":{a:6378137.0,rf:298.257222101,ellipseName:"GRS 1980(IUGG, 1980)"},"IAU76":{a:6378140.0,rf:298.257,ellipseName:"IAU 1976"},"airy":{a:6377563.396,b:6356256.910,ellipseName:"Airy 1830"},"APL4.":{a:6378137,rf:298.25,ellipseName:"Appl. Physics. 1965"},"NWL9D":{a:6378145.0,rf:298.25,ellipseName:"Naval Weapons Lab., 1965"},"mod_airy":{a:6377340.189,b:6356034.446,ellipseName:"Modified Airy"},"andrae":{a:6377104.43,rf:300.0,ellipseName:"Andrae 1876 (Den., Iclnd.)"},"aust_SA":{a:6378160.0,rf:298.25,ellipseName:"Australian Natl & S. Amer. 1969"},"GRS67":{a:6378160.0,rf:298.2471674270,ellipseName:"GRS 67(IUGG 1967)"},"bessel":{a:6377397.155,rf:299.1528128,ellipseName:"Bessel 1841"},"bess_nam":{a:6377483.865,rf:299.1528128,ellipseName:"Bessel 1841 (Namibia)"},"clrk66":{a:6378206.4,b:6356583.8,ellipseName:"Clarke 1866"},"clrk80":{a:6378249.145,rf:293.4663,ellipseName:"Clarke 1880 mod."},"CPM":{a:6375738.7,rf:334.29,ellipseName:"Comm. des Poids et Mesures 1799"},"delmbr":{a:6376428.0,rf:311.5,ellipseName:"Delambre 1810 (Belgium)"},"engelis":{a:6378136.05,rf:298.2566,ellipseName:"Engelis 1985"},"evrst30":{a:6377276.345,rf:300.8017,ellipseName:"Everest 1830"},"evrst48":{a:6377304.063,rf:300.8017,ellipseName:"Everest 1948"},"evrst56":{a:6377301.243,rf:300.8017,ellipseName:"Everest 1956"},"evrst69":{a:6377295.664,rf:300.8017,ellipseName:"Everest 1969"},"evrstSS":{a:6377298.556,rf:300.8017,ellipseName:"Everest (Sabah & Sarawak)"},"fschr60":{a:6378166.0,rf:298.3,ellipseName:"Fischer (Mercury Datum) 1960"},"fschr60m":{a:6378155.0,rf:298.3,ellipseName:"Fischer 1960"},"fschr68":{a:6378150.0,rf:298.3,ellipseName:"Fischer 1968"},"helmert":{a:6378200.0,rf:298.3,ellipseName:"Helmert 1906"},"hough":{a:6378270.0,rf:297.0,ellipseName:"Hough"},"intl":{a:6378388.0,rf:297.0,ellipseName:"International 1909 (Hayford)"},"kaula":{a:6378163.0,rf:298.24,ellipseName:"Kaula 1961"},"lerch":{a:6378139.0,rf:298.257,ellipseName:"Lerch 1979"},"mprts":{a:6397300.0,rf:191.0,ellipseName:"Maupertius 1738"},"new_intl":{a:6378157.5,b:6356772.2,ellipseName:"New International 1967"},"plessis":{a:6376523.0,rf:6355863.0,ellipseName:"Plessis 1817 (France)"},"krass":{a:6378245.0,rf:298.3,ellipseName:"Krassovsky, 1942"},"SEasia":{a:6378155.0,b:6356773.3205,ellipseName:"Southeast Asia"},"walbeck":{a:6376896.0,b:6355834.8467,ellipseName:"Walbeck"},"WGS60":{a:6378165.0,rf:298.3,ellipseName:"WGS 60"},"WGS66":{a:6378145.0,rf:298.25,ellipseName:"WGS 66"},"WGS72":{a:6378135.0,rf:298.26,ellipseName:"WGS 72"},"WGS84":{a:6378137.0,rf:298.257223563,ellipseName:"WGS 84"},"sphere":{a:6370997.0,b:6370997.0,ellipseName:"Normal Sphere (r=6370997)"}};Proj4js.Datum={"WGS84":{towgs84:"0,0,0",ellipse:"WGS84",datumName:"WGS84"},"GGRS87":{towgs84:"-199.87,74.79,246.62",ellipse:"GRS80",datumName:"Greek_Geodetic_Reference_System_1987"},"NAD83":{towgs84:"0,0,0",ellipse:"GRS80",datumName:"North_American_Datum_1983"},"NAD27":{nadgrids:"@conus,@alaska,@ntv2_0.gsb,@ntv1_can.dat",ellipse:"clrk66",datumName:"North_American_Datum_1927"},"potsdam":{towgs84:"606.0,23.0,413.0",ellipse:"bessel",datumName:"Potsdam Rauenberg 1950 DHDN"},"carthage":{towgs84:"-263.0,6.0,431.0",ellipse:"clark80",datumName:"Carthage 1934 Tunisia"},"hermannskogel":{towgs84:"653.0,-212.0,449.0",ellipse:"bessel",datumName:"Hermannskogel"},"ire65":{towgs84:"482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15",ellipse:"mod_airy",datumName:"Ireland 1965"},"nzgd49":{towgs84:"59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993",ellipse:"intl",datumName:"New Zealand Geodetic Datum 1949"},"OSGB36":{towgs84:"446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894",ellipse:"airy",datumName:"Airy 1830"}};Proj4js.WGS84=new Proj4js.Proj('WGS84');Proj4js.Datum['OSB36']=Proj4js.Datum['OSGB36'];Proj4js.Proj.aea={init:function(){if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("aeaInitEqualLatitudes");return;}
     152this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e3=Math.sqrt(this.es);this.sin_po=Math.sin(this.lat1);this.cos_po=Math.cos(this.lat1);this.t1=this.sin_po;this.con=this.sin_po;this.ms1=Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);this.qs1=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);this.sin_po=Math.sin(this.lat2);this.cos_po=Math.cos(this.lat2);this.t2=this.sin_po;this.ms2=Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);this.qs2=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);this.sin_po=Math.sin(this.lat0);this.cos_po=Math.cos(this.lat0);this.t3=this.sin_po;this.qs0=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);if(Math.abs(this.lat1-this.lat2)>Proj4js.common.EPSLN){this.ns0=(this.ms1*this.ms1-this.ms2*this.ms2)/(this.qs2-this.qs1);}else{this.ns0=this.con;}
     153this.c=this.ms1*this.ms1+this.ns0*this.qs1;this.rh=this.a*Math.sqrt(this.c-this.ns0*this.qs0)/this.ns0;},forward:function(p){var lon=p.x;var lat=p.y;this.sin_phi=Math.sin(lat);this.cos_phi=Math.cos(lat);var qs=Proj4js.common.qsfnz(this.e3,this.sin_phi,this.cos_phi);var rh1=this.a*Math.sqrt(this.c-this.ns0*qs)/this.ns0;var theta=this.ns0*Proj4js.common.adjust_lon(lon-this.long0);var x=rh1*Math.sin(theta)+this.x0;var y=this.rh-rh1*Math.cos(theta)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var rh1,qs,con,theta,lon,lat;p.x-=this.x0;p.y=this.rh-p.y+this.y0;if(this.ns0>=0){rh1=Math.sqrt(p.x*p.x+p.y*p.y);con=1.0;}else{rh1=-Math.sqrt(p.x*p.x+p.y*p.y);con=-1.0;}
     154theta=0.0;if(rh1!=0.0){theta=Math.atan2(con*p.x,con*p.y);}
     155con=rh1*this.ns0/this.a;qs=(this.c-con*con)/this.ns0;if(this.e3>=1e-10){con=1-.5*(1.0-this.es)*Math.log((1.0-this.e3)/(1.0+this.e3))/this.e3;if(Math.abs(Math.abs(con)-Math.abs(qs))>.0000000001){lat=this.phi1z(this.e3,qs);}else{if(qs>=0){lat=.5*PI;}else{lat=-.5*PI;}}}else{lat=this.phi1z(e3,qs);}
     156lon=Proj4js.common.adjust_lon(theta/this.ns0+this.long0);p.x=lon;p.y=lat;return p;},phi1z:function(eccent,qs){var con,com,dphi;var phi=Proj4js.common.asinz(.5*qs);if(eccent<Proj4js.common.EPSLN)return phi;var eccnts=eccent*eccent;for(var i=1;i<=25;i++){sinphi=Math.sin(phi);cosphi=Math.cos(phi);con=eccent*sinphi;com=1.0-con*con;dphi=.5*com*com/cosphi*(qs/(1.0-eccnts)-sinphi/com+.5/eccent*Math.log((1.0-con)/(1.0+con)));phi=phi+dphi;if(Math.abs(dphi)<=1e-7)return phi;}
     157Proj4js.reportError("aea:phi1z:Convergence error");return null;}};Proj4js.Proj.sterea={dependsOn:'gauss',init:function(){Proj4js.Proj['gauss'].init.apply(this);if(!this.rc){Proj4js.reportError("sterea:init:E_ERROR_0");return;}
     158this.sinc0=Math.sin(this.phic0);this.cosc0=Math.cos(this.phic0);this.R2=2.0*this.rc;if(!this.title)this.title="Oblique Stereographic Alternative";},forward:function(p){p.x=Proj4js.common.adjust_lon(p.x-this.long0);Proj4js.Proj['gauss'].forward.apply(this,[p]);sinc=Math.sin(p.y);cosc=Math.cos(p.y);cosl=Math.cos(p.x);k=this.k0*this.R2/(1.0+this.sinc0*sinc+this.cosc0*cosc*cosl);p.x=k*cosc*Math.sin(p.x);p.y=k*(this.cosc0*sinc-this.sinc0*cosc*cosl);p.x=this.a*p.x+this.x0;p.y=this.a*p.y+this.y0;return p;},inverse:function(p){var lon,lat;p.x=(p.x-this.x0)/this.a;p.y=(p.y-this.y0)/this.a;p.x/=this.k0;p.y/=this.k0;if((rho=Math.sqrt(p.x*p.x+p.y*p.y))){c=2.0*Math.atan2(rho,this.R2);sinc=Math.sin(c);cosc=Math.cos(c);lat=Math.asin(cosc*this.sinc0+p.y*sinc*this.cosc0/rho);lon=Math.atan2(p.x*sinc,rho*this.cosc0*cosc-p.y*this.sinc0*sinc);}else{lat=this.phic0;lon=0.;}
     159p.x=lon;p.y=lat;Proj4js.Proj['gauss'].inverse.apply(this,[p]);p.x=Proj4js.common.adjust_lon(p.x+this.long0);return p;}};function phi4z(eccent,e0,e1,e2,e3,a,b,c,phi){var sinphi,sin2ph,tanph,ml,mlp,con1,con2,con3,dphi,i;phi=a;for(i=1;i<=15;i++){sinphi=Math.sin(phi);tanphi=Math.tan(phi);c=tanphi*Math.sqrt(1.0-eccent*sinphi*sinphi);sin2ph=Math.sin(2.0*phi);ml=e0*phi-e1*sin2ph+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi);mlp=e0-2.0*e1*Math.cos(2.0*phi)+4.0*e2*Math.cos(4.0*phi)-6.0*e3*Math.cos(6.0*phi);con1=2.0*ml+c*(ml*ml+b)-2.0*a*(c*ml+1.0);con2=eccent*sin2ph*(ml*ml+b-2.0*a*ml)/(2.0*c);con3=2.0*(a-ml)*(c*mlp-2.0/sin2ph)-2.0*mlp;dphi=con1/(con2+con3);phi+=dphi;if(Math.abs(dphi)<=.0000000001)return(phi);}
     160Proj4js.reportError("phi4z: No convergence");return null;}
     161function e4fn(x){var con,com;con=1.0+x;com=1.0-x;return(Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));}
     162Proj4js.Proj.poly={init:function(){var temp;if(this.lat0=0)this.lat0=90;this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e=Math.sqrt(this.es);this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.ml0=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);},forward:function(p){var sinphi,cosphi;var al;var c;var con,ml;var ms;var x,y;var lon=p.x;var lat=p.y;con=Proj4js.common.adjust_lon(lon-this.long0);if(Math.abs(lat)<=.0000001){x=this.x0+this.a*con;y=this.y0-this.a*this.ml0;}else{sinphi=Math.sin(lat);cosphi=Math.cos(lat);ml=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);ms=Proj4js.common.msfnz(this.e,sinphi,cosphi);con=sinphi;x=this.x0+this.a*ms*Math.sin(con)/sinphi;y=this.y0+this.a*(ml-this.ml0+ms*(1.0-Math.cos(con))/sinphi);}
     163p.x=x;p.y=y;return p;},inverse:function(p){var sin_phi,cos_phi;var al;var b;var c;var con,ml;var iflg;var lon,lat;p.x-=this.x0;p.y-=this.y0;al=this.ml0+p.y/this.a;iflg=0;if(Math.abs(al)<=.0000001){lon=p.x/this.a+this.long0;lat=0.0;}else{b=al*al+(p.x/this.a)*(p.x/this.a);iflg=phi4z(this.es,this.e0,this.e1,this.e2,this.e3,this.al,b,c,lat);if(iflg!=1)return(iflg);lon=Proj4js.common.adjust_lon((Proj4js.common.asinz(p.x*c/this.a)/Math.sin(lat))+this.long0);}
     164p.x=lon;p.y=lat;return p;}};Proj4js.Proj.equi={init:function(){if(!this.x0)this.x0=0;if(!this.y0)this.y0=0;if(!this.lat0)this.lat0=0;if(!this.long0)this.long0=0;},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon*Math.cos(this.lat0);var y=this.y0+this.a*lat;this.t1=x;this.t2=Math.cos(this.lat0);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lat=p.y/this.a;if(Math.abs(lat)>Proj4js.common.HALF_PI){Proj4js.reportError("equi:Inv:DataError");}
     165var lon=Proj4js.common.adjust_lon(this.long0+p.x/(this.a*Math.cos(this.lat0)));p.x=lon;p.y=lat;}};Proj4js.Proj.merc={init:function(){if(this.lat_ts){if(this.sphere){this.k0=Math.cos(this.lat_ts);}else{this.k0=Proj4js.common.msfnz(this.es,Math.sin(this.lat_ts),Math.cos(this.lat_ts));}}},forward:function(p){var lon=p.x;var lat=p.y;if(lat*Proj4js.common.R2D>90.0&&lat*Proj4js.common.R2D<-90.0&&lon*Proj4js.common.R2D>180.0&&lon*Proj4js.common.R2D<-180.0){Proj4js.reportError("merc:forward: llInputOutOfRange: "+lon+" : "+lat);return null;}
     166var x,y;if(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN){Proj4js.reportError("merc:forward: ll2mAtPoles");return null;}else{if(this.sphere){x=this.x0+this.a*this.k0*Proj4js.common.adjust_lon(lon-this.long0);y=this.y0+this.a*this.k0*Math.log(Math.tan(Proj4js.common.FORTPI+0.5*lat));}else{var sinphi=Math.sin(lat);var ts=Proj4js.common.tsfnz(this.e,lat,sinphi);x=this.x0+this.a*this.k0*Proj4js.common.adjust_lon(lon-this.long0);y=this.y0-this.a*this.k0*Math.log(ts);}
     167p.x=x;p.y=y;return p;}},inverse:function(p){var x=p.x-this.x0;var y=p.y-this.y0;var lon,lat;if(this.sphere){lat=Proj4js.common.HALF_PI-2.0*Math.atan(Math.exp(-y/this.a*this.k0));}else{var ts=Math.exp(-y/(this.a*this.k0));lat=Proj4js.common.phi2z(this.e,ts);if(lat==-9999){Proj4js.reportError("merc:inverse: lat = -9999");return null;}}
     168lon=Proj4js.common.adjust_lon(this.long0+x/(this.a*this.k0));p.x=lon;p.y=lat;return p;}};Proj4js.Proj.utm={dependsOn:'tmerc',init:function(){if(!this.zone){Proj4js.reportError("utm:init: zone must be specified for UTM");return;}
     169this.lat0=0.0;this.long0=((6*Math.abs(this.zone))-183)*Proj4js.common.D2R;this.x0=500000.0;this.y0=this.utmSouth?10000000.0:0.0;this.k0=0.9996;Proj4js.Proj['tmerc'].init.apply(this);this.forward=Proj4js.Proj['tmerc'].forward;this.inverse=Proj4js.Proj['tmerc'].inverse;}};Proj4js.Proj.eqdc={init:function(){if(!this.mode)this.mode=0;this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e=Math.sqrt(this.es);this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.sinphi=Math.sin(this.lat1);this.cosphi=Math.cos(this.lat1);this.ms1=Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);this.ml1=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat1);if(this.mode!=0){if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("eqdc:Init:EqualLatitudes");}
     170this.sinphi=Math.sin(this.lat2);this.cosphi=Math.cos(this.lat2);this.ms2=Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);this.ml2=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat2);if(Math.abs(this.lat1-this.lat2)>=Proj4js.common.EPSLN){this.ns=(this.ms1-this.ms2)/(this.ml2-this.ml1);}else{this.ns=this.sinphi;}}else{this.ns=this.sinphi;}
     171this.g=this.ml1+this.ms1/this.ns;this.ml0=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);this.rh=this.a*(this.g-this.ml0);},forward:function(p){var lon=p.x;var lat=p.y;var ml=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);var rh1=this.a*(this.g-ml);var theta=this.ns*Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+rh1*Math.sin(theta);var y=this.y0+this.rh-rh1*Math.cos(theta);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y=this.rh-p.y+this.y0;var con,rh1;if(this.ns>=0){var rh1=Math.sqrt(p.x*p.x+p.y*p.y);var con=1.0;}else{rh1=-Math.sqrt(p.x*p.x+p.y*p.y);con=-1.0;}
     172var theta=0.0;if(rh1!=0.0)theta=Math.atan2(con*p.x,con*p.y);var ml=this.g-rh1/this.a;var lat=this.phi3z(this.ml,this.e0,this.e1,this.e2,this.e3);var lon=Proj4js.common.adjust_lon(this.long0+theta/this.ns);p.x=lon;p.y=lat;return p;},phi3z:function(ml,e0,e1,e2,e3){var phi;var dphi;phi=ml;for(var i=0;i<15;i++){dphi=(ml+e1*Math.sin(2.0*phi)-e2*Math.sin(4.0*phi)+e3*Math.sin(6.0*phi))/e0-phi;phi+=dphi;if(Math.abs(dphi)<=.0000000001){return phi;}}
     173Proj4js.reportError("PHI3Z-CONV:Latitude failed to converge after 15 iterations");return null;}};Proj4js.Proj.tmerc={init:function(){this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.ml0=this.a*Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);},forward:function(p){var lon=p.x;var lat=p.y;var delta_lon=Proj4js.common.adjust_lon(lon-this.long0);var con;var x,y;var sin_phi=Math.sin(lat);var cos_phi=Math.cos(lat);if(this.sphere){var b=cos_phi*Math.sin(delta_lon);if((Math.abs(Math.abs(b)-1.0))<.0000000001){Proj4js.reportError("tmerc:forward: Point projects into infinity");return(93);}else{x=.5*this.a*this.k0*Math.log((1.0+b)/(1.0-b));con=Math.acos(cos_phi*Math.cos(delta_lon)/Math.sqrt(1.0-b*b));if(lat<0)con=-con;y=this.a*this.k0*(con-this.lat0);}}else{var al=cos_phi*delta_lon;var als=Math.pow(al,2);var c=this.ep2*Math.pow(cos_phi,2);var tq=Math.tan(lat);var t=Math.pow(tq,2);con=1.0-this.es*Math.pow(sin_phi,2);var n=this.a/Math.sqrt(con);var ml=this.a*Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);x=this.k0*n*al*(1.0+als/6.0*(1.0-t+c+als/20.0*(5.0-18.0*t+Math.pow(t,2)+72.0*c-58.0*this.ep2)))+this.x0;y=this.k0*(ml-this.ml0+n*tq*(als*(0.5+als/24.0*(5.0-t+9.0*c+4.0*Math.pow(c,2)+als/30.0*(61.0-58.0*t+Math.pow(t,2)+600.0*c-330.0*this.ep2)))))+this.y0;}
     174p.x=x;p.y=y;return p;},inverse:function(p){var con,phi;var delta_phi;var i;var max_iter=6;var lat,lon;if(this.sphere){var f=Math.exp(p.x/(this.a*this.k0));var g=.5*(f-1/f);var temp=this.lat0+p.y/(this.a*this.k0);var h=Math.cos(temp);con=Math.sqrt((1.0-h*h)/(1.0+g*g));lat=Proj4js.common.asinz(con);if(temp<0)
     175lat=-lat;if((g==0)&&(h==0)){lon=this.long0;}else{lon=Proj4js.common.adjust_lon(Math.atan2(g,h)+this.long0);}}else{var x=p.x-this.x0;var y=p.y-this.y0;con=(this.ml0+y/this.k0)/this.a;phi=con;for(i=0;true;i++){delta_phi=((con+this.e1*Math.sin(2.0*phi)-this.e2*Math.sin(4.0*phi)+this.e3*Math.sin(6.0*phi))/this.e0)-phi;phi+=delta_phi;if(Math.abs(delta_phi)<=Proj4js.common.EPSLN)break;if(i>=max_iter){Proj4js.reportError("tmerc:inverse: Latitude failed to converge");return(95);}}
     176if(Math.abs(phi)<Proj4js.common.HALF_PI){var sin_phi=Math.sin(phi);var cos_phi=Math.cos(phi);var tan_phi=Math.tan(phi);var c=this.ep2*Math.pow(cos_phi,2);var cs=Math.pow(c,2);var t=Math.pow(tan_phi,2);var ts=Math.pow(t,2);con=1.0-this.es*Math.pow(sin_phi,2);var n=this.a/Math.sqrt(con);var r=n*(1.0-this.es)/con;var d=x/(n*this.k0);var ds=Math.pow(d,2);lat=phi-(n*tan_phi*ds/r)*(0.5-ds/24.0*(5.0+3.0*t+10.0*c-4.0*cs-9.0*this.ep2-ds/30.0*(61.0+90.0*t+298.0*c+45.0*ts-252.0*this.ep2-3.0*cs)));lon=Proj4js.common.adjust_lon(this.long0+(d*(1.0-ds/6.0*(1.0+2.0*t+c-ds/20.0*(5.0-2.0*c+28.0*t-3.0*cs+8.0*this.ep2+24.0*ts)))/cos_phi));}else{lat=Proj4js.common.HALF_PI*Proj4js.common.sign(y);lon=this.long0;}}
     177p.x=lon;p.y=lat;return p;}};Proj4js.defs["GOOGLE"]="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";Proj4js.defs["EPSG:900913"]=Proj4js.defs["GOOGLE"];Proj4js.Proj.gstmerc={init:function(){var temp=this.b/this.a;this.e=Math.sqrt(1.0-temp*temp);this.lc=this.long0;this.rs=Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e));var sinz=Math.sin(this.lat0);var pc=Math.asin(sinz/this.rs);var sinzpc=Math.sin(pc);this.cp=Proj4js.common.latiso(0.0,pc,sinzpc)-this.rs*Proj4js.common.latiso(this.e,this.lat0,sinz);this.n2=this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz);this.xs=this.x0;this.ys=this.y0-this.n2*pc;if(!this.title)this.title="Gauss Schreiber transverse mercator";},forward:function(p){var lon=p.x;var lat=p.y;var L=this.rs*(lon-this.lc);var Ls=this.cp+(this.rs*Proj4js.common.latiso(this.e,lat,Math.sin(lat)));var lat1=Math.asin(Math.sin(L)/Proj4js.common.cosh(Ls));var Ls1=Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));p.x=this.xs+(this.n2*Ls1);p.y=this.ys+(this.n2*Math.atan(Proj4js.common.sinh(Ls)/Math.cos(L)));return p;},inverse:function(p){var x=p.x;var y=p.y;var L=Math.atan(Proj4js.common.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2));var lat1=Math.asin(Math.sin((y-this.ys)/this.n2)/Proj4js.common.cosh((x-this.xs)/this.n2));var LC=Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));p.x=this.lc+L/this.rs;p.y=Proj4js.common.invlatiso(this.e,(LC-this.cp)/this.rs);return p;}};Proj4js.Proj.ortho={init:function(def){;this.sin_p14=Math.sin(this.lat0);this.cos_p14=Math.cos(this.lat0);},forward:function(p){var sinphi,cosphi;var dlon;var coslon;var ksp;var g;var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslon=Math.cos(dlon);g=this.sin_p14*sinphi+this.cos_p14*cosphi*coslon;ksp=1.0;if((g>0)||(Math.abs(g)<=Proj4js.common.EPSLN)){var x=this.a*ksp*cosphi*Math.sin(dlon);var y=this.y0+this.a*ksp*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon);}else{Proj4js.reportError("orthoFwdPointError");}
     178p.x=x;p.y=y;return p;},inverse:function(p){var rh;var z;var sinz,cosz;var temp;var con;var lon,lat;p.x-=this.x0;p.y-=this.y0;rh=Math.sqrt(p.x*p.x+p.y*p.y);if(rh>this.a+.0000001){Proj4js.reportError("orthoInvDataError");}
     179z=Proj4js.common.asinz(rh/this.a);sinz=Math.sin(z);cosz=Math.cos(z);lon=this.long0;if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.lat0;}
     180lat=Proj4js.common.asinz(cosz*this.sin_p14+(p.y*sinz*this.cos_p14)/rh);con=Math.abs(lat0)-Proj4js.common.HALF_PI;if(Math.abs(con)<=Proj4js.common.EPSLN){if(this.lat0>=0){lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x,-p.y));}else{lon=Proj4js.common.adjust_lon(this.long0-Math.atan2(-p.x,p.y));}}
     181con=cosz-this.sin_p14*Math.sin(lat);if((Math.abs(con)>=Proj4js.common.EPSLN)||(Math.abs(x)>=Proj4js.common.EPSLN)){lon=Proj4js.common.adjust_lon(this.long0+Math.atan2((p.x*sinz*this.cos_p14),(con*rh)));}
     182p.x=lon;p.y=lat;return p;}};Proj4js.Proj.somerc={init:function(){var phy0=this.lat0;this.lambda0=this.long0;var sinPhy0=Math.sin(phy0);var semiMajorAxis=this.a;var invF=this.rf;var flattening=1/invF;var e2=2*flattening-Math.pow(flattening,2);var e=this.e=Math.sqrt(e2);this.R=semiMajorAxis*Math.sqrt(1-e2)/(1-e2*Math.pow(sinPhy0,2.0));this.alpha=Math.sqrt(1+e2/(1-e2)*Math.pow(Math.cos(phy0),4.0));this.b0=Math.asin(sinPhy0/this.alpha);this.K=Math.log(Math.tan(Math.PI/4.0+this.b0/2.0))
     183-this.alpha*Math.log(Math.tan(Math.PI/4.0+phy0/2.0))
     184+this.alpha*e/2*Math.log((1+e*sinPhy0)/(1-e*sinPhy0));},forward:function(p){var Sa1=Math.log(Math.tan(Math.PI/4.0-p.y/2.0));var Sa2=this.e/2.0*Math.log((1+this.e*Math.sin(p.y))/(1-this.e*Math.sin(p.y)));var S=-this.alpha*(Sa1+Sa2)+this.K;var b=2.0*(Math.atan(Math.exp(S))-Math.PI/4.0);var I=this.alpha*(p.x-this.lambda0);var rotI=Math.atan(Math.sin(I)/(Math.sin(this.b0)*Math.tan(b)+
     185Math.cos(this.b0)*Math.cos(I)));var rotB=Math.asin(Math.cos(this.b0)*Math.sin(b)-
     186Math.sin(this.b0)*Math.cos(b)*Math.cos(I));p.y=this.R/2.0*Math.log((1+Math.sin(rotB))/(1-Math.sin(rotB)))
     187+this.y0;p.x=this.R*rotI+this.x0;return p;},inverse:function(p){var Y=p.x-this.x0;var X=p.y-this.y0;var rotI=Y/this.R;var rotB=2*(Math.atan(Math.exp(X/this.R))-Math.PI/4.0);var b=Math.asin(Math.cos(this.b0)*Math.sin(rotB)
     188+Math.sin(this.b0)*Math.cos(rotB)*Math.cos(rotI));var I=Math.atan(Math.sin(rotI)/(Math.cos(this.b0)*Math.cos(rotI)-Math.sin(this.b0)*Math.tan(rotB)));var lambda=this.lambda0+I/this.alpha;var S=0.0;var phy=b;var prevPhy=-1000.0;var iteration=0;while(Math.abs(phy-prevPhy)>0.0000001)
     189{if(++iteration>20)
     190{Proj4js.reportError("omercFwdInfinity");return;}
     191S=1.0/this.alpha*(Math.log(Math.tan(Math.PI/4.0+b/2.0))-this.K)
     192+this.e*Math.log(Math.tan(Math.PI/4.0
     193+Math.asin(this.e*Math.sin(phy))/2.0));prevPhy=phy;phy=2.0*Math.atan(Math.exp(S))-Math.PI/2.0;}
     194p.x=lambda;p.y=phy;return p;}};Proj4js.Proj.stere={ssfn_:function(phit,sinphi,eccen){sinphi*=eccen;return(Math.tan(.5*(Proj4js.common.HALF_PI+phit))*Math.pow((1.-sinphi)/(1.+sinphi),.5*eccen));},TOL:1.e-8,NITER:8,CONV:1.e-10,S_POLE:0,N_POLE:1,OBLIQ:2,EQUIT:3,init:function(){this.phits=this.lat_ts?this.lat_ts:Proj4js.common.HALF_PI;var t=Math.abs(this.lat0);if((Math.abs(t)-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.mode=this.lat0<0.?this.S_POLE:this.N_POLE;}else{this.mode=t>Proj4js.common.EPSLN?this.OBLIQ:this.EQUIT;}
     195this.phits=Math.abs(this.phits);if(this.es){var X;switch(this.mode){case this.N_POLE:case this.S_POLE:if(Math.abs(this.phits-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.akm1=2.*this.k0/Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e));}else{t=Math.sin(this.phits);this.akm1=Math.cos(this.phits)/Proj4js.common.tsfnz(this.e,this.phits,t);t*=this.e;this.akm1/=Math.sqrt(1.-t*t);}
     196break;case this.EQUIT:this.akm1=2.*this.k0;break;case this.OBLIQ:t=Math.sin(this.lat0);X=2.*Math.atan(this.ssfn_(this.lat0,t,this.e))-Proj4js.common.HALF_PI;t*=this.e;this.akm1=2.*this.k0*Math.cos(this.lat0)/Math.sqrt(1.-t*t);this.sinX1=Math.sin(X);this.cosX1=Math.cos(X);break;}}else{switch(this.mode){case this.OBLIQ:this.sinph0=Math.sin(this.lat0);this.cosph0=Math.cos(this.lat0);case this.EQUIT:this.akm1=2.*this.k0;break;case this.S_POLE:case this.N_POLE:this.akm1=Math.abs(this.phits-Proj4js.common.HALF_PI)>=Proj4js.common.EPSLN?Math.cos(this.phits)/Math.tan(Proj4js.common.FORTPI-.5*this.phits):2.*this.k0;break;}}},forward:function(p){var lon=p.x;lon=Proj4js.common.adjust_lon(lon-this.long0);var lat=p.y;var x,y;if(this.sphere){var sinphi,cosphi,coslam,sinlam;sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslam=Math.cos(lon);sinlam=Math.sin(lon);switch(this.mode){case this.EQUIT:y=1.+cosphi*coslam;if(y<=Proj4js.common.EPSLN){F_ERROR;}
     197y=this.akm1/y;x=y*cosphi*sinlam;y*=sinphi;break;case this.OBLIQ:y=1.+this.sinph0*sinphi+this.cosph0*cosphi*coslam;if(y<=Proj4js.common.EPSLN){F_ERROR;}
     198y=this.akm1/y;x=y*cosphi*sinlam;y*=this.cosph0*sinphi-this.sinph0*cosphi*coslam;break;case this.N_POLE:coslam=-coslam;lat=-lat;case this.S_POLE:if(Math.abs(lat-Proj4js.common.HALF_PI)<this.TOL){F_ERROR;}
     199y=this.akm1*Math.tan(Proj4js.common.FORTPI+.5*lat);x=sinlam*y;y*=coslam;break;}}else{coslam=Math.cos(lon);sinlam=Math.sin(lon);sinphi=Math.sin(lat);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){X=2.*Math.atan(this.ssfn_(lat,sinphi,this.e));sinX=Math.sin(X-Proj4js.common.HALF_PI);cosX=Math.cos(X);}
     200switch(this.mode){case this.OBLIQ:A=this.akm1/(this.cosX1*(1.+this.sinX1*sinX+this.cosX1*cosX*coslam));y=A*(this.cosX1*sinX-this.sinX1*cosX*coslam);x=A*cosX;break;case this.EQUIT:A=2.*this.akm1/(1.+cosX*coslam);y=A*sinX;x=A*cosX;break;case this.S_POLE:lat=-lat;coslam=-coslam;sinphi=-sinphi;case this.N_POLE:x=this.akm1*Proj4js.common.tsfnz(this.e,lat,sinphi);y=-x*cos