Geospatial Analysis with Python: Topological Calculations (Part 1)

Using Shapely and Geopandas for PIP queries and intersection checks.

Determining whether a point is inside a region, or whether a line intersects another line or a polygon, are fundamental topological operations. These operations have a wide range of applications, such as filtering data based on location. In spatial analysis, such spatial queries are often one of the typical initial steps in the workflow. Performing spatial joins between two spatial datasets is one of the most typical applications of checking whether a point is inside a polygon.

01

PIP (Point in Polygon) Query

From a computational perspective, determining whether a point is inside a polygon is a complex task. However, we can use built-in functions to perform the calculations and evaluate the topological relationships between geographic objects, such as the PIP (Point in Polygon) relationship.

There are two main methods to perform PIP queries in Shapely:

  • Using the within() function: This function checks whether a point is inside a polygon;

  • Using the contains() function: This function checks whether a polygon contains a point;

Additionally, the logic of PIP operations also applies to checking whether a LineString or a polygon is inside another polygon.

As an example, we first create a polygon using a set of coordinate tuples, and then create several point objects.

from shapely.geometry import Point, Polygon
p1 = Point(24.952242, 60.1696017)
p2 = Point(24.976567, 60.1612500)
coords = [(24.950899, 60.169158), (24.953492, 60.169158), (24.953510, 60.170104), (24.950958, 60.169990)]
poly = Polygon(coords)

We check whether the points are inside the polygon.

p1.within(poly)True
p2.within(poly)False

From this, we can see that the first point is inside the polygon, while the other point is not.

In fact, the first point is very close to the center of the polygon.

print(p1)POINT (24.952242 60.1696017)
print(poly.centroid)POINT (24.95224242849236 60.16960179038188)

We can also perform a PIP query in another way, which is to check whether a polygon contains a point.

poly.contains(p1)True
poly.contains(p2)False

The results of these two methods for checking spatial relationships are ultimately the same.

If we have multiple points and a polygon, and we want to determine which of these points are inside the polygon, we need to iterate through all the points and use the within() function to check each point against the specified polygon. If we have multiple polygons and a point, and we want to determine which polygon contains the point, we need to iterate through all the polygons and use the contains() function until we find a polygon that contains the specified point (assuming there are no overlapping polygons).

02

Intersection

Another typical topological operation is to determine whether one geometric object intersects (intersect) or touches (touch) another geometric object.

The difference between these two relationships is:

  • If two objects intersect (intersect), then the boundary and interior of one object must intersect with the boundary and interior of the other object in any way.

  • If one object touches (touch) another object, then they only need to have (at least) one common point on their boundaries, but their interiors must not intersect.

As an example, we first create two LineStrings.

from shapely.geometry import LineString, MultiLineString
line_a = LineString([(0, 0), (1, 1)])
line_b = LineString([(1, 1), (0, 2)])

Let’s check if they intersect.

line_a.intersects(line_b)True

Now let’s check if they touch.

line_a.touches(line_b)True

Let’s see if a LineString can touch itself.

line_a.touches(line_a)False

Now let’s see if a LineString can intersect with itself.

line_a.intersects(line_a)True

03

Using Geopandas for PIP Queries

We just used the Shapely package for topological calculations, now we will use Geopandas for related operations.

This time we will use aGeoPackage file as an example, which can still be downloaded from the following link:

https://pan.baidu.com/s/1bAnUo0S_ojxXdkyBqWAnLg?pwd=gxu2 Extraction code: gxu2

The example file contains observation records of protected species in a certain area, as well as a GeoJSON format polygon simulating the sub-basin of a river (which can also be downloaded from the above link). We will cross-check these two files to determine which observation records of protected species are located within the basin.

When reading layers from the GeoPackage file, we need to provide additional layer name information. This is because GeoPackage is essentially an embedded database format built on SQLite.

We first read the relevant data from this GeoPackage layer file.

import geopandas as gpd# Adjust the path according to the file storage location
species_fp = ".../species.gpkg"
species_data = gpd.read_file(species_fp, layer='category_3_species_porijogi', driver='GPKG')

We then read the GeoJSON file, which is the same as reading a Shapefile.

# Adjust the path according to the file storage location
polys_fp = ".../catchments.geojson"
polys = gpd.read_file(polys_fp, driver='GeoJSON')

Next, we filter for theIdaoja sub-basin, check its location, and plot the species observation points on the map layer.

import matplotlib.pyplot as plt
plt.style.use('ggplot')
plt.rcParams['figure.figsize'] = (15, 15)
subcatch = polys.loc[polys['NAME_1']=='Idaoja']
subcatch.reset_index(drop=True, inplace=True)
fig, ax = plt.subplots();
polys.plot(ax=ax, facecolor='gray')
subcatch.plot(ax=ax, facecolor='red')
species_data.plot(ax=ax, color='blue', markersize=5)
plt.tight_layout()
plt.show()

Geospatial Analysis with Python: Topological Calculations (Part 1)

From this, we can see that some points are indeed located within the selected red polygon (sub-basin).

Next, we need to find out which points are located within this polygon, which requires performing a PIP query.

First, we enable shapely.speedups (the acceleration module of the Shapely library), which can speed up some spatial queries.

import shapely.speedups
shapely.speedups.enable()

We will check which points are within the subcatch polygon. Since we need to determine whether these points are within the geometry of the subcatch GeoDataFrame, we need to use loc[0, ‘geometry’] to extract the actual polygon geometry from the GeoDataFrame.

pip_mask = species_data.within(subcatch.loc[0, 'geometry'])
pip_mask0       False
1       False
2       False
3       False
4       False        ...  1032    False
1033    False
1034    False
1035    False
1036    False
Length: 1037, dtype: bool

Now we have a boolean array corresponding to each row of data: if a point is inside the polygon, the result is True; otherwise, it is False.

Next, we can use this mask array to filter out the points that are inside the polygon.

pip_data = species_data.loc[pip_mask]pip_data     OBJECTID        LIIK             NIMI   EXT_SYST_I    KKR_KOOD PRIV_TYYP  
249    152958  taimed III    ohakasoomukas  -1902179792  KLO9331094    Avalik   
674    145079  loomad III  valge-toonekurg  -1632330969  KLO9105497    Avalik   
691    145191  loomad III  valge-toonekurg   1355787943  KLO9105625    Avalik   
694    145194  loomad III  valge-toonekurg   1430734590  KLO9105624    Avalik   
695    145196  loomad III  valge-toonekurg   1653031368  KLO9105598    Avalik   
979    147275  loomad III  valge-toonekurg   -934352158  KLO9108256    Avalik   
980    147279  loomad III  valge-toonekurg   -345614917  KLO9108257    Avalik   
982    147282  loomad III  valge-toonekurg     13169300  KLO9108254    Avalik   
985    147297  loomad III  valge-toonekurg   1849924613  KLO9108255    Avalik             STAATUS  IMPORT  LAADIMISKP                        geometry  
249  kontrollitud       0  2018-10-29  POINT (657531.007 6454827.405)  
674  kontrollitud       0  2018-10-29  POINT (657952.380 6451525.770)  
691  kontrollitud       0  2018-10-29  POINT (659189.190 6448592.205)  
694  kontrollitud       0  2018-10-29  POINT (658311.690 6451115.475)  
695  kontrollitud       0  2018-10-29  POINT (658117.710 6447988.785)  
979  kontrollitud       0  2018-10-29  POINT (659040.735 6454585.439)  
980  kontrollitud       0  2018-10-29  POINT (658493.413 6453377.590)  
982  kontrollitud       0  2018-10-29  POINT (658495.234 6452311.248)  
985  kontrollitud       0  2018-10-29  POINT (658387.491 6452891.505)  

Finally, we validate whether the PIP query executed successfully as expected by plotting the data.

subcatch = polys.loc[polys['NAME_1']=='Idaoja']
subcatch.reset_index(drop=True, inplace=True)
fig, ax = plt.subplots()
polys.plot(ax=ax, facecolor='gray')
subcatch.plot(ax=ax, facecolor='red')
pip_data.plot(ax=ax, color='gold', markersize=10)
plt.tight_layout()
plt.show()

Geospatial Analysis with Python: Topological Calculations (Part 1)

Now we see that the golden points are indeed all located within the red polygon, which is exactly the result we wanted.

Leave a Comment