File Coverage

blib/lib/Gpx/Addons/Filter.pm
Criterion Covered Total %
statement 38 113 33.6
branch 3 52 5.7
condition 4 36 11.1
subroutine 10 13 76.9
pod 4 4 100.0
total 59 218 27.0


line stmt bran cond sub pod time code
1             package Gpx::Addons::Filter;
2             #use 5.010_000; use 5.10.0; # perl 5.10, revision 5 version 10 subversion 0
3 5     5   220536 use 5.008_000; use 5.8.0;
  5     5   20  
  5         186  
  5         47  
  5         15  
  5         165  
4 5     5   25 use warnings;
  5         12  
  5         153  
5 5     5   24 use strict;
  5         8  
  5         235  
6 5     5   28 use Carp;
  5         11  
  5         594  
7              
8             # Debugging
9             #use Smart::Comments '###';
10             our $DEBUG = 0;
11              
12             =pod
13              
14             =head1 NAME
15              
16             Gpx::Addons::Filter - filter Geo::Gpx-data based on time-boundaries
17              
18             =head1 VERSION
19              
20             Version 0.03
21              
22             =cut
23              
24             our $VERSION = '0.04';
25              
26             =pod
27              
28             =head1 SYNOPSIS
29              
30             The core-function of this module is B which returns all track-segments of a Geo::Gpx-datastructure,
31             with timestamps in a given time-period.
32              
33             use Geo::Gpx;
34             use Gpx::Addons::Filter;
35            
36             # ... open the filehandle $fh
37             my $gpx = Geo::Gpx->new( input => $fh ); # see documentation of Geo::Gpx for details
38             my $all_tracks = $gpx->tracks();
39            
40             my $selected_tracks = filter_trk($all_tracks, $first_second, $last_second);
41            
42             # create a new gpx-object and fill it with the selcted tracks
43             my $new_gpx = Geo::Gpx->new();
44             $new_gpx->tracks( $selected_tracks );
45              
46             To include waypoints into the export an additional function B is provided.
47              
48             my $bounds = $new_gpx->bounds(); # calculate the boundin-box of the selected tracks
49             my $all_wp = $gpx->waypoins(); # export all waypoints from the original GPX-file
50             my $sel_wp = filter_wp($all_wp, $bounds); # select all waypoints within this box
51             $new_gpx->waypoints( $sel_wp ); # add these wayponts to the new gpx-object
52              
53             =head1 EXPORT
54              
55             =over
56              
57             =item *
58              
59             flter_trk
60              
61             =item *
62              
63             filter_wp
64              
65             =back
66              
67             =cut
68              
69 5     5   26 use base qw(Exporter);
  5         10  
  5         692  
70             our @EXPORT_OK = qw( filter_trk filter_wp first_and_last_second_of );
71              
72             =pod
73              
74             =head1 FUNCTIONS
75              
76             =cut
77              
78             sub filter_trk {
79              
80             =pod
81              
82             =head2 filter_trk
83              
84             This function takes 3 arguments:
85              
86             =over
87              
88             =item 1
89              
90             Reference to a data-structure (tracks) from Geo::Gpx
91              
92             =item 2
93              
94             The first second of the time-frame we want to export (UNIX-Time)
95              
96             =item 3
97              
98             The last second of the time-frame we want to export (UNIX-Time)
99              
100             =back
101              
102             It returns a reference to an array containing all the selected segments.
103             This pointer can be used by the waypoints-method of Geo::Gpx to add them to a new GPX-datastructure.
104             See the examples in SYNOPSIS.
105              
106             =head3 Selection-Logic
107              
108             Segments are never split. If at least one trackpoints creation-time is within the given time-frame,
109             the whole segment is returned. Points with a creation-time equal to one of the frame-boundarys are
110             considered to be inside of the time-frame. Tracks without any segment are not returned.
111              
112             If the second parameter (first-second) is undef, all segments up to the last-second will be returned.
113              
114             If the third parameter (last-second) is undef, all segments after the first-second will be returned.
115              
116             If second and third parameter are undef, all segments will be returned (quiete useless).
117              
118             =head3 Returnvalues and Warnings
119              
120             Returns the arraypointer on success (empty if no segments have matched).
121              
122             The function checks if the timestamp of the last-point in a segment is larger than the one of the fist point.
123             A warning is printed that this segment will be completely ignored. Beside of this, the function assumes,
124             that the trackpoints in the array are in chronological order. As of this version there is no
125             checking, if track-points in the middle of the segment are larger or small than the end-points.
126              
127             =cut
128            
129 5     5   3308 use Data::Dumper;
  5         28556  
  5         3711  
130 0     0 1 0 my $ref = shift; # ref to a Geo::Gpx-exported track-structure
131 0         0 my $start = shift; # start-time
132 0         0 my $end = shift; # end-time
133            
134             # Checks for plausibility
135 0 0       0 if (not defined $ref) {
136 0         0 croak("At least the reference to some data must be passed to this function!\n")
137             }
138            
139 0         0 my @tracks;
140 0         0 foreach my $trk (@{$ref}) {
  0         0  
141 0         0 my $trackname = $trk->{name};
142             ### \$trackname: $trackname
143 0 0       0 print {*STDERR} "Dump of \$trk:\n" . Dumper($trk) . "\n" if $DEBUG > 2;
  0         0  
144 0         0 my $filtered; #TODO: ??? ok - vereinfachen??
145 0         0 SEGMENT: foreach my $seg (@{$trk->{segments}}) {
  0         0  
146 0 0       0 print {*STDERR} "Dump of \$seg:\n" . Dumper($seg) . "\n" if $DEBUG > 1;
  0         0  
147 0         0 my $first_point = $seg->{points}->[0]->{time};
148 0         0 my $last_point = $seg->{points}->[$#{$seg->{points}}]->{time};
  0         0  
149             ##### frame start-time: $start
150             ##### first point in segment: $first_point
151             ##### frame end-time: $end
152             ##### last point in segment: $last_point
153            
154             # Some checks for plausibility
155 0 0       0 if ($first_point > $last_point) {
156 0         0 carp ("The timestamp of the first point is later than the one of the last point. This segment will be ignored.\n");
157 0         0 next SEGMENT;
158             }
159             # TODO: checking, if track-points in the middle of the segment are larger or small than the end-points
160            
161 0 0 0     0 if (defined $start and defined $end) {
    0 0        
    0 0        
162             #### both start and end of timeframe are present - so we want to filter ==> return only segments within time-frame
163            
164             # Plausibilitychecks
165 0 0 0     0 unless ( $start =~ /\d+/ and $end =~ /\d+/ ) {
166 0         0 croak("Start- and end-time must be unix-epoche-seconds!\n")
167             }
168 0 0       0 if ($start > $end) {
169 0         0 croak("hmh - you passed an end-time greater than the start-time - this can not work\n")
170             }
171 0 0 0     0 if ($start < 915148800 or $end < 915148800) {
172 0         0 carp("You are working on track-points dated before Jan 1, 1999 - strange. (Tip: this function accepts epoch-seconds only)")
173             }
174            
175            
176             # Comparing the timeframe with the segments start- and endpoint
177 0 0 0     0 if ( ($first_point < $start) and ($last_point < $start) ) {
    0 0        
178             ##### Segment is completely outside of time-frame (before)
179 0         0 next SEGMENT;
180             } elsif (($first_point > $end) and ($last_point > $end)) {
181             ##### Segment is completely outside of time-frame (after)
182 0         0 next SEGMENT;
183             } else {
184             ##### Segment is inside of timeframe (at least a part of it)
185 0         0 push @{$filtered->{segments}}, $seg;
  0         0  
186             }
187             } elsif (defined $start and not defined $end) {
188             #### only start of timeframe present ==> return all segments with later points
189 0 0       0 if ($last_point >= $start) {
190 0         0 push @{$filtered->{segments}}, $seg;
  0         0  
191             } else {
192 0         0 next SEGMENT;
193             }
194             } elsif (defined $end and not defined $start) {
195             #### only end of timeframe present ==> return all segments with earlier points
196 0 0       0 if ($first_point <= $end ) {
197 0         0 push @{$filtered->{segments}}, $seg;
  0         0  
198             } else {
199 0         0 next SEGMENT;
200             }
201             } else {
202             #### ok, we do NOT want to filter ==> return all segments
203 0         0 push @{$filtered->{segments}}, $seg;
  0         0  
204             }
205             }
206 0 0       0 if ( defined $filtered->{segments} ) {
207             #### Segments within time-frame found => the track is worth getting a name (which we take from the original track-file)
208 0         0 $filtered->{name} = $trk->{name};
209 0 0       0 print STDERR "Dump of \$filtered:\n" . Dumper($filtered) . "\n" if $DEBUG > 2;
210 0         0 push @tracks, $filtered;
211             } else {
212             #### No Segments within time-frame found => delete this empty track
213 0         0 $filtered = undef;
214             }
215             }
216 0         0 return \@tracks;
217             }
218              
219             sub filter_wp {
220              
221             =pod
222              
223             =head2 filter_wp
224              
225             This function takes 3 arguments:
226              
227             =over
228              
229             =item 1
230              
231             Reference to a data-structure (waypoints) from Geo::Gpx
232              
233             =item 2
234              
235             Reference to a bounding-box as created by Geo::Gpx
236              
237             =item 3
238              
239             Tolerance (number) for inclusion of nearby-waypoints (see function within_bounds)
240              
241             =back
242              
243             It returns a pointer to an array containing all waypoints, which are on or within these bounds.
244              
245             This pointer can be used by the waypoints-method of Geo::Gpx to add them to a new GPX-datastructure.
246             See the examples in SYNOPSIS.
247              
248             =cut
249              
250 5     5   38 use Data::Dumper;
  5         35  
  5         1391  
251 0     0 1 0 my $ref = shift; # ref to a Geo::Gpx-exported track-structure
252 0         0 my $box = shift; # ref to bounding-box
253 0         0 my $tolerance = shift;
254 0 0       0 if (not defined $tolerance) {
255 0         0 $tolerance = 0;
256             }
257 0         0 my @filtered;
258 0         0 foreach my $wp (@{$ref}) {
  0         0  
259 0 0       0 if ( within_bounds($wp, $box, $tolerance) ) {
260 0         0 push @filtered, $wp;
261             }
262             }
263 0         0 return \@filtered;
264             }
265              
266             sub within_bounds {
267             =pod
268              
269             =head2 within_bounds
270              
271             This is a helper-function for filter_wp.
272             It returns 1 if a waypoint is on or within the bounds, undef if outside
273              
274             =head3 Expected Parameters
275              
276             =over
277              
278             =item waypoint
279              
280             Pointer to the waypoint-hash.
281              
282             mandatory
283              
284             =item box
285              
286             Pointer to the bounding-box-hash
287              
288             mandatory
289              
290             =item tolerance
291              
292             Tolerance of waypoints (expands the box slightly so that points near the birder still get included ).
293              
294             optional, number
295              
296             =back
297              
298             =cut
299              
300 0     0 1 0 my $wp = shift;
301 0         0 my $box = shift;
302 0 0 0     0 if (not defined $wp or not defined $box) {
303 0         0 croak "Both waypoint and box mus be defined!"
304             }
305 0         0 my $tolerance = shift;
306             #### Waypoint: $wp
307             #### Bounding-box: $box
308 0 0       0 if ( $wp->{lat} <= $box->{maxlat} + $tolerance ) {
309 0 0       0 if ( $wp->{lat} >= $box->{minlat} - $tolerance ) {
310 0 0       0 if ($wp->{lon} <= $box->{maxlon} + $tolerance ) {
311 0 0       0 if ($wp->{lon} >= $box->{minlon} - $tolerance ) {
312 0         0 return 1;
313             }
314             }
315             }
316             }
317 0         0 return;
318             }
319              
320             sub first_and_last_second_of {
321            
322             =pod
323              
324             =head2 first_and_last_second_of
325              
326             Gets one day as string in ISO-Format (yyyy-mm-dd) and
327             returns the first and last second of this day in UNIX-time.
328             Returns undef on error.
329              
330             TODO: Evaluate TZ-Problem
331              
332             =cut
333              
334 5     5   4777 use Time::Local;
  5         9318  
  5         2557  
335 3     3 1 1045 my $date_strg = shift;
336 3 50       23 if ( $date_strg =~ m{ ^ # nothing in front
337             (\d{4}) # year
338             - # seperated by a dash
339             (\d{1,2}) # month (may have only one number)
340             - # seperated by a dash
341             (\d{1,2}) # day (may be one number)
342             $ # nothing after
343             }x
344             ){
345 3         10 my $day = $3;
346 3         11 my $month = $2-1; # see documentation of Time::Local for the reason for these calculations
347              
348             # Plausibility-Checks
349 3 50 33     42 if ( ($day < 1) or ($day > 31) or (($month + 1) < 1) or ($month + 1 > 12) ) {
      33        
      33        
350 0         0 croak 'Did you swap day with month? Valid format is yyyy-mm-dd. Stopped' ;
351             }
352 3         7 my $year = $1;
353             ### $year: $year
354 3         6 $year = $year-1900;
355             ### $year - 1900 (prepared for Time::Local::timegm): $year
356            
357 3 50 33     20 if ( (($year + 1900) < 32) or (($year + 1900) > 2037) ) {
358 0         0 croak 'Years before 32 (Jesus wasn\'t guided by GPS) and after 2037 are not supported. Stopped';
359             }
360            
361             # Calculations (epoche-seconds of 0h and 23:59:59h GMT)
362 3         17 my $day_0h = timegm(0,0,0,$day,$month,$year);
363 3         120 my $day_24h = timegm(59,59,23,$day,$month,$year);
364 3         80 return ($day_0h, $day_24h);
365             } else {
366 0           croak 'Format of date must be yyyy-mm-dd! Stopped';
367             }
368             }
369              
370             1; # End of Gpx::Addons::Filter
371              
372             __END__