Draw flight plan paths in the react UI.

https://github.com/dcs-liberation/dcs_liberation/issues/2039
This commit is contained in:
Dan Albert
2022-03-01 00:08:34 -08:00
parent bd8aa0296b
commit 406a64ae3f
12 changed files with 183 additions and 18 deletions

View File

@@ -1,12 +1,14 @@
import { LatLng } from "leaflet";
import "./App.css";
import { LiberationMap } from "./map/liberationmap/LiberationMap";
import { ControlPoint } from "./game/controlpoint";
import { useEffect } from "react";
import { useAppDispatch } from "./app/hooks";
import { setControlPoints } from "./game/theater/theaterSlice";
import { Flight } from "./game/flight";
import { LatLng } from "leaflet";
import { LiberationMap } from "./map/liberationmap/LiberationMap";
import axios from "axios";
import { registerFlight } from "./game/ato/atoSlice";
import { setControlPoints } from "./game/theater/theaterSlice";
import { useAppDispatch } from "./app/hooks";
import { useEffect } from "react";
function App() {
const mapCenter: LatLng = new LatLng(25.58, 54.9);
@@ -22,6 +24,16 @@ function App() {
dispatch(setControlPoints(response.data as ControlPoint[]));
}
});
axios
.get("http://[::1]:5000/flights?with_waypoints=true")
.catch((error) => console.log(`Error fetching flights: ${error}`))
.then((response) => {
if (response != null) {
for (const flight of response.data) {
dispatch(registerFlight(flight as Flight));
}
}
});
});
console.log(`mapCenter=${mapCenter}`);

View File

@@ -1,8 +1,11 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
import atoReducer from "../game/ato/atoSlice";
import theaterReducer from "../game/theater/theaterSlice";
export const store = configureStore({
reducer: {
atos: atoReducer,
theater: theaterReducer,
},
});

View File

@@ -0,0 +1,53 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { Flight } from "../flight";
import { RootState } from "../../app/store";
interface AtoState {
blue: { [id: string]: Flight };
red: { [id: string]: Flight };
}
const initialState: AtoState = {
blue: {},
red: {},
};
export const atoSlice = createSlice({
name: "ato",
initialState,
reducers: {
clearFlights: (state) => {
state.blue = {};
state.red = {};
},
registerFlight: (state, action: PayloadAction<Flight>) => {
const flight = action.payload;
const ato = flight.blue ? state.blue : state.red;
if (flight.id in ato) {
console.log(`Overriding flight with ID: ${flight.id}`);
}
ato[flight.id] = flight;
},
unregisterFlight: (state, action: PayloadAction<string>) => {
const id = action.payload;
if (id in state.blue) {
delete state.blue[id];
} else if (id in state.red) {
delete state.red[id];
} else {
console.log(
`Could not delete flight with ID ${id} because no flight with that ` +
`ID exists`
);
}
},
},
});
export const { clearFlights, registerFlight, unregisterFlight } =
atoSlice.actions;
export const selectAtos = (state: RootState) => state.atos;
export default atoSlice.reducer;

10
client/src/game/flight.ts Normal file
View File

@@ -0,0 +1,10 @@
import { LatLng } from "leaflet";
import { Waypoint } from "./waypoint";
export interface Flight {
id: string;
blue: boolean;
position: LatLng;
sidc: string;
waypoints: Waypoint[] | null;
}

View File

@@ -0,0 +1,11 @@
import { LatLng } from "leaflet";
export interface Waypoint {
name: string;
position: LatLng;
altitude_ft: number;
altitude_reference: string;
is_movable: boolean;
should_mark: boolean;
include_in_path: boolean;
}

View File

@@ -0,0 +1,33 @@
import { Flight } from "../../game/flight";
import { Polyline } from "react-leaflet";
const BLUE_PATH = "#0084ff";
const RED_PATH = "#c85050";
interface FlightPlanProps {
flight: Flight;
selected: boolean;
}
export function FlightPlanPath(props: FlightPlanProps) {
const color = props.flight.blue ? BLUE_PATH : RED_PATH;
const waypoints = props.flight.waypoints;
if (waypoints == null) {
return <></>;
}
const points = waypoints.map((waypoint) => waypoint.position);
return (
<Polyline
positions={points}
pathOptions={{ color: color, interactive: false }}
/>
);
}
export function FlightPlan(props: FlightPlanProps) {
return (
<>
<FlightPlanPath {...props} />
</>
);
}

View File

@@ -0,0 +1,20 @@
import { FlightPlan } from "../flightplan/FlightPlan";
import { LayerGroup } from "react-leaflet";
import { selectAtos } from "../../game/ato/atoSlice";
import { useAppSelector } from "../../app/hooks";
interface FlightPlansLayerProps {
blue: boolean;
}
export function FlightPlansLayer(props: FlightPlansLayerProps) {
const atos = useAppSelector(selectAtos);
const flights = props.blue ? atos.blue : atos.red;
return (
<LayerGroup>
{Object.values(flights).map((flight) => {
return <FlightPlan key={flight.id} flight={flight} selected={false} />;
})}
</LayerGroup>
);
}

View File

@@ -3,6 +3,7 @@ import { BasemapLayer } from "react-esri-leaflet";
import { ControlPointsLayer } from "../controlpointslayer/ControlPointsLayer";
import "./LiberationMap.css";
import { LatLng } from "leaflet";
import { FlightPlansLayer } from "../flightplanslayer/FlightPlansLayer";
interface GameProps {
mapCenter: LatLng;
@@ -13,6 +14,8 @@ export function LiberationMap(props: GameProps) {
<MapContainer zoom={8} center={props.mapCenter}>
<BasemapLayer name="ImageryClarity" />
<ControlPointsLayer />
<FlightPlansLayer blue={true} />
<FlightPlansLayer blue={false} />
</MapContainer>
);
}