Part 5 - Merge Parcels

Merge combines two or more parent parcels into one new child parcel. Merge sums up legal areas of parent parcels to the new child parcel legal area.

When merge is called with the record argument, the Merge function will update the new parcel's CreatedByRecord field with the GlobalID of the record. It will also update the original feature's RetiredByRecord with the same GlobalID value.

This notebook will demonstrate:

  1. Creating a branch version
  2. Creating a new parcel record
  3. Querying for parcels to merge
  4. Merging the parcels.
from arcgis import GIS
from arcgis.features.layer import FeatureLayer
from arcgis.features.layer import FeatureLayerCollection
base_server_url = (
    "https://rextapilnx02eb.esri.com/server/rest/services/WashingtonCounty/"
)
gis = GIS(
    "https://rextapilnx02eb.esri.com/portal/",
    "gisproadv2",
    "portalaccount1",
    verify_cert=False,
)

# generate the enpoint urls for feature server, version management and parcel fabric
service_endpoints = ["FeatureServer", "ParcelFabricServer", "VersionManagementServer"]
service_urls = {url: base_server_url + url for url in service_endpoints}
Setting `verify_cert` to False is a security risk, use at your own risk.

Create a new branch version

Get access to the VersionManager object through the Parcel Fabric FeatureLayerCollection

parcel_fabric_flc = FeatureLayerCollection(service_urls["FeatureServer"], gis)
version_management_server = parcel_fabric_flc.versions

new_version_name = "merge_version_1"

version = version_management_server.create(new_version_name)
fq_version_name = version["versionInfo"]["versionName"]
fq_version_name
'creator2.merge_version_1'

Create a new parcel record

This is the name of the legal record which states two parcels should be combined into one.

# Get the URL of the Records feature layer
records_fl_url = [n for n in parcel_fabric_flc.layers if n.properties.name == "Records"]
records_fl_url = records_fl_url[0].url

# A name for the new record
new_record_name = "ParcelRecord001"

# Record information with empty geometry.  The geometry is created during Build
record_dict = {"attributes": {"name": new_record_name}, "geometry": None}

# Access the Records FeatureLayer object
records_fl = FeatureLayer(records_fl_url, gis)

# Create the new record within the new branch version
new_record = records_fl.edit_features(adds=[record_dict], gdb_version=fq_version_name)

new_record
{'addResults': [{'objectId': 112952,
   'globalId': '{F6D9657E-BA81-486E-80A6-84C3AE95D009}',
   'success': True}],
 'updateResults': [],
 'deleteResults': []}
# Get the globalID of the new record
record_globalid = new_record["addResults"][0]["globalId"]
record_globalid
'{F6D9657E-BA81-486E-80A6-84C3AE95D009}'

Query parcels to merge

The Merge function takes an argument of parcel features in the following format:

[{"id":"<parcelguid>","layerId":"<layer_id>"},{...}]

This can be accomplished by identifying the layer ID of the parcels and querying the parcels by their name (PIN, APN, etc)

# parcels to be queried and merged
parcel_apns = ["0031001", "0031002"]
# build up a WHERE IN clause to search for parcels
where_clause = "NAME in ("
for p in range(len(parcel_apns)):
    where_clause += f"'{str(parcel_apns[p])}',"
where_clause = where_clause[:-1] + ")"

where_clause
"NAME in ('0031001','0031002')"
# Get the URL of the parcel polygon feature layer
parcel_fl_props = [n for n in parcel_fabric_flc.layers if n.properties.name == "Tax_PF"]
parcels_fl_url = parcel_fl_props[0].url

# Get the parcels feature layer
parcels_fl = FeatureLayer(parcels_fl_url, gis)

# query the features using the WHERE clause
# use to_dict() to get access to attributes
parcels_subset = parcels_fl.query(
    where=where_clause,
    out_fields=["GlobalID"],
    gdb_version=fq_version_name,
)

parcels_subset_dict = parcels_subset.to_dict()
print(
    [
        (a["attributes"]["objectid"], a["attributes"]["globalid"])
        for a in parcels_subset_dict["features"]
    ]
)
[(77, '{F736D9F3-DFD9-4FEE-A2E8-07352E74EBDF}'), (79, '{13CD345B-7C4F-41C7-A22D-24E4C6670D85}')]
map = gis.map("Keowns, WI")
map.draw(parcels_subset)
map.zoom_to_layer(parcels_subset)
# Generate a list of dicts of {id: <globalid>, layerid: <layer_id>}
parcels_to_merge = []

parcels_fl_layerid = parcel_fl_props[0].properties.id

for item in parcels_subset_dict["features"]:
    parcels_to_merge.append(
        {"id": item["attributes"]["globalid"], "layerId": parcels_fl_layerid}
    )

parcels_to_merge
[{'id': '{F736D9F3-DFD9-4FEE-A2E8-07352E74EBDF}', 'layerId': 15},
 {'id': '{13CD345B-7C4F-41C7-A22D-24E4C6670D85}', 'layerId': 15}]

Merge the parcels

  • Create a simple dict of feature attributes to update merged parcel (optional)
  • Start an edit session in the new version
  • Get the ParcelManager object
  • Pass the required arguments to the ParcelManager.merge function
from arcgis.features._parcel import ParcelFabricManager

# Values to update the merged parcel attributes
merged_parcel_name = "0033004"
attributes = {
    "type": "PropertySet",
    "propertySetItems": ["name", merged_parcel_name, "statedarea", 18002],
}

version_management_server.purge(fq_version_name)
# Start the edit session, get the ParcelFabricManager and merge
with version_management_server.get(fq_version_name, "read") as version:
    parcel_fabric = ParcelFabricManager(
        service_urls["ParcelFabricServer"], gis, version, parcel_fabric_flc
    )

    merge = parcel_fabric.merge(
        parent_parcels=parcels_to_merge,
        merge_record=record_globalid,
        target_parcel_type=15,
        default_area_unit=109405,
        merge_into="{00000000-0000-0000-0000-000000000000}",
        attribute_overrides=attributes,
        future=True,
    )
---------------------------------------------------------------------------Exception                                 Traceback (most recent call last)~\AppData\Local\Temp\1\ipykernel_10068\1893668656.py in <cell line: 12>()
     15     )
     16 
---> 17     merge = parcel_fabric.merge(
     18         parent_parcels=parcels_to_merge,
     19         merge_record=record_globalid,
c:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\lib\site-packages\arcgis\features\_parcel.py in merge(self, parent_parcels, target_parcel_type, attribute_overrides, child_name, default_area_unit, merge_record, merge_into, moment, future)
    490         }
    491         if future:
--> 492             res = self._con.post(path=url, postdata=params)
    493             f = self._run_async(
    494                 self._status_via_url,
c:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\lib\site-packages\arcgis\gis\_impl\_con\_connection.py in post(self, path, params, files, **kwargs)
   1555         if return_raw_response:
   1556             return resp
-> 1557         return self._handle_response(
   1558             resp=resp,
   1559             out_path=out_path,
c:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\lib\site-packages\arcgis\gis\_impl\_con\_connection.py in _handle_response(self, resp, file_name, out_path, try_json, force_bytes, ignore_error_key)
   1025                     data["error"]["code"] if "code" in data["error"] else 0
   1026                 )
-> 1027                 self._handle_json_error(data["error"], errorcode)
   1028             return data
   1029         else:
c:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\lib\site-packages\arcgis\gis\_impl\_con\_connection.py in _handle_json_error(self, error, errorcode)
   1050             errormessage + "\n(Error Code: " + str(errorcode) + ")"
   1051         )
-> 1052         raise Exception(errormessage)
   1053 
   1054     def post_multipart(
Exception: Unable to resume a service session.
(Error Code: 0)
tax_service_edits = [se for se in merge.get("serviceEdits") if se["id"] == 15][0]
merged_feature = (
    tax_service_edits.get("editedFeatures").get("adds")[0].get("attributes", {})
)
print("Name:", merged_feature.get("Name"))
print("Stated Area:", merged_feature.get("StatedArea"))
Name: 0033004
Stated Area: 18002.0
merged_parcel = parcels_fl.query(
    gdb_version=fq_version_name, where=f"Name = '{merged_parcel_name}'"
)
map1 = gis.map("Keowns, WI")
map1.draw(merged_parcel)
map1.zoom_to_layer(merged_parcel)

Recocile, Post and Delete the version

When editing is complete, the new features can be posted from the new branch version to the default version. In this workflow, Reconcile must occur first. Once posted, the version can optionally be deleted.

version = version_management_server.get(f"gisproadv2.{new_version_name}")
version.reconcile()
version.post()
version.delete()
True

API Ref Documentation

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