From ed45fab721fed461e5f0899851b2a8f4d810980a Mon Sep 17 00:00:00 2001 From: Ben Antony Date: Mon, 15 Jan 2018 13:55:45 +0100 Subject: [PATCH] Add API for routing with coordinates --- .../contargo/iris/route/RouteInformation.java | 26 +++ .../iris/route/api/RoutesApiController.java | 70 ++++++- .../iris/route/dto/RoutingResultDto.java | 180 ++++++++++++++++++ .../api/RoutesApiControllerMvcUnitTest.java | 80 +++++++- 4 files changed, 352 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/contargo/iris/route/dto/RoutingResultDto.java diff --git a/src/main/java/net/contargo/iris/route/RouteInformation.java b/src/main/java/net/contargo/iris/route/RouteInformation.java index c93ac985..80ed5f55 100644 --- a/src/main/java/net/contargo/iris/route/RouteInformation.java +++ b/src/main/java/net/contargo/iris/route/RouteInformation.java @@ -3,6 +3,8 @@ import net.contargo.iris.GeoLocation; import net.contargo.iris.container.ContainerType; +import java.util.Objects; + /** * Encapsulates detail information that are needed to compute a {@link net.contargo.iris.route.Route}. @@ -96,4 +98,28 @@ public void setRouteCombo(RouteCombo routeCombo) { this.routeCombo = routeCombo; } + + + @Override + public boolean equals(Object o) { + + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + RouteInformation that = (RouteInformation) o; + + return Objects.equals(destination, that.destination) && product == that.product + && containerType == that.containerType && routeDirection == that.routeDirection + && routeCombo == that.routeCombo; + } + + + @Override + public int hashCode() { + + return Objects.hash(destination, product, containerType, routeDirection, routeCombo); + } } diff --git a/src/main/java/net/contargo/iris/route/api/RoutesApiController.java b/src/main/java/net/contargo/iris/route/api/RoutesApiController.java index 99fb1530..e74f93ea 100644 --- a/src/main/java/net/contargo/iris/route/api/RoutesApiController.java +++ b/src/main/java/net/contargo/iris/route/api/RoutesApiController.java @@ -2,12 +2,18 @@ import com.wordnik.swagger.annotations.ApiOperation; +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.dto.GeoLocationDto; import net.contargo.iris.api.ControllerConstants; +import net.contargo.iris.connection.dto.RouteDataDto; import net.contargo.iris.connection.dto.RouteDto; +import net.contargo.iris.connection.dto.RoutePartDto; import net.contargo.iris.connection.dto.SeaportConnectionRoutesDtoService; import net.contargo.iris.container.ContainerType; import net.contargo.iris.route.RouteCombo; import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.route.dto.EnricherDtoService; +import net.contargo.iris.route.dto.RoutingResultDto; import net.contargo.iris.route.service.RouteUrlSerializationService; import net.contargo.iris.seaport.dto.SeaportDto; import net.contargo.iris.seaport.dto.SeaportDtoService; @@ -22,13 +28,23 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; import java.lang.invoke.MethodHandles; +import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import static net.contargo.iris.container.ContainerType.TWENTY_LIGHT; +import static net.contargo.iris.route.RouteCombo.WATERWAY; +import static net.contargo.iris.route.RouteDirection.EXPORT; +import static net.contargo.iris.route.RouteProduct.ONEWAY; +import static net.contargo.iris.route.RouteType.BARGE; + import static org.slf4j.LoggerFactory.getLogger; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; @@ -36,9 +52,15 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static java.math.BigDecimal.ZERO; + +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; + /** * @author Sandra Thieme - thieme@synyx.de + * @author Ben Antony - antony@synyx.de */ @Controller public class RoutesApiController { @@ -50,15 +72,17 @@ public class RoutesApiController { private final SeaportDtoService seaportDtoService; private final SeaportConnectionRoutesDtoService seaportConnectionRoutesDtoService; private final RouteUrlSerializationService routeUrlSerializationService; + private final EnricherDtoService enricherDtoService; @Autowired public RoutesApiController(SeaportDtoService seaportDtoService, SeaportConnectionRoutesDtoService seaportConnectionRoutesDtoService, - RouteUrlSerializationService routeUrlSerializationService) { + RouteUrlSerializationService routeUrlSerializationService, EnricherDtoService enricherDtoService) { this.seaportDtoService = seaportDtoService; this.seaportConnectionRoutesDtoService = seaportConnectionRoutesDtoService; this.routeUrlSerializationService = routeUrlSerializationService; + this.enricherDtoService = enricherDtoService; } /** @@ -124,4 +148,48 @@ public RoutesResponse getRoutes(@PathVariable("seaportuid") BigInteger seaportUi return response; } + + + @ApiOperation(value = "Returns a list of all possible routes to a location.") + @RequestMapping(value = "/routes", method = GET) + @ResponseBody + public List getRoutesWithCoordinates(@RequestParam(value = "lat") double latitude, + @RequestParam(value = "lon") double longitude) { + + RouteInformation routeInformation = new RouteInformation(new GeoLocation(BigDecimal.valueOf(latitude), + BigDecimal.valueOf(longitude)), ONEWAY, TWENTY_LIGHT, EXPORT, WATERWAY); + + return seaportDtoService.getAllActive() + .stream() + .map(s -> seaportConnectionRoutesDtoService.getAvailableSeaportConnectionRoutes(s, routeInformation)) + .flatMap(Collection::stream) + .map(enricherDtoService::enrich) + .map(this::toRoutingResultDto) + .sorted(comparing(RoutingResultDto::getDistance)) + .collect(toList()); + } + + + private RoutingResultDto toRoutingResultDto(RouteDto routeDto) { + + RouteDataDto data = routeDto.getData(); + BigDecimal bargeDistance = data.getParts().stream().filter(p -> p.getRouteType() == BARGE) + .map(p -> p.getData().getBargeDieselDistance()) + .reduce(ZERO, BigDecimal::add); + + List stops = new ArrayList<>(); + stops.add(data.getParts().get(0).getOrigin()); + stops.addAll(data.getParts().stream().map(RoutePartDto::getDestination).collect(toList())); + + return new RoutingResultDto.Builder().withCo2(data.getCo2()) + .withCo2DirectTruck(data.getCo2DirectTruck()) + .withDistance(data.getTotalDistance()) + .withDuration(data.getTotalDuration()) + .withOnewayTruckDistance(data.getTotalOnewayTruckDistance()) + .withRealTollDistance(data.getTotalRealTollDistance()) + .withTollDistance(data.getTotalTollDistance()) + .withBargeDistance(bargeDistance) + .withStops(stops) + .build(); + } } diff --git a/src/main/java/net/contargo/iris/route/dto/RoutingResultDto.java b/src/main/java/net/contargo/iris/route/dto/RoutingResultDto.java new file mode 100644 index 00000000..1f7a9ec8 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/dto/RoutingResultDto.java @@ -0,0 +1,180 @@ +package net.contargo.iris.route.dto; + +import net.contargo.iris.address.dto.GeoLocationDto; + +import java.math.BigDecimal; + +import java.util.List; + + +/** + * @author Ben Antony - antony@synyx.de + */ +public class RoutingResultDto { + + private final BigDecimal co2; + private final BigDecimal co2DirectTruck; + private final BigDecimal distance; + private final BigDecimal bargeDistance; + private final BigDecimal onewayTruckDistance; + private final BigDecimal realTollDistance; + private final BigDecimal tollDistance; + private final BigDecimal duration; + private final List stops; + + private RoutingResultDto(Builder builder) { + + this.co2 = builder.co2; + this.co2DirectTruck = builder.co2DirectTruck; + this.distance = builder.distance; + this.bargeDistance = builder.bargeDistance; + this.onewayTruckDistance = builder.onewayTruckDistance; + this.realTollDistance = builder.realTollDistance; + this.tollDistance = builder.tollDistance; + this.duration = builder.duration; + this.stops = builder.stops; + } + + public BigDecimal getCo2() { + + return co2; + } + + + public BigDecimal getCo2DirectTruck() { + + return co2DirectTruck; + } + + + public BigDecimal getDistance() { + + return distance; + } + + + public BigDecimal getBargeDistance() { + + return bargeDistance; + } + + + public BigDecimal getOnewayTruckDistance() { + + return onewayTruckDistance; + } + + + public BigDecimal getRealTollDistance() { + + return realTollDistance; + } + + + public BigDecimal getTollDistance() { + + return tollDistance; + } + + + public BigDecimal getDuration() { + + return duration; + } + + + public List getStops() { + + return stops; + } + + public static class Builder { + + private BigDecimal co2; + private BigDecimal co2DirectTruck; + private BigDecimal distance; + private BigDecimal bargeDistance; + private BigDecimal onewayTruckDistance; + private BigDecimal realTollDistance; + private BigDecimal tollDistance; + private BigDecimal duration; + private List stops; + + public RoutingResultDto build() { + + return new RoutingResultDto(this); + } + + + public Builder withCo2(BigDecimal co2) { + + this.co2 = co2; + + return this; + } + + + public Builder withCo2DirectTruck(BigDecimal co2DirectTruck) { + + this.co2DirectTruck = co2DirectTruck; + + return this; + } + + + public Builder withDistance(BigDecimal distance) { + + this.distance = distance; + + return this; + } + + + public Builder withBargeDistance(BigDecimal bargeDistance) { + + this.bargeDistance = bargeDistance; + + return this; + } + + + public Builder withOnewayTruckDistance(BigDecimal onewayTruckDistance) { + + this.onewayTruckDistance = onewayTruckDistance; + + return this; + } + + + public Builder withRealTollDistance(BigDecimal realTollDistance) { + + this.realTollDistance = realTollDistance; + + return this; + } + + + public Builder withTollDistance(BigDecimal tollDistance) { + + this.tollDistance = tollDistance; + + return this; + } + + + public Builder withDuration(BigDecimal duration) { + + this.duration = duration; + + return this; + } + + + public Builder withStops(List stops) { + + this.stops = stops; + + return this; + } + } +} diff --git a/src/test/java/net/contargo/iris/route/api/RoutesApiControllerMvcUnitTest.java b/src/test/java/net/contargo/iris/route/api/RoutesApiControllerMvcUnitTest.java index 4b29fffd..7ab5cd3f 100644 --- a/src/test/java/net/contargo/iris/route/api/RoutesApiControllerMvcUnitTest.java +++ b/src/test/java/net/contargo/iris/route/api/RoutesApiControllerMvcUnitTest.java @@ -1,9 +1,15 @@ package net.contargo.iris.route.api; +import net.contargo.iris.GeoLocation; +import net.contargo.iris.connection.dto.RouteDataDto; import net.contargo.iris.connection.dto.RouteDto; +import net.contargo.iris.connection.dto.RoutePartDataDto; +import net.contargo.iris.connection.dto.RoutePartDto; import net.contargo.iris.connection.dto.SeaportConnectionRoutesDtoService; import net.contargo.iris.route.Route; import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.route.RoutePartData; +import net.contargo.iris.route.dto.EnricherDtoService; import net.contargo.iris.route.service.RouteUrlSerializationService; import net.contargo.iris.seaport.Seaport; import net.contargo.iris.seaport.dto.SeaportDto; @@ -24,9 +30,14 @@ import org.springframework.web.context.WebApplicationContext; +import java.math.BigDecimal; import java.math.BigInteger; import static net.contargo.iris.container.ContainerType.TWENTY_LIGHT; +import static net.contargo.iris.route.RouteCombo.WATERWAY; +import static net.contargo.iris.route.RouteDirection.EXPORT; +import static net.contargo.iris.route.RouteProduct.ONEWAY; +import static net.contargo.iris.route.RouteType.BARGE; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; @@ -49,6 +60,7 @@ import static java.math.BigDecimal.ONE; import static java.math.BigDecimal.TEN; +import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -68,13 +80,14 @@ public class RoutesApiControllerMvcUnitTest { private SeaportConnectionRoutesDtoService seaportConnectionRoutesDtoServiceMock; @Autowired private RouteUrlSerializationService routeUrlSerializationServiceMock; + @Autowired + private EnricherDtoService enricherDtoServiceMock; @Before public void setUp() { - reset(seaportDtoServiceMock); - reset(seaportConnectionRoutesDtoServiceMock); - reset(routeUrlSerializationServiceMock); + reset(seaportDtoServiceMock, seaportConnectionRoutesDtoServiceMock, routeUrlSerializationServiceMock, + enricherDtoServiceMock); } @@ -144,6 +157,67 @@ public void getRoutes() throws Exception { } + @Test + public void getRoutesWithCoordinates() throws Exception { + + SeaportDto seaport1 = new SeaportDto(); + seaport1.setUniqueId("uuid"); + + SeaportDto seaport2 = new SeaportDto(); + seaport2.setUniqueId("uuid2"); + + when(seaportDtoServiceMock.getAllActive()).thenReturn(asList(seaport1, seaport2)); + + RouteInformation routeInformation = new RouteInformation(new GeoLocation(new BigDecimal("49.00383145"), + new BigDecimal("8.3849306514255")), ONEWAY, TWENTY_LIGHT, EXPORT, WATERWAY); + + RouteDto routeDto1 = new RouteDto(); + RouteDto routeDto2 = new RouteDto(); + + when(seaportConnectionRoutesDtoServiceMock.getAvailableSeaportConnectionRoutes(seaport1, routeInformation)) + .thenReturn(asList(routeDto1, routeDto2)); + + RoutePartData routePartData1 = new RoutePartData(); + routePartData1.setBargeDieselDistance(new BigDecimal("300")); + + RoutePartDto routePartDto1 = new RoutePartDto(); + routePartDto1.setRouteType(BARGE); + routePartDto1.setData(new RoutePartDataDto(routePartData1)); + + RouteDataDto routeDataDto1 = new RouteDataDto(); + routeDataDto1.setTotalDistance(new BigDecimal("434")); + routeDataDto1.setParts(singletonList(routePartDto1)); + + RouteDto enrichedRouteDto1 = new RouteDto(); + enrichedRouteDto1.setData(routeDataDto1); + + RoutePartData routePartData2 = new RoutePartData(); + routePartData2.setBargeDieselDistance(new BigDecimal("150")); + + RoutePartDto routePartDto2 = new RoutePartDto(); + routePartDto2.setRouteType(BARGE); + routePartDto2.setData(new RoutePartDataDto(routePartData2)); + + RouteDataDto routeDataDto2 = new RouteDataDto(); + routeDataDto2.setTotalDistance(new BigDecimal("200")); + routeDataDto2.setParts(singletonList(routePartDto2)); + + RouteDto enrichedRouteDto2 = new RouteDto(); + enrichedRouteDto2.setData(routeDataDto2); + + when(enricherDtoServiceMock.enrich(routeDto1)).thenReturn(enrichedRouteDto1); + when(enricherDtoServiceMock.enrich(routeDto2)).thenReturn(enrichedRouteDto2); + + ResultActions resultActions = perform(get("/routes").param("lat", "49.00383145") + .param("lon", "8.3849306514255")); + + resultActions.andExpect(status().isOk()) + .andExpect(jsonPath("$[0].distance").value(200)) + .andExpect(jsonPath("$[0].bargeDistance").value(150)) + .andExpect(jsonPath("$[1].distance").value(434)); + } + + private ResultActions perform(MockHttpServletRequestBuilder builder) throws Exception { return webAppContextSetup(webApplicationContext).build().perform(builder);