-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy path10-multilateration.Rmd
More file actions
executable file
·236 lines (193 loc) · 7.22 KB
/
Copy path10-multilateration.Rmd
File metadata and controls
executable file
·236 lines (193 loc) · 7.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
```{r, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
knitr::opts_chunk$set(eval = FALSE)
```
# Multilateration
This process is just like habitat use and grid search analysis, but instead of using the RSSI, it uses time difference of arrival instead to calculate the location of an animal.
You should use this method when your node grid is evenly spaced.
This code was developed by Dr. Kristina Paxton and Dr. Jessica Gorzo, and was adapted from the study below:
[Multilateration paper](https://pubmed.ncbi.nlm.nih.gov/35169450/)
## Load settings
### Reset R's brain, remove all previous objects
```{r}
rm(list=ls())
tagid = c("072A6633","2D4B782D") #"0C5F5CED"
timezone="UTC"
options(digits=9) # sets number after decimal
```
### Load libraries
```{r}
library(celltracktech)
```
### Re-create sample data from node calibration (Chapter 5)
```{r}
# create time window by reducing location precision or can input data with TestId column (user-defined window)
# load node calibration file from Chapter 5
# mytest <- read.csv("./calibration_2023_8_3_all.csv")
mytest <- read.csv("./data/Meadows V2/sidekick/calibration_2023_8_3_all.csv")
mytest$Time <- as.POSIXct(mytest$time_utc, tz="UTC")
# connect to database
con <- DBI::dbConnect(duckdb::duckdb(),
dbdir = "./data/Meadows V2/meadows.duckdb",
read_only = TRUE)
# filter for specific dates and for the specified tags in tag id
testdata <- tbl(con, "raw") |>
filter(time >= as.Date("2023-07-31") && time <= as.Date("2023-10-31")) |>
filter(tag_id %in% tagid) |>
collect()
# set node start and stop dates
start_buff = as.Date("2023-08-01", tz="UTC")
end_buff = as.Date("2023-08-07", tz="UTC")
nodehealth <- tbl(con, "node_health") |>
filter(time >= start_buff && time <= end_buff) |>
collect()
# disconnect from database
DBI::dbDisconnect(con)
# create dataframe of nodes and their locations (lat, lon)
nodes <- node_file(nodehealth)
```
## Isolate raw Received Signal Strength (RSS) data from Node network associated with Test Data
```{r}
combined_data <- data.setup(mytest,
testdata,
nodes,
tag_col = "tag_id",
tagid = "072A6633",
time_col = "Time",
timezone = "UTC",
x = "lon",
y = "lat",
loc_precision = 6,
fileloc = "./data/Meadows V2/meadows.duckdb",
filetype = "raw")
```
## Exponential Decay Function - Relationship between Distance and Tag RSS Values
```{r}
# Plot of the relationship between RSS and distance
ggplot(data = combined_data,
aes(x = distance,
y = avgRSS,
color = node_id)) +
geom_point(size = 2)
```

As distance increases, we see average RSS decreasing exponentially.
### Preliminary Exponential Decay Model - Determine starting values for the final model
* SSasvmp - self start for exponential model to find the data starting values
* Asvm - horizontal asymptote (when large values) - y values decay to this value
* R0 - numeric value when avgRSS (i.e., response variable) = 0
* lrc - natural logarithm of the rate constant (rate of decay)
```{r}
# preliminary model - non-linear sampling
exp.mod <- nls(avgRSS ~ SSasymp(distance,
Asym,
R0,
lrc),
data = combined_data)
# Summary of Model
summary(exp.mod)
# rate of decay
exp(coef(exp.mod)[["lrc"]])
```
### Final Exponential Decay Model
User provides self-starting values based on visualization of the data and values in the Preiliminary Model Output
exponential model formula: avgRSS ~ a * exp(-S * distance) + K
* a = intercept
* S = decay factor
* K = horizontal asymptote
```{r}
## ***** Variables to define for final model below - replace values below with values from exp.mod **** ##
a <- coef(exp.mod)[["R0"]]
S <- exp(coef(exp.mod)[["lrc"]])
K <- coef(exp.mod)[["Asym"]]
# Final Model
nls.mod <- nls(avgRSS ~ a * exp(-S * distance) + K,
start = list(a = a,
S = S,
K= K),
data = combined_data)
# Model Summary
summary(nls.mod)
# Model Coefficients
coef(nls.mod)
## Check the fit of the model and get predicted values
# Get residuals and fit of model and add variables to main table
combined_data$E <- residuals(nls.mod)
combined_data$fit <- fitted(nls.mod)
# Plot residuals by fit or distance
#ggplot(combined_data, aes(x = distance, y = E, color = node_id)) +
# geom_point(size = 2)
#ggplot(combined_data, aes(x = fit, y = E, color = node_id)) +
# geom_point(size = 2)
# Get model predictions
combined_data$pred <- predict(nls.mod)
## Plot with predicted line
ggplot(combined_data, aes(x = distance,
y = avgRSS,
color=node_id)) +
geom_point() +
geom_line(aes(y = pred), color="black", lwd = 1.25) +
scale_y_continuous(name = "RSS (dB)") +
scale_x_continuous(name = "Distance (m)") +
theme_classic()
```

```{r}
a <- unname(coef(nls.mod)[1])
S <- unname(coef(nls.mod)[2])
K <- unname(coef(nls.mod)[3])
combined_data <- estimate.distance(combined_data, K, a, S)
tile_url = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
testout <- combined_data[combined_data$TestId==0,]
leaflet() %>%
addTiles(
urlTemplate = tile_url,
options = tileOptions(maxZoom = 25)
) %>%
addCircleMarkers(
data = nodes,
lat = nodes$node_lat,
lng = nodes$node_lng,
radius = 5,
color = "cyan",
fillColor = "cyan",
fillOpacity = 0.5,
label = nodes$node_id
) %>%
addCircles(
data=testout,
lat = testout$node_lat,
lng = testout$node_lng,
radius = testout$distance,
color = "red",
#fillColor = "red",
fillOpacity = 0)
```

```{r}
no.filters <- trilateration_testdata_nofilter(combined_data)
RSS.FILTER <- c(-80, -85, -90, -95)
RSS.filters <- trilateration_testdata_rss_filter(combined_data, RSS.FILTER)
#DIST.FILTER <- c(315,500,750,1000)
# Calculate error of location estimates of each test location when Distance filters are applied prior to trilateration
#Dist.filters <- trilateration_testdata_distance_filter(combined_data, DIST.FILTER)
SLIDE.TIME <- 2
GROUP.TIME <- "1 min"
test_data <- testdata %>%
filter(time >= as.Date("2023-10-05") & time <= as.Date("2023-10-15")) %>%
filter(tag_id == "2D4B782D") %>%
collect()
# Function to prepare beep data for trilateration
# by estimating distance of a signal based on RSS values
beep.grouped <- prep.data(test_data,
nodes,
SLIDE.TIME,
GROUP.TIME,
K,
a,
S)
RSS.filter <- -95
location.estimates <- trilateration(beep.grouped, nodes, RSS.FILTER)
# this will take a while...
mapping(nodes, location.estimates)
```