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>} points - 多边形顶点数组,格式为 [[lng, lat], [lng, lat], ...] * @returns {{longitude: number, latitude: number}} - 质心坐标 */ export function getPolygonCentroid(points: Array>): { 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, } }