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 =