├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .markdownlintrc
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── docs
├── README.md
├── developer-guide
│ ├── authentication.md
│ ├── get-started.md
│ └── pydeck-integration.md
├── table-of-contents.json
└── whats-new.md
├── examples
├── image-collection
│ ├── README.md
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── image
│ ├── README.md
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── intl-boundary
│ ├── README.md
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── noaa-hurricanes
│ ├── README.md
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── power-plants
│ ├── README.md
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── shared
│ ├── google-login
│ │ ├── google-api.js
│ │ └── google-login-provider.js
│ ├── google-maps
│ │ └── deck-with-google-maps.js
│ ├── index.js
│ ├── libs
│ │ └── ee_api_js.js
│ └── react-components
│ │ ├── earthengine-icon.js
│ │ ├── google-login-pane.js
│ │ ├── icon-wrapper.js
│ │ └── info-box.js
└── terrain
│ ├── README.md
│ ├── app.js
│ ├── index.html
│ ├── package.json
│ └── webpack.config.js
├── lerna.json
├── modules
└── earthengine-layers
│ ├── README.md
│ ├── docs
│ └── api-reference
│ │ ├── earthengine-layer.md
│ │ └── earthengine-terrain-layer.md
│ ├── package.json
│ ├── rollup.config.js
│ ├── src
│ ├── bundle.js
│ ├── earth-engine-layer.js
│ ├── earth-engine-terrain-layer.js
│ ├── ee-api.js
│ ├── index.js
│ └── utils.js
│ └── test
│ ├── index.js
│ ├── tile-layer
│ └── tile-layer.spec.js
│ └── tileset-2d
│ ├── tileset-2d.spec.js
│ └── viewport-util.spec.js
├── ocular-dev-tools.config.js
├── package.json
├── py
├── .gitignore
├── CHANGELOG.md
├── MANIFEST.in
├── README.md
├── docs
│ ├── pydeck-earthengine-layer.md
│ └── pydeck-earthengine-terrain-layer.md
├── examples
│ ├── Introduction.ipynb
│ ├── README.md
│ ├── international_boundaries.ipynb
│ ├── noaa_hurricanes.ipynb
│ ├── power_plants.ipynb
│ ├── temperature.ipynb
│ └── terrain.ipynb
├── pydeck_earthengine_layers
│ ├── __init__.py
│ └── pydeck_earthengine_layers.py
├── requirements.txt
├── requirements_dev.txt
├── setup.cfg
└── setup.py
├── test
├── browser.js
├── compare-image.js
├── modules.js
├── node-examples.js
├── node.js
├── render
│ ├── constants.js
│ ├── index.js
│ ├── jupyter-widget-custom-layer-bundle.js
│ ├── jupyter-widget-test.html
│ └── jupyter-widget.js
├── size
│ └── import-nothing.js
└── utils
│ └── utils.js
├── webpack.config.js
├── website
├── .eslintignore
├── .gitignore
├── gatsby-config.js
├── gatsby-node.js
├── package.json
├── src
│ └── components
│ │ └── README.md
├── static
│ ├── CNAME
│ └── images
│ │ ├── icon-high-precision.svg
│ │ ├── image-animation-example-screenshot.gif
│ │ ├── image-animation-example-still.png
│ │ ├── image-animation-wide_less-bright.gif
│ │ ├── image-example-screenshot.jpg
│ │ ├── intl-boundaries.jpg
│ │ ├── noaa_hurricanes.jpg
│ │ ├── power-plants.jpg
│ │ ├── terrain.jpg
│ │ └── unfolded.png
└── templates
│ └── index.jsx
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | libs/
2 | dist/
3 | node_modules/
4 | workers/
5 | dist.js
6 | *.min.js
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | // prettier-ignore
2 | module.exports = {
3 | parserOptions: {
4 | ecmaVersion: 2018
5 | },
6 | plugins: ['react', 'import'],
7 | extends: ['uber-jsx', 'uber-es2015', 'prettier', 'prettier/react', 'plugin:import/errors'],
8 | overrides: [{
9 | files: ['*.spec.js', 'webpack.config.js', '**/bundle/*.js'],
10 | rules: {
11 | 'import/no-extraneous-dependencies': 0
12 | }
13 | }],
14 | settings: {
15 | 'import/core-modules': [
16 | '@luma.gl/core',
17 | '@luma.gl/constants',
18 | 'math.gl',
19 | '@math.gl/web-mercator'
20 | ],
21 | react: {
22 | version: 'detect'
23 | }
24 | },
25 | rules: {
26 | 'guard-for-in': 0,
27 | 'no-inline-comments': 0,
28 | camelcase: 0,
29 | 'react/forbid-prop-types': 0,
30 | 'react/no-deprecated': 0,
31 | 'import/no-unresolved': ['error', {ignore: ['test']}],
32 | 'import/no-extraneous-dependencies': 0 // ['error', {devDependencies: false, peerDependencies: true}]
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | **/*.min.js
4 | node_modules/
5 | coverage/
6 | test/**/*-failed.png
7 | .nyc_output/
8 | .reify-cache/
9 |
10 | */**/yarn.lock
11 | yarn-error.log
12 | package-lock.json
13 |
14 | .vscode/
15 | .project
16 | .idea
17 | .DS_Store
18 | *.zip
19 | *.rar
20 | *.log
21 | .exit_code
22 |
23 | # Jupyter Notebook
24 | .ipynb_checkpoints
25 |
--------------------------------------------------------------------------------
/.markdownlintrc:
--------------------------------------------------------------------------------
1 | {
2 | "default": true,
3 | "colors": true,
4 | "header-increment": false,
5 | "line-length": false,
6 | "ul-style": {"style": "sublist"},
7 | "no-trailing-punctuation": {"punctuation": ".,;:"},
8 | "no-duplicate-header": false,
9 | "no-inline-html": false,
10 | "no-hard-tabs": false,
11 | "whitespace": false
12 | }
13 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | workers/
2 | **/dist*/**/*.js
3 | dist.js
4 | *.min.js
5 | *.md
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | printWidth: 100
2 | semi: true
3 | singleQuote: true
4 | trailingComma: none
5 | bracketSpacing: false
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Unfolded
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # earthengine-layers
2 |
3 | This repository contains [deck.gl](https://deck.gl) layers to visualize [Google Earth Engine](https://github.com/google/earthengine-api) objects in JavaScript and Python.
4 |
5 | For documentation please visit the [website](https://earthengine-layers.com).
6 |
7 | ## License
8 |
9 | MIT License
10 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # earthengine-layers
2 |
3 | [deck.gl](https://deck.gl) layers for Google Earth Engine for JavaScript and
4 | Python.
5 |
6 | The primary export is the `EarthEngineLayer` layer, which accepts [Google Earth
7 | Engine API](https://github.com/google/earthengine-api) objects (`ee.Image`,
8 | `ee.ImageCollection`, `ee.FeatureCollection`) through its `eeObject` prop, and
9 | renders the desired datasets via a customized deck.gl `TileLayer`.
10 |
11 | ## License
12 |
13 | MIT License
14 |
--------------------------------------------------------------------------------
/docs/developer-guide/authentication.md:
--------------------------------------------------------------------------------
1 | # Authentication
2 |
3 | Authenticating with Earth Engine services is likely to be the biggest
4 | complication for developers who are not already working with the EE API.
5 |
6 | While the Earth Engine API documentation and forums are the official source of
7 | information on how to authenticate, this is a quick overview.
8 |
9 | ## Pydeck
10 |
11 | If using the `pydeck-earthengine-layers` package, any extra authentication steps
12 | should be handled for you automatically. You'll need only the standard
13 | authentication required by the `earthengine-api` library to work with Earth
14 | Engine Python objects, i.e.:
15 |
16 | ```py
17 | import ee
18 |
19 | # Necessary only on the first install; opens a Google sign-in prompt
20 | ee.Authenticate()
21 |
22 | # Necessary in every Python session
23 | ee.Initialize()
24 | ```
25 |
26 | ## JavaScript
27 |
28 | The `EarthEngineLayer` provides an `initializeEEApi` helper to authenticate and
29 | initialize the JavaScript Earth Engine library. This calls
30 | `ee.data.authenticateViaOauth()` or `ee.setToken()`, and then `ee.initialize()`,
31 | and returns a `Promise` that resolves when authentication and initialization is
32 | completed and the EE API is ready to use.
33 |
34 | ### Authenticating via Login (OAuth2)
35 |
36 | The easiest way to authenticate from a JavaScript application is to use
37 | `initializeEEApi` with a `clientId`.
38 |
39 | Note that this requires:
40 |
41 | - registering a Google client id
42 | - requesting Earth Engine access for that client
43 | - adding the URLs from which the application will be served to the whitelisted origins for that client id.
44 | - Visitors to your website must have a Google Account that is approved for use with Earth Engine.
45 |
46 | ## Authenticating via Access Token
47 |
48 | If you have an existing OAuth2 authentication workflow, you can use that to
49 | generate access tokens, which you then pass to
50 |
51 | ```js
52 | initializeEEApi({token})
53 | ```
54 |
55 | An access token is valid for a short period of time, usually one hour, and any
56 | requests to the EarthEngine backend will fail after that time until a new access
57 | token is provided.
58 |
--------------------------------------------------------------------------------
/docs/developer-guide/get-started.md:
--------------------------------------------------------------------------------
1 | # Get Started
2 |
3 | ## Installing
4 |
5 | ```sh
6 | $ yarn add @google/earthengine
7 | $ yarn add @unfolded/earthengine-layers
8 | $ yarn add deck.gl
9 | ```
10 |
11 | ## Using in Python
12 |
13 | The `EarthEngineLayer` can be used as a plugin layer to
14 | [`pydeck`](https://pydeck.gl). For more information see [pydeck
15 | integration](/docs/developer-guide/pydeck-integration.md).
16 |
17 | ## Using in JavaScript
18 |
19 | To use the `EarthEngineLayer` in your JavaScript application to visualize Earth
20 | Engine API objects (such as `ee.Image` objects):
21 |
22 | ```js
23 | import {Deck} from '@deck.gl/core';
24 | import {EarthEngineLayer} from '@unfolded/earthengine-layers';
25 | import ee from '@google/earthengine';
26 |
27 | const eeObject = ee.Image('CGIAR/SRTM90_V4');
28 | const visParams = {
29 | min: 0,
30 | max: 4000,
31 | palette: ['006633', 'E5FFCC', '662A00', 'D8D8D8', 'F5F5F5']
32 | };
33 |
34 | new Deck({
35 | ...,
36 | layers: new EarthEngineLayer({eeObject, visParams})
37 | });
38 | ```
39 |
40 | ## Cloning the Repo
41 |
42 | ```sh
43 | git clone https://github.com/UnfoldedInc/earthengine-layers
44 | cd earthengine-layers
45 | ```
46 |
47 | ## Running Examples
48 |
49 | You will need a Google client id which has been approved for use with Earth
50 | Engine. You also need to make sure you log in with a Google user account which
51 | has been approved for use with earth engine. [Go
52 | here](https://console.cloud.google.com/apis/credentials/oauthclient) to create a
53 | client ID, and add appropriate urls (such as `http://localhost:8080` for
54 | examples) in the "Authorized JavaScript origins" section.
55 |
56 | ```sh
57 | cd examples/image
58 | EE_CLIENT_ID=
63 |
64 | Hourly temperature
65 | {' '}
66 | animated using an ee.ImageCollection
object.
67 | Note that this currently does not work on Safari or iOS devices.
68 |
ee.Image
object.
60 | ee.FeatureCollection
object.
70 | 23 |
24 | 25 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /examples/intl-boundary/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deck.gl-earthengine-feature-collection-tile-demo", 3 | "version": "1.2.1", 4 | "license": "MIT", 5 | "private": true, 6 | "scripts": { 7 | "start": "webpack-dev-server --progress --hot --open", 8 | "start-local": "webpack-dev-server --env.local --progress --hot --open", 9 | "build": "webpack -p" 10 | }, 11 | "dependencies": { 12 | "@babel/plugin-proposal-class-properties": "^7.5.5", 13 | "@deck.gl/core": "^8.2.5", 14 | "@deck.gl/geo-layers": "^8.2.5", 15 | "@deck.gl/layers": "^8.2.5", 16 | "@deck.gl/mesh-layers": "^8.2.5", 17 | "@deck.gl/react": "^8.2.5", 18 | "@google/earthengine": "^0.1.234", 19 | "@unfolded.gl/earthengine-layers": "1.2.1", 20 | "react": "^16.3.0", 21 | "react-dom": "^16.3.0", 22 | "styled-components": "^5.0.1" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.0.0", 26 | "@babel/preset-react": "^7.0.0", 27 | "babel-loader": "^8.0.5", 28 | "webpack": "^4.20.2", 29 | "webpack-cli": "^3.1.2", 30 | "webpack-dev-server": "^3.1.1" 31 | }, 32 | "gitHead": "c57dbd5b98fb743617ed6770ed5bf031c4f09410" 33 | } 34 | -------------------------------------------------------------------------------- /examples/intl-boundary/webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const MODULE_ALIASES = {}; // require('../aliases'); 3 | const {resolve} = require('path'); 4 | 5 | // eslint-disable-next-line 6 | if (!process.env.EE_CLIENT_ID) { 7 | throw new Error('Environment variable EE_CLIENT_ID not set'); 8 | } 9 | 10 | const CONFIG = { 11 | mode: 'development', 12 | 13 | entry: { 14 | app: './app.js' 15 | }, 16 | 17 | output: { 18 | library: 'App' 19 | }, 20 | 21 | resolve: { 22 | // Make src files outside of this dir resolve modules in our node_modules folder 23 | modules: [resolve(__dirname, '.'), resolve(__dirname, 'node_modules'), 'node_modules'] 24 | }, 25 | 26 | module: { 27 | rules: [ 28 | { 29 | // Transpile ES6 to ES5 with babel 30 | // Remove if your app does not use JSX or you don't need to support old browsers 31 | test: /\.js$/, 32 | loader: 'babel-loader', 33 | exclude: [/node_modules/], 34 | include: [ 35 | resolve(__dirname, '.'), 36 | resolve(__dirname, '../shared'), 37 | /modules\/.*\/src/, 38 | ...Object.values(MODULE_ALIASES) 39 | ], 40 | options: { 41 | presets: ['@babel/preset-react'], 42 | ignore: [/ee_api/] 43 | } 44 | } 45 | ] 46 | }, 47 | 48 | plugins: [new webpack.EnvironmentPlugin(['EE_CLIENT_ID'])] 49 | }; 50 | 51 | // This line enables bundling against src in this repo rather than installed module 52 | module.exports = CONFIG; // env => (env ? require('../webpack.config.local')(CONFIG)(env) : CONFIG); 53 | -------------------------------------------------------------------------------- /examples/noaa-hurricanes/README.md: -------------------------------------------------------------------------------- 1 | ## Example: Use deck.gl with Google Earth Engine 2 | 3 | Uses [Webpack](https://github.com/webpack/webpack) to bundle files and serves it 4 | with [webpack-dev-server](https://webpack.js.org/guides/development/#webpack-dev-server). 5 | 6 | ## Usage 7 | 8 | To install dependencies: 9 | 10 | ```bash 11 | npm install 12 | # or 13 | yarn 14 | ``` 15 | 16 | Commands: 17 | * `npm start` is the development target, to serves the app and hot reload. 18 | * `npm run build` is the production target, to create the final bundle and write to disk. 19 | -------------------------------------------------------------------------------- /examples/noaa-hurricanes/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render} from 'react-dom'; 3 | 4 | import {EarthEngineLayer} from '@unfolded.gl/earthengine-layers'; 5 | import ee from '@google/earthengine'; 6 | 7 | import {DeckWithGoogleMaps, GoogleLoginProvider, GoogleLoginPane, InfoBox} from '../shared'; 8 | 9 | // Add a EE-enabled Google Client id here (or inject it with e.g. a webpack environment plugin) 10 | const EE_CLIENT_ID = process.env.EE_CLIENT_ID; // eslint-disable-line 11 | const GOOGLE_MAPS_TOKEN = process.env.GoogleMapsAPIKey; // eslint-disable-line 12 | 13 | const INITIAL_VIEW_STATE = { 14 | longitude: -53, 15 | latitude: 36, 16 | zoom: 3, 17 | pitch: 0, 18 | bearing: 0 19 | }; 20 | 21 | export default class App extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = {eeObject: null, asVector: true}; 25 | 26 | this.loginProvider = new GoogleLoginProvider({ 27 | scopes: ['https://www.googleapis.com/auth/earthengine'], 28 | clientId: EE_CLIENT_ID, 29 | onLoginChange: this._onLoginSuccess.bind(this) 30 | }); 31 | } 32 | 33 | async _onLoginSuccess(user, loginProvider) { 34 | await EarthEngineLayer.initializeEEApi({clientId: EE_CLIENT_ID}); 35 | const {year = '2017'} = this.props; 36 | 37 | // Show hurricane tracks and points for 2017. 38 | const hurricanes = ee.FeatureCollection('NOAA/NHC/HURDAT2/atlantic'); 39 | 40 | const points = hurricanes.filter(ee.Filter.date(ee.Date(year).getRange('year'))); 41 | 42 | // Find all of the hurricane ids. 43 | const storm_ids = points 44 | .toList(1000) 45 | .map(point => ee.Feature(point).get('id')) 46 | .distinct(); 47 | 48 | // Create a line for each hurricane. 49 | const lines = ee.FeatureCollection( 50 | storm_ids.map(storm_id => { 51 | const pts = points 52 | .filter(ee.Filter.eq('id', ee.String(storm_id))) 53 | .sort('system:time_start'); 54 | const line = ee.Geometry.LineString(pts.geometry().coordinates()); 55 | const feature = ee.Feature(line); 56 | return feature.set('id', storm_id); 57 | }) 58 | ); 59 | 60 | this.setState({points, lines}); 61 | } 62 | 63 | render() { 64 | const {points, lines, asVector} = this.state; 65 | 66 | const layers = asVector 67 | ? [ 68 | new EarthEngineLayer({ 69 | id: 'lines-vector', 70 | eeObject: lines, 71 | asVector, 72 | getLineColor: [255, 0, 0], 73 | getLineWidth: 1000, 74 | lineWidthMinPixels: 3 75 | }), 76 | new EarthEngineLayer({ 77 | id: 'points-vector', 78 | eeObject: points, 79 | asVector, 80 | getFillColor: [0, 0, 0], 81 | pointRadiusMinPixels: 3, 82 | getRadius: 100, 83 | getLineColor: [255, 255, 255], 84 | lineWidthMinPixels: 0.5, 85 | stroked: true 86 | }) 87 | ] 88 | : [ 89 | new EarthEngineLayer({ 90 | id: 'lines-raster', 91 | eeObject: lines, 92 | visParams: {color: 'red'} 93 | }), 94 | new EarthEngineLayer({ 95 | id: 'points-raster', 96 | eeObject: points, 97 | visParams: {color: 'black'} 98 | }) 99 | ]; 100 | 101 | return ( 102 |
ee.FeatureCollection
object.
116 | 117 | 121 | this.setState(prevState => { 122 | return {asVector: !prevState.asVector}; 123 | }) 124 | } 125 | /> 126 | Render as vector data 127 |
128 |
130 | );
131 | }
132 | }
133 |
134 | export function renderToDOM(container) {
135 | return render(
4 | 5 |
6 | 21 | 22 |
23 |
24 | 25 | 26 | 29 |