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

Last change on this file since 278 was 278, checked in by djay, 13 years ago

Small forgotten fix for ZOO-API (using CDATA for value content, still not xlink:href support).

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

Search

Context Navigation

ZOO Sponsors

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

Become a sponsor !

Knowledge partners

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

Become a knowledge partner

Related links

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