mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Draggable waypoints with timing info.
https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
parent
6933470ce0
commit
811f46c289
@ -8,4 +8,5 @@ export interface Waypoint {
|
|||||||
is_movable: boolean;
|
is_movable: boolean;
|
||||||
should_mark: boolean;
|
should_mark: boolean;
|
||||||
include_in_path: boolean;
|
include_in_path: boolean;
|
||||||
|
timing: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Flight } from "../../api/flight";
|
import { Flight } from "../../api/flight";
|
||||||
import WaypointMarker from "../waypointmarker";
|
import WaypointMarker from "../waypointmarker";
|
||||||
|
import { ReactElement } from "react";
|
||||||
import { Polyline } from "react-leaflet";
|
import { Polyline } from "react-leaflet";
|
||||||
|
|
||||||
const BLUE_PATH = "#0084ff";
|
const BLUE_PATH = "#0084ff";
|
||||||
@ -39,25 +40,23 @@ function FlightPlanPath(props: FlightPlanProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WaypointMarkers = (props: FlightPlanProps) => {
|
const WaypointMarkers = (props: FlightPlanProps) => {
|
||||||
if (!props.selected || props.flight.waypoints == null) {
|
if (props.selected && props.flight.waypoints == null) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
var markers: ReactElement[] = [];
|
||||||
<>
|
props.flight.waypoints?.forEach((p, idx) => {
|
||||||
{props.flight.waypoints
|
markers.push(
|
||||||
.filter((p) => p.should_mark)
|
<WaypointMarker
|
||||||
.map((p, idx) => {
|
key={idx}
|
||||||
return (
|
number={idx}
|
||||||
<WaypointMarker
|
waypoint={p}
|
||||||
key={idx}
|
flight={props.flight}
|
||||||
number={idx}
|
/>
|
||||||
waypoint={p}
|
);
|
||||||
></WaypointMarker>
|
});
|
||||||
);
|
|
||||||
})}
|
return <>{markers}</>;
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FlightPlan(props: FlightPlanProps) {
|
export default function FlightPlan(props: FlightPlanProps) {
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
import backend from "../../api/backend";
|
||||||
|
import { Flight } from "../../api/flight";
|
||||||
import { Waypoint } from "../../api/waypoint";
|
import { Waypoint } from "../../api/waypoint";
|
||||||
import { Icon } from "leaflet";
|
import { Icon } from "leaflet";
|
||||||
import { Marker as LMarker } from "leaflet";
|
import { Marker as LMarker } from "leaflet";
|
||||||
import icon from "leaflet/dist/images/marker-icon.png";
|
import icon from "leaflet/dist/images/marker-icon.png";
|
||||||
import iconShadow from "leaflet/dist/images/marker-shadow.png";
|
import iconShadow from "leaflet/dist/images/marker-shadow.png";
|
||||||
import { MutableRefObject, useCallback, useRef } from "react";
|
import { MutableRefObject, useCallback, useEffect, useRef } from "react";
|
||||||
import { Marker, Tooltip, useMap, useMapEvent } from "react-leaflet";
|
import { Marker, Tooltip, useMap, useMapEvent } from "react-leaflet";
|
||||||
|
|
||||||
const WAYPOINT_ICON = new Icon({
|
const WAYPOINT_ICON = new Icon({
|
||||||
@ -15,6 +17,7 @@ const WAYPOINT_ICON = new Icon({
|
|||||||
interface WaypointMarkerProps {
|
interface WaypointMarkerProps {
|
||||||
number: number;
|
number: number;
|
||||||
waypoint: Waypoint;
|
waypoint: Waypoint;
|
||||||
|
flight: Flight;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WaypointMarker = (props: WaypointMarkerProps) => {
|
const WaypointMarker = (props: WaypointMarkerProps) => {
|
||||||
@ -50,24 +53,42 @@ const WaypointMarker = (props: WaypointMarkerProps) => {
|
|||||||
}, [map]);
|
}, [map]);
|
||||||
useMapEvent("zoomend", rebindTooltip);
|
useMapEvent("zoomend", rebindTooltip);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const waypoint = props.waypoint;
|
||||||
|
marker.current?.setTooltipContent(
|
||||||
|
`${props.number} ${waypoint.name}<br />` +
|
||||||
|
`${waypoint.altitude_ft} ft ${waypoint.altitude_reference}<br />` +
|
||||||
|
waypoint.timing
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const waypoint = props.waypoint;
|
const waypoint = props.waypoint;
|
||||||
return (
|
return (
|
||||||
<Marker
|
<Marker
|
||||||
position={waypoint.position}
|
position={waypoint.position}
|
||||||
icon={WAYPOINT_ICON}
|
icon={WAYPOINT_ICON}
|
||||||
|
draggable
|
||||||
|
eventHandlers={{
|
||||||
|
dragstart: (e) => {
|
||||||
|
const m: LMarker = e.target;
|
||||||
|
m.setTooltipContent("Waiting to recompute TOT...");
|
||||||
|
},
|
||||||
|
dragend: (e) => {
|
||||||
|
const m: LMarker = e.target;
|
||||||
|
const destination = m.getLatLng();
|
||||||
|
backend.post(
|
||||||
|
`/waypoints/${props.flight.id}/${props.number}/position`,
|
||||||
|
destination
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}}
|
||||||
ref={(ref) => {
|
ref={(ref) => {
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
marker.current = ref;
|
marker.current = ref;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip position={waypoint.position}>
|
<Tooltip position={waypoint.position} />
|
||||||
{`${props.number} ${waypoint.name}`}
|
|
||||||
<br />
|
|
||||||
{`${waypoint.altitude_ft} ft ${waypoint.altitude_reference}`}
|
|
||||||
<br />
|
|
||||||
TODO: Timing info
|
|
||||||
</Tooltip>
|
|
||||||
</Marker>
|
</Marker>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,12 +1,29 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from game.ato import FlightWaypoint
|
from game.ato import Flight, FlightWaypoint
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.server.leaflet import LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
|
|
||||||
|
|
||||||
|
def timing_info(flight: Flight, waypoint_idx: int) -> str:
|
||||||
|
if waypoint_idx == 0:
|
||||||
|
return f"Depart T+{flight.flight_plan.takeoff_time()}"
|
||||||
|
|
||||||
|
waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
|
||||||
|
prefix = "TOT"
|
||||||
|
time = flight.flight_plan.tot_for_waypoint(waypoint)
|
||||||
|
if time is None:
|
||||||
|
prefix = "Depart"
|
||||||
|
time = flight.flight_plan.depart_time_for_waypoint(waypoint)
|
||||||
|
if time is None:
|
||||||
|
return ""
|
||||||
|
return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
|
||||||
|
|
||||||
|
|
||||||
class FlightWaypointJs(BaseModel):
|
class FlightWaypointJs(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
position: LeafletPoint
|
position: LeafletPoint
|
||||||
@ -15,9 +32,12 @@ class FlightWaypointJs(BaseModel):
|
|||||||
is_movable: bool
|
is_movable: bool
|
||||||
should_mark: bool
|
should_mark: bool
|
||||||
include_in_path: bool
|
include_in_path: bool
|
||||||
|
timing: str
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def for_waypoint(waypoint: FlightWaypoint) -> FlightWaypointJs:
|
def for_waypoint(
|
||||||
|
waypoint: FlightWaypoint, flight: Flight, waypoint_idx: int
|
||||||
|
) -> FlightWaypointJs:
|
||||||
# Target *points* are the exact location of a unit, whereas the target area is
|
# Target *points* are the exact location of a unit, whereas the target area is
|
||||||
# only the center of the objective. Allow moving the latter since its exact
|
# only the center of the objective. Allow moving the latter since its exact
|
||||||
# location isn't very important.
|
# location isn't very important.
|
||||||
@ -67,4 +87,5 @@ class FlightWaypointJs(BaseModel):
|
|||||||
is_movable=is_movable,
|
is_movable=is_movable,
|
||||||
should_mark=should_mark,
|
should_mark=should_mark,
|
||||||
include_in_path=include_in_path,
|
include_in_path=include_in_path,
|
||||||
|
timing=timing_info(flight, waypoint_idx),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
from datetime import timedelta
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from dcs.mapping import LatLng, Point
|
from dcs.mapping import LatLng, Point
|
||||||
@ -11,6 +10,7 @@ from game.ato.flightwaypointtype import FlightWaypointType
|
|||||||
from game.server import GameContext
|
from game.server import GameContext
|
||||||
from game.server.leaflet import LeafletPoint
|
from game.server.leaflet import LeafletPoint
|
||||||
from game.server.waypoints.models import FlightWaypointJs
|
from game.server.waypoints.models import FlightWaypointJs
|
||||||
|
from game.sim import GameUpdateEvents
|
||||||
from game.utils import meters
|
from game.utils import meters
|
||||||
|
|
||||||
router: APIRouter = APIRouter(prefix="/waypoints")
|
router: APIRouter = APIRouter(prefix="/waypoints")
|
||||||
@ -24,10 +24,13 @@ def waypoints_for_flight(flight: Flight) -> list[FlightWaypointJs]:
|
|||||||
flight.departure.position,
|
flight.departure.position,
|
||||||
meters(0),
|
meters(0),
|
||||||
"RADIO",
|
"RADIO",
|
||||||
)
|
),
|
||||||
|
flight,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
return [departure] + [
|
return [departure] + [
|
||||||
FlightWaypointJs.for_waypoint(w) for w in flight.flight_plan.waypoints
|
FlightWaypointJs.for_waypoint(w, flight, i)
|
||||||
|
for i, w in enumerate(flight.flight_plan.waypoints, 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -45,6 +48,8 @@ def set_position(
|
|||||||
position: LeafletPoint,
|
position: LeafletPoint,
|
||||||
game: Game = Depends(GameContext.get),
|
game: Game = Depends(GameContext.get),
|
||||||
) -> None:
|
) -> None:
|
||||||
|
from game.server import EventStream
|
||||||
|
|
||||||
flight = game.db.flights.get(flight_id)
|
flight = game.db.flights.get(flight_id)
|
||||||
if waypoint_idx == 0:
|
if waypoint_idx == 0:
|
||||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||||
@ -64,22 +69,4 @@ def set_position(
|
|||||||
detail=f"Could not find PackageModel owning {flight}",
|
detail=f"Could not find PackageModel owning {flight}",
|
||||||
)
|
)
|
||||||
package_model.update_tot()
|
package_model.update_tot()
|
||||||
|
EventStream.put_nowait(GameUpdateEvents().update_flight(flight))
|
||||||
|
|
||||||
@router.get("/{flight_id}/{waypoint_idx}/timing")
|
|
||||||
def waypoint_timing(
|
|
||||||
flight_id: UUID, waypoint_idx: int, game: Game = Depends(GameContext.get)
|
|
||||||
) -> str | None:
|
|
||||||
flight = game.db.flights.get(flight_id)
|
|
||||||
if waypoint_idx == 0:
|
|
||||||
return f"Depart T+{flight.flight_plan.takeoff_time()}"
|
|
||||||
|
|
||||||
waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
|
|
||||||
prefix = "TOT"
|
|
||||||
time = flight.flight_plan.tot_for_waypoint(waypoint)
|
|
||||||
if time is None:
|
|
||||||
prefix = "Depart"
|
|
||||||
time = flight.flight_plan.depart_time_for_waypoint(waypoint)
|
|
||||||
if time is None:
|
|
||||||
return ""
|
|
||||||
return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}"
|
|
||||||
|
|||||||
@ -301,7 +301,7 @@ function handleStreamedEvents(events) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const flightId of events.updated_flights) {
|
for (const flightId of events.updated_flights) {
|
||||||
Flight.withId(flightId).draw();
|
Flight.withId(flightId).update();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const flightId of events.deleted_flights) {
|
for (const flightId of events.deleted_flights) {
|
||||||
@ -775,20 +775,13 @@ class Waypoint {
|
|||||||
return this.waypoint.should_mark;
|
return this.waypoint.should_mark;
|
||||||
}
|
}
|
||||||
|
|
||||||
async timing(dragging) {
|
description() {
|
||||||
if (dragging) {
|
|
||||||
return "Waiting to recompute TOT...";
|
|
||||||
}
|
|
||||||
return await getJson(`/waypoints/${this.flight.id}/${this.number}/timing`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async description(dragging) {
|
|
||||||
const alt = this.waypoint.altitude_ft;
|
const alt = this.waypoint.altitude_ft;
|
||||||
const altRef = this.waypoint.altitude_reference;
|
const altRef = this.waypoint.altitude_reference;
|
||||||
return (
|
return (
|
||||||
`${this.number} ${this.waypoint.name}<br />` +
|
`${this.number} ${this.waypoint.name}<br />` +
|
||||||
`${alt} ft ${altRef}<br />` +
|
`${alt} ft ${altRef}<br />` +
|
||||||
`${await this.timing(dragging)}`
|
`${this.waypoint.timing}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -796,19 +789,13 @@ class Waypoint {
|
|||||||
this.marker.setLatLng(this.position());
|
this.marker.setLatLng(this.position());
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDescription(dragging) {
|
|
||||||
this.description(dragging).then((description) => {
|
|
||||||
this.marker.setTooltipContent(description);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
makeMarker() {
|
makeMarker() {
|
||||||
const zoom = map.getZoom();
|
const zoom = map.getZoom();
|
||||||
const marker = L.marker(this.position(), {
|
const marker = L.marker(this.position(), {
|
||||||
draggable: this.waypoint.is_movable,
|
draggable: this.waypoint.is_movable,
|
||||||
})
|
})
|
||||||
.on("dragstart", (e) => {
|
.on("dragstart", (e) => {
|
||||||
this.updateDescription(true);
|
this.marker.setTooltipContent("Waiting to recompute TOT...");
|
||||||
})
|
})
|
||||||
.on("drag", (e) => {
|
.on("drag", (e) => {
|
||||||
const marker = e.target;
|
const marker = e.target;
|
||||||
@ -824,7 +811,6 @@ class Waypoint {
|
|||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.waypoint.position = destination;
|
this.waypoint.position = destination;
|
||||||
this.updateDescription(false);
|
|
||||||
this.flight.drawCommitBoundary();
|
this.flight.drawCommitBoundary();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -837,11 +823,9 @@ class Waypoint {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (this.flight.selected) {
|
if (this.flight.selected) {
|
||||||
this.description(false).then((description) =>
|
marker.bindTooltip(this.description(), {
|
||||||
marker.bindTooltip(description, {
|
permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM,
|
||||||
permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM,
|
});
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return marker;
|
return marker;
|
||||||
@ -961,6 +945,13 @@ class Flight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
getJson(`/flights/${this.id}?with_waypoints=true`).then((flight) => {
|
||||||
|
this.flight = flight;
|
||||||
|
this.draw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
this.drawAircraftLocation();
|
this.drawAircraftLocation();
|
||||||
this.drawFlightPlan();
|
this.drawFlightPlan();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user