The feature layer is the primary concept for working with features in a GIS.
Users create, import, export, analyze, edit, and visualize features, i.e. “entities in space” as feature layers.
Feature layers can be added to and visualized using maps. They act as inputs to and outputs from feature analysis tools.
Feature layers are created by publishing feature data to a GIS, and are exposed as a broader resource (Item) in the GIS. Feature layer instances can be obtained through the layers attribute on feature layer collection Items in the GIS. A feature layer collection is a collection of feature layers and tables, with the associated relationships among the entities. A feature layer collection is backed by a feature service in a web GIS.
Accessing Feature Layers
Feature layer collection items are available as content in the GIS. You can search the GIS for feature layer collection items, or get them using their item id.
Feature layers are available through the layers attribute on feature layer collection Items in the GIS.
Searching the GIS for feature layers
You can search the GIS for feature layer collections by specifying the item type as 'Feature Layer Collection' or 'Feature Layer'.
Note: A feature layer collection can be considered a type of feature layer such as a group feature layer. Hence, you can specify the item type as 'Feature Layer' and still get back feature layer collection items as results.
The examples below will clarify this further:
# Establish a connection to your GIS.
from arcgis.gis import GIS
from IPython.display import display
gis = GIS() # anonymous connection to www.arcgis.com
# Search for 'USA major cities' feature layer collection
search_results = gis.content.search('title: USA Major Cities',
'Feature Layer')
# Access the first Item that's returned
major_cities_item = search_results[0]
major_cities_item
Note that the major_cities_item is a 'Feature Layer Collection' item.
Since this item is a Feature Layer Collection, accessing the layers
property will give us a list of FeatureLayer
objects.
major_cities_layers = major_cities_item.layers
major_cities_layers
[<FeatureLayer url:"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Major_Cities/FeatureServer/0">]
Accessing feature layers using item id
Feature layer collection items are available as content in the GIS. You can get them using their item id, and query their layers property to get to the feature layers:
freeways = gis.content.get('91c6a5f6410b4991ab0db1d7c26daacb')
freeways
Since freeways is a Feature Layer Collection item, accessing the layers property will give us a list of FeatureLayer objects. This item has two layers:
freeways.layers
[<FeatureLayer url:"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Freeway_System/FeatureServer/1">, <FeatureLayer url:"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Freeway_System/FeatureServer/2">]
The code below cycles through the layers and prints their names:
for lyr in freeways.layers:
print(lyr.properties.name)
USA Freeway System (over 1:500k) USA Freeway System (below 1:500k)
Accessing feature layers and tables from feature services
A feature service serves a collection of feature layers and tables, with the associated relationships among the entities. It is represented by arcgis.features.FeatureLayerCollection
in the ArcGIS Python API.
Instances of FeatureLayerCollection can be constructed using a feature service url, as shown below:
from arcgis.features import FeatureLayerCollection
fs_url = 'https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/SF_311_Incidents/FeatureServer'
sanfran = FeatureLayerCollection(fs_url)
The collection of layers and tables in a FeatureLayerCollection can be accessed using the layers and tables properties respectively:
sanfran.layers
[<FeatureLayer url:"https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/SF_311_Incidents/FeatureServer/0">]
Tables represent entity classes with uniform properties. In addition to working with “entities with location” as features, the GIS can also work with non-spatial entities as rows in tables. Working with tables is similar to working with feature layers, except that the rows (Features) in a table do not have a geometry, and tables ignore any geometry related operation.
The sanfran feature layer collection also has a table that can be obtained using its tables property:
sanfran.tables
[<Table url:"https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/SF_311_Incidents/FeatureServer/1">]
Accessing feature layers from a feature layer url
Instances of FeatureLayers can also be constructed using a url to the REST endpoint of a feature layer:
from arcgis.features import FeatureLayer
lyr_url = 'https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/SF_311_Incidents/FeatureServer/0'
layer = FeatureLayer(lyr_url)
layer
<FeatureLayer url:"https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/arcgis/rest/services/SF_311_Incidents/FeatureServer/0">
Properties of FeatureLayer
In this section, let us take a closer look at the properties of a FeatureLayer
object. We will use the major_cities_layers
object created earlier
feature_layer = major_cities_item.layers[0]
feature_layer
<FeatureLayer url:"https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Major_Cities/FeatureServer/0">
The properties
field on a FeatureLayer
object provides a dictionary representation of all its properties. However you can access individual properties as fields as well:
feature_layer.properties.extent
{ "xmin": -17608123.3895845, "ymin": 2237818.89912024, "xmax": -7656889.54512499, "ymax": 9568526.08684907, "spatialReference": { "wkid": 102100, "latestWkid": 3857 } }
The capabilities
property is useful to know what kinds of edits and operations be performed on the feature layer
feature_layer.properties.capabilities
'Query'
You can access the rendering information from the drawingInfo
property
feature_layer.properties.drawingInfo.renderer.type
'classBreaks'
Querying feature layers
Querying is a powerful operation that can be performed on a FeatureLayer
object. Let's take a closer look here. To write meaningful queries, we need to know the names of fields present in the layer. This can be determined by calling the fields
property:
for f in feature_layer.properties.fields:
print(f['name'])
FID NAME CLASS ST STFIPS PLACEFIPS CAPITAL POP_CLASS POPULATION POP2010 WHITE BLACK AMERI_ES ASIAN HAWN_PI HISPANIC OTHER MULT_RACE MALES FEMALES AGE_UNDER5 AGE_5_9 AGE_10_14 AGE_15_19 AGE_20_24 AGE_25_34 AGE_35_44 AGE_45_54 AGE_55_64 AGE_65_74 AGE_75_84 AGE_85_UP MED_AGE MED_AGE_M MED_AGE_F HOUSEHOLDS AVE_HH_SZ HSEHLD_1_M HSEHLD_1_F MARHH_CHD MARHH_NO_C MHH_CHILD FHH_CHILD FAMILIES AVE_FAM_SZ HSE_UNITS VACANT OWNER_OCC RENTER_OCC
The query method has a number of parameters that allow you to refine and transform the results. Since the processing is performed on the server, this operation is not restricted by the capacity of the client computer.
For instance, let us select all the cities whose population in the year 2010 was greater than 1 million. Instead of returning all the fields, let us get only population related fields
query_result1 = feature_layer.query(where='POP2010>1000000',
out_fields='WHITE,BLACK,MULT_RACE,HISPANIC')
len(query_result1.features)
9
query_result1.fields
[{'name': 'FID', 'type': 'esriFieldTypeOID', 'alias': 'FID', 'sqlType': 'sqlTypeInteger', 'domain': None, 'defaultValue': None}, {'name': 'WHITE', 'type': 'esriFieldTypeInteger', 'alias': 'WHITE', 'sqlType': 'sqlTypeInteger', 'domain': None, 'defaultValue': None}, {'name': 'BLACK', 'type': 'esriFieldTypeInteger', 'alias': 'BLACK', 'sqlType': 'sqlTypeInteger', 'domain': None, 'defaultValue': None}, {'name': 'MULT_RACE', 'type': 'esriFieldTypeInteger', 'alias': 'MULT_RACE', 'sqlType': 'sqlTypeInteger', 'domain': None, 'defaultValue': None}, {'name': 'HISPANIC', 'type': 'esriFieldTypeInteger', 'alias': 'HISPANIC', 'sqlType': 'sqlTypeInteger', 'domain': None, 'defaultValue': None}]
If we are only interested in the count, we could save bandwidth by setting the return_count_only
to True
feature_layer.query(where='POP2010>1000000', return_count_only=True)
9
Querying features using a different spatial reference
query_result1.spatial_reference
{'wkid': 102100, 'latestWkid': 3857}
By default, the query results are in the same spatial reference as the source layer. However you can use the out_sr
parameter to reproject the result into a desired spatial reference. The projection happens on the server and on all the resulting features.
In the example above, we obtained data in wkid:3857, a well known id for 'Web Mercator' projection. We can observe how the coordinates look like below:
query_result1.features[0].geometry
{'x': -9756835.705284344, 'y': 5124572.044517264, 'spatialReference': {'wkid': 102100, 'latestWkid': 3857}}
The coordinates are in projected coordinate system as expected. If we wish to have this data in latitude and longitude instead, we could do so by changing the out_sr
to wkid:4326
FeatureSet properties
As seen previously, a FeatureSet
is returned by a query()
operation. The FeatureSet
object packs a bunch of useful properties that help us discern useful information about the features under access
One of the important properties is the spatial_reference
as you saw earlier. Below, we are using the same query_result1
FeatureSet
from earlier query operation.
query_result1.spatial_reference
{'wkid': 102100, 'latestWkid': 3857}
One of the most powerful operation on a FeatureSet
is accessing the features not as Feature
objects, but as pandas dataframe objects. The sdf
property, returns a dataframe object:
query2 = feature_layer.query(where="POP2010 > 1000000")
query2.sdf
FID | NAME | CLASS | ST | STFIPS | PLACEFIPS | CAPITAL | POP_CLASS | POPULATION | POP2010 | ... | MARHH_NO_C | MHH_CHILD | FHH_CHILD | FAMILIES | AVE_FAM_SZ | HSE_UNITS | VACANT | OWNER_OCC | RENTER_OCC | SHAPE | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 63 | Chicago | city | IL | 17 | 1714000 | 10 | 2781116 | 2695598 | ... | 168435 | 28332 | 116555 | 576793 | 3.4 | 1194337 | 148777 | 469562 | 575998 | {"x": -9756835.705284344, "y": 5124572.0445172... | |
1 | 415 | Los Angeles | city | CA | 06 | 0644000 | 10 | 3986442 | 3792621 | ... | 245056 | 46908 | 118939 | 807326 | 3.53 | 1413995 | 95827 | 503863 | 814305 | {"x": -13165820.99345245, "y": 4035892.5853389... | |
2 | 550 | San Diego | city | CA | 06 | 0666000 | 10 | 1397856 | 1307402 | ... | 105134 | 12062 | 33865 | 285221 | 3.28 | 516033 | 32941 | 233158 | 249934 | {"x": -13040584.546974456, "y": 3858240.841215... | |
3 | 910 | Phoenix | city | AZ | 04 | 0455000 | State | 10 | 1601381 | 1445632 | ... | 102047 | 23353 | 54452 | 330762 | 3.42 | 590149 | 75343 | 296742 | 218064 | {"x": -12476005.927345268, "y": 3954668.990210... |
4 | 2174 | New York | city | NY | 36 | 3651000 | 10 | 8691599 | 8175133 | ... | 566484 | 77061 | 344755 | 1850221 | 3.32 | 3371062 | 261278 | 962892 | 2146892 | {"x": -8238770.183515309, "y": 4969744.1655956... | |
5 | 2987 | Philadelphia | city | PA | 42 | 4260000 | 10 | 1587761 | 1526006 | ... | 95310 | 18486 | 86946 | 340354 | 3.2 | 670171 | 70435 | 324536 | 275200 | {"x": -8366882.6254769275, "y": 4858876.345453... | |
6 | 3592 | Dallas | city | TX | 48 | 4819000 | 10 | 1323651 | 1197816 | ... | 76902 | 14512 | 50242 | 265538 | 3.42 | 516639 | 58582 | 201880 | 256177 | {"x": -10775254.59703981, "y": 3865959.6760583... | |
7 | 3641 | Houston | city | TX | 48 | 4835000 | 10 | 2333285 | 2099451 | ... | 145478 | 26039 | 85014 | 481570 | 3.38 | 892646 | 110003 | 355236 | 427407 | {"x": -10616262.315905476, "y": 3472578.411494... | |
8 | 3726 | San Antonio | city | TX | 48 | 4865000 | 10 | 1442472 | 1327407 | ... | 100314 | 16067 | 56901 | 318043 | 3.34 | 524246 | 44604 | 271070 | 208572 | {"x": -10964134.924024679, "y": 3429663.909911... |
9 rows × 50 columns
Accessing the features as a dataframe makes if easier to analyze the data statistically.
Accessing Features from query results
query_geographic = feature_layer.query(where='POP2010 > 1000000', out_sr='4326')
query_geographic.features[0].geometry
{'x': -87.64714638699998, 'y': 41.75648806100002, 'spatialReference': {'wkid': 4326, 'latestWkid': 4326}}
We can execute the query()
method on the first FeatureLayer
object and get a FeatureSet
. Let us query and access the first 10 features in this layer
major_cities_l1 = major_cities_item.layers[0]
major_cities_l1_fset = major_cities_l1.query(where= 'FID < 11')
type(major_cities_l1_fset)
arcgis.features.feature.FeatureSet
Now, accessing the features
property of the above FeatureSet
object will provide us the individual point Features
.
major_cities_l1_features = major_cities_l1_fset.features
len(major_cities_l1_features)
10
Accessing Feature geometry and attributes
As mentioned earlier, the Feature
object is a fine grained representation of spatial information. Two important properties of a Feature
object are its geometry
and attributes
:
Let us display the geometry and attributes of the first feature
major_cities_l1_features[0].geometry
{'x': -12462673.723706165, 'y': 5384674.994080178, 'spatialReference': {'wkid': 102100, 'latestWkid': 3857}}
major_cities_l1_features[0].attributes
{'FID': 1, 'NAME': 'Ammon', 'CLASS': 'city', 'ST': 'ID', 'STFIPS': '16', 'PLACEFIPS': '1601990', 'CAPITAL': ' ', 'POP_CLASS': 6, 'POPULATION': 15181, 'POP2010': 13816, 'WHITE': 13002, 'BLACK': 73, 'AMERI_ES': 67, 'ASIAN': 113, 'HAWN_PI': 9, 'HISPANIC': 884, 'OTHER': 307, 'MULT_RACE': 245, 'MALES': 6750, 'FEMALES': 7066, 'AGE_UNDER5': 1468, 'AGE_5_9': 1503, 'AGE_10_14': 1313, 'AGE_15_19': 1058, 'AGE_20_24': 734, 'AGE_25_34': 2031, 'AGE_35_44': 1767, 'AGE_45_54': 1446, 'AGE_55_64': 1136, 'AGE_65_74': 665, 'AGE_75_84': 486, 'AGE_85_UP': 209, 'MED_AGE': 29.6, 'MED_AGE_M': 28, 'MED_AGE_F': 30.8, 'HOUSEHOLDS': 4476, 'AVE_HH_SZ': 3.05, 'HSEHLD_1_M': 457, 'HSEHLD_1_F': 648, 'MARHH_CHD': 1618, 'MARHH_NO_C': 1131, 'MHH_CHILD': 106, 'FHH_CHILD': 335, 'FAMILIES': 3352, 'AVE_FAM_SZ': 3.61, 'HSE_UNITS': 4747, 'VACANT': 271, 'OWNER_OCC': 3205, 'RENTER_OCC': 1271}
Using feature collections
Similar to feature layers, feature collections can also be used to store features. With a feature collection, a service is not created to serve out feature data. Instead it is stored as json data with the item. Feature collections can be added to maps as layers, passed as input to feature analysis tools and queried for feature data.
Feature collections are shared in the GIS as items. Feature Collection Items can be searched by specifying 'Feature Collection' as the item_type.
Let us search for feature collection items published by Esri Media as an example:
Searching for Feature Collections
search_fc = gis.content.search("owner:EsriMedia", item_type='Feature Collection', max_items=20)
em_fc_item = search_fc[1]
em_fc_item
Accessing the layers
property on a feature collection item returns a list of FeatureCollection
objects
em_fc_item.layers
[<FeatureCollection>]
crime_pot_fc = em_fc_item.layers[0]
Querying the Feature Collection
You can call the query()
method on a FeatureCollection
object to get a FeatureSet
.
crime_fset = crime_pot_fc.query()
Accessing features from a Feature Collection
Once you have a FeatureSet
object, you can access the features
property to get a list of Feature
objects as seen earlier
crime_features = crime_fset.features
crime_features[0].geometry
{'rings': [[[-13162769, 4027325], [-13162572, 4027320], [-13162548, 4027353], [-13162307, 4027204], [-13162293, 4027223], [-13162309, 4027351], [-13162179, 4027349], [-13162165, 4027265], [-13162142, 4027265], [-13162097, 4027023], [-13162120, 4027016], [-13162117, 4026993], [-13162001, 4026344], [-13161974, 4026340], [-13161800, 4025409], [-13161828, 4025409], [-13161809, 4025235], [-13161662, 4024432], [-13161632, 4024432], [-13161536, 4024043], [-13161424, 4023822], [-13161405, 4023733], [-13161505, 4023717], [-13161459, 4023479], [-13161486, 4023475], [-13161482, 4023448], [-13161625, 4023452], [-13161625, 4023340], [-13161696, 4023422], [-13161785, 4023452], [-13162794, 4023452], [-13162836, 4023413], [-13162883, 4023452], [-13165214, 4023450], [-13165223, 4027359], [-13163604, 4027386], [-13163280, 4027371], [-13163256, 4027336], [-13162769, 4027325]]], 'spatialReference': {'wkid': 102100, 'latestWkid': 3857}}