From cd24787be3369861e108a0756c8a20ebac573b84 Mon Sep 17 00:00:00 2001 From: jobulcke <127748878+jobulcke@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:41:13 +0200 Subject: [PATCH] Feat/show historical state (#10) * feat: slider + shortcuts + get members in time period * feat: play button logic implemented * feat: rebase issues fixed * feat: slight improvements --- docker-compose.yml | 3 +- .../application/services/MemberService.java | 2 +- .../services/MemberServiceImpl.java | 9 +- .../member/repositories/MemberRepository.java | 2 +- .../member/infra/MemberEntity.java | 2 +- .../infra/MemberEntityJpaRepository.java | 4 +- .../member/infra/MemberRepositoryImpl.java | 4 +- .../member/rest/MembersController.java | 4 +- .../src/main/resources/application.yaml | 22 -- .../services/MemberServiceImplTest.java | 15 +- .../infra/MemberRepositoryImplTest.java | 14 +- .../member/rest/MembersControllerTest.java | 9 +- vue-frontend/src/App.vue | 9 - .../src/components/map/LeafletMap.vue | 233 ++++-------------- vue-frontend/src/components/map/useMarkers.js | 179 ++++++++++++++ vue-frontend/src/components/slider/Slider.vue | 79 +++++- vue-frontend/vite.config.js | 2 +- 17 files changed, 340 insertions(+), 252 deletions(-) delete mode 100644 spring-boot-backend/src/main/resources/application.yaml create mode 100644 vue-frontend/src/components/map/useMarkers.js diff --git a/docker-compose.yml b/docker-compose.yml index 95b75c0..7d70e36 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,8 @@ services: - SPRING_DATASOURCE_PASSWORD=test - SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.PostgreSQLDialect - SPRING_JPA_HIBERNATE_DDLAUTO=update - - LDES_STREAMS_0_=https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder + - LDES_STREAMS_0_MEMBER_TYPE=https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder + - LDES_STREAMS_0_TIMESTAMP_PATH=http://www.w3.org/ns/prov#generatedAtTime - GRAPHDB_URL=http://rdf4j-server:8080/rdf4j-server/repositories/ - GRAPHDB_REPOSITORYID=test ports: diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberService.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberService.java index 40cf50b..75d6828 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberService.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberService.java @@ -10,7 +10,7 @@ public interface MemberService { void ingestMember(IngestedMemberDto ingestedMemberDto); - List getMembersInRectangle(Geometry rectangleGeometry, LocalDateTime timestamp); + List getMembersInRectangle(Geometry rectangleGeometry, LocalDateTime timestamp, String timePeriod); MemberDto getMemberById(String memberId); } diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImpl.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImpl.java index d8eee5f..d7419d5 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImpl.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImpl.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Service; import org.wololo.jts2geojson.GeoJSONWriter; +import java.time.Duration; import java.time.LocalDateTime; import java.util.List; @@ -36,8 +37,12 @@ public void ingestMember(IngestedMemberDto ingestedMemberDto) { } @Override - public List getMembersInRectangle(Geometry rectangleGeometry, LocalDateTime timestamp) { - return repository.getMembersByGeometry(rectangleGeometry, timestamp).stream().map(memberGeometry -> new MemberDto(memberGeometry.getMemberId(), geoJSONWriter.write(memberGeometry.getGeometry()), memberGeometry.getTimestamp())).toList(); + public List getMembersInRectangle(Geometry rectangleGeometry, LocalDateTime timestamp, String timePeriod) { + var duration = Duration.parse(timePeriod).dividedBy(2); + return repository.getMembersByGeometry(rectangleGeometry, timestamp.minus(duration), timestamp.plus(duration)) + .stream() + .map(memberGeometry -> new MemberDto(memberGeometry.getMemberId(), geoJSONWriter.write(memberGeometry.getGeometry()), memberGeometry.getTimestamp())) + .toList(); } @Override diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/domain/member/repositories/MemberRepository.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/domain/member/repositories/MemberRepository.java index f05ac6b..b69a231 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/domain/member/repositories/MemberRepository.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/domain/member/repositories/MemberRepository.java @@ -9,6 +9,6 @@ public interface MemberRepository { void saveMember(Member geometry); - List getMembersByGeometry(Geometry geometry, LocalDateTime timestamp); + List getMembersByGeometry(Geometry geometry, LocalDateTime startTime, LocalDateTime endTime); Optional findByMemberId(String memberId); } diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntity.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntity.java index 3c95afd..bb06565 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntity.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntity.java @@ -6,7 +6,7 @@ import java.time.LocalDateTime; -@Entity +@Entity(name = "member_entity") public class MemberEntity { @Id private String memberId; diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntityJpaRepository.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntityJpaRepository.java index e0db65d..c7da968 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntityJpaRepository.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberEntityJpaRepository.java @@ -10,6 +10,6 @@ public interface MemberEntityJpaRepository extends JpaRepository { - @Query(value = "select l from MemberEntity l where intersects(l.geometry, :geometry) = true and l.timestamp = :timestamp") - List getMemberGeometryEntitiesCoveredByGeometry(@Param("geometry") Geometry geometry, @Param("timestamp") LocalDateTime timestamp); + @Query(value = "select l from member_entity l where intersects(l.geometry, :geometry) = true and l.timestamp >= :startTime and l.timestamp <= :endTime") + List getMemberGeometryEntitiesCoveredByGeometryInTimePeriod(@Param("geometry") Geometry geometry, @Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime); } diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java index 23a40d8..01e2364 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java @@ -23,9 +23,9 @@ public void saveMember(Member member) { } @Override - public List getMembersByGeometry(Geometry geometry, LocalDateTime timestamp) { + public List getMembersByGeometry(Geometry geometry, LocalDateTime startTime, LocalDateTime endTime) { return memberGeometryJpaRepo - .getMemberGeometryEntitiesCoveredByGeometry(geometry, timestamp) + .getMemberGeometryEntitiesCoveredByGeometryInTimePeriod(geometry, startTime, endTime) .stream() .map(entity -> new Member(entity.getMemberId(), entity.getGeometry(), entity.getTimestamp())) .toList(); diff --git a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersController.java b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersController.java index fc89161..a8c989f 100644 --- a/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersController.java +++ b/spring-boot-backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersController.java @@ -25,8 +25,8 @@ public MemberDto retrieveLdesFragment(@PathVariable String memberId) { @PostMapping(value = "/in-rectangle", consumes = {"application/json"}) @CrossOrigin(origins = "*", allowedHeaders = "*") - public List getMembersInRectangle(@RequestBody MapBoundsDto mapBoundsDto, @RequestParam LocalDateTime timestamp) { - return service.getMembersInRectangle(mapBoundsDto.getGeometry(), timestamp); + public List getMembersInRectangle(@RequestBody MapBoundsDto mapBoundsDto, @RequestParam LocalDateTime timestamp, @RequestParam(defaultValue = "PT1M") String timePeriod) { + return service.getMembersInRectangle(mapBoundsDto.getGeometry(), timestamp, timePeriod); } @PostMapping(value = "/members") diff --git a/spring-boot-backend/src/main/resources/application.yaml b/spring-boot-backend/src/main/resources/application.yaml deleted file mode 100644 index 3ef4bbd..0000000 --- a/spring-boot-backend/src/main/resources/application.yaml +++ /dev/null @@ -1,22 +0,0 @@ -spring: - datasource: - url: jdbc:postgresql://localhost:25432/test - username: postgres - password: test - jpa: - properties: - hibernate: - dialect: org.hibernate.dialect.PostgreSQLDialect - hibernate: - ddl-auto: update - -ldes: - streams: - - - member-type: "https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder" - timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" -graphdb: - url: "http://localhost:8080/rdf4j-server/repositories/" - repositoryId: "test" -server: - port: 8084 \ No newline at end of file diff --git a/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImplTest.java b/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImplTest.java index a3d84e4..5d55bea 100644 --- a/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImplTest.java +++ b/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/application/services/MemberServiceImplTest.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.time.Duration; import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -38,7 +39,11 @@ @ExtendWith(MockitoExtension.class) class MemberServiceImplTest { + private static final String TIME_PERIOD = "PT5M"; + private static final Duration duration = Duration.parse(TIME_PERIOD).dividedBy(2); private static final LocalDateTime timestamp = ZonedDateTime.parse("2022-05-20T09:58:15.867Z").toLocalDateTime(); + private static final LocalDateTime startTime = timestamp.minus(duration); + private static final LocalDateTime endTime = timestamp.plus(duration); private static Geometry rectangle; @Mock @@ -67,22 +72,22 @@ class GetMembersInRectangle { void when_GetMembersInRectangle_then_ReturnListOfMemberDtos() throws ParseException { final GeoJSONReader geoJSONReader = new GeoJSONReader(); final List members = initMembers(); - when(repository.getMembersByGeometry(rectangle, timestamp)).thenReturn(members); + when(repository.getMembersByGeometry(rectangle, startTime, endTime)).thenReturn(members); - final List retrievedMembers = service.getMembersInRectangle(rectangle, timestamp).stream() + final List retrievedMembers = service.getMembersInRectangle(rectangle, timestamp, TIME_PERIOD).stream() .map(dto -> new Member(dto.getMemberId(), geoJSONReader.read(dto.getGeojsonGeometry()), dto.getTimestamp())) .toList(); assertEquals(members, retrievedMembers); - verify(repository).getMembersByGeometry(rectangle, timestamp); + verify(repository).getMembersByGeometry(rectangle, startTime, endTime); } @Test void when_GetMembersInRectangle_then_VerifyMemberIsInRectangle() throws ParseException { final WKTReader wktReader = new WKTReader(); final GeoJSONReader geoJSONReader = new GeoJSONReader(); - when(repository.getMembersByGeometry(rectangle, timestamp)).thenReturn(initMembers()); + when(repository.getMembersByGeometry(rectangle, startTime, endTime)).thenReturn(initMembers()); - List retrievedMembers = service.getMembersInRectangle(rectangle, timestamp).stream() + List retrievedMembers = service.getMembersInRectangle(rectangle, timestamp, TIME_PERIOD).stream() .map(dto -> geoJSONReader.read(dto.getGeojsonGeometry())) .toList(); Geometry outsidePoint = wktReader.read("POINT(6 6)"); diff --git a/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImplTest.java b/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImplTest.java index ca96578..b174aac 100644 --- a/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImplTest.java +++ b/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImplTest.java @@ -27,6 +27,8 @@ class MemberRepositoryImplTest { private static final String ID = "member-id"; private static final LocalDateTime timestamp = ZonedDateTime.parse("2022-05-20T09:58:15.867Z").toLocalDateTime(); + private static final LocalDateTime startTime = ZonedDateTime.parse("2022-05-20T09:53:15.867Z").toLocalDateTime(); + private static final LocalDateTime endTime = ZonedDateTime.parse("2022-05-20T10:03:15.867Z").toLocalDateTime(); private static Geometry point; private static Geometry rectangle; private MemberRepository repository; @@ -82,20 +84,20 @@ void when_DbDoesContainMembers_then_ReturnMembersInRectangle() throws ParseExcep .map(entity -> new Member(entity.getMemberId(), entity.getGeometry(), timestamp)) .toList(); - when(jpaRepository.getMemberGeometryEntitiesCoveredByGeometry(rectangle, timestamp)).thenReturn(entities); + when(jpaRepository.getMemberGeometryEntitiesCoveredByGeometryInTimePeriod(rectangle, startTime, endTime)).thenReturn(entities); - assertEquals(members, repository.getMembersByGeometry(rectangle, timestamp)); + assertEquals(members, repository.getMembersByGeometry(rectangle, startTime, endTime)); - verify(jpaRepository).getMemberGeometryEntitiesCoveredByGeometry(rectangle, timestamp); + verify(jpaRepository).getMemberGeometryEntitiesCoveredByGeometryInTimePeriod(rectangle, startTime, endTime); } @Test void when_DbContainsOnlyMembersOutsideRectangle_then_ReturnEmptyList() { - when(jpaRepository.getMemberGeometryEntitiesCoveredByGeometry(rectangle, timestamp)).thenReturn(List.of()); + when(jpaRepository.getMemberGeometryEntitiesCoveredByGeometryInTimePeriod(rectangle, startTime, endTime)).thenReturn(List.of()); - assertEquals(List.of(), repository.getMembersByGeometry(rectangle, timestamp)); + assertEquals(List.of(), repository.getMembersByGeometry(rectangle, startTime, endTime)); - verify(jpaRepository).getMemberGeometryEntitiesCoveredByGeometry(rectangle, timestamp); + verify(jpaRepository).getMemberGeometryEntitiesCoveredByGeometryInTimePeriod(rectangle, startTime, endTime); } } diff --git a/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersControllerTest.java b/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersControllerTest.java index 1c976bd..6e25268 100644 --- a/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersControllerTest.java +++ b/spring-boot-backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/member/rest/MembersControllerTest.java @@ -92,21 +92,24 @@ void when_MemberGeometryIsNotPresent_then_StatusIs404() throws Exception { @Test void when_MapBoundariesArePosted_then_ReturnMemberGeometries() throws Exception { + final String timePeriod = "PT1M"; Geometry rectangle = new WKTReader().read("POLYGON((0 0, 0 5, 5 5, 5 0, 0 0))"); List members = initMembers(); String json = "[" + members.stream() .map(dto -> transformToJson(dto.getMemberId(), dto.getGeojsonGeometry(), dto.getTimestamp())) .collect(Collectors.joining(", ")) + "]"; - when(service.getMembersInRectangle(rectangle, timestamp)).thenReturn(members); + when(service.getMembersInRectangle(rectangle, timestamp, timePeriod)).thenReturn(members); - mockMvc.perform(post("/in-rectangle?timestamp=" + timestamp) + mockMvc.perform(post("/in-rectangle") + .param("timestamp", timestamp.toString()) + .param("timePeriod", timePeriod) .contentType(MediaType.APPLICATION_JSON) .content(readDataFromFile("mapbounds/rectangle.json"))) .andExpect(content().json(json)) .andExpect(status().isOk()); - verify(service).getMembersInRectangle(rectangle, timestamp); + verify(service).getMembersInRectangle(rectangle, timestamp, timePeriod); } @Nested diff --git a/vue-frontend/src/App.vue b/vue-frontend/src/App.vue index 289cc50..00203a8 100644 --- a/vue-frontend/src/App.vue +++ b/vue-frontend/src/App.vue @@ -1,9 +1,6 @@ + + \ No newline at end of file diff --git a/vue-frontend/vite.config.js b/vue-frontend/vite.config.js index 84b42a3..00247f8 100644 --- a/vue-frontend/vite.config.js +++ b/vue-frontend/vite.config.js @@ -20,7 +20,7 @@ export default defineConfig({ proxy: { // string shorthand: http://localhost:5173/foo -> http://localhost:8084/foo '/in-rectangle': 'http://localhost:8084', - '/triples': 'http://spring-boot-backend:8084' + '/triples': 'http://localhost:8084' } } })