Creating and naming links using start and end nodes from .shp file - netlogo

The attribute table of the .shp file has the following format:
street_name start_node end_node
street_1 A B
street_1 B C
street_2 B D
How could I create links using the start and end nodes and then assign to each link the street name associated with its start and end node. For example, the link with start node A and end node B should get the name "street_1" and the street with start node B and end node D should get the name "street_2".
I used foreach gis:feature-list-of to link the nodes of the dataset but this way I cannot name the links based on their start and end nodes because some of the nodes are shared between street segments.
Many thanks.
Edit:
The columns of the attribute table I am interested in are name1, startNode, and endNode. I have already connected the nodes using the code below and I now have a fully connected road network. I am unsure how I could integrate your code so as the links between the nodes will get the name associated with the combination of nodes that form that link.
foreach gis:feature-list-of roads-dataset [ vector-feature ->
foreach gis:vertex-lists-of vector-feature [ vertex ->
let previous-turtle nobody
foreach vertex [point ->
let location gis:location-of point
if not empty? location
[
let x item 0 location
let y item 1 location
let current-node one-of (turtles-on patch x y) with [ xcor = x and ycor = y ]
if current-node = nobody [
create-nodes 1 [
setxy x y
set size 0.2
set shape "circle"
set color black
set hidden? true
set name gis:property-value vector-feature "name1"
set current-node self
]
]
ask current-node [
if is-turtle? previous-turtle [
create-link-with previous-turtle
]
set previous-turtle self
]
]
]
]
]

Are you saying that your nodes are now correctly named in your model? If that's the case, here's a simplified version of an approach that may work for you. I will note that this is not a very efficient way to go about this as it loops through your links and your attribute table, so if you have many many links it will take a while. To start out with, since I don't have your shapefile, I have made a version of your links example:
extensions [csv]
globals [ whole-file ]
turtles-own [ node ]
links-own [ name ]
to setup
ca
reset-ticks
let names [ "A" "B" "C" "D" ]
let n 0
crt 4 [
setxy random 30 - 15 random 30 - 15
set node item n names
set n n + 1
]
ask turtles with [ node = "A" ] [
create-links-to turtles with [node = "B" ]
]
ask turtles with [ node = "B" ] [
create-links-to turtles with [ node = "C" or node = "D" ]
]
end
That just builds four turtles, with links as indicated in your example shapefile attribute table. I'm using a file called "node_example.csv" that looks like this:
street_name start_node end_node
1 street_1 A B
2 street_1 B C
3 street_2 B D
with four columns, where the first is the observation number.
Essentially, the approach is to iterate through the list and pull out the names of the nodes, from end1 to end2 and vice versa (since both-ends would pull them in a random order), and compare them to each start_node and end_node combination in the table. If they match, assign the street_name from that row to the link with the match:
to link-name
set whole-file csv:from-file "node_example.csv"
foreach sort links [
[ i ] ->
show i
let way-1 list ( [node] of [end1] of i ) ( [node] of [end2] of i )
let way-2 list ( [node] of [end2] of i ) ( [node] of [end1] of i )
foreach whole-file [
[j] ->
if sublist j 2 4 = way-1 or sublist j 2 4 = way-2 [
ask i [
set name item 1 j
]
]
]
]
ask links [
print name
print (word [node] of end1 [node] of end2 )
]
end
Obviously, this is predicated on your nodes being named in your model (in this example, the variable used is node)- if that's not the case this won't work.
Edit 1
Ok, I played around a little using your shapefile. This is not perfect yet, and I can't work on it any more for a while, but maybe it will get you started. Using this setup:
extensions [gis]
breed [ nodes node ]
globals [ roads-dataset ]
turtles-own [ name line-start line-end]
links-own [ lname ]
My idea is to assign the start and end node names to each point along the line feature, so that the links can check against the feature list. More specific notes in comments, but I've basically modified your gis-feature-node code to do that. Play around with it a bit (takes a while to run) and you'll see there are gaps that I haven't quite figured out- maybe you can make progress.
to gis-feature-node
set roads-dataset gis:load-dataset "road_links.shp"
foreach gis:feature-list-of roads-dataset [ vector-feature ->
; First, grab the names of the starting and ending node for the current
; vector feature in order to assign common names to all nodes within
; the feature
let first-vertex gis:property-value vector-feature "startNode"
let last-vertex gis:property-value vector-feature "endNode"
foreach gis:vertex-lists-of vector-feature [ vertex ->
let previous-turtle nobody
foreach vertex [ point ->
let location gis:location-of point
if not empty? location
[
let x item 0 location
let y item 1 location
let current-node one-of (turtles-on patch x y) with [ xcor = x and ycor = y ]
if current-node = nobody [
create-nodes 1 [
setxy x y
set size 0.05
set shape "circle"
set color white
set hidden? false
set name gis:property-value vector-feature "name1"
; Here you assign the first-vertex and last-vertex of the entire line
; to each node
set line-start first-vertex
set line-end last-vertex
set current-node self
]
]
ask current-node [
if is-turtle? previous-turtle [
create-link-with previous-turtle
]
set previous-turtle self
]
]
]
]
]
ask links [
;; Here is a major slowdown- reiterate through the entire roads-dataset
; and, if the names in "startNode" and "endNode" match, assign the
; value from "name1" to the link currently being created.
let way-1 list [line-start] of end1 [line-end] of end2
let way-2 list [line-end] of end1 [line-start] of end2
foreach gis:feature-list-of roads-dataset [ vector-feature-sub ->
let vector-start gis:property-value vector-feature-sub "startNode"
let vector-end gis:property-value vector-feature-sub "endNode"
let start-end list vector-start vector-end
if way-1 = start-end or way-2 = start-end [
set lname gis:property-value vector-feature-sub "name1"
]
]
]
ask links with [ lname = "Hamilton Place" ] [
set color red
set thickness 0.2
]
ask links with [ lname = "Whitcomb Street" ] [
set color yellow
set thickness 0.2
]
end
EDIT 2
The code below is tested and works - problem sorted.
ask links [
set is-road? true
;; Here is a major slowdown- reiterate through the entire roads-dataset
; and, if the names in "startNode" and "endNode" match, assign the
; value from "name1" to the link currently being created.
let way-1 list [line-start] of end1 [line-end] of end2
let way-2 list [line-end] of end1 [line-start] of end2
let way-3 list [ line-start ] of end1 [ line-end ] of end1
let way-4 list [ line-start ] of end2 [ line-end ] of end2
foreach gis:feature-list-of roads-dataset [ vector-feature-sub ->
let vector-start gis:property-value vector-feature-sub "startNode"
let vector-end gis:property-value vector-feature-sub "endNode"
let start-end list vector-start vector-end
let end-start list vector-end vector-start
if way-1 = start-end or way-2 = start-end or way-3 = start-end or way-4 = start-end [
set lname gis:property-value vector-feature-sub "name1"
]
]
]

Related

Netlogo : calling a procedure after opening a file

Here-below is the code for opening a file, reading it and writing it into a list (inspired from another discussion) :
to setup
reset-timer
; first, we load the database file
; We check to make sure the file exists first
ifelse ( file-exists? "AT_data.txt" )
[
; We are saving the data into a list, so it only needs to be loaded once.
set AT-data []
file-open "AT_data.txt"
while [ not file-at-end? ]
[
; file-read gives variables stored in a double list
; Each iteration we append the next three-tuple to the current list: ID AT1 AT2
set AT-data sentence AT-data (list (list file-read file-read file-read))
]
user-message "File loading complete!"
file-close
;; when adding this, the procedure is running endlessly, to be checked
;; ask patches [ assign-data ]
]
[ user-message "There is no AT_data.txt file in current directory!" ]
file-close-all
print timer
end
As I wrote as a comment, when I call the next procedure [assign-data], the procedure [assign-data] runs endlessly. I setup a timer in the [assign-data] procedure and I see that it is running over and over again. When I run [assign-data] on its own, it is working appropriatly, only once.
I tried with a stop after [assign-data] but it is not working.
There must be something that I did not get yet about the use of Netlogo, do you know what it is ?
Here is the code of the assign-dataprocedure (there are 2 choices, the second is running faster)
to assign-farmers1
reset-timer
ask patches with [seed = 1] [
set death last (first (filter [current-inner-list -> (item 0 current-inner-list = ID_farm)] AT-data))
set age item 1 (first (filter [current-inner-list -> (item 0 current-inner-list = ID_farm)] AT-data))
]
print timer
end
to assign-data2
reset-timer
ask patches with [seed = 1] [
let i 1
while [i < length AT-data] [
let current-inner-list item i AT-data
ifelse (ID_farm = item 0 current-inner-list)
[ set age item 1 current-inner-list set death item 2 current-inner-list
stop]
[ set i i + 1 ]
]
]
print timer
end
-> that lead me to another question : how to stop a simulation from running endlessly ? I tried with stop in the command center but it is not working.
Thanks for you time.
HERE IS A REPRODUCIBLE EXAMPLE
(not sure if I should leave the beginning of the question)
AT_data.txt is a file made of 3 col where the first goes from 1 to 100, second and third are just random numbers.
globals [
AT-data
]
patches-own [
ID
AT1
AT2
seed
]
to setup
;; here I just create patches with different values that also appear in the list
ca
ask patches [ set seed random 10 set ID random 100
ifelse (seed = 4)
[ set pcolor orange] [set pcolor white]
]
end
to load
reset-timer
; first, we load the database file
; We check to make sure the file exists first
ifelse ( file-exists? "AT_data.txt" )
[
; We are saving the data into a list, so it only needs to be loaded once.
set AT-data []
file-open "AT_data.txt"
while [ not file-at-end? ]
[
; file-read gives variables stored in a double list
; Each iteration we append the next three-tuple to the current list: ID AT1 AT2
set AT-data sentence AT-data (list (list file-read file-read file-read))
]
user-message "File loading complete!"
file-close
;; when adding this, the procedure is running endlessly, to be checked
ask patches [ assign-data ]
stop
]
[ user-message "There is no AT_data.txt file in current directory!" ]
file-close-all
print timer
end
to assign-data
reset-timer
ask patches with [seed = 4] [
let i 1
while [i < length AT-data] [
let current-inner-list item i AT-data
ifelse (ID = item 0 current-inner-list)
[ set AT1 item 1 current-inner-list set AT2 item 2 current-inner-list
stop]
[ set i i + 1 ]
]
]
print timer
end
Are you sure the run is endless and not exponential? You ask patches to assign-data and in assign-data you use ask patches again. That means that every single patch is checking every single patch and letting the qualified patch go through the loop, which can take a while.

Netlogo: How to make a turtle move towards an unique patch target?

I have turtles (patients), and they can only use only one bed each (white patch). Since patients are randomly generated in a waiting room (green patches), sometimes two or more of them get at the same distance and therefore they find the same patch as its target. I tried to add an attribute to the patch with the purpose of assigning that particular bed to a specific patient. The idea is something like this (please indulge on the ugly code, I'm learning :P):
globals [
waitxmax
waitxmin
waitymax
waitymin
box
]
breed [ patients patient ]
patients-own [ target ]
patches-own [ assigned ]
to setup-wait-room
set waitxmax -15
set waitxmin 15
set waitymax 11
set waitymin 15
ask patches with [
pxcor >= waitxmax and
pxcor <= waitxmin and
pycor >= waitymax and
pycor <= waitymin
] [ set pcolor green ]
end
to setup-beds
let cmy 7
let cmx 15
let dst 3
let nbox 7
ask patch cmx cmy [ set pcolor white ]
let i 1
while [ i < nbox ] [
ask patch (cmx - dst) cmy [ set pcolor white ]
set i i + 1
set cmx cmx - dst
]
ask patches with [ pcolor = white ] [ set assigned false ]
set box patches with [ pcolor = white ]
end
to setup-patients
create-patients start-patients [
set shape "person"
set target nobody
move-to one-of patches with [ pcolor = green ] ]
end
to setup [
clear-all
setup-wait-room
setup-beds
reset-ticks
]
to go
ask patients [ go-to-bed ]
tick
end
to go-to-bed
let _p box with [ self != [ patch-here ] of myself ]
if target = nobody [
set target min-one-of _p [ distance myself ]
ask target [ set assigned myself ]
]
;;; FIXME
if ([ assigned ] of target) != self [ show "not true" ]
if target != nobody [
face target
fd 1
]
end
When I print the two sides of the comparison below FIXME, from the command center I actually get the expected result. For example: both patient 0 and patient 1 have the same target (patch -3 7), but that patch is assigned to (patient 0). I would have expected that comparison to force patient 1 to get a new target since the bed doesn't have his name (I haven't written that code yet), but it always evaluates to true. This is more notorious as more patients I add over available beds (if no beds available, they should wait as soon as one gets free).
When inspecting trough the interface I also see that the patch -3 7 says (patient 0), so I don't know what's happening. Command center example:
observer> show [ assigned ] of patch -3 7
observer: (patient 0)
observer> if ([ assigned ] of patch -3 7) = [self] of patient 0 [ show "true" ]
observer: "true"
observer> if ([ assigned ] of patch -3 7) = [self] of patient 1 [ show "true" ]
;;;; SETUP AND GO
(patient 0): (patch -3 7)
(patient 0): (patient 0)
(patient 0): "true"
(patient 2): (patch 12 7)
(patient 2): (patient 2)
(patient 2): "true"
(patient 1): (patch -3 7)
(patient 1): (patient 1)
(patient 1): "true"
Maybe I'm just overthinking this and there are is a simpler way to assign a bed to a patient and vice versa?
There seems to be a chunk or two missing from your code above (I can't copy-paste and run it), so please have a look at the option below.
This version works by having a single place to store the 'claimed' beds- in the turtle variable. Since the turtle variables can be queried as a list using of, a bed-less turtle can check if there are any beds that are not already present in that list and, if so, claim one.
turtles-own [ owned-bed ]
to setup
ca
ask n-of 5 patches [
set pcolor green
]
crt 10 [
set owned-bed nobody
claim-unclaimed-bed
if owned-bed != nobody [
print word "I own the bed " owned-bed
]
]
reset-ticks
end
to claim-unclaimed-bed
; If I have no bed
if owned-bed = nobody [
; Pull the current owned beds for comparison
let all-owned-beds [owned-bed] of turtles
; Pull those beds that are green AND are not found in 'all-owned-beds'
let available-beds patches with [
pcolor = green and not member? self all-owned-beds
]
; If there are any beds available, claim one
ifelse any? available-beds [
set owned-bed one-of available-beds
] [
; If there are none available, print so
print "There are no available beds."
]
]
end
Edit: Forgot the actual question title- to actually move to their owned-bed (if they have one) in the example above, they simply face it and move how you like- for example:
to go
ask turtles with [ owned-bed != nobody ] [
ifelse distance owned-bed > 1 [
face owned-bed
fd 1
] [
move-to owned-bed
]
]
tick
end
Edit: added complexity
Ok, for an added element of severity, you will likely want to avoid using multiple with [ ... = x statements, and instead to use a primitive called min-one-of which returns the agent with the minimum value of some reporter. Then, you want to tell NetLogo to keep asking the next most severe and the next most severe, etc. One way to do this is with a while loop, which basically says "While this condition is met, continue evaluating the following code." Be careful with while loops- if you forget to write your code such that eventually the condition is no longer true, the while loop will just continue running until you will eventually Tools > Halt your model (or, as has happened to me with a large model, NetLogo crashes).
I've reworked the code (much of what was above is unchanged) to include such a while loop. Note as well that, since the model needs to check which beds are available multiple times, I've made that code into a to-report chunk to condense / simplify.
turtles-own [ owned-bed severity ]
to setup
ca
ask n-of 5 patches [
set pcolor green
]
crt 10 [
set owned-bed nobody
set severity random-float 10
]
let current-available-beds report-available-beds
while [any? current-available-beds] [
; From the turtles with no bed, ask the one with the lowest severity number to
; claim an unclaimed bed. Then, reset the current-available-beds
ask min-one-of ( turtles with [owned-bed = nobody] ) [ severity ] [
claim-unclaimed-bed
if owned-bed != nobody [
show ( word "I have a severity of " severity " so I am claiming the bed " owned-bed )
]
]
set current-available-beds report-available-beds
]
reset-ticks
end
to-report report-available-beds
let all-owned-beds [owned-bed] of turtles
report patches with [
pcolor = green and not member? self all-owned-beds
]
end
to claim-unclaimed-bed
; If I have no bed
if owned-bed = nobody [
let available-beds report-available-beds
; If there are any beds available, claim one
ifelse any? available-beds [
set owned-bed one-of available-beds
] [
; If there are none available, print so
print "There are no available beds."
]
]
end

How to create a table to know which turtles visited each patch in the world?

I would like to remove a doubt and have some help.
I have a closed world of 600X600 patches. Each patch spawns a turtle (using the sprout command). Each turtle makes a series of moves and returns a value for its home patch. I would like to have the following result: know which turtle was in each patch in the world and export this result in table form in .csv
I created a list for this. But, NetLogo is running for a while and then it closes and doesn't finish the model. And so I think if I create a table it should work. The question is: will creating a table solve the problem of the model not running? And if so, how can I create a table by generating an output from that table in .csv? But, I haven't found a NetLogo command that I can create a table to adjust my code to.
Any tip is very welcome. I thank the attention
globals [ edge-size output-turtle-visits ]
patches-own [ turtle-visits ]
to setup
ca
random-seed 1
set edge-size 599
set-patch-size 1.2
resize-world 0 edge-size 0 edge-size
let pcolors []
set pcolors [ 85 95 ]
ask patches [ sprout 1 ]
ask patches [
set turtle-visits n-values count turtles [0]
set pcolor item (random 2) pcolors
]
reset-ticks
end
to go
ask turtles [
rt random 360
fd 1
]
ask patches [
foreach [who] of turtles-here [ id ->
let current-num-visits item id turtle-visits
set turtle-visits replace-item id turtle-visits (current-num-visits + 1)
]
]
end
to output
file-open ( output-turtle-visits )
file-print ( word "id_turtle;my_xcor;my_ycor;turtle_visits" )
foreach sort patches
[
t ->
ask t
[
file-print ( word self " ; " xcor " ; " ycor " ; " turtle-visits )
]
]
file-print "" ;; blank line
file-close
end

How to separate values from a list using commas in netlogo?

Situation: I have a code that exports turtle coordinates according to the code below:
to path
file-open (word fileName ".csv")
file-print (word self xcor " " ycor)
file-close
end
The result is something like:
(turtle 1)[1 1 1 1 1 2] [4 4 4 2 1 5]
Question: How can I export this same list, but with its items separated by commas?
From [1 2 1 1 1] to [1,2,1,1,1], for example.
Thanks in advance
If you are trying to process this in R or something after the fact, I'd recommend potentially reporting in long format (ie, each line indicates a turtle, a tick [or similar], and the coordinates)- I find it simpler to process.
To answer your actual question- one way would be to manually collapse each list of coordinates into a string separated by commas. For example, see the toy model below.
Simple setup:
extensions [csv]
globals [ test ]
turtles-own [ xcor-list ycor-list ]
to setup
ca
crt 10 [
set xcor-list []
set ycor-list []
]
repeat 5 [
ask turtles [
rt random 90 - 45
fd 1
set xcor-list lput pxcor xcor-list
set ycor-list lput pycor ycor-list
]
]
reset-ticks
end
This reporter is what's actually doing the work of collapsing the list into a simple string for output:
to-report collapse-string-list [str-list]
report reduce word ( sentence map [ str -> word str ", " ] but-last str-list last str-list )
end
And this chunk pulls the desired turtle variables into a list of lists, calls the collapse-string-list reporter on them, then exports to a csv:
to output-coord-file
let all-turtles sort turtles
; Pull coordinates from each turtle
let who-coord-list map [
current-turtle ->
(list
[who] of current-turtle
collapse-string-list [xcor-list] of current-turtle
collapse-string-list [ycor-list] of current-turtle
)] all-turtles
; Add headers
set who-coord-list fput ["who" "x" "y"] who-coord-list
; Export
csv:to-file "toy.csv" (map [ row -> (map [i -> (word i)] row ) ] who-coord-list)
end
Output:

I get the following error when I run this code netlogo

my model
of netlogo
globals [ mejor-recorrido
coste-mejor-recorrido ]
breed [ nodos nodo ]
breed [ hormigas hormiga ]
links-own [ coste
feromona ]
hormigas-own [ recorrido
coste-recorrido ]
to setup
__clear-all-and-reset-ticks
set-default-shape nodos "circle"
ask patches [set pcolor white]
crea-nodos
crea-aristas
crea-hormigas
set mejor-recorrido camino-aleatorio
set coste-mejor-recorrido longitud-recorrido mejor-recorrido
end
to crea-nodos
ask n-of num-nodos patches
[
sprout-nodos 1
[
set color blue + 2
set size 2
set label-color black
set label (word "Ciudad-" who)
]
]
end
to crea-aristas
ask nodos
[
create-links-with other nodos
[
; hide-link
set color red
set coste link-length
set feromona random-float 0.1
]
]
ask links [
if(feromona < 0.05) [die] ]
let max-coste max [coste] of links
ask links
[
set coste coste / max-coste
]
end
to crea-hormigas
create-hormigas num-hormigas [
hide-turtle
set recorrido []
set coste-recorrido 0
]
end
to reset
ask hormigas [die]
ask links [
hide-link
set feromona random-float 0.1
]
crea-hormigas
set mejor-recorrido camino-aleatorio
set coste-mejor-recorrido longitud-recorrido mejor-recorrido
clear-all-plots
end
to go
no-display
ask hormigas [
set recorrido generar-recorrido
set coste-recorrido longitud-recorrido recorrido
if coste-recorrido < coste-mejor-recorrido [
set mejor-recorrido recorrido
set coste-mejor-recorrido coste-recorrido
]
]
actualiza-feromona
tick
display
end
to-report camino-aleatorio
let resp [self] of nodos
report lput (first resp) resp
end
to-report generar-recorrido
let origen one-of nodos
let nuevo-recorrido (list origen)
let resto-nodos [self] of nodos with [self != origen]
let nodo-actual origen
while [not empty? resto-nodos] [
if (self = origen) [
ask hormigas [die]
]
let siguiente-nodo elige-siguiente-nodo nodo-actual resto-nodos
set nuevo-recorrido lput siguiente-nodo nuevo-recorrido
set resto-nodos remove siguiente-nodo resto-nodos
set nodo-actual siguiente-nodo
]
set nuevo-recorrido lput origen nuevo-recorrido
report nuevo-recorrido
end
to-report elige-siguiente-nodo [nodo-actual resto-nodos]
let probabilidades calcula-probabilidades nodo-actual resto-nodos
let rand-num random-float 1
report last first filter [first ? >= rand-num] probabilidades
end
to-report calcula-probabilidades [nodo-actual resto-nodos]
let pt map [([feromona] of ? ^ alpha) * ((1 / [coste] of ?) ^ beta)]
(map [arista nodo-actual ?] resto-nodos)
let denominador sum pt
set pt map [? / denominador] pt
let probabilidades sort-by [first ?1 < first ?2]
(map [(list ?1 ?2)] pt resto-nodos)
let probabilidad-normalizada []
let ac 0
foreach probabilidades [
set ac (ac + first ?)
set probabilidad-normalizada lput (list ac last ?) probabilidad-normalizada
]
report probabilidad-normalizada
end
to actualiza-feromona
;; Evapora la feromona del grafo
ask links [
set feromona (feromona * (1 - rho))
]
ask hormigas [
let inc-feromona (100 / coste-recorrido)
foreach aristas-recorrido recorrido [
ask ? [ set feromona (feromona + inc-feromona) ]
]
]
end
to-report aristas-recorrido [nodos-recorrido]
report map [arista (item ? nodos-recorrido)
(item (? + 1) nodos-recorrido)] (n-values num-nodos [?])
end
to-report arista [n1 n2]
report (link [who] of n1 [who] of n2)
end
to-report longitud-recorrido [nodos-recorrido]
report reduce [?1 + ?2] map [[coste] of ?] (aristas-recorrido nodos-recorrido)
end
I added
print aristas-recorrido nodos-recorrido
as the first line after to-report longitud-recorrido [nodos-recorrido]. This shows that aristas-recorrido nodos-recorrido returns a list in which some of the elements are nobody. That's what's processed by one of the lines that is generating the error you mentioned.
I believe that the problem is that in the definition of aristas-recorrido,
to-report aristas-recorrido [nodos-recorrido]
report map [arista (item ? nodos-recorrido)
(item (? + 1) nodos-recorrido)]
(n-values num-nodos [?])
end
arista attempts to report the link between subsequent nodo turtles in the list nodos-recorrido, but some of the pairs of nodos are not linked.
It looks to me like the procedure crea-aristas links every nodo with every other nodo, but then removes links with feromona < 0.05. After that, not all nodos are linked. Since nodos-recorrido above is just a randomly-ordered list of nodos (is this right?), some of the pairs of nodos are not linked, and thus arista returns nobody rather than a link for some pairs. Then this leads to the error in longitud-recorrido.
(I didn't investigate the other line that's generating the error, but I think you'll have enough information to track down that error now.)