intensityProcessor.pm


#    intensityProcessor
#    Copyright (C) 2022 Cliff Hammett
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
#################### META ###############################################################################
#   Title                   :   intensityProcessor.pm                                                   #
#   Purpose                 :   processes GPS and stimulation data                                      #
#   By                      :   Cliff Hammett                                                           #
#                                                                                                       #
#                                                  #
#################### PREQUISITES ########################################################################
# perl                      :   tested on perl v5.26.1. Should run on other versions!                   #
# GIS::Distance             :   perl GIS library, available on CPAN                                     #
# Math::Trig                :   perl trigonometry library, available on CPAN                            #
#                                                                                                       #
# This module was written for operating on linux systems. It will probably run on Macs. It may need     #
# some modification to run on windows                                                                   #
#                                                                                                       #
#################### USAGE ##############################################################################
# This module is used by peakSoundSplit                                                                 #
#########################################################################################################

package intensityProcessor {
    use strict;
    use warnings;
    use GIS::Distance;
    use Math::Trig;

    sub new{
        my ($class, $rah, $start, $md, $ll) = @_;
        my @poi = ();
        my $this = {
            _gis => GIS::Distance->new(),
            _rah => $rah,
            _p => 0,
            _start => $start,
            _lc => 0,
            _spike => 0,
            _maxDist => $md,
            _legLen => $ll,
            _poi => \@poi
        };
        bless $this, $class;
        return $this;
    }

    sub initRecord{
        my ($this, $i, $l) = @_;
        print "initRecord\n";
        my @loc = ($this->initLocEntry($i));
        my $template = {
            lat => 'tbc',
            lon => 'tbc',
            timeSt => $this->{_rah}->[$i]->{time},
            timeTo => "tbc",
            millisSt => $this->{_rah}->[$i]->{millis},
            millisTo => "tbc",
            intens_ttl => 0,
            intens_avg => 0,
            leg => $l,
            rah_loc => \@loc,
        };
        $this->{_out}->[$this->{_p}] = $template;
        $this->{_lc} = 0;
    }

    sub initLocEntry{
        my ($this, $i) = @_;
        my $rh_loc =  {
            lat => $this->{_rah}->[$i]->{lat},
            lon => $this->{_rah}->[$i]->{lon},
            st =>  $this->{_rah}->[$i]->{millis},
            to => -1
        } ;
        return $rh_loc;
    }

    sub process{    
        my $this = shift;
        $this->initRecord($this->{_start}, 0);
        my $size = @{$this->{_rah}};
        my $pspike = 0;
        my %leg = (
            st => $this->{_rah}->[$this->{_start}]->{millis},
            no => 0
        ); 
        for (my $i=$this->{_start}+1; $i<$size-1; $i++){
            my $r = $this->{_rah}->[$i];
            my $o = $this->{_out}->[$this->{_p}];
            print "$r->{spike} vs $pspike\n";
            if ($pspike != $r->{spike}){ #check if spike state has changed
                print "peak change detected\n";
                $o->{intens_ttl} += $r->{spike};
            }
            $pspike = $r->{spike};
            print "looking at $this->{_p}, point $this->{_lc}\n";
            my $rl = $o->{rah_loc}->[$this->{_lc}];
            if ($r->{lat} != $rl->{lat} || $r->{lon} != $rl->{lon}){ # has location changed? 
                my $rp = $this->{_rah}->[$i-1];
                $rl->{to} = $rp->{millis};
                my $rs = $o->{rah_loc}->[0];
                my $dist = $this->getDistance($r,$rs);
                print "$dist\n";
                my $maxDist = $this->{_maxDist} - (sqrt($o->{intens_ttl})*5+($o->{intens_ttl}/6)); #experimental difference variance on stimulation
                if($dist > $maxDist){ #is this more than the max distance for a geopoint?
                    $this->completeOutPoint($i);
                    $this->legAndPoiCheck($rp, \%leg);
                    $this->initRecord($i, $leg{no});
                }else{
                    push @{$o->{rah_loc}}, $this->initLocEntry($i);
                    $this->{_lc} = @{$o->{rah_loc}}-1;
                }
            }
        }
        $this->completeOutPoint($size-1, \%leg);
        $this->processPointsOfInterest($leg{st}, $this->{_rah}->[$size-1]->{millis});
        return $this->{_out};
    }

    sub completeOutPoint{
        my ($this, $i) = @_;
        print "distance exceeded\n";
        my $rp = $this->{_rah}->[$i-1];
        my $o = $this->{_out}->[$this->{_p}];
        $o->{timeTo} = $rp->{time};
        $o->{millisTo} = $rp->{millis};
        my $hv = ($o->{intens_ttl} * 50000) / ($o->{millisTo} - $o->{millisSt});
        $o->{intens_avg} = int(sqrt($hv+$o->{intens_ttl}));
        $this->avgGPS;
        $this->{_p}++;
    }    

    sub legAndPoiCheck{
        my ($this, $rp, $leg) = @_;
        my $rtn;
        if ($rp->{millis} - $leg->{st} > $this->{_legLen}){#have we had the alloted time for this leg?
            $this->processPointsOfInterest($leg->{st}, $rp->{millis});
            $leg->{no}++;
            $leg->{st} = $rp->{millis};
            $rtn = 1;
        }else{
            $rtn = 0;
        }
        return $rtn
    }

    sub processPointsOfInterest{
        my ($this, $milSt, $milEnd) = @_;
        my $size = @{$this->{_rah}};       
        my $poiState = 0;  
        my @poiSet;
        my %mon = (lat => 0,
                   lon => 0,      
                   millis => 0);  
        for (my $i=0; $i+1<$size && $this->{_rah}->[$i]->{millis} < $milEnd; $i++){                                                                                                                      
            my $r = $this->{_rah}->[$i];   
            if ($r->{millis} > $milSt){        
                if ($mon{lat} != $r->{lat} || $mon{lon} != $r->{lon}){
                    my $psize = @poiSet;           
                    for (my $k=0; $k < $psize; $k++){  
                        if (!$poiSet[$k]->{millisEnd} || !$poiSet[$k]->{latEnd}){
                            $poiSet[$k]->{millisEnd} = $r->{millis};
                            $poiSet[$k]->{latEnd} = $r->{lat};
                            $poiSet[$k]->{lonEnd} = $r->{lon};                                                                                                                                  
                        }
                    }
                    %mon = (lat => $r->{lat},              
                            lon => $r->{lon},              
                            millis => $r->{millis});                                                                                                                                            
                }
                if ($r->{interest} == 1 && $poiState == 0){
                    $poiState = 1;
                    print "entering point of interest at lat: $r->{lat}, lon: $r->{lon}, millis: $r->{millis}\n";                                                                               
                    my %poi = ( latInterest => $r->{lat},   
                             lonInterest => $r->{lon},      
                             millisInterest => $r->{millis},
                             millisSt => $mon{millis}); 
                    push @poiSet, \%poi;       
                } elsif ($r->{interest} == 0 && $poiState == 1){
                    $poiState = 0;
                }
            }
        }
        push @{$this->{_poi}}, \@poiSet;

    }

    sub getDistance{
        my ($this, $loc1, $loc2,) = @_;
        my $distance = $this->{_gis}->distance( $loc1->{lat}, $loc1->{lon} => $loc2->{lat}, $loc2->{lon} );
        return $distance->meters;
    }

    sub avgGPS{
        my ($this) = @_;
        my $o = $this->{_out}->[$this->{_p}];
        my $rah_co = $o->{rah_loc};
        my $size = @{$rah_co};
        my $totweight = 0;
        my $x = 0;
        my $y = 0;
        my $z = 0;
        for (my $i=0; $i<$size; $i++){
            print "lat $rah_co->[$i]->{lat}  lon  $rah_co->[$i]->{lon}\n";
            my $radlat = $rah_co->[$i]->{lat} * (pi/180);
            my $radlon = $rah_co->[$i]->{lon} * (pi/180);
            #my $w =  $rah_co->[$i]->{to} - $rah_co->[$i]->{st};
            my $w = 1; 
            $totweight += $w;
            $x += (cos($radlat) * cos($radlon)) * $w;
            $y += (cos($radlat) * sin($radlon)) * $w;
            $z += sin($radlat) * $w;
        }
        my $x_avg = $x/$totweight;
        my $y_avg = $y/$totweight;
        my $z_avg = $z/$totweight;
        my $Lon = atan2($y_avg, $x_avg);
        my $Hyp = sqrt(($x_avg * $x_avg) + ($y_avg * $y_avg));
        my $Lat = atan2($z_avg, $Hyp);
        $o->{lat} = $Lat * (180/pi);
        $o->{lon} = $Lon * (180/pi);
    }
}
1;