adding support for range markers such as used for qtls.
[cxgn-corelibs.git] / lib / CXGN / Marker / Location.pm
blobf74b022eb3eb010b97e33dba11c800c39007e35a
2 =head1 NAME
4 CXGN::Marker::Location;
6 =head1 AUTHOR
8 John Binns <zombieite@gmail.com>
10 =head1 DESCRIPTION
12 Location object. It's a very simple match to the marker_location table in the database, but it has a little bit of intelligence too.
14 =cut
16 package CXGN::Marker::Location;
18 use strict;
19 use CXGN::Marker::Tools;
20 use CXGN::DB::Connection;
21 use CXGN::Tools::Text;
22 use Carp;
24 =head2 new
26 my $location=CXGN::Marker::Location->new($dbh,$location_id);
28 Takes a dbh and a location_id and returns an object representing little more than a row in the marker_location table.
30 my $location=CXGN::Marker::Location->new($dbh);
32 Takes a dbh and returns an empty object which can perform an insert into the marker_location table.
34 =cut
36 sub new
38 my $class=shift;
39 my($dbh,$id)=@_;
40 unless(CXGN::DB::Connection::is_valid_dbh($dbh))
42 croak"Invalid DBH";
44 my $self=bless({},$class);
45 $self->{dbh}=$dbh;
46 if($id)
48 my $q=$dbh->prepare
50 select
51 marker_id,
52 location_id,
53 lg_id,
54 lg_name,
55 marker_location.map_version_id,
56 position,
57 confidence_id,
58 confidence_name as confidence,
59 subscript
60 from
61 marker_experiment
62 inner join marker_location using (location_id)
63 inner join linkage_group using (lg_id)
64 inner join marker_confidence using (confidence_id)
65 where
66 location_id=?
67 ');
68 $q->execute($id);
69 my $hr=$q->fetchrow_hashref();
70 while(my($key,$value)=each %$hr)
72 $self->{$key}=$value;
75 return $self;
78 =head2 location_id
80 my $id=$location->location_id();
82 Gets location ID. Cannot set it since it is either retrieved from the database or sent in to the constructor.
84 =cut
86 #this is not a setter, since these ids are assigned by the database
87 sub location_id
89 my $self=shift;
90 return $self->{location_id};
93 =head2 marker_id, lg_name, map_version_id, position, confidence, subscript
95 Getters/setters.
97 =cut
99 sub marker_id
101 my $self=shift;
102 my($value)=@_;
103 if($value)
105 unless($value=~/^\d+$/)
107 croak"Marker ID must be a number, not '$value'";
109 unless(CXGN::Marker::Tools::is_valid_marker_id($self->{dbh},$value))
111 croak"Marker ID '$value' does not exist in the database";
113 $self->{marker_id}=$value;
115 return $self->{marker_id};
118 sub lg_name
120 my $self=shift;
121 my($lg_name)=@_;
122 if($lg_name)
124 unless($self->{map_version_id})
126 croak"You must set this object's map_version_id before throwing around lg_names like that, else how can it know what map_version those lg_names are on?";
128 my $lg_id=CXGN::Marker::Tools::get_lg_id($self->{dbh},$lg_name,$self->{map_version_id});
129 unless($lg_id)
131 croak"Linkage group '$lg_name' does not exist on map_version_id '$self->{map_version_id}'";
133 $self->{lg_id}=$lg_id;
134 $self->{lg_name}=$lg_name;
136 return $self->{lg_name};
140 sub lg_id {
141 my $self = shift;
142 my $lg_id = shift;
143 if ($lg_id) {
144 unless ($self->{map_version_id}) {
145 croak "You must set map_version_id before trying to set lg_id. Thanks!\n";
147 $self->{lg_id}=$lg_id;
149 return $self->{lg_id};
152 sub map_version_id
154 my $self=shift;
155 my($map_version_id)=@_;
156 if($map_version_id)
158 unless($map_version_id=~/^\d+$/)
160 croak"Map version ID must be an integer, not '$map_version_id'";
162 $self->{map_version_id}=$map_version_id;
164 return $self->{map_version_id};
167 sub position
169 my $self=shift;
170 my($position)=@_;
172 if ($self->{position} =~ /\-/) { # if position describes a range, such as a QTL
173 print STDERR "RANGE DETECTED ($self->{position})\n";
174 ($self->{position_north}, $self->{position_south}) = split "-", $self->{position};
175 $self->{position} = ($self->{position_south} - $self->{position_north})/2;
180 if(defined($position))
182 unless(CXGN::Tools::Text::is_number($position))
184 print STDERR "Position must be a floating-point number, not '$position'";
186 $self->{position}=$position;
188 return $self->{position};
191 sub confidence
193 my $self=shift;
194 my($confidence)=@_;
195 if($confidence)
197 my $confidence_id;
198 $confidence_id=CXGN::Marker::Tools::get_marker_confidence_id($self->{dbh},$confidence);
199 unless(defined($confidence_id))
201 croak"Confidence ID not found for confidence '$confidence'";
203 $self->{confidence_id}=$confidence_id;
204 $self->{confidence}=$confidence;
206 return $self->{confidence};
209 sub subscript
211 my $self=shift;
212 my($subscript)=@_;
213 if($subscript)
215 $subscript=uc($subscript);
216 unless($subscript=~/^[ABC]$/)
218 croak"Subscript must be a 'A', 'B', or 'C', not '$subscript'";
220 $self->{subscript}=$subscript;
222 return $self->{subscript};
225 =head2 equals
227 if($location1->equals($location2)){print"Location 1 and 2 are the same.";}
229 Takes another location object and tells you if it is equivalent to the first location object.
231 =cut
233 sub equals
235 my $self=shift;
236 my($other)=@_;
239 $self->{marker_id}==$other->{marker_id}
240 and $self->{lg_id}==$other->{lg_id}
241 and $self->{map_version_id}==$other->{map_version_id}
242 and $self->{position}==$other->{position}
243 and $self->{confidence} eq $other->{confidence}
244 and $self->{subscript} eq $other->{subscript}
247 return 1;
249 return 0;
252 =head2 exists
254 if($location->exists()){print"Location exists in database.";}
256 Returns its location_id if location is already in the database, or undef if not. Mainly used by store_unless_exists.
258 =cut
260 sub exists
262 my $self=shift;
263 unless($self->{marker_id})
265 croak"Cannot test for a location's existence without knowing which marker it goes with--store marker and set experiment's marker ID before storing locations";
267 unless($self->{lg_id})
269 croak"You really should have an lg_id set before testing for a location's existence";
271 unless($self->{map_version_id})
273 croak"You really should have a map_version_id set before testing for a location's existence";
275 unless(defined($self->{position}))
277 croak"You really should have a position set before testing for a location's existence";
279 unless(defined($self->{confidence_id}))
281 croak"You really should have a confidence_id set before testing for a location's existence";
283 if($self->{location_id})
285 #warn"I think it's pretty obvious that this location exists, since it seems to have been loaded from the database, or recently stored to the database--it already has an id of $self->{location_id}";
286 return $self->{location_id};
288 my $dbh=$self->{dbh};
289 my $q;
290 $q=$dbh->prepare
292 select
293 distinct location_id
294 from
295 marker_location
296 inner join marker_experiment using (location_id)
297 where
298 marker_id=?
299 and lg_id=?
300 and marker_location.map_version_id=?
301 and position=?
302 and confidence_id=?
303 and not(subscript is distinct from ?)
305 $q->execute($self->{marker_id},$self->{lg_id},$self->{map_version_id},$self->{position},$self->{confidence_id},$self->{subscript});
306 my %found_location_ids;#a place to keep all location IDs that match for this marker, for use in error checking in a moment
307 my($location_id)=$q->fetchrow_array();
308 if($location_id)#if we found some matching locations for this marker
310 $self->{location_id}=$location_id;#get the ID of the existing row in the database so we know we've already been stored
311 $found_location_ids{$location_id}=1;#make a note of the location ID found
312 while(my($other_location_id)=$q->fetchrow_array())#grab all other location IDs
314 $found_location_ids{$other_location_id}=1;
316 if(keys(%found_location_ids)>1)#if we found more than one matching location ID, then the database data is not how we expect it to be
318 die"Multiple locations found like\n".$self->as_string()."Locations found: ".CXGN::Tools::Text::list_to_string(keys(%found_location_ids));
320 return $self->{location_id};
322 return;
325 =head2 exists_with_any_confidence
327 Checks to see if a location exists, but not knowing its confidence. Used by CAPS loading scripts which know which location
328 the PCR experiment maps to, but they do not know the confidence.
330 $loc->exists_with_any_confidence() or die"Could not find location:\n".$loc->as_string()."in database--load locations first, before running this script";
332 =cut
334 sub exists_with_any_confidence
336 my $self=shift;
337 unless($self->{marker_id})
339 croak"Cannot test for a location's existence without knowing which marker it goes with--store marker and set experiment's marker ID before storing locations";
341 unless($self->{lg_id})
343 croak"You really should have an lg_id set before testing for a location's existence";
345 unless($self->{map_version_id})
347 croak"You really should have a map_version_id set before testing for a location's existence";
349 unless(defined($self->{position}))
351 croak"You really should have a position set before testing for a location's existence";
353 if(defined($self->{confidence_id}))
355 croak"You have a confidence_id set--why not just use the 'exists' function instead?";
357 if($self->{location_id})
359 #warn"I think it's pretty obvious that this location exists, since it seems to have been loaded from the database, or recently stored to the database--it already has an id of $self->{location_id}";
360 return $self->{location_id};
362 my $dbh=$self->{dbh};
363 my $q;
364 $q=$dbh->prepare
366 select
367 distinct location_id
368 from
369 marker_location
370 inner join marker_experiment using (location_id)
371 where
372 marker_id=?
373 and lg_id=?
374 and map_version_id=?
375 and position=?
376 and not(subscript is distinct from ?)
378 $q->execute($self->{marker_id},$self->{lg_id},$self->{map_version_id},$self->{position},$self->{subscript});
379 my %found_location_ids;#a place to keep all location IDs that match for this marker, for use in error checking in a moment
380 my($location_id)=$q->fetchrow_array();
381 if($location_id)#if we found some matching locations for this marker
383 $self->{location_id}=$location_id;#get the ID of the existing row in the database so we know we've already been stored
384 $found_location_ids{$location_id}=1;#make a note of the location ID found
385 while(my($other_location_id)=$q->fetchrow_array())#grab all other location IDs
387 $found_location_ids{$other_location_id}=1;
389 if(keys(%found_location_ids)>1)#if we found more than one matching location ID, then the database data is not how we expect it to be
391 die"Multiple locations found like\n".$self->as_string()."Locations found: ".CXGN::Tools::Text::list_to_string(keys(%found_location_ids));
393 return $self->{location_id};
395 return;
398 =head2 store_unless_exists
400 my $location_id,$existing_location_id,$new_location_id;
401 $location_id=$new_location_id=$location->store_unless_exists();
402 unless($location_id)
404 $location_id=$existing_location_id=$location->location_id();
407 Makes a database insert unless a similar row exists. Returns a location_id ONLY if a new insert was made. If a matching entry was found, location_id is now set, but not returned.
409 =cut
411 sub store_unless_exists
413 my $self=shift;
414 if($self->exists()){return;}
415 unless($self->{lg_id})
417 croak"No lg_id set";
419 unless($self->{map_version_id})
421 croak"No map_version_id set";
423 unless(defined($self->{position}))
425 croak"No position set";
427 unless(defined($self->{confidence_id}))
429 croak"No confidence set";
431 my $dbh=$self->{dbh};
433 my $statement='insert into sgn.marker_location (lg_id,map_version_id,position,confidence_id,subscript, position_north, position_south) values (?,?,?,?,?,?,?)';
434 my @values=($self->{lg_id},$self->{map_version_id},$self->{position},$self->{confidence_id},$self->{subscript}, $self->{position_north}, $self->{position_south});
435 my $q=$dbh->prepare($statement);
436 #print STDERR "$statement; (@values)\n";
437 $q->execute(@values);
438 $self->{location_id}=$dbh->last_insert_id('marker_location') or croak"Can't find last insert id for location ".$self->as_string();
439 return($self->{location_id});
442 =head2 as_string
444 print $location->as_string();
446 Prints a location string for debugging.
448 =cut
450 sub as_string
452 my $self=shift;
453 my $string="<location>\n";
454 $string.="\tmarker_id: '$self->{marker_id}'\tsubscript: '$self->{subscript}'\n";
455 $string.="\tlg_name: '$self->{lg_name}'\tlg_id: '$self->{lg_id}'\tposition: '$self->{position}'\n";
456 $string.="\tlocation_id: '$self->{location_id}'\tmap_version_id: '$self->{map_version_id}'\n";
457 $string.="\tconfidence: '$self->{confidence}'\tconfidence_id: '$self->{confidence_id}'\n";
458 $string.="</location>\n";
459 return $string;