绘图工具
图表(数据可视化):Chart.js 、ECharts.js、Highcharts.js、D3.js
流程图:vueflow
Canvas 2D:Fabric.js、ZRender.js
矢量图(SVG,VML):SVG.js、ZRender.js
地图层叠(GIS):Echarts Map、Mapv、AntV L7、deck.gl、Leafletjs、Openlayers、Maptalks.js、Mapbox-gljs(不开源)
地图辅助工具:Turf.js(地图计算)、Gcoord.js(地图坐标)、chaikin-smooth(平滑曲线)
地图数据源:腾讯地图、百度地图、高德地图、天地图、BigMap
地图区域数据:GeoJSON、 DataV.GeoAtlas、Index of /examples/data/asset/geo
Canvas 2D
案例:图片上画8点框
<template><div class="image-json" :style="{ width: canvas2dWidth + 'px', height: canvas2dHeight + 'px', backgroundImage: `url(${imgUrl})` }"><canvas ref="canvas_2d" :width="canvas2dWidth" :height="canvas2dHeight"></canvas></div>
</template><script setup>
import { ref } from 'vue';let imgUrl = ref('');
let jsonUrl = ref(''); // 框数据
let canvas2dWidth = ref(800);
let canvas2dHeight = ref(450);
let canvas_2d = ref();const renderImageJson = () => {let imgPro = new Promise((resolve, reject) => {let img = new Image();img.src = imgUrl.value;img.onload = () => {resolve(img);};});let jsonPro = new Promise((resolve, reject) => {fetch(jsonUrl.value).then((res) => {res.json().then((result) => {resolve(result);});});});Promise.all([imgPro, jsonPro]).then((result) => {let img = result[0];let json = result[1];let zoom = 1; // 缩放比let left = 0, top = 0; // 图片左上角在画布中的坐标if (img.naturalWidth / img.naturalHeight > canvas2dWidth.value / canvas2dHeight.value) {zoom = canvas2dWidth.value / img.naturalWidth;left = 0;top = (canvas2dHeight.value - img.naturalHeight * zoom) / 2;} else {zoom = canvas2dHeight.value / img.naturalHeight;top = 0;left = (canvas2dWidth.value - img.naturalWidth * zoom) / 2;}let context2d = canvas_2d.value.getContext('2d');context2d.clearRect(0, 0, canvas2dWidth.value, canvas2dHeight.value);// 图片也可以通过这种方式画上 context2d.drawImage(img, left, top, img.naturalWidth * zoom, img.naturalHeight * zoom);json.forEach((box) => {// 在一次 beginPath() 和 stroke() 之间,strokeStyle 只能生效一次context2d.beginPath();context2d.strokeStyle = '#ff0000';// 下底 4 条边context2d.moveTo(left + box[0].x * zoom, top + box[0].y * zoom);context2d.lineTo(left + box[1].x * zoom, top + box[1].y * zoom);context2d.lineTo(left + box[2].x * zoom, top + box[2].y * zoom);context2d.lineTo(left + box[3].x * zoom, top + box[3].y * zoom);context2d.lineTo(left + box[0].x * zoom, top + box[0].y * zoom);// 上底 4 条边context2d.moveTo(left + box[4].x * zoom, top + box[4].y * zoom);context2d.lineTo(left + box[5].x * zoom, top + box[5].y * zoom);context2d.lineTo(left + box[6].x * zoom, top + box[6].y * zoom);context2d.lineTo(left + box[7].x * zoom, top + box[7].y * zoom);context2d.lineTo(left + box[4].x * zoom, top + box[4].y * zoom);// 竖边context2d.moveTo(left + box[0].x * zoom, top + box[0].y * zoom);context2d.lineTo(left + box[4].x * zoom, top + box[4].y * zoom);context2d.moveTo(left + box[1].x * zoom, top + box[1].y * zoom);context2d.lineTo(left + box[5].x * zoom, top + box[5].y * zoom);context2d.moveTo(left + box[2].x * zoom, top + box[2].y * zoom);context2d.lineTo(left + box[6].x * zoom, top + box[6].y * zoom);context2d.moveTo(left + box[3].x * zoom, top + box[3].y * zoom);context2d.lineTo(left + box[7].x * zoom, top + box[7].y * zoom);// 每次 beginPath() 后得 stroke() 才能生效context2d.stroke();});});
};</script><style lang="scss" scoped>
.image-json {position: relative;background-size: contain; // 图片比容器小时会放大图片到贴边background-position: center;background-repeat: no-repeat;
}
</style>
Openlayers
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>Title</title><link href="https://lib.baomitu.com/ol3/4.6.5/ol.css" rel="stylesheet" /><script src="https://lib.baomitu.com/ol3/4.6.5/ol.js"></script><style>.ol-zoomslider {top: 7.5em;}</style></head><body><div id="map_ele"></div><script>/* 图层与地图 */// 瓦片图层const gaode = new ol.layer.Tile({title: "高德地图",source: new ol.source.XYZ({url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7", // 高德地图瓦片地址wrapX: false,// 自定义瓦片服务tileUrlFunction: (zxy) => {let [z, x, y] = zxy; // z 即 缩放级别,xy 为序号,参照Web墨卡托投影坐标系return `/tile/${z}/${x}/${y}.png`; // 256px * 256px 的图片},}),});// 地图const map = new ol.Map({target: "map_ele",layers: [gaode], // 使用图层view: new ol.View({center: [114.3, 30.5], // 视图中心点zoom: 10, // 缩放级别projection: "EPSG:4326", // 坐标系}),});/* 控件 */// 跳到指定范围 按钮控件const zoomToExtent = new ol.control.ZoomToExtent({extent: [110, 30, 120, 40],});map.addControl(zoomToExtent);// 调整缩放级别 滑块控件map.addControl(new ol.control.ZoomSlider());// 全屏 控件map.addControl(new ol.control.FullScreen());/* 矢量元素 */// 元素样式let style = new ol.style.Style({image: new ol.style.Circle({radius: 10, // 单位是像素fill: new ol.style.Fill({color: "#ff2d51",}),stroke: new ol.style.Stroke({width: 2, // 单位是像素color: "#333",}),}),});// 点元素const point = new ol.Feature({geometry: new ol.geom.Point([114.3, 30.5]),});point.setStyle(style);// 矢量图层let layer = new ol.layer.Vector({source: new ol.source.Vector({features: [point],}),});map.addLayer(layer);/* geojson 矢量元素之点 */let geojson = {type: "FeatureCollection",features: [{type: "Feature",geometry: {type: "Point",coordinates: [114.3, 30.6],},},],};layer = new ol.layer.Vector({source: new ol.source.Vector({features: new ol.format.GeoJSON().readFeatures(geojson),}),});layer.setStyle(style);map.addLayer(layer);/* geojson 矢量元素之线条、区域 */geojson = {type: "FeatureCollection",features: [{type: "Feature",geometry: {type: "LineString",coordinates: [[114.3, 30.5],[114.3, 30.6],],},},{type: "Feature",geometry: {type: "Polygon",coordinates: [[[114.4, 30.5],[114.4, 30.6],[114.5, 30.5],],],},},],};layer = new ol.layer.Vector({source: new ol.source.Vector({features: new ol.format.GeoJSON().readFeatures(geojson),}),});style = new ol.style.Style({stroke: new ol.style.Stroke({color: "#ff2d51",width: 3,}),fill: new ol.style.Fill({color: "rgba(50, 50, 50, 0.3)",}),});layer.setStyle(style);map.addLayer(layer);/* 加载geojson */layer = new ol.layer.Vector({source: new ol.source.Vector({url: "./USA.json",format: new ol.format.GeoJSON(),}),});map.addLayer(layer);/* 点击事件 */map.on("click", (evt) => {let { coordinate } = evt;const view = map.getView();// 飞行view.animate({center: coordinate,zoom: 8,duration: 3000,});});</script></body>
</html>
Leafletjs
安装
npm install leaflet --save
导入
import 'leaflet/dist/leaflet.css'
import L from 'leaflet'
容器
<div id="mapx"></div>
加载
let map = L.map('map').setView([90.56466, 39.7385], 8) // 中心点[纬度,经度],层级
L.tileLayer('http://***/{z}/{x}/{y}.png', { // 瓦片服务maxZoom: 17, // 最大缩放级别minZoom: 0, // 最小缩放级别
}).addTo(map)let meta = await fetch(`http://***/meta.json`).then(response => response.text()).then(str => JSON.parse(str)) // TMS (Tile Map Service) 目录里会有一个元数据文件
let bounds = L.latLngBounds(L.latLng(meta.latLonBounds.north, meta.latLonBounds.west), L.latLng(meta.latLonBounds.south, meta.latLonBounds.east)) // 有瓦片区域的经纬度范围 L.tileLayer(`http://***/{z}/{x}/{y}.${meta.contentType.substring(meta.contentType.lastIndexOf('/') + 1)}`, {tileSize: meta.tilesize,minZoom: meta.minzoom,maxZoom: meta.maxzoom,tms: true // 数据格式为 TMS (Tile Map Service) }).addTo(map) // 可以多个 layer 分别 addTo(map),即叠加
map.fitBounds(bounds) // 中心点 和 缩放级别 调整到刚好能看全区域// 自定义 marker 图标
let markerIcon = L.icon({iconUrl: './marker.png',iconSize: [16, 16], // 图标尺寸iconAnchor: [8, 8], // 图标锚点,即图标的中心点
});
L.marker([latitude, longitude], {icon: markerIcon}).addTo(map).on('click', () => {// 点击图标
})