diff --git a/html/santa_cruz_precincts.py.html b/html/santa_cruz_precincts.py.html new file mode 100644 index 0000000..cb4da04 --- /dev/null +++ b/html/santa_cruz_precincts.py.html @@ -0,0 +1,741 @@ + + +santa_cruz_precincts.py + + + + + +
+ +santa_cruz_precincts.py +
+
1    # ----------------------------------------------------------------------
+2    # santa_cruz_precincts.py
+3    # Christopher Prendergast
+4    # 2024/06/01
+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\CodeChallenge3"
+634      my_in_gdb = "CodeChallenge3.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, "CodeChallenge3.gdb")
+642      #
+643      my_temp_gdb = "memory"
+644      my_out_gdb = "CodeChallenge3.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:\PythonPro\Ex11\challenge3_v1.py
+655  # ...project directory: C:\ArcGIS_local_projects\CodeChallenge3
+656  # ...input geo-database: C:\ArcGIS_local_projects\CodeChallenge3\CodeChallenge3.gdb
+657  # ...temporary geo-database: memory
+658  # ...output geo_database: C:\ArcGIS_local_projects\CodeChallenge3\CodeChallenge3.gdb
+659  # ...load_shapefile, shape_file C:\ArcGIS_local_projects\CodeChallenge3\Precincts\Precincts.shp
+660  # ...load_shapefile, out_gdb: memory
+661  # C:\ArcGIS_local_projects\CodeChallenge3\Precincts\Precincts.shp Successfully converted:  memory\Precincts
+662  # Start Time: Wednesday, May 15, 2024 4:15:29 PM
+663  # C:\ArcGIS_local_projects\CodeChallenge3\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\CodeChallenge3\Precincts\Precinct_Cross_Reference.dbf
+668  # ...load_table, out_gdb: memory
+669  # Converted C:\ArcGIS_local_projects\CodeChallenge3\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\CodeChallenge3\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\CodeChallenge3\CodeChallenge3.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\CodeChallenge3\CodeChallenge3.gdb\voting_precincts
+713  # 297 voting precincts created.
+714  # C:\ArcGIS_local_projects\CodeChallenge3\CodeChallenge3.gdb\voting_precincts
+715  #
+716  # Process finished with exit code 0
+717  # ----------------------------------------------------------------------
+718  
+ + \ No newline at end of file