如何在 openlayers 的地图上显示全球 UTM 网格?

Posted

技术标签:

【中文标题】如何在 openlayers 的地图上显示全球 UTM 网格?【英文标题】:How can I display the global UTM grid on a map in openlayers? 【发布时间】:2021-10-18 10:09:37 【问题描述】:

我想在 openlayers 的地图上显示全球 UTM 网格。整个系统没有项目定义,只有各个区域。对于我的地图的投影,我想使用 EPSG:3857.Im 使用 Ol-Ext 刻度对地图和网格使用不同的投影,因为这是我的项目中需要的。有没有办法用 ol-ext 刻度线做到这一点,还是我必须开发自定义解决方案?

【问题讨论】:

哪个 UTM 网格?关于部门或公制网格?无论如何:投影不是网格。只需画出相关的线条(图书馆就是这样做的)。 我希望网格看起来像 map.army 上的 UTM 网格。什么是部门和公制网格? 所以,描述它(并通过编辑问题)。注意:没有多少人会打开随机链接(而且“军队”不是官方的,而且这种***域名通常会吸引骗子)。 [对不起。 UTM 使用“区域”而不是“扇区”。但是公制:UTM 提供公制坐标,所以您是否需要一条线,例如一个区域内 100 公里(+ 区域网格,由度数定义)] 【参考方案1】:

您可以定义一个将所有 UTM 投影合并为一个的投影(例如,在区域的每次增加时将 x 坐标添加 1000000),同时仍对该区域使用适当的 UTM 变换。

<!doctype html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/css/ol.css" type="text/css">
    <style>
      html, body, .map 
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
      
    </style>
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/build/ol.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.7.5/proj4.js"></script>
    <link rel="stylesheet" href="https://viglino.github.io/ol-ext/dist/ol-ext.css" type="text/css">
    <script src="https://viglino.github.io/ol-ext/dist/ol-ext.js"></script>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script type="text/javascript">

      const utmProjs = [];
      for (let zone = 1; zone <= 60; zone++) 
        const code = "EPSG:" + (32600 + zone);
        proj4.defs(code, "+proj=utm +zone=" + zone + " +ellps=WGS84 +datum=WGS84 +units=m +no_defs");
        ol.proj.proj4.register(proj4);
        utmProjs[zone] = ol.proj.get(code);
      

      const llProj = ol.proj.get("EPSG:4326");

      const midpointX = 500000;
      const width = midpointX  * 2;

      function ll2utm(ll) 
        const lon = (((ll[0] % 360) + 540) % 360) - 180; // normalise any wrapx
        const lat = ll[1];
        const zone = Math.floor((180 + lon) / 6) + 1;
        const zoneCoord = ol.proj.transform([lon, lat], llProj, utmProjs[zone]);
        return [zoneCoord[0] + (zone - 1) * width, zoneCoord[1]];
      

      function utm2ll(coord) 
        const zone = Math.floor(coord[0] / width) % 60 + 1;
        const c0 = coord[0] % width;
        const c1 = coord[1];
        const ll = ol.proj.transform([c0, c1], utmProjs[zone], llProj);
        if (Math.floor((180 + ll[0]) / 6) + 1 != zone) 
          ll[0] = (zone - (c0 < midpointX ? 1 : 0)) * 6 - 180;
        
        return ll;
      

      const UTM = new ol.proj.Projection(
        code: 'GlobalUTM',
        units: 'm',
        extent: [0, -10000000, 60 * width, 10000000]
      );

      ol.proj.addProjection(UTM);

      ol.proj.addCoordinateTransforms(
        llProj,
        UTM,
        ll2utm,
        utm2ll
      );

      const map = new ol.Map(
        target: 'map',
        layers: [
          new ol.layer.Tile(
            source: new ol.source.OSM()
          )
        ],
        view: new ol.View(
          center: ol.proj.fromLonLat([37.41, 8.82]),
          zoom: 4
        )
      );

      const viewProj = map.getView().getProjection();
      ol.proj.addCoordinateTransforms(
        viewProj,
        UTM,
        function(coord) 
          return ll2utm(ol.proj.toLonLat(coord, viewProj));
        ,
        function(coord) 
          return ol.proj.fromLonLat(utm2ll(coord), viewProj);
        ,
      );

      map.addControl(
        new ol.control.Graticule(
          projection: UTM,
          step: 1000,
          stepCoord: 1
        )
      );

    </script>
  </body>
</html>

如果 ol-ext 刻度有一个类似于 OpenLayers 刻度中的 intervals 选项,而不是使用 step 设置的随机倍数绘制线条

intervals: [1000, 5000, 10000, 50000, 100000, 500000]

可以使区域边界清晰,同时在放大时仍显示细节。如果没有该选项,您可能会使用step 设置为 1000 公里的刻度线来突出显示和标记区域,并使用另一个刻度线step 为 1 公里以获取区域内的详细信息。不幸的是,ol-ext 刻度只是在交叉点之间绘制直线,而不是计算正确的曲线,因此也会产生的 1000 公里 y 值线相对于更准确的 1 公里细节会错位。然而,它可以用于显示标签,但是因为它缺少用于 x 和 y 坐标的单独标签格式化程序,所以需要额外的 x 偏移量才能区分 x 和 y 坐标。在 EPSG:3857 投影中,区域边界线可以很容易地添加到单独的矢量图层中。

<!doctype html>
<html lang="en">
  <head>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/css/ol.css" type="text/css">
    <style>
      html, body, .map 
        margin: 0;
        padding: 0;
        width: 100%;
        height: 100%;
      
    </style>
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.6.1/build/ol.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.7.5/proj4.js"></script>
    <link rel="stylesheet" href="https://viglino.github.io/ol-ext/dist/ol-ext.css" type="text/css">
    <script src="https://viglino.github.io/ol-ext/dist/ol-ext.js"></script>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script type="text/javascript">

      const utmProjs = [];
      for (let zone = 1; zone <= 60; zone++) 
        const code = "EPSG:" + (32600 + zone);
        proj4.defs(code, "+proj=utm +zone=" + zone + " +ellps=WGS84 +datum=WGS84 +units=m +no_defs");
        ol.proj.proj4.register(proj4);
        utmProjs[zone] = ol.proj.get(code);
      

      const llProj = ol.proj.get("EPSG:4326");

      const midpointX = 500000;
      const width = midpointX  * 2;
      const xOffset = 100 * 60 * width;

      function ll2utm(ll) 
        //const world = Math.floor((ll[0] + 180) / 360);
        const lon = (((ll[0] % 360) + 540) % 360) - 180; // normalise any wrapx
        const lat = ll[1];
        const zone = Math.floor((180 + lon) / 6) + 1;
        const zoneCoord = ol.proj.transform([lon, lat], llProj, utmProjs[zone]);
        const coord = [xOffset + zoneCoord[0] + (zone - 1) * width, zoneCoord[1]];
        //coord[0] += world * 60 * width;
        return coord;
      

      function utm2ll(coord) 
        //const world = Math.floor((coord[0] - xOffset) / (60 * width));
        const zone = Math.floor(coord[0] / width) % 60 + 1;
        const c0 = coord[0] % width;
        const c1 = coord[1];
        const ll = ol.proj.transform([c0, c1], utmProjs[zone], llProj);
        if (Math.floor((180 + ll[0]) / 6) + 1 != zone) 
          ll[0] = (zone - (c0 < midpointX ? 1 : 0)) * 6 - 180;
        
        //ll[0] += world * 360;
        return ll;
      

      const UTM = new ol.proj.Projection(
        code: 'GlobalUTM',
        units: 'm',
        extent: [xOffset, -10000000, xOffset + 60 * width, 10000000],
        //global: true
      );

      ol.proj.addProjection(UTM);

      ol.proj.addCoordinateTransforms(
        llProj,
        UTM,
        ll2utm,
        utm2ll
      );

      const map = new ol.Map(
        target: 'map',
        layers: [
          new ol.layer.Tile(
            source: new ol.source.OSM()
          )
        ],
        view: new ol.View(
          center: ol.proj.fromLonLat([37.41, 8.82]),
          zoom: 4
        )
      );

      const viewProj = map.getView().getProjection();
      ol.proj.addCoordinateTransforms(
        viewProj,
        UTM,
        function(coord) 
          return ll2utm(ol.proj.toLonLat(coord, viewProj));
        ,
        function(coord) 
          return ol.proj.fromLonLat(utm2ll(coord), viewProj);
        ,
      );

      const features = [
        new ol.Feature(
          new ol.geom.LineString([[-180, 0], [180, 0]]).transform(llProj, viewProj)
        )
      ];
      for (let i = -180; i <= 180; i += 6) 
        features.push(
          new ol.Feature(
            new ol.geom.LineString([[i, -85], [i, 85]]).transform(llProj, viewProj)
          )
        );
      ;

      map.addLayer(
        new ol.layer.Vector(
          source: new ol.source.Vector(
            features: features
          ),
          style: new ol.style.Style(
            stroke: new ol.style.Stroke(
              color: 'black',
              width: 3
            ),
          )
        )
      );

      map.addControl(
        new ol.control.Graticule(
          projection: UTM,
          step: 1000,
          stepCoord: 1,
          formatCoord: function(coord) 
            if (coord % width == 0) 
              return '';
             else 
              return (20000 + (coord % width) / 1000).toString().slice(2);
            
          ,
          style: new ol.style.Style(
            stroke: new ol.style.Stroke(
              color: 'black',
              width: 1
            ),
            fill: new ol.style.Fill(
              color: 'white'
            ),
            text: new ol.style.Text(
              stroke: new ol.style.Stroke(
                color: 'white',
                width: 3
              ),
              fill: new ol.style.Fill(
                color: 'black'
              ),
              font: 'bold 12px Arial, Helvetica, Helvetica, sans-serif',
            )
          )
        )
      );

      map.addControl(
        new ol.control.Graticule(
          projection: UTM,
          step: width,
          stepCoord: 1,
          formatCoord: function(coord) 
            if (coord < 0) 
              return 'S' + (20 + coord / width).toString().slice(1);
             else if (coord < xOffset) 
              return 'N' + (20 + coord / width).toString().slice(1);
             else 
              return 'Z' + (100 + Math.floor(coord / width) % 60 + 1).toString().slice(1);
            
          ,
          style: new ol.style.Style(
            stroke: new ol.style.Stroke(
              color: 'transparent',
              width: 1
            ),
            fill: new ol.style.Fill(
              color: 'transparent'
            ),
            text: new ol.style.Text(
              stroke: new ol.style.Stroke(
                color: 'white',
                width: 3
              ),
              fill: new ol.style.Fill(
                color: 'black'
              ),
              font: 'bold 15px Arial, Helvetica, Helvetica, sans-serif',
            )
          )
        )
      );

    </script>
  </body>
</html>

【讨论】:

谢谢!有没有办法突出区域的边界并添加区域编号? 我添加了一个更新,它解决了 ol-ext 刻度的一些问题(特定间隔、正确的曲线以及单独的 x 和 y 轴标签格式),通过复制刻度代码并对其进行自定义。当 180 经线在视图中时,我的变换也存在问题。

以上是关于如何在 openlayers 的地图上显示全球 UTM 网格?的主要内容,如果未能解决你的问题,请参考以下文章

在节点上显示 OpenLayers 地图

如何从业力测试触发 openLayers 3 'singleClick'

如何使用 OpenLayers 显示、居中和缩放地图?

如何在 OpenLayers 中设置初始地图区域?

在地图上基于OpenLayers实现点/线/面静态的绘制显示

基于OpenLayers实现导航地图上(起/终)点的交互式图标显示