openforis-whisp 3.0.0a1__py3-none-any.whl → 3.0.0a3__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/__init__.py +7 -7
- openforis_whisp/advanced_stats.py +400 -93
- openforis_whisp/data_checks.py +178 -15
- openforis_whisp/data_conversion.py +154 -59
- openforis_whisp/reformat.py +2 -29
- openforis_whisp/stats.py +15 -45
- openforis_whisp/utils.py +449 -80
- {openforis_whisp-3.0.0a1.dist-info → openforis_whisp-3.0.0a3.dist-info}/METADATA +1 -1
- {openforis_whisp-3.0.0a1.dist-info → openforis_whisp-3.0.0a3.dist-info}/RECORD +11 -11
- {openforis_whisp-3.0.0a1.dist-info → openforis_whisp-3.0.0a3.dist-info}/LICENSE +0 -0
- {openforis_whisp-3.0.0a1.dist-info → openforis_whisp-3.0.0a3.dist-info}/WHEEL +0 -0
openforis_whisp/utils.py
CHANGED
|
@@ -238,11 +238,11 @@ def generate_random_polygon(
|
|
|
238
238
|
center_point = Point(center_lon, center_lat)
|
|
239
239
|
|
|
240
240
|
# Use buffer with resolution to control vertices for smaller vertex counts
|
|
241
|
-
if vertex_count <=
|
|
241
|
+
if vertex_count <= 20:
|
|
242
242
|
poly = center_point.buffer(radius_degrees, resolution=vertex_count // 4)
|
|
243
243
|
|
|
244
|
-
# Manual vertex creation for higher vertex counts
|
|
245
|
-
if vertex_count >
|
|
244
|
+
# Manual vertex creation for higher vertex counts (sine wave distortions for realistic shapes)
|
|
245
|
+
if vertex_count > 20:
|
|
246
246
|
angles = np.linspace(0, 2 * math.pi, vertex_count, endpoint=False)
|
|
247
247
|
|
|
248
248
|
base_radius = radius_degrees
|
|
@@ -319,6 +319,10 @@ def generate_test_polygons(
|
|
|
319
319
|
"""
|
|
320
320
|
Generate synthetic test polygons with exact vertex count control.
|
|
321
321
|
|
|
322
|
+
**Deprecated**: This is a legacy alias for generate_random_polygons().
|
|
323
|
+
Use generate_random_polygons() for new code, which provides additional
|
|
324
|
+
features like save_path and seed parameters.
|
|
325
|
+
|
|
322
326
|
This utility is useful for testing WHISP processing with controlled test data,
|
|
323
327
|
especially when you need polygons with specific characteristics (area, complexity).
|
|
324
328
|
|
|
@@ -326,10 +330,6 @@ def generate_test_polygons(
|
|
|
326
330
|
----------
|
|
327
331
|
bounds : list or ee.Geometry
|
|
328
332
|
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
333
|
num_polygons : int, optional
|
|
334
334
|
Number of polygons to generate (default: 25)
|
|
335
335
|
min_area_ha : float, optional
|
|
@@ -344,36 +344,153 @@ def generate_test_polygons(
|
|
|
344
344
|
Returns
|
|
345
345
|
-------
|
|
346
346
|
dict
|
|
347
|
-
GeoJSON FeatureCollection with generated polygons.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
347
|
+
GeoJSON FeatureCollection with generated polygons.
|
|
348
|
+
|
|
349
|
+
See Also
|
|
350
|
+
--------
|
|
351
|
+
generate_random_polygons : Recommended replacement with additional options
|
|
352
|
+
"""
|
|
353
|
+
return generate_random_polygons(
|
|
354
|
+
bounds=bounds,
|
|
355
|
+
num_polygons=num_polygons,
|
|
356
|
+
min_area_ha=min_area_ha,
|
|
357
|
+
max_area_ha=max_area_ha,
|
|
358
|
+
min_number_vert=min_number_vert,
|
|
359
|
+
max_number_vert=max_number_vert,
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def generate_random_features(
|
|
364
|
+
bounds,
|
|
365
|
+
feature_type="point",
|
|
366
|
+
num_features=10,
|
|
367
|
+
seed=None,
|
|
368
|
+
min_area_ha=1,
|
|
369
|
+
max_area_ha=10,
|
|
370
|
+
min_number_vert=10,
|
|
371
|
+
max_number_vert=20,
|
|
372
|
+
multipolygon_pct=20,
|
|
373
|
+
min_parts=2,
|
|
374
|
+
max_parts=4,
|
|
375
|
+
save_path=None,
|
|
376
|
+
return_path=False,
|
|
377
|
+
):
|
|
378
|
+
"""
|
|
379
|
+
Generate random test features (points, polygons, or mixed) with exact geographic bounds control.
|
|
380
|
+
|
|
381
|
+
This utility is useful for testing WHISP processing with random feature data,
|
|
382
|
+
especially when you need features with specific geographic characteristics.
|
|
383
|
+
For polygon features, reuses the production polygon generation logic from
|
|
384
|
+
generate_random_polygon() to ensure consistency.
|
|
385
|
+
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
bounds : list or ee.Geometry
|
|
389
|
+
Either a list of [min_lon, min_lat, max_lon, max_lat] or an Earth Engine Geometry.
|
|
390
|
+
Examples:
|
|
391
|
+
- Simple bounds: [-81.0, -19.3, -31.5, 9.6]
|
|
392
|
+
- EE Geometry: ee.FeatureCollection('USDOS/LSIB_SIMPLE/2017').filter(
|
|
393
|
+
ee.Filter.eq('country_na', 'Brazil')).first().geometry()
|
|
394
|
+
feature_type : str, optional
|
|
395
|
+
Type of features to generate:
|
|
396
|
+
- 'point': Random points
|
|
397
|
+
- 'polygon': Single-part polygons
|
|
398
|
+
- 'mixed': Blend of single polygons and multipolygons (default: 'point')
|
|
399
|
+
num_features : int, optional
|
|
400
|
+
Number of features to generate (default: 10)
|
|
401
|
+
seed : int or None, optional
|
|
402
|
+
Random seed for reproducibility. None = different each run to avoid GEE caching (default: None)
|
|
403
|
+
min_area_ha : float, optional
|
|
404
|
+
Minimum area in hectares for polygons (default: 1)
|
|
405
|
+
max_area_ha : float, optional
|
|
406
|
+
Maximum area in hectares for polygons (default: 10)
|
|
407
|
+
min_number_vert : int, optional
|
|
408
|
+
Minimum vertices per polygon (default: 10)
|
|
409
|
+
max_number_vert : int, optional
|
|
410
|
+
Maximum vertices per polygon (default: 20)
|
|
411
|
+
multipolygon_pct : float, optional
|
|
412
|
+
Percentage of features that are multipolygons for 'mixed' type (0-100, default: 20)
|
|
413
|
+
min_parts : int, optional
|
|
414
|
+
Minimum polygon parts per multipolygon (default: 2)
|
|
415
|
+
max_parts : int, optional
|
|
416
|
+
Maximum polygon parts per multipolygon (default: 4)
|
|
417
|
+
save_path : str or Path, optional
|
|
418
|
+
Directory path where to save the GeoJSON file. If None, file is not saved (default: None)
|
|
419
|
+
return_path : bool, optional
|
|
420
|
+
If True, return the file path instead of the GeoJSON dict. Only used if save_path is provided (default: False)
|
|
421
|
+
|
|
422
|
+
Returns
|
|
423
|
+
-------
|
|
424
|
+
dict or Path
|
|
425
|
+
If save_path is None: GeoJSON FeatureCollection dict
|
|
426
|
+
If save_path is provided and return_path=False: GeoJSON FeatureCollection dict
|
|
427
|
+
If save_path is provided and return_path=True: Path to saved GeoJSON file
|
|
428
|
+
|
|
429
|
+
All features include:
|
|
430
|
+
- internal_id: Sequential feature ID
|
|
431
|
+
- geometry_type: 'Point', 'Polygon', or 'MultiPolygon' (for distinguishing feature types)
|
|
432
|
+
|
|
433
|
+
For polygons, features include:
|
|
434
|
+
- requested_vertices: Requested vertex count
|
|
435
|
+
- actual_vertices: Actual vertices created
|
|
351
436
|
- requested_area_ha: Target area in hectares
|
|
352
437
|
- actual_area_ha: Actual area in hectares
|
|
353
438
|
|
|
354
439
|
Examples
|
|
355
440
|
--------
|
|
356
441
|
>>> import openforis_whisp as whisp
|
|
357
|
-
>>> import ee
|
|
358
442
|
>>>
|
|
359
|
-
>>> #
|
|
360
|
-
>>>
|
|
361
|
-
>>> geojson = whisp.
|
|
443
|
+
>>> # Generate random points (in-memory)
|
|
444
|
+
>>> bounds = [-81.0, -19.3, -31.5, 9.6]
|
|
445
|
+
>>> geojson = whisp.generate_random_features(bounds, feature_type='point', num_features=50)
|
|
362
446
|
>>>
|
|
363
|
-
>>> #
|
|
364
|
-
>>>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
...
|
|
447
|
+
>>> # Generate and save to file
|
|
448
|
+
>>> from pathlib import Path
|
|
449
|
+
>>> save_dir = Path.home() / 'downloads'
|
|
450
|
+
>>> geojson = whisp.generate_random_features(
|
|
451
|
+
... bounds,
|
|
452
|
+
... feature_type='polygon',
|
|
453
|
+
... num_features=100,
|
|
454
|
+
... save_path=save_dir
|
|
455
|
+
... )
|
|
369
456
|
>>>
|
|
370
|
-
>>> #
|
|
371
|
-
>>>
|
|
372
|
-
|
|
373
|
-
...
|
|
457
|
+
>>> # Generate mixed geometries and return file path
|
|
458
|
+
>>> file_path = whisp.generate_random_features(
|
|
459
|
+
... bounds,
|
|
460
|
+
... feature_type='mixed',
|
|
461
|
+
... num_features=100,
|
|
462
|
+
... multipolygon_pct=30,
|
|
463
|
+
... min_parts=2,
|
|
464
|
+
... max_parts=5,
|
|
465
|
+
... save_path=save_dir,
|
|
466
|
+
... return_path=True
|
|
467
|
+
... )
|
|
374
468
|
"""
|
|
469
|
+
# Validate feature_type
|
|
470
|
+
if feature_type not in ("point", "polygon", "mixed"):
|
|
471
|
+
raise ValueError(
|
|
472
|
+
f"feature_type must be 'point', 'polygon', or 'mixed' (got {feature_type!r})"
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
# Validate num_features
|
|
476
|
+
if num_features < 1:
|
|
477
|
+
raise ValueError(f"num_features must be at least 1 (got {num_features})")
|
|
478
|
+
|
|
479
|
+
# Validate multipolygon_pct
|
|
480
|
+
if not (0 <= multipolygon_pct <= 100):
|
|
481
|
+
raise ValueError(
|
|
482
|
+
f"multipolygon_pct must be between 0-100 (got {multipolygon_pct})"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Validate min/max parts
|
|
486
|
+
if min_parts < 2:
|
|
487
|
+
raise ValueError(f"min_parts must be at least 2 (got {min_parts})")
|
|
488
|
+
if min_parts > max_parts:
|
|
489
|
+
raise ValueError(
|
|
490
|
+
f"min_parts ({min_parts}) cannot be greater than max_parts ({max_parts})"
|
|
491
|
+
)
|
|
375
492
|
|
|
376
|
-
#
|
|
493
|
+
# Extract and validate bounds
|
|
377
494
|
if hasattr(bounds, "bounds"): # It's an ee.Geometry
|
|
378
495
|
logger.logger.info("Extracting bounds from Earth Engine Geometry...")
|
|
379
496
|
try:
|
|
@@ -404,7 +521,13 @@ def generate_test_polygons(
|
|
|
404
521
|
" - An Earth Engine Geometry (ee.Geometry, ee.Feature.geometry(), etc.)"
|
|
405
522
|
)
|
|
406
523
|
|
|
407
|
-
# Validate
|
|
524
|
+
# Validate bounds
|
|
525
|
+
if min_lon >= max_lon:
|
|
526
|
+
raise ValueError(f"min_lon ({min_lon}) must be less than max_lon ({max_lon})")
|
|
527
|
+
if min_lat >= max_lat:
|
|
528
|
+
raise ValueError(f"min_lat ({min_lat}) must be less than max_lat ({max_lat})")
|
|
529
|
+
|
|
530
|
+
# Validate vertex and area parameters
|
|
408
531
|
if min_number_vert > max_number_vert:
|
|
409
532
|
raise ValueError(
|
|
410
533
|
f"min_number_vert ({min_number_vert}) cannot be greater than max_number_vert ({max_number_vert})"
|
|
@@ -413,75 +536,321 @@ def generate_test_polygons(
|
|
|
413
536
|
raise ValueError(
|
|
414
537
|
f"min_area_ha ({min_area_ha}) cannot be greater than max_area_ha ({max_area_ha})"
|
|
415
538
|
)
|
|
416
|
-
if num_polygons < 1:
|
|
417
|
-
raise ValueError(f"num_polygons must be at least 1 (got {num_polygons})")
|
|
418
539
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
540
|
+
# Set random seed if provided
|
|
541
|
+
if seed is not None:
|
|
542
|
+
random.seed(seed)
|
|
543
|
+
np.random.seed(seed)
|
|
544
|
+
|
|
545
|
+
logger.logger.info(f"Generating {num_features} test {feature_type} features...")
|
|
422
546
|
|
|
423
547
|
features = []
|
|
424
548
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
549
|
+
if feature_type == "point":
|
|
550
|
+
# Generate point features
|
|
551
|
+
for i in range(num_features):
|
|
552
|
+
lon = random.uniform(min_lon, max_lon)
|
|
553
|
+
lat = random.uniform(min_lat, max_lat)
|
|
554
|
+
feature = {
|
|
555
|
+
"type": "Feature",
|
|
556
|
+
"properties": {
|
|
557
|
+
"internal_id": i + 1,
|
|
558
|
+
"name": f"Point_{i+1}",
|
|
559
|
+
"geometry_type": "Point",
|
|
560
|
+
},
|
|
561
|
+
"geometry": {"type": "Point", "coordinates": [lon, lat]},
|
|
562
|
+
}
|
|
563
|
+
features.append(feature)
|
|
564
|
+
|
|
565
|
+
elif feature_type == "polygon":
|
|
566
|
+
# Generate single polygon features - reuse production polygon generation
|
|
567
|
+
vertex_counts = np.random.randint(
|
|
568
|
+
min_number_vert, max_number_vert + 1, num_features
|
|
569
|
+
)
|
|
430
570
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
571
|
+
for i in range(num_features):
|
|
572
|
+
if i > 0 and i % 250 == 0:
|
|
573
|
+
logger.logger.info(
|
|
574
|
+
f"Generated {i}/{num_features} polygons ({i/num_features*100:.0f}%)..."
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
polygon, actual_area = generate_random_polygon(
|
|
578
|
+
min_lon,
|
|
579
|
+
min_lat,
|
|
580
|
+
max_lon,
|
|
581
|
+
max_lat,
|
|
582
|
+
min_area_ha=min_area_ha,
|
|
583
|
+
max_area_ha=max_area_ha,
|
|
584
|
+
vertex_count=vertex_counts[i],
|
|
435
585
|
)
|
|
436
586
|
|
|
437
|
-
|
|
587
|
+
actual_vertex_count = len(list(polygon.exterior.coords)) - 1
|
|
588
|
+
|
|
589
|
+
properties = {
|
|
590
|
+
"internal_id": i + 1,
|
|
591
|
+
"geometry_type": "Polygon",
|
|
592
|
+
"requested_vertices": int(vertex_counts[i]),
|
|
593
|
+
"actual_vertices": int(actual_vertex_count),
|
|
594
|
+
"requested_area_ha": round(random.uniform(min_area_ha, max_area_ha), 2),
|
|
595
|
+
"actual_area_ha": round(actual_area, 2),
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
feature = {
|
|
599
|
+
"type": "Feature",
|
|
600
|
+
"properties": properties,
|
|
601
|
+
"geometry": mapping(polygon),
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
features.append(feature)
|
|
605
|
+
|
|
606
|
+
else: # mixed
|
|
607
|
+
# Generate blend of single polygons and multipolygons
|
|
608
|
+
num_multipolygons = int(num_features * multipolygon_pct / 100)
|
|
609
|
+
num_polygons = num_features - num_multipolygons
|
|
610
|
+
|
|
611
|
+
# Generate single polygons
|
|
612
|
+
if num_polygons > 0:
|
|
613
|
+
vertex_counts = np.random.randint(
|
|
614
|
+
min_number_vert, max_number_vert + 1, num_polygons
|
|
615
|
+
)
|
|
438
616
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
617
|
+
for i in range(num_polygons):
|
|
618
|
+
if i > 0 and i % 250 == 0:
|
|
619
|
+
logger.logger.info(
|
|
620
|
+
f"Generated {i}/{num_features} features ({i/num_features*100:.0f}%)..."
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
polygon, actual_area = generate_random_polygon(
|
|
624
|
+
min_lon,
|
|
625
|
+
min_lat,
|
|
626
|
+
max_lon,
|
|
627
|
+
max_lat,
|
|
628
|
+
min_area_ha=min_area_ha,
|
|
629
|
+
max_area_ha=max_area_ha,
|
|
630
|
+
vertex_count=vertex_counts[i],
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
actual_vertex_count = len(list(polygon.exterior.coords)) - 1
|
|
634
|
+
|
|
635
|
+
properties = {
|
|
636
|
+
"internal_id": i + 1,
|
|
637
|
+
"geometry_type": "Polygon",
|
|
638
|
+
"requested_vertices": int(vertex_counts[i]),
|
|
639
|
+
"actual_vertices": int(actual_vertex_count),
|
|
640
|
+
"requested_area_ha": round(
|
|
641
|
+
random.uniform(min_area_ha, max_area_ha), 2
|
|
642
|
+
),
|
|
643
|
+
"actual_area_ha": round(actual_area, 2),
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
feature = {
|
|
647
|
+
"type": "Feature",
|
|
648
|
+
"properties": properties,
|
|
649
|
+
"geometry": mapping(polygon),
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
features.append(feature)
|
|
653
|
+
|
|
654
|
+
# Generate multipolygons
|
|
655
|
+
if num_multipolygons > 0:
|
|
656
|
+
for i in range(num_multipolygons):
|
|
657
|
+
if i > 0 and i % 50 == 0:
|
|
658
|
+
logger.logger.info(
|
|
659
|
+
f"Generated {num_polygons + i}/{num_features} features ({(num_polygons + i)/num_features*100:.0f}%)..."
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
num_parts = random.randint(min_parts, max_parts)
|
|
663
|
+
polygon_parts = []
|
|
664
|
+
total_area_ha = 0
|
|
665
|
+
|
|
666
|
+
# Generate a target total area for this multipolygon
|
|
667
|
+
target_total_area = random.uniform(min_area_ha, max_area_ha)
|
|
668
|
+
# Divide target area among parts (with some randomness)
|
|
669
|
+
part_areas = []
|
|
670
|
+
remaining_area = target_total_area
|
|
671
|
+
for part_idx in range(num_parts):
|
|
672
|
+
if part_idx == num_parts - 1:
|
|
673
|
+
# Last part gets all remaining area
|
|
674
|
+
part_area = remaining_area
|
|
675
|
+
else:
|
|
676
|
+
# Distribute remaining area with randomness
|
|
677
|
+
max_for_part = remaining_area * 0.8
|
|
678
|
+
part_area = random.uniform(remaining_area * 0.3, max_for_part)
|
|
679
|
+
part_areas.append(part_area)
|
|
680
|
+
remaining_area -= part_area
|
|
681
|
+
|
|
682
|
+
for part, area_budget in enumerate(part_areas):
|
|
683
|
+
# Generate each part in the full bounds (randomization spreads them naturally)
|
|
684
|
+
polygon, actual_area = generate_random_polygon(
|
|
685
|
+
min_lon,
|
|
686
|
+
min_lat,
|
|
687
|
+
max_lon,
|
|
688
|
+
max_lat,
|
|
689
|
+
min_area_ha=area_budget * 0.8,
|
|
690
|
+
max_area_ha=area_budget * 1.2,
|
|
691
|
+
vertex_count=random.randint(min_number_vert, max_number_vert),
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
polygon_parts.append([list(polygon.exterior.coords)])
|
|
695
|
+
total_area_ha += actual_area
|
|
696
|
+
|
|
697
|
+
properties = {
|
|
698
|
+
"internal_id": num_polygons + i + 1,
|
|
699
|
+
"geometry_type": "MultiPolygon",
|
|
700
|
+
"num_parts": num_parts,
|
|
701
|
+
"total_area_ha": round(total_area_ha, 2),
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
feature = {
|
|
705
|
+
"type": "Feature",
|
|
706
|
+
"properties": properties,
|
|
707
|
+
"geometry": {
|
|
708
|
+
"type": "MultiPolygon",
|
|
709
|
+
"coordinates": polygon_parts,
|
|
710
|
+
},
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
features.append(feature)
|
|
448
714
|
|
|
449
|
-
|
|
715
|
+
geojson = {"type": "FeatureCollection", "features": features}
|
|
450
716
|
|
|
451
|
-
|
|
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
|
-
}
|
|
717
|
+
logger.logger.info(f"Generated {num_features} test {feature_type} features!")
|
|
458
718
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
"geometry": mapping(polygon),
|
|
463
|
-
}
|
|
719
|
+
# Save to file if save_path is provided
|
|
720
|
+
if save_path is not None:
|
|
721
|
+
import json
|
|
464
722
|
|
|
465
|
-
|
|
723
|
+
save_path = Path(save_path)
|
|
724
|
+
save_path.mkdir(parents=True, exist_ok=True)
|
|
466
725
|
|
|
467
|
-
|
|
726
|
+
output_file = save_path / f"{feature_type}s_{num_features}_features.geojson"
|
|
468
727
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
requested_vertex_counts = [f["properties"]["requested_vertices"] for f in features]
|
|
728
|
+
with open(output_file, "w") as f:
|
|
729
|
+
json.dump(geojson, f, indent=2)
|
|
472
730
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
731
|
+
logger.logger.info(f"GeoJSON saved to: {output_file}")
|
|
732
|
+
|
|
733
|
+
if return_path:
|
|
734
|
+
return output_file
|
|
477
735
|
|
|
478
|
-
|
|
479
|
-
requested_area_counts = [f["properties"]["requested_area_ha"] for f in features]
|
|
736
|
+
return geojson
|
|
480
737
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
738
|
+
|
|
739
|
+
def generate_random_points(bounds, num_features=10, seed=None, save_path=None):
|
|
740
|
+
"""
|
|
741
|
+
Generate random test points with optional save.
|
|
742
|
+
|
|
743
|
+
Simplified wrapper around generate_random_features for point-only generation.
|
|
744
|
+
|
|
745
|
+
Parameters
|
|
746
|
+
----------
|
|
747
|
+
bounds : list or ee.Geometry
|
|
748
|
+
Either a list of [min_lon, min_lat, max_lon, max_lat] or an Earth Engine Geometry.
|
|
749
|
+
num_features : int, optional
|
|
750
|
+
Number of points to generate (default: 10)
|
|
751
|
+
seed : int or None, optional
|
|
752
|
+
Random seed for reproducibility (default: None)
|
|
753
|
+
save_path : str or Path, optional
|
|
754
|
+
Directory path where to save the GeoJSON file. If None, file is not saved (default: None)
|
|
755
|
+
|
|
756
|
+
Returns
|
|
757
|
+
-------
|
|
758
|
+
dict or Path
|
|
759
|
+
If save_path is None: GeoJSON FeatureCollection dict
|
|
760
|
+
If save_path is provided: Path to saved GeoJSON file
|
|
761
|
+
|
|
762
|
+
Examples
|
|
763
|
+
--------
|
|
764
|
+
>>> import openforis_whisp as whisp
|
|
765
|
+
>>>
|
|
766
|
+
>>> bounds = [-81.0, -19.3, -31.5, 9.6]
|
|
767
|
+
>>> geojson = whisp.generate_random_points(bounds, num_features=100)
|
|
768
|
+
>>>
|
|
769
|
+
>>> # Generate and save
|
|
770
|
+
>>> from pathlib import Path
|
|
771
|
+
>>> file_path = whisp.generate_random_points(
|
|
772
|
+
... bounds,
|
|
773
|
+
... num_features=500,
|
|
774
|
+
... save_path=Path.home() / 'downloads'
|
|
775
|
+
... )
|
|
776
|
+
"""
|
|
777
|
+
return generate_random_features(
|
|
778
|
+
bounds=bounds,
|
|
779
|
+
feature_type="point",
|
|
780
|
+
num_features=num_features,
|
|
781
|
+
seed=seed,
|
|
782
|
+
save_path=save_path,
|
|
783
|
+
return_path=True if save_path else False,
|
|
484
784
|
)
|
|
485
785
|
|
|
486
|
-
|
|
487
|
-
|
|
786
|
+
|
|
787
|
+
def generate_random_polygons(
|
|
788
|
+
bounds,
|
|
789
|
+
num_polygons=25,
|
|
790
|
+
min_area_ha=1,
|
|
791
|
+
max_area_ha=10,
|
|
792
|
+
min_number_vert=10,
|
|
793
|
+
max_number_vert=20,
|
|
794
|
+
seed=None,
|
|
795
|
+
save_path=None,
|
|
796
|
+
):
|
|
797
|
+
"""
|
|
798
|
+
Generate random test polygons with optional save.
|
|
799
|
+
|
|
800
|
+
Wrapper around generate_random_features for polygon-only generation.
|
|
801
|
+
Uses the same production-quality polygon generation as generate_test_polygons.
|
|
802
|
+
|
|
803
|
+
Parameters
|
|
804
|
+
----------
|
|
805
|
+
bounds : list or ee.Geometry
|
|
806
|
+
Either a list of [min_lon, min_lat, max_lon, max_lon] or an Earth Engine Geometry.
|
|
807
|
+
num_polygons : int, optional
|
|
808
|
+
Number of polygons to generate (default: 25)
|
|
809
|
+
min_area_ha : float, optional
|
|
810
|
+
Minimum area in hectares (default: 1)
|
|
811
|
+
max_area_ha : float, optional
|
|
812
|
+
Maximum area in hectares (default: 10)
|
|
813
|
+
min_number_vert : int, optional
|
|
814
|
+
Minimum number of vertices per polygon (default: 10)
|
|
815
|
+
max_number_vert : int, optional
|
|
816
|
+
Maximum number of vertices per polygon (default: 20)
|
|
817
|
+
seed : int or None, optional
|
|
818
|
+
Random seed for reproducibility (default: None)
|
|
819
|
+
save_path : str or Path, optional
|
|
820
|
+
Directory path where to save the GeoJSON file. If None, file is not saved (default: None)
|
|
821
|
+
|
|
822
|
+
Returns
|
|
823
|
+
-------
|
|
824
|
+
dict or Path
|
|
825
|
+
If save_path is None: GeoJSON FeatureCollection dict
|
|
826
|
+
If save_path is provided: Path to saved GeoJSON file
|
|
827
|
+
|
|
828
|
+
Examples
|
|
829
|
+
--------
|
|
830
|
+
>>> import openforis_whisp as whisp
|
|
831
|
+
>>>
|
|
832
|
+
>>> bounds = [-81.0, -19.3, -31.5, 9.6]
|
|
833
|
+
>>> geojson = whisp.generate_random_polygons(bounds, num_polygons=50)
|
|
834
|
+
>>>
|
|
835
|
+
>>> # Generate and save
|
|
836
|
+
>>> from pathlib import Path
|
|
837
|
+
>>> file_path = whisp.generate_random_polygons(
|
|
838
|
+
... bounds,
|
|
839
|
+
... num_polygons=100,
|
|
840
|
+
... min_area_ha=5,
|
|
841
|
+
... max_area_ha=50,
|
|
842
|
+
... save_path=Path.home() / 'downloads'
|
|
843
|
+
... )
|
|
844
|
+
"""
|
|
845
|
+
return generate_random_features(
|
|
846
|
+
bounds=bounds,
|
|
847
|
+
feature_type="polygon",
|
|
848
|
+
num_features=num_polygons,
|
|
849
|
+
seed=seed,
|
|
850
|
+
min_area_ha=min_area_ha,
|
|
851
|
+
max_area_ha=max_area_ha,
|
|
852
|
+
min_number_vert=min_number_vert,
|
|
853
|
+
max_number_vert=max_number_vert,
|
|
854
|
+
save_path=save_path,
|
|
855
|
+
return_path=True if save_path else False,
|
|
856
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: openforis-whisp
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0a3
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
openforis_whisp/__init__.py,sha256
|
|
2
|
-
openforis_whisp/advanced_stats.py,sha256=
|
|
3
|
-
openforis_whisp/data_checks.py,sha256=
|
|
4
|
-
openforis_whisp/data_conversion.py,sha256=
|
|
1
|
+
openforis_whisp/__init__.py,sha256=s42Q0VJdzm8mgnxfYg1hUEJPM2VLWIva2h-mdKyr444,3538
|
|
2
|
+
openforis_whisp/advanced_stats.py,sha256=tvhgNTCGlT3aYecUPP6QCTO0FRrjk0qjs95NoVZvIt4,90935
|
|
3
|
+
openforis_whisp/data_checks.py,sha256=KwgD72FA_n7joiJadGRpzntd2sLo0aqGNbOjRkB8iQI,32293
|
|
4
|
+
openforis_whisp/data_conversion.py,sha256=L2IsiUyQUt3aHgSYGbIhgPGwM7eyS3nLVEoNO9YqQeM,21888
|
|
5
5
|
openforis_whisp/datasets.py,sha256=aGJy0OYN4d0nsH3_IOYlHl-WCB7KFwZwMJ-dBi5Hc5Y,53470
|
|
6
6
|
openforis_whisp/logger.py,sha256=9M6_3mdpoiWfC-pDwM9vKmB2l5Gul6Rb5rNTNh-_nzs,3054
|
|
7
7
|
openforis_whisp/parameters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -10,11 +10,11 @@ openforis_whisp/parameters/lookup_context_and_metadata.csv,sha256=KgK0ik_Gd4t_Nq
|
|
|
10
10
|
openforis_whisp/parameters/lookup_gaul1_admin.py,sha256=cQr5liRdXi85QieTxrz4VAkn0COvRCp82ZV0dYFWOio,474980
|
|
11
11
|
openforis_whisp/parameters/lookup_gee_datasets.csv,sha256=UDvZrQsL5rXJn6CW6P3wofUrPLRmUFZWt6ETbXaxBMs,17454
|
|
12
12
|
openforis_whisp/pd_schemas.py,sha256=W_ocS773LHfc05dJqvWRa-bRdX0wKFoNp0lMxgFx94Y,2681
|
|
13
|
-
openforis_whisp/reformat.py,sha256=
|
|
13
|
+
openforis_whisp/reformat.py,sha256=MPjP5lb218GTcTpd_Qvbj5ER_8EY4JjLDteQaS5OZCQ,32620
|
|
14
14
|
openforis_whisp/risk.py,sha256=d_Di5XB8BnHdVXG56xdHTcpB4-CIF5vo2ZRMQRG7Pek,34420
|
|
15
|
-
openforis_whisp/stats.py,sha256=
|
|
16
|
-
openforis_whisp/utils.py,sha256=
|
|
17
|
-
openforis_whisp-3.0.
|
|
18
|
-
openforis_whisp-3.0.
|
|
19
|
-
openforis_whisp-3.0.
|
|
20
|
-
openforis_whisp-3.0.
|
|
15
|
+
openforis_whisp/stats.py,sha256=nVzQpSu7BoSb2S6HheLeoK_pmguZ9Lyw0ZfbTTMVq4Q,63720
|
|
16
|
+
openforis_whisp/utils.py,sha256=Q-EwhUaohk63WCx7Rr5VuR3X-oGtgILZDc8JsjbWhgg,30538
|
|
17
|
+
openforis_whisp-3.0.0a3.dist-info/LICENSE,sha256=nqyqICO95iw_iwzP1t_IIAf7ZX3DPbL_M9WyQfh2q1k,1085
|
|
18
|
+
openforis_whisp-3.0.0a3.dist-info/METADATA,sha256=6xuNhUpQWyzKU3m13FnJ7SX39jAVry1YEKNAdH0D2to,16684
|
|
19
|
+
openforis_whisp-3.0.0a3.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
20
|
+
openforis_whisp-3.0.0a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|