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. */
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.
12 ### *$('selector')*.`mapQuery([options])`
14 ####**Description**: initialise MapQuery and associate it with
17 **options** an object of key-value pairs with options for the map. Possible
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.
26 > Returns: $('selector') (jQuery object)
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.
33 var map = $('#map').mapQuery(); //create an empty map
34 var map = $('#map').mapQuery({layers:[{type:'osm'}]); //create a map with osm
36 var mq = map.data('mapQuery'); //get the MapQuery object
38 $.MapQuery = $.MapQuery || {};
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.
50 $.MapQuery.Map = function(element, options) {
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
57 if(!options.maxResolution&&options.maxExtent&&options.projection){
58 options.maxResolution = (options.maxExtent[2]-options.maxExtent[0])/256;
60 this.options = $.extend({}, new $.fn.mapQuery.defaults.map(), options);
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;
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]);
80 this.projection = this.options.projection;
81 this.displayProjection = this.options.displayProjection;
83 OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
84 OpenLayers.Util.onImageLoadErrorColor = "transparent";
86 // create the OpenLayers Map
87 this.olMap = new OpenLayers.Map(this.element[0], this.olMapOptions);
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}));
93 // Keep IDs of vector layer for select feature control
94 this.vectorLayers = [];
95 this.selectFeatureControl = null;
96 // Counts up to create unique IDs
99 element.data('mapQuery', this);
100 this.layersList = {};
102 // To bind and trigger jQuery events
106 // Triggers the jQuery events, after the OpenLayers events
107 // happened without any further processing
108 simple: function(data) {
109 this.trigger(data.type);
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
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
125 // - generally spoken, the map events follow the OpeLayer events
126 // - preaddlayer, movestart, move, moveend, zoomend: no additional
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({
139 movestart: this.handlers.simple,
140 move: this.handlers.simple,
141 moveend: this.handlers.simple,
142 zoomend: this.handlers.simple
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);
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();
160 $.MapQuery.Map.prototype = {
162 ###*map*.`layers([options])`
164 ####**Description**: get/set the layers of the map
166 **options** an object of key-value pairs with options to create one or
169 >Returns: [layer] (array of MapQuery.Layer) _or_ false
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
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).
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
187 layers: function(options) {
188 //var o = $.extend({}, options);
190 switch(arguments.length) {
192 return this._allLayers();
194 if (!$.isArray(options)) {
195 return this._addLayer(options);
198 return $.map(options, function(layer) {
199 return self._addLayer(layer);
204 throw('wrong argument number');
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() {
211 $.each(this.layersList, function(id, layer) {
212 var item = [layer.position(), layer];
215 var sorted = layers.sort( function compare(a, b) {
218 var result = $.map(sorted, function(item) {
221 return result.reverse();
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) {
231 this.olMap.addLayer(layer.olLayer);
233 this.layersList[id] = layer;
234 if (layer.isVector) {
235 this.vectorLayers.push(id);
237 this._updateSelectFeatureControl(this.vectorLayers);
239 layer.trigger('addlayer');
242 // Creates a new unique ID for a layer
243 _createId: function() {
244 return 'mapquery_' + this.idCounter++;
246 _removeLayer: function(id) {
247 var layer = this.layersList[id];
248 if (this._triggerReturn('preremovelayer', [layer])===false) {
252 // remove id from vectorlayer if it is there list
253 this.vectorLayers = $.grep(this.vectorLayers, function(elem) {
256 this._updateSelectFeatureControl(this.vectorLayers);
257 this.olMap.removeLayer(layer.olLayer);
259 // XXX vmx: shouldn't the layer be destroyed() properly?
260 delete this.layersList[id];
262 layer.trigger('removelayer');
266 ###*map*.`center([options])`
268 ####**Description**: get/set the extent, zoom and position of the map
270 * **position** the position as [x,y] in displayProjection (default EPSG:4326)
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
279 >Returns: {position: [x,y], zoom: z(int), box: [llx,lly,urx,ury]}
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.
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'});
297 center: function (options) {
299 var mapProjection = new OpenLayers.Projection(this.projection);
300 // Determine source projection
301 var sourceProjection = null;
304 if(options && options.projection) {
305 sourceProjection = options.projection.CLASS_NAME ===
306 'OpenLayers.Projection' ? options.projection :
307 new OpenLayers.Projection(options.projection);
309 var displayProjection = this.displayProjection;
310 if(!displayProjection) {
312 sourceProjection = new OpenLayers.Projection('EPSG:4326');
314 sourceProjection = displayProjection.CLASS_NAME ===
315 'OpenLayers.Projection' ? displayProjection :
316 new OpenLayers.Projection(displayProjection);
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();
326 if (!mapProjection.equals(sourceProjection)) {
327 position.transform(mapProjection, sourceProjection);
329 box.transform(mapProjection,sourceProjection);
330 box = box!==null ? box.toArray() : [];
332 position: [position.lon, position.lat],
333 zoom: this.olMap.getZoom(),
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);
345 this.olMap.zoomToExtent(box);
348 // Only zoom is given
349 else if (options.position===undefined) {
350 this.olMap.zoomTo(options.zoom);
352 // Position is given, zoom maybe as well
354 position = new OpenLayers.LonLat(options.position[0],
355 options.position[1]);
356 if (!mapProjection.equals(sourceProjection)) {
357 position.transform(sourceProjection, mapProjection);
359 // options.zoom might be undefined, so we are good to
361 this.olMap.setCenter(position, options.zoom);
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();
371 $.each(layerIds, function() {
372 vectorLayers.push(layersList[this].olLayer);
374 this.selectFeatureControl = new OpenLayers.Control.SelectFeature(
376 this.olMap.addControl(this.selectFeatureControl);
377 this.selectFeatureControl.activate();
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
384 bind: function(types, data, fn) {
387 // A map of event/handle pairs, wrap each of them
388 if(arguments.length===1) {
390 $.each(types, function(type, fn) {
391 wrapped[type] = function() {
392 return fn.apply(self, arguments);
395 this.events.bind.apply(this.events, [wrapped]);
399 // Only callback given, but no data (types, fn), hence
400 // `data` is the function
401 if(arguments.length===2) {
405 if (!$.isFunction(fn)) {
406 throw('bind: you might have a typo in the function name');
408 // Callback and data given (types, data, fn), hence include
409 // the data in the argument list
413 args.push(function() {
414 return fn.apply(self, arguments);
417 this.events.bind.apply(this.events, args);
420 //this.events.bind.call(this.events, types, function() {
421 // data.apply(self, arguments);
423 //this.events.bind.call(this.events, types, function() {
424 // data.apply(self, arguments);
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);
437 ###*map*.`trigger(name [, parameters])`
439 ####**Description**: triggers an event on the map
441 * **name** the name of the event
442 * **parameters** additional parameters that will be passed on with the event
444 >Returns: map (MapQuery.Map)
446 To subscribe to the triggered events, you need to bind to the mapuuu.
448 map.bind('myEvent', function(evt) {
449 console.log('the values are: ' + evt.data[0] + ' and ' + evt.data[1])
451 map.trigger('myEvent', 'some', 'values');
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);
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);
463 destroy: function() {
464 this.olMap.destroy();
465 this.element.removeData('mapQuery');
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
479 $.MapQuery.Layer = function(map, id, options) {
482 // apply default options that are not specific to a layer
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
491 // true if this layer is a vector layer
492 this.isVector = false;
494 // to bind and trigger jQuery events
498 // Triggers the jQuery events, after the OpenLayers events
499 // happened without any further processing
500 simple: function(data) {
501 this.trigger(data.type);
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:
515 this.trigger(data.type, [feature]);
517 prependLayer: function(data) {
518 this.trigger('layer' + data.type);
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(
528 this.olLayer = res.layer;
529 this.options = res.options;
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({
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
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;
553 $.MapQuery.Layer.prototype = {
555 ###*layer*.`down([delta])`
557 ####**Description**: move the layer down in the layer stack of the map
559 * **delta** the amount of layers the layer has to move down in the layer
562 >Returns layer (MapQuery.Layer)
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.
572 layer.down(); //move layer 1 place down
573 layer.down(3); //move layer 3 places down
576 down: function(delta) {
578 var pos = this.position();
580 if (pos<0) {pos = 0;}
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
587 each: function () {},
589 ###*layer*.`remove()`
591 ####**Description**: remove the layer from the map
593 >Returns: map (MapQuery.Map) or false
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.
600 var id = layer.remove(); //remove this layer
605 // remove references to this layer that are stored in the
607 return this.map._removeLayer(this.id);
610 ###*layer*.`position([position])`
612 ####**Description**: get/set the `position` of the layer in the layer
615 * **position** an integer setting the new position of the layer in the layer stack
617 >Returns: position (integer) _or_ layer (MapQuery.Layer)
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.
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
630 position: function(pos) {
631 if (pos===undefined) {
632 return this.map.olMap.getLayerIndex(this.olLayer)-1;
635 this.map.olMap.setLayerIndex(this.olLayer, pos+1);
636 this.trigger('changelayer', ['position']);
641 ###*layer*.`up([delta])`
643 ####**Description**: move the layer up in the layer stack of the map
645 * **delta** the amount of layers the layer has to move up in the layer
648 >Returns: layer (MapQuery.Layer)
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
658 layer.up(); //move layer 1 place up
659 layer.up(3); //move layer 3 places up
661 up: function(delta) {
663 var pos = this.position();
669 ###*layer*.`visible([visible])`
671 ####**Description**: get/set the `visible` state of the layer
673 * **visible** a boolean setting the visibility of the layer
675 >Returns: visible (boolean)
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.
682 var vis = layer.visible(); //get the visibility of layer
683 layer.visible(true); //set visibility of layer to true
686 visible: function(vis) {
687 if (vis===undefined) {
688 return this.olLayer.getVisibility();
691 this.olLayer.setVisibility(vis);
692 this.trigger('changelayer', ['visibility']);
697 ###*layer*.`opacity([opacity])`
699 ####**Description**: get/set the `opacity` of the layer
701 * **position** a float [0-1] setting the opacity of the layer
703 >Returns: opacity (float) _or_ layer (MapQuery.Layer)
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.
710 var opac = layer.opacity(); //get opacity of layer
711 layer.opacity(0.7); //set opacity of layer to 0.7
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();
723 this.olLayer.setOpacity(opac);
724 this.trigger('changelayer', ['opacity']);
728 // every event gets the layer passed in
730 // Use the same bind function as for the map
731 this.map.bind.apply(this, arguments);
735 ###*layer*.`trigger(name [, parameters])`
737 ####**Description**: triggers an event on the layer and map
739 * **name** the name of the event
740 * **parameters** additional parameters that will be passed on with the event
742 >Returns: layer (MapQuery.Layer)
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
749 layer.bind('myEvent', function(evt) {
750 console.log('the values are: ' + evt.data[0] + ' and ' + evt.data[1])
752 map.bind('myEvent', function(evt, layer) {
753 console.log('the values are: ' + evt.data[0] + ' and ' + evt.data[1])
755 layer.trigger('myEvent', 'some', 'values');
757 trigger: function() {
758 var args = Array.prototype.slice.call(arguments);
759 this.events.triggerHandler.apply(this.events, args);
761 this._addLayerToArgs(args);
763 this.map.events.triggerHandler.apply(this.map.events, args);
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) {
774 this._addLayerToArgs(args);
775 return this.events.triggerHandler.apply(this.map.events, args);
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) {
785 args[1].unshift(this);
789 ###*layer*.`features([options])`
790 _version added 0.2.0_
791 ####**Description**: get/set the features of a (vector) layer
793 **options** an object of key-value pairs with options to create one or
796 >Returns: [features] (array of MapQuery.Feature)
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.
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();
813 features: function(options) {
815 switch(arguments.length) {
816 // return all features
818 return this._allFeatures();
819 // add new feature(s)
821 if (!$.isArray(options)) {
822 return this._addFeature(options);
825 return $.map(options, function(feature) {
826 return self._addFeature(feature);
831 throw('wrong argument number');
834 _allFeatures: function() {
836 return $.map(layer.olLayer.features, function(feature) {
837 return new $.MapQuery.Feature(layer, {olFeature: feature});
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) {
847 this.olLayer.addFeatures(feature.olFeature);
848 this.trigger('addfeature', [feature]);
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.
860 TODO vmx 20110905: Support other geometry types than GeoJSON
862 * geometry: A GeoJSON geometry
863 * properties: Properties for the feature
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) {
869 this._id = layer.map._createId();
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;
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);
886 new OpenLayers.Projection(this.layer.map.displaProjection),
887 new OpenLayers.Projection(this.layer.map.projection));
889 this.olFeature = new OpenLayers.Feature.Vector(geometry,
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));
903 $.MapQuery.Feature.prototype = {
905 ###*feature*.`remove()`
906 _version added 0.2.0_
907 ####**Description**: remove the feature from the layer
909 >Returns: layer (layer) or false
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.
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
922 if (this.layer._triggerReturn('preremovefeature', [this])===false) {
925 this.layer.olLayer.removeFeatures(this.olFeature);
926 // The `removefeature` event is triggered by an OpenLayes event handler
930 ###*feature*.`select(exclusive)`
931 _version added 0.2.0_
932 ####**Description**: select a feature
934 **exclusive** (boolean, default: true) True means that all other features get
937 >Returns: layer (layer)
940 The `.select()` method allows us to select a feature from the layer.
941 A `featureselected` will be fired.
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
948 select: function(exclusive) {
949 if (exclusive===undefined || exclusive===true) {
950 this.layer.map.selectFeatureControl.unselectAll();
952 this.layer.map.selectFeatureControl.select(this.olFeature);
955 ###*feature*.`unselect()`
956 _version added 0.2.0_
957 ####**Description**: unselect a feature
959 >Returns: layer (layer)
962 The `.unselect()` method allows us to unselect a feature from the layer.
963 A `featureunselected` will be fired.
965 // add a feature to a layer
966 var feature = layer.features({geometry: {type: "Point", coordinates: [5.3, 7.4]}});
967 // select the feature
969 // unselect the feature again
972 unselect: function() {
973 this.layer.map.selectFeatureControl.unselect(this.olFeature);
977 $.fn.mapQuery = function(options) {
978 return this.each(function() {
979 var instance = $.data(this, 'mapQuery');
981 $.data(this, 'mapQuery', new $.MapQuery.Map($(this), options));
986 $.extend($.MapQuery.Layer, {
989 ###*layer* `{type:bing}`
991 ####**Description**: create a Bing maps layer
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
1001 type:'bing', //create a bing maps layer
1002 view:'satellite', //use the bing satellite layer
1003 key:'ArAGGPJ16xm0RX' //the Bing maps API key
1007 bing: function(options) {
1008 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1009 $.fn.mapQuery.defaults.layer.bing,
1014 view = 'Road'; break;
1016 view = 'AerialWithLabels'; break;
1018 view = 'Aerial'; break;
1021 layer: new OpenLayers.Layer.Bing({type:view,key:o.key}),
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,
1032 layer: new OpenLayers.Layer.ECWP(o.label, o.url, o),
1037 ###*layer* `{type:google}`
1039 ####**Description**: create a Google maps layer
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
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&sensor=false"type="text/javascript"></script>`
1051 type:'google', //create a google maps layer
1052 view:'hybrid' //use the google hybridlayer
1056 google: function(options) {
1057 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1058 $.fn.mapQuery.defaults.layer.google,
1063 view = google.maps.MapTypeId.ROADMAP; break;
1065 view = google.maps.MapTypeId.TERRAIN; break;
1067 view = google.maps.MapTypeId.HYBRID; break;
1069 view = google.maps.MapTypeId.SATELLITE; break;
1072 layer: new OpenLayers.Layer.Google({type:view}),
1077 ###*layer* `{type:vector}`
1079 ####**Description**: create a vector layer
1081 * **label** string with the name of the layer
1085 type:'vector' //create a vector layer
1089 vector: function(options) {
1090 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1091 $.fn.mapQuery.defaults.layer.vector,
1093 this.isVector = true;
1095 layer: new OpenLayers.Layer.Vector(o.label),
1100 ###*layer* `{type:json}`
1102 ####**Description**: create a JSON layer
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
1115 url: 'data/reservate.json',
1120 json: function(options) {
1121 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1122 $.fn.mapQuery.defaults.layer.vector,
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()) {
1130 strategies.push(new OpenLayers.Strategy.BBOX());
1133 strategies.push(new OpenLayers.Strategy.Cluster());
1136 strategies.push(new OpenLayers.Strategy.Filter());
1139 strategies.push(new OpenLayers.Strategy.Fixed());
1142 strategies.push(new OpenLayers.Strategy.Paging());
1145 strategies.push(new OpenLayers.Strategy.Refresh());
1148 strategies.push(new OpenLayers.Strategy.Save());
1156 strategies: strategies,
1157 projection: o.projection || 'EPSG:4326',
1158 styleMap: o.styleMap
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';
1170 params.protocol = new OpenLayers.Protocol[protocol]({
1172 format: new OpenLayers.Format.GeoJSON()
1176 var layer = new OpenLayers.Layer.Vector(o.label, params);
1183 ###*layer* `{type:osm}`
1185 ####**Description**: create an OpenStreetMap layer
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
1191 * **attribution** A string to put some attribution on the map
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'
1200 attribution: "Data © 2009 <a href='http://openstreetmap.org/'>
1201 OpenStreetMap</a>. Rendering © 2009
1202 <a href='http://cloudmade.com'>CloudMade</a>."
1206 osm: function(options) {
1207 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1208 $.fn.mapQuery.defaults.layer.osm,
1210 var label = options.label || undefined;
1211 var url = options.url || undefined;
1213 layer: new OpenLayers.Layer.OSM(label, url, o),
1218 ###*layer* `{type:tms}`
1220 ####**Description**: create an OpenStreetMap layer
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)
1232 url: 'http://tilecache.osgeo.org/wms-c/Basic.py/',
1237 tms: function(options) {
1238 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1239 $.fn.mapQuery.defaults.layer.tms,
1241 var label = options.label || undefined;
1242 var url = options.url || undefined;
1248 layer: new OpenLayers.Layer.TMS(label, url, params),
1253 ###*layer* `{type:wms}`
1255 ####**Description**: create a WMS layer
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
1267 url:'http://vmap0.tiles.osgeo.org/wms/vmap0',
1272 wms: function(options) {
1273 var o = $.extend(true, {}, $.fn.mapQuery.defaults.layer.all,
1274 $.fn.mapQuery.defaults.layer.raster,
1278 transparent: o.transparent,
1281 if(typeof o.wms_parameters != "undefined"){
1282 params = $.extend(params, o.wms_parameters);
1285 layer: new OpenLayers.Layer.WMS(o.label, o.url, params, o),
1289 //TODO complete this documentation
1291 ###*layer* `{type:wmts}`
1293 ####**Description**: create a WMTS (tiling) layer
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
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) {
1313 maxExtent: new OpenLayers.Bounds(
1314 -128 * 156543.0339, -128 * 156543.0339,
1315 128 * 156543.0339, 128 * 156543.0339),
1316 maxResolution: 156543.0339,
1318 projection: 'EPSG:900913',
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);
1327 // remove trailing slash
1328 if (params.url.charAt(params.url.length-1)==='/') {
1329 params.url = params.url.slice(0, params.url.length-1);
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];
1347 layer: new OpenLayers.Layer.WMTS(params),
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
1360 // Remove quirky moveTo behavior, probably not a good idea in the
1364 // Since OL2.11 the Navigation control includes touch navigation as well
1365 new OpenLayers.Control.Navigation({
1372 new OpenLayers.Control.ArgParser(),
1373 new OpenLayers.Control.Attribution(),
1374 new OpenLayers.Control.KeyboardDefaults()
1376 format: 'image/png',
1377 maxExtent: [-128*156543.0339,
1381 maxResolution: 156543.0339,
1383 projection: 'EPSG:900913',
1384 displayProjection: 'EPSG:4326',
1385 zoomToMaxExtent: true,
1392 //in general it is kinda pointless to load tiles outside a maxextent
1393 displayOutsideMaxExtent: false
1396 transitionEffect: 'resize',
1398 sphericalMercator: true
1401 transitionEffect: 'resize',
1403 sphericalMercator: true
1406 transitionEffect: 'resize',
1407 sphericalMercator: true
1410 transitionEffect: 'resize',
1414 // options for raster layers
1418 // options for vector layers
1419 strategies: ['bbox']
1422 format: 'image/jpeg',
1423 requestEncoding: 'REST',
1424 sphericalMercator: false
1429 // Some utility functions
1431 $.MapQuery.util = {};
1432 // http://blog.stevenlevithan.com/archives/parseuri (2010-12-18)
1434 // (c) Steven Levithan <stevenlevithan.com>
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),
1444 while (i--) {uri[o.key[i]] = m[i] || "";}
1447 uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
1448 if ($1) {uri[o.q.name][$1] = $2;}
1453 $.MapQuery.util.parseUri.options = {
1455 key: ["source", "protocol", "authority", "userInfo", "user",
1456 "password", "host", "port", "relative", "path", "directory",
1457 "file", "query", "anchor"],
1460 parser: /(?:^|&)([^&=]*)=?([^&]*)/g
1463 strict: /^(?:([^:\/?#]+:))?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
1464 loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+:))?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
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";
1474 domain: document.domain,
1475 port: window.location.port,
1476 protocol: window.location.protocol
1478 current.port = current.port || "80";
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;