Update geodata pipeline and exports
This commit is contained in:
272
PIPELINE.md
Normal file
272
PIPELINE.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# GeoData Pipeline Architecture
|
||||
|
||||
Visual documentation showing how the GeoData pipeline transforms raw geospatial data into Unity-ready assets.
|
||||
|
||||
---
|
||||
|
||||
## Complete Pipeline Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ INPUT LAYER │
|
||||
├──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┤
|
||||
│ DGM1 │ DOM1 │ DOP20 │ CityGML │ LPG │ LPO │ BDOM20 │
|
||||
│ TIFF │ TIFF │ JP2 │ GML │ XYZ │ XYZ │ LAZ │
|
||||
│ terrain │ surface │ ortho │ buildings│ ground │ object │ RGB │
|
||||
│ 10m │ 10m │ 20cm │ LoD2 │ points │ points │ points │
|
||||
├──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┤
|
||||
│ raw/ directory (inputs) │
|
||||
└────────────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ PROCESSING LAYER │
|
||||
├───────────────────┬────────────────────┬────────────────────────────────────┤
|
||||
│ │ │ │
|
||||
│ HEIGHTMAPS │ BUILDINGS │ VEGETATION & OBJECTS │
|
||||
│ │ │ │
|
||||
│ ┌─────────────┐ │ ┌──────────────┐ │ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │ DGM1 → VRT │ │ │GML→CityJSON │ │ │ DOM1-DGM1 │ │nDSM detect │ │
|
||||
│ │ ↓ │ │ │ ↓ │ │ │ = CHM │ │ ↓ │ │
|
||||
│ │ Warp 1025² │ │ │ Triangulate │ │ │ ↓ │ │ Classify │ │
|
||||
│ │ ↓ │ │ │ ↓ │ │ │Local maxima │ │(lamp/bench │ │
|
||||
│ │ Scale 16bit│ │ │ Decimate │ │ │ ↓ │ │ /sign) │ │
|
||||
│ │ ↓ │ │ │ ↓ │ │ │ Tree peaks │ │ │ │
|
||||
│ │ [LPG valid]│ │ │ Ground-snap │ │ │ [LPO refine] │ │ [LPO ref] │ │
|
||||
│ └─────────────┘ │ │ [RANSAC roof]│ │ │ [DOP20 RGB] │ └─────────────┘ │
|
||||
│ │ └──────────────┘ │ └──────────────┘ │
|
||||
├───────────────────┴────────────────────┴────────────────────────────────────┤
|
||||
│ work/ directory (temp) │
|
||||
└────────────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ OUTPUT LAYER │
|
||||
├─────────────────┬─────────────────┬─────────────────┬───────────────────────┤
|
||||
│ height_png16/ │ ortho_jpg/ │ buildings_tiles/│ trees/ + furniture/ │
|
||||
│ *.png (16-bit) │ *.jpg (2048²) │ *.glb │ *.csv │
|
||||
│ *.pgw │ *.jgw │ │ trees_tiles/*.glb │
|
||||
├─────────────────┴─────────────────┴─────────────────┴───────────────────────┤
|
||||
│ tile_index.csv (manifest with bounds + elevation range) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ export_unity/ directory │
|
||||
└────────────────────────────────────────┬────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ UNITY IMPORT │
|
||||
│ GeoTileImporter.cs reads tile_index.csv, creates: │
|
||||
│ • Terrain tiles (heightmaps + ortho textures) │
|
||||
│ • Building GLB instances │
|
||||
│ • Tree proxies with canopy colors │
|
||||
│ • Street furniture placeholders │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pipeline Dependency Graph
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ --setup │
|
||||
│ materialize │
|
||||
│ archives │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ HEIGHTMAP │◄──── MUST RUN FIRST
|
||||
│ export_height │ Creates tile_index.csv
|
||||
│ maps() │ Creates work/dgm.vrt
|
||||
└────────┬────────┘
|
||||
│
|
||||
┌──────────────┼──────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────┐ ┌────────────┐ ┌────────────┐
|
||||
│ TEXTURES │ │ BUILDINGS │ │ TREES │
|
||||
│ ortho │ │ citygml │ │ dom1-dgm1 │
|
||||
│ photos │ │ → glb │ │ → csv+glb │
|
||||
└────────────┘ └────────────┘ └─────┬──────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────┐
|
||||
│ STREET FURNITURE │
|
||||
│ ndsm detection │
|
||||
│ → csv │
|
||||
└─────────┬─────────┘
|
||||
│
|
||||
┌──────────────────────────┼──────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌────────────────┐ ┌────────────────┐ ┌─────────────────┐
|
||||
│ HEIGHTMAP- │ │ BUILDINGS- │ │ TREES- │
|
||||
│ ENHANCED │ │ ENHANCED │ │ ENHANCED │
|
||||
│ +LPG valid │ │ +RANSAC roofs │ │ +LPO heights │
|
||||
│ +quality map │ │ +LPO heights │ │ +DOP20 colors │
|
||||
└────────────────┘ └────────────────┘ │ -furniture mask │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow Per Export Type
|
||||
|
||||
### 1. Heightmap (Standard)
|
||||
```
|
||||
raw/dgm1/*.tif ──► work/dgm.vrt ──► Per-tile warp (1025×1025)
|
||||
│
|
||||
▼
|
||||
Scale to UInt16
|
||||
│
|
||||
▼
|
||||
export_unity/height_png16/{tile}.png
|
||||
export_unity/tile_index.csv (manifest)
|
||||
```
|
||||
|
||||
### 2. Orthophoto (Textures)
|
||||
```
|
||||
raw/dop20/jp2/*.jp2 ──► work/dop.vrt ──► Per-tile extract
|
||||
│ │
|
||||
│ tile_index.csv ────────────►│ (read bounds)
|
||||
│ │
|
||||
▼ ▼
|
||||
export_unity/ortho_jpg/{tile}.jpg (2048×2048)
|
||||
```
|
||||
|
||||
### 3. Buildings (Standard)
|
||||
```
|
||||
raw/citygml/lod2/*.gml ──► citygml-tools ──► work/cityjson/*.json
|
||||
│
|
||||
▼
|
||||
cjio triangulate
|
||||
│
|
||||
▼
|
||||
work/cityjson_local/*.tri.json
|
||||
│
|
||||
work/dgm.vrt ──────────────────────────►│ (ground-snap)
|
||||
│
|
||||
▼
|
||||
export_unity/buildings_tiles/{tile}.glb
|
||||
```
|
||||
|
||||
### 4. Trees (Standard)
|
||||
```
|
||||
raw/dom1/*.tif ─────────► CHM = DOM1 - DGM1
|
||||
raw/dgm1/*.tif ─────────► │
|
||||
▼
|
||||
Local maxima detection
|
||||
│
|
||||
raw/citygml/lod2/*.gml ─────────►│ (building mask)
|
||||
│
|
||||
▼
|
||||
export_unity/trees/{tile}.csv
|
||||
export_unity/trees_tiles/{tile}_{chunk}.glb
|
||||
```
|
||||
|
||||
### 5. Street Furniture
|
||||
```
|
||||
raw/dom1/*.tif ─────────► nDSM = DOM1 - DGM1
|
||||
raw/dgm1/*.tif ─────────► │
|
||||
▼
|
||||
Blob detection (0.3-8m)
|
||||
│
|
||||
raw/lpo/*.xyz ──────────────────►│ (optional LPO refinement)
|
||||
│
|
||||
▼
|
||||
Classify: lamp/bench/sign/bollard
|
||||
│
|
||||
▼
|
||||
export_unity/street_furniture/{tile}.csv
|
||||
```
|
||||
|
||||
### 6. Enhanced Pipelines
|
||||
```
|
||||
HEIGHTMAP-ENHANCED:
|
||||
heightmap output + raw/lpg/*.xyz ──► LPG validation ──► quality map
|
||||
|
||||
BUILDINGS-ENHANCED:
|
||||
buildings output + raw/bdom20rgbi/*.laz ──► RANSAC roof extraction
|
||||
+ raw/lpo/*.xyz ──────────► height refinement
|
||||
|
||||
TREES-ENHANCED:
|
||||
trees output + raw/lpo/*.xyz ──────► height refinement
|
||||
+ raw/dop20/*.jp2 ──────► canopy RGB sampling
|
||||
+ street_furniture/*.csv ► exclusion mask
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Format Reference
|
||||
|
||||
| Output | Format | Resolution | Purpose |
|
||||
|--------|--------|------------|---------|
|
||||
| `height_png16/*.png` | 16-bit PNG | 1025×1025 | Unity terrain heightmap |
|
||||
| `ortho_jpg/*.jpg` | JPEG Q90 | 2048×2048 | Terrain texture |
|
||||
| `buildings_tiles/*.glb` | glTF Binary | 200-350k tris | 3D building models |
|
||||
| `trees/*.csv` | CSV | - | Tree positions (x,y,z,h,r) |
|
||||
| `trees_tiles/*.glb` | glTF Binary | - | Proxy geometry chunks |
|
||||
| `street_furniture/*.csv` | CSV | - | Furniture positions |
|
||||
| `tile_index.csv` | CSV | - | Manifest (bounds, min/max) |
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands
|
||||
|
||||
```bash
|
||||
# Setup (extract archives, create directories)
|
||||
uv run python geodata_to_unity.py --setup --build-from-archive
|
||||
|
||||
# Standard exports
|
||||
uv run python geodata_to_unity.py --export heightmap # First!
|
||||
uv run python geodata_to_unity.py --export textures
|
||||
uv run python geodata_to_unity.py --export buildings
|
||||
uv run python geodata_to_unity.py --export trees
|
||||
uv run python geodata_to_unity.py --export all # All standard
|
||||
|
||||
# Enhanced exports (requires point cloud data)
|
||||
uv run python geodata_to_unity.py --export heightmap-enhanced
|
||||
uv run python geodata_to_unity.py --export buildings-enhanced
|
||||
uv run python geodata_to_unity.py --export trees-enhanced
|
||||
uv run python geodata_to_unity.py --export street-furniture
|
||||
uv run python geodata_to_unity.py --export all-enhanced # All enhanced
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Config Classes
|
||||
|
||||
```
|
||||
Config
|
||||
├── raw: RawConfig # Input directories
|
||||
├── archives: ArchiveConfig # Archive staging
|
||||
├── work: WorkConfig # Temp/intermediate
|
||||
├── export: ExportConfig # Output directories
|
||||
├── heightmap: HeightmapConfig # out_res=1025, tile_size_m=1000
|
||||
├── ortho: OrthoConfig # out_res=2048, quality=90
|
||||
├── buildings: BuildingConfig # triangle budget 200-350k
|
||||
├── trees: TreeConfig # max_trees=5000, chunk_grid=4x4
|
||||
├── pointcloud: PointCloudConfig # LPG/LPO/BDOM directories
|
||||
├── heightmap_enhanced: EnhancedHeightmapConfig
|
||||
├── buildings_enhanced: EnhancedBuildingConfig
|
||||
├── trees_enhanced: EnhancedTreeConfig
|
||||
└── street_furniture: StreetFurnitureConfig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| Module | Purpose |
|
||||
|--------|---------|
|
||||
| `geodata_to_unity.py` | CLI entry point |
|
||||
| `geodata_pipeline/config.py` | Configuration dataclasses |
|
||||
| `geodata_pipeline/heightmaps.py` | DGM1 → PNG16 |
|
||||
| `geodata_pipeline/orthophotos.py` | DOP20 → JPEG |
|
||||
| `geodata_pipeline/buildings.py` | CityGML → GLB |
|
||||
| `geodata_pipeline/trees.py` | CHM → CSV + GLB |
|
||||
| `geodata_pipeline/street_furniture.py` | nDSM → CSV |
|
||||
| `geodata_pipeline/pointcloud.py` | XYZ/LAZ utilities |
|
||||
| `geodata_pipeline/*_enhanced.py` | Enhanced pipelines |
|
||||
| `GeoTileImporter.cs` | Unity importer |
|
||||
BIN
export_unity/buildings_tiles/dgm1_32_328_5511.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_328_5511.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_328_5512.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_328_5512.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_328_5513.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_328_5513.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_328_5514.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_328_5514.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_328_5515.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_328_5515.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_329_5511.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_329_5511.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_329_5512.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_329_5512.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_329_5513.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_329_5513.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_329_5514.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_329_5514.glb
Normal file
Binary file not shown.
BIN
export_unity/buildings_tiles/dgm1_32_329_5515.glb
Normal file
BIN
export_unity/buildings_tiles/dgm1_32_329_5515.glb
Normal file
Binary file not shown.
1
export_unity/street_furniture/dgm1_32_328_5511.csv
Normal file
1
export_unity/street_furniture/dgm1_32_328_5511.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_328_5512.csv
Normal file
1
export_unity/street_furniture/dgm1_32_328_5512.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_328_5513.csv
Normal file
1
export_unity/street_furniture/dgm1_32_328_5513.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_328_5514.csv
Normal file
1
export_unity/street_furniture/dgm1_32_328_5514.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_328_5515.csv
Normal file
1
export_unity/street_furniture/dgm1_32_328_5515.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_329_5511.csv
Normal file
1
export_unity/street_furniture/dgm1_32_329_5511.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_329_5512.csv
Normal file
1
export_unity/street_furniture/dgm1_32_329_5512.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_329_5513.csv
Normal file
1
export_unity/street_furniture/dgm1_32_329_5513.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_329_5514.csv
Normal file
1
export_unity/street_furniture/dgm1_32_329_5514.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
1
export_unity/street_furniture/dgm1_32_329_5515.csv
Normal file
1
export_unity/street_furniture/dgm1_32_329_5515.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
BIN
export_unity/tree_proxies.glb
Normal file
BIN
export_unity/tree_proxies.glb
Normal file
Binary file not shown.
1933
export_unity/trees/dgm1_32_328_5511.csv
Normal file
1933
export_unity/trees/dgm1_32_328_5511.csv
Normal file
File diff suppressed because it is too large
Load Diff
1967
export_unity/trees/dgm1_32_328_5512.csv
Normal file
1967
export_unity/trees/dgm1_32_328_5512.csv
Normal file
File diff suppressed because it is too large
Load Diff
2414
export_unity/trees/dgm1_32_328_5513.csv
Normal file
2414
export_unity/trees/dgm1_32_328_5513.csv
Normal file
File diff suppressed because it is too large
Load Diff
5001
export_unity/trees/dgm1_32_328_5514.csv
Normal file
5001
export_unity/trees/dgm1_32_328_5514.csv
Normal file
File diff suppressed because it is too large
Load Diff
5001
export_unity/trees/dgm1_32_328_5515.csv
Normal file
5001
export_unity/trees/dgm1_32_328_5515.csv
Normal file
File diff suppressed because it is too large
Load Diff
2494
export_unity/trees/dgm1_32_329_5511.csv
Normal file
2494
export_unity/trees/dgm1_32_329_5511.csv
Normal file
File diff suppressed because it is too large
Load Diff
2503
export_unity/trees/dgm1_32_329_5512.csv
Normal file
2503
export_unity/trees/dgm1_32_329_5512.csv
Normal file
File diff suppressed because it is too large
Load Diff
1717
export_unity/trees/dgm1_32_329_5513.csv
Normal file
1717
export_unity/trees/dgm1_32_329_5513.csv
Normal file
File diff suppressed because it is too large
Load Diff
2381
export_unity/trees/dgm1_32_329_5514.csv
Normal file
2381
export_unity/trees/dgm1_32_329_5514.csv
Normal file
File diff suppressed because it is too large
Load Diff
3250
export_unity/trees/dgm1_32_329_5515.csv
Normal file
3250
export_unity/trees/dgm1_32_329_5515.csv
Normal file
File diff suppressed because it is too large
Load Diff
1
export_unity/trees_enhanced/dgm1_32_328_5511.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_328_5511.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_328_5512.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_328_5512.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_328_5513.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_328_5513.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_328_5514.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_328_5514.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_328_5515.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_328_5515.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_329_5511.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_329_5511.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_329_5512.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_329_5512.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_329_5513.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_329_5513.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_329_5514.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_329_5514.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
1
export_unity/trees_enhanced/dgm1_32_329_5515.csv
Normal file
1
export_unity/trees_enhanced/dgm1_32_329_5515.csv
Normal file
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
BIN
export_unity/trees_tiles/dgm1_32_328_5511.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_328_5511.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_328_5512.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_328_5512.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_328_5513.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_328_5513.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_328_5514.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_328_5514.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_328_5515.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_328_5515.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_329_5511.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_329_5511.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_329_5512.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_329_5512.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_329_5513.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_329_5513.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_329_5514.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_329_5514.glb
Normal file
Binary file not shown.
BIN
export_unity/trees_tiles/dgm1_32_329_5515.glb
Normal file
BIN
export_unity/trees_tiles/dgm1_32_329_5515.glb
Normal file
Binary file not shown.
519
geodata_pipeline/buildings_enhanced.py
Normal file
519
geodata_pipeline/buildings_enhanced.py
Normal file
@@ -0,0 +1,519 @@
|
||||
"""Enhanced building export with point cloud roof extraction.
|
||||
|
||||
Combines CityGML footprints with BDOM20RGBI/LPO point clouds for:
|
||||
1. RANSAC-based roof plane extraction (where BDOM available)
|
||||
2. LPO height refinement
|
||||
3. DOP20 facade texturing
|
||||
4. Fallback to standard CityGML pipeline
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import glob
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from osgeo import gdal
|
||||
|
||||
from .config import Config
|
||||
from .gdal_utils import open_dataset
|
||||
from .pointcloud import (
|
||||
PointCloud,
|
||||
find_laz_file,
|
||||
find_xyz_file,
|
||||
has_bdom_data,
|
||||
has_lpo_data,
|
||||
read_laz_file,
|
||||
read_xyz_file,
|
||||
sample_rgb_average,
|
||||
)
|
||||
|
||||
try:
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.ops import unary_union
|
||||
HAS_SHAPELY = True
|
||||
except ImportError:
|
||||
HAS_SHAPELY = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoofPlane:
|
||||
"""A detected roof plane from RANSAC."""
|
||||
normal: np.ndarray # Unit normal vector
|
||||
d: float # Plane distance from origin
|
||||
inliers: np.ndarray # Point indices
|
||||
centroid: np.ndarray # Plane centroid
|
||||
|
||||
|
||||
def ransac_plane_fit(
|
||||
points: np.ndarray,
|
||||
max_iterations: int = 1000,
|
||||
distance_threshold: float = 0.15,
|
||||
min_inliers: int = 50,
|
||||
) -> Optional[RoofPlane]:
|
||||
"""Fit a single plane using RANSAC.
|
||||
|
||||
Args:
|
||||
points: Nx3 array of points
|
||||
max_iterations: Maximum RANSAC iterations
|
||||
distance_threshold: Inlier distance threshold in meters
|
||||
min_inliers: Minimum inliers for valid plane
|
||||
|
||||
Returns:
|
||||
RoofPlane or None if no plane found
|
||||
"""
|
||||
if len(points) < 3:
|
||||
return None
|
||||
|
||||
best_inliers = None
|
||||
best_normal = None
|
||||
best_d = None
|
||||
|
||||
n_points = len(points)
|
||||
|
||||
for _ in range(max_iterations):
|
||||
# Sample 3 random points
|
||||
idx = np.random.choice(n_points, 3, replace=False)
|
||||
p1, p2, p3 = points[idx]
|
||||
|
||||
# Compute plane normal
|
||||
v1 = p2 - p1
|
||||
v2 = p3 - p1
|
||||
normal = np.cross(v1, v2)
|
||||
|
||||
norm_len = np.linalg.norm(normal)
|
||||
if norm_len < 1e-10:
|
||||
continue
|
||||
|
||||
normal = normal / norm_len
|
||||
|
||||
# Plane equation: normal . (x - p1) = 0
|
||||
# => normal . x = normal . p1 = d
|
||||
d = np.dot(normal, p1)
|
||||
|
||||
# Compute distances to plane
|
||||
distances = np.abs(np.dot(points, normal) - d)
|
||||
|
||||
# Find inliers
|
||||
inlier_mask = distances < distance_threshold
|
||||
inlier_count = np.sum(inlier_mask)
|
||||
|
||||
if inlier_count >= min_inliers:
|
||||
if best_inliers is None or inlier_count > len(best_inliers):
|
||||
best_inliers = np.where(inlier_mask)[0]
|
||||
best_normal = normal
|
||||
best_d = d
|
||||
|
||||
if best_inliers is None:
|
||||
return None
|
||||
|
||||
# Refit plane using all inliers (least squares)
|
||||
inlier_points = points[best_inliers]
|
||||
centroid = np.mean(inlier_points, axis=0)
|
||||
|
||||
# SVD to find best-fit plane
|
||||
centered = inlier_points - centroid
|
||||
_, _, vh = np.linalg.svd(centered)
|
||||
refined_normal = vh[2] # Smallest singular value direction
|
||||
|
||||
# Ensure normal points upward
|
||||
if refined_normal[2] < 0:
|
||||
refined_normal = -refined_normal
|
||||
|
||||
refined_d = np.dot(refined_normal, centroid)
|
||||
|
||||
return RoofPlane(
|
||||
normal=refined_normal,
|
||||
d=refined_d,
|
||||
inliers=best_inliers,
|
||||
centroid=centroid,
|
||||
)
|
||||
|
||||
|
||||
def extract_roof_planes(
|
||||
points: PointCloud,
|
||||
max_planes: int = 6,
|
||||
ransac_threshold: float = 0.15,
|
||||
ransac_iterations: int = 1000,
|
||||
min_inliers: int = 50,
|
||||
) -> List[RoofPlane]:
|
||||
"""Extract multiple roof planes using iterative RANSAC.
|
||||
|
||||
Args:
|
||||
points: Point cloud of roof points
|
||||
max_planes: Maximum number of planes to extract
|
||||
ransac_threshold: Distance threshold for inliers
|
||||
ransac_iterations: RANSAC iterations per plane
|
||||
min_inliers: Minimum points for valid plane
|
||||
|
||||
Returns:
|
||||
List of extracted RoofPlane objects
|
||||
"""
|
||||
if len(points) < min_inliers:
|
||||
return []
|
||||
|
||||
xyz = points.xyz.copy()
|
||||
planes = []
|
||||
remaining_mask = np.ones(len(xyz), dtype=bool)
|
||||
|
||||
for _ in range(max_planes):
|
||||
remaining_points = xyz[remaining_mask]
|
||||
|
||||
if len(remaining_points) < min_inliers:
|
||||
break
|
||||
|
||||
plane = ransac_plane_fit(
|
||||
remaining_points,
|
||||
max_iterations=ransac_iterations,
|
||||
distance_threshold=ransac_threshold,
|
||||
min_inliers=min_inliers,
|
||||
)
|
||||
|
||||
if plane is None:
|
||||
break
|
||||
|
||||
# Map inlier indices back to original array
|
||||
remaining_indices = np.where(remaining_mask)[0]
|
||||
original_inliers = remaining_indices[plane.inliers]
|
||||
plane.inliers = original_inliers
|
||||
|
||||
planes.append(plane)
|
||||
|
||||
# Remove inliers from remaining points
|
||||
remaining_mask[original_inliers] = False
|
||||
|
||||
return planes
|
||||
|
||||
|
||||
def classify_roof_type(planes: List[RoofPlane]) -> str:
|
||||
"""Classify roof type from extracted planes.
|
||||
|
||||
Args:
|
||||
planes: List of roof planes
|
||||
|
||||
Returns:
|
||||
Roof type string: 'flat', 'gable', 'hip', 'complex'
|
||||
"""
|
||||
if not planes:
|
||||
return "unknown"
|
||||
|
||||
n_planes = len(planes)
|
||||
|
||||
if n_planes == 1:
|
||||
# Check if plane is horizontal (flat roof)
|
||||
normal = planes[0].normal
|
||||
if abs(normal[2]) > 0.95: # Nearly vertical normal
|
||||
return "flat"
|
||||
return "shed"
|
||||
|
||||
if n_planes == 2:
|
||||
# Check for gable roof (two planes meeting at ridge)
|
||||
n1, n2 = planes[0].normal, planes[1].normal
|
||||
dot = abs(np.dot(n1, n2))
|
||||
if dot < 0.9: # Planes at an angle
|
||||
return "gable"
|
||||
|
||||
if n_planes == 4:
|
||||
return "hip"
|
||||
|
||||
return "complex"
|
||||
|
||||
|
||||
def refine_building_with_lpo(
|
||||
tile_id: str,
|
||||
footprint_bounds: Tuple[float, float, float, float],
|
||||
citygml_height: float,
|
||||
cfg: Config,
|
||||
) -> Optional[float]:
|
||||
"""Refine building height using LPO points.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
footprint_bounds: (xmin, ymin, xmax, ymax) of building
|
||||
citygml_height: Original height from CityGML
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Refined height or None if no LPO data
|
||||
"""
|
||||
lpo_file = find_xyz_file(cfg.pointcloud.lpo_dir, tile_id)
|
||||
if not lpo_file:
|
||||
return None
|
||||
|
||||
xmin, ymin, xmax, ymax = footprint_bounds
|
||||
|
||||
# Load LPO points in building bounds
|
||||
lpo = read_xyz_file(lpo_file, bounds=(xmin, ymin, xmax, ymax))
|
||||
if len(lpo) < 10:
|
||||
return None
|
||||
|
||||
# Get 95th percentile height
|
||||
max_z = float(np.percentile(lpo.z, 95))
|
||||
|
||||
# Load DGM for ground elevation
|
||||
dgm_pattern = os.path.join(cfg.raw.dgm1_dir, f"*{tile_id}*.tif")
|
||||
dgm_files = glob.glob(dgm_pattern)
|
||||
if not dgm_files:
|
||||
return None
|
||||
|
||||
dgm_ds = open_dataset(dgm_files[0], required=False)
|
||||
if dgm_ds is None:
|
||||
return None
|
||||
|
||||
dgm = dgm_ds.GetRasterBand(1).ReadAsArray()
|
||||
gt = dgm_ds.GetGeoTransform()
|
||||
|
||||
# Sample ground elevation at building center
|
||||
center_x = (xmin + xmax) / 2
|
||||
center_y = (ymin + ymax) / 2
|
||||
col = int((center_x - gt[0]) / gt[1])
|
||||
row = int((center_y - gt[3]) / gt[5])
|
||||
|
||||
if 0 <= row < dgm.shape[0] and 0 <= col < dgm.shape[1]:
|
||||
ground_z = float(dgm[row, col])
|
||||
refined_height = max_z - ground_z
|
||||
if refined_height > 0:
|
||||
return refined_height
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def sample_facade_color(
|
||||
tile_id: str,
|
||||
world_x: float,
|
||||
world_y: float,
|
||||
cfg: Config,
|
||||
) -> Tuple[int, int, int]:
|
||||
"""Sample facade color from orthophoto.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
world_x, world_y: World coordinates
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
RGB tuple (0-255)
|
||||
"""
|
||||
ortho_path = os.path.join(cfg.export.ortho_dir, f"{tile_id}.jpg")
|
||||
if not os.path.exists(ortho_path):
|
||||
return (180, 170, 160) # Default neutral
|
||||
|
||||
ds = open_dataset(ortho_path, required=False)
|
||||
if ds is None:
|
||||
return (180, 170, 160)
|
||||
|
||||
gt = ds.GetGeoTransform()
|
||||
col = int((world_x - gt[0]) / gt[1])
|
||||
row = int((world_y - gt[3]) / gt[5])
|
||||
|
||||
if 0 <= row < ds.RasterYSize and 0 <= col < ds.RasterXSize:
|
||||
try:
|
||||
r = ds.GetRasterBand(1).ReadAsArray(col, row, 1, 1)[0, 0]
|
||||
g = ds.GetRasterBand(2).ReadAsArray(col, row, 1, 1)[0, 0]
|
||||
b = ds.GetRasterBand(3).ReadAsArray(col, row, 1, 1)[0, 0]
|
||||
return (int(r), int(g), int(b))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return (180, 170, 160)
|
||||
|
||||
|
||||
def process_building_with_bdom(
|
||||
tile_id: str,
|
||||
footprint_bounds: Tuple[float, float, float, float],
|
||||
cfg: Config,
|
||||
) -> Optional[dict]:
|
||||
"""Process a building using BDOM point cloud for roof geometry.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
footprint_bounds: (xmin, ymin, xmax, ymax) of building
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Dict with roof planes and metadata, or None if processing fails
|
||||
"""
|
||||
eb_cfg = cfg.buildings_enhanced
|
||||
|
||||
bdom_file = find_laz_file(cfg.pointcloud.bdom_dir, tile_id)
|
||||
if not bdom_file:
|
||||
return None
|
||||
|
||||
xmin, ymin, xmax, ymax = footprint_bounds
|
||||
|
||||
# Load BDOM points in building bounds (with small buffer)
|
||||
buffer = 0.5
|
||||
bdom = read_laz_file(bdom_file, bounds=(
|
||||
xmin - buffer, ymin - buffer,
|
||||
xmax + buffer, ymax + buffer
|
||||
))
|
||||
|
||||
if len(bdom) < eb_cfg.min_plane_inliers:
|
||||
return None
|
||||
|
||||
# Load DGM for ground reference
|
||||
dgm_pattern = os.path.join(cfg.raw.dgm1_dir, f"*{tile_id}*.tif")
|
||||
dgm_files = glob.glob(dgm_pattern)
|
||||
if not dgm_files:
|
||||
return None
|
||||
|
||||
dgm_ds = open_dataset(dgm_files[0], required=False)
|
||||
if dgm_ds is None:
|
||||
return None
|
||||
|
||||
dgm = dgm_ds.GetRasterBand(1).ReadAsArray()
|
||||
gt = dgm_ds.GetGeoTransform()
|
||||
|
||||
# Get ground elevation at building center
|
||||
center_x = (xmin + xmax) / 2
|
||||
center_y = (ymin + ymax) / 2
|
||||
col = int((center_x - gt[0]) / gt[1])
|
||||
row = int((center_y - gt[3]) / gt[5])
|
||||
|
||||
if 0 <= row < dgm.shape[0] and 0 <= col < dgm.shape[1]:
|
||||
ground_z = float(dgm[row, col])
|
||||
else:
|
||||
ground_z = float(np.min(bdom.z))
|
||||
|
||||
# Filter to points above ground (roof points)
|
||||
roof_threshold = ground_z + 2.0 # At least 2m above ground
|
||||
roof_mask = bdom.z > roof_threshold
|
||||
roof_points = PointCloud(
|
||||
x=bdom.x[roof_mask],
|
||||
y=bdom.y[roof_mask],
|
||||
z=bdom.z[roof_mask],
|
||||
r=bdom.r[roof_mask] if bdom.r is not None else None,
|
||||
g=bdom.g[roof_mask] if bdom.g is not None else None,
|
||||
b=bdom.b[roof_mask] if bdom.b is not None else None,
|
||||
)
|
||||
|
||||
if len(roof_points) < eb_cfg.min_plane_inliers:
|
||||
return None
|
||||
|
||||
# Extract roof planes
|
||||
planes = extract_roof_planes(
|
||||
roof_points,
|
||||
max_planes=eb_cfg.max_roof_planes,
|
||||
ransac_threshold=eb_cfg.ransac_threshold_m,
|
||||
ransac_iterations=eb_cfg.ransac_iterations,
|
||||
min_inliers=eb_cfg.min_plane_inliers,
|
||||
)
|
||||
|
||||
if not planes:
|
||||
return None
|
||||
|
||||
# Get roof RGB if available
|
||||
roof_rgb = None
|
||||
if roof_points.has_rgb:
|
||||
r_avg = int(np.mean(roof_points.r))
|
||||
g_avg = int(np.mean(roof_points.g))
|
||||
b_avg = int(np.mean(roof_points.b))
|
||||
roof_rgb = (r_avg, g_avg, b_avg)
|
||||
|
||||
# Compute building height
|
||||
max_z = float(np.max(roof_points.z))
|
||||
height = max_z - ground_z
|
||||
|
||||
return {
|
||||
"planes": planes,
|
||||
"roof_type": classify_roof_type(planes),
|
||||
"height": height,
|
||||
"ground_z": ground_z,
|
||||
"roof_rgb": roof_rgb,
|
||||
"source": "bdom",
|
||||
}
|
||||
|
||||
|
||||
def export_buildings_enhanced(cfg: Config) -> int:
|
||||
"""Export enhanced buildings with point cloud fusion.
|
||||
|
||||
For tiles with BDOM20RGBI:
|
||||
- Extract roof planes using RANSAC
|
||||
- Use RGB from point cloud for facade colors
|
||||
|
||||
For tiles with only LPO:
|
||||
- Refine building heights from LPO
|
||||
- Use DOP20 for facade colors
|
||||
|
||||
For tiles without point cloud:
|
||||
- Fall back to standard CityGML pipeline
|
||||
|
||||
Args:
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Exit code (0 = success)
|
||||
"""
|
||||
eb_cfg = cfg.buildings_enhanced
|
||||
|
||||
# Check for manifest
|
||||
if not os.path.exists(cfg.export.manifest_path):
|
||||
print(f"[buildings_enhanced] ERROR: Manifest not found: {cfg.export.manifest_path}")
|
||||
print("[buildings_enhanced] Run heightmap export first.")
|
||||
return 1
|
||||
|
||||
# Load tiles
|
||||
tiles = []
|
||||
with open(cfg.export.manifest_path, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
tiles.append({
|
||||
"tile_id": row["tile_id"],
|
||||
"xmin": float(row["xmin"]),
|
||||
"ymin": float(row["ymin"]),
|
||||
"xmax": float(row["xmax"]),
|
||||
"ymax": float(row["ymax"]),
|
||||
})
|
||||
|
||||
if not tiles:
|
||||
print("[buildings_enhanced] No tiles in manifest.")
|
||||
return 1
|
||||
|
||||
os.makedirs(eb_cfg.out_dir, exist_ok=True)
|
||||
|
||||
stats = {"bdom": 0, "lpo": 0, "citygml": 0, "failed": 0}
|
||||
|
||||
for tile in tiles:
|
||||
tile_id = tile["tile_id"]
|
||||
print(f"[buildings_enhanced] Processing {tile_id}...")
|
||||
|
||||
# Check data availability
|
||||
has_bdom = has_bdom_data(tile_id, cfg.pointcloud.bdom_dir)
|
||||
has_lpo = has_lpo_data(tile_id, cfg.pointcloud.lpo_dir)
|
||||
|
||||
if has_bdom and eb_cfg.use_bdom_roof:
|
||||
print(f"[buildings_enhanced] {tile_id}: Using BDOM for roof extraction")
|
||||
stats["bdom"] += 1
|
||||
# BDOM processing would create enhanced GLB here
|
||||
# For now, we log that BDOM is available
|
||||
|
||||
elif has_lpo and eb_cfg.use_lpo_refinement:
|
||||
print(f"[buildings_enhanced] {tile_id}: Using LPO for height refinement")
|
||||
stats["lpo"] += 1
|
||||
# LPO refinement would adjust CityGML heights here
|
||||
|
||||
elif eb_cfg.fallback_to_citygml:
|
||||
print(f"[buildings_enhanced] {tile_id}: Falling back to CityGML")
|
||||
stats["citygml"] += 1
|
||||
# Standard CityGML export
|
||||
|
||||
else:
|
||||
print(f"[buildings_enhanced] {tile_id}: No data available, skipping")
|
||||
stats["failed"] += 1
|
||||
|
||||
print(f"[buildings_enhanced] DONE. "
|
||||
f"BDOM={stats['bdom']}, LPO={stats['lpo']}, "
|
||||
f"CityGML={stats['citygml']}, Failed={stats['failed']}")
|
||||
|
||||
# Note: Full GLB export would integrate with existing buildings.py
|
||||
# This module provides the data fusion logic; actual mesh export
|
||||
# requires integration with trimesh and GLB generation from buildings.py
|
||||
|
||||
if stats["bdom"] > 0 or stats["lpo"] > 0:
|
||||
print("[buildings_enhanced] Note: Full GLB export requires integration with buildings.py")
|
||||
print("[buildings_enhanced] Use --export buildings for standard CityGML export")
|
||||
|
||||
return 0
|
||||
@@ -79,6 +79,67 @@ class TreeConfig:
|
||||
instancing: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class PointCloudConfig:
|
||||
"""Configuration for point cloud data sources."""
|
||||
lpg_dir: str = "raw/lpg"
|
||||
lpo_dir: str = "raw/lpo"
|
||||
bdom_dir: str = "raw/bdom20rgbi"
|
||||
dom1_dir: str = "raw/dom1"
|
||||
chunk_size: int = 5_000_000
|
||||
use_lpg_validation: bool = True
|
||||
use_lpo_refinement: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnhancedHeightmapConfig:
|
||||
"""Configuration for enhanced heightmap export with LPG validation."""
|
||||
lpg_validation_threshold_m: float = 0.5
|
||||
export_quality_map: bool = True
|
||||
quality_map_dir: str = "export_unity/height_quality"
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnhancedBuildingConfig:
|
||||
"""Configuration for enhanced building export with point cloud fusion."""
|
||||
out_dir: str = "export_unity/buildings_enhanced"
|
||||
ransac_threshold_m: float = 0.15
|
||||
ransac_iterations: int = 1000
|
||||
max_roof_planes: int = 6
|
||||
min_plane_inliers: int = 50
|
||||
use_bdom_roof: bool = True
|
||||
use_lpo_refinement: bool = True
|
||||
use_dop20_textures: bool = True
|
||||
fallback_to_citygml: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class StreetFurnitureConfig:
|
||||
"""Configuration for street furniture detection."""
|
||||
enabled: bool = True
|
||||
csv_dir: str = "export_unity/street_furniture"
|
||||
min_height_m: float = 0.3
|
||||
max_height_m: float = 8.0
|
||||
min_confidence: float = 0.6
|
||||
use_lpo_refinement: bool = True
|
||||
lamp_height_range: tuple = (4.0, 8.0)
|
||||
bench_height_range: tuple = (0.4, 1.0)
|
||||
sign_height_range: tuple = (2.0, 4.0)
|
||||
bollard_height_range: tuple = (0.5, 1.2)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnhancedTreeConfig:
|
||||
"""Configuration for enhanced tree detection."""
|
||||
csv_dir: str = "export_unity/trees_enhanced"
|
||||
glb_dir: str = "export_unity/trees_enhanced_tiles"
|
||||
exclude_furniture: bool = True
|
||||
use_lpo_refinement: bool = True
|
||||
sample_canopy_color: bool = True
|
||||
lpo_search_radius_m: float = 5.0
|
||||
canopy_sample_radius_factor: float = 0.5
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
raw: RawConfig = field(default_factory=RawConfig)
|
||||
@@ -89,6 +150,12 @@ class Config:
|
||||
ortho: OrthoConfig = field(default_factory=OrthoConfig)
|
||||
buildings: BuildingConfig = field(default_factory=BuildingConfig)
|
||||
trees: TreeConfig = field(default_factory=TreeConfig)
|
||||
# Enhanced pipeline configs
|
||||
pointcloud: PointCloudConfig = field(default_factory=PointCloudConfig)
|
||||
heightmap_enhanced: EnhancedHeightmapConfig = field(default_factory=EnhancedHeightmapConfig)
|
||||
buildings_enhanced: EnhancedBuildingConfig = field(default_factory=EnhancedBuildingConfig)
|
||||
street_furniture: StreetFurnitureConfig = field(default_factory=StreetFurnitureConfig)
|
||||
trees_enhanced: EnhancedTreeConfig = field(default_factory=EnhancedTreeConfig)
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "Config":
|
||||
@@ -111,6 +178,12 @@ class Config:
|
||||
ortho=OrthoConfig(**data["ortho"]),
|
||||
buildings=BuildingConfig(**data.get("buildings", {})),
|
||||
trees=TreeConfig(**data.get("trees", {})),
|
||||
# Enhanced pipeline configs (with defaults for backward compat)
|
||||
pointcloud=PointCloudConfig(**data.get("pointcloud", {})),
|
||||
heightmap_enhanced=EnhancedHeightmapConfig(**data.get("heightmap_enhanced", {})),
|
||||
buildings_enhanced=EnhancedBuildingConfig(**data.get("buildings_enhanced", {})),
|
||||
street_furniture=StreetFurnitureConfig(**data.get("street_furniture", {})),
|
||||
trees_enhanced=EnhancedTreeConfig(**data.get("trees_enhanced", {})),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
|
||||
@@ -68,7 +68,7 @@ def export_heightmaps(cfg: Config, *, force_vrt: bool = False) -> int:
|
||||
height=cfg.heightmap.out_res,
|
||||
resampleAlg=cfg.heightmap.resample,
|
||||
srcNodata=-9999,
|
||||
dstNodata=gmin,
|
||||
dstNodata=0, # Use 0 for nodata (safe since valid elevations scale to 1-65535)
|
||||
)
|
||||
try:
|
||||
gdal.Warp(tmp_path, ds, options=warp_opts)
|
||||
|
||||
298
geodata_pipeline/heightmaps_enhanced.py
Normal file
298
geodata_pipeline/heightmaps_enhanced.py
Normal file
@@ -0,0 +1,298 @@
|
||||
"""Enhanced heightmap export with LPG ground point validation.
|
||||
|
||||
Extends the standard heightmap export by:
|
||||
1. Validating DGM1 raster against LPG ground points
|
||||
2. Flagging areas where DGM1 and LPG diverge significantly
|
||||
3. Optionally exporting quality metrics
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import os
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from osgeo import gdal
|
||||
|
||||
from .config import Config
|
||||
from .gdal_utils import build_vrt, open_dataset
|
||||
from .heightmaps import export_heightmaps # Reuse existing export
|
||||
from .pointcloud import find_xyz_file, has_lpg_data, read_xyz_file
|
||||
|
||||
|
||||
def validate_tile_with_lpg(
|
||||
tile_id: str,
|
||||
dgm1_path: str,
|
||||
xmin: float,
|
||||
ymin: float,
|
||||
xmax: float,
|
||||
ymax: float,
|
||||
cfg: Config,
|
||||
) -> Optional[dict]:
|
||||
"""Validate DGM1 tile against LPG ground points.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
dgm1_path: Path to DGM1 raster
|
||||
xmin, ymin, xmax, ymax: Tile bounds
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Dict with validation metrics, or None if no LPG data
|
||||
"""
|
||||
pc_cfg = cfg.pointcloud
|
||||
eh_cfg = cfg.heightmap_enhanced
|
||||
|
||||
# Check for LPG data
|
||||
lpg_file = find_xyz_file(pc_cfg.lpg_dir, tile_id)
|
||||
if not lpg_file:
|
||||
return None
|
||||
|
||||
# Load DGM1 raster
|
||||
dgm1_ds = open_dataset(dgm1_path, required=False)
|
||||
if dgm1_ds is None:
|
||||
return None
|
||||
|
||||
dgm1 = dgm1_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||
gt = dgm1_ds.GetGeoTransform()
|
||||
nodata = dgm1_ds.GetRasterBand(1).GetNoDataValue() or -9999
|
||||
|
||||
# Load LPG points for tile
|
||||
lpg = read_xyz_file(lpg_file, bounds=(xmin, ymin, xmax, ymax))
|
||||
if len(lpg) == 0:
|
||||
return {"point_count": 0, "valid": True}
|
||||
|
||||
# Sample DGM1 at LPG point locations
|
||||
differences = []
|
||||
for x, y, z in zip(lpg.x, lpg.y, lpg.z):
|
||||
# Convert world coords to pixel coords
|
||||
col = int((x - gt[0]) / gt[1])
|
||||
row = int((y - gt[3]) / gt[5])
|
||||
|
||||
if 0 <= row < dgm1.shape[0] and 0 <= col < dgm1.shape[1]:
|
||||
dgm_val = dgm1[row, col]
|
||||
if dgm_val > nodata + 1:
|
||||
diff = z - dgm_val
|
||||
differences.append(diff)
|
||||
|
||||
if not differences:
|
||||
return {"point_count": len(lpg), "sampled": 0, "valid": True}
|
||||
|
||||
diffs = np.array(differences)
|
||||
|
||||
# Compute validation metrics
|
||||
metrics = {
|
||||
"point_count": len(lpg),
|
||||
"sampled": len(diffs),
|
||||
"mean_diff": float(np.mean(diffs)),
|
||||
"std_diff": float(np.std(diffs)),
|
||||
"median_diff": float(np.median(diffs)),
|
||||
"min_diff": float(np.min(diffs)),
|
||||
"max_diff": float(np.max(diffs)),
|
||||
"rmse": float(np.sqrt(np.mean(diffs ** 2))),
|
||||
"outlier_count": int(np.sum(np.abs(diffs) > eh_cfg.lpg_validation_threshold_m)),
|
||||
"outlier_pct": float(np.mean(np.abs(diffs) > eh_cfg.lpg_validation_threshold_m) * 100),
|
||||
"valid": bool(np.abs(np.median(diffs)) < eh_cfg.lpg_validation_threshold_m),
|
||||
}
|
||||
|
||||
return metrics
|
||||
|
||||
|
||||
def export_quality_map(
|
||||
tile_id: str,
|
||||
dgm1_path: str,
|
||||
xmin: float,
|
||||
ymin: float,
|
||||
xmax: float,
|
||||
ymax: float,
|
||||
cfg: Config,
|
||||
) -> Optional[str]:
|
||||
"""Export quality/difference map comparing DGM1 to LPG.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
dgm1_path: Path to DGM1 raster
|
||||
xmin, ymin, xmax, ymax: Tile bounds
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Path to quality map PNG, or None if no LPG data
|
||||
"""
|
||||
pc_cfg = cfg.pointcloud
|
||||
eh_cfg = cfg.heightmap_enhanced
|
||||
|
||||
lpg_file = find_xyz_file(pc_cfg.lpg_dir, tile_id)
|
||||
if not lpg_file:
|
||||
return None
|
||||
|
||||
dgm1_ds = open_dataset(dgm1_path, required=False)
|
||||
if dgm1_ds is None:
|
||||
return None
|
||||
|
||||
dgm1 = dgm1_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||
gt = dgm1_ds.GetGeoTransform()
|
||||
nodata = dgm1_ds.GetRasterBand(1).GetNoDataValue() or -9999
|
||||
|
||||
# Load LPG points
|
||||
lpg = read_xyz_file(lpg_file, bounds=(xmin, ymin, xmax, ymax))
|
||||
if len(lpg) == 0:
|
||||
return None
|
||||
|
||||
# Create difference accumulator
|
||||
diff_sum = np.zeros_like(dgm1)
|
||||
diff_count = np.zeros_like(dgm1, dtype=np.int32)
|
||||
|
||||
for x, y, z in zip(lpg.x, lpg.y, lpg.z):
|
||||
col = int((x - gt[0]) / gt[1])
|
||||
row = int((y - gt[3]) / gt[5])
|
||||
|
||||
if 0 <= row < dgm1.shape[0] and 0 <= col < dgm1.shape[1]:
|
||||
dgm_val = dgm1[row, col]
|
||||
if dgm_val > nodata + 1:
|
||||
diff_sum[row, col] += (z - dgm_val)
|
||||
diff_count[row, col] += 1
|
||||
|
||||
# Compute mean difference per cell
|
||||
with np.errstate(divide='ignore', invalid='ignore'):
|
||||
diff_mean = np.where(diff_count > 0, diff_sum / diff_count, 0)
|
||||
|
||||
# Scale to 8-bit for visualization
|
||||
# Map [-threshold, +threshold] to [0, 255]
|
||||
threshold = eh_cfg.lpg_validation_threshold_m * 2
|
||||
quality = (diff_mean + threshold) / (2 * threshold) * 255
|
||||
quality = np.clip(quality, 0, 255).astype(np.uint8)
|
||||
|
||||
# Export as PNG
|
||||
os.makedirs(eh_cfg.quality_map_dir, exist_ok=True)
|
||||
out_path = os.path.join(eh_cfg.quality_map_dir, f"{tile_id}_quality.png")
|
||||
|
||||
driver = gdal.GetDriverByName("PNG")
|
||||
out_ds = driver.Create(out_path, dgm1.shape[1], dgm1.shape[0], 1, gdal.GDT_Byte)
|
||||
out_ds.SetGeoTransform(gt)
|
||||
out_ds.GetRasterBand(1).WriteArray(quality)
|
||||
out_ds = None
|
||||
|
||||
return out_path
|
||||
|
||||
|
||||
def export_heightmaps_enhanced(cfg: Config) -> int:
|
||||
"""Export heightmaps with LPG validation.
|
||||
|
||||
This wraps the standard heightmap export and adds:
|
||||
1. LPG validation metrics per tile
|
||||
2. Optional quality map export
|
||||
|
||||
Args:
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Exit code (0 = success)
|
||||
"""
|
||||
pc_cfg = cfg.pointcloud
|
||||
eh_cfg = cfg.heightmap_enhanced
|
||||
|
||||
# First, run standard heightmap export
|
||||
result = export_heightmaps(cfg)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
# Now validate with LPG
|
||||
if not pc_cfg.use_lpg_validation:
|
||||
print("[heightmaps_enhanced] LPG validation disabled, skipping.")
|
||||
return 0
|
||||
|
||||
# Load tile manifest
|
||||
if not os.path.exists(cfg.export.manifest_path):
|
||||
print(f"[heightmaps_enhanced] Manifest not found: {cfg.export.manifest_path}")
|
||||
return 1
|
||||
|
||||
tiles = []
|
||||
with open(cfg.export.manifest_path, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
tiles.append({
|
||||
"tile_id": row["tile_id"],
|
||||
"xmin": float(row["xmin"]),
|
||||
"ymin": float(row["ymin"]),
|
||||
"xmax": float(row["xmax"]),
|
||||
"ymax": float(row["ymax"]),
|
||||
})
|
||||
|
||||
validation_results = []
|
||||
quality_maps = []
|
||||
|
||||
for tile in tiles:
|
||||
tile_id = tile["tile_id"]
|
||||
|
||||
# Find DGM1 file
|
||||
import glob
|
||||
dgm1_pattern = os.path.join(cfg.raw.dgm1_dir, f"*{tile_id}*.tif")
|
||||
dgm1_files = glob.glob(dgm1_pattern)
|
||||
|
||||
if not dgm1_files:
|
||||
print(f"[heightmaps_enhanced] No DGM1 for {tile_id}, skipping validation")
|
||||
continue
|
||||
|
||||
dgm1_path = dgm1_files[0]
|
||||
|
||||
# Validate against LPG
|
||||
metrics = validate_tile_with_lpg(
|
||||
tile_id,
|
||||
dgm1_path,
|
||||
tile["xmin"],
|
||||
tile["ymin"],
|
||||
tile["xmax"],
|
||||
tile["ymax"],
|
||||
cfg,
|
||||
)
|
||||
|
||||
if metrics:
|
||||
metrics["tile_id"] = tile_id
|
||||
validation_results.append(metrics)
|
||||
|
||||
status = "OK" if metrics.get("valid", True) else "WARN"
|
||||
rmse = metrics.get("rmse", 0)
|
||||
outlier_pct = metrics.get("outlier_pct", 0)
|
||||
print(f"[heightmaps_enhanced] {tile_id}: {status} (RMSE={rmse:.3f}m, outliers={outlier_pct:.1f}%)")
|
||||
|
||||
# Export quality map if enabled
|
||||
if eh_cfg.export_quality_map:
|
||||
qm_path = export_quality_map(
|
||||
tile_id,
|
||||
dgm1_path,
|
||||
tile["xmin"],
|
||||
tile["ymin"],
|
||||
tile["xmax"],
|
||||
tile["ymax"],
|
||||
cfg,
|
||||
)
|
||||
if qm_path:
|
||||
quality_maps.append(qm_path)
|
||||
else:
|
||||
print(f"[heightmaps_enhanced] {tile_id}: No LPG data")
|
||||
|
||||
# Export validation summary
|
||||
if validation_results:
|
||||
summary_path = os.path.join(eh_cfg.quality_map_dir, "validation_summary.csv")
|
||||
os.makedirs(eh_cfg.quality_map_dir, exist_ok=True)
|
||||
|
||||
fieldnames = ["tile_id", "point_count", "sampled", "mean_diff", "std_diff",
|
||||
"median_diff", "min_diff", "max_diff", "rmse", "outlier_count",
|
||||
"outlier_pct", "valid"]
|
||||
|
||||
with open(summary_path, "w", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
|
||||
writer.writeheader()
|
||||
writer.writerows(validation_results)
|
||||
|
||||
print(f"[heightmaps_enhanced] Validation summary: {summary_path}")
|
||||
|
||||
valid_count = sum(1 for r in validation_results if r.get("valid", True))
|
||||
total_count = len(validation_results)
|
||||
print(f"[heightmaps_enhanced] DONE. {valid_count}/{total_count} tiles passed validation.")
|
||||
|
||||
if quality_maps:
|
||||
print(f"[heightmaps_enhanced] Quality maps exported: {len(quality_maps)}")
|
||||
|
||||
return 0
|
||||
411
geodata_pipeline/pointcloud.py
Normal file
411
geodata_pipeline/pointcloud.py
Normal file
@@ -0,0 +1,411 @@
|
||||
"""Point cloud utilities for XYZ and LAZ file handling.
|
||||
|
||||
Provides unified reading for:
|
||||
- LPG (ground points) and LPO (object points) in XYZ format
|
||||
- BDOM20RGBI in LAZ format with RGB data
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
|
||||
try:
|
||||
import laspy
|
||||
HAS_LASPY = True
|
||||
except ImportError:
|
||||
HAS_LASPY = False
|
||||
|
||||
try:
|
||||
from shapely.geometry import Polygon, Point
|
||||
from shapely.prepared import prep
|
||||
HAS_SHAPELY = True
|
||||
except ImportError:
|
||||
HAS_SHAPELY = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class PointCloud:
|
||||
"""Container for point cloud data with optional RGB."""
|
||||
x: np.ndarray
|
||||
y: np.ndarray
|
||||
z: np.ndarray
|
||||
r: Optional[np.ndarray] = None
|
||||
g: Optional[np.ndarray] = None
|
||||
b: Optional[np.ndarray] = None
|
||||
intensity: Optional[np.ndarray] = None
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.x)
|
||||
|
||||
@property
|
||||
def has_rgb(self) -> bool:
|
||||
return self.r is not None and self.g is not None and self.b is not None
|
||||
|
||||
@property
|
||||
def xyz(self) -> np.ndarray:
|
||||
"""Return Nx3 array of XYZ coordinates."""
|
||||
return np.column_stack([self.x, self.y, self.z])
|
||||
|
||||
@property
|
||||
def rgb(self) -> Optional[np.ndarray]:
|
||||
"""Return Nx3 array of RGB values (0-255) if available."""
|
||||
if not self.has_rgb:
|
||||
return None
|
||||
return np.column_stack([self.r, self.g, self.b])
|
||||
|
||||
def filter_bounds(self, xmin: float, ymin: float, xmax: float, ymax: float) -> "PointCloud":
|
||||
"""Filter points to bounding box."""
|
||||
mask = (
|
||||
(self.x >= xmin) & (self.x <= xmax) &
|
||||
(self.y >= ymin) & (self.y <= ymax)
|
||||
)
|
||||
return PointCloud(
|
||||
x=self.x[mask],
|
||||
y=self.y[mask],
|
||||
z=self.z[mask],
|
||||
r=self.r[mask] if self.r is not None else None,
|
||||
g=self.g[mask] if self.g is not None else None,
|
||||
b=self.b[mask] if self.b is not None else None,
|
||||
intensity=self.intensity[mask] if self.intensity is not None else None,
|
||||
)
|
||||
|
||||
def filter_polygon(self, polygon: "Polygon", buffer_m: float = 0.0) -> "PointCloud":
|
||||
"""Filter points within a polygon (with optional buffer)."""
|
||||
if not HAS_SHAPELY:
|
||||
raise ImportError("shapely required for polygon filtering")
|
||||
|
||||
if buffer_m > 0:
|
||||
polygon = polygon.buffer(buffer_m)
|
||||
|
||||
prepared = prep(polygon)
|
||||
mask = np.array([prepared.contains(Point(x, y)) for x, y in zip(self.x, self.y)])
|
||||
|
||||
return PointCloud(
|
||||
x=self.x[mask],
|
||||
y=self.y[mask],
|
||||
z=self.z[mask],
|
||||
r=self.r[mask] if self.r is not None else None,
|
||||
g=self.g[mask] if self.g is not None else None,
|
||||
b=self.b[mask] if self.b is not None else None,
|
||||
intensity=self.intensity[mask] if self.intensity is not None else None,
|
||||
)
|
||||
|
||||
|
||||
def read_xyz_file(
|
||||
path: str,
|
||||
bounds: Optional[Tuple[float, float, float, float]] = None,
|
||||
chunk_size: int = 1_000_000,
|
||||
) -> PointCloud:
|
||||
"""Read XYZ text file (space/tab delimited) with optional bounds filtering.
|
||||
|
||||
Args:
|
||||
path: Path to XYZ file
|
||||
bounds: Optional (xmin, ymin, xmax, ymax) to filter points
|
||||
chunk_size: Number of lines to read at once for memory efficiency
|
||||
|
||||
Returns:
|
||||
PointCloud with x, y, z arrays
|
||||
"""
|
||||
all_x, all_y, all_z = [], [], []
|
||||
|
||||
with open(path, "r") as f:
|
||||
while True:
|
||||
lines = []
|
||||
for _ in range(chunk_size):
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
lines.append(line)
|
||||
|
||||
if not lines:
|
||||
break
|
||||
|
||||
# Parse chunk
|
||||
chunk_data = []
|
||||
for line in lines:
|
||||
parts = line.strip().split()
|
||||
if len(parts) >= 3:
|
||||
try:
|
||||
x, y, z = float(parts[0]), float(parts[1]), float(parts[2])
|
||||
chunk_data.append((x, y, z))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if not chunk_data:
|
||||
continue
|
||||
|
||||
chunk_arr = np.array(chunk_data, dtype=np.float64)
|
||||
|
||||
# Apply bounds filter if specified
|
||||
if bounds is not None:
|
||||
xmin, ymin, xmax, ymax = bounds
|
||||
mask = (
|
||||
(chunk_arr[:, 0] >= xmin) & (chunk_arr[:, 0] <= xmax) &
|
||||
(chunk_arr[:, 1] >= ymin) & (chunk_arr[:, 1] <= ymax)
|
||||
)
|
||||
chunk_arr = chunk_arr[mask]
|
||||
|
||||
if len(chunk_arr) > 0:
|
||||
all_x.append(chunk_arr[:, 0])
|
||||
all_y.append(chunk_arr[:, 1])
|
||||
all_z.append(chunk_arr[:, 2])
|
||||
|
||||
if not all_x:
|
||||
return PointCloud(
|
||||
x=np.array([], dtype=np.float64),
|
||||
y=np.array([], dtype=np.float64),
|
||||
z=np.array([], dtype=np.float64),
|
||||
)
|
||||
|
||||
return PointCloud(
|
||||
x=np.concatenate(all_x),
|
||||
y=np.concatenate(all_y),
|
||||
z=np.concatenate(all_z),
|
||||
)
|
||||
|
||||
|
||||
def read_laz_file(
|
||||
path: str,
|
||||
bounds: Optional[Tuple[float, float, float, float]] = None,
|
||||
) -> PointCloud:
|
||||
"""Read LAZ/LAS file with optional bounds filtering.
|
||||
|
||||
Args:
|
||||
path: Path to LAZ/LAS file
|
||||
bounds: Optional (xmin, ymin, xmax, ymax) to filter points
|
||||
|
||||
Returns:
|
||||
PointCloud with x, y, z and optional r, g, b, intensity
|
||||
"""
|
||||
if not HAS_LASPY:
|
||||
raise ImportError("laspy required for LAZ file reading. Install with: pip install laspy[lazrs]")
|
||||
|
||||
with laspy.open(path) as reader:
|
||||
# Read all points (laspy handles decompression)
|
||||
las = reader.read()
|
||||
|
||||
x = np.array(las.x, dtype=np.float64)
|
||||
y = np.array(las.y, dtype=np.float64)
|
||||
z = np.array(las.z, dtype=np.float64)
|
||||
|
||||
# Extract RGB if available (typically 16-bit, scale to 8-bit)
|
||||
r, g, b = None, None, None
|
||||
if hasattr(las, 'red') and hasattr(las, 'green') and hasattr(las, 'blue'):
|
||||
# LAZ RGB is often 16-bit, convert to 8-bit
|
||||
r = (np.array(las.red, dtype=np.float32) / 256).astype(np.uint8)
|
||||
g = (np.array(las.green, dtype=np.float32) / 256).astype(np.uint8)
|
||||
b = (np.array(las.blue, dtype=np.float32) / 256).astype(np.uint8)
|
||||
|
||||
# Extract intensity if available
|
||||
intensity = None
|
||||
if hasattr(las, 'intensity'):
|
||||
intensity = np.array(las.intensity, dtype=np.uint16)
|
||||
|
||||
pc = PointCloud(x=x, y=y, z=z, r=r, g=g, b=b, intensity=intensity)
|
||||
|
||||
# Apply bounds filter
|
||||
if bounds is not None:
|
||||
pc = pc.filter_bounds(*bounds)
|
||||
|
||||
return pc
|
||||
|
||||
|
||||
def _extract_tile_coords(tile_id: str) -> str:
|
||||
"""Extract tile coordinates from tile ID.
|
||||
|
||||
E.g., 'dgm1_32_328_5511' -> '328_5511'
|
||||
"""
|
||||
parts = tile_id.split("_")
|
||||
# Find numeric parts that look like coordinates
|
||||
coords = []
|
||||
for p in parts:
|
||||
if p.isdigit() and len(p) >= 3:
|
||||
coords.append(p)
|
||||
if len(coords) >= 2:
|
||||
return f"{coords[-2]}_{coords[-1]}"
|
||||
return tile_id
|
||||
|
||||
|
||||
def find_xyz_file(directory: str, tile_id: str) -> Optional[str]:
|
||||
"""Find XYZ file for a tile in directory.
|
||||
|
||||
Args:
|
||||
directory: Directory to search (e.g., 'raw/lpg' or 'raw/lpo')
|
||||
tile_id: Tile ID (e.g., 'dgm1_32_328_5511' or '32_328_5511')
|
||||
|
||||
Returns:
|
||||
Path to XYZ file or None if not found
|
||||
"""
|
||||
# Extract coordinate suffix (e.g., '328_5511')
|
||||
coords = _extract_tile_coords(tile_id)
|
||||
|
||||
# Try common naming patterns
|
||||
patterns = [
|
||||
os.path.join(directory, f"*{coords}*.xyz"),
|
||||
os.path.join(directory, f"*{tile_id}*.xyz"),
|
||||
os.path.join(directory, f"*_{tile_id}.xyz"),
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
matches = glob.glob(pattern)
|
||||
if matches:
|
||||
return matches[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def find_laz_file(directory: str, tile_id: str) -> Optional[str]:
|
||||
"""Find LAZ file for a tile in directory.
|
||||
|
||||
Args:
|
||||
directory: Directory to search (e.g., 'raw/bdom20rgbi')
|
||||
tile_id: Tile ID (e.g., '32_328_5511')
|
||||
|
||||
Returns:
|
||||
Path to LAZ file or None if not found
|
||||
"""
|
||||
# Extract tile suffix for matching (e.g., '328_5511' from '32_328_5511')
|
||||
parts = tile_id.split("_")
|
||||
if len(parts) >= 3:
|
||||
tile_suffix = f"{parts[1]}_{parts[2]}"
|
||||
else:
|
||||
tile_suffix = tile_id
|
||||
|
||||
patterns = [
|
||||
os.path.join(directory, f"*{tile_suffix}*.laz"),
|
||||
os.path.join(directory, f"*{tile_id}*.laz"),
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
matches = glob.glob(pattern)
|
||||
if matches:
|
||||
return matches[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def has_lpg_data(tile_id: str, lpg_dir: str = "raw/lpg") -> bool:
|
||||
"""Check if LPG (ground points) data exists for tile."""
|
||||
return find_xyz_file(lpg_dir, tile_id) is not None
|
||||
|
||||
|
||||
def has_lpo_data(tile_id: str, lpo_dir: str = "raw/lpo") -> bool:
|
||||
"""Check if LPO (object points) data exists for tile."""
|
||||
return find_xyz_file(lpo_dir, tile_id) is not None
|
||||
|
||||
|
||||
def has_bdom_data(tile_id: str, bdom_dir: str = "raw/bdom20rgbi") -> bool:
|
||||
"""Check if BDOM20RGBI data exists for tile."""
|
||||
return find_laz_file(bdom_dir, tile_id) is not None
|
||||
|
||||
|
||||
def compute_point_density(
|
||||
points: PointCloud,
|
||||
cell_size: float = 1.0,
|
||||
bounds: Optional[Tuple[float, float, float, float]] = None,
|
||||
) -> Tuple[np.ndarray, Tuple[float, float, float, float]]:
|
||||
"""Compute point density raster.
|
||||
|
||||
Args:
|
||||
points: PointCloud to analyze
|
||||
cell_size: Grid cell size in meters
|
||||
bounds: Optional bounds, otherwise computed from points
|
||||
|
||||
Returns:
|
||||
Tuple of (density_array, (xmin, ymin, xmax, ymax))
|
||||
"""
|
||||
if len(points) == 0:
|
||||
return np.array([[0]]), (0, 0, 1, 1)
|
||||
|
||||
if bounds is None:
|
||||
xmin, xmax = points.x.min(), points.x.max()
|
||||
ymin, ymax = points.y.min(), points.y.max()
|
||||
else:
|
||||
xmin, ymin, xmax, ymax = bounds
|
||||
|
||||
# Compute grid dimensions
|
||||
nx = int(np.ceil((xmax - xmin) / cell_size))
|
||||
ny = int(np.ceil((ymax - ymin) / cell_size))
|
||||
|
||||
if nx <= 0 or ny <= 0:
|
||||
return np.array([[0]]), (xmin, ymin, xmax, ymax)
|
||||
|
||||
# Compute cell indices for each point
|
||||
ix = ((points.x - xmin) / cell_size).astype(int)
|
||||
iy = ((points.y - ymin) / cell_size).astype(int)
|
||||
|
||||
# Clamp to grid bounds
|
||||
ix = np.clip(ix, 0, nx - 1)
|
||||
iy = np.clip(iy, 0, ny - 1)
|
||||
|
||||
# Count points per cell
|
||||
density = np.zeros((ny, nx), dtype=np.int32)
|
||||
np.add.at(density, (iy, ix), 1)
|
||||
|
||||
return density, (xmin, ymin, xmax, ymax)
|
||||
|
||||
|
||||
def compute_height_percentiles(
|
||||
points: PointCloud,
|
||||
percentiles: list[float] = [5, 25, 50, 75, 95],
|
||||
) -> dict[str, float]:
|
||||
"""Compute height percentiles from point cloud.
|
||||
|
||||
Args:
|
||||
points: PointCloud to analyze
|
||||
percentiles: List of percentiles to compute
|
||||
|
||||
Returns:
|
||||
Dict mapping percentile name (e.g., 'p50') to value
|
||||
"""
|
||||
if len(points) == 0:
|
||||
return {f"p{int(p)}": 0.0 for p in percentiles}
|
||||
|
||||
result = {}
|
||||
for p in percentiles:
|
||||
result[f"p{int(p)}"] = float(np.percentile(points.z, p))
|
||||
|
||||
result["min"] = float(points.z.min())
|
||||
result["max"] = float(points.z.max())
|
||||
result["mean"] = float(points.z.mean())
|
||||
result["std"] = float(points.z.std())
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def sample_rgb_average(
|
||||
points: PointCloud,
|
||||
center_x: float,
|
||||
center_y: float,
|
||||
radius: float,
|
||||
) -> Optional[Tuple[int, int, int]]:
|
||||
"""Sample average RGB color from points within radius of center.
|
||||
|
||||
Args:
|
||||
points: PointCloud with RGB data
|
||||
center_x, center_y: Center point
|
||||
radius: Search radius in meters
|
||||
|
||||
Returns:
|
||||
Tuple of (r, g, b) in 0-255 range, or None if no points/no RGB
|
||||
"""
|
||||
if not points.has_rgb or len(points) == 0:
|
||||
return None
|
||||
|
||||
# Find points within radius
|
||||
dist_sq = (points.x - center_x) ** 2 + (points.y - center_y) ** 2
|
||||
mask = dist_sq <= radius ** 2
|
||||
|
||||
if not mask.any():
|
||||
return None
|
||||
|
||||
r_avg = int(np.mean(points.r[mask]))
|
||||
g_avg = int(np.mean(points.g[mask]))
|
||||
b_avg = int(np.mean(points.b[mask]))
|
||||
|
||||
return (r_avg, g_avg, b_avg)
|
||||
520
geodata_pipeline/street_furniture.py
Normal file
520
geodata_pipeline/street_furniture.py
Normal file
@@ -0,0 +1,520 @@
|
||||
"""Street furniture detection from nDSM and point cloud data.
|
||||
|
||||
Detects and classifies small urban objects like lamps, benches, signs, and bollards
|
||||
by analyzing the normalized DSM (DOM1 - DGM1) and optionally refining with LPO points.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from osgeo import gdal, ogr
|
||||
|
||||
from .config import Config
|
||||
from .gdal_utils import open_dataset
|
||||
from .pointcloud import (
|
||||
PointCloud,
|
||||
find_xyz_file,
|
||||
has_lpo_data,
|
||||
read_xyz_file,
|
||||
)
|
||||
|
||||
|
||||
class FurnitureType(Enum):
|
||||
"""Types of detectable street furniture."""
|
||||
LAMP = "lamp"
|
||||
BENCH = "bench"
|
||||
SIGN = "sign"
|
||||
BOLLARD = "bollard"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
@dataclass
|
||||
class FurnitureDetection:
|
||||
"""A detected street furniture object."""
|
||||
tile_id: str
|
||||
x_local: float # X relative to tile origin
|
||||
y_local: float # Y relative to tile origin
|
||||
z_ground: float # Ground elevation
|
||||
height: float # Object height above ground
|
||||
width: float # Approximate object width
|
||||
furniture_type: FurnitureType
|
||||
confidence: float
|
||||
|
||||
def to_csv_row(self) -> list:
|
||||
"""Convert to CSV row."""
|
||||
return [
|
||||
self.tile_id,
|
||||
f"{self.x_local:.2f}",
|
||||
f"{self.y_local:.2f}",
|
||||
f"{self.z_ground:.2f}",
|
||||
f"{self.height:.2f}",
|
||||
self.furniture_type.value,
|
||||
f"{self.confidence:.3f}",
|
||||
]
|
||||
|
||||
|
||||
def classify_furniture(
|
||||
height: float,
|
||||
width: float,
|
||||
cfg: Config,
|
||||
) -> Tuple[FurnitureType, float]:
|
||||
"""Classify object type based on height and width.
|
||||
|
||||
Args:
|
||||
height: Object height in meters
|
||||
width: Object width in meters
|
||||
cfg: Configuration with height ranges
|
||||
|
||||
Returns:
|
||||
Tuple of (FurnitureType, base_confidence)
|
||||
"""
|
||||
sf_cfg = cfg.street_furniture
|
||||
|
||||
# Check each type's height range
|
||||
lamp_min, lamp_max = sf_cfg.lamp_height_range
|
||||
bench_min, bench_max = sf_cfg.bench_height_range
|
||||
sign_min, sign_max = sf_cfg.sign_height_range
|
||||
bollard_min, bollard_max = sf_cfg.bollard_height_range
|
||||
|
||||
# Lamp: tall and narrow
|
||||
if lamp_min <= height <= lamp_max and width < 0.5:
|
||||
return FurnitureType.LAMP, 0.7
|
||||
|
||||
# Bench: low and wide
|
||||
if bench_min <= height <= bench_max and 0.5 <= width <= 2.0:
|
||||
return FurnitureType.BENCH, 0.65
|
||||
|
||||
# Sign: medium height, narrow
|
||||
if sign_min <= height <= sign_max and width < 0.5:
|
||||
return FurnitureType.SIGN, 0.6
|
||||
|
||||
# Bollard: short, very narrow
|
||||
if bollard_min <= height <= bollard_max and width < 0.3:
|
||||
return FurnitureType.BOLLARD, 0.55
|
||||
|
||||
return FurnitureType.UNKNOWN, 0.3
|
||||
|
||||
|
||||
def _connected_components_2d(
|
||||
binary_mask: np.ndarray,
|
||||
min_pixels: int = 4,
|
||||
) -> List[Tuple[np.ndarray, int, int, int, int]]:
|
||||
"""Find connected components in binary mask.
|
||||
|
||||
Args:
|
||||
binary_mask: 2D boolean array
|
||||
min_pixels: Minimum component size
|
||||
|
||||
Returns:
|
||||
List of (component_mask, row_min, row_max, col_min, col_max)
|
||||
"""
|
||||
from scipy import ndimage
|
||||
|
||||
labeled, num_features = ndimage.label(binary_mask)
|
||||
components = []
|
||||
|
||||
for i in range(1, num_features + 1):
|
||||
component_mask = labeled == i
|
||||
pixel_count = np.sum(component_mask)
|
||||
|
||||
if pixel_count < min_pixels:
|
||||
continue
|
||||
|
||||
rows, cols = np.where(component_mask)
|
||||
row_min, row_max = rows.min(), rows.max()
|
||||
col_min, col_max = cols.min(), cols.max()
|
||||
|
||||
components.append((component_mask, row_min, row_max, col_min, col_max))
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def _rasterize_citygml_footprints(
|
||||
tile_id: str,
|
||||
cfg: Config,
|
||||
shape: Tuple[int, int],
|
||||
geotransform: Tuple[float, ...],
|
||||
) -> np.ndarray:
|
||||
"""Rasterize CityGML building footprints to create exclusion mask.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
cfg: Configuration
|
||||
shape: Output raster shape (rows, cols)
|
||||
geotransform: GDAL geotransform
|
||||
|
||||
Returns:
|
||||
Boolean mask where True = building footprint
|
||||
"""
|
||||
import glob
|
||||
|
||||
# Find CityGML file for tile
|
||||
citygml_pattern = os.path.join(cfg.raw.citygml_lod2_dir, f"*{tile_id}*.gml")
|
||||
citygml_files = glob.glob(citygml_pattern)
|
||||
|
||||
if not citygml_files:
|
||||
return np.zeros(shape, dtype=bool)
|
||||
|
||||
# Create memory raster for rasterization
|
||||
driver = gdal.GetDriverByName("MEM")
|
||||
target_ds = driver.Create("", shape[1], shape[0], 1, gdal.GDT_Byte)
|
||||
target_ds.SetGeoTransform(geotransform)
|
||||
|
||||
# Open GML and rasterize
|
||||
try:
|
||||
gml_ds = ogr.Open(citygml_files[0])
|
||||
if gml_ds is None:
|
||||
return np.zeros(shape, dtype=bool)
|
||||
|
||||
layer = gml_ds.GetLayer(0)
|
||||
gdal.RasterizeLayer(target_ds, [1], layer, burn_values=[1])
|
||||
|
||||
mask = target_ds.GetRasterBand(1).ReadAsArray()
|
||||
return mask > 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"[street_furniture] Warning: Could not rasterize CityGML for {tile_id}: {e}")
|
||||
return np.zeros(shape, dtype=bool)
|
||||
|
||||
|
||||
def detect_furniture_in_tile(
|
||||
tile_id: str,
|
||||
xmin: float,
|
||||
ymin: float,
|
||||
xmax: float,
|
||||
ymax: float,
|
||||
cfg: Config,
|
||||
) -> List[FurnitureDetection]:
|
||||
"""Detect street furniture in a single tile.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier (e.g., '32_328_5511')
|
||||
xmin, ymin, xmax, ymax: Tile bounds in meters
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
List of detected furniture objects
|
||||
"""
|
||||
from scipy import ndimage
|
||||
|
||||
sf_cfg = cfg.street_furniture
|
||||
pc_cfg = cfg.pointcloud
|
||||
|
||||
# Load DOM1 (surface model)
|
||||
dom1_pattern = os.path.join(pc_cfg.dom1_dir, f"*{tile_id}*.tif")
|
||||
import glob
|
||||
dom1_files = glob.glob(dom1_pattern)
|
||||
if not dom1_files:
|
||||
print(f"[street_furniture] No DOM1 found for {tile_id}")
|
||||
return []
|
||||
|
||||
dom1_ds = open_dataset(dom1_files[0], required=False)
|
||||
if dom1_ds is None:
|
||||
return []
|
||||
|
||||
# Load DGM1 (terrain model)
|
||||
dgm1_pattern = os.path.join(cfg.raw.dgm1_dir, f"*{tile_id}*.tif")
|
||||
dgm1_files = glob.glob(dgm1_pattern)
|
||||
if not dgm1_files:
|
||||
print(f"[street_furniture] No DGM1 found for {tile_id}")
|
||||
return []
|
||||
|
||||
dgm1_ds = open_dataset(dgm1_files[0], required=False)
|
||||
if dgm1_ds is None:
|
||||
return []
|
||||
|
||||
# Read rasters
|
||||
dom1 = dom1_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||
dgm1 = dgm1_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||
gt = dom1_ds.GetGeoTransform()
|
||||
|
||||
# Handle nodata
|
||||
dom1_nodata = dom1_ds.GetRasterBand(1).GetNoDataValue() or -9999
|
||||
dgm1_nodata = dgm1_ds.GetRasterBand(1).GetNoDataValue() or -9999
|
||||
valid_mask = (dom1 > dom1_nodata + 1) & (dgm1 > dgm1_nodata + 1)
|
||||
|
||||
# Compute nDSM (normalized DSM)
|
||||
ndsm = np.where(valid_mask, dom1 - dgm1, 0)
|
||||
ndsm = np.clip(ndsm, 0, None) # Remove negative values
|
||||
|
||||
# Create building exclusion mask
|
||||
building_mask = _rasterize_citygml_footprints(
|
||||
tile_id, cfg, dom1.shape, gt
|
||||
)
|
||||
|
||||
# Create tree exclusion mask (objects > 2m that are likely trees)
|
||||
tree_mask = ndsm > 2.0
|
||||
|
||||
# Exclude buildings from tree mask
|
||||
tree_mask = tree_mask & ~building_mask
|
||||
|
||||
# Detect small objects (between min and max height, not buildings/trees)
|
||||
object_mask = (
|
||||
(ndsm >= sf_cfg.min_height_m) &
|
||||
(ndsm <= sf_cfg.max_height_m) &
|
||||
~building_mask &
|
||||
~tree_mask
|
||||
)
|
||||
|
||||
# Find connected components
|
||||
components = _connected_components_2d(object_mask, min_pixels=2)
|
||||
|
||||
detections = []
|
||||
pixel_size = abs(gt[1]) # Assume square pixels
|
||||
|
||||
for comp_mask, row_min, row_max, col_min, col_max in components:
|
||||
# Compute object properties
|
||||
rows, cols = np.where(comp_mask)
|
||||
heights = ndsm[rows, cols]
|
||||
|
||||
max_height = float(np.max(heights))
|
||||
mean_height = float(np.mean(heights))
|
||||
|
||||
# Compute width from bounding box
|
||||
width_pixels = max(col_max - col_min + 1, row_max - row_min + 1)
|
||||
width = width_pixels * pixel_size
|
||||
|
||||
# Compute centroid
|
||||
centroid_row = (row_min + row_max) / 2
|
||||
centroid_col = (col_min + col_max) / 2
|
||||
|
||||
# Convert to world coordinates
|
||||
world_x = gt[0] + centroid_col * gt[1]
|
||||
world_y = gt[3] + centroid_row * gt[5]
|
||||
|
||||
# Get ground elevation at centroid
|
||||
dgm_row = int(centroid_row)
|
||||
dgm_col = int(centroid_col)
|
||||
if 0 <= dgm_row < dgm1.shape[0] and 0 <= dgm_col < dgm1.shape[1]:
|
||||
z_ground = float(dgm1[dgm_row, dgm_col])
|
||||
else:
|
||||
z_ground = 0.0
|
||||
|
||||
# Convert to local coordinates (relative to tile origin)
|
||||
x_local = world_x - xmin
|
||||
y_local = world_y - ymin
|
||||
|
||||
# Classify object
|
||||
furniture_type, base_confidence = classify_furniture(max_height, width, cfg)
|
||||
|
||||
# Skip unknown types below threshold
|
||||
if furniture_type == FurnitureType.UNKNOWN and base_confidence < sf_cfg.min_confidence:
|
||||
continue
|
||||
|
||||
detection = FurnitureDetection(
|
||||
tile_id=tile_id,
|
||||
x_local=x_local,
|
||||
y_local=y_local,
|
||||
z_ground=z_ground,
|
||||
height=max_height,
|
||||
width=width,
|
||||
furniture_type=furniture_type,
|
||||
confidence=base_confidence,
|
||||
)
|
||||
detections.append(detection)
|
||||
|
||||
# Optionally refine with LPO points
|
||||
if sf_cfg.use_lpo_refinement and has_lpo_data(tile_id, pc_cfg.lpo_dir):
|
||||
detections = _refine_with_lpo(
|
||||
detections,
|
||||
tile_id,
|
||||
xmin, ymin, xmax, ymax,
|
||||
cfg,
|
||||
)
|
||||
|
||||
# Filter by minimum confidence
|
||||
detections = [d for d in detections if d.confidence >= sf_cfg.min_confidence]
|
||||
|
||||
return detections
|
||||
|
||||
|
||||
def _refine_with_lpo(
|
||||
detections: List[FurnitureDetection],
|
||||
tile_id: str,
|
||||
xmin: float,
|
||||
ymin: float,
|
||||
xmax: float,
|
||||
ymax: float,
|
||||
cfg: Config,
|
||||
) -> List[FurnitureDetection]:
|
||||
"""Refine furniture detections using LPO point cloud.
|
||||
|
||||
Args:
|
||||
detections: Initial detections from nDSM
|
||||
tile_id: Tile identifier
|
||||
xmin, ymin, xmax, ymax: Tile bounds
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Refined detections with updated heights and confidence
|
||||
"""
|
||||
lpo_file = find_xyz_file(cfg.pointcloud.lpo_dir, tile_id)
|
||||
if not lpo_file:
|
||||
return detections
|
||||
|
||||
# Load LPO points for tile
|
||||
lpo = read_xyz_file(lpo_file, bounds=(xmin, ymin, xmax, ymax))
|
||||
if len(lpo) == 0:
|
||||
return detections
|
||||
|
||||
refined = []
|
||||
for det in detections:
|
||||
# Find LPO points near detection
|
||||
world_x = det.x_local + xmin
|
||||
world_y = det.y_local + ymin
|
||||
|
||||
dist_sq = (lpo.x - world_x) ** 2 + (lpo.y - world_y) ** 2
|
||||
nearby_mask = dist_sq < (det.width + 1.0) ** 2
|
||||
|
||||
if nearby_mask.any():
|
||||
nearby_z = lpo.z[nearby_mask]
|
||||
|
||||
# Refine height using 95th percentile
|
||||
refined_height = float(np.percentile(nearby_z, 95)) - det.z_ground
|
||||
if refined_height > 0:
|
||||
det.height = refined_height
|
||||
|
||||
# Boost confidence for LPO-confirmed detections
|
||||
if det.furniture_type == FurnitureType.LAMP:
|
||||
det.confidence += 0.2
|
||||
elif det.furniture_type == FurnitureType.SIGN:
|
||||
det.confidence += 0.15
|
||||
else:
|
||||
det.confidence += 0.1
|
||||
|
||||
det.confidence = min(det.confidence, 1.0)
|
||||
|
||||
refined.append(det)
|
||||
|
||||
return refined
|
||||
|
||||
|
||||
def export_furniture_csv(
|
||||
detections: List[FurnitureDetection],
|
||||
tile_id: str,
|
||||
cfg: Config,
|
||||
) -> str:
|
||||
"""Export furniture detections to CSV file.
|
||||
|
||||
Args:
|
||||
detections: List of detections
|
||||
tile_id: Tile identifier
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Path to written CSV file
|
||||
"""
|
||||
os.makedirs(cfg.street_furniture.csv_dir, exist_ok=True)
|
||||
csv_path = os.path.join(cfg.street_furniture.csv_dir, f"{tile_id}.csv")
|
||||
|
||||
with open(csv_path, "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"tile_id", "x_local", "y_local", "z_ground",
|
||||
"height", "type", "confidence"
|
||||
])
|
||||
for det in detections:
|
||||
writer.writerow(det.to_csv_row())
|
||||
|
||||
return csv_path
|
||||
|
||||
|
||||
def load_furniture_detections(
|
||||
tile_id: str,
|
||||
cfg: Config,
|
||||
) -> List[FurnitureDetection]:
|
||||
"""Load furniture detections from CSV.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
List of detections, empty if file not found
|
||||
"""
|
||||
csv_path = os.path.join(cfg.street_furniture.csv_dir, f"{tile_id}.csv")
|
||||
if not os.path.exists(csv_path):
|
||||
return []
|
||||
|
||||
detections = []
|
||||
with open(csv_path, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
det = FurnitureDetection(
|
||||
tile_id=row["tile_id"],
|
||||
x_local=float(row["x_local"]),
|
||||
y_local=float(row["y_local"]),
|
||||
z_ground=float(row["z_ground"]),
|
||||
height=float(row["height"]),
|
||||
width=0.0, # Not stored in CSV
|
||||
furniture_type=FurnitureType(row["type"]),
|
||||
confidence=float(row["confidence"]),
|
||||
)
|
||||
detections.append(det)
|
||||
|
||||
return detections
|
||||
|
||||
|
||||
def export_street_furniture(cfg: Config) -> int:
|
||||
"""Export street furniture detections for all tiles.
|
||||
|
||||
Args:
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Exit code (0 = success)
|
||||
"""
|
||||
if not cfg.street_furniture.enabled:
|
||||
print("[street_furniture] Disabled in config, skipping.")
|
||||
return 0
|
||||
|
||||
# Load tile manifest
|
||||
if not os.path.exists(cfg.export.manifest_path):
|
||||
print(f"[street_furniture] ERROR: Manifest not found: {cfg.export.manifest_path}")
|
||||
print("[street_furniture] Run heightmap export first.")
|
||||
return 1
|
||||
|
||||
tiles = []
|
||||
with open(cfg.export.manifest_path, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
tiles.append({
|
||||
"tile_id": row["tile_id"],
|
||||
"xmin": float(row["xmin"]),
|
||||
"ymin": float(row["ymin"]),
|
||||
"xmax": float(row["xmax"]),
|
||||
"ymax": float(row["ymax"]),
|
||||
})
|
||||
|
||||
if not tiles:
|
||||
print("[street_furniture] No tiles found in manifest.")
|
||||
return 1
|
||||
|
||||
os.makedirs(cfg.street_furniture.csv_dir, exist_ok=True)
|
||||
|
||||
total_detections = 0
|
||||
for tile in tiles:
|
||||
tile_id = tile["tile_id"]
|
||||
print(f"[street_furniture] Processing {tile_id}...")
|
||||
|
||||
detections = detect_furniture_in_tile(
|
||||
tile_id,
|
||||
tile["xmin"],
|
||||
tile["ymin"],
|
||||
tile["xmax"],
|
||||
tile["ymax"],
|
||||
cfg,
|
||||
)
|
||||
|
||||
csv_path = export_furniture_csv(detections, tile_id, cfg)
|
||||
print(f"[street_furniture] {tile_id}: {len(detections)} objects -> {csv_path}")
|
||||
total_detections += len(detections)
|
||||
|
||||
print(f"[street_furniture] DONE. Total detections: {total_detections}")
|
||||
return 0
|
||||
420
geodata_pipeline/trees_enhanced.py
Normal file
420
geodata_pipeline/trees_enhanced.py
Normal file
@@ -0,0 +1,420 @@
|
||||
"""Enhanced tree detection with LPO refinement and canopy color sampling.
|
||||
|
||||
Extends the standard tree detection by:
|
||||
1. Excluding detected street furniture
|
||||
2. Refining tree heights using LPO point cloud
|
||||
3. Sampling canopy colors from DOP20 orthophotos
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import glob
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
from osgeo import gdal
|
||||
|
||||
from .config import Config
|
||||
from .gdal_utils import open_dataset
|
||||
from .pointcloud import find_xyz_file, has_lpo_data, read_xyz_file
|
||||
from .street_furniture import load_furniture_detections
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnhancedTree:
|
||||
"""Enhanced tree detection with additional attributes."""
|
||||
tile_id: str
|
||||
x_local: float
|
||||
y_local: float
|
||||
z_ground: float
|
||||
height: float
|
||||
radius: float
|
||||
confidence: float
|
||||
canopy_r: int = 128 # Default green-ish
|
||||
canopy_g: int = 160
|
||||
canopy_b: int = 80
|
||||
|
||||
def to_csv_row(self) -> list:
|
||||
"""Convert to CSV row."""
|
||||
return [
|
||||
self.tile_id,
|
||||
f"{self.x_local:.2f}",
|
||||
f"{self.y_local:.2f}",
|
||||
f"{self.z_ground:.2f}",
|
||||
f"{self.height:.2f}",
|
||||
f"{self.radius:.2f}",
|
||||
f"{self.confidence:.3f}",
|
||||
str(self.canopy_r),
|
||||
str(self.canopy_g),
|
||||
str(self.canopy_b),
|
||||
]
|
||||
|
||||
|
||||
def _create_furniture_mask(
|
||||
detections: list,
|
||||
shape: Tuple[int, int],
|
||||
geotransform: Tuple[float, ...],
|
||||
tile_xmin: float,
|
||||
tile_ymin: float,
|
||||
buffer_m: float = 2.0,
|
||||
) -> np.ndarray:
|
||||
"""Create raster mask from furniture detections.
|
||||
|
||||
Args:
|
||||
detections: List of FurnitureDetection objects
|
||||
shape: Output raster shape (rows, cols)
|
||||
geotransform: GDAL geotransform
|
||||
tile_xmin, tile_ymin: Tile origin
|
||||
buffer_m: Buffer around each detection
|
||||
|
||||
Returns:
|
||||
Boolean mask where True = furniture location
|
||||
"""
|
||||
mask = np.zeros(shape, dtype=bool)
|
||||
pixel_size = abs(geotransform[1])
|
||||
|
||||
for det in detections:
|
||||
# Convert local coords to world coords
|
||||
world_x = det.x_local + tile_xmin
|
||||
world_y = det.y_local + tile_ymin
|
||||
|
||||
# Convert to pixel coords
|
||||
col = int((world_x - geotransform[0]) / geotransform[1])
|
||||
row = int((world_y - geotransform[3]) / geotransform[5])
|
||||
|
||||
# Apply buffer
|
||||
buffer_pixels = int(buffer_m / pixel_size) + 1
|
||||
|
||||
row_min = max(0, row - buffer_pixels)
|
||||
row_max = min(shape[0], row + buffer_pixels + 1)
|
||||
col_min = max(0, col - buffer_pixels)
|
||||
col_max = min(shape[1], col + buffer_pixels + 1)
|
||||
|
||||
mask[row_min:row_max, col_min:col_max] = True
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def _detect_local_maxima(
|
||||
chm: np.ndarray,
|
||||
min_height: float,
|
||||
window_size: int = 5,
|
||||
) -> List[Tuple[int, int, float]]:
|
||||
"""Detect local maxima in canopy height model.
|
||||
|
||||
Args:
|
||||
chm: Canopy height model array
|
||||
min_height: Minimum tree height
|
||||
window_size: Search window size
|
||||
|
||||
Returns:
|
||||
List of (row, col, height) tuples
|
||||
"""
|
||||
from scipy import ndimage
|
||||
|
||||
# Apply minimum height threshold
|
||||
chm_thresh = np.where(chm >= min_height, chm, 0)
|
||||
|
||||
# Find local maxima using maximum filter
|
||||
local_max = ndimage.maximum_filter(chm_thresh, size=window_size)
|
||||
peaks = (chm_thresh == local_max) & (chm_thresh > 0)
|
||||
|
||||
# Get peak locations
|
||||
rows, cols = np.where(peaks)
|
||||
heights = chm[rows, cols]
|
||||
|
||||
return list(zip(rows, cols, heights))
|
||||
|
||||
|
||||
def _sample_ortho_color(
|
||||
ortho_path: str,
|
||||
world_x: float,
|
||||
world_y: float,
|
||||
radius: float,
|
||||
) -> Optional[Tuple[int, int, int]]:
|
||||
"""Sample average color from orthophoto within radius.
|
||||
|
||||
Args:
|
||||
ortho_path: Path to orthophoto JPEG
|
||||
world_x, world_y: World coordinates
|
||||
radius: Sample radius in meters
|
||||
|
||||
Returns:
|
||||
Tuple of (R, G, B) or None if sampling fails
|
||||
"""
|
||||
ds = open_dataset(ortho_path, required=False)
|
||||
if ds is None:
|
||||
return None
|
||||
|
||||
gt = ds.GetGeoTransform()
|
||||
pixel_size = abs(gt[1])
|
||||
|
||||
# Convert to pixel coords
|
||||
center_col = int((world_x - gt[0]) / gt[1])
|
||||
center_row = int((world_y - gt[3]) / gt[5])
|
||||
|
||||
# Compute sample window
|
||||
radius_pixels = int(radius / pixel_size) + 1
|
||||
|
||||
col_min = max(0, center_col - radius_pixels)
|
||||
col_max = min(ds.RasterXSize, center_col + radius_pixels + 1)
|
||||
row_min = max(0, center_row - radius_pixels)
|
||||
row_max = min(ds.RasterYSize, center_row + radius_pixels + 1)
|
||||
|
||||
if col_min >= col_max or row_min >= row_max:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Read RGB bands
|
||||
r = ds.GetRasterBand(1).ReadAsArray(col_min, row_min, col_max - col_min, row_max - row_min)
|
||||
g = ds.GetRasterBand(2).ReadAsArray(col_min, row_min, col_max - col_min, row_max - row_min)
|
||||
b = ds.GetRasterBand(3).ReadAsArray(col_min, row_min, col_max - col_min, row_max - row_min)
|
||||
|
||||
# Create circular mask
|
||||
y_indices, x_indices = np.ogrid[:r.shape[0], :r.shape[1]]
|
||||
center_y = (row_max - row_min) / 2
|
||||
center_x = (col_max - col_min) / 2
|
||||
dist_sq = (y_indices - center_y) ** 2 + (x_indices - center_x) ** 2
|
||||
circle_mask = dist_sq <= radius_pixels ** 2
|
||||
|
||||
if not circle_mask.any():
|
||||
return None
|
||||
|
||||
r_avg = int(np.mean(r[circle_mask]))
|
||||
g_avg = int(np.mean(g[circle_mask]))
|
||||
b_avg = int(np.mean(b[circle_mask]))
|
||||
|
||||
return (r_avg, g_avg, b_avg)
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def detect_trees_in_tile(
|
||||
tile_id: str,
|
||||
xmin: float,
|
||||
ymin: float,
|
||||
xmax: float,
|
||||
ymax: float,
|
||||
cfg: Config,
|
||||
) -> List[EnhancedTree]:
|
||||
"""Detect trees in a single tile with enhanced processing.
|
||||
|
||||
Args:
|
||||
tile_id: Tile identifier
|
||||
xmin, ymin, xmax, ymax: Tile bounds
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
List of enhanced tree detections
|
||||
"""
|
||||
pc_cfg = cfg.pointcloud
|
||||
et_cfg = cfg.trees_enhanced
|
||||
tree_cfg = cfg.trees
|
||||
|
||||
# Load DOM1 and DGM1
|
||||
dom1_pattern = os.path.join(pc_cfg.dom1_dir, f"*{tile_id}*.tif")
|
||||
dom1_files = glob.glob(dom1_pattern)
|
||||
if not dom1_files:
|
||||
print(f"[trees_enhanced] No DOM1 for {tile_id}")
|
||||
return []
|
||||
|
||||
dgm1_pattern = os.path.join(cfg.raw.dgm1_dir, f"*{tile_id}*.tif")
|
||||
dgm1_files = glob.glob(dgm1_pattern)
|
||||
if not dgm1_files:
|
||||
print(f"[trees_enhanced] No DGM1 for {tile_id}")
|
||||
return []
|
||||
|
||||
dom1_ds = open_dataset(dom1_files[0], required=False)
|
||||
dgm1_ds = open_dataset(dgm1_files[0], required=False)
|
||||
if dom1_ds is None or dgm1_ds is None:
|
||||
return []
|
||||
|
||||
dom1 = dom1_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||
dgm1 = dgm1_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
|
||||
gt = dom1_ds.GetGeoTransform()
|
||||
|
||||
# Handle nodata
|
||||
dom1_nodata = dom1_ds.GetRasterBand(1).GetNoDataValue() or -9999
|
||||
dgm1_nodata = dgm1_ds.GetRasterBand(1).GetNoDataValue() or -9999
|
||||
valid_mask = (dom1 > dom1_nodata + 1) & (dgm1 > dgm1_nodata + 1)
|
||||
|
||||
# Compute CHM
|
||||
chm = np.where(valid_mask, dom1 - dgm1, 0)
|
||||
chm = np.clip(chm, 0, None)
|
||||
|
||||
# Load and apply furniture exclusion mask
|
||||
if et_cfg.exclude_furniture:
|
||||
furniture = load_furniture_detections(tile_id, cfg)
|
||||
if furniture:
|
||||
furniture_mask = _create_furniture_mask(
|
||||
furniture, chm.shape, gt, xmin, ymin, buffer_m=2.0
|
||||
)
|
||||
chm = np.where(furniture_mask, 0, chm)
|
||||
|
||||
# Detect tree peaks
|
||||
peaks = _detect_local_maxima(chm, tree_cfg.min_height_m, window_size=5)
|
||||
|
||||
# Limit to max trees
|
||||
if len(peaks) > tree_cfg.max_trees:
|
||||
# Sort by height descending, keep tallest
|
||||
peaks = sorted(peaks, key=lambda p: p[2], reverse=True)[:tree_cfg.max_trees]
|
||||
|
||||
# Find ortho for color sampling
|
||||
ortho_path = os.path.join(cfg.export.ortho_dir, f"{tile_id}.jpg")
|
||||
has_ortho = os.path.exists(ortho_path)
|
||||
|
||||
# Load LPO for height refinement
|
||||
lpo = None
|
||||
if et_cfg.use_lpo_refinement and has_lpo_data(tile_id, pc_cfg.lpo_dir):
|
||||
lpo_file = find_xyz_file(pc_cfg.lpo_dir, tile_id)
|
||||
if lpo_file:
|
||||
lpo = read_xyz_file(lpo_file, bounds=(xmin, ymin, xmax, ymax))
|
||||
|
||||
trees = []
|
||||
pixel_size = abs(gt[1])
|
||||
|
||||
for row, col, height in peaks:
|
||||
# Convert to world coordinates
|
||||
world_x = gt[0] + col * gt[1]
|
||||
world_y = gt[3] + row * gt[5]
|
||||
|
||||
# Local coordinates
|
||||
x_local = world_x - xmin
|
||||
y_local = world_y - ymin
|
||||
|
||||
# Ground elevation
|
||||
z_ground = float(dgm1[row, col])
|
||||
|
||||
# Estimate radius (simple heuristic)
|
||||
radius = height * 0.25
|
||||
|
||||
# Base confidence
|
||||
confidence = 0.6 + 0.4 * min(1.0, height / 30.0)
|
||||
|
||||
# Refine with LPO
|
||||
if lpo is not None and len(lpo) > 0:
|
||||
search_radius = et_cfg.lpo_search_radius_m
|
||||
dist_sq = (lpo.x - world_x) ** 2 + (lpo.y - world_y) ** 2
|
||||
nearby_mask = dist_sq < search_radius ** 2
|
||||
|
||||
if nearby_mask.any():
|
||||
nearby_z = lpo.z[nearby_mask]
|
||||
refined_height = float(np.percentile(nearby_z, 95)) - z_ground
|
||||
if refined_height > tree_cfg.min_height_m:
|
||||
height = refined_height
|
||||
confidence += 0.1
|
||||
|
||||
# Sample canopy color
|
||||
canopy_r, canopy_g, canopy_b = 128, 160, 80 # Default
|
||||
if et_cfg.sample_canopy_color and has_ortho:
|
||||
sample_radius = radius * et_cfg.canopy_sample_radius_factor
|
||||
color = _sample_ortho_color(ortho_path, world_x, world_y, sample_radius)
|
||||
if color:
|
||||
canopy_r, canopy_g, canopy_b = color
|
||||
|
||||
tree = EnhancedTree(
|
||||
tile_id=tile_id,
|
||||
x_local=x_local,
|
||||
y_local=y_local,
|
||||
z_ground=z_ground,
|
||||
height=height,
|
||||
radius=radius,
|
||||
confidence=min(confidence, 1.0),
|
||||
canopy_r=canopy_r,
|
||||
canopy_g=canopy_g,
|
||||
canopy_b=canopy_b,
|
||||
)
|
||||
trees.append(tree)
|
||||
|
||||
return trees
|
||||
|
||||
|
||||
def export_trees_csv(
|
||||
trees: List[EnhancedTree],
|
||||
tile_id: str,
|
||||
cfg: Config,
|
||||
) -> str:
|
||||
"""Export enhanced trees to CSV.
|
||||
|
||||
Args:
|
||||
trees: List of tree detections
|
||||
tile_id: Tile identifier
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Path to CSV file
|
||||
"""
|
||||
os.makedirs(cfg.trees_enhanced.csv_dir, exist_ok=True)
|
||||
csv_path = os.path.join(cfg.trees_enhanced.csv_dir, f"{tile_id}.csv")
|
||||
|
||||
with open(csv_path, "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"tile_id", "x_local", "y_local", "z_ground",
|
||||
"height", "radius", "confidence",
|
||||
"canopy_r", "canopy_g", "canopy_b"
|
||||
])
|
||||
for tree in trees:
|
||||
writer.writerow(tree.to_csv_row())
|
||||
|
||||
return csv_path
|
||||
|
||||
|
||||
def export_trees_enhanced(cfg: Config) -> int:
|
||||
"""Export enhanced tree detections for all tiles.
|
||||
|
||||
Args:
|
||||
cfg: Configuration
|
||||
|
||||
Returns:
|
||||
Exit code (0 = success)
|
||||
"""
|
||||
# Check for manifest
|
||||
if not os.path.exists(cfg.export.manifest_path):
|
||||
print(f"[trees_enhanced] ERROR: Manifest not found: {cfg.export.manifest_path}")
|
||||
print("[trees_enhanced] Run heightmap export first.")
|
||||
return 1
|
||||
|
||||
# Load tiles from manifest
|
||||
tiles = []
|
||||
with open(cfg.export.manifest_path, "r") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
tiles.append({
|
||||
"tile_id": row["tile_id"],
|
||||
"xmin": float(row["xmin"]),
|
||||
"ymin": float(row["ymin"]),
|
||||
"xmax": float(row["xmax"]),
|
||||
"ymax": float(row["ymax"]),
|
||||
})
|
||||
|
||||
if not tiles:
|
||||
print("[trees_enhanced] No tiles in manifest.")
|
||||
return 1
|
||||
|
||||
os.makedirs(cfg.trees_enhanced.csv_dir, exist_ok=True)
|
||||
|
||||
total_trees = 0
|
||||
for tile in tiles:
|
||||
tile_id = tile["tile_id"]
|
||||
print(f"[trees_enhanced] Processing {tile_id}...")
|
||||
|
||||
trees = detect_trees_in_tile(
|
||||
tile_id,
|
||||
tile["xmin"],
|
||||
tile["ymin"],
|
||||
tile["xmax"],
|
||||
tile["ymax"],
|
||||
cfg,
|
||||
)
|
||||
|
||||
csv_path = export_trees_csv(trees, tile_id, cfg)
|
||||
print(f"[trees_enhanced] {tile_id}: {len(trees)} trees -> {csv_path}")
|
||||
total_trees += len(trees)
|
||||
|
||||
print(f"[trees_enhanced] DONE. Total trees: {total_trees}")
|
||||
return 0
|
||||
@@ -9,10 +9,14 @@ from typing import Iterable
|
||||
|
||||
from geodata_pipeline.config import Config, DEFAULT_CONFIG_PATH, ensure_default_config
|
||||
from geodata_pipeline.buildings import export_buildings
|
||||
from geodata_pipeline.buildings_enhanced import export_buildings_enhanced
|
||||
from geodata_pipeline.heightmaps import export_heightmaps
|
||||
from geodata_pipeline.heightmaps_enhanced import export_heightmaps_enhanced
|
||||
from geodata_pipeline.orthophotos import export_orthophotos
|
||||
from geodata_pipeline.setup_helpers import ensure_directories, materialize_archives
|
||||
from geodata_pipeline.street_furniture import export_street_furniture
|
||||
from geodata_pipeline.trees import export_trees
|
||||
from geodata_pipeline.trees_enhanced import export_trees_enhanced
|
||||
|
||||
|
||||
def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace:
|
||||
@@ -24,9 +28,13 @@ def parse_args(argv: Iterable[str] | None = None) -> argparse.Namespace:
|
||||
)
|
||||
parser.add_argument(
|
||||
"--export",
|
||||
choices=["heightmap", "textures", "buildings", "trees", "all"],
|
||||
choices=[
|
||||
"heightmap", "textures", "buildings", "trees", "all",
|
||||
"heightmap-enhanced", "buildings-enhanced", "trees-enhanced",
|
||||
"street-furniture", "all-enhanced"
|
||||
],
|
||||
default=None,
|
||||
help="Which assets to export (default: all; skipped on --setup unless explicitly set).",
|
||||
help="Which assets to export. Enhanced options use point cloud data.",
|
||||
)
|
||||
parser.add_argument("--raw-dgm1-path", dest="raw_dgm1_path", help="Override raw DGM1 directory.")
|
||||
parser.add_argument("--raw-dop20-path", dest="raw_dop20_path", help="Override raw DOP20 JP2 directory.")
|
||||
@@ -78,6 +86,8 @@ def main(argv: Iterable[str] | None = None) -> int:
|
||||
materialize_archives(cfg)
|
||||
|
||||
exit_codes = []
|
||||
|
||||
# Standard exports
|
||||
if target_export in ("heightmap", "all"):
|
||||
exit_codes.append(export_heightmaps(cfg, force_vrt=args.force_vrt))
|
||||
if target_export in ("textures", "all"):
|
||||
@@ -87,6 +97,20 @@ def main(argv: Iterable[str] | None = None) -> int:
|
||||
if target_export in ("trees", "all"):
|
||||
exit_codes.append(export_trees(cfg))
|
||||
|
||||
# Enhanced exports (use point cloud data)
|
||||
# Order matters: heightmap-enhanced creates tile_index.csv needed by others
|
||||
# street-furniture must run before trees-enhanced (for exclusion mask)
|
||||
if target_export in ("heightmap-enhanced", "all-enhanced"):
|
||||
exit_codes.append(export_heightmaps_enhanced(cfg))
|
||||
if target_export in ("all-enhanced",):
|
||||
exit_codes.append(export_orthophotos(cfg, force_vrt=args.force_vrt))
|
||||
if target_export in ("street-furniture", "all-enhanced"):
|
||||
exit_codes.append(export_street_furniture(cfg))
|
||||
if target_export in ("buildings-enhanced", "all-enhanced"):
|
||||
exit_codes.append(export_buildings_enhanced(cfg))
|
||||
if target_export in ("trees-enhanced", "all-enhanced"):
|
||||
exit_codes.append(export_trees_enhanced(cfg))
|
||||
|
||||
return max(exit_codes) if exit_codes else 0
|
||||
|
||||
|
||||
|
||||
@@ -3,8 +3,17 @@ name = "geodata-toolkit"
|
||||
version = "0.1.0"
|
||||
description = "Heightmap and orthophoto exporters using GDAL for Unity terrains."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9,<3.11"
|
||||
dependencies = ["gdal>=3.4", "cjio[export,reproject]>=0.9"]
|
||||
requires-python = ">=3.10,<3.13"
|
||||
dependencies = [
|
||||
"gdal>=3.4",
|
||||
"cjio[export,reproject]>=0.9",
|
||||
"laspy[lazrs]>=2.5",
|
||||
"scipy>=1.11",
|
||||
"scikit-learn>=1.3",
|
||||
"shapely>=2.0",
|
||||
"numpy>=1.24",
|
||||
"trimesh>=4.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
|
||||
477
uv.lock
generated
477
uv.lock
generated
@@ -1,9 +1,10 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.9, <3.11"
|
||||
requires-python = ">=3.10, <3.13"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
"python_full_version < '3.10'",
|
||||
"python_full_version >= '3.12'",
|
||||
"python_full_version == '3.11.*'",
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -20,10 +21,8 @@ name = "cjio"
|
||||
version = "0.10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "click", version = "8.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "click" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/69/33/125385a9e3438b3d31dcb2d788719ca4e929e98675c7a8c1e2e5507c898f/cjio-0.10.1.tar.gz", hash = "sha256:8788a1333bcac3dc74fc3fa4c9f220b3015a742eb4c27af55161ec077a75fc44", size = 48480, upload-time = "2025-05-09T17:31:20.614Z" }
|
||||
wheels = [
|
||||
@@ -37,34 +36,15 @@ export = [
|
||||
{ name = "triangle2" },
|
||||
]
|
||||
reproject = [
|
||||
{ name = "pyproj", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "pyproj", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
|
||||
{ name = "pyproj" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
||||
wheels = [
|
||||
@@ -93,12 +73,78 @@ source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "cjio", extra = ["export", "reproject"] },
|
||||
{ name = "gdal" },
|
||||
{ name = "laspy", extra = ["lazrs"] },
|
||||
{ name = "numpy" },
|
||||
{ name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "shapely" },
|
||||
{ name = "trimesh" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "cjio", extras = ["export", "reproject"], specifier = ">=0.9" },
|
||||
{ name = "gdal", specifier = ">=3.4" },
|
||||
{ name = "laspy", extras = ["lazrs"], specifier = ">=2.5" },
|
||||
{ name = "numpy", specifier = ">=1.24" },
|
||||
{ name = "scikit-learn", specifier = ">=1.3" },
|
||||
{ name = "scipy", specifier = ">=1.11" },
|
||||
{ name = "shapely", specifier = ">=2.0" },
|
||||
{ name = "trimesh", specifier = ">=4.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "joblib"
|
||||
version = "1.5.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "laspy"
|
||||
version = "2.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/54/b6387699d944653955fcb80ceb2bb2a9d5fc8a9a56acccb4a45d16b29ef3/laspy-2.6.1.tar.gz", hash = "sha256:ce9cb9a18528b2a2b985583df40a4dea68cdda7995e47e4b00b6d48df0e88daa", size = 1940211, upload-time = "2025-07-07T19:49:32.873Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/28/d1/c3d09cadb41b6d7381a01e41db70419b21c9ccb3cc8ab1e3a0bd37397d82/laspy-2.6.1-py3-none-any.whl", hash = "sha256:44c4d3c38fcef81cdb9201a0b98e5e4f09831c98d2ec1335b9ee59da16a37349", size = 86053, upload-time = "2025-07-07T19:49:08.842Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
lazrs = [
|
||||
{ name = "lazrs" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazrs"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9b/37/169d27b57af14b0f5992debf1477a1d7f1497d738432b9f383b271e739e4/lazrs-0.7.0.tar.gz", hash = "sha256:53191b351c1d9fa45f74471698384bf42bde14599309645fb9d4c353f0fb7f24", size = 10098, upload-time = "2025-06-06T15:58:13.026Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/9c/eab617a474e65421967faae0c3169f3f0ab0b10646feb6b95d5a5e8d2abe/lazrs-0.7.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:19d9897941c1a3c54198cb9db19526d7840bd7f82487e3542d28c5be80c6ded1", size = 569479, upload-time = "2025-06-06T15:57:36.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/e3/41aaebf1d31e9750d9f68d79e2aea245d97ee088a53375b42af62f232542/lazrs-0.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:834ef7f828044d8322fa53ee2f2b9bcd615f2bdf0c123504f622e54cb4f98836", size = 570906, upload-time = "2025-06-06T15:57:38.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/a5/1f2bc60b3ccb5e349333d2212af653d775ebb047d73ac47e71857d26a85c/lazrs-0.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e64dadfafcea7bf9df6f27d9439f5f75eae22a280399cc0de735abaa4bcb6f72", size = 633428, upload-time = "2025-06-06T15:57:39.747Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/28/d1dcc28c35c8a913821edb76b55d988b4f20ef50317304d5732eae869861/lazrs-0.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:376d8ca61cb00580a1a7e68aa1dc8a80d230953b255c7b0f16abae7f4edb4391", size = 639937, upload-time = "2025-06-06T15:57:41.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/60/ff29fc539824c9c2a5727639ec7dae12ce68bd9534b2166d1d935a7099b6/lazrs-0.7.0-cp310-cp310-win32.whl", hash = "sha256:f469ee4847214a3f901c8419213cd87b9ebeb22308af8253faad45bde8fc34b2", size = 407819, upload-time = "2025-06-06T15:57:42.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/4e/132051aa292353cf8929a2c0ce90681eb877e9fb2cdb7b7de523af380d38/lazrs-0.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1bd15a9ddec761a356d65b3ce819336ffdfcc3a79fd3b34272709f0d7c00ad5", size = 421002, upload-time = "2025-06-06T15:57:43.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/c6/506f7222db694f93384fee5fe420041062327d35247831e3ce458219fe41/lazrs-0.7.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4236ace28a55d8b1b6d181ea2390a4a57de896587b914ea21a7e887cd493e87f", size = 569443, upload-time = "2025-06-06T15:57:46.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/68/bc71647430e852070b6a2cf62117843daa1955ff10311ee5857eda3f3304/lazrs-0.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:42e943f7859c5f31067f28e5766f3a23237b3c3246b57c98ad4b765ccad69b11", size = 570900, upload-time = "2025-06-06T15:57:47.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/53/534844b7f7034dea47db0c445d5b6a6c3d40330e45db28a5faeab386ff7e/lazrs-0.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17a2dde6acbc77e13981f6d85b7c8e994b0004e63abb15602dc2b65d1301550d", size = 632368, upload-time = "2025-06-06T15:57:49.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/22/f4d6b91e596d2822cc2d7164028ff0152068f369c7be6123030084bc9841/lazrs-0.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17369fd3b8518f2f35f342f3802a373cb722e39a8ed0353d2d37632f6210145d", size = 639576, upload-time = "2025-06-06T15:57:51.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/0d/bbf46cbda2ef857887411369ac40ff45f2ab3a90e55d535a76801cea34cf/lazrs-0.7.0-cp311-cp311-win32.whl", hash = "sha256:b89c2cfdf38264356549d73246fb4a490add3e9cddd38b0334633cc0f5a529ee", size = 408012, upload-time = "2025-06-06T15:57:52.39Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/b9/bbf92491e1572826e17918cf97f544f14805b0a37c761a4f11f89ff8d8f2/lazrs-0.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:c09e1adaa6ba3a6a5b1287bdb3b4b88500ad0a1d7847ee0902041175e16fa5aa", size = 421304, upload-time = "2025-06-06T15:57:53.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/bf/303d94c87d6195f395ad7fa685728aafeb9817c8df9f876e668530f64e6a/lazrs-0.7.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a6fc38ef1b1226ebfe86bcc9ee3b07856f51baf91be6be6e958b1c88807460d6", size = 569011, upload-time = "2025-06-06T15:57:56.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/1c/dfde2c8bcc122355596398317adbf46c223ba987e64435c7706e00cba092/lazrs-0.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a05f25e15ac8fcbedc9cf513258f0f7631d029275b2e86a424af4638d39142f3", size = 570823, upload-time = "2025-06-06T15:57:57.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/88/f0e168b4062cce9850e26fbd9378aa8c90a5fdf292be5fec5598c5d8da3d/lazrs-0.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e1e59591f290e9400acfff9380a43c71b91db5c740f7b303c2ca17c289574ab", size = 631060, upload-time = "2025-06-06T15:57:59.359Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/c2/fc35531ee1b1dad2912e3c4b54e2a862ee037d4ce963df8f7608a6ef8369/lazrs-0.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:051503a48d2db393230adc5442b484edcc1c8d089d8cc82dbb079faf6b2477c8", size = 638735, upload-time = "2025-06-06T15:58:01.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/c5/a5bf2275c1aa426232ba39bf09ed331c4198e5c1325e756e0e0b6691dccc/lazrs-0.7.0-cp312-cp312-win32.whl", hash = "sha256:1cfa3857dc19acc3fa56e0d481d7114254999c2620b29c9d53fc4358d62d5e0b", size = 408821, upload-time = "2025-06-06T15:58:02.495Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/62/e24db76c60d358207b224d9b0063748ae1abbc9cb0a1a35b6e14fca8dff9/lazrs-0.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:b908d90ed3872424f93c0c22f89568f08ec9af9b4fc6811b9978c5f97b018dfc", size = 420615, upload-time = "2025-06-06T15:58:04.405Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -106,8 +152,7 @@ name = "mapbox-earcut"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" }
|
||||
wheels = [
|
||||
@@ -119,58 +164,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/6a/d39ebaaa9010ea6c9f4d468f8812b1a1b31a40fba4f02ff29bc1bf321c30/mapbox_earcut-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6e2d1bf5af90d5857955775b4d8ea15b02e172f2a8f194bba50ff95f8ff3e80e", size = 157736, upload-time = "2025-11-16T18:40:16.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/00/6a59cdb8d8c1bf7e3cc92f0404f68fdb1a3cb0bbb0837af0dbb93d6290a6/mapbox_earcut-2.0.0-cp310-cp310-win32.whl", hash = "sha256:5b0aa63dd890d712343095b05eb7b60e071912ad3ced1fc4187d6a6a739677bc", size = 51564, upload-time = "2025-11-16T18:40:17.852Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/7b/af69669c959d8f7fd1bd49c15deace2360bf6a79dad7bf9f7a7f1c137da6/mapbox_earcut-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b1355f13af89ea815b32f59a5455db295c965d51ab501bde0459cddc010a7149", size = 56793, upload-time = "2025-11-16T18:40:18.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/ec/25491ab17b70e909e6cd69cc9e0b73f2636686031fb0d15995bf2bf9fbf1/mapbox_earcut-2.0.0-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:6717796a5923ae57b3f805db4b2876b5dc90d4dbabc0585de9b45fcda7fa33af", size = 55962, upload-time = "2025-11-16T18:41:16.955Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/1e/67d66790422e92e712d2b8ed623909ff1ef4f23dab2098fd0b09cfa7a7c7/mapbox_earcut-2.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:008bbf38abb0112736b2522fa68553a92a51829d3a578bc1cd7e7c7cf7f6be6c", size = 52583, upload-time = "2025-11-16T18:41:18.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/95/fd3c5ae233a5173a714969cc5ebecd9772fcea21292ae6ab976147164c0e/mapbox_earcut-2.0.0-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68582876cdc7c420e452a8eec9cdbbeaf52b652d46481524c3469f452758b1b0", size = 56998, upload-time = "2025-11-16T18:41:19.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/09/915c7b534761d39588d3d825a2d781cbd974d7b1a5a8304f173a4768be51/mapbox_earcut-2.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4a90f8bcebbc4eacb18270f42a3bb4d2b37394fc738469fa7454cdf3c0e9e9e", size = 59691, upload-time = "2025-11-16T18:41:20.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/f7/00c887aa41d9bb241b056c436657860f93d4851491e334be0dc60ac53eb1/mapbox_earcut-2.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c5472c11973ea1d1f5cfa76987a3b0885019a16d25b9a4d67b645fb73cb35328", size = 153114, upload-time = "2025-11-16T18:41:22.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/a7/362c4d0ef31e41a633120b6ff3055b1cb077af7168b5b7695da5a07b6591/mapbox_earcut-2.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1192327e316df76649776de344356f3ced1625722f4c3ea86955d3233aadbda5", size = 157734, upload-time = "2025-11-16T18:41:23.428Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/aa/03be318d4fae7f0fa7250fcfe51ef74f72ca3eebb895b6b21630c4642cdd/mapbox_earcut-2.0.0-cp39-cp39-win32.whl", hash = "sha256:dd9699f99b461c8f540ae451cc1a399014275a082c44fb39f8b2e727d0f2e0ec", size = 51986, upload-time = "2025-11-16T18:41:24.694Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/98/01f397f28e0543554c5b79fe072f546ad9f9d7f0f20367538c9cdfb7fffd/mapbox_earcut-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:f6bc47aa17b753400151983bd00c1a5e74bb2b577fb56631bece4c6e4fff50c8", size = 57092, upload-time = "2025-11-16T18:41:26.112Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a9/75/10dd1f8116a8b796cb2c737b674e02d02e80454bda953fa7e65d8c12b016/numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78", size = 18902015, upload-time = "2024-08-26T20:19:40.945Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/21/91/3495b3237510f79f5d81f2508f9f13fea78ebfdf07538fc7444badda173d/numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece", size = 21165245, upload-time = "2024-08-26T20:04:14.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/33/26178c7d437a87082d11019292dce6d3fe6f0e9026b7b2309cbf3e489b1d/numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04", size = 13738540, upload-time = "2024-08-26T20:04:36.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/31/cc46e13bf07644efc7a4bf68df2df5fb2a1a88d0cd0da9ddc84dc0033e51/numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66", size = 5300623, upload-time = "2024-08-26T20:04:46.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/16/7bfcebf27bb4f9d7ec67332ffebee4d1bf085c84246552d52dbb548600e7/numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b", size = 6901774, upload-time = "2024-08-26T20:04:58.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/a3/561c531c0e8bf082c5bef509d00d56f82e0ea7e1e3e3a7fc8fa78742a6e5/numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd", size = 13907081, upload-time = "2024-08-26T20:05:19.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/66/f7177ab331876200ac7563a580140643d1179c8b4b6a6b0fc9838de2a9b8/numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318", size = 19523451, upload-time = "2024-08-26T20:05:47.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/7f/0b209498009ad6453e4efc2c65bcdf0ae08a182b2b7877d7ab38a92dc542/numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8", size = 19927572, upload-time = "2024-08-26T20:06:17.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/df/2619393b1e1b565cd2d4c4403bdd979621e2c4dea1f8532754b2598ed63b/numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326", size = 14400722, upload-time = "2024-08-26T20:06:39.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ad/77e921b9f256d5da36424ffb711ae79ca3f451ff8489eeca544d0701d74a/numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97", size = 6472170, upload-time = "2024-08-26T20:06:50.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/05/3442317535028bc29cf0c0dd4c191a4481e8376e9f0db6bcf29703cadae6/numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131", size = 15905558, upload-time = "2024-08-26T20:07:13.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/c1/41c8f6df3162b0c6ffd4437d729115704bd43363de0090c7f913cfbc2d89/numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c", size = 21169942, upload-time = "2024-08-26T20:14:40.108Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/bc/fd298f308dcd232b56a4031fd6ddf11c43f9917fbc937e53762f7b5a3bb1/numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd", size = 13711512, upload-time = "2024-08-26T20:15:00.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/ff/06d1aa3eeb1c614eda245c1ba4fb88c483bee6520d361641331872ac4b82/numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b", size = 5306976, upload-time = "2024-08-26T20:15:10.876Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/98/121996dcfb10a6087a05e54453e28e58694a7db62c5a5a29cee14c6e047b/numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729", size = 6906494, upload-time = "2024-08-26T20:15:22.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/31/9dffc70da6b9bbf7968f6551967fc21156207366272c2a40b4ed6008dc9b/numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1", size = 13912596, upload-time = "2024-08-26T20:15:42.452Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/14/78635daab4b07c0930c919d451b8bf8c164774e6a3413aed04a6d95758ce/numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd", size = 19526099, upload-time = "2024-08-26T20:16:11.048Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/4c/0eeca4614003077f68bfe7aac8b7496f04221865b3a5e7cb230c9d055afd/numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d", size = 19932823, upload-time = "2024-08-26T20:16:40.171Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/46/ea25b98b13dccaebddf1a803f8c748680d972e00507cd9bc6dcdb5aa2ac1/numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d", size = 14404424, upload-time = "2024-08-26T20:17:02.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/a6/177dd88d95ecf07e722d21008b1b40e681a929eb9e329684d449c36586b2/numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa", size = 6476809, upload-time = "2024-08-26T20:17:13.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/2b/7fc9f4e7ae5b507c1a3a21f0f15ed03e794c1242ea8a242ac158beb56034/numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73", size = 15911314, upload-time = "2024-08-26T20:17:36.72Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/3b/df5a870ac6a3be3a86856ce195ef42eec7ae50d2a202be1f5a4b3b340e14/numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8", size = 21025288, upload-time = "2024-08-26T20:18:07.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/97/51af92f18d6f6f2d9ad8b482a99fb74e142d71372da5d834b3a2747a446e/numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4", size = 6762793, upload-time = "2024-08-26T20:18:19.125Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/46/de1fbd0c1b5ccaa7f9a005b66761533e2f6a3e560096682683a223631fe9/numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c", size = 19334885, upload-time = "2024-08-26T20:18:47.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/dc/d330a6faefd92b446ec0f0dfea4c3207bb1fef3c4771d19cf4543efd2c78/numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385", size = 15828784, upload-time = "2024-08-26T20:19:11.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/9f/fbd15d9e348e75e986d6912c4eab99888106b7e5fb0a01e765422f7cd464/mapbox_earcut-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:9b5040e79e3783295e99c90277f31c1cbaddd3335297275331995ba5680e3649", size = 55773, upload-time = "2025-11-16T18:40:20.045Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/40/be761298704fbbaa81c5618bb306f1510fb068e482f6a1c8b3b6c1b31479/mapbox_earcut-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf43baafec3ef1e967319d9b5da96bc6ddf3dbb204b6f3535275eda4b519a72", size = 52444, upload-time = "2025-11-16T18:40:21.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/0b/0c0c08db9663238ffb82c48259582dc0047a3255d98c0ac83c48026b7544/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a283531847f603dd9d69afb75b21bd009d385ca9485fcd3e5a7fa5db1ccd913", size = 56803, upload-time = "2025-11-16T18:40:22.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/4a/86796859383d7d11fa5d4bcf1983f94c6cbb9eeb60fb3bab527fec4b32fa/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab697676f4cec4572d4e941b7a3429a6687bf2ac6e8db3f3781024e3239ae3a0", size = 59403, upload-time = "2025-11-16T18:40:24.021Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/db/adaf981ab3bcfcf993ef317636b1f27210d6834bb1e8d63db6ad7c08214a/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1bdac76e048f4299accf4eaf797079ddfc330442e7231c15535ed198100d6c5", size = 152876, upload-time = "2025-11-16T18:40:25.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/83/86417974039e7554c9e1e55c852a7e9c2a1390d64675eb85d70e5fa7eb37/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a6945b23f859bef11ce3194303d17bd371c86b637e7029f81b1feaff3db3758", size = 157548, upload-time = "2025-11-16T18:40:27.202Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/4c/c82a292bb21e5c651d81334123db2d654c5c9d19b2197080d3429dc1e49a/mapbox_earcut-2.0.0-cp311-cp311-win32.whl", hash = "sha256:8e119524c29406afb5eaa15e933f297d35679293a3ca62ced22f97a14c484cb5", size = 51424, upload-time = "2025-11-16T18:40:28.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/57/6c39d7db81f72a3e4814ef152c8fb8dfe275dc4b03c9bfa073d251e3755f/mapbox_earcut-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:378bbbb3304e446023752db8f44ecd6e7ef965bcbda36541d2ae64442ba94254", size = 56662, upload-time = "2025-11-16T18:40:29.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d6/a1ef6e196b3d6968bf6546d4f7e54c559f9cff8991fdb880df0ba1618f52/mapbox_earcut-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:6d249a431abd6bbff36f1fd0493247a86de962244cc4081b4d5050b02ed48fb1", size = 50505, upload-time = "2025-11-16T18:40:30.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/f7/5cdd3752526e91d91336c7263af7767b291d21e63c89d7190a60051f0f87/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de35f241d0b9110ad9260f295acedd9d7cc0d7acfe30d36b1b3ee8419c2caba1", size = 59209, upload-time = "2025-11-16T18:40:36.634Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/a2/b7781416cb93b37b95d0444e03f87184de8815e57ff202ce4105fa921325/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cb63ab85e2e430c350f93e75c13f8b91cb8c8a045f3cd714c390b69a720368a", size = 152316, upload-time = "2025-11-16T18:40:38.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/74/396338e3d345e4e36fb23a0380921098b6a95ce7fb19c4777f4185a5974e/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb3c9f069fc3795306db87f8139f70c4f047532f897a3de05f54dc1faebc97f6", size = 157268, upload-time = "2025-11-16T18:40:39.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/2c/66fd137ea86c508f6cd7247f7f6e2d1dabffc9f0e9ccf14c71406b197af1/mapbox_earcut-2.0.0-cp312-cp312-win32.whl", hash = "sha256:eb290e6676217707ed238dd55e07b0a8ca3ab928f6a27c4afefb2ff3af08d7cb", size = 51226, upload-time = "2025-11-16T18:40:41.018Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/84/7b78e37b0c2109243c0dad7d9ba9774b02fcee228bf61cf727a5aa1702e2/mapbox_earcut-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ef5b3319a43375272ad2cad9333ed16e569b5102e32a4241451358897e6f6ee", size = 56417, upload-time = "2025-11-16T18:40:42.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/7f/cd7195aa27c1c8f2b9d38025a5a8663f32cd01c07b648a54b1308ab26c15/mapbox_earcut-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4a3706feb5cc8c782d8f68bb0110c8d551304043f680a87a54b0651a2c208c3", size = 50111, upload-time = "2025-11-16T18:40:43.334Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
|
||||
@@ -183,6 +200,26 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
|
||||
@@ -194,8 +231,7 @@ name = "pandas"
|
||||
version = "2.3.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "tzdata" },
|
||||
@@ -209,52 +245,28 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/b4/52eeb530a99e2a4c55ffcd352772b599ed4473a0f892d127f4147cf0f88e/pandas-2.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", size = 11567720, upload-time = "2025-09-29T23:33:06.209Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/4a/2d8b67632a021bced649ba940455ed441ca854e57d6e7658a6024587b083/pandas-2.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", size = 10810302, upload-time = "2025-09-29T23:33:35.846Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/e6/d2465010ee0569a245c975dc6967b801887068bc893e908239b1f4b6c1ac/pandas-2.3.3-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", size = 12154874, upload-time = "2025-09-29T23:33:49.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/18/aae8c0aa69a386a3255940e9317f793808ea79d0a525a97a903366bb2569/pandas-2.3.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", size = 12790141, upload-time = "2025-09-29T23:34:05.655Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/26/617f98de789de00c2a444fbe6301bb19e66556ac78cff933d2c98f62f2b4/pandas-2.3.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", size = 13208697, upload-time = "2025-09-29T23:34:21.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/fb/25709afa4552042bd0e15717c75e9b4a2294c3dc4f7e6ea50f03c5136600/pandas-2.3.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", size = 13879233, upload-time = "2025-09-29T23:34:35.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/af/7be05277859a7bc399da8ba68b88c96b27b48740b6cf49688899c6eb4176/pandas-2.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", size = 11359119, upload-time = "2025-09-29T23:34:46.339Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyproj"
|
||||
version = "3.6.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "certifi", marker = "python_full_version < '3.10'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/84/2b39bbf888c753ea48b40d47511548c77aa03445465c35cc4c4e9649b643/pyproj-3.6.1.tar.gz", hash = "sha256:44aa7c704c2b7d8fb3d483bbf75af6cb2350d30a63b144279a09b75fead501bf", size = 225131, upload-time = "2023-09-21T02:07:51.593Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/32/63cf474f4a8d4804b3bdf7c16b8589f38142e8e2f8319dcea27e0bc21a87/pyproj-3.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab7aa4d9ff3c3acf60d4b285ccec134167a948df02347585fdd934ebad8811b4", size = 6142763, upload-time = "2023-09-21T02:07:12.844Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/86/2e7cb9de40492f1bafbf11f4c9072edc394509a40b5e4c52f8139546f039/pyproj-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc0472302919e59114aa140fd7213c2370d848a7249d09704f10f5b062031fe", size = 4877123, upload-time = "2023-09-21T02:10:37.905Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/c5/928d5a26995dbefbebd7507d982141cd9153bc7e4392b334fff722c4af12/pyproj-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5279586013b8d6582e22b6f9e30c49796966770389a9d5b85e25a4223286cd3f", size = 6190576, upload-time = "2023-09-21T02:17:08.637Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/2b/b60cf73b0720abca313bfffef34e34f7f7dae23852b2853cf0368d49426b/pyproj-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fafd1f3eb421694857f254a9bdbacd1eb22fc6c24ca74b136679f376f97d35", size = 8328075, upload-time = "2023-09-21T02:07:15.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/a8/7193f46032636be917bc775506ae987aad72c931b1f691b775ca812a2917/pyproj-3.6.1-cp310-cp310-win32.whl", hash = "sha256:c41e80ddee130450dcb8829af7118f1ab69eaf8169c4bf0ee8d52b72f098dc2f", size = 5635713, upload-time = "2023-09-21T02:07:17.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/8f/27350c8fba71a37cd0d316f100fbd96bf139cc2b5ff1ab0dcbc7ac64010a/pyproj-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:db3aedd458e7f7f21d8176f0a1d924f1ae06d725228302b872885a1c34f3119e", size = 6087932, upload-time = "2023-09-21T02:07:19.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/50/d369bbe62d7a0d1e2cb40bc211da86a3f6e0f3c99f872957a72c3d5492d6/pyproj-3.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4ba1f9b03d04d8cab24d6375609070580a26ce76eaed54631f03bab00a9c737b", size = 6144755, upload-time = "2023-09-21T02:07:39.611Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/c2/8d4f61065dfed965e53badd41201ad86a05af0c1bbc75dffb12ef0f5a7dd/pyproj-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18faa54a3ca475bfe6255156f2f2874e9a1c8917b0004eee9f664b86ccc513d3", size = 4879187, upload-time = "2023-09-21T02:10:45.519Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/38/2cf8777cb2d5622a78195e690281b7029098795fde4751aec8128238b8bb/pyproj-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd43bd9a9b9239805f406fd82ba6b106bf4838d9ef37c167d3ed70383943ade1", size = 6192339, upload-time = "2023-09-21T02:17:09.942Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/0a/b1525be9680369cc06dd288e12c59d24d5798b4afcdcf1b0915836e1caa6/pyproj-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50100b2726a3ca946906cbaa789dd0749f213abf0cbb877e6de72ca7aa50e1ae", size = 8332638, upload-time = "2023-09-21T02:07:41.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/e8/e826e0a962f36bd925a933829cf6ef218efe2055db5ea292be40974a929d/pyproj-3.6.1-cp39-cp39-win32.whl", hash = "sha256:9274880263256f6292ff644ca92c46d96aa7e57a75c6df3f11d636ce845a1877", size = 5638159, upload-time = "2023-09-21T02:07:43.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/d0/cbe29a4dcf38ee7e72bf695d0d3f2bee21b4f22ee6cf579ad974de9edfc8/pyproj-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:36b64c2cb6ea1cc091f329c5bd34f9c01bb5da8c8e4492c709bda6a09f96808f", size = 6090565, upload-time = "2023-09-21T02:07:45.735Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/28/e8d2ca71dd56c27cbe668e4226963d61956cded222a2e839e6fec1ab6d82/pyproj-3.6.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd93c1a0c6c4aedc77c0fe275a9f2aba4d59b8acf88cebfc19fe3c430cfabf4f", size = 6034252, upload-time = "2023-09-21T02:07:47.906Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/39/1ce27cb86f51a1f5aed3a1617802a6131b59ea78492141d1fbe36722595e/pyproj-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6420ea8e7d2a88cb148b124429fba8cd2e0fae700a2d96eab7083c0928a85110", size = 6386263, upload-time = "2023-09-21T02:07:49.586Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyproj"
|
||||
version = "3.7.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.10'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "certifi", marker = "python_full_version >= '3.10'" },
|
||||
{ name = "certifi" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/10/a8480ea27ea4bbe896c168808854d00f2a9b49f95c0319ddcbba693c8a90/pyproj-3.7.1.tar.gz", hash = "sha256:60d72facd7b6b79853f19744779abcd3f804c4e0d4fa8815469db20c9f640a47", size = 226339, upload-time = "2025-02-16T04:28:46.621Z" }
|
||||
wheels = [
|
||||
@@ -266,6 +278,22 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/73/c9194c2802fefe2a4fd4230bdd5ab083e7604e93c64d0356fa49c363bad6/pyproj-3.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f173f851ee75e54acdaa053382b6825b400cb2085663a9bb073728a59c60aebb", size = 10401391, upload-time = "2025-02-16T04:27:36.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/1d/ce8bb5b9251b04d7c22d63619bb3db3d2397f79000a9ae05b3fd86a5837e/pyproj-3.7.1-cp310-cp310-win32.whl", hash = "sha256:f550281ed6e5ea88fcf04a7c6154e246d5714be495c50c9e8e6b12d3fb63e158", size = 5869997, upload-time = "2025-02-16T04:27:38.302Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/6a/ca145467fd2e5b21e3d5b8c2b9645dcfb3b68f08b62417699a1f5689008e/pyproj-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3537668992a709a2e7f068069192138618c00d0ba113572fdd5ee5ffde8222f3", size = 6278581, upload-time = "2025-02-16T04:27:41.051Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/0d/63670fc527e664068b70b7cab599aa38b7420dd009bdc29ea257e7f3dfb3/pyproj-3.7.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:a94e26c1a4950cea40116775588a2ca7cf56f1f434ff54ee35a84718f3841a3d", size = 6264315, upload-time = "2025-02-16T04:27:44.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/9d/cbaf82cfb290d1f1fa42feb9ba9464013bb3891e40c4199f8072112e4589/pyproj-3.7.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:263b54ba5004b6b957d55757d846fc5081bc02980caa0279c4fc95fa0fff6067", size = 4666267, upload-time = "2025-02-16T04:27:47.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/53/24f9f9b8918c0550f3ff49ad5de4cf3f0688c9f91ff191476db8979146fe/pyproj-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d6a2ccd5607cd15ef990c51e6f2dd27ec0a741e72069c387088bba3aab60fa", size = 9680510, upload-time = "2025-02-16T04:27:49.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/ac/12fab74a908d40b63174dc704587febd0729414804bbfd873cabe504ff2d/pyproj-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5dcf24ede53d8abab7d8a77f69ff1936c6a8843ef4fcc574646e4be66e5739", size = 9493619, upload-time = "2025-02-16T04:27:52.65Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/45/26311d6437135da2153a178125db5dfb6abce831ce04d10ec207eabac70a/pyproj-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c2e7449840a44ce860d8bea2c6c1c4bc63fa07cba801dcce581d14dcb031a02", size = 10709755, upload-time = "2025-02-16T04:27:55.239Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/52/4ecd0986f27d0e6c8ee3a7bc5c63da15acd30ac23034f871325b297e61fd/pyproj-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0829865c1d3a3543f918b3919dc601eea572d6091c0dd175e1a054db9c109274", size = 10642970, upload-time = "2025-02-16T04:27:58.343Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/a5/d3bfc018fc92195a000d1d28acc1f3f1df15ff9f09ece68f45a2636c0134/pyproj-3.7.1-cp311-cp311-win32.whl", hash = "sha256:6181960b4b812e82e588407fe5c9c68ada267c3b084db078f248db5d7f45d18a", size = 5868295, upload-time = "2025-02-16T04:28:01.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/39/ef6f06a5b223dbea308cfcbb7a0f72e7b506aef1850e061b2c73b0818715/pyproj-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ad0ff443a785d84e2b380869fdd82e6bfc11eba6057d25b4409a9bbfa867970", size = 6279871, upload-time = "2025-02-16T04:28:04.988Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/c9/876d4345b8d17f37ac59ebd39f8fa52fc6a6a9891a420f72d050edb6b899/pyproj-3.7.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:2781029d90df7f8d431e29562a3f2d8eafdf233c4010d6fc0381858dc7373217", size = 6264087, upload-time = "2025-02-16T04:28:09.036Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/e6/5f8691f8c90e7f402cc80a6276eb19d2ec1faa150d5ae2dd9c7b0a254da8/pyproj-3.7.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d61bf8ab04c73c1da08eedaf21a103b72fa5b0a9b854762905f65ff8b375d394", size = 4669628, upload-time = "2025-02-16T04:28:10.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/ec/16475bbb79c1c68845c0a0d9c60c4fb31e61b8a2a20bc18b1a81e81c7f68/pyproj-3.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04abc517a8555d1b05fcee768db3280143fe42ec39fdd926a2feef31631a1f2f", size = 9721415, upload-time = "2025-02-16T04:28:13.342Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/a3/448f05b15e318bd6bea9a32cfaf11e886c4ae61fa3eee6e09ed5c3b74bb2/pyproj-3.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084c0a475688f934d386c2ab3b6ce03398a473cd48adfda70d9ab8f87f2394a0", size = 9556447, upload-time = "2025-02-16T04:28:15.818Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/ae/bd15fe8d8bd914ead6d60bca7f895a4e6f8ef7e3928295134ff9a7dad14c/pyproj-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a20727a23b1e49c7dc7fe3c3df8e56a8a7acdade80ac2f5cca29d7ca5564c145", size = 10758317, upload-time = "2025-02-16T04:28:18.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/d9/5ccefb8bca925f44256b188a91c31238cae29ab6ee7f53661ecc04616146/pyproj-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bf84d766646f1ebd706d883755df4370aaf02b48187cedaa7e4239f16bc8213d", size = 10771259, upload-time = "2025-02-16T04:28:20.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/7d/31dedff9c35fa703162f922eeb0baa6c44a3288469a5fd88d209e2892f9e/pyproj-3.7.1-cp312-cp312-win32.whl", hash = "sha256:5f0da2711364d7cb9f115b52289d4a9b61e8bca0da57f44a3a9d6fc9bdeb7274", size = 5859914, upload-time = "2025-02-16T04:28:23.303Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/47/c6ab03d6564a7c937590cff81a2742b5990f096cce7c1a622d325be340ee/pyproj-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:aee664a9d806612af30a19dba49e55a7a78ebfec3e9d198f6a6176e1d140ec98", size = 6273196, upload-time = "2025-02-16T04:28:25.227Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -289,6 +317,179 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scikit-learn"
|
||||
version = "1.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "joblib", marker = "python_full_version < '3.11'" },
|
||||
{ name = "numpy", marker = "python_full_version < '3.11'" },
|
||||
{ name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "threadpoolctl", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scikit-learn"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "joblib", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "numpy", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "threadpoolctl", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/92/53ea2181da8ac6bf27170191028aee7251f8f841f8d3edbfdcaf2008fde9/scikit_learn-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:146b4d36f800c013d267b29168813f7a03a43ecd2895d04861f1240b564421da", size = 8595835, upload-time = "2025-12-10T07:07:39.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/18/d154dc1638803adf987910cdd07097d9c526663a55666a97c124d09fb96a/scikit_learn-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f984ca4b14914e6b4094c5d52a32ea16b49832c03bd17a110f004db3c223e8e1", size = 8080381, upload-time = "2025-12-10T07:07:41.93Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/44/226142fcb7b7101e64fdee5f49dbe6288d4c7af8abf593237b70fca080a4/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e30adb87f0cc81c7690a84f7932dd66be5bac57cfe16b91cb9151683a4a2d3b", size = 8799632, upload-time = "2025-12-10T07:07:43.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/4d/4a67f30778a45d542bbea5db2dbfa1e9e100bf9ba64aefe34215ba9f11f6/scikit_learn-1.8.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ada8121bcb4dac28d930febc791a69f7cb1673c8495e5eee274190b73a4559c1", size = 9103788, upload-time = "2025-12-10T07:07:45.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/3c/45c352094cfa60050bcbb967b1faf246b22e93cb459f2f907b600f2ceda5/scikit_learn-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:c57b1b610bd1f40ba43970e11ce62821c2e6569e4d74023db19c6b26f246cb3b", size = 8081706, upload-time = "2025-12-10T07:07:48.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/46/5416595bb395757f754feb20c3d776553a386b661658fb21b7c814e89efe/scikit_learn-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:2838551e011a64e3053ad7618dda9310175f7515f1742fa2d756f7c874c05961", size = 7688451, upload-time = "2025-12-10T07:07:49.873Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/74/e6a7cc4b820e95cc38cf36cd74d5aa2b42e8ffc2d21fe5a9a9c45c1c7630/scikit_learn-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5fb63362b5a7ddab88e52b6dbb47dac3fd7dafeee740dc6c8d8a446ddedade8e", size = 8548242, upload-time = "2025-12-10T07:07:51.568Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d8/9be608c6024d021041c7f0b3928d4749a706f4e2c3832bbede4fb4f58c95/scikit_learn-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5025ce924beccb28298246e589c691fe1b8c1c96507e6d27d12c5fadd85bfd76", size = 8079075, upload-time = "2025-12-10T07:07:53.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/47/f187b4636ff80cc63f21cd40b7b2d177134acaa10f6bb73746130ee8c2e5/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4496bb2cf7a43ce1a2d7524a79e40bc5da45cf598dbf9545b7e8316ccba47bb4", size = 8660492, upload-time = "2025-12-10T07:07:55.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/97/74/b7a304feb2b49df9fafa9382d4d09061a96ee9a9449a7cbea7988dda0828/scikit_learn-1.8.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0bcfe4d0d14aec44921545fd2af2338c7471de9cb701f1da4c9d85906ab847a", size = 8931904, upload-time = "2025-12-10T07:07:57.666Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/c4/0ab22726a04ede56f689476b760f98f8f46607caecff993017ac1b64aa5d/scikit_learn-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:35c007dedb2ffe38fe3ee7d201ebac4a2deccd2408e8621d53067733e3c74809", size = 8019359, upload-time = "2025-12-10T07:07:59.838Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/90/344a67811cfd561d7335c1b96ca21455e7e472d281c3c279c4d3f2300236/scikit_learn-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:8c497fff237d7b4e07e9ef1a640887fa4fb765647f86fbe00f969ff6280ce2bb", size = 7641898, upload-time = "2025-12-10T07:08:01.36Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.15.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scipy"
|
||||
version = "1.16.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12'",
|
||||
"python_full_version == '3.11.*'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "numpy", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881, upload-time = "2025-10-28T17:31:47.104Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012, upload-time = "2025-10-28T17:31:53.411Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935, upload-time = "2025-10-28T17:31:57.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466, upload-time = "2025-10-28T17:32:01.875Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618, upload-time = "2025-10-28T17:32:06.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798, upload-time = "2025-10-28T17:32:12.665Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154, upload-time = "2025-10-28T17:32:17.961Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540, upload-time = "2025-10-28T17:32:23.907Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107, upload-time = "2025-10-28T17:32:29.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shapely"
|
||||
version = "2.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/05/89/c3548aa9b9812a5d143986764dededfa48d817714e947398bdda87c77a72/shapely-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7ae48c236c0324b4e139bea88a306a04ca630f49be66741b340729d380d8f52f", size = 1825959, upload-time = "2025-09-24T13:50:00.682Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/8a/7ebc947080442edd614ceebe0ce2cdbd00c25e832c240e1d1de61d0e6b38/shapely-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eba6710407f1daa8e7602c347dfc94adc02205ec27ed956346190d66579eb9ea", size = 1629196, upload-time = "2025-09-24T13:50:03.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/86/c9c27881c20d00fc409e7e059de569d5ed0abfcec9c49548b124ebddea51/shapely-2.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef4a456cc8b7b3d50ccec29642aa4aeda959e9da2fe9540a92754770d5f0cf1f", size = 2951065, upload-time = "2025-09-24T13:50:05.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/8a/0ab1f7433a2a85d9e9aea5b1fbb333f3b09b309e7817309250b4b7b2cc7a/shapely-2.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e38a190442aacc67ff9f75ce60aec04893041f16f97d242209106d502486a142", size = 3058666, upload-time = "2025-09-24T13:50:06.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/c6/5a30ffac9c4f3ffd5b7113a7f5299ccec4713acd5ee44039778a7698224e/shapely-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:40d784101f5d06a1fd30b55fc11ea58a61be23f930d934d86f19a180909908a4", size = 3966905, upload-time = "2025-09-24T13:50:09.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/72/e92f3035ba43e53959007f928315a68fbcf2eeb4e5ededb6f0dc7ff1ecc3/shapely-2.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6f6cd5819c50d9bcf921882784586aab34a4bd53e7553e175dece6db513a6f0", size = 4129260, upload-time = "2025-09-24T13:50:11.183Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/24/605901b73a3d9f65fa958e63c9211f4be23d584da8a1a7487382fac7fdc5/shapely-2.1.2-cp310-cp310-win32.whl", hash = "sha256:fe9627c39c59e553c90f5bc3128252cb85dc3b3be8189710666d2f8bc3a5503e", size = 1544301, upload-time = "2025-09-24T13:50:12.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/89/6db795b8dd3919851856bd2ddd13ce434a748072f6fdee42ff30cbd3afa3/shapely-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:1d0bfb4b8f661b3b4ec3565fa36c340bfb1cda82087199711f86a88647d26b2f", size = 1722074, upload-time = "2025-09-24T13:50:13.909Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
@@ -298,13 +499,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpoolctl"
|
||||
version = "3.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "triangle2"
|
||||
version = "20230923"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
|
||||
{ name = "numpy" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/81/f5610bda7a92ce0ce2c284295ba11acfa429d4c2056c39e44ffd82db926d/triangle2-20230923-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d442d16e0ca0975c6c8f56f7b8e08639d31bcea2c985ebd0ee265ab9f3ad6bd4", size = 1457243, upload-time = "2024-02-26T14:39:41.088Z" },
|
||||
@@ -313,12 +522,30 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/2a/7bd73f5cc1509cbe12207bf8d5c12b06c78baea5ac2ba2d1e3ce6621984a/triangle2-20230923-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f758f99aca653e7d8bc4d2e14c034f09e7af6de1919d9792876d0d74dc6b4c49", size = 1977571, upload-time = "2024-02-26T14:39:46.643Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/2a/15bb0609c3c1a679533c02b97176a2967dd3c38275854e02c32b26778ca9/triangle2-20230923-cp310-cp310-win32.whl", hash = "sha256:c117a62965a64d07d5ec82b1880357a765c35f6b86820a7737170b3ec8c63725", size = 1406919, upload-time = "2024-02-26T14:39:48.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/6b/e6d963f0e6555a2626aa2954d9cee541b20f6a15c3bf80f4610bdbf4b727/triangle2-20230923-cp310-cp310-win_amd64.whl", hash = "sha256:eef547477e5dc0522b8504f1eba585ae938054016839df81d730b731f9e5b922", size = 1426296, upload-time = "2024-02-26T14:39:49.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/e5/234b6c9e93f8dd75683f7fcb51867a3c44f3cc0fe0f7ecb31f1b96a41088/triangle2-20230923-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c077be838336baf40b7fa4bb8ace2434997d67ee3a1353c38009eddf9b2678d", size = 1457955, upload-time = "2024-02-26T14:40:34.951Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/2e/66a4b097397472a2b34f0c6e76c0be437da59e5a47d6feb870ac21db9f0e/triangle2-20230923-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d31f1b52f6aecc613329822d6f4cf918e49cc4a8a0094977d9eb4279c096435b", size = 1441512, upload-time = "2024-02-26T14:40:37.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/3a/34cea18ff5801b4ae8e7a6e2780838c600f36f9ab65347e78d1abbf6fbeb/triangle2-20230923-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34419be0744e682448af37c4353b0bc5affa4962ffa7e972224342dcb0b86a17", size = 2054893, upload-time = "2024-02-26T14:40:39.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/23/90976726121a34c2aeb67c490be2632a0362798198b5c8f247ddea7e39c6/triangle2-20230923-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77e072dcbd7ad15da4d8b8d1c8732a960518f886266711a07e43a8776a30bdaf", size = 1982038, upload-time = "2024-02-26T14:40:40.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/54/d24711cbd2c2efdc004144abe90e36281dde5fca3ab965a414374803b3b1/triangle2-20230923-cp39-cp39-win32.whl", hash = "sha256:c0c54f1ca1399463677cf988374eee4c1a9dda223e6d288288c66db5fd369f70", size = 1407592, upload-time = "2024-02-26T14:40:42.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/42/eb1f16b6df2324602aa1e8147000249f41cf645779d63a3eaada26134d64/triangle2-20230923-cp39-cp39-win_amd64.whl", hash = "sha256:c111816c984a26457ff414fea3e74c94b40705e1ca827aa00d09bd42d810eba2", size = 1426962, upload-time = "2024-02-26T14:40:44.537Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/6d/90208d36e9cb2ba53414ee325cedf6aaff962f3577abbab2e0a73cc67225/triangle2-20230923-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e8ea0abc8e6d8bc08fec14541da0c564e5722b5d401f045d996ee04efac99a1e", size = 1457376, upload-time = "2024-02-26T14:39:51.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/24/f191d15419ae14d83e2cf4d0ca6f61340aeb39a0b74d6eed0cbd96e72522/triangle2-20230923-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4080e08adad70e882734f2f483e7985923e105fbd946998d222f7060ae7851ff", size = 1441037, upload-time = "2024-02-26T14:39:53.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/ae/ffc18956e59c9e5aad6d85e411f5522193c0c866cd331a35d662c9a454d2/triangle2-20230923-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3007191f44c3f37eb98d9e4751ff075a8f011ea359e14d7aae31f387de4edec", size = 2101785, upload-time = "2024-02-26T14:39:54.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/dc/45a9547f48895099831f2d2b52f1b1a96843110ec9a0f30e94dc1a971b53/triangle2-20230923-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d251089e20a0d78adfcea0accb6ff5ae28ce4a3e613803e00fc71043cb890e02", size = 2027689, upload-time = "2024-02-26T14:39:56.985Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/2a/d421e6691b4191af340de260457e97dc11a1eafd0019ad5686bd0d35ad2e/triangle2-20230923-cp311-cp311-win32.whl", hash = "sha256:7e50ca6b8636bad6af0f76e822e9788f92687d12cee75e063209cef9f68464a5", size = 1406499, upload-time = "2024-02-26T14:39:58.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/06/777b5ee9f5b8c08f231e11c24da2cc5c68ad23bce5d346f43686fe1d8dcd/triangle2-20230923-cp311-cp311-win_amd64.whl", hash = "sha256:d4a10cda1a91c9888e4764ae29d9cf29d07cd09399053c16d6e38475edfcd45c", size = 1426343, upload-time = "2024-02-26T14:40:01.103Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/06/e0d7c8b6b0ba2fdd190355cac191803773638abb841175d972486fac3ebd/triangle2-20230923-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a7e0626d31296a6fac0c3b7ee44cc44450c3092bf1f576130770395d134326d1", size = 1459256, upload-time = "2024-02-26T14:40:03.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/2d/fd9d5dbe88cc36725eb2106f5f79f17885686367d14bd08cfaff0d672dbf/triangle2-20230923-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7217cf5a61333ad0bd53a63c252cb32d42a4e24d4157e08c5c828d0d07e26343", size = 1442624, upload-time = "2024-02-26T14:40:05.969Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/b0/1956a01690397c5a5d9d4b54908dd84c3604ac48d9565f596678d903850c/triangle2-20230923-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52a0aad9e852680adea23f5038e776f9a54dad9618cf9dcbcaffb312fc241b64", size = 2094363, upload-time = "2024-02-26T14:40:07.699Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/72/def62d6de340f9389a2ee1c07a73af46f834ad5938da5cdd069c3bb0de41/triangle2-20230923-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8974b1a1318e0fa3c7b97459b5327843d551c892a4694f22235138c7244c463c", size = 2011718, upload-time = "2024-02-26T14:40:09.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/5b/4dac900a5c2fca480c3dd813766337d3a1f62bae7270fe44b97f9e4f2358/triangle2-20230923-cp312-cp312-win32.whl", hash = "sha256:92b5b03eeb157e2194cb7107a5e5da07719765d96982b3286404a9914f7fe6b9", size = 1407427, upload-time = "2024-02-26T14:40:10.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/fb/562f77a71ded88608103cb92c26582ff3121b8cf585d97916233e9574759/triangle2-20230923-cp312-cp312-win_amd64.whl", hash = "sha256:d66f5c1f9c06e09984870556eb6be1fe96dac44e8a275eee4afc99ef3eb64ae2", size = 1426888, upload-time = "2024-02-26T14:40:13.116Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trimesh"
|
||||
version = "4.10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/83/69/eedfeb084460d429368e03db83ed41b18d6de4fd4945de7eb8874b9fae36/trimesh-4.10.1.tar.gz", hash = "sha256:2067ebb8dcde0d7f00c2a85bfcae4aa891c40898e5f14232592429025ee2c593", size = 831998, upload-time = "2025-12-07T00:39:05.838Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/0c/f08f0d16b4f97ec2ea6d542b9a70472a344384382fa3543a12ec417cc063/trimesh-4.10.1-py3-none-any.whl", hash = "sha256:4e81fae696683dfe912ef54ce124869487d35d267b87e10fe07fc05ab62aaadb", size = 737037, upload-time = "2025-12-07T00:39:04.086Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user