import { getCrossPoint } from "./util"; import Events from "./events"; import dijkstra from "dijkstrajs"; import { getMapData, mall } from "../../getMapData"; const getDistance = (a, b) => Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); export const minScale = 0.1; export const maxScale = 10; export default class MAPAPP { events = new Events(); mall = mall; groups = []; x = null; y = null; intervals = []; shown = false; theta2s = []; scale = 0.5; constructor(query, component) { this.searchType = component.data.searchType; const canvas = query.node; const ctx = canvas.getContext("2d"); const dpr = wx.getSystemInfoSync().pixelRatio; canvas.width = query.width * dpr; canvas.height = query.height * dpr; const start = canvas.createImage(); start.src = "/pages/map2d/start.png"; const end = canvas.createImage(); end.src = "/pages/map2d/end.png"; const arrow = canvas.createImage(); arrow.src = "/pages/map2d/arrow.png"; ctx.scale(dpr, dpr); Object.assign(this, { canvas, ctx, start, end, arrow, width: query.width, height: query.height, }); this.init(); } on(event, cb) { return this.events.on(event, cb); } once(event, cb) { return this.events.once(event, cb); } off(event, cb) { return this.events.off(event, cb); } dispose() { console.log("map interval disposed"); this.intervals && this.intervals.forEach(clearInterval); this.intervals = []; } async init() { const data = await getMapData(); Object.assign(this, data); const { floors } = this.mall; const { canvas } = this; const groups = floors.map(([_, name, map2dData]) => { const floor = canvas.createImage(); if (map2dData) floor.src = map2dData.data; return floor; }); this.groups = groups; this.events.dispatch("loaded", this); this.intervals.push( setInterval(() => { this.animate(); }, 1000 / 30) ); } get shops() { return !this.serverShopInfo ? [] : this.serverShopInfo[this.floorOrder].shopList; } get currentFloorObject() { return this.groups[this.floorOrder]; } updateTheta2(rag) { if (!this.theta2s) this.theta2s = [rag]; else this.theta2s.push(rag); if (this.theta2s.length > 20) this.theta2s.shift(); const { x, y } = this.theta2s.reduce( ({ x, y }, nxt) => ({ x: x + Math.cos(nxt), y: y + Math.sin(nxt), }), { x: 0, y: 0, } ); this.theta2 = Math.atan2(y, x); } setXY(x, y) { this.x = x; this.y = y; } scaleX(number) { const { x, width, scale } = this; return (number - x) * scale + width / 2; } scaleY(number) { const { y, height, scale } = this; return (number - y) * scale + height / 2; } scaleLen(number) { const { scale } = this; return number * scale; } animate() { const { shown, ctx, x, y, width, height } = this; if (!shown) return; ctx.clearRect(0, 0, width, height); ctx.save(); // ctx.translate(this.scaleX(-x) + width / 2, this.scaleY(-y) + height / 2); this.drawFloor(); this.drawLines(); ctx.restore(); this.drawArrow(); } drawArrow() { const { ctx, arrow, width, height, theta2 } = this; ctx.save(); ctx.translate(width / 2, height / 2); ctx.rotate(-theta2); ctx.drawImage(arrow, -16, -16, 32, 32); ctx.restore(); } drawPath() { const { linePath, ctx } = this; const start = linePath[0]; const end = linePath[linePath.length - 1]; ctx.fillStyle = "#518cf7"; ctx.beginPath(); ctx.arc( this.scaleX(start[0]), this.scaleY(start[1]), // this.scaleLen(10), 10, 0, 2 * Math.PI ); ctx.fill(); ctx.beginPath(); ctx.arc( this.scaleX(end[0]), this.scaleY(end[1]), // this.scaleLen(10), 10, 0, 2 * Math.PI ); ctx.fill(); ctx.beginPath(); linePath.forEach(([x, y], i) => { if (i === 0) { ctx.moveTo(this.scaleX(x), this.scaleY(y)); } else { ctx.lineTo(this.scaleX(x), this.scaleY(y)); } }); } drawLines() { const { linePath, ctx } = this; if (!linePath || !linePath.length) return; this.drawPath(); ctx.lineCap = "round"; ctx.lineJoin = "round"; // ctx.lineWidth = this.scaleLen(11); ctx.lineWidth = 11; ctx.strokeStyle = "#518cf7"; ctx.stroke(); for (let i = 9; i >= 1; i -= 1) { ctx.lineCap = "butt"; ctx.lineJoin = "butt"; // ctx.lineWidth = this.scaleLen(i); ctx.lineWidth = i; ctx.strokeStyle = "#fff"; // ctx.setLineDash([this.scaleLen(4), this.scaleLen(13)]); ctx.setLineDash([4, 13]); // ctx.lineDashOffset = this.scaleLen(i - 9); ctx.lineDashOffset = i - 9; ctx.stroke(); ctx.strokeStyle = "#437af7"; // ctx.setLineDash([0, this.scaleLen(4), this.scaleLen(13), 0]); ctx.setLineDash([0, 4, 13, 0]); // ctx.lineDashOffset = this.scaleLen(i - 9); ctx.lineDashOffset = i - 9; ctx.stroke(); } const start = linePath[0]; const end = linePath[linePath.length - 1]; ctx.fillStyle = "#437af7"; // ctx.lineWidth = this.scaleLen(1); ctx.lineWidth = 1; ctx.beginPath(); ctx.arc( this.scaleX(start[0]), this.scaleY(start[1]), // this.scaleLen(9), 9, 0, 2 * Math.PI ); ctx.fill(); ctx.beginPath(); ctx.arc( this.scaleX(end[0]), this.scaleY(end[1]), // this.scaleLen(9), 9, 0, 2 * Math.PI ); ctx.fill(); ctx.fillStyle = "#fff"; ctx.beginPath(); ctx.arc( this.scaleX(start[0]), this.scaleY(start[1]), // this.scaleLen(4.5), 4.5, 0, 2 * Math.PI ); ctx.fill(); ctx.beginPath(); ctx.arc( this.scaleX(end[0]), this.scaleY(end[1]), // this.scaleLen(4.5), 4.5, 0, 2 * Math.PI ); ctx.fill(); } drawFloor() { const { floorOrder, groups, ctx } = this; const floor = groups[floorOrder]; ctx.save(); ctx.drawImage( floor, this.scaleX(-floor.width / 2), this.scaleY(-floor.height / 2), this.scaleLen(floor.width), this.scaleLen(floor.height) ); ctx.restore(); } async changeFloor(num) { this.floorOrder = num; this.x = null; this.y = null; this.events.dispatch("floorchange", Number(num)); return; } cross({ x, y }) { const point = { x, y }; const { positions } = this; if (!(positions && positions.length)) return point; let minDistance = Infinity; let crossPoint = null; let index = 1; for (let k = 1; k < positions.length; k++) { let point1 = positions[k - 1]; let point2 = positions[k]; let cross = getCrossPoint(point, point1, point2); let d = Math.sqrt( (point.x - cross.x) * (point.x - cross.x) + (point.y - cross.y) * (point.y - cross.y) ); if (d < minDistance) { minDistance = d; crossPoint = cross; index = k; if (d < 0.5) break; } } let distance2End = 0; for (let i = index; i < positions.length; i++) { let point1 = i === index ? crossPoint : positions[i - 1]; let point2 = positions[i]; distance2End += getDistance(point1, point2); } return [crossPoint, minDistance, distance2End]; } requestRoute(start, end, draw = true) { if (!(start && end)) return; const startFloorId = start[2]; const startFloorOrder = getApp().globalData.floorIdFloorOrderMap[ startFloorId ]; const { name: sname } = this.getNearestPoint({ x: start[0], y: start[1], floorOrder: startFloorOrder, }); const startFloor = startFloorOrder; const startPoint = Number(sname); const endFloor = end.floorOrder; const endPoint = end.navPoint; let path = this.shortestPath( { floorOrder: startFloor, NavPoint: startPoint, }, { floorOrder: endFloor, NavPoint: endPoint, } ); if (!path) { console.log("寻路失败", "start", start, "end", end); return { naviData: null, nextFloorId: null }; } let floorIdPoints = path.map((str) => str.split("_")); const byFirstDiffFloor = ([floorId], i) => i === 0 ? false : floorIdPoints[i - 1][0] !== floorId; const index = floorIdPoints.findIndex(byFirstDiffFloor); console.log("index", index); const isFloorChange = index !== -1 && getApp().globalData.floors[path[index].split("_")[0]]; const currentFloorOrder = Number(floorIdPoints[0][0]); const nextFloorOrder = !isFloorChange ? null : Number(floorIdPoints[index][0]); const fac = !isFloorChange ? null : this.facilityLiftMap[path[index - 1]]; let naviData = floorIdPoints.slice(0, index === -1 ? undefined : index); naviData = naviData.reduce((acc, [floorOrder, point]) => { const last = acc[acc.length - 1]; const x = this.points[floorOrder][point].position[0]; const y = this.points[floorOrder][point].position[2]; const floorId = startFloorId; if (last && Math.abs(last.y - y) <= 3 && Math.abs(last.x - x) <= 3) return acc; else return [...acc, { x, y, floorId }]; }, []); if (naviData.length === 1) { naviData.unshift({ x: start[0], y: start[1], flooorId: naviData[0].flooorId, }); } naviData.forEach((data, i) => { data.pointType = i === 0 ? 0 : i !== naviData.length - 1 ? 1 : !isFloorChange ? 6 : nextFloorOrder > currentFloorOrder ? fac.Type == 5 ? 2 : 4 : fac.Type == 5 ? 3 : 5; }); this.positions = naviData; console.log("originalNaviData", naviData); if (draw && naviData.length) { this.linePath = naviData.map(({ x, y }) => [x, y]); } const nextFloorId = !isFloorChange ? null : getApp().globalData.floors[path[index].split("_")[0]].floorId; console.log("nextFloorId", nextFloorId); return { naviData, nextFloorId, }; } shortestPath( { floorOrder: floorOrder1, NavPoint: NavPoint1 }, { floorOrder: floorOrder2, NavPoint: NavPoint2 }, searchType ) { const { graph, graphDt, graphFt } = this; searchType = [0, 1, 2].includes(searchType) ? searchType : this.searchType; try { let currentGraph = searchType === 0 ? graph : searchType === 1 ? graphFt : graphDt; const s = floorOrder1 + "_" + NavPoint1; const d = floorOrder2 + "_" + NavPoint2; if (currentGraph && currentGraph[s] && currentGraph[d]) { let path = dijkstra.find_path(currentGraph, s, d); return path; } else return null; } catch (e) { console.log(e); return this.shortestPath( { floorOrder: floorOrder1, NavPoint: NavPoint1 }, { floorOrder: floorOrder2, NavPoint: NavPoint2 }, 0 ); } } getNearestPoint({ x, y, floorOrder }) { const { points } = this; const [nearest] = points[floorOrder].reduce( ([last, min], nxt) => { if (last === null) { return [ nxt, getDistance( { x, y, }, { x: nxt.position[0], y: nxt.position[2], } ), ]; } const dis = getDistance( { x, y, }, { x: nxt.position[0], y: nxt.position[2], } ); if (dis < min) return [nxt, dis]; else return [last, min]; }, [null, Infinity] ); return nearest; } getFloorHeightByFloorOrder(floorOrder) { const { config, groups } = this; const floor = groups[floorOrder]; const { floorHeights } = config; return floorOrder in floorHeights ? floorHeights[floorOrder] : floor.floorHeight; } getBoxHeightByFloorOrder(floorOrder) { const { groups } = this; const floor = groups[floorOrder]; return floor.boxHeight; } getNavIconY(floorOrder) { const { groups } = this; const floor = groups[floorOrder]; if (!floor) return this.getNavIconY(this.floorOrder); const boxHeight = this.getBoxHeightByFloorOrder(floorOrder); const radius = PathLine.getPathLineRadius(this, floor); return boxHeight + radius * 5; } }