这个示例讲的是如何修改地图的颜色。
先初始化一个地图
this.map = new Map({
target: "map",
view: new View({
projection: "EPSG:4326",
center: [0, 0],
zoom: 2,
maxZoom: 12,
}),
});
初始化一个颜色信息的对象
colorObj: {
redMax: 3500,
greenMax: 3500,
blueMax: 3500,
red: 1,
green: 2,
blue: 3,
},
加载geotiff格式的地图,TileLayer表示瓦片图层,color定义了图层样式数组。其中["/", ["band", ["var", "red"]], ["var", "redMax"]]
:是一个颜色通道的定义。它使用了一个除法运算符 "/"
,将图像的一个波段(band)值除以一个最大值来计算颜色通道的值。["band", ["var", "red"]]
表示取图像的红色波段的值,["var", "redMax"]
表示取 redMax
变量的值作为最大值。1表示透明度。通过计算图像的红、绿、蓝通道的值,以及设定的最大值,来确定图像的颜色。(平时拿ps软件p图的时候也是调节的红绿蓝通道,具体原理及操作我这个非专业人士也不是很了解)。
addGeoTifLayer() {
this.geoTifLayer = new TileLayer({
style: {
variables: this.colorObj,
color: [
"array",
["/", ["band", ["var", "red"]], ["var", "redMax"]],
["/", ["band", ["var", "green"]], ["var", "greenMax"]],
["/", ["band", ["var", "blue"]], ["var", "blueMax"]],
1,
],
},
source: new GeoTIFF({
normalize: false,
sources: [
{
url: `https://s2downloads.eox.at/demo/EOxCloudless/2020/
rgbnir/s2cloudless2020-16bits_sinlge-file_z0-4.tif`,
},
],
}),
});
this.map.addLayer(this.geoTifLayer);
},
然后html里写了选框和滑块动态调整值,生成一个颜色信息对象,调用updateStyleVariables就可以实现动态修改地图颜色了。现实需求中,如果修改地图颜色只是为了完成UI设计,可以使用滑块调节好了把colorObj的值直接复制过去。注意传递的对象里的值要是数字格式的。
我这里用vue的语法改写了事件,v-model.number让获取到的滑块的值是数字而非字符串。
<label for="blue">蓝</label>
<select id="blue" @change="update" v-model="colorObj.blue">
<option :value="1">visible red</option>
<option :value="2">visible green</option>
<option :value="3" selected>visible blue</option>
<option :value="4">near infrared</option>
</select>
<label
>max
<input
type="range"
@input="update"
id="blueMax"
:min="2000"
:max="5000"
v-model.number="colorObj.blueMax"
/>
</label>
值得我们注意的是,不是所有地图都可以通过这种方式去修改颜色的,只有使用geotiff格式的地图才可以。原谅我目前我还没找到别的地图可以这样修改颜色的/😓/,学习的道路漫长且艰辛,待我慢慢踩坑。。
但是之前工作中用另外一种方式修改了地图颜色,这里给大家也分享下,这种思路主要是在图层加载时,在tileLoadFunction函数里面拿到每个切片的图像src,手动绘制canvas把图片加上去,并通过context.filter给图片添加滤镜。调节滤镜的参数即可修改颜色。我这里使用的底图是arcgis的,这里使用天地图、高德地图等其他地图也是可以的。
addunderLayer() {
const layer = new Tile({
source: new XYZ({
url: `https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}`,
tileLoadFunction: function (imageTile, src) {
let img = new Image();
img.crossOrigin = "";
img.onload = function () {
let canvas = document.createElement("canvas");
let w = img.width;
let h = img.height;
canvas.width = w;
canvas.height = h;
let context = canvas.getContext("2d");
context.filter =
"grayscale(0%) invert(15%) sepia(0%) hue-rotate(75deg) saturate(200%) brightness(100%) contrast(100%)";
// grayscale 灰度 invert反相 sepia将图像转化成深褐色 saturate饱和度 brightness暗度 contrast对比度
context.drawImage(img, 0, 0, w, h, 0, 0, w, h);
imageTile.getImage().src = canvas.toDataURL("image/png");
};
img.src = src;
},
}),
zIndex: 1,
});
this.map.addLayer(layer);
},
完整代码:
<template>
<div class="box">
<h1>BandContrastStretch</h1>
<div id="map"></div>
<div class="controls">
<label for="red">红</label>
<select id="red" @change="update" v-model="colorObj.red">
<option :value="1" selected>visible red</option>
<option :value="2">visible green</option>
<option :value="3">visible blue</option>
<option :value="4">near infrared</option>
</select>
<label
>max
<input
type="range"
id="redMax"
@input="update"
v-model.number="colorObj.redMax"
:min="2000"
:max="5000"
/>
</label>
<label for="green">绿</label>
<select id="green" @change="update" v-model="colorObj.green">
<option :value="1">visible red</option>
<option :value="2" selected>visible green</option>
<option :value="3">visible blue</option>
<option :value="4">near infrared</option>
</select>
<label
>max
<input
type="range"
id="greenMax"
@input="update"
:min="2000"
v-model.number="colorObj.greenMax"
:max="5000"
/>
</label>
<label for="blue">蓝</label>
<select id="blue" @change="update" v-model="colorObj.blue">
<option :value="1">visible red</option>
<option :value="2">visible green</option>
<option :value="3" selected>visible blue</option>
<option :value="4">near infrared</option>
</select>
<label
>max
<input
type="range"
@input="update"
id="blueMax"
:min="2000"
:max="5000"
v-model.number="colorObj.blueMax"
/>
</label>
</div>
</div>
</template>
<script>
import GeoTIFF from "ol/source/GeoTIFF.js";
import Map from "ol/Map.js";
import TileLayer from "ol/layer/WebGLTile.js";
import Tile from "ol/layer/Tile.js";
import View from "ol/View.js";
import { XYZ } from "ol/source";
export default {
name: "",
components: {},
data() {
return {
map: null,
geoTifLayer: null,
colorObj: {
redMax: 3500,
greenMax: 3500,
blueMax: 3500,
red: 1,
green: 2,
blue: 3,
},
};
},
computed: {},
created() {},
methods: {
initMap() {
this.map = new Map({
target: "map",
view: new View({
projection: "EPSG:4326",
center: [0, 0],
zoom: 2,
maxZoom: 12,
}),
});
},
update() {
this.geoTifLayer.updateStyleVariables(this.colorObj);
},
addGeoTifLayer() {
this.geoTifLayer = new TileLayer({
style: {
variables: this.colorObj,
color: [
"array",
["/", ["band", ["var", "red"]], ["var", "redMax"]],
["/", ["band", ["var", "green"]], ["var", "greenMax"]],
["/", ["band", ["var", "blue"]], ["var", "blueMax"]],
1,
],
},
source: new GeoTIFF({
normalize: false,
sources: [
{
url: "https://s2downloads.eox.at/demo/EOxCloudless/2020/rgbnir/s2cloudless2020-16bits_sinlge-file_z0-4.tif",
},
],
}),
});
this.map.addLayer(this.geoTifLayer);
},
addunderLayer() {
const layer = new Tile({
source: new XYZ({
url: `https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}`,
tileLoadFunction: function (imageTile, src) {
console.log('src',src);
let img = new Image();
img.crossOrigin = "";
img.onload = function () {
let canvas = document.createElement("canvas");
let w = img.width;
let h = img.height;
canvas.width = w;
canvas.height = h;
let context = canvas.getContext("2d");
context.filter =
"grayscale(0%) invert(15%) sepia(0%) hue-rotate(75deg) saturate(200%) brightness(100%) contrast(100%)";
// grayscale 灰度 invert反相 sepia将图像转化成深褐色 saturate饱和度 brightness暗度 contrast对比度
context.drawImage(img, 0, 0, w, h, 0, 0, w, h);
imageTile.getImage().src = canvas.toDataURL("image/png");
};
img.src = src;
},
}),
zIndex: 1,
});
this.map.addLayer(layer);
},
},
mounted() {
this.initMap();
this.addGeoTifLayer();
// this.addunderLayer();
},
};
</script>
<style lang="scss" scoped>
#map {
width: 100%;
height: 500px;
}
.box {
height: 100%;
}
.controls {
display: grid;
grid-template-columns: auto auto 1fr;
align-items: baseline;
gap: 0 1em;
}
</style>