Hey GIS, Give me a map of the recent natural disasters

Local, State, National or even foreign governments need support to implement short-term emergency response, and long-term hazard mitigation measures at the occurrence of natural disasters. The sample notebook takes advantage of NASA's Earth Observatory Natural Event Tracker (EONET) API to collect a curated and continuously updated set of natural event metadata, and transform them into ArcGIS FeatureCollection(s) and save them into Web Maps in your GIS.

Using the temporal and location information of the event occurrence, this notebook queries the before- and after- disaster satellite images of the prone areas, and adds as overlay layers along with the FeatureCollection generated at the first step. The differences between the two fore and aft images can be used to build a training dataset. Thus when there are sufficient numbers of these images (or labels), you can build a model to predict, from the satellite images, if there was a natural disaster (e.g. tropical cyclone) or not.

With the sample notebook run in a scheduled fashion, you can produce a web map that contains the up-to-date list of natural disasters at any given time. For example, the eruption of Karymsky Volcano in Russia on Feb 15th, 2019 appears as one of the recent events in the EONET API Feed, and it would be represented in three forms - as an event vector, a pre-disaster satellite imagery, and a post-disaster satellite image (as shown in Figs 1, 2 and 3), in the resulting web map.

A Volcano eruption event in Russia Fig 1. A Volcano eruption event in Russia

Satellite Image of the area before the volcano eruption event Fig 2. Satellite Image of the area before the volcano eruption event

Satellite Image of the area after the volcano eruption event Fig 3. Satellite Image of the area after the volcano eruption event

Step 1. Preparation

In order to access the Land Remote-Sensing Satellite (System) (in short, Landsat) dataset, the first step is to connect to ArcGIS Online organization. The esri livingatlas account publishes a series of good-to-use Landsat products in forms of imagery layers ready that you can query, visualize and analyze. You can do so via an existing online profile, or just enter username/password as in gis = GIS('https://www.arcgis.com',"arcgis_python","P@ssword123").

Create another GIS connection to your ArcGIS Enterprise deployment to save the target web map. Same here, the GIS connection can be created via a profile, or using u/p (portal_gis = GIS('https://pythonapi.playground.esri.com/portal','arcgis_python','amazing_arcgis_123')).

from arcgis.gis import GIS

gis = GIS('home', verify_cert = False)

portal_gis = GIS(url='https://pythonapi.playground.esri.com/portal')

The exact_search function below extends the generic search method and returns you a single match instead of a list. It uses fields such as title, owner and item type, to loop through all the query results and gets an exact match. As seen here, the output of the exact_search is a Imagery Layer item titled "Multispectral Landsat".

def exact_search(my_gis, title, owner_value, item_type_value):
    final_match = None
    search_result = my_gis.content.search(query= title + ' AND owner:' + owner_value, item_type=item_type_value, outside_org=True)
    
    if "Imagery Layer" in item_type_value:
        item_type_value = item_type_value.replace("Imagery Layer", "Image Service")
    elif "Layer" in item_type_value:
        item_type_value = item_type_value.replace("Layer", "Service")
    
    for result in search_result:
        if result.title == title and result.type == item_type_value :
            final_match = result
            break
    return final_match

landsat_item = exact_search(gis, 'Multispectral Landsat', 'esri', 'Imagery Layer')
landsat = landsat_item.layers[0]
landsat_item
Multispectral Landsat
Landsat 8 OLI, 30m multispectral and multitemporal 8-band imagery, with on-the-fly renderings and indices. This imagery layer is sourced from the Landsat on AWS collections and is updated daily with new imagery.Imagery Layer by esri
Last Modified: May 03, 2018
0 comments, 143,954 views

Step 2. Understanding the data structure of EONET Feed

Carefully studying the geodata feed at https://eonet.sci.gsfc.nasa.gov/api/v2.1/events, we notice the JSON feed does not strictly follow the GeoJSON standard, which requires a tree structure of FeatureCollection > features > Feature > geometry. In order to make the EONET feed easier for parsing, we rewrite the JSON feed to conform to the GeoJSON standard.

We can then use from_geojson() of the ArcGIS API for Python to create a FeatureSet object, and eventually a FeatureCollection object to save into the Web Map.

import json
from arcgis.geometry import Geometry
import requests
import pandas as pd
from arcgis.features import Feature, FeatureSet, FeatureCollection, GeoAccessor
""" read the response from HTTP request, and load as JSON object;
    all "events" in original JSON will be stored as "features"
"""
response = requests.get("https://eonet.sci.gsfc.nasa.gov/api/v2.1/events")  
obj = json.loads(response.text)
features = obj['events']
obj['features'] = features

Preview the feed data:

from pprint import pprint
print(obj.keys())
print(len(obj['features']), "\n")
pprint(obj['features'][0])
dict_keys(['title', 'description', 'link', 'events', 'features'])
124 

{'categories': [{'id': 8, 'title': 'Wildfires'}],
 'description': '',
 'geometries': [{'coordinates': [34.99872, 31.41316],
                 'date': '2019-05-24T13:41:00Z',
                 'type': 'Point'}],
 'id': 'EONET_4196',
 'link': 'https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4196',
 'sources': [{'id': 'PDC',
              'url': 'http://emops.pdc.org/emops/?hazard_id=92664'}],
 'title': 'Wildfires - Central Israel'}

We have obtained 124 events from the EO feed.

Note: The number of events may vary depending on the time you execute this sample and the number of disasters detected at that time.

Each "event" shall be stored as "Feature", and its geometry will be rewritten here based on its geometry type.

for feature in features:
    
    feature['type'] = "Feature"
    feature['objectIdFieldName'] = feature['id']
    feature["properties"] = {}
    feature["properties"]["id"] = feature["id"]
    feature["properties"]["title"] = feature["title"]
    feature["properties"]["date"] = feature["geometries"][0]['date']
    feature["properties"]["link"] = feature["link"]
    if len(feature["sources"]) > 0:
        feature["properties"]["source_id"] = feature["sources"][0]["id"]
        feature["properties"]["source_url"] = feature["sources"][0]["url"]
    feature["name"] = feature["title"]
    feature['geometry'] = {}
    
    if len(feature["geometries"]) == 1 and feature["geometries"][0]["type"] == "Point":
        feature["geometryType"] = "esriGeometryPoint"
        feature['geometry']['type'] = "Point"
        feature['geometry']['coordinates'] = feature["geometries"][0]['coordinates']
        feature['geometry']['date'] = feature["geometries"][0]['date']
        
    elif len(feature["geometries"]) > 1 and feature["geometries"][0]["type"] == "Point":
        feature["geometryType"] = "esriGeometryPolyline"
        feature['geometry']['type'] = "LineString"
        feature['geometry']['coordinates'] = []
        feature['geometry']['date'] = []
        for g in feature["geometries"]:
            feature['geometry']['date'].append(g['date'])
            feature['geometry']['coordinates'].append(g['coordinates'])
            
    elif len(feature["geometries"]) > 1 and feature["geometries"][0]["type"] == "Point":
        feature["geometryType"] = "esriGeometryMultiPoint" 
        feature['geometry']['type'] = "MultiPoint" 
        feature['geometry']['points'] = []
        feature['geometry']['date'] = []
        for g in feature["geometries"]:
            tmp = {'type': 'Point',
                   'coordinates': None}
            tmp['coordinates'] = g['coordinates']
            feature['geometry']['points'].append(tmp)
            feature['geometry']['date'].append(g['date'])
        
    elif feature["geometries"][0]["type"] == "Polygon":
        feature["geometryType"] = "esriGeometryPolygon"
        feature['geometry']['type'] = "Polygon"
        feature['geometry']['coordinates'] = []
        feature['geometry']['date'] = []
        for g in feature["geometries"]:
            feature['geometry']['coordinates'].append(g['coordinates'][0])
            feature['geometry']['date'].append(g['date'])
    else:
        print(feature)

Next, give the JSON object a root node called features and set the type to FeatureCollection

obj['features'] = features
obj['type'] = "FeatureCollection"

Now that we standardized the GeoJSON, we can use from_geojson method directly to create the FeatureSet object, which can be later be used as input as to create a FeatureCollection object using the from_featureset() method.

fset = FeatureSet.from_geojson(obj)
fc = FeatureCollection.from_featureset(fset, symbol=None, 
                                       name="Natural Disaster Feed Events Feature Collection")
fc.query()
<FeatureSet> 124 features

Read the data as a spatially enabled data frame (SeDF)

df = fc.query().sdf

# Visualize the top 5 records
df.head()
OBJECTIDSHAPEdateidlinksource_idsource_urltitle
01{"x": 34.99872, "y": 31.41316, "spatialReferen...2019-05-24T13:41:00ZEONET_4196https://eonet.sci.gsfc.nasa.gov/api/v2.1/event...PDChttp://emops.pdc.org/emops/?hazard_id=92664Wildfires - Central Israel
12{"x": -68.7, "y": 28.8, "spatialReference": {"...2019-05-21T00:00:00ZEONET_4192https://eonet.sci.gsfc.nasa.gov/api/v2.1/event...NOAA_NHChttps://www.nhc.noaa.gov/archive/2019/ANDREA.s...Subtropical Storm Andrea
23{"x": -120.4293365, "y": 58.204834, "spatialRe...2019-05-21T00:00:00ZEONET_4195https://eonet.sci.gsfc.nasa.gov/api/v2.1/event...BCWILDFIREhttp://bcfireinfo.for.gov.bc.ca/hprScripts/Wil...Fontas River Fire, British Columbia, Canada
34{"x": -114.40635, "y": 55.716367, "spatialRefe...2019-05-18T14:46:00ZEONET_4190https://eonet.sci.gsfc.nasa.gov/api/v2.1/event...ABFIREhttp://wildfire.alberta.ca/reports/activedd.csvWildfire SWF049-2019 Slave Lake, Alberta, Canada
45{"x": -114.544067, "y": 55.655167, "spatialRef...2019-05-18T14:46:00ZEONET_4191https://eonet.sci.gsfc.nasa.gov/api/v2.1/event...ABFIREhttp://wildfire.alberta.ca/reports/activedd.csvWildfire SWF050-2019 Slave Lake, Alberta, Canada

Make URLs clickable

The columns link and source_url both contain HTTP URLs. Users will understand the data and source of the feed better if they are made into clickable links. The cells below demonstrate how to display these two columns in clickable hyperlink texts. Further, we split the DataFrame based on the disaster type.

def make_clickable(val):
    return '<a href="{}">{}</a>'.format(val,val)

# query for rows with "Fire" or "Wildfire" in its "title" column
df1 = df[df['title'].str.contains("Fire|Wildfire") == True]
# drop unnecessary columns
cols = [col for col in df.columns if col not in ['OBJECTID', 'SHAPE', 'index']]
df1 = df1[cols]
# attach the two columns with clickable hyperlinks
df1.reset_index().style.format({'link': make_clickable, 'source_url': make_clickable})
indexdateidlinksource_idsource_urltitle
002019-05-13T15:40:00ZEONET_4182https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4182PDChttp://emops.pdc.org/emops/?hazard_id=92189Wildfires - Southern Switzerland
112019-05-13T00:00:00ZEONET_4183https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4183MBFIREhttp://www.gov.mb.ca/sd/fire/Fire-Statistic/2019/MBFIRE19.txtWildfire MB-CE038, Manitoba, Canada
222019-05-13T00:00:00ZEONET_4184https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4184MBFIREhttp://www.gov.mb.ca/sd/fire/Fire-Statistic/2019/MBFIRE19.txtWildfire MB-CE039, Manitoba, Canada
332019-05-13T00:00:00ZEONET_4185https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4185BCWILDFIREhttp://bcfireinfo.for.gov.bc.ca/hprScripts/WildfireNews/OneFire.asp?ID=796Richter Creek Fire, British Columbia, Canada
442019-05-11T22:18:00ZEONET_4181https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4181ABFIREhttp://wildfire.alberta.ca/reports/activedd.csvWildfire PWF052-2019 Peace River, Alberta, Canada
562019-05-10T15:47:00ZEONET_4179https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4179PDChttp://emops.pdc.org/emops/?hazard_id=92097Wildfires - Mexico
672019-05-07T16:27:00ZEONET_4178https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4178PDChttp://emops.pdc.org/emops/?hazard_id=91989Wildfires - Lithuania and Latvia
782019-05-03T00:00:00ZEONET_4174https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4174MBFIREhttp://www.gov.mb.ca/sd/fire/Fire-Statistic/2019/MBFIRE19.txtWildfire MB-CE018, Manitoba, Canada
892019-04-30T14:22:00ZEONET_4170https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4170PDChttp://emops.pdc.org/emops/?hazard_id=91717Wildfires - Mexico
9102019-04-30T13:00:00ZEONET_4173https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4173InciWebhttp://inciweb.nwcg.gov/incident/6308/Oregon Lakes Fire
10112019-04-30T00:00:00ZEONET_4172https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4172MBFIREhttp://www.gov.mb.ca/sd/fire/Fire-Statistic/2019/MBFIRE19.txtWildfire MB-CE015, Manitoba, Canada
11122019-04-29T00:00:00ZEONET_4171https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4171MBFIREhttp://www.gov.mb.ca/sd/fire/Fire-Statistic/2019/MBFIRE19.txtWildfire MB-CE013, Manitoba, Canada
12132019-04-26T15:51:00ZEONET_4168https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4168PDChttp://emops.pdc.org/emops/?hazard_id=91596Wildfires - Southern Uruguay
13142019-04-25T17:27:00ZEONET_4167https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4167PDChttp://emops.pdc.org/emops/?hazard_id=91557Wildfires - Southern Sweden
14152019-04-24T17:25:00ZEONET_4165https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4165PDChttp://emops.pdc.org/emops/?hazard_id=91507Wildfires - Southern Norway
15162019-04-22T15:40:00ZEONET_4161https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4161PDChttp://emops.pdc.org/emops/?hazard_id=91432Wildfires - Switzerland
16182019-04-12T16:48:00ZEONET_4159https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4159PDChttp://emops.pdc.org/emops/?hazard_id=91030Wildfires - Macarao National Park, Venezuela
17202019-03-29T16:21:00ZEONET_4155https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4155PDChttp://emops.pdc.org/emops/?hazard_id=90680Wildfires - Central and Northern Portugal
18222019-03-21T14:30:00ZEONET_4154https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4154PDChttp://emops.pdc.org/emops/?hazard_id=90525Wildfires - Paredones, Chile
19232019-03-18T16:24:00ZEONET_4149https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4149PDChttp://emops.pdc.org/emops/?hazard_id=90465Wildfires - Southern Switzerland
20242019-03-18T16:00:00ZEONET_4150https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4150PDChttp://emops.pdc.org/emops/?hazard_id=90455Wildfires - Northwestern South Africa
21522019-03-11T17:02:00ZEONET_4118https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4118PDChttp://emops.pdc.org/emops/?hazard_id=90299Wildfires - Traiguén, Chile
22532019-03-11T16:56:00ZEONET_4117https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4117PDChttp://emops.pdc.org/emops/?hazard_id=90298Wildfire - Cautín, Chile
23542019-03-11T16:48:00ZEONET_4116https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4116PDChttp://emops.pdc.org/emops/?hazard_id=90296Wildfire - Ercilla, Chile
24552019-03-11T16:14:00ZEONET_4119https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4119PDChttp://emops.pdc.org/emops/?hazard_id=90288Wildfire - Woodgate, Queensland, Australia
25562019-03-07T15:34:00ZEONET_4114https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4114PDChttp://emops.pdc.org/emops/?hazard_id=90193Wildfires - Sierra Nevada de Santa Marta, Colombia
26572019-03-05T15:44:00ZEONET_4113https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4113PDChttp://emops.pdc.org/emops/?hazard_id=90130Wildfires - Southeastern Victoria, Australia
27582019-03-05T15:41:00ZEONET_4112https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4112PDChttp://emops.pdc.org/emops/?hazard_id=90129Wildfires - Western Basque Country, Cantabria and Asturias, Spain
28592019-03-04T15:40:00ZEONET_4111https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4111PDChttp://emops.pdc.org/emops/?hazard_id=90105Wildfire - Bullsbrook, Western Australia, Australia
29602019-03-04T15:23:00ZEONET_4110https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4110PDChttp://emops.pdc.org/emops/?hazard_id=90103Wildfire - Mount Kenya, Kenya
30612019-03-01T18:39:00ZEONET_4108https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4108PDChttp://emops.pdc.org/emops/?hazard_id=90023Wildfires - Ercilla and Collipulli, Chile
31622019-03-01T18:34:00ZEONET_4107https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4107PDChttp://emops.pdc.org/emops/?hazard_id=90021Wildfires - Esperance, Western Australia
32632019-03-01T18:27:00ZEONET_4106https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4106PDChttp://emops.pdc.org/emops/?hazard_id=90018Wildfires - Slovenia
33642019-03-01T18:24:00ZEONET_4105https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4105PDChttp://emops.pdc.org/emops/?hazard_id=90017Wildfires - South Africa
34652019-02-27T15:41:00ZEONET_4103https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4103PDChttp://emops.pdc.org/emops/?hazard_id=89969Wildfires - Saddleworth Moor, United Kingdom
35662019-02-25T16:29:00ZEONET_4101https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4101PDChttp://emops.pdc.org/emops/?hazard_id=89922Wildfires - Corsica, France
36682019-02-20T15:26:00ZEONET_4097https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4097PDChttp://emops.pdc.org/emops/?hazard_id=89779Wildfire - Southampton, Western Australia, Australia
37692019-02-19T17:44:00ZEONET_4096https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4096PDChttp://emops.pdc.org/emops/?hazard_id=89746Wildfire - Nelson Tasman Region (Pigeon Valley Fire), New Zealand
38732019-02-15T16:47:00ZEONET_4094https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4094PDChttp://emops.pdc.org/emops/?hazard_id=89665Wildfires - Southern Switzerland
39742019-02-14T16:38:00ZEONET_4093https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4093PDChttp://emops.pdc.org/emops/?hazard_id=89628Wildfire - Laingsburg, South Africa
40752019-02-11T16:42:00ZEONET_4090https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4090PDChttp://emops.pdc.org/emops/?hazard_id=89507Wildfires - Galvarino, Chile
41782019-02-07T19:43:00ZEONET_4088https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4088PDChttp://emops.pdc.org/emops/?hazard_id=89420Wildfire - Cochamo, Chile
42792019-02-06T16:51:00ZEONET_4086https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4086PDChttp://emops.pdc.org/emops/?hazard_id=89381Wildfires - Nelson - Tasman, New Zealand
43802019-02-06T16:37:00ZEONET_4087https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4087PDChttp://emops.pdc.org/emops/?hazard_id=89378Wildfires - Southwestern, South Africa
44812019-02-05T15:42:00ZEONET_4081https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4081PDChttp://emops.pdc.org/emops/?hazard_id=89352Wildfires - Southern Chile
45872019-01-07T04:20:00ZEONET_4072https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4072PDChttp://emops.pdc.org/emops/?hazard_id=88644Wildfire - Wailea, Hawaii, United States
df2 = df[df['title'].str.contains("Cyclone") == True]
df2 = df2[cols]
df2.reset_index().style.format({'link': make_clickable, 'source_url': make_clickable})
indexdateidlinksource_idsource_urltitle
052019-05-11T00:00:00ZEONET_4180https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4180JTWChttp://www.metoc.navy.mil/jtwc/products/sh2719.tcwTropical Cyclone Ann
df3 = df[df['title'].str.contains("Volcano") == True]
df3 = df3[cols]
df3.reset_index().style.format({'link': make_clickable, 'source_url': make_clickable})
indexdateidlinksource_idsource_urltitle
0172019-04-16T10:12:29ZEONET_4160https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4160SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=282110Asosan Volcano, Japan
1192019-04-09T00:00:00ZEONET_4158https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4158SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=300260Klyuchevskoy Volcano, Russia
2212019-03-26T09:19:10ZEONET_4156https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4156SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=352090Sangay Volcano, Ecuador
3252019-03-17T00:00:00ZEONET_4152https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4152nannanVillarica Volcano, Chile
4672019-02-24T10:58:37ZEONET_4104https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4104SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=263300Semeru Volcano, Indonesia
5702019-02-18T00:00:00ZEONET_4098https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4098SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=263310Tengger Caldera Volcano, Indonesia
6712019-02-16T00:00:00ZEONET_4099https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4099SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=300130Karymsky Volcano, Russia
7722019-02-16T00:00:00ZEONET_4100https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4100SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=233020Piton de la Fournaise Volcano, Réunion (France)
8762019-02-11T09:50:08ZEONET_4092https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4092SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=345040Poas Volcano, Costa Rica
9822019-01-29T00:00:00ZEONET_4085https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4085SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=263250Merapi Volcano, Indonesia
10832019-01-20T00:00:00ZEONET_4075https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4075SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=300250Bezymianny Volcano, Russia
11842019-01-19T08:49:43ZEONET_4074https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4074SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=261170Kerinci Volcano, Indonesia
12852019-01-11T00:00:00ZEONET_4079https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4079SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=344020San Cristobal Volcano
13862019-01-10T00:00:00ZEONET_4073https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4073SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=264020Agung Volcano, Indonesia
14882019-01-06T08:15:24ZEONET_4069https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4069SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=352010Reventador Volcano, Ecuador
15892019-01-05T00:00:00ZEONET_4070https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4070SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=357040Planchón-Peteroa Volcano, Chile
16902019-01-02T09:05:32ZEONET_4071https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4071SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=211040Stromboli Volcano, Italy
17912018-12-02T00:00:00ZEONET_4049https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4049SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=341090Popocatepetl Volcano, Mexico
18922018-11-25T00:00:00ZEONET_4044https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4044SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=267020Karangetang Volcano, Indonesia
19942018-11-09T00:00:00ZEONET_4048https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4048SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=282030Suwanosejima Volcano, Japan
20952018-09-28T00:00:00ZEONET_3999https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3999SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=251002Kadovar Volcano, Papua New Guinea
21962018-08-25T06:00:00ZEONET_3934https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3934NASA_DISPhttps://disasters.nasa.gov/manam-island-eruption-august-2018Manam Volcano, Papua New Guinea
22972018-08-01T00:00:00ZEONET_3867https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3867EOhttps://earthobservatory.nasa.gov/images/144493/etna-awakens-on-its-sideEtna Volcano, Italy
23982018-07-23T00:00:00ZEONET_3871https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3871SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=345070Turrialba Volcano, Costa Rica
24992018-07-11T00:00:00ZEONET_3868https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3868SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=357070Nevados de Chillan Volcano, Chile
251002018-06-18T00:00:00ZEONET_3618https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3618SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=262000Krakatau Volcano, Indonesia
261012018-05-01T00:00:00ZEONET_3674https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3674SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=342030Santa Maria Volcano, Guatemala
271022018-03-07T00:00:00ZEONET_3491https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3491SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=268030Ibu Volcano, Indonesia
281032017-08-12T00:00:00ZEONET_3390https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_3390SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=342110Pacaya Volcano, Guatemala
291092017-03-25T00:00:00ZEONET_2761https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2761SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=282080Aira Volcano, Japan
301102016-12-08T00:00:00ZEONET_2693https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2693SIVolcanohttp://volcano.si.edu/volcano.cfm?vn=290380Ebeko Volcano, Russia
311112016-11-07T00:00:00ZEONET_2654https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2654EOhttps://earthobservatory.nasa.gov/NaturalHazards/event.php?id=90273Sabancaya Volcano, Peru
321122016-09-18T00:00:00ZEONET_2632https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2632EOhttps://earthobservatory.nasa.gov/NaturalHazards/view.php?id=90242Sheveluch Volcano, Russia
331132016-03-16T00:00:00ZEONET_354https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_354EOhttp://earthobservatory.nasa.gov/NaturalHazards/view.php?id=87762Dukono Volcano, Indonesia
341262002-01-04T00:00:00ZEONET_980https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_980CEMShttp://emergency.copernicus.eu/mapping/list-of-components/EMSR289Fuego Volcano, Guatemala
df4 = df[df['title'].str.contains("Iceberg") == True]
df4 = df4[cols]
df4.reset_index().style.format({'link': make_clickable, 'source_url': make_clickable})
indexdateidlinksource_idsource_urltitle
0262019-03-13T00:00:00ZEONET_4129https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4129NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B38
1272019-03-13T00:00:00ZEONET_4130https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4130NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B39
2282019-03-13T00:00:00ZEONET_4131https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4131NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B40
3292019-03-13T00:00:00ZEONET_4132https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4132NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B45
4302019-03-13T00:00:00ZEONET_4133https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4133NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C18B
5312019-03-13T00:00:00ZEONET_4134https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4134NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C24
6322019-03-13T00:00:00ZEONET_4135https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4135NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C29
7332019-03-13T00:00:00ZEONET_4136https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4136NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C30
8342019-03-13T00:00:00ZEONET_4137https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4137NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C31
9352019-03-13T00:00:00ZEONET_4138https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4138NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C32
10362019-03-13T00:00:00ZEONET_4139https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4139NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C33
11372019-03-13T00:00:00ZEONET_4140https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4140NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C35
12382019-03-13T00:00:00ZEONET_4141https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4141NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg D21B
13392019-03-13T00:00:00ZEONET_4142https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4142NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg D22
14402019-03-13T00:00:00ZEONET_4143https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4143NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg D23
15412019-03-13T00:00:00ZEONET_4144https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4144NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg D26
16422019-03-13T00:00:00ZEONET_4145https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4145NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg D27
17432019-03-12T00:00:00ZEONET_4120https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4120NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg A57A
18442019-03-12T00:00:00ZEONET_4121https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4121NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg A63
19452019-03-12T00:00:00ZEONET_4122https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4122NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B09G
20462019-03-12T00:00:00ZEONET_4123https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4123NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B09I
21472019-03-12T00:00:00ZEONET_4124https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4124NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B15AA
22482019-03-12T00:00:00ZEONET_4125https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4125NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B15AB
23492019-03-12T00:00:00ZEONET_4126https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4126NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B28
24502019-03-12T00:00:00ZEONET_4127https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4127NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B29
25512019-03-12T00:00:00ZEONET_4128https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4128NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B37
26772019-02-11T00:00:00ZEONET_4089https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4089NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C36
27932018-11-09T00:00:00ZEONET_4031https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_4031EOhttps://earthobservatory.nasa.gov/images/144212/pine-island-glacier-quickly-drops-another-icebergIceberg B46
281042017-07-21T00:00:00ZEONET_2998https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2998NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg A68B
291052017-07-12T00:00:00ZEONET_2934https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2934EOhttps://earthobservatory.nasa.gov/NaturalHazards/event.php?id=90556Iceberg A68A
301062017-05-19T00:00:00ZEONET_2883https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2883NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg C34
311072017-04-21T00:00:00ZEONET_2881https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2881NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B42
321082017-04-21T00:00:00ZEONET_2882https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2882NATICEhttp://www.natice.noaa.gov/pub/icebergs/Iceberg_Tabular.csvIceberg B43
331142016-02-01T00:00:00ZEONET_2871https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2871BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/d15b.ascatIceberg D15B
341152016-01-31T00:00:00ZEONET_2870https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2870BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/d15a.ascatIceberg D15A
351162014-07-23T00:00:00ZEONET_2880https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2880BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/a64.ascatIceberg A64
361172012-05-24T00:00:00ZEONET_2733https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2733BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/b30.ascatIceberg B30
371182012-02-03T00:00:00ZEONET_2874https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2874BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/b09f.ascatIceberg B09F
381192011-08-30T00:00:00ZEONET_2734https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2734BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/a23a.ascatIceberg A23A
391202011-08-30T00:00:00ZEONET_2736https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2736BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/b22a.ascatIceberg B22A
401212011-08-30T00:00:00ZEONET_2876https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2876BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/c15.ascatIceberg C15
411222011-08-30T00:00:00ZEONET_2878https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2878BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/b09b.ascatIceberg B09B
421232011-08-30T00:00:00ZEONET_2879https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2879BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/d20a.ascatIceberg D20A
431242011-08-30T00:00:00ZEONET_2996https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2996BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/b16.ascatIceberg B16
441252011-08-30T00:00:00ZEONET_2997https://eonet.sci.gsfc.nasa.gov/api/v2.1/events/EONET_2997BYU_ICEhttp://www.scp.byu.edu/data/iceberg/ascat/c21b.ascatIceberg C21B

Step 3. Plot the features

So far, we have transformed and read the EONet feed into a Spatially Enabled DataFrame object. We noticed that features of different disaster types have different geometry types, properties, and data structures. We can use these properties to symbolize these disasters and draw them on a map.

Before plotting on a map, let's take a look at the built-in symbol styles that comes with the arcgis.mapping module. Symbol styles come in different sets for Points, Lines and Polygons as shown below.

from arcgis.mapping import show_styles

for idx, style in show_styles('Point').iterrows():
    print(style['ESRI_STYLE'] + " : " + style['MARKER'])
    style_volcano = style['MARKER']
    
print("----")
    
for idx, style in show_styles('Line').iterrows():
    print(style['ESRI_STYLE'] + " : " + style['MARKER'])
    style_cyclone = style['MARKER']
    
print("----")
    
for idx, style in show_styles('Polygon').iterrows():
    print(style['ESRI_STYLE'] + " : " + style['MARKER'])
    style_iceburg = style['MARKER']
Circle (default) : o
Cross : +
Diamond : d
Square : s
X : x
----
Solid (default) : s
Dash : -
Dash Dot : -.
Dash Dot Dot : -..
Dot : .
Long Dash : --
Long Dash Dot : --.
Null : n
Short Dash : s-
Short Dash Dot : s-.
Short Dash Dot Dot : s-..
Short Dot : s.
----
Backward Diagonal : \
Forward Diagonal : /
Vertical Bar : |
Horizontal Bar : -
Diagonal Cross : x
Cross : +
Solid Fill (default) : s

Create a temporary map object.

map_g = portal_gis.map()

The EONet takes liberty in varying the geometry type used to record events of the same type. Thus an Iceburg could be a point or a polygon. The cell below handles this uncertainity. It also picks an appropriate symbol for each geometry type.

The boolean variable if_Changed is used to represent if the geometry of the features inside the FeatureSet has been changed during the parsing or execution process. A hint here: If you are interested to find out the details of each feature stored in the FeatureSet Class object, use print(ea.get_value('title'), " - ", ea.geometry_type, " - ", ea.geometry) to display information of each entry.

Also note that, there are three kinds of representations for cyclones - namely, "Cyclone", "Typhoon", and "Tropical Storm". To determine if the disaster is of type cyclones, we need to confirm it falls within one of three categories.

if_Changed = False

# Filter based on disaster type, and each type is assigned a different symbology
for ea in fc.query():
    
    title = ea.get_value('title')
    obj_id = ea.attributes['OBJECTID']
    obj_type = ea.geometry_type
    
    if any(disaster_type in title for disaster_type in ["Cyclone", "Typhoon", "Tropical Storm"]):
        df_sel = df[df['OBJECTID']==obj_id]
        df_sel.spatial.plot(map_widget=map_g,
                            name=title,
                            color='C0',
                            symbol_type = 'simple',
                            symbol_style = style_cyclone)
        
    elif "Volcano" in title:
        if obj_type == 'Point':
            df_sel = df[df['OBJECTID']==obj_id]
            df_sel.spatial.plot(map_widget= map_g,
                                name=title,
                                colors='Reds_r',
                                symbol_type = 'simple',
                                symbol_style = style_volcano)
        else: # Polygon
            if not if_Changed and 'rings' in ea.geometry:
                if isinstance(ea.geometry['rings'], list) and not isinstance(ea.geometry['rings'][0], float):
                    ea.geometry['rings'] = ea.geometry['rings'][0]
                else:
                    ea.geometry['rings'] = ea.attributes['SHAPE']['rings'][0]
                if_Changed = True
            
            df_sel = df[df['OBJECTID']==obj_id]
            df_sel.spatial.plot(map_widget= map_g,
                                name=title,
                                colors='Reds_r',
                                symbol_type = 'simple',
                                symbol_style = style_volcano,
                                outline_style='s',
                                outline_color=[0,0,0,255],
                                line_width=1.0)
            
    elif "Wildfire" in title or "Fire" in title:
        df_sel = df[df['OBJECTID']==obj_id]
        df_sel.spatial.plot(map_widget= map_g,
                            name=title,
                            symbol_color='C1',
                            symbol_type = 'simple',
                            symbol_style = 'd')
        
    elif "Iceberg" in title:
        if obj_type == 'Point':
            df_sel = df[df['OBJECTID']==obj_id]
            df_sel.spatial.plot(map_widget= map_g,
                                name=title,
                                symbol_color='C2',
                                symbol_type = 'simple',
                                symbol_style = style_iceburg)
        else: # Polyline
            df_sel = df[df['OBJECTID']==obj_id]
            df_sel.spatial.plot(map_widget= map_g,
                                name=title,
                                symbol_color='C2',
                                symbol_type = 'simple',
                                symbol_style = style_iceburg)
            
    else:
        print(" - Not recognized - ", title)
# map_g
map_g1 = gis.map()
for lyr in map_g.layers:
    map_g1.add_layer(lyr)
map_g1

The map view can be saved as a web map item:

web_map_properties = {'title': "Natural Disasters (FC only) Collection",
                      'snippet':'This web map contains multiple FC',
                      'tags':'ArcGIS Python API'}

map_g.save(item_properties=web_map_properties)
Natural Disasters (FC only) Collection
This web map contains multiple FCWeb Map by arcgis_python
Last Modified: May 16, 2019
0 comments, 0 views

In preparation for the next step in which satellite imageries of the same extent and temporal range will be also added to the web map, let's run the cell below to validate that all Feature(s) inside the FeatureCollection object have valid extent and date properties.

# validation cell
for ea in fc.query():
    if if_Changed and ea.geometry_type == "Polygon":
        if 'rings' in ea.geometry:
                tmp = ea.geometry['rings']
                ea.geometry['rings'] = []
                ea.geometry['rings'].append(tmp)
                if_Changed = False

    if not isinstance(Geometry(ea.geometry).extent, tuple): 
        print("Not able to get geoextent from ", ea.geometry_type, 
              " - ", ea.get_value('title'), " - ", ea.geometry, " - ", ea.get_value('date'))

Step 4. Creating the web map

This part of the notebook creates the following 3 functions:

  1. parse the metadata feed (in GeoJSON) and create one FeatureClass per event, and add the FeatureClass to a map,
  2. obtain the temporal range and the spatial boundingbox of the FeatureClass, perform filter_by(...) to the landsat imagery layer with the criteria, and add the before- and after- disaster imageries to the map, and
  3. store the current map as web map.

The filter_images function defined below accepts a temporal range and a spatial boundingbox of the FeatureCollection.It then calls filter_by(...) on the LandSat Imagery Layer, and when one or more mosiac tiles meet the requirements, it gets the mean of these tiles and adds it to the current map. It also returns the filter result as a DataFrame.

import arcgis
from arcgis import geometry
from arcgis.geometry import Geometry
import datetime as dt

def filter_images(fc, my_map, start_datetime, end_datetime):
    
    geom_obj = Geometry(fc.geometry)
    selected = landsat.filter_by(where="(Category = 1) AND (CloudCover <=0.2)",
                                 time=[start_datetime, end_datetime],
                                 geometry=geometry.filters.intersects(geom_obj))
    
    if not selected._mosaic_rule.get("lockRasterIds"):
        return None
    else:
        date_title_from = start_datetime.strftime("%m/%d/%Y")
        date_title_to = end_datetime.strftime("%m/%d/%Y")
        extents = Geometry(fc.geometry).extent

        my_map.add_layer(selected.mean(), {"title": date_title_from + "-" + date_title_to + " @ [" + str(extents) + "]"})

        fs = selected.query(out_fields="AcquisitionDate, GroupName, Month, DayOfYear, WRS_Row, WRS_Path")
        tdf = fs.sdf  
        
        return tdf

Next, the cell below will loop through all features in the FeatureCollection, and automatically invoke filter_images to get the before- and after- disaster satellite images to the map, when images are available.

(A hint here, use print(ea.get_value('title'), " - ", ea.geometry_type, " - ", Geometry(ea.geometry).extent, " - ", ea.get_value('date')) to get more information of the features being queried.)

%%time

""" Use `fclist_l` to save a reference of which feature has a corresponding raster image before event happens;
    `fclist_r` for the list of features that have corresponding imageries after event happens;
    and `fclist_lr` representing the list of features that have both.
"""
fclist_l = []
fclist_r = []
fclist_lr = []
add_to_map = map_g
gap = 21

for ea in fc.query():
    
    datetime_object = dt.datetime.strptime(ea.get_value('date'), '%Y-%m-%dT%H:%M:%SZ')
    start_datetime = datetime_object - dt.timedelta(days=gap*3)
    end_datetime = datetime_object - dt.timedelta(days=gap)
    tdf1 = filter_images(ea, add_to_map, start_datetime, end_datetime)
    if tdf1 is not None:
        tdf1.head()
        fclist_l.append(ea) 

    start_datetime = datetime_object - dt.timedelta(days=gap)
    end_datetime = datetime_object + dt.timedelta(days=gap)
    tdf2 = filter_images(ea, add_to_map, start_datetime, end_datetime)
    if tdf2 is not None:
        tdf2.head()
        fclist_r.append(ea) 
        if tdf1 is not None:
            fclist_lr.append(ea)                    
Wall time: 1min 38s

We then call the save method to save the map view as a web map. This web map contains recent natural disasters as FeatureCollection objects, and their corresponding before- and after-disaster satellite images.

web_map_properties = {'title': "Natural Disasters (FC and Imagery) Collection",
                      'snippet':'This web map contains multiple FC and imageries',
                      'tags':'ArcGIS Python API'}

map_g.save(item_properties=web_map_properties)
Natural Disasters (FC and Imagery) Collection
This web map contains multiple FC and imageriesWeb Map by arcgis_python
Last Modified: May 16, 2019
0 comments, 0 views

Due to the limitations in the spatial and temporal coverage of lansat dataset, not all of the features created from the natual disaster feed is accompanied with its satellite image. The percentage of features having both before- and post- disaster images is shown as:

print("Out of ", len(fc.query()), " natural disaster events:")
print("# of features with pre- img: ", len(fclist_l))
print("# of features with post- img: ", len(fclist_r))
print("# of features with both img: ", len(fclist_lr))
Out of  127  natural disaster events:
# of features with pre- img:  59
# of features with post- img:  56
# of features with both img:  44

Step 5. Explore the data and the maps

You can either access the web map item from opening the link, or simply drawing the map in the current map view.

Use visit_features_on_map_widget function defined below to explore the places programmatically:

"""# Helper functions to change the extent of a web map 
   # based on point's location, or envelope's bbox
"""
import time
from arcgis.geometry import project


""" Zoom to the center if feature is of Point type, 
    else, zoom to the extents.
"""
def visit_site(m, fc, timesleep, zoom_level=15):
    m.zoom = zoom_level
    
    site = Geometry(fc.geometry).extent
    if fc.geometry_type == 'Point':
        m_center = [site[1], site[0]]
        # print("title: ", fc.get_value('title'), ", center: ", m_center)
        m.center = m_center
    else:
        m_extent = [[site[0], site[1]], [site[2], site[3]]]
        # print("title: ", fc.get_value('title'), ", extent: ", m_extent)
        m.extent = m_extent
    
    time.sleep(timesleep)

    
""" Loop through all features in the map view
"""
def visit_features_on_map_widget(map_obj, fclist_sel, zoom_level=15,  
                                 timesleep=5):
    for ea in fclist_sel:
        visit_site(map_obj, ea, timesleep, zoom_level)

The automaitic map traversal of natural disaster events can be saved as animations, e.g.

map_h = gis.map()
for lyr in map_g.layers:
    map_h.add_layer(lyr)
map_h
<IPython.core.display.Image object>
visit_features_on_map_widget(map_h, fclist_lr, zoom_level=5, timesleep=10)

Besides creating a long animation that visits all feature classes in the collection, you can also view an individual event only. For example, the cells below demonstrates a short animation for the Volcano eruption event in Russia:

lock_ids = [1022215, 1085347]
map_i = gis.map()

map_i.extent = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
 'xmin': 17695788.32155422,
 'ymin': 7149265.133738836,
 'xmax': 17846216.39321951,
 'ymax': 7210414.756367}
map_i.zoom = 10
map_i
<IPython.core.display.Image object>
for lyr in map_g.layers:
    if 'featureSet' in lyr.properties:
        title = lyr.properties.featureSet.features[0]['attributes']['title']
        if 'Karymsky Volcano, Russia' in title:
            map_i.add_layer(lyr)
            time.sleep(5)
    elif lyr._mosaic_rule.get("lockRasterIds")[0] == lock_ids[0]:
            map_i.add_layer(lyr)
            time.sleep(5)
    elif lyr._mosaic_rule.get("lockRasterIds")[0] == lock_ids[1]:
            map_i.add_layer(lyr)
            time.sleep(5)
            break

Or, if you know the exact location (center or extents) of the event, set the map to display the area manually.

Note: Depending on when you run this notebook, the disasters mentioned below may not be part of your feed. Hence, use the cells below as a hypothetical example.
# Tropical Cyclone Ann
map_h.zoom = 13
map_h.extent = [[146.3, -16.3], [159.5, -13.9]]
# Wildfire, Spain
map_h.zoom = 13
map_h.center = [43.10972, -4.85376]
# Wildfire, Mexico
map_h.center = [17.474261478, -92.414422489]
# Sabancaya Volcano, Peru
map_h.zoom = 12
map_h.center = [-15.779999999986844,  -71.85000000002958]
# Or Aira Volcano, Japan
map_h.zoom = 12.5
map_h.center = [31.592999999761126, 130.6570000002089]

Alternatively, you can either take a screenshot of the current map, or export it to a local HTML file.

from pathlib import Path

filename = Path('output/screenshots/volcano.png')

map_h.take_screenshot(file_path = str(filename))

Step 6. Statistics of natural disaster occurrences

Finally, you can loop through the events stored in the web map, count the number of occurrences for each type and produce a bar chart:

def count_features(feature_list):
    counter_c, counter_v, counter_i, counter_w = (0,)*4

    for ea in feature_list:
        title = ea.attributes['title']
        if 'Cyclone' in title:
            counter_c += 1
        elif 'Volcano' in title:
            counter_v += 1
        elif 'Iceberg' in title:
            counter_i += 1
        elif 'Wildfire' in title or 'Fire' in title:
            counter_w += 1
        else:
            pass
    return [counter_c, counter_v, counter_i, counter_w]
data = [count_features(fc.query()),
        count_features(fclist_l),
        count_features(fclist_r),
        count_features(fclist_lr)]

index = ['Cyclone', 'Volcano', 'Iceburg','Wildfire']
df1 = pd.DataFrame({"# of Features": data[0], \
                   "# of Fs w| pre-img": data[1], \
                   "# of Fs w| post-img": data[2], \
                   "# of Fs w| both-img": data[3]}, index=index)

ax = df1.plot.bar(rot=4, figsize=(10,8))

for i in ax.patches:
    # get_x pulls left or right; get_height pushes up or down
    ax.text(i.get_x()-.03, i.get_height()+.15, \
            str(i.get_height()), fontsize=11,
                color='dimgrey')
<Figure size 720x576 with 1 Axes>

Conclusion

This sample showcases an automated workflow to aggregate the recent ocurrences of natural disasters from NASA's EONET API. It creates FeatureCollections based on the events and adds the before- and post- disaster Landsat satellite images related to the event, and saves all such layers in a web map item on ArcGIS enterprise. Decision makers or Emergency Response crew members can take advantage of this web map to perform disaster impact analysis or other operations.

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.