Skip to content

Commit 92da348

Browse files
ZetisonJon Vegard VenåsJulStraus
authored
Adapt descriptive_names to new elements in EnergyModelsFlex (#47)
* Adapted `descriptive_names` to new elements in `EnergyModelsFlex` * Fixed bug that did not show the background map of the topology when exporting to .svg-format. * Cleaned up hack * Used `Downloads` instead of `HTTP` to download `.geojson` files. This resolves warning and uses a standard julia library hat is faster to load. * Cleaned up `test/case7.jl` * Added a white background to `.svg`-files. --------- Co-authored-by: Jon Vegard Venås <jonvegard.venas@sintef.no> Co-authored-by: Julian Straus <julian.straus@sintef.no>
1 parent f226a6b commit 92da348

13 files changed

Lines changed: 136 additions & 201 deletions

File tree

NEWS.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Release notes
22

3+
## Version 0.6.3 (2026-01-13)
4+
5+
### Bugfix
6+
7+
* Fix bug that did not show the background map of the topology when exporting to .svg-format.
8+
9+
### Enhancements
10+
11+
* Adjust `descriptive_names` to new elements in `EnergyModelsFlex`.
12+
13+
### Adjustments
14+
15+
* Use `Downloads` instead of `HTTP` to download `.geojson` files. This resolves warning and uses a standard julia library that is faster to load.
16+
* Cleaned up `test/case7.jl`.
17+
* Add a white background to `.svg`-files.
18+
319
## Version 0.6.2 (2025-12-18)
420

521
### Bugfix
@@ -61,7 +77,7 @@
6177

6278
### Enhancements
6379

64-
* Use `Float32` instead of `Number`/`Real`/`Float64` for coordinate related computations in topo (also `Point2f` instead of `Tuple` and `Vector`).
80+
* Use `Float32` instead of `Number`/`Real`/`Float64` for coordinate related computations in topo (also `Point2f` instead of `Tuple` and `Vector`).
6581
* Remove redundant `notify_component` function and `Observable`s (use the `@lift` macro instead).
6682
* Improve performance of updates to `ax_info`.
6783
* Add missing tests for show-function on the types `AbstractSystem` and `ProcInvData`, and improve code structure.

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "EnergyModelsGUI"
22
uuid = "737a7361-d3b7-40e9-b1ac-59bee4c5ea2d"
3-
version = "0.6.2"
3+
version = "0.6.3"
44
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>"]
55

66
[deps]
@@ -9,13 +9,13 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
99
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
1010
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
1111
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
12+
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
1213
EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
1314
EnergyModelsInvestments = "fca3f8eb-b383-437d-8e7b-aac76bb2004f"
1415
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
1516
GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a"
1617
GeoJSON = "61d90e0f-e114-555e-ac52-39dfb47a3ef9"
1718
GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6"
18-
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
1919
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
2020
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
2121
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
@@ -40,14 +40,14 @@ CairoMakie = "0.15"
4040
Colors = "0.13"
4141
DataFrames = "1"
4242
Dates = "1"
43+
Downloads = "1"
4344
EnergyModelsBase = "0.9"
4445
EnergyModelsGeography = "0.11.3"
4546
EnergyModelsInvestments = "0.8"
4647
FileIO = "1"
4748
GLMakie = "0.13"
4849
GeoJSON = "0.8"
4950
GeoMakie = "0.7.16"
50-
HTTP = "1.10"
5151
ImageMagick = "1"
5252
InteractiveUtils = "1"
5353
IntervalSets = "<0.7.12"

src/EnergyModelsGUI.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ using DataFrames
4848
using GeoMakie, GeoJSON
4949

5050
# Needed to download the .json file for geographical coastlines
51-
using HTTP
51+
using Downloads
5252

5353
# Use PrettyTables to enable printing data to the REPL
5454
using PrettyTables

src/descriptive_names.yml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# This file contains description of EMX element fields (and potential sub-fields) and variables
1+
# This file contains description of EMX element fields (and potential sub-fields) and variables
22
# with fields of type TimeStruct.TimeProfile and fields that cannot be inherited
33
# from supertypes.
44
structures:
@@ -86,7 +86,7 @@ structures:
8686

8787
HydroGenerator:
8888
cap: "Installed discharge or power capacity"
89-
89+
9090
HydroPump:
9191
cap: "Installed pumping capacity"
9292

@@ -142,6 +142,13 @@ structures:
142142
CCSRetroFit:
143143
opex_var: "Variable operating expense per unit of CO₂ captured"
144144

145+
EnergyModelsFlex:
146+
## links/datastructures.jl
147+
CapacityCostLink:
148+
cap: "Installed capacity"
149+
cap_price: "Price of capacity usage"
150+
cap_price_periods: "The number of sub periods in an investment period"
151+
145152

146153
variables:
147154
# EnergyModelsBase
@@ -184,7 +191,7 @@ variables:
184191
linepack_stor_level: "Storage level in linepack"
185192
emissions_trans: "Emissions of a transmission mode"
186193

187-
# EnergyModelsInvestment
194+
# EnergyModelsInvestment
188195
cap_capex: "Absolute CAPEX for investments in the capacity of a technology"
189196
cap_invest_b: "Binary indicator of capacity investments"
190197
cap_remove_b: "Binary indicator of capacity investments removal"
@@ -262,11 +269,13 @@ variables:
262269

263270
# EnergyModelsFlex
264271
input_frac_strat: "Input resource fraction"
265-
load_shift_from: "Load shift from"
266-
load_shift_to: "Load shift to"
272+
load_shift_from: "Load shift from"
273+
load_shift_to: "Load shift to"
267274
load_shifted: "Load shifted"
268-
sink_surplus_p: "Penalties for surplus of resource"
269-
sink_deficit_p: "Penalties for deficits of resource"
275+
sink_surplus_p: "Penalties for surplus of resource"
276+
sink_deficit_p: "Penalties for deficits of resource"
277+
ccl_cap_use_cost: "Cost over sub periods"
278+
ccl_cap_use_max: "Maximum capacity usage over sub periods"
270279

271280
# Overview of total quantities and their components
272281
total:
@@ -275,6 +284,8 @@ total:
275284
opex_fixed: "Total absolute fixed OPEX"
276285
trans_opex_var: "Total absolute variable transmission OPEX"
277286
trans_opex_fixed: "Total absolute fixed transmission OPEX"
287+
link_opex_var: "Total absolute variable link OPEX"
288+
link_opex_fixed: "Total absolute fixed link OPEX"
278289
capex_fields:
279290
cap_capex: "Total absolute CAPEX for investments in the capacity of technologies"
280291
stor_level_capex: "Total absolute CAPEX for investments in the capacity of storages"

src/setup_GUI.jl

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)
329329
alignmode = Inside(),
330330
)
331331

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

344344
# Download the file if it doesn't exist in the temporary directory
345345
if !isfile(local_file_path)
346-
HTTP.download(url, local_file_path)
346+
Downloads.download(url, local_file_path)
347347
end
348348

349349
# Now read the data from the file
@@ -352,21 +352,10 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)
352352
# Create GeoMakie plotable object
353353
boundary = GeoMakie.to_multipoly(boundary_geo_json.geometry)
354354
end
355-
else
355+
else # Use user-provided coast lines
356356
boundary_geo_json = GeoJSON.read(read(vars[:map_boundary_file], String))
357357
boundary = GeoMakie.to_multipoly(boundary_geo_json.geometry)
358358
end
359-
poly!(
360-
ax,
361-
boundary;
362-
color = :honeydew,
363-
colormap = :dense,
364-
strokecolor = :gray50,
365-
strokewidth = 0.5,
366-
inspectable = false,
367-
depth_shift = 1.0f0 - 2.0f-5,
368-
stroke_depth_shift = 1.0f0 - 3.0f-5,
369-
)
370359
ocean_coords = [(180, -90), (-180, -90), (-180, 90), (180, 90)]
371360
poly!(
372361
ax,
@@ -378,6 +367,17 @@ function create_makie_objects(vars::Dict, design::EnergySystemDesign)
378367
depth_shift = 1.0f0,
379368
stroke_depth_shift = 1.0f0 - 1.0f-5,
380369
)
370+
poly!(
371+
ax,
372+
boundary;
373+
color = :honeydew,
374+
colormap = :dense,
375+
strokecolor = :gray50,
376+
strokewidth = 0.5,
377+
inspectable = false,
378+
depth_shift = 1.0f0 - 2.0f-5,
379+
stroke_depth_shift = 1.0f0 - 3.0f-5,
380+
)
381381
else # The design does not use the EnergyModelsGeography package: Create a simple Makie axis
382382
ax = Axis(
383383
gridlayout_topology_ax[1, 1];

src/utils_gen/export_utils.jl

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,50 +26,67 @@ function merge_svg_strings(svg1, svg2)
2626
return header * svg_str1 * svg_str2 * "</svg>\n"
2727
end
2828

29+
"""
30+
outer_bbox(ax::Makie.AbstractAxis; padding::Number = 0)
31+
32+
Compute the outer bounding box of the axis `ax` with additional `padding`.
33+
"""
34+
function outer_bbox(ax::Makie.AbstractAxis; padding::Number = 0)
35+
sbb = ax.layoutobservables.suggestedbbox[]
36+
prot = ax.layoutobservables.reporteddimensions[].outer
37+
o = sbb.origin .- (prot.left, prot.bottom) .- padding
38+
w = sbb.widths .+ (prot.left + prot.right, prot.bottom + prot.top) .+ 2 * padding
39+
return Rect2f(o, w)
40+
end
41+
42+
"""
43+
get_svg(blockscene::Makie.Scene)
44+
45+
Get the SVG representation of the `blockscene`.
46+
"""
47+
function get_svg(blockscene::Makie.Scene)
48+
svg = mktempdir() do dir
49+
save(joinpath(dir, "output.svg"), blockscene; backend = CairoMakie)
50+
read(joinpath(dir, "output.svg"), String)
51+
end
52+
return svg
53+
end
54+
2955
"""
3056
export_svg(ax::Makie.Block, filename::String)
3157
3258
Export the `ax` to a .svg file with path given by `filename`.
59+
60+
!!! note "Temporary approach"
61+
This approach awaits solution from issue https://github.com/MakieOrg/Makie.jl/issues/4500
3362
"""
3463
function export_svg(
3564
ax::Makie.Block, filename::String; legend::Union{Makie.Legend,Nothing} = nothing,
3665
)
37-
bb = ax.layoutobservables.suggestedbbox[]
38-
protrusions = ax.layoutobservables.reporteddimensions[].outer
39-
40-
offset = 0 #ax.spinewidth[] / 2
41-
axis_bb = Rect2f(
42-
bb.origin .- (protrusions.left, protrusions.bottom) .- offset,
43-
bb.widths .+
44-
(protrusions.left + protrusions.right, protrusions.bottom + protrusions.top) .+
45-
2 * offset,
66+
bbox = outer_bbox(ax)
67+
_, sh = ax.blockscene.viewport[].widths
68+
ox, oy = bbox.origin
69+
w, h = bbox.widths
70+
svg_ax = get_svg(ax.blockscene)
71+
svg_legend = isnothing(legend) ? "" : get_svg(legend.blockscene)
72+
svg = merge_svg_strings(svg_ax, svg_legend)
73+
svg = replace(
74+
svg,
75+
r"viewBox=\".*?\"" => "viewBox=\"$ox $(sh - oy - h) $w $h\"",
76+
r"width=\".*?\"" => "width=\"$w\"",
77+
r"height=\".*?\"" => "height=\"$h\"",
78+
count = 3,
4679
)
4780

48-
pad = 5
49-
50-
ws = axis_bb.widths
51-
o = axis_bb.origin
52-
width = "$(ws[1] + 2 * pad)pt"
53-
height = "$(ws[2] + 2 * pad)pt"
54-
55-
# Temporary hack to fix viewBox for SVG export:
56-
# Based on the default (1920,1080) resolution, set hack such that
57-
# [:results]: when ws[2] is 575.80005 then hack should be 202.442, and
58-
# [:topo]: when ws[2] is 1001.0 then hack should be 953.000
59-
# Awaiting solution from issue https://github.com/MakieOrg/Makie.jl/issues/4500
60-
# pad should arguably also be set to 0 when solution is found
61-
hack = 202.442 + (ws[2] - 575.80005) * (953.000 - 202.442) / (1001.0 - 575.80005)
62-
viewBox = "$(o[1] - pad) $(o[2] - hack + ws[2] - pad) $(ws[1] + 2 * pad) $(ws[2] + 2 * pad)"
81+
# Add white background
82+
svg_str1, header = extract_svg(svg)
83+
svg =
84+
header *
85+
"""<rect x="$ox" y="$(sh - oy - h)" width="$w" height="$h" fill="white"/> """ *
86+
svg_str1 * "</svg>\n"
6387

64-
svgstring_ax = repr(MIME"image/svg+xml"(), ax.blockscene)
65-
svgstring_legend =
66-
isnothing(legend) ? "" : repr(MIME"image/svg+xml"(), legend.blockscene)
67-
svgstring = merge_svg_strings(svgstring_ax, svgstring_legend)
68-
svgstring = replace(svgstring, r"""(?<=width=")[^"]*(?=")""" => width; count = 1)
69-
svgstring = replace(svgstring, r"""(?<=height=")[^"]*(?=")""" => height; count = 1)
70-
svgstring = replace(svgstring, r"""(?<=viewBox=")[^"]*(?=")""" => viewBox; count = 1)
7188
open(filename, "w") do io
72-
print(io, svgstring)
89+
print(io, svg)
7390
end
7491
return 0
7592
end

test/case7.jl

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ function read_data()
7272
lat = [xy[2] for xy coordinates]
7373

7474
# Create the individual areas and transmission modes
75-
areas = [RefArea(i, area_ids[i], lon[i], lat[i], an[i]) for i eachindex(area_ids)]
75+
areas = [
76+
RefArea("area" * string(i), area_ids[i], lon[i], lat[i], an[i]) for
77+
i eachindex(area_ids)
78+
]
7679

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

245248
# Create links between nodes
246249
links = [
247-
Direct(1, el_busbar_11125, el_1, Linear()),
248-
Direct(2, el_busbar_11125, heat_generator, Linear()),
249-
Direct(3, el_busbar_11125, water_heater, Linear()),
250-
Direct(4, heat_generator, heating_1, Linear()),
251-
Direct(5, water_heater, hot_water_1, Linear()),
252-
Direct(6, el_busbar_11125, heat_pump, Linear()),
253-
Direct(7, heat_pump, heating_1, Linear()),
254-
Direct(8, waste_heat_data_center, heat_pump, Linear()),
250+
Direct("El busbar_11125 - El 1", el_busbar_11125, el_1, Linear()),
251+
Direct(
252+
"El busbar_11125 - Heat generator",
253+
el_busbar_11125,
254+
heat_generator,
255+
Linear(),
256+
),
257+
Direct(
258+
"El busbar_11125 - Water heater",
259+
el_busbar_11125,
260+
water_heater,
261+
Linear(),
262+
),
263+
Direct("Heat generator - Heating 1", heat_generator, heating_1, Linear()),
264+
Direct("Water heater - Hot water 1", water_heater, hot_water_1, Linear()),
265+
Direct("El busbar_11125 - Heat pump", el_busbar_11125, heat_pump, Linear()),
266+
Direct("Heat pump - Heating 1", heat_pump, heating_1, Linear()),
267+
Direct(
268+
"Waste heat data center - Heat pump",
269+
waste_heat_data_center,
270+
heat_pump,
271+
Linear(),
272+
),
255273
]
256274
elseif a_id == "Area 2"
257275
# Load the electricity cost from file
258276
El_cost_file = readlines(inputFolder * raw"/el cost.dat")
259277
El_cost = [parse(Float64, line) for line El_cost_file] # In NOK/MWh
260278

261-
# Load the power supply capacity from file
262-
max_outtake_file = readlines(inputFolder * raw"/10.dat")
263-
max_outtake = [parse(Float64, line) for line max_outtake_file] # In MW
264-
max_waste_outtake = [
265-
i > 1 ? FixedProfile(0.0) : FixedProfile(0.0) for i 1:(T.len)
266-
] # Make Waste supply available only from 2030
267-
268279
# Construct nodes
269280
el_busbar_11124 = GeoAvailability(
270281
"El busbar_11124", # Node id
271282
products[1:1], # Resources available at the busbar
272283
)
273284
power_supply = RefSource(
274285
"Power supply", # Node id
275-
OperationalProfile(max_outtake), # Cap, installed capacity
286+
FixedProfile(10), # Cap, installed capacity
276287
OperationalProfile(El_cost), # Variable operational cost per unit produced
277288
FixedProfile(0), # Fixed operational cost per unit produced
278289
Dict(Power => 1), # The generated resources with conversion value 1
@@ -288,7 +299,7 @@ function get_sub_system_data_case7(a_id, products, T)
288299
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)
289300
EV_charger_demand = [
290301
if i == 1
291-
OperationalProfile(0.0 * ones(24))
302+
FixedProfile(0.0)
292303
else
293304
OperationalProfile(
294305
EV_charger_demand_day * EV_charger_change_factors[i-1],

0 commit comments

Comments
 (0)