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

Last change on this file since 383 was 383, checked in by djay, 11 years ago

Fix issue in JS support and for binary RawDataOutput?. Add Content-Length and Content-Disposition (optional) to response header. Add support to JS ZOO-API for sending headers and handling multi-valued inputs.

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

Search

ZOO Sponsors

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

Become a sponsor !

Knowledge partners

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

Become a knowledge partner

Related links

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