octopus-map
Version:
基于openlayer V6.6.1 为基础,进行二次开发的地图工具包,通过二次封装,简化 openlayer 的开发难度
459 lines (380 loc) • 13.3 kB
JavaScript
/**
*
* @description 创建轨迹动画
*
* @author OctopusRoe
*
* @version 0.0.1
*
*/
import Feature from 'ol/Feature'
import { boundingExtent, getCenter } from 'ol/extent'
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import { Point, LineString } from 'ol/geom'
import { getVectorContext } from 'ol/render'
import { Style, Icon, Stroke, Circle, Fill } from 'ol/style'
import { unByKey } from 'ol/Observable'
import { toSize } from 'ol/size'
const PI = 3.1415926535897932384626
const halfPI = PI / 2
/**
* @description 路径线的样式
*
* @typedef {Object} style
* @param {Number} [options.iconSize] icon的大小
* @property {Number} [strokeWidth] 路径的宽度,默认为5
* @property {String} [strokeColor] 路径的颜色,与 css 的 color 字段一样,默认为 #000
* @property {String} [markColor] 节点的颜色
* @property {Number[]} [lineDash] 路径的线段样式
* @property {String} [lineDashColor] 路径的线段颜色
*/
export default class Trajectory {
/**
*
* @param {Object} options
* @param {import('ol/Map').default} map Map 实例
* @param {String} options.iconUrl 动画图标的 URL
* @param {String} [options.name] Layer 实例的名字
* @param {style} [options.style] 路径的线段样式
* @param {Number} [options.speed] 动画速度
* @param {Boolean} [options.repeat] 是否重复
* @param {Number} [options.pointAmount] 路径上点的密度
*/
constructor (options) {
/** @description 动画默认为关闭 */
this._animating = false
/** @description 动画开始时间 */
this._startTime = null
/** @description 动画速度源 */
this._speed = options.speed || 0.1
/** @description postrender 监听事件的 key */
this._listenKey = null
/** @description 用于保存动画的行走分数 */
this._distance = null
/** @description */
this._index = 0
/** @description 保存创建实例时, 传入的全部参数 */
this._options = options
/** @description 用于储存 Feature 实例的仓库 */
this._feature = []
/** @description 用于储存路径的仓库 */
this._route = []
/** @description 用于储存已经经过的路径仓库 */
this._newRoute = []
/** @description 创建源 */
this._source = new VectorSource({ wrapX: false })
/** @description 创建图层 */
this._layer = new VectorLayer({
source: this._source,
style: (feature) => this._returnStyle(feature)
})
/** @description 设置 Layer 层名字 */
this._layer.set('name', options.name || 'trajectory')
/** @description 创建样式 */
this._style = this._createStyle(options)
/** @description 用于保存 geoMarker Feature 的几何形状 */
this._geoMarkerGeometry = null
}
/** @description 获取轨迹的路径 LineString 对象 */
get route () {
return this._route.concat()
}
/** @description 获取动画的 layer 对象 */
get layer () {
return this._layer
}
/**
* @description 创建样式
*
* @param {Object} options 实例化类时的参数
*/
_createStyle (options) {
// 解构 lineSyle
const { style } = options
// 创建路径样式
const route = new Style({
stroke: new Stroke({
width: style && style.strokeWidth || 5,
color: style && style.strokeColor || 'rgba(255, 255, 255, 1)'
})
})
const mark = new Style({
image: new Circle({
radius: style && style.strokeWidth - 1 || 4,
fill: new Fill({
color: style && style.markColor || 'rgba(0, 0, 0, 1)'
}),
stroke: new Stroke({
width: 1,
color: '#fff'
})
})
})
// 创建 icon 样式
const geoMarker = new Style({
image: new Icon({
src: options.iconUrl,
scale: toSize(style && style.iconSize ? style.iconSize : 1)
})
})
// 创建已经过路径
const lineDash = new Style({
stroke: new Stroke({
width: style && style.strokeWidth + 1 || 6,
color: style && style.lineDashColor || 'rgba(0, 0, 0 ,1)',
lineDash: style && style.lineDash || [10, 10]
})
})
return { route, mark, geoMarker, lineDash, startMarker: null, endMarker: null }
}
/**
* @description 返回样式
*
* @param {import('ol/Feature').default} feature
* @return {import('ol/style/Style').default} 返回 Style 实例
*/
_returnStyle (feature) {
if (this._animating && feature.get('type') === 'geoMarker') return null
return this._style[feature.get('type')]
}
/**
* @description 根据传入的参数,返回 Feature 实例
*
* @param {String} options type 的类型
* @return {import('ol/Feature').default} 返回的 Feature 实例
*/
_searchType (options) {
let feature
this._feature.forEach(item => {
if (item.get('type') === options) {
feature = item
}
})
return feature
}
/**
* @description 创建路径的
*
* @param {String} options Feature 实例的名字
* @return {Array<import('ol/Feature').default>} 返回由 Feature 实例组成的一个对象组
*/
_createRoute (options) {
// 创建 路径的 Feature 实例
const route = new Feature({
type: 'route',
name: options || 'trajectory',
geometry: this._route
})
// 创建节点的 Feature 实例
const mark = this._route.getCoordinates().map(item => (
new Feature({
type: 'mark',
name: options || 'trajectory',
geometry: new Point(item)
})
))
// 筛选点位的算法,建议更换个更好的
const midMark = screen(mark, this._options.pointAmount)
// 创建标记的 Feature 实例
const geoMarker = new Feature({
type: 'geoMarker',
name: options || 'trajectory',
geometry: new Point(this._route.getCoordinateAt(0))
})
// 创建开始位置的 Feature 实例
const startMarker = new Feature({
type: 'startMarker',
name: options || 'trajectory',
geometry: new Point(this._route.getCoordinateAt(0))
})
// 创建结束位置的 Feature 实例
const endMarker = new Feature({
type: 'endMarker',
name: options || 'trajectory',
geometry: new Point(this._route.getCoordinateAt(1))
})
this._source.addFeatures([route, geoMarker, startMarker, endMarker, ...midMark])
return [route, geoMarker, startMarker, endMarker, ...midMark]
}
/**
* @description 动画的核心方法, 图标移动的方法
*
* @param {Event} event Event 事件
*/
_animation (event) {
// 保留一个用于在事件画布上绘图的矢量上下文
const vectorContext = getVectorContext(event)
// 获取渲染帧状态
const frameState = event.frameState
if (this._animating) {
// 使用帧状态时间减去当前时间
const elapsedTime = frameState.time - this._startTime
// 基础参数
const baseNumber = 1000
// 得到一个小数
const distance = this._distance = (this._speed * elapsedTime) / baseNumber
if (distance >= 1) {
// 停止动画
this._stopAnimation(true, this._options.repeat)
return
}
// 获取现在的坐标
const newPoint = this._route.getCoordinateAt(distance)
// 获取路径坐标
const route = this._route.getCoordinates()
// 循环判断经过的 index 值
for (let i = 0; i < route.length; i++) {
if (i + 1 === route.length) {
continue
}
const array = [route[i], route[i + 1]]
const routeLineString = new LineString(array)
if (routeLineString.intersectsCoordinate(newPoint)) {
this._index = i
}
}
let index
// 判断 实例中的 _index 是否等于路径数组的 length
if (this._index === route.length) {
index = this._index
} else {
index = this._index + 1
}
// 角度计算
const rotation = -Math.atan2(newPoint[1] - route[index][1], newPoint[0] - route[index][0]) - halfPI
// 设置样式角度
this._style['geoMarker'].getImage().setRotation(rotation)
// 将从 LineString 中提供的分数处理成为一个点坐标
const currentPoint = new Point(newPoint)
// 把行动路径坐标加入仓库
this._newRoute.push(newPoint)
// 创建一个新的 LineString 对象
const lineString = new LineString(this._newRoute)
// 创建一个 Feature 实例
const feature = new Feature(currentPoint)
// 创建一个 Feature 实例
const lineStringFeature = new Feature(lineString)
// 把 Feature 实例渲染进 canvas
vectorContext.drawFeature(lineStringFeature, this._style['lineDash'])
// 把 Feature 实例渲染进 canvas
vectorContext.drawFeature(feature, this._style['geoMarker'])
}
// 渲染地图
this._options.map.render()
}
/**
* @description 私有关闭方法
*
* @param {Boolean} options 是否直接到达终点
* @param {Boolean} repeat 是否重复
*/
_stopAnimation (options, repeat) {
// 获取名称为 geoMarker 的 Feature
const geoMarker = this._searchType('geoMarker')
// 重新设置 geoMarker 的几何形状
geoMarker.setGeometry(this._geoMarkerGeometry)
this._animating = false
// 判断是否直接跳到终点
const distance = options ? 1 : this._distance
// 从 LineString 获取一个坐标点
const coord = this._route.getCoordinateAt(distance)
// 获取geoMarker特征几何,并设置一个新的坐标GIS数组
geoMarker.getGeometry().setCoordinates(coord)
// 重置已经过的路径仓库
this._newRoute = []
// 停止监听 'postrender' 事件
unByKey(this._listenKey)
if (repeat) {
this.start()
}
}
/**
* @description 创建轨迹动画
*
* @param {Object} options 创建轨迹动画的参数
* @param {String} options.name Feature 实例的名字
* @param {Number[][]} options.route 轨迹路径的坐标
*/
create (options) {
const { route } = options
// 创建 LineString 实例
this._route = new LineString(route)
// 计算初始化时的角度
const rotation = -Math.atan2(route[0][1] - route[1][1], route[0][0] - route[1][0]) - halfPI
// 设置 Icon 角度
this._style['geoMarker'].getImage().setRotation(rotation)
// 创建轨迹 Feature, 并放入仓库中
this._feature = this._createRoute(options.name)
return { layer: this._layer, feature: this._feature }
}
/** @description 开始动画的方法 */
start () {
// 设置一个setTimeout 等待 Icon 加载 image
setTimeout(() => {
if (this._animating) {
this._stopAnimation(true)
} else {
// 设置 animation
this._animating = true
// 设置 _startTime
this._startTime = new Date().getTime()
// 监听 'postrender' 事件
this._listenKey = this._layer.on('postrender', this._animation.bind(this))
// 渲染地图
this._options.map.render()
// 查询名称为 geoMarker 的 Feature
const geoMarker = this._searchType('geoMarker')
// 保存 geoMarker Feature 的几何形状
this._geoMarkerGeometry = geoMarker.getGeometry()
// 设置名称为 geoMarker 的 Feature 几何形状为 null
geoMarker.setGeometry(null)
}
})
}
/** @description 停止动画的方法 */
stop () {
this._stopAnimation(false, false)
}
/**
* @description 获取范围的中心点
*
* @param {Array<Number[]>} route 实例返回的 route
* @return {Number[]} 返回一个经纬度数组
*/
getCenterCoordinates (route) {
const extent = boundingExtent(route[0])
const center = getCenter(extent)
return center
}
}
/**
* @description 筛选算法
* @param {Array} arr 需要帅选的数组
* @param {Number} options 筛选的参数
*/
function screen (arr, options) {
const backList = []
// 删除第一个点,并且返回
const startMark = arr.shift()
// 删除最后一项,并且返回
const endMark = arr.pop()
// 获取筛选参数
let minIndex = Math.ceil(Math.ceil(arr.length / options) / options)
if (minIndex <= 0) {
minIndex = 2
}
// 保存的 Index 索引
let saveIndex = 0
for (let index = 0; index < arr.length; index++) {
if (index === saveIndex + minIndex) {
backList.push(arr[index])
saveIndex = index
}
}
// 向路径数组头部塞入开始点
backList.unshift(startMark)
// 想路径数组最后塞入结束点
backList.push(endMark)
return backList
}