Using the map widget
The GIS
object includes a map widget for displaying geographic locations, visualizing GIS content, and displaying the results of your analysis. To use the map widget, call gis.map()
and assign it to a variable that you will then be able to query to bring up the widget in the notebook:
import arcgis
from arcgis.gis import GIS
# Create a GIS object, as an anonymous user for this example
gis = GIS()
# Create a map widget
map1 = gis.map('Redlands, CA') # Passing a place name to the constructor
# will initialize the extent of the map.
map1
Buttons
Now that we have created a map view, let us explore the default buttons enabled on the widget:
1. Zoom in
Click the "+" sign shown on the top left corner of the widget (marked as button #1 in previous map output) to zoom into details of the map. Users can either manually zoom into a desired level of detail or set the zoom levels to an assigned number, which we will elaborate on in the next section.
2. Zoom out
Click the "-" sign shown on the top left corner of the widget (marked as button #2 in previous map output) to zoom out to a rough display of the map. Users can either manually zoom out to a desired level of details, or set the zoom levels to an assigned number, which we will elaborate on in the next section.
3. Reset the compass orientation
Click the compass sign (marked as button #3 in the previous map display) to switch the map's heading to 0.0 (north) in relation to the current device, and click again to switch back to the absolute 0.0 north.
4. 2D Map to 3D Scene
Click the "Map to Scene" button (marked as #4 in the previous map display) to switch the current view from a 2D Map to a 3D Scene. Click the button again to switch back.
Properties of the map widget
Operations of the map widget
The map widget has several properties that you can query and set, such as its zoom level, basemap, height, extent, mode, heading, rotation, tilt, scale, etc.
Zoom Level and Rotation
# Create a map widget
map2 = gis.map('Redlands, CA') # Passing a place name to the constructor
# will initialize the extent of the map.
map2
map2.zoom
11.0
Assigning a value to the zoom property will update the widget, which is equivalent to manually clicking the "zoom in" button twice.
map2.zoom = 9
You can also set the rotation property for the 2D mode. This can similarly be achieved by right-clicking and dragging on the map.
map2.rotation = 45
Your notebook can have as many of these widgets as you wish. Let's create another map widget and modify some of its properties.
Map Center
The center property reveals the coordinates of the center of the map.
map3 = gis.map() # creating a map object with default parameters
map3
map3.center
{'spatialReference': {'latestWkid': 3857, 'wkid': 102100}, 'x': 0, 'y': 1.30385160446167e-08}
If you know the latitude and longitude of your place of interest, you can assign it to the center property. For instance, we can now set the center to be within California.
map3.center = {'spatialReference': {'latestWkid': 3857, 'wkid': 102100},
'x': -13044706.248636946,
'y': 4036244.856763349}
Extent
You can use geocoding
to get the coordinates of different places and use those coordinates to drive the extent property. Geocoding
converts place names to coordinates and can be implemented by using the arcgis.geocoding.geocode()
function.
Let's geocode "Disneyland, CA" and set the map's extent to the geocoded location's extent:
location = arcgis.geocoding.geocode('Disneyland, CA', max_locations=1)[0]
map3.extent = location['extent']
Basemap
ArcGIS Living Atlas of the World is an evolving collection of authoritative, curated, ready-to-use global geographic information from Esri and the GIS user community. One of the most used types of content from the Living Atlas is basemaps. Basemaps are layers on your map over which all other operational layers that you add are displayed. Basemaps typically span the full extent of the world and provide context to your GIS
layers. It helps viewers understand where each feature is located as they pan and zoom to various extents.
When you create a new map or scene, you can choose which basemap you want from the basemap gallery in the Map Viewer. By default, the basemap gallery for your organization is a pre-configured collection from Esri using the Living Atlas. There are many more Living Atlas basemaps to choose from, and you can create your own custom basemap gallery with the ones you like. Learn more on this here.
As an administrator of your organization, you can change which basemaps your organization uses by creating a custom basemap gallery. The custom gallery can include a combination of your own basemaps, plus Living Atlas basemaps. In a nutshell, the steps to create a custom basemap gallery are as follows:
- Createa group for your custom basemap gallery.
- Add maps you want to use as basemaps to the group.
- Set the group as your organization’s basemap gallery.
These steps are detailed in the Create a custom basemap gallery for your organization blog. After step one is done, users can move forward to :
When your gis
connection is created anonymously, the basemaps
and gallery_basemaps
properties of the created MapView
object displays the default themes. While signing onto your own organization, the two properties would display your own customized options if set.
Your map can have a number of different basemaps. To see what basemaps are included with the widget, query the basemaps
property:
map3.basemap # the current basemap being used
'default'
map3.basemaps # the basemap galleries
['dark-gray', 'dark-gray-vector', 'gray', 'gray-vector', 'hybrid', 'national-geographic', 'oceans', 'osm', 'satellite', 'streets', 'streets-navigation-vector', 'streets-night-vector', 'streets-relief-vector', 'streets-vector', 'terrain', 'topo', 'topo-vector']
You can assign any one of the supported basemaps to the basemap
property to change the basemap. For instance, you can change the basemap to the satellite
basemap as below:
map3.basemap = 'satellite'
Mode
The map widget also includes support for a 3D mode! You can specify the mode
parameter either through gis.map(mode="foo")
or by setting the mode property of any initiated map object. Run the following cell:
usa_map = gis.map('USA', zoomlevel=4, mode="3D") # Notice `mode="3D"`
usa_map
# And you can set the mode separately
usa_map.mode = "2D"
Heading, tilt and scale
Just like the 2D mode, you can pan by clicking-and-dragging with the left mouse button, and you can zoom with the mouse wheel. In 3D mode, clicking-and-dragging with the right mouse button modifies the tilt field and the heading field.
tilt
is a number from 0-90, with 0 representing a top-down 'birds-eye' view, while 90 represents being completely parallel to the ground, facing the horizon.
usa_map.tilt
0.2263099051855245
usa_map.tilt = 22.63
It's important to note that 2D mode uses rotation
to specify the number of angles clockwise from due north, while 3D mode uses heading
to specify the number of degrees counterclockwise of due north. See the API reference for more information.
usa_map.heading
0.0
usa_map.heading = 60
Using multiple map widgets
Demo: creating multiple widgets in the same notebook
Stacking maps using HBox and VBox
One commonly adopted workflow for creating multiple widgets in the same notebook is to embed Python API map widgets within HBox
and VBox
. First, let's walk through an example of displaying Landsat imagery of two different dates side by side using an HBox
structure:
# search for the landsat multispectral imagery layer
landsat_item = gis.content.search("Landsat Multispectral tags:'Landsat on AWS','landsat 8', 'Multispectral', 'Multitemporal', 'imagery', 'temporal', 'MS'", 'Imagery Layer', outside_org=True)[0]
landsat = landsat_item.layers[0]
landsat
import pandas as pd
from datetime import datetime
from ipywidgets import *
from arcgis import geocode
aoi = {'xmin': -117.58051663099998,
'ymin': 33.43943880400006,
'xmax': -114.77651663099998,
'ymax': 36.243438804000064,
'spatialReference': {'latestWkid': 4326, 'wkid': 102100},}
selected1 = landsat.filter_by(where="(Category = 1) AND (CloudCover <=0.10)",
time=[datetime(2017, 11, 15), datetime(2018, 1, 1)],
geometry=arcgis.geometry.filters.intersects(aoi))
df = selected1.query(out_fields="AcquisitionDate, GroupName, CloudCover, DayOfYear",
order_by_fields="AcquisitionDate").sdf
df['AcquisitionDate'] = pd.to_datetime(df['AcquisitionDate'], unit='ms')
df.tail(5)
OBJECTID | AcquisitionDate | GroupName | CloudCover | DayOfYear | SHAPE | |
---|---|---|---|---|---|---|
17 | 2077276 | 2017-12-27 18:21:56 | LC08_L1TP_040035_20171227_20200902_02_T1_MTL | 0.0033 | 35 | {"rings": [[[-12818204.659699999, 4395823.9407... |
18 | 2077299 | 2017-12-27 18:22:20 | LC08_L1TP_040036_20171227_20200902_02_T1_MTL | 0.0023 | 36 | {"rings": [[[-12866539.864599999, 4199117.8993... |
19 | 2077322 | 2017-12-27 18:22:44 | LC08_L1TP_040037_20171227_20200902_02_T1_MTL | 0.0025 | 37 | {"rings": [[[-12913258.506099999, 4005782.8035... |
20 | 2072419 | 2017-12-29 18:09:58 | LC08_L1TP_038036_20171229_20200902_02_T1_MTL | 0.0004 | 36 | {"rings": [[[-12522415.1111, 4199148.978100002... |
21 | 2072442 | 2017-12-29 18:10:21 | LC08_L1TP_038037_20171229_20200902_02_T1_MTL | 0.0014 | 37 | {"rings": [[[-12569056.4399, 4005554.541000001... |
selected2 = landsat.filter_by(where="(Category = 1) AND (CloudCover <=0.10)",
time=[datetime(2021, 11, 15), datetime(2022, 1, 1)],
geometry=arcgis.geometry.filters.intersects(aoi))
df = selected2.query(out_fields="AcquisitionDate, GroupName, CloudCover, DayOfYear",
order_by_fields="AcquisitionDate").sdf
df['AcquisitionDate'] = pd.to_datetime(df['AcquisitionDate'], unit='ms')
df.tail(5)
OBJECTID | AcquisitionDate | GroupName | CloudCover | DayOfYear | SHAPE | |
---|---|---|---|---|---|---|
5 | 3312582 | 2021-11-27 18:29:05 | LC08_L1TP_041037_20211127_20211208_02_T1_MTL | 0.0444 | 37 | {"rings": [[[-13082602.7775, 4005564.120899997... |
6 | 3312557 | 2021-11-29 18:15:56 | LC08_L1TP_039035_20211129_20211209_02_T1_MTL | 0.0027 | 35 | {"rings": [[[-12643840.9522, 4395757.556199998... |
7 | 3312558 | 2021-11-29 18:16:20 | LC08_L1TP_039036_20211129_20211209_02_T1_MTL | 0.0014 | 36 | {"rings": [[[-12691989.560800001, 4198998.6582... |
8 | 3312559 | 2021-11-29 18:16:44 | LC08_L1TP_039037_20211129_20211209_02_T1_MTL | 0.0013 | 37 | {"rings": [[[-12738717.156100001, 4005622.7378... |
9 | 3329077 | 2021-12-15 18:16:43 | LC08_L1TP_039037_20211215_20211223_02_T1_MTL | 0.0052 | 37 | {"rings": [[[-12739407.4636, 4005732.446599997... |
def side_by_side(address, layer1, layer2):
location = geocode(address)[0]
satmap1 = gis.map(location)
satmap1.add_layer(layer1)
satmap2 = gis.map(location)
satmap2.add_layer(layer2)
satmap1.layout=Layout(flex='1 1', padding='6px', height='450px')
satmap2.layout=Layout(flex='1 1', padding='6px', height='450px')
box = HBox([satmap1, satmap2])
return box
side_by_side("San Bernadino County, CA", selected1, selected2)
Synchronizing nav between multiple widgets
A side-by-side display of two maps is great for users wanting to explore the differences between two maps. However, if one map gets dragged or panned, the other map is not following the movements automatically. The methods sync_navigation
and unsync_navigation
can be introduced to resolve this. With these methods, we can modify the previous example to have the maps in sync.
def side_by_side2(address, layer1, label1, layer2, label2):
location = geocode(address)[0]
satmap1 = gis.map(location)
satmap1.add_layer(layer1)
satmap2 = gis.map(location)
satmap2.add_layer(layer2)
# create 2 hbox - one for title, another for maps
hb1 = HBox([Label(label1), Label(label2)])
hb2 = HBox([satmap1, satmap2])
# set hbox layout preferences
hbox_layout = Layout()
hbox_layout.justify_content = 'space-around'
hb1.layout, hb2.layout = hbox_layout, hbox_layout
# sync all maps
satmap1.sync_navigation(satmap2)
return VBox([hb1,hb2])
side_by_side2("San Bernadino County, CA", selected1, 'Dec 2017', selected2, 'Dec 2021')
The synced display of 4 maps
Now let's sync the display of 4 maps:
selected1r = landsat.filter_by(where="(Category = 1) AND (CloudCover <=0.10)",
time=[datetime(2018, 11, 15), datetime(2019, 1, 1)],
geometry=arcgis.geometry.filters.intersects(aoi))
df = selected1r.query(out_fields="AcquisitionDate, GroupName, CloudCover, DayOfYear",
order_by_fields="AcquisitionDate").sdf
df['AcquisitionDate'] = pd.to_datetime(df['AcquisitionDate'], unit='ms')
df.tail(5)
OBJECTID | AcquisitionDate | GroupName | CloudCover | DayOfYear | SHAPE | |
---|---|---|---|---|---|---|
4 | 1683891 | 2018-12-16 18:09:40 | LC08_L1TP_038036_20181216_20200830_02_T1_MTL | 0.0311 | 36 | {"rings": [[[-12518740.9842, 4199058.405000001... |
5 | 1687739 | 2018-12-23 18:15:27 | LC08_L1TP_039035_20181223_20200830_02_T1_MTL | 0.0453 | 35 | {"rings": [[[-12642295.304499999, 4395699.8553... |
6 | 1690955 | 2018-12-30 18:21:38 | LC08_L1TP_040035_20181230_20200829_02_T1_MTL | 0.0548 | 35 | {"rings": [[[-12814406.3816, 4395749.071999997... |
7 | 1690978 | 2018-12-30 18:22:02 | LC08_L1TP_040036_20181230_20200830_02_T1_MTL | 0.0085 | 36 | {"rings": [[[-12862669.9624, 4198971.424900003... |
8 | 1691001 | 2018-12-30 18:22:26 | LC08_L1TP_040037_20181230_20200830_02_T1_MTL | 0.0011 | 37 | {"rings": [[[-12909449.0056, 4005655.638700001... |
selected2l = landsat.filter_by(where="(Category = 1) AND (CloudCover <=0.10)",
time=[datetime(2020, 11, 15), datetime(2021, 1, 1)],
geometry=arcgis.geometry.filters.intersects(aoi))
df = selected2l.query(out_fields="AcquisitionDate, GroupName, CloudCover, DayOfYear",
order_by_fields="AcquisitionDate").sdf
df['AcquisitionDate'] = pd.to_datetime(df['AcquisitionDate'], unit='ms')
df.tail(5)
OBJECTID | AcquisitionDate | GroupName | CloudCover | DayOfYear | SHAPE | |
---|---|---|---|---|---|---|
14 | 338139 | 2020-12-05 18:10:33 | LC08_L1TP_038037_20201205_20210313_02_T1_MTL | 0.0010 | 37 | {"rings": [[[-12590665.892099999, 3909761.2709... |
15 | 2952645 | 2020-12-10 18:28:17 | LC08_L1TP_041035_20201210_20210313_02_T1_MTL | 0.0963 | 35 | {"rings": [[[-12988587.3908, 4395753.784199998... |
16 | 356604 | 2020-12-19 18:22:05 | LC08_L1TP_040035_20201219_20210310_02_T1_MTL | 0.0060 | 35 | {"rings": [[[-12816854.251699999, 4395799.5128... |
17 | 356626 | 2020-12-19 18:22:29 | LC08_L1TP_040036_20201219_20210310_02_T1_MTL | 0.0026 | 36 | {"rings": [[[-12865073.5445, 4199098.650499999... |
18 | 356648 | 2020-12-19 18:22:53 | LC08_L1TP_040037_20201219_20210310_02_T1_MTL | 0.0004 | 37 | {"rings": [[[-12911955.1563, 4005769.613499999... |
def side_by_side3(address, layers_list, labels_list):
[layer1, layer2, layer3, layer4] = layers_list
[label1, label2, label3, label4] = labels_list
location = geocode(address)[0]
satmap1 = gis.map(location)
satmap1.add_layer(layer1)
satmap2 = gis.map(location)
satmap2.add_layer(layer2)
satmap3 = gis.map(location)
satmap3.add_layer(layer3)
satmap4 = gis.map(location)
satmap4.add_layer(layer4)
# create 2 hbox - one for title, another for maps
hb1 = HBox([Label(label1), Label(label2)])
hb2 = HBox([satmap1, satmap2])
hb3 = HBox([Label(label3), Label(label4)])
hb4 = HBox([satmap3, satmap4])
# set hbox layout preferences
hbox_layout = Layout()
hbox_layout.justify_content = 'space-around'
hb1.layout, hb2.layout, hb3.layout, hb4.layout = hbox_layout, hbox_layout, hbox_layout, hbox_layout
# sync all maps
satmap1.sync_navigation(satmap2)
satmap1.sync_navigation(satmap3)
satmap1.sync_navigation(satmap4)
return VBox([hb1,hb2,hb3,hb4])
side_by_side3("San Bernadino County, CA",
[selected1,selected1r,selected2l,selected2],
['Dec 2017', 'Dec 2018', 'Dec 2020', 'Dec 2021'])
Dragging and panning onto any of these 4 maps will lead to synchronous movements of the other three maps, which means that the map center, zoom levels, and extent of these maps will always stay the same. This is one of the biggest advantages of sync_navigation()
.
Conclusion
In Part 2 of the guide series, we have talked about the use of map widgets, including buttons, features, and properties, and have seen three examples of displaying multiple map widgets in a group view using HBox
and VBox
. In the next chapter, we will discuss how to visualize spatial data on the map widget.