Свой редактор для ОСМ

У JOSM`а - немного другой формат файла.
По поводу уид - http://wiki.openstreetmap.org/wiki/API_v0.6#Reliably_identifying_users - взамен user.

Ivan Komarov, проект пока еще не завел…
Думаю, побольше нужно написать, привести код в человеческий вид, а то стыдно показывать))

С Qt и начал, честно говоря… но что-то мы с его QDomDocument сразу не подружились… не то что с рапидХМЛ!
У меня при работой с QDomDocument вылетала прога так и не понял почему, вроде исключения старался поймать…
В общем не понравилось сразу… да и не слышал чтобы Qt’ешный парсер производительностью блистал)

AkMeR, Спасибо! Я думал это дело от АПИ 0.5 например осталось, просто в ДЖОСМе не обновили…

Нарисовал осмофайлик кое-как… в JOSM’е по кускам загрузил центр Москвы, osm файл весит 20 Мб
Никаких тормозов не наблюдается, хотя тут и тормозить нечему… все в одном большом VBO.

Render

Сперва просто линиями обвел, поскольку никакой триангуляции пока нет, сверху отмечены вершины темным.
Прикрутил примитивную двигалку, зумилку… Летает))

Проблема, как я и ожидал в работе с памятью… нужно очень быстро получать координаты точек по id’шникам.
Я сперва тупо перебором по массиву точек искал, чтобы по-быстрее посмотреть что получится)

Точек много… просто нодов в файле ~94’000. В полигонах вершин 114’000 т.к. некоторые ноды используются повторно.
По id’шникам координаты искать “влоб” не получится никак)

Сейчас заюзал Boost.unordered_map… пока очень шустро работает, но цифры не мерял, не знаю сколько оно памяти есть)
Кстати, 20 метровый файл RapidXML грузит секунд 10 наверно…

Так, к слову. Удалось на своей HTC-шке запустить OpenGL-ES -based приложение.

И сколько при этом памяти потребляет? И на каком железе? Правда, 20-мегабайтные файлы для редактирования вряд ли кто-то загружать будет, а то 10 секунд - это уже неудобно.

Программа предполагается кроссплатформенной?

И еще, в качестве пожелания и для обсуждения. Мне давно уже укажется, что хороший редактор для OSM должен иметь в себе препроцессор (встроенный или серверный). Дело в том, что сейчас порог входа в проект для новичков достаточно высок - необходимо обладать знаниями о раличных тегах и их сочетаниях, читать форум и вики для понимания существующих договоренностей в сообществе и так далее (знать английский, например). JOSM, Merkaartor и Potlatch уже предоставили возможность использования готовых пресетов для множества объектов, но и это не идеальное решение. Возможно, ситуацию изменит Mapzen, но его пока мало кто видел, да и не все готовы работать с онлайновым редактором.

Решением этой проблемы я вижу создание набора правил, обрабатывающих те или иные ситуации. По этим правилам программа автоматически заменяет “пользовательское” представление на принятое в проекте. Например, рисует человек линию, в списке типов отмечает - “улица” (не непонятный и общий highway, а просто “улица”). Дальше - какая улица? Он выбирает - “дворовый проезд”. И программа сама проставляет все необходимые теги, и пользователю совершенно не обязательно знать, что в базе это будет сохранено как набор highway=residential + living_street=yes.
То же самое, например, с релейшенами. Это сложное и не очень понятное понятие. Зачем забивать пользователю голову, если он может просто выделить куски улицы, нажать кнопки, скажем, “Транспорт” → “Автобус” и так обозначить маршрут? Или границу.
Или та же проблема типов дорог. Пусть программа смотрит - если дорога нарисована внутри границ города, то предлагать один список понятных типов, если за пределами - другой (федеральная трасса, дорога между городами и т.д.).
Еще на форуме обсуждалось наличие чисто локальных реалий - например, как обозначать аул или гаражи. Тут решение может быть таким же - программа автоматически ставит принятые сообществом “похожие” теги плюс свой, дополнительный. Тогда рендеры и другие редакторы увидят стандартное представление, а пользователи нашего редактора - именно свой “аул”. И будут уверены в том, что сохранилось именно так, а он не стал каким-нибудь hamlet’ом.
Еще есть просто сложные объекты - строения на островах внутри озера, берега рек всякие. Такие вещи надо знать как рисовать - вот это должно быть с таким тегом, чтобы рисовалось красиво, а вот это - таким релейшеном. Почему бы не переложить применение этого сакрального знания на программу?

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

Я считаю, что главное - дать возможность людям рисовать, максимально просто для них. А чтобы не переделывать потом, лучше сразу предоставить удобный для всех инструмент. Все помнят, что для работы с GPSMapEdit’ом нужно было проходить специальные курсы. С имеющимися редакторами OSM стало проще, но давайте сделаем еще один шаг к удобству!

Удваиваю и подписываюсь.

Ivan Komarov, а на этом HTC что стоит? Андройд или вин мобиайл? Жалко под андройд Qt нету, сам андройд это, конечно, круто, но не нравится мне их явская идеология)
Eugene, я обычно все приложения пишу на кроссплатформенных либах, winAPI практически никогда не использую.
Пока на стандартном С++, помимо Qt и либ nVidia для OpenGL (там обычный glut, glew и их Cg шейдеры) использую boost, RapidXML. RapidXML весь в исходниках и ничего за собой не тянет, думаю, будет работать на всем что имеет компилятор С++ со стандартной либой.

На счет 10 - секунд это я погорячился! Сейчас поставил мерялку на основе виндовской GetTickCount()… загрузка 20 мб xml файла ~6’700 ms, генерация ~16’000 way’ев из ~95’000 нодов ~620 мс.
Памяти сколько ест точно не знаю… если посмотреть в диспетчере задач, то при отображении 20Мб файла процесс занимает 105Мб. Если открыть очень маленький osm файл, то 18 Мб.
Железо: проц Q6600, 2Gb оперативы, видюшка 8800GT 512Мб

Пока особо не экономил… данные дублируются, например у нодов широта, долгота хранятся одновременно и в QString и в double.
Можно оптимизировать. При загрузке xml есть что сокращать… слишком дотошно отслеживаю соблюдение стандарта, может даже слишком много всяких проверок.

Про удобство редактирования идея очень правильная! Я, например, релейшены не осилил…
только один использую для полигонов с дырками… даже разбираться лень было как посмотрел что это такое. Порог входа для новичков действительно очень высок!
Тема очень важная! Если получится основу редактора написать обязательно нужно реализовывать что-то такое, иначе новый редактор просто не нужен)
На мой взгляд это должно быть удобно как чисто для редактирования (выравнивание, объединение, всякие умные залипания и т.д) так и для расстановки тегов… нужно это автоматизировать. Ну и это должно быть еще быстро и красиво)

Очень хорошо было бы прикрутить скриптовый язык. Я уже пытался нечто подобное сделать в своих проектах.
Как например в modo, да и во многих CAD’ах, рисовалках.
Сталкивался с проблемами связи Python и С++, которую так и не решил как мне бы хотелось.
Если конкретно, то хотелось бы иметь возможность вызывать методы экземпляров классов С++ созданных и работающих в программе из скрипта.
Возможно кто уже с таким сталкивался?
Вроде как такое умеет Kross… либа KDE. Но пока результата нет.
Думаю, со временем решаемо.

6.1 но рендер оказался при ближайшем рассмотрении софтверный :frowning: Но работает, что уже хорошо

та же фигня :slight_smile:

А встроенное, средствами QT, скриптование чем не угодило? В 4.6 они вовсе JS деклалировали как бэкэнд скриптовый. Но сам не щупал.

Не работал с ним, да и не хочется… он вроде как на ява-скрипт смахивает.
Не слышал чтобы Qt script где-нибудь использовали. Мне кажется лучше уж питон взять, либ для него много, туторов всяких… проблема только в связке.
Вон даже мапник под питон заточили)

UPD: Занялся вопросом прикручивания питона к C++! Давно меня эта тема интересует, да и на работе нужно бы использовать…
Раньше не получалось создать pyd из С++ исходников… ну никак, а сейчас получилось! Причем на удивление быстро и гладко!)
Использовал SWIG… можно работать с С++ классами из питона!!!

Теперь могу вызвать С++ функции, создавать экземпляры С++ классов в питоне, понимает примитивные типы, а также некоторые std, например vector, string.
Думаю теперь когда питон знает что такое нод, вей и т.д. нужно вызвать скрипт из С++ кода (это позволяет Python API) и как-нибудь передать ему указатель на класс содержащий координаты нодов, например.
Надеюсь тогда можно будет создавать алгоритмы обработки тегов или рисования, создание интерфейса и т.д без перекомпиляции приложения!
Отлаженные алгоритмы можно переносить в C++ и “увековечить” в проге, если они будут часто выполняться или требовать высокой производительности.
Правда как передать указатель на уже созданный С++ объект питону я еще не разобрался, даже не уверен возможно ли это, но надеюсь выкрутится можно…

swig может помочь, если хочется быстро что-то сварганить. При этом он не лишён косяков. Лучше делать без него - это не так уж и сложно. Я в своё время конструировал древовидные питоновские структуры прямо в C-модуле.

Вот, кстати, интересная новость: Nokia анонсировала официальный порт Qt для платформы Maemo 5
там даже отображение OSM’овских карт показали

UPD: Заюзал Boost.Python!
Получилось написать пример, который заработал!!!

Статический и динамический миры соединены!!!
Даже не верится что оно работает)))

Вот если кому интересно, надеюсь пригодится:

#include <stdlib.h>
#include <iostream>

#define BOOST_PYTHON_STATIC_LIB
#include <boost/python.hpp>
using namespace boost::python;

class CppClass {
public:
    CppClass()
    {
        t = 0;
    }
    int getNum() {
        return t;
    }
    void inc()
    {
        t++;
    }
private: 
    int t;
};

int main( int argc, char ** argv ) 
{
    setlocale(LC_ALL, "Russian");
    try 
    {
        Py_Initialize();

        object main_module((handle<>(borrowed(PyImport_AddModule("__main__")))));

        object main_namespace = main_module.attr("__dict__");
        main_namespace["CppClass"] = class_<CppClass>("CppClass")
                                                        .def("getNum",&CppClass::getNum)
                                                        .def("inc",&CppClass::inc);

        CppClass *cpp = new CppClass();

        main_namespace["cpp"] = ptr(cpp);

        PyRun_String( "print \"Python: \", cpp.getNum()\n", 
            Py_file_input, main_namespace.ptr(), main_namespace.ptr());

        std::cout << "C++ inc()" << std::endl;
        cpp->inc();

        PyRun_String( "print \"Python: \", cpp.getNum()\n"
            "cpp.inc()\n"
            "print \"Python inc()\" \n"
            "print \"Python: \", cpp.getNum()\n",
            Py_file_input, main_namespace.ptr(), main_namespace.ptr());

        std::cout << "C++: " << cpp->getNum() << std::endl;
        std::cout << "C++ inc()" << std::endl;
        cpp->inc();

        PyRun_String( "print \"Python: \", cpp.getNum()\n",
            Py_file_input, main_namespace.ptr(), main_namespace.ptr() );

        Py_Finalize();
    } 
    catch( error_already_set ) 
    {
        PyErr_Print();
    }

    system("pause");
    return EXIT_SUCCESS;
}

На выходе получаем:

Python:  0
C++ inc()
Python:  1
Python inc()
Python:  2
C++: 2
C++ inc()
Python:  3
Для продолжения нажмите любую клавишу . . .

PS:* кажется я эту тему уже, можно сказать, в блог превратил)))*

Это хорошо. Главное, чтобы он и дальше обновлялся так же часто - так мы можем быть уверены, что проект развивается и еще не умер, как было со многими другими :frowning:

А название у него планируется? А то как-то неудобно :slight_smile:

P.S. Я пока буду список замен для препроцессора составлять, раз идея была поддержана.

Пытаюсь сделать примитивное API для скриптов.
Пока заработала вот такая штука!!

>>> print mapData.nodesCount()
94746
>>> print mapData.nodes(1).lat
55.5781635

Команды питона, работают с классом mapData в котором все данные из осмофайла (ноды, веи, рилейшены)…

И вот в связи с этим вопрос возник…
Корректны ли теги если ключ задан, а в значении стоит пустая строка?
например, “building”=“”
Могу ли я считать что при отсутствии значения сам тег отсутствует?

Немного переделал базовые классы, поубирал строки, поставил нормальные типы, скомпилировал не в дебаге, а в релизе… объем занимаемой памяти упал до 48 Мб при работе с 20Мб файлом.
Время генерации VBO снизилось до ~80 мс… но это число я думаю уже сравнимо с погрешностью измерения, т.к. у счетчика вроде бы точность фиговая, хоть и выдает в мс.

PS: времени становится маловато… и в универе нагрузили, и на работе(

Пустых значений быть не должно, это кривости, считай что отсутствует.

Eugene, простите про название просмотрел)
Даже и не знаю… Пару лет назад назвал папку со старым проектом “Road Flow”)
Правда я тогда пытался собрать оболочку для PcCar… на форуме www.pccar.ru начитался)

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

Вот границу Москвы пилит где-то за 30-70 мс…

результат, правда, не очень нравится. Поскольку на выходе получаю набор отдельных треугольников, что довольно не экономно расходует память.
В идеале лучше бы разбивать многоугольник на выпуклые многоугольники, а потом рисовать их как GL_POLYGON, или GL_TRIANGLE_FAN.
Пересчитывать разбиение нужно при изменении многоугольника.
Надеюсь, позже этим займусь.

Вот прикручиваю дырки и возник вопрос:
Если у меня полигон имеет дырку… например в море остров как дырка.
Но в этой дырке есть полигон… на острове озеро)
То в этой ситуации это озеро уже новый полигон и никакими релейшенами с морем и островом не связано? Или же это “дырка в дырке”)

Приделал консоль, вполне можно юзать… даже довольно удобно!)
Нету правда восстановления команд по кнопкам вверх или вниз, как во всех нормальных консолях…

Сейчас питон понимает все простые типы… node, way, relation
и соответственно все типы, которые в них входят… т.е. tag, member
понимает коллекции… не знаю что это в питоне, но аналог в C++ std::vector<>
И поэтому есть доступ к объекту dataController в котором все ноды, веи и релейшены карты.
У меня уже было в каком-то посте упоминание… там он назывался mapData.

Теперь хочу простенький редактор приделать, чтобы не построчно, а сразу скриптом можно было код выполнять. Сохранять в файлы, загружать… добавлять в автозапуск и т.д.
Ну и в идеале нужно сделать возможность назначать выполнение скрипта из такого-то файлика по событию…
Например, по нажатию кнопки на тулбаре, или перед отправкой данных на сервак осма.
Это даст возможность писать на питоне много полезных инструментов, проверялок, подсказывалок и т.д.
В питоне доступен Qt… т.е. при большом желании можно создавать свои диалоги, формы и т.д. в Qt много всего)
С формами, наверное, нужно будет запускать интерпретатор питона в отдельном потоке… чтобы не подвесило. Я этот момент еще не проверял.
А вообще куча идей для плагинов… от рисовалок, до получалок ошибок на карте от юзеров через какой-нибудь сервак)))

При встраивании питона я использую Boost.Python, а для “опитонивания” Qt разработчики использовали SIP, поэтому, например, QString из PyQt не работает с бустом… Но все простые типы, конечно, понимает, включая std::string… поэтому вполне удобно и так. Возиться со скриптами рановато, поскольку сейчас даже выбирать ничего нельзя и карту двигать по-нормальному, но мне нужна связка питона и С++ для проекта на работе, поэтому и углубился в это дело.

PS: скриншот по размеру меньше… тут он чего-то сам растягивается. Надеюсь только у меня так)

Потестил простенькое выделение, толку от него никакого, только оценил производительность… пока только на вершинах
Приделал “редактор” питоновских скриптов… как я и думал для создания нормальных Qt’шных форм из питона нужно создавать QApplication в скрипте…
Но такая штука не работает( Для того чтобы использовать Qt думаю нужно передать в скрипт указатель на основную форму из C++… а поскольку Boost.Python и SIP несовместимы этого сделать не могу(
Есть довольно молодой проект PySide, который использует Boost.Python в качестве обертки над Qt
Вот при его использовании, думаю, таких проблем не будет, но под винду они пока Qt не собрали…
Select tool
Все тот же центр Москвы, ~100’000 вершин. Ищет вершины входящие в прямоугольник вроде без тормозов… как двигаю мышкой, так и выделяет сразу.

Для хранения данных пытаюсь использовать boost::multi_index_container, пока на нем и сделано, но толком в нем еще не разобрался, некоторые вещи не понимаю, есть глюки)

у qt есть собственный механизм плагабельных модулей (ака плагинов)
насколько помню особенных хависимостей на интерфейсы там нет.
т.е. вполне можно плагин на pyqt подрубить к прилаге на C++ QT
хотя возможно я и ошибаюсь
тему эту глядел поверхностно

Не знаю точно можно ли прикрутить PyQT’ешные плагины к С++ проге… поиск в инете толком ничего не дал.

Вот небольшая заметка для программеров, потом может попробую в вики написать.
Для отправки запросов на сервак OSM’а где нужна авторизация (например при изменении данных) использую такую штуку на Qt:

OSMQHttp.hpp


#pragma once
#include <QHttp>
#include <QString>
#include <QEventLoop>

class OSMResponse
{
public:
    bool error;
    QHttp::Error errorType;
    QString errorMessage;
    QString response;

    OSMResponse()
    {
        error = false;
        errorType = QHttp::NoError;
        errorMessage = "";
        response = "";
    }
};

class OSMQHttp : public QObject
{
    Q_OBJECT

public:
    OSMQHttp(QString login = "", QString password = "", QString softwareId = "");
    virtual ~OSMQHttp();

    void SetUser(QString login, QString password);
    void SetSoftwareId(QString softwareId);

    // send PUT request and wait until finished
    OSMResponse sendPut(QString path, QString data);

private:
    QHttp *http;
    QHttpRequestHeader header;
    OSMResponse response;
    int requestID;        // id of current request
    QEventLoop loop;    // event loop used to block until request finished

private slots:
    void httpRequestFinished(int requestId, bool error);
};

OSMQHttp.cpp


#include "OSMQHttp.hpp"
#include <iostream>
#include <QUrl>

OSMQHttp::OSMQHttp(QString login, QString password, QString softwareId)
{
    http = new QHttp();
    requestID = -1;

    http->setUser(login, password);
    connect(http, SIGNAL(requestFinished(int, bool)), SLOT(httpRequestFinished(int, bool)));

    header.setValue("Content-Type", "text/xml");
    header.setValue("User-Agent", softwareId);
    header.setValue("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");
    header.setValue("Connection", "keep-alive");
}

OSMQHttp::~OSMQHttp() { }

void OSMQHttp::SetUser(QString login, QString password)
{
    http->setUser(login, password);
}

void OSMQHttp::SetSoftwareId(QString softwareId)
{
    header.setValue("User-Agent", softwareId);
}

OSMResponse OSMQHttp::sendPut(QString path, QString content)
{
    QByteArray data = content.toAscii();

    QUrl url(path);
    QHttp::ConnectionMode mode = url.scheme().toLower() == "https" ? QHttp::ConnectionModeHttps : QHttp::ConnectionModeHttp;

    http->setHost(url.host(), mode, url.port() == -1 ? 0 : url.port());

    header.setRequest("PUT", url.toEncoded(QUrl::RemoveScheme | QUrl::RemoveAuthority));
    header.setValue("Host", url.host());
    header.setValue("Content-Length", QString::number(data.size()));

    requestID = http->request(header, data);

    /// block until the request is finished
    loop.exec();

    return response;
}

void OSMQHttp::httpRequestFinished(int requestId, bool error)
{
    /// check to see if it’s the request we made
    if(requestId != requestID) return;

    OSMResponse response;
    response.error = error;

    if(error){
        response.errorMessage = http->errorString();
        response.errorType = http->error();
        //std::cout << "Error: " << http->errorString().toStdString().data() << "   " << requestId << std::endl;
    }else{
        QByteArray data = http->readAll();
        response.response = QString(data);
        //std::cout << "Response: " << QString(data).toStdString().data() << std::endl;
    }

    this->response = response;

    /// end the loop
    loop.exit();
}

Испльзовать можно так:


#include <stdlib.h>
#include <QApplication>
#include "OSMQHttp.hpp"

int main(int argc, char *argv[])
{    
    setlocale(LC_ALL, "Russian");

    QApplication qapp(argc, argv);

    QString login("MyLogin");
    QString password("MyPass");

    QString url("http://api.openstreetmap.org/api/0.6/changeset/create");
    QString content("<?xml version='1.0' encoding='UTF-8'?>\n<osm version='0.6' generator='JOSM'>\n<changeset  id='0' open='false'>\n<tag k='created_by' v='MyNewEditor' />\n<tag k='comment' v='editor test' />\n</changeset>\n</osm>");

    OSMQHttp osmhttp;
    osmhttp.SetUser(login, password);
    osmhttp.SetSoftwareId("MyNewEditor");

    OSMResponse response = osmhttp.sendPut(url, content);
    std::cout << "OSMResponse: " << response.response.toStdString().data() << std::endl;

    return qapp.exec();
}

Присоединяюсь к вопросу: когда будет репозиторий?
Лично я бы предпочёл GitHub…

Upliner, репозиторий есть, но исходники пока закрыты. Открывать исходники с целью получить поддержку программистов сообщества рано, да и наверное пока не нужно… а с целью просто их показать не хотелось бы. Открывают исходники для того чтобы их кто-то улучшал используя в своих проектах, а не потому что опенсорсеры такие добрые) У меня на данный момент улучшать нечего… ничего работающего еще нет(

В идеале классно бы было выделить несколько “подпроектов” небольших…
А именно пытаюсь сделать такое:

  1. написать хорошую связку С++ и питона. Впринципе оно есть и работает через Boost.Python, но написать, например, виджеты на PyQt в скриптах, которые выполняет встроенный интерпретатор, не получается.
    Хочу добавить возможности использовать PyQt или PySide (но его еще нет под виндой) в скриптах и можно выделить такую штуку в отдельную GPL или даже LGPL либу.
  2. сделать либу - набор виджетов Qt… по аналогии с modo. Вот примеры интерфейса http://www.google.com/images?q=modo
    т.е. сделать удобные виджеты и повозможности прикрутить к ним обертку для питона чтобы их можно было использовать из плагинов. И тоже отправить это все сообществу под LGPL. Как-то находил тему насчет виджетов из blender’а… читал вроде бы в каком-то последующем релизе будет вынесен весь GUI из исходников проекта в отдельную либу, но пока вроде все вместе. Еще давно немного листал его исходники… на мой взгляд там творился ужас) Написано было на С, рисовалось вроде в одном OpenGL контексте… не очень красиво на мой взгляд, но ведь “снаружи” выглядит и работает все довольно классно.

Эти две идеи не касаются ОСМ’а или редактора карт вообще. Они подойдут для многих задач.
А что касательно ОСМ’а, то тут пока пробую сделать загружалку данных с сервера и на него на С++… кусочек кода для работы с сетью уже привел выше.
Это тоже можно было бы выделить в либу.

Появился вопрос по организации ОСМ’а:

У каждого объекта в осме (node, way, relation… может и вместе с changeset’ами, юзерами и т.д) есть свой id. Вот мне хотелось бы узнать уникален ли он для всех объектов вообще или только для объектов определенного типа. т.е., например, могут ли существовать в базе node и way с одинаковыми id?