///|
/// A biological span with optional remote accession metadata.
pub struct LocationSummary {
raw : String
minimum : Int
maximum : Int
length : Int
segment_count : Int
reverse_segments : Int
remote_segments : Int
fuzzy : Bool
between : Bool
} derive(Debug, Eq)
///|
pub fn summarize_location(location : Location) -> LocationSummary {
let mut reverse = 0
let mut remote = 0
for segment in location.segments {
if segment.reverse {
reverse += 1
}
if segment.remote is Some(_) {
remote += 1
}
}
{
raw: location.raw,
minimum: location_minimum(location),
maximum: location_span(location),
length: location_length(location),
segment_count: location.segments.length(),
reverse_segments: reverse,
remote_segments: remote,
fuzzy: location_is_fuzzy(location),
between: location.ranges.any(fn(range) { range.between }),
}
}
///|
pub fn LocationSummary::to_json_string(self : LocationSummary) -> String {
"{\"raw\":" +
json_string(self.raw) +
",\"minimum\":" +
self.minimum.to_string() +
",\"maximum\":" +
self.maximum.to_string() +
",\"length\":" +
self.length.to_string() +
",\"segment_count\":" +
self.segment_count.to_string() +
",\"reverse_segments\":" +
self.reverse_segments.to_string() +
",\"remote_segments\":" +
self.remote_segments.to_string() +
",\"fuzzy\":" +
self.fuzzy.to_string() +
",\"between\":" +
self.between.to_string() +
"}"
}
///|
pub fn location_segments(location : Location) -> Array[Range] {
location.segments.map(fn(segment) { segment.range })
}
///|
pub fn location_forward_coordinates(location : Location) -> Array[Int] {
let result : Array[Int] = []
for segment in location.segments {
for coordinate in segment.range.start..<=segment.range.end {
result.push(coordinate)
}
}
result
}
///|
pub fn location_unique_coordinates(location : Location) -> Array[Int] {
let result : Array[Int] = []
for coordinate in location_coordinates(location) {
if !result.contains(coordinate) {
result.push(coordinate)
}
}
result
}
///|
pub fn location_has_overlap(left : Location, right : Location) -> Bool {
left.ranges.any(fn(a) {
right.ranges.any(fn(b) { a.start <= b.end && b.start <= a.end })
})
}
///|
pub fn location_distance(left : Location, right : Location) -> Int? {
if location_has_overlap(left, right) {
Some(0)
} else {
let left_end = location_span(left)
let right_end = location_span(right)
let left_start = location_minimum(left)
let right_start = location_minimum(right)
if left_end < right_start {
Some(right_start - left_end)
} else if right_end < left_start {
Some(left_start - right_end)
} else {
None
}
}
}
///|
pub fn location_is_single_range(location : Location) -> Bool {
location.segments.length() == 1
}
///|
pub fn location_is_joined(location : Location) -> Bool {
location.joined || location.segments.length() > 1
}
///|
pub fn location_is_ordered(location : Location) -> Bool {
location.ordered
}
///|
pub fn location_is_complemented(location : Location) -> Bool {
location.complement || location.segments.any(fn(segment) { segment.reverse })
}
///|
pub fn location_remote_accessions(location : Location) -> Array[String] {
let result : Array[String] = []
for segment in location.segments {
match segment.remote {
Some(accession) if !result.contains(accession) => result.push(accession)
_ => ()
}
}
result
}
///|
pub fn location_partial_ranges(location : Location) -> Array[Range] {
location.ranges.filter(fn(range) { range.partial_start || range.partial_end })
}
///|
pub fn location_between_ranges(location : Location) -> Array[Range] {
location.ranges.filter(fn(range) { range.between })
}
///|
pub fn location_range_text(range : Range) -> String {
let start = (if range.partial_start { "<" } else { "" }) +
range.start.to_string()
let end = (if range.partial_end { ">" } else { "" }) + range.end.to_string()
if range.between {
start + "^" + end
} else if range.start == range.end {
start
} else {
start + ".." + end
}
}
///|
pub fn location_ranges_text(location : Location) -> Array[String] {
location.ranges.map(location_range_text)
}
///|
pub fn location_summary_text(location : Location) -> String {
let summary = summarize_location(location)
"location: " +
summary.raw +
"\n" +
"span: " +
summary.minimum.to_string() +
".." +
summary.maximum.to_string() +
"\n" +
"length: " +
summary.length.to_string() +
"\n" +
"segments: " +
summary.segment_count.to_string() +
"\n" +
"reverse: " +
summary.reverse_segments.to_string() +
"\n" +
"remote: " +
summary.remote_segments.to_string() +
"\n" +
"fuzzy: " +
summary.fuzzy.to_string() +
"\n"
}
///|
pub fn features_with_location_property(
features : Array[Feature],
include_remote : Bool,
include_fuzzy : Bool,
) -> Array[Feature] {
features.filter(fn(feature) {
(include_remote && location_is_remote(feature.location)) ||
(include_fuzzy && location_is_fuzzy(feature.location))
})
}
///|
pub fn features_with_strand(
features : Array[Feature],
reverse : Bool,
) -> Array[Feature] {
features.filter(fn(feature) {
location_is_complemented(feature.location) == reverse
})
}
///|
pub fn feature_span(feature : Feature) -> Int {
location_span(feature.location)
}
///|
pub fn feature_length(feature : Feature) -> Int {
location_length(feature.location)
}
///|
pub fn feature_start(feature : Feature) -> Int {
location_minimum(feature.location)
}
///|
pub fn feature_end(feature : Feature) -> Int {
location_span(feature.location)
}
///|
pub fn feature_is_empty(feature : Feature) -> Bool {
feature.location.segments.length() == 0
}
///|
pub fn feature_is_cds(feature : Feature) -> Bool {
feature.key == "CDS"
}
///|
pub fn feature_is_gene(feature : Feature) -> Bool {
feature.key == "gene"
}
///|
pub fn feature_is_source(feature : Feature) -> Bool {
feature.key == "source"
}
///|
pub fn feature_has_translation(feature : Feature) -> Bool {
feature.qualifier("translation") is Some(_)
}
///|
pub fn feature_gene_name(feature : Feature) -> String? {
feature.qualifier("gene")
}
///|
pub fn feature_product_name(feature : Feature) -> String? {
feature.qualifier("product")
}
///|
pub fn feature_note_values(feature : Feature) -> Array[String] {
feature.qualifiers.filter_map(fn(qualifier) {
if qualifier.name == "note" {
qualifier.value
} else {
None
}
})
}
///|
pub fn feature_qualifier_names(feature : Feature) -> Array[String] {
feature.qualifiers.map(fn(qualifier) { qualifier.name })
}
///|
pub fn feature_qualifier_count(feature : Feature) -> Int {
feature.qualifiers.length()
}
///|
pub fn feature_summary_text(feature : Feature) -> String {
let gene = match feature_gene_name(feature) {
Some(name) => " gene=" + name
None => ""
}
feature.key + " " + render_location(feature.location) + gene
}
///|
pub fn record_location_summaries(record : Record) -> Array[LocationSummary] {
record.features.map(fn(feature) { summarize_location(feature.location) })
}
///|
pub fn record_location_json(record : Record) -> String {
array_json(
record_location_summaries(record).map(fn(summary) {
summary.to_json_string()
}),
)
}
///|
pub fn record_span(record : Record) -> Int {
record.features.fold(init=0, fn(best, feature) {
Int::max(best, feature_end(feature))
})
}
///|
pub fn record_minimum_feature_start(record : Record) -> Int {
let starts = record.features.map(feature_start)
if starts.length() == 0 {
0
} else {
starts.fold(init=starts[0], fn(a, b) { Int::min(a, b) })
}
}
///|
pub fn record_maximum_feature_end(record : Record) -> Int {
record_span(record)
}
///|
pub fn record_feature_span(record : Record) -> Int {
let minimum = record_minimum_feature_start(record)
if minimum == 0 {
0
} else {
record_span(record) - minimum + 1
}
}