Browse Source

Issue #2356593: Added the ability to link to GBrowse/JBrowse instances. Furthermore, added hook_blast_linkout_info() to allow other developers to create their own complex link types

Lacey Sanderson 10 years ago
parent
commit
f394fc20bd
5 changed files with 268 additions and 15 deletions
  1. 26 0
      blast_ui.install
  2. 4 1
      blast_ui.module
  3. 162 0
      includes/blast_ui.linkouts.inc
  4. 32 1
      includes/blast_ui.node.inc
  5. 44 13
      theme/blast_report.tpl.php

+ 26 - 0
blast_ui.install

@@ -61,6 +61,13 @@ function blast_ui_schema(){
         'description' => t('The Database records from this BLAST Database reference.'),
         'type' => 'int',
       ),
+      'dbxref_linkout_type' => array(
+        'description' => t('Type of linkout to be used for this database reference.'),
+        'type' => 'varchar',
+        'length' => 50,
+        'not null' => true,
+        'default' => 'link'
+      ),
     ),
     'indexes' => array(
       'name' => array('name'),
@@ -109,3 +116,22 @@ function blast_ui_update_7101() {
   );
 
 }
+
+/**
+ * Support complex types of link-outs such as GBrowse & JBrowse coordinate links.
+ */
+function blast_ui_update_7102() {
+
+  db_add_field(
+    'blastdb',
+    'dbxref_linkout_type',
+    array(
+      'description' => t('Type of linkout to be used for this database reference.'),
+      'type' => 'varchar',
+      'length' => 50,
+      'not null' => true,
+      'default' => 'link'
+    )
+  );
+
+}

+ 4 - 1
blast_ui.module

@@ -14,6 +14,9 @@ require_once 'includes/blast_ui.form_advanced_options.inc';
 // BLAST DB Node functionality
 require_once 'includes/blast_ui.node.inc';
 
+// BLAST Link-out functionality.
+require_once 'includes/blast_ui.linkouts.inc';
+
 // Functions specific to themeing (ie: preprocess)
 require_once 'theme/blast_ui.theme.inc';
 
@@ -114,7 +117,7 @@ function blast_ui_menu() {
     'type' => MENU_CALLBACK,
   );
 
-  // BLAST Admin  
+  // BLAST Admin
   $items['admin/tripal/extension/tripal_blast'] = array(
     'title' => 'Tripal BLAST User Interface',
     'description' => 'Provides an interface allowing users to execute their own BLASTs.',

+ 162 - 0
includes/blast_ui.linkouts.inc

@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Provides Link-out functionality for BLAST hits.
+ *
+ * Specifically,
+ */
+
+/**
+ * Implements hook_blast_linkout_info().
+ *
+ * Provide information on basic link-out types: link, GBrowse, JBrowse.
+ */
+function blast_ui_blast_linkout_info() {
+  $types = array();
+
+  $types['link'] = array(
+    // Human-readable Type name to display to users in the BLAST Database
+    // create/edit form.
+    'name' => 'Generic Link',
+    // The function used to generate the URL to be linked to.
+    // This function will have full access to the blast hit and database
+    // prefix information and is expected to return a URL.
+    'process function' => 'tripal_blast_generate_linkout_link',
+  );
+
+  $types['gbrowse'] = array(
+    'name' => 'GBrowse',
+    'process function' => 'tripal_blast_generate_linkout_gbrowse',
+  );
+
+  $types['jbrowse'] = array(
+    'name' => 'JBrowse',
+    'process function' => 'tripal_blast_generate_linkout_jbrowse',
+  );
+
+  return $types;
+}
+
+/**
+ * Generate a basic link-out for a given hit.
+ *
+ * Essentially, concatenate the URL prefix with the extracted hit identifier
+ * and return the URL to be displayed by the BLAST report template.
+ *
+ * @param $url_prefix
+ *   The URL prefix for the BLAST Database queried.
+ * @param $hit
+ *   The blast XML hit object. This object has the following keys based on the
+ *   XML: Hit_num, Hit_id, Hit_def, Hit_accession, Hit_len and Hit_hsps.
+ *   Furthermore, a linkout_id key has beek added that contains the part of the
+ *   Hit_def extracted using a regex provided when the blastdb node was created.
+ * @param $info
+ *   Additional information that may be useful in creating a link-out. Includes:
+ *    - query_name: the name of the query sequence.
+ *    - score: the score of the blast hit.
+ *    - e-value: the e-value of the blast hit.
+ * @param $options
+ *   Any additional options needed to determine the type of link-out. None are
+ *   supported by this particular link-out type.
+ *
+ * @return
+ *   The URL string to be linked to.
+ */
+function tripal_blast_generate_linkout_link($url_prefix, $hit, $info, $options = array()) {
+
+  if (isset($hit->{'linkout_id'})) {
+    return $url_prefix . $hit->{'linkout_id'};
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Generate a GBrowse link-out with location information for a given hit.
+ *
+ * NOTE: Assumes the hit is a backbone feature in the GBrowse linked to.
+ *  Otherwise, the basic link can be used.
+ *
+ * @param $url_prefix
+ *   The URL prefix for the BLAST Database queried.
+ * @param $hit
+ *   The blast XML hit object. This object has the following keys based on the
+ *   XML: Hit_num, Hit_id, Hit_def, Hit_accession, Hit_len and Hit_hsps.
+ *   Furthermore, a linkout_id key has beek added that contains the part of the
+ *   Hit_def extracted using a regex provided when the blastdb node was created.
+ * @param $info
+ *   Additional information that may be useful in creating a link-out. Includes:
+ *    - query_name: the name of the query sequence.
+ *    - score: the score of the blast hit.
+ *    - e-value: the e-value of the blast hit.
+ * @param $options
+ *   Any additional options needed to determine the type of link-out. None are
+ *   supported by this particular link-out type.
+ *
+ * @return
+ *   The URL string to be linked to.
+ */
+function tripal_blast_generate_linkout_gbrowse($url_prefix, $hit, $info, $options = array()) {
+
+  $ranges = array();
+  $coords = array();
+  foreach($info['HSPs'] as $hsp) {
+     array_push($ranges,$hsp['Hsp_hit-from'] . '..' . $hsp['Hsp_hit-to'] );
+     array_push($coords,$hsp['Hsp_hit-from'] , $hsp['Hsp_hit-to'] );
+   }
+   $min = min($coords);
+   $max = max($coords);
+   $joined_ranges = join ("," , $ranges);
+   $url_postfix = '&start=' . $min . '&stop='  . $max . '&add=' . $hit->{'hit_name'} . '+BLAST+' . $hit->{'hit_name'} . '_' . $info['query_name'] . '_' . $info['e-value'] . '+' . $joined_ranges ;
+
+   return $url_prefix . $url_postfix;
+}
+
+/**
+ * Generate a JBrowse link-out with location information for a given hit.
+ *
+ * NOTE: Assumes the hit is a backbone feature in the JBrowse linked to.
+ *  Otherwise, the basic link can be used.
+ *
+ * @param $url_prefix
+ *   The URL prefix for the BLAST Database queried.
+ * @param $hit
+ *   The blast XML hit object. This object has the following keys based on the
+ *   XML: Hit_num, Hit_id, Hit_def, Hit_accession, Hit_len and Hit_hsps.
+ *   Furthermore, the following keys have been added:
+ *    -linkout_id: the part of the Hit_def extracted using a regex provided
+ *     when the blastdb node was created.
+ *    -hit_name: the name of the hit extracted in the template.
+ * @param $info
+ *   Additional information that may be useful in creating a link-out. Includes:
+ *    - query_name: the name of the query sequence.
+ *    - score: the score of the blast hit.
+ *    - e-value: the e-value of the blast hit.
+ * @param $options
+ *   Any additional options needed to determine the type of link-out. None are
+ *   supported by this particular link-out type.
+ *
+ * @return
+ *   The URL string to be linked to.
+ */
+function tripal_blast_generate_linkout_jbrowse($url_prefix, $hit, $info, $options = array()) {
+
+  $ranges = array();
+  $coords = array();
+  $hsps = array();
+  foreach($info['HSPs'] as $hsp) {
+   $hsp_start = $hsp['Hsp_hit-from'];
+   $hsp_end = $hsp['Hsp_hit-to'] ;
+   array_push($coords,$hsp['Hsp_hit-from'] , $hsp['Hsp_hit-to'] );
+   array_push($ranges, '{"start":'.$hsp_start.',"end":'.$hsp_end.',"type":"match_part"}');
+  }
+  $min = min($coords);
+  $max = max($coords);
+  $url_postfix = '&addFeatures=[{"seq_id":"'.$hit->{'hit_name'}.'","score":"'.$info['e-value'].'","start":'.$min.',"end":'.$max.',"type":"match","name":"MyBLASTHit","subfeatures":[';
+  $joined_ranges = join ("," , $ranges);
+  $url_postfix = $url_postfix . $joined_ranges . ']}]&addTracks=[{"label":"BLAST","type":"JBrowse/View/Track/HTMLFeatures","store":"url"}]&loc='.$hit->{'hit_name'}.'&tracks=DNA%2CBLAST&highlight=';
+
+  return $url_prefix . $url_postfix;
+}

+ 32 - 1
includes/blast_ui.node.inc

@@ -180,6 +180,19 @@ function blastdb_form($node, &$form_state) {
     '#default_value' => (isset($node->linkout->db_id->db_id)) ? $node->linkout->db_id->db_id : 0
   );
 
+  $types = module_invoke_all('blast_linkout_info');
+  $options = array();
+  foreach ($types as $machine_name => $details) {
+    $options[$machine_name] = (isset($details['name'])) ? $details['name'] : $machine_name;
+  }
+  $form['dbxref']['dbxref_linkout_type'] = array(
+    '#type' => 'select',
+    '#title' => 'Link-out Type',
+    '#description' => 'This determines how the URL to be linked to is formed. NOTE: this is very dependant on the External Database chosen above since it needs to be able to support the type of linking choosen. For example, only External Databases which reference a GBrowse instance can use the GBrowse link type.',
+    '#options' => $options,
+    '#default_value' => (isset($node->linkout->type)) ? $node->linkout->type : 'link'
+  );
+
   return $form;
 }
 
@@ -237,6 +250,7 @@ function blastdb_insert($node) {
     'dbtype' => $node->db_dbtype,
     'dbxref_id_regex' => $regex,
     'dbxref_db_id' => $node->db_id,
+    'dbxref_linkout_type' => $node->dbxref_linkout_type
   ))->execute();
 
 }
@@ -271,6 +285,7 @@ function blastdb_update($node) {
     'dbtype' => $node->db_dbtype,
     'dbxref_id_regex' => $regex,
     'dbxref_db_id' => $node->db_id,
+    'dbxref_linkout_type' => $node->dbxref_linkout_type
   ))->condition('nid', $node->nid)->execute();
 }
 
@@ -296,7 +311,7 @@ function blastdb_delete($node) {
  */
 function blastdb_load($nodes) {
 
-  $result = db_query('SELECT nid, name, path, dbtype, dbxref_id_regex, dbxref_db_id FROM {blastdb} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
+  $result = db_query('SELECT nid, name, path, dbtype, dbxref_id_regex, dbxref_db_id, dbxref_linkout_type FROM {blastdb} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
 
   foreach ($result as $record) {
     $nodes[$record->nid]->db_name = $record->name;
@@ -317,6 +332,22 @@ function blastdb_load($nodes) {
       }
       $nodes[$record->nid]->linkout->db_id = tripal_get_db(array('db_id' => $record->dbxref_db_id));
       $nodes[$record->nid]->linkout->none = FALSE;
+
+      // Support complex link-outs.
+      $nodes[$record->nid]->linkout->type = $record->dbxref_linkout_type;
+      $types = module_invoke_all('blast_linkout_info');
+      if (isset($types[$record->dbxref_linkout_type])) {
+        $nodes[$record->nid]->linkout->url_function = $types[$record->dbxref_linkout_type]['process function'];
+      }
+      else {
+        $nodes[$record->nid]->linkout->url_function = '';
+        tripal_report_error(
+          'blast_ui',
+          TRIPAL_ERROR,
+          'Unable to find details on the type of link-out choosen (%type). Have you defined hook_blast_linkout_info()? Make sure to clear the cache once you do so.',
+          array('%type' => $record->dbxref_linkout_type)
+        );
+      }
     }
     else {
       $nodes[$record->nid]->linkout = new stdClass();

+ 44 - 13
theme/blast_report.tpl.php

@@ -14,6 +14,15 @@ if ($blastdb->linkout->none === FALSE) {
   $linkout_regex = $blastdb->linkout->regex;
   if (isset($blastdb->linkout->db_id->urlprefix) AND !empty($blastdb->linkout->db_id->urlprefix)) {
     $linkout_urlprefix = $blastdb->linkout->db_id->urlprefix;
+
+    // Furthermore, check that we can determine the URL.
+    // (ie: that the function specified to do so, exists).
+    if (function_exists($blastdb->linkout->url_function)) {
+      $url_function = $blastdb->linkout->url_function;
+    }
+    else {
+      $linkout = FALSE;
+    }
   }
   else {
     $linkout = FALSE;
@@ -100,28 +109,54 @@ if ($xml) {
           $zebra_class = ($count % 2 == 0) ? 'even' : 'odd';
           $no_hits = FALSE;
 
+          // RETRIEVE INFO
+          $hit_name = (preg_match('/BL_ORD_ID/', $hit->{'Hit_id'})) ? $hit->{'Hit_def'} : $hit->{'Hit_id'};
+          $score = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
+          $evalue = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
+          $query_name = $iteration->{'Iteration_query-def'};
+
+          $HSPs = array();
+          foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
+            $HSPs[] = (array) $hsp_xml;
+          }
+
           // SUMMARY ROW
           // If the id is of the form gnl|BL_ORD_ID|### then the parseids flag
           // to makeblastdb did a really poor job. In thhis case we want to use
           // the def to provide the original FASTA header.
-          $hit_name = (preg_match('/BL_ORD_ID/', $hit->{'Hit_id'})) ? $hit->{'Hit_def'} : $hit->{'Hit_id'};
+
 
           // If our BLAST DB is configured to handle link-outs then use the
           // regex & URL prefix provided to create one.
           if ($linkout) {
             if (preg_match($linkout_regex, $hit_name, $linkout_match)) {
-              $hit_name = l(
-                $linkout_match[1],
-                $linkout_urlprefix . $linkout_match[1],
-                array('attributes' => array('target' => '_blank'))
+
+              $linkout_id = $linkout_match[1];
+              $hit->{'linkout_id'} = $linkout_id;
+              $hit->{'hit_name'} = $hit_name;
+
+              $hit_url = call_user_func(
+                $url_function,
+                $linkout_urlprefix,
+                $hit,
+                array(
+                  'query_name' => $query_name,
+                  'score' => $score,
+                  'e-value' => $evalue,
+                  'HSPs' => $HSPs
+                )
               );
+
+              if ($hit_url) {
+                $hit_name = l(
+                  $linkout_id,
+                  $hit_url,
+                  array('attributes' => array('target' => '_blank'))
+                );
+              }
             }
           }
 
-          $score = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
-          $evalue = $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
-          $query_name = $iteration->{'Iteration_query-def'};
-
           $row = array(
             'data' => array(
               'number' => array('data' => $count, 'class' => array('number')),
@@ -136,10 +171,6 @@ if ($xml) {
 
           // ALIGNMENT ROW (collapsed by default)
           // Process HSPs
-          $HSPs = array();
-          foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
-            $HSPs[] = (array) $hsp_xml;
-          }
 
           $row = array(
             'data' => array(