Initial import: WebAisMap

Closes TG-4

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-04 07:56:45 +03:00
commit 03075f1ef1
1460 changed files with 16334 additions and 0 deletions
+6077
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
{"273":"RU","275":"LV","277":"LT","276":"EE","272":"UA","261":"PL","211":"DE","218":"DE","226":"FR","227":"FR","228":"FR","247":"IT","257":"NO","258":"NO","259":"NO","265":"SE","266":"SE","230":"FI","219":"DK","220":"DK","244":"NL","245":"NL","246":"NL","205":"BE","224":"ES","225":"ES","237":"GR","239":"GR","240":"GR","241":"GR","271":"TR","264":"RO","207":"BG","238":"HR","232":"GB","233":"GB","234":"GB","235":"GB","250":"IE","251":"IS","215":"MT","229":"MT","248":"MT","249":"MT","256":"MT","209":"CY","210":"CY","212":"CY","338":"US","366":"US","367":"US","368":"US","369":"US","316":"CA","412":"CN","413":"CN","414":"CN","431":"JP","432":"JP","440":"KR","441":"KR","563":"SG","564":"SG","565":"SG","566":"SG","419":"IN","503":"AU","710":"BR","701":"AR","351":"PA","352":"PA","353":"PA","354":"PA","355":"PA","356":"PA","357":"PA","370":"PA","371":"PA","372":"PA","373":"PA","374":"PA","636":"LR","637":"LR","538":"MH","308":"BS","309":"BS","311":"BS","477":"HK","416":"TW","574":"VN","525":"ID","533":"MY","548":"PH","567":"TH","403":"SA","470":"AE","471":"AE","428":"IL","622":"EG","601":"ZA","512":"NZ","345":"MX","725":"CL","263":"PT","269":"CH","203":"AT","270":"CZ","243":"HU","279":"RS","278":"SI","267":"SK","262":"ME","201":"AL","213":"GE","436":"KZ","422":"IR"}
+473
View File
@@ -0,0 +1,473 @@
/**
* Редактор габаритов (ITU Fig. 38).
* Корма — L, правый борт — W (доли A/L и C/W фиксируются на время жеста); точка GPS — перетаскивание.
* Для роста L/W координаты могут выходить за контур корпуса (опорный масштаб wPxRef/hPxRef с pointerdown).
*/
(function () {
'use strict';
var NS = 'http://www.w3.org/2000/svg';
var MIN_LEN = 20;
var MIN_BEAM = 6;
var MAX_AB = 511;
var MAX_CD = 63;
var MAX_L = MAX_AB + MAX_AB;
var MAX_W = MAX_CD + MAX_CD;
/** Макс. размер корпуса в пикселях (пропорции сохраняются) */
var MAX_DRAW_W = 175;
var MAX_DRAW_H = 210;
/** Минимум по меньшей стороне корпуса в px — иначе ручки и клампы «схлопываются» */
var MIN_HULL_PX = 48;
var PAD_L = 12;
var PAD_T = 48;
var PAD_R = 78;
var PAD_B = 42;
var svg, inner, hull, marker, handles;
var gridH, gridV, lblBow, lblStern, dimBeam, dimLength;
var drag = null;
/** Текущая геометрия (обновляется в refresh) */
var layout = {
wPx: 100,
hPx: 160,
dispL: MIN_LEN,
dispW: MIN_BEAM,
};
function clamp(n, lo, hi) {
return Math.max(lo, Math.min(hi, n));
}
function el(name, attrs) {
var e = document.createElementNS(NS, name);
if (attrs) {
Object.keys(attrs).forEach(function (k) {
e.setAttribute(k, attrs[k]);
});
}
return e;
}
function readDims() {
var A = parseInt(document.getElementById('tp-to-bow').value, 10) || 0;
var B = parseInt(document.getElementById('tp-to-stern').value, 10) || 0;
var C = parseInt(document.getElementById('tp-to-port').value, 10) || 0;
var D = parseInt(document.getElementById('tp-to-starboard').value, 10) || 0;
A = clamp(A, 0, MAX_AB);
B = clamp(B, 0, MAX_AB);
C = clamp(C, 0, MAX_CD);
D = clamp(D, 0, MAX_CD);
return { A: A, B: B, C: C, D: D, L: A + B, W: C + D };
}
function writeDims(A, B, C, D) {
A = clamp(Math.round(A), 0, MAX_AB);
B = clamp(Math.round(B), 0, MAX_AB);
C = clamp(Math.round(C), 0, MAX_CD);
D = clamp(Math.round(D), 0, MAX_CD);
document.getElementById('tp-to-bow').value = A;
document.getElementById('tp-to-stern').value = B;
document.getElementById('tp-to-port').value = C;
document.getElementById('tp-to-starboard').value = D;
}
function displayLW(d) {
var L = d.L > 0 ? d.L : MIN_LEN;
var W = d.W > 0 ? d.W : MIN_BEAM;
return { L: L, W: W, template: d.L === 0 && d.W === 0 };
}
function hullPath(w, h) {
var bow = Math.min(20, h * 0.11);
var tipX = w / 2;
return (
'M ' + tipX + ',0 L ' + w + ',' + bow + ' L ' + w + ',' + h + ' L 0,' + h + ' L 0,' + bow + ' Z'
);
}
function computeLayout(d, disp) {
var scale = Math.min(MAX_DRAW_W / disp.W, MAX_DRAW_H / disp.L);
if (!isFinite(scale) || scale <= 0) scale = 1;
var wPx0 = disp.W * scale;
var hPx0 = disp.L * scale;
var bump = 1;
if (wPx0 < MIN_HULL_PX) bump = Math.max(bump, MIN_HULL_PX / Math.max(wPx0, 1e-6));
if (hPx0 < MIN_HULL_PX) bump = Math.max(bump, MIN_HULL_PX / Math.max(hPx0, 1e-6));
scale *= bump;
var wPx = disp.W * scale;
var hPx = disp.L * scale;
layout.wPx = wPx;
layout.hPx = hPx;
layout.dispL = disp.L;
layout.dispW = disp.W;
layout.scale = scale;
return { sx: scale, sy: scale, wPx: wPx, hPx: hPx };
}
function scales(d, disp, geo) {
var wPx = geo.wPx;
var hPx = geo.hPx;
var padX = Math.max(1, Math.min(8, wPx * 0.1));
var padY = Math.max(1, Math.min(8, hPx * 0.1));
var aVis = d.L > 0 ? d.A : disp.L / 2;
var cVis = d.W > 0 ? d.C : disp.W / 2;
var mx = clamp(cVis * geo.sx, padX, wPx - padX);
var my = clamp(aVis * geo.sy, padY, hPx - padY);
return { mx: mx, my: my };
}
function ensureNonZeroDims() {
var d = readDims();
if (d.L === 0 && d.W === 0) {
writeDims(MIN_LEN / 2, MIN_LEN / 2, MIN_BEAM / 2, MIN_BEAM / 2);
}
}
function clearG(g) {
while (g.firstChild) g.removeChild(g.firstChild);
}
function rebuildBeamDimension(g, wPx, hPx, Wm) {
clearG(g);
var bow = Math.min(20, hPx * 0.11);
var off = 24;
var y = -off;
var yPast = y - 1.5;
g.appendChild(
el('line', { class: 'ship-dim-ext', x1: 0, y1: bow, x2: 0, y2: yPast })
);
g.appendChild(
el('line', { class: 'ship-dim-ext', x1: wPx, y1: bow, x2: wPx, y2: yPast })
);
g.appendChild(
el('line', {
class: 'ship-dim-main',
x1: 0,
y1: y,
x2: wPx,
y2: y,
'marker-start': 'url(#ship-dim-arrow)',
'marker-end': 'url(#ship-dim-arrow)',
})
);
var t = el('text', {
class: 'ship-dim-txt',
x: wPx / 2,
y: y - 8,
'text-anchor': 'middle',
});
t.textContent = 'W = ' + Wm + ' м';
g.appendChild(t);
}
function rebuildLengthDimension(g, wPx, hPx, Lm) {
clearG(g);
var bow = Math.min(20, hPx * 0.11);
var off = 22;
var x = wPx + off;
var xPast = x + 1.5;
g.appendChild(
el('line', { class: 'ship-dim-ext', x1: wPx, y1: bow, x2: x, y2: bow })
);
g.appendChild(
el('line', { class: 'ship-dim-ext', x1: x, y1: bow, x2: x, y2: 0 })
);
g.appendChild(
el('line', { class: 'ship-dim-ext', x1: wPx, y1: hPx, x2: xPast, y2: hPx })
);
g.appendChild(
el('line', {
class: 'ship-dim-main',
x1: x,
y1: 0,
x2: x,
y2: hPx,
'marker-start': 'url(#ship-dim-arrow)',
'marker-end': 'url(#ship-dim-arrow)',
})
);
var t = el('text', {
class: 'ship-dim-txt',
x: x + 14,
y: hPx / 2,
'text-anchor': 'middle',
transform: 'rotate(-90 ' + (x + 14) + ' ' + hPx / 2 + ')',
});
t.textContent = 'L = ' + Lm + ' м';
g.appendChild(t);
}
function positionHandles(wPx, hPx) {
if (!handles) return;
var stern = handles.querySelector('[data-ship-edge="stern"]');
var sb = handles.querySelector('[data-ship-edge="starboard"]');
if (stern) stern.setAttribute('transform', 'translate(' + wPx / 2 + ',' + hPx + ')');
if (sb) sb.setAttribute('transform', 'translate(' + wPx + ',' + hPx / 2 + ')');
}
function refreshFromInputs() {
if (!svg || !inner || !hull || !marker) return;
var d = readDims();
var disp = displayLW(d);
var geo = computeLayout(d, disp);
var sc = scales(d, disp, geo);
var wPx = geo.wPx;
var hPx = geo.hPx;
inner.setAttribute('transform', 'translate(' + PAD_L + ',' + PAD_T + ')');
var vbW = PAD_L + wPx + PAD_R;
var vbH = PAD_T + hPx + PAD_B;
svg.setAttribute('viewBox', '0 0 ' + vbW + ' ' + vbH);
rebuildBeamDimension(dimBeam, wPx, hPx, disp.W);
rebuildLengthDimension(dimLength, wPx, hPx, disp.L);
hull.setAttribute('d', hullPath(wPx, hPx));
if (gridH && gridV) {
gridH.setAttribute('x1', 0);
gridH.setAttribute('y1', sc.my);
gridH.setAttribute('x2', wPx);
gridH.setAttribute('y2', sc.my);
gridV.setAttribute('x1', sc.mx);
gridV.setAttribute('y1', 0);
gridV.setAttribute('x2', sc.mx);
gridV.setAttribute('y2', hPx);
}
if (lblBow) {
lblBow.setAttribute('x', wPx / 2);
lblBow.setAttribute('y', -38);
}
if (lblStern) {
lblStern.setAttribute('x', wPx / 2);
lblStern.setAttribute('y', hPx + 22);
}
marker.setAttribute('transform', 'translate(' + sc.mx + ',' + sc.my + ')');
marker.setAttribute('class', 'ship-gps-group' + (disp.template ? ' ship-gps-group--template' : ''));
positionHandles(wPx, hPx);
}
function svgPointFromClient(clientX, clientY) {
var pt = svg.createSVGPoint();
pt.x = clientX;
pt.y = clientY;
var ctm = inner.getScreenCTM();
if (!ctm) return null;
var p = pt.matrixTransform(ctm.inverse());
return { x: p.x, y: p.y };
}
function splitLength(u, Ls) {
u = clamp(u, 0, 1);
var A = Math.round(u * Ls);
A = clamp(A, 0, MAX_AB);
var B = Ls - A;
if (B > MAX_AB) {
B = MAX_AB;
A = clamp(Ls - B, 0, MAX_AB);
}
if (B < 0) {
B = 0;
A = clamp(Ls, 0, MAX_AB);
}
return { A: A, B: B };
}
function splitBeam(v, Ws) {
v = clamp(v, 0, 1);
var C = Math.round(v * Ws);
C = clamp(C, 0, MAX_CD);
var D = Ws - C;
if (D > MAX_CD) {
D = MAX_CD;
C = clamp(Ws - D, 0, MAX_CD);
}
if (D < 0) {
D = 0;
C = clamp(Ws, 0, MAX_CD);
}
return { C: C, D: D };
}
/** Новая длина L; сохраняем долю носа A/L ≈ rA. */
function abFromLength(Ln, rA) {
Ln = clamp(Math.round(Ln), 1, MAX_L);
var r = clamp(isFinite(rA) ? rA : 0.5, 0, 1);
var A = Math.round(r * Ln);
A = clamp(A, 0, MAX_AB);
var B = Ln - A;
if (B > MAX_AB) {
B = MAX_AB;
A = clamp(Ln - B, 0, MAX_AB);
}
if (B < 0) {
B = 0;
A = clamp(Ln, 0, MAX_AB);
}
return { A: A, B: B };
}
/** Новая ширина W; сохраняем долю порта C/W ≈ rC. */
function cdFromWidth(Wn, rC) {
Wn = clamp(Math.round(Wn), 1, MAX_W);
var r = clamp(isFinite(rC) ? rC : 0.5, 0, 1);
var C = Math.round(r * Wn);
C = clamp(C, 0, MAX_CD);
var D = Wn - C;
if (D > MAX_CD) {
D = MAX_CD;
C = clamp(Wn - D, 0, MAX_CD);
}
if (D < 0) {
D = 0;
C = clamp(Wn, 0, MAX_CD);
}
return { C: C, D: D };
}
function startDrag(ev, kind, captureEl) {
ev.preventDefault();
captureEl.setPointerCapture(ev.pointerId);
document.body.classList.add('ship-editor-dragging');
document.addEventListener('pointermove', onDocumentPointerMove);
document.addEventListener('pointerup', onDocumentPointerEnd);
document.addEventListener('pointercancel', onDocumentPointerEnd);
return { kind: kind, pid: ev.pointerId, el: captureEl };
}
function stopDragListeners() {
document.removeEventListener('pointermove', onDocumentPointerMove);
document.removeEventListener('pointerup', onDocumentPointerEnd);
document.removeEventListener('pointercancel', onDocumentPointerEnd);
}
function onInnerPointerDown(ev) {
if (!inner) return;
var edgeG = ev.target.closest ? ev.target.closest('[data-ship-edge]') : null;
if (edgeG) {
ensureNonZeroDims();
var d0 = readDims();
var disp0 = displayLW(d0);
var geo0 = computeLayout(d0, disp0);
var kind = edgeG.getAttribute('data-ship-edge');
drag = startDrag(ev, kind, edgeG);
drag.L0 = d0.L;
drag.W0 = d0.W;
drag.rA = d0.L > 0 ? d0.A / d0.L : 0.5;
drag.rC = d0.W > 0 ? d0.C / d0.W : 0.5;
drag.wPxRef = geo0.wPx;
drag.hPxRef = geo0.hPx;
return;
}
if (marker && marker.contains(ev.target)) {
ensureNonZeroDims();
var dg = readDims();
var Ls = dg.L > 0 ? Math.min(dg.L, MAX_L) : MIN_LEN;
var Ws = dg.W > 0 ? Math.min(dg.W, MAX_W) : MIN_BEAM;
Ls = clamp(Ls, 1, MAX_L);
Ws = clamp(Ws, 1, MAX_W);
drag = startDrag(ev, 'gps', marker);
drag.Ls = Ls;
drag.Ws = Ws;
}
}
function onDocumentPointerMove(ev) {
if (!drag || ev.pointerId !== drag.pid) return;
var p = svgPointFromClient(ev.clientX, ev.clientY);
if (!p) return;
var d = readDims();
var disp = displayLW(d);
var geo = computeLayout(d, disp);
var wPx = geo.wPx;
var hPx = geo.hPx;
if (drag.kind === 'gps') {
var u = p.y / hPx;
var v = p.x / wPx;
var ab = splitLength(u, drag.Ls);
var cd = splitBeam(v, drag.Ws);
writeDims(ab.A, ab.B, cd.C, cd.D);
refreshFromInputs();
return;
}
var wRef = drag.wPxRef;
var hRef = drag.hPxRef;
if (drag.kind === 'stern' && wRef > 0 && hRef > 0) {
var yMin = (hRef * 1) / Math.max(drag.L0, 1);
var yMax = (hRef * MAX_L) / Math.max(drag.L0, 1);
var yS = clamp(p.y, yMin, yMax);
var L1 = Math.round((drag.L0 * yS) / hRef);
L1 = clamp(L1, 1, MAX_L);
var abS = abFromLength(L1, drag.rA);
writeDims(abS.A, abS.B, d.C, d.D);
} else if (drag.kind === 'starboard' && wRef > 0 && hRef > 0) {
var xMin = (wRef * 1) / Math.max(drag.W0, 1);
var xMax = (wRef * MAX_W) / Math.max(drag.W0, 1);
var xSb = clamp(p.x, xMin, xMax);
var W1s = Math.round((drag.W0 * xSb) / wRef);
W1s = clamp(W1s, 1, MAX_W);
var cd2 = cdFromWidth(W1s, drag.rC);
writeDims(d.A, d.B, cd2.C, cd2.D);
}
refreshFromInputs();
}
function onDocumentPointerEnd(ev) {
if (!drag || ev.pointerId !== drag.pid) return;
stopDragListeners();
try {
drag.el.releasePointerCapture(ev.pointerId);
} catch (e) { /* ignore */ }
drag = null;
document.body.classList.remove('ship-editor-dragging');
}
function bindInputs() {
['tp-to-bow', 'tp-to-stern', 'tp-to-port', 'tp-to-starboard'].forEach(function (id) {
var eln = document.getElementById(id);
if (eln) {
eln.addEventListener('input', refreshFromInputs);
eln.addEventListener('change', refreshFromInputs);
}
});
}
function boot() {
svg = document.getElementById('ship-editor-svg');
inner = document.getElementById('ship-editor-inner');
hull = document.getElementById('ship-hull');
marker = document.getElementById('ship-gps-marker');
handles = document.getElementById('ship-edge-handles');
gridH = document.getElementById('ship-grid-h');
gridV = document.getElementById('ship-grid-v');
lblBow = document.getElementById('ship-lbl-bow');
lblStern = document.getElementById('ship-lbl-stern');
dimBeam = document.getElementById('ship-dim-beam');
dimLength = document.getElementById('ship-dim-length');
if (!svg || !inner || !hull || !marker) return;
inner.addEventListener('pointerdown', onInnerPointerDown);
bindInputs();
refreshFromInputs();
}
window.shipDimsEditorRefresh = refreshFromInputs;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();
+179
View File
@@ -0,0 +1,179 @@
/**
* ITU-R M.1371-6, табл. 51 — тип судна (идентификатор 0–99).
* Подписи на русском по официальным формулировкам (сокращ.: ОПГ — опасные грузы;
* ОН — опасные только насыпью; ВВ — вредные вещества; МЗ — морские загрязнители).
*/
(function () {
'use strict';
var LABELS = [
'Не указано',
'Научно-исследовательское судно',
'Учебное судно',
'Судно, принадлежащее или эксплуатируемое государственным органом',
'Ледокол',
'Судно обслуживания буёв (навигационных знаков)',
'Кабелеукладчик',
'Трубоукладчик',
'Зарезервировано (на будущее)',
'Судно специального назначения, дополнительные сведения не указаны',
'Зарезервировано (на будущее)',
'Судно FPSO (добыча, хранение и отгрузка нефти на месторождении)',
'Рыбозавод (плавучий рыбоперерабатывающий завод)',
'Судно обеспечения рыбоводческого хозяйства',
'Судно оффшорного обеспечения и т. п.',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Судно для строительных работ',
'Катер доставки экипажа (crew boat)',
'Судно обеспечения, дополнительные сведения не указаны',
'Судно на воздушной подушке (WIG), все суда этого типа',
'WIG с ОПГ и/или ОН, ВВ или МЗ, категория опасности X (ИМО)',
'WIG с ОПГ и/или ОН, ВВ или МЗ, категория опасности Y (ИМО)',
'WIG с ОПГ и/или ОН, ВВ или МЗ, категория опасности Z (ИМО)',
'WIG с ОПГ и/или ОН, ВВ или МЗ, категория OS (ИМО)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'WIG, дополнительные сведения не указаны',
'Рыболовное судно',
'Буксир',
'Буксир; длина буксира >200 м или ширина >25 м',
'Земснаряд',
'Водолазное судно',
'Военный корабль или вспомогательное судно ВМС',
'Парусное судно',
'Прогулочное моторное судно',
'Тральщик',
'Патрульное судно',
'Высокоскоростное судно (ВСС), все суда этого типа',
'ВСС с ОПГ и/или ОН, ВВ или МЗ, категория X (ИМО)',
'ВСС с ОПГ и/или ОН, ВВ или МЗ, категория Y (ИМО)',
'ВСС с ОПГ и/или ОН, ВВ или МЗ, категория Z (ИМО)',
'ВСС с ОПГ и/или ОН, ВВ или МЗ, категория OS (ИМО)',
'ВСС, перевозка пассажиров',
'ВСС Ro-Ro (автомобили / ж/д)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'ВСС, дополнительные сведения не указаны',
'Лоцманское судно',
'Поисково-спасательное судно',
'Буксиры',
'Портовое или рыболовное вспомогательное судно',
'Противозагрязнение или пожарный резерв',
'Судно правоохранительных органов',
'Резерв 1 — для назначений местным судам',
'Резерв 2 — для назначений местным судам',
'Медицинский транспорт (Женевские конвенции 1949 г. и Доп. протоколы)',
'Судна государств, не участвующих в вооружённом конфликте',
'Пассажирское судно, все суда этого типа',
'Пассажирское с ОПГ и/или ОН, ВВ или МЗ, категория X (ИМО)',
'Пассажирское с ОПГ и/или ОН, ВВ или МЗ, категория Y (ИМО)',
'Пассажирское с ОПГ и/или ОН, ВВ или МЗ, категория Z (ИМО)',
'Пассажирское с ОПГ и/или ОН, ВВ или МЗ, категория OS (ИМО)',
'Пассажирский лайнер (круизное судно)',
'Пассажирский паром',
'Пассажирское прогулочное (напр. по гавани, наблюдение за китами)',
'Зарезервировано (на будущее)',
'Пассажирское судно, дополнительные сведения не указаны',
'Грузовое судно, все суда этого типа',
'Грузовое с ОПГ и/или ОН, ВВ или МЗ, категория X (ИМО)',
'Грузовое с ОПГ и/или ОН, ВВ или МЗ, категория Y (ИМО)',
'Грузовое с ОПГ и/или ОН, ВВ или МЗ, категория Z (ИМО)',
'Грузовое с ОПГ и/или ОН, ВВ или МЗ, категория OS (ИМО)',
'Грузовое судно, балкер',
'Грузовое судно, контейнеровоз',
'Грузовое судно, Ro-Ro',
'Грузовое судно, десантная баржа',
'Грузовое судно, дополнительные сведения не указаны',
'Танкер, все суда этого типа',
'Танкер с ОПГ и/или ОН, ВВ или МЗ, категория X (ИМО)',
'Танкер с ОПГ и/или ОН, ВВ или МЗ, категория Y (ИМО)',
'Танкер с ОПГ и/или ОН, ВВ или МЗ, категория Z (ИМО)',
'Танкер с ОПГ и/или ОН, ВВ или МЗ, категория OS (ИМО)',
'Танкер, неопасный груз / не загрязняющий',
'Составной буксир и танкерная баржа (A–D — буксир и баржа)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Танкер, дополнительные сведения не указаны',
'Прочий тип судна',
'Прочий тип с ОПГ и/или ОН, ВВ или МЗ, категория X (ИМО)',
'Прочий тип с ОПГ и/или ОН, ВВ или МЗ, категория Y (ИМО)',
'Прочий тип с ОПГ и/или ОН, ВВ или МЗ, категория Z (ИМО)',
'Прочий тип с ОПГ и/или ОН, ВВ или МЗ, категория OS (ИМО)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Зарезервировано (на будущее)',
'Прочий тип судна, дополнительные сведения не указаны',
];
var GROUPS = [
{ from: 1, to: 9, title: '01–09 Судно специального назначения' },
{ from: 10, to: 19, title: '10–19 Судно обеспечения' },
{ from: 20, to: 29, title: '20–29 Судно на воздушной подушке (WIG)' },
{ from: 30, to: 39, title: '3039 Особое судно' },
{ from: 40, to: 49, title: '40–49 Высокоскоростное судно (ВСС)' },
{ from: 50, to: 59, title: '5059 Особое судно' },
{ from: 60, to: 69, title: '60–69 Пассажирское судно' },
{ from: 70, to: 79, title: '70–79 Грузовое судно' },
{ from: 80, to: 89, title: '8089 Танкер' },
{ from: 90, to: 99, title: '9099 Прочие типы' },
];
function addOption(sel, value, text) {
var o = document.createElement('option');
o.value = String(value);
o.textContent = String(value) + ' — ' + text;
sel.appendChild(o);
}
function fillShipTypeSelect() {
var sel = document.getElementById('tp-ship-type');
if (!sel || sel.getAttribute('data-table51') === '1') return;
sel.setAttribute('data-table51', '1');
sel.innerHTML = '';
addOption(sel, 0, LABELS[0]);
GROUPS.forEach(function (g) {
var og = document.createElement('optgroup');
og.label = g.title;
for (var c = g.from; c <= g.to; c++) {
var o = document.createElement('option');
o.value = String(c);
var pad = c < 10 ? '0' + c : String(c);
o.textContent = pad + ' — ' + LABELS[c];
og.appendChild(o);
}
sel.appendChild(og);
});
}
function ensureShipTypeOption(value) {
var sel = document.getElementById('tp-ship-type');
if (!sel) return;
var v = Math.max(0, Math.min(255, parseInt(value, 10) || 0));
var s = String(v);
for (var i = 0; i < sel.options.length; i++) {
if (sel.options[i].value === s) return;
}
var o = document.createElement('option');
o.value = s;
o.textContent = s + ' — (из конфигурации, вне табл. 51 / 099)';
sel.appendChild(o);
}
window.fillShipTypeSelect = fillShipTypeSelect;
window.ensureShipTypeOption = ensureShipTypeOption;
window.AIS_TABLE51_SHIP_TYPE_LABEL = function (code) {
var c = parseInt(code, 10);
if (isNaN(c) || c < 0 || c > 99) return null;
return LABELS[c] || null;
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', fillShipTypeSelect);
} else {
fillShipTypeSelect();
}
})();