Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Release notes

## Version 0.6.3 (2026-01-13)

### Bugfix

* Fix bug that did not show the background map of the topology when exporting to .svg-format.

### Enhancements

* Adjust `descriptive_names` to new elements in `EnergyModelsFlex`.

### Adjustments

* Use `Downloads` instead of `HTTP` to download `.geojson` files. This resolves warning and uses a standard julia library that is faster to load.
* Cleaned up `test/case7.jl`.
* Add a white background to `.svg`-files.

## Version 0.6.2 (2025-12-18)

### Bugfix
Expand Down Expand Up @@ -61,7 +77,7 @@

### Enhancements

* Use `Float32` instead of `Number`/`Real`/`Float64` for coordinate related computations in topo (also `Point2f` instead of `Tuple` and `Vector`).
* Use `Float32` instead of `Number`/`Real`/`Float64` for coordinate related computations in topo (also `Point2f` instead of `Tuple` and `Vector`).
* Remove redundant `notify_component` function and `Observable`s (use the `@lift` macro instead).
* Improve performance of updates to `ax_info`.
* Add missing tests for show-function on the types `AbstractSystem` and `ProcInvData`, and improve code structure.
Expand Down
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "EnergyModelsGUI"
uuid = "737a7361-d3b7-40e9-b1ac-59bee4c5ea2d"
version = "0.6.2"
version = "0.6.3"
authors = ["Jon Vegard Venås <JonVegard.Venas@sintef.no>", "Dimitri Pinel <Dimitri.Pinel@sintef.no>", "Magnus Askeland <Magnus.Askeland@sintef.no>", "Shweta Tiwari <Shweta.Tiwari@sintef.no>"]

[deps]
Expand All @@ -9,13 +9,13 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
EnergyModelsInvestments = "fca3f8eb-b383-437d-8e7b-aac76bb2004f"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a"
GeoJSON = "61d90e0f-e114-555e-ac52-39dfb47a3ef9"
GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
Expand All @@ -40,14 +40,14 @@ CairoMakie = "0.15"
Colors = "0.13"
DataFrames = "1"
Dates = "1"
Downloads = "1"
EnergyModelsBase = "0.9"
EnergyModelsGeography = "0.11.3"
EnergyModelsInvestments = "0.8"
FileIO = "1"
GLMakie = "0.13"
GeoJSON = "0.8"
GeoMakie = "0.7.16"
HTTP = "1.10"
ImageMagick = "1"
InteractiveUtils = "1"
IntervalSets = "<0.7.12"
Expand Down
2 changes: 1 addition & 1 deletion src/EnergyModelsGUI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ using DataFrames
using GeoMakie, GeoJSON

# Needed to download the .json file for geographical coastlines
using HTTP
using Downloads

# Use PrettyTables to enable printing data to the REPL
using PrettyTables
Expand Down
25 changes: 18 additions & 7 deletions src/descriptive_names.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file contains description of EMX element fields (and potential sub-fields) and variables
# This file contains description of EMX element fields (and potential sub-fields) and variables
# with fields of type TimeStruct.TimeProfile and fields that cannot be inherited
# from supertypes.
structures:
Expand Down Expand Up @@ -86,7 +86,7 @@ structures:

HydroGenerator:
cap: "Installed discharge or power capacity"

HydroPump:
cap: "Installed pumping capacity"

Expand Down Expand Up @@ -142,6 +142,13 @@ structures:
CCSRetroFit:
opex_var: "Variable operating expense per unit of CO₂ captured"

EnergyModelsFlex:
## links/datastructures.jl
CapacityCostLink:
cap: "Installed capacity"
cap_price: "Price of capacity usage"
cap_price_periods: "The number of sub periods in an investment period"


variables:
# EnergyModelsBase
Expand Down Expand Up @@ -184,7 +191,7 @@ variables:
linepack_stor_level: "Storage level in linepack"
emissions_trans: "Emissions of a transmission mode"

# EnergyModelsInvestment
# EnergyModelsInvestment
cap_capex: "Absolute CAPEX for investments in the capacity of a technology"
cap_invest_b: "Binary indicator of capacity investments"
cap_remove_b: "Binary indicator of capacity investments removal"
Expand Down Expand Up @@ -262,11 +269,13 @@ variables:

# EnergyModelsFlex
input_frac_strat: "Input resource fraction"
load_shift_from: "Load shift from"
load_shift_to: "Load shift to"
load_shift_from: "Load shift from"
load_shift_to: "Load shift to"
load_shifted: "Load shifted"
sink_surplus_p: "Penalties for surplus of resource"
sink_deficit_p: "Penalties for deficits of resource"
sink_surplus_p: "Penalties for surplus of resource"
sink_deficit_p: "Penalties for deficits of resource"
ccl_cap_use_cost: "Cost over sub periods"
ccl_cap_use_max: "Maximum capacity usage over sub periods"

# Overview of total quantities and their components
total:
Expand All @@ -275,6 +284,8 @@ total:
opex_fixed: "Total absolute fixed OPEX"
trans_opex_var: "Total absolute variable transmission OPEX"
trans_opex_fixed: "Total absolute fixed transmission OPEX"
link_opex_var: "Total absolute variable link OPEX"
link_opex_fixed: "Total absolute fixed link OPEX"
capex_fields:
cap_capex: "Total absolute CAPEX for investments in the capacity of technologies"
stor_level_capex: "Total absolute CAPEX for investments in the capacity of storages"
Expand Down
30 changes: 15 additions & 15 deletions src/setup_GUI.jl
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)
alignmode = Inside(),
)

if isempty(vars[:map_boundary_file])
# Plot coast lines
# Plot coast lines
if isempty(vars[:map_boundary_file]) # Use default coast lines
if vars[:coarse_coast_lines] # Use low resolution coast lines
boundary = GeoMakie.land()
else # Use high resolution coast lines
Expand All @@ -343,7 +343,7 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)

# Download the file if it doesn't exist in the temporary directory
if !isfile(local_file_path)
HTTP.download(url, local_file_path)
Downloads.download(url, local_file_path)
end

# Now read the data from the file
Expand All @@ -352,21 +352,10 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)
# Create GeoMakie plotable object
boundary = GeoMakie.to_multipoly(boundary_geo_json.geometry)
end
else
else # Use user-provided coast lines
boundary_geo_json = GeoJSON.read(read(vars[:map_boundary_file], String))
boundary = GeoMakie.to_multipoly(boundary_geo_json.geometry)
end
poly!(
ax,
boundary;
color = :honeydew,
colormap = :dense,
strokecolor = :gray50,
strokewidth = 0.5,
inspectable = false,
depth_shift = 1.0f0 - 2.0f-5,
stroke_depth_shift = 1.0f0 - 3.0f-5,
)
ocean_coords = [(180, -90), (-180, -90), (-180, 90), (180, 90)]
poly!(
ax,
Expand All @@ -378,6 +367,17 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)
depth_shift = 1.0f0,
stroke_depth_shift = 1.0f0 - 1.0f-5,
)
poly!(
ax,
boundary;
color = :honeydew,
colormap = :dense,
strokecolor = :gray50,
strokewidth = 0.5,
inspectable = false,
depth_shift = 1.0f0 - 2.0f-5,
stroke_depth_shift = 1.0f0 - 3.0f-5,
)
else # The design does not use the EnergyModelsGeography package: Create a simple Makie axis
ax = Axis(
gridlayout_topology_ax[1, 1];
Expand Down
81 changes: 49 additions & 32 deletions src/utils_gen/export_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,67 @@ function merge_svg_strings(svg1, svg2)
return header * svg_str1 * svg_str2 * "</svg>\n"
end

"""
outer_bbox(ax::Makie.AbstractAxis; padding::Number = 0)

Compute the outer bounding box of the axis `ax` with additional `padding`.
"""
function outer_bbox(ax::Makie.AbstractAxis; padding::Number = 0)
sbb = ax.layoutobservables.suggestedbbox[]
prot = ax.layoutobservables.reporteddimensions[].outer
o = sbb.origin .- (prot.left, prot.bottom) .- padding
w = sbb.widths .+ (prot.left + prot.right, prot.bottom + prot.top) .+ 2 * padding
return Rect2f(o, w)
end

"""
get_svg(blockscene::Makie.Scene)

Get the SVG representation of the `blockscene`.
"""
function get_svg(blockscene::Makie.Scene)
svg = mktempdir() do dir
save(joinpath(dir, "output.svg"), blockscene; backend = CairoMakie)
read(joinpath(dir, "output.svg"), String)
end
return svg
end

"""
export_svg(ax::Makie.Block, filename::String)

Export the `ax` to a .svg file with path given by `filename`.

!!! note "Temporary approach"
This approach awaits solution from issue https://github.com/MakieOrg/Makie.jl/issues/4500
"""
function export_svg(
ax::Makie.Block, filename::String; legend::Union{Makie.Legend,Nothing} = nothing,
)
bb = ax.layoutobservables.suggestedbbox[]
protrusions = ax.layoutobservables.reporteddimensions[].outer

offset = 0 #ax.spinewidth[] / 2
axis_bb = Rect2f(
bb.origin .- (protrusions.left, protrusions.bottom) .- offset,
bb.widths .+
(protrusions.left + protrusions.right, protrusions.bottom + protrusions.top) .+
2 * offset,
bbox = outer_bbox(ax)
_, sh = ax.blockscene.viewport[].widths
ox, oy = bbox.origin
w, h = bbox.widths
svg_ax = get_svg(ax.blockscene)
svg_legend = isnothing(legend) ? "" : get_svg(legend.blockscene)
svg = merge_svg_strings(svg_ax, svg_legend)
svg = replace(
svg,
r"viewBox=\".*?\"" => "viewBox=\"$ox $(sh - oy - h) $w $h\"",
r"width=\".*?\"" => "width=\"$w\"",
r"height=\".*?\"" => "height=\"$h\"",
count = 3,
)

pad = 5

ws = axis_bb.widths
o = axis_bb.origin
width = "$(ws[1] + 2 * pad)pt"
height = "$(ws[2] + 2 * pad)pt"

# Temporary hack to fix viewBox for SVG export:
# Based on the default (1920,1080) resolution, set hack such that
# [:results]: when ws[2] is 575.80005 then hack should be 202.442, and
# [:topo]: when ws[2] is 1001.0 then hack should be 953.000
# Awaiting solution from issue https://github.com/MakieOrg/Makie.jl/issues/4500
# pad should arguably also be set to 0 when solution is found
hack = 202.442 + (ws[2] - 575.80005) * (953.000 - 202.442) / (1001.0 - 575.80005)
viewBox = "$(o[1] - pad) $(o[2] - hack + ws[2] - pad) $(ws[1] + 2 * pad) $(ws[2] + 2 * pad)"
# Add white background
svg_str1, header = extract_svg(svg)
svg =
header *
"""<rect x="$ox" y="$(sh - oy - h)" width="$w" height="$h" fill="white"/> """ *
svg_str1 * "</svg>\n"

svgstring_ax = repr(MIME"image/svg+xml"(), ax.blockscene)
svgstring_legend =
isnothing(legend) ? "" : repr(MIME"image/svg+xml"(), legend.blockscene)
svgstring = merge_svg_strings(svgstring_ax, svgstring_legend)
svgstring = replace(svgstring, r"""(?<=width=")[^"]*(?=")""" => width; count = 1)
svgstring = replace(svgstring, r"""(?<=height=")[^"]*(?=")""" => height; count = 1)
svgstring = replace(svgstring, r"""(?<=viewBox=")[^"]*(?=")""" => viewBox; count = 1)
open(filename, "w") do io
print(io, svgstring)
print(io, svg)
end
return 0
end
Expand Down
47 changes: 29 additions & 18 deletions test/case7.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ function read_data()
lat = [xy[2] for xy ∈ coordinates]

# Create the individual areas and transmission modes
areas = [RefArea(i, area_ids[i], lon[i], lat[i], an[i]) for i ∈ eachindex(area_ids)]
areas = [
RefArea("area" * string(i), area_ids[i], lon[i], lat[i], an[i]) for
i ∈ eachindex(area_ids)
]

# Set parameters for the power line
capacity, lossRatio = get_cable_data()
Expand Down Expand Up @@ -244,35 +247,43 @@ function get_sub_system_data_case7(a_id, products, T)

# Create links between nodes
links = [
Direct(1, el_busbar_11125, el_1, Linear()),
Direct(2, el_busbar_11125, heat_generator, Linear()),
Direct(3, el_busbar_11125, water_heater, Linear()),
Direct(4, heat_generator, heating_1, Linear()),
Direct(5, water_heater, hot_water_1, Linear()),
Direct(6, el_busbar_11125, heat_pump, Linear()),
Direct(7, heat_pump, heating_1, Linear()),
Direct(8, waste_heat_data_center, heat_pump, Linear()),
Direct("El busbar_11125 - El 1", el_busbar_11125, el_1, Linear()),
Direct(
"El busbar_11125 - Heat generator",
el_busbar_11125,
heat_generator,
Linear(),
),
Direct(
"El busbar_11125 - Water heater",
el_busbar_11125,
water_heater,
Linear(),
),
Direct("Heat generator - Heating 1", heat_generator, heating_1, Linear()),
Direct("Water heater - Hot water 1", water_heater, hot_water_1, Linear()),
Direct("El busbar_11125 - Heat pump", el_busbar_11125, heat_pump, Linear()),
Direct("Heat pump - Heating 1", heat_pump, heating_1, Linear()),
Direct(
"Waste heat data center - Heat pump",
waste_heat_data_center,
heat_pump,
Linear(),
),
]
elseif a_id == "Area 2"
# Load the electricity cost from file
El_cost_file = readlines(inputFolder * raw"/el cost.dat")
El_cost = [parse(Float64, line) for line ∈ El_cost_file] # In NOK/MWh

# Load the power supply capacity from file
max_outtake_file = readlines(inputFolder * raw"/10.dat")
max_outtake = [parse(Float64, line) for line ∈ max_outtake_file] # In MW
max_waste_outtake = [
i > 1 ? FixedProfile(0.0) : FixedProfile(0.0) for i ∈ 1:(T.len)
] # Make Waste supply available only from 2030

# Construct nodes
el_busbar_11124 = GeoAvailability(
"El busbar_11124", # Node id
products[1:1], # Resources available at the busbar
)
power_supply = RefSource(
"Power supply", # Node id
OperationalProfile(max_outtake), # Cap, installed capacity
FixedProfile(10), # Cap, installed capacity
OperationalProfile(El_cost), # Variable operational cost per unit produced
FixedProfile(0), # Fixed operational cost per unit produced
Dict(Power => 1), # The generated resources with conversion value 1
Expand All @@ -288,7 +299,7 @@ function get_sub_system_data_case7(a_id, products, T)
EV_charger_change_factors = [1, El_change_factor[3] / El_change_factor[2]] # Since the EV_charger is introduced in 2030, the El_change_factor is shifted such that the initial profile is not scaled (starting at the second strategic period) # Since the EV_charger is introduced in 2030, the El_change_factor is shifted such that the initial profile is not scaled (starting at the second strategic period)
EV_charger_demand = [
if i == 1
OperationalProfile(0.0 * ones(24))
FixedProfile(0.0)
else
OperationalProfile(
EV_charger_demand_day * EV_charger_change_factors[i-1],
Expand Down
Loading
Loading