geoai-py 0.5.0__py2.py3-none-any.whl → 0.5.2__py2.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.
geoai/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  __author__ = """Qiusheng Wu"""
4
4
  __email__ = "giswqs@gmail.com"
5
- __version__ = "0.5.0"
5
+ __version__ = "0.5.2"
6
6
 
7
7
 
8
8
  import os
geoai/geoai.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Main module."""
2
2
 
3
3
  import leafmap
4
+ import leafmap.maplibregl as maplibregl
4
5
 
5
6
  from .download import (
6
7
  download_naip,
@@ -17,7 +18,7 @@ from .classify import train_classifier, classify_image, classify_images
17
18
  from .extract import *
18
19
  from .hf import *
19
20
  from .segment import *
20
- from .train import object_detection, train_MaskRCNN_model
21
+ from .train import object_detection, object_detection_batch, train_MaskRCNN_model
21
22
  from .utils import *
22
23
 
23
24
 
@@ -27,3 +28,180 @@ class Map(leafmap.Map):
27
28
  def __init__(self, *args, **kwargs):
28
29
  """Initialize the Map class."""
29
30
  super().__init__(*args, **kwargs)
31
+
32
+
33
+ class MapLibre(maplibregl.Map):
34
+ """A subclass of maplibregl.Map for GeoAI applications."""
35
+
36
+ def __init__(self, *args, **kwargs):
37
+ """Initialize the MapLibre class."""
38
+ super().__init__(*args, **kwargs)
39
+
40
+
41
+ def create_vector_data(
42
+ m: Optional[Map] = None,
43
+ properties: Optional[Dict[str, List[Any]]] = None,
44
+ time_format: str = "%Y%m%dT%H%M%S",
45
+ column_widths: Optional[List[int]] = (9, 3),
46
+ map_height: str = "600px",
47
+ out_dir: Optional[str] = None,
48
+ filename_prefix: str = "",
49
+ file_ext: str = "geojson",
50
+ add_mapillary: bool = False,
51
+ style: str = "photo",
52
+ radius: float = 0.00005,
53
+ width: int = 300,
54
+ height: int = 420,
55
+ frame_border: int = 0,
56
+ **kwargs: Any,
57
+ ):
58
+ """Generates a widget-based interface for creating and managing vector data on a map.
59
+
60
+ This function creates an interactive widget interface that allows users to draw features
61
+ (points, lines, polygons) on a map, assign properties to these features, and export them
62
+ as GeoJSON files. The interface includes a map, a sidebar for property management, and
63
+ buttons for saving, exporting, and resetting the data.
64
+
65
+ Args:
66
+ m (Map, optional): An existing Map object. If not provided, a default map with
67
+ basemaps and drawing controls will be created. Defaults to None.
68
+ properties (Dict[str, List[Any]], optional): A dictionary where keys are property names
69
+ and values are lists of possible values for each property. These properties can be
70
+ assigned to the drawn features. Defaults to None.
71
+ time_format (str, optional): The format string for the timestamp used in the exported
72
+ filename. Defaults to "%Y%m%dT%H%M%S".
73
+ column_widths (Optional[List[int]], optional): A list of two integers specifying the
74
+ relative widths of the map and sidebar columns. Defaults to (9, 3).
75
+ map_height (str, optional): The height of the map widget. Defaults to "600px".
76
+ out_dir (str, optional): The directory where the exported GeoJSON files will be saved.
77
+ If not provided, the current working directory is used. Defaults to None.
78
+ filename_prefix (str, optional): A prefix to be added to the exported filename.
79
+ Defaults to "".
80
+ file_ext (str, optional): The file extension for the exported file. Defaults to "geojson".
81
+ add_mapillary (bool, optional): Whether to add a Mapillary image widget that displays the
82
+ nearest image to the clicked point on the map. Defaults to False.
83
+ style (str, optional): The style of the Mapillary image widget. Can be "classic", "photo",
84
+ or "split". Defaults to "photo".
85
+ radius (float, optional): The radius (in degrees) used to search for the nearest Mapillary
86
+ image. Defaults to 0.00005 degrees.
87
+ width (int, optional): The width of the Mapillary image widget. Defaults to 300.
88
+ height (int, optional): The height of the Mapillary image widget. Defaults to 420.
89
+ frame_border (int, optional): The width of the frame border for the Mapillary image widget.
90
+ Defaults to 0.
91
+ **kwargs (Any): Additional keyword arguments that may be passed to the function.
92
+
93
+ Returns:
94
+ widgets.VBox: A vertical box widget containing the map, sidebar, and control buttons.
95
+
96
+ Example:
97
+ >>> properties = {
98
+ ... "Type": ["Residential", "Commercial", "Industrial"],
99
+ ... "Area": [100, 200, 300],
100
+ ... }
101
+ >>> widget = create_vector_data(properties=properties)
102
+ >>> display(widget) # Display the widget in a Jupyter notebook
103
+ """
104
+ return maplibregl.create_vector_data(
105
+ m=m,
106
+ properties=properties,
107
+ time_format=time_format,
108
+ column_widths=column_widths,
109
+ map_height=map_height,
110
+ out_dir=out_dir,
111
+ filename_prefix=filename_prefix,
112
+ file_ext=file_ext,
113
+ add_mapillary=add_mapillary,
114
+ style=style,
115
+ radius=radius,
116
+ width=width,
117
+ height=height,
118
+ frame_border=frame_border,
119
+ **kwargs,
120
+ )
121
+
122
+
123
+ def edit_vector_data(
124
+ m: Optional[Map] = None,
125
+ filename: str = None,
126
+ properties: Optional[Dict[str, List[Any]]] = None,
127
+ time_format: str = "%Y%m%dT%H%M%S",
128
+ column_widths: Optional[List[int]] = (9, 3),
129
+ map_height: str = "600px",
130
+ out_dir: Optional[str] = None,
131
+ filename_prefix: str = "",
132
+ file_ext: str = "geojson",
133
+ add_mapillary: bool = False,
134
+ style: str = "photo",
135
+ radius: float = 0.00005,
136
+ width: int = 300,
137
+ height: int = 420,
138
+ frame_border: int = 0,
139
+ controls: Optional[List[str]] = None,
140
+ position: str = "top-right",
141
+ fit_bounds_options: Dict = None,
142
+ **kwargs: Any,
143
+ ):
144
+ """Generates a widget-based interface for creating and managing vector data on a map.
145
+
146
+ This function creates an interactive widget interface that allows users to draw features
147
+ (points, lines, polygons) on a map, assign properties to these features, and export them
148
+ as GeoJSON files. The interface includes a map, a sidebar for property management, and
149
+ buttons for saving, exporting, and resetting the data.
150
+
151
+ Args:
152
+ m (Map, optional): An existing Map object. If not provided, a default map with
153
+ basemaps and drawing controls will be created. Defaults to None.
154
+ filename (str or gpd.GeoDataFrame): The path to a GeoJSON file or a GeoDataFrame
155
+ containing the vector data to be edited. Defaults to None.
156
+ properties (Dict[str, List[Any]], optional): A dictionary where keys are property names
157
+ and values are lists of possible values for each property. These properties can be
158
+ assigned to the drawn features. Defaults to None.
159
+ time_format (str, optional): The format string for the timestamp used in the exported
160
+ filename. Defaults to "%Y%m%dT%H%M%S".
161
+ column_widths (Optional[List[int]], optional): A list of two integers specifying the
162
+ relative widths of the map and sidebar columns. Defaults to (9, 3).
163
+ map_height (str, optional): The height of the map widget. Defaults to "600px".
164
+ out_dir (str, optional): The directory where the exported GeoJSON files will be saved.
165
+ If not provided, the current working directory is used. Defaults to None.
166
+ filename_prefix (str, optional): A prefix to be added to the exported filename.
167
+ Defaults to "".
168
+ file_ext (str, optional): The file extension for the exported file. Defaults to "geojson".
169
+ add_mapillary (bool, optional): Whether to add a Mapillary image widget that displays the
170
+ nearest image to the clicked point on the map. Defaults to False.
171
+ style (str, optional): The style of the Mapillary image widget. Can be "classic", "photo",
172
+ or "split". Defaults to "photo".
173
+ radius (float, optional): The radius (in degrees) used to search for the nearest Mapillary
174
+ image. Defaults to 0.00005 degrees.
175
+ width (int, optional): The width of the Mapillary image widget. Defaults to 300.
176
+ height (int, optional): The height of the Mapillary image widget. Defaults to 420.
177
+ frame_border (int, optional): The width of the frame border for the Mapillary image widget.
178
+ Defaults to 0.
179
+ controls (Optional[List[str]], optional): The drawing controls to be added to the map.
180
+ Defaults to ["point", "polygon", "line_string", "trash"].
181
+ position (str, optional): The position of the drawing controls on the map. Defaults to "top-right".
182
+ **kwargs (Any): Additional keyword arguments that may be passed to the function.
183
+
184
+ Returns:
185
+ widgets.VBox: A vertical box widget containing the map, sidebar, and control buttons.
186
+ """
187
+ return maplibregl.edit_vector_data(
188
+ m=m,
189
+ filename=filename,
190
+ properties=properties,
191
+ time_format=time_format,
192
+ column_widths=column_widths,
193
+ map_height=map_height,
194
+ out_dir=out_dir,
195
+ filename_prefix=filename_prefix,
196
+ file_ext=file_ext,
197
+ add_mapillary=add_mapillary,
198
+ style=style,
199
+ radius=radius,
200
+ width=width,
201
+ height=height,
202
+ frame_border=frame_border,
203
+ controls=controls,
204
+ position=position,
205
+ fit_bounds_options=fit_bounds_options,
206
+ **kwargs,
207
+ )
geoai/train.py CHANGED
@@ -1,3 +1,4 @@
1
+ import glob
1
2
  import math
2
3
  import os
3
4
  import random
@@ -20,6 +21,8 @@ from torchvision.models.detection.faster_rcnn import FastRCNNPredictor
20
21
  from torchvision.models.detection.mask_rcnn import MaskRCNNPredictor
21
22
  from tqdm import tqdm
22
23
 
24
+ from .utils import download_model_from_hf
25
+
23
26
 
24
27
  def get_instance_segmentation_model(num_classes=2, num_channels=3, pretrained=True):
25
28
  """
@@ -352,7 +355,9 @@ def collate_fn(batch):
352
355
  return tuple(zip(*batch))
353
356
 
354
357
 
355
- def train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10):
358
+ def train_one_epoch(
359
+ model, optimizer, data_loader, device, epoch, print_freq=10, verbose=True
360
+ ):
356
361
  """
357
362
  Train the model for one epoch.
358
363
 
@@ -363,6 +368,7 @@ def train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10)
363
368
  device (torch.device): Device to train on.
364
369
  epoch (int): Current epoch number.
365
370
  print_freq (int): How often to print progress.
371
+ verbose (bool): Whether to print detailed progress.
366
372
 
367
373
  Returns:
368
374
  float: Average loss for the epoch.
@@ -392,9 +398,10 @@ def train_one_epoch(model, optimizer, data_loader, device, epoch, print_freq=10)
392
398
  # Print progress
393
399
  if i % print_freq == 0:
394
400
  elapsed_time = time.time() - start_time
395
- print(
396
- f"Epoch: {epoch}, Batch: {i}/{len(data_loader)}, Loss: {losses.item():.4f}, Time: {elapsed_time:.2f}s"
397
- )
401
+ if verbose:
402
+ print(
403
+ f"Epoch: {epoch}, Batch: {i}/{len(data_loader)}, Loss: {losses.item():.4f}, Time: {elapsed_time:.2f}s"
404
+ )
398
405
  start_time = time.time()
399
406
 
400
407
  # Calculate average loss
@@ -579,6 +586,8 @@ def train_MaskRCNN_model(
579
586
  val_split=0.2,
580
587
  visualize=False,
581
588
  resume_training=False,
589
+ print_freq=10,
590
+ verbose=True,
582
591
  ):
583
592
  """Train and evaluate Mask R-CNN model for instance segmentation.
584
593
 
@@ -605,7 +614,8 @@ def train_MaskRCNN_model(
605
614
  Defaults to False.
606
615
  resume_training (bool): If True and pretrained_model_path is provided,
607
616
  will try to load optimizer and scheduler states as well. Defaults to False.
608
-
617
+ print_freq (int): Frequency of printing training progress. Defaults to 10.
618
+ verbose (bool): If True, prints detailed training progress. Defaults to True.
609
619
  Returns:
610
620
  None: Model weights are saved to output_dir.
611
621
 
@@ -756,7 +766,9 @@ def train_MaskRCNN_model(
756
766
  # Training loop
757
767
  for epoch in range(start_epoch, num_epochs):
758
768
  # Train one epoch
759
- train_loss = train_one_epoch(model, optimizer, train_loader, device, epoch)
769
+ train_loss = train_one_epoch(
770
+ model, optimizer, train_loader, device, epoch, print_freq, verbose
771
+ )
760
772
 
761
773
  # Update learning rate
762
774
  lr_scheduler.step()
@@ -1107,6 +1119,13 @@ def object_detection(
1107
1119
  model = get_instance_segmentation_model(
1108
1120
  num_classes=2, num_channels=num_channels, pretrained=pretrained
1109
1121
  )
1122
+
1123
+ if not os.path.exists(model_path):
1124
+ try:
1125
+ model_path = download_model_from_hf(model_path)
1126
+ except Exception as e:
1127
+ raise FileNotFoundError(f"Model file not found: {model_path}")
1128
+
1110
1129
  model.load_state_dict(torch.load(model_path, map_location=device))
1111
1130
  model.to(device)
1112
1131
  model.eval()
@@ -1123,3 +1142,93 @@ def object_detection(
1123
1142
  device=device,
1124
1143
  **kwargs,
1125
1144
  )
1145
+
1146
+
1147
+ def object_detection_batch(
1148
+ input_paths,
1149
+ output_dir,
1150
+ model_path,
1151
+ filenames=None,
1152
+ window_size=512,
1153
+ overlap=256,
1154
+ confidence_threshold=0.5,
1155
+ batch_size=4,
1156
+ num_channels=3,
1157
+ pretrained=True,
1158
+ device=None,
1159
+ **kwargs,
1160
+ ):
1161
+ """
1162
+ Perform object detection on a GeoTIFF using a pre-trained Mask R-CNN model.
1163
+
1164
+ Args:
1165
+ input_paths (str or list): Path(s) to input GeoTIFF file(s). If a directory is provided,
1166
+ all .tif files in that directory will be processed.
1167
+ output_dir (str): Directory to save output mask GeoTIFF files.
1168
+ model_path (str): Path to trained model weights.
1169
+ filenames (list, optional): List of output filenames. If None, defaults to
1170
+ "<input_filename>_mask.tif" for each input file.
1171
+ If provided, must match the number of input files.
1172
+ window_size (int): Size of sliding window for inference.
1173
+ overlap (int): Overlap between adjacent windows.
1174
+ confidence_threshold (float): Confidence threshold for predictions (0-1).
1175
+ batch_size (int): Batch size for inference.
1176
+ num_channels (int): Number of channels in the input image and model.
1177
+ pretrained (bool): Whether to use pretrained backbone for model loading.
1178
+ device (torch.device, optional): Device to run inference on. If None, uses CUDA if available.
1179
+ **kwargs: Additional arguments passed to inference_on_geotiff.
1180
+
1181
+ Returns:
1182
+ None: Output mask is saved to output_path.
1183
+ """
1184
+ # Load your trained model
1185
+ if device is None:
1186
+ device = (
1187
+ torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
1188
+ )
1189
+ model = get_instance_segmentation_model(
1190
+ num_classes=2, num_channels=num_channels, pretrained=pretrained
1191
+ )
1192
+
1193
+ if not os.path.exists(output_dir):
1194
+ os.makedirs(output_dir, exist_ok=True)
1195
+
1196
+ if not os.path.exists(model_path):
1197
+ try:
1198
+ model_path = download_model_from_hf(model_path)
1199
+ except Exception as e:
1200
+ raise FileNotFoundError(f"Model file not found: {model_path}")
1201
+
1202
+ model.load_state_dict(torch.load(model_path, map_location=device))
1203
+ model.to(device)
1204
+ model.eval()
1205
+
1206
+ if isinstance(input_paths, str) and (not input_paths.endswith(".tif")):
1207
+ files = glob.glob(os.path.join(input_paths, "*.tif"))
1208
+ files.sort()
1209
+ elif isinstance(input_paths, str):
1210
+ files = [input_paths]
1211
+
1212
+ if filenames is None:
1213
+ filenames = [
1214
+ os.path.join(output_dir, os.path.basename(f).replace(".tif", "_mask.tif"))
1215
+ for f in files
1216
+ ]
1217
+ else:
1218
+ if len(filenames) != len(files):
1219
+ raise ValueError("Number of filenames must match number of input files.")
1220
+
1221
+ for index, file in enumerate(files):
1222
+ print(f"Processing file {index + 1}/{len(files)}: {file}")
1223
+ inference_on_geotiff(
1224
+ model=model,
1225
+ geotiff_path=file,
1226
+ output_path=filenames[index],
1227
+ window_size=window_size, # Adjust based on your model and memory
1228
+ overlap=overlap, # Overlap to avoid edge artifacts
1229
+ confidence_threshold=confidence_threshold,
1230
+ batch_size=batch_size, # Adjust based on your GPU memory
1231
+ num_channels=num_channels,
1232
+ device=device,
1233
+ **kwargs,
1234
+ )
geoai/utils.py CHANGED
@@ -6215,3 +6215,37 @@ def mosaic_geotiffs(input_dir, output_file, mask_file=None):
6215
6215
 
6216
6216
  print(f"Cloud Optimized GeoTIFF mosaic created successfully: {output_file}")
6217
6217
  return True
6218
+
6219
+
6220
+ def download_model_from_hf(model_path, repo_id=None):
6221
+ """
6222
+ Download the object detection model from Hugging Face.
6223
+
6224
+ Args:
6225
+ model_path: Path to the model file.
6226
+ repo_id: Hugging Face repository ID.
6227
+
6228
+ Returns:
6229
+ Path to the downloaded model file
6230
+ """
6231
+ from huggingface_hub import hf_hub_download
6232
+
6233
+ try:
6234
+
6235
+ # Define the repository ID and model filename
6236
+ if repo_id is None:
6237
+ print(
6238
+ "Repo is is not specified, using default Hugging Face repo_id: giswqs/geoai"
6239
+ )
6240
+ repo_id = "giswqs/geoai"
6241
+
6242
+ # Download the model
6243
+ model_path = hf_hub_download(repo_id=repo_id, filename=model_path)
6244
+ print(f"Model downloaded to: {model_path}")
6245
+
6246
+ return model_path
6247
+
6248
+ except Exception as e:
6249
+ print(f"Error downloading model from Hugging Face: {e}")
6250
+ print("Please specify a local model path or ensure internet connectivity.")
6251
+ raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geoai-py
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: A Python package for using Artificial Intelligence (AI) with geospatial data
5
5
  Author-email: Qiusheng Wu <giswqs@gmail.com>
6
6
  License: MIT License
@@ -25,6 +25,7 @@ Requires-Dist: jupyter-server-proxy
25
25
  Requires-Dist: leafmap
26
26
  Requires-Dist: localtileserver
27
27
  Requires-Dist: mapclassify
28
+ Requires-Dist: maplibre
28
29
  Requires-Dist: overturemaps
29
30
  Requires-Dist: planetary-computer
30
31
  Requires-Dist: pystac-client
@@ -143,3 +144,10 @@ We welcome contributions of all kinds! See our [contributing guide](https://geoa
143
144
  ## 📄 License
144
145
 
145
146
  GeoAI is free and open source software, licensed under the MIT License.
147
+
148
+ ## Acknowledgments
149
+
150
+ We gratefully acknowledge the support of the following organizations:
151
+
152
+ - [NASA](https://www.nasa.gov): This research is partially supported by the National Aeronautics and Space Administration (NASA) through Grant No. 80NSSC22K1742, awarded under the [Open Source Tools, Frameworks, and Libraries Program](https://bit.ly/3RVBRcQ).
153
+ - [AmericaView](https://americaview.org): This work is also partially supported by the U.S. Geological Survey through Grant/Cooperative Agreement No. G23AP00683 (GY23-GY27) in collaboration with AmericaView.
@@ -0,0 +1,16 @@
1
+ geoai/__init__.py,sha256=0Q6M9_AJdoXX-sWjkXWJLrghRAP41WNAA6AT_mn-_Rk,3765
2
+ geoai/classify.py,sha256=_e-193QzAx3pIxUflPIsIs1qZevQx5ADu7i3bOL1G70,35055
3
+ geoai/download.py,sha256=lJ1GsJOZsKc2i6_dQyPV-XXIXmlADOpmSBo-wha4DEU,40892
4
+ geoai/extract.py,sha256=GocJufMmrwEWxNBL1J91EXXHL8AKcO8m_lmtUF5AKPw,119102
5
+ geoai/geoai.py,sha256=41ppSiGizvXf-b773QrBzjOlQe0VfHMRJnqQDF22RVY,9199
6
+ geoai/hf.py,sha256=mLKGxEAS5eHkxZLwuLpYc1o7e3-7QIXdBv-QUY-RkFk,17072
7
+ geoai/segment.py,sha256=g3YW17ftr--CKq6VB32TJEPY8owGQ7uQ0sg_tUT2ooE,13681
8
+ geoai/segmentation.py,sha256=AtPzCvguHAEeuyXafa4bzMFATvltEYcah1B8ZMfkM_s,11373
9
+ geoai/train.py,sha256=mQXat2yuddT-2rME4xnX_m3SkY23E_-zdxLnBIKxw8o,44091
10
+ geoai/utils.py,sha256=5BZTL9QlJGEs9uw5w6i_aZ4s8SH_FGvb6ZFlIyEHEZI,239703
11
+ geoai_py-0.5.2.dist-info/licenses/LICENSE,sha256=vN2L5U7cZ6ZkOHFmc8WiGlsogWsZc5dllMeNxnKVOZg,1070
12
+ geoai_py-0.5.2.dist-info/METADATA,sha256=QT135XcjnpSQWchbm-_kB2F4PesM-BJCzkkz8nxiozk,6637
13
+ geoai_py-0.5.2.dist-info/WHEEL,sha256=MAQBAzGbXNI3bUmkDsiV_duv8i-gcdnLzw7cfUFwqhU,109
14
+ geoai_py-0.5.2.dist-info/entry_points.txt,sha256=uGp3Az3HURIsRHP9v-ys0hIbUuBBNUfXv6VbYHIXeg4,41
15
+ geoai_py-0.5.2.dist-info/top_level.txt,sha256=1YkCUWu-ii-0qIex7kbwAvfei-gos9ycyDyUCJPNWHY,6
16
+ geoai_py-0.5.2.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- geoai/__init__.py,sha256=jGJl23LMoJDPdWTrlWAPxXK8cjwWllGQcFQ5IJOJ0s0,3765
2
- geoai/classify.py,sha256=_e-193QzAx3pIxUflPIsIs1qZevQx5ADu7i3bOL1G70,35055
3
- geoai/download.py,sha256=lJ1GsJOZsKc2i6_dQyPV-XXIXmlADOpmSBo-wha4DEU,40892
4
- geoai/extract.py,sha256=GocJufMmrwEWxNBL1J91EXXHL8AKcO8m_lmtUF5AKPw,119102
5
- geoai/geoai.py,sha256=5zXt7WQ0FbiksXKQ9fBNnfa9dhJ3QVd8LXicMxyynTg,698
6
- geoai/hf.py,sha256=mLKGxEAS5eHkxZLwuLpYc1o7e3-7QIXdBv-QUY-RkFk,17072
7
- geoai/segment.py,sha256=g3YW17ftr--CKq6VB32TJEPY8owGQ7uQ0sg_tUT2ooE,13681
8
- geoai/segmentation.py,sha256=AtPzCvguHAEeuyXafa4bzMFATvltEYcah1B8ZMfkM_s,11373
9
- geoai/train.py,sha256=-l2j1leTxDnFDLaBslu1q6CobXjm3LEdiQwUWOU8P6M,40088
10
- geoai/utils.py,sha256=xxhIkNFFtv-s_tzrlAyD4qIOaxo2JtKJEzG6vFgA5y8,238737
11
- geoai_py-0.5.0.dist-info/licenses/LICENSE,sha256=vN2L5U7cZ6ZkOHFmc8WiGlsogWsZc5dllMeNxnKVOZg,1070
12
- geoai_py-0.5.0.dist-info/METADATA,sha256=tzHz2Q1iTpCO25T8W3N2RtyaHdwdb9umS9YIgCciFdo,6049
13
- geoai_py-0.5.0.dist-info/WHEEL,sha256=MAQBAzGbXNI3bUmkDsiV_duv8i-gcdnLzw7cfUFwqhU,109
14
- geoai_py-0.5.0.dist-info/entry_points.txt,sha256=uGp3Az3HURIsRHP9v-ys0hIbUuBBNUfXv6VbYHIXeg4,41
15
- geoai_py-0.5.0.dist-info/top_level.txt,sha256=1YkCUWu-ii-0qIex7kbwAvfei-gos9ycyDyUCJPNWHY,6
16
- geoai_py-0.5.0.dist-info/RECORD,,