How to create an Elevation Raster from Contour Lines with Python, Geopandas, Numpy and Gdal - Tutorial

createRasterwithPython.PNG

Spatial analysis is such an interesting discipline because it allows the evaluation of every phenomena related to their location. However, for some parts of the data processing the workflow on a GIS Graphical Computer Interface (GUI) can be repetitive and time consuming. Researchers need better and more efficient tools to process more amount of data in less amount of time and even with less quantity of software tools.

We have create a innovative script to generate an elevation raster file from a contour line with several steps of data processing. The script recognizes invalid geometries, simplify the polylines and extract vertices while creates a point geodataframe that is interpolated and geotransformed as a geospatial raster in .tiff format.

Tutorial

Python script


Geopandas geodataframes generation

%matplotlib inline
import geopandas as gpd
import pandas as pd
import matplotlib.pyplot as plt
from shapely.geometry import Point, Polygon
from shapely.ops import split

#Shapefile list
%ls ..\Shp\*.shp
El volumen de la unidad C no tiene etiqueta.
 El n£mero de serie del volumen es: 24E6-96EB

 Directorio de C:\Users\GIDAWK1\Documents\howToCreateAreaofInterestRasterfromContourLineswithPythonandGeopandas\Shp

27/11/2019  09:43 a.m.         7,667,184 contours_10m.shp
29/11/2019  10:51 a.m.         7,436,972 contours_10m_v2.shp
02/12/2019  03:08 p.m.           187,336 pointsAoi.shp
02/12/2019  11:44 a.m.               236 rasterAoi.shp
               4 archivos     15,291,728 bytes
               0 dirs  28,501,250,048 bytes libres
#Open countour lines and area of interest
topo1 = gpd.read_file('../Shp/contours_10m_v2.shp')
rasterAoi = gpd.read_file('../Shp/rasterAoi.shp')
aoiGeom = rasterAoi.loc[0].geometry
topo1.crs
{'init': 'epsg:32611'}
#Plot of the countour line with the selected area to extract vertices
ax = rasterAoi.plot(color='green', figsize=(12,12), alpha=0.2);
topo1.plot(ax=ax,alpha=0.5)
ax.grid()
output_3_0.png

Vertex extraction to the Area of Interest

compDf = gpd.GeoDataFrame()
compDf['geometry']=None
i=0

for index, row in topo1.iterrows():
    #index counter
    if index % 100 == 0:
        print('Processing polylines_'+str(index)+'\n')

    if row.geometry!=None: #Check for false geometries
        try:          
            if row.geometry.disjoint(aoiGeom)==False: #Filter all lines that dont cross the polygon 
                #print(len(row.geometry.coords))
                simpRow = row.geometry.simplify(5,preserve_topology=True)
                vertexBefore = len(row.geometry.coords)
                vertexAfter = len(simpRow.coords)
                reductionPercen = vertexAfter/vertexBefore*100

                print('Vertex before: %d, Vertex after: %d, Reduction rate: %.2f'%(vertexBefore,vertexAfter,reductionPercen))
                for coord in simpRow.coords:
                    if Point(coord).within(aoiGeom): #Check if vertex is inside the polygon
                        compDf.loc[i]=str(row.ID) #Assign values to the dataframe
                        compDf.loc[i,'Easting']=coord[0]
                        compDf.loc[i,'Northing']=coord[1]
                        compDf.loc[i,'Elevation']=row.CONTOUR
                        compDf.loc[i,'geometry']=Point(coord)
                        i+=1

                        #Counter of proccessed points
                        if i % 1000 == 0:
                            print('Processed Vertices_'+str(i))                    
        except ValueError: pass

compDf.head()
Processing polylines_0

Vertex before: 81, Vertex after: 48, Reduction rate: 59.26
Vertex before: 1888, Vertex after: 1035, Reduction rate: 54.82
Vertex before: 2111, Vertex after: 1049, Reduction rate: 49.69
Vertex before: 2486, Vertex after: 1229, Reduction rate: 49.44
Vertex before: 2665, Vertex after: 1291, Reduction rate: 48.44
Processed Vertices_1000
Vertex before: 2721, Vertex after: 1288, Reduction rate: 47.34
Vertex before: 2828, Vertex after: 1286, Reduction rate: 45.47
Vertex before: 3056, Vertex after: 1306, Reduction rate: 42.74
Processed Vertices_2000
Vertex before: 3093, Vertex after: 1324, Reduction rate: 42.81
Vertex before: 3480, Vertex after: 1475, Reduction rate: 42.39
Vertex before: 3893, Vertex after: 1655, Reduction rate: 42.51
Processed Vertices_3000
Vertex before: 3111, Vertex after: 1275, Reduction rate: 40.98
Vertex before: 3654, Vertex after: 1398, Reduction rate: 38.26
Vertex before: 3558, Vertex after: 1216, Reduction rate: 34.18
Vertex before: 3612, Vertex after: 1294, Reduction rate: 35.83
Processed Vertices_4000
Vertex before: 3606, Vertex after: 1283, Reduction rate: 35.58
Vertex before: 3674, Vertex after: 1226, Reduction rate: 33.37
Vertex before: 3723, Vertex after: 1268, Reduction rate: 34.06
Vertex before: 3796, Vertex after: 1290, Reduction rate: 33.98
Processed Vertices_5000
Vertex before: 3832, Vertex after: 1293, Reduction rate: 33.74
Vertex before: 3838, Vertex after: 1207, Reduction rate: 31.45
Vertex before: 4043, Vertex after: 1247, Reduction rate: 30.84
Vertex before: 4121, Vertex after: 1259, Reduction rate: 30.55
Vertex before: 4177, Vertex after: 1252, Reduction rate: 29.97
Processed Vertices_6000
Vertex before: 4293, Vertex after: 1245, Reduction rate: 29.00
Vertex before: 4399, Vertex after: 1310, Reduction rate: 29.78
Vertex before: 4367, Vertex after: 1242, Reduction rate: 28.44
Vertex before: 4435, Vertex after: 1259, Reduction rate: 28.39
Vertex before: 4490, Vertex after: 1266, Reduction rate: 28.20
Vertex before: 4525, Vertex after: 1316, Reduction rate: 29.08
Vertex before: 4616, Vertex after: 1367, Reduction rate: 29.61
Vertex before: 4679, Vertex after: 1349, Reduction rate: 28.83
Vertex before: 4857, Vertex after: 1445, Reduction rate: 29.75
Vertex before: 4945, Vertex after: 1489, Reduction rate: 30.11
Vertex before: 5242, Vertex after: 1620, Reduction rate: 30.90
Vertex before: 5754, Vertex after: 1666, Reduction rate: 28.95
Vertex before: 6588, Vertex after: 1929, Reduction rate: 29.28
Processing polylines_100

Processing polylines_200

Processing polylines_300
geometry Easting Northing Elevation
0 POINT (222455.383 3788700.041) 222455 3.7887e+06 -590
1 POINT (222411.465 3788720.048) 222411 3.78872e+06 -590
2 POINT (222401.342 3788809.926) 222401 3.78881e+06 -590
3 POINT (222355.383 3788845.643) 222355 3.78885e+06 -590
4 POINT (222255.383 3788803.316) 222255 3.7888e+06 -590
#Representation of the extracted vertices and the area of interest
ax = rasterAoi.plot(color='green', figsize=(12,12), alpha=0.2);
topo1.plot(ax=ax,color='cyan',alpha=0.5)
compDf.plot(ax=ax,alpha=0.5)
ax.grid()
output_6_0.png
#Export point to shapefile
compDf.crs={'init': 'epsg:32611'}
compDf.to_file('../Shp/pointsAoi.shp')

Point interpolation

import numpy as np
from scipy.interpolate import griddata

#From the dataFrame
points = compDf[['Easting','Northing']].values
values = compDf['Elevation'].values

#Assign the raster resoulution
rasterRes = 100.0

#Get the Area of Interest dimensions and number of rows / cols
print(aoiGeom.bounds)
xDim = aoiGeom.bounds[2]-aoiGeom.bounds[0]
yDim = aoiGeom.bounds[3]-aoiGeom.bounds[1]
print('Raster X Dim: %.2f, Raster Y Dim: %.2f'%(xDim,yDim))
nCols = xDim / rasterRes
nRows = yDim / rasterRes
print('Number of cols:  %.2f, Number of rows: %.2f'%(nCols,nRows)) #Check if the cols and row don't have decimals
(222000.0, 3783000.0, 243000.0, 3796000.0)
Raster X Dim: 21000.00, Raster Y Dim: 13000.00
Number of cols:  210.00, Number of rows: 130.00
#We create an array on the cell centroid
grid_y, grid_x = np.mgrid[aoiGeom.bounds[1]+rasterRes/2:aoiGeom.bounds[3]-rasterRes/2:nRows*1j,
                         aoiGeom.bounds[0]+rasterRes/2:aoiGeom.bounds[2]+rasterRes/2:nCols*1j]

mtop = griddata(points, values, (grid_x, grid_y), method='cubic')
mtop[:5,:5]
array([[          nan, -480.77331   , -480.06521355, -479.63126685,
        -480.03963447],
       [          nan, -485.85168813, -484.67218837, -484.55962848,
        -483.67878073],
       [          nan, -491.72186276, -490.12375354, -491.89022442,
        -491.8011287 ],
       [          nan, -498.59717162, -498.41920838, -497.27970783,
        -497.03388778],
       [          nan, -504.09576321, -504.01128831, -501.76031806,
        -500.74936978]])
import matplotlib.pyplot as plt
plt.imshow(mtop)
plt.colorbar()
<matplotlib.colorbar.Colorbar at 0xeaaafd0>
output_11_1.png

Raster generation

from osgeo import gdal, gdal_array, osr

geotransform = [aoiGeom.bounds[0],rasterRes,0,aoiGeom.bounds[1],0,rasterRes] #[xmin,xres,0,ymax,0,-yres]
raster = gdal.GetDriverByName('GTiff').Create("../Rst/aoiRaster.tif",int(nCols),int(nRows),1,gdal.GDT_Float64)
raster.SetGeoTransform(geotransform)

srs=osr.SpatialReference()
srs.ImportFromEPSG(32611)

raster.SetProjection(srs.ExportToWkt())
raster.GetRasterBand(1).WriteArray(mtop)
del raster

Input files

You can download the input files from this link.

3 Comments

Saul Montoya

Saul Montoya es Ingeniero Civil graduado de la Pontificia Universidad Católica del Perú en Lima con estudios de postgrado en Manejo e Ingeniería de Recursos Hídricos (Programa WAREM) de la Universidad de Stuttgart con mención en Ingeniería de Aguas Subterráneas y Hidroinformática.

 

Suscribe to our online newsletter

Subscribe for free newsletter, receive news, interesting facts and dates of our courses in water resources.