Merge branch 'topic/related_stock_datatables' into topic/trials_from_seedlots
[sgn.git] / lib / CXGN / Location.pm
blob76db4b08f6822d052b021d969be046dc872061b7
2 =head1 NAME
4 CXGN::Location - helper class for locations
6 =head1 SYNOPSYS
8 my $location = CXGN::Location->new( { bcs_schema => $schema } );
9 $location->set_altitude(280);
10 etc.
12 =head1 AUTHOR
14 Bryan Ellerbrock <bje24@cornell.edu>
16 =head1 METHODS
18 =cut
20 package CXGN::Location;
22 use Moose;
23 use Data::Dumper;
24 use Try::Tiny;
25 use SGN::Model::Cvterm;
27 has 'bcs_schema' => (
28 isa => 'Bio::Chado::Schema',
29 is => 'rw',
30 required => 1,
33 has 'location' => (
34 isa => 'Bio::Chado::Schema::Result::NaturalDiversity::NdGeolocation',
35 is => 'rw',
38 has 'nd_geolocation_id' => (
39 isa => 'Maybe[Int]',
40 is => 'rw',
43 has 'name' => (
44 isa => 'Str',
45 is => 'rw',
48 has 'abbreviation' => (
49 isa => 'Maybe[Str]',
50 is => 'rw',
53 has 'country_name' => (
54 isa => 'Maybe[Str]',
55 is => 'rw',
58 has 'country_code' => (
59 isa => 'Maybe[Str]',
60 is => 'rw',
63 has 'breeding_program' => (
64 isa => 'Maybe[Str]',
65 is => 'rw',
68 has 'location_type' => (
69 isa => 'Maybe[Str]',
70 is => 'rw',
73 has 'latitude' => (
74 isa => 'Maybe[Num]',
75 is => 'rw',
78 has 'longitude' => (
79 isa => 'Maybe[Num]',
80 is => 'rw',
83 has 'altitude' => (
84 isa => 'Maybe[Num]',
85 is => 'rw',
88 sub BUILD {
89 my $self = shift;
91 print STDERR "RUNNING BUILD FOR LOCATION.PM...\n";
92 my $location;
93 if ($self->nd_geolocation_id){
94 $location = $self->bcs_schema->resultset("NaturalDiversity::NdGeolocation")->find( { nd_geolocation_id => $self->nd_geolocation_id });
95 $self->location($location);
97 if (defined $location) {
98 $self->location( $self->location || $location );
99 $self->nd_geolocation_id( $self->nd_geolocation_id || $location->nd_geolocation_id );
100 $self->name( $self->name || $location->description );
101 $self->latitude( $self->latitude || $location->latitude );
102 $self->longitude( $self->longitude || $location->longitude );
103 $self->altitude( $self->altitude || $location->altitude );
104 $self->abbreviation( $self->abbreviation || $self->_get_ndgeolocationprop('abbreviation', 'geolocation_property') );
105 $self->country_name( $self->country_name || $self->_get_ndgeolocationprop('country_name', 'geolocation_property') );
106 $self->country_code( $self->country_code || $self->_get_ndgeolocationprop('country_code', 'geolocation_property') );
107 $self->breeding_program( $self->breeding_program || $self->_get_ndgeolocationprop('breeding_program', 'project_property') );
108 $self->location_type( $self->location_type || $self->_get_ndgeolocationprop('location_type', 'geolocation_property') );
111 return $self;
114 sub store_location {
115 my $self = shift;
116 my $schema = $self->bcs_schema();
117 my $error;
119 my $nd_geolocation_id = $self->nd_geolocation_id();
120 my $name = $self->name();
121 my $abbreviation = $self->abbreviation();
122 my $country_name = $self->country_name();
123 my $country_code = $self->country_code();
124 my $breeding_program = $self->breeding_program();
125 my $location_type = $self->location_type();
126 my $latitude = $self->latitude();
127 my $longitude = $self->longitude();
128 my $altitude = $self->altitude();
130 # Validate properties
132 if (!$nd_geolocation_id && !$name) {
133 return { error => "Cannot add a new location with an undefined name. A location name is required" };
135 elsif (!$nd_geolocation_id && !$self->_is_valid_name($name)) { # can't add a new location with name that already exists
136 return { error => "The location - $name - already exists. Please choose another name, or use the existing location" };
139 if ($abbreviation && !$self->_is_valid_abbreviation($abbreviation)) {
140 return { error => "Abbreviation $abbreviation already exists in the database. Please choose another abbreviation" };
143 if ($country_name && $country_name =~ m/[0-9]/) {
144 return { error => "Country name $country_name is not a valid ISO standard country name." };
147 if ($country_code && ($country_code !~ m/^[^a-z]*$/) || (length($country_code) != 3 )) {
148 return { error => "Country code $country_code is not a valid ISO Alpha-3 code." };
151 if ($breeding_program && !$self->_is_valid_program($breeding_program)) { # can't use a breeding program that doesn't exist
152 return { error => "Breeding program $breeding_program doesn't exist in the database." };
155 if ($location_type && !$self->_is_valid_type($location_type)) {
156 return { error => "Location type $location_type must be must be one of the following: Farm, Field, Greenhouse, Screenhouse, Lab, Storage, Other." };
159 if ( ($latitude && $latitude !~ /^-?[0-9.]+$/) || ($latitude && $latitude < -90) || ($latitude && $latitude > 90)) {
160 return { error => "Latitude (in degrees) must be a number between 90 and -90." };
163 if ( ($longitude && $longitude !~ /^-?[0-9.]+$/) || ($longitude && $longitude < -180) || ($longitude && $longitude > 180)) {
164 return { error => "Longitude (in degrees) must be a number between 180 and -180." };
167 if ( ($altitude && $altitude !~ /^-?[0-9.]+$/) || ($altitude && $altitude < -418) || ($altitude && $altitude > 8848) ) {
168 return { error => "Altitude (in meters) must be a number between -418 (Dead Sea) and 8,848 (Mt. Everest)." };
171 # Add new location if no id supplied
172 if (!$nd_geolocation_id) {
173 print STDERR "Checks completed, adding new location $name\n";
174 try {
175 my $new_row = $schema->resultset('NaturalDiversity::NdGeolocation')
176 ->new({
177 description => $name,
180 if ($longitude) { $new_row->longitude($longitude); }
181 if ($latitude) { $new_row->latitude($latitude); }
182 if ($altitude) { $new_row->altitude($altitude); }
183 $new_row->insert();
185 #$self->ndgeolocation_id($new_row->ndgeolocation_id());
186 $self->location($new_row);
188 if ($abbreviation){
189 $self->_store_ndgeolocationprop('abbreviation', 'geolocation_property', $abbreviation);
191 if ($country_name){
192 $self->_store_ndgeolocationprop('country_name', 'geolocation_property', $country_name);
194 if ($country_code){
195 $self->_store_ndgeolocationprop('country_code', 'geolocation_property', $country_code);
197 if ($breeding_program){
198 my $id = $self->bcs_schema->resultset("Project::Project")->search({ name => $breeding_program })->first->project_id();
199 $self->_store_ndgeolocationprop('breeding_program', 'project_property', $id);
201 if ($location_type){
202 $self->_store_ndgeolocationprop('location_type', 'geolocation_property', $location_type);
206 catch {
207 $error = $_;
210 if ($error) {
211 print STDERR "Error creating location $name: $error\n";
212 return { error => $error };
213 } else {
214 print STDERR "Location $name added successfully\n";
215 return { success => "Location $name added successfully\n" };
218 # Edit existing location if id supplied
219 elsif ($nd_geolocation_id) {
220 print STDERR "Checks completed, editing existing location $name\n";
221 try {
222 my $row = $schema->resultset("NaturalDiversity::NdGeolocation")->find({ nd_geolocation_id => $nd_geolocation_id });
223 $row->description($name);
224 $row->latitude($latitude);
225 $row->longitude($longitude);
226 $row->altitude($altitude);
227 $row->update();
229 if ($abbreviation){
230 $self->_store_ndgeolocationprop('abbreviation', 'geolocation_property', $abbreviation);
232 if ($country_name){
233 $self->_store_ndgeolocationprop('country_name', 'geolocation_property', $country_name);
235 if ($country_code){
236 $self->_store_ndgeolocationprop('country_code', 'geolocation_property', $country_code);
238 if ($breeding_program){
239 my $id = $self->bcs_schema->resultset("Project::Project")->search({ name => $breeding_program })->first->project_id();
240 $self->_store_ndgeolocationprop('breeding_program', 'project_property', $id);
242 if ($location_type){
243 $self->_store_ndgeolocationprop('location_type', 'geolocation_property', $location_type);
246 catch {
247 $error = $_;
250 if ($error) {
251 print STDERR "Error editing location $name: $error\n";
252 return { error => $error };
253 } else {
254 print STDERR "Location $name was successfully updated\n";
255 return { success => "Location $name was successfully updated\n" };
260 sub delete_location {
261 my $self = shift;
263 my $row = $self->bcs_schema->resultset("NaturalDiversity::NdGeolocation")->find({ nd_geolocation_id=> $self->nd_geolocation_id() });
264 my $name = $row->description();
265 my @experiments = $row->nd_experiments;
266 #print STDERR "Associated experiments: ".Dumper(@experiments)."\n";
268 if (@experiments) {
269 my $error = "Location $name cannot be deleted because there are ".scalar @experiments." measurements associated with it from at least one trial.\n";
270 print STDERR $error;
271 return { error => $error };
273 else {
274 $row->delete();
275 return { success => "Location $name was successfully deleted.\n" };
279 sub _get_ndgeolocationprop {
280 my $self = shift;
281 my $type = shift;
282 my $cv = shift;
284 my $ndgeolocationprop_type_id = SGN::Model::Cvterm->get_cvterm_row($self->bcs_schema, $type, $cv)->cvterm_id();
285 my $rs = $self->bcs_schema()->resultset("NaturalDiversity::NdGeolocationprop")->search({ nd_geolocation_id=> $self->nd_geolocation_id(), type_id => $ndgeolocationprop_type_id }, { order_by => {-asc => 'nd_geolocationprop_id'} });
287 my @results;
288 while (my $r = $rs->next()){
289 push @results, $r->value;
291 my $res = join ',', @results;
292 return $res;
295 sub _store_ndgeolocationprop {
296 my $self = shift;
297 my $type = shift;
298 my $cv = shift;
299 my $value = shift;
300 #print STDERR " Storing value $value with type $type\n";
301 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->bcs_schema, $type, $cv)->cvterm_id();
302 my $row = $self->bcs_schema()->resultset("NaturalDiversity::NdGeolocationprop")->find( { type_id=>$type_id, nd_geolocation_id=> $self->nd_geolocation_id() } );
304 if (defined $row) {
305 $row->value($value);
306 $row->update();
307 } else {
308 my $stored_ndgeolocationprop = $self->location->create_geolocationprops({ $type => $value}, {cv_name => $cv });
312 sub _remove_ndgeolocationprop {
313 my $self = shift;
314 my $type = shift;
315 my $cv = shift;
316 my $value = shift;
317 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->bcs_schema, $type, $cv)->cvterm_id();
318 my $rs = $self->bcs_schema()->resultset("NaturalDiversity::NdGeolocationprop")->search( { type_id=>$type_id, nd_geolocation_id=> $self->nd_geolocation_id(), value=>$value } );
320 if ($rs->count() == 1) {
321 $rs->first->delete();
322 return 1;
324 elsif ($rs->count() == 0) {
325 return 0;
327 else {
328 print STDERR "Error removing ndgeolocationprop from location ".$self->ndgeolocation_id().". Please check this manually.\n";
329 return 0;
334 sub _is_valid_name {
335 my $self = shift;
336 my $name = shift;
337 my $schema = $self->bcs_schema();
338 my $existing_name_count = $schema->resultset('NaturalDiversity::NdGeolocation')->search( { description => $name } )->count();
339 if ($existing_name_count > 0) {
340 return 0;
342 else {
343 return 1;
347 sub _is_valid_abbreviation {
348 my $self = shift;
349 my $abbreviation = shift;
350 my $schema = $self->bcs_schema();
351 my $existing_abbreviation_count = $schema->resultset('NaturalDiversity::NdGeolocationprop')->search( { value => $abbreviation } )->count();
352 if ($existing_abbreviation_count > 0) {
353 return 0;
355 else {
356 return 1;
360 sub _is_valid_program {
361 my $self = shift;
362 my $program = shift;
363 my $schema = $self->bcs_schema();
364 my $existing_program_count = $schema->resultset('Project::Project')->search(
366 'type.name'=> 'breeding_program',
367 'me.name' => $program
370 join => {
371 'projectprops' =>
372 'type'
375 )->count();
376 if ($existing_program_count < 1) {
377 return 0;
379 else {
380 return 1;
384 sub _is_valid_type {
385 my $self = shift;
386 my $type = shift;
387 my %valid_types = (
388 Farm => 1,
389 Field => 1,
390 Greenhouse => 1,
391 Screenhouse => 1,
392 Lab => 1,
393 Storage => 1,
394 Other => 1
396 if (!$valid_types{$type}) {
397 return 0;
399 else {
400 return 1;