source: branches/prototype-v0/zoo-project/zoo-api/js/ZOO-api.js @ 883

Last change on this file since 883 was 883, checked in by djay, 6 years ago

Add the capability to parse Execute response when service has been called asynchronously

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

Search

Context Navigation

ZOO Sponsors

http://www.zoo-project.org/trac/chrome/site/img/geolabs-logo.pnghttp://www.zoo-project.org/trac/chrome/site/img/neogeo-logo.png http://www.zoo-project.org/trac/chrome/site/img/apptech-logo.png http://www.zoo-project.org/trac/chrome/site/img/3liz-logo.png http://www.zoo-project.org/trac/chrome/site/img/gateway-logo.png

Become a sponsor !

Knowledge partners

http://www.zoo-project.org/trac/chrome/site/img/ocu-logo.png http://www.zoo-project.org/trac/chrome/site/img/gucas-logo.png http://www.zoo-project.org/trac/chrome/site/img/polimi-logo.png http://www.zoo-project.org/trac/chrome/site/img/fem-logo.png http://www.zoo-project.org/trac/chrome/site/img/supsi-logo.png http://www.zoo-project.org/trac/chrome/site/img/cumtb-logo.png

Become a knowledge partner

Related links

http://zoo-project.org/img/ogclogo.png http://zoo-project.org/img/osgeologo.png