Python Shapefile Adjacency Code – Queen vs. Rook

When calculating adjacency, there is sometimes a distinction made between queen adjacency and rook adjacency.

Queen vs. Rook Adjacency

For a given shape X, the Queen adjacent shapes are all the shapes that touch X. For a given shape X, the Rook adjacent shapes are all the shapes that touch X at more than just one particular point.

The two types of adjacencies are named as such as a nod to the movement of the chess pieces. Rooks can only move up or down or left to right, whereas queens can move up, down, left, right or any direction diagonally. From a given square X on a chess board, the rook adjacent squares would be those that border square X and could be traveled to by a rook, and likewise for the queen adjacency squares.

Source: “Investigating Commuting Time in a Metropolitan Statistical Area Using Spatial Autocorrelation Analysis” by S. Hessam Miri

Calculating the Two

The shapefile adjacency code I shared here, which uses a buffer, will only return the queen adjacency.

The new code, shared below, which does not user a buffer, allows a user to specify whether they want point adjacencies to be return or not. This occurs by intersecting the shapefile with itself, without a buffer and then, depending on the “include_point_adjacency” parameter, filtering out the intersection that are just of Point geometry type. These are the point intersections

import pandas as pd
import geopandas as gp
from collections import defaultdict

def calculate_adjacency(gdf, unique_col):
    '''
    Code that takes a geodataframe and returns two dataframes: the first with rook adjacencies and the second with queen adjacencies.
    
    Both dataframes have two columns the first column with the unique column values, and the second column with a list of adjacent geometries, listed by their unique column value.
    ''' 
    
    # Confirm that unique_col is actually unique
    if not(max(gdf[unique_col].value_counts(dropna = False) == 1)):
        raise ValueError("Non-unique column provided")
    
    # Intersected the GeoDataFrame with the buffer with the original GeoDataFrame
    all_intersections = gp.overlay(gdf, gdf, how = "intersection", keep_geom_type = False)
   
    # Filter out self-intersections
    filtered_intersections = all_intersections[all_intersections[unique_col+"_1"]!=all_intersections[unique_col+"_2"]]
    
    # Separate out point intersections
    point_intersections = filtered_intersections[filtered_intersections.geom_type == "Point"]
    non_point_intersections = filtered_intersections[filtered_intersections.geom_type != "Point"]
    
    # Define a tuple of zips of the unique_col pairs present in the non-point intersections
    non_point_intersections_tuples = tuple(zip(non_point_intersections[unique_col+"_1"], non_point_intersections[unique_col+"_2"]))
    
    # Define a dictionary that will map from a unique_col value to a list of other unique_cols it is adjacent to
    rook_dict = defaultdict(list)
    
    # Iterate over the tuples
    for val in non_point_intersections_tuples:        
        rook_dict[val[0]].append(val[1])

    # Some shapes will only intersect with themselves and not be added to the above
    not_added = list(set(gdf[unique_col]).difference(set(rook_dict.keys())))
    for val in not_added:
        
        # For each of these, add a blank list to the dictionary
        rook_dict[val] = []
     
    # Create DataFrame of rook intersections
    df_rook = pd.DataFrame()
    df_rook['GEOID20'] = rook_dict.keys()
    df_rook["ADJ_GEOMS"] = rook_dict.values()
        
    # Make a copy of the dictionary so we can add the point intersections
    queen_dict = {key: value[:] for key, value in rook_dict.items()}
    
    # Define a tuple of zips of the unique_col pairs present in the point intersections
    point_intersection_tuples = tuple(zip(point_intersections[unique_col+"_1"], point_intersections[unique_col+"_2"]))
    for val in point_intersection_tuples:        
        queen_dict[val[0]].append(val[1])
         
    # Create DataFrame of queen intersections
    df_queen = pd.DataFrame()
    df_queen['GEOID20'] = queen_dict.keys()
    df_queen["ADJ_GEOMS"] = queen_dict.values()
    
    return df_rook, df_queen

Comparing the Code

As you can see in comparing this image with the earlier image, AZ + CO and UT + NM are point adjacent to one another

Geopandas Shapefile Adjacency

The below code takes in a geodataframe and a unique column and returns a dictionary mapping from each unique column value to a list of the column values it is adjacent too.

As written, the code uses a buffer of 1 in the 3857 crs and, as you can see below, accounts for point (Queen’s) adjacency.

The next version of the code will attempt to do the same thing without using a buffer and return an adjacency matrix rather than a dictionary.

def calculate_adjacency(gdf, unique_col):
    '''
    Code that takes a geodataframe and returns a dictionary of adjacencies
    '''
    
    # Convert to a crs to make sure the buffer area works
    gdf = gdf.to_crs(3857)
    
    # Make a copy of the GeoDataFrame
    gdf_buffer = gdf.copy(deep = True)
    
    # Add a buffer of 1 to the geometry of the copied GeoDataFrame
    gdf_buffer["geometry"] = gdf.buffer(1)
    
    # Intersected the GeoDataFrame with the buffer with the original GeoDataFrame
    test_intersection = gp.overlay(gdf_buffer, gdf, how = "intersection")
    
    # Define a tuple of zips of the unique_col pairs present in the intersection
    test_intersection_tuples = tuple(zip(test_intersection[unique_col+"_1"], test_intersection[unique_col+"_2"]))
    
    # Define a dictionary that will map from a unique_col value to a list of other unique_cols it is adjacent to
    final_dict = {}
    
    # Iterate over the tuples
    for val in test_intersection_tuples:
        
        # The shapes will intersect with themselves, we don't want to add these to the dictionary
        if val[0] != val[1]:
            
            # If the shape is already in the dictionary
            if val[0] in list(final_dict.keys()):
                
                # Append the adjacent shape to the list
                holder = final_dict[val[0]]
                holder.append(val[1])
                final_dict[val[0]] = holder
            else:
                
                # Otherwise, create a key in the dictionary mapping to a list with the adjacenct shape
                final_dict[val[0]] = [val[1]]
                
    # Some shapes will only intersect with themselves and not be added to the above
    for val in [i for i in gdf[unique_col] if i not in list(final_dict.keys())]:
        
        # For each of these, add a blank list to the dictionary
        final_dict[val] = []
        
    # Return the adjacency dictionary    
    return final_dict

Example output from running the code on a shape file of the US States from the census.

Census State FIPs Dictionary

Summary

It can be hard to find easily usable datasets that link state names or abbreviations to state FIPS codes.

On this page, I’ve copied 4 Python dictionaries with this correspondence. You can also download them in .csv format (note due to WordPress issues they are technically saved as .txt files)

  1. State Name to State FIPS (csv)
  2. State FIPS to State Name (csv)
  3. State Abbreviation to State FIPS (csv)
  4. State FIPS to State Abbreviation (csv)

The source of the data is the Natural Resources Conservation Service

State Name to State FIPS:

state_name_fips_dict = {
 'Alabama': '01',
 'Alaska': '02',
 'Arizona': '04',
 'Arkansas': '05',
 'California': '06',
 'Colorado': '08',
 'Connecticut': '09',
 'Delaware': '10',
 'Florida': '12',
 'Georgia': '13',
 'Hawaii': '15',
 'Idaho': '16',
 'Illinois': '17',
 'Indiana': '18',
 'Iowa': '19',
 'Kansas': '20',
 'Kentucky': '21',
 'Louisiana': '22',
 'Maine': '23',
 'Maryland': '24',
 'Massachusetts': '25',
 'Michigan': '26',
 'Minnesota': '27',
 'Mississippi': '28',
 'Missouri': '29',
 'Montana': '30',
 'Nebraska': '31',
 'Nevada': '32',
 'New Hampshire': '33',
 'New Jersey': '34',
 'New Mexico': '35',
 'New York': '36',
 'North Carolina': '37',
 'North Dakota': '38',
 'Ohio': '39',
 'Oklahoma': '40',
 'Oregon': '41',
 'Pennsylvania': '42',
 'Rhode Island': '44',
 'South Carolina': '45',
 'South Dakota': '46',
 'Tennessee': '47',
 'Texas': '48',
 'Utah': '49',
 'Vermont': '50',
 'Virginia': '51',
 'Washington': '53',
 'West Virginia': '54',
 'Wisconsin': '55',
 'Wyoming': '56',
 'American Samoa': '60',
 'Guam': '66',
 'Northern Mariana Islands': '69',
 'Puerto Rico': '72',
 'Virgin Islands': '78'}

State FIPS to State Name:

fips_state_name_dict = {
 '01': 'Alabama',
 '02': 'Alaska',
 '04': 'Arizona',
 '05': 'Arkansas',
 '06': 'California',
 '08': 'Colorado',
 '09': 'Connecticut',
 '10': 'Delaware',
 '12': 'Florida',
 '13': 'Georgia',
 '15': 'Hawaii',
 '16': 'Idaho',
 '17': 'Illinois',
 '18': 'Indiana',
 '19': 'Iowa',
 '20': 'Kansas',
 '21': 'Kentucky',
 '22': 'Louisiana',
 '23': 'Maine',
 '24': 'Maryland',
 '25': 'Massachusetts',
 '26': 'Michigan',
 '27': 'Minnesota',
 '28': 'Mississippi',
 '29': 'Missouri',
 '30': 'Montana',
 '31': 'Nebraska',
 '32': 'Nevada',
 '33': 'New Hampshire',
 '34': 'New Jersey',
 '35': 'New Mexico',
 '36': 'New York',
 '37': 'North Carolina',
 '38': 'North Dakota',
 '39': 'Ohio',
 '40': 'Oklahoma',
 '41': 'Oregon',
 '42': 'Pennsylvania',
 '44': 'Rhode Island',
 '45': 'South Carolina',
 '46': 'South Dakota',
 '47': 'Tennessee',
 '48': 'Texas',
 '49': 'Utah',
 '50': 'Vermont',
 '51': 'Virginia',
 '53': 'Washington',
 '54': 'West Virginia',
 '55': 'Wisconsin',
 '56': 'Wyoming',
 '60': 'American Samoa',
 '66': 'Guam',
 '69': 'Northern Mariana Islands',
 '72': 'Puerto Rico',
 '78': 'Virgin Islands'}

State Abbreviation to State FIPS:

state_abbrev_fips_dict = {
 'AL': '01',
 'AK': '02',
 'AZ': '04',
 'AR': '05',
 'CA': '06',
 'CO': '08',
 'CT': '09',
 'DE': '10',
 'FL': '12',
 'GA': '13',
 'HI': '15',
 'ID': '16',
 'IL': '17',
 'IN': '18',
 'IA': '19',
 'KS': '20',
 'KY': '21',
 'LA': '22',
 'ME': '23',
 'MD': '24',
 'MA': '25',
 'MI': '26',
 'MN': '27',
 'MS': '28',
 'MO': '29',
 'MT': '30',
 'NE': '31',
 'NV': '32',
 'NH': '33',
 'NJ': '34',
 'NM': '35',
 'NY': '36',
 'NC': '37',
 'ND': '38',
 'OH': '39',
 'OK': '40',
 'OR': '41',
 'PA': '42',
 'RI': '44',
 'SC': '45',
 'SD': '46',
 'TN': '47',
 'TX': '48',
 'UT': '49',
 'VT': '50',
 'VA': '51',
 'WA': '53',
 'WV': '54',
 'WI': '55',
 'WY': '56',
 'AS': '60',
 'GU': '66',
 'MP': '69',
 'PR': '72',
 'VI': '78'}

State FIPS to State Abbreviation:

fips_state_abbrev_dict = {
 '01': 'AL',
 '02': 'AK',
 '04': 'AZ',
 '05': 'AR',
 '06': 'CA',
 '08': 'CO',
 '09': 'CT',
 '10': 'DE',
 '12': 'FL',
 '13': 'GA',
 '15': 'HI',
 '16': 'ID',
 '17': 'IL',
 '18': 'IN',
 '19': 'IA',
 '20': 'KS',
 '21': 'KY',
 '22': 'LA',
 '23': 'ME',
 '24': 'MD',
 '25': 'MA',
 '26': 'MI',
 '27': 'MN',
 '28': 'MS',
 '29': 'MO',
 '30': 'MT',
 '31': 'NE',
 '32': 'NV',
 '33': 'NH',
 '34': 'NJ',
 '35': 'NM',
 '36': 'NY',
 '37': 'NC',
 '38': 'ND',
 '39': 'OH',
 '40': 'OK',
 '41': 'OR',
 '42': 'PA',
 '44': 'RI',
 '45': 'SC',
 '46': 'SD',
 '47': 'TN',
 '48': 'TX',
 '49': 'UT',
 '50': 'VT',
 '51': 'VA',
 '53': 'WA',
 '54': 'WV',
 '55': 'WI',
 '56': 'WY',
 '60': 'AS',
 '66': 'GU',
 '69': 'MP',
 '72': 'PR',
 '78': 'VI'}