cjerzak commited on
Commit
dc52259
·
verified ·
1 Parent(s): 76c7165

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +90 -65
app.R CHANGED
@@ -321,115 +321,140 @@ server <- function(input, output, session) {
321
  return()
322
  }
323
 
324
- gee_project <- Sys.getenv("GEE_PROJECT", unset = NULL)
325
- gee_email <- Sys.getenv("GEE_EMAIL", unset = NULL)
326
- gee_key <- Sys.getenv("GEE_KEY", unset = NULL)
327
 
328
- py_run_string("
329
- import ee, pandas as pd, json
 
 
 
 
330
 
331
  def _ee_init(project, email=None, key_data=None):
332
- if email is None and key_data is None:
 
 
 
 
 
 
 
 
 
 
 
333
  try:
334
  ee.Initialize(project=project)
335
- except:
336
- ee.Authenticate()
337
- ee.Initialize(project=project)
338
- else:
339
- key_dict = json.loads(key_data)
340
- credentials = ee.ServiceAccountCredentials(email, key_data=key_dict)
341
- ee.Initialize(credentials=credentials, project=project)
342
 
343
- def satellite_stats(points, start, end, scale=250):
 
344
  feats = [ee.Feature(ee.Geometry.Point([float(p['lon']), float(p['lat'])]),
345
  {'rowid': str(p['rowid'])}) for p in points]
346
  fc = ee.FeatureCollection(feats)
347
 
 
348
  def mask_modis(img):
349
  qa = img.select('SummaryQA')
350
- mask = qa.eq(0)
351
- return img.updateMask(mask).select('NDVI').multiply(0.0001).copyProperties(img, img.propertyNames())
 
 
352
  modis = (ee.ImageCollection('MODIS/061/MOD13Q1')
353
  .filterDate(start, end)
354
  .map(mask_modis))
355
- ndvi_mean = modis.select('NDVI').mean().rename('ndvi_mean')
356
- ndvi_median = modis.reduce(ee.Reducer.median()).rename('ndvi_median')
357
- ndvi_max = modis.select('NDVI').max().rename('ndvi_max')
358
 
359
- dmsp = ee.ImageCollection('NOAA/DMSP-OLS/NIGHTTIME_LIGHTS').select('stable_lights')
360
- viirs = ee.ImageCollection('NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG').select('avg_rad')
 
 
 
 
 
 
361
 
362
- dmsp_window = dmsp.filterDate(start, end)
363
  viirs_window = viirs.filterDate(start, end)
364
 
365
- overlap_start = ee.Date('2012-01-01')
366
- overlap_end = ee.Date('2014-12-31')
367
- dmsp_ov_img = dmsp.filterDate(overlap_start, overlap_end).mean()
 
 
 
368
  viirs_ov_img = viirs.filterDate(overlap_start, overlap_end).mean()
369
 
370
- def _buffer_feat(f):
371
- f = ee.Feature(f)
372
- return f.buffer(5000)
373
- fc_buffer = fc.map(_buffer_feat)
374
  region_geom = fc_buffer.geometry()
375
 
376
- dmsp_ov_mean = ee.Number(dmsp_ov_img.reduceRegion(
377
- reducer=ee.Reducer.mean(), geometry=region_geom, scale=5000, maxPixels=1e13
378
- ).get('stable_lights'))
379
- viirs_ov_mean = ee.Number(viirs_ov_img.reduceRegion(
380
- reducer=ee.Reducer.mean(), geometry=region_geom, scale=5000, maxPixels=1e13
381
- ).get('avg_rad'))
382
 
383
- dmsp_global_mean = ee.Number(dmsp_ov_img.reduceRegion(
384
  reducer=ee.Reducer.mean(), geometry=ee.Geometry.Rectangle([-180, -90, 180, 90]),
385
- scale=50000, maxPixels=1e13
386
- ).get('stable_lights'))
387
- viirs_global_mean = ee.Number(viirs_ov_img.reduceRegion(
388
  reducer=ee.Reducer.mean(), geometry=ee.Geometry.Rectangle([-180, -90, 180, 90]),
389
- scale=50000, maxPixels=1e13
390
- ).get('avg_rad'))
391
-
392
- dmsp_use = ee.Algorithms.If(dmsp_ov_mean, dmsp_ov_mean, dmsp_global_mean)
393
- viirs_use = ee.Algorithms.If(viirs_ov_mean, viirs_ov_mean, viirs_global_mean)
394
 
395
- dmsp_use = ee.Number(dmsp_use)
396
- viirs_use = ee.Number(viirs_use)
397
 
398
- scale = ee.Algorithms.If(dmsp_use.gt(0), viirs_use.divide(dmsp_use), 1)
399
- scale = ee.Number(scale)
400
 
401
- def calib_img(img):
402
- return img.multiply(scale).toFloat()
403
 
404
- dmsp_equiv = dmsp_window.map(lambda img: calib_img(img.select('stable_lights').rename('ntl')))
405
- viirs_prep = viirs_window.map(lambda img: img.select('avg_rad').rename('ntl').toFloat())
 
 
 
 
 
406
  ntl_window = dmsp_equiv.merge(viirs_prep)
407
 
408
- ntl_mean = ntl_window.mean().rename('ntl_mean')
409
- ntl_median = ntl_window.reduce(ee.Reducer.median()).rename('ntl_median')
410
- ntl_max = ntl_window.max().rename('ntl_max')
411
 
 
412
  stacked = (ndvi_mean
413
- .addBands([ndvi_median, ndvi_max,
414
- ntl_mean, ntl_median, ntl_max]))
415
 
416
- samples = stacked.sampleRegions(collection=fc, properties=['rowid'], scale=scale)
 
 
 
417
  info = samples.getInfo()
418
  rows = []
419
  for f in info.get('features', []):
420
  p = f.get('properties', {}) or {}
421
  rows.append({
422
- 'rowid': p.get('rowid'),
423
- 'ndvi_mean': p.get('ndvi_mean'),
424
  'ndvi_median': p.get('ndvi_median'),
425
- 'ndvi_max': p.get('ndvi_max'),
426
- 'ntl_mean': p.get('ntl_mean'),
427
- 'ntl_median': p.get('ntl_median'),
428
- 'ntl_max': p.get('ntl_max')
429
  })
430
  return pd.DataFrame(rows)
431
  ")
432
-
 
433
  py$`_ee_init`(project = gee_project, email = gee_email, key_data = gee_key)
434
 
435
  df$rowid <- seq_len(nrow(df))
 
321
  return()
322
  }
323
 
324
+ gee_project <- Sys.getenv("GEE_PROJECT")
325
+ gee_email <- Sys.getenv("GEE_EMAIL")
326
+ gee_key <- Sys.getenv("GEE_KEY")
327
 
328
+ {
329
+ py_run_string("
330
+ import ee
331
+ import pandas as pd
332
+ import json
333
+ import os
334
 
335
  def _ee_init(project, email=None, key_data=None):
336
+ # Normalize empty strings to None
337
+ project = project or None
338
+ email = (email or None) if email else None
339
+ key_data = (key_data or None) if key_data else None
340
+
341
+ # Prefer service account if provided; otherwise try ADC (non-interactive)
342
+ if key_data:
343
+ # key_data must be a JSON string
344
+ key_json = key_data if isinstance(key_data, str) else json.dumps(key_data)
345
+ credentials = ee.ServiceAccountCredentials(email, key_data=key_json)
346
+ ee.Initialize(credentials=credentials, project=project)
347
+ else:
348
  try:
349
  ee.Initialize(project=project)
350
+ except Exception as e:
351
+ raise RuntimeError(
352
+ 'No service-account key provided and ADC not available. '
353
+ 'Set GOOGLE_APPLICATION_CREDENTIALS or provide GEE_EMAIL+GEE_KEY.'
354
+ ) from e
 
 
355
 
356
+ def satellite_stats(points, start, end, sample_scale=250):
357
+ # Build FeatureCollection from input points
358
  feats = [ee.Feature(ee.Geometry.Point([float(p['lon']), float(p['lat'])]),
359
  {'rowid': str(p['rowid'])}) for p in points]
360
  fc = ee.FeatureCollection(feats)
361
 
362
+ # MODIS NDVI (scaled by 0.0001), mask to SummaryQA == 0
363
  def mask_modis(img):
364
  qa = img.select('SummaryQA')
365
+ return (img.updateMask(qa.eq(0))
366
+ .select('NDVI').multiply(0.0001)
367
+ .copyProperties(img, img.propertyNames()))
368
+
369
  modis = (ee.ImageCollection('MODIS/061/MOD13Q1')
370
  .filterDate(start, end)
371
  .map(mask_modis))
 
 
 
372
 
373
+ ndvi_mean = modis.select('NDVI').mean().rename('ndvi_mean')
374
+ ndvi_median = modis.select('NDVI').median().rename('ndvi_median')
375
+ ndvi_max = modis.select('NDVI').max().rename('ndvi_max')
376
+
377
+ # Night lights: DMSP (pre-2014) and VIIRS (2012+)
378
+ dmsp = ee.ImageCollection('NOAA/DMSP-OLS/NIGHTTIME_LIGHTS').select('stable_lights')
379
+ #viirs = ee.ImageCollection('NOAA/VIIRS/DNB/MONTHLY_V1/VCMSLCFG').select('avg_rad') # old
380
+ viirs = ee.ImageCollection('NOAA/VIIRS/DNB/MONTHLY_V1/VCMCFG').select('avg_rad') # new
381
 
382
+ dmsp_window = dmsp.filterDate(start, end)
383
  viirs_window = viirs.filterDate(start, end)
384
 
385
+ # Overlap window for simple cross-calibration (DMSP↔VIIRS)
386
+ #overlap_start = ee.Date('2012-01-01') # old
387
+ overlap_start = ee.Date('2012-04-01') # new
388
+ overlap_end = ee.Date('2013-12-31') # DMSP coverage effectively ends 2013
389
+
390
+ dmsp_ov_img = dmsp.filterDate(overlap_start, overlap_end).mean()
391
  viirs_ov_img = viirs.filterDate(overlap_start, overlap_end).mean()
392
 
393
+ # Buffer features to form a region-of-interest for overlap means
394
+ fc_buffer = fc.map(lambda f: ee.Feature(f).buffer(5000))
 
 
395
  region_geom = fc_buffer.geometry()
396
 
397
+ # Null-safe reducers via dictionaries + contains()
398
+ dmsp_ov_dict = ee.Dictionary(dmsp_ov_img.reduceRegion(
399
+ reducer=ee.Reducer.mean(), geometry=region_geom, scale=5000, maxPixels=1e13))
400
+ viirs_ov_dict = ee.Dictionary(viirs_ov_img.reduceRegion(
401
+ reducer=ee.Reducer.mean(), geometry=region_geom, scale=5000, maxPixels=1e13))
 
402
 
403
+ dmsp_global_mean = ee.Number(ee.Image(dmsp_ov_img).reduceRegion(
404
  reducer=ee.Reducer.mean(), geometry=ee.Geometry.Rectangle([-180, -90, 180, 90]),
405
+ scale=50000, maxPixels=1e13).get('stable_lights'))
406
+ viirs_global_mean = ee.Number(ee.Image(viirs_ov_img).reduceRegion(
 
407
  reducer=ee.Reducer.mean(), geometry=ee.Geometry.Rectangle([-180, -90, 180, 90]),
408
+ scale=50000, maxPixels=1e13).get('avg_rad'))
 
 
 
 
409
 
410
+ dmsp_has = dmsp_ov_dict.contains('stable_lights')
411
+ viirs_has = viirs_ov_dict.contains('avg_rad')
412
 
413
+ dmsp_use = ee.Number(ee.Algorithms.If(dmsp_has, dmsp_ov_dict.get('stable_lights'), dmsp_global_mean))
414
+ viirs_use = ee.Number(ee.Algorithms.If(viirs_has, viirs_ov_dict.get('avg_rad'), viirs_global_mean))
415
 
416
+ # Guard divide-by-zero; produce a VIIRS-per-DMSP scale factor (ratio)
417
+ k_viirs_per_dmsp = ee.Number(ee.Algorithms.If(dmsp_use.gt(0), viirs_use.divide(dmsp_use), 1))
418
 
419
+ # Build a merged NTL series in VIIRS-equivalent units
420
+ dmsp_equiv = dmsp_window.map(
421
+ lambda img: img.select('stable_lights').multiply(k_viirs_per_dmsp).rename('ntl').toFloat()
422
+ )
423
+ viirs_prep = viirs_window.map(
424
+ lambda img: img.select('avg_rad').rename('ntl').toFloat()
425
+ )
426
  ntl_window = dmsp_equiv.merge(viirs_prep)
427
 
428
+ ntl_mean = ntl_window.mean().rename('ntl_mean')
429
+ ntl_median = ntl_window.median().rename('ntl_median')
430
+ ntl_max = ntl_window.max().rename('ntl_max')
431
 
432
+ # Stack all bands
433
  stacked = (ndvi_mean
434
+ .addBands([ndvi_median, ndvi_max,
435
+ ntl_mean, ntl_median, ntl_max]))
436
 
437
+ # IMPORTANT: use the intended pixel size (meters) for sampling
438
+ samples = stacked.sampleRegions(collection=fc, properties=['rowid'], scale=sample_scale)
439
+
440
+ # Bring results client-side
441
  info = samples.getInfo()
442
  rows = []
443
  for f in info.get('features', []):
444
  p = f.get('properties', {}) or {}
445
  rows.append({
446
+ 'rowid': p.get('rowid'),
447
+ 'ndvi_mean': p.get('ndvi_mean'),
448
  'ndvi_median': p.get('ndvi_median'),
449
+ 'ndvi_max': p.get('ndvi_max'),
450
+ 'ntl_mean': p.get('ntl_mean'),
451
+ 'ntl_median': p.get('ntl_median'),
452
+ 'ntl_max': p.get('ntl_max')
453
  })
454
  return pd.DataFrame(rows)
455
  ")
456
+ }
457
+
458
  py$`_ee_init`(project = gee_project, email = gee_email, key_data = gee_key)
459
 
460
  df$rowid <- seq_len(nrow(df))