MStripeCorrection.F90 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. ! First include the set of model-wide compiler flags
  2. #include "tm5.inc"
  3. #define TRACEBACK write (gol,'("in ",a," (",a,i6,")")') rname, __FILE__, __LINE__ ; call goErr
  4. #define IF_NOTOK_RETURN if (status/=0) then; TRACEBACK; return; end if
  5. #define IF_ERROR_RETURN if (status> 0) then; TRACEBACK; return; end if
  6. #define IF_NOTOK_MDF(action) if (status/=0) then; TRACEBACK; action; call MDF_CLose(fid,status); status=1; return; end if
  7. ! First include the set of model-wide compiler flags
  8. ! #include "tm5.inc"
  9. module MStripeCorrection
  10. ! Apply stripe correction for OMI/TROPOMI
  11. !
  12. ! Steps:
  13. ! 1. Read the stripe correction file, which contains:
  14. ! - the mean stripe correction over the past period (roughly 1 week)
  15. ! - the latest strip correction of the previous day
  16. ! 2. Apply this to the slant column NO2 DOAS results
  17. ! 3. After the vertical column has been computed:
  18. ! - Determine if the orbit is over the pacific
  19. ! - If yes, then update the stripe correction, and write to file
  20. ! - (Apply the new stripe correction to the SCD, VCD)
  21. !
  22. !
  23. !
  24. ! Henk Eskes, KNMI, March-April 2016
  25. use GO, only : gol, goErr
  26. use MTObsTrack, only : TObsTrack
  27. use datetime, only : date2tau, tau2date
  28. ! netcdf input/output makes use of the MDF module
  29. use MDF
  30. implicit none
  31. private
  32. public :: ReadStripeCorrection, ComputeStripeCorrection
  33. public :: stripeCorr, stripeCorrAvailable, stripeCorrDate
  34. public :: stripeCorr_averaging_time
  35. ! --- Storage of the stripe correction data during the run ---
  36. ! This (public) array stores the stripe correction = mean(scd - scd_model)
  37. ! which is an average over the past stripeCorr_averaging_time days
  38. real, dimension(:), allocatable :: stripeCorr
  39. ! This (private) array stores the last stripe correction determined
  40. real, dimension(:), allocatable :: stripeCorr_last
  41. ! Have the stripe correction parameters been read ?
  42. ! should be .true. when the stripCorr is applied
  43. logical :: stripeCorrAvailable = .false.
  44. ! Date for which the stripe correction was determined
  45. integer, dimension(6) :: stripeCorrDate = (/ 1000, 1, 1, 0, 0, 0 /)
  46. ! This number defines over what period the stripe correction is averaged
  47. ! If set to 1.0, stripeCorr = stripeCorr_last
  48. ! Overwritten by value from the rc file
  49. real :: stripeCorr_averaging_time = 7.0 ! in days
  50. ! Do we start with a new stripe correction (missing file)
  51. logical :: stripeCorr_new = .true.
  52. ! Debug print statements?
  53. logical :: debug_stripe = .true.
  54. ! Name of the stripecorr files
  55. character(len=*), parameter :: StripeCorrFilenameBase = "NO2_StripeCorrection_"
  56. ! Orbit within this window are used for determining the stripe correction
  57. real, parameter :: pacific_lonmin = -163.0
  58. real, parameter :: pacific_lonmax = -137.0
  59. ! Minimum number of valid pixels along track (stripe correction = undefined otherwise)
  60. integer, parameter :: minNrValidPixels = 100
  61. ! real, parameter :: undef = -1.0e30
  62. real, parameter :: nf_fill_float = 9.9692099683868690e+36
  63. ! attributes of the files
  64. character(len=*), parameter :: dataset_author = 'Henk Eskes'
  65. character(len=*), parameter :: institution = 'KNMI'
  66. ! Module name
  67. character(len=*), parameter :: mname = 'MStripeCorrection'
  68. contains
  69. subroutine ReadStripeCorrection ( filedir, date, nGroundPixels, status )
  70. !=======================================================================
  71. !
  72. ! ReadStripeCorrection:
  73. ! Read the list of NO2 slant column corrections to be applied
  74. ! to the viewing angles of e.g. OMI
  75. ! It reads from the file of date-minus-one-day, at the start of the run
  76. ! Example file name: "NO2_StripeCorrection_20160131.dat"
  77. ! for a run starting on 2016-02-01, 00:00
  78. !
  79. ! This routine must be called before stripe corrections are applied,
  80. ! because it initialises the "stripeCorr" array
  81. ! The routine may be called every time step, but will only read the
  82. ! stripe correction once during the first call
  83. !
  84. ! input:
  85. ! filedir = directory with stripecorr files
  86. ! date = the actual date-time in TM5
  87. ! nGroundPixels = number of across track pixels (60 for OMI)
  88. ! output:
  89. ! status = status ( 0 means ok)
  90. !
  91. ! Henk Eskes, KNMI, March 2016
  92. !=======================================================================
  93. use MDF, only : MDF_Open, MDF_NETCDF, MDF_READ
  94. use MDF, only : MDF_Inq_VarID, MDF_Get_Var, MDF_Close
  95. use MDF, only : MDF_Inquire_Dimension, MDF_Inq_DimID
  96. implicit none
  97. ! in/out
  98. character(len=*), intent(in) :: filedir
  99. integer, dimension(6), intent(in) :: date
  100. integer, intent(in) :: nGroundPixels
  101. integer, intent(out) :: status
  102. ! local
  103. ! character(len=*), parameter :: rname = mname//'/ReadStripeCorrection'
  104. character(len=*), parameter :: rname = 'ReadStripeCorrection'
  105. integer, dimension(6) :: date_yesterday
  106. integer :: fid, dimid, id_stripecorr, id_stripecorr_last
  107. integer :: i, ct_itau, nGP
  108. character(256) :: filename
  109. character(256), dimension(4) :: comments
  110. character(8) :: datestr
  111. real :: sc
  112. logical :: file_exists
  113. real, dimension(:), allocatable :: stripeCorr_read
  114. ! start code
  115. ! Read only once from file at the start of the run
  116. if ( .not. stripeCorrAvailable ) then
  117. status = 0
  118. ! allocate and initialise stripe correction to = 0
  119. allocate ( stripeCorr(nGroundPixels) )
  120. allocate ( stripeCorr_Last(nGroundPixels) )
  121. stripeCorr(:) = 0.0
  122. stripeCorr_last(:) = 0.0
  123. ! The stripe correction has been initialised
  124. ! (even if the stripe correction file does not exist)
  125. stripeCorrAvailable = .true.
  126. ! Determine the day before the start of the run
  127. call date2tau ( date, ct_itau )
  128. ct_itau = ct_itau - 24*3600
  129. call tau2date ( ct_itau, date_yesterday )
  130. ! Construct file name, yesterday
  131. write ( datestr, '(i4,2i2.2)' ) date_yesterday(1:3)
  132. filename = trim(filedir)//'/'//StripeCorrFilenameBase//datestr//".nc"
  133. print '(a)', trim(rname) // ', try to read StripeCorr from "' // trim(filename) // '" '
  134. ! Check file availability
  135. inquire ( file=trim(filename), Exist=file_exists )
  136. if ( .not. file_exists ) then
  137. print '(a)', " WARNING netcdf file not found '" // trim(filename) // "'"
  138. ! Check for file two days before the start of the run
  139. call date2tau ( date, ct_itau )
  140. ct_itau = ct_itau - 48*3600
  141. call tau2date ( ct_itau, date_yesterday )
  142. ! Construct file name, day before yesterday
  143. write ( datestr, '(i4,2i2.2)' ) date_yesterday(1:3)
  144. filename = trim(filedir)//'/'//StripeCorrFilenameBase//datestr//".nc"
  145. print '(a)', trim(rname) // ', try to read StripeCorr from "' // trim(filename) // '" '
  146. ! Check file availability, report errors
  147. inquire ( file=trim(filename), Exist=file_exists )
  148. if ( .not. file_exists ) then
  149. print '(a)', " WARNING netcdf file also not found '" // trim(filename) // "'"
  150. print '(a)', " Initialising stripe correction to = 0 "
  151. status = -1
  152. return
  153. end if
  154. end if
  155. ! Open file
  156. call MDF_Open( trim(filename), MDF_NETCDF, MDF_READ, fid, status )
  157. IF_NOTOK_RETURN
  158. if ( debug_stripe ) print '(a,a)', ' stripeCorr file opened: ', trim(filename)
  159. ! get the dimension
  160. call MDF_Inq_DimID( fid, 'dimGroundPixel', dimid, status )
  161. IF_NOTOK_RETURN
  162. call MDF_Inquire_Dimension( fid, dimid, status, length=nGP )
  163. IF_NOTOK_RETURN
  164. if ( nGP /= nGroundPixels ) then
  165. print '(a)', trim(rname) // ", Error reading stripe correction, number of ground pixels incorrect"
  166. print '(a)', " Initialising stripe correction to = 0 "
  167. return
  168. end if
  169. ! read the date
  170. call MDF_Get_Att( fid, MDF_GLOBAL, 'Date', stripeCorrDate, status )
  171. IF_NOTOK_RETURN
  172. if ( debug_stripe ) print '(a,i5,5i3)', ' stripeCorrDate = ',stripeCorrDate
  173. ! temp storage
  174. allocate ( stripeCorr_read(nGroundPixels) )
  175. ! Read stripe correction coefficients
  176. CALL MDF_Inq_VarID( fid, 'stripeCorrection', id_stripecorr, status )
  177. IF_ERROR_RETURN
  178. CALL MDF_Get_Var( fid, id_stripecorr, stripeCorr_read, status )
  179. IF_NOTOK_RETURN
  180. if ( debug_stripe ) print '(a)', ' read array stripeCorrection '
  181. ! if reading went well, copy
  182. stripeCorr(:) = stripeCorr_read(:)
  183. stripeCorr_read(:) = 0.0
  184. CALL MDF_Inq_VarID( fid, 'stripeCorrectionLast', id_stripecorr_last, status )
  185. IF_ERROR_RETURN
  186. CALL MDF_Get_Var( fid, id_stripecorr_last, stripeCorr_read, status )
  187. IF_NOTOK_RETURN
  188. if ( debug_stripe ) print '(a)', ' read array stripeCorrectionLast '
  189. ! if reading went well, copy
  190. stripeCorr_last(:) = stripeCorr_read(:)
  191. ! remove storage
  192. deallocate ( stripeCorr_read )
  193. ! Close file
  194. CALL MDF_Close( fid, status )
  195. IF_NOTOK_RETURN
  196. ! if stripeCorr file was available, and no error occurred
  197. stripeCorr_new = .false.
  198. if ( debug_stripe ) print '(a,60f7.3)', ' stripeCorr = ', stripeCorr(:)
  199. end if
  200. end subroutine ReadStripeCorrection
  201. subroutine WriteStripeCorrection ( filedir, no2Tr, lat_min, lat_max, status )
  202. !=======================================================================
  203. !
  204. ! WriteStripeCorrection:
  205. ! Write the stripe correction data to file
  206. !
  207. ! This routine is not public, but is called by "ComputeStripeCorrection"
  208. !
  209. ! input:
  210. ! filedir = directory with stripe correction files
  211. ! no2Tr = used are the file name info
  212. ! lat_min, lat_max = latitude range for which the stripe correction
  213. ! was determined
  214. ! output:
  215. ! status = status ( 0 means the file has been created )
  216. !
  217. ! Henk Eskes, KNMI, March 2016, Aug 2016
  218. !=======================================================================
  219. implicit none
  220. ! in/out
  221. character(len=*), intent(in) :: filedir
  222. type(TObsTrack), intent(in) :: no2Tr
  223. real, intent(in) :: lat_min, lat_max
  224. integer, intent(out) :: status
  225. ! local
  226. ! character(len=*), parameter :: rname = mname//'/WriteStripeCorrection'
  227. character(len=*), parameter :: rname = 'WriteStripeCorrection'
  228. integer :: ncid, fid
  229. character(256) :: filename
  230. character(256) :: commentline
  231. character(8) :: datestr
  232. integer :: nGroundPixels
  233. integer :: itau_x
  234. integer, dimension(6) :: idate_x
  235. integer :: dimid, varid_stripecorr, varid_stripecorr_last
  236. ! start code
  237. ! Determine the day to be used for the filename, e.g.
  238. ! stripecorr determined for orbit 24 jan, 23:52 => file date = 24 jan
  239. ! stripecorr determined for orbit 25 jan, 00:12 => file date = 24 jan
  240. call date2tau ( stripeCorrDate, itau_x )
  241. itau_x = itau_x - 6*3600
  242. call tau2date ( itau_x, idate_x )
  243. ! Construct file name
  244. write ( datestr, '(i4,2i2.2)' ) idate_x(1:3)
  245. filename = trim(filedir)//'/'//trim(StripeCorrFilenameBase)//datestr//".nc"
  246. ! The stripe correction should have been initialised
  247. if ( .not. stripeCorrAvailable ) then
  248. print '(a)', trim(rname) // ": ERROR stripeCorr not initialised, file not created"
  249. status = -1
  250. return
  251. end if
  252. print '(a)', trim(rname) // ": now writing stripeCorr to file " // trim(filename)
  253. status = 0
  254. ! nr of viewing angles = nr of stripe correction values
  255. nGroundPixels = no2Tr%dimGroundPixel
  256. ! overwrite existing files (clobber)
  257. call MDF_Create( trim(filename), MDF_NETCDF4, MDF_REPLACE, ncid, status )
  258. IF_NOTOK_RETURN
  259. ! o global attributes
  260. call MDF_Put_Att( ncid, MDF_GLOBAL, 'Title', 'Stripe correction factors, NO2' , status)
  261. IF_NOTOK_MDF(fid=ncid)
  262. call MDF_Put_Att( ncid, MDF_GLOBAL, 'DatasetAuthor' , trim(dataset_author) , status)
  263. IF_NOTOK_MDF(fid=ncid)
  264. call MDF_Put_Att( ncid, MDF_GLOBAL, 'Institution' , trim(institution) , status)
  265. IF_NOTOK_MDF(fid=ncid)
  266. call MDF_Put_Att( ncid, MDF_GLOBAL, 'Date' , stripeCorrDate(1:6) , status)
  267. IF_NOTOK_MDF(fid=ncid)
  268. call MDF_Put_Att( ncid, MDF_GLOBAL, 'L2_OrbitFilename' , trim(no2Tr%orbitParts(1)%filename), status)
  269. IF_NOTOK_MDF(fid=ncid)
  270. ! Write the two comment lines
  271. write ( commentline, '(i5,5i3,a)' ) stripeCorrDate(1:6)," Date and time for which time-averaged slant column stripe correction was determined "
  272. call MDF_Put_Att( ncid, MDF_GLOBAL, 'Comment1' , trim(commentline), status)
  273. IF_NOTOK_MDF(fid=ncid)
  274. write ( commentline, '(a,f8.2,a,f8.2)' ) "Latitude range, from ",lat_min," to ", lat_max
  275. call MDF_Put_Att( ncid, MDF_GLOBAL, 'Comment2' , trim(commentline), status)
  276. IF_NOTOK_MDF(fid=ncid)
  277. ! o define dimensions
  278. call MDF_Def_Dim( ncid, 'dimGroundPixel', nGroundPixels, dimid, status )
  279. IF_NOTOK_MDF(fid=ncid)
  280. ! o define variables
  281. call MDF_Def_Var( ncid, 'stripeCorrection', MDF_FLOAT, (/dimid/), varid_stripecorr, status)
  282. IF_NOTOK_MDF(fid=ncid)
  283. call MDF_Put_Att( ncid, varid_stripecorr, 'standard_name', 'stripe correction' , status)
  284. IF_NOTOK_MDF(fid=ncid)
  285. call MDF_Put_Att( ncid, varid_stripecorr, 'long_name', 'row-dependent correction factor, mean over past orbits' , status)
  286. IF_NOTOK_MDF(fid=ncid)
  287. call MDF_Put_Att( ncid, varid_stripecorr, 'units', '10^15 molecules cm^-2' , status)
  288. IF_NOTOK_MDF(fid=ncid)
  289. call MDF_Def_Var( ncid, 'stripeCorrectionLast', MDF_FLOAT, (/dimid/), varid_stripecorr_last, status)
  290. IF_NOTOK_MDF(fid=ncid)
  291. call MDF_Put_Att( ncid, varid_stripecorr_last, 'standard_name', 'stripe correction last' , status)
  292. IF_NOTOK_MDF(fid=ncid)
  293. call MDF_Put_Att( ncid, varid_stripecorr_last, 'long_name', 'row-dependent correction factor, derived from last orbit only' , status)
  294. IF_NOTOK_MDF(fid=ncid)
  295. call MDF_Put_Att( ncid, varid_stripecorr_last, 'units', '10^15 molecules cm^-2' , status)
  296. IF_NOTOK_MDF(fid=ncid)
  297. ! o end definition mode
  298. call MDF_EndDef( ncid , status)
  299. IF_NOTOK_MDF(fid=ncid)
  300. ! o write stripe correction arrays
  301. call MDF_Put_Var( ncid, varid_stripecorr, stripeCorr, status)
  302. IF_NOTOK_MDF(fid=ncid)
  303. call MDF_Put_Var( ncid, varid_stripecorr_last, stripeCorr_last, status)
  304. IF_NOTOK_MDF(fid=ncid)
  305. ! close the file
  306. call MDF_Close( ncid, status)
  307. IF_NOTOK_RETURN
  308. end subroutine WriteStripeCorrection
  309. subroutine ComputeStripeCorrection ( filedir, date, no2Tr, nObs, modelSCD, flag, status )
  310. !=======================================================================
  311. !
  312. ! ComputeStripeCorrection:
  313. ! Check if the stripe correction should be determined for this orbit
  314. ! (only over the Pacific)
  315. ! If so, compute NO2 slant column stripe correction
  316. ! and write the stripe correction data to file
  317. !
  318. ! The stripe correction is derived over the Pacific, for latitudes
  319. ! between "lat_min" and "lat_max"
  320. !
  321. ! input:
  322. ! filedir = directory with stripe correction files
  323. ! date = the actual date-time in TM5
  324. ! no2Tr = contains the L2 slant column data product
  325. ! nObs = number of observations in this orbit
  326. ! modelSCD = slant column computed from the model
  327. ! flag = 0 for a retrieval without errors and (snow-ice) warnings
  328. !
  329. ! output:
  330. ! status = status ( 0 means the file has been created )
  331. !
  332. ! Henk Eskes, KNMI, March 2016
  333. !=======================================================================
  334. use physconstants, only : pi
  335. implicit none
  336. ! in/out
  337. character(len=*), intent(in) :: filedir
  338. integer, dimension(6), intent(in) :: date
  339. type(TObsTrack), intent(in) :: no2Tr
  340. integer, intent(in) :: nObs
  341. real, intent(in), dimension(nObs) :: modelSCD
  342. integer, intent(in), dimension(nObs) :: flag
  343. integer, intent(out) :: status
  344. ! local
  345. ! character(len=*), parameter :: rname = mname//'/ComputeStripeCorrection'
  346. character(len=*), parameter :: rname = 'ComputeStripeCorrection'
  347. real :: dist_min, dist, lat_equator, lon_equator
  348. real :: fit_a, fit_b
  349. integer :: iObs, iObs_equator, i, j
  350. integer :: maxScanLineIndex
  351. integer :: itau, itau_stripecorr
  352. integer :: block_jmin, block_jmax
  353. real :: block_lat_min, block_lat_max
  354. real :: coeff, season_shift, year_fraction
  355. integer :: nGroundPixel
  356. integer :: nValidStripeCorrs
  357. real, dimension(:,:), allocatable :: delta_scd_2d
  358. real, dimension(:), allocatable :: lat_2d
  359. integer, dimension(:), allocatable :: nlat_2d
  360. real, dimension(:), allocatable :: delta_scd_aver, rownr
  361. integer, dimension(:), allocatable :: n_scd_aver
  362. real, dimension(5) :: xtest = (/ 1.0, 2.0, 3.0, 4.0, 5.0 /), ytest = (/ 3.0, 3.0, 4.0, 4.0, nf_fill_float /)
  363. ! start code
  364. ! Check if this time step has already been processed
  365. call date2tau ( date, itau )
  366. call date2tau ( stripeCorrDate, itau_stripecorr )
  367. if ( itau == itau_stripecorr ) then
  368. ! A stripe correction has already been determined for this orbit
  369. print '(a)', trim(rname) // ": a stripe correction was already computed for this orbit"
  370. return
  371. end if
  372. ! Across-track number of pixels = number of stripe correction coefficients
  373. nGroundPixel = no2Tr%dimGroundPixel
  374. ! Determine if the orbit is over the Pacific ?
  375. ! Nadir longitude @ equator window : -150 \pm 30
  376. ! OMI advances 24.72 degree longitude between orbits
  377. ! Criterion for accepting the orbit: When orbit longitude at the equator falls within [-163,-137]
  378. ! Determine equator crossing orbit longitude
  379. ! by minimising the distance to the equator and to the middle of the orbit
  380. ! (0.24 = roughly the width of an OMI pixel in degree)
  381. dist_min = 1.0e6 ! degree
  382. do iObs = 1, nObs
  383. dist = 0.24 * Abs( real(no2Tr%pixelIndex(iObs)) - (0.5*nGroundPixel+0.5) ) + Abs( no2Tr%latitude(iObs) )
  384. if ( dist < dist_min ) then
  385. iObs_equator = iObs
  386. lat_equator = no2Tr%latitude(iObs)
  387. lon_equator = no2Tr%longitude(iObs)
  388. dist_min = dist
  389. end if
  390. end do
  391. if ( debug_stripe ) print '(a,i6,2f8.2,i5)', trim(rname)//': Equator crossing coordinates: iObs, lat, lon, pixIndex = ', &
  392. iObs_equator, lat_equator, lon_equator, no2Tr%pixelIndex(iObs_equator)
  393. ! Check if this is a pacific orbit. If not, leave.
  394. if ( ( lon_equator < pacific_lonmin ) .or. ( lon_equator > pacific_lonmax ) ) then
  395. print '(a)', trim(rname) // ": this orbit is not a Pacific ocean orbit, return "
  396. return
  397. end if
  398. ! Now we have found an orbit over the Pacific: determine stripe correction coefficients
  399. print '(a)', trim(rname) // ": this orbit is a Pacific ocean orbit, now determining stripe amplitude "
  400. ! --- Re-grid the data from 1D to 2D: lon, lat, scd-scd_model ---
  401. ! First compute max scan line index
  402. maxScanLineIndex = -1
  403. do iObs = 1, nObs
  404. if ( no2Tr%scanLineIndex(iObs) > maxScanLineIndex ) then
  405. maxScanLineIndex = no2Tr%scanLineIndex(iObs)
  406. end if
  407. end do
  408. if ( debug_stripe ) print *, ' Max scan line index = ', maxScanLineIndex
  409. ! Then allocate and regrid
  410. allocate ( delta_scd_2d ( nGroundPixel, maxScanLineIndex ) )
  411. allocate ( lat_2d ( maxScanLineIndex ) )
  412. allocate ( nlat_2d ( maxScanLineIndex ) )
  413. allocate ( delta_scd_aver( nGroundPixel ) )
  414. allocate ( rownr( nGroundPixel ) )
  415. allocate ( n_scd_aver( nGroundPixel ) )
  416. ! Determine ( SCD_sat - SCD_model ) on the 2D swath grid
  417. delta_scd_2d(:,:) = nf_fill_float
  418. lat_2d(:) = 0.0
  419. nlat_2d(:) = 0
  420. do iObs = 1, nObs
  421. if ( iand(flag(iObs), 255) == 0 ) then
  422. i = no2Tr%pixelIndex(iObs)
  423. j = no2Tr%scanLineIndex(iObs)
  424. delta_scd_2d(i,j) = no2Tr%no2SLC(iObs) - modelSCD(iObs)
  425. lat_2d(j) = lat_2d(j) + no2Tr%latitude(iObs)
  426. nlat_2d(j) = nlat_2d(j) + 1
  427. end if
  428. end do
  429. do j = 1, maxScanLineIndex
  430. if ( nlat_2d(j) > 0 ) then
  431. lat_2d(j) = lat_2d(j) / nlat_2d(j)
  432. else
  433. lat_2d(j) = nf_fill_float
  434. end if
  435. end do
  436. ! print '(a,2000e10.2)', ' lat_2d = ', lat_2d(:)
  437. if ( debug_stripe ) print '(a,60i6)', ' pixInd 50000+60 = ', no2Tr%pixelIndex(50000:50059)
  438. if ( debug_stripe ) print '(a,60f6.2)', ' no2SLC 50000+60 = ', no2Tr%no2SLC(50000:50059)
  439. if ( debug_stripe ) print '(a,60f6.2)', ' modelSCD 50000+60 = ', modelSCD(50000:50059)
  440. ! Determine block of data in latitude range
  441. ! winter: [30S,0], summer: [0,30N]
  442. year_fraction = (real(date(2))+real(date(3))/31.0-1.0)/12.0
  443. season_shift = sin ( pi*year_fraction )
  444. block_lat_min = -30.0 + 30.0 * season_shift
  445. block_lat_max = 30.0 * season_shift
  446. if ( debug_stripe ) print *,' date = ',date
  447. if ( debug_stripe ) print *,' year fraction, season shift, latitude range = ', year_fraction, season_shift, block_lat_min, block_lat_max
  448. ! determine block indices
  449. block_jmin = 1
  450. block_jmax = 1
  451. do j = 1, maxScanLineIndex
  452. if ( lat_2d(j) < 0.9*nf_fill_float ) then
  453. if ( block_lat_min > lat_2d(j) ) block_jmin = j
  454. if ( block_lat_max > lat_2d(j) ) block_jmax = j
  455. end if
  456. end do
  457. ! print *, ' lat_2d = ', lat_2d(:)
  458. if ( debug_stripe ) print *, ' block range = ',block_jmin,block_jmax
  459. if ( debug_stripe ) print '(a,2f8.2)', ' lat range = ',lat_2d(block_jmin),lat_2d(block_jmax)
  460. ! determine mean stripe amplitude for each row over the latitude block
  461. nValidStripeCorrs = 0
  462. do i = 1, nGroundPixel
  463. delta_scd_aver(i) = 0.0
  464. n_scd_aver(i) = 0
  465. rownr(i) = real(i)
  466. do j = block_jmin, block_jmax
  467. if ( delta_scd_2d(i,j) < 0.9*nf_fill_float ) then
  468. delta_scd_aver(i) = delta_scd_aver(i) + delta_scd_2d(i,j)
  469. n_scd_aver(i) = n_scd_aver(i) + 1
  470. end if
  471. end do
  472. if ( n_scd_aver(i) >= minNrValidPixels ) then
  473. delta_scd_aver(i) = delta_scd_aver(i) / real(n_scd_aver(i))
  474. nValidStripeCorrs = nValidStripeCorrs + 1
  475. else
  476. delta_scd_aver(i) = nf_fill_float
  477. end if
  478. end do
  479. if ( debug_stripe ) print '(a,60f8.3)', ' delta_scd_aver(:) = ', delta_scd_aver(:)
  480. if ( debug_stripe ) print '(a,60i5)', ' n_scd_aver(:) = ', n_scd_aver(:)
  481. if ( debug_stripe ) print '(a,i4)', ' nr of valid stripe corrs = ', nValidStripeCorrs
  482. ! release storage
  483. deallocate ( nlat_2d )
  484. deallocate ( lat_2d )
  485. deallocate ( delta_scd_2d )
  486. if ( debug_stripe ) then
  487. ! determine linear fit
  488. call linearFit ( xtest, ytest, 5, nf_fill_float, fit_a, fit_b )
  489. print '(a,2f10.4)', ' test fit_a, fit_b = ',fit_a, fit_b
  490. end if
  491. ! determine linear fit
  492. call linearFit ( rownr, delta_scd_aver, nGroundPixel, nf_fill_float, fit_a, fit_b )
  493. if ( debug_stripe ) print '(a,2f10.4)', ' fit_a, fit_b = ',fit_a, fit_b
  494. ! New "stripeCorr_last":
  495. ! stripe correction = mean-slant-column-difference minus linear-fit
  496. do i = 1, nGroundPixel
  497. if ( delta_scd_aver(i) < 0.9*nf_fill_float ) then
  498. stripeCorr_last(i) = delta_scd_aver(i) - ( fit_a * rownr(i) + fit_b )
  499. else
  500. stripeCorr_last(i) = nf_fill_float
  501. end if
  502. end do
  503. if ( debug_stripe ) print '(a,60f8.3)', ' stripeCorr_last(1:60) = ',stripeCorr_last(1:60)
  504. ! Define new stripe correction as a mix of the previous one and the new one found for this orbit
  505. ! with a mean averaging time
  506. if ( stripeCorr_averaging_time > 1.99 ) then
  507. if ( stripeCorr_new ) then
  508. ! No information from the day before, so simply copy the new stripe correction found
  509. stripeCorr(:) = stripeCorr_last(:)
  510. else
  511. ! mix of new stripecorr with stripecorr obtained on previous days
  512. coeff = 1.0 / stripeCorr_averaging_time
  513. do i = 1, nGroundPixel
  514. if ( stripeCorr_last(i) < 0.9*nf_fill_float ) then
  515. if ( stripeCorr(i) < 0.9*nf_fill_float ) then
  516. ! well defined new stripe corr value, and existing old one
  517. stripeCorr(i) = (1.0-coeff)*stripeCorr(i) + coeff*stripeCorr_last(i)
  518. else
  519. ! replace stripecorr when not defined
  520. stripeCorr(i) = stripeCorr_last(i)
  521. end if
  522. else
  523. ! do not change existing stripeCorr(i)
  524. end if
  525. end do
  526. end if
  527. else
  528. ! The averaging time is shorter than 2 days: use instantaneous value
  529. stripeCorr(:) = stripeCorr_last(:)
  530. end if
  531. if ( debug_stripe ) print '(a,60f8.3)', ' stripeCorr(1:60) = ',stripeCorr(1:60)
  532. ! Date for which the stripe correction was determined
  533. stripeCorrDate(:) = date(:)
  534. ! And, finally, write the two new stripe coefficients to file
  535. call WriteStripeCorrection ( filedir, no2Tr, block_lat_min, block_lat_max, status )
  536. ! stripeCorr file is available
  537. stripeCorr_new = .false.
  538. deallocate ( delta_scd_aver )
  539. deallocate ( rownr )
  540. deallocate ( n_scd_aver )
  541. if ( status /= 0 ) then
  542. print '(a)', trim(rname) // ": ERROR writing the new stripe correction "
  543. end if
  544. end subroutine ComputeStripeCorrection
  545. subroutine linearFit ( x, y, n0, nf_fill_float, a, b )
  546. ! Compute simple linear fit
  547. ! y = a x + b
  548. ! For a set of n points (xi,yi) where yi <> nf_fill_float
  549. !
  550. ! nf_fill_float : large negative value indicating value is not defined
  551. !
  552. ! Solution: a = ( ave(xy) -ave(x) ave(y) ) / ( ave(x^2) - ave(x)^2 )
  553. ! b = ave(y) - a ave(x)
  554. !
  555. implicit none
  556. ! in/out
  557. real, dimension(n0), intent(in) :: x, y
  558. integer, intent(in) :: n0
  559. real, intent(in) :: nf_fill_float
  560. real, intent(out) :: a, b
  561. ! local
  562. integer :: n, i
  563. real :: sx, sy, sxx, sxy
  564. ! begin code
  565. n = 0
  566. sx = 0.0
  567. sy = 0.0
  568. sxx = 0.0
  569. sxy = 0.0
  570. do i = 1, n0
  571. if ( y(i) < 0.9 * nf_fill_float ) then
  572. n = n + 1
  573. sx = sx + x(i)
  574. sy = sy + y(i)
  575. sxx = sxx + x(i)*x(i)
  576. sxy = sxy + x(i)*y(i)
  577. end if
  578. end do
  579. if ( n > 1 ) then ! need at least two valid points
  580. sx = sx/real(n)
  581. sy = sy/real(n)
  582. sxx = sxx/real(n)
  583. sxy = sxy/real(n)
  584. a = (sxy-sx*sy)/(sxx-sx*sx)
  585. b = sy - a*sx
  586. else
  587. a = nf_fill_float
  588. b = nf_fill_float
  589. end if
  590. end subroutine linearFit
  591. end module MStripeCorrection