diff --git simutrans/history.txt simutrans/history.txt
index 97a1aa0f6..f83de1d09 100644
--- simutrans/history.txt
+++ simutrans/history.txt
@@ -1,5 +1,6 @@
 	FIX: stations on bridge heads drawn too low
 	ADD: new freight sorting order "via owner": groups goods by owner of their next transfer stop and sort by amount (also change order a little and fixed several inconsitencies: Now all windows show same ordering)
+	ADD: new freight sorting order "line": groups goods by the lines they can take to get to their next transfer stop and sort by amount
 	CHG: Help texts will be first searched in pak directory
 	FIX: could built powerlines under bridges by just building a zero length power bridge starting under a bridge
 	ADD: New simple tool 43 to highlight single ribi ways
diff --git simutrans/text/en/convoiinfo.txt simutrans/text/en/convoiinfo.txt
index 595d0c7bf..6e9d7ee03 100644
--- simutrans/text/en/convoiinfo.txt
+++ simutrans/text/en/convoiinfo.txt
@@ -91,4 +91,5 @@ The option-button (which changes its name with your selection) sorts the list of
  - <em>via (detail):</em> sorts items carried by assigned name of first transfer <a href="station.txt">stop</a>, alphanumerically by ASCII-code order (capital letters before lower case letters).<br>
  - <em>via (amount):</em> sorts items carried by quantity headed to first transfer <a href="station.txt">stop</a>.<br>
  - <em>via (owner):</em> sorts items carried by quantity by the owner of the next <a href="station.txt">stop</a>.<br>
+ - <em>line:</em> for convoys, equivalent to <em>via (amount)</em>.<br>
 </p>
diff --git simutrans/text/en/station.txt simutrans/text/en/station.txt
index 8fbe9dcad..78e6368a0 100644
--- simutrans/text/en/station.txt
+++ simutrans/text/en/station.txt
@@ -59,7 +59,8 @@ Option-button (changes name with selection) sorts of items within groups:<br>
  - <em>amount:</em> sorts items by quantity in descending order.<be>
  - <em>via (detail):</em> sorts items, alphanumerically within ASCII-code order (capital letters before lower case letters), by assigned name of first transfer Stop.<br>
  - <em>via (amount):</em> sorts items by quantity headed to first transfer Stop.<br>
- - <em>via (owner):</em> sorts items carried by quantity by the owner of the next <a href="station.txt">stop</a>.<br>
+ - <em>via (owner):</em> sorts items by quantity by the owner of the next <a href="station.txt">stop</a>.<br>
+ - <em>line:</em> sorts items by quantity, grouping them according to the next line(s) they may take.<br>
 {Tips: goods-colour-bars above a Stop in game-view (use [!] to toggle) indicate quantity of items that wait for transport and are the same colour as <a href="goods_filter.txt">colour-squares</a> in Goods List.}
 </p>
 <p>
diff --git src/simutrans/freight_list_sorter.cc src/simutrans/freight_list_sorter.cc
index 78bf2bed5..4b4f6ac31 100644
--- src/simutrans/freight_list_sorter.cc
+++ src/simutrans/freight_list_sorter.cc
@@ -8,12 +8,16 @@
 #include "freight_list_sorter.h"
 #include "simhalt.h"
 #include "simtypes.h"
+#include "simline.h"
+#include "simconvoi.h"
 #include "simware.h"
 #include "simfab.h"
 #include "simmem.h"
 #include "world/simworld.h"
 #include "player/simplay.h"
 
+#include "dataobj/schedule.h"
+#include "dataobj/schedule_entry.h"
 #include "dataobj/translator.h"
 
 #include "tpl/slist_tpl.h"
@@ -40,7 +44,8 @@ static const char* sort_text[freight_list_sorter_t::SORT_MODES] = {
 		"Menge",
 		"via",
 		"via Menge",
-		"via owner"
+		"via owner",
+		"line"
 };
 
 const char *freight_list_sorter_t::get_sort_mode_string(uint8 mode)
@@ -73,6 +78,7 @@ bool freight_list_sorter_t::compare_ware(ware_t const& w1, ware_t const& w2)
 			dbg->error("freight_list_sorter::compare_ware()", "illegal sort mode!");
 			/* FALLTHROUGH */
 
+		case by_line:
 		case by_via_sum:
 		case by_amount: { // sort by ware amount
 			int const order = w2.amount - w1.amount;
@@ -148,7 +154,35 @@ void freight_list_sorter_t::add_ware_heading( cbuffer_t &buf, uint64 sum, uint32
 }
 
 
-void freight_list_sorter_t::sort_freight(vector_tpl<ware_t> const& warray, cbuffer_t& buf, sort_mode_t sort_mode, const slist_tpl<ware_t>* full_list, const char* what_doing)
+// Utility functions for by_line sort mode to avoid repetitions.
+static inline uint16 get_next_halt_id(ware_t const& ware)
+{
+	halthandle_t halt = ware.get_via_halt();
+	if(  !halt.is_bound()  ) {
+		halt = ware.get_target_halt();
+	}
+	return halt.is_bound() ? halt.get_id() : 0;
+}
+
+
+static inline bool is_same_vector(vector_tpl<linehandle_t> const* v1, vector_tpl<linehandle_t> const* v2)
+{
+	if(  v1 == v2  ){
+		return true;
+	}
+	if(  v1==NULL  ||  v2==NULL  ||  v1->get_count()!=v2->get_count()  ) {
+		return false;
+	}
+	for(  uint32 i=0;  i<v1->get_count();  i++) {
+			  if(  (*v1)[i] != (*v2)[i]  ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+
+void freight_list_sorter_t::sort_freight(vector_tpl<ware_t> const& warray, cbuffer_t& buf, sort_mode_t sort_mode, const slist_tpl<ware_t>* full_list, const char* what_doing, haltestelle_t const* where)
 {
 	sortby = sort_mode;
 
@@ -160,13 +194,89 @@ void freight_list_sorter_t::sort_freight(vector_tpl<ware_t> const& warray, cbuff
 	// only created when needed
 	uint64* categories_goods_amount_lost = NULL;
 
+	// For sortby == by_line
+	inthashtable_tpl<uint16, vector_tpl<linehandle_t> > lines_by_halt;
+
+
+	if(  sortby == by_line  &&  where == NULL  ) {
+		sortby = by_via_sum;
+	}
+	if(  sortby == by_line  ) {
+		// Special setup: for each stop connected to the halt, we obtain all
+		// the lines we can use to get there, and store them in lines_by_halt.
+		// Follow the lines and convoys and fill in, for each halt thus encountered,
+		// what lines serve it.
+		const player_t *owner;
+		const schedule_t *schedule;
+		const minivec_tpl<uint8> *goods_catg_index;
+
+		// Note: since this only applies to halts, and halts ask us to sort goods
+		// one type at a time, we can globally ignore lines and convoys that do
+		// not support the type of the first good (if any).
+		const uint8 good_index = warray.empty() ? 255 : warray[0].get_index();
+
+		// Try not to create too much unbound handles if there are lots
+		// of lineless convoys
+		linehandle_t lineless_convoy_line;
+
+		bool is_line = true;
+		uint32 i = 0;
+		while(  is_line  ||  i < where->registered_convoys.get_count()  ) {
+			// Collect the "schedule" and "owner" from line resp. convoy.
+			linehandle_t line;
+			if(  is_line  ) {
+				if(  i >= where->registered_lines.get_count()  ) {
+					// We have looped over all lines.
+					is_line = false;
+					i = 0; // start over for registered lineless convoys
+					continue;
+				}
+				line = where->registered_lines[i];
+				i++;
+				owner = line->get_owner();
+				schedule = line->get_schedule();
+				goods_catg_index = &line->get_goods_catg_index();
+			}
+			else {
+				const convoihandle_t cnv = where->registered_convoys[i];
+				i++;
+				owner = cnv->get_owner();
+				schedule = cnv->get_schedule();
+				goods_catg_index = &cnv->get_goods_catg_index();
+			}
+
+			// Ignore convoys that can't transport our goods
+			if(  !goods_catg_index->is_contained(good_index)  ) {
+				continue;
+			}
+
+			// Add the line to all halts in the schedule
+			for(schedule_entry_t const& schedule_entry : schedule->entries) {
+				const halthandle_t schedule_halt = haltestelle_t::get_halt(schedule_entry.pos, owner);
+				if(  !schedule_halt.is_bound()  ) {
+						  continue;
+				}
+				lines_by_halt.put(schedule_halt.get_id());
+				vector_tpl<linehandle_t>* lines = lines_by_halt.access(schedule_halt.get_id());
+				if(  !is_line  ) {
+					// We use unbound handles to represent lineless convoys.
+					line = lineless_convoy_line;
+				}
+				// Avoid duplicating lines
+				if(  lines->empty()  ||  lines->back()!=line  ) {
+					lines->append(line);
+				}
+			}  /* end  for schedule_entry : schedule->entries */
+		}
+	}  /* end  preliminary work if sort_by==by_line */
+
 	for(ware_t const& ware : warray) {
 		if(  ware.get_desc() == goods_manager_t::none  ||  ware.amount == 0  ) {
 			continue;
 		}
 		wlist[pos] = ware;
 
-		if(  sort_mode == by_via_sum  ) {
+		if(  sortby == by_via_sum  ) {
 			// via sort mode merges packets with a common next stop
 			for(  int i=0;  i<pos;  i++  ) {
 				ware_t& wi = wlist[i];
@@ -187,7 +297,7 @@ void freight_list_sorter_t::sort_freight(vector_tpl<ware_t> const& warray, cbuff
 			}
 		}
 
-		else if(  sort_mode == by_via_owner  ) {
+		else if(  sortby == by_via_owner  ) {
 			// player sort mode merges packets which next stop is owned by the
 			// same player
 			for(  int i=0;  i<pos;  i++  ) {
@@ -206,6 +316,31 @@ void freight_list_sorter_t::sort_freight(vector_tpl<ware_t> const& warray, cbuff
 				}
 			}
 		}
+
+		else if(  sortby == by_line  ) {
+			// line sort mode merges packets which may take the same set of lines
+			const vector_tpl<linehandle_t> *current_ware_lines = lines_by_halt.access(get_next_halt_id(ware));
+			for(  int i=0;  i<pos;  i++  ) {
+				ware_t& wi = wlist[i];
+				const uint16 other_halt_id = get_next_halt_id(wi);
+				// Note: this works since we are guaranteed that, if both vectors
+				// have the same elements, they will be in the same order, since
+				// we filled them line after line.
+				if(  ware.get_index()==wi.get_index() && is_same_vector(current_ware_lines, lines_by_halt.access(other_halt_id))  ) {
+					// Found a packet which can take the same lines as us: merge!
+					ware_t::goods_amount_t const remaining_amount = wi.add_goods(ware.amount);
+					if(  remaining_amount > 0  ) {
+						// reached goods amount limit, have to discard amount and track category totals separatly
+						if(  categories_goods_amount_lost == NULL  ) {
+							categories_goods_amount_lost = new uint64[256](); // this should be tied to a category index limit constant
+						}
+						categories_goods_amount_lost[wi.get_desc()->get_catg_index()]+= remaining_amount;
+					}
+					--pos;
+					break;
+				}
+			}
+		}
 		pos++;
 	}
 
@@ -285,6 +420,38 @@ void freight_list_sorter_t::sort_freight(vector_tpl<ware_t> const& warray, cbuff
 				continue;
 			}
 
+			// special mode: construct the list of lines
+			else if(  sortby == by_line  ) {
+				vector_tpl<linehandle_t>* lines = lines_by_halt.access(get_next_halt_id(ware));
+				// Set the length to 0 if no lines were found to avoid indexing NULL.
+				int n_lines = lines==NULL ? 0 : lines->get_count();
+				// If there are unbound handles, they are the last item of the
+				// vector, so we know we won't get one here.
+				for(  int i=0;  i<n_lines-2;  i++  ) {
+					buf.append((*lines)[i]->get_name());
+					buf.append(", ");
+				}
+				// Same here.
+				if(  n_lines>1  ) {
+					buf.append((*lines)[n_lines-2]->get_name());
+					buf.append(" or ");
+				}
+				// But here, since we use the end of the vector, we may get an
+				// unbound handle -> lineless convoys.
+				if(  n_lines>0  ) {
+					buf.append((*lines)[n_lines-1].is_bound()
+					           ? (*lines)[n_lines-1]->get_name()
+					           : translator::translate("lineless convoys"));
+				}
+				// No lines at the next halt? ;-;
+				// TODO: differentiate between "error in routing" and "no route yet"
+				else {
+					buf.append("Error in routing");
+				}
+				buf.append("\n");
+				continue;
+			}
+
 			// the target name is not correct for the via sort
 			const bool is_factory_going = ( sortby!=by_via_sum  &&  ware.to_factory ); // exclude merged packets
 			if(  sortby!=by_via_sum  ||  via_halt==halt  ) {
diff --git src/simutrans/freight_list_sorter.h src/simutrans/freight_list_sorter.h
index c2c07afcd..009061b87 100644
--- src/simutrans/freight_list_sorter.h
+++ src/simutrans/freight_list_sorter.h
@@ -14,6 +14,7 @@
 template<class T> class slist_tpl;
 template<class T> class vector_tpl;
 class ware_t;
+class haltestelle_t;
 class cbuffer_t;
 class karte_ptr_t;
 
@@ -27,13 +28,14 @@ public:
 		by_via       = 2,
 		by_via_sum   = 3,
 		by_via_owner = 4,
+		by_line      = 5,
 		SORT_MODES
 	};
 
 	// returns the string for the current sort mode (untranslated)
 	static const char *get_sort_mode_string(uint8 mode);
 
-	static void sort_freight(vector_tpl<ware_t> const& warray, cbuffer_t& buf, sort_mode_t sort_mode, const slist_tpl<ware_t>* full_list, const char* what_doing);
+		  static void sort_freight(vector_tpl<ware_t> const& warray, cbuffer_t& buf, sort_mode_t sort_mode, const slist_tpl<ware_t>* full_list, const char* what_doing, haltestelle_t const* where);
 
 private:
 	static karte_ptr_t welt;
diff --git src/simutrans/simconvoi.cc src/simutrans/simconvoi.cc
index 0c18540a2..f42194775 100644
--- src/simutrans/simconvoi.cc
+++ src/simutrans/simconvoi.cc
@@ -2729,7 +2729,7 @@ void convoi_t::get_freight_info(cbuffer_t & buf)
 		}
 
 		// show new info
-		freight_list_sorter_t::sort_freight(total_fracht, buf, (freight_list_sorter_t::sort_mode_t)env_t::default_sortmode, &capacity, "loaded");
+		freight_list_sorter_t::sort_freight(total_fracht, buf, (freight_list_sorter_t::sort_mode_t)env_t::default_sortmode, &capacity, "loaded", NULL);
 	}
 }
 
diff --git src/simutrans/simhalt.cc src/simutrans/simhalt.cc
index 5bd161519..be21d8dd9 100644
--- src/simutrans/simhalt.cc
+++ src/simutrans/simhalt.cc
@@ -2417,7 +2417,7 @@ void haltestelle_t::get_freight_info(cbuffer_t & buf)
 		for(unsigned i=0; i<goods_manager_t::get_max_catg_index(); i++) {
 			const vector_tpl<ware_t> * warray = cargo[i];
 			if(warray) {
-				freight_list_sorter_t::sort_freight(*warray, buf, (freight_list_sorter_t::sort_mode_t)env_t::default_sortmode, NULL, "waiting");
+				freight_list_sorter_t::sort_freight(*warray, buf, (freight_list_sorter_t::sort_mode_t)env_t::default_sortmode, NULL, "waiting", this);
 			}
 		}
 	}
