radarx 0.1.dev2__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.
radarx/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Top-level package for radarx."""
2
+
3
+ __author__ = """Hamid Ali Syed"""
4
+ __email__ = 'hamidsyed37@gmail.com'
5
+ __version__ = '0.1.0'
radarx/io/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env python
2
+ # Copyright (c) 2024, Hamid Ali Syed.
3
+ # Distributed under the MIT License. See LICENSE for more info.
4
+
5
+ """
6
+ Xarray Backends
7
+ ===============
8
+
9
+ .. toctree::
10
+ :maxdepth: 4
11
+
12
+ .. automodule:: radarx.io.imd
13
+ """
14
+
15
+ from imd import * # noqa
radarx/io/imd.py ADDED
@@ -0,0 +1,645 @@
1
+ """
2
+ IMD Data Reader
3
+ ---------------
4
+
5
+ This module provides functionality to read and process single radar files from
6
+ the Indian Meteorological Department (IMD), returning a quasi-CF-Radial xarray
7
+ Dataset.
8
+
9
+ Author: Hamid Ali Syed
10
+ Email: hamidsyed37 [at] gmail [dot] com
11
+ """
12
+
13
+ import itertools
14
+
15
+ import numpy as np
16
+ import xarray as xr
17
+ from datatree import DataTree
18
+
19
+ __all__ = [
20
+ "read_sweep" "read_volume",
21
+ ]
22
+
23
+
24
+ def read_sweep(file):
25
+ """
26
+ Reads a single radar file from IMD and processes it into a quasi-CF-Radial dataset.
27
+
28
+ Parameters
29
+ ----------
30
+ file : str or Path
31
+ Path to the radar file.
32
+
33
+ Returns
34
+ -------
35
+ ds : xarray.Dataset
36
+ Quasi-CF-Radial dataset with renamed coordinates, variables, and calculated fields.
37
+ """
38
+ ds = xr.open_dataset(file, engine="netcdf4")
39
+
40
+ # Rename dimensions and variables for consistency with CF-Radial format
41
+ ds = ds.rename_dims({"radial": "azimuth", "bin": "range"})
42
+ ds = ds.rename_vars(
43
+ {
44
+ "radialAzim": "azimuth",
45
+ "radialElev": "elevation",
46
+ "elevationAngle": "fixed_angle",
47
+ "radialTime": "time",
48
+ "nyquist": "nyquist_velocity",
49
+ "unambigRange": "unambiguous_range",
50
+ }
51
+ )
52
+ ds = ds.set_coords(["azimuth", "elevation"])
53
+
54
+ # Rename site-related coordinates
55
+ site_coords = {"siteLat": "latitude", "siteLon": "longitude", "siteAlt": "altitude"}
56
+ ds = ds.rename_vars(site_coords)
57
+
58
+ # Add ray gate spacing variable
59
+ ds["ray_gate_spacing"] = xr.DataArray(
60
+ data=np.repeat(ds.gateSize.values, ds.azimuth.size),
61
+ dims=("azimuth",),
62
+ attrs={"long_name": "gate_spacing_for_ray", "units": "meters"},
63
+ )
64
+
65
+ # Rename moments for radar variables
66
+ moments = {
67
+ "T": "DBT", # Total power
68
+ "Z": "DBZ", # Reflectivity
69
+ "V": "VEL", # Velocity
70
+ "W": "WIDTH", # Spectrum width
71
+ }
72
+ # Goes First in order
73
+ ds = ds.rename_vars(moments)
74
+
75
+ # Compute range and assign to dataset, Goes 2nd
76
+ ds = ds.pipe(_compute_range)
77
+
78
+ # Compute angle resolution, Goes 3rd
79
+ ds = ds.pipe(_angle_resolution)
80
+
81
+ # Determine scan type and mode, Goes 4th
82
+ ds = ds.pipe(_scantype)
83
+
84
+ # Time coverage handling, Goes 5th
85
+ ds = ds.pipe(_time_coverage)
86
+
87
+ # Handle sweep numbers, Goes 6th
88
+ ds = ds.pipe(_sweep_number)
89
+
90
+ # Assign start_end_ray_index, Goes 6th
91
+ ds = ds.pipe(_assign_sweep_metadata)
92
+
93
+ ds = ds.pipe(_assign_metadata)
94
+
95
+ ds = ds.swap_dims({"azimuth": "time"})
96
+
97
+ return ds
98
+
99
+
100
+ def read_volume(files):
101
+ """
102
+ Reads multiple files and creates a volume scan data.
103
+
104
+ Parameters
105
+ ----------
106
+ files : list of files or list of lists
107
+
108
+ Returns
109
+ -------
110
+ DataTree of the volumes
111
+ """
112
+ # Check if there are nested lists and merge them if needed
113
+ if any(isinstance(i, list) for i in files):
114
+ files = _merge_file_lists(files)
115
+
116
+ # Determine volume groups from files
117
+ grouped_dataset_list = _determine_volumes(files)
118
+
119
+ # Create a list to store the volumes
120
+ volume_datasets = []
121
+
122
+ # Iterate over each group of datasets (which represent a volume)
123
+ for datasets in grouped_dataset_list:
124
+ vol = create_volume(datasets) # Custom function to create the volume
125
+ if isinstance(vol, xr.Dataset):
126
+ volume_datasets.append(vol) # Only add if it's an xarray.Dataset
127
+ del vol # Cleanup after each volume is processed
128
+
129
+ # Construct the DataTree from the volume datasets (ensure that we pass datasets, not a list)
130
+ volumes = DataTree.from_dict(
131
+ {f"volume_{i}": volume for i, volume in enumerate(volume_datasets)}
132
+ )
133
+
134
+ return volumes
135
+
136
+
137
+ def _merge_file_lists(file_lists):
138
+ """
139
+ Merges internal lists of files into a single list.
140
+
141
+ Parameters
142
+ ----------
143
+ file_lists : list of lists
144
+ A list containing multiple internal lists of file paths.
145
+
146
+ Returns
147
+ -------
148
+ merged_list : list
149
+ A single list containing all the file paths from the internal lists.
150
+ """
151
+ # Use itertools.chain to flatten the list of lists
152
+ merged_list = list(itertools.chain(*file_lists))
153
+
154
+ return merged_list
155
+
156
+
157
+ def _compute_range(ds):
158
+ """
159
+ Computes the range to each gate and adds it to the dataset.
160
+
161
+ Parameters
162
+ ----------
163
+ ds : xarray.Dataset
164
+ Input dataset.
165
+
166
+ Returns
167
+ -------
168
+ ds : xarray.Dataset
169
+ Dataset with computed range coordinates.
170
+ """
171
+ # Using ds.sizes instead of ds.dims to avoid future warning
172
+ gate_count = ds.sizes["range"]
173
+
174
+ # Ensure proper conversion to avoid deprecation warning
175
+ ranges = np.arange(
176
+ ds["firstGateRange"].values,
177
+ gate_count * ds["gateSize"].values + ds["firstGateRange"].values,
178
+ ds["gateSize"].astype(int).values, # Ensure proper integer type conversion
179
+ )
180
+
181
+ ds.coords["range"] = xr.DataArray(
182
+ ranges,
183
+ dims=("range",),
184
+ attrs={
185
+ "standard_name": "range_to_center_of_measurement_volume",
186
+ "long_name": "Range from instrument to center of gate",
187
+ "units": "meters",
188
+ "spacing_is_constant": "true",
189
+ "meters_to_center_of_first_gate": ds["firstGateRange"].values,
190
+ "meters_between_gates": ds["gateSize"].values,
191
+ },
192
+ )
193
+ return ds
194
+
195
+
196
+ def _angle_resolution(ds):
197
+ """
198
+ Adds angular resolution for each ray to the dataset.
199
+
200
+ Parameters
201
+ ----------
202
+ ds : xarray.Dataset
203
+ Input dataset.
204
+
205
+ Returns
206
+ -------
207
+ ds : xarray.Dataset
208
+ Dataset with angular resolution variable.
209
+ """
210
+ ds = ds.drop_dims("sweep", errors="ignore")
211
+ ds["ray_angle_res"] = xr.DataArray(
212
+ data=[ds["angleResolution"]],
213
+ dims=("sweep",),
214
+ attrs={"long_name": "angular_resolution_between_rays", "units": "degrees"},
215
+ )
216
+ ds = ds.drop_vars("angleResolution", errors="ignore")
217
+ return ds
218
+
219
+
220
+ def _scantype(ds):
221
+ """
222
+ Determines the scan type based on the scanType variable.
223
+
224
+ Parameters
225
+ ----------
226
+ ds : xarray.Dataset
227
+ Input dataset.
228
+
229
+ Returns
230
+ -------
231
+ ds : xarray.Dataset
232
+ Dataset with scan type and sweep mode variables.
233
+ """
234
+ scan_type_lookup = {
235
+ 4: (b"azimuth_surveillance", b"ppi"),
236
+ 7: (b"elevation_surveillance", b"rhi"),
237
+ 1: (b"sector", b"ppi_sector"),
238
+ 2: (b"manual_rhi", b"rhi_sector"),
239
+ 0: (b"unknown", b"unknown"),
240
+ }
241
+
242
+ # Extract the scalar value from the array
243
+ scan_type = int(ds["scanType"].astype(int).values)
244
+
245
+ # Get sweep_mode and scan_type_name from lookup dictionary
246
+ sweep_mode, scan_type_name = scan_type_lookup.get(
247
+ scan_type, (b"unknown", b"unknown")
248
+ )
249
+
250
+ ds["sweep_mode"] = xr.DataArray(data=[sweep_mode], dims=("sweep"))
251
+ ds["scan_type"] = xr.DataArray(data=[scan_type_name], dims=("sweep"))
252
+ # ds = ds.drop_vars("scanType", errors="ignore")
253
+ ds["fixed_angle"] = xr.DataArray(data=[ds["fixed_angle"]], dims=("sweep",))
254
+ return ds
255
+
256
+
257
+ def _time_coverage(ds):
258
+ """
259
+ Processes time coverage start and end times.
260
+
261
+ Parameters
262
+ ----------
263
+ ds : xarray.Dataset
264
+ Input dataset.
265
+
266
+ Returns
267
+ -------
268
+ ds : xarray.Dataset
269
+ Dataset with time coverage start and end variables.
270
+ """
271
+ ds = ds.rename_vars({"esStartTime": "time_coverage_start"})
272
+ ds["time_coverage_start"] = xr.DataArray(
273
+ data=str(
274
+ ds.time_coverage_start.dt.strftime("%Y-%m-%dT%H:%M:%SZ").values
275
+ ).encode("utf-8"),
276
+ attrs={"standard_name": "data_volume_start_time_utc"},
277
+ )
278
+
279
+ ds["time_coverage_end"] = xr.DataArray(
280
+ data=str(ds.time.max().dt.strftime("%Y-%m-%dT%H:%M:%SZ").values).encode(
281
+ "utf-8"
282
+ ),
283
+ attrs={"standard_name": "data_volume_end_time_utc"},
284
+ )
285
+ return ds
286
+
287
+
288
+ def _sweep_number(ds):
289
+ """
290
+ Adds sweep number information to the dataset.
291
+
292
+ Parameters
293
+ ----------
294
+ ds : xarray.Dataset
295
+ Input dataset.
296
+
297
+ Returns
298
+ -------
299
+ ds : xarray.Dataset
300
+ Dataset with sweep number variable.
301
+ """
302
+ sweep_num = ds["elevationNumber"].astype(int).values
303
+ ds["sweep_number"] = xr.DataArray(
304
+ data=[sweep_num],
305
+ dims="sweep",
306
+ attrs={"long_name": "sweep_index_number_0_based", "units": ""},
307
+ )
308
+ ds = ds.drop_vars("elevationNumber")
309
+ return ds
310
+
311
+
312
+ def _assign_sweep_metadata(ds):
313
+ """
314
+ Assigns and updates the sweep metadata after reading multiple files.
315
+
316
+ Parameters
317
+ ----------
318
+ ds : xarray.Dataset
319
+ Combined dataset with multiple sweeps.
320
+
321
+ Returns
322
+ -------
323
+ ds : xarray.Dataset
324
+ Dataset with updated sweep metadata.
325
+ """
326
+ num_sweeps = ds.sizes["sweep"]
327
+ sweep_start = np.arange(0, num_sweeps * ds.sizes["azimuth"], ds.sizes["azimuth"])
328
+ sweep_end = sweep_start + ds.sizes["azimuth"] - 1
329
+
330
+ ds["sweep_start_ray_index"] = xr.DataArray(
331
+ sweep_start,
332
+ dims="sweep",
333
+ attrs={"long_name": "Index of the first ray in the sweep"},
334
+ )
335
+ ds["sweep_end_ray_index"] = xr.DataArray(
336
+ sweep_end,
337
+ dims="sweep",
338
+ attrs={"long_name": "Index of the last ray in the sweep"},
339
+ )
340
+
341
+ # Update sweep number if not done already
342
+ if "sweep_number" not in ds:
343
+ ds["sweep_number"] = xr.DataArray(
344
+ np.arange(num_sweeps), dims="sweep", attrs={"long_name": "Sweep number"}
345
+ )
346
+ return ds
347
+
348
+
349
+ def _find_variables_by_coords(ds, required_dims):
350
+ """
351
+ Finds variables in the dataset that have the specified dimensions.
352
+
353
+ Parameters
354
+ ----------
355
+ ds : xarray.Dataset
356
+ The input dataset.
357
+
358
+ required_dims : set
359
+ A set of required dimensions (e.g., {"range", "azimuth"}).
360
+
361
+ Returns
362
+ -------
363
+ matching_vars : list
364
+ List of variable names that have the specified dimensions.
365
+
366
+ Example
367
+ -------
368
+ Assuming `ds` is your xarray.Dataset and you want to find variables
369
+ with both "range" and "azimuth" dimensions
370
+ vars_with_rng_and_az = find_variables_by_coords(ds, {"range", "azimuth"})
371
+ """
372
+ matching_vars = []
373
+
374
+ for var_name, var_data in ds.data_vars.items():
375
+ var_dims = set(var_data.dims)
376
+ if required_dims.issubset(var_dims):
377
+ matching_vars.append(var_name)
378
+
379
+ return matching_vars
380
+
381
+
382
+ def _assign_metadata(ds):
383
+ field_names = _find_variables_by_coords(ds, {"azimuth", "range"})
384
+ field_names = ", ".join(field_names)
385
+ ds.attrs = {
386
+ "Conventions": "CF/Radial instrument_parameters",
387
+ "version": "1.3",
388
+ "title": "",
389
+ "institution": "India Meteorological Department",
390
+ "references": "",
391
+ "source": "",
392
+ "comment": "im/radarx",
393
+ "instrument_name": "",
394
+ "history": "",
395
+ "field_names": field_names,
396
+ }
397
+
398
+ return ds
399
+
400
+
401
+ def _determine_nsweeps(files):
402
+ """
403
+ Determine the number of sweeps (nsweeps) and group files
404
+ accordingly into sweep_groups.
405
+ If the nsweeps cannot be determined from metadata, the
406
+ function will detect where the sweep elevations start
407
+ decreasing to identify the start of a new volume.
408
+ """
409
+ import re
410
+
411
+ import xarray as xr
412
+
413
+ def _natural_sort_key(s, _re=re.compile(r"(\d+)")):
414
+ return [int(t) if i & 1 else t.lower() for i, t in enumerate(_re.split(s))]
415
+
416
+ try:
417
+ # Check if files exist and open the first file to determine nsweeps
418
+ if len(files):
419
+ file = files[0]
420
+ else:
421
+ return []
422
+
423
+ try:
424
+ ds = read_sweep(file)
425
+ except OSError:
426
+ ds = xr.open_dataset(file, engine="netcdf4")
427
+
428
+ # Try to retrieve nsweeps from the dataset
429
+ try:
430
+ nsweeps = ds.attrs["nsweeps"]
431
+ except KeyError:
432
+ nsweeps = ds.sizes["sweep"]
433
+ except KeyError:
434
+ nsweeps = ds["sweep"].size
435
+ except KeyError:
436
+ nsweeps = ds["fixed_angle"].size
437
+
438
+ # Sort the files naturally and group them by nsweeps
439
+ files = sorted(files, key=_natural_sort_key)
440
+ sweep_groups = []
441
+ for i in range(0, len(files), nsweeps):
442
+ sweep_groups.append(files[i : i + nsweeps])
443
+
444
+ del ds
445
+
446
+ return sweep_groups
447
+
448
+ except Exception:
449
+ # Fallback method if nsweeps cannot be determined from metadata
450
+ sweep_groups = []
451
+ sweep_times = []
452
+ sweep_elevs = []
453
+ files = sorted(files, key=_natural_sort_key)
454
+
455
+ # Collect sweep times and elevations
456
+ for file in files:
457
+ ds = xr.open_dataset(file, engine="netcdf4")
458
+ if "esStartTime" in ds:
459
+ estime = (
460
+ ds["esStartTime"].dt.strftime("%Y-%m-%dT%H:%M:%S").values.item()
461
+ )
462
+ elif "time_coverage_start" in ds:
463
+ estime = (
464
+ ds["time_coverage_start"]
465
+ .dt.strftime("%Y-%m-%dT%H:%M:%S")
466
+ .values.item()
467
+ )
468
+
469
+ if "elevationAngle" in ds:
470
+ ele_ang = ds["elevationAngle"].values.item()
471
+ elif "fixed_angle" in ds:
472
+ ele_ang = ds["fixed_angle"].values.item()
473
+ sweep_times.append(estime)
474
+ sweep_elevs.append(ele_ang)
475
+ del ds
476
+
477
+ # Determine sweep groups based on elevation increases/decreases
478
+ current_group = []
479
+ nsweeps = 0
480
+
481
+ for i in range(1, len(sweep_elevs)):
482
+ current_group.append(files[i - 1])
483
+
484
+ # Check if the elevation decreases, indicating the start of a new volume
485
+ if sweep_elevs[i] < sweep_elevs[i - 1]:
486
+ nsweeps = len(current_group)
487
+ sweep_groups.append(current_group)
488
+ current_group = []
489
+
490
+ # Append the last group
491
+ current_group.append(files[-1])
492
+ sweep_groups.append(current_group)
493
+
494
+ return sweep_groups
495
+
496
+
497
+ def _determine_volumes(files):
498
+ """Determine Volumes"""
499
+ swp_lists = _determine_nsweeps(files)
500
+ volumes = []
501
+ for i, swp_list in enumerate(swp_lists):
502
+ dataset = []
503
+ for file in swp_list:
504
+ try:
505
+ ds = read_sweep(file)
506
+ except OSError:
507
+ ds = xr.open_dataset(file)
508
+ dataset.append(ds)
509
+ del ds
510
+ volumes.append(dataset)
511
+ del dataset
512
+ return volumes
513
+
514
+
515
+ def create_volume(dataset_list):
516
+ """
517
+ Concatenate a list of datasets along the 'time' and 'sweep' dimensions.
518
+ Non-time-varying variables will be retained as single values.
519
+ Also updates `time_coverage_end` based on the maximum time value.
520
+
521
+ You have to give the list of sweeps which are already organized/sorted,
522
+ for that you can either make it manually, or use `_determine_volumes` function
523
+
524
+ Parameters
525
+ ----------
526
+ dataset_list : list of xarray.Dataset
527
+ List of datasets representing individual sweeps.
528
+
529
+ Returns
530
+ -------
531
+ xarray.Dataset
532
+ Concatenated dataset with proper time and sweep dimensions.
533
+ """
534
+
535
+ # Identify variables that should be concatenated along the sweep dimension
536
+ sweep_vars = [
537
+ "fixed_angle",
538
+ "ray_angle_res",
539
+ "sweep_mode",
540
+ "scan_type",
541
+ "sweep_number",
542
+ "sweep_start_ray_index",
543
+ "sweep_end_ray_index",
544
+ ]
545
+
546
+ # Identify variables that are constant across sweeps and should not be concatenated
547
+ constant_vars = [
548
+ "time_coverage_start",
549
+ "latitude",
550
+ "longitude",
551
+ "altitude",
552
+ "firstGateRange",
553
+ "gateSize",
554
+ "nyquist_velocity",
555
+ "unambiguous_range",
556
+ "calibConst",
557
+ "radarConst",
558
+ "beamWidthHori",
559
+ "pulseWidth",
560
+ "bandWidth",
561
+ "filterDop",
562
+ "azimuthSpeed",
563
+ "highPRF",
564
+ "lowPRF",
565
+ "dwellTime",
566
+ "waveLength",
567
+ "calI0",
568
+ "calNoise",
569
+ "groundHeight",
570
+ "meltHeight",
571
+ "scanType",
572
+ "logNoise",
573
+ "linNoise",
574
+ "inphaseOffset",
575
+ "quadratureOffset",
576
+ "logSlope",
577
+ "logFilter",
578
+ "filterPntClt",
579
+ "filterThreshold",
580
+ "sampleNum",
581
+ "SQIThresh",
582
+ "LOGThresh",
583
+ "SIGThresh",
584
+ "CSRThresh",
585
+ "DBTThreshFlag",
586
+ "DBZThreshFlag",
587
+ "VELThreshFlag",
588
+ "WIDThreshFlag",
589
+ ]
590
+
591
+ # Step 1: Concatenate the datasets along the time dimension
592
+ time_concat = xr.concat(
593
+ [ds.drop_vars(sweep_vars, errors="ignore") for ds in dataset_list], dim="time"
594
+ )
595
+
596
+ # Step 2: Handle sweep-specific variables (metadata)
597
+ sweep_datasets = []
598
+ for i, ds in enumerate(dataset_list):
599
+ sweep_ds = {}
600
+ for var in sweep_vars:
601
+ if var in ds:
602
+ # Expand dims to add a 'sweep' dimension if needed
603
+ if "sweep" not in ds[var].dims:
604
+ sweep_ds[var] = ds[var].expand_dims("sweep")
605
+ else:
606
+ sweep_ds[var] = ds[var]
607
+
608
+ # Update sweep start and end ray indices
609
+ sweep_start = i * ds.sizes["time"]
610
+ sweep_end = sweep_start + ds.sizes["time"] - 1
611
+
612
+ sweep_ds["sweep_start_ray_index"] = xr.DataArray(
613
+ np.array([sweep_start]),
614
+ dims="sweep",
615
+ attrs={"long_name": "Index of the first ray in the sweep"},
616
+ )
617
+ sweep_ds["sweep_end_ray_index"] = xr.DataArray(
618
+ np.array([sweep_end]),
619
+ dims="sweep",
620
+ attrs={"long_name": "Index of the last ray in the sweep"},
621
+ )
622
+
623
+ # Add the updated sweep metadata dataset to the list
624
+ sweep_datasets.append(xr.Dataset(sweep_ds))
625
+
626
+ # Step 3: Concatenate the sweep-specific datasets along the sweep dimension
627
+ sweep_concat = xr.concat(sweep_datasets, dim="sweep")
628
+
629
+ # Step 4: Merge the time-concatenated data, sweep-concatenated metadata, and constant variables
630
+ combined_ds = xr.merge([time_concat, sweep_concat])
631
+
632
+ # Step 5: Assign constant variables (they do not need to be concatenated)
633
+ for var in constant_vars:
634
+ if var in dataset_list[0]:
635
+ combined_ds[var] = dataset_list[0][var]
636
+
637
+ # Step 6: Update the 'time_coverage_end' variable with the maximum time value
638
+ combined_ds["time_coverage_end"] = xr.DataArray(
639
+ data=str(
640
+ combined_ds.time.max().dt.strftime("%Y-%m-%dT%H:%M:%SZ").values
641
+ ).encode("utf-8"),
642
+ attrs={"standard_name": "data_volume_end_time_utc"},
643
+ )
644
+
645
+ return combined_ds
radarx/radarx.py ADDED
@@ -0,0 +1 @@
1
+ """Main module."""
radarx/version.py ADDED
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.1.dev2'
16
+ __version_tuple__ = version_tuple = (0, 1, 'dev2')
@@ -0,0 +1,13 @@
1
+ =======
2
+ Credits
3
+ =======
4
+
5
+ Development Lead
6
+ ----------------
7
+
8
+ * Hamid Ali Syed <hamidsyed37@gmail.com>
9
+
10
+ Contributors
11
+ ------------
12
+
13
+ None yet. Why not be the first?
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024, Hamid Ali Syed
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.
22
+
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.1
2
+ Name: radarx
3
+ Version: 0.1.dev2
4
+ Summary: Xarray based radar toolkit
5
+ Author-email: Hamid Ali Syed <hamidsyed37@gmail.com>
6
+ Maintainer-email: Hamid Ali Syed <hamidsyed37@gmail.com>
7
+ License: MIT license
8
+ Project-URL: bugs, https://github.com/syedhamidali/radarx/issues
9
+ Project-URL: changelog, https://github.com/syedhamidali/radarx/blob/master/changelog.md
10
+ Project-URL: homepage, https://github.com/syedhamidali/radarx
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Education
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
24
+ License-File: LICENSE
25
+ License-File: AUTHORS.rst
26
+ Provides-Extra: dev
27
+ Requires-Dist: coverage; extra == "dev"
28
+ Requires-Dist: mypy; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: ruff; extra == "dev"
31
+
@@ -0,0 +1,11 @@
1
+ radarx/__init__.py,sha256=QWCxSXc75OVIFZMdBmyS16rGb2-gwooHiZxME5mxL3E,129
2
+ radarx/radarx.py,sha256=h0hwdogXGFqerm-5ZPeT-irPn91pCcQRjiHThXsRzEk,19
3
+ radarx/version.py,sha256=qI8Vf33xG4VceR8oyHmT7zDIjATXRT7C3GYLCaQ4dbk,419
4
+ radarx/io/__init__.py,sha256=T54dKCI1Ry9H0JTxqTCM7beOv_gKMtbpmSuaEdLHdTw,254
5
+ radarx/io/imd.py,sha256=V1KcaPcocjh894QnjYejTTLAhTRiEeM081U2WB5YMuw,18448
6
+ radarx-0.1.dev2.dist-info/AUTHORS.rst,sha256=ofa-p8YxgkF9L_CBDywg145c4zJi4RLxp4Z2O0ftep8,161
7
+ radarx-0.1.dev2.dist-info/LICENSE,sha256=ZEEk5q67LfzfTDG9HPbSXWJ5UskfEKD9CihOh44XxwU,1073
8
+ radarx-0.1.dev2.dist-info/METADATA,sha256=ump2wHwpa6SbMwfRMzJ-WLR4poazfSQNOI0Ne_9ecvE,1284
9
+ radarx-0.1.dev2.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
10
+ radarx-0.1.dev2.dist-info/top_level.txt,sha256=UssaY3VGVe1ajQm-JktlZII5EhQKhXGqw-e5upCC3zk,7
11
+ radarx-0.1.dev2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (74.1.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ radarx