文章目录
前言
GIS 作为获取、存储、分析和管理地理空间数据的重要工具,用 GIS 技术绘制地图比用传统的手工操作或自动制图工具更加灵活。今天给大家分享一个专为 GIS 客户端开发提供的 JavaScript 类库包 — leafletJs。
一、leafletJs是什么?
引用官方的话来说,Leaflet 是一个开源且对移动端友好的交互式地图 JavaScript 库。并且拥有绝大部分开发者所需要的所有地图特性。而且 Leaflet 可以高效的运行在桌面和移动平台,拥有着大量的扩展插件、优秀的文档、简单易用的 API 和完善的案例,以及可读性较好的源码。中文文档地址:https://leafletjs.cn/reference.html
二、快速入门
1、安装
leafletJs 提供了两种方式引入,大家可根据个人需求任选其一。
方式一:直接引入使用
<link rel = "stylesheet" href = "http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<script src = "http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
方式二:npm 安装
npm install leaflet --save
cnpm install leaflet --save
main.js 引入并注册
import Leaflet from 'leaflet'
import "leaflet/dist/leaflet.css"
Vue.use(Leaflet);
2、快速入门
2.1. 准备一个 div 容器,一定要有宽高。
<div class="oilMap" id="oilMap"></div>
2.2. data 中定义一个地图实例
data() {
return {
map: null,
};
},
2.3. 初始化地图实例
mounted() {
this.initMap();
},
// 地图初始化
initMap() {
// 使用 id 为 map 的 div 容器初始化地图,同时指定地图的中心点和缩放级别
this.map = L.map("oilMap", {
center: [39.91, 116.91], //中心经纬度
zoom: 8, //初始缩放大小
});
// 引入图层
L.tileLayer("http://192.168.0.138/OSM_BLUE/{z}/{x}/{y}.png").addTo(
this.map
);
},
2.4. 不要忘了给地图设置宽高
<style lang="scss" scoped>
.oilMap {
width: 100%;
height: 100vh;
z-index: 9;
background: #00192e;
}
</style>
由此,你就可以得到如下的一个初始化的地图
三、进阶学习
1、Map 控件
Map
控件主要是用来控制地图显示的工具。
1.1 比例尺控件
L.control.scale({
metric: true, // 显示米
imperial: false, // 不显示英尺
}).addTo(this.map);
1.2 禁用双击放大
this.map = L.map("oilMap", {
center: [39.91, 116.91], //中心经纬度
zoom: 8, //初始缩放大小
doubleClickZoom: false, //禁用双击放大
});
1.3 缩放控件是否显示
this.map = L.map("oilMap", {
center: [39.91, 116.91], //中心经纬度
zoom: 8, //初始缩放大小
zoomControl: true, //缩放控件是否显示
});
1.4 版权控件是否显示
this.map = L.map("oilMap", {
center: [39.91, 116.91], //中心经纬度
zoom: 8, //初始缩放大小
attributionControl: false, //版权控件是否显示
});
1.5 地图最大/小缩放层级
this.map = L.map("oilMap", {
center: [39.91, 116.91], //中心经纬度
zoom: 8, //初始缩放大小
minZoom: 6,//地图最小缩放层级
maxZoom: 10,//地图最大缩放层级
});
2、Marker 标记
L.Marker
用于在地图上显示可点击/可拖动的图标。
2.1 添加图标标记点
// 定义图标属性
let markerIcon = L.icon({
iconUrl:"https://unpkg.com/[email protected]/dist/images/marker-icon-2x.png",
iconSize: [20, 34],//图标图像的尺寸,单位是像素。
iconAnchor: [7, 9],//图标 "tip" 的坐标(相对于其左上角)。图标将被对齐,使该点位于标记的地理位置。如果指定了尺寸,默认为居中,也可以在CSS中设置负的边距。
popupAnchor: [-3, -3],//弹出窗口(popup)的坐标,相对于图标锚点而言,将从该点打开。
});
// 生成标记点
L.marker([39.91, 116.91], {icon: markerIcon,}).addTo(this.map);
图标标记点实现效果
2.2 添加圆形标记
L.circle([40.91, 116.91], {
color: "red", //描边颜色
fillColor: "yellow", //填充颜色
fillOpacity: 0.5, //填充的不透明度
radius: 5000, //圆的半径,以米为单位
}).addTo(this.map);
圆形标记实现效果
2.3 添加多边形标记
let latlngs = [
[39.91, 116.91],
[39.87, 116.11],
[39.51, 116.51],
[39.51, 116.81],
[39.91, 116.91],
];
L.polygon(latlngs,{color:'rgb(51,136,255)',fillColor:'red', fillOpacity:'0.4'}).addTo(this.map);
多边形标记实现效果
3、Popup 弹出窗口
bindPopup("弹出内容").openPopup()
用于在地图的某些位置打开弹出窗口。
栗子
// 定义图标属性
let markerIcon = L.icon({
iconUrl:"https://unpkg.com/[email protected]/dist/images/marker-icon-2x.png",
iconSize: [20, 34],//图标图像的尺寸,单位是像素。
iconAnchor: [7, 9],//图标 "tip" 的坐标(相对于其左上角)。图标将被对齐,使该点位于标记的地理位置。如果指定了尺寸,默认为居中,也可以在CSS中设置负的边距。
popupAnchor: [-3, -3],//弹出窗口(popup)的坐标,相对于图标锚点而言,将从该点打开。
});
// 生成标记点
L.marker([39.91, 116.91], {icon: markerIcon,}).addTo(this.map).bindPopup("弹出内容").openPopup();
实现效果
4、图层
在 Leaflet
中,可以添加到地图的任何内容都是一个图层。因此,上面的标记、圆形、多边形其实都属于图层,也可以理解为地图的要素。
4.1 清除图层
//示例图层
let lineSet = L.polygon(latlngs,{color:'rgb(51,136,255)',fillColor:'red', fillOpacity:'0.4'}).addTo(this.map);
//清除指定layer
this.map.removeLayer(lineSet)
//清除地图上所有layer
this.map.clearLayers()
4.2 地图层级判断
this.map.on("zoom", function (e) {
if (e.target._zoom > 8) {
// 执行操作
}
});
4.3 监听地图缩放
this.map.on("zoom", function () {
// 执行操作
});
4.4 绑定点击事件
let marker = L.marker([39.91, 116.91]).addTo(this.map);
marker.on("click", function (e) {
// 执行操作
});
4.5 添加GeoJson图层
//jsondata必须为指定格式为GeoJson格式
let GeoJsonlayer = L.geoJSON(jsondata, {
style: {
color: "#4e98f444",
fillOpacity: 1,
weight: 1,
},
});
GeoJsonlayer.setZIndex(1);// 改变网格图层的 zIndex
this.map.addLayer(GeoJsonlayer);// 将给定的图层添加到组中
四、项目实战
上述所说都是 Leaflet
地图使用的要点,下面我们将上面提到的要点结合项目中的需求实现在地图上各种有趣的操作。
封装文件
// 地图公共方法封装 /utils/index
import * as L from 'leaflet'
import { Message, Notification } from 'element-ui'
export default class LMap {
// 初始化地图
/***
* 【options】参数合集
* [tileUrl]瓦片地图地址
* [minZoom]最小级别
* [maxZoom]最大级别
* [zoom]初始化级别
* [crs]坐标系
* [center]初始化视图中心点
* [fullscreenControl]是否显示全屏控件
* [zoomControl]是否显示缩放控件
* [inertia]是否开启惯性,在拖动和在某一时间段内持续朝同一方向移动建有动力的地图时,会有惯性的效果
* [attributionControl]属性控制是否默认加载在地图上
* [renderer]// 渲染方式 (默认使用canvas提高性能)
* [bounds]// 地图限制拖动区域 [下,上,右,左]
*
*
* ***/
async initMap(divId, options) {
// let url = process.env.VUE_APP_MAP_URL + '/OSM_BLUE/{z}/{x}/{y}.png'
const {
tileUrl,
minZoom,
maxZoom,
zoom,
crs,
center,
fullscreenControl,
zoomControl,
inertia,
attributionControl,
renderer,
bounds
} = options
this.map = L.map(divId, {
minZoom: minZoom ? minZoom : 8, // 地图最小缩放层级
maxZoom: maxZoom ? maxZoom : 15, // 地图最大缩放层级,
// crs: crs ? crs : L.CRS.EPSG3857, // 坐标系
center: center ? center : [39.7506, 118.3423], // 地图中心
zoom: zoom ? zoom : 8, // 地图默认缩放层级
fullscreenControl: fullscreenControl ? fullscreenControl : false, // 是否显示全屏控件
zoomControl: zoomControl ? zoomControl : false, // 是否显示缩放控件
inertia: inertia ? inertia : true, // 是否开启惯性,在拖动和在某一时间段内持续朝同一方向移动建有动力的地图时,会有惯性的效果
attributionControl: attributionControl ? attributionControl : false, // 属性控制是否默认加载在地图上
renderer: renderer ? renderer : L.canvas() //使用canvas 渲染 提高性能
})
if (tileUrl) {
// 将mapbox自定义样式添加到地图中
// this.map.addLayer(L.tileLayer(tileUrl, {
// tileSize: 256,
// zoomOffset: 1,
// attribution: 'stamen',
// }))
this.map.addLayer(L.tileLayer(tileUrl))
} else {
console.error('---请配置瓦片图层地址---')
}
// 给地图区域做限制的上下左右四部分
if (bounds) {
const mapBounds = bounds
// L.latLngBounds([
// [40.7844, 118.2935], // 下
// [38.4575, 118.4979], // 上
// [39.782, 115.778], // 左
// [39.8041, 121.583] // 右
// ])
this.map.setMaxBounds(mapBounds)
}
// 渲染完成返回true
return this.map
}
getMap() {
return this.map || {}
}
mapArea() {}
mapDistance() {}
}
4.1 基础点位图
<template>
<div>
<div class="zMap" id="map"></div>
<!-- 点位详情 -->
<div ref="detailsObj" v-if="showPup">
<div class="dataBox">
<div class="item">
<div>点位名称:</div>
<div>{{ markerData.name ? markerData.name : "--" }}</div>
</div>
<div class="item">
<div>车牌号:</div>
<div> {{ markerData.cph ? markerData.cph : "--" }}</div>
</div>
<div class="item">
<div>排放阶段:</div>
<div>{{ markerData.pfjd ? markerData.pfjd : "--" }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import areaGeo from "@/utils/area.json"; //引入离线本地json
import LMap from "@/utils/index";//地图公共方法封装
import * as L from "leaflet";
import onlineMarker from "../assets/zaix.png";//不同状态的图片
import offlineMarker from "../assets/lix.png";//不同状态的图片
export default {
name: "lMap",
data() {
return {
map: null, //地图对象
lmap: new LMap(), //地图公共方法封装
labelGroup: null, //文本图层
markerLayer: null, //marker 图层
bondarylayer: null, //geojson 图层
layerPup: null, //弹窗
markerData: {}, //详情数据
showPup: false, //详情弹框是否弹出
};
},
mounted() {
this.initMap(); //初始化地图方法
},
methods: {
//初始化地图
async initMap() {
let _this = this;
let { lmap } = this;
//配置地图项
const mapConfig = {
tileUrl: "http://192.168.0.138/OSM_BLUE/{z}/{x}/{y}.png", //瓦片图层地址
minZoom: 8, //允许缩放最小级别
maxZoom: 10, //允许缩放最大级别
zoom: 8, //初始化级别
center: [30.536095454317287, 104.15024279778913], //中心经纬度
bounds: L.latLngBounds([
[30.536095454317287, 104.15024279778913], // 下
[30.536095454317287, 104.15024279778913], // 上
[30.536095454317287, 104.15024279778913], // 左
[30.536095454317287, 104.15024279778913], // 右
]), //限制拖动的区域
};
this.map = await lmap.initMap("map", mapConfig);
if (this.map) {
this.labelGroup = new L.layerGroup(); //文本图层
this.labelGroup.addTo(this.map);
this.markerLayer = new L.layerGroup(); //标记
this.markerLayer.addTo(this.map);
this.getGeojsonByName(areaGeo, true);
this.maptopPoint();
} else {
console.error("--地图加载失败!!!--");
}
},
//加载geojson数据
getGeojsonByName(data, flag) {
let _this = this;
this.bondarylayer = L.geoJSON(data, {
style: {
color: "#4e98f444",
fillOpacity: 1,
weight: 2,
},
pane: "overlayPane",
onEachFeature: (feature, layer) => {
if (!flag) {
_this.addText(feature, layer);
}
},
});
this.bondarylayer.setZIndex(1);
this.map.addLayer(this.bondarylayer);
},
addText(feature, layer) {
let name = feature.properties.NAME;
let location = layer._bounds.getCenter();
//显示文字
var content = `${name}`;
// 区县名称文字
var text = L.divIcon({
html: "<div>" + content + "</div>",
iconSize: [60, 20],
iconAnchor: [0, 10],
className: "labelStyle",
});
let marker = new L.marker(location, { icon: text });
//中心点位
this.labelGroup.addLayer(marker);
},
//模拟地图点位接口
maptopPoint() {
let dataList = [
{
lng: "104.882",
lat: "30.436",
name: "点位1",
cph: "川A31255",
pfjd: "国五",
status: "0",
},
{
lng: "103.652",
lat: "30.565",
name: "点位2",
cph: "川C32585",
pfjd: "国四",
status: "1",
},
{
lng: "104.352",
lat: "31.136",
name: "点位3",
cph: "川X36985",
pfjd: "国三",
status: "0",
},
{
lng: "104.452",
lat: "30.536",
name: "点位4",
cph: "川D31255",
pfjd: "国二",
status: "1",
},
{
lng: "104.202",
lat: "30.136",
name: "点位5",
cph: "川E31125",
pfjd: "国一",
status: "1",
},
];
this.checkMarkerList(dataList);
},
//监测数据对marker进行渲染
checkMarkerList(list) {
let _this = this;
if (!list.length) {
return;
}
list.map((item) => {
_this.addMarker(item);
});
},
addMarker(item) {
let _this = this;
let Icon = _this.getMarkerIcon(item);
if (Icon) {
var marker = new L.marker(L.latLng(item.lat, item.lng), {
icon: Icon,
});
//区县编码
marker["markerInfo"] = item;
marker.bindTooltip(item.name); //鼠标触摸显示提示信息
marker.on("click", function (e) {
let MarkerTarget = e.target; //获取 点击marker
_this.showMarkerPup(MarkerTarget);
});
this.markerLayer.addLayer(marker);
}
},
//显示marker 弹窗信息
showMarkerPup(marker) {
let _this = this;
_this.markerData = marker["markerInfo"];
let location = marker._latlng;
_this.showPup = true;
_this.$nextTick(() => {
_this.layerPup = L.popup({
className: "district-pup",
closeButton: false,
})
.setLatLng(location)
.setContent(_this.$refs.detailsObj)
.openOn(_this.map);
});
},
//获取marker图标
getMarkerIcon(data) {
let markerIcon, imgUrl;
let { status } = data; //1在线 2离线
if (status) {
status = Number(status);
}
if (status == 1) {
imgUrl = onlineMarker;
} else {
imgUrl = offlineMarker;
}
markerIcon = L.icon({
iconUrl: imgUrl,
iconSize: [20, 20],
iconAnchor: [12, 12],
popupAnchor: [-3, -3],
});
return markerIcon;
},
},
};
</script>
<style lang="scss" scoped>
.zMap {
width: 100%;
height: 100vh;
z-index: 9;
}
.dataBox {
width: 100%;
.item {
display: flex;
padding: 4px 0px;
}
}
::v-deep {
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
border: none;
background: rgba($color: #020a31, $alpha: 0.7) !important;
border-radius: 4px;
box-shadow: 0px 0 6px 0px #33aeff inset;
color: rgba($color: #fff, $alpha: 0.8);
}
.leaflet-container a.leaflet-popup-close-button {
color: #fff;
}
}
</style>
实现效果
4.2 行驶轨迹图
<template>
<div>
<div class="leftRightBox">
<!-- 左 -->
<div class="oilMap" id="map"></div>
<!-- 右 -->
<div class="rightTxtBox">
<el-card class="box-card">
<!-- 播放轨迹 -->
<div class="trackBox">
<div>
<i title="播放" v-if="playIsShow" @click="playPauseOn" class="el-icon-video-play"></i>
<i title="暂停" v-else @click="playPauseOn" class="el-icon-video-pause"></i>
</div>
<div>
<el-slider :min="0" :max="points.length - 1" @change="sliderOn" v-model="progress"></el-slider>
</div>
</div>
<div class="multipleBox">
<span>倍速:</span>
<el-select @change="multipleOn" style="width:70px" size="mini" v-model="multipleNumber" placeholder="请选择倍速">
<el-option v-for="item in multipleOptions" :key="item.multipleNumber" :label="item.label" :value="item.value">
</el-option>
</el-select>
</div>
</el-card>
</div>
</div>
</div>
</template>
<script>
import LMap from "@/mapUtils";
import areaGeo from "@/mapUtils/area.json";
import abnormal from "../../../../assets/overview/normal.png"; //地图图标
export default {
data() {
return {
multipleOptions: [
{
value: 800,
label: "1",
},
{
value: 550,
label: "1.5",
},
{
value: 400,
label: "2",
},
],
map: null,
lmap: new LMap(), //地图公共方法封装
labelGroup: null, //文本图层
tipsLayerGroup: null, //tips
bondarylayer: null, //geojson 图层
points: [
//模拟点位数据
[39.898457, 116.391844],
[39.898595, 116.377947],
[39.898341, 116.368001],
[39.898063, 116.357144],
[39.899095, 116.351934],
[39.905871, 116.35067],
[39.922329, 116.3498],
[39.931017, 116.349671],
[39.939104, 116.349225],
[39.942233, 116.34991],
[39.947263, 116.366892],
[39.947568, 116.387537],
[39.947764, 116.401988],
[39.947929, 116.410824],
[39.947558, 116.42674],
[39.9397, 116.427338],
[39.932404, 116.427919],
[39.923109, 116.428377],
[39.907094, 116.429583],
[39.906858, 116.41404],
[39.906622, 116.405321],
[39.906324, 116.394954],
[39.906308, 116.391264],
],
playIsShow: true, //播放/暂停
multipleNumber: 1, //倍速
progress: 0, //进度
marker: null,
polyline: null,
duration: 0,
timer: null, //定时器
time: 800, //定时器速度
};
},
mounted() {
this.duration = this.points.length - 1;
this.initMap();
},
watch: {
progress() {
this.updatePosition();
},
},
destroyed() {
clearTimeout(this.timer);
},
methods: {
updatePosition() {
const position = this.points[this.progress];
this.marker.setLatLng(position);
this.map.setView(position, 12); //放大比例
},
// 滑动进度
sliderOn(e) {
console.log(e, "滑动进度");
},
// 选择倍速
multipleOn(e) {
clearTimeout(this.timer);
this.time = e;
this.recursion();
},
// 点击播放暂停按钮
playPauseOn() {
if (this.progress == this.points.length - 1) {
this.progress = 0;
}
this.playIsShow = !this.playIsShow;
this.recursion();
},
// 递归
recursion() {
if (!this.playIsShow) {
// 模拟数据变化
this.timer = setTimeout(() => {
this.progress++;
if (this.progress >= this.points.length - 1) {
this.playIsShow = true;
this.polyline = L.polyline(this.points, { color: "red" }).addTo(
this.map
);
clearTimeout(this.timer);
}
this.recursion();
}, this.time);
}
},
//初始化地图
async initMap() {
let { lmap } = this;
//配置地图项
const mapConfig = {
tileUrl: window.api.teilUrl + "/OSM_BLUE/{z}/{x}/{y}.png", //瓦片图层地址
crs: L.CRS.EPSG4326,
minZoom: 8, //允许缩放最小级别
maxZoom: 15, //允许缩放最大级别
zoom: 10, //初始化级别
center: [39.91, 116.91], //初始化中心点
};
this.map = await lmap.initMap("map", mapConfig);
if (this.map) {
this.labelGroup = new L.layerGroup(); //文本图层
this.labelGroup.addTo(this.map);
this.tipsLayerGroup = new L.layerGroup();
this.tipsLayerGroup.addTo(this.map);
this.getGeojsonByName(areaGeo, true);
} else {
console.error("--地图加载失败!!!--");
}
var icon = L.icon({
iconUrl: abnormal,
iconSize: [16, 13],
iconAnchor: [8, 7],
});
this.marker = L.marker(this.points[0], { icon: icon }).addTo(this.map);
this.polyline = L.polyline(this.points, { color: "red" }).addTo(this.map);
setInterval(() => {
this.marker.setLatLng(this.points[this.progress]);
this.polyline.setLatLngs(this.points.slice(0, this.progress + 1));
}, 100);
},
//加载geojson数据 falg 是否显示名称
getGeojsonByName(data, flag) {
let _this = this;
this.bondarylayer = L.geoJSON(data, {
style: {
color: "#4e98f444",
fillOpacity: 1,
weight: 1,
},
pane: "overlayPane",
onEachFeature: (feature, layer) => {
// console.log(layer)
if (flag) {
_this.addText(feature, layer);
}
},
});
this.bondarylayer.setZIndex(1);
this.map.addLayer(this.bondarylayer);
},
//添加市所有区县
addText(feature, layer) {
let name = feature.properties.name;
// let center = feature.properties.center;
let location = layer._bounds.getCenter(); //[center[1], center[0]]; //
//显示文字
var content = `${name}`;
// 区县名称文字
var text = L.divIcon({
html: "<div class='labelStyle'>" + content + "</div>",
iconSize: [60, 20],
iconAnchor: [0, 10],
className: "labelStyle",
});
let marker = new L.marker(location, { icon: text });
//中心点位
this.labelGroup.addLayer(marker);
},
},
};
</script>
<style scoped>
.oilMap {
height: 500px;
}
</style>
实现效果
持续更新...