diff --git a/src/components/MapClient.d.ts b/src/components/MapClient.d.ts index 45b76fc..32e90aa 100644 --- a/src/components/MapClient.d.ts +++ b/src/components/MapClient.d.ts @@ -19,3 +19,13 @@ export enum ShapeTypes { } export type DrawingModes = 'MARKER' | 'RECTANGLE' | 'POLYGON' + +/** well known text simplified result object, as returned by mapSearch */ +export type WKTResult = { + uri: string + link: string + wkt: string + fcLabel: string + label: string + id?: string +} diff --git a/src/components/MapClient.vue b/src/components/MapClient.vue index 7f28a36..a6ae9be 100644 --- a/src/components/MapClient.vue +++ b/src/components/MapClient.vue @@ -6,7 +6,7 @@ import { wktToGeoJSON } from "@terraformer/wkt" import { mapConfigKey, type MapConfig } from "@/types"; import { convertConfigTypes } from '@/util/mapSearchHelper' import type { MapOptionsCenter } from '@/types' -import type { WKTResult } from '@/stores/mapSearchStore.d'; +import type { WKTResult } from "@/components/MapClient.d"; import { ShapeTypes, type DrawingModes } from "@/components/MapClient.d"; diff --git a/src/components/search/CatPrezSearchMap.vue b/src/components/search/CatPrezSearchMap.vue index 60f1b23..e162714 100644 --- a/src/components/search/CatPrezSearchMap.vue +++ b/src/components/search/CatPrezSearchMap.vue @@ -172,7 +172,6 @@ async function getThemes() { * Performs search via a SPARQL query */ async function doSearch() { - console.log("performing search...") const searchData = await searchSparqlGetRequest(`${apiBaseUrl}/sparql`, query.value); if (searchData && !searchError.value) { results.value = (searchData.results.bindings as SparqlBinding[]).map(result => { @@ -317,7 +316,6 @@ onMounted(async () => {
diff --git a/src/components/search/SpacePrezSearchMap.vue b/src/components/search/SpacePrezSearchMap.vue index 5cdc674..018e7ac 100644 --- a/src/components/search/SpacePrezSearchMap.vue +++ b/src/components/search/SpacePrezSearchMap.vue @@ -256,6 +256,9 @@ async function doSearch() { label: result.f_label ? result.f_label.value : "" } }); + if (searchMap.value) { + searchMap.value.drawShape(results.value); + } } } } diff --git a/src/stores/catalogQueries.d.ts b/src/stores/catalogQueries.d.ts deleted file mode 100644 index 364c43c..0000000 --- a/src/stores/catalogQueries.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -export type RDCatalog = { - c: string; - t: string; -} - -export type RDTheme = { - count: string; - pl: string; - th: string; -} - -export type RDSearch = { - r: string; - t: string; - d: string; - thlist: string; - thpllist: string; - weight: string; -} - diff --git a/src/stores/catalogQueries.ts b/src/stores/catalogQueries.ts deleted file mode 100644 index 34be86f..0000000 --- a/src/stores/catalogQueries.ts +++ /dev/null @@ -1,99 +0,0 @@ -export const QUERY_GET_CATALOGS = ` -PREFIX dcterms: -PREFIX dcat: - -SELECT ?c ?t -WHERE { - GRAPH ?g { - ?c a dcat:Catalog ; - dcterms:title ?t . - } -} -ORDER BY ?t` - -export function QUERY_GET_THEMES(catalogs:string[]=[]) { - return ` -PREFIX skos: -PREFIX dcterms: -PREFIX dcat: -SELECT ?th ?pl (COUNT(?th) AS ?count) -WHERE { - ?r a dcat:Resource ; - ^dcterms:hasPart ?hasPart . - ${catalogs.length > 0 ? `FILTER (?hasPart IN (${catalogs.map(cat=>`<${cat}>`).join(', ')}))` : ``} - ?r dcat:theme ?th . - ?th skos:prefLabel ?pl . -} -GROUP BY ?th ?pl -ORDER BY DESC(?count) ?pl -LIMIT 10` -} - -export function QUERY_SEARCH(catalogs:string[]=[], searchTerms:string='', themes:string[]=[], shape:string='', limit:number=0) { - return ` - PREFIX skos: - PREFIX dcat: - PREFIX dcterms: - PREFIX geo: - PREFIX geof: - - SELECT ?r ?t ?d (GROUP_CONCAT(?th;separator="\t") AS ?thlist) (GROUP_CONCAT(?thpl;separator="\t") AS ?thpllist) ?weight - WHERE { - # Only look for Resources (not spatial Datasets) - ?r a dcat:Resource . - - # Only look in selected cats - ?r ^dcterms:hasPart ?hasPart . - ${catalogs.length > 0 ? `FILTER (?hasPart IN (${catalogs.map(cat=>`<${cat}>`).join(', ')}))` : ``} - - # Weighted text search - { - SELECT DISTINCT ?r ?t ?d (SUM(?w) AS ?weight) - WHERE { - ?r a dcat:Resource . - { - ?r - dcterms:title ?t ; - dcterms:description ?d ; - . - BIND (50 AS ?w) - FILTER REGEX(?t, "^${searchTerms}$", "i") - } - UNION - { - ?r - dcterms:title ?t ; - dcterms:description ?d ; - . - BIND (10 AS ?w) - FILTER REGEX(?t, "${searchTerms}", "i") - FILTER(?d!="") - } - UNION - { - ?r - dcterms:title ?t ; - dcterms:description ?d ; - . - BIND (5 AS ?w) - FILTER REGEX(?d, "${searchTerms}", "i") - } - } - GROUP BY ?r ?t ?d ?match - ORDER BY DESC(?weight) ?t - } - - # Theme filter. each theme's IRI is a new line in the VALUES {} - ${themes.length > 0 ? `VALUES ?th { ${themes.map(theme=>`<${theme}>`).join(' ')} }` : ``} - ?r dcat:theme ?th . - ?th skos:prefLabel ?thpl . - - # Spatial Filter - ${shape != '' ? ` - ?r geo:hasBoundingBox/geo:asWKT ?wkt ; - FILTER (geof:sfOverlaps("${shape})"^^geo:wktLiteral, ?wkt))` : ``} - - } GROUP BY ?r ?t ?d ?weight - ${limit > 0 ? `LIMIT ${limit}` : ``}` - -} diff --git a/src/stores/datasetsStore.d.ts b/src/stores/datasetsStore.d.ts deleted file mode 100644 index 26642cb..0000000 --- a/src/stores/datasetsStore.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Term } from "n3" - -/** - * @typedef {Object} MatchFilter - A match object type to provide to the N3 match function to filter results - * @property {Term | null} subject - The subject term to match - * @property {Term | null} object - The object term to match - * @property {Term | null} predicate - The predicate term to match - */ -export type MatchFilter = { - subject?: Term | null, - object?: Term | null, - predicate?: Term | null -} - -/** - * @typedef {Object} SimpleQueryResult - This simple query result object is to enable a view to easily work with a result set to display, without needing to worry about getting an id/value or processing of the literal text properly - * @property {string} subject - The subject of the triple/quad - * @property {string} predicate - The predicate of the triple/quad - * @property {string} object - The object of the triple/quad, with the literal value processed if applicable - */ -export type SimpleQueryResult = { - subject: string, - predicate: string, - object: string -} - -/** - * @typedef {Object} DatasetTreeNode - The node containing the item and featureCollection list - * @property {SimpleQueryResult} item - The item being represented in the tree node - * @property {SimpleQueryResult[]} featureCollections - The child featureCollections for the given item - */ -export type DatasetTreeNode = { - item: SimpleQueryResult, - featureCollections: SimpleQueryResult[] -} - -/** - * @typedef {DatasetTreeNode[]} DatasetTree - This tree object provides a way to store the main list of datasets and child featureCollections - */ -export type DatasetTree = DatasetTreeNode[] - diff --git a/src/stores/datasetsStore.ts b/src/stores/datasetsStore.ts deleted file mode 100644 index d199fdf..0000000 --- a/src/stores/datasetsStore.ts +++ /dev/null @@ -1,202 +0,0 @@ -/** - * This is a pinia store module that makes a sparql API query to get datasets and feature collection information. - * It uses the N3.js library for parsing and querying RDF data, and axios for HTTP requests. - * This store provides a method to get a full tree representation of the datasets and their child feature collections, - * and a method to get a list of datasets with their titles. - */ -import { defineStore } from 'pinia' -import axios from 'axios' -import { Util, Store, Parser, DataFactory, Quad } from "n3"; -const { namedNode } = DataFactory; -import type { DatasetTree, MatchFilter, SimpleQueryResult } from '@/stores/datasetsStore.d' -import { mapConfigKey, type MapConfig, type MapSearchConfig } from "@/types"; -import { apiBaseUrlConfigKey } from "@/types"; -import { inject } from 'vue'; -import { convertConfigTypes } from '@/util/mapSearchHelper'; - -/** - * SPARQL query to return all datasets and feature collections required - */ -const getDatasetFeatureQuery = (config: MapSearchConfig) => { - return `PREFIX geo: -PREFIX rdf: -PREFIX prez: - -CONSTRUCT {?ds a <${config.spatial.datasetClass}> ; - <${config.props.dsLabel}> ?ds_title ; - <${config.spatial.membershipRelationship}> ?fc . - ?fc a geo:FeatureCollection ; - <${config.props.fcLabel}> ?fc_title} -WHERE { ?ds a <${config.spatial.datasetClass}> ; - <${config.spatial.membershipRelationship}> ?fc ; - <${config.props.dsLabel}> ?ds_title . - ?fc <${config.props.fcLabel}> ?fc_title - }` -} - -/** - * Converts a quad/triple object from N3 into a simple result type for easier template processing - * @param {Object} quad - The quad/triple object from N3 to convert - * @returns {SimpleQueryResult} - The simple query result object - */ -export const quadToSimpleQueryResult = (quad:any):SimpleQueryResult => { - // Note: Quad type import issues, conflict with RDF library, so has an any type for now... - return { - subject: quad.subject.id, - predicate: quad.predicate.id, - object: Util.isLiteral(quad.object) ? quad.object.value : quad.object.id - } -} - -/** - * Main search store for processing the results of a search SPARQL query - */ -export const datasetsStore = defineStore({ - id: 'datasetsStore', - - /** - * The initial store state - * @returns {Object} - */ - state: () => { - // get the default map settings - const mapConfig = convertConfigTypes(inject(mapConfigKey)) as MapConfig; - const apiBaseUrl = inject(apiBaseUrlConfigKey) as string; - //const config = inject(configKey, defaultConfig) - return { - data: [], - success: false, - store: new Store(), - loading: false, - error: null, - apiBaseUrl, - mapConfig - } - }, - - /** - * Callable actions - */ - actions: { - - /** - * Get a list of triple/quads matching the provided filter, simplified for easier template processing - * @param {MatchFilter} filter - The filter to match the triples/quads - * @returns {SimpleQueryResult[]} - The list of matching triples/quads - */ - getMatchedData(filter:MatchFilter) { - const data:SimpleQueryResult[] = [] - - const { subject=null, predicate=null, object=null } = filter - for (const item of this.store.match(subject, predicate, object)) { - data.push(quadToSimpleQueryResult(item))// {subject: item.subject.id, predicate: item.predicate.id, object: Util.isLiteral(item.object) ? item.object.value : item.object.id}) - } - return data - }, - - /** - * Get a full tree representation of the datasets and child feature collections - * @returns {DatasetTree} - The tree representation of the datasets and child feature collections - */ - getDatasetTree() { - const datasetTree:DatasetTree = [ - ] - this.getDatasets().forEach(dataset=>{ - datasetTree.push({ - item: dataset, - featureCollections: this.getFeatureCollections([dataset.subject]) - }) - }) - return datasetTree; - - }, - - - /** - * Get a list of dataset, performs a second match on the dataset subject to get the title - * @returns SimpleQueryResult array - */ - getDatasets() { - let results: SimpleQueryResult[] = [] - const matched = this.getMatchedData({object: namedNode(this.mapConfig.search.spatial.datasetClass)}) - matched.forEach(itemLink=>{ - for(const item of this.store.match(namedNode(itemLink.subject), namedNode(this.mapConfig.search.props.dsLabel))) { - results.push(quadToSimpleQueryResult(item)) - } - }) - return results - }, - - - /** - * Returns a list of SimpleQueryResult objects representing all feature collections for a given list of dataset subjects. - * If no datasetSubjects are provided, all matching feature collections will be returned. - * @param datasetSubjects - List of dataset subjects to filter feature collections by. - * @returns List of SimpleQueryResult objects representing the feature collections. - */ - getFeatureCollections(datasetSubjects:string[]):SimpleQueryResult[] { - if(datasetSubjects.length == 0) { - return [] // changed to return empty - // simply returns all matching feature collections - // return this.getMatchedData({predicate: namedNode('http://purl.org/dc/terms/title')}) - } else { - // narrow down to a specific dataset - let results:SimpleQueryResult[] = [] - datasetSubjects.forEach(subject=>{ - const matchedFCs = this.getMatchedData({predicate: namedNode(this.mapConfig.search.spatial.membershipRelationship), subject: namedNode(subject)}) - matchedFCs.forEach(itemLink=>{ - for(const item of this.store.match(namedNode(itemLink.object), namedNode(this.mapConfig.search.props.fcLabel))) { - results.push(quadToSimpleQueryResult(item)) - } - }) - }) - return results - } - }, - - /** - * Calls the spacePrez search endpoint using the query for spacePrez - */ - async fetchSpacePrezData() { - return await this.fetchData(`${this.apiBaseUrl}/sparql`, getDatasetFeatureQuery(this.mapConfig.search)); - }, - - /** - * Makes a GET request to the specified API endpoint with the provided SPARQL query. - * Parses the response with the N3 parser and creates a new N3 store from the parsed data. - * @param apiUrl - The URL of the SPARQL endpoint. - * @param query - The SPARQL query to execute. - */ - async fetchData(apiUrl:string, query:string) { - try { - // initialise the state - const parser = new Parser(); - this.loading = true - this.success = false - - // make the API call to the SPARQL endpoint - const response = await axios.get(apiUrl, { params: { query }}) - - // process the response through the N3 parser - this.data = parser.parse(response.data) - - // create a new N3 store from the parsed data - this.store = new Store(this.data) - - // successfully processed - this.success = true - this.error = null - - } catch (error:any) { - - // set the error status - this.error = error.message - this.success = false - - } finally { - // always set loading to complete - this.loading = false - } - } - } -}) diff --git a/src/stores/mapSearchStore.d.ts b/src/stores/mapSearchStore.d.ts deleted file mode 100644 index 6cc68dc..0000000 --- a/src/stores/mapSearchStore.d.ts +++ /dev/null @@ -1,11 +0,0 @@ - - -/** well known text simplified result object, as returned by mapSearch */ -export type WKTResult = { - uri: string - link: string - wkt: string - fcLabel: string - label: string - id?: string -} diff --git a/src/stores/mapSearchStore.ts b/src/stores/mapSearchStore.ts deleted file mode 100644 index a5a163c..0000000 --- a/src/stores/mapSearchStore.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * This is a pinia store module that provides search functionality for a triple store. It uses the N3.js library for parsing and querying RDF data, and axios for HTTP requests. - * The searchStore provides a method to get a full tree representation of the datasets and their child feature collections, and a method to get a list of datasets with their titles. - */ -import { defineStore } from 'pinia' -import axios from 'axios' -import { inject } from 'vue'; -import type { WKTResult } from '@/stores/mapSearchStore.d' - -import { apiBaseUrlConfigKey } from "@/types"; - -const linkPrefix = '/object?uri=' - -/** - * Main search store for processing the results of a search SPARQL query - */ -export const mapSearchStore = defineStore({ - id: 'mapSearchStore', - - /** - * The initial store state - * @returns {Object} - */ - state: () => { - const apiBaseUrl = inject(apiBaseUrlConfigKey) as string; - - // const config = inject(configKey, defaultConfig) - return { - data: [], - success: false, - loading: false, - error: null as null | string, - apiBaseUrl: apiBaseUrl - } - }, - - /** - * Callable actions - */ - actions: { - - /** - * Calls the spacePrez search endpoint using a passed in generated query, sets the result WKT in the data object - */ - async searchMap(query:string) { - // make the API call to the SPARQL endpoint - const url = `${this.apiBaseUrl}/sparql` - this.loading = true - this.success = false - - try { - const response = await axios.get(url, { headers: {"accept": "application/sparql-results+json"}, params: { query }}) - this.data = response.data.results.bindings.filter((item:any)=>item.fc_label?.value).map((item:any)=>{ - return { - uri: item.f_uri.value, - link: `${linkPrefix}${item.f_uri.value}`, - wkt: item.wkt.value, - fcLabel: item.fc_label?.value, - label: item.f_label.value - } - }) - // successfully processed - this.success = true - this.error = null - - } catch (error:any) { - // set the error status - this.error = error.message as string - this.data = [] - this.success = false - - } finally { - // always set loading to complete - this.loading = false - } - - } - - } -}) diff --git a/src/stores/refDataStore.ts b/src/stores/refDataStore.ts deleted file mode 100644 index f37b862..0000000 --- a/src/stores/refDataStore.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * This is a pinia store module that provides search functionality for a triple store. It uses the N3.js library for parsing and querying RDF data, and axios for HTTP requests. - * The searchStore provides a method to get a full tree representation of the datasets and their child feature collections, and a method to get a list of datasets with their titles. - */ -import { defineStore } from 'pinia' -import axios from 'axios' -import { inject } from 'vue'; -import { apiBaseUrlConfigKey } from "@/types"; - - -/** - * Factory store function - */ -export function refDataStore(id:string) { - - return defineStore( - { - id, - - /** - * The initial store state - * @returns {Object} - */ - state: () => { - const apiBaseUrl = inject(apiBaseUrlConfigKey) as string; - - // const config = inject(configKey, defaultConfig) - return { - data: [] as Array, - success: false, - loading: false, - error: null, - apiBaseUrl: apiBaseUrl - } - }, - - /** - * Callable actions - */ - actions: { - - /** - * Calls the SPARQL endpoint with the predefined query, returns a generalised object array from the json response - */ - async fetch(query:string):Promise { - // make the API call to the SPARQL endpoint - const url = `${this.apiBaseUrl}/sparql` - this.loading = true - this.success = false - - try { - const response = await axios.get(url, { headers: {"accept": "application/sparql-results+json"}, params: { query }}) - this.data = response.data.results.bindings.map((item:any)=>{ - let result:any = {} - Object.keys(item).forEach(key=>{ - result[key] = item[key].value - }) - return result - }) - // successfully processed - this.success = true - this.error = null - - } catch (error:any) { - // set the error status - this.error = error.message - this.data = [] - this.success = false - - } finally { - // always set loading to complete - this.loading = false - } - - } - - } - }) -} - diff --git a/src/views/PropTableView.vue b/src/views/PropTableView.vue index 96051b8..9aa43fd 100644 --- a/src/views/PropTableView.vue +++ b/src/views/PropTableView.vue @@ -6,6 +6,7 @@ import { useUiStore } from "@/stores/ui"; import { useRdfStore } from "@/composables/rdfStore"; import { useApiRequest } from "@/composables/api"; import { apiBaseUrlConfigKey, conceptPerPageConfigKey, type ListItem, type AnnotatedQuad, type Breadcrumb, type Concept, type PrezFlavour, type Profile, type ListItemExtra, type ListItemSortable } from "@/types"; +import type { WKTResult } from "@/components/MapClient.d"; import PropTable from "@/components/proptable/PropTable.vue"; import ConceptComponent from "@/components/ConceptComponent.vue"; import AdvancedSearch from "@/components/search/AdvancedSearch.vue"; @@ -13,7 +14,6 @@ import ProfilesTable from "@/components/ProfilesTable.vue"; import ErrorMessage from "@/components/ErrorMessage.vue"; import { getPrezSystemLabel } from "@/util/prezSystemLabelMapping"; import MapClient from "@/components/MapClient.vue"; -import type { WKTResult } from "@/stores/mapSearchStore.d"; import SortableTabularList from "@/components/SortableTabularList.vue"; import LoadingMessage from "@/components/LoadingMessage.vue"; import { ensureProfiles } from "@/util/helpers"; diff --git a/src/views/SearchView.vue b/src/views/SearchView.vue index 520c512..456c50d 100644 --- a/src/views/SearchView.vue +++ b/src/views/SearchView.vue @@ -5,7 +5,7 @@ import { DataFactory } from "n3"; import { useUiStore } from "@/stores/ui"; import { useApiRequest } from "@/composables/api"; import { useRdfStore } from "@/composables/rdfStore"; -import type { WKTResult } from "@/stores/mapSearchStore.d"; +import type { WKTResult } from "@/components/MapClient.d"; import AdvancedSearch from "@/components/search/AdvancedSearch.vue"; import LoadingMessage from "@/components/LoadingMessage.vue"; import ErrorMessage from "@/components/ErrorMessage.vue";