Weather routing: Difference between revisions
Content added Content deleted
No edit summary |
(julia example) |
||
Line 10: | Line 10: | ||
Given the above information and a specific path, progress and arrival time are determined. The weather routing problem, conversely, is to determine the path which results in the earliest arrival time. |
Given the above information and a specific path, progress and arrival time are determined. The weather routing problem, conversely, is to determine the path which results in the earliest arrival time. |
||
=={{header|Julia}}== |
|||
<lang julia>module SailingPolars |
|||
using DelimitedFiles |
|||
export SailingPolar, SurfaceParameters, getpolardata, deg2rad, rad2deg, cartesian2polar |
|||
export polar2cartesian, haversine, inverse_haversine, boatspeed, bestvectorspeed |
|||
export sailingspeed, sailsegmenttime |
|||
""" |
|||
Structure to represent a polar CSV file's data. |
|||
Contains a matrix, speeds, of sailing speeds indexed by wind velocity and angle of boat to wind |
|||
winds is a list of wind speeds |
|||
degrees is a list of angles in degrees of direction relative to the wind |
|||
Note 0.0 degrees is directly into the wind, 180 degrees is directly downwind. |
|||
""" |
|||
struct SailingPolar |
|||
winds::Vector{Float32} |
|||
degrees::Vector{Float32} |
|||
speeds::Matrix{Float32} # speeds[wind direction degrees, windspeed knots] |
|||
end |
|||
""" |
|||
struct SurfaceParameters |
|||
Structure that represents wind and surface current direction and velocity for a given position |
|||
Angles in degrees, velocities in knots |
|||
""" |
|||
struct SurfaceParameters |
|||
winddeg::Float32 |
|||
windkts::Float32 |
|||
currentdeg::Float32 |
|||
currentkts::Float32 |
|||
end |
|||
""" |
|||
function getpolardata(filename) |
|||
Read a sailing polar CSV file and return a SurfaceParameters struct for the file data. |
|||
A sailing polar file is a CSV file, with ';' used as the comma separator instead of a comma. |
|||
The first line of file contains labels for the wind velocities that make up columns, and |
|||
the first entry of each row makes up a column of angle of sailing direction from wind in degrees |
|||
""" |
|||
function getpolardata(filename) |
|||
datacells, headercells = readdlm(filename, ';', header=true) |
|||
winds = map(x -> parse(Float32, x), headercells[2:end]) |
|||
degrees = datacells[:, 1] |
|||
speeds = datacells[:, 2:end] |
|||
return SailingPolar(winds, degrees, speeds) |
|||
end |
|||
const R = 6372800 # Earth's approximate radius in meters |
|||
""" |
|||
deg2rad(deg) |
|||
Convert degrees to radians |
|||
""" |
|||
deg2rad(deg) = (deg * π / 180.0 + 2π) % 2π |
|||
""" |
|||
rad2deg(rad) |
|||
Convert radians to degrees |
|||
""" |
|||
rad2deg(rad) = (rad * (180.0 / π) + 360.0) % 360.0 |
|||
""" |
|||
cartesian2polard(x, y) |
|||
Convert x, y coordinates to polar coordinates with angle in degrees |
|||
""" |
|||
cartesian2polard(x, y) = sqrt(x * x + y * y), atand(x, y) |
|||
""" |
|||
polard2cartesuan(r, deg) |
|||
Convert polar coordinates in degrees to cartesian x, y coordinates |
|||
""" |
|||
polard2cartesian(r, deg) = r .* sincosd(deg) |
|||
""" |
|||
function haversine(lat1, lon1, lat2, lon2) |
|||
Calculate the haversine function for two points on the Earth's surface. |
|||
Given two latitude, longitude pairs in degrees for a point on the Earth, |
|||
get distance in meters and the initial direction of travel in degrees for |
|||
movement from point 1 to point 2. |
|||
""" |
|||
function haversine(lat1, lon1, lat2, lon2) |
|||
dlat = lat2 - lat1 |
|||
dlon = lon2 - lon1 |
|||
a = sind(dlat / 2)^2 + cosd(lat1) * cosd(lat2) * sind(dlon / 2)^2 |
|||
c = 2.0 * asind(sqrt(a)) |
|||
theta = atand(sind(dlon) * cosd(lat2), |
|||
cosd(lat1) * sind(lat2) - sind(lat1) * cosd(lat2) * cosd(dlon)) |
|||
theta = (theta + 360) % 360 |
|||
return R * c * 0.5399565, theta |
|||
end |
|||
""" |
|||
function inverse_haversine(lat1, lon1, distance, direction) |
|||
Calculate an inverse haversine function. |
|||
Takes the point of origin in degrees (latitude, longitude), distance in meters, and |
|||
initial direction in degrees, and returns the latitude and longitude of the endpoint |
|||
in degrees after traveling the specified distance. |
|||
""" |
|||
function inverse_haversine(lat1, lon1, distance, direction) |
|||
lat2 = asind(sind(lat1) * cos(distance / R) + cosd(lat1) * sin(distance / R) * cosd(direction)) |
|||
lon2 = lon1 + atand(sind(direction) * sin(distance / R) * cosd(lat1), |
|||
cos(distance / R) - sind(lat1) * sind(lat2)) |
|||
return lat2, lon2 |
|||
end |
|||
""" |
|||
function boatspeed(sp::SailingPolar, pointofsail, windspeed) |
|||
Calculate the expected sailing speed in a specified direction in knots, |
|||
given sailing polar data, a desired point of sail in degrees, and wind speed in knots |
|||
""" |
|||
function boatspeed(sp::SailingPolar, pointofsail, windspeed) |
|||
winds, degrees, speeds = sp.winds, sp.degrees, sp.speeds |
|||
udeg = findlast(t -> t <= pointofsail, degrees) |
|||
odeg = findfirst(t -> t >= pointofsail, degrees) |
|||
uvel = findlast(t -> t <= windspeed, winds) |
|||
ovel = findfirst(t -> t >= windspeed, winds) |
|||
if any(t -> t == nothing, [udeg, odeg, uvel, ovel]) |
|||
return -1.0 |
|||
end |
|||
frac = (odeg == udeg && uvel == ovel) ? 1.0 : |
|||
(odeg == udeg) ? (windspeed - winds[uvel]) / (winds[ovel] - winds[uvel]) : |
|||
(uvel == ovel) ? (pointofsail - degrees[udeg]) / (degrees[odeg] - degrees[udeg]) : |
|||
((pointofsail - degrees[udeg]) / (degrees[odeg] - degrees[udeg]) + |
|||
(windspeed - winds[uvel]) / (winds[ovel] - winds[uvel])) / 2 |
|||
return speeds[udeg, uvel] + frac * (speeds[odeg, ovel] - speeds[udeg, uvel]) |
|||
end |
|||
""" |
|||
sailingspeed(sp::SailingPolar, azimuth, dir, ws) |
|||
Calculate the expected net boat speed in a desired direction (azimuth). |
|||
This is generally different from the actual boat speed in its actual direction. |
|||
Directions are in degrees (dir is wind direction), velocity of wind (ws) is in knots. |
|||
""" |
|||
sailingspeed(sp, azimuth, dir, ws) = boatspeed(sp, dir, ws) * cosd(abs(dir - azimuth)) |
|||
""" |
|||
function bestvectorspeed(sp::SailingPolar, dirtravel, dirwind, windspeed, dircur, velcur) |
|||
Calculate the net direction and velocity of a sailing ship. |
|||
Arguments are sailing polar data, direction of travel in degrees from north, wind direction in |
|||
degrees from north, wind velocity in knots, surface current direction in degrees, and |
|||
current velocity in knots. |
|||
""" |
|||
function bestvectorspeed(sp::SailingPolar, dirtravel, dirwind, windspeed, dircur, velcur) |
|||
pointofsail = (dirtravel - dirwind) % 360.0 |
|||
pointofsail = pointofsail < 0 ? pointofsail + 360.0 : pointofsail |
|||
pointofsail = pointofsail > 180.0 ? 360.0 - pointofsail : pointofsail |
|||
VMG = boatspeed(sp, pointofsail, windspeed) |
|||
other, idx = findmax([sailingspeed(sp, pointofsail, x, windspeed) for x in sp.degrees]) |
|||
if other > VMG |
|||
pointofsail = sp.degrees[idx] |
|||
VMG = other |
|||
end |
|||
dirchosen = deg2rad(dirwind + pointofsail) |
|||
wx, wy = VMG * sin(dirchosen), VMG * cos(dirchosen) |
|||
curx, cury = velcur * sin(deg2rad(dircur)), velcur * cos(deg2rad(dircur)) |
|||
return rad2deg(atan(wy + cury, wx + curx)), sqrt((wx + curx)^2 + (wy + cury)^2) |
|||
end |
|||
""" |
|||
function sailsegmenttime(sp::SailingPolar, p::SurfaceParameters, lat1, lon1, lat2, lon2) |
|||
Calculate the trip time in minutes from (lat1, lon1) to the destination (lat2, lon2). |
|||
Uses the data in SurfaceParameters for wind and current velocity and direction. |
|||
""" |
|||
function sailsegmenttime(sp::SailingPolar, p::SurfaceParameters, lat1, lon1, lat2, lon2) |
|||
distance, dir = haversine(lat1, lon1, lat2, lon2) |
|||
dir2, vel = bestvectorspeed(sp, dir, p.winddeg, p.windkts, p.currentdeg, p.currentkts) |
|||
endlat2, endlon2 = inverse_haversine(lat1, lon1, distance, dir2) |
|||
# minutes/s * m / (knots * (m/s / knot)) = minutes |
|||
return (1 / 60) * distance / (vel * 1.94384) |
|||
end |
|||
end # module |
|||
module SailingNavigation |
|||
export Position, lat, lon, GridPoint, TimeSlice, TimedPath, closestpoint, surround |
|||
export RoutingProblem, minimumtimeroute |
|||
using GeometryTypes |
|||
using ..SailingPolars |
|||
# NB: This uses latitude (often considered to be y) first then longitude (often considered to be x). |
|||
# This latitude, then longitude ordering is as per ISO 6709 (en.wikipedia.org/wiki/ISO_6709) |
|||
# Position is a Float32 2-tuple of latitude and longitude in degrees |
|||
Position = Point2f0 |
|||
# latitude from Position |
|||
lat(p::Position) = p[1] |
|||
# longitude from Position |
|||
lon(p::Position) = p[2] |
|||
# A GridPoint is a Position with the SurfaceParameters of wind and current at the Position |
|||
mutable struct GridPoint |
|||
pt::Position |
|||
sp::SurfaceParameters |
|||
end |
|||
""" |
|||
TimeSlice |
|||
A TimeSlice is a matrix of GridPoints, each Position point with their SurfaceParameters |
|||
A Vector of TimeSlice can give the surface characteristics for an ocean region over time. |
|||
""" |
|||
TimeSlice = Matrix{GridPoint} |
|||
""" |
|||
mutable struct RoutingProblem |
|||
timeinterval: the seconds duration for each TimeSlice |
|||
timeframe: a vector of sequntial timeslices for the ocean region |
|||
obstacleindices: the Cartesian indices in each timeslice for |
|||
obstacles, such as land or shoals, where the ship may not go |
|||
startindex: the timeslice position for time of starting |
|||
start: starting location on grid of GridPoints |
|||
finish: destination / finish location on grid of GridPoints |
|||
allowrepeatvisits: whether the vessel may overlap its prior path, usually false |
|||
""" |
|||
mutable struct RoutingProblem |
|||
timeinterval::Float64 # seconds between timeframe slices |
|||
timeframe::Vector{TimeSlice} |
|||
obstacleindices::Vector{Point2{Int}} |
|||
startindex::Int |
|||
start::Point2{Int} |
|||
finish::Point2{Int} |
|||
allowrepeatvisits::Bool |
|||
end |
|||
""" |
|||
mutable struct TimedPath |
|||
duration: minutes total to travel the path |
|||
path: vector of Cartesian indices of points in grid for path to travel |
|||
""" |
|||
mutable struct TimedPath |
|||
duration::Float64 |
|||
path::Vector{Point2{Int}} |
|||
end |
|||
""" |
|||
closestpoint(p, mat) |
|||
Get the closest GridPoint in matrix mat to a given position p. |
|||
p: Cartesian indices of a Position (latitude, longitude in degrees) in grid of GridPoints |
|||
mat: matrix of Gridpoints |
|||
""" |
|||
closestpoint(p, mat) = findmin(gp -> haversine(p[1], p[2], gp.pt[1], gp.pt[2])[1], mat)[2] |
|||
function surround(p, mat, excluded) |
|||
neighbors = Point2{Int}[(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] |
|||
(xmax, ymax) = size(mat) |
|||
return filter(q -> 1 <= q[1] <= xmax && 1 <= q[2] <= ymax && !(q in excluded), |
|||
[x + p for x in neighbors]) |
|||
end |
|||
""" |
|||
function minimumtimeroute(rp::RoutingProblem, sp::SailingPolar, verbose=false) |
|||
Get the route (as a TimedPath) that minimizes time from start to finish for a given |
|||
RoutingProblem (sea parameters) given sailing polar data (ship parameters). |
|||
""" |
|||
function minimumtimeroute(rp::RoutingProblem, sp::SailingPolar, verbose=false) |
|||
timedpaths = [TimedPath(0.0, [rp.start])] |
|||
completed, mintime, minpath = false, 10_000_000.0, TimedPath(10_000_000.0, []) |
|||
for (i, timeframe) in enumerate(rp.timeframe) |
|||
newpaths = TimedPath[] |
|||
verbose && println("Checking ", length(timedpaths), " paths of length ", |
|||
length(timedpaths[1].path) - 1) |
|||
for tpath in timedpaths |
|||
if tpath.path[end] == rp.finish |
|||
completed = true |
|||
push!(newpaths, tpath) |
|||
else |
|||
p1 = tpath.path[end] |
|||
for p2 in surround([p1[1], p1[2]], timeframe, rp.obstacleindices) |
|||
!rp.allowrepeatvisits && p2 in tpath.path && continue |
|||
gp1, gp2 = timeframe[p1[1], p1[2]], timeframe[p2[1], p2[2]] |
|||
lat1, lon1, lat2, lon2 = gp1.pt[1], gp1.pt[2], gp2.pt[1], gp2.pt[2] |
|||
t = sailsegmenttime(sp, gp1.sp, lat1, lon1, lat2, lon2) |
|||
path = deepcopy(tpath.path) |
|||
push!(path, p2) |
|||
push!(newpaths, TimedPath(tpath.duration + t, path)) |
|||
end |
|||
end |
|||
end |
|||
timedpaths = unique(newpaths) |
|||
if completed |
|||
mindur = minimum(map(x -> x.duration, timedpaths)) |
|||
finished = filter(x -> x.path[end] == rp.finish, timedpaths) |
|||
minfindur, idx = findmin(map(x -> x.duration, finished)) |
|||
verbose && println("Current finished minimum: ", finished[idx], ", others $mindur") |
|||
if mindur == minfindur |
|||
minpath = finished[idx] |
|||
break |
|||
end |
|||
end |
|||
end |
|||
return minpath |
|||
end |
|||
end # module |
|||
using GeometryTypes |
|||
using .SailingNavigation, .SailingPolars |
|||
#= |
|||
The data is selected so the best time path is slightly longer than the |
|||
shortest length path. The forbidden regions are x, representing land or reef. |
|||
The allowed sailing points are . and start and finish are S and F. |
|||
x . . F . . x . x |
|||
. . . . . . . x x |
|||
x . . x x x . . . |
|||
. . x x x x . x x |
|||
x . . . x x . x . |
|||
x . . . x x . x . |
|||
. . . . x . . x . |
|||
x . . . . . . x . |
|||
. . . S . . . . . |
|||
=# |
|||
const forbidden = Point2{Int}.([ |
|||
[1, 8], [2, 1], [2, 8], [3, 5], [3, 8], [4, 1], [4, 5], [4, 6], [4, 8], [5, 1], |
|||
[5, 5], [5, 6], [5, 8], [6, 3], [6, 4], [6, 5], [6, 6], [6, 8], [6, 9], [7, 1], |
|||
[7, 4], [7, 5], [7, 6], [8, 8], [8, 9], [9, 1], [9, 7], [9, 9], |
|||
]) |
|||
# Create regional wind patterns on the map. |
|||
function surfacebylongitude(lon) |
|||
return lon < -155.03 ? SurfaceParameters(-5.0, 8, 150, 0.5) : |
|||
lon < -155.99 ? SurfaceParameters(-90.0, 20, 150, 0.4) : |
|||
SurfaceParameters(180.0, 25, 150, 0.3) |
|||
end |
|||
# Vary wind speeds over time. |
|||
function mutatetimeslices!(gridpoints) |
|||
for (i, x) in enumerate(gridpoints) |
|||
x.sp = SurfaceParameters(x.sp.winddeg, x.sp.windkts * (1 + 0.002 * i), |
|||
x.sp.currentdeg, x.sp.currentkts) |
|||
end |
|||
end |
|||
const startpos = Point2{Int}(1, 4) |
|||
const endpos = Point2{Int}(9, 4) |
|||
const pmat = [Position(19.78 - 1/60 + i/60, -155.0 - 5/60 + j/60) for i in 0:8, j in 0:8] |
|||
const gpoints = map(pt -> GridPoint(pt, surfacebylongitude(lon(pt))), pmat) |
|||
mutatetimeslices!(gpoints) |
|||
const routeprob = RoutingProblem(600.0, fill(gpoints, 200), forbidden, 1, startpos, endpos, false) |
|||
const filename = "polar.csv" |
|||
const sp = getpolardata(filename) |
|||
const tp = minimumtimeroute(routeprob, sp) |
|||
println("The route taking the least time found was:\n", tp.path, |
|||
"\nWhich has duration $(div(tp.duration, 60)) hours, $(rem(tp.duration, 60)) minutes.") |
|||
</lang> |
|||
The polar CSV file used for this solution, named polar.csv, is as follows. Note that this is a very detailed |
|||
polar, chosen to stress the testing of the code. Most polar files are far smaller, |
|||
with fewer choices for angle and wind speed. |
|||
<pre style="height:80ex;overflow:scroll;"> |
|||
TWA\TWS;0;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25;26;27;28;29;30;35;40;60;70 |
|||
40;0;0.53;0.54;0.49;0.4;0.31;0.21;0.16;0.11;0.08;0.05;0.03;0.02;0.01;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-0.01;-0.05;-0.1;-0.11 |
|||
41;0;0.61;0.62;0.56;0.47;0.36;0.25;0.19;0.14;0.1;0.07;0.04;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;0;0;0;0;0;-0.04;-0.09;-0.1 |
|||
44;0;0.89;0.91;0.82;0.69;0.56;0.42;0.33;0.24;0.18;0.13;0.08;0.05;0.03;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;0;0;0;-0.02;-0.06;-0.06 |
|||
45;0;0.99;1.02;0.92;0.78;0.64;0.49;0.39;0.29;0.22;0.15;0.1;0.07;0.04;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;0;0;0;-0.01;-0.05;-0.05 |
|||
46;0;1.11;1.14;1.02;0.87;0.73;0.57;0.45;0.35;0.26;0.18;0.13;0.08;0.05;0.03;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;0;0;-0.01;-0.04;-0.05 |
|||
47;0;1.23;1.25;1.14;0.97;0.82;0.66;0.53;0.41;0.31;0.22;0.15;0.1;0.07;0.04;0.02;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;0;-0.01;-0.03;-0.04 |
|||
48;0;1.37;1.37;1.26;1.08;0.93;0.76;0.61;0.48;0.36;0.26;0.19;0.13;0.08;0.05;0.03;0.02;0.01;0.01;0.01;0;0;0;0;0;0;0;0;0;0;-0.02;-0.03 |
|||
49;0;1.5;1.5;1.39;1.2;1.05;0.87;0.71;0.56;0.42;0.31;0.22;0.15;0.1;0.07;0.04;0.03;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;0;-0.02;-0.02 |
|||
50;0;1.65;1.64;1.52;1.33;1.18;1;0.81;0.65;0.49;0.37;0.26;0.19;0.13;0.08;0.05;0.04;0.03;0.02;0.01;0.01;0;0;0;0;0;0;0;0;0;-0.01;-0.02 |
|||
51;0;1.79;1.77;1.67;1.46;1.32;1.13;0.92;0.74;0.57;0.43;0.31;0.22;0.15;0.1;0.07;0.05;0.03;0.02;0.02;0.01;0.01;0;0;0;0;0;0;0;0;-0.01;-0.02 |
|||
53;0;2.1;2.07;1.99;1.76;1.62;1.4;1.14;0.95;0.74;0.57;0.43;0.31;0.22;0.16;0.1;0.08;0.06;0.04;0.03;0.02;0.01;0.01;0.01;0;0;0;0;0;0;-0.01;-0.01 |
|||
54;0;2.26;2.22;2.16;1.92;1.78;1.55;1.28;1.06;0.84;0.65;0.5;0.37;0.27;0.19;0.13;0.1;0.07;0.06;0.04;0.03;0.02;0.01;0.01;0;0;0;0;0;0;0;-0.01 |
|||
55;0;2.43;2.39;2.34;2.09;1.95;1.7;1.42;1.18;0.95;0.74;0.57;0.43;0.32;0.23;0.16;0.12;0.09;0.07;0.05;0.04;0.03;0.02;0.01;0.01;0;0;0;0;0;0;-0.01 |
|||
60;0;3.29;3.33;3.33;3.08;2.93;2.64;2.29;1.98;1.66;1.36;1.1;0.88;0.68;0.53;0.39;0.32;0.26;0.21;0.17;0.13;0.1;0.07;0.05;0.04;0.03;0.02;0.01;0;0;0;0 |
|||
70;0;5.2;5.53;5.74;5.59;5.5;5.22;4.84;4.46;3.94;3.51;3.08;2.65;2.26;1.9;1.55;1.38;1.22;1.06;0.92;0.78;0.66;0.55;0.46;0.37;0.3;0.24;0.18;0.03;0;0;0 |
|||
80;0;6.8;7.43;7.97;8.02;8.23;8.34;8.2;7.9;7.37;6.91;6.43;5.9;5.32;4.72;4.12;3.83;3.55;3.25;2.96;2.67;2.4;2.13;1.88;1.65;1.43;1.22;1.04;0.37;0.09;0.01;0 |
|||
90;0;7.59;8.5;9.4;9.73;10.4;11.16;11.53;11.56;11.3;11.05;10.77;10.44;9.83;9.07;8.34;8;7.65;7.27;6.88;6.46;6.04;5.61;5.15;4.74;4.33;3.88;3.51;1.72;0.67;0.12;0.03 |
|||
100;0;7.34;8.25;9.16;9.86;10.5;11.95;12.79;13.5;14.02;14.4;14.37;14.5;14.4;13.92;13.52;13.19;12.79;12.51;12.1;11.66;11.22;10.77;10.26;9.72;9.2;8.58;8.01;4.87;2.51;0.7;0.23 |
|||
110;0;7.09;7.97;8.84;9.74;10.09;11.85;12.75;13.84;14.99;16.02;16.33;17.1;17.83;17.99;18.32;18.14;17.81;17.84;17.6;17.3;17.05;16.83;16.53;16.03;15.59;15.03;14.37;10.26;6.41;2.32;0.86 |
|||
120;0;6.59;7.42;8.3;9.1;9.56;10.83;11.6;13.1;13.87;14.66;15.75;16.67;17.63;18.43;19.62;20.17;20.6;21.12;21.55;21.75;21.91;22.07;21.9;21.58;21.29;20.92;20.29;16.47;12.03;5.49;2.26 |
|||
129;0;6.14;6.93;7.83;8.52;9.09;9.89;10.57;12.42;12.87;13.43;15.23;16.16;17.08;18.07;19.48;20.35;21.22;21.93;22.85;23.44;23.98;24.55;24.59;24.55;24.51;24.46;24;21.56;17.75;9.64;4.25 |
|||
130;0;6.07;6.87;7.76;8.44;9.02;9.8;10.48;12.29;12.73;13.27;15.08;16.03;16.97;17.96;19.36;20.25;21.15;21.88;22.82;23.44;24.03;24.6;24.66;24.68;24.67;24.64;24.24;22;18.33;10.11;4.5 |
|||
135;0;5.72;6.57;7.36;8.02;8.65;9.38;10.11;11.52;11.97;12.55;13.85;15.31;16.31;17.33;18.54;19.48;20.35;21.28;22.3;23.08;24.09;24.63;24.69;24.78;24.79;24.91;24.82;23.74;20.98;12.39;5.78 |
|||
136;0;5.66;6.5;7.28;7.93;8.57;9.3;10.04;11.34;11.82;12.42;13.62;15.06;16.17;17.2;18.35;19.29;20.15;21.12;22.15;22.96;24.07;24.6;24.67;24.76;24.75;24.85;24.81;23.98;21.45;12.8;6.03 |
|||
139;0;5.42;6.31;6.92;7.67;8.34;9.08;9.86;10.86;11.32;12.03;12.99;14.3;15.73;16.76;17.76;18.71;19.53;20.6;21.66;22.54;23.92;24.44;24.53;24.64;24.58;24.65;24.67;24.47;22.68;13.79;6.73 |
|||
140;0;5.35;6.22;6.79;7.59;8.26;9;9.8;10.72;11.16;11.89;12.79;14.06;15.5;16.62;17.57;18.51;19.32;20.43;21.49;22.4;23.84;24.36;24.46;24.58;24.51;24.57;24.61;24.56;23.02;14.08;6.96 |
|||
141;0;5.29;6.12;6.67;7.48;8.18;8.93;9.74;10.57;11.02;11.77;12.62;13.82;15.26;16.47;17.38;18.32;19.04;20.28;21.31;22.07;23.53;24;24.21;24.29;24.43;24.48;24.55;24.61;23.33;14.31;7.18 |
|||
142;0;5.23;6.02;6.57;7.39;8.1;8.86;9.67;10.43;10.88;11.64;12.45;13.59;15.03;16.24;17.14;18.06;18.77;19.98;21.01;21.75;23.18;23.65;23.86;23.95;24.34;24.39;24.48;24.61;23.61;14.54;7.4 |
|||
143;0;5.16;5.93;6.45;7.3;8;8.78;9.54;10.27;10.75;11.5;12.28;13.36;14.8;16.01;16.9;17.81;18.5;19.69;20.72;21.43;22.84;23.31;23.52;23.61;24.05;24.27;24.41;24.57;23.85;14.8;7.6 |
|||
144;0;5.09;5.83;6.33;7.23;7.92;8.66;9.41;10.13;10.62;11.39;12.13;13.13;14.57;15.78;16.65;17.56;18.24;19.41;20.43;21.12;22.5;22.97;23.19;23.28;23.73;24.08;24.33;24.49;24.04;15;7.8 |
|||
145;0;5.02;5.73;6.23;7.15;7.85;8.55;9.28;9.98;10.51;11.27;11.98;12.92;14.35;15.56;16.42;17.31;17.97;19.13;20.14;20.82;22.17;22.64;22.87;22.96;23.42;23.81;24.23;24.41;24.19;15.14;7.98 |
|||
146;0;4.96;5.64;6.12;7.07;7.77;8.43;9.15;9.84;10.38;11.16;11.83;12.71;14.12;15.35;16.19;17.07;17.72;18.86;19.86;20.51;21.84;22.31;22.56;22.65;23.12;23.48;23.94;24.33;24.3;15.3;8.16 |
|||
148;0;4.82;5.45;5.91;6.9;7.59;8.21;8.89;9.55;10.14;10.89;11.55;12.29;13.7;14.92;15.74;16.6;17.23;18.32;19.3;19.91;21.2;21.67;21.95;22.05;22.53;22.87;23.38;24.13;24.39;15.52;8.46 |
|||
149;0;4.76;5.35;5.81;6.78;7.49;8.09;8.78;9.42;10.01;10.76;11.41;12.1;13.48;14.71;15.52;16.36;16.98;18.06;19.03;19.63;20.89;21.37;21.67;21.77;22.26;22.61;23.12;23.98;24.37;15.57;8.58 |
|||
150;0;4.69;5.26;5.7;6.67;7.37;7.96;8.64;9.26;9.86;10.6;11.24;11.89;13.26;14.48;15.29;16.11;16.73;17.79;18.74;19.33;20.55;21.04;21.37;21.48;21.98;22.34;22.83;23.69;24.11;15.6;8.67 |
|||
155;0;4.33;4.74;5.16;6.16;6.79;7.33;7.96;8.51;9.15;9.81;10.4;10.85;12.14;13.37;14.1;14.87;15.48;16.42;17.3;17.8;18.88;19.39;19.86;20;20.54;20.97;21.45;22.25;22.77;15.38;8.89 |
|||
160;0;4.09;4.41;4.83;5.77;6.39;6.94;7.55;8.04;8.67;9.28;9.83;10.24;11.46;12.69;13.39;14.11;14.73;15.6;16.41;16.87;17.85;18.4;18.97;19.15;19.72;20.2;20.65;21.35;21.84;14.95;8.74 |
|||
162;0;4;4.29;4.69;5.62;6.23;6.77;7.38;7.86;8.48;9.07;9.6;10;11.18;12.42;13.1;13.81;14.43;15.27;16.06;16.5;17.43;18;18.62;18.81;19.39;19.89;20.33;20.99;21.48;14.76;8.61 |
|||
168;0;3.74;3.93;4.35;5.15;5.75;6.31;6.93;7.34;7.92;8.45;8.95;9.35;10.44;11.68;12.32;12.99;13.63;14.39;15.11;15.5;16.31;16.94;17.7;17.92;18.53;19.08;19.49;19.99;20.46;14.34;8.3 |
|||
170;0;3.69;3.85;4.27;5.04;5.65;6.22;6.82;7.23;7.8;8.31;8.8;9.22;10.27;11.51;12.15;12.81;13.45;14.19;14.9;15.28;16.06;16.7;17.51;17.73;18.34;18.91;19.31;19.77;20.22;14.24;8.24 |
|||
174;0;3.57;3.69;4.11;4.83;5.43;6.01;6.62;7;7.55;8.03;8.5;8.93;9.95;11.19;11.81;12.45;13.11;13.81;14.48;14.84;15.57;16.24;17.11;17.35;17.98;18.57;18.95;19.33;19.77;14.03;8.13 |
|||
180;0;3.51;3.6;4.03;4.71;5.31;5.91;6.51;6.88;7.41;7.88;8.33;8.79;9.78;11.02;11.63;12.26;12.93;13.61;14.26;14.61;15.31;15.99;16.9;17.15;17.79;18.39;18.77;19.09;19.52;13.87;8.07 |
|||
</pre> |
|||
{{out}} |
|||
<pre> |
|||
The route taking the least time found was: |
|||
Point{2,Int64}[[1, 4], [1, 5], [2, 6], [3, 7], [4, 7], [5, 7], [6, 7], [7, 7], [8, 6], [9, 5], [9, 4]] |
|||
Which has duration 4.0 hours, 39.72104601694747 minutes. |
|||
</pre> |