From 93aec9025f4c1e653ece89bc931bc3cfea79a484 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 09:43:04 +0200 Subject: [PATCH 01/22] Fix detail form fields for floating numbers --- digiplan/map/forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 76a43513..a1504127 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -1,6 +1,6 @@ from itertools import count # noqa: D100 -from django.forms import BooleanField, Form, IntegerField, TextInput, renderers +from django.forms import BooleanField, FloatField, Form, TextInput, renderers from django.utils.safestring import mark_safe from django_mapengine import legend @@ -78,7 +78,7 @@ def generate_fields(parameters, additional_parameters=None): # noqa: ANN001, AN if "from-max" in item: attrs["data-from-max"] = item["from-max"] - field = IntegerField( + field = FloatField( label=item["label"], widget=TextInput(attrs=attrs), help_text=item["tooltip"], From 7187b9a7ed7676251e758f460331e201e93731dc Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 09:43:22 +0200 Subject: [PATCH 02/22] Enable results menu tab --- digiplan/static/js/menu.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/digiplan/static/js/menu.js b/digiplan/static/js/menu.js index 1f0fc865..771aa642 100644 --- a/digiplan/static/js/menu.js +++ b/digiplan/static/js/menu.js @@ -101,10 +101,6 @@ function toggleMenuButtons(tabIndex) { if (tabIndex === 0) { menuPreviousBtn.hidden = true; } - // TODO: Currently step 5 shall be inactive, to activate it again, remove next if-statement - if (tabIndex >= menuTabs.length - 2) { - menuNextBtn.disabled = true; - } if (tabIndex >= menuTabs.length - 1) { menuNextBtn.hidden = true; } From 3509e73ed88208c434820f25cbae7ae992be2e37 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Wed, 10 Apr 2024 09:45:41 +0200 Subject: [PATCH 03/22] Add celery make command --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 509dfaaf..a09d0c79 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ .PHONY : load_regions load_data empty_data dump_fixtures load_fixtures distill check_distill_coordinates MAP_ENGINE_DISTILL=True +DJANGO_READ_DOT_ENV_FILE=True export load_regions: @@ -40,6 +41,9 @@ check_distill_coordinates: local_env_file: python merge_local_dotenvs_in_dotenv.py +celery: + redis-server --port 6379 & celery -A config.celery worker -l INFO + update_vendor_assets: # Note: call this command from the same folder your Makefile is located # Note: this run only update minor versions. From 8400d032eabe2b4561918ad47f20a862740b621d Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Fri, 12 Apr 2024 14:42:12 +0200 Subject: [PATCH 04/22] Enable pre-results in results dropdown --- digiplan/map/migrations/0045_preresults.py | 21 ++ digiplan/map/models.py | 7 + digiplan/map/urls.py | 1 + digiplan/map/views.py | 29 +- digiplan/static/js/event-topics.js | 1 + digiplan/static/js/results.js | 299 ++++++++++++--------- 6 files changed, 230 insertions(+), 128 deletions(-) create mode 100644 digiplan/map/migrations/0045_preresults.py diff --git a/digiplan/map/migrations/0045_preresults.py b/digiplan/map/migrations/0045_preresults.py new file mode 100644 index 00000000..9e744ca7 --- /dev/null +++ b/digiplan/map/migrations/0045_preresults.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.11 on 2024-04-12 11:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("map", "0044_merge_20240318_1859"), + ] + + operations = [ + migrations.CreateModel( + name="PreResults", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("scenario", models.CharField(max_length=255)), + ("parameters", models.JSONField()), + ], + ), + ] diff --git a/digiplan/map/models.py b/digiplan/map/models.py index 98a62113..f31e37c7 100644 --- a/digiplan/map/models.py +++ b/digiplan/map/models.py @@ -691,3 +691,10 @@ class PotentialAreaWindSTP2018EG(StaticRegionModel): # noqa: D101 class PotentialAreaWindSTP2024VR(StaticRegionModel): # noqa: D101 data_file = "potentialarea_wind_stp_2024_vr" layer = "potentialarea_wind_stp_2024_vr" + + +class PreResults(models.Model): + """Model to store pre results ID related to given parameters.""" + + scenario = models.CharField(max_length=255) + parameters = models.JSONField() diff --git a/digiplan/map/urls.py b/digiplan/map/urls.py index 2a3d8b28..f3834d38 100644 --- a/digiplan/map/urls.py +++ b/digiplan/map/urls.py @@ -12,4 +12,5 @@ path("choropleth//", views.get_choropleth, name="choropleth"), path("popup//", views.get_popup, name="popup"), path("charts", views.get_charts, name="charts"), + path("pre_result", views.store_pre_result, name="pre_result"), ] diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 87c7abeb..b7ba338a 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -3,6 +3,7 @@ As map app is SPA, this module contains main view and various API points. """ +import json from django.conf import settings from django.http import HttpRequest, response @@ -13,7 +14,7 @@ from digiplan import __version__ from digiplan.map import config -from . import charts, choropleths, forms, map_config, popups, utils +from . import charts, choropleths, forms, hooks, map_config, models, popups, utils class MapGLView(TemplateView, views.MapEngineMixin): @@ -185,3 +186,29 @@ def get_charts(request: HttpRequest) -> response.JsonResponse: return response.JsonResponse( {lookup: charts.CHARTS[lookup](simulation_id=simulation_id).render() for lookup in lookups}, ) + + +def store_pre_result(request: HttpRequest) -> response.JsonResponse: + """ + Store simulation parameters for pre-results. + + Parameters + ---------- + request: HttpRequest + request holding scenario and parameters for selected settings + + Returns + ------- + JsonResponse + holding pre result ID which links to selected scenario and parameter settings. + """ + scenario = request.POST["scenario"] + parameters_raw = request.POST.get("parameters") + parameters = json.loads(parameters_raw) if parameters_raw else {} + parameters = hooks.read_parameters(scenario, parameters, request) + try: + pre_result = models.PreResults.objects.get(scenario=scenario, parameters=parameters) + except models.PreResults.DoesNotExist: + pre_result = models.PreResults.objects.create(scenario=scenario, parameters=parameters) + pre_result.save() + return response.JsonResponse({"pre_result_id": pre_result.id}) diff --git a/digiplan/static/js/event-topics.js b/digiplan/static/js/event-topics.js index 8fc67d02..1702dccc 100644 --- a/digiplan/static/js/event-topics.js +++ b/digiplan/static/js/event-topics.js @@ -35,6 +35,7 @@ const eventTopics = { SETTINGS_CHANGED: "SETTINGS_CHANGED", SIMULATION_STARTED: "SIMULATION_STARTED", SIMULATION_FINISHED: "SIMULATION_FINISHED", + PRE_RESULTS_READY: "PRE_RESULTS_READY", MAP_VIEW_SELECTED: "MAP_VIEW_SELECTED", CHART_VIEW_SELECTED: "CHART_VIEW_SELECTED", diff --git a/digiplan/static/js/results.js b/digiplan/static/js/results.js index be89f748..696d425d 100644 --- a/digiplan/static/js/results.js +++ b/digiplan/static/js/results.js @@ -1,4 +1,4 @@ -import {statusquoDropdown, futureDropdown} from "./elements.js"; +import { statusquoDropdown, futureDropdown } from "./elements.js"; const imageResults = document.getElementById("info_tooltip_results"); const simulation_spinner = document.getElementById("simulation_spinner"); @@ -7,192 +7,237 @@ const mapViewTab = document.getElementById("map-view-tab"); const resultSimNote = document.getElementById("result_simnote"); const SIMULATION_CHECK_TIME = 5000; +const PRE_RESULTS = [ + "electricity_demand_2045", + "electricity_demand_capita_2045", + "heat_demand_2045", + "heat_demand_capita_2045", +]; const resultCharts = { - "electricity_overview": "electricity_overview_chart", - "electricity_autarky": "electricity_autarky_chart", - "ghg_reduction": "ghg_reduction_chart", - "heat_centralized": "heat_centralized_chart", - "heat_decentralized": "heat_decentralized_chart", + electricity_overview: "electricity_overview_chart", + electricity_autarky: "electricity_autarky_chart", + ghg_reduction: "ghg_reduction_chart", + heat_centralized: "heat_centralized_chart", + heat_decentralized: "heat_decentralized_chart", }; // Setup // Disable settings form submit -$('#settings').submit(false); +$("#settings").submit(false); statusquoDropdown.addEventListener("change", function () { - if (statusquoDropdown.value === "") { - deactivateChoropleth(); - PubSub.publish(eventTopics.CHOROPLETH_DEACTIVATED); - } else { - PubSub.publish(mapEvent.CHOROPLETH_SELECTED, statusquoDropdown.value); - } - imageResults.title = statusquoDropdown.options[statusquoDropdown.selectedIndex].title; + if (statusquoDropdown.value === "") { + deactivateChoropleth(); + PubSub.publish(eventTopics.CHOROPLETH_DEACTIVATED); + } else { + PubSub.publish(mapEvent.CHOROPLETH_SELECTED, statusquoDropdown.value); + } + imageResults.title = + statusquoDropdown.options[statusquoDropdown.selectedIndex].title; }); futureDropdown.addEventListener("change", function () { - if (futureDropdown.value === "") { - deactivateChoropleth(); - PubSub.publish(eventTopics.CHOROPLETH_DEACTIVATED); - } else { - PubSub.publish(mapEvent.CHOROPLETH_SELECTED, futureDropdown.value); - } - imageResults.title = futureDropdown.options[futureDropdown.selectedIndex].title; + if (futureDropdown.value === "") { + deactivateChoropleth(); + PubSub.publish(eventTopics.CHOROPLETH_DEACTIVATED); + } else { + PubSub.publish(mapEvent.CHOROPLETH_SELECTED, futureDropdown.value); + } + imageResults.title = + futureDropdown.options[futureDropdown.selectedIndex].title; }); - // Subscriptions PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, simulate); +PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, storePreResults); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, showSimulationSpinner); +PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, disableResultButtons); +PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, hideRegionChart); +PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, resetResultDropdown); +PubSub.subscribe(eventTopics.PRE_RESULTS_READY, enableResultButton); PubSub.subscribe(eventTopics.SIMULATION_STARTED, checkResultsPeriodically); -PubSub.subscribe(eventTopics.SIMULATION_STARTED, hideResultButtons); -PubSub.subscribe(eventTopics.SIMULATION_STARTED, hideRegionChart); -PubSub.subscribe(eventTopics.SIMULATION_STARTED, resetResultDropdown); -PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResultButtons); +PubSub.subscribe(eventTopics.SIMULATION_FINISHED, enableChartButton); +PubSub.subscribe(eventTopics.SIMULATION_FINISHED, enableFutureResults); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResults); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, hideSimulationSpinner); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResultCharts); PubSub.subscribe(mapEvent.CHOROPLETH_SELECTED, showRegionChart); PubSub.subscribe(eventTopics.CHOROPLETH_DEACTIVATED, hideRegionChart); - // Subscriber Functions function simulate(msg) { - const settings = document.getElementById("settings"); - const formData = new FormData(settings); // jshint ignore:line - if (store.cold.task_id != null) { - $.ajax({ - url: "/oemof/terminate", - type: "POST", - data: {task_id: store.cold.task_id}, - success: function () { - store.cold.task_id = null; - } - }); - } + const settings = document.getElementById("settings"); + const formData = new FormData(settings); // jshint ignore:line + if (store.cold.task_id != null) { $.ajax({ - url: "/oemof/simulate", - type: "POST", - processData: false, - contentType: false, - data: formData, - success: function (json) { - store.cold.task_id = json.task_id; - PubSub.publish(eventTopics.SIMULATION_STARTED); - }, + url: "/oemof/terminate", + type: "POST", + data: { task_id: store.cold.task_id }, + success: function () { + store.cold.task_id = null; + }, }); - return logMessage(msg); + } + $.ajax({ + url: "/oemof/simulate", + type: "POST", + async: false, + processData: false, + contentType: false, + data: formData, + success: function (json) { + store.cold.task_id = json.task_id; + PubSub.publish(eventTopics.SIMULATION_STARTED); + }, + }); + return logMessage(msg); +} + +function storePreResults(msg) { + const settings = document.getElementById("settings"); + const formData = new FormData(settings); // jshint ignore:line + $.ajax({ + url: "pre_result", + type: "POST", + processData: false, + contentType: false, + data: formData, + success: function (json) { + map_store.cold.state.pre_result_id = json.pre_result_id; + PubSub.publish(eventTopics.PRE_RESULTS_READY); + }, + }); + return logMessage(msg); } function checkResultsPeriodically(msg) { - setTimeout(checkResults, SIMULATION_CHECK_TIME); - return logMessage(msg); + setTimeout(checkResults, SIMULATION_CHECK_TIME); + return logMessage(msg); } function checkResults() { - $.ajax({ - url: "/oemof/simulate", - type: "GET", - data: {task_id: store.cold.task_id}, - success: function (json) { - if (json.simulation_id == null) { - setTimeout(checkResults, SIMULATION_CHECK_TIME); - } else { - store.cold.task_id = null; - map_store.cold.state.simulation_id = json.simulation_id; - PubSub.publish(eventTopics.SIMULATION_FINISHED); - } - }, - error: function (json) { - store.cold.task_id = null; - map_store.cold.state.simulation_id = null; - PubSub.publish(eventTopics.SIMULATION_FINISHED); - } - }); + $.ajax({ + url: "/oemof/simulate", + type: "GET", + data: { task_id: store.cold.task_id }, + success: function (json) { + if (json.simulation_id == null) { + setTimeout(checkResults, SIMULATION_CHECK_TIME); + } else { + store.cold.task_id = null; + map_store.cold.state.simulation_id = json.simulation_id; + PubSub.publish(eventTopics.SIMULATION_FINISHED); + } + }, + error: function (json) { + store.cold.task_id = null; + map_store.cold.state.simulation_id = null; + PubSub.publish(eventTopics.SIMULATION_FINISHED); + }, + }); } function showResults(msg, simulation_id) { - $.ajax({ - url: "/visualization", - type: "GET", - data: { - simulation_ids: simulation_id, - visualization: "total_system_costs", - }, - success: function (json) { - console.log(json); - }, - }); - return logMessage(msg); + $.ajax({ + url: "/visualization", + type: "GET", + data: { + simulation_ids: simulation_id, + visualization: "total_system_costs", + }, + success: function (json) { + console.log(json); + }, + }); + return logMessage(msg); } function showSimulationSpinner(msg) { - simulation_spinner.hidden = false; - return logMessage(msg); + simulation_spinner.hidden = false; + return logMessage(msg); } function hideSimulationSpinner(msg) { - simulation_spinner.hidden = true; - return logMessage(msg); + simulation_spinner.hidden = true; + return logMessage(msg); } -function showResultButtons(msg) { - chartViewTab.classList.remove("disabled"); - mapViewTab.classList.remove("disabled"); - futureDropdown.disabled = false; - return logMessage(msg); +function enableResultButton(msg) { + futureDropdown.disabled = false; + return logMessage(msg); } -function hideResultButtons(msg) { - chartViewTab.classList.add("disabled"); - mapViewTab.classList.add("disabled"); - futureDropdown.disabled = true; - return logMessage(msg); +function enableChartButton(msg) { + chartViewTab.classList.remove("disabled"); + mapViewTab.classList.remove("disabled"); + return logMessage(msg); } -function showRegionChart(msg, lookup) { - const region_lookup = `${lookup}_region`; - let charts = {}; - if (region_lookup.includes("2045")) { - charts[region_lookup] = "region_chart_2045"; - } else { - charts[region_lookup] = "region_chart_statusquo"; +function enableFutureResults(msg) { + const options = futureDropdown.querySelectorAll("option"); + for (const option of options) { + option.disabled = false; + } + return logMessage(msg); +} + +function disableResultButtons(msg) { + chartViewTab.classList.add("disabled"); + mapViewTab.classList.add("disabled"); + futureDropdown.disabled = true; + const options = futureDropdown.querySelectorAll("option"); + for (const option of options) { + if (!PRE_RESULTS.includes(option.value)) { + option.disabled = true; } - showCharts(charts); - return logMessage(msg); + } + return logMessage(msg); +} + +function showRegionChart(msg, lookup) { + const region_lookup = `${lookup}_region`; + let charts = {}; + if (region_lookup.includes("2045")) { + charts[region_lookup] = "region_chart_2045"; + } else { + charts[region_lookup] = "region_chart_statusquo"; + } + showCharts(charts); + return logMessage(msg); } function hideRegionChart(msg) { - clearChart("region_chart_statusquo"); - clearChart("region_chart_2045"); - resultSimNote.innerText = "Berechnung läuft ..."; - return logMessage(msg); + clearChart("region_chart_statusquo"); + clearChart("region_chart_2045"); + resultSimNote.innerText = "Berechnung läuft ..."; + return logMessage(msg); } function showResultCharts(msg) { - showCharts(resultCharts); - resultSimNote.innerText = ""; - return logMessage(msg); + showCharts(resultCharts); + resultSimNote.innerText = ""; + return logMessage(msg); } function resetResultDropdown(msg) { - futureDropdown.selectedIndex = 0; - return logMessage(msg); + futureDropdown.selectedIndex = 0; + return logMessage(msg); } function showCharts(charts = {}) { - $.ajax({ - url: "/charts", - type: "GET", - data: { - "charts": Object.keys(charts), - "map_state": map_store.cold.state - }, - success: function (chart_options) { - for (const chart in charts) { - createChart(charts[chart], chart_options[chart]); - } - }, - }); + $.ajax({ + url: "/charts", + type: "GET", + data: { + charts: Object.keys(charts), + map_state: map_store.cold.state, + }, + success: function (chart_options) { + for (const chart in charts) { + createChart(charts[chart], chart_options[chart]); + } + }, + }); } From 5786643a4f96841e82fcac4e178bf889feda3d81 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Mon, 15 Apr 2024 16:28:50 +0200 Subject: [PATCH 05/22] Refactored heat demand 2045 to calculate results from datapackage instead of simulation results --- digiplan/map/calculations.py | 33 +++++++-------------------------- digiplan/map/charts.py | 15 +++++++++++---- digiplan/map/choropleths.py | 7 ++++--- digiplan/map/popups.py | 12 +++++++----- digiplan/map/views.py | 10 +++++++++- 5 files changed, 38 insertions(+), 39 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index e461c393..9bc2b978 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -308,7 +308,7 @@ def electricity_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame return demand.astype(float) -def heat_demand_per_municipality() -> pd.DataFrame: +def heat_demand_per_municipality(year: int) -> pd.DataFrame: """ Calculate heat demand per sector per municipality in GWh. @@ -319,7 +319,7 @@ def heat_demand_per_municipality() -> pd.DataFrame: """ demands_raw = datapackage.get_summed_heat_demand_per_municipality() demands_per_sector = pd.concat( - [distributions["cen"]["2022"] + distributions["dec"]["2022"] for distributions in demands_raw.values()], + [distributions["cen"][str(year)] + distributions["dec"][str(year)] for distributions in demands_raw.values()], axis=1, ) demands_per_sector.columns = [ @@ -330,7 +330,7 @@ def heat_demand_per_municipality() -> pd.DataFrame: return demands_per_sector.astype(float) * 1e-3 -def heat_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame: +def heat_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame: """ Calculate heat demand per sector per municipality in GWh in 2045. @@ -339,29 +339,10 @@ def heat_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame: pd.DataFrame Heat demand per municipality (index) and sector (column) """ - results = get_results( - simulation_id, - { - "heat_demand": heat_demand, - }, - ) - demand = results["heat_demand"] - demand.index = demand.index.map(lambda ind: f"heat-demand-{ind[1].split('_')[2]}") - demand = demand.groupby(level=0).sum() - demands_per_sector = datapackage.get_heat_demand() - mappings = { - "hh": "heat-demand-hh", - "cts": "heat-demand-cts", - "ind": "heat-demand-ind", - } - demand = demand.reindex(mappings.values()) - sector_shares = pd.DataFrame( - {sector: demands_per_sector[sector]["2022"] / demands_per_sector[sector]["2022"].sum() for sector in mappings}, - ) - demand = sector_shares * demand.values - demand.columns = demand.columns.map(lambda column: config.SIMULATION_DEMANDS[mappings[column]]) - demand = demand * 1e-3 - return demand.astype(float) + demand = heat_demand_per_municipality(year=2022) + pre_results = models.PreResults.objects.get(pk=pre_result_id) + shares = [pre_results.parameters[key] / 100 for key in ("s_v_3", "s_v_4", "s_v_5")] + return demand.iloc[:] * shares def ghg_reduction(simulation_id: int) -> pd.Series: diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index 9b8d964b..4b718e0c 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -834,7 +834,7 @@ class HeatDemandRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.heat_demand_per_municipality().sum().round(1) + return calculations.heat_demand_per_municipality(year=2022).sum().round(1) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -851,7 +851,7 @@ class HeatDemand2045RegionChart(SimulationChart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - status_quo_data = calculations.heat_demand_per_municipality().sum().round(1) + status_quo_data = calculations.heat_demand_per_municipality(year=2022).sum().round(1) future_data = calculations.heat_demand_per_municipality_2045(self.simulation_id).sum().astype(float).round(1) return list(zip(status_quo_data, future_data)) @@ -873,7 +873,7 @@ def get_chart_data(self) -> None: """Calculate capacities for whole region.""" return ( calculations.calculate_capita_for_value( - pd.DataFrame(calculations.heat_demand_per_municipality().sum()).transpose(), + pd.DataFrame(calculations.heat_demand_per_municipality(year=2022).sum()).transpose(), ).sum() * 1e6 ).round(1) @@ -895,7 +895,7 @@ def get_chart_data(self) -> pd.DataFrame: """Calculate capacities for whole region.""" status_quo_data = ( calculations.calculate_capita_for_value( - pd.DataFrame(calculations.heat_demand_per_municipality().sum()).transpose(), + pd.DataFrame(calculations.heat_demand_per_municipality(year=2022).sum()).transpose(), ).sum() * 1e6 ).round(1) @@ -997,6 +997,13 @@ def get_chart_options(self) -> dict: "batteries_capacity_statusquo_region": BatteriesCapacityRegionChart, } +PRE_RESULTS = ( + "electricity_demand_2045_region", + "electricity_demand_capita_2045_region", + "heat_demand_2045_region", + "heat_demand_capita_2045_region", +) + def create_chart(lookup: str, chart_data: Optional[Any] = None) -> dict: """ diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index aaa1932d..d49eb4c1 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -232,18 +232,19 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class HeatDemandChoropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - return calculations.heat_demand_per_municipality().sum(axis=1).to_dict() + return calculations.heat_demand_per_municipality(year=2022).sum(axis=1).to_dict() class HeatDemand2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - return calculations.heat_demand_per_municipality_2045(self.map_state["simulation_id"]).sum(axis=1).to_dict() + return calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]).sum(axis=1).to_dict() class HeatDemandCapitaChoropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capita_demand = ( - calculations.calculate_capita_for_value(calculations.heat_demand_per_municipality().sum(axis=1)) * 1e6 + calculations.calculate_capita_for_value(calculations.heat_demand_per_municipality(year=2022).sum(axis=1)) + * 1e6 ) return capita_demand.to_dict() diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 41e4fab3..4098d42c 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -777,7 +777,7 @@ class HeatDemandPopup(RegionPopup): title = _("Wärmebedarf") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.heat_demand_per_municipality().round(1) + return calculations.heat_demand_per_municipality(year=2022).round(1) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -794,11 +794,11 @@ class HeatDemand2045Popup(RegionPopup): title = _("Wärmebedarf") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.heat_demand_per_municipality_2045(self.map_state["simulation_id"]) + return calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]) def get_chart_data(self) -> Iterable: """Create capacity chart data for SQ and future scenario.""" - status_quo_data = calculations.heat_demand_per_municipality().loc[self.selected_id].round(1) + status_quo_data = calculations.heat_demand_per_municipality(year=2022).loc[self.selected_id].round(1) future_data = super().get_chart_data().round(1) return list(zip(status_quo_data, future_data)) @@ -818,7 +818,9 @@ class HeatDemandCapitaPopup(RegionPopup): title = _("Wärmebedarf je EinwohnerIn") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return (calculations.calculate_capita_for_value(calculations.heat_demand_per_municipality()) * 1e6).round(1) + return ( + calculations.calculate_capita_for_value(calculations.heat_demand_per_municipality(year=2022)) * 1e6 + ).round(1) def get_region_value(self) -> float: # noqa: D102 return self.detailed_data.sum(axis=1).mean() @@ -849,7 +851,7 @@ def get_chart_data(self) -> Iterable: """Create capacity chart data for SQ and future scenario.""" status_quo_data = ( calculations.calculate_capita_for_value( - calculations.heat_demand_per_municipality(), + calculations.heat_demand_per_municipality(year=2022), ) .loc[self.selected_id] .mul(1e6) diff --git a/digiplan/map/views.py b/digiplan/map/views.py index b7ba338a..3b02784f 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -181,10 +181,18 @@ def get_charts(request: HttpRequest) -> response.JsonResponse: """ lookups = request.GET.getlist("charts[]") simulation_id = None + pre_result_id = None if "map_state[simulation_id]" in request.GET.dict(): simulation_id = int(request.GET.dict()["map_state[simulation_id]"]) + if "map_state[pre_result_id]" in request.GET.dict(): + pre_result_id = int(request.GET.dict()["map_state[pre_result_id]"]) return response.JsonResponse( - {lookup: charts.CHARTS[lookup](simulation_id=simulation_id).render() for lookup in lookups}, + { + lookup: charts.CHARTS[lookup]( + simulation_id=pre_result_id if lookup in charts.PRE_RESULTS else simulation_id, + ).render() + for lookup in lookups + }, ) From e5bd00df3730b8b3ced654f624c0caf6afaf0177 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 18 Apr 2024 09:09:46 +0200 Subject: [PATCH 06/22] Add test for heat demand 2045 --- tests/test_calculations.py | 69 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/tests/test_calculations.py b/tests/test_calculations.py index fcf26fb9..1aa894d9 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -2,6 +2,7 @@ import os import pandas as pd +import pytest from django.test import SimpleTestCase from django_oemof import models from django_oemof import results as oemof_results @@ -10,6 +11,7 @@ from oemof.tabular.postprocessing import core from digiplan.map import calculations, charts +from digiplan.map import models as dm class SimulationTest(SimpleTestCase): @@ -64,6 +66,61 @@ def tearDownClass(cls): # noqa: D102, ANN206 Needed to keep results in test DB pass +class PreResultTest(SimpleTestCase): + """Base class for pre result tests.""" + + databases = ("default",) # Needed, as otherwise django complains about tests using "default" DB + parameters = { + "s_v_1": 100, + "s_v_3": 10, + "s_v_4": 20, + "s_v_5": 30, + "s_w_1": 714, + "w_v_1": 100, + "w_v_3": 100, + "w_v_4": 100, + "w_v_5": 100, + "s_pv_ff_1": 388, + "s_pv_d_1": 298, + "s_h_1": 5, + "s_s_g_1": 1, + "w_d_wp_3": 50, + "w_d_wp_4": 50, + "w_d_wp_5": 50, + "w_z_wp_1": 50, + "w_d_s_1": 100, + "w_z_s_1": 100, + "w_d_wp_1": True, + "s_w_3": True, + "s_w_4": True, + "s_w_4_1": True, + "s_w_4_2": True, + "s_w_5": False, + "s_w_5_1": 50, + "s_w_5_2": 50, + "s_pv_ff_3": 11, + "s_pv_ff_4": 11, + "s_pv_d_3": 5, + "s_pv_d_4": 13, + } + + def setUp(self) -> None: + """Starts/loads oemof simulation for given parameters.""" + try: + pre_result = dm.PreResults.objects.get(scenario="scenario_2045", parameters=self.parameters) + except dm.PreResults.DoesNotExist: + pre_result = dm.PreResults.objects.create(scenario="scenario_2045", parameters=self.parameters) + pre_result.save() + self.pre_result_id = pre_result.id + + def tearDown(self) -> None: # noqa: D102 Needed to keep results in test DB + pass + + @classmethod + def tearDownClass(cls): # noqa: D102, ANN206 Needed to keep results in test DB + pass + + class EnergySharePerMunicipalityTest(SimpleTestCase): """Test energy shares per municipality calculation.""" @@ -199,14 +256,22 @@ def test_heat_demand_all_outputs(self): # noqa: D102 assert list(results.values())[0].iloc[0] > 0 -class HeatDemand2045Test(SimulationTest): +class HeatDemand2045Test(PreResultTest): """Test heat demand calculation in 2045.""" def test_electricity_demand(self): # noqa: D102 - results = calculations.heat_demand_per_municipality_2045(self.simulation_id) + results = calculations.heat_demand_per_municipality_2045(self.pre_result_id) assert len(results) == 20 assert len(results.columns) == 3 + municipality_id = 9 + hh = 171353.1535566939 + cts = 71958.67546243734 + ind = 280765.29433642636 + assert results.iloc[municipality_id, 0] == pytest.approx(hh * 0.1 * 1e-3) + assert results.iloc[municipality_id, 1] == pytest.approx(cts * 0.2 * 1e-3) + assert results.iloc[municipality_id, 2] == pytest.approx(ind * 0.3 * 1e-3) + class RegionalIndependency(SimulationTest): """Test regional dependency calculation.""" From 2ff24d4fb40de27cfdbbd42e190b2abf84352ef4 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 18 Apr 2024 09:17:06 +0200 Subject: [PATCH 07/22] Fix heat demand parameter keys --- digiplan/map/calculations.py | 2 +- tests/test_calculations.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index 9bc2b978..d6159715 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -341,7 +341,7 @@ def heat_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame: """ demand = heat_demand_per_municipality(year=2022) pre_results = models.PreResults.objects.get(pk=pre_result_id) - shares = [pre_results.parameters[key] / 100 for key in ("s_v_3", "s_v_4", "s_v_5")] + shares = [pre_results.parameters[key] / 100 for key in ("w_v_3", "w_v_4", "w_v_5")] return demand.iloc[:] * shares diff --git a/tests/test_calculations.py b/tests/test_calculations.py index 1aa894d9..88432502 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -77,9 +77,9 @@ class PreResultTest(SimpleTestCase): "s_v_5": 30, "s_w_1": 714, "w_v_1": 100, - "w_v_3": 100, - "w_v_4": 100, - "w_v_5": 100, + "w_v_3": 10, + "w_v_4": 20, + "w_v_5": 30, "s_pv_ff_1": 388, "s_pv_d_1": 298, "s_h_1": 5, From 063242ae0f59ef2fd54615ce74491403d28cf78f Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 18 Apr 2024 09:26:14 +0200 Subject: [PATCH 08/22] Refactor electricity demand from pre results --- CHANGELOG.md | 3 +++ digiplan/map/calculations.py | 30 +++++------------------------- digiplan/map/choropleths.py | 6 +++--- digiplan/map/popups.py | 6 +++--- tests/test_calculations.py | 20 ++++++++++++++------ 5 files changed, 28 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ed950f3..1cdd9659 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project tries to adhere to [Semantic Versioning](https://semver.org/spe - coupling of duplicated map panel controls - dependabot +### Changed +- pre results can be shown before simulation has finished + ### Fixed - duplicate loading of JS modules due to missing module support in django staticfile storage - settlement 200m layer is coupled to settlement layer (de)-activation diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index d6159715..a4a85c72 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -273,7 +273,7 @@ def energy_shares_2045_region(simulation_id: int) -> pd.DataFrame: return energy_shares.astype(float).mul(1e2) -def electricity_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame: +def electricity_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame: """ Calculate electricity demand per sector per municipality in GWh in 2045. @@ -282,30 +282,10 @@ def electricity_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame pd.DataFrame Electricity demand per municipality (index) and sector (column) """ - results = get_results( - simulation_id, - { - "electricity_demand": electricity_demand, - }, - ) - demand = results["electricity_demand"][ - results["electricity_demand"].index.get_level_values(1).isin(config.SIMULATION_DEMANDS) - ] - demand = demand.droplevel([0, 2]) - demands_per_sector = datapackage.get_power_demand() - mappings = { - "hh": "ABW-electricity-demand_hh", - "cts": "ABW-electricity-demand_cts", - "ind": "ABW-electricity-demand_ind", - } - demand = demand.reindex(mappings.values()) - sector_shares = pd.DataFrame( - {sector: demands_per_sector[sector]["2022"] / demands_per_sector[sector]["2022"].sum() for sector in mappings}, - ) - demand = sector_shares * demand.values - demand.columns = demand.columns.map(lambda column: config.SIMULATION_DEMANDS[mappings[column]]) - demand = demand * 1e-3 - return demand.astype(float) + demand = electricity_demand_per_municipality(year=2022) + pre_results = models.PreResults.objects.get(pk=pre_result_id) + shares = [pre_results.parameters[key] / 100 for key in ("s_v_3", "s_v_4", "s_v_5")] + return demand.iloc[:] * shares def heat_demand_per_municipality(year: int) -> pd.DataFrame: diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index d49eb4c1..dff12cdf 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -206,7 +206,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class ElectricityDemand2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 return ( - calculations.electricity_demand_per_municipality_2045(self.map_state["simulation_id"]).sum(axis=1).to_dict() + calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]).sum(axis=1).to_dict() ) @@ -223,7 +223,7 @@ class ElectricityDemandCapita2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capita_demand = ( calculations.calculate_capita_for_value( - calculations.electricity_demand_per_municipality_2045(self.map_state["simulation_id"]), + calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]), ).sum(axis=1) * 1e6 ) @@ -253,7 +253,7 @@ class HeatDemandCapita2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capita_demand = ( calculations.calculate_capita_for_value( - calculations.heat_demand_per_municipality_2045(self.map_state["simulation_id"]), + calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]), ).sum(axis=1) * 1e6 ) diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 4098d42c..10da3a7f 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -697,7 +697,7 @@ class ElectricityDemand2045Popup(RegionPopup): title = _("Strombedarf") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.electricity_demand_per_municipality_2045(self.map_state["simulation_id"]) + return calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]) def get_chart_data(self) -> Iterable: """Create capacity chart data for SQ and future scenario.""" @@ -744,7 +744,7 @@ class ElectricityDemandCapita2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_capita_for_value( - calculations.electricity_demand_per_municipality_2045(self.map_state["simulation_id"]), + calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]), ) def get_region_value(self) -> float: # noqa: D102 @@ -841,7 +841,7 @@ class HeatDemandCapita2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_capita_for_value( - calculations.heat_demand_per_municipality_2045(self.map_state["simulation_id"]), + calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]), ).mul(1e6) def get_region_value(self) -> float: # noqa: D102 diff --git a/tests/test_calculations.py b/tests/test_calculations.py index 88432502..62cb8eb1 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -72,9 +72,9 @@ class PreResultTest(SimpleTestCase): databases = ("default",) # Needed, as otherwise django complains about tests using "default" DB parameters = { "s_v_1": 100, - "s_v_3": 10, - "s_v_4": 20, - "s_v_5": 30, + "s_v_3": 11, + "s_v_4": 22, + "s_v_5": 33, "s_w_1": 714, "w_v_1": 100, "w_v_3": 10, @@ -222,13 +222,21 @@ def test_electricity_demand(self): # noqa: D102 assert list(results.values())[0].iloc[1] > 0 -class ElectricityDemand2045Test(SimulationTest): +class ElectricityDemand2045Test(PreResultTest): """Test electricity demand calculation.""" def test_electricity_demand(self): # noqa: D102 - results = calculations.electricity_demand_per_municipality_2045(self.simulation_id) + results = calculations.electricity_demand_per_municipality_2045(self.pre_result_id) assert len(results) == 20 - assert len(results.columns) == 4 + assert len(results.columns) == 3 + + municipality_id = 13 + hh = 15368.324510202196 + cts = 15885.55560626756 + ind = 79900.92318810655 + assert results.iloc[municipality_id, 0] == pytest.approx(hh * 0.11 * 1e-3) + assert results.iloc[municipality_id, 1] == pytest.approx(cts * 0.22 * 1e-3) + assert results.iloc[municipality_id, 2] == pytest.approx(ind * 0.33 * 1e-3) class HeatDemandTest(SimulationTest): From 8794459924a5e2afe5259594522b7c8cb1ccf41c Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Thu, 18 Apr 2024 09:43:41 +0200 Subject: [PATCH 09/22] Always show results charts --- digiplan/static/js/results.js | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/digiplan/static/js/results.js b/digiplan/static/js/results.js index 696d425d..0d22749e 100644 --- a/digiplan/static/js/results.js +++ b/digiplan/static/js/results.js @@ -1,7 +1,6 @@ import { statusquoDropdown, futureDropdown } from "./elements.js"; const imageResults = document.getElementById("info_tooltip_results"); -const simulation_spinner = document.getElementById("simulation_spinner"); const chartViewTab = document.getElementById("chart-view-tab"); const mapViewTab = document.getElementById("map-view-tab"); const resultSimNote = document.getElementById("result_simnote"); @@ -51,16 +50,13 @@ futureDropdown.addEventListener("change", function () { // Subscriptions PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, simulate); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, storePreResults); -PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, showSimulationSpinner); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, disableResultButtons); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, hideRegionChart); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, resetResultDropdown); PubSub.subscribe(eventTopics.PRE_RESULTS_READY, enableResultButton); PubSub.subscribe(eventTopics.SIMULATION_STARTED, checkResultsPeriodically); -PubSub.subscribe(eventTopics.SIMULATION_FINISHED, enableChartButton); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, enableFutureResults); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResults); -PubSub.subscribe(eventTopics.SIMULATION_FINISHED, hideSimulationSpinner); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResultCharts); PubSub.subscribe(mapEvent.CHOROPLETH_SELECTED, showRegionChart); PubSub.subscribe(eventTopics.CHOROPLETH_DEACTIVATED, hideRegionChart); @@ -83,7 +79,6 @@ function simulate(msg) { $.ajax({ url: "/oemof/simulate", type: "POST", - async: false, processData: false, contentType: false, data: formData, @@ -154,28 +149,13 @@ function showResults(msg, simulation_id) { return logMessage(msg); } -function showSimulationSpinner(msg) { - simulation_spinner.hidden = false; - return logMessage(msg); -} - -function hideSimulationSpinner(msg) { - simulation_spinner.hidden = true; - return logMessage(msg); -} - function enableResultButton(msg) { futureDropdown.disabled = false; return logMessage(msg); } -function enableChartButton(msg) { - chartViewTab.classList.remove("disabled"); - mapViewTab.classList.remove("disabled"); - return logMessage(msg); -} - function enableFutureResults(msg) { + resultSimNote.innerText = ""; const options = futureDropdown.querySelectorAll("option"); for (const option of options) { option.disabled = false; @@ -184,8 +164,7 @@ function enableFutureResults(msg) { } function disableResultButtons(msg) { - chartViewTab.classList.add("disabled"); - mapViewTab.classList.add("disabled"); + resultSimNote.innerText = "Berechnung läuft ..."; futureDropdown.disabled = true; const options = futureDropdown.querySelectorAll("option"); for (const option of options) { @@ -211,13 +190,11 @@ function showRegionChart(msg, lookup) { function hideRegionChart(msg) { clearChart("region_chart_statusquo"); clearChart("region_chart_2045"); - resultSimNote.innerText = "Berechnung läuft ..."; return logMessage(msg); } function showResultCharts(msg) { showCharts(resultCharts); - resultSimNote.innerText = ""; return logMessage(msg); } From 11de6f0fb26734c8eafe0754245aa5a098119e9e Mon Sep 17 00:00:00 2001 From: nesnoj Date: Tue, 23 Apr 2024 17:34:18 +0200 Subject: [PATCH 10/22] Resolve migration conflict --- ...6_merge_0045_preresults_0045_regionboundaries.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 digiplan/map/migrations/0046_merge_0045_preresults_0045_regionboundaries.py diff --git a/digiplan/map/migrations/0046_merge_0045_preresults_0045_regionboundaries.py b/digiplan/map/migrations/0046_merge_0045_preresults_0045_regionboundaries.py new file mode 100644 index 00000000..4e0d7091 --- /dev/null +++ b/digiplan/map/migrations/0046_merge_0045_preresults_0045_regionboundaries.py @@ -0,0 +1,13 @@ +# Generated by Django 4.2.11 on 2024-04-23 15:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("map", "0045_preresults"), + ("map", "0045_regionboundaries"), + ] + + operations = [] From 3d0671b22af6b61ba692f3566ab2956f6e79e518 Mon Sep 17 00:00:00 2001 From: nesnoj Date: Wed, 24 Apr 2024 10:52:53 +0200 Subject: [PATCH 11/22] Extend list of pre-results for menu/choropleths --- digiplan/static/js/results.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/digiplan/static/js/results.js b/digiplan/static/js/results.js index 0d22749e..2d6a6297 100644 --- a/digiplan/static/js/results.js +++ b/digiplan/static/js/results.js @@ -7,6 +7,14 @@ const resultSimNote = document.getElementById("result_simnote"); const SIMULATION_CHECK_TIME = 5000; const PRE_RESULTS = [ + "energy_share_2045", + "energy_2045", + "energy_capita_2045", + "energy_square_2045", + "capacity_2045", + "capacity_square_2045", + "wind_turbines_2045", + "wind_turbines_square_2045", "electricity_demand_2045", "electricity_demand_capita_2045", "heat_demand_2045", From 379e47af68a0bbac82b5c700c0d1b76323ab2a0c Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Fri, 26 Apr 2024 14:22:54 +0200 Subject: [PATCH 12/22] User settings are stored in map state; pre-results removed in backend --- digiplan/map/calculations.py | 10 +++---- digiplan/map/choropleths.py | 10 +++---- .../map/migrations/0047_delete_preresults.py | 16 ++++++++++ digiplan/map/models.py | 7 ----- digiplan/map/popups.py | 8 ++--- digiplan/map/urls.py | 1 - digiplan/map/views.py | 30 +------------------ digiplan/static/js/results.js | 22 ++------------ 8 files changed, 31 insertions(+), 73 deletions(-) create mode 100644 digiplan/map/migrations/0047_delete_preresults.py diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index a4a85c72..d88d4456 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -273,7 +273,7 @@ def energy_shares_2045_region(simulation_id: int) -> pd.DataFrame: return energy_shares.astype(float).mul(1e2) -def electricity_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame: +def electricity_demand_per_municipality_2045(user_settings: dict) -> pd.DataFrame: """ Calculate electricity demand per sector per municipality in GWh in 2045. @@ -283,8 +283,7 @@ def electricity_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame Electricity demand per municipality (index) and sector (column) """ demand = electricity_demand_per_municipality(year=2022) - pre_results = models.PreResults.objects.get(pk=pre_result_id) - shares = [pre_results.parameters[key] / 100 for key in ("s_v_3", "s_v_4", "s_v_5")] + shares = [int(user_settings[key]) / 100 for key in ("s_v_3", "s_v_4", "s_v_5")] return demand.iloc[:] * shares @@ -310,7 +309,7 @@ def heat_demand_per_municipality(year: int) -> pd.DataFrame: return demands_per_sector.astype(float) * 1e-3 -def heat_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame: +def heat_demand_per_municipality_2045(user_settings: dict) -> pd.DataFrame: """ Calculate heat demand per sector per municipality in GWh in 2045. @@ -320,8 +319,7 @@ def heat_demand_per_municipality_2045(pre_result_id: int) -> pd.DataFrame: Heat demand per municipality (index) and sector (column) """ demand = heat_demand_per_municipality(year=2022) - pre_results = models.PreResults.objects.get(pk=pre_result_id) - shares = [pre_results.parameters[key] / 100 for key in ("w_v_3", "w_v_4", "w_v_5")] + shares = [int(user_settings[key]) / 100 for key in ("w_v_3", "w_v_4", "w_v_5")] return demand.iloc[:] * shares diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index dff12cdf..a61fa0f0 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -205,9 +205,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class ElectricityDemand2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - return ( - calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]).sum(axis=1).to_dict() - ) + return calculations.electricity_demand_per_municipality_2045(self.map_state).sum(axis=1).to_dict() class ElectricityDemandCapitaChoropleth(Choropleth): # noqa: D101 @@ -223,7 +221,7 @@ class ElectricityDemandCapita2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capita_demand = ( calculations.calculate_capita_for_value( - calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]), + calculations.electricity_demand_per_municipality_2045(self.map_state), ).sum(axis=1) * 1e6 ) @@ -237,7 +235,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class HeatDemand2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - return calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]).sum(axis=1).to_dict() + return calculations.heat_demand_per_municipality_2045(self.map_state).sum(axis=1).to_dict() class HeatDemandCapitaChoropleth(Choropleth): # noqa: D101 @@ -253,7 +251,7 @@ class HeatDemandCapita2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capita_demand = ( calculations.calculate_capita_for_value( - calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]), + calculations.heat_demand_per_municipality_2045(self.map_state), ).sum(axis=1) * 1e6 ) diff --git a/digiplan/map/migrations/0047_delete_preresults.py b/digiplan/map/migrations/0047_delete_preresults.py new file mode 100644 index 00000000..3f9971cb --- /dev/null +++ b/digiplan/map/migrations/0047_delete_preresults.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.11 on 2024-04-26 12:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("map", "0046_merge_0045_preresults_0045_regionboundaries"), + ] + + operations = [ + migrations.DeleteModel( + name="PreResults", + ), + ] diff --git a/digiplan/map/models.py b/digiplan/map/models.py index 4d7c1e4e..cf5310b9 100644 --- a/digiplan/map/models.py +++ b/digiplan/map/models.py @@ -708,10 +708,3 @@ class PotentialAreaWindSTP2018EG(StaticRegionModel): # noqa: D101 class PotentialAreaWindSTP2024VR(StaticRegionModel): # noqa: D101 data_file = "potentialarea_wind_stp_2024_vr" layer = "potentialarea_wind_stp_2024_vr" - - -class PreResults(models.Model): - """Model to store pre results ID related to given parameters.""" - - scenario = models.CharField(max_length=255) - parameters = models.JSONField() diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 10da3a7f..6e7dc2be 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -697,7 +697,7 @@ class ElectricityDemand2045Popup(RegionPopup): title = _("Strombedarf") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]) + return calculations.electricity_demand_per_municipality_2045(self.map_state) def get_chart_data(self) -> Iterable: """Create capacity chart data for SQ and future scenario.""" @@ -744,7 +744,7 @@ class ElectricityDemandCapita2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_capita_for_value( - calculations.electricity_demand_per_municipality_2045(self.map_state["pre_result_id"]), + calculations.electricity_demand_per_municipality_2045(self.map_state), ) def get_region_value(self) -> float: # noqa: D102 @@ -794,7 +794,7 @@ class HeatDemand2045Popup(RegionPopup): title = _("Wärmebedarf") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]) + return calculations.heat_demand_per_municipality_2045(self.map_state) def get_chart_data(self) -> Iterable: """Create capacity chart data for SQ and future scenario.""" @@ -841,7 +841,7 @@ class HeatDemandCapita2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_capita_for_value( - calculations.heat_demand_per_municipality_2045(self.map_state["pre_result_id"]), + calculations.heat_demand_per_municipality_2045(self.map_state), ).mul(1e6) def get_region_value(self) -> float: # noqa: D102 diff --git a/digiplan/map/urls.py b/digiplan/map/urls.py index 0943c72e..9bdb13f8 100644 --- a/digiplan/map/urls.py +++ b/digiplan/map/urls.py @@ -13,5 +13,4 @@ path("popup//", views.get_popup, name="popup"), path("charts", views.get_charts, name="charts"), path("detail_key_results", views.DetailKeyResultsView.as_view(), name="detail_key_results"), - path("pre_result", views.store_pre_result, name="pre_result"), ] diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 91af89f7..5beccac5 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -3,8 +3,6 @@ As map app is SPA, this module contains main view and various API points. """ -import json - from django.conf import settings from django.http import HttpRequest, response from django.utils.translation import gettext_lazy as _ @@ -14,7 +12,7 @@ from digiplan import __version__ from digiplan.map import config, menu -from . import charts, choropleths, forms, hooks, map_config, models, popups, utils +from . import charts, choropleths, forms, map_config, popups, utils class MapGLView(TemplateView, views.MapEngineMixin): @@ -206,32 +204,6 @@ def get_charts(request: HttpRequest) -> response.JsonResponse: ) -def store_pre_result(request: HttpRequest) -> response.JsonResponse: - """ - Store simulation parameters for pre-results. - - Parameters - ---------- - request: HttpRequest - request holding scenario and parameters for selected settings - - Returns - ------- - JsonResponse - holding pre result ID which links to selected scenario and parameter settings. - """ - scenario = request.POST["scenario"] - parameters_raw = request.POST.get("parameters") - parameters = json.loads(parameters_raw) if parameters_raw else {} - parameters = hooks.read_parameters(scenario, parameters, request) - try: - pre_result = models.PreResults.objects.get(scenario=scenario, parameters=parameters) - except models.PreResults.DoesNotExist: - pre_result = models.PreResults.objects.create(scenario=scenario, parameters=parameters) - pre_result.save() - return response.JsonResponse({"pre_result_id": pre_result.id}) - - class DetailKeyResultsView(TemplateView): """Return HTMX-partial for requested detail key results.""" diff --git a/digiplan/static/js/results.js b/digiplan/static/js/results.js index 2d6a6297..f6e8960d 100644 --- a/digiplan/static/js/results.js +++ b/digiplan/static/js/results.js @@ -1,8 +1,6 @@ import { statusquoDropdown, futureDropdown } from "./elements.js"; const imageResults = document.getElementById("info_tooltip_results"); -const chartViewTab = document.getElementById("chart-view-tab"); -const mapViewTab = document.getElementById("map-view-tab"); const resultSimNote = document.getElementById("result_simnote"); const SIMULATION_CHECK_TIME = 5000; @@ -61,7 +59,6 @@ PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, storePreResults); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, disableResultButtons); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, hideRegionChart); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, resetResultDropdown); -PubSub.subscribe(eventTopics.PRE_RESULTS_READY, enableResultButton); PubSub.subscribe(eventTopics.SIMULATION_STARTED, checkResultsPeriodically); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, enableFutureResults); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResults); @@ -101,17 +98,8 @@ function simulate(msg) { function storePreResults(msg) { const settings = document.getElementById("settings"); const formData = new FormData(settings); // jshint ignore:line - $.ajax({ - url: "pre_result", - type: "POST", - processData: false, - contentType: false, - data: formData, - success: function (json) { - map_store.cold.state.pre_result_id = json.pre_result_id; - PubSub.publish(eventTopics.PRE_RESULTS_READY); - }, - }); + const userSettings = Object.fromEntries(formData.entries()); + Object.assign(map_store.cold.state, userSettings); return logMessage(msg); } @@ -157,11 +145,6 @@ function showResults(msg, simulation_id) { return logMessage(msg); } -function enableResultButton(msg) { - futureDropdown.disabled = false; - return logMessage(msg); -} - function enableFutureResults(msg) { resultSimNote.innerText = ""; const options = futureDropdown.querySelectorAll("option"); @@ -173,7 +156,6 @@ function enableFutureResults(msg) { function disableResultButtons(msg) { resultSimNote.innerText = "Berechnung läuft ..."; - futureDropdown.disabled = true; const options = futureDropdown.querySelectorAll("option"); for (const option of options) { if (!PRE_RESULTS.includes(option.value)) { From 24f99087954bced147f56bf7c3b78f4aff058fb4 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Fri, 26 Apr 2024 14:58:53 +0200 Subject: [PATCH 13/22] Fix region charts to work with user settings --- digiplan/map/charts.py | 46 +++++++++++++++++++++++------------ digiplan/map/views.py | 16 +++--------- digiplan/static/js/results.js | 2 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index 4b718e0c..d8859405 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -2,7 +2,7 @@ import json import pathlib -from typing import Any, Optional +from typing import Any, Optional, Union import pandas as pd from django.utils.translation import gettext_lazy as _ @@ -101,19 +101,35 @@ def get_chart_data(self) -> None: return +class PreResultsChart(Chart): + """For charts based on user settings.""" + + def __init__(self, user_settings: dict) -> None: + """ + Init Chart. + + Parameters + ---------- + user_settings: dict + User settings coming from map + """ + self.user_settings = user_settings + super().__init__() + + class SimulationChart(Chart): """For charts based on simulations.""" - def __init__(self, simulation_id: int) -> None: + def __init__(self, user_settings: dict) -> None: """ - Init Detailed Overview Chart. + Init Chart. Parameters ---------- - simulation_id: any - id of used Simulation + user_settings: dict + User settings coming from map """ - self.simulation_id = simulation_id + self.simulation_id = user_settings["simulation_id"] super().__init__() @@ -747,7 +763,7 @@ def get_chart_options(self) -> dict: return chart_options -class ElectricityDemand2045RegionChart(SimulationChart): +class ElectricityDemand2045RegionChart(PreResultsChart): """Chart for regional electricity demand.""" lookup = "electricity_demand" @@ -756,7 +772,7 @@ def get_chart_data(self) -> None: """Calculate capacities for whole region.""" status_quo_data = calculations.electricity_demand_per_municipality().sum().round(1) future_data = ( - calculations.electricity_demand_per_municipality_2045(self.simulation_id).sum().astype(float).round(1) + calculations.electricity_demand_per_municipality_2045(self.user_settings).sum().astype(float).round(1) ) return list(zip(status_quo_data, future_data)) @@ -791,7 +807,7 @@ def get_chart_options(self) -> dict: return chart_options -class ElectricityDemandCapita2045RegionChart(SimulationChart): +class ElectricityDemandCapita2045RegionChart(PreResultsChart): """Chart for regional electricity demand per population in 2045.""" lookup = "electricity_demand" @@ -808,7 +824,7 @@ def get_chart_data(self) -> pd.DataFrame: ( calculations.calculate_capita_for_value( pd.DataFrame( - calculations.electricity_demand_per_municipality_2045(self.simulation_id).sum(), + calculations.electricity_demand_per_municipality_2045(self.user_settings).sum(), ).transpose(), ).sum() * 1e6 @@ -844,7 +860,7 @@ def get_chart_options(self) -> dict: return chart_options -class HeatDemand2045RegionChart(SimulationChart): +class HeatDemand2045RegionChart(PreResultsChart): """Chart for regional heat demand in 2045.""" lookup = "heat_demand" @@ -852,7 +868,7 @@ class HeatDemand2045RegionChart(SimulationChart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" status_quo_data = calculations.heat_demand_per_municipality(year=2022).sum().round(1) - future_data = calculations.heat_demand_per_municipality_2045(self.simulation_id).sum().astype(float).round(1) + future_data = calculations.heat_demand_per_municipality_2045(self.user_settings).sum().astype(float).round(1) return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: @@ -886,7 +902,7 @@ def get_chart_options(self) -> dict: return chart_options -class HeatDemandCapita2045RegionChart(SimulationChart): +class HeatDemandCapita2045RegionChart(PreResultsChart): """Chart for regional heat demand per population in 2045.""" lookup = "heat_demand" @@ -903,7 +919,7 @@ def get_chart_data(self) -> pd.DataFrame: ( calculations.calculate_capita_for_value( pd.DataFrame( - calculations.heat_demand_per_municipality_2045(self.simulation_id).sum(), + calculations.heat_demand_per_municipality_2045(self.user_settings).sum(), ).transpose(), ).sum() * 1e6 @@ -958,7 +974,7 @@ def get_chart_options(self) -> dict: return chart_options -CHARTS: dict[str, type[Chart]] = { +CHARTS: dict[str, Union[type[PreResultsChart], type[SimulationChart]]] = { "detailed_overview": DetailedOverviewChart, "ghg_reduction": GHGReductionChart, "electricity_overview": ElectricityOverviewChart, diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 5beccac5..532fd6a9 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -3,6 +3,8 @@ As map app is SPA, this module contains main view and various API points. """ +import json + from django.conf import settings from django.http import HttpRequest, response from django.utils.translation import gettext_lazy as _ @@ -188,19 +190,9 @@ def get_charts(request: HttpRequest) -> response.JsonResponse: `div_id` is used in frontend to detect chart container. """ lookups = request.GET.getlist("charts[]") - simulation_id = None - pre_result_id = None - if "map_state[simulation_id]" in request.GET.dict(): - simulation_id = int(request.GET.dict()["map_state[simulation_id]"]) - if "map_state[pre_result_id]" in request.GET.dict(): - pre_result_id = int(request.GET.dict()["map_state[pre_result_id]"]) + map_state = json.loads(request.GET.get("map_state", "{}")) return response.JsonResponse( - { - lookup: charts.CHARTS[lookup]( - simulation_id=pre_result_id if lookup in charts.PRE_RESULTS else simulation_id, - ).render() - for lookup in lookups - }, + {lookup: charts.CHARTS[lookup](user_settings=map_state).render() for lookup in lookups}, ) diff --git a/digiplan/static/js/results.js b/digiplan/static/js/results.js index f6e8960d..6975f927 100644 --- a/digiplan/static/js/results.js +++ b/digiplan/static/js/results.js @@ -199,7 +199,7 @@ function showCharts(charts = {}) { type: "GET", data: { charts: Object.keys(charts), - map_state: map_store.cold.state, + map_state: JSON.stringify(map_store.cold.state), }, success: function (chart_options) { for (const chart in charts) { From 1fcf15ec25584943db3ed4d96288c6758afbbab2 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Mon, 29 Apr 2024 16:44:24 +0200 Subject: [PATCH 14/22] Refactor potential shares calculation --- digiplan/map/calculations.py | 102 ++++++++-------------------------- digiplan/map/choropleths.py | 2 +- digiplan/map/config.py | 2 +- digiplan/map/datapackage.py | 41 ++++++-------- digiplan/map/forms.py | 20 ++++--- digiplan/map/menu.py | 46 +++++++-------- digiplan/static/js/sliders.js | 13 +++-- 7 files changed, 80 insertions(+), 146 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index d88d4456..7297a8b6 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -3,7 +3,6 @@ from typing import Optional import pandas as pd -from django.conf import settings from django.utils.translation import gettext_lazy as _ from django_oemof.models import Simulation from django_oemof.results import get_results @@ -112,32 +111,11 @@ def capacities_per_municipality() -> pd.DataFrame: return datapackage.get_capacities_from_datapackage() -def capacities_per_municipality_2045(simulation_id: int) -> pd.DataFrame: +def capacities_per_municipality_2045(parameters: dict) -> pd.DataFrame: """Calculate capacities from 2045 scenario per municipality.""" - results = get_results( - simulation_id, - { - "capacities": Capacities, - }, - ) - renewables = results["capacities"][ - results["capacities"].index.get_level_values(0).isin(config.SIMULATION_RENEWABLES) - ] - mapping = { - "ABW-solar-pv_ground": "pv_ground", - "ABW-solar-pv_rooftop": "pv_roof", - "ABW-wind-onshore": "wind", - "ABW-hydro-ror": "hydro", - "ABW-biomass": "biomass", - } - renewables.index = renewables.index.droplevel(1).map(mapping) - renewables = renewables.reindex(["wind", "pv_roof", "pv_ground", "hydro"]) - - parameters = Simulation.objects.get(pk=simulation_id).parameters - renewables = renewables * calculate_potential_shares(parameters) - renewables["bioenergy"] = 0.0 - renewables["st"] = 0.0 - return renewables.astype(float) + shares = calculate_potential_shares(parameters) + potential_capacities = datapackage.get_potential_values() # in MW + return potential_capacities * shares def energies_per_municipality() -> pd.DataFrame: @@ -471,61 +449,27 @@ def electricity_heat_demand(simulation_id: int) -> pd.Series: return electricity_for_heat_sum -def calculate_potential_shares(parameters: dict) -> pd.DataFrame: +def calculate_potential_shares(parameters: dict) -> dict[str, float]: """Calculate potential shares depending on user settings.""" - # DISAGGREGATION - # Wind - wind_areas = pd.read_csv( - settings.DIGIPIPE_DIR.path("scalars").path("potentialarea_wind_area_stats_muns.csv"), - index_col=0, - ) - if parameters["s_w_3"]: - wind_area_per_mun = wind_areas["stp_2018_vreg"] - elif parameters["s_w_4_1"]: - wind_area_per_mun = wind_areas["stp_2027_vr"] - elif parameters["s_w_4_2"]: - wind_area_per_mun = wind_areas["stp_2027_repowering"] - elif parameters["s_w_5"]: - wind_area_per_mun = ( - wind_areas["stp_2027_search_area_open_area"] * parameters["s_w_5_1"] / 100 - + wind_areas["stp_2027_search_area_forest_area"] * parameters["s_w_5_2"] / 100 + shares = {} + if "wind_year" in parameters: + wind_year = parameters["wind_year"] + share = 1 + if wind_year == "wind_2024": + share = float(parameters["id_s_w_6"]) / float(config.ENERGY_SETTINGS_PANEL["s_w_6"]["max"]) + if wind_year == "wind_2027": + share = float(parameters["id_s_w_7"]) / 100 + shares["wind"] = share + if "id_s_pv_ff_3" in parameters: + shares.update( + { + "pv_soil_quality_low": int(parameters["id_s_pv_ff_3"]) / 100, + "pv_soil_quality_medium": int(parameters["id_s_pv_ff_4"]) / 100, + "pv_permanent_crops": int(parameters["id_s_pv_ff_5"]) / 100, + }, ) - else: - msg = "No wind switch set" - raise KeyError(msg) - wind_share_per_mun = wind_area_per_mun / wind_area_per_mun.sum() - - # PV ground - pv_ground_areas = pd.read_csv( - settings.DIGIPIPE_DIR.path("scalars").path("potentialarea_pv_ground_area_stats_muns.csv", index_col=0), - ) - pv_ground_area_per_mun = ( - pv_ground_areas["agriculture_lfa-off_region"] * parameters["s_pv_ff_3"] / 100 - + pv_ground_areas["road_railway_region"] * parameters["s_pv_ff_4"] / 100 - ) - pv_ground_share_per_mun = pv_ground_area_per_mun / pv_ground_area_per_mun.sum() - - # PV roof - pv_roof_areas = pd.read_csv( - settings.DIGIPIPE_DIR.path("scalars").path("potentialarea_pv_roof_area_stats_muns.csv"), - index_col=0, - ) - pv_roof_area_per_mun = pv_roof_areas["installable_power_total"] - pv_roof_share_per_mun = pv_roof_area_per_mun / pv_roof_area_per_mun.sum() - - # Hydro - hydro_areas = pd.read_csv( - settings.DIGIPIPE_DIR.path("scalars").path("bnetza_mastr_hydro_stats_muns.csv"), - index_col=0, - ) - hydro_area_per_mun = hydro_areas["capacity_net"] - hydro_share_per_mun = hydro_area_per_mun / hydro_area_per_mun.sum() - - shares = pd.concat( - [wind_share_per_mun, pv_roof_share_per_mun, pv_ground_share_per_mun, hydro_share_per_mun], - axis=1, - ) - shares.columns = ["wind", "pv_roof", "pv_ground", "hydro"] + if "id_s_pv_d_3" in parameters: + shares["pv_roof"] = int(parameters["id_s_pv_d_3"]) / 100 return shares diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index a61fa0f0..78aec10c 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -134,7 +134,7 @@ def get_values_per_feature(self) -> pd.DataFrame: # noqa: D102 class Capacity2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> pd.DataFrame: # noqa: D102 - capacities = calculations.capacities_per_municipality_2045(self.map_state["simulation_id"]).sum(axis=1) + capacities = calculations.capacities_per_municipality_2045(self.map_state).sum(axis=1) return capacities.to_dict() diff --git a/digiplan/map/config.py b/digiplan/map/config.py index b3adb9c9..fa717c8d 100644 --- a/digiplan/map/config.py +++ b/digiplan/map/config.py @@ -118,7 +118,7 @@ def get_slider_per_sector() -> dict: STORE_COLD_INIT = { "version": __version__, "slider_marks": get_slider_marks(), - "potentials": datapackage.get_potential_values(), + "potentials": datapackage.get_potential_values().sum().to_dict(), "slider_per_sector": get_slider_per_sector(), "allowedSwitches": ["wind_distance"], "detailTab": {"showPotentialLayers": True}, diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index 690d5d6e..2f3f31c4 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -29,7 +29,7 @@ def get_data_from_sources(sources: Union[Source, list[Source]]) -> pd.DataFrame: for source_file, columns in source_files.items(): source_path = Path(DIGIPIPE_DIR, "scalars", source_file) dfs.append(pd.read_csv(source_path, usecols=columns)) - return pd.concat(dfs) + return pd.concat(dfs, axis=1) def get_employment() -> pd.DataFrame: @@ -164,7 +164,7 @@ def get_thermal_efficiency(component: str) -> float: @cache_memoize(timeout=None) -def get_potential_values() -> dict: +def get_potential_values() -> pd.DataFrame: """ Calculate max_values for sliders. @@ -180,20 +180,16 @@ def get_potential_values() -> dict: "pv_permanent_crops": "pv_ground_elevated", "pv_roof": "pv_roof", } - power_density = json.load(Path.open(Path(settings.DIGIPIPE_DIR, "scalars/technology_data.json")))["power_density"] - - potentials = {} - for key in areas: - if key.startswith("wind"): - potentials[key] = areas[key] * power_density["wind"] - if key.startswith("pv"): - potentials[key] = areas[key] * power_density[pv_density[key]] - return potentials + densities = [ + power_density["wind"] if technology.startswith("wind") else power_density[pv_density[technology]] + for technology in areas + ] + return areas * densities @cache_memoize(timeout=None) -def get_potential_areas(technology: Optional[str] = None) -> dict: +def get_potential_areas(technology: Optional[str] = None) -> pd.DataFrame: """ Return potential areas. @@ -216,23 +212,20 @@ def get_potential_areas(technology: Optional[str] = None) -> dict: "pv_roof": Source("potentialarea_pv_roof_area_stats_muns.csv", "roof_area_pv_potential_sqkm"), } + column_mapping = {source.column: new_column for new_column, source in sources.items()} + column_mapping["wind_2027"] = "wind_2027" + # Add wind for 2027 directly from model data, as it is not included in datapackage - areas = { - "wind_2027": area - if (area := models.Municipality.objects.all().values("area").aggregate(models.Sum("area"))["area__sum"]) - else 0, - } + wind_2027 = pd.DataFrame(models.Municipality.objects.all().values("id", "area")).set_index("id") + wind_2027.columns = ["wind_2027"] if technology is not None: if technology == "wind_2027": - return areas["wind_2027"] + return wind_2027 sources = {technology: sources[technology]} - data = get_data_from_sources(sources.values()) - data = data.sum() - for index in data.index: - # Add extracted data to areas and map source columns to keys accordingly - areas[next(key for key, source in sources.items() if source.column == index)] = data[index] - + areas = get_data_from_sources(sources.values()) + areas = pd.concat([areas, wind_2027], axis=1) + areas.columns = [column_mapping[column] for column in areas.columns] if technology is not None: return areas[technology] return areas diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 6ec4f077..35e60a17 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -117,18 +117,22 @@ class EnergyPanelForm(PanelForm): # noqa: D101 def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: ANN001 """Overwrite init function to add initial key results for detail panels.""" super().__init__(parameters, additional_parameters, **kwargs) - for technology in ("wind_2018", "wind_2024", "wind_2027", "pv_ground", "pv_roof"): + key_results = {} + for wind_year in ("wind_2018", "wind_2024", "wind_2027"): # get initial slider values for wind and pv: - key_results = menu.detail_key_results( - technology, + key_results[wind_year] = menu.detail_key_results( + wind_year=wind_year, id_s_w_6=parameters["s_w_6"]["start"], id_s_w_7=parameters["s_w_7"]["start"], - id_s_pv_ff_3=parameters["s_pv_ff_3"]["start"], - id_s_pv_ff_4=parameters["s_pv_ff_4"]["start"], - id_s_pv_ff_5=parameters["s_pv_ff_5"]["start"], - id_s_pv_d_3=parameters["s_pv_d_3"]["start"], ) - for key, value in key_results.items(): + key_results["pv_ground"] = menu.detail_key_results( + id_s_pv_ff_3=parameters["s_pv_ff_3"]["start"], + id_s_pv_ff_4=parameters["s_pv_ff_4"]["start"], + id_s_pv_ff_5=parameters["s_pv_ff_5"]["start"], + ) + key_results["pv_roof"] = menu.detail_key_results(id_s_pv_d_3=parameters["s_pv_d_3"]["start"]) + for technology, key_result in key_results.items(): + for key, value in key_result.items(): self.extra_content[f"{technology}_key_result_{key}"] = value def get_field_attrs(self, name: str, parameters: dict) -> dict: diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index 73cecbba..e3e7348e 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -1,49 +1,41 @@ """Add calculations for menu items.""" -from . import config, datapackage +from . import calculations, config, datapackage -def detail_key_results(technology: str, **kwargs: dict) -> dict: +def detail_key_results(**kwargs: dict) -> dict: """Calculate detail key results for given technology.""" - areas = datapackage.get_potential_areas() - potential_capacities = datapackage.get_potential_values() # in MW + shares = calculations.calculate_potential_shares(kwargs) + areas = datapackage.get_potential_areas().sum() + potential_capacities = datapackage.get_potential_values().sum() # in MW full_load_hours = datapackage.get_full_load_hours(2045) nominal_power_per_unit = config.TECHNOLOGY_DATA["nominal_power_per_unit"]["wind"] - if technology.startswith("wind"): - percentage = 1 - if technology == "wind_2024": - percentage = float(kwargs["id_s_w_6"]) / float(config.ENERGY_SETTINGS_PANEL["s_w_6"]["max"]) - if technology == "wind_2027": - percentage = float(kwargs["id_s_w_7"]) / 100 + if "wind_year" in kwargs: + wind_year = kwargs["wind_year"] + share = shares["wind"] return { - "area": areas[technology] * 100 * percentage, - "turbines": potential_capacities[technology] / nominal_power_per_unit * percentage, - "energy": potential_capacities[technology] * full_load_hours["wind"] * percentage * 1e-6, - } - if technology == "pv_ground": - percentages = { - "pv_soil_quality_low": int(kwargs["id_s_pv_ff_3"]) / 100, - "pv_soil_quality_medium": int(kwargs["id_s_pv_ff_4"]) / 100, - "pv_permanent_crops": int(kwargs["id_s_pv_ff_5"]) / 100, + "area": areas[wind_year] * 100 * share, + "turbines": potential_capacities[wind_year] / nominal_power_per_unit * share, + "energy": potential_capacities[wind_year] * full_load_hours["wind"] * share * 1e-6, } + if "id_s_pv_ff_3" in kwargs: flh_mapping = { "pv_soil_quality_low": "pv_ground", "pv_soil_quality_medium": "pv_ground_vertical_bifacial", "pv_permanent_crops": "pv_ground_elevated", } return { - "area": sum(areas[pv_type] * 100 * percentages[pv_type] for pv_type in percentages), + "area": sum(areas[pv_type] * 100 * shares[pv_type] for pv_type in shares), "energy": sum( - potential_capacities[pv_type] * full_load_hours[flh_mapping[pv_type]] * percentages[pv_type] - for pv_type in percentages + potential_capacities[pv_type] * full_load_hours[flh_mapping[pv_type]] * shares[pv_type] + for pv_type in shares ) * 1e-6, } - if technology == "pv_roof": - percentage = int(kwargs["id_s_pv_d_3"]) / 100 + if "id_s_pv_d_3" in kwargs: return { - "area": areas[technology] * 100 * percentage, - "energy": potential_capacities[technology] * full_load_hours[technology] * percentage * 1e-6, + "area": areas["pv_roof"] * 100 * shares["pv_roof"], + "energy": potential_capacities["pv_roof"] * full_load_hours["pv_roof"] * shares["pv_roof"] * 1e-6, } - raise KeyError(f"Unknown technology '{technology}'.") + raise KeyError(f"Unknown parameters ({kwargs}).") diff --git a/digiplan/static/js/sliders.js b/digiplan/static/js/sliders.js index 640af9a9..92008d71 100644 --- a/digiplan/static/js/sliders.js +++ b/digiplan/static/js/sliders.js @@ -410,22 +410,21 @@ function highlightPVMapControls(msg) { function adaptDetailKeyResults(msg, data) { const slider_id = data.input[0].id; - let technology; + let wind_year; let target; let url_data = {}; if (slider_id === "id_s_w_6") { - technology = "wind_2024"; + wind_year = "wind_2024"; url_data.id_s_w_6 = data.from; target = "wind_key_results_2024"; } else if (slider_id === "id_s_w_7") { - technology = "wind_2027"; + wind_year = "wind_2027"; url_data.id_s_w_7 = data.from; target = "wind_key_results_2027"; } else if ( ["id_s_pv_ff_3", "id_s_pv_ff_4", "id_s_pv_ff_5"].includes(slider_id) ) { - technology = "pv_ground"; url_data.id_s_pv_ff_3 = $("#id_s_pv_ff_3").data("ionRangeSlider").result.from; url_data.id_s_pv_ff_4 = @@ -434,7 +433,6 @@ function adaptDetailKeyResults(msg, data) { $("#id_s_pv_ff_5").data("ionRangeSlider").result.from; target = "pv_ground_key_results"; } else if (slider_id === "id_s_pv_d_3") { - technology = "pv_roof"; url_data.id_s_pv_d_3 = data.from; target = "pv_roof_key_results"; } else { @@ -442,7 +440,10 @@ function adaptDetailKeyResults(msg, data) { } const query = new URLSearchParams(url_data).toString(); - let url = `/detail_key_results?technology=${technology}&${query}`; + let url = `/detail_key_results?${query}`; + if (wind_year !== undefined) { + url += "&wind_year=" + wind_year; + } fetch(url) .then((response) => { // Check if the response is successful From 70e09e0e62823292e895aae5b9ad10aa14630b58 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 13:23:03 +0200 Subject: [PATCH 15/22] Add hidden input for selected wind year --- digiplan/map/forms.py | 16 ++++++++++++++-- digiplan/static/js/sliders.js | 18 ++++++++++++++++++ digiplan/templates/forms/panel_energy.html | 1 + 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 35e60a17..ff86bd3b 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -4,7 +4,15 @@ from itertools import count from typing import TYPE_CHECKING -from django.forms import BooleanField, FloatField, Form, TextInput, renderers +from django.forms import ( + BooleanField, + CharField, + FloatField, + Form, + HiddenInput, + TextInput, + renderers, +) from django.shortcuts import reverse from django.utils.safestring import mark_safe @@ -60,7 +68,9 @@ def __init__(self, layer: legend.LegendLayer, *args, **kwargs) -> None: # noqa: class PanelForm(TemplateForm): # noqa: D101 def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: D107, ANN001 super().__init__(**kwargs) - self.fields = {item["name"]: item["field"] for item in self.generate_fields(parameters, additional_parameters)} + self.fields.update( + {item["name"]: item["field"] for item in self.generate_fields(parameters, additional_parameters)}, + ) def get_field_attrs(self, name: str, parameters: dict) -> dict: # noqa: ARG002 """Set up field attributes from parameters.""" @@ -114,6 +124,8 @@ def generate_fields(self, parameters: dict, additional_parameters: dict | None = class EnergyPanelForm(PanelForm): # noqa: D101 template_name = "forms/panel_energy.html" + wind_year = CharField(initial="wind_2024", max_length=9, widget=HiddenInput) + def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # noqa: ANN001 """Overwrite init function to add initial key results for detail panels.""" super().__init__(parameters, additional_parameters, **kwargs) diff --git a/digiplan/static/js/sliders.js b/digiplan/static/js/sliders.js index 92008d71..a0d2d698 100644 --- a/digiplan/static/js/sliders.js +++ b/digiplan/static/js/sliders.js @@ -104,6 +104,7 @@ PubSub.subscribe( PubSub.subscribe(eventTopics.PV_CONTROL_ACTIVATED, showPVLayers); PubSub.subscribe(eventTopics.PV_CONTROL_ACTIVATED, highlightPVMapControls); PubSub.subscribe(eventTopics.PV_ROOF_CONTROL_ACTIVATED, showPVRoofLayers); +PubSub.subscribe(eventTopics.WIND_CONTROL_ACTIVATED, updateWindSelection); PubSub.subscribe(eventTopics.WIND_CONTROL_ACTIVATED, showWindLayers); // Subscriber Functions @@ -390,6 +391,23 @@ function showWindLayers(msg) { return logMessage(msg); } +function updateWindSelection(msg) { + const windInput = document.getElementById("id_wind_year"); + const currentWindTab = document + .getElementById("windTab") + .getElementsByClassName("active")[0].id; + if (currentWindTab === "windPastTab") { + windInput.value = "wind_2018"; + } else if (currentWindTab === "windPresentTab") { + windInput.value = "wind_2024"; + } else if (currentWindTab === "windFutureTab") { + windInput.value = "wind_2027"; + } else { + throw Error(`Unknown wind tab '${currentWindTab}' found.`); + } + return logMessage(msg); +} + function hidePotentialLayers(msg) { for (const layer of potentialPVLayers .concat(potentialPVRoofLayers) diff --git a/digiplan/templates/forms/panel_energy.html b/digiplan/templates/forms/panel_energy.html index 588c807c..fb2f4566 100644 --- a/digiplan/templates/forms/panel_energy.html +++ b/digiplan/templates/forms/panel_energy.html @@ -48,6 +48,7 @@

{% trans "Generation" %}

+{{ form.wind_year }} {% include "widgets/slider.html" with field=form.s_w_1 %}
From 654c0449f7d4e29ca4aca481b3da0ef1bb0fb2b6 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 13:23:46 +0200 Subject: [PATCH 16/22] Fix parameter names extraction for settings --- digiplan/map/forms.py | 12 ++++++------ digiplan/map/menu.py | 4 ++-- digiplan/map/views.py | 6 +++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index ff86bd3b..9786ca5b 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -134,15 +134,15 @@ def __init__(self, parameters, additional_parameters=None, **kwargs) -> None: # # get initial slider values for wind and pv: key_results[wind_year] = menu.detail_key_results( wind_year=wind_year, - id_s_w_6=parameters["s_w_6"]["start"], - id_s_w_7=parameters["s_w_7"]["start"], + s_w_6=parameters["s_w_6"]["start"], + s_w_7=parameters["s_w_7"]["start"], ) key_results["pv_ground"] = menu.detail_key_results( - id_s_pv_ff_3=parameters["s_pv_ff_3"]["start"], - id_s_pv_ff_4=parameters["s_pv_ff_4"]["start"], - id_s_pv_ff_5=parameters["s_pv_ff_5"]["start"], + s_pv_ff_3=parameters["s_pv_ff_3"]["start"], + s_pv_ff_4=parameters["s_pv_ff_4"]["start"], + s_pv_ff_5=parameters["s_pv_ff_5"]["start"], ) - key_results["pv_roof"] = menu.detail_key_results(id_s_pv_d_3=parameters["s_pv_d_3"]["start"]) + key_results["pv_roof"] = menu.detail_key_results(s_pv_d_3=parameters["s_pv_d_3"]["start"]) for technology, key_result in key_results.items(): for key, value in key_result.items(): self.extra_content[f"{technology}_key_result_{key}"] = value diff --git a/digiplan/map/menu.py b/digiplan/map/menu.py index e3e7348e..8ef25934 100644 --- a/digiplan/map/menu.py +++ b/digiplan/map/menu.py @@ -19,7 +19,7 @@ def detail_key_results(**kwargs: dict) -> dict: "turbines": potential_capacities[wind_year] / nominal_power_per_unit * share, "energy": potential_capacities[wind_year] * full_load_hours["wind"] * share * 1e-6, } - if "id_s_pv_ff_3" in kwargs: + if "s_pv_ff_3" in kwargs: flh_mapping = { "pv_soil_quality_low": "pv_ground", "pv_soil_quality_medium": "pv_ground_vertical_bifacial", @@ -33,7 +33,7 @@ def detail_key_results(**kwargs: dict) -> dict: ) * 1e-6, } - if "id_s_pv_d_3" in kwargs: + if "s_pv_d_3" in kwargs: return { "area": areas["pv_roof"] * 100 * shares["pv_roof"], "energy": potential_capacities["pv_roof"] * full_load_hours["pv_roof"] * shares["pv_roof"] * 1e-6, diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 532fd6a9..f3c528b4 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -203,4 +203,8 @@ class DetailKeyResultsView(TemplateView): def get_context_data(self, **kwargs) -> dict: # noqa: ARG002 """Get detail key results for requested technology.""" - return {f"key_result_{key}": value for key, value in menu.detail_key_results(**self.request.GET.dict()).items()} + # Cut off leading "id_" from form field id + parameters = { + key[3:] if key.startswith("id_") else key: value for key, value in self.request.GET.dict().items() + } + return {f"key_result_{key}": value for key, value in menu.detail_key_results(**parameters).items()} From 75c0e7426add349446aef4e37c16967ff351ce61 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 13:24:16 +0200 Subject: [PATCH 17/22] Fix capacities per municipality in 2045 --- digiplan/map/calculations.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index 7297a8b6..e58e7830 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -115,7 +115,24 @@ def capacities_per_municipality_2045(parameters: dict) -> pd.DataFrame: """Calculate capacities from 2045 scenario per municipality.""" shares = calculate_potential_shares(parameters) potential_capacities = datapackage.get_potential_values() # in MW - return potential_capacities * shares + + # Use wind profile for selected wind year + potential_capacities = potential_capacities.drop( + columns=[ + column for column in potential_capacities if column.startswith("wind") and column != parameters["wind_year"] + ], + ) + potential_capacities = potential_capacities.rename(columns={parameters["wind_year"]: "wind"}) + + # Apply shares from user selection + potential_capacities = potential_capacities * shares + + # Aggregate pv ground profiles + pv_ground_columns = ["pv_soil_quality_low", "pv_soil_quality_medium", "pv_permanent_crops"] + potential_capacities["pv_ground"] = potential_capacities[pv_ground_columns].sum(axis=1) + potential_capacities = potential_capacities.drop(pv_ground_columns, axis=1) + + return potential_capacities def energies_per_municipality() -> pd.DataFrame: @@ -456,20 +473,20 @@ def calculate_potential_shares(parameters: dict) -> dict[str, float]: wind_year = parameters["wind_year"] share = 1 if wind_year == "wind_2024": - share = float(parameters["id_s_w_6"]) / float(config.ENERGY_SETTINGS_PANEL["s_w_6"]["max"]) + share = float(parameters["s_w_6"]) / float(config.ENERGY_SETTINGS_PANEL["s_w_6"]["max"]) if wind_year == "wind_2027": - share = float(parameters["id_s_w_7"]) / 100 + share = float(parameters["s_w_7"]) / 100 shares["wind"] = share - if "id_s_pv_ff_3" in parameters: + if "s_pv_ff_3" in parameters: shares.update( { - "pv_soil_quality_low": int(parameters["id_s_pv_ff_3"]) / 100, - "pv_soil_quality_medium": int(parameters["id_s_pv_ff_4"]) / 100, - "pv_permanent_crops": int(parameters["id_s_pv_ff_5"]) / 100, + "pv_soil_quality_low": int(parameters["s_pv_ff_3"]) / 100, + "pv_soil_quality_medium": int(parameters["s_pv_ff_4"]) / 100, + "pv_permanent_crops": int(parameters["s_pv_ff_5"]) / 100, }, ) - if "id_s_pv_d_3" in parameters: - shares["pv_roof"] = int(parameters["id_s_pv_d_3"]) / 100 + if "s_pv_d_3" in parameters: + shares["pv_roof"] = int(parameters["s_pv_d_3"]) / 100 return shares From 3a83bc827aaaf6a6c1ec9d54d03d0c01122a67b1 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 13:29:08 +0200 Subject: [PATCH 18/22] Fix square capacities per municipality in 2045 --- digiplan/map/choropleths.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index 78aec10c..fdef049f 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -140,7 +140,7 @@ def get_values_per_feature(self) -> pd.DataFrame: # noqa: D102 class CapacitySquare2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - capacities = calculations.capacities_per_municipality_2045(self.map_state["simulation_id"]) + capacities = calculations.capacities_per_municipality_2045(self.map_state) capacities_per_square = calculations.calculate_square_for_value(capacities) return capacities_per_square.sum(axis=1).to_dict() From 8cab967eda7feee49353f59d92bad01ede8b29b3 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 14:31:27 +0200 Subject: [PATCH 19/22] Adapt new calculation of capacities in 2045 for charts --- digiplan/map/calculations.py | 8 ++++++++ digiplan/map/charts.py | 8 ++++---- digiplan/map/datapackage.py | 5 ++++- digiplan/map/popups.py | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index e58e7830..1848a978 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -132,6 +132,12 @@ def capacities_per_municipality_2045(parameters: dict) -> pd.DataFrame: potential_capacities["pv_ground"] = potential_capacities[pv_ground_columns].sum(axis=1) potential_capacities = potential_capacities.drop(pv_ground_columns, axis=1) + # Set biomass potential to zero + potential_capacities["biomass"] = 0 + + # Correct order (for charts) + potential_capacities = potential_capacities[["wind", "pv_roof", "pv_ground", "hydro", "biomass"]] + return potential_capacities @@ -487,6 +493,8 @@ def calculate_potential_shares(parameters: dict) -> dict[str, float]: ) if "s_pv_d_3" in parameters: shares["pv_roof"] = int(parameters["s_pv_d_3"]) / 100 + if "s_h_1" in parameters: + shares["hydro"] = int(parameters["s_h_1"]) / 100 return shares diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index d8859405..52eefb2a 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -389,7 +389,7 @@ def get_chart_options(self) -> dict: return chart_options -class Capacity2045RegionChart(SimulationChart): +class Capacity2045RegionChart(PreResultsChart): """Chart for regional capacities in 2045.""" lookup = "capacity" @@ -397,7 +397,7 @@ class Capacity2045RegionChart(SimulationChart): def get_chart_data(self) -> list: """Calculate capacities for whole region.""" status_quo_data = calculations.capacities_per_municipality().sum().round(1) - future_data = calculations.capacities_per_municipality_2045(self.simulation_id).sum().astype(float).round(1) + future_data = calculations.capacities_per_municipality_2045(self.user_settings).sum().astype(float).round(1) return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: @@ -431,7 +431,7 @@ def get_chart_options(self) -> dict: return chart_options -class CapacitySquare2045RegionChart(SimulationChart): +class CapacitySquare2045RegionChart(PreResultsChart): """Chart for regional capacities in 2045.""" lookup = "capacity" @@ -447,7 +447,7 @@ def get_chart_data(self) -> list: ) future_data = ( calculations.calculate_square_for_value( - pd.DataFrame(calculations.capacities_per_municipality_2045(self.simulation_id).sum()).transpose(), + pd.DataFrame(calculations.capacities_per_municipality_2045(self.user_settings).sum()).transpose(), ) .sum() .astype(float) diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index 2f3f31c4..9ece6852 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -185,7 +185,10 @@ def get_potential_values() -> pd.DataFrame: power_density["wind"] if technology.startswith("wind") else power_density[pv_density[technology]] for technology in areas ] - return areas * densities + hydro = get_data_from_sources(Source("bnetza_mastr_hydro_stats_muns.csv", "capacity_net")) + potential_values = areas * densities + potential_values["hydro"] = hydro + return potential_values @cache_memoize(timeout=None) diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 6e7dc2be..4fb0d222 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -228,7 +228,7 @@ class Capacity2045Popup(RegionPopup): title = "Installierte Leistung EE" def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.capacities_per_municipality_2045(self.map_state["simulation_id"]) + return calculations.capacities_per_municipality_2045(self.map_state) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -270,7 +270,7 @@ class CapacitySquare2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_square_for_value( - calculations.capacities_per_municipality_2045(self.map_state["simulation_id"]), + calculations.capacities_per_municipality_2045(self.map_state), ) def get_chart_options(self) -> dict: From 619b7057542675fb244f28abf7f4c67b7fe21947 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 15:00:21 +0200 Subject: [PATCH 20/22] Adapt new calculation of energies in 2045 for choropleths, charts & popups --- digiplan/map/calculations.py | 52 +++++++++++------------------------- digiplan/map/charts.py | 12 ++++----- digiplan/map/choropleths.py | 6 ++--- digiplan/map/popups.py | 6 ++--- tests/test_calculations.py | 24 +++-------------- 5 files changed, 31 insertions(+), 69 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index 1848a978..64c051f0 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -4,7 +4,6 @@ import pandas as pd from django.utils.translation import gettext_lazy as _ -from django_oemof.models import Simulation from django_oemof.results import get_results from oemof.tabular.postprocessing import calculations, core, helper @@ -112,7 +111,7 @@ def capacities_per_municipality() -> pd.DataFrame: def capacities_per_municipality_2045(parameters: dict) -> pd.DataFrame: - """Calculate capacities from 2045 scenario per municipality.""" + """Calculate capacities from 2045 scenario per municipality in MW.""" shares = calculate_potential_shares(parameters) potential_capacities = datapackage.get_potential_values() # in MW @@ -133,10 +132,10 @@ def capacities_per_municipality_2045(parameters: dict) -> pd.DataFrame: potential_capacities = potential_capacities.drop(pv_ground_columns, axis=1) # Set biomass potential to zero - potential_capacities["biomass"] = 0 + potential_capacities["bioenergy"] = 0 # Correct order (for charts) - potential_capacities = potential_capacities[["wind", "pv_roof", "pv_ground", "hydro", "biomass"]] + potential_capacities = potential_capacities[["wind", "pv_roof", "pv_ground", "hydro", "bioenergy"]] return potential_capacities @@ -156,32 +155,13 @@ def energies_per_municipality() -> pd.DataFrame: return capacities * full_load_hours.values / 1e3 -def energies_per_municipality_2045(simulation_id: int) -> pd.DataFrame: - """Calculate energies from 2045 scenario per municipality.""" - results = get_results( - simulation_id, - { - "electricity_production": electricity_production, - }, - ) - renewables = results["electricity_production"][ - results["electricity_production"].index.get_level_values(0).isin(config.SIMULATION_RENEWABLES) - ] - mapping = { - "ABW-solar-pv_ground": "pv_ground", - "ABW-solar-pv_rooftop": "pv_roof", - "ABW-wind-onshore": "wind", - "ABW-hydro-ror": "hydro", - "ABW-biomass": "biomass", - } - renewables.index = renewables.index.droplevel([1, 2]).map(mapping) - renewables = renewables.reindex(["wind", "pv_roof", "pv_ground", "hydro"]) - - parameters = Simulation.objects.get(pk=simulation_id).parameters - renewables = renewables * calculate_potential_shares(parameters) - renewables["bioenergy"] = 0.0 - renewables["st"] = 0.0 - return renewables.astype(float) +def energies_per_municipality_2045(parameters: dict) -> pd.DataFrame: + """Calculate energies from 2045 scenario per municipality in MWh.""" + capacities = capacities_per_municipality_2045(parameters) # in MW + full_load_hours = datapackage.get_full_load_hours(year=2045).drop("st").rename({"ror": "hydro"}) + full_load_hours = full_load_hours.reindex(index=["wind", "pv_roof", "pv_ground", "hydro", "bioenergy"]) + energies = capacities * full_load_hours.values + return energies.fillna(0.0) def energy_shares_per_municipality() -> pd.DataFrame: @@ -239,7 +219,7 @@ def electricity_demand_per_municipality(year: int = 2022) -> pd.DataFrame: return demands_per_sector.astype(float) * 1e-3 -def energy_shares_2045_per_municipality(simulation_id: int) -> pd.DataFrame: +def energy_shares_2045_per_municipality(parameters: dict) -> pd.DataFrame: """ Calculate energy shares of renewables from electric demand per municipality in 2045. @@ -248,13 +228,13 @@ def energy_shares_2045_per_municipality(simulation_id: int) -> pd.DataFrame: pd.DataFrame Energy share per municipality (index) and technology (column) """ - energies = energies_per_municipality_2045(simulation_id).mul(1e-3) - demands = electricity_demand_per_municipality_2045(simulation_id).sum(axis=1) + energies = energies_per_municipality_2045(parameters).mul(1e-3) + demands = electricity_demand_per_municipality_2045(parameters).sum(axis=1) energy_shares = energies.div(demands, axis=0) return energy_shares.astype(float).mul(1e2) -def energy_shares_2045_region(simulation_id: int) -> pd.DataFrame: +def energy_shares_2045_region(parameters: dict) -> pd.DataFrame: """ Calculate energy shares of renewables from electric demand for region in 2045. @@ -266,8 +246,8 @@ def energy_shares_2045_region(simulation_id: int) -> pd.DataFrame: pd.DataFrame Energy share per municipality (index) and technology (column) """ - energies = energies_per_municipality_2045(simulation_id) - demands = electricity_demand_per_municipality_2045(simulation_id).sum(axis=1).mul(1e3) + energies = energies_per_municipality_2045(parameters) + demands = electricity_demand_per_municipality_2045(parameters).sum(axis=1).mul(1e3) demand_share = demands / demands.sum() energy_shares = energies.div(demands, axis=0).mul(demand_share, axis=0).sum(axis=0) diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index 52eefb2a..79aef313 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -481,7 +481,7 @@ def get_chart_options(self) -> dict: return chart_options -class Energy2045RegionChart(SimulationChart): +class Energy2045RegionChart(PreResultsChart): """Chart for regional energy.""" lookup = "capacity" @@ -489,7 +489,7 @@ class Energy2045RegionChart(SimulationChart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" status_quo_data = calculations.energies_per_municipality().sum().round(1) - future_data = calculations.energies_per_municipality_2045(self.simulation_id).sum().astype(float) * 1e-3 + future_data = calculations.energies_per_municipality_2045(self.user_settings).sum().astype(float) * 1e-3 future_data = future_data.round(1) return list(zip(status_quo_data, future_data)) @@ -561,7 +561,7 @@ def get_chart_options(self) -> dict: return chart_options -class EnergyCapita2045RegionChart(SimulationChart): +class EnergyCapita2045RegionChart(PreResultsChart): """Chart for regional energy.""" lookup = "capacity" @@ -577,7 +577,7 @@ def get_chart_data(self) -> None: future_data = ( ( calculations.calculate_capita_for_value( - pd.DataFrame(calculations.energies_per_municipality_2045(self.simulation_id).sum()).transpose(), + pd.DataFrame(calculations.energies_per_municipality_2045(self.user_settings).sum()).transpose(), ).sum() ) .astype(float) @@ -617,7 +617,7 @@ def get_chart_options(self) -> dict: return chart_options -class EnergySquare2045RegionChart(SimulationChart): +class EnergySquare2045RegionChart(PreResultsChart): """Chart for regional energy shares per square meter.""" lookup = "capacity" @@ -633,7 +633,7 @@ def get_chart_data(self) -> None: future_data = ( ( calculations.calculate_square_for_value( - pd.DataFrame(calculations.energies_per_municipality_2045(self.simulation_id).sum()).transpose(), + pd.DataFrame(calculations.energies_per_municipality_2045(self.user_settings).sum()).transpose(), ).sum() ) .astype(float) diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index fdef049f..f7c856ba 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -94,7 +94,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class Energy2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - energies = calculations.energies_per_municipality_2045(self.map_state["simulation_id"]).sum(axis=1) * 1e-3 + energies = calculations.energies_per_municipality_2045(self.map_state).sum(axis=1) * 1e-3 return energies.to_dict() @@ -107,7 +107,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class EnergyCapita2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - energies = calculations.energies_per_municipality_2045(self.map_state["simulation_id"]) + energies = calculations.energies_per_municipality_2045(self.map_state) energies_per_capita = calculations.calculate_capita_for_value(energies) return energies_per_capita.sum(axis=1).to_dict() @@ -121,7 +121,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class EnergySquare2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - energies = calculations.energies_per_municipality_2045(self.map_state["simulation_id"]) + energies = calculations.energies_per_municipality_2045(self.map_state) energies_per_square = calculations.calculate_square_for_value(energies) return energies_per_square.sum(axis=1).to_dict() diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 4fb0d222..16149787 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -316,7 +316,7 @@ class Energy2045Popup(RegionPopup): title = _("Gewonnene Energie aus EE") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.energies_per_municipality_2045(self.map_state["simulation_id"]) + return calculations.energies_per_municipality_2045(self.map_state) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -399,7 +399,7 @@ class EnergyCapita2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_capita_for_value( - calculations.energies_per_municipality_2045(self.map_state["simulation_id"]), + calculations.energies_per_municipality_2045(self.map_state), ) def get_chart_options(self) -> dict: @@ -445,7 +445,7 @@ class EnergySquare2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.calculate_square_for_value( - calculations.energies_per_municipality_2045(self.map_state["simulation_id"]), + calculations.energies_per_municipality_2045(self.map_state), ) def get_chart_options(self) -> dict: diff --git a/tests/test_calculations.py b/tests/test_calculations.py index 62cb8eb1..3d8aa68d 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -11,7 +11,6 @@ from oemof.tabular.postprocessing import core from digiplan.map import calculations, charts -from digiplan.map import models as dm class SimulationTest(SimpleTestCase): @@ -69,7 +68,6 @@ def tearDownClass(cls): # noqa: D102, ANN206 Needed to keep results in test DB class PreResultTest(SimpleTestCase): """Base class for pre result tests.""" - databases = ("default",) # Needed, as otherwise django complains about tests using "default" DB parameters = { "s_v_1": 100, "s_v_3": 11, @@ -104,22 +102,6 @@ class PreResultTest(SimpleTestCase): "s_pv_d_4": 13, } - def setUp(self) -> None: - """Starts/loads oemof simulation for given parameters.""" - try: - pre_result = dm.PreResults.objects.get(scenario="scenario_2045", parameters=self.parameters) - except dm.PreResults.DoesNotExist: - pre_result = dm.PreResults.objects.create(scenario="scenario_2045", parameters=self.parameters) - pre_result.save() - self.pre_result_id = pre_result.id - - def tearDown(self) -> None: # noqa: D102 Needed to keep results in test DB - pass - - @classmethod - def tearDownClass(cls): # noqa: D102, ANN206 Needed to keep results in test DB - pass - class EnergySharePerMunicipalityTest(SimpleTestCase): """Test energy shares per municipality calculation.""" @@ -178,18 +160,18 @@ def test_electricity_production(self): # noqa: D102 assert list(results.values())[0].iloc[0] > 0 -class Energies2045Test(SimulationTest): +class Energies2045Test(PreResultTest): """Test electricity production calculation.""" def test_electricity_production(self): # noqa: D102 - calculations.energies_per_municipality_2045(self.simulation_id) + calculations.energies_per_municipality_2045(self.parameters) class Capacities2045Test(SimulationTest): """Test electricity production calculation.""" def test_capacities_2045(self): # noqa: D102 - calculations.capacities_per_municipality_2045(self.simulation_id) + calculations.capacities_per_municipality_2045(self.parameters) class WindTurbines2045Test(SimulationTest): From de8cc1cc5f14e98e6ac6398c404ca3f153a580c2 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 15:02:47 +0200 Subject: [PATCH 21/22] Adapt wind turbine calculation --- digiplan/map/calculations.py | 4 ++-- digiplan/map/charts.py | 8 ++++---- digiplan/map/choropleths.py | 4 ++-- digiplan/map/popups.py | 4 ++-- tests/test_calculations.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index 64c051f0..29cc7208 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -396,9 +396,9 @@ def electricity_from_from_biomass(simulation_id: int) -> pd.Series: return biomass.sum() -def wind_turbines_per_municipality_2045(simulation_id: int) -> pd.DataFrame: +def wind_turbines_per_municipality_2045(parameters: dict) -> pd.DataFrame: """Calculate number of wind turbines from 2045 scenario per municipality.""" - capacities = capacities_per_municipality_2045(simulation_id) + capacities = capacities_per_municipality_2045(parameters) return capacities["wind"] / config.TECHNOLOGY_DATA["nominal_power_per_unit"]["wind"] diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index 79aef313..83c73523 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -667,7 +667,7 @@ def get_chart_options(self) -> dict: return chart_options -class WindTurbines2045RegionChart(SimulationChart): +class WindTurbines2045RegionChart(PreResultsChart): """Chart for regional wind turbines in 2045.""" lookup = "wind_turbines" @@ -675,7 +675,7 @@ class WindTurbines2045RegionChart(SimulationChart): def get_chart_data(self) -> list[int]: """Calculate population for whole region.""" status_quo_data = models.WindTurbine.quantity_per_municipality().sum() - future_data = calculations.wind_turbines_per_municipality_2045(self.simulation_id).sum() + future_data = calculations.wind_turbines_per_municipality_2045(self.user_settings).sum() return [int(status_quo_data), int(future_data)] def get_chart_options(self) -> dict: @@ -711,7 +711,7 @@ def get_chart_options(self) -> dict: return chart_options -class WindTurbinesSquare2045RegionChart(SimulationChart): +class WindTurbinesSquare2045RegionChart(PreResultsChart): """Chart for regional wind turbines per square meter in 2045.""" lookup = "wind_turbines" @@ -728,7 +728,7 @@ def get_chart_data(self) -> list[float]: future_data = ( calculations.calculate_square_for_value( pd.DataFrame( - {"turbines": calculations.wind_turbines_per_municipality_2045(self.simulation_id).sum()}, + {"turbines": calculations.wind_turbines_per_municipality_2045(self.user_settings).sum()}, index=[1], ), ) diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index f7c856ba..eec88510 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -181,7 +181,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class WindTurbines2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - return calculations.wind_turbines_per_municipality_2045(simulation_id=self.map_state["simulation_id"]).to_dict() + return calculations.wind_turbines_per_municipality_2045(self.map_state).to_dict() class WindTurbinesSquareChoropleth(Choropleth): # noqa: D101 @@ -193,7 +193,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class WindTurbinesSquare2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - wind_turbines = calculations.wind_turbines_per_municipality_2045(self.map_state["simulation_id"]) + wind_turbines = calculations.wind_turbines_per_municipality_2045(self.map_state) wind_turbines_square = calculations.calculate_square_for_value(wind_turbines) return wind_turbines_square.to_dict() diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 16149787..eba9659d 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -585,7 +585,7 @@ class NumberWindturbines2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: """Return quantity of wind turbines per municipality (index).""" - return calculations.wind_turbines_per_municipality_2045(self.map_state["simulation_id"]) + return calculations.wind_turbines_per_municipality_2045(self.map_state) def get_region_value(self) -> float: """Return aggregated data of all municipalities and technologies.""" @@ -641,7 +641,7 @@ class NumberWindturbinesSquare2045Popup(RegionPopup): def get_detailed_data(self) -> pd.DataFrame: """Return quantity of wind turbines per municipality (index).""" - wind_turbines = calculations.wind_turbines_per_municipality_2045(self.map_state["simulation_id"]) + wind_turbines = calculations.wind_turbines_per_municipality_2045(self.map_state) return calculations.calculate_square_for_value(wind_turbines) def get_region_value(self) -> float: diff --git a/tests/test_calculations.py b/tests/test_calculations.py index 3d8aa68d..f05d327c 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -174,11 +174,11 @@ def test_capacities_2045(self): # noqa: D102 calculations.capacities_per_municipality_2045(self.parameters) -class WindTurbines2045Test(SimulationTest): +class WindTurbines2045Test(PreResultTest): """Test wind turbine calculation.""" def test_wind_turbines_2045(self): # noqa: D102 - result = calculations.wind_turbines_per_municipality_2045(self.simulation_id) + result = calculations.wind_turbines_per_municipality_2045(self.parameters) assert len(result) == 20 From e80b66507b22cf909a08f46565ba2ee2e605e345 Mon Sep 17 00:00:00 2001 From: Hendrik Huyskens Date: Tue, 30 Apr 2024 15:06:00 +0200 Subject: [PATCH 22/22] Adapt energy share calculation --- digiplan/map/charts.py | 4 ++-- digiplan/map/choropleths.py | 2 +- digiplan/map/popups.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index 83c73523..bf140ffd 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -519,7 +519,7 @@ def get_chart_options(self) -> dict: return chart_options -class EnergyShare2045RegionChart(SimulationChart): +class EnergyShare2045RegionChart(PreResultsChart): """Chart for regional energy shares.""" lookup = "capacity" @@ -527,7 +527,7 @@ class EnergyShare2045RegionChart(SimulationChart): def get_chart_data(self) -> None: """Calculate RES energy shares for whole region.""" status_quo_data = calculations.energy_shares_region().round(1) - future_data = calculations.energy_shares_2045_region(self.simulation_id).round(1) + future_data = calculations.energy_shares_2045_region(self.user_settings).round(1) return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index eec88510..1e002b4d 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -84,7 +84,7 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 class EnergyShare2045Choropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 - return calculations.energy_shares_2045_per_municipality(self.map_state["simulation_id"]).sum(axis=1).to_dict() + return calculations.energy_shares_2045_per_municipality(self.map_state).sum(axis=1).to_dict() class EnergyChoropleth(Choropleth): # noqa: D101 diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index eba9659d..e51327ac 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -357,7 +357,7 @@ class EnergyShare2045Popup(RegionPopup): title = _("Anteil Energie aus EE") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.energy_shares_2045_per_municipality(self.map_state["simulation_id"]) + return calculations.energy_shares_2045_per_municipality(self.map_state) def get_chart_options(self) -> dict: """Overwrite title and unit."""