Export to file

Export to file#

A need for exporting dfsu files to other formats, such as raster formats (NetCDF, GeoTIFF) or vector formats (ESRI Shape, GeoJSON) are not uncommon.

There are usually very specific requirements about the exported data, naming, metadata, resolution and so on, which is why we don’t provide out of the box tools to convert files back and forth from a lot of formats, but instead provide the necessary building blocks and some examples to use as inspiration.

Examples#

Export to shapefile#

import mikeio
import geopandas as gpd

dfs = mikeio.open("data/oresundHD_run1.dfsu")
dfs
<mikeio.Dfsu2DH>
number of elements: 3612
number of nodes: 2046
projection: UTM-33
items:
  0:  Surface elevation <Surface Elevation> (meter)
  1:  Total water depth <Water Depth> (meter)
  2:  U velocity <u velocity component> (meter per sec)
  3:  V velocity <v velocity component> (meter per sec)
time: 2018-03-07 00:00:00 - 2018-03-11 00:00:00 (5 records)
ds = dfs.read(items="Surface elevation")
ds
<mikeio.Dataset>
dims: (time:5, element:3612)
time: 2018-03-07 00:00:00 - 2018-03-11 00:00:00 (5 records)
geometry: Dfsu2D (3612 elements, 2046 nodes)
items:
  0:  Surface elevation <Surface Elevation> (meter)
maxwl = ds["Surface elevation"].max(axis=0).values
maxwl.shape
(3612,)
shp = dfs.geometry.to_shapely()
type(shp)
shapely.geometry.multipolygon.MultiPolygon
poly_list = [p for p in shp.geoms]
gdf = gpd.GeoDataFrame({'waterlevel': maxwl},geometry=poly_list, crs=dfs.geometry.projection_string)
---------------------------------------------------------------------------
CRSError                                  Traceback (most recent call last)
Cell In[6], line 1
----> 1 gdf = gpd.GeoDataFrame({'waterlevel': maxwl},geometry=poly_list, crs=dfs.geometry.projection_string)

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/geopandas/geodataframe.py:243, in GeoDataFrame.__init__(self, data, geometry, crs, *args, **kwargs)
    235     if isinstance(geometry, pd.Series) and geometry.name not in (
    236         "geometry",
    237         None,
    238     ):
    239         # __init__ always creates geometry col named "geometry"
    240         # rename as `set_geometry` respects the given series name
    241         geometry = geometry.rename("geometry")
--> 243     self.set_geometry(geometry, inplace=True, crs=crs)
    245 if geometry is None and crs:
    246     raise ValueError(
    247         "Assigning CRS to a GeoDataFrame without a geometry column is not "
    248         "supported. Supply geometry using the 'geometry=' keyword argument, "
    249         "or by providing a DataFrame with column name 'geometry'",
    250     )

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/geopandas/geodataframe.py:462, in GeoDataFrame.set_geometry(self, col, drop, inplace, crs)
    459     crs = getattr(level, "crs", None)
    461 # Check that we are using a listlike of geometries
--> 462 level = _ensure_geometry(level, crs=crs)
    463 # ensure_geometry only sets crs on level if it has crs==None
    464 if isinstance(level, GeoSeries):

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/geopandas/geodataframe.py:71, in _ensure_geometry(data, crs)
     69     return GeoSeries(out, index=data.index, name=data.name)
     70 else:
---> 71     out = from_shapely(data, crs=crs)
     72     return out

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/geopandas/array.py:190, in from_shapely(data, crs)
    187             raise TypeError(f"Input must be valid geometry objects: {geom}")
    188     arr = np.array(out, dtype=object)
--> 190 return GeometryArray(arr, crs=crs)

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/geopandas/array.py:340, in GeometryArray.__init__(self, data, crs)
    337 self._data = data
    339 self._crs = None
--> 340 self.crs = crs
    341 self._sindex = None

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/geopandas/array.py:392, in GeometryArray.crs(self, value)
    389 if HAS_PYPROJ:
    390     from pyproj import CRS
--> 392     self._crs = None if not value else CRS.from_user_input(value)
    393 else:
    394     if value is not None:

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/pyproj/crs/crs.py:503, in CRS.from_user_input(cls, value, **kwargs)
    501 if isinstance(value, cls):
    502     return value
--> 503 return cls(value, **kwargs)

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/pyproj/crs/crs.py:350, in CRS.__init__(self, projparams, **kwargs)
    348     self._local.crs = projparams
    349 else:
--> 350     self._local.crs = _CRS(self.srs)

File /opt/hostedtoolcache/Python/3.13.9/x64/lib/python3.13/site-packages/pyproj/_crs.pyx:2364, in pyproj._crs._CRS.__init__()

CRSError: Invalid projection: UTM-33: (Internal Proj Error: proj_create: several objects matching this name: Schwarzeck / UTM zone 33S, RGRDC 2005 / UTM zone 33S, Malongo 1987 / UTM zone 33S, ETRS89/DREF91/2016 / UTM zone 33N, ETRS89/DREF91/2016 / UTM zone 33N (N-zE), ETRS89/DREF91/2016 / UTM zone 33N (zE-N))

Ouch… The short and smart projection string “UTM-33” is apparently not understood by GeoPandas. Better look it up on the web https://spatialreference.org/ref/epsg/32433/

gdf = gpd.GeoDataFrame({'waterlevel': maxwl}, geometry=poly_list, crs="EPSG:32433")
gdf.head()
waterlevel geometry
0 0.321391 POLYGON ((353122.611 6199631.339, 354250.947 6...
1 0.254802 POLYGON ((356811.295 6165979.11, 356926.413 61...
2 0.210542 POLYGON ((334193.183 6143454.831, 332951.036 6...
3 0.213418 POLYGON ((330871.279 6142677.275, 331831.835 6...
4 0.208291 POLYGON ((334327.062 6140906.232, 335381.131 6...

Export as ESRI Shapefile

gdf.to_file("waterlevel_utm.shp")

Which can be used together with other data sources and customized in QGIS

Or GeoJSON, which some might prefer…

gdf.to_file("waterlevel_utm.json")