🔭

RでGoogle Earth Engineを操作できるようにする

に公開
変更履歴・概要

2025/3/29 更新

LandsatのCollection 1データセットが利用できなくなったのでコードを修正。あわせて,earthengine-apiについて,rgeeライブラリで未検証の最新バージョンを利用するための対応を追記。

2024/3/28 更新

rgeeライブラリを1.1.5から1.1.7に更新したので,対応するearthengine-apiのバージョンを0.1.323から0.1.370に変更。

宙畑の衛星データ分析におススメの言語は?言語別の特徴まとめとElixir紹介へのリンクを追加。

2024/2/1 初版

2023年6~9月に行った環境構築作業のメモを整理したものです。一部足りないところや分かりにくいところがあるかもしれません。また,それぞれの環境によって違いがあるかもしれませんが,ご容赦ください。

できるようになること

RからGoogle Earth Engine(GEE)を操作できる環境を構築します。

Google Earth Engine(GEE)って何?

Googleから提供される,地球観測衛星が取得した大量のデータを,クラウド上で(研究・教育・非営利目的であれば)無料で解析・利用できるサービスです。

https://earthengine.google.com/

https://developers.google.com/earth-engine

2023年10月にはGEEに関する書籍もでています。

Cloud-Based Remote Sensing with Google Earth Engine

日本語での説明は以下のサイトがあります。

Hiroki MiZUOCHIさんのページ

宙畑のページ

片木仁さん・奈良原顕郎さんのページ

sindicumさんのページ

なお,データを見るだけなら,Earth Engine Explorerで可能です。使い方は,Google Earth Engine の概要を参考にしてください。

ほかにも,RESTEC(一般財団法人リモート・センシング技術センター)が作成しているVEGAでも,LandsatシリーズやSentinelシリーズなどの衛星データを見ることができます。

Rで操作するためには

GoogleからはJavaScriptとPythonがサポートされています。Rから操作するには,rgeeライブラリを利用します。rgeeライブラリはPython経由でGEEにアクセスすることを可能にしたライブラリです。

https://cran.r-project.org/web/packages/rgee/index.html

https://github.com/r-spatial/rgee

https://qiita.com/iwasaki_kenichi/items/b4db19204cf4343e82ce

なお,宙畑に衛星データ分析に用いられる言語について説明記事がありました。Rについても言及があるので,参考にしてください。

衛星データ分析におススメの言語は?言語別の特徴まとめとElixir紹介

rgeeライブラリのインストール

Rへはrgeeライブラリは以下によりインストールできます。

install.packages('rgee')

ついでに,rgeeExtraライブラリもインストールしておきます。

https://github.com/r-earthengine/rgeeExtra

remotes::install_github("r-earthengine/rgeeExtra")

ただし,rgeeライブラリを利用するには以下の条件を満たす必要があります。

  • GEEへのGoogleアカウント登録
  • Python環境の構築

GEEへのアカウント登録

GEEを利用するにはGoogleアカウントが必要ですので,まずは用意してください。

GEEへのアカウント登録は,sindicumさんのGoogle Earth EngineとPythonで始める衛星データ利用入門第2章に説明があるので参考にしてください。概要としては,GEEのWebページにアクセスし,右上のGet Startedをクリックして,画面の指示にしたがってCloud Projectを作成します。

成功したらEarth Engine Code Editorが立ち上がります。JavaScriptで操作するための画面で,普段は使いませんので,詳しい説明は省略します。

Python 環境の構築

最初に書いたとおりrgeeライブラリは(reticulateライブラリを利用して)PythonのEarth Engine APIからアクセスします。そのため,GEEにアクセスできるPython環境を用意する必要があります。

rgeeライブラリの説明では,Pythonの操作経験がない場合には以下で構築することが推奨されています。

rgee::ee_install(py_env = "rgee")

ここでは,(既にcondaをインストールしてあったので)独自にPython環境を構築し,Rから操作できるようにします。インストールはGEEの以下の説明を参考にしました。

https://developers.google.com/earth-engine/guides/python_install

https://developers.google.com/earth-engine/guides/python_install-conda

まず,condaをインストールします。私の環境はAnacondaで構築していますが,Minicondaでもいいと思います。インストール方法は他のサイトでたくさん紹介されているので,そちらを参考にしてください。

次に,GEE用の仮想環境を構築します。condaのコマンドで以下のとおり入力していきます。最後のeemontは必須ではありませんが,rgeeExtraライブラリをインストールすると利用可能なeeExtraの読み込みが,EEextra_PYTHON_PACKAGE$を介さずに利用できます(https://eemont.readthedocs.io/en/latest/guide/eemontR.html)。

conda create -n r_gee python=3.8.16
conda activate r_gee
conda install -c conda-forge earthengine-api==0.1.323 # rgee1.1.7の検証が0.1.323のため。
conda install numpy # earthengine-apiにはnumpyが必要
conda install -c conda-forge eemont #ついでにインストール

GEEへのアクセス認証を行うため,r_gee仮想環境で以下のコマンドを入力し,求められるとおりに操作していってください。

earthengine authenticate

Python環境を構築できたかは,r_gee仮想環境でPythonを起動して以下のとおり試してみてください。なお,プロジェクト名にはGEEへのアカウント登録で入力したproject-IDを入力します(https://developers.google.com/earth-engine/guides/access)。

import ee
ee.Initialize(project='プロジェクト名') 
print(ee.Image("NASA/NASADEM_HGT/001").get("title").getInfo())

rgeeライブラリでの環境構築

次に,以下でrgeeライブラリの環境を構築します。なお,RStudio(V1.4以上)のPythonパスの設定でも可能だったようです。

rgee::ee_install_set_pyenv(
  py_path = "C:/Users/####/anaconda3/envs/r_gee", # Change it for your own Python PATH
  py_env = "r_gee" # Change it for your own Python ENV
)

GEEを始めるには,ee_Initialize(project = 'プロジェクト名')とします。なお,データ保存用にGoogle Driveを利用するにはee_Initialize(project = 'プロジェクト名', drive=T)とします(最初にはtokenの設定が必要ですが,特に意識しなくても,表示される画面に従えば設定ができるようです)。

インストール後には以下で環境を確認できます。

rgee::ee_check_python()
rgee::ee_check_python_packages()
rgee::ee_check_credentials()

最新のearth engine apiを利用する方法

RでHansenGlobalForestChangeデータと雲除去したSentinel画像を表示してみるで記載したlinkcollection関数を利用するためにはearthengine-apiのバージョンをあげる必要がありますが,rgeeライブラリで検証されたバージョンではなく,ee_Initialize()ee_check_root_folderに関するエラーがでてGEEの認証ができません。

https://github.com/r-spatial/rgee/issues/355

https://github.com/r-spatial/rgee/issues/370

そこで,reticulateライブラリを利用してpythonコードを直接実行します。

reticulate::py_run_string("import ee")
reticulate::py_run_string("ee.Initialize(project='プロジェクト名')")

これだけでは,rgeeライブラリにある関数のうち,RへGoogleDrive等を経由してデータを読み込ませるee_as_sf()などが機能しません。原因は,これらの関数がee_Initialize()により生成されるrgee_sessioninfo.txtを読み込もうとするのですが,上記のreticulateライブラリから直接認証させた状態ではtxtファイルが生成されていないことによるものです。以下のファイルをRスクリプトファイルとして保存した上で,source()により読み込みます。

ee_sessioninfo.R
## ee_sessioninfo.R
## rgeeライブラリで`rgee_sessioninfo.txt`を生成する関数のみを抽出したファイル
# ee_utils.R
ee_utils_py_to_r <- function(x) {
  p_r <- suppressWarnings(try(reticulate::py_to_r(x), silent = TRUE))
  if (class(p_r) %in% 'try-error') {
    return(x)
  } else {
    return(p_r)
  }
}
# utils-auth.R
ee_source_python <- function(oauth_func_path) {
  module_name <- gsub("\\.py$", "", basename(oauth_func_path))
  module_path <- dirname(oauth_func_path)
  reticulate::import_from_path(module_name, path = module_path, convert = FALSE)
}
ee_connect_to_py <- function(path, n = 5) {
  ee_utils <- try(ee_source_python(oauth_func_path = path), silent = TRUE)
  # counter added to prevent problems with reticulate
  con_reticulate_counter <- 1
  while (any(class(ee_utils) %in%  "try-error")) {
    ee_utils <- try(ee_source_python(path), silent = TRUE)
    con_reticulate_counter <- con_reticulate_counter + 1
    if (con_reticulate_counter == (n + 1)) {
      python_path <- reticulate::py_discover_config()
      message_con <- c(
        sprintf("The current Python PATH: %s",
                bold(python_path[["python"]])),
        "does not have the Python package \"earthengine-api\" installed. Do you restarted/terminated",
        "your R session after install miniconda or run ee_install()?",
        "If this is not the case, try:",
        "> ee_install_upgrade(): Install the latest Earth Engine Python version.",
        "> reticulate::use_python(): Refresh your R session and manually set the Python environment with all rgee dependencies.",
        "> ee_install(): To create and set a Python environment with all rgee dependencies.",
        "> ee_install_set_pyenv(): To set a specific Python environment."
      )
      
      stop(paste(message_con,collapse = "\n"))
    }
  }
  return(ee_utils)
}
ee_check_init <- function() {
  
  # if EARTHENGINE_PYTHON is defined, then send it to RETICULATE_PYTHON
  earthengine_python <- Sys.getenv("EARTHENGINE_PYTHON", unset = NA)
  if (!is.na(earthengine_python))
    Sys.setenv(RETICULATE_PYTHON = earthengine_python)
  
  # Check ee_utils.py sanity
  ee_current_version <- system.file("python/ee_utils.py", package = "rgee")
  if (!file.exists(ee_current_version)) {
    stop(
      sprintf(
        "The file %s does not exist in your system. Please re-install rgee: %s",
        ee_current_version,
        "remotes::install_github(\"r-spatial/rgee\")."
      )
    )
  }
  
  
  ee_utils <- ee_connect_to_py(path = ee_current_version, n = 5)
  earthengine_version <- ee_utils_py_to_r(ee_utils$ee_getversion())
  
  # is earthengine-api greater than 0.1.317?
  #if (as.numeric(gsub("\\.","",earthengine_version)) < 01317) {
  #  warning(
  #    "Update your earthengine-api installations to v0.1.317 or greater. ",
  #    "Earlier versions are not compatible with recent ",
  #    "changes to the Earth Engine backend."
  #  )
  # }
  
  list(earthengine_version = earthengine_version, ee_utils = ee_utils)
}
ee_check_packages <- function(fn_name, packages) {
  pkg_exists <- rep(NA, length(packages))
  counter <- 0
  for(package in packages) {
    counter <- counter + 1
    pkg_exists[counter] <- requireNamespace(package, quietly = TRUE)
  }
  
  if (!all(pkg_exists)) {
    to_install <- packages[!pkg_exists]
    to_install_len <- length(to_install)
    error_msg <- sprintf(
      "%s required the %s: %s. Please install %s first.",
      bold(fn_name),
      if (to_install_len == 1) "package" else "packages",
      paste0(bold(to_install), collapse = ", "),
      if (to_install_len == 1) "it" else "them"
    )
    stop(error_msg)
  }
}
# utils-credentials.R
ee_create_credentials_drive <- function(user=NULL, ee_utils, quiet) {
  # check googledrive R package installation
  ee_check_packages("ee_Initialize", "googledrive")
  
  # Check sanity of earth-engine and return ee_utils.py module
  init <- ee_check_init()
  ee_utils <- init$ee_utils
  
  
  # setting drive folder
  if (is.null(user)) {
    ee_path <- ee_utils_py_to_r(ee_utils$ee_path())
    ee_path_user <- ee_path
  } else {
    ee_path <- ee_utils_py_to_r(ee_utils$ee_path())
    ee_path_user <- sprintf("%s/%s", ee_path, user)
  }
  
  # Load GD credentials (googledrive::drive_auth)
  repeat {
    full_credentials <- list.files(path = ee_path_user, full.names = TRUE)
    drive_condition <- grepl(".*_.*@.*", basename(full_credentials))
    
    # If the googledrive credential does not exist run googledrive::drive_auth
    if (!any(drive_condition)) {
      suppressMessages(
        googledrive::drive_auth(
          email = NULL,
          cache = ee_path_user
        )
      )
    } else {
      drive_credentials <- full_credentials[drive_condition]
      email <- sub("^[^_]*_", "", basename(drive_credentials)) # Obtain the email
      suppressMessages(
        googledrive::drive_auth(
          email = email,
          cache = ee_path_user
        )
      )
      
      # This lines is to avoid that users have multiple token file.
      # It delete the older one if the system detect two different token files.
      new_full_credentials <- list.files(path = ee_path_user, full.names = TRUE)
      new_drive_condition <- grepl(".*_.*@.*", basename(new_full_credentials))
      if (sum(new_drive_condition) > 1) {
        files_credentials_time <- file.info(new_full_credentials[new_drive_condition])$ctime
        drive_credential_to_remove <- new_full_credentials[which.min(files_credentials_time)]
        if (!quiet) {
          message("Removing previous Google Drive Token ....")
        }
        file.remove(drive_credential_to_remove)
      }
      break
    }
  }
  
  # Move credential to the main folder is user is set
  if (!is.null(user)) {
    # Clean previous and copy new GD credentials in ./earthengine folder
    clean_drive <- list.files(ee_path, ".*_.*@.*", full.names = TRUE) %in% list.dirs(ee_path)
    unlink(
      list.files(ee_path, ".*_.*@.*", full.names = TRUE)[!clean_drive]
    )
    file.copy(
      from = drive_credentials,
      to = sprintf("%s/%s", ee_path, basename(drive_credentials)),
      overwrite = TRUE
    )
  }
  invisible(drive_credentials)
}
ee_create_credentials_gcs_ <- function(user, ee_utils) {
  # check packages
  ee_check_packages("ee_Initialize", "googleCloudStorageR")
  
  # Check sanity of earth-engine and return ee_utils.py module
  init <- ee_check_init()
  ee_utils <- init$ee_utils
  
  # setting gcs folder
  if (is.null(user)) {
    ee_path <- suppressWarnings(ee_utils_py_to_r(ee_utils$ee_path()))
    ee_path_user <- ee_path
  } else {
    ee_path <- suppressWarnings(ee_utils_py_to_r(ee_utils$ee_path()))
    ee_path_user <- sprintf("%s/%s", ee_path, user)
  }
  
  # gcs_credentials
  full_credentials <- list.files(path = ee_path_user, full.names = TRUE)
  gcs_condition <- grepl(".json", full_credentials)
  
  if (!any(gcs_condition)) {
    gcs_text <- paste(
      sprintf("Unable to find a service account key (SAK) file in: %s",  crayon::bold(ee_path_user)),
      "To solve this problem:",
      "  1) download it from your Google cloud console",
      "  2) validate it using rgee::ee_utils_sak_validate [OPTIONAL].",
      "  3) Use rgee::ee_utils_sak_copy to set the SaK in rgee.",
      "A tutorial to obtain the SAK file is available at:",
      "> https://r-spatial.github.io/rgee/articles/rgee05.html",
      crayon::bold("As long as you haven't saved a SKA file, the following functions will not work:"),
      "- rgee::ee_gcs_to_local()",
      "- ee_extract(..., via = \"gcs\")",
      "- ee_as_raster(..., via = \"gcs\")",
      "- ee_as_stars(..., via = \"gcs\")",
      "- ee_as_sf(..., via = \"gcs\")",
      "- sf_as_ee(..., via = \"gcs_to_asset\")",
      "- gcs_to_ee_image",
      "- raster_as_ee",
      "- local_to_gcs",
      "- stars_as_ee",
      sep = "\n"
    )
    gcs_info <- list(path = NA, message = gcs_text)
  } else {
    gcs_credentials <- full_credentials[gcs_condition]
    googleCloudStorageR::gcs_auth(gcs_credentials)
    
    if (!is.null(user)) {
      unlink(list.files(ee_path, ".json", full.names = TRUE))
      file.copy(
        from = gcs_credentials,
        to = sprintf("%s/%s", ee_path, basename(gcs_credentials)),
        overwrite = TRUE
      )
      gcs_info <- list(path = gcs_credentials, message = NA)
    } else {
      gcs_info <- list(path = gcs_credentials, message = NA)
    }
  }
  gcs_info
}
# utils-auth.R & ee_Initialize.R
ee_sessioninfo <- function(email = NULL,
                           user = NULL,
                           drive = NULL,
                           gcs = NULL) {
  init <- ee_check_init()
  ee_utils <- init$ee_utils
  
  oauth_func_path <- system.file("python/ee_utils.py", package = "rgee")
  utils_py <- ee_source_python(oauth_func_path)
  sessioninfo <- sprintf(
    "%s/rgee_sessioninfo.txt",
    ee_utils_py_to_r(utils_py$ee_path())
  )
  if (is.null(email)) {
    email <- NA
  }
  # Loading all the credentials: earthengine, drive and GCS.
  drive_credentials <- NA
  gcs_credentials <- list(path = NA, message = NA)
  
  if (drive) {
    ee_check_packages("ee_Initialize", "googledrive")
    
    # drive init message
    # if (!quiet) ee_message_02(init = TRUE)
    
    # If the user is not NULL copy the drive credential in the subfolder
    drive_credentials <- ee_create_credentials_drive(user, ee_utils, quiet = quiet)
    # test_drive_privileges(user)
    
    # if (!quiet) ee_message_02(init = FALSE)
  }
  if (gcs) {
    ee_check_packages("ee_Initialize", "googleCloudStorageR")
    
    # if (!quiet) ee_message_03(init=TRUE, gcs_credentials)
    
    # Load GCS credentials
    gcs_credentials <- ee_create_credentials_gcs(user, ee_utils)
    
    # if (!quiet) ee_message_03(init=FALSE, gcs_credentials)
  }
  
  ## rgee session file
  # options(rgee.gcs.auth = gcs_credentials[["path"]])
  
  # if (!quiet) ee_message_04(init = TRUE)
  
  df <- data.frame(
    email = email, user = user, drive_cre = drive_credentials, gcs_cre = gcs_credentials[["path"]],
    stringsAsFactors = FALSE
  )
  write.table(df, sessioninfo, row.names = FALSE)
}
source("ee_sessioninfo.R")
ee_sessioninfo(email = "ndef", user = "GEEのユーザ名", drive=T, gcs=F)

試してみよう

library(rgee)
library(leaflet) # attributionの挿入に必要
ee_Initialize(project = 'プロジェクト名')

Earth Engine JavaScript API Documentationをもとに,サンフランシスコ湾周辺のLandsat8による撮影画像を表示させてみましょう。

# Load an image.
landsat <- ee$Image("LANDSAT/LC08/C02/T1_TOA/LC08_044034_20140318")
# Define the visualization parameters.
vizParams <- list(
  bands = c("B5", "B4", "B3"),
  min = 0,
  max = 0.5,
  gamma = c(0.95, 1.1, 1)
)
# Center the map and display the image.
Map$setCenter(lon = -122.1899, lat = 37.5010, zoom = 10) # San Francisco Bay
Map$addLayer(landsat, vizParams, "false color composite") %>%
  addTiles(urlTemplate = "", attribution = '| Landsat-8 image courtesy of the U.S. Geological Survey')


Session info
sessionInfo()
## R version 4.3.3 (2024-02-29 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19045)
## 
## Matrix products: default
## 
## 
## locale:
## [1] LC_COLLATE=Japanese_Japan.utf8  LC_CTYPE=Japanese_Japan.utf8    LC_MONETARY=Japanese_Japan.utf8
## [4] LC_NUMERIC=C                    LC_TIME=Japanese_Japan.utf8    
## 
## time zone: Etc/GMT-9
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] leaflet_2.2.2  rgee_1.1.7     here_1.0.1     fs_1.6.5       rmarkdown_2.29
## 
## loaded via a namespace (and not attached):
##  [1] class_7.3-22            KernSmooth_2.23-22      stringi_1.8.7           lattice_0.22-5         
##  [5] digest_0.6.37           magrittr_2.0.3          evaluate_1.0.3          grid_4.3.3             
##  [9] fastmap_1.2.0           rprojroot_2.0.4         jsonlite_2.0.0          Matrix_1.6-5           
## [13] processx_3.8.6          e1071_1.7-16            chromote_0.5.0          DBI_1.2.3              
## [17] promises_1.3.2          ps_1.9.0                crosstalk_1.2.1         codetools_0.2-19       
## [21] jquerylib_0.1.4         cli_3.6.1               rlang_1.1.5             crayon_1.5.3           
## [25] units_0.8-7             base64enc_0.1-3         withr_3.0.2             yaml_2.3.10            
## [29] tools_4.3.3             raster_3.6-32           webshot_0.5.5           reticulate_1.42.0      
## [33] vctrs_0.6.5             R6_2.6.1                png_0.1-8               proxy_0.4-27           
## [37] lifecycle_1.0.4         classInt_0.4-11         stringr_1.5.1           leaflet.providers_2.0.0
## [41] htmlwidgets_1.6.4       pkgconfig_2.0.3         later_1.4.1             terra_1.8-29           
## [45] pillar_1.10.1           glue_1.8.0              Rcpp_1.0.14             sf_1.0-20              
## [49] xfun_0.51               tibble_3.2.1            rstudioapi_0.17.1       knitr_1.50             
## [53] websocket_1.4.2         htmltools_0.5.8.1       webshot2_0.1.1          leafem_0.2.3           
## [57] compiler_4.3.3          sp_2.2-0

Discussion