JAL-3076 fetch Ensembl sequence as JSON instead of Fasta
[jalview.git] / src / jalview / ext / ensembl / EnsemblMap.java
diff --git a/src/jalview/ext/ensembl/EnsemblMap.java b/src/jalview/ext/ensembl/EnsemblMap.java
new file mode 100644 (file)
index 0000000..c94528e
--- /dev/null
@@ -0,0 +1,213 @@
+package jalview.ext.ensembl;
+
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.DBRefSource;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.List;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+
+public class EnsemblMap extends EnsemblRestClient
+{
+  private static final String MAPPED = "mapped";
+
+  private static final String MAPPINGS = "mappings";
+
+  /**
+   * Default constructor (to use rest.ensembl.org)
+   */
+  public EnsemblMap()
+  {
+    super();
+  }
+
+  /**
+   * Constructor given the target domain to fetch data from
+   * 
+   * @param
+   */
+  public EnsemblMap(String domain)
+  {
+    super(domain);
+  }
+
+  @Override
+  public String getDbName()
+  {
+    return DBRefSource.ENSEMBL;
+  }
+
+  @Override
+  public AlignmentI getSequenceRecords(String queries) throws Exception
+  {
+    return null; // not used
+  }
+
+  /**
+   * Constructs a URL of the format <code>
+   * http://rest.ensembl.org/map/human/GRCh38/17:45051610..45109016:1/GRCh37?content-type=application/json
+   * </code>
+   * 
+   * @param species
+   * @param chromosome
+   * @param fromRef
+   * @param toRef
+   * @param startPos
+   * @param endPos
+   * @return
+   * @throws MalformedURLException
+   */
+  protected URL getAssemblyMapUrl(String species, String chromosome, String fromRef,
+          String toRef, int startPos, int endPos)
+          throws MalformedURLException
+  {
+    /*
+     * start-end might be reverse strand - present forwards to the service
+     */
+    boolean forward = startPos <= endPos;
+    int start = forward ? startPos : endPos;
+    int end = forward ? endPos : startPos;
+    String strand = forward ? "1" : "-1";
+    String url = String.format(
+            "%s/map/%s/%s/%s:%d..%d:%s/%s?content-type=application/json",
+            getDomain(), species, fromRef, chromosome, start, end, strand,
+            toRef);
+    return new URL(url);
+  }
+
+  @Override
+  protected boolean useGetRequest()
+  {
+    return true;
+  }
+
+  @Override
+  protected URL getUrl(List<String> ids) throws MalformedURLException
+  {
+    return null; // not used
+  }
+
+  /**
+   * Calls the REST /map service to get the chromosomal coordinates (start/end)
+   * in 'toRef' that corresponding to the (start/end) queryRange in 'fromRef'
+   * 
+   * @param species
+   * @param chromosome
+   * @param fromRef
+   * @param toRef
+   * @param queryRange
+   * @return
+   * @see http://rest.ensemblgenomes.org/documentation/info/assembly_map
+   */
+  public int[] getAssemblyMapping(String species, String chromosome,
+          String fromRef, String toRef, int[] queryRange)
+  {
+    URL url = null;
+    BufferedReader br = null;
+
+    try
+    {
+      url = getAssemblyMapUrl(species, chromosome, fromRef, toRef, queryRange[0],
+              queryRange[1]);
+      br = getHttpResponse(url, null);
+      return (parseAssemblyMappingResponse(br));
+    } catch (Throwable t)
+    {
+      System.out.println("Error calling " + url + ": " + t.getMessage());
+      return null;
+    } finally
+    {
+      if (br != null)
+      {
+        try
+        {
+          br.close();
+        } catch (IOException e)
+        {
+          // ignore
+        }
+      }
+    }
+  }
+
+  /**
+   * Parses the JSON response from the /map/&lt;species&gt;/ REST service. The
+   * format is (with some fields omitted)
+   * 
+   * <pre>
+   *  {"mappings": 
+   *    [{
+   *       "original": {"end":45109016,"start":45051610},
+   *       "mapped"  : {"end":43186384,"start":43128978} 
+   *  }] }
+   * </pre>
+   * 
+   * @param br
+   * @return
+   */
+  protected int[] parseAssemblyMappingResponse(BufferedReader br)
+  {
+    int[] result = null;
+    JSONParser jp = new JSONParser();
+
+    try
+    {
+      JSONObject parsed = (JSONObject) jp.parse(br);
+      JSONArray mappings = (JSONArray) parsed.get(MAPPINGS);
+
+      Iterator rvals = mappings.iterator();
+      while (rvals.hasNext())
+      {
+        // todo check for "mapped"
+        JSONObject val = (JSONObject) rvals.next();
+        JSONObject mapped = (JSONObject) val.get(MAPPED);
+        int start = Integer.parseInt(mapped.get("start").toString());
+        int end = Integer.parseInt(mapped.get("end").toString());
+        String strand = mapped.get("strand").toString();
+        if ("1".equals(strand))
+        {
+          result = new int[] { start, end };
+        }
+        else
+        {
+          result = new int[] { end, start };
+        }
+      }
+    } catch (IOException | ParseException | NumberFormatException e)
+    {
+      // ignore
+    }
+    return result;
+  }
+
+  /**
+   * Constructs a URL to the /map/cds/<id> or /map/cdna/<id> REST service. The
+   * REST call is to either ensembl or ensemblgenomes, as determined from the
+   * division, e.g. Ensembl or EnsemblProtists.
+   * 
+   * @param domain
+   * @param accession
+   * @param start
+   * @param end
+   * @param cdsOrCdna
+   * @return
+   * @throws MalformedURLException
+   */
+  URL getIdMapUrl(String domain, String accession, int start, int end,
+          String cdsOrCdna) throws MalformedURLException
+  {
+    String url = String
+            .format("%s/map/%s/%s/%d..%d?include_original_region=1&content-type=application/json",
+                    domain, cdsOrCdna, accession, start, end);
+    return new URL(url);
+  }
+
+}