This commit is contained in:
104
www/map-planner-zones.js
Normal file
104
www/map-planner-zones.js
Normal file
@@ -0,0 +1,104 @@
|
||||
(() => {
|
||||
/**
|
||||
* Planner hooks for Forbidden / Preferred / Unpreferred drive zones (MiR §4.2.6.6).
|
||||
* Vector overlay only — consumed by future global planner / path preview.
|
||||
*/
|
||||
const TYPES = {
|
||||
forbidden: "forbidden",
|
||||
preferred: "preferred",
|
||||
unpreferred: "unpreferred",
|
||||
};
|
||||
|
||||
const PLANNER_TYPES = new Set([TYPES.forbidden, TYPES.preferred, TYPES.unpreferred]);
|
||||
|
||||
/** Relative traversal cost (forbidden = impassable). */
|
||||
const COST = {
|
||||
forbidden: Infinity,
|
||||
unpreferred: 4,
|
||||
preferred: 0.35,
|
||||
neutral: 1,
|
||||
};
|
||||
|
||||
function pointInPolygon(px, py, points) {
|
||||
if (!points?.length) return false;
|
||||
let inside = false;
|
||||
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
||||
const xi = points[i].x;
|
||||
const yi = points[i].y;
|
||||
const xj = points[j].x;
|
||||
const yj = points[j].y;
|
||||
const intersect = yi > py !== yj > py && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi;
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
function isPlannerZoneType(type) {
|
||||
return PLANNER_TYPES.has(type);
|
||||
}
|
||||
|
||||
function filterPlannerZones(zones) {
|
||||
return (Array.isArray(zones) ? zones : []).filter((z) => isPlannerZoneType(z?.type));
|
||||
}
|
||||
|
||||
/** Topmost planner zones containing image pixel (newest wins for overlaps). */
|
||||
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 (!isPlannerZoneType(z?.type) || !z.points?.length) continue;
|
||||
if (pointInPolygon(px, py, z.points)) hits.push(z);
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
function pixelCost(zones, px, py) {
|
||||
const hits = zonesAtPoint(zones, px, py);
|
||||
if (hits.some((z) => z.type === TYPES.forbidden)) return COST.forbidden;
|
||||
if (hits.some((z) => z.type === TYPES.unpreferred)) return COST.unpreferred;
|
||||
if (hits.some((z) => z.type === TYPES.preferred)) return COST.preferred;
|
||||
return COST.neutral;
|
||||
}
|
||||
|
||||
function classifyPoint(zones, px, py) {
|
||||
const hits = zonesAtPoint(zones, px, py);
|
||||
return {
|
||||
forbidden: hits.some((z) => z.type === TYPES.forbidden),
|
||||
preferred: hits.some((z) => z.type === TYPES.preferred),
|
||||
unpreferred: hits.some((z) => z.type === TYPES.unpreferred),
|
||||
zones: hits,
|
||||
cost: pixelCost(zones, px, py),
|
||||
};
|
||||
}
|
||||
|
||||
function isPathBlocked(zones, pathPoints) {
|
||||
const pts = Array.isArray(pathPoints) ? pathPoints : [];
|
||||
return pts.some((p) => pixelCost(zones, p.x, p.y) === COST.forbidden);
|
||||
}
|
||||
|
||||
function pathCost(zones, pathPoints) {
|
||||
const pts = Array.isArray(pathPoints) ? pathPoints : [];
|
||||
let sum = 0;
|
||||
for (const p of pts) {
|
||||
const c = pixelCost(zones, p.x, p.y);
|
||||
if (!Number.isFinite(c)) return Infinity;
|
||||
sum += c;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
window.MapPlannerZones = {
|
||||
TYPES,
|
||||
PLANNER_TYPES,
|
||||
COST,
|
||||
isPlannerZoneType,
|
||||
filterPlannerZones,
|
||||
zonesAtPoint,
|
||||
pixelCost,
|
||||
classifyPoint,
|
||||
isPathBlocked,
|
||||
pathCost,
|
||||
pointInPolygon,
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user