@idm-plugin/vessel
Version:
idm plugin for vessel
1,238 lines • 57 kB
JavaScript
var ht = Object.defineProperty;
var lt = (E, t, e) => t in E ? ht(E, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : E[t] = e;
var K = (E, t, e) => (lt(E, typeof t != "symbol" ? t + "" : t, e), e);
import B from "got";
import ct from "@log4js-node/log4js-api";
import b from "moment";
import { LngLatHelper as J, LaneHelper as W } from "@idm-plugin/geo2";
import { MeteoHelper2 as ft } from "@idm-plugin/meteo2";
import { Meteo2Assist as rt } from "@idm-plugin/meteo";
let v;
try {
v = ct.getLogger("vessel");
} catch {
} finally {
}
class st {
/**
* 解析AIS状态码
* @param status
*/
parseStatus(t) {
let e, a;
switch (t) {
case 0:
e = "在航(主机推动)", a = "Underway Using Engine";
break;
case 1:
e = "锚泊", a = "Anchored";
break;
case 2:
e = "失控", a = "Not operated";
break;
case 3:
e = "操纵受限", a = "Limited airworthiness";
break;
case 4:
e = "吃水受限", a = "Limited by ship's draft";
break;
case 5:
e = "靠泊", a = "Mooring";
break;
case 6:
e = "搁浅", a = "Stranded";
break;
case 7:
e = "捕捞作业", a = "Engaged in fishing";
break;
case 8:
e = "靠帆船提供动力", a = "Sailing";
break;
default:
e = "未定义", a = "Undefined";
}
return { labelCn: e, labelEn: a };
}
}
class xt extends st {
constructor(e, a) {
super();
K(this, "clientId");
K(this, "clientSecret");
K(this, "token");
this.clientId = e, this.clientSecret = a;
}
async authToken(e = {}) {
const a = "https://svc.data.myvessel.cn/ada/oauth/token", i = {
searchParams: {
client_id: this.clientId,
client_secret: this.clientSecret,
grant_type: "client_credentials"
}
}, n = await B.post(a, i).json();
v == null || v.info("[%s] fetch access token from: %s - %j", e.requestId, a, n), n.error || (this.token = {
accessToken: n.access_token,
tokenType: n.token_type,
expiresIn: n.expires_in,
scope: n.scope,
jti: n.jti,
issuedAt: b().utc().format()
});
}
async realTimePosition(e, a = {}) {
var d, r, m;
(!this.token || b().diff(b(this.token.issuedAt), "seconds") > ((d = this.token) == null ? void 0 : d.expiresIn) - 300) && await this.authToken(a);
const i = "https://svc.data.myvessel.cn/sdc/v1/vessels/status/location/unit", n = {
headers: {
Authorization: `${(r = this.token) == null ? void 0 : r.tokenType} ${(m = this.token) == null ? void 0 : m.accessToken}`
},
searchParams: { mmsi: e }
};
v == null || v.info("[%s] fetch realtime position from: %s - %j", a.requestId, i, n);
const o = await B.get(i, n).json();
if (o.code)
return v == null || v.warn("[%s] fetch realtime position failed: %j", a.requestId, i, { message: o.message, status: o.status, code: o.code }), o;
const s = o.data;
for (const M in s)
!isNaN(s[M]) && Number(s[M]) !== 1 / 0 && (s[M] = Number(s[M]));
if (s) {
const M = b(`${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) ? b.utc(s.eta).format() : void 0,
destination: s.dest,
positionTime: M.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: M.utc().format()
};
} else
return {};
}
async trajectory(e, a, i, n, o = !0, s = {}) {
(!this.token || b().diff(b(this.token.issuedAt), "seconds") > this.token.expiresIn - 300) && await this.authToken(s);
const d = await this.realTimePosition(e, s), r = b(a), m = b(i), M = [];
for (; m.diff(r, "day", !0) > 30; )
await this.trajectoryIn30Day(e, r, r.clone().add(30, "day"), d, n, M, s), r.add(30, "day");
return await this.trajectoryIn30Day(e, r, m, d, n, M, s), M;
}
async trajectoryIn30Day(e, a, i, n, o, s, d = {}) {
var f, C, S, p, w;
const r = "https://svc.data.myvessel.cn/sdc/v1/vessels/status/track", m = {
headers: {
Authorization: `${(f = this.token) == null ? void 0 : f.tokenType} ${(C = this.token) == null ? void 0 : C.accessToken}`
},
json: {
mmsi: e,
startTime: a.utcOffset(8).format("YYYY-MM-DD HH:mm:ss"),
endTime: i.utcOffset(8).format("YYYY-MM-DD HH:mm:ss")
}
};
v == null || v.info("[%s] fetch trajectory from: %s - %j", d.requestId, r, m);
const M = await B.post(r, m).json();
if (M.code)
return v == null || v.warn("[%s] fetch trajectory failed: %j", d.requestId, r, { message: M.message, status: M.status, code: M.code }), M;
let l = -1;
const g = b(`${(p = (S = M.data) == null ? void 0 : S[0]) == null ? void 0 : p.postime} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00");
return (w = M.data) == null || w.forEach((c) => {
for (const Y in c)
!isNaN(c[Y]) && Number(c[Y]) !== 1 / 0 && (c[Y] = Number(c[Y]));
const y = b(`${c.postime} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00"), h = c.status, { labelCn: u, labelEn: I } = this.parseStatus(h), k = {
mmsi: c.mmsi,
imo: n == null ? void 0 : n.imo,
lat: c.lat,
lng: c.lon,
sog: c.sog,
cog: c.cog,
hdg: c.hdg,
draught: c.draught,
status: h,
eta: /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(c.eta) ? b(`${c.eta} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00").utc().format() : void 0,
destination: c.dest,
positionTime: y.unix(),
labelCn: u,
labelEn: I,
method: "trajectory",
vendor: "myVessel",
utc: y.utc().format()
}, D = Math.floor(y.diff(g, "minute", !0) / (o || 1));
D !== l && (l = D, s.push(k));
}), s;
}
}
class Et extends st {
constructor(e) {
super();
K(this, "token");
this.token = e;
}
async realTimePosition(e, a = {}) {
const i = "https://api.hifleet.com/position/position/get/token", n = {
searchParams: {
mmsi: e,
usertoken: this.token
}
}, o = await B.post(i, n).json();
v == null || v.info("[%s] fetch realtime position from: %s - %j", a.requestId, i, n);
const s = o == null ? void 0 : o.list;
if (!s)
return v == null || v.warn("[%s] fetch realtime position failed: %j", a.requestId, i, o), o;
for (const g in s)
!isNaN(s[g]) && Number(s[g]) !== 1 / 0 && (s[g] = Number(s[g]));
s.status = s.sp > 3 ? 0 : 1;
const d = s.status, { labelCn: r, labelEn: m } = this.parseStatus(d), M = b(`${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) ? b.utc(s.eta).format() : void 0,
destination: s.destination,
vesselType: s.type,
dwt: s.dwt,
build: s.buildyear,
flag: s.fn,
positionTime: M.unix(),
utc: M.utc().format(),
status: d,
labelCn: r,
labelEn: m,
method: "position",
vendor: "hifleet"
};
}
async search(e, a = {}) {
let i = "https://www.hifleet.com/hifleetapi/searchVesselOL.do";
const n = {
searchParams: {
keyword: e
},
headers: {
Referer: "https://www.hifleet.com",
Origin: "https://www.hifleet.com",
Host: "www.hifleet.com"
}
};
let o = await B.post(i, n).json();
v == null || v.info("[%s] fetch vessel props from: %s - %j", a.requestId, i, n), o instanceof Array && (o = o[0]);
for (const d in o)
!isNaN(o[d]) && Number(o[d]) !== 1 / 0 && (o[d] = Number(o[d]));
const s = {
mmsi: o.m,
name: o.n,
imo: o.i,
callSign: o.c,
length: o.l,
breadth: o.b,
draught: o.dr,
type: o.t
};
return i = "https://www.hifleet.com/hifleetapi/sameShipSearch.do", o = await B.post(i, n).json(), v == null || v.info("[%s] search vessel dead weight from: %s - %j", a.requestId, i, n), o instanceof Array && (o = o[0]), o && (s.deadweight = Number(o.dwt)), s;
}
async suggest(e, a = {}) {
const i = "https://www.hifleet.com/hifleetapi/getShipSuggest.do", n = {
searchParams: {
q: e
},
headers: {
Referer: "https://www.hifleet.com",
Origin: "https://www.hifleet.com",
Host: "www.hifleet.com"
}
}, o = await B.post(i, n).json();
v == null || v.info("[%s] suggest vessel props from: %s - %j", a.requestId, i, n);
const s = [];
for (const d of o)
s.push({
mmsi: !d.mmsi || isNaN(d.mmsi) ? null : Number(d.mmsi),
name: d.name,
callSign: d.callsign,
imo: !d.imo || isNaN(d.imo) ? null : Number(d.imo),
score: d._score
});
return s.sort((d, r) => r.score - d.score), s;
}
async trajectory(e, a, i, n, o = !0, s = {}) {
var c, y, h;
const d = await this.realTimePosition(e, s);
let r = b(a);
const m = b(i), M = b();
if (o) {
let u = m.diff(r, "d", !0);
u < 0 ? r = m.clone().subtract(40, "d") : u < 30 ? r.subtract(10, "d") : u < 60 ? r.subtract(5, "d") : r = m.clone().subtract(80, "d"), u = M.diff(m, "d", !0), m.add(u > 10 ? 240 : u * 24, "h");
}
const l = {
searchParams: {
endtime: m.utcOffset("+8:00").format("YYYY-MM-DD HH:mm:ss"),
starttime: r.utcOffset("+8:00").format("YYYY-MM-DD HH:mm:ss"),
mmsi: e,
usertoken: this.token
}
}, g = "https://api.hifleet.com/position/trajectory/nocompressed/withstatic/token", f = await B.get(g, l).json();
v == null || v.info("[%s] fetch trajectory from: %s - %j", s.requestId, g, l);
let C;
f && (C = ((y = (c = f.ships) == null ? void 0 : c.offors) == null ? void 0 : y.ship) || [], C.length || v == null || v.warn("[%s] fetch trajectory failed: %j", s.requestId, f));
const S = [];
let p = -1;
const w = b(`${(h = C == null ? void 0 : C[0]) == null ? void 0 : h.ti} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00");
for (const u of C) {
for (const P in u)
!isNaN(u[P]) && Number(u[P]) !== 1 / 0 && (u[P] = Number(u[P]));
const I = b(`${u.ti} +08:00`, "YYYY-MM-DD HH:mm:ss +08:00");
u.status = u.sp > 4 ? 0 : 1;
const { labelEn: k, labelCn: D } = this.parseStatus(u.status), Y = {
mmsi: u.m,
name: u.n,
imo: d == null ? void 0 : d.imo,
lat: u.la,
lng: u.lo,
draught: u.draught,
sog: u.sp,
cog: u.co,
hdg: u.hdg,
positionTime: I.unix(),
utc: I.utc().format(),
status: u.status,
labelCn: D,
labelEn: k,
method: "trajectory",
vendor: "hifleet"
}, j = Math.floor(I.diff(w, "minute", !0) / (n || 1));
j !== p && (p = j, S.push(Y));
}
return S;
}
}
class jt extends st {
constructor(e) {
super();
K(this, "token");
this.token = e;
}
async realTimePosition(e, a = {}) {
const i = {
searchParams: {
id: e,
k: this.token,
enc: 1
}
}, n = "https://api.shipxy.com/apicall/GetSingleShip", o = await B.get(n, i).json();
if (v == null || v.info("[%s] fetch realtime position from: %s - %j", a.requestId, n, i), (o == null ? void 0 : o.status) !== 0)
return o;
const s = o.data[0];
for (const l in s)
!isNaN(s[l]) && Number(s[l]) !== 1 / 0 && (s[l] = Number(s[l]));
const { labelCn: d, labelEn: r } = await this.parseStatus(s.navistat), m = b.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: m.utc().format(),
status: s.navistat,
labelEn: r,
labelCn: d,
method: "position",
vendor: "shipxy"
};
}
async trajectory(e, a, i, n, o = !0, s = {}) {
var w;
const d = await this.realTimePosition(e, s), r = b(a), m = b(i), M = "https://api.shipxy.com/apicall/GetShipTrack", l = {
searchParams: {
id: e,
k: this.token,
enc: 1,
cut: 0,
btm: r.unix(),
etm: m.unix()
}
}, g = await B.get(M, l).json();
if (v == null || v.info("[%s] fetch trajectory from: %s - %j", s.requestId, M, l), (g == null ? void 0 : g.status) !== 0)
return g;
const f = g == null ? void 0 : g.points, C = [], S = b.unix((w = f[0]) == null ? void 0 : w.utc);
let p = -1;
for (const c of f) {
const y = b.unix(c.utc), h = {
imo: d == null ? void 0 : d.imo,
mmsi: e,
sog: Math.round(c.sog * 3600 / 1e3 / 1852 * 100) / 100,
cog: Math.round(c.cog / 100 * 100) / 100,
lat: Math.round(c.lat / 1e6 * 1e5) / 1e5,
lng: Math.round(c.lon / 1e6 * 1e5) / 1e5,
positionTime: y.unix(),
utc: y.utc().format(),
method: "trajectory",
vendor: "shipxy"
}, u = Math.floor(y.diff(S, "minute", !0) / (n || 1));
u !== p && (p = u, C.push(h));
}
return C;
}
}
class Nt extends st {
constructor(e) {
super();
K(this, "token");
this.token = e;
}
async getShipId(e, a = {}) {
const i = {
headers: {
appKey: this.token
},
json: {
mmsiList: e
}
}, n = "https://api3.myships.com/sp/ships/getShipIdByMMSI", o = await B.post(n, i).json();
return v == null || v.info("[%s] fetch ship id from: %s - %j", a.requestId, n, i), o.code !== "0" ? o : o.data[0].shipId;
}
async getShipInfo(e, a = {}) {
const i = {
headers: {
appKey: this.token
},
json: {
shipId: e
}
}, n = "https://api3.myships.com/sp/ships/aissta", o = await B.post(n, i).json();
if (v == null || v.info("[%s] fetch ship info from: %s - %j", a.requestId, n, i), o.code !== "0")
return o;
const s = o.data;
let d = s.imo;
return e === "407170" && (d = "9198379", v == null || v.warn("[%s] ship(%s) imo error: %s, should be %s", a.requestId, e, s.imo, d)), {
mmsi: s.mmsi,
name: s.shipnameEn,
imo: d,
callSign: s.callSign,
length: s.length,
width: s.breadth,
draught: (s.draught || 100) / 10
};
}
async realTimePosition(e, a = {}) {
const i = await this.getShipId(e, a), n = await this.getShipInfo(i, a), o = {
headers: {
appKey: this.token
},
json: {
shipId: i
}
}, s = "https://api3.myships.com/sp/ships/position/latest", d = await B.post(s, o).json();
v == null || v.info("[%s] fetch realtime position from: %s - %j", a.requestId, s, o);
const r = d.data[0];
for (const f in r)
!isNaN(r[f]) && Number(r[f]) !== 1 / 0 && (r[f] = Number(r[f]));
const { labelCn: m, labelEn: M } = await this.parseStatus(r.aisNavStatus), l = b.unix(r.posTime);
return {
...n,
mmsi: e,
lat: Math.round(r.lat / 1e4 / 60 * 1e5) / 1e5,
lng: Math.round(r.lon / 1e4 / 60 * 1e5) / 1e5,
sog: Math.round(r.sog / 10 * 100) / 100,
cog: Math.round(r.cog / 10 * 100) / 100,
hdg: Math.round(r.heading * 100) / 100,
rot: Math.round(r.rot * 100) / 100,
positionTime: r.posTime,
utc: l.utc().format(),
status: r.aisNavStatus,
labelEn: M,
labelCn: m,
method: "position",
vendor: "myship"
};
}
async trajectory(e, a, i, n, o = !0, s = {}) {
const d = b(a), r = b(i), m = await this.getShipId(e), M = await this.getShipInfo(m), l = [];
for (; r.diff(d, "day", !0) > 30; )
await this.trajectoryIn30Day(m, d.unix(), d.add(30, "day").unix(), M, e, n, l);
return await this.trajectoryIn30Day(m, d.unix(), r.unix(), M, e, n, l), l;
}
async trajectoryIn30Day(e, a, i, n, o, s, d, r = {}) {
var S;
const m = {
headers: {
appKey: this.token
},
json: {
shipId: e,
startTime: a,
endTime: i
}
}, M = "https://api3.myships.com/sp/ships/position/history", l = await B.post(M, m).json();
if (v == null || v.info("[%s] fetch trajectory from: %s - %j", r.requestId, M, m), l.code !== "0")
return v == null || v.warn("[%s] invoke myship trajectory failed: %j", r.requestId, l), l;
const g = l.data;
for (const p in g)
!isNaN(g[p]) && Number(g[p]) !== 1 / 0 && (g[p] = Number(g[p]));
const f = b.unix((S = g[0]) == null ? void 0 : S.posTime);
let C = -1;
for (const p of g) {
const w = b.unix(p.posTime), c = {
imo: n == null ? void 0 : n.imo,
mmsi: o,
lat: Math.round(p.lat / 1e4 / 60 * 1e5) / 1e5,
lng: Math.round(p.lon / 1e4 / 60 * 1e5) / 1e5,
sog: Math.round(p.sog / 10 * 100) / 100,
cog: Math.round(p.cog / 10 * 100) / 100,
hdg: Math.round(p.heading * 100) / 100,
rot: Math.round(p.rot * 100) / 100,
positionTime: w.unix(),
utc: w.utc().format(),
method: "trajectory",
vendor: "myship"
}, y = Math.floor(w.diff(f, "minute", !0) / (s || 1));
y !== C && (C = y, d.push(c));
}
return d;
}
}
let _;
try {
_ = ct.getLogger("vessel");
} catch {
} finally {
}
var mt = /* @__PURE__ */ ((E) => (E.NOTICE = "NOTICE", E.WARN = "WARN", E.HEAVY = "HEAVY", E.SEVERE = "SEVERE", E.ERROR = "ERROR", E.FATAL = "FATAL", E))(mt || {});
class yt {
/**
* 解析告警规则, 多规则场景
* @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, d, r;
_ == null || _.info("[%s] parse rule: %s", e.requestId, t);
const a = new RegExp("(?<=\\[)(.+)(?=])", "g"), i = t.match(a) ? (s = t.match(a)) == null ? void 0 : s[0] : void 0, n = i == null ? void 0 : i.split(";");
if (!n)
return;
const o = {};
for (let m = 0; m < (n == null ? void 0 : n.length); m++) {
const M = (r = (d = n[m].match(a)) == null ? void 0 : d[0]) == null ? void 0 : r.split("],");
if (m === 0 && !M)
o.scope = n[0];
else if (M)
for (let l = 0, g = M.length; l < g; l++) {
const f = this.parseRule(M[l]);
f && (o[f.level] ? f.key ? o[f.level][f == null ? void 0 : f.key] = f : o[f.level] = f : f.key ? o[f.level] = { [f == null ? void 0 : f.key]: f } : o[f.level] = f);
}
}
return o;
}
/**
* 解析单一告警规则
* e.g.1 [>,maxCP,WARN,0,TotCons_VLSFO]
* @param rule
* @param options
*/
parseRule(t, e = {}) {
var o;
_ == null || _.debug("[%s] parse rule: %s", e.requestId, t), t = t.startsWith("[") ? t : `[${t}`, t = t.endsWith("]") ? t : `${t}]`;
const a = new RegExp("(?<=\\[)(.+?)(?=])", "g"), i = (o = t == null ? void 0 : t.match(a)) == null ? void 0 : o[0], n = i == null ? void 0 : i.split(",");
if (n)
return {
operator: n[0],
number: Number.isNaN(Number(n[1])) ? n[1] : Number(n[1]),
level: n[2],
time: Number(n[3]),
key: n[4]
};
}
/**
* 检查航路点天气
* @param sample 航路点
* @param principle 告警规则
* @param options
*/
checkWeather(t, e, a = {}) {
var f, C, S, p, w, c, y, h, u, I, k, D, Y, j, P;
let i = 0, n = 0, o = 0, s = 0;
const d = Math.round(((C = (f = e == null ? void 0 : e.SEVERE) == null ? void 0 : f.sigWave) == null ? void 0 : C.number) * 1.6 * 100) / 100, r = (p = (S = e == null ? void 0 : e.SEVERE) == null ? void 0 : S.sigWave) == null ? void 0 : p.number, m = (c = (w = e == null ? void 0 : e.HEAVY) == null ? void 0 : w.sigWave) == null ? void 0 : c.number, M = Math.round((((h = (y = e == null ? void 0 : e.SEVERE) == null ? void 0 : y.wind) == null ? void 0 : h.number) + 2) * 100) / 100, l = (I = (u = e == null ? void 0 : e.SEVERE) == null ? void 0 : u.wind) == null ? void 0 : I.number, g = (D = (k = e == null ? void 0 : e.HEAVY) == null ? void 0 : k.wind) == null ? void 0 : D.number;
for (let T = 0; T < (t == null ? void 0 : t.length); T++) {
const N = t[T], A = (j = (Y = N == null ? void 0 : N.meteo) == null ? void 0 : Y.wave) == null ? void 0 : j.sig, R = (P = N == null ? void 0 : N.meteo) == null ? void 0 : P.wind, U = T ? b(N.eta).diff(b(t[T - 1].eta), "hour", !0) : 0;
s = U > s ? U : s, _ == null || _.debug("[%s] check sig.wave: %j", a.requestId, { ...A, dgThd4Wv: d, svThd4Wv: r, hvThd4Wv: m }), (A == null ? void 0 : A.height) >= d ? N.isDangerous = !0 : (A == null ? void 0 : A.height) >= r ? N.isSevere = !0 : (A == null ? void 0 : A.height) >= m && (N.isHeavy = !0), _ == null || _.debug("[%s] check wind: %j", a.requestId, { ...R, dgThd4Wd: M, svThd4Wd: l, hvThd4Wd: g }), (R == null ? void 0 : R.scale) >= M ? (N.isDangerous = !0, delete N.isSevere, delete N.isHeavy) : (R == null ? void 0 : R.scale) > l ? (N.isDangerous || (N.isSevere = !0), delete N.isHeavy) : (R == null ? void 0 : R.scale) === g && !N.isDangerous && !N.isSevere && (N.isHeavy = !0), i += N.isDangerous ? U : 0, n += N.isSevere ? U : 0, o += N.isHeavy ? U : 0;
}
return i = Math.round(i * 100) / 100, n = Math.round(n * 100) / 100, o = Math.round(o * 100) / 100, s = Math.round(s), { sample: t, dangerous: i, severe: n, heavy: o, step: s < 3 ? 3 : s, wind: { dgThd4Wd: M, svThd4Wd: l, hvThd4Wd: g }, sig: { dgThd4Wv: d, svThd4Wv: r, hvThd4Wv: m } };
}
}
const Tt = new yt();
let F;
try {
F = ct.getLogger("vessel");
} catch {
} finally {
}
const Mt = new ft("", !0);
var gt = /* @__PURE__ */ ((E) => (E.common = "common", E.container = "container", E.tugs = "tugs", E))(gt || {}), bt = /* @__PURE__ */ ((E) => (E.Ballast = "Ballast", E.Laden = "Laden", E))(bt || {}), wt = /* @__PURE__ */ ((E) => (E.Cp = "CP", E.Perf = "Basis", E.Instruct = "Other", E))(wt || {});
class O {
/**
* @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, a, i) {
let n = Math.round(t / (e * a * i) * 100) / 100;
n = n < 0.55 ? 0.55 : n > 0.85 ? 0.85 : n;
const o = [0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85], s = o.map((d) => Math.abs(d - n));
return o[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, a = 9.8) {
let i = Math.round(Math.sqrt(t * t / (a * e)) * 100) / 100;
return i = i < 0.05 ? 0.05 : i > 0.3 ? 0.3 : i, i;
}
/**
* 失速修正系數
* @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, a) {
const i = {
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 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.6, -12.5, -13.5],
0.8: [3, -16.3, -21.6],
0.85: [3.4, -20.9, 31.8]
}[t];
return a === "Laden" && (o = i[t]), o[0] + o[1] * e + o[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 a;
return t > 30 && t <= 60 ? a = (1.7 - 0.03 * Math.pow(e - 4, 2)) / 2 : t > 60 && t <= 150 ? a = (0.9 - 0.06 * Math.pow(e - 6, 2)) / 2 : t > 150 && t <= 180 ? a = (0.4 - 0.03 * Math.pow(e - 8, 2)) / 2 : a = 1, Math.round(a * 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
* @private
*/
static vesselTagFactor(t, e, a, i = 0) {
i = i > 6 ? i - 0.9 * (i - 6) : i;
let n;
return a === "container" ? n = 0.7 * i + Math.pow(i, 6.5) / (22 * Math.pow(t, 2 / 3)) : e === "Ballast" ? n = 0.7 * i + Math.pow(i, 6.5) / (2.7 * Math.pow(t, 2 / 3)) : n = 0.5 * i + Math.pow(i, 6.5) / (2.7 * Math.pow(t, 2 / 3)), n;
}
/**
* 浪高影响因子
* @param ht 浪高,单位m
* @private
*/
static waveHeightFactor(t, e) {
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;
let a;
return e > 30 && e <= 60 ? a = -0.6 : e > 60 && e <= 90 ? a = -0.4 : e > 90 && e <= 120 ? a = t < 3 ? 0.4 : -0.3 : e > 120 && e <= 150 ? a = t < 3 ? 0.6 : -0.5 : e > 150 && e <= 180 ? a = t < 3 ? 0.7 : -0.6 : a = -0.7, Math.round(a * (0.102 * Math.pow(t, 2) + 0.178 * t) * 1e4) / 1e4;
}
/**
* 组装船舶运行参数
* @param vessel 船舶基础档案
* @param loadCondition 装载状态
* @param speed 速度,kts
* @param bearing 方位角
* @private
*/
static assembleProperties(t, e, a, i) {
var l;
const n = t.lbp ?? t.length ?? t.lengthOverall ?? 198.9642, o = t.draught ?? 8, s = t.breadthMoulded ?? t.breadth ?? t.breadthExtreme ?? 32.4572, d = t.deadweight ?? 67035.7773, r = ((l = t == null ? void 0 : t.type) == null ? void 0 : l.toLowerCase()) || "common";
return {
tag: r.indexOf("container") > -1 ? "container" : r.indexOf("tugs") > -1 ? "tugs" : "common",
lbp: n,
loadCondition: e,
draught: o,
breadthMoulded: s,
// 排水量(吨)= 载重量(吨)/ 1.025 + 吃水(米)× 船舶型宽(米)× 船舶型长(米)× 0.7
// 其中,1.025是指海水的密度,吨是指公吨,吃水是指船舶的最大吃水深度。船舶型宽是指船舶的最大型宽,船舶型长是指船舶的设计型长。上述公式是针对常规船舶适用的,不同类型的船舶可能会有一些差异。
displacement: Math.round((d / 1.025 + o * s * n * 0.7) * 1e4) / 1e4,
// 换算为m/s
speed: Math.round((a ?? 14.1382) * 1852 / 3600 * 1e4) / 1e4,
bearing: i || 90
};
}
/**
* 船舶特定时间的瞬时失速样本
* @param props 船舶属性
* @param coordinate {lat: 纬度, lng: 经度, velocity: 速度(kts, 可选,指定速度) }
* @param eta 位置时间
* @param source [CMEMS, GFS, Other]
* @param role 1: 船东, 2: 租家, 0: 未知
* @param useMeteo true 启用气象分析
* @param useRouteParam true 启用设置速度
*/
static async speedLoseAt(t, e, a, i = "", n = 2, o = !0, s = !1, d = {}) {
let r;
if (e.velocity && s && (t.speed = J.roundPrecision(e.velocity * 1852 / 3600, 6)), o) {
let m;
try {
i = (i == null ? void 0 : i.toUpperCase()) === "CMEMS" ? "ECMWF" : i, i = (i == null ? void 0 : i.toUpperCase()) === "METEO2" ? "best_match" : i;
const { weatherModels: f, marineModels: C } = await rt.autoPickMeteoModel(i), S = await Mt.spotForecast(e.lat, e.lng, a.utc().format(), !1, !1, !0, {
...d,
pastDays: 1,
forecastDays: 1,
weatherModels: f,
marineModels: C
}), [p] = rt.pickHourly(S, a);
m = rt.toLegacy(p);
} catch (f) {
F.warn("[%s] meteo2 spot(%j) forecast failed: %s", d.requestId, { ...e, eta: a.utc().format(), source: i }, f);
}
const M = O.weatherFactor(t, m), l = O.currentFactor(t.bearing, m == null ? void 0 : m.current, n), g = Math.round((t.speed * 1.943844 + M + l) * 100) / 100;
r = {
meteo: { ...m },
wxFactor: M,
cFactor: l,
speed: e.velocity && s ? e.velocity : g < 0 ? 1 : g,
eta: a.utc().format(),
etd: a.utc().format()
};
} else
r = {
wxFactor: 0,
cFactor: 0,
speed: e.velocity && s ? e.velocity : Math.round((t.speed * 1.943844 + 0 + 0) * 100) / 100,
eta: a.utc().format(),
etd: a.utc().format()
};
return delete e.meteo, delete e.wxFactor, delete e.cFactor, delete e.speed, delete e.etd, { ...r, ...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)}
* @private
*/
static async speedLoseInHoursStep(t, e, a, i, n, o, s = "", d = !0, r = !1, m = {}) {
e.utc();
const M = e.clone().add(14, "days"), l = [], g = [];
let f = 0, C = 0, S, p;
for (let w = 0; w < o.length - 1; w++) {
let c = o[w];
c.distanceFromStart = Math.round((n + C) * 1e3) / 1e3;
const y = o[w + 1];
if (t.bearing = W.calculateBearing(c, y, !y.gcToPrevious), c.bearing = t.bearing, c.suspend && r) {
c.eta = c.eta || e.utc().format(), c.elapsed = c.elapsed ?? 0;
const I = c.suspend - c.elapsed;
if (i - f > I)
i = i - f - I, e.add(I, "hour"), c.elapsed = c.suspend;
else {
const k = i - f;
c.elapsed += k, e.add(k, "hour"), i = 0;
}
if (F == null || F.info(`[%s] suspend ${c.elapsed} hours at %j, and remain ${i} hours need to go...`, m.requestId, c), i === 0)
return c.distanceFromPrevious = C, { etd: e, from: p || c, to: c, next: o.filter((k) => k), wps: l, days: g };
} else
c.suspend = 0;
d = e.isAfter(M) ? !1 : d, c = await O.speedLoseAt(t, c, e, s, 0, d, r, m), p = p || c, c.important && l.push(c), e.isSameOrAfter(a) && (g.push(c), a.add(24, "hour"));
const h = W.calculateDistance(c, y, !y.gcToPrevious);
let u = Math.round(h / p.speed * 1e5) / 1e5;
if (f + u < i) {
if (f += u, e.add(u, "hour"), delete o[w], F == null || F.debug(
`[%s] go to %j from %j with ${h}nm, and cost ${u} hours`,
m.requestId,
{ lat: y.lat, lng: y.lng },
{ lat: p.lat, lng: p.lng, etd: p.etd }
), C += h, o.filter((I) => I).length <= 1) {
S = y, S.eta = e.utc().format(), S.distanceFromPrevious = h, S.distanceFromStart = Math.round((n + C) * 1e4) / 1e4, l.push(S), delete o[w + 1];
break;
}
} else {
u = i - f, e.add(u, "hour");
const I = J.roundPrecision(p.speed * u, 5);
S = W.calculateCoordinate(c, t.bearing, I, "nauticalmiles", !y.gcToPrevious), S.eta = e.utc().format(), o[w] = S, F == null || F.debug(
`[%s] go to %j from %j with ${I}nm, and cost ${u} hours`,
m.requestId,
{ lat: S.lat, lng: S.lng },
{ lat: c.lat, lng: c.lng, etd: c.etd }
), C += I, S.distanceFromPrevious = Math.round(C * 1e4) / 1e4, S.distanceFromStart = Math.round((n + C) * 1e4) / 1e4;
break;
}
}
return { etd: e, from: p, to: S, next: o.filter((w) => w), wps: l, days: g };
}
/**
* 洋流影响因子
* @param bearing 船舶航行方位角
* @param current 洋流要素
* @param role 1: 船东, 2: 租家, 0: 未知
*/
static currentFactor(t, e, a = 0) {
const i = (t - (e == null ? void 0 : e.degree) || 0) / 180 * Math.PI;
if (Math.abs(i) === Math.PI / 2)
return 0;
let n = ((e == null ? void 0 : e.kts) || 0) * Math.cos(i);
return a & 2 ? n = Math.ceil(n * 100) / 100 : a & 1 ? n = Math.floor(n * 100) / 100 : n = Math.round(n * 100) / 100, Math.abs(n) > 5 ? 0 : n;
}
/**
* 风浪影响因子
* @param props 船舶档案
* @param wwc 气象要素
*/
static weatherFactor(t, e) {
var M, l, g, f, C, S, p;
F == null || F.debug("calculate weather factor via: %j", { ...t, ...e });
const a = O.blockCoefficient(t.displacement, t.lbp, t.breadthMoulded, t.draught), i = O.froudeNumber(t.speed, t.lbp), n = O.amendFactor(a, i, t.loadCondition);
let o = Math.abs(t.bearing % 360 - (((M = e == null ? void 0 : e.wind) == null ? void 0 : M.degree) % 360 || 0));
o = o > 180 ? 360 - o : o;
const s = O.directionFactor(o, (l = e == null ? void 0 : e.wind) == null ? void 0 : l.scale), d = O.vesselTagFactor(t.displacement, t.loadCondition, t.tag, (g = e == null ? void 0 : e.wind) == null ? void 0 : g.scale);
let r = s * n * d / 100 * t.speed;
r = Math.round(r * 1.943844 * 1e4) / 1e4 * -1, t.tag === "tugs" && Math.abs(r) > 1 && (r = r / (Math.abs(Math.round(r)) + 1)), F == null || F.debug("wind wx factor = %d", r), o = Math.abs(t.bearing % 360 - (((C = (f = e == null ? void 0 : e.wave) == null ? void 0 : f.sig) == null ? void 0 : C.degree) % 360 || 0));
const m = O.waveHeightFactor(((p = (S = e == null ? void 0 : e.wave) == null ? void 0 : S.sig) == null ? void 0 : p.height) ?? 1, o);
return F == null || F.debug("wave wx factor = %d", m), r = r * 0.3 + m * 0.7, F == null || F.debug("weather factor = %d", r), r = Math.abs(r) > 2 ? 2 * (Math.abs(r) / r) + Math.abs(r) / r * (Math.abs(r) - 2) * 0.1 : r, Math.round((r || 0) * 100) / 100;
}
/**
* 全程失速分析(走完航程)
* @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
*/
static async analyseInstant(t, e, a, i, n, o = "", s = 0, d = !0, r = !1, m = {}) {
var z, G, Q, X, Z, $;
const M = b().valueOf();
t.lng = J.convertToStdLng(t.lng);
const { route: l, waypoints: g } = n.points, f = W.calculateSubRoute(t, l);
if (((z = f[0]) == null ? void 0 : z.length) <= 1)
return;
const { v0: C, label: S } = t.sog ? {
v0: t.sog,
label: t.label || "Other"
/* Instruct */
} : {
v0: i.speed,
label: "CP"
/* Cp */
}, p = O.assembleProperties(a, i.loadCondition, C, 0), w = g.length ? W.calculateSubWaypoints(t, g) : [];
w.forEach((q) => q.important = !0);
const c = {
from: { ...t },
route: f,
waypoints: w,
v0: C,
label: S
}, y = {
hours: [],
days: [],
wps: []
};
s || (W.calculateRouteDistance(f) / i.speed <= 72 ? s = 3 : s = 6);
let h = W.simplifyRouteToCoordinates(f, w, 0), u = 0, I = 0, k = 0, D = 0;
e = b(e).utc();
const Y = e.clone();
for (; h.length > 0; ) {
const q = s - e.hour() % s, V = Math.ceil(e.clone().add(q, "h").set({ minute: 0, second: 0, millisecond: 0 }).diff(e, "h", !0) * 1e4) / 1e4, x = await O.speedLoseInHoursStep(
p,
e,
Y,
V,
u,
h,
o,
d,
r,
m
);
(G = x.from) != null && G.speed && (y.hours.push(x.from), y.wps.push(...x.wps), y.days.push(...x.days)), h = x == null ? void 0 : x.next, h.length || y.hours.push(x == null ? void 0 : x.to), u += Math.round((((Q = x == null ? void 0 : x.to) == null ? void 0 : Q.distanceFromPrevious) ?? 0) * 1e4) / 1e4;
}
const j = y.hours;
for (let q = 0; q < j.length - 1; q++) {
const V = b(j[q + 1].eta).diff(j[q].etd, "hour", !0) || 1;
I += (j[q].wxFactor || 0) * V, k += (j[q].cFactor || 0) * V, D += V;
}
(X = y.wps) == null || X.forEach((q, V) => {
q.positionTime = b.utc(q.etd || q.eta).unix();
const x = y.wps[V - 1];
if (x) {
const L = q.distanceFromStart - x.distanceFromStart, H = b(q.eta || q.etd).diff(b(x.etd || x.eta), "h", !0);
q.avgSpd = Math.round(L / H * 100) / 100;
const it = W.calculateBearing(x, q);
x.bearing = it;
}
}), y.wps = (Z = y.wps) == null ? void 0 : Z.reduce((q, V) => (q.some((x) => Math.round(x.positionTime / 60) === Math.round(V.positionTime / 60)) || q.push(V), q), []), c.sample = y;
const P = y.hours.at(0), T = y.hours.at(-1);
c.distance = Math.round(T.distanceFromStart * 1e3) / 1e3, c.etd = b(P.eta).utc().format(), c.eta = b(T.eta).utc().format(), c.wxFactor = Math.round(I / D * 1e3) / 1e3, c.cFactor = Math.round(k / D * 1e3) / 1e3, c.avgSpeed = Math.round(T.distanceFromStart / D * 1e3) / 1e3, c.totalHrs = Math.round(D * 1e3) / 1e3;
const { distanceInECA: N, hoursInECA: A, totalDgoConsInECA: R, eca: U } = await this.calculateECA(c, i, m), tt = J.roundPrecision(i.fo / 24 * (D - A), 3), at = J.roundPrecision(i.dgo / 24 * D, 3);
c.extend = {
eca: U,
distanceInECA: N,
hoursInECA: A,
totalDgoConsInECA: R
}, c.totalFoCons = tt < 0 ? 0 : tt, c.totalDgoCons = at;
const et = b().valueOf() - M, nt = (($ = y == null ? void 0 : y.hours) == null ? void 0 : $.length) || 1;
return F == null || F.info("[%s] each hour-sample speed analyse cost: (%d / %d = %d) ms", m == null ? void 0 : m.requestId, et, nt, Math.round(et / nt * 1e3) / 1e3), c;
}
/**
* 分段失速分析(最多走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 source 气象数据源,GFS or CMEMES, 默认CMEMS
* @param stepHrs
* @param useMeteo true 启用气象分析
* @param useRouteParam
*/
static async analyseInstantWithThreshed(t, e, a, i, n, o, s, d = "", r = 3, m = !0, M = !1, l = {}) {
var Q, X, Z, $, q, V;
const g = b().valueOf();
t.lng = J.convertToStdLng(t.lng);
const { v0: f, label: C } = t.sog ? {
v0: t.sog,
label: t.label || "Other"
/* Instruct */
} : {
v0: n.speed,
label: "CP"
/* Cp */
}, S = O.assembleProperties(i, n.loadCondition, f, 0), p = W.calculateSubRoute(t, o);
if (((Q = p[0]) == null ? void 0 : Q.length) <= 1)
return;
const w = s.length ? W.calculateSubWaypoints(t, s) : [];
w.forEach((x) => x.important = !0);
let c = W.simplifyRouteToCoordinates(p, w, 0), y = 0, h = 0, u = 0, I = 0;
const k = {
hours: [],
wps: [],
days: []
};
e = b(e).utc();
const D = e.clone();
for (; c.length > 0; ) {
const x = r - e.hour() % r;
let L = Math.ceil(e.clone().add(x, "h").set({ minute: 0, second: 0, millisecond: 0 }).diff(e, "h", !0) * 1e4) / 1e4;
L = e.clone().add(L, "h").isSameOrAfter(a) ? a.diff(e, "h", !0) * 1e4 / 1e4 : L;
const H = await O.speedLoseInHoursStep(S, e, D, L, y, c, d, m, M, l);
if ((X = H.from) != null && X.speed && (k.hours.push(H.from), H != null && H.wps && k.wps.push(...H.wps), k.days.push(...H.days)), c = H == null ? void 0 : H.next, c.length || k.hours.push(H == null ? void 0 : H.to), y += Math.round((((Z = H == null ? void 0 : H.to) == null ? void 0 : Z.distanceFromPrevious) ?? 0) * 1e4) / 1e4, !L)
break;
}
k.wps = ($ = k.wps) == null ? void 0 : $.reduce((x, L) => (x.some((H) => Math.round(b(H.etd).unix() / 60) === Math.round(b(L.etd).unix() / 60)) || x.push(L), x), []), (q = k.wps) == null || q.forEach((x, L) => {
const H = k.wps[L - 1];
if (H) {
const it = x.distanceFromStart - H.distanceFromStart, dt = b(x.eta || x.etd).diff(b(H.etd || H.eta), "h", !0);
x.avgSpd = Math.round(it / dt * 100) / 100;
const ut = W.calculateBearing(H, x);
H.bearing = ut;
}
});
const Y = k.hours;
for (let x = 0; x < Y.length - 1; x++) {
const L = b(Y[x + 1].eta).diff(Y[x].etd, "hour", !0);
h += Y[x].wxFactor * L, u += Y[x].cFactor * L, I += L;
}
const j = k.hours.at(0), P = k.hours.at(-1), T = await W.calculateRangeRoute(j, P, p), N = await W.calculateRangeWaypoints(j, P, p, w), A = {
sample: k,
distance: Math.round(((P == null ? void 0 : P.distanceFromStart) || 0) * 1e4) / 1e4,
// 注意,可能会在first节点Drift,所有采用eta做为初始出发时间
etd: b(j.eta).utc().format(),
eta: b(P == null ? void 0 : P.eta).utc().format(),
wxFactor: Math.round(h / I * 1e3) / 1e3,
cFactor: Math.round(u / I * 1e3) / 1e3,
avgSpeed: Math.round(((P == null ? void 0 : P.distanceFromStart) || 0) / I * 1e3) / 1e3,
totalHrs: Math.round(I * 1e3) / 1e3,
from: j,
to: P,
route: T,
waypoints: N,
v0: f,
label: C
}, { distanceInECA: R, hoursInECA: U, totalDgoConsInECA: tt, eca: at } = await this.calculateECA(A, n, l), ot = J.roundPrecision(n.fo / 24 * (I - U), 3), et = J.roundPrecision(n.dgo / 24 * I, 3);
A.extend = {
eca: at,
distanceInECA: R,
hoursInECA: U,
totalDgoConsInECA: tt
}, A.totalDgoCons = et, A.totalFoCons = ot < 0 ? 0 : ot;
const z = b().valueOf() - g, G = ((V = k == null ? void 0 : k.hours) == null ? void 0 : V.length) || 1;
return F == null || F.debug("[%s] each hour-sample speed analyse cost: (%d / %d = %d) ms", l == null ? void 0 : l.requestId, z, G, Math.round(z / G * 1e3) / 1e3), A;
}
/**
* 在指定航线条件下,基于多CP,动态计算最优成本(租金+油费)方案
* 1)首先分别计算单CP下的成本方案,基于成本排序,保留最省成本的两个方案,分别用 a, b 表示;
* 2) 选择步骤1)中a,b的CP,分别用 cpa, cpb 表示;
* 3) 基于当前有效天气预报时长(14天),按步长多次减半,分别用7,4,2,1天步长及cpa,cpb交替计算各种组合下的成本;
* 4)保留步骤3)中的最省成本的两个方案,并与步骤1)合并按成本排序,获取成本较优的三个方案;
* 5) 基于步骤4),同客户沟通后,最终基于时间和成本,选择最合适的方案;
*
*
* @param props 基础属性(位置,出发时间等)
* @param vessel 船舶档案(长、宽、吃水、船舶类型等)
* @param cps CP条款(装载状态,速度、油耗等)
* @param lane 基础航线(重要转向点)
* @param options
*/
static async analyseCost(t, e, a, i, n = {}) {
var p, w;
const o = b().valueOf(), s = [];
t.speedStep = t.speedStep || 3, t.alterStep = t.alterStep ?? 1;
const d = W.calculateRouteDistance(i.route);
let r = 0;
a.forEach((c) => {
const y = Math.ceil(d / c.speed / 24);
r = r < y ? y : r;
}), r = r * 1.3;
const m = b.utc(t.etd).add(r ?? 14, "day");
let M = 1;
for (const c of a) {
const y = JSON.parse(JSON.stringify(i.route)), h = JSON.parse(JSON.stringify(i.waypoints)), u = await O.analyseInstantWithThreshed(
{ lat: t.lat, lng: t.lng },
t.etd,
m,
e,
c,
y,
h,
t.meteoVendor,
t.speedStep,
t.useMeteo,
t.useRouteParam,
n
);
u && (await O.calculateCost(u, c, t, n), s.push(u), F == null || F.info("[%s][L%d-%d] analyse from %s to %s cost: %j", n.requestId, 1, M, t.etd, m.format(), {
cost: u.cost.total,
hire: u.cost.hire,
bunker: u.cost.bunker,
distance: u.distance,
hours: u.totalHrs,
cp: `${c.speed}/${c.fo}/${c.dgo}`
})), M++;
}
s.sort((c, y) => c.cost.total - y.cost.total);
const l = s.at(0), g = s.at(1), f = [];
if (f.push({ combined: !1, speeds: [l], cost: (p = l.cost) == null ? void 0 : p.total }), g) {
const c = l.cost.cp, y = g.cost.cp, h = b(l.eta), u = b(l.etd), I = h.diff(u, "days", !0);
let k = Math.ceil(I / 2);
k = k > 7 ? 7 : k < t.alterStep ? t.alterStep : k;
let D = 2, Y = { combined: !1, speeds: [g], cost: (w = g.cost) == null ? void 0 : w.total }, j;
for (; k >= t.alterStep; ) {
const P = await O.combinedAnalyse(t, e, m, [c, y], i, k, { ...n, level: D });
if (Y.cost > P.cost ? j ? (j == null ? void 0 : j.cost) > P.cost && (j = P) : (j = Y, Y = P) : (!j || (j == null ? void 0 : j.cost) > P.cost) && (j = P), k <= t.alterStep)
break;
k = Math.ceil(k / 2), D += 1;
}
f.push(Y), j && f.push(j);
}
const S = b().valueOf() - o;
return F == null || F.info("[%s] analyse elapsed: %d ms", n == null ? void 0 : n.requestId, S), f.sort((c, y) => c.cost - y.cost);
}
/**
* 按步长多次减半,分别用7,4,2,1天步长及cpa,cpb交替计算各种组合下的成本
* @param props 基础属性(位置,出发时间,日租金,FO/DO/GO单价等)
* @param vessel 船舶档案(长、宽、吃水、船舶类型等)
* @param cps CP条款(装载状态,速度、油耗等)
* @param max 最长分析未来14天
* @param lane 基础航线(重要转向点)
* @param step 步长,7,4,2,1
* @param options
*/
static async combinedAnalyse(t, e, a, i, n, o, s = {}) {
s.counter = 1, F == null || F.info("[%s][L%d] analyse with alternate cp in every %d days", s.requestId, s.level, o);
const d = await O.alternateAnalyse(t, e, a, i, 0, n, o, s), r = d.reduce((y, h) => y + h.cost.total, 0), m = d.reduce((y, h) => y + h.cost.hire, 0), M = d.reduce((y, h) => y + h.cost.bunker, 0), l = d.reduce((y, h) => y + h.distance, 0), g = d.reduce((y, h) => y + h.totalHrs, 0);
F == null || F.info("[%s][L%d] cost with cpa/cpb turn: %j", s.requestId, s.level, {
cost: r,
hire: m,
bunker: M,
distance: l,
hours: g
});
const f = await O.alternateAnalyse(t, e, a, i, 1, n, o, s), C = f.reduce((y, h) => y + h.cost.total, 0), S = f.reduce((y, h) => y + h.cost.hire, 0), p = f.reduce((y, h) => y + h.cost.bunker, 0), w = f.reduce((y, h) => y + h.distance, 0), c = f.reduce((y, h) => y + h.totalHrs, 0);
return F == null || F.info("[%s][L%d] cost with cpb/cpa turn: %j", s.requestId, s.level, {
cost: C,
hire: S,
bunker: p,
distance: w,
hours: c
}), r < C ? { combined: !0, cost: Math.round(r * 1e3) / 1e3, speeds: d, step: o } : { combined: !0, cost: Math.round(C * 1e3) / 1e3, speeds: f, step: o };
}
/**
* 基于cp索引,交替计算指定步长下的成本
* @param props 基础属性(位置,出发时间等)
* @param vessel 船舶档案(长、宽、吃水、船舶类型等)
* @param cps CP条款(装载状态,速度、油耗等)
* @param cpIndex cp索引
* @param max 最长分析未来14天
* @param lane 基础航线(重要转向点)
* @param step 步长,7,4,2,1
* @param options
*/
static async alternateAnalyse(t, e, a, i, n, o, s, d = {}) {
var l, g;
let r = b.utc(t.etd);
const m = { lat: t.lat, lng: t.lng }, M = [];
for (; r.isBefore(a); ) {
const f = r.clone().utc().add(s, "day"), C = JSON.parse(JSON.stringify(o.route)), S = JSON.parse(JSON.stringify(o.waypoints)), p = i[n], w = await O.analyseInstantWithThreshed(
m,
r.utc().format(),
f,
e,
p,
C,
S,
t.meteoVendor,
t.speedStep,
t.useMeteo,
t.useRouteParam,
d
);
w && (await O.calculateCost(w, p, t, d), F == null || F.info(
"[%s][L%d-%d] analyse from %s to %s cost: %j",
d.requestId,
d.level,
d.counter,
r.utc().format(),
f.utc().format(),
{
cost: w.cost.total,
hire: w.cost.hire,
bunker: w.cost.bunker,
distance: w.distance,
hours: w.totalHrs,
cp: `${p.speed}/${p.fo}/${p.dgo}`
}
)), d.counter = d.counter + 1;
const c = (g = (l = w == null ? void 0 : w.sample) == null ? void 0 : l.hours) == null ? void 0 : g.at(-1);
if (c)
m.lat = c.lat, m.lng = c.lng, r = b(c.eta), M.push(w), n = n ? 0 : 1;
else
break;
}
return M;
}
/**
* 计算Speed的cost
* @param speed
* @param cp
* @param props
* @param options
*/
static async calculateCost(t, e, a, i = {}) {
var n;
if (t) {
const o = (a.addComm || 0) >= 1 ? (a.addComm || 0) / 100 : a.addComm || 0, s = Math.round(t.totalHrs / 24 * (a.dailyHire || 0) * (1 - o) * 1e3) / 1e3, d = Math.round(t.totalFoCons * (a.priceFO || 0) * 1e3) / 1e3, r = Math.round((t.totalDgoCons + (((n = t.extend) == null ? void 0 : n.totalDgoConsInECA) || 0)) * (a.priceDGO || 0) * 1e3) / 1e3;
t.cost = {
total: Math.round((s + d + r) * 1e3) / 1e3,
hire: s,
bunker: Math.round((d + r) * 1e3) / 1e3,
cp: e
};
}
return t;
}
/**
* 计算单cp模式下的ECA属性
*
*/
static async calculateECA(t, e, a = {}) {
var d, r, m, M;
const i = await W.intersectInECA((t == null ? void 0 : t.route) || []);
let n = 0, o = 0, s = 0;
(r = (d = t == null ? void 0 : t.sample) == null ? void 0 : d.wps) == null || r.forEach((l) => {
l.positionTime = b.utc(l.etd || l.eta).unix();
});
for (const l of i) {
n += l.distance;
const g = await W.deadReckoningTime((m = l.waypoints) == null ? void 0 : m.at(0), t.sample.wps), f = await W.deadReckoningTime((M = l.waypoints) == null ? void 0 : M.at(-1), t.sample.wps);
l.in = g, l.out = f, l.totalHrs = J.roundPrecision((f.positionTime - g.positionTime) / 3600, 3), l.totalDgoCons = J.roundPrecision(e.fo / 24 * l.totalHrs, 3), o += l.totalHrs, s += l.totalDgoCons;
}
return n = J.roundPrecision(n, 3), o = J.roundPrecision(o, 3), s = J.roundPrecision(s, 3), {
distanceInECA: n,
hoursInECA: