#!/usr/bin/env python
# coding: utf-8
# ## Geospatial Data Science and Analysis
# ### using ArcGIS API for Python
#
#
#
#
# ### Rohit Singh - rsingh@esri.com
#
#
# # ArcGIS API for Python
#
#
# * Python library for spatial analysis, mapping and GIS
# * Powerful, modern and easy to use
# * Powered by Web GIS
# # ArcGIS + Jupyter = ❤
# ![](images/jupyter-notebook.png)
# # API Overview
# ## Pythonic platform for geospatial analysis
#
# # It all starts with your GIS
# In[1]:
from arcgis.gis import GIS
# In[2]:
gis = GIS('https://deldev.maps.arcgis.com', 'demo_deldev', 'P@ssword123')
# In[3]:
enterprise = GIS('https://python.playground.esri.com/portal', 'arcgis_python', 'amazing_arcgis_123')
# # Search for content
# In[4]:
items = gis.content.search('San Diego')
# In[5]:
for item in items:
display(item)
# In[6]:
trolley_stations = items[0]
sd_attractions = items[2]
# # Visualize layers on map widget
# In[7]:
sdmap = gis.map('San Diego', zoomlevel=14)
sdmap
# In[8]:
sdmap.add_layer(sd_attractions)
# In[9]:
sdmap.add_layer(trolley_stations)
#
# ## Which attractions are near trolley stations?
# In[10]:
arcgis.env.active_gis = gis
# In[11]:
from arcgis.features.use_proximity import create_buffers
from arcgis.features.manage_data import overlay_layers
# In[12]:
places_to_see = overlay_layers(input_layer=sd_attractions,
overlay_layer=create_buffers(trolley_stations,
[0.5], units='Miles'))
# In[13]:
my_map = gis.map('San Diego', zoomlevel=14)
display(my_map)
my_map.add_layer(places_to_see)
# ## Analysis results as a table
# In[14]:
df = places_to_see.query().df
df = df[['NAME', 'Title']]
df
# # Feature data and analysis
# In[15]:
counties_item = gis.content.search('USA Counties', 'Feature Layer',
outside_org=True)[5]
counties_item
# In[16]:
counties = counties_item.layers[0]
# In[17]:
ca_counties = counties.query(where="STATE_NAME='California'")
# ## Spatial dataframe
# In[18]:
counties_df = ca_counties.df
counties_df
# ### Pandorable (attribute and spatial) selections
# In[19]:
sd_county = counties_df[counties_df.NAME == 'San Diego']
# In[20]:
# Attribute query
sd_county.POP2012.iloc[0]
# In[21]:
# query the shape of San Diego county and set it's spatial reference
sd_geom = sd_county.SHAPE.iloc[0]
sd_geom.spatial_reference = counties.container.properties.spatialReference
# In[22]:
m = gis.map('San Diego', zoomlevel=7)
m.draw(sd_geom)
m
# ## Exploratory data analysis
# In[23]:
get_ipython().run_line_magic('matplotlib', 'inline')
plotdf = counties_df.set_index('NAME')
plotdf.POP2012.plot(kind='bar', x='NAME', figsize=(15,5))
# # Smart Mapping
# In[24]:
ca_map = gis.map("California")
ca_map
# In[25]:
ca_map.add_layer(counties, {
"definition_expression": "STATE_NAME='California'",
"renderer": "ClassedColorRenderer",
"field_name": "POP2012"
})
#
# ## Big-data analysis (GeoAnalytics)
#
# # Analyse crimes in Houston, TX
#
# In[26]:
houston_gis = GIS('https://dev003246.esri.com/portal', USERNAME, PASSWORD, verify_cert=False)
# # Attach data
# In[27]:
datastores = arcgis.geoanalytics.get_datastores()
datastores.add_bigdata('Houston_crime_yearly',
r'\\teton\atma_shared\datasets\HoustonCrime')
# In[28]:
houston_yearly = houston_gis.content.search('Houston_crime_yearly',
'big data file share')[0]
houston_yearly
# In[29]:
houston_yearly = houston_gis.content.search('Houston_crime_yearly',
'big data file share')[0]
# In[30]:
houston_yearly.layers
# # Invoke batch analytics
# In[31]:
from ipywidgets import *
from arcgis.geoanalytics.analyze_patterns import find_hot_spots
arcgis.env.process_spatial_reference=32611
arcgis.env.verbose = False
houston = arcgis.geocoding.geocode('Houston, TX')[0]
# In[ ]:
for category in df.Category.unique()[:-1]:
lyrid = 0
for year in range(2010, 2017):
output_name='Houston_' + category.replace(' ', '_') + '_Hotspot_' + str(year)
print('Generating ' + output_name)
layer = houston_yearly.layers[lyrid]
layer.filter = "Category='{}'".format(category)
find_hot_spots(layer, bin_size=0.5, bin_size_unit='Miles',
neighborhood_distance=1, neighborhood_distance_unit='Miles',
output_name=output_name)
lyrid = lyrid + 1
# # View results
# ## Hot Spots across crime categories
# In[33]:
hotmap1 = houston_gis.map(houston, 10)
hotmap1.add_layer(houston_gis.content.search('Houston_Burglary_Hotspot_2016')[0])
hotmap2 = houston_gis.map(houston, 10)
hotmap2.add_layer(houston_gis.content.search('Houston_Auto_Theft_Hotspot_2016')[0])
hotmap1.layout=Layout(flex='1 1', padding='3px')
hotmap2.layout=Layout(flex='1 1', padding='3px')
items_layout = Layout(flex='1 1 auto', width='auto')
# In[34]:
display(HBox([hotmap1, hotmap2]))
display(HBox(children=[Button(description='Burglary hot spots in 2016', layout=items_layout, button_style='danger'),
Button(description='Auto theft hot spots in 2016', layout=items_layout, button_style='danger')],
layout=Layout(width='100%')))
# ## Compare Hot Spots over time
# In[35]:
maps = []
labels = []
items_layout = Layout(flex='1 1 auto', width='auto')
layout=Layout(height='300px')
# In[36]:
for year in range(2014, 2017):
layer = houston_gis.content.search('Houston_Auto_Theft_Hotspot_' + str(year))[0]
hotspotmap = houston_gis.map(houston)
hotspotmap.add_layer(layer)
hotspotmap.layout=Layout(flex='1 1', padding='3px')
maps.append(hotspotmap)
hotspotmap.basemap='gray'
labels.append(Button(description='Auto theft hot spots in ' + str(year),
layout=items_layout, button_style='danger'))
display(HBox([maps[0], maps[1], maps[2]], layout=layout))
display(HBox(children=labels, layout=Layout(width='100%')))
#
# # Use Landsat Imagery
# In[37]:
landsat_item = gis.content.search('"Landsat Multispectral"',
'Imagery Layer', outside_org=True)[0]
# In[38]:
landsat_item
# In[39]:
landsat = landsat_item.layers[0]
# ## Visualize imagery layers
# In[40]:
imagery_map = gis.map('San Diego, CA', zoomlevel=12)
imagery_map.add_layer(landsat)
imagery_map
# ## Code: Plot spectral profile at clicked location
# In[41]:
import pandas as pd
pd.DataFrame(landsat.key_properties()['BandProperties'])
# In[42]:
from bokeh.models import Range1d
from bokeh.plotting import figure, show, output_notebook
from IPython.display import clear_output
output_notebook()
def handle_click(m, pt):
m.draw(pt)
samples = landsat.get_samples(pt, pixel_size=30)
values = samples[0]['value']
x = ['1','2', '3', '4', '5', '6', '7', '8']
y = [float(int(s)/100000) for s in values.split(' ')]
p = figure(title="Spectral Profile", x_axis_label='Spectral Bands',
y_axis_label='Data Values', width=600, height=300)
p.line(x, y, legend="Selected Point", line_color="red", line_width=2)
p.circle(x, y, line_color="red", fill_color="white", size=8)
p.set(y_range=Range1d(0, 1.0))
clear_output()
show(p)
# In[43]:
click_map = gis.map('San Diego, CA', zoomlevel=12)
click_map.add_layer(landsat)
click_map.on_click(handle_click)
# ## Demo: Plot spectral profile at clicked location
# In[44]:
click_map
# ## Dynamic raster processing
# In[45]:
dynamic_map = gis.map('San Diego, CA', zoomlevel=12)
dynamic_map.add_layer(landsat)
dynamic_map
# In[46]:
from arcgis.raster.functions import *
from IPython.display import clear_output
import time
# In[111]:
for rasterfunc in landsat.properties.rasterFunctionInfos:
clear_output()
print(rasterfunc.name)
dynamic_map.add_layer(apply(landsat, rasterfunc.name))
time.sleep(2)
# ### Custom raster processing using Raster Functions
#
#
# ![](images/run.jpg)
#
# ## Function chain for weighted overlay analysis
#
#
# ## Inputs
# ### Elevation raster
# In[48]:
# Digital elevation model for the US
item_dem = enterprise.content.search('elevation_270m')[0]
lyr_dem = item_dem.layers[0]
lyr_dem
# ### Human modified index
# In[49]:
# Human Modified Index imagery layer
# This dataset is based on research on the degree of human modification to
# the landscape, on a scale of 0 - 1, where 0.0 indicates unmodified natural
# landscape and 1.0 indicates the landscape is completely modified by human activity.
item_hmi = enterprise.content.search('human_modification_index')[0]
lyr_hmi = item_hmi.layers[0]
lyr_hmi
# ## Set area of interest as layer's extent
# In[50]:
# geocode the area of interest
from arcgis.geocoding import geocode
sd_county = geocode('San Diego County',
out_sr=lyr_dem.properties.spatialReference)[0]
# In[51]:
# set the area of interest as the layer's extent
lyr_dem.extent = sd_county['extent']
lyr_dem
# ### Interactive raster processing in Jupyter Notebook
# In[52]:
clipped_elev = clip(lyr_dem, sd_geom)
clipped_elev
# In[53]:
# Create a colormap for rendering the analysis results.
red_green = [[1, 38, 115,0],[2, 86, 148,0],[3, 0x8B, 0xB5,0],[4, 0xC5, 0xDB,0],
[5, 255, 255, 0],[6, 0xFF, 0xC3,0],[7, 0xFA, 0x8E, 0],[8, 0xF2, 0x55,0],
[9, 0xE6, 0, 0]]
# ## Chaining raster functions
# In[54]:
output_values = [1,2,3,4,5,6,7,8,9]
colormap(remap(slope(clipped_elev,
slope_type='DEGREE',
z_factor=1),
input_ranges=[0,1, 1,2, 2,3, 3,5, 5,7, 7,9, 9,12, 12,15, 15,100],
output_values=output_values),
colormap=red_green)
# In[55]:
colormap(remap(clip(lyr_dem, sd_geom), # Elevation
input_ranges=[-90,250, 250,500, 500,750, 750,1000, 1000,1500, 1500,2000, 2000,2500, 2500,3000, 3000,5000],
output_values=[1,2,3,4,5,6,7,8,9]) ,
colormap=red_green)
# In[56]:
lyr_hmi.extent = sd_county['extent']
colormap(remap(clip(lyr_hmi, sd_geom), # Human modified index
input_ranges=[0.0,0.1, 0.1,0.2, 0.2,0.3, 0.3,0.4, 0.4,0.5,0.5,0.6, 0.6,0.7, 0.7,0.8, 0.8,1.1],
output_values=[1,2,3,4,5,6,7,8,9]),
colormap=red_green)
# ## Prepare input layers
# In[57]:
elevation = remap(lyr_dem, # Elevation
[-90,250, 250,500, 500,750, 750,1000, 1000,1500, 1500,2000, 2000,2500, 2500,3000, 3000,5000],
output_values)
# In[58]:
natural_areas = remap(lyr_hmi, # Human Modified Index
[0.0,0.1, 0.1,0.2, 0.2,0.3, 0.3,0.4, 0.4,0.5,0.5,0.6, 0.6,0.7, 0.7,0.8, 0.8,1.1],
output_values)
# In[59]:
terrain = remap(slope(lyr_dem, slope_type='DEGREE', z_factor=1), # Slope
[0,1, 1,2, 2,3, 3,5, 5,7, 7,9, 9,12, 12,15, 15,100],
output_values)
# ## Map algebra for Weighted Overlay Analysis
# In[60]:
# Map algebra for the web GIS!
result = 0.2*elevation + 0.3*terrain + 0.5*natural_areas
# In[61]:
run_raster = colormap(clip(result, sd_geom), colormap=red_green)
run_raster
# ## Visualize results using map widget
# In[62]:
surface_map = gis.map('San Diego, CA', zoomlevel=12)
surface_map.add_layer(run_raster)
surface_map
#
# ## Persist results as an imagery layer
# In[ ]:
# Generate a persistent result at source resolution using Raster Analytics
# resultlyr = weighted_overlay.save('SanDiego_PlacesToRun')
# In[67]:
resultlyr = enterprise.content.search('SanDiego_PlacesToRun')[0]
# In[68]:
resultlyr
# ## Results: finding solitude by applying GIS
#
# # Cross Country Mobility
# ### How does terrain/soil/vegetation... affect offroad vehicle movement?
#
# # Generate cost surface
# ## Use function chain authored in ArcGIS Pro
# In[69]:
enterprise_b = GIS('https://dev004546.esri.com/portal', 'rsinghRA', 'rsinghRA1', verify_cert=False)
# In[81]:
with open("CCM_CostRnR.rft.xml", "r", encoding='utf-8-sig') as rft:
raster_fn = rft.read()
# In[ ]:
from arcgis.raster.analytics import generate_raster
surface = generate_raster(raster_fn, output_name='CCM_Cost_Surface')
# In[ ]:
enterprise_b.content.search('CCM_Cost_Surface')
# In[71]:
surface = enterprise_b.content.search('title:CCM_Cost_Surface', 'Imagery Layer')[0]
# In[73]:
surface
# # Generate least cost paths
# ## Draw map with origin locations
# In[74]:
tank_symbol = {
"type" : "esriPMS",
"url" : "http://static.arcgis.com/images/Symbols/Transportation/Tank.png",
"contentType" : "image/png",
"width" : 19.5,
"height" : 19.5,
"angle" : 0,
"xoffset" : 0,
"yoffset" : 0
}
finish_symbol = {"angle":0,"xoffset":12,"yoffset":12,"type":"esriPMS","url":"http://static.arcgis.com/images/Symbols/Basic/CheckeredFlag.png","contentType":"image/png","width":24,"height":24}
# In[75]:
from arcgis.features import FeatureSet, Feature
arcgis.env.out_spatial_reference = 4326
# In[76]:
ramona = geocode("Ramona, CA", out_sr=102100)[0]
poway = geocode("Poway, CA", out_sr=102100)[0]
barona = geocode("Barona Reservation, CA", out_sr=102100)[0]
origins=FeatureSet([Feature(ramona['location']),
Feature(poway['location']),
Feature(barona['location'])])
#
# ## Offroad vehicle routing
# #### Generate least cost paths using cost surface
# In[77]:
from arcgis.geoprocessing import import_toolbox
ccmurl='https://maps.esri.com/apl3/rest/services/LCP/LCP/GPServer/LeastCostPath'
ccm = import_toolbox(ccmurl)
# In[78]:
def find_path(m, pt):
m.draw(pt, symbol=finish_symbol)
paths = ccm.least_cost_path(destination=FeatureSet([Feature(pt)]),
origins=origins)
m.draw(paths)
# ## Finding least cost paths interactively
# In[112]:
ccm_map = gis.map('San Vicente Reservoir', zoomlevel=10)
ccm_map.on_click(find_path)
ccm_map
# In[113]:
ramona = geocode("Ramona, CA")[0]
poway = geocode("Poway, CA")[0]
barona = geocode("Barona Reservation, CA")[0]
ccm_map.draw(ramona, symbol=tank_symbol)
ccm_map.draw(poway, symbol=tank_symbol)
ccm_map.draw(barona, symbol=tank_symbol)
#
# # Integration with scikit-image
# ### Use computer vision to locate features
# In[65]:
landsat.extent = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
'type': 'extent',
'xmax': 4296559.143733407,
'xmin': 4219969.241391764,
'ymax': 3522726.823081019,
'ymin': 3492152.0117669892}
# In[66]:
landsat
# In[67]:
preprocessed = stretch(ndvi(landsat), stretch_type='PercentClip', min_percent=30, max_percent=70, dra=True)
preprocessed
# In[68]:
def count_farms():
from skimage import feature, color
import numpy as np
from scipy.signal import convolve2d
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = preprocessed.export_image(size=[1200, 450],
export_format='jpeg', save_folder='c:\\xc', save_file='saudiarabia.jpg', f='image')
img = mpimg.imread('c:\\xc\\saudiarabia.jpg')
bw = img.mean(axis=2)
fig=plt.figure(figsize = (15,15))
ax=fig.add_subplot(1,1,1)
blobs_dog = [(x[0],x[1],x[2]) for x in feature.blob_dog(-bw,
min_sigma=4,
max_sigma=8,
threshold=0.1,
overlap=0.6)]
blobs_dog = set(blobs_dog)
img_blobs = color.gray2rgb(img)
for blob in blobs_dog:
y, x, r = blob
c = plt.Circle((x, y), r+1, color='red', linewidth=2, fill=False)
ax.add_patch(c)
plt.imshow(img_blobs)
plt.title('Center Pivot Farms')
plt.show()
print('Number of center pivot farms detected: ' + str(len(blobs_dog)))
# In[69]:
count_farms()
# # IBM Watson integration
# ### Inspection and maintenance of power insulators
# In[71]:
from IPython.display import Image
# In[89]:
img = r'C:\xc\Presentations\GeoPython\Watson\insulators\150362.JPG'
Image(img, width="600")
# In[85]:
import json
import datetime
import pandas as pd
from os import listdir
from os.path import isfile, join
from IPython.display import Image
from arcgis.gis import GIS
from arcgis.geocoding import geocode
from arcgis.features.use_proximity import plan_routes
from arcgis.features.summarize_data import join_features
# ## Using IBM Watson Image Classifier
# In[117]:
from watson_developer_cloud import VisualRecognitionV3
visual_recognition = VisualRecognitionV3('2016-05-20', api_key='f135ee67e833d45e90e2304608e0376b5a148688')
def is_broken(filepath):
with open(filepath, 'rb') as images_file:
car_results = visual_recognition.classify(images_file=images_file,
threshold=0.1,
classifier_ids=['BrokenInsulators'])
print(car_results)
classes = car_results['images'][0]['classifiers'][0]['classes']
for cls in classes:
if cls['class'] == 'broken':
if float(cls['score']) > 0.5:
return True
return False
# In[100]:
import piexif
def get_location(filename):
exif_dict = piexif.load(filename)
for tag in exif_dict['GPS']:
name = piexif.TAGS['GPS'][tag]["name"]
value = exif_dict['GPS'][tag]
if name == 'GPSLatitudeRef':
gps_latitude_ref = value
elif name == 'GPSLongitudeRef':
gps_longitude_ref = value
elif name == 'GPSLatitude':
gps_latitude = value
elif name == 'GPSLongitude':
gps_longitude = value
lat = _convert_to_degress(gps_latitude)
if gps_latitude_ref != b'N':
lat = 0 - lat
lon = _convert_to_degress(gps_longitude)
if gps_longitude_ref != b'E':
lon = 0 - lon
return lon, lat
def _convert_to_degress(val):
"""
Helper function to convert the GPS coordinates stored in the EXIF to degress in float format
:param value:
:type value: exifread.utils.Ratio
:rtype: float
"""
d = float(val[0][0]) / float(val[0][1])
m = float(val[1][0]) / float(val[1][1])
s = float(val[2][0]) / float(val[2][1])
return d + (m / 60.0) + (s / 3600.0)
# In[115]:
get_location(img)
# In[118]:
is_broken(img)
# ## Integration workflow
# In[79]:
locations = []
path = r'C:\xc\Presentations\GeoPython\Watson\insulators'
# find locations of broken insulators
for file in listdir(path):
filepath = path + '\\' + file
if is_broken(filepath):
locations.append(get_location(filepath))
# import into ArcGIS as a layer
df = pd.DataFrame.from_records(locations)
df.columns = ['x', 'y']
broken_insulators = gis.content.import_data(df)
# In[104]:
locations = []
path = r'C:\xc\Presentations\GeoPython\Watson\insulators'
# find locations of broken insulators
for file in listdir(path):
filepath = path + '\\' + file
if True: #is_broken(filepath):
locations.append(get_location(filepath))
# import into ArcGIS as a layer
df = pd.DataFrame.from_records(locations)
df.columns = ['x', 'y']
broken_insulators = gis.content.import_data(df)
# In[105]:
arcgis.env.active_gis = gis
start_location = geocode('2715 Tepper Ln NE, Keizer, OR 97303')[0]
start_time = datetime.datetime(2017, 5, 31, 9, 0)
# In[106]:
# get layer of transmission towers
towers = gis.content.search('BPA_TransmissionStructures', 'Feature Layer', outside_org=True)[0]
# find transmission towers near image locations
destinations = join_features(towers, broken_insulators,
"withindistance", 0.1, "Miles")
# plan and sequence routes for maintenance
routes = plan_routes(destinations, 2, 15,
start_time, start_location, stop_service_time=30)
# In[107]:
def show_map():
webmap = gis.content.get('4a24e5829d174732bc04f16de83eaffe')
powermap = gis.map(webmap)
display(powermap)
powermap.height ='600px'
powermap.draw(start_location, {"title": "Start location", "content": "Bonneville Power Administration"})
return powermap
# In[109]:
powermap = show_map()
# In[110]:
powermap.basemap = 'dark-gray'
powermap.add_layer(broken_insulators)
powermap.add_layer(routes['routes_layer'])
powermap.add_layer(routes['assigned_stops_layer'])
# In[114]:
powermap.zoom = 9
# # Try it out!
#
#
# * install: conda install -c esri arcgis
#
# * github repo: https://github.com/esri/arcgis-python-api
#
# * website: https://developers.arcgis.com/python
#
# We're hiring :)
#