├── cover.png ├── Ch 3 ├── world_boundaries.gpkg ├── chapter_3_saving.r ├── chapter_3_joining.r ├── chapter_3_load_csv.r ├── chapter_3_load_vector.r ├── chapter_3_cleaning.r └── country_indicators.csv ├── Ch 6 ├── chapter_6_setup.r ├── chapter_6_style_lines.r ├── chapter_6_fetch_lines.r ├── chapter_6_project_rivers.r ├── chapter_6_flow_map_simple.r └── chapter_6_simplify_lines.r ├── Ch 7 ├── chapter_7_plot_terra.r ├── chapter_7_setup.r ├── chapter_7_load_raster.r ├── chapter_7_inspect_raster.r ├── chapter_7_project_country_elev.r ├── chapter_7_crop_raster.r └── chapter_7_plot_ggplot.r ├── Ch 9 ├── chapter_9_setup.r ├── chapter_9_basic_map.r ├── chapter_9_save_map.r ├── chapter_9_custom_popups.r ├── chapter_9_provider_tiles.r ├── chapter_9_add_polygons.r ├── chapter_9_add_markers.r ├── chapter_9_layers_control.r └── chapter_9_project_landmarks.r ├── Ch 10 ├── chapter_10_camera_test.r ├── chapter_10_setup.r ├── chapter_10_color_3d.r ├── chapter_10_prep_data.r ├── chapter_10_basic_3d.r ├── chapter_10_save_output.r ├── chapter_10_ggplot_3d.r └── chapter_10_project_3d_model.r ├── Ch 8 ├── chapter_8_setup.r ├── chapter_8_get_data.r ├── chapter_8_overlay_vector.r ├── chapter_8_contours.r ├── chapter_8_shaded_relief.r ├── chapter_8_terrain_attributes.r └── chapter_8_project_topo.r ├── Ch 11 ├── chapter_11_setup.r ├── chapter_11_easing.r ├── chapter_11_base_ggplot.r ├── chapter_11_basic_animation.r ├── chapter_11_save_animation.r ├── chapter_11_data_prep.r └── chapter_11_project_gdp.r ├── Ch 4 ├── chapter_4_basic_choropleth.r ├── chapter_4_custom_colors.r ├── chapter_4_project_africa.r └── chapter_4_map_elements.r ├── Ch 5 ├── chapter_5_dot_map.r ├── chapter_5_bubble_map.r ├── chapter_5_setup.r ├── chapter_5_labels.r ├── chapter_5_style_by_category.r └── chapter_5_project_germany.r ├── Ch 1 └── chapter_1_exercise.R ├── README.md └── Ch 2 └── chapter_2_explore_sf.r /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milos-agathon/book-mapping-worlds-with-r-examples/HEAD/cover.png -------------------------------------------------------------------------------- /Ch 3/world_boundaries.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/milos-agathon/book-mapping-worlds-with-r-examples/HEAD/Ch 3/world_boundaries.gpkg -------------------------------------------------------------------------------- /Ch 6/chapter_6_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_6_setup.R --- 2 | 3 | # Step 1: Load necessary packages 4 | print("Loading packages...") 5 | # Ensure pacman is installed and loaded if you use it 6 | if (!requireNamespace("pacman", quietly = TRUE)) install.packages("pacman") 7 | pacman::p_load(sf, tidyverse, osmdata, ggspatial, patchwork) 8 | # ggspatial for map elements later 9 | # patchwork for combining plots later 10 | print("Packages ready.") -------------------------------------------------------------------------------- /Ch 7/chapter_7_plot_terra.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_plot_terra.R --- 2 | # (Can be in same script) 3 | 4 | # Ensure the raster object exists 5 | 6 | # Step 1: Use terra's built-in plot function 7 | # Just pass the SpatRaster object to plot() 8 | print("Generating basic plot using terra::plot()...") 9 | # This opens in the standard R graphics device 10 | # (Plots pane in RStudio) 11 | terra::plot(global_elevation_10min) 12 | print("Basic plot displayed in Plots pane.") -------------------------------------------------------------------------------- /Ch 3/chapter_3_saving.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_3_saving.R --- 2 | 3 | # Step 1: Load required package 4 | message("Loading packages...") 5 | pacman::p_load(sf) 6 | 7 | # Step 2: Define output path 8 | output_filename <- "world_data_cleaned_joined.gpkg" 9 | message("Saving cleaned data to: ", output_filename) 10 | 11 | # Step 4: Write the GeoPackage 12 | sf::st_write(world_data_joined, output_filename, delete_layer = TRUE) 13 | 14 | message("Data saved successfully!") -------------------------------------------------------------------------------- /Ch 9/chapter_9_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_setup.R --- 2 | 3 | # Step 1: Load necessary packages using pacman 4 | print("Loading packages: leaflet, sf, tidyverse...") 5 | # Ensure pacman is loaded first if needed 6 | if ( 7 | !requireNamespace( 8 | "pacman", quietly = TRUE) 9 | ) install.packages("pacman") 10 | library(pacman) 11 | 12 | # Load our main packages for this chapter 13 | p_load(leaflet, sf, tidyverse) 14 | 15 | print("Packages ready for interactive mapping!") -------------------------------------------------------------------------------- /Ch 10/chapter_10_camera_test.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_camera_test.R --- 2 | # (Can be in same script) 3 | # Example: Re-run plot_gg with different camera angles 4 | 5 | print("Plotting with different camera angle (more top-down)...") 6 | rayshader::render_camera( 7 | zoom = 0.6, 8 | theta = 0, 9 | phi = 60 10 | ) # Changed theta and phi! 11 | 12 | print("--- Now try manually rotating the RGL window! ---") 13 | print("--- Then type 'rayshader::render_camera()' in the 14 | CONSOLE ---") 15 | # Sys.sleep(10) # Pause to allow interaction -------------------------------------------------------------------------------- /Ch 8/chapter_8_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_setup.R --- 2 | 3 | # Step 1: Load necessary packages using pacman 4 | print("Loading packages...") 5 | # Ensure pacman is loaded first if needed 6 | if ( 7 | !requireNamespace("pacman", quietly = TRUE) 8 | ) install.packages("pacman") 9 | library(pacman) 10 | 11 | # Load our main packages 12 | p_load(terra, geodata, sf, tidyverse, viridis) # ggplot2 is part of 13 | # tidyverse 14 | 15 | # Step 2: Set theme (optional, for consistent ggplot plots) 16 | theme_set(theme_minimal()) 17 | 18 | print("Packages ready!") -------------------------------------------------------------------------------- /Ch 7/chapter_7_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_setup.R --- 2 | 3 | # Step 1: Load necessary packages using pacman 4 | print("Loading packages: terra, sf, tidyverse, geodata...") 5 | # Ensure pacman is loaded first if needed 6 | if ( 7 | !requireNamespace("pacman", quietly = TRUE) 8 | ) install.packages("pacman") 9 | library(pacman) 10 | 11 | # Load our main packages 12 | p_load(terra, sf, tidyverse, geodata) 13 | 14 | # Step 2: Set theme (optional, for consistent ggplot plots later) 15 | theme_set(theme_minimal()) 16 | 17 | print("Packages ready for raster action!") -------------------------------------------------------------------------------- /Ch 10/chapter_10_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_setup.R --- 2 | 3 | # Step 1: Load necessary packages using pacman 4 | print("Loading packages") 5 | # Ensure pacman is loaded first if needed 6 | if (!requireNamespace( 7 | "pacman", quietly = TRUE) 8 | ) install.packages("pacman") 9 | library(pacman) 10 | 11 | # Load our main packages 12 | # This might trigger installation if it's the first time! 13 | p_load(rayshader, terra, geodata, sf, tidyverse) 14 | 15 | print("Packages ready for 3D!") 16 | print("--- IMPORTANT: If 'rayshader' loading failed, check 17 | installation notes! ---") -------------------------------------------------------------------------------- /Ch 9/chapter_9_basic_map.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_basic_map.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Initialize a leaflet map widget 5 | # The leaflet() function creates the basic map container. 6 | print("Creating basic leaflet map...") 7 | basic_map <- leaflet() |> # Start with leaflet() 8 | # Step 2: Add the default base map tiles (OpenStreetMap) 9 | # Use the pipe |> to send the map object to addTiles() 10 | addTiles() # This adds the standard OSM tile layer 11 | 12 | # Step 3: Print the map object to display it 13 | print("Displaying the map (check RStudio Viewer pane)...") 14 | # In RStudio, this will show the map in the Viewer pane 15 | # (often near Plots) 16 | # Outside RStudio (e.g., in R Markdown), it embeds the interactive 17 | # map. 18 | print(basic_map) -------------------------------------------------------------------------------- /Ch 3/chapter_3_joining.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_3_joining.R --- 2 | # Step 1: Load packages and verify data exists 3 | pacman::p_load(sf, dplyr, readr) 4 | 5 | # Step 2: Load the dataset and inspect the key columns 6 | country_indicators_loaded <- readr::read_csv( 7 | "country_indicators.csv") 8 | print(head(world_renamed$iso3)) 9 | print(head(country_indicators_loaded$iso3c)) 10 | 11 | # Step 3: Perform the left join 12 | world_data_joined <- world_renamed |> 13 | dplyr::left_join( 14 | country_indicators_loaded, 15 | by = c("iso3" = "iso3c")) 16 | 17 | # Step 4: Inspect the result 18 | print("Join complete! Here's a quick glimpse:") 19 | glimpse(world_data_joined) 20 | 21 | na_count <- sum(is.na(world_data_joined$population)) 22 | print("Number of countries with NA in 'population': ", na_count) -------------------------------------------------------------------------------- /Ch 9/chapter_9_save_map.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_save_map.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure the map object exists and load htmlwidgets 5 | pacman::p_load(htmlwidgets) 6 | 7 | # Step 2: Define the output filename (in your project folder) 8 | output_html_file <- "my_interactive_map.html" 9 | print(paste("Saving map to:", output_html_file)) 10 | 11 | # Step 3: Save the widget using saveWidget() 12 | # First argument is the leaflet map object 13 | # Second argument is the file path/name 14 | # selfcontained = TRUE bundles everything needed into the 15 | # single HTML file 16 | htmlwidgets::saveWidget( 17 | map_with_layers, file = output_html_file, 18 | selfcontained = TRUE) 19 | 20 | print("Map saved successfully! Find the HTML file in your project 21 | folder.") 22 | print("You can double-click the HTML file to open it in your web 23 | browser.") -------------------------------------------------------------------------------- /Ch 11/chapter_11_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_setup.R --- 2 | 3 | # Step 1: Load necessary packages using pacman 4 | print("Loading packages for Animation...") 5 | # Ensure pacman is loaded first if needed 6 | if (!requireNamespace( 7 | "pacman", quietly = TRUE) 8 | ) install.packages("pacman") 9 | library(pacman) 10 | 11 | # Load our main packages 12 | p_load( 13 | gganimate, # The star for animating ggplots 14 | sf, # For spatial data 15 | tidyverse, # For dplyr, piping etc. 16 | gapminder, # Our time series dataset 17 | rnaturalearth, # For world map boundaries 18 | countrycode, # For matching country names to ISO codes 19 | gifski, # For rendering GIFs 20 | av # For rendering MP4s (needs ffmpeg) 21 | ) 22 | 23 | print("Packages ready for animation!") 24 | print( 25 | "--- NOTE: Saving MP4 video with 'av' requires external 'ffmpeg' 26 | installation. ---") -------------------------------------------------------------------------------- /Ch 7/chapter_7_load_raster.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_load_raster.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Use geodata to download and load global elevation data 5 | # res = 10 means 10 arc-minute resolution (coarse, smaller file) 6 | # path = tempdir() saves the download temporarily 7 | print("Fetching 10-minute resolution global elevation data 8 | using geodata...") 9 | # This might take a moment the first time it downloads the file! 10 | 11 | global_elevation_10min <- geodata::elevation_global( 12 | res = 10, path = tempdir()) 13 | print("Elevation data downloaded/loaded!") 14 | 15 | # Step 2: Print the object to see the summary 16 | # This shows the key properties of the raster data we loaded. 17 | print("--- Raster Summary ---") 18 | print(global_elevation_10min) 19 | print("--- End Summary ---") 20 | 21 | # Store for later use 22 | assign( 23 | "global_elevation_10min", 24 | global_elevation_10min, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 4/chapter_4_basic_choropleth.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_4_basic_choropleth.R --- 2 | 3 | # Step 1: Load necessary packages 4 | print("Loading packages...") 5 | if (!requireNamespace("pacman", quietly = TRUE)) { 6 | install.packages("pacman") 7 | } 8 | pacman::p_load(tidyverse) 9 | print("Packages ready.") 10 | 11 | # Step 2: Load the cleaned and joined data 12 | print("Loading cleaned spatial data...") 13 | data_file <- "world_data_cleaned_joined.gpkg" 14 | world_data_loaded <- sf::st_read(data_file) 15 | print("Cleaned data loaded.") 16 | 17 | # Step 3: Plot a basic choropleth 18 | map_plot_basic <- ggplot2::ggplot(world_data_loaded) + 19 | ggplot2::geom_sf( 20 | mapping = aes(fill = gdp_per_capita), 21 | color = "white", 22 | linewidth = 0.1 23 | ) + 24 | ggplot2::labs( 25 | title = "GDP per capita (2020)", 26 | fill = "US Dollars" 27 | ) + 28 | ggplot2::theme_minimal() 29 | # print("Map potentially saved.") 30 | 31 | # Step 4: Render 32 | print(map_plot_basic) -------------------------------------------------------------------------------- /Ch 4/chapter_4_custom_colors.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_4_custom_colors.R --- 2 | 3 | # Ensure required packages are installed and loaded 4 | pacman::p_load(sf, ggplot2, scales) 5 | 6 | # Create a choropleth using the Viridis 'rocket' palette with log10 7 | # scaling 8 | map_plot_viridis <- ggplot2::ggplot(data = world_data_loaded) + 9 | ggplot2::geom_sf( 10 | mapping = ggplot2::aes(fill = gdp_per_capita), 11 | color = "white", 12 | linewidth = 0.1 13 | ) + 14 | scale_fill_viridis_c( 15 | option = "rocket", 16 | direction = -1, 17 | name = "US dollars", 18 | na.value = "grey80", 19 | trans = "log10", 20 | # Use special labels for log scale 21 | # (requires scales package) 22 | labels = scales::label_log(digits = 2) 23 | ) + 24 | ggplot2::labs(title = "GDP per capita") + 25 | ggplot2::theme_minimal() + 26 | ggplot2::theme(legend.position = "bottom", 27 | legend.key.width = grid::unit(1.5, "cm")) 28 | 29 | # Display the map 30 | print(map_plot_viridis) -------------------------------------------------------------------------------- /Ch 3/chapter_3_load_csv.r: -------------------------------------------------------------------------------- 1 | # Step 1: Load necessary packages 2 | print("Loading packages...") 3 | pacman::p_load(readr, dplyr) # readr for read_csv, dplyr for glimpse 4 | print("Packages ready.") 5 | 6 | # Step 2: Load the CSV file using read_csv() 7 | # Again, use the relative path from the project root. 8 | print("Loading tabular data country_indicators.csv...") 9 | 10 | country_indicators_loaded <- readr::read_csv("country_indicators.csv") 11 | 12 | # Step 3: Inspect the loaded data 13 | print("CSV data loaded successfully!") 14 | print("--- Object Class ---") 15 | print(class(country_indicators_loaded)) # Should be 'spec_tbl_df', 'tbl_df', 'tbl', 'data.frame' 16 | print("--- First few rows ---") 17 | print(head(country_indicators_loaded)) 18 | print("--- Column Summary (glimpse) ---") 19 | dplyr::glimpse(country_indicators_loaded) # Useful for seeing column types 20 | 21 | # Make the object available outside the chunk if needed 22 | assign("country_indicators_loaded", 23 | country_indicators_loaded, 24 | envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 11/chapter_11_easing.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_easing.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Start with the base ggplot (base_life_exp_map) 5 | print("Adding animation layers with easing...") 6 | animated_map_eased <- base_life_exp_map + 7 | gganimate::transition_time(year) + 8 | labs( 9 | title = "Life Expectancy in Year: {as.integer(frame_time)" 10 | ) + 11 | # *** ADD Easing function *** # 'sine-in-out' is common 12 | # choice. 13 | # Other options: 'linear', 'quadratic-in-out', 'bounce-in', 14 | # etc. 15 | gganimate::ease_aes('sine-in-out') 16 | 17 | # Step 3: Render the eased animation 18 | print("Rendering eased animation...") 19 | anim_render_eased <- gganimate::animate( 20 | animated_map_eased, 21 | nframes = 150, 22 | fps = 10, 23 | width = 800, 24 | height = 500, 25 | renderer = gifski_renderer() 26 | ) 27 | print(anim_render_eased) 28 | print("Eased animation rendering complete.") 29 | 30 | # Store eased animation 31 | assign("animated_map_eased", animated_map_eased, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 6/chapter_6_style_lines.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_6_style_lines.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure ggplot2 and data exist 5 | pacman::p_load(ggplot2) 6 | 7 | # Step 2: Create the ggplot map 8 | print("Creating styled motorway map...") 9 | 10 | styled_motorway_map <- ggplot() + 11 | # Add the motorway lines using geom_sf 12 | # Set style attributes OUTSIDE aes() for a fixed style for all motorways 13 | geom_sf(data = city_motorways_sf, 14 | color = "darkgoldenrod", 15 | # Make motorways goldenrod color 16 | linewidth = 0.6) + # Set line thickness (use size pre ggplot2 3.4.0) 17 | 18 | # Add labels and theme 19 | labs( 20 | title = "Motorways in Shenzhen", 21 | caption = "Data: OpenStreetMap contributors") + 22 | theme_minimal() + 23 | theme( 24 | axis.text = element_blank(), 25 | # Hide axis text/ticks 26 | axis.ticks = element_blank(), 27 | panel.grid = element_blank() 28 | ) # Hide grid lines 29 | 30 | # Step 3: Display the map 31 | print(styled_motorway_map) 32 | -------------------------------------------------------------------------------- /Ch 9/chapter_9_custom_popups.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_custom_popups.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure packages and city data are ready 5 | pacman::p_load(leaflet, sf, tidyverse, glue) # Need glue package 6 | 7 | # Step 2: Create map with customized popups 8 | print("Adding markers with custom HTML popups...") 9 | map_custom_popups <- leaflet(data = cities_sf) |> 10 | addProviderTiles(provider = providers$CartoDB.Positron) |> 11 | addMarkers( 12 | # Create popup content using glue::glue() inside the ~ 13 | # formula 14 | # glue() makes it easy to mix plain text and 15 | # {variable_names} 16 | # We include basic HTML tags: ... for bold,
17 | # line break 18 | popup = ~ glue::glue( 19 | "{name}
", # City name in bold, then line break 20 | "{info}" # Info from the 'info' column in italics) 21 | )) |> 22 | setView(lng = 30, lat = 20, zoom = 2) # Set view 23 | 24 | # Step 3: Display map 25 | print("Displaying map with custom popups...") 26 | print(map_custom_popups) -------------------------------------------------------------------------------- /Ch 3/chapter_3_load_vector.r: -------------------------------------------------------------------------------- 1 | # Step 1: Load necessary packages 2 | 3 | print("Loading packages...") 4 | # Ensure pacman is installed and loaded if you use it 5 | if (!requireNamespace("pacman", quietly = TRUE)) 6 | install.packages("pacman") 7 | pacman::p_load(sf, dplyr) # Need sf for st_read, 8 | # dplyr for glimpse later 9 | print("Packages ready.") 10 | 11 | # Step 2: Load the GeoPackage file using st_read() 12 | # We provide the RELATIVE PATH from the project root folder. 13 | # Our file is inside the 'data' subfolder. 14 | print("Loading vector data world_boundaries.gpkg...") 15 | 16 | world_boundaries_loaded <- sf::st_read("world_boundaries.gpkg") 17 | 18 | # Step 3: Inspect the loaded data 19 | print("Inspecting the data...") 20 | print(class(world_boundaries_loaded)) 21 | 22 | print("First few rows:") 23 | print(head(world_boundaries_loaded)) 24 | 25 | print("Coordinate Reference System:") 26 | print(sf::st_crs(world_boundaries_loaded)) 27 | 28 | # Make the object available outside the chunk if needed 29 | assign("world_boundaries_loaded", world_boundaries_loaded, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 5/chapter_5_dot_map.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_5_dot_map.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure ggplot2 is loaded and data exists 5 | pacman::p_load(tidyverse) 6 | 7 | dot_map <- ggplot() + 8 | # Layer 1: World map background (light grey, white borders) 9 | geom_sf( 10 | data = world_map_background, 11 | fill = "grey80", 12 | color = "white", 13 | linewidth = 0.1 14 | ) + 15 | # Layer 2: City points 16 | # geom_sf automatically draws points because 17 | # cities_sf has POINT geometry 18 | geom_sf( 19 | data = cities_sf, 20 | color = "purple", 21 | # Set all points to blue 22 | size = 1, 23 | # Set point size 24 | shape = 16, 25 | # Use a solid circle shape 26 | # (default is often hollow) 27 | alpha = 0.6 28 | ) + # Add transparency for overplotting! 29 | 30 | # Add title and theme 31 | labs(title = "World Cities with Population > 1 Million") + 32 | theme_minimal() + 33 | theme(axis.text = element_blank()) # Hide axis labels 34 | 35 | # Step 3: Display the map 36 | print("Displaying dot map...") 37 | print(dot_map) -------------------------------------------------------------------------------- /Ch 8/chapter_8_get_data.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_get_data.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Define country code 5 | country_iso <- "CHE" # ISO 3166-1 alpha-3 code for Switzerland 6 | print(paste("Fetching elevation data for:", country_iso)) 7 | 8 | # Step 2: Download/Load elevation data using geodata::elevation_30s 9 | # This gets SRTM 30-arc-second data (~1km resolution) 10 | # path = tempdir() saves download temporarily 11 | 12 | swiss_elev_raster <- geodata::elevation_30s( 13 | country = country_iso, path = tempdir()) 14 | print("Elevation data downloaded/loaded.") 15 | 16 | # Step 3: Inspect the loaded raster object using print() 17 | print("--- Raster Summary ---") 18 | print(swiss_elev_raster) 19 | # Note the CRS (likely WGS84 EPSG:4326) and resolution (approx 20 | # 0.0083 degrees) 21 | 22 | # Step 4: Quick plot using terra's built-in plot() function 23 | print("Generating quick plot...") 24 | terra::plot( 25 | swiss_elev_raster, 26 | main = paste("Elevation (SRTM 30s) -", country_iso)) 27 | print("Quick plot done.") 28 | 29 | # Store for later use 30 | assign( 31 | "swiss_elev_raster", swiss_elev_raster, 32 | envir = .GlobalEnv 33 | ) -------------------------------------------------------------------------------- /Ch 10/chapter_10_color_3d.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_color_3d.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure elevation_matrix exists 5 | if (!exists("elevation_matrix")) { 6 | print( 7 | "ERROR: 'elevation_matrix' not found. 8 | Run data prep step first!") 9 | stop() 10 | } 11 | 12 | # Step 2: Create 3D plot with elevation-based color using 13 | # height_shade 14 | print("Creating colorized 3D plot using height_shade...") 15 | 16 | # Start with the elevation matrix 17 | elevation_matrix |> 18 | # Create shading based on height, using R's built-in terrain 19 | # color palette 20 | # texture = grDevices::terrain.colors(256) generates 256 colors 21 | rayshader::height_shade( 22 | texture = grDevices::terrain.colors(256)) |> 23 | 24 | # Pipe this colored texture into plot_3d 25 | rayshader::plot_3d( 26 | heightmap = elevation_matrix, # Need heightmap for geometry 27 | zscale = 10, solid = FALSE, fov = 0, 28 | theta = 0, phi = 80, 29 | zoom = 0.6, windowsize = c(1000, 800), 30 | background = "lightgrey" 31 | ) 32 | 33 | print("Colorized 3D plot should appear in RGL window.") 34 | # Remember to close the RGL window manually. 35 | Sys.sleep(2) -------------------------------------------------------------------------------- /Ch 6/chapter_6_fetch_lines.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_6_fetch_lines.R --- # (Can be in same script) 2 | 3 | # Step 1: Get bounding box for Shenzhen, China 4 | # Using a smaller, specific area is key for manageable line queries! 5 | print("Getting bounding box for Shenzhen, China...") 6 | 7 | city_bbox <- osmdata::getbb("Shenzhen, China") 8 | print("Bounding box obtained:") 9 | print(city_bbox) 10 | 11 | # Step 2: Build and run OSM query for paths within the city 12 | print("Querying OSM for motorways (highway=motorway) in Shenzhen.") 13 | # opq() starts the query for the bounding box. Increase timeout if needed. 14 | # add_osm_feature specifies we want features tagged highway=path 15 | # osmdata_sf() executes the query and returns sf objects 16 | osm_paths_query <- osmdata::opq(bbox = city_bbox, timeout = 60) |> 17 | osmdata::add_osm_feature(key = "highway", value = "motorway") |> 18 | osmdata::osmdata_sf() # Execute and get sf objects 19 | 20 | print("OSM query complete.") 21 | 22 | # Step 3: Extract the lines object 23 | # Path geometries are usually in osm_lines 24 | city_motorways_sf <- osm_paths_query$osm_lines 25 | plot(sf::st_geometry(city_motorways_sf)) 26 | 27 | assign("city_motorways_sf", city_motorways_sf, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 10/chapter_10_prep_data.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_prep_data.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Get Swiss elevation data (as SpatRaster) 5 | # Reload if needed, or use object from Chapter 8 if still in 6 | # environment 7 | 8 | country_iso <- "CHE" 9 | 10 | swiss_elev_raster <- geodata::elevation_30s( 11 | country = country_iso, path = tempdir()) 12 | print("Elevation data loaded.") 13 | 14 | assign( 15 | "swiss_elev_raster", swiss_elev_raster, 16 | envir = .GlobalEnv 17 | ) 18 | 19 | print("Using existing 'swiss_elev_raster' object.") 20 | 21 | # Step 2: Convert the SpatRaster to a matrix using 22 | # rayshader::raster_to_matrix 23 | # This function specifically prepares the raster data for rayshader 24 | # functions. 25 | print("Converting elevation raster to matrix...") 26 | elevation_matrix <- rayshader::raster_to_matrix( 27 | swiss_elev_raster) 28 | print("Conversion complete!") 29 | 30 | # Step 3: Inspect the matrix (optional, to see the structure) 31 | print("--- Matrix Dimensions (rows, columns) ---") 32 | print(dim(elevation_matrix)) 33 | print("--- Top-Left Corner Values (elevation numbers) ---") 34 | # Show the elevation numbers for the first 5 rows and 5 columns 35 | print(elevation_matrix[1:5, 1:5]) 36 | 37 | # Store matrix for later use 38 | assign("elevation_matrix", elevation_matrix, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 9/chapter_9_provider_tiles.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_provider_tiles.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Initialize leaflet map 5 | print("Creating map with provider tiles...") 6 | provider_map_light <- leaflet() |> 7 | # Step 2: Add a different provider tile layer using the pipe 8 | # We use addProviderTiles() and specify the provider name. 9 | # Examples: "CartoDB.Positron", "Esri.WorldImagery", "OpenTopoMap" 10 | # Find many more provider names here: 11 | # http://leaflet-extras.github.io/leaflet-providers/preview/ 12 | # providers$CartoDB.Positron selects the specific tile set. 13 | addProviderTiles( 14 | provider = providers$CartoDB.Positron 15 | ) |> # Light grey map 16 | 17 | # Step 3: Optional: Set the initial view using setView() 18 | # setView(lng = longitude, lat = latitude, zoom = zoom_level) 19 | setView(lng = 0, lat = 30, zoom = 2) # Center roughly globally 20 | 21 | # Step 4: Display map 22 | print("Displaying light map...") 23 | print(provider_map_light) 24 | 25 | # --- Try another one! (Satellite imagery) --- 26 | print("Creating map with satellite tiles...") 27 | provider_map_sat <- leaflet() |> 28 | addProviderTiles( 29 | provider = providers$Esri.WorldImagery 30 | ) |> # Satellite 31 | setView( 32 | lng = 10, lat = 50, zoom = 4 33 | ) # Center roughly on Europe 34 | 35 | print("Displaying satellite map...") 36 | print(provider_map_sat) -------------------------------------------------------------------------------- /Ch 11/chapter_11_base_ggplot.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_base_ggplot.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure ggplot2 and data exist 5 | pacman::p_load(tidyverse) 6 | 7 | # Step 2: Create the base static ggplot 8 | # Use the FULL world_gapminder_sf dataset 9 | # Map the aesthetic (fill) to the variable we want to animate 10 | # (lifeExp) 11 | print("Creating base ggplot for animation...") 12 | 13 | base_life_exp_map <- ggplot(data = world_gapminder_sf) + 14 | # Use geom_sf, mapping lifeExp to fill color 15 | geom_sf( 16 | aes(fill = lifeExp), 17 | color = "white", # Add a light border 18 | linewidth = 0.1) + # Thin border 19 | 20 | # Use a nice sequential color scale (Magma is good for 21 | # low-to-high) 22 | scale_fill_viridis_c( 23 | option = "magma", name = "Life Exp.", 24 | na.value="lightgrey") + 25 | 26 | # Use a map-friendly theme 27 | theme_void() + 28 | theme( 29 | legend.position = "bottom", 30 | legend.key.width = unit(1.5, "cm"), 31 | plot.title = element_text(hjust = 0.5, size=16, face="bold"), 32 | # Title style 33 | plot.subtitle = element_text(hjust = 0.5, size=14) # Subtitle 34 | # style 35 | ) 36 | 37 | print("Base ggplot object 'base_life_exp_map' created.") 38 | # print(base_life_exp_map) # Static plot overlays ALL years! Not 39 | # useful. 40 | 41 | # Store base map 42 | assign("base_life_exp_map", base_life_exp_map, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 8/chapter_8_overlay_vector.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_overlay_vector.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure needed data exists from previous steps 5 | pacman::p_load( 6 | terra, tidyverse, tidyterra, ggnewscale, geodata 7 | ) 8 | 9 | print("Fetching Swiss boundary...") 10 | 11 | ch_boundary_sf <- geodata::gadm( 12 | country = "CHE", level = 0, path = tempdir()) |> 13 | sf::st_as_sf() |> 14 | sf::st_transform( 15 | crs = terra::crs(swiss_elev_raster)) # Match raster CRS 16 | 17 | assign("ch_boundary_sf", ch_boundary_sf, envir = .GlobalEnv) 18 | 19 | # Step 2: Add the shaded relief map from the previous step 20 | # and then add the boundary layer 21 | 22 | shaded_relief_with_border <- shaded_relief_map + 23 | # ADD LAYER: Country Boundary using geom_sf - TOP 24 | geom_sf( 25 | data = ch_boundary_sf, 26 | fill = NA, 27 | color = "black", 28 | linewidth = 0.5 29 | ) + # NA fill, black border 30 | # Labels and theme 31 | labs( 32 | title = "Shaded Relief Map of Switzerland with Border", 33 | x = "Longitude", 34 | y = "Latitude", 35 | caption = paste("Data: SRTM 30s via geodata.", "Hillshade: Sun from West (270 deg)") 36 | ) + 37 | coord_sf(crs = terra::crs(swiss_elev_raster)) + # Use 38 | # appropriate CRS 39 | theme_minimal() + 40 | theme(legend.position = "right", axis.text = element_blank()) 41 | 42 | # Step 3: Display map 43 | print(shaded_relief_with_border) 44 | print("Map with boundary overlay generated.") -------------------------------------------------------------------------------- /Ch 5/chapter_5_bubble_map.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_5_bubble_map.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure ggplot2 and data exist 5 | pacman::p_load(ggplot2, scales) # Need scales for label formatting 6 | # Step 2: Create the bubble map 7 | print("Creating bubble map (size by population)...") 8 | 9 | bubble_map <- ggplot() + 10 | # Layer 1: World map background 11 | geom_sf( 12 | data = world_map_background, 13 | fill = "grey80", 14 | color = "white", 15 | linewidth = 0.1 16 | ) + 17 | 18 | # Layer 2: City points - MAP population to SIZE! 19 | geom_sf( 20 | data = cities_sf, 21 | # Use aes() to map 'pop' column to 'size' 22 | aes(size = pop), 23 | color = "dodgerblue", 24 | # Set a fixed color 25 | shape = 16, 26 | # Solid circle 27 | alpha = 0.6 28 | ) + # Transparency helps with overlap 29 | 30 | # Step 3: Control the size scaling and legend 31 | # scale_size_area ensures area is proportional to value 32 | # (better perception) 33 | scale_size_area( 34 | name = "Population", 35 | # Legend title 36 | max_size = 5, 37 | # Max bubble size on plot 38 | labels = scales::label_number(scale = 1e-6, suffix = "M") 39 | ) + # Format labels (Millions) 40 | 41 | # Add title and theme 42 | labs( 43 | title = "World Cities Sized by Population (> 1M)", 44 | caption = "") + 45 | theme_minimal() + 46 | theme(axis.text = element_blank(), legend.position = "bottom") 47 | 48 | # Step 4: Display the map 49 | print("Displaying bubble map...") 50 | print(bubble_map) -------------------------------------------------------------------------------- /Ch 9/chapter_9_add_polygons.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_add_polygons.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Load packages & get boundary data 5 | pacman::p_load(leaflet, sf, giscoR) # giscoR helps get boundaries 6 | 7 | # Get boundary for France using giscoR 8 | # giscoR usually returns data in EPSG:4326, which is good for 9 | # leaflet. 10 | print("Getting boundary for France...") 11 | 12 | france_sf <- giscoR::gisco_get_countries( 13 | country = "FRA", resolution = "10") 14 | 15 | print("Boundary obtained.") 16 | assign( 17 | "france_sf", france_sf, envir = .GlobalEnv) # Store for later 18 | 19 | # Step 2: Create map and add the polygon layer 20 | print("Adding polygon layer...") 21 | # Initialize leaflet, passing the france_sf data 22 | map_with_polygon <- leaflet(data = france_sf) |> 23 | addProviderTiles( 24 | provider = providers$CartoDB.Positron) |> 25 | 26 | # Add polygons layer using addPolygons() 27 | addPolygons( 28 | # --- Styling Options --- 29 | fillColor = "darkslateblue", 30 | # Set the fill color 31 | color = "#FFFFFF", 32 | # Set the border color (white) 33 | weight = 1.5, 34 | # Set the border thickness (pixels) 35 | fillOpacity = 0.7, 36 | # Set fill transparency (0-1) 37 | 38 | # --- Popup Content --- 39 | # Use the NAME_ENGL column from the france_sf data 40 | # for the popup 41 | popup = ~ NAME_ENGL 42 | ) |> 43 | 44 | # Set view centered on France 45 | setView(lng = 2.3, lat = 46.8, zoom = 5) 46 | 47 | # Step 3: Display map 48 | print("Displaying map with polygon...") 49 | print(map_with_polygon) -------------------------------------------------------------------------------- /Ch 5/chapter_5_setup.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_5_setup.R --- 2 | 3 | # Step 1: Load necessary packages 4 | print("Loading packages...") 5 | # Ensure pacman is installed and loaded if you use it 6 | if (!requireNamespace("pacman", quietly = TRUE)) 7 | install.packages("pacman") 8 | pacman::p_load( 9 | sf, tidyverse, rnaturalearth, maps, ggrepel 10 | ) 11 | # maps package has simpler world.cities data 12 | # ggrepel for non-overlapping labels later 13 | print("Packages ready.") 14 | 15 | # Step 2: Get world boundaries for background map 16 | print("Getting world boundaries...") 17 | world_map_background <- rnaturalearth::ne_countries( 18 | scale = 'medium', returnclass = 'sf') |> 19 | dplyr::select(iso_a2, name, geometry) # Keep only a few columns 20 | print("World boundaries loaded.") 21 | 22 | # Step 3: Get world city data 23 | # (using maps::world.cities for simplicity) 24 | # This data is simpler and smaller than rnaturalearth cities for 25 | # this example 26 | print("Loading city data from 'maps' package...") 27 | cities_df <- maps::world.cities |> 28 | # Optional: Filter for larger cities if desired 29 | dplyr::filter(pop > 1000000) |> 30 | dplyr::select(name, country.etc, pop, lat, long) 31 | 32 | # CRUCIAL: Convert the data frame to an sf object! 33 | # Need to specify coordinate columns and the CRS 34 | # (WGS84 for this dataset) 35 | cities_sf <- sf::st_as_sf( 36 | cities_df, 37 | coords = c("long", "lat"), # IMPORTANT: Longitude first! 38 | crs = 4326) # Assume WGS84 Lat/Lon 39 | print("City data loaded and converted to sf object:") 40 | print(head(cities_sf)) 41 | print(paste("Number of cities loaded:", nrow(cities_sf))) 42 | 43 | # Store for later use 44 | assign( 45 | "world_map_background", world_map_background, 46 | envir = .GlobalEnv) 47 | assign("cities_sf", cities_sf, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 6/chapter_6_project_rivers.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_6_project_rivers.R --- 2 | 3 | # Step 1: Setup 4 | print("Loading packages for Kolkata Rivers Exercise...") 5 | pacman::p_load(sf, tidyverse, osmdata, ggspatial) 6 | 7 | # Step 2: Get Boundary 8 | bbox <- osmdata::getbb("Kolkata, India") 9 | print("Bounding box obtained:") 10 | print(bbox) 11 | 12 | # Step 3: Get Line Data (OSM Rivers) 13 | print("Getting OSM river data for Kolkata, India...") 14 | osm_rivers_query <- osmdata::opq( 15 | bbox = bbox, timeout = 120) |> # Increase timeout 16 | osmdata::add_osm_feature(key = "waterway", value = "river") |> 17 | osmdata::osmdata_sf() 18 | 19 | kolkata_rivers_sf <- osm_rivers_query$osm_lines 20 | 21 | # Step 4: Clean Data 22 | print("Cleaning river data...") 23 | kolkata_rivers_sf_clean <- kolkata_rivers_sf |> 24 | dplyr::select(osm_id, name, waterway, geometry) |> 25 | dplyr::filter(!sf::st_is_empty(geometry)) 26 | print("Cleaning complete.") 27 | 28 | # Step 5: Create Map 29 | print("Creating map of kolkata rivers...") 30 | kolkata_river_map <- ggplot() + 31 | # River lines 32 | geom_sf(data = kolkata_rivers_sf_clean, 33 | color = "steelblue", 34 | linewidth = 0.4) + 35 | # Labels 36 | labs( 37 | title = "Major Rivers in Kolkata, India", 38 | caption = "Data: OpenStreetMap contributors") + 39 | # Optional scale bar 40 | ggspatial::annotation_scale( 41 | location = "bl", 42 | width_hint = 0.4, 43 | style = "ticks") + 44 | # Theme 45 | theme_void() + 46 | theme(plot.title = element_text( 47 | hjust = 0.5, face = "bold") 48 | ) 49 | 50 | print(kolkata_river_map) 51 | print("Kolkata river map created.") 52 | 53 | # Step 6: Optional Save 54 | ggsave( 55 | "kolkata_rivers.png", plot = kolkata_river_map, width = 6, 56 | height = 8, dpi = 300, bg = "white") 57 | # print("Map potentially saved.") 58 | -------------------------------------------------------------------------------- /Ch 11/chapter_11_basic_animation.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_basic_animation.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure gganimate and base plot exist 5 | pacman::p_load( 6 | gganimate, gifski 7 | ) 8 | 9 | # Step 2: Add the gganimate transition layer! 10 | print("Adding animation layer...") 11 | animated_map <- base_life_exp_map + # Start with our static ggplot 12 | # object 13 | 14 | # Tell gganimate to create frames based on the 'year' column 15 | gganimate::transition_time(time = year) + 16 | 17 | # Add a title that shows the CURRENT year being displayed 18 | # '{frame_time}' is a special variable provided by transition_time 19 | # () 20 | # It holds the value of the 'time' variable for the current frame. 21 | labs( 22 | title = "Life Expectancy in Year: {as.integer(frame_time)}" 23 | ) 24 | 25 | # Step 3: "Render" the animation 26 | # Printing the gganimate object starts the rendering process. 27 | # In RStudio, it usually shows progress and displays the animation. 28 | # This can take some time depending on data size and number of 29 | # frames! 30 | print("Rendering animation (this may take a while)...") 31 | 32 | # Render the animation (adjust nframes, fps for speed/smoothness) 33 | # animate() function explicitly renders it with control over 34 | # parameters 35 | # Lower nframes/fps for faster previews during testing! 36 | # Using gifski_renderer for reliable preview in RStudio Viewer 37 | anim_render <- gganimate::animate( 38 | animated_map, nframes = 150, fps = 10, 39 | width=800, height=500, 40 | renderer = gifski_renderer()) 41 | 42 | print("Animation rendering complete (check Viewer pane).") 43 | # Display the rendered animation object 44 | print(anim_render) 45 | 46 | # Store animation object 47 | assign("animated_map", animated_map, envir = .GlobalEnv) 48 | assign("anim_render", anim_render, envir = .GlobalEnv) # Store 49 | # rendered gif -------------------------------------------------------------------------------- /Ch 10/chapter_10_basic_3d.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_basic_3d.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure the elevation_matrix exists 5 | if ( 6 | !exists("elevation_matrix")) { 7 | print( 8 | "ERROR: 'elevation_matrix' not found. 9 | Run data prep step first!") 10 | stop() 11 | } 12 | 13 | # Step 2: Create the basic 3D plot using sphere_shade 14 | print( 15 | "Creating basic 3D plot... 16 | This may open a new window (RGL). Be patient!" 17 | ) 18 | 19 | # Start with the elevation matrix (our heightmap) 20 | elevation_matrix |> 21 | # Create a basic spherical shading texture using the built-in 22 | # 'desert' palette 23 | # This calculates shading based only on the shape of the 24 | # heightmap matrix 25 | rayshader::sphere_shade(texture = "desert") |> 26 | 27 | # Pipe the shaded texture into plot_3d 28 | # This function renders the 3D scene in a new window (RGL device) 29 | rayshader::plot_3d( 30 | heightmap = elevation_matrix, # Matrix providing elevation 31 | # for shape 32 | zscale = 10, # Vertical exaggeration. TRY 5, 15, 30! 33 | solid = FALSE, # Make the base transparent 34 | fov = 0, # Field of view (0=orthographic, good for maps) 35 | theta = 0, # Rotation angle around Z axis (view FROM) 36 | phi = 80, # Vertical viewing angle (degrees from horizon) 37 | zoom = 0.6, # Zoom level (0-1, smaller is closer) 38 | windowsize = c(1000, 800), # Size of the pop-up window 39 | # (width, height) 40 | background = "lightgrey" # Set background color 41 | ) 42 | 43 | # Step 3: Interact with the window! 44 | print( 45 | "3D plot should appear in a new RGL window." 46 | ) 47 | print( 48 | "Try clicking and dragging in the RGL window to rotate the view!" 49 | ) 50 | print("Close the RGL window manually when done viewing.") 51 | # May need rgl::rgl.close() in Console 52 | 53 | # Pause briefly to allow window to maybe open fully 54 | Sys.sleep(2) -------------------------------------------------------------------------------- /Ch 6/chapter_6_flow_map_simple.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_6_flow_map_simple.R --- # (Can be in same script) 2 | 3 | # Step 1: Load ggplot2 4 | pacman::p_load(ggplot2) 5 | 6 | # Step 2: Define start and end points (just coordinates for this example) 7 | origin_x <- 4.86 # Approx. Longitude near Vondelpark West 8 | origin_y <- 52.358 # Approx. Latitude 9 | dest_x <- 4.88 # Approx. Longitude near Vondelpark East 10 | dest_y <- 52.36 11 | 12 | # Step 3: Create a data frame for the flow segment 13 | # geom_segment needs x, y (start) and xend, yend (end) coordinates 14 | flow_data <- data.frame( 15 | x_start = origin_x, 16 | y_start = origin_y, 17 | x_end = dest_x, 18 | y_end = dest_y, 19 | volume = 100 # A fictional flow volume 20 | ) 21 | print("Flow segment data created:") 22 | print(flow_data) 23 | 24 | # Step 4: Create the plot using geom_segment 25 | print("Creating simple flow map plot...") 26 | 27 | # Start ggplot (no base data needed here) 28 | flow_map_simple <- ggplot(data = flow_data) + 29 | # Add the segment layer 30 | geom_segment( 31 | aes(x = x_start, y = y_start, xend = x_end, yend = y_end, 32 | # Map volume to linewidth (thickness) 33 | linewidth = volume), 34 | color = "purple", 35 | alpha = 0.8, 36 | # Add an arrowhead 37 | arrow = arrow(length = unit(0.3, "cm"), type = "closed") 38 | ) + 39 | # Control how volume maps to thickness 40 | scale_linewidth(range = c(0.5, 3), name = "Flow Volume") + # Min/max thickness 41 | 42 | # Optional: Add points for origin/destination 43 | geom_point(aes(x = x_start, y = y_start), color = "darkgreen", size = 3) + 44 | geom_point(aes(x = x_end, y = y_end), color = "darkred", size = 3) + 45 | 46 | # Add labels and theme 47 | labs(title = "Simple Flow Map Example (Conceptual)") + 48 | # Use coord_map or coord_sf if plotting on a real map background 49 | coord_fixed(ratio = 1.6) + # Adjust aspect ratio for pseudo-map look 50 | theme_minimal() 51 | 52 | # Step 5: Display the plot 53 | print(flow_map_simple) -------------------------------------------------------------------------------- /Ch 1/chapter_1_exercise.R: -------------------------------------------------------------------------------- 1 | # Quick CRS Introduction Exercise 2 | 3 | # Step 1: Ensure sf is loaded 4 | pacman::p_load(sf, rnaturalearth) # Need rnaturalearth for the data 5 | 6 | # Step 2: Get world map data as an sf object 7 | print("Getting world map data...") 8 | world_sf <- rnaturalearth::ne_countries(scale = 'medium', returnclass = 'sf') 9 | print("World data loaded.") 10 | 11 | # Step 3: Check the CRS of this data! 12 | print("Checking the original CRS...") 13 | original_crs <- st_crs(world_sf) 14 | print(original_crs) 15 | # Look for the EPSG code near the bottom! Should be 4326 (WGS84 Lat/Lon) 16 | 17 | # Step 4: Define a different target CRS (e.g., Robinson Projection) 18 | # Robinson is often used for world maps. We use its "ESRI" code here. 19 | target_crs_robinson <- "ESRI:54030" 20 | print(paste("Target CRS:", target_crs_robinson)) 21 | 22 | # Step 5: Transform the data to the new CRS using st_transform() 23 | print("Transforming data to Robinson projection...") 24 | # The |> pipe sends world_sf to the st_transform function 25 | world_robinson_sf <- world_sf |> 26 | st_transform(crs = target_crs_robinson) 27 | print("Transformation complete!") 28 | 29 | # Step 6: Check the CRS of the NEW, transformed data 30 | print("Checking the NEW CRS...") 31 | new_crs <- st_crs(world_robinson_sf) 32 | print(new_crs) 33 | # Notice the name and details are different now! It's no longer EPSG:4326. 34 | 35 | # Step 7: Quick plot comparison (using base plot for simplicity here) 36 | print("Plotting comparison (Original vs. Transformed)...") 37 | par(mfrow = c(2, 1)) # Arrange plots side-by-side 38 | plot( 39 | sf::st_geometry(world_sf), 40 | main = "Original (EPSG:4326)", 41 | key.pos = NULL, reset = FALSE, 42 | border="grey30", cex.main = 5, 43 | lwd = 3 44 | ) 45 | plot( 46 | sf::st_geometry(world_robinson_sf), 47 | main = "Transformed (Robinson)", 48 | key.pos = NULL, reset = FALSE, border="blue", 49 | cex.main = 5, lwd = 3 50 | ) 51 | par(mfrow = c(1, 1)) # Reset plot layout 52 | print("Plots displayed. Notice the shape difference!") 53 | -------------------------------------------------------------------------------- /Ch 5/chapter_5_labels.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_5_labels.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure packages and data exist 5 | pacman::p_load(sf, tidyverse, ggrepel, rnaturalearth) 6 | # Need 'cities_with_continent' from the spatial join step 7 | 8 | namerica_cities_labeled <- cities_with_continent |> 9 | dplyr::filter(continent == "North America") 10 | 11 | # Also get namerica boundaries for context 12 | namerica_map_background <- rnaturalearth::ne_countries( 13 | scale = 'medium', 14 | continent = 'North America', 15 | returnclass = 'sf') 16 | 17 | print( 18 | paste( 19 | "Labeling", 20 | nrow(namerica_cities_labeled), "North American cities...") 21 | ) 22 | 23 | # Step 3: Create map with repel labels 24 | map_labels_namerica <- ggplot() + 25 | # Background map 26 | geom_sf(data = namerica_map_background, 27 | fill = "grey90", 28 | color = "white") + 29 | # City points (optional, can just have labels) 30 | geom_sf(data = namerica_cities_labeled, 31 | color = "navy", 32 | size = 1.5) + 33 | 34 | # *** ADD LABELS using geom_text_repel *** 35 | ggrepel::geom_text_repel( 36 | data = namerica_cities_labeled, 37 | # Map 'name.x' (city name from join) to the 38 | # label aesthetic 39 | aes(label = name.x, geometry = geometry), 40 | stat = "sf_coordinates", 41 | # Important for sf objects! 42 | # Tells repel how to get coords 43 | size = 2.5, 44 | # Font size 45 | min.segment.length = 0, 46 | # Draw line even if label 47 | # is close to point 48 | max.overlaps = 30, 49 | # Allow some overlap if needed, 50 | # increase if too sparse 51 | force = 0.5, 52 | # How strongly labels push away 53 | box.padding = 0.2 # Padding around text 54 | ) + 55 | 56 | # Add title and theme 57 | labs(title = "Major North American Cities (Labeled)") + 58 | theme_minimal() + 59 | theme(axis.title = element_blank()) 60 | 61 | # Step 4: Display map 62 | print("Displaying map with labels...") 63 | print(map_labels_namerica) -------------------------------------------------------------------------------- /Ch 7/chapter_7_inspect_raster.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_inspect_raster.R --- 2 | # (Can be in same script) 3 | 4 | # Ensure the raster object exists before inspecting 5 | print("Inspecting raster properties...") 6 | 7 | # Step 1: Check Resolution (Pixel Size X, Y) 8 | # terra::res() returns the size of one pixel in map units 9 | # (degrees here). 10 | print("--- Resolution (x, y degrees) ---") 11 | print(terra::res(global_elevation_10min)) 12 | # Expected output: Shows approx 0.1667 x 0.1667 degrees 13 | 14 | # Step 2: Check Extent (Bounding Box) 15 | # terra::ext() shows the geographic boundaries (edges) of the 16 | # raster. 17 | print("--- Extent (xmin, xmax, ymin, ymax) ---") 18 | print(terra::ext(global_elevation_10min)) 19 | # Expected output: Shows ext(-180, 180, -90, 90) for global data 20 | 21 | # Step 3: Check Coordinate Reference System (CRS) 22 | # terra::crs() gives details about the map projection and datum. 23 | print("--- CRS (WKT format) ---") 24 | print(terra::crs(global_elevation_10min)) 25 | # Expected output: Shows detailed WGS 84 definition (EPSG:4326) 26 | 27 | # Step 4: Check Number of Layers/Bands 28 | # terra::nlyr() tells us how many bands the raster has. 29 | print("--- Number of layers ---") 30 | print(terra::nlyr(global_elevation_10min)) 31 | # Expected output: 1 (since it's just elevation) 32 | 33 | # Step 5: Look at some Pixel Values (use head() for large 34 | # rasters!) 35 | # terra::values() extracts the actual data values from the 36 | # pixels. 37 | # CAUTION: This can be huge for large rasters! Use head() to 38 | # see just the start. 39 | print("--- First 6 pixel values ---") 40 | # Convert to vector first for head() to work cleanly 41 | print(head(terra::values(global_elevation_10min)[, 1])) 42 | # Expected output: Shows the actual elevation numbers for the 43 | # first 6 pixels 44 | 45 | # Step 6: Get Min/Max Values efficiently 46 | # terra::minmax() quickly finds the minimum and maximum pixel 47 | # values. 48 | print("--- Min/Max values ---") 49 | print(terra::minmax(global_elevation_10min)) 50 | # Expected output: Shows the lowest and highest elevation found 51 | # globally -------------------------------------------------------------------------------- /Ch 6/chapter_6_simplify_lines.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_6_simplify_lines.R --- # (Can be in same script) 2 | 3 | # Step 1: Ensure packages and data are ready 4 | pacman::p_load(sf, tidyverse, patchwork) # patchwork for combining plots 5 | # Step 2: Transform paths to a local projected CRS for Shenzhen 6 | # The CGCS2000 / 3-degree Gauss-Kruger zone 38 (EPSG:4526), uses meters. 7 | print("Transforming paths to local projected CRS (EPSG:4526)...") 8 | 9 | # Check current CRS first 10 | original_crs <- sf::st_crs(city_motorways_sf) 11 | print(paste("Original CRS:", original_crs$input)) 12 | 13 | # Transform if not already projected 14 | city_motorways_sf_proj <- city_motorways_sf |> 15 | sf::st_transform(crs = 4526) 16 | print("Transformation successful.") 17 | 18 | # Proceed only if transformation was successful 19 | 20 | # Step 3: Simplify the lines 21 | # Try simplifying details smaller than 5 meters. Adjust tolerance if needed. 22 | tolerance_meters <- 5 23 | print(paste( 24 | "Simplifying lines with dTolerance =", 25 | tolerance_meters, 26 | "meters..." 27 | )) 28 | 29 | # Apply st_simplify 30 | city_motorways_sf_simplified <- sf::st_simplify( 31 | city_motorways_sf_proj, dTolerance = tolerance_meters) 32 | 33 | print("Simplification complete.") 34 | 35 | # Step 4: Plot Original vs Simplified side-by-side using patchwork 36 | print("Plotting comparison...") 37 | 38 | # Plot 1: Original (Projected CRS) 39 | plot_original_motorways <- ggplot() + 40 | geom_sf(data = city_motorways_sf_proj, 41 | linewidth = 0.5, 42 | color = "black") + 43 | labs(title = "Original Paths (Projected)") + theme_void() 44 | 45 | # Plot 2: Simplified (Projected CRS) 46 | plot_simplified_motorways <- ggplot() + 47 | geom_sf(data = city_motorways_sf_simplified, 48 | linewidth = 0.5, 49 | color = "blue") + 50 | labs( 51 | title = paste( 52 | "Simplified (", tolerance_meters, "m Tol.)", 53 | sep = "")) + 54 | theme_void() 55 | 56 | # Combine plots using patchwork's '+' operator 57 | comparison_plot_motorways <- plot_original_motorways + plot_simplified_motorways 58 | print(comparison_plot_motorways) -------------------------------------------------------------------------------- /Ch 4/chapter_4_project_africa.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_4_project_africa.R --- 2 | 3 | # Step 1: Setup 4 | print("Loading packages for Africa Exercise...") 5 | pacman::p_load( 6 | sf, tidyverse, rnaturalearth, gapminder, countrycode 7 | ) 8 | 9 | # Step 2: Prepare Data 10 | print("Preparing African data...") 11 | # Get African polygons 12 | africa_sf <- rnaturalearth::ne_countries( 13 | scale = 'medium', 14 | continent = 'Africa', 15 | returnclass = 'sf') |> 16 | dplyr::select(iso_a3 = adm0_a3, name, geometry) 17 | 18 | # Filter gapminder for latest year and add ISO codes 19 | latest_year <- max(gapminder::gapminder$year) 20 | gapminder_latest <- gapminder::gapminder |> 21 | dplyr::filter(year == latest_year) |> 22 | dplyr::select(country, lifeExp) |> 23 | dplyr::mutate(iso_a3 = suppressWarnings( 24 | countrycode::countrycode( 25 | country, origin = 'country.name', 26 | destination = 'iso3c') 27 | )) 28 | 29 | # Join life expectancy to polygons 30 | africa_life_exp_sf <- africa_sf |> 31 | dplyr::left_join(gapminder_latest, by = "iso_a3") |> 32 | dplyr::filter(!is.na(lifeExp)) # Keep only countries with data 33 | 34 | print("Data prepared.") 35 | 36 | # Step 3: Create Plot 37 | print("Creating Africaan Life Expectancy map...") 38 | africa_map_plot <- ggplot(data = africa_life_exp_sf) + 39 | geom_sf(aes(fill = lifeExp), 40 | color = "white", 41 | linewidth = 0.1) + 42 | scale_fill_viridis_c( 43 | option = "viridis", 44 | direction = -1, 45 | name = "Life Exp.", 46 | na.value = "grey80") + 47 | labs( 48 | title = paste("African Life Expectancy", latest_year), 49 | caption = "Data: Gapminder & Natural Earth") + 50 | theme_void() + 51 | theme( 52 | legend.position.inside = c(0.15, 0.8), 53 | # Semi-transparent background 54 | plot.title = element_text(hjust = 0.5, face = "bold"), 55 | plot.caption = element_text(hjust = 0.95, vjust = 50) 56 | ) 57 | 58 | print(africa_map_plot) 59 | print("Africa map created.") 60 | 61 | ggsave( 62 | "africa_map_.png", 63 | plot = africa_map_plot, 64 | width = 7, height = 8, 65 | bg = "white", dpi = 600 66 | ) -------------------------------------------------------------------------------- /Ch 9/chapter_9_add_markers.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_add_markers.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Load packages if needed 5 | pacman::p_load(leaflet, sf, tidyverse) 6 | 7 | # Step 2: Create sample city data as an sf object 8 | # We create a standard R data frame first. 9 | city_data_df <- data.frame( 10 | name = c( 11 | "Amsterdam", "London", "Tokyo", "Nairobi", "Rio de Janeiro" 12 | ), 13 | latitude = c(52.37, 51.51, 35.68, -1.29, -22.91), # Latitudes 14 | longitude = c(4.89, -0.13, 139.69, 36.82, -43.17), # Longitudes 15 | info = c( 16 | "Canals & Culture", 17 | "Big Ben & Buses", 18 | "Bustling Metropolis", 19 | "Safari Gateway", 20 | "Christ the Redeemer") 21 | ) 22 | 23 | # Convert the data frame to an sf object using st_as_sf() 24 | # Specify coordinate columns (longitude first!) and CRS (must be 25 | # 4326) 26 | cities_sf <- sf::st_as_sf( 27 | city_data_df, 28 | coords = c("longitude", "latitude"), 29 | crs = 4326) 30 | print("Sample city sf object created:") 31 | print(cities_sf) 32 | # Store for later 33 | assign("cities_sf", cities_sf, envir = .GlobalEnv) 34 | 35 | # Step 3: Create map and add markers 36 | print("Adding markers to the map...") 37 | # Initialize leaflet map, passing the sf object as the main data 38 | # source 39 | # This makes the data available to layers added via the pipe |> 40 | map_with_markers <- leaflet(data = cities_sf) |> 41 | addProviderTiles( 42 | provider = providers$CartoDB.Positron 43 | ) |> # Light base map 44 | 45 | # Add markers using addMarkers() 46 | # It automatically uses the geometry from the input data 47 | # (cities_sf) 48 | addMarkers( 49 | # The 'popup' argument defines what shows when clicked. 50 | # Using the tilde '~' followed by a column name tells leaflet 51 | # to get the popup text FROM THAT COLUMN in the 'cities_sf' 52 | # data. 53 | popup = ~name # Show the city 'name' column value in the popup 54 | ) |> 55 | 56 | # Set the initial map view to show the markers 57 | setView(lng = 30, lat = 20, zoom = 2) # Adjust view 58 | 59 | # Step 4: Display map 60 | print("Displaying map with markers...") 61 | print(map_with_markers) -------------------------------------------------------------------------------- /Ch 11/chapter_11_save_animation.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_save_animation.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure the animation object exists and gifski is loaded 5 | pacman::p_load(gganimate, gifski) 6 | # Let's use 'animated_map_eased' from the previous step 7 | 8 | # Step 2: Save the animation using anim_save() 9 | # It needs the animation object created by ggplot + gganimate layers 10 | # OR the object returned by the animate() function. Let's use the 11 | # ggplot object. 12 | output_filename_gif <- "life_expectancy_animated.gif" 13 | 14 | print( 15 | paste( 16 | "Saving animation as GIF:", 17 | output_filename_gif, "(takes time!)...") 18 | ) 19 | 20 | # Provide the filename (ending determines format if renderer not 21 | # specified) 22 | # Provide the animation object (the ggplot object with gganimate 23 | # layers) 24 | # Adjust width, height, resolution (res), fps, duration/nframes as 25 | # needed 26 | # Using animate() first and saving its result is also fine. 27 | # Directly saving the ggplot object: 28 | gganimate::anim_save( 29 | filename = output_filename_gif, 30 | animation = animated_map_eased, # The ggplot object with 31 | # gganimate layers 32 | nframes = 200, # More frames for smoother final GIF 33 | fps = 15, # Frames per second 34 | width = 800, # Width in pixels 35 | height = 500, # Height in pixels 36 | res = 100, # Resolution (pixels per inch) 37 | renderer = gifski_renderer() # Use gifski for good quality GIFs 38 | ) 39 | print( 40 | paste( 41 | "Animation saved as", 42 | output_filename_gif, "in project folder!" 43 | )) 44 | 45 | # Example: Saving as MP4 (if av/ffmpeg work) 46 | # output_filename_mp4 <- "outputs/life_expectancy_animated.mp4" 47 | # print(paste("Saving animation as MP4:", output_filename_mp4, " 48 | # (requires ffmpeg)...")) 49 | # gganimate::anim_save( 50 | # filename = output_filename_mp4, 51 | # animation = animated_map_eased, 52 | # nframes = 300, # Can use more frames for video 53 | # fps = 30, 54 | # width = 1280, height = 720, # Standard HD resolution 55 | # renderer = av_renderer() # Use av package renderer 56 | # ) 57 | # print("MP4 potentially saved (check console for errors).") -------------------------------------------------------------------------------- /Ch 8/chapter_8_contours.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_contours.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure terra and swiss_elev_raster exist 5 | pacman::p_load(terra, sf, tidyverse) 6 | # Step 2: Generate contour lines using terra::as.contour() 7 | print("Generating contour lines (can take time)...") 8 | # Input is the elevation raster 9 | # 'nlevels' suggests how many contour levels terra should aim 10 | # for (adjust!) 11 | # 'maxcells' can limit processing on huge rasters if needed 12 | contour_lines_terra <- terra::as.contour( 13 | swiss_elev_raster, nlevels = 10) 14 | print("Contour lines generated by terra.") 15 | 16 | # Step 3: Convert terra's contour output (SpatVector) to an sf object 17 | print("Converting contours to sf object...") 18 | # The |> pipe sends the terra object to the conversion function 19 | contour_lines_sf <- contour_lines_terra |> 20 | sf::st_as_sf() 21 | print("Conversion complete.") 22 | 23 | # Inspect the sf object - it has a 'level' column with elevation 24 | print("--- First few rows of contour sf object ---") 25 | print(head(contour_lines_sf)) 26 | print("--- CRS of contours ---") 27 | print(sf::st_crs(contour_lines_sf)) # Should match original 28 | # raster CRS 29 | 30 | # Step 4: Map contours with ggplot2 31 | print("Creating contour map...") 32 | 33 | contour_map <- ggplot() + 34 | # Layer 1: Plot the contour lines using geom_sf 35 | # Map the 'level' column (elevation value) to the color aesthetic 36 | geom_sf( 37 | data = contour_lines_sf, 38 | aes(color = level), linewidth = 0.3) + 39 | 40 | # Use a sequential color scale that matches elevation idea 41 | # scale_color_viridis_c(option = "mako", ...) works well 42 | scale_color_viridis_c( 43 | option = "mako", name = "Elevation (m)") + 44 | 45 | # Add labels and theme 46 | labs( 47 | title = "Contour Map of Switzerland", 48 | caption = "Data: SRTM 30s via geodata") + 49 | theme_minimal() + 50 | theme( 51 | axis.text = element_blank(), 52 | panel.grid = element_blank()) 53 | 54 | # Step 5: Display map 55 | print(contour_map) 56 | print("Contour map generated.") 57 | 58 | # Store for later 59 | assign("contour_lines_sf", contour_lines_sf, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 4/chapter_4_map_elements.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_4_map_elements.R --- # (Can be in same script) 2 | 3 | # Step 1: Load ggspatial and ensure data exists 4 | pacman::p_load(ggspatial, tidyverse) 5 | 6 | # Start with the previous map structure 7 | map_plot_elements <- ggplot2::ggplot(data = world_data_loaded) + 8 | ggplot2::geom_sf( 9 | mapping = ggplot2::aes(fill = gdp_per_capita), 10 | color = "white", 11 | linewidth = 0.1 12 | ) + 13 | scale_fill_viridis_c( 14 | option = "rocket", 15 | # Choose a specific Viridis palette 16 | direction = -1, 17 | # reverse palette so that lighter colors are 18 | # associated with lower values, and darker with higher 19 | name = "US dollars", 20 | # legend title 21 | na.value = "grey80", 22 | # color for missing values 23 | trans = "log10", 24 | # Apply log10 transformation because density 25 | # is highly skewed (many low values, few very high values) 26 | # Use special labels for log scale 27 | # (requires scales package) 28 | labels = scales::label_log(digits = 2) # Use special labels for 29 | # log scale (requires scales package) 30 | ) + 31 | # *** ADD GGSPATIAL LAYERS *** 32 | # Add a scale bar 33 | # location="bl" (bottomleft), style="ticks" 34 | ggspatial::annotation_scale( 35 | location = "bl", 36 | width_hint = 0.3, 37 | style = "ticks") + 38 | # Add a north arrow 39 | # location="tr" (topright), 40 | # style=north_arrow_fancy_orienteering 41 | ggspatial::annotation_north_arrow( 42 | location = "tr", 43 | which_north = "true", 44 | pad_x = unit(0.1, "in"), 45 | pad_y = unit(1, "in"), 46 | style = ggspatial::north_arrow_fancy_orienteering 47 | ) + 48 | # Add labels and theme 49 | ggplot2::labs(title = "GDP per capita") + 50 | # Use theme_void for cleaner look with scale bar/arrow 51 | theme_void() + 52 | theme( 53 | legend.position = "bottom", 54 | legend.key.width = unit(1.5, "cm"), 55 | plot.title = element_text(hjust = 0.5, face = "bold") 56 | ) # Center title 57 | 58 | # Display the map 59 | print("Displaying map with elements...") 60 | print(map_plot_elements) 61 | 62 | # Save the previous map 63 | assign("plot_original", map_plot_viridis, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 9/chapter_9_layers_control.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_layers_control.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Load packages and ensure data exists 5 | pacman::p_load(leaflet, sf, tidyverse, giscoR, glue) # Need all previous pieces 6 | 7 | # Step 2: Create map with multiple layers assigned to groups 8 | 9 | print("Creating map with layers control...") 10 | map_with_layers <- leaflet() |> 11 | # --- Base Map Layers --- 12 | # Add provider tiles one by one. 13 | # CRUCIAL: Assign each to a unique 'group' name. 14 | addProviderTiles( 15 | provider = providers$CartoDB.Positron, 16 | group = "Simple Map") |> 17 | addProviderTiles( 18 | provider = providers$OpenStreetMap.Mapnik, 19 | group = "OSM Map") |> 20 | addProviderTiles( 21 | provider = providers$Esri.WorldImagery, 22 | group = "Satellite") |> 23 | 24 | # --- Overlay Data Layers --- 25 | # Add France polygon layer. Add data= here. 26 | # CRUCIAL: Assign it to an 'overlay group' name. 27 | addPolygons( 28 | data = france_sf, 29 | fillColor = "darkslateblue", 30 | color = "white", 31 | weight = 1.5, 32 | fillOpacity = 0.7, 33 | popup = ~ NAME_ENGL, 34 | group = "Countries" 35 | ) |> # Group name for this layer 36 | 37 | # Add City markers layer. Add data= here. 38 | # CRUCIAL: Assign it to an 'overlay group' name. 39 | addMarkers( 40 | data = cities_sf, 41 | popup = ~ glue::glue("{name}
{info}"), 42 | group = "Cities" 43 | ) |> # Group name for this layer 44 | 45 | # --- Add Layers Control Widget --- 46 | # This function reads the 'group' names assigned above. 47 | addLayersControl( 48 | # List the names of the groups for the base maps (radio 49 | # buttons) 50 | baseGroups = c("Simple Map", "OSM Map", "Satellite"), 51 | # List the names of the groups for the overlay data 52 | # (checkboxes) 53 | overlayGroups = c("Cities", "Countries"), 54 | # Options for the control widget itself 55 | options = layersControlOptions(collapsed = FALSE) # Keep 56 | # expanded 57 | ) |> 58 | 59 | # Set initial view 60 | setView(lng = 2.3, lat = 46.8, zoom = 4) # Center near France 61 | 62 | # Step 3: Display map 63 | print("Displaying map with controls...") 64 | print(map_with_layers) 65 | 66 | # Store map object 67 | assign("map_with_layers", map_with_layers, envir = .GlobalEnv) -------------------------------------------------------------------------------- /Ch 7/chapter_7_project_country_elev.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_project_country_elev.R --- 2 | 3 | # Step 1: Choose Country & Setup 4 | print("Loading packages for Country Elevation Exercise...") 5 | pacman::p_load(sf, tidyverse, terra, geodata) 6 | theme_set(theme_minimal()) 7 | 8 | # --- USER INPUT: SET YOUR COUNTRY ISO CODE HERE --- 9 | # Example: Use "NPL" for Nepal, "NZL" for New Zealand, 10 | # "ITA" for Italy 11 | your_iso_code <- "NPL" 12 | your_country_name <- "Nepal" # For title 13 | # ------------------------------------------------- 14 | 15 | # Step 2: Download/Load Elevation 16 | print(paste( 17 | "Fetching 30s elevation data for", 18 | your_country_name, 19 | "(may take time)..." 20 | )) 21 | 22 | my_country_elev <- geodata::elevation_30s( 23 | country = your_iso_code, path = tempdir()) 24 | print("Elevation data loaded.") 25 | # Rename the layer for easier use later 26 | names(my_country_elev) <- "elevation" 27 | 28 | # Step 3: Inspect 29 | print("--- Elevation Raster Summary ---") 30 | print(my_country_elev) 31 | print("--- Elevation CRS ---") 32 | print(terra::crs(my_country_elev)) 33 | 34 | # Step 4: Map with ggplot2 35 | print("Converting raster to data frame...") 36 | # Use na.rm=TRUE in as.data.frame for efficiency 37 | elev_df <- terra::as.data.frame( 38 | my_country_elev, xy = TRUE, na.rm = TRUE 39 | ) 40 | 41 | print("Creating ggplot map...") 42 | my_country_map <- ggplot(data = elev_df) + 43 | geom_raster(aes(x = x, y = y, fill = elevation)) + 44 | # Try different viridis options: "mako", "rocket", 45 | # "turbo", "viridis" 46 | scale_fill_viridis_c( 47 | option = "mako", 48 | direction = -1, 49 | name = "Elevation (m)" 50 | ) + 51 | labs(title = paste("Elevation Map of", your_country_name), 52 | caption = "Data: SRTM 30s via geodata") + 53 | coord_sf( 54 | crs = terra::crs(my_country_elev), 55 | expand = FALSE 56 | ) + 57 | theme_minimal() + 58 | theme( 59 | axis.title = element_blank(), 60 | plot.title = element_text(hjust = 0.5, face = "bold") 61 | ) 62 | 63 | print(my_country_map) 64 | print("Map created.") 65 | 66 | # Step 5: Optional Save 67 | # ggsave( 68 | # "my_country_dem.png", plot = my_country_map, width = 8, 69 | # height = 6, dpi = 300, bg = "white") 70 | # print("Map potentially saved.") 71 | 72 | # Cleanup (optional) 73 | # rm(elev_df) # Remove potentially large data frame 74 | # gc() # Suggest garbage collection -------------------------------------------------------------------------------- /Ch 10/chapter_10_save_output.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_save_output.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Make sure a 3D plot window (RGL) from plot_3d or plot_gg 5 | # is open. 6 | # If not, re-run one of those commands first to create the scene. 7 | # Arrange the view interactively in the RGL window if desired! 8 | # Example: Ensure the plot_gg window is open 9 | rayshader::plot_gg( 10 | gg_swiss_elev, 11 | heightmap = elevation_matrix, 12 | scale = 30, 13 | multicore = TRUE, 14 | windowsize = c(800, 700), 15 | solid = FALSE, 16 | shadow = TRUE, 17 | sunangle = 270, 18 | zoom = 0.5, 19 | theta = -30, 20 | phi = 35 21 | ) 22 | 23 | # Step 2: Save a quick snapshot of the current view 24 | print("Taking quick snapshot of current RGL view...") 25 | # Create output directory if it doesn't exist 26 | snapshot_file <- "swiss_3d_snapshot.png" 27 | rayshader::render_snapshot( 28 | filename = snapshot_file, 29 | # Output filename 30 | title_text = "Swiss Alps (Snapshot)", 31 | # Optional title on image 32 | title_bar_color = "black", 33 | title_color = "white", 34 | title_font = "sans", 35 | vignette = 0.2 # Adds subtle darkening at edges 36 | ) 37 | print(paste("Snapshot saved as", snapshot_file)) 38 | 39 | # Step 3: Render a high-quality image (takes longer!) 40 | print("Rendering high-quality image... 41 | This might take several minutes!") 42 | hq_file <- "swiss_3d_highquality.png" 43 | rayshader::render_highquality( 44 | filename = hq_file, 45 | # Name of the output file 46 | light = TRUE, 47 | # Turn on simulated lighting (YES!) 48 | lightdirection = 315, 49 | # Direction sun comes FROM (e.g., 315=NW) 50 | lightintensity = 800, 51 | # Brightness of the main light source 52 | # (adjust) 53 | lightaltitude = 45, 54 | # Angle of the sun above the horizon (degrees) 55 | interactive = FALSE, 56 | # Prevent extra preview windows during render 57 | width = 1200, 58 | height = 1000, 59 | # Dimensions of the saved image in pixels 60 | # Optional: Add title directly to the render 61 | title_text = "Swiss Alps (High Quality Render)", 62 | title_offset = c(20, 20), 63 | # Position from top-left 64 | title_color = "white", 65 | title_bar_color = "black" 66 | ) 67 | print( 68 | paste( 69 | "High-quality image saved as", 70 | hq_file, "in your project folder." 71 | ) 72 | ) 73 | 74 | # Close the RGL window if you are done 75 | try(rgl::rgl.close(), silent = TRUE 76 | ) -------------------------------------------------------------------------------- /Ch 11/chapter_11_data_prep.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_data_prep.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Get World Polygons 5 | print("Getting world polygons...") 6 | world_polygons_anim <- rnaturalearth::ne_countries( 7 | scale = 'medium', 8 | returnclass = 'sf') |> 9 | # Keep only a few columns, ensure we have a consistent country 10 | # code (ISO A3) 11 | # adm0_a3 is a good ISO 3-letter code from rnaturalearth 12 | dplyr::select(iso_a3 = adm0_a3, name, geometry) |> 13 | # Filter out Antarctica for a cleaner map 14 | dplyr::filter(name != "Antarctica") 15 | print("World polygons loaded.") 16 | 17 | # Step 2: Get gapminder data and add matching ISO codes 18 | print("Preparing gapminder data...") 19 | gapminder_data_anim <- gapminder::gapminder |> 20 | # Create an iso_a3 column using countrycode to match the polygons 21 | dplyr::mutate( 22 | iso_a3 = suppressWarnings( 23 | countrycode::countrycode( 24 | country, 25 | origin = 'country.name', 26 | destination = 'iso3c'))) |> 27 | # Keep only needed columns (the key, the time variable, and data 28 | # to map) 29 | dplyr::select(iso_a3, country, year, lifeExp, pop, gdpPercap) |> 30 | # Remove rows where country code couldn't be matched 31 | dplyr::filter(!is.na(iso_a3)) 32 | print("Gapminder data prepared.") 33 | 34 | # Step 3: Join spatial polygons with time series data 35 | print("Joining spatial and time series data...") 36 | # Use a left_join to keep all polygons and add matching gapminder 37 | # data 38 | # A country might not have data for every single year in gapminder 39 | world_gapminder_sf <- world_polygons_anim |> 40 | dplyr::left_join(gapminder_data_anim, by = "iso_a3") 41 | print("Data prepared and joined.") 42 | print("Checking joined data structure:") 43 | dplyr::glimpse(world_gapminder_sf) 44 | # Note: Each country polygon is now repeated for each year it has 45 | # data! 46 | # Also check for NAs in the joined columns 47 | # (lifeExp, pop, gdpPercap) 48 | print( 49 | paste( 50 | "Total rows in joined data:", 51 | nrow(world_gapminder_sf) 52 | ) 53 | ) 54 | print( 55 | paste( 56 | "Number of unique countries:", 57 | dplyr::n_distinct(world_gapminder_sf$iso_a3) 58 | ) 59 | ) 60 | print( 61 | paste( 62 | "Number of unique years:", 63 | dplyr::n_distinct(world_gapminder_sf$year) 64 | ) 65 | ) 66 | 67 | # Store for later use 68 | assign( 69 | "world_gapminder_sf", world_gapminder_sf, 70 | envir = .GlobalEnv 71 | ) -------------------------------------------------------------------------------- /Ch 7/chapter_7_crop_raster.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_crop_raster.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Load necessary packages 5 | pacman::p_load(terra, sf, rnaturalearth) # rnaturalearth helps get continent boundaries 6 | 7 | # Step 2: Ensure global elevation raster exists 8 | # Step 3: Get South America boundary polygon using rnaturalearth 9 | print("Fetching South America boundary...") 10 | sa_boundary_sf <- rnaturalearth::ne_countries( 11 | scale = 'medium', 12 | continent = "South America", 13 | returnclass = 'sf') |> 14 | dplyr::select(iso_a3 = adm0_a3, name, geometry) 15 | 16 | print("Boundary fetched.") 17 | 18 | # Step 4: CRITICAL - Check CRS match between raster and 19 | # vector polygon 20 | # Mismatched CRS is a very common error source when 21 | # cropping! 22 | print("--- Checking CRS ---") 23 | # Use terra::crs() for raster, st_crs() for sf object 24 | # Get comparable proj strings or EPSG codes if possible 25 | raster_crs_str <- terra::crs(global_elevation_10min, proj = TRUE) 26 | vector_crs_sf <- sf::st_crs(sa_boundary_sf) # Get sf crs 27 | # object 28 | 29 | print(paste( 30 | "Raster CRS:", 31 | terra::crs(global_elevation_10min, describe = TRUE)$name 32 | )) 33 | print(paste("Vector CRS:", vector_crs_sf$Name)) 34 | 35 | # Step 5: Transform if necessary 36 | # (they should both be WGS84 here) 37 | # Compare EPSG codes if available and reliable 38 | 39 | # Transform the vector boundary to match the raster's CRS 40 | sa_boundary_sf <- sa_boundary_sf |> 41 | sf::st_transform(crs = terra::crs(global_elevation_10min)) 42 | print("Vector CRS transformed.") 43 | 44 | # Step 6: Crop the raster using the sf polygon boundary 45 | print("Cropping global elevation raster to South America") 46 | # terra::crop(raster_to_crop, object_to_crop_with) 47 | # mask=TRUE makes pixels outside polygon NA (good for 48 | # non-rectangular shapes) 49 | sa_elevation <- terra::crop(global_elevation_10min, sa_boundary_sf, mask = 50 | TRUE) 51 | print("Cropping complete.") 52 | 53 | # Step 7: Inspect the cropped raster (check the new, 54 | # smaller extent!) 55 | print("--- Cropped Raster Summary ---") 56 | print(sa_elevation) 57 | print("--- End Summary ---") 58 | 59 | # Step 8: Plot the cropped raster using terra::plot for 60 | # quick view 61 | print("Plotting cropped raster...") 62 | terra::plot(sa_elevation) 63 | print("Cropped plot displayed.") 64 | 65 | # Store for potential use 66 | assign("sa_elevation", sa_elevation, envir = .GlobalEnv) 67 | -------------------------------------------------------------------------------- /Ch 8/chapter_8_shaded_relief.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_shaded_relief.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure ggplot2, terra, dplyr, tidyterra and 5 | # ggnewscale are loaded 6 | pacman::p_load(terra, tidyverse, tidyterra, ggnewscale) 7 | print("Converting rasters to data frames for ggplot2...") 8 | 9 | # Rename layers for clarity BEFORE converting 10 | names(swiss_elev_raster) <- "elevation" 11 | names(hillshade) <- "hillshade_val" 12 | 13 | # Use as.data.frame with xy=TRUE to get coordinates 14 | # Use na.rm=TRUE to potentially reduce data frame size 15 | elev_df_gg <- terra::as.data.frame( 16 | swiss_elev_raster, xy = TRUE, na.rm = TRUE) 17 | 18 | hillshade_df_gg <- terra::as.data.frame( 19 | hillshade, xy = TRUE, na.rm = TRUE) 20 | 21 | # Step 2: Create the ggplot map with hillshade overlay 22 | print("Creating shaded relief map...") 23 | 24 | limits <- terra::minmax(swiss_elev_raster) 25 | 26 | shaded_relief_map <- ggplot() + 27 | geom_raster( 28 | data = hillshade_df_gg, 29 | aes(x = x, y = y, fill = hillshade_val), 30 | show.legend = FALSE 31 | ) + 32 | # Choose a light gray palette for the hillshade layer. 33 | # scale_fill_gradientn() lets us define exactly how 34 | # numeric values map to colors. 35 | scale_fill_gradientn( 36 | colors = hcl.colors( 37 | 12, "Light Grays", rev = TRUE), # A light gray palette 38 | na.value = NA # Transparent for any missing cells 39 | ) + 40 | ggnewscale::new_scale_fill() + 41 | # Second layer: draw the elevation raster on top of the hillshade. 42 | geom_raster( 43 | data = elev_df_gg, 44 | aes(x = x, y = y, fill = elevation), 45 | alpha = 0.5 # decrease alpha to 46 | # make hillshade more visible 47 | ) + 48 | # Choose a continuous hypso palette for elevation. 49 | tidyterra::scale_fill_hypso_tint_c( 50 | palette = "dem_print", 51 | limits = limits) + 52 | # Add labels and theme 53 | labs( 54 | title = "Shaded Relief Map of Switzerland", 55 | fill = "Elevation (m)", # Legend title 56 | caption = paste( 57 | "Data: SRTM 30s via geodata.", 58 | "Hillshade: Sun from West (270 deg)" 59 | ) 60 | ) + 61 | # Use coord_sf to ensure correct aspect ratio for maps 62 | coord_sf(crs = terra::crs(swiss_elev_raster)) + # Use 63 | # raster's CRS 64 | theme_minimal() + 65 | theme( 66 | legend.position = "right", 67 | axis.title = element_blank() 68 | ) 69 | 70 | # Step 3: Display the map 71 | print(shaded_relief_map) 72 | print("Shaded relief map generated.") -------------------------------------------------------------------------------- /Ch 9/chapter_9_project_landmarks.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_9_project_landmarks.R --- 2 | 3 | # Step 1: Setup 4 | print("Loading packages for Landmark Exercise...") 5 | pacman::p_load(leaflet, sf, tidyverse, glue, htmlwidgets) 6 | 7 | # Step 2: Create Landmark Data 8 | print("Creating landmark data...") 9 | landmarks_df <- data.frame( 10 | landmark_name = c( 11 | "Eiffel Tower", 12 | "Statue of Liberty", 13 | "Taj Mahal", 14 | "Great Wall (Badaling)", 15 | "Machu Picchu", 16 | "Sydney Opera House" 17 | ), 18 | latitude = c( 19 | 48.8584, 40.6892, 27.1751, 20 | 40.3540, -13.1631, -33.8568 21 | ), 22 | longitude = c( 23 | 2.2945, -74.0445, 78.0421, 24 | 116.0005, -72.5450, 151.2153 25 | ), 26 | description = c( 27 | "Iconic tower in Paris, France.", 28 | "Symbol of freedom in New York Harbor, USA.", 29 | "Ivory-white marble mausoleum in Agra, India.", 30 | "Famous section of the Great Wall near Beijing, China.", 31 | "Incan citadel set high in the Andes Mountains, Peru.", 32 | "Multi-venue performing arts centre in Sydney, Australia." 33 | ) 34 | ) 35 | 36 | # Step 3: Convert to sf 37 | print("Converting data to sf object...") 38 | landmarks_sf <- sf::st_as_sf( 39 | landmarks_df, 40 | coords = c("longitude", "latitude"), 41 | crs = 4326) 42 | 43 | # Step 4: Build Leaflet Map 44 | print("Building interactive landmark map...") 45 | landmark_map <- leaflet() |> 46 | # Base Maps 47 | addProviderTiles( 48 | provider = providers$OpenStreetMap.Mapnik, 49 | group = "Street Map") |> 50 | addProviderTiles( 51 | provider = providers$Esri.WorldImagery, 52 | group = "Satellite") |> 53 | # Landmark Markers 54 | addMarkers( 55 | data = landmarks_sf, 56 | popup = ~ glue::glue( 57 | "{landmark_name}
{description}"), 58 | group = "Landmarks" 59 | ) |> 60 | # Step 5: Add Controls 61 | addLayersControl( 62 | baseGroups = c("Street Map", "Satellite"), 63 | overlayGroups = c("Landmarks"), 64 | options = layersControlOptions(collapsed = FALSE) 65 | ) |> 66 | # Set View 67 | setView(lng = 10, lat = 30, zoom = 2) # Global view 68 | 69 | # Step 6: Store & Display 70 | print("Displaying landmark map...") 71 | print(landmark_map) 72 | 73 | # Step 7: Save Map 74 | output_landmark_map <- "landmark_map.html" 75 | print(paste("Saving map as:", output_landmark_map)) 76 | htmlwidgets::saveWidget( 77 | landmark_map, file = output_landmark_map, 78 | selfcontained = TRUE 79 | ) 80 | print("Landmark map saved.") -------------------------------------------------------------------------------- /Ch 10/chapter_10_ggplot_3d.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_ggplot_3d.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure packages and data are ready 5 | pacman::p_load(rayshader, terra, tidyverse) 6 | 7 | # Step 2: Create the 2D ggplot map (using elevation color) 8 | # This ggplot object will become the texture for our 3D map. 9 | 10 | # Rename layer if needed for aes mapping 11 | names(swiss_elev_raster) <- "elevation" 12 | 13 | # Transform raster into data.frame for plotting 14 | swiss_elev_raster_df <- as.data.frame( 15 | swiss_elev_raster, xy = TRUE, na.rm = TRUE 16 | ) 17 | 18 | # rayshader doesn't work well with theme_void() 19 | # so we'll define our theme here 20 | 21 | theme_for_the_win <- function(){ 22 | theme_minimal() + 23 | theme( 24 | axis.line = element_blank(), 25 | axis.title.x = element_blank(), 26 | axis.title.y = element_blank(), 27 | axis.text.x = element_blank(), 28 | axis.text.y = element_blank(), 29 | panel.grid.major = element_blank(), 30 | panel.grid.minor = element_blank(), 31 | plot.background = element_rect( 32 | fill = "white", color = NA 33 | ), 34 | legend.position = "none" 35 | ) 36 | } 37 | 38 | # Use geom_raster for direct plotting 39 | gg_swiss_elev <- ggplot() + 40 | geom_raster( 41 | data = swiss_elev_raster_df, 42 | aes(x = x, y = y, fill = elevation)) + 43 | scale_fill_viridis_c( 44 | option = "mako", name = "") + # Nice Viridis 45 | theme_for_the_win() 46 | 47 | # Step 3: Convert the ggplot to 3D using plot_gg() 48 | print("Converting ggplot to 3D with rayshader... 49 | (Opens RGL window)") 50 | # plot_gg takes the ggplot object and drapes it onto the 51 | # heightmap matrix 52 | rayshader::plot_gg( 53 | ggobj = gg_swiss_elev, 54 | # The ggplot object created above 55 | multicore = TRUE, 56 | # Use multiple CPU cores if available 57 | width = 7, 58 | height = 7, 59 | # Width/height ratio for texture 60 | # render 61 | scale = 30, 62 | # Vertical exaggeration (adjust!) 63 | solid = FALSE, 64 | # Make the base transparent 65 | shadow = TRUE, 66 | # Render shadows based on sun angle 67 | shadow_darkness = 0.6, 68 | # How dark the shadows are 69 | sunangle = 270, 70 | # Direction sun comes FROM (270=West) 71 | windowsize = c(1000, 800), 72 | # RGL window size 73 | # Camera position arguments 74 | zoom = 0.5, 75 | theta = -30, 76 | # Different angle 77 | phi = 35 # Different tilt 78 | ) 79 | 80 | print("3D plot from ggplot should appear in RGL window.") 81 | # Remember to close the RGL window manually. 82 | Sys.sleep(2) -------------------------------------------------------------------------------- /Ch 5/chapter_5_style_by_category.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_5_style_by_category.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure packages and data exist 5 | pacman::p_load(sf, tidyverse, rnaturalearth) 6 | # We need country polygons with continent info 7 | # Reload background map to ensure 'continent' 8 | # column is present 9 | world_polygons_cont <- rnaturalearth::ne_countries(scale = 'medium', returnclass = 'sf') |> 10 | dplyr::select(name, continent, geometry) |> # Keep only needed cols 11 | sf::st_make_valid() # ← fix any self-crossing or invalid rings 12 | 13 | # (Optional) If you still see S2 errors, disable S2: 14 | # sf::sf_use_s2(FALSE) 15 | 16 | # Use st_join with st_within: find which polygon each city 17 | # point is WITHIN. Make sure both layers use the same CRS 18 | # (they should both be 4326 here) 19 | 20 | cities_sf <- sf::st_transform(cities_sf, sf::st_crs(world_polygons_cont)) 21 | 22 | # Perform the join, keeping city attributes by default 23 | cities_with_continent <- sf::st_join(cities_sf, world_polygons_cont, join = sf::st_within) 24 | print("Spatial join complete.") 25 | # Check the result - should have a 'continent' column now 26 | print(head(cities_with_continent[, c("name.x", "continent")])) 27 | # Store the joined data 28 | assign("cities_with_continent", cities_with_continent, envir = .GlobalEnv) 29 | 30 | 31 | # Step 3: Create map, coloring points by continent 32 | print("Creating map colored by continent...") 33 | 34 | map_color_by_cont <- ggplot() + 35 | # Background map 36 | geom_sf( 37 | data = world_map_background, 38 | fill = "grey90", 39 | color = "white", 40 | linewidth = 0.1 41 | ) + 42 | 43 | # City points - MAP continent to COLOR! 44 | geom_sf( 45 | data = cities_with_continent |> 46 | dplyr::filter(!is.na(continent)), 47 | # Filter out points not 48 | # joined 49 | # Use aes() to map 'continent' column to 'color' 50 | aes(color = continent), 51 | size = 1.5, 52 | # Fixed size 53 | shape = 16, 54 | # Fixed shape 55 | alpha = 0.7 56 | ) + # Fixed transparency 57 | 58 | # Step 4: Customize the color scale for categorical data 59 | # Use scale_color_brewer for qualitative palettes 60 | # (good for categories) 61 | # Or scale_color_viridis_d for discrete viridis colors 62 | scale_color_brewer(palette = "Set1", name = "Continent") + 63 | 64 | # Add title and theme 65 | labs(title = "World Cities Colored by Continent") + 66 | theme_minimal() + 67 | theme(axis.text = element_blank(), legend.position = "bottom") + 68 | guides(color = guide_legend(override.aes = list(size = 3))) # Make legend points larger 69 | 70 | # Step 5: Display the map 71 | print("Displaying map...") 72 | print(map_color_by_cont) -------------------------------------------------------------------------------- /Ch 8/chapter_8_terrain_attributes.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_terrain_attributes.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Calculate Slope and Aspect using terra::terrain() 5 | print("Calculating slope and aspect...") 6 | # Input is the elevation raster 7 | # v = c("slope", "aspect") specifies which attributes to 8 | # calculate 9 | # unit="radians" gives slope in radians 10 | terrain_attributes <- terra::terrain( 11 | swiss_elev_raster, 12 | v = c("slope", "aspect"), 13 | unit = "radians") 14 | print("Slope and aspect calculated.") 15 | 16 | # Inspect the result - it's a SpatRaster with MULTIPLE layers! 17 | print("--- Terrain Attributes Summary ---") 18 | print(terrain_attributes) 19 | # Note it now has two layers named 'slope' and 'aspect' 20 | 21 | # Step 2: Plot Slope and Aspect individually using terra::plot() 22 | print("Plotting slope...") 23 | # Select the 'slope' layer using $slope or [[1]] 24 | # col = viridis(100) uses 100 colors from the viridis palette 25 | terra::plot( 26 | terrain_attributes$slope, 27 | main = "Slope (Radians)", 28 | col = viridis(100)) 29 | 30 | print("Plotting aspect...") 31 | # Select the 'aspect' layer using $aspect or [[2]] 32 | # Aspect is circular so rainbow() is sometimes 33 | # used but hcl.colors("Circular") is often better. 34 | terra::plot( 35 | terrain_attributes$aspect, 36 | main = "Aspect (Radians from North)", 37 | col = viridis(100)) 38 | 39 | # Step 3: Calculate Hillshade using terra::shade() 40 | print("Calculating hillshade...") 41 | # Requires the slope and aspect layers (which we just 42 | # calculated) 43 | # angle = 45 sets the sun's altitude (45 Radians above horizon) 44 | # direction = 270 sets the sun's direction (270=West, 0=N, 45 | # 90=E, 180=S) 46 | # We pass the slope and aspect layers directly from our 47 | # multi-layer object 48 | hillshade <- terra::shade( 49 | terrain_attributes[[1]], 50 | terrain_attributes[[2]], 51 | angle = 45, 52 | direction = 270) 53 | print("Hillshade calculated.") 54 | 55 | # Inspect the hillshade raster (single layer, values 0-255) 56 | print("--- Hillshade Summary ---") 57 | print(hillshade) 58 | 59 | # Step 4: Plot the Hillshade (greyscale is standard) 60 | print("Plotting hillshade...") 61 | # Use a grey color palette: grey(0:100/100) creates 101 shades 62 | # of grey 63 | # legend = FALSE turns off the unnecessary color legend for 64 | # hillshade 65 | terra::plot( 66 | hillshade, 67 | col = grey(0:100 / 100), 68 | legend = FALSE, 69 | main = "Hillshade (Sun from West)" 70 | ) 71 | print("Hillshade plot done.") 72 | 73 | # Store for later use 74 | assign( 75 | "hillshade", hillshade, envir = .GlobalEnv 76 | ) 77 | assign( 78 | "terrain_attributes", 79 | terrain_attributes, envir = .GlobalEnv 80 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mapping Worlds with R: Your Journey from Beginner to Data Cartographer-examples 2 | You will find all the scripts organized by chapter of my book "Mapping Worlds with R: Your Journey from Beginner to Data Cartographer" in this GitHub repo. 3 | 4 | ![alt text](https://github.com/milos-agathon/book-mapping-worlds-with-r-examples/blob/main/cover.png?raw=true) 5 | 6 | Whether you’re a total newcomer to coding or an experienced analyst who’s never made a map, Mapping the World with R turns “I-wish-I-could-do-that” into “I-just-did-that.” In crisp and straightforward language, it shows you exactly how to install R, wrangle real-world spatial data, and publish beautiful, ethical maps—step by step, project by project. 7 | 8 | 9 | # What You’ll Create & Learn 10 | 11 | 11 hands-on projects that start with “Hello, mapper!” and finish with slick, publish-ready visualizations. 12 | Dozens of bite-size exercises so you can practice each new skill the moment you meet it (skip the theory tunnel, build muscle memory instead). 13 | Authentic data, served automatically via R packages: no dead links or dummy CSVs. You’ll learn to cope with messy, real-life datasets the way professionals do. 14 | Clear, fully annotated code in every chapter, plus a companion GitHub repo you can clone, tweak, and remix. 15 | 16 | 17 | # Why This Book Sells Itself 18 | 19 | - Problem-solving DNA – every chapter starts with a mapping dilemma and walks you through the fix, so you always know why you’re learning each technique. 20 | - Beginner-proof pacing – short digestible chunks, zero jargon, and friendly nudges that keep you moving forward. 21 | - Instant-download convenience – sold as a DRM-free PDF so you can read it on any device, annotate freely, and search like a pro. 22 | Lifetime updates – buy once, get every future revision at no extra cost via Payhip’s “update existing product” feature 23 | 24 | 25 | # Who It’s For 26 | - Absolute Beginners 27 | - Data-Curious Student 28 | - Researchers/Analysts 29 | - Designers/Storytellers 30 | 31 | 32 | # What You Get 33 | - A no-fear setup guide and your very first working map within an hour. 34 | - A plug-and-play portfolio of maps for class projects or job interviews. 35 | - A repeatable workflow for cleaning, joining, and visualizing spatial data ethically. 36 | - Practical color, projection, and layout tips that make your maps sing. 37 | 38 | # Quick Facts 39 | - Format: PDF + GitHub repo 40 | - Length: 233 pages, full color 41 | - Skill Level: True beginner → proficient mapper 42 | - Software Needed: Free R & RStudio (easy install instructions included) 43 | - License: Personal use, unlimited devices 44 | 45 | 46 | Ready to turn raw data into persuasive, publication-ready maps? 47 | 48 | Get your copy here: https://payhip.com/b/MH9Ot 49 | 50 | (Need a sneak peek? Read Intro and Chapter 1 before you commit here: https://milospopovic.net/research/book_sample.pdf 51 | -------------------------------------------------------------------------------- /Ch 11/chapter_11_project_gdp.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_11_project_gdp.R --- 2 | 3 | # Step 1: Setup 4 | print("Loading packages for GDP Animation Exercise...") 5 | pacman::p_load(gganimate, 6 | sf, 7 | tidyverse, 8 | gapminder, 9 | rnaturalearth, 10 | countrycode, 11 | gifski, 12 | scales) 13 | 14 | # Step 2: Get world boundaries and join with gapminder data 15 | 16 | world_sf <- rnaturalearth::ne_countries( 17 | scale = 'medium', 18 | returnclass = 'sf') |> 19 | dplyr::select(iso_a3 = adm0_a3, name, geometry) |> 20 | dplyr::filter( 21 | name != "Antarctica" # remove Antarctica 22 | ) 23 | 24 | # Filter gapminder for all years and add ISO codes 25 | gdp_pc_df <- gapminder::gapminder |> 26 | dplyr::select(country, gdpPercap, year) |> 27 | dplyr::mutate(iso_a3 = suppressWarnings( 28 | countrycode::countrycode( 29 | country, origin = 'country.name', 30 | destination = 'iso3c') 31 | )) 32 | 33 | # Join GDP per capita to polygons 34 | gdp_pc_sf <- world_sf |> 35 | dplyr::left_join(gdp_pc_df, by = "iso_a3") 36 | 37 | # Step 3: Create Base ggplot 38 | print("Creating base ggplot for GDP animation...") 39 | base_gdp_map <- ggplot(data = gdp_pc_sf) + 40 | geom_sf(aes(fill = gdpPercap), 41 | color = "white", 42 | linewidth = 0.1) + 43 | # Use log10 scale for skewed GDP data 44 | scale_fill_viridis_c( 45 | option = "viridis", 46 | name = "GDP/Cap (log10)", 47 | trans = "log10", 48 | labels = scales::label_log(digits = 2), 49 | na.value = "lightgrey" 50 | ) + 51 | theme_void() + 52 | theme( 53 | legend.position = "bottom", 54 | legend.key.width = unit(1.5, "cm"), 55 | plot.title = element_text( 56 | hjust = 0.5, size = 16, face = "bold"), 57 | plot.caption = element_text(size = 8, hjust = 0.95) 58 | ) 59 | 60 | # Step 4: Add Animation Layers 61 | print("Adding animation layers...") 62 | gdp_animation <- base_gdp_map + 63 | transition_time(year) + 64 | labs( 65 | title = "GDP per Capita in {as.integer(frame_time)}", 66 | caption = "Data: Gapminder (GDP/Cap on log10 scale)") + 67 | ease_aes('cubic-in-out') # Try a different easing 68 | 69 | # Step 5: Render and Save 70 | output_gdp_gif <- "gdp_animation.gif" 71 | print( 72 | paste( 73 | "Saving GDP animation:", 74 | output_gdp_gif, "(takes time!)...") 75 | ) 76 | 77 | anim_save( 78 | filename = output_gdp_gif, 79 | animation = gdp_animation, 80 | nframes = 200, 81 | # Adjust frame count 82 | fps = 15, 83 | # Adjust speed 84 | width = 800, 85 | height = 500, 86 | res = 100, 87 | renderer = gifski_renderer() 88 | ) 89 | print("GDP animation saved.") 90 | 91 | # Step 6: View (Manually open the file) 92 | print(paste("View the saved animation:", output_gdp_gif)) -------------------------------------------------------------------------------- /Ch 10/chapter_10_project_3d_model.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_10_project_3d_model.R --- 2 | 3 | # Step 1: Choose Region & Setup 4 | print("Loading packages for 3D Model Exercise...") 5 | pacman::p_load(rayshader, terra, geodata, sf, tidyverse) 6 | 7 | # --- USER INPUT: SET YOUR REGION HERE --- 8 | # Use ISO code for a country 9 | project_iso_code <- "NZL" 10 | project_region_name <- "New Zealand" 11 | 12 | # Step 2: Get Elevation Data 13 | print(paste("Fetching elevation data for", project_region_name)) 14 | 15 | elevation_raster <- geodata::elevation_30s( 16 | country = project_iso_code, path = tempdir()) 17 | 18 | print("Elevation data obtained.") 19 | 20 | # Optional Crop (Example: if using ISO for a large country) 21 | # crop_extent <- terra::ext(xmin, xmax, ymin, ymax) 22 | # Define smaller extent 23 | # elevation_raster <- terra::crop(elevation_raster, crop_extent) 24 | 25 | # Step 3: Convert to Matrix 26 | print("Converting raster to matrix...") 27 | elevation_matrix <- rayshader::raster_to_matrix(elevation_raster) 28 | 29 | print( 30 | paste( 31 | "Matrix dimensions:", 32 | paste(dim(elevation_matrix), 33 | collapse = " x ") 34 | ) 35 | ) 36 | 37 | # Step 4: Create Basic 3D Plot 38 | print("Creating initial 3D plot (opens RGL)...") 39 | # Experiment with texture: 40 | # "imhof1", "imhof2", "imhof3", "imhof4", "desert", "bw" 41 | initial_zscale <- 15 # Starting vertical exaggeration 42 | initial_theta <- 0 43 | initial_phi <- 85 44 | 45 | elevation_matrix |> 46 | rayshader::sphere_shade(texture = "imhof1") |> 47 | rayshader::plot_3d( 48 | heightmap = elevation_matrix, 49 | zscale = initial_zscale, 50 | fov = 0, 51 | theta = initial_theta, 52 | phi = initial_phi, 53 | zoom = 0.7, 54 | windowsize = c(800, 600) 55 | ) 56 | 57 | # Step 5 & 6: Experiment with zscale and Camera 58 | print(paste("Initial plot uses zscale =", initial_zscale)) 59 | print( 60 | "-> Try changing 'initial_zscale' in the code and re-running.") 61 | 62 | print( 63 | "-> Or, drag the RGL window view, then run 64 | 'rayshader::render_camera()' in Console." 65 | ) 66 | 67 | # Example: Capture current camera after manual adjustment (run in 68 | # Console) 69 | # current_cam <- rayshader::render_camera() 70 | # print(current_cam) 71 | 72 | # Step 7: Save Snapshot 73 | print("Saving snapshot of the current RGL view...") 74 | snapshot_filename <- paste0( 75 | project_iso_code, "_3d_snapshot.png") 76 | 77 | rayshader::render_snapshot( 78 | filename = snapshot_filename, 79 | title_text = paste( 80 | project_region_name, "(3D Snapshot)"), 81 | title_bar_color = "black", 82 | title_color = "white" 83 | ) 84 | print(paste("Snapshot saved to", snapshot_filename)) 85 | 86 | # Step 8: High Quality Render 87 | print("Starting high quality render (takes time)...") 88 | 89 | hq_filename <- paste0(project_iso_code, "_3d_hq.png") 90 | rayshader::render_highquality( 91 | filename = hq_filename, 92 | lightdirection = 290, 93 | lightaltitude = 60, 94 | lightintensity = 1000, 95 | title_text = paste(project_region_name, "(High Quality)"), 96 | width = 1080, 97 | height = 1200, 98 | interactive = FALSE 99 | ) 100 | print(paste("High quality render saved to", hq_filename)) 101 | 102 | # Close RGL window 103 | try(rgl::rgl.close(), silent = TRUE 104 | ) -------------------------------------------------------------------------------- /Ch 3/chapter_3_cleaning.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_3_cleaning.R --- # 2 | # (Can be in the same script file) 3 | 4 | # Step 1: Ensure packages and data are ready 5 | print("Loading packages...") 6 | pacman::p_load(sf, dplyr, rnaturalearth) # Need rnaturalearth for continent join example 7 | 8 | # Step 2: Select only needed columns 9 | # Let's imagine we only need name, iso code, 10 | # and geometry for our map 11 | # Load world_boundaries.gpkg 12 | world_boundaries_loaded <- sf::st_read("world_boundaries.gpkg") 13 | sf::st_geometry( 14 | world_boundaries_loaded 15 | ) <- "geometry" # name geometry field 16 | print("Selecting specific columns...") 17 | # Use the pipe |> to pass the data through steps 18 | world_selected <- world_boundaries_loaded |> 19 | # Keep only these columns 20 | # (adjust names based on your actual data!) 21 | # Use any_of() to avoid errors if a column doesn't exist 22 | # Ensure the geometry column is always included when selecting 23 | dplyr::select( 24 | dplyr::any_of( 25 | c("NAME_ENGL", "ISO3_CODE")), geometry) 26 | print("Columns selected:") 27 | print(head(world_selected)) 28 | 29 | # Step 3: Rename columns for easier use 30 | print("Renaming columns...") 31 | world_renamed <- world_selected |> 32 | dplyr::rename( 33 | country_name = NAME_ENGL, # New name = Old name 34 | iso3 = ISO3_CODE) # Adjust old names based on your data! 35 | # geometry column usually keeps its name) 36 | print("Columns renamed:") 37 | print(head(world_renamed)) 38 | 39 | # Store the cleaned data (before filtering for Africa) 40 | # for the next step 41 | assign("world_renamed", world_cleaned, envir = .GlobalEnv) 42 | 43 | # Step 4: Filter rows - e.g., keep only African countries 44 | # First, we need continent info, which wasn't in our 45 | # simplified gpkg. 46 | # Let's reload the rnaturalearth data which has continents. 47 | print("Reloading rnaturalearth data to get continents for 48 | filtering example...") 49 | world_ne <- rnaturalearth::ne_countries( 50 | scale = "medium", returnclass = "sf") |> 51 | dplyr::select(adm0_a3, continent) |> # Keep ISO and continent 52 | sf::st_drop_geometry() # We only need the table for joining 53 | 54 | # Now join the continent info to our renamed boundaries 55 | world_renamed_with_cont <- world_renamed |> 56 | dplyr::left_join(world_ne, by = c("iso3" = "adm0_a3")) 57 | 58 | print("Filtering for African countries...") 59 | 60 | # Filter Africa 61 | africa_only <- world_renamed_with_cont |> 62 | dplyr::filter(continent == "Africa") 63 | print("Filtered data for Africa:") 64 | print(africa_only) # Show the filtered sf object 65 | 66 | # Step 5: Check for and filter out empty/invalid geometries 67 | # (Good Practice!) 68 | print("Checking for/removing empty or invalid geometries...") 69 | 70 | # Count initial rows 71 | initial_rows <- nrow(africa_only) 72 | 73 | africa_valid_geom <- africa_only |> 74 | # Remove features where geometry is empty 75 | dplyr::filter(!sf::st_is_empty(geometry)) |> 76 | # Optionally, try to fix invalid geometries 77 | sf::st_make_valid() |> 78 | # Filter again in case st_make_valid resulted in empty 79 | # ones 80 | dplyr::filter(!sf::st_is_empty(geometry)) 81 | 82 | # Count final rows 83 | final_rows <- nrow(africa_valid_geom) 84 | 85 | print(paste("Initial Africa rows:", initial_rows)) 86 | print(paste( 87 | "Rows after removing/fixing empty/invalid 88 | geometries:", 89 | final_rows 90 | )) -------------------------------------------------------------------------------- /Ch 7/chapter_7_plot_ggplot.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_7_plot_ggplot.R --- 2 | # (Can be in same script) 3 | 4 | # Step 1: Ensure packages and data exist 5 | # Need viridis for colors, part of tidyverse but good to ensure 6 | pacman::p_load(terra, tidyverse) 7 | # Step 2: Convert SpatRaster to data frame for ggplot2 8 | # terra::as.data.frame() does the conversion. 9 | # IMPORTANT: xy = TRUE includes the x (longitude) and y 10 | # (latitude) 11 | # coordinates for the center of each pixel! We need these for 12 | # plotting. 13 | print("Converting raster to data frame for ggplot2 14 | (can take time)...") 15 | elevation_df <- terra::as.data.frame( 16 | global_elevation_10min, xy = TRUE 17 | ) 18 | print("Conversion complete.") 19 | 20 | # Step 3: Inspect the data frame structure 21 | # See the new columns: x, y, and one with the raster 22 | # layer's name. 23 | print("--- First few rows of the data frame ---") 24 | print(head(elevation_df)) 25 | print("--- Column names ---") 26 | print(colnames(elevation_df)) # Note the 3rd column name! 27 | 28 | # Step 4: Rename the value column for easier use in ggplot 29 | # aes(). The value column name comes from the raster layer 30 | # name (e.g., 'wc2.1_10m_elev') 31 | # Let's find its name (it's usually the 3rd column after x 32 | # and y) and rename it 'elevation'. 33 | value_col_name <- names(elevation_df)[3] 34 | print(paste("Renaming column:", value_col_name, "to 'elevation'")) 35 | 36 | # Use the pipe |> with dplyr::rename() 37 | elevation_df <- elevation_df |> 38 | # new_name = old_name. Use all_of() for safety if name has 39 | # spaces/symbols 40 | dplyr::rename(elevation = dplyr::all_of(value_col_name)) |> 41 | # Optional but recommended: Remove rows where elevation is NA 42 | # NA values can sometimes cause issues or appear as unwanted 43 | # colors 44 | dplyr::filter(!is.na(elevation)) 45 | 46 | print("Column renamed. New names:") 47 | print(colnames(elevation_df)) 48 | 49 | # Step 5: Create the ggplot 50 | print("Generating plot with ggplot2...") 51 | # Start ggplot, providing the data frame 52 | gg_elevation_map <- ggplot(data = elevation_df) + 53 | 54 | # Add the raster layer using geom_raster 55 | # aes() maps columns to visual properties: 56 | # x -> x-axis, y -> y-axis, elevation -> fill color 57 | geom_raster(aes(x = x, y = y, fill = elevation)) + 58 | 59 | # Apply a color scale suitable for continuous elevation data 60 | # scale_fill_viridis_c is perceptually uniform 61 | # 'option = "cividis"' is one specific viridis palette 62 | # 'name = ...' sets the legend title 63 | scale_fill_viridis_c(option = "turbo", name = "Elevation (m)") + 64 | 65 | # Add labels: title, axis labels, caption 66 | labs( 67 | title = "Global Elevation", 68 | x = "Longitude", 69 | y = "Latitude", 70 | caption = "Data source: WorldClim via geodata" 71 | ) + 72 | 73 | # Use coord_sf() to ensure ggplot uses an appropriate map 74 | # aspect ratio 75 | # It helps prevent the map from looking stretched or squashed. 76 | # crs = 4326 tells it the input coordinates are WGS84 Lat/Lon 77 | coord_sf(crs = 4326, expand = FALSE) + # expand=FALSE removes 78 | # padding 79 | 80 | # Apply theme adjustments for appearance 81 | theme_minimal() + 82 | theme( 83 | legend.position = "bottom", # Move legend below map 84 | legend.key.width = unit(1.5, "cm") 85 | ) # Make legend color bar wider 86 | 87 | # Step 6: Display the ggplot map 88 | # Printing the ggplot object renders it in the Plots pane. 89 | print(gg_elevation_map) 90 | print("ggplot map generated.") -------------------------------------------------------------------------------- /Ch 8/chapter_8_project_topo.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_8_project_topo.R --- 2 | # Step 1: Choose Region & Setup 3 | print("Loading packages for Topo Map Exercise...") 4 | pacman::p_load( 5 | terra, geodata, sf, tidyverse, 6 | tidyterra, ggnewscale) 7 | theme_set(theme_minimal()) 8 | 9 | # --- USER INPUT: SET YOUR COUNTRY ISO CODE & NAME HERE --- 10 | # Example: "PER" for Peru, "NZL" for New Zealand, "ITA" for Italy 11 | your_iso_code <- "PER" 12 | your_country_name <- "Peru" 13 | # ---------------------------------------------------------- 14 | 15 | # Step 2: Get Data 16 | 17 | paste( 18 | "Fetching elevation data for", 19 | your_country_name, "(may take time)...") 20 | 21 | elevation_raster <- geodata::elevation_30s( 22 | country = your_iso_code, path = tempdir()) 23 | names(elevation_raster) <- "elevation" # Rename layer 24 | print("Elevation data loaded.") 25 | 26 | print(paste("Fetching boundary for", your_country_name)) 27 | 28 | boundary_sf <- geodata::gadm( 29 | country = your_iso_code, 30 | level = 0, 31 | path = tempdir()) |> 32 | sf::st_as_sf() |> 33 | sf::st_transform( 34 | crs = terra::crs(elevation_raster)) # Match CRS 35 | 36 | # Step 3: Calculate Terrain Attributes 37 | print("Calculating terrain attributes...") 38 | terrain_attrs <- terra::terrain( 39 | elevation_raster, 40 | v = c("slope", "aspect"), 41 | unit = "degrees") 42 | 43 | hillshade_raster <- terra::shade( 44 | terrain_attrs[[1]], 45 | terrain_attrs[[2]], 46 | angle = 45, 47 | direction = 225) # Sun from NE 48 | names(hillshade_raster) <- "hillshade_val" 49 | print("Terrain attributes calculated.") 50 | 51 | # Step 4: Prepare for ggplot 52 | print("Converting rasters to data frame...") 53 | elev_df <- terra::as.data.frame( 54 | elevation_raster, xy = TRUE, na.rm = TRUE) 55 | 56 | hill_df <- terra::as.data.frame( 57 | hillshade_raster, xy = TRUE, na.rm = TRUE) 58 | print("Data prepared for ggplot.") 59 | 60 | # Step 5: Set the limits 61 | limits <- terra::minmax(elevation_raster) 62 | 63 | # Step 6: Create Shaded Relief Map 64 | print("Creating shaded relief map...") 65 | topo_map_project <- ggplot() + 66 | # Hillshade layer 67 | geom_raster( 68 | data = hill_df, 69 | aes( 70 | x = x, y = y, 71 | fill = hillshade_val 72 | ), 73 | show.legend = FALSE) + 74 | scale_fill_gradientn( 75 | colors = hcl.colors( 76 | 12, "Light Grays", rev = TRUE), # A light gray palette 77 | na.value = NA # Transparent for any missing cells 78 | ) + 79 | # Elevation layer 80 | ggnewscale::new_scale_fill() + 81 | geom_raster( 82 | data = elev_df, 83 | aes(x = x, y = y, fill = elevation)) + 84 | tidyterra::scale_fill_hypso_tint_c( 85 | palette = "dem_poster", 86 | limits = limits, 87 | alpha = 0.5) + 88 | # Optional boundary 89 | geom_sf( 90 | data = boundary_sf, 91 | fill = NA, 92 | color = "black", 93 | linewidth = 0.3 94 | ) + 95 | # Labels, coord, theme 96 | labs( 97 | title = paste( 98 | "Shaded Relief Map of", your_country_name), 99 | caption = "Data: SRTM 30s via geodata") + 100 | coord_sf( 101 | crs = terra::crs(elevation_raster), expand = FALSE) + 102 | theme_void() + 103 | theme( 104 | legend.position = "right", 105 | plot.title = element_text(hjust = 0.5, face = "bold")) 106 | 107 | # Step 6: Print 108 | print(topo_map_project) 109 | print("Map created.") 110 | 111 | # Step 7: Bonus Save 112 | # ggsave( 113 | # paste0(your_iso_code, "_topo_map.png"), 114 | # plot = topo_map_project, width = 7, height = 8, 115 | # dpi = 300, bg = "white") 116 | # print("Map potentially saved.") 117 | 118 | # Cleanup 119 | # rm(elev_df, hill_df, terrain_df) 120 | # gc() -------------------------------------------------------------------------------- /Ch 5/chapter_5_project_germany.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_5_project_germany.R --- 2 | 3 | # Step 1: Setup 4 | print("Loading packages for Germany Exercise...") 5 | pacman::p_load( 6 | sf, # For spatial data handling 7 | tidyverse, # For data manipulation and plotting 8 | maps, # Provides world.cities dataset 9 | rnaturalearth, # Provides Natural Earth map data 10 | scales, # Scale functions for legends 11 | ggrepel # For non-overlapping text labels 12 | ) 13 | 14 | # Step 2: Get Germany Boundary 15 | print("Getting Germany boundary...") 16 | germany_boundary_sf <- rnaturalearth::ne_countries( 17 | country = "Germany", # Only Germany 18 | scale = "medium", # Medium resolution 19 | returnclass = "sf" # Return as sf object 20 | ) 21 | 22 | # Step 3: Load & Prepare City Data 23 | print("Loading and preparing German city data...") 24 | german_cities_df <- maps::world.cities |> 25 | dplyr::filter( 26 | country.etc == "Germany", # Only cities in Germany 27 | !is.na(pop) # Exclude missing populations 28 | ) |> 29 | dplyr::select( 30 | name, # City name 31 | pop, # Population 32 | lat, # Latitude 33 | long # Longitude 34 | ) 35 | 36 | # Convert the data.frame to an sf object using the latitude/longitude columns 37 | german_cities_sf <- sf::st_as_sf( 38 | german_cities_df, 39 | coords = c("long", "lat"), # Columns to use for coordinates 40 | crs = 4326 # WGS84 geographic coordinate system 41 | ) 42 | 43 | print(paste("Loaded", nrow(german_cities_sf), "German cities with population data.")) 44 | 45 | # Step 4: Identify Top 5 Most Populous Cities 46 | # ------------------------------------------ 47 | # Arrange cities by descending population and keep the first five 48 | top5_cities_sf <- german_cities_sf |> 49 | dplyr::arrange(dplyr::desc(pop)) |> 50 | dplyr::slice(1:5) |> 51 | # Extract numeric X/Y coordinates for labeling 52 | dplyr::mutate( 53 | lon = sf::st_coordinates(geometry)[,1], 54 | lat = sf::st_coordinates(geometry)[,2] 55 | ) 56 | 57 | # Step 5: Create Bubble Map with Labels 58 | print("Creating German city bubble map with top-5 labels...") 59 | german_bubble_map <- ggplot2::ggplot() + 60 | # Background layer: Germany boundary 61 | ggplot2::geom_sf( 62 | data = germany_boundary_sf, 63 | fill = "grey95", 64 | color = "grey70" 65 | ) + 66 | # City points: size represents population 67 | ggplot2::geom_sf( 68 | data = german_cities_sf, 69 | mapping = aes(size = pop), 70 | color = "darkred", 71 | shape = 16, 72 | alpha = 0.6 73 | ) + 74 | # Label the top 5 largest cities 75 | ggrepel::geom_label_repel( 76 | data = top5_cities_sf, 77 | mapping = aes(x = lon, y = lat, label = name), 78 | # Tells repel how to get coords 79 | size = 2.5, 80 | # Font size 81 | min.segment.length = 0, 82 | # Draw line even if label 83 | # is close to point 84 | max.overlaps = 30, 85 | # Allow some overlap if needed, 86 | # increase if too sparse 87 | force = 0.5, 88 | # How strongly labels push away 89 | box.padding = 0.2 # Padding around text 90 | ) + 91 | # Control the city bubble sizes and legend 92 | ggplot2::scale_size_area( 93 | name = "Population", 94 | max_size = 12, 95 | labels = scales::label_number(scale = 1e-3, suffix = "K") 96 | ) + 97 | # Titles and caption 98 | ggplot2::labs( 99 | title = "Major German Cities by Population", 100 | caption = "Data: maps::world.cities" 101 | ) + 102 | # Constrain the plot to Germany's bounding box 103 | # Clean theme 104 | ggplot2::theme_void() 105 | 106 | # Step 6: Display the Map 107 | print(german_bubble_map) 108 | print("Germany map with top-5 labels created.") 109 | 110 | # Step 7: Save the Map as image (optional) 111 | # ggsave( 112 | # filename = "germany_major_cities.png", 113 | # plot = german_bubble_map, device = "png", 114 | # width = 7, height = 8, bg = "white" 115 | # ) -------------------------------------------------------------------------------- /Ch 2/chapter_2_explore_sf.r: -------------------------------------------------------------------------------- 1 | # --- Script: chapter_2_explore_sf.R --- 2 | 3 | # Step 1: Load necessary packages 4 | # We need sf for spatial operations, rnaturalearth for data, 5 | # and tidyverse for data viewing/manipulation. 6 | print("Loading packages...") 7 | pacman::p_load(sf, rnaturalearth, tidyverse) 8 | print("Packages ready") 9 | 10 | # Step 2: Get world countries data as an sf object 11 | # This is vector data (polygons) 12 | print("Getting world countries sf object...") 13 | world_countries <- rnaturalearth::ne_countries( 14 | scale = 'medium', returnclass = 'sf') 15 | print("Data loaded into 'world_countries'.") 16 | 17 | # Step 3: Get world cities data as an sf object 18 | # This is vector data (points) 19 | print("Getting world cities sf object...") 20 | world_cities <- rnaturalearth::ne_download( 21 | scale = "medium", 22 | type = "populated_places", # <— note this 23 | category = "cultural", # defaults to "cultural" 24 | returnclass = "sf" 25 | ) 26 | print("Data loaded into 'world_cities'.") 27 | 28 | # Inspecting the world_countries sf object 29 | 30 | # Step 4: Print the whole object - see the table + geometry 31 | print("--- Full sf object (first few rows) ---") 32 | print(world_countries) 33 | # Notice it looks like a table, but the last column is 'geometry' 34 | # and it shows the geometry type (MULTIPOLYGON) and CRS info! 35 | 36 | # Step 5: Check the class - What kind of object is it? 37 | print("--- Object Class ---") 38 | print(class(world_countries)) 39 | # Expected output: Should include "sf" and "data.frame" - it's both! 40 | 41 | # Step 6: Use glimpse() for a compact summary of attributes 42 | print("--- Attribute Summary (glimpse) ---") 43 | # glimpse() is from dplyr, great for seeing column names and types 44 | dplyr::glimpse(world_countries) 45 | # Look at all the attribute columns (like name, continent, pop_est) 46 | # and their types (, ) 47 | # Find the special 'geometry' column at the end () 48 | 49 | # Step 7: Look at JUST the attribute table (like a regular data 50 | # frame) 51 | print("--- Attribute Table Only (st_drop_geometry) ---") 52 | # sf::st_drop_geometry() removes the special geometry column 53 | world_attributes_only <- sf::st_drop_geometry(world_countries) 54 | print(head(world_attributes_only)) # Show first few rows of the 55 | # plain table 56 | print(class(world_attributes_only)) # Should now just be "data. 57 | # frame" 58 | 59 | # Step 8: Look at JUST the geometry column 60 | print("--- Geometry Column Only (st_geometry) ---") 61 | # sf::st_geometry() extracts only the geometry list-column 62 | world_geometry_only <- sf::st_geometry(world_countries) 63 | print(world_geometry_only[1:3]) # Show geometry info for the first 64 | # 3 countries 65 | print(class(world_geometry_only)) # Should be "sfc" (simple feature 66 | # column) and "sfc_MULTIPOLYGON" 67 | 68 | # Step 9: Check the CRS again! 69 | print("--- Coordinate Reference System (st_crs) ---") 70 | print(sf::st_crs(world_countries)) 71 | # Confirm it's likely EPSG:4326 (WGS84 Lat/Lon) 72 | 73 | # CRS Transformation Recap 74 | 75 | # Step 10: Transform world_countries to Robinson projection again 76 | print("Transforming countries to Robinson...") 77 | target_crs_robinson <- "ESRI:54030" # Robinson code 78 | world_countries_robinson <- world_countries |> 79 | sf::st_transform(crs = target_crs_robinson) 80 | print("Transformation complete.") 81 | 82 | # Step 11: Check the NEW CRS 83 | print("--- CRS of Transformed Data ---") 84 | print(sf::st_crs(world_countries_robinson)) 85 | 86 | # Step 12: Plot comparison (using ggplot this time!) 87 | print("Plotting comparison with ggplot2...") 88 | 89 | plot_original <- ggplot() + 90 | geom_sf( 91 | data = world_countries, linewidth=0.2 92 | ) + ggtitle("Original (EPSG:4326)") + 93 | theme_minimal() 94 | plot_transformed <- ggplot() + 95 | geom_sf( 96 | data = world_countries_robinson, linewidth=0.2 97 | ) + 98 | ggtitle("Transformed (Robinson)") + 99 | theme_minimal() 100 | 101 | # Arrange side-by-side (requires 'patchwork' package) 102 | pacman::p_load(patchwork) 103 | print(plot_original / plot_transformed) 104 | print("Comparison plot displayed.") -------------------------------------------------------------------------------- /Ch 3/country_indicators.csv: -------------------------------------------------------------------------------- 1 | country,iso2c,iso3c,year,gdp_per_capita,life_expectancy,population 2 | Afghanistan,AF,AFG,2020,510.787063366811,61.454,39068979 3 | Africa Eastern and Southern,ZH,AFE,2020,1344.08096192004,63.7664840026943,694446100 4 | Africa Western and Central,ZI,AFW,2020,1664.24917643553,57.3644254011107,474569351 5 | Albania,AL,ALB,2020,5370.77862312929,77.824,2837849 6 | Algeria,DZ,DZA,2020,3743.5419522929,73.257,44042091 7 | American Samoa,AS,ASM,2020,14489.2586563775,72.672,49761 8 | Andorra,AD,AND,2020,37361.0900666982,79.418,77380 9 | Angola,AO,AGO,2020,1449.92286669308,63.116,33451132 10 | Antigua and Barbuda,AG,ATG,2020,15360.4544160475,77.161,91846 11 | Arab World,1A,ARB,2020,5648.34111789395,70.7265588103862,453723239 12 | Argentina,AR,ARG,2020,8535.59938004389,75.878,45191965 13 | Armenia,AM,ARM,2020,4268.68093304583,73.3756097560976,2961500 14 | Aruba,AW,ABW,2020,22855.9323200761,75.406,108587 15 | Australia,AU,AUS,2020,51791.540179984,83.2,25649248 16 | Austria,AT,AUT,2020,48716.4098900349,81.1926829268293,8916864 17 | Azerbaijan,AZ,AZE,2020,4229.91064904503,70.312,10093121 18 | "Bahamas, The",BS,BHS,2020,25155.6725432789,72.994,395863 19 | Bahrain,BH,BHR,2020,24342.8444554718,78.683,1472204 20 | Bangladesh,BD,BGD,2020,2248.85078828233,71.421,166298024 21 | Barbados,BB,BRB,2020,18347.1109131055,76.647,281698 22 | Belarus,BY,BLR,2020,6542.8645398436,72.1385609756098,9379952 23 | Belgium,BE,BEL,2020,45906.2875805246,80.6951219512195,11538604 24 | Belize,BZ,BLZ,2020,5227.19363786168,71.58,390812 25 | Benin,BJ,BEN,2020,1197.50136428063,60.154,13070169 26 | Bermuda,BM,BMU,2020,106973.175732348,81.456,64382 27 | Bhutan,BT,BTN,2020,3191.66907031782,72.253,770006 28 | Bolivia,BO,BOL,2020,3099.94219044572,62.906,11816299 29 | Bosnia and Herzegovina,BA,BIH,2020,6130.31133245161,76.04,3299349 30 | Botswana,BW,BWA,2020,6323.31419156209,67.643,2365894 31 | Brazil,BR,BRA,2020,7074.19378281866,74.506,208660842 32 | British Virgin Islands,VG,VGB,2020,NA,76.894,37135 33 | Brunei Darussalam,BN,BRN,2020,26834.3592463839,75.114,447404 34 | Bulgaria,BG,BGR,2020,10198.5388313404,73.3560975609756,6934015 35 | Burkina Faso,BF,BFA,2020,825.237038782475,60.454,21478690 36 | Burundi,BI,BDI,2020,210.008139902508,62.569,12617036 37 | Cabo Verde,CV,CPV,2020,3539.22662027109,73.818,514679 38 | Cambodia,KH,KHM,2020,2081.73914242939,70.059,16725474 39 | Cameroon,CM,CMR,2020,1555.60370660749,61.674,26210558 40 | Canada,CA,CAN,2020,43537.839298904,81.5351219512195,38028638 41 | Caribbean small states,S3,CSS,2020,10270.790474345,71.6276208799328,3092577 42 | Cayman Islands,KY,CYM,2020,82338.7982853661,79.23,68684 43 | Central African Republic,CF,CAF,2020,462.879071294011,50.596,5026628 44 | Central Europe and the Baltics,B8,CEB,2020,16450.8775088615,76.0074026201527,102046650 45 | Chad,TD,TCD,2020,622.095543396442,53.082,17224679 46 | Channel Islands,JG,CHI,2020,56785.9402392525,80.9,166235 47 | Chile,CL,CHL,2020,13114.815470545,79.349,19370624 48 | China,CN,CHN,2020,10408.7195541075,78.019,1411100000 49 | Colombia,CO,COL,2020,5339.68711357943,74.757,50629997 50 | Comoros,KM,COM,2020,1527.16991011916,65.756,802163 51 | "Congo, Dem. Rep.",CD,COD,2020,507.521230077872,60.428,95989998 52 | "Congo, Rep.",CG,COG,2020,1993.586671931,64.391,5752791 53 | Costa Rica,CR,CRI,2020,12394.0493969492,79.725,5034320 54 | Cote d'Ivoire,CI,CIV,2020,2179.72934832935,60.14,28915449 55 | Croatia,HR,HRV,2020,14808.4620188819,77.5243902439024,3914206 56 | Cuba,CU,CUB,2020,9605.26125067263,77.407,11176354 57 | Curacao,CW,CUW,2020,16356.0933594414,76.501,154947 58 | Cyprus,CY,CYP,2020,28649.015625,81.23,1302247 59 | Czechia,CZ,CZE,2020,23472.8915454417,78.2268292682927,10697858 60 | Denmark,DK,DNK,2020,60985.4885601514,81.6024390243903,5831404 61 | Djibouti,DJ,DJI,2020,2881.99924450145,64.2,1105188 62 | Dominica,DM,DMA,2020,7461.77933220095,71.269,67573 63 | Dominican Republic,DO,DOM,2020,7162.29175242114,72.636,11008300 64 | Early-demographic dividend,V2,EAR,2020,3240.95150218079,69.8801308490982,3396051187 65 | East Asia & Pacific (excluding high income),4E,EAP,2020,8245.71521577714,75.6976393295064,2121866294 66 | East Asia & Pacific (IDA & IBRD countries),T4,TEA,2020,8338.13776036985,75.73384970547,2095759417 67 | East Asia & Pacific,Z4,EAS,2020,11464.1012855988,76.5434376080231,2369438682 68 | Ecuador,EC,ECU,2020,5463.64509649315,72.004,17546065 69 | "Egypt, Arab Rep.",EG,EGY,2020,3511.11381026379,69.79,109315124 70 | El Salvador,SV,SLV,2020,3997.19279583709,70.244,6234673 71 | Equatorial Guinea,GQ,GNQ,2020,5764.05502912947,62.141,1716468 72 | Eritrea,ER,ERI,2020,NA,66.98,3291271 73 | Estonia,EE,EST,2020,23933.9939421065,78.5951219512195,1329522 74 | Eswatini,SZ,SWZ,2020,3442.3216527415,59.862,1192729 75 | Ethiopia,ET,ETH,2020,905.313175805855,65.969,118917671 76 | Euro area,XC,EMU,2020,38241.5756519082,81.487506689434,346922366 77 | Europe & Central Asia (excluding high income),7E,ECA,2020,5733.53881172869,73.5589073374531,250655325.5 78 | Europe & Central Asia (IDA & IBRD countries),T7,TEC,2020,8443.06159084581,73.1551939454689,463913014.5 79 | Europe & Central Asia,Z7,ECS,2020,24091.3416895738,77.1746102388977,925088545.5 80 | European Union,EU,EUU,2020,34635.4895902222,80.4310201133423,447653554 81 | Faroe Islands,FO,FRO,2020,62236.0866558197,82.6439024390244,52571 82 | Fiji,FJ,FJI,2020,4844.42129036462,66.924,914963 83 | Finland,FI,FIN,2020,48828.6846862799,81.9317073170732,5529543 84 | Fragile and conflict affected situations,F1,FCS,2020,1680.96884674984,62.0881829918929,996691242 85 | France,FR,FRA,2020,39169.8606000707,82.1756097560976,67601110 86 | French Polynesia,PF,PYF,2020,20746.2720428286,82.459,279209 87 | Gabon,GA,GAB,2020,6605.80346077565,67.073,2322539 88 | "Gambia, The",GM,GMB,2020,720.335143352133,64.418,2515733 89 | Georgia,GE,GEO,2020,4300.85701291207,73.509,3722716 90 | Germany,DE,DEU,2020,47379.765194548,81.0414634146342,83160871 91 | Ghana,GH,GHA,2020,2196.54789313621,64.309,31887809 92 | Gibraltar,GI,GIB,2020,NA,83.07,36173 93 | Greece,GR,GRC,2020,17886.733165236,81.2878048780488,10698599 94 | Greenland,GL,GRL,2020,54693.0766804265,71.8507317073171,56367 95 | Grenada,GD,GRD,2020,8968.55890108484,75.019,116341 96 | Guam,GU,GUM,2020,36482.9363953675,75.529,162158 97 | Guatemala,GT,GTM,2020,4477.61761796418,69.971,17357325 98 | Guinea-Bissau,GW,GNB,2020,817.780335749159,61.343,2013255 99 | Guinea,GN,GIN,2020,1053.66097696415,59.35,13371183 100 | Guyana,GY,GUY,2020,6775.70939096303,67.751,807481 101 | Haiti,HT,HTI,2020,1290.32538667383,63.773,11243848 102 | Heavily indebted poor countries (HIPC),XE,HPC,2020,959.181737577661,63.0726319690016,847743308 103 | High income,XD,,2020,40262.8698752887,79.2001486815502,1389524835 104 | Honduras,HN,HND,2020,2307.61494319511,70.864,10119640 105 | "Hong Kong SAR, China",HK,HKG,2020,46109.2299946609,85.4963414634146,7481000 106 | Hungary,HU,HUN,2020,16131.9540355923,75.419512195122,9750149 107 | IBRD only,XF,IBD,2020,6171.87965743072,73.2956686942805,4879367453 108 | Iceland,IS,ISL,2020,59023.5663465778,83.0634146341463,366463 109 | IDA & IBRD total,ZT,IBT,2020,4884.2624249805,70.7496272014679,6667541078.5 110 | IDA blend,XH,IDB,2020,1713.86457937643,60.850816739658,597524859 111 | IDA only,XI,IDX,2020,1197.82655626131,65.2834587967517,1190648766.5 112 | IDA total,XG,IDA,2020,1370.26257847044,63.8022749886515,1788173625.5 113 | India,IN,IND,2020,1907.04251637669,70.156,1402617695 114 | Indonesia,ID,IDN,2020,3853.70288778903,68.817,274814866 115 | "Iran, Islamic Rep.",IR,IRN,2020,2988.78122414716,74.141,87723443 116 | Iraq,IQ,IRQ,2020,4295.18945121228,69.651,42116605 117 | Ireland,IE,IRL,2020,87567.1148972037,82.4560975609756,4985382 118 | Isle of Man,IM,IMN,2020,79513.5330346598,80.591,84064 119 | Israel,IL,ISR,2020,44679.7715799755,82.6487804878049,9215100 120 | Italy,IT,ITA,2020,32091.4866621366,82.1951219512195,59438851 121 | Jamaica,JM,JAM,2020,4879.44024631319,71.452,2830739 122 | Japan,JP,JPN,2020,40040.7655055923,84.56,126261000 123 | Jordan,JO,JOR,2020,4022.0401356135,75.583,10865228 124 | Kazakhstan,KZ,KAZ,2020,8781.50797787648,71.195,19482117 125 | Kenya,KE,KEN,2020,1927.66459027849,61.596,52217334 126 | Kiribati,KI,KIR,2020,1769.43211760217,65.205,126099 127 | "Korea, Dem. People's Rep.",KP,PRK,2020,NA,72.783,26136312 128 | "Korea, Rep.",KR,KOR,2020,31721.2989141857,83.4268292682927,51836239 129 | Kosovo,XK,XKX,2020,4310.88824737367,74.212,1790152 130 | Kuwait,KW,KWT,2020,25236.0755520534,78.7609756097561,4400267 131 | Kyrgyz Republic,KG,KGZ,2020,1229.51775742135,71.8,6726595.5 132 | Lao PDR,LA,LAO,2020,2583.77730696131,68.339,7346533 133 | Late-demographic dividend,V3,LTE,2020,9734.68959265356,76.6935326569972,2316785459.5 134 | Latin America & Caribbean (excluding high income),XJ,LAC,2020,6940.27896334688,72.9350521073235,583125825 135 | Latin America & Caribbean,ZJ,LCN,2020,7444.66073393848,73.1889336743784,645497618 136 | Latin America & the Caribbean (IDA & IBRD countries),T2,TLA,2020,7240.56006051251,73.0747493617473,630051021 137 | Latvia,LV,LVA,2020,17564.2321549354,75.1853658536585,1900449 138 | Least developed countries: UN classification,XL,LDC,2020,1081.79748199294,64.8323585417323,1083636132 139 | Lebanon,LB,LBN,2020,5561.19166950397,76.299,5702398 140 | Lesotho,LS,LSO,2020,918.582577421982,55.133,2235727 141 | Liberia,LR,LBR,2020,616.787867006715,61.267,5149463 142 | Libya,LY,LBY,2020,6650.33810585348,72.388,7045399 143 | Liechtenstein,LI,LIE,2020,164671.093553454,81.6585365853659,38901 144 | Lithuania,LT,LTU,2020,20541.824988603,75.0292682926829,2794885 145 | Low & middle income,XO,LMY,2020,4585.37575872202,70.6674106202506,6438169877.5 146 | Low income,XM,,2020,651.453378446489,63.3694817765636,679560744 147 | Lower middle income,XN,,2020,2057.14629082262,68.045202107109,2966396716.5 148 | Luxembourg,LU,LUX,2020,116905.370396853,82.1439024390244,630419 149 | "Macao SAR, China",MO,MAC,2020,36976.2561071429,84.1292682926829,685400 150 | Madagascar,MG,MDG,2020,450.771615201509,62.706,28953556 151 | Malawi,MW,MWI,2020,602.507478176106,65.216,19533888 152 | Malaysia,MY,MYS,2020,9957.52626697613,76.057,33889558 153 | Maldives,MV,MDV,2020,7393.8886543933,78.713,502118 154 | Mali,ML,MLI,2020,804.343956745857,58.858,21713836 155 | Malta,MT,MLT,2020,31796.8213417258,82.0975609756098,515332 156 | Marshall Islands,MH,MHL,2020,5661.96787336674,65.387,42706 157 | Mauritania,MR,MRT,2020,1795.76459566714,66.794,4600131 158 | Mauritius,MU,MUS,2020,9011.04288445023,74.1770731707317,1266014 159 | Mexico,MX,MEX,2020,8841.27075003163,70.449,126799054 160 | "Micronesia, Fed. Sts.",FM,FSM,2020,3353.8894298388,65.878,110916 161 | Middle East & North Africa (excluding high income),XQ,MNA,2020,3175.61706654976,71.6409876742629,418461302 162 | Middle East & North Africa (IDA & IBRD countries),T3,TMN,2020,3174.94415084122,71.602007086554,413658033 163 | Middle East & North Africa,ZQ,MEA,2020,6650.85368826242,72.6473205039178,482334398 164 | Middle income,XP,MIC,2020,5049.55823705701,71.5286230831394,5758609133.5 165 | Moldova,MD,MDA,2020,4375.7788930524,69.914,2635130 166 | Monaco,MC,MCO,2020,176891.886538492,86.089,38050 167 | Mongolia,MN,MNG,2020,4001.25193077703,71.3490243902439,3327204 168 | Montenegro,ME,MNE,2020,7677.37132106817,75.9317073170732,621306 169 | Morocco,MA,MAR,2020,3268.0302734375,73.133,36584208 170 | Mozambique,MZ,MOZ,2020,462.433876479921,61.39,30783688 171 | Myanmar,MM,MMR,2020,1490.21683548367,66.612,53016522 172 | Namibia,NA,NAM,2020,3878.58982993966,64.071,2728762 173 | Nauru,NR,NRU,2020,10695.6993301601,61.527,11643 174 | Nepal,NP,NPL,2020,1154.21517578318,69.106,28966574 175 | Netherlands,NL,NLD,2020,53467.9277413737,81.3585365853659,17441500 176 | New Caledonia,NC,NCL,2020,33270.330597637,77.049,284176 177 | New Zealand,NZ,NZL,2020,41785.6922905898,82.2560975609756,5090200 178 | Nicaragua,NI,NIC,2020,1931.61515811642,70.766,6565267 179 | Niger,NE,NER,2020,579.512495758236,59.89,23717613 180 | Nigeria,NG,NGA,2020,2019.65706326224,53.072,213996181 181 | North America,XU,NAC,2020,62271.1969150653,77.449874465938,369619953 182 | North Macedonia,MK,MKD,2020,6659.59651070776,74.3951219512195,1856124 183 | Northern Mariana Islands,MP,MNP,2020,18220.8382427201,78.03,47528 184 | Norway,NO,NOR,2020,68340.0181033702,83.209756097561,5379475 185 | Not classified,XY,,2020,NA,NA,NA 186 | OECD members,OE,OED,2020,38686.327403326,78.9574582347209,1370013876 187 | Oman,OM,OMN,2020,16784.8630630058,77.763,4522497 188 | Other small states,S4,OSS,2020,13071.2236222876,73.1628849294383,13884095 189 | Pacific island small states,S2,PSS,2020,3666.59898132535,68.3568108792759,2595522 190 | Pakistan,PK,PAK,2020,1278.39735206896,65.701,235001746 191 | Palau,PW,PLW,2020,14556.5896891862,68.689,17792 192 | Panama,PA,PAN,2020,13290.5608347594,76.33,4293261 193 | Papua New Guinea,PG,PNG,2020,2429.61134586681,65.075,9815746 194 | Paraguay,PY,PRY,2020,5365.47220721191,72.72,6603739 195 | Peru,PE,PER,2020,6133.32552410183,73.832,32838579 196 | Philippines,PH,PHL,2020,3227.57910235199,70.097,112081264 197 | Poland,PL,POL,2020,15987.5753654045,76.4,37899070 198 | Portugal,PT,PRT,2020,22299.4044062173,81.3292682926829,10297081 199 | Post-demographic dividend,V4,PST,2020,44858.1104615819,80.0635835800272,1117765361 200 | Pre-demographic dividend,V1,PRE,2020,1313.17035996031,60.501207001444,998624261 201 | Puerto Rico,PR,PRI,2020,31427.4291136799,80.009,3281557 202 | Qatar,QA,QAT,2020,51683.5054353851,80.409,2794148 203 | Romania,RO,ROU,2020,13082.3006559554,74.2536585365854,19265250 204 | Russian Federation,RU,RUS,2020,10108.3271484375,71.3387804878049,145245148 205 | Rwanda,RW,RWA,2020,778.701538550324,66.953,13065837 206 | Samoa,WS,WSM,2020,4099.6600907147,70.727,211944 207 | San Marino,SM,SMR,2020,44426.6463400658,82.65,34770 208 | Sao Tome and Principe,ST,STP,2020,2167.22001796925,67.979,217435 209 | Saudi Arabia,SA,SAU,2020,23271.4037647084,77.596,31552510 210 | Senegal,SN,SEN,2020,1461.08720350553,67.496,16789219 211 | Serbia,RS,SRB,2020,8098.70955668849,74.4780487804878,6899126 212 | Seychelles,SC,SYC,2020,14041.4754074575,77.2365853658537,98462 213 | Sierra Leone,SL,SLE,2020,845.313836261458,59.694,7912558 214 | Singapore,SG,SGP,2020,61466.803676358,83.5439024390244,5685807 215 | Sint Maarten (Dutch part),SX,SXM,2020,30150.8998380477,75.002,41008 216 | Slovak Republic,SK,SVK,2020,19735.4858280801,76.8658536585366,5458827 217 | Slovenia,SI,SVN,2020,25392.0651093612,80.5317073170732,2102419 218 | Small states,S1,SST,2020,11381.557900564,72.2829532662199,19572194 219 | Solomon Islands,SB,SLB,2020,2063.33101522108,69.258,744498 220 | Somalia,SO,SOM,2020,518.184778494176,57.095,16651191 221 | South Africa,ZA,ZAF,2020,5580.60383075189,65.15,60562381 222 | South Asia (IDA & IBRD),T5,TSA,2020,1843.19527438293,69.6005740994618,1895144142 223 | South Asia,8S,SAS,2020,1843.19527438293,69.6005740994618,1895144142 224 | South Sudan,SS,SSD,2020,NA,57.648,10698467 225 | Spain,ES,ESP,2020,27230.3599933583,82.2317073170732,47365655 226 | Sri Lanka,LK,LKA,2020,3846.17449566736,76.928,21919000 227 | St. Kitts and Nevis,KN,KNA,2020,18859.0190361046,71.181,46870 228 | St. Lucia,LC,LCA,2020,8411.19941821204,72.308,178250 229 | St. Martin (French part),MF,MAF,2020,NA,80.085,31786 230 | St. Vincent and the Grenadines,VC,VCT,2020,8395.09988902412,69.605,103526 231 | Sub-Saharan Africa (excluding high income),ZF,SSA,2020,1472.99671243066,61.1661735295458,1168916989 232 | Sub-Saharan Africa (IDA & IBRD countries),T6,TSS,2020,1474.05531054261,61.1675270829902,1169015451 233 | Sub-Saharan Africa,ZG,SSF,2020,1474.05531054261,61.1675270829902,1169015451 234 | Sudan,SD,SDN,2020,577.795227050781,65.124,46789231 235 | Suriname,SR,SUR,2020,4755.39221710693,72.321,612317 236 | Sweden,SE,SWE,2020,52653.7565934247,82.3560975609756,10353442 237 | Switzerland,CH,CHE,2020,85897.7843338323,83,8638167 238 | Syrian Arab Republic,SY,SYR,2020,572.355289831901,71.944,21049429 239 | Tajikistan,TJ,TJK,2020,834.311715460625,68.605,9749310 240 | Tanzania,TZ,TZA,2020,1117.41528320313,66.771,60972798 241 | Thailand,TH,THA,2020,6985.64393892574,77.331,71641484 242 | Timor-Leste,TL,TLS,2020,1630.86935439232,66.919,1326053 243 | Togo,TG,TGO,2020,853.578220263722,61.123,8669720 244 | Tonga,TO,TON,2020,4700.44932800575,72.379,105704 245 | Trinidad and Tobago,TT,TTO,2020,15224.5083495616,72.645,1366725 246 | Tunisia,TN,TUN,2020,3548.65363663803,75.004,11974057 247 | Turkiye,TR,TUR,2020,8638.73903848102,76.525,83384680 248 | Turkmenistan,TM,TKM,2020,6592.60146027748,68.846,6949912 249 | Turks and Caicos Islands,TC,TCA,2020,17452.6578046047,77.499,44386 250 | Tuvalu,TV,TUV,2020,4976.11254109571,66.357,10399 251 | Uganda,UG,UGA,2020,845.766463890124,66.41,44457152 252 | Ukraine,UA,UKR,2020,3709.76928710938,73.283,44680014 253 | United Arab Emirates,AE,ARE,2020,37173.8754100281,81.936,9401038 254 | United Kingdom,GB,GBR,2020,40201.6812422928,80.331756097561,67081234 255 | United States,US,USA,2020,64411.3731779373,76.9804878048781,331526933 256 | Upper middle income,XT,,2020,8228.64336599657,75.2293474558881,2792212417 257 | Uruguay,UY,URY,2020,15789.6857422214,78.381,3398968 258 | Uzbekistan,UZ,UZB,2020,1978.28051860763,71.461,33586372 259 | Vanuatu,VU,VUT,2020,3042.98711598286,70.385,298858 260 | "Venezuela, RB",VE,VEN,2020,NA,72.369,28444077 261 | Viet Nam,VN,VNM,2020,3534.03953482647,75.383,98079191 262 | Virgin Islands (U.S.),VI,VIR,2020,39787.3741650202,79.819512195122,106290 263 | West Bank and Gaza,PS,PSE,2020,3233.56863835858,74.998,4803269 264 | World,1W,WLD,2020,10916.687666264,72.1827671974507,7856138789.5 265 | "Yemen, Rep.",YE,YEM,2020,559.564672794182,66.435,36134863 266 | Zambia,ZM,ZMB,2020,951.644316655957,63.361,19059395 267 | Zimbabwe,ZW,ZWE,2020,1730.41348946953,61.53,15526888 268 | --------------------------------------------------------------------------------