diff --git a/traque-back/admin_socket.js b/traque-back/admin_socket.js index 5bebd34..eb7dc44 100644 --- a/traque-back/admin_socket.js +++ b/traque-back/admin_socket.js @@ -90,9 +90,9 @@ export function initAdminSocketHandler() { } if (!zoneManager.changeSettings(settings)) { socket.emit("error", "Error changing zone"); - socket.emit("zone_settings", zoneManager.settings) + socket.emit("zone_settings", settings) } else { - secureAdminBroadcast("zone_settings", zoneManager.settings) + secureAdminBroadcast("zone_settings", settings) } }) diff --git a/traque-back/zone_manager.js b/traque-back/zone_manager.js index b926553..55444d2 100644 --- a/traque-back/zone_manager.js +++ b/traque-back/zone_manager.js @@ -4,6 +4,11 @@ import { secureAdminBroadcast } from './admin_socket.js'; /* -------------------------------- Useful functions and constants -------------------------------- */ +const zoneTypes = { + circle: "circle", + polygon: "polygon" +} + const EARTH_RADIUS = 6_371_000; // Radius of the earth in m function haversine_distance({ lat: lat1, lng: lon1 }, { lat: lat2, lng: lon2 }) { @@ -20,9 +25,64 @@ function latlngEqual(latlng1, latlng2, epsilon = 1e-9) { } +/* -------------------------------- Circle zones -------------------------------- */ + +const defaultCircleSettings = {type: zoneTypes.circle, min: null, max: null, reductionCount: 4, duration: 10} + +function circleZone(center, radius, duration) { + return { + center: center, + radius: radius, + duration: duration, + + isInZone(location) { + return haversine_distance(center, location) < this.radius; + } + } +} + +function circleSettingsToZones(settings) { + const {min, max, reductionCount, duration} = settings; + + if (!min || !max) return []; + if (haversine_distance(max.center, min.center) > max.radius - min.radius) return []; + + const zones = [circleZone(max.center, max.radius, duration)]; + const radiusReductionLength = (max.radius - min.radius) / reductionCount; + let center = max.center; + let radius = max.radius; + + for (let i = 1; i < reductionCount; i++) { + radius -= radiusReductionLength; + let new_center = null; + while (!new_center || haversine_distance(new_center, min.center) > radius - min.radius) { + const angle = Math.random() * 2 * Math.PI; + const angularDistance = Math.sqrt(Math.random()) * radiusReductionLength / EARTH_RADIUS; + const lat0Rad = center.lat * Math.PI / 180; + const lon0Rad = center.lng * Math.PI / 180; + const latRad = Math.asin( + Math.sin(lat0Rad) * Math.cos(angularDistance) + + Math.cos(lat0Rad) * Math.sin(angularDistance) * Math.cos(angle) + ); + + const lonRad = lon0Rad + Math.atan2( + Math.sin(angle) * Math.sin(angularDistance) * Math.cos(lat0Rad), + Math.cos(angularDistance) - Math.sin(lat0Rad) * Math.sin(latRad) + ); + new_center = {lat: latRad * 180 / Math.PI, lng: lonRad * 180 / Math.PI}; + } + center = new_center; + zones.push(circleZone(center, radius, duration)) + } + zones.push(circleZone(min.center, min.radius, 0)); + + return zones; +} + + /* -------------------------------- Polygon zones -------------------------------- */ -const defaultPolygonSettings = []; +const defaultPolygonSettings = {type: zoneTypes.polygon, polygons: []} function polygonZone(points, duration) { return { @@ -82,9 +142,11 @@ function mergePolygons(poly1, poly2) { } function polygonSettingsToZones(settings) { + const {polygons} = settings; + const zones = []; - for (const { polygon, duration } of settings.slice().reverse()) { + for (const { polygon, duration } of polygons.slice().reverse()) { const length = zones.length; if (length == 0) { @@ -104,67 +166,13 @@ function polygonSettingsToZones(settings) { } -/* -------------------------------- Circle zones -------------------------------- */ - -const defaultCircleSettings = { min: null, max: null, reductionCount: 4, duration: 1 }; - -function circleZone(center, radius, duration) { - return { - center: center, - radius: radius, - duration: duration, - - isInZone(location) { - return haversine_distance(center, location) < this.radius; - } - } -} - -function circleSettingsToZones(settings) { - const {min, max, reductionCount, duration} = settings; - if (haversine_distance(max.center, min.center) > max.radius - min.radius) { - return null; - } - const zones = [circleZone(max.center, max.radius, duration)]; - const radiusReductionLength = (max.radius - min.radius) / reductionCount; - let center = max.center; - let radius = max.radius; - for (let i = 1; i < reductionCount; i++) { - radius -= radiusReductionLength; - let new_center = null; - while (!new_center || haversine_distance(new_center, min.center) > radius - min.radius) { - const angle = Math.random() * 2 * Math.PI; - const angularDistance = Math.sqrt(Math.random()) * radiusReductionLength / EARTH_RADIUS; - const lat0Rad = center.lat * Math.PI / 180; - const lon0Rad = center.lng * Math.PI / 180; - const latRad = Math.asin( - Math.sin(lat0Rad) * Math.cos(angularDistance) + - Math.cos(lat0Rad) * Math.sin(angularDistance) * Math.cos(angle) - ); - - const lonRad = lon0Rad + Math.atan2( - Math.sin(angle) * Math.sin(angularDistance) * Math.cos(lat0Rad), - Math.cos(angularDistance) - Math.sin(lat0Rad) * Math.sin(latRad) - ); - new_center = {lat: latRad * 180 / Math.PI, lng: lonRad * 180 / Math.PI}; - } - center = new_center; - zones.push(circleZone(center, radius, duration)) - } - zones.push(circleZone(min.center, min.radius, 0)); - return zones; -} - - /* -------------------------------- Zone manager -------------------------------- */ export default { isRunning: false, zones: [], // A zone has to be connected space that doesn't contain an earth pole currentZone: { id: 0, timeoutId: null, endDate: null }, - zoneType: "polygon", settings: defaultPolygonSettings, - settingsToZones: polygonSettingsToZones, start() { this.isRunning = true; @@ -209,25 +217,18 @@ export default { }, changeSettings(settings) { - const zones = this.settingsToZones(settings); - if (!zones) return false; - this.zones = zones; + switch (settings.type) { + case zoneTypes.circle: + this.zones = circleSettingsToZones(settings); + break; + case zoneTypes.polygon: + this.zones = polygonSettingsToZones(settings); + break; + default: + return; + } this.settings = settings; this.zoneBroadcast(); - return true; - }, - - changeZoneType(type) { - if (this.zoneType == type) return; - if (type == "circle") { - this.zoneType = "circle"; - this.settings = defaultCircleSettings; - this.settingsToZones = circleSettingsToZones; - } else if (type == "polygon") { - this.zoneType = "polygon"; - this.settings = defaultPolygonSettings; - this.settingsToZones = polygonSettingsToZones; - } }, zoneBroadcast() { diff --git a/traque-front/app/admin/components/liveMap.jsx b/traque-front/app/admin/components/liveMap.jsx index 28ac044..321708d 100644 --- a/traque-front/app/admin/components/liveMap.jsx +++ b/traque-front/app/admin/components/liveMap.jsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { Marker, Tooltip, Polyline, Polygon } from "react-leaflet"; +import { Marker, Tooltip, Polyline, Polygon, Circle } from "react-leaflet"; import "leaflet/dist/leaflet.css"; import { CustomMapContainer } from "@/components/map"; import useAdmin from "@/hook/useAdmin"; @@ -13,9 +13,14 @@ const positionIcon = new L.Icon({ shadowSize: [30, 30], }); +const zoneTypes = { + circle: "circle", + polygon: "polygon" +} + export default function LiveMap({mapStyle, showZones, showNames, showArrows}) { + const { zoneSettings, zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin(); const [timeLeftNextZone, setTimeLeftNextZone] = useState(null); - const { zoneExtremities, teams, nextZoneDate, getTeam, gameState } = useAdmin(); // Remaining time before sending position useEffect(() => { @@ -49,12 +54,34 @@ export default function LiveMap({mapStyle, showZones, showNames, showArrows}) { } } + function Zones() { + if (!(showZones && gameState == GameState.PLAYING && zoneSettings)) return null; + + switch (zoneSettings.type) { + case zoneTypes.circle: + return ( +
{`Next zone in : ${formatTime(timeLeftNextZone)}`}
}Number of zones
-Duration of a zone
-Number
+Duration
+Timeout
+Zone {i+1}
diff --git a/traque-front/app/admin/parameters/page.js b/traque-front/app/admin/parameters/page.js index 11fe72a..e5f30e1 100644 --- a/traque-front/app/admin/parameters/page.js +++ b/traque-front/app/admin/parameters/page.js @@ -4,6 +4,7 @@ import dynamic from "next/dynamic"; import Link from "next/link"; import { TextInput } from "@/components/input"; import { Section } from "@/components/section"; +import { BlueButton } from "@/components/button"; import { useAdminConnexion } from "@/context/adminConnexionContext"; import useAdmin from '@/hook/useAdmin'; import Messages from "./components/messages"; @@ -13,17 +14,20 @@ import TeamManager from './components/teamManager'; const PolygonZoneSelector = dynamic(() => import('./components/polygonZoneSelector'), { ssr: false }); const CircleZoneSelector = dynamic(() => import('./components/circleZoneSelector'), { ssr: false }); -const zoneSelectors = { +const zoneTypes = { circle: "circle", polygon: "polygon" } +const defaultCircleSettings = {type: zoneTypes.circle, min: null, max: null, reductionCount: 4, duration: 10} +const defaultPolygonSettings = {type: zoneTypes.polygon, polygons: []} + export default function ConfigurationPage() { - const {penaltySettings, changePenaltySettings, addTeam} = useAdmin(); + const {zoneSettings, changeZoneSettings, penaltySettings, changePenaltySettings, addTeam} = useAdmin(); const { useProtect } = useAdminConnexion(); const [allowedTimeBetweenUpdates, setAllowedTimeBetweenUpdates] = useState(""); const [teamName, setTeamName] = useState(''); - const [zoneSelector, setZoneSelector] = useState(zoneSelectors.polygon); + const [localZoneSettings, setLocalZoneSettings] = useState(zoneSettings); useProtect(); @@ -32,12 +36,26 @@ export default function ConfigurationPage() { setAllowedTimeBetweenUpdates(penaltySettings.allowedTimeBetweenPositionUpdate.toString()); } }, [penaltySettings]); + + useEffect(() => { + if (zoneSettings) { + setLocalZoneSettings(zoneSettings); + } + }, [zoneSettings]); + + function updateLocalZoneSettings(key, value) { + setLocalZoneSettings(prev => ({...prev, [key]: value})); + }; function applySettings() { if (Number(allowedTimeBetweenUpdates) != penaltySettings.allowedTimeBetweenPositionUpdate) { changePenaltySettings({allowedTimeBetweenPositionUpdate: Number(allowedTimeBetweenUpdates)}); } } + + function handleChangeZoneType() { + setLocalZoneSettings(localZoneSettings.type == zoneTypes.circle ? defaultPolygonSettings : defaultCircleSettings) + } function handleSubmit(e) { e.preventDefault(); @@ -77,9 +95,18 @@ export default function ConfigurationPage() {