index.js

import {
  SparqlEndpointResponseProvider,
  SparqlQueryWindow,
} from '@ud-viz/widget_sparql';
import { getUriLocalname } from '@ud-viz/utils_browser';

/**
 * The SPARQL query window class which provides the user interface for querying
 * a SPARQL endpoint and displaying the endpoint response.
 */
export class SparqlWorkspaceQueryWindow extends SparqlQueryWindow {
  /**
   * Creates a SPARQL query window.
   *
   * @param {SparqlEndpointResponseProvider} sparqlProvider The SPARQL Endpoint Response Provider.
   * @param {object} configSparqlWidget The sparqlModule view configuration.
   * @param {object} configSparqlWidget.queries Query configurations
   * @param {object} configSparqlWidget.queries.title The query title
   * @param {object} configSparqlWidget.queries.filepath The path to the file which contains the query text
   * @param {object} configSparqlWidget.queries.formats Configuration for which visualizations are allowed
   *                                                    with this query. Should be an object of key, value
   *                                                    pairs. The keys of these pairs should correspond
   *                                                    with the cases in the updateDataView() function.
   */
  constructor(sparqlProvider, configSparqlWidget) {
    super(sparqlProvider, configSparqlWidget);
  }

  /**
   *
   * @param {string} id an identifier
   * @returns {Array<Array<string>>} - transaction chain
   */
  getTransactionChain(id) {
    const result = this.sparqlProvider.querySparqlEndpointService(
      this.transactionChainQuery(id)
    );
    return result;
  }

  /**
   * Get the CityGML 3.0 transaction chain query for as a string
   *
   * @param {string} gml_id a gml_id identifier linked to a feature
   * @returns {string} - query plain text
   */
  transactionChainQuery(gml_id) {
    return /* SPARQL */ `
# Workspace prefixes
PREFIX vers: <https://dataset-dl.liris.cnrs.fr/rdf-owl-urban-data-ontologies/Ontologies/CityGML/3.0/versioning#>

# return a the transactions connected to a city objects URI through a distance of 4

#SELECT DISTINCT ?transition ?transaction ?sourceGmlId ?sourceVersion ?targetGmlId ?targetVersion
SELECT ?gmlID ?gmlID_future_1 ?gmlID_future_2 ?gmlID_future_3 ?gmlID_future_4
WHERE {
  ?gmlID ^vers:Transaction.oldFeature|^vers:Transaction.newFeature ?transaction .
  # search forward in time
  OPTIONAL {
    ?transaction vers:Transaction.newFeature ?gmlID_future_1 .
  } OPTIONAL {
    ?transaction vers:Transaction.newFeature/^vers:Transaction.oldFeature/vers:Transaction.newFeature ?gmlID_future_2 .
  } OPTIONAL {
    ?transaction vers:Transaction.newFeature/^vers:Transaction.oldFeature/vers:Transaction.newFeature/^vers:Transaction.oldFeature/vers:Transaction.newFeature ?gmlID_future_3 .
  } OPTIONAL {
    ?transaction vers:Transaction.newFeature/^vers:Transaction.oldFeature/vers:Transaction.newFeature/^vers:Transaction.oldFeature/vers:Transaction.newFeature/^vers:Transaction.oldFeature/vers:Transaction.newFeature ?gmlID_future_4 .
  }

  FILTER REGEX( str(?gmlID), ".*${gml_id}" ) 
}
      `;
  }

  /**
   * Given the index of a node (of type Version), return the node of the first Scenario links to it using Scenario.versionMember.
   *
   * @param {number} d the index of the Version node
   * @returns {object|null} return the object that represents the datum of a Scenario
   */
  getVersionScenarioByIndex(d) {
    const uri = this.d3Graph.data.getNodeByIndex(d).id;
    return this.getVersionScenarioByUri(uri);
  }

  /**
   * Given the index of a node (of type VersionTransition), return the node of the first Scenario links to it using Scenario.versionTransitionMember.
   *
   * @param {number} d the index of the Version node
   * @returns {object|null} return the object that represents the datum of a Scenario
   */
  getVersionTransitionScenarioByIndex(d) {
    const uri = this.d3Graph.data.getNodeByIndex(d).id;
    return this.getVersionTransitionScenarioByUri(uri);
  }

  /**
   * Given the uri of a node, return the node of the first Scenario links to it using Scenario.versionMember.
   * The uri should belong to a node of type Version.
   *
   * @param {string} uri the URI of the Version node
   * @returns {object|null} return the object that represents the datum of a Scenario
   */
  getVersionScenarioByUri(uri) {
    const memberLink = this.d3Graph.data.links.find((element) => {
      return (
        getUriLocalname(element.label) == 'Scenario.versionMember' &&
        element.target == uri
      );
    });
    if (memberLink) {
      return this.d3Graph.data.getNodeByUri(memberLink.source);
    }
    console.warn(`No Scenario found for version with uri: ${uri}`);
    return null;
  }

  /**
   * Given the uri of a node, return the node of the first Scenario links to it using Scenario.versionTransitionMember.
   * The uri should belong to a node of type VersionTransition.
   *
   * @param {string} uri the URI of the Version node
   * @returns {object|null} return the object that represents the datum of a Scenario
   */
  getVersionTransitionScenarioByUri(uri) {
    const memberLink = this.d3Graph.data.links.find((element) => {
      return (
        getUriLocalname(element.label) == 'Scenario.versionTransitionMember' &&
        element.target == uri
      );
    });
    if (memberLink) {
      return this.d3Graph.data.getNodeByUri(memberLink.source);
    }
    console.warn(`No Scenario found for versionTransition with uri: ${uri}`);
    return null;
  }

  /**
   * Given the index of a node (of type Version or VersionTransition) and a layerManager,
   * find the first Scenario that links to it and return the first layer that matches
   * the layer.name of the Scenario's localname.
   *
   * @param {number} d the index of the Version or VersionTransition node
   * @returns {string|null} return the name of a matching geometryLayer
   */
  getScenarioLayerNameByIndex(d) {
    const node = this.d3Graph.data.getNodeByIndex(d);
    switch (
      getUriLocalname(node.type) // behavior changes based on the node type
    ) {
      case 'Version': {
        const versionScenarioByIndex = this.getVersionScenarioByIndex(d);

        if (versionScenarioByIndex) {
          return getUriLocalname(versionScenarioByIndex.id);
        }

        break;
      }
      case 'VersionTransition': {
        const versionTransitionScenarioByIndex =
          this.getVersionTransitionScenarioByIndex(d);

        if (versionTransitionScenarioByIndex) {
          return getUriLocalname(versionTransitionScenarioByIndex.id);
        }
        break;
      }
      default:
        console.warn(
          `Cannot find scenario associated with node, ${getUriLocalname(
            node.id
          )}; Unknown node type: ${getUriLocalname(node.type)}`
        );
        return null;
    }
    return null;
  }

  /**
   * Given the uri of a node (of type Version or VersionTransition) and a layerManager,
   * find the first Scenario that links to it and return the first layer that matches
   * the layer.name of the Scenario's localname.
   *
   * @param {string} uri the uri of the Version or VersionTransition node
   * @returns {string|null} return the a matching geometryLayer name
   */
  getScenarioLayerNameByUri(uri) {
    const node = this.d3Graph.data.getNodeByUri(uri);
    switch (
      getUriLocalname(node.type) // behavior changes based on the node type
    ) {
      case 'Version': {
        return getUriLocalname(this.getVersionScenarioByUri(uri).id);
      }
      case 'VersionTransition': {
        return getUriLocalname(this.getVersionTransitionScenarioByUri(uri).id);
      }
      default:
        console.warn(
          `Cannot find scenario associated with node, ${getUriLocalname(
            node.id
          )}; Unknown node type: ${getUriLocalname(node.type)}`
        );
        return null;
    }
  }

  /**
   * Given the index of a node, find the bitemporal timestamps that
   * that link to it (with the properties AbstractFeatureWithLifespan.validFrom and
   * AbstractFeatureWithLifespan.validTo) and return them as an object of Dates
   *
   * @param {number} d the index of the node
   * @returns {{validFrom:Date,validTo:Date}|null} return the object that represents the datum of a node
   */
  getBitemporalTimestampsByIndex(d) {
    const links = this.d3Graph.data.getLinksByIndex(d);
    const validFrom = links.find((link) => {
      return (
        getUriLocalname(link.label) == 'AbstractFeatureWithLifespan.validFrom'
      );
    });
    const validTo = links.find((link) => {
      return (
        getUriLocalname(link.label) == 'AbstractFeatureWithLifespan.validTo'
      );
    });

    if (!validFrom || !validTo) {
      console.warn(
        `could not find bitemporal timestamps for ${
          this.d3Graph.data.getNodeByIndex(d).id
        }`
      );
      return null;
    }
    const timestamp1 = new Date(String(validFrom.target)).getFullYear();
    const timestamp2 = new Date(String(validTo.target)).getFullYear();

    console.debug(
      `bitemporal timestamps found: ${validFrom.target} and ${validTo.target}`
    );
    return {
      validFrom: timestamp1,
      validTo: timestamp2,
    };
  }
}