openlayers marker光晕扩散(光环扩散)(postrender事件和render方法)

本篇介绍一下使用openlayersmarker光晕扩散(光环扩散)(postrender事件和render方法)

1 需求

  • marker光晕扩散(光环扩散)

2 分析

marker光晕扩散(光环扩散)使用postrender事件和render方法

3 知识点

即时渲染

  1. 解释:
    OpenLayers 的即时渲染功能允许用户在不先将几何图形添加到图层的情况下,直接在渲染事件中绘制样式化的几何图形。可以根据应用的需求,在渲染事件中动态地添加、修改或删除几何图形。
  2. 使用:
  • openLayers 提供了即时渲染 API,允许在渲染事件中直接绘制几何图形,如在postrender事件中,使用即时渲染 API 来绘制和更新几何图形。
  • 使用 getVectorContext 函数从渲染事件中获取渲染上下文。
  • 在这个渲染上下文中,使用 context.drawGeometry() 和 context.setStyle() 方法来绘制和设置样式的几何体。
  • 如果想在渲染过程中获取或处理即时渲染的几何图形,通常并不是直接“获取”,因为它们是作为渲染过程的一部分被绘制到 Canvas 上的
  • 如果需要更精细地控制几何图形的渲染,可以通过自定义图层的渲染函数来实现。这通常涉及到覆盖图层的 render 方法,并在其中使用 getVectorContext 来获取渲染上下文,并直接调用渲染上下文的方法来绘制几何图形。
  • 如果想要获取渲染后的几何图形在 Canvas 上的像素数据(例如,用于进一步分析或导出为图像),可以使用 Canvas 的 toDataURL 方法来获取包含渲染结果的图像数据。

4 实现

4.1 实现一(使用vectorContext)

<template>
  <div id="map" class="map"></div>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ animationFlag ? '停止' : '开始' }}</el-button>
    <el-input v-model.number="duration"></el-input><span>持续时间</span>
  </div>
</template>

<script setup lang="ts">
import { Feature, Map, View } from 'ol';
import { LineString, Point } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { get } from 'ol/proj';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
import { easeOut } from 'ol/easing.js';
import { getVectorContext } from 'ol/render.js';
import iconSrc from '@/assets/image/truck.png';

const projection = get('EPSG:4326');

const layerTypeMap = {
  vector: ['vec', 'cva'], // [矢量底图, 矢量注记]
  image: ['img', 'cia'], // [影像底图, 影像注记]
  terrain: ['ter', 'cta'] // [地形晕渲, 地形注记]
};

const map = shallowRef();
const vectorLayer = shallowRef();
const source = shallowRef<VectorSource>(new VectorSource());
const animationFlag = ref(false);
const duration = ref(3000);
const start = ref(Date.now());

onMounted(() => {
  initMap('image');
  initFeature();
});

const initMap = (layerType = 'image') => {
  const key = '替换为天地图key';

  vectorLayer.value = new VectorLayer({
    source: source.value,
    style: new Style({
      image: new Icon({
        crossOrigin: 'anonymous',
        src: iconSrc,
        width: 30,
        height: 30
      })
    })
  });
  // c: 经纬度投影 w: 墨卡托投影
  const matrixSet = 'c';
  map.value = new Map({
    target: 'map',
    layers: [
      // 底图
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][0]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      // 注记
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][1]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      vectorLayer.value
    ],
    view: new View({
      center: [116.406393, 39.909006],
      projection: projection,
      zoom: 5,
      maxZoom: 17,
      minZoom: 1
    })
  });
};

const initFeature = () => {
  const coordinates = [
    [110, 30],
    [111, 31],
    [112, 32],
    [113, 33]
  ];
  coordinates.forEach(c => {
    const f = new Feature({
      geometry: new Point(c)
    });
    source.value.addFeature(f);
  });
};

const handleClick = () => {
  if (!animationFlag.value) {
    startAnimation();
  } else {
    stopAnimation();
  }
  animationFlag.value = !animationFlag.value;
};

const startAnimation = () => {
  start.value = Date.now();
  vectorLayer.value.on('postrender', flash);
  // 触发地图渲染
  vectorLayer.value.changed();
};
const flash = e => {
  const time = e.frameState.time;
  // 时间戳差(毫秒)
  const elapsedTime = time - start.value;
  if (elapsedTime >= duration.value) {
    stopAnimation();
    startAnimation();
    return;
  }
	// 获取用于绘制到事件画布的矢量上下文
  const vectorContext = getVectorContext(e);
  // elapsedRatio值0到1之间
  const elapsedRatio = elapsedTime / duration.value;
  const radius = easeOut(elapsedRatio) * 25 + 5;
  const opacity = easeOut(1 - elapsedRatio);

  const style = new Style({
    image: new Circle({
      radius: radius,
      stroke: new Stroke({
        color: 'rgba(255, 0, 0, ' + opacity + ')',
        width: 1 + opacity
      })
    })
  });

	// 首先调用setStyle()来设置呈现样式
  vectorContext.setStyle(style);
  const fea = source.value.getFeatures();
  fea.forEach(f => {
    const geo = f.getGeometry().clone();
    // 将几何体渲染到画布中
    vectorContext.drawGeometry(geo);
  });
  // 调用地图渲染
  map.value.render();
};

const stopAnimation = () => {
  vectorLayer.value.un('postrender', flash);
};
</script>
<style scoped lang="scss">
.map {
  width: 100%;
  height: 100%;
}
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  color:#FFF;
  .el-input {
    width: 100px;
    margin: 0 20px;
  }
}
</style>

4.2 实现二(不使用vectorContext,直接动态修改样式)

<template>
  <div id="map" class="map"></div>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ animationFlag ? '停止' : '开始' }}</el-button>
    <el-input v-model.number="duration"></el-input><span>持续时间</span>
  </div>
</template>

<script setup lang="ts">
import { Feature, Map, View } from 'ol';
import { LineString, Point } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { get } from 'ol/proj';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
import { easeOut } from 'ol/easing.js';
import iconSrc from '@/assets/image/truck.png';

const projection = get('EPSG:4326');

const layerTypeMap = {
  vector: ['vec', 'cva'], // [矢量底图, 矢量注记]
  image: ['img', 'cia'], // [影像底图, 影像注记]
  terrain: ['ter', 'cta'] // [地形晕渲, 地形注记]
};

const map = shallowRef();
const vectorLayer = shallowRef();
const source = shallowRef<VectorSource>(new VectorSource());
const animationFlag = ref(false);
const duration = ref(3000);
const start = ref(Date.now());
const iconStyle = new Style({
  image: new Icon({
    crossOrigin: 'anonymous',
    src: iconSrc,
    width: 30,
    height: 30
  })
});

onMounted(() => {
  initMap('image');
  initFeature();
});

const initMap = (layerType = 'image') => {
  const key = '替换为天地图key';

  vectorLayer.value = new VectorLayer({
    source: source.value,
    style: iconStyle
  });
  // c: 经纬度投影 w: 墨卡托投影
  const matrixSet = 'c';
  map.value = new Map({
    target: 'map',
    layers: [
      // 底图
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][0]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      // 注记
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][1]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      vectorLayer.value
    ],
    view: new View({
      center: [116.406393, 39.909006],
      projection: projection,
      zoom: 5,
      maxZoom: 17,
      minZoom: 1
    })
  });
};

const initFeature = () => {
  const coordinates = [
    [110, 30],
    [111, 31],
    [112, 32],
    [113, 33]
  ];
  coordinates.forEach(c => {
    const f = new Feature({
      geometry: new Point(c)
    });
    source.value.addFeature(f);
  });
};

const handleClick = () => {
  if (!animationFlag.value) {
    startAnimation();
  } else {
    stopAnimation();
  }
  animationFlag.value = !animationFlag.value;
};

const startAnimation = () => {
  start.value = Date.now();
  vectorLayer.value.on('postrender', flash);
  // 触发地图渲染
  vectorLayer.value.changed();
};
const flash = e => {
  const time = e.frameState.time;
  // 时间戳差(毫秒)
  const elapsedTime = time - start.value;
  if (elapsedTime >= duration.value) {
    stopAnimation();
    startAnimation();
    return;
  }
  // elapsedRatio值0到1之间
  const elapsedRatio = elapsedTime / duration.value;
  const radius = easeOut(elapsedRatio) * 25 + 5;
  const opacity = easeOut(1 - elapsedRatio);

  const style = new Style({
    image: new Circle({
      radius: radius,
      stroke: new Stroke({
        color: 'rgba(255, 0, 0, ' + opacity + ')',
        width: 1 + opacity
      })
    })
  });

  const fea = source.value.getFeatures();
  fea.forEach(f => {
    f.setStyle([iconStyle, style]);
  });
  // 调用地图渲染
  map.value.render();
};

const stopAnimation = () => {
  const fea = source.value.getFeatures();
  fea.forEach(f => {
    f.setStyle(iconStyle);
  });
  vectorLayer.value.un('postrender', flash);
};
</script>
<style scoped lang="scss">
.map {
  width: 100%;
  height: 100%;
}
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  .el-input {
    width: 100px;
    margin: 0 20px;
  }
}
</style>


问题:

所有扩散频率都是一致的,过于呆板

分析:

频率一致主要是因为duration和start一样,只要为不同图标修改为不同值的即可

4.3 实现三(多个icon)

<template>
  <div id="map" class="map"></div>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ animationFlag ? '停止' : '开始' }}</el-button>
  </div>
</template>

<script setup lang="ts">
import { Feature, Map, View } from 'ol';
import { LineString, Point } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { get } from 'ol/proj';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
import { easeOut } from 'ol/easing.js';
import { getVectorContext } from 'ol/render.js';
import iconSrc from '@/assets/image/truck.png';

const projection = get('EPSG:4326');

const layerTypeMap = {
  vector: ['vec', 'cva'], // [矢量底图, 矢量注记]
  image: ['img', 'cia'], // [影像底图, 影像注记]
  terrain: ['ter', 'cta'] // [地形晕渲, 地形注记]
};

const map = shallowRef();
const vectorLayer = shallowRef();
const source = shallowRef<VectorSource>(new VectorSource());
const animationFlag = ref(false);
const duration = ref([3000, 5000, 8000, 1000]);//可以加入随机值
const start = ref([0, 0, 0, 0]);

onMounted(() => {
  initMap('image');
  initFeature();
});

const initMap = (layerType = 'image') => {
  const key = '替换为天地图key';

  vectorLayer.value = new VectorLayer({
    source: source.value,
    style: new Style({
      image: new Icon({
        crossOrigin: 'anonymous',
        src: iconSrc,
        width: 30,
        height: 30
      })
    })
  });
  // c: 经纬度投影 w: 墨卡托投影
  const matrixSet = 'c';
  map.value = new Map({
    target: 'map',
    layers: [
      // 底图
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][0]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      // 注记
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][1]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      vectorLayer.value
    ],
    view: new View({
      center: [116.406393, 39.909006],
      projection: projection,
      zoom: 5,
      maxZoom: 17,
      minZoom: 1
    })
  });
};

const initFeature = () => {
  const coordinates = [
    [110, 30],
    [111, 31],
    [112, 32],
    [113, 33]
  ];
  coordinates.forEach(c => {
    const f = new Feature({
      geometry: new Point(c)
    });
    source.value.addFeature(f);
  });
};

const handleClick = () => {
  if (!animationFlag.value) {
    startAnimation();
  } else {
    stopAnimation();
  }
  animationFlag.value = !animationFlag.value;
};

const startAnimation = () => {
  start.value = start.value.map(i => Date.now());
  vectorLayer.value.on('postrender', flash);
  // 触发地图渲染
  vectorLayer.value.changed();
};
const flash = e => {
  const time = e.frameState.time;
  const fea = source.value.getFeatures();
  fea.forEach((f, idx) => {
  // 时间戳差(毫秒)
    let elapsedTime = time - start.value[idx];
    if (elapsedTime >= duration.value[idx]) {
      start.value[idx] = Date.now();
      elapsedTime = duration.value[idx];
    }
    const vectorContext = getVectorContext(e);
    // elapsedRatio值0到1之间
    const elapsedRatio = elapsedTime / duration.value[idx];
    const radius = easeOut(elapsedRatio) * 25 + 5;
    const opacity = easeOut(1 - elapsedRatio);

    const style = new Style({
      image: new Circle({
        radius: radius,
        stroke: new Stroke({
          color: 'rgba(255, 0, 0, ' + opacity + ')',
          width: 1 + opacity
        })
      })
    });
		// 将feature渲染到画布中。
    vectorContext.drawFeature(f.clone(), style);
  });
  // 调用地图渲染
  map.value.render();
};

const stopAnimation = () => {
  vectorLayer.value.un('postrender', flash);
};
</script>
<style scoped lang="scss">
.map {
  width: 100%;
  height: 100%;
}
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  .el-input {
    width: 100px;
    margin: 0 20px;
  }
}
</style>


4.4 实现四(增加光晕)

<template>
  <div id="map" class="map"></div>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ animationFlag ? '停止' : '开始' }}</el-button>
  </div>
</template>

<script setup lang="ts">
import { Feature, Map, View } from 'ol';
import { LineString, Point } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { get } from 'ol/proj';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
import { easeOut } from 'ol/easing.js';
import { getVectorContext } from 'ol/render.js';
import iconSrc from '@/assets/image/truck.png';

const projection = get('EPSG:4326');

const layerTypeMap = {
  vector: ['vec', 'cva'], // [矢量底图, 矢量注记]
  image: ['img', 'cia'], // [影像底图, 影像注记]
  terrain: ['ter', 'cta'] // [地形晕渲, 地形注记]
};

const map = shallowRef();
const vectorLayer = shallowRef();
const source = shallowRef<VectorSource>(new VectorSource());
const animationFlag = ref(false);
const duration = ref([3000, 5000, 8000, 1000]);
const start = ref([0, 0, 0, 0]);

onMounted(() => {
  initMap('image');
  initFeature();
});

const initMap = (layerType = 'image') => {
  const key = '替换为天地图key';

  vectorLayer.value = new VectorLayer({
    source: source.value,
    style: new Style({
      image: new Circle({
        radius: 0,
        fill: new Fill({
          color: 'rgba(228, 147, 87, 1)'
        }),
        stroke: new Stroke({
          color: 'rgba(255, 0, 0,1)',
          width: 1 
        })
      })
    })
  });
  // c: 经纬度投影 w: 墨卡托投影
  const matrixSet = 'c';
  map.value = new Map({
    target: 'map',
    layers: [
      // 底图
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][0]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      // 注记
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][1]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      vectorLayer.value,
      new VectorLayer({
        source: source.value,
        style: new Style({
          image: new Icon({
            crossOrigin: 'anonymous',
            src: iconSrc,
            width: 30,
            height: 30
          })
        })
      })
    ],
    view: new View({
      center: [116.406393, 39.909006],
      projection: projection,
      zoom: 5,
      maxZoom: 17,
      minZoom: 1
    })
  });
};

const initFeature = () => {
  const coordinates = [
    [110, 30],
    [111, 31],
    [112, 32],
    [113, 33]
  ];
  coordinates.forEach(c => {
    const f = new Feature({
      geometry: new Point(c)
    });
    source.value.addFeature(f);
  });
};

const handleClick = () => {
  if (!animationFlag.value) {
    startAnimation();
  } else {
    stopAnimation();
  }
  animationFlag.value = !animationFlag.value;
};

const startAnimation = () => {
  start.value = start.value.map(i => Date.now());
  vectorLayer.value.on('postrender', flash);
  // 触发地图渲染
  vectorLayer.value.changed();
};
const flash = e => {
  const time = e.frameState.time;
  const fea = source.value.getFeatures();
  fea.forEach((f, idx) => {
    // 时间戳差(毫秒)
    let elapsedTime = time - start.value[idx];
    if (elapsedTime >= duration.value[idx]) {
      start.value[idx] = Date.now();
      elapsedTime = duration.value[idx];
    }
    const vectorContext = getVectorContext(e);
    // elapsedRatio值0到1之间
    const elapsedRatio = elapsedTime / duration.value[idx];
    const radius = easeOut(elapsedRatio) * 25 + 5;
    const opacity = easeOut(1 - elapsedRatio);

    const style = new Style({
      image: new Circle({
        radius: radius,
        fill: new Fill({
          color: 'rgba(228, 147, 87, ' + opacity + ')'
        }),
        stroke: new Stroke({
          color: 'rgba(255, 0, 0, ' + opacity + ')',
          width: 1 + opacity
        })
      })
    });
    // 将feature渲染到画布中。
    vectorContext.drawFeature(f.clone(), style);
  });
  // 调用地图渲染
  map.value.render();
};

const stopAnimation = () => {
  vectorLayer.value.un('postrender', flash);
};
</script>
<style scoped lang="scss">
.map {
  width: 100%;
  height: 100%;
}
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  .el-input {
    width: 100px;
    margin: 0 20px;
  }
}
</style>


  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值