Openlayers实现雷达效果

409 阅读5分钟

效果

视频需要审核,先用两张图凑合一下吧 image.png

image.png

分析

实现一个雷达扫描效果图,我们需要实现两个部分即可分别为底盘扫描区域的基本内容及旋转的扫描区域

如何实现底盘:

  • 方案一
    • 使用ol/geom/Circle或者ol/style/Circle绘制圆
    • 优点:大小可控,并跟随地图缩放
    • 缺点:遇到定制化业务时比较难处理
  • 方案二
    • 使用图片
    • 优点:图片可以显示的更花哨一些
    • 缺点:不能跟随地图缩放

综上根据业务情况选中方案一较好。

实现

底盘的实现

这里的实现方式是用两个圆当作底板

import Circle from 'ol/geom/Circle'
import Feature from 'ol/Feature'
import Style from 'ol/style/Style'
import Stroke from 'ol/style/Stroke'
import { Vector as VectorSource } from 'ol/source'
import { Vector as VectorLayer } from 'ol/layer'
import Fill from 'ol/style/Fill'
import Polygon from 'ol/geom/Polygon'
// 半径转弧度
function distanceToRadian(l) {
   const EARTH_RADIUS = 6378137; // 地球半径
   return l / (2 * Math.PI * EARTH_RADIUS / 360);
}
export default {
   methods: {
    createRadar(opt) {
           const { center, 
           radius, 
           title = 'radar', 
           outerCircleConfig = { 
               zIndex: 100,
               title: 'outerCircle'
            }, innerCircleConfig = {
               zIndex: 100,
               title: 'innerCircle'
            } } = opt
           const radian = distanceToRadian(radius)
            // 绘制外圈圆
           const outerCircle = new Circle(center, radian)
           const outerCircleFeature = new Feature({
               geometry: outerCircle,
           })
          // 设置外圈圆样式
           outerCircleFeature.setStyle(new Style({
               stroke: new Stroke({
                   color: 'rgba(0, 210, 255, 0.7)',
                   width: 3
               }),
               fill: new Fill({
                   color: 'rgba(0, 210, 255, 0.40)'
               }),
               zIndex: outerCircleConfig.zIndex,
               title: outerCircleConfig.title
           }))
           // 绘制内圈圆
           // 内圈圆为外圈圆半径的1/2
           const innerCircle = new Circle(center, radian / 2)
           const innerCircleFeature = new Feature({
               geometry: innerCircle,
           })
           // 设置内圈圆样式
           innerCircleFeature.setStyle(new Style({
               stroke: new Stroke({
                   color: 'rgba(15, 189, 226, 0.6)',
                   width: 3
               }),
               fill: new Fill({
                   color: 'rgba(0, 210, 255, 0.40)'
               }),
               title: innerCircleConfig.title,
               zIndex: innerCircleConfig.zIndex
           }))
           // 创建source
           const vectorSource = new VectorSource({
               features: [outerCircleFeature, innerCircleFeature]
           })
           // 创建layer
           const vectorLayer = new VectorLayer({
               source: vectorSource,
               zIndex: 100,
               title,
           });
           // 添加到地图图层
           this.map.addLayer(vectorLayer)
       },
       mounted() {
           this.createRadar({
               center: this.center, // 中心点
               radius: 10 * 1000,  // 半径 10 * 1000 米
               title: 'circle_radar',
               outerCircleConfig: {
                   zIndex: 100,
                   title: 'outerCircle'
               },
               innerCircleConfig: {
                   zIndex: 100,
                   title: 'innerCircle'
               }
           })
       }

}

这里需要说明一点的是ol/geom/Circle的半径参数是弧度,因此无法具体到半径,需要使用distanceToRadian进行转换。

绘制扫描区域

绘制扫描区域实际上就是实现一个扇形,这里我们使用ol/geom/Polygon实现

let angle = 0; // 起始角度
const scanAngle = Math.PI / 4 // 扇形区域 45° 可自行设置
export default {
    data(){
        return {
            polygonFeature: null
        }
    },
    methods: {
        createRadar(){
            
            ...
            // 获取绘制扇形经纬度点位
            const polygonPoints = this.createScanPoints(center, radian, startAngle, endAngle)
            // 多边形实例
            const polygon = new Polygon([polygonPoints])
            // 多边形特性
            const polygonFeature = new Feature({
                geometry: polygon,
            })
            // 设置扇形样式
            polygonFeature.setStyle(new Style({
                stroke: new Stroke({
                    color: 'transparent',
                    width: 3
                }),
                fill: new Fill({
                    color: 'rgba(0, 210, 255, 0.40)'
                }),
                zIndex: 101,
                title: 'polygon'
            }))
            // 便于后续设置动画
            this.polygonFeature = polygonFeature
            vectorSource.addFeature(polygonFeature)
            this.map.addLayer(vectorLayer)
          
        },
         /**
         * 
         * @param center 中心点
         * @param radius 半径 (弧度)
         * @param startAngle 开始角度
         * @param endAngle 结束角度
         */
        createScanPoints(center, radius, startAngle, endAngle) {
            const points = []
            const step = 0.001 // 数值越小,经纬度颗粒度越细致,可自行设置打印查看points
            for (let a = startAngle; a <= endAngle; a += step) {
                const x = center[0] + radius * Math.cos(a)
                const y = center[1] + radius * Math.sin(a)
                points.push([x, y])
            }
            points.push(center) // 闭合多边形
            return points
        }
    }

}

createScanPoints方法用于获取中心点弧度范围绘制的点,至此我们已经绘制了底盘和扫描区域,接下来就是让扇形运动起来。

旋转扫描区域

export default {
    methods: {
        createRadar(opt) {
            ...
             this.animateRadar({ radian, center })
        },
        createScanPoints(center, radius, startAngle, endAngle) {
            const points = []
            const step = 0.001 // 角度步长
            for (let a = startAngle; a <= endAngle; a += step) {
                const x = center[0] + radius * Math.cos(a)
                const y = center[1] + radius * Math.sin(a)
                points.push([x, y])
            }
            points.push(center) // 闭合多边形
            return points
        },
        /**
         * 
         * @param opt 
         * radian 弧度
         * center 中心点
         */
        animateRadar(opt) {
            const { radian, center } = opt
            angle -= 0.05 // 每次增加的角度,角度越小速度越慢 负:表示顺时针,正:逆时针
            const startAngle = angle - scanAngle / 2
            const endAngle = angle + scanAngle / 2
            const polygonPoints = this.createScanPoints(center, radian, startAngle, endAngle)
            this.polygonFeature.setGeometry(new Polygon([polygonPoints]))
            this.animateId =  requestAnimationFrame(() => this.animateRadar(opt))
        }
    }
}

实现旋转我们只需要每次去改变angle角度就行了,其中负号表示顺时针旋转,正号表示逆时针旋转,切数值越大,旋转越快,可以通过设置angle从而达到对扫描速度的控制。

这里我们使用requestAnimationFrame让扇形区域旋转,requestAnimationFrame相比传统的定时器在性能方面更好。

整体代码

<script>
import Circle from 'ol/geom/Circle'
import Feature from 'ol/Feature'
import Style from 'ol/style/Style'
import Stroke from 'ol/style/Stroke'
import { Vector as VectorSource } from 'ol/source'
import { Vector as VectorLayer } from 'ol/layer'
import Fill from 'ol/style/Fill'
import Polygon from 'ol/geom/Polygon'
let angle = 0;
const scanAngle = Math.PI / 4
// 半径转弧度
function distanceToRadian(l) {
    const EARTH_RADIUS = 6378137; // 地球半径
    return l / (2 * Math.PI * EARTH_RADIUS / 360);
}

export default {
    data() {
        return {
            polygonFeature: null,
            center: [116.53813608364993, 36.91149388658502],
            animateId: null
        }

    },
    computed: {
        map() {
            window.p = this.$refs.htMapRef?.getMap();
            return this.$refs.htMapRef?.getMap()
        },
    },
    methods: {

        createRadar(opt) {
            const { center, radius, title = 'radar', 
            outerCircleConfig = { 
                zIndex: 100,
                title: 'outerCircle'
             }, innerCircleConfig = {
                zIndex: 100,
                title: 'innerCircle'
             } } = opt
            const radian = distanceToRadian(radius)
            const outerCircle = new Circle(center, radian)
            const outerCircleFeature = new Feature({
                geometry: outerCircle,
            })
            // 绘制外圈圆
            outerCircleFeature.setStyle(new Style({
                stroke: new Stroke({
                    color: 'rgba(0, 210, 255, 0.7)',
                    width: 3
                }),
                fill: new Fill({
                    color: 'rgba(0, 210, 255, 0.40)'
                }),
                zIndex: outerCircleConfig.zIndex,
                title: outerCircleConfig.title
            }))
            // 绘制内圈圆
            const innerCircle = new Circle(center, radian / 2)
            const innerCircleFeature = new Feature({
                geometry: innerCircle,
            })
            innerCircleFeature.setStyle(new Style({
                stroke: new Stroke({
                    color: 'rgba(15, 189, 226, 0.6)',
                    width: 3
                }),
                fill: new Fill({
                    color: 'rgba(0, 210, 255, 0.40)'
                }),
                title: innerCircleConfig.title,
                zIndex: innerCircleConfig.zIndex
            }))

            const vectorSource = new VectorSource({
                features: [outerCircleFeature, innerCircleFeature]
            })
            const vectorLayer = new VectorLayer({
                source: vectorSource,
                zIndex: 100,
                title,
            });
            const startAngle = angle - scanAngle / 2
            const endAngle = angle + scanAngle / 2
            const polygonPoints = this.createScanPoints(center, radian, startAngle, endAngle)

            const polygon = new Polygon([polygonPoints])

            const polygonFeature = new Feature({
                geometry: polygon,
            })
            polygonFeature.setStyle(new Style({
                stroke: new Stroke({
                    color: 'transparent',
                    width: 3
                }),
                fill: new Fill({
                    color: 'rgba(0, 210, 255, 0.40)'
                }),
                zIndex: 101,
                title: 'polygon'
            }))

            this.polygonFeature = polygonFeature

            vectorSource.addFeature(polygonFeature)
            this.map.addLayer(vectorLayer)
            this.animateRadar({ radian, center })

            return {
            }

        },
        /**
         * 
         * @param center 中心点
         * @param radius 半径 (弧度)
         * @param startAngle 开始角度
         * @param endAngle 结束角度
         */
        createScanPoints(center, radius, startAngle, endAngle) {
            const points = []
            const step = 0.001 // 角度步长
            for (let a = startAngle; a <= endAngle; a += step) {
                const x = center[0] + radius * Math.cos(a)
                const y = center[1] + radius * Math.sin(a)
                points.push([x, y])
            }
            points.push(center) // 闭合多边形
            return points
        },
        /**
         * 
         * @param opt 
         * radian 弧度
         * center 中心点
         */
        animateRadar(opt) {
            const { radian, center } = opt
            angle -= 0.05 // 每次增加的角度,角度越小速度越慢
            const startAngle = angle - scanAngle / 2
            const endAngle = angle + scanAngle / 2
            const polygonPoints = this.createScanPoints(center, radian, startAngle, endAngle)
            this.polygonFeature.setGeometry(new Polygon([polygonPoints]))
            this.animateId =  requestAnimationFrame(() => this.animateRadar(opt))
        }
    },
    mounted() {

       this.createRadar({
            center: this.center, // 中心点
            radius: 10 * 1000,  // 半径 10 * 1000 米
            title: 'circle_radar',
            outerCircleConfig: {
                zIndex: 100,
                title: 'outerCircle'
            },
            innerCircleConfig: {
                zIndex: 100,
                title: 'innerCircle'
            }
        })
        // setTimeout(() => {
        //     console.log('animateId', this.animateId)
        //     this.animateId && cancelAnimationFrame(this.animateId)
        // }, 1000 * 5)
    },

}
</script>