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

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

Fix tickets #63, #64 and #68

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

Search

ZOO Sponsors

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

Become a sponsor !

Knowledge partners

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

Become a knowledge partner

Related links

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