Create a new project and edit an object

Download the sample

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. Both files are provided in a shapefile format. They contain the selection of parcels that define the study area of your project, and the building footprints.

To test the code from this sample, set up your own Hardeeville example urban model. 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 Get Started 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.

      
1
2
3
4
5
6
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_url 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 Urban API Overview section. Set paths of two additional datasets.

       
1
2
3
4
5
6
7
token = "ACCESS_TOKEN"
endpoint_url = 'https://urban-api.arcgis.com/graphql?token='+token
endpoint = HTTPEndpoint(endpoint_url)

# set paths to datasets
dataset_parcels = 'LOCAL_PATH/parcels_hardeville_selection.shp'
dataset_footprint = 'LOCAL_PATH/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_urban_design_database mutation and provide the required arguments: urban_model_id, title, and type. Note, that you need to replace the urban_model_id variable in the code below with the urban model id value of the urban model you created. Furthermore, add the value to the folder_id 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.

              
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)",
    type="Project"
)

# 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_urban_design_database mutation returns a single field only, the PortalItemId. That is why you do not need to provide return fields for your mutation. Assign the returned PortalItemId to the urban_design_database_id variable, which you will use in the next steps.

  
1
2
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 in the city of Hardeeville. 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.

            
1
2
3
4
5
6
7
8
9
10
11
12
# 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_projects 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_name, start_date, end_date, and owner_name, as well as a project geometry. Note, that you need to replace the owner_name variable with the ArcGIS account username of the user which you are using to try out this sample code.

                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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_id as a return field. Note, that the urban_database_id 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.

                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 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_id of the just created project to a project_global_id variable, which you will use later to invoke the new project.

  
1
2
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.

                                        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 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_id of the just created future branch to a branch_id variable, which you will use when adding a LOD1 building.

     
1
2
3
4
5
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.

           
1
2
3
4
5
6
7
8
9
10
11
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_lod1_buildings 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. For additional information on how the API handles metrics, check the API overview.

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.

                            
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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_id of the just created LOD1 building in a building_lod1_id variable, which you will use later to edit and delete the object.

  
1
2
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_lod1_buildings mutation. Required arguments are urban_database_id and geometry.

                         
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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_lod1_buildings mutation. Required arguments are urban_database_id, geometry and the global_id of the LOD1 building to be deleted.

             
1
2
3
4
5
6
7
8
9
10
11
12
13
# 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.

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