From bd47cca1bf991d970484e0de96b1864438103ed5 Mon Sep 17 00:00:00 2001 From: jobulcke <127748878+jobulcke@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:54:23 +0200 Subject: [PATCH] feat: use name spaces in knowledge graph (#32) * feat: small improvements * feat: use name spaces in knowledge graph * fix: broken test * feat: blue bike popup improved * feat: small improvements and fixes --- backend/examples/example-application.yaml | 14 ++- backend/pom.xml | 12 ++ .../member/infra/MemberRepositoryImpl.java | 1 - .../rest/converters/ModelHttpConverter.java | 5 +- .../application/services/TripleService.java | 2 +- .../services/TripleServiceImpl.java | 7 +- .../domain/entities/MemberDescription.java | 22 ---- .../domain/repositories/TripleRepository.java | 6 +- .../domain/services/TriplesFactory.java | 117 ++++++++++++++++++ .../triple/domain/valueobjects/Node.java | 50 ++++++++ .../valueobjects/Triple.java | 27 ++-- .../triple/infra/TripleRepositoryImpl.java | 53 -------- .../infra/TripleRepositoryRDF4JImpl.java | 20 +-- .../triple/rest/TriplesController.java | 2 +- backend/src/main/resources/application.yaml | 70 ++++++----- .../services/TripleServiceImplTest.java | 58 ++++----- .../application/valueobjects/TripleTest.java | 52 -------- .../domain/services/TriplesFactoryTest.java | 66 ++++++++++ .../triple/domain/valueobjects/NodeTest.java | 55 ++++++++ .../domain/valueobjects/TripleTest.java | 55 ++++++++ .../infra/TripleRepositoryImplTest.java | 76 ------------ .../infra/TripleRepositoryRDF4JImplTest.java | 15 ++- .../triple/rest/TriplesControllerTest.java | 7 +- demonstrator.env | 22 ++++ docker-compose.yml | 20 +-- frontend/.env | 14 +-- frontend/src/App.vue | 2 + frontend/src/assets/main.css | 6 +- .../src/assets/svgs/legend/Wegsegment.svg | 2 +- .../svgs/legend/polygon_wegenwerken.svg | 2 +- .../src/components/graph/KnowledgeGraph.vue | 41 +++++- frontend/src/components/graph/ZoomButtons.vue | 50 ++++++++ .../graph/composables/useTriplesFetching.js | 71 ++++++++--- .../graph/functions/triplesToGraph.js | 37 +++--- .../src/components/linechart/LineChart.vue | 2 +- frontend/src/components/map/LeafletMap.vue | 54 ++++++-- .../components/map/composables/useMarkers.js | 2 +- .../components/map/composables/usePopup.js | 35 +++--- .../membercounter/MemberCounter.vue | 2 +- frontend/src/components/modal/MapButtons.vue | 6 +- frontend/vite.config.js | 2 +- 41 files changed, 746 insertions(+), 416 deletions(-) delete mode 100644 backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/entities/MemberDescription.java create mode 100644 backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactory.java create mode 100644 backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Node.java rename backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/{application => domain}/valueobjects/Triple.java (55%) delete mode 100644 backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImpl.java delete mode 100644 backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/TripleTest.java create mode 100644 backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactoryTest.java create mode 100644 backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/NodeTest.java create mode 100644 backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/TripleTest.java delete mode 100644 backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImplTest.java create mode 100644 demonstrator.env create mode 100644 frontend/src/components/graph/ZoomButtons.vue diff --git a/backend/examples/example-application.yaml b/backend/examples/example-application.yaml index 30b8355..681e0ae 100644 --- a/backend/examples/example-application.yaml +++ b/backend/examples/example-application.yaml @@ -16,14 +16,22 @@ ldes: member-type: "https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder" timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" property-predicates: - startTime: "http://data.europa.eu/m8g/startTime" - endTime: "http://data.europa.eu/m8g/endTime" + starttime: "http://data.europa.eu/m8g/startTime" + endtime: "http://data.europa.eu/m8g/endTime" verkeersmeting: member-type: "https://data.vlaanderen.be/ns/verkeersmetingen#Verkeersmeting" timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" property-predicates: - fullName: "http://custom/meetpunt#VolledigeNaam" + fullname: "http://custom/meetpunt#VolledigeNaam" countObservationResult: "http://def.isotc211.org/iso19156/2011/CountObservation#OM_CountObservation.result" + bluebikes: + member-type: "https://w3id.org/gbfs#Station" + timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" + property-predicates: + fullname: "http://schema.org/name" + capacity: "https://blue-bike.be/ns#capacity" + available: "https://w3id.org/gbfs#bikes_available" + used: "https://w3id.org/gbfs#bikes_in_use" graphdb: url: "http://localhost:8080/rdf4j-server/repositories/" repositoryId: "test" diff --git a/backend/pom.xml b/backend/pom.xml index 023b3b9..85841a5 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -15,6 +15,7 @@ UTF-8 3.1.2 3.0.0-M7 + 24.0.1 3.1.2 @@ -132,6 +133,12 @@ 1.16.1 + + org.jetbrains + annotations + ${jetbrains-annotations.version} + + org.springframework.boot @@ -178,6 +185,11 @@ ${wiremock.version} test + + org.assertj + assertj-core + 3.24.2 + diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java index 24f7864..2b15e5e 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/infra/MemberRepositoryImpl.java @@ -8,7 +8,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Optional; -import java.util.function.Function; @Repository public class MemberRepositoryImpl implements MemberRepository { diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/converters/ModelHttpConverter.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/converters/ModelHttpConverter.java index 93a894f..a5d0706 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/converters/ModelHttpConverter.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/member/rest/converters/ModelHttpConverter.java @@ -4,6 +4,7 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFParser; +import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpOutputMessage; import org.springframework.http.MediaType; @@ -31,7 +32,7 @@ protected boolean supports(Class clazz) { } @Override - protected Model readInternal(Class clazz, HttpInputMessage inputMessage) + protected Model readInternal(@NotNull Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { MediaType contentType = Objects.requireNonNull(inputMessage.getHeaders().getContentType()); Lang lang = @@ -42,7 +43,7 @@ protected Model readInternal(Class clazz, HttpInputMessage inpu } @Override - protected void writeInternal(Model model, HttpOutputMessage outputMessage) + protected void writeInternal(@NotNull Model model, @NotNull HttpOutputMessage outputMessage) throws UnsupportedOperationException, HttpMessageNotWritableException { throw new UnsupportedOperationException(); } diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleService.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleService.java index a761201..a682e27 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleService.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleService.java @@ -1,6 +1,6 @@ package be.informatievlaanderen.vsds.demonstrator.triple.application.services; -import be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects.Triple; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import java.util.List; diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImpl.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImpl.java index c905dab..51539c0 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImpl.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImpl.java @@ -1,6 +1,6 @@ package be.informatievlaanderen.vsds.demonstrator.triple.application.services; -import be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects.Triple; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import be.informatievlaanderen.vsds.demonstrator.triple.domain.repositories.TripleRepository; import org.springframework.stereotype.Service; @@ -16,9 +16,6 @@ public TripleServiceImpl(TripleRepository repository) { @Override public List getTriplesById(String id) { - return repository.getById(id).getModel().stream() - .map(statement -> new Triple(statement.getSubject().stringValue(), statement.getPredicate().stringValue(), statement.getObject().stringValue())) - .toList(); - + return repository.getById(id); } } diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/entities/MemberDescription.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/entities/MemberDescription.java deleted file mode 100644 index 2350797..0000000 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/entities/MemberDescription.java +++ /dev/null @@ -1,22 +0,0 @@ -package be.informatievlaanderen.vsds.demonstrator.triple.domain.entities; - - -import org.eclipse.rdf4j.model.Model; - -public class MemberDescription { - private final String memberId; - private final Model model; - - public MemberDescription(String memberId, Model model) { - this.memberId = memberId; - this.model = model; - } - - public String getMemberId() { - return memberId; - } - - public Model getModel() { - return model; - } -} diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/repositories/TripleRepository.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/repositories/TripleRepository.java index 1ee0214..7e272a9 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/repositories/TripleRepository.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/repositories/TripleRepository.java @@ -1,7 +1,9 @@ package be.informatievlaanderen.vsds.demonstrator.triple.domain.repositories; -import be.informatievlaanderen.vsds.demonstrator.triple.domain.entities.MemberDescription; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; + +import java.util.List; public interface TripleRepository { - MemberDescription getById(String id); + List getById(String id); } diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactory.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactory.java new file mode 100644 index 0000000..ccda2b2 --- /dev/null +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactory.java @@ -0,0 +1,117 @@ +package be.informatievlaanderen.vsds.demonstrator.triple.domain.services; + +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Node; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +public class TriplesFactory { + private static final Map COMMON_NAMESPACES = Map.ofEntries( + Map.entry("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), + Map.entry("rdfs", "http://www.w3.org/2000/01/rdf-schema#"), + Map.entry("owl", "http://www.w3.org/2002/07/owl#"), + Map.entry("xsd", "http://www.w3.org/2001/XMLSchema#"), + Map.entry("dct", "http://purl.org/dc/terms/"), + Map.entry("adms", "http://www.w3.org/ns/adms#"), + Map.entry("prov", "http://www.w3.org/ns/prov#"), + Map.entry("purl", "http://purl.org/dc/elements/1.1/"), + Map.entry("foaf", "http://xmlns.com/foaf/0.1/"), + Map.entry("org", "http://www.w3.org/ns/org#"), + Map.entry("legal", "http://www.w3.org/ns/legal#"), + Map.entry("m8g", "http://data.europa.eu/m8g/"), + Map.entry("locn", "http://www.w3.org/ns/locn#"), + Map.entry("ldes", "http://w3id.org/ldes#"), + Map.entry("tree", "https://w3id.org/tree#"), + Map.entry("sh", "http://www.w3.org/ns/shacl#"), + Map.entry("skos", "http://www.w3.org/2004/02/skos/core#"), + Map.entry("schema", "http://schema.org/") + ); + private static final String VALID_PREFIX_REGEX = "^[a-zA-Z_][\\w.-]*$"; // NCName regex + private final Stream statementStream; + private final Map namespaces; + + private TriplesFactory(@NotNull Model model) { + this.statementStream = model.stream(); + namespaces = new HashMap<>(COMMON_NAMESPACES); + + } + + /** + * Assigns the namespaces to this factory + * + * @param namespaces Map with namespace prefix as key and the according name as value + * @return the factory whereon this method is called + */ + public TriplesFactory withNamespaces(@NotNull Map namespaces) { + this.namespaces.putAll(namespaces); + + return this; + } + + /** + * Initialize an instance of TriplesFactory with the specified Model + * + * @param model Model with which the initialization needs to be done + * @return new instance of TriplesFactory + */ + public static TriplesFactory fromModel(@NotNull Model model) { + return new TriplesFactory(model); + } + + /** + * Converts all the statements of the model in this factory to a list of triples + * + * @return the list of triples + */ + public List getTriples() { + return statementStream + .map(statement -> new Triple(valueToNode(statement.getSubject()), valueToNode(statement.getPredicate()), valueToNode(statement.getObject()))) + .toList(); + } + + private Node valueToNode(@NotNull Value value) { + if (value.isIRI()) { + String name = ((IRI) value).getNamespace(); + return getNamespace(name).or(() -> createNamespace(name)) + .map(namespace -> new Node(value.stringValue(), namespace)) + .orElseGet(() -> new Node(value.stringValue())); + } + return new Node(value.stringValue()); + } + + private Optional> getNamespace(@NotNull String name) { + return namespaces.entrySet().stream().filter(entry -> entry.getValue().equals(name)).findFirst(); + } + + private Optional> createNamespace(@NotNull String name) { + Optional prefixOptional = name.contains("#") + ? createNamespacePrefix(name) + : createNonNamespacePrefix(name); + + return prefixOptional + .filter(prefix -> prefix.matches(VALID_PREFIX_REGEX)) + .map(prefix -> Map.entry(prefix, name)); + } + + private Optional createNamespacePrefix(@NotNull String name) { + int lastIndexOfHashtag = name.lastIndexOf('#'); + int lastIndexOfSlash = name.lastIndexOf('/'); + return Optional.of(name.substring(lastIndexOfSlash + 1, lastIndexOfHashtag)); + + } + + private Optional createNonNamespacePrefix(@NotNull String name) { + String[] nameSplit = name.split("/"); + int index = nameSplit.length - 1; + return Optional.ofNullable(nameSplit[index]); + } +} diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Node.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Node.java new file mode 100644 index 0000000..3b3b1e0 --- /dev/null +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Node.java @@ -0,0 +1,50 @@ +package be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; +import java.util.Objects; + +public class Node { + private final String value; + private Map.Entry namespace; + + public Node(@NotNull String value) { + this.value = value; + } + + public Node(@NotNull String value, @Nullable Map.Entry namespace) { + this.value = value; + this.namespace = namespace; + } + + public String getValue() { + return value; + } + + public String getPrefixedValue() { + if (namespace != null) { + return value.replace(namespace.getValue(), "%s:".formatted(namespace.getKey())); + } + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Node node = (Node) o; + + if (!value.equals(node.value)) return false; + return Objects.equals(namespace, node.namespace); + } + + @Override + public int hashCode() { + int result = value.hashCode(); + result = 31 * result + (namespace != null ? namespace.hashCode() : 0); + return result; + } +} diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/Triple.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Triple.java similarity index 55% rename from backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/Triple.java rename to backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Triple.java index ee064c5..2956cf5 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/Triple.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/Triple.java @@ -1,11 +1,13 @@ -package be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects; +package be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects; + +import org.jetbrains.annotations.NotNull; public class Triple { - private final String subject; - private final String predicate; - private final String object; + private final Node subject; + private final Node predicate; + private final Node object; - public Triple(String subject, String predicate, String object) { + public Triple(@NotNull Node subject, @NotNull Node predicate, @NotNull Node object) { this.subject = subject; this.predicate = predicate; this.object = object; @@ -31,15 +33,24 @@ public int hashCode() { return result; } - public String getSubject() { + @Override + public String toString() { + return "Triple{" + + "subject=" + subject.getValue() + + ", predicate=" + predicate.getValue() + + ", object=" + object.getValue() + + '}'; + } + + public Node getSubject() { return subject; } - public String getPredicate() { + public Node getPredicate() { return predicate; } - public String getObject() { + public Node getObject() { return object; } } diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImpl.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImpl.java deleted file mode 100644 index 3ff4ea2..0000000 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImpl.java +++ /dev/null @@ -1,53 +0,0 @@ -package be.informatievlaanderen.vsds.demonstrator.triple.infra; - -import be.informatievlaanderen.vsds.demonstrator.triple.domain.entities.MemberDescription; -import be.informatievlaanderen.vsds.demonstrator.triple.domain.repositories.TripleRepository; -import be.informatievlaanderen.vsds.demonstrator.triple.infra.exceptions.TripleFetchFailedException; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; -import org.eclipse.rdf4j.model.Model; -import org.eclipse.rdf4j.rio.RDFFormat; -import org.eclipse.rdf4j.rio.Rio; -import org.springframework.stereotype.Repository; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -//@Repository -public class TripleRepositoryImpl implements TripleRepository { - private final GraphDBConfig graphDBConfig; - - public TripleRepositoryImpl(GraphDBConfig graphDBConfig) { - this.graphDBConfig = graphDBConfig; - } - - @Override - public MemberDescription getById(String id) { - try (CloseableHttpClient httpclient = HttpClients.createDefault()) { - HttpPost httppost = new HttpPost(graphDBConfig.getUrl() + graphDBConfig.getRepositoryId()); - -// Request parameters and other properties. - - String queryString = "Describe<" + id + ">"; - List params = new ArrayList<>(1); - params.add(new BasicNameValuePair("query", queryString)); - httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); - -//Execute and get the response. - HttpResponse response = httpclient.execute(httppost); - HttpEntity entity = Objects.requireNonNull(response.getEntity()); - - Model model = Rio.parse(entity.getContent(), RDFFormat.NTRIPLES); - return new MemberDescription(id, model); - } catch (Exception e) { - throw new TripleFetchFailedException(id, e); - } - } -} diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImpl.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImpl.java index 26619a1..46db427 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImpl.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImpl.java @@ -1,13 +1,13 @@ package be.informatievlaanderen.vsds.demonstrator.triple.infra; -import be.informatievlaanderen.vsds.demonstrator.triple.domain.entities.MemberDescription; import be.informatievlaanderen.vsds.demonstrator.triple.domain.repositories.TripleRepository; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.services.TriplesFactory; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import be.informatievlaanderen.vsds.demonstrator.triple.infra.exceptions.TripleFetchFailedException; import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.Model; -import org.eclipse.rdf4j.model.impl.DynamicModel; -import org.eclipse.rdf4j.model.impl.DynamicModelFactory; import org.eclipse.rdf4j.query.GraphQueryResult; +import org.eclipse.rdf4j.query.QueryResults; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.config.RepositoryConfig; @@ -16,13 +16,15 @@ import org.eclipse.rdf4j.repository.manager.RepositoryManager; import org.eclipse.rdf4j.repository.sail.config.SailRepositoryConfig; import org.eclipse.rdf4j.sail.memory.config.MemoryStoreConfig; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + @org.springframework.stereotype.Repository public class TripleRepositoryRDF4JImpl implements TripleRepository { private final GraphDBConfig graphDBConfig; - private RepositoryManager repositoryManager; private Repository repository; private static final Logger log = LoggerFactory.getLogger(TripleRepositoryRDF4JImpl.class); @@ -32,8 +34,7 @@ public TripleRepositoryRDF4JImpl(GraphDBConfig graphDBConfig) { initRepo(new RemoteRepositoryManager(url)); } - protected void initRepo(RepositoryManager manager) { - repositoryManager = manager; + protected void initRepo(RepositoryManager repositoryManager) { repositoryManager.init(); try { if(!repositoryManager.hasRepositoryConfig(graphDBConfig.getRepositoryId())) { @@ -51,18 +52,17 @@ protected void initRepo(RepositoryManager manager) { } @Override - public MemberDescription getById(String id) { + public List getById(@NotNull String id) { try (RepositoryConnection dbConnection = repository.getConnection()) { dbConnection.setIsolationLevel(IsolationLevels.NONE); dbConnection.begin(); String queryString = "Describe<" + id + ">"; GraphQueryResult result = dbConnection.prepareGraphQuery(queryString).evaluate(); - Model model = new DynamicModel(new DynamicModelFactory()); - result.forEach(model::add); + Model model = QueryResults.asModel(result); dbConnection.commit(); - return new MemberDescription(id, model); + return TriplesFactory.fromModel(model).withNamespaces(result.getNamespaces()).getTriples(); } catch (Exception e) { throw new TripleFetchFailedException(id, e); } diff --git a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesController.java b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesController.java index 0818c0f..eaa660c 100644 --- a/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesController.java +++ b/backend/src/main/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesController.java @@ -1,7 +1,7 @@ package be.informatievlaanderen.vsds.demonstrator.triple.rest; import be.informatievlaanderen.vsds.demonstrator.triple.application.services.TripleService; -import be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects.Triple; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; diff --git a/backend/src/main/resources/application.yaml b/backend/src/main/resources/application.yaml index 30b8355..fe6d920 100644 --- a/backend/src/main/resources/application.yaml +++ b/backend/src/main/resources/application.yaml @@ -1,31 +1,39 @@ -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: - gipod: - member-type: "https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder" - timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" - property-predicates: - startTime: "http://data.europa.eu/m8g/startTime" - endTime: "http://data.europa.eu/m8g/endTime" - verkeersmeting: - member-type: "https://data.vlaanderen.be/ns/verkeersmetingen#Verkeersmeting" - timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" - property-predicates: - fullName: "http://custom/meetpunt#VolledigeNaam" - countObservationResult: "http://def.isotc211.org/iso19156/2011/CountObservation#OM_CountObservation.result" -graphdb: - url: "http://localhost:8080/rdf4j-server/repositories/" - repositoryId: "test" -server: - port: 8084 \ No newline at end of file +#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: +# gipod: +# member-type: "https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder" +# timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" +# property-predicates: +# starttime: "http://data.europa.eu/m8g/startTime" +# endtime: "http://data.europa.eu/m8g/endTime" +# verkeersmeting: +# member-type: "https://data.vlaanderen.be/ns/verkeersmetingen#Verkeersmeting" +# timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" +# property-predicates: +# fullname: "http://custom/meetpunt#VolledigeNaam" +# countObservationResult: "http://def.isotc211.org/iso19156/2011/CountObservation#OM_CountObservation.result" +# bluebikes: +# member-type: "https://w3id.org/gbfs#Station" +# timestamp-path: "http://www.w3.org/ns/prov#generatedAtTime" +# property-predicates: +# fullname: "http://schema.org/name" +# capacity: "https://blue-bike.be/ns#capacity" +# available: "https://w3id.org/gbfs#bikes_available" +# used: "https://w3id.org/gbfs#bikes_in_use" +#graphdb: +# url: "http://localhost:8080/rdf4j-server/repositories/" +# repositoryId: "test" +#server: +# port: 8084 \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImplTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImplTest.java index d7a83fc..cb1dc62 100644 --- a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImplTest.java +++ b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/services/TripleServiceImplTest.java @@ -1,67 +1,60 @@ package be.informatievlaanderen.vsds.demonstrator.triple.application.services; -import be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects.Triple; -import be.informatievlaanderen.vsds.demonstrator.triple.domain.entities.MemberDescription; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Node; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import be.informatievlaanderen.vsds.demonstrator.triple.domain.repositories.TripleRepository; import be.informatievlaanderen.vsds.demonstrator.triple.infra.exceptions.TripleFetchFailedException; -import org.eclipse.rdf4j.model.Model; -import org.eclipse.rdf4j.model.impl.TreeModelFactory; -import org.eclipse.rdf4j.rio.RDFFormat; -import org.eclipse.rdf4j.rio.Rio; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.util.ResourceUtils; import wiremock.com.google.common.io.Files; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; import java.util.function.Function; -import java.util.function.Predicate; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class TripleServiceImplTest { + private static final String FILE_NAME = "members/mobility-hindrance.nq"; + private static final int NUMBER_OF_STATEMENTS = 36; + private static final int NUMBER_OF_STATEMENTS_WITH_ID_AS_SUBJECT = 15; private static final String MEMBER_ID = "https://private-api.gipod.beta-vlaanderen.be/api/v1/mobility-hindrances/10810464/#ID"; + @InjectMocks + private TripleServiceImpl service; @Mock private TripleRepository repository; - private TripleService service; - - @BeforeEach - void setUp() { - service = new TripleServiceImpl(repository); - } @Test void when_MemberIsPresent_then_RetrieveTriples() throws IOException { - when(repository.getById(MEMBER_ID)).thenReturn(new MemberDescription(MEMBER_ID, readModelFromFile())); + when(repository.getById(MEMBER_ID)).thenReturn(readTriplesFromFile()); - var triples = readTriplesFromFile(); var fetchedTriples = service.getTriplesById(MEMBER_ID); - Predicate subjectPredicate = triple -> triple.getSubject().equals(MEMBER_ID); - assertEquals(triples.size(), fetchedTriples.size()); - assertEquals(triples.stream().filter(subjectPredicate).count(), fetchedTriples.stream().filter(subjectPredicate).count()); + assertThat(fetchedTriples) + .hasSize(NUMBER_OF_STATEMENTS) + .filteredOn(triple -> triple.getSubject().getValue().equals(MEMBER_ID)) + .hasSize(NUMBER_OF_STATEMENTS_WITH_ID_AS_SUBJECT); verify(repository).getById(MEMBER_ID); } @Test void when_MemberHasEmptyModel_then_RetrieveEmptyList() { - when(repository.getById(MEMBER_ID)).thenReturn(new MemberDescription(MEMBER_ID, new TreeModelFactory().createEmptyModel())); + when(repository.getById(MEMBER_ID)).thenReturn(List.of()); var fetchedTriples = service.getTriplesById(MEMBER_ID); - assertEquals(0, fetchedTriples.size()); + assertThat(fetchedTriples).isEmpty(); verify(repository).getById(MEMBER_ID); } @@ -69,23 +62,20 @@ void when_MemberHasEmptyModel_then_RetrieveEmptyList() { void when_MemberCannotBeFetched_then_RetrieveEmptyList() { when(repository.getById(MEMBER_ID)).thenThrow(new TripleFetchFailedException(MEMBER_ID, new RuntimeException())); - assertThrows(TripleFetchFailedException.class, () -> service.getTriplesById(MEMBER_ID)); - verify(repository).getById(MEMBER_ID); - } - + assertThatThrownBy(() -> service.getTriplesById(MEMBER_ID)) + .isInstanceOf(TripleFetchFailedException.class) + .hasMessage("Something went wrong while trying to fetch the triples with id %s".formatted(MEMBER_ID)); - private Model readModelFromFile() throws IOException { - File file = ResourceUtils.getFile("classpath:members/mobility-hindrance.nq"); - return Rio.parse(new FileInputStream(file), RDFFormat.NQUADS); + verify(repository).getById(MEMBER_ID); } private List readTriplesFromFile() throws IOException { - File file = ResourceUtils.getFile("classpath:members/mobility-hindrance.nq"); + File file = ResourceUtils.getFile("classpath:" + FILE_NAME); Function transformer = str -> str.length() > 1 ? str.substring(1, str.length() - 1) : str; return Files.readLines(file, Charset.defaultCharset()).stream() .map(line -> line.split(" ")) - .map(strings -> Arrays.stream(strings).map(transformer).toList()) - .map(strings -> new Triple(strings.get(0), strings.get(1), strings.get(2))) + .map(strings -> Arrays.stream(strings).map(transformer).map(Node::new).toList()) + .map(nodes -> new Triple(nodes.get(0), nodes.get(1), nodes.get(2))) .toList(); } } \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/TripleTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/TripleTest.java deleted file mode 100644 index 8d1e9e5..0000000 --- a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/application/valueobjects/TripleTest.java +++ /dev/null @@ -1,52 +0,0 @@ -package be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects; - -import org.antlr.v4.runtime.tree.Tree; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; - -import java.util.stream.Stream; - -import static org.junit.jupiter.api.Assertions.*; - -class TripleTest { - private static final String SUBJECT = "my-subject"; - private static final String PREDICATE = "my-predicate"; - private static final String OBJECT = "my-object"; - private static final Triple triple = new Triple(SUBJECT, PREDICATE, OBJECT); - - @Test - void test_equality() { - final Triple other = new Triple(SUBJECT, PREDICATE, OBJECT); - - assertEquals(triple.hashCode(), triple.hashCode()); - assertEquals(triple.hashCode(), other.hashCode()); - - assertEquals(triple, triple); - assertEquals(triple, other); - assertEquals(other, triple); - } - - @ParameterizedTest - @ArgumentsSource(TripleArgumentsProvider.class) - void test_inequality(Object other) { - assertNotEquals(triple, other); - } - - static class TripleArgumentsProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext extensionContext) throws Exception { - return Stream.of( - new Triple("false-subject", "false-predicate", "false-object"), - new Triple("other-subject", PREDICATE, OBJECT), - new Triple(SUBJECT, "other-predicate", OBJECT), - new Triple(SUBJECT, PREDICATE, "other-object"), - null, - new Object() - ).map(Arguments::of); - } - } -} \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactoryTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactoryTest.java new file mode 100644 index 0000000..af86109 --- /dev/null +++ b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/services/TriplesFactoryTest.java @@ -0,0 +1,66 @@ +package be.informatievlaanderen.vsds.demonstrator.triple.domain.services; + +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Node; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.rio.Rio; +import org.junit.jupiter.api.Test; +import org.springframework.util.ResourceUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class TriplesFactoryTest { + @Test + void given_RdfModel_when_GetTriplesFromFactory_then_ReturnList() throws IOException { + Model model = readModelFromFile(); + + List actualTriples = TriplesFactory.fromModel(model).getTriples(); + List predicates = model.predicates().stream() + .map(IRI::stringValue) + .toList(); + + assertThat(actualTriples) + .hasSameSizeAs(model) + .map(triple -> triple.getPredicate().getValue()) + .containsAll(predicates); + } + + @Test + void given_RdfModel_when_GetTriplesFromFactoryWithNamespaces_then_ReturnList() throws IOException { + Model model = readModelFromFile(); + Map namespaceMap = initNamespaceMap(); + + List triples = TriplesFactory.fromModel(model).withNamespaces(namespaceMap).getTriples(); + + assertThat(triples) + .hasSameSizeAs(model) + .map(Triple::getPredicate) + .filteredOn(predicate -> !predicate.getValue().equals(predicate.getPrefixedValue())) + .map(Node::getPrefixedValue) + .map(value -> value.split(":")[0]) + .containsAll(namespaceMap.keySet()); + } + + private Model readModelFromFile() throws IOException { + File file = ResourceUtils.getFile("classpath:members/mobility-hindrance.nq"); + return Rio.parse(Files.newInputStream(file.toPath()), RDFFormat.NQUADS); + } + + private Map initNamespaceMap() { + return Map.of( + "rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "dct", "http://purl.org/dc/terms/", + "adms", "http://www.w3.org/ns/adms#", + "prov", "http://www.w3.org/ns/prov#", + "purl", "http://purl.org/dc/elements/1.1/" + ); + } +} \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/NodeTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/NodeTest.java new file mode 100644 index 0000000..8422139 --- /dev/null +++ b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/NodeTest.java @@ -0,0 +1,55 @@ +package be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.Map; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class NodeTest { + private static final String VALUE = "my-node-value"; + private static final String NS_PREFIX = "ns"; + private static final String NS_NAME = "some/namespace#"; + private static final Node NODE = new Node(VALUE, Map.entry(NS_PREFIX, NS_NAME)); + + @Test + void test_equality() { + final Node other = new Node(VALUE, Map.entry(NS_PREFIX, NS_NAME)); + + assertThat(other.hashCode()) + .isEqualTo(NODE.hashCode()) + .isEqualTo(other.hashCode()); + + assertThat(NODE) + .isEqualTo(other) + .isEqualTo(NODE); + + assertThat(other).isEqualTo(NODE); + } + + @ParameterizedTest + @ArgumentsSource(NodeArgumentsProvider.class) + void test_inequality(Object other) { + assertThat(other).isNotEqualTo(NODE); + } + + static class NodeArgumentsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + new Node(VALUE, null), + new Node("other-value", Map.entry(NS_PREFIX, NS_NAME)), + new Node("other-value", null), + new Node("fake-value", Map.entry("other-ns", "my-custom.domain/ns#")), + null, + "This is not a node" + ).map(Arguments::of); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/TripleTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/TripleTest.java new file mode 100644 index 0000000..be015d6 --- /dev/null +++ b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/domain/valueobjects/TripleTest.java @@ -0,0 +1,55 @@ +package be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; + +class TripleTest { + private static final String SUBJECT = "my-subject"; + private static final String PREDICATE = "my-predicate"; + private static final String OBJECT = "my-object"; + private static final Triple triple = new Triple(new Node(SUBJECT), new Node(PREDICATE), new Node(OBJECT)); + + @Test + void test_equality() { + final Triple other = new Triple(new Node(SUBJECT), new Node(PREDICATE), new Node(OBJECT)); + + assertThat(triple.hashCode()) + .isEqualTo(triple.hashCode()) + .isEqualTo(other.hashCode()); + + assertThat(triple) + .isEqualTo(other) + .isEqualTo(triple); + + assertThat(other).isEqualTo(triple); + + } + + @ParameterizedTest + @ArgumentsSource(TripleArgumentsProvider.class) + void test_inequality(Object other) { + assertThat(other).isNotEqualTo(triple); + } + + static class TripleArgumentsProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) { + return Stream.of( + new Triple(new Node("false-subject"), new Node("false-predicate"), new Node("false-object")), + new Triple(new Node("other-subject"), new Node(PREDICATE), new Node(OBJECT)), + new Triple(new Node(SUBJECT), new Node("other-predicate"), new Node(OBJECT)), + new Triple(new Node(SUBJECT), new Node(PREDICATE), new Node("other-object")), + null, + new Object() + ).map(Arguments::of); + } + } +} \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImplTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImplTest.java deleted file mode 100644 index 5bcedf3..0000000 --- a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryImplTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package be.informatievlaanderen.vsds.demonstrator.triple.infra; - -import be.informatievlaanderen.vsds.demonstrator.triple.domain.entities.MemberDescription; -import be.informatievlaanderen.vsds.demonstrator.triple.domain.repositories.TripleRepository; -import be.informatievlaanderen.vsds.demonstrator.triple.infra.exceptions.TripleFetchFailedException; -import com.github.tomakehurst.wiremock.client.WireMock; -import com.github.tomakehurst.wiremock.http.Fault; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.github.tomakehurst.wiremock.junit5.WireMockTest; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.util.ResourceUtils; -import wiremock.com.fasterxml.jackson.databind.JsonNode; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.jupiter.api.Assertions.*; - -@WireMockTest(httpPort = 8189) -class TripleRepositoryImplTest { - private static final String MEMBER_ID = "https://private-api.gipod.beta-vlaanderen.be/api/v1/mobility-hindrances/10810464/#ID"; - private static final String ENDPOINT = "/rdf4j-server/repositories/test"; - private static final GraphDBConfig graphDbConfig = new GraphDBConfig(); - private TripleRepository repo; - - @BeforeAll - static void beforeAll() { - graphDbConfig.setUrl("http://localhost:8189/rdf4j-server/repositories/"); - graphDbConfig.setRepositoryId("test"); - } - - @BeforeEach - void setUp() { - repo = new TripleRepositoryImpl(graphDbConfig); - } - - @Test - void when_ExistingTriplesAreRequested_then_MemberDescriptionIsExpected() throws IOException { - stubFor(post(ENDPOINT).willReturn(ok().withBody(readDataFromFile()))); - - MemberDescription memberDescription = repo.getById(MEMBER_ID); - - assertEquals(MEMBER_ID, memberDescription.getMemberId()); - assertFalse(memberDescription.getModel().isEmpty()); - verify(postRequestedFor(urlEqualTo(ENDPOINT))); - } - - @Test - void when_BadRequestIsSent_then_EmptyModelIsExpected() { - stubFor(post(ENDPOINT).willReturn(badRequest())); - - MemberDescription memberDescription = repo.getById(MEMBER_ID); - - assertEquals(MEMBER_ID, memberDescription.getMemberId()); - assertTrue(memberDescription.getModel().isEmpty()); - verify(postRequestedFor(urlEqualTo(ENDPOINT))); - } - - @Test - void when_RequestFails_then_ExceptionIsExpected() { - stubFor(post(ENDPOINT).willReturn(badRequest().withFault(Fault.EMPTY_RESPONSE))); - - assertThrows(TripleFetchFailedException.class, () -> repo.getById(MEMBER_ID)); - verify(postRequestedFor(urlEqualTo(ENDPOINT))); - } - - private byte[] readDataFromFile() throws IOException { - Path path = ResourceUtils.getFile("classpath:members/mobility-hindrance.nq").toPath(); - return Files.readAllBytes(path); - } -} \ No newline at end of file diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImplTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImplTest.java index c05ca13..9512342 100644 --- a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImplTest.java +++ b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/infra/TripleRepositoryRDF4JImplTest.java @@ -1,6 +1,6 @@ package be.informatievlaanderen.vsds.demonstrator.triple.infra; -import be.informatievlaanderen.vsds.demonstrator.triple.domain.entities.MemberDescription; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.config.RepositoryConfig; import org.eclipse.rdf4j.repository.manager.LocalRepositoryManager; @@ -20,8 +20,9 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; class TripleRepositoryRDF4JImplTest { @@ -61,18 +62,16 @@ public void tearDown() { void when_ExistingTriplesAreRequested_then_MemberDescriptionIsExpected() throws IOException { populateRepository(); - MemberDescription memberDescription = repo.getById(MEMBER_ID); + List triples = repo.getById(MEMBER_ID); - assertEquals(MEMBER_ID, memberDescription.getMemberId()); - assertFalse(memberDescription.getModel().isEmpty()); + assertThat(triples).isNotEmpty(); } @Test void when_MemberNotPresent_then_EmptyModelIsExpected() { - MemberDescription memberDescription = repo.getById(MEMBER_ID); + List triples = repo.getById(MEMBER_ID); - assertEquals(MEMBER_ID, memberDescription.getMemberId()); - assertTrue(memberDescription.getModel().isEmpty()); + assertThat(triples).isEmpty(); } void populateRepository() throws IOException { diff --git a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesControllerTest.java b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesControllerTest.java index bea4359..b021f43 100644 --- a/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesControllerTest.java +++ b/backend/src/test/java/be/informatievlaanderen/vsds/demonstrator/triple/rest/TriplesControllerTest.java @@ -1,7 +1,8 @@ package be.informatievlaanderen.vsds.demonstrator.triple.rest; import be.informatievlaanderen.vsds.demonstrator.triple.application.services.TripleService; -import be.informatievlaanderen.vsds.demonstrator.triple.application.valueobjects.Triple; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Node; +import be.informatievlaanderen.vsds.demonstrator.triple.domain.valueobjects.Triple; import be.informatievlaanderen.vsds.demonstrator.triple.infra.exceptions.TripleFetchFailedException; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; @@ -66,8 +67,8 @@ private List readTriplesFromFile() throws IOException { return Files.readAllLines(path).stream() .map(str -> str.split(" ")) - .map(strings -> Arrays.stream(strings).map(transformer).toList()) - .map(strings -> new Triple(strings.get(0), strings.get(1), strings.get(2))) + .map(strings -> Arrays.stream(strings).map(transformer).map(Node::new).toList()) + .map(nodes -> new Triple(nodes.get(0), nodes.get(1), nodes.get(2))) .toList(); } } \ No newline at end of file diff --git a/demonstrator.env b/demonstrator.env new file mode 100644 index 0000000..2c1be55 --- /dev/null +++ b/demonstrator.env @@ -0,0 +1,22 @@ +SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/test +SPRING_DATASOURCE_USERNAME=postgres +SPRING_DATASOURCE_PASSWORD=test +SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.PostgreSQLDialect +SPRING_JPA_HIBERNATE_DDLAUTO=update +LDES_STREAMS_GIPOD_MEMBERTYPE=https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder +LDES_STREAMS_GIPOD_TIMESTAMPPATH=http://www.w3.org/ns/prov#generatedAtTime +LDES_STREAMS_GIPOD_PROPERTYPREDICATES_STARTTIME=http://data.europa.eu/m8g/startTime +LDES_STREAMS_GIPOD_PROPERTYPREDICATES_ENDTIME=http://data.europa.eu/m8g/endTime +LDES_STREAMS_VERKEERSMETING_MEMBERTYPE=https://data.vlaanderen.be/ns/verkeersmetingen#Verkeersmeting +LDES_STREAMS_VERKEERSMETING_TIMESTAMPPATH=http://www.w3.org/ns/prov#generatedAtTime +LDES_STREAMS_VERKEERSMETING_PROPERTYPREDICATES_FULLNAME=http://custom/meetpunt#VolledigeNaam +LDES_STREAMS_VERKEERSMETING_PROPERTYPREDICATES_COUNTOBSERVATIONRESULT=http://def.isotc211.org/iso19156/2011/CountObservation#OM_CountObservation.result +LDES_STREAMS_BLUEBIKES_MEMBERTYPE=https://w3id.org/gbfs#Station +LDES_STREAMS_BLUEBIKES_TIMESTAMPPATH=http://www.w3.org/ns/prov#generatedAtTime +LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_FULLNAME=http://schema.org/name +LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_CAPACITY=https://blue-bike.be/ns#capacity +LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_AVAILABLE=https://w3id.org/gbfs#bikes_available +LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_USED=https://w3id.org/gbfs#bikes_in_use +GRAPHDB_URL=http://rdf4j-server:8080/rdf4j-server/repositories/ +GRAPHDB_REPOSITORYID=test +SERVER_PORT=8080 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index c43bd3e..32c2b12 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,25 +23,7 @@ services: build: context: . dockerfile: Dockerfile - environment: - - SPRING_DATASOURCE_URL=jdbc:postgresql://postgresql:5432/test - - SPRING_DATASOURCE_USERNAME=postgres - - SPRING_DATASOURCE_PASSWORD=test - - SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT=org.hibernate.dialect.PostgreSQLDialect - - SPRING_JPA_HIBERNATE_DDLAUTO=update - - LDES_STREAMS_GIPOD_MEMBERTYPE=https://data.vlaanderen.be/ns/mobiliteit#Mobiliteitshinder - - LDES_STREAMS_GIPOD_TIMESTAMPPATH=http://www.w3.org/ns/prov#generatedAtTime - - LDES_STREAMS_VERKEERSMETING_MEMBERTYPE=https://data.vlaanderen.be/ns/verkeersmetingen#Verkeersmeting - - LDES_STREAMS_VERKEERSMETING_TIMESTAMPPATH=http://www.w3.org/ns/prov#generatedAtTime - - LDES_STREAMS_BLUEBIKES_MEMBERTYPE=https://w3id.org/gbfs#Station - - LDES_STREAMS_BLUEBIKES_TIMESTAMPPATH=http://www.w3.org/ns/prov#generatedAtTime - - LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_FULLNAME=http://schema.org/name - - LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_CAPACITY=https://blue-bike.be/ns#capacity - - LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_AVAILABLE=https://w3id.org/gbfs#bikes_available - - LDES_STREAMS_BLUEBIKES_PROPERTYPREDICATES_USED=https://w3id.org/gbfs#bikes_in_use - - GRAPHDB_URL=http://rdf4j-server:8080/rdf4j-server/repositories/ - - GRAPHDB_REPOSITORYID=test - - SERVER_PORT=8080 + env_file: ./demonstrator.env ports: - 8084:8080 depends_on: diff --git a/frontend/.env b/frontend/.env index 69dd8dc..006b70b 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,10 +1,4 @@ -# production -# VITE_API_BASE_URL=https://demonstrator.smartdataspace.dev-vlaanderen.be -# VITE_WS_BASE_URL=wss://demonstrator.smartdataspace.dev-vlaanderen.be - - -VITE_API_BASE_URL=http://localhost:8084 -VITE_WS_BASE_URL=ws://localhost:8084 - - - +### PRODUCTION URLS +## PLEASE DO NOT CHANGE OR COMMENT THESE, BUT ADD AN .env.local FOR LOCAL DEVELOPMENT +VITE_API_BASE_URL=https://demonstrator.smartdataspace.dev-vlaanderen.be +VITE_WS_BASE_URL=wss://demonstrator.smartdataspace.dev-vlaanderen.be \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 9ca5d88..bc8b89a 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -59,6 +59,8 @@ import LineChart from "@/components/linechart/LineChart.vue"; } .line-chart { + width: 40%; + height: 250px; min-width: 40%; min-height: 250px; } diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 272683a..7ecc712 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -122,12 +122,16 @@ body { line-height: 27px; /* 150% */ } +.body-small-regular { + font-size: 16px; + line-height: 16px; +} + .body-xxsmall-regular { font-size: 12px; line-height: 16px; } - b { font-weight: 700; } diff --git a/frontend/src/assets/svgs/legend/Wegsegment.svg b/frontend/src/assets/svgs/legend/Wegsegment.svg index deb0c0d..3571cb2 100644 --- a/frontend/src/assets/svgs/legend/Wegsegment.svg +++ b/frontend/src/assets/svgs/legend/Wegsegment.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/src/assets/svgs/legend/polygon_wegenwerken.svg b/frontend/src/assets/svgs/legend/polygon_wegenwerken.svg index f7ed816..0e19dca 100644 --- a/frontend/src/assets/svgs/legend/polygon_wegenwerken.svg +++ b/frontend/src/assets/svgs/legend/polygon_wegenwerken.svg @@ -1,4 +1,4 @@ + fill="#A813F7" fill-opacity="0.3" stroke="#A813F7"/> diff --git a/frontend/src/components/graph/KnowledgeGraph.vue b/frontend/src/components/graph/KnowledgeGraph.vue index 60159a6..b6ff805 100644 --- a/frontend/src/components/graph/KnowledgeGraph.vue +++ b/frontend/src/components/graph/KnowledgeGraph.vue @@ -1,7 +1,10 @@ + + + + \ No newline at end of file diff --git a/frontend/src/components/graph/composables/useTriplesFetching.js b/frontend/src/components/graph/composables/useTriplesFetching.js index f25d7cd..2e46e77 100644 --- a/frontend/src/components/graph/composables/useTriplesFetching.js +++ b/frontend/src/components/graph/composables/useTriplesFetching.js @@ -3,17 +3,27 @@ import * as d3 from "d3"; import {triplesToGraph} from "@/components/graph/functions/triplesToGraph"; const NODE_RADIUS = 8; +const NODE_STROKE_WIDTH = 1.5; const NODE_TEXT_FONT_SIZE = 11; const LINK_TEXT_FONT_SIZE = 9; function visualizeTriples(triples) { - const svg = d3.select("#knowledge-graph"); + const div = d3.select("#knowledge-graph") + div.select("svg").remove(); + const svg = div.append("svg") svg.selectAll("*").remove(); const g = svg.append("g"); + const tooltip = div.append("div") + .attr("id", "tooltip") + .attr("class", "shadow-medium body body-xxsmall-regular") + .style("display", "none") + const width = +svg.style("width").replace("px", "") const height = +svg.style("height").replace("px", ""); const graph = triplesToGraph(triples); + d3.select("#knowledge-graph-loading").remove(); + d3.select("#knowledge-graph-zoom-buttons").style("opacity", "1") const force = d3.forceSimulation(graph.nodes); function dragstart() { @@ -24,6 +34,19 @@ function visualizeTriples(triples) { return x < lo ? lo : x > hi ? hi : x; } + function onMouseOver(event, text) { + tooltip.style("display", "block").text(text) + } + + function onMouseOut() { + tooltip.style("display", "none"); + } + + function onMouseMove(event, marginBottom) { + let width = +tooltip.style("width").replace("px", ""); + tooltip.style("left", `${event.layerX - width / 2}px`).style("top", `${event.layerY - marginBottom}px`) + } + function dragged(event, d) { d.fx = clamp(event.x, 0, width); d.fy = clamp(event.y, 0, height); @@ -63,9 +86,10 @@ function visualizeTriples(triples) { .append("text") .attr("class", "link-text") .style("font-size", `${LINK_TEXT_FONT_SIZE}px`) - .text(function (d) { - return d.predicate; - }); + .on("mouseover", (event, d) => onMouseOver(event, d.predicate.id)) + .on("mouseout", onMouseOut) + .on("mousemove", event => onMouseMove(event, 42)) + .text(d => d.predicate.label); // ==================== Add Link Names ===================== const nodeTexts = g .selectAll(".node-text") @@ -74,9 +98,10 @@ function visualizeTriples(triples) { .append("text") .attr("class", "node-text") .style("font-size", `${NODE_TEXT_FONT_SIZE}px`) - .text(function (d) { - return d.label; - }); + .on("mouseover", (event, d) => onMouseOver(event, d.id)) + .on("mouseout", onMouseOut) + .on("mousemove", event => onMouseMove(event, 42)) + .text(d => d.label); // ==================== Add Node ===================== const nodes = g .selectAll(".node") @@ -85,6 +110,7 @@ function visualizeTriples(triples) { .append("circle") .attr("class", "node") .attr("r", NODE_RADIUS) + .style("stroke-width", `${NODE_STROKE_WIDTH}px`) .call(drag); let transform; @@ -92,47 +118,54 @@ function visualizeTriples(triples) { const zoom = d3.zoom().on("zoom", e => { g.attr("transform", () => transform = e.transform); nodes.attr("r", NODE_RADIUS / Math.sqrt(transform.k)) - nodeTexts.style("font-size", `${11 / Math.sqrt(transform.k)}px`) - linkTexts.style("font-size", `${9 / Math.sqrt(transform.k)}px`) + nodes.style("stroke-width", `${NODE_STROKE_WIDTH / Math.sqrt(transform.k)}px`) + nodeTexts.style("font-size", `${NODE_TEXT_FONT_SIZE / Math.sqrt(transform.k)}px`) + linkTexts.style("font-size", `${LINK_TEXT_FONT_SIZE / Math.sqrt(transform.k)}px`) }) + const zoomInBtn = d3.select("#knowledge-graph-zoom-in-btn") + .on("click", () => zoom.scaleBy(svg.transition().duration(350), 1.5)) + const zoomOutBtn = d3.select("#knowledge-graph-zoom-out-btn") + .on("click", () => zoom.scaleBy(svg.transition().duration(350), 0.75)) + function ticked() { + const DISTANCE_FACTOR = 2.5; nodes .attr("cx", function (d) { - return 2 * d.x; + return DISTANCE_FACTOR * d.x; }) .attr("cy", function (d) { - return 2 * d.y; + return DISTANCE_FACTOR * d.y; }); links .attr("x1", function (d) { - return 2 * d.source.x; + return DISTANCE_FACTOR * d.source.x; }) .attr("y1", function (d) { - return 2 * d.source.y; + return DISTANCE_FACTOR * d.source.y; }) .attr("x2", function (d) { - return 2 * d.target.x; + return DISTANCE_FACTOR * d.target.x; }) .attr("y2", function (d) { - return 2 * d.target.y; + return DISTANCE_FACTOR * d.target.y; }); nodeTexts .attr("x", function (d) { - return 2 * d.x + 12; + return DISTANCE_FACTOR * d.x + 12; }) .attr("y", function (d) { - return 2 * d.y + 3; + return DISTANCE_FACTOR * d.y + 3; }); linkTexts .attr("x", function (d) { - return 4 + 2 * (d.source.x + d.target.x) / 2; + return 4 + DISTANCE_FACTOR * (d.source.x + d.target.x) / 2; }) .attr("y", function (d) { - return 4 + 2 * (d.source.y + d.target.y) / 2; + return 4 + DISTANCE_FACTOR * (d.source.y + d.target.y) / 2; }); } diff --git a/frontend/src/components/graph/functions/triplesToGraph.js b/frontend/src/components/graph/functions/triplesToGraph.js index 7ade3c4..95c5810 100644 --- a/frontend/src/components/graph/functions/triplesToGraph.js +++ b/frontend/src/components/graph/functions/triplesToGraph.js @@ -1,33 +1,40 @@ -function filterNodesById(nodes,id){ - return nodes.filter(function(n) { return n.id === id; }); +function filterNodesById(nodes, id) { + return nodes.filter(function (n) { + return n.id === id; + }); } -export function triplesToGraph(triples){ +export function triplesToGraph(triples) { //Graph - var graph={nodes:[], links:[]}; + var graph = {nodes: [], links: []}; //Initial Graph from triples - triples.forEach(function(triple){ - var subjId = triple.subject; - var predId = triple.predicate; - var objId = triple.object; + triples.forEach(function (triple) { + var subject = triple.subject; + var predicate = triple.predicate; + var object = triple.object; - var subjNode = filterNodesById(graph.nodes, subjId)[0]; - var objNode = filterNodesById(graph.nodes, objId)[0]; + var subjNode = filterNodesById(graph.nodes, subject.value)[0]; + var objNode = filterNodesById(graph.nodes, object.value)[0]; - if(subjNode==null){ - subjNode = {id:subjId, label:subjId, weight:1}; + if (subjNode == null) { + subjNode = {id: subject.value, label: subject.prefixedValue, weight: 1}; graph.nodes.push(subjNode); } - if(objNode==null){ - objNode = {id:objId, label:objId, weight:1}; + if (objNode == null) { + objNode = {id: object.value, label: object.prefixedValue, weight: 1}; graph.nodes.push(objNode); } - graph.links.push({source:subjNode, target:objNode, predicate:predId, weight:1}); + graph.links.push({ + source: subjNode, + target: objNode, + predicate: {label: predicate.prefixedValue, id: predicate.value}, + weight: 1 + }); }); return graph; diff --git a/frontend/src/components/linechart/LineChart.vue b/frontend/src/components/linechart/LineChart.vue index 3cb2bd9..78032a8 100644 --- a/frontend/src/components/linechart/LineChart.vue +++ b/frontend/src/components/linechart/LineChart.vue @@ -108,7 +108,7 @@ export default { }); }, error => { - console.log(error); + console.error(error); this.connect() } ); diff --git a/frontend/src/components/map/LeafletMap.vue b/frontend/src/components/map/LeafletMap.vue index ea9f7dc..543868f 100644 --- a/frontend/src/components/map/LeafletMap.vue +++ b/frontend/src/components/map/LeafletMap.vue @@ -13,7 +13,7 @@ time = timestamp; timePeriod = period; }" - @realtime-toggled="(isRealTimeEnabled) => isRealTimeEnabled ? connect() : disconnect()" + @realtime-toggled="isRealTimeEnabled => isRealTimeEnabled ? connect() : disconnect()" /> @@ -100,18 +100,13 @@ export default { mounted() { this.connect() - console.log(import.meta.env.VITE_WS_BASE_URL); - this.map = L.map("map", {zoomAnimation: false, zoomControl: false}).setView([50.7747, 4.4852], 8) + this.map = L.map("map", {zoomAnimation: false, zoomControl: false}).setView([50.9, 4.15], 8) L.control.zoom({position: "topright"}).addTo(this.map) L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(this.map); - //TODO: delete this and hard code the bounds of Flanders/Belgium for performance reasons this.map.on("popupclose", () => this.memberId = null) - // this.map.on("moveend", () => { - // this.fetchMembers(); - // }); this.fetchMembers(); for (let [key, value] of this.layersToShow.entries()) { if (value) { @@ -139,7 +134,10 @@ export default { timestamp: new Date(this.time).toISOString().replace("Z", ""), timePeriod: this.timePeriod }, - data: this.map.getBounds(), + data: { + _northEast: {lat: 51.61113728, lng: 6.60827637}, + _southWest: {lat: 49.37098431, lng: 2.38952637} + }, headers: { 'Content-type': 'application/json', 'Access-Control-Allow-Origin': '*' @@ -157,12 +155,13 @@ export default { }, //websocket connect() { + this.layers.forEach(layer => layer.clearLayers()) this.stompClient = new Stomp.client(`${import.meta.env.VITE_WS_BASE_URL}/update`, {debug: false}); this.stompClient.connect( {}, () => this.subscribe(), error => { - console.log(error); + console.error(error); this.connect() } ); @@ -190,7 +189,7 @@ export default { } }, updateMarker(marker) { - marker.setStyle({color: '#808080'}) + marker.setStyle({color: '#A813F7'}) } } @@ -241,6 +240,10 @@ export default { margin: 12px; } +.leaflet-popup-content-wrapper { + border-radius: 3px !important; +} + .marker-cluster-flanders { position: relative; color: #fff; @@ -262,6 +265,37 @@ export default { gap: 6px; } +.popup-grid { + display: grid; + row-gap: 6px; + column-gap: 12px; + grid-template-columns: auto auto; +} + +.popup-gipod-icon { + width: 11px; + height: 16px; +} + +.popup-bluebike-icon { + width: 24px; + height: 24px; +} + +.popup-grid-icon { + grid-column-start: span 2; + justify-self: center; +} + +.popup-title { + grid-column-start: span 2; + justify-self: center; +} + +.popup-grid-end { + justify-self: end; +} + .popup-verkeersmeting-map-marker { width: 16px; height: 16px; diff --git a/frontend/src/components/map/composables/useMarkers.js b/frontend/src/components/map/composables/useMarkers.js index 191071b..95b57b6 100644 --- a/frontend/src/components/map/composables/useMarkers.js +++ b/frontend/src/components/map/composables/useMarkers.js @@ -54,7 +54,7 @@ export function useMarkers(memberGeometries, collection, onMarkerClicked, onPopu } } let geoJson = L.geoJson(geoJsonFeature, {onEachFeature: onEachFeature, pointToLayer: pointToLayer}) - geoJson.setStyle({color: '#808080'}); + geoJson.setStyle({color: '#A813F7'}); markers.push(geoJson) }) return markers; diff --git a/frontend/src/components/map/composables/usePopup.js b/frontend/src/components/map/composables/usePopup.js index 58952e5..579cc14 100644 --- a/frontend/src/components/map/composables/usePopup.js +++ b/frontend/src/components/map/composables/usePopup.js @@ -1,34 +1,37 @@ import mapsMarker from "../../../assets/svgs/legend/maps.marker.svg" import carIcon from "../../../assets/svgs/legend/car.svg" import bikeIcon from "../../../assets/svgs/legend/bike.svg" +import alertIcon from "../../../assets/svgs/legend/alert-triangle-filled.svg" + +function getBikeString(number) { + return number === "1" ? "fiets" : "fietsen"; +} + export function usePopup(collection, properties) { - console.log(collection) switch (collection) { case "gipod": - return `

${properties.startTime}

` + return `` case "verkeersmeting": return `` case "bluebikes": - return `