source: trunk/zoo-project/zoo-api/js/ZOO-api.js @ 955

Last change on this file since 955 was 917, checked in by djay, 5 years ago

Merge prototype-v0 branch in trunk

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

Search

Context Navigation

ZOO Sponsors

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

Become a sponsor !

Knowledge partners

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

Become a knowledge partner

Related links

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