diff --git a/config/config.default.yaml b/config/config.default.yaml index 1598ca1c..33078468 100644 --- a/config/config.default.yaml +++ b/config/config.default.yaml @@ -165,6 +165,10 @@ renewable: grid_codes: [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32] distance: 1000 distance_grid_codes: [1, 2, 3, 4, 5, 6] + luisa: false + # grid_codes: [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242] + # distance: 1000 + # distance_grid_codes: [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242] natura: true excluder_resolution: 100 clip_p_max_pu: 1.e-2 @@ -177,6 +181,7 @@ renewable: capacity_per_sqkm: 2 correction_factor: 0.8855 corine: [44, 255] + luisa: false # [0, 5230] natura: true ship_threshold: 400 max_depth: 50 @@ -192,6 +197,7 @@ renewable: capacity_per_sqkm: 2 correction_factor: 0.8855 corine: [44, 255] + luisa: false # [0, 5230] natura: true ship_threshold: 400 max_depth: 50 @@ -209,6 +215,7 @@ renewable: capacity_per_sqkm: 1.7 # correction_factor: 0.854337 corine: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 26, 31, 32] + luisa: false # [1111, 1121, 1122, 1123, 1130, 1210, 1221, 1222, 1230, 1241, 1242, 1310, 1320, 1330, 1410, 1421, 1422, 2110, 2120, 2130, 2210, 2220, 2230, 2310, 2410, 2420, 3210, 3320, 3330] natura: true excluder_resolution: 100 clip_p_max_pu: 1.e-2 diff --git a/doc/configtables/offwind-ac.csv b/doc/configtables/offwind-ac.csv index c3512a9e..9dc0614c 100644 --- a/doc/configtables/offwind-ac.csv +++ b/doc/configtables/offwind-ac.csv @@ -7,6 +7,7 @@ capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine place correction_factor,--,float,"Correction factor for capacity factor time series." excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis." corine,--,"Any *realistic* subset of the `CORINE Land Cover code list `_","Specifies areas according to CORINE Land Cover codes which are generally eligible for AC-connected offshore wind turbine placement." +luisa,--,"Any subset of the `LUISA Base Map codes in Annex 1 `_","Specifies areas according to the LUISA Base Map codes which are generally eligible for AC-connected offshore wind turbine placement." natura,bool,"{true, false}","Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if ``true``." ship_threshold,--,float,"Ship density threshold from which areas are excluded." max_depth,m,float,"Maximum sea water depth at which wind turbines can be build. Maritime areas with deeper waters are excluded in the process of calculating the AC-connected offshore wind potential." diff --git a/doc/configtables/offwind-dc.csv b/doc/configtables/offwind-dc.csv index 35095597..c947f358 100644 --- a/doc/configtables/offwind-dc.csv +++ b/doc/configtables/offwind-dc.csv @@ -7,6 +7,7 @@ capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of wind turbine place correction_factor,--,float,"Correction factor for capacity factor time series." excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis." corine,--,"Any *realistic* subset of the `CORINE Land Cover code list `_","Specifies areas according to CORINE Land Cover codes which are generally eligible for AC-connected offshore wind turbine placement." +luisa,--,"Any subset of the `LUISA Base Map codes in Annex 1 `_","Specifies areas according to the LUISA Base Map codes which are generally eligible for DC-connected offshore wind turbine placement." natura,bool,"{true, false}","Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if ``true``." ship_threshold,--,float,"Ship density threshold from which areas are excluded." max_depth,m,float,"Maximum sea water depth at which wind turbines can be build. Maritime areas with deeper waters are excluded in the process of calculating the AC-connected offshore wind potential." diff --git a/doc/configtables/onwind.csv b/doc/configtables/onwind.csv index b7e823b3..f6b36e5d 100644 --- a/doc/configtables/onwind.csv +++ b/doc/configtables/onwind.csv @@ -8,6 +8,10 @@ corine,,, -- grid_codes,--,"Any subset of the `CORINE Land Cover code list `_","Specifies areas according to CORINE Land Cover codes which are generally eligible for wind turbine placement." -- distance,m,float,"Distance to keep from areas specified in ``distance_grid_codes``" -- distance_grid_codes,--,"Any subset of the `CORINE Land Cover code list `_","Specifies areas according to CORINE Land Cover codes to which wind turbines must maintain a distance specified in the setting ``distance``." +luisa,,, +-- grid_codes,--,"Any subset of the `LUISA Base Map codes in Annex 1 `_","Specifies areas according to the LUISA Base Map codes which are generally eligible for wind turbine placement." +-- distance,m,float,"Distance to keep from areas specified in ``distance_grid_codes``" +-- distance_grid_codes,--,"Any subset of the `LUISA Base Map codes in Annex 1 `_","Specifies areas according to the LUISA Base Map codes to which wind turbines must maintain a distance specified in the setting ``distance``." natura,bool,"{true, false}","Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if ``true``." clip_p_max_pu,p.u.,float,"To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero." correction_factor,--,float,"Correction factor for capacity factor time series." diff --git a/doc/configtables/solar.csv b/doc/configtables/solar.csv index 7da1281b..8328d342 100644 --- a/doc/configtables/solar.csv +++ b/doc/configtables/solar.csv @@ -9,6 +9,7 @@ resource,,, capacity_per_sqkm,:math:`MW/km^2`,float,"Allowable density of solar panel placement." correction_factor,--,float,"A correction factor for the capacity factor (availability) time series." corine,--,"Any subset of the `CORINE Land Cover code list `_","Specifies areas according to CORINE Land Cover codes which are generally eligible for solar panel placement." +luisa,--,"Any subset of the `LUISA Base Map codes in Annex 1 `_","Specifies areas according to the LUISA Base Map codes which are generally eligible for solar panel placement." natura,bool,"{true, false}","Switch to exclude `Natura 2000 `_ natural protection areas. Area is excluded if ``true``." clip_p_max_pu,p.u.,float,"To avoid too small values in the renewables` per-unit availability time series values below this threshold are set to zero." excluder_resolution,m,float,"Resolution on which to perform geographical elibility analysis." diff --git a/doc/release_notes.rst b/doc/release_notes.rst index a4f20b86..bb9732de 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -96,6 +96,16 @@ Upcoming Release * Print Irreducible Infeasible Subset (IIS) if model is infeasible. Only for solvers with IIS support. +* Add option to use `LUISA Base Map + `_ 50m land + coverage dataset for land eligibility analysis in + :mod:`build_renewable_profiles`. Settings are analogous to the CORINE dataset + but with the key ``luisa:`` in the configuration file. To leverage the + dataset's full advantages, set the excluder resolution to 50m + (``excluder_resolution: 50``). For land category codes, see `Annex 1 of the + technical documentation + `_. + **Bugs and Compatibility** * A bug preventing custom powerplants specified in ``data/custom_powerplants.csv`` was fixed. (https://github.com/PyPSA/pypsa-eur/pull/732) diff --git a/rules/build_electricity.smk b/rules/build_electricity.smk index 6308552f..055cffca 100644 --- a/rules/build_electricity.smk +++ b/rules/build_electricity.smk @@ -268,6 +268,11 @@ rule build_renewable_profiles: if config["renewable"][w.technology]["natura"] else [] ), + luisa=lambda w: ( + "data/LUISA_basemap_020321_50m.tif" + if config["renewable"][w.technology].get("luisa") + else [] + ), gebco=ancient( lambda w: ( "data/bundle/GEBCO_2014_2D.nc" diff --git a/rules/retrieve.smk b/rules/retrieve.smk index 4ded2a46..e062091e 100644 --- a/rules/retrieve.smk +++ b/rules/retrieve.smk @@ -249,6 +249,22 @@ if config["enable"]["retrieve"]: validate_checksum(output[0], input[0]) +if config["enable"]["retrieve"]: + + # Downloading LUISA Base Map for land cover and land use: + # Website: https://ec.europa.eu/jrc/en/luisa + rule retrieve_luisa_land_cover: + input: + HTTP.remote( + "jeodpp.jrc.ec.europa.eu/ftp/jrc-opendata/LUISA/EUROPE/Basemaps/LandUse/2018/LATEST/LUISA_basemap_020321_50m.tif", + static=True, + ), + output: + "data/LUISA_basemap_020321_50m.tif", + run: + move(input[0], output[0]) + + if config["enable"]["retrieve"]: # Some logic to find the correct file URL # Sometimes files are released delayed or ahead of schedule, check which file is currently available diff --git a/scripts/build_renewable_profiles.py b/scripts/build_renewable_profiles.py index c579f588..b736f68a 100644 --- a/scripts/build_renewable_profiles.py +++ b/scripts/build_renewable_profiles.py @@ -26,7 +26,7 @@ Relevant settings renewable: {technology}: - cutout: corine: grid_codes: distance: natura: max_depth: + cutout: corine: luisa: grid_codes: distance: natura: max_depth: max_shore_distance: min_shore_distance: capacity_per_sqkm: correction_factor: min_p_max_pu: clip_p_max_pu: resource: @@ -40,11 +40,18 @@ Inputs - ``data/bundle/corine/g250_clc06_V18_5.tif``: `CORINE Land Cover (CLC) `_ inventory on `44 classes `_ of - land use (e.g. forests, arable land, industrial, urban areas). + land use (e.g. forests, arable land, industrial, urban areas) at 100m + resolution. .. image:: img/corine.png :scale: 33 % +- ``data/LUISA_basemap_020321_50m.tif``: `LUISA Base Map + `_ land + coverage dataset at 50m resolution similar to CORINE. For codes in relation to + CORINE land cover, see `Annex 1 of the technical documentation + `_. + - ``data/bundle/GEBCO_2014_2D.nc``: A `bathymetric `_ data set with a global terrain model for ocean and land at 15 arc-second intervals by the `General @@ -133,9 +140,10 @@ of a combination of the available land at each grid cell and the capacity factor there. First the script computes how much of the technology can be installed at each -cutout grid cell and each node using the `GLAES -`_ library. This uses the CORINE land use -data, Natura2000 nature reserves and GEBCO bathymetry data. +cutout grid cell and each node using the `atlite +`_ library. This uses the CORINE land use data, +LUISA land use data, Natura2000 nature reserves, GEBCO bathymetry data, and +shipping lanes. .. image:: img/eligibility.png :scale: 50 % @@ -166,10 +174,10 @@ This layout is then used to compute the generation availability time series from the weather data cutout from ``atlite``. The maximal installable potential for the node (`p_nom_max`) is computed by -adding up the installable potentials of the individual grid cells. -If the model comes close to this limit, then the time series may slightly -overestimate production since it is assumed the geographical distribution is -proportional to capacity factor. +adding up the installable potentials of the individual grid cells. If the model +comes close to this limit, then the time series may slightly overestimate +production since it is assumed the geographical distribution is proportional to +capacity factor. """ import functools import logging @@ -203,9 +211,6 @@ if __name__ == "__main__": correction_factor = params.get("correction_factor", 1.0) capacity_per_sqkm = params["capacity_per_sqkm"] - if isinstance(params.get("corine", {}), list): - params["corine"] = {"grid_codes": params["corine"]} - if correction_factor != 1.0: logger.info(f"correction_factor is set as {correction_factor}") @@ -231,16 +236,29 @@ if __name__ == "__main__": if params["natura"]: excluder.add_raster(snakemake.input.natura, nodata=0, allow_no_overlap=True) - corine = params.get("corine", {}) - if "grid_codes" in corine: - codes = corine["grid_codes"] - excluder.add_raster(snakemake.input.corine, codes=codes, invert=True, crs=3035) - if corine.get("distance", 0.0) > 0.0: - codes = corine["distance_grid_codes"] - buffer = corine["distance"] - excluder.add_raster( - snakemake.input.corine, codes=codes, buffer=buffer, crs=3035 - ) + for dataset in ["corine", "luisa"]: + kwargs = {"nodata": 0} if dataset == "luisa" else {} + settings = params.get(dataset, {}) + if not settings: + continue + if dataset == "luisa" and res > 50: + logger.info( + "LUISA data is available at 50m resolution, " + f"but coarser {res}m resolution is used." + ) + if isinstance(settings, list): + settings = {"grid_codes": settings} + if "grid_codes" in settings: + codes = settings["grid_codes"] + excluder.add_raster( + snakemake.input[dataset], codes=codes, invert=True, crs=3035, **kwargs + ) + if settings.get("distance", 0.0) > 0.0: + codes = settings["distance_grid_codes"] + buffer = settings["distance"] + excluder.add_raster( + snakemake.input[dataset], codes=codes, buffer=buffer, crs=3035, **kwargs + ) if params.get("ship_threshold"): shipping_threshold = (