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

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

Add the ZOOTranslate method to the JavaScript? API for supporting multiple languages support from JavaScript? services.

File size: 191.6 KB
Line 
1/**
2 * Author : René-Luc D'Hont
3 *
4 * Copyright 2010 3liz SARL. All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25/**
26 * 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 response = ZOO.Request.Post(this.url,body,['Content-Type: text/xml; charset=UTF-8']);
6162    return response;
6163  },
6164  buildOutput:{
6165    /**
6166     * Method: buildOutput.ResponseDocument
6167     * Given an E4XElement representing the WPS ResponseDocument output.
6168     *
6169     * Parameters:
6170     * identifier - {String} the input indetifier
6171     * data - {Object} A WPS complex data input.
6172     *
6173     * Returns:
6174     * {E4XElement} A WPS Input node.
6175     */
6176    'ResponseDocument': function(identifier,obj) {
6177      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>');
6178      if (obj.encoding)
6179        output.*::Data.*::ComplexData.@encoding = obj.encoding;
6180      if (obj.schema)
6181        output.*::Data.*::ComplexData.@schema = obj.schema;
6182      output = output.toXMLString();
6183      return output;
6184    },
6185    'RawDataOutput': function(identifier,obj) {
6186      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>');
6187      if (obj.encoding)
6188        output.*::Data.*::ComplexData.@encoding = obj.encoding;
6189      if (obj.schema)
6190        output.*::Data.*::ComplexData.@schema = obj.schema;
6191      output = output.toXMLString();
6192      return output;
6193    }
6194
6195  },
6196  /**
6197   * Property: buildInput
6198   * Object containing methods to build WPS inputs.
6199   */
6200  buildInput: {
6201    /**
6202     * Method: buildInput.complex
6203     * Given an E4XElement representing the WPS complex data input.
6204     *
6205     * Parameters:
6206     * identifier - {String} the input indetifier
6207     * data - {Object} A WPS complex data input.
6208     *
6209     * Returns:
6210     * {E4XElement} A WPS Input node.
6211     */
6212    'complex': function(identifier,data) {
6213      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>');
6214      if(data.xlink)
6215        input.*::Reference.@mimeType = data.mimetype ? data.mimetype : 'application/json';
6216      else
6217        input.*::Data.*::ComplexData.@mimeType = data.mimetype ? data.mimetype : 'application/json';
6218      if (data.encoding)
6219        input.*::Data.*::ComplexData.@encoding = data.encoding;
6220      if (data.schema)
6221        input.*::Data.*::ComplexData.@schema = data.schema;
6222      input = input.toXMLString();
6223      return input;
6224    },
6225    /**
6226     * Method: buildInput.reference
6227     * Given an E4XElement representing the WPS reference input.
6228     *
6229     * Parameters:
6230     * identifier - {String} the input indetifier
6231     * data - {Object} A WPS reference input.
6232     *
6233     * Returns:
6234     * {E4XElement} A WPS Input node.
6235     */
6236    'reference': function(identifier,data) {
6237      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>';
6238    },
6239    /**
6240     * Method: buildInput.literal
6241     * Given an E4XElement representing the WPS literal data input.
6242     *
6243     * Parameters:
6244     * identifier - {String} the input indetifier
6245     * data - {Object} A WPS literal data input.
6246     *
6247     * Returns:
6248     * {E4XElement} The WPS Input node.
6249     */
6250    'literal': function(identifier,data) {
6251      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>');
6252      if (data.type)
6253        input.*::Data.*::LiteralData.@dataType = data.type;
6254      if (data.uom)
6255        input.*::Data.*::LiteralData.@uom = data.uom;
6256      input = input.toXMLString();
6257      return input;
6258    }
6259  },
6260  /**
6261   * Method: buildDataInputsNode
6262   * Method to build the WPS DataInputs element.
6263   *
6264   * Parameters:
6265   * inputs - {Object}
6266   *
6267   * Returns:
6268   * {E4XElement} The WPS DataInputs node for Execute query.
6269   */
6270  buildDataInputsNode:function(inputs){
6271    var data, builder, inputsArray=[];
6272    for (var attr in inputs) {
6273      data = inputs[attr];
6274      if (data.mimetype || data.type == 'complex')
6275        builder = this.buildInput['complex'];
6276      else if (data.type == 'reference' || data.type == 'url')
6277        builder = this.buildInput['reference'];
6278      else
6279        builder = this.buildInput['literal'];
6280      inputsArray.push(builder.apply(this,[attr,data]));
6281    }
6282    return '<wps:DataInputs xmlns:wps="'+this.namespaces['wps']+'">'+inputsArray.join('\n')+'</wps:DataInputs>';
6283  },
6284
6285  buildDataOutputsNode:function(outputs){
6286    var data, builder, outputsArray=[];
6287    for (var attr in outputs) {
6288      data = outputs[attr];
6289      builder = this.buildOutput[data.type];
6290      outputsArray.push(builder.apply(this,[attr,data]));
6291    }
6292    return outputsArray.join('\n');
6293  },
6294
6295  CLASS_NAME: "ZOO.Process"
6296});
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