Logo Search packages:      
Sourcecode: qlandkarte version File versions  Download package

CGarminDBMap.cpp

/**********************************************************************************************
    Copyright (C) 2006, 2007 Oliver Eichler oliver.eichler@gmx.de

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA

  The TDB decoding stuff is based on the work of
  Konstantin Galichsky (kg@geopainting.com), http://www.geopainting.com

  Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd.
  or one of its subsidiaries.

**********************************************************************************************/
#include "CCentralResources.h"
#include "CGarminDBMap.h"
#include "CToolViewMap.h"
#include "gmapsupp.h"
#include "IMap.h"
#include "CGpx.h"
#include "GeoMath.h"
#include "icons.h"

#include <IDevice.h>

#include <QtGui>

#pragma pack(1)

struct tdb_hdr_t
{
    quint8  type;
    quint16 size;
};

struct tdb_product_t : public tdb_hdr_t
{
    quint32 id;
    quint16 version;
    char *  name[];
};

struct tdb_map_t : public tdb_hdr_t
{
    quint32 id;
    quint32 country;
    qint32  north;
    qint32  east;
    qint32  south;
    qint32  west;
    char    name[];
};

struct tdb_map_size_t
{
    quint16 dummy;
    quint16 count;
    quint32 sizes[1];
};

struct tdb_copyright_t
{
    quint8  type;
    quint16 count;
    quint8  flag;
    char    str[];
};

struct tdb_copyrights_t : public tdb_hdr_t
{
    tdb_copyright_t entry;
};

#pragma pack(0)

/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
CGarminDBMap::CGarminDBMap(QTabWidget * parent)
: QObject(parent)
, tab(parent)
, activeMap(&maps[tr("--- choose map ---")])
, overlayMap(0)
, activeMapLevel(0)
, activeOvlLevel(0)
{
    toolview = new CToolViewMap(0,*this);
    toolview->show();
    parent->addTab(toolview,QPixmap(iconMap16x16),"");
    parent->setTabToolTip(parent->indexOf(toolview), tr("Map View"));

    QSettings   cfg;
    QString     path;
    QStringList l = cfg.value("maps/registered").toStringList();

    foreach(path,l)
        loadTDB(path);

    QString name;
    name = cfg.value("maps/current",tr("--- choose map ---")).toString();
    toolview->comboSelectMap->setCurrentIndex(toolview->comboSelectMap->findText(name));
    name = cfg.value("maps/overlay",tr("--- choose map ---")).toString();
    toolview->comboSelectOverlay->setCurrentIndex(toolview->comboSelectOverlay->findText(name));

}


CGarminDBMap::~CGarminDBMap()
{
}


void CGarminDBMap::gainFocus()
{
    if(tab) {
        if(tab->currentWidget() != toolview) {
            tab->setCurrentWidget(toolview);
        }
    }
}


const QList<CGarminImg*>& CGarminDBMap::getVisibleTiles(maptype_e type)
{
    if(type == eMap && activeMap) {
        return activeMap->useBaseMap ? activeMap->base : activeMap->visible;
    }
    else if(type == eOvl && overlayMap) {
        return overlayMap->useBaseMap ? overlayMap->base : overlayMap->visible;
    }
    return maps[tr("--- choose map ---")].base;
}


void CGarminDBMap::loadIMG(const QString& filename)
{
    map_t * map = 0;
    CGarminImg  * img = 0;
    try
    {
        img = new CGarminImg(this);
        img->load(filename,true);
    }
    catch(exce_garmin_t e) {
        QMessageBox::warning(0,tr("Error"),e.msg,QMessageBox::Ok,QMessageBox::NoButton);
        if(img) delete img;
        return;
    }

    QString name(img->getMapDesc().trimmed());
    if(maps.contains(name)) {
        toolview->comboSelectMap->setCurrentIndex(toolview->comboSelectMap->findText(name));
        delete img;
        return;
    }
    map = &maps[name];
    map->name = name;

    const QMap<QString,subfile_desc_t>&  subfiles = img->getSubFiles();
    QMap<QString,subfile_desc_t>::const_iterator subfile = subfiles.begin();

    while(subfile != subfiles.end()) {
        double north = RAD_TO_DEG * subfile->north;
        double east  = RAD_TO_DEG * subfile->east;
        double south = RAD_TO_DEG * subfile->south;
        double west  = RAD_TO_DEG * subfile->west;

        if(map->north < north) map->north = north;
        if(map->east  < east)  map->east  = east;
        if(map->south > south) map->south = south;
        if(map->west  > west)  map->west  = west;

        ++subfile;
    }

    gpProj->registerMap(map->name,map->south,map->north,map->west,map->east);
    img->load(filename);

    map->basemap    = filename;
    map->img        = img;

    tile_t * tile   = &map->tiles[name];
    tile->name      = name;
    tile->id        = 0;
    tile->north     = map->north;
    tile->east      = map->east;
    tile->south     = map->south;
    tile->west      = map->west;

    tile->key       = name;

    QFileInfo finfo(filename);
    tile->file      = filename;
    tile->memSize   = finfo.size();

    registerMap(*map);

    toolview->comboSelectMap->setCurrentIndex(toolview->comboSelectMap->findText(name));

}


void CGarminDBMap::loadTDB(const QString& path)
{
    QDir dir(path);
    QStringList filter, files;
    filter << "*.TDB" << "*.tdb";
    dir.setNameFilters(filter);

    files = dir.entryList(filter,QDir::Files);

    QString file;
    foreach(file,files)
        decodeTDB(dir.absoluteFilePath(file));
}


void CGarminDBMap::decodeTDB(const QString& filepath)
{
    map_t * map = 0;
    quint32 basemapId = 0;
    QByteArray data;
    QFileInfo finfo(filepath);
    QFile file(filepath);

    file.open(QIODevice::ReadOnly);
    data = file.readAll();
    file.close();

    quint8 * const pRawData = (quint8*)data.data();
    tdb_hdr_t * pRecord     = (tdb_hdr_t*)pRawData;

    while((quint8*)pRecord < pRawData + data.size()) {
        switch(pRecord->type) {
            case 0x50:           // product name
            {
                tdb_product_t * p = (tdb_product_t*)pRecord;
                QString name((char*)p->name);
                if(maps.contains(name)) {
                    toolview->comboSelectMap->setCurrentIndex(toolview->comboSelectMap->findText(name));
                    return;
                }
                map = &maps[name];
                map->name = name;
            }
            break;

            case 0x42:           // base map
            {
                QSettings cfg;
                tdb_map_t * p = (tdb_map_t*)pRecord;

                basemapId   = p->id;

                p->north = (p->north >> 8) & 0x00FFFFFF;
                p->east  = (p->east >> 8)  & 0x00FFFFFF;
                p->south = (p->south >> 8) & 0x00FFFFFF;
                p->west  = (p->west >> 8)  & 0x00FFFFFF;

                double north = DEG(p->north);
                double east  = DEG(p->east);
                double south = DEG(p->south);
                double west  = DEG(p->west);

                if(map->north < north) map->north = north;
                if(map->east  < east)  map->east  = east;
                if(map->south > south) map->south = south;
                if(map->west  > west)  map->west  = west;

                cfg.beginGroup("maps/basemap");
                map->basemap = cfg.value(map->name,"").toString();
                cfg.endGroup();

                QFileInfo basemapFileInfo(map->basemap);
                if(!basemapFileInfo.isFile()) {
                    QString filename = QFileDialog::getOpenFileName( 0, tr("Select Base Map for ") + map->name
                        ,finfo.dir().path()
                        ,"Map File (*.img)"
                        );
                    if(filename.isEmpty()) {
                        maps.remove(map->name);
                        return;
                    }

                    cfg.beginGroup("maps/basemap");
                    cfg.setValue(map->name,filename);
                    cfg.endGroup();

                    map->basemap = filename;
                }
            }
            break;

            case 0x4C:           // map tiles
            {
                tdb_map_t * p = (tdb_map_t*)pRecord;
                if(p->id == basemapId) break;
                /*
                #ifdef WIN_DEBUG
                                    FILE *fp = NULL;
                                    fp = fopen( "report.txt","a");
                                    if (fp != NULL) {
                                      fprintf(fp,"tdb: tile=%s\n",p->name);
                                      fclose(fp);
                                    }
                #endif // WIN_DEBUG
                */
                //                    QLatin1String txt(p->name);
                //                    QString name(txt);
                                 // fromLocal8Bit(p->name);
                QString name = QString::fromLatin1(p->name);
                //                    QString name(p->name);
                // produce a unique key form the tile name and it's ID. Some tiles
                // might have the same name but never the same ID
                QString key = QString("%1 (%2)").arg(name).arg(p->id,8,10,QChar('0'));
                //qDebug() << key;

                tile_t * tile = &map->tiles[key];
                tile->id = p->id;
                tile->key = key;
                tile->name = name;
                tile->cname = p->name;
                tile->file.sprintf("%08i.img",p->id);
                tile->file = finfo.dir().filePath(tile->file);

                p->north = (p->north >> 8) & 0x00FFFFFF;
                p->east  = (p->east >> 8)  & 0x00FFFFFF;
                p->south = (p->south >> 8) & 0x00FFFFFF;
                p->west  = (p->west >> 8)  & 0x00FFFFFF;

                tile->north  = DEG(p->north);
                tile->east   = DEG(p->east);
                tile->south  = DEG(p->south);
                tile->west   = DEG(p->west);

                tile->memSize = 0;
                tdb_map_size_t * s = (tdb_map_size_t*)(p->name + name.size() + 1);
                for(quint16 i=0; i < s->count; ++i) {
                    tile->memSize += s->sizes[i];
                }
            }
            break;

            case 0x44:
            {
                QString str;
                QTextStream out(&str,QIODevice::WriteOnly);

                out << "<h1>" << map->name << "</h1>" << endl;

                tdb_copyrights_t * p = (tdb_copyrights_t*)pRecord;
                tdb_copyright_t  * c = &p->entry;
                while((void*)c < (void*)((quint8*)p + p->size + 3)) {

                    if(c->type != 0x07) {
                        out << c->str << "<br/>" << endl;
                    }
                    c = (tdb_copyright_t*)((quint8*)c + 4 + strlen(c->str) + 1);
                }

                copyright += str;
            }
            break;

            default:
                //qDebug() << "unknown TDB record type" << hex << pRecord->type << (char)pRecord->type;
                ;
        }

        pRecord = (tdb_hdr_t*)((quint8*)pRecord + pRecord->size + sizeof(tdb_hdr_t));
    }

    if(registerMap(*map)) {
        QSettings cfg;
        QStringList l = cfg.value("maps/registered").toStringList();
        if(!l.contains(finfo.dir().path())) {
            l << finfo.dir().path();
            cfg.setValue("maps/registered",l);
        }
    }
}


bool CGarminDBMap::registerMap(map_t& map)
{
    // create projection context and calculate
    // map areas
    gpProj->registerMap(map.name,map.south,map.north,map.west,map.east);

    QRectF rect;
    gpProj->fwdQRectF(DEG_TO_RAD * map.north, DEG_TO_RAD * map.east
        , DEG_TO_RAD * map.south, DEG_TO_RAD * map.west, rect);
    map.area = rect;

    QMap<QString,tile_t>::iterator tile = map.tiles.begin();
    while(tile != map.tiles.end()) {
        QRectF rect;
        gpProj->fwdQRectF(DEG_TO_RAD * tile->north, DEG_TO_RAD * tile->east
            , DEG_TO_RAD * tile->south, DEG_TO_RAD * tile->west, rect);
        tile->area = rect;
        ++tile;
    }

    // read base map for maplevels
    try
    {
        if(map.img.isNull()) {
            map.img = new CGarminImg(this);
            map.img->load(map.basemap);
        }
        map.base.push_back(map.img);

        const QMap<QString,subfile_desc_t>& subfiles                 = map.img->getSubFiles();
        QMap<QString,subfile_desc_t>::const_iterator subfile         = subfiles.begin();

        QVector<subfile_desc_t::maplevel_t>::const_iterator maplevel = subfile->maplevels.begin();
        while(maplevel != subfile->maplevels.end()) {
            if(!maplevel->inherited) {
                map_level_t ml;
                ml.bits  = maplevel->bits;
                ml.level = maplevel->level;
                ml.useBaseMap = true;
                map.maplevels << ml;
            }
            ++maplevel;
        }
    }
    catch(exce_garmin_t e) {
        // no basemap? bad luck!
        QMessageBox::warning(0,tr("Error"),e.msg,QMessageBox::Ok,QMessageBox::NoButton);
        maps.remove(map.name);
        activeMap = &maps[tr("--- choose map ---")];
        return false;
    }

    QSettings cfg;
    cfg.beginGroup("maps/key");
    map.key = cfg.value(map.name).toString();
    cfg.endGroup();

    // read first submap tile for maplevels
    if(!map.tiles.isEmpty()) {
        try
        {
            CGarminImg img(0);
            img.load(map.tiles.begin()->file,true);

            const QMap<QString,subfile_desc_t>& subfiles                 = img.getSubFiles();
            QMap<QString,subfile_desc_t>::const_iterator subfile         = subfiles.begin();
            QVector<subfile_desc_t::maplevel_t>::const_iterator maplevel = subfile->maplevels.begin();
            while(maplevel != subfile->maplevels.end()) {
                if(!maplevel->inherited) {
                    map_level_t ml;
                    ml.bits  = maplevel->bits;
                    ml.level = maplevel->level;
                    ml.useBaseMap = false;
                    map.maplevels << ml;
                }
                ++maplevel;
            }
            map.isTransparent = img.isTransparent();
        }
        catch(exce_garmin_t e) {

            if(map.key.isEmpty()) {

                QMessageBox::warning(0,tr("Error"),e.msg,QMessageBox::Ok,QMessageBox::NoButton);
                // unless it is the infamous lock problem, I can't help
                if(e.err != errLock) {
                    maps.remove(map.name);
                    activeMap = &maps[tr("--- choose map ---")];
                    return false;
                }
                // help is on the way!!!
                map.key = QInputDialog::getText(0,tr("However ...")
                    ,tr("<p><b>However ...</b></p>"
                    "<p>as I can read the basemap, and the information from the *tdb file,<br/>"
                    "I am able to let you select the map tiles for upload. To do this I<br/>"
                    "need the unlock key (25 digits) for this map, as it has to be uploaded<br/>"
                    "to the unit together with the map.</p>"
                    ));
                // no money, no brother, no sister - no key
                if(map.key.isEmpty()) {
                    maps.remove(map.name);
                    activeMap = &maps[tr("--- choose map ---")];
                    return false;
                }
                cfg.beginGroup("maps/key");
                cfg.setValue(map.name,map.key);
                cfg.endGroup();
            }
        }
    }

    copyDefinitionAreaInfo(map);

    activeMap = &map;

    emit sigRegisterMap(map.name, map.isTransparent);

    return true;
}


void CGarminDBMap::copyDefinitionAreaInfo(map_t& baseMap)
{

    const QMap<QString,subfile_desc_t>& subfiles = baseMap.img->getSubFiles();
    QMap<QString,subfile_desc_t>::const_iterator subfile;

    QString key;
    QStringList keys = baseMap.tiles.keys();
    foreach(key,keys) {
        tile_t& tile = baseMap.tiles[key];
        QString key2 = QString("%1").arg(tile.id,8,10,QChar('0'));

        subfile = subfiles.begin();
        while(subfile != subfiles.end()) {
            if(subfile->definitionAreas.contains(key2)) break;
            ++subfile;
        }

        tile.definitionArea.clear();
        if(subfile == subfiles.end()) {
            qWarning() << "!!!!!!!!!!!! no definition area !!!!!!!!!!!!!!!!!" << key2;
            XY pt;
            pt.u = tile.area.topLeft().x();
            pt.v = tile.area.topLeft().y();
            tile.definitionArea << pt;
            pt.u = tile.area.topRight().x();
            pt.v = tile.area.topRight().y();
            tile.definitionArea << pt;
            pt.u = tile.area.bottomRight().x();
            pt.v = tile.area.bottomRight().y();
            tile.definitionArea << pt;
            pt.u = tile.area.bottomLeft().x();
            pt.v = tile.area.bottomLeft().y();
            tile.definitionArea << pt;
            continue;
        }

        const CGarminPolygon& polygon = subfile->definitionAreas[key2];
        tile.definitionArea = polygon.points;
    }
}


void CGarminDBMap::slotChangeMap(const QString& key)
{
    activeMap = &maps[key];
    gpProj->switchMap(activeMap->name);
    emit sigMapChanged(activeMap->area);
}


void CGarminDBMap::slotChangeOverlay(const QString& key)
{
    overlayMap = &maps[key];
    emit sigMapChanged();
}


void CGarminDBMap::slotSaveMapSet()
{
    uploadSelectedTiles(true);
}


quint8 CGarminDBMap::selectVisibleTiles(maptype_e type, quint8 bits, const QRectF& area)
{
    quint8 level = 0;
    QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));

    if(type == eMap && activeMap) {
        level = activeMapLevel = selectVisibleTiles(*activeMap,bits,area);
    }
    else if(type == eOvl && overlayMap) {
        level = activeOvlLevel = selectVisibleTiles(*overlayMap,bits,area);
    }

    QApplication::restoreOverrideCursor();
    return level;
}


quint8 CGarminDBMap::selectVisibleTiles(map_t& map, quint8 bits, const QRectF& area)
{
    quint8 level = 0;
    // try to find matching map level in basemap maplevels
    QVector<map_level_t>::const_iterator ml = map.maplevels.begin();
    while(ml != map.maplevels.end()) {
        level           = ml->level;
        map.useBaseMap  = ml->useBaseMap;
        if(ml->bits >= bits) break;
        ++ml;
    }

    // found matching level in the basemap ?
    if(map.useBaseMap) {
        return level;
    }

    // make sure all tiles under area are loaded and in the visible list
    QMap<QString,tile_t>::iterator tile = map.tiles.begin();
    while(tile != map.tiles.end()) {
        if(tile->area.intersects(area)) {
            if(tile->img.isNull()) {
                try
                {
                    tile->img = new CGarminImg(this);
                    tile->img->load(tile->file);
                    map.visible << tile->img;
                }
                catch(exce_garmin_t e) {
                    qDebug() << e.msg;
                }
            }
        }
        ++tile;
    }
    return level;
}


void CGarminDBMap::releaseVisibleTiles()
{
    while(activeMap->visible.size()) {
        delete activeMap->visible.takeLast();
    }
}


void CGarminDBMap::selectTiles(const QVector<XY>& area)
{
    // collect selected areas to be used with the overlay map
    QList< QVector<XY> > selArea;

    QMap<QString,tile_t>::iterator tile = activeMap->tiles.begin();
    while(tile != activeMap->tiles.end()) {
        // qDebug() << tile->key;
        // looks funny but either the selected area can contain the definition area
        // or the definition area can contain the selected area. Either way the tile's
        // selection state must be toggled.
        if(testPolygonsForIntersect(area,tile->definitionArea)
        || testPolygonsForIntersect(tile->definitionArea,area)) {

            selArea << tile->definitionArea;

            if(activeMap->selected.contains(&(*tile))) {
                activeMap->selected.removeAll(&(*tile));
            }
            else {
                activeMap->selected.append(&(*tile));
            }
        }
        ++tile;
    }

    if(overlayMap) {

        // the area to compare will be the touched definition areas of the active
        // map. This will ensure that all overlay tiles intersecting with the map
        // tiles will get selected / deselected
        QVector<XY> area;
        foreach(area,selArea) {
            QMap<QString,tile_t>::iterator tile = overlayMap->tiles.begin();
            while(tile != overlayMap->tiles.end()) {
                // looks funny but either the selected area can contain the definition area
                // or the definition area can contain the selected area. Either way the tile's
                // selection state must be toggled.
                if(testPolygonsForIntersect(area,tile->definitionArea)
                || testPolygonsForIntersect(tile->definitionArea,area)) {

                    if(overlayMap->selected.contains(&(*tile))) {
                        overlayMap->selected.removeAll(&(*tile));
                    }
                    else {
                        overlayMap->selected.append(&(*tile));
                    }
                }
                ++tile;
            }
        }
    }

    toolview->updateMapList();
}


void CGarminDBMap::deselectTile(const QString& mapKey, const QString& tileKey)
{
    CGarminDBMap::map_t& map   = maps[mapKey];
    CGarminDBMap::tile_t& tile = map.tiles[tileKey];
    map.selected.removeAll(&tile);
    emit sigMapChanged(map.area);
}


// Upload the selected map tiles to the GPS or save them to a file.
void CGarminDBMap::uploadSelectedTiles(bool save)
{
    // index of the selected tiles
    QTreeWidgetItem * item;
    unsigned int cnt =  toolview->listSelectedMaps->topLevelItemCount();

    // list of data for the selected map tiles
    QList<gms_map_def_t> files;
    // keys to upload with the tiles
    QSet<QString> usedKeys;
    // map sets to which the tiles belong
    QSet<QString> usedMaps;
    // GPS device properties such as available memory and maximum tiles
    Garmin::DevProperties_t dev_properties;

    // buffer to hold the GMAPSUPP.IMG file with the maps to upload
    QByteArray buffer;
    // size of the file
    quint64 file_size;
    // number of map tiles in the file
    quint32 subfile_cnt;
    // skipped tiles (because they were invalid or the GPS limits were exceeded)
    quint32 skipped_cnt;
    // output: invalid tiles (having duplicated internal IDs)
    quint32 invalid_cnt;

    // maximum memory size of the GPS
    quint64 memory_limit = 0xFFFFFFFF;
    // maximum number of maps for the GPS
    quint32 maps_limit = 0xFFFF;
    // input: whether to disallow duplicate internal tile IDs
    quint32 no_duplicates = 1;

    // define pointers to the selected tiles and their map sets
    tile_t * curTile = 0;
    map_t  * curMap  = 0;

    // loop over the selected tiles
    for (unsigned int i = 0; i < cnt; ++i) {

        // current tile index
        item = toolview->listSelectedMaps->topLevelItem(i);

        // pointer to the map set of this tile
        curMap  = &maps[item->data(1,Qt::UserRole).toString()];

        // pointer to the actual tile in the set
        curTile = &curMap->tiles[item->data(0,Qt::UserRole).toString()];

        // create a data block for the tile
        gms_map_def_t file;
        file.id       = curTile->id;
        file.filename = curTile->file;
        file.tilename = curTile->name;
        file.mapname  = curMap->name;
        file.mapKey   = item->data(1,Qt::UserRole).toString();
        file.tileKey  = item->data(0,Qt::UserRole).toString();
        file.disabled = 0;

        // add it to the list
        files << file;

        // add the map set of the tile to the list
        usedMaps << file.mapname;

        // add the key of the map set, if any, to the list
        if(!curMap->key.isEmpty()) {
            usedKeys << QString(curMap->key);
        }
    }

    // nothing to do?
    if(files.isEmpty()) {
        QMessageBox::warning(0,tr("Error"),tr("No files to upload!"),QMessageBox::Ok,QMessageBox::NoButton);
        return;
    }

    // create a string of the keys, up to 10
    char keys_string[261];
    unsigned int keys_string_lt = 0;
    QSet<QString>::const_iterator key = usedKeys.begin();
    while(key != usedKeys.end()) {
        if(key->size() != 25) {  // length is hardcoded in gmapsupp.cpp
            QMessageBox::warning(0,tr("Wrong key size"),tr("Program error: wrong key size!"),QMessageBox::Ok,QMessageBox::NoButton);
            return;
        }
        for(int i=0; i<25 ; i++) {
            if(keys_string_lt > sizeof(keys_string)-3) {
                QMessageBox::warning(0,tr("Too many keys"),tr("Program limitation: too many keys!"),QMessageBox::Ok,QMessageBox::NoButton);
                return;
            }
            keys_string[keys_string_lt++] = key->toAscii().constData()[i];
        }
        keys_string[keys_string_lt++] = '\0';
        ++key;
    }
    keys_string[keys_string_lt++] = '\0';

    // get the device limits
    dev_properties.set.all = 0;
    int ask_device = 1;
    if (save) {
        int res = QMessageBox::question(0,QObject::tr("Query the GPS about limits?"),QObject::tr("Query the GPS about map tile number and memory size limits?"),QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
        if(res == QMessageBox::No) {
            ask_device=0;
        }
    }
    if (ask_device) {
        Garmin::IDevice * dev = 0;
        try
        {
            dev = gpResources->device();
            if(dev) {
                dev->getDevProperties(dev_properties);
            }
        }
        catch(int e) {
            if(dev == 0) return;
            QMessageBox::warning(0,tr("Device Inquiry Error"),dev->getLastError().c_str(),QMessageBox::Ok,QMessageBox::NoButton);
            if(e == Garmin::errSync) {
                gpResources->resetDevice();
            }
            return;
        }
    }

    // set the memory limit, if known
    if (dev_properties.set.item.memory_limit) {
        memory_limit =  dev_properties.memory_limit;
    }

    // set the limit on the number of maps, if known
    if (dev_properties.set.item.maps_limit) {
        maps_limit =  dev_properties.maps_limit;
    }

    // see whether the device has map memory
    if (!memory_limit) {
        QMessageBox::warning(0,tr("No memory"),tr("Your GPS cannot upload maps or the SD card is missing!"),QMessageBox::Ok,QMessageBox::NoButton);
        return;
    }

    // see whether the number of tiles to upload is too large
    if (cnt > maps_limit) {
        int res = QMessageBox::question(0,QObject::tr("Too many maps"),QObject::tr("There are too many maps for your GPS.  Selected: ") + QString::number(cnt) + QObject::tr(".  Possible: ") + QString::number(maps_limit) + QObject::tr(".  Just upload the first ") + QString::number(maps_limit) + QObject::tr("?"),QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
        if(res == QMessageBox::No) {
            return;
        }
    }

    // create the map set
    try
    {
        // try creating GMAPSUPP.IMG
        mkGMapSupp(files, usedMaps, usedKeys,
            memory_limit, maps_limit, no_duplicates,
            buffer, file_size, subfile_cnt, skipped_cnt, invalid_cnt);
        // deselect the maps that could not be uploaded
        // Note that it is very important to always keep your fingers crossed
        // when messing with code you do not have a vague clue about.
        // TODO: Also deselect from the list
        QList<gms_map_def_t>::iterator file = files.begin();
        while(file != files.end()) {
            if (file->disabled) deselectTile(file->mapKey, file->tileKey);
            file++;
        }

        // allow the user to verify acceptability of the map set
        if(!dev_properties.set.item.memory_limit || !dev_properties.set.item.maps_limit || skipped_cnt || invalid_cnt) {

            QString msg;
            msg += tr("Size of the map set is ") + QString::number(file_size) + tr(" bytes out of a maximum possible ") + (dev_properties.set.item.memory_limit? QString::number(memory_limit) : tr("that is unknown"));
            msg += tr(".\n\nNumber of map tiles is ") + QString::number(subfile_cnt) + tr(" out of a maximum possible ") + (dev_properties.set.item.maps_limit ? QString::number(maps_limit) : tr("that is unknown"));
            msg += tr(".\n\nSkipped tiles: ") + QString::number(skipped_cnt);
            msg += tr(".\nInvalid tiles: ") + QString::number(invalid_cnt);
            msg += tr(".\n\nProceed?");

            int res = QMessageBox::question(0,QObject::tr("Process the map set?"),msg,QMessageBox::Yes|QMessageBox::No,QMessageBox::No);
            if(res == QMessageBox::No) {
                return;
            }
        }

        // if we are to save the file, do so now
        if(save) {
            // ask for a file name to save the GMAPSUPP.IMG map set as
            QSettings cfg;
            QString pathMaps = cfg.value("path/maps","./").toString() + QObject::tr("/gmapsupp.img");
            QString filename = QFileDialog::getSaveFileName(0,tr("Select file ..."),pathMaps,"*.img");
            if(filename.isEmpty()) return;
            // pause
            QApplication::setOverrideCursor(Qt::WaitCursor);
            // write the file
            QDir dir(filename);
            QFile f(filename);
            f.open(QIODevice::WriteOnly);
            f.write(buffer);
            f.close();
            // resume
            QApplication::restoreOverrideCursor();
            return;
        }
    }
    catch(const QString& msg) {
        QMessageBox::warning(0,tr("Error"),msg,QMessageBox::Ok,QMessageBox::NoButton);
        return;
    }

    // upload it
    Garmin::IDevice * dev = 0;
    try
    {
        dev = gpResources->device();
        if(dev) {
            dev->uploadMap((uint8_t*)buffer.data(),buffer.count(),keys_string);
        }
    }
    catch(int e) {
        if(dev == 0) return;
        QMessageBox::warning(0,tr("Device Link Error"),dev->getLastError().c_str(),QMessageBox::Ok,QMessageBox::NoButton);
        if(e == Garmin::errSync) {
            gpResources->resetDevice();
        }
    }
}


void CGarminDBMap::downloadMapInfo()
{
    std::list<Garmin::Map_t> mapInfos;
    Garmin::IDevice * dev = 0;
    int nWarnings = 0;
    QString warnString;

    try
    {

        dev = gpResources->device();

        if(dev) {
            dev->queryMap(mapInfos);
        }

        std::list<Garmin::Map_t>::const_iterator mapInfo = mapInfos.begin();
        while(mapInfo != mapInfos.end()) {
            if(maps.contains(mapInfo->mapName.c_str())) {
                map_t& theMap = maps[mapInfo->mapName.c_str()];

                // Iterate over all tiles and compare their names to the one form
                // the info structure. You can't use the dictionary keys for that
                // because they contain the tile's name and ID.
                QMap<QString,tile_t>::iterator tile = theMap.tiles.begin();
                while(tile != theMap.tiles.end()) {
                    if(tile->cname == mapInfo->tileName) {
                        if(!theMap.selected.contains(&(*tile))) {
                            theMap.selected.append(&(*tile));
                        }
                        break;
                    }
                    ++tile;
                }
                if(tile == theMap.tiles.end()) {
                    nWarnings++;
                    if ( nWarnings < 20 ) {
                        QString s(tr("I can not find '%1' in map collection '%2'\n"));
                        s = s.arg(mapInfo->tileName.c_str()).arg(mapInfo->mapName.c_str());
                        warnString.append(s);
                    }
                }
            }
            else {
                nWarnings++;
                if ( nWarnings < 20 ) {
                    QString s(tr("I can not find map collection '%1' (tile '%2')\n"));
                    s = s.arg(mapInfo->mapName.c_str()).arg(mapInfo->tileName.c_str());
                    warnString.append(s);
                }
            }
            ++mapInfo;
        }
        if ( nWarnings > 0 ) {
            QString s(tr("...\nNumber of warnings = %1"));
            s = s.arg(nWarnings);
            warnString.append(s);
            QMessageBox::warning(0,tr("Missing map tile/collection ..."), warnString, QMessageBox::Ok );
        }
    }
    catch(int e) {
        if(dev == 0) return;
        QMessageBox::warning(0,tr("Device Link Error"),dev->getLastError().c_str(),QMessageBox::Ok,QMessageBox::NoButton);
        if(e == Garmin::errSync) {
            gpResources->resetDevice();
        }
    }
    toolview->updateMapList();
    gpResources->canvas().update();
}


void CGarminDBMap::getInfo(QPointF& pt, QMultiMap<QString,QString>& dict, IMap * theMap, bool streetOnly)
{
    dict.clear();

    // main map
    CGarminImg * tile;
    const QList<CGarminImg*>& mapTiles = getVisibleTiles(eMap);
    foreach(tile,mapTiles) {
        const QMap<QString,subfile_desc_t>& subfiles = tile->getSubFiles();
        QMap<QString,subfile_desc_t>::const_iterator subfile = subfiles.begin();
        while(subfile != subfiles.end()) {
            if(subfile->area.contains(pt)) {
                const QVector<subdiv_desc_t>& subdivs = subfile->subdivs;
                QVector<subdiv_desc_t>::const_iterator subdiv = subdivs.begin();
                while(subdiv != subdivs.end()) {

                    if((subdiv->level == activeMapLevel) && subdiv->area.contains(pt)) {
                        if (streetOnly) {
                            getInfoPolylines(*subdiv, pt, dict, theMap);
                            getInfoPolygons(*subdiv, pt, dict, theMap);
                        }
                        else {
                            getInfoPoints(*subdiv, pt, dict, theMap);
                            getInfoPOIs(*subdiv, pt, dict, theMap);
                            getInfoPolygons(*subdiv, pt, dict, theMap);
                            getInfoPolylines(*subdiv, pt, dict, theMap);
                        }

                    }
                    ++subdiv;
                }
            }
            ++subfile;
        }
    }

    // overlay map
    const QList<CGarminImg*>& ovlTiles = getVisibleTiles(eOvl);
    foreach(tile,ovlTiles) {
        const QMap<QString,subfile_desc_t>& subfiles = tile->getSubFiles();
        QMap<QString,subfile_desc_t>::const_iterator subfile = subfiles.begin();
        while(subfile != subfiles.end()) {
            if(subfile->area.contains(pt)) {
                const QVector<subdiv_desc_t>& subdivs = subfile->subdivs;
                QVector<subdiv_desc_t>::const_iterator subdiv = subdivs.begin();
                while(subdiv != subdivs.end()) {
                    if((subdiv->level == activeOvlLevel) && subdiv->area.contains(pt)) {
                        getInfoPoints(*subdiv, pt, dict, theMap);
                        getInfoPOIs(*subdiv, pt, dict, theMap);
                        getInfoPolylines(*subdiv, pt, dict, theMap);
                    }
                    ++subdiv;
                }
            }
            ++subfile;
        }
    }
}


void CGarminDBMap::getInfoPoints(const subdiv_desc_t& subdiv, QPointF& pt, QMultiMap<QString,QString>& dict, IMap * theMap)
{
    double limit = 10 * theMap->getScale();

    const QVector<CGarminPoint>& points = subdiv.points;
    QVector<CGarminPoint>::const_iterator point = points.begin();

    while(point != points.end()) {
        double distance = fabs(point->point.u - pt.x()) + fabs(point->point.v - pt.y());
        if(distance < limit) {
            QString str = point->labels.join(", ").simplified();
            if(!str.isEmpty()) {
                dict.insert(tr("Point of Interest"),point->labels.join(", "));
            }
        }
        ++point;
    }

}


void CGarminDBMap::getInfoPOIs(const subdiv_desc_t& subdiv, QPointF& pt, QMultiMap<QString,QString>& dict, IMap * theMap)
{
    double limit = 10 * theMap->getScale();

    const QVector<CGarminPoint>& points = subdiv.pois;
    QVector<CGarminPoint>::const_iterator point = points.begin();

    while(point != points.end()) {
        double distance = fabs(point->point.u - pt.x()) + fabs(point->point.v - pt.y());
        if(distance < limit) {
            QString str = point->labels.join(", ").simplified();
            if(!str.isEmpty()) {
                dict.insert(tr("Point of Interest"),point->labels.join(", "));
            }
        }
        ++point;
    }

}


void CGarminDBMap::getInfoPolylines(const subdiv_desc_t& subdiv, QPointF& pt, QMultiMap<QString,QString>& dict, IMap * theMap)
{

    int i = 0;                   // index into poly line
    int len;                     // number of points in line
    XY p1, p2;                   // the two points of the polyline close to pt
    double dx,dy;                // delta x and y defined by p1 and p2
    double d_p1_p2;              // distance between p1 and p2
    double u;                    // ratio u the tangent point will divide d_p1_p2
    double x,y;                  // coord. (x,y) of the point on line defined by [p1,p2] close to pt
    double distance;             // the distance to the polyline
    double shortest;             // shortest distance sofar

    QPointF resPt = pt;
    QString key, value;

    shortest = 50 * theMap->getScale();

    const QVector<CGarminPolygon>& polylines = subdiv.polylines;
    QVector<CGarminPolygon>::const_iterator line = polylines.begin();
    while(line != polylines.end()) {
        len = line->points.count();
        // need at least 2 points
        if(len < 2) {
            ++line;
            continue;
        }

        // see http://local.wasp.uwa.edu.au/~pbourke/geometry/pointline/
        for(i=1; i<len; ++i) {
            p1 = line->points[i-1];
            p2 = line->points[i];

            dx = p2.u - p1.u;
            dy = p2.v - p1.v;

            d_p1_p2 = sqrt(dx * dx + dy * dy);

            u = ((pt.x() - p1.u) * dx + (pt.y() - p1.v) * dy) / (d_p1_p2 * d_p1_p2);

            if(u < 0.0 || u > 1.0) continue;

            x = p1.u + u * dx;
            y = p1.v + u * dy;

            distance = sqrt((x - pt.x())*(x - pt.x()) + (y - pt.y())*(y - pt.y()));

            if(distance < shortest) {

                key = CGarminImg::polyline_typestr[line->type];

                if(!line->labels.isEmpty()) {
                    switch(line->type) {
                                 // "Minor depht contour"
                        case 0x23:
                                 // "Minor land contour"
                        case 0x20:
                                 // "Intermediate depth contour",
                        case 0x24:
                                 // "Intermediate land contour",
                        case 0x21:
                                 // "Major depth contour",
                        case 0x25:
                                 // "Major land contour",
                        case 0x22:
                        {
                            QString s = line->label1();
                            gpResources->convertHeight(s);
                            value = s;
                        }
                        break;

                        default:
                            value = line->labels.join(" ").simplified();
                    }
                }
                else {
                    value = "-";
                }
                resPt.setX(x);
                resPt.setY(y);
                shortest = distance;
            }

        }
        ++line;
    }

    if(!key.isEmpty()) {
        dict.insert(key,value);
    }

    pt = resPt;
}


void CGarminDBMap::getInfoPolygons(const subdiv_desc_t& subdiv, QPointF& pt, QMultiMap<QString,QString>& dict, IMap * )
{
    int     npol;
    int     i = 0, j = 0 ,c = 0;
    XY      p1, p2;              // the two points of the polyline close to pt
    double  x = pt.x();
    double  y = pt.y();
    QString value;

    const QMultiMap<quint16,CGarminPolygon>& polygons = subdiv.polygons;
    QMultiMap<quint16,CGarminPolygon>::const_iterator line = polygons.begin();
    while(line != polygons.end()) {

        npol = line->points.count();
        if(npol > 2) {
            c = 0;
            // see http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
            for (i = 0, j = npol-1; i < npol; j = i++) {
                p1 = line->points[j];
                p2 = line->points[i];

                if ((((p2.v <= y) && (y < p1.v))  || ((p1.v <= y) && (y < p2.v))) &&
                (x < (p1.u - p2.u) * (y - p2.v) / (p1.v - p2.v) + p2.u)) {
                    c = !c;
                }
            }

            if(c) {
                if(!line->labels.isEmpty()) {
                    dict.insert(tr("Area"), line->labels.join(" ").simplified());
                }
                //dict.insert(CGarminImg::polygon_typestr[line->type].str,QString::number(line->type,16));
            }
        }
        ++line;
    }
}


void CGarminDBMap::clear()
{
    QMap<QString,CGarminDBMap::map_t>::iterator map = maps.begin();

    while(map != maps.end()) {
        map->selected.clear();
        ++map;
    }
    toolview->updateMapList();
}


void CGarminDBMap::loadGPX(CGpx& gpx)
{
    QDomElement root = gpx.documentElement();

    // maps are not handled natively by GPX -> extension section
    if(root.namedItem("extension").isElement()) {

        const QDomNode& ext = root.namedItem("extension");

        QDomElement e = ext.firstChildElement();
        while(!e.isNull()) {
            if(e.nodeName() == "map") {
                QDomNamedNodeMap attr = e.attributes();
                QString mapKey = attr.namedItem("name").nodeValue();

                if(maps.contains(mapKey)) {
                    map_t& map = maps[mapKey];
                    QDomElement t = e.firstChildElement();
                    while(!t.isNull()) {
                        if(t.nodeName() == "tile") {
                            if(map.tiles.contains(t.toElement().text())) {
                                tile_t& tile = map.tiles[t.toElement().text()];
                                if(!map.selected.contains(&tile)) {
                                    map.selected.append(&tile);
                                }
                            }
                        }
                        t = t.nextSiblingElement();
                    }
                }
            }
            e = e.nextSiblingElement();
        }
    }
    toolview->updateMapList();
}


void CGarminDBMap::saveGPX(CGpx& gpx)
{
    if(toolview->listSelectedMaps->topLevelItemCount() == 0) return;

    QDomElement root = gpx.documentElement();
    QDomElement ext = gpx.createElement("extension");
    root.appendChild(ext);

    QMap<QString,map_t>::const_iterator map = maps.begin();
    while(map != maps.end()) {
        if(map->selected.isEmpty()) {
            ++map;
            continue;
        }

        QDomElement gpxMap = gpx.createElement("map");
        ext.appendChild(gpxMap);
        gpxMap.setAttribute("name",map.key());

        QList<tile_t*>::const_iterator tile = map->selected.begin();
        while(tile != map->selected.end()) {
            QDomElement gpxTile = gpx.createElement("tile");
            gpxMap.appendChild(gpxTile);
            QDomText _gpxTile_ = gpx.createTextNode((*tile)->name);
            gpxTile.appendChild(_gpxTile_);

            ++tile;
        }

        ++map;
    }
}

Generated by  Doxygen 1.6.0   Back to index