Travel-Status-MOTIS/lib/Travel/Status/MOTIS/Trip.pm
package Travel::Status::MOTIS::Trip;
use strict;
use warnings;
use 5.020;
use parent 'Class::Accessor';
use DateTime::Format::ISO8601;
use Travel::Status::MOTIS::Stop;
use Travel::Status::MOTIS::Polyline qw(decode_polyline);
our $VERSION = '0.03';
Travel::Status::MOTIS::Trip->mk_ro_accessors(
qw(
id
mode
agency
route_name
route_color
route_text_color
headsign
is_realtime
is_cancelled
arrival
scheduled_arrival
realtime_arrival
departure
scheduled_departure
realtime_departure
)
);
sub new {
my ( $obj, %opt ) = @_;
my $json = $opt{json}{legs}[0];
my $time_zone = $opt{time_zone};
my $ref = {
id => $json->{tripId},
mode => $json->{mode},
agency => $json->{agencyName},
route_name => $json->{routeShortName},
route_color => $json->{routeColor},
route_text_color => $json->{routeTextColor},
headsign => $json->{headsign},
is_cancelled => $json->{cancelled},
is_realtime => $json->{realTime},
raw_stopovers =>
[ $json->{from}, @{ $json->{intermediateStops} }, $json->{to} ],
raw_polyline => $json->{legGeometry},
time_zone => $time_zone,
};
$ref->{scheduled_departure} = DateTime::Format::ISO8601->parse_datetime(
$json->{scheduledStartTime} );
$ref->{scheduled_departure}->set_time_zone( $time_zone );
if ( $json->{realTime} ) {
$ref->{realtime_departure}
= DateTime::Format::ISO8601->parse_datetime( $json->{startTime} );
$ref->{realtime_departure}->set_time_zone( $time_zone );
}
$ref->{departure} = $ref->{realtime_departure}
// $ref->{scheduled_departure};
$ref->{scheduled_arrival}
= DateTime::Format::ISO8601->parse_datetime( $json->{scheduledEndTime} );
$ref->{scheduled_arrival}->set_time_zone( $time_zone );
if ( $json->{realTime} ) {
$ref->{realtime_arrival}
= DateTime::Format::ISO8601->parse_datetime( $json->{endTime} );
$ref->{realtime_arrival}->set_time_zone( $time_zone );
}
$ref->{arrival} = $ref->{realtime_arrival} // $ref->{scheduled_arrival};
bless( $ref, $obj );
return $ref;
}
sub polyline {
my ($self) = @_;
if ( not $self->{raw_polyline} ) {
return;
}
if ( $self->{polyline} ) {
return @{ $self->{polyline} };
}
my $polyline = [ decode_polyline( $self->{raw_polyline} ) ];
my $gis_distance;
eval {
require GIS::Distance;
$gis_distance = GIS::Distance->new;
};
if ($gis_distance) {
my %minimum_distances;
for my $stopover ( $self->stopovers ) {
my $stop = $stopover->stop;
for my $polyline_index ( 0 .. $#{$polyline} ) {
my $coordinate = $polyline->[$polyline_index];
my $distance = $gis_distance->distance_metal(
$stop->{lat}, $stop->{lon},
$coordinate->{lat}, $coordinate->{lon},
);
if ( not $minimum_distances{ $stop->id }
or $minimum_distances{ $stop->id }{distance} > $distance )
{
$minimum_distances{ $stop->id } = {
distance => $distance,
index => $polyline_index,
};
}
}
}
for my $stopover ( $self->stopovers ) {
my $stop = $stopover->stop;
if ( $minimum_distances{ $stop->id } ) {
$polyline->[ $minimum_distances{ $stop->id }{index} ]{stop}
= $stop;
}
}
}
$self->{polyline} = $polyline;
return @{ $self->{polyline} };
}
sub stopovers {
my ($self) = @_;
if ( $self->{stopovers} ) {
return @{ $self->{stopovers} };
}
@{ $self->{stopovers} } = map {
Travel::Status::MOTIS::Stopover->new(
json => $_,
realtime => $self->{is_realtime},
time_zone => $self->{time_zone},
)
} ( @{ $self->{raw_stopovers} // [] } );
return @{ $self->{stopovers} };
}
sub TO_JSON {
my ($self) = @_;
# transform raw_stopovers into stopovers (lazy accessor)
$self->stopovers;
# transform raw_polyline into polyline (lazy accessor)
$self->polyline;
my $ret = { %{$self} };
for my $timestamp_key (
qw(
arrival
scheduled_arrival
realtime_arrival
departure
scheduled_departure
realtime_departure
)
)
{
if ( $ret->{$timestamp_key} ) {
$ret->{$timestamp_key} = $ret->{$timestamp_key}->epoch;
}
}
return $ret;
}
1;