# Таблицы

Для того, чтобы мы могли работать как с географической, так и с атрибутивной информацией, данные в ГИС Аксиома организованы в виде групп файлов, имеющих общее имя, но разные расширения.

Пользователь оперирует только с одним файлом из этой группы – так называемым «табличным» файлом, которые имеет расширение TAB. Все файлы из группы автоматически создаются, обновляются и поддерживаются самой программой ГИС Аксиома. Таблица `axipy.Table` является наследником класса объекта данных `axipy.DataObject`.

## Открытие таблиц

Работа с источником данных начинается с открытия объекта данных с помощью функции `axipy.ProviderManager.openfile()` объекта `axipy.provider_manager`. Для таблиц возвращаемый объект данных будет типа `axipy.Table`.

    table = provider_manager.openfile('../path/to/datadir/worldcap.tab')

Некоторые форматы могут содержать несколько таблиц в одном файле, например GeoPackage. В таком случае нужно указать в параметре `dataobject=`, какую таблицу из файла вы хотите открыть.

    table = provider_manager.openfile('../path/to/datadir/example.gpkg', dataobject='world')

У таблицы можно узнать провайдер, которым она была открыта - свойство `axipy.DataObject.provider`:

    print(table.provider)

    >>> 'SqliteDataProvider'

### Источники данных и дополнительные параметры

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

Для открытия таких объектов используйте конкретный провайдер данных из коллекции провайдеров `axipy.ProviderManager`. Он содержит все методы, параметры и допустимые значения, для работы с конкретным типом объектов данных.

Если по какой-то причине использовать конкретный провайдер данных не удается, то используется функция `axipy.ProviderManager.open()`, которая принимает словарь со всеми необходимыми параметрами.

Пример открытия таблицы из базы данных PostgreSQL:

Примечание

Исключительно в качестве примера. Намного проще напрямую использовать провайдер данных `axipy.PostgreDataProvider`.

    definition = {
        "src": "<Адрес сервера БД>",
        "port": 5432,
        "db": "<Имя базы данных>",
        "user": "<Имя прользователя>",
        "password": "<Пароль>",
        "dataobject": '"DataAxi"."World"',
        "provider": "PgDataProvider"
    }
    table = provider_manager.open(definition)

### Открытие источников с множеством таблиц

Для получения всех доступных таблиц одного источника используется функция `axipy.ProviderManager.read_contents()`:

    contents = axipy.provider_manager.read_contents('../path/to/datadir/example.gpkg')
    print(contents)

    >>> ['world', 'worldcap']

Для источников с единственной таблицей список будет содержать одно имя:

    contents = axipy.provider_manager.read_contents({'src': '../path/to/datadir/worldcap.tab'})
    print(contents)

    >>> ['worldcap']

## Схема таблицы

Записи таблицы имеют фиксированную структуру, повторяющую столбцы таблицы. Схема представлена типом `axipy.Schema`. Свойство `axipy.Schema.coordsystem` указывает на то, что таблица является пространственной. Атрибуты `axipy.Attribute` перечислены в том же порядке, что и столбцы таблицы.

Совет

Схему можно рассматривать как список `list` атрибутов `axipy.Attribute`. Манипулировать атрибутами схемы можно также, как элементами списка.

Схему таблицы можно получить, используя свойство `axipy.Table.schema`.

Например:

    schema = table.schema

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

    schema.attribute_names
    # эквивалентно
    [attr.name for attr in schema]

### Атрибуты схемы

Атрибут представлен типом `axipy.Attribute`. Его главные параметры - это имя `axipy.Attribute.name` и тип `axipy.Attribute.typedef`. Тип атрибута представляется строкой. В нем может быть указана максимальная длина - для строк и десятичного типа через двоеточие, например, `string:254`. И точность - для десятичного типа через точку, например, `decimal:7.3`.

Доступные типы:

Тип | Описание
--- | ---
string | строка
int | целое число
double | вещественное число с плавающей запятой
decimal | вещественное число с фиксированной запятой
bool | логическое значение
date | дата
time | время
datetime | дата и время

Специальный атрибут Система Координат `axipy.Schema.coordsystem` содержит значение СК и указывает на то, что таблица пространственная - может содержать геометрию и стиль.

См.также

Подробнее в главе Системы Координат.

#### Создание схемы таблицы. Вспомогательные функции

Класс `axipy.Attribute` содержит вспомогательные функции `axipy.Attribute.string()` и другие для создания атрибутов; для создания схемы таблицы используется конструктор - `axipy.Schema`.

Например, так можно создать схему таблицы:

    schema = Schema(
        Attribute.string('Столица', 25),
        Attribute.string('Capital', 25),
        Attribute.string('Страна', 30),
        Attribute.string('Country', 30),
        Attribute.decimal('Cap_Pop', 8, 5),
        coordsystem='prj:Earth Projection 12, 62, "m", 0'
    )

Свойства `axipy.Attribute.length` и `axipy.Attribute.precision` позволяют получить длину и точность типа. Список всех свойств описан в справочнике на тип `axipy.Attribute`.

### Чтение записей

Объект таблица `axipy.Table` дает возможность работать с записями `axipy.Feature`. Метод `axipy.Table.items()` возвращает итератор (`iterator`) по записям.

См.также

Подробнее о записях в главе Записи.

    features = table.items()
    for feature in features:
        # обработка записи
        ...

Возвращается именно Итератор. При чтении он движется вперед и в конце становится пустым. Чтобы начать чтение сначала, нужно создать новый итератор.

## Создание таблиц

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

Провайдер может быть задан явно:

    newtable = provider_manager.tab.create_open('../path/to/datadir/newtable.tab', schema)

или найден автоматически из расширения файла:

    newtable = provider_manager.createfile('../path/to/datadir/newtable.tab', schema)

См.также

`axipy.ProviderManager.tab`, `axipy.ProviderManager.createfile()`.

Добавим в таблицу несколько записей из вселенной Властелин Колец:

    features_to_insert = [
        Feature({'country': 'Мордор', 'capital': 'Барад-Дур'}),
        Feature(country='Гондор', capital='Минас Тирит'),  # создание с использованием **kwargs
        Feature({'country': 'Рохан'}),  # не обязательно подавать все значения, они будут пустыми
    ]
    newtable.insert(features_to_insert)

Иногда может потребоваться массовая вставка записей в таблицу. В случае, если это делать по одной записи, то после каждой вставки будут отрабатываться события на изменение данных, которые в свою очередь используются в инструментах и прочих GUI компонентах. Для того, чтобы это избежать предлагается два метода. Рассмотрим их на примере вставки 1000 записей в таблицу:

1. С предварительных занесением в список `list` и последующей единовременной вставкой:

    table = provider_manager.openfile('sample.tab')
    point = Point(1,1)
    pstyle = PointStyle()
    features = []
    for i in range (1, 1000):
      fpoint = Feature({}, geometry = point, style = pstyle )
      features.append(fpoint)
    table.insert(features)
    table.commit()

Но при использовании данного метода при большом количестве данных есть вероятность перерасхода памяти. Этого можно избежать, если воспользоваться вторым подходом.

2. Использование функции-генератора. При этом читаемые данные сразу же используются при вставке. Этот метод можно использовать, как пример, если необходимо зачитать данные из текстового файла с неподдерживаемым форматом.

    table = provider_manager.openfile('sample.tab')
    point = Point(1,1)
    pstyle = PointStyle()

    def generate_features(): # функция-генератор
        for i in range(1, 1000):
            yield Feature(geometry = point, style = pstyle)

    table.insert(generate_features())
    table.commit()

Или же это можно записать в другом виде (результат аналогичный):

    table = provider_manager.openfile('sample.tab')
    point = Point(1,1)
    pstyle = PointStyle()
    generator = ( Feature(geometry = point, style = pstyle) for i in range(1, 1000) )
    table.insert(generator)
    table.commit()

## Редактирование таблиц

Один из возможных способов редактирования таблиц - открыть исходную таблицу, создать целевую таблицу с той же или другой структурой. И при чтении записей из исходной таблицы редактировать их и записывать в целевую. В результате получится отредактированная копия.

Например, для таблицы `world` необходимо оставить только колонки “Страна” и “Население”; и оставить только те страны, население которых больше `100 миллионов`.

Вот один из вариантов, как это можно реализовать.

    def is_over_100million(feature) -> bool:
        return feature['Население'] > 100_000_000

    orig_table = provider_manager.openfile('../path/to/datadir/world.tab')
    schema = Schema(
        Attribute.string('Страна'),
        Attribute.integer('Население'),
    )
    copy_table = provider_manager.createfile('../path/to/edited_world.tab', schema)
    orig_features = orig_table.items()

    filtered_features = filter(is_over_100million, orig_features)
    copy_table.insert(filtered_features)

При редактировании таблицы так-же присутствует возможность более гибкого управления производимыми в таблице изменениями. Допустимо выполнение отката как назад (отмена последних изменений) `axipy.Table.undo()`, так и откат вперед (возврат после выполнения отката назад) `axipy.Table.redo()`.

    table.insert(feature1)
    table.insert(feature2)
    if table.can_undo:
       table.undo()

В рассмотренном примере будет вставлена только feature1.

## Запросы

SQL-запросы являются мощным инструментом обработки данных. При выполнении запроса к таблицам образуется отдельный объект данных. При открытии данных они попадают в единый каталог `axipy.DataManager`. Запрос выполняется относительно всех таблиц, находящихся в этом каталоге, с помощью метода `axipy.DataManager.query()`.

    query_text = 'SELECT * FROM world WHERE Население > 100000000'
    query_table = provider_manager.query(query_text)

Список поддерживаемых функций можно найти в руководстве пользователя ГИС Аксиома и в диалоге построения SQL-запросов самого приложения ГИС Аксиома.

### Интерфейс Qt

Более низкоуровневый доступ к данным возможен через стандартный Qt интерфейс `PySide2.QtSql` и вспомогательный `axipy.sql`. Это позволяет:

> - избежать лишних округлений и нормализации значений, так как нет схемы таблицы `axipy.Schema` и атрибутов `axipy.Attribute`;

> - не конвертировать значения, так как нет приведения к `axipy.Feature`;

> - выделять меньше ресурсов, так как результат читается по одной записи без создания объекта данных `axipy.Table`;

> - проще интегрировать с другим Qt кодом.

Примечание

Доступ к колонкам с геометрией и стилем осуществляется через значения `axipy.sql.geometry_uid` и `axipy.sql.style_uid`.

Все открытые таблицы образуют базу данных, которую можно получить функцией `axipy.sql.get_database()`.

    db = axipy.sql.get_database()
    query = QSqlQuery(db)
    query.exec_('SELECT * FROM world WHERE Население > 100000000')
    while query.next():
        print(query.value(0))

#### Обработка ошибок

При выполнении запроса возвращается признак успешности выполнения. Если выполнение оказалось неуспешным, функция `PySide2.QtSql.QSqlQuery.lastError()` может показать последнюю ошибку. Стандартная обработка ошибок в `PySide2.QtSql` выглядит следующим образом:

    q = QSqlQuery(database)
    success = q.exec_(sql_text)
    if not success:
        # Передаем ошибку выше
        raise RuntimeError(q.lastError().text())