3 # BioPerl module for Bio::Map::PositionI
5 # Cared for by Sendu Bala <bix@sendu.me.uk>
7 # Copyright Jason Stajich
9 # You may distribute this module under the same terms as perl itself
11 # POD documentation - main docs before the code
15 Bio::Map::PositionI - Abstracts the notion of a position having a value in the context of a marker and a Map
19 # do not use this module directly
20 # See Bio::Map::Position for an example of
25 This object stores one of the postions that a mappable object
26 (e.g. Marker) may have in a map.
28 Positions can have non-numeric values or other methods to store the locations,
29 so they have a method numeric() which does the conversion. numeric()
30 returns the position in a form that can be compared between other positions of
31 the same type. It is not necessarily a value suitable for sorting positions (it
32 may be the distance from the previous position); for that purpose the result of
33 sortable() should be used.
35 A 'position', in addition to being a single point, can also be an area and so
36 can be imagined as a range and compared with other positions on the basis of
37 overlap, intersection etc.
43 User feedback is an integral part of the evolution of this and other
44 Bioperl modules. Send your comments and suggestions preferably to
45 the Bioperl mailing list. Your participation is much appreciated.
47 bioperl-l@bioperl.org - General discussion
48 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
52 Report bugs to the Bioperl bug tracking system to help us keep track
53 of the bugs and their resolution. Bug reports can be submitted via the
56 http://bugzilla.open-bio.org/
58 =head1 AUTHOR - Jason Stajich
60 Email jason-at-bioperl.org
64 Lincoln Stein, lstein-at-cshl.org
65 Heikki Lehvaslaiho, heikki-at-bioperl-dot-org
66 Sendu Bala, bix@sendu.me.uk
70 The rest of the documentation details each of the object methods.
71 Internal methods are usually preceded with a _
75 # Let the code begin...
77 package Bio
::Map
::PositionI
;
79 use Bio
::Map
::PositionHandler
;
80 use Bio
::Map
::Mappable
;
81 use Scalar
::Util
qw(looks_like_number);
83 use base
qw(Bio::Map::EntityI Bio::RangeI);
85 =head2 EntityI methods
87 These are fundamental to coordination of Positions and other entities, so are
88 implemented at the interface level
92 =head2 get_position_handler
94 Title : get_position_handler
95 Usage : my $position_handler = $entity->get_position_handler();
96 Function: Gets a PositionHandlerI that $entity is registered with.
97 Returns : Bio::Map::PositionHandlerI object
102 sub get_position_handler
{
104 unless (defined $self->{_eh
}) {
105 my $ph = Bio
::Map
::PositionHandler
->new(-self
=> $self);
112 =head2 PositionHandlerI-related methods
114 These are fundamental to coordination of Positions and other entities, so are
115 implemented at the interface level
122 Usage : my $map = $position->map();
123 $position->map($map);
124 Function: Get/Set the map the position is in.
125 Returns : L<Bio::Map::MapI>
127 new L<Bio::Map::MapI> to set
132 my ($self, $map) = @_;
133 return $self->get_position_handler->map($map);
139 Usage : my $element = $position->element();
140 $position->element($element);
141 Function: Get/Set the element the position is for.
142 Returns : L<Bio::Map::MappableI>
144 new L<Bio::Map::MappableI> to set
149 my ($self, $element) = @_;
150 return $self->get_position_handler->element($element);
156 Function: This is a synonym of the element() method
157 Status : deprecated, will be removed in the next version
163 =head2 PositionI-specific methods
170 Usage : my $pos = $position->value();
171 Function: Get/Set the value for this position
172 Returns : scalar, value
173 Args : [optional] new value to set
179 $self->throw_not_implemented();
185 Usage : my $num = $position->numeric;
186 Function: Read-only method that is guaranteed to return a numeric
187 representation of the start of this position.
188 Returns : scalar numeric
189 Args : none to get the co-ordinate normally (see absolute() method), OR
190 Bio::Map::RelativeI to get the co-ordinate converted to be
191 relative to what this Relative describes.
197 $self->throw_not_implemented();
203 Usage : my $num = $position->sortable();
204 Function: Read-only method that is guaranteed to return a value suitable
205 for correctly sorting this kind of position amongst other positions
206 of the same kind on the same map. Note that sorting different kinds
207 of position together is unlikely to give sane results.
215 $self->throw_not_implemented();
221 Usage : my $relative = $position->relative();
222 $position->relative($relative);
223 Function: Get/set the thing this Position's coordinates (numerical(), start(),
224 end()) are relative to, as described by a Relative object.
225 Returns : Bio::Map::RelativeI (default is one describing "relative to the
226 start of the Position's map")
227 Args : none to get, OR
228 Bio::Map::RelativeI to set
234 $self->throw_not_implemented();
240 Usage : my $absolute = $position->absolute();
241 $position->absolute($absolute);
242 Function: Get/set how this Position's co-ordinates (numerical(), start(),
243 end()) are reported. When absolute is off, co-ordinates are
244 relative to the thing described by relative(). Ie. the value
245 returned by start() will be the same as the value you set start()
246 to. When absolute is on, co-ordinates are converted to be relative
247 to the start of the map.
249 So if relative() currently points to a Relative object describing
250 "relative to another position which is 100 bp from the start of
251 the map", this Position's start() had been set to 50 and absolute()
252 returns 1, $position->start() will return 150. If absolute() returns
253 0 in the same situation, $position->start() would return 50.
255 Returns : boolean (default 0)
256 Args : none to get, OR
263 $self->throw_not_implemented();
266 =head2 RangeI-based methods
273 Usage : my $start = $position->start();
274 $position->start($start);
275 Function: Get/set the start co-ordinate of this position.
276 Returns : the start of this position
277 Args : scalar numeric to set, OR
278 none to get the co-ordinate normally (see absolute() method), OR
279 Bio::Map::RelativeI to get the co-ordinate converted to be
280 relative to what this Relative describes.
287 Usage : my $end = $position->end();
288 $position->end($end);
289 Function: Get/set the end co-ordinate of this position.
290 Returns : the end of this position
291 Args : scalar numeric to set, OR
292 none to get the co-ordinate normally (see absolute() method), OR
293 Bio::Map::RelativeI to get the co-ordinate converted to be
294 relative to what this Relative describes.
301 Usage : $length = $position->length();
302 Function: Get the length of this position.
303 Returns : the length of this position
311 Usage : $strand = $position->strand();
312 Function: Get the strand of this position; it is always 1 since maps to not
326 Usage : print $position->toString(), "\n";
327 Function: stringifies this range
328 Returns : a string representation of the range of this Position
329 Args : optional Bio::Map::RelativeI to have the co-ordinates reported
330 relative to the thing described by that Relative
336 $self->throw_not_implemented();
339 =head1 RangeI-related methods
341 These methods work by considering only the values of start() and end(), as
342 modified by considering every such co-ordinate relative to the start of the map
343 (ie. absolute(1) is set temporarily during the calculation), or any supplied
344 Relative. For the boolean methods, when the comparison Position is on the same
345 map as the calling Position, there is no point supplying a Relative since the
346 answer will be the same as without. Relative is most useful when comparing
347 Positions on different maps and you have a Relative that describes some special
348 place on each map like 'the start of the gene', where the actual start of the
349 gene relative to the start of the map is different for each map.
351 The methods do not consider maps during their calculations - things on different
352 maps can overlap/contain/intersect/etc. each other.
354 The geometrical methods (intersect, union etc.) do things to the geometry of
355 ranges, and return Bio::Map::PositionI compliant objects or triplets (start,
356 stop, strand) from which new positions could be built. When a PositionI is made
357 it will have a map transferred to it if all the arguments shared the same map.
358 If a Relative was supplied the result will have that same Relative.
360 Note that the strand-testing args are there for compatability with the RangeI
361 interface. They have no meaning when only using PositionI objects since maps do
362 not have strands. Typically you will just set the argument to undef if you want
363 to supply the argument after it.
368 Usage : if ($p1->equals($p2)) {...}
369 Function: Test whether $p1 has the same start, end, length as $p2.
370 Returns : true if they are describing the same position (regardless of map)
371 Args : arg #1 = a Bio::RangeI (eg. a Bio::Map::Position) to compare this
373 arg #2 = optional strand-testing arg ('strong', 'weak', 'ignore')
374 arg #3 = optional Bio::Map::RelativeI to ask if the Positions
375 equal in terms of their relative position to the thing
376 described by that Relative
381 # overriding the RangeI implementation so we can handle Relative
382 my ($self, $other, $so, $rel) = @_;
384 my ($own_start, $own_end) = $self->_pre_rangei($self, $rel);
385 my ($other_start, $other_end) = $self->_pre_rangei($other, $rel);
387 return ($self->_testStrand($other, $so) and
388 $own_start == $other_start and $own_end == $other_end);
395 Usage : if ($position->less_than($other_position)) {...}
396 Function: Ask if this Position ends before another starts.
398 Args : arg #1 = a Bio::RangeI (eg. a Bio::Map::Position) to compare this
400 arg #2 = optional Bio::Map::RelativeI to ask if the Position is less
401 in terms of their relative position to the thing described
407 my ($self, $other, $rel) = @_;
409 my ($own_start, $own_end) = $self->_pre_rangei($self, $rel);
410 my ($other_start, $other_end) = $self->_pre_rangei($other, $rel);
412 return $own_end < $other_start;
418 Usage : if ($position->greater_than($other_position)) {...}
419 Function: Ask if this Position starts after another ends.
421 Args : arg #1 = a Bio::RangeI (eg. a Bio::Map::Position) to compare this
423 arg #2 = optional Bio::Map::RelativeI to ask if the Position is
424 greater in terms of their relative position to the thing
425 described by that Relative
430 my ($self, $other, $rel) = @_;
432 my ($own_start, $own_end) = $self->_pre_rangei($self, $rel);
433 my ($other_start, $other_end) = $self->_pre_rangei($other, $rel);
435 return $own_start > $other_end;
441 Usage : if ($p1->overlaps($p2)) {...}
442 Function: Tests if $p1 overlaps $p2.
443 Returns : True if the positions overlap (regardless of map), false otherwise
444 Args : arg #1 = a Bio::RangeI (eg. a Bio::Map::Position) to compare this
446 arg #2 = optional strand-testing arg ('strong', 'weak', 'ignore')
447 arg #3 = optional Bio::Map::RelativeI to ask if the Positions
448 overlap in terms of their relative position to the thing
449 described by that Relative
454 # overriding the RangeI implementation so we can handle Relative
455 my ($self, $other, $so, $rel) = @_;
457 my ($own_start, $own_end) = $self->_pre_rangei($self, $rel);
458 my ($other_start, $other_end) = $self->_pre_rangei($other, $rel);
460 return ($self->_testStrand($other, $so) and not
461 (($own_start > $other_end or $own_end < $other_start)));
467 Usage : if ($p1->contains($p2)) {...}
468 Function: Tests whether $p1 totally contains $p2.
469 Returns : true if the argument is totally contained within this position
470 (regardless of map), false otherwise
471 Args : arg #1 = a Bio::RangeI (eg. a Bio::Map::Position) to compare this
472 one to, or scalar number (mandatory)
473 arg #2 = optional strand-testing arg ('strong', 'weak', 'ignore')
474 arg #3 = optional Bio::Map::RelativeI to ask if the Position
475 is contained in terms of their relative position to the
476 thing described by that Relative
481 # overriding the RangeI implementation so we can handle Relative
482 my ($self, $other, $so, $rel) = @_;
484 my ($own_start, $own_end) = $self->_pre_rangei($self, $rel);
485 my ($other_start, $other_end) = $self->_pre_rangei($other, $rel);
487 return ($self->_testStrand($other, $so) and
488 $other_start >= $own_start and $other_end <= $own_end);
494 Usage : ($start, $stop, $strand) = $p1->intersection($p2)
495 ($start, $stop, $strand) = Bio::Map::Position->intersection(\@positions);
496 $mappable = $p1->intersection($p2, undef, $relative);
497 $mappable = Bio::Map::Position->intersection(\@positions);
498 Function: gives the range that is contained by all ranges
499 Returns : undef if they do not overlap, OR
500 Bio::Map::Mappable object who's positions are the
501 cross-map-calculated intersection of the input positions on all the
502 maps that the input positions belong to, OR, in list context, a three
503 element array (start, end, strand)
504 Args : arg #1 = [REQUIRED] a Bio::RangeI (eg. a Bio::Map::Position) to
505 compare this one to, or an array ref of Bio::RangeI
506 arg #2 = optional strand-testing arg ('strong', 'weak', 'ignore')
507 arg #3 = optional Bio::Map::RelativeI to ask how the Positions
508 intersect in terms of their relative position to the thing
509 described by that Relative
514 # overriding the RangeI implementation so we can transfer map and handle
516 my ($self, $given, $so, $rel) = @_;
517 $self->throw("missing arg: you need to pass in another argument") unless $given;
520 if ($self eq "Bio::Map::PositionI") {
521 $self = "Bio::Map::Position";
522 $self->warn("calling static methods of an interface is deprecated; use $self instead");
525 push(@positions, $self);
527 ref($given) eq 'ARRAY' ?
push(@positions, @
{$given}) : push(@positions, $given);
528 $self->throw("Need at least 2 Positions") unless @positions >= 2;
530 my ($intersect, $i_start, $i_end, $c_start, $c_end, %known_maps);
531 while (@positions > 0) {
532 unless ($intersect) {
533 $intersect = shift(@positions);
534 ($i_start, $i_end) = $self->_pre_rangei($intersect, $rel);
535 my $map = $intersect->map;
536 $known_maps{$map->unique_id} = $map;
539 my $compare = shift(@positions);
540 ($c_start, $c_end) = $self->_pre_rangei($compare, $rel);
541 return unless $compare->_testStrand($intersect, $so);
542 if ($compare->isa('Bio::Map::PositionI')) {
543 my $this_map = $compare->map;
545 $known_maps{$this_map->unique_id} = $this_map;
549 $self->throw("Only Bio::Map::PositionI objects are supported, not [$compare]");
552 my @starts = sort {$a <=> $b} ($i_start, $c_start);
553 my @ends = sort {$a <=> $b} ($i_end, $c_end);
555 my $start = pop @starts; # larger of the 2 starts
556 my $end = shift @ends; # smaller of the 2 ends
558 my $intersect_strand; # strand for the intersection
559 if (defined($intersect->strand) && defined($compare->strand) && $intersect->strand == $compare->strand) {
560 $intersect_strand = $compare->strand;
563 $intersect_strand = 0;
570 $intersect = $self->new(-start
=> $start,
572 -strand
=> $intersect_strand);
576 $intersect || return;
577 my ($start, $end, $strand) = ($intersect->start, $intersect->end, $intersect->strand);
580 foreach my $known_map (values %known_maps) {
581 my $new_intersect = $intersect->new(-start
=> $start,
585 $new_intersect->relative($rel) if $rel;
586 push(@intersects, $new_intersect);
588 unless (@intersects) {
589 $intersect->relative($rel) if $rel;
590 @intersects = ($intersect);
593 my $result = Bio
::Map
::Mappable
->new();
594 $result->add_position(@intersects); # sneaky, add_position can take a list of positions
601 Usage : ($start, $stop, $strand) = $p1->union($p2);
602 ($start, $stop, $strand) = Bio::Map::Position->union(@positions);
603 my $mappable = $p1->union($p2);
604 my $mappable = Bio::Map::Position->union(@positions);
605 Function: finds the minimal position/range that contains all of the positions
606 Returns : Bio::Map::Mappable object who's positions are the
607 cross-map-calculated union of the input positions on all the maps
608 that the input positions belong to, OR, in list context, a three
609 element array (start, end, strand)
610 Args : a Bio::Map::PositionI to compare this one to, or a list of such
612 a single Bio::Map::PositionI or array ref of such AND a
613 Bio::Map::RelativeI to ask for the Position's union in terms of their
614 relative position to the thing described by that Relative
619 # overriding the RangeI implementation so we can transfer map and handle
621 my ($self, @args) = @_;
622 $self->throw("Not enough arguments") unless @args >= 1;
626 if ($self eq "Bio::Map::PositionI") {
627 $self = "Bio::Map::Position";
628 $self->warn("calling static methods of an interface is deprecated; use $self instead");
631 push(@positions, $self);
633 if (ref $args[0] eq 'ARRAY') {
634 push(@positions, @
{shift(@args)});
637 push(@positions, shift(@args));
639 if ($args[0] && $args[0]->isa('Bio::Map::RelativeI')) {
642 foreach my $arg (@args) {
643 # avoid pushing undefined values into @positions
644 push(@positions, $arg) if $arg;
646 $self->throw("Need at least 2 Positions") unless @positions >= 2;
648 my (@starts, @ends, %known_maps, $union_strand);
649 foreach my $compare (@positions) {
650 # RangeI union allows start or end to be undefined; however _pre_rangei
652 my ($start, $end) = $self->_pre_rangei($compare, $rel);
654 if ($compare->isa('Bio::Map::PositionI')) {
655 my $this_map = $compare->map;
657 $known_maps{$this_map->unique_id} = $this_map;
661 $self->throw("Only Bio::Map::PositionI objects are supported, not [$compare]");
664 if (! defined $union_strand) {
665 $union_strand = $compare->strand;
668 if (! defined $compare->strand or $union_strand ne $compare->strand) {
673 push(@starts, $start);
677 @starts = sort { $a <=> $b } @starts;
678 @ends = sort { $a <=> $b } @ends;
679 my $start = shift @starts;
683 foreach my $known_map (values %known_maps) {
684 my $new_union = $self->new(-start
=> $start,
686 -strand
=> $union_strand,
688 $new_union->relative($rel) if $rel;
689 push(@unions, $new_union);
692 @unions = ($self->new(-start
=> $start,
694 -strand
=> $union_strand));
695 $unions[0]->relative($rel) if $rel;
698 my $result = Bio
::Map
::Mappable
->new();
699 $result->add_position(@unions); # sneaky, add_position can take a list of positions
703 =head2 overlap_extent
705 Title : overlap_extent
706 Usage : ($a_unique,$common,$b_unique) = $a->overlap_extent($b)
707 Function: Provides actual amount of overlap between two different
710 Returns : array of values containing the length unique to the calling
711 position, the length common to both, and the length unique to
712 the argument position
717 #*** should this be overridden from RangeI?
719 =head2 disconnected_ranges
721 Title : disconnected_ranges
722 Usage : my @disc_ranges = Bio::Map::Position->disconnected_ranges(@ranges);
723 Function: Creates the minimal set of positions such that each input position is
724 fully contained by at least one output position, and none of the
725 output positions overlap.
726 Returns : Bio::Map::Mappable with the calculated disconnected ranges
727 Args : a Bio::Map::PositionI to compare this one to, or a list of such,
729 a single Bio::Map::PositionI or array ref of such AND a
730 Bio::Map::RelativeI to consider all Position's co-ordinates in terms
731 of their relative position to the thing described by that Relative
735 sub disconnected_ranges
{
736 # overriding the RangeI implementation so we can transfer map and handle
738 my ($self, @args) = @_;
739 $self->throw("Not enough arguments") unless @args >= 1;
743 if ($self eq "Bio::Map::PositionI") {
744 $self = "Bio::Map::Position";
745 $self->warn("calling static methods of an interface is deprecated; use $self instead");
748 push(@positions, $self);
750 if (ref $args[0] eq 'ARRAY') {
751 push(@positions, @
{shift(@args)});
754 push(@positions, shift(@args));
756 if ($args[0] && $args[0]->isa('Bio::Map::RelativeI')) {
759 foreach my $arg (@args) {
760 push(@positions, $arg) if $arg;
762 $self->throw("Need at least 2 Positions") unless @positions >= 2;
765 foreach my $pos (@positions) {
766 $pos->isa('Bio::Map::PositionI') || $self->throw("Must supply only Bio::Map::PositionI objects, not [$pos]");
767 my $map = $pos->map || next;
768 $known_maps{$map->unique_id} = $map;
771 foreach my $map (values %known_maps) {
772 foreach my $pos ($map->get_positions) {
773 $prior_positions{$pos} = 1;
778 foreach my $inrange (@positions) {
779 my @outranges_new = ();
780 my %overlapping_ranges = ();
782 for (my $i=0; $i<@outranges; $i++) {
783 my $outrange = $outranges[$i];
784 if ($inrange->overlaps($outrange, undef, $rel)) {
785 my $union_able = $inrange->intersection($outrange, undef, $rel);
786 foreach my $pos ($union_able->get_positions) {
787 $overlapping_ranges{$pos->toString} = $pos; # we flatten down to a result on a single map
788 # to avoid creating 10s of thousands of positions during this process;
789 # we then apply the final answer to all maps at the very end
793 push(@outranges_new, $outrange);
797 @outranges = @outranges_new;
799 my @overlappers = values %overlapping_ranges;
801 if (@overlappers > 1) {
802 my $merged_range_able = shift(@overlappers)->union(\
@overlappers, $rel);
803 push(@outranges, $merged_range_able->get_positions);
806 push(@outranges, @overlappers);
810 push(@outranges, $self->new(-start
=> $inrange->start($rel), -end
=> $inrange->end($rel), -strand
=> $inrange->strand, -map => $inrange->map, -relative
=> $rel));
814 # purge positions that were created whilst calculating the answer, but
815 # aren't the final answer and weren't there previously
816 my %answers = map { $_ => 1 } @outranges;
817 foreach my $map (values %known_maps) {
818 foreach my $pos ($map->get_positions) {
819 if (! exists $prior_positions{$pos} && ! exists $answers{$pos}) {
820 $map->purge_positions($pos);
826 foreach my $map (values %known_maps) {
827 foreach my $pos ($map->get_positions) {
828 $post_positions{$pos} = 1;
832 @outranges || return;
834 # make an outrange on all known maps
836 foreach my $map (values %known_maps) {
837 foreach my $pos (@outranges) {
838 if ($pos->map eq $map) {
839 push(@final_positions, $pos);
842 push(@final_positions, $pos->new(-start
=> $pos->start,
844 -relative
=> $pos->relative,
850 # assign the positions to a result mappable
851 my $result = Bio
::Map
::Mappable
->new();
852 $result->add_position(@final_positions); # sneaky, add_position can take a list of positions
857 # get start & end suitable for rangeI methods, taking relative into account
859 my ($self, $other, $rel) = @_;
860 $self->throw("Must supply an object") unless $other;
862 $self->throw("Must supply an object for the Relative argument") unless ref($rel);
863 $self->throw("This is [$rel], not a Bio::Map::RelativeI") unless $rel->isa('Bio::Map::RelativeI');
866 my ($other_start, $other_end);
868 if (ref($other) eq 'ARRAY') {
869 $self->throw("_pre_rangei got an array");
871 $self->throw("This is [$other], not a Bio::RangeI object") unless defined $other && $other->isa('Bio::RangeI');
873 if ($other->isa('Bio::Map::PositionI')) {
874 # to get the desired start/end we need the position to be on a map;
875 # if it isn't on one temporarily place it on self's map
876 # - this lets us have 'generic' positions that aren't on any map
877 # but have a relative defined and can thus be usefully compared to
878 # positions that /are/ on maps
879 my $other_map = $other->map;
880 unless ($other_map) {
881 my $self_map = $self->map || $self->throw("Trying to compare two positions but neither had been placed on a map");
882 $other->map($self_map);
885 # want start and end positions relative to the supplied rel or map start
886 $rel ||= $other->absolute_relative;
887 $other_start = $other->start($rel);
888 $other_end = $other->end($rel);
890 unless ($other_map) {
891 $self->map->purge_positions($other);
895 $other_start = $other->start;
896 $other_end = $other->end;
900 $self->throw("not a number") unless looks_like_number
($other);
901 $other_start = $other_end = $other;
904 $other->throw("start is undefined") unless defined $other_start;
905 $other->throw("end is undefined") unless defined $other_end;
907 return ($other_start, $other_end);