Unverified Commit ec599ed2 authored by Birte Kristina Friesel's avatar Birte Kristina Friesel
Browse files

Support polyline uploads

parent f6d9d738
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@ requires 'GIS::Distance';
requires 'GIS::Distance::Fast';
requires 'IO::Socket::Socks', '>= 0.64';
requires 'IO::Socket::SSL',   '>= 2.009';
requires 'JSON';
requires 'JSON::XS';
requires 'List::UtilsBy';
requires 'Locale::Maketext';
requires 'Locale::Maketext::Lexicon';
@@ -25,5 +27,4 @@ requires 'Travel::Status::DE::DBRIS', '>= 0.10';
requires 'Travel::Status::DE::HAFAS', '>= 6.20';
requires 'Travel::Status::DE::IRIS';
requires 'UUID::Tiny';
requires 'JSON';
requires 'JSON::XS';
requires 'XML::LibXML';
+1 −0
Original line number Diff line number Diff line
@@ -3193,6 +3193,7 @@ sub startup {
	$authed_r->post('/account/select_backend')->to('account#change_backend');
	$authed_r->post('/checkin/add')->to('traveling#add_intransit_form');
	$authed_r->post('/journey/add')->to('traveling#add_journey_form');
	$authed_r->post('/polyline/set')->to('traveling#set_polyline');
	$authed_r->post('/journey/comment')->to('traveling#comment_form');
	$authed_r->post('/journey/visibility')->to('traveling#visibility_form');
	$authed_r->post('/journey/edit')->to('traveling#edit_journey');
+185 −1
Original line number Diff line number Diff line
@@ -8,13 +8,15 @@ use Mojo::Base 'Mojolicious::Controller';

use DateTime;
use DateTime::Format::Strptime;
use GIS::Distance;
use List::Util      qw(uniq min max);
use List::UtilsBy   qw(max_by uniq_by);
use List::MoreUtils qw(first_index);
use List::MoreUtils qw(first_index last_index);
use Mojo::UserAgent;
use Mojo::Promise;
use Text::CSV;
use Travel::Status::DE::IRIS::Stations;
use XML::LibXML;

# Internal Helpers

@@ -2565,6 +2567,188 @@ sub edit_journey {
	);
}

sub polyline_add_stops {
	my ( $self, %opt ) = @_;

	my $polyline = $opt{polyline};
	my $route    = $opt{route};

	my $distance = GIS::Distance->new;

	my %min_dist;
	for my $stop ( @{$route} ) {
		for my $polyline_index ( 0 .. $#{$polyline} ) {
			my $pl = $polyline->[$polyline_index];
			my $dist
			  = $distance->distance_metal( $stop->[2]{lat}, $stop->[2]{lon},
				$pl->[1], $pl->[0] );
			if ( not $min_dist{ $stop->[1] }
				or $min_dist{ $stop->[1] }{dist} > $dist )
			{
				$min_dist{ $stop->[1] } = {
					dist  => $dist,
					index => $polyline_index,
				};
			}
		}
	}
	for my $stop ( @{$route} ) {
		if ( $min_dist{ $stop->[1] } ) {
			if ( defined $polyline->[ $min_dist{ $stop->[1] }{index} ][2] ) {
				return sprintf(
					'Error: Stop IDs %d and %d both map to lon %f, lat %f',
					$polyline->[ $min_dist{ $stop->[1] }{index} ][2],
					$stop->[1],
					$polyline->[ $min_dist{ $stop->[1] }{index} ][0],
					$polyline->[ $min_dist{ $stop->[1] }{index} ][1]
				);
			}
			$polyline->[ $min_dist{ $stop->[1] }{index} ][2]
			  = $stop->[1];
		}
	}
	return;
}

sub set_polyline {
	my ($self) = @_;

	if ( $self->validation->csrf_protect->has_error('csrf_token') ) {
		$self->render(
			'bad_request',
			csrf   => 1,
			status => 400
		);
		return;
	}

	my $journey_id = $self->param('id');
	my $uid        = $self->current_user->{id};

	# Ensure that the journey exists and belongs to the user
	my $journey = $self->journeys->get_single(
		uid        => $uid,
		journey_id => $journey_id,
	);

	if ( not $journey ) {
		$self->render(
			'bad_request',
			message => 'Invalid journey ID',
			status  => 400,
		);
		return;
	}

	if ( my $upload = $self->req->upload('file') ) {
		my $root;
		eval {
			$root = XML::LibXML->load_xml( string => $upload->asset->slurp );
		};

		if ($@) {
			$self->render(
				'bad_request',
				message => "Invalid GPX file: Invalid XML: $@",
				status  => 400,
			);
			return;
		}

		my $context = XML::LibXML::XPathContext->new($root);
		$context->registerNs( 'gpx', 'http://www.topografix.com/GPX/1/1' );

		use Data::Dumper;

		my @polyline;
		for my $point (
			$context->findnodes('/gpx:gpx/gpx:trk/gpx:trkseg/gpx:trkpt') )
		{
			push(
				@polyline,
				[
					0.0 + $point->getAttribute('lon'),
					0.0 + $point->getAttribute('lat')
				]
			);
		}

		if ( not @polyline ) {
			$self->render(
				'bad_request',
				message => 'Invalid GPX file: found no track points',
				status  => 400,
			);
			return;
		}

		my @route = @{ $journey->{route} };

		if ( $self->param('upload-partial') ) {
			my $route_start = first_index {
				(
					(
						     $_->[1] and $_->[1] == $journey->{from_eva}
						  or $_->[0] eq $journey->{from_name}
					)
					  and (
						not(   defined $_->[2]{sched_dep}
							or defined $_->[2]{rt_dep} )
						or ( $_->[2]{sched_dep} // $_->[2]{rt_dep} )
						== $journey->{sched_dep_ts}
					  )
				)
			}
			@route;

			my $route_end = last_index {
				(
					(
						     $_->[1] and $_->[1] == $journey->{to_eva}
						  or $_->[0] eq $journey->{to_name}
					)
					  and (
						not(   defined $_->[2]{sched_arr}
							or defined $_->[2]{rt_arr} )
						or ( $_->[2]{sched_arr} // $_->[2]{rt_arr} )
						== $journey->{sched_arr_ts}
					  )
				)
			}
			@route;

			if ( $route_start > -1 and $route_end > -1 ) {
				@route = @route[ $route_start .. $route_end ];
			}
		}

		my $err = $self->polyline_add_stops(
			polyline => \@polyline,
			route    => \@route,
		);

		if ($err) {
			$self->render(
				'bad_request',
				message => $err,
				status  => 400,
			);
			return;
		}

		$self->journeys->set_polyline(
			uid        => $uid,
			journey_id => $journey_id,
			edited     => $journey->{edited},
			polyline   => \@polyline,
			from_eva   => $route[0][1],
			to_eva     => $route[-1][1],
		);
	}

	$self->redirect_to("/journey/${journey_id}");
}

sub add_journey_form {
	my ($self) = @_;

+72 −0
Original line number Diff line number Diff line
@@ -569,6 +569,78 @@ sub pop {
	return $journey;
}

sub set_polyline {
	my ( $self, %opt ) = @_;

	my $uid      = $opt{uid};
	my $db       = $opt{db} // $self->{pg}->db;
	my $polyline = $opt{polyline};

	my $from_eva = $opt{from_eva};
	my $to_eva   = $opt{to_eva};

	my $polyline_str = JSON->new->encode($polyline);

	my $pl_res = $db->select(
		'polylines',
		['id'],
		{
			origin_eva      => $from_eva,
			destination_eva => $to_eva,
			polyline        => $polyline_str,
		},
		{ limit => 1 }
	);

	my $polyline_id;
	if ( my $h = $pl_res->hash ) {
		$polyline_id = $h->{id};
	}
	else {
		$polyline_id = $db->insert(
			'polylines',
			{
				origin_eva      => $from_eva,
				destination_eva => $to_eva,
				polyline        => $polyline_str
			},
			{ returning => 'id' }
		)->hash->{id};
	}
	if ($polyline_id) {
		$self->set_polyline_id(
			uid         => $uid,
			db          => $db,
			polyline_id => $polyline_id,
			journey_id  => $opt{journey_id},
			edited      => $opt{edited},
		);
	}

}

sub set_polyline_id {
	my ( $self, %opt ) = @_;

	my $uid         = $opt{uid};
	my $db          = $opt{db} // $self->{pg}->db;
	my $polyline_id = $opt{polyline_id};
	my $journey_id  = $opt{journey_id};
	my $edited      = $opt{edited};

	$db->update(
		'journeys',
		{
			polyline_id => $polyline_id,
			edited      => $edited | 0x0040
		},
		{
			user_id => $uid,
			id      => $opt{journey_id}
		}
	);
}

sub get {
	my ( $self, %opt ) = @_;

+24 −0
Original line number Diff line number Diff line
@@ -270,6 +270,30 @@ msgstr "Exportieren"
msgid "journey.edit"
msgstr "Bearbeiten"

msgid "journey.map-data"
msgstr "Kartendaten"

msgid "journey.map.download"
msgstr "Herunterladen"

msgid "journey.map.upload"
msgstr "Hochladen"

msgid "journey.map.upload-full"
msgstr "Komplette Route"

msgid "journey.map.upload-partial"
msgstr "Gefahrenes Segment"

msgid "journey.map.info.download"
msgstr "JSON-Format: [[lon, lat, ID], ...] in WGS84-Koordinaten. GPX-Dateien sind mit BRouter kompatibel."

msgid "journey.map.info.upload"
msgstr "GPX-Uploads müssen ein einzelnes track-Element mit einem einzelnen track segment enthalten. Ein BRouter-GPX-Export erfüllt diese Vorgaben. Uploads müssen entweder die komplette Route des Verkehrsmittels oder nur den zu diesem Checkin zugehörigen Abschnitt enthalten. Beim Hochladen bitte die passende Schaltfläche auswählen. IDs von Halten müssen beim Upload nicht angegeben werden. Bitte beachten: Beim Einspielen eigener Kartendaten werden die zuvor gespeicherten unwiderruflich gelöscht."

msgid "journey.danger"
msgstr "Danger Zone"

msgid "journey.delete"
msgstr "Löschen"

Loading