7

I have a shapefile with some polygons and I'd like to create a graph that assigns vertices to polygons and creates edges between vertices if the corresponding polygons share a border. Since I'm using networkx, a Python graph library, I was thinking of using GeoPandas to convert from the shapefile to a nx graph. Does anyone have any idea how to do this?

(networkx has a built-in read_shp method, but I don't think that's what I want; I'm pretty sure (but not confident) that that translates points to vertices and lines to edges, not polygons and borders.)

3 Answers 3

4

You could use pysal to create a contiguity matrix. This can be done from a data frame using pysal.weights.Rook.from_dataframe(args), or direct from a shapefile. You could then use this to add nodes (these are the keys in both dictionaries dictionary) and edges (a list of tuples of each key paired with each neighbour from it's value list. Something like:

import networkx as nx
import pysal

#build contiguity matrix - uses rook contiguity - queen is available
path_to_shp = r'come/path/to/file.shp'
my_keys = 'name_of_id_for_your_features'
contig_matrix = pysal.weights.Rook.from_shapefile(path_to_shp,idVariable = my_keys)

#build list of edges - this will create edges going both ways from connected nodes, so you might need to remove duplicates
nodes = contig_matrix.weights.keys() # to get dict of keys, alternatively use contig_matrix.id2i.keys()
edges = [(node,neighbour) for node in nodes for neighbour in contig_matrix[node]]
my_graph = nx.Graph(edges)
2
  • This is cool -- I hadn't heard of pysal! I ended up coming up with a (pretty hacky) solution, but nonetheless one that works, by creating a bunch of shapely objects and using the builtin obj.touches(other) method.
    – rohan
    Commented Jun 21, 2017 at 17:04
  • Great, always good to discover new things. Why not add your solution as an answer for others. As an aside I think pysal implements graphs too.
    – RoperMaps
    Commented Jun 21, 2017 at 20:14
2

I figured it out! The gist of the solution is this: use fiona to read in the shapefile, shapely to convert them into shapes that can be analyzed, and the shape.touches(other) method shapely provides to build the graph, as follows:

import fiona
from shapely.geometry import shape
import networkx as nx

# Converts a shapefile located at indir/infile to a networkx graph.
# If draw_graph is set to True, the graph is drawn using matplotlib.
def create_graph(indir, infile, draw_shapefile=False, draw_graph=False):
  G = nx.Graph()
  i = 1

  with cd(indir):
    with fiona.open(infile) as blocks:
      for shp in blocks:
        # the geometry property here may be specific to my shapefile
        block = shape(shp['geometry'])

        G.add_node(i)
        i += 1

  for n in G.nodes(data=True):
    state = n[1]['block']
    for o in G.nodes(data=True):
      other = o[1]['block']
      if state is not other and state.touches(other):
        G.add_edge(n[0], o[0])

  return G
1
  • Hello--I'm working on a similar problem, and I'm wondering what the key 'block' in your code refers to (when you iterate over the nodes with a for loop). Is it a field in your shapefile? Commented Jul 28, 2023 at 22:27
0

Spatial join the dataframe to itself

import geopandas as gpd
import networkx as nx

df = gpd.read_file(r"/home/bera/Desktop/gistest/poly_to_graph.shp")
# df.head()
#   id                                           geometry
# 0  A  POLYGON ((648258.309 7280682.864, 649701.685 7...
# 1  B  POLYGON ((648258.309 7275682.864, 649701.685 7...
# 2  C  POLYGON ((648258.309 7270682.864, 649701.685 7...
# 3  E  POLYGON ((648258.309 7260682.864, 649701.685 7...
# 4  G  POLYGON ((652588.436 7278182.864, 654031.812 7...

sj = df.sjoin(df, how="left", predicate="intersects") #Join the df to itself
#sj[["id_left","id_right"]].head()
# 0       A        B
# 0       A        G
# 0       A        A
# 1       B        C
# 1       B        B

#Create a set of edges
edges = {tuple(sorted([x,y])) for x,y in zip(sj.id_left, sj.id_right) if x!=y}
# {('A', 'B'),
#  ('A', 'G'),
#  ('B', 'C'),
#  ('B', 'G'),
#  ...

G = nx.Graph()
G.add_nodes_from(sj.id_left) #To add any nodes without an edge (Y node)
G.add_edges_from(edges)
nx.draw(G, with_labels=True)

enter image description here enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.