Update app.R
Browse files
app.R
CHANGED
|
@@ -321,115 +321,140 @@ server <- function(input, output, session) {
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
def _ee_init(project, email=None, key_data=None):
|
| 332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
try:
|
| 334 |
ee.Initialize(project=project)
|
| 335 |
-
except:
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
credentials = ee.ServiceAccountCredentials(email, key_data=key_dict)
|
| 341 |
-
ee.Initialize(credentials=credentials, project=project)
|
| 342 |
|
| 343 |
-
def satellite_stats(points, start, end,
|
|
|
|
| 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 |
-
|
| 351 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 360 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 361 |
|
| 362 |
-
dmsp_window
|
| 363 |
viirs_window = viirs.filterDate(start, end)
|
| 364 |
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
|
|
|
|
|
|
|
|
|
| 368 |
viirs_ov_img = viirs.filterDate(overlap_start, overlap_end).mean()
|
| 369 |
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
return f.buffer(5000)
|
| 373 |
-
fc_buffer = fc.map(_buffer_feat)
|
| 374 |
region_geom = fc_buffer.geometry()
|
| 375 |
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 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 |
-
).
|
| 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 |
-
|
| 396 |
-
|
| 397 |
|
| 398 |
-
|
| 399 |
-
|
| 400 |
|
| 401 |
-
|
| 402 |
-
|
| 403 |
|
| 404 |
-
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
ntl_window = dmsp_equiv.merge(viirs_prep)
|
| 407 |
|
| 408 |
-
ntl_mean
|
| 409 |
-
ntl_median = ntl_window.
|
| 410 |
-
ntl_max
|
| 411 |
|
|
|
|
| 412 |
stacked = (ndvi_mean
|
| 413 |
-
|
| 414 |
-
|
| 415 |
|
| 416 |
-
|
|
|
|
|
|
|
|
|
|
| 417 |
info = samples.getInfo()
|
| 418 |
rows = []
|
| 419 |
for f in info.get('features', []):
|
| 420 |
p = f.get('properties', {}) or {}
|
| 421 |
rows.append({
|
| 422 |
-
'rowid':
|
| 423 |
-
'ndvi_mean':
|
| 424 |
'ndvi_median': p.get('ndvi_median'),
|
| 425 |
-
'ndvi_max':
|
| 426 |
-
'ntl_mean':
|
| 427 |
-
'ntl_median':
|
| 428 |
-
'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))
|