На главную... Продукты | Технологии | Классификаторы | Проекты | Скачать | Цены| Форум | Статьи | Обучение | Контакты

Параллельное считываение рельефа

Поиск  Пользователи  Правила  Войти
Форум » Linux » Средства разработки ГИС-приложений для Linux
Страницы: 1
RSS
Параллельное считываение рельефа, mapGetHeightValue, mapGetHeightArray, QThread
 
Добрый день.

Мы используем Gis Designer версии 11.10.7 под Astra Linux SE 1.4. Среда разработки Qt Creator.
Столкнулись со следующей проблемой. При использовании функций mapGetHeightValue или mapGetHeightArray в не основном потоке часто происходит зависание
потока, либо в последствии возникают наведенные ошибки (Вроде повисания процесса отрисовки карты). При чтении рельефа мы используем мьютексы.
Причем, если теже самые действия выполнять в основном потоке (Где отрисовывается GUI-интерфейс), то все нормально.

Урезали нашу задачу до маленького примера, где подобные глюки возникают всегда, даже если все выполнять все в одном, но не основном потоке. Карта масштаба 1 : 50000 и состоит из 350 листов.
Матрица высот расчитана с дискретом в 50 м. Если не вызыватть функцию mapGetHeightValue, то глюки пропадают.
В связи с этим вопрос: Как осуществлять параллельное считывание рельефа? Поддерживает ли функция mapGetHeightArray работу в множестве потоков?

Прилагаю пример кода:

Заголовочный файл calcMap.h:
Код
#include <QApplication>
#include <QVector>
#include <QList>
#include <QRunnable>
#include <QThread>
#include <QMutex>
#include <ma th.h>

#include <QDebug>

#ifndef MAP_API_H
#include <mapapi.h>
#endif

using namespace std;

class informVyr {
private:

public:
    informVyr() {}
    ~informVyr() {}

    QMutex *hMut_ptr;

    QString pathToMap;
    HMAP hmap;

    double Xstart; // Координаты начала матрицы
    double Ystart;
    double dsk; // дискрет

    QVector <QVector <double> > masIfo;

    void testVyr(int numL, int kolL);
};

// Класс расчета
class calcMap {
private:

public:
    calcMap(QString Mappath);
    ~calcMap();

    informVyr MIV;
};

// Класс параллельной обработки
class parInf : public QThread {
private:
    informVyr *MIV;
    int numL; // Номер среза
    int kolL; // кол-во срезов

public:
    parInf(informVyr *MIV, int numL, int kolL);
    void run();
};


Файл calcMap.cpp:
Код
#include "calcMap.h"

calcMap::calcMap(QString Mappath) {

    QMutex infMut;

    MIV.hMut_ptr = &infMut;
    MIV.pathToMap = Mappath; // Путь к карте
    MIV.hmap = mapOpenMap(Mappath.toStdString().c_str(), GENERIC_READ);
    if (!MIV.hmap)
        return;

    // Путь к матрице высот (Должна лежать в каталоге с картой и иметь такое же имя)
    string pathMtw = Mappath.toStdString();
    pathMtw =  pathMtw.substr(0, pathMtw.length()-4);
    pathMtw += ".mtw";

    qDebug() << "\nПодключение матрицы высот";
    if (!mapOpenMtrForMap( MIV.hmap, pathMtw.c_str(), GENERIC_READ))
        return;

    double dsk = 4000; // Дискрет

    // Расчет кол-ва элементов матрицы
    int cou = mapGetMtrCount(MIV.hmap);
    MTRDESCRIBE desc;
    mapGetMtrDescribe(MIV.hmap, 1, &desc);

    // Граничные точки матрицы 
    double X1 = desc.FrameMeters.X1;
    double Y1 = desc.FrameMeters.Y1;
    double X2 = desc.FrameMeters.X2;
    double Y2 = desc.FrameMeters.Y2;

    for (int i = 2; i <= cou; i++) {
        mapGetMtrDescribe(MIV.hmap, i, &desc);

        if (desc.FrameMeters.X1 < X1)
            X1 = desc.FrameMeters.X1;
        if (desc.FrameMeters.Y1 < Y1)
            Y1 = desc.FrameMeters.Y1;

        if (desc.FrameMeters.X2 > X2)
            X2 = desc.FrameMeters.X2;
        if (desc.FrameMeters.Y2 > Y2)
            Y2 = desc.FrameMeters.Y2;

    }

    int xCou = ceil((X2 - X1)/dsk);
    int yCou = ceil((Y2 - Y1)/dsk);
    X2 = X1 + xCou*dsk;
    Y2 = Y1 + yCou*dsk;

    qDebug() << "\nМатрица:" << xCou << yCou;

    // Формирование матрицы
    MIV.masIfo.clear();
    MIV.masIfo.resize(xCou);

    for (int i=0 ; i < xCou; i++) {
        MIV.masIfo[i].resize(yCou);
        for (int j=0; j < yCou; j++)
            MIV.masIfo[i][j] = 0; // Зануление
    }

    // Инициализация параметров
    MIV.Xstart = X1;
    MIV.Ystart = Y1;
    MIV.dsk = dsk;

    int kolL = QThread::idealThreadCount(); // Кол-во параллельных процессов
    if (kolL < 1)
        kolL = 1;

    bool ParalPro = true; // Признак распараллеливания
    kolL = 1;             // Принудительная установка 1 потока

    qDebug() << "Кол-во процессов" << kolL;

    if (!ParalPro) {
        qDebug() << "\nПоследовательный расчет";

        for (int i = 0; i < kolL; i++)
            MIV.testVyr(i, kolL);

    } else {
        qDebug() << "\nПараллельный расчет по линиям";

        // Массив обрабатывющих потоков
        QVector <parInf*> masThread;
        masThread.resize(kolL);

        // Создание потоков
        for (int i = 0; i < kolL; i++)
            masThread[i] = new parInf(&MIV, i, kolL);

        // Запуск потоков
        for (int i = 0; i < kolL; i++)
            masThread[i]->start();

        int activeTCou = kolL;

        // Ожидание завершения всех потоков
        while (activeTCou > 0) {

           activeTCou = 0;
           for (int i = 0; i < kolL; i++) // Подсчет текущих потоков
               if (masThread[i]->isRunning())
                  activeTCou++;
        }

        // Удаление потоков
        for (int i = 0; i < kolL; i++) {
            delete masThread[i];
        }

    }

    mapCloseMap(MIV.hmap); // Закрытие карты

    return;
}

calcMap::~calcMap() { }

// Распараллеливание
parInf::parInf(informVyr *MIV, int numL, int kolL) : QThread() {
    this->MIV = MIV;
    this->numL = numL;
    this->kolL = kolL;;
}

void parInf::run() {
    MIV->testVyr(numL, kolL);
}

void informVyr::testVyr(int numL, int kolL) {
    qDebug() << "\nНачало расчета среза линий №" << numL;

    int begL = 0;
    int endL = masIfo.size();

    if (numL != -1) { // Если задан номер среза
        begL = numL*masIfo.size()/kolL;
        endL = (numL+1)*masIfo.size()/kolL;

        if (numL == kolL-1) // Если срез последний
           endL = masIfo.size(); // Конец массива
    }

    qDebug() << "От" << begL << " До" << endL;

    for (int i = begL; i < endL; i++) {
        if (i%5 == 0)
            qDebug() << "i = " << i;

        for (int j = 0; j < masIfo[i].size(); j++) {
            hMut_ptr->lock();
            mapGetHeightValue(hmap, Xstart + dsk*i, Ystart + dsk*j);
            hMut_ptr->unlock();
        }
    }

    qDebug() << "\nКонец расчета среза линий" << numL;

    return;
}
 
Здравствуйте!
К сожалению, в 11 версии ГИС Конструктор не было механизма для многопоточной работы. В актуальной версии ГИС Контруктор есть функции с операндом типа HPAINT. На каждый поток создается отдельный экземпляр типа HPAINT. Например, этот механизм позволяет выполнять отображение карты в несколько потоков. Для работы с матрицей высот есть функции mapGetHeightValueOfMtrControl, mapGetPrecisionHeightValueEx и т.д.
 
Добрый день. Возникла похожая проблема при работе с функцией   mapGetGeneralHeight для расчёта средней высоты в окрестности точки.
Данная функция также вызывается не в основном потоке (чтобы не тормозить отрисовку интерфейса) а периодически асинхронно через QFutureWatcher. При смене карты  вызывается закрытие предыдущей карты (mapCloseData) и происходит ошибка если параллельно в этот момент работает функция   mapGetGeneralHeight с закрытой картой. Снимок стека во время данной ситуации:



Существует ли возможность безопасно отменить выполнение функции mapGeneralHeight (и всех подобных операций выполняемых в данный момент для карты) перед закрытием данных?
Изменено: Олег Темеров - 19.04.2023 15:47:12
 
Добрый день!

В контексте многопоточного использования функций получения высоты можно выделить два момента:
- предполагается, что данные функции атомарны и будут выполняться до конца;
- для запуска в отдельном потоке предназначены функции с собственным контекстом отображения HPAINT (более подробно описано в п. 3.9.9 "Многопоточное отображение карт" Руководства программиста ГИС Конструктор).

Применительно к Вашей можно порекомендовать следующие подходы:
- использовать функции получения высота с параметром HPAINT - например, mapGetPrecisionHeightTriangle() (mtrapi.h). Примеры использования данной функции можно посмотреть в тестовых примерах из состава инсталляции ГИС Конструктор.
Код
  // Расчет абсолютной высоты методом треугольников в заданной точке  // по матрице с наименьшим размером элемента (более точной).
  // В матрицах обрабатываются нормальные высоты
  // Высота вычисляется по самой точной матрице высот,а в случае
  // необеспеченности заданной точки данными матриц высот -
  // по самой точной матрице слоев.
  // hMap   - идентификатор открытой основной векторной карты
  // Координаты точки (x,y) задаются в метрах в системе координат
  // векторной карты. Возвращает значение высоты в метрах.
  // hPaint - идентификатор контекста отображения для многопоточного вызова функций,
  //          создается функцией mapCreatePaintControl, освобождается - mapFreePaintControl
  // В случае ошибки при выборе высоты и в случае необеспеченности
  // заданной точки матричными данными возвращает ERRORHEIGHT (-111111)
_MAPIMP double _MAPAPI mapGetPrecisionHeightTriangle(HMAP hMap, double x, double y);
_MAPIMP double _MAPAPI mapGetPrecisionHeightTriangleEx(HMAP hMap,double x, double y, HPAINT hPaint);
- вместо использования функции mapGetGeneralHeight() можно считать участок матрицы в память с помощью функции mapGetMtrFrame() (mtrapi.h) и произвести необходимые расчеты. При этом необходимо сделать чтение участка матрицы потокобезопасным. О функциях работы с матричными картами можно посмотреть в п. 3.9.25 "Работа с матричной электронной картой" Руководства программиста ГИС Конструктор.
Код
  // Чтение прямоугольного участка матрицы в заданную область памяти
  // hMap -  идентификатор открытых данных
  // number - номер матрицы в списке открытых матриц
  // bits - указатель на начало области памяти
  // left - смещение участка матрицы слева (в элементах)
  // top - смещение участка матрицы сверху (в элементах)
  // width - ширина участка матрицы (в элементах)
  // height - высота участка матрицы (в элементах)
  // widthinbyte - ширинa участка матрицы в байтах
  // Размер участка, заданного адресом bits, должен быть не менее
  // (width * height * размер элемента матрицы в байтах),
  // в противном случае возможны ошибки работы с памятью.
  // Запрос размера элемента матрицы в байтах - функция mapGetMtrElementSize.
  // Высоты участка записываются в область bits в единицах измерения
  // значений высот данной матрицы.
  // Запрос единицы измерения значений высот матрицы - функция mapGetMtrMeasure.
  // При ошибке возвращает ноль
_MAPIMP long int _MAPAPI mapGetMtrFrame(HMAP hMap, long int number, char *bits,
                                        long int left, long int top,
                                        long int width, long int height,
                                        long int widthinbyte = 0);

В любом случае описанные подходы отталкиваются от того, что функции получения высоты атомарны и подразумевают, что будут выполнены до конца.
Если Вы предоставите дополнительную информацию о решаемой Вами задаче, мы сможем порекомендовать более подходящее решение.
 
Большое спасибо за ответ.
Функция расчёта средней высоты в области карты используется для вычисления координаты  наблюдаемой с камеры БЛА точки карты с учётом высот рельефа. То есть, речь идёт о нахождении точки пересечения луча под заданным углом к Земле с заданной высоты. Сначала ищется точка пересечения к Земле как к голому шарику (то есть высота искомой наблюдаемой точки равна 0). Потом запрашивается средняя высота в её окрестности, пересчитывается новое положение точки пересечения для радиуса Земли увеличенного на эту среднюю высоту. Потом берётся среднее значение высоты в окрестности уже этой точки.
И так алгоритм повторяется заданное количество раз с постепенным уменьшением зоны вычисления средней высоты. Если брать точечную или максимальную высоту (с помощью функций с аргументом HPAINT), то алгоритм может давать сильно скачущие положения при небольшом изменении камеры (луч зрения может упираться то в гору, то в равнину), поэтому использована готовая функция mapGetGeneralHeight как сглаживающий механизм.
Использование многопоточных HPAINT-функций не поможет, так как вопрос не в непосредственно многопоточном использовании картографических данных (матрица высот в этот момент не изменяется), а в невозможности досрочно отменить данную операцию.
К примеру, в другие затратные по времени функции работы с матрицей высот (построение матрицы по карте) есть возможность передать  дополнительный аргумент - функцию-обработчик, в которой можно отслеживать и обновлять для пользователя процесс выполнения задачи, и в том числе, через возврат определённого значения, отменить её.
 
Код
 _MAPIMP double _MAPAPI mapGetGeneralHeight(HMAP hMap, double xcenter, double ycenter, double size)
  {
    int mtrCount = mapGetMtrCount(hMap);

    if (hMap == 0 || mtrCount == 0 || size <= 0)
      return ERRORHEIGHT;

    // Поиск в цепочке матрицы для запроса высот
    int iMtr = 0;           // Номер матрицы для запроса высот
    double elemMin = 1e200; // Минимальный суммарный размер элемента
    double elemMinX = 1;    // Размеры элемента матрицы для запроса высот
    double elemMinY = 1;

    for (int i = 1; i <= mtrCount; i++)
    {
      double elemX = 1;
      if ((mapGetMtrMeterInElementX(hMap, i, &elemX) == 0) || (elemX <= 0))
        continue;

      double elemY = 1;
      if ((mapGetMtrMeterInElementY(hMap, i, &elemY) == 0) || (elemY <= 0))
        continue;

      double elemSum = elemX + elemY;
      if (elemMin > elemSum)
      {
        elemMin = elemSum;
        elemMinX = elemX;
        elemMinY = elemX;
        iMtr = i;
      }
    }

    // Если матрица для запроса высот не найдена
    if (iMtr == 0)
      return ERRORHEIGHT;

    // Габариты квадратной области
    double x1 = xcenter - size / 2;
    double y1 = ycenter - size / 2;

    double x2 = xcenter + size / 2;
    double y2 = ycenter + size / 2;

    if ((x2 - x1 <= elemMinX * 2) && (y2 - y1 <= elemMinY * 2))
    {
      // Расчет абсолютной высоты методом треугольников
      return mapGetPrecisionHeightTriangle(hMap, xcenter, ycenter);
    }

    // Запрос значений высот прямоугольной области
    int count = 0;
    double sum = 0;

    for (double x = x1; x <= x2; x += elemMinX)
    {
      for (double y = y1; y <= y2; y += elemMinY)
      {
        double h = mapGetHeightValueOfMtr(hMap, iMtr, x, y);

        if (h == ERRORHEIGHT)
          continue;

        count++;
        sum += h;
      }
    }

    if (count == 0)
      return ERRORHEIGHT;

    return sum / count;
  }
Может этот код Вам поможет.
 
Огромное спасибо за предоставленный код.
Если использовать данную функцию как есть, то ошибка  воспроизводится так же. Доработка её как "потокобезопасной" (замена  mapGetHeightValueOfMtr на   mapGetHeightValueOfMtrControl с дополнительным параметром типа HPAINT создаваемого перед двойным циклом) привела к устранению ошибки.
Дополнительный вопрос - насколько затратным по времени является вызов функции   mapIsMapHandleCorrect? Имеет ли смыл вызывать её на каждой итерации двойного цикла перед mapGetHeightValueOfMtr, либо это будет занимать в сумме очень много времени?
 
Зависит от перекрученности проекта. В простом коде она не нужна. Открыли карту/матрицу, обработали, закрыли.
Нет необходимости проверять идентификатор карты.
 
Спасибо за ответ.
Надеюсь, что последний вопрос от меня по данной теме: в реализации функции mapGetGeneralHeight среди всех матриц для вычисления средней высоты выбирается матрица с наибольшей разрешающей способностью на карте. Но ведь матрицы высот могут не покрывать всю карту. То есть, возможен ли теоретически случай, когда выбранная матрица не перекрывает окрестность точки?
Как можно проверить, что именно данная матрица имеет обеспечение высот в этой точке? Дополнительной проверкой "mapGetMtrNumberInPoint(hMap,  xcenter,  ycenter, i)>0"? То есть функция mapGetMtrNumberInPoint гарантированно вернёт ноль, если i-ая матрица не перекрывает точку [xcenter, ycenter]?
Цитата
if ((elemMin > elemSum)&&(mapGetMtrNumberInPoint(hMap,  xcenter,  ycenter, i)>0))
Изменено: Олег Темеров - 21.04.2023 12:28:47
 
Есть еще такой вариант:
Код
  // Запросить фактические габариты отображаемой матрицы в метрах в системе координат документа
  // При отображении матрицы по рамке возвращаются габариты рамки
  // hMap -  идентификатор открытых данных
  // number - номер матрицы в списке открытых матриц
  // frame  - адрес структуры, в которой будут размещены габариты матрицы в метрах
  // При ошибке возвращает ноль

_MAPIMP long int _MAPAPI mapGetActualMtrFrame(HMAP hMap, DFRAME *frame, long int number);
И далее проверить по габаритам матрицы. Хотя матрицы могут быть и с пропуском блоков.
Код
  // Выбор значения абсолютной высоты в заданной точке из матрицы
  // с номером number в цепочке.
  // hMap   - идентификатор открытой основной векторной карты
  // number - номер матрицы в цепочке.
  // Координаты точки (x,y) задаются в метрах в системе координат
  // векторной карты. Возвращает значение высоты в метрах.
  // hpaint - контекст поддержки многопоточного вызова (см. mapCreatePaintControl)
  // В случае ошибки при выборе высоты и в случае необеспеченности заданной точки матричными данными возвращает ERRORHEIGHT

_MAPIMP double _MAPAPI mapGetHeightValueOfMtr(HMAP hMap, long int number, double x, double y);
_MAPIMP double _MAPAPI mapGetHeightValueOfMtrControl(HMAP hMap, long int number, double x, double y, HPAINT hPaint);
Или запросить высоту в центре. Она должна быть больще, чем ERRORHEIGHT (-111111.)
Страницы: 1
Читают тему (гостей: 1)



© КБ Панорама, 1991-2024

Регистрируясь или авторизуясь на форуме, Вы соглашаетесь с Политикой конфиденциальности