ccfx 1.0.9__py3-none-any.whl → 1.1.1__py3-none-any.whl

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.
ccfx/ccfx.py CHANGED
@@ -22,7 +22,6 @@ import platform
22
22
  import zipfile
23
23
  import pickle
24
24
  import time
25
- from shapely.geometry import box, Point
26
25
  import geopandas, pandas
27
26
  from collections import defaultdict
28
27
  import py7zr
@@ -38,6 +37,12 @@ import yt_dlp
38
37
  from typing import Optional, Any
39
38
  from datetime import datetime, timedelta
40
39
  from PIL import Image
40
+ import scipy as scipy
41
+ from shapely.geometry import LineString, Polygon, MultiPolygon, box, Point
42
+ from shapely.ops import polygonize, unary_union
43
+ import rasterio
44
+ from rasterio import features
45
+ from rasterio.transform import from_bounds
41
46
 
42
47
  # functions
43
48
  def listFiles(path: str, ext: Optional[str] = None) -> list:
@@ -568,6 +573,14 @@ def correctFisheye(inputFile: str, outputFile: str = '',
568
573
  subprocess.run(cmd, check=True)
569
574
  return outputFile
570
575
 
576
+ def correctLens(inputFile: str, outputFile: str = '',
577
+ k1: float = -0.1, k2: float = 0.05,
578
+ cx: float = 0.5, cy: float = 0.5,
579
+ crf: int = 20) -> str:
580
+ """
581
+ Alias for correctFisheye
582
+ """
583
+ return correctFisheye(inputFile, outputFile, k1, k2, cx, cy, crf)
571
584
 
572
585
  def formatStringBlock(input_str: str, max_chars: int = 70) -> str:
573
586
  '''
@@ -680,6 +693,76 @@ def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_co
680
693
 
681
694
 
682
695
 
696
+
697
+ def createPolygonFromOuterPoints(pointsGdf: geopandas.GeoDataFrame, alpha: float = 1.6, keepHoles: bool = False) -> geopandas.GeoDataFrame:
698
+ """
699
+ Concave hull (alpha-shape) from points.
700
+ alpha: larger -> tighter/more detailed; too large can fragment.
701
+ keepHoles: keep interior holes if True, otherwise drop them.
702
+ """
703
+
704
+ pointsGdf = pointsGdf[pointsGdf.geometry.type.eq("Point") & pointsGdf.geometry.notna()]
705
+ if pointsGdf.empty or pointsGdf.geometry.nunique() < 3:
706
+ raise ValueError("need at least three distinct points")
707
+
708
+ # coordinates (N, 2)
709
+ coords = numpy.array([(g.x, g.y) for g in pointsGdf.geometry])
710
+
711
+ tri = scipy.spatial.Delaunay(coords)
712
+ simplices = tri.simplices # (M, 3) indices into coords
713
+ triPts = coords[simplices] # (M, 3, 2)
714
+
715
+ # side lengths
716
+ a = numpy.linalg.norm(triPts[:, 1] - triPts[:, 2], axis=1)
717
+ b = numpy.linalg.norm(triPts[:, 0] - triPts[:, 2], axis=1)
718
+ c = numpy.linalg.norm(triPts[:, 0] - triPts[:, 1], axis=1)
719
+
720
+ s = (a + b + c) / 2.0
721
+ # heron area, guard against tiny/negative due to fp error
722
+ areaSq = numpy.maximum(s * (s - a) * (s - b) * (s - c), 0.0)
723
+ area = numpy.sqrt(areaSq)
724
+ valid = area > 0.0
725
+ if not numpy.any(valid):
726
+ hull = pointsGdf.unary_union.convex_hull
727
+ return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
728
+
729
+ circumradius = (a * b * c) / (4.0 * area)
730
+ keep = valid & (circumradius < (1.0 / alpha))
731
+ keptSimplices = simplices[keep]
732
+ if keptSimplices.size == 0:
733
+ hull = pointsGdf.unary_union.convex_hull
734
+ return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
735
+
736
+ # count triangle edges; boundary edges appear exactly once
737
+ edgeCounts: dict[tuple[int, int], int] = {}
738
+ for i0, i1, i2 in keptSimplices:
739
+ for e in ((i0, i1), (i1, i2), (i2, i0)):
740
+ key = (e[0], e[1]) if e[0] < e[1] else (e[1], e[0])
741
+ edgeCounts[key] = edgeCounts.get(key, 0) + 1
742
+
743
+ boundaryLines = [
744
+ LineString([coords[i], coords[j]])
745
+ for (i, j), count in edgeCounts.items()
746
+ if count == 1
747
+ ]
748
+ if not boundaryLines:
749
+ hull = pointsGdf.unary_union.convex_hull
750
+ return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
751
+
752
+ polygons = list(polygonize(boundaryLines))
753
+ if not polygons:
754
+ hull = pointsGdf.unary_union.convex_hull
755
+ return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
756
+
757
+ merged = unary_union(polygons)
758
+ if isinstance(merged, MultiPolygon):
759
+ merged = max(merged.geoms, key=lambda g: g.area)
760
+ if not keepHoles and isinstance(merged, Polygon):
761
+ merged = Polygon(merged.exterior)
762
+
763
+ return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[merged], crs=pointsGdf.crs)
764
+
765
+
683
766
  def mergeRasterTiles(tileList:list, outFile:str) -> str:
684
767
  '''
685
768
  Merge raster tiles into one raster file
@@ -1030,6 +1113,116 @@ def extractCompressedFile(inputFile: str, outputDir: str, v: bool = False) -> No
1030
1113
  """
1031
1114
  uncompress(inputFile, outputDir, v)
1032
1115
 
1116
+
1117
+
1118
+ def rasterizeGDF(gdf: geopandas.GeoDataFrame, valueField: str, outRasterFN: str, resolution: float, isCOG: bool = True, allTouched: bool = False, profileOverrides: dict | None = None) -> str:
1119
+ """
1120
+ Rasterize a GeoDataFrame to GeoTIFF/COG.
1121
+
1122
+ Parameters
1123
+ ----------
1124
+ gdf : geopandas.GeoDataFrame
1125
+ valueField : str
1126
+ Column to burn as pixel values.
1127
+ outRasterFN : str
1128
+ resolution : float
1129
+ Pixel size in CRS units (assumes a projected CRS).
1130
+ isCOG : bool, default True
1131
+ If True, writes Cloud-Optimized GeoTIFF. Otherwise plain GeoTIFF.
1132
+ allTouched : bool, default False
1133
+ Pass-through to rasterio.features.rasterize.
1134
+ profileOverrides : dict | None
1135
+ Extra GDAL profile options (e.g., {"compress": "LZW"}). Overrides defaults.
1136
+
1137
+ Returns
1138
+ -------
1139
+ str
1140
+ The path written to (outRasterFN).
1141
+ """
1142
+ # basic checks
1143
+ if gdf is None or len(gdf) == 0 or gdf.geometry.isna().all():
1144
+ raise ValueError("gdf is empty or has no valid geometries.")
1145
+ if gdf.crs is None:
1146
+ raise ValueError("gdf must have a defined CRS.")
1147
+ if resolution <= 0:
1148
+ raise ValueError("resolution must be > 0.")
1149
+ if valueField not in gdf.columns:
1150
+ raise ValueError(f"valueField '{valueField}' not found in gdf.")
1151
+
1152
+ # compute raster shape + transform
1153
+ bounds = gdf.total_bounds # (minx, miny, maxx, maxy)
1154
+ width = int(numpy.ceil((bounds[2] - bounds[0]) / float(resolution)))
1155
+ height = int(numpy.ceil((bounds[3] - bounds[1]) / float(resolution)))
1156
+ if width < 1 or height < 1:
1157
+ raise ValueError("computed raster dimensions are invalid (check resolution and bounds).")
1158
+
1159
+ transform = from_bounds(bounds[0], bounds[1], bounds[2], bounds[3], width, height)
1160
+
1161
+ # infer dtype + nodata
1162
+ pandasDtype = gdf[valueField].dtype
1163
+ if numpy.issubdtype(pandasDtype, numpy.floating):
1164
+ dtype = numpy.float32
1165
+ nodata = numpy.nan
1166
+ fillValue = numpy.nan
1167
+ elif numpy.issubdtype(pandasDtype, numpy.bool_):
1168
+ dtype = numpy.uint8
1169
+ nodata = 255 # sentinel for bool raster
1170
+ fillValue = nodata
1171
+ else:
1172
+ dtype = numpy.int32
1173
+ nodata = -9999
1174
+ fillValue = nodata
1175
+
1176
+ # prefill target array
1177
+ raster = numpy.full((height, width), fillValue, dtype=dtype)
1178
+
1179
+ # build shapes generator (ensure python scalars)
1180
+ shapes = ((geom, (None if numpy.isnan(val) else val) if isinstance(val, float) else int(val) if numpy.issubdtype(type(val), numpy.integer) else float(val))
1181
+ for geom, val in zip(gdf.geometry, gdf[valueField]))
1182
+
1183
+ # burn
1184
+ features.rasterize(
1185
+ shapes=shapes,
1186
+ out_shape=raster.shape,
1187
+ transform=transform,
1188
+ fill=fillValue,
1189
+ out=raster,
1190
+ all_touched=allTouched,
1191
+ dtype=dtype
1192
+ )
1193
+
1194
+ # default profile settings
1195
+ profile = {
1196
+ "driver": "COG" if isCOG else "GTiff",
1197
+ "height": raster.shape[0],
1198
+ "width": raster.shape[1],
1199
+ "count": 1,
1200
+ "dtype": raster.dtype,
1201
+ "crs": gdf.crs,
1202
+ "transform": transform,
1203
+ "nodata": nodata,
1204
+ }
1205
+ # sane compression defaults
1206
+ if isCOG:
1207
+ profile.setdefault("compress", "LZW")
1208
+ profile.setdefault("blocksize", 512) # GDAL COG option via rasterio
1209
+ # overviews are handled by the COG driver
1210
+ else:
1211
+ profile.setdefault("compress", "LZW"),
1212
+ profile.setdefault("tiled", True)
1213
+ profile.setdefault("blockxsize", 512)
1214
+ profile.setdefault("blockysize", 512)
1215
+
1216
+ if profileOverrides:
1217
+ profile.update(profileOverrides)
1218
+
1219
+ # write
1220
+ with rasterio.open(outRasterFN, "w", **profile) as dst:
1221
+ dst.write(raster, 1)
1222
+
1223
+ return outRasterFN
1224
+
1225
+
1033
1226
  def moveDirectory(srcDir:str, destDir:str, v:bool = False) -> bool:
1034
1227
  '''
1035
1228
  this function moves all files from srcDir to destDir
@@ -1656,17 +1849,33 @@ def dualProgress(primaryCount: int, primaryEnd: int,
1656
1849
  Bars are redrawn entirely each call.
1657
1850
  '''
1658
1851
 
1852
+ darkBlock = '█'
1853
+ denseBlock = '▒'
1854
+ lightBlock = '░'
1855
+ emptyBlock = '-'
1856
+
1857
+
1858
+
1659
1859
  primaryPercent = float(primaryCount / primaryEnd * 100) if primaryEnd > 0 else 100
1660
1860
  secondaryPercent = float(secondaryCount / secondaryEnd * 100) if secondaryEnd > 0 else 100
1661
1861
 
1662
1862
  filledPrimary = int(barLength * primaryCount / primaryEnd) if primaryEnd > 0 else barLength
1663
- filledSecondary = int((barLength - filledPrimary) * secondaryCount / secondaryEnd) if secondaryEnd > 0 else barLength
1863
+ filledShadow = int(barLength * secondaryCount / secondaryEnd) if secondaryEnd > 0 else barLength
1664
1864
 
1865
+ if filledShadow < filledPrimary:
1866
+ filledPrimary = filledPrimary - filledShadow
1867
+ filledSecondary = 0
1868
+ else:
1869
+ filledShadow = 0
1870
+ filledSecondary = int(barLength * secondaryCount / secondaryEnd) if secondaryEnd > 0 else barLength
1871
+ filledSecondary = filledSecondary - filledPrimary
1872
+
1873
+ shadowSection = filledShadow
1665
1874
  startSection = filledPrimary
1666
1875
  middleSection = filledSecondary
1667
- endSection = barLength - startSection - middleSection
1876
+ endSection = barLength - startSection - middleSection - shadowSection
1668
1877
 
1669
- bar = '█' * startSection + '░' * middleSection + '-' * endSection
1878
+ bar = denseBlock * shadowSection + darkBlock * startSection + lightBlock * middleSection + emptyBlock * endSection
1670
1879
  formattedPrimaryPercent = f'{primaryPercent:03.1f}'
1671
1880
  formattedSecondaryPercent = f'{secondaryPercent:03.1f}'
1672
1881
  print(f'\r{bar} {formattedPrimaryPercent.rjust(6)}% | {formattedSecondaryPercent.rjust(6)}% | {message} ', end='', flush=True)
@@ -2076,7 +2285,7 @@ def getTimeseriesStats(data:pandas.DataFrame, observed:Optional[str] = None, sim
2076
2285
 
2077
2286
  return stats
2078
2287
 
2079
- def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None) -> Optional[pandas.DataFrame]:
2288
+ def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None, coerceNumeric: bool = True) -> Optional[pandas.DataFrame]:
2080
2289
  '''
2081
2290
  Read SWAT+ output files and return a pandas DataFrame with proper date handling
2082
2291
  and optional filtering capabilities.
@@ -2138,8 +2347,15 @@ def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optio
2138
2347
  # Convert all columns to numeric except 'name' (which is string)
2139
2348
  for col in df.columns:
2140
2349
  if col != 'name':
2141
- df[col] = pandas.to_numeric(df[col], errors='coerce')
2350
+ if coerceNumeric:
2351
+ df[col] = pandas.to_numeric(df[col], errors='coerce')
2142
2352
 
2353
+ if not coerceNumeric:
2354
+ # If not coercing to numeric, ensure date columns are numeric
2355
+ for mandatoryCol in ['yr', 'mon', 'day', 'gis_id']:
2356
+ if mandatoryCol in df.columns:
2357
+ df[mandatoryCol] = pandas.to_numeric(df[mandatoryCol], errors='coerce')
2358
+
2143
2359
  # Create date column from yr, mon, day
2144
2360
  try:
2145
2361
  df['date'] = pandas.to_datetime(pandas.DataFrame({'year': df.yr, 'month': df.mon, 'day': df.day}))
@@ -2179,4 +2395,5 @@ def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optio
2179
2395
 
2180
2396
  return df
2181
2397
 
2398
+
2182
2399
  ignoreWarnings()
@@ -0,0 +1,595 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccfx
3
+ Version: 1.1.1
4
+ Summary: This package simplifies regular common actions for quick prototyping in a user friendly way
5
+ Author-email: Celray James CHAWANDA <celray@chawanda.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/celray/ccfx
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: netCDF4
14
+ Requires-Dist: yt_dlp
15
+ Requires-Dist: gdal
16
+ Requires-Dist: numpy
17
+ Requires-Dist: shapely
18
+ Requires-Dist: geopandas
19
+ Requires-Dist: pandas
20
+ Requires-Dist: xlsxwriter
21
+ Requires-Dist: pyodbc
22
+ Requires-Dist: sqlalchemy
23
+ Requires-Dist: python-docx
24
+ Requires-Dist: py7zr
25
+ Requires-Dist: mutagen
26
+ Requires-Dist: requests
27
+ Requires-Dist: tqdm
28
+ Requires-Dist: pillow
29
+ Requires-Dist: scipy
30
+ Requires-Dist: rasterio
31
+ Requires-Dist: matplotlib
32
+ Dynamic: license-file
33
+
34
+ # ccfx
35
+
36
+ [![PyPI version](https://badge.fury.io/py/ccfx.svg)](https://badge.fury.io/py/ccfx)
37
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
38
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
39
+ [![GitHub stars](https://img.shields.io/github/stars/celray/ccfx.svg)](https://github.com/celray/ccfx/stargazers)
40
+
41
+ `ccfx` is a comprehensive Python package designed to streamline file and data management, geospatial analysis, NetCDF file processing, database interactions, document generation, and multimedia handling for rapid prototyping and development workflows.
42
+
43
+ ## Table of Contents
44
+
45
+ - [Features](#features)
46
+ - [Installation](#installation)
47
+ - [Quick Start](#quick-start)
48
+ - [Dependencies](#dependencies)
49
+ - [Usage Examples](#usage-examples)
50
+ - [API Reference](#api-reference-selected-functions)
51
+ - [File Management](#file-management-ccfxpy)
52
+ - [Geospatial](#geospatial-ccfxpy)
53
+ - [NetCDF](#netcdf-ccfxpy)
54
+ - [Database](#database-mssqlconnectionpy-sqliteconnectionpy)
55
+ - [Document/Spreadsheet](#documentspreadsheet-wordpy-excelpy)
56
+ - [Multimedia & Web](#multimedia--web-ccfxpy)
57
+ - [Data Analysis & Utilities](#data-analysis--utilities-ccfxpy)
58
+ - [System Requirements](#system-requirements)
59
+ - [Contributing](#contributing)
60
+ - [Changelog](#changelog)
61
+ - [License](#license)
62
+
63
+ ## Features
64
+
65
+ 1. **File Management**:
66
+ * List, delete, move, copy, and count files/directories.
67
+ * Monitor file count over time.
68
+ * Save, load, and manage Python variables via pickle serialization.
69
+ * Compress directories to `.7z` archives.
70
+ * Read/write text files with encoding support.
71
+ * Download files from URLs with resume and multi-connection support.
72
+
73
+ 2. **Geospatial Data Processing**:
74
+ * Read, write, clip (by extent/feature), resample, reproject, merge, and rasterize raster data (GeoTIFF, NetCDF).
75
+ * Read, write, and clip vector data (Shapefile, GeoPackage).
76
+ * Create grids of polygons based on shapefile boundaries.
77
+ * Convert coordinates between Coordinate Reference Systems (CRS).
78
+ * Extract raster values at specific coordinates.
79
+ * Convert point lists to GeoDataFrames.
80
+ * Get vector layer bounds.
81
+
82
+ 3. **NetCDF File Handling**:
83
+ * List variables and dimensions.
84
+ * Export NetCDF variables to GeoTIFF format (single or multiple bands).
85
+ * Calculate sum and average maps from NetCDF data across multiple files.
86
+ * Rename variables using CDO (if available).
87
+
88
+ 4. **Database Connectivity**:
89
+ * **MS SQL Server**: Connect, list databases/tables, read tables (including spatial data into GeoDataFrames), write DataFrames/GeoDataFrames to tables, drop tables.
90
+ * **SQLite**: Connect, create/rename/drop tables, read tables (as dict, specific columns), insert data (rows, dicts, partial dicts), update values, dump tables to CSV.
91
+
92
+ 5. **Document & Spreadsheet Handling**:
93
+ * **Excel**: Create `.xlsx` files, add sheets, write data (including dates), set column widths, add scatter plot charts.
94
+ * **Word**: Create `.docx` files, add headings, paragraphs (with alignment), list items, formatted text (bold/italic), images, page breaks, set margins.
95
+
96
+ 6. **Multimedia & Web**:
97
+ * Read and write MP3 metadata (ID3 tags), including album art.
98
+ * Download videos/audio from YouTube using `yt-dlp`.
99
+
100
+ 7. **Data Analysis & Utilities**:
101
+ * Calculate timeseries statistics (NSE, KGE, PBIAS, LNSE, R2, RMSE, MAE, MSE, MAPE, alpha, beta) with resampling options.
102
+ * Display dynamic progress bars.
103
+ * Check system platform information.
104
+ * Enable or disable warnings programmatically.
105
+ * Set the working directory.
106
+
107
+ ## Installation
108
+
109
+ Install `ccfx` via pip:
110
+ ```bash
111
+ pip install ccfx
112
+ ```
113
+
114
+ > **Note**: GDAL is a core dependency and may require additional system-level installation steps. See [System Requirements](#system-requirements) for details.
115
+
116
+ ## Quick Start
117
+
118
+ Here's a simple example to get you started with `ccfx`:
119
+
120
+ ```python
121
+ import ccfx
122
+
123
+ # File management
124
+ files = ccfx.listFiles("/path/to/directory", ext=".txt")
125
+ print(f"Found {len(files)} text files")
126
+
127
+ # Create and save data to Excel
128
+ excel_doc = ccfx.excel("output.xlsx")
129
+ excel_doc.create()
130
+ excel_doc.addSheet("MyData")
131
+ excel_doc.write("A1", "Hello, ccfx!")
132
+ excel_doc.save()
133
+
134
+ # Work with geospatial data
135
+ bounds = (-180, -90, 180, 90) # Global bounds
136
+ ccfx.clipRasterByExtent("input.tif", "clipped.tif", bounds)
137
+
138
+ # Download and process data
139
+ ccfx.downloadFile("https://example.com/data.zip", "./data.zip")
140
+ ccfx.compressTo7z("./my_folder", "archive.7z")
141
+ ```
142
+
143
+ ## Dependencies
144
+
145
+ `ccfx` relies on the following libraries (automatically installed with pip):
146
+
147
+ ### Core Dependencies
148
+ * **gdal**: For geospatial raster and vector data manipulation.
149
+ * **numpy**: For array processing and numerical operations.
150
+ * **pandas**: For data manipulation and analysis.
151
+ * **geopandas**: Extends pandas to handle geospatial vector data.
152
+ * **shapely**: Provides geometric objects and operations.
153
+ * **netCDF4**: For working with NetCDF files.
154
+ * **rasterio**: Advanced raster I/O and processing.
155
+
156
+ ### Document & Database
157
+ * **xlsxwriter**: For creating and writing Excel `.xlsx` files.
158
+ * **python-docx**: Enables creation and manipulation of Word `.docx` documents.
159
+ * **pyodbc**: Enables connectivity to databases through ODBC (e.g., MS SQL Server).
160
+ * **sqlalchemy**: Provides SQL toolkit and ORM features for database access.
161
+
162
+ ### Utilities & Multimedia
163
+ * **py7zr**: For creating `.7z` archives.
164
+ * **mutagen**: For reading and writing MP3 metadata (ID3 tags).
165
+ * **requests**: For downloading files via HTTP.
166
+ * **tqdm**: For displaying progress bars.
167
+ * **yt-dlp**: For downloading YouTube content.
168
+ * **pillow**: For image processing.
169
+ * **scipy**: For scientific computing.
170
+ * **matplotlib**: For plotting and visualization.
171
+
172
+ ## System Requirements
173
+
174
+ ### Python Version
175
+ - **Python 3.10+** is required
176
+
177
+ ### GDAL Installation
178
+ GDAL can be challenging to install depending on your operating system:
179
+
180
+ #### Windows
181
+ ```bash
182
+ # Using conda (recommended)
183
+ conda install -c conda-forge gdal
184
+
185
+ # Or using pip with pre-compiled binaries
186
+ pip install GDAL
187
+
188
+ # Alternative: If struggling with GDAL installation on Windows
189
+ pip install gdal-installer
190
+ install-gdal
191
+ ```
192
+
193
+ #### macOS
194
+ ```bash
195
+ # Using Homebrew
196
+ brew install gdal
197
+ pip install gdal
198
+
199
+ # Using conda
200
+ conda install -c conda-forge gdal
201
+ ```
202
+
203
+ #### Linux (Ubuntu/Debian)
204
+ ```bash
205
+ # Install system dependencies
206
+ sudo apt-get update
207
+ sudo apt-get install gdal-bin libgdal-dev
208
+
209
+ # Install Python bindings
210
+ pip install gdal
211
+ ```
212
+
213
+ #### Docker
214
+ For a hassle-free setup, consider using the official GDAL Docker images:
215
+ ```bash
216
+ docker pull ghcr.io/osgeo/gdal:ubuntu-small-latest
217
+ ```
218
+
219
+ ## Usage Examples
220
+
221
+ ### File Management
222
+ ```python
223
+ import ccfx
224
+
225
+ # List all Python files in a directory
226
+ python_files = ccfx.listFiles("/path/to/project", ext=".py")
227
+
228
+ # Create a backup directory and copy files
229
+ ccfx.createPath("/backup/location")
230
+ ccfx.copyDirectory("/source/dir", "/backup/location")
231
+
232
+ # Compress a directory
233
+ ccfx.compressTo7z("/data/folder", "backup.7z")
234
+
235
+ # Download a file with resume capability
236
+ ccfx.downloadFile(
237
+ "https://example.com/largefile.zip",
238
+ "downloaded_file.zip",
239
+ exists_action='resume'
240
+ )
241
+ ```
242
+
243
+ ### Geospatial Data Processing
244
+ ```python
245
+ import ccfx
246
+
247
+ # Clip a raster to a specific bounding box
248
+ bounds = (-74.0, 40.7, -73.9, 40.8) # NYC area
249
+ ccfx.clipRasterByExtent("satellite_image.tif", "nyc_clip.tif", bounds)
250
+
251
+ # Resample a raster to different resolution
252
+ ccfx.resampleRaster(
253
+ "high_res.tif",
254
+ "low_res.tif",
255
+ resolution=1000, # 1km resolution
256
+ resamplingMethod='bilinear'
257
+ )
258
+
259
+ # Convert coordinates between projections
260
+ lat, lon = 40.7128, -74.0060 # NYC coordinates
261
+ x, y = ccfx.convertCoordinates(lon, lat, "EPSG:4326", "EPSG:3857")
262
+
263
+ # Extract raster value at specific point
264
+ value = ccfx.extractRasterValue("elevation.tif", 40.7128, -74.0060)
265
+ print(f"Elevation at NYC: {value}")
266
+ ```
267
+
268
+ ### Database Operations
269
+ ```python
270
+ import ccfx
271
+
272
+ # SQLite operations
273
+ db = ccfx.sqliteConnection("my_data.db", connect=True)
274
+ db.createTable("users", ["id INTEGER PRIMARY KEY", "name TEXT", "email TEXT"])
275
+ db.insertDict("users", {"name": "John Doe", "email": "john@example.com"})
276
+ users = db.readTableAsDict("users")
277
+ db.closeConnection()
278
+
279
+ # MS SQL Server operations
280
+ mssql = ccfx.mssql_connection("server", "username", "password", "driver")
281
+ mssql.connect()
282
+ databases = mssql.listDatabases()
283
+ df = mssql.readTable("MyDatabase", "MyTable")
284
+ mssql.close()
285
+ ```
286
+
287
+ ### Document Generation
288
+ ```python
289
+ import ccfx
290
+
291
+ # Create Excel spreadsheet
292
+ excel = ccfx.excel("report.xlsx")
293
+ excel.create()
294
+ excel.addSheet("Sales Data")
295
+ excel.write("A1", "Product")
296
+ excel.write("B1", "Revenue")
297
+ excel.writeColumn("A", ["Product A", "Product B", "Product C"], start_row=2)
298
+ excel.writeColumn("B", [1000, 1500, 800], start_row=2)
299
+ excel.save()
300
+
301
+ # Create Word document
302
+ doc = ccfx.word_document("report.docx")
303
+ doc.addHeading("Monthly Report", level=1)
304
+ doc.addParagraph("This report summarizes our monthly performance.")
305
+ doc.addListItem("Revenue increased by 15%")
306
+ doc.addListItem("Customer satisfaction improved")
307
+ doc.save()
308
+ ```
309
+
310
+ ### NetCDF Processing
311
+ ```python
312
+ import ccfx
313
+
314
+ # List variables in NetCDF file
315
+ variables = ccfx.netcdfVariablesList("climate_data.nc")
316
+ print("Available variables:", variables)
317
+
318
+ # Export NetCDF variable to GeoTIFF
319
+ ccfx.netcdfExportTif(
320
+ "temperature_data.nc",
321
+ "temperature",
322
+ "temp_map.tif",
323
+ band=1
324
+ )
325
+
326
+ # Calculate average from multiple NetCDF files
327
+ nc_files = ["data_2020.nc", "data_2021.nc", "data_2022.nc"]
328
+ avg_map = ccfx.netcdfAverageMap(nc_files, "precipitation")
329
+ ```
330
+
331
+ ### Data Analysis
332
+ ```python
333
+ import ccfx
334
+ import pandas as pd
335
+
336
+ # Calculate timeseries statistics
337
+ observed_data = pd.read_csv("observed.csv")
338
+ simulated_data = pd.read_csv("simulated.csv")
339
+
340
+ # Combine data
341
+ data = pd.merge(observed_data, simulated_data, on='date')
342
+
343
+ # Calculate Nash-Sutcliffe Efficiency
344
+ stats = ccfx.calculateTimeseriesStats(data, observed='obs', simulated='sim')
345
+ print(f"NSE: {stats['NSE']:.3f}")
346
+ print(f"KGE: {stats['KGE']:.3f}")
347
+ print(f"R²: {stats['R2']:.3f}")
348
+ ```
349
+
350
+ ## API Reference (Complete Function List)
351
+
352
+ ### File & Directory Management (`ccfx.py`)
353
+
354
+ **Basic File Operations:**
355
+ * **`listFiles(path: str, ext: Optional[str] = None) -> list`**: Lists files in a directory, optionally filtering by extension.
356
+ * **`listAllFiles(folder: str, extension: str = "*") -> list`**: Recursively lists all files in a folder and its subfolders.
357
+ * **`listFolders(path: str) -> list`**: Lists all folders in a directory.
358
+ * **`listDirectories(path: str) -> list`**: Alias for listFolders.
359
+ * **`deleteFile(filePath: str, v: bool = False) -> bool`**: Deletes a specified file.
360
+ * **`deletePath(path: str, v: bool = False) -> bool`**: Deletes a directory and its contents.
361
+ * **`createPath(pathName: str, v: bool = False) -> str`**: Creates a directory path if it doesn't exist.
362
+ * **`getExtension(filePath: str) -> str`**: Gets the extension of a file.
363
+ * **`getFileBaseName(filePath: str, extension: bool = True) -> str`**: Gets the base name of a file.
364
+
365
+ **File Copying & Moving:**
366
+ * **`copyFile(source: str, destination: str, v: bool = True) -> None`**: Copies a single file.
367
+ * **`copyDirectory(source: str, destination: str, recursive: bool = True, v: bool = True, filter: list = []) -> None`**: Copies a directory's contents.
368
+ * **`copyFolder(source: str, destination: str, v: bool = True) -> None`**: Alias for copyDirectory.
369
+ * **`moveDirectory(srcDir: str, destDir: str, v: bool = False) -> bool`**: Moves all files from source to destination directory.
370
+ * **`moveDirectoryFiles(srcDir: str, destDir: str, v: bool = False) -> bool`**: Moves files and subdirectories from source to destination.
371
+
372
+ **File I/O Operations:**
373
+ * **`readFrom(filename: str, decode_codec: Optional[str] = None, v: bool = False) -> Any`**: Reads ASCII files.
374
+ * **`readFile(filename: str, decode_codec: Optional[str] = None, v: bool = False) -> Any`**: Alias for readFrom.
375
+ * **`writeTo(filename: str, file_text: Any, encode_codec: Optional[str] = None, v: bool = False) -> bool`**: Writes ASCII files.
376
+ * **`writeToFile(filename: str, file_text: Any, encode_codec: Optional[str] = None, v: bool = False) -> bool`**: Alias for writeTo.
377
+ * **`writeFile(filename: str, file_text: Any, encode_codec: Optional[str] = None, v: bool = False) -> bool`**: Alias for writeTo.
378
+
379
+ **Compression & Archives:**
380
+ * **`compressTo7z(input_dir: str, output_file: str, compressionLevel: int = 4, excludeExt: Optional[list] = None, v: bool = False) -> None`**: Compresses a directory into a .7z file.
381
+ * **`uncompress(inputFile: str, outputDir: str, v: bool = False) -> None`**: Extracts various archive formats (.7z, .zip, .tar, etc.).
382
+ * **`uncompressFile(inputFile: str, outputDir: str, v: bool = False) -> None`**: Alias for uncompress.
383
+ * **`unzipFile(inputFile: str, outputDir: str, v: bool = False) -> None`**: Alias for uncompress.
384
+ * **`extractZip(inputFile: str, outputDir: str, v: bool = False) -> None`**: Alias for uncompress.
385
+ * **`extractCompressedFile(inputFile: str, outputDir: str, v: bool = False) -> None`**: Alias for uncompress.
386
+
387
+ **File Monitoring & Statistics:**
388
+ * **`fileCount(path: str = "./", extension: str = ".*", v: bool = True) -> int`**: Gets the number of files in a directory with a specific extension.
389
+ * **`watchFileCount(path: str = "./", extension: str = ".*", interval: float = 0.2, duration = 3, v: bool = True) -> None`**: Monitors file count over time.
390
+
391
+ **Variable Persistence:**
392
+ * **`pythonVariable(filename: str, option: str, variable: Any = None) -> Any`**: Saves ('dump') or loads ('load') Python variables using pickle.
393
+
394
+ **Download Operations:**
395
+ * **`downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_connections: int = 5, v: bool = False) -> None`**: Downloads files with resume and multi-connection support.
396
+ * **`downloadChunk(url: str, start: int, end: int, path: str) -> None`**: Internal function for chunked downloads.
397
+
398
+ ### Geospatial Data Processing (`ccfx.py`)
399
+
400
+ **Raster Operations:**
401
+ * **`clipRasterByExtent(inFile: str, outFile: str, bounds: tuple) -> str`**: Clips a raster using bounding box coordinates.
402
+ * **`clipRasterByVector(inFile: str, outFile: str, vectorFile: str) -> str`**: Clips a raster using a vector file.
403
+ * **`resampleRaster(inFile: str, outFile: str, resolution: float, dstSRS = None, resamplingMethod = 'bilinear', replaceOutput: bool = True, v: bool = True) -> Optional[str]`**: Resamples a raster to a new resolution.
404
+ * **`reprojectRaster(inFile: str, outFile: str, dstProjection: str, resamplingMethod: str = 'mode') -> str`**: Reprojects a raster to a new CRS.
405
+ * **`mergeRasterTiles(tileList: list, outFile: str) -> str`**: Merges multiple raster files into one.
406
+ * **`mergeRasterFiles(tileList: list, outFile: str) -> str`**: Alias for mergeRasterTiles.
407
+ * **`rasterizeRaster(inFile: str, outFile: str, targetField: str, targetResolution: float) -> str`**: Rasterizes a vector layer based on an attribute field.
408
+ * **`rasterizeGDF(gdf: geopandas.GeoDataFrame, valueField: str, outRasterFN: str, resolution: float, isCOG: bool = True, allTouched: bool = False, profileOverrides: dict | None = None) -> str`**: Rasterizes a GeoDataFrame to GeoTIFF/COG.
409
+ * **`tiffWriteArray(array: numpy.ndarray, outputFile: str, geoTransform: tuple = (0, 1, 0, 0, 0, -1), projection: str = 'EPSG:4326', noData: Optional[float] = None, v: bool = False) -> gdal.Dataset`**: Writes a NumPy array to a GeoTIFF file.
410
+
411
+ **Vector Operations:**
412
+ * **`clipVectorByExtent(inFile: str, outFile: str, bounds: tuple) -> str`**: Clips a vector file using bounding box coordinates.
413
+ * **`clipFeatures(inputFeaturePath: str, boundaryFeature: str, outputFeature: str, keepOnlyTypes: Optional[list] = None, v: bool = False) -> geopandas.GeoDataFrame`**: Clips input features by a boundary feature.
414
+ * **`getVectorBounds(grid_gdf: geopandas.GeoDataFrame) -> tuple`**: Gets the bounds of a GeoDataFrame.
415
+
416
+ **Coordinate & Geometry Operations:**
417
+ * **`convertCoordinates(lon: float, lat: float, srcEPSG: str, dstCRS: str) -> tuple`**: Converts coordinates between CRSs.
418
+ * **`extractRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> Optional[float]`**: Extracts the raster value at a specific point.
419
+ * **`getRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> Optional[float]`**: Alias for extractRasterValue.
420
+ * **`pointsToGeodataframe(rowList: list, columnNames: list, latIndex: int, lonIndex: int, auth: str = "EPSG", code: str = "4326", outShape: str = "", format: str = "gpkg", v: bool = False, includeLatLon: bool = True) -> geopandas.GeoDataFrame`**: Converts a list of point coordinates to a GeoDataFrame.
421
+ * **`createPointGeometry(coords: list, proj: str = "EPSG:4326") -> geopandas.GeoDataFrame`**: Converts list of coordinate tuples to GeoDataFrame.
422
+ * **`createGrid(topLeft: Optional[list] = None, bottomRight: Optional[list] = None, resolution: Optional[float] = None, inputShape: Optional[str] = None, crs: str = "EPSG:4326", saveVector: Optional[str] = None) -> geopandas.GeoDataFrame`**: Creates a grid of polygons based on shapefile or coordinates.
423
+ * **`createPolygonFromOuterPoints(pointsGdf: geopandas.GeoDataFrame, alpha: float = 1.6, keepHoles: bool = False) -> geopandas.GeoDataFrame`**: Creates concave hull (alpha-shape) from points.
424
+
425
+ ### NetCDF File Handling (`ccfx.py`)
426
+
427
+ * **`netcdfVariablesList(ncFile: str) -> list`**: Lists variables in a NetCDF file.
428
+ * **`netcdfVariableDimensions(ncFile: str, variable: str) -> dict`**: Gets dimensions and their sizes for a NetCDF variable.
429
+ * **`netcdfExportTif(ncFile: str, variable: str, outputFile: Optional[str] = None, band: Optional[int] = None, v: bool = True) -> gdal.Dataset`**: Exports a NetCDF variable (optionally a specific band) to GeoTIFF.
430
+ * **`netcdfAverageMap(ncFiles: list, variable: str, band: int = 1) -> numpy.ndarray`**: Calculates the average map from a variable across multiple NetCDF files.
431
+ * **`netcdfSumMaps(ncFiles: list, variable: str, band: int = 1) -> numpy.ndarray`**: Calculates the sum map from a variable across multiple NetCDF files.
432
+ * **`renameNetCDFvariable(input_file: str, output_file: str, old_var_name: str, new_var_name: str, v: bool = False) -> None`**: Renames a variable in a NetCDF file using CDO.
433
+
434
+ ### Database Connectivity
435
+
436
+ #### MS SQL Server (`mssqlConnection.py`)
437
+ **Class: `mssqlConnection(server, username, password, driver, trust_server_ssl=True)`**
438
+ * **`__init__(server, username, password, driver, trust_server_ssl=True) -> None`**: Initialize connection parameters.
439
+ * **`connect()`**: Establish connection to server.
440
+ * **`listDatabases() -> list`**: List available databases.
441
+ * **`listTables(db_name=None) -> list`**: List tables in database.
442
+ * **`readTable()`**: Read table data (including spatial data into GeoDataFrames).
443
+ * **`connectDB()`**: Connect to specific database.
444
+ * **`dataframeToSql()`**: Write DataFrame/GeoDataFrame to table.
445
+ * **`dropTable()`**: Remove table.
446
+ * **`close()`**: Close connection.
447
+
448
+ #### SQLite (`sqliteConnection.py`)
449
+ **Class: `sqliteConnection(sqlite_database, connect=False)`**
450
+ * **`__init__(sqlite_database, connect=False) -> None`**: Initialize database connection.
451
+ * **`connect(v=True) -> None`**: Establish connection.
452
+ * **`createTable(table_name, initial_field_name, data_type) -> None`**: Create new table.
453
+ * **`renameTable()`**: Rename existing table.
454
+ * **`deleteTable()`**: Remove table.
455
+ * **`readTableAsDict()`**: Read table data as dictionary.
456
+ * **`insertDict()`**: Insert data from dictionary.
457
+ * **`insertRow()`**: Insert single row.
458
+ * **`updateValue(table_name, col_name, new_value, col_where1, val_1, v=False) -> None`**: Update specific values.
459
+ * **`dumpCSV()`**: Export table to CSV.
460
+ * **`commitChanges()`**: Commit transactions.
461
+ * **`closeConnection()`**: Close connection.
462
+
463
+ ### Document & Spreadsheet Generation
464
+
465
+ #### Excel Spreadsheets (`excel.py`)
466
+ **Class: `excel(path)`**
467
+ * **`__init__(path)`**: Initialize Excel document.
468
+ * **`create()`**: Create new workbook.
469
+ * **`addSheet(sheet_name)`**: Add worksheet.
470
+ * **`write(sheet_name, row, column, value)`**: Write data to cells.
471
+ * **`writeDate(sheet_name, row, column, datetime_obj)`**: Write date values.
472
+ * **`setDateFormat(format_string='dd/mm/yyyy')`**: Set date format.
473
+ * **`setColumnWidth(sheet_name, column_names, width=12)`**: Adjust column widths.
474
+ * **`addFigure(sheet_name, x_src_sheet_name, x_start, x_end, y_src_sheet_name, y_start, y_end, position_cell="E2", chart_type='subtype')`**: Insert charts/graphs.
475
+ * **`save()`**: Save workbook.
476
+ * **`open()`**: Open existing file.
477
+
478
+ #### Word Documents (`word.py`)
479
+ **Class: `word_document(path)`**
480
+ * **`__init__(path) -> None`**: Initialize Word document.
481
+ * **`addHeading(heading, level=2)`**: Add document headings.
482
+ * **`addParagraph(text="", alignment='justify')`**: Add text paragraphs.
483
+ * **`addListItem(text="", numbers=False)`**: Add list items.
484
+ * **`addText(text, bold=False, italic=False)`**: Add formatted text.
485
+ * **`addImage(path_to_image, width_=16)`**: Insert images.
486
+ * **`addPageBreak()`**: Insert page breaks.
487
+ * **`setMargins()`**: Configure page margins.
488
+ * **`save()`**: Save document.
489
+
490
+ ### Multimedia & Web (`ccfx.py`)
491
+
492
+ **MP3 Metadata:**
493
+ * **`getMp3Metadata(fn: str, imagePath: Optional[str] = None) -> dict`**: Extracts ID3 metadata from an MP3 file.
494
+ * **`setMp3Metadata(fn: str, metadata: dict, imagePath: Optional[str] = None) -> bool`**: Writes ID3 metadata (including album art) to an MP3 file.
495
+ * **`guessMimeType(imagePath: str) -> str`**: Determines MIME type of image files.
496
+
497
+ **YouTube Downloads:**
498
+ * **`downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, cookiesFile: Optional[str] = None, dstFileName: Optional[str] = None) -> str`**: Downloads video or audio from a YouTube URL.
499
+ * **`parseYoutubePlaylist(playlistUrl: str) -> list[str]`**: Returns list of video URLs from a YouTube playlist.
500
+ * **`parseYoutubeChannelVideos(channelUrl: str, maxItems: Optional[int] = None) -> list[str]`**: Returns list of video URLs from a YouTube channel.
501
+
502
+ **Image Processing:**
503
+ * **`removeImageColour(inPath: str, outPath: str, colour: tuple = (255, 255, 255), tolerance: int = 30) -> None`**: Removes a specific color from an image.
504
+ * **`makeTransparent(inPath: str, outPath: str, colour: tuple = (255, 255, 255), tolerance: int = 30) -> None`**: Makes pixels in an image transparent.
505
+
506
+ **Video Processing:**
507
+ * **`correctFisheye(inputFile: str, outputFile: str = '', k1: float = -0.1, k2: float = 0.05, cx: float = 0.5, cy: float = 0.5, crf: int = 20) -> str`**: Corrects fisheye distortion in videos.
508
+ * **`correctLens(inputFile: str, outputFile: str = '', k1: float = -0.1, k2: float = 0.05, cx: float = 0.5, cy: float = 0.5, crf: int = 20) -> str`**: Alias for correctFisheye.
509
+
510
+ ### Data Analysis & Statistics (`ccfx.py`)
511
+
512
+ **Timeseries Analysis:**
513
+ * **`calculateTimeseriesStats(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> dict`**: Calculates comprehensive statistics between observed and simulated timeseries.
514
+ * **`getNSE(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Nash-Sutcliffe Efficiency.
515
+ * **`getKGE(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Kling-Gupta Efficiency.
516
+ * **`getPBIAS(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Percent Bias.
517
+ * **`getLNSE(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Log Nash-Sutcliffe Efficiency.
518
+ * **`getR2(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates R-squared.
519
+ * **`getRMSE(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Root Mean Square Error.
520
+ * **`getMAE(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Mean Absolute Error.
521
+ * **`getMSE(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> float`**: Calculates Mean Square Error.
522
+ * **`getTimeseriesStats(data: pandas.DataFrame, observed: Optional[str] = None, simulated: Optional[str] = None, resample: Optional[str] = None) -> dict`**: Alias for calculateTimeseriesStats.
523
+
524
+ **SWAT+ Integration:**
525
+ * **`readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None, coerceNumeric: bool = True) -> Optional[pandas.DataFrame]`**: Reads SWAT+ output files with filtering capabilities.
526
+ * **`runSWATPlus(txtinoutDir: str, finalDir: str, executablePath: str = "swatplus", v: bool = True) -> None`**: Runs SWAT+ model with progress monitoring.
527
+
528
+ ### Utility Functions (`ccfx.py`)
529
+
530
+ **Progress & Display:**
531
+ * **`progressBar(count: int, total: int, message: str = "") -> None`**: Displays a simple console progress bar.
532
+ * **`showProgress(count: int, end: int, message: str, barLength: int = 100) -> None`**: Displays a detailed console progress bar.
533
+ * **`dualProgress(primaryCount: int, primaryEnd: int, secondaryCount: int, secondaryEnd: int, barLength: int = 40, message: str = '') -> None`**: Displays two progress bars simultaneously.
534
+
535
+ **System & Environment:**
536
+ * **`systemPlatform() -> str`**: Gets the system platform.
537
+ * **`setHomeDir(path: str) -> str`**: Sets the working directory to script location.
538
+ * **`ignoreWarnings(ignore: bool = True, v: bool = False) -> None`**: Suppresses or enables Python warnings.
539
+
540
+ **Text & String Processing:**
541
+ * **`formatStringBlock(input_str: str, max_chars: int = 70) -> str`**: Formats a string into a block of text with maximum characters per line.
542
+ * **`formatTimedelta(delta: timedelta) -> str`**: Formats a timedelta duration to readable format.
543
+
544
+ **Mathematical Utilities:**
545
+ * **`isBetween(number: float, a: float, b: float) -> bool`**: Returns True if number is between a and b.
546
+
547
+ **Notifications:**
548
+ * **`alert(message: str, server: str = "http://ntfy.sh", topic: str = "pythonAlerts", attachment: Optional[str] = None, messageTitle: str = "info", priority: int = None, tags: list = [], printIt: bool = True, v: bool = False) -> bool`**: Sends notifications to external servers.
549
+
550
+ ## Contributing
551
+
552
+ Contributions are welcome! Please fork the repository, make your changes, and submit a pull request. Ensure code is well-documented and includes tests where applicable.
553
+
554
+ ### Development Setup
555
+ ```bash
556
+ git clone https://github.com/celray/ccfx.git
557
+ cd ccfx
558
+ pip install -e .
559
+ ```
560
+
561
+ ### Version Management
562
+ To automatically update the version in README.md based on `pyproject.toml`:
563
+ ```bash
564
+ # Make the script executable (if not already)
565
+ chmod +x updateReadmeVersion.py
566
+
567
+ # Run the version update script
568
+ ./updateReadmeVersion.py
569
+ ```
570
+
571
+ You can also integrate this into your build process by adding it to your CI/CD pipeline or as a pre-commit hook.
572
+
573
+ ### Testing
574
+ Please ensure your changes don't break existing functionality and add tests for new features.
575
+
576
+ ## Changelog
577
+
578
+ ### Version 1.1.1 (Current)
579
+ - Enhanced geospatial processing capabilities
580
+ - Improved database connectivity options
581
+ - Added comprehensive multimedia support
582
+ - Better error handling and documentation
583
+
584
+ For detailed release notes, visit the [Releases page](https://github.com/celray/ccfx/releases).
585
+
586
+ ## License
587
+
588
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
589
+
590
+ ---
591
+
592
+ **Author**: Celray James CHAWANDA
593
+ **Email**: celray@chawanda.com
594
+ **GitHub**: [@celray](https://github.com/celray)
595
+ **Package Homepage**: [https://github.com/celray/ccfx](https://github.com/celray/ccfx)
@@ -1,11 +1,11 @@
1
1
  ccfx/__init__.py,sha256=UK62VcGS84SJyGVg1bK4FltZj7OkpdoyhoFWeXcKsX0,144
2
- ccfx/ccfx.py,sha256=doZ9tSJWRBAC3TKd6joThjDpivc6ef3bd1U5FuOVVh0,78988
2
+ ccfx/ccfx.py,sha256=H1Kqo7rQuiGkH_pr8KxWMwZVY5tOUuMWoNk29KzMkt0,87188
3
3
  ccfx/excel.py,sha256=vm_cm4huKKx4_Nstr5neJzhBLmoZjg8qxjzz4hcF5hg,4754
4
4
  ccfx/mssqlConnection.py,sha256=C3HxzgZHmHy_de9EbMaXzR8NrkJxwHc8a00qzxQu_gs,8984
5
5
  ccfx/sqliteConnection.py,sha256=pOT9BBEAcm2kmoS0yBkUi4m9srQVe62J4xG5bnddvis,16207
6
6
  ccfx/word.py,sha256=AGa64jX5Zl5qotZh5L0QmrsjTnktIBhmj_ByRKZ88vw,3061
7
- ccfx-1.0.9.dist-info/licenses/LICENSE,sha256=EuxaawJg_OOCLfikkCGgfXPZmxR-x_5PH7_2e9M-3eA,1099
8
- ccfx-1.0.9.dist-info/METADATA,sha256=asJO3MTzoPwEhrQRcacRz1_GOIlD45X0tFu9tjb9-QE,11282
9
- ccfx-1.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- ccfx-1.0.9.dist-info/top_level.txt,sha256=_cSvSA1WX2K8TgoV3iBJUdUZZqMKJbOPLNnKLYSLHaw,5
11
- ccfx-1.0.9.dist-info/RECORD,,
7
+ ccfx-1.1.1.dist-info/licenses/LICENSE,sha256=EuxaawJg_OOCLfikkCGgfXPZmxR-x_5PH7_2e9M-3eA,1099
8
+ ccfx-1.1.1.dist-info/METADATA,sha256=xvkQuCHd7Kecmlj6DQjpYLHW9fUc93acFx2EV5ONO08,29271
9
+ ccfx-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ ccfx-1.1.1.dist-info/top_level.txt,sha256=_cSvSA1WX2K8TgoV3iBJUdUZZqMKJbOPLNnKLYSLHaw,5
11
+ ccfx-1.1.1.dist-info/RECORD,,
@@ -1,181 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ccfx
3
- Version: 1.0.9
4
- Summary: This package simplifies regular common actions for quick prototyping in a user friendly way
5
- Author-email: Celray James CHAWANDA <celray@chawanda.com>
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/celray/ccfx
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: OS Independent
10
- Requires-Python: >=3.10
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Requires-Dist: netCDF4
14
- Requires-Dist: yt_dlp
15
- Requires-Dist: gdal
16
- Requires-Dist: numpy
17
- Requires-Dist: shapely
18
- Requires-Dist: geopandas
19
- Requires-Dist: pandas
20
- Requires-Dist: xlsxwriter
21
- Requires-Dist: pyodbc
22
- Requires-Dist: sqlalchemy
23
- Requires-Dist: python-docx
24
- Requires-Dist: py7zr
25
- Requires-Dist: mutagen
26
- Requires-Dist: requests
27
- Requires-Dist: tqdm
28
- Requires-Dist: pillow
29
- Dynamic: license-file
30
-
31
- # ccfx
32
-
33
- `ccfx` is a comprehensive Python package designed to streamline file and data management, geospatial analysis, NetCDF file processing, database interactions, document generation, and multimedia handling for rapid prototyping and development workflows.
34
-
35
- ## Features
36
-
37
- 1. **File Management**:
38
- * List, delete, move, copy, and count files/directories.
39
- * Monitor file count over time.
40
- * Save, load, and manage Python variables via pickle serialization.
41
- * Compress directories to `.7z` archives.
42
- * Read/write text files with encoding support.
43
- * Download files from URLs with resume and multi-connection support.
44
-
45
- 2. **Geospatial Data Processing**:
46
- * Read, write, clip (by extent/feature), resample, reproject, merge, and rasterize raster data (GeoTIFF, NetCDF).
47
- * Read, write, and clip vector data (Shapefile, GeoPackage).
48
- * Create grids of polygons based on shapefile boundaries.
49
- * Convert coordinates between Coordinate Reference Systems (CRS).
50
- * Extract raster values at specific coordinates.
51
- * Convert point lists to GeoDataFrames.
52
- * Get vector layer bounds.
53
-
54
- 3. **NetCDF File Handling**:
55
- * List variables and dimensions.
56
- * Export NetCDF variables to GeoTIFF format (single or multiple bands).
57
- * Calculate sum and average maps from NetCDF data across multiple files.
58
- * Rename variables using CDO (if available).
59
-
60
- 4. **Database Connectivity**:
61
- * **MS SQL Server**: Connect, list databases/tables, read tables (including spatial data into GeoDataFrames), write DataFrames/GeoDataFrames to tables, drop tables.
62
- * **SQLite**: Connect, create/rename/drop tables, read tables (as dict, specific columns), insert data (rows, dicts, partial dicts), update values, dump tables to CSV.
63
-
64
- 5. **Document & Spreadsheet Handling**:
65
- * **Excel**: Create `.xlsx` files, add sheets, write data (including dates), set column widths, add scatter plot charts.
66
- * **Word**: Create `.docx` files, add headings, paragraphs (with alignment), list items, formatted text (bold/italic), images, page breaks, set margins.
67
-
68
- 6. **Multimedia & Web**:
69
- * Read and write MP3 metadata (ID3 tags), including album art.
70
- * Download videos/audio from YouTube using `yt-dlp`.
71
-
72
- 7. **Data Analysis & Utilities**:
73
- * Calculate timeseries statistics (NSE, KGE, PBIAS, LNSE, R2, RMSE, MAE, MSE, MAPE, alpha, beta) with resampling options.
74
- * Display dynamic progress bars.
75
- * Check system platform information.
76
- * Enable or disable warnings programmatically.
77
- * Set the working directory.
78
-
79
- ## Installation
80
-
81
- Install `ccfx` via pip:
82
- ```bash
83
- pip install ccfx
84
- ```
85
-
86
- ## Dependencies
87
-
88
- `ccfx` relies on the following libraries:
89
-
90
- * **gdal**: For geospatial raster and vector data manipulation.
91
- * **numpy**: For array processing and numerical operations.
92
- * **pandas**: For data manipulation and analysis.
93
- * **geopandas**: Extends pandas to handle geospatial vector data.
94
- * **shapely**: Provides geometric objects and operations.
95
- * **netCDF4**: For working with NetCDF files.
96
- * **xlsxwriter**: For creating and writing Excel `.xlsx` files.
97
- * **python-docx**: Enables creation and manipulation of Word `.docx` documents.
98
- * **pyodbc**: Enables connectivity to databases through ODBC (e.g., MS SQL Server).
99
- * **sqlalchemy**: Provides SQL toolkit and ORM features for database access (used with MS SQL).
100
- * **py7zr**: For creating `.7z` archives.
101
- * **mutagen**: For reading and writing MP3 metadata (ID3 tags).
102
- * **requests**: For downloading files via HTTP.
103
- * **tqdm**: For displaying progress bars.
104
- * **yt-dlp**: For downloading YouTube content.
105
- * **matplotlib** (Optional, often used with geospatial/data analysis): For plotting.
106
-
107
- These dependencies should be installed automatically when `ccfx` is installed via pip, but GDAL might require manual installation steps depending on your OS.
108
-
109
- ## API Reference (Selected Functions)
110
-
111
- ### File Management (`ccfx.py`)
112
-
113
- * **`listFiles(path: str, ext: str = None) -> list`**: Lists files in a directory, optionally filtering by extension.
114
- * **`deleteFile(filePath: str, v: bool = False) -> bool`**: Deletes a specified file.
115
- * **`deletePath(path: str, v: bool = False) -> bool`**: Deletes a directory and its contents.
116
- * **`createPath(pathName, v = False)`**: Creates a directory path if it doesn't exist.
117
- * **`copyFile(source: str, destination: str, v: bool = True)`**: Copies a single file.
118
- * **`copyDirectory(source: str, destination: str, recursive=True, v=True, filter=[])`**: Copies a directory's contents.
119
- * **`moveDirectoryFiles(srcDir: str, destDir: str, v: bool = False) -> bool`**: Moves files and subdirectories from source to destination.
120
- * **`pythonVariable(filename, option, variable=None)`**: Saves ('dump') or loads ('load') Python variables using pickle.
121
- * **`compressTo7z(input_dir: str, output_file: str)`**: Compresses a directory into a .7z file.
122
- * **`downloadFile(url, save_path, exists_action='resume', num_connections=5, v=False)`**: Downloads a file from a URL with advanced options.
123
- * **`listAllFiles(folder, extension="*")`**: Recursively lists all files in a folder and its subfolders.
124
-
125
- ### Geospatial (`ccfx.py`)
126
-
127
- * **`createGrid(shapefile_path: str, resolution: float, useDegree: bool = True) -> tuple`**: Generates a grid of polygons based on a shapefile extent.
128
- * **`clipRasterByExtent(inFile: str, outFile: str, bounds: tuple) -> str`**: Clips a raster using bounding box coordinates.
129
- * **`clipVectorByExtent(inFile: str, outFile: str, bounds: tuple) -> str`**: Clips a vector file using bounding box coordinates.
130
- * **`clipFeatures(inputFeaturePath:str, boundaryFeature:str, outputFeature:str, keepOnlyTypes = None, v = False) -> geopandas.GeoDataFrame`**: Clips input features by a boundary feature.
131
- * **`resampleRaster(inFile:str, outFile:str, resolution:float, dstSRS = None, resamplingMethod = 'bilinear', replaceOutput:bool = True, v:bool = True) -> str`**: Resamples a raster to a new resolution and optionally CRS.
132
- * **`reprojectRaster(inFile: str, outFile: str, dstProjection: str, resamplingMethod: str = 'mode') -> str`**: Reprojects a raster to a new CRS.
133
- * **`mergeRasterTiles(tileList:list, outFile:str) -> str`**: Merges multiple raster files into one.
134
- * **`rasterizeRaster(inFile: str, outFile: str, targetField: str, targetResolution: float) -> str`**: Rasterizes a vector layer based on an attribute field.
135
- * **`extractRasterValue(rasterPath: str, lat: float, lon: float, coordProj: str = 'EPSG:4326') -> float`**: Extracts the raster value at a specific point.
136
- * **`convertCoordinates(lon, lat, srcEPSG, dstCRS) -> tuple`**: Converts coordinates between CRSs.
137
- * **`tiffWriteArray(array: numpy.ndarray, outputFile: str, geoTransform: tuple, projection: str, noData:float = None, v:bool = False) -> gdal.Dataset`**: Writes a NumPy array to a GeoTIFF file.
138
- * **`pointsToGeodataframe(point_pairs_list, columns = ['latitude', 'longitude'], auth = "EPSG", code = '4326', out_shape = '', format = 'gpkg', v = False, get_geometry_only = False)`**: Converts a list of point coordinates to a GeoDataFrame.
139
-
140
- ### NetCDF (`ccfx.py`)
141
-
142
- * **`netcdfVariablesList(ncFile: str) -> list`**: Lists variables in a NetCDF file.
143
- * **`netcdfVariableDimensions(ncFile: str, variable: str) -> dict`**: Gets dimensions and their sizes for a NetCDF variable.
144
- * **`netcdfExportTif(ncFile: str, variable: str, outputFile: str = None, band: int = None, v:bool = True) -> gdal.Dataset`**: Exports a NetCDF variable (optionally a specific band) to GeoTIFF.
145
- * **`netcdfAverageMap(ncFiles:list, variable:str, band:int = 1) -> numpy.ndarray`**: Calculates the average map from a variable across multiple NetCDF files.
146
- * **`netcdfSumMaps(ncFiles:list, variable:str, band:int = 1) -> numpy.ndarray`**: Calculates the sum map from a variable across multiple NetCDF files.
147
- * **`renameNetCDFvariable(input_file: str, output_file: str, old_var_name: str, new_var_name: str, v = False)`**: Renames a variable in a NetCDF file using CDO.
148
-
149
- ### Database (`mssqlConnection.py`, `sqliteConnection.py`)
150
-
151
- * **`mssql_connection(server, username, password, driver, ...)`**: Class for MS SQL Server interactions.
152
- * `connect()`, `listDatabases()`, `listTables()`, `readTable()`, `connectDB()`, `dataframeToSql()`, `dropTable()`, `close()`
153
- * **`sqliteConnection(sqlite_database, connect=False)`**: Class for SQLite interactions.
154
- * `connect()`, `createTable()`, `renameTable()`, `deleteTable()`, `readTableAsDict()`, `insertDict()`, `insertRow()`, `updateValue()`, `dumpCSV()`, `commitChanges()`, `closeConnection()`
155
-
156
- ### Document/Spreadsheet (`word.py`, `excel.py`)
157
-
158
- * **`word_document(path)`**: Class for creating Word documents.
159
- * `addHeading()`, `addParagraph()`, `addListItem()`, `addText()`, `addImage()`, `addPageBreak()`, `setMargins()`, `save()`
160
- * **`excel(path)`**: Class for creating Excel spreadsheets.
161
- * `create()`, `addSheet()`, `write()`, `writeDate()`, `setColumnWidth()`, `addFigure()`, `writeColumn()`, `save()`, `open()`
162
-
163
- ### Multimedia & Web (`ccfx.py`)
164
-
165
- * **`getMp3Metadata(fn, imagePath=None)`**: Extracts ID3 metadata from an MP3 file.
166
- * **`setMp3Metadata(fn, metadata, imagePath=None)`**: Writes ID3 metadata (including album art) to an MP3 file.
167
- * **`downloadYoutubeVideo(url: str, dstDir: str, audioOnly: bool = False, dstFileName: Optional[str] = None ) -> str`**: Downloads video or audio from a YouTube URL.
168
-
169
- ### Data Analysis & Utilities (`ccfx.py`)
170
-
171
- * **`calculateTimeseriesStats(data:pandas.DataFrame, observed:str = None, simulated:str = None, resample:str = None ) -> dict`**: Calculates various statistics between observed and simulated timeseries. (Wrappers like `getNSE`, `getKGE`, etc., are also available).
172
- * **`progressBar(count, total, message="")`**: Displays a simple console progress bar.
173
- * **`showProgress(count: int, end: int, message: str, barLength: int = 100)`**: Displays a more detailed console progress bar.
174
- * **`ignoreWarnings(ignore:bool = True, v:bool = False)`**: Suppresses or enables Python warnings.
175
-
176
- ## Contributing
177
-
178
- Contributions are welcome! Please fork the repository, make your changes, and submit a pull request. Ensure code is well-documented and includes tests where applicable.
179
-
180
- ## License
181
- This project is licensed under the MIT License. See the LICENSE file for details.
File without changes