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

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

ZOO-Kernel updates and bug fixes :

  • Fixing gestion of RawDataOutput? when the Service return SERVICE_FAILED an ExceptionReport? was returned to the client.
  • Use gcc when compiling service_internal.c to avoid strange error messages about switch ....
  • Function setMapInMaps was added in service.h to let users set the value of a specific map in a maps.
  • Fixing JavaScript? issue during the context destruction process.
  • Use the GetStatus? ZOO Service when it is available on the local installation for statusLocation.
  • Add some comments for ServiceStarted?, ServiceSucceeded?, ServiceFailed? and ServiceAccepted?.
  • Dynamic creation of a lenv maps in the main configuration file maps, containing the current status and a sid (Service ID). Those informations can be used later by the GetStatus? Service to let user check the on-going status during the Service runs.
  • Function updateStatus was added to service_internal.h which let the Services developers set the current percentCompleted value.

ZOO-Service updates and bug fixes :

  • Add GetStatus? Service and its demo longProcess Service. All are in the wps_status.zo Services Provider.
  • Use the setMapInMaps in the base-vect-ops code to enhance readibility.

ZOO-API updates :

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

Search

ZOO Sponsors

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

Become a sponsor !

Knowledge partners

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

Become a knowledge partner

Related links

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