Propagated leftover changes from v1.6.x
[bioperl-live.git] / Bio / Map / Position.pm
blob8c6698c268600dc46bcc5ace05efddf988ac0df5
2 # BioPerl module for Bio::Map::Position
4 # Please direct questions and support issues to <bioperl-l@bioperl.org>
6 # Cared for by Sendu Bala <bix@sendu.me.uk>
8 # Copyright Jason Stajich
10 # You may distribute this module under the same terms as perl itself
12 # POD documentation - main docs before the code
14 =head1 NAME
16 Bio::Map::Position - A single position of a Marker, or the range over which
17 that marker lies, in a Map
19 =head1 SYNOPSIS
21 use Bio::Map::Position;
22 my $position = Bio::Map::Position->new(-map => $map,
23 -element => $marker,
24 -value => 100
27 my $position_with_range = Bio::Map::Position->new(-map => $map,
28 -element => $marker,
29 -start => 100,
30 -length => 10
33 =head1 DESCRIPTION
35 This object is an implementation of the PositionI interface that
36 handles the specific values of a position. This allows a map element
37 (e.g. Marker) to have multiple positions within a map and still be
38 treated as a single entity.
40 This handles the concept of a relative map in which the order of
41 elements and the distance between them is known, but does not
42 directly handle the case when distances are unknown - in that case
43 arbitrary values must be assigned for position values.
45 No units are assumed here - units are handled by context of which Map
46 a position is placed in or the subclass of this Position.
48 =head1 FEEDBACK
50 =head2 Mailing Lists
52 User feedback is an integral part of the evolution of this and other
53 Bioperl modules. Send your comments and suggestions preferably to
54 the Bioperl mailing list. Your participation is much appreciated.
56 bioperl-l@bioperl.org - General discussion
57 http://bioperl.org/wiki/Mailing_lists - About the mailing lists
59 =head2 Support
61 Please direct usage questions or support issues to the mailing list:
63 I<bioperl-l@bioperl.org>
65 rather than to the module maintainer directly. Many experienced and
66 reponsive experts will be able look at the problem and quickly
67 address it. Please include a thorough description of the problem
68 with code and data examples if at all possible.
70 =head2 Reporting Bugs
72 Report bugs to the Bioperl bug tracking system to help us keep track
73 of the bugs and their resolution. Bug reports can be submitted via the
74 web:
76 https://redmine.open-bio.org/projects/bioperl/
78 =head1 AUTHOR - Jason Stajich
80 Email jason@bioperl.org
82 =head1 CONTRIBUTORS
84 Lincoln Stein, lstein@cshl.org
85 Heikki Lehvaslaiho, heikki-at-bioperl-dot-org
86 Chad Matsalla, bioinformatics1@dieselwurks.com
87 Sendu Bala, bix@sendu.me.uk
89 =head1 APPENDIX
91 The rest of the documentation details each of the object methods.
92 Internal methods are usually preceded with a _
94 =cut
96 # Let the code begin...
98 package Bio::Map::Position;
99 use strict;
101 use Scalar::Util qw(looks_like_number);
102 use Bio::Map::Relative;
104 use base qw(Bio::Root::Root Bio::Map::PositionI);
106 =head2 new
108 Title : new
109 Usage : my $obj = Bio::Map::Position->new();
110 Function: Builds a new Bio::Map::Position object
111 Returns : Bio::Map::Position
112 Args : -map => Bio::Map::MapI object
113 -element => Bio::Map::MappableI object
114 -relative => Bio::Map::RelativeI object
116 * If this position has no range, or if a single value can describe
117 the range *
118 -value => scalar : something that describes the single
119 point position or range of this
120 Position, most likely an int
122 * Or if this position has a range, at least two of *
123 -start => int : value of the start co-ordinate
124 -end => int : value of the end co-ordinate
125 -length => int : length of the range
127 =cut
129 sub new {
130 my ($class, @args) = @_;
131 my $self = $class->SUPER::new(@args);
133 my ($map, $marker, $element, $value, $start, $end, $length, $relative) =
134 $self->_rearrange([qw( MAP
135 MARKER
136 ELEMENT
137 VALUE
138 START
140 LENGTH
141 RELATIVE
142 )], @args);
144 my $do_range = defined($start) || defined($end);
145 if ($value && $do_range) {
146 $self->warn("-value and (-start|-end|-length) are mutually exclusive, ignoring value");
147 $value = undef;
150 $map && $self->map($map);
151 $marker && $self->element($marker); # backwards compatibility
152 $element && $self->element($element);
153 $relative && $self->relative($relative);
154 defined($value) && $self->value($value);
156 if ($do_range) {
157 defined($start) && $self->start($start);
158 defined($end) && $self->end($end);
159 if ($length) {
160 if (defined($start) && ! defined($end)) {
161 $self->end($start + $length - 1);
163 elsif (! defined($start)) {
164 $self->start($end - $length + 1);
167 defined($self->end) || $self->end($start);
170 return $self;
173 =head2 relative
175 Title : relative
176 Usage : my $relative = $position->relative();
177 $position->relative($relative);
178 Function: Get/set the thing this Position's coordinates (numerical(), start(),
179 end()) are relative to, as described by a Relative object.
180 Returns : Bio::Map::RelativeI (default is one describing "relative to the
181 start of the Position's map")
182 Args : none to get, OR
183 Bio::Map::RelativeI to set
185 =cut
187 sub relative {
188 my ($self, $relative) = @_;
189 if ($relative) {
190 $self->throw("Must supply an object") unless ref($relative);
191 $self->throw("This is [$relative], not a Bio::Map::RelativeI") unless $relative->isa('Bio::Map::RelativeI');
192 $self->{_relative_not_implicit} = 1;
193 $self->{_relative} = $relative;
195 return $self->{_relative} || $self->absolute_relative;
198 =head2 absolute
200 Title : absolute
201 Usage : my $absolute = $position->absolute();
202 $position->absolute($absolute);
203 Function: Get/set how this Position's co-ordinates (numerical(), start(),
204 end()) are reported. When absolute is off, co-ordinates are
205 relative to the thing described by relative(). Ie. the value
206 returned by start() will be the same as the value you set start()
207 to. When absolute is on, co-ordinates are converted to be relative
208 to the start of the map.
210 So if relative() currently points to a Relative object describing
211 "relative to another position which is 100 bp from the start of
212 the map", this Position's start() had been set to 50 and absolute()
213 returns 1, $position->start() will return 150. If absolute() returns
214 0 in the same situation, $position->start() would return 50.
216 Returns : boolean (default 0)
217 Args : none to get, OR
218 boolean to set
220 =cut
222 sub absolute {
223 my $self = shift;
224 if (@_) { $self->{_absolute} = shift }
225 return $self->{_absolute} || 0;
228 =head2 value
230 Title : value
231 Usage : my $pos = $position->value;
232 Function: Get/Set the value for this postion
233 Returns : scalar, value
234 Args : [optional] new value to set
236 =cut
238 sub value {
239 my ($self, $value) = @_;
240 if (defined $value) {
241 $self->{'_value'} = $value;
242 $self->start($self->numeric) unless defined($self->start);
243 $self->end($self->numeric) unless defined($self->end);
245 return $self->{'_value'};
248 =head2 numeric
250 Title : numeric
251 Usage : my $num = $position->numeric;
252 Function: Read-only method that is guaranteed to return a numeric
253 representation of the start of this position.
254 Returns : scalar numeric
255 Args : none to get the co-ordinate normally (see absolute() method), OR
256 Bio::Map::RelativeI to get the co-ordinate converted to be
257 relative to what this Relative describes.
259 =cut
261 sub numeric {
262 my ($self, $value) = @_;
263 my $num = $self->{'_value'};
264 $self->throw("The value has not been set, can't convert to numeric") unless defined($num);
265 $self->throw("This value [$num] is not numeric") unless looks_like_number($num);
267 if (ref($value) && $value->isa('Bio::Map::RelativeI')) {
268 # get the value after co-ordinate conversion
269 my $raw = $num;
270 my ($abs_start, $rel_start) = $self->_relative_handler($value);
271 return $abs_start + $raw - $rel_start;
274 # get the value as per absolute
275 if ($self->{_relative_not_implicit} && $self->absolute) {
276 # this actually returns the start, but should be the same thing...
277 return $self->relative->absolute_conversion($self);
280 return $num;
283 =head2 start
285 Title : start
286 Usage : my $start = $position->start();
287 $position->start($start);
288 Function: Get/set the start co-ordinate of this position.
289 Returns : the start of this position
290 Args : scalar numeric to set, OR
291 none to get the co-ordinate normally (see absolute() method), OR
292 Bio::Map::RelativeI to get the co-ordinate converted to be
293 relative to what this Relative describes.
295 =cut
297 sub start {
298 my ($self, $value) = @_;
299 if (defined $value) {
300 if (ref($value) && $value->isa('Bio::Map::RelativeI')) {
301 # get the value after co-ordinate conversion
302 my $raw = $self->{start};
303 defined $raw || return;
304 my ($abs_start, $rel_start) = $self->_relative_handler($value);
305 return $abs_start + $raw - $rel_start;
307 else {
308 # set the value
309 $self->throw("This is [$value], not a number") unless looks_like_number($value);
310 $self->{start} = $value;
311 $self->value($value) unless defined($self->value);
315 # get the value as per absolute
316 if ($self->{_relative_not_implicit} && $self->absolute) {
317 return $self->relative->absolute_conversion($self);
320 return defined($self->{start}) ? $self->{start} : return;
323 =head2 end
325 Title : end
326 Usage : my $end = $position->end();
327 $position->end($end);
328 Function: Get/set the end co-ordinate of this position.
329 Returns : the end of this position
330 Args : scalar numeric to set, OR
331 none to get the co-ordinate normally (see absolute() method), OR
332 Bio::Map::RelativeI to get the co-ordinate converted to be
333 relative to what this Relative describes.
335 =cut
337 sub end {
338 my ($self, $value) = @_;
339 if (defined $value) {
340 if (ref($value) && $value->isa('Bio::Map::RelativeI')) {
341 # get the value after co-ordinate conversion
342 my $raw = $self->{end};
343 defined $raw || return;
344 my ($abs_start, $rel_start) = $self->_relative_handler($value);
345 return $abs_start + $raw - $rel_start;
347 else {
348 # set the value
349 $self->throw("This value [$value] is not numeric!") unless looks_like_number($value);
350 $self->{end} = $value;
354 # get the value as per absolute
355 if ($self->{_relative_not_implicit} && $self->absolute) {
356 my $raw = $self->{end} || return;
357 my $abs_start = $self->relative->absolute_conversion($self) || return;
358 return $abs_start + ($raw - $self->{start});
361 return defined($self->{end}) ? $self->{end} : return;
364 =head2 length
366 Title : length
367 Usage : $length = $position->length();
368 Function: Get/set the length of this position's range, changing the end() if
369 necessary. Getting and even setting the length will fail if both
370 start() and end() are not already defined.
371 Returns : the length of this range
372 Args : none to get, OR scalar numeric (>0) to set.
374 =cut
376 sub length {
377 my ($self, $length) = @_;
378 if ($length) {
379 $length > 0 || return;
380 my $existing_length = $self->length || return;
381 return $length if $existing_length == $length;
382 $self->end($self->{start} + $length - 1);
385 if (defined($self->start) && defined($self->end)) {
386 return $self->end - $self->start + 1;
388 return;
391 =head2 sortable
393 Title : sortable
394 Usage : my $num = $position->sortable();
395 Function: Read-only method that is guaranteed to return a value suitable
396 for correctly sorting this kind of position amongst other positions
397 of the same kind on the same map. Note that sorting different kinds
398 of position together is unlikely to give sane results.
399 Returns : numeric
400 Args : none
402 =cut
404 sub sortable {
405 my ($self, $given_map) = @_;
406 my $answer = $self->numeric($self->absolute_relative);
407 return $answer;
410 =head2 toString
412 Title : toString
413 Usage : print $position->toString(), "\n";
414 Function: stringifies this range
415 Returns : a string representation of the range of this Position
416 Args : optional Bio::Map::RelativeI to have the co-ordinates reported
417 relative to the thing described by that Relative
419 =cut
421 sub toString {
422 my ($self, $rel) = @_;
423 if (defined($self->start) && defined($self->end)) {
424 return $self->start($rel).'..'.$self->end($rel);
426 return '';
429 =head2 absolute_relative
431 Title : absolute_relative
432 Usage : my $rel = $position->absolute_relative();
433 Function: Get a relative describing the start of the map. This is useful for
434 supplying to the coordinate methods (start(), end() etc.) to get
435 the temporary effect of having set absolute(1).
436 Returns : Bio::Map::Relative
437 Args : none
439 =cut
441 sub absolute_relative {
442 return Bio::Map::Relative->new(-map => 0, -description => 'start of map');
445 # get our own absolute start and that of the thing we want as a frame of
446 # reference
447 sub _relative_handler {
448 my ($self, $value) = @_;
450 my $own_relative = $self->relative;
452 # if the requested relative position is the same as the actual
453 # relative, the current co-ordinate values are correct so shortcut
454 my ($own_type, $req_type) = ($own_relative->type, $value->type);
455 if ($own_type && $req_type && $own_type eq $req_type && $own_relative->$own_type eq $value->$req_type) {
456 return (0, 0);
459 my $abs_start = $own_relative->absolute_conversion($self);
460 my $rel_start = $value->absolute_conversion($self);
461 $self->throw("Unable to resolve co-ordinate because relative to something that ultimately isn't relative to the map start")
462 unless defined($abs_start) && defined($rel_start);
464 return ($abs_start, $rel_start);