Project

General

Profile

Download (13.7 KB) Statistics
| Branch: | Tag: | Revision:
1
/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
2
 * full list of contributors). Published under the 2-clause BSD license.
3
 * See license.txt in the OpenLayers distribution or repository for the
4
 * full text of the license. */
5

    
6
/**
7
 * @requires OpenLayers/Control.js
8
 * @requires OpenLayers/Lang.js
9
 * @requires OpenLayers/Rule.js
10
 * @requires OpenLayers/StyleMap.js
11
 * @requires OpenLayers/Layer/Vector.js
12
 */
13

    
14
/**
15
 * Class: OpenLayers.Control.Graticule
16
 * The Graticule displays a grid of latitude/longitude lines reprojected on
17
 * the map.  
18
 * 
19
 * Inherits from:
20
 *  - <OpenLayers.Control>
21
 *  
22
 */
23
OpenLayers.Control.Graticule = OpenLayers.Class(OpenLayers.Control, {
24

    
25
    /**
26
     * APIProperty: autoActivate
27
     * {Boolean} Activate the control when it is added to a map. Default is
28
     *     true. 
29
     */
30
    autoActivate: true,
31
    
32
    /**
33
    * APIProperty: intervals
34
    * {Array(Float)} A list of possible graticule widths in degrees.
35
    */
36
    intervals: [ 45, 30, 20, 10, 5, 2, 1,
37
                 0.5, 0.2, 0.1, 0.05, 0.01, 
38
                 0.005, 0.002, 0.001 ],
39

    
40
    /**
41
     * APIProperty: displayInLayerSwitcher
42
     * {Boolean} Allows the Graticule control to be switched on and off by 
43
     *     LayerSwitcher control. Defaults is true.
44
     */
45
    displayInLayerSwitcher: true,
46

    
47
    /**
48
     * APIProperty: visible
49
     * {Boolean} should the graticule be initially visible (default=true)
50
     */
51
    visible: true,
52

    
53
    /**
54
     * APIProperty: numPoints
55
     * {Integer} The number of points to use in each graticule line.  Higher
56
     * numbers result in a smoother curve for projected maps 
57
     */
58
    numPoints: 50,
59

    
60
    /**
61
     * APIProperty: targetSize
62
     * {Integer} The maximum size of the grid in pixels on the map
63
     */
64
    targetSize: 200,
65

    
66
    /**
67
     * APIProperty: layerName
68
     * {String} The name to be displayed in the layer switcher, default is set 
69
     *     by {<OpenLayers.Lang>}.
70
     */
71
    layerName: null,
72

    
73
    /**
74
     * APIProperty: labelled
75
     * {Boolean} Should the graticule lines be labelled?. default=true
76
     */
77
    labelled: true,
78

    
79
    /**
80
     * APIProperty: labelFormat
81
     * {String} the format of the labels, default = 'dm'. See
82
     * <OpenLayers.Util.getFormattedLonLat> for other options.
83
     */
84
    labelFormat: 'dm',
85

    
86
    /**
87
     * APIProperty: lineSymbolizer
88
     * {symbolizer} the symbolizer used to render lines
89
     */
90
    lineSymbolizer: {
91
                strokeColor: "#333",
92
                strokeWidth: 1,
93
                strokeOpacity: 0.5
94
            },
95

    
96
    /**
97
     * APIProperty: labelSymbolizer
98
     * {symbolizer} the symbolizer used to render labels
99
     */
100
     labelSymbolizer: {},
101

    
102
    /**
103
     * Property: gratLayer
104
     * {<OpenLayers.Layer.Vector>} vector layer used to draw the graticule on
105
     */
106
    gratLayer: null,
107

    
108
    /**
109
     * Constructor: OpenLayers.Control.Graticule
110
     * Create a new graticule control to display a grid of latitude longitude
111
     * lines.
112
     * 
113
     * Parameters:
114
     * options - {Object} An optional object whose properties will be used
115
     *     to extend the control.
116
     */
117
    initialize: function(options) {
118
        options = options || {};
119
        options.layerName = options.layerName || OpenLayers.i18n("Graticule");
120
        OpenLayers.Control.prototype.initialize.apply(this, [options]);
121
        
122
        this.labelSymbolizer.stroke = false;
123
        this.labelSymbolizer.fill = false;
124
        this.labelSymbolizer.label = "${label}";
125
        this.labelSymbolizer.labelAlign = "${labelAlign}";
126
        this.labelSymbolizer.labelXOffset = "${xOffset}";
127
        this.labelSymbolizer.labelYOffset = "${yOffset}";
128
    },
129

    
130
    /**
131
     * APIMethod: destroy
132
     */
133
    destroy: function() {
134
        this.deactivate();        
135
        OpenLayers.Control.prototype.destroy.apply(this, arguments);        
136
        if (this.gratLayer) {
137
            this.gratLayer.destroy();
138
            this.gratLayer = null;
139
        }
140
    },
141
    
142
    /**
143
     * Method: draw
144
     *
145
     * initializes the graticule layer and does the initial update
146
     * 
147
     * Returns:
148
     * {DOMElement}
149
     */
150
    draw: function() {
151
        OpenLayers.Control.prototype.draw.apply(this, arguments);
152
        if (!this.gratLayer) {
153
            var gratStyle = new OpenLayers.Style({},{
154
                rules: [new OpenLayers.Rule({'symbolizer':
155
                    {"Point":this.labelSymbolizer,
156
                     "Line":this.lineSymbolizer}
157
                })]
158
            });
159
            this.gratLayer = new OpenLayers.Layer.Vector(this.layerName, {
160
                styleMap: new OpenLayers.StyleMap({'default':gratStyle}),
161
                visibility: this.visible,
162
                displayInLayerSwitcher: this.displayInLayerSwitcher
163
            });
164
        }
165
        return this.div;
166
    },
167

    
168
     /**
169
     * APIMethod: activate
170
     */
171
    activate: function() {
172
        if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
173
            this.map.addLayer(this.gratLayer);
174
            this.map.events.register('moveend', this, this.update);     
175
            this.update();
176
            return true;            
177
        } else {
178
            return false;
179
        }
180
    },
181
    
182
    /**
183
     * APIMethod: deactivate
184
     */
185
    deactivate: function() {
186
        if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
187
            this.map.events.unregister('moveend', this, this.update);
188
            this.map.removeLayer(this.gratLayer);
189
            return true;
190
        } else {
191
            return false;
192
        }
193
    },
194
    /**
195
     * Method: update
196
     *
197
     * calculates the grid to be displayed and actually draws it
198
     * 
199
     * Returns:
200
     * {DOMElement}
201
     */
202
    update: function() {
203
        //wait for the map to be initialized before proceeding
204
        var mapBounds = this.map.getExtent();
205
        if (!mapBounds) {
206
            return;
207
        }
208
        
209
        //clear out the old grid
210
        this.gratLayer.destroyFeatures();
211
        
212
        //get the projection objects required
213
        var llProj = new OpenLayers.Projection("EPSG:4326");
214
        var mapProj = this.map.getProjectionObject();
215
        var mapRes = this.map.getResolution();
216
        
217
        //if the map is in lon/lat, then the lines are straight and only one
218
        //point is required
219
        if (mapProj.proj && mapProj.proj.projName == "longlat") {
220
            this.numPoints = 1;
221
        }
222
        
223
        //get the map center in EPSG:4326
224
        var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y
225
        var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat);
226
        OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj);
227
        
228
        /* This block of code determines the lon/lat interval to use for the
229
         * grid by calculating the diagonal size of one grid cell at the map
230
         * center.  Iterates through the intervals array until the diagonal
231
         * length is less than the targetSize option.
232
         */
233
        //find lat/lon interval that results in a grid of less than the target size
234
        var testSq = this.targetSize*mapRes;
235
        testSq *= testSq;   //compare squares rather than doing a square root to save time
236
        var llInterval;
237
        for (var i=0; i<this.intervals.length; ++i) {
238
            llInterval = this.intervals[i];   //could do this for both x and y??
239
            var delta = llInterval/2;  
240
            var p1 = mapCenterLL.offset({x: -delta, y: -delta});  //test coords in EPSG:4326 space
241
            var p2 = mapCenterLL.offset({x: delta, y: delta});
242
            OpenLayers.Projection.transform(p1, llProj, mapProj); // convert them back to map projection
243
            OpenLayers.Projection.transform(p2, llProj, mapProj);
244
            var distSq = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y);
245
            if (distSq <= testSq) {
246
                break;
247
            }
248
        }
249
        //alert(llInterval);
250
        
251
        //round the LL center to an even number based on the interval
252
        mapCenterLL.x = Math.floor(mapCenterLL.x/llInterval)*llInterval;
253
        mapCenterLL.y = Math.floor(mapCenterLL.y/llInterval)*llInterval;
254
        //TODO adjust for minutses/seconds?
255
        
256
        /* The following 2 blocks calculate the nodes of the grid along a 
257
         * line of constant longitude (then latitiude) running through the
258
         * center of the map until it reaches the map edge.  The calculation
259
         * goes from the center in both directions to the edge.
260
         */
261
        //get the central longitude line, increment the latitude
262
        var iter = 0;
263
        var centerLonPoints = [mapCenterLL.clone()];
264
        var newPoint = mapCenterLL.clone();
265
        var mapXY;
266
        do {
267
            newPoint = newPoint.offset({x: 0, y: llInterval});
268
            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
269
            centerLonPoints.unshift(newPoint);
270
        } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
271
        newPoint = mapCenterLL.clone();
272
        do {          
273
            newPoint = newPoint.offset({x: 0, y: -llInterval});
274
            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
275
            centerLonPoints.push(newPoint);
276
        } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
277
        
278
        //get the central latitude line, increment the longitude
279
        iter = 0;
280
        var centerLatPoints = [mapCenterLL.clone()];
281
        newPoint = mapCenterLL.clone();
282
        do {
283
            newPoint = newPoint.offset({x: -llInterval, y: 0});
284
            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
285
            centerLatPoints.unshift(newPoint);
286
        } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
287
        newPoint = mapCenterLL.clone();
288
        do {          
289
            newPoint = newPoint.offset({x: llInterval, y: 0});
290
            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
291
            centerLatPoints.push(newPoint);
292
        } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
293
        
294
        //now generate a line for each node in the central lat and lon lines
295
        //first loop over constant longitude
296
        var lines = [];
297
        for(var i=0; i < centerLatPoints.length; ++i) {
298
            var lon = centerLatPoints[i].x;
299
            var pointList = [];
300
            var labelPoint = null;
301
            var latEnd = Math.min(centerLonPoints[0].y, 90);
302
            var latStart = Math.max(centerLonPoints[centerLonPoints.length - 1].y, -90);
303
            var latDelta = (latEnd - latStart)/this.numPoints;
304
            var lat = latStart;
305
            for(var j=0; j<= this.numPoints; ++j) {
306
                var gridPoint = new OpenLayers.Geometry.Point(lon,lat);
307
                gridPoint.transform(llProj, mapProj);
308
                pointList.push(gridPoint);
309
                lat += latDelta;
310
                if (gridPoint.y >= mapBounds.bottom && !labelPoint) {
311
                    labelPoint = gridPoint;
312
                }
313
            }
314
            if (this.labelled) {
315
                //keep track of when this grid line crosses the map bounds to set
316
                //the label position
317
                //labels along the bottom, add 10 pixel offset up into the map
318
                //TODO add option for labels on top
319
                var labelPos = new OpenLayers.Geometry.Point(labelPoint.x,mapBounds.bottom);
320
                var labelAttrs = {
321
                    value: lon,
322
                    label: this.labelled?OpenLayers.Util.getFormattedLonLat(lon, "lon", this.labelFormat):"",
323
                    labelAlign: "cb",
324
                    xOffset: 0,
325
                    yOffset: 2
326
                }; 
327
                this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));
328
            }
329
            var geom = new OpenLayers.Geometry.LineString(pointList);
330
            lines.push(new OpenLayers.Feature.Vector(geom));
331
        }
332
        
333
        //now draw the lines of constant latitude
334
        for (var j=0; j < centerLonPoints.length; ++j) {
335
            lat = centerLonPoints[j].y;
336
            if (lat<-90 || lat>90) {  //latitudes only valid between -90 and 90
337
                continue;
338
            }
339
            var pointList = [];
340
            var lonStart = centerLatPoints[0].x;
341
            var lonEnd = centerLatPoints[centerLatPoints.length - 1].x;
342
            var lonDelta = (lonEnd - lonStart)/this.numPoints;
343
            var lon = lonStart;
344
            var labelPoint = null;
345
            for(var i=0; i <= this.numPoints ; ++i) {
346
                var gridPoint = new OpenLayers.Geometry.Point(lon,lat);
347
                gridPoint.transform(llProj, mapProj);
348
                pointList.push(gridPoint);
349
                lon += lonDelta;
350
                if (gridPoint.x < mapBounds.right) {
351
                    labelPoint = gridPoint;
352
                }
353
            }
354
            if (this.labelled) {
355
                //keep track of when this grid line crosses the map bounds to set
356
                //the label position
357
                //labels along the right, 30 pixel offset left into the map
358
                //TODO add option for labels on left
359
                var labelPos = new OpenLayers.Geometry.Point(mapBounds.right, labelPoint.y); 
360
                var labelAttrs = {
361
                    value: lat,
362
                    label: this.labelled?OpenLayers.Util.getFormattedLonLat(lat, "lat", this.labelFormat):"",
363
                    labelAlign: "rb",
364
                    xOffset: -2,
365
                    yOffset: 2
366
                }; 
367
                this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));
368
            }
369
            var geom = new OpenLayers.Geometry.LineString(pointList);
370
            lines.push(new OpenLayers.Feature.Vector(geom));
371
          }
372
          this.gratLayer.addFeatures(lines);
373
    },
374
    
375
    CLASS_NAME: "OpenLayers.Control.Graticule"
376
});
377

    
(12-12/45)