Как мне перебрать каждую ячейку в непрерывном растре?


13

Смотрите эту ссылку для более подробной информации.

Проблема:

Я хочу перебрать непрерывный растр (тот, у которого нет таблицы атрибутов), ячейка за ячейкой и получить значение ячейки. Я хочу взять эти значения и выполнить для них условия, эмулируя шаги алгебры карты, подробно описанные ниже, без фактического использования растрового калькулятора.

В ответ на запрос комментариев ниже, я добавил подробности, предоставив предысторию проблемы и обосновав необходимость реализации метода как такового в разделе ниже под названием «Необходим анализ:».

Анализ, предложенный ниже, хотя и имеет отношение к моей проблеме, предоставляя справочную информацию, не нуждается в реализации в ответе. Объем вопроса касается только итерации непрерывного растра для получения / установки значений ячеек.

Необходим анализ:

Если выполняется ЛЮБОЕ из следующих условий, присвойте выходной ячейке значение 1. Только присвойте выходной ячейке значение 0, если ни одно из условий не выполнено.

Условие 1: если значение ячейки больше, чем верхняя и нижняя ячейки, укажите значение 1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

Где файл ядра выглядит так:

3 3 
0 1 0
0 0 0
0 1 0

Условие 2: если значение ячейки больше, чем у левой и правой ячейки, укажите значение 1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

Где файл ядра выглядит так:

3 3 
0 0 0
1 0 1
0 0 0  

Условие 3: если значение ячейки больше, чем у верхнего и нижнего ячеек, укажите значение 1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

Где файл ядра выглядит так:

3 3 
1 0 0
0 0 0
0 0 1 

Условие 4: Если значение ячейки больше, чем у нижнего левого и верхнего правого ячеек, задайте значение 1:

Con("raster" > FocalStatistics("raster", NbrIrregular("C:\filepath\kernel_file.txt"), "MAXIMUM"), 1, 0)

Где файл ядра выглядит так:

3 3 
0 0 1
0 0 0
1 0 0 

Условие 5: если любая из соседних ячеек имеет значение EQUAL для центральной ячейки, присвойте выходному растру значение 1 ( используя фокусное многообразие с двумя расчетами ближайших окрестностей )

Почему бы не использовать алгебру карт?

Ниже было отмечено, что моя проблема может быть решена с помощью алгебры карт, но, как видно выше, это всего шесть вычислений растра, плюс один для объединения всех растров, созданных вместе. Мне кажется, что гораздо эффективнее переходить от ячейки к ячейке и выполнять все сравнения сразу в каждой ячейке, вместо того, чтобы перебирать каждую по отдельности семь раз и использовать довольно много памяти для создания семи растров.

Как решить проблему?

Приведенная выше ссылка рекомендует использовать интерфейс IPixelBlock, однако из документации ESRI неясно, осуществляете ли вы доступ к самому значению одной ячейки через IPixelBlock, или если вы обращаетесь к нескольким значениям ячеек из размера установленного IPixelBlock. Хороший ответ должен предложить метод доступа к значениям ячеек непрерывного растра и дать объяснение методологии, лежащей в основе кода, если это не очевидно.

В итоге:

Каков наилучший метод для обхода каждой ячейки в НЕПРЕРЫВНОМ растре (у которого нет таблицы атрибутов ) для доступа к значениям ее ячеек?

Хороший ответ не должен реализовывать шаги анализа, описанные выше, он должен только предоставить методологию для доступа к значениям ячеек растра.


4
Почти всегда нет необходимости перебирать каждую ячейку в растре. Можете ли вы предоставить больше информации о том, что вы пытаетесь сделать?
user2856 23.09.13

2
@Luke верен: безусловно, лучший способ выполнить итеративное растровое вычисление в любой ГИС - это избегать явного зацикливания ячеек, потому что все циклы, которые должны быть выполнены, уже оптимизированы. Вместо этого ищите способ использовать функциональность алгебры карт, предоставляемую ГИС, если это вообще возможно. Если бы вы описали свой анализ, вы могли бы получить полезные ответы, использующие такой подход.
whuber

@Luke Я добавил подробности анализа.
Конор

1
Спасибо за разъяснения, Конор. Я согласен с тем, что если ваша ГИС требует значительных накладных расходов при каждом расчете растра, написание собственного цикла может быть более эффективным. Из любопытства, какова предполагаемая интерпретация этого (необычного) набора условий?
whuber

1
@whuber Это для операций по обнаружению краев для создания векторных многоугольников из моего растра. Приложение концептуально аналогично идентификации гидрологических бассейнов из матрицы высот (представьте, что центральная ячейка в статистике окрестностей, упомянутой выше, как «пик», с которого вода будет течь вниз по склону), но находится вне области гидрологии. Ранее я использовал для этого Flow Direction и Basin Rasters, но в моем конечном анализе они подвержены ошибкам из-за того, что свойства этих методов не совсем то, что мне нужно.
Конор

Ответы:


11

Я вижу, что это уже было решено с помощью Original Poster (OP), но я опубликую простое решение на python на тот случай, если кто-нибудь в будущем заинтересуется различными способами решения этой проблемы. Я неравнодушен к программному обеспечению с открытым исходным кодом, поэтому вот решение, использующее GDAL в python:

import gdal

#Set GeoTiff driver
driver = gdal.GetDriverByName("GTiff")
driver.Register()

#Open raster and read number of rows, columns, bands
dataset = gdal.Open(filepath)
cols = dataset.RasterXSize
rows = dataset.RasterYSize
allBands = dataset.RasterCount
band = dataset.GetRasterBand(1)

#Get array of raster cell values.  The two zeros tell the 
#iterator which cell to start on and the 'cols' and 'rows' 
#tell the iterator to iterate through all columns and all rows.
def get_raster_cells(band,cols,rows):
    return band.ReadAsArray(0,0,cols,rows)

Реализуйте функцию так:

#Bind array to a variable
rasterData = get_raster_cells(band,cols,rows)

#The array will look something like this if you print it
print rasterData
> [[ 1, 2, 3 ],
   [ 4, 5, 6 ],
   [ 7, 8, 9 ]]

Затем выполните итерацию ваших данных с помощью вложенного цикла:

for row in rasterData:
    for val in row:
        print val
> 1
  2
  3
  4...

Или, может быть, вы хотите сгладить двумерный массив с помощью понимания списка:

flat = [val for row in rasterData for val in row]

В любом случае, при переборе данных по ячейкам можно добавлять некоторые условия в ваш цикл для изменения / редактирования значений. Посмотрите этот сценарий, который я написал для разных способов доступа к данным: https://github.com/azgs/hazards-viewer/blob/master/python/zonal_stats.py .


Мне нравится простота и элегантность этого решения. Я собираюсь подождать еще несколько дней, и, если никто не найдет решение такого же или более высокого качества, я добавлю теги, чтобы расширить суть вопроса на благо сообщества и наградить вас за вознаграждение.
Конор

Спасибо @Conor! Мы столкнулись с подобной проблемой на моем рабочем месте ранее на этой неделе, поэтому я решил ее, написав класс с использованием GDAL / python. В частности, нам нужен был метод на стороне сервера для расчета среднего значения области растра, учитывая только ограничивающий прямоугольник от пользователя в нашем клиентском приложении. Как вы думаете, было бы полезно, если бы я добавил остальную часть класса, который я написал?
asonnenschein

Было бы полезно добавить код, показывающий, как читать полученный двумерный массив и редактировать его значения.
Конор

9

Обновить! Numpy решение:

import arcpy
import numpy as np

in_ras = path + "/rastername"

raster_Array = arcpy.RasterToNumPyArray(in_ras)
row_num = raster_Array.shape[0]
col_num = raster_Array.shape[1]
cell_count = row_num * row_num

row = 0
col = 0
temp_it = 0

while temp_it < cell_count:
    # Insert conditional statements
    if raster_Array[row, col] > 0:
        # Do something
        val = raster_Array[row, col]
        print val
    row+=1
    if col > col_num - 1:
        row = 0
        col+=1

Таким образом, получение готового массива обратно в растр с использованием arcpy является проблематичным. arcpy.NumPyArrayToRaster является коротким и имеет тенденцию переопределять экстенты, даже если вы передаете ему свои координаты LL.

Я предпочитаю сохранить как текст.

np.savetxt(path + "output.txt", output, fmt='%.10f', delimiter = " ")

Я использую Python как 64-битный для скорости - на данный момент это означает, что я не могу передать numpy.savetxt заголовок. Поэтому мне нужно открыть вывод и добавить заголовок ASCII, который хочет Arc, прежде чем конвертировать ASCII в растр

File_header = "NCOLS xxx" + '\n'+ "NROWS xxx" + '\n' + "XLLCORNER xxx"+'\n'+"YLLCORNER xxx"+'\n'+"CELLSIZE xxx"+'\n'+"NODATA_VALUE xxx"+'\n'

Numpy версия запускает мой сдвиговый растр, умножения и сложения намного быстрее (1000 итераций за 2 минуты), чем arcpy версия (1000 итераций за 15 минут)

Старая версия Я могу удалить это позже, я просто написал похожий сценарий. Я пытался конвертировать в точки и использовать курсор поиска. Я получил только 5000 итераций за 12 часов. Итак, я искал другой путь.

Мой способ сделать это - перебрать координаты центра ячейки каждой ячейки. Я начинаю в верхнем левом углу и двигаюсь справа налево. В конце ряда я опускаюсь вниз и снова начинаю слева. У меня есть 240-метровый растр с 2603 столбцами и 2438 строками, так что всего 6111844 ячеек. Я использую переменную итератора и цикл while. См. ниже

Несколько замечаний: 1 - нужно знать координаты экстента

2 - запустить с точечными координатами для центра ячейки - сдвинуть на 1/2 размера ячейки от значений экстента

3 - Мой сценарий использует значение ячейки, чтобы вытянуть растр, зависящий от значения, а затем сместить этот растр в центр исходной ячейки. Это добавляет нулевой растр для расширения экстента перед добавлением в окончательный растр. Это всего лишь пример. Здесь вы можете поместить свои условные операторы (второй оператор if в цикле while).

4 - Этот сценарий предполагает, что все растровые значения могут быть преобразованы как целые числа. Это означает, что вам нужно сначала избавиться от данных. Con IsNull.

6 - Я до сих пор не доволен этим, и я работаю над тем, чтобы полностью избавиться от этого. Я бы предпочел разыгрывать массивы и делать там математику, а затем возвращать ее в Арк.

ULx = 959415 ## coordinates for the Upper Left of the entire raster 
ULy = 2044545
x = ULx ## I redefine these if I want to run over a smaller area
y = ULy
temp_it = 0

while temp_it < 6111844: # Total cell count in the data extent
        if x <= 1583895 and y >= 1459474: # Coordinates for the lower right corner of the raster
           # Get the Cell Value
           val_result = arcpy.GetCellValue_management(inraster, str(x)+" " +str(y), "1")
           val = int(val_result.getOutput(0))
        if val > 0: ## Here you could insert your conditional statements
            val_pdf = Raster(path + "pdf_"str(val))
            shift_x  =  ULx - x # This will be a negative value
            shift_y = ULy - y # This will be a positive value
            arcpy.Shift_management(val_pdf, path+ "val_pdf_shift", str(-shift_x), str(-shift_y))
            val_pdf_shift = Raster(path + "val_pdf_shift")
            val_pdf_sh_exp = CellStatistics([zeros, val_pdf_shift], "SUM", "DATA")
            distr_days = Plus(val_pdf_sh_exp, distr_days)
        if temp_it % 20000 == 0: # Just a print statement to tell me how it's going
                print "Iteration number " + str(temp_it) +" completed at " + str(time_it)
        x += 240 # shift x over one column
        if x > 1538295: # if your at the right hand side of a row
            y = y-240 # Shift y down a row
            x = 959415 # Shift x back to the first left hand column
        temp_it+=1

distr_days.save(path + "Final_distr_days")

4

Попробуйте использовать IGridTable, ICursor, IRow. Этот фрагмент кода предназначен для обновления значений растровых ячеек, однако он показывает основы итерации:

Как я могу добавить новое поле в таблицу атрибутов растра и пройти по нему?

Public Sub CalculateArea(raster As IRaster, areaField As String)
    Dim bandCol As IRasterBandCollection
    Dim band As IRasterBand

    Set bandCol = raster
    Set band = bandCol.Item(0)

    Dim hasTable As Boolean
    band.hasTable hasTable
    If (hasTable = False) Then
        Exit Sub
    End If    

    If (AddVatField(raster, areaField, esriFieldTypeDouble, 38) = True) Then
        ' calculate cell size
        Dim rstProps As IRasterProps
        Set rstProps = raster

        Dim pnt As IPnt
        Set pnt = rstProps.MeanCellSize

        Dim cellSize As Double
        cellSize = (pnt.X + pnt.Y) / 2#

        ' get fields index
        Dim attTable As ITable
        Set attTable = band.AttributeTable

        Dim idxArea As Long, idxCount As Long
        idxArea = attTable.FindField(areaField)
        idxCount = attTable.FindField("COUNT")

        ' using update cursor
        Dim gridTableOp As IGridTableOp
        Set gridTableOp = New gridTableOp

        Dim cellCount As Long, cellArea As Double

        Dim updateCursor As ICursor, updateRow As IRow
        Set updateCursor = gridTableOp.Update(band.RasterDataset, Nothing, False)
        Set updateRow = updateCursor.NextRow()
        Do Until updateRow Is Nothing
            cellCount = CLng(updateRow.Value(idxCount))
            cellArea = cellCount * (cellSize * cellSize)

            updateRow.Value(idxArea) = cellArea
            updateCursor.updateRow updateRow

            Set updateRow = updateCursor.NextRow()
        Loop

    End If
End Sub

Пройдя по таблице, вы можете получить значение строки определенного поля, используя row.get_Value(yourfieldIndex). Если вы Google

arcobjects row.get_Value

Вы должны быть в состоянии получить множество примеров, показывающих это.

Надеюсь, это поможет.


1
К сожалению, я не заметил, и я отредактирую в своем первоначальном вопросе выше, что мой растр имеет много непрерывных значений, состоящих из больших двойных значений, и поэтому этот метод не будет работать, потому что у моего растра нет значений таблицы атрибутов.
Конор

4

Как насчет радикальной идеи, это потребует от вас программирования на python или ArcObjects.

  1. Конвертируйте вашу сетку в точечный объект класса.
  2. Создайте поля XY и заполните.
  3. Загрузите точки в словарь, где ключ - это строка из X, Y, а item - это значение ячейки.
  4. Пройдите по своему словарю и для каждой точки отработайте 8 XY окружающих ячеек.
  5. Извлеките их из своего словаря и протестируйте с вашими правилами, как только вы найдете истинное значение, вы можете пропустить остальные тесты.
  6. Записать результаты в другой словарь, а затем преобразовать обратно в сетку, сначала создав точечный класс объектов, а затем преобразовав точки в сетку.

2
Преобразовывая в набор точечных объектов, эта идея устраняет два качества представления данных на растровой основе, которые делают его таким эффективным: (1) поиск соседей является чрезвычайно простой операцией с постоянным временем и (2) потому что явное хранение местоположений не требуется, требования к ОЗУ, диску и вводу / выводу минимальны. Таким образом, хотя этот подход будет работать, трудно найти причину, чтобы рекомендовать его.
whuber

Спасибо за ваш ответ Hornbydd. Я в порядке с реализацией метода, подобного этому, но кажется, что шаги 4 и 5 не будут очень эффективными в вычислительном отношении. Мои растры будут иметь минимум 62 500 ячеек (минимальное разрешение для моего растра, которое я установил, составляет 250 ячеек x 250 ячеек, но разрешение может и обычно состоит из гораздо большего), и мне придется выполнять пространственный запрос для каждого условия, чтобы выполнить мои сравнения ... Так как у меня есть 6 условий, это будет 6 * 62500 = 375000 пространственных запросов. Я был бы лучше с алгеброй карт. Но спасибо за этот новый способ просмотра проблемы. Upvoted.
Конор

Разве вы не можете просто преобразовать его в ASCII, а затем использовать такую ​​программу, как R, для выполнения вычислений?
Оливер Бурдекин

Кроме того, у меня есть Java-апплет, который я написал, который можно легко изменить, чтобы он соответствовал вашим условиям. Это был просто алгоритм сглаживания, но обновления было бы довольно легко сделать.
Оливер Бурдекин

Пока программа может вызываться из платформы .NET для пользователя, у которого установлены только .NET Framework 3.5 и ArcGIS 10. Программа с открытым исходным кодом, и я намерен, чтобы они были единственными требованиями к программному обеспечению при доставке конечным пользователям. Если ваш ответ может быть реализован в соответствии с этими двумя требованиями, то он будет считаться действительным ответом. Я добавлю тег версии к вопросу, а также для уточнения.
Конор

2

Решение:

Я решил это ранее сегодня. Код является адаптацией этого метода . Концепция этого не была ужасно сложной, когда я выяснил, что на самом деле делают объекты, используемые для взаимодействия с растром. Приведенный ниже метод принимает два набора входных данных (inRasterDS и outRasterDS). Они оба представляют собой один и тот же набор данных, я просто сделал копию inRasterDS и передал ее в метод как outRasterDS. Таким образом, они оба имеют одинаковый экстент, пространственную привязку и т. Д. Метод считывает значения из inRasterDS, ячейка за ячейкой, и сравнивает их с ближайшими соседями. Он использует результаты этих сравнений в качестве сохраненных значений в outRasterDS.

Процесс:

Я использовал IRasterCursor -> IPixelBlock -> SafeArray, чтобы получить значения пикселей, и IRasterEdit, чтобы записывать новые значения в растр. Когда вы создаете IPixelBlock, вы указываете машине размер и местоположение области, в которую вы хотите читать / записывать. Если вы хотите редактировать только нижнюю половину растра, установите это в качестве параметров IPixelBlock. Если вы хотите зациклить весь растр, вы должны установить IPixelBlock равным размеру всего растра. Я делаю это в методе ниже, передавая размер IRasterCursor (pSize), затем получая PixelBlock от растрового курсора.

Другой ключ заключается в том, что вы должны использовать SafeArray для взаимодействия со значениями в этом методе. Вы получаете IPixelBlock от IRasterCursor, затем SafeArray от IPixelBlock. Затем вы читаете и пишете в SafeArray. Когда вы закончите чтение / запись в SafeArray, запишите весь свой SafeArray обратно в IPixelBlock, затем запишите свой IPixelBlock в IRasterCursor, а затем, наконец, используйте IRasterCursor, чтобы установить местоположение для начала записи, и IRasterEdit, чтобы выполнить саму запись. Этот последний шаг - то, где вы фактически редактируете значения набора данных.

    public static void CreateBoundaryRaster(IRasterDataset2 inRasterDS, IRasterDataset2 outRasterDS)
    {
        try
        {
            //Create a raster. 
            IRaster2 inRaster = inRasterDS.CreateFullRaster() as IRaster2; //Create dataset from input raster
            IRaster2 outRaster = outRasterDS.CreateFullRaster() as IRaster2; //Create dataset from output raster
            IRasterProps pInRasterProps = (IRasterProps)inRaster;
            //Create a raster cursor with a pixel block size matching the extent of the input raster
            IPnt pSize = new DblPnt();
            pSize.SetCoords(pInRasterProps.Width, pInRasterProps.Height); //Give the size of the raster as a IPnt to pass to IRasterCursor
            IRasterCursor inrasterCursor = inRaster.CreateCursorEx(pSize); //Create IRasterCursor to parse input raster 
            IRasterCursor outRasterCursor = outRaster.CreateCursorEx(pSize); //Create IRasterCursor to parse output raster
            //Declare IRasterEdit, used to write the new values to raster
            IRasterEdit rasterEdit = outRaster as IRasterEdit;
            IRasterBandCollection inbands = inRasterDS as IRasterBandCollection;//set input raster as IRasterBandCollection
            IRasterBandCollection outbands = outRasterDS as IRasterBandCollection;//set output raster as IRasterBandCollection
            IPixelBlock3 inpixelblock3 = null; //declare input raster IPixelBlock
            IPixelBlock3 outpixelblock3 = null; //declare output raster IPixelBlock
            long blockwidth = 0; //store # of columns of raster
            long blockheight = 0; //store # of rows of raster

            //create system array for input/output raster. System array is used to interface with values directly. It is a grid that overlays your IPixelBlock which in turn overlays your raster.
            System.Array inpixels; 
            System.Array outpixels; 
            IPnt tlc = null; //set the top left corner

            // define the 3x3 neighborhood objects
            object center;
            object topleft;
            object topmiddle;
            object topright;
            object middleleft;
            object middleright;
            object bottomleft;
            object bottommiddle;
            object bottomright;

            long bandCount = outbands.Count; //use for multiple bands (only one in this case)

            do
            {

                inpixelblock3 = inrasterCursor.PixelBlock as IPixelBlock3; //get the pixel block from raster cursor
                outpixelblock3 = outRasterCursor.PixelBlock as IPixelBlock3;
                blockwidth = inpixelblock3.Width; //set the # of columns in raster
                blockheight = inpixelblock3.Height; //set the # of rows in raster
                outpixelblock3.Mask(255); //set any NoData values

                for (int k = 0; k < bandCount; k++) //for every band in raster (will always be 1 in this case)
                {
                    //Get the pixel array.
                    inpixels = (System.Array)inpixelblock3.get_PixelData(k); //store the raster values in a System Array to read
                    outpixels = (System.Array)outpixelblock3.get_PixelData(k); //store the raster values in a System Array to write
                    for (long i = 1; i < blockwidth - 1; i++) //for every column (except outside columns)
                    {
                        for (long j = 1; j < blockheight - 1; j++) //for every row (except outside rows)
                        {
                            //Get the pixel values of center cell and  neighboring cells

                            center = inpixels.GetValue(i, j);

                            topleft = inpixels.GetValue(i - 1, j + 1);
                            topmiddle = inpixels.GetValue(i, j + 1);
                            topright = inpixels.GetValue(i + 1, j + 1);
                            middleleft = inpixels.GetValue(i - 1, j);
                            middleright = inpixels.GetValue(i + 1, j);
                            bottomleft = inpixels.GetValue(i - 1, j - 1);
                            bottommiddle = inpixels.GetValue(i, j - 1);
                            bottomright = inpixels.GetValue(i - 1, j - 1);


                            //compare center cell value with middle left cell and middle right cell in a 3x3 grid. If true, give output raster value of 1
                            if ((Convert.ToDouble(center) >= Convert.ToDouble(middleleft)) && (Convert.ToDouble(center) >= Convert.ToDouble(middleright)))
                            {
                                outpixels.SetValue(1, i, j);
                            }


                            //compare center cell value with top middle and bottom middle cell in a 3x3 grid. If true, give output raster value of 1
                            else if ((Convert.ToDouble(center) >= Convert.ToDouble(topmiddle)) && (Convert.ToDouble(center) >= Convert.ToDouble(bottommiddle)))
                            {
                                outpixels.SetValue(1, i, j);
                            }

                            //if neither conditions are true, give raster value of 0
                            else
                            {

                                outpixels.SetValue(0, i, j);
                            }
                        }
                    }
                    //Write the pixel array to the pixel block.
                    outpixelblock3.set_PixelData(k, outpixels);
                }
                //Finally, write the pixel block back to the raster.
                tlc = outRasterCursor.TopLeft;
                rasterEdit.Write(tlc, (IPixelBlock)outpixelblock3);
            }
            while (inrasterCursor.Next() == true && outRasterCursor.Next() == true);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(rasterEdit);


        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }

    }

1

Растровые данные AFAIK можно прочитать тремя способами:

  • клеткой (неэффективно);
  • по изображению (достаточно эффективно);
  • по блокам (самый эффективный способ).

Не изобретая колесо, я предлагаю прочитать эти поучительные слайды Криса Гаррарда.

Таким образом, наиболее эффективным методом является чтение данных по блокам, однако это приведет к потере данных в соответствии пикселей, расположенных за границами блоков, при применении фильтра. Таким образом, безопасный альтернативный способ должен состоять в том, чтобы читать все изображение одновременно и использовать простой подход.

Что касается вычислений, вместо этого я должен использовать gdalfilter.py и неявно подход VRT KernelFilteredSource, чтобы применять необходимые фильтры и, прежде всего, избегать тяжелых вычислений.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.