├── 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 | 
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 |
--------------------------------------------------------------------------------