Skip to content

HmsSqlite

SQLite geometry and grid database helpers used by HMS geospatial workflows.

hms_commander.HmsSqlite

HmsSqlite - SQLite Grid Database Operations for HMS Commander

Provides static methods for reading spatial geometry from HEC-HMS 4.x SQLite grid databases. These databases store authoritative subbasin polygons, reach linestrings, outlet points, and discretization grids for gridded HMS models (Modified Clark, SCS Grid).

Classes:

Name Description
HmsSqlite

Static class for SQLite grid database operations

Key Functions

list_layers: List tables with row counts and geometry types get_crs: Extract CRS WKT from spatial_ref_sys table get_subbasins: Read subbasin2d polygons as GeoDataFrame get_reaches: Read reach2d linestrings as GeoDataFrame get_outlets: Read outlet points as GeoDataFrame get_junctions: Read junction points as GeoDataFrame get_discretization: Read grid cells (large, opt-in) read_grid_database: Read all layers in one call discover_sqlite_files: Find .sqlite files in a project directory join_with_parameters: Merge geometry with basin parameter DataFrame

Dependencies

Required for geometry methods: - geopandas: Spatial data handling - (fiona/GDAL backend reads SpatiaLite natively)

Not required for list_layers, get_crs, discover_sqlite_files: - Uses stdlib sqlite3 only

Install with: pip install hms-commander[gis] # OR pip install geopandas

Example

from hms_commander import HmsSqlite

List layers (no geopandas needed)

layers = HmsSqlite.list_layers("project.sqlite") print(layers)

Read subbasin polygons

subs = HmsSqlite.get_subbasins("project.sqlite") print(f"Found {len(subs)} subbasins")

Notes
  • All methods are static (no instantiation required)
  • HEC-HMS 4.x gridded models store geometry in SpatiaLite format
  • The spatial_ref_sys table contains CRS as WKT (often custom, no EPSG)
  • Geometry is stored as WKB blobs in GEOMETRY columns

HmsSqlite

Static class for HEC-HMS SQLite grid database operations.

Provides methods for reading spatial geometry from SpatiaLite databases created by HEC-HMS 4.x for gridded models (Modified Clark, SCS Grid).

All methods are static - do not instantiate this class.

Example

from hms_commander import HmsSqlite

Read subbasin polygons

subs = HmsSqlite.get_subbasins("Minimum_Facility.sqlite") print(f"Found {len(subs)} subbasins")

Read all layers at once

layers = HmsSqlite.read_grid_database("Minimum_Facility.sqlite") for name, gdf in layers.items(): ... print(f" {name}: {len(gdf)} features")

Source code in hms_commander/HmsSqlite.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
class HmsSqlite:
    """
    Static class for HEC-HMS SQLite grid database operations.

    Provides methods for reading spatial geometry from SpatiaLite databases
    created by HEC-HMS 4.x for gridded models (Modified Clark, SCS Grid).

    All methods are static - do not instantiate this class.

    Example:
        >>> from hms_commander import HmsSqlite
        >>>
        >>> # Read subbasin polygons
        >>> subs = HmsSqlite.get_subbasins("Minimum_Facility.sqlite")
        >>> print(f"Found {len(subs)} subbasins")
        >>>
        >>> # Read all layers at once
        >>> layers = HmsSqlite.read_grid_database("Minimum_Facility.sqlite")
        >>> for name, gdf in layers.items():
        ...     print(f"  {name}: {len(gdf)} features")
    """

    @staticmethod
    def _check_gis_dependencies():
        """Check that geopandas is installed."""
        try:
            import geopandas
        except ImportError:
            raise ImportError(
                "geopandas is required for geometry operations.\n"
                "Install with: pip install hms-commander[gis]\n"
                "Or: pip install geopandas"
            )

    @staticmethod
    @log_call
    def list_layers(sqlite_path: Union[str, Path]) -> pd.DataFrame:
        """
        List all spatial layers in an HMS SQLite database.

        Uses stdlib sqlite3 only - no geopandas required.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        pd.DataFrame
            DataFrame with columns:
                - table_name: Name of the table
                - row_count: Number of rows
                - geometry_type: Geometry type name (Point, LineString, Polygon, etc.)
                - srid: Spatial reference ID

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.

        Example
        -------
        >>> layers = HmsSqlite.list_layers("Minimum_Facility.sqlite")
        >>> print(layers)
        """
        sqlite_path = Path(sqlite_path)
        if not sqlite_path.exists():
            raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

        conn = sqlite3.connect(str(sqlite_path))
        try:
            cursor = conn.cursor()

            # Read geometry_columns metadata
            geom_info = {}
            try:
                cursor.execute("SELECT f_table_name, geometry_type, srid FROM geometry_columns")
                for row in cursor.fetchall():
                    table_name = row[0]
                    geom_type_val = row[1]
                    srid = row[2]
                    # type can be int or string depending on SpatiaLite version
                    if isinstance(geom_type_val, int):
                        geom_type = _GEOM_TYPE_NAMES.get(geom_type_val, f"Unknown({geom_type_val})")
                    else:
                        geom_type = str(geom_type_val)
                    geom_info[table_name] = {"geometry_type": geom_type, "srid": srid}
            except sqlite3.OperationalError:
                logger.debug("No geometry_columns table found")

            # Get row counts for all spatial tables
            records = []
            for table_name in sorted(geom_info.keys()):
                try:
                    cursor.execute(f'SELECT COUNT(*) FROM "{table_name}"')
                    row_count = cursor.fetchone()[0]
                except sqlite3.OperationalError:
                    row_count = 0

                records.append({
                    "table_name": table_name,
                    "row_count": row_count,
                    "geometry_type": geom_info[table_name]["geometry_type"],
                    "srid": geom_info[table_name]["srid"],
                })

            logger.info(f"Found {len(records)} spatial layers in {sqlite_path.name}")
            return pd.DataFrame(records)
        finally:
            conn.close()

    @staticmethod
    @log_call
    def get_crs(sqlite_path: Union[str, Path]) -> Optional[str]:
        """
        Extract CRS as WKT string from the spatial_ref_sys table.

        Uses stdlib sqlite3 only - no geopandas required.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        Optional[str]
            WKT string of the coordinate reference system, or None if not found.

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.

        Example
        -------
        >>> wkt = HmsSqlite.get_crs("Minimum_Facility.sqlite")
        >>> print(wkt[:50])
        'PROJCS["NAD83 / UTM zone 16N",...'
        """
        sqlite_path = Path(sqlite_path)
        if not sqlite_path.exists():
            raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

        conn = sqlite3.connect(str(sqlite_path))
        try:
            cursor = conn.cursor()
            try:
                cursor.execute("SELECT srtext FROM spatial_ref_sys LIMIT 1")
                row = cursor.fetchone()
                if row and row[0]:
                    wkt = row[0].strip()
                    if wkt:
                        logger.info(f"CRS WKT found in {sqlite_path.name} ({len(wkt)} chars)")
                        return wkt
            except sqlite3.OperationalError:
                logger.debug(f"No spatial_ref_sys table in {sqlite_path.name}")

            return None
        finally:
            conn.close()

    @staticmethod
    @log_call
    def get_subbasins(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read subbasin polygons from the subbasin2d table.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with subbasin polygons. Columns depend on the HMS
            project configuration; always includes ``geometry`` (Polygon or
            MultiPolygon) and typically ``name``. May also include:
            ``area_sqkm``, ``centroid_x``, ``centroid_y``, ``latitude``,
            ``longitude`` (these are NULL in some projects).

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ImportError
            If geopandas is not installed.
        ValueError
            If the subbasin2d table is not found.

        Example
        -------
        >>> subs = HmsSqlite.get_subbasins("Minimum_Facility.sqlite")
        >>> print(f"Found {len(subs)} subbasins")
        """
        return HmsSqlite._read_layer(sqlite_path, "subbasin2d")

    @staticmethod
    @log_call
    def get_reaches(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read reach linestrings from the reach2d table.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with reach linestrings and topology attributes including:
                - name: Reach name
                - linkno, dslinkno: Link numbers for topology
                - uslinkno1, uslinkno2: Upstream link numbers
                - strmorder: Stream order
                - length: Reach length
                - slope: Reach slope
                - geometry: LineString geometry

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ImportError
            If geopandas is not installed.
        ValueError
            If the reach2d table is not found.

        Example
        -------
        >>> reaches = HmsSqlite.get_reaches("Minimum_Facility.sqlite")
        >>> print(f"Found {len(reaches)} reaches")
        """
        return HmsSqlite._read_layer(sqlite_path, "reach2d")

    @staticmethod
    @log_call
    def get_outlets(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read outlet points from the outlet table.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with outlet points and attributes including:
                - name: Outlet name
                - geometry: Point geometry

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ImportError
            If geopandas is not installed.
        ValueError
            If the outlet table is not found.

        Example
        -------
        >>> outlets = HmsSqlite.get_outlets("Minimum_Facility.sqlite")
        >>> for _, row in outlets.iterrows():
        ...     print(f"  {row['name']}: ({row.geometry.x:.1f}, {row.geometry.y:.1f})")
        """
        return HmsSqlite._read_layer(sqlite_path, "outlet")

    @staticmethod
    @log_call
    def get_junctions(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read junction points from the junction table.

        Returns an empty GeoDataFrame if the junction table has no rows
        (common in gridded models where junctions are implicit).

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with junction points (may be empty).

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ImportError
            If geopandas is not installed.

        Example
        -------
        >>> junctions = HmsSqlite.get_junctions("Minimum_Facility.sqlite")
        >>> print(f"Found {len(junctions)} junctions")  # Often 0
        """
        return HmsSqlite._read_layer(sqlite_path, "junction")

    @staticmethod
    @log_call
    def get_discretization(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read grid cell discretization polygons.

        This layer can be very large (thousands to hundreds of thousands of cells).
        Use only when grid cell geometry is needed.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with discretization grid cell polygons.

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ImportError
            If geopandas is not installed.

        Example
        -------
        >>> cells = HmsSqlite.get_discretization("Minimum_Facility.sqlite")
        >>> print(f"Found {len(cells)} grid cells")
        """
        return HmsSqlite._read_layer(sqlite_path, "discretization")

    @staticmethod
    @log_call
    def read_grid_database(
        sqlite_path: Union[str, Path],
        include_discretization: bool = False,
        skip_empty: bool = True
    ) -> Dict[str, 'gpd.GeoDataFrame']:
        """
        Read all spatial layers from an HMS SQLite database.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.
        include_discretization : bool, default False
            If True, include the discretization grid cells (can be very large).
        skip_empty : bool, default True
            If True, omit layers with zero rows from the result.

        Returns
        -------
        Dict[str, gpd.GeoDataFrame]
            Dictionary mapping layer name to GeoDataFrame.

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ImportError
            If geopandas is not installed.

        Example
        -------
        >>> layers = HmsSqlite.read_grid_database("Minimum_Facility.sqlite")
        >>> for name, gdf in layers.items():
        ...     print(f"  {name}: {len(gdf)} features")
        """
        HmsSqlite._check_gis_dependencies()
        sqlite_path = Path(sqlite_path)
        if not sqlite_path.exists():
            raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

        # Get layer info to know which have data
        layer_info = HmsSqlite.list_layers(sqlite_path)

        # Standard layers to read (order matters for logging)
        target_layers = ["subbasin2d", "reach2d", "outlet", "junction",
                         "subbasin", "reach", "diversion", "reservoir",
                         "reservoir2d", "sink", "source"]
        if include_discretization:
            target_layers.append("discretization")

        result = {}
        for layer_name in target_layers:
            # Check if layer exists in database
            layer_rows = layer_info[layer_info["table_name"] == layer_name]
            if layer_rows.empty:
                continue

            row_count = layer_rows.iloc[0]["row_count"]
            if skip_empty and row_count == 0:
                continue

            try:
                gdf = HmsSqlite._read_layer(sqlite_path, layer_name)
                result[layer_name] = gdf
            except ValueError as e:
                logger.warning(f"Could not read layer '{layer_name}': {e}")

        logger.info(
            f"Read {len(result)} layers from {sqlite_path.name}: "
            + ", ".join(f"{k}({len(v)})" for k, v in result.items())
        )
        return result

    @staticmethod
    @log_call
    def discover_sqlite_files(project_dir: Union[str, Path]) -> List[Path]:
        """
        Find all .sqlite files in an HMS project directory.

        Parameters
        ----------
        project_dir : Union[str, Path]
            Path to the HMS project directory.

        Returns
        -------
        List[Path]
            Sorted list of .sqlite file paths.

        Example
        -------
        >>> files = HmsSqlite.discover_sqlite_files("river_bend/")
        >>> print(f"Found {len(files)} SQLite files")
        """
        project_dir = Path(project_dir)
        if not project_dir.is_dir():
            logger.warning(f"Directory not found: {project_dir}")
            return []

        sqlite_files = sorted(project_dir.glob("*.sqlite"))
        logger.info(f"Found {len(sqlite_files)} .sqlite files in {project_dir}")
        return sqlite_files

    @staticmethod
    @log_call
    def join_with_parameters(
        sqlite_gdf: 'gpd.GeoDataFrame',
        subbasin_df: pd.DataFrame,
        join_column: str = "name"
    ) -> 'gpd.GeoDataFrame':
        """
        Merge SQLite geometry with basin parameter DataFrame.

        Joins on the specified column (default: 'name') to combine spatial
        geometry from the SQLite database with hydrologic parameters from
        basin file parsing.

        Parameters
        ----------
        sqlite_gdf : gpd.GeoDataFrame
            GeoDataFrame from get_subbasins() or similar.
        subbasin_df : pd.DataFrame
            DataFrame with subbasin parameters (e.g., from HmsPrj.subbasin_df
            or HmsBasin.get_subbasins()).
        join_column : str, default "name"
            Column name to join on (must exist in both DataFrames).

        Returns
        -------
        gpd.GeoDataFrame
            Merged GeoDataFrame with geometry and parameters.

        Raises
        ------
        ImportError
            If geopandas is not installed.
        ValueError
            If join_column is missing from either DataFrame.

        Example
        -------
        >>> subs_geo = HmsSqlite.get_subbasins("Minimum_Facility.sqlite")
        >>> from hms_commander import HmsBasin
        >>> subs_params = HmsBasin.get_subbasins("Minimum_Facility.basin")
        >>> merged = HmsSqlite.join_with_parameters(subs_geo, subs_params)
        >>> print(f"Merged: {len(merged)} rows with geometry + parameters")
        """
        HmsSqlite._check_gis_dependencies()
        import geopandas as gpd

        if join_column not in sqlite_gdf.columns:
            raise ValueError(
                f"Column '{join_column}' not found in geometry GeoDataFrame. "
                f"Available: {list(sqlite_gdf.columns)}"
            )
        if join_column not in subbasin_df.columns:
            raise ValueError(
                f"Column '{join_column}' not found in parameter DataFrame. "
                f"Available: {list(subbasin_df.columns)}"
            )

        # Determine columns to bring from subbasin_df (avoid duplicates)
        geo_cols = set(sqlite_gdf.columns)
        param_cols = [join_column] + [
            c for c in subbasin_df.columns
            if c not in geo_cols and c != join_column
        ]
        param_subset = subbasin_df[param_cols].copy()

        merged = sqlite_gdf.merge(param_subset, on=join_column, how="left")

        # Ensure result is GeoDataFrame
        if not isinstance(merged, gpd.GeoDataFrame):
            merged = gpd.GeoDataFrame(merged, geometry="geometry", crs=sqlite_gdf.crs)

        logger.info(
            f"Joined {len(sqlite_gdf)} geometries with {len(subbasin_df)} parameters "
            f"on '{join_column}' -> {len(merged)} rows"
        )
        return merged

    # =========================================================================
    # Subbasin & reach statistics / flow paths
    # =========================================================================

    @staticmethod
    @log_call
    def get_subbasin_statistics(sqlite_path: Union[str, Path]) -> pd.DataFrame:
        """
        Read subbasin statistics (longest flow path, slopes, relief, etc.).

        Reads from the ``subbasin_statistics`` table that HEC-HMS populates
        when terrain/GIS preprocessing has been performed.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        pd.DataFrame
            DataFrame with columns including: subbasin_name, longest_length,
            longest_slope, centroidal_length, centroidal_slope, 10_85_length,
            10_85_slope, basin_slope, basin_relief, elongation_ratio,
            relief_ratio, drainage_density, length_units.

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ValueError
            If the subbasin_statistics table is not found.
        """
        return HmsSqlite._read_table(sqlite_path, "subbasin_statistics")

    @staticmethod
    @log_call
    def get_reach_statistics(sqlite_path: Union[str, Path]) -> pd.DataFrame:
        """
        Read reach statistics (length, slope, relief, sinuosity).

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        pd.DataFrame
            DataFrame with columns: reach_name, reach_length, reach_slope,
            reach_relief, reach_sinuosity, length_units.

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ValueError
            If the reach_statistics table is not found.
        """
        return HmsSqlite._read_table(sqlite_path, "reach_statistics")

    @staticmethod
    @log_call
    def get_longest_flowpaths(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read longest flow path linestrings per subbasin.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with columns: subbasin, geometry (LineString).

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ValueError
            If the longest_flowpath table is not found.
        """
        return HmsSqlite._read_layer(sqlite_path, "longest_flowpath")

    @staticmethod
    @log_call
    def get_centroidal_flowpaths(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read centroidal flow path linestrings per subbasin.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with columns: subbasin, geometry (LineString).

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ValueError
            If the centroidal_flowpath table is not found.
        """
        return HmsSqlite._read_layer(sqlite_path, "centroidal_flowpath")

    @staticmethod
    @log_call
    def get_teneightyfive_flowpaths(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
        """
        Read 10-85% flow path linestrings per subbasin.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with columns: subbasin, geometry (LineString).

        Raises
        ------
        FileNotFoundError
            If sqlite_path does not exist.
        ValueError
            If the teneightyfive_flowpath table is not found.
        """
        return HmsSqlite._read_layer(sqlite_path, "teneightyfive_flowpath")

    # =========================================================================
    # Internal helpers
    # =========================================================================

    @staticmethod
    def _read_table(
        sqlite_path: Union[str, Path],
        table_name: str
    ) -> pd.DataFrame:
        """
        Read a non-spatial table from an HMS SQLite database.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.
        table_name : str
            Name of the table to read.

        Returns
        -------
        pd.DataFrame
            DataFrame with table data.
        """
        sqlite_path = Path(sqlite_path)
        if not sqlite_path.exists():
            raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

        conn = sqlite3.connect(str(sqlite_path))
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
                (table_name,)
            )
            if cursor.fetchone() is None:
                raise ValueError(
                    f"Table '{table_name}' not found in {sqlite_path.name}"
                )

            df = pd.read_sql(f'SELECT * FROM "{table_name}"', conn)
            logger.debug(f"Read {len(df)} rows from {sqlite_path.name}:{table_name}")
            return df
        finally:
            conn.close()

    @staticmethod
    def _read_layer(
        sqlite_path: Union[str, Path],
        layer_name: str
    ) -> 'gpd.GeoDataFrame':
        """
        Read a single spatial layer from an HMS SQLite database.

        Parameters
        ----------
        sqlite_path : Union[str, Path]
            Path to the SQLite database file.
        layer_name : str
            Name of the layer/table to read.

        Returns
        -------
        gpd.GeoDataFrame
            GeoDataFrame with layer data.
        """
        HmsSqlite._check_gis_dependencies()
        import geopandas as gpd

        sqlite_path = Path(sqlite_path)
        if not sqlite_path.exists():
            raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

        # Verify the layer exists
        conn = sqlite3.connect(str(sqlite_path))
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
                (layer_name,)
            )
            if cursor.fetchone() is None:
                raise ValueError(
                    f"Layer '{layer_name}' not found in {sqlite_path.name}"
                )

            # Check row count
            cursor.execute(f'SELECT COUNT(*) FROM "{layer_name}"')
            row_count = cursor.fetchone()[0]
        finally:
            conn.close()

        if row_count == 0:
            # Return empty GeoDataFrame with correct CRS
            wkt = HmsSqlite.get_crs(sqlite_path)
            crs = None
            if wkt:
                try:
                    from pyproj import CRS as PyprojCRS
                    crs = PyprojCRS.from_wkt(wkt)
                except (ImportError, Exception):
                    pass
            return gpd.GeoDataFrame(geometry=[], crs=crs)

        # Read using geopandas (fiona/GDAL handles SpatiaLite natively)
        gdf = gpd.read_file(str(sqlite_path), layer=layer_name)

        logger.debug(f"Read {len(gdf)} features from {sqlite_path.name}:{layer_name}")
        return gdf

list_layers(sqlite_path) staticmethod

List all spatial layers in an HMS SQLite database.

Uses stdlib sqlite3 only - no geopandas required.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

pd.DataFrame DataFrame with columns: - table_name: Name of the table - row_count: Number of rows - geometry_type: Geometry type name (Point, LineString, Polygon, etc.) - srid: Spatial reference ID

Raises

FileNotFoundError If sqlite_path does not exist.

Example

layers = HmsSqlite.list_layers("Minimum_Facility.sqlite") print(layers)

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def list_layers(sqlite_path: Union[str, Path]) -> pd.DataFrame:
    """
    List all spatial layers in an HMS SQLite database.

    Uses stdlib sqlite3 only - no geopandas required.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    pd.DataFrame
        DataFrame with columns:
            - table_name: Name of the table
            - row_count: Number of rows
            - geometry_type: Geometry type name (Point, LineString, Polygon, etc.)
            - srid: Spatial reference ID

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.

    Example
    -------
    >>> layers = HmsSqlite.list_layers("Minimum_Facility.sqlite")
    >>> print(layers)
    """
    sqlite_path = Path(sqlite_path)
    if not sqlite_path.exists():
        raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

    conn = sqlite3.connect(str(sqlite_path))
    try:
        cursor = conn.cursor()

        # Read geometry_columns metadata
        geom_info = {}
        try:
            cursor.execute("SELECT f_table_name, geometry_type, srid FROM geometry_columns")
            for row in cursor.fetchall():
                table_name = row[0]
                geom_type_val = row[1]
                srid = row[2]
                # type can be int or string depending on SpatiaLite version
                if isinstance(geom_type_val, int):
                    geom_type = _GEOM_TYPE_NAMES.get(geom_type_val, f"Unknown({geom_type_val})")
                else:
                    geom_type = str(geom_type_val)
                geom_info[table_name] = {"geometry_type": geom_type, "srid": srid}
        except sqlite3.OperationalError:
            logger.debug("No geometry_columns table found")

        # Get row counts for all spatial tables
        records = []
        for table_name in sorted(geom_info.keys()):
            try:
                cursor.execute(f'SELECT COUNT(*) FROM "{table_name}"')
                row_count = cursor.fetchone()[0]
            except sqlite3.OperationalError:
                row_count = 0

            records.append({
                "table_name": table_name,
                "row_count": row_count,
                "geometry_type": geom_info[table_name]["geometry_type"],
                "srid": geom_info[table_name]["srid"],
            })

        logger.info(f"Found {len(records)} spatial layers in {sqlite_path.name}")
        return pd.DataFrame(records)
    finally:
        conn.close()

get_crs(sqlite_path) staticmethod

Extract CRS as WKT string from the spatial_ref_sys table.

Uses stdlib sqlite3 only - no geopandas required.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

Optional[str] WKT string of the coordinate reference system, or None if not found.

Raises

FileNotFoundError If sqlite_path does not exist.

Example

wkt = HmsSqlite.get_crs("Minimum_Facility.sqlite") print(wkt[:50]) 'PROJCS["NAD83 / UTM zone 16N",...'

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_crs(sqlite_path: Union[str, Path]) -> Optional[str]:
    """
    Extract CRS as WKT string from the spatial_ref_sys table.

    Uses stdlib sqlite3 only - no geopandas required.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    Optional[str]
        WKT string of the coordinate reference system, or None if not found.

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.

    Example
    -------
    >>> wkt = HmsSqlite.get_crs("Minimum_Facility.sqlite")
    >>> print(wkt[:50])
    'PROJCS["NAD83 / UTM zone 16N",...'
    """
    sqlite_path = Path(sqlite_path)
    if not sqlite_path.exists():
        raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

    conn = sqlite3.connect(str(sqlite_path))
    try:
        cursor = conn.cursor()
        try:
            cursor.execute("SELECT srtext FROM spatial_ref_sys LIMIT 1")
            row = cursor.fetchone()
            if row and row[0]:
                wkt = row[0].strip()
                if wkt:
                    logger.info(f"CRS WKT found in {sqlite_path.name} ({len(wkt)} chars)")
                    return wkt
        except sqlite3.OperationalError:
            logger.debug(f"No spatial_ref_sys table in {sqlite_path.name}")

        return None
    finally:
        conn.close()

get_subbasins(sqlite_path) staticmethod

Read subbasin polygons from the subbasin2d table.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with subbasin polygons. Columns depend on the HMS project configuration; always includes geometry (Polygon or MultiPolygon) and typically name. May also include: area_sqkm, centroid_x, centroid_y, latitude, longitude (these are NULL in some projects).

Raises

FileNotFoundError If sqlite_path does not exist. ImportError If geopandas is not installed. ValueError If the subbasin2d table is not found.

Example

subs = HmsSqlite.get_subbasins("Minimum_Facility.sqlite") print(f"Found {len(subs)} subbasins")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_subbasins(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read subbasin polygons from the subbasin2d table.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with subbasin polygons. Columns depend on the HMS
        project configuration; always includes ``geometry`` (Polygon or
        MultiPolygon) and typically ``name``. May also include:
        ``area_sqkm``, ``centroid_x``, ``centroid_y``, ``latitude``,
        ``longitude`` (these are NULL in some projects).

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ImportError
        If geopandas is not installed.
    ValueError
        If the subbasin2d table is not found.

    Example
    -------
    >>> subs = HmsSqlite.get_subbasins("Minimum_Facility.sqlite")
    >>> print(f"Found {len(subs)} subbasins")
    """
    return HmsSqlite._read_layer(sqlite_path, "subbasin2d")

get_reaches(sqlite_path) staticmethod

Read reach linestrings from the reach2d table.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with reach linestrings and topology attributes including: - name: Reach name - linkno, dslinkno: Link numbers for topology - uslinkno1, uslinkno2: Upstream link numbers - strmorder: Stream order - length: Reach length - slope: Reach slope - geometry: LineString geometry

Raises

FileNotFoundError If sqlite_path does not exist. ImportError If geopandas is not installed. ValueError If the reach2d table is not found.

Example

reaches = HmsSqlite.get_reaches("Minimum_Facility.sqlite") print(f"Found {len(reaches)} reaches")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_reaches(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read reach linestrings from the reach2d table.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with reach linestrings and topology attributes including:
            - name: Reach name
            - linkno, dslinkno: Link numbers for topology
            - uslinkno1, uslinkno2: Upstream link numbers
            - strmorder: Stream order
            - length: Reach length
            - slope: Reach slope
            - geometry: LineString geometry

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ImportError
        If geopandas is not installed.
    ValueError
        If the reach2d table is not found.

    Example
    -------
    >>> reaches = HmsSqlite.get_reaches("Minimum_Facility.sqlite")
    >>> print(f"Found {len(reaches)} reaches")
    """
    return HmsSqlite._read_layer(sqlite_path, "reach2d")

get_outlets(sqlite_path) staticmethod

Read outlet points from the outlet table.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with outlet points and attributes including: - name: Outlet name - geometry: Point geometry

Raises

FileNotFoundError If sqlite_path does not exist. ImportError If geopandas is not installed. ValueError If the outlet table is not found.

Example

outlets = HmsSqlite.get_outlets("Minimum_Facility.sqlite") for _, row in outlets.iterrows(): ... print(f" {row['name']}: ({row.geometry.x:.1f}, {row.geometry.y:.1f})")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_outlets(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read outlet points from the outlet table.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with outlet points and attributes including:
            - name: Outlet name
            - geometry: Point geometry

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ImportError
        If geopandas is not installed.
    ValueError
        If the outlet table is not found.

    Example
    -------
    >>> outlets = HmsSqlite.get_outlets("Minimum_Facility.sqlite")
    >>> for _, row in outlets.iterrows():
    ...     print(f"  {row['name']}: ({row.geometry.x:.1f}, {row.geometry.y:.1f})")
    """
    return HmsSqlite._read_layer(sqlite_path, "outlet")

get_junctions(sqlite_path) staticmethod

Read junction points from the junction table.

Returns an empty GeoDataFrame if the junction table has no rows (common in gridded models where junctions are implicit).

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with junction points (may be empty).

Raises

FileNotFoundError If sqlite_path does not exist. ImportError If geopandas is not installed.

Example

junctions = HmsSqlite.get_junctions("Minimum_Facility.sqlite") print(f"Found {len(junctions)} junctions") # Often 0

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_junctions(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read junction points from the junction table.

    Returns an empty GeoDataFrame if the junction table has no rows
    (common in gridded models where junctions are implicit).

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with junction points (may be empty).

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ImportError
        If geopandas is not installed.

    Example
    -------
    >>> junctions = HmsSqlite.get_junctions("Minimum_Facility.sqlite")
    >>> print(f"Found {len(junctions)} junctions")  # Often 0
    """
    return HmsSqlite._read_layer(sqlite_path, "junction")

get_discretization(sqlite_path) staticmethod

Read grid cell discretization polygons.

This layer can be very large (thousands to hundreds of thousands of cells). Use only when grid cell geometry is needed.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with discretization grid cell polygons.

Raises

FileNotFoundError If sqlite_path does not exist. ImportError If geopandas is not installed.

Example

cells = HmsSqlite.get_discretization("Minimum_Facility.sqlite") print(f"Found {len(cells)} grid cells")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_discretization(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read grid cell discretization polygons.

    This layer can be very large (thousands to hundreds of thousands of cells).
    Use only when grid cell geometry is needed.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with discretization grid cell polygons.

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ImportError
        If geopandas is not installed.

    Example
    -------
    >>> cells = HmsSqlite.get_discretization("Minimum_Facility.sqlite")
    >>> print(f"Found {len(cells)} grid cells")
    """
    return HmsSqlite._read_layer(sqlite_path, "discretization")

read_grid_database(sqlite_path, include_discretization=False, skip_empty=True) staticmethod

Read all spatial layers from an HMS SQLite database.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file. include_discretization : bool, default False If True, include the discretization grid cells (can be very large). skip_empty : bool, default True If True, omit layers with zero rows from the result.

Returns

Dict[str, gpd.GeoDataFrame] Dictionary mapping layer name to GeoDataFrame.

Raises

FileNotFoundError If sqlite_path does not exist. ImportError If geopandas is not installed.

Example

layers = HmsSqlite.read_grid_database("Minimum_Facility.sqlite") for name, gdf in layers.items(): ... print(f" {name}: {len(gdf)} features")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def read_grid_database(
    sqlite_path: Union[str, Path],
    include_discretization: bool = False,
    skip_empty: bool = True
) -> Dict[str, 'gpd.GeoDataFrame']:
    """
    Read all spatial layers from an HMS SQLite database.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.
    include_discretization : bool, default False
        If True, include the discretization grid cells (can be very large).
    skip_empty : bool, default True
        If True, omit layers with zero rows from the result.

    Returns
    -------
    Dict[str, gpd.GeoDataFrame]
        Dictionary mapping layer name to GeoDataFrame.

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ImportError
        If geopandas is not installed.

    Example
    -------
    >>> layers = HmsSqlite.read_grid_database("Minimum_Facility.sqlite")
    >>> for name, gdf in layers.items():
    ...     print(f"  {name}: {len(gdf)} features")
    """
    HmsSqlite._check_gis_dependencies()
    sqlite_path = Path(sqlite_path)
    if not sqlite_path.exists():
        raise FileNotFoundError(f"SQLite file not found: {sqlite_path}")

    # Get layer info to know which have data
    layer_info = HmsSqlite.list_layers(sqlite_path)

    # Standard layers to read (order matters for logging)
    target_layers = ["subbasin2d", "reach2d", "outlet", "junction",
                     "subbasin", "reach", "diversion", "reservoir",
                     "reservoir2d", "sink", "source"]
    if include_discretization:
        target_layers.append("discretization")

    result = {}
    for layer_name in target_layers:
        # Check if layer exists in database
        layer_rows = layer_info[layer_info["table_name"] == layer_name]
        if layer_rows.empty:
            continue

        row_count = layer_rows.iloc[0]["row_count"]
        if skip_empty and row_count == 0:
            continue

        try:
            gdf = HmsSqlite._read_layer(sqlite_path, layer_name)
            result[layer_name] = gdf
        except ValueError as e:
            logger.warning(f"Could not read layer '{layer_name}': {e}")

    logger.info(
        f"Read {len(result)} layers from {sqlite_path.name}: "
        + ", ".join(f"{k}({len(v)})" for k, v in result.items())
    )
    return result

discover_sqlite_files(project_dir) staticmethod

Find all .sqlite files in an HMS project directory.

Parameters

project_dir : Union[str, Path] Path to the HMS project directory.

Returns

List[Path] Sorted list of .sqlite file paths.

Example

files = HmsSqlite.discover_sqlite_files("river_bend/") print(f"Found {len(files)} SQLite files")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def discover_sqlite_files(project_dir: Union[str, Path]) -> List[Path]:
    """
    Find all .sqlite files in an HMS project directory.

    Parameters
    ----------
    project_dir : Union[str, Path]
        Path to the HMS project directory.

    Returns
    -------
    List[Path]
        Sorted list of .sqlite file paths.

    Example
    -------
    >>> files = HmsSqlite.discover_sqlite_files("river_bend/")
    >>> print(f"Found {len(files)} SQLite files")
    """
    project_dir = Path(project_dir)
    if not project_dir.is_dir():
        logger.warning(f"Directory not found: {project_dir}")
        return []

    sqlite_files = sorted(project_dir.glob("*.sqlite"))
    logger.info(f"Found {len(sqlite_files)} .sqlite files in {project_dir}")
    return sqlite_files

join_with_parameters(sqlite_gdf, subbasin_df, join_column='name') staticmethod

Merge SQLite geometry with basin parameter DataFrame.

Joins on the specified column (default: 'name') to combine spatial geometry from the SQLite database with hydrologic parameters from basin file parsing.

Parameters

sqlite_gdf : gpd.GeoDataFrame GeoDataFrame from get_subbasins() or similar. subbasin_df : pd.DataFrame DataFrame with subbasin parameters (e.g., from HmsPrj.subbasin_df or HmsBasin.get_subbasins()). join_column : str, default "name" Column name to join on (must exist in both DataFrames).

Returns

gpd.GeoDataFrame Merged GeoDataFrame with geometry and parameters.

Raises

ImportError If geopandas is not installed. ValueError If join_column is missing from either DataFrame.

Example

subs_geo = HmsSqlite.get_subbasins("Minimum_Facility.sqlite") from hms_commander import HmsBasin subs_params = HmsBasin.get_subbasins("Minimum_Facility.basin") merged = HmsSqlite.join_with_parameters(subs_geo, subs_params) print(f"Merged: {len(merged)} rows with geometry + parameters")

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def join_with_parameters(
    sqlite_gdf: 'gpd.GeoDataFrame',
    subbasin_df: pd.DataFrame,
    join_column: str = "name"
) -> 'gpd.GeoDataFrame':
    """
    Merge SQLite geometry with basin parameter DataFrame.

    Joins on the specified column (default: 'name') to combine spatial
    geometry from the SQLite database with hydrologic parameters from
    basin file parsing.

    Parameters
    ----------
    sqlite_gdf : gpd.GeoDataFrame
        GeoDataFrame from get_subbasins() or similar.
    subbasin_df : pd.DataFrame
        DataFrame with subbasin parameters (e.g., from HmsPrj.subbasin_df
        or HmsBasin.get_subbasins()).
    join_column : str, default "name"
        Column name to join on (must exist in both DataFrames).

    Returns
    -------
    gpd.GeoDataFrame
        Merged GeoDataFrame with geometry and parameters.

    Raises
    ------
    ImportError
        If geopandas is not installed.
    ValueError
        If join_column is missing from either DataFrame.

    Example
    -------
    >>> subs_geo = HmsSqlite.get_subbasins("Minimum_Facility.sqlite")
    >>> from hms_commander import HmsBasin
    >>> subs_params = HmsBasin.get_subbasins("Minimum_Facility.basin")
    >>> merged = HmsSqlite.join_with_parameters(subs_geo, subs_params)
    >>> print(f"Merged: {len(merged)} rows with geometry + parameters")
    """
    HmsSqlite._check_gis_dependencies()
    import geopandas as gpd

    if join_column not in sqlite_gdf.columns:
        raise ValueError(
            f"Column '{join_column}' not found in geometry GeoDataFrame. "
            f"Available: {list(sqlite_gdf.columns)}"
        )
    if join_column not in subbasin_df.columns:
        raise ValueError(
            f"Column '{join_column}' not found in parameter DataFrame. "
            f"Available: {list(subbasin_df.columns)}"
        )

    # Determine columns to bring from subbasin_df (avoid duplicates)
    geo_cols = set(sqlite_gdf.columns)
    param_cols = [join_column] + [
        c for c in subbasin_df.columns
        if c not in geo_cols and c != join_column
    ]
    param_subset = subbasin_df[param_cols].copy()

    merged = sqlite_gdf.merge(param_subset, on=join_column, how="left")

    # Ensure result is GeoDataFrame
    if not isinstance(merged, gpd.GeoDataFrame):
        merged = gpd.GeoDataFrame(merged, geometry="geometry", crs=sqlite_gdf.crs)

    logger.info(
        f"Joined {len(sqlite_gdf)} geometries with {len(subbasin_df)} parameters "
        f"on '{join_column}' -> {len(merged)} rows"
    )
    return merged

get_subbasin_statistics(sqlite_path) staticmethod

Read subbasin statistics (longest flow path, slopes, relief, etc.).

Reads from the subbasin_statistics table that HEC-HMS populates when terrain/GIS preprocessing has been performed.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

pd.DataFrame DataFrame with columns including: subbasin_name, longest_length, longest_slope, centroidal_length, centroidal_slope, 10_85_length, 10_85_slope, basin_slope, basin_relief, elongation_ratio, relief_ratio, drainage_density, length_units.

Raises

FileNotFoundError If sqlite_path does not exist. ValueError If the subbasin_statistics table is not found.

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_subbasin_statistics(sqlite_path: Union[str, Path]) -> pd.DataFrame:
    """
    Read subbasin statistics (longest flow path, slopes, relief, etc.).

    Reads from the ``subbasin_statistics`` table that HEC-HMS populates
    when terrain/GIS preprocessing has been performed.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    pd.DataFrame
        DataFrame with columns including: subbasin_name, longest_length,
        longest_slope, centroidal_length, centroidal_slope, 10_85_length,
        10_85_slope, basin_slope, basin_relief, elongation_ratio,
        relief_ratio, drainage_density, length_units.

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ValueError
        If the subbasin_statistics table is not found.
    """
    return HmsSqlite._read_table(sqlite_path, "subbasin_statistics")

get_reach_statistics(sqlite_path) staticmethod

Read reach statistics (length, slope, relief, sinuosity).

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

pd.DataFrame DataFrame with columns: reach_name, reach_length, reach_slope, reach_relief, reach_sinuosity, length_units.

Raises

FileNotFoundError If sqlite_path does not exist. ValueError If the reach_statistics table is not found.

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_reach_statistics(sqlite_path: Union[str, Path]) -> pd.DataFrame:
    """
    Read reach statistics (length, slope, relief, sinuosity).

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    pd.DataFrame
        DataFrame with columns: reach_name, reach_length, reach_slope,
        reach_relief, reach_sinuosity, length_units.

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ValueError
        If the reach_statistics table is not found.
    """
    return HmsSqlite._read_table(sqlite_path, "reach_statistics")

get_longest_flowpaths(sqlite_path) staticmethod

Read longest flow path linestrings per subbasin.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with columns: subbasin, geometry (LineString).

Raises

FileNotFoundError If sqlite_path does not exist. ValueError If the longest_flowpath table is not found.

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_longest_flowpaths(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read longest flow path linestrings per subbasin.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with columns: subbasin, geometry (LineString).

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ValueError
        If the longest_flowpath table is not found.
    """
    return HmsSqlite._read_layer(sqlite_path, "longest_flowpath")

get_centroidal_flowpaths(sqlite_path) staticmethod

Read centroidal flow path linestrings per subbasin.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with columns: subbasin, geometry (LineString).

Raises

FileNotFoundError If sqlite_path does not exist. ValueError If the centroidal_flowpath table is not found.

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_centroidal_flowpaths(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read centroidal flow path linestrings per subbasin.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with columns: subbasin, geometry (LineString).

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ValueError
        If the centroidal_flowpath table is not found.
    """
    return HmsSqlite._read_layer(sqlite_path, "centroidal_flowpath")

get_teneightyfive_flowpaths(sqlite_path) staticmethod

Read 10-85% flow path linestrings per subbasin.

Parameters

sqlite_path : Union[str, Path] Path to the SQLite database file.

Returns

gpd.GeoDataFrame GeoDataFrame with columns: subbasin, geometry (LineString).

Raises

FileNotFoundError If sqlite_path does not exist. ValueError If the teneightyfive_flowpath table is not found.

Source code in hms_commander/HmsSqlite.py
@staticmethod
@log_call
def get_teneightyfive_flowpaths(sqlite_path: Union[str, Path]) -> 'gpd.GeoDataFrame':
    """
    Read 10-85% flow path linestrings per subbasin.

    Parameters
    ----------
    sqlite_path : Union[str, Path]
        Path to the SQLite database file.

    Returns
    -------
    gpd.GeoDataFrame
        GeoDataFrame with columns: subbasin, geometry (LineString).

    Raises
    ------
    FileNotFoundError
        If sqlite_path does not exist.
    ValueError
        If the teneightyfive_flowpath table is not found.
    """
    return HmsSqlite._read_layer(sqlite_path, "teneightyfive_flowpath")
CLB Engineering Corporation  ·  LLM Forward Engineering
HMS Commander is a free and open-source project maintained by CLB Engineering Corporation. For agencies and firms seeking to modernize H&H workflows with LLM Forward approaches, contact CLB to partner with the engineers who wrote the automation.