Setting Up R for Satellite Imagery Work
I spent about three days getting my R environment configured properly for handling Sentinel-2 and Landsat 8 data. The initial setup is not trivial. You need the right combination of packages, a working GDAL installation, and enough RAM if you plan to process anything larger than a single scene at full resolution. I ran into a problem where terra would crash silently whenever I tried to mosaic more than twelve 10-meter bands together on a machine with 32GB of RAM. The workaround was to chunk the rasters by band rather than by spatial extent, which let the operation finish in about twenty minutes instead of hanging until the swap file consumed everything. R was never designed as a remote sensing tool. It was built for statistics. That mismatch is exactly why the workflow feels awkward at first, but it also means you have statistical modeling capabilities baked into the same environment where your imagery lives. You do not need to export to Python or ArcGIS just to run a classifier or fit a regression. The terra and stars packages have closed a lot of that gap in recent years, but there are still rough edges worth knowing about before you commit to a project. The basic pipeline involves downloading raw surface reflectance data from USGS EarthExplorer or the Copernicus Open Access Hub, organizing it into a consistent directory structure, reading the files with terra or stars, aligning projections, running any atmospheric or cloud correction you need, and then proceeding to whatever analysis you are doing. I keep my scenes in year/month folders because the metadata files are scattered across different locations and you need a predictable path to loop through them reliably.
One thing most tutorials skip is the nodata handling. Sentinel-2 bands carry fill values that are not the same as actual no-data pixels, and if you do not flag them correctly before any calculation, your NDVI outputs will be garbage. The raster values for water in Band 1 (coastal aerosol) show up as negative numbers in the surface reflectance products, which trips up anyone who assumes all valid pixels are positive. I add a simple masking step after reading every scene, setting values below -0.01 to NA regardless of band. It takes an extra thirty seconds per image and has saved me from publishing incorrect results more than once. Required packages for a standard workflow You will need terra for the heavy lifting, stars for multi-dimensional array operations, sf for vector work, rgdal or the newer gdalUtils for projection handling, and RStoolbox if you want supervised classification without writing your own code. Install them once and forget about updating individual versions unless something breaks, because package dependencies in this ecosystem can cascade unpredictably.
The download process itself is tedious. USGS requires an account, and the EarthSearch API endpoint through STAC catalogs is faster if you script it. I use the earthaccess package in combination with aws-sigv4 for authenticated S3 access to Sentinel-2 collections. A single L2A scene at granule level is roughly 400 megabytes uncompressed. If you are processing a time series across multiple months, you will fill up disk space quickly, so I keep only the bands I actually need and delete the XML metadata files after extracting the product name and acquisition date from them. Atmospheric correction is another area where people make assumptions they should not. The Sen2Cor processor that generates Sentinel-2 L2A products is decent for open terrain but struggles under persistent haze and thin cirrus. I cross-check the QA60 band against visual inspection of the blue band before trusting the surface reflectance values. When the QA60 cloud probability flags are borderline, I apply a manual mask using the circular polarization band (Band 9) combined with a threshold on the water vapor absorption band. This catches about twelve percent more contaminated pixels than relying on the built-in quality flags alone. For classification, RStoolbox's rand_forest function works well for land cover mapping when you have field data or a reasonable training dataset. The default parameters are adequate for a first pass, but the model tends to overfit on training accuracy while underperforming on held-out validation sites by ten to fifteen percent. I allocate a fixed random seed and reserve twenty percent of my reference points upfront rather than splitting them after looking at the data, because data leakage in remote sensing classification is easier to introduce than most people realize.
Get the Full Details

The biggest bottleneck I encounter is not computation, it is I/O. Reading large NetCDF or GeoTIFF stacks from spinning disks dominates processing time more than any calculation step. I store my active project data on an NVMe drive and keep archived scenes on a separate volume. This cut my typical scene read time from forty-five seconds down to about eight seconds for a full L2A product. Spectral indices are straightforward once your data is in surface reflectance form. Normalized Difference Vegetation Index is just (NIR - Red) divided by (NIR + Red), and terra computes that in roughly two seconds for a 10980 by 10980 pixel Sentinel-2 scene on my machine. The NDWI variant for water body extraction uses the green band instead of NIR and flags differently depending on whether you want vegetation moisture or open water. Pick the version that matches your actual question instead of using the first one you find on a blog post. Temporal compositing is where R shows real strength compared to point-and-click GIS tools. Building a median composite across a growing season using terra's app function with a groupedBy approach handles the aggregation without loading every image into memory simultaneously. A six-month median composite for a moderate-sized area typically takes between three and six minutes depending on cloud cover and your chunk size parameter.
There are scenarios where R is the wrong choice. If you need real-time processing of streaming satellite data, or if your analysis requires GPU acceleration for deep learning models, you are better off in Python withRasterio and PyTorch. R handles structured batch analysis efficiently, but it does not scale to petabyte-level archives or continuous ingestion pipelines without significant additional infrastructure. For those cases, I hand off the preprocessing to an automated Python pipeline and bring the cleaned surface reflectance stacks into R for the actual modeling and statistical work. The learning curve is steeper than working in a graphical interface, but the reproducibility gains are real. A complete analysis that I previously ran manually in ArcGIS and Export to Excel can now be executed end-to-end from raw download to publication-ready maps in a single R Markdown document, usually taking under ten minutes of wall clock time after the initial scripts are in place. The scripts themselves require maintenance when package updates change function signatures, which happens maybe once or twice a year for the core spatial packages. Reference data integration is another practical advantage. I frequently merge field-collected GPS coordinates stored as CSV files with satellite-derived indices using simple coordinate matching or nearest-neighbor interpolation within sf. The spatial join operations are fast and keep the data types consistent throughout the workflow, which eliminates a whole category of errors that comes from switching between different software environments.