目录
一、Mapbox简介
Mapbox 是一家提供定制地图服务的公司,它允许开发者和设计师通过其平台创建和部署个性化的地图。Mapbox 提供了一整套地图工具和API,使得用户可以轻松地在网站、移动应用和各种设备上集成地图服务。
以下是Mapbox的一些主要特点:
-
定制化:Mapbox 允许用户根据自己的品牌和设计需求定制地图样式,包括颜色、图标、字体等。
-
数据驱动:Mapbox 支持使用各种数据源,包括开放街道地图(OpenStreetMap)数据,以及其他商业和私有数据。
-
实时更新:Mapbox 提供的地图服务可以实时更新,确保地图信息的准确性和最新性。
-
多平台支持:Mapbox 的API和服务支持多种平台和语言,包括Web、iOS、Android等。
-
交互式地图:Mapbox 支持创建交互式地图,用户可以添加图层、标记、路径、热力图等。
-
位置服务:Mapbox 提供了一套完整的位置服务,包括地理编码、逆地理编码、方向和路由规划等。
-
地图分析:Mapbox 提供地图分析工具,帮助用户理解用户行为和地图数据。
-
社区和生态系统:Mapbox 拥有一个活跃的开发者社区,提供了大量的教程、文档和论坛支持。
-
安全性:Mapbox 提供了安全措施,如访问控制和数据加密,以保护用户数据的安全。
-
可扩展性:Mapbox 的服务设计为可扩展的,可以支持从小规模到大规模的地图应用。
-
企业解决方案:Mapbox 为企业提供定制化的解决方案,满足特定业务需求。
-
集成第三方服务:Mapbox 可以与许多第三方服务和API集成,如天气、交通、社交网络等。
-
地图编辑工具:Mapbox 提供了地图编辑工具,允许用户直接在地图上添加或修改数据。
-
地图SDK:Mapbox 提供了软件开发工具包(SDK),使得开发者可以快速地在自己的应用中集成地图功能。
Mapbox 的服务通常是基于订阅模式的,用户根据自己的使用量和需求选择合适的订阅计划。Mapbox 的服务广泛应用于交通、物流、房地产、旅游、城市规划等多个领域。通过Mapbox,用户可以创建出既美观又功能强大的地图应用。
二、Mapbox添加地图、各数据图层和功能的思路
2.1、添加天地图底图
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,
- });
- }
2.2、Mapbox添加行政区矢量图层
这个很简单,主要是需要行政区边界的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,
- },
- });
- });
- }
2.3、Mapbox添加分级设色图层
分级设色本质是为了直观的体现不同的等级或者不同的数值:
- 体现不同的等级:根据数值的极差分档,类似于arcgis的重分类,不同档位设置不同的颜色
- 体现不同的数值:根据数值的极差用颜色渐变平滑的展示数值的区别,一目了然可任意两两比较。
这里展示的是体现不同的数值的写法:
- 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();
- }
- }
2.4、Mapbox添加文本标记图层
底图嘛,只有矢量边界不够直观,底图信息又会被颜色图层盖住,所以需要在最上方添加文本注记图层,当然也可以添加一些别的文本内容,标记等都可以。
这里有一个额外引入的数据源,是一个点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, // 文本描边宽度
- },
- });
- });
- }
2.5、Mapbox自定义鼠标悬浮框
鼠标悬浮框主要是增加地图图层的互动效果,用于展示更多的信息。
- // 在鼠标移动到地图上显示信息
- 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(`${cityName}
评分为: ${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);
- }
三、最终效果及代码展示
3.1、最终效果
包括天地图底图,行政区矢量边界图层,分层设色图层,文本标记图层,还有鼠标悬浮框(不包括图例)在内的综合效果。
3.2、完整代码展示:
3.2.1、map.vue(map所在的组件):
- <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>
3.2.2、mapbox.js:
- 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(`${cityName}
评分为: ${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应用需求,就做了一些整理和探索,分享给大家。
更多前端好文:各种前端问题的技巧和解决方案
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
评论记录:
回复评论: