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

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

Add comments to ZOO-api.js, need to be continue

File size: 120.5 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 * Class: ZOO
27 */
28ZOO = {
29  /**
30   * Constant: SERVICE_ACCEPTED
31   * {Integer} used for
32   */
33  SERVICE_ACCEPTED: 0,
34  /**
35   * Constant: SERVICE_STARTED
36   * {Integer} used for
37   */
38  SERVICE_STARTED: 1,
39  /**
40   * Constant: SERVICE_PAUSED
41   * {Integer} used for
42   */
43  SERVICE_PAUSED: 2,
44  /**
45   * Constant: SERVICE_SUCCEEDED
46   * {Integer} used for
47   */
48  SERVICE_SUCCEEDED: 3,
49  /**
50   * Constant: SERVICE_FAILED
51   * {Integer} used for
52   */
53  SERVICE_FAILED: 4,
54  /**
55   * Function: removeItem
56   * Remove an object from an array. Iterates through the array
57   *     to find the item, then removes it.
58   *
59   * Parameters:
60   * array - {Array}
61   * item - {Object}
62   *
63   * Return
64   * {Array} A reference to the array
65   */
66  removeItem: function(array, item) {
67    for(var i = array.length - 1; i >= 0; i--) {
68        if(array[i] == item) {
69            array.splice(i,1);
70        }
71    }
72    return array;
73  },
74  /**
75   * Function: indexOf
76   *
77   * Parameters:
78   * array - {Array}
79   * obj - {Object}
80   *
81   * Returns:
82   * {Integer} The index at, which the first object was found in the array.
83   *           If not found, returns -1.
84   */
85  indexOf: function(array, obj) {
86    for(var i=0, len=array.length; i<len; i++) {
87      if (array[i] == obj)
88        return i;
89    }
90    return -1;   
91  },
92  /**
93   * Function: extend
94   * Copy all properties of a source object to a destination object. Modifies
95   *     the passed in destination object.  Any properties on the source object
96   *     that are set to undefined will not be (re)set on the destination object.
97   *
98   * Parameters:
99   * destination - {Object} The object that will be modified
100   * source - {Object} The object with properties to be set on the destination
101   *
102   * Returns:
103   * {Object} The destination object.
104   */
105  extend: function(destination, source) {
106    destination = destination || {};
107    if(source) {
108      for(var property in source) {
109        var value = source[property];
110        if(value !== undefined)
111          destination[property] = value;
112      }
113    }
114    return destination;
115  },
116  /**
117   * Function: Class
118   * Method used to create ZOO classes. Includes support for
119   *     multiple inheritance.
120   */
121  Class: function() {
122    var Class = function() {
123      this.initialize.apply(this, arguments);
124    };
125    var extended = {};
126    var parent;
127    for(var i=0; i<arguments.length; ++i) {
128      if(typeof arguments[i] == "function") {
129        // get the prototype of the superclass
130        parent = arguments[i].prototype;
131      } else {
132        // in this case we're extending with the prototype
133        parent = arguments[i];
134      }
135      ZOO.extend(extended, parent);
136    }
137    Class.prototype = extended;
138
139    return Class;
140  },
141  /**
142   * Function: UpdateStatus
143   * Method used to update the status of the process
144   *
145   * Parameters:
146   * env - {Object} The environment object
147   * value - {Float} the status value between 0 to 100
148   */
149  UpdateStatus: function(env,value) {
150    return ZOOUpdateStatus(env,value);
151  }
152};
153
154/**
155 * Class: ZOO.String
156 * Contains convenience methods for string manipulation
157 */
158ZOO.String = {
159  /**
160   * Function: startsWith
161   * Test whether a string starts with another string.
162   *
163   * Parameters:
164   * str - {String} The string to test.
165   * sub - {Sring} The substring to look for.
166   * 
167   * Returns:
168   * {Boolean} The first string starts with the second.
169   */
170  startsWith: function(str, sub) {
171    return (str.indexOf(sub) == 0);
172  },
173  /**
174   * Function: contains
175   * Test whether a string contains another string.
176   *
177   * Parameters:
178   * str - {String} The string to test.
179   * sub - {String} The substring to look for.
180   *
181   * Returns:
182   * {Boolean} The first string contains the second.
183   */
184  contains: function(str, sub) {
185    return (str.indexOf(sub) != -1);
186  },
187  /**
188   * Function: trim
189   * Removes leading and trailing whitespace characters from a string.
190   *
191   * Parameters:
192   * str - {String} The (potentially) space padded string.  This string is not
193   *     modified.
194   *
195   * Returns:
196   * {String} A trimmed version of the string with all leading and
197   *     trailing spaces removed.
198   */
199  trim: function(str) {
200    return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
201  },
202  /**
203   * Function: camelize
204   * Camel-case a hyphenated string.
205   *     Ex. "chicken-head" becomes "chickenHead", and
206   *     "-chicken-head" becomes "ChickenHead".
207   *
208   * Parameters:
209   * str - {String} The string to be camelized.  The original is not modified.
210   *
211   * Returns:
212   * {String} The string, camelized
213   *
214   */
215  camelize: function(str) {
216    var oStringList = str.split('-');
217    var camelizedString = oStringList[0];
218    for (var i=1, len=oStringList.length; i<len; i++) {
219      var s = oStringList[i];
220      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
221    }
222    return camelizedString;
223  },
224  /**
225   * Property: tokenRegEx
226   * Used to find tokens in a string.
227   * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
228   */
229  tokenRegEx:  /\$\{([\w.]+?)\}/g,
230  /**
231   * Property: numberRegEx
232   * Used to test strings as numbers.
233   */
234  numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
235  /**
236   * Function: isNumeric
237   * Determine whether a string contains only a numeric value.
238   *
239   * Examples:
240   * (code)
241   * ZOO.String.isNumeric("6.02e23") // true
242   * ZOO.String.isNumeric("12 dozen") // false
243   * ZOO.String.isNumeric("4") // true
244   * ZOO.String.isNumeric(" 4 ") // false
245   * (end)
246   *
247   * Returns:
248   * {Boolean} String contains only a number.
249   */
250  isNumeric: function(value) {
251    return ZOO.String.numberRegEx.test(value);
252  },
253  /**
254   * Function: numericIf
255   * Converts a string that appears to be a numeric value into a number.
256   *
257   * Returns
258   * {Number|String} a Number if the passed value is a number, a String
259   *     otherwise.
260   */
261  numericIf: function(value) {
262    return ZOO.String.isNumeric(value) ? parseFloat(value) : value;
263  }
264};
265
266/**
267 * Class: ZOO.Request
268 * Contains convenience methods for working with ZOORequest which
269 *     replace XMLHttpRequest. Because of we are not in a browser
270 *     JavaScript environment, ZOO Project provides a method to
271 *     query servers which is based on curl : ZOORequest.
272 */
273ZOO.Request = {
274  /**
275   * Function: GET
276   * Send an HTTP GET request.
277   *
278   * Parameters:
279   * url - {String} The URL to request.
280   * params - {Object} Params to add to the url
281   *
282   * Returns:
283   * {String} Request result.
284   */
285  Get: function(url,params) {
286    var paramsArray = [];
287    for (var key in params) {
288      var value = params[key];
289      if ((value != null) && (typeof value != 'function')) {
290        var encodedValue;
291        if (typeof value == 'object' && value.constructor == Array) {
292          /* value is an array; encode items and separate with "," */
293          var encodedItemArray = [];
294          for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
295            encodedItemArray.push(encodeURIComponent(value[itemIndex]));
296          }
297          encodedValue = encodedItemArray.join(",");
298        }
299        else {
300          /* value is a string; simply encode */
301          encodedValue = encodeURIComponent(value);
302        }
303        paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
304      }
305    }
306    var paramString = paramsArray.join("&");
307    if(paramString.length > 0) {
308      var separator = (url.indexOf('?') > -1) ? '&' : '?';
309      url += separator + paramString;
310    }
311    return ZOORequest('GET',url);
312  },
313  /**
314   * Function: POST
315   * Send an HTTP POST request.
316   *
317   * Parameters:
318   * url - {String} The URL to request.
319   * body - {String} The request's body to send.
320   * headers - {Object} A key-value object of headers to push to
321   *     the request's head
322   *
323   * Returns:
324   * {String} Request result.
325   */
326  Post: function(url,body,headers) {
327    if(!(headers instanceof Array)) {
328      var headersArray = [];
329      for (var name in headers) {
330        headersArray.push(name+': '+headers[name]); 
331      }
332      headers = headersArray;
333    }
334    return ZOORequest('POST',url,body,headers);
335  }
336};
337
338/**
339 * Class: ZOO.Bounds
340 * Instances of this class represent bounding boxes.  Data stored as left,
341 *     bottom, right, top floats. All values are initialized to null,
342 *     however, you should make sure you set them before using the bounds
343 *     for anything.
344 */
345ZOO.Bounds = ZOO.Class({
346  /**
347   * Property: left
348   * {Number} Minimum horizontal coordinate.
349   */
350  left: null,
351  /**
352   * Property: bottom
353   * {Number} Minimum vertical coordinate.
354   */
355  bottom: null,
356  /**
357   * Property: right
358   * {Number} Maximum horizontal coordinate.
359   */
360  right: null,
361  /**
362   * Property: top
363   * {Number} Maximum vertical coordinate.
364   */
365  top: null,
366  /**
367   * Constructor: ZOO.Bounds
368   * Construct a new bounds object.
369   *
370   * Parameters:
371   * left - {Number} The left bounds of the box.  Note that for width
372   *        calculations, this is assumed to be less than the right value.
373   * bottom - {Number} The bottom bounds of the box.  Note that for height
374   *          calculations, this is assumed to be more than the top value.
375   * right - {Number} The right bounds.
376   * top - {Number} The top bounds.
377   */
378  initialize: function(left, bottom, right, top) {
379    if (left != null)
380      this.left = parseFloat(left);
381    if (bottom != null)
382      this.bottom = parseFloat(bottom);
383    if (right != null)
384      this.right = parseFloat(right);
385    if (top != null)
386      this.top = parseFloat(top);
387  },
388  /**
389   * Method: clone
390   * Create a cloned instance of this bounds.
391   *
392   * Returns:
393   * {<ZOO.Bounds>} A fresh copy of the bounds
394   */
395  clone:function() {
396    return new ZOO.Bounds(this.left, this.bottom, 
397                          this.right, this.top);
398  },
399  /**
400   * Method: equals
401   * Test a two bounds for equivalence.
402   *
403   * Parameters:
404   * bounds - {<ZOO.Bounds>}
405   *
406   * Returns:
407   * {Boolean} The passed-in bounds object has the same left,
408   *           right, top, bottom components as this.  Note that if bounds
409   *           passed in is null, returns false.
410   */
411  equals:function(bounds) {
412    var equals = false;
413    if (bounds != null)
414        equals = ((this.left == bounds.left) && 
415                  (this.right == bounds.right) &&
416                  (this.top == bounds.top) && 
417                  (this.bottom == bounds.bottom));
418    return equals;
419  },
420  /**
421   * Method: toString
422   *
423   * Returns:
424   * {String} String representation of bounds object.
425   *          (ex.<i>"left-bottom=(5,42) right-top=(10,45)"</i>)
426   */
427  toString:function() {
428    return ( "left-bottom=(" + this.left + "," + this.bottom + ")"
429              + " right-top=(" + this.right + "," + this.top + ")" );
430  },
431  /**
432   * APIMethod: toArray
433   *
434   * Returns:
435   * {Array} array of left, bottom, right, top
436   */
437  toArray: function() {
438    return [this.left, this.bottom, this.right, this.top];
439  },
440  /**
441   * Method: toBBOX
442   *
443   * Parameters:
444   * decimal - {Integer} How many significant digits in the bbox coords?
445   *                     Default is 6
446   *
447   * Returns:
448   * {String} Simple String representation of bounds object.
449   *          (ex. <i>"5,42,10,45"</i>)
450   */
451  toBBOX:function(decimal) {
452    if (decimal== null)
453      decimal = 6; 
454    var mult = Math.pow(10, decimal);
455    var bbox = Math.round(this.left * mult) / mult + "," + 
456               Math.round(this.bottom * mult) / mult + "," + 
457               Math.round(this.right * mult) / mult + "," + 
458               Math.round(this.top * mult) / mult;
459    return bbox;
460  },
461  /**
462   * Method: toGeometry
463   * Create a new polygon geometry based on this bounds.
464   *
465   * Returns:
466   * {<ZOO.Geometry.Polygon>} A new polygon with the coordinates
467   *     of this bounds.
468   */
469  toGeometry: function() {
470    return new ZOO.Geometry.Polygon([
471      new ZOO.Geometry.LinearRing([
472        new ZOO.Geometry.Point(this.left, this.bottom),
473        new ZOO.Geometry.Point(this.right, this.bottom),
474        new ZOO.Geometry.Point(this.right, this.top),
475        new ZOO.Geometry.Point(this.left, this.top)
476      ])
477    ]);
478  },
479  /**
480   * Method: getWidth
481   *
482   * Returns:
483   * {Float} The width of the bounds
484   */
485  getWidth:function() {
486    return (this.right - this.left);
487  },
488  /**
489   * Method: getHeight
490   *
491   * Returns:
492   * {Float} The height of the bounds (top minus bottom).
493   */
494  getHeight:function() {
495    return (this.top - this.bottom);
496  },
497  /**
498   * Method: add
499   *
500   * Parameters:
501   * x - {Float}
502   * y - {Float}
503   *
504   * Returns:
505   * {<ZOO.Bounds>} A new bounds whose coordinates are the same as
506   *     this, but shifted by the passed-in x and y values.
507   */
508  add:function(x, y) {
509    if ( (x == null) || (y == null) )
510      return null;
511    return new ZOO.Bounds(this.left + x, this.bottom + y,
512                                 this.right + x, this.top + y);
513  },
514  /**
515   * Method: extend
516   * Extend the bounds to include the point, lonlat, or bounds specified.
517   *     Note, this function assumes that left < right and bottom < top.
518   *
519   * Parameters:
520   * object - {Object} Can be Point, or Bounds
521   */
522  extend:function(object) {
523    var bounds = null;
524    if (object) {
525      // clear cached center location
526      switch(object.CLASS_NAME) {
527        case "ZOO.Geometry.Point":
528          bounds = new ZOO.Bounds(object.x, object.y,
529                                         object.x, object.y);
530          break;
531        case "ZOO.Bounds":   
532          bounds = object;
533          break;
534      }
535      if (bounds) {
536        if ( (this.left == null) || (bounds.left < this.left))
537          this.left = bounds.left;
538        if ( (this.bottom == null) || (bounds.bottom < this.bottom) )
539          this.bottom = bounds.bottom;
540        if ( (this.right == null) || (bounds.right > this.right) )
541          this.right = bounds.right;
542        if ( (this.top == null) || (bounds.top > this.top) )
543          this.top = bounds.top;
544      }
545    }
546  },
547  /**
548   * APIMethod: contains
549   *
550   * Parameters:
551   * x - {Float}
552   * y - {Float}
553   * inclusive - {Boolean} Whether or not to include the border.
554   *     Default is true.
555   *
556   * Returns:
557   * {Boolean} Whether or not the passed-in coordinates are within this
558   *     bounds.
559   */
560  contains:function(x, y, inclusive) {
561     //set default
562     if (inclusive == null)
563       inclusive = true;
564     if (x == null || y == null)
565       return false;
566     x = parseFloat(x);
567     y = parseFloat(y);
568
569     var contains = false;
570     if (inclusive)
571       contains = ((x >= this.left) && (x <= this.right) && 
572                   (y >= this.bottom) && (y <= this.top));
573     else
574       contains = ((x > this.left) && (x < this.right) && 
575                   (y > this.bottom) && (y < this.top));
576     return contains;
577  },
578  /**
579   * Method: intersectsBounds
580   * Determine whether the target bounds intersects this bounds.  Bounds are
581   *     considered intersecting if any of their edges intersect or if one
582   *     bounds contains the other.
583   *
584   * Parameters:
585   * bounds - {<ZOO.Bounds>} The target bounds.
586   * inclusive - {Boolean} Treat coincident borders as intersecting.  Default
587   *     is true.  If false, bounds that do not overlap but only touch at the
588   *     border will not be considered as intersecting.
589   *
590   * Returns:
591   * {Boolean} The passed-in bounds object intersects this bounds.
592   */
593  intersectsBounds:function(bounds, inclusive) {
594    if (inclusive == null)
595      inclusive = true;
596    var intersects = false;
597    var mightTouch = (
598        this.left == bounds.right ||
599        this.right == bounds.left ||
600        this.top == bounds.bottom ||
601        this.bottom == bounds.top
602    );
603    if (inclusive || !mightTouch) {
604      var inBottom = (
605          ((bounds.bottom >= this.bottom) && (bounds.bottom <= this.top)) ||
606          ((this.bottom >= bounds.bottom) && (this.bottom <= bounds.top))
607          );
608      var inTop = (
609          ((bounds.top >= this.bottom) && (bounds.top <= this.top)) ||
610          ((this.top > bounds.bottom) && (this.top < bounds.top))
611          );
612      var inLeft = (
613          ((bounds.left >= this.left) && (bounds.left <= this.right)) ||
614          ((this.left >= bounds.left) && (this.left <= bounds.right))
615          );
616      var inRight = (
617          ((bounds.right >= this.left) && (bounds.right <= this.right)) ||
618          ((this.right >= bounds.left) && (this.right <= bounds.right))
619          );
620      intersects = ((inBottom || inTop) && (inLeft || inRight));
621    }
622    return intersects;
623  },
624  /**
625   * Method: containsBounds
626   * Determine whether the target bounds is contained within this bounds.
627   *
628   * bounds - {<ZOO.Bounds>} The target bounds.
629   * partial - {Boolean} If any of the target corners is within this bounds
630   *     consider the bounds contained.  Default is false.  If true, the
631   *     entire target bounds must be contained within this bounds.
632   * inclusive - {Boolean} Treat shared edges as contained.  Default is
633   *     true.
634   *
635   * Returns:
636   * {Boolean} The passed-in bounds object is contained within this bounds.
637   */
638  containsBounds:function(bounds, partial, inclusive) {
639    if (partial == null)
640      partial = false;
641    if (inclusive == null)
642      inclusive = true;
643    var bottomLeft  = this.contains(bounds.left, bounds.bottom, inclusive);
644    var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
645    var topLeft  = this.contains(bounds.left, bounds.top, inclusive);
646    var topRight = this.contains(bounds.right, bounds.top, inclusive);
647    return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
648                     : (bottomLeft && bottomRight && topLeft && topRight);
649  },
650  CLASS_NAME: 'ZOO.Bounds'
651});
652
653/**
654 * Class: ZOO.Projection
655 * Class for coordinate transforms between coordinate systems.
656 *     Depends on the zoo-proj4js library. zoo-proj4js library
657 *     is loaded by the ZOO Kernel with zoo-api.
658 */
659ZOO.Projection = ZOO.Class({
660  /**
661   * Property: proj
662   * {Object} Proj4js.Proj instance.
663   */
664  proj: null,
665  /**
666   * Property: projCode
667   * {String}
668   */
669  projCode: null,
670  /**
671   * Constructor: OpenLayers.Projection
672   * This class offers several methods for interacting with a wrapped
673   *     zoo-pro4js projection object.
674   *
675   * Parameters:
676   * projCode - {String} A string identifying the Well Known Identifier for
677   *    the projection.
678   * options - {Object} An optional object to set additional properties.
679   *
680   * Returns:
681   * {<ZOO.Projection>} A projection object.
682   */
683  initialize: function(projCode, options) {
684    ZOO.extend(this, options);
685    this.projCode = projCode;
686    if (Proj4js) {
687      this.proj = new Proj4js.Proj(projCode);
688    }
689  },
690  /**
691   * Method: getCode
692   * Get the string SRS code.
693   *
694   * Returns:
695   * {String} The SRS code.
696   */
697  getCode: function() {
698    return this.proj ? this.proj.srsCode : this.projCode;
699  },
700  /**
701   * Method: getUnits
702   * Get the units string for the projection -- returns null if
703   *     zoo-proj4js is not available.
704   *
705   * Returns:
706   * {String} The units abbreviation.
707   */
708  getUnits: function() {
709    return this.proj ? this.proj.units : null;
710  },
711  /**
712   * Method: toString
713   * Convert projection to string (getCode wrapper).
714   *
715   * Returns:
716   * {String} The projection code.
717   */
718  toString: function() {
719    return this.getCode();
720  },
721  /**
722   * Method: equals
723   * Test equality of two projection instances.  Determines equality based
724   *     soley on the projection code.
725   *
726   * Returns:
727   * {Boolean} The two projections are equivalent.
728   */
729  equals: function(projection) {
730    if (projection && projection.getCode)
731      return this.getCode() == projection.getCode();
732    else
733      return false;
734  },
735  /* Method: destroy
736   * Destroy projection object.
737   */
738  destroy: function() {
739    this.proj = null;
740    this.projCode = null;
741  },
742  CLASS_NAME: 'ZOO.Projection'
743});
744/**
745 * Method: transform
746 * Transform a point coordinate from one projection to another.  Note that
747 *     the input point is transformed in place.
748 *
749 * Parameters:
750 * point - {{ZOO.Geometry.Point> | Object} An object with x and y
751 *     properties representing coordinates in those dimensions.
752 * sourceProj - {OpenLayers.Projection} Source map coordinate system
753 * destProj - {OpenLayers.Projection} Destination map coordinate system
754 *
755 * Returns:
756 * point - {object} A transformed coordinate.  The original point is modified.
757 */
758ZOO.Projection.transform = function(point, source, dest) {
759    if (source.proj && dest.proj)
760        point = Proj4js.transform(source.proj, dest.proj, point);
761    return point;
762};
763
764/**
765 * Class: ZOO.Format
766 * Base class for format reading/writing a variety of formats. Subclasses
767 *     of ZOO.Format are expected to have read and write methods.
768 */
769ZOO.Format = ZOO.Class({
770  /**
771   * Property: options
772   * {Object} A reference to options passed to the constructor.
773   */
774  options:null,
775  /**
776   * Property: externalProjection
777   * {<ZOO.Projection>} When passed a externalProjection and
778   *     internalProjection, the format will reproject the geometries it
779   *     reads or writes. The externalProjection is the projection used by
780   *     the content which is passed into read or which comes out of write.
781   *     In order to reproject, a projection transformation function for the
782   *     specified projections must be available. This support is provided
783   *     via zoo-proj4js.
784   */
785  externalProjection: null,
786  /**
787   * Property: internalProjection
788   * {<ZOO.Projection>} When passed a externalProjection and
789   *     internalProjection, the format will reproject the geometries it
790   *     reads or writes. The internalProjection is the projection used by
791   *     the geometries which are returned by read or which are passed into
792   *     write.  In order to reproject, a projection transformation function
793   *     for the specified projections must be available. This support is
794   *     provided via zoo-proj4js.
795   */
796  internalProjection: null,
797  /**
798   * Property: data
799   * {Object} When <keepData> is true, this is the parsed string sent to
800   *     <read>.
801   */
802  data: null,
803  /**
804   * Property: keepData
805   * {Object} Maintain a reference (<data>) to the most recently read data.
806   *     Default is false.
807   */
808  keepData: false,
809  /**
810   * Constructor: ZOO.Format
811   * Instances of this class are not useful.  See one of the subclasses.
812   *
813   * Parameters:
814   * options - {Object} An optional object with properties to set on the
815   *           format
816   *
817   * Valid options:
818   * keepData - {Boolean} If true, upon <read>, the data property will be
819   *     set to the parsed object (e.g. the json or xml object).
820   *
821   * Returns:
822   * An instance of ZOO.Format
823   */
824  initialize: function(options) {
825    ZOO.extend(this, options);
826    this.options = options;
827  },
828  /**
829   * Method: destroy
830   * Clean up.
831   */
832  destroy: function() {
833  },
834  /**
835   * Method: read
836   * Read data from a string, and return an object whose type depends on the
837   * subclass.
838   *
839   * Parameters:
840   * data - {string} Data to read/parse.
841   *
842   * Returns:
843   * Depends on the subclass
844   */
845  read: function(data) {
846  },
847  /**
848   * Method: write
849   * Accept an object, and return a string.
850   *
851   * Parameters:
852   * object - {Object} Object to be serialized
853   *
854   * Returns:
855   * {String} A string representation of the object.
856   */
857  write: function(data) {
858  },
859  CLASS_NAME: 'ZOO.Format'
860});
861/**
862 * Class: ZOO.Format.WKT
863 * Class for reading and writing Well-Known Text. Create a new instance
864 * with the <ZOO.Format.WKT> constructor.
865 *
866 * Inherits from:
867 *  - <ZOO.Format>
868 */
869ZOO.Format.WKT = ZOO.Class(ZOO.Format, {
870  /**
871   * Constructor: ZOO.Format.WKT
872   * Create a new parser for WKT
873   *
874   * Parameters:
875   * options - {Object} An optional object whose properties will be set on
876   *           this instance
877   *
878   * Returns:
879   * {<ZOO.Format.WKT>} A new WKT parser.
880   */
881  initialize: function(options) {
882    this.regExes = {
883      'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
884      'spaces': /\s+/,
885      'parenComma': /\)\s*,\s*\(/,
886      'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/,  // can't use {2} here
887      'trimParens': /^\s*\(?(.*?)\)?\s*$/
888    };
889    ZOO.Format.prototype.initialize.apply(this, [options]);
890  },
891  /**
892   * Method: read
893   * Deserialize a WKT string and return a vector feature or an
894   *     array of vector features.  Supports WKT for POINT,
895   *     MULTIPOINT, LINESTRING, MULTILINESTRING, POLYGON,
896   *     MULTIPOLYGON, and GEOMETRYCOLLECTION.
897   *
898   * Parameters:
899   * wkt - {String} A WKT string
900   *
901   * Returns:
902   * {<ZOO.Feature.Vector>|Array} A feature or array of features for
903   *     GEOMETRYCOLLECTION WKT.
904   */
905  read: function(wkt) {
906    var features, type, str;
907    var matches = this.regExes.typeStr.exec(wkt);
908    if(matches) {
909      type = matches[1].toLowerCase();
910      str = matches[2];
911      if(this.parse[type]) {
912        features = this.parse[type].apply(this, [str]);
913      }
914      if (this.internalProjection && this.externalProjection) {
915        if (features && 
916            features.CLASS_NAME == "ZOO.Feature") {
917          features.geometry.transform(this.externalProjection,
918                                      this.internalProjection);
919        } else if (features &&
920            type != "geometrycollection" &&
921            typeof features == "object") {
922          for (var i=0, len=features.length; i<len; i++) {
923            var component = features[i];
924            component.geometry.transform(this.externalProjection,
925                                         this.internalProjection);
926          }
927        }
928      }
929    }   
930    return features;
931  },
932  /**
933   * Method: write
934   * Serialize a feature or array of features into a WKT string.
935   *
936   * Parameters:
937   * features - {<ZOO.Feature.Vector>|Array} A feature or array of
938   *            features
939   *
940   * Returns:
941   * {String} The WKT string representation of the input geometries
942   */
943  write: function(features) {
944    var collection, geometry, type, data, isCollection;
945    if(features.constructor == Array) {
946      collection = features;
947      isCollection = true;
948    } else {
949      collection = [features];
950      isCollection = false;
951    }
952    var pieces = [];
953    if(isCollection)
954      pieces.push('GEOMETRYCOLLECTION(');
955    for(var i=0, len=collection.length; i<len; ++i) {
956      if(isCollection && i>0)
957        pieces.push(',');
958      geometry = collection[i].geometry;
959      type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
960      if(!this.extract[type])
961        return null;
962      if (this.internalProjection && this.externalProjection) {
963        geometry = geometry.clone();
964        geometry.transform(this.internalProjection, 
965                          this.externalProjection);
966      }                       
967      data = this.extract[type].apply(this, [geometry]);
968      pieces.push(type.toUpperCase() + '(' + data + ')');
969    }
970    if(isCollection)
971      pieces.push(')');
972    return pieces.join('');
973  },
974  /**
975   * Object with properties corresponding to the geometry types.
976   * Property values are functions that do the actual data extraction.
977   */
978  extract: {
979    /**
980     * Return a space delimited string of point coordinates.
981     * @param {<ZOO.Geometry.Point>} point
982     * @returns {String} A string of coordinates representing the point
983     */
984    'point': function(point) {
985      return point.x + ' ' + point.y;
986    },
987    /**
988     * Return a comma delimited string of point coordinates from a multipoint.
989     * @param {<ZOO.Geometry.MultiPoint>} multipoint
990     * @returns {String} A string of point coordinate strings representing
991     *                  the multipoint
992     */
993    'multipoint': function(multipoint) {
994      var array = [];
995      for(var i=0, len=multipoint.components.length; i<len; ++i) {
996        array.push(this.extract.point.apply(this, [multipoint.components[i]]));
997      }
998      return array.join(',');
999    },
1000    /**
1001     * Return a comma delimited string of point coordinates from a line.
1002     * @param {<ZOO.Geometry.LineString>} linestring
1003     * @returns {String} A string of point coordinate strings representing
1004     *                  the linestring
1005     */
1006    'linestring': function(linestring) {
1007      var array = [];
1008      for(var i=0, len=linestring.components.length; i<len; ++i) {
1009        array.push(this.extract.point.apply(this, [linestring.components[i]]));
1010      }
1011      return array.join(',');
1012    },
1013    /**
1014     * Return a comma delimited string of linestring strings from a multilinestring.
1015     * @param {<ZOO.Geometry.MultiLineString>} multilinestring
1016     * @returns {String} A string of of linestring strings representing
1017     *                  the multilinestring
1018     */
1019    'multilinestring': function(multilinestring) {
1020      var array = [];
1021      for(var i=0, len=multilinestring.components.length; i<len; ++i) {
1022        array.push('(' +
1023            this.extract.linestring.apply(this, [multilinestring.components[i]]) +
1024            ')');
1025      }
1026      return array.join(',');
1027    },
1028    /**
1029     * Return a comma delimited string of linear ring arrays from a polygon.
1030     * @param {<ZOO.Geometry.Polygon>} polygon
1031     * @returns {String} An array of linear ring arrays representing the polygon
1032     */
1033    'polygon': function(polygon) {
1034      var array = [];
1035      for(var i=0, len=polygon.components.length; i<len; ++i) {
1036        array.push('(' +
1037            this.extract.linestring.apply(this, [polygon.components[i]]) +
1038            ')');
1039      }
1040      return array.join(',');
1041    },
1042    /**
1043     * Return an array of polygon arrays from a multipolygon.
1044     * @param {<ZOO.Geometry.MultiPolygon>} multipolygon
1045     * @returns {Array} An array of polygon arrays representing
1046     *                  the multipolygon
1047     */
1048    'multipolygon': function(multipolygon) {
1049      var array = [];
1050      for(var i=0, len=multipolygon.components.length; i<len; ++i) {
1051        array.push('(' +
1052            this.extract.polygon.apply(this, [multipolygon.components[i]]) +
1053            ')');
1054      }
1055      return array.join(',');
1056    }
1057  },
1058  /**
1059   * Object with properties corresponding to the geometry types.
1060   * Property values are functions that do the actual parsing.
1061   */
1062  parse: {
1063    /**
1064     * Return point feature given a point WKT fragment.
1065     * @param {String} str A WKT fragment representing the point
1066     * @returns {<ZOO.Feature>} A point feature
1067     */
1068    'point': function(str) {
1069       var coords = ZOO.String.trim(str).split(this.regExes.spaces);
1070            return new ZOO.Feature(
1071                new ZOO.Geometry.Point(coords[0], coords[1])
1072            );
1073    },
1074    /**
1075     * Return a multipoint feature given a multipoint WKT fragment.
1076     * @param {String} A WKT fragment representing the multipoint
1077     * @returns {<ZOO.Feature>} A multipoint feature
1078     */
1079    'multipoint': function(str) {
1080       var points = ZOO.String.trim(str).split(',');
1081       var components = [];
1082       for(var i=0, len=points.length; i<len; ++i) {
1083         components.push(this.parse.point.apply(this, [points[i]]).geometry);
1084       }
1085       return new ZOO.Feature(
1086           new ZOO.Geometry.MultiPoint(components)
1087           );
1088    },
1089    /**
1090     * Return a linestring feature given a linestring WKT fragment.
1091     * @param {String} A WKT fragment representing the linestring
1092     * @returns {<ZOO.Feature>} A linestring feature
1093     */
1094    'linestring': function(str) {
1095      var points = ZOO.String.trim(str).split(',');
1096      var components = [];
1097      for(var i=0, len=points.length; i<len; ++i) {
1098        components.push(this.parse.point.apply(this, [points[i]]).geometry);
1099      }
1100      return new ZOO.Feature(
1101          new ZOO.Geometry.LineString(components)
1102          );
1103    },
1104    /**
1105     * Return a multilinestring feature given a multilinestring WKT fragment.
1106     * @param {String} A WKT fragment representing the multilinestring
1107     * @returns {<ZOO.Feature>} A multilinestring feature
1108     */
1109    'multilinestring': function(str) {
1110      var line;
1111      var lines = ZOO.String.trim(str).split(this.regExes.parenComma);
1112      var components = [];
1113      for(var i=0, len=lines.length; i<len; ++i) {
1114        line = lines[i].replace(this.regExes.trimParens, '$1');
1115        components.push(this.parse.linestring.apply(this, [line]).geometry);
1116      }
1117      return new ZOO.Feature(
1118          new ZOO.Geometry.MultiLineString(components)
1119          );
1120    },
1121    /**
1122     * Return a polygon feature given a polygon WKT fragment.
1123     * @param {String} A WKT fragment representing the polygon
1124     * @returns {<ZOO.Feature>} A polygon feature
1125     */
1126    'polygon': function(str) {
1127       var ring, linestring, linearring;
1128       var rings = ZOO.String.trim(str).split(this.regExes.parenComma);
1129       var components = [];
1130       for(var i=0, len=rings.length; i<len; ++i) {
1131         ring = rings[i].replace(this.regExes.trimParens, '$1');
1132         linestring = this.parse.linestring.apply(this, [ring]).geometry;
1133         linearring = new ZOO.Geometry.LinearRing(linestring.components);
1134         components.push(linearring);
1135       }
1136       return new ZOO.Feature(
1137           new ZOO.Geometry.Polygon(components)
1138           );
1139    },
1140    /**
1141     * Return a multipolygon feature given a multipolygon WKT fragment.
1142     * @param {String} A WKT fragment representing the multipolygon
1143     * @returns {<ZOO.Feature>} A multipolygon feature
1144     * @private
1145     */
1146    'multipolygon': function(str) {
1147      var polygon;
1148      var polygons = ZOO.String.trim(str).split(this.regExes.doubleParenComma);
1149      var components = [];
1150      for(var i=0, len=polygons.length; i<len; ++i) {
1151        polygon = polygons[i].replace(this.regExes.trimParens, '$1');
1152        components.push(this.parse.polygon.apply(this, [polygon]).geometry);
1153      }
1154      return new ZOO.Feature(
1155          new ZOO.Geometry.MultiPolygon(components)
1156          );
1157    },
1158    /**
1159     * Return an array of features given a geometrycollection WKT fragment.
1160     * @param {String} A WKT fragment representing the geometrycollection
1161     * @returns {Array} An array of ZOO.Feature
1162     */
1163    'geometrycollection': function(str) {
1164      // separate components of the collection with |
1165      str = str.replace(/,\s*([A-Za-z])/g, '|$1');
1166      var wktArray = ZOO.String.trim(str).split('|');
1167      var components = [];
1168      for(var i=0, len=wktArray.length; i<len; ++i) {
1169        components.push(ZOO.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
1170      }
1171      return components;
1172    }
1173  },
1174  CLASS_NAME: 'ZOO.Format.WKT'
1175});
1176/**
1177 * Class: ZOO.Format.JSON
1178 * A parser to read/write JSON safely. Create a new instance with the
1179 *     <ZOO.Format.JSON> constructor.
1180 *
1181 * Inherits from:
1182 *  - <OpenLayers.Format>
1183 */
1184ZOO.Format.JSON = ZOO.Class(ZOO.Format, {
1185  indent: "    ",
1186  space: " ",
1187  newline: "\n",
1188  level: 0,
1189  pretty: false,
1190  initialize: function(options) {
1191    ZOO.Format.prototype.initialize.apply(this, [options]);
1192  },
1193  read: function(json, filter) {
1194    try {
1195      if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
1196                          replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
1197                          replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
1198        var object = eval('(' + json + ')');
1199        if(typeof filter === 'function') {
1200          function walk(k, v) {
1201            if(v && typeof v === 'object') {
1202              for(var i in v) {
1203                if(v.hasOwnProperty(i)) {
1204                  v[i] = walk(i, v[i]);
1205                }
1206              }
1207            }
1208            return filter(k, v);
1209          }
1210          object = walk('', object);
1211        }
1212        if(this.keepData) {
1213          this.data = object;
1214        }
1215        return object;
1216      }
1217    } catch(e) {
1218      // Fall through if the regexp test fails.
1219    }
1220    return null;
1221  },
1222  write: function(value, pretty) {
1223    this.pretty = !!pretty;
1224    var json = null;
1225    var type = typeof value;
1226    if(this.serialize[type]) {
1227      try {
1228        json = this.serialize[type].apply(this, [value]);
1229      } catch(err) {
1230        //OpenLayers.Console.error("Trouble serializing: " + err);
1231      }
1232    }
1233    return json;
1234  },
1235  writeIndent: function() {
1236    var pieces = [];
1237    if(this.pretty) {
1238      for(var i=0; i<this.level; ++i) {
1239        pieces.push(this.indent);
1240      }
1241    }
1242    return pieces.join('');
1243  },
1244  writeNewline: function() {
1245    return (this.pretty) ? this.newline : '';
1246  },
1247  writeSpace: function() {
1248    return (this.pretty) ? this.space : '';
1249  },
1250  serialize: {
1251    'object': function(object) {
1252       // three special objects that we want to treat differently
1253       if(object == null)
1254         return "null";
1255       if(object.constructor == Date)
1256         return this.serialize.date.apply(this, [object]);
1257       if(object.constructor == Array)
1258         return this.serialize.array.apply(this, [object]);
1259       var pieces = ['{'];
1260       this.level += 1;
1261       var key, keyJSON, valueJSON;
1262
1263       var addComma = false;
1264       for(key in object) {
1265         if(object.hasOwnProperty(key)) {
1266           // recursive calls need to allow for sub-classing
1267           keyJSON = ZOO.Format.JSON.prototype.write.apply(this,
1268                                                           [key, this.pretty]);
1269           valueJSON = ZOO.Format.JSON.prototype.write.apply(this,
1270                                                             [object[key], this.pretty]);
1271           if(keyJSON != null && valueJSON != null) {
1272             if(addComma)
1273               pieces.push(',');
1274             pieces.push(this.writeNewline(), this.writeIndent(),
1275                         keyJSON, ':', this.writeSpace(), valueJSON);
1276             addComma = true;
1277           }
1278         }
1279       }
1280       this.level -= 1;
1281       pieces.push(this.writeNewline(), this.writeIndent(), '}');
1282       return pieces.join('');
1283    },
1284    'array': function(array) {
1285      var json;
1286      var pieces = ['['];
1287      this.level += 1;
1288      for(var i=0, len=array.length; i<len; ++i) {
1289        // recursive calls need to allow for sub-classing
1290        json = ZOO.Format.JSON.prototype.write.apply(this,
1291                                                     [array[i], this.pretty]);
1292        if(json != null) {
1293          if(i > 0)
1294            pieces.push(',');
1295          pieces.push(this.writeNewline(), this.writeIndent(), json);
1296        }
1297      }
1298      this.level -= 1;   
1299      pieces.push(this.writeNewline(), this.writeIndent(), ']');
1300      return pieces.join('');
1301    },
1302    'string': function(string) {
1303      var m = {
1304                '\b': '\\b',
1305                '\t': '\\t',
1306                '\n': '\\n',
1307                '\f': '\\f',
1308                '\r': '\\r',
1309                '"' : '\\"',
1310                '\\': '\\\\'
1311      };
1312      if(/["\\\x00-\x1f]/.test(string)) {
1313        return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
1314            var c = m[b];
1315            if(c)
1316              return c;
1317            c = b.charCodeAt();
1318            return '\\u00' +
1319            Math.floor(c / 16).toString(16) +
1320            (c % 16).toString(16);
1321        }) + '"';
1322      }
1323      return '"' + string + '"';
1324    },
1325    'number': function(number) {
1326      return isFinite(number) ? String(number) : "null";
1327    },
1328    'boolean': function(bool) {
1329      return String(bool);
1330    },
1331    'date': function(date) {   
1332      function format(number) {
1333        // Format integers to have at least two digits.
1334        return (number < 10) ? '0' + number : number;
1335      }
1336      return '"' + date.getFullYear() + '-' +
1337        format(date.getMonth() + 1) + '-' +
1338        format(date.getDate()) + 'T' +
1339        format(date.getHours()) + ':' +
1340        format(date.getMinutes()) + ':' +
1341        format(date.getSeconds()) + '"';
1342    }
1343  },
1344  CLASS_NAME: 'ZOO.Format.JSON'
1345});
1346ZOO.Format.GeoJSON = ZOO.Class(ZOO.Format.JSON, {
1347  initialize: function(options) {
1348    ZOO.Format.JSON.prototype.initialize.apply(this, [options]);
1349  },
1350  read: function(json, type, filter) {
1351    type = (type) ? type : "FeatureCollection";
1352    var results = null;
1353    var obj = null;
1354    if (typeof json == "string")
1355      obj = ZOO.Format.JSON.prototype.read.apply(this,[json, filter]);
1356    else
1357      obj = json;
1358    if(!obj) {
1359      //OpenLayers.Console.error("Bad JSON: " + json);
1360    } else if(typeof(obj.type) != "string") {
1361      //OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
1362    } else if(this.isValidType(obj, type)) {
1363      switch(type) {
1364        case "Geometry":
1365          try {
1366            results = this.parseGeometry(obj);
1367          } catch(err) {
1368            //OpenLayers.Console.error(err);
1369          }
1370          break;
1371        case "Feature":
1372          try {
1373            results = this.parseFeature(obj);
1374            results.type = "Feature";
1375          } catch(err) {
1376            //OpenLayers.Console.error(err);
1377          }
1378          break;
1379        case "FeatureCollection":
1380          // for type FeatureCollection, we allow input to be any type
1381          results = [];
1382          switch(obj.type) {
1383            case "Feature":
1384              try {
1385                results.push(this.parseFeature(obj));
1386              } catch(err) {
1387                results = null;
1388                //OpenLayers.Console.error(err);
1389              }
1390              break;
1391            case "FeatureCollection":
1392              for(var i=0, len=obj.features.length; i<len; ++i) {
1393                try {
1394                  results.push(this.parseFeature(obj.features[i]));
1395                } catch(err) {
1396                  results = null;
1397                  //OpenLayers.Console.error(err);
1398                }
1399              }
1400              break;
1401            default:
1402              try {
1403                var geom = this.parseGeometry(obj);
1404                results.push(new ZOO.Feature(geom));
1405              } catch(err) {
1406                results = null;
1407                //OpenLayers.Console.error(err);
1408              }
1409          }
1410          break;
1411      }
1412    }
1413    return results;
1414  },
1415  isValidType: function(obj, type) {
1416    var valid = false;
1417    switch(type) {
1418      case "Geometry":
1419        if(ZOO.indexOf(
1420              ["Point", "MultiPoint", "LineString", "MultiLineString",
1421              "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
1422              obj.type) == -1) {
1423          // unsupported geometry type
1424          //OpenLayers.Console.error("Unsupported geometry type: " +obj.type);
1425        } else {
1426          valid = true;
1427        }
1428        break;
1429      case "FeatureCollection":
1430        // allow for any type to be converted to a feature collection
1431        valid = true;
1432        break;
1433      default:
1434        // for Feature types must match
1435        if(obj.type == type) {
1436          valid = true;
1437        } else {
1438          //OpenLayers.Console.error("Cannot convert types from " +obj.type + " to " + type);
1439        }
1440    }
1441    return valid;
1442  },
1443  parseFeature: function(obj) {
1444    var feature, geometry, attributes, bbox;
1445    attributes = (obj.properties) ? obj.properties : {};
1446    bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
1447    try {
1448      geometry = this.parseGeometry(obj.geometry);
1449    } catch(err) {
1450      // deal with bad geometries
1451      throw err;
1452    }
1453    feature = new ZOO.Feature(geometry, attributes);
1454    if(bbox)
1455      feature.bounds = ZOO.Bounds.fromArray(bbox);
1456    if(obj.id)
1457      feature.fid = obj.id;
1458    return feature;
1459  },
1460  parseGeometry: function(obj) {
1461    if (obj == null)
1462      return null;
1463    var geometry, collection = false;
1464    if(obj.type == "GeometryCollection") {
1465      if(!(obj.geometries instanceof Array)) {
1466        throw "GeometryCollection must have geometries array: " + obj;
1467      }
1468      var numGeom = obj.geometries.length;
1469      var components = new Array(numGeom);
1470      for(var i=0; i<numGeom; ++i) {
1471        components[i] = this.parseGeometry.apply(
1472            this, [obj.geometries[i]]
1473            );
1474      }
1475      geometry = new ZOO.Geometry.Collection(components);
1476      collection = true;
1477    } else {
1478      if(!(obj.coordinates instanceof Array)) {
1479        throw "Geometry must have coordinates array: " + obj;
1480      }
1481      if(!this.parseCoords[obj.type.toLowerCase()]) {
1482        throw "Unsupported geometry type: " + obj.type;
1483      }
1484      try {
1485        geometry = this.parseCoords[obj.type.toLowerCase()].apply(
1486            this, [obj.coordinates]
1487            );
1488      } catch(err) {
1489        // deal with bad coordinates
1490        throw err;
1491      }
1492    }
1493        // We don't reproject collections because the children are reprojected
1494        // for us when they are created.
1495    if (this.internalProjection && this.externalProjection && !collection) {
1496      geometry.transform(this.externalProjection, 
1497          this.internalProjection); 
1498    }                       
1499    return geometry;
1500  },
1501  parseCoords: {
1502    "point": function(array) {
1503      if(array.length != 2) {
1504        throw "Only 2D points are supported: " + array;
1505      }
1506      return new ZOO.Geometry.Point(array[0], array[1]);
1507    },
1508    "multipoint": function(array) {
1509      var points = [];
1510      var p = null;
1511      for(var i=0, len=array.length; i<len; ++i) {
1512        try {
1513          p = this.parseCoords["point"].apply(this, [array[i]]);
1514        } catch(err) {
1515          throw err;
1516        }
1517        points.push(p);
1518      }
1519      return new ZOO.Geometry.MultiPoint(points);
1520    },
1521    "linestring": function(array) {
1522      var points = [];
1523      var p = null;
1524      for(var i=0, len=array.length; i<len; ++i) {
1525        try {
1526          p = this.parseCoords["point"].apply(this, [array[i]]);
1527        } catch(err) {
1528          throw err;
1529        }
1530        points.push(p);
1531      }
1532      return new ZOO.Geometry.LineString(points);
1533    },
1534    "multilinestring": function(array) {
1535      var lines = [];
1536      var l = null;
1537      for(var i=0, len=array.length; i<len; ++i) {
1538        try {
1539          l = this.parseCoords["linestring"].apply(this, [array[i]]);
1540        } catch(err) {
1541          throw err;
1542        }
1543        lines.push(l);
1544      }
1545      return new ZOO.Geometry.MultiLineString(lines);
1546    },
1547    "polygon": function(array) {
1548      var rings = [];
1549      var r, l;
1550      for(var i=0, len=array.length; i<len; ++i) {
1551        try {
1552          l = this.parseCoords["linestring"].apply(this, [array[i]]);
1553        } catch(err) {
1554          throw err;
1555        }
1556        r = new ZOO.Geometry.LinearRing(l.components);
1557        rings.push(r);
1558      }
1559      return new ZOO.Geometry.Polygon(rings);
1560    },
1561    "multipolygon": function(array) {
1562      var polys = [];
1563      var p = null;
1564      for(var i=0, len=array.length; i<len; ++i) {
1565        try {
1566          p = this.parseCoords["polygon"].apply(this, [array[i]]);
1567        } catch(err) {
1568          throw err;
1569        }
1570        polys.push(p);
1571      }
1572      return new ZOO.Geometry.MultiPolygon(polys);
1573    },
1574    "box": function(array) {
1575      if(array.length != 2) {
1576        throw "GeoJSON box coordinates must have 2 elements";
1577      }
1578      return new ZOO.Geometry.Polygon([
1579          new ZOO.Geometry.LinearRing([
1580            new ZOO.Geometry.Point(array[0][0], array[0][1]),
1581            new ZOO.Geometry.Point(array[1][0], array[0][1]),
1582            new ZOO.Geometry.Point(array[1][0], array[1][1]),
1583            new ZOO.Geometry.Point(array[0][0], array[1][1]),
1584            new Z0O.Geometry.Point(array[0][0], array[0][1])
1585          ])
1586      ]);
1587    }
1588  },
1589  write: function(obj, pretty) {
1590    var geojson = {
1591      "type": null
1592    };
1593    if(obj instanceof Array) {
1594      geojson.type = "FeatureCollection";
1595      var numFeatures = obj.length;
1596      geojson.features = new Array(numFeatures);
1597      for(var i=0; i<numFeatures; ++i) {
1598        var element = obj[i];
1599        if(!element instanceof ZOO.Feature) {
1600          var msg = "FeatureCollection only supports collections " +
1601            "of features: " + element;
1602          throw msg;
1603        }
1604        geojson.features[i] = this.extract.feature.apply(this, [element]);
1605      }
1606    } else if (obj.CLASS_NAME.indexOf("ZOO.Geometry") == 0) {
1607      geojson = this.extract.geometry.apply(this, [obj]);
1608    } else if (obj instanceof ZOO.Feature) {
1609      geojson = this.extract.feature.apply(this, [obj]);
1610      /*
1611      if(obj.layer && obj.layer.projection) {
1612        geojson.crs = this.createCRSObject(obj);
1613      }
1614      */
1615    }
1616    return ZOO.Format.JSON.prototype.write.apply(this,
1617                                                 [geojson, pretty]);
1618  },
1619  createCRSObject: function(object) {
1620    //var proj = object.layer.projection.toString();
1621    var proj = object.projection.toString();
1622    var crs = {};
1623    if (proj.match(/epsg:/i)) {
1624      var code = parseInt(proj.substring(proj.indexOf(":") + 1));
1625      if (code == 4326) {
1626        crs = {
1627          "type": "OGC",
1628          "properties": {
1629            "urn": "urn:ogc:def:crs:OGC:1.3:CRS84"
1630          }
1631        };
1632      } else {   
1633        crs = {
1634          "type": "EPSG",
1635          "properties": {
1636            "code": code 
1637          }
1638        };
1639      }   
1640    }
1641    return crs;
1642  },
1643  extract: {
1644    'feature': function(feature) {
1645      var geom = this.extract.geometry.apply(this, [feature.geometry]);
1646      return {
1647        "type": "Feature",
1648        "id": feature.fid == null ? feature.id : feature.fid,
1649        "properties": feature.attributes,
1650        "geometry": geom
1651      };
1652    },
1653    'geometry': function(geometry) {
1654      if (geometry == null)
1655        return null;
1656      if (this.internalProjection && this.externalProjection) {
1657        geometry = geometry.clone();
1658        geometry.transform(this.internalProjection, 
1659            this.externalProjection);
1660      }                       
1661      var geometryType = geometry.CLASS_NAME.split('.')[2];
1662      var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
1663      var json;
1664      if(geometryType == "Collection")
1665        json = {
1666          "type": "GeometryCollection",
1667          "geometries": data
1668        };
1669      else
1670        json = {
1671          "type": geometryType,
1672          "coordinates": data
1673        };
1674      return json;
1675    },
1676    'point': function(point) {
1677      return [point.x, point.y];
1678    },
1679    'multipoint': function(multipoint) {
1680      var array = [];
1681      for(var i=0, len=multipoint.components.length; i<len; ++i) {
1682        array.push(this.extract.point.apply(this, [multipoint.components[i]]));
1683      }
1684      return array;
1685    },
1686    'linestring': function(linestring) {
1687      var array = [];
1688      for(var i=0, len=linestring.components.length; i<len; ++i) {
1689        array.push(this.extract.point.apply(this, [linestring.components[i]]));
1690      }
1691      return array;
1692    },
1693    'multilinestring': function(multilinestring) {
1694      var array = [];
1695      for(var i=0, len=multilinestring.components.length; i<len; ++i) {
1696        array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
1697      }
1698      return array;
1699    },
1700    'polygon': function(polygon) {
1701      var array = [];
1702      for(var i=0, len=polygon.components.length; i<len; ++i) {
1703        array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
1704      }
1705      return array;
1706    },
1707    'multipolygon': function(multipolygon) {
1708      var array = [];
1709      for(var i=0, len=multipolygon.components.length; i<len; ++i) {
1710        array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
1711      }
1712      return array;
1713    },
1714    'collection': function(collection) {
1715      var len = collection.components.length;
1716      var array = new Array(len);
1717      for(var i=0; i<len; ++i) {
1718        array[i] = this.extract.geometry.apply(
1719            this, [collection.components[i]]
1720            );
1721      }
1722      return array;
1723    }
1724  },
1725  CLASS_NAME: 'ZOO.Format.GeoJSON'
1726});
1727ZOO.Format.KML = ZOO.Class(ZOO.Format, {
1728  kmlns: "http://www.opengis.net/kml/2.2",
1729  foldersName: "ZOO export",
1730  foldersDesc: "Created on " + new Date(),
1731  placemarksDesc: "No description available",
1732  extractAttributes: true,
1733  initialize: function(options) {
1734    // compile regular expressions once instead of every time they are used
1735    this.regExes = {
1736           trimSpace: (/^\s*|\s*$/g),
1737           removeSpace: (/\s*/g),
1738           splitSpace: (/\s+/),
1739           trimComma: (/\s*,\s*/g),
1740           kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
1741           kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
1742           straightBracket: (/\$\[(.*?)\]/g)
1743    };
1744    ZOO.Format.prototype.initialize.apply(this, [options]);
1745  },
1746  read: function(data) {
1747    this.features = [];
1748    data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
1749    data = new XML(data);
1750    var placemarks = data..*::Placemark;
1751    this.parseFeatures(placemarks);
1752    return this.features;
1753  },
1754  parseFeatures: function(nodes) {
1755    var features = new Array(nodes.length());
1756    for(var i=0, len=nodes.length(); i<len; i++) {
1757      var featureNode = nodes[i];
1758      var feature = this.parseFeature.apply(this,[featureNode]) ;
1759      features[i] = feature;
1760    }
1761    this.features = this.features.concat(features);
1762  },
1763  parseFeature: function(node) {
1764    var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
1765    var type, nodeList, geometry, parser;
1766    for(var i=0, len=order.length; i<len; ++i) {
1767      type = order[i];
1768      nodeList = node.descendants(QName(null,type));
1769      if (nodeList.length()> 0) {
1770        var parser = this.parseGeometry[type.toLowerCase()];
1771        if(parser) {
1772          geometry = parser.apply(this, [nodeList[0]]);
1773          if (this.internalProjection && this.externalProjection) {
1774            geometry.transform(this.externalProjection, 
1775                               this.internalProjection); 
1776          }                       
1777        }
1778        break;
1779      }
1780    }
1781    var attributes;
1782    if(this.extractAttributes) {
1783      attributes = this.parseAttributes(node);
1784    }
1785    var feature = new ZOO.Feature(geometry, attributes);
1786    var fid = node.@id || node.@name;
1787    if(fid != null)
1788      feature.fid = fid;
1789    return feature;
1790  },
1791  parseGeometry: {
1792    'point': function(node) {
1793      var coordString = node.*::coordinates.toString();
1794      coordString = coordString.replace(this.regExes.removeSpace, "");
1795      coords = coordString.split(",");
1796      var point = null;
1797      if(coords.length > 1) {
1798        // preserve third dimension
1799        if(coords.length == 2) {
1800          coords[2] = null;
1801        }
1802        point = new ZOO.Geometry.Point(coords[0], coords[1], coords[2]);
1803      }
1804      return point;
1805    },
1806    'linestring': function(node, ring) {
1807      var line = null;
1808      var coordString = node.*::coordinates.toString();
1809      coordString = coordString.replace(this.regExes.trimSpace,
1810          "");
1811      coordString = coordString.replace(this.regExes.trimComma,
1812          ",");
1813      var pointList = coordString.split(this.regExes.splitSpace);
1814      var numPoints = pointList.length;
1815      var points = new Array(numPoints);
1816      var coords, numCoords;
1817      for(var i=0; i<numPoints; ++i) {
1818        coords = pointList[i].split(",");
1819        numCoords = coords.length;
1820        if(numCoords > 1) {
1821          if(coords.length == 2) {
1822            coords[2] = null;
1823          }
1824          points[i] = new ZOO.Geometry.Point(coords[0],
1825                                             coords[1],
1826                                             coords[2]);
1827        }
1828      }
1829      if(numPoints) {
1830        if(ring) {
1831          line = new ZOO.Geometry.LinearRing(points);
1832        } else {
1833          line = new ZOO.Geometry.LineString(points);
1834        }
1835      } else {
1836        throw "Bad LineString coordinates: " + coordString;
1837      }
1838      return line;
1839    },
1840    'polygon': function(node) {
1841      var nodeList = node..*::LinearRing;
1842      var numRings = nodeList.length();
1843      var components = new Array(numRings);
1844      if(numRings > 0) {
1845        // this assumes exterior ring first, inner rings after
1846        var ring;
1847        for(var i=0, len=nodeList.length(); i<len; ++i) {
1848          ring = this.parseGeometry.linestring.apply(this,
1849                                                     [nodeList[i], true]);
1850          if(ring) {
1851            components[i] = ring;
1852          } else {
1853            throw "Bad LinearRing geometry: " + i;
1854          }
1855        }
1856      }
1857      return new ZOO.Geometry.Polygon(components);
1858    },
1859    multigeometry: function(node) {
1860      var child, parser;
1861      var parts = [];
1862      var children = node.*::*;
1863      for(var i=0, len=children.length(); i<len; ++i ) {
1864        child = children[i];
1865        var type = child.localName();
1866        var parser = this.parseGeometry[type.toLowerCase()];
1867        if(parser) {
1868          parts.push(parser.apply(this, [child]));
1869        }
1870      }
1871      return new ZOO.Geometry.Collection(parts);
1872    }
1873  },
1874  parseAttributes: function(node) {
1875    var attributes = {};
1876    var edNodes = node.*::ExtendedData;
1877    if (edNodes.length() > 0) {
1878      attributes = this.parseExtendedData(edNodes[0])
1879    }
1880    var child, grandchildren;
1881    var children = node.*::*;
1882    for(var i=0, len=children.length(); i<len; ++i) {
1883      child = children[i];
1884      grandchildren = child..*::*;
1885      if(grandchildren.length() == 1) {
1886        var name = child.localName();
1887        var value = child.toString();
1888        if (value) {
1889          value = value.replace(this.regExes.trimSpace, "");
1890          attributes[name] = value;
1891        }
1892      }
1893    }
1894    return attributes;
1895  },
1896  parseExtendedData: function(node) {
1897    var attributes = {};
1898    var dataNodes = node.*::Data;
1899    for (var i = 0, len = dataNodes.length(); i < len; i++) {
1900      var data = dataNodes[i];
1901      var key = data.@name;
1902      var ed = {};
1903      var valueNode = data.*::value;
1904      if (valueNode.length() > 0)
1905        ed['value'] = valueNode[0].toString();
1906      var nameNode = data.*::displayName;
1907      if (nameNode.length() > 0)
1908        ed['displayName'] = valueNode[0].toString();
1909      attributes[key] = ed;
1910    }
1911    return attributes;
1912  },
1913  write: function(features) {
1914    if(!(features instanceof Array))
1915      features = [features];
1916    var kml = new XML('<kml xmlns="'+this.kmlns+'"></kml>');
1917    var folder = kml.Document.Folder;
1918    folder.name = this.foldersName;
1919    folder.description = this.foldersDesc;
1920    for(var i=0, len=features.length; i<len; ++i) {
1921      //folder.appendChild(this.createPlacemarkXML(features[i]));
1922      folder.Placemark[i] = this.createPlacemark(features[i]);
1923    }
1924    return kml.toXMLString();
1925  },
1926  createPlacemark: function(feature) {
1927    var placemark = new XML('<Placemark xmlns="'+this.kmlns+'"></Placemark>');
1928    placemark.name = (feature.attributes.name) ?
1929                    feature.attributes.name : feature.id;
1930    placemark.description = (feature.attributes.description) ?
1931                             feature.attributes.description : this.placemarksDesc;
1932    if(feature.fid != null)
1933      placemark.@id = feature.fid;
1934    placemark.*[2] = this.buildGeometryNode(feature.geometry);
1935    return placemark;
1936  },
1937  buildGeometryNode: function(geometry) {
1938    if (this.internalProjection && this.externalProjection) {
1939      geometry = geometry.clone();
1940      geometry.transform(this.internalProjection, 
1941                         this.externalProjection);
1942    }
1943    var className = geometry.CLASS_NAME;
1944    var type = className.substring(className.lastIndexOf(".") + 1);
1945    var builder = this.buildGeometry[type.toLowerCase()];
1946    var node = null;
1947    if(builder) {
1948      node = builder.apply(this, [geometry]);
1949    }
1950    return node;
1951  },
1952  buildGeometry: {
1953    'point': function(geometry) {
1954      var kml = new XML('<Point xmlns="'+this.kmlns+'"></Point>');
1955      kml.coordinates = this.buildCoordinatesNode(geometry);
1956      return kml;
1957    },
1958    'multipoint': function(geometry) {
1959      return this.buildGeometry.collection.apply(this, [geometry]);
1960    },
1961    'linestring': function(geometry) {
1962      var kml = new XML('<LineString xmlns="'+this.kmlns+'"></LineString>');
1963      kml.coordinates = this.buildCoordinatesNode(geometry);
1964      return kml;
1965    },
1966    'multilinestring': function(geometry) {
1967      return this.buildGeometry.collection.apply(this, [geometry]);
1968    },
1969    'linearring': function(geometry) {
1970      var kml = new XML('<LinearRing xmlns="'+this.kmlns+'"></LinearRing>');
1971      kml.coordinates = this.buildCoordinatesNode(geometry);
1972      return kml;
1973    },
1974    'polygon': function(geometry) {
1975      var kml = new XML('<Polygon xmlns="'+this.kmlns+'"></Polygon>');
1976      var rings = geometry.components;
1977      var ringMember, ringGeom, type;
1978      for(var i=0, len=rings.length; i<len; ++i) {
1979        type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
1980        ringMember = new XML('<'+type+' xmlns="'+this.kmlns+'"></'+type+'>');
1981        ringMember.LinearRing = this.buildGeometry.linearring.apply(this,[rings[i]]);
1982        kml.*[i] = ringMember;
1983      }
1984      return kml;
1985    },
1986    'multipolygon': function(geometry) {
1987      return this.buildGeometry.collection.apply(this, [geometry]);
1988    },
1989    'collection': function(geometry) {
1990      var kml = new XML('<MultiGeometry xmlns="'+this.kmlns+'"></MultiGeometry>');
1991      var child;
1992      for(var i=0, len=geometry.components.length; i<len; ++i) {
1993        kml.*[i] = this.buildGeometryNode.apply(this,[geometry.components[i]]);
1994      }
1995      return kml;
1996    }
1997  },
1998  buildCoordinatesNode: function(geometry) {
1999    var cooridnates = new XML('<coordinates xmlns="'+this.kmlns+'"></coordinates>');
2000    var points = geometry.components;
2001    if(points) {
2002      // LineString or LinearRing
2003      var point;
2004      var numPoints = points.length;
2005      var parts = new Array(numPoints);
2006      for(var i=0; i<numPoints; ++i) {
2007        point = points[i];
2008        parts[i] = point.x + "," + point.y;
2009      }
2010      coordinates = parts.join(" ");
2011    } else {
2012      // Point
2013      coordinates = geometry.x + "," + geometry.y;
2014    }
2015    return coordinates;
2016  },
2017  CLASS_NAME: 'ZOO.Format.KML'
2018});
2019ZOO.Format.GML = ZOO.Class(ZOO.Format, {
2020  schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
2021  namespaces: {
2022    ogr: "http://ogr.maptools.org/",
2023    gml: "http://www.opengis.net/gml",
2024    xlink: "http://www.w3.org/1999/xlink",
2025    xsi: "http://www.w3.org/2001/XMLSchema-instance",
2026    wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
2027  },
2028  defaultPrefix: 'ogr',
2029  collectionName: "FeatureCollection",
2030  featureName: "sql_statement",
2031  geometryName: "geometryProperty",
2032  xy: true,
2033  initialize: function(options) {
2034    // compile regular expressions once instead of every time they are used
2035    this.regExes = {
2036      trimSpace: (/^\s*|\s*$/g),
2037      removeSpace: (/\s*/g),
2038      splitSpace: (/\s+/),
2039      trimComma: (/\s*,\s*/g)
2040    };
2041    ZOO.Format.prototype.initialize.apply(this, [options]);
2042  },
2043  read: function(data) {
2044    this.features = [];
2045    data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
2046    data = new XML(data);
2047
2048    var gmlns = Namespace(this.namespaces['gml']);
2049    var featureNodes = data..gmlns::featureMember;
2050    var features = [];
2051    for(var i=0,len=featureNodes.length(); i<len; i++) {
2052      var feature = this.parseFeature(featureNodes[i]);
2053      if(feature) {
2054        features.push(feature);
2055      }
2056    }
2057    return features;
2058  },
2059  parseFeature: function(node) {
2060    // only accept one geometry per feature - look for highest "order"
2061    var gmlns = Namespace(this.namespaces['gml']);
2062    var order = ["MultiPolygon", "Polygon",
2063                 "MultiLineString", "LineString",
2064                 "MultiPoint", "Point", "Envelope", "Box"];
2065    var type, nodeList, geometry, parser;
2066    for(var i=0; i<order.length; ++i) {
2067      type = order[i];
2068      nodeList = node.descendants(QName(gmlns,type));
2069      if (nodeList.length() > 0) {
2070        var parser = this.parseGeometry[type.toLowerCase()];
2071        if(parser) {
2072          geometry = parser.apply(this, [nodeList[0]]);
2073          if (this.internalProjection && this.externalProjection) {
2074            geometry.transform(this.externalProjection, 
2075                               this.internalProjection); 
2076          }                       
2077        }
2078        break;
2079      }
2080    }
2081    var attributes;
2082    if(this.extractAttributes) {
2083      //attributes = this.parseAttributes(node);
2084    }
2085    var feature = new ZOO.Feature(geometry, attributes);
2086    return feature;
2087  },
2088  parseGeometry: {
2089    'point': function(node) {
2090      /**
2091       * Three coordinate variations to consider:
2092       * 1) <gml:pos>x y z</gml:pos>
2093       * 2) <gml:coordinates>x, y, z</gml:coordinates>
2094       * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
2095       */
2096      var nodeList, coordString;
2097      var coords = [];
2098      // look for <gml:pos>
2099      var nodeList = node..*::pos;
2100      if(nodeList.length() > 0) {
2101        coordString = nodeList[0].toString();
2102        coordString = coordString.replace(this.regExes.trimSpace, "");
2103        coords = coordString.split(this.regExes.splitSpace);
2104      }
2105      // look for <gml:coordinates>
2106      if(coords.length == 0) {
2107        nodeList = node..*::coordinates;
2108        if(nodeList.length() > 0) {
2109          coordString = nodeList[0].toString();
2110          coordString = coordString.replace(this.regExes.removeSpace,"");
2111          coords = coordString.split(",");
2112        }
2113      }
2114      // look for <gml:coord>
2115      if(coords.length == 0) {
2116        nodeList = node..*::coord;
2117        if(nodeList.length() > 0) {
2118          var xList = nodeList[0].*::X;
2119          var yList = nodeList[0].*::Y;
2120          if(xList.length() > 0 && yList.length() > 0)
2121            coords = [xList[0].toString(),
2122                      yList[0].toString()];
2123        }
2124      }
2125      // preserve third dimension
2126      if(coords.length == 2)
2127        coords[2] = null;
2128      if (this.xy)
2129        return new ZOO.Geometry.Point(coords[0],coords[1],coords[2]);
2130      else
2131        return new ZOO.Geometry.Point(coords[1],coords[0],coords[2]);
2132    },
2133    'multipoint': function(node) {
2134      var nodeList = node..*::Point;
2135      var components = [];
2136      if(nodeList.length() > 0) {
2137        var point;
2138        for(var i=0, len=nodeList.length(); i<len; ++i) {
2139          point = this.parseGeometry.point.apply(this, [nodeList[i]]);
2140          if(point)
2141            components.push(point);
2142        }
2143      }
2144      return new ZOO.Geometry.MultiPoint(components);
2145    },
2146    'linestring': function(node, ring) {
2147      /**
2148       * Two coordinate variations to consider:
2149       * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
2150       * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
2151       */
2152      var nodeList, coordString;
2153      var coords = [];
2154      var points = [];
2155      // look for <gml:posList>
2156      nodeList = node..*::posList;
2157      if(nodeList.length() > 0) {
2158        coordString = nodeList[0].toString();
2159        coordString = coordString.replace(this.regExes.trimSpace, "");
2160        coords = coordString.split(this.regExes.splitSpace);
2161        var dim = parseInt(nodeList[0].@dimension);
2162        var j, x, y, z;
2163        for(var i=0; i<coords.length/dim; ++i) {
2164          j = i * dim;
2165          x = coords[j];
2166          y = coords[j+1];
2167          z = (dim == 2) ? null : coords[j+2];
2168          if (this.xy)
2169            points.push(new ZOO.Geometry.Point(x, y, z));
2170          else
2171            points.push(new Z0O.Geometry.Point(y, x, z));
2172        }
2173      }
2174      // look for <gml:coordinates>
2175      if(coords.length == 0) {
2176        nodeList = node..*::coordinates;
2177        if(nodeList.length() > 0) {
2178          coordString = nodeList[0].toString();
2179          coordString = coordString.replace(this.regExes.trimSpace,"");
2180          coordString = coordString.replace(this.regExes.trimComma,",");
2181          var pointList = coordString.split(this.regExes.splitSpace);
2182          for(var i=0; i<pointList.length; ++i) {
2183            coords = pointList[i].split(",");
2184            if(coords.length == 2)
2185              coords[2] = null;
2186            if (this.xy)
2187              points.push(new ZOO.Geometry.Point(coords[0],coords[1],coords[2]));
2188            else
2189              points.push(new ZOO.Geometry.Point(coords[1],coords[0],coords[2]));
2190          }
2191        }
2192      }
2193      var line = null;
2194      if(points.length != 0) {
2195        if(ring)
2196          line = new ZOO.Geometry.LinearRing(points);
2197        else
2198          line = new ZOO.Geometry.LineString(points);
2199      }
2200      return line;
2201    },
2202    'multilinestring': function(node) {
2203      var nodeList = node..*::LineString;
2204      var components = [];
2205      if(nodeList.length() > 0) {
2206        var line;
2207        for(var i=0, len=nodeList.length(); i<len; ++i) {
2208          line = this.parseGeometry.linestring.apply(this, [nodeList[i]]);
2209          if(point)
2210            components.push(point);
2211        }
2212      }
2213      return new ZOO.Geometry.MultiLineString(components);
2214    },
2215    'polygon': function(node) {
2216      nodeList = node..*::LinearRing;
2217      var components = [];
2218      if(nodeList.length() > 0) {
2219        // this assumes exterior ring first, inner rings after
2220        var ring;
2221        for(var i=0, len = nodeList.length(); i<len; ++i) {
2222          ring = this.parseGeometry.linestring.apply(this,[nodeList[i], true]);
2223          if(ring)
2224            components.push(ring);
2225        }
2226      }
2227      return new ZOO.Geometry.Polygon(components);
2228    },
2229    'multipolygon': function(node) {
2230      var nodeList = node..*::Polygon;
2231      var components = [];
2232      if(nodeList.length() > 0) {
2233        var polygon;
2234        for(var i=0, len=nodeList.length(); i<len; ++i) {
2235          polygon = this.parseGeometry.polygon.apply(this, [nodeList[i]]);
2236          if(polygon)
2237            components.push(polygon);
2238        }
2239      }
2240      return new ZOO.Geometry.MultiPolygon(components);
2241    },
2242    'envelope': function(node) {
2243      var components = [];
2244      var coordString;
2245      var envelope;
2246      var lpoint = node..*::lowerCorner;
2247      if (lpoint.length() > 0) {
2248        var coords = [];
2249        if(lpoint.length() > 0) {
2250          coordString = lpoint[0].toString();
2251          coordString = coordString.replace(this.regExes.trimSpace, "");
2252          coords = coordString.split(this.regExes.splitSpace);
2253        }
2254        if(coords.length == 2)
2255          coords[2] = null;
2256        if (this.xy)
2257          var lowerPoint = new ZOO.Geometry.Point(coords[0], coords[1],coords[2]);
2258        else
2259          var lowerPoint = new ZOO.Geometry.Point(coords[1], coords[0],coords[2]);
2260      }
2261      var upoint = node..*::upperCorner;
2262      if (upoint.length() > 0) {
2263        var coords = [];
2264        if(upoint.length > 0) {
2265          coordString = upoint[0].toString();
2266          coordString = coordString.replace(this.regExes.trimSpace, "");
2267          coords = coordString.split(this.regExes.splitSpace);
2268        }
2269        if(coords.length == 2)
2270          coords[2] = null;
2271        if (this.xy)
2272          var upperPoint = new ZOO.Geometry.Point(coords[0], coords[1],coords[2]);
2273        else
2274          var upperPoint = new ZOO.Geometry.Point(coords[1], coords[0],coords[2]);
2275      }
2276      if (lowerPoint && upperPoint) {
2277        components.push(new ZOO.Geometry.Point(lowerPoint.x, lowerPoint.y));
2278        components.push(new ZOO.Geometry.Point(upperPoint.x, lowerPoint.y));
2279        components.push(new ZOO.Geometry.Point(upperPoint.x, upperPoint.y));
2280        components.push(new ZOO.Geometry.Point(lowerPoint.x, upperPoint.y));
2281        components.push(new ZOO.Geometry.Point(lowerPoint.x, lowerPoint.y));
2282        var ring = new ZOO.Geometry.LinearRing(components);
2283        envelope = new ZOO.Geometry.Polygon([ring]);
2284      }
2285      return envelope;
2286    }
2287  },
2288  write: function(features) {
2289    if(!(features instanceof Array)) {
2290      features = [features];
2291    }
2292    var pfx = this.defaultPrefix;
2293    var name = pfx+':'+this.collectionName;
2294    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+'>');
2295    for(var i=0; i<features.length; i++) {
2296      gml.*::*[i] = this.createFeatureXML(features[i]);
2297    }
2298    return gml.toXMLString();
2299  },
2300  createFeatureXML: function(feature) {
2301    var pfx = this.defaultPrefix;
2302    var name = pfx+':'+this.featureName;
2303    var fid = feature.fid || feature.id;
2304    var gml = new XML('<gml:featureMember xmlns:gml="'+this.namespaces['gml']+'"><'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'" fid="'+fid+'"></'+name+'></gml:featureMember>');
2305    var geometry = feature.geometry;
2306    gml.*::*[0].*::* = this.buildGeometryNode(geometry);
2307    for(var attr in feature.attributes) {
2308      var attrNode = new XML('<'+pfx+':'+attr+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'">'+feature.attributes[attr]+'</'+pfx+':'+attr+'>');
2309      gml.*::*[0].appendChild(attrNode);
2310    }
2311    return gml;
2312  },
2313  buildGeometryNode: function(geometry) {
2314    if (this.externalProjection && this.internalProjection) {
2315      geometry = geometry.clone();
2316      geometry.transform(this.internalProjection, 
2317          this.externalProjection);
2318    }   
2319    var className = geometry.CLASS_NAME;
2320    var type = className.substring(className.lastIndexOf(".") + 1);
2321    var builder = this.buildGeometry[type.toLowerCase()];
2322    var pfx = this.defaultPrefix;
2323    var name = pfx+':'+this.geometryName;
2324    var gml = new XML('<'+name+' xmlns:'+pfx+'="'+this.namespaces[pfx]+'"></'+name+'>');
2325    if (builder)
2326      gml.*::* = builder.apply(this, [geometry]);
2327    return gml;
2328  },
2329  buildGeometry: {
2330    'point': function(geometry) {
2331      var gml = new XML('<gml:Point xmlns:gml="'+this.namespaces['gml']+'"></gml:Point>');
2332      gml.*::*[0] = this.buildCoordinatesNode(geometry);
2333      return gml;
2334    },
2335    'multipoint': function(geometry) {
2336      var gml = new XML('<gml:MultiPoint xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiPoint>');
2337      var points = geometry.components;
2338      var pointMember;
2339      for(var i=0; i<points.length; i++) { 
2340        pointMember = new XML('<gml:pointMember xmlns:gml="'+this.namespaces['gml']+'"></gml:pointMember>');
2341        pointMember.*::* = this.buildGeometry.point.apply(this,[points[i]]);
2342        gml.*::*[i] = pointMember;
2343      }
2344      return gml;           
2345    },
2346    'linestring': function(geometry) {
2347      var gml = new XML('<gml:LineString xmlns:gml="'+this.namespaces['gml']+'"></gml:LineString>');
2348      gml.*::*[0] = this.buildCoordinatesNode(geometry);
2349      return gml;
2350    },
2351    'multilinestring': function(geometry) {
2352      var gml = new XML('<gml:MultiLineString xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiLineString>');
2353      var lines = geometry.components;
2354      var lineMember;
2355      for(var i=0; i<lines.length; i++) { 
2356        lineMember = new XML('<gml:lineStringMember xmlns:gml="'+this.namespaces['gml']+'"></gml:lineStringMember>');
2357        lineMember.*::* = this.buildGeometry.linestring.apply(this,[lines[i]]);
2358        gml.*::*[i] = lineMember;
2359      }
2360      return gml;           
2361    },
2362    'linearring': function(geometry) {
2363      var gml = new XML('<gml:LinearRing xmlns:gml="'+this.namespaces['gml']+'"></gml:LinearRing>');
2364      gml.*::*[0] = this.buildCoordinatesNode(geometry);
2365      return gml;
2366    },
2367    'polygon': function(geometry) {
2368      var gml = new XML('<gml:Polygon xmlns:gml="'+this.namespaces['gml']+'"></gml:Polygon>');
2369      var rings = geometry.components;
2370      var ringMember, type;
2371      for(var i=0; i<rings.length; ++i) {
2372        type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
2373        var ringMember = new XML('<gml:'+type+' xmlns:gml="'+this.namespaces['gml']+'"></gml:'+type+'>');
2374        ringMember.*::* = this.buildGeometry.linearring.apply(this,[rings[i]]);
2375        gml.*::*[i] = ringMember;
2376      }
2377      return gml;
2378    },
2379    'multipolygon': function(geometry) {
2380      var gml = new XML('<gml:MultiPolygon xmlns:gml="'+this.namespaces['gml']+'"></gml:MultiPolygon>');
2381      var polys = geometry.components;
2382      var polyMember;
2383      for(var i=0; i<polys.length; i++) { 
2384        polyMember = new XML('<gml:polygonMember xmlns:gml="'+this.namespaces['gml']+'"></gml:polygonMember>');
2385        polyMember.*::* = this.buildGeometry.polygon.apply(this,[polys[i]]);
2386        gml.*::*[i] = polyMember;
2387      }
2388      return gml;           
2389    },
2390    'bounds': function(bounds) {
2391      var gml = new XML('<gml:Box xmlns:gml="'+this.namespaces['gml']+'"></gml:Box>');
2392      gml.*::*[0] = this.buildCoordinatesNode(bounds);
2393      return gml;
2394    }
2395  },
2396  buildCoordinatesNode: function(geometry) {
2397    var parts = [];
2398    if(geometry instanceof ZOO.Bounds){
2399      parts.push(geometry.left + "," + geometry.bottom);
2400      parts.push(geometry.right + "," + geometry.top);
2401    } else {
2402      var points = (geometry.components) ? geometry.components : [geometry];
2403      for(var i=0; i<points.length; i++) {
2404        parts.push(points[i].x + "," + points[i].y);               
2405      }           
2406    }
2407    return new XML('<gml:coordinates xmlns:gml="'+this.namespaces['gml']+'" decimal="." cs=", " ts=" ">'+parts.join(" ")+'</gml:coordinates>');
2408  },
2409  CLASS_NAME: 'ZOO.Format.GML'
2410});
2411ZOO.Format.WPS = ZOO.Class(ZOO.Format, {
2412  schemaLocation: "http://www.opengis.net/wps/1.0.0/../wpsExecute_request.xsd",
2413  namespaces: {
2414    ows: "http://www.opengis.net/ows/1.1",
2415    wps: "http://www.opengis.net/wps/1.0.0",
2416    xlink: "http://www.w3.org/1999/xlink",
2417    xsi: "http://www.w3.org/2001/XMLSchema-instance",
2418  },
2419  read:function(data) {
2420    data = data.replace(/^<\?xml\s+version\s*=\s*(["'])[^\1]+\1[^?]*\?>/, "");
2421    data = new XML(data);
2422    switch (data.localName()) {
2423      case 'ExecuteResponse':
2424        return this.parseExecuteResponse(data);
2425      default:
2426        return null;
2427    }
2428  },
2429  parseExecuteResponse: function(node) {
2430    var outputs = node.*::ProcessOutputs.*::Output;
2431    if (outputs.length() > 0) {
2432      var data = outputs[0].*::Data.*::*[0];
2433      var builder = this.parseData[data.localName().toLowerCase()];
2434      if (builder)
2435        return builder.apply(this,[data]);
2436      else
2437        return null;
2438    } else
2439      return null;
2440  },
2441  parseData: {
2442    'complexdata': function(node) {
2443      var result = {value:node.toString()};
2444      if (node.@mimeType.length()>0)
2445        result.mimeType = node.@mimeType;
2446      if (node.@encoding.length()>0)
2447        result.encoding = node.@encoding;
2448      if (node.@schema.length()>0)
2449        result.schema = node.@schema;
2450      return result;
2451    },
2452    'literaldata': function(node) {
2453      var result = {value:node.toString()};
2454      if (node.@dataType.length()>0)
2455        result.dataType = node.@dataType;
2456      if (node.@uom.length()>0)
2457        result.uom = node.@uom;
2458      return result;
2459    }
2460  },
2461  CLASS_NAME: 'ZOO.Format.WPS'
2462});
2463
2464
2465ZOO.Feature = ZOO.Class({
2466  fid: null,
2467  geometry: null,
2468  attributes: null,
2469  bounds: null,
2470  initialize: function(geometry, attributes) {
2471    this.geometry = geometry ? geometry : null;
2472    this.attributes = {};
2473    if (attributes)
2474      this.attributes = ZOO.extend(this.attributes,attributes);
2475  },
2476  destroy: function() {
2477    this.geometry = null;
2478  },
2479  clone: function () {
2480    return new ZOO.Feature(this.geometry ? this.geometry.clone() : null,
2481            this.attributes);
2482  },
2483  move: function(x, y) {
2484    if(!this.geometry.move)
2485      return;
2486
2487    this.geometry.move(x,y);
2488    return this.geometry;
2489  },
2490  CLASS_NAME: 'ZOO.Feature'
2491});
2492
2493ZOO.Geometry = ZOO.Class({
2494  id: null,
2495  parent: null,
2496  bounds: null,
2497  initialize: function() {
2498    //generate unique id
2499  },
2500  destroy: function() {
2501    this.id = null;
2502    this.bounds = null;
2503  },
2504  clone: function() {
2505    return new ZOO.Geometry();
2506  },
2507  extendBounds: function(newBounds){
2508    var bounds = this.getBounds();
2509    if (!bounds)
2510      this.setBounds(newBounds);
2511    else
2512      this.bounds.extend(newBounds);
2513  },
2514  setBounds: function(bounds) {
2515    if (bounds)
2516      this.bounds = bounds.clone();
2517  },
2518  clearBounds: function() {
2519    this.bounds = null;
2520    if (this.parent)
2521      this.parent.clearBounds();
2522  },
2523  getBounds: function() {
2524    if (this.bounds == null) {
2525      this.calculateBounds();
2526    }
2527    return this.bounds;
2528  },
2529  calculateBounds: function() {
2530    return this.bounds = null;
2531  },
2532  distanceTo: function(geometry, options) {
2533  },
2534  getVertices: function(nodes) {
2535  },
2536  getLength: function() {
2537    return 0.0;
2538  },
2539  getArea: function() {
2540    return 0.0;
2541  },
2542  getCentroid: function() {
2543    return null;
2544  },
2545  toString: function() {
2546    return ZOO.Format.WKT.prototype.write(
2547        new ZOO.Feature(this)
2548    );
2549  },
2550  CLASS_NAME: 'ZOO.Geometry'
2551});
2552ZOO.Geometry.fromWKT = function(wkt) {
2553  var format = arguments.callee.format;
2554  if(!format) {
2555    format = new ZOO.Format.WKT();
2556    arguments.callee.format = format;
2557  }
2558  var geom;
2559  var result = format.read(wkt);
2560  if(result instanceof ZOO.Feature) {
2561    geom = result.geometry;
2562  } else if(result instanceof Array) {
2563    var len = result.length;
2564    var components = new Array(len);
2565    for(var i=0; i<len; ++i) {
2566      components[i] = result[i].geometry;
2567    }
2568    geom = new ZOO.Geometry.Collection(components);
2569  }
2570  return geom;
2571};
2572ZOO.Geometry.segmentsIntersect = function(seg1, seg2, options) {
2573  var point = options && options.point;
2574  var tolerance = options && options.tolerance;
2575  var intersection = false;
2576  var x11_21 = seg1.x1 - seg2.x1;
2577  var y11_21 = seg1.y1 - seg2.y1;
2578  var x12_11 = seg1.x2 - seg1.x1;
2579  var y12_11 = seg1.y2 - seg1.y1;
2580  var y22_21 = seg2.y2 - seg2.y1;
2581  var x22_21 = seg2.x2 - seg2.x1;
2582  var d = (y22_21 * x12_11) - (x22_21 * y12_11);
2583  var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
2584  var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
2585  if(d == 0) {
2586    // parallel
2587    if(n1 == 0 && n2 == 0) {
2588      // coincident
2589      intersection = true;
2590    }
2591  } else {
2592    var along1 = n1 / d;
2593    var along2 = n2 / d;
2594    if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
2595      // intersect
2596      if(!point) {
2597        intersection = true;
2598      } else {
2599        // calculate the intersection point
2600        var x = seg1.x1 + (along1 * x12_11);
2601        var y = seg1.y1 + (along1 * y12_11);
2602        intersection = new ZOO.Geometry.Point(x, y);
2603      }
2604    }
2605  }
2606  if(tolerance) {
2607    var dist;
2608    if(intersection) {
2609      if(point) {
2610        var segs = [seg1, seg2];
2611        var seg, x, y;
2612        // check segment endpoints for proximity to intersection
2613        // set intersection to first endpoint within the tolerance
2614        outer: for(var i=0; i<2; ++i) {
2615          seg = segs[i];
2616          for(var j=1; j<3; ++j) {
2617            x = seg["x" + j];
2618            y = seg["y" + j];
2619            dist = Math.sqrt(
2620                Math.pow(x - intersection.x, 2) +
2621                Math.pow(y - intersection.y, 2)
2622            );
2623            if(dist < tolerance) {
2624              intersection.x = x;
2625              intersection.y = y;
2626              break outer;
2627            }
2628          }
2629        }
2630      }
2631    } else {
2632      // no calculated intersection, but segments could be within
2633      // the tolerance of one another
2634      var segs = [seg1, seg2];
2635      var source, target, x, y, p, result;
2636      // check segment endpoints for proximity to intersection
2637      // set intersection to first endpoint within the tolerance
2638      outer: for(var i=0; i<2; ++i) {
2639        source = segs[i];
2640        target = segs[(i+1)%2];
2641        for(var j=1; j<3; ++j) {
2642          p = {x: source["x"+j], y: source["y"+j]};
2643          result = ZOO.Geometry.distanceToSegment(p, target);
2644          if(result.distance < tolerance) {
2645            if(point) {
2646              intersection = new ZOO.Geometry.Point(p.x, p.y);
2647            } else {
2648              intersection = true;
2649            }
2650            break outer;
2651          }
2652        }
2653      }
2654    }
2655  }
2656  return intersection;
2657};
2658ZOO.Geometry.distanceToSegment = function(point, segment) {
2659  var x0 = point.x;
2660  var y0 = point.y;
2661  var x1 = segment.x1;
2662  var y1 = segment.y1;
2663  var x2 = segment.x2;
2664  var y2 = segment.y2;
2665  var dx = x2 - x1;
2666  var dy = y2 - y1;
2667  var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
2668               (Math.pow(dx, 2) + Math.pow(dy, 2));
2669  var x, y;
2670  if(along <= 0.0) {
2671    x = x1;
2672    y = y1;
2673  } else if(along >= 1.0) {
2674    x = x2;
2675    y = y2;
2676  } else {
2677    x = x1 + along * dx;
2678    y = y1 + along * dy;
2679  }
2680  return {
2681    distance: Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)),
2682    x: x, y: y
2683  };
2684};
2685ZOO.Geometry.Collection = ZOO.Class(ZOO.Geometry, {
2686  components: null,
2687  componentTypes: null,
2688  initialize: function (components) {
2689    ZOO.Geometry.prototype.initialize.apply(this, arguments);
2690    this.components = [];
2691    if (components != null) {
2692      this.addComponents(components);
2693    }
2694  },
2695  destroy: function () {
2696    this.components.length = 0;
2697    this.components = null;
2698  },
2699  clone: function() {
2700    var geometry = eval("new " + this.CLASS_NAME + "()");
2701    for(var i=0, len=this.components.length; i<len; i++) {
2702      geometry.addComponent(this.components[i].clone());
2703    }
2704    return geometry;
2705  },
2706  getComponentsString: function(){
2707    var strings = [];
2708    for(var i=0, len=this.components.length; i<len; i++) {
2709      strings.push(this.components[i].toShortString()); 
2710    }
2711    return strings.join(",");
2712  },
2713  calculateBounds: function() {
2714    this.bounds = null;
2715    if ( this.components && this.components.length > 0) {
2716      this.setBounds(this.components[0].getBounds());
2717      for (var i=1, len=this.components.length; i<len; i++) {
2718        this.extendBounds(this.components[i].getBounds());
2719      }
2720    }
2721    return this.bounds
2722  },
2723  addComponents: function(components){
2724    if(!(components instanceof Array))
2725      components = [components];
2726    for(var i=0, len=components.length; i<len; i++) {
2727      this.addComponent(components[i]);
2728    }
2729  },
2730  addComponent: function(component, index) {
2731    var added = false;
2732    if(component) {
2733      if(this.componentTypes == null ||
2734          (ZOO.indexOf(this.componentTypes,
2735                       component.CLASS_NAME) > -1)) {
2736        if(index != null && (index < this.components.length)) {
2737          var components1 = this.components.slice(0, index);
2738          var components2 = this.components.slice(index, 
2739                                                  this.components.length);
2740          components1.push(component);
2741          this.components = components1.concat(components2);
2742        } else {
2743          this.components.push(component);
2744        }
2745        component.parent = this;
2746        this.clearBounds();
2747        added = true;
2748      }
2749    }
2750    return added;
2751  },
2752  removeComponents: function(components) {
2753    if(!(components instanceof Array))
2754      components = [components];
2755    for(var i=components.length-1; i>=0; --i) {
2756      this.removeComponent(components[i]);
2757    }
2758  },
2759  removeComponent: function(component) {     
2760    ZOO.removeItem(this.components, component);
2761    // clearBounds() so that it gets recalculated on the next call
2762    // to this.getBounds();
2763    this.clearBounds();
2764  },
2765  getLength: function() {
2766    var length = 0.0;
2767    for (var i=0, len=this.components.length; i<len; i++) {
2768      length += this.components[i].getLength();
2769    }
2770    return length;
2771  },
2772  getArea: function() {
2773    var area = 0.0;
2774    for (var i=0, len=this.components.length; i<len; i++) {
2775      area += this.components[i].getArea();
2776    }
2777    return area;
2778  },
2779  getCentroid: function() {
2780    return this.components.length && this.components[0].getCentroid();
2781  },
2782  move: function(x, y) {
2783    for(var i=0, len=this.components.length; i<len; i++) {
2784      this.components[i].move(x, y);
2785    }
2786  },
2787  rotate: function(angle, origin) {
2788    for(var i=0, len=this.components.length; i<len; ++i) {
2789      this.components[i].rotate(angle, origin);
2790    }
2791  },
2792  resize: function(scale, origin, ratio) {
2793    for(var i=0; i<this.components.length; ++i) {
2794      this.components[i].resize(scale, origin, ratio);
2795    }
2796    return this;
2797  },
2798  distanceTo: function(geometry, options) {
2799    var edge = !(options && options.edge === false);
2800    var details = edge && options && options.details;
2801    var result, best;
2802    var min = Number.POSITIVE_INFINITY;
2803    for(var i=0, len=this.components.length; i<len; ++i) {
2804      result = this.components[i].distanceTo(geometry, options);
2805      distance = details ? result.distance : result;
2806      if(distance < min) {
2807        min = distance;
2808        best = result;
2809        if(min == 0)
2810          break;
2811      }
2812    }
2813    return best;
2814  },
2815  equals: function(geometry) {
2816    var equivalent = true;
2817    if(!geometry || !geometry.CLASS_NAME ||
2818       (this.CLASS_NAME != geometry.CLASS_NAME))
2819      equivalent = false;
2820    else if(!(geometry.components instanceof Array) ||
2821             (geometry.components.length != this.components.length))
2822      equivalent = false;
2823    else
2824      for(var i=0, len=this.components.length; i<len; ++i) {
2825        if(!this.components[i].equals(geometry.components[i])) {
2826          equivalent = false;
2827          break;
2828        }
2829      }
2830    return equivalent;
2831  },
2832  transform: function(source, dest) {
2833    if (source && dest) {
2834      for (var i=0, len=this.components.length; i<len; i++) { 
2835        var component = this.components[i];
2836        component.transform(source, dest);
2837      }
2838      this.bounds = null;
2839    }
2840    return this;
2841  },
2842  intersects: function(geometry) {
2843    var intersect = false;
2844    for(var i=0, len=this.components.length; i<len; ++ i) {
2845      intersect = geometry.intersects(this.components[i]);
2846      if(intersect)
2847        break;
2848    }
2849    return intersect;
2850  },
2851  getVertices: function(nodes) {
2852    var vertices = [];
2853    for(var i=0, len=this.components.length; i<len; ++i) {
2854      Array.prototype.push.apply(
2855          vertices, this.components[i].getVertices(nodes)
2856          );
2857    }
2858    return vertices;
2859  },
2860  CLASS_NAME: 'ZOO.Geometry.Collection'
2861});
2862ZOO.Geometry.Point = ZOO.Class(ZOO.Geometry, {
2863  x: null,
2864  y: null,
2865  initialize: function(x, y) {
2866    ZOO.Geometry.prototype.initialize.apply(this, arguments);
2867    this.x = parseFloat(x);
2868    this.y = parseFloat(y);
2869  },
2870  clone: function(obj) {
2871    if (obj == null)
2872      obj = new ZOO.Geometry.Point(this.x, this.y);
2873    // catch any randomly tagged-on properties
2874    //OpenLayers.Util.applyDefaults(obj, this);
2875    return obj;
2876  },
2877  calculateBounds: function () {
2878    this.bounds = new ZOO.Bounds(this.x, this.y,
2879                                        this.x, this.y);
2880  },
2881  distanceTo: function(geometry, options) {
2882    var edge = !(options && options.edge === false);
2883    var details = edge && options && options.details;
2884    var distance, x0, y0, x1, y1, result;
2885    if(geometry instanceof ZOO.Geometry.Point) {
2886      x0 = this.x;
2887      y0 = this.y;
2888      x1 = geometry.x;
2889      y1 = geometry.y;
2890      distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
2891      result = !details ?
2892        distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
2893    } else {
2894      result = geometry.distanceTo(this, options);
2895      if(details) {
2896        // switch coord order since this geom is target
2897        result = {
2898          x0: result.x1, y0: result.y1,
2899          x1: result.x0, y1: result.y0,
2900          distance: result.distance
2901        };
2902      }
2903    }
2904    return result;
2905  },
2906  equals: function(geom) {
2907    var equals = false;
2908    if (geom != null)
2909      equals = ((this.x == geom.x && this.y == geom.y) ||
2910                (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
2911    return equals;
2912  },
2913  toShortString: function() {
2914    return (this.x + ", " + this.y);
2915  },
2916  move: function(x, y) {
2917    this.x = this.x + x;
2918    this.y = this.y + y;
2919    this.clearBounds();
2920  },
2921  rotate: function(angle, origin) {
2922        angle *= Math.PI / 180;
2923        var radius = this.distanceTo(origin);
2924        var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
2925        this.x = origin.x + (radius * Math.cos(theta));
2926        this.y = origin.y + (radius * Math.sin(theta));
2927        this.clearBounds();
2928  },
2929  getCentroid: function() {
2930    return new ZOO.Geometry.Point(this.x, this.y);
2931  },
2932  resize: function(scale, origin, ratio) {
2933    ratio = (ratio == undefined) ? 1 : ratio;
2934    this.x = origin.x + (scale * ratio * (this.x - origin.x));
2935    this.y = origin.y + (scale * (this.y - origin.y));
2936    this.clearBounds();
2937    return this;
2938  },
2939  intersects: function(geometry) {
2940    var intersect = false;
2941    if(geometry.CLASS_NAME == "ZOO.Geometry.Point") {
2942      intersect = this.equals(geometry);
2943    } else {
2944      intersect = geometry.intersects(this);
2945    }
2946    return intersect;
2947  },
2948  transform: function(source, dest) {
2949    if ((source && dest)) {
2950      ZOO.Projection.transform(
2951          this, source, dest); 
2952      this.bounds = null;
2953    }       
2954    return this;
2955  },
2956  getVertices: function(nodes) {
2957    return [this];
2958  },
2959  CLASS_NAME: 'ZOO.Geometry.Point'
2960});
2961ZOO.Geometry.Surface = ZOO.Class(ZOO.Geometry, {
2962  initialize: function() {
2963    ZOO.Geometry.prototype.initialize.apply(this, arguments);
2964  },
2965  CLASS_NAME: "ZOO.Geometry.Surface"
2966});
2967ZOO.Geometry.MultiPoint = ZOO.Class(
2968  ZOO.Geometry.Collection, {
2969  componentTypes: ["ZOO.Geometry.Point"],
2970  initialize: function(components) {
2971    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
2972  },
2973  addPoint: function(point, index) {
2974    this.addComponent(point, index);
2975  },
2976  removePoint: function(point){
2977    this.removeComponent(point);
2978  },
2979  CLASS_NAME: "ZOO.Geometry.MultiPoint"
2980});
2981ZOO.Geometry.Curve = ZOO.Class(ZOO.Geometry.MultiPoint, {
2982  componentTypes: ["ZOO.Geometry.Point"],
2983  initialize: function(points) {
2984    ZOO.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);
2985  },
2986  getLength: function() {
2987    var length = 0.0;
2988    if ( this.components && (this.components.length > 1)) {
2989      for(var i=1, len=this.components.length; i<len; i++) {
2990        length += this.components[i-1].distanceTo(this.components[i]);
2991      }
2992    }
2993    return length;
2994  },
2995  CLASS_NAME: "ZOO.Geometry.Curve"
2996});
2997ZOO.Geometry.LineString = ZOO.Class(ZOO.Geometry.Curve, {
2998  initialize: function(points) {
2999    ZOO.Geometry.Curve.prototype.initialize.apply(this, arguments);       
3000  },
3001  removeComponent: function(point) {
3002    if ( this.components && (this.components.length > 2))
3003      ZOO.Geometry.Collection.prototype.removeComponent.apply(this,arguments);
3004  },
3005  intersects: function(geometry) {
3006    var intersect = false;
3007    var type = geometry.CLASS_NAME;
3008    if(type == "ZOO.Geometry.LineString" ||
3009       type == "ZOO.Geometry.LinearRing" ||
3010       type == "ZOO.Geometry.Point") {
3011      var segs1 = this.getSortedSegments();
3012      var segs2;
3013      if(type == "ZOO.Geometry.Point")
3014        segs2 = [{
3015          x1: geometry.x, y1: geometry.y,
3016          x2: geometry.x, y2: geometry.y
3017        }];
3018      else
3019        segs2 = geometry.getSortedSegments();
3020      var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
3021          seg2, seg2y1, seg2y2;
3022      // sweep right
3023      outer: for(var i=0, len=segs1.length; i<len; ++i) {
3024         seg1 = segs1[i];
3025         seg1x1 = seg1.x1;
3026         seg1x2 = seg1.x2;
3027         seg1y1 = seg1.y1;
3028         seg1y2 = seg1.y2;
3029         inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
3030           seg2 = segs2[j];
3031           if(seg2.x1 > seg1x2)
3032             break;
3033           if(seg2.x2 < seg1x1)
3034             continue;
3035           seg2y1 = seg2.y1;
3036           seg2y2 = seg2.y2;
3037           if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2))
3038             continue;
3039           if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2))
3040             continue;
3041           if(ZOO.Geometry.segmentsIntersect(seg1, seg2)) {
3042             intersect = true;
3043             break outer;
3044           }
3045         }
3046      }
3047    } else {
3048      intersect = geometry.intersects(this);
3049    }
3050    return intersect;
3051  },
3052  getSortedSegments: function() {
3053    var numSeg = this.components.length - 1;
3054    var segments = new Array(numSeg);
3055    for(var i=0; i<numSeg; ++i) {
3056      point1 = this.components[i];
3057      point2 = this.components[i + 1];
3058      if(point1.x < point2.x)
3059        segments[i] = {
3060          x1: point1.x,
3061          y1: point1.y,
3062          x2: point2.x,
3063          y2: point2.y
3064        };
3065      else
3066        segments[i] = {
3067          x1: point2.x,
3068          y1: point2.y,
3069          x2: point1.x,
3070          y2: point1.y
3071        };
3072    }
3073    // more efficient to define this somewhere static
3074    function byX1(seg1, seg2) {
3075      return seg1.x1 - seg2.x1;
3076    }
3077    return segments.sort(byX1);
3078  },
3079  splitWithSegment: function(seg, options) {
3080    var edge = !(options && options.edge === false);
3081    var tolerance = options && options.tolerance;
3082    var lines = [];
3083    var verts = this.getVertices();
3084    var points = [];
3085    var intersections = [];
3086    var split = false;
3087    var vert1, vert2, point;
3088    var node, vertex, target;
3089    var interOptions = {point: true, tolerance: tolerance};
3090    var result = null;
3091    for(var i=0, stop=verts.length-2; i<=stop; ++i) {
3092      vert1 = verts[i];
3093      points.push(vert1.clone());
3094      vert2 = verts[i+1];
3095      target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
3096      point = ZOO.Geometry.segmentsIntersect(seg, target, interOptions);
3097      if(point instanceof ZOO.Geometry.Point) {
3098        if((point.x === seg.x1 && point.y === seg.y1) ||
3099           (point.x === seg.x2 && point.y === seg.y2) ||
3100            point.equals(vert1) || point.equals(vert2))
3101          vertex = true;
3102        else
3103          vertex = false;
3104        if(vertex || edge) {
3105          // push intersections different than the previous
3106          if(!point.equals(intersections[intersections.length-1]))
3107            intersections.push(point.clone());
3108          if(i === 0) {
3109            if(point.equals(vert1))
3110              continue;
3111          }
3112          if(point.equals(vert2))
3113            continue;
3114          split = true;
3115          if(!point.equals(vert1))
3116            points.push(point);
3117          lines.push(new ZOO.Geometry.LineString(points));
3118          points = [point.clone()];
3119        }
3120      }
3121    }
3122    if(split) {
3123      points.push(vert2.clone());
3124      lines.push(new ZOO.Geometry.LineString(points));
3125    }
3126    if(intersections.length > 0) {
3127      // sort intersections along segment
3128      var xDir = seg.x1 < seg.x2 ? 1 : -1;
3129      var yDir = seg.y1 < seg.y2 ? 1 : -1;
3130      result = {
3131        lines: lines,
3132        points: intersections.sort(function(p1, p2) {
3133           return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
3134        })
3135      };
3136    }
3137    return result;
3138  },
3139  split: function(target, options) {
3140    var results = null;
3141    var mutual = options && options.mutual;
3142    var sourceSplit, targetSplit, sourceParts, targetParts;
3143    if(target instanceof ZOO.Geometry.LineString) {
3144      var verts = this.getVertices();
3145      var vert1, vert2, seg, splits, lines, point;
3146      var points = [];
3147      sourceParts = [];
3148      for(var i=0, stop=verts.length-2; i<=stop; ++i) {
3149        vert1 = verts[i];
3150        vert2 = verts[i+1];
3151        seg = {
3152          x1: vert1.x, y1: vert1.y,
3153          x2: vert2.x, y2: vert2.y
3154        };
3155        targetParts = targetParts || [target];
3156        if(mutual)
3157          points.push(vert1.clone());
3158        for(var j=0; j<targetParts.length; ++j) {
3159          splits = targetParts[j].splitWithSegment(seg, options);
3160          if(splits) {
3161            // splice in new features
3162            lines = splits.lines;
3163            if(lines.length > 0) {
3164              lines.unshift(j, 1);
3165              Array.prototype.splice.apply(targetParts, lines);
3166              j += lines.length - 2;
3167            }
3168            if(mutual) {
3169              for(var k=0, len=splits.points.length; k<len; ++k) {
3170                point = splits.points[k];
3171                if(!point.equals(vert1)) {
3172                  points.push(point);
3173                  sourceParts.push(new ZOO.Geometry.LineString(points));
3174                  if(point.equals(vert2))
3175                    points = [];
3176                  else
3177                    points = [point.clone()];
3178                }
3179              }
3180            }
3181          }
3182        }
3183      }
3184      if(mutual && sourceParts.length > 0 && points.length > 0) {
3185        points.push(vert2.clone());
3186        sourceParts.push(new ZOO.Geometry.LineString(points));
3187      }
3188    } else {
3189      results = target.splitWith(this, options);
3190    }
3191    if(targetParts && targetParts.length > 1)
3192      targetSplit = true;
3193    else
3194      targetParts = [];
3195    if(sourceParts && sourceParts.length > 1)
3196      sourceSplit = true;
3197    else
3198      sourceParts = [];
3199    if(targetSplit || sourceSplit) {
3200      if(mutual)
3201        results = [sourceParts, targetParts];
3202      else
3203        results = targetParts;
3204    }
3205    return results;
3206  },
3207  splitWith: function(geometry, options) {
3208    return geometry.split(this, options);
3209  },
3210  getVertices: function(nodes) {
3211    var vertices;
3212    if(nodes === true)
3213      vertices = [
3214        this.components[0],
3215        this.components[this.components.length-1]
3216      ];
3217    else if (nodes === false)
3218      vertices = this.components.slice(1, this.components.length-1);
3219    else
3220      vertices = this.components.slice();
3221    return vertices;
3222  },
3223  distanceTo: function(geometry, options) {
3224    var edge = !(options && options.edge === false);
3225    var details = edge && options && options.details;
3226    var result, best = {};
3227    var min = Number.POSITIVE_INFINITY;
3228    if(geometry instanceof ZOO.Geometry.Point) {
3229      var segs = this.getSortedSegments();
3230      var x = geometry.x;
3231      var y = geometry.y;
3232      var seg;
3233      for(var i=0, len=segs.length; i<len; ++i) {
3234        seg = segs[i];
3235        result = ZOO.Geometry.distanceToSegment(geometry, seg);
3236        if(result.distance < min) {
3237          min = result.distance;
3238          best = result;
3239          if(min === 0)
3240            break;
3241        } else {
3242          // if distance increases and we cross y0 to the right of x0, no need to keep looking.
3243          if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2)))
3244            break;
3245        }
3246      }
3247      if(details)
3248        best = {
3249          distance: best.distance,
3250          x0: best.x, y0: best.y,
3251          x1: x, y1: y
3252        };
3253      else
3254        best = best.distance;
3255    } else if(geometry instanceof ZOO.Geometry.LineString) { 
3256      var segs0 = this.getSortedSegments();
3257      var segs1 = geometry.getSortedSegments();
3258      var seg0, seg1, intersection, x0, y0;
3259      var len1 = segs1.length;
3260      var interOptions = {point: true};
3261      outer: for(var i=0, len=segs0.length; i<len; ++i) {
3262        seg0 = segs0[i];
3263        x0 = seg0.x1;
3264        y0 = seg0.y1;
3265        for(var j=0; j<len1; ++j) {
3266          seg1 = segs1[j];
3267          intersection = ZOO.Geometry.segmentsIntersect(seg0, seg1, interOptions);
3268          if(intersection) {
3269            min = 0;
3270            best = {
3271              distance: 0,
3272              x0: intersection.x, y0: intersection.y,
3273              x1: intersection.x, y1: intersection.y
3274            };
3275            break outer;
3276          } else {
3277            result = ZOO.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
3278            if(result.distance < min) {
3279              min = result.distance;
3280              best = {
3281                distance: min,
3282                x0: x0, y0: y0,
3283                x1: result.x, y1: result.y
3284              };
3285            }
3286          }
3287        }
3288      }
3289      if(!details)
3290        best = best.distance;
3291      if(min !== 0) {
3292        // check the final vertex in this line's sorted segments
3293        if(seg0) {
3294          result = geometry.distanceTo(
3295              new ZOO.Geometry.Point(seg0.x2, seg0.y2),
3296              options
3297              );
3298          var dist = details ? result.distance : result;
3299          if(dist < min) {
3300            if(details)
3301              best = {
3302                distance: min,
3303                x0: result.x1, y0: result.y1,
3304                x1: result.x0, y1: result.y0
3305              };
3306            else
3307              best = dist;
3308          }
3309        }
3310      }
3311    } else {
3312      best = geometry.distanceTo(this, options);
3313      // swap since target comes from this line
3314      if(details)
3315        best = {
3316          distance: best.distance,
3317          x0: best.x1, y0: best.y1,
3318          x1: best.x0, y1: best.y0
3319        };
3320    }
3321    return best;
3322  },
3323  CLASS_NAME: "ZOO.Geometry.LineString"
3324});
3325ZOO.Geometry.LinearRing = ZOO.Class(
3326  ZOO.Geometry.LineString, {
3327  componentTypes: ["ZOO.Geometry.Point"],
3328  initialize: function(points) {
3329    ZOO.Geometry.LineString.prototype.initialize.apply(this,arguments);
3330  },
3331  addComponent: function(point, index) {
3332    var added = false;
3333    //remove last point
3334    var lastPoint = this.components.pop();
3335    // given an index, add the point
3336    // without an index only add non-duplicate points
3337    if(index != null || !point.equals(lastPoint))
3338      added = ZOO.Geometry.Collection.prototype.addComponent.apply(this,arguments);
3339    //append copy of first point
3340    var firstPoint = this.components[0];
3341    ZOO.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);
3342    return added;
3343  },
3344  removeComponent: function(point) {
3345    if (this.components.length > 4) {
3346      //remove last point
3347      this.components.pop();
3348      //remove our point
3349      ZOO.Geometry.Collection.prototype.removeComponent.apply(this,arguments);
3350      //append copy of first point
3351      var firstPoint = this.components[0];
3352      ZOO.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);
3353    }
3354  },
3355  move: function(x, y) {
3356    for(var i = 0, len=this.components.length; i<len - 1; i++) {
3357      this.components[i].move(x, y);
3358    }
3359  },
3360  rotate: function(angle, origin) {
3361    for(var i=0, len=this.components.length; i<len - 1; ++i) {
3362      this.components[i].rotate(angle, origin);
3363    }
3364  },
3365  resize: function(scale, origin, ratio) {
3366    for(var i=0, len=this.components.length; i<len - 1; ++i) {
3367      this.components[i].resize(scale, origin, ratio);
3368    }
3369    return this;
3370  },
3371  transform: function(source, dest) {
3372    if (source && dest) {
3373      for (var i=0, len=this.components.length; i<len - 1; i++) {
3374        var component = this.components[i];
3375        component.transform(source, dest);
3376      }
3377      this.bounds = null;
3378    }
3379    return this;
3380  },
3381  getCentroid: function() {
3382    if ( this.components && (this.components.length > 2)) {
3383      var sumX = 0.0;
3384      var sumY = 0.0;
3385      for (var i = 0; i < this.components.length - 1; i++) {
3386        var b = this.components[i];
3387        var c = this.components[i+1];
3388        sumX += (b.x + c.x) * (b.x * c.y - c.x * b.y);
3389        sumY += (b.y + c.y) * (b.x * c.y - c.x * b.y);
3390      }
3391      var area = -1 * this.getArea();
3392      var x = sumX / (6 * area);
3393      var y = sumY / (6 * area);
3394    }
3395    return new ZOO.Geometry.Point(x, y);
3396  },
3397  getArea: function() {
3398    var area = 0.0;
3399    if ( this.components && (this.components.length > 2)) {
3400      var sum = 0.0;
3401      for (var i=0, len=this.components.length; i<len - 1; i++) {
3402        var b = this.components[i];
3403        var c = this.components[i+1];
3404        sum += (b.x + c.x) * (c.y - b.y);
3405      }
3406      area = - sum / 2.0;
3407    }
3408    return area;
3409  },
3410  containsPoint: function(point) {
3411    var approx = OpenLayers.Number.limitSigDigs;
3412    var digs = 14;
3413    var px = approx(point.x, digs);
3414    var py = approx(point.y, digs);
3415    function getX(y, x1, y1, x2, y2) {
3416      return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2);
3417    }
3418    var numSeg = this.components.length - 1;
3419    var start, end, x1, y1, x2, y2, cx, cy;
3420    var crosses = 0;
3421    for(var i=0; i<numSeg; ++i) {
3422      start = this.components[i];
3423      x1 = approx(start.x, digs);
3424      y1 = approx(start.y, digs);
3425      end = this.components[i + 1];
3426      x2 = approx(end.x, digs);
3427      y2 = approx(end.y, digs);
3428
3429      /**
3430       * The following conditions enforce five edge-crossing rules:
3431       *    1. points coincident with edges are considered contained;
3432       *    2. an upward edge includes its starting endpoint, and
3433       *    excludes its final endpoint;
3434       *    3. a downward edge excludes its starting endpoint, and
3435       *    includes its final endpoint;
3436       *    4. horizontal edges are excluded; and
3437       *    5. the edge-ray intersection point must be strictly right
3438       *    of the point P.
3439       */
3440      if(y1 == y2) {
3441        // horizontal edge
3442        if(py == y1) {
3443          // point on horizontal line
3444          if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
3445              x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
3446            // point on edge
3447            crosses = -1;
3448            break;
3449          }
3450        }
3451        // ignore other horizontal edges
3452        continue;
3453      }
3454      cx = approx(getX(py, x1, y1, x2, y2), digs);
3455      if(cx == px) {
3456        // point on line
3457        if(y1 < y2 && (py >= y1 && py <= y2) || // upward
3458            y1 > y2 && (py <= y1 && py >= y2)) { // downward
3459          // point on edge
3460          crosses = -1;
3461          break;
3462        }
3463      }
3464      if(cx <= px) {
3465        // no crossing to the right
3466        continue;
3467      }
3468      if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
3469        // no crossing
3470        continue;
3471      }
3472      if(y1 < y2 && (py >= y1 && py < y2) || // upward
3473          y1 > y2 && (py < y1 && py >= y2)) { // downward
3474        ++crosses;
3475      }
3476    }
3477    var contained = (crosses == -1) ?
3478      // on edge
3479      1 :
3480      // even (out) or odd (in)
3481      !!(crosses & 1);
3482
3483    return contained;
3484  },
3485  intersects: function(geometry) {
3486    var intersect = false;
3487    if(geometry.CLASS_NAME == "ZOO.Geometry.Point")
3488      intersect = this.containsPoint(geometry);
3489    else if(geometry.CLASS_NAME == "ZOO.Geometry.LineString")
3490      intersect = geometry.intersects(this);
3491    else if(geometry.CLASS_NAME == "ZOO.Geometry.LinearRing")
3492      intersect = ZOO.Geometry.LineString.prototype.intersects.apply(
3493          this, [geometry]
3494          );
3495    else
3496      for(var i=0, len=geometry.components.length; i<len; ++ i) {
3497        intersect = geometry.components[i].intersects(this);
3498        if(intersect)
3499          break;
3500      }
3501    return intersect;
3502  },
3503  getVertices: function(nodes) {
3504    return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
3505  },
3506  CLASS_NAME: "ZOO.Geometry.LinearRing"
3507});
3508ZOO.Geometry.MultiLineString = ZOO.Class(
3509  ZOO.Geometry.Collection, {
3510  componentTypes: ["ZOO.Geometry.LineString"],
3511  initialize: function(components) {
3512    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);       
3513  },
3514  split: function(geometry, options) {
3515    var results = null;
3516    var mutual = options && options.mutual;
3517    var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
3518    var sourceParts = [];
3519    var targetParts = [geometry];
3520    for(var i=0, len=this.components.length; i<len; ++i) {
3521      sourceLine = this.components[i];
3522      sourceSplit = false;
3523      for(var j=0; j < targetParts.length; ++j) { 
3524        splits = sourceLine.split(targetParts[j], options);
3525        if(splits) {
3526          if(mutual) {
3527            sourceLines = splits[0];
3528            for(var k=0, klen=sourceLines.length; k<klen; ++k) {
3529              if(k===0 && sourceParts.length)
3530                sourceParts[sourceParts.length-1].addComponent(
3531                  sourceLines[k]
3532                );
3533              else
3534                sourceParts.push(
3535                  new ZOO.Geometry.MultiLineString([
3536                    sourceLines[k]
3537                    ])
3538                );
3539            }
3540            sourceSplit = true;
3541            splits = splits[1];
3542          }
3543          if(splits.length) {
3544            // splice in new target parts
3545            splits.unshift(j, 1);
3546            Array.prototype.splice.apply(targetParts, splits);
3547            break;
3548          }
3549        }
3550      }
3551      if(!sourceSplit) {
3552        // source line was not hit
3553        if(sourceParts.length) {
3554          // add line to existing multi
3555          sourceParts[sourceParts.length-1].addComponent(
3556              sourceLine.clone()
3557              );
3558        } else {
3559          // create a fresh multi
3560          sourceParts = [
3561            new ZOO.Geometry.MultiLineString(
3562                sourceLine.clone()
3563                )
3564            ];
3565        }
3566      }
3567    }
3568    if(sourceParts && sourceParts.length > 1)
3569      sourceSplit = true;
3570    else
3571      sourceParts = [];
3572    if(targetParts && targetParts.length > 1)
3573      targetSplit = true;
3574    else
3575      targetParts = [];
3576    if(sourceSplit || targetSplit) {
3577      if(mutual)
3578        results = [sourceParts, targetParts];
3579      else
3580        results = targetParts;
3581    }
3582    return results;
3583  },
3584  splitWith: function(geometry, options) {
3585    var results = null;
3586    var mutual = options && options.mutual;
3587    var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
3588    if(geometry instanceof ZOO.Geometry.LineString) {
3589      targetParts = [];
3590      sourceParts = [geometry];
3591      for(var i=0, len=this.components.length; i<len; ++i) {
3592        targetSplit = false;
3593        targetLine = this.components[i];
3594        for(var j=0; j<sourceParts.length; ++j) {
3595          splits = sourceParts[j].split(targetLine, options);
3596          if(splits) {
3597            if(mutual) {
3598              sourceLines = splits[0];
3599              if(sourceLines.length) {
3600                // splice in new source parts
3601                sourceLines.unshift(j, 1);
3602                Array.prototype.splice.apply(sourceParts, sourceLines);
3603                j += sourceLines.length - 2;
3604              }
3605              splits = splits[1];
3606              if(splits.length === 0) {
3607                splits = [targetLine.clone()];
3608              }
3609            }
3610            for(var k=0, klen=splits.length; k<klen; ++k) {
3611              if(k===0 && targetParts.length) {
3612                targetParts[targetParts.length-1].addComponent(
3613                    splits[k]
3614                    );
3615              } else {
3616                targetParts.push(
3617                    new ZOO.Geometry.MultiLineString([
3618                      splits[k]
3619                      ])
3620                    );
3621              }
3622            }
3623            targetSplit = true;                   
3624          }
3625        }
3626        if(!targetSplit) {
3627          // target component was not hit
3628          if(targetParts.length) {
3629            // add it to any existing multi-line
3630            targetParts[targetParts.length-1].addComponent(
3631                targetLine.clone()
3632                );
3633          } else {
3634            // or start with a fresh multi-line
3635            targetParts = [
3636              new ZOO.Geometry.MultiLineString([
3637                  targetLine.clone()
3638                  ])
3639              ];
3640          }
3641
3642        }
3643      }
3644    } else {
3645      results = geometry.split(this);
3646    }
3647    if(sourceParts && sourceParts.length > 1)
3648      sourceSplit = true;
3649    else
3650      sourceParts = [];
3651    if(targetParts && targetParts.length > 1)
3652      targetSplit = true;
3653    else
3654      targetParts = [];
3655    if(sourceSplit || targetSplit) {
3656      if(mutual)
3657        results = [sourceParts, targetParts];
3658      else
3659        results = targetParts;
3660    }
3661    return results;
3662  },
3663  CLASS_NAME: "ZOO.Geometry.MultiLineString"
3664});
3665ZOO.Geometry.Polygon = ZOO.Class(
3666  ZOO.Geometry.Collection, {
3667  componentTypes: ["ZOO.Geometry.LinearRing"],
3668  initialize: function(components) {
3669    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
3670  },
3671  getArea: function() {
3672    var area = 0.0;
3673    if ( this.components && (this.components.length > 0)) {
3674      area += Math.abs(this.components[0].getArea());
3675      for (var i=1, len=this.components.length; i<len; i++) {
3676        area -= Math.abs(this.components[i].getArea());
3677      }
3678    }
3679    return area;
3680  },
3681  containsPoint: function(point) {
3682    var numRings = this.components.length;
3683    var contained = false;
3684    if(numRings > 0) {
3685    // check exterior ring - 1 means on edge, boolean otherwise
3686      contained = this.components[0].containsPoint(point);
3687      if(contained !== 1) {
3688        if(contained && numRings > 1) {
3689          // check interior rings
3690          var hole;
3691          for(var i=1; i<numRings; ++i) {
3692            hole = this.components[i].containsPoint(point);
3693            if(hole) {
3694              if(hole === 1)
3695                contained = 1;
3696              else
3697                contained = false;
3698              break;
3699            }
3700          }
3701        }
3702      }
3703    }
3704    return contained;
3705  },
3706  intersects: function(geometry) {
3707    var intersect = false;
3708    var i, len;
3709    if(geometry.CLASS_NAME == "ZOO.Geometry.Point") {
3710      intersect = this.containsPoint(geometry);
3711    } else if(geometry.CLASS_NAME == "ZOO.Geometry.LineString" ||
3712              geometry.CLASS_NAME == "ZOO.Geometry.LinearRing") {
3713      // check if rings/linestrings intersect
3714      for(i=0, len=this.components.length; i<len; ++i) {
3715        intersect = geometry.intersects(this.components[i]);
3716        if(intersect) {
3717          break;
3718        }
3719      }
3720      if(!intersect) {
3721        // check if this poly contains points of the ring/linestring
3722        for(i=0, len=geometry.components.length; i<len; ++i) {
3723          intersect = this.containsPoint(geometry.components[i]);
3724          if(intersect) {
3725            break;
3726          }
3727        }
3728      }
3729    } else {
3730      for(i=0, len=geometry.components.length; i<len; ++ i) {
3731        intersect = this.intersects(geometry.components[i]);
3732        if(intersect)
3733          break;
3734      }
3735    }
3736    // check case where this poly is wholly contained by another
3737    if(!intersect && geometry.CLASS_NAME == "ZOO.Geometry.Polygon") {
3738      // exterior ring points will be contained in the other geometry
3739      var ring = this.components[0];
3740      for(i=0, len=ring.components.length; i<len; ++i) {
3741        intersect = geometry.containsPoint(ring.components[i]);
3742        if(intersect)
3743          break;
3744      }
3745    }
3746    return intersect;
3747  },
3748  distanceTo: function(geometry, options) {
3749    var edge = !(options && options.edge === false);
3750    var result;
3751    // this is the case where we might not be looking for distance to edge
3752    if(!edge && this.intersects(geometry))
3753      result = 0;
3754    else
3755      result = ZOO.Geometry.Collection.prototype.distanceTo.apply(
3756          this, [geometry, options]
3757          );
3758    return result;
3759  },
3760  CLASS_NAME: "ZOO.Geometry.Polygon"
3761});
3762ZOO.Geometry.MultiPolygon = ZOO.Class(
3763  ZOO.Geometry.Collection, {
3764  componentTypes: ["ZOO.Geometry.Polygon"],
3765  initialize: function(components) {
3766    ZOO.Geometry.Collection.prototype.initialize.apply(this,arguments);
3767  },
3768  CLASS_NAME: "ZOO.Geometry.MultiPolygon"
3769});
3770
3771ZOO.Process = ZOO.Class({
3772  schemaLocation: "http://www.opengis.net/wps/1.0.0/../wpsExecute_request.xsd",
3773  namespaces: {
3774    ows: "http://www.opengis.net/ows/1.1",
3775    wps: "http://www.opengis.net/wps/1.0.0",
3776    xlink: "http://www.w3.org/1999/xlink",
3777    xsi: "http://www.w3.org/2001/XMLSchema-instance",
3778  },
3779  url: 'http://localhost/zoo',
3780  identifier: null,
3781  initialize: function(url,identifier) {
3782    this.url = url;
3783    this.identifier = identifier;
3784  },
3785  Execute: function(inputs) {
3786    if (this.identifier == null)
3787      return null;
3788    var body = new XML('<wps:Execute service="WPS" version="1.0.0" xmlns:wps="'+this.namespaces['wps']+'" xmlns:ows="'+this.namespaces['ows']+'" xmlns:xlink="'+this.namespaces['xlink']+'" xmlns:xsi="'+this.namespaces['xsi']+'" xsi:schemaLocation="'+this.schemaLocation+'"><ows:Identifier>'+this.identifier+'</ows:Identifier>'+this.buildDataInputsNode(inputs)+'</wps:Execute>');
3789    body = body.toXMLString();
3790    var response = ZOO.Request.Post(this.url,body,['Content-Type: text/xml; charset=UTF-8']);
3791    return response;
3792  },
3793  buildInput: {
3794    'complex': function(identifier,data) {
3795      var input = new XML('<wps:Input xmlns:wps="'+this.namespaces['wps']+'"><ows:Identifier xmlns:ows="'+this.namespaces['ows']+'">'+identifier+'</ows:Identifier><wps:Data><wps:ComplexData>'+data.value+'</wps:ComplexData></wps:Data></wps:Input>');
3796      input.*::Data.*::ComplexData.@mimeType = data.mimetype ? data.mimetype : 'text/plain';
3797      if (data.encoding)
3798        input.*::Data.*::ComplexData.@encoding = data.encoding;
3799      if (data.schema)
3800        input.*::Data.*::ComplexData.@schema = data.schema;
3801      input = input.toXMLString();
3802      return input;
3803    },
3804    'reference': function(identifier,data) {
3805      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>';
3806    },
3807    'literal': function(identifier,data) {
3808      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>');
3809      if (data.type)
3810        input.*::Data.*::LiteralData.@dataType = data.type;
3811      if (data.uom)
3812        input.*::Data.*::LiteralData.@uom = data.uom;
3813      input = input.toXMLString();
3814      return input;
3815    }
3816  },
3817  buildDataInputsNode:function(inputs){
3818    var data, builder, inputsArray=[];
3819    for (var attr in inputs) {
3820      data = inputs[attr];
3821      if (data.mimetype || data.type == 'complex')
3822        builder = this.buildInput['complex'];
3823      else if (data.type == 'reference' || data.type == 'url')
3824        builder = this.buildInput['reference'];
3825      else
3826        builder = this.buildInput['literal'];
3827      inputsArray.push(builder.apply(this,[attr,data]));
3828    }
3829    return '<wps:DataInputs xmlns:wps="'+this.namespaces['wps']+'">'+inputsArray.join('\n')+'</wps:DataInputs>';
3830  },
3831  CLASS_NAME: "ZOO.Process"
3832});
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