概述
本文介绍基于geojson-vt
和canvas
,实现node端高性能出图。
效果
实现
1. canvas绘图
js
import { createCanvas } from 'canvas'
const tileSize = 256;
const canvas = createCanvas(tileSize, tileSize)
const ctx = canvas.getContext('2d')
2. 处理geojson
js
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 || [];
3. 绘制features
js
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();
}
}
}
}
4. 设置缓存并发送到前端
js
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);
}
})
5. 数据获取
数据是存在PG数据库中,可通过node连接获取,可通过如下语句直接将结果转换为geojson。
sql
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);
6. 前端调用
js
new ol.layer.Tile({
source: new ol.source.XYZ({
url: 'http://127.0.0.1:18089/tile/{z}/{x}/{y}'
})
}),