d3dtools 0.1.0__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.
- d3dtools/__init__.py +10 -0
- d3dtools/ncrain.py +267 -0
- d3dtools/shpbc2pli.py +83 -0
- d3dtools/shpblock2pol.py +78 -0
- d3dtools/shpdike2pliz.py +89 -0
- d3dtools-0.1.0.dist-info/LICENSE +21 -0
- d3dtools-0.1.0.dist-info/METADATA +151 -0
- d3dtools-0.1.0.dist-info/RECORD +11 -0
- d3dtools-0.1.0.dist-info/WHEEL +5 -0
- d3dtools-0.1.0.dist-info/entry_points.txt +5 -0
- d3dtools-0.1.0.dist-info/top_level.txt +1 -0
d3dtools/__init__.py
ADDED
d3dtools/ncrain.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate a NetCDF file from rainfall data and thiessen polygon shapefile.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import geopandas as gpd
|
|
8
|
+
import rasterio as rio
|
|
9
|
+
import shutil
|
|
10
|
+
from osgeo import gdal
|
|
11
|
+
from netCDF4 import Dataset
|
|
12
|
+
import pyproj
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def generate(
|
|
17
|
+
input_shp_folder='SHP',
|
|
18
|
+
input_tab_folder='TAB',
|
|
19
|
+
output_nc_folder='NC',
|
|
20
|
+
intermediate_ras_folder='RAS_RAIN',
|
|
21
|
+
intermediate_shp_folder='SHP_RAIN',
|
|
22
|
+
clean_intermediate=True,
|
|
23
|
+
raster_resolution=320):
|
|
24
|
+
"""
|
|
25
|
+
Generate a NetCDF file from rainfall data and thiessen polygon shapefile.
|
|
26
|
+
|
|
27
|
+
Parameters:
|
|
28
|
+
-----------
|
|
29
|
+
input_shp_folder : str
|
|
30
|
+
Path to the folder containing input shapefiles (default: 'SHP')
|
|
31
|
+
input_tab_folder : str
|
|
32
|
+
Path to the folder containing input tabular data (CSV files) (default: 'TAB')
|
|
33
|
+
output_nc_folder : str
|
|
34
|
+
Path to the folder where NetCDF output will be saved (default: 'NC')
|
|
35
|
+
intermediate_ras_folder : str
|
|
36
|
+
Path to the folder where intermediate raster files will be saved (default: 'RAS_RAIN')
|
|
37
|
+
intermediate_shp_folder : str
|
|
38
|
+
Path to the folder where intermediate shapefile files will be saved (default: 'SHP_RAIN')
|
|
39
|
+
clean_intermediate : bool
|
|
40
|
+
Whether to clean up intermediate files after processing (default: True)
|
|
41
|
+
raster_resolution : float
|
|
42
|
+
Resolution of the raster in meters (default: 320)
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
--------
|
|
46
|
+
str
|
|
47
|
+
Path to the generated NetCDF file
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Create output directories if they don't exist
|
|
51
|
+
if not os.path.exists(intermediate_ras_folder):
|
|
52
|
+
os.makedirs(intermediate_ras_folder)
|
|
53
|
+
|
|
54
|
+
if not os.path.exists(intermediate_shp_folder):
|
|
55
|
+
os.makedirs(intermediate_shp_folder)
|
|
56
|
+
|
|
57
|
+
if not os.path.exists(output_nc_folder):
|
|
58
|
+
os.makedirs(output_nc_folder)
|
|
59
|
+
|
|
60
|
+
# Read THIESSEN POLYGON shp file
|
|
61
|
+
thiessen = gpd.read_file(f'{input_shp_folder}/THIESSEN.shp')
|
|
62
|
+
|
|
63
|
+
# Add field rainfall to thiessen with datatype float
|
|
64
|
+
thiessen['rainfall'] = 0.0
|
|
65
|
+
|
|
66
|
+
# Read the rainfall data
|
|
67
|
+
# Search folder and get the first file name ending with '.csv', without the extension
|
|
68
|
+
for file in os.listdir(input_tab_folder):
|
|
69
|
+
if file.endswith('.csv'):
|
|
70
|
+
rainfall_ts = file[:-4]
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
rainfall = pd.read_csv(f'{input_tab_folder}/{rainfall_ts}.csv')
|
|
74
|
+
|
|
75
|
+
# Convert 'time' filed to datetime
|
|
76
|
+
rainfall['time'] = pd.to_datetime(rainfall['time'])
|
|
77
|
+
|
|
78
|
+
# List the stations
|
|
79
|
+
stations = rainfall.columns[1:].to_list()
|
|
80
|
+
|
|
81
|
+
# Assign rainfall at time steps to the thiessen polygons
|
|
82
|
+
# when the 'Station' field matches the station name
|
|
83
|
+
for i, timestep in enumerate(rainfall['time']):
|
|
84
|
+
for station in stations:
|
|
85
|
+
for j in range(len(thiessen)):
|
|
86
|
+
if thiessen.iloc[j, 1] == station:
|
|
87
|
+
# thiessen['rainfall'][j] = rainfall[station][i]
|
|
88
|
+
thiessen.loc[j, 'rainfall'] = rainfall[station][i]
|
|
89
|
+
# Save the rainfall data to the shapefile
|
|
90
|
+
thiessen.to_file(
|
|
91
|
+
f'{intermediate_shp_folder}/THIESSEN_{i}.shp')
|
|
92
|
+
# Open the shapefile
|
|
93
|
+
thiessen = gpd.read_file(
|
|
94
|
+
f'{intermediate_shp_folder}/THIESSEN_{i}.shp')
|
|
95
|
+
# Get the extent of the shapefile
|
|
96
|
+
xmin, ymin, xmax, ymax = thiessen.total_bounds
|
|
97
|
+
# Get the resolution of the raster
|
|
98
|
+
res = raster_resolution
|
|
99
|
+
# Create the raster
|
|
100
|
+
raster = f'{intermediate_ras_folder}/THIESSEN_{i}.tif'
|
|
101
|
+
# Create the raster, set nodata value to -9999
|
|
102
|
+
gdal.Rasterize(raster,
|
|
103
|
+
f'{intermediate_shp_folder}/THIESSEN_{i}.shp',
|
|
104
|
+
format='GTiff',
|
|
105
|
+
outputType=gdal.GDT_Float32,
|
|
106
|
+
xRes=res,
|
|
107
|
+
yRes=res,
|
|
108
|
+
attribute='rainfall',
|
|
109
|
+
outputSRS=thiessen.crs,
|
|
110
|
+
noData=-9999)
|
|
111
|
+
|
|
112
|
+
# Get timestamps, including hours, minutes, and seconds
|
|
113
|
+
timestamps = rainfall['time'].dt.strftime('%Y-%m-%d %H:%M:%S').to_list()
|
|
114
|
+
|
|
115
|
+
# Read the raster file
|
|
116
|
+
with rio.open(f'{intermediate_ras_folder}/THIESSEN_0.tif') as src:
|
|
117
|
+
# get the resolution of the raster
|
|
118
|
+
res = src.res[0]
|
|
119
|
+
# get the extent of the raster
|
|
120
|
+
xmin, ymin, xmax, ymax = src.bounds
|
|
121
|
+
# Find bounds for extend without bounding elements
|
|
122
|
+
xmin = xmin + res
|
|
123
|
+
ymin = ymin + res
|
|
124
|
+
xmax = xmax - res
|
|
125
|
+
ymax = ymax - res
|
|
126
|
+
|
|
127
|
+
# Get X coordinates of the raster
|
|
128
|
+
x = np.arange(xmin, xmax, res)
|
|
129
|
+
# Get Y coordinates of the raster
|
|
130
|
+
y = np.arange(ymin, ymax, res)
|
|
131
|
+
|
|
132
|
+
# Shift x, y by half of the resolution
|
|
133
|
+
x = x + res / 2
|
|
134
|
+
y = y + res / 2
|
|
135
|
+
|
|
136
|
+
lonList = []
|
|
137
|
+
latList = []
|
|
138
|
+
|
|
139
|
+
project = pyproj.Transformer.from_crs("epsg:3826", "epsg:4326")
|
|
140
|
+
|
|
141
|
+
# convert x to lat
|
|
142
|
+
for i in x:
|
|
143
|
+
lat, lon = project.transform(i, 0)
|
|
144
|
+
lonList.append(lon)
|
|
145
|
+
|
|
146
|
+
# convert y to lon
|
|
147
|
+
for i in y:
|
|
148
|
+
lat, lon = project.transform(0, i)
|
|
149
|
+
latList.append(lat)
|
|
150
|
+
|
|
151
|
+
# Create mesh grids
|
|
152
|
+
lonM, latM = np.meshgrid(lonList, latList)
|
|
153
|
+
X, Y = np.meshgrid(x, y)
|
|
154
|
+
|
|
155
|
+
# Create netcdf file
|
|
156
|
+
try:
|
|
157
|
+
# just to be safe, make sure dataset is not already open.
|
|
158
|
+
ncfile.close()
|
|
159
|
+
except:
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
nc_file_path = f'{output_nc_folder}/{rainfall_ts}.nc'
|
|
163
|
+
ncFile = Dataset(nc_file_path, 'w', format='NETCDF4')
|
|
164
|
+
|
|
165
|
+
# Create the dimensions
|
|
166
|
+
x_dim = ncFile.createDimension('x', len(lonList))
|
|
167
|
+
y_dim = ncFile.createDimension('y', len(latList))
|
|
168
|
+
lat_dim = ncFile.createDimension('lat', len(latList))
|
|
169
|
+
lon_dim = ncFile.createDimension('lon', len(lonList))
|
|
170
|
+
time_dim = ncFile.createDimension('time', None)
|
|
171
|
+
|
|
172
|
+
# Creating variables
|
|
173
|
+
x2 = ncFile.createVariable('x', np.float64, ('x', ), fill_value=9.96921E36)
|
|
174
|
+
x2.standard_name = "projection_x_coordinate"
|
|
175
|
+
x2.long_name = "x coordinate according to TWD 1997"
|
|
176
|
+
x2.units = "m"
|
|
177
|
+
x2.axis = "X"
|
|
178
|
+
x2[:] = x
|
|
179
|
+
|
|
180
|
+
y2 = ncFile.createVariable('y', np.float64, ('y', ), fill_value=9.96921E36)
|
|
181
|
+
y2.standard_name = "projection_y_coordinate"
|
|
182
|
+
y2.long_name = "y coordinate according to TWD 1997"
|
|
183
|
+
y2.units = "m"
|
|
184
|
+
y2.axis = "Y"
|
|
185
|
+
y2[:] = y
|
|
186
|
+
|
|
187
|
+
lat2 = ncFile.createVariable('lat',
|
|
188
|
+
np.float64, ('y', 'x'),
|
|
189
|
+
fill_value=9.96921E36)
|
|
190
|
+
lat2.standard_name = "latitude"
|
|
191
|
+
lat2.long_name = "latitude"
|
|
192
|
+
lat2.units = "degrees_north"
|
|
193
|
+
lat2[:] = latM
|
|
194
|
+
|
|
195
|
+
lon2 = ncFile.createVariable('lon',
|
|
196
|
+
np.float64, ('y', 'x'),
|
|
197
|
+
fill_value=9.96921E36)
|
|
198
|
+
lon2.standard_name = "longitude"
|
|
199
|
+
lon2.long_name = "longitude"
|
|
200
|
+
lon2.units = "degrees_east"
|
|
201
|
+
lon2[:] = lonM
|
|
202
|
+
|
|
203
|
+
time = ncFile.createVariable('time', np.float64, ('time', ))
|
|
204
|
+
time.standard_name = "time"
|
|
205
|
+
time.long_name = "time"
|
|
206
|
+
time.units = "minutes since 1970-01-01 08:00:00.0 +0800"
|
|
207
|
+
time.axis = "T"
|
|
208
|
+
|
|
209
|
+
# Convert timestamp to minutes since 1970-01-01 08:00:00.0 +0800
|
|
210
|
+
timestamp = [datetime.strptime(i, '%Y-%m-%d %H:%M:%S') for i in timestamps]
|
|
211
|
+
time[:] = [(i - datetime(1970, 1, 1, 8, 0, 0)).total_seconds() / 60
|
|
212
|
+
for i in timestamp]
|
|
213
|
+
|
|
214
|
+
# CRS
|
|
215
|
+
crs = ncFile.createVariable('crs', np.int32)
|
|
216
|
+
crs.long_name = "coordinate reference system"
|
|
217
|
+
crs.crs_wkt = "PROJCS[\"TWD97 / TM2 zone 121\", \r\n GEOGCS[\"TWD97\", \r\n DATUM[\"Taiwan Datum 1997\", \r\n SPHEROID[\"GRS 1980\", 6378137.0, 298.257222101, AUTHORITY[\"EPSG\",\"7019\"]], \r\n TOWGS84[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], \r\n AUTHORITY[\"EPSG\",\"1026\"]], \r\n PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]], \r\n UNIT[\"degree\", 0.017453292519943295], \r\n AXIS[\"Geodetic longitude\", EAST], \r\n AXIS[\"Geodetic latitude\", NORTH], \r\n AUTHORITY[\"EPSG\",\"3824\"]], \r\n PROJECTION[\"Transverse_Mercator\"], \r\n PARAMETER[\"central_meridian\", 121.00000000000001], \r\n PARAMETER[\"latitude_of_origin\", 0.0], \r\n PARAMETER[\"scale_factor\", 0.9999], \r\n PARAMETER[\"false_easting\", 250000.0], \r\n PARAMETER[\"false_northing\", 0.0], \r\n UNIT[\"m\", 1.0], \r\n AXIS[\"Easting\", EAST], \r\n AXIS[\"Northing\", NORTH], \r\n AUTHORITY[\"EPSG\",\"3826\"]]"
|
|
218
|
+
crs.epsg_code = "EPSG:3826"
|
|
219
|
+
# set crs value to 0
|
|
220
|
+
crs[:] = 0
|
|
221
|
+
|
|
222
|
+
# Create the grid mapping variable
|
|
223
|
+
rainfall = ncFile.createVariable('rainfall',
|
|
224
|
+
np.float32, ('time', 'y', 'x'),
|
|
225
|
+
fill_value=-999.0)
|
|
226
|
+
rainfall.long_name = "rainfall"
|
|
227
|
+
rainfall.units = "mm"
|
|
228
|
+
rainfall.coordinates = "lat lon"
|
|
229
|
+
rainfall.grid_mapping = "crs"
|
|
230
|
+
|
|
231
|
+
# Write rainfall data to the netcdf file
|
|
232
|
+
data_arr = np.zeros((len(timestamp), len(latList), len(lonList)))
|
|
233
|
+
|
|
234
|
+
for i in range(len(timestamp)):
|
|
235
|
+
with rio.open(f'{intermediate_ras_folder}/THIESSEN_{i}.tif') as src:
|
|
236
|
+
data_arr_tmp = src.read(1)
|
|
237
|
+
# Get raster data without bounding elements
|
|
238
|
+
data_arr_tmp = data_arr_tmp[1:-1, 1:-1]
|
|
239
|
+
# flip in y direction
|
|
240
|
+
data_arr[i, :, :] = np.flipud(data_arr_tmp)
|
|
241
|
+
|
|
242
|
+
rainfall[:] = data_arr
|
|
243
|
+
|
|
244
|
+
# Close the netcdf file
|
|
245
|
+
ncFile.close()
|
|
246
|
+
|
|
247
|
+
# Remove intermediate files if requested
|
|
248
|
+
if clean_intermediate:
|
|
249
|
+
try:
|
|
250
|
+
shutil.rmtree(intermediate_ras_folder)
|
|
251
|
+
shutil.rmtree(intermediate_shp_folder)
|
|
252
|
+
except Exception as e:
|
|
253
|
+
print(f"Warning: Could not clean up intermediate files: {e}")
|
|
254
|
+
|
|
255
|
+
return nc_file_path
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def main():
|
|
259
|
+
"""
|
|
260
|
+
Command line entry point
|
|
261
|
+
"""
|
|
262
|
+
output_path = generate()
|
|
263
|
+
print(f"NetCDF file generated at: {output_path}")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if __name__ == "__main__":
|
|
267
|
+
main()
|
d3dtools/shpbc2pli.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Convert boundary line shapefile to *.pli file
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import glob
|
|
6
|
+
import geopandas as gpd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def convert(input_folder='SHP_BC', output_folder='PLI_BC'):
|
|
10
|
+
"""
|
|
11
|
+
Convert boundary line shapefile to *.pli file
|
|
12
|
+
Attribute table must contain 'Id' or 'id' field for boundary name
|
|
13
|
+
|
|
14
|
+
Parameters:
|
|
15
|
+
-----------
|
|
16
|
+
input_folder : str
|
|
17
|
+
Path to the folder containing shapefiles with MultiLineString geometry (default: 'SHP_BC')
|
|
18
|
+
output_folder : str
|
|
19
|
+
Path to the output folder for PLI files (default: 'PLI_BC')
|
|
20
|
+
"""
|
|
21
|
+
# Specify file source
|
|
22
|
+
fileList = glob.glob(f'{input_folder}/*.shp')
|
|
23
|
+
print(f"Found {len(fileList)} shapefiles in {input_folder}")
|
|
24
|
+
|
|
25
|
+
gdfs = []
|
|
26
|
+
for i, item in enumerate(fileList):
|
|
27
|
+
gdf = gpd.read_file(item)
|
|
28
|
+
gdfs.append(gdf)
|
|
29
|
+
|
|
30
|
+
# Read wkt
|
|
31
|
+
ref_wkts = []
|
|
32
|
+
for i, gdf in enumerate(gdfs):
|
|
33
|
+
ref_wkt = [g.wkt for g in gdf['geometry'].values]
|
|
34
|
+
ref_wkts.append(ref_wkt)
|
|
35
|
+
|
|
36
|
+
print(f"Total features: {len(ref_wkts)}")
|
|
37
|
+
|
|
38
|
+
# Get boundary names
|
|
39
|
+
bcNames = []
|
|
40
|
+
for i, gdf in enumerate(gdfs):
|
|
41
|
+
try:
|
|
42
|
+
bcName = [name for name in gdf['Id'].values]
|
|
43
|
+
except:
|
|
44
|
+
bcName = [name for name in gdf['id'].values]
|
|
45
|
+
bcNames.append(bcName)
|
|
46
|
+
|
|
47
|
+
# Create output folder if not exist
|
|
48
|
+
if not os.path.exists(output_folder):
|
|
49
|
+
os.makedirs(output_folder)
|
|
50
|
+
print(f"Created output folder: {output_folder}")
|
|
51
|
+
|
|
52
|
+
# For loop gdfs, create a .pli file with id as its name
|
|
53
|
+
file_count = 0
|
|
54
|
+
for i, ref_wkt in enumerate(ref_wkts):
|
|
55
|
+
for j, item in enumerate(ref_wkt):
|
|
56
|
+
with open(f'{output_folder}/{bcNames[i][j]}.pli', 'w',
|
|
57
|
+
encoding='utf-8') as f:
|
|
58
|
+
f.write('{}\n'.format(bcNames[i][j]))
|
|
59
|
+
points = [
|
|
60
|
+
point.split() for point in item.replace(
|
|
61
|
+
"LINESTRING (", "").replace(")", "").split(',')
|
|
62
|
+
]
|
|
63
|
+
f.write('{} {}\n'.format(len(points), 2))
|
|
64
|
+
for k, ktem in enumerate(points):
|
|
65
|
+
f.write(
|
|
66
|
+
f'{float(ktem[0]):.6f} {float(ktem[1]):.6f} {bcNames[i][j]}_{k+1:0>4}\n'
|
|
67
|
+
)
|
|
68
|
+
f.write('\n')
|
|
69
|
+
file_count += 1
|
|
70
|
+
|
|
71
|
+
print(f'Done! Generated {file_count} PLI files in {output_folder}')
|
|
72
|
+
return file_count
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def main():
|
|
76
|
+
"""
|
|
77
|
+
Command line entry point
|
|
78
|
+
"""
|
|
79
|
+
convert()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
d3dtools/shpblock2pol.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Convert shapefile blocks to *.pol files
|
|
3
|
+
"""
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import geopandas as gpd
|
|
7
|
+
from glob import glob
|
|
8
|
+
import os
|
|
9
|
+
import shapely.wkt
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
from shapely.geometry import Point, LineString
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def convert(input_folder='SHP_BLOCK', output_folder='POL_BLOCK'):
|
|
15
|
+
"""
|
|
16
|
+
Convert shapefile blocks to *.pol files
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
-----------
|
|
20
|
+
input_folder : str
|
|
21
|
+
Path to the folder containing shapefiles (default: 'SHP_BLOCK')
|
|
22
|
+
output_folder : str
|
|
23
|
+
Path to the folder where .pol files will be saved (default: 'POL_BLOCK')
|
|
24
|
+
"""
|
|
25
|
+
# Read shapefile data
|
|
26
|
+
blockList = glob(f'{input_folder}/*.shp')
|
|
27
|
+
blockNameList = [os.path.basename(block).split('.')[0]
|
|
28
|
+
for block in blockList]
|
|
29
|
+
|
|
30
|
+
# Extract wkt from blocks and convert to pol
|
|
31
|
+
for i, block in enumerate(blockList):
|
|
32
|
+
# Read block
|
|
33
|
+
blockGdf = gpd.read_file(block)
|
|
34
|
+
# Remove data without geometry
|
|
35
|
+
blockGdf = blockGdf[blockGdf['geometry'].notnull()]
|
|
36
|
+
# Assign id (serial number) to each feature in block
|
|
37
|
+
blockGdf['id'] = range(1, len(blockGdf) + 1)
|
|
38
|
+
# Get blockName
|
|
39
|
+
blockName = blockGdf['id'].values
|
|
40
|
+
# Get wkt
|
|
41
|
+
ref_wkt = [g.wkt for g in blockGdf['geometry'].values]
|
|
42
|
+
print('Processing block: {}'.format(blockNameList[i]))
|
|
43
|
+
|
|
44
|
+
# If output folder does not exist, create it
|
|
45
|
+
if not os.path.exists(output_folder):
|
|
46
|
+
os.makedirs(output_folder)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
with open(f'{output_folder}/{blockNameList[i]}.pol', 'w') as f:
|
|
50
|
+
for j, wkt in enumerate(ref_wkt):
|
|
51
|
+
points = [
|
|
52
|
+
point.split() for point in wkt.replace("POLYGON ((", "").replace(
|
|
53
|
+
"))", "").split(',')
|
|
54
|
+
]
|
|
55
|
+
# Write to *.pol
|
|
56
|
+
# Write blockName
|
|
57
|
+
f.write('{}\n'.format(blockName[j]))
|
|
58
|
+
f.write('{} {}\n'.format(len(points), 2))
|
|
59
|
+
|
|
60
|
+
for point in points:
|
|
61
|
+
# convert string to float and format
|
|
62
|
+
f.write(
|
|
63
|
+
f'{float(point[0]):.3f} {float(point[1]):.3f}\n')
|
|
64
|
+
except:
|
|
65
|
+
print('Error in block: {}'.format(blockNameList[i]))
|
|
66
|
+
|
|
67
|
+
return len(blockList)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main():
|
|
71
|
+
"""
|
|
72
|
+
Command line entry point
|
|
73
|
+
"""
|
|
74
|
+
convert()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
d3dtools/shpdike2pliz.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Convert bankline shapefile to PLIZ file
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import glob
|
|
6
|
+
import geopandas as gpd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def convert(input_folder='SHP_DIKE',
|
|
10
|
+
output_folder='PLIZ_DIKE',
|
|
11
|
+
output_filename='Dike'):
|
|
12
|
+
"""
|
|
13
|
+
Convert shapefile to DIKE PLIZ file
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
-----------
|
|
17
|
+
input_folder : str
|
|
18
|
+
Path to the folder containing dike shapefiles with MultiLineStringZ geometry (default: 'SHP_DIKE')
|
|
19
|
+
output_folder : str
|
|
20
|
+
Output folder path (default: 'PLIZ_DIKE')
|
|
21
|
+
output_filename : str
|
|
22
|
+
Name of the output file without extension (default: 'Dike')
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
--------
|
|
26
|
+
str
|
|
27
|
+
Path to the created PLIZ file
|
|
28
|
+
"""
|
|
29
|
+
# Specify file source
|
|
30
|
+
fileList = glob.glob(f'{input_folder}/*.shp')
|
|
31
|
+
print(f"Found {len(fileList)} files: {fileList}")
|
|
32
|
+
|
|
33
|
+
# Read files
|
|
34
|
+
gdfs = []
|
|
35
|
+
for i, item in enumerate(fileList):
|
|
36
|
+
gdf = gpd.read_file(item)
|
|
37
|
+
gdfs.append(gdf)
|
|
38
|
+
|
|
39
|
+
# Read wkt
|
|
40
|
+
ref_wkts = []
|
|
41
|
+
for i, gdf in enumerate(gdfs):
|
|
42
|
+
ref_wkt = [g.wkt for g in gdf['geometry'].values]
|
|
43
|
+
ref_wkts.append(ref_wkt)
|
|
44
|
+
|
|
45
|
+
# Get dike name
|
|
46
|
+
dikeNames = []
|
|
47
|
+
for i, gdf in enumerate(gdfs):
|
|
48
|
+
try:
|
|
49
|
+
dikeName = [name for name in gdf['Id'].values]
|
|
50
|
+
except:
|
|
51
|
+
dikeName = [name for name in gdf['id'].values]
|
|
52
|
+
dikeNames.append(dikeName)
|
|
53
|
+
|
|
54
|
+
# Create output folder if not exist
|
|
55
|
+
if not os.path.exists(output_folder):
|
|
56
|
+
os.makedirs(output_folder)
|
|
57
|
+
|
|
58
|
+
# Write to .pliz
|
|
59
|
+
output_path = os.path.join(output_folder, f"{output_filename}.pliz")
|
|
60
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
61
|
+
for k in range(len(gdfs)):
|
|
62
|
+
for i, item in enumerate(ref_wkts[k]):
|
|
63
|
+
f.write('{}\n'.format(dikeNames[k][i]))
|
|
64
|
+
# Remove heading "LINESTRING Z (" and trailing ")" characters using replace
|
|
65
|
+
points = [
|
|
66
|
+
point.split() for point in item.replace(
|
|
67
|
+
"LINESTRING Z (", "").replace(")", "").split(',')
|
|
68
|
+
]
|
|
69
|
+
f.write('{} {}\n'.format(len(points), 5))
|
|
70
|
+
for j, jtem in enumerate(points):
|
|
71
|
+
f.write('{:.6f} {:.6f} {} {} {}\n'.format(float(jtem[0]),
|
|
72
|
+
float(jtem[1]),
|
|
73
|
+
float(jtem[2]),
|
|
74
|
+
float(jtem[2]),
|
|
75
|
+
float(jtem[2])))
|
|
76
|
+
f.write('\n')
|
|
77
|
+
print(f'Done! PLIZ file created at: {output_path}')
|
|
78
|
+
return output_path
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main():
|
|
82
|
+
"""
|
|
83
|
+
Command line entry point
|
|
84
|
+
"""
|
|
85
|
+
convert()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Chih-Hung Hsu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: d3dtools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A collection of tools for working with shapefiles and converting them for Delft3D modeling
|
|
5
|
+
Home-page: https://github.com/AaronOET/d3dtools
|
|
6
|
+
Author: aaronchh
|
|
7
|
+
Author-email: aaronhsu219@gmail.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy>=1.20.0
|
|
15
|
+
Requires-Dist: pandas>=1.3.0
|
|
16
|
+
Requires-Dist: geopandas>=0.10.0
|
|
17
|
+
Requires-Dist: rasterio>=1.2.0
|
|
18
|
+
Requires-Dist: netCDF4>=1.5.0
|
|
19
|
+
Requires-Dist: pyproj>=3.0.0
|
|
20
|
+
Requires-Dist: shapely>=1.8.0
|
|
21
|
+
Requires-Dist: matplotlib>=3.4.0
|
|
22
|
+
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
24
|
+
Dynamic: classifier
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: description-content-type
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: requires-dist
|
|
29
|
+
Dynamic: requires-python
|
|
30
|
+
Dynamic: summary
|
|
31
|
+
|
|
32
|
+
# D3DTOOLS
|
|
33
|
+
|
|
34
|
+
A collection of Python tools for working with shapefiles and converting them for Delft3D modeling.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install d3dtools
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
This package provides several utilities for converting shapefiles to various formats used in Delft3D modeling:
|
|
45
|
+
|
|
46
|
+
- **ncrain**: Generate a NetCDF file from rainfall data and thiessen polygon shapefiles
|
|
47
|
+
- **shpbc2pli**: Convert boundary line shapefiles to PLI files
|
|
48
|
+
- **shpblock2pol**: Convert shapefile blocks to POL files
|
|
49
|
+
- **shpdike2pliz**: Convert bankline shapefiles to PLIZ files
|
|
50
|
+
|
|
51
|
+
## Usage Examples
|
|
52
|
+
|
|
53
|
+
### Generate NetCDF from rainfall data
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from d3dtools import ncrain
|
|
57
|
+
|
|
58
|
+
# Default usage
|
|
59
|
+
output_path = ncrain.generate()
|
|
60
|
+
print(f"NetCDF file generated at: {output_path}")
|
|
61
|
+
|
|
62
|
+
# With custom parameters
|
|
63
|
+
output_path = ncrain.generate(
|
|
64
|
+
input_shp_folder='custom/SHP',
|
|
65
|
+
input_tab_folder='custom/TAB',
|
|
66
|
+
output_nc_folder='custom/NC',
|
|
67
|
+
intermediate_ras_folder='custom/RAS_RAIN',
|
|
68
|
+
intermediate_shp_folder='custom/SHP_RAIN',
|
|
69
|
+
clean_intermediate=True,
|
|
70
|
+
raster_resolution=360
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Convert boundary shapefiles to PLI
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from d3dtools import shpbc2pli
|
|
78
|
+
|
|
79
|
+
# Default usage
|
|
80
|
+
shpbc2pli.convert()
|
|
81
|
+
|
|
82
|
+
# With custom parameters
|
|
83
|
+
shpbc2pli.convert(
|
|
84
|
+
input_folder='custom/SHP_BC',
|
|
85
|
+
output_folder='custom/PLI_BC'
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Convert block shapefiles to POL
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from d3dtools import shpblock2pol
|
|
93
|
+
|
|
94
|
+
# Default usage
|
|
95
|
+
shpblock2pol.convert()
|
|
96
|
+
|
|
97
|
+
# With custom parameters
|
|
98
|
+
shpblock2pol.convert(
|
|
99
|
+
input_folder='custom/SHP_BLOCK',
|
|
100
|
+
output_folder='custom/POL_BLOCK'
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Convert dike shapefiles to PLIZ
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from d3dtools import shpdike2pliz
|
|
108
|
+
|
|
109
|
+
# Default usage
|
|
110
|
+
shpdike2pliz.convert()
|
|
111
|
+
|
|
112
|
+
# With custom parameters
|
|
113
|
+
shpdike2pliz.convert(
|
|
114
|
+
input_folder='custom/SHP_DIKE',
|
|
115
|
+
output_folder='custom/PLIZ_DIKE',
|
|
116
|
+
output_filename='CustomDike'
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Command-line Usage
|
|
121
|
+
|
|
122
|
+
The package also provides command-line utilities:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Generate NetCDF from rainfall data
|
|
126
|
+
ncrain
|
|
127
|
+
|
|
128
|
+
# Convert boundary shapefiles to PLI
|
|
129
|
+
shpbc2pli
|
|
130
|
+
|
|
131
|
+
# Convert block shapefiles to POL
|
|
132
|
+
shpblock2pol
|
|
133
|
+
|
|
134
|
+
# Convert dike shapefiles to PLIZ
|
|
135
|
+
shpdike2pliz
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Requirements
|
|
139
|
+
|
|
140
|
+
- numpy>=1.20.0
|
|
141
|
+
- pandas>=1.3.0
|
|
142
|
+
- geopandas>=0.10.0
|
|
143
|
+
- rasterio>=1.2.0
|
|
144
|
+
- netCDF4>=1.5.0
|
|
145
|
+
- pyproj>=3.0.0
|
|
146
|
+
- shapely>=1.8.0
|
|
147
|
+
- matplotlib>=3.4.0
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
d3dtools/__init__.py,sha256=FHIt7wSDhSLqCW2XyjAU6ovedC-3jKGjrMNzbsmT134,242
|
|
2
|
+
d3dtools/ncrain.py,sha256=NASYyddLn9T2YrrKvRZSBWLvgZ1LWK1TBGTUgtiexyE,10251
|
|
3
|
+
d3dtools/shpbc2pli.py,sha256=xMzv3fAGEsx16UmJvuf4-zW8Iz1ovci8jUkFqD87NSM,2546
|
|
4
|
+
d3dtools/shpblock2pol.py,sha256=qMVRdMJSX2F32mkBJlXAEbhn1ENDRzimt-JFfNJWHXk,2530
|
|
5
|
+
d3dtools/shpdike2pliz.py,sha256=d7bQncHdqOhXZ0mOQ2zlgyP1HDn9v7apzgRJFV4gVck,2878
|
|
6
|
+
d3dtools-0.1.0.dist-info/LICENSE,sha256=Y4E4BtkoFh6GfTSIMn7HvnEJfg5ITZldXCIjESEOaTE,1089
|
|
7
|
+
d3dtools-0.1.0.dist-info/METADATA,sha256=-TGMaeU7qh9v_wIv_DZl1rITQr_RdOj6qY7R71LTZK4,3370
|
|
8
|
+
d3dtools-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
9
|
+
d3dtools-0.1.0.dist-info/entry_points.txt,sha256=XmpphWSk1AUh7ISlOACKT_u94ZLeVu3eeK1OJZqdsEE,181
|
|
10
|
+
d3dtools-0.1.0.dist-info/top_level.txt,sha256=1FjGJxUu_zw__xOPcdP599x1LtGJDY22zLTjfuzSc7c,9
|
|
11
|
+
d3dtools-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
d3dtools
|