geoai-py 0.5.5__py2.py3-none-any.whl → 0.6.0__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/utils.py CHANGED
@@ -2112,7 +2112,7 @@ def raster_to_vector(
2112
2112
  return gdf
2113
2113
 
2114
2114
 
2115
- def batch_raster_to_vector(
2115
+ def raster_to_vector_batch(
2116
2116
  input_dir,
2117
2117
  output_dir,
2118
2118
  pattern="*.tif",
@@ -6080,6 +6080,7 @@ def mosaic_geotiffs(input_dir, output_file, mask_file=None):
6080
6080
  True
6081
6081
  """
6082
6082
  import glob
6083
+
6083
6084
  from osgeo import gdal
6084
6085
 
6085
6086
  gdal.UseExceptions()
@@ -6345,3 +6346,331 @@ def regularize(
6345
6346
  gdf.to_file(output_path, **kwargs)
6346
6347
 
6347
6348
  return gdf
6349
+
6350
+
6351
+ def vector_to_geojson(filename, output=None, **kwargs):
6352
+ """Converts a vector file to a geojson file.
6353
+
6354
+ Args:
6355
+ filename (str): The vector file path.
6356
+ output (str, optional): The output geojson file path. Defaults to None.
6357
+
6358
+ Returns:
6359
+ dict: The geojson dictionary.
6360
+ """
6361
+
6362
+ if filename.startswith("http"):
6363
+ filename = download_file(filename)
6364
+
6365
+ gdf = gpd.read_file(filename, **kwargs)
6366
+ if output is None:
6367
+ return gdf.__geo_interface__
6368
+ else:
6369
+ gdf.to_file(output, driver="GeoJSON")
6370
+
6371
+
6372
+ def geojson_to_coords(
6373
+ geojson: str, src_crs: str = "epsg:4326", dst_crs: str = "epsg:4326"
6374
+ ) -> list:
6375
+ """Converts a geojson file or a dictionary of feature collection to a list of centroid coordinates.
6376
+
6377
+ Args:
6378
+ geojson (str | dict): The geojson file path or a dictionary of feature collection.
6379
+ src_crs (str, optional): The source CRS. Defaults to "epsg:4326".
6380
+ dst_crs (str, optional): The destination CRS. Defaults to "epsg:4326".
6381
+
6382
+ Returns:
6383
+ list: A list of centroid coordinates in the format of [[x1, y1], [x2, y2], ...]
6384
+ """
6385
+
6386
+ import json
6387
+ import warnings
6388
+
6389
+ warnings.filterwarnings("ignore")
6390
+
6391
+ if isinstance(geojson, dict):
6392
+ geojson = json.dumps(geojson)
6393
+ gdf = gpd.read_file(geojson, driver="GeoJSON")
6394
+ centroids = gdf.geometry.centroid
6395
+ centroid_list = [[point.x, point.y] for point in centroids]
6396
+ if src_crs != dst_crs:
6397
+ centroid_list = transform_coords(
6398
+ [x[0] for x in centroid_list],
6399
+ [x[1] for x in centroid_list],
6400
+ src_crs,
6401
+ dst_crs,
6402
+ )
6403
+ centroid_list = [[x, y] for x, y in zip(centroid_list[0], centroid_list[1])]
6404
+ return centroid_list
6405
+
6406
+
6407
+ def coords_to_xy(
6408
+ src_fp: str,
6409
+ coords: np.ndarray,
6410
+ coord_crs: str = "epsg:4326",
6411
+ return_out_of_bounds=False,
6412
+ **kwargs,
6413
+ ) -> np.ndarray:
6414
+ """Converts a list or array of coordinates to pixel coordinates, i.e., (col, row) coordinates.
6415
+
6416
+ Args:
6417
+ src_fp: The source raster file path.
6418
+ coords: A 2D or 3D array of coordinates. Can be of shape [[x1, y1], [x2, y2], ...]
6419
+ or [[[x1, y1]], [[x2, y2]], ...].
6420
+ coord_crs: The coordinate CRS of the input coordinates. Defaults to "epsg:4326".
6421
+ return_out_of_bounds: Whether to return out-of-bounds coordinates. Defaults to False.
6422
+ **kwargs: Additional keyword arguments to pass to rasterio.transform.rowcol.
6423
+
6424
+ Returns:
6425
+ A 2D or 3D array of pixel coordinates in the same format as the input.
6426
+ """
6427
+ from rasterio.warp import transform as transform_coords
6428
+
6429
+ out_of_bounds = []
6430
+ if isinstance(coords, np.ndarray):
6431
+ input_is_3d = coords.ndim == 3 # Check if the input is a 3D array
6432
+ else:
6433
+ input_is_3d = False
6434
+
6435
+ # Flatten the 3D array to 2D if necessary
6436
+ if input_is_3d:
6437
+ original_shape = coords.shape # Store the original shape
6438
+ coords = coords.reshape(-1, 2) # Flatten to 2D
6439
+
6440
+ # Convert ndarray to a list if necessary
6441
+ if isinstance(coords, np.ndarray):
6442
+ coords = coords.tolist()
6443
+
6444
+ xs, ys = zip(*coords)
6445
+ with rasterio.open(src_fp) as src:
6446
+ width = src.width
6447
+ height = src.height
6448
+ if coord_crs != src.crs:
6449
+ xs, ys = transform_coords(coord_crs, src.crs, xs, ys, **kwargs)
6450
+ rows, cols = rasterio.transform.rowcol(src.transform, xs, ys, **kwargs)
6451
+
6452
+ result = [[col, row] for col, row in zip(cols, rows)]
6453
+
6454
+ output = []
6455
+
6456
+ for i, (x, y) in enumerate(result):
6457
+ if x >= 0 and y >= 0 and x < width and y < height:
6458
+ output.append([x, y])
6459
+ else:
6460
+ out_of_bounds.append(i)
6461
+
6462
+ # Convert the output back to the original shape if input was 3D
6463
+ output = np.array(output)
6464
+ if input_is_3d:
6465
+ output = output.reshape(original_shape)
6466
+
6467
+ # Handle cases where no valid pixel coordinates are found
6468
+ if len(output) == 0:
6469
+ print("No valid pixel coordinates found.")
6470
+ elif len(output) < len(coords):
6471
+ print("Some coordinates are out of the image boundary.")
6472
+
6473
+ if return_out_of_bounds:
6474
+ return output, out_of_bounds
6475
+ else:
6476
+ return output
6477
+
6478
+
6479
+ def boxes_to_vector(coords, src_crs, dst_crs="EPSG:4326", output=None, **kwargs):
6480
+ """
6481
+ Convert a list of bounding box coordinates to vector data.
6482
+
6483
+ Args:
6484
+ coords (list): A list of bounding box coordinates in the format [[left, top, right, bottom], [left, top, right, bottom], ...].
6485
+ src_crs (int or str): The EPSG code or proj4 string representing the source coordinate reference system (CRS) of the input coordinates.
6486
+ dst_crs (int or str, optional): The EPSG code or proj4 string representing the destination CRS to reproject the data (default is "EPSG:4326").
6487
+ output (str or None, optional): The full file path (including the directory and filename without the extension) where the vector data should be saved.
6488
+ If None (default), the function returns the GeoDataFrame without saving it to a file.
6489
+ **kwargs: Additional keyword arguments to pass to geopandas.GeoDataFrame.to_file() when saving the vector data.
6490
+
6491
+ Returns:
6492
+ geopandas.GeoDataFrame or None: The GeoDataFrame with the converted vector data if output is None, otherwise None if the data is saved to a file.
6493
+ """
6494
+
6495
+ from shapely.geometry import box
6496
+
6497
+ # Create a list of Shapely Polygon objects based on the provided coordinates
6498
+ polygons = [box(*coord) for coord in coords]
6499
+
6500
+ # Create a GeoDataFrame with the Shapely Polygon objects
6501
+ gdf = gpd.GeoDataFrame({"geometry": polygons}, crs=src_crs)
6502
+
6503
+ # Reproject the GeoDataFrame to the specified EPSG code
6504
+ gdf_reprojected = gdf.to_crs(dst_crs)
6505
+
6506
+ if output is not None:
6507
+ gdf_reprojected.to_file(output, **kwargs)
6508
+ else:
6509
+ return gdf_reprojected
6510
+
6511
+
6512
+ def rowcol_to_xy(
6513
+ src_fp,
6514
+ rows=None,
6515
+ cols=None,
6516
+ boxes=None,
6517
+ zs=None,
6518
+ offset="center",
6519
+ output=None,
6520
+ dst_crs="EPSG:4326",
6521
+ **kwargs,
6522
+ ):
6523
+ """Converts a list of (row, col) coordinates to (x, y) coordinates.
6524
+
6525
+ Args:
6526
+ src_fp (str): The source raster file path.
6527
+ rows (list, optional): A list of row coordinates. Defaults to None.
6528
+ cols (list, optional): A list of col coordinates. Defaults to None.
6529
+ boxes (list, optional): A list of (row, col) coordinates in the format of [[left, top, right, bottom], [left, top, right, bottom], ...]
6530
+ zs: zs (list or float, optional): Height associated with coordinates. Primarily used for RPC based coordinate transformations.
6531
+ offset (str, optional): Determines if the returned coordinates are for the center of the pixel or for a corner.
6532
+ output (str, optional): The output vector file path. Defaults to None.
6533
+ dst_crs (str, optional): The destination CRS. Defaults to "EPSG:4326".
6534
+ **kwargs: Additional keyword arguments to pass to rasterio.transform.xy.
6535
+
6536
+ Returns:
6537
+ A list of (x, y) coordinates.
6538
+ """
6539
+
6540
+ if boxes is not None:
6541
+ rows = []
6542
+ cols = []
6543
+
6544
+ for box in boxes:
6545
+ rows.append(box[1])
6546
+ rows.append(box[3])
6547
+ cols.append(box[0])
6548
+ cols.append(box[2])
6549
+
6550
+ if rows is None or cols is None:
6551
+ raise ValueError("rows and cols must be provided.")
6552
+
6553
+ with rasterio.open(src_fp) as src:
6554
+ xs, ys = rasterio.transform.xy(src.transform, rows, cols, zs, offset, **kwargs)
6555
+ src_crs = src.crs
6556
+
6557
+ if boxes is None:
6558
+ return [[x, y] for x, y in zip(xs, ys)]
6559
+ else:
6560
+ result = [[xs[i], ys[i + 1], xs[i + 1], ys[i]] for i in range(0, len(xs), 2)]
6561
+
6562
+ if output is not None:
6563
+ boxes_to_vector(result, src_crs, dst_crs, output)
6564
+ else:
6565
+ return result
6566
+
6567
+
6568
+ def bbox_to_xy(
6569
+ src_fp: str, coords: list, coord_crs: str = "epsg:4326", **kwargs
6570
+ ) -> list:
6571
+ """Converts a list of coordinates to pixel coordinates, i.e., (col, row) coordinates.
6572
+ Note that map bbox coords is [minx, miny, maxx, maxy] from bottomleft to topright
6573
+ While rasterio bbox coords is [minx, max, maxx, min] from topleft to bottomright
6574
+
6575
+ Args:
6576
+ src_fp (str): The source raster file path.
6577
+ coords (list): A list of coordinates in the format of [[minx, miny, maxx, maxy], [minx, miny, maxx, maxy], ...]
6578
+ coord_crs (str, optional): The coordinate CRS of the input coordinates. Defaults to "epsg:4326".
6579
+
6580
+ Returns:
6581
+ list: A list of pixel coordinates in the format of [[minx, maxy, maxx, miny], ...] from top left to bottom right.
6582
+ """
6583
+
6584
+ if isinstance(coords, str):
6585
+ gdf = gpd.read_file(coords)
6586
+ coords = gdf.geometry.bounds.values.tolist()
6587
+ if gdf.crs is not None:
6588
+ coord_crs = f"epsg:{gdf.crs.to_epsg()}"
6589
+ elif isinstance(coords, np.ndarray):
6590
+ coords = coords.tolist()
6591
+ if isinstance(coords, dict):
6592
+ import json
6593
+
6594
+ geojson = json.dumps(coords)
6595
+ gdf = gpd.read_file(geojson, driver="GeoJSON")
6596
+ coords = gdf.geometry.bounds.values.tolist()
6597
+
6598
+ elif not isinstance(coords, list):
6599
+ raise ValueError("coords must be a list of coordinates.")
6600
+
6601
+ if not isinstance(coords[0], list):
6602
+ coords = [coords]
6603
+
6604
+ new_coords = []
6605
+
6606
+ with rasterio.open(src_fp) as src:
6607
+ width = src.width
6608
+ height = src.height
6609
+
6610
+ for coord in coords:
6611
+ minx, miny, maxx, maxy = coord
6612
+
6613
+ if coord_crs != src.crs:
6614
+ minx, miny = transform_coords(minx, miny, coord_crs, src.crs, **kwargs)
6615
+ maxx, maxy = transform_coords(maxx, maxy, coord_crs, src.crs, **kwargs)
6616
+
6617
+ rows1, cols1 = rasterio.transform.rowcol(
6618
+ src.transform, minx, miny, **kwargs
6619
+ )
6620
+ rows2, cols2 = rasterio.transform.rowcol(
6621
+ src.transform, maxx, maxy, **kwargs
6622
+ )
6623
+
6624
+ new_coords.append([cols1, rows1, cols2, rows2])
6625
+
6626
+ else:
6627
+ new_coords.append([minx, miny, maxx, maxy])
6628
+
6629
+ result = []
6630
+
6631
+ for coord in new_coords:
6632
+ minx, miny, maxx, maxy = coord
6633
+
6634
+ if (
6635
+ minx >= 0
6636
+ and miny >= 0
6637
+ and maxx >= 0
6638
+ and maxy >= 0
6639
+ and minx < width
6640
+ and miny < height
6641
+ and maxx < width
6642
+ and maxy < height
6643
+ ):
6644
+ # Note that map bbox coords is [minx, miny, maxx, maxy] from bottomleft to topright
6645
+ # While rasterio bbox coords is [minx, max, maxx, min] from topleft to bottomright
6646
+ result.append([minx, maxy, maxx, miny])
6647
+
6648
+ if len(result) == 0:
6649
+ print("No valid pixel coordinates found.")
6650
+ return None
6651
+ elif len(result) == 1:
6652
+ return result[0]
6653
+ elif len(result) < len(coords):
6654
+ print("Some coordinates are out of the image boundary.")
6655
+
6656
+ return result
6657
+
6658
+
6659
+ def geojson_to_xy(
6660
+ src_fp: str, geojson: str, coord_crs: str = "epsg:4326", **kwargs
6661
+ ) -> list:
6662
+ """Converts a geojson file or a dictionary of feature collection to a list of pixel coordinates.
6663
+
6664
+ Args:
6665
+ src_fp: The source raster file path.
6666
+ geojson: The geojson file path or a dictionary of feature collection.
6667
+ coord_crs: The coordinate CRS of the input coordinates. Defaults to "epsg:4326".
6668
+ **kwargs: Additional keyword arguments to pass to rasterio.transform.rowcol.
6669
+
6670
+ Returns:
6671
+ A list of pixel coordinates in the format of [[x1, y1], [x2, y2], ...]
6672
+ """
6673
+ with rasterio.open(src_fp) as src:
6674
+ src_crs = src.crs
6675
+ coords = geojson_to_coords(geojson, coord_crs, src_crs)
6676
+ return coords_to_xy(src_fp, coords, src_crs, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geoai-py
3
- Version: 0.5.5
3
+ Version: 0.6.0
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
@@ -26,6 +26,7 @@ Requires-Dist: leafmap
26
26
  Requires-Dist: localtileserver
27
27
  Requires-Dist: mapclassify
28
28
  Requires-Dist: maplibre
29
+ Requires-Dist: opencv-python-headless
29
30
  Requires-Dist: overturemaps
30
31
  Requires-Dist: planetary-computer
31
32
  Requires-Dist: pystac-client
@@ -0,0 +1,17 @@
1
+ geoai/__init__.py,sha256=rmYcVF2DPBAO_lOf6gIVqvT6U0BcdzDbAF9sLqkXuyE,3765
2
+ geoai/classify.py,sha256=O8fah3DOBDMZW7V_qfDYsUnjB-9Wo5fjA-0e4wvUeAE,35054
3
+ geoai/download.py,sha256=EQpcrcqMsYhDpd7bpjf4hGS5xL2oO-jsjngLgjjP3cE,46599
4
+ geoai/extract.py,sha256=vyHH1k5zaXiy1SLdCLXxbWiNLp8XKdu_MXZoREMtAOQ,119102
5
+ geoai/geoai.py,sha256=WljRN1KlFJ3rlBeSwXs7OXa14xYFR66Yp-rqsTzskv8,9340
6
+ geoai/hf.py,sha256=mLKGxEAS5eHkxZLwuLpYc1o7e3-7QIXdBv-QUY-RkFk,17072
7
+ geoai/sam.py,sha256=O6S-kGiFn7YEcFbfWFItZZQOhnsm6-GlunxQLY0daEs,34345
8
+ geoai/segment.py,sha256=g3YW17ftr--CKq6VB32TJEPY8owGQ7uQ0sg_tUT2ooE,13681
9
+ geoai/segmentation.py,sha256=AtPzCvguHAEeuyXafa4bzMFATvltEYcah1B8ZMfkM_s,11373
10
+ geoai/train.py,sha256=mQXat2yuddT-2rME4xnX_m3SkY23E_-zdxLnBIKxw8o,44091
11
+ geoai/utils.py,sha256=3l4ft_qTJm90q9oQUkFKz1f87_xKkuun237m6_DDVAY,256085
12
+ geoai_py-0.6.0.dist-info/licenses/LICENSE,sha256=vN2L5U7cZ6ZkOHFmc8WiGlsogWsZc5dllMeNxnKVOZg,1070
13
+ geoai_py-0.6.0.dist-info/METADATA,sha256=trRCYv82UZHfAZDMZyMY8-38Db6Cb1ys7H6SeVrwECc,6661
14
+ geoai_py-0.6.0.dist-info/WHEEL,sha256=oSJJyWjO7Z2XSScFQUpXG1HL-N0sFMqqeKVVbZTPkWc,109
15
+ geoai_py-0.6.0.dist-info/entry_points.txt,sha256=uGp3Az3HURIsRHP9v-ys0hIbUuBBNUfXv6VbYHIXeg4,41
16
+ geoai_py-0.6.0.dist-info/top_level.txt,sha256=1YkCUWu-ii-0qIex7kbwAvfei-gos9ycyDyUCJPNWHY,6
17
+ geoai_py-0.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,16 +0,0 @@
1
- geoai/__init__.py,sha256=IOmIEHpwSXTIcrja0XC9W8ltKl6LfHEFUHSaFedMhAI,3765
2
- geoai/classify.py,sha256=_e-193QzAx3pIxUflPIsIs1qZevQx5ADu7i3bOL1G70,35055
3
- geoai/download.py,sha256=PjGiPP8Q37_3XU1R4aWxu8iDIHVOBpHoJGGdrPDWchw,40892
4
- geoai/extract.py,sha256=GocJufMmrwEWxNBL1J91EXXHL8AKcO8m_lmtUF5AKPw,119102
5
- geoai/geoai.py,sha256=qstOl2BKlvaVndrldUN3v-t--v0qMEFkKKvmH0FhGuc,9321
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=dzoOOI30JK8Moj-ygKLtx-dOW2_b7MIKRXQ__gzfg9o,244383
11
- geoai_py-0.5.5.dist-info/licenses/LICENSE,sha256=vN2L5U7cZ6ZkOHFmc8WiGlsogWsZc5dllMeNxnKVOZg,1070
12
- geoai_py-0.5.5.dist-info/METADATA,sha256=vVU3bp2QtxizALztaVnOZtECUtCzlegOojAgR4zigtQ,6623
13
- geoai_py-0.5.5.dist-info/WHEEL,sha256=MAQBAzGbXNI3bUmkDsiV_duv8i-gcdnLzw7cfUFwqhU,109
14
- geoai_py-0.5.5.dist-info/entry_points.txt,sha256=uGp3Az3HURIsRHP9v-ys0hIbUuBBNUfXv6VbYHIXeg4,41
15
- geoai_py-0.5.5.dist-info/top_level.txt,sha256=1YkCUWu-ii-0qIex7kbwAvfei-gos9ycyDyUCJPNWHY,6
16
- geoai_py-0.5.5.dist-info/RECORD,,