MODISTransects.R 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. MODISTransects <-
  2. function(LoadData, FileSep = NULL, Product, Bands, Size, SaveDir = ".", StartDate = FALSE, TimeSeriesLength = 0)
  3. {
  4. # Define:
  5. # Data are gridded in equal-area tiles in a sinusoidal projection. Each tile consists of a 1200x1200 km data
  6. # array of pixels at a finer resolution (see http://modis-land.gsfc.nasa.gov/MODLAND_grid.html).
  7. LONG_EQTR_M = 111.2 * 1000
  8. if(!is.object(LoadData) & !is.character(LoadData)){
  9. stop("Data is incorrectly specified. Must either be the name of an object in R, or a file path character string.")
  10. }
  11. # Load data of locations; external data file, or an R object.
  12. if(is.object(LoadData)) dat <- data.frame(LoadData)
  13. if(is.character(LoadData)){
  14. if(!file.exists(LoadData)) stop("Character string input for LoadData does not resemble an existing file path.")
  15. if(is.null(FileSep)) stop("Data is a file path, the files delimiter (FileSep) also needed.")
  16. dat <- read.delim(LoadData, sep = FileSep)
  17. }
  18. # Check input dataset has variables named as necessary.
  19. if(!any(names(dat) == "transect") | !any(names(dat) == "start.lat") |
  20. !any(names(dat) == "end.lat") | !any(names(dat) == "start.long") |
  21. !any(names(dat) == "end.long") | !any(names(dat) == "end.date")){
  22. stop("Could not find some information that is necessary. May either be missing or incorrectly named.
  23. See ?MODISTransects for help on data requirements. If data file is loaded, make sure FileSep is sensible.")
  24. }
  25. # Check SaveDir matches an existing directory.
  26. if(!file.exists(SaveDir)) stop("Character string input for SaveDir argument does not resemble an existing file path.")
  27. # Check argument inputs are sensible.
  28. # If the Product input does not match any product codes in the list output from GetProducts(), stop with error.
  29. if(!any(Product == GetProducts())) stop("Product entered does not match any available products; see ?GetProducts.")
  30. # If the Bands input does not match with the Product input, stop with error.
  31. band.test <- lapply(Bands, function(x) !any(x == GetBands(Product)))
  32. if(any(band.test == TRUE)) stop("A Band does not match Product; see ?GetBands for bands within each product.")
  33. # If Size is not two dimensions or are not integers (greater than expected after rounding, with tolerance around
  34. # computing precision), stop with error.
  35. if(!is.numeric(Size)) stop("Size should be numeric class. Two integers.")
  36. if(length(Size) != 2) stop("Size input must be a vector of integers, with two elements.")
  37. if(abs(Size[1] - round(Size[1])) > .Machine$double.eps^0.5 | abs(Size[2] - round(Size[2])) > .Machine$double.eps^0.5){
  38. stop("Size input must be integers.")
  39. }
  40. if(!is.logical(StartDate)) stop("StartDate must be logical.")
  41. # Year or posixt date format?
  42. Year <- FALSE
  43. POSIXt <- FALSE
  44. char.compatible <- as.character(dat$end.date)
  45. if(!is.character(char.compatible) | all(is.na(char.compatible)) & any(nchar(char.compatible) != 4)) POSIXt <- TRUE
  46. posix.compatible <- try(as.POSIXlt(dat$end.date), silent=TRUE)
  47. if(class(posix.compatible) == "try-error") Year <- TRUE
  48. if(!Year & !POSIXt) stop("Dates in LoadDat not recognised as years or as POSIXt format.")
  49. if(Year & POSIXt) stop("Dates in LoadDat recognised as both year and POSIXt formats.")
  50. # Check the start dates are valid.
  51. if(StartDate){
  52. if(Year){
  53. char.compatible <- as.character(dat$start.date)
  54. if(!is.character(char.compatible) | all(is.na(char.compatible))){
  55. stop("Year date format detected, but start.date are not compatible with numeric class.")
  56. }
  57. if(any(nchar(dat$start.date) != 4)) stop("start.date is not matching year format.")
  58. } else {
  59. posix.compatible <- try(as.POSIXlt(dat$start.date), silent = TRUE)
  60. if(class(posix.compatible) == "try-error") stop("start.date are not all in standard POSIXt format. See ?POSIXt.")
  61. }
  62. }
  63. # Check latitude and longitude inputs are valid coordinate data.
  64. # Check for missing lat/long data
  65. if(any(is.na(dat$start.lat) != is.na(dat$start.long) | is.na(dat$start.lat) != is.na(dat$end.lat) |
  66. is.na(dat$start.lat) != is.na(dat$end.long) | is.na(dat$start.lat) != is.na(dat$end.date))) {
  67. stop("Not equal amount of lats, longs, and dates: there must be locations with incomplete time-series information.")
  68. }
  69. if(abs(dat$start.lat) > 90 || abs(dat$start.long) > 180 || abs(dat$end.lat) > 90 || abs(dat$end.long) > 180){
  70. stop("Detected some lats or longs beyond the range of valid coordinates.")
  71. }
  72. if(StartDate){
  73. if(!any(names(dat) == "start.date")) stop("StartDate == TRUE, but start.date not detected. See ?MODISTransects.")
  74. if(any(is.na(dat$start.lat) != is.na(dat$start.date))) stop("Not all coordinates have a corresponding start date.")
  75. }
  76. # Work out actual width of each pixel in the MODIS projection.
  77. cell.size.dates <- GetDates(Lat = LoadData$start.lat[1], Long = LoadData$start.long[1], Product = Product)[1:2]
  78. cell.size <- as.numeric(unname(
  79. GetSubset(Lat = LoadData$start.lat[1], Long = LoadData$start.long[1], Product = Product, Band = Bands[1],
  80. StartDate = cell.size.dates[1], EndDate = cell.size.dates[2], KmAboveBelow = 0, KmLeftRight = 0)$pixelsize[[1]]
  81. ))
  82. # Find all unique transects to download pixels for.
  83. t.dat <- dat[!duplicated(dat$transect), ]
  84. cat("Found", nrow(t.dat), "transects. Downloading time-series sets for each transect...\n")
  85. # Loop that reiterates download for each transect.
  86. for(i in 1:nrow(t.dat)){
  87. # Find the distance, in decimal degrees between the start and end of the transect.
  88. delta.lat <- t.dat$end.lat[i] - t.dat$start.lat[i]
  89. delta.long <- round(t.dat$end.long[i] - t.dat$start.long[i], digits = 5)
  90. # Work out how far in metres is the latitudinal difference between start and end locations.
  91. lat.metres <- delta.lat * LONG_EQTR_M
  92. # Determine the curvature angle from the latitude to calculate distance between one longitude at transect location.
  93. lat.rad <- median(c(t.dat$start.lat[i], t.dat$end.lat[i])) * (pi / 180)
  94. # Work out how far in metres is the longitudinal difference between start and end locations.
  95. long.metres <- delta.long * (LONG_EQTR_M * cos(lat.rad))
  96. # Work out the actual length of the transect.
  97. transect <- sqrt((lat.metres^2) + (long.metres^2))
  98. # Work out how many points can be equally spaced (i.e. how many pixels) between the start and end coordinates.
  99. num.points <- transect / cell.size
  100. # Work out the lat and long distances of the equally spaced points.
  101. lat.increment <- round(delta.lat / num.points, digits = 5)
  102. long.increment <- round(delta.long / num.points, digits = 5)
  103. lat <- t.dat$start.lat[i]
  104. long <- round(t.dat$start.long[i], digits = 5)
  105. # Take the start lat & long and interpolate new lat & long, using lat & long increments, until the end lat & long.
  106. # Produces vector of lats and long for all pixels along transect for time-series information in MODISSubsets.
  107. if(lat.increment > 0){
  108. if(long.increment > 0){
  109. while(lat[length(lat)] <= (t.dat$end.lat[i] - lat.increment) & long[length(long)] <=
  110. (round(t.dat$end.long[i], digits=5) - long.increment)){
  111. lat <- c(lat, round(lat[length(lat)] + lat.increment, digits = 5))
  112. long <- c(long, round(long[length(long)] + long.increment, digits = 5))
  113. }
  114. } else {
  115. while(lat[length(lat)] <= (t.dat$end.lat[i] - lat.increment) & long[length(long)] >=
  116. (round(t.dat$end.long[i], digits=5) - long.increment)){
  117. lat <- c(lat, round(lat[length(lat)] + lat.increment, digits = 5))
  118. long <- c(long, round(long[length(long)] + long.increment, digits = 5))
  119. }
  120. }
  121. } else {
  122. if(long.increment > 0){
  123. while(lat[length(lat)] >= (t.dat$end.lat[i] - lat.increment) & long[length(long)] <=
  124. (round(t.dat$end.long[i], digits=5) - long.increment)){
  125. lat <- c(lat, round(lat[length(lat)] + lat.increment, digits = 5))
  126. long <- c(long, round(long[length(long)] + long.increment, digits = 5))
  127. }
  128. } else {
  129. while(lat[length(lat)] >= (t.dat$end.lat[i] - lat.increment) & long[length(long)] >=
  130. (round(t.dat$end.long[i], digits=5) - long.increment)){
  131. lat <- c(lat, round(lat[length(lat)] + lat.increment, digits = 5))
  132. long <- c(long, round(long[length(long)] + long.increment, digits = 5))
  133. }
  134. }
  135. } # End of if statements that correctly interpolate points along transect line.
  136. # Write vector of end dates & IDs of each transect point to be used for time-series information in MODISSubsets.
  137. end.date <- rep(t.dat$end.date[i], length(lat))
  138. ID <- paste("Transect", t.dat$transect[i], "Point", 1:length(lat), sep = "")
  139. # Organise time-series information, with new by-transect IDs for each pixel, for input into MODISSubsets call
  140. # with optional start date as well as end date.
  141. if(StartDate){
  142. start.date <- rep(t.dat$start.date[i], length(lat))
  143. t.subset <- data.frame(ID, lat, long, start.date, end.date)
  144. } else {
  145. t.subset <- data.frame(ID, lat, long, end.date)
  146. }
  147. #####
  148. # Do some error checking of pixels in transect before requesting data in MODISSubsets function call.
  149. xll <- vector(mode = "numeric", length = nrow(t.subset))
  150. yll <- vector(mode = "numeric", length = nrow(t.subset))
  151. date.for.xy <- GetDates(t.subset$lat[1], t.subset$long[1], Product)[1]
  152. for(n in 1:nrow(t.subset)){
  153. t.point <- GetSubset(t.subset$lat[n], t.subset$long[n], Product,
  154. Bands[1], date.for.xy, date.for.xy, 0, 0)
  155. xll[n] <- as.numeric(as.character(t.point$xll))
  156. yll[n] <- as.numeric(as.character(t.point$yll))
  157. }
  158. # Check which pixels have the same x or y as the previous pixel.
  159. check.equal.x <- signif(xll[1:length(xll) - 1], digits = 6) == signif(xll[2:length(xll)], digits = 6)
  160. check.equal.y <- signif(yll[1:length(yll) - 1], digits = 5) == signif(yll[2:length(yll)], digits = 5)
  161. # From remaining pixels, check if they are +/- 1 pixel width (i.e. adjacent pixel) away.
  162. check.new.x <- ifelse(xll[which(!check.equal.x)] < xll[which(!check.equal.x) + 1],
  163. round(xll[which(!check.equal.x)]) == round(xll[which(!check.equal.x) + 1] - cell.size),
  164. round(xll[which(!check.equal.x)]) == round(xll[which(!check.equal.x) + 1] + cell.size))
  165. check.new.y <- ifelse(yll[which(!check.equal.y)] < yll[which(!check.equal.y) + 1],
  166. round(yll[which(!check.equal.y)]) == round(yll[which(!check.equal.y) + 1] - cell.size),
  167. round(yll[which(!check.equal.y)]) == round(yll[which(!check.equal.y) + 1] + cell.size))
  168. if(!all(check.new.x) | !all(check.new.y)){
  169. # Check if unaccounted for distance between pixels can be attributed to rounding error or MODIS proj uncertainty.
  170. check.error.x <- ifelse(xll[which(!check.equal.x)] < xll[which(!check.equal.x) + 1],
  171. signif(xll[which(!check.equal.x) + 1] - xll[which(!check.equal.x)], digits = 3) == signif(cell.size, digits = 3),
  172. signif(xll[which(!check.equal.x) + 1] - xll[which(!check.equal.x)], digits = 3) == -signif(cell.size, digits = 3))
  173. check.error.y <- ifelse(yll[which(!check.equal.y)] < yll[which(!check.equal.y) + 1],
  174. signif(yll[which(!check.equal.y) + 1] - yll[which(!check.equal.y)], digits = 3) == signif(cell.size, digits = 3),
  175. signif(yll[which(!check.equal.y) + 1] - yll[which(!check.equal.y)], digits = 3) == -signif(cell.size, digits = 3))
  176. # If differences between pixel > expected from checks, abort and produce error stating there are gaps in transect.
  177. if(any(!check.error.x) | any(!check.error.y)) stop("Error: Gap in transect pixels")
  178. }
  179. # Run MODISSubsets to retrieve subset for this transect of pixels.
  180. MODISSubsets(LoadDat = t.subset, Products = Product, Bands = Bands, Size = Size, SaveDir = SaveDir,
  181. StartDate = StartDate, TimeSeriesLength = TimeSeriesLength, Transect = TRUE)
  182. } # End of loop that reiterates download for each transect.
  183. }