Files
App/www/map-advanced-zones.js
HiepLM 365a15c32a
Some checks are pending
Test / test (push) Waiting to run
update full objects type
2026-06-20 11:43:48 +02:00

234 lines
6.8 KiB
JavaScript

(() => {
/**
* Runtime hooks for Directional / Planner settings / I/O map zones (MiR §4.2.6.5, .10, .11).
*/
const TYPES = {
directional: "directional",
directional_line: "directional_line",
planner: "planner",
io: "io",
};
const ADVANCED_TYPES = new Set([
TYPES.directional,
TYPES.directional_line,
TYPES.planner,
TYPES.io,
]);
const DIRECTION_DEGREES = [0, 45, 90, 135, 180, 225, 270, 315];
const DEFAULT_PLANNER = {
no_localization: false,
look_ahead: false,
path_deviation: 0.5,
path_timeout: 30,
ignore_obstacles: false,
};
const DEFAULT_IO = {
io_module: "",
plc_register: null,
plc_value: null,
plc_mode: "set",
};
function pointInPolygon(px, py, points) {
return window.MapPlannerZones?.pointInPolygon(px, py, points) || false;
}
function normalizeDirectionDeg(value) {
const n = Number(value);
if (!Number.isFinite(n)) return 0;
let deg = ((Math.round(n / 45) * 45) % 360 + 360) % 360;
if (!DIRECTION_DEGREES.includes(deg)) {
deg = DIRECTION_DEGREES.reduce((best, d) =>
Math.abs(d - deg) < Math.abs(best - deg) ? d : best,
);
}
return deg;
}
function clampPathDeviation(value) {
const n = Number(value);
if (!Number.isFinite(n)) return DEFAULT_PLANNER.path_deviation;
return Math.min(3, Math.max(0, n));
}
function clampPathTimeout(value) {
const n = Number(value);
if (!Number.isFinite(n) || n < 0) return DEFAULT_PLANNER.path_timeout;
return n;
}
function normalizePlannerSettings(raw = {}) {
return {
no_localization: !!raw.no_localization,
look_ahead: !!raw.look_ahead,
path_deviation: clampPathDeviation(raw.path_deviation),
path_timeout: clampPathTimeout(raw.path_timeout),
ignore_obstacles: !!raw.ignore_obstacles,
};
}
function normalizeIoSettings(raw = {}) {
const mode = raw.plc_mode;
return {
io_module: typeof raw.io_module === "string" ? raw.io_module : "",
plc_register:
raw.plc_register == null || raw.plc_register === ""
? null
: Number(raw.plc_register),
plc_value:
raw.plc_value == null || raw.plc_value === "" ? null : Number(raw.plc_value),
plc_mode: mode === "add" || mode === "subtract" ? mode : "set",
};
}
function isAdvancedZoneType(type) {
return ADVANCED_TYPES.has(type);
}
function isDirectionalType(type) {
return type === TYPES.directional || type === TYPES.directional_line;
}
function filterAdvancedZones(zones) {
return (Array.isArray(zones) ? zones : []).filter((z) => isAdvancedZoneType(z?.type));
}
function pointNearPolyline(px, py, points, halfWidth = 8) {
if (!points?.length || points.length < 2) return false;
for (let i = 0; i < points.length - 1; i++) {
const p1 = points[i];
const p2 = points[i + 1];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const lenSq = dx * dx + dy * dy;
let dist;
if (lenSq === 0) dist = Math.hypot(px - p1.x, py - p1.y);
else {
let t = ((px - p1.x) * dx + (py - p1.y) * dy) / lenSq;
t = Math.max(0, Math.min(1, t));
dist = Math.hypot(px - (p1.x + t * dx), py - (p1.y + t * dy));
}
if (dist <= halfWidth) return true;
}
return false;
}
function zonesAtPoint(zones, px, py) {
const list = Array.isArray(zones) ? zones : [];
const hits = [];
for (let i = list.length - 1; i >= 0; i--) {
const z = list[i];
if (!isAdvancedZoneType(z?.type) || !z.points?.length) continue;
if (z.type === TYPES.directional_line) {
const width = Number(z.line_width) || 8;
if (pointNearPolyline(px, py, z.points, width / 2 + 4)) hits.push(z);
continue;
}
if (pointInPolygon(px, py, z.points)) hits.push(z);
}
return hits;
}
function lineDirectionDeg(zone) {
if (!zone?.points?.length || zone.points.length < 2) return 0;
const p0 = zone.points[0];
const p1 = zone.points[zone.points.length - 1];
let deg = (Math.atan2(-(p1.y - p0.y), p1.x - p0.x) * 180) / Math.PI;
if (zone.reversed) deg = (deg + 180) % 360;
return normalizeDirectionDeg(deg);
}
function zoneDirectionDeg(zone) {
if (!zone) return 0;
if (zone.type === TYPES.directional_line) return lineDirectionDeg(zone);
if (zone.type === TYPES.directional) return normalizeDirectionDeg(zone.direction_deg);
return 0;
}
/** Heading radians (map image coords) allowed if dot >= 0 with zone arrow. */
function isHeadingAllowed(zone, headingRad) {
const arrowRad = (-zoneDirectionDeg(zone) * Math.PI) / 180;
const hx = Math.cos(headingRad);
const hy = Math.sin(headingRad);
const ax = Math.cos(arrowRad);
const ay = Math.sin(arrowRad);
return hx * ax + hy * ay >= 0;
}
function getDirectionalConstraint(zones, px, py, headingRad) {
const hits = zonesAtPoint(zones, px, py).filter(isDirectionalType);
const zone = hits[0];
if (!zone) return null;
return {
zone,
direction_deg: zoneDirectionDeg(zone),
allowed: isHeadingAllowed(zone, headingRad),
};
}
function getPlannerSettings(zones, px, py) {
const zone = zonesAtPoint(zones, px, py).find((z) => z.type === TYPES.planner);
if (!zone) return null;
return {
zone,
settings: normalizePlannerSettings(zone),
};
}
function getIoActivation(zones, px, py) {
const zone = zonesAtPoint(zones, px, py).find((z) => z.type === TYPES.io);
if (!zone) return null;
return {
zone,
settings: normalizeIoSettings(zone),
};
}
function classifyPoint(zones, px, py, headingRad = null) {
const hits = zonesAtPoint(zones, px, py);
const directional = hits.find(isDirectionalType);
const planner = hits.find((z) => z.type === TYPES.planner);
const io = hits.find((z) => z.type === TYPES.io);
const out = {
directional: directional || null,
direction_deg: directional ? zoneDirectionDeg(directional) : null,
planner: planner || null,
planner_settings: planner ? normalizePlannerSettings(planner) : null,
io: io || null,
io_settings: io ? normalizeIoSettings(io) : null,
zones: hits,
};
if (directional && headingRad != null) {
out.heading_allowed = isHeadingAllowed(directional, headingRad);
}
return out;
}
window.MapAdvancedZones = {
TYPES,
ADVANCED_TYPES,
DIRECTION_DEGREES,
DEFAULT_PLANNER,
DEFAULT_IO,
normalizeDirectionDeg,
normalizePlannerSettings,
normalizeIoSettings,
clampPathDeviation,
clampPathTimeout,
isAdvancedZoneType,
isDirectionalType,
filterAdvancedZones,
zonesAtPoint,
zoneDirectionDeg,
isHeadingAllowed,
getDirectionalConstraint,
getPlannerSettings,
getIoActivation,
classifyPoint,
};
})();