Changeset 85


Ignore:
Timestamp:
Jan 20, 2011, 4:36:00 PM (14 years ago)
Author:
reluc
Message:

Update ZOO.Format.GML

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

Legend:

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

    r81 r85  
    28832883  xy: true,
    28842884  /**
     2885   * Property: extractAttributes
     2886   * {Boolean} Could we extract attributes
     2887   */
     2888  extractAttributes: true,
     2889  /**
    28852890   * Constructor: ZOO.Format.GML
    28862891   * Create a new parser for GML.
     
    29172922    var gmlns = Namespace(this.namespaces['gml']);
    29182923    var featureNodes = data..gmlns::featureMember;
     2924    if (data.localName() == 'featureMember')
     2925      featureNodes = data;
    29192926    var features = [];
    29202927    for(var i=0,len=featureNodes.length(); i<len; i++) {
  • trunk/zoo-api/js/ZOO-proj4js.js

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