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

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

Use valid header as described by Chris long time ago sorry for taking too much time to apply this (http://lists.osgeo.org/pipermail/zoo-discuss/2011-April/000632.html).

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