Предварительный текст
Данный пример является предварительным и будет заменен в будущем
Диаграммы на основе Python Data Source
В предыдущих примерах мы рассматривали построение диаграмм на основе источников данных в виде слоев или EQL-запросов. В этом примере мы рассмотрим создание диаграмм в карточках объектов, которые будут создаваться на основе данных, возвращаемых из Python кода. Часто использование Python является более предпочтительным, так как такой код написать проще, чем большой SQL запрос.
Исходные данные
Мы используем наборы данных из Kaggle и OSM. В этих данных находится информация о числе квартир, их площади и стоимости в разрезе ближайших станций метро. Информацию о станциях метро и их местоположении мы взяли из OSM. Эти данные имеют большое количество информации и далеко не вся она будет использоваться при построении диаграмм.
Карта будет иметь два масштабных уровня. На первом уровне будут видны слой со станциями метро, а на втором – линии. Диаграммы, относящиеся к карте, будут видны на всех масштабных уровнях.
Слои находятся в карте Python DS (eromakh.python_ds_test):
- Информация о недвижимости (eromakh.kaggle_realty_moscow) – информация о недвижимости, у этого слоя нет геометрии
- Станции метро Москва (eromakh.symtest_metro_msk_stations) – информация о станциях метро
Мы будем динамически объединять и фильтровать их для составления диаграмм. Всего построим две диаграммы для первого масштабного уровня:
- Круговую диаграмму с распределением квартир по числу комнат в карточке объекта
- Гистограмму с распределением квартир по площади с шагом, который задаст пользователь в квадратных метрах
А также построим две диаграммы для второго масштабного уровня:
- Линейный график со стоимостью квадратного метра при удалении от станции метро в разрезе линий
- Линейчатую диаграмму средней стоимости квадратного метра для всех станций линии
Данные о станциях метро обладают некоторыми проблемами: не все названия станций уникальны – есть две Смоленские (на Арбатско-Покровской и Филевской линиях), а также в OSM кросс-платформенные станции отрисованы как две отдельные точки. Датасет с данными о недвижимости не учитывает таких тонкостей, поэтому мы кросс-платформенные станции отнесем только к одной линии, а Смоленские объединим (в целом они расположены очень близко друг к другу). Другой проблемой является то, что в геометрии станций не указаны названия линий, но указаны их цвета. Из-за этого нам придется объединить данные по Калининской и Солнцевской линиии.
Особенности запуска Python кода
Все эти диаграммы будут создаваться на основе Python кода. Мы создадим несколько файлов внутри одного проекта. В первую очередь нам нужно провести обработку данных о станциях метро: исключить строящиеся станции, а также привести названия линий к официальному.
Создайте проект
Не забудьте создать проект в инструментах разработчика. Без этого не получится передать информацию в дашборды. В конфигурации карты нам нужно будет указывать ID этого проекта, а не файла со скриптом
Структура проекта будет выглядеть следующим образом:
Tool
├─ orchestrator.py
└── src/
├── preprocess_data.py
├── pie_chart.py
└── ...
Когда вы запускаете один файл, то интерпретатору подается полный путь до этого файла. Но в случае, если кодовая база разбита на множество файлов, возникает необходимость включения функций из одного файла в другой. При работе с Python на локальном компьютере проблем не возникает, операционная система справляется с передачей путей между файлами.
Однако в ЭверГИС запуск всех скриптов происходит в рамках песочницы, где интерпретатору подается только путь до исполняемого скрипта. Для того, чтобы импортировать функцию из файла preprocess_data.py в файле orchestrator.py недостаточно просто сделать импорт по схеме:
from src import preprocess dataСистеме нужно в явном виде передать пути до папки, в которой хранится кодовая база. По умолчанию в sys.path хранится путь /app. Файлы инструмента сохраняются в папке /app/user. Нам нужно добавить этот путь в нее и создать дополнительные файлы с названием __init__.py внутри папки src. Для того, чтобы добавить путь в файле orchestrator.py мы добавим следующие строки кода:
import sys
sys.path.insert(0, "/app/user")После этого мы можем импортировать функции/классы из файлов, находящихся в других папках в файл с функциями-орекстраторами внутри инструмента. Для их вызова достаточно использовать стандартную схему импорта:
from src import preprocess dataПостроение круговой диаграммы в карточке
Подробно о создании инструментов и .py файлов написано в разделе работа с инструментами разработчика. Кроме нюанса с импортом файлов наши скрипты особо ничем не отличаются от указанного примера.
Поле с геометрией
Не забывайте проверять название поля с активной геометрией при импорте
Есть небольшой нюанс для диаграмм на основе Python DS. Клиент ЭверГИС строит диаграммы на основе Feature Collection. Для того, чтобы работать с этой структурой нам потребуется функция df_to_feature_collection:
from evergis_tools.geodataframes import df_to_feature_collectionФункция будет возвращать значения в формате Feature Collection из pandas DataFrame, который мы будем использовать как хранилище данных. В нашей диаграмме будет присутствовать пять категорий квартир:
- Студии
- Однокомнатные квартиры
- Двухкомнатные квартиры
- Трехкомнатные квартиры
- Четырехкомнатные и более квартиры
Мы сгруппируем данные о квартирах для каждой станции метро. Для этого вызываемая функция из оркестратора будет получать уникальный идентификатор объекта (gid) для того объекта, карточку которого мы открываем.
Python код круговой диаграммы
В файле preprocess_data.py создадим несколько функций, необходимых для корректной предобработки сырых данных:
check_geometry_field(layer_name: str, client: Client) -> str– проверка названия поля с активной геометриейget_metro_data(metro_layer: str, geometry_column: str, client: Client) -> pd.DataFrame:– извлечение данных о станциях метроget_realty_data(realty_layer: str, client: Client) -> pd.DataFrame:– извлечение данных о недвижимостиcombine_layers(target_gid: int, metro_data: pd.DataFrame, realty_data: pd.DataFrame) -> pd.DataFrame:– обогащение слоя станций метро данными о недвижимости для одной станции
Полный код функций файла preprocess_data.py, необходимых для этой диаграммы:
Показать фрагмент Python кода
from evergis_api import Client
from evergis_tools.eql import eql_query_to_geodataframe
import pandas as pd
import geopandas as gpd
def check_geometry_field(layer_name: str, client: Client) → str:
geom_check = eql_query_to_geodataframe(f”SELECT * FROM {layer_name} LIMIT 1”, client)
data_columns = list(geom_check.columns)
if (“geom” in data_columns):
return “geom”
else:
return “geometry”
def get_metro_data(metro_layer: str, geometry_column: str, client: Client) → pd.DataFrame:
rename_field: dict[str, str] = {
“#FFCBDB”: “Некрасовская”,
“#b4d445”: “Люблинско-Дмитровская”,
“green”: “Замоскворецкая”,
“blue”: “Арбатско-Покровская”,
“green;#82C0C0”: “Большая кольцевая”,
“#a0a2a3”: “Серпуховско-Тимирязевская”,
“red”: “Сокольническая”,
“lightblue”: “Филевская”,
“orange;violet”: “Таганско-Краснопресненская”,
“#03795f”: “Троицкая”,
“#03795F”: “Троицкая”,
“violet”: “Таганско-Краснопресненская”,
“violet;orange”: “Таганско-Краснопресненская”,
“#bac8e8”: “Бутовская”,
“orange”: “Калужско-Рижская”,
“brown”: “Кольцевая”,
“#82C0C0”: “Большая кольцевая”,
“yellow”: “Калиниско-Солнцевская”
}
metro_stations = eql_query_to_geodataframe(f"select gid, name, colour, geometry from {metro_layer} where proposed_railway IS NULL and colour IS NOT NULL", client, geometry_field=geometry_column)
line_name: list[str] = []
for cl in metro_stations["colour"].tolist():
line_name.append(rename_field[cl])
metro_stations["line_name"] = line_name
metro_stations.drop(columns=["colour"], inplace=True)
metro_stations.set_crs(4326, inplace=True)
return metro_stations
def get_realty_data(realty_layer: str, client: Client) → pd.DataFrame:
query = f"""select price, metro_station, minutes_to_metro, number_of_rooms, area, renovation from {realty_layer}
where price is not null and metro_station is not null and minutes_to_metro is not null
and number_of_rooms is not null and area is not null and renovation is not null"""
realty = eql_query_to_geodataframe(query, client, with_geom=False)
realty.drop(columns=[“geometry”], inplace=True)
return realty
def combine_layers(target_gid: int, metro_data: pd.DataFrame, realty_data: pd.DataFrame) → pd.DataFrame:
metro_station = metro_data.loc[metro_data[“gid”] == target_gid]
station_name = metro_station[“name”].tolist()[0]
realty_nearby = realty_data.loc[realty_data[“metro_station”]== station_name]
return realty_nearby
В файле pie_chart.py мы поместим функцию агрегации данных. Также мы добавим в нее задание цвета для категорий квартир. Технически, мы можем задать цвета в конфигурации диаграммы, но тогда ее будет сложнее менять в будущем, например, если мы разделим категорию “четырехкомнтные и более” на отдельные группы со своими значениями.
Полный код функций файла pie_chart.py, необходимых для этой диаграммы::
Показать фрагмент Python кода
from evergis_api import Client
from evergis_tools.eql import eql_query_to_geodataframe
import pandas as pd
def room_bins(realty_data: pd.DataFrame) → pd.DataFrame:
color_dict = {
“студия”: “#8EA604”,
“1-комнатная”: “#F5BB00”,
“2-комнатная”: “#EC9F05”,
“3-комнатная”: “#D76A03”,
“4-комнатная и более”: “#BF3100”
}
type_dict = {
"студия": 0,
"1-комнатная": 0,
"2-комнатная": 0,
"3-комнатная": 0,
"4-комнатная и более": 0
}
for index, row in realty_data.iterrows():
if row["number_of_rooms"] == 0:
type_dict["студия"] += 1
elif row["number_of_rooms"] == 1:
type_dict["1-комнатная"] += 1
elif row["number_of_rooms"] == 2:
type_dict["2-комнатная"] += 1
elif row["number_of_rooms"] == 3:
type_dict["3-комнатная"] += 1
else:
type_dict["4-комнатная и более"] += 1
return pd.DataFrame({"name": list(type_dict.keys()), "value": list(type_dict.values()), "color": list(color_dict.values())})
В файле orchestrator.py будут хранится все вызываемые функции. На данный момент нам потребуется только одна. Важный нюанс: в функцию, которую вы будете передавать в конфигурацию вам необходимо сделать либо любое количество именованных атрибутов (**kwargs), либо передавать только те атрибуты, которые вы будете явно укажете в конфигурации. Из-за этого мы сделали названия слоев глобальными переменными и не указывали их среди аргументов функции (тем не менее вы можете передать их из конфигурации). Полный код:
Показать фрагмент Python кода
from evergis_api import Client
from evergis_tools.eql import eql_query_to_geodataframe
from evergis_tools.geodataframes import df_to_feature_collection
import pandas as pd
import geopandas as gpd
import sys
sys.path.insert(0, “/app/user”)
from src import histogram as hst
from src import pie_chart as pct
from src import preprocess_data as pdt
layer_metro = “eromakh.symtest_metro_msk_stations”
layer_realty = “eromakh.kaggle_realty_moscow”
def card_pie_chart(target_gid: int):
client = Client()
geometry_field: str = pdt.check_geometry_field(layer_metro, client)
metro_df: pd.DataFrame = pdt.get_metro_data(layer_metro, geometry_field, client)
realty_df: pd.DataFrame = pdt.get_realty_data(layer_realty, client)
nearby_realty: pd.DataFrame = pdt.combine_layers(target_gid, metro_df, realty_df)
return df_to_feature_collection(pct.room_bins(nearby_realty))
Конфигурация карточки объекта
В первую очередь изменим базовое отображение карточки станции метро. Так как данные были загружены из OpenStreetMap, они имеют большое количество лишней информации, которая только нагружает карточку. Сделаем основной раздел с информацией сворачиваем. Зададим ему название Информация о станции, а также изменим отображение названия объекта в заголовке карточки с gid на name. О том как это сделать вы можете прочитать в разделе создание кастомной карточки объекта.
Редактируемая конфигурация
В отличие от предыдущего примера мы редактируем конфигурацию слоя, а не всей карты
Мы же разберем создание источника данных на основе нашего инструмента. Создадим второй раздел и назовем его “Квартиры в разрезе комнатности”. Далее в конфигурации слоя находим список объектов dataSources. По умолчанию он пустой. Добавим в него объект с кодом:
"dataSources": [
{
"name": "pie_chart_ds",
"fileName": "orchestrator.py",
"methodName": "card_pie_chart",
"parameters": {
"target_gid": "{gid}"
},
"resourceId": "97f760c942e8414eb886ce3bca948993"
}
]Обратите внимание на параметры объекта. Для того, чтобы передать значение атрибута из открытой карточки объекта, необходимо передать его в фигурных скобках. Строка
"parameters": {
"target_gid": "{gid}"
}Означает, что мы передаем в вызываемую функцию (card_pie_chart) один аргумент gid, который при этом берется из открываемой нами карточки. Он будет разным для разных объектов. Обратите внимание, в отличие от аргумента фильтра мы не используем знак @ в названии переменной.
resourceId
Не забывайте о том, что в resourceId передается идентификатор инструмента, а не файла, из которого вызывается функция
После создания объектов в конфигурации и настройки Python DS мы можем создать диаграмму внутри раздела карточки. Для этого в списке children раздела создадим уже знакомую нам конфигурацию по примерам создания круговой диаграммы и создания инструментов на карте. Конфигурация практически ничем не отличается от уже приводившихся нами конфигураций на предыдущем этапе, разница только в том, что мы в параметре dataSourceName указываем не название слоя или источника данных на основе EQL, а название Python DS, которое мы задали в атрибуте name. Полный код раздела карточки с диаграммой в конфигурации:
Показать фрагмент конфигурации
{
"id": "chart_1_data",
"options": {
"expanded": true,
"expandable": true
},
"children": [
{
"id": "title",
"type": "text",
"value": "Квартиры в разрезе комнатности"
},
{
"id": "pie_chart",
"style": {
"marginBottom": 8
},
"options": {
"twoColumns": true
},
"children": [
{
"id": "chart",
"type": "chart",
"style": {},
"options": {
"limit": 5,
"height": 100,
"radius": 0.7,
"padding": 1,
"chartType": "pie",
"showTotal": true,
"otherItems": 100,
"shownItems": 5,
"cornerRadius": 1,
"orderByValue": false,
"relatedDataSources": [
{
"hideAxis": true,
"axisColor": "#CB94E5",
"chartAxis": "y",
"attributeName": "value",
"attributeColor": "color",
"attributeTitle": "name",
"dataSourceName": "pie_chart_ds"
}
]
}
},
{
"id": "legend",
"type": "legend",
"options": {
"chartId": "chart"
}
}
],
"templateName": "Chart"
}
],
"templateName": "ContainersGroup"
}
После применения изменений на карточке появится ее один раздел, в котором будет диаграмма. Диаграмма строится автоматически при выборе объекта. Так как расчеты производятся на лету, не пытайтесь создавать инструменты расчета, которым требуется значительное время на обработку. Для этих целей лучше создать инструмент.

Создание инструмента и фильтра в карточке
To be continued
Данный раздел будет заполнен позднее
Мультимасштабность
Клиент использует Mapbox GL, в связи с чем мы можем использовать стандартные функции данного фреймворка для настройки отображения слоев. Для того, чтобы включать или отключать отображение слоев на определенных масштабных уровнях нам потребуются параметры
"maxzoom": 18
"minzoom": 11Обратите внимание на то, что цифра обозначает уровень тайла, с которого слой будет видимым или не видимым. Значения из примера выше включат отображение слоя с 18 по 11 уровень тайлов включительно. Задать численный масштаб видимости как на топографической карте (например от 1:10000 до 1:100000) – нельзя.
Для того, чтобы задать ограничения видимости откройте конфигурацию слоя. Найдите в ней параметр clientStyle и внутри него в списке items допишите нужные строки в конфигурацию. Например, для слоя со станциями этот фрагмент конфигурации будет выглядеть так:
"clientStyle": {
"items": [
{
"type": "circle",
"paint": {
"circle-color": "#cc0000",
"circle-radius": 4,
"circle-opacity": 1,
"circle-stroke-color": "#ff0000",
"circle-stroke-width": 0,
"circle-stroke-opacity": 0.5
},
"layout": {},
"maxzoom": 18,
"minzoom": 11
}
]
}Добавим еще один слой – в нем будет геометрия линий метрополитена. В этом слое уже есть HEX коды цвета линий. Мы можем использовать эти коды для изменения цвета линии в обход графического интерфейса оформления линии в клиенте. Для этого нам потребуется отредактировать конфигурацию слоя с линиями метро.
По умолчанию в clientStyle указано оформление по умолчанию для всех типов геометрии. Для того, чтобы классифицировать слой по HEX коду из таблицы атрибута нам необходимо создать список items, который бы не входил ни в один из объектов со стандартным отображением геометрии. Внутри этого списка нам потребуются следующие элементы:
- type – тип геометрии, в нашем случае – line
- paint – параметры отображения. В нашем случае нам нужны:
- line-color – цвет линии
- line_width – толщина линии
- line-opacity – непрозрачность отрисовки линии
- layout
Для слоя с линиями метро конфигурация будет выглядеть так:
"items": [
{
"type": "line",
"paint": {
"line-color": [
"get",
"colour"
],
"line-width": 2,
"line-opacity": 1
},
"layout": {}
}
]В итоге несмотря на то, что видимость слоя не отключена, мы не видим точки станций на 10 масштабном уровне, а линии метро раскрашены в соответствии с HEX кодом. Обратите внимание на то, что когда вы вручную изменяете оформление через конфигурацию, вы не увидите соответствующие изменения в визуальных настрйоках оформления в клиенте. Вы не увидите правила классификации слоя, однако все будет работать.

При этом на 12 уровне масштабном уровне тайлов мы увидим точки станций как и предполагалось:

Создание линейного графика и линейчатой диаграммы
To be continued
Данный раздел будет заполнен позднее