在MapBox开发中的开发中,尤其是大屏类的开发,需要加载不同风格的栅格瓦片,而MapBox自带的样式规范中关于栅格切片配色的参数提供得非常少,自定义程度不够高。这就需要我们借助其他的方案来实现栅格瓦片风格的修改。
方案一: CSS滤镜
关键代码如下:
<style scoped>
#map {
width: 100vw;
height: 100vh;
filter: grayscale(0.15) saturate(1.8) sepia(0.1) brightness(1.1) contrast(1.5);
}
</style>
该方案实现简单,只需要在css样式中对地图容器中添加filter滤镜即可实现。但该方法有一个缺点,css滤镜会修改地图容器内所有覆盖物的配色。所以该方案仅适合底图展示。
方案二: 基于Deck.gl加载栅格瓦片图层
Deck.gl是mapbox的第三方插件,支持高度自定义的瓦片加载,完整示例代码如下:
filter.js
// 反相滤镜
function invertFilter(imageData, intensity = 1) {
const data = imageData;
// 规范强度值到[0,1]范围
intensity = Math.min(1, Math.max(0, intensity));
for (let i = 0; i < data.length; i += 4) {
// 核心算法:线性插值实现渐进反转
const calc = (value) => value + (255 - 2 * value) * intensity;
data[i] = calc(data[i]); // R
data[i+1] = calc(data[i+1]); // G
data[i+2] = calc(data[i+2]); // B
}
return imageData;
}
// 亮度滤镜
function brightnessFilter(data, level = 0) {
const adjust = level * 255;
for (let i = 0; i < data.length; i += 4) {
data[i] += adjust; // R
data[i+1] += adjust; // G
data[i+2] += adjust; // B
}
}
// 对比度滤镜
function applyContrast(data, level = 1.22) {
const factor = (259 * (level + 255)) / (255 * (259 - level));
for (let i = 0; i < data.length; i += 4) {
data[i] = factor * (data[i] - 128) + 128;
data[i+1] = factor * (data[i+1] - 128) + 128;
data[i+2] = factor * (data[i+2] - 128) + 128;
}
}
// 饱和度滤镜
function applySaturation(data, saturation = 1.6) {
const lumR = 0.299, lumG = 0.587, lumB = 0.114;
for (let i = 0; i < data.length; i += 4) {
const gray = data[i] * lumR + data[i+1] * lumG + data[i+2] * lumB;
data[i] = gray + (data[i] - gray) * saturation;
data[i+1] = gray + (data[i+1] - gray) * saturation;
data[i+2] = gray + (data[i+2] - gray) * saturation;
}
}
// 褐色滤镜
function applySepia(imageData, intensity = 0.54) {
const data = imageData;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i+1], b = data[i+2];
data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189)) * intensity;
data[i+1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168)) * intensity;
data[i+2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131)) * intensity;
}
return imageData;
}
export default [
invertFilter,
// brightnessFilter,
applyContrast,
applySaturation,
// applySepia
];
map.vue
<template>
<div id="map"></div>
</template>
<script setup>
import { onMounted } from "vue"
import mapboxgl from "mapbox-gl"
import "mapbox-gl/dist/mapbox-gl.css"
import { MapboxLayer } from "@deck.gl/mapbox"
import { BitmapLayer } from "@deck.gl/layers"
import { TileLayer } from "@deck.gl/geo-layers"
import filterFuncs from "./filter" //滤镜函数列表
mapboxgl.accessToken = "accessToken" //v2及以上版本必需accessToken
const token = "天地图token"
// 应用bitImage滤镜
async function applyFilter(imageBitmap, filterFunctions) {
// 1. 创建离屏Canvas
const canvas = new OffscreenCanvas(imageBitmap.width, imageBitmap.height)
const ctx = canvas.getContext("2d")
// 2. 绘制原始图像
ctx.drawImage(imageBitmap, 0, 0)
// 3. 获取像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
const data = imageData.data
// 4. 应用滤镜算法
for (const filterFunction of filterFunctions) {
filterFunction(data)
}
// 5. 回写处理后的数据
ctx.putImageData(imageData, 0, 0)
const bitData = await createImageBitmap(canvas)
// 6. 生成新ImageBitmap
return bitData
}
onMounted(() => {
// 初始化地图
const map = new mapboxgl.Map({
container: "map",
zoom: 6,
center: [110.210792, 30.246026],
style: {
version: 8,
name: "base",
sources: {
TDT_cva: {
type: "raster",
tiles: [
"https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=" +
token
],
tileSize: 256
}
},
layers: [
{
id: "tdt-cva-layer",
type: "raster",
source: "TDT_cva"
}
]
}
})
map.on("load", () => {
// 加载影像栅格切片并应用滤镜
let layer = new MapboxLayer({
id: "tdt_vec",
type: TileLayer,
data:
"https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=" +
token,
maxZoom: 18,
minZoom: 0,
renderSubLayers: props => {
const { boundingBox } = props.tile
return new BitmapLayer(props, {
data: null,
image: applyFilter(props.data, filterFuncs),
bounds: [boundingBox[0][0], boundingBox[0][1], boundingBox[1][0], boundingBox[1][1]]
})
}
})
map.addLayer(layer, "tdt-cva-layer")
})
})
</script>
<style scoped>
#map {
width: 100vw;
height: 100vh;
}
</style>
最终效果如下