-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscalebar_use_sf_prettymapr.R
373 lines (348 loc) · 14 KB
/
scalebar_use_sf_prettymapr.R
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# small edit of code at
# https://github.com/paleolimbot/prettymapr/blob/master/R/scalebar.R
# (or https://github.com/paleolimbot/prettymapr/raw/master/R/scalebar.R)
# so that sf:: is now used for CRS conversions instead of sp::
# Calculates the geodesic distance between two points specified by radian
# latitude/longitude using the Haversine formula (hf)
.torad <- function(deg) {
deg*pi/180.0
}
.tolatlon <- function(x, y, epsg) {
# for lat/lon coordinates just return x and y
if(epsg == 4326) return(cbind(x, y))
# require sf for actual projections
if(!requireNamespace("sf")) stop("package 'sf' is required when coordinates are not WGS84 lat/lon")
coords <- data.frame(x=x, y=y)
spoints <- sf::st_as_sf(coords, coords=c("x","y"), crs=sf::st_crs(epsg))
spnew <- sf::st_transform(spoints, crs=sf::st_crs(4326))
as.vector(sf::st_coordinates(spnew))
}
.geodist <- function(x1, y1, x2, y2, epsg) {
lonlat1 <- .tolatlon(x1, y1, epsg)
lonlat2 <- .tolatlon(x2, y2, epsg)
long1 <- .torad(lonlat1[1])
lat1 <- .torad(lonlat1[2])
long2 <- .torad(lonlat2[1])
lat2 <- .torad(lonlat2[2])
R <- 6371009 # Earth mean radius [m]
delta.long <- (long2 - long1)
delta.lat <- (lat2 - lat1)
a <- sin(delta.lat/2)^2 + cos(lat1) * cos(lat2) * sin(delta.long/2)^2
c <- 2 * asin(min(1,sqrt(a)))
d = R * c
return(d) # Distance in m
}
.fromsi <- function(sivalue, unit) {
if(unit == "km") {
sivalue / 1000.0
} else if(unit == "m") {
sivalue
} else if(unit =="ft") {
sivalue * 3.28084
} else if(unit == "mi") {
sivalue / 1609.344051499
} else if(unit == "in") {
sivalue * 39.370079999999809672
} else if(unit == "cm") {
sivalue * 100.0
} else {
stop("Unrecognized unit: ", unit)
}
}
.tosi <- function(unitvalue, unit) {
if(unit == "km") {
unitvalue * 1000.0
} else if(unit == "m") {
unitvalue
} else if(unit =="ft") {
unitvalue / 3.28084
} else if(unit == "mi") {
unitvalue * 1609.344051499
} else if(unit == "in") {
unitvalue / 39.370079999999809672
} else if(unit == "cm") {
unitvalue / 100.0
} else {
stop("Unrecognized unit: ", unit)
}
}
#' Get Scale Bar Parameters
#'
#' Get default scale bar parameters based on the current plot (i.e. \code{par("usr")}).
#' The algorithm attempts to detect the best equally divisable distance to use for the
#' scale bar, and returns a \code{list} object with attributes that allow any type of
#' scale bar to be drawn. The only way to manipulate the values chosen by the algorithm
#' is to change the \code{widthhint} argument. For generic XY plots, pass \code{plotunit}.
#'
#' @param extents The plot extents
#' @param plotunit The unit which the current plot is plotted in, one of \code{cm},
#' \code{m}, \code{km}, \code{in}, \code{ft}, \code{mi}. or \code{latlon}. This
#' parameter is optional if \code{plotepsg} is passed.
#' @param plotepsg The projection of the current plot. If extents are valid lat/lons,
#' the projection is assumed to be lat/lon (EPSG:4326), or Spherical Mercator otherwise
#' (EPSG:3857). This is done to work seamlessly with OpenStreetMap packages.
#' @param widthhint The fraction of the plottable width which the scale bar should
#' (mostly) occupy.
#' @param unitcategory One of "metric" or "imperial"
#' @return a \code{list} of parameters: \code{$widthu} (width of the scalebar in human
#' readable units); \code{$unit} (the human readable unit); \code{$majordivu} (the size
#' of the divisions in human readable units); \code{$majordivs} (the number of divisions);
#' \code{$widthplotunit} (width of the scalebar in plotting units); \code{$majordivplotunit}
#' (the width of divisions in plotting units); \code{$labeltext} (label text); and \code{extents}
#' the user extents (\code{par('usr')}) that were used to calculate the parameters.
#' @export
#'
#' @examples
#' plot(1:5, 1:5, asp=1)
#' scalebarparams(plotunit="m")
#' \donttest{
#' library(maptools)
#' data(wrld_simpl)
#' plot(wrld_simpl, xlim=c(-66.86, -59.75), ylim=c(43, 47.3)) # Nova Scotia
#' scalebarparams()
#' }
#'
#' @seealso \link{addscalebar}
#'
#'
scalebarparams <- function(plotunit=NULL, plotepsg=NULL, widthhint=0.25, unitcategory="metric",
extents = graphics::par("usr")) {
# params check
if(!(unitcategory %in% c("metric", "imperial"))) stop("Unrecognized unitcategory: ", unitcategory)
if(is.null(plotepsg) && is.null(plotunit)) {
#check for valid lat/lon in extents
if(extents[1] >= -180 &&
extents[1] <= 180 &&
extents[2] >= -180 &&
extents[2] <= 180 &&
extents[3] >= -90 &&
extents[3] <= 90 &&
extents[4] >= -90 &&
extents[4] <= 90) {
message("Autodetect projection: assuming lat/lon (epsg 4326)")
plotepsg <- 4326
} else {
#else assume google mercator used by {OpenStreetMap} (epsg 3857)
message("Audotdetect projection: assuming Google Mercator (epsg 3857)")
plotepsg <- 3857
}
} else if(!is.null(plotunit)) {
if(plotunit=="latlon") {
plotepsg <- 4326
}
}
if(!is.null(plotepsg)) {
widthbottom <- .geodist(extents[1], extents[3], extents[2], extents[3], plotepsg)
midY <- mean(c(extents[3], extents[4]))
widthmiddle <- .geodist(extents[1], midY, extents[2], midY, plotepsg)
widthtop <- .geodist(extents[1], extents[4], extents[2], extents[4], plotepsg)
percentdiff <- (max(widthbottom, widthmiddle, widthtop) -
min(widthbottom, widthmiddle, widthtop)) / min(widthbottom, widthmiddle, widthtop)
if(percentdiff > .05) message("Scale on map varies by more than 5%, scalebar may be inaccurate")
widthm <- widthmiddle
mperplotunit <- widthmiddle/(extents[2]-extents[1])
} else {
heightm <- .tosi(extents[4] - extents[3], plotunit)
widthm <- .tosi(extents[2] - extents[1], plotunit)
mperplotunit <- .tosi(1.0, plotunit)
}
geowidthm <- widthm * widthhint
if(geowidthm < 1) {
scaleunits <- c("cm", "in")
} else if(geowidthm < 1600) {
scaleunits <- c("m", "ft")
} else {
scaleunits <- c("km", "mi")
}
# String unit = units[unitCategory] ;
if(unitcategory == "metric") {
unit <- scaleunits[1]
} else {
unit <- scaleunits[2]
}
# double widthHintU = Units.fromSI(geoWidthM, unit) ;
widthhintu <- .fromsi(geowidthm, unit)
# double tenFactor = Math.floor(Math.log10(widthHintU)) ;
tenfactor <- floor(log10(widthhintu))
# double widthInTens = Math.floor(widthHintU / Math.pow(10, tenFactor)) ;
widthintens <- floor(widthhintu / (10^tenfactor))
if(widthintens == 1) {
widthintens <- 10
tenfactor = tenfactor - 1 ;
} else if(widthintens == 7) {
widthintens <- 6
} else if(widthintens == 9) {
widthintens <- 8
}
if(widthintens < 6) {
majdivtens <- 1
} else {
majdivtens <- 2
}
# double widthU = widthInTens * Math.pow(10, tenFactor) ;
widthu <- widthintens * 10^tenfactor
# double majorDiv = majDivTens * Math.pow(10, tenFactor) ;
majordiv <- majdivtens * 10^tenfactor
# long majorDivs = Math.round(widthU / majorDiv) ;
majordivs <- round(widthu / majordiv)
# double widthPx = Units.toSI(widthU, unit) / mPerPixel ;
widthplotunit <- .tosi(widthu, unit) / mperplotunit
# double majorDivPx = widthPx / majorDivs ;
majordivplotunit <- widthplotunit / majordivs
# this.scaleParameters = new double[] {widthU, majorDiv, widthPx, majorDivPx} ;
params = list()
params$widthu <- widthu
params$unit <- unit
params$majordivu <- majordiv
params$majordivs <- majordivs
params$widthplotunit <- widthplotunit
params$majordivplotunit <- majordivplotunit
params$labeltext <- paste(as.integer(widthu), unit)
params$extents <- extents
# this.labelText = String.valueOf(Math.round(widthU)) + " " + unit ;
params
}
#' Raw Plot Scale Bar
#'
#' Just in case anybody is hoping to draw a custom scalebar, this is the
#' method used to plot it. If you don't know what this is, you should probably
#' be using \link{addscalebar}.
#'
#' @param x The position (user) to draw the scale bar
#' @param y The position (user) to draw the scale bar
#' @param ht The height(in user coordinates) to draw the scale bar
#' @param params Scalebar parameters as generated by \link{scalebarparams}
#' @param style One of \code{bar} or \code{ticks}
#' @param adj Where to align the scale bar relative to \code{x} and \code{y}
#' @param tick.cex If \code{style=="ticks"}, the height of interior ticks.
#' @param bar.cols A vector of color names to be repeated for a \code{bar}
#' style scalebar.
#' @param lwd Passed when drawing lines associated with the scalebar
#' @param linecol Passed when drawing lines associated with the scalebar
#'
#' @seealso \link{addscalebar}
#'
#' @export
#'
#'
plotscalebar <- function(x, y, ht, params, style="bar", adj=c(0,0), tick.cex=0.7,
bar.cols=c("black", "white"), lwd=1, linecol="black") {
wd <- params$widthplotunit
if(style=="bar") {
cols <- rep(bar.cols, params$majordivs/(length(bar.cols))+1)
for(i in 1:params$majordivs) {
graphics::rect(x-adj[1]*wd+(i-1)*params$majordivplotunit, y-adj[2]*ht+ht,
x-adj[1]*wd+i*params$majordivplotunit, y-adj[2]*ht, col=cols[i],
lwd=lwd, border=linecol)
}
} else if(style=="ticks") {
outerx <- c(x-adj[1]*wd,
x-adj[1]*wd,
x-adj[1]*wd+wd,
x-adj[1]*wd+wd)
outery <- c(y-adj[2]*ht+ht,
y-adj[2]*ht,
y-adj[2]*ht,
y-adj[2]*ht+ht)
graphics::lines(outerx, outery, lwd=lwd, col=linecol)
for(i in 2:params$majordivs) {
x1 <- x-adj[1]*wd+(i-1)*params$majordivplotunit
y1 <- y-adj[2]*ht
y2 <- y1+ht*tick.cex
graphics::lines(c(x1, x1), c(y1, y2), col=linecol, lwd=lwd)
}
} else {
stop("Invalid style specified to drawscalebar: ", style)
}
}
#' Auto Plot Scalebar
#'
#' Automatically determines the geographical scale of the plot and
#' draws a labelled scalebar.
#'
#' @param plotunit The unit which the current plot is plotted in, one of \code{cm},
#' \code{m}, \code{km}, \code{in}, \code{ft}, \code{mi}. or \code{latlon}. This
#' parameter is optional if \code{plotepsg} is passed.
#' @param plotepsg The projection of the current plot. If extents are valid lat/lons,
#' the projection is assumed to be lat/lon (EPSG:4326), or Spherical Mercator otherwise
#' (EPSG:3857). This is done to work seamlessly with OpenStreetMap packages.
#' @param widthhint The fraction of the plottable width which the scale bar should
#' (mostly) occupy.
#' @param unitcategory One of "metric" or "imperial"
#' @param htin Height (in inches) of the desired scale bar
#' @param padin A vector of length 2 determining the distance in inches between the scalebar
#' and the edge of the plottable area.
#' @param style One of "bar" or "ticks".
#' @param bar.cols If \code{style=="bar"}, the colors to be repeated to make the bar.
#' @param lwd The line width to use when drawing the scalebar
#' @param linecol The line color to use when drawing the scalebar
#' @param tick.cex If \code{style=="ticks"}, the height of interior ticks.
#' @param labelpadin The distance between the end of the scalebar and the label (inches)
#' @param label.cex The font size of the label
#' @param label.col The color of the label
#' @param pos Where to align the scalebar. One of "bottomleft", "bottomright", "topleft",
#' or "topright".
#'
#' @export
#'
#' @examples
#' plot(1:5, 1:5, asp=1)
#' addscalebar(plotunit="m")
#' \donttest{
#' library(maptools)
#' data(wrld_simpl)
#' plot(wrld_simpl, xlim=c(-66.86, -59.75), ylim=c(43, 47.3)) #Nova Scotia
#' addscalebar()
#'
#' #also works in non-lat/lon coordinate systems
#' addscalebar(plotepsg=3395) #specify plot is in mercator projection
#' addscalebar(plotepsg=26920) #specify plot is in UTM Zone 20N
#'
#' }
#'
addscalebar <- function(plotunit=NULL, plotepsg=NULL, widthhint=0.25, unitcategory="metric",
htin=0.1, padin=c(0.15, 0.15), style="bar", bar.cols=c("black", "white"),
lwd=1, linecol="black", tick.cex=0.7, labelpadin=0.08, label.cex=0.8,
label.col="black", pos="bottomleft") {
params <- scalebarparams(plotunit=plotunit, plotepsg=plotepsg, widthhint = widthhint,
unitcategory=unitcategory)
extents <- params$extents
bottomin <- graphics::grconvertY(extents[3], from="user", to="inches")
leftin <- graphics::grconvertX(extents[1], from="user", to="inches")
topin <- graphics::grconvertY(extents[4], from="user", to="inches")
rightin <- graphics::grconvertX(extents[2], from="user", to="inches")
ht <- graphics::grconvertY(bottomin+htin, from="inches", to="user") - extents[3]
paduser <- graphics::grconvertX(leftin+labelpadin, from="inches", to="user") - extents[1]
if(pos=="bottomleft") {
x <- graphics::grconvertX(leftin+padin[1], from="inches", to="user")
y <- graphics::grconvertY(bottomin+padin[2], from="inches", to="user")
adj <- c(0,0)
textadj <- c(0,0.5)
textx <- x+params$widthplotunit+paduser
texty <- y+0.5*ht
} else if(pos=="topleft") {
x <- graphics::grconvertX(leftin+padin[1], from="inches", to="user")
y <- graphics::grconvertY(topin-padin[2], from="inches", to="user")
adj <- c(0,1)
textadj <- c(0, 0.5)
textx <- x+params$widthplotunit+paduser
texty <- y-0.5*ht
} else if(pos=="topright") {
x <- graphics::grconvertX(rightin-padin[1], from="inches", to="user")
y <- graphics::grconvertY(topin-padin[2], from="inches", to="user")
adj <- c(1,1)
textadj <- c(1, 0.5)
textx <- x-params$widthplotunit-paduser
texty <- y-0.5*ht
} else if(pos=="bottomright") {
x <- graphics::grconvertX(rightin-padin[1], from="inches", to="user")
y <- graphics::grconvertY(bottomin+padin[2], from="inches", to="user")
adj <- c(1,0)
textadj <- c(1, 0.5)
textx <- x-params$widthplotunit-paduser
texty <- y+0.5*ht
}
plotscalebar(x, y, ht, params, adj=adj, style=style, lwd=lwd, linecol=linecol,
bar.cols=bar.cols, tick.cex=tick.cex)
graphics::text(textx, texty, params$labeltext, adj=textadj, cex=label.cex, col=label.col)
}