JAL-3725 helper methods for computing mapped feature range overlap
[jalview.git] / src / jalview / util / MapList.java
index 731e976..c5654fb 100644 (file)
@@ -24,6 +24,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import jalview.bin.Cache;
+
 /**
  * A simple way of bijectively mapping a non-contiguous linear range to another
  * non-contiguous linear range.
@@ -209,8 +211,7 @@ public class MapList
 
   /**
    * Constructor given from and to ranges as [start1, end1, start2, end2,...].
-   * If any end is equal to the next start, the ranges will be merged. There is
-   * no validation check that the ranges do not overlap each other.
+   * There is no validation check that the ranges do not overlap each other.
    * 
    * @param from
    *          contiguous regions as [start1, end1, start2, end2, ...]
@@ -228,7 +229,6 @@ public class MapList
     this.toRatio = toRatio;
     fromLowest = Integer.MAX_VALUE;
     fromHighest = Integer.MIN_VALUE;
-    int added = 0;
 
     for (int i = 0; i < from.length; i += 2)
     {
@@ -238,36 +238,16 @@ public class MapList
        */
       fromLowest = Math.min(fromLowest, Math.min(from[i], from[i + 1]));
       fromHighest = Math.max(fromHighest, Math.max(from[i], from[i + 1]));
-      if (added > 0 && from[i] == fromShifts.get(added - 1)[1])
-      {
-        /*
-         * this range starts where the last ended - just extend it
-         */
-        fromShifts.get(added - 1)[1] = from[i + 1];
-      }
-      else
-      {
-        fromShifts.add(new int[] { from[i], from[i + 1] });
-        added++;
-      }
+      fromShifts.add(new int[] { from[i], from[i + 1] });
     }
 
     toLowest = Integer.MAX_VALUE;
     toHighest = Integer.MIN_VALUE;
-    added = 0;
     for (int i = 0; i < to.length; i += 2)
     {
       toLowest = Math.min(toLowest, Math.min(to[i], to[i + 1]));
       toHighest = Math.max(toHighest, Math.max(to[i], to[i + 1]));
-      if (added > 0 && to[i] == toShifts.get(added - 1)[1])
-      {
-        toShifts.get(added - 1)[1] = to[i + 1];
-      }
-      else
-      {
-        toShifts.add(new int[] { to[i], to[i + 1] });
-        added++;
-      }
+      toShifts.add(new int[] { to[i], to[i + 1] });
     }
   }
 
@@ -330,9 +310,8 @@ public class MapList
       if (range.length != 2)
       {
         // throw new IllegalArgumentException(range);
-        System.err.println(
-                "Invalid format for fromRange " + Arrays.toString(range)
-                + " may cause errors");
+        Cache.log.error("Invalid format for fromRange "
+                + Arrays.toString(range) + " may cause errors");
       }
       fromLowest = Math.min(fromLowest, Math.min(range[0], range[1]));
       fromHighest = Math.max(fromHighest, Math.max(range[0], range[1]));
@@ -345,9 +324,8 @@ public class MapList
       if (range.length != 2)
       {
         // throw new IllegalArgumentException(range);
-        System.err.println("Invalid format for toRange "
-                + Arrays.toString(range)
-                + " may cause errors");
+        Cache.log.error("Invalid format for toRange "
+                + Arrays.toString(range) + " may cause errors");
       }
       toLowest = Math.min(toLowest, Math.min(range[0], range[1]));
       toHighest = Math.max(toHighest, Math.max(range[0], range[1]));
@@ -357,6 +335,16 @@ public class MapList
   /**
    * Consolidates a list of ranges so that any contiguous ranges are merged.
    * This assumes the ranges are already in start order (does not sort them).
+   * <p>
+   * The main use case for this method is when mapping cDNA sequence to its
+   * protein product, based on CDS feature ranges which derive from spliced
+   * exons, but are contiguous on the cDNA sequence. For example 
+   * <pre>
+   *   CDS 1-20  // from exon1
+   *   CDS 21-35 // from exon2
+   *   CDS 36-71 // from exon3
+   * 'coalesce' to range 1-71
+   * </pre>
    * 
    * @param ranges
    * @return the same list (if unchanged), else a new merged list, leaving the
@@ -384,27 +372,6 @@ public class MapList
         first = false;
         continue;
       }
-      if (range[0] == lastRange[0] && range[1] == lastRange[1])
-      {
-        // drop duplicate range
-        changed = true;
-        continue;
-      }
-
-      /*
-       * drop this range if it lies within the last range
-       */
-      if ((lastDirection == 1 && range[0] >= lastRange[0]
-              && range[0] <= lastRange[1] && range[1] >= lastRange[0]
-              && range[1] <= lastRange[1])
-              || (lastDirection == -1 && range[0] <= lastRange[0]
-                      && range[0] >= lastRange[1]
-                      && range[1] <= lastRange[0]
-                      && range[1] >= lastRange[1]))
-      {
-        changed = true;
-        continue;
-      }
 
       int direction = range[1] >= range[0] ? 1 : -1;
 
@@ -415,11 +382,7 @@ public class MapList
       boolean sameDirection = range[1] == range[0]
               || direction == lastDirection;
       boolean extending = range[0] == lastRange[1] + lastDirection;
-      boolean overlapping = (lastDirection == 1 && range[0] >= lastRange[0]
-              && range[0] <= lastRange[1])
-              || (lastDirection == -1 && range[0] <= lastRange[0]
-                      && range[0] >= lastRange[1]);
-      if (sameDirection && (overlapping || extending))
+      if (sameDirection && extending)
       {
         lastRange[1] = range[1];
         changed = true;
@@ -1134,8 +1097,8 @@ public class MapList
   }
 
   /**
-   * A helper method that returns true unless at least one range has start > end.
-   * Behaviour is undefined for a mixture of forward and reverse ranges.
+   * A helper method that returns true unless at least one range has start >
+   * end. Behaviour is undefined for a mixture of forward and reverse ranges.
    * 
    * @param ranges
    * @return
@@ -1246,4 +1209,36 @@ public class MapList
   {
     return fromShifts.size() == 1 && toShifts.size() == 1;
   }
+
+  /**
+   * Returns the [start, end...] positions in the range mapped from, that are
+   * mapped to by part or all of the given begin-end of the range mapped to.
+   * Returns null if begin-end does not overlap any position mapped to.
+   * 
+   * @param begin
+   * @param end
+   * @return
+   */
+  public int[] getOverlapsInFrom(final int begin, final int end)
+  {
+    int[] overlaps = MappingUtils.findOverlap(toShifts, begin, end);
+
+    return overlaps == null ? null : locateInFrom(overlaps[0], overlaps[1]);
+  }
+
+  /**
+   * Returns the [start, end...] positions in the range mapped to, that are
+   * mapped to by part or all of the given begin-end of the range mapped from.
+   * Returns null if begin-end does not overlap any position mapped from.
+   * 
+   * @param begin
+   * @param end
+   * @return
+   */
+  public int[] getOverlapsInTo(final int begin, final int end)
+  {
+    int[] overlaps = MappingUtils.findOverlap(fromShifts, begin, end);
+
+    return overlaps == null ? null : locateInTo(overlaps[0], overlaps[1]);
+  }
 }