Skip to content

案例 底图颜色

使用RasterSource栅格数据源,图层必须使用ImageLayer来渲染

功能说明

  1. 通过属性operationType: "image", 设置为 image 模式,保持无卡顿高效渲染
  2. 通过调节RGB取色来修改底图颜色,同时保证底图字体随着颜色变化而变化
展开代码
vue
<template>
  <div>
    <div id="map" class="map"></div>
    <table class="controls">
      <tr>
        <td><label for="red">Red</label></td>
        <td>
          <input id="red" type="range" min="0" max="500" v-model="redValue" />
        </td>
        <td>
          <span>{{ redValue }}</span> %
        </td>
      </tr>
      <tr>
        <td><label for="green">Green</label></td>
        <td>
          <input
            id="green"
            type="range"
            min="0"
            max="500"
            v-model="greenValue"
          />
        </td>
        <td>
          <span>{{ greenValue }}</span> %
        </td>
      </tr>
      <tr>
        <td><label for="blue">Blue</label></td>
        <td>
          <input id="blue" type="range" min="0" max="500" v-model="blueValue" />
        </td>
        <td>
          <span>{{ blueValue }}</span> %
        </td>
      </tr>
    </table>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from "vue";
import Map from "ol/Map.js";
import View from "ol/View.js";
import XYZ from "ol/source/XYZ.js";
import ImageLayer from "ol/layer/Image.js";
import RasterSource from "ol/source/Raster.js";
import "ol/ol.css";

let map = null;
let raster = null;

// 定义响应式变量,用于双向绑定
const redValue = ref(89);
const greenValue = ref(223);
const blueValue = ref(325);

onMounted(() => {
  // 定义RGB调节函数
  const adjustRGB = function (imageData, data) {
    const pixels = imageData.data;
    const redMultiplier = data.red || 89;
    const greenMultiplier = data.green || 223;
    const blueMultiplier = data.blue || 325;
    // 遍历所有像素 (每4个值代表一个像素: R, G, B, A)
    for (let i = 0; i < pixels.length; i += 4) {
      pixels[i] = redMultiplier - pixels[i]; // Red
      pixels[i + 1] = greenMultiplier - pixels[i + 1]; // Green
      pixels[i + 2] = blueMultiplier - pixels[i + 2]; // Blue
    }
  };

  // 创建栅格源 - 使用XYZ瓦片源
  raster = new RasterSource({
    sources: [
      new XYZ({
        url: "https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}",
        crossOrigin: "anonymous", // 添加跨域支持
      }),
    ],
    operationType: "image", // 设置为image模式,无卡顿高效渲染
    operation: function (pixels, data) {
      // 在image模式下,pixels[0]是ImageData对象
      const imageData = pixels[0];
      // 调用RGB调节函数
      adjustRGB(imageData, data);
      // 返回修改后的ImageData
      return imageData;
    },
    // 将函数添加到lib中
    lib: {
      adjustRGB: adjustRGB,
    },
  });

  // 监听栅格操作前事件,将响应式变量的值传递给操作数据
  raster.on("beforeoperations", function (event) {
    const data = event.data;
    data.red = redValue.value;
    data.green = greenValue.value;
    data.blue = blueValue.value;
  });

  // 创建地图
  map = new Map({
    layers: [
      // 使用ImageLayer显示处理后的栅格
      new ImageLayer({
        source: raster,
      }),
    ],
    target: "map",
    view: new View({
      center: [12958752, 4825923], // 中国中心坐标 (Web Mercator)
      zoom: 4,
      maxZoom: 18,
    }),
  });

  // 监听响应式变量的变化,并触发栅格图层更新
  watch([redValue, greenValue, blueValue], () => {
    if (raster) {
      raster.changed();
    }
  });
});

onUnmounted(() => {
  if (map) {
    map.setTarget(null);
    map = null;
  }
});
</script>

<style scoped>
.map {
  width: 100vw;
  height: 100vh;
}
table.controls {
  position: absolute;
  top: 10px;
  right: 10px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 5px;
  padding: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}
table.controls td {
  padding: 2px 5px;
}
table.controls td:nth-child(3) {
  text-align: right;
  min-width: 4.5em;
}
table.controls label {
  font-weight: bold;
  color: #333;
}
table.controls input[type="range"] {
  width: 150px;
}
table.controls span {
  color: #666;
  font-family: monospace;
}
/* 为不同的滑块添加颜色提示 */
table.controls tr:nth-child(1) input[type="range"] {
  accent-color: #ff4444;
}
table.controls tr:nth-child(2) input[type="range"] {
  accent-color: #44ff44;
}
table.controls tr:nth-child(3) input[type="range"] {
  accent-color: #4444ff;
}
</style>

官方 hcl 案例调节代码

官网案例路径,该案例底图字体不会随颜色变化而变化,渲染卡顿,不建议使用

展开代码
vue
<template>
  <div>
    <div id="map" class="map"></div>
    <table class="controls">
      <tr>
        <td><label for="hue">hue</label></td>
        <td><input id="hue" type="range" min="-180" max="180" value="0" /></td>
        <td><span id="hueOut"></span> °&nbsp;</td>
      </tr>
      <tr>
        <td><label for="chroma">chroma</label></td>
        <td>
          <input id="chroma" type="range" min="0" max="100" value="100" />
        </td>
        <td><span id="chromaOut"></span> %</td>
      </tr>
      <tr>
        <td><label for="lightness">lightness</label></td>
        <td>
          <input id="lightness" type="range" min="0" max="100" value="100" />
        </td>
        <td><span id="lightnessOut"></span> %</td>
      </tr>
    </table>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted } from "vue";
import Map from "ol/Map.js";
import View from "ol/View.js";
import XYZ from "ol/source/XYZ.js";
import ImageLayer from "ol/layer/Image.js";
import RasterSource from "ol/source/Raster.js";
import "ol/ol.css";

const Xn = 0.95047;
const Yn = 1;
const Zn = 1.08883;
const t0 = 4 / 29;
const t1 = 6 / 29;
const t2 = 3 * t1 * t1;
const t3 = t1 * t1 * t1;
const twoPi = 2 * Math.PI;

/**
 * Convert an RGB pixel into an HCL pixel.
 * @param {Array<number>} pixel A pixel in RGB space.
 * @return {Array<number>} A pixel in HCL space.
 */
function rgb2hcl(pixel) {
  const red = rgb2xyz(pixel[0]);
  const green = rgb2xyz(pixel[1]);
  const blue = rgb2xyz(pixel[2]);
  const x = xyz2lab(
    (0.4124564 * red + 0.3575761 * green + 0.1804375 * blue) / Xn
  );
  const y = xyz2lab(
    (0.2126729 * red + 0.7151522 * green + 0.072175 * blue) / Yn
  );
  const z = xyz2lab(
    (0.0193339 * red + 0.119192 * green + 0.9503041 * blue) / Zn
  );
  const l = 116 * y - 16;
  const a = 500 * (x - y);
  const b = 200 * (y - z);
  const c = Math.sqrt(a * a + b * b);
  let h = Math.atan2(b, a);
  if (h < 0) {
    h += twoPi;
  }
  pixel[0] = h;
  pixel[1] = c;
  pixel[2] = l;
  return pixel;
}

/**
 * Convert an HCL pixel into an RGB pixel.
 * @param {Array<number>} pixel A pixel in HCL space.
 * @return {Array<number>} A pixel in RGB space.
 */
function hcl2rgb(pixel) {
  const h = pixel[0];
  const c = pixel[1];
  const l = pixel[2];
  const a = Math.cos(h) * c;
  const b = Math.sin(h) * c;
  let y = (l + 16) / 116;
  let x = isNaN(a) ? y : y + a / 500;
  let z = isNaN(b) ? y : y - b / 200;
  y = Yn * lab2xyz(y);
  x = Xn * lab2xyz(x);
  z = Zn * lab2xyz(z);
  pixel[0] = xyz2rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
  pixel[1] = xyz2rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
  pixel[2] = xyz2rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
  return pixel;
}

function xyz2lab(t) {
  return t > t3 ? Math.pow(t, 1 / 3) : t / t2 + t0;
}

function lab2xyz(t) {
  return t > t1 ? t * t * t : t2 * (t - t0);
}

function rgb2xyz(x) {
  return (x /= 255) <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
}

function xyz2rgb(x) {
  return (
    255 * (x <= 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055)
  );
}

let map = null;
let raster = null;
const controls = {};

onMounted(() => {
  // 创建栅格源 - 使用XYZ瓦片源
  raster = new RasterSource({
    sources: [
      new XYZ({
        url: "https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}",
        crossOrigin: "anonymous", // 添加跨域支持
      }),
    ],
    //这里设置为image类型,与官方示例不同,优化速度
    // operationType: "image",
    operation: function (pixels, data) {
      const hcl = rgb2hcl(pixels[0]);
      let h = hcl[0] + (Math.PI * data.hue) / 180;
      if (h < 0) {
        h += twoPi;
      } else if (h > twoPi) {
        h -= twoPi;
      }
      hcl[0] = h;
      hcl[1] *= data.chroma / 100;
      hcl[2] *= data.lightness / 100;
      return hcl2rgb(hcl);
    },
    lib: {
      rgb2hcl: rgb2hcl,
      hcl2rgb: hcl2rgb,
      rgb2xyz: rgb2xyz,
      lab2xyz: lab2xyz,
      xyz2lab: xyz2lab,
      xyz2rgb: xyz2rgb,
      Xn: Xn,
      Yn: Yn,
      Zn: Zn,
      t0: t0,
      t1: t1,
      t2: t2,
      t3: t3,
      twoPi: twoPi,
    },
  });

  // 监听栅格操作前事件
  raster.on("beforeoperations", function (event) {
    const data = event.data;
    for (const id in controls) {
      data[id] = Number(controls[id].value);
    }
  });

  // 创建地图
  map = new Map({
    layers: [
      // 使用ImageLayer显示处理后的栅格
      new ImageLayer({
        source: raster,
      }),
    ],
    target: "map",
    view: new View({
      center: [12958752, 4825923], // 中国中心坐标 (Web Mercator)
      zoom: 4,
      maxZoom: 18,
    }),
  });

  // 初始化控制器
  const controlIds = ["hue", "chroma", "lightness"];
  controlIds.forEach(function (id) {
    const control = document.getElementById(id);
    const output = document.getElementById(id + "Out");
    control.addEventListener("input", function () {
      output.innerText = control.value;
      raster.changed();
    });
    output.innerText = control.value;
    controls[id] = control;
  });
});

onUnmounted(() => {
  if (map) {
    map.setTarget(null);
    map = null;
  }
});
</script>

<style scoped>
.map {
  width: 100vw;
  height: 100vh;
}

table.controls {
  position: absolute;
  top: 10px;
  right: 10px;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 5px;
  padding: 10px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  z-index: 1000;
}

table.controls td {
  padding: 2px 5px;
}

table.controls td:nth-child(3) {
  text-align: right;
  min-width: 4.5em;
}

table.controls label {
  font-weight: bold;
  color: #333;
}

table.controls input[type="range"] {
  width: 150px;
}

table.controls span {
  color: #666;
  font-family: monospace;
}
</style>