cesium广告牌渲染,快速学会在地图上使用图片添加标记

话不多说,我直接上方法,再逐一讲

这个方法需要的参数有 :
  • data / 数据
  • idName / 实体参数id需要数据中对应的唯一值字段,默认为'id',可能是创建时间戳等
  • imgName / 图片名称,用于在固定位置查找需要的图片
  • entityName / 用于dataSources的创建和entity的建组,在触发全局事件时通过entityName查询事件处理函数
  • coordinateSystemChange / 用于判断是否需要坐标系转换,以及使用什么方法进行坐标系转换
这个方法需要在内部调用的函数有 :
  • addAction / 添加事件监听器,用于触发事件
  • drawCanvas / 根据传入的参数生成所需的Canvas元素并获取它的url
  • transformCoordinate / 根据传入的经纬度和需要转换的坐标系进行转换
arduino 复制代码
全局创建dataSources变量,储存dataSources对象
let dataSources
export const drawMark = async (data, idName = 'id', imgName, entityName, coordinateSystemChange) => {
  // 判断渲染是否可以开始
  if (!data.length || !imgName) return console.log('无法开始渲染,因为无图片或数据')
  // 移除之前添加到Viewer上的的dataSources
  if (dataSources) {
    viewer.dataSources.remove(dataSources, true)
  }
  // 新建dataSources实例
  dataSources = new Cesium.CustomDataSource(entityName)
  // 将新的dataSources实例添加进Viewer中
  viewer.dataSources.add(dataSources)
  // 添加事件监听器, 方法写在该函数外
  addAction()
  // drawCanvas函数参数, 方法写在该函数外
  const canvasOpt = {
    width: 48,
    height: 48,
    url: `./assets/image/shys/${imgName}.png`,
    offset: [0, 0] //图标偏移
  }
  // 根据传入的参数生成所需的Canvas元素并获取它的url
  const image = await drawCanvas(canvasOpt)
  
  // 开始渲染
  data.forEach((item, i) => {
    // 获取本次循环元素(以下称之为该元素)的经纬度,存在经度时,判断是否需要坐标系转换
    let latitude, longitude
    if (!item.longitude) return console.log('未找到坐标', item)
    if (coordinateSystemChange) {
      const arr = transformCoordinate(item.longitude, item.latitude, coordinateSystemChange)
      longitude = arr[0]
      latitude = arr[1]
    } else {
      longitude = item.longitude
      latitude = item.latitude
    }
    
    // 
    const scaleByDistance = new Cesium.NearFarScalar(1000, 1, 10000, 0.1)
    // 图标在地图上最大显示高度
    const distanceDisplayCondition = new Cesium.DistanceDisplayCondition(0, 60000)
    // 获取图标显示位置
    const position = Cesium.Cartesian3.fromDegrees(longitude, latitude, 50)
    // 根据css颜色获取颜色,此处没用到
    // const color = Cesium.Color.fromCssColorString('#fff')
    
    // 根据以上参数创建entity
    const entity = new Cesium.Entity({
      position,
      billboard: {
        image,
        show: true,
        pixelOffset: new Cesium.Cartesian2(0, 0),
        eyeOffset: new Cesium.Cartesian3(0.0, 0.0, 0.0), // default
        horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // default
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // default: CENTER
        width: canvasOpt.width, // default: undefined
        height: canvasOpt.height, // default: undefined
        scaleByDistance,
        distanceDisplayCondition // 可见范围
      },
      properties: item,
      id: item[idName],
      name: entityName,
      startPosition: position
    })
    将entity添加到dataSources中
    dataSources.entities.add(entity)
  })
}
内部调用函数

addAction

javascript 复制代码
let action = {}
function addAction() {
  // 如果存在环境的监听器,将其销毁
  if (action.handler) {
    action.handler.destroy()
  }
  // 创建一个在cesium环境上的监听器
  const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
  action.handler = handler
  
  // 给监听器添加回调
  action.handler.setInputAction((obj) => {
    // obj内存储有点击位置,通过位置和cesium自带的pick方法获取点击到的entity中的properties属性
    // 看过上面方法应该知道properties属性存储了entity所在该元素的数据
    const item = viewer.scene.pick(obj.position)?.id?.properties
    if (!item) return
    // 判断是进入还是取消进入详情,用于执行不同操作
    const obj1 = IsSameObj(item, store.state.selectedSocialElementItem) ? undefined : item
    store.commit('setSelectedSocialElementItem', obj1)
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}

drawCanvas

这个方法需要在内部调用的函数有 loadImg

ini 复制代码
async function drawCanvas(props = {}) {
  let { url, width, height, offset = [0, 0], labels = [] } = props
  let img = await loadImg(url)
  let imgW = width || img.width
  let imgH = height || img.height
  // 根据参数,创建Canvas HTML元素
  let canvas = document.createElement('canvas')
  // 注意,为了聚合,这里指示线使用canvas伪装,因而需要在实际创建时增加宽高划线
  canvas.setAttribute('width', imgW + 25)
  canvas.setAttribute('height', imgH + 25)
  
  // 绘制参数中的图片
  let ctx = canvas.getContext('2d')
  ctx.clearRect(0, 0, imgW, imgH)
  ctx.drawImage(img, 0, 0, imgW, imgH)
  
  // 绘制伪装的指示线
  const ctx1 = canvas.getContext('2d')
  ctx1.fillStyle = '#5791ff'
  ctx1.fillRect(imgW / 2, imgH, 2, 40)
  let top = 0
  
  // 绘制文字信息
  labels.map((item) => {
    ctx.font = item.font || '12px Microsoft YaHei'
    ctx.textAlign = item.textAlign || 'center'
    ctx.fillStyle = item?.color || '#fff'
    let arr = item.font.split(' ')
    let size = ''
    arr.map((it) => {
      if (it.indexOf('px ') > -1) {
        size = parseInt(it)
      }
    })
    size = size || 14
    let lineHeight = item.lineHeight ? parseInt(item.lineHeight) : 16
    let itemTop = parseInt(item.top || 0) + lineHeight / 2 + size / 2 + top
    let text = item.text || ''
    let leftW = ctx.textAlign === 'left' ? 0 : ctx.textAlign === 'right' ? canvas.width : canvas.width / 2
    let textIndent = item.textIndent ? parseInt(item.textIndent) : 0
    leftW = leftW + textIndent
    ctx.fillText(text, leftW, itemTop)
    top = itemTop - size / 2 + lineHeight / 2
  })
  // 将Canvas HTML元素转换为url,方便使用
  return canvas.toDataURL()
}

loadImg

javascript 复制代码
function loadImg(src) {
  // 1.创建一个p存储一个Promise类的实例
  const p = new Promise(
    // 2.传入一个函数,内部有两个形参分别为resolve和reject,这两个形参同样也是函数
    (resolve, reject) => {
      //3. 先定义一张图片,创建一个element
      const img = document.createElement('img')
      //4. 加载完之后传入一个回调函数
      img.onload = () => {
        //5.通过resolve方法将要加载的对象传进来
        resolve(img)
      }
      //5.当遇到问题,就执行reject这个函数
      img.onerror = () => {
        //6. 失败的话就传入一个模板字符串,把错误信息传递出去
        const err = new Error(`图片加载失败${src}`)
        reject(err)
      }
      img.src = src
    }
  )
  return p
}

transformCoordinate

typescript 复制代码
// 没啥好说的,可以用策略模式优化一下,反正我是直接c的
import { bd09togcj02, bd09towgs84, gcj02tobd09, gcj02towgs84, wgs84tobd09, wgs84togcj02 } from './transform'
export const transformCoordinate = (lng, lat, type = 'gc2wgs') => {
  if (type === 'gc2wgs') {
    return gcj02towgs84(lng, lat)
  } else if (type === 'wgs2gc') {
    return wgs84togcj02(lng, lat)
  } else if (type === 'bd2gc') {
    return bd09togcj02(lng, lat)
  } else if (type === 'gc2bd') {
    return gcj02tobd09(lng, lat)
  } else if (type === 'bd2wgs') {
    return bd09towgs84(lng, lat)
  }
  return [lng, lat]
}

transform文件的内容, c的大佬的坐标系转换,看不懂,用就完事了

scss 复制代码
// 定义一些常量
const x_PI = 3.14159265358979324 * 3000.0 / 180.0
const PI = 3.1415926535897932384626
const a = 6378245.0
const ee = 0.00669342162296594323
 
/**
 * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 / 即百度转谷歌、高德
 * @param { Number } bd_lon
 * @param { Number } bd_lat
 */
export function bd09togcj02 (bd_lon, bd_lat) {
  var x_pi = 3.14159265358979324 * 3000.0 / 180.0
  var x = bd_lon - 0.0065
  var y = bd_lat - 0.006
  var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi)
  var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi)
  var gg_lng = z * Math.cos(theta)
  var gg_lat = z * Math.sin(theta)
  return [gg_lng, gg_lat]
}
 
/**
 * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 / 即谷歌、高德 转 百度
 * @param { Number } lng
 * @param { Number } lat
 */
export function gcj02tobd09 (lng, lat) {
  var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI)
  var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI)
  var bd_lng = z * Math.cos(theta) + 0.0065
  var bd_lat = z * Math.sin(theta) + 0.006
  return [bd_lng, bd_lat]
}
 
/**
 * WGS84坐标系转火星坐标系GCj02 / 即WGS84 转谷歌、高德
 * @param { Number } lng 
 * @param { Number } lat 
 */
export function wgs84togcj02 (lng, lat) {
  if (outOfChina(lng, lat)) {
    return [lng, lat]
  }
  else {
    var dlat = transformlat(lng - 105.0, lat - 35.0)
    var dlng = transformlng(lng - 105.0, lat - 35.0)
    var radlat = lat / 180.0 * PI
    var magic = Math.sin(radlat)
    magic = 1 - ee * magic * magic
    var sqrtmagic = Math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI)
    dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI)
    const mglat = lat + dlat
    const mglng = lng + dlng
    return [mglng, mglat]
  }
}
 
/**
 * GCJ02(火星坐标系) 转换为 WGS84 / 即谷歌高德转WGS84
 * @param { Number } lng 
 * @param { Number } lat 
 */
export function gcj02towgs84 (lng, lat) {
  if (outOfChina(lng, lat)) {
    return [lng, lat]
  }
  else {
    var dlat = transformlat(lng - 105.0, lat - 35.0)
    var dlng = transformlng(lng - 105.0, lat - 35.0)
    var radlat = lat / 180.0 * PI
    var magic = Math.sin(radlat)
    magic = 1 - ee * magic * magic
    var sqrtmagic = Math.sqrt(magic)
    dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI)
    dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * PI)
    const mglat = lat + dlat
    const mglng = lng + dlng
    return [lng * 2 - mglng, lat * 2 - mglat]
  }
}
 
/**
 * 百度坐标系转wgs84坐标系
 * @param {*} lng 
 * @param {*} lat 
 */
export function bd09towgs84 (lng, lat) {
  // 百度坐标系先转为火星坐标系
  const gcj02 = bd09togcj02(lng, lat)
  // 火星坐标系转wgs84坐标系
  const result = gcj02towgs84(gcj02[0], gcj02[1])
  return result
}
 
/**
 * wgs84坐标系转百度坐标系
 * @param {*} lng 
 * @param {*} lat 
 */
export function wgs84tobd09 (lng, lat) {
  // wgs84先转为火星坐标系
  const gcj02 = wgs84togcj02(lng, lat)
  // 火星坐标系转百度坐标系
  const result = gcj02tobd09(gcj02[0], gcj02[1])
  return result
}
 
/**
 * 经度转换
 * @param { Number } lng 
 * @param { Number } lat 
 */
function transformlat (lng, lat) {
  var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng))
  ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0
  ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0
  ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0
  return ret
}
 
/**
 * 纬度转换
 * @param { Number } lng 
 * @param { Number } lat 
 */
function transformlng (lng, lat) {
  var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng))
  ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0
  ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0
  ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0
  return ret
}
 
/**
 * 判断是否在国内,不在国内则不做偏移
 * @param {*} lng 
 * @param {*} lat 
 */
function outOfChina (lng, lat) {
  return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false)
}
在列一下其他常用的工具

聚合效果

最基础的聚合效果

如果你不满意,需要根据不同的聚合数量显示不同的聚合效果,我暂时写不出来,大家自行野蛮生长

ini 复制代码
// 开启聚合
let clusteringListner
export function startClustering(options) {
  dataSources.clustering.enabled = true
  clusteringListner = dataSources.clustering.clusterEvent.addEventListener(function (clusteredEntities, cluster) {
    defaultCallback(cluster)
  })
  console.log('clusteringListner', clusteringListner)
  console.log(dataSources.clustering.clusterEvent._listeners[0])

  function defaultCallback(cluster) {
    cluster.label.show = true
    cluster.billboard.show = true

    cluster.label.font = '24px sans-serif'
    cluster.label.pixelOffset = new Cesium.Cartesian2(-12, -35)
    cluster.label.eyeOffset = new Cesium.Cartesian3(0, 0, 0)
    cluster.label.showBackground = true
    cluster.label.backgroundColor = new Cesium.Color.fromCssColorString('rgba(19,68,119, 0.8)')
    cluster.billboard.show = true
    cluster.billboard.width = cluster.label.text.length * 15 + 20
    cluster.billboard.height = 120
    // cluster.polyline.show = false
    cluster.type = 'cameraMore'
  }
}

// 移除聚合效果
export function removeClustering() {
  if (clusteringListner) {
    clusteringListner()
    clusteringListner = undefined
    dataSources.clustering.enabled = false
  }
}
相关推荐
不浪brown11 小时前
开源!矢量建筑白模泛光特效以及全国77个大中城市的矢量shp数据获取!
前端·cesium
在下胡三汉2 天前
怎么解决cesium加载模型太黑,程序崩溃,不显示,位置不对模型太大,Cesium加载gltf/glb模型后变暗
3dmax·cesium
GIS之家6 天前
vue+cesium示例:地形开挖(附源码下载)
前端·javascript·vue.js·gis·cesium·webgis
duansamve6 天前
Cesium快速入门到精通系列教程二:添加地形与添加自定义地形、相机控制
cesium
duansamve7 天前
Cesium快速入门到精通系列教程三:添加物体与3D建筑物
cesium
GIS之家13 天前
vue+cesium示例:3Dtiles三维模型高度调整(附源码下载)
前端·vue.js·3d·cesium·webgis
小野猫子22 天前
在vue3中使用Cesium的保姆教程
cesium
AllBlue23 天前
常见三维引擎坐标轴 webgl threejs cesium blender unity ue 左手坐标系、右手坐标系、坐标轴方向
blender·webgl·cesium
前端熊猫1 个月前
Cesium 3D Tiles
3d·cesium·tiles
不浪brown1 个月前
WebGISer的福利!基于Cesium+Vue3的智慧数字农田项目,开源!
前端·cesium