Mapbox 是一家提供定制地图服务的公司,它允许开发者和设计师通过其平台创建和部署个性化的地图。Mapbox 提供了一整套地图工具和API,使得用户可以轻松地在网站、移动应用和各种设备上集成地图服务。
以下是Mapbox的一些主要特点:
Mapbox 的服务通常是基于订阅模式的,用户根据自己的使用量和需求选择合适的订阅计划。Mapbox 的服务广泛应用于交通、物流、房地产、旅游、城市规划等多个领域。通过Mapbox,用户可以创建出既美观又功能强大的地图应用。
mapbox导入天地图比较复杂,如下代码所示,配置一个配置项,然后在初始化的时候放到设置底图的位置即可。
// 将天地图作为底图
const vecUrl =
// "http://t0.tianditu.gov.cn/vec_w/wmts?tk=yourtoken";
"http://t0.tianditu.gov.cn/vec_w/wmts?tk=yourtoken";
const cvaUrl =
// "http://t0.tianditu.gov.cn/cva_w/wmts?tk=yourtoken";
"http://t0.tianditu.gov.cn/cva_w/wmts?tk=yourtoken";
//实例化source对象
var tdtVec = {
//类型为栅格瓦片
type: "raster",
tiles: [
//请求地址
vecUrl +
"&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles",
],
crossOrigin: "anonymous",
//分辨率
tileSize: 256,
};
var tdtCva = {
type: "raster",
tiles: [
cvaUrl +
"&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles",
],
tileSize: 256,
};
var style = {
//设置版本号,一定要设置
version: 8,
//添加来源
sources: {
tdtVec: tdtVec,
tdtCva: tdtCva,
},
layers: [
{
//图层id,要保证唯一性
id: "tdtVec",
//图层类型
type: "raster",
//数据源
source: "tdtVec",
//图层最小缩放级数
minzoom: 0,
//图层最大缩放级数
maxzoom: 17,
},
{
id: "tdtCva",
type: "raster",
source: "tdtCva",
minzoom: 0,
maxzoom: 17,
},
],
glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
};
export function loadMap(box) {
map = new mapboxgl.Map({
container: box,
// 导入设置好的天地图配置
style: style,
preserveDrawingBuffer: true,
center: [114, 30],
zoom: 4,
});
}
这个很简单,主要是需要行政区边界的geojson,这个一般是用shpfile文件转化为geojson,可以通过这个在线网站实现:mapshaper
代码实现,先完成geojson数据源添加,然后添加一个矢量边界图层就可以了。
export function loadMap(box) {
map = new mapboxgl.Map({
container: box,
// style: 'mapbox://styles/mapbox/streets-v11',
// style: 'mapbox://styles/examples/cjgioozof002u2sr5k7t14dim',
style: style,
preserveDrawingBuffer: true,
center: [114, 30],
zoom: 4,
});
}
export function addGeoJson() {
map.on("style.load", () => {
// 加载 GeoJSON 数据源
map.addSource("geojsonSource", {
type: "geojson",
data: CityData,
});
// 添加图层来显示行政区划的边界
map.addLayer({
id: "lineLayer",
type: "line",
source: "geojsonSource",
paint: {
"line-color": "#FFF",
"line-width": 1.5,
},
});
});
}
分级设色本质是为了直观的体现不同的等级或者不同的数值:
这里展示的是体现不同的数值的写法:
export function loadMap(box) {
map = new mapboxgl.Map({
container: box,
// style: 'mapbox://styles/mapbox/streets-v11',
// style: 'mapbox://styles/examples/cjgioozof002u2sr5k7t14dim',
style: style,
preserveDrawingBuffer: true,
center: [114, 30],
zoom: 4,
});
}
// 根据value值上色
export function paintMap() {
// 添加图层来上色
map.addLayer({
id: "geojsonLayer",
type: "fill", // 根据你的数据类型设置合适的图层类型,比如 'fill'、'circle'、'line' 等
source: "geojsonSource",
paint: {
"fill-color": [
"match",
//在geojson中获取name属性
["get", "name"],
//将geojson中的name属性与cityValueData进行匹配,得到正确的综合得分,并根据colorRanges的情况上色
...rankingFormatted.reduce((acc, data) => {
return [...acc, data.cityName, getColor2(data.score)];
}, []),
"#000000", // 默认颜色
],
"fill-opacity": 1, // 填充透明度
},
});
}
// 设置颜色范围
// const colorRange = ['#ADD8E6','#00008B'];
const colorRange = ["#DBEEF6", "#36869A"];
const colorRangeMin = ["#E2F0D9", "#385723"];
const innovation = ["#fff4f8", "#dc7d61"];
const operation = ["#e0e5e9", "#73a9d7"];
const green = ["#e9f0e8", "#80c67d"];
const share = ["#fff8eb", "#f6bb81"];
const open = ["#eaebff", "#b67ebd"];
// 创建颜色插值函数(综合得分)
const colorInterpolate = chroma.scale(colorRange).domain([37, 90]);
// 创建颜色插值函数(单指标得分)
const colorInterpolateMin = chroma.scale(colorRangeMin).domain([1.5, 20]);
const colorInnovation = chroma.scale(innovation).domain([4.1, 17.4]);
const colorOperation = chroma.scale(operation).domain([9.4, 18.7]);
const colorGreen = chroma.scale(green).domain([9.2, 18]);
const colorOpen = chroma.scale(open).domain([1.6, 20]);
const colorShare = chroma.scale(share).domain([7.5, 20]);
// 根据城市值获取对应颜色
function getColor2(value, type) {
if (value > 20) {
return colorInterpolate(value).hex();
} else {
switch (type) {
case 0:
return colorInnovation(value).hex();
case 1:
return colorOperation(value).hex();
case 2:
return colorGreen(value).hex();
case 3:
return colorShare(value).hex();
case 4:
return colorOpen(value).hex();
default:
break;
}
// return colorInterpolateMin(value).hex();
}
}
底图嘛,只有矢量边界不够直观,底图信息又会被颜色图层盖住,所以需要在最上方添加文本注记图层,当然也可以添加一些别的文本内容,标记等都可以。
这里有一个额外引入的数据源,是一个点shpfile转化的geojson,这个点是用来规定显示文本注记的位置的,也可以直接在原先面数据源的基础上使用文本注记,那么文本注记会直接显示在每一个闭合曲线(拓扑展现就是一个面)上显示。
export function loadMap(box) {
map = new mapboxgl.Map({
container: box,
// style: 'mapbox://styles/mapbox/streets-v11',
// style: 'mapbox://styles/examples/cjgioozof002u2sr5k7t14dim',
style: style,
preserveDrawingBuffer: true,
center: [114, 30],
zoom: 4,
});
}
export function addGeoJson() {
map.on("style.load", () => {
map.addSource("pointGeojsonSource", {
type: "geojson",
data: cityPoint,
});
// 添加symbol图层以显示文本
map.addLayer({
id: "pointLabel",
type: "symbol",
source: "pointGeojsonSource",
layout: {
"text-field": ["get", "name"], // 使用get表达式来获取"title"属性
"text-size": 14,
"text-variable-anchor": ["top", "bottom", "left", "right"],
"text-radial-offset": 0.5,
"text-justify": "auto",
},
paint: {
"text-color": "#000", // 文本颜色
"text-halo-color": "#FFF", // 文本描边颜色
"text-halo-width": 1, // 文本描边宽度
},
});
});
}
鼠标悬浮框主要是增加地图图层的互动效果,用于展示更多的信息。
// 在鼠标移动到地图上显示信息
let popup = null;
function showPopup(e, value) {
const features = e.features;
if (features.length > 0) {
map.getCanvas().style.cursor = "pointer";
const cityName = features[0].properties.name; // 城市名称
const cityValue = getCityValue(cityName, value); // 获取对应的值
if (popup) {
popup.remove();
}
// 在页面上显示浮动信息
popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<strong>${cityName}</strong><br>评分为: ${cityValue}`)
.addTo(map);
} else {
map.getCanvas().style.cursor = "";
}
}
// 在鼠标移出时移除浮动信息
function removePopup() {
if (popup) {
popup.remove();
}
map.getCanvas().style.cursor = "";
}
// 绑定地图交互事件,鼠标悬浮和移出事件
function bindMapInteractions(value) {
map.off("mousemove", "geojsonLayer", showPopup);
map.on("mousemove", "geojsonLayer", (e) => showPopup(e, value));
map.off("mouseout", "geojsonLayer", removePopup);
map.on("mouseout", "geojsonLayer", removePopup);
}
包括天地图底图,行政区矢量边界图层,分层设色图层,文本标记图层,还有鼠标悬浮框(不包括图例)在内的综合效果。
<template>
<div class="mapboxBorder">
<div
id="mapbox"
style="width: 100%; height: 100%; border-radius: 18px"
></div>
</div>
</template>
<script setup>
import { map, loadMap, addGeoJson, updateMap } from "@/utils/mapbox.js";
import { onMounted, ref, watch } from "vue";
const initMapbox = () => {
loadMap("mapbox");
map.setCenter([119.14, 31.22]); //修改地图中心点
map.setZoom(6.4); //设置缩放级别
};
onMounted(() => {
//挂载mapbox
initMapbox();
//添加矢量图层
addGeoJson();
});
// ...map组件中的其他事件内容
</script>
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
// import MapboxLanguage from "@mapbox/mapbox-gl-language";
import CityData from "@/assets/json/standardCityBoundary.json";
import ranking from "@/assets/json/scoreDetail.json";
import { scoreFormat } from "@/utils/format.ts";
import cityPoint from "@/assets/json/cityCenterPoint.json";
const rankingFormatted = scoreFormat(ranking);
mapboxgl.accessToken =
"yourtoken"; //去mapbox官⽹申请
const colorRanges = [
{ min: 0, max: 45, color: "#E31A1C" },
{ min: 45, max: 55, color: "#FEB24C" },
{ min: 55, max: 70, color: "#FFEDA0" },
{ min: 70, max: 100, color: "#90EE90" },
];
const colorRangesSingle = [
{ min: 0, max: 5, color: "#E31A1C" },
{ min: 5, max: 10, color: "#FEB24C" },
{ min: 10, max: 15, color: "#FFEDA0" },
{ min: 15, max: 20, color: "#90EE90" },
];
// 将天地图作为底图
const vecUrl =
// "http://t0.tianditu.gov.cn/vec_w/wmts?tk=yourtoken";
"http://t0.tianditu.gov.cn/vec_w/wmts?tk=yourtoken";
const cvaUrl =
// "http://t0.tianditu.gov.cn/cva_w/wmts?tk=yourtoken";
"http://t0.tianditu.gov.cn/cva_w/wmts?tk=yourtoken";
//实例化source对象
var tdtVec = {
//类型为栅格瓦片
type: "raster",
tiles: [
//请求地址
vecUrl +
"&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles",
],
crossOrigin: "anonymous",
//分辨率
tileSize: 256,
};
var tdtCva = {
type: "raster",
tiles: [
cvaUrl +
"&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles",
],
tileSize: 256,
};
var style = {
//设置版本号,一定要设置
version: 8,
//添加来源
sources: {
tdtVec: tdtVec,
tdtCva: tdtCva,
},
layers: [
{
//图层id,要保证唯一性
id: "tdtVec",
//图层类型
type: "raster",
//数据源
source: "tdtVec",
//图层最小缩放级数
minzoom: 0,
//图层最大缩放级数
maxzoom: 17,
},
{
id: "tdtCva",
type: "raster",
source: "tdtCva",
minzoom: 0,
maxzoom: 17,
},
],
glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
};
export let map = null; // 导出 map 对象
export function loadMap(box) {
map = new mapboxgl.Map({
container: box,
// style: 'mapbox://styles/mapbox/streets-v11',
// style: 'mapbox://styles/examples/cjgioozof002u2sr5k7t14dim',
style: style,
preserveDrawingBuffer: true,
center: [114, 30],
zoom: 4,
});
// 设置汉化
// map.addControl(new MapboxLanguage({
// defaultLanguage: 'zh-Hans'
// }));
}
export function addGeoJson() {
map.on("style.load", () => {
// 加载 GeoJSON 数据源
map.addSource("geojsonSource", {
type: "geojson",
data: CityData,
});
map.addSource("pointGeojsonSource", {
// 注意:这里使用的是不同的ID
type: "geojson",
data: cityPoint,
});
//初始化上色
paintMap();
//绑定地图事件
bindMapInteractions();
// 添加图层来显示行政区划的边界
map.addLayer({
id: "lineLayer",
type: "line",
source: "geojsonSource",
paint: {
"line-color": "#FFF",
"line-width": 1.5,
},
});
// 添加symbol图层以显示文本
map.addLayer({
id: "pointLabel",
type: "symbol",
source: "pointGeojsonSource",
layout: {
"text-field": ["get", "name"], // 使用get表达式来获取"title"属性
"text-size": 14,
"text-variable-anchor": ["top", "bottom", "left", "right"],
"text-radial-offset": 0.5,
"text-justify": "auto",
},
paint: {
"text-color": "#000", // 文本颜色
"text-halo-color": "#FFF", // 文本描边颜色
"text-halo-width": 1, // 文本描边宽度
},
});
// 添加地级市名称的文本图层
// map.addLayer({
// id: "cityNameLayer",
// type: "symbol",
// source: "geojsonSource",
// layout: {
// "text-field": [
// "match",
// ["get", "is_island"],
// "true",
// "", // 如果是群岛城市,不显示名字
// ["get", "name"], // 如果不是群岛城市,显示名字
// ],
// "text-size": 14,
// "text-variable-anchor": ["top", "bottom", "left", "right"],
// "text-radial-offset": 0.5,
// "text-justify": "auto",
// "text-allow-overlap": false, // 不允许文本标签重叠
// },
// paint: {
// "text-color": "#000", // 文本颜色
// "text-halo-color": "#FFF", // 文本描边颜色
// "text-halo-width": 1, // 文本描边宽度
// },
// });
});
}
// 根据value值上色
export function paintMap() {
// 添加图层来上色
map.addLayer({
id: "geojsonLayer",
type: "fill", // 根据你的数据类型设置合适的图层类型,比如 'fill'、'circle'、'line' 等
source: "geojsonSource",
paint: {
"fill-color": [
"match",
//在geojson中获取name属性
["get", "name"],
//将geojson中的name属性与cityValueData进行匹配,得到正确的综合得分,并根据colorRanges的情况上色
...rankingFormatted.reduce((acc, data) => {
return [...acc, data.cityName, getColor2(data.score)];
}, []),
"#000000", // 默认颜色
],
"fill-opacity": 1, // 填充透明度
},
});
}
// 切换数据更新地图上色
export function updateMap(value) {
// 根据新的 value 更新绘制属性
let propertiesSelect = "";
switch (parseInt(value)) {
case 0:
propertiesSelect = "创新发展";
break;
case 1:
propertiesSelect = "协调发展";
break;
case 2:
propertiesSelect = "绿色发展";
break;
case 3:
propertiesSelect = "开放发展";
break;
case 4:
propertiesSelect = "共享发展";
break;
case 5:
propertiesSelect = "score";
break;
default:
break;
}
map.setPaintProperty("geojsonLayer", "fill-color", [
"match",
["get", "name"],
...rankingFormatted.reduce((acc, data) => {
return [
...acc,
data.cityName,
getColor2(data[propertiesSelect], parseInt(value)),
];
}, []),
"#000000", // 默认颜色
]);
bindMapInteractions(value);
}
// 设置颜色范围
// const colorRange = ['#ADD8E6','#00008B'];
const colorRange = ["#DBEEF6", "#36869A"];
const colorRangeMin = ["#E2F0D9", "#385723"];
const innovation = ["#fff4f8", "#dc7d61"];
const operation = ["#e0e5e9", "#73a9d7"];
const green = ["#e9f0e8", "#80c67d"];
const share = ["#fff8eb", "#f6bb81"];
const open = ["#eaebff", "#b67ebd"];
// 创建颜色插值函数(综合得分)
const colorInterpolate = chroma.scale(colorRange).domain([37, 90]);
// 创建颜色插值函数(单指标得分)
const colorInterpolateMin = chroma.scale(colorRangeMin).domain([1.5, 20]);
const colorInnovation = chroma.scale(innovation).domain([4.1, 17.4]);
const colorOperation = chroma.scale(operation).domain([9.4, 18.7]);
const colorGreen = chroma.scale(green).domain([9.2, 18]);
const colorOpen = chroma.scale(open).domain([1.6, 20]);
const colorShare = chroma.scale(share).domain([7.5, 20]);
// 根据城市值获取对应颜色
function getColor2(value, type) {
if (value > 20) {
return colorInterpolate(value).hex();
} else {
switch (type) {
case 0:
return colorInnovation(value).hex();
case 1:
return colorOperation(value).hex();
case 2:
return colorGreen(value).hex();
case 3:
return colorShare(value).hex();
case 4:
return colorOpen(value).hex();
default:
break;
}
// return colorInterpolateMin(value).hex();
}
}
// 悬浮地图上时,获取该城市的值
function getCityValue(cityName, value) {
const cityData = rankingFormatted.find((data) => data.cityName === cityName);
switch (parseInt(value)) {
case 0:
return cityData ? cityData["创新发展"] : "N/A";
case 1:
return cityData ? cityData["协调发展"] : "N/A";
case 2:
return cityData ? cityData["绿色发展"] : "N/A";
case 3:
return cityData ? cityData["开放发展"] : "N/A";
case 4:
return cityData ? cityData["共享发展"] : "N/A";
case 5:
return cityData ? cityData.score : "N/A";
default:
return cityData ? cityData.score : "N/A";
}
}
// 在鼠标移动到地图上显示信息
let popup = null;
function showPopup(e, value) {
const features = e.features;
if (features.length > 0) {
map.getCanvas().style.cursor = "pointer";
const cityName = features[0].properties.name; // 城市名称
const cityValue = getCityValue(cityName, value); // 获取对应的值
if (popup) {
popup.remove();
}
// 在页面上显示浮动信息
popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(`<strong>${cityName}</strong><br>评分为: ${cityValue}`)
.addTo(map);
} else {
map.getCanvas().style.cursor = "";
}
}
// 在鼠标移出时移除浮动信息
function removePopup() {
if (popup) {
popup.remove();
}
map.getCanvas().style.cursor = "";
}
// 绑定地图交互事件,鼠标悬浮和移出事件
function bindMapInteractions(value) {
map.off("mousemove", "geojsonLayer", showPopup);
map.on("mousemove", "geojsonLayer", (e) => showPopup(e, value));
map.off("mouseout", "geojsonLayer", removePopup);
map.on("mouseout", "geojsonLayer", removePopup);
}
Mapbox的中国分部好像在2021年左右就退出中国了,官方文档的汉化工作也戛然而止,相关的社区建设也相当欠缺,内容比较混乱,最离谱的是mapbox官方底图库中的中国地图基本都是错的,天地图引入又麻烦......
恰好我最近有一个基础的mapbox应用需求,就做了一些整理和探索,分享给大家。
更多前端好文:各种前端问题的技巧和解决方案
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~