This sample uses the Urban API for two separate tasks. First, to create a new project within the existing urban model. Second, to demonstrate a lifecycle of an object:
- Add a LOD1 building to the project.
- Make changes to the LOD1 building, that is change the extrusion height of the building.
- Delete (retire) the LOD1 building.
Download the study area and the LOD1 building footprint used in this sample. Unpack the zip files to get to both files provided in a shapefile format. The files contain the selection of parcels that define the study area of your project, and the building footprints. The geometries are located in the city of Hardeeville, USA.
To test the code from this sample, set up your own new urban model based on the USA Default template. This way you have an access to the relevant feature service and can create, update or delete objects.
Import relevant libraries and the Urban API schema stored as a Python module. See the Python sgqlc client library section to get instructions on how to export the schema to a Python module. The geopandas library is used to read the geometry of the study area and the building footprint from the shapefile files.
from sgqlc.operation import Operation
from sgqlc.endpoint.http import HTTPEndpoint
from urban_api_schema import urban_api_schema as schema
import geopandas as gpd
import re
Provide the endpoint
and token
variables.
Replace the token
variable with your token to authenticate the account which hosts the created model.
You can read more about how to retrieve a token in the Authentication guide.
Set paths of two additional datasets.
token = "ACCESS_TOKEN"
endpoint_headers = {
'X-Esri-Authorization': 'Bearer ' + token,
}
endpoint_url = 'https://urban-api.arcgis.com/graphql'
endpoint = HTTPEndpoint(endpoint_url, endpoint_headers)
# set paths to datasets
dataset_parcels = 'LOCAL_PATH/parcels_hardeeville_selection/parcels_hardeeville_selection.shp'
dataset_footprint = 'LOCAL_PATH/footprints_3d/footprints_3d.shp'
Part I: Create a project
Create an urban design database
An urban design database is an ArcGIS Online placeholder for your future project.
To create it, use the create
mutation and provide the required arguments: urban
and title
.
Note, that you need to replace the urban
variable in the code below with the urban model id value of the urban model you created.
Furthermore, add the value to the folder
variable if you want to save the urban design database item in a specific folder in ArcGIS Online.
Make a call to the endpoint to create the urban design database.
op = Operation(schema.Mutation)
create_urban_design_database = op.create_urban_design_database(
urban_model_id="8d199b9a69664aac9b2c3a69573XXXXX",
folder_id="2b65bb43c066428bac634f22e74XXXXX",
title="Public school (Urban Design Database)"
)
# make a call to the endpoint to create the urban design database
json_data = endpoint(op)
errors = json_data.get('errors')
if errors:
print(errors)
To parse the returned JSON data into more convenient native objects, add the query operation to the results variable.
The create
mutation returns a single field only, the Portal
.
That is why you do not need to provide return fields for your mutation.
Assign the returned Portal
to the urban
variable, which you will use in the next steps.
obj = op + json_data
urban_design_database_id = obj.create_urban_design_database
Load the study area of the project
Load the study area from a shapefile. The area encompasses 3 parcels.
Make a union of all 3 parcels to a single area. Assign the geometry and spatial reference of a newly created parcel to rings
and wkid
variables respectively.
# read the shapefile to pandas geodataframe
parcels = gpd.read_file(dataset_parcels)
# make a union of 3 polygons (parcels) to a single area
parcels['new_column'] = 0
parcels_joined = parcels.dissolve(by='new_column')
# set the geometry of merged parcels -> 2d rings
rings = [list(geom.exterior.coords) for geom in parcels_joined.geometry]
# get the spatial reference information
wkid = int(re.search(r'\d+', str(parcels.crs)).group())
Create a project
You will use a create
mutation to create a new project.
First, prepare an attributes
variable that holds the properties of a single project.
Add the required arguments which are project attributes event
, start
, end
, and owner
, as well as a project geometry
.
Note, that you need to replace the owner
variable with the ArcGIS account username of the user which you are using to try out this sample code.
owner_name = "urban_sandbox_prod"
# a list of projects to be added --> in this case, only a single one
attributes = {
'event_name': "Primary school",
'start_date': 1459943798196,
'end_date': 1514847600000,
'owner_name': owner_name
}
projects = [
{
'attributes': attributes,
'geometry':{'rings':rings, 'spatial_reference': {'wkid':wkid}}
}
]
Initialize the mutation and select a global
as a return field.
Note, that the urban
argument is the id of the urban design database item created in the previous step.
Make a call to the endpoint and create a project.
# initialize the mutation
op2 = Operation(schema.Mutation)
create_projects = op2.create_projects(
urban_database_id=urban_design_database_id,
projects=projects
)
# select relevant return fields
create_projects.attributes.__fields__('global_id')
# make a call to the endpoint and create a project
json_data = endpoint(op2)
errors = json_data.get('errors')
if errors:
print(errors)
To parse the returned JSON data into more convenient native objects, add the query operation to the results variable.
Save the global
of the just created project to a project
variable, which you will use later to invoke the new project.
obj = op2 + json_data
project_global_id = obj.create_projects[0].attributes.global_id
Create scenarios
Add two scenarios (existing conditions and future conditions) to the just created project. The logic behind creating scenarios is very similar to creating projects, therefore the steps are not described in detail.
# set up existing branch
attributes_existing = {
'branch_name': 'Existing',
'branch_order': 1,
'owner_name': owner_name,
'urban_event_id': project_global_id,
'existing': True,
'description':"existing conditions"
}
single_branch_existing = {'attributes': attributes_existing}
# set up future scenario branch
attributes_future = {
'branch_name': 'Scenario 1',
'branch_order': 2,
'owner_name': owner_name,
'urban_event_id': project_global_id,
'existing': False,
'description':"the basic concept"
}
single_branch_future = {'attributes': attributes_future}
branches_list = [single_branch_existing, single_branch_future]
# initialize the mutation
op3 = Operation(schema.Mutation)
create_branches = op3.create_branches(
urban_database_id=urban_design_database_id,
branches=branches_list
)
# select relevant return fields
create_branches.attributes.__fields__('global_id', 'existing')
# make a call to the endpoint and create a project
json_data = endpoint(op3)
errors = json_data.get('errors')
if errors:
print(errors)
To parse the returned JSON data into more convenient native objects, add the query operation to the results variable.
Save the global
of the just created future branch to a branch
variable, which you will use when adding a LOD1 building.
obj = op3 + json_data
for branch in obj.create_branches:
if branch.attributes.existing==False:
branch_id = branch.attributes.global_id
Finally, open your urban model in ArcGIS Urban to see the new project with two added scenarios.
Part II: Add, change and delete a LOD1 building
Load the building footprint
Load the building footprint from the shapefile. The file contains not only the geometry but also the height of the building (extrusion) saved in the attributes.
footprints = gpd.read_file(dataset_footprint)
# transform the geodataframe to a list of dictionaries
# each row (that is polygon) is shown as a separate dictionary
rings_transformed = footprints.to_dict('index')
# since we have only one polygon, it's the first dictionary of the transformed list
footprint = rings_transformed[0]
extrusion = footprint['Extrusion']
footprint_geometry = [list(footprint['geometry'].exterior.coords)]
Create a LOD1 building
Create a LOD1 building using the create
mutation.
Provide required arguments which are urban database id
, building height
, branch id
, and geometry
.
Note, that you must provide the extrusion height of the building in meters.
Again, the format of the mutations used to create, edit, and delete an object is similar to the format of mutations used in the previous steps of this sample. That is why the steps are not described in detail.
attributes = {
'height': extrusion,
'branch_id': branch_id
}
lod1_buildings = [
{
'attributes': attributes,
'geometry':{'rings':footprint_geometry, 'spatial_reference': {'wkid':wkid}}
}
]
# initialize the mutation
op4 = Operation(schema.Mutation)
create_lod1_buildings = op4.create_lod1_buildings(
urban_database_id=urban_design_database_id,
lod1_buildings=lod1_buildings
)
# select relevant return fields
create_lod1_buildings.attributes.__fields__('global_id')
# make a call to the endpoint and create an LOD1 building
json_data = endpoint(op4)
errors = json_data.get('errors')
if errors:
print(errors)
To parse the returned JSON data into more convenient native objects, add the query operation to the results variable.
Save the global
of the just created LOD1 building in a building
variable, which you will use later to edit and delete the object.
obj = op4 + json_data
building_lod1_id = obj.create_lod1_buildings[0].attributes.global_id
Open the project in ArcGIS Urban to see the created LOD1 building.
Make changes to the LOD1 building
Change the extrusion height of the LOD1 building from the default 11 m to 25 m using a update
mutation.
Required arguments are urban
and geometry
.
attributes = {
'global_id': building_lod1_id,
'height': 25
}
lod1_buildings = [
{
'attributes': attributes,
'geometry':{'rings':footprint_geometry, 'spatial_reference': {'wkid':wkid}}
}
]
# initialize the mutation
op5 = Operation(schema.Mutation)
update_lod1_buildings = op5.update_lod1_buildings(
urban_database_id=urban_design_database_id,
lod1_buildings=lod1_buildings
)
# make a call to the endpoint and change the LOD1 building
json_data = endpoint(op5)
errors = json_data.get('errors')
if errors:
print(errors)
Refresh the project in ArcGIS Urban to see the updated LOD1 building.
Delete the LOD1 building
Finally, delete the LOD1 building using the delete
mutation.
Required arguments are urban
, geometry
and the global
of the LOD1 building to be deleted.
# initialize the mutation
op6 = Operation(schema.Mutation)
delete_lod1_buildings = op6.delete_lod1_buildings(
urban_database_id=urban_design_database_id,
global_ids = [building_lod1_id]
)
# make a call to the endpoint and delete the LOD1 building
json_data = endpoint(op6)
errors = json_data.get('errors')
if errors:
print(errors)
Refresh the project in ArcGIS Urban to see the effect of the mutation.