# Map with connections

**URL:** <https://discourse.julialang.org/t/map-with-connections/138533>\
**Category:** Visualization\
**Tags:** question\
**Created:** [July 30, 2026, 9:03am UTC](https://discourse.julialang.org/t/map-with-connections/138533 "2026-07-30T09:03:10Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![mahmah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mahmah/32/214326_2.png) [@mahmah](https://discourse.julialang.org/u/mahmah)\
**Post date:** [July 30, 2026, 9:03am UTC](https://discourse.julialang.org/t/map-with-connections/138533/1 "2026-07-30T09:03:10Z")

</div>

I’d like to make a map of Europe with connections for a funding application. It has to look very good and show the institutions involved, the fields of expertise of each institution, and some connections to show who is travelling where. What packages should I look at for this?

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [July 30, 2026, 9:10am UTC](https://discourse.julialang.org/t/map-with-connections/138533/2 "2026-07-30T09:10:23Z")

</div>

Please search this forum. There are many similar questions.

---

<div class="post-metadata">

**Author:** ![mahmah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mahmah/32/214326_2.png) [@mahmah](https://discourse.julialang.org/u/mahmah)\
**Post date:** [July 30, 2026, 9:12am UTC](https://discourse.julialang.org/t/map-with-connections/138533/3 "2026-07-30T09:12:39Z")

</div>

Sure, I was looking for a more updated answer. Is this more of a GMT job or a GeoMakie one?

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [July 30, 2026, 9:21am UTC](https://discourse.julialang.org/t/map-with-connections/138533/4 "2026-07-30T09:21:41Z")

</div>

Nothing changed since the last posts. Pick one that suits your needs.

---

<div class="post-metadata">

**Author:** ![mahmah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mahmah/32/214326_2.png) [@mahmah](https://discourse.julialang.org/u/mahmah)\
**Post date:** [July 30, 2026, 10:37am UTC](https://discourse.julialang.org/t/map-with-connections/138533/5 "2026-07-30T10:37:33Z")

</div>

Thanks. On a related note, it seems that there is an issue with GeoMakie.land():

```julia-auto
GeoMakie.land(10)
ERROR: MethodError: no method matching to_multipoly(::Nothing, ::Vector{GeometryBasics.MultiPolygon{2, Float32}})
The function `to_multipoly` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  to_multipoly(::GeoInterface.MultiPolygonTrait, ::Any)
   @ GeoMakie ~/.julia/packages/GeoMakie/QcBwP/src/geojson.jl:103
  to_multipoly(::GeoInterface.PolygonTrait, ::Any)
   @ GeoMakie ~/.julia/packages/GeoMakie/QcBwP/src/geojson.jl:102
  to_multipoly(::GeoInterface.GeometryCollectionTrait, ::Any)
   @ GeoMakie ~/.julia/packages/GeoMakie/QcBwP/src/geojson.jl:105
  ...

Stacktrace:
 [1] _broadcast_getindex_evalf
   @ ./broadcast.jl:699 [inlined]
 [2] _broadcast_getindex
   @ ./broadcast.jl:672 [inlined]
 [3] _getindex
   @ ./broadcast.jl:620 [inlined]
 [4] getindex
   @ ./broadcast.jl:616 [inlined]
 [5] copy
   @ ./broadcast.jl:933 [inlined]
 [6] materialize
   @ ./broadcast.jl:894 [inlined]
 [7] to_multipoly(geom::Vector{Any})
   @ GeoMakie ~/.julia/packages/GeoMakie/QcBwP/src/geojson.jl:101
 [8] land(scale::Int64)
   @ GeoMakie ~/.julia/packages/GeoMakie/QcBwP/src/data.jl:63
 [9] top-level scope
   @ none:1

```

---

<div class="post-metadata">

**Author:** ![mahmah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mahmah/32/214326_2.png) [@mahmah](https://discourse.julialang.org/u/mahmah)\
**Post date:** [July 30, 2026, 10:42am UTC](https://discourse.julialang.org/t/map-with-connections/138533/6 "2026-07-30T10:42:18Z")

</div>

I guess these two outputs should have the same type but they don’t:

```julia-auto
julia> typeof(GeoMakie.geo2basic(NaturalEarth.naturalearth("land", 50)))
Vector{Any} (alias for Array{Any, 1})

julia> typeof(GeoMakie.geo2basic(NaturalEarth.naturalearth("land", 110)))
Vector{Polygon{2, Float32}} (alias for Array{GeometryBasics.Polygon{2, Float32}, 1})

```

---

<div class="post-metadata">

**Author:** ![juliohm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/juliohm/32/215266_2.png) [@juliohm](https://discourse.julialang.org/u/juliohm)\
**Post date:** [July 30, 2026, 10:45am UTC](https://discourse.julialang.org/t/map-with-connections/138533/7 "2026-07-30T10:45:48Z")

</div>

Please start a dedicated thread.

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [July 30, 2026, 11:12am UTC](https://discourse.julialang.org/t/map-with-connections/138533/8 "2026-07-30T11:12:31Z")

</div>

I just made one here recently to visualize interconnections in europe: [Tutorial: cross-border flow map of Europe | ENTSOE.jl](https://europeanpowersystems.github.io/ENTSOE.jl/dev/tutorial_flow_map)

 ![image](https://global.discourse-cdn.com/julialang/original/3X/1/2/1264ec0d68a386e889002901f737de29543af053.png)

colorscheme could use some work…

---

<div class="post-metadata">

**Author:** ![mahmah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mahmah/32/214326_2.png) [@mahmah](https://discourse.julialang.org/u/mahmah)\
**Post date:** [July 30, 2026, 6:00pm UTC](https://discourse.julialang.org/t/map-with-connections/138533/9 "2026-07-30T18:00:11Z")

</div>

Thanks so much, this was a fantastic starting point for me! This and GraphMakie.jl (for the connections) was all I needed.

---

<div class="post-metadata">

**Author:** ![mkborregaard](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkborregaard/32/556_2.png) [@mkborregaard](https://discourse.julialang.org/u/mkborregaard)\
**Post date:** [July 31, 2026, 9:13am UTC](https://discourse.julialang.org/t/map-with-connections/138533/10 "2026-07-31T09:13:04Z")

</div>

Would be great to see your final result, this is a nice idea.

---

<div class="post-metadata">

**Author:** ![mahmah](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mahmah/32/214326_2.png) [@mahmah](https://discourse.julialang.org/u/mahmah)\
**Post date:** [August 3, 2026, 5:30am UTC](https://discourse.julialang.org/t/map-with-connections/138533/11 "2026-08-03T05:30:04Z")

</div>

Sure, it would be something like this (one can change the limits to make it Europe only for instance. The code has lots of extra stuff that comes from @langestefan 's version, it should be cleaned up a bit. And a typo in the title 🙂

 ![connections](https://global.discourse-cdn.com/julialang/original/3X/6/c/6c076bbc84cc7dd9364057ab1c723b27c78f5511.png)

```julia-auto
using GeoMakie, CairoMakie, GraphMakie,Graphs
using GeoMakie: NaturalEarth

const PROJ_STR = "+proj=merc"
const WGS84 = "+proj=longlat +datum=WGS84"

countries_fc = NaturalEarth.naturalearth("admin_0_countries", 50)

function _country_iso2(feature)
    p = feature.properties
    for key in (:ISO_A2_EH, :ISO_A2)
        v = get(p, key, nothing)
        v === nothing && continue
        v isa AbstractString && v != "-99" && return String(v)
    end
    return ""
end

const CORE_CCR = Set(["AT", "BE", "HR", "CZ", "FR", "DE_LU", "HU", "NL",
                      "PL", "RO", "SK", "SI"])

# Core members we hold no bidding-zone polygon for, coloured via their
# Natural Earth outline instead. Ireland is in Core through the Celtic
# Interconnector (IE↔FR border) — see ENTSO-E's CCR Regions map.
const CORE_BACKDROP_ISO2 = Set(["IS", "MA", "KE", "GE", "TH", "CL"])

const OTHER_COUNTRY = RGBAf(0.93, 0.93, 0.93, 1) # non-modelled: very light grey
const OTHER_ZONE = RGBAf(0.86, 0.95, 0.86, 1) # modelled non-Core: very light green
const CORE_COLOR = RGBAf(0.78, 0.88, 0.97, 0.7) # Core CCR: very light blue

function draw_flow_map(;title)
    fig = Figure(size = (800, 600))
    ax = GeoAxis(fig[1, 1];
        dest = PROJ_STR,
        limits = ((-78, 120), (-60, 71)),
        title = title,
        xgridvisible = false, ygridvisible = false,
        xticksvisible = false, yticksvisible = false,
        xticklabelsvisible = false, yticklabelsvisible = false,
    )

    # Layer 1: every other European country as light context, with any
    # polygon-less Core member (Ireland) highlighted like the Core zones.
    for feat in countries_fc
        poly!(ax, feat.geometry; color = OTHER_COUNTRY,
            strokecolor = :white, strokewidth = 0.4)
    end
    for feat in countries_fc
        core = _country_iso2(feat) in CORE_BACKDROP_ISO2
        if core
        poly!(ax, feat.geometry; color = CORE_COLOR,
            strokecolor = :darkgreen, strokewidth = 0.4)
        end
    end

# Cities: (longitude, latitude)
cities = Dict(
    "Reykjavík" => (-21.9426, 64.1466), # Iceland
    "Rabat" => (-6.8498, 34.0209), # Morocco
    "Nairobi" => (36.8219, -1.2921), # Kenya
    "Tbilisi" => (44.7930, 41.7151), # Georgia
    "Bangkok" => (100.5018, 13.7563), # Thailand
    "Santiago" => (-70.6693, -33.4489), # Chile
    "Wellington" => (174.7772, -41.2866) # New Zealand (outside your current x-limit)
)
lons = first.(values(cities))
lats = last.(values(cities))
# City points
scatter!(
    ax,
    lons,
    lats;
    color = :red,
    markersize = 12
)

drawcurve!(ax,cities,"Reykjavík","Rabat","")
drawcurve!(ax,cities,"Reykjavík","Nairobi","")
drawcurve!(ax,cities,"Reykjavík","Tbilisi","")
drawcurve!(ax,cities,"Reykjavík","Bangkok","")
drawcurve!(ax,cities,"Reykjavík","Santiago","")

# City labels
for (name, (lon, lat)) in cities
    if name == "London" || name == "Zürich"
        alignpar = (:right, :top)
        offsetpar = (-4,-0)
    elseif name == "Aarhus"
        alignpar = (:right, :bottom)
        offsetpar = (-5,-1)
    elseif name == "Oslo"
        alignpar = (:right, :bottom)
        offsetpar = (-5,5)
    elseif name == "La Plata"
        alignpar = (:left, :top)
        offsetpar = (-5,-5)
        else
        alignpar = (:left, :bottom)
        offsetpar = (2,2)
    end
        text!(
        ax,
        name;
        position = (lon, lat),
        align = alignpar,
        fontsize = 14,
        offset = offsetpar
    )

end

    # Bottom legends, grouped together and centred: area fills on the
    # left, the line-thickness (volume) key on the right.
    area_elems = [
        PolyElement(color = CORE_COLOR, strokecolor = :white, strokewidth = 1),
        PolyElement(color = OTHER_ZONE, strokecolor = :white, strokewidth = 1),
    ]
    rowgap!(fig.layout, 4)
    display(fig)
    save(plotsdir("connections.png"),fig)
    save(plotsdir("connections.pdf"),fig)
end
drawcurve!(ax,cities, from::String,to::String,text::String;valign=:baseline,offset=0.0) = drawcurve!(ax,cities[from],cities[to],text;valign=valign,offset=offset)

function drawcurve!(ax, origin, destination, text;
    curve_distance = 4,
    valign = :baseline,
    offset = 0.0
)

    layout = [Point2(origin...), Point2(destination...)]

    g = Graphs.SimpleDiGraph(2)
    Graphs.add_edge!(g, 1, 2)

    dist = hypot(
        destination[1] - origin[1],
        destination[2] - origin[2]
    )

    graphplot!(
        ax,
        g;
        layout = layout,
        curve_distance = dist*.2,
        elabels = [text],
        curve_distance_usage = true,
        elabels_distance = 15,
        node_color = (:white, 0.7),
        edge_color = (:steelblue, 0.7),
        linewidth = 0.6,
        arrow_size = 20,
    )
end

draw_flow_map(title = "Map with connectionls between partners")

```

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [August 3, 2026, 8:57am UTC](https://discourse.julialang.org/t/map-with-connections/138533/12 "2026-08-03T08:57:28Z")

</div>

I like it! Now to be completely over the top, you need to of course animate it.
