OpenLayers OpenLayers

Ticket #1083: graticule_v4.patch

File graticule_v4.patch, 22.1 kB (added by madair, 5 months ago)

updated with feedback

  • examples/graticule.html

    old new  
     1<html xmlns="http://www.w3.org/1999/xhtml"> 
     2    <head> 
     3        <title>OpenLayers Graticule Example</title> 
     4 
     5        <link rel="stylesheet" href="../theme/default/style.css" type="text/css" /> 
     6        <link rel="stylesheet" href="style.css" type="text/css" /> 
     7        <style type="text/css"> 
     8            #map { 
     9                width: 600px; 
     10                height: 300px; 
     11                border: 1px solid black; 
     12                float:left; 
     13            } 
     14            #map2 { 
     15                width: 400px; 
     16                height: 400px; 
     17                border: 1px solid black; 
     18                float:left; 
     19            } 
     20        </style> 
     21        <script src="../lib/OpenLayers.js"></script> 
     22        <script src="http://proj4js.org/lib/proj4js-compressed.js"></script> 
     23        <script type="text/javascript"> 
     24            Proj4js.defs["EPSG:42304"]="+title=Atlas of Canada, LCC +proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs"; 
     25             
     26            var map1, map2; 
     27            function init(){ 
     28              initLonLat(); 
     29              initProjected(); 
     30            } 
     31            function initLonLat(){ 
     32                map1 = new OpenLayers.Map('map', { 
     33                    controls: [ 
     34                    new OpenLayers.Control.Graticule({ 
     35                          numPoints: 2,  
     36                          labelled: true, 
     37                          visible: true 
     38                      }), 
     39                      new OpenLayers.Control.LayerSwitcher(), 
     40                      new OpenLayers.Control.PanZoomBar(), 
     41                      new OpenLayers.Control.Navigation() 
     42                      ] 
     43                }); 
     44 
     45                var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS", 
     46                    "http://labs.metacarta.com/wms/vmap0", 
     47                    {layers: 'basic'}, {wrapDateLine: true} ); 
     48 
     49                map1.addLayers([ol_wms]); 
     50                if (!map1.getCenter()) map1.zoomToMaxExtent(); 
     51            }; 
     52             
     53            function initProjected(){ 
     54                var extent = new OpenLayers.Bounds(-2200000,-712631,3072800,3840000); 
     55                var mapOptions = { 
     56                      controls: [ 
     57                      new OpenLayers.Control.Graticule({ 
     58                          labelled: true, 
     59                          targetSize: 200 
     60                        }), 
     61                        new OpenLayers.Control.LayerSwitcher(), 
     62                        new OpenLayers.Control.PanZoomBar(), 
     63                        new OpenLayers.Control.Navigation() 
     64                      ], 
     65                      //scales: tempScales, 
     66                      maxExtent: extent, 
     67                      maxResolution: 50000, 
     68                      units: 'm', 
     69                      projection: 'EPSG:42304' 
     70                }; 
     71                map2 = new OpenLayers.Map('map2', mapOptions); 
     72 
     73                var dm_wms = new OpenLayers.Layer.WMS( "DM Solutions Demo", 
     74                  "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap", { 
     75                     layers: "bathymetry", 
     76                     format: "image/png" 
     77                  },{ 
     78                      singleTile: true 
     79                  }); 
     80 
     81                map2.addLayers([dm_wms]); 
     82                if (!map2.getCenter()) map2.zoomToExtent(extent); 
     83            } 
     84        </script> 
     85    </head> 
     86    <body onload="init()"> 
     87        <h1 id="title">Graticule Example</h1> 
     88 
     89        <div id="tags"> 
     90        </div> 
     91 
     92        <p id="shortdesc"> 
     93            Adds a Graticule control to the map to display a grid of  
     94            latitude and longitude.  
     95        </p> 
     96 
     97        <div id="map" class="smallmap"></div> 
     98        <div id="map2" class="smallmap"></div> 
     99 
     100        <div id="docs"></div> 
     101    </body> 
     102</html> 
  • lib/OpenLayers.js

    old new  
    168168            "OpenLayers/Control/NavigationHistory.js", 
    169169            "OpenLayers/Control/Measure.js", 
    170170            "OpenLayers/Control/WMSGetFeatureInfo.js", 
     171            "OpenLayers/Control/Graticule.js", 
    171172            "OpenLayers/Geometry.js", 
    172173            "OpenLayers/Geometry/Rectangle.js", 
    173174            "OpenLayers/Geometry/Collection.js", 
  • lib/OpenLayers/Control/Graticule.js

    old new  
     1/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license. 
     2 * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt  
     3 * for the full text of the license. */ 
     4 
     5/** 
     6 * @requires OpenLayers/Control.js 
     7 */ 
     8 
     9/** 
     10 * Class: OpenLayers.Control.Graticule 
     11 * The Graticule displays a grid of latitude/longitude lines reprojected on 
     12 * the map.   
     13 *  
     14 * Inherits from: 
     15 *  - <OpenLayers.Control> 
     16 *   
     17 */ 
     18OpenLayers.Control.Graticule = OpenLayers.Class(OpenLayers.Control, { 
     19 
     20    /** 
     21    * APIProperty: intervals 
     22    * {Array(Float)} A list of possible graticule widths in degrees. 
     23    */ 
     24    intervals: [ 45, 30, 20, 10, 5, 2, 1, 
     25                 0.5, 0.2, 0.1, 0.05, 0.01,  
     26                 0.005, 0.002, 0.001 ], 
     27 
     28    /** 
     29     * APIProperty: displayInLayerSwitcher 
     30     * {Boolean} Allows the Graticule control to be switched on and off.  
     31     * defaults to true. 
     32     */ 
     33    displayInLayerSwitcher: true, 
     34 
     35    /** 
     36     * APIProperty: visible 
     37     * {Boolean} should the graticule be initially visible (default=true) 
     38     */ 
     39    visible: true, 
     40 
     41    /** 
     42     * APIProperty: numPoints 
     43     * {Integer} The number of points to use in each graticule line.  Higher 
     44     * numbers result in a smoother curve for projected maps  
     45     */ 
     46    numPoints: 50, 
     47 
     48    /** 
     49     * APIProperty: targetSize 
     50     * {Integer} The maximum size of the grid in pixels on the map 
     51     */ 
     52    targetSize: 200, 
     53 
     54    /** 
     55     * APIProperty: layerName 
     56     * {String} the name to be displayed in the layer switcher 
     57     */ 
     58    layerName: "Graticule", 
     59 
     60    /** 
     61     * APIProperty: labelled 
     62     * {Boolean} Should the graticule lines be labelled?. default=true 
     63     */ 
     64    labelled: true, 
     65 
     66    /** 
     67     * APIProperty: labelFormat 
     68     * {String} the format of the labels, default = 'dm' 
     69     */ 
     70    labelFormat: 'dm', 
     71 
     72    /** 
     73     * APIProperty: lineSymbolizer 
     74     * {symbolizer} the symbolizer used to render lines 
     75     */ 
     76    lineSymbolizer: { 
     77                strokeColor: "#333", 
     78                strokeWidth: 1, 
     79                strokeOpacity: 0.5 
     80            }, 
     81 
     82    /** 
     83     * APIProperty: labelSymbolizer 
     84     * {symbolizer} the symbolizer used to render labels 
     85     */ 
     86     labelSymbolizer: {}, 
     87 
     88    /** 
     89     * Property: gratLayer 
     90     * {OpenLayers.Layer.Vector} vector layer used to draw the graticule on 
     91     */ 
     92    gratLayer: null, 
     93 
     94    /** 
     95     * Constructor: OpenLayers.Control.Graticule 
     96     * Create a new graticule control to display a grid of latitude longitude 
     97     * lines. 
     98     *  
     99     * Parameters: 
     100     * options - {Object} An optional object whose properties will be used 
     101     *     to extend the control. 
     102     */ 
     103    initialize: function(options) { 
     104        OpenLayers.Control.prototype.initialize.apply(this, [options]); 
     105         
     106        this.labelSymbolizer.stroke = false; 
     107        this.labelSymbolizer.fill = false; 
     108        this.labelSymbolizer.label = "${label}"; 
     109        this.labelSymbolizer.labelAlign = "${labelAlign}"; 
     110        this.labelSymbolizer.labelXOffset = "${xOffset}"; 
     111        this.labelSymbolizer.labelYOffset = "${yOffset}"; 
     112    }, 
     113 
     114    /** 
     115     * Method: draw 
     116     * 
     117     * initializes the graticule layer and does the initial update 
     118     *  
     119     * Returns: 
     120     * {DOMElement} 
     121     */ 
     122    draw: function() { 
     123        OpenLayers.Control.prototype.draw.apply(this, arguments); 
     124        if (!this.gratLayer) { 
     125            var gratStyle = new OpenLayers.Style({},{ 
     126                rules: [new OpenLayers.Rule({'symbolizer': 
     127                    {"Point":this.labelSymbolizer, 
     128                     "Line":this.lineSymbolizer} 
     129                })] 
     130            }); 
     131            this.gratLayer = new OpenLayers.Layer.Vector(this.layerName, { 
     132                styleMap: new OpenLayers.StyleMap({'default':gratStyle}), 
     133                visibility: this.visible, 
     134                displayInLayerSwitcher: this.displayInLayerSwitcher 
     135            }); 
     136            this.map.addLayer(this.gratLayer); 
     137        } 
     138        this.map.events.register('moveend', this, this.update); 
     139        this.update(); 
     140        return this.div; 
     141    }, 
     142 
     143    /** 
     144     * Method: update 
     145     * 
     146     * calculates the grid to be displayed and actually draws it 
     147     *  
     148     * Returns: 
     149     * {DOMElement} 
     150     */ 
     151    update: function() { 
     152        //wait for the map to be initialized before proceeding 
     153        var mapBounds = this.map.getExtent(); 
     154        if (!mapBounds) { 
     155            return; 
     156        } 
     157        var mapRect = mapBounds.toGeometry(); 
     158         
     159        //clear out the old grid 
     160        this.gratLayer.destroyFeatures(); 
     161         
     162        //get the projection objects required 
     163        var llProj = new OpenLayers.Projection("EPSG:4326"); 
     164        var mapProj = this.map.getProjectionObject(); 
     165        var mapRes = this.map.getResolution(); 
     166         
     167        //if the map is in lon/lat, then the lines are straight and only one 
     168        //point is required 
     169        if (mapProj.proj && mapProj.proj.projName == "longlat") { 
     170            this.numPoints = 1; 
     171        } 
     172         
     173        //get the map center in EPSG:4326 
     174        var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y 
     175        var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat); 
     176        OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj); 
     177         
     178        /* This block of code determines the lon/lat interval to use for the 
     179         * grid by calculating the diagonal size of one grid cell at the map 
     180         * center.  Iterates through the intervals array until the diagonal 
     181         * length is less than the targetSize option. 
     182         */ 
     183        //find lat/lon interval that results in a grid of less than the target size 
     184        var testSq = this.targetSize*mapRes; 
     185        testSq *= testSq;   //compare squares rather than doing a square root to save time 
     186        var llInterval; 
     187        for (var i=0; i<this.intervals.length; ++i) { 
     188            llInterval = this.intervals[i];   //could do this for both x and y?? 
     189            var delta = llInterval/2;   
     190            var p1 = mapCenterLL.offset(new OpenLayers.Pixel(-delta, -delta));  //test coords in EPSG:4326 space 
     191            var p2 = mapCenterLL.offset(new OpenLayers.Pixel( delta,  delta)); 
     192            OpenLayers.Projection.transform(p1, llProj, mapProj); // convert them back to map projection 
     193            OpenLayers.Projection.transform(p2, llProj, mapProj); 
     194            var distSq = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y); 
     195            if (distSq <= testSq) { 
     196                break; 
     197            } 
     198        } 
     199        //alert(llInterval); 
     200         
     201        //round the LL center to an even number based on the interval 
     202        mapCenterLL.x = Math.floor(mapCenterLL.x/llInterval)*llInterval; 
     203        mapCenterLL.y = Math.floor(mapCenterLL.y/llInterval)*llInterval; 
     204        //TODO adjust for minutses/seconds? 
     205         
     206        /* The following 2 blocks calculate the nodes of the grid along a  
     207         * line of constant longitude (then latitiude) running through the 
     208         * center of the map until it reaches the map edge.  The calculation 
     209         * goes from the center in both directions to the edge. 
     210         */ 
     211        //get the central longitude line, increment the latitude 
     212        var iter = 0 
     213        var centerLonPoints = [mapCenterLL.clone()]; 
     214        var newPoint = mapCenterLL.clone(); 
     215        var mapXY; 
     216        do { 
     217            newPoint = newPoint.offset(new OpenLayers.Pixel(0,llInterval)); 
     218            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); 
     219            centerLonPoints.unshift(newPoint); 
     220        } while (mapBounds.containsPixel(mapXY) && ++iter<1000); 
     221        newPoint = mapCenterLL.clone(); 
     222        do {           
     223            newPoint = newPoint.offset(new OpenLayers.Pixel(0,-llInterval)); 
     224            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); 
     225            centerLonPoints.push(newPoint); 
     226        } while (mapBounds.containsPixel(mapXY) && ++iter<1000); 
     227         
     228        //get the central latitude line, increment the longitude 
     229        iter = 0 
     230        var centerLatPoints = [mapCenterLL.clone()]; 
     231        newPoint = mapCenterLL.clone(); 
     232        do { 
     233            newPoint = newPoint.offset(new OpenLayers.Pixel(-llInterval, 0)); 
     234            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); 
     235            centerLatPoints.unshift(newPoint); 
     236        } while (mapBounds.containsPixel(mapXY) && ++iter<1000); 
     237        newPoint = mapCenterLL.clone(); 
     238        do {           
     239            newPoint = newPoint.offset(new OpenLayers.Pixel(llInterval, 0)); 
     240            mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj); 
     241            centerLatPoints.push(newPoint); 
     242        } while (mapBounds.containsPixel(mapXY) && ++iter<1000); 
     243         
     244        //now generate a line for each node in the central lat and lon lines 
     245        //first loop over constant longitude 
     246        var lines = []; 
     247        for(var i=0; i < centerLatPoints.length; ++i) { 
     248            var lon = centerLatPoints[i].x; 
     249            var pointList = []; 
     250            var labelPoint = null; 
     251            var latEnd = Math.min(centerLonPoints[0].y, 90); 
     252            var latStart = Math.max(centerLonPoints[centerLonPoints.length - 1].y, -90); 
     253            var latDelta = (latEnd - latStart)/this.numPoints; 
     254            var lat = latStart; 
     255            for(var j=0; j<= this.numPoints; ++j) { 
     256                var gridPoint = new OpenLayers.Geometry.Point(lon,lat); 
     257                gridPoint.transform(llProj, mapProj); 
     258                pointList.push(gridPoint); 
     259                lat += latDelta; 
     260                if (gridPoint.y >= mapBounds.bottom && !labelPoint) { 
     261                    labelPoint = gridPoint; 
     262                } 
     263            } 
     264            if (this.labelled) { 
     265                //keep track of when this grid line crosses the map bounds to set 
     266                //the label position 
     267                //labels along the bottom, add 10 pixel offset up into the map 
     268                //TODO add option for labels on top 
     269                var labelPos = new OpenLayers.Geometry.Point(labelPoint.x,mapBounds.bottom); 
     270                var labelAttrs = { 
     271                    value: lon, 
     272                    label: this.labelled?OpenLayers.Util.getFormattedLonLat(lon, "lon", this.labelFormat):"", 
     273                    labelAlign: "cb", 
     274                    xOffset: 0, 
     275                    yOffset: 2 
     276                };  
     277                this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs)); 
     278            } 
     279            var geom = new OpenLayers.Geometry.LineString(pointList); 
     280            lines.push(new OpenLayers.Feature.Vector(geom)); 
     281        } 
     282         
     283        //now draw the lines of constant latitude 
     284        for (var j=0; j < centerLonPoints.length; ++j) { 
     285            lat = centerLonPoints[j].y; 
     286            if (lat<-90 || lat>90) {  //latitudes only valid between -90 and 90 
     287                continue; 
     288            } 
     289            var pointList = []; 
     290            var lonStart = centerLatPoints[0].x; 
     291            var lonEnd = centerLatPoints[centerLatPoints.length - 1].x; 
     292            var lonDelta = (lonEnd - lonStart)/this.numPoints; 
     293            var lon = lonStart; 
     294            var labelPoint = null; 
     295            for(var i=0; i <= this.numPoints ; ++i) { 
     296                var gridPoint = new OpenLayers.Geometry.Point(lon,lat); 
     297                gridPoint.transform(llProj, mapProj); 
     298                pointList.push(gridPoint); 
     299                lon += lonDelta; 
     300                if (gridPoint.x < mapBounds.right) { 
     301                    labelPoint = gridPoint; 
     302                } 
     303            } 
     304            if (this.labelled) { 
     305                //keep track of when this grid line crosses the map bounds to set 
     306                //the label position 
     307                //labels along the right, 30 pixel offset left into the map 
     308                //TODO add option for labels on left 
     309                var labelPos = new OpenLayers.Geometry.Point(mapBounds.right, labelPoint.y);  
     310                var labelAttrs = { 
     311                    value: lat, 
     312                    label: this.labelled?OpenLayers.Util.getFormattedLonLat(lat, "lat", this.labelFormat):"", 
     313                    labelAlign: "rb", 
     314                    xOffset: -2, 
     315                    yOffset: 2 
     316                };  
     317                this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs)); 
     318            } 
     319            var geom = new OpenLayers.Geometry.LineString(pointList); 
     320            lines.push(new OpenLayers.Feature.Vector(geom)); 
     321          } 
     322          this.gratLayer.addFeatures(lines); 
     323    }, 
     324     
     325    CLASS_NAME: "OpenLayers.Control.Graticule" 
     326}); 
     327 
  • lib/OpenLayers/Lang/en.js

    old new  
    8181        "target='_blank'>click here</a>", 
    8282 
    8383    'scale': "Scale = 1 : ${scaleDenom}", 
     84     
     85    //labels for the graticule control 
     86    'W': 'W', 
     87    'E': 'E', 
     88    'N': 'N', 
     89    'S': 'S', 
    8490 
    8591    // console message 
    8692    'layerAlreadyAdded': 
  • lib/OpenLayers/Util.js

    old new  
    958958        var end = OpenLayers.String.contains(url, "#") ? 
    959959                    url.indexOf('#') : url.length; 
    960960        paramsString = url.substring(start, end); 
     961    } else { 
     962        paramsString = url; 
    961963    } 
    962964         
    963965    var parameters = {}; 
     
    16421644 
    16431645    return scrollbarWidth; 
    16441646}; 
     1647 
     1648/** 
     1649 * APIFunction: getFormattedLonLat 
     1650 * This function will return latitude or longitude value formatted as  
     1651 * 
     1652 * Parameters: 
     1653 * coordinate - {Float} the coordinate value to be formatted 
     1654 * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to 
     1655 *          to be formatted (default = lat) 
     1656 * dmsOption - {String} specify the precision of the output can be one of: 
     1657 *           'dms' show degrees minutes and seconds 
     1658 *           'dm' show only degrees and minutes 
     1659 *           'd' show only degrees 
     1660 *  
     1661 * Returns: 
     1662 * {String} the coordinate value formatted as a string 
     1663 */ 
     1664OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) { 
     1665    if (!dmsOption) { 
     1666        dmsOption = 'dms';    //default to show degree, minutes, seconds 
     1667    } 
     1668    var abscoordinate = Math.abs(coordinate) 
     1669    var coordinatedegrees = Math.floor(abscoordinate); 
     1670 
     1671    var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60); 
     1672    var tempcoordinateminutes = coordinateminutes; 
     1673    coordinateminutes = Math.floor(coordinateminutes); 
     1674    var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60); 
     1675    coordinateseconds =  Math.round(coordinateseconds*10); 
     1676    coordinateseconds /= 10; 
     1677 
     1678    if( coordinatedegrees < 10 ) { 
     1679        coordinatedegrees = "0" + coordinatedegrees; 
     1680    } 
     1681    var str = coordinatedegrees + " ";  //get degree symbol here somehow for SVG/VML labelling 
     1682 
     1683    if (dmsOption.indexOf('dm') >= 0) { 
     1684        if( coordinateminutes < 10 ) { 
     1685            coordinateminutes = "0" + coordinateminutes; 
     1686        } 
     1687        str += coordinateminutes + "'"; 
     1688   
     1689        if (dmsOption.indexOf('dms') >= 0) { 
     1690            if( coordinateseconds < 10 ) { 
     1691                coordinateseconds = "0" + coordinateseconds; 
     1692            } 
     1693            str += coordinateseconds + '"'; 
     1694        } 
     1695    } 
     1696     
     1697    if (axis == "lon") { 
     1698        str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E"); 
     1699    } else { 
     1700        str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N"); 
     1701    } 
     1702    return str; 
     1703}; 
     1704 
  • tests/Control/Graticule.html

    old new  
     1<html> 
     2<head> 
     3    <script src="../../lib/OpenLayers.js"></script> 
     4    <script src="http://proj4js.org/lib/proj4js-compressed.js"></script> 
     5    <script type="text/javascript"> 
     6     
     7    function test_initialize(t) { 
     8        t.plan(2); 
     9        var options = {}; 
     10        map = new OpenLayers.Map("map",{projection:"EPSG:4326"}); 
     11        var layer = new OpenLayers.Layer.WMS(); 
     12        map.addLayers([layer]); 
     13         
     14        var control = new OpenLayers.Control.Graticule(options); 
     15        map.addControl(control); 
     16        map.zoomToMaxExtent(); 
     17         
     18        t.ok(control.gratLayer instanceof OpenLayers.Layer.Vector, 
     19             "constructor sets layer correctly"); 
     20        t.ok(control.gratLayer.features.length > 0, 
     21             "graticule has features"); 
     22        control.destroy(); 
     23    } 
     24 
     25    </script> 
     26</head> 
     27<body> 
     28    <div id="map" style="width: 400px; height: 250px;"/> 
     29</body> 
     30</html> 
  • tests/list-tests.html

    old new  
    1515    <li>Control/DragPan.html</li> 
    1616    <li>Control/DrawFeature.html</li> 
    1717    <li>Control/GetFeature.html</li> 
     18    <li>Control/Graticule.html</li> 
    1819    <li>Control/KeyboardDefaults.html</li> 
    1920    <li>Control/LayerSwitcher.html</li> 
    2021    <li>Control/Measure.html</li>