Geo-Referencing a Figure as an Image
By: Gijs Van Den Dool
Tags: Editors Pick, Mapping, Geotiff, Cartography, Data Conversion

Creating the Example maps

Note that in this example, the (city) points are shown and saved with the image below, to prove that the method is returning the desired result. The code is intended to run without the control points enabled.

# print(eea_FR.total_bounds)

# Expanding the area of interest to have a bit clearnace around the edges of the data

# Values are set to get an image which is almost square

(~7.86 x7.86 // ~12.25x12.25)

# These values have to be set based on the area of interest and the projection:

('Map1 Width: ', 7.852 // Height: 7.877)

('Map2 Width: ', 12.245 // Height: 12.269) Code section << Creating the Example maps>> not fully displayed

Result of the reprojection

The image below the code block shows the effect of the projection of the vector data in both the original and target projection systems.

#Combining the two maps #figsize : (float, float), as width, height

#f, ax = plt.subplots(1, figsize=((maxx2 - minx2)/150000,(maxy2 - miny2)/150000))

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,7.5))

fig.suptitle('Two map Projections', fontsize=20)

ax1.set_title('France in EPSG:4326 (Geodetic)', fontsize=15)

ax2.set_title('France in EPSG:3395 (Mercator)', fontsize=15)

ax1.set(xlim=(minx1, maxx1), ylim=(miny1, maxy1))

ax2.set(xlim=(minx2, maxx2), ylim=(miny2, maxy2))

ax1.set_axis_off()

ax2.set_axis_off() #plotting on the defined axis => ax1=ax1

eea_FR.plot(color='lightgrey', edgecolor='white', ax=ax1)

rdf.plot(facecolor='none', color='none', edgecolor='grey', ax=ax1)

eea_FRA.plot(facecolor='none', color='none', edgecolor='black', ax=ax1) #plotting on the defined axis => ax=ax2

eea_FR_rp.plot(color='lightgrey', edgecolor='white', ax=ax2)

rdf_rp.plot(facecolor='none', color='none', edgecolor='grey', ax=ax2)

eea_FRA_rp.plot(facecolor='none', color='none', edgecolor='black', ax=ax2) # In this example the points are on, and saved with the image, to prove that the

# method is returnin the desired result.

# The code is intended to run without the contol points

geo_FR_df.plot(ax=ax1, facecolor='black', edgecolor='red')

points.plot(ax=ax2, facecolor='black', edgecolor='red') plt.savefig('FRA_map1.eps', dpi=1080, bbox_inches='tight')

plt.show()

The difference in map projection (EPSG: 4326 to 3395)

This is the example background map, with France in grey, and white provincial boundaries, the borders of the countries surrounding France are set to dark grey without a fill.

When you are running this code with your own data, the layout can be set to your personal preferences. I like background maps to be as neutral as possible so that the attention is drawn to the essential elements of the map, in this case, the major cities in France.

Preparing the export

The next section of the code is preparing the formatted figure, as a background map, to be saved in a GeoTIFF format. The advantage of using the GeoTiff format when exporting is because metadata information is embedded in the image.

When using the exported GeoTiff in a new figure, the metadata will be used to reconstruct the background image, including projection, pixel size, and figure dimensions.

Data preparation

Data visualisation of the background map is not necessary, the final (right side) figure is handled in memory.

Code section << Data preparation>> not displayed, duplicating the earlier code block

Converting the figure

This is the most crucial step in the process, the figure is converted to a multi-dimensional array, storing the pixels in width and height, and the colour depth. Please, follow the link below for detailed information about the method.



# https://matplotlib.org/3.1.1/gallery/

user_interfaces/canvasagg.html # MatPlotLib as another method to convert a Canvas to an Array:user_interfaces/canvasagg.html canvas = FigureCanvasAgg(f)

# Save the figure to a string.

canvas.draw()

s, (width, height) = canvas.print_to_buffer() # Convert to a NumPy array.

X = np.frombuffer(s, np.uint8).reshape((height, width, 4))

Converting the Data Array

The original colours are stored in the last dimension of the image matrix, as a list with four attributes. In this example, the colours are compressed to a grey value because the created map is a background map with neutral colours. The fourth column is used for alpha (transparency) which is not used in this example and is set to 0. When creating a more complex background map, this fourth element could be used, but with this map, there is no need to store the original values in a multi-band image.

Colour Conversion Function:

Y = (0.2989 * (X[:,:,0])) +

(0.5870 * (X[:,:,1])) +

(0.1140 * (X[:,:,2]))

Storing the data array as a GeoTiff

In the next cell/step of this NoteBook, the data array is exported to an image using the GDAL library, with:

GetDriverByName: export as GeoTIFF SetGeoTransform: to preserve the coordinates, and osr.SpatialReference: as well as the projection, in this example, hardcoded (3395)

Creating the image from the figure data, with:

newRasterfn: File Name (str)

rasterOrigin: upper left corner (minx, maxy)

pixelWidth: cell dimension (w)

pixelHeight: cell dimension (h)

array: the flattened data from the figure (NxM)

Code section << Storing the data array as a GeoTiff>> is not displayed fully, and is only giving the high level view of the functions # Export array to raster (main function)

def array2raster(newRasterfn,

rasterOrigin,pixelWidth,pixelHeight,array):

# newRasterfn: File Name

# rasterOrigin: upper left corner

# pixelWidth: cell dimetion (w)

# pixelHeight: cell dimetion (h)

# array: the data from the figure # Creating the Image from a Figure

def createImageFromFigure(newRasterfn,

rasterOrigin,pixelWidth,pixelHeight,array): # Creating the image from the figure data, with:

createImageFromFigure (newRasterfn,

rasterOrigin,pixelWidth,pixelHeight,Y) Rechecking the dimentions

cols (X): 881 // rows (Y): 883

Cell size: 2084 (w) , 2084 (h)

rasterOrigin: (-672599.7581629481, 6738853.145639915)

Image saved to disk

Summary Part One

This concludes the first part of the code, to summarise what we have covered so far:

Conversion WorkFlow

Vector data was loaded to a MatPlotLib figure Selecting the right projection for the project, not all projects are using the same coordinate system. Restructuring the pixel information of the image, from multi-dimensional to a simple data array. Only greyscale values are needed in the background The parameters of the image are stored as metadata The figure is stored as a GeoTiff

In the next part, the converted image is reloaded in the project and the conversion process checked (to see if the image projected in the correct location).