FEMlium 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
femlium-0.1.0/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Francesco Ballarin <francesco.ballarin@unicatt.it>
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: FEMlium
3
+ Version: 0.1.0
4
+ Summary: Interactive geographic plots of finite element data with folium
5
+ Author-email: Francesco Ballarin <francesco.ballarin@unicatt.it>
6
+ Maintainer-email: Francesco Ballarin <francesco.ballarin@unicatt.it>
7
+ License: Copyright 2021-2025 FEMlium authors and contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14
+
15
+ Project-URL: homepage, https://femlium.github.io
16
+ Project-URL: repository, https://github.com/FEMlium/FEMlium
17
+ Project-URL: issues, https://github.com/FEMlium/FEMlium/issues
18
+ Project-URL: funding, https://github.com/sponsors/francesco-ballarin
19
+ Classifier: Development Status :: 5 - Production/Stable
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Intended Audience :: Science/Research
22
+ Classifier: License :: OSI Approved :: MIT License
23
+ Classifier: Operating System :: POSIX
24
+ Classifier: Operating System :: POSIX :: Linux
25
+ Classifier: Operating System :: MacOS :: MacOS X
26
+ Classifier: Programming Language :: Python
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Programming Language :: Python :: 3.9
29
+ Classifier: Programming Language :: Python :: 3.10
30
+ Classifier: Programming Language :: Python :: 3.11
31
+ Classifier: Programming Language :: Python :: 3.12
32
+ Classifier: Programming Language :: Python :: 3.13
33
+ Classifier: Programming Language :: Python :: 3.14
34
+ Classifier: Topic :: Scientific/Engineering :: GIS
35
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
36
+ Classifier: Topic :: Scientific/Engineering :: Visualization
37
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
38
+ Requires-Python: >=3.9
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ License-File: AUTHORS
42
+ Requires-Dist: branca
43
+ Requires-Dist: folium>=0.12.0
44
+ Requires-Dist: geojson
45
+ Requires-Dist: matplotlib
46
+ Requires-Dist: numpy
47
+ Requires-Dist: pyproj
48
+ Provides-Extra: backend-dolfinx
49
+ Requires-Dist: fenics-dolfinx>=0.8.0; extra == "backend-dolfinx"
50
+ Provides-Extra: backend-firedrake
51
+ Requires-Dist: firedrake>=2025.4.0; extra == "backend-firedrake"
52
+ Provides-Extra: docs
53
+ Requires-Dist: sphinx; extra == "docs"
54
+ Provides-Extra: lint
55
+ Requires-Dist: isort; extra == "lint"
56
+ Requires-Dist: nbqa; extra == "lint"
57
+ Requires-Dist: ruff; extra == "lint"
58
+ Requires-Dist: yamllint; extra == "lint"
59
+ Provides-Extra: tests
60
+ Requires-Dist: coverage[toml]; extra == "tests"
61
+ Requires-Dist: nbvalx[unit-tests]; extra == "tests"
62
+ Requires-Dist: pytest; extra == "tests"
63
+ Provides-Extra: tutorials
64
+ Requires-Dist: gmsh; extra == "tutorials"
65
+ Requires-Dist: meshio; extra == "tutorials"
66
+ Requires-Dist: nbvalx[notebooks]; extra == "tutorials"
67
+ Requires-Dist: viskex; extra == "tutorials"
68
+ Dynamic: license-file
69
+
70
+ ## FEMlium – interactive visualization of finite element simulations on geographic maps with folium ##
71
+ <img src="https://femlium.github.io/_images/FEMlium-logo.png" alt="FEMlium – interactive visualization of finite element simulations on geographic maps with folium" width="150px">
72
+
73
+ **FEMlium** is a library that enables visualizing finite element simulations on geographic maps using **folium**. Supported finite element backends are dolfinx and firedrake.
74
+
75
+ **FEMlium** is currently developed and maintained at [Università Cattolica del Sacro Cuore](https://www.unicatt.it/) by [Prof. Francesco Ballarin](https://www.francescoballarin.it).
76
+
77
+ Like **folium**, **FEMlium** is freely available under the MIT license.
78
+
79
+ Visit [femlium.github.io](https://femlium.github.io/) for installation instructions and additional information.
@@ -0,0 +1,22 @@
1
+ AUTHORS
2
+ LICENSE
3
+ README.md
4
+ pyproject.toml
5
+ FEMlium.egg-info/PKG-INFO
6
+ FEMlium.egg-info/SOURCES.txt
7
+ FEMlium.egg-info/dependency_links.txt
8
+ FEMlium.egg-info/requires.txt
9
+ FEMlium.egg-info/top_level.txt
10
+ femlium/__init__.py
11
+ femlium/base_mesh_plotter.py
12
+ femlium/base_plotter.py
13
+ femlium/base_solution_plotter.py
14
+ femlium/dolfinx_plotter.py
15
+ femlium/domain_plotter.py
16
+ femlium/firedrake_plotter.py
17
+ femlium/meshio_plotter.py
18
+ femlium/numpy_plotter.py
19
+ femlium/utils/__init__.py
20
+ femlium/utils/colorbar_wrapper.py
21
+ femlium/utils/geojson_with_arrows.py
22
+ femlium/utils/transformer_wrapper.py
@@ -0,0 +1,32 @@
1
+ branca
2
+ folium>=0.12.0
3
+ geojson
4
+ matplotlib
5
+ numpy
6
+ pyproj
7
+
8
+ [backend_dolfinx]
9
+ fenics-dolfinx>=0.8.0
10
+
11
+ [backend_firedrake]
12
+ firedrake>=2025.4.0
13
+
14
+ [docs]
15
+ sphinx
16
+
17
+ [lint]
18
+ isort
19
+ nbqa
20
+ ruff
21
+ yamllint
22
+
23
+ [tests]
24
+ coverage[toml]
25
+ nbvalx[unit-tests]
26
+ pytest
27
+
28
+ [tutorials]
29
+ gmsh
30
+ meshio
31
+ nbvalx[notebooks]
32
+ viskex
@@ -0,0 +1 @@
1
+ femlium
femlium-0.1.0/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2021-2025 FEMlium authors and contributors
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
femlium-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: FEMlium
3
+ Version: 0.1.0
4
+ Summary: Interactive geographic plots of finite element data with folium
5
+ Author-email: Francesco Ballarin <francesco.ballarin@unicatt.it>
6
+ Maintainer-email: Francesco Ballarin <francesco.ballarin@unicatt.it>
7
+ License: Copyright 2021-2025 FEMlium authors and contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14
+
15
+ Project-URL: homepage, https://femlium.github.io
16
+ Project-URL: repository, https://github.com/FEMlium/FEMlium
17
+ Project-URL: issues, https://github.com/FEMlium/FEMlium/issues
18
+ Project-URL: funding, https://github.com/sponsors/francesco-ballarin
19
+ Classifier: Development Status :: 5 - Production/Stable
20
+ Classifier: Intended Audience :: Developers
21
+ Classifier: Intended Audience :: Science/Research
22
+ Classifier: License :: OSI Approved :: MIT License
23
+ Classifier: Operating System :: POSIX
24
+ Classifier: Operating System :: POSIX :: Linux
25
+ Classifier: Operating System :: MacOS :: MacOS X
26
+ Classifier: Programming Language :: Python
27
+ Classifier: Programming Language :: Python :: 3
28
+ Classifier: Programming Language :: Python :: 3.9
29
+ Classifier: Programming Language :: Python :: 3.10
30
+ Classifier: Programming Language :: Python :: 3.11
31
+ Classifier: Programming Language :: Python :: 3.12
32
+ Classifier: Programming Language :: Python :: 3.13
33
+ Classifier: Programming Language :: Python :: 3.14
34
+ Classifier: Topic :: Scientific/Engineering :: GIS
35
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
36
+ Classifier: Topic :: Scientific/Engineering :: Visualization
37
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
38
+ Requires-Python: >=3.9
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ License-File: AUTHORS
42
+ Requires-Dist: branca
43
+ Requires-Dist: folium>=0.12.0
44
+ Requires-Dist: geojson
45
+ Requires-Dist: matplotlib
46
+ Requires-Dist: numpy
47
+ Requires-Dist: pyproj
48
+ Provides-Extra: backend-dolfinx
49
+ Requires-Dist: fenics-dolfinx>=0.8.0; extra == "backend-dolfinx"
50
+ Provides-Extra: backend-firedrake
51
+ Requires-Dist: firedrake>=2025.4.0; extra == "backend-firedrake"
52
+ Provides-Extra: docs
53
+ Requires-Dist: sphinx; extra == "docs"
54
+ Provides-Extra: lint
55
+ Requires-Dist: isort; extra == "lint"
56
+ Requires-Dist: nbqa; extra == "lint"
57
+ Requires-Dist: ruff; extra == "lint"
58
+ Requires-Dist: yamllint; extra == "lint"
59
+ Provides-Extra: tests
60
+ Requires-Dist: coverage[toml]; extra == "tests"
61
+ Requires-Dist: nbvalx[unit-tests]; extra == "tests"
62
+ Requires-Dist: pytest; extra == "tests"
63
+ Provides-Extra: tutorials
64
+ Requires-Dist: gmsh; extra == "tutorials"
65
+ Requires-Dist: meshio; extra == "tutorials"
66
+ Requires-Dist: nbvalx[notebooks]; extra == "tutorials"
67
+ Requires-Dist: viskex; extra == "tutorials"
68
+ Dynamic: license-file
69
+
70
+ ## FEMlium – interactive visualization of finite element simulations on geographic maps with folium ##
71
+ <img src="https://femlium.github.io/_images/FEMlium-logo.png" alt="FEMlium – interactive visualization of finite element simulations on geographic maps with folium" width="150px">
72
+
73
+ **FEMlium** is a library that enables visualizing finite element simulations on geographic maps using **folium**. Supported finite element backends are dolfinx and firedrake.
74
+
75
+ **FEMlium** is currently developed and maintained at [Università Cattolica del Sacro Cuore](https://www.unicatt.it/) by [Prof. Francesco Ballarin](https://www.francescoballarin.it).
76
+
77
+ Like **folium**, **FEMlium** is freely available under the MIT license.
78
+
79
+ Visit [femlium.github.io](https://femlium.github.io/) for installation instructions and additional information.
@@ -0,0 +1,10 @@
1
+ ## FEMlium – interactive visualization of finite element simulations on geographic maps with folium ##
2
+ <img src="https://femlium.github.io/_images/FEMlium-logo.png" alt="FEMlium – interactive visualization of finite element simulations on geographic maps with folium" width="150px">
3
+
4
+ **FEMlium** is a library that enables visualizing finite element simulations on geographic maps using **folium**. Supported finite element backends are dolfinx and firedrake.
5
+
6
+ **FEMlium** is currently developed and maintained at [Università Cattolica del Sacro Cuore](https://www.unicatt.it/) by [Prof. Francesco Ballarin](https://www.francescoballarin.it).
7
+
8
+ Like **folium**, **FEMlium** is freely available under the MIT license.
9
+
10
+ Visit [femlium.github.io](https://femlium.github.io/) for installation instructions and additional information.
@@ -0,0 +1,33 @@
1
+ # Copyright (C) 2021-2025 by the FEMlium authors
2
+ #
3
+ # This file is part of FEMlium.
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+ """FEMlium main module."""
7
+
8
+ from femlium.base_mesh_plotter import BaseMeshPlotter
9
+ from femlium.base_plotter import BasePlotter
10
+ from femlium.base_solution_plotter import BaseSolutionPlotter
11
+ from femlium.domain_plotter import DomainPlotter
12
+ from femlium.numpy_plotter import NumpyPlotter
13
+
14
+ try:
15
+ import meshio
16
+ except ImportError: # pragma: no cover
17
+ pass
18
+ else:
19
+ from femlium.meshio_plotter import MeshioPlotter
20
+
21
+ try:
22
+ import dolfinx
23
+ except ImportError:
24
+ pass
25
+ else:
26
+ from femlium.dolfinx_plotter import DolfinxPlotter
27
+
28
+ try:
29
+ import firedrake
30
+ except ImportError:
31
+ pass
32
+ else:
33
+ from femlium.firedrake_plotter import FiredrakePlotter
@@ -0,0 +1,263 @@
1
+ # Copyright (C) 2021-2025 by the FEMlium authors
2
+ #
3
+ # This file is part of FEMlium.
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+ """Base interface of a geographic plotter for mesh-related plots."""
7
+
8
+ import typing
9
+
10
+ import folium
11
+ import geojson
12
+ import numpy as np
13
+ import numpy.typing as npt
14
+
15
+ from femlium.base_plotter import BasePlotter
16
+ from femlium.utils import ColorbarWrapper
17
+
18
+
19
+ class BaseMeshPlotter(BasePlotter):
20
+ """Base interface of a geographic plotter for mesh-related plots."""
21
+
22
+ def add_mesh_to(
23
+ self, geo_map: folium.Map, vertices: npt.NDArray[np.float64], cells: npt.NDArray[np.int64],
24
+ cell_markers: typing.Optional[npt.NDArray[np.int64]] = None,
25
+ face_markers: typing.Optional[npt.NDArray[np.int64]] = None,
26
+ cell_colors: typing.Optional[typing.Union[str, dict[int, str]]] = None,
27
+ face_colors: typing.Optional[typing.Union[str, dict[int, str]]] = None,
28
+ face_weights: typing.Optional[typing.Union[int, dict[int, int]]] = None
29
+ ) -> None:
30
+ """
31
+ Add a triangular mesh to a folium map.
32
+
33
+ Parameters
34
+ ----------
35
+ geo_map
36
+ Map to which the mesh plot should be added.
37
+ vertices
38
+ Matrix containing the coordinates of the vertices.
39
+ The matrix should have as many rows as vertices in the mesh, and two columns.
40
+ cells
41
+ Matrix containing the connectivity of the cells.
42
+ The matrix should have as many rows as cells in the mesh, and three columns.
43
+ cell_markers
44
+ Vector containing a marker (i.e., an integer number) for each cell.
45
+ The vector should have as many entries as cells in the mesh.
46
+ If not provided, the marker will be set to 0 everywhere.
47
+ face_markers
48
+ Matrix containing a marker (i.e., an integer number) for each face.
49
+ The matrix should have the same shape of the cells argument.
50
+ Given a row index r, the entry face_markers[r, 0] is the marker of the
51
+ face connecting the first and second vertex of the r-th cell.
52
+ Similarly, face_markers[r, 1] is the marker associated to the face connecting
53
+ the second and third vertex of the r-th cell. Finally, face_markers[r, 2] is
54
+ the marker associated to the face connecting the first and third vertex of the
55
+ r-th cell.
56
+ If not provided, the marker will be set to 0 everywhere.
57
+ cell_colors
58
+ If a dictionary is provided, it should contain key: value pairs defining the mapping
59
+ marker: color for cells.
60
+ If a string is provided instead of a dictionary, the same color will be used for all
61
+ cell markers.
62
+ If not provided, the cells will not be colored.
63
+ face_colors
64
+ If a dictionary is provided, it should contain key: value pairs defining the mapping
65
+ marker: color for faces.
66
+ If a string is provided instead of a dictionary, the same color will be used for all
67
+ face markers.
68
+ If not provided, a default black color will be used for faces.
69
+ face_weights
70
+ Line weight of each face. Input should be provided following a similar convention for
71
+ the face_colors argument.
72
+ If not provided, a unit weight will be used.
73
+ """
74
+ if cell_markers is None:
75
+ cell_markers = np.zeros((cells.shape[0], ), dtype=np.int64)
76
+ else:
77
+ assert cell_markers.shape[0] == cells.shape[0]
78
+
79
+ if face_markers is None:
80
+ face_markers = np.zeros(cells.shape, dtype=np.int64)
81
+ else:
82
+ assert face_markers.shape == cells.shape
83
+
84
+ unique_cell_markers = np.unique(cell_markers).astype(int)
85
+ unique_face_markers = np.unique(face_markers).astype(int)
86
+ cell_colors = self._process_optional_argument_on_markers(cell_colors, "none", unique_cell_markers)
87
+ face_colors = self._process_optional_argument_on_markers(face_colors, "black", unique_face_markers)
88
+ face_weights = self._process_optional_argument_on_markers(face_weights, 1, unique_face_markers)
89
+
90
+ def style_function(x: dict[str, dict[str, typing.Any]]) -> dict[str, typing.Any]:
91
+ if x["geometry"]["type"] == "MultiPolygon":
92
+ return {
93
+ # Boundary properties
94
+ "stroke": x["properties"]["stroke"],
95
+ "color": x["properties"]["color"],
96
+ "weight": x["properties"]["weight"],
97
+ # Interior properties
98
+ "fill": x["properties"]["fill"],
99
+ "fillColor": x["properties"]["fillColor"],
100
+ "fillOpacity": x["properties"]["fillOpacity"]
101
+ }
102
+ elif x["geometry"]["type"] == "MultiLineString":
103
+ return {
104
+ "stroke": x["properties"]["stroke"],
105
+ "color": x["properties"]["color"],
106
+ "weight": x["properties"]["weight"]
107
+ }
108
+ else: # pragma: no cover
109
+ raise ValueError("Invalid type")
110
+
111
+ json = self._convert_mesh_to_geojson(
112
+ vertices, cells, cell_markers, face_markers, cell_colors, face_colors, face_weights)
113
+ folium.GeoJson(json, style_function=style_function).add_to(geo_map)
114
+
115
+ cell_colors_where_none = np.argwhere(cell_colors == "none")
116
+ cell_colors_not_none = np.delete(cell_colors, cell_colors_where_none)
117
+ cell_colors_values = np.arange(0, np.max(unique_cell_markers) + 1)
118
+ cell_colors_values_not_none = np.delete(cell_colors_values, cell_colors_where_none)
119
+ assert cell_colors_not_none.shape == cell_colors_values_not_none.shape
120
+ cell_colors_in_figure = np.delete(
121
+ cell_colors_not_none, np.setdiff1d(cell_colors_values_not_none, unique_cell_markers))
122
+ cell_colors_values_in_figure = np.delete(
123
+ cell_colors_values_not_none, np.setdiff1d(cell_colors_values_not_none, unique_cell_markers))
124
+ if np.unique(cell_colors_in_figure).shape[0] > 1:
125
+ colorbar = ColorbarWrapper(
126
+ colors=cell_colors_in_figure, values=cell_colors_values_in_figure, caption="Cell markers")
127
+ colorbar.add_to(geo_map)
128
+
129
+ face_colors_values = np.arange(0, np.max(unique_face_markers) + 1)
130
+ assert face_colors.shape == face_colors_values.shape
131
+ face_colors_in_figure = np.delete(
132
+ face_colors, np.setdiff1d(face_colors_values, unique_face_markers))
133
+ face_colors_values_in_figure = np.delete(
134
+ face_colors_values, np.setdiff1d(face_colors_values, unique_face_markers))
135
+ if np.unique(face_colors_in_figure).shape[0] > 1:
136
+ colorbar = ColorbarWrapper(
137
+ colors=face_colors_in_figure, values=face_colors_values_in_figure, caption="Face markers")
138
+ colorbar.add_to(geo_map)
139
+
140
+ def _convert_mesh_to_geojson(
141
+ self, vertices: npt.NDArray[np.float64], cells: npt.NDArray[np.int64],
142
+ cell_markers: npt.NDArray[np.int64], face_markers: npt.NDArray[np.int64],
143
+ cell_colors: typing.Union[str, dict[int, str]], face_colors: typing.Union[str, dict[int, str]],
144
+ face_weights: typing.Union[int, dict[int, int]]
145
+ ) -> geojson.FeatureCollection:
146
+ """
147
+ Convert a mesh to a geojson FeatureCollection.
148
+
149
+ Parameters
150
+ ----------
151
+ vertices
152
+ Matrix containing the coordinates of the vertices.
153
+ The matrix should have as many rows as vertices in the mesh, and two columns.
154
+ cells
155
+ Matrix containing the connectivity of the cells.
156
+ The matrix should have as many rows as cells in the mesh, and three columns.
157
+ cell_markers
158
+ Vector containing a marker (i.e., an integer number) for each cell.
159
+ The vector should have as many entries as cells in the mesh.
160
+ face_markers
161
+ Vector containing a marker (i.e., an integer number) for each face.
162
+ The matrix should have the same shape of the cells argument.
163
+ cell_colors
164
+ Vector associating a cell marker to its color (i.e., a string).
165
+ The vector should have as many entries as the number of cell markers.
166
+ face_colors
167
+ Vector associating a face marker to its color (i.e., a string).
168
+ The vector should have as many entries as the number of face markers.
169
+ face_weights
170
+ Vector associating a face marker to its weight (i.e., a int).
171
+ The vector should have as many entries as the number of face markers.
172
+
173
+ Returns
174
+ -------
175
+ :
176
+ A geojson FeatureCollection representing the mesh.
177
+ """
178
+ multipolygon_coordinates = dict()
179
+ multipolygon_properties = dict()
180
+ multiline_coordinates = dict()
181
+ multiline_properties = dict()
182
+ for c in range(cells.shape[0]):
183
+ coordinates = [self.transformer(*vertices[cells[c, v], :]) for v in range(3)]
184
+ coordinates.append(coordinates[0])
185
+ cell_face_markers = np.unique([face_markers[c, f] for f in range(3)]).astype(np.int64)
186
+ if cell_face_markers.shape[0] == 1:
187
+ cell_key = (cell_markers[c], True)
188
+ cell_properties = {
189
+ # Boundary properties
190
+ "stroke": True,
191
+ "color": face_colors[cell_face_markers[0]],
192
+ "weight": int(face_weights[cell_face_markers[0]]),
193
+ }
194
+ else:
195
+ cell_key = (cell_markers[c], False)
196
+ cell_properties = {
197
+ # Boundary properties
198
+ "stroke": False,
199
+ "color": None,
200
+ "weight": None
201
+ }
202
+ if cell_colors[cell_key[0]] != "none":
203
+ cell_properties.update({
204
+ # Interior properties
205
+ "fill": True,
206
+ "fillColor": cell_colors[cell_key[0]],
207
+ "fillOpacity": 1
208
+ })
209
+ else:
210
+ cell_properties.update({
211
+ # Interior properties
212
+ "fill": False,
213
+ "fillColor": None,
214
+ "fillOpacity": None
215
+ })
216
+ # Store current cell
217
+ if cell_key not in multipolygon_coordinates:
218
+ multipolygon_coordinates[cell_key] = list()
219
+ multipolygon_coordinates[cell_key].append([coordinates])
220
+ # Store current cell properties
221
+ if cell_key not in multipolygon_properties:
222
+ multipolygon_properties[cell_key] = cell_properties
223
+ else:
224
+ assert multipolygon_properties[cell_key] == cell_properties
225
+ # Store faces only if there are multiple face markers in this cell,
226
+ # otherwise the boundary representation of the cell is sufficient.
227
+ if not cell_key[1]:
228
+ for (f, pair) in enumerate(((0, 1), (1, 2), (0, 2))):
229
+ face_key = face_markers[c, f]
230
+ # Store current face
231
+ if face_key not in multiline_coordinates:
232
+ multiline_coordinates[face_key] = list()
233
+ multiline_coordinates[face_key].append([coordinates[pair[0]], coordinates[pair[1]]])
234
+ # Store current face properties
235
+ face_properties = {
236
+ "stroke": True,
237
+ "color": face_colors[face_markers[c, f]],
238
+ "weight": int(face_weights[face_markers[c, f]]),
239
+ }
240
+ if face_key not in multiline_properties:
241
+ multiline_properties[face_key] = face_properties
242
+ else:
243
+ assert multiline_properties[face_key] == face_properties
244
+
245
+ multipolygon_features = list()
246
+ for cell_key in multipolygon_coordinates.keys():
247
+ multipolygon = geojson.MultiPolygon(coordinates=multipolygon_coordinates[cell_key])
248
+ feature = geojson.Feature(
249
+ geometry=multipolygon,
250
+ properties=multipolygon_properties[cell_key]
251
+ )
252
+ multipolygon_features.append(feature)
253
+
254
+ multiline_features = list()
255
+ for face_key in multiline_coordinates.keys():
256
+ multiline = geojson.MultiLineString(coordinates=multiline_coordinates[face_key])
257
+ feature = geojson.Feature(
258
+ geometry=multiline,
259
+ properties=multiline_properties[face_key]
260
+ )
261
+ multiline_features.append(feature)
262
+
263
+ return geojson.FeatureCollection(multipolygon_features + multiline_features)
@@ -0,0 +1,64 @@
1
+ # Copyright (C) 2021-2025 by the FEMlium authors
2
+ #
3
+ # This file is part of FEMlium.
4
+ #
5
+ # SPDX-License-Identifier: MIT
6
+ """Interface of a geographic plotter."""
7
+
8
+ import typing
9
+
10
+ import numpy as np
11
+ import numpy.typing as npt
12
+ import pyproj
13
+
14
+ from femlium.utils import TransformerWrapper
15
+
16
+
17
+ class BasePlotter:
18
+ """
19
+ Interface of a geographic plotter.
20
+
21
+ Parameters
22
+ ----------
23
+ transformer
24
+ Defines an optional transformation between coordinate reference systems (CRS) if
25
+ the input data use a different CRS than the output plot.
26
+ If not provided, the identity map is used.
27
+
28
+ Attributes
29
+ ----------
30
+ transformer
31
+ Wrapper to the transformer object provided as first input parameter.
32
+ """
33
+
34
+ def __init__(self, transformer: typing.Optional[pyproj.Transformer] = None) -> None:
35
+ self.transformer = TransformerWrapper(transformer)
36
+
37
+ @staticmethod
38
+ def _process_optional_argument_on_markers(
39
+ argument: typing.Any, default: typing.Any, unique_markers: npt.NDArray[typing.Any] # noqa: ANN401
40
+ ) -> npt.NDArray[typing.Any]:
41
+ """Fill optinal arguments related to markers with default value."""
42
+ expected_type = type(default)
43
+ assert isinstance(argument, (expected_type, dict)) or argument is None
44
+ if isinstance(argument, dict):
45
+ assert all(isinstance(value, expected_type) for (_, value) in argument.items())
46
+
47
+ if isinstance(default, str):
48
+ dtype = np.dtype(object) # otherwise np.dtype(str) only allows a single character
49
+ else:
50
+ dtype = np.dtype(expected_type)
51
+
52
+ assert np.min(unique_markers) >= 0
53
+ output = np.full(np.max(unique_markers) + 1, default, dtype=dtype)
54
+ for m in unique_markers:
55
+ if argument is None:
56
+ pass
57
+ elif isinstance(argument, expected_type):
58
+ output[m] = argument
59
+ elif isinstance(argument, dict):
60
+ if m in argument:
61
+ output[m] = argument[m]
62
+ else: # pragma: no cover
63
+ raise ValueError("Invalid argument provided")
64
+ return output