diff --git a/.gitignore b/.gitignore index dc6a0dbb..f0a2745f 100644 --- a/.gitignore +++ b/.gitignore @@ -170,6 +170,7 @@ scratch.py ispypsa_runs/**/*.csv ispypsa_runs/**/*.parquet ispypsa_runs/**/*.hdf5 +ispypsa_runs/**/results/** # ignore doit database .doit* diff --git a/dodo.py b/dodo.py index 540c1bfd..42d2dd94 100644 --- a/dodo.py +++ b/dodo.py @@ -15,8 +15,9 @@ add_lines_to_network, initialise_network, run, - save_results, ) +from ispypsa.results import load_results, save_results +from ispypsa.results.plotting import plot_map_of_energy_generation_by_carrier from ispypsa.templater.dynamic_generator_properties import ( template_generator_dynamic_properties, ) @@ -57,6 +58,7 @@ _PYPSA_FRIENDLY_DIRECTORY = Path(run_folder, "pypsa_friendly") _PARSED_TRACE_DIRECTORY = Path(config.traces.path_to_parsed_traces) _PYPSA_OUTPUTS_DIRECTORY = Path(run_folder, "outputs") +_RESULTS_DIRECTORY = Path(run_folder, "results") configure_logging() @@ -203,6 +205,19 @@ def create_and_run_pypsa_model( save_results(network, pypsa_outputs_location) +def create_results( + config: ModelConfig, pypsa_outputs_location: Path, results_location: Path +) -> None: + create_or_clean_task_output_folder(results_location) + network = load_results(pypsa_outputs_location) + fig, ax = plot_map_of_energy_generation_by_carrier( + network, config, figure_size_inches=(6.5, 8), figure_kwargs=dict(dpi=600) + ) + fig.savefig( + Path(results_location, "map_of_total_generation_by_carrier.png"), dpi=600 + ) + + def task_cache_required_tables(): return { "actions": [(build_parsed_workbook_cache, [_PARSED_WORKBOOK_CACHE])], @@ -288,3 +303,18 @@ def task_create_and_run_pypsa_model(): ], "targets": [Path(_PYPSA_OUTPUTS_DIRECTORY, "network.hdf5")], } + + +def task_produce_results(): + return { + "actions": [ + ( + create_results, + [config, _PYPSA_OUTPUTS_DIRECTORY, _RESULTS_DIRECTORY], + ) + ], + "file_dep": [ + Path(_PYPSA_OUTPUTS_DIRECTORY, "network.hdf5"), + ], + "targets": [Path(_RESULTS_DIRECTORY, "map_of_total_generation_by_carrier.png")], + } diff --git a/pyproject.toml b/pyproject.toml index 06a29bbb..b0c8d87b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ dependencies = [ "isp-trace-parser>=1.0.0", "pyarrow>=18.0.0", "tables>=3.10.1", + "cartopy>=0.24.1", ] readme = "README.md" requires-python = ">= 3.10" diff --git a/src/ispypsa/model/__init__.py b/src/ispypsa/model/__init__.py index ece4d007..075c794a 100644 --- a/src/ispypsa/model/__init__.py +++ b/src/ispypsa/model/__init__.py @@ -4,7 +4,6 @@ from ispypsa.model.initialise import initialise_network from ispypsa.model.lines import add_lines_to_network from ispypsa.model.run import run -from ispypsa.model.save_results import save_results __all__ = [ "add_buses_to_network", @@ -13,5 +12,4 @@ "add_carriers_to_network", "add_lines_to_network", "run", - "save_results", ] diff --git a/src/ispypsa/model/buses.py b/src/ispypsa/model/buses.py index da2efa1f..e2e9166d 100644 --- a/src/ispypsa/model/buses.py +++ b/src/ispypsa/model/buses.py @@ -5,7 +5,7 @@ def _add_bus_to_network( - bus_name: str, network: pypsa.Network, path_to_demand_traces: Path + bus_definition: dict, network: pypsa.Network, path_to_demand_traces: Path ) -> None: """ Adds a Bus to the network and if a demand trace for the Bus exists, also adds the @@ -19,8 +19,9 @@ def _add_bus_to_network( Returns: None """ - network.add(class_name="Bus", name=bus_name) - + bus_definition["class_name"] = "Bus" + network.add(**bus_definition) + bus_name = bus_definition["name"] demand_trace_path = path_to_demand_traces / Path(f"{bus_name}.parquet") if demand_trace_path.exists(): demand = pd.read_parquet(demand_trace_path) @@ -48,6 +49,7 @@ def add_buses_to_network(network: pypsa.Network, path_pypsa_inputs: Path) -> Non """ buses = pd.read_csv(path_pypsa_inputs / Path("buses.csv")) path_to_demand_traces = path_pypsa_inputs / Path("demand_traces") - buses["name"].apply( - lambda x: _add_bus_to_network(x, network, path_to_demand_traces) + buses.apply( + lambda row: _add_bus_to_network(row.to_dict(), network, path_to_demand_traces), + axis=1, ) diff --git a/src/ispypsa/model/save_results.py b/src/ispypsa/model/save_results.py deleted file mode 100644 index 0a62bf06..00000000 --- a/src/ispypsa/model/save_results.py +++ /dev/null @@ -1,7 +0,0 @@ -from pathlib import Path - -import pypsa - - -def save_results(network: pypsa.Network, pypsa_outputs_location: Path) -> None: - network.export_to_hdf5(Path(pypsa_outputs_location, "network.hdf5")) diff --git a/src/ispypsa/results/__init__.py b/src/ispypsa/results/__init__.py new file mode 100644 index 00000000..70113a59 --- /dev/null +++ b/src/ispypsa/results/__init__.py @@ -0,0 +1,3 @@ +from ispypsa.results.io import load_results, save_results + +__all__ = ["load_results", "save_results"] diff --git a/src/ispypsa/results/io.py b/src/ispypsa/results/io.py new file mode 100644 index 00000000..32dd6af1 --- /dev/null +++ b/src/ispypsa/results/io.py @@ -0,0 +1,13 @@ +from pathlib import Path + +import pypsa + + +def save_results(network: pypsa.Network, pypsa_outputs_location: Path) -> None: + network.export_to_hdf5(Path(pypsa_outputs_location, "network.hdf5")) + + +def load_results(pypsa_outputs_location: str | Path) -> pypsa.Network: + network = pypsa.Network() + network.import_from_hdf5(Path(pypsa_outputs_location, "network.hdf5")) + return network diff --git a/src/ispypsa/results/plot_helpers.py b/src/ispypsa/results/plot_helpers.py new file mode 100644 index 00000000..c9653316 --- /dev/null +++ b/src/ispypsa/results/plot_helpers.py @@ -0,0 +1,86 @@ +import matplotlib.figure +from matplotlib.patches import Patch + +from ispypsa.config.validators import ModelConfig + +_DEFAULT_CARRIER_COLOUR_MAPPING = { + # corresponds to distillate in OpenElectricity + "Liquid Fuel": "#E46E56", + "Black Coal": "#251C00", + "Brown Coal": "#675B42", + "Gas": "#E78114", + "Water": "#ACE9FE", + "Solar": "#FECE00", + "Wind": "#2A7E3F", + # corresponds to gas_hydrogen in OpenElectricity + "Hyblend": "#C75338", +} +"""Colour mapping for carriers/fuel types. Same colour scheme as OpenElectricity""" + +_DEFAULT_GEOMAP_COLOURS = dict(ocean="#dbdbdd", land="#fdfdfe") +"""Colour mapping for ocean and land in the map plot""" + +_DEFAULT_FACECOLOR = "#faf9f6" +"""Facecolour to use in Figures (from OpenElectricity)""" + + +def _determine_title_year_range(config: ModelConfig) -> str: + """ + Determines the year range string for use in plot titles based on + ISPyPSA configuration options. + """ + (start, end) = (config.traces.start_year, config.traces.end_year) + year_type = config.traces.year_type + if year_type == "fy" and start != end: + year_range = f"FY{str(start)[-2:]}-{str(end)[-2:]}" + elif start == end: + year_range = f"{start}" + else: + year_range = f"{start}-{end}" + return year_range + + +def _add_figure_fuel_type_legend( + fig: matplotlib.figure.Figure, + carrier_colour_mapping: dict[str, str], + legend_kwargs=dict(), +) -> None: + """Adds a legend that maps fuel types to their patch colours to a + `matplotlib.figure.Figure`. + + Args: + fig: `matplotlib.figure.Figure` + carrier_colour_mapping: Dictionary that maps each carrier to a colour + legend_kwargs (optional): Keyword arguments for + `matplotlib.figure.Figure.legend()`. Anything specified in this dict will + overwrite ISPyPSA defaults. Defaults to dict(). + """ + legend_patches = [ + Patch(color=color, label=carrier) + for carrier, color in carrier_colour_mapping.items() + ] + fig.legend( + handles=legend_patches, + **_consolidate_plot_kwargs( + dict( + title="Fuel Type", + loc="lower center", + fontsize=8, + title_fontsize=10, + ncol=4, + frameon=False, + ), + legend_kwargs, + ), + ) + + +def _consolidate_plot_kwargs( + predefined_kwargs: dict, user_specified_kwargs: dict +) -> dict: + """Adds to or replaces ISPyPSA's keyword arguments for plot functions using those + provided by the user in the function call. + """ + kwargs = predefined_kwargs + kwargs.update(user_specified_kwargs) + return kwargs diff --git a/src/ispypsa/results/plotting.py b/src/ispypsa/results/plotting.py new file mode 100644 index 00000000..293afde5 --- /dev/null +++ b/src/ispypsa/results/plotting.py @@ -0,0 +1,274 @@ +import cartopy.crs as ccrs +import matplotlib +import matplotlib.axes +import matplotlib.figure +import matplotlib.pyplot as plt +import pandas as pd +import pypsa + +from ispypsa.config.validators import ModelConfig +from ispypsa.results.plot_helpers import ( + _DEFAULT_CARRIER_COLOUR_MAPPING, + _DEFAULT_FACECOLOR, + _DEFAULT_GEOMAP_COLOURS, + _add_figure_fuel_type_legend, + _consolidate_plot_kwargs, + _determine_title_year_range, +) + + +def plot_map_of_energy_generation_by_carrier( + network: pypsa.Network, + config: ModelConfig, + figure_size_inches: tuple[float, float], + bus_size_scaling_factor: float | None = None, + flow_arrow_size_scaling_factor: float | None = None, + flow_colormap: str = "inferno", + min_max_latitudes: tuple[float, float] = (-44.0, -15.0), + min_max_longitudes: tuple[float, float] = (137.5, 156.0), + bus_labels_x_offset: float = 0.15, + bus_labels_y_offset: float = -0.2, + pypsa_plot_kwargs: dict = dict(), + figure_kwargs: dict = dict(), + subplot_kwargs: dict = dict(), + fuel_type_legend_kwargs: dict = dict(), + generation_reference_legend_kwargs: dict = dict(), + bus_label_kwargs: dict = dict(), +) -> tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: + """Creates a geomap plot that shows the total energy generated at each bus in the + modelled period, the percentage of that energy produced generators of a particular + fuel type, and the mean flows across lines in the same period. + + Args: + network: The `pypsa.Network` with the model solved. + config: The ISPyPSA model configuration used to generate the model. + figure_size_inches: Figure (width, height) in inches (input to `matplotlib`). + bus_size_scaling_factor (optional): The factor used to scale total generation at + each bus to obtain bus circle/pie sizes. If set to `None`, this factor will + be automatically determined based on a sensible preset. Defaults to None. + flow_arrow_size_scaling_factor (optional): The factor used to mean flow on each + line to obtain arrow sizes. If set to `None`, this factor will be + automatically determined based on a sensible preset. Defaults to None. + flow_colormap (optional): The `matplotlib` colormap to use for the flow colormap. + Defaults to "inferno". + min_max_latitudes (optional): Geomap latitude limits. Defaults to (-44.0, -15.0). + min_max_longitudes (optional): Geomap longitude limits. Defaults to (137.5, 156.0). + bus_labels_x_offset (optional): X-axis offset from the edge of the bus circle/pie. + Defaults to 0.15. + bus_labels_y_offset (optional): Y-axis offset from the centre of the bus + circle/pie. Defaults to -0.2. + pypsa_plot_kwargs (optional): Keyword arguments for `pyspa.Network.plot()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + figure_kwargs (optional): Keyword arguments for `matplotlib.figure.Figure()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + subplot_kwargs (optional): Keyword arguments for subplots created by + `matplotlib.pyplot.subplots()`. Anything specified in this dict will + overwrite ISPyPSA defaults. Defaults to dict(). + fuel_type_legend_kwargs (optional): Keyword arguments for + `matplotlib.figure.Figure.legend()`. Anything specified in this dict will + overwrite ISPyPSA defaults. Defaults to dict(). + generation_reference_legend_kwargs (optional): Keyword arguments for + `matplotlib.ax.legend()`. Anything specified in this dict will overwrite + ISPyPSA defaults. Defaults to dict(). + bus_label_kwargs (optional): Keyword arguments for `ax.text`. + Anything specified in this dict will overwrite ISPyPSA defaults. + Defaults to dict(). + + Returns: + tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]: Figure and axis with + geomap plot. + """ + # use total generation to define bus sizes + total_gen_by_bus_and_carrier = _sum_generation_by_bus_and_carrier(network) + max_bus_gen = max(total_gen_by_bus_and_carrier.groupby("bus").sum()) + # size of ~1.0 appears to work well from trial-and-error + if bus_size_scaling_factor is None: + bus_size_scaling_factor = 1.0 / max_bus_gen + # size of ~300.0 appears to work well from trial-and-error + if flow_arrow_size_scaling_factor is None: + flow_arrow_size_scaling_factor = min(300.0 / network.lines_t.p0.mean().abs()) + (main_title, sub_title) = (_create_main_title(config), _create_sub_title(config)) + fig, ax = plt.subplots( + 1, + 1, + subplot_kw=_consolidate_plot_kwargs( + dict(projection=ccrs.PlateCarree()), subplot_kwargs + ), + **_consolidate_plot_kwargs( + dict(figsize=figure_size_inches, facecolor=_DEFAULT_FACECOLOR), + figure_kwargs, + ), + ) + pyspsa_plot_kwgs = _consolidate_plot_kwargs( + dict( + geomap=True, + color_geomap=_DEFAULT_GEOMAP_COLOURS, + ax=ax, + bus_colors=_DEFAULT_CARRIER_COLOUR_MAPPING, + bus_sizes=total_gen_by_bus_and_carrier * bus_size_scaling_factor, + flow="mean", + line_colors=network.lines_t.p0.mean().abs(), + line_cmap=flow_colormap, + line_widths=flow_arrow_size_scaling_factor, + boundaries=[ + min_max_longitudes[0], + min_max_longitudes[1], + min_max_latitudes[0], + min_max_latitudes[1], + ], + title=sub_title, + ), + pypsa_plot_kwargs, + ) + collection = network.plot(**pyspsa_plot_kwgs) + _add_bus_name_labels( + ax, + network, + bus_size_scaling_factor, + x_offset=bus_labels_x_offset, + y_offset=bus_labels_y_offset, + label_kwargs=bus_label_kwargs, + ) + plt.colorbar(collection[2], fraction=0.04, pad=0.04, label="Mean flow (MW)") + _add_figure_fuel_type_legend( + fig, _DEFAULT_CARRIER_COLOUR_MAPPING, legend_kwargs=fuel_type_legend_kwargs + ) + _add_generation_circle_reference_legend( + ax, max_bus_gen, legend_kwargs=generation_reference_legend_kwargs + ) + fig.suptitle(main_title, fontsize=16) + return fig, ax + + +def _sum_generation_by_bus_and_carrier(network: pypsa.Network) -> pd.Series: + generators_with_total_generation = network.generators.assign( + total_generation=network.generators_t.p.sum() + ) + return generators_with_total_generation.groupby( + ["bus", "carrier"] + ).total_generation.sum() + + +def _create_main_title(config: ModelConfig) -> str: + year_range = _determine_title_year_range(config) + title = f"Total energy generation by fuel type\nand mean line flows, {year_range}" + return title + + +def _create_sub_title(config: ModelConfig) -> str: + run_name = config.ispypsa_run_name + title = f"ISPyPSA run: '{run_name}'" + return title + + +def _add_generation_circle_reference_legend( + ax: matplotlib.axes.Axes, + max_bus_gen: float, + patch_kwargs=dict(), + legend_kwargs=dict(), +) -> None: + """Adds a legend with circles with reference sizes that correspond to total + generation of 1 TWh, 10 TWh and 100 TWh. + + Args: + ax: `matplotlib.axes.Axes` with geomap. + max_bus_gen: The maximum total generation at any one bus. + patch_kwargs (optional). Keyword arguments for `matplotlib.patches.Circle()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + legend_kwargs (optional): Keyword arguments for `matplotlib.ax.legend()`. + Anything specified in this dict will overwrite ISPyPSA defaults. Defaults to + dict(). + """ + pypsa.plot.add_legend_circles( + ax, + [1 / max_bus_gen * factor for factor in (1e6, 1e7, 1e8)], + ["1 TWh", "10 TWh", "100 TWh"], + patch_kw=_consolidate_plot_kwargs( + dict(edgecolor="black", facecolor=("black", 0.0)), patch_kwargs + ), + legend_kw=_consolidate_plot_kwargs( + dict(labelspacing=1.75, frameon=False, title="Total generation"), + legend_kwargs, + ), + ) + + +def _add_bus_name_labels( + ax: matplotlib.axes.Axes, + network: pypsa.Network, + bus_size_scaling_factor: float, + x_offset: float, + y_offset: float, + label_kwargs: dict = dict(), +) -> None: + """Adds bus name labels for plotted buses to the map. + + Args: + ax: `matplotlib.axes.Axes` with geomap. + network: The `pypsa.Network` with the model solved. + bus_size_scaling_factor: The factor used to scale the size of the buses. + x_offset: X-axis offset from the edge of the bus circle/pie. + y_offset: Y-axis offset from the centre of the bus circle/pie. + label_kwargs (optional): Keyword arguments for `ax.text`. Anything specified in + this dict will overwrite ISPyPSA defaults. Defaults to dict(). + """ + plotted_buses = network.buses[network.buses.x != 0.0] + label_offsets = _calculate_bus_label_offsets( + network, bus_size_scaling_factor, x_offset, y_offset + ) + plotted_buses = plotted_buses.merge( + label_offsets, how="left", left_index=True, right_index=True + ) + bus_name_xy = plotted_buses[["x", "y", "x_offset", "y_offset"]].T.to_dict() + for bus, attrs in bus_name_xy.items(): + ax.text( + attrs["x"] + attrs["x_offset"], + attrs["y"] + attrs["y_offset"], + bus, + **_consolidate_plot_kwargs(dict(horizontalalignment="left"), label_kwargs), + ) + + +def _calculate_bus_label_offsets( + network: pypsa.Network, + bus_size_scaling_factor: float, + x_offset: float, + y_offset: float, +) -> pd.DataFrame: + """Calculates bus label offsets accounting for the size of the bus generation pie + chart + + X-axis text label offsets are applied on top of a base offset that roughly + corresponds to the radius of the bus circle/pie. Without the base offset, the label + would be obscured where the bus has a large circle/pie. + + Note 1: By default, the bottom left hand corner of the bus label text box is used + as the anchor point. + + Note 2: PyPSA appears to calculate the radius of a bus circle/pie by taking the + square root of `bus_size` (or area). `bus_size` is proportional to total generation + at that bus in this chart. + + Args: + network: The `pypsa.Network` with the model solved. + bus_size_scaling_factor: The factor used to scale the size of the buses. + x_offset: X-axis offset from the edge of the bus circle/pie. + y_offset: Y-axis offset from the centre of the bus circle/pie. + + Returns: + pd.DataFrame: DataFrame with x-axis and y-axis offsets for each bus. + """ + total_gen_by_bus = _sum_generation_by_bus_and_carrier(network).groupby("bus").sum() + rough_bus_radii = (total_gen_by_bus * bus_size_scaling_factor).pow(0.5) + offsets = pd.DataFrame(index=rough_bus_radii.index) + if x_offset > 0: + # offset from right end of pie + offsets["x_offset"] = rough_bus_radii + x_offset + else: + # offset from left end of pie + offsets["x_offset"] = -1 * rough_bus_radii - x_offset + offsets["y_offset"] = y_offset + return offsets diff --git a/src/ispypsa/translator/mappings.py b/src/ispypsa/translator/mappings.py index eb0cadaf..98531de8 100644 --- a/src/ispypsa/translator/mappings.py +++ b/src/ispypsa/translator/mappings.py @@ -4,7 +4,11 @@ "fuel_type": "carrier", } -_BUS_ATTRIBUTES = {"node_id": "name"} +_BUS_ATTRIBUTES = { + "node_id": "name", + "substation_longitude": "x", + "substation_latitude": "y", +} _LINE_ATTRIBUTES = { "flow_path_name": "name", diff --git a/uv.lock b/uv.lock index 892ae159..3ad07144 100644 --- a/uv.lock +++ b/uv.lock @@ -176,6 +176,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/f0/e1640ccd8468c61693092f38f835ef35a68a1ea72c3388683148b3800aa6/Bottleneck-1.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:26b5f0531f7044befaad95c20365dd666372e66bdacbfaf009ff65d60285534d", size = 111774 }, ] +[[package]] +name = "cartopy" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyproj" }, + { name = "pyshp" }, + { name = "shapely" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e0/75/94aff4fef338887641aa780d13795609861e6e9f9593bd66d4917ab7954b/cartopy-0.24.1.tar.gz", hash = "sha256:01c910d5634c69a7efdec46e0a17d473d2328767f001d4dc0b5c4b48e585c8bd", size = 10741277 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/41/9dd14e3ee3f7a0546768c11a8f4a37b1c09fc4868b992f431431d526502b/Cartopy-0.24.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce0c83314570c61a695a1f7c3a4a22dc75f79d28f4c68b88a8aeaf13d6a2343c", size = 10982199 }, + { url = "https://files.pythonhosted.org/packages/72/57/8b4a3856aaf4c600504566d7d956928b79d8b17e8d3a1c70060e5f90124f/Cartopy-0.24.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:511f992340baea2c171cb17b3ef595537e5355640f3baa7ac895de25df016a70", size = 10971756 }, + { url = "https://files.pythonhosted.org/packages/ea/50/e5170302a62259f34289ff7f4944a32ac04a49b38713d001873732742726/Cartopy-0.24.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54f4d23961e0f9436baaf4747928361ccdcc893fa9b7bad9f615551bc8aa3fe8", size = 11658621 }, + { url = "https://files.pythonhosted.org/packages/0b/78/7d77c72c85371f5d87088887ec05fd3701dfdcd640845f85378341a355de/Cartopy-0.24.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0b55526b605a9dee4fa3d7e5659b6d7d9d30e609bc5e62487bc4f7d8e90873b", size = 10960201 }, + { url = "https://files.pythonhosted.org/packages/c4/f0/eaa16216c8b91cfd433b60e79080ffaf9e470bb5c939662835faa40fd347/Cartopy-0.24.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a14638b63d7df2858f73e9f8f4f4826d7c9cf13781aa6824fa0134fbaebbd98", size = 10982474 }, + { url = "https://files.pythonhosted.org/packages/63/99/681a7ae5e572343e15ce8697dd4b41f49d45fe89f5e5d8b122bff0f8165c/Cartopy-0.24.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d74b4a3eae9e570f474276fb61847112cdccdead396ec2ddad226dad9eaf4564", size = 10971918 }, + { url = "https://files.pythonhosted.org/packages/04/87/8dc9249e67c635a5c08ae81d4243a1ad69a1b91b703d3ab9be7fa78efc2b/Cartopy-0.24.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa38fb216cfd16cc266fd6f86b60ebdf0056839b490f38d2d89229b03abc877", size = 11718335 }, + { url = "https://files.pythonhosted.org/packages/4f/ce/ba4baced164ecd78b4109cd611d7b64d256f012784e944c1b0f6f5dff5c1/Cartopy-0.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:f440ddb61171319adf34ecec4d91202864cd514a7bc8a252e0ff7788a6604031", size = 10960539 }, + { url = "https://files.pythonhosted.org/packages/6e/76/774a4f808c6a4fc19b87c2cc38dd8731d413aad606689451c017ff93ad12/Cartopy-0.24.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a984e33977daed8f760c09c331c8368a6af060db1190af89d74a027c272e39c3", size = 10983939 }, + { url = "https://files.pythonhosted.org/packages/2f/48/8517d5d1cc56ce5c4abda1de6454593474a23412115a543f7981aa7e4377/Cartopy-0.24.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71d8a6d061d0764aba3baf357a68f3d73796a8a46d34b8c9fb241171b273c69e", size = 10972374 }, + { url = "https://files.pythonhosted.org/packages/c8/84/cb1577d5ac2f0deb002001c6e25b291735151c8c3033c97f212dc482ef72/Cartopy-0.24.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f354a1d902a8d6ee33b099acc86ac2e1af528bbc0ea718b834111c97e604981", size = 11715215 }, + { url = "https://files.pythonhosted.org/packages/11/95/40c7abae8789aae22ad2a5da3974d3270dc3526b46cee253f680f72ee6cc/Cartopy-0.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:b1bb2d02b31884ee1d4f14e5b436bbf95745eac39c6fc0d6c67c83bb907b55b3", size = 10959875 }, + { url = "https://files.pythonhosted.org/packages/e6/e8/38e00eb35743f22d4ee9bcb131ab273fb47ec39cc03ce5144686a3142756/Cartopy-0.24.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d279968b845f72e3423e454b2b0b985fb2389e6ccd18fb73324abeca4e43f516", size = 10982533 }, + { url = "https://files.pythonhosted.org/packages/66/60/cc852a0835a053db18085dec1d1dd6f9cedc764d1524bfe30fd8be5ee3a7/Cartopy-0.24.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f0963b80a048252815c56fbd21bc4e5d163618a9eaa36c8898ce2c60b6c03979", size = 10971183 }, + { url = "https://files.pythonhosted.org/packages/a3/5b/476c8f3a277d7c78e5a5318bd32f234b994bfdc5d7731ae84218f2fa8a8f/Cartopy-0.24.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfdde0a6e0e56c5fc46f4e7d332237eb31bbd9908417f0f190fda5d322754184", size = 11709060 }, + { url = "https://files.pythonhosted.org/packages/93/31/50bf07ba820c5aa5d4e674d72cdb5da90bbd012ba1b9c6c95c3f96afe233/Cartopy-0.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:b17cf23dd74d0a922c2a5682dacef3c0bf89fa8c0bd0eae96b87fb684f966b15", size = 10959830 }, +] + [[package]] name = "certifi" version = "2024.8.30" @@ -993,6 +1025,7 @@ name = "ispypsa" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "cartopy" }, { name = "doit" }, { name = "isp-trace-parser" }, { name = "isp-workbook-parser" }, @@ -1030,6 +1063,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "cartopy", specifier = ">=0.24.1" }, { name = "doit", specifier = ">=0.36.0" }, { name = "isp-trace-parser", specifier = ">=1.0.0" }, { name = "isp-workbook-parser", specifier = ">=2.0.3" }, @@ -2262,6 +2296,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/8d/7e86347eda3dca8eaf6da846ba093340398dbd89c6a2ca5f331a82a3408b/pypsa-0.31.2-py3-none-any.whl", hash = "sha256:2d1befbaaf295856dfe299378f2b9ffda0e0948812823ebb2845595062491ab1", size = 144609 }, ] +[[package]] +name = "pyshp" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/9f/0dd21250c60375a532c35e89fad8d5e8a3f1a2e3f7c389ccc5a60b05263e/pyshp-2.3.1.tar.gz", hash = "sha256:4caec82fd8dd096feba8217858068bacb2a3b5950f43c048c6dc32a3489d5af1", size = 1731544 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/2f/68116db5b36b895c0450e3072b8cb6c2fac0359279b182ea97014d3c8ac0/pyshp-2.3.1-py2.py3-none-any.whl", hash = "sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49", size = 46537 }, +] + [[package]] name = "pytest" version = "8.3.3"