]> git.openstreetmap.org Git - rails.git/blob - public/lib/OpenLayers/Map.js
Merged 17073:17076 from trunk.
[rails.git] / public / lib / OpenLayers / Map.js
1 /* Copyright (c) 2006 MetaCarta, Inc., published under the BSD license.
2  * See http://svn.openlayers.org/trunk/openlayers/license.txt for the full
3  * text of the license. */
4 // @require: OpenLayers/Util.js
5 /**
6 * @class
7 *
8 *
9 */
10
11 OpenLayers.Map = Class.create();
12 OpenLayers.Map.prototype = {
13     // Hash: base z-indexes for different classes of thing 
14     Z_INDEX_BASE: { Layer: 100, Popup: 200, Control: 1000 },
15
16     // Array: supported application event types
17     EVENT_TYPES: [ 
18         "addlayer", "removelayer", "movestart", "move", "moveend",
19         "zoomend", "layerchanged", "popupopen", "popupclose",
20         "addmarker", "removemarker", "clearmarkers", "mouseover",
21         "mouseout", "mousemove", "dragstart", "drag", "dragend" ],
22
23     // int: zoom levels, used to draw zoom dragging control and limit zooming
24     maxZoomLevel: 16,
25
26     // OpenLayers.Bounds
27     maxExtent: new OpenLayers.Bounds(-180, -90, 180, 90),
28
29     /* projection */
30     projection: "EPSG:4326",
31
32     /** @type OpenLayers.Size */
33     size: null,
34
35     // float
36     maxResolution: 1.40625, // degrees per pixel 
37                             // Default is whole world in 256 pixels, from GMaps
38
39     // DOMElement: the div that our map lives in
40     div: null,
41
42     // HTMLDivElement: the map's view port             
43     viewPortDiv: null,
44
45     // HTMLDivElement: the map's layer container
46     layerContainerDiv: null,
47
48     // Array(OpenLayers.Layer): ordered list of layers in the map
49     layers: null,
50
51     // Array(OpenLayers.Control)
52     controls: null,
53
54     // Array(OpenLayers.Popup)
55     popups: null,
56
57     // OpenLayers.LonLat
58     center: null,
59
60     // int
61     zoom: null,
62
63     // OpenLayers.Events
64     events: null,
65
66     // OpenLayers.Pixel
67     mouseDragStart: null,
68
69     /** @type OpenLayers.Layer */
70     baseLayer: null,
71
72     /**
73     * @param {DOMElement} div
74     */    
75     initialize: function (div, options) {
76         Object.extend(this, options);
77
78         this.div = div = $(div);
79
80         // the viewPortDiv is the outermost div we modify
81         var id = div.id + "_OpenLayers_ViewPort";
82         this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
83                                                      "relative", null,
84                                                      "hidden");
85         this.viewPortDiv.style.width = "100%";
86         this.viewPortDiv.style.height = "100%";
87         this.div.appendChild(this.viewPortDiv);
88
89         // the layerContainerDiv is the one that holds all the layers
90         id = div.id + "_OpenLayers_Container";
91         this.layerContainerDiv = OpenLayers.Util.createDiv(id);
92         this.viewPortDiv.appendChild(this.layerContainerDiv);
93
94         this.events = new OpenLayers.Events(this, div, this.EVENT_TYPES);
95
96         this.updateSize();
97         // make the entire maxExtent fix in zoom level 0 by default
98         if (this.maxResolution == null || this.maxResolution == "auto") {
99             this.maxResolution = Math.max(
100                 this.maxExtent.getWidth()  / this.size.w,
101                 this.maxExtent.getHeight() / this.size.h );
102         }
103         // update the internal size register whenever the div is resized
104         this.events.register("resize", this, this.updateSize);
105
106         this.layers = [];
107         
108         if (!this.controls) {
109             this.controls = [];
110             this.addControl(new OpenLayers.Control.MouseDefaults());
111             this.addControl(new OpenLayers.Control.PanZoom());
112         }
113
114         this.popups = new Array();
115
116         // always call map.destroy()
117         Event.observe(window, 'unload', 
118             this.destroy.bindAsEventListener(this));
119     },
120
121     /**
122     * @private
123     */
124     destroy:function() {
125         if (this.layers != null) {
126             for(var i=0; i< this.layers.length; i++) {
127                 this.layers[i].destroy();
128             } 
129             this.layers = null;
130         }
131         if (this.controls != null) {
132             for(var i=0; i< this.controls.length; i++) {
133                 this.controls[i].destroy();
134             } 
135             this.controls = null;
136         }
137     },
138
139     /**
140     * @param {OpenLayers.Layer} layer
141     */    
142     addLayer: function (layer) {
143         layer.setMap(this);
144         layer.div.style.overflow = "";
145         layer.div.style.zIndex = this.Z_INDEX_BASE['Layer'] + this.layers.length;
146
147         if (layer.viewPortLayer) {
148             this.viewPortDiv.appendChild(layer.div);
149         } else {
150             this.layerContainerDiv.appendChild(layer.div);
151         }
152         this.layers.push(layer);
153
154         // hack hack hack - until we add a more robust layer switcher,
155         //   which is able to determine which layers are base layers and 
156         //   which are not (and put baselayers in a radiobutton group and 
157         //   other layers in checkboxes) this seems to be the most straight-
158         //   forward way of dealing with this. 
159         //
160         if (layer.isBaseLayer()) {
161             this.baseLayer = layer;
162         }
163         this.events.triggerEvent("addlayer");
164     },
165
166     /** Removes a layer from the map by removing its visual element (the 
167      *   layer.div property), then removing it from the map's internal list 
168      *   of layers, setting the layer's map property to null. 
169      * 
170      *   a "removelayer" event is triggered.
171      * 
172      *   very worthy of mention is that simply removing a layer from a map
173      *   will not cause the removal of any popups which may have been created
174      *   by the layer. this is due to the fact that it was decided at some
175      *   point that popups would not belong to layers. thus there is no way 
176      *   for us to know here to which layer the popup belongs.
177      *    
178      *     A simple solution to this is simply to call destroy() on the layer.
179      *     the default OpenLayers.Layer class's destroy() function
180      *     automatically takes care to remove itself from whatever map it has
181      *     been attached to. 
182      * 
183      *     The correct solution is for the layer itself to register an 
184      *     event-handler on "removelayer" and when it is called, if it 
185      *     recognizes itself as the layer being removed, then it cycles through
186      *     its own personal list of popups, removing them from the map.
187      * 
188      * @param {OpenLayers.Layer} layer
189      */
190     removeLayer: function(layer) {
191         this.layerContainerDiv.removeChild(layer.div);
192         this.layers.remove(layer);
193         layer.map = null;
194         this.events.triggerEvent("removelayer");
195     },
196
197     /**
198     * @param {Array(OpenLayers.Layer)} layers
199     */    
200     addLayers: function (layers) {
201         for (var i = 0; i <  layers.length; i++) {
202             this.addLayer(layers[i]);
203         }
204     },
205
206     /**
207     * @param {OpenLayers.Control} control
208     * @param {OpenLayers.Pixel} px
209     */    
210     addControl: function (control, px) {
211         control.map = this;
212         this.controls.push(control);
213         var div = control.draw(px);
214         if (div) {
215             div.style.zIndex = this.Z_INDEX_BASE['Control'] +
216                                 this.controls.length;
217             this.viewPortDiv.appendChild( div );
218         }
219     },
220
221     /** 
222     * @param {OpenLayers.Popup} popup
223     */
224     addPopup: function(popup) {
225         popup.map = this;
226         this.popups.push(popup);
227         var popupDiv = popup.draw();
228         if (popupDiv) {
229             popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
230                                     this.popups.length;
231             this.layerContainerDiv.appendChild(popupDiv);
232         }
233     },
234     
235     /** 
236     * @param {OpenLayers.Popup} popup
237     */
238     removePopup: function(popup) {
239         this.popups.remove(popup);
240         if (popup.div) {
241             this.layerContainerDiv.removeChild(popup.div);
242         }
243         popup.map = null;
244     },
245         
246     /**
247     * @return {float}
248     */
249     getResolution: function () {
250         // return degrees per pixel
251         return this.maxResolution / Math.pow(2, this.zoom);
252     },
253
254     /**
255     * @return {int}
256     */
257     getZoom: function () {
258         return this.zoom;
259     },
260
261     /**
262     * @returns {OpenLayers.Size}
263     */
264     getSize: function () {
265         return this.size;
266     },
267
268     /**
269     * @private
270     */
271     updateSize: function() {
272         this.size = new OpenLayers.Size(
273                     this.div.clientWidth, this.div.clientHeight);
274         this.events.div.offsets = null;
275         // Workaround for the fact that hidden elements return 0 for size.
276         if (this.size.w == 0 && this.size.h == 0) {
277             var dim = Element.getDimensions(this.div);
278             this.size.w = dim.width;
279             this.size.h = dim.height;
280         }
281         if (this.size.w == 0 && this.size.h == 0) {
282             this.size.w = parseInt(this.div.style.width);
283             this.size.h = parseInt(this.div.style.height);
284         }
285     },
286
287     /**
288     * @return {OpenLayers.LonLat}
289     */
290     getCenter: function () {
291         return this.center;
292     },
293
294     /**
295     * @return {OpenLayers.Bounds}
296     */
297     getExtent: function () {
298         if (this.center) {
299             var res = this.getResolution();
300             var size = this.getSize();
301             var w_deg = size.w * res;
302             var h_deg = size.h * res;
303             return new OpenLayers.Bounds(
304                 this.center.lon - w_deg / 2, 
305                 this.center.lat - h_deg / 2,
306                 this.center.lon + w_deg / 2,
307                 this.center.lat + h_deg / 2);
308         } else {
309             return null;
310         }
311     },
312
313     /**
314     * @return {OpenLayers.Bounds}
315     */
316     getFullExtent: function () {
317         return this.maxExtent;
318     },
319     
320     getZoomLevels: function() {
321         return this.maxZoomLevel;
322     },
323
324     /**
325     * @param {OpenLayers.Bounds} bounds
326     *
327     * @return {int}
328     */
329     getZoomForExtent: function (bounds) {
330         var size = this.getSize();
331         var width = bounds.getWidth();
332         var height = bounds.getHeight();
333         var deg_per_pixel = (width > height ? width / size.w : height / size.h);
334         var zoom = Math.log(this.maxResolution / deg_per_pixel) / Math.log(2);
335         return Math.floor(Math.min(Math.max(zoom, 0), this.getZoomLevels())); 
336     },
337     
338     /**
339      * @param {OpenLayers.Pixel} layerPx
340      * 
341      * @returns px translated into view port pixel coordinates
342      * @type OpenLayers.Pixel
343      * @private
344      */
345     getViewPortPxFromLayerPx:function(layerPx) {
346         var viewPortPx = layerPx.copyOf();
347
348         viewPortPx.x += parseInt(this.layerContainerDiv.style.left);
349         viewPortPx.y += parseInt(this.layerContainerDiv.style.top);
350
351         return viewPortPx;
352     },
353     
354     /**
355      * @param {OpenLayers.Pixel} viewPortPx
356      * 
357      * @returns px translated into view port pixel coordinates
358      * @type OpenLayers.Pixel
359      * @private
360      */
361     getLayerPxFromViewPortPx:function(viewPortPx) {
362         var layerPx = viewPortPx.copyOf();
363
364         layerPx.x -= parseInt(this.layerContainerDiv.style.left);
365         layerPx.y -= parseInt(this.layerContainerDiv.style.top);
366
367         return layerPx;
368     },
369
370
371     /**
372     * @param {OpenLayers.Pixel} px
373     *
374     * @return {OpenLayers.LonLat} 
375     */
376     getLonLatFromLayerPx: function (px) {
377        //adjust for displacement of layerContainerDiv
378        px = this.getViewPortPxFromLayerPx(px);
379        return this.getLonLatFromViewPortPx(px);         
380     },
381     
382     /**
383     * @param {OpenLayers.Pixel} viewPortPx
384     *
385     * @returns An OpenLayers.LonLat which is the passed-in view port
386     *          OpenLayers.Pixel, translated into lon/lat given the 
387     *          current extent and resolution
388     * @type OpenLayers.LonLat
389     * @private
390     */
391     getLonLatFromViewPortPx: function (viewPortPx) {
392         var center = this.getCenter();        //map center lon/lat
393         var res  = this.getResolution();
394         var size = this.getSize();
395     
396         var delta_x = viewPortPx.x - (size.w / 2);
397         var delta_y = viewPortPx.y - (size.h / 2);
398         
399         return new OpenLayers.LonLat(center.lon + delta_x * res ,
400                                      center.lat - delta_y * res); 
401     },
402
403     // getLonLatFromPixel is a convenience function for the API
404     /**
405     * @param {OpenLayers.Pixel} pixel
406     *
407     * @returns An OpenLayers.LonLat corresponding to the given
408     *          OpenLayers.Pixel, translated into lon/lat using the 
409     *          current extent and resolution
410     * @type OpenLayers.LonLat
411     */
412     getLonLatFromPixel: function (px) {
413         return this.getLonLatFromViewPortPx(px);
414     },
415
416     /**
417     * @param {OpenLayers.LonLat} lonlat
418     *
419     * @returns An OpenLayers.Pixel which is the passed-in OpenLayers.LonLat, 
420     *          translated into layer pixels given the current extent 
421     *          and resolution
422     * @type OpenLayers.Pixel
423     */
424     getLayerPxFromLonLat: function (lonlat) {
425        //adjust for displacement of layerContainerDiv
426        var px = this.getViewPortPxFromLonLat(lonlat);
427        return this.getLayerPxFromViewPortPx(px);         
428     },
429
430     /**
431     * @param {OpenLayers.LonLat} lonlat
432     *
433     * @returns An OpenLayers.Pixel which is the passed-in OpenLayers.LonLat, 
434     *          translated into view port pixels given the current extent 
435     *          and resolution
436     * @type OpenLayers.Pixel
437     * @private
438     */
439     getViewPortPxFromLonLat: function (lonlat) {
440         var resolution = this.getResolution();
441         var extent = this.getExtent();
442         return new OpenLayers.Pixel(
443                        Math.round(1/resolution * (lonlat.lon - extent.left)),
444                        Math.round(1/resolution * (extent.top - lonlat.lat))
445                        );    
446     },
447
448     // getLonLatFromPixel is a convenience function for the API
449     /**
450     * @param {OpenLayers.LonLat} lonlat
451     *
452     * @returns An OpenLayers.Pixel corresponding to the OpenLayers.LonLat
453     *          translated into view port pixels using the current extent 
454     *          and resolution
455     * @type OpenLayers.Pixel
456     */
457     getPixelFromLonLat: function (lonlat) {
458         return this.getViewPortPxFromLonLat(lonlat);
459     },
460
461     /**
462     * @param {OpenLayers.LonLat} lonlat
463     * @param {int} zoom
464     */
465     setCenter: function (lonlat, zoom, minor) {
466         if (this.center) { // otherwise there's nothing to move yet
467             this.moveLayerContainer(lonlat);
468         }
469         this.center = lonlat.copyOf();
470         var zoomChanged = null;
471         if (zoom != null && zoom != this.zoom 
472             && zoom >= 0 && zoom <= this.getZoomLevels()) {
473             zoomChanged = (this.zoom == null ? 0 : this.zoom);
474             this.zoom = zoom;
475         }
476
477         if (!minor) this.events.triggerEvent("movestart");
478         this.moveToNewExtent(zoomChanged, minor);
479         if (!minor) this.events.triggerEvent("moveend");
480     },
481     
482     /**
483      * ZOOM TO BOUNDS FUNCTION
484      * @private
485      */
486     moveToNewExtent: function (zoomChanged, minor) {
487         if (zoomChanged != null) { // reset the layerContainerDiv's location
488             this.layerContainerDiv.style.left = "0px";
489             this.layerContainerDiv.style.top  = "0px";
490
491             //redraw popups
492             for (var i = 0; i < this.popups.length; i++) {
493                 this.popups[i].updatePosition();
494             }
495
496         }
497         var bounds = this.getExtent();
498         for (var i = 0; i < this.layers.length; i++) {
499             this.layers[i].moveTo(bounds, (zoomChanged != null), minor);
500         }
501         this.events.triggerEvent("move");
502         if (zoomChanged != null)
503             this.events.triggerEvent("zoomend", 
504                 {oldZoom: zoomChanged, newZoom: this.zoom});
505     },
506
507     /**
508      * zoomIn
509      * Increase zoom level by one.
510      * @param {int} zoom
511      */
512     zoomIn: function() {
513         if (this.zoom != null && this.zoom <= this.getZoomLevels()) {
514             this.zoomTo( this.zoom += 1 );
515         }
516     },
517     
518     /**
519      * zoomTo
520      * Set Zoom To int
521      * @param {int} zoom
522      */
523     zoomTo: function(zoom) {
524        if (zoom >= 0 && zoom <= this.getZoomLevels()) {
525             var oldZoom = this.zoom;
526             this.zoom = zoom;
527             this.moveToNewExtent(oldZoom);
528        }
529     },
530
531     /**
532      * zoomOut
533      * Decrease zoom level by one.
534      * @param {int} zoom
535      */
536     zoomOut: function() {
537         if (this.zoom != null && this.zoom > 0) {
538             this.zoomTo( this.zoom - 1 );
539         }
540     },
541     
542     /**
543      * zoomToFullExtent
544      * Zoom to the full extent and recenter.
545      */
546     zoomToFullExtent: function() {
547         var fullExtent = this.getFullExtent();
548         this.setCenter(
549           new OpenLayers.LonLat((fullExtent.left+fullExtent.right)/2,
550                                 (fullExtent.bottom+fullExtent.top)/2),
551           this.getZoomForExtent(fullExtent)
552         );
553     },
554
555     /**
556     * @param {OpenLayers.LonLat} lonlat
557     * @private
558     */
559     moveLayerContainer: function (lonlat) {
560         var container = this.layerContainerDiv;
561         var resolution = this.getResolution();
562
563         var deltaX = Math.round((this.center.lon - lonlat.lon) / resolution);
564         var deltaY = Math.round((this.center.lat - lonlat.lat) / resolution);
565      
566         var offsetLeft = parseInt(container.style.left);
567         var offsetTop  = parseInt(container.style.top);
568
569         container.style.left = (offsetLeft + deltaX) + "px";
570         container.style.top  = (offsetTop  - deltaY) + "px";
571     },
572
573     CLASS_NAME: "OpenLayers.Map"
574 };