├── tests ├── data │ ├── written_files │ │ └── .gitkeep │ ├── OneMesh2D.nc │ ├── ResultFile.nc │ └── AllUGridEntities.nc ├── test_meshkernel_to_ugrid.py ├── test_variables.py ├── test_mesh2d.py ├── test_mesh1d.py ├── test_contacts.py └── test_network1d.py ├── ugrid ├── version.py ├── utils.py ├── __init__.py ├── errors.py ├── py_structures.py ├── c_structures.py └── ugrid.py ├── pyproject.toml ├── docs ├── examples │ ├── data_examples │ │ ├── OneMesh2D.nc │ │ ├── ADH_SanDiego.nc │ │ ├── ResultFile.nc │ │ ├── AllUGridEntities.nc │ │ └── test.pol │ ├── index.rst │ ├── 03_mesh1d_basics.ipynb │ ├── 02_mesh2d_basics.ipynb │ ├── 01_network1d_basics.ipynb │ ├── 05_working_with_meshkernel.ipynb │ └── 04_variables_basics.ipynb ├── Makefile ├── make.bat ├── index.md └── conf.py ├── .github └── workflows │ ├── spellcheck.yml │ └── docs.yml ├── guide-to-publish.md ├── .flake8 ├── LICENSE ├── .gitignore ├── README.md └── setup.py /tests/data/written_files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ugrid/version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.12.0" 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | profile = "black" 3 | multi_line_output = 3 4 | -------------------------------------------------------------------------------- /tests/data/OneMesh2D.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/tests/data/OneMesh2D.nc -------------------------------------------------------------------------------- /tests/data/ResultFile.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/tests/data/ResultFile.nc -------------------------------------------------------------------------------- /ugrid/utils.py: -------------------------------------------------------------------------------- 1 | # def decode_byte_vectors(cNetwork1D: CNetwork1D, network1D: Network1D) -> None: 2 | -------------------------------------------------------------------------------- /tests/data/AllUGridEntities.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/tests/data/AllUGridEntities.nc -------------------------------------------------------------------------------- /docs/examples/data_examples/OneMesh2D.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/docs/examples/data_examples/OneMesh2D.nc -------------------------------------------------------------------------------- /docs/examples/data_examples/ADH_SanDiego.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/docs/examples/data_examples/ADH_SanDiego.nc -------------------------------------------------------------------------------- /docs/examples/data_examples/ResultFile.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/docs/examples/data_examples/ResultFile.nc -------------------------------------------------------------------------------- /docs/examples/data_examples/AllUGridEntities.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/UGridPy/main/docs/examples/data_examples/AllUGridEntities.nc -------------------------------------------------------------------------------- /ugrid/__init__.py: -------------------------------------------------------------------------------- 1 | # If you change these imports, 2 | # do not forget to sync the docs at "docs/api" 3 | from ugrid.errors import InputError, UGridError 4 | from ugrid.py_structures import UGridContacts, UGridMesh1D, UGridMesh2D, UGridNetwork1D 5 | from ugrid.ugrid import UGrid 6 | from ugrid.version import __version__ 7 | -------------------------------------------------------------------------------- /ugrid/errors.py: -------------------------------------------------------------------------------- 1 | class Error(Exception): 2 | """Base class for exceptions in this module.""" 3 | 4 | 5 | class InputError(Error): 6 | """Exception raised for errors in the input.""" 7 | 8 | 9 | class UGridError(Error): 10 | """Exception raised for errors coming from the MeshKernel library.""" 11 | -------------------------------------------------------------------------------- /.github/workflows/spellcheck.yml: -------------------------------------------------------------------------------- 1 | name: Codespell 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: codespell-project/actions-codespell@master 14 | with: 15 | check_filenames: true 16 | skip: tests,extern,build,.git,.gitignore,*.tif,*.ppt,*.pdf,*.jpg,*.cd,*.ipynb 17 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _examples/main: 2 | 3 | Examples 4 | ========== 5 | 6 | These are Jupyter notebooks embedded via `MyST-NB 7 | `_. 8 | If you want to play with them on your own machine, you can download them from `here 9 | `_. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 01_network1d_basics.ipynb 15 | 02_mesh1d_basics.ipynb 16 | 03_tri_mesh2d_pol.ipynb 17 | -------------------------------------------------------------------------------- /guide-to-publish.md: -------------------------------------------------------------------------------- 1 | # How to publish to PyPi 2 | 3 | 1) If present remove build and dist folder 4 | 5 | 2) Recursively remove all .egg-info files 6 | On powershell you can do this with 7 | ``` 8 | rm -r *.egg-info 9 | ``` 10 | 11 | 3) If not done yet, install twine via 12 | ``` 13 | pip install twine 14 | ``` 15 | 4) Update the version number in the setup.py file. 16 | 17 | 5) Re-create the wheels: 18 | ``` 19 | python setup.py sdist bdist_wheel 20 | ``` 21 | 6) Re-upload the new files: 22 | ``` 23 | twine upload dist/* 24 | ``` -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | check: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 3.9 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: 3.9 16 | 17 | - name: Install dependencies 18 | run: | 19 | pip install -e ".[docs]" 20 | 21 | - name: Run sphinx 22 | run: make html 23 | working-directory: docs 24 | 25 | - name: Deploy to github pages 26 | uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | publish_dir: ./docs/_build/html 30 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | .git, 4 | __pycache__, 5 | autotest 6 | ignore = 7 | # https://flake8.pycqa.org/en/latest/user/error-codes.html 8 | # 'module' imported but unused 9 | F401, 10 | # https://pycodestyle.readthedocs.io/en/latest/intro.html#error-codes 11 | # module level import not at top of file 12 | E402, 13 | # no newline at end of file 14 | W292, 15 | # blank line contains whitespace 16 | W293, 17 | # blank line at end of file 18 | W391, 19 | # line break before binary operator 20 | W503, 21 | # line break after binary operator 22 | W504, 23 | # whitespace before ':' 24 | E203 25 | statistics = True 26 | max-line-length = 140 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Deltares 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | MANIFEST 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # shell stuff 52 | #*.sh 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | docs/api/stubs 57 | 58 | # PyBuilder 59 | .idea/ 60 | 61 | # ignore all .ipynb_checkpoints 62 | .ipynb_checkpoints 63 | 64 | # Visual Studio Code 65 | .vscode/ 66 | 67 | # Ignore mypy cache 68 | .mypy_cache 69 | 70 | # Test artifacts 71 | tests/bin 72 | tests/data/written_files 73 | 74 | # DLLs 75 | *.dll 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UGridPy 2 | 3 | `UGridPy` is a library for writing/reading UGrid files. It supports writing 1D networks, 1D mesh, 2D meshes and 1D-2D contacts in UGrid Format. 4 | The underlying C++ library `UGrid` can be found [here](https://github.com/Deltares/UGrid.git). 5 | 6 | # Installation 7 | 8 | ## Windows 9 | 10 | The library can be installed from PyPI by executing 11 | 12 | ```bash 13 | pip install ugrid 14 | ``` 15 | 16 | ## Linux 17 | 18 | Currently, we only offer wheels specific to Deltares' CentOS machines. 19 | We plan to release a manylinux wheel at PyPI in the future. 20 | 21 | # Examples 22 | 23 | *To be detailed* 24 | 25 | # License 26 | 27 | `UGridPy` uses the MIT license. 28 | However, the wheels on PyPI bundle the LGPL licensed [UGrid](https://github.com/Deltares/UGrid). 29 | Please make sure that this fits your needs before depending on it. 30 | 31 | 32 | # Contributing 33 | 34 | In order to install `UGridPy` locally, please execute the following line inside your virtual environment 35 | 36 | ```bash 37 | pip install -e ".[tests, lint, docs]" 38 | ``` 39 | 40 | Then add a compiled `UGridApi.dll` into your `src` folder. 41 | 42 | Also make sure that your editor is configured to format the code with [`black`](https://black.readthedocs.io/en/stable/) and [`isort`](https://pycqa.github.io/isort/). 43 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # UGridPy 2 | 3 | `UGridPy` is a library for writing/reading ugrid files. 4 | It supports 1D and 2D unstructured meshes. 5 | Support for curvilinear meshes is planned. 6 | The underlying C++ library `UGrid` can be found [here](https://github.com/Deltares/UGrid.git). 7 | 8 | # Installation 9 | 10 | ## Windows 11 | 12 | The library can be installed from PyPI by executing 13 | 14 | ```bash 15 | pip install ugrid 16 | ``` 17 | 18 | ## Linux 19 | 20 | Currently, we only offer wheels specific to Deltares' CentOS machines. 21 | We plan to release a manylinux wheel at PyPI in the future. 22 | 23 | # Examples 24 | 25 | *To be detailed* 26 | 27 | # License 28 | 29 | `UGridPy` uses the MIT license. 30 | However, the wheels on PyPI bundle the LGPL licensed [UGrid](https://github.com/Deltares/UGrid). 31 | Please make sure that this fits your needs before depending on it. 32 | 33 | 34 | # Contributing 35 | 36 | In order to install `UGridPy` locally, please execute the following line inside your virtual environment 37 | 38 | ```bash 39 | pip install -e ".[tests, lint, docs]" 40 | ``` 41 | 42 | Then add a compiled `UGridApi.dll` into your `src` folder. 43 | 44 | Also make sure that your editor is configured to format the code with [`black`](https://black.readthedocs.io/en/stable/) and [`isort`](https://pycqa.github.io/isort/). 45 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = "UGridPy" 21 | copyright = "2021, Deltares" 22 | 23 | # -- General configuration --------------------------------------------------- 24 | 25 | # Add any Sphinx extension module names here, as strings. They can be 26 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 27 | # ones. 28 | extensions = [ 29 | "myst_nb", 30 | "sphinx.ext.autodoc", 31 | "sphinx.ext.napoleon", 32 | ] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ["_templates"] 36 | 37 | # List of patterns, relative to source directory, that match files and 38 | # directories to ignore when looking for source files. 39 | # This pattern also affects html_static_path and html_extra_path. 40 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 41 | 42 | 43 | # -- Options for HTML output ------------------------------------------------- 44 | 45 | # The theme to use for HTML and HTML Help pages. See the documentation for 46 | # a list of builtin themes. 47 | # 48 | html_theme = "sphinx_book_theme" 49 | 50 | # Add any paths that contain custom static files (such as style sheets) here, 51 | # relative to this directory. They are copied after the builtin static files, 52 | # so a file named "default.css" will overwrite the builtin "default.css". 53 | html_static_path = ["_static"] 54 | 55 | # Don't actually execute notebooks 56 | jupyter_execute_notebooks = "off" 57 | -------------------------------------------------------------------------------- /tests/test_meshkernel_to_ugrid.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from meshkernel import Mesh1d, Mesh2d 3 | from test_utils import Mesh2dFactory 4 | 5 | from ugrid import UGrid 6 | 7 | 8 | def test_mesh2d_meshkernel_define_and_put(): 9 | r"""Tests a meshkernel mesh2d is correctly converted to UGridMesh2D and written to file.""" 10 | node_x = np.array([0.0, 1.0, 1.0, 0.0], dtype=np.double) 11 | node_y = np.array([0.0, 0.0, 1.0, 1.0], dtype=np.double) 12 | 13 | edge_nodes = np.array([0, 1, 1, 2, 2, 3, 2, 0], dtype=np.int32) 14 | 15 | face_nodes = np.array([0, 1, 2, 3], dtype=np.int32) 16 | nodes_per_face = np.array([4], dtype=np.int32) 17 | 18 | with UGrid("./data/written_files/Mesh2DMesKernelWrite.nc", "w+") as ug: 19 | ugrid_mesh2d = Mesh2d( 20 | node_x=node_x, 21 | node_y=node_y, 22 | edge_nodes=edge_nodes, 23 | face_nodes=face_nodes, 24 | nodes_per_face=nodes_per_face, 25 | ) 26 | 27 | ugrid_mesh2d = ug.from_meshkernel_mesh2d_to_ugrid_mesh2d( 28 | mesh2d=ugrid_mesh2d, name="mesh2d", is_spherical=False 29 | ) 30 | topology_id = ug.mesh2d_define(ugrid_mesh2d) 31 | assert topology_id == 0 32 | ug.mesh2d_put(topology_id, ugrid_mesh2d) 33 | 34 | 35 | def test_mesh2d_meshkernel_factory_define_and_put(): 36 | r"""Tests a meshkernel mesh2d is correctly converted to UGridMesh2D and written to file 37 | when Mesh2dFactory is used.""" 38 | 39 | mesh2d_mesh_kernel = Mesh2dFactory.create(3, 7, origin_x=-0.1, origin_y=-1.5) 40 | 41 | with UGrid("./data/written_files/Mesh2DMesKernelWithFactoryWrite.nc", "w+") as ug: 42 | ugrid_mesh2d = ug.from_meshkernel_mesh2d_to_ugrid_mesh2d( 43 | mesh2d=mesh2d_mesh_kernel, name="mesh2d", is_spherical=False 44 | ) 45 | topology_id = ug.mesh2d_define(ugrid_mesh2d) 46 | assert topology_id == 0 47 | ug.mesh2d_put(topology_id, ugrid_mesh2d) 48 | 49 | 50 | def test_mesh1d_meshkernel_define_and_put(): 51 | r"""Tests a meshkernel mesh1d is correctly converted to UGridMesh1D and written to file.""" 52 | with UGrid("./data/written_files/Mesh1DMesKernelWrite.nc", "w+") as ug: 53 | # create a meshkernel mesh1d 54 | node_x = np.array([0.0, 1.0, 2.0, 3.0], dtype=np.double) 55 | node_y = np.array([0.0, 0.0, 0.0, 0.0], dtype=np.double) 56 | edge_nodes = np.array([0, 1, 1, 2, 2, 3], dtype=np.int32) 57 | mesh1d = Mesh1d(node_x=node_x, node_y=node_y, edge_nodes=edge_nodes) 58 | 59 | # all extra data required to instatiate a valid ugrid mesh1d 60 | branch_id = np.array([0, 1, 1, 2, 2, 3, 2, 0], dtype=np.int32) 61 | branch_offset = np.array([0, 1, 1, 2, 2, 3, 2, 0], dtype=np.double) 62 | node_name_id = ["branchname"] 63 | node_name_long = ["branchnamelong"] 64 | edge_edge_id = np.array([0, 0, 0], dtype=np.int32) 65 | edge_edge_offset = np.array([0.5, 1.5, 2.5], dtype=np.double) 66 | edge_x = np.array([0.5, 1.5, 2.5], dtype=np.double) 67 | edge_y = np.array([0.0, 0.0, 0.0], dtype=np.double) 68 | 69 | ugrid_mesh1d = ug.from_meshkernel_mesh1d_to_ugrid_mesh1d( 70 | mesh1d=mesh1d, 71 | name="mesh1d", 72 | network_name="network1d", 73 | node_edge_id=branch_id, 74 | node_edge_offset=branch_offset, 75 | node_name_id=node_name_id, 76 | node_name_long=node_name_long, 77 | edge_edge_id=edge_edge_id, 78 | edge_edge_offset=edge_edge_offset, 79 | edge_x=edge_x, 80 | edge_y=edge_y, 81 | double_fill_value=-999.0, 82 | int_fill_value=999, 83 | ) 84 | 85 | topology_id = ug.mesh1d_define(ugrid_mesh1d) 86 | assert topology_id == 0 87 | ug.mesh1d_put(topology_id, ugrid_mesh1d) 88 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | import os.path 3 | import platform 4 | 5 | from setuptools import setup 6 | 7 | author_dict = { 8 | "Luca Carniato": "luca.carniato@deltares.nl", 9 | } 10 | __author__ = ", ".join(author_dict.keys()) 11 | __author_email__ = ", ".join(s for _, s in author_dict.items()) 12 | 13 | 14 | def read(rel_path: str) -> str: 15 | """Used to read a text file 16 | 17 | Args: 18 | rel_path (str): Relative path to the file 19 | 20 | Returns: 21 | str: File content 22 | """ 23 | here = os.path.abspath(os.path.dirname(__file__)) 24 | with codecs.open(os.path.join(here, rel_path), "r") as fp: 25 | return fp.read() 26 | 27 | 28 | def get_version(rel_path: str) -> str: 29 | """Get the version string 30 | 31 | Args: 32 | rel_path (str): Relative path to the file 33 | 34 | Raises: 35 | RuntimeError: Raised if the version string could not be found 36 | 37 | Returns: 38 | str: The version string 39 | """ 40 | for line in read(rel_path).splitlines(): 41 | if line.startswith("__version__"): 42 | delim = '"' if '"' in line else "'" 43 | return line.split(delim)[1] 44 | 45 | raise RuntimeError("Unable to find version string.") 46 | 47 | 48 | def get_runtime_libraries() -> str: 49 | """Get the UGrid runtime libraries 50 | 51 | Raises: 52 | OSError: If the operating system is not supported 53 | 54 | Returns: 55 | str: Filename of the MeshKernel library 56 | """ 57 | system = platform.system() 58 | if system == "Windows": 59 | return "*.dll" 60 | elif system == "Linux": 61 | return "*.so" 62 | elif system == "Darwin": 63 | return "*.dylib" 64 | else: 65 | raise OSError(f"Unsupported operating system: {system}") 66 | 67 | 68 | try: 69 | from wheel.bdist_wheel import bdist_wheel as _bdist_wheel 70 | 71 | class bdist_wheel(_bdist_wheel): 72 | """Class describing our wheel. 73 | Basically it says that it is not a pure Python package, 74 | but it also does not contain any Python source and 75 | therefore works for all Python versions 76 | """ 77 | 78 | def finalize_options(self): 79 | _bdist_wheel.finalize_options(self) 80 | # Mark us as not a pure python package 81 | self.root_is_pure = False 82 | 83 | def get_tag(self): 84 | python, abi, plat = _bdist_wheel.get_tag(self) 85 | # We don't contain any python source 86 | python, abi = "py3", "none" 87 | return python, abi, plat 88 | 89 | 90 | except ImportError: 91 | bdist_wheel = None 92 | 93 | 94 | long_description = read("README.md") 95 | 96 | setup( 97 | name="UGrid", 98 | description="`UGrid` is a library to write and read UGrid files.", 99 | long_description=long_description, 100 | long_description_content_type="text/markdown", 101 | author=__author__, 102 | author_email=__author_email__, 103 | url="https://github.com/Deltares/UGridPy", 104 | license="MIT", 105 | platforms="Windows, Linux", 106 | install_requires=["numpy", "meshkernel"], 107 | extras_require={ 108 | "tests": ["pytest", "pytest-cov", "nbval"], 109 | "lint": [ 110 | "flake8", 111 | "black==21.4b1", 112 | "isort", 113 | ], 114 | "docs": ["sphinx", "sphinx_book_theme", "myst_nb"], 115 | }, 116 | python_requires=">=3.8", 117 | packages=["ugrid"], 118 | package_data={ 119 | "ugrid": [get_runtime_libraries()], 120 | }, 121 | cmdclass={"bdist_wheel": bdist_wheel}, 122 | version=get_version("ugrid/version.py"), 123 | classifiers=["Topic :: Scientific/Engineering :: Mathematics"], 124 | ) 125 | -------------------------------------------------------------------------------- /tests/test_variables.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from ugrid import UGrid 5 | 6 | 7 | def test_get_topology_attributes_names_and_values(): 8 | r"""Tests `topology_get_attributes_names` and `topology_get_attributes_values` can read attributes names and 9 | values of a mesh2d topology.""" 10 | 11 | with UGrid("./data/OneMesh2D.nc", "r") as ug: 12 | # 1. Get the first mesh2D 13 | ugrid_mesh2d = ug.mesh2d_get(0) 14 | # 2. Get the mesh2D attribute names 15 | attribute_names = ug.variable_get_attributes_names(ugrid_mesh2d.name) 16 | # 3. Get the mesh2D attribute values 17 | attribute_values = ug.variable_get_attributes_values(ugrid_mesh2d.name) 18 | assert_array_equal( 19 | attribute_names, 20 | [ 21 | "cf_role", 22 | "edge_coordinates", 23 | "edge_dimension", 24 | "edge_node_connectivity", 25 | "face_coordinates", 26 | "face_dimension", 27 | "face_node_connectivity", 28 | "long_name", 29 | "max_face_nodes_dimension", 30 | "node_coordinates", 31 | "node_dimension", 32 | "topology_dimension", 33 | ], 34 | ) 35 | assert_array_equal( 36 | attribute_values, 37 | [ 38 | "mesh_topology", 39 | "mesh2d_edge_x mesh2d_edge_y", 40 | "mesh2d_nEdges", 41 | "mesh2d_edge_nodes", 42 | "mesh2d_face_x mesh2d_face_y", 43 | "mesh2d_nFaces", 44 | "mesh2d_face_nodes", 45 | "Topology data of 2D mesh", 46 | "mesh2d_nMax_face_nodes", 47 | "mesh2d_node_x mesh2d_node_y", 48 | "mesh2d_nNodes", 49 | "2", 50 | ], 51 | ) 52 | 53 | 54 | def test_get_data_double(): 55 | r"""Tests `variable_get_data_double` gets double data.""" 56 | 57 | with UGrid("./data/ResultFile.nc", "r") as ug: 58 | data_variable = ug.variable_get_data_double("mesh1d_s0") 59 | assert_array_equal(data_variable[:5], [-5.0, -5.0, -5.0, -5.0, -5.0]) 60 | 61 | 62 | def test_get_data_int(): 63 | r"""Tests `variable_get_data_double` gets int data.""" 64 | 65 | with UGrid("./data/ResultFile.nc", "r") as ug: 66 | data_variable = ug.variable_get_data_int("mesh1d_edge_nodes") 67 | assert_array_equal(data_variable[:5], [1, 2, 1, 3, 4]) 68 | 69 | 70 | def test_variable_int_with_attributes_define(): 71 | r"""Tests `variable_int_with_attributes_define` for defining a coordinate reference system.""" 72 | 73 | with UGrid("./data/written_files/CoordinateReferenceSystem.nc", "w+") as ug: 74 | attribute_dict = { 75 | "name": "Unknown projected", 76 | "epsg": np.array([0], dtype=int), 77 | "grid_mapping_name": "Unknown projected", 78 | "longitude_of_prime_meridian": np.array([0.0], dtype=float), 79 | "semi_major_axis": np.array([6378137.0], dtype=float), 80 | "semi_minor_axis": np.array([6356752.314245], dtype=float), 81 | "inverse_flattening": np.array([6356752.314245], dtype=float), 82 | "EPSG_code": "EPSG:0", 83 | "value": "value is equal to EPSG code", 84 | } 85 | ug.variable_int_with_attributes_define( 86 | "projected_coordinate_system", attribute_dict 87 | ) 88 | 89 | 90 | def test_attribute_global_define(): 91 | r"""Tests `attribute_global_define` for defining global attributes, such as the conventions.""" 92 | 93 | with UGrid("./data/written_files/Conventions.nc", "w+") as ug: 94 | conventions = { 95 | "institution": "Deltares", 96 | "references": "Unknown", 97 | "source": "Unknown Unknown. Model: Unknown", 98 | "history": "Created on 2017-11-27T18:05:09+0100, Unknown", 99 | "Conventions": "CF-1.6 UGRID-1.0/Deltares-0.8", 100 | } 101 | ug.attribute_global_define(conventions) 102 | -------------------------------------------------------------------------------- /docs/examples/data_examples/test.pol: -------------------------------------------------------------------------------- 1 | * 2 | * Deltares, RGFGRID Version 6.05.00.70663 (Win64), Mar 04 2021, 08:25:21 3 | * File creation date: 2021-05-27, 16:21:48 4 | * 5 | * Coordinate System = Cartesian 6 | * 7 | L000001 8 | 106 2 9 | 2.0344000E+03 6.3593333E+02 10 | 1.9923861E+03 7.2265000E+02 11 | 1.9503722E+03 8.0936667E+02 12 | 1.9083583E+03 8.9608333E+02 13 | 1.8663444E+03 9.8280000E+02 14 | 1.8243306E+03 1.0695167E+03 15 | 1.7823167E+03 1.1562333E+03 16 | 1.7403028E+03 1.2429500E+03 17 | 1.6982889E+03 1.3296667E+03 18 | 1.6562750E+03 1.4163833E+03 19 | 1.6142611E+03 1.5031000E+03 20 | 1.5722472E+03 1.5898167E+03 21 | 1.5302333E+03 1.6765333E+03 22 | 1.5769528E+03 1.7676194E+03 23 | 1.6236722E+03 1.8587056E+03 24 | 1.6703917E+03 1.9497917E+03 25 | 1.7171111E+03 2.0408778E+03 26 | 1.7638306E+03 2.1319639E+03 27 | 1.8105500E+03 2.2230500E+03 28 | 1.8572694E+03 2.3141361E+03 29 | 1.9039889E+03 2.4052222E+03 30 | 1.9507083E+03 2.4963083E+03 31 | 1.9974278E+03 2.5873944E+03 32 | 2.0441472E+03 2.6784806E+03 33 | 2.0908667E+03 2.7695667E+03 34 | 2.1885630E+03 2.7718074E+03 35 | 2.2862593E+03 2.7740481E+03 36 | 2.3839556E+03 2.7762889E+03 37 | 2.4816519E+03 2.7785296E+03 38 | 2.5793481E+03 2.7807704E+03 39 | 2.6770444E+03 2.7830111E+03 40 | 2.7747407E+03 2.7852519E+03 41 | 2.8724370E+03 2.7874926E+03 42 | 2.9701333E+03 2.7897333E+03 43 | 3.0678296E+03 2.7919741E+03 44 | 3.1655259E+03 2.7942148E+03 45 | 3.2632222E+03 2.7964556E+03 46 | 3.3609185E+03 2.7986963E+03 47 | 3.4586148E+03 2.8009370E+03 48 | 3.5563111E+03 2.8031778E+03 49 | 3.6540074E+03 2.8054185E+03 50 | 3.7517037E+03 2.8076593E+03 51 | 3.8494000E+03 2.8099000E+03 52 | 3.9465667E+03 2.7732333E+03 53 | 4.0437333E+03 2.7365667E+03 54 | 4.1409000E+03 2.6999000E+03 55 | 4.2380667E+03 2.6632333E+03 56 | 4.3352333E+03 2.6265667E+03 57 | 4.4324000E+03 2.5899000E+03 58 | 4.5295667E+03 2.5532333E+03 59 | 4.6267333E+03 2.5165667E+03 60 | 4.7239000E+03 2.4799000E+03 61 | 4.8210667E+03 2.4432333E+03 62 | 4.9182333E+03 2.4065667E+03 63 | 4.9648407E+03 2.3133519E+03 64 | 5.0114481E+03 2.2201370E+03 65 | 5.0580556E+03 2.1269222E+03 66 | 5.1046630E+03 2.0337074E+03 67 | 5.1512704E+03 1.9404926E+03 68 | 5.1978778E+03 1.8472778E+03 69 | 5.2444852E+03 1.7540630E+03 70 | 5.2910926E+03 1.6608481E+03 71 | 5.3377000E+03 1.5676333E+03 72 | 5.3287370E+03 1.4712815E+03 73 | 5.3197741E+03 1.3749296E+03 74 | 5.3108111E+03 1.2785778E+03 75 | 5.3018481E+03 1.1822259E+03 76 | 5.2928852E+03 1.0858741E+03 77 | 5.2839222E+03 9.8952222E+02 78 | 5.2749593E+03 8.9317037E+02 79 | 5.2659963E+03 7.9681852E+02 80 | 5.2570333E+03 7.0046667E+02 81 | 5.1642667E+03 6.5690667E+02 82 | 5.0715000E+03 6.1334667E+02 83 | 4.9787333E+03 5.6978667E+02 84 | 4.8859667E+03 5.2622667E+02 85 | 4.7932000E+03 4.8266667E+02 86 | 4.7004333E+03 4.3910667E+02 87 | 4.6076667E+03 3.9554667E+02 88 | 4.5149000E+03 3.5198667E+02 89 | 4.4221333E+03 3.0842667E+02 90 | 4.3293667E+03 2.6486667E+02 91 | 4.2300458E+03 2.6133750E+02 92 | 4.1307250E+03 2.5780833E+02 93 | 4.0314042E+03 2.5427917E+02 94 | 3.9320833E+03 2.5075000E+02 95 | 3.8327625E+03 2.4722083E+02 96 | 3.7334417E+03 2.4369167E+02 97 | 3.6341208E+03 2.4016250E+02 98 | 3.5348000E+03 2.3663333E+02 99 | 3.4354792E+03 2.3310417E+02 100 | 3.3361583E+03 2.2957500E+02 101 | 3.2368375E+03 2.2604583E+02 102 | 3.1375167E+03 2.2251667E+02 103 | 3.0381958E+03 2.1898750E+02 104 | 2.9388750E+03 2.1545833E+02 105 | 2.8395542E+03 2.1192917E+02 106 | 2.7402333E+03 2.0840000E+02 107 | 2.6520042E+03 2.6184167E+02 108 | 2.5637750E+03 3.1528333E+02 109 | 2.4755458E+03 3.6872500E+02 110 | 2.3873167E+03 4.2216667E+02 111 | 2.2990875E+03 4.7560833E+02 112 | 2.2108583E+03 5.2905000E+02 113 | 2.1226292E+03 5.8249167E+02 114 | 2.0344000E+03 6.3593333E+02 115 | -------------------------------------------------------------------------------- /tests/test_mesh2d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from ugrid import UGrid, UGridMesh2D 5 | 6 | 7 | def create_ugrid_mesh2d(): 8 | r"""Creates an instance of UGridMesh2D to be used for testing""" 9 | 10 | name = "mesh2d" 11 | node_x = np.array([0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3], dtype=np.double) 12 | node_y = np.array([0, 0, 1, 1, 2, 2, 3, 3, 0, 1, 2, 3, 0, 1, 2, 3], dtype=np.double) 13 | edge_node = np.array( 14 | [ 15 | 1, 16 | 2, 17 | 3, 18 | 4, 19 | 5, 20 | 6, 21 | 7, 22 | 8, 23 | 2, 24 | 9, 25 | 4, 26 | 10, 27 | 6, 28 | 11, 29 | 8, 30 | 12, 31 | 9, 32 | 13, 33 | 10, 34 | 14, 35 | 11, 36 | 15, 37 | 12, 38 | 16, 39 | 1, 40 | 3, 41 | 3, 42 | 5, 43 | 5, 44 | 7, 45 | 2, 46 | 4, 47 | 4, 48 | 6, 49 | 6, 50 | 8, 51 | 9, 52 | 10, 53 | 10, 54 | 11, 55 | 11, 56 | 12, 57 | 13, 58 | 14, 59 | 14, 60 | 15, 61 | 15, 62 | 16, 63 | ], 64 | dtype=np.int32, 65 | ) 66 | 67 | face_x = np.array([0.5, 0.5, 0.5, 1.5, 1.5, 1.5, 2.5, 2.5, 2.5], dtype=np.double) 68 | face_y = np.array([0.5, 1.5, 2.5, 0.5, 1.5, 2.5, 0.5, 1.5, 2.5], dtype=np.double) 69 | face_node = np.array( 70 | [ 71 | 1, 72 | 2, 73 | 4, 74 | 3, 75 | 3, 76 | 4, 77 | 6, 78 | 5, 79 | 5, 80 | 6, 81 | 8, 82 | 7, 83 | 2, 84 | 9, 85 | 10, 86 | 4, 87 | 4, 88 | 10, 89 | 11, 90 | 6, 91 | 6, 92 | 11, 93 | 12, 94 | 8, 95 | 9, 96 | 13, 97 | 14, 98 | 10, 99 | 10, 100 | 14, 101 | 15, 102 | 11, 103 | 11, 104 | 15, 105 | 16, 106 | 12, 107 | ], 108 | dtype=np.int32, 109 | ) 110 | 111 | ugrid_mesh2d = UGridMesh2D( 112 | name=name, 113 | node_x=node_x, 114 | node_y=node_y, 115 | edge_node=edge_node, 116 | face_x=face_x, 117 | face_y=face_y, 118 | face_nodes=face_node, 119 | ) 120 | return ugrid_mesh2d 121 | 122 | 123 | def test_ugrid_mesh2d_get(): 124 | r"""Tests `mesh2d_get_num_topologies` and `mesh2d_get` to read a mesh2d from file.""" 125 | 126 | with UGrid("./data/OneMesh2D.nc", "r") as ug: 127 | num_mesh2d_topologies = ug.mesh2d_get_num_topologies() 128 | ugrid_mesh2d = ug.mesh2d_get(num_mesh2d_topologies - 1) 129 | 130 | expected_ugrid_mesh2d = create_ugrid_mesh2d() 131 | 132 | assert expected_ugrid_mesh2d.name == ugrid_mesh2d.name 133 | 134 | assert_array_equal(ugrid_mesh2d.node_x, expected_ugrid_mesh2d.node_x) 135 | assert_array_equal(ugrid_mesh2d.node_y, expected_ugrid_mesh2d.node_y) 136 | assert_array_equal(ugrid_mesh2d.edge_nodes, expected_ugrid_mesh2d.edge_nodes) 137 | 138 | assert_array_equal(ugrid_mesh2d.face_x, expected_ugrid_mesh2d.face_x) 139 | assert_array_equal(ugrid_mesh2d.face_y, expected_ugrid_mesh2d.face_y) 140 | assert_array_equal(ugrid_mesh2d.face_nodes, expected_ugrid_mesh2d.face_nodes) 141 | 142 | 143 | def test_mesh2d_get_attributes(): 144 | actual = UGrid.mesh2d_get_attributes("my-mesh") 145 | expected = { 146 | "cf_role": "mesh_topology", 147 | "long_name": "Topology data of 2D mesh", 148 | "topology_dimension": 2, 149 | "node_dimension": "my-mesh_nNodes", 150 | "node_coordinates": "my-mesh_node_x my-mesh_node_y", 151 | "edge_dimension": "my-mesh_nEdges", 152 | "edge_node_connectivity": "my-mesh_edge_nodes", 153 | "face_dimension": "my-mesh_nFaces", 154 | "face_node_connectivity": "my-mesh_face_nodes", 155 | "max_face_nodes_dimension": "my-mesh_nMax_face_nodes", 156 | "face_coordinates": "my-mesh_face_x my-mesh_face_y", 157 | } 158 | assert actual == expected 159 | -------------------------------------------------------------------------------- /tests/test_mesh1d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from ugrid import UGrid, UGridMesh1D 5 | 6 | 7 | def create_mesh1d(): 8 | r"""Creates an instance of UGridMesh1D to be used for testing""" 9 | 10 | name = "1dmesh" 11 | network_name = "network" 12 | edge_node = np.array( 13 | [ 14 | 0, 15 | 1, 16 | 1, 17 | 2, 18 | 2, 19 | 3, 20 | 3, 21 | 4, 22 | 4, 23 | 5, 24 | 5, 25 | 6, 26 | 6, 27 | 7, 28 | 7, 29 | 8, 30 | 8, 31 | 9, 32 | 9, 33 | 10, 34 | 10, 35 | 11, 36 | 11, 37 | 12, 38 | 12, 39 | 13, 40 | 13, 41 | 14, 42 | 14, 43 | 15, 44 | 15, 45 | 16, 46 | 16, 47 | 17, 48 | 17, 49 | 18, 50 | 18, 51 | 19, 52 | 19, 53 | 20, 54 | 20, 55 | 21, 56 | 21, 57 | 22, 58 | 22, 59 | 23, 60 | 23, 61 | 24, 62 | ], 63 | dtype=np.int32, 64 | ) 65 | node_edge_id = np.array( 66 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 67 | dtype=np.int32, 68 | ) 69 | branch_offset = np.array( 70 | [ 71 | 0, 72 | 49.65, 73 | 99.29, 74 | 148.92, 75 | 198.54, 76 | 248.09, 77 | 297.62, 78 | 347.15, 79 | 396.66, 80 | 446.19, 81 | 495.8, 82 | 545.44, 83 | 595.08, 84 | 644.63, 85 | 694.04, 86 | 743.52, 87 | 793.07, 88 | 842.65, 89 | 892.26, 90 | 941.89, 91 | 991.53, 92 | 1041.17, 93 | 1090.82, 94 | 1140.46, 95 | 1165.29, 96 | ], 97 | dtype=np.double, 98 | ) 99 | node_x = np.empty(node_edge_id.size, dtype=np.double) 100 | node_y = np.empty(node_edge_id.size, dtype=np.double) 101 | edge_edge_id = np.empty(edge_node.size // 2, dtype=np.int32) 102 | edge_edge_offset = np.empty(edge_node.size // 2, dtype=np.double) 103 | edge_x = np.empty(edge_node.size // 2, dtype=np.double) 104 | edge_y = np.empty(edge_node.size // 2, dtype=np.double) 105 | 106 | node_name_id = ["meshnodeids" for _ in range(node_edge_id.size)] 107 | node_name_long = ["meshnodelongnames" for _ in range(node_edge_id.size)] 108 | 109 | mesh1d = UGridMesh1D( 110 | name=name, 111 | network_name=network_name, 112 | node_edge_id=node_edge_id, 113 | node_edge_offset=branch_offset, 114 | node_x=node_x, 115 | node_y=node_y, 116 | edge_node=edge_node, 117 | edge_edge_id=edge_edge_id, 118 | edge_edge_offset=edge_edge_offset, 119 | edge_x=edge_x, 120 | edge_y=edge_y, 121 | node_name_id=node_name_id, 122 | node_name_long=node_name_long, 123 | ) 124 | return mesh1d 125 | 126 | 127 | def test_mesh1d_get(): 128 | r"""Tests `mesh1d_get_num_topologies` and `network1d_get` to read a network1d from file.""" 129 | 130 | with UGrid("./data/AllUGridEntities.nc", "r") as ug: 131 | num_mesh1d_topologies = ug.mesh1d_get_num_topologies() 132 | mesh1d = ug.mesh1d_get(num_mesh1d_topologies - 1) 133 | 134 | expected_mesh1d = create_mesh1d() 135 | 136 | assert expected_mesh1d.name == mesh1d.name 137 | assert expected_mesh1d.network_name == mesh1d.network_name 138 | 139 | assert_array_equal(mesh1d.edge_node, expected_mesh1d.edge_node) 140 | assert_array_equal(mesh1d.node_edge_id, expected_mesh1d.node_edge_id) 141 | assert_array_equal(mesh1d.node_edge_offset, expected_mesh1d.node_edge_offset) 142 | 143 | assert_array_equal(mesh1d.node_name_id, expected_mesh1d.node_name_id) 144 | assert_array_equal(mesh1d.node_name_long, expected_mesh1d.node_name_long) 145 | 146 | 147 | def test_mesh1d_define_and_put(): 148 | r"""Tests `mesh1d_define` and `mesh1d_put` to define and write a mesh1d to file.""" 149 | 150 | with UGrid("./data/written_files/Mesh1DWrite.nc", "w+") as ug: 151 | mesh1d = create_mesh1d() 152 | topology_id = ug.mesh1d_define(mesh1d) 153 | assert topology_id == 0 154 | ug.mesh1d_put(topology_id, mesh1d) 155 | -------------------------------------------------------------------------------- /tests/test_contacts.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from meshkernel import Contacts 3 | from numpy.testing import assert_array_equal 4 | 5 | from ugrid import UGrid, UGridContacts 6 | 7 | 8 | def create_contacts() -> UGridContacts: 9 | r"""Creates an instance of UGridContacts to be used for testing""" 10 | 11 | name = "2d1dlinks" 12 | edges = np.array( 13 | [ 14 | 13, 15 | 1, 16 | 13, 17 | 2, 18 | 13, 19 | 3, 20 | 13, 21 | 4, 22 | 70, 23 | 5, 24 | 76, 25 | 6, 26 | 91, 27 | 7, 28 | 13, 29 | 8, 30 | 13, 31 | 9, 32 | 13, 33 | 10, 34 | 13, 35 | 11, 36 | 13, 37 | 12, 38 | 178, 39 | 13, 40 | 200, 41 | 14, 42 | 228, 43 | 15, 44 | 255, 45 | 16, 46 | 277, 47 | 17, 48 | 293, 49 | 18, 50 | 304, 51 | 19, 52 | 315, 53 | 20, 54 | 326, 55 | 21, 56 | 337, 57 | 22, 58 | 353, 59 | 23, 60 | ], 61 | dtype=np.int32, 62 | ) 63 | contact_type = np.array( 64 | [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], 65 | dtype=np.int32, 66 | ) 67 | 68 | mesh_from_name = "mesh2d" 69 | mesh_to_name = "1dmesh" 70 | mesh_from_location = 0 71 | mesh_to_location = 0 72 | contact_name_id = ["linkid" for _ in range(edges.size // 2)] 73 | contact_name_long = ["linklongname" for _ in range(edges.size // 2)] 74 | 75 | ugrid_contacts = UGridContacts( 76 | name=name, 77 | edges=edges, 78 | contact_type=contact_type, 79 | mesh_from_name=mesh_from_name, 80 | mesh_to_name=mesh_to_name, 81 | mesh_from_location=mesh_from_location, 82 | mesh_to_location=mesh_to_location, 83 | contact_name_id=contact_name_id, 84 | contact_name_long=contact_name_long, 85 | ) 86 | return ugrid_contacts 87 | 88 | 89 | def test_contacts_get(): 90 | r"""Tests `contacts_get_num_topologies` and `contacts_get` to read a contacts from file.""" 91 | 92 | with UGrid("./data/AllUGridEntities.nc", "r") as ug: 93 | num_contacts_topologies = ug.contacts_get_num_topologies() 94 | ugrid_contacts = ug.contacts_get(num_contacts_topologies - 1) 95 | 96 | expected_contacts = create_contacts() 97 | 98 | assert expected_contacts.name == ugrid_contacts.name 99 | assert expected_contacts.mesh_from_name == ugrid_contacts.mesh_from_name 100 | assert expected_contacts.mesh_to_name == ugrid_contacts.mesh_to_name 101 | 102 | assert expected_contacts.mesh_from_location == ugrid_contacts.mesh_from_location 103 | assert expected_contacts.mesh_to_location == ugrid_contacts.mesh_to_location 104 | 105 | assert_array_equal(ugrid_contacts.contact_type, expected_contacts.contact_type) 106 | assert_array_equal(ugrid_contacts.edges, expected_contacts.edges) 107 | 108 | 109 | def test_contacts_define_and_put(): 110 | r"""Tests `contacts_define` and `contacts_put` to define and write a contacts to file.""" 111 | 112 | with UGrid("./data/written_files/ContactsWrite.nc", "w+") as ug: 113 | ugrid_contacts = create_contacts() 114 | topology_id = ug.contacts_define(ugrid_contacts) 115 | assert topology_id == 0 116 | ug.contacts_put(topology_id, ugrid_contacts) 117 | 118 | 119 | def test_contacts_meshkernel_define_and_put(): 120 | r"""Tests a meshkernel contacts is correctly converted to UGridContacts and written to file.""" 121 | mesh1d_indices = np.array([0, 1, 2], dtype=np.int32) 122 | mesh2d_indices = np.array([0, 1, 2], dtype=np.int32) 123 | 124 | contacts = Contacts(mesh1d_indices=mesh1d_indices, mesh2d_indices=mesh2d_indices) 125 | 126 | contact_type = np.array([3, 3, 3], dtype=np.int32) 127 | contact_name_id = ["linkid" for _ in range(mesh1d_indices.size)] 128 | contact_name_long = ["linklongname" for _ in range(mesh1d_indices.size)] 129 | 130 | with UGrid("./data/written_files/Mesh2DMesKernelWrite.nc", "w+") as ug: 131 | ugrid_contacts = ug.from_meshkernel_contacts_to_ugrid_contacts( 132 | contacts=contacts, 133 | name="contacts", 134 | contact_type=contact_type, 135 | contact_name_id=contact_name_id, 136 | contact_name_long=contact_name_long, 137 | mesh_from_name="mesh2d", 138 | mesh_to_name="mesh1d", 139 | mesh_from_location=0, 140 | mesh_to_location=0, 141 | ) 142 | 143 | topology_id = ug.contacts_define(ugrid_contacts) 144 | assert topology_id == 0 145 | ug.contacts_put(topology_id, ugrid_contacts) 146 | -------------------------------------------------------------------------------- /tests/test_network1d.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from ugrid import UGrid, UGridNetwork1D 5 | 6 | 7 | def create_network1d(): 8 | r"""Creates an instance of UGridNetwork1D to be used for testing""" 9 | 10 | node_x = np.array([293.78, 538.89], dtype=np.double) 11 | node_y = np.array([27.48, 956.75], dtype=np.double) 12 | edge_node = np.array([0, 1], dtype=np.int32) 13 | edge_length = np.array([1165.29], dtype=np.double) 14 | branch_order = np.array([0], dtype=np.int32) 15 | 16 | geometry_nodes_x = np.array( 17 | [ 18 | 293.78, 19 | 278.97, 20 | 265.31, 21 | 254.17, 22 | 247.44, 23 | 248.3, 24 | 259.58, 25 | 282.24, 26 | 314.61, 27 | 354.44, 28 | 398.94, 29 | 445, 30 | 490.6, 31 | 532.84, 32 | 566.64, 33 | 589.08, 34 | 600.72, 35 | 603.53, 36 | 599.27, 37 | 590.05, 38 | 577.56, 39 | 562.97, 40 | 547.12, 41 | 530.67, 42 | 538.89, 43 | ], 44 | dtype=np.double, 45 | ) 46 | 47 | geometry_nodes_y = np.array( 48 | [ 49 | 27.48, 50 | 74.87, 51 | 122.59, 52 | 170.96, 53 | 220.12, 54 | 269.67, 55 | 317.89, 56 | 361.93, 57 | 399.39, 58 | 428.84, 59 | 450.76, 60 | 469.28, 61 | 488.89, 62 | 514.78, 63 | 550.83, 64 | 594.93, 65 | 643.09, 66 | 692.6, 67 | 742.02, 68 | 790.79, 69 | 838.83, 70 | 886.28, 71 | 933.33, 72 | 980.17, 73 | 956.75, 74 | ], 75 | dtype=np.double, 76 | ) 77 | 78 | num_branch_geometry_nodes = np.array([len(geometry_nodes_x)], dtype=np.int32) 79 | 80 | network1d = UGridNetwork1D( 81 | name="network", 82 | node_x=node_x, 83 | node_y=node_y, 84 | edge_node=edge_node, 85 | edge_length=edge_length, 86 | edge_order=branch_order, 87 | geometry_nodes_x=geometry_nodes_x, 88 | geometry_nodes_y=geometry_nodes_y, 89 | num_edge_geometry_nodes=num_branch_geometry_nodes, 90 | ) 91 | 92 | network1d.node_id = ["nodesids", "nodesids"] 93 | network1d.node_long_name = ["nodeslongNames", "nodeslongNames"] 94 | network1d.edge_id = ["branchids"] 95 | network1d.edge_long_name = ["branchlongNames"] 96 | 97 | return network1d 98 | 99 | 100 | def test_network1d_get(): 101 | r"""Tests `network1d_get_num_topologies` and `network1d_get` to read a network1d from file.""" 102 | 103 | with UGrid("./data/AllUGridEntities.nc", "r") as ug: 104 | num_network_topologies = ug.network1d_get_num_topologies() 105 | network1d = ug.network1d_get(num_network_topologies - 1) 106 | 107 | expected_network1d = create_network1d() 108 | 109 | assert expected_network1d.name == network1d.name 110 | 111 | assert_array_equal(network1d.node_id, expected_network1d.node_id) 112 | assert_array_equal(network1d.node_long_name, expected_network1d.node_long_name) 113 | 114 | assert_array_equal(network1d.edge_id, expected_network1d.edge_id) 115 | assert_array_equal(network1d.edge_long_name, expected_network1d.edge_long_name) 116 | 117 | assert_array_equal(network1d.node_x, expected_network1d.node_x) 118 | assert_array_equal(network1d.node_y, expected_network1d.node_y) 119 | 120 | assert_array_equal(network1d.edge_node, expected_network1d.edge_node) 121 | assert_array_equal(network1d.edge_length, expected_network1d.edge_length) 122 | 123 | assert_array_equal( 124 | network1d.geometry_nodes_x, expected_network1d.geometry_nodes_x 125 | ) 126 | assert_array_equal( 127 | network1d.geometry_nodes_y, expected_network1d.geometry_nodes_y 128 | ) 129 | 130 | 131 | def test_network1d_define_and_put(): 132 | r"""Tests `network1d_define` and `network1d_put` to define and write a mesh1d to file.""" 133 | 134 | with UGrid("./data/written_files/Network1DWrite.nc", "w+") as ug: 135 | network1d = create_network1d() 136 | topology_id = ug.network1d_define(network1d) 137 | assert topology_id == 0 138 | ug.network1d_put(topology_id, network1d) 139 | 140 | 141 | def test_network1d_get_attributes(): 142 | actual = UGrid.network1d_get_attributes("my-network") 143 | expected = { 144 | "cf_role": "mesh_topology", 145 | "long_name": "Topology data of 1D network", 146 | "topology_dimension": 1, 147 | "node_dimension": "my-network_nNodes", 148 | "node_coordinates": "my-network_node_x my-network_node_y", 149 | "node_id": "my-network_node_id", 150 | "node_long_name": "my-network_node_long_name", 151 | "edge_dimension": "my-network_nEdges", 152 | "edge_node_connectivity": "my-network_edge_nodes", 153 | "edge_length": "my-network_edge_length", 154 | "edge_id": "my-network_edge_id", 155 | "edge_long_name": "my-network_edge_long_name", 156 | "edge_geometry": "my-network_edge_geometry", 157 | } 158 | assert actual == expected 159 | -------------------------------------------------------------------------------- /docs/examples/03_mesh1d_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UGrid Mesh1D basics\n", 8 | "\n", 9 | "This is the basic introduction for using the `ugridpy` library.\n", 10 | "\n", 11 | "`ugridpy` can be used for reading and writing mesh1D. \n", 12 | "\n", 13 | "At the very beginning, the necessary libraries have to be imported." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "text/plain": [ 24 | "'0.9.0'" 25 | ] 26 | }, 27 | "execution_count": 1, 28 | "metadata": {}, 29 | "output_type": "execute_result" 30 | } 31 | ], 32 | "source": [ 33 | "from ugrid import UGrid, UGridMesh1D\n", 34 | "from ugrid.version import __version__\n", 35 | "__version__" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Other imports" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "import numpy as np" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "## Mesh1D reading" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | " with UGrid(\"./data_examples/AllUGridEntities.nc\", \"r\") as ug:\n", 68 | " num_mesh1d_topologies = ug.mesh1d_get_num_topologies()\n", 69 | " mesh1d = ug.mesh1d_get(num_mesh1d_topologies - 1)" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "### Print some data" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "Mesh 1D name: 1dmesh\n", 89 | "Mesh 1D network name : network\n", 90 | "Mesh 2D first 10 elements of edge_node: [0 1 1 2 2 3 3 4 4 5]\n" 91 | ] 92 | } 93 | ], 94 | "source": [ 95 | "print(f\"Mesh 1D name: {mesh1d.name}\")\n", 96 | "print(f\"Mesh 1D network name : {mesh1d.network_name}\")\n", 97 | "print(f\"Mesh 2D first 10 elements of edge_node: {mesh1d.edge_node[:10]}\")" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Mesh1D writing" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "### Define Mesh1D arrays" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 5, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "name = \"1dmesh\"\n", 121 | "network_name = \"network\"\n", 122 | "edge_node = np.array(\n", 123 | " [\n", 124 | " 0,\n", 125 | " 1,\n", 126 | " 1,\n", 127 | " 2,\n", 128 | " 2,\n", 129 | " 3,\n", 130 | " 3,\n", 131 | " 4,\n", 132 | " 4,\n", 133 | " 5,\n", 134 | " 5,\n", 135 | " 6,\n", 136 | " 6,\n", 137 | " 7,\n", 138 | " 7,\n", 139 | " 8,\n", 140 | " 8,\n", 141 | " 9,\n", 142 | " 9,\n", 143 | " 10,\n", 144 | " 10,\n", 145 | " 11,\n", 146 | " 11,\n", 147 | " 12,\n", 148 | " 12,\n", 149 | " 13,\n", 150 | " 13,\n", 151 | " 14,\n", 152 | " 14,\n", 153 | " 15,\n", 154 | " 15,\n", 155 | " 16,\n", 156 | " 16,\n", 157 | " 17,\n", 158 | " 17,\n", 159 | " 18,\n", 160 | " 18,\n", 161 | " 19,\n", 162 | " 19,\n", 163 | " 20,\n", 164 | " 20,\n", 165 | " 21,\n", 166 | " 21,\n", 167 | " 22,\n", 168 | " 22,\n", 169 | " 23,\n", 170 | " 23,\n", 171 | " 24,\n", 172 | " ],\n", 173 | " dtype=np.int32,\n", 174 | ")\n", 175 | "node_edge_id = np.array(\n", 176 | " [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n", 177 | " dtype=np.int32,\n", 178 | ")\n", 179 | "node_edge_offset = np.array(\n", 180 | " [\n", 181 | " 0,\n", 182 | " 49.65,\n", 183 | " 99.29,\n", 184 | " 148.92,\n", 185 | " 198.54,\n", 186 | " 248.09,\n", 187 | " 297.62,\n", 188 | " 347.15,\n", 189 | " 396.66,\n", 190 | " 446.19,\n", 191 | " 495.8,\n", 192 | " 545.44,\n", 193 | " 595.08,\n", 194 | " 644.63,\n", 195 | " 694.04,\n", 196 | " 743.52,\n", 197 | " 793.07,\n", 198 | " 842.65,\n", 199 | " 892.26,\n", 200 | " 941.89,\n", 201 | " 991.53,\n", 202 | " 1041.17,\n", 203 | " 1090.82,\n", 204 | " 1140.46,\n", 205 | " 1165.29,\n", 206 | " ],\n", 207 | " dtype=np.double,\n", 208 | ")\n", 209 | "node_x = np.empty(node_edge_id.size, dtype=np.double)\n", 210 | "node_y = np.empty(node_edge_id.size, dtype=np.double)\n", 211 | "edge_edge_id = np.empty(edge_node.size // 2, dtype=np.int32)\n", 212 | "edge_edge_offset = np.empty(edge_node.size // 2, dtype=np.double)\n", 213 | "edge_x = np.empty(edge_node.size // 2, dtype=np.double)\n", 214 | "edge_y = np.empty(edge_node.size // 2, dtype=np.double)\n", 215 | "\n", 216 | "node_name_id = [\"meshnodeids\" for _ in range(node_edge_id.size)]\n", 217 | "node_name_long = [\"meshnodelongnames\" for _ in range(node_edge_id.size)]" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "### Instantiate UGridMesh1D class" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": 6, 230 | "metadata": {}, 231 | "outputs": [], 232 | "source": [ 233 | "mesh1d = UGridMesh1D(\n", 234 | " name=name,\n", 235 | " network_name=network_name,\n", 236 | " node_edge_id=node_edge_id,\n", 237 | " node_edge_offset=node_edge_offset,\n", 238 | " node_x=node_x,\n", 239 | " node_y=node_y,\n", 240 | " edge_node=edge_node,\n", 241 | " edge_edge_id=edge_edge_id,\n", 242 | " edge_edge_offset=edge_edge_offset,\n", 243 | " edge_x=edge_x,\n", 244 | " edge_y=edge_y,\n", 245 | " node_name_id=node_name_id,\n", 246 | " node_name_long=node_name_long,\n", 247 | ")" 248 | ] 249 | }, 250 | { 251 | "cell_type": "markdown", 252 | "metadata": {}, 253 | "source": [ 254 | "### Write UGridMesh1D instance to file" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": 7, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [ 263 | "with UGrid(\"Mesh1DWrite.nc\", \"w+\") as ug:\n", 264 | " # 1. Define a new mesh1d\n", 265 | " topology_id = ug.mesh1d_define(mesh1d)\n", 266 | " # 2. Write a new mesh1d\n", 267 | " ug.mesh1d_put(topology_id, mesh1d)\n", 268 | " # 3. Add crs to file\n", 269 | " attribute_dict = {\n", 270 | " \"name\": \"Unknown projected\",\n", 271 | " \"epsg\": np.array([0], dtype=int),\n", 272 | " \"grid_mapping_name\": \"Unknown projected\",\n", 273 | " \"longitude_of_prime_meridian\": np.array([0.0], dtype=float),\n", 274 | " \"semi_major_axis\": np.array([6378137.0], dtype=float),\n", 275 | " \"semi_minor_axis\": np.array([6356752.314245], dtype=float),\n", 276 | " \"inverse_flattening\": np.array([6356752.314245], dtype=float),\n", 277 | " \"EPSG_code\": \"EPSG:0\",\n", 278 | " \"value\": \"value is equal to EPSG code\"}\n", 279 | " ug.variable_int_with_attributes_define(\"projected_coordinate_system\", attribute_dict)\n", 280 | " # 4. Add conventions (global attributes)\n", 281 | " conventions = {\n", 282 | " \"institution\": \"Deltares\",\n", 283 | " \"references\": \"Unknown\",\n", 284 | " \"source\": \"Unknown Unknown. Model: Unknown\",\n", 285 | " \"history\": \"Created on 2017-11-27T18:05:09+0100, Unknown\",\n", 286 | " \"Conventions\": \"CF-1.6 UGRID-1.0/Deltares-0.8\"}\n", 287 | " ug.attribute_global_define(conventions)" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": null, 293 | "metadata": {}, 294 | "outputs": [], 295 | "source": [] 296 | } 297 | ], 298 | "metadata": { 299 | "kernelspec": { 300 | "display_name": "Python 3", 301 | "language": "python", 302 | "name": "python3" 303 | }, 304 | "language_info": { 305 | "codemirror_mode": { 306 | "name": "ipython", 307 | "version": 3 308 | }, 309 | "file_extension": ".py", 310 | "mimetype": "text/x-python", 311 | "name": "python", 312 | "nbconvert_exporter": "python", 313 | "pygments_lexer": "ipython3", 314 | "version": "3.8.6" 315 | } 316 | }, 317 | "nbformat": 4, 318 | "nbformat_minor": 4 319 | } 320 | -------------------------------------------------------------------------------- /docs/examples/02_mesh2d_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UGrid Mesh2D basics\n", 8 | "\n", 9 | "This is the basic introduction for using the `ugridpy` library.\n", 10 | "\n", 11 | "`ugridpy` can be used for reading and writing mesh2D. \n", 12 | "\n", 13 | "At the very beginning, the necessary libraries have to be imported." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "text/plain": [ 24 | "'0.13.0'" 25 | ] 26 | }, 27 | "execution_count": 1, 28 | "metadata": {}, 29 | "output_type": "execute_result" 30 | } 31 | ], 32 | "source": [ 33 | "from ugrid import UGrid, UGridMesh2D\n", 34 | "from ugrid.version import __version__\n", 35 | "__version__" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Other imports" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "import numpy as np" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "## Mesh2D reading" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "with UGrid(\"./data_examples/AllUGridEntities.nc\", \"r\") as ug:\n", 68 | " # 1. Count the number of mesh2d topologies\n", 69 | " num_mesh2d_topologies = ug.mesh2d_get_num_topologies()\n", 70 | " # 2. Get the data of the last mesh2d in the list\n", 71 | " mesh2d = ug.mesh2d_get(num_mesh2d_topologies - 1)" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "### Print some data" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 6, 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "name": "stdout", 88 | "output_type": "stream", 89 | "text": [ 90 | "Mesh 2D name: mesh2d\n", 91 | "Mesh 2D first 10 elements of node_x: [480.21459667 451.69658827 425.13756519 401.45064545 380.26184868\n", 92 | " 361.45101045 343.94589513 261.25486588 242.90975278 222.99468601]\n", 93 | "Mesh 2D first 10 elements of node_y: [82.29153983 73.89186051 66.06917971 59.09244673 52.87717774 47.40762474\n", 94 | " 42.38109201 19.25716325 14.35937578 9.08769854]\n", 95 | "Mesh 2D edge_node: [ 20 33 46 ... 451 451 452]\n", 96 | "Mesh 2D face_node: [ 79 132 80 ... 451 452 439]\n", 97 | "Mesh 2D face_edge: [1890582864 568 1981989360 ... 0 0 0]\n" 98 | ] 99 | } 100 | ], 101 | "source": [ 102 | "print(f\"Mesh 2D name: {mesh2d.name}\")\n", 103 | "print(f\"Mesh 2D first 10 elements of node_x: {mesh2d.node_x[:10]}\")\n", 104 | "print(f\"Mesh 2D first 10 elements of node_y: {mesh2d.node_y[:10]}\")\n", 105 | "print(f\"Mesh 2D edge_node: {mesh2d.edge_node}\")\n", 106 | "print(f\"Mesh 2D face_node: {mesh2d.face_node}\")\n", 107 | "print(f\"Mesh 2D face_edge: {mesh2d.face_edge}\")" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": {}, 113 | "source": [ 114 | "## Mesh2D writing" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "### Define Mesh2D arrays" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 5, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "name = \"mesh2d\"\n", 131 | "node_x = np.array([0, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3], dtype=np.double)\n", 132 | "node_y = np.array([0, 0, 1, 1, 2, 2, 3, 3, 0, 1, 2, 3, 0, 1, 2, 3], dtype=np.double)\n", 133 | "edge_node = np.array(\n", 134 | " [\n", 135 | " 1,\n", 136 | " 2,\n", 137 | " 3,\n", 138 | " 4,\n", 139 | " 5,\n", 140 | " 6,\n", 141 | " 7,\n", 142 | " 8,\n", 143 | " 2,\n", 144 | " 9,\n", 145 | " 4,\n", 146 | " 10,\n", 147 | " 6,\n", 148 | " 11,\n", 149 | " 8,\n", 150 | " 12,\n", 151 | " 9,\n", 152 | " 13,\n", 153 | " 10,\n", 154 | " 14,\n", 155 | " 11,\n", 156 | " 15,\n", 157 | " 12,\n", 158 | " 16,\n", 159 | " 1,\n", 160 | " 3,\n", 161 | " 3,\n", 162 | " 5,\n", 163 | " 5,\n", 164 | " 7,\n", 165 | " 2,\n", 166 | " 4,\n", 167 | " 4,\n", 168 | " 6,\n", 169 | " 6,\n", 170 | " 8,\n", 171 | " 9,\n", 172 | " 10,\n", 173 | " 10,\n", 174 | " 11,\n", 175 | " 11,\n", 176 | " 12,\n", 177 | " 13,\n", 178 | " 14,\n", 179 | " 14,\n", 180 | " 15,\n", 181 | " 15,\n", 182 | " 16,\n", 183 | " ],\n", 184 | " dtype=np.int32,\n", 185 | ")\n", 186 | "\n", 187 | "face_x = np.array([0.5, 0.5, 0.5, 1.5, 1.5, 1.5, 2.5, 2.5, 2.5], dtype=np.double)\n", 188 | "face_y = np.array([0.5, 1.5, 2.5, 0.5, 1.5, 2.5, 0.5, 1.5, 2.5], dtype=np.double)\n", 189 | "face_node = np.array(\n", 190 | " [\n", 191 | " 1,\n", 192 | " 2,\n", 193 | " 4,\n", 194 | " 3,\n", 195 | " 3,\n", 196 | " 4,\n", 197 | " 6,\n", 198 | " 5,\n", 199 | " 5,\n", 200 | " 6,\n", 201 | " 8,\n", 202 | " 7,\n", 203 | " 2,\n", 204 | " 9,\n", 205 | " 10,\n", 206 | " 4,\n", 207 | " 4,\n", 208 | " 10,\n", 209 | " 11,\n", 210 | " 6,\n", 211 | " 6,\n", 212 | " 11,\n", 213 | " 12,\n", 214 | " 8,\n", 215 | " 9,\n", 216 | " 13,\n", 217 | " 14,\n", 218 | " 10,\n", 219 | " 10,\n", 220 | " 14,\n", 221 | " 15,\n", 222 | " 11,\n", 223 | " 11,\n", 224 | " 15,\n", 225 | " 16,\n", 226 | " 12,\n", 227 | " ],\n", 228 | " dtype=np.int32,\n", 229 | ")" 230 | ] 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "### Instantiate UGridMesh2D class" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 6, 242 | "metadata": {}, 243 | "outputs": [], 244 | "source": [ 245 | "mesh2d = UGridMesh2D(\n", 246 | " name=name,\n", 247 | " node_x=node_x,\n", 248 | " node_y=node_y,\n", 249 | " edge_node=edge_node,\n", 250 | " face_x=face_x,\n", 251 | " face_y=face_y,\n", 252 | " face_node=face_node,\n", 253 | " start_index=1\n", 254 | ")" 255 | ] 256 | }, 257 | { 258 | "cell_type": "markdown", 259 | "metadata": {}, 260 | "source": [ 261 | "### Write UGridMesh2D instance to file" 262 | ] 263 | }, 264 | { 265 | "cell_type": "code", 266 | "execution_count": 7, 267 | "metadata": {}, 268 | "outputs": [], 269 | "source": [ 270 | "with UGrid(\"Mesh2DWrite.nc\", \"w+\") as ug:\n", 271 | " # 1. Define a new network1d\n", 272 | " topology_id = ug.mesh2d_define(mesh2d)\n", 273 | " # 2. Write a new network1d\n", 274 | " ug.mesh2d_put(topology_id, mesh2d)\n", 275 | " # 3. Add crs to file\n", 276 | " attribute_dict = {\n", 277 | " \"name\": \"Unknown projected\",\n", 278 | " \"epsg\": np.array([0], dtype=int),\n", 279 | " \"grid_mapping_name\": \"Unknown projected\",\n", 280 | " \"longitude_of_prime_meridian\": np.array([0.0], dtype=float),\n", 281 | " \"semi_major_axis\": np.array([6378137.0], dtype=float),\n", 282 | " \"semi_minor_axis\": np.array([6356752.314245], dtype=float),\n", 283 | " \"inverse_flattening\": np.array([6356752.314245], dtype=float),\n", 284 | " \"EPSG_code\": \"EPSG:0\",\n", 285 | " \"value\": \"value is equal to EPSG code\"}\n", 286 | " ug.variable_int_with_attributes_define(\"projected_coordinate_system\", attribute_dict)\n", 287 | " # 4. Add conventions (global attributes)\n", 288 | " conventions = {\n", 289 | " \"institution\": \"Deltares\",\n", 290 | " \"references\": \"Unknown\",\n", 291 | " \"source\": \"Unknown Unknown. Model: Unknown\",\n", 292 | " \"history\": \"Created on 2017-11-27T18:05:09+0100, Unknown\",\n", 293 | " \"Conventions\": \"CF-1.6 UGRID-1.0/Deltares-0.8\"}\n", 294 | " ug.attribute_global_define(conventions)" 295 | ] 296 | }, 297 | { 298 | "cell_type": "code", 299 | "execution_count": null, 300 | "metadata": {}, 301 | "outputs": [], 302 | "source": [] 303 | } 304 | ], 305 | "metadata": { 306 | "kernelspec": { 307 | "display_name": "Python 3", 308 | "language": "python", 309 | "name": "python3" 310 | }, 311 | "language_info": { 312 | "codemirror_mode": { 313 | "name": "ipython", 314 | "version": 3 315 | }, 316 | "file_extension": ".py", 317 | "mimetype": "text/x-python", 318 | "name": "python", 319 | "nbconvert_exporter": "python", 320 | "pygments_lexer": "ipython3", 321 | "version": "3.8.6" 322 | } 323 | }, 324 | "nbformat": 4, 325 | "nbformat_minor": 5 326 | } 327 | -------------------------------------------------------------------------------- /docs/examples/01_network1d_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UGrid Network1d basics\n", 8 | "\n", 9 | "This is the basic introduction for using the `ugridpy` library.\n", 10 | "\n", 11 | "`ugridpy` can be used for reading and writing network1d. \n", 12 | "\n", 13 | "At the very beginning, the necessary libraries have to be imported." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 8, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "text/plain": [ 24 | "'0.13.0'" 25 | ] 26 | }, 27 | "execution_count": 8, 28 | "metadata": {}, 29 | "output_type": "execute_result" 30 | } 31 | ], 32 | "source": [ 33 | "from ugrid import UGrid, UGridNetwork1D\n", 34 | "from ugrid.version import __version__\n", 35 | "__version__" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Other imports" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 9, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "import numpy as np" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "## Network1d reading" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 10, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "with UGrid(\"./data_examples/AllUGridEntities.nc\", \"r\") as ug:\n", 68 | " # 1. Count the number of network topologies\n", 69 | " num_network_topologies = ug.network1d_get_num_topologies()\n", 70 | " # 2. Get the data of the last network in the list\n", 71 | " network1d = ug.network1d_get(num_network_topologies - 1)" 72 | ] 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "metadata": {}, 77 | "source": [ 78 | "### Print some data" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 11, 84 | "metadata": {}, 85 | "outputs": [ 86 | { 87 | "name": "stdout", 88 | "output_type": "stream", 89 | "text": [ 90 | "Network 1D name: network\n", 91 | "Network 1D num branch geometry nodes: [1918854755]\n", 92 | "Network 1D branch nodes: [0 1]\n", 93 | "Network 1D node x: [293.78 538.89]\n", 94 | "Network 1D node y: [ 27.48 956.75]\n", 95 | "Network 1D geometry nodes x: [293.78 278.97 265.31 254.17 247.44 248.3 259.58 282.24 314.61 354.44\n", 96 | " 398.94 445. 490.6 532.84 566.64 589.08 600.72 603.53 599.27 590.05\n", 97 | " 577.56 562.97 547.12 530.67 538.89]\n", 98 | "Network 1D geometry nodes y: [ 27.48 74.87 122.59 170.96 220.12 269.67 317.89 361.93 399.39 428.84\n", 99 | " 450.76 469.28 488.89 514.78 550.83 594.93 643.09 692.6 742.02 790.79\n", 100 | " 838.83 886.28 933.33 980.17 956.75]\n", 101 | "Network 1D node name id: ['nodesids', 'nodesids']\n", 102 | "Network 1D node name long: ['nodeslongNames', 'nodeslongNames']\n", 103 | "Network 1D is spherical: False\n" 104 | ] 105 | } 106 | ], 107 | "source": [ 108 | "print(f\"Network 1D name: {network1d.name}\")\n", 109 | "print(f\"Network 1D num branch geometry nodes: {network1d.num_edge_geometry_nodes}\")\n", 110 | "print(f\"Network 1D branch nodes: {network1d.edge_node}\")\n", 111 | "print(f\"Network 1D node x: {network1d.node_x}\")\n", 112 | "print(f\"Network 1D node y: {network1d.node_y}\")\n", 113 | "print(f\"Network 1D geometry nodes x: {network1d.geometry_nodes_x}\")\n", 114 | "print(f\"Network 1D geometry nodes y: {network1d.geometry_nodes_y}\")\n", 115 | "print(f\"Network 1D node name id: {network1d.node_id}\")\n", 116 | "print(f\"Network 1D node name long: {network1d.node_long_name}\")\n", 117 | "print(f\"Network 1D is spherical: {network1d.is_spherical}\")" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "## Network1d writing" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "metadata": {}, 130 | "source": [ 131 | "### Define Network1d arrays" 132 | ] 133 | }, 134 | { 135 | "cell_type": "code", 136 | "execution_count": 5, 137 | "metadata": {}, 138 | "outputs": [], 139 | "source": [ 140 | "node_x = np.array([293.78, 538.89], dtype=np.double)\n", 141 | "node_y = np.array([27.48, 956.75], dtype=np.double)\n", 142 | "branch_node = np.array([0, 1], dtype=np.int32)\n", 143 | "branch_length = np.array([1165.29], dtype=np.double)\n", 144 | "branch_order = np.array([0], dtype=np.int32)\n", 145 | "\n", 146 | "geometry_nodes_x = np.array(\n", 147 | " [\n", 148 | " 293.78,\n", 149 | " 278.97,\n", 150 | " 265.31,\n", 151 | " 254.17,\n", 152 | " 247.44,\n", 153 | " 248.3,\n", 154 | " 259.58,\n", 155 | " 282.24,\n", 156 | " 314.61,\n", 157 | " 354.44,\n", 158 | " 398.94,\n", 159 | " 445,\n", 160 | " 490.6,\n", 161 | " 532.84,\n", 162 | " 566.64,\n", 163 | " 589.08,\n", 164 | " 600.72,\n", 165 | " 603.53,\n", 166 | " 599.27,\n", 167 | " 590.05,\n", 168 | " 577.56,\n", 169 | " 562.97,\n", 170 | " 547.12,\n", 171 | " 530.67,\n", 172 | " 538.89,\n", 173 | " ],\n", 174 | " dtype=np.double,\n", 175 | ")\n", 176 | "\n", 177 | "geometry_nodes_y = np.array(\n", 178 | " [\n", 179 | " 27.48,\n", 180 | " 74.87,\n", 181 | " 122.59,\n", 182 | " 170.96,\n", 183 | " 220.12,\n", 184 | " 269.67,\n", 185 | " 317.89,\n", 186 | " 361.93,\n", 187 | " 399.39,\n", 188 | " 428.84,\n", 189 | " 450.76,\n", 190 | " 469.28,\n", 191 | " 488.89,\n", 192 | " 514.78,\n", 193 | " 550.83,\n", 194 | " 594.93,\n", 195 | " 643.09,\n", 196 | " 692.6,\n", 197 | " 742.02,\n", 198 | " 790.79,\n", 199 | " 838.83,\n", 200 | " 886.28,\n", 201 | " 933.33,\n", 202 | " 980.17,\n", 203 | " 956.75,\n", 204 | " ],\n", 205 | " dtype=np.double,\n", 206 | ")\n", 207 | "\n", 208 | "num_branch_geometry_nodes = np.array([len(geometry_nodes_x)], dtype=np.int32)" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "### Instantiate UGridNetwork1D class" 216 | ] 217 | }, 218 | { 219 | "cell_type": "code", 220 | "execution_count": 6, 221 | "metadata": {}, 222 | "outputs": [], 223 | "source": [ 224 | "network1d = UGridNetwork1D(\n", 225 | " name=\"network\",\n", 226 | " node_x=node_x,\n", 227 | " node_y=node_y,\n", 228 | " edge_node=branch_node,\n", 229 | " edge_length=branch_length,\n", 230 | " edge_order=branch_order,\n", 231 | " geometry_nodes_x=geometry_nodes_x,\n", 232 | " geometry_nodes_y=geometry_nodes_y,\n", 233 | " num_edge_geometry_nodes=num_branch_geometry_nodes,\n", 234 | ")" 235 | ] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "metadata": {}, 240 | "source": [ 241 | "### Write the UGridNetwork1D instance to file and add crs and conventions" 242 | ] 243 | }, 244 | { 245 | "cell_type": "code", 246 | "execution_count": 7, 247 | "metadata": {}, 248 | "outputs": [], 249 | "source": [ 250 | "with UGrid(\"Network1DWrite.nc\", \"w+\") as ug:\n", 251 | " # 1. Define a new network1d\n", 252 | " topology_id = ug.network1d_define(network1d)\n", 253 | " # 2. Write network1d\n", 254 | " ug.network1d_put(topology_id, network1d)\n", 255 | " # 3. Add crs to file\n", 256 | " attribute_dict = {\n", 257 | " \"name\": \"Unknown projected\",\n", 258 | " \"epsg\": np.array([0], dtype=int),\n", 259 | " \"grid_mapping_name\": \"Unknown projected\",\n", 260 | " \"longitude_of_prime_meridian\": np.array([0.0], dtype=float),\n", 261 | " \"semi_major_axis\": np.array([6378137.0], dtype=float),\n", 262 | " \"semi_minor_axis\": np.array([6356752.314245], dtype=float),\n", 263 | " \"inverse_flattening\": np.array([6356752.314245], dtype=float),\n", 264 | " \"EPSG_code\": \"EPSG:0\",\n", 265 | " \"value\": \"value is equal to EPSG code\"}\n", 266 | " ug.variable_int_with_attributes_define(\"projected_coordinate_system\", attribute_dict)\n", 267 | " # 4. Add conventions (global attributes)\n", 268 | " conventions = {\n", 269 | " \"institution\": \"Deltares\",\n", 270 | " \"references\": \"Unknown\",\n", 271 | " \"source\": \"Unknown Unknown. Model: Unknown\",\n", 272 | " \"history\": \"Created on 2017-11-27T18:05:09+0100, Unknown\",\n", 273 | " \"Conventions\": \"CF-1.6 UGRID-1.0/Deltares-0.8\"}\n", 274 | " ug.attribute_global_define(conventions)" 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": null, 280 | "metadata": {}, 281 | "outputs": [], 282 | "source": [] 283 | } 284 | ], 285 | "metadata": { 286 | "interpreter": { 287 | "hash": "f9a40a62ca6aed642a82485872208121632e5b8a3de7333679d67474b0155150" 288 | }, 289 | "kernelspec": { 290 | "display_name": "Python 3", 291 | "language": "python", 292 | "name": "python3" 293 | }, 294 | "language_info": { 295 | "codemirror_mode": { 296 | "name": "ipython", 297 | "version": 3 298 | }, 299 | "file_extension": ".py", 300 | "mimetype": "text/x-python", 301 | "name": "python", 302 | "nbconvert_exporter": "python", 303 | "pygments_lexer": "ipython3", 304 | "version": "3.8.6" 305 | } 306 | }, 307 | "nbformat": 4, 308 | "nbformat_minor": 5 309 | } 310 | -------------------------------------------------------------------------------- /docs/examples/05_working_with_meshkernel.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Writing meshkernel meshes to ugrid file\n", 8 | "\n", 9 | "This is a basic introduction for writing meshkernel meshes to file.\n", 10 | "\n", 11 | "First, UGrid is imported" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": {}, 18 | "outputs": [ 19 | { 20 | "data": { 21 | "text/plain": [ 22 | "'0.12.0'" 23 | ] 24 | }, 25 | "execution_count": 1, 26 | "metadata": {}, 27 | "output_type": "execute_result" 28 | } 29 | ], 30 | "source": [ 31 | "from ugrid import UGrid, UGridMesh2D\n", 32 | "from ugrid.version import __version__\n", 33 | "__version__" 34 | ] 35 | }, 36 | { 37 | "cell_type": "markdown", 38 | "metadata": {}, 39 | "source": [ 40 | "Then meshkernel is imported" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": 2, 46 | "metadata": {}, 47 | "outputs": [], 48 | "source": [ 49 | "from meshkernel import meshkernel, Mesh1d, MeshKernel, GeometryList, factories" 50 | ] 51 | }, 52 | { 53 | "cell_type": "markdown", 54 | "metadata": {}, 55 | "source": [ 56 | "Other imports" 57 | ] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "execution_count": 3, 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "import numpy as np\n", 66 | "from pathlib import Path" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "## Write a meshkernel mesh2d" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "### Create a triangular mesh2d with meshkernel" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 4, 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "mesh2d_mesh_kernel = factories.Mesh2dFactory.create(3, 7, origin_x=-0.1, origin_y=-1.5)\n", 90 | "mk = MeshKernel()" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 5, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "polygon_file_path = Path().absolute() / \"data_examples\" / \"test.pol\"\n", 100 | "polygon_np = np.loadtxt(polygon_file_path, comments=\"*\", skiprows=8, dtype=np.double)" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 6, 106 | "metadata": {}, 107 | "outputs": [], 108 | "source": [ 109 | "x_coordinates = np.array(polygon_np[:, 0], dtype=np.double)\n", 110 | "y_coordinates = np.array(polygon_np[:, 1], dtype=np.double)\n", 111 | "polygon = GeometryList(x_coordinates, y_coordinates)" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 7, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "mk.mesh2d_make_mesh_from_polygon(polygon)" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": 8, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [ 129 | "mesh2d_mesh_kernel = mk.mesh2d_get()" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "### Write UGrid mesh2d to file" 137 | ] 138 | }, 139 | { 140 | "cell_type": "code", 141 | "execution_count": 9, 142 | "metadata": {}, 143 | "outputs": [], 144 | "source": [ 145 | "with UGrid(\"./Mesh2DMesKernelWrite_net.nc\", \"w+\") as ug:\n", 146 | " # 1. Convert a meshkernel mesh2d to an ugrid mesh2d\n", 147 | " mesh2d_ugrid = ug.from_meshkernel_mesh2d_to_ugrid_mesh2d(mesh2d=mesh2d_mesh_kernel, name=\"mesh2d\", is_spherical=False)\n", 148 | " # 2. Define a new mesh2d\n", 149 | " topology_id = ug.mesh2d_define(mesh2d_ugrid)\n", 150 | " # 3. Put a new mesh2d\n", 151 | " ug.mesh2d_put(topology_id, mesh2d_ugrid)\n", 152 | " # 4. Add crs to file\n", 153 | " attribute_dict = {\n", 154 | " \"name\": \"Unknown projected\",\n", 155 | " \"epsg\": np.array([0], dtype=int),\n", 156 | " \"grid_mapping_name\": \"Unknown projected\",\n", 157 | " \"longitude_of_prime_meridian\": np.array([0.0], dtype=float),\n", 158 | " \"semi_major_axis\": np.array([6378137.0], dtype=float),\n", 159 | " \"semi_minor_axis\": np.array([6356752.314245], dtype=float),\n", 160 | " \"inverse_flattening\": np.array([6356752.314245], dtype=float),\n", 161 | " \"EPSG_code\": \"EPSG:0\",\n", 162 | " \"value\": \"value is equal to EPSG code\"}\n", 163 | " ug.variable_int_with_attributes_define(\"projected_coordinate_system\", attribute_dict)\n", 164 | " # 5. Add conventions (global attributes)\n", 165 | " conventions = {\n", 166 | " \"institution\": \"Deltares\",\n", 167 | " \"references\": \"Unknown\",\n", 168 | " \"source\": \"Unknown Unknown. Model: Unknown\",\n", 169 | " \"history\": \"Created on 2017-11-27T18:05:09+0100, Unknown\",\n", 170 | " \"Conventions\": \"CF-1.6 UGRID-1.0/Deltares-0.8\"}\n", 171 | " ug.attribute_global_define(conventions)" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "## Write a meshkernel mesh1d" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "### Create a meshkernel mesh1d" 186 | ] 187 | }, 188 | { 189 | "cell_type": "code", 190 | "execution_count": 10, 191 | "metadata": {}, 192 | "outputs": [], 193 | "source": [ 194 | "# create a meshkernel mesh1d\n", 195 | "node_x = np.array([0.0, 1.0, 2.0, 3.0], dtype=np.double)\n", 196 | "node_y = np.array([0.0, 0.0, 0.0, 0.0], dtype=np.double)\n", 197 | "edge_nodes = np.array([0, 1, 1, 2, 2, 3], dtype=np.int32)\n", 198 | "mesh1d = Mesh1d(node_x=node_x, node_y=node_y, edge_nodes=edge_nodes)" 199 | ] 200 | }, 201 | { 202 | "cell_type": "markdown", 203 | "metadata": {}, 204 | "source": [ 205 | "### Create data required to instatiate a valid ugrid mesh1d" 206 | ] 207 | }, 208 | { 209 | "cell_type": "code", 210 | "execution_count": 11, 211 | "metadata": {}, 212 | "outputs": [], 213 | "source": [ 214 | "node_edge_id = np.array([0, 1, 1, 2, 2, 3, 2, 0], dtype=np.int32)\n", 215 | "node_edge_offset = np.array([0, 1, 1, 2, 2, 3, 2, 0], dtype=np.double)\n", 216 | "node_name_id = [\"branchname\"]\n", 217 | "node_name_long = [\"branchnamelong\"]\n", 218 | "edge_edge_id = np.array([0, 0, 0], dtype=np.int32)\n", 219 | "edge_edge_offset = np.array([0.5, 1.5, 2.5], dtype=np.double)\n", 220 | "edge_x = np.array([0.5, 1.5, 2.5], dtype=np.double)\n", 221 | "edge_y = np.array([0.0, 0.0, 0.0], dtype=np.double)" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": 12, 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [ 230 | "with UGrid(\"Mesh1DMesKernelWrite_net.nc\", \"w+\") as ug:\n", 231 | " # 1. Converts a meshkernel mesh1d to an ugrid mesh1d, by adding the additional fields, such as network names or branch offsets\n", 232 | " mesh1d_ugrid = ug.from_meshkernel_mesh1d_to_ugrid_mesh1d(\n", 233 | " mesh1d=mesh1d,\n", 234 | " name=\"mesh1d\",\n", 235 | " network_name=\"network1d\",\n", 236 | " node_edge_id=node_edge_id,\n", 237 | " node_edge_offset=node_edge_offset,\n", 238 | " node_name_id=node_name_id,\n", 239 | " node_name_long=node_name_long,\n", 240 | " edge_edge_id=edge_edge_id,\n", 241 | " edge_edge_offset=edge_edge_offset,\n", 242 | " edge_x=edge_x,\n", 243 | " edge_y=edge_y,\n", 244 | " double_fill_value=-999.0,\n", 245 | " int_fill_value=999)\n", 246 | " \n", 247 | " # 2. Define a new mesh1d\n", 248 | " topology_id = ug.mesh1d_define(mesh1d_ugrid)\n", 249 | " # 3. Write a new mesh1d\n", 250 | " ug.mesh1d_put(topology_id, mesh1d_ugrid)\n", 251 | " # 4. Add crs to file\n", 252 | " attribute_dict = {\n", 253 | " \"name\": \"Unknown projected\",\n", 254 | " \"epsg\": np.array([0], dtype=int),\n", 255 | " \"grid_mapping_name\": \"Unknown projected\",\n", 256 | " \"longitude_of_prime_meridian\": np.array([0.0], dtype=float),\n", 257 | " \"semi_major_axis\": np.array([6378137.0], dtype=float),\n", 258 | " \"semi_minor_axis\": np.array([6356752.314245], dtype=float),\n", 259 | " \"inverse_flattening\": np.array([6356752.314245], dtype=float),\n", 260 | " \"EPSG_code\": \"EPSG:0\",\n", 261 | " \"value\": \"value is equal to EPSG code\"}\n", 262 | " ug.variable_int_with_attributes_define(\"projected_coordinate_system\", attribute_dict)\n", 263 | " # 5. Add conventions (global attributes)\n", 264 | " conventions = {\n", 265 | " \"institution\": \"Deltares\",\n", 266 | " \"references\": \"Unknown\",\n", 267 | " \"source\": \"Unknown Unknown. Model: Unknown\",\n", 268 | " \"history\": \"Created on 2017-11-27T18:05:09+0100, Unknown\",\n", 269 | " \"Conventions\": \"CF-1.6 UGRID-1.0/Deltares-0.8\"}\n", 270 | " ug.attribute_global_define(conventions)" 271 | ] 272 | }, 273 | { 274 | "cell_type": "code", 275 | "execution_count": null, 276 | "metadata": {}, 277 | "outputs": [], 278 | "source": [] 279 | } 280 | ], 281 | "metadata": { 282 | "kernelspec": { 283 | "display_name": "Python 3", 284 | "language": "python", 285 | "name": "python3" 286 | }, 287 | "language_info": { 288 | "codemirror_mode": { 289 | "name": "ipython", 290 | "version": 3 291 | }, 292 | "file_extension": ".py", 293 | "mimetype": "text/x-python", 294 | "name": "python", 295 | "nbconvert_exporter": "python", 296 | "pygments_lexer": "ipython3", 297 | "version": "3.8.6" 298 | } 299 | }, 300 | "nbformat": 4, 301 | "nbformat_minor": 4 302 | } 303 | -------------------------------------------------------------------------------- /ugrid/py_structures.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from numpy import array, ndarray 4 | 5 | 6 | class UGridNetwork1D: 7 | """This class is used for define/put/inquire/get Network1D data 8 | 9 | Attributes: 10 | name (str): The network name. 11 | node_x (ndarray): The x-coordinates of the network node. 12 | node_y (ndarray): The y-coordinates of the network node. 13 | edge_node (ndarray): The nodes defining each branch. 14 | edge_length (ndarray): The edge lengths. 15 | geometry_nodes_x (ndarray): The geometry nodes x coordinates. 16 | geometry_nodes_y (ndarray): The geometry nodes y coordinates. 17 | num_edge_geometry_nodes (ndarray): The number of geometry node on each branch. 18 | edge_order (ndarray): The order of the branches. 19 | node_id (str): The node names ids. 20 | node_name_long (str): The node long names. 21 | edge_id (list): The name of the branches. 22 | edge_long_name (list): The long name of the branches. 23 | """ 24 | 25 | def __init__( 26 | self, 27 | name, 28 | node_x, 29 | node_y, 30 | edge_node, 31 | edge_length, 32 | geometry_nodes_x, 33 | geometry_nodes_y, 34 | num_edge_geometry_nodes, 35 | edge_order=array([]), 36 | node_id=[], 37 | node_name_long=[], 38 | edge_id=[], 39 | edge_long_name=[], 40 | ): 41 | self.name: str = name 42 | self.node_x: ndarray = node_x 43 | self.node_y: ndarray = node_y 44 | self.node_id: list = node_id 45 | self.node_long_name: list = node_name_long 46 | self.edge_node: ndarray = edge_node 47 | self.edge_length: ndarray = edge_length 48 | self.edge_order: ndarray = edge_order 49 | self.edge_id: list = edge_id 50 | self.edge_long_name: list = edge_long_name 51 | self.geometry_nodes_x: ndarray = geometry_nodes_x 52 | self.geometry_nodes_y: ndarray = geometry_nodes_y 53 | self.num_edge_geometry_nodes = num_edge_geometry_nodes 54 | self.is_spherical: bool = False 55 | self.start_index: int = 0 56 | 57 | 58 | class UGridMesh1D: 59 | """This class is used for define/put/inquire/get Mesh1D data 60 | 61 | Attributes: 62 | name (c_char_p): The mesh name. 63 | network_name (c_char_p): The x-coordinates of the network node. 64 | node_x (ndarray): The node x coordinate. 65 | node_y (ndarray): The node y coordinate. 66 | edge_node (ndarray): The edge node connectivity. 67 | node_edge_id (ndarray): The network edge id where every node lies. 68 | node_edge_offset (ndarray): The offset of each node on the network edge. 69 | node_name_id (list): A list of node names ids. 70 | node_name_long (c_char_p): A list of node long names. 71 | edge_edge_id (ndarray): The network edge id where every edge lies. 72 | edge_edge_offset (ndarray): The offset of each edge on the network edge. 73 | edge_x (ndarray): The edge x coordinate. 74 | edge_y (ndarray): The edge y coordinate. 75 | double_fill_value (c_double): The fill value for array of doubles. 76 | int_fill_value (c_int): The fill value for array of integers. 77 | """ 78 | 79 | def __init__( 80 | self, 81 | name, 82 | network_name, 83 | node_edge_id, 84 | node_edge_offset, 85 | node_x=array([]), 86 | node_y=array([]), 87 | edge_node=array([]), 88 | edge_edge_id=array([]), 89 | edge_edge_offset=array([]), 90 | edge_x=array([]), 91 | edge_y=array([]), 92 | node_name_id=[], 93 | node_name_long=[], 94 | double_fill_value=-999.0, 95 | int_fill_value=-999, 96 | ): 97 | self.name: str = name 98 | self.network_name: str = network_name 99 | self.node_x: ndarray = node_x 100 | self.node_y: ndarray = node_y 101 | self.edge_node: ndarray = edge_node 102 | self.node_edge_id: ndarray = node_edge_id 103 | self.node_edge_offset: ndarray = node_edge_offset 104 | self.node_name_id: list = node_name_id 105 | self.node_name_long: list = node_name_long 106 | self.edge_edge_id: ndarray = edge_edge_id 107 | self.edge_edge_offset: ndarray = edge_edge_offset 108 | self.edge_x: ndarray = edge_x 109 | self.edge_y: ndarray = edge_y 110 | self.is_spherical: bool = False 111 | self.start_index: int = 0 112 | self.double_fill_value: float = double_fill_value 113 | self.int_fill_value: int = int_fill_value 114 | 115 | 116 | class UGridMesh2D: 117 | """This class is used for define/put/inquire/get Mesh2D data 118 | 119 | Attributes: 120 | name (str): The mesh name. 121 | edge_nodes (ndarray): The nodes composing each mesh 2d edge. 122 | face_nodes (ndarray): The nodes composing each mesh 2d face. 123 | nodes_per_face (ndarray): The nodes composing each mesh 2d face. 124 | node_x (ndarray): The x-coordinates of the nodes. 125 | node_y (ndarray): The y-coordinates of the nodes. 126 | edge_x (ndarray): The x-coordinates of the mesh edges' middle points. 127 | edge_y (ndarray): The x-coordinates of the mesh edges' middle points. 128 | face_x (ndarray): The x-coordinates of the mesh faces' mass centers. 129 | face_y (ndarray): The y-coordinates of the mesh faces' mass centers. 130 | edge_faces (ndarray): The edges composing each face. 131 | face_edges (ndarray): For each face, the edges composing it. 132 | face_faces (ndarray): For each face, the neighboring faces. 133 | node_z (ndarray): The node z coordinates. 134 | edge_z (ndarray): The edge z coordinates. 135 | face_z (ndarray): The face z coordinates. 136 | layer_zs (ndarray): The z coordinates of a layer. 137 | interface_zs (ndarray): The z coordinates of a layer interface. 138 | boundary_node_connectivity (ndarray): To be detailed. 139 | volume_coordinates (ndarray): To be detailed. 140 | start_index (int): The start index used in arrays using indices, such as in the branch_node array. 141 | num_face_nodes_max (int): The maximum number of face nodes. 142 | is_spherical (c_int): 1 if coordinates are in a spherical system, 0 otherwise. 143 | double_fill_value (c_double): The fill value for array of doubles. 144 | int_fill_value (c_int): The fill value for array of integers. 145 | """ 146 | 147 | def __init__( 148 | self, 149 | name, 150 | node_x, 151 | node_y, 152 | edge_node, 153 | face_nodes=array([]), 154 | edge_x=array([]), 155 | edge_y=array([]), 156 | face_x=array([]), 157 | face_y=array([]), 158 | edge_faces=array([]), 159 | face_edges=array([]), 160 | face_faces=array([]), 161 | node_z=array([]), 162 | edge_z=array([]), 163 | face_z=array([]), 164 | layer_zs=array([]), 165 | interface_zs=array([]), 166 | boundary_node_connectivity=array([]), 167 | volume_coordinates=array([]), 168 | start_index=0, 169 | num_face_nodes_max=4, 170 | is_spherical=False, 171 | double_fill_value=-999.0, 172 | int_fill_value=-999, 173 | ): 174 | self.name: str = name 175 | self.edge_nodes: ndarray = edge_node 176 | self.node_x: ndarray = node_x 177 | self.node_y: ndarray = node_y 178 | 179 | self.face_nodes: ndarray = face_nodes 180 | self.edge_x: ndarray = edge_x 181 | self.edge_y: ndarray = edge_y 182 | self.face_x: ndarray = face_x 183 | self.face_y: ndarray = face_y 184 | self.edge_faces: ndarray = edge_faces 185 | self.face_edges: ndarray = face_edges 186 | self.face_faces: ndarray = face_faces 187 | self.node_z: ndarray = node_z 188 | self.edge_z: ndarray = edge_z 189 | self.face_z: ndarray = face_z 190 | self.layer_zs: ndarray = layer_zs 191 | self.interface_zs: ndarray = interface_zs 192 | self.boundary_node_connectivity: ndarray = boundary_node_connectivity 193 | self.volume_coordinates: ndarray = volume_coordinates 194 | self.start_index: int = start_index 195 | self.num_face_nodes_max: int = num_face_nodes_max 196 | self.is_spherical: int = is_spherical 197 | self.double_fill_value: float = double_fill_value 198 | self.int_fill_value: int = int_fill_value 199 | 200 | 201 | class UGridContacts: 202 | """This class is used for define/put/inquire/get Contacts data 203 | 204 | Attributes: 205 | name (str): The name of the contact entity. 206 | edges (ndarray): The actual contacts, expressed as pair of indices from a mesh index to another mesh index. 207 | contact_type (ndarray): For each contact its type. 208 | contact_name_id (list): The name of each contact. 209 | contact_name_long (list): The long name of each contact. 210 | mesh_from_name (str): The name of the mesh where the contacts start. 211 | mesh_to_name (str): The name of the mesh where the contacts ends. 212 | mesh_from_location (c_int): The location type (node, edge or face) at the contact start. 213 | mesh_to_location (c_int): The location type (node, edge or face) at the contact end. 214 | num_contacts (c_int): The number of contacts. 215 | """ 216 | 217 | def __init__( 218 | self, 219 | name, 220 | edges, 221 | mesh_from_name, 222 | mesh_to_name, 223 | contact_type=array([]), 224 | contact_name_id=list, 225 | contact_name_long=list, 226 | mesh_from_location=0, 227 | mesh_to_location=0, 228 | ): 229 | self.name: str = name 230 | self.edges: ndarray = edges 231 | self.mesh_from_name: str = mesh_from_name 232 | self.mesh_to_name: str = mesh_to_name 233 | 234 | self.contact_type: ndarray = contact_type 235 | self.contact_name_id: list = contact_name_id 236 | self.contact_name_long: list = contact_name_long 237 | self.mesh_from_location: int = mesh_from_location 238 | self.mesh_to_location: int = mesh_to_location 239 | -------------------------------------------------------------------------------- /docs/examples/04_variables_basics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# UGrid variable basics\n", 8 | "\n", 9 | "This is the basic introduction for using the `ugridpy` library.\n", 10 | "\n", 11 | "`ugridpy` can be used for reading variable information from UGrid files. \n", 12 | "\n", 13 | "At the very beginning, the necessary libraries have to be imported." 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 1, 19 | "metadata": {}, 20 | "outputs": [ 21 | { 22 | "data": { 23 | "text/plain": [ 24 | "'0.9.0'" 25 | ] 26 | }, 27 | "execution_count": 1, 28 | "metadata": {}, 29 | "output_type": "execute_result" 30 | } 31 | ], 32 | "source": [ 33 | "from ugrid import UGrid, UGridMesh1D\n", 34 | "from ugrid.version import __version__\n", 35 | "__version__" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "Other imports" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "import numpy as np" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "## Reading topology's attributes names and values" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "with UGrid(\"./data_examples/OneMesh2D.nc\", \"r\") as ug:\n", 68 | " # 1. Get the mesh2d name. This is also the name of a variable \n", 69 | " variable_name = ug.mesh2d_get(0).name\n", 70 | " # 2. Get the mesh2D attribute names\n", 71 | " attribute_names = ug.variable_get_attributes_names(variable_name)\n", 72 | " # 3. Get the mesh2D attribute values\n", 73 | " attribute_values = ug.variable_get_attributes_values(variable_name)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "metadata": {}, 79 | "source": [ 80 | "### Create a dictionary of topology attributes names and values. This dictionary can be used for reading the file content with other libraries (xarray)" 81 | ] 82 | }, 83 | { 84 | "cell_type": "code", 85 | "execution_count": 4, 86 | "metadata": {}, 87 | "outputs": [ 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "{'cf_role': 'mesh_topology', 'edge_coordinates': 'mesh2d_edge_x mesh2d_edge_y', 'edge_dimension': 'mesh2d_nEdges', 'edge_node_connectivity': 'mesh2d_edge_nodes', 'face_coordinates': 'mesh2d_face_x mesh2d_face_y', 'face_dimension': 'mesh2d_nFaces', 'face_node_connectivity': 'mesh2d_face_nodes', 'long_name': 'Topology data of 2D mesh', 'max_face_nodes_dimension': 'mesh2d_nMax_face_nodes', 'node_coordinates': 'mesh2d_node_x mesh2d_node_y', 'node_dimension': 'mesh2d_nNodes', 'topology_dimension': '2'}\n" 93 | ] 94 | } 95 | ], 96 | "source": [ 97 | "variable_attributes = {name:value for (name,value) in zip(attribute_names,attribute_values) }\n", 98 | "print(variable_attributes)" 99 | ] 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "## Reading another variable attribute's names and values" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "metadata": {}, 111 | "source": [ 112 | "### In this case the attribute flag_values contains multiple values" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 5, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "with UGrid(\"./data_examples/OneMesh2D.nc\", \"r\") as ug:\n", 122 | " # 1. Get the mesh2d name. This is also the name of a variable \n", 123 | " variable_name = \"mesh2d_edge_type\"\n", 124 | " # 2. Get the mesh2D attribute names\n", 125 | " attribute_names = ug.variable_get_attributes_names(variable_name)\n", 126 | " # 3. Get the mesh2D attribute values\n", 127 | " attribute_values = ug.variable_get_attributes_values(variable_name)" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 6, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "name": "stdout", 137 | "output_type": "stream", 138 | "text": [ 139 | "{'_FillValue': '-999', 'coordinates': 'mesh2d_edge_x mesh2d_edge_y', 'flag_meanings': 'internal_closed internal boundary boundary_closed', 'flag_values': '0 1 2 3', 'location': 'edge', 'long_name': 'edge type (relation between edge and flow geometry)', 'mesh': 'mesh2d'}\n" 140 | ] 141 | } 142 | ], 143 | "source": [ 144 | "variable_attributes = {name:value for (name,value) in zip(attribute_names,attribute_values) }\n", 145 | "print(variable_attributes)" 146 | ] 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "metadata": {}, 151 | "source": [ 152 | "## Reading data variable from a NetCDF file" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 7, 158 | "metadata": {}, 159 | "outputs": [], 160 | "source": [ 161 | "with UGrid(\"./data_examples/ADH_SanDiego.nc\", \"r\") as ug:\n", 162 | " depth = ug.variable_get_data_double(\"depth\")\n", 163 | " node_x = ug.variable_get_data_double(\"node_x\")\n", 164 | " node_y = ug.variable_get_data_double(\"node_y\")" 165 | ] 166 | }, 167 | { 168 | "cell_type": "code", 169 | "execution_count": 8, 170 | "metadata": {}, 171 | "outputs": [ 172 | { 173 | "data": { 174 | "image/png": "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\n", 175 | "text/plain": [ 176 | "
" 177 | ] 178 | }, 179 | "metadata": { 180 | "needs_background": "light" 181 | }, 182 | "output_type": "display_data" 183 | } 184 | ], 185 | "source": [ 186 | "import numpy as np\n", 187 | "import matplotlib.pyplot as plt\n", 188 | " \n", 189 | "# Plotting scalar field with tricontour\n", 190 | "plt.tricontourf(node_x, node_y, depth[:9140])\n", 191 | "plt.title('Scalar Field')\n", 192 | " \n", 193 | "# Show plot with gird\n", 194 | "plt.grid()" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [] 203 | } 204 | ], 205 | "metadata": { 206 | "kernelspec": { 207 | "display_name": "Python 3", 208 | "language": "python", 209 | "name": "python3" 210 | }, 211 | "language_info": { 212 | "codemirror_mode": { 213 | "name": "ipython", 214 | "version": 3 215 | }, 216 | "file_extension": ".py", 217 | "mimetype": "text/x-python", 218 | "name": "python", 219 | "nbconvert_exporter": "python", 220 | "pygments_lexer": "ipython3", 221 | "version": "3.8.6" 222 | } 223 | }, 224 | "nbformat": 4, 225 | "nbformat_minor": 4 226 | } 227 | -------------------------------------------------------------------------------- /ugrid/c_structures.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ctypes import POINTER, Structure, c_char_p, c_double, c_int 4 | 5 | import numpy as np 6 | from numpy.ctypeslib import as_ctypes 7 | 8 | from ugrid.py_structures import UGridContacts, UGridMesh1D, UGridMesh2D, UGridNetwork1D 9 | 10 | 11 | def decode_byte_vector_to_string(byte_vector: bytes, ncolumns: int) -> str: 12 | """From byte vector to string""" 13 | return byte_vector[:ncolumns].decode("ASCII").strip() 14 | 15 | 16 | def decode_byte_vector_to_list_of_strings( 17 | byte_vector: bytes, nrows: int, str_size: int 18 | ) -> list: 19 | """From byte vector to a vector of strings""" 20 | return [ 21 | byte_vector[str_size * r : str_size * (r + 1)].decode("ASCII").strip() 22 | for r in range(nrows) 23 | ] 24 | 25 | 26 | def pad_and_join_list_of_strings(string_list: list, str_size: int): 27 | """Pad each entry to a defined size and join the padded entries into one string""" 28 | for i in range(len(string_list)): 29 | string_list[i] = string_list[i].ljust(str_size) 30 | result = "".join(string_list) 31 | return result 32 | 33 | 34 | def numpy_array_to_ctypes(arr): 35 | """Cast an array to ctypes only if its len is not 0""" 36 | if arr is not None and len(arr) > 0: 37 | return as_ctypes(arr) 38 | return None 39 | 40 | 41 | class CUGridNetwork1D(Structure): 42 | """C-structure intended for internal use only. 43 | It represents a Network1d struct as described by the UGrid API. 44 | 45 | Used for communicating with the UGrid dll. 46 | 47 | Attributes: 48 | name (c_char_p): The network name. 49 | node_x (POINTER(c_double)): The x-coordinates of the network node. 50 | node_y (POINTER(c_double)): The y-coordinates of the network node. 51 | node_id (c_char_p): The node names ids. 52 | node_long_name (c_char_p): The node long names. 53 | edge_node (POINTER(c_int)): The nodes defining each branch. 54 | edge_length (POINTER(c_double)): The edge lengths. 55 | edge_order (POINTER(c_int)): The order of the branches. 56 | edge_id (c_char_p): The name of the branches. 57 | edge_long_name (c_char_p): The long name of the branches. 58 | geometry_nodes_x (POINTER(c_double)): The geometry nodes x coordinates. 59 | geometry_nodes_y (POINTER(c_double)): The geometry nodes y coordinates. 60 | num_edges_geometry_nodes (POINTER(c_int)): The number of geometry nodes for each branch. 61 | num_geometry_nodes (c_int): The number of geometry nodes. 62 | num_nodes (c_int): The number of network1d nodes. 63 | num_edges (c_int): The number of network1d branches. 64 | is_spherical (c_int): 1 if the coordinates are in a spherical system, 0 otherwise . 65 | start_index (c_int): The start index used in arrays using indices, such as in the branch_node array. 66 | """ 67 | 68 | _fields_ = [ 69 | ("name", c_char_p), 70 | ("node_x", POINTER(c_double)), 71 | ("node_y", POINTER(c_double)), 72 | ("node_id", c_char_p), 73 | ("node_long_name", c_char_p), 74 | ("edge_node", POINTER(c_int)), 75 | ("edge_length", POINTER(c_double)), 76 | ("edge_order", POINTER(c_int)), 77 | ("edge_id", c_char_p), 78 | ("edge_long_name", c_char_p), 79 | ("geometry_nodes_x", POINTER(c_double)), 80 | ("geometry_nodes_y", POINTER(c_double)), 81 | ("num_edges_geometry_nodes", POINTER(c_int)), 82 | ("num_geometry_nodes", c_int), 83 | ("num_nodes", c_int), 84 | ("num_edges", c_int), 85 | ("is_spherical", c_int), 86 | ("start_index", c_int), 87 | ] 88 | 89 | @staticmethod 90 | def from_py_structure( 91 | ugrid_network1D: UGridNetwork1D, name_size: int, name_long_size: int 92 | ) -> CUGridNetwork1D: 93 | """Creates a new CMesh instance from a given Mesh2d instance. 94 | 95 | Args: 96 | ugrid_network1D (UGridNetwork1D): Class of numpy instances owning the state. 97 | 98 | Returns: 99 | CMesh2d: The created CMesh2d instance. 100 | """ 101 | 102 | name_padded = ugrid_network1D.name.ljust(name_long_size) 103 | 104 | node_id = pad_and_join_list_of_strings(ugrid_network1D.node_id, name_size) 105 | node_long_name = pad_and_join_list_of_strings( 106 | ugrid_network1D.node_long_name, name_long_size 107 | ) 108 | 109 | edge_id = pad_and_join_list_of_strings(ugrid_network1D.edge_id, name_size) 110 | edge_long_name = pad_and_join_list_of_strings( 111 | ugrid_network1D.edge_long_name, name_long_size 112 | ) 113 | 114 | c_ugrid_network = CUGridNetwork1D() 115 | 116 | # Set the pointers 117 | c_ugrid_network.name = c_char_p(name_padded.encode("ASCII")) 118 | c_ugrid_network.node_x = numpy_array_to_ctypes(ugrid_network1D.node_x) 119 | c_ugrid_network.node_y = numpy_array_to_ctypes(ugrid_network1D.node_y) 120 | c_ugrid_network.node_id = c_char_p(node_id.encode("ASCII")) 121 | c_ugrid_network.node_long_name = c_char_p(node_long_name.encode("ASCII")) 122 | c_ugrid_network.edge_node = numpy_array_to_ctypes(ugrid_network1D.edge_node) 123 | c_ugrid_network.edge_length = numpy_array_to_ctypes(ugrid_network1D.edge_length) 124 | c_ugrid_network.edge_order = numpy_array_to_ctypes(ugrid_network1D.edge_order) 125 | c_ugrid_network.edge_id = c_char_p(edge_id.encode("ASCII")) 126 | c_ugrid_network.edge_long_name = c_char_p(edge_long_name.encode("ASCII")) 127 | c_ugrid_network.geometry_nodes_x = numpy_array_to_ctypes( 128 | ugrid_network1D.geometry_nodes_x 129 | ) 130 | c_ugrid_network.geometry_nodes_y = numpy_array_to_ctypes( 131 | ugrid_network1D.geometry_nodes_y 132 | ) 133 | 134 | # Set the sizes 135 | if ugrid_network1D.geometry_nodes_x is not None: 136 | c_ugrid_network.num_geometry_nodes = ugrid_network1D.geometry_nodes_x.size 137 | if ugrid_network1D.node_x is not None: 138 | c_ugrid_network.num_nodes = ugrid_network1D.node_x.size 139 | if ugrid_network1D.edge_node is not None: 140 | c_ugrid_network.num_edges = ugrid_network1D.edge_node.size // 2 141 | c_ugrid_network.is_spherical = ugrid_network1D.is_spherical 142 | c_ugrid_network.start_index = ugrid_network1D.start_index 143 | 144 | return c_ugrid_network 145 | 146 | def allocate_memory(self, name_size: int, name_long_size: int) -> UGridNetwork1D: 147 | """Allocate data according to the parameters with the "num_" prefix. 148 | The pointers are then set to the freshly allocated memory. 149 | The memory is owned by the Network1D instance which is returned by this method. 150 | 151 | Args: 152 | name_size (int): The size of the names. 153 | name_long_size (int): The size of the long names. 154 | 155 | Returns: 156 | UGridNetwork1D: The object owning the allocated memory. 157 | """ 158 | 159 | name = " " * name_long_size 160 | node_x = np.empty(self.num_nodes, dtype=np.double) 161 | node_y = np.empty(self.num_nodes, dtype=np.double) 162 | node_id = " " * self.num_nodes * name_size 163 | node_long_name = " " * self.num_nodes * name_long_size 164 | edge_node = np.empty(self.num_nodes, dtype=np.int32) 165 | edge_length = np.empty(self.num_edges, dtype=np.double) 166 | edge_order = np.empty(self.num_edges, dtype=np.int32) 167 | edge_id = " " * self.num_edges * name_size 168 | edge_long_name = " " * self.num_edges * name_long_size 169 | geometry_nodes_x = np.empty(self.num_geometry_nodes, dtype=np.double) 170 | geometry_nodes_y = np.empty(self.num_geometry_nodes, dtype=np.double) 171 | num_edge_geometry_nodes = np.empty(self.num_edges, dtype=np.int32) 172 | 173 | self.name = c_char_p(name.encode("ASCII")) 174 | self.node_x = numpy_array_to_ctypes(node_x) 175 | self.node_y = numpy_array_to_ctypes(node_y) 176 | self.node_id = c_char_p(node_id.encode("ASCII")) 177 | self.node_long_name = c_char_p(node_long_name.encode("ASCII")) 178 | self.edge_node = numpy_array_to_ctypes(edge_node) 179 | self.edge_length = numpy_array_to_ctypes(edge_length) 180 | self.edge_order = numpy_array_to_ctypes(edge_order) 181 | self.edge_id = c_char_p(edge_id.encode("ASCII")) 182 | self.edge_long_name = c_char_p(edge_long_name.encode("ASCII")) 183 | self.geometry_nodes_x = numpy_array_to_ctypes(geometry_nodes_x) 184 | self.geometry_nodes_y = numpy_array_to_ctypes(geometry_nodes_y) 185 | self.num_edge_geometry_nodes = numpy_array_to_ctypes(num_edge_geometry_nodes) 186 | 187 | return UGridNetwork1D( 188 | name=name, 189 | node_x=node_x, 190 | node_y=node_y, 191 | edge_node=edge_node, 192 | edge_length=edge_length, 193 | edge_order=edge_order, 194 | geometry_nodes_x=geometry_nodes_x, 195 | geometry_nodes_y=geometry_nodes_y, 196 | num_edge_geometry_nodes=num_edge_geometry_nodes, 197 | ) 198 | 199 | 200 | class CUGridMesh1D(Structure): 201 | """C-structure intended for internal use only. 202 | It represents a Mesh1d struct as described by the UGrid API. 203 | 204 | Used for communicating with the UGrid dll. 205 | 206 | Attributes: 207 | name (c_char_p): The mesh name. 208 | network_name (c_char_p): The x-coordinates of the network node. 209 | node_x (POINTER(c_double)): The node x coordinate. 210 | node_y (POINTER(c_double)): The node y coordinate. 211 | edge_node (POINTER(c_int)): The edge node connectivity. 212 | node_edge_id (POINTER(c_int)): The network edge id where every node lies. 213 | branch_offset (POINTER(c_double)): The offset of each node on the network edge 214 | node_name_id (POINTER(c_int)): The node name. 215 | node_name_long (c_char_p): The node long name. 216 | edge_edge_id (c_char_p): The network edge id where every edge lies. 217 | edge_edge_offset (POINTER(c_double)): The offset of each edge on the network edge. 218 | edge_x (POINTER(c_double)): The edge x coordinate. 219 | edge_y (c_int): The edge y coordinate. 220 | num_nodes (c_int): The number of nodes. 221 | num_edges (c_int): The number of edges. 222 | is_spherical (c_int): 1 if coordinates are in a spherical system, 0 otherwise. 223 | start_index (c_int): The start index used in arrays using indices, such as in the branch_node array. 224 | double_fill_value (c_double): The fill value for array of doubles. 225 | int_fill_value (c_int): The fill value for array of integers. 226 | """ 227 | 228 | _fields_ = [ 229 | ("name", c_char_p), 230 | ("network_name", c_char_p), 231 | ("node_x", POINTER(c_double)), 232 | ("node_y", POINTER(c_double)), 233 | ("edge_node", POINTER(c_int)), 234 | ("node_edge_id", POINTER(c_int)), 235 | ("branch_offset", POINTER(c_double)), 236 | ("node_name_id", c_char_p), 237 | ("node_name_long", c_char_p), 238 | ("edge_edge_id", POINTER(c_int)), 239 | ("edge_edge_offset", POINTER(c_double)), 240 | ("edge_x", POINTER(c_double)), 241 | ("edge_y", POINTER(c_double)), 242 | ("num_nodes", c_int), 243 | ("num_edges", c_int), 244 | ("is_spherical", c_int), 245 | ("start_index", c_int), 246 | ("double_fill_value", c_double), 247 | ("int_fill_value", c_int), 248 | ] 249 | 250 | @staticmethod 251 | def from_py_structure( 252 | mesh1d: UGridMesh1D, name_size: int, name_long_size: int 253 | ) -> CUGridMesh1D: 254 | """Creates a new CMesh instance from a given Mesh2d instance. 255 | 256 | Args: 257 | name_size (int): The size of the names. 258 | name_long_size (int): The size of the long names. 259 | mesh1d (UGridMesh1D): Class of numpy instances owning the state. 260 | 261 | Returns: 262 | CMesh2d: The created CMesh2d instance. 263 | """ 264 | 265 | c_mesh1d = CUGridMesh1D() 266 | 267 | # Set the pointers 268 | mesh1d_name_padded = mesh1d.name.ljust(name_long_size) 269 | network1d_name_padded = mesh1d.network_name.ljust(name_long_size) 270 | node_name_id = pad_and_join_list_of_strings(mesh1d.node_name_id, name_size) 271 | node_name_long = pad_and_join_list_of_strings( 272 | mesh1d.node_name_long, name_long_size 273 | ) 274 | 275 | c_mesh1d.name = c_char_p(mesh1d_name_padded.encode("ASCII")) 276 | c_mesh1d.network_name = c_char_p(network1d_name_padded.encode("ASCII")) 277 | c_mesh1d.node_x = numpy_array_to_ctypes(mesh1d.node_x) 278 | c_mesh1d.node_y = numpy_array_to_ctypes(mesh1d.node_y) 279 | c_mesh1d.edge_node = numpy_array_to_ctypes(mesh1d.edge_node) 280 | c_mesh1d.node_edge_id = numpy_array_to_ctypes(mesh1d.node_edge_id) 281 | c_mesh1d.branch_offset = numpy_array_to_ctypes(mesh1d.node_edge_offset) 282 | c_mesh1d.node_name_id = c_char_p(node_name_id.encode("ASCII")) 283 | c_mesh1d.node_name_long = c_char_p(node_name_long.encode("ASCII")) 284 | c_mesh1d.edge_edge_id = numpy_array_to_ctypes(mesh1d.edge_edge_id) 285 | c_mesh1d.edge_edge_offset = numpy_array_to_ctypes(mesh1d.edge_edge_offset) 286 | c_mesh1d.edge_x = numpy_array_to_ctypes(mesh1d.edge_x) 287 | c_mesh1d.edge_y = numpy_array_to_ctypes(mesh1d.edge_y) 288 | 289 | # Set the sizes 290 | if mesh1d.node_x is not None: 291 | c_mesh1d.num_nodes = mesh1d.node_x.size 292 | if mesh1d.edge_node is not None: 293 | c_mesh1d.num_edges = mesh1d.edge_node.size // 2 294 | 295 | # Set other properties 296 | c_mesh1d.is_spherical = mesh1d.is_spherical 297 | c_mesh1d.start_index = mesh1d.start_index 298 | 299 | return c_mesh1d 300 | 301 | def allocate_memory(self, name_size: int, name_long_size: int) -> UGridMesh1D: 302 | """Allocate data according to the parameters with the "num_" prefix. 303 | The pointers are then set to the freshly allocated memory. 304 | The memory is owned by the Mesh1d instance which is returned by this method. 305 | 306 | Args: 307 | name_size (int): The size of the names. 308 | name_long_size (int): The size of the long names. 309 | 310 | Returns: 311 | UGridMesh1D: The object owning the allocated memory. 312 | """ 313 | 314 | name = " " * name_long_size 315 | network_name = " " * name_long_size 316 | node_x = np.empty(self.num_nodes, dtype=np.double) 317 | node_y = np.empty(self.num_nodes, dtype=np.double) 318 | edge_node = np.empty(self.num_edges * 2, dtype=np.int32) 319 | branch_id = np.empty(self.num_nodes, dtype=np.int32) 320 | branch_offset = np.empty(self.num_nodes, dtype=np.double) 321 | node_name_id = " " * self.num_nodes * name_size 322 | node_name_long = " " * self.num_nodes * name_long_size 323 | edge_edge_id = np.empty(self.num_edges, dtype=np.int32) 324 | edge_edge_offset = np.empty(self.num_edges, dtype=np.double) 325 | edge_x = np.empty(self.num_edges, dtype=np.double) 326 | edge_y = np.empty(self.num_edges, dtype=np.double) 327 | 328 | self.name = c_char_p(name.encode("ASCII")) 329 | self.network_name = c_char_p(network_name.encode("ASCII")) 330 | self.node_x = numpy_array_to_ctypes(node_x) 331 | self.node_y = numpy_array_to_ctypes(node_y) 332 | self.edge_node = numpy_array_to_ctypes(edge_node) 333 | self.node_edge_id = numpy_array_to_ctypes(branch_id) 334 | self.branch_offset = numpy_array_to_ctypes(branch_offset) 335 | self.node_name_id = c_char_p(node_name_id.encode("ASCII")) 336 | self.node_name_long = c_char_p(node_name_long.encode("ASCII")) 337 | self.edge_edge_id = numpy_array_to_ctypes(edge_edge_id) 338 | self.edge_edge_offset = numpy_array_to_ctypes(edge_edge_offset) 339 | self.edge_x = numpy_array_to_ctypes(edge_x) 340 | self.edge_y = numpy_array_to_ctypes(edge_y) 341 | 342 | return UGridMesh1D( 343 | name=name, 344 | network_name=network_name, 345 | node_edge_id=branch_id, 346 | node_edge_offset=branch_offset, 347 | node_x=node_x, 348 | node_y=node_y, 349 | edge_node=edge_node, 350 | edge_edge_id=edge_edge_id, 351 | edge_edge_offset=edge_edge_offset, 352 | edge_x=edge_x, 353 | edge_y=edge_y, 354 | node_name_id=node_name_id, 355 | node_name_long=node_name_long, 356 | ) 357 | 358 | 359 | class CUGridMesh2D(Structure): 360 | """C-structure intended for internal use only. 361 | It represents a Mesh2D struct as described by the UGrid API. 362 | 363 | Used for communicating with the UGrid dll. 364 | 365 | Attributes: 366 | name (c_char_p): The mesh name. 367 | edge_nodes (POINTER(c_int)): The nodes composing each mesh 2d edge. 368 | face_nodes (POINTER(c_int)): The nodes composing each mesh 2d face. 369 | nodes_per_face (POINTER(c_int)): The nodes composing each mesh 2d face. 370 | node_x (POINTER(c_double)): The x-coordinates of the nodes. 371 | node_y (POINTER(c_double)): The y-coordinates of the nodes. 372 | edge_x (POINTER(c_double)): The x-coordinates of the mesh edges' middle points. 373 | edge_y (POINTER(c_double)): The x-coordinates of the mesh edges' middle points. 374 | face_x (POINTER(c_double)): The x-coordinates of the mesh faces' mass centers. 375 | face_y (POINTER(c_double)): The y-coordinates of the mesh faces' mass centers. 376 | edge_face (POINTER(c_int)): The edges composing each face. 377 | face_edge (POINTER(c_int)): For each face, the edges composing it. 378 | face_face (POINTER(c_int)): For each face, the neighboring faces. 379 | node_z (POINTER(c_double)): The node z coordinates. 380 | edge_z (POINTER(c_double)): The edge z coordinates. 381 | face_z (POINTER(c_double)): The face z coordinates. 382 | layer_zs (POINTER(c_double)): The z coordinates of a layer. 383 | interface_zs (POINTER(c_double)): The z coordinates of a layer interface. 384 | boundary_node_connectivity (POINTER(c_double)): To be detailed. 385 | volume_coordinates (POINTER(c_double)): To be detailed. 386 | num_nodes (c_int): The number of mesh nodes. 387 | num_edges (c_int): The number of edges. 388 | num_faces (c_int): The number of faces. 389 | num_layers (c_int): The number of layers. 390 | start_index (c_int): The start index used in arrays using indices, such as in the branch_node array. 391 | num_face_nodes_max (c_int): 1 if coordinates are in a spherical system, 0 otherwise. 392 | is_spherical (c_int): 1 if coordinates are in a spherical system, 0 otherwise. 393 | double_fill_value (c_double): The fill value for array of doubles. 394 | int_fill_value (c_int): The fill value for array of integers. 395 | """ 396 | 397 | _fields_ = [ 398 | ("name", c_char_p), 399 | ("edge_node", POINTER(c_int)), 400 | ("face_node", POINTER(c_int)), 401 | ("node_x", POINTER(c_double)), 402 | ("node_y", POINTER(c_double)), 403 | ("edge_x", POINTER(c_double)), 404 | ("edge_y", POINTER(c_double)), 405 | ("face_x", POINTER(c_double)), 406 | ("face_y", POINTER(c_double)), 407 | ("edge_face", POINTER(c_int)), 408 | ("face_edge", POINTER(c_int)), 409 | ("face_face", POINTER(c_int)), 410 | ("node_z", POINTER(c_double)), 411 | ("edge_z", POINTER(c_double)), 412 | ("face_z", POINTER(c_double)), 413 | ("layer_zs", POINTER(c_double)), 414 | ("interface_zs", POINTER(c_double)), 415 | ("boundary_node_connectivity", POINTER(c_double)), 416 | ("volume_coordinates", POINTER(c_int)), 417 | ("num_nodes", c_int), 418 | ("num_edges", c_int), 419 | ("num_faces", c_int), 420 | ("num_layers", c_int), 421 | ("start_index", c_int), 422 | ("num_face_nodes_max", c_int), 423 | ("is_spherical", c_int), 424 | ("double_fill_value", c_double), 425 | ("int_fill_value", c_int), 426 | ] 427 | 428 | @staticmethod 429 | def from_py_structure(mesh2d: UGridMesh2D, name_long_size: int) -> CUGridMesh2D: 430 | """Creates a new CMesh instance from a given Mesh2d instance. 431 | 432 | Args: 433 | mesh2d (UGridMesh2D): Class of numpy instances owning the state. 434 | name_long_size (int): The size of the long names. 435 | 436 | Returns: 437 | CMesh2d: The created CMesh2d instance. 438 | """ 439 | 440 | c_mesh2d = CUGridMesh2D() 441 | 442 | # Required arrays 443 | mesh2d_name_padded = mesh2d.name.ljust(name_long_size) 444 | c_mesh2d.name = c_char_p(mesh2d_name_padded.encode("ASCII")) 445 | c_mesh2d.edge_node = numpy_array_to_ctypes(mesh2d.edge_nodes) 446 | c_mesh2d.node_x = numpy_array_to_ctypes(mesh2d.node_x) 447 | c_mesh2d.node_y = numpy_array_to_ctypes(mesh2d.node_y) 448 | 449 | # Optional arrays 450 | c_mesh2d.edge_x = numpy_array_to_ctypes(mesh2d.edge_x) 451 | c_mesh2d.edge_y = numpy_array_to_ctypes(mesh2d.edge_y) 452 | c_mesh2d.face_x = numpy_array_to_ctypes(mesh2d.face_x) 453 | c_mesh2d.face_y = numpy_array_to_ctypes(mesh2d.face_y) 454 | c_mesh2d.edge_face = numpy_array_to_ctypes(mesh2d.edge_faces) 455 | 456 | c_mesh2d.face_node = numpy_array_to_ctypes(mesh2d.face_nodes) 457 | c_mesh2d.face_edge = numpy_array_to_ctypes(mesh2d.face_edges) 458 | c_mesh2d.face_face = numpy_array_to_ctypes(mesh2d.face_faces) 459 | 460 | c_mesh2d.node_z = numpy_array_to_ctypes(mesh2d.node_z) 461 | c_mesh2d.edge_z = numpy_array_to_ctypes(mesh2d.edge_z) 462 | c_mesh2d.face_z = numpy_array_to_ctypes(mesh2d.face_z) 463 | c_mesh2d.layer_zs = numpy_array_to_ctypes(mesh2d.layer_zs) 464 | c_mesh2d.interface_zs = numpy_array_to_ctypes(mesh2d.interface_zs) 465 | c_mesh2d.boundary_node_connectivity = numpy_array_to_ctypes( 466 | mesh2d.boundary_node_connectivity 467 | ) 468 | c_mesh2d.volume_coordinates = numpy_array_to_ctypes(mesh2d.volume_coordinates) 469 | 470 | # Set the sizes 471 | if mesh2d.node_x is not None: 472 | c_mesh2d.num_nodes = mesh2d.node_x.size 473 | if mesh2d.edge_nodes is not None: 474 | c_mesh2d.num_edges = mesh2d.edge_nodes.size // 2 475 | if mesh2d.face_x is not None: 476 | c_mesh2d.num_faces = mesh2d.face_x.size 477 | if mesh2d.layer_zs is not None: 478 | c_mesh2d.num_layers = mesh2d.layer_zs.size 479 | 480 | # Set other properties 481 | c_mesh2d.start_index = mesh2d.start_index 482 | c_mesh2d.num_face_nodes_max = mesh2d.num_face_nodes_max 483 | c_mesh2d.is_spherical = mesh2d.is_spherical 484 | c_mesh2d.double_fill_value = mesh2d.double_fill_value 485 | c_mesh2d.int_fill_value = mesh2d.int_fill_value 486 | 487 | return c_mesh2d 488 | 489 | def allocate_memory(self, name_long_size: int) -> UGridMesh2D: 490 | """Allocate data according to the parameters with the "num_" prefix. 491 | The pointers are then set to the freshly allocated memory. 492 | The memory is owned by the Mesh2D instance which is returned by this method. 493 | 494 | Args: 495 | name_long_size (int): The size of the long names. 496 | 497 | Returns: 498 | UGridMesh2D: The object owning the allocated memory. 499 | """ 500 | 501 | name = " " * name_long_size 502 | node_x = np.empty(self.num_nodes, dtype=np.double) 503 | node_y = np.empty(self.num_nodes, dtype=np.double) 504 | edge_node = np.empty(self.num_edges * 2, dtype=np.int32) 505 | 506 | node_z = np.empty(self.num_nodes, dtype=np.double) 507 | edge_x = np.empty(self.num_edges, dtype=np.double) 508 | edge_y = np.empty(self.num_edges, dtype=np.double) 509 | edge_z = np.empty(self.num_edges, dtype=np.double) 510 | edge_face = np.empty(self.num_edges * 2, dtype=np.int32) 511 | 512 | face_node = np.empty(self.num_faces * self.num_face_nodes_max, dtype=np.int32) 513 | face_edge = np.empty(self.num_faces * self.num_face_nodes_max, dtype=np.int32) 514 | face_face = np.empty(self.num_faces * self.num_face_nodes_max, dtype=np.int32) 515 | face_x = np.empty(self.num_faces, dtype=np.double) 516 | face_y = np.empty(self.num_faces, dtype=np.double) 517 | face_z = np.empty(self.num_faces, dtype=np.double) 518 | 519 | layer_zs = np.empty(self.num_layers, dtype=np.double) 520 | boundary_node_connectivity = np.empty(self.num_nodes, dtype=np.double) 521 | volume_coordinates = np.empty(self.num_faces, dtype=np.int32) 522 | 523 | self.name = c_char_p(name.encode("ASCII")) 524 | self.edge_node = numpy_array_to_ctypes(edge_node) 525 | self.face_node = numpy_array_to_ctypes(face_node) 526 | self.node_x = numpy_array_to_ctypes(node_x) 527 | self.node_y = numpy_array_to_ctypes(node_y) 528 | self.edge_x = numpy_array_to_ctypes(edge_x) 529 | self.edge_y = numpy_array_to_ctypes(edge_y) 530 | self.face_x = numpy_array_to_ctypes(face_x) 531 | self.face_y = numpy_array_to_ctypes(face_y) 532 | self.edge_face = numpy_array_to_ctypes(edge_face) 533 | self.face_edge = numpy_array_to_ctypes(face_edge) 534 | self.face_face = numpy_array_to_ctypes(face_face) 535 | self.node_z = numpy_array_to_ctypes(node_z) 536 | self.edge_z = numpy_array_to_ctypes(edge_z) 537 | self.face_z = numpy_array_to_ctypes(face_z) 538 | 539 | self.layer_zs = numpy_array_to_ctypes(layer_zs) 540 | self.boundary_node_connectivity = numpy_array_to_ctypes( 541 | boundary_node_connectivity 542 | ) 543 | self.volume_coordinates = numpy_array_to_ctypes(volume_coordinates) 544 | 545 | if self.num_layers > 1: 546 | interface_zs = np.empty(self.num_layers - 1, dtype=np.double) 547 | self.interface_zs = numpy_array_to_ctypes(interface_zs) 548 | else: 549 | interface_zs = None 550 | self.interface_zs = None 551 | 552 | return UGridMesh2D( 553 | name=name, 554 | node_x=node_x, 555 | node_y=node_y, 556 | edge_node=edge_node, 557 | face_nodes=face_node, 558 | edge_x=edge_x, 559 | edge_y=edge_y, 560 | face_x=face_x, 561 | face_y=face_y, 562 | edge_faces=edge_face, 563 | face_edges=face_edge, 564 | face_faces=face_face, 565 | node_z=node_z, 566 | edge_z=edge_z, 567 | face_z=face_z, 568 | layer_zs=layer_zs, 569 | interface_zs=interface_zs, 570 | boundary_node_connectivity=boundary_node_connectivity, 571 | volume_coordinates=volume_coordinates, 572 | ) 573 | 574 | 575 | class CUGridContacts(Structure): 576 | """C-structure intended for internal use only. 577 | It represents a Contacts struct as described by the UGrid API. 578 | 579 | Used for communicating with the UGrid dll. 580 | 581 | Attributes: 582 | name (c_char_p): The name of the contact entity. 583 | edges (POINTER(c_int)): The actual contacts, expressed as pair of indices from a mesh index 584 | to another mesh index. 585 | contact_type (POINTER(c_int)): For each contact its type. 586 | contact_name_id (c_char_p): The name of each contact. 587 | contact_name_long (c_char_p): The long name of each contact. 588 | mesh_from_name (c_char_p): The name of the mesh where the contacts start. 589 | mesh_to_name (c_char_p): The name of the mesh where the contacts ends. 590 | mesh_from_location (c_int): The location type (node, edge or face) at the contact start. 591 | mesh_to_location (c_int): The location type (node, edge or face) at the contact end. 592 | num_contacts (c_int): The number of contacts. 593 | """ 594 | 595 | _fields_ = [ 596 | ("name", c_char_p), 597 | ("edges", POINTER(c_int)), 598 | ("contact_type", POINTER(c_int)), 599 | ("contact_name_id", c_char_p), 600 | ("contact_name_long", c_char_p), 601 | ("mesh_from_name", c_char_p), 602 | ("mesh_to_name", c_char_p), 603 | ("mesh_from_location", c_int), 604 | ("mesh_to_location", c_int), 605 | ("num_contacts", c_int), 606 | ] 607 | 608 | @staticmethod 609 | def from_py_structure( 610 | contacts: UGridContacts, name_size: int, name_long_size: int 611 | ) -> CUGridContacts: 612 | """Creates a new CContacts instance from a given Contacts instance. 613 | 614 | Args: 615 | contacts (UGridContacts): Class of numpy instances owning the state. 616 | 617 | Returns: 618 | CUGridContacts: The created CContacts instance. 619 | """ 620 | 621 | c_contacts = CUGridContacts() 622 | 623 | # Set the pointers 624 | contacts_name_padded = contacts.name.ljust(name_long_size) 625 | mesh_from_name_padded = contacts.mesh_from_name.ljust(name_long_size) 626 | mesh_to_name_padded = contacts.mesh_to_name.ljust(name_long_size) 627 | 628 | contact_name_id_padded = pad_and_join_list_of_strings( 629 | contacts.contact_name_id, name_size 630 | ) 631 | contact_name_long_padded = pad_and_join_list_of_strings( 632 | contacts.contact_name_long, name_long_size 633 | ) 634 | 635 | c_contacts.name = c_char_p(contacts_name_padded.encode("ASCII")) 636 | c_contacts.edges = numpy_array_to_ctypes(contacts.edges) 637 | c_contacts.contact_type = numpy_array_to_ctypes(contacts.contact_type) 638 | c_contacts.mesh_from_name = c_char_p(mesh_from_name_padded.encode("ASCII")) 639 | c_contacts.mesh_to_name = c_char_p(mesh_to_name_padded.encode("ASCII")) 640 | c_contacts.contact_name_id = c_char_p(contact_name_id_padded.encode("ASCII")) 641 | c_contacts.contact_name_long = c_char_p( 642 | contact_name_long_padded.encode("ASCII") 643 | ) 644 | c_contacts.mesh_from_location = contacts.mesh_from_location 645 | c_contacts.mesh_to_location = contacts.mesh_to_location 646 | 647 | # Set the size 648 | if contacts.edges is not None: 649 | c_contacts.num_contacts = contacts.edges.size // 2 650 | 651 | return c_contacts 652 | 653 | def allocate_memory(self, name_size: int, name_long_size: int) -> UGridContacts: 654 | """Allocate data according to the parameters with the "num_" prefix. 655 | The pointers are then set to the freshly allocated memory. 656 | The memory is owned by the Contacts instance which is returned by this method. 657 | 658 | Args: 659 | name_long_size (int): The size of the long names. 660 | 661 | Returns: 662 | UGridContacts: The object owning the allocated memory. 663 | """ 664 | 665 | name = " " * name_long_size 666 | mesh_from_name = " " * name_long_size 667 | mesh_to_name = " " * name_long_size 668 | edges = np.empty(self.num_contacts * 2, dtype=np.int32) 669 | contact_type = np.empty(self.num_contacts, dtype=np.int32) 670 | contact_name_id = " " * self.num_contacts * name_size 671 | contact_name_long = " " * self.num_contacts * name_long_size 672 | 673 | self.name = c_char_p(name.encode("ASCII")) 674 | self.edges = numpy_array_to_ctypes(edges) 675 | self.contact_type = numpy_array_to_ctypes(contact_type) 676 | self.contact_name_id = c_char_p(contact_name_id.encode("ASCII")) 677 | self.contact_name_long = c_char_p(contact_name_long.encode("ASCII")) 678 | self.mesh_from_name = c_char_p(mesh_from_name.encode("ASCII")) 679 | self.mesh_to_name = c_char_p(mesh_to_name.encode("ASCII")) 680 | 681 | return UGridContacts( 682 | name=name, 683 | edges=edges, 684 | mesh_from_name=mesh_from_name, 685 | mesh_to_name=mesh_to_name, 686 | contact_type=contact_type, 687 | contact_name_id=contact_name_id, 688 | contact_name_long=contact_name_long, 689 | ) 690 | -------------------------------------------------------------------------------- /ugrid/ugrid.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import logging 3 | import operator 4 | import os 5 | import platform 6 | from ctypes import CDLL, byref, c_char_p, c_double, c_int 7 | from enum import IntEnum, unique 8 | from pathlib import Path 9 | from typing import Callable 10 | 11 | import numpy as np 12 | from meshkernel import Contacts, Mesh1d, Mesh2d 13 | from numpy.ctypeslib import as_ctypes 14 | 15 | from ugrid.c_structures import ( 16 | CUGridContacts, 17 | CUGridMesh1D, 18 | CUGridMesh2D, 19 | CUGridNetwork1D, 20 | decode_byte_vector_to_list_of_strings, 21 | decode_byte_vector_to_string, 22 | numpy_array_to_ctypes, 23 | ) 24 | from ugrid.errors import UGridError 25 | from ugrid.py_structures import UGridContacts, UGridMesh1D, UGridMesh2D, UGridNetwork1D 26 | from ugrid.version import __version__ 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | 31 | @unique 32 | class Status(IntEnum): 33 | SUCCESS = 0 34 | EXCEPTION = 1 35 | 36 | 37 | class UGrid: 38 | """This class is the entry point for interacting with the UGridPy library""" 39 | 40 | def __init__(self, file_path, method): 41 | """Constructor of UGrid 42 | 43 | Raises: 44 | OSError: This gets raised in case UGrid is used within an unsupported OS. 45 | """ 46 | 47 | lib_path = self.__get_library_path() 48 | self.lib = CDLL(str(lib_path)) 49 | self.__open(file_path, method) 50 | 51 | def __enter__(self): 52 | return self 53 | 54 | def __get_library_path(self): 55 | """Gets the library path 56 | 57 | Raises: 58 | OSError: This gets raised in case UGrid is used within an unsupported OS. 59 | """ 60 | system = platform.system() 61 | lib_path = Path(__file__).parent 62 | if system == "Windows": 63 | lib_path = os.path.join(lib_path, "UGridApi.dll") 64 | else: 65 | if not system: 66 | system = "Unknown OS" 67 | raise OSError(f"Unsupported operating system: {system}") 68 | 69 | return lib_path 70 | 71 | def __exit__(self, type, value, traceback): 72 | self.__execute_function(self.lib.ug_file_close, self._file_id) 73 | error_message = self.__get_error() 74 | 75 | # Raise an exception if an error is present 76 | if error_message: 77 | raise UGridError(error_message) 78 | 79 | def __open(self, file_path: str, method: str) -> None: 80 | """Opens a NetCDF file containing a UGrid entities 81 | 82 | Comment 83 | 84 | Args: 85 | file_path (bool): The path of the file to open 86 | method (bool): The opening method ("r" for read, "w" for write, and "w+" for replace) 87 | """ 88 | 89 | file_mode = c_int(0) 90 | if method == "r": 91 | self.__execute_function(self.lib.ug_file_read_mode, byref(file_mode)) 92 | elif method == "w": 93 | self.__execute_function(self.lib.ug_file_write_mode, byref(file_mode)) 94 | elif method == "w+": 95 | self.__execute_function(self.lib.ug_file_replace_mode, byref(file_mode)) 96 | else: 97 | raise ValueError("Unsupported file mode") 98 | 99 | self._file_id = c_int(-1) 100 | 101 | file_path_bytes = bytes(file_path, encoding="utf8") 102 | self.__execute_function( 103 | self.lib.ug_file_open, 104 | file_path_bytes, 105 | file_mode, 106 | byref(self._file_id), 107 | ) 108 | 109 | def __get_name_size(self): 110 | """Get the size of name strings""" 111 | name_size = c_int(0) 112 | self.__execute_function(self.lib.ug_name_get_length, byref(name_size)) 113 | return name_size.value 114 | 115 | def __get_name_long_size(self): 116 | """Get the size of long name strings""" 117 | name_long_size = c_int(0) 118 | self.__execute_function(self.lib.ug_name_get_long_length, byref(name_long_size)) 119 | return name_long_size.value 120 | 121 | def network1d_get_num_topologies(self) -> int: 122 | """Gets the number of network topologies contained in the file. 123 | 124 | Returns: 125 | int: The number of network topologies contained in the file. 126 | """ 127 | 128 | topology_enum = self.topology_get_network1d_enum() 129 | 130 | topology_count = c_int(0) 131 | self.__execute_function( 132 | self.lib.ug_topology_get_count, 133 | self._file_id, 134 | c_int(topology_enum), 135 | byref(topology_count), 136 | ) 137 | return topology_count.value 138 | 139 | def __network1d_inquire(self, topology_id) -> CUGridNetwork1D: 140 | """For internal use only. 141 | 142 | Inquires the network1d dimensions and names. 143 | 144 | Args: 145 | topology_id (int): The index of the network topology to inquire. 146 | 147 | Returns: 148 | CUGridNetwork1D: The network1d dimensions. 149 | """ 150 | 151 | c_ugrid_network1d = CUGridNetwork1D() 152 | self.__execute_function( 153 | self.lib.ug_network1d_inq, 154 | self._file_id, 155 | c_int(topology_id), 156 | byref(c_ugrid_network1d), 157 | ) 158 | return c_ugrid_network1d 159 | 160 | def network1d_get(self, topology_id) -> UGridNetwork1D: 161 | """Gets the network1d data. 162 | 163 | Args: 164 | topology_id (int): The index of the network1d topology to retrieve. 165 | 166 | Returns: 167 | UGridNetwork1D: The network1d (dimensions and data) 168 | """ 169 | 170 | c_ugrid_network1d = self.__network1d_inquire(topology_id) 171 | name_size = self.__get_name_size() 172 | name_long_size = self.__get_name_long_size() 173 | 174 | ugrid_network1d = c_ugrid_network1d.allocate_memory(name_size, name_long_size) 175 | self.__execute_function( 176 | self.lib.ug_network1d_get, 177 | self._file_id, 178 | c_int(topology_id), 179 | byref(c_ugrid_network1d), 180 | ) 181 | 182 | ugrid_network1d.is_spherical = bool(c_ugrid_network1d.is_spherical) 183 | ugrid_network1d.start_index = c_ugrid_network1d.start_index 184 | ugrid_network1d.name = decode_byte_vector_to_string( 185 | c_ugrid_network1d.name, name_size 186 | ) 187 | ugrid_network1d.node_id = decode_byte_vector_to_list_of_strings( 188 | c_ugrid_network1d.node_id, c_ugrid_network1d.num_nodes, name_size 189 | ) 190 | ugrid_network1d.node_long_name = decode_byte_vector_to_list_of_strings( 191 | c_ugrid_network1d.node_long_name, 192 | c_ugrid_network1d.num_nodes, 193 | name_long_size, 194 | ) 195 | 196 | ugrid_network1d.edge_id = decode_byte_vector_to_list_of_strings( 197 | c_ugrid_network1d.edge_id, 198 | c_ugrid_network1d.num_edges, 199 | name_size, 200 | ) 201 | ugrid_network1d.edge_long_name = decode_byte_vector_to_list_of_strings( 202 | c_ugrid_network1d.edge_long_name, 203 | c_ugrid_network1d.num_edges, 204 | name_long_size, 205 | ) 206 | 207 | return ugrid_network1d 208 | 209 | def network1d_define(self, network1d: UGridNetwork1D) -> int: 210 | """Defines a new network1d in a UGrid file. 211 | 212 | Args: 213 | network1d (UGridNetwork1D): A network1d (dimensions and data) 214 | 215 | Returns: 216 | int: The index of the defined network topology. 217 | """ 218 | 219 | name_size = self.__get_name_size() 220 | name_long_size = self.__get_name_long_size() 221 | 222 | c_ugrid_network = CUGridNetwork1D.from_py_structure( 223 | network1d, name_size, name_long_size 224 | ) 225 | 226 | c_topology_id = c_int(-1) 227 | 228 | self.__execute_function( 229 | self.lib.ug_network1d_def, 230 | self._file_id, 231 | byref(c_ugrid_network), 232 | byref(c_topology_id), 233 | ) 234 | 235 | return c_topology_id.value 236 | 237 | def network1d_put(self, topology_id: int, network1d: UGridNetwork1D) -> None: 238 | """Writes a new network1d to UGrid file. 239 | 240 | Args: 241 | topology_id (int): The index of the network1d topology to write. 242 | network1d (UGridNetwork1D): An instance of Network1D (with dimensions and data) 243 | """ 244 | 245 | name_size = self.__get_name_size() 246 | name_long_size = self.__get_name_long_size() 247 | 248 | c_ugrid_network = CUGridNetwork1D.from_py_structure( 249 | network1d, name_size, name_long_size 250 | ) 251 | 252 | self.__execute_function( 253 | self.lib.ug_network1d_put, 254 | self._file_id, 255 | c_int(topology_id), 256 | byref(c_ugrid_network), 257 | ) 258 | 259 | def mesh1d_get_num_topologies(self) -> int: 260 | """Gets the number of mesh1d topologies contained in the file. 261 | 262 | Returns: 263 | int: The number of mesh1d topologies contained in the file. 264 | """ 265 | 266 | topology_enum = self.topology_get_mesh1d_enum() 267 | 268 | topology_count = c_int(0) 269 | self.__execute_function( 270 | self.lib.ug_topology_get_count, 271 | self._file_id, 272 | c_int(topology_enum), 273 | byref(topology_count), 274 | ) 275 | return topology_count.value 276 | 277 | def __mesh1d_inquire(self, topology_id) -> CUGridMesh1D: 278 | """For internal use only. 279 | 280 | Inquires the mesh1d dimensions and names. 281 | 282 | Args: 283 | topology_id (int): The index of the mesh1d topology to inquire. 284 | 285 | Returns: 286 | CUGridMesh1D: The mesh1d dimensions. 287 | 288 | """ 289 | 290 | c_ugrid_mesh1d = CUGridMesh1D() 291 | self.__execute_function( 292 | self.lib.ug_mesh1d_inq, 293 | self._file_id, 294 | c_int(topology_id), 295 | byref(c_ugrid_mesh1d), 296 | ) 297 | return c_ugrid_mesh1d 298 | 299 | def mesh1d_get(self, topology_id) -> UGridMesh1D: 300 | """Gets the mesh1d data. 301 | 302 | Args: 303 | topology_id (int): The index of the mesh1d topology to retrieve. 304 | 305 | Returns: 306 | UGridMesh1D: The mesh1d (dimensions and data) 307 | """ 308 | 309 | c_mesh1d = self.__mesh1d_inquire(topology_id) 310 | name_size = self.__get_name_size() 311 | name_long_size = self.__get_name_long_size() 312 | 313 | ugrid_mesh1d = c_mesh1d.allocate_memory(name_size, name_long_size) 314 | 315 | self.__execute_function( 316 | self.lib.ug_mesh1d_get, 317 | self._file_id, 318 | c_int(topology_id), 319 | byref(c_mesh1d), 320 | ) 321 | 322 | ugrid_mesh1d.is_spherical = bool(c_mesh1d.is_spherical) 323 | ugrid_mesh1d.start_index = c_mesh1d.start_index 324 | ugrid_mesh1d.double_fill_value = c_mesh1d.double_fill_value 325 | ugrid_mesh1d.int_fill_value = c_mesh1d.int_fill_value 326 | 327 | ugrid_mesh1d.name = decode_byte_vector_to_string(c_mesh1d.name, name_size) 328 | ugrid_mesh1d.network_name = decode_byte_vector_to_string( 329 | c_mesh1d.network_name, name_size 330 | ) 331 | 332 | ugrid_mesh1d.node_name_id = decode_byte_vector_to_list_of_strings( 333 | c_mesh1d.node_name_id, c_mesh1d.num_nodes, name_size 334 | ) 335 | ugrid_mesh1d.node_name_long = decode_byte_vector_to_list_of_strings( 336 | c_mesh1d.node_name_long, c_mesh1d.num_nodes, name_long_size 337 | ) 338 | 339 | return ugrid_mesh1d 340 | 341 | def mesh1d_define(self, mesh1d: UGridMesh1D) -> int: 342 | """Defines a new mesh1d in a UGrid file. 343 | 344 | Args: 345 | mesh1d (UGridMesh1D): A mesh1d (dimensions and data) 346 | 347 | Returns: 348 | int: The index of the defined mesh1d topology. 349 | """ 350 | 351 | name_size = self.__get_name_size() 352 | name_long_size = self.__get_name_long_size() 353 | 354 | c_ugrid_mesh1d = CUGridMesh1D.from_py_structure( 355 | mesh1d, name_size, name_long_size 356 | ) 357 | 358 | c_topology_id = c_int(-1) 359 | 360 | self.__execute_function( 361 | self.lib.ug_mesh1d_def, 362 | self._file_id, 363 | byref(c_ugrid_mesh1d), 364 | byref(c_topology_id), 365 | ) 366 | 367 | return c_topology_id.value 368 | 369 | def mesh1d_put(self, topology_id: int, mesh1d: UGridMesh1D) -> None: 370 | """Writes a new mesh1d to UGrid file. 371 | 372 | Args: 373 | topology_id (int): The index of the mesh1d topology to write. 374 | mesh1d (UGridMesh1D): An instance of mesh1d (with dimensions and data) 375 | """ 376 | 377 | name_size = self.__get_name_size() 378 | name_long_size = self.__get_name_long_size() 379 | 380 | c_ugrid_mesh1d = CUGridMesh1D.from_py_structure( 381 | mesh1d, name_size, name_long_size 382 | ) 383 | 384 | self.__execute_function( 385 | self.lib.ug_mesh1d_put, 386 | self._file_id, 387 | c_int(topology_id), 388 | byref(c_ugrid_mesh1d), 389 | ) 390 | 391 | def mesh2d_get_num_topologies(self) -> int: 392 | """Description 393 | 394 | Gets the number of mesh2d topologies contained in the file. 395 | 396 | Returns: 397 | int: The number of mesh2d topologies contained in the file. 398 | 399 | """ 400 | 401 | topology_enum = self.topology_get_mesh2d_enum() 402 | 403 | topology_count = c_int(0) 404 | self.__execute_function( 405 | self.lib.ug_topology_get_count, 406 | self._file_id, 407 | c_int(topology_enum), 408 | byref(topology_count), 409 | ) 410 | return topology_count.value 411 | 412 | def __mesh2d_inquire(self, topology_id) -> CUGridMesh2D: 413 | """For internal use only. 414 | 415 | Inquires the mesh2d dimensions and names. 416 | 417 | Args: 418 | topology_id (int): The index of the mesh2d topology to inquire. 419 | 420 | Returns: 421 | CUGridMesh2D: The mesh2d dimensions. 422 | 423 | """ 424 | 425 | c_ugrid_mesh2d = CUGridMesh2D() 426 | self.__execute_function( 427 | self.lib.ug_mesh2d_inq, 428 | self._file_id, 429 | c_int(topology_id), 430 | byref(c_ugrid_mesh2d), 431 | ) 432 | return c_ugrid_mesh2d 433 | 434 | def mesh2d_get(self, topology_id) -> UGridMesh2D: 435 | """Gets the mesh2d data. 436 | 437 | Args: 438 | topology_id (int): The index of the mesh2d topology to retrieve. 439 | 440 | Returns: 441 | UGridMesh2D: The mesh2d (dimensions and data) 442 | """ 443 | 444 | c_ugrid_mesh2d = self.__mesh2d_inquire(topology_id) 445 | long_name_size = self.__get_name_long_size() 446 | 447 | ugrid_mesh2d = c_ugrid_mesh2d.allocate_memory(long_name_size) 448 | self.__execute_function( 449 | self.lib.ug_mesh2d_get, 450 | self._file_id, 451 | c_int(topology_id), 452 | byref(c_ugrid_mesh2d), 453 | ) 454 | 455 | ugrid_mesh2d.is_spherical = bool(c_ugrid_mesh2d.is_spherical) 456 | ugrid_mesh2d.start_index = c_ugrid_mesh2d.start_index 457 | ugrid_mesh2d.double_fill_value = c_ugrid_mesh2d.double_fill_value 458 | ugrid_mesh2d.int_fill_value = c_ugrid_mesh2d.int_fill_value 459 | 460 | ugrid_mesh2d.name = decode_byte_vector_to_string( 461 | c_ugrid_mesh2d.name, long_name_size 462 | ) 463 | 464 | return ugrid_mesh2d 465 | 466 | def mesh2d_define(self, ugrid_mesh2d: UGridMesh2D) -> int: 467 | """Defines a new mesh2d in a UGrid file. 468 | 469 | Args: 470 | ugrid_mesh2d (UGridMesh2D): A mesh2d (dimensions and data) 471 | 472 | Returns: 473 | int: The index of the defined mesh2d topology. 474 | """ 475 | 476 | long_name_size = self.__get_name_long_size() 477 | 478 | c_ugrid_mesh2d = CUGridMesh2D.from_py_structure(ugrid_mesh2d, long_name_size) 479 | 480 | c_topology_id = c_int(-1) 481 | 482 | self.__execute_function( 483 | self.lib.ug_mesh2d_def, 484 | self._file_id, 485 | byref(c_ugrid_mesh2d), 486 | byref(c_topology_id), 487 | ) 488 | 489 | return c_topology_id.value 490 | 491 | def mesh2d_put(self, topology_id: int, ugrid_mesh2d: UGridMesh2D) -> None: 492 | """Writes a new mesh2d in a UGrid file. 493 | 494 | Args: 495 | topology_id (int): The index of the mesh2d topology to write. 496 | ugrid_mesh2d (UGridMesh2D): A mesh2d (dimensions and data) 497 | """ 498 | 499 | name_size = self.__get_name_size() 500 | c_ugrid_mesh2d = CUGridMesh2D.from_py_structure(ugrid_mesh2d, name_size) 501 | 502 | self.__execute_function( 503 | self.lib.ug_mesh2d_put, 504 | self._file_id, 505 | c_int(topology_id), 506 | byref(c_ugrid_mesh2d), 507 | ) 508 | 509 | def from_meshkernel_mesh2d_to_ugrid_mesh2d( 510 | self, mesh2d: Mesh2d, name: str, is_spherical: bool 511 | ) -> UGridMesh2D: 512 | """Converts a meshkernel mesh2d to ugrid mesh2d 513 | 514 | Args: 515 | mesh2d (Mesh2d): An instance of a meshkernel mesh2d 516 | name (str): The name of mesh2d 517 | is_spherical (bool): Spherical or cartesian coordinate system 518 | """ 519 | num_faces = len(mesh2d.nodes_per_face) 520 | 521 | if num_faces > 0: 522 | num_face_nodes_max = np.max(mesh2d.nodes_per_face) 523 | 524 | def fill_int_face_array(face_array): 525 | if len(face_array) == 0: 526 | return np.array([], dtype=np.int32) 527 | 528 | result = np.full( 529 | num_faces * num_face_nodes_max, 530 | dtype=np.int32, 531 | fill_value=self.__get_int_fill_value(), 532 | ) 533 | index = 0 534 | for face_index, num_face_nodes in enumerate(mesh2d.nodes_per_face): 535 | current_index = face_index * num_face_nodes_max 536 | result[current_index : current_index + num_face_nodes] = face_array[ 537 | index : index + num_face_nodes 538 | ] 539 | index = index + num_face_nodes 540 | return result 541 | 542 | face_nodes_flat_array = fill_int_face_array(mesh2d.face_nodes) 543 | face_edges_flat_array = fill_int_face_array(mesh2d.face_edges) 544 | 545 | return UGridMesh2D( 546 | name=name, 547 | node_x=mesh2d.node_x, 548 | node_y=mesh2d.node_y, 549 | edge_node=mesh2d.edge_nodes, 550 | face_nodes=face_nodes_flat_array, 551 | face_edges=face_edges_flat_array, 552 | edge_faces=mesh2d.edge_faces, 553 | edge_x=mesh2d.edge_x, 554 | edge_y=mesh2d.edge_y, 555 | face_x=mesh2d.face_x, 556 | face_y=mesh2d.face_y, 557 | num_face_nodes_max=num_face_nodes_max, 558 | is_spherical=is_spherical, 559 | double_fill_value=-999.0, 560 | int_fill_value=-999, 561 | ) 562 | 563 | return UGridMesh2D( 564 | name=name, 565 | node_x=mesh2d.node_x, 566 | node_y=mesh2d.node_y, 567 | edge_node=mesh2d.edge_nodes, 568 | ) 569 | 570 | def from_meshkernel_mesh1d_to_ugrid_mesh1d( 571 | self, 572 | mesh1d: Mesh1d, 573 | name: str, 574 | network_name: str, 575 | node_edge_id: np.ndarray, 576 | node_edge_offset: np.ndarray, 577 | node_name_id: list, 578 | node_name_long: list, 579 | edge_edge_id: np.ndarray, 580 | edge_edge_offset: np.ndarray, 581 | edge_x: np.ndarray, 582 | edge_y: np.ndarray, 583 | double_fill_value: float, 584 | int_fill_value: int, 585 | ) -> UGridMesh1D: 586 | """Converts a meshkernel mesh1d to a ugrid mesh1d 587 | 588 | Args: 589 | mesh1d (Mesh1d): An instance of a meshkernel mesh1d 590 | network_name (c_char_p): The x-coordinates of the network node. 591 | node_x (ndarray): The node x coordinate. 592 | node_y (ndarray): The node y coordinate. 593 | node_edge_id (ndarray): The network edge id where every node lies. 594 | node_edge_offset (ndarray): The offset of each node on the network edge 595 | node_name_id (list): A list of node names ids. 596 | node_name_long (c_char_p): A list of node long names. 597 | edge_edge_id (ndarray): The network edge id where every edge lies. 598 | edge_edge_offset (ndarray): The offset of each edge on the network edge. 599 | edge_x (ndarray): The edge x coordinate. 600 | edge_y (ndarray): The edge y coordinate. 601 | double_fill_value (c_double): The fill value for array of doubles. 602 | int_fill_value (c_int): The fill value for array of integers. 603 | """ 604 | 605 | ugrid_mesh1d = UGridMesh1D( 606 | name=name, 607 | network_name=network_name, 608 | node_x=mesh1d.node_x, 609 | node_y=mesh1d.node_y, 610 | edge_node=mesh1d.edge_nodes, 611 | node_edge_id=node_edge_id, 612 | node_edge_offset=node_edge_offset, 613 | node_name_id=node_name_id, 614 | node_name_long=node_name_long, 615 | edge_edge_id=edge_edge_id, 616 | edge_edge_offset=edge_edge_offset, 617 | edge_x=edge_x, 618 | edge_y=edge_y, 619 | double_fill_value=double_fill_value, 620 | int_fill_value=int_fill_value, 621 | ) 622 | 623 | return ugrid_mesh1d 624 | 625 | def from_meshkernel_contacts_to_ugrid_contacts( 626 | self, 627 | contacts: Contacts, 628 | name: str, 629 | contact_type: np.ndarray, 630 | contact_name_id: str, 631 | contact_name_long: str, 632 | mesh_from_name: str, 633 | mesh_to_name: str, 634 | mesh_from_location: int, 635 | mesh_to_location: int, 636 | ) -> UGridContacts: 637 | """Converts a meshkernel mesh1d to a ugrid mesh1d 638 | 639 | Args: 640 | contacts (Contacts): An instance of a meshkernel contacts entity 641 | name (str): The name of the contact entity. 642 | contact_type (ndarray): For each contact its type. 643 | contact_name_id (list): The name of each contact. 644 | contact_name_long (list): The long name of each contact. 645 | mesh_from_name (str): The name of the mesh where the contacts start. 646 | mesh_to_name (str): The name of the mesh where the contacts ends. 647 | mesh_from_location (c_int): The location type (node, edge or face) at the contact start. 648 | mesh_to_location (c_int): The location type (node, edge or face) at the contact end. 649 | """ 650 | 651 | num_edges = len(contacts.mesh1d_indices) 652 | edges_array = np.full( 653 | num_edges * 2, dtype=np.int32, fill_value=self.__get_int_fill_value() 654 | ) 655 | for index, (mesh1d_indices, mesh2d_indices) in enumerate( 656 | zip(contacts.mesh1d_indices, contacts.mesh2d_indices) 657 | ): 658 | edges_array[index * 2] = mesh1d_indices 659 | edges_array[index * 2 + 1] = mesh2d_indices 660 | 661 | ugrid_contacts = UGridContacts( 662 | name=name, 663 | edges=edges_array, 664 | mesh_from_name=mesh_from_name, 665 | mesh_to_name=mesh_to_name, 666 | contact_type=contact_type, 667 | contact_name_id=contact_name_id, 668 | contact_name_long=contact_name_long, 669 | mesh_from_location=mesh_from_location, 670 | mesh_to_location=mesh_to_location, 671 | ) 672 | 673 | return ugrid_contacts 674 | 675 | def contacts_get_num_topologies(self) -> int: 676 | """Description 677 | 678 | Gets the number of contacts topologies contained in the file. 679 | 680 | Returns: 681 | int: The number of contacts topologies contained in the file. 682 | 683 | """ 684 | 685 | topology_enum = self.topology_get_contacts_enum() 686 | 687 | topology_count = c_int(0) 688 | self.__execute_function( 689 | self.lib.ug_topology_get_count, 690 | self._file_id, 691 | c_int(topology_enum), 692 | byref(topology_count), 693 | ) 694 | return topology_count.value 695 | 696 | def __contacts_inquire(self, topology_id) -> CUGridContacts: 697 | """For internal use only. 698 | 699 | Inquires the contacts dimensions and names. 700 | 701 | Args: 702 | topology_id (int): The index of the contacts topology to inquire. 703 | 704 | Returns: 705 | CUGridContacts: The contacts dimensions. 706 | 707 | """ 708 | 709 | c_ugrid_contacts = CUGridContacts() 710 | self.__execute_function( 711 | self.lib.ug_contacts_inq, 712 | self._file_id, 713 | c_int(topology_id), 714 | byref(c_ugrid_contacts), 715 | ) 716 | return c_ugrid_contacts 717 | 718 | def contacts_get(self, topology_id) -> UGridContacts: 719 | """Gets the contacts data. 720 | 721 | Args: 722 | topology_id (int): The index of the contacts topology to retrieve. 723 | 724 | Returns: 725 | UGridContacts: The contacts (dimensions and data) 726 | """ 727 | 728 | c_ugrid_contacts = self.__contacts_inquire(topology_id) 729 | name_size = self.__get_name_size() 730 | name_long_size = self.__get_name_long_size() 731 | 732 | ugrid_contacts = c_ugrid_contacts.allocate_memory(name_size, name_long_size) 733 | self.__execute_function( 734 | self.lib.ug_contacts_get, 735 | self._file_id, 736 | c_int(topology_id), 737 | byref(c_ugrid_contacts), 738 | ) 739 | 740 | ugrid_contacts.name = decode_byte_vector_to_string( 741 | c_ugrid_contacts.name, name_size 742 | ) 743 | ugrid_contacts.contact_name_id = decode_byte_vector_to_list_of_strings( 744 | c_ugrid_contacts.contact_name_id, 745 | c_ugrid_contacts.num_contacts, 746 | name_size, 747 | ) 748 | ugrid_contacts.contact_name_long = decode_byte_vector_to_list_of_strings( 749 | c_ugrid_contacts.contact_name_long, 750 | c_ugrid_contacts.num_contacts, 751 | name_long_size, 752 | ) 753 | 754 | ugrid_contacts.mesh_from_name = decode_byte_vector_to_string( 755 | c_ugrid_contacts.mesh_from_name, name_size 756 | ) 757 | ugrid_contacts.mesh_to_name = decode_byte_vector_to_string( 758 | c_ugrid_contacts.mesh_to_name, name_size 759 | ) 760 | 761 | return ugrid_contacts 762 | 763 | def contacts_define(self, contacts: UGridContacts) -> int: 764 | """Defines a new contacts in a UGrid file. 765 | 766 | Args: 767 | contacts (UGridContacts): An instance of Contacts class (dimensions and data) 768 | 769 | Returns: 770 | int: The index of the defined contacts topology. 771 | """ 772 | 773 | name_size = self.__get_name_size() 774 | name_long_size = self.__get_name_long_size() 775 | 776 | c_ugrid_contacts = CUGridContacts.from_py_structure( 777 | contacts, name_size, name_long_size 778 | ) 779 | 780 | c_topology_id = c_int(-1) 781 | 782 | self.__execute_function( 783 | self.lib.ug_contacts_def, 784 | self._file_id, 785 | byref(c_ugrid_contacts), 786 | byref(c_topology_id), 787 | ) 788 | 789 | return c_topology_id.value 790 | 791 | def contacts_put(self, topology_id: int, contacts: UGridContacts) -> None: 792 | """Writes a new contacts in a UGrid file. 793 | 794 | Args: 795 | topology_id (int): The index of the contacts topology to write. 796 | contacts (UGridContacts): A contacts (dimensions and data) 797 | """ 798 | name_size = self.__get_name_size() 799 | name_long_size = self.__get_name_long_size() 800 | 801 | c_ugrid_contacts = CUGridContacts.from_py_structure( 802 | contacts, name_size, name_long_size 803 | ) 804 | 805 | self.__execute_function( 806 | self.lib.ug_contacts_put, 807 | self._file_id, 808 | c_int(topology_id), 809 | byref(c_ugrid_contacts), 810 | ) 811 | 812 | def __get_error(self) -> str: 813 | c_error_message = c_char_p() 814 | self.__execute_function(self.lib.ug_error_get, byref(c_error_message)) 815 | return c_error_message.value.decode("ASCII") 816 | 817 | def get_ugrid_version(self) -> str: 818 | """Get the version of the C++ UGrid library 819 | 820 | Returns: 821 | str: The version string 822 | """ 823 | 824 | c_ugrid_version = c_char_p() 825 | self.__execute_function(self.lib.mkernel_get_version, byref(c_ugrid_version)) 826 | return c_ugrid_version.value.decode("ASCII") 827 | 828 | def get_ugridpy_version(self) -> str: 829 | """Get the version of this Python wrapper 830 | 831 | Returns: 832 | str: The version string 833 | """ 834 | 835 | return __version__ 836 | 837 | def __execute_function(self, function: Callable, *args): 838 | """Utility function to execute a C function of UGrid and checks its status. 839 | 840 | Args: 841 | function (Callable): The function which we want to call. 842 | args: Arguments which will be passed to `function`. 843 | 844 | Raises: 845 | UGridError: This exception gets raised, 846 | if the UGrid library reports an error. 847 | """ 848 | if function(*args) != Status.SUCCESS: 849 | error_message = self.__get_error() 850 | raise UGridError(error_message) 851 | 852 | def entity_get_node_location_enum(self) -> int: 853 | """Get the node location enum value 854 | 855 | Returns: 856 | int: The node location enum value 857 | """ 858 | 859 | location = c_int(0) 860 | self.__execute_function( 861 | self.lib.ug_entity_get_node_location_enum, byref(location) 862 | ) 863 | return location.value 864 | 865 | def entity_get_edge_location_enum(self) -> int: 866 | """Get the edge location enum value 867 | 868 | Returns: 869 | int: The edge location enum value 870 | """ 871 | 872 | location = c_int(0) 873 | self.__execute_function( 874 | self.lib.ug_entity_get_edge_location_enum, byref(location) 875 | ) 876 | return location.value 877 | 878 | def entity_get_face_location_enum(self) -> int: 879 | """Get the face location enum value 880 | 881 | Returns: 882 | int: The face location enum value 883 | """ 884 | 885 | location = c_int(0) 886 | self.__execute_function( 887 | self.lib.ug_entity_get_face_location_enum, byref(location) 888 | ) 889 | return location.value 890 | 891 | def topology_get_network1d_enum(self) -> int: 892 | """Gets the topology enum value associated with network1d. 893 | 894 | Returns: 895 | int: the topology enum value associated with network1d. 896 | """ 897 | topology_enum = c_int(0) 898 | self.__execute_function( 899 | self.lib.ug_topology_get_network1d_enum, byref(topology_enum) 900 | ) 901 | 902 | return topology_enum.value 903 | 904 | def topology_get_mesh1d_enum(self) -> int: 905 | """Gets the topology enum value associated with mesh1d. 906 | 907 | Returns: 908 | int: the topology enum value associated with mesh1d. 909 | """ 910 | topology_enum = c_int(0) 911 | self.__execute_function( 912 | self.lib.ug_topology_get_mesh1d_enum, byref(topology_enum) 913 | ) 914 | 915 | return topology_enum.value 916 | 917 | def topology_get_mesh2d_enum(self) -> int: 918 | """Gets the topology enum value associated with mesh2d. 919 | 920 | Returns: 921 | int: the topology enum value associated with mesh2d. 922 | """ 923 | topology_enum = c_int(0) 924 | self.__execute_function( 925 | self.lib.ug_topology_get_mesh2d_enum, byref(topology_enum) 926 | ) 927 | 928 | return topology_enum.value 929 | 930 | def topology_get_contacts_enum(self) -> int: 931 | """Gets the topology enum value associated with contacts. 932 | 933 | Returns: 934 | int: the topology enum value associated with contacts. 935 | """ 936 | topology_enum = c_int(0) 937 | self.__execute_function( 938 | self.lib.ug_topology_get_contacts_enum, byref(topology_enum) 939 | ) 940 | 941 | return topology_enum.value 942 | 943 | def __topology_count_data_variables( 944 | self, topology_id: int, topology_type: int, location: int 945 | ) -> int: 946 | """Gets the number of data variables for a specific topology at a specific location 947 | 948 | Args: 949 | topology_id (int): The index of the topology type. 950 | topology_type (int): The list with the attributes values. 951 | location (int): The location type 952 | 953 | Returns: 954 | int: The number of data 955 | """ 956 | 957 | data_variable_count = c_int(0) 958 | self.__execute_function( 959 | self.lib.ug_topology_count_data_variables, 960 | self._file_id, 961 | c_int(topology_type), 962 | c_int(topology_id), 963 | c_int(location), 964 | byref(data_variable_count), 965 | ) 966 | 967 | return data_variable_count.value 968 | 969 | def topology_get_data_variables( 970 | self, topology_id: int, topology_type: int, location: int 971 | ) -> list: 972 | """Gets the data variables for a specific topology at a specific location 973 | 974 | Args: 975 | topology_id (int): The index of the topology type. 976 | topology_type (int): The list with the attributes values. 977 | location (int): The location type 978 | 979 | Returns: 980 | list: The list of data variables 981 | """ 982 | 983 | num_data_variables = self.__topology_count_data_variables( 984 | topology_id, topology_type, location 985 | ) 986 | name_long_size = self.__get_name_long_size() 987 | 988 | buffer_size = name_long_size * num_data_variables 989 | string_buffer = " " * buffer_size 990 | 991 | string_buffer_encoded = c_char_p(string_buffer.encode("ASCII")) 992 | self.__execute_function( 993 | self.lib.ug_topology_count_data_variables, 994 | self._file_id, 995 | c_int(topology_type), 996 | c_int(topology_id), 997 | c_int(location), 998 | string_buffer_encoded, 999 | ) 1000 | 1001 | attribute_list = decode_byte_vector_to_list_of_strings( 1002 | string_buffer_encoded.value, num_data_variables, name_long_size 1003 | ) 1004 | 1005 | return attribute_list.value 1006 | 1007 | def __adjust_name(self, name: str) -> str: 1008 | long_name = self.__get_name_long_size() 1009 | name_long = name.ljust(long_name) 1010 | return name_long 1011 | 1012 | def __variable_count_attributes(self, variable_name: str) -> int: 1013 | """Counts the number of variable attributes. 1014 | 1015 | Args: 1016 | variable_name (str): The variable name. 1017 | 1018 | Returns: 1019 | int: The number of attributes 1020 | """ 1021 | 1022 | attributes_count = c_int(0) 1023 | variable_name_long = self.__adjust_name(variable_name) 1024 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1025 | self.__execute_function( 1026 | self.lib.ug_variable_count_attributes, 1027 | self._file_id, 1028 | c_variable_name_encoded, 1029 | byref(attributes_count), 1030 | ) 1031 | return attributes_count.value 1032 | 1033 | def variable_get_attributes_names(self, variable_name: str) -> list: 1034 | """Gets the variable attribute names. 1035 | 1036 | Args: 1037 | variable_name (str): The variable name. 1038 | 1039 | Returns: 1040 | list: A list containing the variable attribute names. 1041 | """ 1042 | 1043 | num_attributes = self.__variable_count_attributes(variable_name) 1044 | 1045 | name_long_size = self.__get_name_long_size() 1046 | buffer_size = name_long_size * num_attributes 1047 | attribute_buffer = " " * buffer_size 1048 | attribute_buffer_encoded = c_char_p(attribute_buffer.encode("ASCII")) 1049 | 1050 | variable_name_long = self.__adjust_name(variable_name) 1051 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1052 | 1053 | self.__execute_function( 1054 | self.lib.ug_variable_get_attributes_names, 1055 | self._file_id, 1056 | c_variable_name_encoded, 1057 | attribute_buffer_encoded, 1058 | ) 1059 | 1060 | attribute_list = decode_byte_vector_to_list_of_strings( 1061 | attribute_buffer_encoded.value, num_attributes, name_long_size 1062 | ) 1063 | 1064 | return attribute_list 1065 | 1066 | def variable_get_attributes_values(self, variable_name: str) -> list: 1067 | """Gets the variable attribute values. 1068 | 1069 | Args: 1070 | variable_name (str): The variable name. 1071 | 1072 | Returns: 1073 | list: A list containing the variable attribute values. 1074 | """ 1075 | 1076 | num_attributes = self.__variable_count_attributes(variable_name) 1077 | name_long_size = self.__get_name_long_size() 1078 | 1079 | buffer_size = name_long_size * num_attributes 1080 | string_buffer = " " * buffer_size 1081 | string_buffer_encoded = c_char_p(string_buffer.encode("ASCII")) 1082 | 1083 | variable_name_long = self.__adjust_name(variable_name) 1084 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1085 | 1086 | self.__execute_function( 1087 | self.lib.ug_variable_get_attributes_values, 1088 | self._file_id, 1089 | c_variable_name_encoded, 1090 | string_buffer_encoded, 1091 | ) 1092 | 1093 | attribute_list = decode_byte_vector_to_list_of_strings( 1094 | string_buffer_encoded.value, num_attributes, name_long_size 1095 | ) 1096 | 1097 | return attribute_list 1098 | 1099 | def __variable_get_dimensions(self, variable_name) -> np.ndarray: 1100 | """Gets the variable dimensions 1101 | 1102 | Args: 1103 | data_variable_name (str): The data variable name. 1104 | 1105 | Returns: 1106 | np.ndarray: An array with the dimension values 1107 | """ 1108 | 1109 | variable_name_long = self.__adjust_name(variable_name) 1110 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1111 | c_num_dimensions = c_int(0) 1112 | self.__execute_function( 1113 | self.lib.ug_variable_count_dimensions, 1114 | self._file_id, 1115 | c_variable_name_encoded, 1116 | byref(c_num_dimensions), 1117 | ) 1118 | 1119 | dimension_vec = np.empty(c_num_dimensions.value, dtype=np.int32) 1120 | dimension_vec_ptr = as_ctypes(dimension_vec) 1121 | self.__execute_function( 1122 | self.lib.ug_variable_get_data_dimensions, 1123 | self._file_id, 1124 | c_variable_name_encoded, 1125 | dimension_vec_ptr, 1126 | ) 1127 | return dimension_vec 1128 | 1129 | def variable_get_data_double(self, variable_name: str) -> np.ndarray: 1130 | """Gets the variable data as a flat array of double 1131 | 1132 | Args: 1133 | variable_name (str): The variable name. 1134 | 1135 | Returns: 1136 | np.ndarray: A numpy array with the variable data 1137 | """ 1138 | 1139 | dimension_vec = self.__variable_get_dimensions(variable_name) 1140 | data_vec_dimension = functools.reduce(operator.mul, dimension_vec) 1141 | 1142 | data_vec = np.empty(data_vec_dimension, dtype=np.double) 1143 | data_vec_ptr = as_ctypes(data_vec) 1144 | variable_name_long = self.__adjust_name(variable_name) 1145 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1146 | self.__execute_function( 1147 | self.lib.ug_variable_get_data_double, 1148 | self._file_id, 1149 | c_variable_name_encoded, 1150 | data_vec_ptr, 1151 | ) 1152 | 1153 | return data_vec 1154 | 1155 | def variable_get_data_int(self, variable_name: str) -> np.ndarray: 1156 | """Gets the variable data as a flat array of strings 1157 | 1158 | Args: 1159 | variable_name (str): The variable name. 1160 | 1161 | Returns: 1162 | np.ndarray: A numpy array with the variable data 1163 | """ 1164 | 1165 | dimension_vec = self.__variable_get_dimensions(variable_name) 1166 | 1167 | data_vec_dimension = functools.reduce(operator.mul, dimension_vec) 1168 | 1169 | data_vec = np.empty(data_vec_dimension, dtype=np.int32) 1170 | data_vec_ptr = as_ctypes(data_vec) 1171 | variable_name_long = self.__adjust_name(variable_name) 1172 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1173 | self.__execute_function( 1174 | self.lib.ug_variable_get_data_int, 1175 | self._file_id, 1176 | c_variable_name_encoded, 1177 | data_vec_ptr, 1178 | ) 1179 | 1180 | return data_vec 1181 | 1182 | def __variable_int_define(self, variable_name: str): 1183 | """Defines a new integer variable 1184 | 1185 | Args: 1186 | variable_name (str): The variable name. 1187 | """ 1188 | 1189 | variable_name_long = self.__adjust_name(variable_name) 1190 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1191 | 1192 | self.__execute_function( 1193 | self.lib.ug_variable_int_define, self._file_id, c_variable_name_encoded 1194 | ) 1195 | 1196 | def __attribute_define( 1197 | self, variable_name: str, attribute_name: str, attribute_values 1198 | ): 1199 | """Defines a new variable attribute with a name and an array of values. 1200 | 1201 | Args: 1202 | variable_name (str): The variable name. 1203 | attribute_name (str): The attribute name. 1204 | attribute_values: The attribute values. Can be a numpy array of int, floats or a string 1205 | """ 1206 | 1207 | variable_name_long = self.__adjust_name(variable_name) 1208 | c_variable_name_encoded = c_char_p(variable_name_long.encode("ASCII")) 1209 | 1210 | attribute_name_long = self.__adjust_name(attribute_name) 1211 | c_attribute_name_encoded = c_char_p(attribute_name_long.encode("ASCII")) 1212 | 1213 | attribute_values_len = c_int(len(attribute_values)) 1214 | 1215 | if isinstance( 1216 | attribute_values, np.ndarray 1217 | ) and attribute_values.dtype is np.dtype("int32"): 1218 | fun = self.lib.ug_attribute_int_define 1219 | c_attribute_values = numpy_array_to_ctypes(attribute_values) 1220 | elif isinstance( 1221 | attribute_values, np.ndarray 1222 | ) and attribute_values.dtype is np.dtype("float"): 1223 | fun = self.lib.ug_attribute_double_define 1224 | c_attribute_values = numpy_array_to_ctypes(attribute_values) 1225 | elif isinstance(attribute_values, str): 1226 | fun = self.lib.ug_attribute_char_define 1227 | c_attribute_values = c_char_p(attribute_values.encode("ASCII")) 1228 | else: 1229 | raise UGridError( 1230 | "__attribute_define: attribute_values types are not supported" 1231 | ) 1232 | 1233 | self.__execute_function( 1234 | fun, 1235 | self._file_id, 1236 | c_variable_name_encoded, 1237 | c_attribute_name_encoded, 1238 | c_attribute_values, 1239 | attribute_values_len, 1240 | ) 1241 | 1242 | def __get_int_fill_value(self): 1243 | """Gets the double fill value""" 1244 | fillValue = c_int(-1) 1245 | self.__execute_function(self.lib.ug_get_int_fill_value, byref(fillValue)) 1246 | return fillValue.value 1247 | 1248 | def __get_double_fill_value(self): 1249 | """Gets the double fill value""" 1250 | fillValue = c_double(-1) 1251 | self.__execute_function(self.lib.ug_get_double_fill_value, byref(fillValue)) 1252 | return fillValue.value 1253 | 1254 | def variable_int_with_attributes_define( 1255 | self, variable_name: str, variable_dict: dict 1256 | ): 1257 | """Defines a new variable with attributes. It can be used to write the coordinate reference system to file. 1258 | 1259 | Args: 1260 | variable_name (str): The new variable name. 1261 | variable_dict (dict): A dictionary containing the variable values and names 1262 | """ 1263 | 1264 | self.__variable_int_define(variable_name) 1265 | for names, values in variable_dict.items(): 1266 | self.__attribute_define(variable_name, names, values) 1267 | 1268 | def attribute_global_define(self, variable_dict: dict): 1269 | """Defines new global attributes 1270 | 1271 | Args: 1272 | variable_dict (dict): A dictionary containing the attribute names and values. 1273 | """ 1274 | for attribute_name, attribute_value in variable_dict.items(): 1275 | attribute_name_long = self.__adjust_name(attribute_name) 1276 | c_attribute_name_encoded = c_char_p(attribute_name_long.encode("ASCII")) 1277 | 1278 | c_attribute_value_encoded = c_char_p(attribute_value.encode("ASCII")) 1279 | attribute_values_len = c_int(len(attribute_value)) 1280 | 1281 | self.__execute_function( 1282 | self.lib.ug_attribute_global_char_define, 1283 | self._file_id, 1284 | c_attribute_name_encoded, 1285 | c_attribute_value_encoded, 1286 | attribute_values_len, 1287 | ) 1288 | 1289 | @staticmethod 1290 | def network1d_get_attributes(name: str): 1291 | """Get a dictionary of network1d default attribute names and the corresponding default values. 1292 | 1293 | Args: 1294 | name (str): The network1d name. 1295 | 1296 | Returns: 1297 | dict: A dictionary containing the attribute names and the corresponding default values. 1298 | """ 1299 | 1300 | return { 1301 | "cf_role": "mesh_topology", 1302 | "long_name": "Topology data of 1D network", 1303 | "topology_dimension": 1, 1304 | "node_dimension": f"{name}_nNodes", 1305 | "node_coordinates": f"{name}_node_x {name}_node_y", 1306 | "node_id": f"{name}_node_id", 1307 | "node_long_name": f"{name}_node_long_name", 1308 | "edge_dimension": f"{name}_nEdges", 1309 | "edge_node_connectivity": f"{name}_edge_nodes", 1310 | "edge_length": f"{name}_edge_length", 1311 | "edge_id": f"{name}_edge_id", 1312 | "edge_long_name": f"{name}_edge_long_name", 1313 | "edge_geometry": f"{name}_edge_geometry", 1314 | } 1315 | 1316 | @staticmethod 1317 | def mesh2d_get_attributes(name: str): 1318 | """Get a dictionary of mesh2d default attribute names. 1319 | 1320 | Args: 1321 | name (str): The mesh2d name. 1322 | 1323 | Returns: 1324 | dict: A dictionary containing the attribute names and the corresponding default values. 1325 | """ 1326 | return { 1327 | "cf_role": "mesh_topology", 1328 | "long_name": "Topology data of 2D mesh", 1329 | "topology_dimension": 2, 1330 | "node_dimension": f"{name}_nNodes", 1331 | "node_coordinates": f"{name}_node_x {name}_node_y", 1332 | "edge_dimension": f"{name}_nEdges", 1333 | "edge_node_connectivity": f"{name}_edge_nodes", 1334 | "face_dimension": f"{name}_nFaces", 1335 | "face_node_connectivity": f"{name}_face_nodes", 1336 | "max_face_nodes_dimension": f"{name}_nMax_face_nodes", 1337 | "face_coordinates": f"{name}_face_x {name}_face_y", 1338 | } 1339 | --------------------------------------------------------------------------------