From cefd4e576f7030abdc07c16687f82fbf7d7519d3 Mon Sep 17 00:00:00 2001 From: Victor Felder Date: Wed, 18 Sep 2019 14:34:16 +0200 Subject: [PATCH] feat(component): get component values and min/max values --- examples/component-values-min-max.ts | 32 +++++++ examples/component-values.ts | 110 +++++++++++++++++++++ src/datacube.ts | 138 +++++++++++++++++++++++++-- 3 files changed, 272 insertions(+), 8 deletions(-) create mode 100644 examples/component-values-min-max.ts create mode 100644 examples/component-values.ts diff --git a/examples/component-values-min-max.ts b/examples/component-values-min-max.ts new file mode 100644 index 0000000..0e2d77e --- /dev/null +++ b/examples/component-values-min-max.ts @@ -0,0 +1,32 @@ +// run this example with: $ ts-node examples/component-values.ts +import { inspect } from "util"; +import { DataCubeEntryPoint } from "../src/entrypoint"; + +function prettyPrint(obj) { + return inspect(obj, false, 10000, true); +} + +(async () => { + const entryPoint = new DataCubeEntryPoint("https://ld.stadt-zuerich.ch/query"); + const dataCubes = await entryPoint.dataCubes(); + const datacube = dataCubes.find((cube) => cube.iri.endsWith("BEW-RAUM-ZEIT")); + + const dimensions = await datacube.dimensions(); + const time = dimensions.find((dimension) => dimension.iri.value.endsWith("/ZEIT")); + const timeMinMax = await datacube.componentMinMax(time); + /* + { + min: Literal { + value: '1408-12-31', + datatype: NamedNode { value: 'http://www.w3.org/2001/XMLSchema#date' }, + language: '' + }, + max: Literal { + value: '2017-12-31', + datatype: NamedNode { value: 'http://www.w3.org/2001/XMLSchema#date' }, + language: '' + } + } + */ + console.log(prettyPrint(timeMinMax)); +})(); diff --git a/examples/component-values.ts b/examples/component-values.ts new file mode 100644 index 0000000..34c8cf5 --- /dev/null +++ b/examples/component-values.ts @@ -0,0 +1,110 @@ +// run this example with: $ ts-node examples/component-values.ts +import { inspect } from "util"; +import { DataCubeEntryPoint } from "../src/entrypoint"; + +function prettyPrint(obj) { + return inspect(obj, false, 10000, true); +} +function printTitle(str) { + return `\n\n---- ${str} ----\n`; +} + +(async () => { + // instantiate an RDF Data Cube + const entryPoint = new DataCubeEntryPoint( + "https://trifid-lindas.test.cluster.ldbar.ch/query", + { languages: ["de"] }, + ); + const dataCubes = await entryPoint.dataCubes(); + const datacube = dataCubes[0]; + + const dimensions = await datacube.dimensions(); + + const sizeClasses = dimensions[1]; + console.log(prettyPrint(sizeClasses)); + + const values = await datacube.componentValues(sizeClasses); + console.log(prettyPrint(values)); + /* + [ + { + label: Literal { + value: 'Grössenklasse - Total', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/0' } + }, + { + label: Literal { + value: '< 50 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/1' } + }, + { + label: Literal { + value: '50 - 100 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/2' } + }, + { + label: Literal { + value: '101 - 200 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/3' } + }, + { + label: Literal { + value: '201 - 500 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/4' } + }, + { + label: Literal { + value: '501 - 1000 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/5' } + }, + { + label: Literal { + value: '1001 - 5000 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/6' } + }, + { + label: Literal { + value: '> 5000 ha', + datatype: NamedNode { value: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#langString' }, + language: 'de' + }, + value: NamedNode { value: 'http://example.org/anzahl-forstbetriebe/property/1/7' } + } + ] + */ + console.log(prettyPrint(values + .map(((row) => `"${row.label.value}"${row.label.language ? `@${row.label.language}` : ""}`)))); + /* + [ + '"Grössenklasse - Total"@de', + '"< 50 ha"@de', + '"50 - 100 ha"@de', + '"101 - 200 ha"@de', + '"201 - 500 ha"@de', + '"501 - 1000 ha"@de', + '"1001 - 5000 ha"@de', + '"> 5000 ha"@de' + ] + */ +})(); diff --git a/src/datacube.ts b/src/datacube.ts index 4561384..36e2613 100644 --- a/src/datacube.ts +++ b/src/datacube.ts @@ -1,5 +1,6 @@ import { namedNode, variable } from "@rdfjs/data-model"; -import { NamedNode, Term } from "rdf-js"; +import namespace from "@rdfjs/namespace"; +import { Literal, NamedNode, Term } from "rdf-js"; import { Generator as SparqlGenerator } from "sparqljs"; import { Attribute, Component, Dimension, Measure } from "./components"; import { EntryPointOptions } from "./entrypoint"; @@ -56,6 +57,8 @@ export interface DataCubeOptions extends EntryPointOptions { graphIri: NamedNode; } +const cube = namespace("http://purl.org/linked-data/cube#"); + /** * @class DataCube */ @@ -178,6 +181,125 @@ export class DataCube { return new Query(this, opts); } + public async componentValues(component: Component): Promise> { + if (!component || !component.componentType) { + throw new Error(`datacube#componentValues expects valid component, got ${component} instead`); + } + const binding = variable("value"); + const labelBinding = variable("label"); + const observation = variable("observation"); + + const query: SelectQuery = { + prefixes, + queryType: "SELECT", + variables: [binding, labelBinding], + distinct: true, + from: { default: [namedNode(this.graphIri)], named: [] }, + where: [ + { + type: "bgp", + triples: [ + { + subject: observation, + predicate: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + object: cube("Observation"), + }, + { + subject: observation, + predicate: component.iri, + object: binding, + }, + { + subject: observation, + predicate: cube("dataSet"), + object: namedNode(this.iri), + }, + ], + }, + ...generateLangOptionals(binding, labelBinding, this.languages), + generateLangCoalesce(labelBinding, this.languages), + ], + type: "query", + }; + + const generator = new SparqlGenerator({ allPrefixes: true }); + const sparql = generator.stringify(query); + return await this.fetcher.select(sparql); + } + + public async componentMinMax(component: Component): Promise<{min: Literal, max: Literal}|null> { + if (!component || !component.componentType) { + throw new Error(`datacube#componentMinMax expects valid component, got ${component} instead`); + } + const binding = variable("value"); + const observation = variable("observation"); + + const query: SelectQuery = { + prefixes, + queryType: "SELECT", + variables: [ + { + expression: { + expression: binding, + type: "aggregate", + aggregation: "min", + distinct: false, + }, + variable: variable("min"), + }, + { + expression: { + expression: binding, + type: "aggregate", + aggregation: "max", + distinct: false, + }, + variable: variable("max"), + }, + ], + from: { default: [namedNode(this.graphIri)], named: [] }, + where: [ + { + type: "bgp", + triples: [ + { + subject: observation, + predicate: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + object: cube("Observation"), + }, + { + subject: observation, + predicate: component.iri, + object: binding, + }, + { + subject: observation, + predicate: cube("dataSet"), + object: namedNode(this.iri), + }, + ], + }, + { + type: "filter", + expression: { + type: "operation", + operator: "isliteral", + args: [binding], + }, + }, + ], + type: "query", + }; + + const generator = new SparqlGenerator({ allPrefixes: true }); + const sparql = generator.stringify(query); + const results = await this.fetcher.select(sparql); + if (results.length) { + return results[0]; + } + return null; + } + private async components() { if (this.componentsLoaded) { return; @@ -194,7 +316,7 @@ export class DataCube { variable("kind"), labelBinding, ], - from: { default: [ namedNode(this.graphIri) ], named: [] }, + from: { default: [namedNode(this.graphIri)], named: [] }, where: [ { type: "bgp", @@ -202,7 +324,7 @@ export class DataCube { { subject: namedNode(this.iri), predicate: namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), - object: namedNode("http://purl.org/linked-data/cube#DataSet"), + object: cube("DataSet"), }, { subject: namedNode(this.iri), @@ -210,8 +332,8 @@ export class DataCube { type: "path", pathType: "/", items: [ - namedNode("http://purl.org/linked-data/cube#structure"), - namedNode("http://purl.org/linked-data/cube#component"), + cube("structure"), + cube("component"), ], }, object: variable("componentSpec"), @@ -231,9 +353,9 @@ export class DataCube { args: [ variable("kind"), [ - namedNode("http://purl.org/linked-data/cube#attribute"), - namedNode("http://purl.org/linked-data/cube#dimension"), - namedNode("http://purl.org/linked-data/cube#measure"), + cube("attribute"), + cube("dimension"), + cube("measure"), ], ], },