话不多说,我直接上方法,再逐一讲
这个方法需要的参数有 :
- 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
}
}