Основною метою дослідження було визначити в межах Кривого Рогу загальну площу території, яку покрито “живою” (не засохлою) травою. Для цього упродовж декількох днів я візуально вивчав фактичний стан газонів, скверів, пустирів та інших об’єктів у Кривому Розі. Під час візуального обстеження стало зрозумілим, що лише мала частка власне газонів представлена “живою” травою. Більшість переглянутих мною ділянок несли або вельми пригнічену, або взагалі висохлу трав’яну рослинність. Також стало зрозумілим, що в окремий клас теоріторії треба винести пустирі. Пустирі здебільшого вкриті або багаторічними високорослими травами (деревій, полин, амброзія тощо), або сумішшю низькорослих та високорослих трав. Також майже всюди трапляються поодинокі дерева.

Через малу кількість “живих” газонів довелось трохи змістити рівень детектування класу “газон”. Під час класифікації до “газонів” мною було віднесено не лише власне “газони”, а й будь-які ділянки з низькорослими “живими” травами - доглянуті стадіони, частини пустирів, ділянки суходолу в безпосередній близькості до водних об’єктів.

Загалом в рамках дослідження було виділено 10 умовних класів:

В якості первинних матеріалів взято:

Для радарного знімку було проведено радіометричну корекцію, фільтрацію спекл-шуму (з використанням фільтру Gamma Map), обчислення сили відбиття в децибелах та геометричну корекцію (Range-Doppler). Для цього було використано програмний засіб SNAP (Sentinel Application Platform).

Знімки оптичних супутників оброблено з використанням модулю Semi-Automatic Classification Plugin геоінформаційної системи QGIS.

Після підготовчого оброблення всі знімки було завантажено в геоінформаційну систему SAGA. Там виконано обрізання по контуру Кривого Рогу (контур взято з проекту OpenStreetMap), приведення всіх знімків до розміру пікселю 10Х10 м. При цьому в якості “основи” використовувався знімок космічного апарату Sentinel-2A.

Для формування навчальної вибірки об’єктів зроблено сегментацію досліджуваної території базовим методом Object Based Image Segmentation геоінформаційної системи SAGA. Потім незначній частині полігонів отриманого шейп-шару приписано означені вище класи території. Загалом було прокласифіковано 461 полігон. Кількість за класами значно різниться.

Наступні кроки виконувались в середовищі мови програмування R. Нижче міститься скрипт, яким виконувався весь процес обчислення та перевірки даних.

#Завантажуємо потрібні пакети
library(plyr)#саме в такій послідовності: спочатку plyr, потім dplyr!
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:plyr’:

    arrange, count, desc, failwith, id, mutate, rename, summarise, summarize

The following objects are masked from ‘package:raster’:

    intersect, select, union

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(raster)
library(ggplot2)
#Встановлюємо директорію для кешування "великих растрів"
rasterOptions(tmpdir = "/home/geka/ssd/LargeTMP/")
#Завантажуємо у вигляді окремих об'єктів всі супутникові матеріали
Image_S2B2 <- raster("/home/geka/Documents/RPubs/Gazon/S2_TWT_UWU_20170808_B02_KrR.sdat")
Image_S2B3 <- raster("/home/geka/Documents/RPubs/Gazon/S2_TWT_UWU_20170808_B03_KrR.sdat")
Image_S2B4 <- raster("/home/geka/Documents/RPubs/Gazon/S2_TWT_UWU_20170808_B04_KrR.sdat")
Image_S2B8 <- raster("/home/geka/Documents/RPubs/Gazon/S2_TWT_UWU_20170808_B08_KrR.sdat")
Image_S2B12 <- raster("/home/geka/Documents/RPubs/Gazon/S2_TWT_UWU_20170808_B12_KrR.sdat")
Image_L8B10 <- raster("/home/geka/Documents/RPubs/Gazon/RT_LC08_L1TP_178027_20170812_20170812_01_RT_B10_KrR.sdat")
Image_S1VH <- raster("/home/geka/Documents/RPubs/Gazon/S1A_GRDH_20170807_SigmaVH_db_KrR.sdat")
Image_S1VV <- raster("/home/geka/Documents/RPubs/Gazon/S1A_GRDH_20170807_SigmaVV_db_KrR.sdat")
#Об'єднуємо всі канали в один об'єкт
ImageStack <- stack(Image_S2B2, Image_S2B3, Image_S2B4, Image_S2B8, Image_S2B12,
                    Image_L8B10, Image_S1VH, Image_S1VV)
#Перейменовуємо канали в об'єднаному об'єкті
names(ImageStack) <- c("S2B2", "S2B3", "S2B4", "S2B8", "S2B12",
                    "L8B10", "S1VH", "S1VV")
#Обчислюємо індекс NDVI на основі даних супутника Sentinel-2A
ImageStack$NDVI <- ((ImageStack$S2B8 - ImageStack$S2B4) / (ImageStack$S2B8 + ImageStack$S2B4))
#Візуально перевіряємо адекватність та повноту даних
plotRGB(ImageStack, r = 6, g = 4, b = 7, stretch = "hist")

#Завантажуємо шейп-файл з навчальними полігонами
trainShape <- shapefile("/home/geka/Documents/RPubs/Gazon/Segments_Class.shp")
#Відкидаємо "порожні" полігони - ті, яким не присвоювались класи
trainData <- trainShape[!is.na(trainShape@data$Class) ,]
#Робимо поле цілих чисел з номерами класів
trainData@data$ClassInt <- as.integer(factor(trainData@data$Class))
#Перевіряємо кількість наявних учбових полігонів за класами
summary(factor(trainData@data$Class))
      Bulrush   Burned Area Degraded Lawn        Forest          Lawn          Park 
           29            28            20            47            13            42 
        Solid       Village     Wasteland         Water 
          122            75            32            53 
#Попіксельно витягуємо дані в межах учбових полігонів та формуємо зведену таблицю значень з супутників та відповідних класів. 
#Приклад програмного коду взято з: http://amsantac.co/blog/en/2015/11/28/classification-r.html
dfAll <- data.frame(matrix(vector(), nrow = 0, ncol = length(names(ImageStack)) + 1))
for (i in 1:length(unique(trainData[["ClassInt"]]))){
  category <- unique(trainData[["ClassInt"]])[i]
  categorymap <- trainData[trainData[["ClassInt"]] == category,]
  #запускаємо кластер багатопоточних обчислень
  beginCluster()
  dataSet <- extract(ImageStack, categorymap)
  #відключаємо кластер багатопоточних обчислень
  endCluster()
  dataSet <- dataSet[!unlist(lapply(dataSet, is.null))]
  dataSet <- lapply(dataSet, function(x){cbind(x, ClassInt = as.numeric(rep(category, nrow(x))))})
  df <- do.call("rbind", dataSet)
  dfAll <- rbind(dfAll, df)
}
Loading required namespace: parallel
4 cores detected, using 3
Using cluster with 3 nodes
#Повертаємо символьні найменування класів в отриману на попередньому кроці таблицю
ClassDF <- data.frame(Class = levels(factor(trainData@data$Class)), ClassInt = seq(1:10))
dfAll <- left_join(dfAll, ClassDF)
Joining, by = "ClassInt"
LearnDF <- dplyr::select(dfAll, -ClassInt)
#Перевіряємо кількість пікселів кожного класу в отриманій таблиці учбової вибірки
summary(factor(LearnDF$Class))
      Bulrush   Burned Area Degraded Lawn        Forest          Lawn          Park 
          701           581           459          1125           322           956 
        Solid       Village     Wasteland         Water 
         4335          2080           968          1564 
#Будуємо діаграму зі статистичними показниками виділених класів
tbl_df(LearnDF) %>%
  reshape2::melt(id = "Class") %>%
  ggplot(aes(y = value, x = Class, colour = variable)) +
  geom_boxplot() +
  coord_flip() +
  facet_wrap( ~variable, nrow = 2, scales = "free_x") +
  labs(y = "величини значень оптичних та радарних діапазонів\n",
       x = "\n",
       title = "Радарні та оптичні властивості\nумовних класів території в межах Кривого Рогу",
       caption = "дані космічних апаратів\nSentinel-1A, Sentinel-2A, Landsat-8\nза 7-12 серпня 2017 року") +
  theme(plot.title = element_text(hjust = 0.5),
        text = element_text(family = "FreeSans",
     face = "plain", size = 12, colour = "firebrick4", lineheight = 0.9),
     legend.position = "none",
     axis.text.x = element_text(colour = "gray15", face="bold",
     size= 8, angle = 0, vjust = 0.5, hjust = 0.5),
     axis.text.y = element_text(colour = "gray15", face="bold",
     size= 10, angle = 0, vjust = 0.5, hjust = 1),
     axis.ticks = element_line(colour = "orange", size = 0.1),
     plot.background = element_rect(fill = "gray95"),
     panel.grid.major = element_line(colour = "orange", size = 0.1),
     panel.grid.minor = element_line(colour = "gray90"),
     panel.background=element_rect(fill="gray95"))

Далі перед нами постає проблема. Справа у тому, що навіть за умови попіксельного розбиття первинних учбових полігонів, порівняно мало навчальних об’єктів для класів Lawn, Degraded Lawn та Burned Area. У якості експерименту я вирішив зробити вибірку з поверненням, щоб досягти умови врівноваженості класів та 1000 навчальних об’єктів для кожного класу. Таким чином, кількість об’єктів для класу Lawn було штучно збільшено шонайменше втричі.

#Формуємо робочу таблицю навчальної вибірки
LearnDF %>% group_by(Class) %>% 
  sample_n(size = 750, replace = TRUE) %>%
  ungroup(Class) -> LearnDF.sample
LearnDF.sample$Class <- factor(LearnDF.sample$Class)
#Завантажуємо пакет Random Forest
library(randomForest)
randomForest 4.6-12
Type rfNews() to see new features/changes/bug fixes.

Attaching package: ‘randomForest’

The following object is masked from ‘package:ggplot2’:

    margin

The following object is masked from ‘package:dplyr’:

    combine
#Запускаємо моделювання та вказуємо робочі параметри процесу моделювання
RF_model <- randomForest(Class ~., data = LearnDF.sample,
                         replace = TRUE,
                         nodesize = 5,
                         importance = TRUE,
                         mtry = 3,
                         ntree = 350,
                         do.trace = 20)
ntree      OOB      1      2      3      4      5      6      7      8      9     10
   20:   5.47%  3.73%  0.53%  7.73%  6.40%  2.53% 12.40%  9.33%  7.47%  3.33%  1.20%
   40:   4.40%  3.07%  0.40%  6.00%  6.00%  1.07% 10.00%  7.60%  5.60%  3.20%  1.07%
   60:   4.19%  2.67%  0.40%  5.33%  5.20%  1.07% 10.80%  7.60%  4.80%  2.67%  1.33%
   80:   4.09%  3.07%  0.40%  4.93%  4.80%  1.33% 10.13%  7.87%  4.93%  2.27%  1.20%
  100:   4.00%  3.20%  0.40%  4.93%  4.53%  1.20%  9.20%  8.13%  4.53%  2.53%  1.33%
  120:   4.00%  2.93%  0.40%  4.67%  4.80%  1.20%  9.33%  8.13%  4.80%  2.53%  1.20%
  140:   3.84%  2.53%  0.40%  4.80%  4.67%  1.20%  8.27%  8.00%  4.67%  2.67%  1.20%
  160:   3.93%  2.80%  0.40%  4.93%  4.80%  1.47%  8.53%  7.87%  4.80%  2.53%  1.20%
  180:   3.87%  2.67%  0.40%  5.33%  4.40%  1.20%  8.13%  7.60%  4.93%  2.80%  1.20%
  200:   3.80%  2.67%  0.40%  5.07%  4.40%  1.33%  8.13%  7.60%  4.67%  2.40%  1.33%
  220:   3.76%  2.67%  0.40%  4.93%  4.40%  1.33%  8.13%  7.60%  4.40%  2.53%  1.20%
  240:   3.75%  2.53%  0.40%  4.93%  4.40%  1.33%  8.27%  7.73%  4.13%  2.53%  1.20%
  260:   3.79%  2.80%  0.40%  4.93%  4.40%  1.33%  8.27%  7.73%  4.27%  2.53%  1.20%
  280:   3.72%  2.80%  0.40%  5.07%  4.27%  1.20%  8.00%  7.47%  4.27%  2.53%  1.20%
  300:   3.75%  2.80%  0.40%  5.07%  4.40%  1.20%  8.00%  7.47%  4.40%  2.53%  1.20%
  320:   3.77%  2.80%  0.40%  5.20%  4.27%  1.20%  8.00%  7.60%  4.67%  2.27%  1.33%
  340:   3.73%  2.93%  0.40%  4.80%  4.13%  1.20%  8.00%  7.33%  4.67%  2.53%  1.33%
#Дивимось результат моделювання
RF_model

Call:
 randomForest(formula = Class ~ ., data = LearnDF.sample, replace = TRUE,      nodesize = 5, importance = TRUE, mtry = 3, ntree = 350, do.trace = 20) 
               Type of random forest: classification
                     Number of trees: 350
No. of variables tried at each split: 3

        OOB estimate of  error rate: 3.76%
Confusion matrix:
              Bulrush Burned Area Degraded Lawn Forest Lawn Park Solid Village
Bulrush           728           0             4      1    2   13     0       1
Burned Area         0         747             0      0    0    0     2       0
Degraded Lawn       0           0           711      0    5   11     6      15
Forest              0           0             0    719    0   30     0       1
Lawn                0           0             2      0  741    0     4       0
Park                5           0            11     33    1  690     1       8
Solid               0           2            23      0    2    4   694       9
Village             0           0             8      0    0   15    10     716
Wasteland           0           1             7      0    6    3     1       0
Water               2           0             2      0    0    3     3       0
              Wasteland Water class.error
Bulrush               0     1  0.02933333
Burned Area           1     0  0.00400000
Degraded Lawn         2     0  0.05200000
Forest                0     0  0.04133333
Lawn                  3     0  0.01200000
Park                  1     0  0.08000000
Solid                14     2  0.07466667
Village               1     0  0.04533333
Wasteland           732     0  0.02400000
Water                 0   740  0.01333333
#Виконуємо класифікацію для всієї території Кривого Рогу та фіксуємо час виконання
beginCluster()
4 cores detected, using 3
system.time(Gazon_RF <- clusterR(ImageStack, raster::predict, args = list(model = RF_model)))
   user  system elapsed 
  7.048   1.028  82.363 
endCluster()
#Дивимось інформацію щодо інформативності предикторів
varImpPlot(RF_model, sort = TRUE, main = "Інформативність предикторів")

#Дивимось інформацію про кількість росщеплення дерев на кожному з предикторів
varUsed(RF_model, by.tree = FALSE, count = TRUE)
[1] 13583 13165 11998 15378 17419 20454 17266 16073 14810
#Записуємо відокремлений растровий файл з результатами класифікації
writeRaster(Gazon_RF, "/home/geka/Documents/RPubs/Gazon/Gazon20170808_NDVI.sdat", format = "SAGA", overwrite = TRUE)
#Перевіряємо результат класифікації на "тестовій" вибірці
Gazon_DF.predict <- predict(RF_model, LearnDF)
table(Gazon_DF.predict, LearnDF$Class)
                
Gazon_DF.predict Bulrush Burned Area Degraded Lawn Forest Lawn Park Solid Village
   Bulrush           688           0             0      0    0    7     0       1
   Burned Area         0         580             0      0    0    0     5       0
   Degraded Lawn       3           0           445      0    2   13   108      23
   Forest              1           0             0   1083    0   31     0       0
   Lawn                1           0             1      0  317    0    36       0
   Park                8           0             6     39    0  890    35      61
   Solid               0           0             1      0    1    0  3973      13
   Village             0           0             6      2    0   15    59    1980
   Wasteland           0           1             0      1    2    0   101       2
   Water               0           0             0      0    0    0    18       0
                
Gazon_DF.predict Wasteland Water
   Bulrush               0     4
   Burned Area           0     0
   Degraded Lawn        12     2
   Forest                0     0
   Lawn                 13     2
   Park                  1    10
   Solid                 7     4
   Village               0     0
   Wasteland           935     0
   Water                 0  1542
#Обчислюємо похибку класифікації на "тестовій"" вибірці
sum(diag(table(Gazon_DF.predict, LearnDF$Class)))/nrow(LearnDF)
[1] 0.9497365
#!!!слово "тестова" я навмисно взяв у лапки. Це не чиста тестова вибірка. Через малу кількість об'єктів для деяких класів ми штучно взяли ці об'єкти по 2-3-5 разів. Тому ймовірно, що для ця цих класів у "тестовій" вибірці не залишилось унікальних об'єктів

Подальша робота з отриманим файлом класифікованих типів території виконується в середовищі геоінформаційних систем. Зокрема, я використав базові можливості ГІС SAGA для проведення згладжувальної фільтрації отриманого растру класифікації. При цьому було усунено одно-двопіксельні “артефакти”. Для обчислення площ та іншої базової статистики я також використав ГІС SAGA. Для “поліграфічного” оформлення результатів я використовую базові можливості ГІС QGIS.

Стосовно загальної точності моделі: в загальних рисах доволі точно повторює особливості міської території: райони приватного сектору, окремі висотні будинки, зарослі очеретів. Трапляються поодинокі місця, які знаходяться у затінку як для оптичних і радарних супутників, так і для Сонця. Ці місця часто розпізнаються як “Вода”. Особливо це стосується східних бортів деяких великих криворізьких кар’єрів. Також спостерігаються пікселі класу “Очерети” у зовсім нехарактерних місцях. Скоріше за все — це щільна кустарникова рослинність. Це потребує перевірки на місцевості.

LS0tCnRpdGxlOiAi0JrQu9Cw0YHQuNGE0ZbQutCw0YbRltGPINC70LDQvdC00YjQsNGE0YLQvdC+0LPQviDQv9C+0LrRgNC40LLRgyDQtyDQstC40LrQvtGA0LjRgdGC0LDQvdC90Y/QvCDQsNC70LPQvtGA0LjRgtC80YMgUmFuZG9tIEZvcmVzdCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoK0J7RgdC90L7QstC90L7RjiDQvNC10YLQvtGOINC00L7RgdC70ZbQtNC20LXQvdC90Y8g0LHRg9C70L4g0LLQuNC30L3QsNGH0LjRgtC4INCyINC80LXQttCw0YUg0JrRgNC40LLQvtCz0L4g0KDQvtCz0YMg0LfQsNCz0LDQu9GM0L3RgyDQv9C70L7RidGDINGC0LXRgNC40YLQvtGA0ZbRlywg0Y/QutGDINC/0L7QutGA0LjRgtC+ICLQttC40LLQvtGOIiAo0L3QtSDQt9Cw0YHQvtGF0LvQvtGOKSDRgtGA0LDQstC+0Y4uINCU0LvRjyDRhtGM0L7Qs9C+INGD0L/RgNC+0LTQvtCy0LYg0LTQtdC60ZbQu9GM0LrQvtGFINC00L3RltCyINGPINCy0ZbQt9GD0LDQu9GM0L3QviDQstC40LLRh9Cw0LIg0YTQsNC60YLQuNGH0L3QuNC5INGB0YLQsNC9INCz0LDQt9C+0L3RltCyLCDRgdC60LLQtdGA0ZbQsiwg0L/Rg9GB0YLQuNGA0ZbQsiDRgtCwINGW0L3RiNC40YUg0L7QsSfRlNC60YLRltCyINGDINCa0YDQuNCy0L7QvNGDINCg0L7Qt9GWLiDQn9GW0LQg0YfQsNGBINCy0ZbQt9GD0LDQu9GM0L3QvtCz0L4g0L7QsdGB0YLQtdC20LXQvdC90Y8g0YHRgtCw0LvQviDQt9GA0L7Qt9GD0LzRltC70LjQvCwg0YnQviDQu9C40YjQtSDQvNCw0LvQsCDRh9Cw0YHRgtC60LAg0LLQu9Cw0YHQvdC1INCz0LDQt9C+0L3RltCyINC/0YDQtdC00YHRgtCw0LLQu9C10L3QsCAi0LbQuNCy0L7RjiIg0YLRgNCw0LLQvtGOLiDQkdGW0LvRjNGI0ZbRgdGC0Ywg0L/QtdGA0LXQs9C70Y/QvdGD0YLQuNGFINC80L3QvtGOINC00ZbQu9GP0L3QvtC6INC90LXRgdC70Lgg0LDQsdC+INCy0LXQu9GM0LzQuCDQv9GA0LjQs9C90ZbRh9C10L3Rgywg0LDQsdC+INCy0LfQsNCz0LDQu9GWINCy0LjRgdC+0YXQu9GDINGC0YDQsNCyJ9GP0L3RgyDRgNC+0YHQu9C40L3QvdGW0YHRgtGMLiDQotCw0LrQvtC2INGB0YLQsNC70L4g0LfRgNC+0LfRg9C80ZbQu9C40LwsINGJ0L4g0LIg0L7QutGA0LXQvNC40Lkg0LrQu9Cw0YEg0YLQtdC+0YDRltGC0L7RgNGW0Zcg0YLRgNC10LHQsCDQstC40L3QtdGB0YLQuCDQv9GD0YHRgtC40YDRli4g0J/Rg9GB0YLQuNGA0ZYg0LfQtNC10LHRltC70YzRiNC+0LPQviDQstC60YDQuNGC0ZYg0LDQsdC+INCx0LDQs9Cw0YLQvtGA0ZbRh9C90LjQvNC4INCy0LjRgdC+0LrQvtGA0L7RgdC70LjQvNC4INGC0YDQsNCy0LDQvNC4ICjQtNC10YDQtdCy0ZbQuSwg0L/QvtC70LjQvSwg0LDQvNCx0YDQvtC30ZbRjyDRgtC+0YnQviksINCw0LHQviDRgdGD0LzRltGI0YjRjiDQvdC40LfRjNC60L7RgNC+0YHQu9C40YUg0YLQsCDQstC40YHQvtC60L7RgNC+0YHQu9C40YUg0YLRgNCw0LIuINCi0LDQutC+0LYg0LzQsNC50LbQtSDQstGB0Y7QtNC4INGC0YDQsNC/0LvRj9GO0YLRjNGB0Y8g0L/QvtC+0LTQuNC90L7QutGWINC00LXRgNC10LLQsC4KCtCn0LXRgNC10Lcg0LzQsNC70YMg0LrRltC70YzQutGW0YHRgtGMICLQttC40LLQuNGFIiDQs9Cw0LfQvtC90ZbQsiDQtNC+0LLQtdC70L7RgdGMINGC0YDQvtGF0Lgg0LfQvNGW0YHRgtC40YLQuCDRgNGW0LLQtdC90Ywg0LTQtdGC0LXQutGC0YPQstCw0L3QvdGPINC60LvQsNGB0YMgItCz0LDQt9C+0L0iLiDQn9GW0LQg0YfQsNGBINC60LvQsNGB0LjRhNGW0LrQsNGG0ZbRlyDQtNC+ICLQs9Cw0LfQvtC90ZbQsiIg0LzQvdC+0Y4g0LHRg9C70L4g0LLRltC00L3QtdGB0LXQvdC+INC90LUg0LvQuNGI0LUg0LLQu9Cw0YHQvdC1ICLQs9Cw0LfQvtC90LgiLCDQsCDQuSDQsdGD0LTRjC3Rj9C60ZYg0LTRltC70Y/QvdC60Lgg0Lcg0L3QuNC30YzQutC+0YDQvtGB0LvQuNC80LggItC20LjQstC40LzQuCIg0YLRgNCw0LLQsNC80LggLSDQtNC+0LPQu9GP0L3Rg9GC0ZYg0YHRgtCw0LTRltC+0L3QuCwg0YfQsNGB0YLQuNC90Lgg0L/Rg9GB0YLQuNGA0ZbQsiwg0LTRltC70Y/QvdC60Lgg0YHRg9GF0L7QtNC+0LvRgyDQsiDQsdC10LfQv9C+0YHQtdGA0LXQtNC90ZbQuSDQsdC70LjQt9GM0LrQvtGB0YLRliDQtNC+INCy0L7QtNC90LjRhSDQvtCxJ9GU0LrRgtGW0LIuCgrQl9Cw0LPQsNC70L7QvCDQsiDRgNCw0LzQutCw0YUg0LTQvtGB0LvRltC00LbQtdC90L3RjyDQsdGD0LvQviDQstC40LTRltC70LXQvdC+IDEwINGD0LzQvtCy0L3QuNGFINC60LvQsNGB0ZbQsjoKCi0gItCe0YfQtdGA0LXRgtC4IiAoKipCdWxydXNoKiopIC0tLSDRgdGD0YbRltC70YzQvdGWINC30LDRgNC+0YHRgtGWINC+0YfQtdGA0LXRgtGW0LI7CgotICLQktC40L/QsNC70LXQvdCwINGC0LXRgNC40YLQvtGA0ZbRjyIgKCoqQnVybmVkIEFyZWEqKikgLS0tINC00ZbQu9GP0L3QutC4INCy0LjQs9C+0YDRltC70L7RlyDQstC90LDRgdC70ZbQtNC+0Log0L/QvtC20LXQtiDRgNC+0YHQu9C40L3QvdC+0YHRgtGWOwoKLSAi0JTQtdCz0YDQsNC00L7QstCw0L3QuNC5INCz0LDQt9C+0L0iICgqKkRlZ3JhZGVkIEFyZWEqKikgLS0tINC00ZbQu9GP0L3QutC4INC90LjQt9GM0LrQvtGC0YDQsNCyJ9GPINC3INC/0YDQuNCz0L3RltGH0LXQvdC+0Y4gKNCy0LjRgdC+0YXQu9C+0Y4pINGA0L7RgdC70LjQvdC90ZbRgdGC0Y47CgotICLQm9GW0YEiICgqKkZvcmVzdCoqKSAtLS0g0YnRltC70YzQvdGWINC70ZbRgdC+0LLRliDQvdCw0YHQsNC00LbQtdC90L3RjyDRltC3INC60YPRidCw0LzQuCDRgtCwINGC0YDQsNCy0LDQvNC4OwoKLSAi0JPQsNC30L7QvSIgKCoqTGF3bioqKSAtLS0g0LTRltC70Y/QvdC60Lgg0L3QuNC30YzQutC+0YLRgNCw0LIn0Y8g0Lcg0L3QvtGA0LzQsNC70YzQvdC+0Y4gKCLQttC40LLQvtGOIikg0YDQvtGB0LvQuNC90L3RltGB0YLRjjsKCi0gItCf0LDRgNC6IiAoKipQYXJrKiopIC0tLSDQtNGW0LvRj9C90LrQuCDQtyDQvdC10YnRltC70YzQvdC40LzQuCDQvdCw0YHQsNC00LbQtdC90L3Rj9C80Lgg0LTQtdGA0LXQsi4g0JIg0Y/QutC+0YHRgtGWINC10YLQsNC70L7QvdGDINGG0YzQvtCz0L4g0LrQu9Cw0YHRgyDQstC30Y/RgtC+INC80ZbRgdGM0LrRliDQv9Cw0YDQutC4INGC0LAg0YHQutCy0LXRgNC4OwoKLSAi0KLQstC10YDQtNCwINC/0L7QstC10YDRhdC90Y8iICgqKlNvbGlkKiopIC0tLSDQsdGD0LTRjC3Rj9C60ZYg0YLQstC10YDQtNGWINC/0L7QstC10YDRhdC90ZY6INC00L7RgNC+0LPQuCwg0LHQtdGC0L7QvSwg0L7Qs9C+0LvQtdC90ZYg0pHRgNGD0L3RgtC4LCDRgdC60LXQu9GM0L3RliDQv9C+0YDQvtC00LgsINCw0L3RgtGA0L7Qv9C+0LPQtdC90L3RliDQvtCxJ9GU0LrRgtC4ICjQutCw0YAn0ZTRgNC4LCDQstGW0LTQstCw0LvQuCwg0YXQstC+0YHRgtC+0YHRhdC+0LLQuNGJ0LAg0YLQvtGJ0L4pLiDQotC+0LHRgtC+INC00L4g0YbRjNC+0LPQviDQutC70LDRgdGDINGD0LLRltC50YjQu9C4INCy0YHRliDRgtC40L/QuCDRgtC10YDQuNGC0L7RgNGW0LksINGP0LrRliDQvdC1INC/0L7QutGA0LjRgtGWINGA0L7RgdC70LjQvdC90ZbRgdGC0Y47CgotICLQn9GA0LjQstCw0YLQvdC40Lkg0YHQtdC60YLQvtGAIiAoKipWaWxsYWdlKiopIC0tLSDQstC40LHRltGAINGG0YzQvtCz0L4g0LrQu9Cw0YHRgyDQvtCx0YPQvNC+0LLQu9C10L3QuNC5INGB0L/QtdGG0LjRhNGW0YfQvdGW0YHRgtGOINC+0LTQvdC+0L/QvtCy0LXRgNGF0L7QstC+0Zcg0LzRltGB0YzQutC+0Zcg0LfQsNCx0YPQtNC+0LLQuDog0YnRltC70YzQvdCwINGB0YPQvNGW0Ygg0LTQtdGA0LXQsiwg0YLRgNCw0LIsINGC0LLQtdGA0LTQuNGFINC/0L7QstC10YDRhdC+0L3RjCwg0L/QvtCx0YPQtNC+0LIg0YLQsCDRgdC/0L7RgNGD0LQuINCi0LDQutCwINGB0YPQvNGW0Ygg0YDRltC30L3QvtGA0L7QtNC90LjRhSDQvtCxJ9GU0LrRgtGW0LIg0YMg0LzQtdC20LDRhSDQvtC00L3QvtCz0L4g0L/RltC60YHQtdC70Y4g0LrQvtGB0LzRltGH0L3QvtCz0L4g0LfQvdGW0LzQutGDINC/0YDQuNC30LLQvtC00LjRgtGMINC00L4g0YHQv9C10YbQuNGE0ZbRh9C90L7Qs9C+INCy0ZbQtNCx0LjRgtGC0Y8g0Y/QuiDQvtC/0YLQuNGH0L3QuNGFINGC0LDQuiDRliDRgNCw0LTQsNGA0L3QuNGFINGF0LLQuNC70YwuINCf0YDQvtGC0LUg0L3QtSDRgdC70ZbQtCDQt9Cw0LHRg9Cy0LDRgtC4LCDRidC+INGG0LXQuSDQutC70LDRgSAtINC/0YDQvtGB0YLQviDRg9C80L7QstC90LAg0YHQuNGC0YPQsNGG0ZbRjy4g0J7QsSfRlNC60YLQuCDRhtGM0L7Qs9C+INC60LvQsNGB0YMg0YTRltC60YHRg9GO0YLRjNGB0Y8g0Lkg0L/QvtC30LAg0LzQtdC20LDQvNC4INC80LDQu9C+0L/QvtCy0LXRgNGF0L7QstC+0Zcg0L/RgNC40LLQsNGC0L3QvtGXINC30LDQsdGD0LTQvtCy0LgsINC90LDQv9GA0LjQutC70LDQtCAtLS0g0LTQstC+0YDQuCDQstGB0LXRgNC10LTQuNC90ZYg0LrQstCw0YDRgtCw0LvRltCyINCx0LDQs9Cw0YLQvtC/0L7QstC10YDRhdC+0LLQvtGXINC30LDQsdGD0LTQvtCy0LguINCi0LDQvCDRgtCw0LrQvtC2INC/0YDQuNGB0YPRgtC90Y8g0YnRltC70YzQvdCwINGB0YPQvNGW0Ygg0LTQtdGA0LXQsiwg0LPQsNC30L7QvdGW0LIg0YLQsCDQsdGD0LTRltCy0LXQu9GM0L3QuNGFINC80LDRgtC10YDRltCw0LvRltCyLgoKLSAi0J/Rg9GB0YLQuNGAIiAoKipXYXN0ZWxhbmQqKikgLS0tINC00ZbQu9GP0L3QutC4INGW0Lcg0YHRg9C80ZbRiNGI0Y4g0L3QuNC30YzQutC+0YDQvtGB0LvQuNGFINGC0LAg0LLQuNGB0L7QutC+0YDQvtGB0LvQuNGFINGC0YDQsNCyINGWINC/0L7QvtC00LjQvdC+0LrQuNGFINC00LXRgNC10LI7CgotICLQktC+0LTQsCIgKCoqV2F0ZXIqKikgLS0tINCx0YPQtNGMLdGP0LrRliDQstGW0LTQutGA0LjRgtGWINCy0L7QtNC90ZYg0L/QvtCy0LXRgNGF0L3RliAo0YDRltC60LgsINGB0YLQsNCy0LrQuCwg0LLRltC00YHRgtGW0LnQvdC40LrQuCwg0YXQstC+0YHRgtC+0YHRhdC+0LLQuNGJ0LAg0YLQvtGJ0L4pCgrQkiDRj9C60L7RgdGC0ZYg0L/QtdGA0LLQuNC90L3QuNGFINC80LDRgtC10YDRltCw0LvRltCyINCy0LfRj9GC0L46CgotINC30L3RltC80L7QuiDQutC+0YHQvNGW0YfQvdC+0LPQviDQsNC/0LDRgNCw0YLRgyBTZW50aW5lbC0xQSDQstGW0LQgNyDRgdC10YDQv9C90Y8gMjAxNyDRgNC+0LrRgyDQutC70LDRgdGDIEdyb3VuZCBSYW5nZSBEZXRlY3RlZCAoR1JEKTsKCi0g0LrQsNC90LDQu9C4IDIsIDMsIDQsIDgg0YLQsCAxMiDQt9C90ZbQvNC60YMg0LrQvtGB0LzRltGH0L3QvtCz0L4g0LDQv9Cw0YDQsNGC0YMgU2VudGluZWwtMkEg0LLRltC0IDgg0YHQtdGA0L/QvdGPIDIwMTcg0YDQvtC60YM7CgotINC60LDQvdCw0LsgMTAg0LrQvtGB0LzRltGH0L3QvtCz0L4g0LDQv9Cw0YDQsNGC0YMgTGFuZHNhdC04INCy0ZbQtCAxMiDRgdC10YDQv9C90Y8gMjAxNyDRgNC+0LrRgy4KCtCU0LvRjyDRgNCw0LTQsNGA0L3QvtCz0L4g0LfQvdGW0LzQutGDINCx0YPQu9C+INC/0YDQvtCy0LXQtNC10L3QviDRgNCw0LTRltC+0LzQtdGC0YDQuNGH0L3RgyDQutC+0YDQtdC60YbRltGOLCDRhNGW0LvRjNGC0YDQsNGG0ZbRjiDRgdC/0LXQutC7LdGI0YPQvNGDICjQtyDQstC40LrQvtGA0LjRgdGC0LDQvdC90Y/QvCDRhNGW0LvRjNGC0YDRgyBHYW1tYSBNYXApLCDQvtCx0YfQuNGB0LvQtdC90L3RjyDRgdC40LvQuCDQstGW0LTQsdC40YLRgtGPINCyINC00LXRhtC40LHQtdC70LDRhSDRgtCwINCz0LXQvtC80LXRgtGA0LjRh9C90YMg0LrQvtGA0LXQutGG0ZbRjiAoUmFuZ2UtRG9wcGxlcikuINCU0LvRjyDRhtGM0L7Qs9C+INCx0YPQu9C+INCy0LjQutC+0YDQuNGB0YLQsNC90L4g0L/RgNC+0LPRgNCw0LzQvdC40Lkg0LfQsNGB0ZbQsSBTTkFQIChTZW50aW5lbCBBcHBsaWNhdGlvbiBQbGF0Zm9ybSkuCgrQl9C90ZbQvNC60Lgg0L7Qv9GC0LjRh9C90LjRhSDRgdGD0L/Rg9GC0L3QuNC60ZbQsiDQvtCx0YDQvtCx0LvQtdC90L4g0Lcg0LLQuNC60L7RgNC40YHRgtCw0L3QvdGP0Lwg0LzQvtC00YPQu9GOIFNlbWktQXV0b21hdGljIENsYXNzaWZpY2F0aW9uIFBsdWdpbiDQs9C10L7RltC90YTQvtGA0LzQsNGG0ZbQudC90L7RlyDRgdC40YHRgtC10LzQuCBRR0lTLgoK0J/RltGB0LvRjyDQv9GW0LTQs9C+0YLQvtCy0YfQvtCz0L4g0L7QsdGA0L7QsdC70LXQvdC90Y8g0LLRgdGWINC30L3RltC80LrQuCDQsdGD0LvQviDQt9Cw0LLQsNC90YLQsNC20LXQvdC+INCyINCz0LXQvtGW0L3RhNC+0YDQvNCw0YbRltC50L3RgyDRgdC40YHRgtC10LzRgyBTQUdBLiDQotCw0Lwg0LLQuNC60L7QvdCw0L3QviDQvtCx0YDRltC30LDQvdC90Y8g0L/QviDQutC+0L3RgtGD0YDRgyDQmtGA0LjQstC+0LPQviDQoNC+0LPRgyAo0LrQvtC90YLRg9GAINCy0LfRj9GC0L4g0Lcg0L/RgNC+0LXQutGC0YMgT3BlblN0cmVldE1hcCksINC/0YDQuNCy0LXQtNC10L3QvdGPINCy0YHRltGFINC30L3RltC80LrRltCyINC00L4g0YDQvtC30LzRltGA0YMg0L/RltC60YHQtdC70Y4gMTDQpTEwINC8LiDQn9GA0Lgg0YbRjNC+0LzRgyDQsiDRj9C60L7RgdGC0ZYgItC+0YHQvdC+0LLQuCIg0LLQuNC60L7RgNC40YHRgtC+0LLRg9Cy0LDQstGB0Y8g0LfQvdGW0LzQvtC6INC60L7RgdC80ZbRh9C90L7Qs9C+INCw0L/QsNGA0LDRgtGDIFNlbnRpbmVsLTJBLgoK0JTQu9GPINGE0L7RgNC80YPQstCw0L3QvdGPINC90LDQstGH0LDQu9GM0L3QvtGXINCy0LjQsdGW0YDQutC4INC+0LEn0ZTQutGC0ZbQsiDQt9GA0L7QsdC70LXQvdC+INGB0LXQs9C80LXQvdGC0LDRhtGW0Y4g0LTQvtGB0LvRltC00LbRg9Cy0LDQvdC+0Zcg0YLQtdGA0LjRgtC+0YDRltGXINCx0LDQt9C+0LLQuNC8INC80LXRgtC+0LTQvtC8IE9iamVjdCBCYXNlZCBJbWFnZSBTZWdtZW50YXRpb24g0LPQtdC+0ZbQvdGE0L7RgNC80LDRhtGW0LnQvdC+0Zcg0YHQuNGB0YLQtdC80LggU0FHQS4g0J/QvtGC0ZbQvCDQvdC10LfQvdCw0YfQvdGW0Lkg0YfQsNGB0YLQuNC90ZYg0L/QvtC70ZbQs9C+0L3RltCyINC+0YLRgNC40LzQsNC90L7Qs9C+INGI0LXQudC/LdGI0LDRgNGDINC/0YDQuNC/0LjRgdCw0L3QviDQvtC30L3QsNGH0LXQvdGWINCy0LjRidC1INC60LvQsNGB0Lgg0YLQtdGA0LjRgtC+0YDRltGXLiDQl9Cw0LPQsNC70L7QvCDQsdGD0LvQviDQv9GA0L7QutC70LDRgdC40YTRltC60L7QstCw0L3QviA0NjEg0L/QvtC70ZbQs9C+0L0uINCa0ZbQu9GM0LrRltGB0YLRjCDQt9CwINC60LvQsNGB0LDQvNC4INC30L3QsNGH0L3QviDRgNGW0LfQvdC40YLRjNGB0Y8uCgrQndCw0YHRgtGD0L/QvdGWINC60YDQvtC60Lgg0LLQuNC60L7QvdGD0LLQsNC70LjRgdGMINCyINGB0LXRgNC10LTQvtCy0LjRidGWINC80L7QstC4INC/0YDQvtCz0YDQsNC80YPQstCw0L3QvdGPIFIuINCd0LjQttGH0LUg0LzRltGB0YLQuNGC0YzRgdGPINGB0LrRgNC40L/Rgiwg0Y/QutC40Lwg0LLQuNC60L7QvdGD0LLQsNCy0YHRjyDQstC10YHRjCDQv9GA0L7RhtC10YEg0L7QsdGH0LjRgdC70LXQvdC90Y8g0YLQsCDQv9C10YDQtdCy0ZbRgNC60Lgg0LTQsNC90LjRhS4KCmBgYHtyfQoj0JfQsNCy0LDQvdGC0LDQttGD0ZTQvNC+INC/0L7RgtGA0ZbQsdC90ZYg0L/QsNC60LXRgtC4CmxpYnJhcnkocGx5cikj0YHQsNC80LUg0LIg0YLQsNC60ZbQuSDQv9C+0YHQu9GW0LTQvtCy0L3QvtGB0YLRljog0YHQv9C+0YfQsNGC0LrRgyBwbHlyLCDQv9C+0YLRltC8IGRwbHlyIQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJhc3RlcikKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCmBgYHtyfQoj0JL