Draggable waypoints with timing info.

https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
Dan Albert 2022-03-04 02:21:22 -08:00
parent 6933470ce0
commit 811f46c289
6 changed files with 91 additions and 71 deletions

View File

@ -8,4 +8,5 @@ export interface Waypoint {
is_movable: boolean;
should_mark: boolean;
include_in_path: boolean;
timing: string;
}

View File

@ -1,5 +1,6 @@
import { Flight } from "../../api/flight";
import WaypointMarker from "../waypointmarker";
import { ReactElement } from "react";
import { Polyline } from "react-leaflet";
const BLUE_PATH = "#0084ff";
@ -39,25 +40,23 @@ function FlightPlanPath(props: FlightPlanProps) {
}
const WaypointMarkers = (props: FlightPlanProps) => {
if (!props.selected || props.flight.waypoints == null) {
if (props.selected && props.flight.waypoints == null) {
return <></>;
}
return (
<>
{props.flight.waypoints
.filter((p) => p.should_mark)
.map((p, idx) => {
return (
var markers: ReactElement[] = [];
props.flight.waypoints?.forEach((p, idx) => {
markers.push(
<WaypointMarker
key={idx}
number={idx}
waypoint={p}
></WaypointMarker>
);
})}
</>
flight={props.flight}
/>
);
});
return <>{markers}</>;
};
export default function FlightPlan(props: FlightPlanProps) {

View File

@ -1,9 +1,11 @@
import backend from "../../api/backend";
import { Flight } from "../../api/flight";
import { Waypoint } from "../../api/waypoint";
import { Icon } from "leaflet";
import { Marker as LMarker } from "leaflet";
import icon from "leaflet/dist/images/marker-icon.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";
const WAYPOINT_ICON = new Icon({
@ -15,6 +17,7 @@ const WAYPOINT_ICON = new Icon({
interface WaypointMarkerProps {
number: number;
waypoint: Waypoint;
flight: Flight;
}
const WaypointMarker = (props: WaypointMarkerProps) => {
@ -50,24 +53,42 @@ const WaypointMarker = (props: WaypointMarkerProps) => {
}, [map]);
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;
return (
<Marker
position={waypoint.position}
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) => {
if (ref != null) {
marker.current = ref;
}
}}
>
<Tooltip position={waypoint.position}>
{`${props.number} ${waypoint.name}`}
<br />
{`${waypoint.altitude_ft} ft ${waypoint.altitude_reference}`}
<br />
TODO: Timing info
</Tooltip>
<Tooltip position={waypoint.position} />
</Marker>
);
};

View File

@ -1,12 +1,29 @@
from __future__ import annotations
from datetime import timedelta
from pydantic import BaseModel
from game.ato import FlightWaypoint
from game.ato import Flight, FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
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):
name: str
position: LeafletPoint
@ -15,9 +32,12 @@ class FlightWaypointJs(BaseModel):
is_movable: bool
should_mark: bool
include_in_path: bool
timing: str
@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
# only the center of the objective. Allow moving the latter since its exact
# location isn't very important.
@ -67,4 +87,5 @@ class FlightWaypointJs(BaseModel):
is_movable=is_movable,
should_mark=should_mark,
include_in_path=include_in_path,
timing=timing_info(flight, waypoint_idx),
)

View File

@ -1,4 +1,3 @@
from datetime import timedelta
from uuid import UUID
from dcs.mapping import LatLng, Point
@ -11,6 +10,7 @@ from game.ato.flightwaypointtype import FlightWaypointType
from game.server import GameContext
from game.server.leaflet import LeafletPoint
from game.server.waypoints.models import FlightWaypointJs
from game.sim import GameUpdateEvents
from game.utils import meters
router: APIRouter = APIRouter(prefix="/waypoints")
@ -24,10 +24,13 @@ def waypoints_for_flight(flight: Flight) -> list[FlightWaypointJs]:
flight.departure.position,
meters(0),
"RADIO",
)
),
flight,
0,
)
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,
game: Game = Depends(GameContext.get),
) -> None:
from game.server import EventStream
flight = game.db.flights.get(flight_id)
if waypoint_idx == 0:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
@ -64,22 +69,4 @@ def set_position(
detail=f"Could not find PackageModel owning {flight}",
)
package_model.update_tot()
@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()))}"
EventStream.put_nowait(GameUpdateEvents().update_flight(flight))

View File

@ -301,7 +301,7 @@ function handleStreamedEvents(events) {
}
for (const flightId of events.updated_flights) {
Flight.withId(flightId).draw();
Flight.withId(flightId).update();
}
for (const flightId of events.deleted_flights) {
@ -775,20 +775,13 @@ class Waypoint {
return this.waypoint.should_mark;
}
async timing(dragging) {
if (dragging) {
return "Waiting to recompute TOT...";
}
return await getJson(`/waypoints/${this.flight.id}/${this.number}/timing`);
}
async description(dragging) {
description() {
const alt = this.waypoint.altitude_ft;
const altRef = this.waypoint.altitude_reference;
return (
`${this.number} ${this.waypoint.name}<br />` +
`${alt} ft ${altRef}<br />` +
`${await this.timing(dragging)}`
`${this.waypoint.timing}`
);
}
@ -796,19 +789,13 @@ class Waypoint {
this.marker.setLatLng(this.position());
}
updateDescription(dragging) {
this.description(dragging).then((description) => {
this.marker.setTooltipContent(description);
});
}
makeMarker() {
const zoom = map.getZoom();
const marker = L.marker(this.position(), {
draggable: this.waypoint.is_movable,
})
.on("dragstart", (e) => {
this.updateDescription(true);
this.marker.setTooltipContent("Waiting to recompute TOT...");
})
.on("drag", (e) => {
const marker = e.target;
@ -824,7 +811,6 @@ class Waypoint {
)
.then(() => {
this.waypoint.position = destination;
this.updateDescription(false);
this.flight.drawCommitBoundary();
})
.catch((err) => {
@ -837,11 +823,9 @@ class Waypoint {
});
if (this.flight.selected) {
this.description(false).then((description) =>
marker.bindTooltip(description, {
marker.bindTooltip(this.description(), {
permanent: zoom >= SHOW_WAYPOINT_INFO_AT_ZOOM,
})
);
});
}
return marker;
@ -961,6 +945,13 @@ class Flight {
}
}
update() {
getJson(`/flights/${this.id}?with_waypoints=true`).then((flight) => {
this.flight = flight;
this.draw();
});
}
draw() {
this.drawAircraftLocation();
this.drawFlightPlan();