# setup
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
import warnings
warnings.filterwarnings('ignore')
import matplotlib.pyplot as plt
from matplotlib import colors
from osgeo import gdal
from osgeo import ogr
from osgeo import osr
import numpy as np
import os
Celem zadania jest podział warstwy danych rastrowych na przedziały (percentyle), wyświetlenie percentyli w postaci wykresów pudełkowych oraz mapy kategoryzowanej gdzie wartości zostaną przypisane do percentyli. Pierwszy krok to zmiana katalogu roboczego oraz otwarcei warstwy rastrowej. Warstwa rastrowa posiada nodata o określonej wartości (minimalna wartość dla danego typu danych).
os.chdir('/home/jarekj/Dropbox/dydaktyka/programowanie2/data_raster')
raster = gdal.Open('pop_low.tif')
band = raster.GetRasterBand(1)
data = band.ReadAsArray()
nodata = band.GetNoDataValue()
nodata
W celu wyświetlenia mapy z uwzględnieniem nodata można zamaskować wartości symbolizujące nodata, przy pomocy funkcji np.ma(sk) oraz wyświetlenie obrazu z dodaniem palety kolorów.
masked_data = np.ma.masked_equal(data,nodata)
plt.imshow(masked_data,cmap="rainbow")
plt.colorbar()
Do wykonania wykresów słupkowych użyjemy jedynie podzbioru danych. W pierwszej kolejności musimy pobrać tę część danych, która posiada wartości (nie null).
subset = masked_data[~masked_data.mask] # następuje spłaszczenie
Uzyjemy podzbioru 10% i 4 przedziałów.
sampling = 0.1 #
nclass = 4
breaks = np.arange(0,1,step=1./nclass) #2
num_of_samples = int(round(len(subset)*sampling)) #1 musi być liczba całkowita
samples = np.random.choice(subset,num_of_samples,replace=False) #2
quants = np.quantile(subset,breaks) #3
quants
categories = np.digitize(samples,quants) #4
samplist = [samples[categories==cat] for cat in np.unique(categories)] #5
Wyświetlanie wykresów pudełkowych pozwala zdefiniować paletę kolorów. Niestety modyfikacja poszczególnych pudełek wymaga zastosowania specyficznej pętli.
cols = ["#FFA118","#FF111F","#AFA118","#9AABCD"]
boxes = plt.boxplot(samplist,vert=True,patch_artist=True)
for patch, color in zip(boxes['boxes'],cols):
patch.set_facecolor(color)
Ostatnim krokiem jest przygotowanie mapy. Przy pomocy funkcji digitize() ponownie kategoryzujemy dane, a następnie maskujemy wartości zerowe, które pelnią fukcję wartości NULL. Wyświetlanie kolorów zgodnych z wykresem, musimy przygotować colormap(). Taką mapę można następnie wyświetlić w formie obrazu.
categories = np.digitize(masked_data,quants)
masked_categories = np.ma.masked_equal(categories,0)
cmap = colors.ListedColormap(cols)
plt.imshow(masked_categories,cmap=cmap)
plt.colorbar()
Kwantyle z próby użyte do podziału mapy i wykonania wykresów odbiegają znacząco od kwantyli z populacji, co powoduje że wykresy nie są do końca wiarygodne. Jak poprawić mechanizm próbkowania aby otrzymać bardziej wiarygodne przedziały (stratyfikacja). Wykorzystać informację o rozkładzie i pojęcie wartości odstających. Zaproponować inne przedziały oparte o liczności danych.
Procesy konwersji jednego formatu na drugi, jest standardową procedurą geo-przetwarzania. W ramach procedury zmodyfikujemy raster zmiennoprzecinkowy, który zostanie zamieniony na wartości binarne, na podstawie jednej wartości.
raster = gdal.Open('pop_low.tif')
band = raster.GetRasterBand(1)
data = band.ReadAsArray()
nodata = band.GetNoDataValue()
x = raster.RasterXSize
y = raster.RasterYSize
proj = raster.GetProjection()
gt = raster.GetGeoTransform()
W celu zbudowania
masked_data = np.ma.masked_equal(data,nodata)
new_data = np.where(masked_data>2,1,0)
W celu dokonania dygitalizacji, tworzymy nowy raster w pamięci operacyjnej komputera używamy sterownika MEM zamiast standardowy geotiff. Tworzony raster jest typu Byte (8 bitów), do którego są dodawane wyniki procesu zmiany danych. Ustawiamy 0 aby symbolizował nodata.
driver = gdal.GetDriverByName('MEM')
new_rast= driver.Create("",x,y,1, gdal.GDT_Byte)
new_rast.SetProjection(proj)
new_rast.SetGeoTransform(gt)
tband=new_rast.GetRasterBand(1)
tband.WriteArray(new_data)
tband.SetNoDataValue(0)
Proces dygitalizacji wymaga utworzenia nowego wektora. Aby utworzyć wektor w tym samym układzie odniesienia należy dokonać konwersji układu odniesienia pliku rastrowego zwracanego w formie WKT na format akceptowany przez wektor, a następnie o utworzenie wektora oraz przynajmniej jedno pole, które będzie przechowywało wartość z wektoryzacji.
srs = osr.SpatialReference()
srs.ImportFromWkt(raster.GetProjection())
drv = ogr.GetDriverByName("ESRI Shapefile")
dst_ds = drv.CreateDataSource("polygon.shp")
dst_layer = dst_ds.CreateLayer("layer", srs = srs )
newField = ogr.FieldDefn('class', ogr.OFTInteger)
dst_layer.CreateField(newField)
Ostatnim krokiem jest poligonizacja, która w bibliotece GDAL jest specjalizowaną funkcją, wymaga podania dygitalizowanej warstwy rastrowej, docelowej warstwy wektorowej oraz numer kolumny do której zostaną zapisane wyniki dygitalizacji.
gdal.Polygonize(tband, None, dst_layer, 0, [], callback=None )
dst_ds=None
W zależności od wielkości rastra zmodyfikować skrypt, tak aby dla dużych rastrów, raster był zapisywany na dysku a następnie po zakończonej wektoryzacji usuwany. Na podstawie poprzedniego przykładu wprowadzić do skryptu więcej przedziałów progowych niż 1.
Procedury geo-przetwarzania bardzo często rozmiar danych rastrowych jest zbyt duży aby przechowywać całość danych w pamięci. Drugim mechanizmem wymuszającym porcjowanie danych jest proces zrównoleglania obliczeń. Z tego powodu stosuje się mechanizmy przetwarzania porcjami (chunks).
os.chdir('/home/jarekj/Downloads/landsat/')
nir_path = "NIR.tif"
red_path = "RED.tif"
Mechanizm porcjowania wymaga wiedzy na temat rozmiaru zbioru rastrowego oraz określenia wielkości porcji przetwarzanych danych. Raster ma rozmiary 5000 x 5000 Tu ustalamy wielkość porcji na 1000x1000 co oznacza 25 niezależnie przetwarzanych porcji. Celem przetwarzania jest wyliczenie indeksu NDVI, wg wzoru: (NIR-RED)/(NIR+RED)
ref = gdal.Open(nir_path)
x = ref.RasterXSize
y = ref.RasterYSize
proj = ref.GetProjection()
gt = ref.GetGeoTransform()
step=1000
W pierwszej kolejności zbudowane zostaną dwie listy: lista przesunięć (od 0 do wielkości wymiaru z przesunięciem o wartość kroku) oraz dwie listy rozmiarów porcji. W zależności czy wielkość rastra dzieli się bez reszty przez wielkość porcji czy nie dodajemy na końcu mniejszą porcję o wielkości tej reszty (modulo z dzielenia)
xoffs = list(range(0,x,step))
yoffs = list(range(0,y,step))
# wyliczenie zakresów
ysizes = [step]*(y//step)
if y%step: ysizes.append(y%step)
xsizes = [step]*(x//step)
if x%step: xsizes.append(x%step)
Otwaramy dostęp do plików i pobieramy z nich pasmo z danymi
nir_rast = gdal.Open(nir_path)
nir_band = nir_rast.GetRasterBand(1)
red_rast = gdal.Open(red_path)
red_band = red_rast.GetRasterBand(1)
Oraz tworzymy nowy plik w którym będziemy zapisywać dane. Plik jest typu Float32
ndvi_rast = gdal.GetDriverByName('GTiff').Create("ndvi.tif", x,y, 1, gdal.GDT_Float32)
ndvi_rast.SetProjection(proj)
ndvi_rast.SetGeoTransform(gt)
nband=ndvi_rast.GetRasterBand(1)
2*liczba_komorek_w_porcji
typu int16 czyli takiego jakiego są dane źródłowe.for xoff,xsize in zip(xoffs,xsizes): #1A
for yoff,ysize in zip(yoffs,ysizes): #1B
data=np.empty([2,xsize*ysize],dtype='int16') #2
data[0] = nir_band.ReadAsArray(xoff,yoff,xsize,ysize).flatten() #3
data[1] = red_band.ReadAsArray(xoff,yoff,xsize,ysize).flatten()
complete1 = (data != -9999).any(0) #4A
complete2 = (data != 0).all(0) #4B
complete = complete1 & complete2
complete_data = data[:,complete] #5
ndvi = np.full(len(complete),-9999,dtype='float32') #6
values = (complete_data[0]-complete_data[1])/(complete_data[0]+complete_data[1]).astype('float32') #7
ndvi[complete] = values
ndvi.shape = (ysize,xsize) #8
nband.WriteArray(ndvi,xoff,yoff)
Ostatnim krokiem jest zdefiniowanie wartości pustej i zamknięcie pliku.
nband.FlushCache()
nband.SetNoDataValue(-9999)
del nband
ndvi_rast = None
ref = None
Dowiedzeić się więcej o funkcji ReadAsArray() a następnie zbudować nowy skrypt, który w jednym kroku wyliczy podgląd ndvi w (dużo) mniejszej rozdzielczości i wyświetli w postaci obrazka z dobraną legendą. Sam skrypt tak zmodyfikować, aby użytkownik mógł wskazać mniejszy zakres rastra a następnie wielkość kroku została dobrana do tego rastra
Celem ćwiczenia jest zmodyfikowanie atrybutów pliku wektorowego, w taki sposób, że do każdego wiersza zostaną dodane dwa nowe atrybuty: najbliższy obiekt oraz odległość do niego. W tym celu otworzymy warstwę "liczniki.shp" oraz sprawdzimy jakie pola w danej warstwie są dostępne.
os.chdir('/home/jarekj/Dropbox/dydaktyka/programowanie2/data_vector')
vect = ogr.Open("gauges.shp",0) # 1 dla update
layer = vect.GetLayer()
layer.GetGeomType() == ogr.wkbPoint # test logiczny
defn = layer.GetLayerDefn()
nfields = defn.GetFieldCount()
nfields
fnames = [defn.GetFieldDefn(i).GetName() for i in range(nfields)]
types = [defn.GetFieldDefn(i).GetTypeName() for i in range(nfields)]
fields = dict(zip(fnames, types))
fields # w formie dict
Następnie zarówno pola jak i geometria zostaną zapisane w odpowiednich tablicach.
layer.ResetReading()
attrs=[]
points=[]
for f in layer: # f - feature
attrs.append([f.GetField(i) for i in range(nfields)])
points.append(f.GetGeometryRef().GetPoint())
arr_points = np.array(points) # ograniczenie do pierwszych 10 punktów
arr_points = arr_points[:,0:2] # usunięcie pustego Z
arr_points
zip()
tak aby odwrócić wiersze z kolumnamifrom scipy.spatial import distance
dst = distance.pdist(arr_points) #1
dst = distance.squareform(dst) #2
np.fill_diagonal(dst,np.nan)
indexes = np.nanargmin(dst,axis=1) #3 opcja z nan
namelist = list(zip(*attrs))[1] #4
names = [namelist[i] for i in list(indexes)] #5
diststo = np.nanmin(dst,axis=1) #6
Wyniki możemy wyświetlić w postaci podwójnej listy
list(zip(names,diststo))
Następnie budujemy nowy wektor według znanego schematu, kopując do niego istniejące pola oraz dodając dwa nowe: "colsest" i "dist_to"
os.environ['SHAPE_ENCODING'] = "utf-8"
# create layer based on the exisiting layer
driver = driver = ogr.GetDriverByName("ESRI Shapefile")
new_vect = driver.CreateDataSource("gauges2.shp")
new_layer = new_vect.CreateLayer("layer", layer.GetSpatialRef(),layer.GetGeomType())
for i in range(nfields):
new_layer.CreateField(defn.GetFieldDefn(i)) # kopiowanie pól z istniejącego shp
new_layer.CreateField(ogr.FieldDefn("closest", ogr.OFTString)) #Nowe pola
new_layer.CreateField(ogr.FieldDefn("dist_to", ogr.OFTReal))
Następnie wypełaniamy wektor przez features: dodajemy geometrię kopiując ją z istniejącego rastra oraz kopiujemy zawartość istniejących pól, na końcu dodając nowe. Wartości zmiennych "closest" i "dist_to" pobieramy z tablic names i distto poprzez index, którego wartość zwiększamy w każdym kroku (index+=1
)
Po zakińczeniu pętli głównej zamykamy wektor.
index=0
new_layer.ResetReading()
for f in layer:
#f = layer.next()
new_feature = ogr.Feature(new_layer.GetLayerDefn())
geom = f.GetGeometryRef() # kopiowanie geometrii
new_feature.SetGeometry(geom)
#i=0
for i in range(nfields): # kopiowanie pól
new_feature.SetField(fnames[i],f.GetField(i))
new_feature.SetField("closest",names[index])
new_feature.SetField("dist_to",diststo[index])
new_layer.CreateFeature(new_feature)
new_feature = None
index+=1
new_vect=None
new_layer=None
vect=None
Zmodyfikować skrypt tak, aby zidentyfikować stopień izolacji punktów (średnia odległość do 4 najbliższych). Wartość izolacji oraz listę czterech najbliższych (jako jeden string) zapisać jako atrybuty wektora.
Celem ćwiczenia jest wykonanie profil ze zbioru danych rastrowych wzdłuż linii zapisanej w formie pliku wektorowego.
W tym celu otwieramy dostęp do zbioru rastrowego i wektorowego
os.chdir('/home/jarekj/Dropbox/dydaktyka/programowanie2')
vect_path = "data_vector/przekroj.shp"
rast_path = "data_raster/pop_low.tif"
Wczytujemy dane wektorowe i dla pierwszej linii (feature) pobieramy jej geometrię. Jest to 6 punktów - wierzchołków linii
vect = ogr.Open(vect_path,0)
layer = vect.GetLayer()
layer.GetGeomType() == ogr.wkbLineString # test logiczny
layer.ResetReading()
f = layer.GetFeature(0)
geom = f.GetGeometryRef()
geom.GetPoints()
Taka liczba punktów jest zbyt mała aby wykonać przekrój. Zwiększamy liczbę punktów przy pomocy metody .Segmentize()
, która dodaje punkty do linii co określony odstęp. Tu użyjemy 200 metrów. Po dodaniu punktów wyniki zapisujemy w tablicy points
geom.Segmentize(200)
points = np.array(geom.GetPoints())
Otwieramy raster i zapisujemy go jako tablicę dwuwymiarową.
raster = gdal.Open(rast_path)
band = raster.GetRasterBand(1)
gtr = raster.GetGeoTransform() # paramtery transformacji rastra
arr = band.ReadAsArray()
Następnie w celu uniknięcia pracy w dwóch wymiarach, współrzędne punktów przeliczamy na numer wiersza i komórki, oraz zamieniamy oczywiście ich typ na wartość całkowitą. W tym samym celu tablicę dwuwymiarową zawierającą dane rastra spłaszczamy a następnie wyliczamy indeksy komórek wg formuły: nr_wiersza * ilosc_kolumn + nr_kolumny
. Ta formuła pozwala na obsługę rastra w postaci tablicy jednowymiarowej.
W ostatnim kroku pobieramy wartości rastra poprzez indeks komórki.
px = (points[:,0] - gtr[0]) / gtr[1]#x pixel
py = (points[:,1] - gtr[3]) / gtr[5] #y pixel
px = px.astype(int)
py = py.astype(int)
index = py * raster.RasterXSize + px
values = arr.flatten()[index]
Aby prawidłowo wykonać wykres należy obliczyć odległości pomiędzy kolejnymi punktami. W tym celu wykoamy dwie tabice:
Dzięki temu zabiegowi możemy obliczyć odległości pomiędzy punktami w formie prostej zwektoryzowanej formuły odległości euklidesowej: $ \sqrt{(x_1-x_2)^2+(y_1-y_2)^2} $. Po wyliczeniu odległości musimy dodać na początku 0 (dla pierwszego punktu) oraz odległości przeliczyć na odległości skumulowane
points1 = points[:-1,:]
points2 = points[1:,:]
dists = np.sqrt((points1[:,0]-points2[:,0])**2 + (points1[:,1]-points2[:,1])**2)
dists = np.insert(dists,0,0)
cum_dists = np.cumsum(dists)
Ostatnim krokiem jest wykreślenie wykresu.
plt.plot(cum_dists,values)
Zmodyfikować skrypt w taki sposób, aby na wykresie dodatkowo oznaczyć miejsca załamania linii przekrojowej