Uses the tiffs that we created with the YGA to make soils for APSIM. It uses layers up to 300 mm and then extrapolates until 1200!!

library(terra)
## Warning: package 'terra' was built under R version 4.3.3
## terra 1.8.29
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.3.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:terra':
## 
##     intersect, union
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
# Make sure points_with_elev is loaded/created
# (run whatever code creates it)

# Create pts fresh
# Set working directory
setwd("D:/Mes Donnees/R/Biomass_Crops/Modeling/APSIM/InitialDomains")

# Load the file
load("points_with_elev.RData")

# Verify it loaded
head(points_with_elev)
## # A tibble: 6 × 7
##     lon   lat domain id     elevation mz_adaptation GDD_flo_mz
##   <dbl> <dbl>  <dbl> <chr>      <dbl> <chr>              <dbl>
## 1  29.5 -2.15      1 point1      1823 mid-altitude        1015
## 2  29.7 -2.70      1 point2      1664 mid-altitude        1015
## 3  29.7 -2.39      1 point3      1662 mid-altitude        1015
## 4  29.8 -1.98      1 point4      1597 mid-altitude        1015
## 5  29.9 -2.00      1 point5      1619 mid-altitude        1015
## 6  29.9 -1.96      1 point6      1718 mid-altitude        1015
# NOW create pts
library(terra)

pts <- vect(
  data.frame(
    lon = points_with_elev$lon,
    lat = points_with_elev$lat
  ),
  geom = c("lon", "lat"),
  crs = "+proj=longlat +datum=WGS84"
)

# Verify
nrow(pts)
## [1] 200
print(pts[1:3])
##  class       : SpatVector 
##  geometry    : points 
##  dimensions  : 3, 0  (geometries, attributes)
##  extent      : 29.54583, 29.6875, -2.695833, -2.145833  (xmin, xmax, ymin, ymax)
##  coord. ref. : +proj=longlat +datum=WGS84 +no_defs
# Load points_with_elev from saved file
# Load from full path

# Set working directory for the Rmd
setwd("D:/Mes Donnees/R/Biomass_Crops/Modeling/APSIM/InitialDomains")

# Load the file
load("points_with_elev.RData")

cat("✅ Loaded points_with_elev\n")
## ✅ Loaded points_with_elev
cat("Number of points:", nrow(points_with_elev), "\n")
## Number of points: 200
# Libraries
library(soilDB)
## Warning: package 'soilDB' was built under R version 4.3.3
library(XML)
## Warning: package 'XML' was built under R version 4.3.3
library(dplyr)
library(terra)
library(apsimx)
## Warning: package 'apsimx' was built under R version 4.3.3
library(stringr)
library(fs)
library(curl)
## Using libcurl 8.3.0 with Schannel
# Define APSIM Layers and Weights
apsim_layers <- c("0-50", "50-150", "150-300", "300-600", "600-1200")
apsim_thickness <- c(50, 100, 150, 300, 600)
apsim_weights <- list(
  `0-50`     = c(5, 5, 0),
  `50-150`   = c(0, 10, 10),
  `150-300`  = c(0, 0, 15),
  `300-600`  = c(0, 0, 30),
  `600-1200` = c(0, 0, 30)
)
apsim_weights <- lapply(apsim_weights, function(w) w / sum(w))

# soil_af() Function — Load Raster from Depth
soil_af <- function(var, depth, path = "Data/soil_af") {
  depth_map <- c(
    `5`  = "0-5cm",
    `15` = "5-15cm",
    `30` = "15-30cm"
  )
  depth_label <- depth_map[as.character(depth)]
  if (is.na(depth_label)) stop(paste("No depth label for depth:", depth))
  
  file_name <- paste0("af_", var, "_", depth_label, "_30s.tif")
  file_path <- file.path(path, file_name)
  
  if (!file.exists(file_path)) stop(paste("File not found:", file_path))
  
  return(terra::rast(file_path))
}

# Simple fallback extraction if terra fails
extract_value_safe <- function(raster, point) {
  tryCatch({
    result <- terra::extract(raster, point, ID = FALSE)
    if (is.null(result) || nrow(result) == 0) return(NA)
    return(as.numeric(result[1, 1]))
  }, error = function(e) {
    return(NA)
  })
}

# Extract weighted average across depths
extract_weighted <- function(var, pt, unit = 1, defaults = NULL) {
  if (is.null(defaults)) defaults <- rep(NA, 5)
  
  tryCatch({
    layers <- list()
    for (depth in c(5, 15, 30)) {
      tryCatch({
        layer <- soil_af(var = var, depth = depth)
        layers[[as.character(depth)]] <- layer
      }, error = function(e) {
        # Silently skip missing layers
      })
    }
    
    if (length(layers) == 0) {
      return(defaults)
    }
    
    # Extract values
    values <- sapply(names(layers), function(depth) {
      extract_value_safe(layers[[depth]], pt)
    })
    
    if (all(is.na(values))) {
      return(defaults)
    }
    
    # Weighted average for APSIM layers
    results <- sapply(apsim_weights, function(w) {
      sum(values * w, na.rm = TRUE)
    })
    
    return(results / unit)
    
  }, error = function(e) {
    return(defaults)
  })
}

# ===== SIMPLIFIED PEDOTRANSFER FUNCTION =====
# Based on your working code but with SAT and BD
# GUARANTEED ordering: AirDry < LL15 < DUL < SAT
ptf_water_retention <- function(clay, sand, silt, om) {
  n <- length(clay)
  
  # Ensure valid ranges
  clay <- pmax(0, pmin(100, clay))
  sand <- pmax(0, pmin(100, sand))
  om <- pmax(0, pmin(10, om))
  
  # LL15 (based on clay content)
  ll15 <- clay * 0.005 + 0.05
  ll15 <- pmax(ll15, 0.05)
  ll15 <- pmin(ll15, 0.25)  # Cap at 0.25
  
  # AirDry (lower than LL15)
  air_dry <- ll15 * 0.5
  air_dry <- pmax(air_dry, 0.01)
  
  # DUL (ALWAYS 0.10 higher than LL15 - GUARANTEED)
  dul <- ll15 + 0.10
  dul <- pmax(dul, 0.15)
  dul <- pmin(dul, 0.50)
  
  # SAT (calculated from sand, silt, clay)
  sat <- 0.505 - 0.142 * (sand/100) - 0.037 * (clay/100) + 0.0694 * (om/100)
  sat <- pmax(sat, dul + 0.10)  # Must be > DUL
  sat <- pmin(sat, 0.43)
  
  # KS (based on clay - your simple formula)
  ks <- 20 - clay * 0.1
  ks <- pmax(ks, 1)
  ks <- pmin(ks, 100)
  
  # BD (bulk density, simple formula)
  bd <- 1.5 - 0.006 * (om/100)
  bd <- pmax(bd, 1.0)
  bd <- pmin(bd, 1.8)
  
  # ===== FINAL VALIDATION =====
  # Ensure ordering one more time
  for (i in 1:n) {
    if (air_dry[i] >= ll15[i]) air_dry[i] <- ll15[i] - 0.02
    if (ll15[i] >= dul[i]) dul[i] <- ll15[i] + 0.10
    if (dul[i] >= sat[i]) sat[i] <- dul[i] + 0.10
  }
  
  return(list(
    SAT = sat, DUL = dul, LL15 = ll15, AirDry = air_dry,
    KS = ks, BD = bd
  ))
}

# ===== MAIN PROCESSING =====
cat("Starting soil parameter extraction with SIMPLIFIED PTF...\n\n")
## Starting soil parameter extraction with SIMPLIFIED PTF...
if (!exists("points_with_elev") || !exists("pts")) {
  stop("❌ Please load points_with_elev and create pts vector first!")
}

# Counter for logging
success_count <- 0
fail_count <- 0
failed_points <- c()

# Loop through points
for (i in 1:nrow(points_with_elev)) {
  id <- points_with_elev$id[i]
  
  tryCatch({
    pt <- pts[i]
    message(paste("📄 [", i, "/", nrow(points_with_elev), "] Processing:", id))
    
    # Extract soil properties with fallback to defaults
    clay <- extract_weighted("clay", pt, defaults = rep(25, 5))
    sand <- extract_weighted("sand", pt, defaults = rep(50, 5))
    oc   <- extract_weighted("SOC", pt, unit = 10, defaults = rep(1.5, 5))
    ph   <- extract_weighted("pH", pt, defaults = rep(6.0, 5))
    cec  <- extract_weighted("CEC", pt, defaults = rep(15, 5))
    ea   <- extract_weighted("acid-exch", pt, defaults = rep(0.15, 5))
    
    # Ensure all numeric and within valid ranges
    clay <- suppressWarnings(as.numeric(clay))
    sand <- suppressWarnings(as.numeric(sand))
    oc   <- suppressWarnings(as.numeric(oc))
    ph   <- suppressWarnings(as.numeric(ph))
    cec  <- suppressWarnings(as.numeric(cec))
    ea   <- suppressWarnings(as.numeric(ea))
    
    # Fill NAs with defaults
    clay[is.na(clay)] <- 25
    sand[is.na(sand)] <- 50
    oc[is.na(oc)] <- 1.5
    ph[is.na(ph)] <- 6.0
    cec[is.na(cec)] <- 15
    ea[is.na(ea)] <- 0.15
    
    clay <- pmax(0, pmin(100, clay))
    sand <- pmax(0, pmin(100, sand))
    
    silt <- 100 - sand - clay
    silt[silt < 0] <- 0
    silt[silt > 100] <- 100
    
    om <- oc * 1.724
    
    # PTF calculation
    water_props <- ptf_water_retention(clay, sand, silt, om)
    
    air_dry <- as.numeric(water_props$AirDry)
    ll15    <- as.numeric(water_props$LL15)
    dul     <- as.numeric(water_props$DUL)
    sat     <- as.numeric(water_props$SAT)
    ks      <- as.numeric(water_props$KS)
    bd      <- as.numeric(water_props$BD)
    
    # ===== CRITICAL VALIDATION =====
    violation_found <- FALSE
    for (layer in 1:5) {
      if (air_dry[layer] >= ll15[layer]) {
        cat("   ⚠️  Layer", layer, "- AirDry >= LL15. Fixing...\n")
        air_dry[layer] <- ll15[layer] - 0.02
      }
      if (ll15[layer] >= dul[layer]) {
        cat("   🚨 CRITICAL: Layer", layer, "- LL15 >= DUL!\n")
        cat("      LL15=", ll15[layer], "DUL=", dul[layer], "\n")
        violation_found <- TRUE
      }
      if (dul[layer] >= sat[layer]) {
        cat("   ⚠️  Layer", layer, "- DUL >= SAT. Fixing...\n")
        sat[layer] <- dul[layer] + 0.10
      }
    }
    
    if (violation_found) {
      stop("VALIDATION FAILED: LL15 >= DUL in one or more layers")
    }
    
    # Format function
    fmt <- function(x) paste0(round(x, 3), collapse = ",")
    
    ec <- rep(0.15, 5)
    
    par_lines <- c(
      paste0("*", id),
      paste0("name = ", id),
      paste0("latitude = ", round(points_with_elev$lat[i], 5)),
      paste0("longitude = ", round(points_with_elev$lon[i], 5)),
      "site = Rwanda",
      "country = Rwanda",
      "tav = 20",
      "amp = 5",
      paste0("dlayer = ", paste(apsim_thickness, collapse = ",")),
      paste0("BD = ", fmt(bd)),
      paste0("AirDry = ", fmt(air_dry)),
      paste0("LL15 = ", fmt(ll15)),
      paste0("DUL = ", fmt(dul)),
      paste0("SAT = ", fmt(sat)),
      paste0("KS = ", fmt(ks)),
      paste0("Carbon = ", fmt(oc)),
      paste0("PH = ", fmt(ph)),
      paste0("CEC = ", fmt(cec)),
      paste0("EA = ", fmt(ea)),
      paste0("SAND = ", fmt(sand)),
      paste0("CLAY = ", fmt(clay)),
      paste0("SILT = ", fmt(silt)),
      paste0("EC = ", fmt(ec))
    )
    
    # Write file
    if (!dir.exists("soils_par")) dir.create("soils_par")
    writeLines(par_lines, con = file.path("soils_par", paste0(id, ".par")))
    
    message(paste("   ✅ Saved:", id, ".par\n"))
    success_count <- success_count + 1
    
  }, error = function(e) {
    warning(paste("❌ Point", id, "failed:", e$message))
    fail_count <<- fail_count + 1
    failed_points <<- c(failed_points, id)
  })
}
## 📄 [ 1 / 200 ] Processing: point1
##    ✅ Saved: point1 .par
## 📄 [ 2 / 200 ] Processing: point2
##    ✅ Saved: point2 .par
## 📄 [ 3 / 200 ] Processing: point3
##    ✅ Saved: point3 .par
## 📄 [ 4 / 200 ] Processing: point4
##    ✅ Saved: point4 .par
## 📄 [ 5 / 200 ] Processing: point5
##    ✅ Saved: point5 .par
## 📄 [ 6 / 200 ] Processing: point6
##    ✅ Saved: point6 .par
## 📄 [ 7 / 200 ] Processing: point7
##    ✅ Saved: point7 .par
## 📄 [ 8 / 200 ] Processing: point8
##    ✅ Saved: point8 .par
## 📄 [ 9 / 200 ] Processing: point9
##    ✅ Saved: point9 .par
## 📄 [ 10 / 200 ] Processing: point10
##    ✅ Saved: point10 .par
## 📄 [ 11 / 200 ] Processing: point11
##    ✅ Saved: point11 .par
## 📄 [ 12 / 200 ] Processing: point12
##    ✅ Saved: point12 .par
## 📄 [ 13 / 200 ] Processing: point13
##    ✅ Saved: point13 .par
## 📄 [ 14 / 200 ] Processing: point14
##    ✅ Saved: point14 .par
## 📄 [ 15 / 200 ] Processing: point15
##    ✅ Saved: point15 .par
## 📄 [ 16 / 200 ] Processing: point16
##    ✅ Saved: point16 .par
## 📄 [ 17 / 200 ] Processing: point17
##    ✅ Saved: point17 .par
## 📄 [ 18 / 200 ] Processing: point18
##    ✅ Saved: point18 .par
## 📄 [ 19 / 200 ] Processing: point19
##    ✅ Saved: point19 .par
## 📄 [ 20 / 200 ] Processing: point20
##    ✅ Saved: point20 .par
## 📄 [ 21 / 200 ] Processing: point21
##    ✅ Saved: point21 .par
## 📄 [ 22 / 200 ] Processing: point22
##    ✅ Saved: point22 .par
## 📄 [ 23 / 200 ] Processing: point23
##    ✅ Saved: point23 .par
## 📄 [ 24 / 200 ] Processing: point24
##    ✅ Saved: point24 .par
## 📄 [ 25 / 200 ] Processing: point25
##    ✅ Saved: point25 .par
## 📄 [ 26 / 200 ] Processing: point26
##    ✅ Saved: point26 .par
## 📄 [ 27 / 200 ] Processing: point27
##    ✅ Saved: point27 .par
## 📄 [ 28 / 200 ] Processing: point28
##    ✅ Saved: point28 .par
## 📄 [ 29 / 200 ] Processing: point29
##    ✅ Saved: point29 .par
## 📄 [ 30 / 200 ] Processing: point30
##    ✅ Saved: point30 .par
## 📄 [ 31 / 200 ] Processing: point31
##    ✅ Saved: point31 .par
## 📄 [ 32 / 200 ] Processing: point32
##    ✅ Saved: point32 .par
## 📄 [ 33 / 200 ] Processing: point33
##    ✅ Saved: point33 .par
## 📄 [ 34 / 200 ] Processing: point34
##    ✅ Saved: point34 .par
## 📄 [ 35 / 200 ] Processing: point35
##    ✅ Saved: point35 .par
## 📄 [ 36 / 200 ] Processing: point36
##    ✅ Saved: point36 .par
## 📄 [ 37 / 200 ] Processing: point37
##    ✅ Saved: point37 .par
## 📄 [ 38 / 200 ] Processing: point38
##    ✅ Saved: point38 .par
## 📄 [ 39 / 200 ] Processing: point39
##    ✅ Saved: point39 .par
## 📄 [ 40 / 200 ] Processing: point40
##    ✅ Saved: point40 .par
## 📄 [ 41 / 200 ] Processing: point41
##    ✅ Saved: point41 .par
## 📄 [ 42 / 200 ] Processing: point42
##    ✅ Saved: point42 .par
## 📄 [ 43 / 200 ] Processing: point43
##    ✅ Saved: point43 .par
## 📄 [ 44 / 200 ] Processing: point44
##    ✅ Saved: point44 .par
## 📄 [ 45 / 200 ] Processing: point45
##    ✅ Saved: point45 .par
## 📄 [ 46 / 200 ] Processing: point46
##    ✅ Saved: point46 .par
## 📄 [ 47 / 200 ] Processing: point47
##    ✅ Saved: point47 .par
## 📄 [ 48 / 200 ] Processing: point48
##    ✅ Saved: point48 .par
## 📄 [ 49 / 200 ] Processing: point49
##    ✅ Saved: point49 .par
## 📄 [ 50 / 200 ] Processing: point50
##    ✅ Saved: point50 .par
## 📄 [ 51 / 200 ] Processing: point51
##    ✅ Saved: point51 .par
## 📄 [ 52 / 200 ] Processing: point52
##    ✅ Saved: point52 .par
## 📄 [ 53 / 200 ] Processing: point53
##    ✅ Saved: point53 .par
## 📄 [ 54 / 200 ] Processing: point54
##    ✅ Saved: point54 .par
## 📄 [ 55 / 200 ] Processing: point55
##    ✅ Saved: point55 .par
## 📄 [ 56 / 200 ] Processing: point56
##    ✅ Saved: point56 .par
## 📄 [ 57 / 200 ] Processing: point57
##    ✅ Saved: point57 .par
## 📄 [ 58 / 200 ] Processing: point58
##    ✅ Saved: point58 .par
## 📄 [ 59 / 200 ] Processing: point59
##    ✅ Saved: point59 .par
## 📄 [ 60 / 200 ] Processing: point60
##    ✅ Saved: point60 .par
## 📄 [ 61 / 200 ] Processing: point61
##    ✅ Saved: point61 .par
## 📄 [ 62 / 200 ] Processing: point62
##    ✅ Saved: point62 .par
## 📄 [ 63 / 200 ] Processing: point63
##    ✅ Saved: point63 .par
## 📄 [ 64 / 200 ] Processing: point64
##    ✅ Saved: point64 .par
## 📄 [ 65 / 200 ] Processing: point65
##    ✅ Saved: point65 .par
## 📄 [ 66 / 200 ] Processing: point66
##    ✅ Saved: point66 .par
## 📄 [ 67 / 200 ] Processing: point67
##    ✅ Saved: point67 .par
## 📄 [ 68 / 200 ] Processing: point68
##    ✅ Saved: point68 .par
## 📄 [ 69 / 200 ] Processing: point69
##    ✅ Saved: point69 .par
## 📄 [ 70 / 200 ] Processing: point70
##    ✅ Saved: point70 .par
## 📄 [ 71 / 200 ] Processing: point71
##    ✅ Saved: point71 .par
## 📄 [ 72 / 200 ] Processing: point72
##    ✅ Saved: point72 .par
## 📄 [ 73 / 200 ] Processing: point73
##    ✅ Saved: point73 .par
## 📄 [ 74 / 200 ] Processing: point74
##    ✅ Saved: point74 .par
## 📄 [ 75 / 200 ] Processing: point75
##    ✅ Saved: point75 .par
## 📄 [ 76 / 200 ] Processing: point76
##    ✅ Saved: point76 .par
## 📄 [ 77 / 200 ] Processing: point77
##    ✅ Saved: point77 .par
## 📄 [ 78 / 200 ] Processing: point78
##    ✅ Saved: point78 .par
## 📄 [ 79 / 200 ] Processing: point79
##    ✅ Saved: point79 .par
## 📄 [ 80 / 200 ] Processing: point80
##    ✅ Saved: point80 .par
## 📄 [ 81 / 200 ] Processing: point81
##    ✅ Saved: point81 .par
## 📄 [ 82 / 200 ] Processing: point82
##    ✅ Saved: point82 .par
## 📄 [ 83 / 200 ] Processing: point83
##    ✅ Saved: point83 .par
## 📄 [ 84 / 200 ] Processing: point84
##    ✅ Saved: point84 .par
## 📄 [ 85 / 200 ] Processing: point85
##    ✅ Saved: point85 .par
## 📄 [ 86 / 200 ] Processing: point86
##    ✅ Saved: point86 .par
## 📄 [ 87 / 200 ] Processing: point87
##    ✅ Saved: point87 .par
## 📄 [ 88 / 200 ] Processing: point88
##    ✅ Saved: point88 .par
## 📄 [ 89 / 200 ] Processing: point89
##    ✅ Saved: point89 .par
## 📄 [ 90 / 200 ] Processing: point90
##    ✅ Saved: point90 .par
## 📄 [ 91 / 200 ] Processing: point91
##    ✅ Saved: point91 .par
## 📄 [ 92 / 200 ] Processing: point92
##    ✅ Saved: point92 .par
## 📄 [ 93 / 200 ] Processing: point93
##    ✅ Saved: point93 .par
## 📄 [ 94 / 200 ] Processing: point94
##    ✅ Saved: point94 .par
## 📄 [ 95 / 200 ] Processing: point95
##    ✅ Saved: point95 .par
## 📄 [ 96 / 200 ] Processing: point96
##    ✅ Saved: point96 .par
## 📄 [ 97 / 200 ] Processing: point97
##    ✅ Saved: point97 .par
## 📄 [ 98 / 200 ] Processing: point98
##    ✅ Saved: point98 .par
## 📄 [ 99 / 200 ] Processing: point99
##    ✅ Saved: point99 .par
## 📄 [ 100 / 200 ] Processing: point100
##    ✅ Saved: point100 .par
## 📄 [ 101 / 200 ] Processing: point101
##    ✅ Saved: point101 .par
## 📄 [ 102 / 200 ] Processing: point102
##    ✅ Saved: point102 .par
## 📄 [ 103 / 200 ] Processing: point103
##    ✅ Saved: point103 .par
## 📄 [ 104 / 200 ] Processing: point104
##    ✅ Saved: point104 .par
## 📄 [ 105 / 200 ] Processing: point105
##    ✅ Saved: point105 .par
## 📄 [ 106 / 200 ] Processing: point106
##    ✅ Saved: point106 .par
## 📄 [ 107 / 200 ] Processing: point107
##    ✅ Saved: point107 .par
## 📄 [ 108 / 200 ] Processing: point108
##    ✅ Saved: point108 .par
## 📄 [ 109 / 200 ] Processing: point109
##    ✅ Saved: point109 .par
## 📄 [ 110 / 200 ] Processing: point110
##    ✅ Saved: point110 .par
## 📄 [ 111 / 200 ] Processing: point111
##    ✅ Saved: point111 .par
## 📄 [ 112 / 200 ] Processing: point112
##    ✅ Saved: point112 .par
## 📄 [ 113 / 200 ] Processing: point113
##    ✅ Saved: point113 .par
## 📄 [ 114 / 200 ] Processing: point114
##    ✅ Saved: point114 .par
## 📄 [ 115 / 200 ] Processing: point115
##    ✅ Saved: point115 .par
## 📄 [ 116 / 200 ] Processing: point116
##    ✅ Saved: point116 .par
## 📄 [ 117 / 200 ] Processing: point117
##    ✅ Saved: point117 .par
## 📄 [ 118 / 200 ] Processing: point118
##    ✅ Saved: point118 .par
## 📄 [ 119 / 200 ] Processing: point119
##    ✅ Saved: point119 .par
## 📄 [ 120 / 200 ] Processing: point120
##    ✅ Saved: point120 .par
## 📄 [ 121 / 200 ] Processing: point121
##    ✅ Saved: point121 .par
## 📄 [ 122 / 200 ] Processing: point122
##    ✅ Saved: point122 .par
## 📄 [ 123 / 200 ] Processing: point123
##    ✅ Saved: point123 .par
## 📄 [ 124 / 200 ] Processing: point124
##    ✅ Saved: point124 .par
## 📄 [ 125 / 200 ] Processing: point125
##    ✅ Saved: point125 .par
## 📄 [ 126 / 200 ] Processing: point126
##    ✅ Saved: point126 .par
## 📄 [ 127 / 200 ] Processing: point127
##    ✅ Saved: point127 .par
## 📄 [ 128 / 200 ] Processing: point128
##    ✅ Saved: point128 .par
## 📄 [ 129 / 200 ] Processing: point129
##    ✅ Saved: point129 .par
## 📄 [ 130 / 200 ] Processing: point130
##    ✅ Saved: point130 .par
## 📄 [ 131 / 200 ] Processing: point131
##    ✅ Saved: point131 .par
## 📄 [ 132 / 200 ] Processing: point132
##    ✅ Saved: point132 .par
## 📄 [ 133 / 200 ] Processing: point133
##    ✅ Saved: point133 .par
## 📄 [ 134 / 200 ] Processing: point134
##    ✅ Saved: point134 .par
## 📄 [ 135 / 200 ] Processing: point135
##    ✅ Saved: point135 .par
## 📄 [ 136 / 200 ] Processing: point136
##    ✅ Saved: point136 .par
## 📄 [ 137 / 200 ] Processing: point137
##    ✅ Saved: point137 .par
## 📄 [ 138 / 200 ] Processing: point138
##    ✅ Saved: point138 .par
## 📄 [ 139 / 200 ] Processing: point139
##    ✅ Saved: point139 .par
## 📄 [ 140 / 200 ] Processing: point140
##    ✅ Saved: point140 .par
## 📄 [ 141 / 200 ] Processing: point141
##    ✅ Saved: point141 .par
## 📄 [ 142 / 200 ] Processing: point142
##    ✅ Saved: point142 .par
## 📄 [ 143 / 200 ] Processing: point143
##    ✅ Saved: point143 .par
## 📄 [ 144 / 200 ] Processing: point144
##    ✅ Saved: point144 .par
## 📄 [ 145 / 200 ] Processing: point145
##    ✅ Saved: point145 .par
## 📄 [ 146 / 200 ] Processing: point146
##    ✅ Saved: point146 .par
## 📄 [ 147 / 200 ] Processing: point147
##    ✅ Saved: point147 .par
## 📄 [ 148 / 200 ] Processing: point148
##    ✅ Saved: point148 .par
## 📄 [ 149 / 200 ] Processing: point149
##    ✅ Saved: point149 .par
## 📄 [ 150 / 200 ] Processing: point150
##    ✅ Saved: point150 .par
## 📄 [ 151 / 200 ] Processing: point151
##    ✅ Saved: point151 .par
## 📄 [ 152 / 200 ] Processing: point152
##    ✅ Saved: point152 .par
## 📄 [ 153 / 200 ] Processing: point153
##    ✅ Saved: point153 .par
## 📄 [ 154 / 200 ] Processing: point154
##    ✅ Saved: point154 .par
## 📄 [ 155 / 200 ] Processing: point155
##    ✅ Saved: point155 .par
## 📄 [ 156 / 200 ] Processing: point156
##    ✅ Saved: point156 .par
## 📄 [ 157 / 200 ] Processing: point157
##    ✅ Saved: point157 .par
## 📄 [ 158 / 200 ] Processing: point158
##    ✅ Saved: point158 .par
## 📄 [ 159 / 200 ] Processing: point159
##    ✅ Saved: point159 .par
## 📄 [ 160 / 200 ] Processing: point160
##    ✅ Saved: point160 .par
## 📄 [ 161 / 200 ] Processing: point161
##    ✅ Saved: point161 .par
## 📄 [ 162 / 200 ] Processing: point162
##    ✅ Saved: point162 .par
## 📄 [ 163 / 200 ] Processing: point163
##    ✅ Saved: point163 .par
## 📄 [ 164 / 200 ] Processing: point164
##    ✅ Saved: point164 .par
## 📄 [ 165 / 200 ] Processing: point165
##    ✅ Saved: point165 .par
## 📄 [ 166 / 200 ] Processing: point166
##    ✅ Saved: point166 .par
## 📄 [ 167 / 200 ] Processing: point167
##    ✅ Saved: point167 .par
## 📄 [ 168 / 200 ] Processing: point168
##    ✅ Saved: point168 .par
## 📄 [ 169 / 200 ] Processing: point169
##    ✅ Saved: point169 .par
## 📄 [ 170 / 200 ] Processing: point170
##    ✅ Saved: point170 .par
## 📄 [ 171 / 200 ] Processing: point171
##    ✅ Saved: point171 .par
## 📄 [ 172 / 200 ] Processing: point172
##    ✅ Saved: point172 .par
## 📄 [ 173 / 200 ] Processing: point173
##    ✅ Saved: point173 .par
## 📄 [ 174 / 200 ] Processing: point174
##    ✅ Saved: point174 .par
## 📄 [ 175 / 200 ] Processing: point175
##    ✅ Saved: point175 .par
## 📄 [ 176 / 200 ] Processing: point176
##    ✅ Saved: point176 .par
## 📄 [ 177 / 200 ] Processing: point177
##    ✅ Saved: point177 .par
## 📄 [ 178 / 200 ] Processing: point178
##    ✅ Saved: point178 .par
## 📄 [ 179 / 200 ] Processing: point179
##    ✅ Saved: point179 .par
## 📄 [ 180 / 200 ] Processing: point180
##    ✅ Saved: point180 .par
## 📄 [ 181 / 200 ] Processing: point181
##    ✅ Saved: point181 .par
## 📄 [ 182 / 200 ] Processing: point182
##    ✅ Saved: point182 .par
## 📄 [ 183 / 200 ] Processing: point183
##    ✅ Saved: point183 .par
## 📄 [ 184 / 200 ] Processing: point184
##    ✅ Saved: point184 .par
## 📄 [ 185 / 200 ] Processing: point185
##    ✅ Saved: point185 .par
## 📄 [ 186 / 200 ] Processing: point186
##    ✅ Saved: point186 .par
## 📄 [ 187 / 200 ] Processing: point187
##    ✅ Saved: point187 .par
## 📄 [ 188 / 200 ] Processing: point188
##    ✅ Saved: point188 .par
## 📄 [ 189 / 200 ] Processing: point189
##    ✅ Saved: point189 .par
## 📄 [ 190 / 200 ] Processing: point190
##    ✅ Saved: point190 .par
## 📄 [ 191 / 200 ] Processing: point191
##    ✅ Saved: point191 .par
## 📄 [ 192 / 200 ] Processing: point192
##    ✅ Saved: point192 .par
## 📄 [ 193 / 200 ] Processing: point193
##    ✅ Saved: point193 .par
## 📄 [ 194 / 200 ] Processing: point194
##    ✅ Saved: point194 .par
## 📄 [ 195 / 200 ] Processing: point195
##    ✅ Saved: point195 .par
## 📄 [ 196 / 200 ] Processing: point196
##    ✅ Saved: point196 .par
## 📄 [ 197 / 200 ] Processing: point197
##    ✅ Saved: point197 .par
## 📄 [ 198 / 200 ] Processing: point198
##    ✅ Saved: point198 .par
## 📄 [ 199 / 200 ] Processing: point199
##    ✅ Saved: point199 .par
## 📄 [ 200 / 200 ] Processing: point200
##    ✅ Saved: point200 .par
cat("\n")
cat("=" , 60, "=\n")
## = 60 =
cat("SUMMARY:\n")
## SUMMARY:
cat("✅ Successfully saved:", success_count, "points\n")
## ✅ Successfully saved: 200 points
cat("❌ Failed:", fail_count, "points\n")
## ❌ Failed: 0 points
if (fail_count > 0) {
  cat("   Failed points:", paste(failed_points, collapse = ", "), "\n")
}
cat("=" , 60, "=\n")
## = 60 =