Note
You can run this notebook interactively: , or view & download the original on GitHub.
⚠ Visualization performance on Binder is very bad ⚠️️
We recommend running this notebook locally instead, and removing the protocol="wss"
line. This is due to a yet-unknown Dask issue.
Live visualization on a map¶
With stackstac.show or stackstac.add_to_map, you can display your data on an interactive ipyleaflet map within your notebook. As you pan and zoom, the portion of the dask array that’s in view is computed on the fly.
By running a Dask cluster colocated with the data, you can quickly aggregate hundreds of gigabytes of imagery on the backend, then only send a few megabytes of pixels back to your browser. This gives you a very simplified version of the Google Earth Engine development experience, but with much more flexibility.
Limitations¶
This functionality is still very much proof-of-concept, and there’s a lot to be improved in the future:
Doesn’t work well on large arrays. Sadly, loading a giant global DataArray and using the map to just view small parts of it won’t work—yet. Plan on only using an array of the area you actually want to look at (passing
bounds_latlon=
tostackstac.stack
helps with this).Resolution doesn’t change as you zoom in or out.
Communication to Dask can be slow, or seem to hang temporarily.
Requires opening port 8000 (only accessible over localhost), which may be blocked in some restrictive environments.
[1]:
import stackstac
import pystac_client
import ipyleaflet
import xarray as xr
import IPython.display as dsp
[2]:
import coiled
import distributed
You definitely will need a cluster near the data for this (or to run on a beefy VM in us-west-2
).
You can sign up for a Coiled account and run clusters for free at https://cloud.coiled.io/ — no credit card or username required, just sign in with your GitHub or Google account.
[3]:
cluster = coiled.Cluster(
name="stackstac-show",
software="gjoseph92/stackstac",
n_workers=22,
worker_cpu=4,
worker_memory="16GiB",
scheduler_cpu=2,
backend_options={"region": "us-west-2"},
protocol="wss", # remove this line when not running on Binder
)
client = distributed.Client(cluster)
client
Using existing cluster: 'stackstac-show'
[3]:
Client
Client-e0ef1c18-526a-11ec-9de0-acde48001122
Connection method: Cluster object | Cluster type: coiled.Cluster |
Dashboard: http://34.220.90.18:8787 |
Cluster Info
Cluster
stackstac-show
Dashboard: http://34.220.90.18:8787 | Workers: 22 |
Total threads: 88 | Total memory: 334.21 GiB |
Scheduler Info
Scheduler
Scheduler-af0c630d-8e00-4903-b8ed-bfb07eeec0a5
Comm: tls://10.6.9.201:8786 | Workers: 22 |
Dashboard: http://10.6.9.201:8787/status | Total threads: 88 |
Started: 3 minutes ago | Total memory: 334.21 GiB |
Workers
Worker: coiled-dask-gjosephf7-74127-worker-1a93b52982
Comm: tls://10.6.26.205:44947 | Total threads: 4 |
Dashboard: http://10.6.26.205:45835/status | Memory: 15.18 GiB |
Nanny: tls://10.6.26.205:42421 | |
Local directory: /dask-worker-space/worker-7j923b_4 |
Worker: coiled-dask-gjosephf7-74127-worker-210418eab6
Comm: tls://10.6.16.148:40101 | Total threads: 4 |
Dashboard: http://10.6.16.148:40573/status | Memory: 15.18 GiB |
Nanny: tls://10.6.16.148:34583 | |
Local directory: /dask-worker-space/worker-fh12r1rh |
Worker: coiled-dask-gjosephf7-74127-worker-212da1cecb
Comm: tls://10.6.24.19:34179 | Total threads: 4 |
Dashboard: http://10.6.24.19:36989/status | Memory: 15.18 GiB |
Nanny: tls://10.6.24.19:46661 | |
Local directory: /dask-worker-space/worker-ykdcgqsr |
Worker: coiled-dask-gjosephf7-74127-worker-2325af0f99
Comm: tls://10.6.27.92:33931 | Total threads: 4 |
Dashboard: http://10.6.27.92:38127/status | Memory: 15.18 GiB |
Nanny: tls://10.6.27.92:42091 | |
Local directory: /dask-worker-space/worker-mt5rxt6l |
Worker: coiled-dask-gjosephf7-74127-worker-2f6bfdc214
Comm: tls://10.6.30.47:40323 | Total threads: 4 |
Dashboard: http://10.6.30.47:42351/status | Memory: 15.18 GiB |
Nanny: tls://10.6.30.47:46813 | |
Local directory: /dask-worker-space/worker-u86t2z6v |
Worker: coiled-dask-gjosephf7-74127-worker-2faaf18b99
Comm: tls://10.6.22.191:33665 | Total threads: 4 |
Dashboard: http://10.6.22.191:39527/status | Memory: 15.18 GiB |
Nanny: tls://10.6.22.191:32791 | |
Local directory: /dask-worker-space/worker-h1o7wx31 |
Worker: coiled-dask-gjosephf7-74127-worker-3b1e884ea2
Comm: tls://10.6.19.46:44589 | Total threads: 4 |
Dashboard: http://10.6.19.46:41301/status | Memory: 15.18 GiB |
Nanny: tls://10.6.19.46:44171 | |
Local directory: /dask-worker-space/worker-hkk5kk64 |
Worker: coiled-dask-gjosephf7-74127-worker-424f162b20
Comm: tls://10.6.23.124:37837 | Total threads: 4 |
Dashboard: http://10.6.23.124:39157/status | Memory: 15.18 GiB |
Nanny: tls://10.6.23.124:33393 | |
Local directory: /dask-worker-space/worker-5urhhufb |
Worker: coiled-dask-gjosephf7-74127-worker-44258e3925
Comm: tls://10.6.18.219:40973 | Total threads: 4 |
Dashboard: http://10.6.18.219:41049/status | Memory: 15.18 GiB |
Nanny: tls://10.6.18.219:46167 | |
Local directory: /dask-worker-space/worker-s12e3azx |
Worker: coiled-dask-gjosephf7-74127-worker-46116255bd
Comm: tls://10.6.29.154:33145 | Total threads: 4 |
Dashboard: http://10.6.29.154:34051/status | Memory: 15.18 GiB |
Nanny: tls://10.6.29.154:37167 | |
Local directory: /dask-worker-space/worker-q1q6sdtm |
Worker: coiled-dask-gjosephf7-74127-worker-4f54cd8da4
Comm: tls://10.6.17.136:32769 | Total threads: 4 |
Dashboard: http://10.6.17.136:39359/status | Memory: 15.18 GiB |
Nanny: tls://10.6.17.136:44671 | |
Local directory: /dask-worker-space/worker-5bzfmk6b |
Worker: coiled-dask-gjosephf7-74127-worker-50ce90ec34
Comm: tls://10.6.16.113:33913 | Total threads: 4 |
Dashboard: http://10.6.16.113:43919/status | Memory: 15.18 GiB |
Nanny: tls://10.6.16.113:37273 | |
Local directory: /dask-worker-space/worker-kv3_un5e |
Worker: coiled-dask-gjosephf7-74127-worker-6080a46186
Comm: tls://10.6.29.150:37855 | Total threads: 4 |
Dashboard: http://10.6.29.150:36633/status | Memory: 15.34 GiB |
Nanny: tls://10.6.29.150:43747 | |
Local directory: /dask-worker-space/worker-h_f_uv8c |
Worker: coiled-dask-gjosephf7-74127-worker-69869f50d5
Comm: tls://10.6.17.175:38047 | Total threads: 4 |
Dashboard: http://10.6.17.175:40081/status | Memory: 15.18 GiB |
Nanny: tls://10.6.17.175:36143 | |
Local directory: /dask-worker-space/worker-jrnodtow |
Worker: coiled-dask-gjosephf7-74127-worker-6a04bb4cba
Comm: tls://10.6.24.165:46157 | Total threads: 4 |
Dashboard: http://10.6.24.165:34447/status | Memory: 15.18 GiB |
Nanny: tls://10.6.24.165:42559 | |
Local directory: /dask-worker-space/worker-5ve7fzz_ |
Worker: coiled-dask-gjosephf7-74127-worker-782d25282a
Comm: tls://10.6.21.182:45983 | Total threads: 4 |
Dashboard: http://10.6.21.182:43337/status | Memory: 15.18 GiB |
Nanny: tls://10.6.21.182:46043 | |
Local directory: /dask-worker-space/worker-xtutaz9u |
Worker: coiled-dask-gjosephf7-74127-worker-8f1fd517a1
Comm: tls://10.6.16.90:41123 | Total threads: 4 |
Dashboard: http://10.6.16.90:46641/status | Memory: 15.34 GiB |
Nanny: tls://10.6.16.90:37931 | |
Local directory: /dask-worker-space/worker-zkde1wrt |
Worker: coiled-dask-gjosephf7-74127-worker-90f60ad8a9
Comm: tls://10.6.29.162:37215 | Total threads: 4 |
Dashboard: http://10.6.29.162:46179/status | Memory: 15.18 GiB |
Nanny: tls://10.6.29.162:33995 | |
Local directory: /dask-worker-space/worker-f3fycvlm |
Worker: coiled-dask-gjosephf7-74127-worker-a2af2543b0
Comm: tls://10.6.31.219:46535 | Total threads: 4 |
Dashboard: http://10.6.31.219:42439/status | Memory: 15.18 GiB |
Nanny: tls://10.6.31.219:34747 | |
Local directory: /dask-worker-space/worker-zsjapi39 |
Worker: coiled-dask-gjosephf7-74127-worker-a9c851f7da
Comm: tls://10.6.18.155:45937 | Total threads: 4 |
Dashboard: http://10.6.18.155:44295/status | Memory: 15.18 GiB |
Nanny: tls://10.6.18.155:38779 | |
Local directory: /dask-worker-space/worker-fj55v574 |
Worker: coiled-dask-gjosephf7-74127-worker-df2e54055f
Comm: tls://10.6.19.52:41215 | Total threads: 4 |
Dashboard: http://10.6.19.52:32909/status | Memory: 15.18 GiB |
Nanny: tls://10.6.19.52:45273 | |
Local directory: /dask-worker-space/worker-605lj88x |
Worker: coiled-dask-gjosephf7-74127-worker-fbc8f7adda
Comm: tls://10.6.22.125:36527 | Total threads: 4 |
Dashboard: http://10.6.22.125:44365/status | Memory: 15.18 GiB |
Nanny: tls://10.6.22.125:46629 | |
Local directory: /dask-worker-space/worker-m3f9k4ax |
Search for Sentinel-2 data overlapping our map
[4]:
m = ipyleaflet.Map()
m.center = 35.677153763176115, -105.8485489524901
m.zoom = 10
m.layout.height = "700px"
m
[5]:
%%time
bbox=[m.west, m.south, m.east, m.north]
stac_items = pystac_client.Client.open(
"https://earth-search.aws.element84.com/v0"
).search(
bbox=bbox,
collections=["sentinel-s2-l2a-cogs"],
datetime="2020-04-01/2020-04-15"
).get_all_items()
len(stac_items)
CPU times: user 27.6 ms, sys: 5.19 ms, total: 32.8 ms
Wall time: 861 ms
[5]:
24
[6]:
dsp.GeoJSON(stac_items.to_dict())
<IPython.display.GeoJSON object>
Create the time stack¶
Important: the resolution you pick here is what the map will use, regardless of zoom level! When you zoom in/out on the map, the data won’t be loaded at lower or higher resolutions. (In the future, we hope to support this.)
Beware of zooming out on high-resolution data; you could trigger a massive amount of compute!
[7]:
%time stack = stackstac.stack(stac_items, resolution=80)
CPU times: user 18.2 ms, sys: 1.36 ms, total: 19.6 ms
Wall time: 18.8 ms
Persist the data we want to view¶
By persisting all the RGB data, Dask will pre-load it and store it in memory, ready to use. That way, we can tweak what we show on the map (different composite operations, scaling, etc.) without having to re-fetch the original data every time. It also means tiles will load much faster as we pan around, since they’re already mostly computed.
It’s generally a good idea to persist somewhere before stackstac.show
. Typically you’d do this after a reduction step (like a temporal composite), but our data here is small, so it doesn’t matter much.
As a rule of thumb, try to persist after the biggest, slowest steps of your analysis, but before the steps you might want to tweak (like thresholds, scaling, etc.). If you want to tweak your big slow steps, well… be prepared to wait (and maybe don’t persist).
[8]:
client.wait_for_workers(22)
[9]:
rgb = stack.sel(band=["B04", "B03", "B02"]).persist()
rgb
[9]:
<xarray.DataArray 'stackstac-97c9a8aaff572ddbce3078a7d9b06bcd' (time: 24, band: 3, y: 2624, x: 2622)> dask.array<getitem, shape=(24, 3, 2624, 2622), dtype=float64, chunksize=(1, 1, 1024, 1024), chunktype=numpy.ndarray> Coordinates: * time (time) datetime64[ns] 2020-04-01T18:03:50 ...... id (time) <U24 'S2B_13SDA_20200401_0_L2A' ... 'S... * band (band) <U8 'B04' 'B03' 'B02' * x (x) float64 3e+05 3.001e+05 ... 5.097e+05 * y (y) float64 4.1e+06 4.1e+06 ... 3.89e+06 gsd int64 10 platform (time) <U11 'sentinel-2b' ... 'sentinel-2a' instruments <U3 'msi' data_coverage (time) float64 58.22 100.0 33.85 ... 100.0 42.69 updated (time) <U24 '2020-09-05T12:39:12.865Z' ... '2... sentinel:grid_square (time) <U2 'DA' 'CA' 'DV' ... 'CA' 'DV' 'CV' eo:cloud_cover (time) float64 27.36 28.71 29.24 ... 100.0 100.0 view:off_nadir int64 0 proj:epsg int64 32613 created (time) <U24 '2020-09-05T12:39:12.865Z' ... '2... sentinel:product_id (time) <U60 'S2B_MSIL2A_20200401T174909_N0214... constellation <U10 'sentinel-2' sentinel:latitude_band <U1 'S' sentinel:data_coverage (time) float64 58.22 100.0 33.85 ... 100.0 42.69 sentinel:sequence <U1 '0' sentinel:valid_cloud_cover bool True sentinel:utm_zone int64 13 title (band) <U31 'Band 4 (red)' ... 'Band 2 (blue)' epsg int64 32613 Attributes: spec: RasterSpec(epsg=32613, bounds=(300000, 3890160, 509760, 4100... crs: epsg:32613 transform: | 80.00, 0.00, 300000.00|\n| 0.00,-80.00, 4100080.00|\n| 0.0... resolution: 80
- time: 24
- band: 3
- y: 2624
- x: 2622
- dask.array<chunksize=(1, 1, 1024, 1024), meta=np.ndarray>
Array Chunk Bytes 3.69 GiB 8.00 MiB Shape (24, 3, 2624, 2622) (1, 1, 1024, 1024) Count 648 Tasks 648 Chunks Type float64 numpy.ndarray 24 1 2622 2624 3 - time(time)datetime64[ns]2020-04-01T18:03:50 ... 2020-04-...
array(['2020-04-01T18:03:50.000000000', '2020-04-01T18:03:54.000000000', '2020-04-01T18:04:04.000000000', '2020-04-01T18:04:08.000000000', '2020-04-03T17:53:53.000000000', '2020-04-03T17:53:57.000000000', '2020-04-03T17:54:07.000000000', '2020-04-03T17:54:10.000000000', '2020-04-06T18:03:49.000000000', '2020-04-06T18:03:53.000000000', '2020-04-06T18:04:04.000000000', '2020-04-06T18:04:08.000000000', '2020-04-08T17:53:53.000000000', '2020-04-08T17:53:57.000000000', '2020-04-08T17:54:08.000000000', '2020-04-08T17:54:11.000000000', '2020-04-11T18:03:49.000000000', '2020-04-11T18:03:53.000000000', '2020-04-11T18:04:03.000000000', '2020-04-11T18:04:07.000000000', '2020-04-13T17:53:56.000000000', '2020-04-13T17:54:00.000000000', '2020-04-13T17:54:10.000000000', '2020-04-13T17:54:13.000000000'], dtype='datetime64[ns]')
- id(time)<U24'S2B_13SDA_20200401_0_L2A' ... '...
array(['S2B_13SDA_20200401_0_L2A', 'S2B_13SCA_20200401_0_L2A', 'S2B_13SDV_20200401_0_L2A', 'S2B_13SCV_20200401_0_L2A', 'S2A_13SDA_20200403_0_L2A', 'S2A_13SCA_20200403_0_L2A', 'S2A_13SDV_20200403_0_L2A', 'S2A_13SCV_20200403_0_L2A', 'S2A_13SDA_20200406_0_L2A', 'S2A_13SCA_20200406_0_L2A', 'S2A_13SDV_20200406_0_L2A', 'S2A_13SCV_20200406_0_L2A', 'S2B_13SDA_20200408_0_L2A', 'S2B_13SCA_20200408_0_L2A', 'S2B_13SDV_20200408_0_L2A', 'S2B_13SCV_20200408_0_L2A', 'S2B_13SDA_20200411_0_L2A', 'S2B_13SCA_20200411_0_L2A', 'S2B_13SDV_20200411_0_L2A', 'S2B_13SCV_20200411_0_L2A', 'S2A_13SDA_20200413_0_L2A', 'S2A_13SCA_20200413_0_L2A', 'S2A_13SDV_20200413_0_L2A', 'S2A_13SCV_20200413_0_L2A'], dtype='<U24')
- band(band)<U8'B04' 'B03' 'B02'
array(['B04', 'B03', 'B02'], dtype='<U8')
- x(x)float643e+05 3.001e+05 ... 5.097e+05
array([300000., 300080., 300160., ..., 509520., 509600., 509680.])
- y(y)float644.1e+06 4.1e+06 ... 3.89e+06
array([4100080., 4100000., 4099920., ..., 3890400., 3890320., 3890240.])
- gsd()int6410
array(10)
- platform(time)<U11'sentinel-2b' ... 'sentinel-2a'
array(['sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2b', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a', 'sentinel-2a'], dtype='<U11')
- instruments()<U3'msi'
array('msi', dtype='<U3')
- data_coverage(time)float6458.22 100.0 33.85 ... 100.0 42.69
array([ 58.22, 100. , 33.85, 100. , 99.97, 19.96, 100. , 42.12, 58.36, 100. , 33.9 , 100. , 99.99, 20.36, 100. , 42.4 , 58.27, 100. , 33.87, 100. , 99.99, 20.57, 100. , 42.69])
- updated(time)<U24'2020-09-05T12:39:12.865Z' ... '...
array(['2020-09-05T12:39:12.865Z', '2020-09-05T06:24:19.862Z', '2020-09-05T06:23:47.836Z', '2020-09-18T23:33:57.593Z', '2020-09-05T10:35:08.576Z', '2020-09-18T22:58:28.920Z', '2020-09-23T15:43:10.502Z', '2020-08-31T15:57:36.461Z', '2020-09-19T03:48:23.504Z', '2020-09-05T10:00:19.229Z', '2020-09-05T11:18:48.722Z', '2020-08-31T15:12:49.407Z', '2020-08-31T15:02:43.771Z', '2020-09-19T00:17:08.540Z', '2020-08-31T15:23:56.855Z', '2020-09-05T12:50:14.783Z', '2020-09-23T09:12:42.490Z', '2020-09-18T23:11:12.399Z', '2020-09-23T16:16:43.693Z', '2020-09-23T09:09:25.619Z', '2020-09-19T01:38:56.112Z', '2020-09-19T10:28:14.469Z', '2020-09-24T06:31:45.567Z', '2020-09-19T03:49:30.360Z'], dtype='<U24')
- sentinel:grid_square(time)<U2'DA' 'CA' 'DV' ... 'CA' 'DV' 'CV'
array(['DA', 'CA', 'DV', 'CV', 'DA', 'CA', 'DV', 'CV', 'DA', 'CA', 'DV', 'CV', 'DA', 'CA', 'DV', 'CV', 'DA', 'CA', 'DV', 'CV', 'DA', 'CA', 'DV', 'CV'], dtype='<U2')
- eo:cloud_cover(time)float6427.36 28.71 29.24 ... 100.0 100.0
array([ 27.36, 28.71, 29.24, 42.53, 12.33, 7.4 , 1.16, 1.58, 2.09, 1.01, 27.26, 15.75, 2.74, 0.53, 1.15, 0.11, 15.67, 11.07, 9.37, 17.83, 99.96, 99.97, 100. , 100. ])
- view:off_nadir()int640
array(0)
- proj:epsg()int6432613
array(32613)
- created(time)<U24'2020-09-05T12:39:12.865Z' ... '...
array(['2020-09-05T12:39:12.865Z', '2020-09-05T06:24:19.862Z', '2020-09-05T06:23:47.836Z', '2020-09-18T23:33:57.593Z', '2020-09-05T10:35:08.576Z', '2020-09-18T22:58:28.920Z', '2020-09-23T15:43:10.502Z', '2020-08-31T15:57:36.461Z', '2020-09-19T03:48:23.504Z', '2020-09-05T10:00:19.229Z', '2020-09-05T11:18:48.722Z', '2020-08-31T15:12:49.407Z', '2020-08-31T15:02:43.771Z', '2020-09-19T00:17:08.540Z', '2020-08-31T15:23:56.855Z', '2020-09-05T12:50:14.783Z', '2020-09-23T09:12:42.490Z', '2020-09-18T23:11:12.399Z', '2020-09-23T16:16:43.693Z', '2020-09-23T09:09:25.619Z', '2020-09-19T01:38:56.112Z', '2020-09-19T10:28:14.469Z', '2020-09-24T06:31:45.567Z', '2020-09-19T03:49:30.360Z'], dtype='<U24')
- sentinel:product_id(time)<U60'S2B_MSIL2A_20200401T174909_N021...
array(['S2B_MSIL2A_20200401T174909_N0214_R141_T13SDA_20200401T220155', 'S2B_MSIL2A_20200401T174909_N0214_R141_T13SCA_20200401T220155', 'S2B_MSIL2A_20200401T174909_N0214_R141_T13SDV_20200401T220155', 'S2B_MSIL2A_20200401T174909_N0214_R141_T13SCV_20200401T220155', 'S2A_MSIL2A_20200403T173901_N0214_R098_T13SDA_20200403T220105', 'S2A_MSIL2A_20200403T173901_N0214_R098_T13SCA_20200403T220105', 'S2A_MSIL2A_20200403T173901_N0214_R098_T13SDV_20200403T220105', 'S2A_MSIL2A_20200403T173901_N0214_R098_T13SCV_20200403T220105', 'S2A_MSIL2A_20200406T174901_N0214_R141_T13SDA_20200406T221027', 'S2A_MSIL2A_20200406T174901_N0214_R141_T13SCA_20200406T221027', 'S2A_MSIL2A_20200406T174901_N0214_R141_T13SDV_20200406T221027', 'S2A_MSIL2A_20200406T174901_N0214_R141_T13SCV_20200406T221027', 'S2B_MSIL2A_20200408T173859_N0214_R098_T13SDA_20200408T215856', 'S2B_MSIL2A_20200408T173859_N0214_R098_T13SCA_20200408T215856', 'S2B_MSIL2A_20200408T173859_N0214_R098_T13SDV_20200408T215856', 'S2B_MSIL2A_20200408T173859_N0214_R098_T13SCV_20200408T215856', 'S2B_MSIL2A_20200411T174909_N0214_R141_T13SDA_20200411T220443', 'S2B_MSIL2A_20200411T174909_N0214_R141_T13SCA_20200411T220443', 'S2B_MSIL2A_20200411T174909_N0214_R141_T13SDV_20200411T220443', 'S2B_MSIL2A_20200411T174909_N0214_R141_T13SCV_20200411T220443', 'S2A_MSIL2A_20200413T173901_N0214_R098_T13SDA_20200413T235616', 'S2A_MSIL2A_20200413T173901_N0214_R098_T13SCA_20200413T235616', 'S2A_MSIL2A_20200413T173901_N0214_R098_T13SDV_20200413T235616', 'S2A_MSIL2A_20200413T173901_N0214_R098_T13SCV_20200413T235616'], dtype='<U60')
- constellation()<U10'sentinel-2'
array('sentinel-2', dtype='<U10')
- sentinel:latitude_band()<U1'S'
array('S', dtype='<U1')
- sentinel:data_coverage(time)float6458.22 100.0 33.85 ... 100.0 42.69
array([ 58.22, 100. , 33.85, 100. , 99.97, 19.96, 100. , 42.12, 58.36, 100. , 33.9 , 100. , 99.99, 20.36, 100. , 42.4 , 58.27, 100. , 33.87, 100. , 99.99, 20.57, 100. , 42.69])
- sentinel:sequence()<U1'0'
array('0', dtype='<U1')
- sentinel:valid_cloud_cover()boolTrue
array(True)
- sentinel:utm_zone()int6413
array(13)
- title(band)<U31'Band 4 (red)' ... 'Band 2 (blue)'
array(['Band 4 (red)', 'Band 3 (green)', 'Band 2 (blue)'], dtype='<U31')
- epsg()int6432613
array(32613)
- spec :
- RasterSpec(epsg=32613, bounds=(300000, 3890160, 509760, 4100080), resolutions_xy=(80, 80))
- crs :
- epsg:32613
- transform :
- | 80.00, 0.00, 300000.00| | 0.00,-80.00, 4100080.00| | 0.00, 0.00, 1.00|
- resolution :
- 80
stackstac.add_to_map
¶
stackstac.add_to_map
displays a DataArray on an existing ipyleaflet map. You give it a layer name—if a layer with this name already exists, it’s replaced; otherwise, it’s added. This is nice for working in notebooks, since you can re-run an add_to_map
cell to adjust it, without piling up new layers.
Before continuing, you should open the distributed dashboard in another window (or use the dask-jupyterlab extension) in order to watch its progress.
[10]:
m.zoom = 10
m
Static screenshot for docs (delete this cell if running the notebook):
stackstac.server_stats
is a widget showing some under-the-hood stats about the computations currently running to generate your tiles. It shows “work bars”—like the inverse of progress bars—indicating the tasks it’s currently waiting on.
[11]:
stackstac.server_stats
Make a temporal median composite, and show that on the map m
above! Pan around and notice how the dask dashboard shows your progress.
[12]:
comp = rgb.median("time")
stackstac.add_to_map(comp, m, "s2", range=[0, 3000])
Try changing median
to mean
, min
, max
, etc. in the cell above, and re-run. The map will update with the new layer contents (since you reused the name "s2"
).
Showing computed values¶
You can display anything you can compute with dask and xarray, not just raw data. Here, we’ll compute NDVI (Normalized Difference Vegetation Index), which indicates the health of vegetation (and is kind of a “hello world” example for remote sensing).
[13]:
nir, red = stack.sel(band="B08"), stack.sel(band="B04")
ndvi = (nir - red) / (nir + red)
ndvi = ndvi.persist()
We’ll show the temporal maximum NDVI (try changing to min
, median
, etc.)
[14]:
ndvi_comp = ndvi.max("time")
stackstac.show
¶
stackstac.show
creates a new map for you, centers it on your array, and displays it. It’s very convenient.
[15]:
stackstac.show(ndvi_comp, range=(0, 0.6), cmap="YlGn")
Static screenshot for docs (delete this cell if running the notebook):
To demonstrate more derived quantities: show each pixel’s deviation from the mean NDVI of the whole array:
[16]:
anomaly = ndvi_comp - ndvi.mean()
[17]:
stackstac.show(anomaly, cmap="RdYlGn")
/Users/gabe/dev/stackstac/stackstac/show.py:484: UserWarning: Calculating 2nd and 98th percentile of the entire array, since no range was given. This could be expensive!
warnings.warn(
Static screenshot for docs (delete this cell if running the notebook):
Interactively explore data with widgets¶
Using ipywidgets.interact, you can interactively threshold the NDVI values by adjusting a slider. It’s a bit clunky, and pretty slow to refresh, but still a nice demonstration of the powerful tools that become available by integrating with the Python ecosystem.
[18]:
import ipywidgets
ndvi_map = ipyleaflet.Map()
ndvi_map.center = m.center
ndvi_map.zoom = m.zoom
@ipywidgets.interact(threshold=(0.0, 1.0, 0.1))
def explore_ndvi(threshold=0.2):
high_ndvi = ndvi_comp.where(ndvi_comp > threshold)
stackstac.add_to_map(high_ndvi, ndvi_map, "ndvi", range=[0, 1], cmap="YlGn")
return ndvi_map
Static screenshot for docs (delete this cell if running the notebook):