refractor(dto): Use nested data package
This commit is contained in:
parent
ec0d9087dd
commit
69d9e0d736
29 changed files with 405 additions and 383 deletions
|
|
@ -6,11 +6,11 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.DistanceRequestDto;
|
||||
import io.github.js0ny.ilp_coursework.data.LngLatDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MovementRequestDto;
|
||||
import io.github.js0ny.ilp_coursework.data.RegionCheckRequestDto;
|
||||
import io.github.js0ny.ilp_coursework.data.RegionDto;
|
||||
import io.github.js0ny.ilp_coursework.data.request.DistanceRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
import io.github.js0ny.ilp_coursework.data.request.MovementRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.common.Region;
|
||||
import io.github.js0ny.ilp_coursework.service.GpsCalculationService;
|
||||
|
||||
/**
|
||||
|
|
@ -48,39 +48,39 @@ public class ApiController {
|
|||
/**
|
||||
* Handles POST requests to get the distance between two positions
|
||||
*
|
||||
* @param request A {@link DistanceRequestDto} containing the two coordinates
|
||||
* @param request A {@link DistanceRequest} containing the two coordinates
|
||||
* @return A {@code double} representing the calculated distance
|
||||
*/
|
||||
@PostMapping("/distanceTo")
|
||||
public double getDistance(@RequestBody DistanceRequestDto request) {
|
||||
public double getDistance(@RequestBody DistanceRequest request) {
|
||||
|
||||
LngLatDto position1 = request.position1();
|
||||
LngLatDto position2 = request.position2();
|
||||
LngLat position1 = request.position1();
|
||||
LngLat position2 = request.position2();
|
||||
return gpsService.calculateDistance(position1, position2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles POST requests to check if the two coordinates are close to each other
|
||||
*
|
||||
* @param request A {@link DistanceRequestDto} containing the two coordinates
|
||||
* @param request A {@link DistanceRequest} containing the two coordinates
|
||||
* @return {@code true} if the distance is less than the predefined threshold, {@code false} otherwise
|
||||
*/
|
||||
@PostMapping("/isCloseTo")
|
||||
public boolean getIsCloseTo(@RequestBody DistanceRequestDto request) {
|
||||
LngLatDto position1 = request.position1();
|
||||
LngLatDto position2 = request.position2();
|
||||
public boolean getIsCloseTo(@RequestBody DistanceRequest request) {
|
||||
LngLat position1 = request.position1();
|
||||
LngLat position2 = request.position2();
|
||||
return gpsService.isCloseTo(position1, position2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles POST requests to get the next position after an angle of movement
|
||||
*
|
||||
* @param request A {@link MovementRequestDto} containing the start coordinate and angle of the movement.
|
||||
* @return A {@link LngLatDto} representing the destination
|
||||
* @param request A {@link MovementRequest} containing the start coordinate and angle of the movement.
|
||||
* @return A {@link LngLat} representing the destination
|
||||
*/
|
||||
@PostMapping("/nextPosition")
|
||||
public LngLatDto getNextPosition(@RequestBody MovementRequestDto request) {
|
||||
LngLatDto start = request.start();
|
||||
public LngLat getNextPosition(@RequestBody MovementRequest request) {
|
||||
LngLat start = request.start();
|
||||
double angle = request.angle();
|
||||
return gpsService.nextPosition(start, angle);
|
||||
}
|
||||
|
|
@ -88,13 +88,13 @@ public class ApiController {
|
|||
/**
|
||||
* Handles POST requests to check if a point is inside a given region
|
||||
*
|
||||
* @param request A {@link RegionCheckRequestDto} containing the coordinate and the region
|
||||
* @param request A {@link RegionCheckRequest} containing the coordinate and the region
|
||||
* @return {@code true} if the coordinate is inside the region, {@code false} otherwise
|
||||
*/
|
||||
@PostMapping("/isInRegion")
|
||||
public boolean getIsInRegion(@RequestBody RegionCheckRequestDto request) {
|
||||
LngLatDto position = request.position();
|
||||
RegionDto region = request.region();
|
||||
public boolean getIsInRegion(@RequestBody RegionCheckRequest request) {
|
||||
LngLat position = request.position();
|
||||
Region region = request.region();
|
||||
return gpsService.checkIsInRegion(position, region);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package io.github.js0ny.ilp_coursework.controller;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.AttrComparatorDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DeliveryPathDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DronePathDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MedDispatchRecDto;
|
||||
import io.github.js0ny.ilp_coursework.data.response.DeliveryPathResponse;
|
||||
import io.github.js0ny.ilp_coursework.data.external.Drone;
|
||||
import io.github.js0ny.ilp_coursework.data.common.DronePathDto;
|
||||
import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.request.AttrQueryRequest;
|
||||
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -55,13 +55,13 @@ public class DroneController {
|
|||
* Handles GET requests to retrieve the drone detail identified by id
|
||||
*
|
||||
* @param id The id of the drone to be queried.
|
||||
* @return 200 with {@link DroneDto}-style json if success, 404 if {@code id}
|
||||
* @return 200 with {@link Drone}-style json if success, 404 if {@code id}
|
||||
* not found, 400 otherwise
|
||||
*/
|
||||
@GetMapping("/droneDetails/{id}")
|
||||
public ResponseEntity<DroneDto> getDroneDetail(@PathVariable String id) {
|
||||
public ResponseEntity<Drone> getDroneDetail(@PathVariable String id) {
|
||||
try {
|
||||
DroneDto drone = droneService.droneDetail(id);
|
||||
Drone drone = droneService.droneDetail(id);
|
||||
return ResponseEntity.ok(drone);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return ResponseEntity.notFound().build();
|
||||
|
|
@ -84,22 +84,22 @@ public class DroneController {
|
|||
}
|
||||
|
||||
@PostMapping("/query")
|
||||
public String[] getIdByAttrMapPost(@RequestBody AttrComparatorDto[] attrComparators) {
|
||||
public String[] getIdByAttrMapPost(@RequestBody AttrQueryRequest[] attrComparators) {
|
||||
return droneService.dronesSatisfyingAttributes(attrComparators);
|
||||
}
|
||||
|
||||
@PostMapping("/queryAvailableDrones")
|
||||
public String[] queryAvailableDrones(@RequestBody MedDispatchRecDto[] records) {
|
||||
public String[] queryAvailableDrones(@RequestBody MedDispatchRecRequest[] records) {
|
||||
return droneService.dronesMatchesRequirements(records);
|
||||
}
|
||||
|
||||
@PostMapping("/calcDeliveryPath")
|
||||
public DeliveryPathDto calculateDeliveryPath(@RequestBody MedDispatchRecDto[] record) {
|
||||
return new DeliveryPathDto(0.0f, 0, new DronePathDto[]{});
|
||||
public DeliveryPathResponse calculateDeliveryPath(@RequestBody MedDispatchRecRequest[] record) {
|
||||
return new DeliveryPathResponse(0.0f, 0, new DronePathDto[]{});
|
||||
}
|
||||
|
||||
@PostMapping("/calcDeliveryPathAsGeoJson")
|
||||
public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispatchRecDto[] record) {
|
||||
public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispatchRecRequest[] record) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
// TODO: Convert operator to Enum
|
||||
// import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||
|
||||
public record AttrComparatorDto(String attribute, String operator, String value) {
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
public record DeliveryPathDto(
|
||||
float totalCost,
|
||||
int totalMoves,
|
||||
DronePathDto[] dronePaths) {
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a distance operation request.
|
||||
* <p>
|
||||
* This record encapsulates the data for several endpoints that involves two {@code LngLatDto}
|
||||
* and serves as the data contract for those API operation
|
||||
*
|
||||
* @param position1 Nested object of {@link LngLatDto}
|
||||
* @param position2 Nested object of {@link LngLatDto}
|
||||
*/
|
||||
public record DistanceRequestDto(LngLatDto position1, LngLatDto position2) {
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a drone, gained from the endpoints
|
||||
*/
|
||||
public record DroneDto(
|
||||
String name,
|
||||
String id,
|
||||
DroneCapabilityDto capability) {
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
public record DronePathDto() {
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record MedDispatchRecDto(
|
||||
int id,
|
||||
LocalDate date,
|
||||
LocalTime time,
|
||||
MedRequirementDto requirements,
|
||||
LngLatDto delivery) {
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
public record MedRequirementDto(
|
||||
float capacity,
|
||||
boolean cooling,
|
||||
boolean heating,
|
||||
float maxCost
|
||||
) {}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public record ServicePointDronesDto(
|
||||
String servicePointId,
|
||||
DroneAvailabilityDto[] drones) {
|
||||
|
||||
@Nullable
|
||||
public DroneAvailabilityDto locateDroneById(String droneId) {
|
||||
for (var drone : drones) {
|
||||
if (drone.id().equals(droneId)) {
|
||||
return drone;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record DroneAvailabilityDto(
|
||||
public record DroneAvailability(
|
||||
String id,
|
||||
AvailabilityTimeSegDto[] availability) {
|
||||
TimeWindow[] availability) {
|
||||
|
||||
public boolean checkAvailability(DayOfWeek day, LocalTime time) {
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
public record DroneCapabilityDto(
|
||||
public record DroneCapability(
|
||||
boolean cooling,
|
||||
boolean heating,
|
||||
float capacity,
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
public record DronePathDto() {
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a point or coordinate
|
||||
|
|
@ -7,5 +7,5 @@ package io.github.js0ny.ilp_coursework.data;
|
|||
* @param lng longitude of the coordinate/point
|
||||
* @param lat latitude of the coordinate/point
|
||||
*/
|
||||
public record LngLatDto(double lng, double lat) {
|
||||
public record LngLat(double lng, double lat) {
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
public record MedRequirement(
|
||||
float capacity,
|
||||
boolean cooling,
|
||||
boolean heating,
|
||||
float maxCost
|
||||
) {
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
|
@ -18,11 +20,11 @@ import java.util.Objects;
|
|||
* In order to define a valid region, the last element of the
|
||||
* list should be the same as the first, or
|
||||
* known as closed
|
||||
* @see RegionCheckRequestDto
|
||||
* @see io.github.js0ny.ilp_coursework.service.GpsCalculationService#checkIsInRegion(LngLatDto,
|
||||
* RegionDto)
|
||||
* @see RegionCheckRequest
|
||||
* @see io.github.js0ny.ilp_coursework.service.GpsCalculationService#checkIsInRegion(LngLat,
|
||||
* Region)
|
||||
*/
|
||||
public record RegionDto(String name, List<LngLatDto> vertices) {
|
||||
public record Region(String name, List<LngLat> vertices) {
|
||||
/**
|
||||
* Magic number 4: For a polygon, 3 edges is required.
|
||||
* <p>
|
||||
|
|
@ -41,8 +43,8 @@ public record RegionDto(String name, List<LngLatDto> vertices) {
|
|||
if (vertices == null || vertices.size() < MINIMUM_VERTICES) {
|
||||
return false;
|
||||
}
|
||||
LngLatDto first = vertices.getFirst();
|
||||
LngLatDto last = vertices.getLast();
|
||||
LngLat first = vertices.getFirst();
|
||||
LngLat last = vertices.getLast();
|
||||
return Objects.equals(last, first);
|
||||
}
|
||||
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.common;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record AvailabilityTimeSegDto(
|
||||
public record TimeWindow(
|
||||
DayOfWeek dayOfWeek,
|
||||
LocalTime from,
|
||||
LocalTime until) {
|
||||
12
src/main/java/io/github/js0ny/ilp_coursework/data/external/Drone.java
vendored
Normal file
12
src/main/java/io/github/js0ny/ilp_coursework/data/external/Drone.java
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package io.github.js0ny.ilp_coursework.data.external;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.DroneCapability;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a drone, gained from the endpoints
|
||||
*/
|
||||
public record Drone(
|
||||
String name,
|
||||
String id,
|
||||
DroneCapability capability) {
|
||||
}
|
||||
19
src/main/java/io/github/js0ny/ilp_coursework/data/external/ServicePointDrones.java
vendored
Normal file
19
src/main/java/io/github/js0ny/ilp_coursework/data/external/ServicePointDrones.java
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package io.github.js0ny.ilp_coursework.data.external;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.DroneAvailability;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public record ServicePointDrones(
|
||||
String servicePointId,
|
||||
DroneAvailability[] drones) {
|
||||
|
||||
@Nullable
|
||||
public DroneAvailability locateDroneById(String droneId) {
|
||||
for (var drone : drones) {
|
||||
if (drone.id().equals(droneId)) {
|
||||
return drone;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package io.github.js0ny.ilp_coursework.data.request;
|
||||
|
||||
// TODO: Convert operator to Enum
|
||||
// import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||
|
||||
public record AttrQueryRequest(String attribute, String operator, String value) {
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package io.github.js0ny.ilp_coursework.data.request;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a distance operation request.
|
||||
* <p>
|
||||
* This record encapsulates the data for several endpoints that involves two {@code LngLatDto}
|
||||
* and serves as the data contract for those API operation
|
||||
*
|
||||
* @param position1 Nested object of {@link LngLat}
|
||||
* @param position2 Nested object of {@link LngLat}
|
||||
*/
|
||||
public record DistanceRequest(LngLat position1, LngLat position2) {
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package io.github.js0ny.ilp_coursework.data.request;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.MedRequirement;
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record MedDispatchRecRequest(
|
||||
int id,
|
||||
LocalDate date,
|
||||
LocalTime time,
|
||||
MedRequirement requirements,
|
||||
LngLat delivery) {
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.request;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a movement action request.
|
||||
|
|
@ -9,7 +11,7 @@ package io.github.js0ny.ilp_coursework.data;
|
|||
* @param start The starting coordinate of the movement
|
||||
* @param angle The angle to movement in degree. This corresponds to compass directions.
|
||||
* For example: 0 for East, 90 for North
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getNextPosition(MovementRequestDto)
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getNextPosition(MovementRequest)
|
||||
*/
|
||||
public record MovementRequestDto(LngLatDto start, double angle) {
|
||||
public record MovementRequest(LngLat start, double angle) {
|
||||
}
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
package io.github.js0ny.ilp_coursework.data.request;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
import io.github.js0ny.ilp_coursework.data.common.Region;
|
||||
|
||||
/**
|
||||
* Represents the data transfer object for a region check request.
|
||||
|
|
@ -9,8 +12,8 @@ package io.github.js0ny.ilp_coursework.data;
|
|||
*
|
||||
* @param position The coordinate to be checked
|
||||
* @param region The region for the check.
|
||||
* This is a nested object represented by {@link RegionDto}
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequestDto)
|
||||
* This is a nested object represented by {@link Region}
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequest)
|
||||
*/
|
||||
public record RegionCheckRequestDto(LngLatDto position, RegionDto region) {
|
||||
public record RegionCheckRequest(LngLat position, Region region) {
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package io.github.js0ny.ilp_coursework.data.response;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.DronePathDto;
|
||||
|
||||
public record DeliveryPathResponse(
|
||||
float totalCost,
|
||||
int totalMoves,
|
||||
DronePathDto[] dronePaths) {
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
package io.github.js0ny.ilp_coursework.service;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.AttrComparatorDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MedDispatchRecDto;
|
||||
import io.github.js0ny.ilp_coursework.data.ServicePointDronesDto;
|
||||
import io.github.js0ny.ilp_coursework.data.external.Drone;
|
||||
import io.github.js0ny.ilp_coursework.data.request.MedDispatchRecRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.external.ServicePointDrones;
|
||||
import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||
import io.github.js0ny.ilp_coursework.data.request.AttrQueryRequest;
|
||||
|
||||
import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;
|
||||
|
||||
|
|
@ -59,9 +59,9 @@ public class DroneInfoService {
|
|||
*/
|
||||
public String[] dronesWithCooling(boolean state) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||
DroneDto[] drones = restTemplate.getForObject(
|
||||
Drone[] drones = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
DroneDto[].class);
|
||||
Drone[].class);
|
||||
|
||||
if (drones == null) {
|
||||
return new String[]{};
|
||||
|
|
@ -69,12 +69,12 @@ public class DroneInfoService {
|
|||
|
||||
return Arrays.stream(drones)
|
||||
.filter(drone -> drone.capability().cooling() == state)
|
||||
.map(DroneDto::id)
|
||||
.map(Drone::id)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link DroneDto}-style json data structure with the given {@code id}
|
||||
* Return a {@link Drone}-style json data structure with the given {@code id}
|
||||
* <p>
|
||||
* Associated service method with {@code /droneDetails/{id}}
|
||||
*
|
||||
|
|
@ -87,11 +87,11 @@ public class DroneInfoService {
|
|||
* this should lead to a 404
|
||||
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getDroneDetail(String)
|
||||
*/
|
||||
public DroneDto droneDetail(String id) {
|
||||
public Drone droneDetail(String id) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||
DroneDto[] drones = restTemplate.getForObject(
|
||||
Drone[] drones = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
DroneDto[].class);
|
||||
Drone[].class);
|
||||
|
||||
if (drones == null) {
|
||||
throw new NullPointerException("drone cannot be found");
|
||||
|
|
@ -131,7 +131,7 @@ public class DroneInfoService {
|
|||
* @param attrComparators The filter rule with Name, Value and Operator
|
||||
* @return array of drone ids that matches all rules
|
||||
*/
|
||||
public String[] dronesSatisfyingAttributes(AttrComparatorDto[] attrComparators) {
|
||||
public String[] dronesSatisfyingAttributes(AttrQueryRequest[] attrComparators) {
|
||||
Set<String> matchingDroneIds = null;
|
||||
for (var comparator : attrComparators) {
|
||||
String attribute = comparator.attribute();
|
||||
|
|
@ -170,9 +170,9 @@ public class DroneInfoService {
|
|||
private String[] dronesWithAttributeCompared(String attrName, String attrVal, AttrOperator op) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||
// This is required to make sure the response is valid
|
||||
DroneDto[] drones = restTemplate.getForObject(
|
||||
Drone[] drones = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
DroneDto[].class);
|
||||
Drone[].class);
|
||||
|
||||
if (drones == null) {
|
||||
return new String[]{};
|
||||
|
|
@ -193,7 +193,7 @@ public class DroneInfoService {
|
|||
return false;
|
||||
}
|
||||
})
|
||||
.map(DroneDto::id)
|
||||
.map(Drone::id)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
|
|
@ -207,11 +207,11 @@ public class DroneInfoService {
|
|||
* @return array of drone ids that match all the requirements
|
||||
* @see io.github.js0ny.ilp_coursework.controller.DroneController#queryAvailableDrones
|
||||
*/
|
||||
public String[] dronesMatchesRequirements(MedDispatchRecDto[] rec) {
|
||||
public String[] dronesMatchesRequirements(MedDispatchRecRequest[] rec) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||
DroneDto[] drones = restTemplate.getForObject(
|
||||
Drone[] drones = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
DroneDto[].class);
|
||||
Drone[].class);
|
||||
|
||||
if (drones == null || rec == null || rec.length == 0) {
|
||||
return new String[]{};
|
||||
|
|
@ -226,7 +226,7 @@ public class DroneInfoService {
|
|||
.filter(record -> record != null && record.requirements() != null)
|
||||
// Every record must be met
|
||||
.allMatch(record -> meetsRequirement(drone, record)))
|
||||
.map(DroneDto::id)
|
||||
.map(Drone::id)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ public class DroneInfoService {
|
|||
* is invalid (capacity and id cannot be null
|
||||
* in {@code MedDispathRecDto})
|
||||
*/
|
||||
private boolean meetsRequirement(DroneDto drone, MedDispatchRecDto record) {
|
||||
private boolean meetsRequirement(Drone drone, MedDispatchRecRequest record) {
|
||||
var requirements = record.requirements();
|
||||
if (requirements == null) {
|
||||
throw new IllegalArgumentException("requirements cannot be null");
|
||||
|
|
@ -284,11 +284,11 @@ public class DroneInfoService {
|
|||
* time
|
||||
* @return true if the drone is available, false otherwise
|
||||
*/
|
||||
private boolean checkAvailability(String droneId, MedDispatchRecDto record) {
|
||||
private boolean checkAvailability(String droneId, MedDispatchRecRequest record) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
|
||||
ServicePointDronesDto[] servicePoints = restTemplate.getForObject(
|
||||
ServicePointDrones[] servicePoints = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
ServicePointDronesDto[].class);
|
||||
ServicePointDrones[].class);
|
||||
|
||||
LocalDate requiredDate = record.date();
|
||||
DayOfWeek requiredDay = requiredDate.getDayOfWeek();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
package io.github.js0ny.ilp_coursework.service;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
import io.github.js0ny.ilp_coursework.data.common.Region;
|
||||
import io.github.js0ny.ilp_coursework.data.request.DistanceRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.request.MovementRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Class that handles calculations about Coordinates
|
||||
*
|
||||
* @see LngLatDto
|
||||
* @see RegionDto
|
||||
* @see LngLat
|
||||
* @see Region
|
||||
*/
|
||||
@Service
|
||||
public class GpsCalculationService {
|
||||
|
|
@ -18,30 +21,28 @@ public class GpsCalculationService {
|
|||
/**
|
||||
* Given step size
|
||||
*
|
||||
* @see #nextPosition(LngLatDto, double)
|
||||
* @see #nextPosition(LngLat, double)
|
||||
*/
|
||||
private static final double STEP = 0.00015;
|
||||
/**
|
||||
* Given threshold to judge if two points are close to each other
|
||||
*
|
||||
* @see #isCloseTo(LngLatDto, LngLatDto)
|
||||
* @see #isCloseTo(LngLat, LngLat)
|
||||
*/
|
||||
private static final double CLOSE_THRESHOLD = 0.00015;
|
||||
|
||||
/**
|
||||
* Calculate the Euclidean distance between {@code position1} and
|
||||
* {@code position2}, which are coordinates
|
||||
* defined as {@link LngLatDto}
|
||||
* defined as {@link LngLat}
|
||||
*
|
||||
* @param position1 The coordinate of the first position
|
||||
*
|
||||
* @param position2 The coordinate of the second position
|
||||
* @return The Euclidean distance between {@code position1} and
|
||||
* {@code position2}
|
||||
* @see
|
||||
* io.github.js0ny.ilp_coursework.controller.ApiController#getDistance(DistanceRequestDto)
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getDistance(DistanceRequest)
|
||||
*/
|
||||
public double calculateDistance(LngLatDto position1, LngLatDto position2) {
|
||||
public double calculateDistance(LngLat position1, LngLat position2) {
|
||||
double lngDistance = position2.lng() - position1.lng();
|
||||
double latDistance = position2.lat() - position1.lat();
|
||||
// Euclidean: \sqrt{a^2 + b^2}
|
||||
|
|
@ -57,14 +58,13 @@ public class GpsCalculationService {
|
|||
* false}
|
||||
*
|
||||
* @param position1 The coordinate of the first position
|
||||
*
|
||||
* @param position2 The coordinate of the second position
|
||||
* @return {@code true} if {@code position1} and
|
||||
* {@code position2} are close to each other
|
||||
* @see #CLOSE_THRESHOLD
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsCloseTo(DistanceRequestDto)
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsCloseTo(DistanceRequest)
|
||||
*/
|
||||
public boolean isCloseTo(LngLatDto position1, LngLatDto position2) {
|
||||
public boolean isCloseTo(LngLat position1, LngLat position2) {
|
||||
double distance = calculateDistance(position1, position2);
|
||||
return distance < CLOSE_THRESHOLD;
|
||||
}
|
||||
|
|
@ -75,17 +75,16 @@ public class GpsCalculationService {
|
|||
* 0.00015
|
||||
*
|
||||
* @param start The coordinate of the original start point.
|
||||
*
|
||||
* @param angle The direction to be moved in angle.
|
||||
* @return The next position moved from {@code start}
|
||||
* @see #STEP
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getNextPosition(MovementRequestDto)
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getNextPosition(MovementRequest)
|
||||
*/
|
||||
public LngLatDto nextPosition(LngLatDto start, double angle) {
|
||||
public LngLat nextPosition(LngLat start, double angle) {
|
||||
double rad = Math.toRadians(angle); // Convert to radian for Java triangle function calculation
|
||||
double newLng = Math.cos(rad) * STEP + start.lng();
|
||||
double newLat = Math.sin(rad) * STEP + start.lat();
|
||||
return new LngLatDto(newLng, newLat);
|
||||
return new LngLat(newLng, newLat);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -93,14 +92,14 @@ public class GpsCalculationService {
|
|||
* is inside the {@code region}, on edge and vertex is considered as inside.
|
||||
*
|
||||
* @param position The coordinate of the position.
|
||||
* @param region A {@link RegionDto} that contains name and a list of
|
||||
* @param region A {@link Region} that contains name and a list of
|
||||
* {@code LngLatDto}
|
||||
* @return {@code true} if {@code position} is inside the {@code region}.
|
||||
* @throws IllegalArgumentException If {@code region} is not closed
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequestDto)
|
||||
* @see RegionDto#isClosed()
|
||||
* @see io.github.js0ny.ilp_coursework.controller.ApiController#getIsInRegion(RegionCheckRequest)
|
||||
* @see Region#isClosed()
|
||||
*/
|
||||
public boolean checkIsInRegion(LngLatDto position, RegionDto region) throws IllegalArgumentException {
|
||||
public boolean checkIsInRegion(LngLat position, Region region) throws IllegalArgumentException {
|
||||
if (!region.isClosed()) { // call method from RegionDto to check if not closed
|
||||
throw new IllegalArgumentException("Region is not closed.");
|
||||
}
|
||||
|
|
@ -116,15 +115,15 @@ public class GpsCalculationService {
|
|||
* sits inside.
|
||||
* @return If the {@code point} sits inside the {@code polygon} then
|
||||
* return {@code true}
|
||||
* @see #isPointOnEdge(LngLatDto, LngLatDto, LngLatDto)
|
||||
* @see #checkIsInRegion(LngLatDto, RegionDto)
|
||||
* @see #isPointOnEdge(LngLat, LngLat, LngLat)
|
||||
* @see #checkIsInRegion(LngLat, Region)
|
||||
*/
|
||||
private boolean rayCasting(LngLatDto point, List<LngLatDto> polygon) {
|
||||
private boolean rayCasting(LngLat point, List<LngLat> polygon) {
|
||||
int intersections = 0;
|
||||
int n = polygon.size();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
LngLatDto a = polygon.get(i);
|
||||
LngLatDto b = polygon.get((i + 1) % n); // Next vertex
|
||||
LngLat a = polygon.get(i);
|
||||
LngLat b = polygon.get((i + 1) % n); // Next vertex
|
||||
|
||||
if (isPointOnEdge(point, a, b)) {
|
||||
return true;
|
||||
|
|
@ -132,7 +131,7 @@ public class GpsCalculationService {
|
|||
|
||||
// Ensure that `a` is norther than `b`, in order to easy classification
|
||||
if (a.lat() > b.lat()) {
|
||||
LngLatDto temp = a;
|
||||
LngLat temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
|
@ -165,13 +164,12 @@ public class GpsCalculationService {
|
|||
* {@code a} and {@code b}
|
||||
*
|
||||
* @param p point to be checked on the edge
|
||||
*
|
||||
* @param a point that forms the edge
|
||||
* @param b point that forms the edge
|
||||
* @return {@code true} if {@code p} is on {@code ab}
|
||||
* @see #rayCasting(LngLatDto, List)
|
||||
* @see #rayCasting(LngLat, List)
|
||||
*/
|
||||
private boolean isPointOnEdge(LngLatDto p, LngLatDto a, LngLatDto b) {
|
||||
private boolean isPointOnEdge(LngLat p, LngLat a, LngLat b) {
|
||||
// Cross product: (p - a) × (b - a)
|
||||
double crossProduct = (p.lng() - a.lng()) * (b.lat() - a.lat())
|
||||
- (p.lat() - a.lat()) * (b.lng() - a.lng());
|
||||
|
|
|
|||
|
|
@ -8,9 +8,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.github.js0ny.ilp_coursework.data.*;
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
import io.github.js0ny.ilp_coursework.data.common.Region;
|
||||
import io.github.js0ny.ilp_coursework.data.request.DistanceRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.request.MovementRequest;
|
||||
import io.github.js0ny.ilp_coursework.data.request.RegionCheckRequest;
|
||||
import io.github.js0ny.ilp_coursework.service.GpsCalculationService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -58,13 +64,13 @@ public class ApiControllerTest {
|
|||
throws Exception {
|
||||
double expected = 5.0;
|
||||
String endpoint = "/api/v1/distanceTo";
|
||||
LngLatDto p1 = new LngLatDto(0, 4.0);
|
||||
LngLatDto p2 = new LngLatDto(3.0, 0);
|
||||
var req = new DistanceRequestDto(p1, p2);
|
||||
LngLat p1 = new LngLat(0, 4.0);
|
||||
LngLat p2 = new LngLat(3.0, 0);
|
||||
var req = new DistanceRequest(p1, p2);
|
||||
when(
|
||||
service.calculateDistance(
|
||||
any(LngLatDto.class),
|
||||
any(LngLatDto.class)
|
||||
any(LngLat.class),
|
||||
any(LngLat.class)
|
||||
)
|
||||
).thenReturn(expected);
|
||||
var mock = mockMvc.perform(
|
||||
|
|
@ -90,7 +96,7 @@ public class ApiControllerTest {
|
|||
}
|
||||
""";
|
||||
when(
|
||||
service.calculateDistance(any(LngLatDto.class), isNull())
|
||||
service.calculateDistance(any(LngLat.class), isNull())
|
||||
).thenThrow(new NullPointerException());
|
||||
mockMvc
|
||||
.perform(
|
||||
|
|
@ -112,11 +118,11 @@ public class ApiControllerTest {
|
|||
throws Exception {
|
||||
boolean expected = false;
|
||||
String endpoint = "/api/v1/isCloseTo";
|
||||
LngLatDto p1 = new LngLatDto(0, 4.0);
|
||||
LngLatDto p2 = new LngLatDto(3.0, 0);
|
||||
var req = new DistanceRequestDto(p1, p2);
|
||||
LngLat p1 = new LngLat(0, 4.0);
|
||||
LngLat p2 = new LngLat(3.0, 0);
|
||||
var req = new DistanceRequest(p1, p2);
|
||||
when(
|
||||
service.isCloseTo(any(LngLatDto.class), any(LngLatDto.class))
|
||||
service.isCloseTo(any(LngLat.class), any(LngLat.class))
|
||||
).thenReturn(expected);
|
||||
var mock = mockMvc.perform(
|
||||
post(endpoint)
|
||||
|
|
@ -157,11 +163,11 @@ public class ApiControllerTest {
|
|||
@DisplayName("POST /nextPosition -> 200 OK")
|
||||
void getNextPosition_shouldReturn200AndCoordinate_whenCorrectInput()
|
||||
throws Exception {
|
||||
LngLatDto expected = new LngLatDto(0.00015, 0.0);
|
||||
LngLatDto p = new LngLatDto(0, 0);
|
||||
var req = new MovementRequestDto(p, 0);
|
||||
LngLat expected = new LngLat(0.00015, 0.0);
|
||||
LngLat p = new LngLat(0, 0);
|
||||
var req = new MovementRequest(p, 0);
|
||||
when(
|
||||
service.nextPosition(any(LngLatDto.class), anyDouble())
|
||||
service.nextPosition(any(LngLat.class), anyDouble())
|
||||
).thenReturn(expected);
|
||||
var mock = mockMvc.perform(
|
||||
post(endpoint)
|
||||
|
|
@ -209,22 +215,22 @@ public class ApiControllerTest {
|
|||
throws Exception {
|
||||
boolean expected = false;
|
||||
String endpoint = "/api/v1/isInRegion";
|
||||
var position = new LngLatDto(1.234, 1.222);
|
||||
var region = new RegionDto(
|
||||
var position = new LngLat(1.234, 1.222);
|
||||
var region = new Region(
|
||||
"central",
|
||||
List.of(
|
||||
new LngLatDto(-3.192473, 55.946233),
|
||||
new LngLatDto(-3.192473, 55.942617),
|
||||
new LngLatDto(-3.184319, 55.942617),
|
||||
new LngLatDto(-3.184319, 55.946233),
|
||||
new LngLatDto(-3.192473, 55.946233)
|
||||
new LngLat(-3.192473, 55.946233),
|
||||
new LngLat(-3.192473, 55.942617),
|
||||
new LngLat(-3.184319, 55.942617),
|
||||
new LngLat(-3.184319, 55.946233),
|
||||
new LngLat(-3.192473, 55.946233)
|
||||
)
|
||||
);
|
||||
var req = new RegionCheckRequestDto(position, region);
|
||||
var req = new RegionCheckRequest(position, region);
|
||||
when(
|
||||
service.checkIsInRegion(
|
||||
any(LngLatDto.class),
|
||||
any(RegionDto.class)
|
||||
any(LngLat.class),
|
||||
any(Region.class)
|
||||
)
|
||||
).thenReturn(expected);
|
||||
var mock = mockMvc.perform(
|
||||
|
|
@ -243,13 +249,13 @@ public class ApiControllerTest {
|
|||
)
|
||||
void getIsInRegion_shouldReturn400_whenPassingIllegalArguments()
|
||||
throws Exception {
|
||||
var position = new LngLatDto(1, 1);
|
||||
var region = new RegionDto("illegal", List.of());
|
||||
var request = new RegionCheckRequestDto(position, region);
|
||||
var position = new LngLat(1, 1);
|
||||
var region = new Region("illegal", List.of());
|
||||
var request = new RegionCheckRequest(position, region);
|
||||
when(
|
||||
service.checkIsInRegion(
|
||||
any(LngLatDto.class),
|
||||
any(RegionDto.class)
|
||||
any(LngLat.class),
|
||||
any(Region.class)
|
||||
)
|
||||
).thenThrow(new IllegalArgumentException("Region is not closed."));
|
||||
mockMvc
|
||||
|
|
@ -267,22 +273,22 @@ public class ApiControllerTest {
|
|||
)
|
||||
void getIsInRegion_shouldReturn400_whenPassingNotClosingVertices()
|
||||
throws Exception {
|
||||
var position = new LngLatDto(1, 1);
|
||||
var region = new RegionDto(
|
||||
var position = new LngLat(1, 1);
|
||||
var region = new Region(
|
||||
"illegal",
|
||||
List.of(
|
||||
new LngLatDto(1, 2),
|
||||
new LngLatDto(3, 4),
|
||||
new LngLatDto(5, 6),
|
||||
new LngLatDto(7, 8),
|
||||
new LngLatDto(9, 10)
|
||||
new LngLat(1, 2),
|
||||
new LngLat(3, 4),
|
||||
new LngLat(5, 6),
|
||||
new LngLat(7, 8),
|
||||
new LngLat(9, 10)
|
||||
)
|
||||
);
|
||||
var request = new RegionCheckRequestDto(position, region);
|
||||
var request = new RegionCheckRequest(position, region);
|
||||
when(
|
||||
service.checkIsInRegion(
|
||||
any(LngLatDto.class),
|
||||
any(RegionDto.class)
|
||||
any(LngLat.class),
|
||||
any(Region.class)
|
||||
)
|
||||
).thenThrow(new IllegalArgumentException("Region is not closed."));
|
||||
mockMvc
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package io.github.js0ny.ilp_coursework.service;
|
||||
|
||||
import io.github.js0ny.ilp_coursework.data.LngLatDto;
|
||||
import io.github.js0ny.ilp_coursework.data.RegionDto;
|
||||
import io.github.js0ny.ilp_coursework.data.common.LngLat;
|
||||
import io.github.js0ny.ilp_coursework.data.common.Region;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
|
|
@ -32,8 +32,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("False: Given Example For Testing")
|
||||
void isCloseTo_shouldReturnFalse_givenExample() {
|
||||
var p1 = new LngLatDto(-3.192473, 55.946233);
|
||||
var p2 = new LngLatDto(-3.192473, 55.942617);
|
||||
var p1 = new LngLat(-3.192473, 55.946233);
|
||||
var p2 = new LngLat(-3.192473, 55.942617);
|
||||
double expected = 0.0036;
|
||||
double actual = service.calculateDistance(p1, p2);
|
||||
assertThat(actual).isCloseTo(expected, within(1e-4));
|
||||
|
|
@ -42,8 +42,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("General Case: 3-4-5 Triangle")
|
||||
void calculateDistance_shouldReturnCorrectEuclideanDistance_forGeneralCase() {
|
||||
var p1 = new LngLatDto(0, 3.0);
|
||||
var p2 = new LngLatDto(4.0, 0);
|
||||
var p1 = new LngLat(0, 3.0);
|
||||
var p2 = new LngLat(4.0, 0);
|
||||
double expected = 5.0;
|
||||
double actual = service.calculateDistance(p1, p2);
|
||||
assertThat(actual).isCloseTo(expected, within(PRECISION));
|
||||
|
|
@ -52,7 +52,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Points are Identical")
|
||||
void calculateDistance_shouldReturnZero_whenPointsAreIdentical() {
|
||||
var p1 = new LngLatDto(123.85, 983.2119);
|
||||
var p1 = new LngLat(123.85, 983.2119);
|
||||
double expected = 0.0;
|
||||
double actual = service.calculateDistance(p1, p1);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -61,8 +61,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Longitudinal-only movement")
|
||||
void calculateDistance_shouldReturnCorrectDistance_forPointsWithSameLatitude() {
|
||||
var p1 = new LngLatDto(123.85, 983.2119);
|
||||
var p2 = new LngLatDto(133.85, 983.2119);
|
||||
var p1 = new LngLat(123.85, 983.2119);
|
||||
var p2 = new LngLat(133.85, 983.2119);
|
||||
double expected = 10.0;
|
||||
double actual = service.calculateDistance(p1, p2);
|
||||
assertThat(actual).isCloseTo(expected, within(PRECISION));
|
||||
|
|
@ -71,8 +71,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Latitude-only movement")
|
||||
void calculateDistance_shouldReturnCorrectDistance_forPointsWithSameLongitude() {
|
||||
var p1 = new LngLatDto(123.85, 983.2119);
|
||||
var p2 = new LngLatDto(123.85, 973.2119);
|
||||
var p1 = new LngLat(123.85, 983.2119);
|
||||
var p2 = new LngLat(123.85, 973.2119);
|
||||
double expected = 10.0;
|
||||
double actual = service.calculateDistance(p1, p2);
|
||||
assertThat(actual).isCloseTo(expected, within(PRECISION));
|
||||
|
|
@ -81,8 +81,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("General Case: Calculate with negative Coordinates")
|
||||
void calculateDistance_shouldReturnCorrectEuclideanDistance_forNegativeCoordinates() {
|
||||
LngLatDto p1 = new LngLatDto(-1.0, -2.0);
|
||||
LngLatDto p2 = new LngLatDto(2.0, 2.0); // lngDiff = 3, latDiff = 4
|
||||
LngLat p1 = new LngLat(-1.0, -2.0);
|
||||
LngLat p2 = new LngLat(2.0, 2.0); // lngDiff = 3, latDiff = 4
|
||||
double expected = 5.0;
|
||||
double actual = service.calculateDistance(p1, p2);
|
||||
assertThat(actual).isCloseTo(expected, within(PRECISION));
|
||||
|
|
@ -95,8 +95,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("False: Given Example For Testing")
|
||||
void isCloseTo_shouldReturnFalse_givenExample() {
|
||||
var p1 = new LngLatDto(-3.192473, 55.946233);
|
||||
var p2 = new LngLatDto(-3.192473, 55.942617);
|
||||
var p1 = new LngLat(-3.192473, 55.946233);
|
||||
var p2 = new LngLat(-3.192473, 55.942617);
|
||||
boolean expected = false;
|
||||
boolean actual = service.isCloseTo(p1, p2);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -105,7 +105,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("True: Two points are the same")
|
||||
void isCloseTo_shouldReturnTrue_whenPointsAreIdentical() {
|
||||
var p1 = new LngLatDto(151.86, 285.37);
|
||||
var p1 = new LngLat(151.86, 285.37);
|
||||
boolean expected = true;
|
||||
boolean actual = service.isCloseTo(p1, p1);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -114,8 +114,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("True: Two points are close to each other and near threshold")
|
||||
void isCloseTo_shouldReturnTrue_whenCloseAndSmallerThanThreshold() {
|
||||
var p1 = new LngLatDto(0.0, 0.0);
|
||||
var p2 = new LngLatDto(0.0, 0.00014);
|
||||
var p1 = new LngLat(0.0, 0.0);
|
||||
var p2 = new LngLat(0.0, 0.00014);
|
||||
boolean expected = true;
|
||||
boolean actual = service.isCloseTo(p1, p2);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -124,8 +124,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("False: Distance nears the threshold")
|
||||
void isCloseTo_shouldReturnFalse_whenEqualsToThreshold() {
|
||||
var p1 = new LngLatDto(0.0, 0.0);
|
||||
var p2 = new LngLatDto(0.0, 0.00015);
|
||||
var p1 = new LngLat(0.0, 0.0);
|
||||
var p2 = new LngLat(0.0, 0.00015);
|
||||
boolean expected = false;
|
||||
boolean actual = service.isCloseTo(p1, p2);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -134,8 +134,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("False: Distance larger to threshold")
|
||||
void isCloseTo_shouldReturnFalse_whenNotCloseAndLargerThanThreshold() {
|
||||
var p1 = new LngLatDto(0.0, 0.0);
|
||||
var p2 = new LngLatDto(0.0, 0.00016);
|
||||
var p1 = new LngLat(0.0, 0.0);
|
||||
var p2 = new LngLat(0.0, 0.00016);
|
||||
boolean expected = false;
|
||||
boolean actual = service.isCloseTo(p1, p2);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -149,10 +149,10 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("General Case: nextPosition in East direction (0 degrees)")
|
||||
void nextPosition_shouldMoveEast_forAngleZero() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
double angle = 0;
|
||||
// For 0 degrees, cos(0)=1, sin(0)=0. Move happens entirely on lng axis.
|
||||
var expected = new LngLatDto(STEP, 0.0);
|
||||
var expected = new LngLat(STEP, 0.0);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -163,10 +163,10 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Cardinal Direction: nextPosition in North direction (90 degrees)")
|
||||
void nextPosition_shouldMoveNorth_forAngle90() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
double angle = 90;
|
||||
// For 90 degrees, cos(90)=0, sin(90)=1. Move happens entirely on lat axis.
|
||||
var expected = new LngLatDto(0.0, STEP);
|
||||
var expected = new LngLat(0.0, STEP);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -177,11 +177,11 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Cardinal Direction: nextPosition in West direction (180 degrees)")
|
||||
void nextPosition_shouldMoveWest_forAngle180() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
double angle = 180;
|
||||
// For 180 degrees, cos(180)=-1, sin(180)=0. Move happens entirely on negative
|
||||
// lng axis.
|
||||
var expected = new LngLatDto(-STEP, 0.0);
|
||||
var expected = new LngLat(-STEP, 0.0);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -192,11 +192,11 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Cardinal Direction: nextPosition in South direction (270 degrees)")
|
||||
void nextPosition_shouldMoveSouth_forAngle270() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
double angle = 270;
|
||||
// For 270 degrees, cos(270)=0, sin(270)=-1. Move happens entirely on negative
|
||||
// lat axis.
|
||||
var expected = new LngLatDto(0.0, -STEP);
|
||||
var expected = new LngLat(0.0, -STEP);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -207,12 +207,12 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Intercardinal Direction: nextPosition in Northeast direction (45 degrees)")
|
||||
void nextPosition_shouldMoveNortheast_forAngle45() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
double angle = 45;
|
||||
// Δlng = step * cos(45°), Δlat = step * sin(45°)
|
||||
double expectedLng = STEP * Math.cos(Math.toRadians(angle));
|
||||
double expectedLat = STEP * Math.sin(Math.toRadians(angle));
|
||||
var expected = new LngLatDto(expectedLng, expectedLat);
|
||||
var expected = new LngLat(expectedLng, expectedLat);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -223,13 +223,13 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Angle larger than 360 should wrap around")
|
||||
void nextPosition_shouldHandleAngleGreaterThan360() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
// 405 degrees is equivalent to 45 degrees (405 % 360 = 45).
|
||||
double angle = 405;
|
||||
double equivalentAngle = 45;
|
||||
double expectedLng = STEP * Math.cos(Math.toRadians(equivalentAngle));
|
||||
double expectedLat = STEP * Math.sin(Math.toRadians(equivalentAngle));
|
||||
var expected = new LngLatDto(expectedLng, expectedLat);
|
||||
var expected = new LngLat(expectedLng, expectedLat);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -240,12 +240,12 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Negative angle should work correctly")
|
||||
void nextPosition_shouldHandleNegativeAngle() {
|
||||
var start = new LngLatDto(0.0, 0.0);
|
||||
var start = new LngLat(0.0, 0.0);
|
||||
// A negative angle of -45° corresponds to the Southeast direction.
|
||||
double angle = -45;
|
||||
double expectedLng = STEP * Math.cos(Math.toRadians(angle));
|
||||
double expectedLat = STEP * Math.sin(Math.toRadians(angle));
|
||||
var expected = new LngLatDto(expectedLng, expectedLat);
|
||||
var expected = new LngLat(expectedLng, expectedLat);
|
||||
|
||||
var actual = service.nextPosition(start, angle);
|
||||
|
||||
|
|
@ -258,17 +258,17 @@ public class GpsCalculationServiceTest {
|
|||
@DisplayName("Test for checkIsInRegion(LngLatDto, RegionDto) -> boolean")
|
||||
class CheckIsInRegionTests {
|
||||
|
||||
public static final RegionDto RECTANGLE_REGION = new RegionDto("rectangle", List.of(new LngLatDto(0.0, 0.0),
|
||||
new LngLatDto(2.0, 0.0), new LngLatDto(2.0, 2.0), new LngLatDto(0.0, 2.0), new LngLatDto(0.0, 0.0)));
|
||||
public static final Region RECTANGLE_REGION = new Region("rectangle", List.of(new LngLat(0.0, 0.0),
|
||||
new LngLat(2.0, 0.0), new LngLat(2.0, 2.0), new LngLat(0.0, 2.0), new LngLat(0.0, 0.0)));
|
||||
|
||||
@Test
|
||||
@DisplayName("General Case: Given Example for Testing")
|
||||
void isInRegion_shouldReturnFalse_givenPolygonCentral() {
|
||||
var position = new LngLatDto(1.234, 1.222);
|
||||
var region = new RegionDto("central",
|
||||
List.of(new LngLatDto(-3.192473, 55.946233), new LngLatDto(-3.192473, 55.942617),
|
||||
new LngLatDto(-3.184319, 55.942617), new LngLatDto(-3.184319, 55.946233),
|
||||
new LngLatDto(-3.192473, 55.946233)));
|
||||
var position = new LngLat(1.234, 1.222);
|
||||
var region = new Region("central",
|
||||
List.of(new LngLat(-3.192473, 55.946233), new LngLat(-3.192473, 55.942617),
|
||||
new LngLat(-3.184319, 55.942617), new LngLat(-3.184319, 55.946233),
|
||||
new LngLat(-3.192473, 55.946233)));
|
||||
boolean expected = false;
|
||||
boolean actual = service.checkIsInRegion(position, region);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -277,7 +277,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("General Case: Simple Rectangle")
|
||||
void isInRegion_shouldReturnTrue_forSimpleRectangle() {
|
||||
var position = new LngLatDto(1.0, 1.0);
|
||||
var position = new LngLat(1.0, 1.0);
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -286,7 +286,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("General Case: Simple Rectangle")
|
||||
void isInRegion_shouldReturnFalse_forSimpleRectangle() {
|
||||
var position = new LngLatDto(3.0, 1.0);
|
||||
var position = new LngLat(3.0, 1.0);
|
||||
boolean expected = false;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -295,11 +295,11 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("General Case: Simple Hexagon")
|
||||
void isInRegion_shouldReturnTrue_forSimpleHexagon() {
|
||||
var position = new LngLatDto(2.0, 2.0);
|
||||
var region = new RegionDto("hexagon",
|
||||
List.of(new LngLatDto(1.0, 0.0), new LngLatDto(4.0, 0.0), new LngLatDto(5.0, 2.0),
|
||||
new LngLatDto(4.0, 4.0), new LngLatDto(1.0, 4.0), new LngLatDto(0.0, 2.0),
|
||||
new LngLatDto(1.0, 0.0)));
|
||||
var position = new LngLat(2.0, 2.0);
|
||||
var region = new Region("hexagon",
|
||||
List.of(new LngLat(1.0, 0.0), new LngLat(4.0, 0.0), new LngLat(5.0, 2.0),
|
||||
new LngLat(4.0, 4.0), new LngLat(1.0, 4.0), new LngLat(0.0, 2.0),
|
||||
new LngLat(1.0, 0.0)));
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, region);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -308,9 +308,9 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Small Triangle")
|
||||
void isInRegion_shouldReturnTrue_forSmallTriangle() {
|
||||
var position = new LngLatDto(0.00001, 0.00001);
|
||||
var region = new RegionDto("triangle", List.of(new LngLatDto(0.0, 0.0), new LngLatDto(0.0001, 0.0),
|
||||
new LngLatDto(0.00005, 0.0001), new LngLatDto(0.0, 0.0)));
|
||||
var position = new LngLat(0.00001, 0.00001);
|
||||
var region = new Region("triangle", List.of(new LngLat(0.0, 0.0), new LngLat(0.0001, 0.0),
|
||||
new LngLat(0.00005, 0.0001), new LngLat(0.0, 0.0)));
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, region);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -319,7 +319,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Point on Lower Edge of Rectangle")
|
||||
void isInRegion_shouldReturnTrue_whenPointOnLowerEdge() {
|
||||
var position = new LngLatDto(0.0, 1.0);
|
||||
var position = new LngLat(0.0, 1.0);
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -328,7 +328,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Point on Upper Edge of Rectangle")
|
||||
void isInRegion_shouldReturnTrue_whenPointOnUpperEdge() {
|
||||
var position = new LngLatDto(2.0, 1.0);
|
||||
var position = new LngLat(2.0, 1.0);
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -337,7 +337,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Point on Left Edge")
|
||||
void isInRegion_shouldReturnTrue_whenPointOnLeftEdge() {
|
||||
var position = new LngLatDto(0.0, 1.0);
|
||||
var position = new LngLat(0.0, 1.0);
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -346,7 +346,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Point on Lower Vertex")
|
||||
void isInRegion_shouldReturnTrue_whenPointOnLowerVertex() {
|
||||
var position = new LngLatDto(0.0, 0.0);
|
||||
var position = new LngLat(0.0, 0.0);
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -355,7 +355,7 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Point on Upper Vertex")
|
||||
void isInRegion_shouldReturnTrue_whenPointOnUpperVertex() {
|
||||
var position = new LngLatDto(2.0, 2.0);
|
||||
var position = new LngLat(2.0, 2.0);
|
||||
boolean expected = true;
|
||||
boolean actual = service.checkIsInRegion(position, RECTANGLE_REGION);
|
||||
assertThat(actual).isEqualTo(expected);
|
||||
|
|
@ -364,9 +364,9 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Region not forming polygon")
|
||||
void isInRegion_shouldThrowExceptions_whenRegionNotFormingPolygon() {
|
||||
var position = new LngLatDto(2.0, 2.0);
|
||||
var region = new RegionDto("line",
|
||||
List.of(new LngLatDto(0.0, 0.0), new LngLatDto(0.0001, 0.0), new LngLatDto(0.0, 0.0)));
|
||||
var position = new LngLat(2.0, 2.0);
|
||||
var region = new Region("line",
|
||||
List.of(new LngLat(0.0, 0.0), new LngLat(0.0001, 0.0), new LngLat(0.0, 0.0)));
|
||||
assertThatThrownBy(() -> {
|
||||
service.checkIsInRegion(position, region);
|
||||
}).isInstanceOf(IllegalArgumentException.class).hasMessage("Region is not closed.");
|
||||
|
|
@ -375,9 +375,9 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Region is not closed")
|
||||
void isInRegion_shouldThrowExceptions_whenRegionNotClose() {
|
||||
var position = new LngLatDto(2.0, 2.0);
|
||||
var region = new RegionDto("rectangle", List.of(new LngLatDto(0.0, 0.0), new LngLatDto(2.0, 0.0),
|
||||
new LngLatDto(2.0, 2.0), new LngLatDto(0.0, 2.0), new LngLatDto(0.0, -1.0)));
|
||||
var position = new LngLat(2.0, 2.0);
|
||||
var region = new Region("rectangle", List.of(new LngLat(0.0, 0.0), new LngLat(2.0, 0.0),
|
||||
new LngLat(2.0, 2.0), new LngLat(0.0, 2.0), new LngLat(0.0, -1.0)));
|
||||
assertThatThrownBy(() -> {
|
||||
service.checkIsInRegion(position, region);
|
||||
}).isInstanceOf(IllegalArgumentException.class).hasMessage("Region is not closed.");
|
||||
|
|
@ -386,8 +386,8 @@ public class GpsCalculationServiceTest {
|
|||
@Test
|
||||
@DisplayName("Edge Case: Vertex list is empty")
|
||||
void isInRegion_shouldThrowExceptions_whenListIsEmpty() {
|
||||
var position = new LngLatDto(2.0, 2.0);
|
||||
var region = new RegionDto("rectangle", List.of());
|
||||
var position = new LngLat(2.0, 2.0);
|
||||
var region = new Region("rectangle", List.of());
|
||||
assertThatThrownBy(() -> {
|
||||
service.checkIsInRegion(position, region);
|
||||
}).isInstanceOf(IllegalArgumentException.class).hasMessage("Region is not closed.");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue