/** * Geometría liviana para validar punto-en-polígono sin deps. * * Polígonos vienen en GeoJSON: { type: "Polygon", coordinates: [[[lng,lat],...]] }. * GeoJSON usa orden [lng, lat] (X, Y). Mantenemos esa convención puertas adentro. */ /** * Ray casting (algoritmo clásico). Devuelve true si (lng, lat) cae dentro del * anillo exterior del polígono. No considera agujeros (holes) — para CABA y un * editor que dibuja polígonos simples, alcanza. * * @param {number} lng * @param {number} lat * @param {{type:string, coordinates:Array>}} polygon * @returns {boolean} */ export function pointInPolygon(lng, lat, polygon) { if (!polygon || polygon.type !== "Polygon" || !Array.isArray(polygon.coordinates)) return false; const ring = polygon.coordinates[0]; if (!Array.isArray(ring) || ring.length < 3) return false; let inside = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) { const xi = ring[i][0], yi = ring[i][1]; const xj = ring[j][0], yj = ring[j][1]; const intersect = (yi > lat) !== (yj > lat) && lng < ((xj - xi) * (lat - yi)) / (yj - yi + 0) + xi; if (intersect) inside = !inside; } return inside; } /** * Busca la primera zona habilitada cuyo polígono contiene al punto. * * @param {number} lng * @param {number} lat * @param {Array} zones - { id, name, polygon, enabled, ... } * @returns {Object|null} */ export function findZoneForPoint(lng, lat, zones) { if (!Array.isArray(zones)) return null; for (const z of zones) { if (z?.enabled === false) continue; if (!z?.polygon) continue; if (pointInPolygon(lng, lat, z.polygon)) return z; } return null; }