feat(frontend): Add restricted area visualisation
This commit is contained in:
parent
47993d66bc
commit
83bb72faac
13 changed files with 1589 additions and 1465 deletions
|
|
@ -48,12 +48,16 @@
|
|||
let map;
|
||||
let pathLayer;
|
||||
let markerLayer;
|
||||
let restrictedLayer;
|
||||
let servicePointLayer;
|
||||
let currentBounds = fallbackBounds;
|
||||
|
||||
let apiBase = "http://localhost:8080/api/v1";
|
||||
let blackBoxBase = "http://localhost:3000";
|
||||
let dispatchBody = JSON.stringify(defaultDispatch, null, 2);
|
||||
let plannedPath = samplePathResponse;
|
||||
let restrictedAreas = [];
|
||||
let servicePoints = [];
|
||||
let stepSeconds = STEP_SECONDS;
|
||||
|
||||
let status = "System Ready. Waiting for dispatch payload.";
|
||||
|
|
@ -101,7 +105,11 @@
|
|||
|
||||
// Reactive Statements
|
||||
$: timeline = buildTimeline(plannedPath);
|
||||
$: totalSteps = Math.max(timeline.totalSteps, plannedPath?.totalMoves || 1, 1);
|
||||
$: totalSteps = Math.max(
|
||||
timeline.totalSteps,
|
||||
plannedPath?.totalMoves || 1,
|
||||
1,
|
||||
);
|
||||
$: tick = Math.min(tick, totalSteps - 1);
|
||||
$: playbackSeconds = tick * stepSeconds;
|
||||
$: wallClock = computeWallClock(startTime, playbackSeconds);
|
||||
|
|
@ -112,7 +120,12 @@
|
|||
? computeBounds(
|
||||
positionsNow
|
||||
.map((d) => d.current)
|
||||
.filter((p) => p && typeof p.lat === "number" && typeof p.lng === "number"),
|
||||
.filter(
|
||||
(p) =>
|
||||
p &&
|
||||
typeof p.lat === "number" &&
|
||||
typeof p.lng === "number",
|
||||
),
|
||||
)
|
||||
: timeline.bounds || fallbackBounds;
|
||||
|
||||
|
|
@ -178,9 +191,13 @@
|
|||
|
||||
pathLayer = L.layerGroup().addTo(map);
|
||||
markerLayer = L.layerGroup().addTo(map);
|
||||
restrictedLayer = L.layerGroup().addTo(map);
|
||||
servicePointLayer = L.layerGroup().addTo(map);
|
||||
|
||||
fitMapToBounds();
|
||||
refreshMap();
|
||||
loadRestrictedAreas();
|
||||
loadServicePoints();
|
||||
return () => map?.remove();
|
||||
});
|
||||
|
||||
|
|
@ -223,7 +240,40 @@
|
|||
await fetchSnapshotForTime(wallClock, false);
|
||||
}
|
||||
|
||||
async function fetchSnapshotForTime(requestedTime = wallClock, silent = false) {
|
||||
async function loadRestrictedAreas() {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/restrictedAreas`);
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
res.statusText || "failed to load restricted areas",
|
||||
);
|
||||
const data = await res.json();
|
||||
restrictedAreas = Array.isArray(data) ? data : [];
|
||||
drawRestrictedAreas();
|
||||
} catch (err) {
|
||||
console.error("[RestrictedAreas] fetch failed:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadServicePoints() {
|
||||
try {
|
||||
const res = await fetch(`${apiBase}/servicePoints`);
|
||||
if (!res.ok)
|
||||
throw new Error(
|
||||
res.statusText || "failed to load service points",
|
||||
);
|
||||
const data = await res.json();
|
||||
servicePoints = Array.isArray(data) ? data : [];
|
||||
drawServicePoints();
|
||||
} catch (err) {
|
||||
console.error("[ServicePoints] fetch failed:", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSnapshotForTime(
|
||||
requestedTime = wallClock,
|
||||
silent = false,
|
||||
) {
|
||||
if (!requestedTime) return;
|
||||
|
||||
if (silent && requestedTime === lastSnapshotTime) return;
|
||||
|
|
@ -366,6 +416,50 @@
|
|||
});
|
||||
}
|
||||
|
||||
function drawRestrictedAreas() {
|
||||
if (!restrictedLayer) return;
|
||||
restrictedLayer.clearLayers();
|
||||
restrictedAreas.forEach((area) => {
|
||||
const coords = (area?.vertices || [])
|
||||
.map((v) => [v.lat, v.lng])
|
||||
.filter((p) => Array.isArray(p) && p.length === 2);
|
||||
if (coords.length < 3) return;
|
||||
L.polygon(coords, {
|
||||
color: "#ef4444", // red
|
||||
weight: 2,
|
||||
fill: false,
|
||||
dashArray: "6,6",
|
||||
})
|
||||
.bindTooltip(area?.name || "Restricted Area", { sticky: true })
|
||||
.addTo(restrictedLayer);
|
||||
});
|
||||
}
|
||||
|
||||
function drawServicePoints() {
|
||||
if (!servicePointLayer) return;
|
||||
servicePointLayer.clearLayers();
|
||||
servicePoints.forEach((sp) => {
|
||||
const loc = sp?.location;
|
||||
if (
|
||||
!loc ||
|
||||
typeof loc.lat !== "number" ||
|
||||
typeof loc.lng !== "number"
|
||||
)
|
||||
return;
|
||||
const marker = L.marker([loc.lat, loc.lng], {
|
||||
icon: L.divIcon({
|
||||
className: "service-point-icon",
|
||||
html: `<div class="triangle-marker"></div>`,
|
||||
iconSize: [16, 16],
|
||||
iconAnchor: [8, 8],
|
||||
}),
|
||||
}).bindTooltip(sp?.name || `Service Point ${sp?.id ?? ""}`, {
|
||||
sticky: true,
|
||||
});
|
||||
marker.addTo(servicePointLayer);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
theme.update((t) => (t === "dark" ? "light" : "dark"));
|
||||
}
|
||||
|
|
@ -410,9 +504,7 @@
|
|||
const coords = timelinePaths.get(String(id)) || [];
|
||||
const matchedIdx = findClosestIndex(coords, point);
|
||||
const visited =
|
||||
matchedIdx >= 0
|
||||
? coords.slice(0, matchedIdx + 1)
|
||||
: [point];
|
||||
matchedIdx >= 0 ? coords.slice(0, matchedIdx + 1) : [point];
|
||||
const fullPath = coords.length ? coords : [point];
|
||||
return {
|
||||
id,
|
||||
|
|
@ -477,7 +569,6 @@
|
|||
<PlaybackControls
|
||||
{wallClock}
|
||||
{playbackSeconds}
|
||||
totalCost={plannedPath?.totalCost}
|
||||
activeDrones={plannedPath?.dronePaths?.length}
|
||||
{isPlaying}
|
||||
bind:tick
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
/* Arrow should be the same color as the tooltip background */
|
||||
.drone-tooltip.leaflet-tooltip-top::before {
|
||||
border-top-color: theme('colors.gray.800');
|
||||
border-top-color: theme("colors.gray.800");
|
||||
}
|
||||
|
||||
/* Custom Scrollbar */
|
||||
|
|
@ -61,3 +61,12 @@
|
|||
.drone-popup .leaflet-popup-content {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.service-point-icon .triangle-marker {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 10px solid #0ea5e9; /* sky-500 */
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
export let wallClock = "";
|
||||
export let playbackSeconds = 0;
|
||||
export let totalCost = 0;
|
||||
export let activeDrones = 0;
|
||||
export let isPlaying = false;
|
||||
export let tick = 0;
|
||||
|
|
@ -48,22 +47,13 @@
|
|||
>{formatDuration(playbackSeconds)}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs uppercase text-slate-500 dark:text-slate-400"
|
||||
>Total Cost</span
|
||||
>
|
||||
<span
|
||||
class="font-mono text-xl font-bold text-slate-800 dark:text-slate-100"
|
||||
>{totalCost ?? "—"}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs uppercase text-slate-500 dark:text-slate-400"
|
||||
>Active Drones</span
|
||||
>
|
||||
<span
|
||||
class="font-mono text-xl font-bold text-slate-800 dark:text-slate-100"
|
||||
>{activeDrones ?? 0}</span
|
||||
>{snapshot.length ?? 0}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,9 @@
|
|||
</script>
|
||||
|
||||
<aside
|
||||
class="absolute top-0 left-0 bottom-0 w-[380px] pointer-events-auto bg-white/80 dark:bg-slate-900/80 backdrop-blur-xl border border-slate-200 dark:border-slate-800 shadow-2xl flex flex-col m-4 rounded-2xl p-5 gap-5 transition-transform duration-500 ease-in-out {open ? 'translate-x-0' : '-translate-x-[120%]'}"
|
||||
class="absolute top-0 left-0 bottom-0 w-[380px] pointer-events-auto bg-white/80 dark:bg-slate-900/80 backdrop-blur-xl border border-slate-200 dark:border-slate-800 shadow-2xl flex flex-col m-4 rounded-2xl p-5 gap-5 transition-transform duration-500 ease-in-out {open
|
||||
? 'translate-x-0'
|
||||
: '-translate-x-[120%]'}"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h1
|
||||
|
|
@ -90,11 +92,27 @@
|
|||
{/if}
|
||||
</button>
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
on:click={() => dispatch("close")}
|
||||
class="p-2 rounded-lg text-slate-500 hover:text-sky-500 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
|
||||
aria-label="Close Sidebar"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
><line x1="18" y1="6" x2="6" y2="18"></line><line
|
||||
x1="6"
|
||||
y1="6"
|
||||
x2="18"
|
||||
y2="18"
|
||||
></line></svg
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -105,7 +123,9 @@
|
|||
> {status}
|
||||
</div>
|
||||
|
||||
<div class="space-y-3 overflow-y-auto pr-1 custom-scrollbar flex-1 flex flex-col">
|
||||
<div
|
||||
class="space-y-3 overflow-y-auto pr-1 custom-scrollbar flex-1 flex flex-col"
|
||||
>
|
||||
<div class="border-b border-slate-200 dark:border-slate-800 pb-2">
|
||||
<button
|
||||
class="w-full flex items-center justify-between text-sm font-semibold tracking-wider uppercase text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 transition-colors focus:outline-none"
|
||||
|
|
@ -189,7 +209,6 @@
|
|||
class="w-full bg-white/50 dark:bg-black/30 border border-slate-300 dark:border-slate-700 text-slate-800 dark:text-slate-100 p-2 rounded-lg font-mono text-xs transition-all focus:outline-none focus:border-sky-500 focus:bg-white dark:focus:bg-black/50 focus:ring-2 focus:ring-sky-500/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -207,15 +226,16 @@
|
|||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-auto pt-4 border-t border-slate-200 dark:border-slate-800">
|
||||
<div
|
||||
class="flex gap-2 mt-auto pt-4 border-t border-slate-200 dark:border-slate-800"
|
||||
>
|
||||
<button
|
||||
class="flex-1 p-2.5 rounded-lg font-semibold text-sm transition-all disabled:opacity-50 disabled:cursor-not-allowed text-white bg-sky-500 shadow-[0_4px_12px_theme(colors.sky.500/30)] hover:bg-sky-600 disabled:hover:bg-sky-500"
|
||||
on:click={() => dispatch('request')}
|
||||
on:click={() => dispatch("request")}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "CALCULATING..." : "REQUEST PATH"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import './app.css';
|
||||
import App from './App.svelte';
|
||||
import "./app.css";
|
||||
import App from "./App.svelte";
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')
|
||||
target: document.getElementById("app"),
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
import { writable } from 'svelte/store';
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export const theme = writable('dark');
|
||||
export const theme = writable("dark");
|
||||
|
|
|
|||
|
|
@ -43,10 +43,7 @@ function flattenDeliveries(deliveries) {
|
|||
const coords = [];
|
||||
for (const delivery of deliveries || []) {
|
||||
for (const point of delivery.flightPath || []) {
|
||||
if (
|
||||
!coords.length ||
|
||||
!samePoint(coords[coords.length - 1], point)
|
||||
) {
|
||||
if (!coords.length || !samePoint(coords[coords.length - 1], point)) {
|
||||
coords.push(point);
|
||||
}
|
||||
}
|
||||
|
|
@ -97,9 +94,7 @@ export function formatDuration(sec) {
|
|||
export function computeWallClock(start, seconds) {
|
||||
const base = start ? new Date(start) : new Date();
|
||||
const ts = new Date(base.getTime() + seconds * 1000);
|
||||
return isNaN(ts.getTime())
|
||||
? new Date().toISOString()
|
||||
: ts.toISOString();
|
||||
return isNaN(ts.getTime()) ? new Date().toISOString() : ts.toISOString();
|
||||
}
|
||||
|
||||
export function boundsKey(bounds) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
bun
|
||||
svelte-language-server
|
||||
typescript-language-server
|
||||
prettier
|
||||
];
|
||||
shellHook = ''
|
||||
export JAVA_HOME=${pkgs.jdk21}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,20 @@ body:json {
|
|||
"lng": -3.167074009381139,
|
||||
"lat": 55.94740195123114
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"date": "2025-11-22",
|
||||
"time": "09:35",
|
||||
"requirements": {
|
||||
"capacity": 0.85,
|
||||
"heating": true,
|
||||
"maxCost": 13.5
|
||||
},
|
||||
"delivery": {
|
||||
"lng": -3.1891515471685636,
|
||||
"lat": 55.95347060952466
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.controller;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
public class GeoJsonDataController {
|
||||
|
||||
private final DroneInfoService droneInfoService;
|
||||
|
||||
public GeoJsonDataController(DroneInfoService droneInfoService) {
|
||||
this.droneInfoService = droneInfoService;
|
||||
}
|
||||
|
||||
@GetMapping("/getAllRestrictedAreaByGeoJson")
|
||||
public String getAllRestrictedAreaGeoJson() throws JsonProcessingException {
|
||||
return droneInfoService.fetchRestrictedAreasInGeoJson().stream().reduce("", String::concat);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package io.github.js0ny.ilp_coursework.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.external.RestrictedArea;
|
||||
import io.github.js0ny.ilp_coursework.data.external.ServicePoint;
|
||||
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1")
|
||||
public class MapMetaController {
|
||||
|
||||
private final DroneInfoService droneInfoService;
|
||||
|
||||
public MapMetaController(DroneInfoService droneInfoService) {
|
||||
this.droneInfoService = droneInfoService;
|
||||
}
|
||||
|
||||
@GetMapping("/restrictedAreas")
|
||||
public List<RestrictedArea> getRestrictedAreas() {
|
||||
return droneInfoService.fetchRestrictedAreas();
|
||||
}
|
||||
|
||||
@GetMapping("/servicePoints")
|
||||
public List<ServicePoint> getServicePoints() {
|
||||
return droneInfoService.fetchServicePoints();
|
||||
}
|
||||
}
|
||||
|
|
@ -54,10 +54,12 @@ public class DroneInfoService {
|
|||
/**
|
||||
* Return an array of ids of drones with/without cooling capability
|
||||
*
|
||||
* <p>Associated service method with {@code /dronesWithCooling/{state}}
|
||||
* <p>
|
||||
* Associated service method with {@code /dronesWithCooling/{state}}
|
||||
*
|
||||
* @param state determines the capability filtering
|
||||
* @return if {@code state} is true, return ids of drones with cooling capability, else without
|
||||
* @return if {@code state} is true, return ids of drones with cooling
|
||||
* capability, else without
|
||||
* cooling
|
||||
* @see
|
||||
* io.github.js0ny.ilp_coursework.controller.DroneController#getDronesWithCoolingCapability(boolean)
|
||||
|
|
@ -80,12 +82,15 @@ public class DroneInfoService {
|
|||
/**
|
||||
* Return a {@link Drone}-style json data structure with the given {@code id}
|
||||
*
|
||||
* <p>Associated service method with {@code /droneDetails/{id}}
|
||||
* <p>
|
||||
* Associated service method with {@code /droneDetails/{id}}
|
||||
*
|
||||
* @param id The id of the drone
|
||||
* @return drone json body of given id
|
||||
* @throws NullPointerException when cannot fetch available drones from remote
|
||||
* @throws IllegalArgumentException when drone with given {@code id} cannot be found this should
|
||||
* @throws NullPointerException when cannot fetch available drones from
|
||||
* remote
|
||||
* @throws IllegalArgumentException when drone with given {@code id} cannot be
|
||||
* found this should
|
||||
* lead to a 404
|
||||
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getDroneDetail(String)
|
||||
*/
|
||||
|
|
@ -103,10 +108,12 @@ public class DroneInfoService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return an array of ids of drones that match all the requirements in the medical dispatch
|
||||
* Return an array of ids of drones that match all the requirements in the
|
||||
* medical dispatch
|
||||
* records
|
||||
*
|
||||
* <p>Associated service method with
|
||||
* <p>
|
||||
* Associated service method with
|
||||
*
|
||||
* @param rec array of medical dispatch records
|
||||
* @return List of drone ids that match all the requirements
|
||||
|
|
@ -128,8 +135,7 @@ public class DroneInfoService {
|
|||
return drones.stream()
|
||||
.filter(d -> d != null && d.capability() != null)
|
||||
.filter(
|
||||
d ->
|
||||
Arrays.stream(rec)
|
||||
d -> Arrays.stream(rec)
|
||||
.filter(r -> r != null && r.requirements() != null)
|
||||
.allMatch(r -> droneMatchesRequirement(d, r)))
|
||||
.map(Drone::id)
|
||||
|
|
@ -142,8 +148,10 @@ public class DroneInfoService {
|
|||
* @param drone the drone to be checked
|
||||
* @param record the medical dispatch record containing the requirement
|
||||
* @return true if the drone meets the requirement, false otherwise
|
||||
* @throws IllegalArgumentException when record requirements or drone capability is invalid
|
||||
* (capacity and id cannot be null in {@code MedDispathRecDto})
|
||||
* @throws IllegalArgumentException when record requirements or drone capability
|
||||
* is invalid
|
||||
* (capacity and id cannot be null in
|
||||
* {@code MedDispathRecDto})
|
||||
*/
|
||||
public boolean droneMatchesRequirement(Drone drone, MedDispatchRecRequest record) {
|
||||
var requirements = record.requirements();
|
||||
|
|
@ -183,13 +191,13 @@ public class DroneInfoService {
|
|||
* Helper to check if a drone is available at the required date and time
|
||||
*
|
||||
* @param droneId the id of the drone to be checked
|
||||
* @param record the medical dispatch record containing the required date and time
|
||||
* @param record the medical dispatch record containing the required date and
|
||||
* time
|
||||
* @return true if the drone is available, false otherwise
|
||||
*/
|
||||
private boolean checkAvailability(String droneId, MedDispatchRecRequest record) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
|
||||
ServicePointDrones[] servicePoints =
|
||||
restTemplate.getForObject(droneUrl, ServicePointDrones[].class);
|
||||
ServicePointDrones[] servicePoints = restTemplate.getForObject(droneUrl, ServicePointDrones[].class);
|
||||
|
||||
LocalDate requiredDate = record.date();
|
||||
DayOfWeek requiredDay = requiredDate.getDayOfWeek();
|
||||
|
|
@ -208,8 +216,7 @@ public class DroneInfoService {
|
|||
|
||||
private LngLat queryServicePointLocationByDroneId(String droneId) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
|
||||
ServicePointDrones[] servicePoints =
|
||||
restTemplate.getForObject(droneUrl, ServicePointDrones[].class);
|
||||
ServicePointDrones[] servicePoints = restTemplate.getForObject(droneUrl, ServicePointDrones[].class);
|
||||
|
||||
assert servicePoints != null;
|
||||
for (var sp : servicePoints) {
|
||||
|
|
@ -226,8 +233,7 @@ public class DroneInfoService {
|
|||
private LngLat queryServicePointLocation(int id) {
|
||||
URI servicePointUrl = URI.create(baseUrl).resolve(servicePointsEndpoint);
|
||||
|
||||
ServicePoint[] servicePoints =
|
||||
restTemplate.getForObject(servicePointUrl, ServicePoint[].class);
|
||||
ServicePoint[] servicePoints = restTemplate.getForObject(servicePointUrl, ServicePoint[].class);
|
||||
|
||||
assert servicePoints != null;
|
||||
for (var sp : servicePoints) {
|
||||
|
|
@ -250,31 +256,22 @@ public class DroneInfoService {
|
|||
|
||||
public List<RestrictedArea> fetchRestrictedAreas() {
|
||||
URI restrictedUrl = URI.create(baseUrl).resolve(restrictedAreasEndpoint);
|
||||
RestrictedArea[] restrictedAreas =
|
||||
restTemplate.getForObject(restrictedUrl, RestrictedArea[].class);
|
||||
RestrictedArea[] restrictedAreas = restTemplate.getForObject(restrictedUrl, RestrictedArea[].class);
|
||||
assert restrictedAreas != null;
|
||||
return Arrays.asList(restrictedAreas);
|
||||
}
|
||||
|
||||
public List<String> fetchRestrictedAreasInGeoJson() throws JsonProcessingException {
|
||||
var mapper = new ObjectMapper();
|
||||
var ras = fetchRestrictedAreas();
|
||||
var geoJson = ras.stream().map(RestrictedArea::toRegion).map(Region::toGeoJson).toList();
|
||||
return Collections.singletonList(mapper.writeValueAsString(geoJson));
|
||||
}
|
||||
|
||||
public List<ServicePoint> fetchServicePoints() {
|
||||
URI servicePointUrl = URI.create(baseUrl).resolve(servicePointsEndpoint);
|
||||
ServicePoint[] servicePoints =
|
||||
restTemplate.getForObject(servicePointUrl, ServicePoint[].class);
|
||||
ServicePoint[] servicePoints = restTemplate.getForObject(servicePointUrl, ServicePoint[].class);
|
||||
assert servicePoints != null;
|
||||
return Arrays.asList(servicePoints);
|
||||
}
|
||||
|
||||
public List<ServicePointDrones> fetchDronesForServicePoints() {
|
||||
URI servicePointDronesUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
|
||||
ServicePointDrones[] servicePointDrones =
|
||||
restTemplate.getForObject(servicePointDronesUrl, ServicePointDrones[].class);
|
||||
ServicePointDrones[] servicePointDrones = restTemplate.getForObject(servicePointDronesUrl,
|
||||
ServicePointDrones[].class);
|
||||
assert servicePointDrones != null;
|
||||
return Arrays.asList(servicePointDrones);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue