]> git.mxchange.org Git - friendica.git/blob - view/theme/diabook/js/jquery.mapquery.core.js
Merge remote branch 'upstream/master'
[friendica.git] / view / theme / diabook / js / jquery.mapquery.core.js
1 /* Copyright (c) 2011 by MapQuery Contributors (see AUTHORS for
2  * full list of contributors). Published under the MIT license.
3  * See https://github.com/mapquery/mapquery/blob/master/LICENSE for the
4  * full text of the license. */
5 (function ($) {
6 /**
7 # jquery.mapquery.core.js
8 The main MapQuery file. It contains the MapQuery constructor, the MapQuery.Map
9 constructor and the MapQuery.Layer constructor.
10
11
12 ### *$('selector')*.`mapQuery([options])`
13 _version added 0.1_
14 ####**Description**: initialise MapQuery and associate it with
15 the matched element
16
17 **options**  an object of key-value pairs with options for the map. Possible
18 pairs are:
19
20  * **layers** (array of MapQuery.Layer *or* MapQuery.Layer): Either an array 
21  or a single layer that should be added to the map
22  * **center** ({position: [x,y], zoom: z(int), box: [llx,lly,urx,ury]}):
23  Initially go to a certain location. At least one layer (in the `layers`
24  option) needs to be specified.
25
26 > Returns: $('selector') (jQuery object)
27
28
29 We can initialise MapQuery without any options, or for instance pass in a layer
30 object. The mapQuery function returns a jQuery object, to access the mapQuery object retrieve
31 the 'mapQuery' data object.
32
33      var map = $('#map').mapQuery(); //create an empty map
34      var map = $('#map').mapQuery({layers:[{type:'osm'}]); //create a map with osm
35
36      var mq = map.data('mapQuery'); //get the MapQuery object
37  */
38 $.MapQuery = $.MapQuery || {};
39
40 /**
41
42 ---
43
44 #MapQuery.Map
45
46 The MapQuery.Map object. It is automatically constructed from the options
47 given in the `mapQuery([options])` constructor. The Map object is refered
48 to as _map_ in the documentation.
49  */
50 $.MapQuery.Map = function(element, options) {
51     var self = this;
52     //If there are a maxExtent and a projection other than Spherical Mercator
53     //automagically set maxResolution if it is not set
54     //TODO smo 20110614: put maxExtent and maxResolution setting in the
55     //proper option building routine
56     if(options){
57     if(!options.maxResolution&&options.maxExtent&&options.projection){
58         options.maxResolution = (options.maxExtent[2]-options.maxExtent[0])/256;
59     }}
60     this.options = $.extend({}, new $.fn.mapQuery.defaults.map(), options);
61
62     this.element = element;
63     // TODO vmx 20110609: do proper options building
64     // TODO SMO 20110616: make sure that all projection strings are uppercase
65     // smo 20110620: you need the exact map options in the overviewmap widget
66     // as such we need to preserve them
67     this.olMapOptions = $.extend({}, this.options);
68     delete this.olMapOptions.layers;
69     delete this.olMapOptions.maxExtent;
70     delete this.olMapOptions.zoomToMaxExtent;
71     delete this.olMapOptions.center;
72
73     //TODO SMO20110630 the maxExtent is in mapprojection, decide whether or
74     //not we need to change it to displayProjection
75     this.maxExtent = this.options.maxExtent;
76     this.olMapOptions.maxExtent = new OpenLayers.Bounds(
77     this.maxExtent[0],this.maxExtent[1],this.maxExtent[2],this.maxExtent[3]);
78
79
80     this.projection = this.options.projection;
81     this.displayProjection = this.options.displayProjection;
82
83     OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
84     OpenLayers.Util.onImageLoadErrorColor = "transparent";
85
86     // create the OpenLayers Map
87     this.olMap = new OpenLayers.Map(this.element[0], this.olMapOptions);
88
89     //OpenLayers doesn't want to return a maxExtent when there is no baselayer
90     //set (eg on an empty map, so we create a fake baselayer
91     this.olMap.addLayer(new OpenLayers.Layer('fake', {baseLayer: true}));
92
93     // Keep IDs of vector layer for select feature control
94     this.vectorLayers = [];
95     this.selectFeatureControl = null;
96     // Counts up to create unique IDs
97     this.idCounter = 0;
98
99     element.data('mapQuery', this);
100     this.layersList = {};
101
102     // To bind and trigger jQuery events
103     this.events = $({});
104
105     this.handlers = {
106         // Triggers the jQuery events, after the OpenLayers events
107         // happened without any further processing
108         simple: function(data) {
109             this.trigger(data.type);
110         }
111     };
112
113     // MapQuery doesn't bind all OpenLayers events automatically,
114     // but just the ones that make sense.
115     // Events that are left out intensionally are:
116     //  - changebaselayer: MapQuery doesn't have the concept of base layers
117     //  - mouseover, mouseout, mousemove: Handle those with jQuery on the
118     //        DOM level
119     // Some events can be triggered by MapQuery without listening to the
120     // OpenLayers events. This only works for events that are triggered
121     // by functionality that MapQuery implements in some custom way, e.g.
122     // (pre)addlayer, (pre)removelayer, changelayer.
123     // TODO vmx 20120309: Proper docs for the events, here's some quickly
124     // written info:
125     //  - generally spoken, the map events follow the OpeLayer events
126     //  - preaddlayer, movestart, move, moveend, zoomend: no additional
127     //        argument
128     //  - addlayer, preremovelayer, removelayer: layer as additional argument
129     //  - changelayer: layer and the property that changed as additional
130     //        argument. Possible values for the property are: position (in
131     //        the layer stack), opacity, visibility
132     //        Currently this event is always fired, even if the property
133     //        was only meant to be changed, but wasn't exctually changed.
134     //        I.e. that the event is fired even if you call
135     //        `layer.visible(true)` although the layer is already visible.
136     //        I'm (vmx) not sure if we want to change that :)
137     this.olMap.events.on({
138         scope: this,
139         movestart: this.handlers.simple,
140         move: this.handlers.simple,
141         moveend: this.handlers.simple,
142         zoomend: this.handlers.simple
143     });
144
145     // Add layers to the map
146     if (this.options.layers!==undefined) {
147         this.layers(this.options.layers);
148         // You can only go to some location if there were layers added
149         if (this.options.center!==undefined) {
150             this.center(this.options.center);
151         }
152     }
153
154     // zoom to the maxExtent of the map if no precise location was specified
155     if (this.options.zoomToMaxExtent && this.options.center===undefined) {
156         this.olMap.zoomToMaxExtent();
157     }
158 };
159
160 $.MapQuery.Map.prototype = {
161  /**
162 ###*map*.`layers([options])`
163 _version added 0.1_
164 ####**Description**: get/set the layers of the map
165
166 **options** an object of key-value pairs with options to create one or
167 more layers
168
169 >Returns: [layer] (array of MapQuery.Layer) _or_ false
170
171
172 The `.layers()` method allows us to attach layers to a mapQuery object. It takes
173 an options object with layer options. To add multiple layers, create an array of
174 layer options objects. If an options object is given, it will return the
175 resulting layer(s). We can also use it to retrieve all layers currently attached
176 to the map.
177
178 When adding layers, those are returned. If the creation is cancled by returning
179 `false` in the `preaddlayer` event, this function returns `false` to
180 intentionally break the chain instead of hiding errors subtly).
181
182
183      var osm = map.layers({type:'osm'}); //add an osm layer to the map
184      var layers = map.layers(); //get all layers of the map
185
186      */
187     layers: function(options) {
188         //var o = $.extend({}, options);
189         var self = this;
190         switch(arguments.length) {
191         case 0:
192             return this._allLayers();
193         case 1:
194             if (!$.isArray(options)) {
195                 return this._addLayer(options);
196             }
197             else {
198                 return $.map(options, function(layer) {
199                     return self._addLayer(layer);
200                 }).reverse();
201             }
202             break;
203         default:
204             throw('wrong argument number');
205         }
206     },
207     // Returns all layers as an array, sorted by there order in the map. First
208     // element in the array is the topmost layer
209     _allLayers: function() {
210         var layers = [];
211         $.each(this.layersList, function(id, layer) {
212             var item = [layer.position(), layer];
213             layers.push(item);
214         });
215         var sorted = layers.sort( function compare(a, b) {
216             return a[0] - b[0];
217         });
218         var result = $.map(sorted, function(item) {
219             return item[1];
220         });
221         return result.reverse();
222     },
223     _addLayer: function(options) {
224         var id = this._createId();
225         var layer = new $.MapQuery.Layer(this, id, options);
226         // NOTE vmx 20120305: Not sure if this is a good idea, or if it would
227         //     be better to include `options` with the preaddlayer event
228         if (this._triggerReturn('preaddlayer', [layer])===false) {
229             return false;
230         }
231         this.olMap.addLayer(layer.olLayer);
232
233         this.layersList[id] = layer;
234         if (layer.isVector) {
235             this.vectorLayers.push(id);
236         }
237         this._updateSelectFeatureControl(this.vectorLayers);
238
239         layer.trigger('addlayer');
240         return layer;
241     },
242     // Creates a new unique ID for a layer
243     _createId: function() {
244         return 'mapquery_' + this.idCounter++;
245     },
246     _removeLayer: function(id) {
247         var layer = this.layersList[id];
248         if (this._triggerReturn('preremovelayer', [layer])===false) {
249             return false;
250         }
251
252         // remove id from vectorlayer if it is there list
253         this.vectorLayers = $.grep(this.vectorLayers, function(elem) {
254             return elem != id;
255         });
256         this._updateSelectFeatureControl(this.vectorLayers);
257         this.olMap.removeLayer(layer.olLayer);
258
259         // XXX vmx: shouldn't the layer be destroyed() properly?
260         delete this.layersList[id];
261
262         layer.trigger('removelayer');
263         return this;
264     },
265 /**
266  ###*map*.`center([options])`
267 _version added 0.1_
268 ####**Description**: get/set the extent, zoom and position of the map
269
270  * **position** the position as [x,y] in displayProjection (default EPSG:4326)
271 to center the map at
272  * **zoom** the zoomlevel as integer to zoom the map to
273  * **box** an array with the lower left x, lower left y, upper right x,
274 upper right y to zoom the map to,
275 this will take precedent when conflicting with any of the above values
276  * **projection** the projection the coordinates are in, default is
277 the displayProjection
278
279 >Returns: {position: [x,y], zoom: z(int), box: [llx,lly,urx,ury]}
280
281
282 The `.center()` method allows us to move to map to a specific zoom level,
283 specific position or a specific extent. We can specify the projection of the
284 coordinates to override the displayProjection. For instance you want to show
285 the coordinates in 4326, but you have a dataset in EPSG:28992
286 (dutch projection). We can also retrieve the current zoomlevel, position and
287 extent from the map. The coordinates are returned in displayProjection.
288
289
290      var center = map.center(); //get the current zoom, position and extent
291      map.center({zoom:4}); //zoom to zoomlevel 4
292      map.center({position:[5,52]}); //pan to point 5,52
293      map.center(box:[-180,-90,180,90]); //zoom to the box -180,-900,180,90
294      //pan to point 125000,485000 in dutch projection
295      map.center({position:[125000,485000],projection:'EPSG:28992'});
296  */
297     center: function (options) {
298         var position;
299         var mapProjection = new OpenLayers.Projection(this.projection);
300         // Determine source projection
301         var sourceProjection = null;
302         var zoom;
303         var box;
304         if(options && options.projection) {
305             sourceProjection = options.projection.CLASS_NAME ===
306             'OpenLayers.Projection' ? options.projection :
307             new OpenLayers.Projection(options.projection);
308         } else {
309             var displayProjection = this.displayProjection;
310             if(!displayProjection) {
311                 // source == target
312                 sourceProjection = new OpenLayers.Projection('EPSG:4326');
313             } else {
314                 sourceProjection = displayProjection.CLASS_NAME ===
315             'OpenLayers.Projection' ? displayProjection :
316             new OpenLayers.Projection(displayProjection);
317             }
318         }
319
320         // Get the current position
321         if (arguments.length===0) {
322             position = this.olMap.getCenter();
323             zoom = this.olMap.getZoom();
324             box = this.olMap.getExtent();
325
326             if (!mapProjection.equals(sourceProjection)) {
327                 position.transform(mapProjection, sourceProjection);
328             }
329             box.transform(mapProjection,sourceProjection);
330             box = box!==null ? box.toArray() : [];
331             return {
332                 position: [position.lon, position.lat],
333                 zoom: this.olMap.getZoom(),
334                 box: box
335             };
336         }
337
338         // Zoom to the extent of the box
339         if (options.box!==undefined) {
340             box = new OpenLayers.Bounds(
341                 options.box[0], options.box[1],options.box[2], options.box[3]);
342             if (!mapProjection.equals(sourceProjection)) {
343                 box.transform(sourceProjection,mapProjection);
344             }
345             this.olMap.zoomToExtent(box);
346
347         }
348         // Only zoom is given
349         else if (options.position===undefined) {
350             this.olMap.zoomTo(options.zoom);
351         }
352         // Position is given, zoom maybe as well
353         else {
354             position = new OpenLayers.LonLat(options.position[0],
355                                              options.position[1]);
356             if (!mapProjection.equals(sourceProjection)) {
357                 position.transform(sourceProjection, mapProjection);
358             }
359             // options.zoom might be undefined, so we are good to
360             // pass it on
361             this.olMap.setCenter(position, options.zoom);
362         }
363     },
364     _updateSelectFeatureControl: function(layerIds) {
365         var vectorLayers = [];
366         var layersList = this.layersList;
367         if (this.selectFeatureControl!==null) {
368             this.selectFeatureControl.deactivate();
369             this.selectFeatureControl.destroy();
370         }
371         $.each(layerIds, function() {
372             vectorLayers.push(layersList[this].olLayer);
373         });
374         this.selectFeatureControl = new OpenLayers.Control.SelectFeature(
375             vectorLayers);
376         this.olMap.addControl(this.selectFeatureControl);
377         this.selectFeatureControl.activate();
378     },
379     // This function got a bit too clever. The reason is, that jQuery's
380     // bind() is overloaded with so many possible combinations of arguments.
381     // And, of course, MapQuery wants to support them all
382     // The essence of the function is to wrap the original callback into
383     // the correct scope
384     bind: function(types, data, fn) {
385         var self = this;
386
387         // A map of event/handle pairs, wrap each of them
388         if(arguments.length===1) {
389             var wrapped = {};
390             $.each(types, function(type, fn) {
391                 wrapped[type] = function() {
392                     return fn.apply(self, arguments);
393                 };
394             });
395             this.events.bind.apply(this.events, [wrapped]);
396         }
397         else {
398             var args = [types];
399             // Only callback given, but no data (types, fn), hence
400             // `data` is the function
401             if(arguments.length===2) {
402                 fn = data;
403             }
404             else {
405                 if (!$.isFunction(fn)) {
406                     throw('bind: you might have a typo in the function name');
407                 }
408                 // Callback and data given (types, data, fn), hence include
409                 // the data in the argument list
410                 args.push(data);
411             }
412
413             args.push(function() {
414                 return fn.apply(self, arguments);
415             });
416
417             this.events.bind.apply(this.events, args);
418         }
419
420         //this.events.bind.call(this.events, types, function() {
421         //    data.apply(self, arguments);
422         //});
423         //this.events.bind.call(this.events, types, function() {
424         //    data.apply(self, arguments);
425         //});
426
427         //this.events.bind.apply(this.events, arguments);
428         //this.events.bind.call(this.events, types, $.proxy(data, self));
429         //this.events.bind.apply(this.events, arguments);//.bind(this);
430         //this.events.bind.apply(this.events, $.proxy(arguments));//.bind(this);
431         //this.events.bind.apply(this.events, $.proxy(arguments));//.bind(this);
432         //this.events.bind(types, data, fn);//.bind(this);
433         //this.events.bind.call(this.events, types, data, fn);//.bind(this);
434         return this;
435     },
436 /**
437 ###*map*.`trigger(name [, parameters])`
438 _version added 0.2_
439 ####**Description**: triggers an event on the map
440
441  * **name** the name of the event
442  * **parameters** additional parameters that will be passed on with the event
443
444 >Returns: map (MapQuery.Map)
445
446 To subscribe to the triggered events, you need to bind to the mapuuu.
447
448      map.bind('myEvent', function(evt) {
449          console.log('the values are: ' + evt.data[0] + ' and ' + evt.data[1])
450      });
451      map.trigger('myEvent', 'some', 'values');
452 */
453     trigger: function() {
454         // There is no point in using trigger() insted of triggerHandler(), as
455         // we don't fire native events
456         this.events.triggerHandler.apply(this.events, arguments);
457         return this;
458     },
459     // Basically a trigger that returns the return value of the last listener
460     _triggerReturn: function() {
461         return this.events.triggerHandler.apply(this.events, arguments);
462     },
463     destroy: function() {
464         this.olMap.destroy();
465         this.element.removeData('mapQuery');
466     }
467 };
468 /**
469
470 ---
471
472 #MapQuery.Layer
473
474 The MapQuery.Layer object. It is constructed with layer options object in the
475 map.`layers([options])` function or by passing a `layer:{options}` object in
476 the `mapQuery()` constructor. The Layer object is refered to as _layer_ in the
477 documentation.
478  */
479 $.MapQuery.Layer = function(map, id, options) {
480
481     var self = this;
482     // apply default options that are not specific to a layer
483
484     this.id = id;
485     this.label = options.label || this.id;
486     // a reference to the map object is needed as it stores e.g. the list
487     // of all layers (and we need to keep track of it, if we delete a
488     // layer)
489     this.map = map;
490
491     // true if this layer is a vector layer
492     this.isVector = false;
493
494     // to bind and trigger jQuery events
495     this.events = $({});
496
497     this.handlers = {
498         // Triggers the jQuery events, after the OpenLayers events
499         // happened without any further processing
500         simple: function(data) {
501             this.trigger(data.type);
502         },
503         // All OpenLayers events that are triggered by user interaction,
504         // like clicking somewhere or selecting a feature, need to be
505         // handled in a special way. Those OpenLayers events will then be
506         // triggered by MapQuery as well
507         // In case of the "featureselected" event, this means that the
508         // logic of handling the event is completely within the event
509         // handler. When ".select()" on a feature is called, it will just
510         // trigger the OpenLayers "featureselected" event, whose handler
511         // will then trigger the corresponding jQuery event.
512         includeFeature: function(data) {
513             var feature = new $.MapQuery.Feature(this, {olFeature:
514                                                         data.feature});
515             this.trigger(data.type, [feature]);
516         },
517         prependLayer: function(data) {
518             this.trigger('layer' + data.type);
519         }
520     };
521
522
523     // create the actual layer based on the options
524     // Returns layer and final options for the layer (for later re-use,
525     // e.g. zoomToMaxExtent).
526     var res = $.MapQuery.Layer.types[options.type.toLowerCase()].call(
527         this, options);
528     this.olLayer = res.layer;
529     this.options = res.options;
530
531     // Some good documentation for the events is needed. Here is a short
532     // description on how the current events compare to the OpenLayer
533     // events on the layer:
534     // - added, remove: not needed, there's addlayer and removelayer
535     // - visibilitychanged: not needed, there's the changelayer event
536     // - move, moveend: not needed as you get them from the map, not the layer
537     // - loadstart, loadend: renamed to layerloadstart, layerloadend
538     this.olLayer.events.on({
539         scope: this,
540         loadstart: this.handlers.prependLayer,
541         loadend: this.handlers.prependLayer,
542         featureselected: this.handlers.includeFeature,
543         featureunselected: this.handlers.includeFeature,
544         featureremoved: this.handlers.includeFeature
545     });
546
547     // To be able to retreive the MapQuery layer, when we only have the
548     // OpenLayers layer available. For example on the layeradded event.
549     // NOTE vmx 2012-02-26: Any nicer solution is welcome
550     this.olLayer.mapQueryId = this.id;
551 };
552
553 $.MapQuery.Layer.prototype = {
554 /**
555 ###*layer*.`down([delta])`
556 _version added 0.1_
557 ####**Description**: move the layer down in the layer stack of the map
558
559  * **delta** the amount of layers the layer has to move down in the layer
560 stack (default 1)
561
562 >Returns layer (MapQuery.Layer)
563
564
565 The `.down()` method is a shortcut method for `.position(pos)` which makes
566 it easier to move a layer down in the layerstack relative to its current
567 position. It takes an integer and will try to move the layer down the number of
568 places given. If delta is bigger than the current position in the stack, it
569 will put the layer at the bottom.
570
571
572      layer.down();  //move layer 1 place down
573      layer.down(3); //move layer 3 places down
574
575  */
576     down: function(delta) {
577         delta = delta || 1;
578         var pos = this.position();
579         pos = pos - delta;
580         if (pos<0) {pos = 0;}
581         this.position(pos);
582         return this;
583     },
584     // NOTE vmx: this would be pretty cool, but it's not easily possible
585     // you could use $.each($.geojq.layer())) instead, this is for pure
586     // convenience.
587     each: function () {},
588 /**
589 ###*layer*.`remove()`
590 _version added 0.2_
591 ####**Description**: remove the layer from the map
592
593 >Returns: map (MapQuery.Map) or false
594
595
596 The `.remove()` method allows us to remove a layer from the map.
597 It returns the `map` object if the layer was removed, or `false` if the
598 removal was prevented in the preremovelayer event.
599
600      var id = layer.remove(); //remove this layer
601
602
603  */
604     remove: function() {
605         // remove references to this layer that are stored in the
606         // map object
607         return this.map._removeLayer(this.id);
608     },
609 /**
610 ###*layer*.`position([position])`
611 _version added 0.1_
612 ####**Description**: get/set the `position` of the layer in the layer
613 stack of the map
614
615  * **position** an integer setting the new position of the layer in the layer stack
616
617 >Returns: position (integer) _or_ layer (MapQuery.Layer)
618
619
620 The `.position()` method allows us to change the position of the layer in the
621 layer stack. It will take into account the hidden baselayer that is used by
622 OpenLayers. The lowest layer is position 0. If no position is given, it will
623 return the current postion.
624
625
626      var pos =  layer.position(); //get position of layer in the layer stack
627      layer.position(2); //put layer on position 2 in the layer stack
628
629  */
630     position: function(pos) {
631         if (pos===undefined) {
632             return this.map.olMap.getLayerIndex(this.olLayer)-1;
633         }
634         else {
635             this.map.olMap.setLayerIndex(this.olLayer, pos+1);
636             this.trigger('changelayer', ['position']);
637             return this;
638         }
639     },
640 /**
641 ###*layer*.`up([delta])`
642 _version added 0.1_
643 ####**Description**: move the layer up in the layer stack of the map
644
645  * **delta** the amount of layers the layer has to move up in the layer
646 stack (default 1)
647
648 >Returns: layer (MapQuery.Layer)
649
650
651 The `.up()` method is a shortcut method for `.position(pos)` which makes
652 it easier to move a layer up in the layerstack relative to its current
653 position. It takes an integer and will move the layer up the number of places
654 given.
655
656
657
658      layer.up();  //move layer 1 place up
659      layer.up(3); //move layer 3 places up
660 */
661     up: function(delta) {
662         delta = delta || 1;
663         var pos = this.position();
664         pos = pos + delta;
665         this.position(pos);
666         return this;
667     },
668 /**
669 ###*layer*.`visible([visible])`
670 _version added 0.1_
671 ####**Description**: get/set the `visible` state of the layer
672
673  * **visible** a boolean setting the visibility of the layer
674
675 >Returns: visible (boolean)
676
677
678 The `.visible()` method allows us to change the visibility of the layer.
679 If no visible is given, it will return the current visibility.
680
681
682      var vis =  layer.visible(); //get the visibility of layer
683      layer.visible(true); //set visibility of layer to true
684
685  */
686     visible: function(vis) {
687         if (vis===undefined) {
688             return this.olLayer.getVisibility();
689         }
690         else {
691             this.olLayer.setVisibility(vis);
692             this.trigger('changelayer', ['visibility']);
693             return this;
694         }
695     },
696 /**
697 ###*layer*.`opacity([opacity])`
698 _version added 0.1_
699 ####**Description**: get/set the `opacity` of the layer
700
701  * **position** a float [0-1] setting the opacity of the layer
702
703 >Returns: opacity (float) _or_ layer (MapQuery.Layer)
704
705
706 The `.opacity()` method allows us to change the opacity of the layer.
707 If no opacity is given, it will return the current opacity.
708
709
710      var opac =  layer.opacity(); //get opacity of layer
711      layer.opacity(0.7); //set opacity of layer to 0.7
712
713  */
714     opacity: function(opac) {
715         if (opac===undefined) {
716             // this.olLayer.opacity can be null if never
717             // set so return the visibility
718             var value = this.olLayer.opacity ?
719             this.olLayer.opacity : this.olLayer.getVisibility();
720             return value;
721         }
722         else {
723             this.olLayer.setOpacity(opac);
724             this.trigger('changelayer', ['opacity']);
725             return this;
726         }
727     },
728     // every event gets the layer passed in
729     bind: function() {
730         // Use the same bind function as for the map
731         this.map.bind.apply(this, arguments);
732         return this;
733     },
734 /**
735 ###*layer*.`trigger(name [, parameters])`
736 _version added 0.2_
737 ####**Description**: triggers an event on the layer and map
738
739  * **name** the name of the event
740  * **parameters** additional parameters that will be passed on with the event
741
742 >Returns: layer (MapQuery.Layer)
743
744 The events get triggered on the layer as well as on the map. To subscribe to
745 the triggered events, you can either bind to the layer or the map. If bound
746 to the map, the second argument in the bind will be the layer the event
747 came from
748
749      layer.bind('myEvent', function(evt) {
750          console.log('the values are: ' + evt.data[0] + ' and ' + evt.data[1])
751      });
752      map.bind('myEvent', function(evt, layer) {
753          console.log('the values are: ' + evt.data[0] + ' and ' + evt.data[1])
754      });
755      layer.trigger('myEvent', 'some', 'values');
756 */
757     trigger: function() {
758         var args = Array.prototype.slice.call(arguments);
759         this.events.triggerHandler.apply(this.events, args);
760
761         this._addLayerToArgs(args);
762
763         this.map.events.triggerHandler.apply(this.map.events, args);
764         return this;
765     },
766     // Basically a trigger that returns the return value of the last listener
767     _triggerReturn: function() {
768         var args = Array.prototype.slice.call(arguments);
769         var ret = this.events.triggerHandler.apply(this.events, args);
770         if (ret !== undefined) {
771             return ret;
772         }
773
774         this._addLayerToArgs(args);
775         return this.events.triggerHandler.apply(this.map.events, args);
776     },
777     // Adds the current layer to the event arguments, so that it is included
778     // in the event on the map
779     _addLayerToArgs: function(args) {
780         // Add layer for the map event
781         if (args.length===1) {
782             args.push([this]);
783         }
784         else {
785             args[1].unshift(this);
786         }
787     },
788 /**
789 ###*layer*.`features([options])`
790 _version added 0.2.0_
791 ####**Description**: get/set the features of a (vector) layer
792
793 **options** an object of key-value pairs with options to create one or
794 more features
795
796 >Returns: [features] (array of MapQuery.Feature)
797
798
799 The `.features()` method allows us to attach features to a mapQuery layer
800 object. It takes an options object with feature options. To add multiple
801 features, create an array of feature options objects. If an options object
802 is given, it will return the resulting feature(s). We can also use it to
803 retrieve all features currently attached to the layer.
804
805
806      // add an (vector) json layer to the map
807      var jsonlayer = map.layers({type:'json'});
808      // add a feature to the layer
809      jsonlayer.features({geometry: {type: "Point", coordinates: [5.3, 7.4]}});
810      // get all features of a layer (sorted with first added feature at the beginning
811      var features = jsonlayer.features();
812 */
813     features: function(options) {
814         var self = this;
815         switch(arguments.length) {
816         // return all features
817         case 0:
818             return this._allFeatures();
819         // add new feature(s)
820         case 1:
821             if (!$.isArray(options)) {
822                 return this._addFeature(options);
823             }
824             else {
825                 return $.map(options, function(feature) {
826                     return self._addFeature(feature);
827                 });
828             }
829             break;
830         default:
831             throw('wrong argument number');
832         }
833     },
834     _allFeatures: function() {
835         var layer = this;
836         return $.map(layer.olLayer.features, function(feature) {
837             return new $.MapQuery.Feature(layer, {olFeature: feature});
838         });
839     },
840     _addFeature: function(options) {
841         var feature = new $.MapQuery.Feature(this, options);
842         // NOTE vmx 2012-04-19: Not sure if this is a good idea, or if it would
843         //     be better to include `options` with the preaddfeature event
844         if (this._triggerReturn('preaddfeature', [feature])===false) {
845             return false;
846         }
847         this.olLayer.addFeatures(feature.olFeature);
848         this.trigger('addfeature', [feature]);
849         return feature;
850     }
851 };
852
853 /**
854 #MapQuery.Feature
855
856 The MapQuery.Feature object. It is constructed with a feature options object
857 in the layer.`features([options])` function. The Feautre object is refered to
858 as _feature_ in the documentation.
859
860 TODO vmx 20110905: Support other geometry types than GeoJSON
861 options:
862  * geometry: A GeoJSON geometry
863  * properties: Properties for the feature
864 */
865 // Not in the pulic API docs: You can pass in as options:
866 //  * olFeature: This will wrap the olFeature in a MapQuery feature
867 $.MapQuery.Feature = function(layer, options) {
868     // The ID is the
869     this._id = layer.map._createId();
870     this.layer = layer;
871
872     // Feature already exists on the layer, it just needs to be wrapped
873     // to an MapQuery feature
874     if (options.olFeature) {
875         this.olFeature = options.olFeature;
876     }
877     else {
878         // XXX vmx 20110905: Different feature types might make sense:
879         //     (Geo)JSON, KML, WKT
880         // vmx 2012-04-14: I changed my mind quite some time ago. We should onlu
881         //     support GeoJSON and let the user easily transfrom their format
882         //     (e.g. KML) to GeoJSON, before they add a feature to the layer
883         var GeoJSON = new OpenLayers.Format.GeoJSON();
884         var geometry = GeoJSON.parseGeometry(options.geometry);
885         geometry.transform(
886             new OpenLayers.Projection(this.layer.map.displaProjection),
887             new OpenLayers.Projection(this.layer.map.projection));
888
889         this.olFeature = new OpenLayers.Feature.Vector(geometry,
890             options.properties);
891     }
892
893     // Modify the features to be more practical
894     // e.g. copy properties that should be easily accessed from the
895     // outside, out of the olLayer and to the feature level
896     this.properties = $.extend(true, {}, this.olFeature.attributes);
897     this.geometry = $.parseJSON(
898         new OpenLayers.Format.GeoJSON().write(this.olFeature.geometry));
899
900     return this;
901 };
902
903 $.MapQuery.Feature.prototype = {
904 /**
905 ###*feature*.`remove()`
906 _version added 0.2.0_
907 ####**Description**: remove the feature from the layer
908
909 >Returns: layer (layer) or false
910
911
912 The `.remove()` method allows us to remove a feature from the layer.
913 It returns the `layer` object if the feature was removed, or `false` if the
914 removal was prevented in the preremovefeature event.
915
916      // add a feature to a layer
917      var feature = layer.features({geometry: {type: "Point", coordinates: [5.3, 7.4]}});
918      // remove the feature again
919      feature.remove();
920 */
921     remove: function() {
922         if (this.layer._triggerReturn('preremovefeature', [this])===false) {
923             return false;
924         }
925         this.layer.olLayer.removeFeatures(this.olFeature);
926         // The `removefeature` event is triggered by an OpenLayes event handler
927         return this.layer;
928     },
929 /**
930 ###*feature*.`select(exclusive)`
931 _version added 0.2.0_
932 ####**Description**: select a feature
933
934 **exclusive** (boolean, default: true) True means that all other features get
935 deselectd
936
937 >Returns: layer (layer)
938
939
940 The `.select()` method allows us to select a feature from the layer.
941 A `featureselected` will be fired.
942
943      // add a feature to a layer
944      var feature = layer.features({geometry: {type: "Point", coordinates: [5.3, 7.4]}});
945      // select the feature again
946      feature.select();
947 */
948     select: function(exclusive) {
949         if (exclusive===undefined || exclusive===true) {
950             this.layer.map.selectFeatureControl.unselectAll();
951         }
952         this.layer.map.selectFeatureControl.select(this.olFeature);
953     },
954 /**
955 ###*feature*.`unselect()`
956 _version added 0.2.0_
957 ####**Description**: unselect a feature
958
959 >Returns: layer (layer)
960
961
962 The `.unselect()` method allows us to unselect a feature from the layer.
963 A `featureunselected` will be fired.
964
965      // add a feature to a layer
966      var feature = layer.features({geometry: {type: "Point", coordinates: [5.3, 7.4]}});
967      // select the feature
968      feature.select();
969      // unselect the feature again
970      feature.unselect();
971 */
972     unselect: function() {
973         this.layer.map.selectFeatureControl.unselect(this.olFeature);
974     }
975 };
976
977 $.fn.mapQuery = function(options) {
978     return this.each(function() {
979         var instance = $.data(this, 'mapQuery');
980         if (!instance) {
981             $.data(this, 'mapQuery', new $.MapQuery.Map($(this), options));
982         }
983     });
984 };
985
986 $.extend($.MapQuery.Layer, {
987     types: {
988 /**
989 ###*layer* `{type:bing}`
990 _version added 0.1_
991 ####**Description**: create a Bing maps layer
992
993  * **view** a string ['road','hybrid','satellite'] to define which Bing maps
994 layer to use (default road)   
995  * **key** Bing Maps API key for your application. Get you own at
996 http://bingmapsportal.com/ 
997  * **label** string with the name of the layer
998
999
1000       layers:[{
1001             type:'bing',      //create a bing maps layer
1002             view:'satellite', //use the bing satellite layer
1003             key:'ArAGGPJ16xm0RX' //the Bing maps API key
1004             }]
1005
1006 */
1007         bing: function(options) {
1008             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1009                 $.fn.mapQuery.defaults.layer.bing,
1010                 options);
1011             var view = o.view;
1012             switch(view){
1013                 case 'road':
1014                     view = 'Road'; break;
1015                 case 'hybrid':
1016                     view = 'AerialWithLabels'; break;
1017                 case 'satellite':
1018                     view = 'Aerial'; break;
1019             }
1020             return {
1021                 layer: new OpenLayers.Layer.Bing({type:view,key:o.key}),
1022                 options: o
1023             };
1024         },
1025         //Not sure this one is worth pursuing works with ecwp:// & jpip:// urls
1026         //See ../lib/NCSOpenLayersECWP.js
1027         ecwp: function(options) {
1028             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1029                     $.fn.mapQuery.defaults.layer.raster,
1030                     options);
1031             return {
1032                 layer: new OpenLayers.Layer.ECWP(o.label, o.url, o),
1033                 options: o
1034             };
1035         },
1036 /**
1037 ###*layer* `{type:google}`
1038 _version added 0.1_
1039 ####**Description**: create a Google maps layer
1040
1041  * **view** a string ['road','hybrid','satellite'] to define which Google maps
1042 layer to use (default road)
1043  * **label** string with the name of the layer
1044
1045
1046 *Note* you need to include the google maps v3 API in your application by adding
1047 `<script src="http://maps.google.com/maps/api/js?v=3.5&amp;sensor=false"type="text/javascript"></script>`
1048
1049
1050       layers:[{
1051             type:'google',      //create a google maps layer
1052             view:'hybrid' //use the google hybridlayer
1053             }]
1054
1055 */
1056         google: function(options) {
1057             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1058                     $.fn.mapQuery.defaults.layer.google,
1059                     options);
1060             var view = o.view;
1061             switch(view){
1062                 case 'road':
1063                     view = google.maps.MapTypeId.ROADMAP; break;
1064                 case 'terrain':
1065                     view = google.maps.MapTypeId.TERRAIN; break;
1066                 case 'hybrid':
1067                     view = google.maps.MapTypeId.HYBRID; break;
1068                 case 'satellite':
1069                     view = google.maps.MapTypeId.SATELLITE; break;
1070             }
1071             return {
1072                 layer: new OpenLayers.Layer.Google({type:view}),
1073                 options: o
1074             };
1075         },
1076 /**
1077 ###*layer* `{type:vector}`
1078 _version added 0.1_
1079 ####**Description**: create a vector layer
1080
1081  * **label** string with the name of the layer
1082
1083
1084       layers:[{
1085             type:'vector'     //create a vector layer
1086             }]
1087
1088 */
1089         vector: function(options) {
1090             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1091                     $.fn.mapQuery.defaults.layer.vector,
1092                     options);
1093             this.isVector = true;
1094             return {
1095                 layer: new OpenLayers.Layer.Vector(o.label),
1096                 options: o
1097             };
1098         },
1099 /**
1100 ###*layer* `{type:json}`
1101 _version added 0.1_
1102 ####**Description**: create a JSON layer
1103
1104  * **url** a string pointing to the location of the JSON data
1105  * **strategies** a string ['bbox','cluster','filter','fixed','paging','refresh','save']
1106 stating which update strategy should be used (default fixed)
1107 (see also http://dev.openlayers.org/apidocs/files/OpenLayers/Strategy-js.html)
1108  * **projection** a string with the projection of the JSON data (default EPSG:4326)
1109  * **styleMap** {object} the style to be used to render the JSON data    
1110  * **label** string with the name of the layer
1111
1112
1113       layers:[{
1114             type: 'JSON',
1115             url: 'data/reservate.json',
1116             label: 'reservate'
1117             }]
1118
1119 */
1120         json: function(options) {
1121             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1122                 $.fn.mapQuery.defaults.layer.vector,
1123                 options);
1124             this.isVector = true;
1125             var strategies = [];
1126             for (var i in o.strategies) {
1127                 if(o.strategies.hasOwnProperty(i)) {
1128                     switch(o.strategies[i].toLowerCase()) {
1129                     case 'bbox':
1130                         strategies.push(new OpenLayers.Strategy.BBOX());
1131                    break;
1132                     case 'cluster':
1133                         strategies.push(new OpenLayers.Strategy.Cluster());
1134                    break;
1135                     case 'filter':
1136                         strategies.push(new OpenLayers.Strategy.Filter());
1137                    break;
1138                     case 'fixed':
1139                         strategies.push(new OpenLayers.Strategy.Fixed());
1140                    break;
1141                     case 'paging':
1142                         strategies.push(new OpenLayers.Strategy.Paging());
1143                    break;
1144                     case 'refresh':
1145                         strategies.push(new OpenLayers.Strategy.Refresh());
1146                    break;
1147                     case 'save':
1148                         strategies.push(new OpenLayers.Strategy.Save());
1149                    break;
1150                     }
1151                 }
1152             }
1153             var protocol;
1154
1155             var params = {
1156                 strategies: strategies,
1157                 projection: o.projection || 'EPSG:4326',
1158                 styleMap: o.styleMap
1159             };
1160
1161             if (o.url) {
1162                 // only use JSONP if we use http(s)
1163                 if (o.url.match(/^https?:\/\//)!==null &&
1164                     !$.MapQuery.util.sameOrigin(o.url)) {
1165                     protocol = 'Script';
1166                 }
1167                 else {
1168                     protocol = 'HTTP';
1169                 }
1170                 params.protocol = new OpenLayers.Protocol[protocol]({
1171                     url: o.url,
1172                     format: new OpenLayers.Format.GeoJSON()
1173                 });
1174             };
1175
1176             var layer = new OpenLayers.Layer.Vector(o.label, params);
1177             return {
1178                 layer: layer,
1179                 options: o
1180             };
1181         },
1182 /**
1183 ###*layer* `{type:osm}`
1184 _version added 0.1_
1185 ####**Description**: create an OpenStreetMap layer
1186
1187  
1188  * **label** string with the name of the layer   
1189  * **url** A single URL (string) or an array of URLs to OSM-like server like 
1190 Cloudmade   
1191  * **attribution** A string to put some attribution on the map
1192
1193       layers:[{
1194         type: 'osm',
1195         url: [
1196           'http://a.tile.cloudmade.com/<yourapikey>/999/256/${z}/${x}/${y}.png',
1197           'http://b.tile.cloudmade.com/<yourapikey>/999/256/${z}/${x}/${y}.png',
1198           'http://c.tile.cloudmade.com/<yourapikey>/999/256/${z}/${x}/${y}.png'
1199         ],
1200         attribution: "Data &copy; 2009 <a href='http://openstreetmap.org/'>
1201           OpenStreetMap</a>. Rendering &copy; 2009 
1202           <a href='http://cloudmade.com'>CloudMade</a>."
1203         }]
1204
1205 */
1206         osm: function(options) {
1207             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1208                 $.fn.mapQuery.defaults.layer.osm,
1209                 options);
1210             var label = options.label || undefined;
1211             var url = options.url || undefined;
1212             return {
1213                 layer: new OpenLayers.Layer.OSM(label, url, o),
1214                 options: o
1215             };
1216         },
1217 /**
1218 ###*layer* `{type:tms}`
1219 _version added 0.1_
1220 ####**Description**: create an OpenStreetMap layer
1221
1222  
1223  * **label** string with the name of the layer   
1224  * **url** A single URL (string) or an array of URLs to the TMS end point
1225  * **layer** The identifier for the <TileMap> as advertised by the service. 
1226  For example, if the service advertises a <TileMap> with ‘href=”http://tms.osgeo.org/1.0.0/vmap0”’, 
1227  the layer property would be set to “vmap0”.
1228  * **format** The image format (default png)
1229
1230       layers:[{
1231         type: 'tms',
1232         url: 'http://tilecache.osgeo.org/wms-c/Basic.py/',
1233         layer: 'basic'
1234         }]
1235
1236 */        
1237         tms: function(options) {
1238             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1239                 $.fn.mapQuery.defaults.layer.tms,
1240                 options);
1241             var label = options.label || undefined;
1242             var url = options.url || undefined;
1243             var params = {
1244                 layername: o.layer,
1245                 type: o.format
1246             };
1247             return {
1248                 layer: new OpenLayers.Layer.TMS(label, url, params),
1249                 options: o
1250             };
1251         },
1252 /**
1253 ###*layer* `{type:wms}`
1254 _version added 0.1_
1255 ####**Description**: create a WMS layer
1256
1257  * **url** a string pointing to the location of the WMS service
1258  * **layers** a string with the name of the WMS layer(s)
1259  * **format** a string with format of the WMS image (default image/jpeg)
1260  * **transparent** a boolean for requesting images with transparency
1261  * **label** string with the name of the layer
1262  * **wms_parameters** an hashtable of extra GetMap query string parameters and parameter values
1263
1264
1265       layers:[{
1266             type:'wms',
1267             url:'http://vmap0.tiles.osgeo.org/wms/vmap0',
1268             layers:'basic'
1269             }]
1270
1271 */
1272         wms: function(options) {
1273             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1274                     $.fn.mapQuery.defaults.layer.raster,
1275                     options);
1276             var params = {
1277                 layers: o.layers,
1278                 transparent: o.transparent,
1279                 format: o.format
1280             };
1281             if(typeof o.wms_parameters != "undefined"){
1282                 params = $.extend(params, o.wms_parameters);
1283             }
1284             return {
1285                 layer: new OpenLayers.Layer.WMS(o.label, o.url, params, o),
1286                 options: o
1287             };
1288         },
1289 //TODO complete this documentation
1290 /**
1291 ###*layer* `{type:wmts}`
1292 _version added 0.1_
1293 ####**Description**: create a WMTS (tiling) layer
1294
1295  * **url** a string pointing to the location of the WMTS service
1296  * **layer** a string with the name of the WMTS layer
1297  * **matrixSet** a string with one of the advertised matrix set identifiers
1298  * **style** a string with one of the advertised layer styles    
1299  * **label** string with the name of the layer
1300
1301
1302       layers:[{
1303             type:'wmts'
1304             }]
1305
1306 */
1307         wmts: function(options) {
1308             var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1309                     $.fn.mapQuery.defaults.layer.wmts);
1310             //smo 20110614 the maxExtent is set here with OpenLayers.Bounds
1311             if (options.sphericalMercator===true) {
1312                 $.extend(true, o, {
1313                     maxExtent: new OpenLayers.Bounds(
1314                         -128 * 156543.0339, -128 * 156543.0339,
1315                         128 * 156543.0339, 128 * 156543.0339),
1316                     maxResolution: 156543.0339,
1317                     numZoomLevels: 19,
1318                     projection: 'EPSG:900913',
1319                     units: 'm'
1320                 });
1321             }
1322             $.extend(true, o, options);
1323             // use by default all options that were passed in for the final
1324             // openlayers layer consrtuctor
1325             var params = $.extend(true, {}, o);
1326
1327             // remove trailing slash
1328             if (params.url.charAt(params.url.length-1)==='/') {
1329                 params.url = params.url.slice(0, params.url.length-1);
1330             }
1331             // if no options that influence the URL where set, extract them
1332             // from the given URL
1333             if (o.layer===undefined && o.matrixSet===undefined &&
1334                     o.style===undefined) {
1335                 var url = $.MapQuery.util.parseUri(params.url);
1336                 var urlParts = url.path.split('/');
1337                 var wmtsPath = urlParts.slice(urlParts.length-3);
1338                 params.url = url.protocol ? url.protocol + '//' : '';
1339                 params.url += url.authority +
1340                     // remove WMTS version (1.0.0) as well
1341                     urlParts.slice(0, urlParts.length-4).join('/');
1342                 params.layer = wmtsPath[0];
1343                 params.style = wmtsPath[1];
1344                 params.matrixSet = wmtsPath[2];
1345             }
1346             return {
1347                 layer: new OpenLayers.Layer.WMTS(params),
1348                 options: o
1349             };
1350         }
1351     }
1352 });
1353
1354 // default options for the map and layers
1355 $.fn.mapQuery.defaults = {
1356     // The controls for the map are per instance, therefore it need to
1357     // be an function that can be initiated per instance
1358     map: function() {
1359         return {
1360             // Remove quirky moveTo behavior, probably not a good idea in the
1361             // long run
1362             allOverlays: true,
1363             controls: [
1364                 // Since OL2.11 the Navigation control includes touch navigation as well
1365                 new OpenLayers.Control.Navigation({
1366                     documentDrag: true,
1367                     dragPanOptions: {
1368                         interval: 1,
1369                         enableKinetic: true
1370                     }
1371                 }),
1372                 new OpenLayers.Control.ArgParser(),
1373                 new OpenLayers.Control.Attribution(),
1374                 new OpenLayers.Control.KeyboardDefaults()
1375             ],
1376             format: 'image/png',
1377             maxExtent: [-128*156543.0339,
1378                 -128*156543.0339,
1379                 128*156543.0339,
1380                 128*156543.0339],
1381             maxResolution: 156543.0339,
1382             numZoomLevels: 19,
1383             projection: 'EPSG:900913',
1384             displayProjection: 'EPSG:4326',
1385             zoomToMaxExtent: true,
1386             units: 'm'
1387         };
1388     },
1389     layer: {
1390         all: {
1391             isBaseLayer: false,
1392         //in general it is kinda pointless to load tiles outside a maxextent
1393             displayOutsideMaxExtent: false
1394         },
1395         bing: {
1396             transitionEffect: 'resize',
1397             view: 'road',
1398             sphericalMercator: true
1399         },
1400         google: {
1401             transitionEffect: 'resize',
1402             view: 'road',
1403             sphericalMercator: true
1404         },
1405         osm: {
1406             transitionEffect: 'resize',
1407             sphericalMercator: true
1408         },
1409         tms: {
1410             transitionEffect: 'resize',
1411             format: 'png'
1412         },
1413         raster: {
1414             // options for raster layers
1415             transparent: true
1416         },
1417         vector: {
1418             // options for vector layers
1419             strategies: ['bbox']
1420         },
1421         wmts: {
1422             format: 'image/jpeg',
1423             requestEncoding: 'REST',
1424             sphericalMercator: false
1425         }
1426     }
1427 };
1428
1429 // Some utility functions
1430
1431 $.MapQuery.util = {};
1432 // http://blog.stevenlevithan.com/archives/parseuri (2010-12-18)
1433 // parseUri 1.2.2
1434 // (c) Steven Levithan <stevenlevithan.com>
1435 // MIT License
1436 // Edited to include the colon in the protocol, just like it is
1437 // with window.location.protocol
1438 $.MapQuery.util.parseUri = function (str) {
1439     var o = $.MapQuery.util.parseUri.options,
1440         m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
1441         uri = {},
1442         i = 14;
1443
1444     while (i--) {uri[o.key[i]] = m[i] || "";}
1445
1446     uri[o.q.name] = {};
1447     uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
1448         if ($1) {uri[o.q.name][$1] = $2;}
1449     });
1450
1451     return uri;
1452 };
1453 $.MapQuery.util.parseUri.options = {
1454     strictMode: false,
1455     key: ["source", "protocol", "authority", "userInfo", "user",
1456             "password", "host", "port", "relative", "path", "directory",
1457             "file", "query", "anchor"],
1458     q: {
1459         name: "queryKey",
1460         parser: /(?:^|&)([^&=]*)=?([^&]*)/g
1461     },
1462     parser: {
1463         strict: /^(?:([^:\/?#]+:))?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
1464         loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+:))?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
1465         }
1466 };
1467 // Checks whether a URL conforms to the same origin policy or not
1468 $.MapQuery.util.sameOrigin = function(url) {
1469     var parsed = $.MapQuery.util.parseUri(url);
1470     parsed.protocol = parsed.protocol || 'file:';
1471     parsed.port = parsed.port || "80";
1472
1473     var current = {
1474         domain: document.domain,
1475         port: window.location.port,
1476         protocol: window.location.protocol
1477     };
1478     current.port = current.port || "80";
1479
1480     return parsed.protocol===current.protocol &&
1481         parsed.port===current.port &&
1482         // the current domain is a suffix of the parsed domain
1483         parsed.host.match(current.domain + '$')!==null;
1484 };
1485 })(jQuery);