Revert "Export"
[framework/web/web-ui-fw.git] / libs / js / jquery-geo-1.0a4 / js / jquery.geo.core.js
1 (function ($, window, undefined) {
2   var pos_oo = Number.POSITIVE_INFINITY,
3       neg_oo = Number.NEGATIVE_INFINITY;
4
5   $.geo = {
6     //
7     // utility functions
8     //
9
10     _allCoordinates: function (geom) {
11       // return array of all positions in all geometries of geom
12       // not in JTS
13       var geometries = this._flatten(geom),
14           curGeom = 0,
15           result = [];
16
17       for (; curGeom < geometries.length; curGeom++) {
18         var coordinates = geometries[curGeom].coordinates,
19             isArray = coordinates && $.isArray(coordinates[0]),
20             isDblArray = isArray && $.isArray(coordinates[0][0]),
21             isTriArray = isDblArray && $.isArray(coordinates[0][0][0]),
22             i, j, k;
23
24         if (!isTriArray) {
25           if (!isDblArray) {
26             if (!isArray) {
27               coordinates = [coordinates];
28             }
29             coordinates = [coordinates];
30           }
31           coordinates = [coordinates];
32         }
33
34         for (i = 0; i < coordinates.length; i++) {
35           for (j = 0; j < coordinates[i].length; j++) {
36             for (k = 0; k < coordinates[i][j].length; k++) {
37               result.push(coordinates[i][j][k]);
38             }
39           }
40         }
41       }
42       return result;
43     },
44
45     _isGeodetic: function( coords ) {
46       // returns true if the first coordinate it can find is geodetic
47
48       while ( $.isArray( coords ) ) {
49         if ( coords.length > 1 && ! $.isArray( coords[ 0 ] ) ) {
50           return ( coords[ 0 ] >= -180 && coords[ 0 ] <= 180 && coords[ 1 ] >= -85 && coords[ 1 ] <= 85 );
51         } else {
52           coords = coords[ 0 ];
53         }
54       }
55
56       return false;
57     },
58
59     //
60     // bbox functions
61     //
62
63     center: function (bbox, _ignoreGeo /* Internal Use Only */) {
64       // Envelope.centre in JTS
65       // bbox only, use centroid for geom
66       var wasGeodetic = false;
67       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
68         wasGeodetic = true;
69         bbox = $.geo.proj.fromGeodetic(bbox);
70       }
71
72       var center = [(bbox[0] + bbox[2]) / 2, (bbox[1] + bbox[3]) / 2];
73       return wasGeodetic ? $.geo.proj.toGeodetic(center) : center;
74     },
75
76     expandBy: function (bbox, dx, dy, _ignoreGeo /* Internal Use Only */) {
77       var wasGeodetic = false;
78       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
79         wasGeodetic = true;
80         bbox = $.geo.proj.fromGeodetic(bbox);
81       }
82
83       bbox = [bbox[0] - dx, bbox[1] - dy, bbox[2] + dx, bbox[3] + dy];
84       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
85     },
86
87     height: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
88       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
89         bbox = $.geo.proj.fromGeodetic(bbox);
90       }
91
92       return bbox[3] - bbox[1];
93     },
94
95     _in: function(bbox1, bbox2) {
96       return bbox1[0] <= bbox2[0] &&
97              bbox1[1] <= bbox2[1] &&
98              bbox1[2] >= bbox2[2] &&
99              bbox1[3] >= bbox2[3];
100     },
101
102     _bboxDisjoint: function( bbox1, bbox2 ) {
103       return bbox2[ 0 ] > bbox1[ 2 ] || 
104              bbox2[ 2 ] < bbox1[ 0 ] || 
105              bbox2[ 1 ] > bbox1[ 3 ] ||
106              bbox2[ 3 ] < bbox1[ 1 ];
107     },
108
109     reaspect: function (bbox, ratio, _ignoreGeo /* Internal Use Only */ ) {
110       // not in JTS
111       var wasGeodetic = false;
112       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
113         wasGeodetic = true;
114         bbox = $.geo.proj.fromGeodetic(bbox);
115       }
116
117       var width = this.width(bbox, true),
118           height = this.height(bbox, true),
119           center = this.center(bbox, true),
120           dx, dy;
121
122       if (width != 0 && height != 0 && ratio > 0) {
123         if (width / height > ratio) {
124           dx = width / 2;
125           dy = dx / ratio;
126         } else {
127           dy = height / 2;
128           dx = dy * ratio;
129         }
130
131         bbox = [center[0] - dx, center[1] - dy, center[0] + dx, center[1] + dy];
132       }
133
134       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
135     },
136
137     recenter: function( bbox, center, _ignoreGeo /* Internal Use Only */ ) {
138       // not in JTS
139       var wasGeodetic = false;
140       if ( !_ignoreGeo && $.geo.proj ) {
141         if ( this._isGeodetic( bbox ) ) {
142           wasGeodetic = true;
143           bbox = $.geo.proj.fromGeodetic(bbox);
144         }
145
146         if ( this._isGeodetic( center ) ) {
147           center = $.geo.proj.fromGeodetic(center);
148         }
149       }
150
151       var halfWidth = ( bbox[ 2 ] - bbox[ 0 ] ) / 2,
152           halfHeight = ( bbox[ 3 ] - bbox[ 1 ] ) / 2;
153
154       bbox = [
155         center[ 0 ] - halfWidth,
156         center[ 1 ] - halfHeight,
157         center[ 0 ] + halfWidth,
158         center[ 1 ] + halfHeight
159       ];
160
161       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
162     },
163
164     scaleBy: function ( bbox, scale, _ignoreGeo /* Internal Use Only */ ) {
165       // not in JTS
166       var wasGeodetic = false;
167       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
168         wasGeodetic = true;
169         bbox = $.geo.proj.fromGeodetic(bbox);
170       }
171
172       var c = this.center(bbox, true),
173           dx = (bbox[2] - bbox[0]) * scale / 2,
174           dy = (bbox[3] - bbox[1]) * scale / 2;
175
176       bbox = [c[0] - dx, c[1] - dy, c[0] + dx, c[1] + dy];
177
178       return wasGeodetic ? $.geo.proj.toGeodetic(bbox) : bbox;
179     },
180
181     width: function (bbox, _ignoreGeo /* Internal Use Only */ ) {
182       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( bbox ) ) {
183         bbox = $.geo.proj.fromGeodetic(bbox);
184       }
185
186       return bbox[2] - bbox[0];
187     },
188
189     //
190     // geometry functions
191     //
192
193     // bbox (Geometry.getEnvelope in JTS)
194
195     bbox: function ( geom, _ignoreGeo /* Internal Use Only */ ) {
196       if ( !geom ) {
197         return undefined;
198       } else if ( geom.bbox ) {
199         result = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.bbox ) ) ? $.geo.proj.fromGeodetic( geom.bbox ) : geom.bbox;
200       } else {
201         result = [ pos_oo, pos_oo, neg_oo, neg_oo ];
202
203         var coordinates = this._allCoordinates( geom ),
204             curCoord = 0;
205
206         if ( coordinates.length == 0 ) {
207           return undefined;
208         }
209
210         var wasGeodetic = false;
211         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coordinates ) ) {
212           wasGeodetic = true;
213           coordinates = $.geo.proj.fromGeodetic( coordinates );
214         }
215
216         for ( ; curCoord < coordinates.length; curCoord++ ) {
217           result[0] = Math.min(coordinates[curCoord][0], result[0]);
218           result[1] = Math.min(coordinates[curCoord][1], result[1]);
219           result[2] = Math.max(coordinates[curCoord][0], result[2]);
220           result[3] = Math.max(coordinates[curCoord][1], result[3]);
221         }
222       }
223
224       return wasGeodetic ? $.geo.proj.toGeodetic(result) : result;
225     },
226
227     // centroid
228     
229     centroid: function( geom, _ignoreGeo /* Internal Use Only */ ) {
230       switch (geom.type) {
231         case "Point":
232           return $.extend({}, geom);
233
234         case "LineString":
235         case "Polygon":
236           var a = 0,
237               c = [0, 0],
238               coords = $.merge( [ ], geom.type == "Polygon" ? geom.coordinates[0] : geom.coordinates ),
239               i = 1, j, n;
240
241           var wasGeodetic = false;
242           if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( coords ) ) {
243             wasGeodetic = true;
244             coords = $.geo.proj.fromGeodetic(coords);
245           }
246
247           //if (coords[0][0] != coords[coords.length - 1][0] || coords[0][1] != coords[coords.length - 1][1]) {
248           //  coords.push(coords[0]);
249           //}
250
251           for (; i <= coords.length; i++) {
252             j = i % coords.length;
253             n = (coords[i - 1][0] * coords[j][1]) - (coords[j][0] * coords[i - 1][1]);
254             a += n;
255             c[0] += (coords[i - 1][0] + coords[j][0]) * n;
256             c[1] += (coords[i - 1][1] + coords[j][1]) * n;
257           }
258
259           if (a == 0) {
260             if (coords.length > 0) {
261               c[0] = coords[0][0];
262               c[1] = coords[0][1];
263               return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
264             } else {
265               return undefined;
266             }
267           }
268
269           a *= 3;
270           c[0] /= a;
271           c[1] /= a;
272
273           return { type: "Point", coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c };
274       }
275       return undefined;
276     },
277
278     // contains
279
280     contains: function (geom1, geom2) {
281       if (geom1.type != "Polygon") {
282         return false;
283       }
284
285       switch (geom2.type) {
286         case "Point":
287           return this._containsPolygonPoint(geom1.coordinates, geom2.coordinates);
288
289         case "LineString":
290           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates);
291
292         case "Polygon":
293           return this._containsPolygonLineString(geom1.coordinates, geom2.coordinates[0]);
294
295         default:
296           return false;
297       }
298     },
299
300     _containsPolygonPoint: function (polygonCoordinates, pointCoordinate) {
301       if (polygonCoordinates.length == 0 || polygonCoordinates[0].length < 4) {
302         return false;
303       }
304
305       var rayCross = 0,
306           a = polygonCoordinates[0][0],
307           i = 1,
308           b,
309           x;
310
311       for (; i < polygonCoordinates[0].length; i++) {
312         b = polygonCoordinates[0][i];
313
314         if ((a[1] <= pointCoordinate[1] && pointCoordinate[1] < b[1]) || (b[1] <= pointCoordinate[1] && pointCoordinate[1] < a[1]) && (pointCoordinate[0] < a[0] || pointCoordinate[0] < b[0])) {
315           x = a[0] + (b[0] - a[0]) * (pointCoordinate[1] - a[1]) / (b[1] - a[1]);
316
317           if (x > pointCoordinate[0]) {
318             rayCross++;
319           }
320         }
321
322         a = b;
323       }
324
325       return rayCross % 2 == 1;
326     },
327
328     _containsPolygonLineString: function (polygonCoordinates, lineStringCoordinates) {
329       for (var i = 0; i < lineStringCoordinates.length; i++) {
330         if (!this._containsPolygonPoint(polygonCoordinates, lineStringCoordinates[i])) {
331           return false;
332         }
333       }
334       return true;
335     },
336
337     // distance
338
339     distance: function ( geom1, geom2, _ignoreGeo /* Internal Use Only */ ) {
340       var geom1CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom1.coordinates ) ) ? $.geo.proj.fromGeodetic(geom1.coordinates) : geom1.coordinates,
341           geom2CoordinatesProjected = ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom2.coordinates ) ) ? $.geo.proj.fromGeodetic(geom2.coordinates) : geom2.coordinates;
342
343       switch (geom1.type) {
344         case "Point":
345           switch (geom2.type) {
346             case "Point":
347               return this._distancePointPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
348             case "LineString":
349               return this._distanceLineStringPoint(geom2CoordinatesProjected, geom1CoordinatesProjected);
350             case "Polygon":
351               return this._containsPolygonPoint(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
352             default:
353               return undefined;
354           }
355           break;
356
357         case "LineString":
358           switch (geom2.type) {
359             case "Point":
360               return this._distanceLineStringPoint(geom1CoordinatesProjected, geom2CoordinatesProjected);
361             case "LineString":
362               return this._distanceLineStringLineString(geom1CoordinatesProjected, geom2CoordinatesProjected);
363             case "Polygon":
364               return this._containsPolygonLineString(geom2CoordinatesProjected, geom1CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom2CoordinatesProjected[0], geom1CoordinatesProjected);
365             default:
366               return undefined;
367           }
368           break;
369
370         case "Polygon":
371           switch (geom2.type) {
372             case "Point":
373               return this._containsPolygonPoint(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringPoint(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
374             case "LineString":
375               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected);
376             case "Polygon":
377               return this._containsPolygonLineString(geom1CoordinatesProjected, geom2CoordinatesProjected[0]) ? 0 : this._distanceLineStringLineString(geom1CoordinatesProjected[0], geom2CoordinatesProjected[0]);
378             default:
379               return undefined;
380           }
381           break;
382       }
383     },
384
385     _distancePointPoint: function (coordinate1, coordinate2) {
386       var dx = coordinate2[0] - coordinate1[0],
387           dy = coordinate2[1] - coordinate1[1];
388       return Math.sqrt((dx * dx) + (dy * dy));
389     },
390
391     _distanceLineStringPoint: function (lineStringCoordinates, pointCoordinate) {
392       var minDist = pos_oo;
393
394       if (lineStringCoordinates.length > 0) {
395         var a = lineStringCoordinates[0],
396
397             apx = pointCoordinate[0] - a[0],
398             apy = pointCoordinate[1] - a[1];
399
400         if (lineStringCoordinates.length == 1) {
401           return Math.sqrt(apx * apx + apy * apy);
402         } else {
403           for (var i = 1; i < lineStringCoordinates.length; i++) {
404             var b = lineStringCoordinates[i],
405
406                 abx = b[0] - a[0],
407                 aby = b[1] - a[1],
408                 bpx = pointCoordinate[0] - b[0],
409                 bpy = pointCoordinate[1] - b[1],
410
411                 d = this._distanceSegmentPoint(abx, aby, apx, apy, bpx, bpy);
412
413             if (d == 0) {
414               return 0;
415             }
416
417             if (d < minDist) {
418               minDist = d;
419             }
420
421             a = b;
422             apx = bpx;
423             apy = bpy;
424           }
425         }
426       }
427
428       return Math.sqrt(minDist);
429     },
430
431     _distanceSegmentPoint: function (abx, aby, apx, apy, bpx, bpy) {
432       var dot1 = abx * apx + aby * apy;
433
434       if (dot1 <= 0) {
435         return apx * apx + apy * apy;
436       }
437
438       var dot2 = abx * abx + aby * aby;
439
440       if (dot1 >= dot2) {
441         return bpx * bpx + bpy * bpy;
442       }
443
444       return apx * apx + apy * apy - dot1 * dot1 / dot2;
445     },
446
447     _distanceLineStringLineString: function (lineStringCoordinates1, lineStringCoordinates2) {
448       var minDist = pos_oo;
449       for (var i = 0; i < lineStringCoordinates2.length; i++) {
450         minDist = Math.min(minDist, this._distanceLineStringPoint(lineStringCoordinates1, lineStringCoordinates2[i]));
451       }
452       return minDist;
453     },
454
455     // buffer
456
457     _buffer: function( geom, distance, _ignoreGeo /* Internal Use Only */ ) {
458       var wasGeodetic = false,
459           coords = geom.coordinates;
460
461       if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( geom.coordinates ) ) {
462         wasGeodetic = true;
463         coords = $.geo.proj.fromGeodetic( geom.coordinates );
464       }
465
466       switch ( geom.type ) {
467         case "Point":
468           var resultCoords = [],
469               slices = 180,
470               i = 0,
471               a;
472
473           for ( ; i <= slices; i++ ) {
474             a = ( i * 360 / slices ) * ( Math.PI / 180 );
475             resultCoords.push( [
476               coords[ 0 ] + Math.cos( a ) * distance,
477               coords[ 1 ] + Math.sin( a ) * distance
478             ] );
479           }
480
481           return {
482             type: "Polygon",
483             coordinates: [ ( wasGeodetic ? $.geo.proj.toGeodetic( resultCoords ) : resultCoords ) ]
484           };
485
486           break;
487
488         default:
489           return undefined;
490       }
491     },
492
493     
494     //
495     // feature
496     //
497
498     _flatten: function (geom) {
499       // return an array of all basic geometries
500       // not in JTS
501       var geometries = [],
502           curGeom = 0;
503       switch (geom.type) {
504         case "Feature":
505           $.merge(geometries, this._flatten(geom.geometry));
506           break;
507
508         case "FeatureCollection":
509           for (; curGeom < geom.features.length; curGeom++) {
510             $.merge(geometries, this._flatten(geom.features[curGeom].geometry));
511           }
512           break;
513
514         case "GeometryCollection":
515           for (; curGeom < geom.geometries.length; curGeom++) {
516             $.merge(geometries, this._flatten(geom.geometries[curGeom]));
517           }
518           break;
519
520         default:
521           geometries[0] = geom;
522           break;
523       }
524       return geometries;
525     },
526
527     length: function( geom, _ignoreGeo /* Internal Use Only */ ) {
528       var sum = 0,
529           lineStringCoordinates,
530           i = 1, dx, dy;
531
532       switch ( geom.type ) {
533         case "Point":
534           return 0;
535
536         case "LineString":
537           lineStringCoordinates = geom.coordinates;
538           break;
539
540         case "Polygon":
541           lineStringCoordinates = geom.coordinates[ 0 ];
542           break;
543       }
544
545       if ( lineStringCoordinates ) {
546         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
547           lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
548         }
549
550         for ( ; i < lineStringCoordinates.length; i++ ) {
551           dx = lineStringCoordinates[ i ][0] - lineStringCoordinates[ i - 1 ][0];
552           dy = lineStringCoordinates[ i ][1] - lineStringCoordinates[ i - 1 ][1];
553           sum += Math.sqrt((dx * dx) + (dy * dy));
554         }
555
556         return sum;
557       }
558
559       // return undefined;
560     },
561
562     area: function( geom, _ignoreGeo /* Internal Use Only */ ) {
563       var sum = 0,
564           polygonCoordinates,
565           i = 1, j;
566
567       switch ( geom.type ) {
568         case "Point":
569         case "LineString":
570           return 0;
571
572         case "Polygon":
573           polygonCoordinates = geom.coordinates[ 0 ];
574           break;
575       }
576
577       if ( polygonCoordinates ) {
578         if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( polygonCoordinates ) ) {
579           polygonCoordinates = $.geo.proj.fromGeodetic( polygonCoordinates );
580         }
581
582         for ( ; i <= polygonCoordinates.length; i++) {
583           j = i %  polygonCoordinates.length;
584           sum += ( polygonCoordinates[ i - 1 ][ 0 ] - polygonCoordinates[ j ][ 0 ] ) * ( polygonCoordinates[ i - 1 ][ 1 ] + polygonCoordinates[ j ][ 1 ] ) / 2;
585         }
586
587         return Math.abs( sum );
588       }
589     },
590
591     pointAlong: function( geom, percentage, _ignoreGeo /* Internal Use Only */ ) {
592       var totalLength = 0,
593           previousPercentageSum = 0,
594           percentageSum = 0,
595           remainderPercentageSum,
596           len,
597           lineStringCoordinates,
598           segmentLengths = [],
599           i = 1, dx, dy,
600           c, c0, c1,
601           wasGeodetic = false;
602
603       switch ( geom.type ) {
604         case "Point":
605           return $.extend( { }, geom );
606
607         case "LineString":
608           lineStringCoordinates = geom.coordinates;
609           break;
610
611         case "Polygon":
612           lineStringCoordinates = geom.coordinates[ 0 ];
613           break;
614       }
615
616       if ( lineStringCoordinates ) {
617         if ( percentage === 0 ) {
618           return {
619             type: "Point",
620             coordinates: [ lineStringCoordinates[ 0 ][ 0 ], lineStringCoordinates[ 0 ][ 1 ] ]
621           };
622         } else if ( percentage === 1 ) {
623           i = lineStringCoordinates.length - 1;
624           return {
625             type: "Point",
626             coordinates: [ lineStringCoordinates[ i ][ 0 ], lineStringCoordinates[ i ][ 1 ] ]
627           };
628         } else {
629           if ( !_ignoreGeo && $.geo.proj && this._isGeodetic( lineStringCoordinates ) ) {
630             wasGeodetic = true;
631             lineStringCoordinates = $.geo.proj.fromGeodetic( lineStringCoordinates );
632           }
633
634           for ( ; i < lineStringCoordinates.length; i++ ) {
635             dx = lineStringCoordinates[ i ][ 0 ] - lineStringCoordinates[ i - 1 ][ 0 ];
636             dy = lineStringCoordinates[ i ][ 1 ] - lineStringCoordinates[ i - 1 ][ 1 ];
637             len = Math.sqrt((dx * dx) + (dy * dy));
638             segmentLengths.push( len );
639             totalLength += len;
640           }
641
642           for ( i = 0; i < segmentLengths.length && percentageSum < percentage; i++ ) {
643             previousPercentageSum = percentageSum;
644             percentageSum += ( segmentLengths[ i ] / totalLength );
645           }
646
647           remainderPercentageSum = percentage - previousPercentageSum;
648
649           c0 = lineStringCoordinates[ i - 1 ];
650           c1 = lineStringCoordinates[ i ];
651
652           c = [
653             c0[ 0 ] + ( remainderPercentageSum * ( c1[ 0 ] - c0[ 0 ] ) ),
654             c0[ 1 ] + ( remainderPercentageSum * ( c1[ 1 ] - c0[ 1 ] ) )
655           ];
656
657           return {
658             type: "Point",
659             coordinates: wasGeodetic ? $.geo.proj.toGeodetic(c) : c
660           };
661         }
662       }
663     },
664
665     //
666     // WKT functions
667     //
668
669     _WKT: (function () {
670       function pointToString(value) {
671         return "POINT " + pointToUntaggedString(value.coordinates);
672       }
673
674       function pointToUntaggedString(coordinates) {
675         if (!(coordinates && coordinates.length)) {
676           return "EMPTY";
677         } else {
678           return "(" + coordinates.join(" ") + ")";
679         }
680       }
681
682       function lineStringToString(value) {
683         return "LINESTRING " + lineStringToUntaggedString(value.coordinates);
684       }
685
686       function lineStringToUntaggedString(coordinates) {
687         if (!(coordinates && coordinates.length)) {
688           return "EMPTY";
689         } else {
690           var points = []
691
692           for (var i = 0; i < coordinates.length; i++) {
693             points.push(coordinates[i].join(" "));
694           }
695
696           return "(" + points + ")";
697         }
698       }
699
700       function polygonToString(value) {
701         return "POLYGON " + polygonToUntaggedString(value.coordinates);
702       }
703
704       function polygonToUntaggedString(coordinates) {
705         if (!(coordinates && coordinates.length)) {
706           return "EMTPY";
707         } else {
708           var lineStrings = [];
709
710           for (var i = 0; i < coordinates.length; i++) {
711             lineStrings.push(lineStringToUntaggedString(coordinates[i]));
712           }
713
714           return "(" + lineStrings + ")";
715         }
716       }
717
718       function multiPointToString(value) {
719         return "MULTIPOINT " + lineStringToUntaggedString(value.coordinates);
720       }
721
722       function multiLineStringToString(value) {
723         return "MULTILINSTRING " + polygonToUntaggedString(value.coordinates);
724       }
725
726       function multiPolygonToString(value) {
727         return "MULTIPOLYGON " + multiPolygonToUntaggedString(value.coordinates);
728       }
729
730       function multiPolygonToUntaggedString(coordinates) {
731         if (!(coordinates && coordinates.length)) {
732           return "EMPTY";
733         } else {
734           var polygons = [];
735           for (var i = 0; i < coordinates.length; i++) {
736             polygons.push(polygonToUntaggedString(coordinates[i]));
737           }
738           return "(" + polygons + ")";
739         }
740       }
741
742       function geometryCollectionToString(value) {
743         return "GEOMETRYCOLLECTION " + geometryCollectionToUntaggedString(value.geometries);
744       }
745
746       function geometryCollectionToUntaggedString(geometries) {
747         if (!(geometries && geometries.length)) {
748           return "EMPTY";
749         } else {
750           var geometryText = [];
751           for (var i = 0; i < geometries.length; i++) {
752             geometryText.push(stringify(geometries[i]));
753           }
754           return "(" + geometries + ")";
755         }
756       }
757
758       function stringify(value) {
759         if (!(value && value.type)) {
760           return "";
761         } else {
762           switch (value.type) {
763             case "Point":
764               return pointToString(value);
765
766             case "LineString":
767               return lineStringToString(value);
768
769             case "Polygon":
770               return polygonToString(value);
771
772             case "MultiPoint":
773               return multiPointToString(value);
774
775             case "MultiLineString":
776               return multiLineStringToString(value);
777
778             case "MultiPolygon":
779               return multiPolygonToString(value);
780
781             case "GeometryCollection":
782               return geometryCollectionToString(value);
783
784             default:
785               return "";
786           }
787         }
788       }
789
790       function pointParseUntagged(wkt) {
791         var pointString = wkt.match( /\(\s*([\d\.-]+)\s+([\d\.-]+)\s*\)/ );
792         return pointString && pointString.length > 2 ? {
793           type: "Point",
794           coordinates: [
795             parseFloat(pointString[1]),
796             parseFloat(pointString[2])
797           ]
798         } : null;
799       }
800
801       function lineStringParseUntagged(wkt) {
802         var lineString = wkt.match( /\s*\((.*)\)/ ),
803             coords = [],
804             pointStrings,
805             pointParts,
806             i = 0;
807
808         if ( lineString.length > 1 ) {
809           pointStrings = lineString[ 1 ].match( /[\d\.-]+\s+[\d\.-]+/g );
810
811           for ( ; i < pointStrings.length; i++ ) {
812             pointParts = pointStrings[ i ].match( /\s*([\d\.-]+)\s+([\d\.-]+)\s*/ );
813             coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
814           }
815
816           return {
817             type: "LineString",
818             coordinates: coords
819           };
820         } else {
821           return null
822         }
823       }
824
825       function polygonParseUntagged(wkt) {
826         var polygon = wkt.match( /\s*\(\s*\((.*)\)\s*\)/ ),
827             coords = [],
828             pointStrings,
829             pointParts,
830             i = 0;
831
832         if ( polygon.length > 1 ) {
833           pointStrings = polygon[ 1 ].match( /[\d\.-]+\s+[\d\.-]+/g );
834
835           for ( ; i < pointStrings.length; i++ ) {
836             pointParts = pointStrings[ i ].match( /\s*([\d\.-]+)\s+([\d\.-]+)\s*/ );
837             coords[ i ] = [ parseFloat( pointParts[ 1 ] ), parseFloat( pointParts[ 2 ] ) ];
838           }
839
840           return {
841             type: "Polygon",
842             coordinates: [ coords ]
843           };
844         } else {
845           return null;
846         }
847       }
848
849       function parse(wkt) {
850         wkt = $.trim(wkt);
851
852         var typeIndex = wkt.indexOf( " " ),
853             untagged = wkt.substr( typeIndex + 1 );
854
855         switch (wkt.substr(0, typeIndex).toUpperCase()) {
856           case "POINT":
857             return pointParseUntagged( untagged );
858
859           case "LINESTRING":
860             return lineStringParseUntagged( untagged );
861
862           case "POLYGON":
863             return polygonParseUntagged( untagged );
864
865           default:
866             return null;
867         }
868       }
869
870       return {
871         stringify: stringify,
872
873         parse: parse
874       };
875     })(),
876
877     //
878     // projection functions
879     //
880
881     proj: (function () {
882       var halfPi = 1.5707963267948966192,
883           quarterPi = 0.7853981633974483096,
884           radiansPerDegree = 0.0174532925199432958,
885           degreesPerRadian = 57.295779513082320877,
886           semiMajorAxis = 6378137;
887
888       return {
889         fromGeodeticPos: function (coordinate) {
890           if (!coordinate) {
891             debugger;
892           }
893           return [
894             semiMajorAxis * coordinate[ 0 ] * radiansPerDegree,
895             semiMajorAxis * Math.log(Math.tan(quarterPi + coordinate[ 1 ] * radiansPerDegree / 2))
896           ];
897         },
898
899         fromGeodetic: function ( coordinates ) {
900           if ( ! $.geo._isGeodetic( coordinates ) ) {
901             return coordinates;
902           }
903
904           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
905               fromGeodeticPos = this.fromGeodeticPos;
906
907           if (!isMultiPointOrLineString && coordinates.length == 4) {
908             // bbox
909             var min = fromGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
910                 max = fromGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
911             return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
912           } else {
913             // geometry
914             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
915                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
916                 result = [ ],
917                 i, j, k;
918
919             if (!isMultiPolygon) {
920               if (!isMultiLineStringOrPolygon) {
921                 if (!isMultiPointOrLineString) {
922                   coordinates = [ coordinates ];
923                 }
924                 coordinates = [ coordinates ];
925               }
926               coordinates = [ coordinates ];
927             }
928
929             for ( i = 0; i < coordinates.length; i++ ) {
930               result[ i ] = [ ];
931               for ( j = 0; j < coordinates[ i ].length; j++ ) {
932                 result[ i ][ j ] = [ ];
933                 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
934                   result[ i ][ j ][ k ] = fromGeodeticPos(coordinates[ i ][ j ][ k ]);
935                 }
936               }
937             }
938
939             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
940           }
941         },
942
943         toGeodeticPos: function (coordinate) {
944           return [
945             (coordinate[ 0 ] / semiMajorAxis) * degreesPerRadian,
946             (halfPi - 2 * Math.atan(1 / Math.exp(coordinate[ 1 ] / semiMajorAxis))) * degreesPerRadian
947           ];
948         },
949
950         toGeodetic: function (coordinates) {
951           if ( $.geo._isGeodetic( coordinates ) ) {
952             return coordinates;
953           }
954
955           var isMultiPointOrLineString = $.isArray(coordinates[ 0 ]),
956               toGeodeticPos = this.toGeodeticPos;
957
958           if (!isMultiPointOrLineString && coordinates.length == 4) {
959             // bbox
960             var min = toGeodeticPos([ coordinates[ 0 ], coordinates[ 1 ] ]),
961                 max = toGeodeticPos([ coordinates[ 2 ], coordinates[ 3 ] ]);
962             return [ min[ 0 ], min[ 1 ], max[ 0 ], max[ 1 ] ];
963           } else {
964             // geometry
965             var isMultiLineStringOrPolygon = isMultiPointOrLineString && $.isArray(coordinates[ 0 ][ 0 ]),
966                 isMultiPolygon = isMultiLineStringOrPolygon && $.isArray(coordinates[ 0 ][ 0 ][ 0 ]),
967                 result = [ ];
968
969             if (!isMultiPolygon) {
970               if (!isMultiLineStringOrPolygon) {
971                 if (!isMultiPointOrLineString) {
972                   coordinates = [ coordinates ];
973                 }
974                 coordinates = [ coordinates ];
975               }
976               coordinates = [ coordinates ];
977             }
978
979             for ( i = 0; i < coordinates.length; i++ ) {
980               result[ i ] = [ ];
981               for ( j = 0; j < coordinates[ i ].length; j++ ) {
982                 result[ i ][ j ] = [ ];
983                 for ( k = 0; k < coordinates[ i ][ j ].length; k++ ) {
984                   result[ i ][ j ][ k ] = toGeodeticPos(coordinates[ i ][ j ][ k ]);
985                 }
986               }
987             }
988
989             return isMultiPolygon ? result : isMultiLineStringOrPolygon ? result[ 0 ] : isMultiPointOrLineString ? result[ 0 ][ 0 ] : result[ 0 ][ 0 ][ 0 ];
990           }
991         }
992       }
993     })(),
994
995     //
996     // service types (defined in other files)
997     //
998
999     _serviceTypes: {}
1000   }
1001 })(jQuery, this);
1002