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

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

Adding getGeodesicLength
Adding getGeodesicArea
Adding ZOO.Geometry.Polygon.createRegularPolygon
Adding more documentation, need to be ended

File size: 184.6 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   * Constructor: ZOO.Format.GML
2886   * Create a new parser for GML.
2887   *
2888   * Parameters:
2889   * options - {Object} An optional object whose properties will be set on
2890   *     this instance.
2891   */
2892  initialize: function(options) {
2893    // compile regular expressions once instead of every time they are used
2894    this.regExes = {
2895      trimSpace: (/^\s*|\s*$/g),
2896      removeSpace: (/\s*/g),
2897      splitSpace: (/\s+/),
2898      trimComma: (/\s*,\s*/g)
2899    };
2900    ZOO.Format.prototype.initialize.apply(this, [options]);
2901  },
2902  /**
2903   * Method: read
2904   * Read data from a string, and return a list of features.
2905   *
2906   * Parameters:
2907   * data - {String} data to read/parse.
2908   *
2909   * Returns:
2910   * {Array(<ZOO.Feature>)} An array of features.
2911   */
2912  read: function(data) {
2913    this.features = [];
2914    data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
2915    data = new XML(data);
2916
2917    var gmlns = Namespace(this.namespaces['gml']);
2918    var featureNodes = data..gmlns::featureMember;
2919    var features = [];
2920    for(var i=0,len=featureNodes.length(); i<len; i++) {
2921      var feature = this.parseFeature(featureNodes[i]);
2922      if(feature) {
2923        features.push(feature);
2924      }
2925    }
2926    return features;
2927  },
2928  /**
2929   * Method: parseFeature
2930   * This function is the core of the GML parsing code in ZOO.
2931   *    It creates the geometries that are then attached to the returned
2932   *    feature, and calls parseAttributes() to get attribute data out.
2933   *   
2934   * Parameters:
2935   * node - {E4XElement} A GML feature node.
2936   */
2937  parseFeature: function(node) {
2938    // only accept one geometry per feature - look for highest "order"
2939    var gmlns = Namespace(this.namespaces['gml']);
2940    var order = ["MultiPolygon", "Polygon",
2941                 "MultiLineString", "LineString",
2942                 "MultiPoint", "Point", "Envelope", "Box"];
2943    var type, nodeList, geometry, parser;
2944    for(var i=0; i<order.length; ++i) {
2945      type = order[i];
2946      nodeList = node.descendants(QName(gmlns,type));
2947      if (nodeList.length() > 0) {
2948        var parser = this.parseGeometry[type.toLowerCase()];
2949        if(parser) {
2950          geometry = parser.apply(this, [nodeList[0]]);
2951          if (this.internalProjection && this.externalProjection) {
2952            geometry.transform(this.externalProjection, 
2953                               this.internalProjection); 
2954          }                       
2955        }
2956        // stop looking for different geometry types
2957        break;
2958      }
2959    }
2960    var attributes;
2961    if(this.extractAttributes) {
2962      attributes = this.parseAttributes(node);
2963    }
2964    var feature = new ZOO.Feature(geometry, attributes);
2965    return feature;
2966  },
2967  /**
2968   * Property: parseGeometry
2969   * Properties of this object are the functions that parse geometries based
2970   *     on their type.
2971   */
2972  parseGeometry: {
2973    /**
2974     * Method: parseGeometry.point
2975     * Given a GML node representing a point geometry, create a ZOO
2976     *     point geometry.
2977     *
2978     * Parameters:
2979     * node - {E4XElement} A GML node.
2980     *
2981     * Returns:
2982     * {<ZOO.Geometry.Point>} A point geometry.
2983     */
2984    'point': function(node) {
2985      /**
2986       * Three coordinate variations to consider:
2987       * 1) <gml:pos>x y z</gml:pos>
2988       * 2) <gml:coordinates>x, y, z</gml:coordinates>
2989       * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
2990       */
2991      var nodeList, coordString;
2992      var coords = [];
2993      // look for <gml:pos>
2994      var nodeList = node..*::pos;
2995      if(nodeList.length() > 0) {
2996        coordString = nodeList[0].toString();
2997        coordString = coordString.replace(this.regExes.trimSpace, "");
2998        coords = coordString.split(this.regExes.splitSpace);
2999      }
3000      // look for <gml:coordinates>
3001      if(coords.length == 0) {
3002        nodeList = node..*::coordinates;
3003        if(nodeList.length() > 0) {
3004          coordString = nodeList[0].toString();
3005          coordString = coordString.replace(this.regExes.removeSpace,"");
3006          coords = coordString.split(",");
3007        }
3008      }
3009      // look for <gml:coord>
3010      if(coords.length == 0) {
3011        nodeList = node..*::coord;
3012        if(nodeList.length() > 0) {
3013          var xList = nodeList[0].*::X;
3014          var yList = nodeList[0].*::Y;
3015          if(xList.length() > 0 && yList.length() > 0)
3016            coords = [xList[0].toString(),
3017                      yList[0].toString()];
3018        }
3019      }
3020      // preserve third dimension
3021      if(coords.length == 2)
3022        coords[2] = null;
3023      if (this.xy)
3024        return new ZOO.Geometry.Point(coords[0],coords[1],coords[2]);
3025      else
3026        return new ZOO.Geometry.Point(coords[1],coords[0],coords[2]);
3027    },
3028    /**
3029     * Method: parseGeometry.multipoint
3030     * Given a GML node representing a multipoint geometry, create a
3031     *     ZOO multipoint geometry.
3032     *
3033     * Parameters:
3034     * node - {E4XElement} A GML node.
3035     *
3036     * Returns:
3037     * {<ZOO.Geometry.MultiPoint>} A multipoint geometry.
3038     */
3039    'multipoint': function(node) {
3040      var nodeList = node..*::Point;
3041      var components = [];
3042      if(nodeList.length() > 0) {
3043        var point;
3044        for(var i=0, len=nodeList.length(); i<len; ++i) {
3045          point = this.parseGeometry.point.apply(this, [nodeList[i]]);
3046          if(point)
3047            components.push(point);
3048        }
3049      }
3050      return new ZOO.Geometry.MultiPoint(components);
3051    },
3052    /**
3053     * Method: parseGeometry.linestring
3054     * Given a GML node representing a linestring geometry, create a
3055     *     ZOO linestring geometry.
3056     *
3057     * Parameters:
3058     * node - {E4XElement} A GML node.
3059     *
3060     * Returns:
3061     * {<ZOO.Geometry.LineString>} A linestring geometry.
3062     */
3063    'linestring': function(node, ring) {
3064      /**
3065       * Two coordinate variations to consider:
3066       * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
3067       * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
3068       */
3069      var nodeList, coordString;
3070      var coords = [];
3071      var points = [];
3072      // look for <gml:posList>
3073      nodeList = node..*::posList;
3074      if(nodeList.length() > 0) {
3075        coordString = nodeList[0].toString();
3076        coordString = coordString.replace(this.regExes.trimSpace, "");
3077        coords = coordString.split(this.regExes.splitSpace);
3078        var dim = parseInt(nodeList[0].@dimension);
3079        var j, x, y, z;
3080        for(var i=0; i<coords.length/dim; ++i) {
3081          j = i * dim;
3082          x = coords[j];
3083          y = coords[j+1];
3084          z = (dim == 2) ? null : coords[j+2];
3085          if (this.xy)
3086            points.push(new ZOO.Geometry.Point(x, y, z));
3087          else
3088            points.push(new Z0O.Geometry.Point(y, x, z));
3089        }
3090      }
3091      // look for <gml:coordinates>
3092      if(coords.length == 0) {
3093        nodeList = node..*::coordinates;
3094        if(nodeList.length() > 0) {
3095          coordString = nodeList[0].toString();
3096          coordString = coordString.replace(this.regExes.trimSpace,"");
3097          coordString = coordString.replace(this.regExes.trimComma,",");
3098          var pointList = coordString.split(this.regExes.splitSpace);
3099          for(var i=0; i<pointList.length; ++i) {
3100            coords = pointList[i].split(",");
3101            if(coords.length == 2)
3102              coords[2] = null;
3103            if (this.xy)
3104              points.push(new ZOO.Geometry.Point(coords[0],coords[1],coords[2]));
3105            else
3106              points.push(new ZOO.Geometry.Point(coords[1],coords[0],coords[2]));
3107          }
3108        }
3109      }
3110      var line = null;
3111      if(points.length != 0) {
3112        if(ring)
3113          line = new ZOO.Geometry.LinearRing(points);
3114        else
3115          line = new ZOO.Geometry.LineString(points);
3116      }
3117      return line;
3118    },
3119    /**
3120     * Method: parseGeometry.multilinestring
3121     * Given a GML node representing a multilinestring geometry, create a
3122     *     ZOO multilinestring geometry.
3123     *
3124     * Parameters:
3125     * node - {E4XElement} A GML node.
3126     *
3127     * Returns:
3128     * {<ZOO.Geometry.MultiLineString>} A multilinestring geometry.
3129     */
3130    'multilinestring': function(node) {
3131      var nodeList = node..*::LineString;
3132      var components = [];
3133      if(nodeList.length() > 0) {
3134        var line;
3135        for(var i=0, len=nodeList.length(); i<len; ++i) {
3136          line = this.parseGeometry.linestring.apply(this, [nodeList[i]]);
3137          if(point)
3138            components.push(point);
3139        }
3140      }
3141      return new ZOO.Geometry.MultiLineString(components);
3142    },
3143    /**
3144     * Method: parseGeometry.polygon
3145     * Given a GML node representing a polygon geometry, create a
3146     *     ZOO polygon geometry.
3147     *
3148     * Parameters:
3149     * node - {E4XElement} A GML node.
3150     *
3151     * Returns:
3152     * {<ZOO.Geometry.Polygon>} A polygon geometry.
3153     */
3154    'polygon': function(node) {
3155      nodeList = node..*::LinearRing;
3156      var components = [];
3157      if(nodeList.length() > 0) {
3158        // this assumes exterior ring first, inner rings after
3159        var ring;
3160        for(var i=0, len = nodeList.length(); i<len; ++i) {
3161          ring = this.parseGeometry.linestring.apply(this,[nodeList[i], true]);
3162          if(ring)
3163            components.push(ring);
3164        }
3165      }
3166      return new ZOO.Geometry.Polygon(components);
3167    },
3168    /**
3169     * Method: parseGeometry.multipolygon
3170     * Given a GML node representing a multipolygon geometry, create a
3171     *     ZOO multipolygon geometry.
3172     *
3173     * Parameters:
3174     * node - {E4XElement} A GML node.
3175     *
3176     * Returns:
3177     * {<ZOO.Geometry.MultiPolygon>} A multipolygon geometry.
3178     */
3179    'multipolygon': function(node) {
3180      var nodeList = node..*::Polygon;
3181      var components = [];
3182      if(nodeList.length() > 0) {
3183        var polygon;
3184        for(var i=0, len=nodeList.length(); i<len; ++i) {
3185          polygon = this.parseGeometry.polygon.apply(this, [nodeList[i]]);
3186          if(polygon)
3187            components.push(polygon);
3188        }
3189      }
3190      return new ZOO.Geometry.MultiPolygon(components);
3191    },
3192    /**
3193     * Method: parseGeometry.polygon
3194     * Given a GML node representing an envelope, create a
3195     *     ZOO polygon geometry.
3196     *
3197     * Parameters:
3198     * node - {E4XElement} A GML node.
3199     *
3200     * Returns:
3201     * {<ZOO.Geometry.Polygon>} A polygon geometry.
3202     */
3203    'envelope': function(node) {
3204      var components = [];
3205      var coordString;
3206      var envelope;
3207      var lpoint = node..*::lowerCorner;
3208      if (lpoint.length() > 0) {
3209        var coords = [];
3210        if(lpoint.length() > 0) {
3211          coordString = lpoint[0].toString();
3212          coordString = coordString.replace(this.regExes.trimSpace, "");
3213          coords = coordString.split(this.regExes.splitSpace);
3214        }
3215        if(coords.length == 2)
3216          coords[2] = null;
3217        if (this.xy)
3218          var lowerPoint = new ZOO.Geometry.Point(coords[0], coords[1],coords[2]);
3219        else
3220          var lowerPoint = new ZOO.Geometry.Point(coords[1], coords[0],coords[2]);
3221      }
3222      var upoint = node..*::upperCorner;
3223      if (upoint.length() > 0) {
3224        var coords = [];
3225        if(upoint.length > 0) {
3226          coordString = upoint[0].toString();
3227          coordString = coordString.replace(this.regExes.trimSpace, "");
3228          coords = coordString.split(this.regExes.splitSpace);
3229        }
3230        if(coords.length == 2)
3231          coords[2] = null;
3232        if (this.xy)
3233          var upperPoint = new ZOO.Geometry.Point(coords[0], coords[1],coords[2]);
3234        else
3235          var upperPoint = new ZOO.Geometry.Point(coords[1], coords[0],coords[2]);
3236      }
3237      if (lowerPoint && upperPoint) {
3238        components.push(new ZOO.Geometry.Point(lowerPoint.x, lowerPoint.y));
3239        components.push(new ZOO.Geometry.Point(upperPoint.x, lowerPoint.y));
3240        components.push(new ZOO.Geometry.Point(upperPoint.x, upperPoint.y));
3241        components.push(new ZOO.Geometry.Point(lowerPoint.x, upperPoint.y));
3242        components.push(new ZOO.Geometry.Point(lowerPoint.x, lowerPoint.y));
3243        var ring = new ZOO.Geometry.LinearRing(components);
3244        envelope = new ZOO.Geometry.Polygon([ring]);
3245      }
3246      return envelope;
3247    }
3248  },
3249  /**
3250   * Method: parseAttributes
3251   *
3252   * Parameters:
3253   * node - {<E4XElement>}
3254   *
3255   * Returns:
3256   * {Object} An attributes object.
3257   */
3258  parseAttributes: function(node) {
3259    var attributes = {};
3260    // assume attributes are children of the first type 1 child
3261    var childNode = node.*::*[0];
3262    var child, grandchildren;
3263    var children = childNode.*::*;
3264    for(var i=0, len=children.length(); i<len; ++i) {
3265      child = children[i];
3266      grandchildren = child..*::*;
3267      if(grandchildren.length() == 1) {
3268        var name = child.localName();
3269        var value = child.toString();
3270        if (value) {
3271          value = value.replace(this.regExes.trimSpace, "");
3272          attributes[name] = value;
3273        } else
3274          attributes[name] = null;
3275      }
3276    }
3277    return attributes;
3278  },
3279  /**
3280   * Method: write
3281   * Generate a GML document string given a list of features.
3282   *
3283   * Parameters:
3284   * features - {Array(<ZOO.Feature>)} List of features to
3285   *     serialize into a string.
3286   *
3287   * Returns:
3288   * {String} A string representing the GML document.
3289   */
3290  write: function(features) {
3291    if(!(features instanceof Array)) {
3292      features = [features];
3293    }
3294    var pfx = this.defaultPrefix;
3295    var name = pfx+':'+this.collectionName;
3296    var gml = new XML('<'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'" xmlns:gml="'+this.namespaces['gml']+'" xmlns:xsi="'+this.namespaces['xsi']+'" xsi:schemaLocation="'+this.schemaLocation+'"></'+name+'>');
3297    for(var i=0; i<features.length; i++) {
3298      gml.*::*[i] = this.createFeature(features[i]);
3299    }
3300    return gml.toXMLString();
3301  },
3302  /**
3303   * Method: createFeature
3304   * Accept an ZOO.Feature, and build a GML node for it.
3305   *
3306   * Parameters:
3307   * feature - {<ZOO.Feature>} The feature to be built as GML.
3308   *
3309   * Returns:
3310   * {E4XElement} A node reprensting the feature in GML.
3311   */
3312  createFeature: function(feature) {
3313    var pfx = this.defaultPrefix;
3314    var name = pfx+':'+this.featureName;
3315    var fid = feature.fid || feature.id;
3316    var gml = new XML('<gml:featureMember xmlns:gml="'+this.namespaces['gml']+'"><'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'" fid="'+fid+'"></'+name+'></gml:featureMember>');
3317    var geometry = feature.geometry;
3318    gml.*::*[0].*::* = this.buildGeometryNode(geometry);
3319    for(var attr in feature.attributes) {
3320      var attrNode = new XML('<'+pfx+':'+attr+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'">'+feature.attributes[attr]+'</'+pfx+':'+attr+'>');
3321      gml.*::*[0].appendChild(attrNode);
3322    }
3323    return gml;
3324  },
3325  /**
3326   * Method: buildGeometryNode
3327   *
3328   * Parameters:
3329   * geometry - {<ZOO.Geometry>} The geometry to be built as GML.
3330   *
3331   * Returns:
3332   * {E4XElement} A node reprensting the geometry in GML.
3333   */
3334  buildGeometryNode: function(geometry) {
3335    if (this.externalProjection && this.internalProjection) {
3336      geometry = geometry.clone();
3337      geometry.transform(this.internalProjection, 
3338          this.externalProjection);
3339    }   
3340    var className = geometry.CLASS_NAME;
3341    var type = className.substring(className.lastIndexOf(".") + 1);
3342    var builder = this.buildGeometry[type.toLowerCase()];
3343    var pfx = this.defaultPrefix;
3344    var name = pfx+':'+this.geometryName;
3345    var gml = new XML('<'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'"></'+name+'>');
3346    if (builder)
3347      gml.*::* = builder.apply(this, [geometry]);
3348    return gml;
3349  },
3350  /**
3351   * Property: buildGeometry
3352   * Object containing methods to do the actual geometry node building
3353   *     based on geometry type.
3354   */
3355  buildGeometry: {
3356    /**
3357     * Method: buildGeometry.point
3358     * Given a ZOO point geometry, create a GML point.
3359     *
3360     * Parameters:
3361     * geometry - {<ZOO.Geometry.Point>} A point geometry.
3362     *
3363     * Returns:
3364     * {E4XElement} A GML point node.
3365     */
3366    'point': function(geometry) {
3367      var gml = new XML('<gml:Point xmlns:gml="'+this.namespaces['gml']+'"></gml:Point>');
3368      gml.*::*[0] = this.buildCoordinatesNode(geometry);
3369      return gml;
3370    },
3371    /**
3372     * Method: buildGeometry.multipoint
3373     * Given a ZOO multipoint geometry, create a GML multipoint.
3374     *
3375     * Parameters:
3376     * geometry - {<ZOO.Geometry.MultiPoint>} A multipoint geometry.
3377     *
3378     * Returns:
3379     * {E4XElement} A GML multipoint node.
3380     */
3381    'multipoint': function(geometry) {
3382      var gml = new XML('<gml:MultiPoint xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiPoint>');
3383      var points = geometry.components;
3384      var pointMember;
3385      for(var i=0; i<points.length; i++) { 
3386        pointMember = new XML('<gml:pointMember xmlns:gml="'+this.namespaces['gml']+'"></gml:pointMember>');
3387        pointMember.*::* = this.buildGeometry.point.apply(this,[points[i]]);
3388        gml.*::*[i] = pointMember;
3389      }
3390      return gml;           
3391    },
3392    /**
3393     * Method: buildGeometry.linestring
3394     * Given a ZOO linestring geometry, create a GML linestring.
3395     *
3396     * Parameters:
3397     * geometry - {<ZOO.Geometry.LineString>} A linestring geometry.
3398     *
3399     * Returns:
3400     * {E4XElement} A GML linestring node.
3401     */
3402    'linestring': function(geometry) {
3403      var gml = new XML('<gml:LineString xmlns:gml="'+this.namespaces['gml']+'"></gml:LineString>');
3404      gml.*::*[0] = this.buildCoordinatesNode(geometry);
3405      return gml;
3406    },
3407    /**
3408     * Method: buildGeometry.multilinestring
3409     * Given a ZOO multilinestring geometry, create a GML
3410     *     multilinestring.
3411     *
3412     * Parameters:
3413     * geometry - {<ZOO.Geometry.MultiLineString>} A multilinestring
3414     *     geometry.
3415     *
3416     * Returns:
3417     * {E4XElement} A GML multilinestring node.
3418     */
3419    'multilinestring': function(geometry) {
3420      var gml = new XML('<gml:MultiLineString xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiLineString>');
3421      var lines = geometry.components;
3422      var lineMember;
3423      for(var i=0; i<lines.length; i++) { 
3424        lineMember = new XML('<gml:lineStringMember xmlns:gml="'+this.namespaces['gml']+'"></gml:lineStringMember>');
3425        lineMember.*::* = this.buildGeometry.linestring.apply(this,[lines[i]]);
3426        gml.*::*[i] = lineMember;
3427      }
3428      return gml;           
3429    },
3430    /**
3431     * Method: buildGeometry.linearring
3432     * Given a ZOO linearring geometry, create a GML linearring.
3433     *
3434     * Parameters:
3435     * geometry - {<ZOO.Geometry.LinearRing>} A linearring geometry.
3436     *
3437     * Returns:
3438     * {E4XElement} A GML linearring node.
3439     */
3440    'linearring': function(geometry) {
3441      var gml = new XML('<gml:LinearRing xmlns:gml="'+this.namespaces['gml']+'"></gml:LinearRing>');
3442      gml.*::*[0] = this.buildCoordinatesNode(geometry);
3443      return gml;
3444    },
3445    /**
3446     * Method: buildGeometry.polygon
3447     * Given an ZOO polygon geometry, create a GML polygon.
3448     *
3449     * Parameters:
3450     * geometry - {<ZOO.Geometry.Polygon>} A polygon geometry.
3451     *
3452     * Returns:
3453     * {E4XElement} A GML polygon node.
3454     */
3455    'polygon': function(geometry) {
3456      var gml = new XML('<gml:Polygon xmlns:gml="'+this.namespaces['gml']+'"></gml:Polygon>');
3457      var rings = geometry.components;
3458      var ringMember, type;
3459      for(var i=0; i<rings.length; ++i) {
3460        type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
3461        var ringMember = new XML('<gml:'+type+' xmlns:gml="'+this.namespaces['gml']+'"></gml:'+type+'>');
3462        ringMember.*::* = this.buildGeometry.linearring.apply(this,[rings[i]]);
3463        gml.*::*[i] = ringMember;
3464      }
3465      return gml;
3466    },
3467    /**
3468     * Method: buildGeometry.multipolygon
3469     * Given a ZOO multipolygon geometry, create a GML multipolygon.
3470     *
3471     * Parameters:
3472     * geometry - {<ZOO.Geometry.MultiPolygon>} A multipolygon
3473     *     geometry.
3474     *
3475     * Returns:
3476     * {E4XElement} A GML multipolygon node.
3477     */
3478    'multipolygon': function(geometry) {
3479      var gml = new XML('<gml:MultiPolygon xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiPolygon>');
3480      var polys = geometry.components;
3481      var polyMember;
3482      for(var i=0; i<polys.length; i++) { 
3483        polyMember = new XML('<gml:polygonMember xmlns:gml="'+this.namespaces['gml']+'"></gml:polygonMember>');
3484        polyMember.*::* = this.buildGeometry.polygon.apply(this,[polys[i]]);
3485        gml.*::*[i] = polyMember;
3486      }
3487      return gml;           
3488    }
3489  },
3490  /**
3491   * Method: buildCoordinatesNode
3492   * builds the coordinates XmlNode
3493   * (code)
3494   * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
3495   * (end)
3496   * Parameters:
3497   * geometry - {<ZOO.Geometry>}
3498   *
3499   * Returns:
3500   * {E4XElement} created E4XElement
3501   */
3502  buildCoordinatesNode: function(geometry) {
3503    var parts = [];
3504    if(geometry instanceof ZOO.Bounds){
3505      parts.push(geometry.left + "," + geometry.bottom);
3506      parts.push(geometry.right + "," + geometry.top);
3507    } else {
3508      var points = (geometry.components) ? geometry.components : [geometry];
3509      for(var i=0; i<points.length; i++) {
3510        parts.push(points[i].x + "," + points[i].y);               
3511      }           
3512    }
3513    return new XML('<gml:coordinates xmlns:gml="'+this.namespaces['gml']+'" decimal="." cs=", " ts=" ">'+parts.join(" ")+'</gml:coordinates>');
3514  },
3515  CLASS_NAME: 'ZOO.Format.GML'
3516});
3517/**
3518 * Class: ZOO.Format.WPS
3519 * Read/Write WPS. Create a new instance with the <ZOO.Format.WPS>
3520 *     constructor. Supports only parseExecuteResponse.
3521 *
3522 * Inherits from:
3523 *  - <ZOO.Format>
3524 */
3525ZOO.Format.WPS = ZOO.Class(ZOO.Format, {
3526  /**
3527   * Property: schemaLocation
3528   * {String} Schema location for a particular minor version.
3529   */
3530  schemaLocation: "http://www.opengis.net/wps/1.0.0/../wpsExecute_request.xsd",
3531  /**
3532   * Property: namespaces
3533   * {Object} Mapping of namespace aliases to namespace URIs.
3534   */
3535  namespaces: {
3536    ows: "http://www.opengis.net/ows/1.1",
3537    wps: "http://www.opengis.net/wps/1.0.0",
3538    xlink: "http://www.w3.org/1999/xlink",
3539    xsi: "http://www.w3.org/2001/XMLSchema-instance",
3540  },
3541  /**
3542   * Method: read
3543   *
3544   * Parameters:
3545   * data - {String} A WPS xml document
3546   *
3547   * Returns:
3548   * {Object} Execute response.
3549   */
3550  read:function(data) {
3551    data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
3552    data = new XML(data);
3553    switch (data.localName()) {
3554      case 'ExecuteResponse':
3555        return this.parseExecuteResponse(data);
3556      default:
3557        return null;
3558    }
3559  },
3560  /**
3561   * Method: parseExecuteResponse
3562   *
3563   * Parameters:
3564   * node - {E4XElement} A WPS ExecuteResponse document
3565   *
3566   * Returns:
3567   * {Object} Execute response.
3568   */
3569  parseExecuteResponse: function(node) {
3570    var outputs = node.*::ProcessOutputs.*::Output;
3571    if (outputs.length() > 0) {
3572      var data = outputs[0].*::Data.*::*[0];
3573      var builder = this.parseData[data.localName().toLowerCase()];
3574      if (builder)
3575        return builder.apply(this,[data]);
3576      else
3577        return null;
3578    } else
3579      return null;
3580  },
3581  /**
3582   * Property: parseData
3583   * Object containing methods to analyse data response.
3584   */
3585  parseData: {
3586    /**
3587     * Method: parseData.complexdata
3588     * Given an Object representing the WPS complex data response.
3589     *
3590     * Parameters:
3591     * node - {E4XElement} A WPS node.
3592     *
3593     * Returns:
3594     * {Object} A WPS complex data response.
3595     */
3596    'complexdata': function(node) {
3597      var result = {value:node.toString()};
3598      if (node.@mimeType.length()>0)
3599        result.mimeType = node.@mimeType;
3600      if (node.@encoding.length()>0)
3601        result.encoding = node.@encoding;
3602      if (node.@schema.length()>0)
3603        result.schema = node.@schema;
3604      return result;
3605    },
3606    /**
3607     * Method: parseData.literaldata
3608     * Given an Object representing the WPS literal data response.
3609     *
3610     * Parameters:
3611     * node - {E4XElement} A WPS node.
3612     *
3613     * Returns:
3614     * {Object} A WPS literal data response.
3615     */
3616    'literaldata': function(node) {
3617      var result = {value:node.toString()};
3618      if (node.@dataType.length()>0)
3619        result.dataType = node.@dataType;
3620      if (node.@uom.length()>0)
3621        result.uom = node.@uom;
3622      return result;
3623    }
3624  },
3625  CLASS_NAME: 'ZOO.Format.WPS'
3626});
3627
3628/**
3629 * Class: ZOO.Feature
3630 * Vector features use the ZOO.Geometry classes as geometry description.
3631 * They have an 'attributes' property, which is the data object
3632 */
3633ZOO.Feature = ZOO.Class({
3634  /**
3635   * Property: fid
3636   * {String}
3637   */
3638  fid: null,
3639  /**
3640   * Property: geometry
3641   * {<ZOO.Geometry>}
3642   */
3643  geometry: null,
3644  /**
3645   * Property: attributes
3646   * {Object} This object holds arbitrary properties that describe the
3647   *     feature.
3648   */
3649  attributes: null,
3650  /**
3651   * Property: bounds
3652   * {<ZOO.Bounds>} The box bounding that feature's geometry, that
3653   *     property can be set by an <ZOO.Format> object when
3654   *     deserializing the feature, so in most cases it represents an
3655   *     information set by the server.
3656   */
3657  bounds: null,
3658  /**
3659   * Constructor: ZOO.Feature
3660   * Create a vector feature.
3661   *
3662   * Parameters:
3663   * geometry - {<ZOO.Geometry>} The geometry that this feature
3664   *     represents.
3665   * attributes - {Object} An optional object that will be mapped to the
3666   *     <attributes> property.
3667   */
3668  initialize: function(geometry, attributes) {
3669    this.geometry = geometry ? geometry : null;
3670    this.attributes = {};
3671    if (attributes)
3672      this.attributes = ZOO.extend(this.attributes,attributes);
3673  },
3674  /**
3675   * Method: destroy
3676   * nullify references to prevent circular references and memory leaks
3677   */
3678  destroy: function() {
3679    this.geometry = null;
3680  },
3681  /**
3682   * Method: clone
3683   * Create a clone of this vector feature.  Does not set any non-standard
3684   *     properties.
3685   *
3686   * Returns:
3687   * {<ZOO.Feature>} An exact clone of this vector feature.
3688   */
3689  clone: function () {
3690    return new ZOO.Feature(this.geometry ? this.geometry.clone() : null,
3691            this.attributes);
3692  },
3693  /**
3694   * Method: move
3695   * Moves the feature and redraws it at its new location
3696   *
3697   * Parameters:
3698   * x - {Float}
3699   * y - {Float}
3700   */
3701  move: function(x, y) {
3702    if(!this.geometry.move)
3703      return;
3704
3705    this.geometry.move(x,y);
3706    return this.geometry;
3707  },
3708  CLASS_NAME: 'ZOO.Feature'
3709});
3710
3711/**
3712 * Class: ZOO.Geometry
3713 * A Geometry is a description of a geographic object. Create an instance
3714 * of this class with the <ZOO.Geometry> constructor. This is a base class,
3715 * typical geometry types are described by subclasses of this class.
3716 */
3717ZOO.Geometry = ZOO.Class({
3718  /**
3719   * Property: id
3720   * {String} A unique identifier for this geometry.
3721   */
3722  id: null,
3723  /**
3724   * Property: parent
3725   * {<ZOO.Geometry>}This is set when a Geometry is added as component
3726   * of another geometry
3727   */
3728  parent: null,
3729  /**
3730   * Property: bounds
3731   * {<ZOO.Bounds>} The bounds of this geometry
3732   */
3733  bounds: null,
3734  /**
3735   * Constructor: ZOO.Geometry
3736   * Creates a geometry object. 
3737   */
3738  initialize: function() {
3739    //generate unique id
3740  },
3741  /**
3742   * Method: destroy
3743   * Destroy this geometry.
3744   */
3745  destroy: function() {
3746    this.id = null;
3747    this.bounds = null;
3748  },
3749  /**
3750   * Method: clone
3751   * Create a clone of this geometry.  Does not set any non-standard
3752   *     properties of the cloned geometry.
3753   *
3754   * Returns:
3755   * {<ZOO.Geometry>} An exact clone of this geometry.
3756   */
3757  clone: function() {
3758    return new ZOO.Geometry();
3759  },
3760  /**
3761   * Method: extendBounds
3762   * Extend the existing bounds to include the new bounds.
3763   * If geometry's bounds is not yet set, then set a new Bounds.
3764   *
3765   * Parameters:
3766   * newBounds - {<ZOO.Bounds>}
3767   */
3768  extendBounds: function(newBounds){
3769    var bounds = this.getBounds();
3770    if (!bounds)
3771      this.setBounds(newBounds);
3772    else
3773      this.bounds.extend(newBounds);
3774  },
3775  /**
3776   * Set the bounds for this Geometry.
3777   *
3778   * Parameters:
3779   * bounds - {<ZOO.Bounds>}
3780   */
3781  setBounds: function(bounds) {
3782    if (bounds)
3783      this.bounds = bounds.clone();
3784  },
3785  /**
3786   * Method: clearBounds
3787   * Nullify this components bounds and that of its parent as well.
3788   */
3789  clearBounds: function() {
3790    this.bounds = null;
3791    if (this.parent)
3792      this.parent.clearBounds();
3793  },
3794  /**
3795   * Method: getBounds
3796   * Get the bounds for this Geometry. If bounds is not set, it
3797   * is calculated again, this makes queries faster.
3798   *
3799   * Returns:
3800   * {<ZOO.Bounds>}
3801   */
3802  getBounds: function() {
3803    if (this.bounds == null) {
3804      this.calculateBounds();
3805    }
3806    return this.bounds;
3807  },
3808  /**
3809   * Method: calculateBounds
3810   * Recalculate the bounds for the geometry.
3811   */
3812  calculateBounds: function() {
3813    // This should be overridden by subclasses.
3814    return this.bounds;
3815  },
3816  distanceTo: function(geometry, options) {
3817  },
3818  getVertices: function(nodes) {
3819  },
3820  getLength: function() {
3821    return 0.0;
3822  },
3823  getArea: function() {
3824    return 0.0;
3825  },
3826  getCentroid: function() {
3827    return null;
3828  },
3829  /**
3830   * Method: toString
3831   * Returns the Well-Known Text representation of a geometry
3832   *
3833   * Returns:
3834   * {String} Well-Known Text
3835   */
3836  toString: function() {
3837    return ZOO.Format.WKT.prototype.write(
3838        new ZOO.Feature(this)
3839    );
3840  },
3841  CLASS_NAME: 'ZOO.Geometry'
3842});
3843/**
3844 * Function: OpenLayers.Geometry.fromWKT
3845 * Generate a geometry given a Well-Known Text string.
3846 *
3847 * Parameters:
3848 * wkt - {String} A string representing the geometry in Well-Known Text.
3849 *
3850 * Returns:
3851 * {<ZOO.Geometry>} A geometry of the appropriate class.
3852 */
3853ZOO.Geometry.fromWKT = function(wkt) {
3854  var format = arguments.callee.format;
3855  if(!format) {
3856    format = new ZOO.Format.WKT();
3857    arguments.callee.format = format;
3858  }
3859  var geom;
3860  var result = format.read(wkt);
3861  if(result instanceof ZOO.Feature) {
3862    geom = result.geometry;
3863  } else if(result instanceof Array) {
3864    var len = result.length;
3865    var components = new Array(len);
3866    for(var i=0; i<len; ++i) {
3867      components[i] = result[i].geometry;
3868    }
3869    geom = new ZOO.Geometry.Collection(components);
3870  }
3871  return geom;
3872};
3873ZOO.Geometry.segmentsIntersect = function(seg1, seg2, options) {
3874  var point = options && options.point;
3875  var tolerance = options && options.tolerance;
3876  var intersection = false;
3877  var x11_21 = seg1.x1 - seg2.x1;
3878  var y11_21 = seg1.y1 - seg2.y1;
3879  var x12_11 = seg1.x2 - seg1.x1;
3880  var y12_11 = seg1.y2 - seg1.y1;
3881  var y22_21 = seg2.y2 - seg2.y1;
3882  var x22_21 = seg2.x2 - seg2.x1;
3883  var d = (y22_21 * x12_11) - (x22_21 * y12_11);
3884  var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
3885  var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
3886  if(d == 0) {
3887    // parallel
3888    if(n1 == 0 && n2 == 0) {
3889      // coincident
3890      intersection = true;
3891    }
3892  } else {
3893    var along1 = n1 / d;
3894    var along2 = n2 / d;
3895    if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
3896      // intersect
3897      if(!point) {
3898        intersection = true;
3899      } else {
3900        // calculate the intersection point
3901        var x = seg1.x1 + (along1 * x12_11);
3902        var y = seg1.y1 + (along1 * y12_11);
3903        intersection = new ZOO.Geometry.Point(x, y);
3904      }
3905    }
3906  }
3907  if(tolerance) {
3908    var dist;
3909    if(intersection) {
3910      if(point) {
3911        var segs = [seg1, seg2];
3912        var seg, x, y;
3913        // check segment endpoints for proximity to intersection
3914        // set intersection to first endpoint within the tolerance
3915        outer: for(var i=0; i<2; ++i) {
3916          seg = segs[i];
3917          for(var j=1; j<3; ++j) {
3918            x = seg["x" + j];
3919            y = seg["y" + j];
3920            dist = Math.sqrt(
3921                Math.pow(x - intersection.x, 2) +
3922                Math.pow(y - intersection.y, 2)
3923            );
3924            if(dist < tolerance) {
3925              intersection.x = x;
3926              intersection.y = y;
3927              break outer;
3928            }
3929          }
3930        }
3931      }
3932    } else {
3933      // no calculated intersection, but segments could be within
3934      // the tolerance of one another
3935      var segs = [seg1, seg2];
3936      var source, target, x, y, p, result;
3937      // check segment endpoints for proximity to intersection
3938      // set intersection to first endpoint within the tolerance
3939      outer: for(var i=0; i<2; ++i) {
3940        source = segs[i];
3941        target = segs[(i+1)%2];
3942        for(var j=1; j<3; ++j) {
3943          p = {x: source["x"+j], y: source["y"+j]};
3944          result = ZOO.Geometry.distanceToSegment(p, target);
3945          if(result.distance < tolerance) {
3946            if(point) {
3947              intersection = new ZOO.Geometry.Point(p.x, p.y);
3948            } else {
3949              intersection = true;
3950            }
3951            break outer;
3952          }
3953        }
3954      }
3955    }
3956  }
3957  return intersection;
3958};
3959ZOO.Geometry.distanceToSegment = function(point, segment) {
3960  var x0 = point.x;
3961  var y0 = point.y;
3962  var x1 = segment.x1;
3963  var y1 = segment.y1;
3964  var x2 = segment.x2;
3965  var y2 = segment.y2;
3966  var dx = x2 - x1;
3967  var dy = y2 - y1;
3968  var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
3969               (Math.pow(dx, 2) + Math.pow(dy, 2));
3970  var x, y;
3971  if(along <= 0.0) {
3972    x = x1;
3973    y = y1;
3974  } else if(along >= 1.0) {
3975    x = x2;
3976    y = y2;
3977  } else {
3978    x = x1 + along * dx;
3979    y = y1 + along * dy;
3980  }
3981  return {
3982    distance: Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)),
3983    x: x, y: y
3984  };
3985};
3986/**
3987 * Class: OpenLayers.Geometry.Collection
3988 * A Collection is exactly what it sounds like: A collection of different
3989 * Geometries. These are stored in the local parameter <components> (which
3990 * can be passed as a parameter to the constructor).
3991 *
3992 * As new geometries are added to the collection, they are NOT cloned.
3993 * When removing geometries, they need to be specified by reference (ie you
3994 * have to pass in the *exact* geometry to be removed).
3995 *
3996 * The <getArea> and <getLength> functions here merely iterate through
3997 * the components, summing their respective areas and lengths.
3998 *
3999 * Create a new instance with the <ZOO.Geometry.Collection> constructor.
4000 *
4001 * Inerhits from:
4002 *  - <ZOO.Geometry>
4003 */
4004ZOO.Geometry.Collection = ZOO.Class(ZOO.Geometry, {
4005  /**
4006   * Property: components
4007   * {Array(<ZOO.Geometry>)} The component parts of this geometry
4008   */
4009  components: null,
4010  /**
4011   * Property: componentTypes
4012   * {Array(String)} An array of class names representing the types of
4013   * components that the collection can include.  A null value means the
4014   * component types are not restricted.
4015   */
4016  componentTypes: null,
4017  /**
4018   * Constructor: ZOO.Geometry.Collection
4019   * Creates a Geometry Collection -- a list of geoms.
4020   *
4021   * Parameters:
4022   * components - {Array(<ZOO.Geometry>)} Optional array of geometries
4023   *
4024   */
4025  initialize: function (components) {
4026    ZOO.Geometry.prototype.initialize.apply(this, arguments);
4027    this.components = [];
4028    if (components != null) {
4029      this.addComponents(components);
4030    }
4031  },
4032  /**
4033   * Method: destroy
4034   * Destroy this geometry.
4035   */
4036  destroy: function () {
4037    this.components.length = 0;
4038    this.components = null;
4039  },
4040  /**
4041   * Method: clone
4042   * Clone this geometry.
4043   *
4044   * Returns:
4045   * {<ZOO.Geometry.Collection>} An exact clone of this collection
4046   */
4047  clone: function() {
4048    var geometry = eval("new " + this.CLASS_NAME + "()");
4049    for(var i=0, len=this.components.length; i<len; i++) {
4050      geometry.addComponent(this.components[i].clone());
4051    }
4052    return geometry;
4053  },
4054  /**
4055   * Method: getComponentsString
4056   * Get a string representing the components for this collection
4057   *
4058   * Returns:
4059   * {String} A string representation of the components of this geometry
4060   */
4061  getComponentsString: function(){
4062    var strings = [];
4063    for(var i=0, len=this.components.length; i<len; i++) {
4064      strings.push(this.components[i].toShortString()); 
4065    }
4066    return strings.join(",");
4067  },
4068  /**
4069   * Method: calculateBounds
4070   * Recalculate the bounds by iterating through the components and
4071   * calling calling extendBounds() on each item.
4072   */
4073  calculateBounds: function() {
4074    this.bounds = null;
4075    if ( this.components && this.components.length > 0) {
4076      this.setBounds(this.components[0].getBounds());
4077      for (var i=1, len=this.components.length; i<len; i++) {
4078        this.extendBounds(this.components[i].getBounds());
4079      }
4080    }
4081    return this.bounds
4082  },
4083  /**
4084   * APIMethod: addComponents
4085   * Add components to this geometry.
4086   *
4087   * Parameters:
4088   * components - {Array(<ZOO.Geometry>)} An array of geometries to add
4089   */
4090  addComponents: function(components){
4091    if(!(components instanceof Array))
4092      components = [components];
4093    for(var i=0, len=components.length; i<len; i++) {
4094      this.addComponent(components[i]);
4095    }
4096  },
4097  /**
4098   * Method: addComponent
4099   * Add a new component (geometry) to the collection.  If this.componentTypes
4100   * is set, then the component class name must be in the componentTypes array.
4101   *
4102   * The bounds cache is reset.
4103   *
4104   * Parameters:
4105   * component - {<ZOO.Geometry>} A geometry to add
4106   * index - {int} Optional index into the array to insert the component
4107   *
4108   * Returns:
4109   * {Boolean} The component geometry was successfully added
4110   */
4111  addComponent: function(component, index) {
4112    var added = false;
4113    if(component) {
4114      if(this.componentTypes == null ||
4115          (ZOO.indexOf(this.componentTypes,
4116                       component.CLASS_NAME) > -1)) {
4117        if(index != null && (index < this.components.length)) {
4118          var components1 = this.components.slice(0, index);
4119          var components2 = this.components.slice(index, 
4120                                                  this.components.length);
4121          components1.push(component);
4122          this.components = components1.concat(components2);
4123        } else {
4124          this.components.push(component);
4125        }
4126        component.parent = this;
4127        this.clearBounds();
4128        added = true;
4129      }
4130    }
4131    return added;
4132  },
4133  /**
4134   * Method: removeComponents
4135   * Remove components from this geometry.
4136   *
4137   * Parameters:
4138   * components - {Array(<ZOO.Geometry>)} The components to be removed
4139   */
4140  removeComponents: function(components) {
4141    if(!(components instanceof Array))
4142      components = [components];
4143    for(var i=components.length-1; i>=0; --i) {
4144      this.removeComponent(components[i]);
4145    }
4146  },
4147  /**
4148   * Method: removeComponent
4149   * Remove a component from this geometry.
4150   *
4151   * Parameters:
4152   * component - {<ZOO.Geometry>}
4153   */
4154  removeComponent: function(component) {     
4155    ZOO.removeItem(this.components, component);
4156    // clearBounds() so that it gets recalculated on the next call
4157    // to this.getBounds();
4158    this.clearBounds();
4159  },
4160  /**
4161   * Method: getLength
4162   * Calculate the length of this geometry
4163   *
4164   * Returns:
4165   * {Float} The length of the geometry
4166   */
4167  getLength: function() {
4168    var length = 0.0;
4169    for (var i=0, len=this.components.length; i<len; i++) {
4170      length += this.components[i].getLength();
4171    }
4172    return length;
4173  },
4174  /**
4175   * APIMethod: getArea
4176   * Calculate the area of this geometry. Note how this function is
4177   * overridden in <ZOO.Geometry.Polygon>.
4178   *
4179   * Returns:
4180   * {Float} The area of the collection by summing its parts
4181   */
4182  getArea: function() {
4183    var area = 0.0;
4184    for (var i=0, len=this.components.length; i<len; i++) {
4185      area += this.components[i].getArea();
4186    }
4187    return area;
4188  },
4189  /**
4190   * APIMethod: getGeodesicArea
4191   * Calculate the approximate area of the polygon were it projected onto
4192   *     the earth.
4193   *
4194   * Parameters:
4195   * projection - {<ZOO.Projection>} The spatial reference system
4196   *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
4197   *     assumed.
4198   *
4199   * Reference:
4200   * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
4201   *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
4202   *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
4203   *
4204   * Returns:
4205   * {float} The approximate geodesic area of the geometry in square meters.
4206   */
4207  getGeodesicArea: function(projection) {
4208    var area = 0.0;
4209    for(var i=0, len=this.components.length; i<len; i++) {
4210      area += this.components[i].getGeodesicArea(projection);
4211    }
4212    return area;
4213  },
4214  /**
4215   * Method: getCentroid
4216   *
4217   * Returns:
4218   * {<ZOO.Geometry.Point>} The centroid of the collection
4219   */
4220  getCentroid: function() {
4221    return this.components.length && this.components[0].getCentroid();
4222  },
4223  /**
4224   * Method: getGeodesicLength
4225   * Calculate the approximate length of the geometry were it projected onto
4226   *     the earth.
4227   *
4228   * projection - {<ZOO.Projection>} The spatial reference system
4229   *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
4230   *     assumed.
4231   *
4232   * Returns:
4233   * {Float} The appoximate geodesic length of the geometry in meters.
4234   */
4235  getGeodesicLength: function(projection) {
4236    var length = 0.0;
4237    for(var i=0, len=this.components.length; i<len; i++) {
4238      length += this.components[i].getGeodesicLength(projection);
4239    }
4240    return length;
4241  },
4242  /**
4243   * Method: move
4244   * Moves a geometry by the given displacement along positive x and y axes.
4245   *     This modifies the position of the geometry and clears the cached
4246   *     bounds.
4247   *
4248   * Parameters:
4249   * x - {Float} Distance to move geometry in positive x direction.
4250   * y - {Float} Distance to move geometry in positive y direction.
4251   */
4252  move: function(x, y) {
4253    for(var i=0, len=this.components.length; i<len; i++) {
4254      this.components[i].move(x, y);
4255    }
4256  },
4257  /**
4258   * Method: rotate
4259   * Rotate a geometry around some origin
4260   *
4261   * Parameters:
4262   * angle - {Float} Rotation angle in degrees (measured counterclockwise
4263   *                 from the positive x-axis)
4264   * origin - {<ZOO.Geometry.Point>} Center point for the rotation
4265   */
4266  rotate: function(angle, origin) {
4267    for(var i=0, len=this.components.length; i<len; ++i) {
4268      this.components[i].rotate(angle, origin);
4269    }
4270  },
4271  /**
4272   * Method: resize
4273   * Resize a geometry relative to some origin.  Use this method to apply
4274   *     a uniform scaling to a geometry.
4275   *
4276   * Parameters:
4277   * scale - {Float} Factor by which to scale the geometry.  A scale of 2
4278   *                 doubles the size of the geometry in each dimension
4279   *                 (lines, for example, will be twice as long, and polygons
4280   *                 will have four times the area).
4281   * origin - {<ZOO.Geometry.Point>} Point of origin for resizing
4282   * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
4283   *
4284   * Returns:
4285   * {ZOO.Geometry} - The current geometry.
4286   */
4287  resize: function(scale, origin, ratio) {
4288    for(var i=0; i<this.components.length; ++i) {
4289      this.components[i].resize(scale, origin, ratio);
4290    }
4291    return this;
4292  },
4293  distanceTo: function(geometry, options) {
4294    var edge = !(options && options.edge === false);
4295    var details = edge && options && options.details;
4296    var result, best;
4297    var min = Number.POSITIVE_INFINITY;
4298    for(var i=0, len=this.components.length; i<len; ++i) {
4299      result = this.components[i].distanceTo(geometry, options);
4300      distance = details ? result.distance : result;
4301      if(distance < min) {
4302        min = distance;
4303        best = result;
4304        if(min == 0)
4305          break;
4306      }
4307    }
4308    return best;
4309  },
4310  /**
4311   * Method: equals
4312   * Determine whether another geometry is equivalent to this one.  Geometries
4313   *     are considered equivalent if all components have the same coordinates.
4314   *
4315   * Parameters:
4316   * geom - {<ZOO.Geometry>} The geometry to test.
4317   *
4318   * Returns:
4319   * {Boolean} The supplied geometry is equivalent to this geometry.
4320   */
4321  equals: function(geometry) {
4322    var equivalent = true;
4323    if(!geometry || !geometry.CLASS_NAME ||
4324       (this.CLASS_NAME != geometry.CLASS_NAME))
4325      equivalent = false;
4326    else if(!(geometry.components instanceof Array) ||
4327             (geometry.components.length != this.components.length))
4328      equivalent = false;
4329    else
4330      for(var i=0, len=this.components.length; i<len; ++i) {
4331        if(!this.components[i].equals(geometry.components[i])) {
4332          equivalent = false;
4333          break;
4334        }
4335      }
4336    return equivalent;
4337  },
4338  /**
4339   * Method: transform
4340   * Reproject the components geometry from source to dest.
4341   *
4342   * Parameters:
4343   * source - {<ZOO.Projection>}
4344   * dest - {<ZOO.Projection>}
4345   *
4346   * Returns:
4347   * {<ZOO.Geometry>}
4348   */
4349  transform: function(source, dest) {
4350    if (source && dest) {
4351      for (var i=0, len=this.components.length; i<len; i++) { 
4352        var component = this.components[i];
4353        component.transform(source, dest);
4354      }
4355      this.bounds = null;
4356    }
4357    return this;
4358  },
4359  /**
4360   * Method: intersects
4361   * Determine if the input geometry intersects this one.
4362   *
4363   * Parameters:
4364   * geometry - {<ZOO.Geometry>} Any type of geometry.
4365   *
4366   * Returns:
4367   * {Boolean} The input geometry intersects this one.
4368   */
4369  intersects: function(geometry) {
4370    var intersect = false;
4371    for(var i=0, len=this.components.length; i<len; ++ i) {
4372      intersect = geometry.intersects(this.components[i]);
4373      if(intersect)
4374        break;
4375    }
4376    return intersect;
4377  },
4378  /**
4379   * Method: getVertices
4380   * Return a list of all points in this geometry.
4381   *
4382   * Parameters:
4383   * nodes - {Boolean} For lines, only return vertices that are
4384   *     endpoints.  If false, for lines, only vertices that are not
4385   *     endpoints will be returned.  If not provided, all vertices will
4386   *     be returned.
4387   *
4388   * Returns:
4389   * {Array} A list of all vertices in the geometry.
4390   */
4391  getVertices: function(nodes) {
4392    var vertices = [];
4393    for(var i=0, len=this.components.length; i<len; ++i) {
4394      Array.prototype.push.apply(
4395          vertices, this.components[i].getVertices(nodes)
4396          );
4397    }
4398    return vertices;
4399  },
4400  CLASS_NAME: 'ZOO.Geometry.Collection'
4401});
4402/**
4403 * Class: ZOO.Geometry.Point
4404 * Point geometry class.
4405 *
4406 * Inherits from:
4407 *  - <ZOO.Geometry>
4408 */
4409ZOO.Geometry.Point = ZOO.Class(ZOO.Geometry, {
4410  /**
4411   * Property: x
4412   * {float}
4413   */
4414  x: null,
4415  /**
4416   * Property: y
4417   * {float}
4418   */
4419  y: null,
4420  /**
4421   * Constructor: ZOO.Geometry.Point
4422   * Construct a point geometry.
4423   *
4424   * Parameters:
4425   * x - {float}
4426   * y - {float}
4427   *
4428   */
4429  initialize: function(x, y) {
4430    ZOO.Geometry.prototype.initialize.apply(this, arguments);
4431    this.x = parseFloat(x);
4432    this.y = parseFloat(y);
4433  },
4434  /**
4435   * Method: clone
4436   *
4437   * Returns:
4438   * {<ZOO.Geometry.Point>} An exact clone of this ZOO.Geometry.Point
4439   */
4440  clone: function(obj) {
4441    if (obj == null)
4442      obj = new ZOO.Geometry.Point(this.x, this.y);
4443    // catch any randomly tagged-on properties
4444    // ZOO.Util.applyDefaults(obj, this);
4445    return obj;
4446  },
4447  /**
4448   * Method: calculateBounds
4449   * Create a new Bounds based on the x/y
4450   */
4451  calculateBounds: function () {
4452    this.bounds = new ZOO.Bounds(this.x, this.y,
4453                                        this.x, this.y);
4454  },
4455  distanceTo: function(geometry, options) {
4456    var edge = !(options && options.edge === false);
4457    var details = edge && options && options.details;
4458    var distance, x0, y0, x1, y1, result;
4459    if(geometry instanceof ZOO.Geometry.Point) {
4460      x0 = this.x;
4461      y0 = this.y;
4462      x1 = geometry.x;
4463      y1 = geometry.y;
4464      distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
4465      result = !details ?
4466        distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
4467    } else {
4468      result = geometry.distanceTo(this, options);
4469      if(details) {
4470        // switch coord order since this geom is target
4471        result = {
4472          x0: result.x1, y0: result.y1,
4473          x1: result.x0, y1: result.y0,
4474          distance: result.distance
4475        };
4476      }
4477    }
4478    return result;
4479  },
4480  /**
4481   * Method: equals
4482   * Determine whether another geometry is equivalent to this one.  Geometries
4483   *     are considered equivalent if all components have the same coordinates.
4484   *
4485   * Parameters:
4486   * geom - {<ZOO.Geometry.Point>} The geometry to test.
4487   *
4488   * Returns:
4489   * {Boolean} The supplied geometry is equivalent to this geometry.
4490   */
4491  equals: function(geom) {
4492    var equals = false;
4493    if (geom != null)
4494      equals = ((this.x == geom.x && this.y == geom.y) ||
4495                (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
4496    return equals;
4497  },
4498  /**
4499   * Method: toShortString
4500   *
4501   * Returns:
4502   * {String} Shortened String representation of Point object.
4503   *         (ex. <i>"5, 42"</i>)
4504   */
4505  toShortString: function() {
4506    return (this.x + ", " + this.y);
4507  },
4508  /**
4509   * Method: move
4510   * Moves a geometry by the given displacement along positive x and y axes.
4511   *     This modifies the position of the geometry and clears the cached
4512   *     bounds.
4513   *
4514   * Parameters:
4515   * x - {Float} Distance to move geometry in positive x direction.
4516   * y - {Float} Distance to move geometry in positive y direction.
4517   */
4518  move: function(x, y) {
4519    this.x = this.x + x;
4520    this.y = this.y + y;
4521    this.clearBounds();
4522  },
4523  /**
4524   * Method: rotate
4525   * Rotate a point around another.
4526   *
4527   * Parameters:
4528   * angle - {Float} Rotation angle in degrees (measured counterclockwise
4529   *                 from the positive x-axis)
4530   * origin - {<ZOO.Geometry.Point>} Center point for the rotation
4531   */
4532  rotate: function(angle, origin) {
4533        angle *= Math.PI / 180;
4534        var radius = this.distanceTo(origin);
4535        var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
4536        this.x = origin.x + (radius * Math.cos(theta));
4537        this.y = origin.y + (radius * Math.sin(theta));
4538        this.clearBounds();
4539  },
4540  /**
4541   * Method: getCentroid
4542   *
4543   * Returns:
4544   * {<ZOO.Geometry.Point>} The centroid of the collection
4545   */
4546  getCentroid: function() {
4547    return new ZOO.Geometry.Point(this.x, this.y);
4548  },
4549  /**
4550   * Method: resize
4551   * Resize a point relative to some origin.  For points, this has the effect
4552   *     of scaling a vector (from the origin to the point).  This method is
4553   *     more useful on geometry collection subclasses.
4554   *
4555   * Parameters:
4556   * scale - {Float} Ratio of the new distance from the origin to the old
4557   *                 distance from the origin.  A scale of 2 doubles the
4558   *                 distance between the point and origin.
4559   * origin - {<ZOO.Geometry.Point>} Point of origin for resizing
4560   * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
4561   *
4562   * Returns:
4563   * {ZOO.Geometry} - The current geometry.
4564   */
4565  resize: function(scale, origin, ratio) {
4566    ratio = (ratio == undefined) ? 1 : ratio;
4567    this.x = origin.x + (scale * ratio * (this.x - origin.x));
4568    this.y = origin.y + (scale * (this.y - origin.y));
4569    this.clearBounds();
4570    return this;
4571  },
4572  /**
4573   * Method: intersects
4574   * Determine if the input geometry intersects this one.
4575   *
4576   * Parameters:
4577   * geometry - {<ZOO.Geometry>} Any type of geometry.
4578   *
4579   * Returns:
4580   * {Boolean} The input geometry intersects this one.
4581   */
4582  intersects: function(geometry) {
4583    var intersect = false;
4584    if(geometry.CLASS_NAME == "ZOO.Geometry.Point") {
4585      intersect = this.equals(geometry);
4586    } else {
4587      intersect = geometry.intersects(this);
4588    }
4589    return intersect;
4590  },
4591  /**
4592   * Method: transform
4593   * Translate the x,y properties of the point from source to dest.
4594   *
4595   * Parameters:
4596   * source - {<ZOO.Projection>}
4597   * dest - {<ZOO.Projection>}
4598   *
4599   * Returns:
4600   * {<ZOO.Geometry>}
4601   */
4602  transform: function(source, dest) {
4603    if ((source && dest)) {
4604      ZOO.Projection.transform(
4605          this, source, dest); 
4606      this.bounds = null;
4607    }       
4608    return this;
4609  },
4610  /**
4611   * Method: getVertices
4612   * Return a list of all points in this geometry.
4613   *
4614   * Parameters:
4615   * nodes - {Boolean} For lines, only return vertices that are
4616   *     endpoints.  If false, for lines, only vertices that are not
4617   *     endpoints will be returned.  If not provided, all vertices will
4618   *     be returned.
4619   *
4620   * Returns:
4621   * {Array} A list of all vertices in the geometry.
4622   */
4623  getVertices: function(nodes) {
4624    return [this];
4625  },
4626  CLASS_NAME: 'ZOO.Geometry.Point'
4627});
4628/**
4629 * Class: ZOO.Geometry.Surface
4630 * Surface geometry class.
4631 *
4632 * Inherits from:
4633 *  - <ZOO.Geometry>
4634 */
4635ZOO.Geometry.Surface = ZOO.Class(ZOO.Geometry, {
4636  initialize: function() {
4637    ZOO.Geometry.prototype.initialize.apply(this, arguments);
4638  },
4639  CLASS_NAME: "ZOO.Geometry.Surface"
4640});
4641/**
4642 * Class: ZOO.Geometry.MultiPoint
4643 * MultiPoint is a collection of Points. Create a new instance with the
4644 * <ZOO.Geometry.MultiPoint> constructor.
4645 *
4646 * Inherits from:
4647 *  - <ZOO.Geometry.Collection>
4648 */
4649ZOO.Geometry.MultiPoint = ZOO.Class(
4650  ZOO.Geometry.Collection, {
4651  /**
4652   * Property: componentTypes
4653   * {Array(String)} An array of class names representing the types of
4654   * components that the collection can include.  A null value means the
4655   * component types are not restricted.
4656   */
4657  componentTypes: ["ZOO.Geometry.Point"],
4658  /**
4659   * Constructor: ZOO.Geometry.MultiPoint
4660   * Create a new MultiPoint Geometry
4661   *
4662   * Parameters:
4663   * components - {Array(<ZOO.Geometry.Point>)}
4664   *
4665   * Returns:
4666   * {<ZOO.Geometry.MultiPoint>}
4667   */
4668  initialize: function(components) {
4669    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
4670  },
4671  /**
4672   * Method: addPoint
4673   * Wrapper for <ZOO.Geometry.Collection.addComponent>
4674   *
4675   * Parameters:
4676   * point - {<ZOO.Geometry.Point>} Point to be added
4677   * index - {Integer} Optional index
4678   */
4679  addPoint: function(point, index) {
4680    this.addComponent(point, index);
4681  },
4682  /**
4683   * Method: removePoint
4684   * Wrapper for <ZOO.Geometry.Collection.removeComponent>
4685   *
4686   * Parameters:
4687   * point - {<ZOO.Geometry.Point>} Point to be removed
4688   */
4689  removePoint: function(point){
4690    this.removeComponent(point);
4691  },
4692  CLASS_NAME: "ZOO.Geometry.MultiPoint"
4693});
4694/**
4695 * Class: ZOO.Geometry.Curve
4696 * A Curve is a MultiPoint, whose points are assumed to be connected. To
4697 * this end, we provide a "getLength()" function, which iterates through
4698 * the points, summing the distances between them.
4699 *
4700 * Inherits:
4701 *  - <ZOO.Geometry.MultiPoint>
4702 */
4703ZOO.Geometry.Curve = ZOO.Class(ZOO.Geometry.MultiPoint, {
4704  /**
4705   * Property: componentTypes
4706   * {Array(String)} An array of class names representing the types of
4707   *                 components that the collection can include.  A null
4708   *                 value means the component types are not restricted.
4709   */
4710  componentTypes: ["ZOO.Geometry.Point"],
4711  /**
4712   * Constructor: ZOO.Geometry.Curve
4713   *
4714   * Parameters:
4715   * point - {Array(<ZOO.Geometry.Point>)}
4716   */
4717  initialize: function(points) {
4718    ZOO.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);
4719  },
4720  /**
4721   * Method: getLength
4722   *
4723   * Returns:
4724   * {Float} The length of the curve
4725   */
4726  getLength: function() {
4727    var length = 0.0;
4728    if ( this.components && (this.components.length > 1)) {
4729      for(var i=1, len=this.components.length; i<len; i++) {
4730        length += this.components[i-1].distanceTo(this.components[i]);
4731      }
4732    }
4733    return length;
4734  },
4735  /**
4736     * APIMethod: getGeodesicLength
4737     * Calculate the approximate length of the geometry were it projected onto
4738     *     the earth.
4739     *
4740     * projection - {<ZOO.Projection>} The spatial reference system
4741     *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
4742     *     assumed.
4743     *
4744     * Returns:
4745     * {Float} The appoximate geodesic length of the geometry in meters.
4746     */
4747    getGeodesicLength: function(projection) {
4748      var geom = this;  // so we can work with a clone if needed
4749      if(projection) {
4750        var gg = new ZOO.Projection("EPSG:4326");
4751        if(!gg.equals(projection)) {
4752          geom = this.clone().transform(projection, gg);
4753       }
4754     }
4755     var length = 0.0;
4756     if(geom.components && (geom.components.length > 1)) {
4757       var p1, p2;
4758       for(var i=1, len=geom.components.length; i<len; i++) {
4759         p1 = geom.components[i-1];
4760         p2 = geom.components[i];
4761        // this returns km and requires x/y properties
4762        length += ZOO.distVincenty(p1,p2);
4763      }
4764    }
4765    // convert to m
4766    return length * 1000;
4767  },
4768  CLASS_NAME: "ZOO.Geometry.Curve"
4769});
4770/**
4771 * Class: ZOO.Geometry.LineString
4772 * A LineString is a Curve which, once two points have been added to it, can
4773 * never be less than two points long.
4774 *
4775 * Inherits from:
4776 *  - <ZOO.Geometry.Curve>
4777 */
4778ZOO.Geometry.LineString = ZOO.Class(ZOO.Geometry.Curve, {
4779  /**
4780   * Constructor: ZOO.Geometry.LineString
4781   * Create a new LineString geometry
4782   *
4783   * Parameters:
4784   * points - {Array(<ZOO.Geometry.Point>)} An array of points used to
4785   *          generate the linestring
4786   *
4787   */
4788  initialize: function(points) {
4789    ZOO.Geometry.Curve.prototype.initialize.apply(this, arguments);       
4790  },
4791  /**
4792   * Method: removeComponent
4793   * Only allows removal of a point if there are three or more points in
4794   * the linestring. (otherwise the result would be just a single point)
4795   *
4796   * Parameters:
4797   * point - {<ZOO.Geometry.Point>} The point to be removed
4798   */
4799  removeComponent: function(point) {
4800    if ( this.components && (this.components.length > 2))
4801      ZOO.Geometry.Collection.prototype.removeComponent.apply(this,arguments);
4802  },
4803  /**
4804   * Method: intersects
4805   * Test for instersection between two geometries.  This is a cheapo
4806   *     implementation of the Bently-Ottmann algorigithm.  It doesn't
4807   *     really keep track of a sweep line data structure.  It is closer
4808   *     to the brute force method, except that segments are sorted and
4809   *     potential intersections are only calculated when bounding boxes
4810   *     intersect.
4811   *
4812   * Parameters:
4813   * geometry - {<ZOO.Geometry>}
4814   *
4815   * Returns:
4816   * {Boolean} The input geometry intersects this geometry.
4817   */
4818  intersects: function(geometry) {
4819    var intersect = false;
4820    var type = geometry.CLASS_NAME;
4821    if(type == "ZOO.Geometry.LineString" ||
4822       type == "ZOO.Geometry.LinearRing" ||
4823       type == "ZOO.Geometry.Point") {
4824      var segs1 = this.getSortedSegments();
4825      var segs2;
4826      if(type == "ZOO.Geometry.Point")
4827        segs2 = [{
4828          x1: geometry.x, y1: geometry.y,
4829          x2: geometry.x, y2: geometry.y
4830        }];
4831      else
4832        segs2 = geometry.getSortedSegments();
4833      var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
4834          seg2, seg2y1, seg2y2;
4835      // sweep right
4836      outer: for(var i=0, len=segs1.length; i<len; ++i) {
4837         seg1 = segs1[i];
4838         seg1x1 = seg1.x1;
4839         seg1x2 = seg1.x2;
4840         seg1y1 = seg1.y1;
4841         seg1y2 = seg1.y2;
4842         inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
4843           seg2 = segs2[j];
4844           if(seg2.x1 > seg1x2)
4845             break;
4846           if(seg2.x2 < seg1x1)
4847             continue;
4848           seg2y1 = seg2.y1;
4849           seg2y2 = seg2.y2;
4850           if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2))
4851             continue;
4852           if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2))
4853             continue;
4854           if(ZOO.Geometry.segmentsIntersect(seg1, seg2)) {
4855             intersect = true;
4856             break outer;
4857           }
4858         }
4859      }
4860    } else {
4861      intersect = geometry.intersects(this);
4862    }
4863    return intersect;
4864  },
4865  /**
4866   * Method: getSortedSegments
4867   *
4868   * Returns:
4869   * {Array} An array of segment objects.  Segment objects have properties
4870   *     x1, y1, x2, and y2.  The start point is represented by x1 and y1.
4871   *     The end point is represented by x2 and y2.  Start and end are
4872   *     ordered so that x1 < x2.
4873   */
4874  getSortedSegments: function() {
4875    var numSeg = this.components.length - 1;
4876    var segments = new Array(numSeg);
4877    for(var i=0; i<numSeg; ++i) {
4878      point1 = this.components[i];
4879      point2 = this.components[i + 1];
4880      if(point1.x < point2.x)
4881        segments[i] = {
4882          x1: point1.x,
4883          y1: point1.y,
4884          x2: point2.x,
4885          y2: point2.y
4886        };
4887      else
4888        segments[i] = {
4889          x1: point2.x,
4890          y1: point2.y,
4891          x2: point1.x,
4892          y2: point1.y
4893        };
4894    }
4895    // more efficient to define this somewhere static
4896    function byX1(seg1, seg2) {
4897      return seg1.x1 - seg2.x1;
4898    }
4899    return segments.sort(byX1);
4900  },
4901  /**
4902   * Method: splitWithSegment
4903   * Split this geometry with the given segment.
4904   *
4905   * Parameters:
4906   * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
4907   *     segment endpoint coordinates.
4908   * options - {Object} Properties of this object will be used to determine
4909   *     how the split is conducted.
4910   *
4911   * Valid options:
4912   * edge - {Boolean} Allow splitting when only edges intersect.  Default is
4913   *     true.  If false, a vertex on the source segment must be within the
4914   *     tolerance distance of the intersection to be considered a split.
4915   * tolerance - {Number} If a non-null value is provided, intersections
4916   *     within the tolerance distance of one of the source segment's
4917   *     endpoints will be assumed to occur at the endpoint.
4918   *
4919   * Returns:
4920   * {Object} An object with *lines* and *points* properties.  If the given
4921   *     segment intersects this linestring, the lines array will reference
4922   *     geometries that result from the split.  The points array will contain
4923   *     all intersection points.  Intersection points are sorted along the
4924   *     segment (in order from x1,y1 to x2,y2).
4925   */
4926  splitWithSegment: function(seg, options) {
4927    var edge = !(options && options.edge === false);
4928    var tolerance = options && options.tolerance;
4929    var lines = [];
4930    var verts = this.getVertices();
4931    var points = [];
4932    var intersections = [];
4933    var split = false;
4934    var vert1, vert2, point;
4935    var node, vertex, target;
4936    var interOptions = {point: true, tolerance: tolerance};
4937    var result = null;
4938    for(var i=0, stop=verts.length-2; i<=stop; ++i) {
4939      vert1 = verts[i];
4940      points.push(vert1.clone());
4941      vert2 = verts[i+1];
4942      target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
4943      point = ZOO.Geometry.segmentsIntersect(seg, target, interOptions);
4944      if(point instanceof ZOO.Geometry.Point) {
4945        if((point.x === seg.x1 && point.y === seg.y1) ||
4946           (point.x === seg.x2 && point.y === seg.y2) ||
4947            point.equals(vert1) || point.equals(vert2))
4948          vertex = true;
4949        else
4950          vertex = false;
4951        if(vertex || edge) {
4952          // push intersections different than the previous
4953          if(!point.equals(intersections[intersections.length-1]))
4954            intersections.push(point.clone());
4955          if(i === 0) {
4956            if(point.equals(vert1))
4957              continue;
4958          }
4959          if(point.equals(vert2))
4960            continue;
4961          split = true;
4962          if(!point.equals(vert1))
4963            points.push(point);
4964          lines.push(new ZOO.Geometry.LineString(points));
4965          points = [point.clone()];
4966        }
4967      }
4968    }
4969    if(split) {
4970      points.push(vert2.clone());
4971      lines.push(new ZOO.Geometry.LineString(points));
4972    }
4973    if(intersections.length > 0) {
4974      // sort intersections along segment
4975      var xDir = seg.x1 < seg.x2 ? 1 : -1;
4976      var yDir = seg.y1 < seg.y2 ? 1 : -1;
4977      result = {
4978        lines: lines,
4979        points: intersections.sort(function(p1, p2) {
4980           return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
4981        })
4982      };
4983    }
4984    return result;
4985  },
4986  /**
4987   * Method: split
4988   * Use this geometry (the source) to attempt to split a target geometry.
4989   *
4990   * Parameters:
4991   * target - {<ZOO.Geometry>} The target geometry.
4992   * options - {Object} Properties of this object will be used to determine
4993   *     how the split is conducted.
4994   *
4995   * Valid options:
4996   * mutual - {Boolean} Split the source geometry in addition to the target
4997   *     geometry.  Default is false.
4998   * edge - {Boolean} Allow splitting when only edges intersect.  Default is
4999   *     true.  If false, a vertex on the source must be within the tolerance
5000   *     distance of the intersection to be considered a split.
5001   * tolerance - {Number} If a non-null value is provided, intersections
5002   *     within the tolerance distance of an existing vertex on the source
5003   *     will be assumed to occur at the vertex.
5004   *
5005   * Returns:
5006   * {Array} A list of geometries (of this same type as the target) that
5007   *     result from splitting the target with the source geometry.  The
5008   *     source and target geometry will remain unmodified.  If no split
5009   *     results, null will be returned.  If mutual is true and a split
5010   *     results, return will be an array of two arrays - the first will be
5011   *     all geometries that result from splitting the source geometry and
5012   *     the second will be all geometries that result from splitting the
5013   *     target geometry.
5014   */
5015  split: function(target, options) {
5016    var results = null;
5017    var mutual = options && options.mutual;
5018    var sourceSplit, targetSplit, sourceParts, targetParts;
5019    if(target instanceof ZOO.Geometry.LineString) {
5020      var verts = this.getVertices();
5021      var vert1, vert2, seg, splits, lines, point;
5022      var points = [];
5023      sourceParts = [];
5024      for(var i=0, stop=verts.length-2; i<=stop; ++i) {
5025        vert1 = verts[i];
5026        vert2 = verts[i+1];
5027        seg = {
5028          x1: vert1.x, y1: vert1.y,
5029          x2: vert2.x, y2: vert2.y
5030        };
5031        targetParts = targetParts || [target];
5032        if(mutual)
5033          points.push(vert1.clone());
5034        for(var j=0; j<targetParts.length; ++j) {
5035          splits = targetParts[j].splitWithSegment(seg, options);
5036          if(splits) {
5037            // splice in new features
5038            lines = splits.lines;
5039            if(lines.length > 0) {
5040              lines.unshift(j, 1);
5041              Array.prototype.splice.apply(targetParts, lines);
5042              j += lines.length - 2;
5043            }
5044            if(mutual) {
5045              for(var k=0, len=splits.points.length; k<len; ++k) {
5046                point = splits.points[k];
5047                if(!point.equals(vert1)) {
5048                  points.push(point);
5049                  sourceParts.push(new ZOO.Geometry.LineString(points));
5050                  if(point.equals(vert2))
5051                    points = [];
5052                  else
5053                    points = [point.clone()];
5054                }
5055              }
5056            }
5057          }
5058        }
5059      }
5060      if(mutual && sourceParts.length > 0 && points.length > 0) {
5061        points.push(vert2.clone());
5062        sourceParts.push(new ZOO.Geometry.LineString(points));
5063      }
5064    } else {
5065      results = target.splitWith(this, options);
5066    }
5067    if(targetParts && targetParts.length > 1)
5068      targetSplit = true;
5069    else
5070      targetParts = [];
5071    if(sourceParts && sourceParts.length > 1)
5072      sourceSplit = true;
5073    else
5074      sourceParts = [];
5075    if(targetSplit || sourceSplit) {
5076      if(mutual)
5077        results = [sourceParts, targetParts];
5078      else
5079        results = targetParts;
5080    }
5081    return results;
5082  },
5083  /**
5084   * Method: splitWith
5085   * Split this geometry (the target) with the given geometry (the source).
5086   *
5087   * Parameters:
5088   * geometry - {<ZOO.Geometry>} A geometry used to split this
5089   *     geometry (the source).
5090   * options - {Object} Properties of this object will be used to determine
5091   *     how the split is conducted.
5092   *
5093   * Valid options:
5094   * mutual - {Boolean} Split the source geometry in addition to the target
5095   *     geometry.  Default is false.
5096   * edge - {Boolean} Allow splitting when only edges intersect.  Default is
5097   *     true.  If false, a vertex on the source must be within the tolerance
5098   *     distance of the intersection to be considered a split.
5099   * tolerance - {Number} If a non-null value is provided, intersections
5100   *     within the tolerance distance of an existing vertex on the source
5101   *     will be assumed to occur at the vertex.
5102   *
5103   * Returns:
5104   * {Array} A list of geometries (of this same type as the target) that
5105   *     result from splitting the target with the source geometry.  The
5106   *     source and target geometry will remain unmodified.  If no split
5107   *     results, null will be returned.  If mutual is true and a split
5108   *     results, return will be an array of two arrays - the first will be
5109   *     all geometries that result from splitting the source geometry and
5110   *     the second will be all geometries that result from splitting the
5111   *     target geometry.
5112   */
5113  splitWith: function(geometry, options) {
5114    return geometry.split(this, options);
5115  },
5116  /**
5117   * Method: getVertices
5118   * Return a list of all points in this geometry.
5119   *
5120   * Parameters:
5121   * nodes - {Boolean} For lines, only return vertices that are
5122   *     endpoints.  If false, for lines, only vertices that are not
5123   *     endpoints will be returned.  If not provided, all vertices will
5124   *     be returned.
5125   *
5126   * Returns:
5127   * {Array} A list of all vertices in the geometry.
5128   */
5129  getVertices: function(nodes) {
5130    var vertices;
5131    if(nodes === true)
5132      vertices = [
5133        this.components[0],
5134        this.components[this.components.length-1]
5135      ];
5136    else if (nodes === false)
5137      vertices = this.components.slice(1, this.components.length-1);
5138    else
5139      vertices = this.components.slice();
5140    return vertices;
5141  },
5142  distanceTo: function(geometry, options) {
5143    var edge = !(options && options.edge === false);
5144    var details = edge && options && options.details;
5145    var result, best = {};
5146    var min = Number.POSITIVE_INFINITY;
5147    if(geometry instanceof ZOO.Geometry.Point) {
5148      var segs = this.getSortedSegments();
5149      var x = geometry.x;
5150      var y = geometry.y;
5151      var seg;
5152      for(var i=0, len=segs.length; i<len; ++i) {
5153        seg = segs[i];
5154        result = ZOO.Geometry.distanceToSegment(geometry, seg);
5155        if(result.distance < min) {
5156          min = result.distance;
5157          best = result;
5158          if(min === 0)
5159            break;
5160        } else {
5161          // if distance increases and we cross y0 to the right of x0, no need to keep looking.
5162          if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2)))
5163            break;
5164        }
5165      }
5166      if(details)
5167        best = {
5168          distance: best.distance,
5169          x0: best.x, y0: best.y,
5170          x1: x, y1: y
5171        };
5172      else
5173        best = best.distance;
5174    } else if(geometry instanceof ZOO.Geometry.LineString) { 
5175      var segs0 = this.getSortedSegments();
5176      var segs1 = geometry.getSortedSegments();
5177      var seg0, seg1, intersection, x0, y0;
5178      var len1 = segs1.length;
5179      var interOptions = {point: true};
5180      outer: for(var i=0, len=segs0.length; i<len; ++i) {
5181        seg0 = segs0[i];
5182        x0 = seg0.x1;
5183        y0 = seg0.y1;
5184        for(var j=0; j<len1; ++j) {
5185          seg1 = segs1[j];
5186          intersection = ZOO.Geometry.segmentsIntersect(seg0, seg1, interOptions);
5187          if(intersection) {
5188            min = 0;
5189            best = {
5190              distance: 0,
5191              x0: intersection.x, y0: intersection.y,
5192              x1: intersection.x, y1: intersection.y
5193            };
5194            break outer;
5195          } else {
5196            result = ZOO.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
5197            if(result.distance < min) {
5198              min = result.distance;
5199              best = {
5200                distance: min,
5201                x0: x0, y0: y0,
5202                x1: result.x, y1: result.y
5203              };
5204            }
5205          }
5206        }
5207      }
5208      if(!details)
5209        best = best.distance;
5210      if(min !== 0) {
5211        // check the final vertex in this line's sorted segments
5212        if(seg0) {
5213          result = geometry.distanceTo(
5214              new ZOO.Geometry.Point(seg0.x2, seg0.y2),
5215              options
5216              );
5217          var dist = details ? result.distance : result;
5218          if(dist < min) {
5219            if(details)
5220              best = {
5221                distance: min,
5222                x0: result.x1, y0: result.y1,
5223                x1: result.x0, y1: result.y0
5224              };
5225            else
5226              best = dist;
5227          }
5228        }
5229      }
5230    } else {
5231      best = geometry.distanceTo(this, options);
5232      // swap since target comes from this line
5233      if(details)
5234        best = {
5235          distance: best.distance,
5236          x0: best.x1, y0: best.y1,
5237          x1: best.x0, y1: best.y0
5238        };
5239    }
5240    return best;
5241  },
5242  CLASS_NAME: "ZOO.Geometry.LineString"
5243});
5244/**
5245 * Class: ZOO.Geometry.LinearRing
5246 *
5247 * A Linear Ring is a special LineString which is closed. It closes itself
5248 * automatically on every addPoint/removePoint by adding a copy of the first
5249 * point as the last point.
5250 *
5251 * Also, as it is the first in the line family to close itself, a getArea()
5252 * function is defined to calculate the enclosed area of the linearRing
5253 *
5254 * Inherits:
5255 *  - <OpenLayers.Geometry.LineString>
5256 */
5257ZOO.Geometry.LinearRing = ZOO.Class(
5258  ZOO.Geometry.LineString, {
5259  /**
5260   * Property: componentTypes
5261   * {Array(String)} An array of class names representing the types of
5262   *                 components that the collection can include.  A null
5263   *                 value means the component types are not restricted.
5264   */
5265  componentTypes: ["ZOO.Geometry.Point"],
5266  /**
5267   * Constructor: OpenLayers.Geometry.LinearRing
5268   * Linear rings are constructed with an array of points.  This array
5269   *     can represent a closed or open ring.  If the ring is open (the last
5270   *     point does not equal the first point), the constructor will close
5271   *     the ring.  If the ring is already closed (the last point does equal
5272   *     the first point), it will be left closed.
5273   *
5274   * Parameters:
5275   * points - {Array(<ZOO.Geometry.Point>)} points
5276   */
5277  initialize: function(points) {
5278    ZOO.Geometry.LineString.prototype.initialize.apply(this,arguments);
5279  },
5280  /**
5281   * Method: addComponent
5282   * Adds a point to geometry components.  If the point is to be added to
5283   *     the end of the components array and it is the same as the last point
5284   *     already in that array, the duplicate point is not added.  This has
5285   *     the effect of closing the ring if it is not already closed, and
5286   *     doing the right thing if it is already closed.  This behavior can
5287   *     be overridden by calling the method with a non-null index as the
5288   *     second argument.
5289   *
5290   * Parameter:
5291   * point - {<ZOO.Geometry.Point>}
5292   * index - {Integer} Index into the array to insert the component
5293   *
5294   * Returns:
5295   * {Boolean} Was the Point successfully added?
5296   */
5297  addComponent: function(point, index) {
5298    var added = false;
5299    //remove last point
5300    var lastPoint = this.components.pop();
5301    // given an index, add the point
5302    // without an index only add non-duplicate points
5303    if(index != null || !point.equals(lastPoint))
5304      added = ZOO.Geometry.Collection.prototype.addComponent.apply(this,arguments);
5305    //append copy of first point
5306    var firstPoint = this.components[0];
5307    ZOO.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);
5308    return added;
5309  },
5310  /**
5311   * APIMethod: removeComponent
5312   * Removes a point from geometry components.
5313   *
5314   * Parameters:
5315   * point - {<ZOO.Geometry.Point>}
5316   */
5317  removeComponent: function(point) {
5318    if (this.components.length > 4) {
5319      //remove last point
5320      this.components.pop();
5321      //remove our point
5322      ZOO.Geometry.Collection.prototype.removeComponent.apply(this,arguments);
5323      //append copy of first point
5324      var firstPoint = this.components[0];
5325      ZOO.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);
5326    }
5327  },
5328  /**
5329   * Method: move
5330   * Moves a geometry by the given displacement along positive x and y axes.
5331   *     This modifies the position of the geometry and clears the cached
5332   *     bounds.
5333   *
5334   * Parameters:
5335   * x - {Float} Distance to move geometry in positive x direction.
5336   * y - {Float} Distance to move geometry in positive y direction.
5337   */
5338  move: function(x, y) {
5339    for(var i = 0, len=this.components.length; i<len - 1; i++) {
5340      this.components[i].move(x, y);
5341    }
5342  },
5343  /**
5344   * Method: rotate
5345   * Rotate a geometry around some origin
5346   *
5347   * Parameters:
5348   * angle - {Float} Rotation angle in degrees (measured counterclockwise
5349   *                 from the positive x-axis)
5350   * origin - {<ZOO.Geometry.Point>} Center point for the rotation
5351   */
5352  rotate: function(angle, origin) {
5353    for(var i=0, len=this.components.length; i<len - 1; ++i) {
5354      this.components[i].rotate(angle, origin);
5355    }
5356  },
5357  /**
5358   * Method: resize
5359   * Resize a geometry relative to some origin.  Use this method to apply
5360   *     a uniform scaling to a geometry.
5361   *
5362   * Parameters:
5363   * scale - {Float} Factor by which to scale the geometry.  A scale of 2
5364   *                 doubles the size of the geometry in each dimension
5365   *                 (lines, for example, will be twice as long, and polygons
5366   *                 will have four times the area).
5367   * origin - {<ZOO.Geometry.Point>} Point of origin for resizing
5368   * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
5369   *
5370   * Returns:
5371   * {ZOO.Geometry} - The current geometry.
5372   */
5373  resize: function(scale, origin, ratio) {
5374    for(var i=0, len=this.components.length; i<len - 1; ++i) {
5375      this.components[i].resize(scale, origin, ratio);
5376    }
5377    return this;
5378  },
5379  /**
5380   * Method: transform
5381   * Reproject the components geometry from source to dest.
5382   *
5383   * Parameters:
5384   * source - {<ZOO.Projection>}
5385   * dest - {<ZOO.Projection>}
5386   *
5387   * Returns:
5388   * {<ZOO.Geometry>}
5389   */
5390  transform: function(source, dest) {
5391    if (source && dest) {
5392      for (var i=0, len=this.components.length; i<len - 1; i++) {
5393        var component = this.components[i];
5394        component.transform(source, dest);
5395      }
5396      this.bounds = null;
5397    }
5398    return this;
5399  },
5400  /**
5401   * Method: getCentroid
5402   *
5403   * Returns:
5404   * {<ZOO.Geometry.Point>} The centroid of the ring
5405   */
5406  getCentroid: function() {
5407    if ( this.components && (this.components.length > 2)) {
5408      var sumX = 0.0;
5409      var sumY = 0.0;
5410      for (var i = 0; i < this.components.length - 1; i++) {
5411        var b = this.components[i];
5412        var c = this.components[i+1];
5413        sumX += (b.x + c.x) * (b.x * c.y - c.x * b.y);
5414        sumY += (b.y + c.y) * (b.x * c.y - c.x * b.y);
5415      }
5416      var area = -1 * this.getArea();
5417      var x = sumX / (6 * area);
5418      var y = sumY / (6 * area);
5419    }
5420    return new ZOO.Geometry.Point(x, y);
5421  },
5422  /**
5423   * Method: getArea
5424   * Note - The area is positive if the ring is oriented CW, otherwise
5425   *         it will be negative.
5426   *
5427   * Returns:
5428   * {Float} The signed area for a ring.
5429   */
5430  getArea: function() {
5431    var area = 0.0;
5432    if ( this.components && (this.components.length > 2)) {
5433      var sum = 0.0;
5434      for (var i=0, len=this.components.length; i<len - 1; i++) {
5435        var b = this.components[i];
5436        var c = this.components[i+1];
5437        sum += (b.x + c.x) * (c.y - b.y);
5438      }
5439      area = - sum / 2.0;
5440    }
5441    return area;
5442  },
5443  /**
5444   * Method: getGeodesicArea
5445   * Calculate the approximate area of the polygon were it projected onto
5446   *     the earth.  Note that this area will be positive if ring is oriented
5447   *     clockwise, otherwise it will be negative.
5448   *
5449   * Parameters:
5450   * projection - {<ZOO.Projection>} The spatial reference system
5451   *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
5452   *     assumed.
5453   *
5454   * Reference:
5455   * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
5456   *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
5457   *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
5458   *
5459   * Returns:
5460   * {float} The approximate signed geodesic area of the polygon in square
5461   *     meters.
5462   */
5463  getGeodesicArea: function(projection) {
5464    var ring = this;  // so we can work with a clone if needed
5465    if(projection) {
5466      var gg = new ZOO.Projection("EPSG:4326");
5467      if(!gg.equals(projection)) {
5468        ring = this.clone().transform(projection, gg);
5469      }
5470    }
5471    var area = 0.0;
5472    var len = ring.components && ring.components.length;
5473    if(len > 2) {
5474      var p1, p2;
5475      for(var i=0; i<len-1; i++) {
5476        p1 = ring.components[i];
5477        p2 = ring.components[i+1];
5478        area += ZOO.rad(p2.x - p1.x) *
5479                (2 + Math.sin(ZOO.rad(p1.y)) +
5480                Math.sin(ZOO.rad(p2.y)));
5481      }
5482      area = area * 6378137.0 * 6378137.0 / 2.0;
5483    }
5484    return area;
5485  },
5486  /**
5487   * Method: containsPoint
5488   * Test if a point is inside a linear ring.  For the case where a point
5489   *     is coincident with a linear ring edge, returns 1.  Otherwise,
5490   *     returns boolean.
5491   *
5492   * Parameters:
5493   * point - {<ZOO.Geometry.Point>}
5494   *
5495   * Returns:
5496   * {Boolean | Number} The point is inside the linear ring.  Returns 1 if
5497   *     the point is coincident with an edge.  Returns boolean otherwise.
5498   */
5499  containsPoint: function(point) {
5500    var approx = OpenLayers.Number.limitSigDigs;
5501    var digs = 14;
5502    var px = approx(point.x, digs);
5503    var py = approx(point.y, digs);
5504    function getX(y, x1, y1, x2, y2) {
5505      return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2);
5506    }
5507    var numSeg = this.components.length - 1;
5508    var start, end, x1, y1, x2, y2, cx, cy;
5509    var crosses = 0;
5510    for(var i=0; i<numSeg; ++i) {
5511      start = this.components[i];
5512      x1 = approx(start.x, digs);
5513      y1 = approx(start.y, digs);
5514      end = this.components[i + 1];
5515      x2 = approx(end.x, digs);
5516      y2 = approx(end.y, digs);
5517
5518      /**
5519       * The following conditions enforce five edge-crossing rules:
5520       *    1. points coincident with edges are considered contained;
5521       *    2. an upward edge includes its starting endpoint, and
5522       *    excludes its final endpoint;
5523       *    3. a downward edge excludes its starting endpoint, and
5524       *    includes its final endpoint;
5525       *    4. horizontal edges are excluded; and
5526       *    5. the edge-ray intersection point must be strictly right
5527       *    of the point P.
5528       */
5529      if(y1 == y2) {
5530        // horizontal edge
5531        if(py == y1) {
5532          // point on horizontal line
5533          if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
5534              x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
5535            // point on edge
5536            crosses = -1;
5537            break;
5538          }
5539        }
5540        // ignore other horizontal edges
5541        continue;
5542      }
5543      cx = approx(getX(py, x1, y1, x2, y2), digs);
5544      if(cx == px) {
5545        // point on line
5546        if(y1 < y2 && (py >= y1 && py <= y2) || // upward
5547            y1 > y2 && (py <= y1 && py >= y2)) { // downward
5548          // point on edge
5549          crosses = -1;
5550          break;
5551        }
5552      }
5553      if(cx <= px) {
5554        // no crossing to the right
5555        continue;
5556      }
5557      if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
5558        // no crossing
5559        continue;
5560      }
5561      if(y1 < y2 && (py >= y1 && py < y2) || // upward
5562          y1 > y2 && (py < y1 && py >= y2)) { // downward
5563        ++crosses;
5564      }
5565    }
5566    var contained = (crosses == -1) ?
5567      // on edge
5568      1 :
5569      // even (out) or odd (in)
5570      !!(crosses & 1);
5571
5572    return contained;
5573  },
5574  intersects: function(geometry) {
5575    var intersect = false;
5576    if(geometry.CLASS_NAME == "ZOO.Geometry.Point")
5577      intersect = this.containsPoint(geometry);
5578    else if(geometry.CLASS_NAME == "ZOO.Geometry.LineString")
5579      intersect = geometry.intersects(this);
5580    else if(geometry.CLASS_NAME == "ZOO.Geometry.LinearRing")
5581      intersect = ZOO.Geometry.LineString.prototype.intersects.apply(
5582          this, [geometry]
5583          );
5584    else
5585      for(var i=0, len=geometry.components.length; i<len; ++ i) {
5586        intersect = geometry.components[i].intersects(this);
5587        if(intersect)
5588          break;
5589      }
5590    return intersect;
5591  },
5592  getVertices: function(nodes) {
5593    return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
5594  },
5595  CLASS_NAME: "ZOO.Geometry.LinearRing"
5596});
5597/**
5598 * Class: ZOO.Geometry.MultiLineString
5599 * A MultiLineString is a geometry with multiple <ZOO.Geometry.LineString>
5600 * components.
5601 *
5602 * Inherits from:
5603 *  - <ZOO.Geometry.Collection>
5604 */
5605ZOO.Geometry.MultiLineString = ZOO.Class(
5606  ZOO.Geometry.Collection, {
5607  componentTypes: ["ZOO.Geometry.LineString"],
5608  /**
5609   * Constructor: ZOO.Geometry.MultiLineString
5610   * Constructor for a MultiLineString Geometry.
5611   *
5612   * Parameters:
5613   * components - {Array(<ZOO.Geometry.LineString>)}
5614   *
5615   */
5616  initialize: function(components) {
5617    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);       
5618  },
5619  split: function(geometry, options) {
5620    var results = null;
5621    var mutual = options && options.mutual;
5622    var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
5623    var sourceParts = [];
5624    var targetParts = [geometry];
5625    for(var i=0, len=this.components.length; i<len; ++i) {
5626      sourceLine = this.components[i];
5627      sourceSplit = false;
5628      for(var j=0; j < targetParts.length; ++j) { 
5629        splits = sourceLine.split(targetParts[j], options);
5630        if(splits) {
5631          if(mutual) {
5632            sourceLines = splits[0];
5633            for(var k=0, klen=sourceLines.length; k<klen; ++k) {
5634              if(k===0 && sourceParts.length)
5635                sourceParts[sourceParts.length-1].addComponent(
5636                  sourceLines[k]
5637                );
5638              else
5639                sourceParts.push(
5640                  new ZOO.Geometry.MultiLineString([
5641                    sourceLines[k]
5642                    ])
5643                );
5644            }
5645            sourceSplit = true;
5646            splits = splits[1];
5647          }
5648          if(splits.length) {
5649            // splice in new target parts
5650            splits.unshift(j, 1);
5651            Array.prototype.splice.apply(targetParts, splits);
5652            break;
5653          }
5654        }
5655      }
5656      if(!sourceSplit) {
5657        // source line was not hit
5658        if(sourceParts.length) {
5659          // add line to existing multi
5660          sourceParts[sourceParts.length-1].addComponent(
5661              sourceLine.clone()
5662              );
5663        } else {
5664          // create a fresh multi
5665          sourceParts = [
5666            new ZOO.Geometry.MultiLineString(
5667                sourceLine.clone()
5668                )
5669            ];
5670        }
5671      }
5672    }
5673    if(sourceParts && sourceParts.length > 1)
5674      sourceSplit = true;
5675    else
5676      sourceParts = [];
5677    if(targetParts && targetParts.length > 1)
5678      targetSplit = true;
5679    else
5680      targetParts = [];
5681    if(sourceSplit || targetSplit) {
5682      if(mutual)
5683        results = [sourceParts, targetParts];
5684      else
5685        results = targetParts;
5686    }
5687    return results;
5688  },
5689  splitWith: function(geometry, options) {
5690    var results = null;
5691    var mutual = options && options.mutual;
5692    var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
5693    if(geometry instanceof ZOO.Geometry.LineString) {
5694      targetParts = [];
5695      sourceParts = [geometry];
5696      for(var i=0, len=this.components.length; i<len; ++i) {
5697        targetSplit = false;
5698        targetLine = this.components[i];
5699        for(var j=0; j<sourceParts.length; ++j) {
5700          splits = sourceParts[j].split(targetLine, options);
5701          if(splits) {
5702            if(mutual) {
5703              sourceLines = splits[0];
5704              if(sourceLines.length) {
5705                // splice in new source parts
5706                sourceLines.unshift(j, 1);
5707                Array.prototype.splice.apply(sourceParts, sourceLines);
5708                j += sourceLines.length - 2;
5709              }
5710              splits = splits[1];
5711              if(splits.length === 0) {
5712                splits = [targetLine.clone()];
5713              }
5714            }
5715            for(var k=0, klen=splits.length; k<klen; ++k) {
5716              if(k===0 && targetParts.length) {
5717                targetParts[targetParts.length-1].addComponent(
5718                    splits[k]
5719                    );
5720              } else {
5721                targetParts.push(
5722                    new ZOO.Geometry.MultiLineString([
5723                      splits[k]
5724                      ])
5725                    );
5726              }
5727            }
5728            targetSplit = true;                   
5729          }
5730        }
5731        if(!targetSplit) {
5732          // target component was not hit
5733          if(targetParts.length) {
5734            // add it to any existing multi-line
5735            targetParts[targetParts.length-1].addComponent(
5736                targetLine.clone()
5737                );
5738          } else {
5739            // or start with a fresh multi-line
5740            targetParts = [
5741              new ZOO.Geometry.MultiLineString([
5742                  targetLine.clone()
5743                  ])
5744              ];
5745          }
5746
5747        }
5748      }
5749    } else {
5750      results = geometry.split(this);
5751    }
5752    if(sourceParts && sourceParts.length > 1)
5753      sourceSplit = true;
5754    else
5755      sourceParts = [];
5756    if(targetParts && targetParts.length > 1)
5757      targetSplit = true;
5758    else
5759      targetParts = [];
5760    if(sourceSplit || targetSplit) {
5761      if(mutual)
5762        results = [sourceParts, targetParts];
5763      else
5764        results = targetParts;
5765    }
5766    return results;
5767  },
5768  CLASS_NAME: "ZOO.Geometry.MultiLineString"
5769});
5770/**
5771 * Class: ZOO.Geometry.Polygon
5772 * Polygon is a collection of <ZOO.Geometry.LinearRing>.
5773 *
5774 * Inherits from:
5775 *  - <ZOO.Geometry.Collection>
5776 */
5777ZOO.Geometry.Polygon = ZOO.Class(
5778  ZOO.Geometry.Collection, {
5779  componentTypes: ["ZOO.Geometry.LinearRing"],
5780  /**
5781   * Constructor: OpenLayers.Geometry.Polygon
5782   * Constructor for a Polygon geometry.
5783   * The first ring (this.component[0])is the outer bounds of the polygon and
5784   * all subsequent rings (this.component[1-n]) are internal holes.
5785   *
5786   *
5787   * Parameters:
5788   * components - {Array(<ZOO.Geometry.LinearRing>)}
5789   */
5790  initialize: function(components) {
5791    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
5792  },
5793  /**
5794   * Method: getArea
5795   * Calculated by subtracting the areas of the internal holes from the
5796   *   area of the outer hole.
5797   *
5798   * Returns:
5799   * {float} The area of the geometry
5800   */
5801  getArea: function() {
5802    var area = 0.0;
5803    if ( this.components && (this.components.length > 0)) {
5804      area += Math.abs(this.components[0].getArea());
5805      for (var i=1, len=this.components.length; i<len; i++) {
5806        area -= Math.abs(this.components[i].getArea());
5807      }
5808    }
5809    return area;
5810  },
5811  /**
5812   * APIMethod: getGeodesicArea
5813   * Calculate the approximate area of the polygon were it projected onto
5814   *     the earth.
5815   *
5816   * Parameters:
5817   * projection - {<ZOO.Projection>} The spatial reference system
5818   *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
5819   *     assumed.
5820   *
5821   * Reference:
5822   * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
5823   *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
5824   *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
5825   *
5826   * Returns:
5827   * {float} The approximate geodesic area of the polygon in square meters.
5828   */
5829  getGeodesicArea: function(projection) {
5830    var area = 0.0;
5831    if(this.components && (this.components.length > 0)) {
5832      area += Math.abs(this.components[0].getGeodesicArea(projection));
5833      for(var i=1, len=this.components.length; i<len; i++) {
5834          area -= Math.abs(this.components[i].getGeodesicArea(projection));
5835      }
5836    }
5837    return area;
5838  },
5839  /**
5840   * Method: containsPoint
5841   * Test if a point is inside a polygon.  Points on a polygon edge are
5842   *     considered inside.
5843   *
5844   * Parameters:
5845   * point - {<ZOO.Geometry.Point>}
5846   *
5847   * Returns:
5848   * {Boolean | Number} The point is inside the polygon.  Returns 1 if the
5849   *     point is on an edge.  Returns boolean otherwise.
5850   */
5851  containsPoint: function(point) {
5852    var numRings = this.components.length;
5853    var contained = false;
5854    if(numRings > 0) {
5855    // check exterior ring - 1 means on edge, boolean otherwise
5856      contained = this.components[0].containsPoint(point);
5857      if(contained !== 1) {
5858        if(contained && numRings > 1) {
5859          // check interior rings
5860          var hole;
5861          for(var i=1; i<numRings; ++i) {
5862            hole = this.components[i].containsPoint(point);
5863            if(hole) {
5864              if(hole === 1)
5865                contained = 1;
5866              else
5867                contained = false;
5868              break;
5869            }
5870          }
5871        }
5872      }
5873    }
5874    return contained;
5875  },
5876  intersects: function(geometry) {
5877    var intersect = false;
5878    var i, len;
5879    if(geometry.CLASS_NAME == "ZOO.Geometry.Point") {
5880      intersect = this.containsPoint(geometry);
5881    } else if(geometry.CLASS_NAME == "ZOO.Geometry.LineString" ||
5882              geometry.CLASS_NAME == "ZOO.Geometry.LinearRing") {
5883      // check if rings/linestrings intersect
5884      for(i=0, len=this.components.length; i<len; ++i) {
5885        intersect = geometry.intersects(this.components[i]);
5886        if(intersect) {
5887          break;
5888        }
5889      }
5890      if(!intersect) {
5891        // check if this poly contains points of the ring/linestring
5892        for(i=0, len=geometry.components.length; i<len; ++i) {
5893          intersect = this.containsPoint(geometry.components[i]);
5894          if(intersect) {
5895            break;
5896          }
5897        }
5898      }
5899    } else {
5900      for(i=0, len=geometry.components.length; i<len; ++ i) {
5901        intersect = this.intersects(geometry.components[i]);
5902        if(intersect)
5903          break;
5904      }
5905    }
5906    // check case where this poly is wholly contained by another
5907    if(!intersect && geometry.CLASS_NAME == "ZOO.Geometry.Polygon") {
5908      // exterior ring points will be contained in the other geometry
5909      var ring = this.components[0];
5910      for(i=0, len=ring.components.length; i<len; ++i) {
5911        intersect = geometry.containsPoint(ring.components[i]);
5912        if(intersect)
5913          break;
5914      }
5915    }
5916    return intersect;
5917  },
5918  distanceTo: function(geometry, options) {
5919    var edge = !(options && options.edge === false);
5920    var result;
5921    // this is the case where we might not be looking for distance to edge
5922    if(!edge && this.intersects(geometry))
5923      result = 0;
5924    else
5925      result = ZOO.Geometry.Collection.prototype.distanceTo.apply(
5926          this, [geometry, options]
5927          );
5928    return result;
5929  },
5930  CLASS_NAME: "ZOO.Geometry.Polygon"
5931});
5932/**
5933 * Method: createRegularPolygon
5934 * Create a regular polygon around a radius. Useful for creating circles
5935 * and the like.
5936 *
5937 * Parameters:
5938 * origin - {<ZOO.Geometry.Point>} center of polygon.
5939 * radius - {Float} distance to vertex, in map units.
5940 * sides - {Integer} Number of sides. 20 approximates a circle.
5941 * rotation - {Float} original angle of rotation, in degrees.
5942 */
5943OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) { 
5944    var angle = Math.PI * ((1/sides) - (1/2));
5945    if(rotation) {
5946        angle += (rotation / 180) * Math.PI;
5947    }
5948    var rotatedAngle, x, y;
5949    var points = [];
5950    for(var i=0; i<sides; ++i) {
5951        rotatedAngle = angle + (i * 2 * Math.PI / sides);
5952        x = origin.x + (radius * Math.cos(rotatedAngle));
5953        y = origin.y + (radius * Math.sin(rotatedAngle));
5954        points.push(new ZOO.Geometry.Point(x, y));
5955    }
5956    var ring = new ZOO.Geometry.LinearRing(points);
5957    return new ZOO.Geometry.Polygon([ring]);
5958};
5959/**
5960 * Class: ZOO.Geometry.MultiPolygon
5961 * MultiPolygon is a geometry with multiple <ZOO.Geometry.Polygon>
5962 * components.  Create a new instance with the <ZOO.Geometry.MultiPolygon>
5963 * constructor.
5964 *
5965 * Inherits from:
5966 *  - <ZOO.Geometry.Collection>
5967 */
5968ZOO.Geometry.MultiPolygon = ZOO.Class(
5969  ZOO.Geometry.Collection, {
5970  componentTypes: ["ZOO.Geometry.Polygon"],
5971  /**
5972   * Constructor: OpenLayers.Geometry.MultiPolygon
5973   * Create a new MultiPolygon geometry
5974   *
5975   * Parameters:
5976   * components - {Array(<ZOO.Geometry.Polygon>)} An array of polygons
5977   *              used to generate the MultiPolygon
5978   *
5979   */
5980  initialize: function(components) {
5981    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
5982  },
5983  CLASS_NAME: "ZOO.Geometry.MultiPolygon"
5984});
5985
5986ZOO.Process = ZOO.Class({
5987  schemaLocation: "http://www.opengis.net/wps/1.0.0/../wpsExecute_request.xsd",
5988  namespaces: {
5989    ows: "http://www.opengis.net/ows/1.1",
5990    wps: "http://www.opengis.net/wps/1.0.0",
5991    xlink: "http://www.w3.org/1999/xlink",
5992    xsi: "http://www.w3.org/2001/XMLSchema-instance",
5993  },
5994  url: 'http://localhost/zoo',
5995  identifier: null,
5996  initialize: function(url,identifier) {
5997    this.url = url;
5998    this.identifier = identifier;
5999  },
6000  Execute: function(inputs) {
6001    if (this.identifier == null)
6002      return null;
6003    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>');
6004    body = body.toXMLString();
6005    var response = ZOO.Request.Post(this.url,body,['Content-Type: text/xml; charset=UTF-8']);
6006    return response;
6007  },
6008  buildInput: {
6009    'complex': function(identifier,data) {
6010      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>');
6011      input.*::Data.*::ComplexData.@mimeType = data.mimetype ? data.mimetype : 'text/plain';
6012      if (data.encoding)
6013        input.*::Data.*::ComplexData.@encoding = data.encoding;
6014      if (data.schema)
6015        input.*::Data.*::ComplexData.@schema = data.schema;
6016      input = input.toXMLString();
6017      return input;
6018    },
6019    'reference': function(identifier,data) {
6020      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>';
6021    },
6022    'literal': function(identifier,data) {
6023      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>');
6024      if (data.type)
6025        input.*::Data.*::LiteralData.@dataType = data.type;
6026      if (data.uom)
6027        input.*::Data.*::LiteralData.@uom = data.uom;
6028      input = input.toXMLString();
6029      return input;
6030    }
6031  },
6032  buildDataInputsNode:function(inputs){
6033    var data, builder, inputsArray=[];
6034    for (var attr in inputs) {
6035      data = inputs[attr];
6036      if (data.mimetype || data.type == 'complex')
6037        builder = this.buildInput['complex'];
6038      else if (data.type == 'reference' || data.type == 'url')
6039        builder = this.buildInput['reference'];
6040      else
6041        builder = this.buildInput['literal'];
6042      inputsArray.push(builder.apply(this,[attr,data]));
6043    }
6044    return '<wps:DataInputs xmlns:wps="'+this.namespaces['wps']+'">'+inputsArray.join('\n')+'</wps:DataInputs>';
6045  },
6046  CLASS_NAME: "ZOO.Process"
6047});
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