• 基于geojson-vt和canvas的高性能出图


    概述

    本文介绍基于geojson-vtcanvas,实现node端高性能出图。

    效果

    动画.gif

    性能

    实现

    1. canvas绘图

    import { createCanvas } from 'canvas'
    
    const tileSize = 256;
    const canvas = createCanvas(tileSize, tileSize)
    const ctx = canvas.getContext('2d')
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2. 处理geojson

    const geojson = result.rows[0].geojson;
    geojson.features = geojson.features || []
    const tileIndex = geojsonvt(geojson, {
        maxZoom: 22,
        tolerance: 3, // simplification tolerance (higher means simpler)
        extent: 4096, // tile extent (both width and height)
        buffer: 64,   // tile buffer on each side
        debug: 0,     // logging level (0 to disable, 1 or 2)
        lineMetrics: false, // whether to enable line metrics tracking for LineString/MultiLineString features
        promoteId: null,    // name of a feature property to promote to feature.id. Cannot be used with `generateId`
        generateId: false,  // whether to generate feature ids. Cannot be used with `promoteId`
        indexMaxZoom: 5,       // max zoom in the initial tile index
        indexMaxPoints: 100000 // max number of points per tile in the index
    })
    const features = tileIndex.getTile(Number(z), Number(x), Number(y))?.features || [];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3. 绘制features

    function drawTile(features, zoom) {
        ctx.clearRect(0, 0, tileSize, tileSize);
        // 绘制边框
        // ctx.strokeStyle = '#fff'
        // ctx.lineWidth = 1
        // ctx.strokeRect(0, 0, tileSize, tileSize)
        for (let i = 0; i < features.length; i++) {
            const feature = features[i];
            const {adcode, name} = feature.tags
            const type = feature.type;
            ctx.beginPath();
            for (let j = 0; j < feature.geometry.length; j++) {
                const geom = feature.geometry[j];
                if (type === 1) { // 1点
                    ctx.save()
                    ctx.fillStyle = `rgba(${'255,0,0'}, 1)`;
                    ctx.strokeStyle = '#fff'
                    ctx.lineWidth = 2;
                    ctx.textAlign = "center";
                    ctx.textBaseline = "middle"
                    ctx.font = "bold 16px 宋体";
                    // const len = ctx.measureText(name).width / 2;
                    // const offset = 5
                    // if(x + len > tileSize) x = tileSize - len - offset
                    // if(x - len < 0 ) x = len + offset
                    if(name && zoom >= 9) {
                        ctx.strokeText(name, geom[0] / 16.0, geom[1] / 16.0)
                        ctx.fillText(name, geom[0] / 16.0, geom[1] / 16.0)
                    }
                    // ctx.arc(geom[0] / 16.0, geom[1] / 16.0, 3, 0, 2 * Math.PI, false);
                    ctx.restore()
                    ctx.fill()
                    ctx.stroke()
                } else { // 2线 或 3面
                    // const color = colorDict[adcode] || '255,0,0'
                    const color = '255,0,0'
                    ctx.strokeStyle = `rgba(${color}, 1)`;
                    ctx.fillStyle = `rgba(${color}, 0.1)`;
                    ctx.lineWidth = 1;
                    for (let k = 0; k < geom.length; k++) {
                        const p = geom[k];
                        if (k) ctx.lineTo(p[0] / 16.0, p[1] / 16.0);
                        else ctx.moveTo(p[0] / 16.0, p[1] / 16.0);
                    }
                    // if (type === 3) ctx.fill();
                    ctx.stroke();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    4. 设置缓存并发送到前端

    app.get('/tile/:z/:x/:y', (req, res) => {
        const {z, x, y} = req.params
        try {
            getFeatures(x, y, z).then(image => {
                res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())
                res.writeHead(200, {
                    "Content-Type": "image/png",
                });
                res.end(image);
            })
        } catch (err) {
            console.error(err);
        }   
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5. 数据获取

    数据是存在PG数据库中,可通过node连接获取,可通过如下语句直接将结果转换为geojson。

    SELECT json_build_object(
       'type', 'FeatureCollection',
       'features', json_agg(ST_AsGeoJSON(t.*)::json)
    ) as geojson FROM (
       select adcode, name, geom from base_county where st_intersects(BBox(101, 52, 7), geom)
    ) as t(adcode, name, geom);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    image.png

    6. 前端调用

    new ol.layer.Tile({
      source: new ol.source.XYZ({
          url: 'http://127.0.0.1:18089/tile/{z}/{x}/{y}'
      })
    }),
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    Java 之 ElasticSearch8.x.x 【一篇文章精通系列】【ES的基本操作,ES安装,ES head + Kibana】
    VS 将 localhost访问改为ip访问
    Stream根据多个字段去重
    【算法竞赛入门练习题】统计一个输入的字符串中出现某子串的个数 以及 求一个字符串中的最大等值子串
    美创数据库安全审计DAS通过IPv6 Ready Logo认证
    归并排序和非比较排序
    记一次血淋淋的MySQL崩溃修复案例
    GO语言学习笔记(与Java的比较学习)(三)
    国产服务器安装onlyoffice详细教程
    程序员看看这是什么代码
  • 原文地址:https://blog.csdn.net/GISShiXiSheng/article/details/133915162