Update geodata pipeline and exports
This commit is contained in:
+272
@@ -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 |
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,type,confidence
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
@@ -0,0 +1 @@
|
||||
tile_id,x_local,y_local,z_ground,height,radius,confidence,canopy_r,canopy_g,canopy_b
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
+26
-2
@@ -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
|
||||
|
||||
|
||||
|
||||
+11
-2
@@ -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"]
|
||||
|
||||
@@ -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