2026-02-26 09:39:10 +08:00

723 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
}
}