glue go and java
This commit is contained in:
parent
3bbfed11fa
commit
8e462fedc1
4 changed files with 217 additions and 64 deletions
|
|
@ -14,9 +14,9 @@ import (
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Define the DroneEvent struct as JSON
|
// DroneEvent defines the DroneEvent struct as JSON
|
||||||
type DroneEvent struct {
|
type DroneEvent struct {
|
||||||
DroneID string `json:"drone_id"`
|
DroneID string `json:"droneId"`
|
||||||
Latitude float64 `json:"latitude"`
|
Latitude float64 `json:"latitude"`
|
||||||
Longitude float64 `json:"longitude"`
|
Longitude float64 `json:"longitude"`
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package io.github.js0ny.ilp_coursework.data.common;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
|
||||||
|
|
||||||
|
// Corresponding in Go
|
||||||
|
//
|
||||||
|
|
||||||
|
/*
|
||||||
|
* type DroneEvent struct {
|
||||||
|
* DroneID string `json:"drone_id"`
|
||||||
|
* Latitude float64 `json:"latitude"`
|
||||||
|
* Longitude float64 `json:"longitude"`
|
||||||
|
* Timestamp string `json:"timestamp"`
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
public record DroneEvent(
|
||||||
|
String droneId,
|
||||||
|
double latitude,
|
||||||
|
double longitude,
|
||||||
|
String timestamp) {
|
||||||
|
// Helper method that converts from DeliveryPathResponse to List<DroneEvent>
|
||||||
|
public static List<DroneEvent> fromPathResponse(DeliveryPathResponse resp) {
|
||||||
|
List<DroneEvent> events = new java.util.ArrayList<>();
|
||||||
|
for (var p : resp.dronePaths()) {
|
||||||
|
String id = p.droneId() + "";
|
||||||
|
for (var d : p.deliveries()) {
|
||||||
|
for (var coord : d.flightPath()) {
|
||||||
|
String timestamp = java.time.Instant.now().toString();
|
||||||
|
events.add(new DroneEvent(
|
||||||
|
id,
|
||||||
|
coord.lat(),
|
||||||
|
coord.lng(),
|
||||||
|
timestamp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method that converts from DeliveryPathResponse to List<DroneEvent>
|
||||||
|
// with base timestamp
|
||||||
|
public static List<DroneEvent> fromPathResponseWithTimestamp(DeliveryPathResponse resp,
|
||||||
|
LocalDateTime baseTimestamp) {
|
||||||
|
List<DroneEvent> events = new java.util.ArrayList<>();
|
||||||
|
java.time.LocalDateTime timestamp = baseTimestamp;
|
||||||
|
for (var p : resp.dronePaths()) {
|
||||||
|
String id = String.valueOf(p.droneId());
|
||||||
|
for (var d : p.deliveries()) {
|
||||||
|
for (var coord : d.flightPath()) {
|
||||||
|
events.add(new DroneEvent(
|
||||||
|
id,
|
||||||
|
coord.lat(),
|
||||||
|
coord.lng(),
|
||||||
|
timestamp.toString()));
|
||||||
|
timestamp = timestamp.plusSeconds(1); // Increment timestamp for each event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
|
||||||
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse.DronePath;
|
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse.DronePath;
|
||||||
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse.DronePath.Delivery;
|
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse.DronePath.Delivery;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -42,8 +43,10 @@ import java.util.stream.Collectors;
|
||||||
public class PathFinderService {
|
public class PathFinderService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hard stop on how many pathfinding iterations we attempt for a single segment before bailing,
|
* Hard stop on how many pathfinding iterations we attempt for a single segment
|
||||||
* useful for preventing infinite loops caused by precision quirks or unexpected map data.
|
* before bailing,
|
||||||
|
* useful for preventing infinite loops caused by precision quirks or unexpected
|
||||||
|
* map data.
|
||||||
*
|
*
|
||||||
* @see #computePath(LngLat, LngLat)
|
* @see #computePath(LngLat, LngLat)
|
||||||
*/
|
*/
|
||||||
|
|
@ -59,12 +62,18 @@ public class PathFinderService {
|
||||||
private final Map<Integer, LngLat> servicePointLocations;
|
private final Map<Integer, LngLat> servicePointLocations;
|
||||||
private final List<Region> restrictedRegions;
|
private final List<Region> restrictedRegions;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private TelemetryService telemetryService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for PathFinderService. The dependencies are injected by Spring and the
|
* Constructor for PathFinderService. The dependencies are injected by Spring
|
||||||
* constructor pre-computes reference maps used throughout the request lifecycle.
|
* and the
|
||||||
|
* constructor pre-computes reference maps used throughout the request
|
||||||
|
* lifecycle.
|
||||||
*
|
*
|
||||||
* @param gpsCalculationService Service handling geometric operations.
|
* @param gpsCalculationService Service handling geometric operations.
|
||||||
* @param droneInfoService Service that exposes drone metadata and capability information.
|
* @param droneInfoService Service that exposes drone metadata and
|
||||||
|
* capability information.
|
||||||
*/
|
*/
|
||||||
public PathFinderService(
|
public PathFinderService(
|
||||||
GpsCalculationService gpsCalculationService, DroneInfoService droneInfoService) {
|
GpsCalculationService gpsCalculationService, DroneInfoService droneInfoService) {
|
||||||
|
|
@ -77,8 +86,7 @@ public class PathFinderService {
|
||||||
|
|
||||||
this.drones = droneInfoService.fetchAllDrones();
|
this.drones = droneInfoService.fetchAllDrones();
|
||||||
List<ServicePoint> servicePoints = droneInfoService.fetchServicePoints();
|
List<ServicePoint> servicePoints = droneInfoService.fetchServicePoints();
|
||||||
List<ServicePointDrones> servicePointAssignments =
|
List<ServicePointDrones> servicePointAssignments = droneInfoService.fetchDronesForServicePoints();
|
||||||
droneInfoService.fetchDronesForServicePoints();
|
|
||||||
List<RestrictedArea> restrictedAreas = droneInfoService.fetchRestrictedAreas();
|
List<RestrictedArea> restrictedAreas = droneInfoService.fetchRestrictedAreas();
|
||||||
|
|
||||||
this.droneById = this.drones.stream().collect(Collectors.toMap(Drone::id, drone -> drone));
|
this.droneById = this.drones.stream().collect(Collectors.toMap(Drone::id, drone -> drone));
|
||||||
|
|
@ -96,17 +104,17 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.servicePointLocations =
|
this.servicePointLocations = servicePoints.stream()
|
||||||
servicePoints.stream()
|
.collect(
|
||||||
.collect(
|
Collectors.toMap(
|
||||||
Collectors.toMap(
|
ServicePoint::id, sp -> new LngLat(sp.location())));
|
||||||
ServicePoint::id, sp -> new LngLat(sp.location())));
|
|
||||||
|
|
||||||
this.restrictedRegions = restrictedAreas.stream().map(RestrictedArea::toRegion).toList();
|
this.restrictedRegions = restrictedAreas.stream().map(RestrictedArea::toRegion).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Produce a delivery plan for the provided dispatch records. Deliveries are grouped per
|
* Produce a delivery plan for the provided dispatch records. Deliveries are
|
||||||
|
* grouped per
|
||||||
* compatible drone and per trip to satisfy each drone move limit.
|
* compatible drone and per trip to satisfy each drone move limit.
|
||||||
*
|
*
|
||||||
* @param records Dispatch records to be fulfilled.
|
* @param records Dispatch records to be fulfilled.
|
||||||
|
|
@ -148,17 +156,14 @@ public class PathFinderService {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MedDispatchRecRequest> sortedDeliveries =
|
List<MedDispatchRecRequest> sortedDeliveries = entry.getValue().stream()
|
||||||
entry.getValue().stream()
|
.sorted(
|
||||||
.sorted(
|
Comparator.comparingDouble(
|
||||||
Comparator.comparingDouble(
|
rec -> gpsCalculationService.calculateDistance(
|
||||||
rec ->
|
servicePointLocation, rec.delivery())))
|
||||||
gpsCalculationService.calculateDistance(
|
.toList();
|
||||||
servicePointLocation, rec.delivery())))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
List<List<MedDispatchRecRequest>> trips =
|
List<List<MedDispatchRecRequest>> trips = splitTrips(sortedDeliveries, drone, servicePointLocation);
|
||||||
splitTrips(sortedDeliveries, drone, servicePointLocation);
|
|
||||||
|
|
||||||
for (List<MedDispatchRecRequest> trip : trips) {
|
for (List<MedDispatchRecRequest> trip : trips) {
|
||||||
TripResult result = buildTrip(drone, servicePointLocation, trip);
|
TripResult result = buildTrip(drone, servicePointLocation, trip);
|
||||||
|
|
@ -170,15 +175,22 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DeliveryPathResponse(totalCost, totalMoves, paths.toArray(new DronePath[0]));
|
var resp = new DeliveryPathResponse(totalCost, totalMoves, paths.toArray(new DronePath[0]));
|
||||||
|
|
||||||
|
telemetryService.sendEventAsyncByPathResponse(resp);
|
||||||
|
|
||||||
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Convenience wrapper around {@link #calculateDeliveryPath} that serializes the result into a
|
* Convenience wrapper around {@link #calculateDeliveryPath} that serializes the
|
||||||
|
* result into a
|
||||||
* GeoJSON FeatureCollection suitable for mapping visualization.
|
* GeoJSON FeatureCollection suitable for mapping visualization.
|
||||||
*
|
*
|
||||||
* @param records Dispatch records to be fulfilled.
|
* @param records Dispatch records to be fulfilled.
|
||||||
|
*
|
||||||
* @return GeoJSON payload representing every delivery flight path.
|
* @return GeoJSON payload representing every delivery flight path.
|
||||||
|
*
|
||||||
* @throws IllegalStateException When the payload cannot be serialized.
|
* @throws IllegalStateException When the payload cannot be serialized.
|
||||||
*/
|
*/
|
||||||
public String calculateDeliveryPathAsGeoJson(MedDispatchRecRequest[] records) {
|
public String calculateDeliveryPathAsGeoJson(MedDispatchRecRequest[] records) {
|
||||||
|
|
@ -227,8 +239,10 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group dispatch records by their assigned drone, ensuring every record is routed through
|
* Group dispatch records by their assigned drone, ensuring every record is
|
||||||
* {@link #findBestDrone(MedDispatchRecRequest)} exactly once and discarding invalid entries.
|
* routed through
|
||||||
|
* {@link #findBestDrone(MedDispatchRecRequest)} exactly once and discarding
|
||||||
|
* invalid entries.
|
||||||
*
|
*
|
||||||
* @param records Dispatch records to be grouped.
|
* @param records Dispatch records to be grouped.
|
||||||
* @return Map keyed by drone ID with the deliveries it should service.
|
* @return Map keyed by drone ID with the deliveries it should service.
|
||||||
|
|
@ -247,7 +261,8 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose the best drone for the provided record. Currently that equates to picking the closest
|
* Choose the best drone for the provided record. Currently that equates to
|
||||||
|
* picking the closest
|
||||||
* compatible drone to the delivery location.
|
* compatible drone to the delivery location.
|
||||||
*
|
*
|
||||||
* @param record Dispatch record that needs fulfillment.
|
* @param record Dispatch record that needs fulfillment.
|
||||||
|
|
@ -272,9 +287,8 @@ public class PathFinderService {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
double distance =
|
double distance = gpsCalculationService.calculateDistance(
|
||||||
gpsCalculationService.calculateDistance(
|
servicePointLocation, record.delivery());
|
||||||
servicePointLocation, record.delivery());
|
|
||||||
|
|
||||||
if (distance < bestScore) {
|
if (distance < bestScore) {
|
||||||
bestScore = distance;
|
bestScore = distance;
|
||||||
|
|
@ -288,14 +302,16 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Break a sequence of deliveries into several trips that each respect the drone move limit. The
|
* Break a sequence of deliveries into several trips that each respect the drone
|
||||||
|
* move limit. The
|
||||||
* deliveries should already be ordered by proximity for sensible grouping.
|
* deliveries should already be ordered by proximity for sensible grouping.
|
||||||
*
|
*
|
||||||
* @param deliveries Deliveries assigned to a drone.
|
* @param deliveries Deliveries assigned to a drone.
|
||||||
* @param drone Drone that will service the deliveries.
|
* @param drone Drone that will service the deliveries.
|
||||||
* @param servicePoint Starting and ending point of every trip.
|
* @param servicePoint Starting and ending point of every trip.
|
||||||
* @return Partitioned trips with at least one delivery each.
|
* @return Partitioned trips with at least one delivery each.
|
||||||
* @throws IllegalStateException If a single delivery exceeds the drone's move limit.
|
* @throws IllegalStateException If a single delivery exceeds the drone's move
|
||||||
|
* limit.
|
||||||
*/
|
*/
|
||||||
private List<List<MedDispatchRecRequest>> splitTrips(
|
private List<List<MedDispatchRecRequest>> splitTrips(
|
||||||
List<MedDispatchRecRequest> deliveries, Drone drone, LngLat servicePoint) {
|
List<MedDispatchRecRequest> deliveries, Drone drone, LngLat servicePoint) {
|
||||||
|
|
@ -331,13 +347,15 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a single trip for the provided drone, including the entire flight path to every
|
* Build a single trip for the provided drone, including the entire flight path
|
||||||
* delivery and back home. The resulting structure contains the {@link DronePath} representation
|
* to every
|
||||||
|
* delivery and back home. The resulting structure contains the
|
||||||
|
* {@link DronePath} representation
|
||||||
* as well as cost and moves consumed.
|
* as well as cost and moves consumed.
|
||||||
*
|
*
|
||||||
* @param drone Drone executing the trip.
|
* @param drone Drone executing the trip.
|
||||||
* @param servicePoint Starting/ending location of the trip.
|
* @param servicePoint Starting/ending location of the trip.
|
||||||
* @param deliveries Deliveries to include in the trip in execution order.
|
* @param deliveries Deliveries to include in the trip in execution order.
|
||||||
* @return Trip information or {@code null} if no deliveries are provided.
|
* @return Trip information or {@code null} if no deliveries are provided.
|
||||||
* @see DeliveryPathResponse.DronePath
|
* @see DeliveryPathResponse.DronePath
|
||||||
*/
|
*/
|
||||||
|
|
@ -377,10 +395,9 @@ public class PathFinderService {
|
||||||
flightPlans.add(new Delivery(delivery.id(), flightPath));
|
flightPlans.add(new Delivery(delivery.id(), flightPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
float cost =
|
float cost = drone.capability().costInitial()
|
||||||
drone.capability().costInitial()
|
+ drone.capability().costFinal()
|
||||||
+ drone.capability().costFinal()
|
+ (float) (drone.capability().costPerMove() * moves);
|
||||||
+ (float) (drone.capability().costPerMove() * moves);
|
|
||||||
|
|
||||||
DronePath path = new DronePath(drone.parseId(), flightPlans);
|
DronePath path = new DronePath(drone.parseId(), flightPlans);
|
||||||
|
|
||||||
|
|
@ -388,11 +405,12 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimate the number of moves a prospective trip would need by replaying the path calculation
|
* Estimate the number of moves a prospective trip would need by replaying the
|
||||||
|
* path calculation
|
||||||
* without mutating any persistent state.
|
* without mutating any persistent state.
|
||||||
*
|
*
|
||||||
* @param servicePoint Trip origin.
|
* @param servicePoint Trip origin.
|
||||||
* @param deliveries Deliveries that would compose the trip.
|
* @param deliveries Deliveries that would compose the trip.
|
||||||
* @return Total moves required to fly the proposed itinerary.
|
* @return Total moves required to fly the proposed itinerary.
|
||||||
*/
|
*/
|
||||||
private int estimateTripMoves(LngLat servicePoint, List<MedDispatchRecRequest> deliveries) {
|
private int estimateTripMoves(LngLat servicePoint, List<MedDispatchRecRequest> deliveries) {
|
||||||
|
|
@ -411,10 +429,11 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a path between {@code start} and {@code target} by repeatedly moving in snapped
|
* Build a path between {@code start} and {@code target} by repeatedly moving in
|
||||||
|
* snapped
|
||||||
* increments while avoiding restricted zones.
|
* increments while avoiding restricted zones.
|
||||||
*
|
*
|
||||||
* @param start Start coordinate.
|
* @param start Start coordinate.
|
||||||
* @param target Destination coordinate.
|
* @param target Destination coordinate.
|
||||||
* @return Sequence of visited coordinates and move count.
|
* @return Sequence of visited coordinates and move count.
|
||||||
* @see #nextPosition(LngLat, LngLat)
|
* @see #nextPosition(LngLat, LngLat)
|
||||||
|
|
@ -444,18 +463,20 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the next position on the path from {@code current} toward {@code target},
|
* Determine the next position on the path from {@code current} toward
|
||||||
* preferring the snapped angle closest to the desired heading that does not infiltrate a
|
* {@code target},
|
||||||
|
* preferring the snapped angle closest to the desired heading that does not
|
||||||
|
* infiltrate a
|
||||||
* restricted region.
|
* restricted region.
|
||||||
*
|
*
|
||||||
* @param current Current coordinate.
|
* @param current Current coordinate.
|
||||||
* @param target Destination coordinate.
|
* @param target Destination coordinate.
|
||||||
* @return Next admissible coordinate or the original point if none can be found.
|
* @return Next admissible coordinate or the original point if none can be
|
||||||
|
* found.
|
||||||
*/
|
*/
|
||||||
private LngLat nextPosition(LngLat current, LngLat target) {
|
private LngLat nextPosition(LngLat current, LngLat target) {
|
||||||
double desiredAngle =
|
double desiredAngle = Math.toDegrees(
|
||||||
Math.toDegrees(
|
Math.atan2(target.lat() - current.lat(), target.lng() - current.lng()));
|
||||||
Math.atan2(target.lat() - current.lat(), target.lng() - current.lng()));
|
|
||||||
List<Angle> candidateAngles = buildAngleCandidates(desiredAngle);
|
List<Angle> candidateAngles = buildAngleCandidates(desiredAngle);
|
||||||
for (Angle angle : candidateAngles) {
|
for (Angle angle : candidateAngles) {
|
||||||
LngLat next = gpsCalculationService.nextPosition(current, angle);
|
LngLat next = gpsCalculationService.nextPosition(current, angle);
|
||||||
|
|
@ -467,8 +488,10 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build a sequence of candidate angles centered on the desired heading, expanding symmetrically
|
* Build a sequence of candidate angles centered on the desired heading,
|
||||||
* clockwise and counter-clockwise to explore alternative headings if the primary path is
|
* expanding symmetrically
|
||||||
|
* clockwise and counter-clockwise to explore alternative headings if the
|
||||||
|
* primary path is
|
||||||
* blocked.
|
* blocked.
|
||||||
*
|
*
|
||||||
* @param desiredAngle Bearing in degrees between current and target positions.
|
* @param desiredAngle Bearing in degrees between current and target positions.
|
||||||
|
|
@ -503,15 +526,17 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a computed path segment wrapping the visited positions and the number of
|
* Representation of a computed path segment wrapping the visited positions and
|
||||||
|
* the number of
|
||||||
* moves taken to traverse them.
|
* moves taken to traverse them.
|
||||||
*
|
*
|
||||||
* @param positions Ordered coordinates that describe the path.
|
* @param positions Ordered coordinates that describe the path.
|
||||||
* @param moves Number of moves consumed by the path.
|
* @param moves Number of moves consumed by the path.
|
||||||
*/
|
*/
|
||||||
private record PathSegment(List<LngLat> positions, int moves) {
|
private record PathSegment(List<LngLat> positions, int moves) {
|
||||||
/**
|
/**
|
||||||
* Append the positions from this segment to {@code target}, skipping the first coordinate
|
* Append the positions from this segment to {@code target}, skipping the first
|
||||||
|
* coordinate
|
||||||
* as it is already represented by the last coordinate in the consumer path.
|
* as it is already represented by the last coordinate in the consumer path.
|
||||||
*
|
*
|
||||||
* @param target Mutable list to append to.
|
* @param target Mutable list to append to.
|
||||||
|
|
@ -524,8 +549,10 @@ public class PathFinderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bundle containing the calculated {@link DronePath}, total moves and financial cost for a
|
* Bundle containing the calculated {@link DronePath}, total moves and financial
|
||||||
|
* cost for a
|
||||||
* single trip.
|
* single trip.
|
||||||
*/
|
*/
|
||||||
private record TripResult(DronePath path, int moves, float cost) {}
|
private record TripResult(DronePath path, int moves, float cost) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package io.github.js0ny.ilp_coursework.service;
|
||||||
|
|
||||||
|
import java.net.http.HttpClient;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.github.js0ny.ilp_coursework.data.common.DroneEvent;
|
||||||
|
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TelemetryService {
|
||||||
|
private final HttpClient client;
|
||||||
|
private final ObjectMapper mapper;
|
||||||
|
|
||||||
|
private final String BLACKBOX_URL;
|
||||||
|
|
||||||
|
public TelemetryService() {
|
||||||
|
this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build();
|
||||||
|
|
||||||
|
this.mapper = new ObjectMapper();
|
||||||
|
this.BLACKBOX_URL = System.getenv().getOrDefault("BLACKBOX_ENDPOINT", "http://localhost:3000");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendEventAsyncByPathResponse(DeliveryPathResponse resp) {
|
||||||
|
var events = DroneEvent.fromPathResponse(resp);
|
||||||
|
for (var event : events) {
|
||||||
|
sendEventAsync(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendEventAsyncByPathResponse(DeliveryPathResponse resp, LocalDateTime baseTimestamp) {
|
||||||
|
var events = DroneEvent.fromPathResponseWithTimestamp(resp, baseTimestamp);
|
||||||
|
for (var event : events) {
|
||||||
|
sendEventAsync(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendEventAsync(DroneEvent event) {
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
String json = mapper.writeValueAsString(event);
|
||||||
|
var request = java.net.http.HttpRequest.newBuilder()
|
||||||
|
.uri(java.net.URI.create(BLACKBOX_URL + "/ingest"))
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(json))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("[ERROR] Failed to send telemetry event: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue