Building Map::Tube::<*> maps, a HOWTO: weaving a web

A real tram network is more like a web of interconnecting lines. Although
more lines mean more complexity, they allow
Map::Tube
to better reflect reality
and thus be more useful and interesting.
Last time, we extended the tram network and created a graph of its stations. This time we’re adding a new line to carefully make the network more “real”. At the end, we’ll add colour to the lines so that it’s easier to tell them apart.
Weaving a web
A railway network with only one line is a bit boring, so it’s high time we
added another. Let’s now add Linie 7
from Wettbergen
to
Misburg
.
This line also goes through Hauptbahnhof
, so we can keep that station as a
central node in the network.
The pace might seem slow and that is intentional: we need to add more
complexity to the map and doing so with care and patience will help keep the
complexity under control. I also want to show how the elements of a
Map::Tube
map fit together while also not overwhelming you. My hope is
that a slower pace will help the material to be more easily digestible.
Fortunately, this post is shorter than the others, so even with the slower
pace it won’t take long to get through.
Devising a plan
A little bit of preparation will help us create the new line. Here’s my plan for the list of stations, the IDs and the links between them.
Station | ID | Links |
---|---|---|
Wettbergen | H6 | H7 |
Allerweg | H7 | H6,H3 |
Hauptbahnhof | H3 | H2,H4,H7,H8 |
Vier Grenzen | H8 | H3,H9 |
Misburg | H9 | H8 |
Again, I’ve chosen the westernmost station as the first station on the line.
Also, I’m continuing the ID numbers from where I left off with Linie 1
.
When building the complete network, one would likely continue incrementing
the IDs by labelling stations along Linie 2
, Linie 3
, etc. Yet, this
isn’t so interesting for Hannover and this HOWTO because Linie 2
shares
most of the same stations as Linie 1
. Hence, I decided to extend the
network in our example with Linie 7
.
Note that Hauptbahnhof
retains the ID it had before, but it ends up having
more links because of the extra stations now connected to it.
Driving the changes
How to drive this change with tests? Well, we now expect there to be two
lines. Thus, we can call the get_lines()
method on a
Map::Tube::Hannover
object and we should see two objects returned. We can
also test that a route from Wettbergen
to Misburg
contains the stations
we expect. That sounds like a plan!
There’s a fair bit of work involved, so we’d best get started. Add a check
for the expected number of lines to the t/map-tube-hannover.t
test file
after the validation tests and before the routes tests:
my $num_lines = scalar @{$hannover->get_lines};
is( $num_lines, 2, "Number of lines in network correct" );
Note that the get_lines()
call returns a reference to an array, hence we
have to dereference it to get an array so that we can count its items.
We expect the test to fail and to tell us that there’s currently only one line:
$ prove -lr t/map-tube-hannover.t
t/map-tube-hannover.t .. 1/?
# Failed test 'Number of lines in network correct'
# at t/map-tube-hannover.t line 15.
# got: '1'
# expected: '2'
# Looks like you failed 1 test of 4.
t/map-tube-hannover.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/4 subtests
Test Summary Report
-------------------
t/map-tube-hannover.t (Wstat: 256 (exited 1) Tests: 4 Failed: 1)
Failed test: 3
Non-zero exit status: 1
Files=1, Tests=4, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.55 cusr 0.05 csys = 0.64 CPU)
Result: FAIL
Expectations met! To fix this, we need to a new line to our map file.
Extend the lines
attribute to look like this:
"lines" : {
"line" : [
{
"id" : "L1",
"name" : "Linie 1"
},
{
"id" : "L7",
"name" : "Linie 7"
}
]
},
Running the tests gives:
$ prove -lr t/map-tube-hannover.t
t/map-tube-hannover.t .. # Line id L7 consists of 0 separate components
# Failed test 'Hannover'
# at /home/cochrane/perl5/perlbrew/perls/perl-5.38.3/lib/site_perl/5.38.3/Test/Map/Tube.pm line 196.
# Line id L7 defined but serves no stations (not even as other_link)
# Failed test 'Hannover'
# at /home/cochrane/perl5/perlbrew/perls/perl-5.38.3/lib/site_perl/5.38.3/Test/Map/Tube.pm line 196.
# Looks like you failed 2 tests of 14.
t/map-tube-hannover.t .. 1/?
# Failed test 'ok_map_data'
# at t/map-tube-hannover.t line 11.
get_lines() returns incorrect line entries at t/map-tube-hannover.t line 12.
# Failed test at t/map-tube-hannover.t line 12.
# got: 0
# expected: 1
# Failed test 'Number of lines in network correct'
# at t/map-tube-hannover.t line 15.
# got: '1'
# expected: '2'
# Looks like you failed 3 tests of 4.
t/map-tube-hannover.t .. Dubious, test returned 3 (wstat 768, 0x300)
Failed 3/4 subtests
Test Summary Report
-------------------
t/map-tube-hannover.t (Wstat: 768 (exited 3) Tests: 4 Failed: 3)
Failed tests: 1-3
Non-zero exit status: 3
Files=1, Tests=4, 1 wallclock secs ( 0.03 usr 0.00 sys + 0.50 cusr 0.07 csys = 0.60 CPU)
Result: FAIL
Oh dear, that doesn’t look good.
The main issues here are at the top of the error messages; in particular:
Line id L7 consists of 0 separate components
and
Line id L7 defined but serves no stations (not even as other_link)
These errors are our validation tests telling us that Linie 7
isn’t
connected to anything. This is a good thing because it tells us that our
validation tests are protecting us from any silly mistakes we might make in
the future.
The remaining errors look like follow-on errors from these, so it’s best to focus on the first ones when debugging.
Stitching things together
So, how do we move forward? We could try adding only the Wettbergen
station and see if that helps. But that means it won’t be linked to
anything, which will still raise an error. How about we add the first two
stations on Linie 7
and link them to each other? Let’s give that a go.
Add the following entries after Sarstedt
in the map file:
{
"id" : "H6",
"name" : "Wettbergen",
"line" : "L7",
"link" : "H7"
},
{
"id" : "H7",
"name" : "Allerweg",
"line" : "L7",
"link" : "H6"
}
The test suite now says this:
$ prove -lr t/map-tube-hannover.t
t/map-tube-hannover.t .. # Map has 2 separate components; e.g., stations with ids H1, H6
# Failed test 'Hannover'
# at /home/cochrane/perl5/perlbrew/perls/perl-5.38.3/lib/site_perl/5.38.3/Test/Map/Tube.pm line 196.
# Looks like you failed 1 test of 14.
t/map-tube-hannover.t .. 1/?
# Failed test 'ok_map_data'
# at t/map-tube-hannover.t line 11.
# Looks like you failed 1 test of 4.
t/map-tube-hannover.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/4 subtests
Test Summary Report
-------------------
t/map-tube-hannover.t (Wstat: 256 (exited 1) Tests: 4 Failed: 1)
Failed test: 1
Non-zero exit status: 1
Files=1, Tests=4, 1 wallclock secs ( 0.03 usr 0.01 sys + 0.54 cusr 0.05 csys = 0.63 CPU)
Result: FAIL
The issue here is:
Map has 2 separate components; e.g., stations with ids H1, H6
Wow! The validation tests check everything very thoroughly! That’s impressive!
Making the connection
Ok, so we didn’t get the tests to pass, but things look much better. Let’s
connect Linie 7
to Linie 1
by adding a link to Hauptbahnhof
and see if that
makes things work. Change the entry for Allerweg
to:
{
"id" : "H7",
"name" : "Allerweg",
"line" : "L7",
"link" : "H3,H6"
}
which adds a link to Hauptbahnhof
. Now change the entry for
Hauptbahnhof
to:
{
"id" : "H3",
"name" : "Hauptbahnhof",
"line" : "L1,L7",
"link" : "H2,H4,H7"
},
which links to the Allerweg
station and adds Hauptbahnhof
to Linie 7
,
thus connecting the two lines as expected by our validation tests.
How did we do?
$ prove -lr t/map-tube-hannover.t
t/map-tube-hannover.t .. ok
All tests successful.
Files=1, Tests=4, 1 wallclock secs ( 0.04 usr 0.01 sys + 0.57 cusr 0.02 csys = 0.64 CPU)
Result: PASS
Brilliant! We’ve got passing tests again! I love it when a test suite passes!
Completing the line
We’ve now got the confidence to add the remaining stations for Linie 7
to
the map. Again, we want to drive the change with a test, so we add a test
for a route from Wettbergen
to Misburg
to our test suite like so:
my @routes = (
"Route 1|Langenhagen|Sarstedt|Langenhagen,Kabelkamp,Hauptbahnhof,Laatzen,Sarstedt",
"Route 7|Wettbergen|Misburg|Wettbergen,Allerweg,Hauptbahnhof,Vier Grenzen,Misburg",
);
ok_map_routes($hannover, \@routes);
This test fairly obviously fails because we’ve not yet added all stations on
Linie 7
:
$ prove -lr t/map-tube-hannover.t
t/map-tube-hannover.t .. 1/? Map::Tube::get_node_by_name(): ERROR: Invalid Station Name [Misburg]. (status: 101) file /home/cochrane/perl5/perlbrew/perls/perl-5.38.3/lib/site_perl/5.38.3/Map/Tube.pm on line 897
# Tests were run but no plan was declared and done_testing() was not seen.
# Looks like your test exited with 255 just after 3.
t/map-tube-hannover.t .. Dubious, test returned 255 (wstat 65280, 0xff00)
All 3 subtests passed
Test Summary Report
-------------------
t/map-tube-hannover.t (Wstat: 65280 (exited 255) Tests: 3 Failed: 0)
Non-zero exit status: 255
Parse errors: No plan found in TAP output
Files=1, Tests=3, 0 wallclock secs ( 0.03 usr 0.01 sys + 0.55 cusr 0.05 csys = 0.64 CPU)
Result: FAIL
We expected the tests to fail, so all’s good. The error message also looks
like the kind of error we should be expecting. Now we can extend Linie 7
to the end of the line. Adding these two entries after Allerweg
:
{
"id" : "H8",
"name" : "Vier Grenzen",
"line" : "L7",
"link" : "H3,H9"
},
{
"id" : "H9",
"name" : "Misburg",
"line" : "L7",
"link" : "H8"
}
we find that the tests pass again:
$ prove -lr t/map-tube-hannover.t
t/map-tube-hannover.t .. ok
All tests successful.
Files=1, Tests=4, 1 wallclock secs ( 0.03 usr 0.00 sys + 0.55 cusr 0.06 csys = 0.64 CPU)
Result: PASS
Woohoo! It’s time for another commit!
$ git commit -m "Add Linie 7 to network via Hauptbahnhof" share/hannover-map.json t/map-tube-hannover.t
[main f4c6a97] Add Linie 7 to network via Hauptbahnhof
2 files changed, 35 insertions(+), 3 deletions(-)
Graphing the network
What does our map now look like? Let’s run map2image.pl
and find out:
$ perl bin/map2image.pl
which gives:
If you’re used to the usual way railway networks are visually presented,1 then this output could be a bit confusing. However, if you stare at it, you’ll realise that the stations and how they’re connected do reflect the same connectivity as in the Üstra “Netzplan U” map. The layout is just a bit different, that’s all. The code that automatically generates the graph doesn’t have the same context as the Üstra map does, hence, at first glance, things will look a bit odd.
A splash of colour
We can make the individual lines in the graph stand out a bit more by
assigning colours to them. One sets the colour for a line by setting its
color
attribute (note the spelling without the ‘u’). The colour can be
set either as an RGB hex
triple
or as a name defined in the color-names.txt
file in the Map::Tube
distribution.
Ok, let’s get back to adding colour to our lines. The Üstra “Netzplan U”
uses red for Linie 1
and blue for Linie 7
. Let’s use these colour names
in our map:
"lines" : {
"line" : [
{
"id" : "L1",
"name" : "Linie 1",
"color" : "red"
},
{
"id" : "L7",
"name" : "Linie 7",
"color" : "blue"
}
]
},
Re-running bin/map2image.pl
gives this image:
Now it’s clearer which line is which. Also, it’s clearer that Hauptbahnhof
is connected to both because it kept its black colour.
Let’s commit this change quickly and finish up for today.
$ git commit share/hannover-map.json -m "Add colour to lines in the network
>
> To make the individual lines more obvious in the graphical output, we've
> set the colours of the (currently two) lines to match the colours in the
> official Üstra tram network map."
[main d592035] Add colour to lines in the network
1 file changed, 4 insertions(+), 2 deletions(-)
Wrapping up
A shorter post this time.2 Hopefully, I avoided both over- and under-whelming any readers!
We used test-driven development to add a new line to our example network and then added colour, making it easier to tell the lines apart. Also, we were consistent in our Git usage, ensuring that we committed atomic, logically cohesive changes. We also made sure that each change is a working state of the project, as verified by the test suite.
Next time, we’ll add more lines to the network which, will allow us to plan routes between stations.
Originally posted on https://peateasea.de.
Image credits: Hannover coat of arms: Wikimedia Commons, U-Bahn symbol: Wikimedia Commons, Langenhagen coat of arms: Wikimedia Commons, Sarstedt coat of arms: Wikimedia Commons, Wettbergen coat of arms, Misburg coat of arms
Thumbnail credits: Swiss Cottage Underground Station (Jubilee Line) by Hugh Llewelyn
- The London Underground network is the classic example. [return]
- This sentence, no verb. Or: This sentence intentionally left verbless. [return]
Tags
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub