723 lines
16 KiB
TypeScript
723 lines
16 KiB
TypeScript
import { charge_toolbar_list, getIconByOrderStatus } from '@/pages/home/config'
|
||
import { getImageFullPath } from '@/utils'
|
||
|
||
// 高德地图key
|
||
export const GAODE_MAP_KEY = import.meta.env.VITE_AMAP_KEY
|
||
|
||
// 站点类型配置
|
||
export const siteTypeConfig = {
|
||
2: {
|
||
strokeColor: '#FF6B6B', // 边框红
|
||
fillColor: '#FF999999', // 填充浅红
|
||
label: '禁停区',
|
||
level: 'abovelabels',
|
||
zIndex: 2,
|
||
},
|
||
1: {
|
||
strokeColor: '#4ECDC4', // 边框青
|
||
fillColor: '#99E6E699', // 填充浅青
|
||
label: '普通站点',
|
||
level: 'abovelabels',
|
||
zIndex: 1,
|
||
},
|
||
3: {
|
||
strokeColor: '#45B7D1', // 边框蓝
|
||
fillColor: '#99D1F099', // 填充浅蓝
|
||
label: '仓库区',
|
||
level: 'abovelabels',
|
||
zIndex: 3,
|
||
},
|
||
}
|
||
|
||
// 获取地图实例
|
||
export function getMapContext(mapid: string, instance: any) {
|
||
return uni.createMapContext(mapid, {
|
||
this: instance.proxy,
|
||
})
|
||
}
|
||
|
||
// 获取当前定位
|
||
export function getLoalcation({
|
||
mapContext,
|
||
success = null,
|
||
fail = null,
|
||
moveCenter = false,
|
||
}) {
|
||
uni.getLocation({
|
||
type: 'gcj02',
|
||
geocode: true,
|
||
success(res) {
|
||
// console.log('定位成功', res)
|
||
const { latitude, longitude } = res
|
||
if (moveCenter && mapContext) {
|
||
moveToLocation(mapContext, longitude, latitude)
|
||
}
|
||
success && success({ latitude, longitude })
|
||
},
|
||
fail(err) {
|
||
console.error('uni.getLocation-------', err)
|
||
fail && fail(err)
|
||
},
|
||
})
|
||
}
|
||
|
||
// 移至中心点
|
||
export function moveToLocation(mapContext: any, longitude: number, latitude: number) {
|
||
const point = {
|
||
latitude,
|
||
longitude,
|
||
}
|
||
mapContext.moveToLocation(point)
|
||
}
|
||
|
||
/**
|
||
* 添加线
|
||
* @param sColor 线颜色
|
||
* @param arrPoint 线坐标
|
||
* @param width 线宽度
|
||
* @param arrowLine 是否有箭头
|
||
*/
|
||
export function addLine({
|
||
sColor,
|
||
arrPoint,
|
||
width = 4,
|
||
arrowLine = false,
|
||
}: {
|
||
sColor: string
|
||
arrPoint: any[]
|
||
width?: number
|
||
arrowLine?: boolean
|
||
}) {
|
||
return {
|
||
points: arrPoint,
|
||
color: sColor,
|
||
width,
|
||
arrowLine,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取路径规划
|
||
* @param mapsdk 高德地图sdk实例
|
||
* @param slng 起点经度
|
||
* @param slat 起点纬度
|
||
* @param elng 终点经度
|
||
* @param elat 终点纬度
|
||
* @param success 成功回调
|
||
* @param fail 失败回调
|
||
*/
|
||
export function getRoutePlan({
|
||
mapsdk,
|
||
slng,
|
||
slat,
|
||
elng,
|
||
elat,
|
||
success,
|
||
fail,
|
||
}: {
|
||
mapsdk: any
|
||
slng: number
|
||
slat: number
|
||
elng: number
|
||
elat: number
|
||
success: (res: any) => void
|
||
fail: (err: any) => void
|
||
}) {
|
||
// 检测 mapsdk 实例是否存在
|
||
if (!mapsdk) {
|
||
fail && fail('mapsdk实例不存在')
|
||
console.error('mapsdk实例不存在')
|
||
return
|
||
}
|
||
// 判断经纬度是否齐全
|
||
if (!slng || !slat || !elng || !elat) {
|
||
fail && fail('经纬度参数不完整')
|
||
console.error('经纬度参数不完整')
|
||
return
|
||
}
|
||
|
||
const options = {
|
||
origin: `${slng},${slat}`,
|
||
destination: `${elng},${elat}`,
|
||
success: (res: any) => {
|
||
const points = []
|
||
let distance = 0
|
||
if (res?.paths[0]?.steps) {
|
||
const steps = res.paths[0].steps
|
||
distance = res.paths[0].distance
|
||
|
||
for (let i = 0; i < steps.length; i++) {
|
||
const poLen = steps[i].polyline.split(';')
|
||
for (let j = 0; j < poLen.length; j++) {
|
||
points.push({
|
||
longitude: Number.parseFloat(poLen[j].split(',')[0]),
|
||
latitude: Number.parseFloat(poLen[j].split(',')[1]),
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
const cnt = (distance / 1000).toFixed(2)
|
||
let sdistance = `${distance}m`
|
||
|
||
if (Number.parseFloat(cnt) >= 1) {
|
||
sdistance = `${cnt} km`
|
||
}
|
||
const data = {
|
||
arrPoint: points,
|
||
distance: sdistance,
|
||
}
|
||
if (success)
|
||
success(data)
|
||
},
|
||
fail: (error: any) => {
|
||
fail && fail(error)
|
||
},
|
||
}
|
||
|
||
mapsdk.getDrivingRoute(options)
|
||
}
|
||
|
||
/**
|
||
* 逆解析地址
|
||
* @param {object} options
|
||
* @param {object} options.mapsdk - 地图对象
|
||
* @param {number} options.longitude - 经度
|
||
* @param {number} options.latitude - 纬度
|
||
* @param {Function} options.success - 成功回调
|
||
* @param {Function} options.fail - 失败回调
|
||
*/
|
||
|
||
export function reverseGeocoder({
|
||
mapsdk,
|
||
longitude,
|
||
latitude,
|
||
success,
|
||
fail,
|
||
}: {
|
||
mapsdk: any
|
||
longitude: number
|
||
latitude: number
|
||
success: (data: any) => void
|
||
fail: (error: any) => void
|
||
}) {
|
||
if (!mapsdk) {
|
||
console.error('请传入地图sdk')
|
||
fail && fail('请传入地图sdk')
|
||
return
|
||
}
|
||
|
||
if (!longitude || !latitude) {
|
||
console.error('请传入经纬度')
|
||
fail && fail('请传入经纬度')
|
||
return
|
||
}
|
||
mapsdk.getRegeo({
|
||
location: `${longitude},${latitude}`,
|
||
success(res: any) {
|
||
console.log(res)
|
||
const { desc, regeocodeData, latitude, longitude } = res[0]
|
||
success && success({
|
||
desc,
|
||
regeocodeData,
|
||
latitude,
|
||
longitude,
|
||
})
|
||
},
|
||
fail(err: any) {
|
||
console.log(err, '地理位置信息失败')
|
||
fail && fail(err)
|
||
},
|
||
})
|
||
}
|
||
|
||
/**
|
||
*绘制区域
|
||
* @param {object} options
|
||
* @param {Array} options.point - 区域点
|
||
* @param {boolean} options.isFormat - 是否格式化(处理经纬度)
|
||
* @param {Function} options.success - 成功回调函数
|
||
* @param {Function} options.fail - 失败回调函数
|
||
* @param {any} options.option - 配置
|
||
*/
|
||
export function drawRegion({
|
||
point,
|
||
isFormat = false,
|
||
zIndex = 0,
|
||
type = '',
|
||
success,
|
||
fail,
|
||
option = {
|
||
strokeWidth: 2,
|
||
strokeColor: '#4595f8',
|
||
fillColor: '#b7dff299',
|
||
level: 'abovelabels', // abovelabels 显示在所有 POI 之上 abovebuildings 显示在楼块之上 POI 之下
|
||
},
|
||
}: {
|
||
point: any
|
||
isFormat?: boolean
|
||
option?: any
|
||
type?: string
|
||
zIndex?: number
|
||
success: (data?: any) => void
|
||
fail: (err?: any) => void
|
||
}) {
|
||
let obj = {
|
||
point: [],
|
||
}
|
||
|
||
if (!point) {
|
||
fail && fail('point参数不能为空')
|
||
return
|
||
}
|
||
|
||
// 格式化数据(判断数据是不是二维数组)
|
||
if (isFormat) {
|
||
if (Array.isArray(point) && point.length > 0 && point.every(item => Array.isArray(item))) {
|
||
const points = point.map((item: any) => ({
|
||
latitude: item[1],
|
||
longitude: item[0],
|
||
}))
|
||
obj = {
|
||
points,
|
||
zIndex,
|
||
type,
|
||
...option,
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
obj = {
|
||
points: point,
|
||
zIndex,
|
||
type,
|
||
...option,
|
||
}
|
||
}
|
||
success && success(obj)
|
||
}
|
||
|
||
/**
|
||
*绘制点位
|
||
* @param {object} param
|
||
* @param {Array} param.point
|
||
* @param {object} param.option
|
||
* @param {boolean} param.isSoc
|
||
* @param {Function} param.success
|
||
* @param {Function} param.fail
|
||
*
|
||
*/
|
||
// iconPath: '/static/images/carImage.png',
|
||
// iconPath: '/static/images/markerH.png',
|
||
export function drawPoint({
|
||
point,
|
||
type = 'none',
|
||
option = {
|
||
width: 35,
|
||
height: 35,
|
||
iconPath: '/static/images/carIcon.png',
|
||
},
|
||
success,
|
||
fail,
|
||
}: {
|
||
type?: 'none' | 'charge' | 'dispatch' | 'polling' | 'maintain'
|
||
// 工具的类型
|
||
point: {
|
||
id?: number
|
||
bikeCode?: string
|
||
latitude: number
|
||
longitude: number
|
||
soc?: number
|
||
online?: boolean
|
||
bikeInfo?: any
|
||
}
|
||
option?: any
|
||
success: (res: any) => void
|
||
fail: (res: any) => void
|
||
}) {
|
||
// console.log(point, '------------>point')
|
||
// console.log(type, '------------>type')
|
||
|
||
let obj = {}
|
||
|
||
if (!point) {
|
||
fail && fail('point参数不能为空')
|
||
return
|
||
}
|
||
|
||
switch (type) {
|
||
case 'charge': {
|
||
obj = setChargePoint(point, option)
|
||
break
|
||
}
|
||
case 'dispatch': {
|
||
obj = setDispatchPoint(point, option)
|
||
break
|
||
}
|
||
case 'maintain': {
|
||
obj = repairPoint(point, option)
|
||
break
|
||
}
|
||
case 'polling': {
|
||
obj = inspectionPoint(point, option)
|
||
break
|
||
}
|
||
case 'none': {
|
||
obj = allCarPoint(point, option)
|
||
break
|
||
}
|
||
default: {
|
||
obj = commonPoint(point, option)
|
||
break
|
||
}
|
||
}
|
||
success && success(obj)
|
||
}
|
||
|
||
// 换电点位设置
|
||
function setChargePoint(point: any, option: any) {
|
||
const { soc, online } = point
|
||
const obj = {
|
||
...point,
|
||
id: Number(point.bikeCode),
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
width: 25,
|
||
height: 25,
|
||
}
|
||
|
||
if (!online) {
|
||
return {
|
||
...obj,
|
||
iconPath: getImageFullPath('MAP_ICON_LIXIAN_IMAGE'),
|
||
}
|
||
}
|
||
if (soc >= charge_toolbar_list[0].section[0] && soc <= charge_toolbar_list[0].section[1]) {
|
||
return {
|
||
...obj,
|
||
iconPath: getImageFullPath('MAP_ICON_DIDIANLIANG_IMAGE'),
|
||
}
|
||
}
|
||
if (soc >= charge_toolbar_list[1].section[0] && soc <= charge_toolbar_list[1].section[1]) {
|
||
return {
|
||
...obj,
|
||
iconPath: getImageFullPath('MAP_ICON_ZHONGDIANLAING_IMAGE'),
|
||
}
|
||
}
|
||
if (soc >= charge_toolbar_list[2].section[0] && soc <= charge_toolbar_list[2].section[1]) {
|
||
return {
|
||
...obj,
|
||
iconPath: getImageFullPath('MAP_ICON_GAODIANLAING_IMAGE'),
|
||
}
|
||
}
|
||
}
|
||
|
||
// 调度点位设置
|
||
export function setDispatchPoint(point: any, option: any) {
|
||
const { bikeInfo } = point
|
||
const { seletType } = option
|
||
let iconPath = getImageFullPath('CUSTOM_CAR_ICON_IMAGE')
|
||
|
||
if (seletType === 'optionalHours') {
|
||
iconPath = getImageFullPath('MAP_ICON_OVER24HCAR_IMAGE')
|
||
}
|
||
else {
|
||
if (bikeInfo.over24Hours) {
|
||
iconPath = getImageFullPath('MAP_ICON_OVER24HCAR_IMAGE')
|
||
}
|
||
if (!bikeInfo.isInParkingArea) {
|
||
iconPath = getImageFullPath('MAP_ICON_NOPARKINGCAR_IMAGE')
|
||
}
|
||
if (!bikeInfo.isInParkingArea && bikeInfo.over24Hours) {
|
||
iconPath = getImageFullPath('MAP_ICON_ALLDISPATCH_IMAGE')
|
||
}
|
||
}
|
||
|
||
const obj = {
|
||
...point,
|
||
id: Number(point.bikeCode),
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
width: 25,
|
||
height: 25,
|
||
iconPath,
|
||
}
|
||
|
||
return obj
|
||
}
|
||
|
||
// 维修点位设置
|
||
export function repairPoint(point: any, option: any) {
|
||
const { seletType } = option
|
||
let iconPath = getImageFullPath('CUSTOM_CAR_ICON_IMAGE')
|
||
|
||
if (seletType === 'offline') {
|
||
iconPath = getImageFullPath('MAP_ICON_LIXIAN_IMAGE')
|
||
}
|
||
else {
|
||
iconPath = getImageFullPath('MAP_ICON_REPAIRE_CAR_IMAGE')
|
||
}
|
||
|
||
const obj = {
|
||
...point,
|
||
id: Number(point.bikeCode),
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
width: 25,
|
||
height: 25,
|
||
iconPath,
|
||
}
|
||
|
||
return obj
|
||
}
|
||
|
||
// 巡检点位设置
|
||
export function inspectionPoint(point: any, option: any) {
|
||
const { bikeInfo } = point
|
||
|
||
let iconPath = getImageFullPath('CUSTOM_CAR_ICON_IMAGE')
|
||
|
||
if (!bikeInfo.isUserReporting) { // 每月任务
|
||
iconPath = getImageFullPath('MAP_ICON_LIXIAN_IMAGE')
|
||
}
|
||
else if (bikeInfo.isUserReporting) {
|
||
iconPath = getImageFullPath('MAP_XUNJIAN_YONGHUSHANGBAO_IMAGE')
|
||
}
|
||
|
||
const obj = {
|
||
...point,
|
||
id: Number(point.bikeCode),
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
width: 25,
|
||
height: 25,
|
||
iconPath,
|
||
}
|
||
|
||
return obj
|
||
}
|
||
|
||
// 所有车辆的点位
|
||
export function allCarPoint(point: any, option: any) {
|
||
const { bikeInfo } = point
|
||
let iconPath = getImageFullPath('CUSTOM_CAR_ICON_IMAGE')
|
||
|
||
iconPath = getIconByOrderStatus(bikeInfo)
|
||
|
||
const obj = {
|
||
...point,
|
||
id: Number(point.bikeCode),
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
width: 25,
|
||
height: 25,
|
||
iconPath,
|
||
}
|
||
return obj
|
||
}
|
||
|
||
// 通用点位设置
|
||
export function commonPoint(point: any, option: any) {
|
||
return {
|
||
id: point.id,
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
...option,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断点是否在多边形内
|
||
* @param point 当前点
|
||
* @param polygon 多边形
|
||
* @returns {boolean}
|
||
*/
|
||
export function isPointInPolygon(
|
||
point: { latitude: number, longitude: number },
|
||
polygon: Array<{ latitude: number, longitude: number }>,
|
||
) {
|
||
if (!point || !polygon || polygon.length < 3)
|
||
return false
|
||
|
||
const { latitude: y, longitude: x } = point
|
||
let inside = false
|
||
|
||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||
const xi = polygon[i].longitude
|
||
const yi = polygon[i].latitude
|
||
const xj = polygon[j].longitude
|
||
const yj = polygon[j].latitude
|
||
|
||
// 判断点是否在线段上下方(y方向)
|
||
const intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)
|
||
|
||
if (intersect)
|
||
inside = !inside
|
||
}
|
||
|
||
return inside
|
||
}
|
||
|
||
/**
|
||
* 判断多边形是否自相交
|
||
* @param {Array<{latitude: number, longitude: number}>} points - 点数组(无需闭合)
|
||
* @returns {boolean}
|
||
*/
|
||
export function isPolygonSelfIntersecting(points) {
|
||
if (points.length < 4)
|
||
return false // 至少4个点才可能自交
|
||
|
||
// 转换为 [lng, lat] 格式,并闭合
|
||
const coords = points.map(p => [p.longitude, p.latitude])
|
||
coords.push(coords[0]) // 闭合多边形
|
||
|
||
const n = coords.length - 1 // 实际边数(最后一点是重复的)
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
for (let j = i + 1; j < n; j++) {
|
||
// 跳过相邻边(共享顶点)
|
||
if (j === i + 1)
|
||
continue
|
||
// 跳过首尾相连的边(第一个和最后一个边)
|
||
if (i === 0 && j === n - 1)
|
||
continue
|
||
|
||
const a1 = coords[i]
|
||
const a2 = coords[i + 1]
|
||
const b1 = coords[j]
|
||
const b2 = coords[j + 1]
|
||
|
||
if (lineSegmentsIntersect(a1, a2, b1, b2)) {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
/**
|
||
* 判断两条线段是否相交(含端点相交)
|
||
*/
|
||
function lineSegmentsIntersect(p1, p2, p3, p4) {
|
||
const d1 = direction(p3, p4, p1)
|
||
const d2 = direction(p3, p4, p2)
|
||
const d3 = direction(p1, p2, p3)
|
||
const d4 = direction(p1, p2, p4)
|
||
|
||
// 一般相交情况
|
||
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0))
|
||
&& ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
|
||
return true
|
||
}
|
||
|
||
// 处理共线且重叠的情况(可选,通常可忽略)
|
||
if (d1 === 0 && onSegment(p3, p4, p1))
|
||
return true
|
||
if (d2 === 0 && onSegment(p3, p4, p2))
|
||
return true
|
||
if (d3 === 0 && onSegment(p1, p2, p3))
|
||
return true
|
||
if (d4 === 0 && onSegment(p1, p2, p4))
|
||
return true
|
||
|
||
return false
|
||
}
|
||
|
||
function direction(a, b, c) {
|
||
return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])
|
||
}
|
||
|
||
function onSegment(a, b, c) {
|
||
return Math.min(a[0], b[0]) <= c[0] && c[0] <= Math.max(a[0], b[0])
|
||
&& Math.min(a[1], b[1]) <= c[1] && c[1] <= Math.max(a[1], b[1])
|
||
}
|
||
|
||
/**
|
||
*绘制label(区域标识)
|
||
* @param {object} param
|
||
* @param {Array} param.point
|
||
* @param {object} param.option
|
||
*/
|
||
export function drawLabel({
|
||
point,
|
||
type,
|
||
option = {
|
||
content: '区域',
|
||
color: '#000000',
|
||
fontSize: 14,
|
||
anchorX: 0,
|
||
anchorY: 0,
|
||
},
|
||
success,
|
||
fail,
|
||
}: {
|
||
point: { latitude: number, longitude: number, id: string | number }
|
||
type: number
|
||
option?: any
|
||
success: (res: any) => void
|
||
fail: (res: any) => void
|
||
}) {
|
||
let obj = {}
|
||
console.log(point, type)
|
||
// 校验point
|
||
if (!point) {
|
||
fail && fail('point参数不能为空')
|
||
return
|
||
}
|
||
|
||
obj = {
|
||
id: Number(point.id),
|
||
type: 'polygons',
|
||
latitude: point.latitude,
|
||
longitude: point.longitude,
|
||
alpha: 0,
|
||
width: 0,
|
||
height: 0,
|
||
label: {
|
||
content: siteTypeConfig[type].label,
|
||
fontSize: 12,
|
||
bgColor: '#FF6B6B',
|
||
textAlign: 'center',
|
||
},
|
||
}
|
||
success && success(obj)
|
||
}
|
||
|
||
/**
|
||
* 计算多边形质心
|
||
* @param {Array<Array<number>>} points - 多边形顶点数组,格式为 [[lng, lat], [lng, lat], ...]
|
||
* @returns {{longitude: number, latitude: number}} - 质心坐标
|
||
*/
|
||
export function getPolygonCentroid(points: Array<Array<number>>): { longitude: number, latitude: number } {
|
||
const n = points.length
|
||
let X = 0
|
||
let Y = 0
|
||
let Z = 0
|
||
|
||
for (let i = 0; i < n; i++) {
|
||
const lng = points[i][0] * Math.PI / 180 // 经度 → 弧度
|
||
const lat = points[i][1] * Math.PI / 180 // 纬度 → 弧度
|
||
|
||
const x = Math.cos(lat) * Math.cos(lng)
|
||
const y = Math.cos(lat) * Math.sin(lng)
|
||
const z = Math.sin(lat)
|
||
|
||
X += x
|
||
Y += y
|
||
Z += z
|
||
}
|
||
|
||
X /= n
|
||
Y /= n
|
||
Z /= n
|
||
|
||
const Lng = Math.atan2(Y, X) // 注意:Y 在前,X 在后
|
||
const Hyp = Math.sqrt(X * X + Y * Y)
|
||
const Lat = Math.atan2(Z, Hyp)
|
||
|
||
return {
|
||
longitude: Lng * 180 / Math.PI,
|
||
latitude: Lat * 180 / Math.PI,
|
||
}
|
||
}
|