/*
* The MIT License (MIT)
*
* Copyright (c) 2007-2015 Broad Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.broad.igv.track;
import org.apache.log4j.Logger;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.util.MessageUtils;
import htsjdk.tribble.Feature;
import java.util.*;
/**
* Represents a table of features, packed so there is no overlap.
* Features are packed into rows, accessible via {@link #getRows}
*
* @author jrobinso
* @date Oct 7, 2010
*/
public class PackedFeatures<T extends Feature>{
protected String trackName;
protected String chr;
protected int start;
protected int end;
protected List<T> features;
protected List<FeatureRow> rows;
private static Logger log = Logger.getLogger(PackedFeatures.class);
protected int maxFeatureLength = 0;
protected static int maxLevels = 1000000;
/**
* No-arg constructor to allow subclassing
*/
PackedFeatures(){
}
PackedFeatures(String chr, int start, int end) {
this.chr = chr;
this.start = start;
this.end = end;
features = Collections.emptyList();
rows = Collections.emptyList();
}
PackedFeatures(String chr, int start, int end, Iterator<T> iter, String trackName) {
this.trackName = trackName;
this.chr = chr;
this.start = start;
this.end = end;
features = new ArrayList(1000);
rows = packFeatures(iter);
}
/**
* Some types of Features (splice junctions) should be packed on the same row even if start and end overlap.
* This can be overridden in a subclass
* @param feature
* @return
*/
protected int getFeatureStartForPacking(Feature feature)
{
return feature.getStart();
}
/**
* Some types of Features (splice junctions) should be packed on the same row even if start and end overlap.
* This can be overridden in a subclass
* @param feature
* @return
*/
protected int getFeatureEndForPacking(Feature feature)
{
return feature.getEnd();
}
int getRowCount() {
return getRows().size();
}
public boolean containsInterval(String chr, int start, int end) {
return this.getChr().equals(chr) && start >= this.getStart() && end <= this.getEnd();
}
public boolean overlapsInterval(String chr, int start, int end) {
return this.getChr().equals(chr) && start <= this.end && end >= this.start;
}
/**
* Allocates each feature to the rows such that there is no overlap.
*
* @param iter TabixLineReader wrapping the collection of alignments. Note that this should
* really be an Iterator<T>, but it can't be subclassed if that's the case.
*/
List<FeatureRow> packFeatures(Iterator iter) {
List<FeatureRow> rows = new ArrayList(10);
if (iter == null || !iter.hasNext()) {
return rows;
}
maxFeatureLength = 0;
int totalCount = 0;
LinkedHashMap<Integer, PriorityQueue<T>> bucketArray = new LinkedHashMap();
Comparator pqComparator = new Comparator<T>() {
public int compare(Feature row1, Feature row2) {
return (row2.getEnd() - row2.getStart()) - (row1.getEnd() - row2.getStart());
}
};
// Allocate features to buckets, 1 bucket per base position
while (iter.hasNext()) {
T feature = (T) iter.next();
maxFeatureLength = Math.max(maxFeatureLength,
getFeatureEndForPacking(feature) - getFeatureStartForPacking(feature));
features.add(feature);
int bucketNumber = getFeatureStartForPacking(feature);
PriorityQueue<T> bucket = bucketArray.get(bucketNumber);
if (bucket == null) {
bucket = new PriorityQueue<T>(5, pqComparator);
bucketArray.put(bucketNumber, bucket);
}
bucket.add(feature);
totalCount++;
}
// Allocate features to rows, pulling at most 1 per bucket for each row
FeatureRow currentRow = new FeatureRow();
int allocatedCount = 0;
int nextStart = Integer.MIN_VALUE;
int lastAllocatedCount = -1;
while (allocatedCount < totalCount && rows.size() < maxLevels) {
// Check to prevent infinite loops
if (lastAllocatedCount == allocatedCount) {
if(IGV.hasInstance()) {
String msg = "Infinite loop detected while packing features for track: " + getTrackName() +
".<br>Not all features will be shown." +
"<br>Please contact igv-team@broadinstitute.org";
log.error(msg);
MessageUtils.showMessage(msg);
}
break;
}
lastAllocatedCount = allocatedCount;
// Next row Loop through alignments until we reach the end of the interval
PriorityQueue<T> bucket = null;
// Advance to nextLine occupied bucket
ArrayList<Integer> emptyBucketKeys = new ArrayList();
for (Integer key : bucketArray.keySet()) {
if (key >= nextStart) {
bucket = bucketArray.get(key);
T feature = bucket.poll();
if (bucket.isEmpty()) {
emptyBucketKeys.add(key);
}
currentRow.addFeature(feature);
nextStart = currentRow.end + FeatureTrack.MINIMUM_FEATURE_SPACING;
allocatedCount++;
}
}
for (Integer key : emptyBucketKeys) {
bucketArray.remove(key);
}
// We've reached the end of the interval, start a new row
if (currentRow.features.size() > 0) {
rows.add(currentRow);
lastAllocatedCount = 0;
}
currentRow = new FeatureRow();
nextStart = 0;
}
// Add the last row
if (currentRow.features.size() > 0) {
rows.add(currentRow);
}
return rows;
}
public String getTrackName() {
return trackName;
}
public String getChr() {
return chr;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public List<T> getFeatures() {
return features;
}
public List<FeatureRow> getRows() {
return rows;
}
public int getMaxFeatureLength() {
return maxFeatureLength;
}
public class FeatureRow {
int start;
int end;
List<T> features;
public FeatureRow() {
this.features = new ArrayList(100);
}
public void addFeature(T feature) {
if (features.isEmpty()) {
this.start = getFeatureStartForPacking(feature);
}
features.add(feature);
end = getFeatureEndForPacking(feature);
}
public List<T> getFeatures() {
return features;
}
}
}