[Программирование, C++, Qt, Геоинформационные сервисы] Создаём плагин Qt GeoServices на примере ОС Аврора, OpenStreetMap и Sight Safari
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр! Хотим рассказать о том, как создать плагин Qt GeoServices и использовать его в своём приложении на ОС Аврора. В этом посте мы подробно объясним, как научить приложение определять координаты устройства на карте и прокладывать оптимальные маршруты с помощью сервиса Sight Safari. Самые нетерпеливые могут пощупать готовый код плагина и демо-приложения на GitHub, всех остальных приглашаем под кат.Зачем писать свой плагинКогда приложению на Qt требуется поддержка карт, то первое, что приходит на ум — использовать QML-компонент Map. Но к нему нужен плагин, реализующий работу с провайдером данных для карты. Так что если ваше приложение должно работать offline или использовать сторонний API, стандартные плагины вас могут не устроить. Придётся реализовывать либо свой плагин, либо свою карту.Мы пойдём по первому пути: создадим плагин Qt GeoServices, который будет обращаться к offline-провайдеру тайлов для отображения карты и к Sight Safari. На Хабре уже рассказывали об этом сервисе для построения маршрутов (раз и два). Возможно, кому-то не помешает изучить базовую информацию о работе с картами в Аврора.Подготовка: настраиваем OSM Scout ServerДля offline-доступа к тайлам карты устанавливаем на наш телефон под управлением ОС Аврора OSM Scout Server — полностью автономное решение для навигации. Ещё нам понадобится дополнительный модуль со шрифтами Noto, которые используются для рендеринга карт.Чтобы тайлы возвращались в подходящем для отображения формате, выбираем профиль «Рекомендовано для карт с векторными и растровыми тайлами». Теперь осталось выбрать и скачать карты необходимого района в «Диспетчере карт». Они отобразятся списком, как на рисунке ниже.
С чего начинается плагин: конфигурационный файлТеатр начинается с вешалки, а Qt-плагин — с конфигурационного json-файла.
{
"Keys": ["osmscoutoffline"],
"Provider": "osmscoutoffline",
"Version": 100,
"Experimental": false,
"Features": [
"OfflineMappingFeature",
"OnlineRoutingFeature"
]
}
Keys — уникальное имя плагина, Provider — имя сервиса-провайдера, Version — версия плагина, Experimental — статус плагина, Features — список поддерживаемых функций. Согласно нашей конфигурации, плагин osmscoutoffline версии 1.0.0, поддерживает функции отображения карт offline и построения маршрутов online и не является экспериментальным, то есть доступен для всех приложений.Регистрируем плагин в системеqgeoserviceproviderfactoryosmscoutoffline.h
#ifndef QGEOSERVICEPROVIDERFACTORYOSMSCOUTOFFLINE_H
#define QGEOSERVICEPROVIDERFACTORYOSMSCOUTOFFLINE_H
#include <QObject>
#include <QGeoServiceProviderFactory>
class QGeoServiceProviderFactoryOsmScoutOffline : public QObject, public QGeoServiceProviderFactory
{
Q_OBJECT
Q_INTERFACES(QGeoServiceProviderFactory)
Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0"
FILE "../osmscoutoffline_plugin.json")
public:
QGeoRoutingManagerEngine *createRoutingManagerEngine(const QVariantMap ¶meters,
QGeoServiceProvider::Error *error,
QString *errorString) const;
QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters,
QGeoServiceProvider::Error *error,
QString *errorString) const;
};
#endif // QGEOSERVICEPROVIDERFACTORYOSMSCOUTOFFLINE_H
qgeoserviceproviderfactoryosmscoutoffline.cpp
#include "qgeoserviceproviderfactoryosmscoutoffline.h"
#include "qgeoroutingmanagerengineosmscoutoffline.h"
#include "qgeotiledmappingmanagerengineosmscoutoffline.h"
QGeoRoutingManagerEngine *QGeoServiceProviderFactoryOsmScoutOffline::createRoutingManagerEngine(
const QVariantMap ¶meters, QGeoServiceProvider::Error *error,
QString *errorString) const
{
return new QGeoRoutingManagerEngineOsmScoutOffline(parameters, error, errorString);
}
QGeoMappingManagerEngine *QGeoServiceProviderFactoryOsmScoutOffline::createMappingManagerEngine(
const QVariantMap ¶meters, QGeoServiceProvider::Error *error,
QString *errorString) const
{
return new QGeoTiledMappingManagerEngineOsmScoutOffline(parameters, error, errorString);
}
Обратите внимание на макрос Q_PLUGIN_METADATA, который регистрирует json-файл в системе. Параметр IID — название упомянутого выше класса для реализации интерфейса (org.qt-project.qt.geoservice.serviceproviderfactory/5.0). Параметр FILE — путь к json-файлу.Объявленные в public-секции методы createRoutingManagerEngine и createMappingManagerEngine создают объекты, отвечающие за ту или иную функцию указанного в Q_PLUGIN_METADATA интерфейса. Класс QGeoRoutingManagerEngineOsmScoutOffline отвечает за составление маршрутов, с ним мы разберёмся позже. Сейчас нас больше интересует QGeoTiledMappingManagerEngineOsmScoutOffline — наследник класса QGeoTiledMappingManagerEngine, работающий с OSM Scout Server. В конструкторе этого класса устанавливаем параметры, необходимые для работы нашего плагина.QGeoTiledMappingManagerEngineOsmScoutOffline::QGeoTiledMappingManagerEngineOsmScoutOffline
QGeoTiledMappingManagerEngineOsmScoutOffline::QGeoTiledMappingManagerEngineOsmScoutOffline(
const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString)
{
QGeoCameraCapabilities cameraCaps;
cameraCaps.setMinimumZoomLevel(0.0);
cameraCaps.setMaximumZoomLevel(19.0);
setCameraCapabilities(cameraCaps);
setTileSize(QSize(256, 256));
QList<QGeoMapType> mapTypes;
mapTypes << QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("OSM Street Map"), false, false, 1);
setSupportedMapTypes(mapTypes);
QGeoTileFetcherOsmScoutOffline *tileFetcher = new QGeoTileFetcherOsmScoutOffline(this);
tileFetcher->setParams(parameters);
setTileFetcher(tileFetcher);
*error = QGeoServiceProvider::NoError;
errorString->clear();
}
Указываем минимальный и максимальный уровни масштабирования карты, а также размер получаемых от сервера тайлов. Затем прописываем поддерживаемые типы карт — в данном случае только карты улиц (StreetMap). Наконец, создаём объект, который будет обращаться к серверу за тайлами. Далее рассмотрим его подробнее.Объект, который обращается к серверу
QGeoTiledMapReply *QGeoTileFetcherOsmScoutOffline::getTileImage(const QGeoTileSpec &spec)
{
QUrlQuery query;
for (QString &key : m_params.keys())
query.addQueryItem(key, m_params[key].toString());
query.addQueryItem(QStringLiteral("x"), QString::number(spec.x()));
query.addQueryItem(QStringLiteral("y"), QString::number(spec.y()));
query.addQueryItem(QStringLiteral("z"), QString::number(spec.zoom()));
QUrl url(QStringLiteral("http://localhost:8553/v1/tile"));
url.setQuery(query);
QNetworkRequest remoteRequest(url);
QNetworkReply *reply = m_networkManager->get(remoteRequest);
return new QGeoMapReplyOsmScoutOffline(reply, spec);
}
Метод getTileImage принимает параметры, которым должен соответствовать запрашиваемый тайл, и возвращает объект, содержащий изображение. Запрос к серверу создаётся внутри метода, поэтому его дополнительная синхронизация не требуется даже в случае нелокального расположения сервера.Важно указать формат API, к которому мы обращаемся. В нашей реализации OSM Scout Server крутится непосредственно на телефоне (http://localhost) на стандартном порте (8553). Запрос тайла с сервера (метод tile) идёт через первую версию API (v1). Параметры x, y и z — координаты и масштаб запрашиваемого тайла. Цикл по m_params в начале метода позволяет добавить к запросу параметры, переданные из определения плагина в QML.Сохраняем тайл в наследнике класса QGeoTiledMapReply — для отображения в компоненте Map.
void QGeoMapReplyOsmScoutOffline::networkReplyFinished()
{
if (!m_reply)
return;
if (m_reply->error() != QNetworkReply::NoError)
return;
setMapImageData(m_reply->readAll());
setMapImageFormat("png");
setFinished(true);
m_reply->deleteLater();
m_reply = 0;
}
Здесь после проверки на наличие ошибок пришедшее изображение считывается в виде массива байтов. Затем указывается формат изображения (в данном случае — png). Сигнал finished уведомляет компонент Map о том, что тайл готов к отрисовке.А что с маршрутом? Обзор Sight Safari APIИтак, наш Qt GeoServices-плагин может определять и показывать положение пользователя на карте. Теперь научим его отображать маршруты, построенные с помощью сервиса Sight Safari.Для поиска маршрута сервис предоставляет метод findpath, который принимает на вход шесть параметров. Впрочем, мы собираемся передавать только три:
- from — начало пути;
- to — конец пути;
- ratio — «интересность».
Чем больше значение последнего параметра, тем больше достопримечательностей и просто красивых мест встретится на пути. Мы зададим ratio равным единице — в качестве оптимального сочетания интересности и протяжённости маршрута.Отладочная информация в нашем приложении не нужна, поэтому параметр debug оставим установленным по умолчанию. Якорная точка через desiredCoordinates не передаётся, так как в параметрах from и to будут указаны точные координаты. Параметр apiKey тоже опустим: для демонстрационных целей хватает бесплатных возможностей API.В качестве ответа findpath возвращает json-объект, в котором нас интересует массив latLonPoints: здесь хранятся точки, по которым проходит маршрут — его-то и надо отобразить на нашей карте. Кстати, несмотря на указанный в документации Sight Safari тип запроса POST, API прекрасно работает и через GET-запросы.Расширение функциональности плагинаНастало время разобраться с классом QGeoServiceProviderFactoryOsmScoutOffline, с которым мы уже сталкивались в ходе регистрации плагина в системе. В нём имплементирован метод createRoutingManagerEngine, возвращающий указатель на объект класса, отвечающего за построение маршрутов:
QGeoRoutingManagerEngine *QGeoServiceProviderFactoryOsmScoutOffline::createRoutingManagerEngine(
const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const
{
return new QGeoRoutingManagerEngineOsmScoutOffline(parameters, error, errorString);
}
Основной метод в этом классе — calculateRoute, принимающий запрос с точками, через которые проходит маршрут, и возвращающий ответ с одним или несколькими маршрутами для отображения:QGeoRouteReply
QGeoRouteReply *QGeoRoutingManagerEngineOsmScoutOffline::calculateRoute(
const QGeoRouteRequest &request)
{
QGeoCoordinate start = request.waypoints()[0];
QGeoCoordinate end = request.waypoints()[1];
QString from = QStringLiteral("%1,%2").arg(QString::number(start.latitude()), QString::number(start.longitude()));
QString to = QStringLiteral("%1,%2").arg(QString::number(end.latitude()), QString::number(end.longitude()));
QUrlQuery query;
query.addQueryItem(QStringLiteral("from"), from);
query.addQueryItem(QStringLiteral("to"), to);
query.addQueryItem(QStringLiteral("ratio"), QStringLiteral("1"));
QUrl url(QStringLiteral("https://sightsafari.city/api/v1/routes/direct"));
url.setQuery(query);
QNetworkRequest remoteRequest(url);
QNetworkReply *reply = mNetworkManager->get(remoteRequest);
QGeoRouteReplyOsmScoutOffline routeReply = new QGeoRouteReplyOsmScoutOffline(reply, request, this);
connect(routeReply, &QGeoRouteReplyOsmScoutOffline::finished,
this, &QGeoRoutingManagerEngineOsmScoutOffline::replyFinished);
connect(routeReply, static_cast<void(QGeoRouteReplyOsmScoutOffline::)
(QGeoRouteReplyOsmScoutOffline::Error, const QString &)>(&QGeoRouteReplyOsmScoutOffline::error),
this, &QGeoRoutingManagerEngineOsmScoutOffline::replyError);
return routeReply;
}
Сначала из пришедшего запроса получаются координаты начала и конца маршрута — подразумевается, что запрос содержит только две координаты. Далее формируется и отправляется GET-запрос к описанному ранее методу API. Здесь поле mUrlPrefix содержит endpoint сервера.После этого формируется и возвращается указатель на объект с полученными маршрутами. У этого объекта могут быть сигналы finished или error, в соответствии с которыми выполняется обработка успешно либо неуспешно завершённого запроса. Обработка у нас простая:
void QGeoRoutingManagerEngineOsmScoutOffline::replyFinished()
{
QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
if (reply)
emit finished(reply);
}
void QGeoRoutingManagerEngineOsmScoutOffline::replyError(QGeoRouteReply::Error errorCode,
const QString &errorString)
{
QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender());
if (reply)
emit error(reply, errorCode, errorString);
}
Метод calculateRoute создаёт и возвращает объект типа QGeoRouteReplyOsmScoutOffline. Всё, что нужно для работы объекта, находится в конструкторе:QGeoRouteReplyOsmScoutOffline::QGeoRouteReplyOsmScoutOffline
QGeoRouteReplyOsmScoutOffline::QGeoRouteReplyOsmScoutOffline(QNetworkReply *reply,
const QGeoRouteRequest &request,
QObject parent)
: QGeoRouteReply(request, parent)
{
if (reply == nullptr) {
setError(UnknownError, QStringLiteral("Null reply"));
return;
}
connect(reply, &QNetworkReply::finished,
this, &QGeoRouteReplyOsmScoutOffline::networkReplyFinished);
connect(reply, static_cast<void(QNetworkReply::)(QNetworkReply::NetworkError)>(&QNetworkReply::error),
this, &QGeoRouteReplyOsmScoutOffline::networkReplyError);
connect(this, &QGeoRouteReplyOsmScoutOffline::destroyed,
reply, &QNetworkReply::deleteLater);
}
После проверки переданного в конструктор указателя на корректность, обработчики привязываются к посылаемым сигналам. Последние две связки — для обработки ошибки сетевого соединения и для освобождения памяти — не представляют большого интереса. А обработку успешного ответа от сервера рассмотрим подробнее:void QGeoRouteReplyOsmScoutOffline::networkReplyFinished
void QGeoRouteReplyOsmScoutOffline::networkReplyFinished()
{
QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
if (reply->error() != QNetworkReply::NoError)
return;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonBody = jsonDoc.object().value(QStringLiteral("body")).toObject();
QJsonArray jsonPath = jsonBody.value(QStringLiteral("latLonPoints")).toArray();
QList<QGeoCoordinate> coords;
for (QJsonValue value : jsonPath) {
QJsonArray coord = value.toArray();
coords.append(QGeoCoordinate(coord.at(0).toDouble(), coord.at(1).toDouble()));
}
QGeoRoute route;
route.setPath(coords);
route.setRequest(request());
setRoutes({ route });
setFinished(true);
}
В этом методе из пришедшего с сервера json-ответа извлекаем массив latLonPoints с точками маршрута, который преобразуем в список объектов координат, сохраняем как маршрут (здесь мы подразумеваем, что маршрут один) и указываем, что обработка запроса произведена успешно.После вызова метода setFinished посылается сигнал finished. Он обрабатывается упомянутым ранее методом replyFinished, сообщающим приложению, использующему плагин, что маршрут построен.Кстати, что там с приложением? Особенности yaml-файлаМы почти готовы собрать и установить rpm-пакет, чтобы перейти к использованию плагина в своих приложениях. Осталось подкорректировать стандартный yaml-файл, создаваемый Аврора IDE, или написать свой с нуля.qtgeoservices-osmscoutoffline.yaml
Name: qtgeoservices_osmscoutoffline
Summary: QtGeoServices OSM Scout Offline with Sight Safary routing
Version: 0.5.0
Release: 1
Group: System/Libraries
URL: https://github.com/osanwe/qtgeoservices-osmscoutoffline
License: BSD-3-Clause
Sources:
- '%{name}-%{version}.tar.bz2'
Description: |
QtGeoServices OSM Scout Offline with Sight Safary routing
Configure: none
Builder: qtc5
PkgConfigBR:
- Qt5Core
- Qt5Location
- Qt5Positioning
- Qt5Network
Files:
- '%{_libdir}/qt5/plugins/geoservices/libqtgeoservices_osmscoutoffline.so'
Самое важное изменение — блок Files, в котором прописывается путь установки скомпилированной библиотекиИщем себя на картеВ новом проекте, созданном Aurora IDE, по умолчанию создаются четыре QML элемента:
- CoverPage.qml — данный элемент отвечает за вывод наиболее важной информации о приложении когда оно свернуто.
- FirstPage.qml — в новом проекте эта страница является первой.
- SecondPage.qml — не будем использовать.
- harbour-walking.qml — название данного файла совпадает с названием проекта; в нем задаются элемент инициализации и элемент обложки приложения.
Для нашего примера потребуется только FirstPage.qml, поэтому SecondPage.qml можно удалить. Далее мы удаляем всё содержимое FirstPage.qml, переименовываем его в MainPage.qml и приступаем к реализации нашего приложения.Первым делом добавляем все необходимые импорты для отображения карты и получения информации с GPS-приемника устройства. Также у нас есть некоторое множество дополнительно реализованных элементов, которые расположены в директории qml/views. Для того, чтобы у нас появился доступ ко всем элементам в данном каталоге необходимо добавить соответствующий импорт.
import QtQuick 2.5
import QtLocation 5.0
import QtPositioning 5.0
import Sailfish.Silica 1.0
import "../views"
Добавляем корневой для данного QML файла элемент Page и начинаем его заполнять.Page
Page {
id: page
property bool mapFollowing: false
property var mapGpsPosition: positionSource.position.coordinate
property var mapCenterPosition: QtPositioning.coordinate(NaN, NaN)
property var pressCoords: QtPositioning.coordinate(NaN, NaN)
allowedOrientations: Orientation.Portrait
Drawer {
id: drawer
anchors.fill: parent
open: true
backgroundSize: background.height
background: Item {
id: background
// some code
}
Plugin {
id: mapPlugin
name: "osmscoutoffline"
}
PositionSource {
id: positionSource
updateInterval: 1000
active: true
preferredPositioningMethods: PositionSource.AllPositioningMethods
}
Map {
id: map
function initMapCenter() {
var moscowCenterPos = QtPositioning.coordinate(55.751244, 37.618423)
if (mapGpsPosition.isValid) {
map.center = mapGpsPosition
mapFollowing = true
} else {
map.center = moscowCenterPos
}
map.zoomLevel = 15
}
function setMapCenterFromGps() {
if (mapGpsPosition.isValid) {
map.zoomLevel = 17
map.center.latitude = mapGpsPosition.latitude
map.center.longitude = mapGpsPosition.longitude
mapFollowing = true
}
}
anchors.fill: parent
plugin: mapPlugin
onCenterChanged: {
mapCenterPosition = center
if (mapFollowing && center !== mapGpsPosition)
mapFollowing = false
}
Component.onCompleted: map.initMapCenter()
MapMarker {
coordinate: mapGpsPosition
visible: mapGpsPosition.isValid
source: "../images/mylocation.svg"
}
Connections {
target: page
onMapGpsPositionChanged: {
if (mapFollowing)
map.setMapCenterFromGps()
}
}
}
}
}
Что здесь было добавлено:
- Дополнительные переменные:
- mapFollowing — переменная в которой хранится флаг следования за изменяющийся позицией, получаемой с GPS датчика устройства; данное поведение будет воспроизводиться в том случае, если мы вызовем далее описанную функцию setMapCenterFromGps у объекта map и не будем изменять область вывода карты вручную.
- mapGpsPosition — переменная в которой хранится текущая координата, полученная от элемента PositionSource.
- mapCenterPosition — в данной переменной хранится координата центра карты.
- pressCoords — переменная для хранения координаты нажатия по карте.
- Drawer — контейнер который позволяет выдвинуть некоторую область на передний план, где могут быть расположены некоторые второстепенные элементы управления; выезжающая область задаётся через свойство: background (содержимое контейнера мы опишем позже).
- Plugin — непосредственно сам плагин, описанный ранее, который будет взаимодействовать с картой и предоставлять ей необходимые тайлы. В качестве значения параметра name устанавливаем имя плагина, указанное в json-файле.
- PositionSource — объект, предоставляющий информацию о текущей позиции телефона (для его работы необходимо включить GPS датчик устройства). Установка свойства active в значение true запускает работу данного элемента. Свойство updateInterval равное 1000 задает элементу таймаут по которому он будет сообщать о текущем положении раз в одну секунду. Свойство preferredPositioningMethods со значением PositionSource.AllPositioningMethods говорит о том, что данным элементом будут использоваться любые методы позиционирования.
- Map — элемент для отображения карты. С помощью свойства anchors.fill: parent указываем, что карта должна занимать все доступное пространство. Значение свойства plugin — идентификатор объявленного ранее плагина. По сигналу onCenterChanged выполняется обновление переменной mapCenterPosition, объявленной в начале элемента Page и в случае, если флаг mapFollowing был выставлен в true, он сбрасывается (это означает, что мы не хотим чтобы центр карты автоматически обновлялся в соответствии с данными, получаемыми с GPS-приемника; для того, чтобы вернуть данное поведение обратно, необходимо нажать на кнопку в элементе Drawer, который будет описан позже). По сигналу Component.onCompleted выполняются действия, которые необходимо произвести после полной инициализации карты; здесь вызывается функция initMapCenter у объекта Map, которая выставляет начальный масштаб и в случае, если у нас есть валидные данные с GPS-приемника, то они выбираются в качестве начальной позиции центра карты, в обратном случае выставляются координаты центра Москвы.
- MapMarker — элемент, унаследованный от MapQuickItem и расположенный в каталоге qml/views (с его реализацией можно ознакомиться в файлах исходника проекта) с предустановленными свойствами отображения границ изображений, их размером и якорной точкой. Таким образом, для данного элемента достаточно установить только иконку в свойстве source, его позицию на карте через свойство coordinate и флаг отображения в свойстве visible (так, например, если нам по каким-либо причинам не удалось получить валидные координаты с GPS-приемника, то и отображать этот элемент смысла нет).
- Connections — данный элемент позволяет задать ему объект, у которого мы хотим обрабатывать сигналы через свойство target. При подключении к сигналам в QML обычным способом является создание обработчика вида on<Signal>. Здесь мы отслеживаем изменение текущей координаты полученной от PositionSource и, если свойство mapFollowing выставлено в true, то происходит автоматическое обновление центра отображаемой области карты при изменении его координат.
Собираем данные исходники и проверяем. Результат должен получиться аналогично изображению ниже.
Добавляем в приложение возможность построения маршрутовДобавим в элемент Map элемент BusyIndicator, отвечающий за индикацию процесса выполнения запроса к online-сервису по построению маршрутов. Данный элемент мы центрируем по отношению к родительскому, а именно к Map через свойство anchors.centerIn. Мы делаем именно так, потому что, если мы отобразим выезжающую область Drawer, то размер отображаемой области карты станет меньше, и таким образом мы избежим наложение этих элементов друг на друга. Так как на картах используется достаточно большое количество цветов, для данного индикатора лучше использовать контрастный цвет, а именно черный. Его мы получаем из системной темы и задаем свойству color. Устанавливаем свойство size, используя стандартное значение перечисления данного элемента. В завершении по данному элементу мы указываем, что изначально он не отображается через свойство running, в дальнейшем это свойство будет изменяться по определенным событиям.
BusyIndicator {
id: routeLoadingIndicator
anchors.centerIn: parent
size: BusyIndicatorSize.Large
color: Theme.rgba(Theme.darkPrimaryColor, Theme.opacityOverlay)
running: false
}
Добавим в элемент Map еще два элемента типа MapMarker для отображения начальной и конечной точек маршрута. Данные элементы аналогичны тому, что был описан ранее и отличаются только иконкой. Туда же добавим элемент MapRoute для вывода самого маршрута. Он аналогично MapMarker расположен в каталоге qml/views (с его реализацией можно ознакомиться в файлах исходника проекта). Данный элемент унаследован от MapPolyline с предустановленными свойствами отображения границ линии и ее толщиной. Этот элемент имеет свойство path которое будет заполняться чуть дальше.
MapRoute { id: mapRoute }
MapMarker {
id: markerStart
visible: false
source: "../images/location.svg"
}
MapMarker {
id: markerFinish
visible: false
source: "../images/location.svg"
}
Добавляем в элемент Drawer объекты, позволяющие получать информацию о маршрутах. Элемент RouteQuery отвечает за формирование запроса на построение маршрута к онлайн сервису Sight Safari. Элемент RouteModel хранит полученные маршруты и связывается с RouteQuery с помощью параметра query. В качестве значения параметра plugin указывается наш плагин объявленный ранее. Параметру autoUpdate присваиваем false, чтобы маршрут перестраивался не при изменении начальных и конечных координат, а только по запросу (по нажатии на кнопку, которая будет располагаться в Drawer). Также, при получении сигнала onRoutesChanged мы устанавливаем параметр path у элемента MapRoute и останавливаем работу индикатора загрузки информации о маршруте.
RouteQuery { id: mapRouteQuery }
RouteModel {
id: mapRouteModel
plugin: mapPlugin
query: mapRouteQuery
autoUpdate: false
onRoutesChanged: {
routeLoadingIndicator.running = false
mapRoute.path = mapRouteModel.get(0).path
}
}
Теперь подготовим выезжающую область в элементе Drawer. Для этого заполним элементами управления Item который установили свойству background в Drawer. Опустим описание всех свойств следующих элементов, достаточно будет описать их назначение. В данном элементе отображаются точки начала и конца маршрута, и две кнопки. Первая кнопка перемещает центр отображаемой области карты в определившиеся GPS координаты (кнопка будет неактивна, если GPS координаты невалидны или текущий отображаемый центр совпадает с координатами полученными с GPS-приемника). Вторая кнопка выполняет построение маршрута между двумя заданными точками (кнопка становится активной только, если обе точки будут заданы). В следующем фрагменте кода мы используем элемент CoordField, который аналогично остальным пользовательским элементам расположен в каталоге qml/views (с его реализацией можно ознакомиться в файлах исходника проекта).Item
Item {
id: background
anchors.fill: parent
height: column.implicitHeight + column.anchors.topMargin + column.anchors.bottomMargin
Column {
id: column
anchors {
fill: parent
margins: Theme.paddingMedium
}
spacing: Theme.paddingMedium
width: parent.width
Label {
text: qsTr("Route from:")
font.bold: true
}
CoordField { id: startCoordField }
Label {
text: qsTr("Route to:")
font.bold: true
}
CoordField { id: endCoordField }
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.paddingMedium
Button {
text: qsTr("My position")
enabled: mapGpsPosition.isValid
&& (mapGpsPosition.latitude !== mapCenterPosition.latitude
|| mapGpsPosition.longitude !== mapCenterPosition.longitude)
onClicked: map.setMapCenterFromGps()
}
Button {
text: qsTr("Route")
enabled: startCoordField.coordinate.isValid && endCoordField.coordinate.isValid
onClicked: {
routeLoadingIndicator.running = true
mapRouteQuery.clearWaypoints()
mapRouteQuery.addWaypoint(startCoordField.coordinate)
mapRouteQuery.addWaypoint(endCoordField.coordinate)
mapRouteModel.update()
}
}
}
}
}
Данный элемент будет выглядеть так, как представлено на изображении ниже.
Добавим возможность управления отображения данным выезжающим элементом. Для этого поместим в элемент Map кнопку типа MapButton, которая аналогично остальным пользовательским элементам расположена в каталоге qml/views (с его реализацией можно ознакомиться в файлах исходника проекта). Этот элемент унаследован от IconButton и дополнен рамкой и фоном. Тут мы его прижимаем к правому нижнему углу и задаем отступы по краям. Устанавливаем элементу в свойстве icon.source стандартную иконку для меню. Указываем, что иконка элемента будет подсвечиваться тогда, когда элемент Drawer открыт. Также при получении сигнала onClicked мы будем менять состояние отображения Drawer на обратное.
MapButton {
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: Theme.paddingLarge
rightMargin: Theme.horizontalPageMargin
}
icon.source: "image://theme/icon-m-menu"
highlighted: drawer.open
onClicked: drawer.open ? drawer.hide() : drawer.show()
}
Раз уж мы добавили одну кнопку, добавим еще парочку для управления масштабом в элемент Map.Map
Column {
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: Theme.horizontalPageMargin
}
spacing: Theme.paddingLarge
MapButton {
id: buttonZoomIn
icon.source: "../images/zoom-plus.svg"
enabled: map.zoomLevel < map.maximumZoomLevel
onClicked: map.zoomLevel = Math.min(map.zoomLevel + 1.0, map.maximumZoomLevel)
}
MapButton {
id: buttonZoomOut
icon.source: "../images/zoom-minus.svg"
enabled: map.zoomLevel > map.minimumZoomLevel
onClicked: map.zoomLevel = Math.max(map.zoomLevel - 1.0, map.minimumZoomLevel)
}
}
Теперь, когда большая часть реализована, необходимо добавить возможность выбора начальной и конечной точек на карте к элементу Map. Для этого добавляем в него MouseArea, отвечающий за обработку нажатий по экрану устройства. При нажатии по области карты, в случае, если диалог был закрыт, то он отображается и координата нажатия сохраняется в объявленную в самом начале переменную pressCoords, после чего отображается диалог PointDialog, который аналогично остальным пользовательским элементам расположен в каталоге qml/views (с его реализацией можно ознакомиться в файлах исходника проекта). В нем выводится координата нажатия, сохраненная ранее в pressCoords, и две кнопки: “От” и “До”. При нажатии на кнопку любую из кнопок происходит передача текущей координаты в соответствующее поле в выезжающей области Drawer. Как только будут заданы обе координаты, кнопка построения маршрута станет активной.
MouseArea {
anchors.fill: parent
z: -1
onClicked: {
if (choosePointDialog.visible) {
choosePointDialog.visible = false
} else {
pressCoords = map.toCoordinate(Qt.point(mouse.x, mouse.y))
choosePointDialog.visible = true
}
}
}
PointDialog { id: choosePointDialog }
После нажатия на кнопку построения маршрута получится что-то наподобие изображения ниже.
На этом всё, подробнее о создании плагинов можно почитать на Хабре или в документации Qt. Мы же со своей стороны готовы дать любые пояснения в комментариях.Материалы для публикации подготовлены Петром Вытовтовым и Павлом Казеко с комментариями и правками от Кирилла Чувилина.
===========
Источник:
habr.com
===========
Похожие новости:
- [Oracle, Программирование, Совершенный код, IT-стандарты] Часть 2. Идентификация событий происходящих в Oracle PL/SQL
- [Python, Программирование, Умный дом, Интернет вещей] Простой Telegram-бот для получения информации через MQTT
- [Программирование, .NET, Visual Studio, Отладка] Почему в Visual Studio стек вызовов асинхронного кода иногда перевёрнут? (перевод)
- [Программирование, C] Почему стоит начать изучение программирования с языка C (перевод)
- [Python, Программирование, Микросервисы] Как превратить скрипт на Python в «настоящую» программу при помощи Docker (перевод)
- [Компьютерное железо, Видеокарты, Криптовалюты] NVIDIA изменила замедление добычи Ethereum для RTX 3060
- [Периферия] Два продвинутых пэка с устройствами для любительской звукозаписи — как они выглядят
- [Программирование] Влияние протокола языкового сервеар (LSP) на будущее IDE (перевод)
- [Системное программирование, Программирование микроконтроллеров, Распределённые системы, Робототехника, Транспорт] Разработчики встраиваемых систем не умеют программировать
- [Тестирование IT-систем, API, Тестирование веб-сервисов, Тестирование мобильных приложений] Что такое JSON
Теги для поиска: #_programmirovanie (Программирование), #_c++, #_qt, #_geoinformatsionnye_servisy (Геоинформационные сервисы), #_avrora_os (аврора ос), #_qt, #_geoservice, #_plagin (плагин), #_scout, #_karty (карты), #_navigatsija (навигация), #_sightsafari, #_api, #_qml, #_blog_kompanii_otkrytaja_mobilnaja_platforma (
Блог компании Открытая мобильная платформа
), #_programmirovanie (
Программирование
), #_c++, #_qt, #_geoinformatsionnye_servisy (
Геоинформационные сервисы
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:51
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр! Хотим рассказать о том, как создать плагин Qt GeoServices и использовать его в своём приложении на ОС Аврора. В этом посте мы подробно объясним, как научить приложение определять координаты устройства на карте и прокладывать оптимальные маршруты с помощью сервиса Sight Safari. Самые нетерпеливые могут пощупать готовый код плагина и демо-приложения на GitHub, всех остальных приглашаем под кат.Зачем писать свой плагинКогда приложению на Qt требуется поддержка карт, то первое, что приходит на ум — использовать QML-компонент Map. Но к нему нужен плагин, реализующий работу с провайдером данных для карты. Так что если ваше приложение должно работать offline или использовать сторонний API, стандартные плагины вас могут не устроить. Придётся реализовывать либо свой плагин, либо свою карту.Мы пойдём по первому пути: создадим плагин Qt GeoServices, который будет обращаться к offline-провайдеру тайлов для отображения карты и к Sight Safari. На Хабре уже рассказывали об этом сервисе для построения маршрутов (раз и два). Возможно, кому-то не помешает изучить базовую информацию о работе с картами в Аврора.Подготовка: настраиваем OSM Scout ServerДля offline-доступа к тайлам карты устанавливаем на наш телефон под управлением ОС Аврора OSM Scout Server — полностью автономное решение для навигации. Ещё нам понадобится дополнительный модуль со шрифтами Noto, которые используются для рендеринга карт.Чтобы тайлы возвращались в подходящем для отображения формате, выбираем профиль «Рекомендовано для карт с векторными и растровыми тайлами». Теперь осталось выбрать и скачать карты необходимого района в «Диспетчере карт». Они отобразятся списком, как на рисунке ниже. С чего начинается плагин: конфигурационный файлТеатр начинается с вешалки, а Qt-плагин — с конфигурационного json-файла. {
"Keys": ["osmscoutoffline"], "Provider": "osmscoutoffline", "Version": 100, "Experimental": false, "Features": [ "OfflineMappingFeature", "OnlineRoutingFeature" ] } #ifndef QGEOSERVICEPROVIDERFACTORYOSMSCOUTOFFLINE_H
#define QGEOSERVICEPROVIDERFACTORYOSMSCOUTOFFLINE_H #include <QObject> #include <QGeoServiceProviderFactory> class QGeoServiceProviderFactoryOsmScoutOffline : public QObject, public QGeoServiceProviderFactory { Q_OBJECT Q_INTERFACES(QGeoServiceProviderFactory) Q_PLUGIN_METADATA(IID "org.qt-project.qt.geoservice.serviceproviderfactory/5.0" FILE "../osmscoutoffline_plugin.json") public: QGeoRoutingManagerEngine *createRoutingManagerEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const; QGeoMappingManagerEngine *createMappingManagerEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const; }; #endif // QGEOSERVICEPROVIDERFACTORYOSMSCOUTOFFLINE_H #include "qgeoserviceproviderfactoryosmscoutoffline.h"
#include "qgeoroutingmanagerengineosmscoutoffline.h" #include "qgeotiledmappingmanagerengineosmscoutoffline.h" QGeoRoutingManagerEngine *QGeoServiceProviderFactoryOsmScoutOffline::createRoutingManagerEngine( const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const { return new QGeoRoutingManagerEngineOsmScoutOffline(parameters, error, errorString); } QGeoMappingManagerEngine *QGeoServiceProviderFactoryOsmScoutOffline::createMappingManagerEngine( const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const { return new QGeoTiledMappingManagerEngineOsmScoutOffline(parameters, error, errorString); } QGeoTiledMappingManagerEngineOsmScoutOffline::QGeoTiledMappingManagerEngineOsmScoutOffline(
const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) { QGeoCameraCapabilities cameraCaps; cameraCaps.setMinimumZoomLevel(0.0); cameraCaps.setMaximumZoomLevel(19.0); setCameraCapabilities(cameraCaps); setTileSize(QSize(256, 256)); QList<QGeoMapType> mapTypes; mapTypes << QGeoMapType(QGeoMapType::StreetMap, tr("Street Map"), tr("OSM Street Map"), false, false, 1); setSupportedMapTypes(mapTypes); QGeoTileFetcherOsmScoutOffline *tileFetcher = new QGeoTileFetcherOsmScoutOffline(this); tileFetcher->setParams(parameters); setTileFetcher(tileFetcher); *error = QGeoServiceProvider::NoError; errorString->clear(); } QGeoTiledMapReply *QGeoTileFetcherOsmScoutOffline::getTileImage(const QGeoTileSpec &spec)
{ QUrlQuery query; for (QString &key : m_params.keys()) query.addQueryItem(key, m_params[key].toString()); query.addQueryItem(QStringLiteral("x"), QString::number(spec.x())); query.addQueryItem(QStringLiteral("y"), QString::number(spec.y())); query.addQueryItem(QStringLiteral("z"), QString::number(spec.zoom())); QUrl url(QStringLiteral("http://localhost:8553/v1/tile")); url.setQuery(query); QNetworkRequest remoteRequest(url); QNetworkReply *reply = m_networkManager->get(remoteRequest); return new QGeoMapReplyOsmScoutOffline(reply, spec); } void QGeoMapReplyOsmScoutOffline::networkReplyFinished()
{ if (!m_reply) return; if (m_reply->error() != QNetworkReply::NoError) return; setMapImageData(m_reply->readAll()); setMapImageFormat("png"); setFinished(true); m_reply->deleteLater(); m_reply = 0; }
QGeoRoutingManagerEngine *QGeoServiceProviderFactoryOsmScoutOffline::createRoutingManagerEngine(
const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString) const { return new QGeoRoutingManagerEngineOsmScoutOffline(parameters, error, errorString); } QGeoRouteReply *QGeoRoutingManagerEngineOsmScoutOffline::calculateRoute(
const QGeoRouteRequest &request) { QGeoCoordinate start = request.waypoints()[0]; QGeoCoordinate end = request.waypoints()[1]; QString from = QStringLiteral("%1,%2").arg(QString::number(start.latitude()), QString::number(start.longitude())); QString to = QStringLiteral("%1,%2").arg(QString::number(end.latitude()), QString::number(end.longitude())); QUrlQuery query; query.addQueryItem(QStringLiteral("from"), from); query.addQueryItem(QStringLiteral("to"), to); query.addQueryItem(QStringLiteral("ratio"), QStringLiteral("1")); QUrl url(QStringLiteral("https://sightsafari.city/api/v1/routes/direct")); url.setQuery(query); QNetworkRequest remoteRequest(url); QNetworkReply *reply = mNetworkManager->get(remoteRequest); QGeoRouteReplyOsmScoutOffline routeReply = new QGeoRouteReplyOsmScoutOffline(reply, request, this); connect(routeReply, &QGeoRouteReplyOsmScoutOffline::finished, this, &QGeoRoutingManagerEngineOsmScoutOffline::replyFinished); connect(routeReply, static_cast<void(QGeoRouteReplyOsmScoutOffline::) (QGeoRouteReplyOsmScoutOffline::Error, const QString &)>(&QGeoRouteReplyOsmScoutOffline::error), this, &QGeoRoutingManagerEngineOsmScoutOffline::replyError); return routeReply; } void QGeoRoutingManagerEngineOsmScoutOffline::replyFinished()
{ QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender()); if (reply) emit finished(reply); } void QGeoRoutingManagerEngineOsmScoutOffline::replyError(QGeoRouteReply::Error errorCode, const QString &errorString) { QGeoRouteReply *reply = qobject_cast<QGeoRouteReply *>(sender()); if (reply) emit error(reply, errorCode, errorString); } QGeoRouteReplyOsmScoutOffline::QGeoRouteReplyOsmScoutOffline(QNetworkReply *reply,
const QGeoRouteRequest &request, QObject parent) : QGeoRouteReply(request, parent) { if (reply == nullptr) { setError(UnknownError, QStringLiteral("Null reply")); return; } connect(reply, &QNetworkReply::finished, this, &QGeoRouteReplyOsmScoutOffline::networkReplyFinished); connect(reply, static_cast<void(QNetworkReply::)(QNetworkReply::NetworkError)>(&QNetworkReply::error), this, &QGeoRouteReplyOsmScoutOffline::networkReplyError); connect(this, &QGeoRouteReplyOsmScoutOffline::destroyed, reply, &QNetworkReply::deleteLater); } void QGeoRouteReplyOsmScoutOffline::networkReplyFinished()
{ QNetworkReply *reply = static_cast<QNetworkReply *>(sender()); reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) return; QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll()); QJsonObject jsonBody = jsonDoc.object().value(QStringLiteral("body")).toObject(); QJsonArray jsonPath = jsonBody.value(QStringLiteral("latLonPoints")).toArray(); QList<QGeoCoordinate> coords; for (QJsonValue value : jsonPath) { QJsonArray coord = value.toArray(); coords.append(QGeoCoordinate(coord.at(0).toDouble(), coord.at(1).toDouble())); } QGeoRoute route; route.setPath(coords); route.setRequest(request()); setRoutes({ route }); setFinished(true); } Name: qtgeoservices_osmscoutoffline
Summary: QtGeoServices OSM Scout Offline with Sight Safary routing Version: 0.5.0 Release: 1 Group: System/Libraries URL: https://github.com/osanwe/qtgeoservices-osmscoutoffline License: BSD-3-Clause Sources: - '%{name}-%{version}.tar.bz2' Description: | QtGeoServices OSM Scout Offline with Sight Safary routing Configure: none Builder: qtc5 PkgConfigBR: - Qt5Core - Qt5Location - Qt5Positioning - Qt5Network Files: - '%{_libdir}/qt5/plugins/geoservices/libqtgeoservices_osmscoutoffline.so'
import QtQuick 2.5
import QtLocation 5.0 import QtPositioning 5.0 import Sailfish.Silica 1.0 import "../views" Page {
id: page property bool mapFollowing: false property var mapGpsPosition: positionSource.position.coordinate property var mapCenterPosition: QtPositioning.coordinate(NaN, NaN) property var pressCoords: QtPositioning.coordinate(NaN, NaN) allowedOrientations: Orientation.Portrait Drawer { id: drawer anchors.fill: parent open: true backgroundSize: background.height background: Item { id: background // some code } Plugin { id: mapPlugin name: "osmscoutoffline" } PositionSource { id: positionSource updateInterval: 1000 active: true preferredPositioningMethods: PositionSource.AllPositioningMethods } Map { id: map function initMapCenter() { var moscowCenterPos = QtPositioning.coordinate(55.751244, 37.618423) if (mapGpsPosition.isValid) { map.center = mapGpsPosition mapFollowing = true } else { map.center = moscowCenterPos } map.zoomLevel = 15 } function setMapCenterFromGps() { if (mapGpsPosition.isValid) { map.zoomLevel = 17 map.center.latitude = mapGpsPosition.latitude map.center.longitude = mapGpsPosition.longitude mapFollowing = true } } anchors.fill: parent plugin: mapPlugin onCenterChanged: { mapCenterPosition = center if (mapFollowing && center !== mapGpsPosition) mapFollowing = false } Component.onCompleted: map.initMapCenter() MapMarker { coordinate: mapGpsPosition visible: mapGpsPosition.isValid source: "../images/mylocation.svg" } Connections { target: page onMapGpsPositionChanged: { if (mapFollowing) map.setMapCenterFromGps() } } } } }
Добавляем в приложение возможность построения маршрутовДобавим в элемент Map элемент BusyIndicator, отвечающий за индикацию процесса выполнения запроса к online-сервису по построению маршрутов. Данный элемент мы центрируем по отношению к родительскому, а именно к Map через свойство anchors.centerIn. Мы делаем именно так, потому что, если мы отобразим выезжающую область Drawer, то размер отображаемой области карты станет меньше, и таким образом мы избежим наложение этих элементов друг на друга. Так как на картах используется достаточно большое количество цветов, для данного индикатора лучше использовать контрастный цвет, а именно черный. Его мы получаем из системной темы и задаем свойству color. Устанавливаем свойство size, используя стандартное значение перечисления данного элемента. В завершении по данному элементу мы указываем, что изначально он не отображается через свойство running, в дальнейшем это свойство будет изменяться по определенным событиям. BusyIndicator {
id: routeLoadingIndicator anchors.centerIn: parent size: BusyIndicatorSize.Large color: Theme.rgba(Theme.darkPrimaryColor, Theme.opacityOverlay) running: false } MapRoute { id: mapRoute }
MapMarker { id: markerStart visible: false source: "../images/location.svg" } MapMarker { id: markerFinish visible: false source: "../images/location.svg" } RouteQuery { id: mapRouteQuery }
RouteModel { id: mapRouteModel plugin: mapPlugin query: mapRouteQuery autoUpdate: false onRoutesChanged: { routeLoadingIndicator.running = false mapRoute.path = mapRouteModel.get(0).path } } Item {
id: background anchors.fill: parent height: column.implicitHeight + column.anchors.topMargin + column.anchors.bottomMargin Column { id: column anchors { fill: parent margins: Theme.paddingMedium } spacing: Theme.paddingMedium width: parent.width Label { text: qsTr("Route from:") font.bold: true } CoordField { id: startCoordField } Label { text: qsTr("Route to:") font.bold: true } CoordField { id: endCoordField } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: Theme.paddingMedium Button { text: qsTr("My position") enabled: mapGpsPosition.isValid && (mapGpsPosition.latitude !== mapCenterPosition.latitude || mapGpsPosition.longitude !== mapCenterPosition.longitude) onClicked: map.setMapCenterFromGps() } Button { text: qsTr("Route") enabled: startCoordField.coordinate.isValid && endCoordField.coordinate.isValid onClicked: { routeLoadingIndicator.running = true mapRouteQuery.clearWaypoints() mapRouteQuery.addWaypoint(startCoordField.coordinate) mapRouteQuery.addWaypoint(endCoordField.coordinate) mapRouteModel.update() } } } } } Добавим возможность управления отображения данным выезжающим элементом. Для этого поместим в элемент Map кнопку типа MapButton, которая аналогично остальным пользовательским элементам расположена в каталоге qml/views (с его реализацией можно ознакомиться в файлах исходника проекта). Этот элемент унаследован от IconButton и дополнен рамкой и фоном. Тут мы его прижимаем к правому нижнему углу и задаем отступы по краям. Устанавливаем элементу в свойстве icon.source стандартную иконку для меню. Указываем, что иконка элемента будет подсвечиваться тогда, когда элемент Drawer открыт. Также при получении сигнала onClicked мы будем менять состояние отображения Drawer на обратное. MapButton {
anchors { bottom: parent.bottom right: parent.right bottomMargin: Theme.paddingLarge rightMargin: Theme.horizontalPageMargin } icon.source: "image://theme/icon-m-menu" highlighted: drawer.open onClicked: drawer.open ? drawer.hide() : drawer.show() } Column {
anchors { right: parent.right verticalCenter: parent.verticalCenter rightMargin: Theme.horizontalPageMargin } spacing: Theme.paddingLarge MapButton { id: buttonZoomIn icon.source: "../images/zoom-plus.svg" enabled: map.zoomLevel < map.maximumZoomLevel onClicked: map.zoomLevel = Math.min(map.zoomLevel + 1.0, map.maximumZoomLevel) } MapButton { id: buttonZoomOut icon.source: "../images/zoom-minus.svg" enabled: map.zoomLevel > map.minimumZoomLevel onClicked: map.zoomLevel = Math.max(map.zoomLevel - 1.0, map.minimumZoomLevel) } } MouseArea {
anchors.fill: parent z: -1 onClicked: { if (choosePointDialog.visible) { choosePointDialog.visible = false } else { pressCoords = map.toCoordinate(Qt.point(mouse.x, mouse.y)) choosePointDialog.visible = true } } } PointDialog { id: choosePointDialog } На этом всё, подробнее о создании плагинов можно почитать на Хабре или в документации Qt. Мы же со своей стороны готовы дать любые пояснения в комментариях.Материалы для публикации подготовлены Петром Вытовтовым и Павлом Казеко с комментариями и правками от Кирилла Чувилина. =========== Источник: habr.com =========== Похожие новости:
Блог компании Открытая мобильная платформа ), #_programmirovanie ( Программирование ), #_c++, #_qt, #_geoinformatsionnye_servisy ( Геоинформационные сервисы ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 08:51
Часовой пояс: UTC + 5