二维绘图,地图(Openlayers/Leafletjs)
绘图工具
图表(数据可视化):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', () => {
// 点击图标
})