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

Last change on this file since 1 was 1, checked in by djay, 14 years ago

Initial ZOO SVN Repository Import.

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

Search

Context Navigation

ZOO Sponsors

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

Become a sponsor !

Knowledge partners

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

Become a knowledge partner

Related links

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