1 # ---------------------------------------------------------------------- 2 # santa_cruz_precincts.py 3 # Christopher Prendergast 4 # 2024/05/15 5 # ---------------------------------------------------------------------- 6 # 7 import arcpy 8 import sys 9 from typing import List 10 from pathlib import Path 11 12 global g_proj_dir 13 global g_in_gdb 14 global g_temp_gdb 15 global g_out_gdb 16 17 18 def full_path(root_dir: str, basename: str) -> str: 19 """ 20 Convert basename to a full path given the root directory or 21 geo-database path. 22 23 :param str root_dir: The root directory or file geo-database 24 path. 25 :param str basename: The basename of a geo-database or feature 26 class. 27 :return str: The root and basename joined as a path. 28 """ 29 return str(Path(root_dir, basename)) 30 31 32 def setup_env(proj_dir_str: str, in_gdb_str: str, temp_gdb_str: str, 33 out_gdb_str: str) -> None: 34 """ 35 Set up the geo-database environment. Assign values to global 36 variables g_in_gdb and g_out_gdb. 37 38 :param str proj_dir_str: The project directory. 39 :param str in_gdb_str: The basename of the input geo-database 40 within the project directory. 41 :param str temp_gdb_str: The full path of the temporary 42 geo-database. 43 :param str out_gdb_str: The basename of the output geo-database 44 within the project directory. 45 :return NoneType: None 46 """ 47 # 48 # Allow overwriting outputs. 49 # Note: overwriting doesn't work if the layer is open in ArcGIS Pro 50 # due to lock. Closing ArgGIS Pro releases the lock and the outputs 51 # can be overwritten. See: 52 # https://community.esri.com/t5/python-questions/arcpy-env-overwriteoutput-true-fails/m-p/411113#M32410 53 # 54 arcpy.env.overwriteOutput = True 55 # 56 # Check the project directory exists. 57 # 58 global g_proj_dir 59 g_proj_dir = str(Path(proj_dir_str)) 60 assert Path(g_proj_dir).is_dir(), \ 61 f"Can't find the project directory {g_proj_dir}" 62 print("...project directory:", g_proj_dir) 63 # 64 # Assign global variables for the input and output geo-databases. 65 # 66 global g_in_gdb 67 g_in_gdb = full_path(proj_dir_str, in_gdb_str) 68 global g_temp_gdb 69 g_temp_gdb = temp_gdb_str 70 global g_out_gdb 71 g_out_gdb = full_path(proj_dir_str, out_gdb_str) 72 # 73 # Check the input and output geo-databases exist. 74 # 75 assert arcpy.Exists(g_in_gdb), \ 76 f"Can't find input geo-database: {g_in_gdb}" 77 assert arcpy.Exists(g_temp_gdb), \ 78 f"Can't find temporary geo-database: {g_temp_gdb}" 79 assert arcpy.Exists(g_out_gdb), \ 80 f"Can't find output geo-database: {g_out_gdb}" 81 print("...input geo-database:", g_in_gdb) 82 print("...temporary geo-database:", g_temp_gdb) 83 print("...output geo_database:", g_out_gdb) 84 85 86 def load_shapefile(shape_file: str, out_gdb: str) -> str: 87 """ 88 Load a shapefile into feature class with the same name in the 89 specified geodatabase. 90 91 :param str shape_file: The file path to the shapefile. 92 :param str out_gdb: The path to the geo-database. 93 :return str: The geo-database path to the feature class 94 created. 95 """ 96 97 print("...load_shapefile, shape_file", shape_file) 98 print("...load_shapefile, out_gdb:", out_gdb) 99 100 assert arcpy.Exists(shape_file), \ 101 f"Can't find input shape_file: {shape_file}" 102 assert arcpy.Exists(out_gdb), \ 103 f"Can't find input out_gdb: {out_gdb}" 104 105 try: 106 result: arcpy.Result = ( 107 arcpy.conversion.FeatureClassToGeodatabase( 108 Input_Features=[shape_file], 109 Output_Geodatabase=out_gdb 110 )) 111 except arcpy.ExecuteError: 112 # 113 # Handle geo-processing specific errors. 114 # 115 print("...load_shapefile, arcpy error executing " 116 "geo-processing tool.") 117 print(arcpy.GetMessages(2)) 118 sys.exit(101) 119 except: 120 # 121 # Handle any other type of error. 122 # 123 e = sys.exc_info()[1] 124 print(e.args[0]) 125 sys.exit(201) 126 127 print(result.getMessages()) 128 # 129 # Unpack first element of result object as return value. 130 # 131 ret_val = result.getOutput(0) 132 # 133 # This tool ony returns the path to the gdb not the feature class. 134 # So, construct path to feature class here. 135 # 136 ret_val = full_path(ret_val, str(Path(shape_file).stem)) 137 138 print("...load_shapefile,", arcpy.management.GetCount(ret_val), 139 "precincts loaded from shapefile.") 140 print("...load_shapefile, ret_val:", ret_val) 141 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 142 return ret_val 143 144 145 def load_table(dbf_file: str, out_gdb: str): 146 """ 147 Load a dbf file into a table with the same name in the specified 148 geo-database. 149 150 :param str dbf_file: The file path to the dbf file. 151 :param str out_gdb: The path to the geo-database. 152 :return str: The geo-database path to the table created. 153 """ 154 155 print("...load_table, dbf_file", dbf_file) 156 print("...load_table, out_gdb:", out_gdb) 157 158 assert arcpy.Exists(dbf_file), \ 159 f"Can't find input dbf_file: {dbf_file}" 160 assert arcpy.Exists(out_gdb), f"Can't find input out_gdb: {out_gdb}" 161 162 try: 163 result: arcpy.Result = arcpy.conversion.TableToGeodatabase( 164 Input_Table=[dbf_file], 165 Output_Geodatabase=out_gdb) 166 except arcpy.ExecuteError: 167 # 168 # Handle geo-processing specific errors. 169 # 170 print("...load_table, arcpy error executing " 171 "geo-processing tool.") 172 print(arcpy.GetMessages(2)) 173 sys.exit(102) 174 except: 175 # 176 # Handle any other type of error. 177 # 178 e = sys.exc_info()[1] 179 print(e.args[0]) 180 sys.exit(202) 181 182 print(result.getMessages()) 183 # 184 # Unpack first element of result object as return value. 185 # 186 ret_val = result.getOutput(0) 187 # 188 # This tool ony returns the path to the gdb not the feature class. 189 # So, construct path to feature class here. 190 # 191 ret_val = full_path(ret_val, str(Path(dbf_file).stem)) 192 print("...load_table,", arcpy.management.GetCount(ret_val), 193 "cross_reference records loaded from dbf file.") 194 print("...load_table, ret_val:", ret_val) 195 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 196 return ret_val 197 198 199 def create_table(out_path: str, out_name: str) -> str: 200 """ 201 Create a new empty table in the specified geo-database. 202 203 :param str out_path: The path to the geo-database 204 :param str out_name: The name of the table to create. 205 :return str: The geo-database path to the table created. 206 """ 207 208 print("...create_table, out_path", out_path) 209 print("...create_table, out_name:", out_name) 210 211 assert arcpy.Exists(out_path), \ 212 f"Can't find input out_path: {out_path}" 213 214 try: 215 result: arcpy.Result = arcpy.management.CreateTable( 216 out_path=out_path, 217 out_name=out_name 218 ) 219 except arcpy.ExecuteError: 220 # 221 # Handle geo-processing specific errors. 222 # 223 print("...create_table, arcpy error executing " 224 "geo-processing tool.") 225 print(arcpy.GetMessages(2)) 226 sys.exit(103) 227 except: 228 # 229 # Handle any other type of error. 230 # 231 e = sys.exc_info()[1] 232 print(e.args[0]) 233 sys.exit(203) 234 235 print(result.getMessages()) 236 # 237 # Unpack first element of result object as return value. 238 # 239 ret_val = result.getOutput(0) 240 print("...create_table, ret_val:", ret_val) 241 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 242 return ret_val 243 244 245 def add_fields(in_table: str, 246 field_description: List[List[str]]) -> str: 247 """ 248 Add fields to a table in the geo-database. 249 250 :param str in_table: The geo-database path to the table. 251 :param str field_description: Specification of fields to add. 252 :return str: The geo-database path to the table. 253 """ 254 255 print("...add_fields, in_table", in_table) 256 print("...add_fields, field_description:", field_description) 257 258 assert arcpy.Exists(in_table), \ 259 f"Can't find input in_table: {in_table}" 260 261 try: 262 result: arcpy.Result = arcpy.management.AddFields( 263 in_table=in_table, 264 field_description=field_description) 265 except arcpy.ExecuteError: 266 # 267 # Handle geo-processing specific errors. 268 # 269 print("...add_fields, arcpy error executing " 270 "geo-processing tool.") 271 print(arcpy.GetMessages(2)) 272 sys.exit(104) 273 except: 274 # 275 # Handle any other type of error. 276 # 277 e = sys.exc_info()[1] 278 print(e.args[0]) 279 sys.exit(204) 280 281 print(result.getMessages()) 282 # 283 # Unpack first element of result object as return value. 284 # 285 ret_val = result.getOutput(0) 286 print("...add_fields, ret_val:", ret_val) 287 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 288 return ret_val 289 290 291 def join_by_field(in_data: str, in_field: str, join_table: str, 292 join_field: str, fields: List[str], 293 index_join_fields="NEW_INDEXES") -> str: 294 """ 295 Join two feature classes/tables based on a common key and transfer 296 the specified fields to one of the tables. 297 298 :param str in_data: The geo-database path to the 1st table. 299 :param str in_field: The join field in the 1st table. 300 :param str join_table: The geo-database path to the 2nd table. 301 :param str str join_field: The join field in the 2nd table. 302 :param list fields: The list of fields to transfer. 303 :param str index_join_fields: Whether to create new indexes. 304 :return str: The geo-database path to the resulting 305 table. 306 """ 307 308 print("...join_field, in_data", in_data) 309 print("...join_field, in_field:", in_field) 310 print("...join_field, join_table:", join_table) 311 print("...join_field, fields:", fields) 312 print("...join_field, index_join_fields:", index_join_fields) 313 314 assert arcpy.Exists(in_data), f"Can't find input in_data: {in_data}" 315 assert arcpy.Exists(join_table), \ 316 f"Can't find input in_table: {join_table}" 317 318 try: 319 result: arcpy.Result = arcpy.management.JoinField( 320 in_data=in_data, 321 in_field=in_field, 322 join_table=join_table, 323 join_field=join_field, 324 fields=fields, 325 index_join_fields=index_join_fields 326 ) 327 except arcpy.ExecuteError: 328 # 329 # Handle geo-processing specific errors. 330 # 331 print("...join_by_field, arcpy error executing " 332 "geo-processing tool.") 333 print(arcpy.GetMessages(2)) 334 sys.exit(105) 335 except: 336 # 337 # Handle any other type of error. 338 # 339 e = sys.exc_info()[1] 340 print(e.args[0]) 341 sys.exit(205) 342 343 print(result.getMessages()) 344 # 345 # Unpack first element of result object as return value. 346 # 347 ret_val = result.getOutput(0) 348 print("...join_field,", arcpy.management.GetCount(ret_val), 349 "records after join.") 350 print("...join_field, ret_val:", ret_val) 351 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 352 return ret_val 353 354 355 def dissolve(in_features: str, out_feature_class: str, 356 dissolve_field: str) -> str: 357 """ 358 Dissolve the polygons in a feature class based on a shared value in 359 :a field. 360 361 :param str in_features: The path to the input feature class. 362 :param str out_feature_class: The path to the output feature class. 363 :param str dissolve_field: The field to dissolve on. 364 :return str: The path to the output feature class. 365 """ 366 367 print("...dissolve, in_features", in_features) 368 print("...dissolve, out_feature_class:", out_feature_class) 369 print("...dissolve, dissolve_field:", dissolve_field) 370 371 assert arcpy.Exists(in_features), \ 372 f"Can't find input in_features: {in_features}" 373 374 try: 375 result: arcpy.Result = arcpy.analysis.PairwiseDissolve( 376 in_features=in_features, 377 out_feature_class=out_feature_class, 378 dissolve_field=[dissolve_field] 379 ) 380 except arcpy.ExecuteError: 381 # 382 # Handle geo-processing specific errors. 383 # 384 print("...dissolve, arcpy error executing " 385 "geo-processing tool.") 386 print(arcpy.GetMessages(2)) 387 sys.exit(106) 388 except: 389 # 390 # Handle any other type of error. 391 # 392 e = sys.exc_info()[1] 393 print(e.args[0]) 394 sys.exit(206) 395 396 print(result.getMessages()) 397 # 398 # Unpack first element of result object as return value. 399 # 400 ret_val = result.getOutput(0) 401 print("...dissolve,", arcpy.management.GetCount(ret_val), 402 "records after dissolve.") 403 print("...dissolve, ret_val:", ret_val) 404 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 405 return ret_val 406 407 408 def explode_xref(xref_table: str, xref_explode_table: str) -> str: 409 """ 410 Explode any regular precincts for each voting precinct in 411 the cross-reference table into multiple separate rows in a new 412 table. Note that the key and value in the exploded results table is 413 swapped from the input cross-reference table. The key in the output 414 table is the regular precinct and there can be only one voting 415 precinct for each regular precinct in the output. Each voting 416 precinct can appear in multiple rows in the output. 417 418 :param str xref_table: The path to the cross-reference table. 419 :param str xref_explode_table: The path to the exploded table. 420 :return str: The path to the exploded table. 421 """ 422 423 print("...explode_xref, xref_table", xref_table) 424 print("...explode_xref, xref_explode_table:", xref_explode_table) 425 426 assert arcpy.Exists(xref_table), \ 427 f"Can't find input xref_table: {xref_table}" 428 assert arcpy.Exists(xref_explode_table), \ 429 f"Can't find input xref_explode_table: {xref_explode_table}" 430 431 # 432 # Iterate over the cross-reference table and unpack multiple regular 433 # precincts dictionary entries. The dictionary key is the regular 434 # precinct and the value is the associated voting precinct. 435 # 436 precincts_1to1 = {} 437 try: 438 with (arcpy.da.SearchCursor( 439 xref_table, ["VotePrec", "_Precincts"]) as search_cur): 440 for voting_precinct, regular_precincts_str in search_cur: 441 # 442 # Unpack the regular precincts into individual items and 443 # add voting precinct to a dictionary with regular 444 # precinct as the key. Note: using a set here just in 445 # case there are any duplicate regular precincts in the 446 # same cross-reference row. 447 # 448 regular_precincts_set = \ 449 set(regular_precincts_str.split()) 450 for regular_precinct in regular_precincts_set: 451 precincts_1to1[regular_precinct] = \ 452 voting_precinct 453 except arcpy.ExecuteError: 454 # 455 # Handle geo-processing specific errors. 456 # 457 print("...explode_xref, arcpy error executing " 458 "geo-processing tool.") 459 print("...explode_xref, error in SearchCursor") 460 print(arcpy.GetMessages(2)) 461 sys.exit(107) 462 except: 463 # 464 # Handle any other type of error. 465 # 466 e = sys.exc_info()[1] 467 print(e.args[0]) 468 sys.exit(207) 469 # 470 # Insert the contents of the dictionary into a table so that we can 471 # use geo-processing tools to join to this table. 472 # 473 try: 474 with ( 475 arcpy.da.InsertCursor( 476 xref_explode_table, 477 ["Precinct", "VotePrec"]) as insert_cur): 478 for regular_precinct, voting_precinct \ 479 in precincts_1to1.items(): 480 insert_cur.insertRow( 481 [regular_precinct, 482 precincts_1to1[voting_precinct] 483 ] 484 ) 485 except arcpy.ExecuteError: 486 # 487 # Handle geo-processing specific errors. 488 # 489 print("...explode_xref, arcpy error executing " 490 "geo-processing tool.") 491 print("...explode_xref, error in InsertCursor") 492 print(arcpy.GetMessages(2)) 493 sys.exit(108) 494 except: 495 # 496 # Handle any other type of error. 497 # 498 e = sys.exc_info()[1] 499 print(e.args[0]) 500 sys.exit(208) 501 ret_val = xref_explode_table 502 print("...explode_xref, ret_val:", ret_val) 503 print("...explode_xref,", arcpy.management.GetCount(ret_val), 504 "regular precincts have a voting precinct assigned.") 505 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 506 return ret_val 507 508 509 def update_precincts(precincts_fc: str) -> str: 510 """ 511 Update the precincts feature class so that regular precincts that 512 don't appear in the cross-reference table have their voting precinct 513 set to the same value as their regular precinct. This is necessary 514 to avoid all these precincts being dissolved into a single polygon 515 because the share the same null value for voting precinct. 516 517 :param str precincts_fc: The path to the precincts feature class. 518 :return str: The path to the precincts feature class. 519 """ 520 521 print("...update_precincts, precincts_fc", precincts_fc) 522 523 assert arcpy.Exists(precincts_fc), \ 524 f"Can't find input precincts_fc: {precincts_fc}" 525 526 try: 527 with arcpy.da.UpdateCursor( 528 precincts_fc, ["Precinct", "VotePrec"], 529 where_clause='"VotePrec" is null') as update_cur: 530 update_count = 0 531 for row in update_cur: 532 precinct, voting_precinct = row 533 if voting_precinct is None: 534 row[1] = precinct 535 update_cur.updateRow(row) 536 update_count += 1 537 print("...update_precincts,", update_count, "rows updated.") 538 except arcpy.ExecuteError: 539 # 540 # Handle geo-processing specific errors. 541 # 542 print("...update_precincts, arcpy error executing " 543 "geo-processing tool.") 544 print("...update_precincts, error in UpdateCursor") 545 print(arcpy.GetMessages(2)) 546 sys.exit(109) 547 except: 548 # 549 # Handle any other type of error. 550 # 551 e = sys.exc_info()[1] 552 print(e.args[0]) 553 sys.exit(209) 554 555 ret_val = precincts_fc 556 print("...update_precincts, ret_val:", ret_val) 557 print( 558 "...update_precincts,", 559 arcpy.management.GetCount(ret_val), 560 "regular precincts with a voting precinct assigned after update." 561 ) 562 assert arcpy.Exists(ret_val), f"Can't find feature class: {ret_val}" 563 return ret_val 564 565 566 def run_tools() -> None: 567 """ 568 Run the geo-processing tools to buffer and join the various feature 569 classes. 570 571 :return NoneType: None 572 """ 573 # 574 # Load precincts from shapefile. 575 # 576 precincts_shp_file = str( 577 Path( 578 g_proj_dir, 579 "Precincts", 580 "Precincts.shp" 581 ) 582 ) 583 precincts = load_shapefile(precincts_shp_file, g_temp_gdb) 584 # 585 # Load cross-reference table from dbf file. 586 # 587 xref_dbf_file = str( 588 Path( 589 g_proj_dir, 590 "Precincts", 591 "Precinct_Cross_Reference.dbf" 592 ) 593 ) 594 xref = load_table(xref_dbf_file, g_temp_gdb) 595 # 596 # Create a table to hold exploded cross-references. 597 # 598 xref_explode = create_table(g_temp_gdb, "xref_explode") 599 fields_to_add = [ 600 ["Precinct", "TEXT", "", "255", "", ""], 601 ["VotePrec", "TEXT", "", "255", "", ""] 602 ] 603 xref_explode = add_fields(xref_explode, fields_to_add) 604 # 605 # Explode the cross-references. 606 # 607 xref_explode = explode_xref(xref, xref_explode) 608 # 609 # Join the exploded cross-references to the precincts. 610 # 611 precincts = join_by_field( 612 precincts, "Precinct", xref_explode, "Precinct", 613 ["VotePrec"]) 614 # 615 # Update precincts with no voting precinct. 616 # 617 update_precincts(precincts) 618 # 619 # Dissolve the precincts to get the voting precincts. 620 # 621 voting_precincts = full_path(g_out_gdb, "voting_precincts") 622 voting_precincts = dissolve( 623 precincts, voting_precincts, "VotePrec") 624 print(arcpy.management.GetCount(voting_precincts), 625 "voting precincts created.") 626 print(voting_precincts) 627 628 629 if __name__ == '__main__': 630 # 631 # Define locations of geodatabases. 632 # 633 my_proj_dir = r"C:\ArcGIS_local_projects\SantaCruzPrecincts" 634 my_in_gdb = "SantaCruzPrecincts.gdb" 635 # 636 # Intermediate results are written to the temporary geo-database. 637 # To speed processing and avoid writing intermediate results to 638 # disk this is set to "memory". 639 # If you need to keep these results, change this to the full path of 640 # a file geodatabase as follows: 641 # my_temp_gdb = full_path(my_proj_dir, "SantaCruzPrecincts.gdb") 642 # 643 my_temp_gdb = "memory" 644 my_out_gdb = "SantaCruzPrecincts.gdb" 645 setup_env(my_proj_dir, my_in_gdb, my_temp_gdb, my_out_gdb) 646 # 647 # Run the main process. 648 # 649 run_tools() 650 651 # ---------------------------------------------------------------------- 652 # Sample Output 653 # ---------------------------------------------------------------------- 654 # "C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\python.exe" C:\ArcGIS_local_projects\SantaCruzPrecincts\santa_cruz_precincts.py 655 # ...project directory: C:\ArcGIS_local_projects\SantaCruzPrecincts 656 # ...input geo-database: C:\ArcGIS_local_projects\SantaCruzPrecincts\SantaCruzPrecincts.gdb 657 # ...temporary geo-database: memory 658 # ...output geo_database: C:\ArcGIS_local_projects\SantaCruzPrecincts\SantaCruzPrecincts.gdb 659 # ...load_shapefile, shape_file C:\ArcGIS_local_projects\SantaCruzPrecincts\Precincts\Precincts.shp 660 # ...load_shapefile, out_gdb: memory 661 # C:\ArcGIS_local_projects\SantaCruzPrecincts\Precincts\Precincts.shp Successfully converted: memory\Precincts 662 # Start Time: Wednesday, May 15, 2024 4:15:29 PM 663 # C:\ArcGIS_local_projects\SantaCruzPrecincts\Precincts\Precincts.shp Successfully converted: memory\Precincts 664 # Succeeded at Wednesday, May 15, 2024 4:15:31 PM (Elapsed Time: 1.22 seconds) 665 # ...load_shapefile, 720 precincts loaded from shapefile. 666 # ...load_shapefile, ret_val: memory\Precincts 667 # ...load_table, dbf_file C:\ArcGIS_local_projects\SantaCruzPrecincts\Precincts\Precinct_Cross_Reference.dbf 668 # ...load_table, out_gdb: memory 669 # Converted C:\ArcGIS_local_projects\SantaCruzPrecincts\Precincts\Precinct_Cross_Reference.dbf to memory\Precinct_Cross_Reference successfully. 670 # Start Time: Wednesday, May 15, 2024 4:15:31 PM 671 # Converted C:\ArcGIS_local_projects\SantaCruzPrecincts\Precincts\Precinct_Cross_Reference.dbf to memory\Precinct_Cross_Reference successfully. 672 # Succeeded at Wednesday, May 15, 2024 4:15:31 PM (Elapsed Time: 0.62 seconds) 673 # ...load_table, 138 cross_reference records loaded from dbf file. 674 # ...load_table, ret_val: memory\Precinct_Cross_Reference 675 # ...create_table, out_path memory 676 # ...create_table, out_name: xref_explode 677 # Start Time: Wednesday, May 15, 2024 4:15:31 PM 678 # Succeeded at Wednesday, May 15, 2024 4:15:31 PM (Elapsed Time: 0.01 seconds) 679 # ...create_table, ret_val: memory\xref_explode 680 # ...add_fields, in_table memory\xref_explode 681 # ...add_fields, field_description: [['Precinct', 'TEXT', '', '255', '', ''], ['VotePrec', 'TEXT', '', '255', '', '']] 682 # Start Time: Wednesday, May 15, 2024 4:15:31 PM 683 # Adding Precinct to xref_explode... 684 # Adding VotePrec to xref_explode... 685 # Succeeded at Wednesday, May 15, 2024 4:15:31 PM (Elapsed Time: 0.01 seconds) 686 # ...add_fields, ret_val: memory\xref_explode 687 # ...explode_xref, xref_table memory\Precinct_Cross_Reference 688 # ...explode_xref, xref_explode_table: memory\xref_explode 689 # ...explode_xref, ret_val: memory\xref_explode 690 # ...explode_xref, 561 regular precincts have a voting precinct assigned. 691 # ...join_field, in_data memory\Precincts 692 # ...join_field, in_field: Precinct 693 # ...join_field, join_table: memory\xref_explode 694 # ...join_field, fields: ['VotePrec'] 695 # ...join_field, index_join_fields: NEW_INDEXES 696 # Start Time: Wednesday, May 15, 2024 4:15:31 PM 697 # Succeeded at Wednesday, May 15, 2024 4:15:32 PM (Elapsed Time: 0.10 seconds) 698 # ...join_field, 720 records after join. 699 # ...join_field, ret_val: memory\Precincts 700 # ...update_precincts, precincts_fc memory\Precincts 701 # ...update_precincts, 159 rows updated. 702 # ...update_precincts, ret_val: memory\Precincts 703 # ...update_precincts, 720 regular precincts with a voting precinct assigned after update. 704 # ...dissolve, in_features memory\Precincts 705 # ...dissolve, out_feature_class: C:\ArcGIS_local_projects\SantaCruzPrecincts\SantaCruzPrecincts.gdb\voting_precincts 706 # ...dissolve, dissolve_field: VotePrec 707 # Start Time: Wednesday, May 15, 2024 4:15:32 PM 708 # Sorting Attributes... 709 # Dissolving... 710 # Succeeded at Wednesday, May 15, 2024 4:15:33 PM (Elapsed Time: 0.91 seconds) 711 # ...dissolve, 297 records after dissolve. 712 # ...dissolve, ret_val: C:\ArcGIS_local_projects\SantaCruzPrecincts\SantaCruzPrecincts.gdb\voting_precincts 713 # 297 voting precincts created. 714 # C:\ArcGIS_local_projects\SantaCruzPrecincts\SantaCruzPrecincts.gdb\voting_precincts 715 # 716 # Process finished with exit code 0 717 # ---------------------------------------------------------------------- 718