GIS加油站

栅格切片在三个框架中的不同表现

2026-02-08  本文已影响0人  牛老师讲GIS

概述

事情的缘起是上线的系统被用户反馈说底图有点模糊,我知道原因是由于使用的框架是mapboxgl,加载的底图是高德的栅格切片,在框架调用的时候会对图片进行拉伸,但具体是什么表现还不清楚,为了搞明白这个问题,我用node写了一个底图边框展示的代码,叠加到地图上加以对比分析。

结论

  1. leaflet其核心渲染方式是dom,地图可展示的级别是整数,所以不存在图片的压缩和模糊的;
  2. mapboxgl渲染方式是webgl,地图展示级别是小数,所以图片存在缩小和放大的情况;
  3. openlayers渲染方式是canvas 2d,根据不同的版本,其表现不一样,4.6.5以上的版本,地图展示级别是小数,图片存在缩小和放大的情况;
    4.被放大后存在底图模糊的情况。
放大和缩小的地图表现

代码

1.切片边框代码

const sharp = require('sharp')
const express = require('express')

console.time('app')

const app = express()

// 自定义跨域中间件
const allowCors = function (req, res, next) {
    res.header('Access-Control-Allow-Origin', req.headers.origin);
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.header('Access-Control-Allow-Credentials', 'true');
    next();
};
app.use(allowCors);// 使用跨域中间件

app.use(express.static('public'))

app.get('/tile-bbox/:z/:x/:y', async (req, res) => {
    try {
        const { z, x, y } = req.params
        const TILE_SIZE = 256;
        const text = `${z}-${x}-${y}`;
        
        // 创建 SVG 内容用于绘制边框和文字
        const svg = `
            <svg width="${TILE_SIZE}" height="${TILE_SIZE}" xmlns="http://www.w3.org/2000/svg">
                <!-- 透明背景 -->
                <rect width="${TILE_SIZE}" height="${TILE_SIZE}" fill="none"/>
                <!-- 红色边框 -->
                <rect x="0" y="0" width="${TILE_SIZE}" height="${TILE_SIZE}" 
                      fill="none" stroke="red" stroke-width="2"/>
                <!-- 文字 -->
                <text x="${TILE_SIZE/2}" y="${TILE_SIZE/2}" 
                      font-family="Arial, sans-serif" font-size="24" 
                      font-weight="bold" text-anchor="middle" 
                      dominant-baseline="middle" fill="red">
                    ${text}
                </text>
            </svg>
        `;
        
        // 使用 Sharp 处理 SVG 转为 PNG
        const buffer = await sharp(Buffer.from(svg))
            .png()
            .toBuffer();
        
        res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString());
        res.setHeader('Content-Type', 'image/png');
        res.send(buffer);
        
    } catch (error) {
        console.error('生成瓦片图像失败:', error);
        res.status(500).send('生成瓦片图像失败');
    }
})


app.listen(18089, () => {
    console.timeEnd('app')
    console.log('express server running at http://127.0.0.1:18089')
})

2. leaflet调用代码

<body>
  <div class="map" id="map">
    <div class="tile-size"></div>
  </div>

  <script>
    let layers = [
      L.tileLayer('https://webrd0{s}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1', { 
        subdomains: '1234' 
      })
    ]
    let map = L.map('map', {
      layers: layers
    }).setView([22.52, 113.94], 4);

    L.tileLayer('http://127.0.0.1:18089/tile-bbox/{z}/{x}/{y}').addTo(map)

    window.map = map
  </script>
</body>

3. openlayers代码

<body>
  <div class="map" id="map">
    <div class="tile-size"></div>
  </div>

  <script>
    const map = new ol.Map({
      target: 'map',
      layers: [
        new ol.layer.Tile({
          source: new ol.source.XYZ({
            url: 'https://webrd0{1-4}.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1'
          })
        }),
        new ol.layer.Tile({
          source: new ol.source.XYZ({
            url: 'http://127.0.0.1:18089/tile-bbox/{z}/{x}/{y}'
          })
        })
      ],
      view: new ol.View({
        center: ol.proj.fromLonLat([113.94, 22.52]), // Longitude, Latitude
        zoom: 4,
        zoomFactor: 1
      })
    });
  </script>
</body>

4.mapboxgl代码

<body>
  <div class="map" id="map">
    <div class="tile-size"></div>
  </div>

  <script>
    const style = {
      version: 8,
      name: "my-map-style",
      sources: {
        "tile-vec-source": {
          type: "raster",
          tiles: [
            "https://webrd01.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1",
            "https://webrd02.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1",
            "https://webrd03.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1",
            "https://webrd04.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1",
          ],
          tileSize: 256
        },
        'tile-boundry': {
          type: "raster",
          tiles: [
            'http://127.0.0.1:18089/tile-bbox/{z}/{x}/{y}'
          ],
          tileSize: 256
        }
      },
      layers: [
        {
          id: "tile-vec-layer",
          type: "raster",
          source: "tile-vec-source",
        },
        {
          id: "tile-boundry",
          type: "raster",
          source: "tile-boundry",
          
        },
      ],
    }
    const map = new mapboxgl.Map({
      container: 'map',
      center: [113.94, 22.52],
      zoom: 4,
      style: style,
      // 重要:限制整体缩放范围,通常栅格瓦片有固定的级别
      minZoom: 0,
      maxZoom: 18,
      pitchWithRotate: false
    });
    map.on('load', () => {
      window.map = map;
    })
  </script>
</body>

5.tile-size样式

 <style>
    html,
    body,
    .map {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      overflow: hidden;
    }

    .tile-size {
      width: 256px;
      height: 256px;
      background-color: rgba(0, 0, 255, 0.5);
      position: absolute;
      border: 1px solid blue;
      box-sizing: border-box;
      top: calc(50% - 128px);
      left: calc(50% - 128px);
      z-index: 9;
    }
  </style>
上一篇 下一篇

猜你喜欢

热点阅读