source: trunk/zoo-api/js/ZOO-proj4js.js @ 144

Last change on this file since 144 was 144, checked in by reluc, 13 years ago

Add copyright

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