<- read_lines("https://people.sc.fsu.edu/~jburkardt/datasets/states/state_capitals_name.txt")
capitals_names <- read_lines("https://people.sc.fsu.edu/~jburkardt/datasets/states/state_capitals_ll.txt") capitals_lat_long
Note: This was initially a collaborative project, but I made some adjustments independently. There are two GitHub links below, one which contains the initial project, and one that contains the finalized assignment. However, the final project’s code is listed here.
View the collaborative code on GitHub
Making the U.S.Capitals dataset
We first created a dataset that contains all of the capital names, as well as their latitude and longitude coordinates.
<- str_split(capitals_lat_long, "\\s+", simplify = TRUE) |>
latlon_df as.data.frame() |>
rename(state = V1, latitude = V2, longitude = V3) |>
mutate(state = trimws(state))
<- str_split(capitals_names, '"', simplify = TRUE) |>
capitals_df as.data.frame() |>
rename(state = V1, capital = V2) |>
select(-V3) |>
mutate(state = trimws(state))
<- left_join(latlon_df, capitals_df, by = "state")
full_capitals_dataset #Technically a "join" is not necessary, since the columns are ordered the same, but we were worried it was going to cause issues
<- full_capitals_dataset |>
full_capitals_dataset mutate(
latitude = as.numeric(latitude),
longitude = as.numeric(longitude)
)
Calling an API to get pass times
We then created a function that inputs latitudes and longitudes, and retrieves the next three times that the ISS passes over that location. Note that the API has a maximum of 72 hours. So, if the ISS does not pass over a location within 72 hours, it will return NA. Additionally, because this API is time dependent, re-running it after some time may produce new results.
Note: We used tca or “time of closest approach” as our pass time, but the API can also provide “start time” and “end time.” These are only about 10 minutes apart, however, so whichever value is chosen should not make a big difference.
<- function(lat, lon) {
get_pass_times #Check inputs: lat and lon should be numeric (I had to Google what ranges they could have, did not know they were on two different scales!)
if (!is.numeric(lat) || lat < -90 || lat > 90) {
warning("Invalid latitude. Must be a numeric value between -90 and 90.")
return(rep(NA, 3))
}if (!is.numeric(lon) || lon < -180 || lon > 180) {
warning("Invalid longitude. Must be a numeric value between -180 and 180.")
return(rep(NA, 3))
}
#Construct the API URL for the given latitude and longitude
<- paste0("https://api.g7vrd.co.uk/v1/satellite-passes/25544/", lat, "/", lon, ".json")
url <- GET(url) #Call the API
response
if (status_code(response) == 200) {
<- fromJSON(rawToChar(response$content)) #Convert raw JSON to usable form
data
if (!is.null(data$passes)) {
return(head(data$passes$tca, 3)) #Extract first three pass times (Ordered descending)
else {
} warning("No passes found in API response.")
}else {
} warning("API request failed or returned non-200 status.")
}
return(rep(NA, 3)) #Return 3 NAs if calling the API fails
}
We now take our function and apply it to every row, thus producing a dataframe which contains the top three pass times for each capital. Because we wanted to limit our dataset to U.S. state capitals, we removed Puerto Rico. Additionally, there was an issue with the initial dataframe that we read in. It included two District of Columbias, one with the correct coordinates, and one with incorrect coordinates. We made sure to remove the incorrect one from the datset.
<- full_capitals_dataset |>
capitals_all_passes mutate(pass_times = pmap(list(latitude, longitude), get_pass_times))
#pass_times is a list of values
<- capitals_all_passes |>
capitals_with_passes unnest_wider(pass_times, names_sep = "_") |> #Creates pass_times_1, pass_times_2, pass_times_3
rename(
pass_time_1 = pass_times_1,
pass_time_2 = pass_times_2,
pass_time_3 = pass_times_3
|>
) filter(!state %in% c('US', 'PR'))
Making the ISS passing visual
The following takes our dataset and creates a visualization.
<- capitals_with_passes |>
capitals_with_passes arrange(is.na(pass_time_1), pass_time_1) |>
rowwise() |>
mutate(
pop_up_html = if (is.na(pass_time_1)) {
paste0("<b>", capital, "</b><br/>",
"ISS will not pass over this location in the next 72 hours")
else {
} paste0("<b>", toupper(capital), "</b><br/>",
"Next Three ISS Passtimes:<br/>",
# read way many articles much about date time formatting and functions, but so happy with how it looks now
# https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings
format(as_datetime(pass_time_1), "%B %d, %Y %I:%M %p %Z"),
"<br/>",
if_else(!is.na(pass_time_2),
format(as_datetime(pass_time_2), "%B %d, %Y %I:%M %p %Z")),
("No second pass"),
"<br/>",
if_else(!is.na(pass_time_3), (format(as_datetime(pass_time_3), "%B %d, %Y %I:%M %p %Z")),
"No third pass"))
},
label_html = htmltools::HTML((paste0(
"<b>", toupper(capital), "<b><br/>",
if_else(is.na(pass_time_1),
"ISS will not pass over this location in the next 72 hours",
paste("Next ISS Passtime:",
format(as_datetime(pass_time_1), "%B %d, %Y %I:%M %p %Z"))
))))|>
) ungroup()
# created new columns in the dataframe, because the html options were having issues running within the leaflet plot and to make the code for the plot cleaner
#dropping NAs from the path
<- capitals_with_passes|> filter(!is.na(pass_time_1)) caps
<- icons(
iss_icon iconUrl = here::here("stat-541-posts/2025-05-19-lab-7/pngtree-space-station-probe-icon-png-image_4687961.png"),
iconWidth = 30,
iconHeight = 30
)
leaflet(capitals_with_passes) |> addTiles() |>
addMarkers(lng = ~longitude,
lat = ~latitude,
icon = iss_icon,
label = ~label_html,
popup = ~pop_up_html
# so excited to have discovered the use of the tilda('~')
|>
) addPolylines(lng = caps$longitude,
lat = caps$latitude,
color = "pink", #I made the lines pink because it's coquette
opacity = 1)
Looks so good!