source: trunk/zoo-api/js/ZOO-api.js @ 101

Last change on this file since 101 was 101, checked in by reluc, 13 years ago

Ending the ZOO-api documentation
Adding the capability to parse WPS Reference Response.

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

Search

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