I have a GPKG file, with 2 layers, p and q. I would like to create a third layer, r. I would like r to contain those parts of p which do not intersect with q. We could phrase this as r=p-q.
Below is a way to achieve this, using ogr2ogr
's -sql
option. I would like to learn how to make the presented method faster.
Simple example
Let p be a chessboard-like grid of squares, and q be circles distributed randomly. I generate such GeoSeries using Python:
import geopandas as gpd
import shapely.geometry
import numpy as np
np.random.seed(42)
n = 8 # <- will try multiple different ns below
p = gpd.GeoSeries(
[shapely.geometry.box(j, i, j + 1, i + 1) for i in range(n) for j in range(n)]
)
q = gpd.GeoSeries(
[
shapely.geometry.Point(e)
for e in np.random.uniform(low=0, high=n, size=[n * n * 10, 2])
]
).buffer(distance=0.1)
Visualization of the result:
import matplotlib.pyplot as plt
np.random.seed(42)
fig, ax = plt.subplots()
p.plot(ax=ax, edgecolor="red",facecolor="none")
q.plot(ax=ax, edgecolor="black",facecolor="none")
ax.axis("off");
Write GeoSeries to GPKG:
!rm -f united.gpkg
p.to_file("united.gpkg",layer="p")
q.to_file("united.gpkg",layer="q")
(I work in a Jupyter-Lab notebook where lines starting with !
are interpreted as bash calls.)
I use ogr2ogr
to compute r:
ogr2ogr \
-update \
-sql "SELECT ST_Difference(p.geom, (SELECT ST_UNION(geom) from q WHERE ST_INTERSECTS(p.geom,geom))) AS geom FROM p" \
-nln r \
-nlt "MULTIPOLYGON" \
united.gpkg united.gpkg
Inspect results in QGIS:
The query worked as expected.
Benchmarking
Above, the r=p-q operation was performed on relatively few p & q geometries. I would like to time how fast r is calculated for different numbers of geometries within p & q. In other words: how fast does the SQL SELECT statement run, if I vary n
? (There are n^2
geometries in layer p.)
I specifically care about how fast the SQL
query is run, not how fast the GPKG file is written. For this reason, I am going to switch to ogrinfo
(and I'll suppress the non-error outputs by using > /dev/null
, as described here).
Execution time measuring script:
import geopandas as gpd
import shapely.geometry
import numpy as np
import time
np.random.seed(42)
elapsedList = []
ns = [8,16,24,32,40,48,56,64]
for n in ns:
p = gpd.GeoSeries(
[shapely.geometry.box(j, i, j + 1, i + 1) for i in range(n) for j in range(n)]
)
q = gpd.GeoSeries(
[
shapely.geometry.Point(e)
for e in np.random.uniform(low=0, high=n, size=[n * n * 10, 2])
]
).buffer(distance=0.1)
!rm -f united.gpkg
p.to_file("united.gpkg",layer="p")
q.to_file("united.gpkg",layer="q")
t0 = time.time()
!ogrinfo -q -sql "SELECT ST_Difference(p.geom, (SELECT ST_UNION(geom) from q WHERE ST_INTERSECTS(p.geom,geom))) AS geom FROM p" united.gpkg > /dev/null
elapsedList.append(time.time()-t0)
Visualize execution time:
import matplotlib.pyplot as plt
plt.figure(figsize=(8,4))
plt.scatter([e**2 for e in ns],elapsedList)
plt.plot([e**2 for e in ns],elapsedList,c='r')
plt.xlabel("Number of geometries in p")
plt.ylabel("ogrinfo execution time")
It seems that the total time needed for the SELECT
statement scales worse than linearly with the number of geometries in p.
Fast execution in the large p, q case
My aim is to make the above process faster for large p and q.
A possible approach would be: instead of straight running ogrinfo
with the SELECT ST_Difference ... geom FROM p
command as above, one could use ogrinfo
to create spatial indexes in the GPKG file, so that the SELECT ST_Difference ... geom FROM p
executes faster, when p and q are large. I don't know much about spatial indexes, but based on ogrinfo -sql "SELECT HasSpatialIndex('q','geom')" united.gpkg
and ogrinfo -sql "SELECT HasSpatialIndex('p','geom')" united.gpkg
, it seems these layers already have a spatial index. Output for both:
INFO: Open of `united.gpkg'
using driver `GPKG' successful.
Layer name: SELECT
Geometry: Unknown (any)
Feature Count: 1
Layer SRS WKT:
(unknown)
HasSpatialIndex: Integer (0.0)
OGRFeature(SELECT):0
HasSpatialIndex (Integer) = 1
Another approach would be to improve the query itself, ie
SELECT ST_Difference(p.geom, (SELECT ST_UNION(geom) FROM q WHERE ST_INTERSECTS(p.geom,geom))) AS geom FROM p
I don't yet know the exact way.
How to make the above r=p-q procedure faster for large p and q?
ogrinfo my.gpkg -sql "create index my_index on my_table (my_column)"
.SELECT ST_Difference(p.geom, (SELECT ST_UNION(geom) from q WHERE ST_INTERSECTS(p.geom,geom))) AS geom FROM p
? Could you give me a pointer on what should I look up to understand how that query is not nearly as good as it could be @GabrielDeLuca?