# -*- coding: utf-8 -*-
"""
Class for defining a spatial grid: structure de données un peu plus évoluée
qu'un tableau 2x2.
"""
# For type annotation
from __future__ import annotations
from typing import Union
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
from tracklib.core.Bbox import Bbox
from tracklib.core.ObsCoords import ECEFCoords, ENUCoords, GeoCoords
import tracklib.core.Utils as utils
NO_DATA_VALUE = -9999
DEFAULT_NAME = 'grid'
[docs]class RasterBand:
[docs] def __init__(
self,
bb: Bbox,
resolution=None,
margin: float = 0.05,
novalue: float = NO_DATA_VALUE,
name = DEFAULT_NAME,
verbose: bool = True,
):
"""
Grid constructor.
:param bbox: Bouding box
:param resolution: Grid resolution
:param margin: relative float. Default value is +5%
:param novalue: value that is regarded as "missing" or "not applicable";
:param verbose: Verbose creation
"""
self.__bbox = bb
bb = bb.copy()
bb.addMargin(margin)
(self.xmin, self.xmax, self.ymin, self.ymax) = bb.asTuple()
#print (self.xmin, self.xmax)
ax, ay = bb.getDimensions()
#print (ax, ay)
if resolution is None:
am = max(ax, ay)
r = am / 100
resolution = (int(ax / r), int(ay / r))
else:
r = resolution
#print (ax, r[0])
resolution = (int(ax / r[0]), int(ay / r[1]))
#print (resolution)
# Nombre de dalles par cote
self.ncol = resolution[0]
self.nrow = resolution[1]
#print (self.nrow, self.ncol)
# Tableau de collections de features appartenant a chaque dalle.
# Un feature peut appartenir a plusieurs dalles.
self.grid = []
for i in range(self.nrow):
self.grid.append([])
for j in range(self.ncol):
self.grid[i].append(NO_DATA_VALUE)
self.XPixelSize = ax / self.ncol
self.YPixelSize = ay / self.nrow
#print (self.XPixelSize, self.YPixelSize)
self.__noDataValue = novalue
self.__name = name
[docs] def bbox(self):
return self.__bbox
[docs] def setName(self, name):
self.__name = name
[docs] def getName(self):
return self.__name
[docs] def setNoDataValue(self, noDataValue):
# On récupere l'ancienne valeur
oldvalue = self.__noDataValue
self.__noDataValue = noDataValue
for i in range(self.nrow):
for j in range(self.ncol):
if self.grid[i][j] == oldvalue:
self.grid[i][j] = self.__noDataValue
[docs] def getNoDataValue(self):
return self.__noDataValue
[docs] def isIn(self, coord: Union[ENUCoords]):
'''
Return true if coord is in spatial grid, false else.
Parameters
----------
coord : Union[ENUCoords]
coordinate of the position to test the contain.
Returns
-------
bool
true if contains, false else.
'''
if coord.E < self.xmin and coord.E > self.xmax:
return False
if coord.N < self.ymin and coord.N > self.ymax:
return False
return True
[docs] def __str__(self):
output = "-------------------------------------\n"
output += "Grid '" + self.__name + "':\n"
output += "-------------------------------------\n"
output += " nrows = " + str(self.nrow) + "\n"
output += " ncols = " + str(self.ncol) + "\n"
output += " XPixelSize = " + str(self.XPixelSize) + "\n"
output += " YPixelSize = " + str(self.YPixelSize) + "\n"
output += " Bounding box: \n"
output += " Lower left corner : " + str(self.xmin) + "," + str(self.ymin) + "\n"
output += " Upper right corner: " + str(self.xmax) + "," + str(self.ymax) + "\n"
output += "-------------------------------------\n"
return output
[docs] def summary(self):
print (self.__str__())
[docs] def bandStatistics(self):
stats = np.array(self.grid)
if self.getNoDataValue() != None:
stats[stats == self.getNoDataValue()] = None
print("-------------------------------------")
print("Grid '" + self.__name + "':")
print("-------------------------------------")
print(" Minimum value: ", np.nanmin(stats))
print(" Maximum value: ", np.nanmax(stats))
print(" Mean value: ", np.nanmean(stats))
print(" Median value: ", np.nanmedian(stats))
print("-------------------------------------\n")
if self.getNoDataValue() != None:
stats[stats == None] = self.getNoDataValue()
[docs] def getCell(self, coord: Union[ENUCoords, ECEFCoords, GeoCoords]) -> Union[tuple[float, float], None]:
"""Normalized coordinates of coord
(x,) -> (i,j) with:
- i = (x-xmin)/(xmax-xmin)*nb_cols
- j = (y-ymin)/(ymax-ymin)*nb_rows
:param coord: Coordinates
:return: Cell for give coordinates (or None if out of grid)
"""
if (coord.getX() < self.xmin) or (coord.getX() > self.xmax):
overflow = "{:5.5f}".format(
max(self.xmin - coord.getX(), coord.getX() - self.xmax)
)
print("Warning: x overflow " + str(coord) + " OVERFLOW = " + str(overflow))
return None
if (coord.getY() < self.ymin) or (coord.getY() > self.ymax):
overflow = "{:5.5f}".format(
max(self.ymin - coord.getY(), coord.getY() - self.ymax)
)
print("Warning: y overflow " + str(coord) + " OVERFLOW = " + str(overflow))
return None
idx = (float(coord.getX()) - self.xmin) / self.XPixelSize
idy = (self.nrow-1) - (float(coord.getY()) - self.ymin) / self.YPixelSize
return (idx, idy)
[docs] def plotAsGraphic(self, backgroundcolor="lightcyan", bordercolor="lightgray"):
"""
Plot grid
"""
fig = plt.figure()
ax = fig.add_subplot(
111, xlim=(self.xmin, self.xmax), ylim=(self.ymin, self.ymax)
)
for i in range(1, self.ncol):
xi = i * self.XPixelSize + self.xmin
ax.plot([xi, xi], [self.ymin, self.ymax], "-", color=bordercolor)
for j in range(1, self.nrow):
yj = j * self.YPixelSize + self.ymin
ax.plot([self.xmin, self.xmax], [yj, yj], "-", color=bordercolor)
for i in range(self.nrow):
y1 = self.ymin + (self.nrow - 1 - i) * self.YPixelSize
y2 = self.ymin + (self.nrow - i) * self.YPixelSize
for j in range(self.ncol):
x1 = self.xmin + j * self.XPixelSize
x2 = x1 + self.XPixelSize
if self.grid[i][j] != NO_DATA_VALUE:
#print (self.xmin, x1, y1, x2, y2)
polygon = plt.Polygon(
[[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]]
)
ax.add_patch(polygon)
polygon.set_facecolor(backgroundcolor)
text_kwargs = dict(ha='center', va='center', fontsize=12, color='C1')
val = str(round(self.grid[i][j], 2))
xm = (x2 - x1) / 2
ym = (y2 - y1) / 2
plt.text(x1 + xm, y1 + ym, val, **text_kwargs)
plt.title(self.getName())
[docs] def plotAsImage(self, axe = None, figure = None,
color1 = (0, 0, 0), color2 = (255, 255, 255), novaluecolor='white'):
"""
Plot as image
"""
tab = np.array(self.grid, dtype=np.float32)
if self.getNoDataValue() != None:
tab[tab == self.getNoDataValue()] = np.nan
cmap = utils.getOffsetColorMap(color1, color2, 0)
cmap.set_bad(color=novaluecolor)
if axe == None:
im = plt.imshow(tab, cmap=cmap)
plt.title(self.getName())
plt.colorbar(im, fraction=0.046, pad=0.04)
plt.show()
else:
im = axe.imshow(tab, cmap=cmap)
axe.set_title(self.getName())
divider = make_axes_locatable(axe)
cax = divider.append_axes('right', size='5%', pad=0.1)
if figure != None:
figure.colorbar(im, cax=cax, orientation='vertical', fraction=0.046)