diff --git a/.gitignore b/.gitignore index 2db3e44d..9ae1b9c1 100644 --- a/.gitignore +++ b/.gitignore @@ -332,7 +332,9 @@ digiplan/media/ !.envs/.local/ .idea/* +dump.rdb /digiplan/data/ /digiplan/static/mvts/ distill/ +/dump.rdb diff --git a/.jshintrc b/.jshintrc index 2b885e31..64f806d5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -33,6 +33,7 @@ "window": false, "subscribeToEvents": false, "createChart": false, + "clearChart": false, "hidePotentialLayers": false }, "strict": "implied" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e4d581a..002d9c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project tries to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.0] - 2023-09-01 +### Added +- electricity autarky chart +- import and export to electricity overview chart +- heat chart overview +- tour explaining statusquo, settings and results +- selection to hide choropleth and region chart in SQ and 2045 dropdown +- results for heat overview chart +- results for GHG reduction chart +- adapt full load hours for renewables +- demand results for 2045 scenario +- onboarding charts + +### Changed +- remove language button +- rework result charts +- rework top navigation and linked pages +- hide main charts in today and result section +- remove transport sector + +### Fixed +- ghg reduction chart +- slider mark at wrong position +- reduce number of attributes in unit popups +- add missing German texts +- remove redundant sliders in settings +- add onboarding texts + ## [0.5.0] - 2023-07-13 ### Added - heat settings set for oemof simulation diff --git a/README.md b/README.md index 270d34e4..281d088d 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,8 @@ You may have to put `sudo` in front of the commands. While using the following commands, exchange _production.yml_ (production server) and _ local.yml_ (local development) accordingly to your needs! +`USE_DISTILLED_MVTS` should be set to False at first in the env file. + ### Start container ``` @@ -167,7 +169,7 @@ Following steps are necessary to refresh/load data on production server: ``` docker-compose -f production.yml run --rm django python manage.py migrate docker-compose -f production.yml run --rm django make empty_data empty_regions -docker-compose -f production.yml run --rm django make load_regions load_data +docker-compose -f production.yml run --rm django make load_regions load_data load_population ``` In order to increase loading speed of vector tiles, the tiles can be prerenderd. This is diff --git a/config/settings/base.py b/config/settings/base.py index 110aa92f..81277adb 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -349,28 +349,42 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 } MAP_ENGINE_CHOROPLETHS = [ - setup.Choropleth("population_statusquo", layers=["municipality"], title=_("Einwohner_innenzahl"), unit=_("")), + setup.Choropleth("population_statusquo", layers=["municipality"], title=_("EinwohnerInnenzahl"), unit=_("EW")), setup.Choropleth( "population_density_statusquo", layers=["municipality"], - title=_("Einwohner_innenzahl pro km²"), + title=_("EinwohnerInnenzahl pro km²"), unit=_(""), ), - setup.Choropleth("employees_statusquo", layers=["municipality"], title=_("Beschäftigte"), unit=_("")), - setup.Choropleth("companies_statusquo", layers=["municipality"], title=_("Betriebe"), unit=_("")), + setup.Choropleth("employees_statusquo", layers=["municipality"], title=_("Beschäftigte"), unit=""), + setup.Choropleth("companies_statusquo", layers=["municipality"], title=_("Betriebe"), unit=""), setup.Choropleth("capacity_statusquo", layers=["municipality"], title=_("Installierte Leistung"), unit=_("MW")), + setup.Choropleth("capacity_2045", layers=["municipality"], title=_("Installierte Leistung"), unit=_("MW")), setup.Choropleth( "capacity_square_statusquo", layers=["municipality"], - title=_("Installierte Leistung pro qm"), - unit=_("MW"), + title=_("Installierte Leistung"), + unit=_("MW/km²"), ), - setup.Choropleth("wind_turbines_statusquo", layers=["municipality"], title=_("Anzahl Windturbinen"), unit=_("")), + setup.Choropleth( + "capacity_square_2045", + layers=["municipality"], + title=_("Installierte Leistung"), + unit=_("MW/km²"), + ), + setup.Choropleth("wind_turbines_statusquo", layers=["municipality"], title=_("Anzahl Windturbinen"), unit=""), + setup.Choropleth("wind_turbines_2045", layers=["municipality"], title=_("Anzahl Windturbinen"), unit=""), setup.Choropleth( "wind_turbines_square_statusquo", layers=["municipality"], - title=_("Anzahl Windturbinen pro qm"), - unit=_(""), + title=_("Anzahl Windturbinen pro km²"), + unit="", + ), + setup.Choropleth( + "wind_turbines_square_2045", + layers=["municipality"], + title=_("Anzahl Windturbinen pro km²"), + unit="", ), setup.Choropleth( "energy_statusquo", @@ -388,7 +402,7 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 "energy_share_statusquo", layers=["municipality"], title=_("Anteil Erneuerbare Energien am Strombedarf"), - unit=_("%"), + unit="%", ), setup.Choropleth( "energy_capita_statusquo", @@ -420,29 +434,53 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 title=_("Strombedarf"), unit=_("GWh"), ), + setup.Choropleth( + "electricity_demand_2045", + layers=["municipality"], + title=_("Strombedarf"), + unit=_("GWh"), + ), setup.Choropleth( "electricity_demand_capita_statusquo", layers=["municipality"], title=_("Strombedarf pro EinwohnerIn"), unit=_("kWh"), ), + setup.Choropleth( + "electricity_demand_capita_2045", + layers=["municipality"], + title=_("Strombedarf pro EinwohnerIn"), + unit=_("kWh"), + ), setup.Choropleth( "heat_demand_statusquo", layers=["municipality"], title=_("Wärmebedarf"), unit=_("GWh"), ), + setup.Choropleth( + "heat_demand_2045", + layers=["municipality"], + title=_("Wärmebedarf"), + unit=_("GWh"), + ), setup.Choropleth( "heat_demand_capita_statusquo", layers=["municipality"], title=_("Wärmebedarf pro EinwohnerIn"), unit=_("kWh"), ), + setup.Choropleth( + "heat_demand_capita_2045", + layers=["municipality"], + title=_("Wärmebedarf pro EinwohnerIn"), + unit=_("kWh"), + ), setup.Choropleth( "batteries_statusquo", layers=["municipality"], title=_("Anzahl Batteriespeicher"), - unit=_("#"), + unit="", ), setup.Choropleth( "batteries_capacity_statusquo", @@ -462,9 +500,13 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 "employees_statusquo", "companies_statusquo", "capacity_statusquo", + "capacity_2045", "capacity_square_statusquo", + "capacity_square_2045", "wind_turbines_statusquo", + "wind_turbines_2045", "wind_turbines_square_statusquo", + "wind_turbines_square_2045", "energy_statusquo", "energy_2045", "energy_share_statusquo", @@ -473,9 +515,13 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 "energy_square_statusquo", "energy_square_2045", "electricity_demand_statusquo", + "electricity_demand_2045", "electricity_demand_capita_statusquo", + "electricity_demand_capita_2045", "heat_demand_statusquo", + "heat_demand_2045", "heat_demand_capita_statusquo", + "heat_demand_capita_2045", "batteries_statusquo", "batteries_capacity_statusquo", ], diff --git a/digiplan/__init__.py b/digiplan/__init__.py index 94761f7d..028d18af 100644 --- a/digiplan/__init__.py +++ b/digiplan/__init__.py @@ -1,4 +1,4 @@ """Digiplan init - holds current version.""" -__version__ = "0.5.0" +__version__ = "0.6.0" __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace("-", ".", 1).split(".")]) diff --git a/digiplan/map/calculations.py b/digiplan/map/calculations.py index 32a09303..207693e0 100644 --- a/digiplan/map/calculations.py +++ b/digiplan/map/calculations.py @@ -1,12 +1,14 @@ """Module for calculations used for choropleths or charts.""" +from typing import Optional + import pandas as pd from django.conf import settings from django.db.models import Sum 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 +from oemof.tabular.postprocessing import calculations, core, helper from digiplan.map import config, datapackage, models @@ -69,8 +71,8 @@ def calculate_capita_for_value(df: pd.DataFrame) -> pd.DataFrame: df = pd.DataFrame(df) # noqa: PD901 population = ( - pd.DataFrame.from_records(models.Population.objects.filter(year=2022).values("id", "value")) - .set_index("id") + pd.DataFrame.from_records(models.Population.objects.filter(year=2022).values("municipality__id", "value")) + .set_index("municipality__id") .sort_index() ) result = df / population.sum().sum() if len(df) == 1 else df.sort_index() / population.to_numpy() @@ -125,6 +127,34 @@ def capacities_per_municipality() -> pd.DataFrame: return pd.concat(capacities, axis=1).fillna(0.0) * 1e-3 +def capacities_per_municipality_2045(simulation_id: int) -> 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_roof", + "ABW-solar-pv_rooftop": "pv_ground", + "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 + + def energies_per_municipality() -> pd.DataFrame: """ Calculate energy of renewables per municipality in GWh. @@ -135,10 +165,7 @@ def energies_per_municipality() -> pd.DataFrame: Energy per municipality (index) and technology (column) """ capacities = capacities_per_municipality() - full_load_hours = pd.Series( - data=[technology_data["2022"] for technology_data in config.TECHNOLOGY_DATA["full_load_hours"].values()], - index=config.TECHNOLOGY_DATA["full_load_hours"].keys(), - ) + full_load_hours = datapackage.get_full_load_hours(year=2022) full_load_hours = full_load_hours.reindex(index=["wind", "pv_roof", "pv_ground", "ror", "bioenergy", "st"]) return capacities * full_load_hours.values / 1e3 @@ -154,7 +181,14 @@ def energies_per_municipality_2045(simulation_id: int) -> pd.DataFrame: renewables = results["electricity_production"][ results["electricity_production"].index.get_level_values(0).isin(config.SIMULATION_RENEWABLES) ] - renewables.index = ["hydro", "pv_ground", "pv_roof", "wind"] + mapping = { + "ABW-solar-pv_ground": "pv_roof", + "ABW-solar-pv_rooftop": "pv_ground", + "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 @@ -181,7 +215,7 @@ def energy_shares_per_municipality() -> pd.DataFrame: return energies.mul(total_demand_share, axis=0) -def electricity_demand_per_municipality() -> pd.DataFrame: +def electricity_demand_per_municipality(year: int = 2022) -> pd.DataFrame: """ Calculate electricity demand per sector per municipality in GWh. @@ -191,7 +225,7 @@ def electricity_demand_per_municipality() -> pd.DataFrame: Electricity demand per municipality (index) and sector (column) """ demands_raw = datapackage.get_power_demand() - demands_per_sector = pd.concat([demand["2022"] for demand in demands_raw.values()], axis=1) + demands_per_sector = pd.concat([demand[str(year)] for demand in demands_raw.values()], axis=1) demands_per_sector.columns = [ _("Electricity Household Demand"), _("Electricity CTS Demand"), @@ -200,6 +234,41 @@ def electricity_demand_per_municipality() -> pd.DataFrame: return demands_per_sector * 1e-3 +def electricity_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame: + """ + Calculate electricity demand per sector per municipality in GWh in 2045. + + Returns + ------- + 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]["2045"] / demands_per_sector[sector]["2045"].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 + + def heat_demand_per_municipality() -> pd.DataFrame: """ Calculate heat demand per sector per municipality in GWh. @@ -222,9 +291,43 @@ def heat_demand_per_municipality() -> pd.DataFrame: return demands_per_sector * 1e-3 -def detailed_overview(simulation_id: int) -> pd.DataFrame: # noqa: ARG001 +def heat_demand_per_municipality_2045(simulation_id: int) -> pd.DataFrame: """ - Calculate data for detailed overview chart from simulation ID. + Calculate heat demand per sector per municipality in GWh in 2045. + + Returns + ------- + 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]["2045"] / demands_per_sector[sector]["2045"].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 + + +def ghg_reduction(simulation_id: int) -> pd.Series: + """ + Calculate data for GHG reduction chart from simulation ID. Parameters ---------- @@ -233,15 +336,21 @@ def detailed_overview(simulation_id: int) -> pd.DataFrame: # noqa: ARG001 Returns ------- - pandas.DataFrame - holding data for detailed overview chart + pandas.Series + holding data for GHG reduction chart """ - # TODO(Hendrik): Calculate real data - # https://github.com/rl-institut-private/digiplan/issues/164 - return pd.DataFrame( - data={"production": [300, 200, 200, 150, 520, 0], "consumption": [0, 0, 0, 0, 0, 1300]}, - index=["wind", "pv_roof", "pv_ground", "biomass", "fossil", "consumption"], + renewables = renewable_electricity_production(simulation_id).sum() + + results = get_results( + simulation_id, + { + "electricity_production": electricity_production, + }, ) + electricity_import = results["electricity_production"].loc[["ABW-electricity-import"]] + electricity_import.index = electricity_import.index.get_level_values(0) + electricity_import["ABW-renewables"] = renewables + return electricity_import * 1e-3 def electricity_from_from_biomass(simulation_id: int) -> pd.Series: @@ -308,6 +417,12 @@ def electricity_from_from_biomass(simulation_id: int) -> pd.Series: return biomass.sum() +def wind_turbines_per_municipality_2045(simulation_id: int) -> pd.DataFrame: + """Calculate number of wind turbines from 2045 scenario per municipality.""" + capacities = capacities_per_municipality_2045(simulation_id) + return capacities["wind"] / config.TECHNOLOGY_DATA["nominal_power_per_unit"]["wind"] + + def electricity_heat_demand(simulation_id: int) -> pd.Series: """ Return electricity demand for heat demand supply. @@ -416,36 +531,29 @@ def calculate_potential_shares(parameters: dict) -> pd.DataFrame: return shares -def capacities_per_municipality_2045(simulation_id: int) -> pd.DataFrame: +def electricity_overview(year: int) -> pd.Series: """ - Return capacities per municipality. + Return static data for electricity overview chart for given year. Parameters ---------- - simulation_id: int - Simulation ID to get results from + year: int + Year, either 2022 or 2045 Returns ------- - pd.DataFrame - containing renewable capacities disaggregated per municipality + pd.Series + containing electricity productions and demands (including heat sector demand for electricity) """ - 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) - ] - renewables.index = renewables.index.get_level_values(0) - - parameters = Simulation.objects.get(pk=simulation_id).parameters - potential_shares = calculate_potential_shares(parameters) - renewable_shares = potential_shares * renewables.values - renewable_shares.columns = renewables.index - return renewable_shares.fillna(0.0) + demand = electricity_demand_per_municipality(year).sum() + production = datapackage.get_full_load_hours(year) * datapackage.get_capacities(year) + production = production[production.notna()] * 1e-3 + return pd.concat([demand, production]) -def electricity_overview(simulation_id: int) -> pd.Series: +def electricity_overview_from_user(simulation_id: int) -> pd.Series: """ - Return data for electricity overview chart. + Return user specific data for electricity overview chart. Parameters ---------- @@ -464,28 +572,119 @@ def electricity_overview(simulation_id: int) -> pd.Series: "electricity_production": electricity_production, }, ) + demand = results["electricity_demand"][ + results["electricity_demand"] + .index.get_level_values(1) + .isin([*list(config.SIMULATION_DEMANDS), "ABW-electricity-export"]) + ] + demand.index = demand.index.get_level_values(1) + + electricity_heat_production_result = electricity_heat_demand(simulation_id) + demand["ABW-electricity-demand_hh"] += electricity_heat_production_result["electricity_heat_demand_hh"] + demand["ABW-electricity-demand_cts"] += electricity_heat_production_result["electricity_heat_demand_cts"] + demand["ABW-electricity-demand_ind"] += electricity_heat_production_result["electricity_heat_demand_ind"] + + renewables = renewable_electricity_production(simulation_id) + + production_import = results["electricity_production"][ + results["electricity_production"].index.get_level_values(0).isin(["ABW-electricity-import"]) + ] + production_import.index = ["ABW-electricity-import"] + + overview_data = pd.concat([renewables, demand, production_import]) + overview_data = overview_data.reindex( + ( + "ABW-wind-onshore", + "ABW-solar-pv_ground", + "ABW-solar-pv_rooftop", + "ABW-biomass", + "ABW-hydro-ror", + "ABW-electricity-demand_cts", + "ABW-electricity-demand_hh", + "ABW-electricity-demand_ind", + "ABW-electricity-import", + "ABW-electricity-export", + ), + ) + overview_data = overview_data * 1e-3 + return overview_data + + +def renewable_electricity_production(simulation_id: int) -> pd.Series: + """Return electricity production from renewables including biomass.""" + 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) ] renewables.index = renewables.index.get_level_values(0) renewables = pd.concat([renewables, pd.Series(electricity_from_from_biomass(simulation_id), index=["ABW-biomass"])]) + return renewables - electricity_import = results["electricity_production"].loc[["ABW-electricity-import"]] - electricity_import.index = electricity_import.index.get_level_values(0) - electricity_export = results["electricity_demand"].loc[:, ["ABW-electricity-export"]] - electricity_export.index = electricity_export.index.get_level_values(1) - demand = results["electricity_demand"][ - results["electricity_demand"].index.get_level_values(1).isin(config.SIMULATION_DEMANDS) - ] - demand.index = demand.index.get_level_values(1) +def get_regional_independency(simulation_id: int) -> tuple[int, int, int, int]: + """Return electricity autarky for 2022 and user scenario.""" + # 2022 + demand = datapackage.get_hourly_electricity_demand(2022) + full_load_hours = datapackage.get_full_load_hours(2022) + capacities = datapackage.get_capacities(2022) + technology_mapping = { + "ABW-wind-onshore": "wind", + "ABW-solar-pv_ground": "pv_ground", + "ABW-solar-pv_rooftop": "pv_roof", + "ABW-hydro-ror": "ror", + } + renewables = [] + for technology, mapped_key in technology_mapping.items(): + renewables.append( + datapackage.get_profile(technology[4:]) * full_load_hours[mapped_key] * capacities[mapped_key], + ) + renewables_summed_flow = pd.concat(renewables, axis=1).sum(axis=1) + # summary + independency_summary_2022 = round(renewables_summed_flow.sum() / demand.sum() * 100) + # temporal + independency_temporal_2022 = renewables_summed_flow - demand + independency_temporal_2022 = round(sum(independency_temporal_2022 > 0) / 8760 * 100) + + # USER + results = get_results( + simulation_id, + {"renewable_flows": renewable_flows, "demand_flows": demand_flows}, + ) + # summary + independency_summary = round(results["renewable_flows"].sum().sum() / results["demand_flows"].sum().sum() * 100) + # temporal + independency_temporal = results["renewable_flows"].sum(axis=1) - results["demand_flows"].sum(axis=1) + independency_temporal = round(sum(independency_temporal > 0) / 8760 * 100) + return independency_summary_2022, independency_temporal_2022, independency_summary, independency_temporal - electricity_heat_production_result = electricity_heat_demand(simulation_id) - return pd.concat([renewables, demand, electricity_heat_production_result]) +def get_heat_production(distribution: str, year: int) -> dict: + """Calculate hea production per technology for given distribution and year.""" + heat_demand_per_sector = datapackage.get_heat_demand(distribution=distribution) + demand = sum(d[str(year)].sum() for d in heat_demand_per_sector.values()) + heat_shares = datapackage.get_heat_capacity_shares(distribution[:3], year=year, include_heatpumps=True) + return {tech: demand * share for tech, share in heat_shares.items()} + + +def get_reduction(simulation_id: int) -> tuple[int, int]: + """Return electricity reduction from renewables and imports.""" + results = get_results( + simulation_id, + {"renewables": reduction_from_renewables, "imports": reduction_from_imports}, + ) + reduction = 2425.9 + res_reduction = results["renewables"].sum() + import_reduction = results["imports"].sum() + summed_reduction = res_reduction + import_reduction + return round(import_reduction / summed_reduction * reduction), round(res_reduction / summed_reduction * reduction) -def heat_overview(simulation_id: int) -> pd.Series: +def heat_overview(simulation_id: int, distribution: str) -> dict: """ Return data for heat overview chart. @@ -493,21 +692,67 @@ def heat_overview(simulation_id: int) -> pd.Series: ---------- simulation_id: int Simulation ID to get results from + distribution: str + central/decentral Returns ------- - pd.Series - containing heat demand for all sectors (hh, cts, ind) + dict + containing heat demand and production for all sectors (hh, cts, ind) and technologies """ + data = {} + for year in (2022, 2045): + demand = datapackage.get_heat_demand(distribution=distribution) + demand = {f"heat-demand-{sector}": demand[str(year)].sum() for sector, demand in demand.items()} + data[str(year)] = demand + data[str(year)].update(get_heat_production(distribution, year)) + results = get_results( simulation_id, - { - "heat_demand": heat_demand, - }, + {"heat_demand": heat_demand, "heat_production": heat_production}, ) - demand = results["heat_demand"] + # Filter distribution: + if distribution == "central": + demand = results["heat_demand"][ + results["heat_demand"].index.get_level_values(0).map(lambda idx: "decentral" not in idx) + ] + production = results["heat_production"][ + results["heat_production"] + .index.get_level_values(0) + .map(lambda idx: "decentral" not in idx and idx not in ("ABW-wood-oven", "ABW-heat-import")) + ] + else: + demand = results["heat_demand"][ + results["heat_demand"].index.get_level_values(0).map(lambda idx: "decentral" in idx) + ] + production = results["heat_production"][ + results["heat_production"] + .index.get_level_values(0) + .map(lambda idx: "decentral" in idx or idx in ("ABW-wood-oven", "ABW-heat-import")) + ] + + # Demand from user scenario: demand.index = demand.index.map(lambda ind: f"heat-demand-{ind[1].split('_')[2]}") - return demand.groupby(level=0).sum() + data["user"] = demand.to_dict() + # Production from user scenario: + production.index = production.index.map(lambda ind: ind[0][4:].split("_")[0]) + mapping = { + "biogas-bpchp": "biogas_bpchp", + "ch4-boiler": "biogas_bpchp", # As in future all methane comes from biogas + "ch4-bpchp": "biogas_bpchp", + "ch4-extchp": "biogas_bpchp", + "electricity-heatpump": "heat_pump", + "electricity-pth": "electricity_direct_heating", + "solar-thermalcollector": "solar_thermal", + "wood-extchp": "wood_extchp", + "wood-bpchp": "wood_bpchp", + "wood-oven": "wood_oven", + } + production = production[production.index.map(lambda idx: idx in mapping)] + production.index = production.index.map(mapping) + production = production.reset_index().groupby("index").sum().iloc[:, 0] + data["user"].update(production.to_dict()) + return data electricity_demand = core.ParametrizedCalculation( @@ -558,3 +803,96 @@ def heat_overview(simulation_id: int) -> pd.Series: ], }, ) + +reduction_from_renewables = core.ParametrizedCalculation( + calculations.AggregatedFlows, + { + "from_nodes": [ + "ABW-wind-onshore", + "ABW-pv_rooftop", + "ABW-pv_ground", + "ABW-hydro-ror", + "ABW-solar-thermalcollector_central", + "ABW-solar-thermalcollector_decentral", + ], + }, +) + +reduction_from_imports = core.ParametrizedCalculation( + calculations.AggregatedFlows, + { + "from_nodes": [ + "ABW-electricity-import", + "ABW-ch4-import", + "ABW-wood-shortage", + "ABW-lignite-shortage", + "ABW-biomass-shortage", + ], + }, +) + + +class Capacities(core.Calculation): + """Oemof postprocessing calculation to read capacities.""" + + name = "capacities" + + def calculate_result(self) -> pd.Series: + """Read attribute "capacity" from parameters.""" + capacities = helper.filter_by_var_name(self.scalar_params, "capacity") + try: + return capacities.unstack(2)["capacity"] # noqa: PD010 + except KeyError: + return pd.Series(dtype="object") + + +class Flows(core.Calculation): + """Oemof postprocessing calculation to read flows.""" + + name = "flows" + + def __init__( + self, + calculator: core.Calculator, + from_nodes: Optional[list[str]] = None, + to_nodes: Optional[list[str]] = None, + ) -> None: + """Init flows.""" + if not from_nodes and not to_nodes: + msg = "Either from or to nodes must be set" + raise ValueError(msg) + self.from_nodes = from_nodes + self.to_nodes = to_nodes + + super().__init__(calculator) + + def calculate_result(self) -> pd.DataFrame: + """Read attribute "capacity" from parameters.""" + from_node_flows = pd.DataFrame() + to_node_flows = pd.DataFrame() + if self.from_nodes: + from_node_flows = self.sequences.iloc[:, self.sequences.columns.get_level_values(0).isin(self.from_nodes)] + from_node_flows.columns = from_node_flows.columns.droplevel([1, 2]) + if self.to_nodes: + to_node_flows = self.sequences.iloc[:, self.sequences.columns.get_level_values(1).isin(self.to_nodes)] + to_node_flows.columns = to_node_flows.columns.droplevel([0, 2]) + return pd.concat([from_node_flows, to_node_flows], axis=1) + + +renewable_flows = core.ParametrizedCalculation( + Flows, + { + "from_nodes": ["ABW-wind-onshore", "ABW-solar-pv_rooftop", "ABW-solar-pv_ground", "ABW-hydro-ror"], + }, +) + +demand_flows = core.ParametrizedCalculation( + Flows, + { + "to_nodes": [ + "ABW-electricity-demand_hh", + "ABW-electricity-demand_cts", + "ABW-electricity-demand_ind", + ], + }, +) diff --git a/digiplan/map/charts.py b/digiplan/map/charts.py index 82198553..8d881f70 100644 --- a/digiplan/map/charts.py +++ b/digiplan/map/charts.py @@ -117,221 +117,98 @@ def __init__(self, simulation_id: int) -> None: super().__init__() -class DetailedOverviewChart(Chart): +class DetailedOverviewChart(SimulationChart): """Detailed Overview Chart.""" lookup = "detailed_overview" - def __init__(self, simulation_id: int) -> None: - """ - Init Detailed Overview Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - def get_chart_data(self): # noqa: D102, ANN201 - return calculations.detailed_overview(simulation_id=self.simulation_id) + return calculations.electricity_overview(simulation_id=self.simulation_id) def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] - + for i, item in enumerate(self.chart_options["series"]): + item["data"][1] = self.chart_data.iloc[i] return self.chart_options -class CTSOverviewChart(Chart): - """CTS Overview Chart. Shows greenhouse gas emissions.""" - - lookup = "ghg_overview" +class GHGReductionChart(SimulationChart): + """GHG Reduction Chart. Shows greenhouse gas emissions.""" - def __init__(self, simulation_id: int) -> None: - """ - Init CTS Overview Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() + lookup = "ghg_reduction" def get_chart_data(self): # noqa: D102, ANN201 - return calculations.detailed_overview(simulation_id=self.simulation_id) + return calculations.get_reduction(simulation_id=self.simulation_id) def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][2] = self.chart_data[profile] - + # Enter import and energy from renewables + for i, item in enumerate(self.chart_options["series"][7:9]): + item["data"][1] = self.chart_data[i] + # Calculate emission offset + summed_emissions_2019 = sum(item["data"][0] for item in self.chart_options["series"][:7]) + self.chart_options["series"][0]["data"][1] = summed_emissions_2019 - sum(self.chart_data) return self.chart_options -class ElectricityOverviewChart(Chart): +class ElectricityOverviewChart(SimulationChart): """Chart for electricity overview.""" lookup = "electricity_overview" - def __init__(self, simulation_id: int) -> None: - """Store simulation ID.""" - self.simulation_id = simulation_id - super().__init__() - def get_chart_data(self): # noqa: ANN201 """Get chart data from electricity overview calculation.""" - return calculations.electricity_overview(simulation_id=self.simulation_id) - - def render(self) -> dict: - """Overwrite render function.""" - self.chart_options["series"][0]["data"][2] = self.chart_data["ABW-wind-onshore"] - self.chart_options["series"][1]["data"][2] = self.chart_data["ABW-solar-pv_ground"] - self.chart_options["series"][2]["data"][2] = self.chart_data["ABW-solar-pv_rooftop"] - self.chart_options["series"][3]["data"][2] = self.chart_data["ABW-biomass"] - self.chart_options["series"][4]["data"][2] = self.chart_data["ABW-hydro-ror"] - self.chart_options["series"][5]["data"][0] = self.chart_data["ABW-electricity-demand_cts"] - self.chart_options["series"][6]["data"][0] = self.chart_data["electricity_heat_demand_cts"] - self.chart_options["series"][7]["data"][0] = self.chart_data["ABW-electricity-demand_hh"] - self.chart_options["series"][8]["data"][0] = self.chart_data["electricity_heat_demand_hh"] - self.chart_options["series"][9]["data"][0] = self.chart_data["ABW-electricity-demand_ind"] - self.chart_options["series"][10]["data"][0] = self.chart_data["electricity_heat_demand_ind"] - self.chart_options["series"][11]["data"][0] = self.chart_data["ABW-electricity-bev_charging"] - return self.chart_options - - -class ElectricityCTSChart(Chart): - """Electricity CTS Chart. Shows greenhouse gas emissions.""" - - lookup = "electricity_ghg" - - def __init__(self, simulation_id: int) -> None: - """ - Init Electricity CTS Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - - def get_chart_data(self): # noqa: D102, ANN201 - return calculations.detailed_overview(simulation_id=self.simulation_id) + return { + "2022": calculations.electricity_overview(2022), + "2045": calculations.electricity_overview(2045), + "user": calculations.electricity_overview_from_user(simulation_id=self.simulation_id), + } def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][2] = self.chart_data[profile] - + mapping = { + "Aufdach-PV": ("ABW-solar-pv_rooftop", "pv_roof"), + "Bioenergie": ("ABW-biomass", ""), + "Export*": ("ABW-electricity-export", ""), + "Freiflächen-PV": ("ABW-solar-pv_grund", "pv_ground"), + "Import*": ("ABW-electricity-import", ""), + "Verbrauch GHD": ("ABW-electricity-demand_cts", "Strombedarf GDP"), + "Verbrauch Haushalte": ("ABW-electricity-demand_hh", "Strombedarf Haushalte"), + "Verbrauch Industrie": ("ABW-electricity-demand_ind", "Strombedarf Industrie"), + "Wasserkraft": ("ABW-hydro-ror", "ror"), + "Windenergie": ("ABW-wind-onshore", "wind"), + } + for _i, item in enumerate(self.chart_options["series"]): + mapped_keys = mapping[item["name"]] + item["data"][0] = round( + self.chart_data["2022"].get(mapped_keys[0], self.chart_data["2022"].get(mapped_keys[1], 0.0)), + ) + item["data"][1] = round( + self.chart_data["user"].get(mapped_keys[0], self.chart_data["user"].get(mapped_keys[1], 0.0)), + ) + item["data"][2] = round( + self.chart_data["2045"].get(mapped_keys[0], self.chart_data["2045"].get(mapped_keys[1], 0.0)), + ) return self.chart_options -class HeatOverviewChart(Chart): - """Heat Overview Chart.""" +class ElectricityAutarkyChart(SimulationChart): + """Chart for electricity autarky.""" - lookup = "overview_heat" - - def __init__(self, simulation_id: int) -> None: - """ - Init Heat Overview Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - - def get_chart_data(self): # noqa: D102, ANN201 - return calculations.heat_overview(simulation_id=self.simulation_id) + lookup = "electricity_autarky" - def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] - - return self.chart_options - - -class HeatProductionChart(Chart): - """Heat Production Chart. Shows decentralized and centralized heat.""" - - lookup = "decentralized_centralized_heat" - - def __init__(self, simulation_id: int) -> None: - """ - Init Heat Production Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - - def get_chart_data(self): # noqa: D102, ANN201 - return calculations.heat_overview(simulation_id=self.simulation_id) - - def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] - - return self.chart_options - - -class MobilityOverviewChart(Chart): - """Mobility Overview Chart. Shows Number of Cars.""" - - lookup = "mobility_overview" - - def __init__(self, simulation_id: int) -> None: - """ - Init Mobility Overview Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - - def get_chart_data(self): # noqa: D102, ANN201 - return calculations.heat_overview(simulation_id=self.simulation_id) + def get_chart_data(self): # noqa: ANN201 + """Get chart data from electricity overview calculation.""" + return calculations.get_regional_independency(self.simulation_id) def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] - + for i, item in enumerate(self.chart_options["series"]): + item["data"][0] = self.chart_data[i + 2] + item["data"][1] = self.chart_data[i] return self.chart_options -class MobilityCTSChart(Chart): - """Mobility CTS Chart. Shows greenhouse gas emissions.""" - - lookup = "mobility_ghg" +class ElectricityCTSChart(SimulationChart): + """Electricity CTS Chart. Shows greenhouse gas emissions.""" - def __init__(self, simulation_id: int) -> None: - """ - Init Mobility CTS Chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() + lookup = "electricity_autarky" def get_chart_data(self): # noqa: D102, ANN201 return calculations.detailed_overview(simulation_id=self.simulation_id) @@ -344,83 +221,59 @@ def render(self) -> dict: # noqa: D102 return self.chart_options -class GhgHistoryChart(Chart): - """GHG history chart.""" - - lookup = "ghg_history" - - def __init__(self, simulation_id: int) -> None: - """ - Init GHG history chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - - def get_chart_data(self): # noqa: D102, ANN201 - # TODO(Hendrik): Get static data from digipipe datapackage # noqa: TD003 - return pd.DataFrame() +class HeatStructureChart(SimulationChart): + """Heat Structure Chart.""" def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] - + mapping = { + "Erdgas": ("methane", "ch4_bpchp", "ch4_extchp"), + "Heizöl": ("fuel_oil",), + "Holz": ("wood_oven", "wood_bpchp", "wood_extchp"), + "Wärmepumpe": ("heat_pump",), + "El. Direktheizung": ("electricity_direct_heating",), + "Biogas": ("biogas_bpchp",), + "Solarthermie": ("solar_thermal",), + "Sonstige": ("other",), + "Verbrauch Haushalte": ("heat-demand-hh",), + "Verbrauch GHD": ("heat-demand-cts",), + "Verbrauch Industrie": ("heat-demand-ind",), + } + for _i, item in enumerate(self.chart_options["series"]): + item["data"][0] = round( + sum(self.chart_data["2045"].get(entry, 0.0) for entry in mapping[item["name"]]) * 1e-3, + ) + item["data"][1] = round( + sum(self.chart_data["user"].get(entry, 0.0) for entry in mapping[item["name"]]) * 1e-3, + ) + item["data"][2] = round( + sum(self.chart_data["2022"].get(entry, 0.0) for entry in mapping[item["name"]]) * 1e-3, + ) return self.chart_options -class GhgReductionChart(Chart): - """GHG reduction chart.""" +class HeatStructureCentralChart(HeatStructureChart): + """Heat structure for centralized heat.""" - lookup = "ghg_reduction" + lookup = "heat_centralized" - def __init__(self, simulation_id: int) -> None: - """ - Init GHG reduction chart. + def get_chart_data(self): # noqa: D102, ANN201 + return calculations.heat_overview(simulation_id=self.simulation_id, distribution="central") - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - def get_chart_data(self): # noqa: D102, ANN201 - # TODO(Hendrik): Get static data (1st column) from # noqa: TD003 - # digipipe datapackage - # and calc reductions for 2nd column. TD003 - return pd.DataFrame() +class HeatStructureDecentralChart(HeatStructureChart): + """Heat structure for decentralized heat.""" - def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] + lookup = "heat_decentralized" - return self.chart_options + def get_chart_data(self): # noqa: D102, ANN201 + return calculations.heat_overview(simulation_id=self.simulation_id, distribution="decentral") -class GhgHistoryChart(Chart): +class GhgHistoryChart(SimulationChart): """GHG history chart.""" lookup = "ghg_history" - def __init__(self, simulation_id: int) -> None: - """ - Init GHG history chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - def get_chart_data(self): # noqa: D102, ANN201 # TODO(Hendrik): Get static data from digipipe datapackage # noqa: TD003 return pd.DataFrame() @@ -433,37 +286,6 @@ def render(self) -> dict: # noqa: D102 return self.chart_options -class GhgReductionChart(Chart): - """GHG reduction chart.""" - - lookup = "ghg_reduction" - - def __init__(self, simulation_id: int) -> None: - """ - Init GHG reduction chart. - - Parameters - ---------- - simulation_id: any - id of used Simulation - """ - self.simulation_id = simulation_id - super().__init__() - - def get_chart_data(self): # noqa: D102, ANN201 - # TODO(Hendrik): Get static data (1st column) from # noqa: TD003 - # digipipe datapackage - # and calc reductions for 2nd column. TD003 - return pd.DataFrame() - - def render(self) -> dict: # noqa: D102 - for item in self.chart_options["series"]: - profile = config.SIMULATION_NAME_MAPPING[item["name"]] - item["data"][1] = self.chart_data[profile] - - return self.chart_options - - class PopulationRegionChart(Chart): """Chart for regional population.""" @@ -512,7 +334,7 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] - chart_options["yAxis"]["name"] = _("") + chart_options["yAxis"]["name"] = "Beschäftigte" del chart_options["series"][0]["name"] return chart_options @@ -530,7 +352,7 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] - chart_options["yAxis"]["name"] = _("") + chart_options["yAxis"]["name"] = "Betriebe" del chart_options["series"][0]["name"] return chart_options @@ -542,11 +364,30 @@ class CapacityRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.capacities_per_municipality().sum() + return calculations.capacities_per_municipality().sum().round(1) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + return chart_options + + +class Capacity2045RegionChart(SimulationChart): + """Chart for regional capacities in 2045.""" + + lookup = "capacity" + + 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) + return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] del chart_options["title"]["text"] return chart_options @@ -558,9 +399,13 @@ class CapacitySquareRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.calculate_square_for_value( - pd.DataFrame(calculations.capacities_per_municipality().sum()).transpose(), - ).sum() + return ( + calculations.calculate_square_for_value( + pd.DataFrame(calculations.capacities_per_municipality().sum()).transpose(), + ) + .round(2) + .sum() + ) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -570,6 +415,39 @@ def get_chart_options(self) -> dict: return chart_options +class CapacitySquare2045RegionChart(SimulationChart): + """Chart for regional capacities in 2045.""" + + lookup = "capacity" + + def get_chart_data(self) -> list: + """Calculate capacities for whole region.""" + status_quo_data = ( + calculations.calculate_square_for_value( + pd.DataFrame(calculations.capacities_per_municipality().sum()).transpose(), + ) + .sum() + .round(2) + ) + future_data = ( + calculations.calculate_square_for_value( + pd.DataFrame(calculations.capacities_per_municipality_2045(self.simulation_id).sum()).transpose(), + ) + .sum() + .astype(float) + .round(2) + ) + return list(zip(status_quo_data, future_data)) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + chart_options["yAxis"]["name"] = _("MW") + del chart_options["title"]["text"] + return chart_options + + class EnergyRegionChart(Chart): """Chart for regional energy.""" @@ -577,7 +455,7 @@ class EnergyRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.energies_per_municipality().sum() + return calculations.energies_per_municipality().sum().round(1) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -594,15 +472,16 @@ class Energy2045RegionChart(SimulationChart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - status_quo_data = calculations.energies_per_municipality().sum() - future_data = calculations.energies_per_municipality_2045(self.simulation_id).sum() + 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 = future_data.round(1) return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] - chart_options["yAxis"]["name"] = _("MWh") + chart_options["yAxis"]["name"] = _("GWh") chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -614,7 +493,7 @@ class EnergyShareRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.energy_shares_per_municipality().sum() + return calculations.energy_shares_per_municipality().sum().round(1) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -636,13 +515,47 @@ def get_chart_data(self) -> None: pd.DataFrame(calculations.energies_per_municipality().sum()).transpose(), ).sum() * 1e3 + ).round(1) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = _("MWh") + return chart_options + + +class EnergyCapita2045RegionChart(SimulationChart): + """Chart for regional energy.""" + + lookup = "capacity" + + def get_chart_data(self) -> None: + """Calculate capacities for whole region.""" + status_quo_data = ( + calculations.calculate_capita_for_value( + pd.DataFrame(calculations.energies_per_municipality().sum()).transpose(), + ).sum() + * 1e3 + ).round(1) + future_data = ( + ( + calculations.calculate_capita_for_value( + pd.DataFrame(calculations.energies_per_municipality_2045(self.simulation_id).sum()).transpose(), + ).sum() + ) + .astype(float) + .round(1) ) + future_data = future_data.round(1) + return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] chart_options["yAxis"]["name"] = _("MWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -658,13 +571,47 @@ def get_chart_data(self) -> None: pd.DataFrame(calculations.energies_per_municipality().sum()).transpose(), ).sum() * 1e3 + ).round(1) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = _("MWh") + return chart_options + + +class EnergySquare2045RegionChart(SimulationChart): + """Chart for regional energy shares per square meter.""" + + lookup = "capacity" + + def get_chart_data(self) -> None: + """Calculate capacities for whole region.""" + status_quo_data = ( + calculations.calculate_square_for_value( + pd.DataFrame(calculations.energies_per_municipality().sum()).transpose(), + ).sum() + * 1e3 + ).round(1) + future_data = ( + ( + calculations.calculate_square_for_value( + pd.DataFrame(calculations.energies_per_municipality_2045(self.simulation_id).sum()).transpose(), + ).sum() + ) + .astype(float) + .round(1) ) + future_data = future_data.round(1) + return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] chart_options["yAxis"]["name"] = _("MWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -684,6 +631,25 @@ def get_chart_options(self) -> dict: return chart_options +class WindTurbines2045RegionChart(SimulationChart): + """Chart for regional wind turbines in 2045.""" + + lookup = "wind_turbines" + + 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() + return [int(status_quo_data), int(future_data)] + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + del chart_options["title"]["text"] + return chart_options + + class WindTurbinesSquareRegionChart(Chart): """Chart for regional wind turbines per square meter.""" @@ -695,7 +661,9 @@ def get_chart_data(self) -> list[float]: float( calculations.calculate_square_for_value( pd.DataFrame({"turbines": models.WindTurbine.quantity_per_municipality().sum()}, index=[1]), - ).sum(), + ) + .sum() + .round(2), ), ] @@ -703,7 +671,42 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] - chart_options["yAxis"]["name"] = _("") + chart_options["yAxis"]["name"] = "Anzahl Windenergieanlagen" + return chart_options + + +class WindTurbinesSquare2045RegionChart(SimulationChart): + """Chart for regional wind turbines per square meter in 2045.""" + + lookup = "wind_turbines" + + def get_chart_data(self) -> list[float]: + """Calculate population for whole region.""" + status_quo_data = ( + calculations.calculate_square_for_value( + pd.DataFrame({"turbines": models.WindTurbine.quantity_per_municipality().sum()}, index=[1]), + ) + .sum() + .round(2) + ) + future_data = ( + calculations.calculate_square_for_value( + pd.DataFrame( + {"turbines": calculations.wind_turbines_per_municipality_2045(self.simulation_id).sum()}, + index=[1], + ), + ) + .sum() + .round(2) + ) + return [float(status_quo_data), float(future_data)] + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = "" + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -714,7 +717,7 @@ class ElectricityDemandRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.electricity_demand_per_municipality().sum() + return calculations.electricity_demand_per_municipality().sum().round(1) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -724,6 +727,28 @@ def get_chart_options(self) -> dict: return chart_options +class ElectricityDemand2045RegionChart(SimulationChart): + """Chart for regional electricity demand.""" + + lookup = "electricity_demand" + + 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) + ) + return list(zip(status_quo_data, future_data)) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = _("GWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + class ElectricityDemandCapitaRegionChart(Chart): """Chart for regional electricity demand per population.""" @@ -736,13 +761,49 @@ def get_chart_data(self) -> pd.DataFrame: pd.DataFrame(calculations.electricity_demand_per_municipality().sum()).transpose(), ).sum() * 1e6 + ).round(1) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = _("kWh") + return chart_options + + +class ElectricityDemandCapita2045RegionChart(SimulationChart): + """Chart for regional electricity demand per population in 2045.""" + + lookup = "electricity_demand" + + def get_chart_data(self) -> pd.DataFrame: + """Calculate capacities for whole region.""" + status_quo_data = ( + calculations.calculate_capita_for_value( + pd.DataFrame(calculations.electricity_demand_per_municipality().sum()).transpose(), + ).sum() + * 1e6 + ).round(1) + future_data = ( + ( + calculations.calculate_capita_for_value( + pd.DataFrame( + calculations.electricity_demand_per_municipality_2045(self.simulation_id).sum(), + ).transpose(), + ).sum() + * 1e6 + ) + .astype(float) + .round(1) ) + return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] chart_options["yAxis"]["name"] = _("kWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -753,7 +814,7 @@ class HeatDemandRegionChart(Chart): def get_chart_data(self) -> None: """Calculate capacities for whole region.""" - return calculations.heat_demand_per_municipality().sum() + return calculations.heat_demand_per_municipality().sum().round(1) def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -763,6 +824,26 @@ def get_chart_options(self) -> dict: return chart_options +class HeatDemand2045RegionChart(SimulationChart): + """Chart for regional heat demand in 2045.""" + + lookup = "heat_demand" + + def get_chart_data(self) -> None: + """Calculate capacities for whole region.""" + status_quo_data = calculations.heat_demand_per_municipality().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)) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = _("GWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + class HeatDemandCapitaRegionChart(Chart): """Chart for regional heat demand per population.""" @@ -775,13 +856,49 @@ def get_chart_data(self) -> None: pd.DataFrame(calculations.heat_demand_per_municipality().sum()).transpose(), ).sum() * 1e6 + ).round(1) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + del chart_options["title"]["text"] + chart_options["yAxis"]["name"] = _("kWh") + return chart_options + + +class HeatDemandCapita2045RegionChart(SimulationChart): + """Chart for regional heat demand per population in 2045.""" + + lookup = "heat_demand" + + 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(), + ).sum() + * 1e6 + ).round(1) + future_data = ( + ( + calculations.calculate_capita_for_value( + pd.DataFrame( + calculations.heat_demand_per_municipality_2045(self.simulation_id).sum(), + ).transpose(), + ).sum() + * 1e6 + ) + .astype(float) + .round(1) ) + return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] chart_options["yAxis"]["name"] = _("kWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -798,7 +915,7 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] - chart_options["yAxis"]["name"] = _("#") + chart_options["yAxis"]["name"] = _("Anzahl") del chart_options["series"][0]["name"] return chart_options @@ -816,31 +933,45 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() del chart_options["title"]["text"] - chart_options["yAxis"]["name"] = _("#") + chart_options["yAxis"]["name"] = _("MWh") del chart_options["series"][0]["name"] return chart_options CHARTS: dict[str, type[Chart]] = { + "detailed_overview": DetailedOverviewChart, + "ghg_reduction": GHGReductionChart, "electricity_overview": ElectricityOverviewChart, - "heat_overview": HeatOverviewChart, + "electricity_autarky": ElectricityAutarkyChart, + "heat_decentralized": HeatStructureDecentralChart, + "heat_centralized": HeatStructureCentralChart, "population_statusquo_region": PopulationRegionChart, "population_density_statusquo_region": PopulationDensityRegionChart, "employees_statusquo_region": EmployeesRegionChart, "companies_statusquo_region": CompaniesRegionChart, "capacity_statusquo_region": CapacityRegionChart, "capacity_square_statusquo_region": CapacitySquareRegionChart, + "capacity_2045_region": Capacity2045RegionChart, + "capacity_square_2045_region": CapacitySquare2045RegionChart, "energy_statusquo_region": EnergyRegionChart, "energy_2045_region": Energy2045RegionChart, "energy_share_statusquo_region": EnergyShareRegionChart, "energy_capita_statusquo_region": EnergyCapitaRegionChart, + "energy_capita_2045_region": EnergyCapita2045RegionChart, "energy_square_statusquo_region": EnergySquareRegionChart, + "energy_square_2045_region": EnergySquare2045RegionChart, "wind_turbines_statusquo_region": WindTurbinesRegionChart, + "wind_turbines_2045_region": WindTurbines2045RegionChart, "wind_turbines_square_statusquo_region": WindTurbinesSquareRegionChart, + "wind_turbines_square_2045_region": WindTurbinesSquare2045RegionChart, "electricity_demand_statusquo_region": ElectricityDemandRegionChart, + "electricity_demand_2045_region": ElectricityDemand2045RegionChart, "electricity_demand_capita_statusquo_region": ElectricityDemandCapitaRegionChart, + "electricity_demand_capita_2045_region": ElectricityDemandCapita2045RegionChart, "heat_demand_statusquo_region": HeatDemandRegionChart, + "heat_demand_2045_region": HeatDemand2045RegionChart, "heat_demand_capita_statusquo_region": HeatDemandCapitaRegionChart, + "heat_demand_capita_2045_region": HeatDemandCapita2045RegionChart, "batteries_statusquo_region": BatteriesRegionChart, "batteries_capacity_statusquo_region": BatteriesCapacityRegionChart, } diff --git a/digiplan/map/charts/capacity.json b/digiplan/map/charts/capacity.json index 7f237570..4ed6881f 100644 --- a/digiplan/map/charts/capacity.json +++ b/digiplan/map/charts/capacity.json @@ -10,14 +10,13 @@ "Aufdach-PV", "Freiflächen-PV", "Wasserkraft", - "Bioenergie", - "Speicher" + "Bioenergie" ], "orient": "vertical", "right": "-5%", "bottom": "15%" }, - "color": ["#6A89CC","#FFD660","#EFAD25","#A9BDE8","#52C41A","#8D2D5F"], + "color": ["#6A89CC","#FFD660","#EFAD25","#A9BDE8","#52C41A"], "xAxis": { "type": "category", "boundaryGap": true, @@ -61,13 +60,7 @@ "stack": "five", "data": [10], "name": "Bioenergie" - }, - { - "type": "bar", - "stack": "five", - "data": [10], - "name": "Speicher" } ], - "title": {"text": "installed capacity of different types"} + "title": {"text": "Installierte Leistung nach Typ"} } diff --git a/digiplan/map/charts/electricity_autarky.json b/digiplan/map/charts/electricity_autarky.json new file mode 100644 index 00000000..68bdfe6e --- /dev/null +++ b/digiplan/map/charts/electricity_autarky.json @@ -0,0 +1,58 @@ +{ + "brush": { + "toolbox": ["rect", "polygon", "lineX", "lineY", "keep", "clear"], + "xAxisIndex": 0 + }, + "grid": { + "bottom": "25%" + }, + "xAxis": { + "type": "value", + "show": true, + "position": "bottom", + "name": "%", + "nameLocation": "end", + "nameTextStyle": "Roboto", + "width": "76", + "heigth": "32", + "fontWeight": "300", + "fontSize": "14" + }, + "yAxis": { + "type": "category", + "data": ["Dein Szenario", "2022"], + "axisTick": { + "show": false + } + }, + "series": [ + { + "name": "Bilanzielle Eigenversorgung", + "type": "bar", + "barWidth": "16", + "stack": "balance", + "color": "#897BD9", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" + }, + "data": [60, 30] + }, + { + "name": "Zeitgleiche Eigenversorgung", + "type": "bar", + "barWidth": "16", + "stack": "temporal", + "color": "#B5ADE0", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" + }, + "data": [40, 10] + } + ] +} diff --git a/digiplan/map/charts/electricity_overview.json b/digiplan/map/charts/electricity_overview.json index 09490896..a886b25d 100644 --- a/digiplan/map/charts/electricity_overview.json +++ b/digiplan/map/charts/electricity_overview.json @@ -20,7 +20,7 @@ "containLabel": true }, "yAxis": { - "data": ["Ziel 2045", "Mein Szenario", "Status Quo"], + "data": ["Ziel 2045", "Dein Szenario", "2022"], "axisLine": { "onZero": true }, "splitLine": { "show": false }, "splitArea": { "show": false } @@ -29,7 +29,7 @@ "type": "value", "show": true, "position": "bottom", - "name": "TWh", + "name": "GWh", "nameLocation": "end", "nameTextStyle": "Roboto", "width": "76", @@ -102,10 +102,10 @@ "shadowColor": "rgba(0,0,0,0.3)" } }, - "data": [132, 334, 700] + "data": [132, 334, 100] }, { - "name": "Stromverbrauch GHD", + "name": "Verbrauch Haushalte", "type": "bar", "barWidth": "16", "stack": "demand", @@ -119,7 +119,7 @@ "data": [300, 344, 380] }, { - "name": "Stromverbrauch Haushalte", + "name": "Verbrauch GHD", "type": "bar", "stack": "demand", "color": "#969696", @@ -132,7 +132,7 @@ "data": [254, 244, 380] }, { - "name": "Stromverbrauch Industrie", + "name": "Verbrauch Industrie", "type": "bar", "barWidth": "35", "stack": "demand", @@ -146,17 +146,32 @@ "data": [280, 300, 350] }, { - "name": "BEV", + "name": "Import*", "type": "bar", - "stack": "demand", - "color": "#2F2F2F", + "barWidth": "16", + "stack": "import", + "color": "#F3AF9C", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [0, 20, 0] + }, + { + "name": "Export*", + "type": "bar", + "barWidth": "16", + "stack": "export", + "color": "#8ECEAB", "emphasis": { "itemStyle": { "shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)" } }, - "data": [145, 144, 180] + "data": [0, 140, 0] } ] } diff --git a/digiplan/map/charts/ghg_history.json b/digiplan/map/charts/ghg_history.json index ffcc1ca9..3a262a39 100644 --- a/digiplan/map/charts/ghg_history.json +++ b/digiplan/map/charts/ghg_history.json @@ -7,7 +7,7 @@ "formatter": "{a}: {c} kt CO₂-Äq." }, "yAxis": { - "name": "kt CO₂-Äquivalent" + "name": "kt CO₂-Äq." }, "xAxis": [ { @@ -99,6 +99,12 @@ "color": "#E6772E", "barWidth":"30", "barGap": "250%", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 15680, 11571 @@ -110,6 +116,12 @@ "type": "bar", "color": "#FA9FB5", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 20519, 9011 @@ -121,6 +133,12 @@ "type": "bar", "color": "#6C567B", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 3779, 4007 @@ -132,6 +150,12 @@ "type": "bar", "color": "#A8DADC", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 13943, 3983 @@ -143,6 +167,12 @@ "type": "bar", "color": "#87D068", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 3275, 2113 @@ -154,6 +184,12 @@ "type": "bar", "color": "#F5F5DC", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 1458, 947 @@ -165,6 +201,12 @@ "type": "bar", "color": "#E6772E", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 930, 538 @@ -176,6 +218,12 @@ "type": "bar", "color": "#FA9FB5", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 4848, 2129 @@ -187,6 +235,12 @@ "type": "bar", "color": "#6C567B", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 698, 672 @@ -198,6 +252,12 @@ "type": "bar", "color": "#A8DADC", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 2434, 695 @@ -209,6 +269,12 @@ "stack": "abw", "color": "#87D068", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 501, 368 @@ -220,6 +286,12 @@ "stack": "abw", "color": "#F5F5DC", "barWidth":"30", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data": [ 262, 157 diff --git a/digiplan/map/charts/ghg_reduction.json b/digiplan/map/charts/ghg_reduction.json index 0bf4e251..05e8915d 100644 --- a/digiplan/map/charts/ghg_reduction.json +++ b/digiplan/map/charts/ghg_reduction.json @@ -26,7 +26,8 @@ ] }, "yAxis":{ - "type":"value" + "type":"value", + "name": "kt CO₂-Äq." }, "series":[ { @@ -59,6 +60,12 @@ "show":false, "position":"inside" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 538, 0, @@ -75,6 +82,12 @@ "show":false, "position":"inside" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 2129, 0, @@ -91,6 +104,12 @@ "show":false, "position":"inside" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 672, 0, @@ -107,6 +126,12 @@ "show":false, "position":"inside" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 695, 0, @@ -123,6 +148,12 @@ "show":false, "position":"inside" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 368, 0, @@ -139,6 +170,12 @@ "show":false, "position":"inside" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 157, 0, @@ -150,6 +187,7 @@ "type":"bar", "barWidth":"30", "stack":"Total", + "stackStrategy": "all", "color":"#D9B38C", "label":{ "show":false, @@ -157,6 +195,12 @@ "color":"#666" }, "legend":false, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 0, 500, @@ -164,16 +208,23 @@ ] }, { - "name":"Regionale Erzeugung", + "name":"Regionale Stromerzeugung", "type":"bar", "barWidth":"30", "stack":"Total", + "stackStrategy": "all", "color":"#48BF91", "label":{ "show":false, "position":"inside", "color":"#666" }, + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, "data":[ 0, 1000, @@ -298,7 +349,7 @@ "fill":"#333", "width":150, "overflow":"break", - "text":"Reduktion durch", + "text":"Reduktion durch Einsparung und", "font":"14px Roboto" } } @@ -316,7 +367,7 @@ "top":"middle", "shape":{ "width":160, - "height":30 + "height":35 }, "style":{ "fill":"#48BF91", @@ -337,7 +388,7 @@ "fill":"#fff", "width":160, "overflow":"break", - "text":"Regionale Erzeugung", + "text":"Regionale Stromerzeugung", "font":"14px Roboto" } } @@ -355,7 +406,7 @@ "top":"middle", "shape":{ "width":160, - "height":30 + "height":35 }, "style":{ "fill":"#D9B38C", diff --git a/digiplan/map/charts/decentralized_centralized_heat.json b/digiplan/map/charts/heat_centralized.json similarity index 67% rename from digiplan/map/charts/decentralized_centralized_heat.json rename to digiplan/map/charts/heat_centralized.json index 84cf4117..03a516a8 100644 --- a/digiplan/map/charts/decentralized_centralized_heat.json +++ b/digiplan/map/charts/heat_centralized.json @@ -18,7 +18,7 @@ } }, "yAxis": { - "data": ["Ziel", "Mein Szenario", "Status Quo"], + "data": ["Ziel 2045", "Dein Szenario", "2022"], "axisLine": { "onZero": true }, "splitLine": { "show": false }, "splitArea": { "show": false } @@ -37,10 +37,10 @@ }, "series": [ { - "name": "Kohleofen", + "name": "Erdgas", "type": "bar", "barWidth": "16", - "stack": "five", + "stack": "decentral", "color": "#FFF7EC", "emphasis": { "itemStyle": { @@ -51,9 +51,9 @@ "data": [132, 334, 700] }, { - "name": "Holzofen", + "name": "Heizöl", "type": "bar", - "stack": "five", + "stack": "decentral", "color": "#FEE8C8", "emphasis": { "itemStyle": { @@ -64,9 +64,9 @@ "data": [102, 234, 280] }, { - "name": "Gasheizkessel", + "name": "Holz", "type": "bar", - "stack": "five", + "stack": "decentral", "color": "#FDD49E", "emphasis": { "itemStyle": { @@ -79,7 +79,7 @@ { "name": "Wärmepumpe", "type": "bar", - "stack": "five", + "stack": "decentral", "color": "#FDBB84", "emphasis": { "itemStyle": { @@ -90,10 +90,10 @@ "data": [136, 134, 130] }, { - "name": "Solarthermie", + "name": "El. Direktheizung", "type": "bar", - "stack": "five", - "color": "#EF6548", + "stack": "decentral", + "color": "#FC8D59", "emphasis": { "itemStyle": { "shadowBlur": 10, @@ -103,58 +103,83 @@ "data": [412, 254, 100] }, { - "name": "FW: Gasheizkessel", + "name": "Biogas", "type": "bar", - "barWidth": "16", - "stack": "four", - "color": "#D7301F", + "stack": "decentral", + "color": "#EF6548", "emphasis": { "itemStyle": { "shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)" } }, - "data": [300, 344, 380] + "data": [412, 254, 100] }, { - "name": "FW: Wärmepumpen", + "name": "Solarthermie", "type": "bar", - "stack": "four", - "color": "#B30000", + "stack": "decentral", + "color": "#D7301F", "emphasis": { "itemStyle": { "shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)" } }, - "data": [254, 244, 380] + "data": [412, 254, 100] }, { - "name": "Power-to-Heat", + "name": "Sonstige", "type": "bar", - "barWidth": "35", - "stack": "four", - "color": "#7F0000", + "stack": "decentral", + "color": "#B30000", "emphasis": { "itemStyle": { "shadowBlur": 10, "shadowColor": "rgba(0,0,0,0.3)" } }, - "data": [280, 300, 350] + "data": [412, 254, 100] }, { - "name": "FW: Solarthermie", + "name": "Verbrauch Haushalte", "type": "bar", - "stack": "four", - "color": "#3F0000", + "barWidth": "16", + "stack": "demand", + "color": "#98C1D7", + "label": { + "show": false + }, "emphasis": { - "itemStyle": { - "shadowBlur": 10, - "shadowColor": "rgba(0,0,0,0.3)" - } + "focus": "series" + }, + "data": [200, 300, 320] + }, + { + "name": "Verbrauch GHD", + "type": "bar", + "stack": "demand", + "color": "#6F9BB2", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" + }, + "data": [260, 300, 310] + }, + { + "name": "Verbrauch Industrie", + "type": "bar", + "stack": "demand", + "color": "#376984", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" }, - "data": [145, 144, 180] + "data": [200, 250, 350] } ] } diff --git a/digiplan/map/charts/heat_decentralized.json b/digiplan/map/charts/heat_decentralized.json new file mode 100644 index 00000000..82820cad --- /dev/null +++ b/digiplan/map/charts/heat_decentralized.json @@ -0,0 +1,185 @@ +{ + "grid": { + "bottom": "28%" + }, + "legend": { + "bottom": "0" + }, + "brush": { + "toolbox": ["rect", "polygon", "lineX", "lineY", "keep", "clear"], + "xAxisIndex": 0 + }, + "toolbox": { + "feature": { + "magicType": { + "type": ["stack"] + }, + "dataView": {} + } + }, + "yAxis": { + "data": ["Ziel 2045", "Dein Szenario", "2022"], + "axisLine": { "onZero": true }, + "splitLine": { "show": false }, + "splitArea": { "show": false } + }, + "xAxis": { + "type": "value", + "show": true, + "position": "bottom", + "name": "GWh", + "nameLocation": "end", + "nameTextStyle": "Roboto", + "width": "76", + "heigth": "32", + "fontWeight": "300", + "fontSize": "14" + }, + "series": [ + { + "name": "Erdgas", + "type": "bar", + "barWidth": "16", + "stack": "decentral", + "color": "#FFF7EC", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [132, 334, 700] + }, + { + "name": "Heizöl", + "type": "bar", + "stack": "decentral", + "color": "#FEE8C8", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [102, 234, 280] + }, + { + "name": "Holz", + "type": "bar", + "stack": "decentral", + "color": "#FDD49E", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [282, 234, 100] + }, + { + "name": "Wärmepumpe", + "type": "bar", + "stack": "decentral", + "color": "#FDBB84", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [136, 134, 130] + }, + { + "name": "El. Direktheizung", + "type": "bar", + "stack": "decentral", + "color": "#FC8D59", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [412, 254, 100] + }, + { + "name": "Biogas", + "type": "bar", + "stack": "decentral", + "color": "#EF6548", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [412, 254, 100] + }, + { + "name": "Solarthermie", + "type": "bar", + "stack": "decentral", + "color": "#D7301F", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [412, 254, 100] + }, + { + "name": "Sonstige", + "type": "bar", + "stack": "decentral", + "color": "#B30000", + "emphasis": { + "itemStyle": { + "shadowBlur": 10, + "shadowColor": "rgba(0,0,0,0.3)" + } + }, + "data": [412, 254, 100] + }, + { + "name": "Verbrauch Haushalte", + "type": "bar", + "barWidth": "16", + "stack": "demand", + "color": "#98C1D7", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" + }, + "data": [300, 400, 420] + }, + { + "name": "Verbrauch GHD", + "type": "bar", + "stack": "demand", + "color": "#6F9BB2", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" + }, + "data": [360, 400, 410] + }, + { + "name": "Verbrauch Industrie", + "type": "bar", + "stack": "demand", + "color": "#376984", + "label": { + "show": false + }, + "emphasis": { + "focus": "series" + }, + "data": [300, 350, 450] + } + ] +} diff --git a/digiplan/map/charts/onboarding_pv_ground.json b/digiplan/map/charts/onboarding_pv_ground.json new file mode 100644 index 00000000..794bdbb1 --- /dev/null +++ b/digiplan/map/charts/onboarding_pv_ground.json @@ -0,0 +1,69 @@ +{ + "legend": { + "data": ["Installierte Leistung", "Anlagenanzahl"] + }, + "xAxis": [ + { + "type": "category", + "data": [ + "2007", + "2008", + "2009", + "2010", + "2011", + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + "2018", + "2019", + "2020", + "2021", + "2022" + ], + "axisPointer": { + "type": "shadow" + } + } + ], + "yAxis": [ + { + "type": "value", + "nameLocation": "start", + "min": 0, + "max": 400, + "interval": 50, + "axisLabel": { + "formatter": "{value} MW" + } + }, + { + "type": "value", + "nameLocation": "start", + "min": 0, + "max": 200, + "interval": 50, + "axisLabel": { + "formatter": "{value}" + } + } + ], + "series": [ + { + "name": "Installierte Leistung", + "type": "bar", + "color": "#EFAD25", + "barWidth": 10, + "data": [3.1, 20.9, 28.1, 58.8, 171.3, 237.2, 240.5, 267.4, 286.9, 303.9, 307.1, 317.7, 322.0, 340.3, 365.5, 371.5] + }, + { + "name": "Anlagenanzahl", + "type": "line", + "color": "#CCCCCC", + "yAxisIndex": 1, + "data": [3, 17, 20, 30, 51, 84, 86, 94, 100, 104, 110, 121, 128, 140, 156, 162] + } + ] +} diff --git a/digiplan/map/charts/onboarding_pv_roof.json b/digiplan/map/charts/onboarding_pv_roof.json new file mode 100644 index 00000000..15f34280 --- /dev/null +++ b/digiplan/map/charts/onboarding_pv_roof.json @@ -0,0 +1,80 @@ +{ + "legend": { + "data": ["Installierte Leistung", "Anlagenanzahl"] + }, + "xAxis": [ + { + "type": "category", + "data": [ + "1993", + "1994", + "1995", + "1998", + "2000", + "2001", + "2002", + "2003", + "2004", + "2005", + "2006", + "2007", + "2008", + "2009", + "2010", + "2011", + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + "2018", + "2019", + "2020", + "2021", + "2022" + ], + "axisPointer": { + "type": "shadow" + } + } + ], + "yAxis": [ + { + "type": "value", + "nameLocation": "start", + "min": 0, + "max": 390, + "interval": 60, + "axisLabel": { + "formatter": "{value} MW" + } + }, + { + "type": "value", + "nameLocation": "start", + "min": 0, + "max": 10400, + "interval": 800, + "axisLabel": { + "formatter": "{value}" + } + } + ], + "series": [ + { + "name": "Installierte Leistung", + "type": "bar", + "color": "#FFD660", + "barWidth": 8, + "data": [0.0, 0.0, 0.0, 0.1, 0.1, 0.1, 0.2, 0.3, 1.1, 2.7, 5.0, 7.5, 12.7, 25.0, 51.4, 77.1, 107.1, 122.4, 130.0, 134.6, 145.8, 158.1, 187.5, 226.4, 256.2, 276.8, 295.2] + }, + { + "name": "Anlagenanzahl", + "type": "line", + "color": "#CCCCCC", + "yAxisIndex": 1, + "data": [5, 14, 17, 19, 22, 46, 63, 82, 166, 314, 541, 774, 1145, 1621, 2333, 3145, 3826, 4371, 4709, 4970, 5228, 5559, 5958, 6608, 7597, 8678, 10308] + } + ] +} diff --git a/digiplan/map/charts/onboarding_wind.json b/digiplan/map/charts/onboarding_wind.json new file mode 100644 index 00000000..891e61a6 --- /dev/null +++ b/digiplan/map/charts/onboarding_wind.json @@ -0,0 +1,79 @@ +{ + "legend": { + "data": ["Installierte Leistung", "Anlagenanzahl"] + }, + "xAxis": [ + { + "type": "category", + "data": [ + "1995", + "1997", + "1998", + "1999", + "2000", + "2001", + "2002", + "2003", + "2004", + "2005", + "2006", + "2007", + "2008", + "2009", + "2010", + "2011", + "2012", + "2013", + "2014", + "2015", + "2016", + "2017", + "2018", + "2019", + "2021", + "2022" + ], + "axisPointer": { + "type": "shadow" + } + } + ], + "yAxis": [ + { + "type": "value", + "nameLocation": "start", + "min": 0, + "max": 800, + "interval": 100, + "axisLabel": { + "formatter": "{value} MW" + } + }, + { + "type": "value", + "nameLocation": "start", + "min": 0, + "max": 400, + "interval": 100, + "axisLabel": { + "formatter": "{value}" + } + } + ], + "series": [ + { + "name": "Installierte Leistung", + "type": "bar", + "color": "#6A89CC", + "barWidth": 10, + "data": [0.3, 1.3, 4.5, 23.6, 50.8, 63.0, 148.8, 178.0, 218.0, 309.6, 376.6, 427.0, 429.0, 437.8, 440.6, 449.8, 480.6, 482.9, 500.6, 536.0, 568.2, 612.9, 612.9, 652.7, 663.9, 669.5] + }, + { + "name": "Anlagenanzahl", + "type": "line", + "color": "#CCCCCC", + "yAxisIndex": 1, + "data": [1, 3, 7, 26, 46, 57, 113, 135, 155, 205, 244, 276, 277, 282, 284, 288, 304, 305, 313, 325, 336, 352, 353, 364, 366, 367] + } + ] +} diff --git a/digiplan/map/charts/overview_heat.json b/digiplan/map/charts/overview_heat.json index 9236797d..09f512e6 100644 --- a/digiplan/map/charts/overview_heat.json +++ b/digiplan/map/charts/overview_heat.json @@ -16,7 +16,7 @@ }, "yAxis": { "type": "category", - "data": ["Ziel", "Mein Szenario", "Status Quo"], + "data": ["Ziel 2045", "Dein Szenario", "2022"], "axisTick": { "show": false } diff --git a/digiplan/map/charts/population.json b/digiplan/map/charts/population.json index d66f354b..5e0f6648 100644 --- a/digiplan/map/charts/population.json +++ b/digiplan/map/charts/population.json @@ -1,10 +1,5 @@ { - "tooltip": { - "trigger": "item", - "axisPointer": { - "type": "cross" - } - }, + "tooltip": {}, "grid": { "top": "24%" }, @@ -17,7 +12,7 @@ "yAxis": { "type": "value", "show": true, - "name": "Population", + "name": "EW", "nameLocation": "end", "nameTextStyle": "Roboto" }, @@ -26,8 +21,8 @@ "type": "line", "barWidth": "16", "data": [5, 20, 36, 10, 10], - "name": "Population" + "name": "EW" } ], - "title": {"text": "Population per year"} + "title": {"text": "Bevölkerung pro Jahr"} } diff --git a/digiplan/map/charts/wind_turbines.json b/digiplan/map/charts/wind_turbines.json index 6bcc0477..c4b4528e 100644 --- a/digiplan/map/charts/wind_turbines.json +++ b/digiplan/map/charts/wind_turbines.json @@ -1,10 +1,5 @@ { - "tooltip": { - "trigger": "item", - "axisPointer": { - "type": "cross" - } - }, + "tooltip": {}, "grid": { "top": "24%" }, @@ -17,7 +12,7 @@ "yAxis": { "type": "value", "show": true, - "name": "Wind Turbines", + "name": "", "nameLocation": "end", "nameTextStyle": "Roboto" }, @@ -26,8 +21,8 @@ "type": "bar", "barWidth": "16", "data": [5], - "name": "Wind Turbines" + "name": "Windenergieanlagen" } ], - "title": { "text": "number of wind turbines" } + "title": { "text": "Anzahl Windenergieanlagen" } } diff --git a/digiplan/map/choropleths.py b/digiplan/map/choropleths.py index d9bbd884..9409d07a 100644 --- a/digiplan/map/choropleths.py +++ b/digiplan/map/choropleths.py @@ -126,6 +126,19 @@ def get_values_per_feature(self) -> pd.DataFrame: # noqa: D102 return capacities.to_dict() +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) + return capacities.to_dict() + + +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_per_square = calculations.calculate_square_for_value(capacities) * 1e3 + return capacities_per_square.sum(axis=1).to_dict() + + class CapacitySquareChoropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capacities = calculations.capacities_per_municipality() @@ -160,6 +173,11 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 return models.WindTurbine.quantity_per_municipality().to_dict() +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() + + class WindTurbinesSquareChoropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 wind_turbines = models.WindTurbine.quantity_per_municipality() @@ -167,11 +185,25 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 return wind_turbines_square.to_dict() +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_square = calculations.calculate_square_for_value(wind_turbines) + return wind_turbines_square.to_dict() + + class ElectricityDemandChoropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 return calculations.electricity_demand_per_municipality().sum(axis=1).to_dict() +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() + ) + + class ElectricityDemandCapitaChoropleth(Choropleth): # noqa: D101 def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 capita_demand = ( @@ -181,15 +213,42 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 return capita_demand.to_dict() +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"]), + ).sum(axis=1) + * 1e6 + ) + return capita_demand.to_dict() + + 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() +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() + + 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().sum(axis=1)) * 1e6 + ) + return capita_demand.to_dict() + + +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"]), + ).sum(axis=1) + * 1e6 ) return capita_demand.to_dict() @@ -217,13 +276,21 @@ def get_values_per_feature(self) -> dict[int, float]: # noqa: D102 "energy_square_statusquo": EnergySquareChoropleth, "energy_square_2045": EnergySquare2045Choropleth, "capacity_statusquo": CapacityChoropleth, + "capacity_2045": Capacity2045Choropleth, "capacity_square_statusquo": CapacitySquareChoropleth, + "capacity_square_2045": CapacitySquare2045Choropleth, "wind_turbines_statusquo": WindTurbinesChoropleth, + "wind_turbines_2045": WindTurbines2045Choropleth, "wind_turbines_square_statusquo": WindTurbinesSquareChoropleth, + "wind_turbines_square_2045": WindTurbinesSquare2045Choropleth, "electricity_demand_statusquo": ElectricityDemandChoropleth, + "electricity_demand_2045": ElectricityDemand2045Choropleth, "electricity_demand_capita_statusquo": ElectricityDemandCapitaChoropleth, + "electricity_demand_capita_2045": ElectricityDemandCapita2045Choropleth, "heat_demand_statusquo": HeatDemandChoropleth, + "heat_demand_2045": HeatDemand2045Choropleth, "heat_demand_capita_statusquo": HeatDemandCapitaChoropleth, + "heat_demand_capita_2045": HeatDemandCapita2045Choropleth, "batteries_statusquo": BatteriesChoropleth, "batteries_capacity_statusquo": BatteriesCapacityChoropleth, } diff --git a/digiplan/map/config.py b/digiplan/map/config.py index 6c77dc1f..ffe37b2d 100644 --- a/digiplan/map/config.py +++ b/digiplan/map/config.py @@ -171,7 +171,7 @@ def init_sources() -> dict[str, dict]: # SIMULATION SIMULATION_RENEWABLES = { - "ABW-solar-pv_ground": _("Outdoor PV"), + "ABW-solar-pv_ground": _("Ground-mounted PV"), "ABW-solar-pv_rooftop": _("Roof-mounted PV"), "ABW-wind-onshore": _("Wind turbine"), "ABW-hydro-ror": _("Hydro"), @@ -180,7 +180,6 @@ def init_sources() -> dict[str, dict]: SIMULATION_DEMANDS = { # electricty demands - "ABW-electricity-bev_charging": _("BEV"), "ABW-electricity-demand_hh": _("Electricity Household Demand"), "ABW-electricity-demand_cts": _("Electricity CTS Demand"), "ABW-electricity-demand_ind": _("Electricity Industry Demand"), diff --git a/digiplan/map/datapackage.py b/digiplan/map/datapackage.py index b1502ec9..ba166ac5 100644 --- a/digiplan/map/datapackage.py +++ b/digiplan/map/datapackage.py @@ -1,4 +1,5 @@ """Read functionality for digipipe datapackage.""" +import csv import json from collections import defaultdict from pathlib import Path @@ -9,6 +10,7 @@ from django_oemof.settings import OEMOF_DIR from config.settings.base import DATA_DIR +from digiplan.map import config def get_employment() -> pd.DataFrame: @@ -33,6 +35,51 @@ def get_power_demand(sector: Optional[str] = None) -> dict[str, pd.DataFrame]: return demand +def get_hourly_electricity_demand(year: int) -> pd.Series: + """Return hourly electricity demand per sector.""" + demand_per_sector = get_power_demand() + demand_profile = get_electricity_demand_profile() + demand = [] + for sector, demand_sector_per_mun in demand_per_sector.items(): + demand.append(demand_profile[sector] * demand_sector_per_mun[str(year)].sum()) + return pd.concat(demand, axis=1).sum(axis=1) + + +def get_heat_demand(sector: Optional[str] = None, distribution: Optional[str] = None) -> dict[str, pd.DataFrame]: + """Return heat demand for given sector or all sectors.""" + sectors = (sector,) if sector else ("hh", "cts", "ind") + distribution_prefix = ("_cen" if distribution == "central" else "_dec") if distribution else "" + demand = {} + for sec in sectors: + demand_filename = settings.DIGIPIPE_DIR.path("scalars").path( + f"demand_{sec}_heat_demand{distribution_prefix}.csv", + ) + demand[sec] = pd.read_csv(demand_filename) + return demand + + +def get_heat_capacity_shares( + distribution: str, + year: Optional[int] = 2045, + *, + include_heatpumps: Optional[bool] = False, +) -> dict: + """Return capacity shares of heating structure.""" + shares_filename = settings.DIGIPIPE_DIR.path("scalars").path(f"demand_heat_structure_esys_{distribution}.csv") + with Path(shares_filename).open("r", encoding="utf-8") as shares_file: + reader = csv.DictReader(shares_file) + shares = {} + summed_shares = 0.0 + for row in reader: + if row["year"] != str(year): + continue + if row["carrier"] == "heat_pump" and not include_heatpumps: + continue + shares[row["carrier"]] = float(row["demand_rel"]) + summed_shares += float(row["demand_rel"]) + return {k: v / summed_shares for k, v in shares.items()} + + def get_summed_heat_demand_per_municipality( sector: Optional[str] = None, distribution: Optional[str] = None, @@ -50,7 +97,7 @@ def get_summed_heat_demand_per_municipality( return demand -def get_heat_demand( +def get_heat_demand_profile( sector: Optional[str] = None, distribution: Optional[str] = None, ) -> dict[str, dict[str, pd.DataFrame]]: @@ -67,6 +114,20 @@ def get_heat_demand( return demand +def get_electricity_demand_profile( + sector: Optional[str] = None, +) -> dict[str, pd.DataFrame]: + """Return heat demand for given sector and distribution.""" + sectors = (sector,) if sector else ("hh", "cts", "ind") + demand = defaultdict(dict) + for sec in sectors: + demand_filename = ( + OEMOF_DIR / settings.OEMOF_SCENARIO / "data" / "sequences" / f"electricity-demand_{sec}_profile.csv" + ) + demand[sec] = pd.read_csv(demand_filename, sep=";")[f"ABW-electricity-demand_{sec}-profile"] + return demand + + def get_thermal_efficiency(component: str) -> float: """Return thermal efficiency from given component from oemof scenario.""" component_filename = OEMOF_DIR / settings.OEMOF_SCENARIO / "data" / "elements" / f"{component}.csv" @@ -140,3 +201,42 @@ def get_potential_values(*, per_municipality: bool = False) -> dict: if profile == "pv_ground": potentials[key] = potentials[key] * tech_data["power_density"]["pv_ground"] return potentials + + +def get_full_load_hours(year: int) -> pd.Series: + """Return full load hours for given year.""" + full_load_hours = pd.Series( + data=[technology_data[str(year)] for technology_data in config.TECHNOLOGY_DATA["full_load_hours"].values()], + index=config.TECHNOLOGY_DATA["full_load_hours"].keys(), + ) + return full_load_hours + + +def get_capacities(year: int) -> pd.Series: + """Return renewable capacities for given year.""" + if year == 2022: # noqa: PLR2004 + lookup = "status_quo" + elif year == 2045: # noqa: PLR2004 + lookup = "future_scenario" + else: + msg = "Unknown year" + raise ValueError(msg) + energy_settings = json.load(Path.open(Path(settings.DIGIPIPE_DIR, "settings/energy_settings_panel.json"))) + technologies = {"wind": "s_w_1", "pv_ground": "s_pv_ff_1", "pv_roof": "s_pv_d_1", "ror": "s_h_1"} + return pd.Series( + data={technology: energy_settings[key].get(lookup, 0.0) for technology, key in technologies.items()}, + ) + + +def get_power_density(technology: Optional[str] = None) -> dict: + """Return power density for technology.""" + if technology: + return config.TECHNOLOGY_DATA["power_density"][technology] + return config.TECHNOLOGY_DATA["power_density"] + + +def get_profile(technology: str) -> pd.Series: + """Return profile for given technology from oemof datapackage.""" + profile_filename = OEMOF_DIR / settings.OEMOF_SCENARIO / "data" / "sequences" / f"{technology}_profile.csv" + profile = pd.read_csv(profile_filename, sep=";", index_col=0) + return profile.iloc[:, 0] diff --git a/digiplan/map/forms.py b/digiplan/map/forms.py index 996a8213..b009d7b5 100644 --- a/digiplan/map/forms.py +++ b/digiplan/map/forms.py @@ -60,6 +60,8 @@ def generate_fields(parameters, additional_parameters=None): # noqa: ANN001, AN attrs["data-to"] = item["to"] if "step" in item: attrs["data-step"] = item["step"] + if "from-min" in item: + attrs["data-from-min"] = item["from-min"] field = IntegerField( label=item["label"], diff --git a/digiplan/map/hooks.py b/digiplan/map/hooks.py index 72b7a520..e4046e25 100644 --- a/digiplan/map/hooks.py +++ b/digiplan/map/hooks.py @@ -2,6 +2,7 @@ import logging import math +from collections import defaultdict import pandas as pd from django.http import HttpRequest @@ -73,50 +74,66 @@ def adapt_electricity_demand(scenario: str, data: dict, request: HttpRequest) -> def adapt_heat_capacities(distribution: str, remaining_energy: pd.Series) -> dict: """Adapt heat settings for remaining energy.""" - # TODO (Hendrik): Read values from datapackage # noqa: TD003 - heat_shares = { - "central": { - "ABW-wood-extchp_central": 0, - "ABW-biogas-bpchp_central": 0, - "ABW-ch4-bpchp_central": 0.5, - "ABW-ch4-extchp_central": 0.5, - "ABW-solar-thermalcollector_central": 0, - "ABW-ch4-boiler_central": 0, - }, - "decentral": { - "ABW-wood-extchp_decentral": 0.15, - "ABW-biogas-bpchp_decentral": 0.15, - "ABW-ch4-bpchp_decentral": 0.15, - "ABW-ch4-extchp_decentral": 0.30, - "ABW-solar-thermalcollector_decentral": 0, - "ABW-ch4-boiler_decentral": 0.15, - "ABW-wood-oven": 0.1, - }, + heat_shares = datapackage.get_heat_capacity_shares(distribution[:3]) + mapping = { + "wood_extchp": f"ABW-wood-extchp_{distribution}", + "biogas_bpchp": f"ABW-biogas-bpchp_{distribution}", + "ch4_bpchp": f"ABW-ch4-bpchp_{distribution}", + "ch4_extchp": f"ABW-ch4-extchp_{distribution}", + "solar_thermal": f"ABW-solar-thermalcollector_{distribution}", + "methane": f"ABW-ch4-boiler_{distribution}", + "hydrogen": f"ABW-ch4-boiler_{distribution}", # hydrogen is added to methane bus + "electricity_direct_heating": f"ABW-electricity-pth_{distribution}", } + if distribution == "decentral": + mapping["wood_oven"] = "ABW-wood-oven" + heat_share_mapped = defaultdict(float) + for com, share in heat_shares.items(): + if com not in mapping: + continue + heat_share_mapped[mapping[com]] += share remaining_energy_sum = remaining_energy.sum() + solar_thermal_max_energy = ( + remaining_energy_sum + * heat_shares["solar_thermal"] + * datapackage.get_thermal_efficiency(mapping["solar_thermal"][4:]).max() + ) + solar_thermal_energy = remaining_energy_sum * heat_shares["solar_thermal"] data = {} - for component, share in heat_shares[distribution].items(): - if share == 0: + for component, share in heat_share_mapped.items(): + if "solar" in component: + data[component] = {"capacity": solar_thermal_energy} continue - efficiency = datapackage.get_thermal_efficiency(component[4:]) - capacity = math.ceil((remaining_energy / efficiency).max() * share) + capacity = math.ceil(remaining_energy.max() * share) + if "boiler" in component: + capacity += solar_thermal_max_energy if capacity == 0: continue energy = remaining_energy_sum * share if "extchp" in component or "bpchp" in component: parameter = "input_parameters" + efficiency = datapackage.get_thermal_efficiency(component[4:]) energy = energy / efficiency + capacity = capacity / efficiency else: parameter = "output_parameters" logging.info(f"Adapting capacity and energy for {component} at {distribution=}.") data[component] = { + # Capacity has to be increased, so that times without energy from solar thermal can be covered by other + # components "capacity": capacity, parameter: { - "summed_min": energy / capacity, - "summed_max": energy / capacity, + "full_load_time_min": energy / capacity, + "full_load_time_max": energy / capacity + solar_thermal_energy + if "boiler" in component + else energy / capacity, }, } + if "extchp" in component or "bpchp" in component: + # Nominal value must be set, if summed_min or summed_max are set in flow. + # In output_parameters, nominal_value is read from component. + data[component]["input_parameters"]["nominal_value"] = capacity return data @@ -142,7 +159,7 @@ def adapt_heat_settings(scenario: str, data: dict, request: HttpRequest) -> dict hp_sliders = {"hh": "w_d_wp_3", "cts": "w_d_wp_4", "ind": "w_d_wp_5"} heat_demand_per_municipality = datapackage.get_summed_heat_demand_per_municipality() - heat_demand = datapackage.get_heat_demand() + heat_demand = datapackage.get_heat_demand_profile() for distribution in ("central", "decentral"): demand = {} @@ -169,7 +186,7 @@ def adapt_heat_settings(scenario: str, data: dict, request: HttpRequest) -> dict hp_energy[sector] = demand[sector] * hp_share else: if sector == "hh": # noqa: PLR5501 - hp_share = data.pop("w_z_wp_3") / 100 + hp_share = data.pop("w_z_wp_1") / 100 hp_energy[sector] = demand[sector] * hp_share else: hp_energy[sector] = demand[sector] * 0 @@ -181,11 +198,12 @@ def adapt_heat_settings(scenario: str, data: dict, request: HttpRequest) -> dict logging.info(f"Adapting capacity and energy for heatpump at {distribution=}.") data[f"ABW-electricity-heatpump_{distribution}"] = { "capacity": capacity, - "output_parameters": { - "summed_min": hp_energy_sum / capacity, - "summed_max": hp_energy_sum / capacity, - }, } + if capacity > 0: + data[f"ABW-electricity-heatpump_{distribution}"]["output_parameters"] = { + "full_load_time_min": hp_energy_sum / capacity, + "full_load_time_max": hp_energy_sum / capacity, + } total_demand = pd.concat(demand.values(), axis=1).sum(axis=1) remaining_energy = total_demand - hp_energy_total @@ -197,16 +215,28 @@ def adapt_heat_settings(scenario: str, data: dict, request: HttpRequest) -> dict storage_sliders = {"decentral": "w_d_s_1", "central": "w_z_s_1"} avg_demand_per_day = total_demand.sum() / 365 logging.info(f"Adapting capacity for storage at {distribution=}.") + capacity = float(avg_demand_per_day * data.pop(storage_sliders[distribution]) / 100) + # Adapt storage capacity to solarthermal collector overpowering: + solar_capacity = data[f"ABW-solar-thermalcollector_{distribution}"]["capacity"] + solar_thermal_energy = ( + datapackage.get_thermal_efficiency(f"solar-thermalcollector_{distribution}") * solar_capacity + ) + delta_solar = solar_thermal_energy - total_demand + solar_peak = delta_solar[delta_solar > 0].max() + capacity = max(capacity, solar_peak) data[f"ABW-heat_{distribution}-storage"] = { - "capacity": float(avg_demand_per_day * data.pop(storage_sliders[distribution]) / 100), + "capacity": capacity, + "storage_capacity": capacity, } + # Adapt biomass to biogas plant size + biogas_capacity = data["ABW-biogas-bpchp_decentral"]["capacity"] + data["ABW-biogas-bpchp_central"]["capacity"] + data["ABW-biomass-biogas_plant"] = {"capacity": biogas_capacity} + data["ABW-biogas-biogas_upgrading_plant"] = {"capacity": biogas_capacity} + # Remove unnecessary heat settings del data["w_v_1"] del data["w_d_wp_1"] - del data["w_z_wp_1"] - del data["w_d_s_3"] - del data["w_z_s_3"] return data @@ -229,13 +259,24 @@ def adapt_renewable_capacities(scenario: str, data: dict, request: HttpRequest) dict Adapted parameters dict with set up capacities """ - # ELECTRICITY + # Capacities data["ABW-wind-onshore"] = {"capacity": data.pop("s_w_1")} data["ABW-solar-pv_ground"] = {"capacity": data.pop("s_pv_ff_1")} data["ABW-solar-pv_rooftop"] = {"capacity": data.pop("s_pv_d_1")} data["ABW-hydro-ror"] = {"capacity": data.pop("s_h_1")} data["ABW-electricity-large_scale_battery"] = {"capacity": data.pop("s_s_g_1")} + # Full load hours + technology_mapping = { + "ABW-wind-onshore": "wind", + "ABW-solar-pv_ground": "pv_ground", + "ABW-solar-pv_rooftop": "pv_roof", + "ABW-hydro-ror": "ror", + } + full_load_hours = datapackage.get_full_load_hours(2045) + for technology, mapped_key in technology_mapping.items(): + data[technology]["profile"] = datapackage.get_profile(technology[4:]) * full_load_hours[mapped_key] + # Remove unnecessary renewable sliders: del data["s_w_3"] del data["s_w_4"] diff --git a/digiplan/map/map_config.py b/digiplan/map/map_config.py index 9f149c63..77e19739 100644 --- a/digiplan/map/map_config.py +++ b/digiplan/map/map_config.py @@ -18,57 +18,231 @@ class SymbolLegendLayer(legend.LegendLayer): _("Renewables"): [ SymbolLegendLayer( _("Wind turbine"), - _("Wind turbine layer"), + _("Windenergieanlagen: Installierte Leistung und Anzahl"), layer_id="wind", color="#6A89CC", symbol="circle", ), SymbolLegendLayer( _("Roof-mounted PV"), - _("PV roof layer"), + _("PV-Aufdachanlagen: Installierte Leistung und Anzahl"), layer_id="pvroof", color="#FFD660", symbol="circle", ), - SymbolLegendLayer(_("Outdoor PV"), _("Hydro layer"), layer_id="pvground", color="#EFAD25", symbol="circle"), - SymbolLegendLayer(_("Hydro"), _("Hydro layer"), layer_id="hydro", color="#A9BDE8", symbol="circle"), - SymbolLegendLayer(_("Biomass"), _("Wind turbine layer"), layer_id="biomass", color="#52C41A", symbol="circle"), + SymbolLegendLayer( + _("Ground-mounted PV"), + _("PV-Freiflächenanlagen: Installierte Leistung und Anzahl"), + layer_id="pvground", + color="#EFAD25", + symbol="circle", + ), + SymbolLegendLayer( + _("Hydro"), + _("Wasserkraftanlagen: Installierte Leistung und Anzahl"), + layer_id="hydro", + color="#A9BDE8", + symbol="circle", + ), + SymbolLegendLayer( + _("Biomass"), + _("Biomasseanlagen: Installierte Leistung und Anzahl"), + layer_id="biomass", + color="#52C41A", + symbol="circle", + ), SymbolLegendLayer( _("Combustion"), - _("Wind turbine layer"), + _("Verbrennungskraftwerke: Installierte Leistung und Anzahl"), layer_id="combustion", color="#E6772E", symbol="circle", ), - SymbolLegendLayer(_("GSGK"), _("Wind turbine layer"), layer_id="gsgk", color="#C27BA0", symbol="circle"), - SymbolLegendLayer(_("Storage"), _("Wind turbine layer"), layer_id="storage", color="#8D2D5F", symbol="circle"), + SymbolLegendLayer( + _("GSGK"), + _("Geo- oder Solarthermie-, Grubengas- und Klärschlamm-Anlagen: Installierte Leistung und Anzahl"), + layer_id="gsgk", + color="#C27BA0", + symbol="circle", + ), + SymbolLegendLayer( + _("Storage"), + _("Batteriespeicher gesamt: Installierte Leistung und Anzahl"), + layer_id="storage", + color="#8D2D5F", + symbol="circle", + ), ], _("Settlements Infrastructure"): [ - legend.LegendLayer(_("Settlement 0m"), _("Aviation layer"), layer_id="settlement-0m"), - legend.LegendLayer(_("Industry"), _("Aviation layer"), layer_id="industry"), - legend.LegendLayer(_("Road Railway 500m"), _("Aviation layer"), layer_id="road_railway-500m_region"), - legend.LegendLayer(_("Road"), _("Aviation layer"), layer_id="road_default"), - legend.LegendLayer(_("Railway"), _("Aviation layer"), layer_id="railway"), - legend.LegendLayer(_("Aviation"), _("Aviation layer"), layer_id="aviation"), - legend.LegendLayer(_("Air Traffic"), _("Air traffic layer"), layer_id="air_traffic"), - legend.LegendLayer(_("Military"), _("Aviation layer"), layer_id="military"), - legend.LegendLayer(_("Grid"), _("Aviation layer"), layer_id="grid"), + legend.LegendLayer( + _("Settlement 0m"), + _( + "Eine Siedlung ist ein Gebiet, welches die menschliche Niederlassung in beliebiger Form der " + "gruppierten Behausung beschreibt. Sie beinhaltet überwiegend Wohngebiete.", + ), + layer_id="settlement-0m", + ), + legend.LegendLayer( + _("Industry"), + _( + "Industrie- und Gewerbegebiete werden ausgewiesen, um störende Einwirkungen von Betrieben wie Lärm, " + "Geruch oder Gefahren auf Wohnbebauung zu vermeiden.", + ), + layer_id="industry", + ), + legend.LegendLayer( + _("Road Railway 500m"), + _( + "Die Flächen längs von Autobahnen oder Schienenwegen werden durch Erstellen einer 500 m breiten " + "Pufferzone abzüglich einer 15 m breiten Pufferzone gebildet.", + ), + layer_id="road_railway-500m_region", + ), + legend.LegendLayer( + _("Road"), + _("Zu den Straßen gehören unter anderem Bundesautobahnen, Bundesfern-, Landes- und Kreisstraßen."), + layer_id="road_default", + ), + legend.LegendLayer( + _("Railway"), + _( + "Der Bahnverkehr ist ein wichtiger Bestandteil der Verkehrsinfrastruktur. Berücksichtigt " + "werden Fernverkehrsbahnen, Regionalverkehrsbahnen und S-Bahnen.", + ), + layer_id="railway", + ), + legend.LegendLayer( + _("Aviation"), + _( + "Zur Infrastruktur des Luftverkehrs gehören neben Start- und Landebahnen die " + "Flughafengebäude und Hangars.", + ), + layer_id="aviation", + ), + legend.LegendLayer( + _("Air Traffic"), + _("Ein Drehfunkfeuer ist ein Funkfeuer für die Luftfahrtnavigation."), + layer_id="air_traffic", + ), + legend.LegendLayer( + _("Military"), + _("Zu den militärisch genutzten Flächen gehören militärische Sperrgebiete und Liegenschaften."), + layer_id="military", + ), + legend.LegendLayer( + _("Grid"), + _( + "Zum Übertragungsnetz zählen die elektrischen Leitungen sowie die dazugehörigen Einrichtungen " + "wie Schalt- und Umspannwerke der Höchst- und Hochspannungsebenen.", + ), + layer_id="grid", + ), ], _("Nature Landscape"): [ - legend.LegendLayer(_("Nature Conservation Area"), _("layer info"), layer_id="nature_conservation_area"), - legend.LegendLayer(_("Fauna Flora Habitat"), _("layer info"), layer_id="fauna_flora_habitat"), - legend.LegendLayer(_("Special Protection Area"), _("layer info"), layer_id="special_protection_area"), - legend.LegendLayer(_("Biosphere Reserve"), _("Biosphere Reserve layer"), layer_id="biosphere_reserve"), - legend.LegendLayer(_("Landscape Protection Area"), _("layer info"), layer_id="landscape_protection_area"), - legend.LegendLayer(_("Forest"), _("layer info"), layer_id="forest"), + legend.LegendLayer( + _("Nature Conservation Area"), + _( + "Naturschutzgebiete dienen dem Schutz der Natur und Landschaft. Sie tragen zur Erhaltung, Entwicklung " + "und Wiederherstellung der Lebensstätte für bestimmte wild lebende Tier- und Pflanzenarten bei. Aber " + "auch aus wissenschaftlichen, naturgeschichtlichen und ästhetischen Gründen werden Teile oder die " + "Gesamtheit der Natur in Schutz genommen.", + ), + layer_id="nature_conservation_area", + ), + legend.LegendLayer( + _("Fauna Flora Habitat"), + _( + "Die Fauna-Flora-Habitat-Richtlinie ist eine Naturschutz-Richtlinie der Europäischen Union (EU), die " + "seltene oder bedrohte Arten und Lebensräume schützt. Sie gehört zum Schutzgebietsnetz Natura 2000.", + ), + layer_id="fauna_flora_habitat", + ), + legend.LegendLayer( + _("Special Protection Area"), + _( + "Die Vogelschutzrichtlinie der Europäischen Union (EU) dient der Erhaltung der wild lebenden, " + "heimischen Vogelarten. Sie regelt den Schutz dieser Vögel, ihrer Eier und Lebensräume wie Brut-, " + "Rast- und Überwinterungsgebiete. Die Vogelschutzgebiete gehören zum Schutzgebietsnetz Natura 2000.", + ), + layer_id="special_protection_area", + ), + legend.LegendLayer( + _("Biosphere Reserve"), + _( + "Biosphärenreservate sind großräumige und für bestimmte Landschaftstypen charakteristische Gebiete " + "mit interdisziplinärem Ansatz. In diesen von der UNESCO initiierten Modellregionen soll nachhaltige " + "Entwicklung in ökologischer, ökonomischer und sozialer Hinsicht exemplarisch verwirklicht werden. " + "Die Biosphärenreservate sind in drei Zonen eingeteilt: Eine naturschutzorientierte Kernzone " + "(Schutzfunktion), eine am Landschaftsschutz orientierte Pflegezone (Forschungs- und Bildungsfunktion)" + " und eine sozioökonomisch orientierte Entwicklungszone (Entwicklungsfunktion).", + ), + layer_id="biosphere_reserve", + ), + legend.LegendLayer( + _("Landscape Protection Area"), + _( + "Landschaftsschutzgebiete sind oft großflächiger angelegt und zielen auf den Erhalt des " + "Landschaftscharakters, das allgemeine Erscheinungsbild der Landschaft und dessen Schönheit ab. " + "Sie haben einen geringeren Schutzstatus als etwa Naturschutzgebiete oder Nationalparke und " + "unterliegen daher weniger strengen Nutzungsbeschränkungen.", + ), + layer_id="landscape_protection_area", + ), + legend.LegendLayer( + _("Forest"), + _( + "Wald umfasst eine Vielzahl an mit Bäumen und anderer Vegetation bedeckten Fläche " + "mit unterschiedlicher forstwirtschaftlicher Nutzung und ökologischer Bedeutung. Wälder können in " + "Nadel-, Laub- und Mischwald sowie anhand der Waldfunktionen (z. B. Schutzwald, Erholungswald) " + "unterschieden werden.", + ), + layer_id="forest", + ), legend.LegendLayer( _("Drinking Water Protection Area"), - _("layer info"), + _( + "Wasserschutzgebiete stellen die öffentliche Wasserversorgung durch die Vermeidung " + "schädlicher Eintragungen in die Gewässer (Grundwasser, oberirdische Gewässer, Küstengewässer) sicher.", + ), layer_id="drinking_water_protection_area", ), - legend.LegendLayer(_("Water"), _("layer info"), layer_id="water"), - legend.LegendLayer(_("Floodplain"), _("layer info"), layer_id="floodplain"), - legend.LegendLayer(_("Soil Quality High"), _("layer info"), layer_id="soil_quality_high"), - legend.LegendLayer(_("Soil Quality Low"), _("layer info"), layer_id="soil_quality_low"), + legend.LegendLayer( + _("Water"), + _( + "Ein Gewässer ist in der Natur fließendes oder stehendes Wasser. " + "Dazu gehören der Wasserkörper, das Gewässerbett und der Grundwasserleiter.", + ), + layer_id="water", + ), + legend.LegendLayer( + _("Floodplain"), + _( + "Bei Überschwemmungsgebieten handelt es sich um die Flächen, " + "die statistisch gesehen mindestens einmal in hundert Jahren überflutet sein können.", + ), + layer_id="floodplain", + ), + legend.LegendLayer( + _("Soil Quality High"), + _( + "Acker- und Grünlandflächen mit hoher Bodenqualität (Soil Quality Rating (SQR) >= 40). Um die " + "Flächenkonkurrenz zwischen landwirtschaftlicher Nutzung und Energiegewinnung zu minimieren, wird bei " + "den links einstellbaren PV-Freiflächenpotenzialen als Grenzwert ein SQR von 40 angenommen, es werden " + "also lediglich Flächen mit sehr geringer und geringer Ertragsfähigkeit als potenzielle " + "Standorte berücksichtigt.", + ), + layer_id="soil_quality_high", + ), + legend.LegendLayer( + _("Soil Quality Low"), + _( + "Acker- und Grünlandflächen inner- und außerhalb benachteiligter Gebiete mit geringer Bodenqualität " + "(Soil Quality Rating (SQR) < 40). Um die Flächenkonkurrenz zwischen landwirtschaftlicher Nutzung und " + "Energiegewinnung zu minimieren, wird bei den links einstellbaren PV-Freiflächenpotenzialen als " + "Grenzwert ein SQR von 40 angenommen, es werden also lediglich Flächen mit sehr geringer und geringer " + "Ertragsfähigkeit als potenzielle Standorte berücksichtigt.", + ), + layer_id="soil_quality_low", + ), ], } diff --git a/digiplan/map/migrations/0025_auto_20230803_1017.py b/digiplan/map/migrations/0025_auto_20230803_1017.py new file mode 100644 index 00000000..95e80aa8 --- /dev/null +++ b/digiplan/map/migrations/0025_auto_20230803_1017.py @@ -0,0 +1,293 @@ +# Generated by Django 3.2.20 on 2023-08-03 10:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0024_auto_20230706_1135'), + ] + + operations = [ + migrations.AddField( + model_name='biomass', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='biomass', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='combustion', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='gsgk', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='hydro', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvground', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvroof', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='storage', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='capacity_gross', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='windturbine', + name='city', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='commissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='commissioning_date_planned', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='decommissioning_date', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='status', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='voltage_level', + field=models.CharField(max_length=50, null=True), + ), + ] diff --git a/digiplan/map/migrations/0026_auto_20230803_1321.py b/digiplan/map/migrations/0026_auto_20230803_1321.py new file mode 100644 index 00000000..6b4d38bc --- /dev/null +++ b/digiplan/map/migrations/0026_auto_20230803_1321.py @@ -0,0 +1,313 @@ +# Generated by Django 3.2.20 on 2023-08-03 13:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0025_auto_20230803_1017'), + ] + + operations = [ + migrations.AddField( + model_name='biomass', + name='biomass_only', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='feedin_type', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='flexibility_bonus', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='fuel', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='kwk_mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='technology', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='biomass', + name='th_capacity', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='combustion', + name='bnetza_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='feedin_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='combustion', + name='fuel_other', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='combustion', + name='fuels', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='combustion', + name='kwk_mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='combustion', + name='technology', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='combustion', + name='th_capacity', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='combustion', + name='usage_sector', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='kwk_mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='technology', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='gsgk', + name='th_capacity', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='gsgk', + name='type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='hydro', + name='feedin_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='hydro', + name='kwk_mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='hydro', + name='plant_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='pvground', + name='area_occupied', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvground', + name='area_type', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvground', + name='citizens_unit', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='feedin_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='pvground', + name='landlord_to_tenant_electricity', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='module_count', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvground', + name='orientation_primary', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='orientation_secondary', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvground', + name='site_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='pvground', + name='usage_sector', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='area_occupied', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvroof', + name='area_type', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvroof', + name='citizens_unit', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='feedin_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='landlord_to_tenant_electricity', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='module_count', + field=models.FloatField(null=True), + ), + migrations.AddField( + model_name='pvroof', + name='orientation_primary', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='orientation_secondary', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='site_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='pvroof', + name='usage_sector', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='storage', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='citizens_unit', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='constraint_deactivation_animals', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='constraint_deactivation_ice', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='constraint_deactivation_shadowing', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='constraint_deactivation_sound_emission', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='constraint_deactivation_sound_emission_day', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='constraint_deactivation_sound_emission_night', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='manufacturer_name', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='mastr_id', + field=models.CharField(max_length=50, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='site_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='windturbine', + name='type_name', + field=models.CharField(max_length=255, null=True), + ), + ] diff --git a/digiplan/map/migrations/0027_auto_20230814_1028.py b/digiplan/map/migrations/0027_auto_20230814_1028.py new file mode 100644 index 00000000..800acb41 --- /dev/null +++ b/digiplan/map/migrations/0027_auto_20230814_1028.py @@ -0,0 +1,27 @@ +# Generated by Django 3.2.20 on 2023-08-14 10:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0026_auto_20230803_1321'), + ] + + operations = [ + migrations.RemoveField( + model_name='gsgk', + name='type', + ), + migrations.AddField( + model_name='gsgk', + name='unit_type', + field=models.CharField(max_length=255, null=True), + ), + migrations.AlterField( + model_name='hydro', + name='kwk_mastr_id', + field=models.FloatField(null=True), + ), + ] diff --git a/digiplan/map/migrations/0028_auto_20230814_1031.py b/digiplan/map/migrations/0028_auto_20230814_1031.py new file mode 100644 index 00000000..5282dc84 --- /dev/null +++ b/digiplan/map/migrations/0028_auto_20230814_1031.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.20 on 2023-08-14 10:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0027_auto_20230814_1028'), + ] + + operations = [ + migrations.AlterField( + model_name='biomass', + name='fuel', + field=models.CharField(max_length=255, null=True), + ), + migrations.AlterField( + model_name='biomass', + name='technology', + field=models.CharField(max_length=255, null=True), + ), + ] diff --git a/digiplan/map/migrations/0029_auto_20230829_0626.py b/digiplan/map/migrations/0029_auto_20230829_0626.py new file mode 100644 index 00000000..d4acf30c --- /dev/null +++ b/digiplan/map/migrations/0029_auto_20230829_0626.py @@ -0,0 +1,21 @@ +# Generated by Django 3.2.20 on 2023-08-29 06:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0028_auto_20230814_1031'), + ] + + operations = [ + migrations.AlterModelOptions( + name='pvground', + options={'verbose_name': 'Ground-mounted PV', 'verbose_name_plural': 'Ground-mounted PV'}, + ), + migrations.AlterModelOptions( + name='storage', + options={'verbose_name': 'Battery storage', 'verbose_name_plural': 'Battery storages'}, + ), + ] diff --git a/digiplan/map/models.py b/digiplan/map/models.py index f5b368d2..70462406 100644 --- a/digiplan/map/models.py +++ b/digiplan/map/models.py @@ -107,6 +107,14 @@ class RenewableModel(models.Model): unit_count = models.BigIntegerField(null=True) capacity_net = models.FloatField(null=True) zip_code = models.CharField(max_length=50, null=True) + status = models.CharField(max_length=50, null=True) + city = models.CharField(max_length=50, null=True) + commissioning_date = models.CharField(max_length=50, null=True) + commissioning_date_planned = models.CharField(max_length=50, null=True) + decommissioning_date = models.CharField(max_length=50, null=True) + capacity_gross = models.FloatField(null=True) + voltage_level = models.CharField(max_length=50, null=True) + mastr_id = models.CharField(max_length=50, null=True) mun_id = models.ForeignKey(Municipality, on_delete=models.DO_NOTHING, null=True) @@ -122,20 +130,48 @@ class WindTurbine(RenewableModel): name_park = models.CharField(max_length=255, null=True) hub_height = models.FloatField(null=True) rotor_diameter = models.FloatField(null=True) + site_type = models.CharField(max_length=255, null=True) + manufacturer_name = models.CharField(max_length=255, null=True) + type_name = models.CharField(max_length=255, null=True) + constraint_deactivation_sound_emission = models.CharField(max_length=50, null=True) + constraint_deactivation_sound_emission_night = models.CharField(max_length=50, null=True) + constraint_deactivation_sound_emission_day = models.CharField(max_length=50, null=True) + constraint_deactivation_shadowing = models.CharField(max_length=50, null=True) + constraint_deactivation_animals = models.CharField(max_length=50, null=True) + constraint_deactivation_ice = models.CharField(max_length=50, null=True) + citizens_unit = models.CharField(max_length=50, null=True) data_file = "bnetza_mastr_wind_agg_region" layer = "bnetza_mastr_wind" mapping = { "geom": "POINT", "name": "name", - "name_park": "name_park", "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", + "zip_code": "zip_code", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", + "name_park": "name_park", "hub_height": "hub_height", "rotor_diameter": "rotor_diameter", - "zip_code": "zip_code", - "mun_id": "municipality_id", + "site_type": "site_type", + "manufacturer_name": "manufacturer_name", + "type_name": "type_name", + "constraint_deactivation_sound_emission": "constraint_deactivation_sound_emission", + "constraint_deactivation_sound_emission_night": "constraint_deactivation_sound_emission_night", + "constraint_deactivation_sound_emission_day": "constraint_deactivation_sound_emission_day", + "constraint_deactivation_shadowing": "constraint_deactivation_shadowing", + "constraint_deactivation_animals": "constraint_deactivation_animals", + "constraint_deactivation_ice": "constraint_deactivation_ice", + "citizens_unit": "citizens_unit", } class Meta: # noqa: D106 @@ -165,6 +201,16 @@ class PVroof(RenewableModel): """Model holding PV roof.""" power_limitation = models.CharField(max_length=50, null=True) + site_type = models.CharField(max_length=255, null=True) + feedin_type = models.CharField(max_length=255, null=True) + module_count = models.FloatField(null=True) + usage_sector = models.CharField(max_length=50, null=True) + orientation_primary = models.CharField(max_length=50, null=True) + orientation_secondary = models.CharField(max_length=50, null=True) + area_type = models.FloatField(null=True) + area_occupied = models.FloatField(null=True) + citizens_unit = models.CharField(max_length=50, null=True) + landlord_to_tenant_electricity = models.CharField(max_length=50, null=True) data_file = "bnetza_mastr_pv_roof_agg_region" layer = "bnetza_mastr_pv_roof" @@ -176,8 +222,26 @@ class PVroof(RenewableModel): "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", "power_limitation": "power_limitation", - "mun_id": "municipality_id", + "site_type": "site_type", + "feedin_type": "feedin_type", + "module_count": "module_count", + "usage_sector": "usage_sector", + "orientation_primary": "orientation_primary", + "orientation_secondary": "orientation_secondary", + "area_type": "area_type", + "area_occupied": "area_occupied", + "citizens_unit": "citizens_unit", + "landlord_to_tenant_electricity": "landlord_to_tenant_electricity", } class Meta: # noqa: D106 @@ -193,6 +257,16 @@ class PVground(RenewableModel): """Model holding PV on ground.""" power_limitation = models.CharField(max_length=50, null=True) + site_type = models.CharField(max_length=255, null=True) + feedin_type = models.CharField(max_length=255, null=True) + module_count = models.FloatField(null=True) + usage_sector = models.CharField(max_length=50, null=True) + orientation_primary = models.CharField(max_length=50, null=True) + orientation_secondary = models.CharField(max_length=50, null=True) + area_type = models.FloatField(null=True) + area_occupied = models.FloatField(null=True) + citizens_unit = models.CharField(max_length=50, null=True) + landlord_to_tenant_electricity = models.CharField(max_length=50, null=True) data_file = "bnetza_mastr_pv_ground_agg_region" layer = "bnetza_mastr_pv_ground" @@ -204,19 +278,40 @@ class PVground(RenewableModel): "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", "power_limitation": "power_limitation", - "mun_id": "municipality_id", + "site_type": "site_type", + "feedin_type": "feedin_type", + "module_count": "module_count", + "usage_sector": "usage_sector", + "orientation_primary": "orientation_primary", + "orientation_secondary": "orientation_secondary", + "area_type": "area_type", + "area_occupied": "area_occupied", + "citizens_unit": "citizens_unit", + "landlord_to_tenant_electricity": "landlord_to_tenant_electricity", } class Meta: # noqa: D106 - verbose_name = _("Outdoor PV") - verbose_name_plural = _("Outdoor PVs") + verbose_name = _("Ground-mounted PV") + verbose_name_plural = _("Ground-mounted PV") class Hydro(RenewableModel): """Hydro model.""" water_origin = models.CharField(max_length=255, null=True) + kwk_mastr_id = models.FloatField(null=True) + plant_type = models.CharField(max_length=255, null=True) + feedin_type = models.CharField(max_length=255, null=True) data_file = "bnetza_mastr_hydro_agg_region" layer = "bnetza_mastr_hydro" @@ -228,8 +323,19 @@ class Hydro(RenewableModel): "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", "water_origin": "water_origin", - "mun_id": "municipality_id", + "kwk_mastr_id": "kwk_mastr_id", + "plant_type": "plant_type", + "feedin_type": "feedin_type", } class Meta: # noqa: D106 @@ -241,6 +347,13 @@ class Biomass(RenewableModel): """Biomass model.""" fuel_type = models.CharField(max_length=50, null=True) + kwk_mastr_id = models.CharField(max_length=50, null=True) + th_capacity = models.FloatField(null=True) + feedin_type = models.CharField(max_length=50, null=True) + technology = models.CharField(max_length=255, null=True) + fuel = models.CharField(max_length=255, null=True) + biomass_only = models.CharField(max_length=50, null=True) + flexibility_bonus = models.CharField(max_length=50, null=True) data_file = "bnetza_mastr_biomass_agg_region" layer = "bnetza_mastr_biomass" @@ -252,8 +365,23 @@ class Biomass(RenewableModel): "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", "fuel_type": "fuel_type", - "mun_id": "municipality_id", + "kwk_mastr_id": "kwk_mastr_id", + "th_capacity": "th_capacity", + "feedin_type": "feedin_type", + "technology": "technology", + "fuel": "fuel", + "biomass_only": "biomass_only", + "flexibility_bonus": "flexibility_bonus", } class Meta: # noqa: D106 @@ -265,6 +393,14 @@ class Combustion(RenewableModel): """Combustion model.""" name_block = models.CharField(max_length=255, null=True) + kwk_mastr_id = models.CharField(max_length=50, null=True) + bnetza_id = models.CharField(max_length=50, null=True) + usage_sector = models.CharField(max_length=50, null=True) + th_capacity = models.FloatField(null=True) + feedin_type = models.CharField(max_length=255, null=True) + technology = models.CharField(max_length=255, null=True) + fuel_other = models.CharField(max_length=255, null=True) + fuels = models.CharField(max_length=255, null=True) data_file = "bnetza_mastr_combustion_agg_region" layer = "bnetza_mastr_combustion" @@ -272,12 +408,28 @@ class Combustion(RenewableModel): mapping = { "geom": "POINT", "name": "name", - "name_block": "block_name", "zip_code": "zip_code", "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", - "mun_id": "municipality_id", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", + "name_block": "block_name", + "kwk_mastr_id": "kwk_mastr_id", + "bnetza_id": "bnetza_id", + "usage_sector": "usage_sector", + "th_capacity": "th_capacity", + "feedin_type": "feedin_type", + "technology": "technology", + "fuel_other": "fuel_other", + "fuels": "fuels", } class Meta: # noqa: D106 @@ -289,6 +441,10 @@ class GSGK(RenewableModel): """GSGK model.""" feedin_type = models.CharField(max_length=50, null=True) + kwk_mastr_id = models.CharField(max_length=50, null=True) + th_capacity = models.FloatField(null=True) + unit_type = models.CharField(max_length=255, null=True) + technology = models.CharField(max_length=255, null=True) data_file = "bnetza_mastr_gsgk_agg_region" layer = "bnetza_mastr_gsgk" @@ -300,8 +456,20 @@ class GSGK(RenewableModel): "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", + "mastr_id": "mastr_id", "feedin_type": "feedin_type", - "mun_id": "municipality_id", + "kwk_mastr_id": "kwk_mastr_id", + "th_capacity": "th_capacity", + "unit_type": "type", + "technology": "technology", } class Meta: # noqa: D106 @@ -322,12 +490,19 @@ class Storage(RenewableModel): "geometry_approximated": "geometry_approximated", "unit_count": "unit_count", "capacity_net": "capacity_net", - "mun_id": "municipality_id", + "mun_id": {"id": "municipality_id"}, + "status": "status", + "city": "city", + "commissioning_date": "commissioning_date", + "commissioning_date_planned": "commissioning_date_planned", + "decommissioning_date": "decommissioning_date", + "capacity_gross": "capacity_gross", + "voltage_level": "voltage_level", } class Meta: # noqa: D106 - verbose_name = _("Storage") - verbose_name_plural = _("Storages") + verbose_name = _("Battery storage") + verbose_name_plural = _("Battery storages") class StaticRegionModel(models.Model): diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 62498976..92a393de 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -147,36 +147,105 @@ def get_context_data(self) -> dict: "gsgk": models.GSGK, "storage": models.Storage, }[self.model_lookup] + # Some attributes have been removed, see + # https://github.com/rl-institut/digiplan/issues/332 # TODO(Hendrik Huyskens): Add mapping # https://github.com/rl-institut-private/digiplan/issues/153 default_attributes = { "name": "Name", - "mun_name": "Gemeindename", - "zip_code": "Postleitzahl", - "geometry_approximated": "Geschätzt", - "unit_count": "Anzahl", - "capacity_net": "Kapazität", + "mun_name": "Gemeinde", + "geometry_approximated": "Standort geschätzt", + "unit_count": "Anlagenanzahl", + "capacity_net": "Nettonennleistung (kW)", + "status": "Betriebsstatus", + "city": "Ort", + "commissioning_date": "Inbetriebnahmedatum", + "commissioning_date_planned": "Geplantes Inbetriebnahmedatum", + "voltage_level": "Spannungsebene", + } + specific_attributes = { + # multiple + "citizens_unit": "Bürgerenergieanlage", + "technology": "Technologie", + "feedin_type": "Einspeisungsart", + "th_capacity": "Thermische Nutzleistung", + # Wind Turbines + "hub_height": "Nabenhöhe", + "rotor_diameter": "Rotordurchmesser", + "manufacturer_name": "Hersteller", + "type_name": "Typenbezeichnung", + "constraint_deactivation_sound_emission": "Auflage Abschaltung Leistungsbegrenzung", + "constraint_deactivation_sound_emission_night": "Auflagen Abschaltung Schallimmissionsschutz Nachts", + "constraint_deactivation_sound_emission_day": "Auflagen Abschaltung Schallimmissionsschutz Tagsüber", + "constraint_deactivation_shadowing": "Auflagen Abschaltung Schattenwurf", + "constraint_deactivation_animals": "Auflagen Abschaltung Tierschutz", + "constraint_deactivation_ice": "Auflagen Abschaltung Eiswurf", + # PV Roof / Ground + "landlord_to_tenant_electricity": "Mieterstrom Zugeordnet", + # Hydro + "water_origin": "Art Des Zuflusses", + "plant_type": "Art Der Wasserkraftanlage", + # Biomass + "fuel_type": "Biomasseart", + "fuel": "Hauptbrennstoff", + # Combustion + "name_block": "Name Kraftwerksblock", + "bnetza_id": "Kraftwerksnummer", + "fuels": "Hauptbrennstoff", + "fuel_other": "Weitere Brennstoffe", + # GSGK + "unit_type": "Einheittyp", } instance = model.objects.annotate(mun_name=F("mun_id__name")).get(pk=self.selected_id) - return { + data_dict = { "title": model._meta.verbose_name, # noqa: SLF001 "data": {name: getattr(instance, key) for key, name in default_attributes.items()}, } + for key, name in specific_attributes.items(): + if hasattr(instance, key): + value = getattr(instance, key) + data_dict["data"][name] = value + + return data_dict class CapacityPopup(RegionPopup): """Popup to show capacities.""" lookup = "capacity" + title = "Installierte Leistung EE" def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.capacities_per_municipality() +class Capacity2045Popup(RegionPopup): + """Popup to show capacities in 2045.""" + + lookup = "capacity" + title = "Installierte Leistung EE" + + def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 + return calculations.capacities_per_municipality_2045(self.map_state["simulation_id"]) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + def get_chart_data(self) -> Iterable: + """Create capacity chart data for SQ and future scenario.""" + status_quo_data = calculations.capacities_per_municipality().loc[self.selected_id] + future_data = super().get_chart_data() + return list(zip(status_quo_data, future_data)) + + class CapacitySquarePopup(RegionPopup): """Popup to show capacities per km².""" lookup = "capacity" + title = "Installierte Leistung EE pro km²" def get_detailed_data(self) -> pd.DataFrame: """Return capacities per square kilometer.""" @@ -186,16 +255,44 @@ def get_detailed_data(self) -> pd.DataFrame: def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Installed capacities per square meter") + chart_options["title"]["text"] = _("installierte Leistung nach Typ") chart_options["yAxis"]["name"] = _("MW/km²") return chart_options +class CapacitySquare2045Popup(RegionPopup): + """Popup to show capacities per km² in 2045.""" + + lookup = "capacity" + title = "Installierte Leistung EE pro km²" + + 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"]), + ) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["title"]["text"] = _("installierte Leistung nach Typ") + chart_options["yAxis"]["name"] = _("MW/km²") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + def get_chart_data(self) -> Iterable: + """Create capacity chart data for SQ and future scenario.""" + status_quo_data = calculations.calculate_square_for_value(calculations.capacities_per_municipality()).loc[ + self.selected_id + ] + future_data = super().get_chart_data() + return list(zip(status_quo_data, future_data)) + + class EnergyPopup(RegionPopup): """Popup to show energies.""" lookup = "capacity" - title = _("Energies") + title = _("Gewonnene Energie aus EE") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.energies_per_municipality() @@ -203,7 +300,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Energies per technology") + chart_options["title"]["text"] = _("Energieanteile pro Technologie") chart_options["yAxis"]["name"] = _("GWh") return chart_options @@ -212,7 +309,7 @@ class Energy2045Popup(RegionPopup): """Popup to show energies.""" lookup = "capacity" - title = _("Energies") + 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"]) @@ -220,7 +317,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Energies per technology") + chart_options["title"]["text"] = _("Energieanteile pro Technologie") chart_options["yAxis"]["name"] = _("GWh") chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -228,7 +325,7 @@ def get_chart_options(self) -> dict: def get_chart_data(self) -> Iterable: """Create capacity chart data for SQ and future scenario.""" status_quo_data = calculations.energies_per_municipality().loc[self.selected_id] - future_data = super().get_chart_data() + future_data = super().get_chart_data() * 1e-3 return list(zip(status_quo_data, future_data)) @@ -236,7 +333,7 @@ class EnergySharePopup(RegionPopup): """Popup to show energy shares.""" lookup = "capacity" - title = _("Energie Shares") + title = _("Anteil Energie aus EE") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 return calculations.energy_shares_per_municipality() @@ -244,7 +341,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Energy shares per technology") + chart_options["title"]["text"] = _("Energieanteile pro Technologie") chart_options["yAxis"]["name"] = _("%") return chart_options @@ -261,7 +358,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Energy per capita per technology") + chart_options["title"]["text"] = _("Energieanteile pro Technologie") chart_options["yAxis"]["name"] = _("MWh") return chart_options @@ -280,7 +377,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Energies per capita per technology") + chart_options["title"]["text"] = _("Energieanteile pro Technologie") chart_options["yAxis"]["name"] = _("GWh") chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -290,7 +387,7 @@ def get_chart_data(self) -> Iterable: status_quo_data = calculations.calculate_capita_for_value(calculations.energies_per_municipality()).loc[ self.selected_id ] - future_data = super().get_chart_data() + future_data = super().get_chart_data() * 1e-3 return list(zip(status_quo_data, future_data)) @@ -306,7 +403,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Energie pro km²") + chart_options["title"]["text"] = _("Energieanteile pro km²") chart_options["yAxis"]["name"] = _("MWh") return chart_options @@ -325,7 +422,7 @@ def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Gewonnene Energie pro km²") + chart_options["title"]["text"] = _("Energieanteile pro km²") chart_options["yAxis"]["name"] = _("MWh") chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -335,7 +432,7 @@ def get_chart_data(self) -> Iterable: status_quo_data = calculations.calculate_square_for_value(calculations.energies_per_municipality()).loc[ self.selected_id ] - future_data = super().get_chart_data() + future_data = super().get_chart_data() * 1e-3 return list(zip(status_quo_data, future_data)) @@ -343,27 +440,47 @@ class PopulationPopup(RegionPopup): """Popup to show Population.""" lookup = "population" + title = "Bevölkerung" def get_detailed_data(self) -> pd.DataFrame: """Return population data.""" return models.Population.quantity_per_municipality_per_year() + def get_municipality_value(self) -> Optional[float]: + """Return municipality value for status quo year.""" + return self.detailed_data.loc[self.selected_id][2022] + + def get_region_value(self) -> float: + """Return region value for status quo year.""" + return self.detailed_data.sum()[2022] + class PopulationDensityPopup(RegionPopup): """Popup to show Population Density.""" lookup = "population" + title = "Bevölkerungsdichte" def get_detailed_data(self) -> pd.DataFrame: """Return population data squared.""" population = models.Population.quantity_per_municipality_per_year() return calculations.calculate_square_for_value(population) + def get_municipality_value(self) -> Optional[float]: + """Return municipality value for status quo year.""" + return self.detailed_data.loc[self.selected_id][2022] + + def get_region_value(self) -> float: + """Return region value for status quo year.""" + return calculations.calculate_square_for_value( + pd.DataFrame(models.Population.quantity_per_municipality_per_year().sum()).transpose(), + ).sum()[2022] + def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() chart_options["title"]["text"] = _("Population density per year") - chart_options["yAxis"]["name"] = _("Pop/km²") + chart_options["yAxis"]["name"] = _("EW/km²") return chart_options @@ -384,7 +501,7 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() chart_options["title"]["text"] = _("Beschäftigte") - chart_options["yAxis"]["name"] = _("") + chart_options["yAxis"]["name"] = "" del chart_options["series"][0]["name"] return chart_options @@ -406,7 +523,7 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() chart_options["title"]["text"] = _("Betriebe") - chart_options["yAxis"]["name"] = _("") + chart_options["yAxis"]["name"] = "" del chart_options["series"][0]["name"] return chart_options @@ -428,10 +545,46 @@ def get_chart_data(self) -> Iterable: return [int(self.detailed_data.loc[self.selected_id])] +class NumberWindturbines2045Popup(RegionPopup): + """Popup to show the number of wind turbines in 2045.""" + + lookup = "wind_turbines" + title = _("Number of wind turbines") + description = _("Description for number of wind turbines") + unit = "" + + 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"]) + + def get_region_value(self) -> float: + """Return aggregated data of all municipalities and technologies.""" + return self.detailed_data.sum() + + def get_municipality_value(self) -> Optional[float]: + """Return aggregated data for all technologies for given municipality ID.""" + if self.selected_id not in self.detailed_data.index: + return 0 + return self.detailed_data.loc[self.selected_id] + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + def get_chart_data(self) -> Iterable: + """Create capacity chart data for SQ and future scenario.""" + status_quo_data = models.WindTurbine.quantity_per_municipality().loc[self.selected_id] + future_data = super().get_chart_data() + return [int(status_quo_data), int(future_data)] + + class NumberWindturbinesSquarePopup(RegionPopup): """Popup to show the number of wind turbines per km².""" lookup = "wind_turbines" + title = "Windenergieanlagen pro km²" def get_detailed_data(self) -> pd.DataFrame: """Return quantity of wind turbines per municipality (index).""" @@ -441,7 +594,7 @@ def get_detailed_data(self) -> pd.DataFrame: def get_chart_options(self) -> dict: """Overwrite title and unit in chart options.""" chart_options = super().get_chart_options() - chart_options["title"]["text"] = _("Wind turbines per square meter") + chart_options["title"]["text"] = _("Windturbinen pro km²") chart_options["yAxis"]["name"] = _("WT/km²") return chart_options @@ -450,6 +603,44 @@ def get_chart_data(self) -> Iterable: return [float(self.detailed_data.loc[self.selected_id])] +class NumberWindturbinesSquare2045Popup(RegionPopup): + """Popup to show the number of wind turbines per km² in 2045.""" + + lookup = "wind_turbines" + title = "Windenergieanlagen pro km²" + + 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"]) + return calculations.calculate_square_for_value(wind_turbines) + + def get_region_value(self) -> float: + """Return aggregated data of all municipalities and technologies.""" + return self.detailed_data.sum() + + def get_municipality_value(self) -> Optional[float]: + """Return aggregated data for all technologies for given municipality ID.""" + if self.selected_id not in self.detailed_data.index: + return 0 + return self.detailed_data.loc[self.selected_id] + + def get_chart_options(self) -> dict: + """Overwrite title and unit in chart options.""" + chart_options = super().get_chart_options() + chart_options["title"]["text"] = _("Windturbinen pro km²") + chart_options["yAxis"]["name"] = _("WT/km²") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + def get_chart_data(self) -> Iterable: + """Return single value for wind turbines in current municipality.""" + status_quo_data = calculations.calculate_square_for_value(models.WindTurbine.quantity_per_municipality()).loc[ + self.selected_id + ] + future_data = super().get_chart_data() + return [float(status_quo_data), float(future_data)] + + class ElectricityDemandPopup(RegionPopup): """Popup to show electricity demand.""" @@ -467,6 +658,30 @@ def get_chart_options(self) -> dict: return chart_options +class ElectricityDemand2045Popup(RegionPopup): + """Popup to show electricity demand in 2045.""" + + lookup = "electricity_demand" + title = _("Strombedarf") + + def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 + return calculations.electricity_demand_per_municipality_2045(self.map_state["simulation_id"]) + + def get_chart_data(self) -> Iterable: + """Create capacity chart data for SQ and future scenario.""" + status_quo_data = calculations.electricity_demand_per_municipality().loc[self.selected_id] + future_data = super().get_chart_data() + return list(zip(status_quo_data, future_data)) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["title"]["text"] = _("Strombedarf") + chart_options["yAxis"]["name"] = _("GWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + class ElectricityDemandCapitaPopup(RegionPopup): """Popup to show electricity demand capita.""" @@ -474,7 +689,10 @@ class ElectricityDemandCapitaPopup(RegionPopup): title = _("Strombedarf je EinwohnerIn") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.calculate_capita_for_value(calculations.electricity_demand_per_municipality()) + return calculations.calculate_capita_for_value(calculations.electricity_demand_per_municipality()) * 1e6 + + def get_region_value(self) -> float: # noqa: D102 + return self.detailed_data.sum(axis=1).mean() def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -484,6 +702,37 @@ def get_chart_options(self) -> dict: return chart_options +class ElectricityDemandCapita2045Popup(RegionPopup): + """Popup to show electricity demand capita in 2045.""" + + lookup = "electricity_demand" + title = _("Strombedarf je EinwohnerIn") + + 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"]), + ) + + def get_region_value(self) -> float: # noqa: D102 + return self.detailed_data.sum(axis=1).mean() + + def get_chart_data(self) -> Iterable: + """Create capacity chart data for SQ and future scenario.""" + status_quo_data = calculations.calculate_capita_for_value( + calculations.electricity_demand_per_municipality(), + ).loc[self.selected_id] + future_data = super().get_chart_data() + return list(zip(status_quo_data, future_data)) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["title"]["text"] = _("Strombedarf je EinwohnerIn") + chart_options["yAxis"]["name"] = _("kWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + class HeatDemandPopup(RegionPopup): """Popup to show heat demand.""" @@ -491,13 +740,37 @@ class HeatDemandPopup(RegionPopup): title = _("Wärmebedarf") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.electricity_demand_per_municipality() + return calculations.heat_demand_per_municipality() + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["title"]["text"] = _("Wärmebedarf") + chart_options["yAxis"]["name"] = _("GWh") + return chart_options + + +class HeatDemand2045Popup(RegionPopup): + """Popup to show heat demand in 2045.""" + + lookup = "heat_demand" + title = _("Wärmebedarf") + + def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 + return calculations.heat_demand_per_municipality_2045(self.map_state["simulation_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] + future_data = super().get_chart_data() + return list(zip(status_quo_data, future_data)) def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() chart_options["title"]["text"] = _("Wärmebedarf") chart_options["yAxis"]["name"] = _("GWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] return chart_options @@ -508,7 +781,10 @@ class HeatDemandCapitaPopup(RegionPopup): title = _("Wärmebedarf je EinwohnerIn") def get_detailed_data(self) -> pd.DataFrame: # noqa: D102 - return calculations.calculate_capita_for_value(calculations.electricity_demand_per_municipality()) + return calculations.calculate_capita_for_value(calculations.heat_demand_per_municipality()) * 1e6 + + def get_region_value(self) -> float: # noqa: D102 + return self.detailed_data.sum(axis=1).mean() def get_chart_options(self) -> dict: """Overwrite title and unit.""" @@ -518,6 +794,37 @@ def get_chart_options(self) -> dict: return chart_options +class HeatDemandCapita2045Popup(RegionPopup): + """Popup to show heat demand capita in 2045.""" + + lookup = "heat_demand" + 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_2045(self.map_state["simulation_id"]), + ) + + def get_region_value(self) -> float: # noqa: D102 + return self.detailed_data.sum(axis=1).mean() + + 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(), + ).loc[self.selected_id] + future_data = super().get_chart_data() + return list(zip(status_quo_data, future_data)) + + def get_chart_options(self) -> dict: + """Overwrite title and unit.""" + chart_options = super().get_chart_options() + chart_options["title"]["text"] = _("Wärmebedarf je EinwohnerIn") + chart_options["yAxis"]["name"] = _("kWh") + chart_options["xAxis"]["data"] = ["Status Quo", "Mein Szenario"] + return chart_options + + class BatteriesPopup(RegionPopup): """Popup to show battery count.""" @@ -535,7 +842,7 @@ def get_chart_options(self) -> dict: """Overwrite title and unit.""" chart_options = super().get_chart_options() chart_options["title"]["text"] = _("Anzahl Batteriespeicher") - chart_options["yAxis"]["name"] = _("#") + chart_options["yAxis"]["name"] = "" del chart_options["series"][0]["name"] return chart_options @@ -584,12 +891,20 @@ def get_chart_options(self) -> dict: "energy_square_2045": EnergySquare2045Popup, "capacity_statusquo": CapacityPopup, "capacity_square_statusquo": CapacitySquarePopup, + "capacity_2045": Capacity2045Popup, + "capacity_square_2045": CapacitySquare2045Popup, "wind_turbines_statusquo": NumberWindturbinesPopup, + "wind_turbines_2045": NumberWindturbines2045Popup, "wind_turbines_square_statusquo": NumberWindturbinesSquarePopup, + "wind_turbines_square_2045": NumberWindturbinesSquare2045Popup, "electricity_demand_statusquo": ElectricityDemandPopup, + "electricity_demand_2045": ElectricityDemand2045Popup, "electricity_demand_capita_statusquo": ElectricityDemandCapitaPopup, + "electricity_demand_capita_2045": ElectricityDemandCapita2045Popup, "heat_demand_statusquo": HeatDemandPopup, + "heat_demand_2045": HeatDemand2045Popup, "heat_demand_capita_statusquo": HeatDemandCapitaPopup, + "heat_demand_capita_2045": HeatDemandCapita2045Popup, "batteries_statusquo": BatteriesPopup, "batteries_capacity_statusquo": BatteriesCapacityPopup, } diff --git a/digiplan/map/views.py b/digiplan/map/views.py index 066af62e..4cd57458 100644 --- a/digiplan/map/views.py +++ b/digiplan/map/views.py @@ -78,13 +78,14 @@ def get_context_data(self, **kwargs) -> dict: context["detailed_overview"] = charts.Chart("detailed_overview").render() context["ghg_overview"] = charts.Chart("ghg_overview").render() context["electricity_overview"] = charts.Chart("electricity_overview").render() - context["electricity_ghg"] = charts.Chart("electricity_ghg").render() - context["mobility_overview"] = charts.Chart("mobility_overview").render() - context["mobility_ghg"] = charts.Chart("mobility_ghg").render() - context["overview_heat"] = charts.Chart("overview_heat").render() - context["decentralized_centralized_heat"] = charts.Chart("decentralized_centralized_heat").render() + context["electricity_autarky"] = charts.Chart("electricity_autarky").render() + context["heat_decentralized"] = charts.Chart("heat_decentralized").render() + context["heat_centralized"] = charts.Chart("heat_centralized").render() context["ghg_history"] = charts.Chart("ghg_history").render() context["ghg_reduction"] = charts.Chart("ghg_reduction").render() + context["onboarding_wind"] = charts.Chart("onboarding_wind").render() + context["onboarding_pv_ground"] = charts.Chart("onboarding_pv_ground").render() + context["onboarding_pv_roof"] = charts.Chart("onboarding_pv_roof").render() return context diff --git a/digiplan/static/config/energy_settings_panel.json b/digiplan/static/config/energy_settings_panel.json index 32e0dee6..76a6665a 100644 --- a/digiplan/static/config/energy_settings_panel.json +++ b/digiplan/static/config/energy_settings_panel.json @@ -1,52 +1,52 @@ { "s_w_1": { "class": "js-slider js-slider-panel js-power-mix", - "color": "#99D8C9", + "color": "#6A89CC", "grid": false, - "label": "{% trans 'Wind energy (MW)' %}", + "label": "{% trans 'Windenergie (MW)' %}", "max": 1512, "min": 0, "start": 30, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'The use of wind potential areas can be set here.\nNote: Since the setting always refers to future, more profitable systems, maintaining the current system capacity also leads to increases in yield.' %} ", + "tooltip": "{% trans 'Nennleistung der in der Region installierten Windenergieanlagen in Megawatt. Die dargestellten Ziele für 2045 sind abgeleitet aus den im Windenergieflächenbedarfsgesetz festgesetzten Zielen für Sachsen-Anhalt (2,2 % bis 2032).\n\nHinweis: Da sich die Einstellung immer auf zukünftige, ertragreichere Anlagen bezieht, führt auch die Beibehaltung der aktuellen Anlagenkapazität zu Ertragssteigerungen.' %} ", "type": "slider", "sidepanel": true }, "s_w_3": { "class": "js-sidepanel-switch", - "label": "{% trans 'Only previous priority areas/suitable areas.' %}", - "tooltip": "{% trans 'Only previous priority areas/suitable areas.' %}", + "label": "{% trans 'Nur bestehende VR/EG' %}", + "tooltip": "{% trans 'Nur die in 2022 rechtskräftig ausgewiesenen Vorrang-/Eignungsgebiete nach Sachlichem Teilplan Wind 2018 verwenden (ca. 0,9 % der Fläche).' %}", "type": "switch" }, "s_w_4": { "class": "js-sidepanel-switch", - "label": "{% trans 'Priority areas according to STP Wind 2027.' %}", - "tooltip": "{% trans 'Priority areas according to STP Wind 2027.' %}", + "label": "{% trans 'Planabsicht nach STP Wind 2027' %}", + "tooltip": "{% trans 'Planabsicht der Regionalen Planungsgemeinschaft nach Entwurf für Sachlichen Teilplan Wind 2027 (ca. 3 % der Fläche).' %}", "type": "switch" }, "s_w_4_1": { "class": "js-sidepanel-switch", - "label": "{% trans 'Priority areas.' %}", - "tooltip": "{% trans 'Priority areas.' %}", + "label": "{% trans 'Vorranggebiete' %}", + "tooltip": "{% trans 'Vorranggebiete: Planabsicht der Regionalen Planungsgemeinschaft nach Entwurf für Sachlichen Teilplan Wind 2027 (ca. 2,7 % der Fläche).' %}", "type": "switch" }, "s_w_4_2": { "class": "js-sidepanel-switch", - "label": "{% trans 'Priority areas repowering.' %}", - "tooltip": "{% trans 'Priority areas repowering.' %}", + "label": "{% trans 'Vorranggebiete Repowering' %}", + "tooltip": "{% trans 'Vorranggebiete für Repowering: Planabsicht der Regionalen Planungsgemeinschaft nach Entwurf für Sachlichen Teilplan Wind 2027 (ca. 0,3 % der Fläche).' %}", "type": "switch" }, "s_w_5": { "class": "js-sidepanel-switch", - "label": "{% trans 'Free extension in the search area.' %}", - "tooltip": "{% trans 'Free extension in the search area.' %}", + "label": "{% trans 'Freier Zubau im Suchraum' %}", + "tooltip": "{% trans 'Hier kann die Nutzung der von der Regionalen Planungsgemeinschaft ermittelten Suchräume frei eingestellt werden.\nDiese Suchräume können jedoch nur zu einem Teil für die Windenergie genutzt werden.' %}", "type": "switch" }, "s_w_5_1": { "class": "js-slider", - "label": "{% trans 'Use of open country search area.' %}", - "tooltip": "{% trans 'Use of open country search area.' %}", + "label": "{% trans 'Nutzung Suchraum Offenland' %}", + "tooltip": "{% trans 'Hier kann die Nutzung des von der Regionalen Planungsgemeinschaft ermittelten Suchraums im Offenland frei eingestellt werden.\nDieser Suchraum kann jedoch nur zu einem Teil für die Windenergie genutzt werden.' %}", "max": 100, "min": 0, "start": 0, @@ -56,8 +56,8 @@ }, "s_w_5_2": { "class": "js-slider", - "label": "{% trans 'Use of forest search space.' %}", - "tooltip": "{% trans 'Use of forest search space.' %}", + "label": "{% trans 'Nutzung Suchraum Wald' %}", + "tooltip": "{% trans 'Hier kann die Nutzung des von der Regionalen Planungsgemeinschaft ermittelten Suchraums in Wäldern frei eingestellt werden.\nDieser Suchraum kann jedoch nur zu einem Teil für die Windenergie genutzt werden.' %}", "max": 100, "min": 0, "start": 0, @@ -67,148 +67,137 @@ }, "s_pv_ff_1": { "class": "js-slider js-slider-panel js-power-mix", - "color": "#A6BDDB", + "color": "#EFAD25", "grid": false, - "label": "{% trans 'Outdoor PV (MW)' %}", + "label": "{% trans 'Freiflächen-PV (MWp)' %}", "max": 1200, "min": 0, "start": 30, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Outdoor PV (MW)' %}", + "tooltip": "{% trans 'Nennleistung der in der Region installierten Freiflächen-PV-Anlagen in Megawatt (peak). Die dargestellten Ziele für 2045 sind abgeleitet aus den im EEG festgesetzten Zielen und den verfügbaren Potenzialen in der Region.\n\nHinweis: Da sich die Einstellung immer auf zukünftige, ertragreichere Anlagen bezieht, führt auch die Beibehaltung der aktuellen Anlagenkapazität zu Ertragssteigerungen.' %}", "type": "slider", "sidepanel": true }, "s_pv_ff_3": { "class": "js-slider", - "label": "{% trans 'Areas along the federal motorway and rail routes (%)' %} ", + "label": "{% trans 'Flächen entlang Autobahnen und Schienenwegen (%)' %} ", "max": 100, "min": 0, "start": 0, "step": 5, - "tooltip": "{% trans 'Areas along the federal motorway and rail routes (%)' %} ", + "tooltip": "{% trans 'Welcher Flächenanteil entlang von Autobahnen und Schienenwegen (500 m-Streifen) soll durch PV-Freiflächenanlagen genutzt werden? 100 % ist hierbei das maximal zur Verfügung stehende Potenzial (Kartendarstellung), das jedoch nur zu einem Teil genutzt werden kann. Die Potenzialflächen stammen aus dem PV- und Windflächenrechner des RLI.' %} ", "type": "slider" }, "s_pv_ff_4": { "class": "js-slider", - "label": "{% trans 'Use of agricultural and grassland areas (%)' %} ", + "label": "{% trans 'Agrar- und Grünlandflächen (%)' %} ", "max": 100, "min": 0, "start": 0, "step": 5, - "tooltip": "{% trans 'Use of agricultural and grassland areas (%)' %} ", + "tooltip": "{% trans 'Welcher Flächenanteil auf Agrar- und Grünlandflächen mit geringer Bodengüte (SQR<40) soll durch PV-Freiflächenanlagen genutzt werden? 100 % ist hierbei das maximal zur Verfügung stehende Potenzial (Kartendarstellung), das jedoch nur zu einem Teil genutzt werden kann. Die Potenzialflächen stammen aus dem PV- und Windflächenrechner des RLI.' %} ", "type": "slider" }, "s_pv_d_1": { "class": "js-slider js-slider-panel js-power-mix", - "color": "#FEC44F", + "color": "#FFD660", "grid": false, - "label": "{% trans 'Roof-mounted PV (MW)' %}", + "label": "{% trans 'Aufdach-PV (MWp)' %}", "max": 400, "min": 0, "start": 30, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Roof-mounted PV' %}", + "tooltip": "{% trans 'Nennleistung der in der Region installierten PV-Aufdachanlagen in Megawatt (peak). Die dargestellten Ziele für 2045 sind abgeleitet aus den im EEG festgesetzten Zielen und den verfügbaren Potenzialen (Dachflächen) in der Region.\n\nHinweis: Da sich die Einstellung immer auf zukünftige, ertragreichere Anlagen bezieht, führt auch die Beibehaltung der aktuellen Anlagenkapazität zu Ertragssteigerungen.' %}", "type": "slider", "sidepanel": true }, "s_pv_d_3": { "class": "js-slider", - "label": "{% trans 'Share of used roofs (%)' %}", + "label": "{% trans 'Anteil genutzter Dächer (%)' %}", "max": 100, "min": 0, "start": 0, "step": 5, - "tooltip": "{% trans 'Share of used roofs (%)' %}", + "tooltip": "{% trans 'Anteil genutzter Dächer (%). Das Maximum liegt hierbei bei 50 % aller Dächer von nicht-denkmalgeschützten Gebäuden mit Ausrichtung Süden, Osten und Westen sowie Flachdächern. Die Dachflächenpotenziale wurden auf Basis des Webtools der Regionalen Planungsgemeinschaft berechnet.' %}", "type": "slider" }, "s_pv_d_4": { "class": "js-slider", - "label": "{% trans 'Proportion of photovoltaic systems with electricity storage (%)' %}", + "label": "{% trans 'Anteil Anlagen mit Stromspeicher (%)' %}", "max": 100, "min": 0, "start": 0, "step": 5, - "tooltip": "{% trans 'Proportion of photovoltaic systems with electricity storage (%)' %}", + "tooltip": "{% trans 'Anteil der PV-Aufdachanlagen, die mit einem Strom(heim)speicher ausgestattet werden.' %}", "type": "slider" }, "s_h_1": { "class": "js-slider js-slider-panel js-power-mix", - "color": "#FA9FB5", - "label": "{% trans 'Hydropower (MW)' %}", + "color": "#A9BDE8", + "label": "{% trans 'Wasserkraft (MW)' %}", "max": 30, "min": 0, "start": 5, "status_quo": 15, "step": 1, - "tooltip": "{% trans 'Run-of-river power plants' %}", + "tooltip": "{% trans 'Nennleistung der in der Region installierten Laufwasserkraftwerke in Megawatt. Da das Potenzial hier als ausgeschöpft gilt, werden nur bestehende Anlagen berücksichtigt.' %}", "type": "slider" }, "s_v_1": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'Power consumption without heat generation (%)' %}", + "label": "{% trans 'Stromverbrauch, ohne Wärme (%)' %}", "max": 210, "min": 50, "start": 50, "status_quo": 50, "step": 10, - "tooltip": "{% trans 'Power consumption without providing heat using power-to-heat (e.g. heat pumps)' %}", + "tooltip": "{% trans 'Relativer Stromverbrauch von Haushalten, GHD und Industrie gegenüber heute, ohne Bereitstellung von Wärme durch Power-to-Heat (z. B. Wärmepumpen). Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider", "sidepanel": true }, "s_v_3": { "class": "js-slider", "grid": false, - "label": "{% trans 'Households (%)' %}", + "label": "{% trans 'Haushalte (%)' %}", "max": 200, "min": 50, "start": 100, "step": 10, - "tooltip": "{% trans 'Haushalte (%)' %}", + "tooltip": "{% trans 'Relativer Stromverbrauch gegenüber heute. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider" }, "s_v_4": { "class": "js-slider", - "label": "{% trans 'Commercial, Trade, Services (%)' %}", + "label": "{% trans 'Gewerbe, Handel, Dienstleistungen (%)' %}", "max": 200, "min": 50, "start": 100, "step": 10, - "tooltip": "{% trans 'Commercial, Trade, Services (%)' %}", + "tooltip": "{% trans 'Relativer Stromverbrauch gegenüber heute. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider" }, "s_v_5": { "class": "js-slider", - "label": "{% trans 'Industry (%)' %}", + "label": "{% trans 'Industrie (%)' %}", "max": 200, "min": 50, "start": 100, "step": 10, - "tooltip": "{% trans 'Industry (%)' %}", + "tooltip": "{% trans 'Relativer Stromverbrauch gegenüber heute. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider" }, "s_s_g_1": { - "class": "js-slider js-slider-panel js-power-mix", - "label": "{% trans 'Large scale batteries (MWh)' %}", + "class": "js-slider js-slider-panel", + "label": "{% trans 'Großbatterien: Zwischenspeicherung der tägl. Einspeisung durch Windenergie und Freiflächen-PV (%)' %}", "max": 20, "min": 0, "start": 2, "status_quo": 3, "step": 0.1, - "tooltip": "{% trans 'Large scale batteries (MWh)' %}", - "type": "slider", - "sidepanel": true - }, - "s_s_g_3": { - "class": "js-slider", - "label": "{% trans 'Storage of wind and PV energy (%)' %}", - "max": 200, - "min": 0, - "start": 5, - "step": 10, - "tooltip": "{% trans 'Storage of wind and PV energy (%)' %}", + "tooltip": "{% trans 'Wieviel der täglich (im Mittel) eingespeisten Energiemenge aus Windenergie und Freiflächen-PV sollen mit Hilfe von Großbatterien zwischengespeichert werden können?\n\nHinweis: PV-Heimspeicher kannst Du bei Aufdach-PV einstellen.' %}", "type": "slider" } } diff --git a/digiplan/static/config/heat_settings_panel.json b/digiplan/static/config/heat_settings_panel.json index e9768828..3b22bc13 100644 --- a/digiplan/static/config/heat_settings_panel.json +++ b/digiplan/static/config/heat_settings_panel.json @@ -2,25 +2,27 @@ "w_d_wp_1": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'Decentralized (%)' %}", + "label": "{% trans 'Anteil Wärmepumpen dezentral (%)' %}", "max": 100, "min": 0, "start": 30, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Decentralized heat supply' %}", + "from-min": 50, + "tooltip": "{% trans 'Energetischer Anteil von Wärmepumpen in dezentralen Heizungssystemen. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider", "sidepanel": true }, "w_d_wp_3": { "class": "js-slider", - "label": "{% trans 'Households (%)' %}", + "label": "{% trans 'Haushalte (%)' %}", "max": 100, "min": 50, "start": 50, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Heat pumps for heating and hot water in households with their own heat generation' %}", + "from-min": 50, + "tooltip": "{% trans 'Anteil von Wärmepumpen für Verbraucher mit eigener Wärmeerzeugung im Haushaltssektor.' %}", "type": "slider" }, "w_d_wp_4": { @@ -31,7 +33,8 @@ "start": 50, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Heat pumps for heating and hot water in the commercial, retail and service sectors with their own heat generation.' %}", + "from-min": 50, + "tooltip": "{% trans 'Anteil von Wärmepumpen für Verbraucher mit eigener Wärmeerzeugung im Gewerbe-, Handels- und Dienstleistungssektor.' %}", "type": "slider" }, "w_d_wp_5": { @@ -42,32 +45,22 @@ "start": 50, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Heat pumps for heating, hot water and process heat in industry with their own heat generation.' %}", + "from-min": 50, + "tooltip": "{% trans 'Anteil von Wärmepumpen für Verbraucher mit eigener Wärmeerzeugung in der Industrie.' %}", "type": "slider" }, "w_z_wp_1": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'District heating (%)' %}", + "label": "{% trans 'Anteil Wärmepumpen Fernwärme (%)' %}", "max": 100, "min": 0, "start": 10, "status_quo": 40, "step": 5, - "tooltip": "{% trans 'District heating supply' %}", - "type": "slider", - "sidepanel": true - }, - "w_z_wp_3": { - "class": "js-slider", - "label": "{% trans 'Share (%)' %}", - "max": 200, - "min": 50, - "start": 50, - "status_quo": 100, - "step": 5, - "tooltip": "{% trans 'Heat pumps for heating, hot water and process heat in district heating networks' %}", - "type": "slider" + "from-min": 50, + "tooltip": "{% trans 'Energetischer Anteil von Wärmepumpen für Heizung, Warmwasser und Prozesswärme in Haushalten, GHD und Industrie in Fernwärmenetzen (Dessau-Roßlau, Bitterfeld-Wolfen, Köthen and Wittenberg). Die übrige Energie wird bereitgestellt durch direktelektrische Heizung, Bioenergie und Solarthermie (s. Beheizungsstruktur in den Ergebnissen). Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.\n\nHinweis: Dezentrale Wärmesysteme werden im Menü separat konfiguriert.' %}", + "type": "slider" }, "w_v_1": { "class": "js-slider js-slider-panel", @@ -78,7 +71,7 @@ "start": 10, "status_quo": 40, "step": 10, - "tooltip": "{% trans 'Heat consumption of decentralized heat generators and district heating networks' %}", + "tooltip": "{% trans 'Relativer Wärmeverbrauch (Endenergie) von Haushalten, GHD und Industrie gegenüber heute. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider", "sidepanel": true }, @@ -90,7 +83,7 @@ "start": 50, "status_quo": 100, "step": 10, - "tooltip": "{% trans 'Includes heating and hot water in households with their own heat generation and households in district heating networks.' %}", + "tooltip": "{% trans 'Umfasst Heizung, Warmwasser und Kochen in Haushalten mit eigener Wärmeerzeugung und in Fernwärmenetzen. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider" }, "w_v_4": { @@ -101,7 +94,7 @@ "start": 10, "status_quo": 100, "step": 10, - "tooltip": "{% trans 'Comprises heating and hot water in the commercial, retail and services sector with its own heat generation and in district heating networks.' %}", + "tooltip": "{% trans 'Umfasst Heizung und Warmwasser im Gewerbe-, Handels- und Dienstleistungssektor mit eigener Wärmeerzeugung und in Fernwärmenetzen. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider" }, "w_v_5": { @@ -112,55 +105,31 @@ "start": 50, "status_quo": 100, "step": 10, - "tooltip": "{% trans 'Includes thermal heat, hot water and process heat in industry with its own heat generation and in district heating networks..' %}", + "tooltip": "{% trans 'Umfasst Heizwärme, Warmwasser und Prozesswärme in der Industrie mit eigener Wärmeerzeugung und in Fernwärmenetzen. Das dargestellte Ziel für 2045 ist abgeleitet aus den BMWK Langfristszenarien.' %}", "type": "slider" }, "w_d_s_1": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'Decentralized (%)' %}", - "max": 200, - "min": 25, - "start": 25, - "status_quo": 100, - "step": 5, - "tooltip": "{% trans 'Storage in the decentralized heat supply' %}", - "type": "slider", - "sidepanel": true - }, - "w_d_s_3": { - "class": "js-slider", - "label": "{% trans 'Hot water tanks (%)' %}", + "label": "{% trans 'Dezentrale Speicher: Zwischenspeicherung des tägl. Wärmebedarfs (%)' %}", "max": 200, "min": 25, "start": 25, "status_quo": 100, "step": 5, - "tooltip": "{% trans 'Hot water tanks for heating, hot water and process heat' %}", + "tooltip": "{% trans 'Wieviel des täglichen Wärmebedarfs in dezentralen Systemen (Heizung, Warmwasser und Prozesswärme) sollen in kleinen Warmwasserspeichern zwischengespeichert werden können?' %}", "type": "slider" }, "w_z_s_1": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'Large heat storage (%)' %}", - "max": 200, - "min": 25, - "start": 25, - "status_quo": 100, - "step": 5, - "tooltip": "{% trans 'Storage in the district heating supply' %}", - "type": "slider", - "sidepanel": true - }, - "w_z_s_3": { - "class": "js-slider", - "label": "{% trans 'Hot water tanks (%)' %}", + "label": "{% trans 'Großwärmespeicher: Zwischenspeicherung des tägl. Wärmebedarfs (%)' %}", "max": 200, "min": 25, "start": 25, "status_quo": 100, "step": 5, - "tooltip": "{% trans 'Hot water tanks for heating, hot water and process heat' %}", + "tooltip": "{% trans 'Wieviel des täglichen Wärmebedarfs in Fernwärmenetzen (Heizung, Warmwasser und Prozesswärme) sollen in großen Warmwasserspeichern zwischengespeichert werden können?' %}", "type": "slider" } } diff --git a/digiplan/static/config/traffic_settings_panel.json b/digiplan/static/config/traffic_settings_panel.json index 69b4b52f..a801e4ea 100644 --- a/digiplan/static/config/traffic_settings_panel.json +++ b/digiplan/static/config/traffic_settings_panel.json @@ -2,26 +2,26 @@ "v_iv_1": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'Share of battery-electric vehicles (%)' %}", + "label": "{% trans 'Anteil privater E-Fahrzeuge (%)' %}", "max": 100, "min": 0, "start": 30, "status_quo": 50, "step": 5, - "tooltip": "{% trans 'Share of battery-electric vehicles (%)' %}", + "tooltip": "{% trans 'Anteil privater elektrischer Fahrzeuge (batterieelektrisch und Plug-in-Hybride).' %}", "type": "slider", "sidepanel": true }, "v_iv_3": { "class": "js-slider js-slider-panel", "grid": false, - "label": "{% trans 'Share of battery electric and plugin-hybrid vehicles (%)' %}", + "label": "{% trans 'Anteil privater E-Fahrzeuge (%)' %}", "max": 100, "min": 0, "start": 25, "status_quo": 45, "step": 5, - "tooltip": "{% trans 'Personal battery electric and plug-in hybrid vehicles' %}", + "tooltip": "{% trans 'Anteil privater elektrischer Fahrzeuge (batterieelektrisch und Plug-in-Hybride).' %}", "type": "slider" } } diff --git a/digiplan/static/js/charts.js b/digiplan/static/js/charts.js index 09e036eb..3c78e0b1 100644 --- a/digiplan/static/js/charts.js +++ b/digiplan/static/js/charts.js @@ -1,25 +1,33 @@ // Goals & scenarios, initioalize charts -const renewable_share_goal_div = document.getElementById("renewable_share_goal_chart"); -const renewable_share_goal_chart = echarts.init(renewable_share_goal_div); -const co2_emissions_goal_div = document.getElementById("co2_emissions_goal_chart"); -const co2_emissions_goal_chart = echarts.init(co2_emissions_goal_div); -const renewable_share_scenario_div = document.getElementById("renewable_share_scenario_chart"); -const renewable_share_scenario_chart = echarts.init(renewable_share_scenario_div); -const co2_emissions_scenario_div = document.getElementById("co2_emissions_scenario_chart"); -const co2_emissions_scenario_chart = echarts.init(co2_emissions_scenario_div); +// const renewable_share_goal_div = document.getElementById("renewable_share_goal_chart"); +// const renewable_share_goal_chart = echarts.init(renewable_share_goal_div); +// const co2_emissions_goal_div = document.getElementById("co2_emissions_goal_chart"); +// const co2_emissions_goal_chart = echarts.init(co2_emissions_goal_div); +// const renewable_share_scenario_div = document.getElementById("renewable_share_scenario_chart"); +// const renewable_share_scenario_chart = echarts.init(renewable_share_scenario_div); +// const co2_emissions_scenario_div = document.getElementById("co2_emissions_scenario_chart"); +// const co2_emissions_scenario_chart = echarts.init(co2_emissions_scenario_div); // Results view, initiliaze charts -const detailed_overview_chart = echarts.init(document.getElementById("detailed_overview_chart")); -const ghg_overview_chart = echarts.init(document.getElementById("ghg_overview_chart")); +// const detailed_overview_chart = echarts.init(document.getElementById("detailed_overview_chart")); +// const ghg_overview_chart = echarts.init(document.getElementById("ghg_overview_chart")); const electricity_overview_chart = echarts.init(document.getElementById("electricity_overview_chart")); -const electricity_THG_chart = echarts.init(document.getElementById("electricity_THG_chart")); -const mobility_overview_chart = echarts.init(document.getElementById("mobility_overview_chart")); -const mobility_THG_chart = echarts.init(document.getElementById("mobility_THG_chart")); -const overview_heat_chart = echarts.init(document.getElementById("overview_heat_chart")); -const decentralized_centralized_heat_chart = echarts.init(document.getElementById("decentralized_centralized_heat_chart")); +const electricity_autarky_chart = echarts.init(document.getElementById("electricity_autarky_chart")); +// const mobility_overview_chart = echarts.init(document.getElementById("mobility_overview_chart")); +// const mobility_THG_chart = echarts.init(document.getElementById("mobility_THG_chart")); +const heat_decentralized_chart = echarts.init(document.getElementById("heat_decentralized_chart")); +const heat_centralized_chart = echarts.init(document.getElementById("heat_centralized_chart")); const ghg_history_chart = echarts.init(document.getElementById("ghg_history_chart")); const ghg_reduction_chart = echarts.init(document.getElementById("ghg_reduction_chart")); +// Onboarding Charts +const onboarding_wind_div = document.getElementById("onboarding_wind_chart"); +const onboarding_wind_chart = echarts.init(onboarding_wind_div); +const onboarding_pv_ground_div = document.getElementById("onboarding_pv_ground_chart"); +const onboarding_pv_ground_chart = echarts.init(onboarding_pv_ground_div); +const onboarding_pv_roof_div = document.getElementById("onboarding_pv_roof_chart"); +const onboarding_pv_roof_chart = echarts.init(onboarding_pv_roof_div); + PubSub.subscribe(eventTopics.MENU_CHANGED, resizeCharts); // Styling variables @@ -69,7 +77,7 @@ const renewable_share_goal = { textStyle: chart_text_style, xAxis: { type: 'category', - data: ['2021', 'Szenario'], + data: ['2022', 'Szenario'], axisTick: { show: false, }, @@ -174,7 +182,7 @@ const renewable_share_scenario = { textStyle: chart_text_style, xAxis: { type: 'category', - data: ['2021', 'Szenario'], + data: ['2022', 'Szenario'], axisTick: { show: false, }, @@ -274,54 +282,67 @@ const co2_emissions_scenario = { }; // get options for result view charts -const detailed_overview_option = JSON.parse(document.getElementById("detailed_overview").textContent); -const ghg_overview_option = JSON.parse(document.getElementById("ghg_overview").textContent); +// const detailed_overview_option = JSON.parse(document.getElementById("detailed_overview").textContent); +// const ghg_overview_option = JSON.parse(document.getElementById("ghg_overview").textContent); const electricity_overview_option = JSON.parse(document.getElementById("electricity_overview").textContent); -const electricity_ghg_option = JSON.parse(document.getElementById("electricity_ghg").textContent); -const mobility_overview_option = JSON.parse(document.getElementById("mobility_overview").textContent); -const mobility_ghg_option = JSON.parse(document.getElementById("mobility_ghg").textContent); -const overview_heat_option = JSON.parse(document.getElementById("overview_heat").textContent); -const decentralized_centralized_heat_option = JSON.parse(document.getElementById("decentralized_centralized_heat").textContent); +const electricity_autarky_option = JSON.parse(document.getElementById("electricity_autarky").textContent); +// const mobility_overview_option = JSON.parse(document.getElementById("mobility_overview").textContent); +// const mobility_ghg_option = JSON.parse(document.getElementById("mobility_ghg").textContent); +const heat_decentralized_option = JSON.parse(document.getElementById("heat_decentralized").textContent); +const heat_centralized_option = JSON.parse(document.getElementById("heat_centralized").textContent); const ghg_history_option = JSON.parse(document.getElementById("ghg_history").textContent); const ghg_reduction_option = JSON.parse(document.getElementById("ghg_reduction").textContent); +// get options for onboarding charts +const onboarding_wind_option = JSON.parse(document.getElementById("onboarding_wind").textContent); +const onboarding_pv_ground_option = JSON.parse(document.getElementById("onboarding_pv_ground").textContent); +const onboarding_pv_roof_option = JSON.parse(document.getElementById("onboarding_pv_roof").textContent); + function resizeCharts() { setTimeout(function () { - renewable_share_goal_chart.resize(); - co2_emissions_goal_chart.resize(); - renewable_share_scenario_chart.resize(); - co2_emissions_scenario_chart.resize(); - detailed_overview_chart.resize(); - ghg_overview_chart.resize(); + // renewable_share_goal_chart.resize(); + // co2_emissions_goal_chart.resize(); + // renewable_share_scenario_chart.resize(); + // co2_emissions_scenario_chart.resize(); + // detailed_overview_chart.resize(); + // ghg_overview_chart.resize(); electricity_overview_chart.resize(); - electricity_THG_chart.resize(); - mobility_overview_chart.resize(); - mobility_THG_chart.resize(); - overview_heat_chart.resize(); - decentralized_centralized_heat_chart.resize(); + electricity_autarky_chart.resize(); + // mobility_overview_chart.resize(); + // mobility_THG_chart.resize(); + heat_decentralized_chart.resize(); + heat_centralized_chart.resize(); ghg_history_chart.resize(); ghg_reduction_chart.resize(); + onboarding_wind_chart.resize(); + onboarding_pv_ground_chart.resize(); + onboarding_pv_roof_chart.resize(); }, 200); } // Goals & scenarios, setOptions -renewable_share_goal_chart.setOption(renewable_share_goal); -co2_emissions_goal_chart.setOption(co2_emissions_goal); -renewable_share_scenario_chart.setOption(renewable_share_scenario); -co2_emissions_scenario_chart.setOption(co2_emissions_scenario); +// renewable_share_goal_chart.setOption(renewable_share_goal); +// co2_emissions_goal_chart.setOption(co2_emissions_goal); +// renewable_share_scenario_chart.setOption(renewable_share_scenario); +// co2_emissions_scenario_chart.setOption(co2_emissions_scenario); // Results, setOptions -detailed_overview_chart.setOption(detailed_overview_option); -ghg_overview_chart.setOption(ghg_overview_option); +// detailed_overview_chart.setOption(detailed_overview_option); +// ghg_overview_chart.setOption(ghg_overview_option); electricity_overview_chart.setOption(electricity_overview_option); -electricity_THG_chart.setOption(electricity_ghg_option); -mobility_overview_chart.setOption(mobility_overview_option); -mobility_THG_chart.setOption(mobility_ghg_option); -overview_heat_chart.setOption(overview_heat_option); -decentralized_centralized_heat_chart.setOption(decentralized_centralized_heat_option); +electricity_autarky_chart.setOption(electricity_autarky_option); +// mobility_overview_chart.setOption(mobility_overview_option); +// mobility_THG_chart.setOption(mobility_ghg_option); +heat_decentralized_chart.setOption(heat_decentralized_option); +heat_centralized_chart.setOption(heat_centralized_option); ghg_history_chart.setOption(ghg_history_option); ghg_reduction_chart.setOption(ghg_reduction_option); +// onboarding Charts +onboarding_wind_chart.setOption(onboarding_wind_option); +onboarding_pv_ground_chart.setOption(onboarding_pv_ground_option); +onboarding_pv_roof_chart.setOption(onboarding_pv_roof_option); + resizeCharts(); window.addEventListener("resize", resizeCharts); @@ -340,3 +361,11 @@ function createChart(div_id, options) { chart.setOption(options); chart.resize(); } + +function clearChart(div_id) { + const chartElement = document.getElementById(div_id); + if (echarts.getInstanceByDom(chartElement)) { + const chart = echarts.getInstanceByDom(chartElement); + chart.clear(); + } +} diff --git a/digiplan/static/js/event-topics.js b/digiplan/static/js/event-topics.js index db3806f1..af3a7f91 100644 --- a/digiplan/static/js/event-topics.js +++ b/digiplan/static/js/event-topics.js @@ -13,6 +13,7 @@ const eventTopics = { RESULTS_TAB_CLICKED: "RESULTS_TAB_CLICKED", RESULT_VIEW_CHANGED: "RESULT_VIEW_CHANGED", RESULT_VIEW_UPDATED: "RESULT_VIEW_UPDATED", + CHOROPLETH_DEACTIVATED: "CHOROPLETH_DEACTIVATED", DEPENDENCY_PANEL_SLIDER_CHANGE: "DEPENDENCY_PANEL_SLIDER_CHANGE", POWER_PANEL_SLIDER_CHANGE: "POWER_PANEL_SLIDER_CHANGE", diff --git a/digiplan/static/js/intro_tour.js b/digiplan/static/js/intro_tour.js index 89655a80..8ea1ca3f 100644 --- a/digiplan/static/js/intro_tour.js +++ b/digiplan/static/js/intro_tour.js @@ -14,7 +14,7 @@ const tour = new Shepherd.Tour({ // jshint ignore:line tour.addStep({ title: 'Navigation', - text: 'Schritt für Schritt zu Ihrem eigenen Szenario', + text: 'Schritt für Schritt zu Ihrem eigenen Szenario.', attachTo: { element: '.steps', on: 'bottom' @@ -29,20 +29,23 @@ tour.addStep({ }, { action() { + const statusquoDropdown = document.getElementById("situation_today"); + statusquoDropdown.value = "capacity_statusquo"; + PubSub.publish(mapEvent.CHOROPLETH_SELECTED, statusquoDropdown.value); return this.next(); }, text: 'Weiter' } ], - id: 'creating' + id: 'start' }); tour.addStep({ - title: 'Einstellungen', + title: 'Situation heute', text: 'Schauen Sie sich die Situation heute an.', attachTo: { - element: '#panel_1_today', + element: '#situation_today', on: 'right' }, buttons: [ @@ -61,16 +64,352 @@ tour.addStep({ text: 'Weiter' } ], - id: 'creating' + id: 'situation_today' }); + tour.addStep({ - title: 'Einstellungen', - text: 'Lassen Sie sich die unterschiedlichen Anlagentypen auf der Karte anzeigen.', + title: 'Situation heute', + text: 'Zu jeder Kategorie gibt es ein Diagramm für die Region.', + attachTo: { + element: '#region_chart_statusquo', + on: 'right' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + // Hide status quo choropleth again + const statusquoDropdown = document.getElementById("situation_today"); + statusquoDropdown.value = ""; + deactivateChoropleth(); + PubSub.publish(eventTopics.CHOROPLETH_DEACTIVATED); + + // Activate layers + document.querySelector(".static-layer #wind").click(); + document.querySelector(".static-layer #road_default").click(); + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'region_chart' +}); + + +tour.addStep({ + title: 'Karte', + text: 'Lassen Sie sich die heutigen Anlagen und Flächen auf der Karte anzeigen.', attachTo: { element: '#js-map-layers-box', on: 'left' }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'layer_switch' +}); + + +tour.addStep({ + title: 'Karte', + text: 'Klicken Sie auf ein einzelnes Icon, um mehr über diese Anlage zu erfahren.', + attachTo: { + element: '.maplibregl-canvas', + on: 'top' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + document.querySelector(".static-layer #wind").click(); + document.querySelector(".static-layer #road_default").click(); + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'cluster_popup' +}); + + +tour.addStep({ + title: 'Nächster Schritt', + text: 'Hier gehts weiter zu den Einstellungen.', + attachTo: { + element: '#menu_next_btn', + on: 'bottom' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + document.getElementById("menu_next_btn").click(); + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'menu_next_btn' +}); + +tour.addStep({ + title: 'Einstellungen', + text: 'Verändern Sie die Einstellungen, um Ihr eigenes Szenario zu erstellen.', + attachTo: { + element: '#panel_2_settings', + on: 'right' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'panel_1_today' +}); + +tour.addStep({ + title: 'Einstellungen', + text: 'Hier können Sie mehr ins Detail gehen.', + attachTo: { + element: '.c-slider__label--more', + on: 'right' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'more_slider' +}); + + +tour.addStep({ + title: 'Einstellungen', + text: 'Schauen Sie, wie sich die Verteilung verändert.', + attachTo: { + element: '.power-mix__chart', + on: 'right' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'power_mix_chart' +}); + + +tour.addStep({ + title: 'Einstellungen', + text: 'Wechseln Sie zu den Einstellungen für Wärme.', + attachTo: { + element: '#settings_area_tab', + on: 'right' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'settings_area_tab' +}); + + +tour.addStep({ + title: 'Nächster Schritt', + text: 'Hier gehts weiter zu den Ergebnissen. Im Hintergrund wird dabei automatisch die Simulation Ihres Szenarios gestartet (gelber Kreis rotiert).', + attachTo: { + element: '#menu_next_btn', + on: 'bottom' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + document.getElementById("menu_next_btn").click(); + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'menu_next_btn2' +}); + +tour.addStep({ + title: 'Ergebnisse', + text: 'Sobald die Simulation abgeschlossen ist, können Sie sich die Ergebnisse im Diagramm links und auf der Karte anschauen. Wählen Sie dazu eine Kategorie aus.', + attachTo: { + element: '#panel_3_results', + on: 'right' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'results_on_map' +}); + + +tour.addStep({ + title: 'Ergebnisse', + text: 'Wählen Sie auf der Karte eine Region aus und schauen Sie sich die detaillierten Informationen in einem Diagramm an.', + attachTo: { + element: '.maplibregl-canvas', + on: 'top' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'popups' +}); + + +tour.addStep({ + title: 'Einstellungen', + text: 'Wechseln Sie zwischen der Karten- und der Diagramm-Ansicht, sobald die Simulation abgeschlossen ist.', + attachTo: { + element: '#myTab', + on: 'bottom' + }, + buttons: [ + { + action() { + return this.back(); + }, + classes: 'shepherd-button-secondary', + text: 'Zurück' + }, + { + action() { + document.getElementById("chart-view-tab").click(); + return this.next(); + }, + classes: 'shepherd-button-primary', + text: 'Weiter' + } + ], + id: 'chart_view_tab' +}); + + +tour.addStep({ + title: 'Fertig', + text: 'Viel Spaß mit dem Digiplan-Anhalt-Tool! :D', + attachTo: { + element: '#chart_view_tab', + on: 'right' + }, buttons: [ { action() { @@ -87,9 +426,10 @@ tour.addStep({ text: 'Fertig' } ], - id: 'creating' + id: 'end' }); + onbaordingCloseBtn.addEventListener("click", function() { tour.start(); }); diff --git a/digiplan/static/js/menu.js b/digiplan/static/js/menu.js index 96846909..e96635fe 100644 --- a/digiplan/static/js/menu.js +++ b/digiplan/static/js/menu.js @@ -1,9 +1,10 @@ -import {statusquoDropdown, resultsTabs} from "./elements.js"; +import {resultsTabs, futureDropdown} from "./elements.js"; const menuNextBtn = document.getElementById("menu_next_btn"); const menuPreviousBtn = document.getElementById("menu_previous_btn"); const mapTab = document.getElementById("map-view-tab"); const chartTab = document.getElementById("chart-view-tab"); +const regionChart = document.getElementById("region_chart_2045"); menuNextBtn.addEventListener("click", function () { nextMenuTab(); @@ -92,10 +93,12 @@ function setMapChartViewVisibility(msg) { function setResultsView(msg) { if (msg === eventTopics.CHART_VIEW_SELECTED) { - statusquoDropdown.parentElement.setAttribute("style", "display: none !important"); + futureDropdown.parentElement.setAttribute("style", "display: none !important"); + regionChart.setAttribute("style", "display: none"); resultsTabs.parentElement.setAttribute("style", ""); } else { - statusquoDropdown.parentElement.setAttribute("style", ""); + futureDropdown.parentElement.setAttribute("style", ""); + regionChart.setAttribute("style", ""); resultsTabs.parentElement.setAttribute("style", "display: none !important"); } return logMessage(msg); diff --git a/digiplan/static/js/offcanvas.js b/digiplan/static/js/offcanvas.js index 66ad2f9d..b2f5f5b5 100644 --- a/digiplan/static/js/offcanvas.js +++ b/digiplan/static/js/offcanvas.js @@ -1,13 +1,13 @@ const mapView = document.getElementById("mapView"); const newOffcanvasDocumentation = new bootstrap.Offcanvas(document.getElementById('offcanvasDocumentation')); -const newOffcanvasSources = new bootstrap.Offcanvas(document.getElementById('offcanvasSources')); +// const newOffcanvasSources = new bootstrap.Offcanvas(document.getElementById('offcanvasSources')); const newOffcanvasContact = new bootstrap.Offcanvas(document.getElementById('offcanvasContact')); const offcanvasDocumentation = document.getElementById('offcanvasDocumentation'); -const offcanvassSources = document.getElementById('offcanvasSources'); +// const offcanvassSources = document.getElementById('offcanvasSources'); const offcanvasContact = document.getElementById('offcanvasContact'); mapView.onclick = function() { newOffcanvasDocumentation.hide(offcanvasDocumentation); - newOffcanvasSources.hide(offcanvassSources); + // newOffcanvasSources.hide(offcanvassSources); newOffcanvasContact.hide(offcanvasContact); }; diff --git a/digiplan/static/js/results.js b/digiplan/static/js/results.js index 01368948..b916e423 100644 --- a/digiplan/static/js/results.js +++ b/digiplan/static/js/results.js @@ -3,12 +3,16 @@ 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 SIMULATION_CHECK_TIME = 5000; const resultCharts = { - "detailed_overview": "detailed_overview_chart", - "electricity_overview": "electricity_overview_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 @@ -17,11 +21,21 @@ const resultCharts = { $('#settings').submit(false); statusquoDropdown.addEventListener("change", function() { - PubSub.publish(mapEvent.CHOROPLETH_SELECTED, statusquoDropdown.value); + 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() { - PubSub.publish(mapEvent.CHOROPLETH_SELECTED, futureDropdown.value); + if (futureDropdown.value === "") { + deactivateChoropleth(); + PubSub.publish(eventTopics.CHOROPLETH_DEACTIVATED); + } else { + PubSub.publish(mapEvent.CHOROPLETH_SELECTED, futureDropdown.value); + } imageResults.title = futureDropdown.options[futureDropdown.selectedIndex].title; }); @@ -30,12 +44,13 @@ futureDropdown.addEventListener("change", function() { PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, simulate); PubSub.subscribe(eventTopics.MENU_RESULTS_SELECTED, showSimulationSpinner); PubSub.subscribe(eventTopics.SIMULATION_STARTED, checkResultsPeriodically); +// PubSub.subscribe(eventTopics.SIMULATION_STARTED, hideResultButtons); +// PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResultButtons); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResults); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, hideSimulationSpinner); PubSub.subscribe(eventTopics.SIMULATION_FINISHED, showResultCharts); PubSub.subscribe(mapEvent.CHOROPLETH_SELECTED, showRegionChart); -// for testing: -PubSub.subscribe(eventTopics.CHART_VIEW_SELECTED, showResultCharts); +PubSub.subscribe(eventTopics.CHOROPLETH_DEACTIVATED, hideRegionChart); // Subscriber Functions @@ -111,6 +126,18 @@ function hideSimulationSpinner(msg) { return logMessage(msg); } +// function showResultButtons(msg) { +// chartViewTab.classList.remove("disabled"); +// futureDropdown.disabled = false; +// return logMessage(msg); +// } +// +// function hideResultButtons(msg) { +// chartViewTab.classList.add("disabled"); +// futureDropdown.disabled = true; +// return logMessage(msg); +// } + function showRegionChart(msg, lookup) { const region_lookup = `${lookup}_region`; let charts = {}; @@ -123,6 +150,12 @@ function showRegionChart(msg, lookup) { return logMessage(msg); } +function hideRegionChart(msg) { + clearChart("region_chart_statusquo"); + clearChart("region_chart_2045"); + return logMessage(msg); +} + function showResultCharts(msg) { showCharts(resultCharts); return logMessage(msg); diff --git a/digiplan/static/js/sliders.js b/digiplan/static/js/sliders.js index 49e36fbc..18bfb4b3 100644 --- a/digiplan/static/js/sliders.js +++ b/digiplan/static/js/sliders.js @@ -8,6 +8,12 @@ const powerPanelSliders = document.querySelectorAll(".js-slider.js-slider-panel. const sliderMoreLabels = document.querySelectorAll(".c-slider__label--more > .button"); const powerMixInfoBanner = document.getElementById("js-power-mix"); +const powerIcons = { + "s_h_1": ``, + "s_pv_ff_1": ``, + "s_pv_d_1": ``, + "s_w_1": `` +}; const potentialPVLayers = ["potentialarea_pv_agriculture_lfa-off_region", "potentialarea_pv_road_railway_region"]; const potentialWindLayers = [ @@ -22,10 +28,6 @@ const potentialWindSwitches = document.querySelectorAll("#id_s_w_3, #id_s_w_4, # const sectorSlider = document.querySelectorAll("#id_s_v_3, #id_s_v_4, #id_s_v_5, #id_w_d_wp_3, #id_w_d_wp_4, #id_w_d_wp_5, #id_w_v_3, #id_w_v_4, #id_w_v_5"); const sliderDependencies = { - "id_s_s_g_1": "id_s_s_g_3", - "id_w_z_wp_1": "id_w_z_wp_3", - "id_w_d_s_1": "id_w_d_s_3", - "id_w_z_s_1": "id_w_z_s_3", "id_v_iv_1": "id_v_iv_3" }; @@ -112,36 +114,12 @@ $("#id_s_pv_d_3").ionRangeSlider({ } } ); -$("#id_w_z_wp_3").ionRangeSlider({ - onChange: function (data) { - $(`#id_w_z_wp_1`).data("ionRangeSlider").update({from:data.from}); - } - } -); -$("#id_w_d_s_3").ionRangeSlider({ - onChange: function (data) { - $(`#id_w_d_s_1`).data("ionRangeSlider").update({from:data.from}); - } - } -); -$("#id_w_z_s_3").ionRangeSlider({ - onChange: function (data) { - $(`#id_w_z_s_1`).data("ionRangeSlider").update({from:data.from}); - } - } -); $("#id_v_iv_3").ionRangeSlider({ onChange: function (data) { $(`#id_v_iv_1`).data("ionRangeSlider").update({from:data.from}); } } ); -$("#id_s_s_g_3").ionRangeSlider({ - onChange: function (data) { - $(`#id_s_s_g_1`).data("ionRangeSlider").update({from:data.from}); - } - } -); $(".js-slider").ionRangeSlider(); @@ -257,7 +235,8 @@ function createPercentagesOfPowerSources(msg) { const total = getTotalOfValues(values); const weights = getWeightsInPercent(values, total); const colors = getColorsByIds(ids); - updatePowerMix(weights, colors); + const icons = getIconsByIds(ids); + updatePowerMix(weights, colors, icons); return logMessage(msg); } @@ -445,12 +424,21 @@ function getColorsByIds(ids) { return colors; } -function updatePowerMix(weights, colors) { +function getIconsByIds(ids) { + let icons = []; + for (let id of ids) { + const cleanedId = id.replace(/^id_/, ""); + icons.push(powerIcons[cleanedId]); + } + return icons; +} + +function updatePowerMix(weights, colors, icons) { const msg = "Unequal amount of weights and colors"; if (weights.length !== colors.length) throw new Error(msg); let html = `
`; for (const index of weights.keys()) { - html += `
`; + html += `
${icons[index]}
`; } html += `
`; for (const index of weights.keys()) { @@ -484,7 +472,9 @@ function addMarks(data, marks) { let html = ""; for (let i = 0; i < marks.length; i++) { - const percent = convertToPercent(marks[i][1], data.min, data.max); + let percent = convertToPercent(marks[i][1], data.min, data.max); + // Fix percentage due to offset + percent = percent - 2.5 - (3.5 * percent / 100); html += ``; html += marks[i][0]; html += ''; diff --git a/digiplan/static/scss/components/_charts.scss b/digiplan/static/scss/components/_charts.scss index 84ce074c..7b424313 100644 --- a/digiplan/static/scss/components/_charts.scss +++ b/digiplan/static/scss/components/_charts.scss @@ -1,5 +1,5 @@ #region_chart_statusquo, -#region_chart_future { +#region_chart_2045 { width: 100%; height: 300px; } diff --git a/digiplan/static/scss/components/_modals.scss b/digiplan/static/scss/components/_modals.scss index 8c0b2d9f..54689ae0 100644 --- a/digiplan/static/scss/components/_modals.scss +++ b/digiplan/static/scss/components/_modals.scss @@ -27,7 +27,28 @@ } .onboarding-modal { + .modal-body { + @extend .p-0; + height: 75vh; + overflow: hidden; + } + + .carousel { + @extend .h-100; + } + + .carousel-inner { + @extend .h-100; + } + .carousel-item { + @extend .h-100; + @extend .p-2; + + .row { + @extend .h-100; + } + &__content { @extend .col-8; @extend .bg-white; @@ -36,6 +57,93 @@ } } + .carousel-item.carousel-item--first { + @extend .p-0; + + .carousel-item__content { + @extend .h-100; + @extend .p-0; + @extend .d-flex; + @extend .flex-column; + @extend .w-100; + } + + .carousel__text { + @extend .row; + @extend .p-2; + @extend .bg-light; + + &-logo { + @extend .d-flex; + @extend .flex-row; + @extend .justify-content-center; + @extend .align-items-center; + + img { + height: 5rem; + } + } + + &-content { + max-width: 50rem; + margin: 0 auto; + + .title { + @extend .text-center; + @extend .fw-semibold; + @extend .fs-4; + } + + .content { + @extend .text-center; + @extend .fs-4; + } + } + } + + .carousel__logos { + @extend .row; + @extend .px-2; + max-height: 20rem; + + @include media-breakpoint-up(xl) { + max-height: 11rem; + } + } + + .carousel__logo { + @extend .col-4; + @extend .col-xl-2; + @extend .d-flex; + @extend .align-items-center; + @extend .justify-content-center; + + &--rli img { + height: 4rem; + } + + &--eaa img { + height: 5rem; + } + + &--gestalten img { + height: 5rem; + } + + &--bbsr img { + height: 9rem; + } + + &--bmwsb img { + height: 9rem; + } + + &--bmi img { + height: 9rem; + } + } + } + img { height: 100%; object-fit: cover; @@ -62,9 +170,4 @@ .btn, .modal-footer button { opacity: 1 !important; } - - .modal-body { - height: 40rem; - overflow: hidden; - } } diff --git a/digiplan/static/scss/components/_sliders.scss b/digiplan/static/scss/components/_sliders.scss index 65a3d14a..c3f7d266 100644 --- a/digiplan/static/scss/components/_sliders.scss +++ b/digiplan/static/scss/components/_sliders.scss @@ -4,7 +4,6 @@ @extend .text-primary; @extend .fw-bold; top: 2.5rem; - margin-left: -10px; &:before { content: ''; diff --git a/digiplan/static/scss/layouts/_panel.scss b/digiplan/static/scss/layouts/_panel.scss index a551c62f..064c0c58 100644 --- a/digiplan/static/scss/layouts/_panel.scss +++ b/digiplan/static/scss/layouts/_panel.scss @@ -383,8 +383,8 @@ } &__results { - @extend .pb-1; @extend .pt-3; + @extend .text-secondary; @extend .fs-7; } diff --git a/digiplan/templates/components/onboarding.html b/digiplan/templates/components/onboarding.html index 63f9c4c4..bf743856 100644 --- a/digiplan/templates/components/onboarding.html +++ b/digiplan/templates/components/onboarding.html @@ -11,48 +11,86 @@
diff --git a/digiplan/templates/components/panel_1_today.html b/digiplan/templates/components/panel_1_today.html index 5203d216..5d66d2f1 100644 --- a/digiplan/templates/components/panel_1_today.html +++ b/digiplan/templates/components/panel_1_today.html @@ -1,28 +1,28 @@ {% load static i18n %} -
- - + + + + + + + + + + + + + + + + + + + + + -
-
-
- {% translate "Share of renewable energies" %} (%) -
-
-
-
-
-
-
- {% translate "CO2-Reduktion ggü. 2019 (%)" %} -
-
-
-
-
-
-
+

@@ -35,14 +35,15 @@

diff --git a/digiplan/templates/components/panel_2_settings.html b/digiplan/templates/components/panel_2_settings.html index d1060c7c..acb2573e 100644 --- a/digiplan/templates/components/panel_2_settings.html +++ b/digiplan/templates/components/panel_2_settings.html @@ -26,14 +26,14 @@

{% translate "Heat" %} - + + + + + + + +
{% csrf_token %} diff --git a/digiplan/templates/components/panel_3_results.html b/digiplan/templates/components/panel_3_results.html index ef52abca..cf103653 100644 --- a/digiplan/templates/components/panel_3_results.html +++ b/digiplan/templates/components/panel_3_results.html @@ -1,25 +1,25 @@ {% load static i18n %} -
-
-
-
- {% translate "Share of renewable energies" %} (%) -
-
-
-
-
-
-
- {% translate "CO2-Reduktion ggü. 2019 (%)" %} -
-
-
-
-
-
-
+ + + + + + + + + + + + + + + + + + + +

@@ -30,13 +30,7 @@