diff --git a/config/settings/base.py b/config/settings/base.py index 085cbc00..25bba274 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -363,6 +363,9 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 setup.ClusterAPI("combustion", "map", "Combustion", properties=["id", "unit_count"]), setup.ClusterAPI("gsgk", "map", "GSGK", properties=["id", "unit_count"]), setup.ClusterAPI("storage", "map", "Storage", properties=["id", "unit_count"]), + setup.ClusterAPI("rpg_ols_wind_approved", "map", "WindTurbine2Approved", properties=["id", "unit_count"]), + setup.ClusterAPI("rpg_ols_wind_operating", "map", "WindTurbine2Operating", properties=["id", "unit_count"]), + setup.ClusterAPI("rpg_ols_wind_planned", "map", "WindTurbine2Planned", properties=["id", "unit_count"]), ] MAP_ENGINE_LAYERS_AT_STARTUP = ["region_boundaries"] @@ -521,6 +524,18 @@ def __getitem__(self, item): # noqa: D105, ANN001, ANN204 ] MAP_ENGINE_POPUPS = [ + setup.Popup( + "rpg_ols_wind_approved", + popup_at_default_layer=True, + ), + setup.Popup( + "rpg_ols_wind_operating", + popup_at_default_layer=True, + ), + setup.Popup( + "rpg_ols_wind_planned", + popup_at_default_layer=True, + ), setup.Popup( "wind", popup_at_default_layer=True, diff --git a/digiplan/map/map_config.py b/digiplan/map/map_config.py index 6c2a8b5a..2164e5a6 100644 --- a/digiplan/map/map_config.py +++ b/digiplan/map/map_config.py @@ -17,7 +17,28 @@ class SymbolLegendLayer(legend.LegendLayer): LEGEND = { _("Renewables"): [ SymbolLegendLayer( - _("Windenergie"), + _("Windenergie (in Betrieb)"), + _("Windenergieanlagen in Betrieb, Punktdaten (Daten: RPG Oderland-Spree, Stand: 31.12.2023)"), + layer_id="rpg_ols_wind_operating", + color="#7a9ce7", + symbol="circle", + ), + SymbolLegendLayer( + _("Windenergie genehmigt)"), + _("Genehmigte Windenergieanlagen, Punktdaten (Daten: RPG Oderland-Spree, Stand: 31.12.2023)"), + layer_id="rpg_ols_wind_approved", + color="#6A89CC", + symbol="circle", + ), + SymbolLegendLayer( + _("Windenergie (geplant)"), + _("Geplante Windenergieanlagen, Punktdaten (Daten: RPG Oderland-Spree, Stand: 31.12.2023)"), + layer_id="rpg_ols_wind_planned", + color="#526ba2", + symbol="circle", + ), + SymbolLegendLayer( + _("Windenergie (MaStR)"), _( "Windenergieanlagen - Punktdaten realisierter oder in Betrieb befindlicher Anlagen (Daten: " "Marktstammdatenregister, Stand: 08.01.2024). Achtung: Aufgrund der Verzögerung bei der Datenmeldung " diff --git a/digiplan/map/migrations/0052_windturbine2planned_windturbine2operating_and_more.py b/digiplan/map/migrations/0052_windturbine2planned_windturbine2operating_and_more.py new file mode 100644 index 00000000..4124f3e1 --- /dev/null +++ b/digiplan/map/migrations/0052_windturbine2planned_windturbine2operating_and_more.py @@ -0,0 +1,87 @@ +# Generated by Django 4.2.11 on 2024-05-01 14:48 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("map", "0051_merge_20240501_1323"), + ] + + operations = [ + migrations.CreateModel( + name="WindTurbine2Planned", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255, null=True)), + ("operator", models.CharField(max_length=255, null=True)), + ("city", models.CharField(max_length=50, null=True)), + ("zip_code", models.CharField(max_length=50, null=True)), + ("commissioning_date", models.CharField(max_length=50, null=True)), + ("capacity_net", models.FloatField(null=True)), + ("hub_height", models.FloatField(null=True)), + ("rotor_diameter", models.FloatField(null=True)), + ("status", models.CharField(max_length=50, null=True)), + ("geom", django.contrib.gis.db.models.fields.PointField(srid=4326)), + ( + "mun_id", + models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="map.municipality"), + ), + ], + options={ + "verbose_name": "Windenergieanlage (geplant)", + "verbose_name_plural": "Windenergieanlagen (geplant)", + }, + ), + migrations.CreateModel( + name="WindTurbine2Operating", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255, null=True)), + ("operator", models.CharField(max_length=255, null=True)), + ("city", models.CharField(max_length=50, null=True)), + ("zip_code", models.CharField(max_length=50, null=True)), + ("commissioning_date", models.CharField(max_length=50, null=True)), + ("capacity_net", models.FloatField(null=True)), + ("hub_height", models.FloatField(null=True)), + ("rotor_diameter", models.FloatField(null=True)), + ("status", models.CharField(max_length=50, null=True)), + ("geom", django.contrib.gis.db.models.fields.PointField(srid=4326)), + ( + "mun_id", + models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="map.municipality"), + ), + ], + options={ + "verbose_name": "Windenergieanlage (in Betrieb)", + "verbose_name_plural": "Windenergieanlagen (in Betrieb)", + }, + ), + migrations.CreateModel( + name="WindTurbine2Approved", + fields=[ + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("name", models.CharField(max_length=255, null=True)), + ("operator", models.CharField(max_length=255, null=True)), + ("city", models.CharField(max_length=50, null=True)), + ("zip_code", models.CharField(max_length=50, null=True)), + ("commissioning_date", models.CharField(max_length=50, null=True)), + ("capacity_net", models.FloatField(null=True)), + ("hub_height", models.FloatField(null=True)), + ("rotor_diameter", models.FloatField(null=True)), + ("status", models.CharField(max_length=50, null=True)), + ("geom", django.contrib.gis.db.models.fields.PointField(srid=4326)), + ( + "mun_id", + models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to="map.municipality"), + ), + ], + options={ + "verbose_name": "Windenergieanlage (genehmigt)", + "verbose_name_plural": "Windenergieanlagen (genehmigt)", + }, + ), + ] diff --git a/digiplan/map/models.py b/digiplan/map/models.py index f785794a..74b04aa5 100644 --- a/digiplan/map/models.py +++ b/digiplan/map/models.py @@ -772,3 +772,72 @@ class PVgroundAreasPlanned(PVgroundAreas): class Meta: # noqa: D106 verbose_name = _("Freiflächen-PV (geplant)") verbose_name_plural = _("Freiflächen-PV (geplant)") + + +class WindTurbine2(models.Model): + """Model holding wind turbines.""" + + name = models.CharField(max_length=255, null=True) + operator = models.CharField(max_length=255, null=True) + city = models.CharField(max_length=50, null=True) + zip_code = models.CharField(max_length=50, null=True) + commissioning_date = models.CharField(max_length=50, null=True) + capacity_net = models.FloatField(null=True) + hub_height = models.FloatField(null=True) + rotor_diameter = models.FloatField(null=True) + status = models.CharField(max_length=50, null=True) + geom = models.PointField(srid=4326) + + mun_id = models.ForeignKey(Municipality, on_delete=models.DO_NOTHING, null=True) + + objects = models.Manager() + vector_tiles = StaticMVTManager(columns=[]) + + mapping = { + "geom": "POINT", + "name": "name", + "operator": "operator", + "city": "city", + "zip_code": "zip_code", + "commissioning_date": "commissioning_date", + "capacity_net": "capacity_net", + "hub_height": "hub_height", + "rotor_diameter": "rotor_diameter", + "mun_id": {"id": "municipality_id"}, + } + + class Meta: # noqa: D106 + abstract = True + + +class WindTurbine2Approved(WindTurbine2): + """Model holding PV on ground (dataset by RPG with areas): Approved units.""" + + data_file = "rpg_ols_wind_approved" + layer = "rpg_ols_wind_approved" + + class Meta: # noqa: D106 + verbose_name = _("Windenergieanlage (genehmigt)") + verbose_name_plural = _("Windenergieanlagen (genehmigt)") + + +class WindTurbine2Operating(WindTurbine2): + """Model holding PV on ground (dataset by RPG with areas): Operating units.""" + + data_file = "rpg_ols_wind_operating" + layer = "rpg_ols_wind_operating" + + class Meta: # noqa: D106 + verbose_name = _("Windenergieanlage (in Betrieb)") + verbose_name_plural = _("Windenergieanlagen (in Betrieb)") + + +class WindTurbine2Planned(WindTurbine2): + """Model holding PV on ground (dataset by RPG with areas): Planned units.""" + + data_file = "rpg_ols_wind_planned" + layer = "rpg_ols_wind_planned" + + class Meta: # noqa: D106 + verbose_name = _("Windenergieanlage (geplant)") + verbose_name_plural = _("Windenergieanlagen (geplant)") diff --git a/digiplan/map/popups.py b/digiplan/map/popups.py index 72c6cff6..a1cc2fcb 100644 --- a/digiplan/map/popups.py +++ b/digiplan/map/popups.py @@ -211,7 +211,7 @@ def get_context_data(self) -> dict: return data_dict -class PVgroundAreaPopup(popups.Popup): +class WindTurbine2ClusterPopup(popups.Popup): """Popup for any entity showing table with attributes.""" description: str = None @@ -219,6 +219,45 @@ class PVgroundAreaPopup(popups.Popup): def __init__(self, lookup: str, selected_id: int, **kwargs) -> None: # noqa: ARG002 """Initialize popup with default cluster template.""" self.model_lookup = lookup + super().__init__(lookup="cluster", selected_id=selected_id) + + def get_context_data(self) -> dict: + """Return cluster data as context data.""" + model = { + "rpg_ols_wind_operating": models.WindTurbine2Operating, + "rpg_ols_wind_approved": models.WindTurbine2Approved, + "rpg_ols_wind_planned": models.WindTurbine2Planned, + }[self.model_lookup] + default_attributes = { + "name": "Name", + "capacity_net": "Nettonennleistung (MW)", + "commissioning_date": "Inbetriebnahmedatum", + "hub_height": "Nabenhöhe", + "rotor_diameter": "Rotordurchmesser", + } + specific_attributes = {} + instance = model.objects.annotate(mun_name=F("mun_id__name")).get(pk=self.selected_id) + data_dict = { + "title": model._meta.verbose_name, # noqa: SLF001 + "description": self.description, + "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 PVgroundAreaPopup(popups.Popup): + """Popup for any entity showing table with attributes.""" + + description: str = None + + def __init__(self, lookup: str, selected_id: int, **kwargs) -> None: # noqa: ARG002 + """Initialize popup with table template.""" + self.model_lookup = lookup super().__init__(lookup="base", selected_id=selected_id) def get_context_data(self) -> dict: @@ -954,24 +993,42 @@ def get_chart_options(self) -> dict: return chart_options -class PVgroundAreasOperating(PVgroundAreaPopup): +class PVgroundAreasOperatingPopup(PVgroundAreaPopup): """Popup for operating PV ground units (dataset by RPG with areas).""" description = _("Photovoltaik-Freiflächenanlagen in Betrieb (Daten: RPG Oderland-Spree, Stand: 31.12.2023)") -class PVgroundAreasApproved(PVgroundAreaPopup): +class PVgroundAreasApprovedPopup(PVgroundAreaPopup): """Popup for approved PV ground units (dataset by RPG with areas).""" description = _("Genehmigte Photovoltaik-Freiflächenanlagen (Daten: RPG Oderland-Spree, Stand: 31.12.2023)") -class PVgroundAreasPlanned(PVgroundAreaPopup): +class PVgroundAreasPlannedPopup(PVgroundAreaPopup): """Popup for planned PV ground units (dataset by RPG with areas).""" description = _("Geplante Photovoltaik-Freiflächenanlagen (Daten: RPG Oderland-Spree, Stand: 31.12.2023)") +class WindTurbine2OperatingPopup(WindTurbine2ClusterPopup): + """Popup for operating wind turbines (dataset by RPG with areas).""" + + description = _("Windenergieanlagen in Betrieb (Daten: RPG Oderland-Spree, Stand: 31.12.2023)") + + +class WindTurbine2ApprovedPopup(WindTurbine2ClusterPopup): + """Popup for approved wind turbines (dataset by RPG with areas).""" + + description = _("Genehmigte Windenergieanlagen (Daten: RPG Oderland-Spree, Stand: 31.12.2023)") + + +class WindTurbine2PlannedPopup(WindTurbine2ClusterPopup): + """Popup for planned wind turbines (dataset by RPG with areas).""" + + description = _("Geplante Windenergieanlagen (Daten: RPG Oderland-Spree, Stand: 31.12.2023)") + + POPUPS: dict[str, type(popups.Popup)] = { "wind": ClusterPopup, "pvroof": ClusterPopup, @@ -1011,7 +1068,10 @@ class PVgroundAreasPlanned(PVgroundAreaPopup): "heat_demand_capita_2045": HeatDemandCapita2045Popup, "batteries_statusquo": BatteriesPopup, "batteries_capacity_statusquo": BatteriesCapacityPopup, - "rpg_ols_pv_ground_operating": PVgroundAreasOperating, - "rpg_ols_pv_ground_approved": PVgroundAreasApproved, - "rpg_ols_pv_ground_planned": PVgroundAreasPlanned, + "rpg_ols_pv_ground_operating": PVgroundAreasOperatingPopup, + "rpg_ols_pv_ground_approved": PVgroundAreasApprovedPopup, + "rpg_ols_pv_ground_planned": PVgroundAreasPlannedPopup, + "rpg_ols_wind_approved": WindTurbine2ApprovedPopup, + "rpg_ols_wind_operating": WindTurbine2OperatingPopup, + "rpg_ols_wind_planned": WindTurbine2PlannedPopup, } diff --git a/digiplan/static/config/layer_styles.json b/digiplan/static/config/layer_styles.json index 39d4f8bf..224e4856 100644 --- a/digiplan/static/config/layer_styles.json +++ b/digiplan/static/config/layer_styles.json @@ -132,6 +132,117 @@ "text-size": 12 } }, + "rpg_ols_wind_approved": { + "type": "symbol", + "filter": ["!", ["has", "point_count"]], + "layout": { + "icon-image": "wind", + "icon-size": 0.4, + "icon-allow-overlap": true + }, + "paint": { + "icon-color": "blue" + } + }, + "rpg_ols_wind_approved_cluster": { + "type": "circle", + "filter": ["has", "point_count"], + "paint": { + "circle-color": "#6A89CC", + "circle-radius": [ + "step", + ["get", "point_count"], + 20, + 15, + 30, + 50, + 40 + ] + } + }, + "rpg_ols_wind_approved_cluster_count": { + "type": "symbol", + "filter": ["has", "point_count"], + "layout": { + "text-field": "{point_count_abbreviated}", + "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"], + "text-size": 12 + } + }, + "rpg_ols_wind_operating": { + "type": "symbol", + "filter": ["!", ["has", "point_count"]], + "layout": { + "icon-image": "wind", + "icon-size": 0.4, + "icon-allow-overlap": true + }, + "paint": { + "icon-color": "blue" + } + }, + "rpg_ols_wind_operating_cluster": { + "type": "circle", + "filter": ["has", "point_count"], + "paint": { + "circle-color": "#A9BDE8", + "circle-radius": [ + "step", + ["get", "point_count"], + 20, + 15, + 30, + 50, + 40 + ] + } + }, + "rpg_ols_wind_operating_cluster_count": { + "type": "symbol", + "filter": ["has", "point_count"], + "layout": { + "text-field": "{point_count_abbreviated}", + "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"], + "text-size": 12 + } + }, + "rpg_ols_wind_planned": { + "type": "symbol", + "filter": ["!", ["has", "point_count"]], + "layout": { + "icon-image": "wind", + "icon-size": 0.4, + "icon-allow-overlap": true + }, + "paint": { + "icon-color": "blue" + } + }, + "rpg_ols_wind_planned_cluster": { + "type": "circle", + "filter": ["has", "point_count"], + "paint": { + "circle-color": "#526ba2", + "circle-radius": [ + "step", + ["get", "point_count"], + 20, + 15, + 30, + 50, + 40 + ] + } + }, + "rpg_ols_wind_planned_cluster_count": { + "type": "symbol", + "filter": ["has", "point_count"], + "layout": { + "text-field": "{point_count_abbreviated}", + "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"], + "text-size": 12 + } + }, "pvroof": { "type": "symbol", "filter": ["!", ["has", "point_count"]], diff --git a/digiplan/utils/data_processing.py b/digiplan/utils/data_processing.py index 0acd2baf..7e44562c 100644 --- a/digiplan/utils/data_processing.py +++ b/digiplan/utils/data_processing.py @@ -57,6 +57,9 @@ models.PVgroundAreasApproved, models.PVgroundAreasOperating, models.PVgroundAreasPlanned, + models.WindTurbine2Approved, + models.WindTurbine2Operating, + models.WindTurbine2Planned, # PotentialAreas models.PotentialareaPVGroundSoilQualityLow, models.PotentialareaPVGroundSoilQualityMedium,