openforis-whisp 3.0.0a2__py3-none-any.whl → 3.0.0a4__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.
openforis_whisp/utils.py CHANGED
@@ -5,6 +5,8 @@ import os
5
5
  import pandas as pd
6
6
  import random
7
7
  import numpy as np
8
+ import logging
9
+ import sys
8
10
 
9
11
  import urllib.request
10
12
  import os
@@ -19,6 +21,23 @@ from shapely.validation import make_valid
19
21
 
20
22
  from .logger import StdoutLogger
21
23
 
24
+ # Configure the "whisp" logger with auto-flush handler for Colab visibility
25
+ _whisp_logger = logging.getLogger("whisp")
26
+ if not _whisp_logger.handlers:
27
+ _handler = logging.StreamHandler(sys.stdout)
28
+ _handler.setLevel(logging.DEBUG)
29
+ _handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
30
+ # Override emit to force flush after each message for Colab
31
+ _original_emit = _handler.emit
32
+
33
+ def _emit_with_flush(record):
34
+ _original_emit(record)
35
+ sys.stdout.flush()
36
+
37
+ _handler.emit = _emit_with_flush
38
+ _whisp_logger.addHandler(_handler)
39
+ _whisp_logger.setLevel(logging.INFO)
40
+ _whisp_logger.propagate = False # Don't propagate to root to avoid duplicates
22
41
 
23
42
  logger = StdoutLogger(__name__)
24
43
 
@@ -238,11 +257,11 @@ def generate_random_polygon(
238
257
  center_point = Point(center_lon, center_lat)
239
258
 
240
259
  # Use buffer with resolution to control vertices for smaller vertex counts
241
- if vertex_count <= 50:
260
+ if vertex_count <= 20:
242
261
  poly = center_point.buffer(radius_degrees, resolution=vertex_count // 4)
243
262
 
244
- # Manual vertex creation for higher vertex counts
245
- if vertex_count > 50:
263
+ # Manual vertex creation for higher vertex counts (sine wave distortions for realistic shapes)
264
+ if vertex_count > 20:
246
265
  angles = np.linspace(0, 2 * math.pi, vertex_count, endpoint=False)
247
266
 
248
267
  base_radius = radius_degrees
@@ -319,6 +338,10 @@ def generate_test_polygons(
319
338
  """
320
339
  Generate synthetic test polygons with exact vertex count control.
321
340
 
341
+ **Deprecated**: This is a legacy alias for generate_random_polygons().
342
+ Use generate_random_polygons() for new code, which provides additional
343
+ features like save_path and seed parameters.
344
+
322
345
  This utility is useful for testing WHISP processing with controlled test data,
323
346
  especially when you need polygons with specific characteristics (area, complexity).
324
347
 
@@ -326,10 +349,6 @@ def generate_test_polygons(
326
349
  ----------
327
350
  bounds : list or ee.Geometry
328
351
  Either a list of [min_lon, min_lat, max_lon, max_lat] or an Earth Engine Geometry.
329
- Examples:
330
- - Simple bounds: [-81.0, -19.3, -31.5, 9.6]
331
- - EE Geometry: ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017').filter(
332
- ee.Filter.eq('country_na', 'Brazil')).first().geometry()
333
352
  num_polygons : int, optional
334
353
  Number of polygons to generate (default: 25)
335
354
  min_area_ha : float, optional
@@ -344,36 +363,153 @@ def generate_test_polygons(
344
363
  Returns
345
364
  -------
346
365
  dict
347
- GeoJSON FeatureCollection with generated polygons. Each feature includes:
348
- - internal_id: Sequential ID starting from 1
349
- - requested_vertices: Number of vertices requested
350
- - actual_vertices: Actual number of vertices created
366
+ GeoJSON FeatureCollection with generated polygons.
367
+
368
+ See Also
369
+ --------
370
+ generate_random_polygons : Recommended replacement with additional options
371
+ """
372
+ return generate_random_polygons(
373
+ bounds=bounds,
374
+ num_polygons=num_polygons,
375
+ min_area_ha=min_area_ha,
376
+ max_area_ha=max_area_ha,
377
+ min_number_vert=min_number_vert,
378
+ max_number_vert=max_number_vert,
379
+ )
380
+
381
+
382
+ def generate_random_features(
383
+ bounds,
384
+ feature_type="point",
385
+ num_features=10,
386
+ seed=None,
387
+ min_area_ha=1,
388
+ max_area_ha=10,
389
+ min_number_vert=10,
390
+ max_number_vert=20,
391
+ multipolygon_pct=20,
392
+ min_parts=2,
393
+ max_parts=4,
394
+ save_path=None,
395
+ return_path=False,
396
+ ):
397
+ """
398
+ Generate random test features (points, polygons, or mixed) with exact geographic bounds control.
399
+
400
+ This utility is useful for testing WHISP processing with random feature data,
401
+ especially when you need features with specific geographic characteristics.
402
+ For polygon features, reuses the production polygon generation logic from
403
+ generate_random_polygon() to ensure consistency.
404
+
405
+ Parameters
406
+ ----------
407
+ bounds : list or ee.Geometry
408
+ Either a list of [min_lon, min_lat, max_lon, max_lat] or an Earth Engine Geometry.
409
+ Examples:
410
+ - Simple bounds: [-81.0, -19.3, -31.5, 9.6]
411
+ - EE Geometry: ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017').filter(
412
+ ee.Filter.eq('country_na', 'Brazil')).first().geometry()
413
+ feature_type : str, optional
414
+ Type of features to generate:
415
+ - 'point': Random points
416
+ - 'polygon': Single-part polygons
417
+ - 'mixed': Blend of single polygons and multipolygons (default: 'point')
418
+ num_features : int, optional
419
+ Number of features to generate (default: 10)
420
+ seed : int or None, optional
421
+ Random seed for reproducibility. None = different each run to avoid GEE caching (default: None)
422
+ min_area_ha : float, optional
423
+ Minimum area in hectares for polygons (default: 1)
424
+ max_area_ha : float, optional
425
+ Maximum area in hectares for polygons (default: 10)
426
+ min_number_vert : int, optional
427
+ Minimum vertices per polygon (default: 10)
428
+ max_number_vert : int, optional
429
+ Maximum vertices per polygon (default: 20)
430
+ multipolygon_pct : float, optional
431
+ Percentage of features that are multipolygons for 'mixed' type (0-100, default: 20)
432
+ min_parts : int, optional
433
+ Minimum polygon parts per multipolygon (default: 2)
434
+ max_parts : int, optional
435
+ Maximum polygon parts per multipolygon (default: 4)
436
+ save_path : str or Path, optional
437
+ Directory path where to save the GeoJSON file. If None, file is not saved (default: None)
438
+ return_path : bool, optional
439
+ If True, return the file path instead of the GeoJSON dict. Only used if save_path is provided (default: False)
440
+
441
+ Returns
442
+ -------
443
+ dict or Path
444
+ If save_path is None: GeoJSON FeatureCollection dict
445
+ If save_path is provided and return_path=False: GeoJSON FeatureCollection dict
446
+ If save_path is provided and return_path=True: Path to saved GeoJSON file
447
+
448
+ All features include:
449
+ - internal_id: Sequential feature ID
450
+ - geometry_type: 'Point', 'Polygon', or 'MultiPolygon' (for distinguishing feature types)
451
+
452
+ For polygons, features include:
453
+ - requested_vertices: Requested vertex count
454
+ - actual_vertices: Actual vertices created
351
455
  - requested_area_ha: Target area in hectares
352
456
  - actual_area_ha: Actual area in hectares
353
457
 
354
458
  Examples
355
459
  --------
356
460
  >>> import openforis_whisp as whisp
357
- >>> import ee
358
461
  >>>
359
- >>> # Using simple bounds (list)
360
- >>> bounds_list = [-81.0, -19.3, -31.5, 9.6]
361
- >>> geojson = whisp.generate_test_polygons(bounds_list, num_polygons=100)
462
+ >>> # Generate random points (in-memory)
463
+ >>> bounds = [-81.0, -19.3, -31.5, 9.6]
464
+ >>> geojson = whisp.generate_random_features(bounds, feature_type='point', num_features=50)
362
465
  >>>
363
- >>> # Using Earth Engine Geometry
364
- >>> brazil = ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017').filter(
365
- ... ee.Filter.eq('country_na', 'Brazil')
366
- ... ).first().geometry()
367
- >>> geojson = whisp.generate_test_polygons(brazil, num_polygons=100,
368
- ... min_area_ha=100, max_area_ha=1000)
466
+ >>> # Generate and save to file
467
+ >>> from pathlib import Path
468
+ >>> save_dir = Path.home() / 'downloads'
469
+ >>> geojson = whisp.generate_random_features(
470
+ ... bounds,
471
+ ... feature_type='polygon',
472
+ ... num_features=100,
473
+ ... save_path=save_dir
474
+ ... )
369
475
  >>>
370
- >>> # Save to file
371
- >>> import json
372
- >>> with open('test_polygons.geojson', 'w') as f:
373
- ... json.dump(geojson, f)
476
+ >>> # Generate mixed geometries and return file path
477
+ >>> file_path = whisp.generate_random_features(
478
+ ... bounds,
479
+ ... feature_type='mixed',
480
+ ... num_features=100,
481
+ ... multipolygon_pct=30,
482
+ ... min_parts=2,
483
+ ... max_parts=5,
484
+ ... save_path=save_dir,
485
+ ... return_path=True
486
+ ... )
374
487
  """
488
+ # Validate feature_type
489
+ if feature_type not in ("point", "polygon", "mixed"):
490
+ raise ValueError(
491
+ f"feature_type must be 'point', 'polygon', or 'mixed' (got {feature_type!r})"
492
+ )
493
+
494
+ # Validate num_features
495
+ if num_features < 1:
496
+ raise ValueError(f"num_features must be at least 1 (got {num_features})")
497
+
498
+ # Validate multipolygon_pct
499
+ if not (0 <= multipolygon_pct <= 100):
500
+ raise ValueError(
501
+ f"multipolygon_pct must be between 0-100 (got {multipolygon_pct})"
502
+ )
375
503
 
376
- # Handle Earth Engine Geometry or simple bounds
504
+ # Validate min/max parts
505
+ if min_parts < 2:
506
+ raise ValueError(f"min_parts must be at least 2 (got {min_parts})")
507
+ if min_parts > max_parts:
508
+ raise ValueError(
509
+ f"min_parts ({min_parts}) cannot be greater than max_parts ({max_parts})"
510
+ )
511
+
512
+ # Extract and validate bounds
377
513
  if hasattr(bounds, "bounds"): # It's an ee.Geometry
378
514
  logger.logger.info("Extracting bounds from Earth Engine Geometry...")
379
515
  try:
@@ -404,7 +540,13 @@ def generate_test_polygons(
404
540
  " - An Earth Engine Geometry (ee.Geometry, ee.Feature.geometry(), etc.)"
405
541
  )
406
542
 
407
- # Validate parameters
543
+ # Validate bounds
544
+ if min_lon >= max_lon:
545
+ raise ValueError(f"min_lon ({min_lon}) must be less than max_lon ({max_lon})")
546
+ if min_lat >= max_lat:
547
+ raise ValueError(f"min_lat ({min_lat}) must be less than max_lat ({max_lat})")
548
+
549
+ # Validate vertex and area parameters
408
550
  if min_number_vert > max_number_vert:
409
551
  raise ValueError(
410
552
  f"min_number_vert ({min_number_vert}) cannot be greater than max_number_vert ({max_number_vert})"
@@ -413,75 +555,321 @@ def generate_test_polygons(
413
555
  raise ValueError(
414
556
  f"min_area_ha ({min_area_ha}) cannot be greater than max_area_ha ({max_area_ha})"
415
557
  )
416
- if num_polygons < 1:
417
- raise ValueError(f"num_polygons must be at least 1 (got {num_polygons})")
418
558
 
419
- logger.logger.info(
420
- f"Generating {num_polygons} test polygons with {min_number_vert}-{max_number_vert} vertices..."
421
- )
559
+ # Set random seed if provided
560
+ if seed is not None:
561
+ random.seed(seed)
562
+ np.random.seed(seed)
563
+
564
+ logger.logger.info(f"Generating {num_features} test {feature_type} features...")
422
565
 
423
566
  features = []
424
567
 
425
- # Pre-generate all random values
426
- vertex_counts = np.random.randint(
427
- min_number_vert, max_number_vert + 1, num_polygons
428
- )
429
- target_areas = np.random.uniform(min_area_ha, max_area_ha, num_polygons)
568
+ if feature_type == "point":
569
+ # Generate point features
570
+ for i in range(num_features):
571
+ lon = random.uniform(min_lon, max_lon)
572
+ lat = random.uniform(min_lat, max_lat)
573
+ feature = {
574
+ "type": "Feature",
575
+ "properties": {
576
+ "internal_id": i + 1,
577
+ "name": f"Point_{i+1}",
578
+ "geometry_type": "Point",
579
+ },
580
+ "geometry": {"type": "Point", "coordinates": [lon, lat]},
581
+ }
582
+ features.append(feature)
583
+
584
+ elif feature_type == "polygon":
585
+ # Generate single polygon features - reuse production polygon generation
586
+ vertex_counts = np.random.randint(
587
+ min_number_vert, max_number_vert + 1, num_features
588
+ )
430
589
 
431
- for i in range(num_polygons):
432
- if i > 0 and i % 250 == 0:
433
- logger.logger.info(
434
- f"Generated {i}/{num_polygons} polygons ({i/num_polygons*100:.0f}%)..."
590
+ for i in range(num_features):
591
+ if i > 0 and i % 250 == 0:
592
+ logger.logger.info(
593
+ f"Generated {i}/{num_features} polygons ({i/num_features*100:.0f}%)..."
594
+ )
595
+
596
+ polygon, actual_area = generate_random_polygon(
597
+ min_lon,
598
+ min_lat,
599
+ max_lon,
600
+ max_lat,
601
+ min_area_ha=min_area_ha,
602
+ max_area_ha=max_area_ha,
603
+ vertex_count=vertex_counts[i],
435
604
  )
436
605
 
437
- requested_vertices = vertex_counts[i]
606
+ actual_vertex_count = len(list(polygon.exterior.coords)) - 1
607
+
608
+ properties = {
609
+ "internal_id": i + 1,
610
+ "geometry_type": "Polygon",
611
+ "requested_vertices": int(vertex_counts[i]),
612
+ "actual_vertices": int(actual_vertex_count),
613
+ "requested_area_ha": round(random.uniform(min_area_ha, max_area_ha), 2),
614
+ "actual_area_ha": round(actual_area, 2),
615
+ }
616
+
617
+ feature = {
618
+ "type": "Feature",
619
+ "properties": properties,
620
+ "geometry": mapping(polygon),
621
+ }
622
+
623
+ features.append(feature)
624
+
625
+ else: # mixed
626
+ # Generate blend of single polygons and multipolygons
627
+ num_multipolygons = int(num_features * multipolygon_pct / 100)
628
+ num_polygons = num_features - num_multipolygons
629
+
630
+ # Generate single polygons
631
+ if num_polygons > 0:
632
+ vertex_counts = np.random.randint(
633
+ min_number_vert, max_number_vert + 1, num_polygons
634
+ )
438
635
 
439
- polygon, actual_area = generate_random_polygon(
440
- min_lon,
441
- min_lat,
442
- max_lon,
443
- max_lat,
444
- min_area_ha=target_areas[i] * 0.9,
445
- max_area_ha=target_areas[i] * 1.1,
446
- vertex_count=requested_vertices,
447
- )
636
+ for i in range(num_polygons):
637
+ if i > 0 and i % 250 == 0:
638
+ logger.logger.info(
639
+ f"Generated {i}/{num_features} features ({i/num_features*100:.0f}%)..."
640
+ )
641
+
642
+ polygon, actual_area = generate_random_polygon(
643
+ min_lon,
644
+ min_lat,
645
+ max_lon,
646
+ max_lat,
647
+ min_area_ha=min_area_ha,
648
+ max_area_ha=max_area_ha,
649
+ vertex_count=vertex_counts[i],
650
+ )
651
+
652
+ actual_vertex_count = len(list(polygon.exterior.coords)) - 1
653
+
654
+ properties = {
655
+ "internal_id": i + 1,
656
+ "geometry_type": "Polygon",
657
+ "requested_vertices": int(vertex_counts[i]),
658
+ "actual_vertices": int(actual_vertex_count),
659
+ "requested_area_ha": round(
660
+ random.uniform(min_area_ha, max_area_ha), 2
661
+ ),
662
+ "actual_area_ha": round(actual_area, 2),
663
+ }
664
+
665
+ feature = {
666
+ "type": "Feature",
667
+ "properties": properties,
668
+ "geometry": mapping(polygon),
669
+ }
670
+
671
+ features.append(feature)
672
+
673
+ # Generate multipolygons
674
+ if num_multipolygons > 0:
675
+ for i in range(num_multipolygons):
676
+ if i > 0 and i % 50 == 0:
677
+ logger.logger.info(
678
+ f"Generated {num_polygons + i}/{num_features} features ({(num_polygons + i)/num_features*100:.0f}%)..."
679
+ )
680
+
681
+ num_parts = random.randint(min_parts, max_parts)
682
+ polygon_parts = []
683
+ total_area_ha = 0
684
+
685
+ # Generate a target total area for this multipolygon
686
+ target_total_area = random.uniform(min_area_ha, max_area_ha)
687
+ # Divide target area among parts (with some randomness)
688
+ part_areas = []
689
+ remaining_area = target_total_area
690
+ for part_idx in range(num_parts):
691
+ if part_idx == num_parts - 1:
692
+ # Last part gets all remaining area
693
+ part_area = remaining_area
694
+ else:
695
+ # Distribute remaining area with randomness
696
+ max_for_part = remaining_area * 0.8
697
+ part_area = random.uniform(remaining_area * 0.3, max_for_part)
698
+ part_areas.append(part_area)
699
+ remaining_area -= part_area
700
+
701
+ for part, area_budget in enumerate(part_areas):
702
+ # Generate each part in the full bounds (randomization spreads them naturally)
703
+ polygon, actual_area = generate_random_polygon(
704
+ min_lon,
705
+ min_lat,
706
+ max_lon,
707
+ max_lat,
708
+ min_area_ha=area_budget * 0.8,
709
+ max_area_ha=area_budget * 1.2,
710
+ vertex_count=random.randint(min_number_vert, max_number_vert),
711
+ )
712
+
713
+ polygon_parts.append([list(polygon.exterior.coords)])
714
+ total_area_ha += actual_area
715
+
716
+ properties = {
717
+ "internal_id": num_polygons + i + 1,
718
+ "geometry_type": "MultiPolygon",
719
+ "num_parts": num_parts,
720
+ "total_area_ha": round(total_area_ha, 2),
721
+ }
722
+
723
+ feature = {
724
+ "type": "Feature",
725
+ "properties": properties,
726
+ "geometry": {
727
+ "type": "MultiPolygon",
728
+ "coordinates": polygon_parts,
729
+ },
730
+ }
731
+
732
+ features.append(feature)
448
733
 
449
- actual_vertex_count = len(list(polygon.exterior.coords)) - 1
734
+ geojson = {"type": "FeatureCollection", "features": features}
450
735
 
451
- properties = {
452
- "internal_id": i + 1,
453
- "requested_vertices": int(requested_vertices),
454
- "actual_vertices": int(actual_vertex_count),
455
- "requested_area_ha": round(target_areas[i], 2),
456
- "actual_area_ha": round(actual_area, 2),
457
- }
736
+ logger.logger.info(f"Generated {num_features} test {feature_type} features!")
458
737
 
459
- feature = {
460
- "type": "Feature",
461
- "properties": properties,
462
- "geometry": mapping(polygon),
463
- }
738
+ # Save to file if save_path is provided
739
+ if save_path is not None:
740
+ import json
464
741
 
465
- features.append(feature)
742
+ save_path = Path(save_path)
743
+ save_path.mkdir(parents=True, exist_ok=True)
466
744
 
467
- logger.logger.info(f"Generated {num_polygons} polygons!")
745
+ output_file = save_path / f"{feature_type}s_{num_features}_features.geojson"
468
746
 
469
- # Print summary statistics
470
- actual_vertex_counts = [f["properties"]["actual_vertices"] for f in features]
471
- requested_vertex_counts = [f["properties"]["requested_vertices"] for f in features]
747
+ with open(output_file, "w") as f:
748
+ json.dump(geojson, f, indent=2)
472
749
 
473
- logger.logger.info(
474
- f"Vertex count - Requested: {min(requested_vertex_counts)}-{max(requested_vertex_counts)}, "
475
- f"Actual: {min(actual_vertex_counts)}-{max(actual_vertex_counts)}"
476
- )
750
+ logger.logger.info(f"GeoJSON saved to: {output_file}")
477
751
 
478
- actual_area_counts = [f["properties"]["actual_area_ha"] for f in features]
479
- requested_area_counts = [f["properties"]["requested_area_ha"] for f in features]
752
+ if return_path:
753
+ return output_file
480
754
 
481
- logger.logger.info(
482
- f"Area (ha) - Requested: {min(requested_area_counts):.1f}-{max(requested_area_counts):.1f}, "
483
- f"Actual: {min(actual_area_counts):.1f}-{max(actual_area_counts):.1f}"
755
+ return geojson
756
+
757
+
758
+ def generate_random_points(bounds, num_features=10, seed=None, save_path=None):
759
+ """
760
+ Generate random test points with optional save.
761
+
762
+ Simplified wrapper around generate_random_features for point-only generation.
763
+
764
+ Parameters
765
+ ----------
766
+ bounds : list or ee.Geometry
767
+ Either a list of [min_lon, min_lat, max_lon, max_lat] or an Earth Engine Geometry.
768
+ num_features : int, optional
769
+ Number of points to generate (default: 10)
770
+ seed : int or None, optional
771
+ Random seed for reproducibility (default: None)
772
+ save_path : str or Path, optional
773
+ Directory path where to save the GeoJSON file. If None, file is not saved (default: None)
774
+
775
+ Returns
776
+ -------
777
+ dict or Path
778
+ If save_path is None: GeoJSON FeatureCollection dict
779
+ If save_path is provided: Path to saved GeoJSON file
780
+
781
+ Examples
782
+ --------
783
+ >>> import openforis_whisp as whisp
784
+ >>>
785
+ >>> bounds = [-81.0, -19.3, -31.5, 9.6]
786
+ >>> geojson = whisp.generate_random_points(bounds, num_features=100)
787
+ >>>
788
+ >>> # Generate and save
789
+ >>> from pathlib import Path
790
+ >>> file_path = whisp.generate_random_points(
791
+ ... bounds,
792
+ ... num_features=500,
793
+ ... save_path=Path.home() / 'downloads'
794
+ ... )
795
+ """
796
+ return generate_random_features(
797
+ bounds=bounds,
798
+ feature_type="point",
799
+ num_features=num_features,
800
+ seed=seed,
801
+ save_path=save_path,
802
+ return_path=True if save_path else False,
484
803
  )
485
804
 
486
- geojson = {"type": "FeatureCollection", "features": features}
487
- return geojson
805
+
806
+ def generate_random_polygons(
807
+ bounds,
808
+ num_polygons=25,
809
+ min_area_ha=1,
810
+ max_area_ha=10,
811
+ min_number_vert=10,
812
+ max_number_vert=20,
813
+ seed=None,
814
+ save_path=None,
815
+ ):
816
+ """
817
+ Generate random test polygons with optional save.
818
+
819
+ Wrapper around generate_random_features for polygon-only generation.
820
+ Uses the same production-quality polygon generation as generate_test_polygons.
821
+
822
+ Parameters
823
+ ----------
824
+ bounds : list or ee.Geometry
825
+ Either a list of [min_lon, min_lat, max_lon, max_lon] or an Earth Engine Geometry.
826
+ num_polygons : int, optional
827
+ Number of polygons to generate (default: 25)
828
+ min_area_ha : float, optional
829
+ Minimum area in hectares (default: 1)
830
+ max_area_ha : float, optional
831
+ Maximum area in hectares (default: 10)
832
+ min_number_vert : int, optional
833
+ Minimum number of vertices per polygon (default: 10)
834
+ max_number_vert : int, optional
835
+ Maximum number of vertices per polygon (default: 20)
836
+ seed : int or None, optional
837
+ Random seed for reproducibility (default: None)
838
+ save_path : str or Path, optional
839
+ Directory path where to save the GeoJSON file. If None, file is not saved (default: None)
840
+
841
+ Returns
842
+ -------
843
+ dict or Path
844
+ If save_path is None: GeoJSON FeatureCollection dict
845
+ If save_path is provided: Path to saved GeoJSON file
846
+
847
+ Examples
848
+ --------
849
+ >>> import openforis_whisp as whisp
850
+ >>>
851
+ >>> bounds = [-81.0, -19.3, -31.5, 9.6]
852
+ >>> geojson = whisp.generate_random_polygons(bounds, num_polygons=50)
853
+ >>>
854
+ >>> # Generate and save
855
+ >>> from pathlib import Path
856
+ >>> file_path = whisp.generate_random_polygons(
857
+ ... bounds,
858
+ ... num_polygons=100,
859
+ ... min_area_ha=5,
860
+ ... max_area_ha=50,
861
+ ... save_path=Path.home() / 'downloads'
862
+ ... )
863
+ """
864
+ return generate_random_features(
865
+ bounds=bounds,
866
+ feature_type="polygon",
867
+ num_features=num_polygons,
868
+ seed=seed,
869
+ min_area_ha=min_area_ha,
870
+ max_area_ha=max_area_ha,
871
+ min_number_vert=min_number_vert,
872
+ max_number_vert=max_number_vert,
873
+ save_path=save_path,
874
+ return_path=True if save_path else False,
875
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: openforis-whisp
3
- Version: 3.0.0a2
3
+ Version: 3.0.0a4
4
4
  Summary: Whisp (What is in that plot) is an open-source solution which helps to produce relevant forest monitoring information and support compliance with deforestation-related regulations.
5
5
  License: MIT
6
6
  Keywords: whisp,geospatial,data-processing
@@ -0,0 +1,20 @@
1
+ openforis_whisp/__init__.py,sha256=5zJK84LYnlslxSajdCz6ZIYxRS4xgN3sGxSD6_GXEHs,3547
2
+ openforis_whisp/advanced_stats.py,sha256=FC1YasSZ93jplF1qBgDopzBIsO2ueXnidomQU3rpP_Q,100006
3
+ openforis_whisp/data_checks.py,sha256=ErIKGbCa3R8eYP0sVoAl-ZUl607W1QrG0Jr2SIVgm2I,34056
4
+ openforis_whisp/data_conversion.py,sha256=L2IsiUyQUt3aHgSYGbIhgPGwM7eyS3nLVEoNO9YqQeM,21888
5
+ openforis_whisp/datasets.py,sha256=F1WxXc93mxxmN-WHa0bf-XX-FloSQyEBJKmnrQEHYn8,53855
6
+ openforis_whisp/logger.py,sha256=gFkRTwJDJKIBWcHDOK74Uln3JM7fAybURo7pQpGL790,3395
7
+ openforis_whisp/parameters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ openforis_whisp/parameters/config_runtime.py,sha256=NOo39MAi60XCwEx5pwkS0EHKJBh0XY1q06y4j0HAABg,1421
9
+ openforis_whisp/parameters/lookup_context_and_metadata.csv,sha256=KgK0ik_Gd4t_Nq5cUkGPT4ZFZVO93HWSG82jRrOukt4,1298
10
+ openforis_whisp/parameters/lookup_gaul1_admin.py,sha256=cQr5liRdXi85QieTxrz4VAkn0COvRCp82ZV0dYFWOio,474980
11
+ openforis_whisp/parameters/lookup_gee_datasets.csv,sha256=7KdnFocEgbZO5m8JmWQchzZTurg9rJ96y17z8UyLtI0,17537
12
+ openforis_whisp/pd_schemas.py,sha256=0z-oPmYIDUIn7mNY41W_uUpmTwjoR7e254mOCoHVsOg,2878
13
+ openforis_whisp/reformat.py,sha256=gvhIa-_kTT5BSO8LuVmJ1TQcf_NwheskXboFM9e0KJY,32758
14
+ openforis_whisp/risk.py,sha256=d_Di5XB8BnHdVXG56xdHTcpB4-CIF5vo2ZRMQRG7Pek,34420
15
+ openforis_whisp/stats.py,sha256=pTSYs77ISRBOIglRpq4SUx3lKRkrUZOKROLRX5IP9IY,63941
16
+ openforis_whisp/utils.py,sha256=AISWF-MpfFdYkhd6bei4BViw2Iag20mmq61ykrF9YTk,31287
17
+ openforis_whisp-3.0.0a4.dist-info/LICENSE,sha256=nqyqICO95iw_iwzP1t_IIAf7ZX3DPbL_M9WyQfh2q1k,1085
18
+ openforis_whisp-3.0.0a4.dist-info/METADATA,sha256=ak2Dw632lgOtXEXkl5-haYK7vF3hPaJ6IkaRRJRdH0Y,16684
19
+ openforis_whisp-3.0.0a4.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
20
+ openforis_whisp-3.0.0a4.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- openforis_whisp/__init__.py,sha256=-r_9LFxbV6d-o4s0_huhaXxve6GIzCwl3pXKuJo6ixE,3663
2
- openforis_whisp/advanced_stats.py,sha256=xrwKHG-c44_UkFha7TFgf71mo9UMw5ZZL3XQTPF5luM,92681
3
- openforis_whisp/data_checks.py,sha256=KwgD72FA_n7joiJadGRpzntd2sLo0aqGNbOjRkB8iQI,32293
4
- openforis_whisp/data_conversion.py,sha256=L2IsiUyQUt3aHgSYGbIhgPGwM7eyS3nLVEoNO9YqQeM,21888
5
- openforis_whisp/datasets.py,sha256=aGJy0OYN4d0nsH3_IOYlHl-WCB7KFwZwMJ-dBi5Hc5Y,53470
6
- openforis_whisp/logger.py,sha256=9M6_3mdpoiWfC-pDwM9vKmB2l5Gul6Rb5rNTNh-_nzs,3054
7
- openforis_whisp/parameters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- openforis_whisp/parameters/config_runtime.py,sha256=NOo39MAi60XCwEx5pwkS0EHKJBh0XY1q06y4j0HAABg,1421
9
- openforis_whisp/parameters/lookup_context_and_metadata.csv,sha256=KgK0ik_Gd4t_Nq5cUkGPT4ZFZVO93HWSG82jRrOukt4,1298
10
- openforis_whisp/parameters/lookup_gaul1_admin.py,sha256=cQr5liRdXi85QieTxrz4VAkn0COvRCp82ZV0dYFWOio,474980
11
- openforis_whisp/parameters/lookup_gee_datasets.csv,sha256=UDvZrQsL5rXJn6CW6P3wofUrPLRmUFZWt6ETbXaxBMs,17454
12
- openforis_whisp/pd_schemas.py,sha256=W_ocS773LHfc05dJqvWRa-bRdX0wKFoNp0lMxgFx94Y,2681
13
- openforis_whisp/reformat.py,sha256=mIooJ3zfSTDY3_Mx3OAW4jpfQ72q3zasG9tl58PdfN4,33729
14
- openforis_whisp/risk.py,sha256=d_Di5XB8BnHdVXG56xdHTcpB4-CIF5vo2ZRMQRG7Pek,34420
15
- openforis_whisp/stats.py,sha256=dCQXx6KKEV99owqyPURk6CL97kQQARjetFrIz1ZbIvs,65725
16
- openforis_whisp/utils.py,sha256=5HHtbK62Swn4-jnlSe1Jc-hVnJhLKMuDW0_ayHY7mIg,17130
17
- openforis_whisp-3.0.0a2.dist-info/LICENSE,sha256=nqyqICO95iw_iwzP1t_IIAf7ZX3DPbL_M9WyQfh2q1k,1085
18
- openforis_whisp-3.0.0a2.dist-info/METADATA,sha256=wG4vc7B-f0JXmNkTUh4wJ-H0KPpbgyU9OfMwGewZq_A,16684
19
- openforis_whisp-3.0.0a2.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
20
- openforis_whisp-3.0.0a2.dist-info/RECORD,,