UNPKG

@idm-plugin/vessel

Version:

idm plugin for vessel

1,258 lines 68.8 kB
var mt = Object.defineProperty; var ft = (x, t, e) => t in x ? mt(x, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : x[t] = e; var K = (x, t, e) => (ft(x, typeof t != "symbol" ? t + "" : t, e), e); import J from "got"; import lt from "@log4js-node/log4js-api"; import p from "moment"; import { LaneHelper as R, LngLatHelper as z } from "@idm-plugin/geo2"; import { MeteoHelper2 as yt } from "@idm-plugin/meteo2"; import { Meteo2Assist as ut } from "@idm-plugin/meteo"; let g; try { g = lt.getLogger("vessel"); } catch { } finally { } class nt { /** * 解析AIS状态码 * @param status */ parseStatus(t) { let e, n; switch (t) { case 0: e = "在航(主机推动)", n = "Underway Using Engine"; break; case 1: e = "锚泊", n = "Anchored"; break; case 2: e = "失控", n = "Not under command"; break; case 3: e = "操纵受限", n = "Limited airworthiness"; break; case 4: e = "吃水受限", n = "Limited by ship's draft"; break; case 5: e = "靠泊", n = "Mooring"; break; case 6: e = "搁浅", n = "Stranded"; break; case 7: e = "捕捞作业", n = "Engaged in fishing"; break; case 8: e = "靠帆船提供动力", n = "Sailing"; break; default: e = "未定义", n = "Undefined"; } return { labelCn: e, labelEn: n }; } } class jt extends nt { constructor(e, n) { super(); K(this, "clientId"); K(this, "clientSecret"); K(this, "token"); this.clientId = e, this.clientSecret = n; } async authToken(e = {}) { const n = "https://svc.data.myvessel.cn/ada/oauth/token", o = { searchParams: { client_id: this.clientId, client_secret: this.clientSecret, grant_type: "client_credentials" } }, i = await J.post(n, o).json(); g == null || g.info("[%s] fetch access token from: %s - %j", e.requestId, n, i), i.error || (this.token = { accessToken: i.access_token, tokenType: i.token_type, expiresIn: i.expires_in, scope: i.scope, jti: i.jti, issuedAt: p().utc().format() }); } async checkToken(e = {}) { var n; return (!this.token || p().diff(p(this.token.issuedAt), "seconds") > (((n = this.token) == null ? void 0 : n.expiresIn) || 0) - 300) && await this.authToken(e), this.token; } /** * 模糊查询 * @param kw * @param options */ async suggest(e, n = {}) { var s, r; await this.checkToken(n); const o = "https://market.myvessel.cn/sdc/v1/mkt/vessels/fuzzy", i = { headers: { Authorization: `${(s = this.token) == null ? void 0 : s.tokenType} ${(r = this.token) == null ? void 0 : r.accessToken}` }, json: { kw: e, recordNum: n.ps || 10 } }; g == null || g.info("[%s] fetch suggest vessels from: %s - %j", n.requestId, o, i); const a = await J.post(o, i).json(); return a.status !== 200 ? (g == null || g.warn("[%s] fetch suggest vessels failed: %j", n.requestId, { message: a.message, status: a.status, code: a.code }), []) : (a.data || []).map((u) => ({ mmsi: u.mmsi, name: u.nameEn, nameCn: u.nameCn, imo: Number.isNaN(u.imo) ? null : Number(u.imo), callSign: u.callsign, type: u.vesselTypeNameEn, flagName: u.flagCtry, vendor: "myvessel", raw: u })); } /** * imo/mmsi 精确查询 * @param imo * @param options */ async search(e, n = {}) { var d, u; await this.checkToken(n); const o = /^\d{7}$/.test(e.toString()), i = o ? "https://market.myvessel.cn/sdc/v1/mkt/vessels/detail/imo" : "https://market.myvessel.cn/sdc/v1/mkt/vessels/detail/mmsi", a = o ? { imo: e } : { mmsi: e }, s = { headers: { Authorization: `${(d = this.token) == null ? void 0 : d.tokenType} ${(u = this.token) == null ? void 0 : u.accessToken}` }, searchParams: a }; g == null || g.info("[%s] fetch vessel from: %s - %j", n.requestId, i, s); const r = await J.get(i, s).json(); if (r.status !== 200) return g == null || g.warn("[%s] fetch suggest vessels failed: %j", n.requestId, { message: r.message, status: r.status, code: r.code }), {}; { const h = r.data; if (h) return { mmsi: h.mmsi, imo: Number.isNaN(h.imo) ? null : Number(h.imo), callSign: h.callsign, name: h.nameEn, nameCn: h.nameCn, type: h.vesselTypeNameEn, flagName: h.flagCtry, clasz: h.classSociety, dateOfBuild: h.buildYearMonth, deadweight: h.dwt, grossTonnage: h.grt, netTonnage: h.net, teu: h.teu, length: h.length, breadth: h.width, height: h.height, draught: h.draught, speed: h.speed, passengerCapacity: h.passengercapacity, vendor: "myvessel", raw: h }; } return {}; } async archives(e, n = {}) { var s, r; await this.checkToken(n); const o = "https://svc.data.myvessel.cn/sdc/v1/ship/info/batch", i = { headers: { Authorization: `${(s = this.token) == null ? void 0 : s.tokenType} ${(r = this.token) == null ? void 0 : r.accessToken}` }, json: { mmsiList: typeof e == "number" ? [e] : e } }; g == null || g.info("[%s] fetch vessel archive from: %s - %j", n.requestId, o, i); const a = await J.post(o, i).json(); return a.status !== 200 ? (g == null || g.warn("[%s] fetch vessel archive failed: %j", n.requestId, { message: a.message, status: a.status, code: a.code }), {}) : a.data; } async realTimePosition(e, n = {}) { var r, d; await this.checkToken(n); const o = "https://svc.data.myvessel.cn/sdc/v1/vessels/status/location/unit", i = { headers: { Authorization: `${(r = this.token) == null ? void 0 : r.tokenType} ${(d = this.token) == null ? void 0 : d.accessToken}` }, searchParams: { mmsi: e } }; g == null || g.info("[%s] fetch realtime position from: %s - %j", n.requestId, o, i); const a = await J.get(o, i).json(); if (a.code) return g == null || g.warn("[%s] fetch realtime position failed: %j", n.requestId, { message: a.message, status: a.status, code: a.code }), a; const s = a.data; for (const u in s) !isNaN(s[u]) && Number(s[u]) !== 1 / 0 && (s[u] = Number(s[u])); if (s) { const u = p(`${s.postime} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"); return { mmsi: s.mmsi, name: s.vesselName || s.aisVesselName, imo: s.imo, callSign: s.callsign || s.aisCallSign, lat: s.lat, lng: s.lon, length: s.length, width: s.width, draught: s.currDraught, sog: s.sog, cog: s.cog, hdg: s.hdg, rot: s.rot, eta: /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(s.eta) ? p.utc(s.eta).format() : void 0, destination: s.dest, positionTime: u.unix(), status: s.status, labelCn: s.statusNameCn, labelEn: s.statusNameEn, vesselType: s.vesselTypeNameEn, flag: s.flagCtryNameEn, clasz: s.classSociety, build: s.buildYear, dwt: s.dwt, grt: s.grt, net: s.net, method: "position", vendor: "myVessel", utc: u.utc().format() }; } else return {}; } /** * @param from 始发点坐标 { lng, lat } * @param to 目的点坐标 { lng, lat } * @param crossMonths 规划月份 [1,...,12] * @param excludeNodes 排除关键节点, [node.code] * @param excludeSeas 排除水域, [sea.code] * @param options { requiretId: '请求ID', useAIModel: '启用AI算法', withECA: '是否计算低硫区航行距离', withSpecial: '是否计算穿越的特战区列表和海盗区列表', draught: '最大吃水' } */ async calculateRoute(e, n, o, i, a, s = {}) { var b, v, E; const r = p(); await this.checkToken(s); const d = "https://market.myvessel.cn/sdc/v1/mkt/routes/plan", u = { maxDraught: s.draught || 10, useAIModel: s.useAIModel ?? !0, withECA: s.withECA || !1, withSpecialRegion: s.withSpecial || !1 }; e.code && (u.startPortCode = e.code), e.lng !== void 0 && e.lat !== void 0 && (u.startPoint = { lon: e.lng, lat: e.lat }), n.code && (u.endPortCode = n.code), n.lng !== void 0 && n.lat !== void 0 && (u.endPoint = { lon: n.lng, lat: n.lat }), o != null && o.length && (u.crossMonthList = o), i != null && i.length && (u.excludeNodes = i), a != null && a.length && (u.excludeSeaAreas = a); const h = { headers: { Authorization: `${(b = this.token) == null ? void 0 : b.tokenType} ${(v = this.token) == null ? void 0 : v.accessToken}` }, json: u }; g == null || g.info("[%s] fetch route from: %s - %j", s.requestId, d, h); const l = await J.post(d, h).json(); if (l.status !== 200) return g == null || g.warn("[%s] fetch route failed: %j", s.requestId, { message: l.message, status: l.status, code: l.code }), {}; { const k = { status: "Success", nodes: [], seas: [], regions: [], waypoints: [], route: [], distance: 0, memo: "" }, { nodes: I, seas: w, tracks: m, specialRegions: c, ecaLength: f } = l.data; k.nodes = I == null ? void 0 : I.map((M) => ({ code: M.nodeCode, nameEn: M.nameEn, nameCn: M.nameCn, // 中心 center: { lat: Math.round(M.lat * 1e6) / 1e6, lng: Math.round(M.lon * 1e6) / 1e6 }, // 起点 start: { lat: Math.round(M.startLat * 1e6) / 1e6, lng: Math.round(M.startLon * 1e6) / 1e6 }, // 终点 end: { lat: Math.round(M.endLat * 1e6) / 1e6, lng: Math.round(M.endLat * 1e6) / 1e6 }, // 是否为不可避开关键节点 isKey: M.isKeyNode, // 重要枢纽节点 isHub: M.isHubNode })), k.seas = w == null ? void 0 : w.map((M) => ({ code: M.mrgidSea, nameEn: M.nameEn, nameCn: M.nameCn, center: { lat: Math.round(M.centerLat * 1e6) / 1e6, lng: Math.round(M.centerLon * 1e6) / 1e6 }, min: { lat: Math.round(M.minLat * 1e6) / 1e6, lng: Math.round(M.minLon * 1e6) / 1e6 }, max: { lat: Math.round(M.maxLat * 1e6) / 1e6, lng: Math.round(M.maxLon * 1e6) / 1e6 }, level: M.mapLevel })), c == null || c.map((M) => { M.regionLength && k.regions.push({ type: M.regionType, distance: M.regionLength, rows: M.regions.map((j) => ({ code: j.regionCode, nameCn: j.nameCn, nameEn: j.nameEn, type: j.regionType, distance: j.length })) }); }), k.waypoints = m == null ? void 0 : m.map((M) => ({ lat: Math.round(M.lat * 1e5) / 1e5, lng: Math.round(M.lon * 1e5) / 1e5 })), (E = k.waypoints) != null && E.length && (k.waypoints = R.simplifyCoordinates(k.waypoints), k.route = R.divideAccordingToLng(k.waypoints), k.distance = R.calculateRouteDistance(k.route), k.distanceInECA = f); const S = p().diff(r, "second"); return k.memo = `time cost: ${S}s`, g.info("[%s] calculate route cost: %d seconds", s.requestId, S), k; } } async trajectory(e, n, o, i, a = !0, s = {}) { await this.checkToken(s); const r = await this.realTimePosition(e, s), d = p(n), u = p(o), h = []; for (; u.diff(d, "day", !0) > 30; ) await this.trajectoryIn30Day(e, d, d.clone().add(30, "day"), r, i, h, s), d.add(30, "day"); return await this.trajectoryIn30Day(e, d, u, r, i, h, s), h; } async trajectoryIn30Day(e, n, o, i, a, s, r = {}) { var v, E, k, I, w; const d = "https://svc.data.myvessel.cn/sdc/v1/vessels/status/track", u = { headers: { Authorization: `${(v = this.token) == null ? void 0 : v.tokenType} ${(E = this.token) == null ? void 0 : E.accessToken}` }, json: { mmsi: e, startTime: n.utcOffset(8).format("YYYY-MM-DD HH:mm:ss"), endTime: o.utcOffset(8).format("YYYY-MM-DD HH:mm:ss") } }; g == null || g.info("[%s] fetch trajectory from: %s - %j", r.requestId, d, u); const h = await J.post(d, u).json(); if (h.code) return g == null || g.warn("[%s] fetch trajectory failed: %j", r.requestId, d, { message: h.message, status: h.status, code: h.code }), h; let l = -1; const b = p(`${(I = (k = h.data) == null ? void 0 : k[0]) == null ? void 0 : I.postime} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"); return (w = h.data) == null || w.forEach((m) => { for (const N in m) !isNaN(m[N]) && Number(m[N]) !== 1 / 0 && (m[N] = Number(m[N])); const c = p(`${m.postime} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"), f = m.status, { labelCn: y, labelEn: S } = this.parseStatus(f), M = { mmsi: m.mmsi, imo: i == null ? void 0 : i.imo, lat: m.lat, lng: m.lon, sog: m.sog, cog: m.cog, hdg: m.hdg, draught: m.draught, status: f, eta: /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(m.eta) ? p(`${m.eta} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00").utc().format() : void 0, destination: m.dest, positionTime: c.unix(), labelCn: y, labelEn: S, method: "trajectory", vendor: "myVessel", utc: c.utc().format() }, j = Math.floor(c.diff(b, "minute", !0) / (a || 1)); j !== l && (l = j, s.push(M)); }), s; } } class xt extends nt { constructor(e) { super(); K(this, "token"); this.token = e; } async realTimePosition(e, n = {}) { const o = "https://api.hifleet.com/position/position/get/token", i = { searchParams: { mmsi: e, usertoken: this.token } }, a = await J.post(o, i).json(); g == null || g.info("[%s] fetch realtime position from: %s - %j", n.requestId, o, i); const s = a == null ? void 0 : a.list; if (!s) return g == null || g.warn("[%s] fetch realtime position failed: %j", n.requestId, o, a), a; for (const b in s) !isNaN(s[b]) && Number(s[b]) !== 1 / 0 && (s[b] = Number(s[b])); s.status = s.sp > 3 ? 0 : 1; const r = s.status, { labelCn: d, labelEn: u } = this.parseStatus(r), h = p(`${s.ti} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"); return { mmsi: s.m, name: s.n, imo: s.imonumber, callSign: s.callsign, lat: Math.round(s.la / 60 * 1e5) / 1e5, lng: Math.round(s.lo / 60 * 1e5) / 1e5, length: s.l, width: s.w, draught: s.draught, sog: s.sp, cog: s.co, hdg: s.h, rot: isNaN(s.rot) ? 0 : s.rot, eta: /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(s.eta) ? p.utc(s.eta).format() : void 0, destination: s.destination, vesselType: s.type, dwt: s.dwt, build: s.buildyear, flag: s.fn, positionTime: h.unix(), utc: h.utc().format(), status: r, labelCn: d, labelEn: u, method: "position", vendor: "hifleet" }; } async search(e, n = {}) { let o = "https://www.hifleet.com/hifleetapi/searchVesselOL.do"; const i = { searchParams: { keyword: e }, headers: { Referer: "https://www.hifleet.com", Origin: "https://www.hifleet.com", Host: "www.hifleet.com" } }; let a = await J.post(o, i).json(); g == null || g.info("[%s] fetch vessel props from: %s - %j", n.requestId, o, i), a instanceof Array && (a = a[0]); for (const r in a) !isNaN(a[r]) && Number(a[r]) !== 1 / 0 && (a[r] = Number(a[r])); const s = { mmsi: a.m, name: a.n, imo: a.i, callSign: a.c, length: a.l, breadth: a.b, draught: a.dr, type: a.t }; return o = "https://www.hifleet.com/hifleetapi/sameShipSearch.do", a = await J.post(o, i).json(), g == null || g.info("[%s] search vessel dead weight from: %s - %j", n.requestId, o, i), a instanceof Array && (a = a[0]), a && (s.deadweight = Number(a.dwt)), s; } async suggest(e, n = {}) { const o = "https://www.hifleet.com/hifleetapi/getShipSuggest.do", i = { searchParams: { q: e }, headers: { Referer: "https://www.hifleet.com", Origin: "https://www.hifleet.com", Host: "www.hifleet.com" } }, a = await J.post(o, i).json(); g == null || g.info("[%s] suggest vessel props from: %s - %j", n.requestId, o, i); const s = []; for (const r of a) s.push({ mmsi: !r.mmsi || isNaN(r.mmsi) ? null : Number(r.mmsi), name: r.name, callSign: r.callsign, imo: !r.imo || isNaN(r.imo) ? null : Number(r.imo), score: r._score }); return s.sort((r, d) => d.score - r.score), s; } async trajectory(e, n, o, i, a = !0, s = {}) { var m, c, f; const r = await this.realTimePosition(e, s); let d = p(n); const u = p(o), h = p(); if (a) { let y = u.diff(d, "d", !0); y < 0 ? d = u.clone().subtract(40, "d") : y < 30 ? d.subtract(10, "d") : y < 60 ? d.subtract(5, "d") : d = u.clone().subtract(80, "d"), y = h.diff(u, "d", !0), u.add(y > 10 ? 240 : y * 24, "h"); } const l = { searchParams: { endtime: u.utcOffset("+8:00").format("YYYY-MM-DD HH:mm:ss"), starttime: d.utcOffset("+8:00").format("YYYY-MM-DD HH:mm:ss"), mmsi: e, usertoken: this.token } }, b = "https://api.hifleet.com/position/trajectory/nocompressed/withstatic/token", v = await J.get(b, l).json(); g == null || g.info("[%s] fetch trajectory from: %s - %j", s.requestId, b, l); let E; v && (E = ((c = (m = v.ships) == null ? void 0 : m.offors) == null ? void 0 : c.ship) || [], E.length || g == null || g.warn("[%s] fetch trajectory failed: %j", s.requestId, v)); const k = []; let I = -1; const w = p(`${(f = E == null ? void 0 : E[0]) == null ? void 0 : f.ti} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"); for (const y of E) { for (const O in y) !isNaN(y[O]) && Number(y[O]) !== 1 / 0 && (y[O] = Number(y[O])); const S = p(`${y.ti} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"); y.status = y.sp > 4 ? 0 : 1; const { labelEn: M, labelCn: j } = this.parseStatus(y.status), N = { mmsi: y.m, name: y.n, imo: r == null ? void 0 : r.imo, lat: y.la, lng: y.lo, draught: y.draught, sog: y.sp, cog: y.co, hdg: y.hdg, positionTime: S.unix(), utc: S.utc().format(), status: y.status, labelCn: j, labelEn: M, method: "trajectory", vendor: "hifleet" }, D = Math.floor(S.diff(w, "minute", !0) / (i || 1)); D !== I && (I = D, k.push(N)); } return k; } } class Nt extends nt { constructor(e) { super(); K(this, "token"); this.token = e; } async realTimePosition(e, n = {}) { const o = { searchParams: { id: e, k: this.token, enc: 1 } }, i = "https://api.shipxy.com/apicall/GetSingleShip", a = await J.get(i, o).json(); if (g == null || g.info("[%s] fetch realtime position from: %s - %j", n.requestId, i, o), (a == null ? void 0 : a.status) !== 0) return a; const s = a.data[0]; for (const l in s) !isNaN(s[l]) && Number(s[l]) !== 1 / 0 && (s[l] = Number(s[l])); const { labelCn: r, labelEn: d } = await this.parseStatus(s.navistat), u = p.unix(s.lasttime); return { mmsi: s.ShipID, name: s.name, imo: s.imo, callSign: s.callsign, lat: Math.round(s.lat / 1e6 * 1e5) / 1e5, lng: Math.round(s.lon / 1e6 * 1e5) / 1e5, length: Math.round(s.length / 10 * 100) / 100, width: Math.round(s.width / 10 * 100) / 100, draught: Math.round(s.draught / 1e3 * 100) / 100, sog: Math.round(s.sog * 3600 / 1e3 / 1852 * 100) / 100, cog: Math.round(s.cog / 100 * 100) / 100, hdg: Math.round(s.hdg / 100 * 100) / 100, rot: Math.round(s.rot / 100 * 100) / 100, positionTime: s.lasttime, utc: u.utc().format(), status: s.navistat, labelEn: d, labelCn: r, method: "position", vendor: "shipxy" }; } async trajectory(e, n, o, i, a = !0, s = {}) { var w; const r = await this.realTimePosition(e, s), d = p(n), u = p(o), h = "https://api.shipxy.com/apicall/GetShipTrack", l = { searchParams: { id: e, k: this.token, enc: 1, cut: 0, btm: d.unix(), etm: u.unix() } }, b = await J.get(h, l).json(); if (g == null || g.info("[%s] fetch trajectory from: %s - %j", s.requestId, h, l), (b == null ? void 0 : b.status) !== 0) return b; const v = b == null ? void 0 : b.points, E = [], k = p.unix((w = v[0]) == null ? void 0 : w.utc); let I = -1; for (const m of v) { const c = p.unix(m.utc), f = { imo: r == null ? void 0 : r.imo, mmsi: e, sog: Math.round(m.sog * 3600 / 1e3 / 1852 * 100) / 100, cog: Math.round(m.cog / 100 * 100) / 100, lat: Math.round(m.lat / 1e6 * 1e5) / 1e5, lng: Math.round(m.lon / 1e6 * 1e5) / 1e5, positionTime: c.unix(), utc: c.utc().format(), method: "trajectory", vendor: "shipxy" }, y = Math.floor(c.diff(k, "minute", !0) / (i || 1)); y !== I && (I = y, E.push(f)); } return E; } } class Dt extends nt { constructor(e) { super(); K(this, "token"); this.token = e; } async getShipId(e, n = {}) { const o = { headers: { appKey: this.token }, json: { mmsiList: e } }, i = "https://api3.myships.com/sp/ships/getShipIdByMMSI", a = await J.post(i, o).json(); return g == null || g.info("[%s] fetch ship id from: %s - %j", n.requestId, i, o), a.code !== "0" ? a : a.data[0].shipId; } async getShipInfo(e, n = {}) { const o = { headers: { appKey: this.token }, json: { shipId: e } }, i = "https://api3.myships.com/sp/ships/aissta", a = await J.post(i, o).json(); if (g == null || g.info("[%s] fetch ship info from: %s - %j", n.requestId, i, o), a.code !== "0") return a; const s = a.data; let r = s.imo; return e === "407170" && (r = "9198379", g == null || g.warn("[%s] ship(%s) imo error: %s, should be %s", n.requestId, e, s.imo, r)), { mmsi: s.mmsi, name: s.shipnameEn, imo: r, callSign: s.callSign, length: s.length, width: s.breadth, draught: (s.draught || 100) / 10 }; } async realTimePosition(e, n = {}) { const o = await this.getShipId(e, n), i = await this.getShipInfo(o, n), a = { headers: { appKey: this.token }, json: { shipId: o } }, s = "https://api3.myships.com/sp/ships/position/latest", r = await J.post(s, a).json(); g == null || g.info("[%s] fetch realtime position from: %s - %j", n.requestId, s, a); const d = r.data[0]; for (const v in d) !isNaN(d[v]) && Number(d[v]) !== 1 / 0 && (d[v] = Number(d[v])); const { labelCn: u, labelEn: h } = await this.parseStatus(d.aisNavStatus), l = p.unix(d.posTime); return { ...i, mmsi: e, lat: Math.round(d.lat / 1e4 / 60 * 1e5) / 1e5, lng: Math.round(d.lon / 1e4 / 60 * 1e5) / 1e5, sog: Math.round(d.sog / 10 * 100) / 100, cog: Math.round(d.cog / 10 * 100) / 100, hdg: Math.round(d.heading * 100) / 100, rot: Math.round(d.rot * 100) / 100, positionTime: d.posTime, utc: l.utc().format(), status: d.aisNavStatus, labelEn: h, labelCn: u, method: "position", vendor: "myship" }; } async trajectory(e, n, o, i, a = !0, s = {}) { const r = p(n), d = p(o), u = await this.getShipId(e), h = await this.getShipInfo(u), l = []; for (; d.diff(r, "day", !0) > 30; ) await this.trajectoryIn30Day(u, r.unix(), r.add(30, "day").unix(), h, e, i, l); return await this.trajectoryIn30Day(u, r.unix(), d.unix(), h, e, i, l), l; } async trajectoryIn30Day(e, n, o, i, a, s, r, d = {}) { var k; const u = { headers: { appKey: this.token }, json: { shipId: e, startTime: n, endTime: o } }, h = "https://api3.myships.com/sp/ships/position/history", l = await J.post(h, u).json(); if (g == null || g.info("[%s] fetch trajectory from: %s - %j", d.requestId, h, u), l.code !== "0") return g == null || g.warn("[%s] invoke myship trajectory failed: %j", d.requestId, l), l; const b = l.data; for (const I in b) !isNaN(b[I]) && Number(b[I]) !== 1 / 0 && (b[I] = Number(b[I])); const v = p.unix((k = b[0]) == null ? void 0 : k.posTime); let E = -1; for (const I of b) { const w = p.unix(I.posTime), m = { imo: i == null ? void 0 : i.imo, mmsi: a, lat: Math.round(I.lat / 1e4 / 60 * 1e5) / 1e5, lng: Math.round(I.lon / 1e4 / 60 * 1e5) / 1e5, sog: Math.round(I.sog / 10 * 100) / 100, cog: Math.round(I.cog / 10 * 100) / 100, hdg: Math.round(I.heading * 100) / 100, rot: Math.round(I.rot * 100) / 100, positionTime: w.unix(), utc: w.utc().format(), method: "trajectory", vendor: "myship" }, c = Math.floor(w.diff(v, "minute", !0) / (s || 1)); c !== E && (E = c, r.push(m)); } return r; } } let _; try { _ = lt.getLogger("vessel"); } catch { } finally { } var Mt = /* @__PURE__ */ ((x) => (x.NOTICE = "NOTICE", x.WARN = "WARN", x.HEAVY = "HEAVY", x.SEVERE = "SEVERE", x.ERROR = "ERROR", x.FATAL = "FATAL", x))(Mt || {}); class gt { /** * 解析告警规则, 多规则场景 * @param rule * e.g.1 [any;[[>,maxCP,WARN,0,TotCons_VLSFO]];[[!==,0,ERROR,0,TotCons_VLSFO]]] * e.g.2 [[>,0,HEAVY,Number.MAX_VALUE],[>,0,SEVERE,Number.MAX_VALUE]] * * @param options */ parsePrinciple(t, e = {}) { var s, r, d; _ == null || _.debug("[%s] parse rule: %s", e.requestId, t); const n = new RegExp("(?<=\\[)(.+)(?=])", "g"), o = t.match(n) ? (s = t.match(n)) == null ? void 0 : s[0] : void 0, i = o == null ? void 0 : o.split(";"); if (!i) return; const a = {}; for (let u = 0; u < (i == null ? void 0 : i.length); u++) { const h = (d = (r = i[u].match(n)) == null ? void 0 : r[0]) == null ? void 0 : d.split("],"); if (u === 0 && !h) a.scope = i[0]; else if (h) for (let l = 0, b = h.length; l < b; l++) { const v = this.parseRule(h[l]); v && (a[v.level] ? v.key ? a[v.level][v == null ? void 0 : v.key] = v : a[v.level] = v : v.key ? a[v.level] = { [v == null ? void 0 : v.key]: v } : a[v.level] = v); } } return a; } /** * 解析单一告警规则 * e.g.1 [>,maxCP,WARN,0,TotCons_VLSFO] * @param rule * @param options */ parseRule(t, e = {}) { var a; _ == null || _.debug("[%s] parse rule: %s", e.requestId, t), t = t.startsWith("[") ? t : `[${t}`, t = t.endsWith("]") ? t : `${t}]`; const n = new RegExp("(?<=\\[)(.+?)(?=])", "g"), o = (a = t == null ? void 0 : t.match(n)) == null ? void 0 : a[0], i = o == null ? void 0 : o.split(","); if (i) { let s = i[3] === "Number.MAX_VALUE" ? 100 : Number(i[3]); return s = isNaN(s) ? 1 : s, { operator: i[0], number: Number.isNaN(Number(i[1])) ? i[1] : Number(i[1]), level: i[2], time: s, key: i[4] }; } } /** * 检查航路点天气 * @param sample 航路点 * @param principle 告警规则 * @param options */ checkWeather(t, e, n = {}) { var v, E, k, I, w, m, c, f, y, S, M, j, N, D, O; let o = 0, i = 0, a = 0, s = 0; const r = Math.round(((E = (v = e == null ? void 0 : e.SEVERE) == null ? void 0 : v.sigWave) == null ? void 0 : E.number) * 1.6 * 100) / 100, d = (I = (k = e == null ? void 0 : e.SEVERE) == null ? void 0 : k.sigWave) == null ? void 0 : I.number, u = (m = (w = e == null ? void 0 : e.HEAVY) == null ? void 0 : w.sigWave) == null ? void 0 : m.number, h = Math.round((((f = (c = e == null ? void 0 : e.SEVERE) == null ? void 0 : c.wind) == null ? void 0 : f.number) + 2) * 100) / 100, l = (S = (y = e == null ? void 0 : e.SEVERE) == null ? void 0 : y.wind) == null ? void 0 : S.number, b = (j = (M = e == null ? void 0 : e.HEAVY) == null ? void 0 : M.wind) == null ? void 0 : j.number; for (let W = 0; W < (t == null ? void 0 : t.length); W++) { const F = t[W], Y = (D = (N = F == null ? void 0 : F.meteo) == null ? void 0 : N.wave) == null ? void 0 : D.sig, A = (O = F == null ? void 0 : F.meteo) == null ? void 0 : O.wind, B = W ? p(F.eta).diff(p(t[W - 1].eta), "hour", !0) : 0; s = B > s ? B : s, _ == null || _.debug("[%s] check sig.wave: %j", n.requestId, { ...Y, dgThd4Wv: r, svThd4Wv: d, hvThd4Wv: u }), (Y == null ? void 0 : Y.height) >= r ? F.isDangerous = !0 : (Y == null ? void 0 : Y.height) >= d ? F.isSevere = !0 : (Y == null ? void 0 : Y.height) >= u && (F.isHeavy = !0), _ == null || _.debug("[%s] check wind: %j", n.requestId, { ...A, dgThd4Wd: h, svThd4Wd: l, hvThd4Wd: b }), (A == null ? void 0 : A.scale) >= h ? (F.isDangerous = !0, delete F.isSevere, delete F.isHeavy) : (A == null ? void 0 : A.scale) > l ? (F.isDangerous || (F.isSevere = !0), delete F.isHeavy) : (A == null ? void 0 : A.scale) === b && !F.isDangerous && !F.isSevere && (F.isHeavy = !0), o += F.isDangerous ? B : 0, i += F.isSevere ? B : 0, a += F.isHeavy ? B : 0; } return o = Math.round(o * 100) / 100, i = Math.round(i * 100) / 100, a = Math.round(a * 100) / 100, s = Math.round(s), { sample: t, dangerous: o, severe: i, heavy: a, step: s < 3 ? 3 : s, wind: { dgThd4Wd: h, svThd4Wd: l, hvThd4Wd: b }, sig: { dgThd4Wv: r, svThd4Wv: d, hvThd4Wv: u } }; } } const At = new gt(); let C; try { C = lt.getLogger("vessel"); } catch { } finally { } const bt = new yt("", !0); var vt = /* @__PURE__ */ ((x) => (x.common = "common", x.container = "container", x.tugs = "tugs", x))(vt || {}), wt = /* @__PURE__ */ ((x) => (x.Ballast = "Ballast", x.Laden = "Laden", x))(wt || {}), It = /* @__PURE__ */ ((x) => (x.Cp = "CP", x.Perf = "Basis", x.Instruct = "Other", x))(It || {}); class L { /** * @see https://baike.baidu.com/item/%E6%96%B9%E5%BD%A2%E7%B3%BB%E6%95%B0/4965568?fr=aladdin * 方形系数(block coefficient) * 是指与基平面相平行的任一水线面以下的船型排水体积与对应的船长、型宽 * 和平均吃水的乘积所表示的长方体体积之比 * 方块系数介于 0.55 - 0.85 * @param displacement 排水 m^3 * @param length 水线长 m * @param breadth 水线宽 m * @param draught 吃水 m * @return [0.55, 0.85] */ static blockCoefficient(t, e, n, o) { let i = Math.round(t / (e * n * o) * 100) / 100; i = i < 0.55 ? 0.55 : i > 0.85 ? 0.85 : i; const a = [0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85], s = a.map((r) => Math.abs(r - i)); return a[s.indexOf(Math.min(...s))]; } /** * @see https://baike.baidu.com/item/%E5%BC%97%E5%8A%B3%E5%BE%B7%E6%95%B0/228891?fromModule=search-result_lemma-recommend * 弗劳德数 (froude number) * 流体力学中表征流体惯性力和重力相对大小的一个无量纲参数,记为Fr。它表示惯性力和重力量级的比,即:Fr=sqrt(U²/(gL)) [6] , * 式中U为物体运动速度,g为重力加速度;L为物体的特征长度 * 佛勞德數介乎 0.05 - 0.30 * @param speed 速度 m/s * @param length 船长 m * @param g 重力加速度 9.80 m/s^2 * @return [0.05, 0.30] */ static froudeNumber(t, e, n = 9.8) { t = t || 10 * 1852 / 3600; let o = Math.round(Math.sqrt(t * t / (n * e)) * 100) / 100; return o = o < 0.05 ? 0.05 : o > 0.3 ? 0.3 : o, o; } /** * 失速修正系數 * @param bc 方形系数 [0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85] * @param fr 弗劳德数 [0.05 - 0.30] * @param loadCondition * @private */ static amendFactor(t, e, n) { const o = { 0.55: [1.7, -1.4, -7.4], 0.6: [2.2, -2.5, -9.7], 0.65: [2.6, -3.7, -11.6], 0.7: [3.1, -5.3, -12.4], 0.75: [2.4, -10.6, -9.5], 0.8: [2.6, -13.1, -15.1], 0.85: [3.1, -18.7, 28] }; let a = { 0.55: [1.7, -1.4, -7.4], 0.6: [2.2, -2.5, -9.7], 0.65: [2.6, -3.7, -11.6], 0.7: [3.1, -5.3, -12.4], 0.75: [2.6, -12.5, -13.5], 0.8: [3, -16.3, -21.6], 0.85: [3.4, -20.9, 31.8] }[t]; return n === "Laden" && (a = o[t]), a[0] + a[1] * e + a[2] * Math.pow(e, 2); } /** * 失速方向因子 * 頂浪(不規則 波)、頂風 (0°): 2 * 𝐶𝜇 = 2 * 艏斜浪(不規 則波)、前迎風 (30°~60°): 2 𝐶𝜇 = 1.7 −0.03(Bn − 4)^2 * 橫浪(不規則 波)、橫風 (60°~150°): 2 𝐶𝜇 = 0.9 −0.06(Bn − 6)^2 * 順浪(不規則 波)、順風 (150°~180°): 2 𝐶𝜇 = 0.4 −0.03(Bn− 8)^2 * * @param beta 航向与风浪的夹角 * @param bn * @private */ static directionFactor(t, e = 0) { let n; return t > 30 && t <= 60 ? n = (1.7 - 0.03 * Math.pow(e - 4, 2)) / 2 : t > 60 && t <= 150 ? n = (0.9 - 0.06 * Math.pow(e - 6, 2)) / 2 : t > 150 && t <= 180 ? n = (0.4 - 0.03 * Math.pow(e - 8, 2)) / 2 : n = 1, Math.round(n * 1e5) / 1e5; } /** * 失速船型因子 * 集装箱(普通裝載狀態): 0.7Bn + Bn^6.5/(22 * ∇^(2/3)) * 散货等(满载): 0.5Bn + Bn^6.5 2 /(2.7 * ∇^(2/3)) * 散货等(压载): 0.7Bn + Bn^6.5 2 /(2.7 * ∇^(2/3)) * @param displacement 排水量,m^3 * @param loadCondition * @param tag * // @param bn * @param kts * @private */ static vesselTagFactor(t, e, n, o) { let i; return n === "container" ? i = 0.7 * o / 2 + Math.pow(o, 3) / (22 * Math.pow(t, 2 / 3)) : e === "Ballast" ? i = 0.7 * o / 2 + Math.pow(o, 3) / (2.7 * Math.pow(t, 2 / 3)) : i = 0.5 * o / 2 + Math.pow(o, 3) / (2.7 * Math.pow(t, 2 / 3)), i; } /** * 浪高影响因子 * @param ht 浪高,单位m * @param beta 夹角 * @param draught 吃水 * @param options * @private */ static waveHeightFactor(t, e, n = 10, o = {}) { t = t < 3 ? t * 0.7 : t, t = t < 0 ? 0.2 : t, t = t > 6 ? t - 0.9 * (t - 6) : t, t = t > 9 ? 9 : t; const i = Math.max(n > 10 ? n - 10 : 2.8); let a; return e > 30 && e <= 60 ? a = -0.6 : e > 60 && e <= 90 ? a = -0.5 : e > 90 && e <= 120 ? a = t < i ? 0.2 : -0.3 : e > 120 && e <= 150 ? a = t < i ? 0.25 : -0.2 : e > 150 && e <= 180 ? a = t < i ? 0.3 : -0.1 : a = -0.7, C == null || C.info("[%s] calculate wave height factor with ht(%d), beta(%d), draught(%d) and factor(%d)", o.requestId, t, e, n, a), Math.round(a * (0.144 * Math.pow(t, 2) + 0.378 * t) * 1e4) / 1e4; } /** * 组装船舶运行参数 * @param vessel 船舶基础档案 * @param loadCondition 装载状态 * @param speed 速度,kts * @param bearing 方位角 * @private */ static assembleProperties(t, e, n, o) { var l; const i = t.lbp ?? t.length ?? t.lengthOverall ?? 198.9642, a = t.draught ?? 8, s = t.breadthMoulded ?? t.breadth ?? t.breadthExtreme ?? 32.4572, r = t.deadweight ?? 67035.7773, d = ((l = t == null ? void 0 : t.type) == null ? void 0 : l.toLowerCase()) || "common"; return { tag: d.indexOf("container") > -1 ? "container" : d.indexOf("tugs") > -1 ? "tugs" : "common", lbp: i, loadCondition: e, draught: a, breadthMoulded: s, // 排水量(吨)= 载重量(吨)/ 1.025 + 吃水(米)× 船舶型宽(米)× 船舶型长(米)× 0.7 // 其中,1.025是指海水的密度,吨是指公吨,吃水是指船舶的最大吃水深度。船舶型宽是指船舶的最大型宽,船舶型长是指船舶的设计型长。上述公式是针对常规船舶适用的,不同类型的船舶可能会有一些差异。 displacement: Math.round((r / 1.025 + a * s * i * 0.7) * 1e4) / 1e4, // 换算为m/s speed: Math.round((n ?? 14.1382) * 1852 / 3600 * 1e4) / 1e4, bearing: o || 90 }; } /** * 船舶特定时间的瞬时失速样本 * @param props 船舶属性 * @param coordinate {lat: 纬度, lng: 经度, velocity: 速度(kts, 可选,指定速度) } * @param eta 位置时间 * @param source * @param role 1: 船东, 2: 租家, 0: 未知 * @param useMeteo true 启用气象分析 * @param useRouteParam true 启用设置速度 * @param options */ static async speedLoseAt(t, e, n, o = "", i = 2, a = !0, s = !1, r = {}) { let d; if (e.velocity && s && (t.speed = z.roundPrecision(e.velocity * 1852 / 3600, 6)), a) { let u; try { o = (o == null ? void 0 : o.toUpperCase()) === "CMEMS" ? "ECMWF" : o, o = (o == null ? void 0 : o.toUpperCase()) === "METEO2" ? "best_match" : o; const { weatherModels: k, marineModels: I } = await ut.autoPickMeteoModel(o), w = n.utc().format(), m = await bt.spotForecast(e.lat, e.lng, w, !1, !1, !0, { ...r, pastDays: 1, forecastDays: 2, weatherModels: k, marineModels: I }), [c] = ut.pickHourly(m, w); u = ut.toLegacy(c); } catch (k) { C.warn("[%s] meteo2 spot(%j) forecast failed: %s", r.requestId, { ...e, eta: n.utc().format(), source: o }, k); } let h = L.currentFactor(t.bearing, u == null ? void 0 : u.current, i, r), l = L.weatherFactor(t, u, h, r); const b = t.speed * 1.943844; b + h + l <= 0 && (C.warn( "[%s] v0(%d) is less then factor(%d) = wxFactor(%d) + cFactor(%d), scale factor with 0.6", r.requestId, b, l + h, l, h ), h = Math.round(h * 0.6 * 1e3) / 1e3, l = Math.round(l * 0.6 * 1e3) / 1e3); const v = l <= (r.minV0Factor || -1) ? Math.max(r.maxV0Factor || -1.5, -1 * l * (l / 1.2)) : 0; C == null || C.info("[%s] calculate speed lose with v0Factor(%d), cFactor(%d) and wxFactor(%d)", r.requestId, v, h, l), l += v; let E = Math.round((b + h + l) * 100) / 100; E = E <= 0 ? 1 : E, d = { meteo: { ...u }, wxFactor: l, v0Factor: v, cFactor: h, speed: e.velocity && s ? e.velocity : E, eta: n.utc().format(), etd: n.utc().format() }; } else d = { wxFactor: 0, v0Factor: 0, cFactor: 0, speed: e.velocity && s ? e.velocity : Math.round(t.speed * 1.943844 * 100) / 100, eta: n.utc().format(), etd: n.utc().format() }; return delete e.meteo, delete e.wxFactor, delete e.cFactor, delete e.speed, delete e.etd, { ...d, ...e }; } /** * 基于步长计算失速样本 * @param props 船舶属性(失速模型依赖) * @param etd 出发时间 * @param etd4Day 前行过程中动态计算加24小时的天(etd + 24 * n) * @param hours 前行的步长(小时) * @param distanceFromStart 与最开始起点的距离 * @param keypoints 剩下的航路点 * @param source 气象数据源: CMEMS / GFS * @param useMeteo true 启用气象分析 * @param useRouteParam true 启用航线上设置的参数 { suspend: 停留时长(小时), velocity: 速度(kts)} * @param options * @private */ static async speedLoseInHoursStep(t, e, n, o, i, a, s = "", r = !0, d = !1, u = {}) { e.utc(); const h = e.clone().add(14, "days"), l = [], b = [], v = []; let E = 0, k = 0, I, w; for (let m = 0; m < a.length - 1; m++) { let c = a[m]; c.distanceFromStart = Math.round((i + k) * 1e3) / 1e3; const f = a[m + 1]; if (t.bearing = R.calculateBearing(c, f, !f.gcToPrevious), c.bearing = t.bearing, c.suspend && d) { c.eta = c.eta || e.utc().format(), c.elapsed = c.elapsed ?? 0; const M = c.suspend - c.elapsed; if (o - E > M) o = o - E - M, e.add(M, "hour"), c.elapsed = c.suspend; else { const j = o - E; c.elapsed += j, e.add(j, "hour"), o = 0; } if (C == null || C.info(`[%s] suspend ${c.elapsed} hours at %j, and remain ${o} hours need to go...`, u.requestId, c), o === 0) return c.distanceFromPrevious = k, { etd: e, from: w || c, to: c, next: a.filter((j) => j), wps: l, days: b, all: v }; } else c.suspend = 0; r = e.isAfter(h) ? !1 : r, c = await L.speedLoseAt(t, c, e, s, 0, r, d, u), v.push(c), w = w || c, c.important && l.push(c), e.isSameOrAfter(n) && (b.push(c), n.add(24, "hour")); const y = R.calculateDistance(c, f, !f.gcToPrevious); let S = Math.round(y / w.speed * 1e5) / 1e5; if (E + S < o) { if (E += S, e.add(S, "hour"), delete a[m], C == null || C.debug( `[%s] go to %j from %j with ${y}nm, and cost ${S} hours`, u.requestId, { lat: f.lat, lng: f.lng }, { lat: w.lat, lng: w.lng, etd: w.etd } ), k += y, a.filter((M) => M).length <= 1) { I = f, I.eta = e.utc().format(), I.distanceFromPrevious = y, I.distanceFromStart = Math.round((i + k) * 1e4) / 1e4, l.push(I), v.push(I), delete a[m + 1]; break; } } else { S = o - E, e.add(S, "hour"); const M = z.roundPrecision(w.speed * S, 5); I = R.calculateCoordinate(c, t.bearing, M, "nauticalmiles", !f.gcToPrevious), I.eta = e.utc().format(), a[m] = I, C == null || C.debug( `[%s] go to %j from %j with ${M}nm, and cost ${S} hours`, u.requestId, { lat: I.lat, lng: I.lng }, { lat: c.lat, lng: c.lng, etd: c.etd } ), k += M, I.distanceFromPrevious = Math.round(k * 1e4) / 1e4, I.distanceFromStart = Math.round((i + k) * 1e4) / 1e4; break; } } return { etd: e, from: w, to: I, next: a.filter((m) => m), wps: l, days: b, all: v }; } /** * 洋流影响因子 * @param bearing 船舶航行方位角 * @param current 洋流要素 * @param role 1: 船东, 2: 租家, 0: 未知 * @param options */ static currentFactor(t, e, n = 0, o = {}) { const i = R.includedAngle(t ?? 0, (e == null ? void 0 : e.degree) ?? 0) / 180 * Math.PI; let a; return Math.abs(i) === Math.PI / 2 && (a = 0), a = ((e == null ? void 0 : e.kts) || 0) * Math.cos(i), n & 2 ? a = Math.ceil(a * 100) / 100 : n & 1 ? a = Math.floor(a * 100) / 100 : a = Math.round(a * 100) / 100, C == null || C.info("[%s] calculate current factor with %j", o.requestId, { beta: i, current: e, factor: a, role: n }), Math.abs(a) > 5 ? 0 : a; } /** * 风浪影响因子 * @param props 船舶档案 * @param wwc 气象要素 * @param cFactor 洋流因子 * @param options */ static weatherFactor(t, e, n = 0, o = {}) { var v, E, k, I, w, m, c; C == null || C.info("[%s] calculate weather factor via: %j", o.requestId, { ...t, ...e }), t.displacement = t.displacement || t.lbp * t.breadthMoulded * t.draught * 0.7 * 1.025, t.speed = t.speed || 10 * 1852 / 3600, t.bearing = t.bearing ?? 0; const i = L.blockCoefficient(t.displacement, t.lbp, t.breadthMoulded, t.draught), a = z.roundPrecision(n * 1852 / 3600, 6), s = L.froudeNumber(t.speed - a, t.lbp), r = L.amendFactor(i, s, t.loadCondition); let d = R.includedAngle(t.bearing ?? 0, (v = e == null ? void 0 : e.wind) == null ? void 0 : v.degree); const u = L.directionFactor(d, (E = e == null ? void 0 : e.wind) == null ? void 0 : E.scale), h = L.vesselTagFactor(t.displacement, t.loadCondition, t.tag, ((k = e == null ? void 0 : e.wind) == null ? void 0 : k.kts) || 10); let l = u * r * h / 100 * (t.speed - a); l = Math.round(l * 1.943844 * 1e4) / 1e4 * -1, t.tag === "tugs" && Math.abs(l) > 1 && (l = l / (Math.abs(Math.round(l)) + 1)), C == null || C.info("[%s] calculate wind wx factor: %d", o.requestId, l), d = R.includedAngle(t.bearing, (w = (I = e == null ? void 0 : e.wave) == null ? void 0 : I.sig) == null ? void 0 : w.degree); const b = L.waveHeightFactor(((c = (m = e == null ? void 0 : e.wave) == null ? void 0 : m.sig) == null ? void 0 : c.height) ?? 1, d, t.draught, o); return C == null || C.info("[%s] calculate wave wx factor: %d", o.requestId, b), l = Math.abs(l) > Math.abs(b) ? l : 0.4 * l + 0.6 * b, C == null || C.info("[%s] calculate finial weather factor: %d = (0.4 * wf) + (0.6 * hf)", o.requestId, l), l = Math.abs(l) > 3 ? 3 * (Math.abs(l) / l) + Math.abs(l) / l * (Math.abs(l) - 2) * 0.1 : l, Math.round((l || 0) * 100) / 100; } /** * 以12小时级别去掉重复的days * @param days * @param interval 12 hours */ static async reduceDays(t, e = 12 * 60 * 60) { return t = t == null ? void 0 : t.reduce((n, o) => (o.positionTime || (o.positionTime = p.utc(o.etd || o.eta).unix()), n.some((i) => Math.floor(i.positionTime / e) === Math.floor(o.positionTime / e)) || n.push(o), n), []), t; } /** * 以分钟级别去掉重复的wps * @param wps * @param interval 1 minute */ static async reduceWPS(t, e = 60) { return t = t == null ? void 0 : t.reduce((n, o) => (n.some((i) => Math.floor(p(i.etd).unix() / e) === Math.floor(p(o.etd).unix() / e)) || n.push(o), n), []), t; } /** * 全程失速分析(走完航程) * @param from 起点 {lng, lat} * @param etd 出发时间,YYYY-MM-DDTHH:mm:ssZ * @param vessel 船舶属性 @see VesselAssemble, 注意吃水为船舶实时吃水,非档案中的固定吃水,一般可以从ais获取; 如获取不得,用档案吃水代替 * @param cp { loadCondition, speed } LoadCondition: Ballast or Laden, speed(kts) * @param lane 航线 { points: { route, waypoints }} * @param source 气象数据源,GFS or CMEMES, 默认CMEMS * @param stepHrs 样本步长, 0表示动态计算(6 or 3 hrs) * @param useMeteo true 启用气象分析 * @param useRouteParam * @param options * withDT: true, desulfurization tower 脱硫塔 */ static async analyseInstant(t, e, n, o, i, a = "", s = 0, r = !0, d = !1, u = {}) { var X, Q, Z, $, tt, et; const h = p().valueOf(); t.lng = z.convertToStdLng(t.lng); const { route: l, waypoints: b } = i.points, v = R.calculateSubRoute(t, l); if (((X = v[0]) == null ? void 0 : X.length) <= 1) return; const { v0: E, label: k } = t.sog ? { v0: t.sog, label: t.label || "Other" /* Instruct */ } : { v0: o.speed, label: "CP" /* Cp */ }, I = L.assembleProperties(n, o.loadCondition, E, 0), w = b.length ? R.calculateSubWaypoints(t, b) : []; w.forEach((P) => P.important = !0); const m = { from: { ...t }, route: v, waypoints: w, v0: E, label: k }, c = { hours: [], days: [], wps: [], all: [] }; if (!s) { const V = R.calculateRouteDistance(v) / o.speed; V < 3 ? s = 1 : V <= 72 ? s = 3 : s = 6; } let f = R.simplifyRouteToCoordinates(v, w, 0), y = 0, S = 0, M = 0, j = 0; e = p(e).utc(); const N = e.clone(); for (; f.length > 0; ) { const P = s - e.hour() % s, V = Math.ceil(e.clone().add(P, "h").set({ minute: 0, second: 0, millisecond: 0 }).diff(e, "h", !0) * 1e4) / 1e4, T = await L.speedLoseInHoursStep( I, e, N, V, y, f, a, r, d, u ); if (c.all.push(...T.all), (Q = T.from) != null && Q.speed && (c.hours.push(T.from), c.wps.push(...T.wps), c.days.push(...T.days)), f = T == null ? void 0 : T.next, !f.length) { const H = await L.speedLoseAt(I, T.to, p(T.to.eta), a, 0, r, d, u); H.bearing = I.bearing, c.hours[c.hours.length - 1] = H, c.all[c.all.length - 1] = H, c.wps[c.wps.length - 1] = H; } y += Math.round((((Z = T == null ? void 0 : T.to) == null ? void 0 : Z.distanceFromPrevious) ?? 0) * 1e4) / 1e4; } const D = c.hours; for (let P = 0; P < D.length - 1; P++) { const V = p(D[P + 1].eta).diff(D[P].etd, "hour", !0) || 1; S += (D[P].wxFactor || 0) * V, M += (D[P].cFactor || 0) * V, j += V; } const O = D.reduce((P, V) => P + (V.suspend || 0), 0); ($ = c.wps) == null || $.forEach((P, V) => { P.positionTime = p.utc(P.etd || P.eta).unix(); const T = c.wps[V - 1]; if (T) { const H = P.distanceFromStart - T.distanceFromStart, q = p(P.eta || P.etd).diff(p(T.etd || T.eta), "h", !0); P.avgSpd = Math.round(H / q * 100) / 100; const at = R.calculateBearing(T, P); P.avgBearing = at, T.bearing = at; } }), c.wps = await L.reduceWPS(c.wps), c.days = await L.reduceDays(c.days), c.all = (tt = c.all) == null ? void 0 : tt.reduce((P, V) => (V.positionTime = p.utc(V.etd || V.eta).unix(), P.some((T) => Math.round(T.positionTime / 60) === Math.round(V.positionTime / 60)) || P.push(V), P), []), m.sample = c; const W = c.hours.at(0), F = c.hours.at(-1); m.distance = Math.round(F.distanceFromStart * 1e3) / 1e3, m.etd = p(W.eta).utc().format(), m.eta = p(F.eta).utc().format(), j = j <= 0 ? s : j, m.wxFactor = Math.round(S / j * 1e3) / 1e3, m.cFactor = Math.round(M / j * 1e3) / 1e3, m.avgSpeed = Math.round(F.distanceFromStart / j * 1e3) / 1e3, m.totalHrs = Math.round(j * 1e3) / 1e3, m.suspend = Math.round(O * 1e3) / 1e3; const Y = z.roundPrecision(o.dgo / 24 * O, 3), { distanceInECA: A, hoursInECA: B, totalDgoConsInECA: ot, totalFoConsInECA: U, eca: it } = await this.calculateECA(m, o, u), G = z.roundPrecision(o.fo / 24 * (j - B) + U, 3), rt = z.roundPrecision(o.dgo / 24 * j + Y, 3); m.extend = { eca: it, distanceInECA: A, hoursInECA: B, totalDgoConsInECA: ot, totalFoConsInECA: U, totalDgoConsInSuspend: Y }, m.totalFoCons = G < 0 ? 0 : G, m.totalDgoCons = rt; const st = p().valueOf() - h, dt = ((et = c == null ? void 0 : c.hours) == null ? void 0 : et.length) || 1; return C == null || C.info("[%s] each hour-sample speed analyse cost: (%d / %d = %d) ms", u == null ? void 0 : u.requestId, st, dt, Math.round(st / dt * 1e3) / 1e3), m; } /** * 分段失速分析(最多走hours 小时) * @param from 起点 {lng, lat} * @param etd 出发时间,YYYY-MM-DDTHH:mm:ssZ * @param threshed 单次所走上限时间 * @param vessel 船舶属性 @see VesselAssemble, 注意吃水为船舶实时吃水,非档案中的固定吃水,一般可以从ais获取; 如获取不得,用档案吃水代替 * @param cp { loadCondition, speed } LoadCondition: Ballast or Laden, speed(kts) * @param route 航路[[[lng, lat]]] * @param waypoints * @param source 气象数据源,GFS or CMEMES, 默认CMEMS * @param stepHrs * @param useMeteo true 启用气象分析 * @param useRouteParam * @param options */ static async analyseInstantWithThreshed(t, e, n, o, i, a, s, r = "", d = 3, u = !0, h = !1, l = {}) { var Z, $, tt, et, P, V; const b = p().valueOf(); t.lng = z.convertToStdLng(t.lng); const { v0: v, label: E } = t.sog ? { v0: t.sog, label: t.label || "Other" /* Instruct */ } : { v0: i.speed, label: "CP" /* Cp */ }, k = L.assembleProperties(o, i.loadCondition, v, 0), I = R.calculateSubRoute(t, a); if (((Z = I[0]) == null ? void 0 : Z.length) <= 1) return; const w = s.length ? R.calculateSubWaypoints(t, s) : []; w.forEach((T) => T.important = !0); let m = R.simplifyRouteToCoordinates(I, w, 0), c = 0, f = 0, y = 0, S = 0; const M = { hours: [], wps: [], days: [], all: [] }; e = p(e).utc(); const j = e.clone(); for (; m.len