library(httr)
library(jsonlite)
library(tidyverse)
library(rvest)
library(oce)
<- function(lat, lon, API_key){
weather <- paste0("api.openweathermap.org/data/2.5/weather?lat=", lat, "&lon=", lon, "&appid=", API_key, "&units=metric")
url
<- GET(url)
rep
<- fromJSON(content(rep, "text"))
table
# The weather API don't provide moon phase so I compute it with Oce package
<- round(moonAngle(t = Sys.Date(),
moon_phase longitude = as.numeric(lon),
latitude = as.numeric(lat))$illuminatedFraction,
3)
<- data.frame(Air_temp = table$main$temp,
current.weather.info Weather = table$weather$main,
Atm_pres = table$main$pressure,
Wind_str = table$wind$speed,
Wind_dir = table$wind$deg,
Cloud_cover = table$clouds$all,
PrecipInt = ifelse(is.null(table$rains$`1h`), 0, table$rains$`1h`),
Moon = moon_phase)
return(current.weather.info)
}
In this previous post, I presented the shiny application I developed to record data about my fishing session. In today’s post, I will present briefly the changes and updates I made to improve the application. Here are the main changes:
Weather API: the API I was using (Dark Sky) stopped furnishing free data. I changed for Openweathermap and I needed to update the functions to gather the same information as before.
River flow: the first version of the application did not collect any data about the flow of the river in which I am fishing. However I am convinced that the river flow before the fishing session might have an impact on the presence of seabass. I therefore created a web scrapping function that collect the flow of the Nive and the Adour (main fishing rivers).
Collecting lure data: in the first version, data about fishing lures I used were only collected when a fish was caught. However I did not have data about how long I used the lure before catching a fish. The new version of the application now collects data about lure and how often I change lure.
Weather API
Small changes were made to adapt the former weather function to the new weather API. As this new API do not furnish moon phase data, I decided to compute the moon phase with the oce package:
River flow
I wrote functions to scrap information about the flows of the rivers in which I fish the most on a french website:
# Get and prepare the flow data
<- function(link){
get_Qdata <- fromJSON(content(GET(link), "text"))
table <- table$Serie$ObssHydro
table <- as.data.frame(table)
table $DtObsHydro <- sub("T", " ", table$DtObsHydro)
table$DtObsHydro <- substr(table$DtObsHydro, start = 1, stop = 19)
table<- data.frame(Date = seq.POSIXt(as.POSIXct(range(table$DtObsHydro)[1],'%m/%d/%y %H:%M:%S'),
ts as.POSIXct(range(table$DtObsHydro)[2],'%m/%d/%y %H:%M:%S'), by="hour"))
$DtObsHydro <- as.POSIXct(table$DtObsHydro, format = "%Y-%m-%d %H:%M:%S")
table
<- full_join(table, ts, by = c("DtObsHydro" = "Date")) %>% arrange(DtObsHydro)
table return(table)
}
# Main function to collect river flow
<- function(){
river_flow # Url of website to scrap:
<- "https://www.vigicrues.gouv.fr/services/station.json/index.php"
url_index
<- GET(url_index)
rep
<- fromJSON(content(rep, "text"))$Stations%>%
table_index na.omit()
# I need to add the flow of several rivers to get the flow of the rivers I am interested in:
<- table_index %>%
stations filter(LbStationHydro %in% c("Pontonx-sur-l'Adour", "St-Pandelon", "Artiguelouve", "Escos",
"Aïcirits [St-Palais]", "Cambo-les-Bains"))
<- "http://www.vigicrues.gouv.fr/services/observations.json?CdStationHydro="
base_url <- "&FormatDate=iso"
height_url <- "&GrdSerie=Q"
Q_url
<- stations %>%
stations mutate(WL_link = paste0(base_url, CdStationHydro, height_url),
Q_link = paste0(WL_link, Q_url))
<- lapply(stations$Q_link,
data_Q function(x){get_Qdata(x)})
<- suppressWarnings(Reduce(function(...) merge(..., all = TRUE, by = "DtObsHydro"),
data_Q
data_Q))
names(data_Q) <- c("Date", stations$LbStationHydro)
<- data_Q %>%
data_Q mutate(hour_of_day = format(Date, "%Y-%m-%d %H"))
<- aggregate(.~hour_of_day, data = data_Q, mean, na.rm = TRUE, na.action = na.pass)
data_Q
<- imputeTS::na_interpolation(data_Q, option = "linear")
data_Q
<- data_Q %>%
final_data mutate(Adour = `Pontonx-sur-l'Adour` + `Aïcirits [St-Palais]` + Artiguelouve + Escos + `St-Pandelon`,
Date = as.POSIXct(hour_of_day, tryFormats = "%Y-%m-%d %H")) %>%
select(Date, `Cambo-les-Bains`, Adour) %>%
rename(Nive = `Cambo-les-Bains`)
<- data.frame("Nive_c" = final_data[nrow(final_data), 2],
Cur_flow "Adour_c" = final_data[nrow(final_data), 3])
<- cbind(Cur_flow, final_data) %>%
final_data nest(Ts_flow = c(Date, Nive, Adour)) %>%
mutate(Ts_flow = paste(Ts_flow))
return(final_data)
}
Shiny application
A simplified graph of the new application is showed below:
UI side
The UI side did not change that much, I only removed the tab that displayed fishing data on a map because I wasn’t using this feature too much:
# Load libraries
library(shiny)
library(shinyWidgets)
library(googlesheets)
library(miniUI)
library(leaflet)
library(rdrop2)
Sys.setenv(TZ="Europe/Paris")
#Import the functions for weather API and webscrapping
suppressMessages(source("api_functions.R"))
# Load the dropbox token :
<<- readRDS("token.rds")
token
# Minipage for small screens
<- miniPage(tags$script('$(document).ready(function () {
ui navigator.geolocation.getCurrentPosition(onSuccess, onError);
function onError (err) {
Shiny.onInputChange("geolocation", false);
}
function onSuccess (position) {
setTimeout(function () {
var coords = position.coords;
console.log(coords.latitude + ", " + coords.longitude);
Shiny.onInputChange("geolocation", true);
Shiny.onInputChange("lat", coords.latitude);
Shiny.onInputChange("long", coords.longitude);
}, 1100)
}
});'),
gadgetTitleBar("Catch them all", left = NULL, right = NULL),
miniTabstripPanel(
miniTabPanel("Session", icon = icon("sliders"),
miniContentPanel(uiOutput("UI_sess", align = "center"),
uiOutput("UI", align = "center"))
)
)
)
Server side
Several changes were made in the server side to collect data about the lures I used. Now, each time I change my fishing lure, I fill a small form to collect the lure characteristics and it adds a line in a third csv file:
<- function(input, output, session){
server
observeEvent(input$go ,{
# Read the csv file containing information about fishing session. If a session is running,
# display the UI that allows the user to input data about the fish caught. If a session is not started,
# display a button to start the session and small survey on lure characteristics.
<<- drop_read_csv("/app_peche/session1.csv", header = T, stringsAsFactors = F, dtoken = token)
dat
# Reactive UI
$UI <- renderUI({
output
if(!is.na(rev(dat$End)[1])){
# We now indicate what type of lure we use at the beginning of the session:
tagList(
selectInput("lure1",
label = "Type de leurre",
choices = list("Shad" = "shad",
"Slug" = "slug",
"Jerkbait" = "jerkbait",
"Casting jig" = "jig",
"Topwater" = "topwater"),
selected = "shad",
selectize = FALSE),
selectInput("color_lure1",
label = "Couleur du leurre",
choices = list("Naturel" = "naturel",
"Sombre" = "sombre",
"Clair" = "clair",
"Flashy" = "flashy" ),
selected = "naturel",
selectize = FALSE),
selectInput("length_lure1",
label = "Taille du leurre",
choices = list("Petit" = "petit",
"Moyen" = "moyen",
"Grand" = "grand"),
selected = "petit",
selectize = FALSE),
actionButton("go","Commencer session !"))
else{
}
tagList(actionButton("go","End session"))
}
})
$UI_sess <- renderUI({
output
if(!is.na(rev(dat$End)[1])){
tagList(textInput("comments", label = "Commentaire avant le début?", value = "NA"))
else{
}$catch
input$lure
inputtagList(
selectInput("lure_type",
label = "Type de leurre",
choices = list("Shad" = "shad",
"Slug" = "slug",
"Jerkbait" = "jerkbait",
"Casting jig" = "jig",
"Topwater" = "topwater"),
selected = "shad",
selectize = FALSE),
selectInput("color_lure",
label = "Couleur du leurre",
choices = list("Naturel" = "naturel",
"Sombre" = "sombre",
"Clair" = "clair",
"Flashy" = "flashy" ),
selected = "naturel",
selectize = FALSE),
selectInput("length_lure",
label = "Taille du leurre",
choices = list("Petit" = "petit",
"Moyen" = "moyen",
"Grand" = "grand"),
selected = "petit",
selectize = FALSE),
actionButton("lure",
label = "Changer de leurre!"),
br(),
br(),
h4("Ajouter une capture"),
selectInput("species",
label = "Espèces",
choices = list("Bar" = "bar",
"Bar moucheté" = "bar_m",
"Alose" = "alose",
"Maquereau" = "maquereau",
"Chinchard" = "chinchard"),
selected = "bar"),
sliderInput("length",
label = "Taille du poisson",
value = 25,
min = 0,
max = 80,
step = 1),
actionButton("catch","Rajoutez cette capture aux stats!"),
br(),
br(),
textInput("comments1", label = h4("Commentaire avant la fin ?"), value = "NA")
)
}
})ignoreNULL = F)
},
#If the button is pushed, create the line to be added in the csv file.
observeEvent(input$go,{
# Two outcomes depending if the session starts or ends. This gives the possibility
# to the user to add a comment before starting the session or after ending the session
if(!is.na(rev(dat$End)[1])){
#Tide + geoloc + Weather
<- tide()
c_tide <- c(input$lat,input$long)
geoloc <- weather(lat = geoloc[1], lon = geoloc[2])
current.weather.info <- river_flow()
river.flow
<- c(rev(dat$Session)[1] + 1)
n_ses <- as.character(as.POSIXct(Sys.time()))
time_beg <- input$comments
comment <<- cbind(data.frame(n_ses,
dat.f
time_beg,NA,
2],
geoloc[1]),
geoloc[
current.weather.info,
c_tide,
river.flow,
comment)names(dat.f) <- names(dat)
print(dat.f)
<- rbind(dat, dat.f)
final_dat
<- drop_read_csv("/app_peche/lure.csv",
lure header = T,
stringsAsFactors = F,
dtoken = token)
<- data.frame(n_ses = n_ses,
new_lure time = as.character(as.POSIXct(Sys.time())),
type_lure = input$lure1,
color_lure = input$color_lure1,
length_lure = input$length_lure1)
<- rbind(lure,
new_df
new_lure)
write_csv(as.data.frame(new_df), "lure.csv")
drop_upload("lure.csv", path = "App_peche", mode = "overwrite", dtoken = token)
else{
}
$End[nrow(dat)] <- as.character(as.POSIXct(Sys.time()))
dat$Comments[nrow(dat)] <- paste(dat$Comments[nrow(dat)], "/", input$comments1)
dat<- dat
final_dat
}
# Write csv in temporary files of shiny server
write_csv(as.data.frame(final_dat), "session1.csv")
# Upload it to dropbox account
drop_upload("session1.csv", path = "App_peche", mode = "overwrite", dtoken = token)
})
# Add a line to the catch csv file whenever a fish is caught
observeEvent(input$catch,{
<- drop_read_csv("/app_peche/catch1.csv", header = T, stringsAsFactors = F, dtoken = token)
caugth
<- data.frame(n_ses = dat$Session[nrow(dat)],
catch time = as.character(as.POSIXct(Sys.time())),
species = input$species,
length = input$length)
<- rbind(caugth,catch)
b
write_csv(as.data.frame(b), "catch1.csv")
drop_upload("catch1.csv", path = "App_peche", mode = "overwrite", dtoken = token)
})
observeEvent(input$lure,{
<- drop_read_csv("/app_peche/lure.csv",
lure header = T,
stringsAsFactors = F,
dtoken = token)
<- data.frame(n_ses = dat$Session[nrow(dat)],
new_lure time = as.character(as.POSIXct(Sys.time())),
type_lure = input$lure_type,
color_lure = input$color_lure,
length_lure = input$length_lure)
<- rbind(lure,
new_df
new_lure)
write_csv(as.data.frame(new_df), "lure.csv")
drop_upload("lure.csv", path = "App_peche", mode = "overwrite", dtoken = token)
}) }
Conclusion
I have tested this new application during two fishing sessions and it has been working like a charm. I can’t wait to present you my findings at the end of this fishing season !