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

CGarminImg.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

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

  This source is based on John Mechalas documentation "Garmin IMG File Format" found
  at sourceforge. The missing bits and error where rectified by the source code of
  Konstantin Galichsky (kg@geopainting.com), http://www.geopainting.com

**********************************************************************************************/

#include "Platform.h"
#include "CGarminImg.h"
#include "GarminTypedef.h"
#include "GarminStrTbl.h"
#include "IProjection.h"
#include <QtCore>

#undef DEBUG_SHOW_SECT_DESC
#undef DEBUG_SHOW_TRE_DATA
#undef DEBUG_SHOW_MAPLEVEL_DATA
#undef DEBUG_SHOW_SUBDIV_DATA
#undef DEBUG_SHOW_POLY_DATA
#undef DEBUG_SHOW_POINTS

#undef SORT_SUBDIV

const QString CGarminImg::polyline_typestr[]=
{
    /*0x00,*/   tr(""),
    /*0x01,*/   tr("Major highway"),
    /*0x02,*/   tr("Principal highway"),
    /*0x03,*/   tr("Other highway"),
    /*0x04,*/   tr("Arterial road"),
    /*0x05,*/   tr("Collector road"),
    /*0x06,*/   tr("Residential street"),
    /*0x07,*/   tr("Alley/Private road"),
    /*0x08,*/   tr("Highway ramp, low speed"),
    /*0x09,*/   tr("Highway ramp, high speed"),
    /*0x0a,*/   tr("Unpaved road"),
    /*0x0b,*/   tr("Major highway connector"),
    /*0x0c,*/   tr("Roundabout"),
    /*0x0d,*/   tr(""),
    /*0x0e,*/   tr(""),
    /*0x0f,*/   tr(""),
    /*0x10,*/   tr(""),
    /*0x11,*/   tr(""),
    /*0x12,*/   tr(""),
    /*0x13,*/   tr(""),
    /*0x14,*/   tr("Railroad"),
    /*0x15,*/   tr("Shoreline"),
    /*0x16,*/   tr("Trail"),
    /*0x17,*/   tr(""),
    /*0x18,*/   tr("Stream"),
    /*0x19,*/   tr("Time zone"),
    /*0x1a,*/   tr("Ferry"),
    /*0x1b,*/   tr("Ferry"),
    /*0x1c,*/   tr("State/province border"),
    /*0x1d,*/   tr("County/parish border"),
    /*0x1e,*/   tr("International border"),
    /*0x1f,*/   tr("River"),
    /*0x20,*/   tr("Minor land contour"),
    /*0x21,*/   tr("Intermediate land contour"),
    /*0x22,*/   tr("Major land contour"),
    /*0x23,*/   tr("Minor deph contour"),
    /*0x24,*/   tr("Intermediate depth contour"),
    /*0x25,*/   tr("Major depth contour"),
    /*0x26,*/   tr("Intermittent stream"),
    /*0x27,*/   tr("Airport runway"),
    /*0x28,*/   tr("Pipeline"),
    /*0x29,*/   tr("Powerline"),
    /*0x2a,*/   tr("Marine boundary"),
    /*0x2b,*/   tr("Hazard boundary")
};

const CGarminImg::polygon_typestr_entry_t CGarminImg::polygon_typestr[]=
{
    /*0x00,*/    {0x00,tr("")}
    /*0x01,*/   ,{0x06,tr("Large urban area (greater 200K)")}   // preceeded by "National Park"
    /*0x02,*/   ,{0x07,tr("Small urban area (less 200K)")}      // preceeded by "National Park"
    /*0x03,*/   ,{0x08,tr("Rural housing area")}                // preceeded by "National Park"
    /*0x04,*/   ,{0x09,tr("Military base")}
    /*0x05,*/   ,{0x0a,tr("Parking lot")}
    /*0x06,*/   ,{0x0b,tr("Parking garage")}
    /*0x07,*/   ,{0x0c,tr("Airport")}
    /*0x08,*/   ,{0x0d,tr("Shopping center")}
    /*0x09,*/   ,{0x0e,tr("Marina")}
    /*0x0a,*/   ,{0x0f,tr("University/College")}
    /*0x0b,*/   ,{0x10,tr("Hospital")}
    /*0x0c,*/   ,{0x11,tr("Industrial complex")}
    /*0x0d,*/   ,{0x12,tr("Reservation")}
    /*0x0e,*/   ,{0x13,tr("Airport runway")}
    /*0x0f,*/   ,{0x14,tr("")}
    /*0x10,*/   ,{0x15,tr("")}
    /*0x11,*/   ,{0x16,tr("")}
    /*0x12,*/   ,{0x17,tr("")}
    /*0x13,*/   ,{0x18,tr("Man-made area")}                     // exchanged with man made area
    /*0x14,*/   ,{0x03,tr("National park")}                     // moved in front of urban areas
    /*0x15,*/   ,{0x04,tr("National park")}                     // moved in front of urban areas
    /*0x16,*/   ,{0x05,tr("National park")}                     // moved in front of urban areas
    /*0x17,*/   ,{0x13,tr("City park")}
    /*0x18,*/   ,{0x19,tr("Golf course")}
    /*0x19,*/   ,{0x1a,tr("Sports complex")}
    /*0x1a,*/   ,{0x1b,tr("Cemetary")}
    /*0x1b,*/   ,{0x1c,tr("")}
    /*0x1c,*/   ,{0x1d,tr("")}
    /*0x1d,*/   ,{0x1e,tr("")}
    /*0x1e,*/   ,{0x1f,tr("State park")}
    /*0x1f,*/   ,{0x20,tr("State park")}
    /*0x20,*/   ,{0x21,tr("State park")}
    /*0x21,*/   ,{0x22,tr("")}
    /*0x22,*/   ,{0x23,tr("")}
    /*0x23,*/   ,{0x24,tr("")}
    /*0x24,*/   ,{0x25,tr("")}
    /*0x25,*/   ,{0x26,tr("")}
    /*0x26,*/   ,{0x27,tr("")}
    /*0x27,*/   ,{0x28,tr("")}
    /*0x28,*/   ,{0x29,tr("Ocean")}
    /*0x29,*/   ,{0x2a,tr("Blue (unknown)")}
    /*0x2a,*/   ,{0x2b,tr("")}
    /*0x2b,*/   ,{0x2c,tr("")}
    /*0x2c,*/   ,{0x2d,tr("")}
    /*0x2d,*/   ,{0x2e,tr("")}
    /*0x2e,*/   ,{0x2f,tr("")}
    /*0x2f,*/   ,{0x30,tr("")}
    /*0x30,*/   ,{0x31,tr("")}
    /*0x31,*/   ,{0x32,tr("")}
    /*0x32,*/   ,{0x33,tr("Sea")}
    /*0x33,*/   ,{0x34,tr("")}
    /*0x34,*/   ,{0x35,tr("")}
    /*0x35,*/   ,{0x36,tr("")}
    /*0x36,*/   ,{0x37,tr("")}
    /*0x37,*/   ,{0x38,tr("")}
    /*0x38,*/   ,{0x39,tr("")}
    /*0x39,*/   ,{0x3a,tr("")}
    /*0x3a,*/   ,{0x3b,tr("")}
    /*0x3b,*/   ,{0x3c,tr("Blue (unknown)")}
    /*0x3c,*/   ,{0x3d,tr("Large lake")}
    /*0x3d,*/   ,{0x3e,tr("Large lake")}
    /*0x3e,*/   ,{0x3f,tr("Medium lake")}
    /*0x3f,*/   ,{0x40,tr("Medium lake")}
    /*0x40,*/   ,{0x41,tr("Small lake")}
    /*0x41,*/   ,{0x42,tr("Small lake")}
    /*0x42,*/   ,{0x43,tr("Major lake")}
    /*0x43,*/   ,{0x44,tr("Major lake")}
    /*0x44,*/   ,{0x45,tr("Large lake")}
    /*0x45,*/   ,{0x46,tr("Blue (unknown)")}
    /*0x46,*/   ,{0x47,tr("Major River")}
    /*0x47,*/   ,{0x48,tr("Large River")}
    /*0x48,*/   ,{0x49,tr("Medium River")}
    /*0x49,*/   ,{0x4a,tr("Small River")}
    /*0x4a,*/   ,{0x4b,tr("Definition area")}
    /*0x4b,*/   ,{0x01,tr("Background")}
    /*0x4c,*/   ,{0x4c,tr("Intermittent water")}
    /*0x4d,*/   ,{0x4d,tr("Glacier")}
    /*0x4e,*/   ,{0x4e,tr("Orchard/Plantation")}
    /*0x4f,*/   ,{0x4f,tr("Scrub")}
    /*0x50,*/   ,{0x50,tr("Forest")}
    /*0x51,*/   ,{0x51,tr("Wetland/Swamp")}
    /*0x52,*/   ,{0x52,tr("Tundra")}
    /*0x53,*/   ,{0x02,tr("Flat")}                              // moved right in front of everything
};

quint32 CGarminPoint::decode(subdiv_desc_t& subdiv, quint8 * pData)
{
    qint16 dLng, dLat;

    type        = (quint16)(*pData) << 8;

    ++pData;

    quint32 lbl_ptr = gar_ptr_load(uint24_t, pData);
    hasSubType  = lbl_ptr & 0x00800000;
    isLbl6      = lbl_ptr & 0x00400000;

    if(subdiv.strtbl) {
        quint32 offset = lbl_ptr & 0x003FFFFF;
        isLbl6 ? subdiv.strtbl->get(offset,IGarminStrTbl::poi, *this) :  subdiv.strtbl->get(offset,IGarminStrTbl::norm, *this);
    }

    pData += 3;

    dLng = gar_ptr_load(int16_t, pData); pData += 2;
    dLat = gar_ptr_load(int16_t, pData); pData += 2;

    qint32 x1,y1;

    x1 = ((qint32)dLng << subdiv.shift) + subdiv.iCenterLng;
    y1 = ((qint32)dLat << subdiv.shift) + subdiv.iCenterLat;
    point.u = RAD(x1);
    point.v = RAD(y1);
    point = pj_fwd(point,*gpProj);
#ifdef DEBUG_SHOW_POINTS
    qDebug() << x1 << y1 << point.u << point.v;
#endif

    if(hasSubType) {
        type |= *pData;
        return 9;
    }

    return 8;
}


CGarminImg::CGarminImg(QObject * parent)
: QObject(parent)
, transparent(false)
{

}


CGarminImg::~CGarminImg()
{
    qDebug() << "~CGarminImg()";
}


00235 void CGarminImg::load(const QString& filename, bool peek)
{
    char tmpstr[64];

    subfiles.clear();
    mapdesc.clear();
    transparent = false;

    FILE * fid = fopen(filename.toUtf8(),"rb");

    if(fid == 0) {
        throw exce_garmin_t(errOpen,tr("Failed to open: ") + filename);
    }

    // get file size
    fseek(fid,0,SEEK_END);
    const size_t fsize = ftell(fid);
    rewind(fid);

    // read file to buffer
    quint8 * const pRawData = new quint8[fsize];
    fread(pRawData,sizeof(quint8),fsize,fid);
    fclose(fid);

    // xor data if neccessary
    if(*pRawData != '\0') {
        quint8 hash = *pRawData;
        quint8 *p   =  pRawData;
        size_t cnt  = 0;

        do {
            *p = (*p) ^ hash;
            ++p;++cnt;
        }while(cnt != fsize);

        //         QFile tmp("tmp.img");
        //         tmp.open(QIODevice::WriteOnly);
        //         tmp.write((char*)pRawData,fsize);
        //         tmp.close();
    }

    hdr_img_t * imghdr = (hdr_img_t *)pRawData;
    if(strncmp(imghdr->signature,"DSKIMG",7) != 0) {
        delete [] pRawData;
        throw exce_garmin_t(errFormat,tr("Bad file format: ") + filename);
    }
    if(strncmp(imghdr->identifier,"GARMIN",7) != 0) {
        delete [] pRawData;
        throw exce_garmin_t(errFormat,tr("Bad file format: ") + filename);
    }
    mapdesc  = QByteArray((const char*)imghdr->desc1,20);
    mapdesc += imghdr->desc2;
    qDebug() << mapdesc;

    size_t blocksize = imghdr->blocksize();

    // read FAT
    const FATblock_t * pFAT = (const FATblock_t *)(pRawData + sizeof(hdr_img_t));
    size_t dataoffset = sizeof(hdr_img_t);

    // skip dummy blocks at the beginning
    while(dataoffset < fsize) {
        if(pFAT->flag != 0x00) {
            break;
        }
        dataoffset += sizeof(FATblock_t);
        ++pFAT;
    }

    QSet<QString> subfileNames;
    while(dataoffset < fsize) {
        if(pFAT->flag != 0x01) {
            break;
        }

        // start of new subfile part
        /*
            It is taken for granted that the single subfile parts are not
            fragmented within the file. Thus it is not really neccessary to
            store and handle all block sequence numbers. Just the first one
            will give us the offset. This also implies that it is not necessary
            to care about FAT blocks with a non-zero part number.

            2007-03-31: Garmin's world base map seems to be coded different.
                        The part field seems to be rather a bit field than
                        a part number. As the total subfile size is given
                        for the first part only (for all otheres it's zero)
                        I use it to identify the 1st part of a subfile

            2007-05-26: Gmapsupp images by Sendmap code quite some bull shit,
                        too. The size is stored in every part and they do have
                        a part number. I introduced a set of subfile names
                        storing the subfile's name and type. The first part
                        with a size info and it's name / type not stored in the
                        set is used to get the location information.
        */
        memcpy(tmpstr,pFAT->name,sizeof(pFAT->name) + sizeof(pFAT->type));
        tmpstr[sizeof(pFAT->name) + sizeof(pFAT->type)] = 0;

        if(gar_load(uint32_t, pFAT->size) != 0 && !subfileNames.contains(tmpstr) && tmpstr[0] != 0x20) {
            //if(pFAT->part == 0){
            subfileNames << tmpstr;

            memcpy(tmpstr,pFAT->name,sizeof(pFAT->name));
            tmpstr[sizeof(pFAT->name)] = 0;

            // skip MAPSORC.MPS section
            if(strcmp(tmpstr,"MAPSOURC") && strcmp(tmpstr,"SENDMAP2")) {

                subfile_desc_t& subfile = subfiles[tmpstr];
                subfile.name = tmpstr;

                memcpy(tmpstr,pFAT->type,sizeof(pFAT->type));
                tmpstr[sizeof(pFAT->type)] = 0;

                subfile_part_t& part = subfile.parts[tmpstr];
                part.size   = gar_load(uint32_t, pFAT->size);
                part.offset = gar_load(uint16_t, pFAT->blocks[0]) * blocksize;
            }
        }
        dataoffset += sizeof(FATblock_t);
        ++pFAT;
    }

    if((dataoffset == sizeof(imghdr)) || (dataoffset >= fsize)) {
        delete [] pRawData;
        throw exce_garmin_t(errFormat,tr("Failed to read file structure: ") + filename);
    }
    // gmapsupp.img files do not have a data offset field
    if(gar_load(uint32_t, imghdr->dataoffset) == 0) {
        imghdr->dataoffset = gar_load(uint32_t, dataoffset);
    }

    // sometimes there are dummy blocks at the end of the FAT
    if(gar_load(uint32_t, imghdr->dataoffset) != dataoffset) {
        dataoffset = gar_load(uint32_t, imghdr->dataoffset);
    }

#ifdef DEBUG_SHOW_SECT_DESC
    {
        QMap<QString,subfile_desc_t>::const_iterator subfile = subfiles.begin();
        while(subfile != subfiles.end()) {
            qDebug() << "--- subfile" << subfile->name << "---";
            QMap<QString,subfile_part_t>::const_iterator part = subfile->parts.begin();
            while(part != subfile->parts.end()) {
                qDebug() << part.key() << hex << part->offset << part->size;
                ++part;
            }
            ++subfile;
        }
    }
#endif                       //DEBUG_SHOW_SECT_DESC

    QMap<QString,subfile_desc_t>::iterator subfile = subfiles.begin();
    while(subfile != subfiles.end()) {
        //qDebug() << "----------------"  << subfile->name;
        subfile->name = mapdesc.trimmed();
        loadSubFile(*subfile,pRawData, peek);
        ++subfile;
    }

    //     fid = fopen("test.img","wb");
    //     fwrite(pRawData,sizeof(quint8),fsize,fid);
    //     fclose(fid);

    delete [] pRawData;

}


void CGarminImg::loadSubFile(subfile_desc_t& subfile, quint8 * const pRawData, bool peek)
{
    quint32 i;
    void (*minno)(subfile_desc_t&,quint8 * const) = 0;
    minno = (void (*)(subfile_desc_t&,quint8 * const))QLibrary::resolve(QDir::home().filePath(".config/QLandkarte/mellon.so"),"minno");
    if(minno) minno(subfile,pRawData);

    // test for mandatory subfile parts
    if(!(subfile.parts.contains("TRE") && subfile.parts.contains("RGN"))) return;

    hdr_tre_t * trehdr = (hdr_tre_t *)(pRawData + subfile.parts["TRE"].offset);
    if(trehdr->flag & 0x80) {
        delete [] pRawData;
        throw exce_garmin_t(errLock,tr( "File contains locked / encypted data. Garmin does not\n"
            "want you to use this file with any other software than\n"
            "the one supplied by Garmin."));
    }

    subfile.isTransparent   = trehdr->POI_flags & 0x0002;
    transparent             = subfile.isTransparent ? true : transparent;

    IGarminStrTbl * strtbl = 0;
    if(subfile.parts.contains("LBL")) {
        strtbl = createStringTable(pRawData + subfile.parts["LBL"].offset, subfile.parts["LBL"].size, this);
        if(subfile.parts.contains("NET")) {
            strtbl->registerNETSection(pRawData + subfile.parts["NET"].offset, subfile.parts["NET"].size);
        }
    }

#ifdef DEBUG_SHOW_TRE_DATA
    qDebug() << "+++" << subfile.name << "+++";
    qDebug() << "TRE header length  :" << gar_load(uint16_t, trehdr->length);
    qDebug() << "TRE1 offset        :" << hex << gar_load(uint32_t, trehdr->tre1_offset);
    qDebug() << "TRE1 size          :" << dec << gar_load(uint32_t, trehdr->tre1_size);
    qDebug() << "TRE2 offset        :" << hex << gar_load(uint32_t, trehdr->tre2_offset);
    qDebug() << "TRE2 size          :" << dec << gar_load(uint32_t, trehdr->tre2_size);
#endif                       // DEBUG_SHOW_TRE_DATA

    hdr_rgn_t * rgnhdr = (hdr_rgn_t *)(pRawData + subfile.parts["RGN"].offset);

    // read map boundaries from header
    qint32 i32;
    i32 = gar_ptr_load(int24_t, trehdr->northbound);
    subfile.north = RAD(i32);
    i32 = gar_ptr_load(int24_t, trehdr->eastbound);
    subfile.east = RAD(i32);
    i32 = gar_ptr_load(int24_t, trehdr->southbound);
    subfile.south = RAD(i32);
    i32 = gar_ptr_load(int24_t, trehdr->westbound);
    subfile.west = RAD(i32);

    if(subfile.east == subfile.west) {
        subfile.east = -subfile.east;
    }

    XY xy;
    xy.u = subfile.east;
    xy.v = subfile.north;
    xy = pj_fwd(xy,*gpProj);
    qDebug() << xy.u << xy.v;

    gpProj->fwdQRectF(subfile.north, subfile.east, subfile.south, subfile.west, subfile.area);

#ifdef DEBUG_SHOW_TRE_DATA
    qDebug() << "bounding area (deg)" << subfile.north * RAD_TO_DEG << subfile.east * RAD_TO_DEG << subfile.south * RAD_TO_DEG << subfile.west * RAD_TO_DEG;
    qDebug() << "bounding area (m)" << subfile.area;
#endif                       // DEBUG_SHOW_TRE_DATA

    tre_map_level_t * maplevel  = (tre_map_level_t *)(pRawData + subfile.parts["TRE"].offset + gar_load(uint32_t, trehdr->tre1_offset));

    quint32 nlevels             = gar_load(uint32_t, trehdr->tre1_size) / sizeof(tre_map_level_t);
    quint32 nsubdivs            = 0;
    quint32 nsubdivs_last       = 0;
    // count subsections
    for(i=0; i<nlevels; ++i) {
        subfile_desc_t::maplevel_t ml;
        ml.inherited    = TRE_MAP_INHER(maplevel);
        ml.level        = TRE_MAP_LEVEL(maplevel);
        ml.bits         = maplevel->bits;
        subfile.maplevels << ml;
        nsubdivs       += gar_load(uint16_t, maplevel->nsubdiv);
        nsubdivs_last   = gar_load(uint16_t, maplevel->nsubdiv);
#ifdef DEBUG_SHOW_MAPLEVEL_DATA
        qDebug() << "level" << TRE_MAP_LEVEL(maplevel) << "inherited" << TRE_MAP_INHER(maplevel)
            << "bits" << maplevel->bits << "#subdivs" << gar_load(uint16_t, maplevel->nsubdiv);
#endif                   // DEBUG_SHOW_MAPLEVEL_DATA
        ++maplevel;
    }

    if(peek) {
        return;
    }

    quint32 nsubdivs_next = nsubdivs - nsubdivs_last;

    //////////////////////////////////
    // read subdivision information
    //////////////////////////////////
    // point to first map level definition
    maplevel        = (tre_map_level_t *)(pRawData + subfile.parts["TRE"].offset + gar_load(uint32_t, trehdr->tre1_offset));
    // number of subdivisions per map level
    quint32 nsubdiv = gar_load(uint16_t, maplevel->nsubdiv);

    // point to first 16 byte subdivision definition entry
    tre_subdiv_next_t* subdiv_n = (tre_subdiv_next_t *)(pRawData + subfile.parts["TRE"].offset + gar_load(uint32_t, trehdr->tre2_offset));

    QVector<subdiv_desc_t> subdivs;
    subdivs.resize(nsubdivs);
    QVector<subdiv_desc_t>::iterator subdiv      = subdivs.begin();
    QVector<subdiv_desc_t>::iterator subdiv_prev = subdivs.end();

    // absolute offset of RGN data
    quint32 rgnoff = subfile.parts["RGN"].offset + gar_load(uint32_t, rgnhdr->offset);

    // parse all 16 byte subdivision entries
    for(i=0; i<nsubdivs_next; ++i, --nsubdiv) {
        qint32 cx,cy;
        qint32 width, height;

        subdiv->n = i;
        subdiv->next         = gar_load(uint16_t, subdiv_n->next);
        subdiv->terminate    = TRE_SUBDIV_TERM(subdiv_n);
        subdiv->rgn_start    = gar_ptr_load(uint24_t, subdiv_n->rgn_offset);
        subdiv->rgn_start   += rgnoff;
        // skip if this is the first entry
        if(subdiv_prev != subdivs.end()) {
            subdiv_prev->rgn_end = subdiv->rgn_start;
        }

        subdiv->hasPoints    = subdiv_n->elements & 0x10;
        subdiv->hasIdxPoints = subdiv_n->elements & 0x20;
        subdiv->hasPolylines = subdiv_n->elements & 0x40;
        subdiv->hasPolygons  = subdiv_n->elements & 0x80;

        // if all subdivisions of this level have been parsed, switch to the next one
        if(nsubdiv == 0) {
            ++maplevel;
            nsubdiv = gar_load(uint16_t, maplevel->nsubdiv);
        }

        subdiv->level = TRE_MAP_LEVEL(maplevel);
        subdiv->shift = 24 - maplevel->bits;

        cx = gar_ptr_load(uint24_t, subdiv_n->center_lng);
        subdiv->iCenterLng = cx;
        cy = gar_ptr_load(uint24_t, subdiv_n->center_lat);
        subdiv->iCenterLat = cy;
        width   = TRE_SUBDIV_WIDTH(subdiv_n) << subdiv->shift;
        height  = gar_load(uint16_t, subdiv_n->height) << subdiv->shift;

        subdiv->north = RAD(cy + height + 1);
        subdiv->south = RAD(cy - height);
        subdiv->east  = RAD(cx + width + 1);
        subdiv->west  = RAD(cx - width);

        gpProj->fwdQRectF(subdiv->north, subdiv->east, subdiv->south, subdiv->west, subdiv->area);

        if(!subdiv->area.isValid()) {
            qDebug() << subdiv->north << subdiv->east << subdiv->south << subdiv->west << subdiv->area;
        }

        subdiv->strtbl = strtbl;

        //qDebug() << subdiv->n << subdiv->level << subdiv_n->terminate << subdiv_n->next;

        subdiv_prev = subdiv;
        ++subdiv_n; ++subdiv;
    }

    // switch to last map level
    ++maplevel;
    // witch pointer to 14 byte subdivision sections
    tre_subdiv_t* subdiv_l = subdiv_n;
    // parse all 14 byte subdivision entries of last map level
    for(; i<nsubdivs; ++i) {
        qint32 cx,cy;
        qint32 width, height;
        subdiv->n = i;
        subdiv->next         = 0;
        subdiv->terminate    = TRE_SUBDIV_TERM(subdiv_l);
        subdiv->rgn_start    = gar_ptr_load(uint24_t, subdiv_l->rgn_offset);
        subdiv->rgn_start   += rgnoff;
        subdiv_prev->rgn_end = subdiv->rgn_start;
        subdiv->hasPoints    = subdiv_l->elements & 0x10;
        subdiv->hasIdxPoints = subdiv_l->elements & 0x20;
        subdiv->hasPolylines = subdiv_l->elements & 0x40;
        subdiv->hasPolygons  = subdiv_l->elements & 0x80;

        subdiv->level = TRE_MAP_LEVEL(maplevel);
        subdiv->shift = 24 - maplevel->bits;

        cx = gar_ptr_load(uint24_t, subdiv_l->center_lng);
        subdiv->iCenterLng = cx;
        cy = gar_ptr_load(uint24_t, subdiv_l->center_lat);
        subdiv->iCenterLat = cy;
        width   = TRE_SUBDIV_WIDTH(subdiv_l) << subdiv->shift;
        height  = gar_load(uint16_t, subdiv_l->height) << subdiv->shift;

        subdiv->north = RAD(cy + height + 1);
        subdiv->south = RAD(cy - height);
        subdiv->east  = RAD(cx + width + 1);
        subdiv->west  = RAD(cx - width);

        gpProj->fwdQRectF(subdiv->north, subdiv->east, subdiv->south, subdiv->west, subdiv->area);
        if(!subdiv->area.isValid()) {
            qDebug() << subdiv->north << subdiv->east << subdiv->south << subdiv->west << subdiv->area;
        }

        subdiv->strtbl = strtbl;

        subdiv_prev = subdiv;
        ++subdiv_l; ++subdiv;
    }
    subdivs.last().rgn_end = subfile.parts["RGN"].offset + subfile.parts["RGN"].size;

#ifdef SORT_SUBDIV
    // sort subdivisions
    /*
        What is this all about? In a sane and perfect world all subdivisions would be
        ordered in a linear way, to make it easy to draw overlapping polygons one after
        another and to be sure not to overlap important information.

        In the Garmin universe the word 'easy' does not exist and sanity is questionable.
        Subdivisions have to be drawn with the order dictated by the next pointer of the
        preceding map detail level and the termination flag. (See IMG format spec
        'Map Levels' if you do not have a clue). This will give you quite some more polygon
        details to see.

        But this will not fix everything. That's where the insane part starts. Most likely
        water and forest polygons will be drawn in the wrong order within urban areas.
        It doesn't matter if you do sorting or not. The are just in the wrong order - either
        way. A render object should draw these elements after all other polygons in the order:

        woods -> air fields (!) -> water.

        More on that: It looks like insanity took over completely. Even with section sorting
        urban areas get drawn across other structures. This has to be handled by the render object.
        Urban areas first, other stuff next, woods secondlast, water last. (I hope they fired the
        jerk responsible for this mess) -> disabled sorting

        Even more on that: It's worse. You have to draw elements by their type ID for the whole
        visible area. See CGarminMap::drawPolygons() for more on that
    */
    {
        quint8 level                                    = nlevels;
        QVector<subdiv_desc_t>::iterator subdiv_base    = subdivs.begin();
        QVector<subdiv_desc_t>::iterator subdiv_n       = subdiv_base;

        QVector<int> set1;
        QVector<int> set2;
        int n = 0/*, m = 0*/;

        set1 << 1;
        while(--level > 0) {
            foreach(n,set1) {
                --n; subdiv_n = subdiv_base + n;
                //qDebug() << n;
                while(subdiv_n != subdivs.end()) {
                    if(subdiv_n->next) {
                        set2 << subdiv_n->next;
                    }
                    subfile.subdivs << *subdiv_n;
                    //qDebug() << level << ":" << m++ << "<-" << subdiv_n->n << subdiv_n->next - 1;
                    if(subdiv_n->terminate == 1) break;
                    ++subdiv_n;
                }
            }
            set1 = set2;
            set2.clear();
        }
        foreach(n,set1) {
            --n; subdiv_n = subdiv_base + n;
            //qDebug() << n;
            while(subdiv_n != subdivs.end()) {
                subfile.subdivs << *subdiv_n;
                //qDebug() << level << ":" << m++ << "<-" << subdiv_n->n << "-";
                if(subdiv_n->terminate == 1) break;
                ++subdiv_n;
            }
        }
    }
#else                        // SORT_SUBDIV
    subfile.subdivs = subdivs;
#endif                       // SORT_SUBDIV

#ifdef DEBUG_SHOW_SUBDIV_DATA
    {
        QVector<subdiv_desc_t>::iterator subdiv = subfile.subdivs.begin();
        while(subdiv != subfile.subdivs.end()) {
            qDebug() << "--- subdiv" << subdiv->n << "---";
            qDebug() << "RGN start          " << hex << subdiv->rgn_start;
            qDebug() << "RGN end            " << hex << subdiv->rgn_end;
            qDebug() << "center lng         " << DEG(subdiv->iCenterLng);
            qDebug() << "center lat         " << DEG(subdiv->iCenterLat);
            qDebug() << "has points         " << subdiv->hasPoints;
            qDebug() << "has indexed points " << subdiv->hasIdxPoints;
            qDebug() << "has polylines      " << subdiv->hasPolylines;
            qDebug() << "has polygons       " << subdiv->hasPolygons;
            qDebug() << "bounding area (deg)" << subdiv->north * RAD_TO_DEG << subdiv->east * RAD_TO_DEG << subdiv->south * RAD_TO_DEG << subdiv->west * RAD_TO_DEG;
            qDebug() << "bounding area (m)  " << subdiv->area;
            qDebug() << "map level          " << subdiv->level;
            qDebug() << "left shifts        " << subdiv->shift;
            ++subdiv;
        }
    }
#endif                       // DEBUG_SHOW_SUBDIV_DATA

    subdiv = subfile.subdivs.begin();
    while(subdiv != subfile.subdivs.end()) {
        loadSubDiv(subfile,*subdiv,pRawData);
        ++subdiv;
    }

    subfile.subdivs.squeeze();
}


void CGarminImg::loadSubDiv(subfile_desc_t& subfile, subdiv_desc_t& subdiv, quint8 * const pRawData)
{
    if(subdiv.rgn_start == subdiv.rgn_end) return;

    quint32 opnt = 0, oidx = 0, opline = 0, opgon = 0;
    quint32 objCnt = subdiv.hasIdxPoints + subdiv.hasPoints + subdiv.hasPolylines + subdiv.hasPolygons;

    quint16 * pOffset = (quint16*)(pRawData + subdiv.rgn_start);

    // test for points
    if(subdiv.hasPoints) {
        opnt = (objCnt - 1) * sizeof(quint16) + subdiv.rgn_start;
    }
    // test for indexed points
    if(subdiv.hasIdxPoints) {
        if(opnt) {
            oidx = gar_load(uint16_t, *pOffset);
            oidx += subdiv.rgn_start;
            ++pOffset;
        }
        else {
            oidx = (objCnt - 1) * sizeof(quint16) + subdiv.rgn_start;
        }

    }
    // test for polylines
    if(subdiv.hasPolylines) {
        if(opnt || oidx) {
            opline = gar_load(uint16_t, *pOffset);
            opline += subdiv.rgn_start;
            ++pOffset;
        }
        else {
            opline = (objCnt - 1) * sizeof(quint16) + subdiv.rgn_start;
        }
    }
    // test for polygons
    if(subdiv.hasPolygons) {
        if(opnt || oidx || opline) {
            opgon = gar_load(uint16_t, *pOffset);
            opgon += subdiv.rgn_start;
            ++pOffset;
        }
        else {
            opgon = (objCnt - 1) * sizeof(quint16) + subdiv.rgn_start;
        }
    }

#ifdef DEBUG_SHOW_POLY_DATA
    qDebug() << "--- Subdivision" << subdiv.n << "---";
    qDebug() << "adress:" << hex << subdiv.rgn_start << "- " << subdiv.rgn_end;
    qDebug() << "points:            " << hex << opnt;
    qDebug() << "indexed points:    " << hex << oidx;
    qDebug() << "polylines:         " << hex << opline;
    qDebug() << "polygons:          " << hex << opgon;
#endif                       // DEBUG_SHOW_POLY_DATA

    quint8 * pData;
    quint8 * pEnd;

    // decode points
    if(subdiv.hasPoints) {
        pData = pRawData + opnt;
        pEnd  = pRawData + (oidx ? oidx : opline ? opline : opgon ? opgon : subdiv.rgn_end);
        while(pData < pEnd) {
            subdiv.points.push_back(CGarminPoint());
            pData += subdiv.points.last().decode(subdiv,pData);
        }
    }
    subdiv.points.squeeze();

    // decode indexed points
    if(subdiv.hasIdxPoints) {
        pData = pRawData + oidx;
        pEnd  = pRawData + (opline ? opline : opgon ? opgon : subdiv.rgn_end);
        while(pData < pEnd) {
            subdiv.pois.push_back(CGarminPoint());
            pData += subdiv.pois.last().decode(subdiv,pData);
        }
    }
    subdiv.pois.squeeze();

    // decode polylines
    if(subdiv.hasPolylines) {
        pData = pRawData + opline;
        pEnd  = pRawData + (opgon ? opgon : subdiv.rgn_end);
        while(pData < pEnd) {
            subdiv.polylines.push_back(CGarminPolygon());
            pData += subdiv.polylines.last().decode(subdiv,true,pData);
        }
    }
    subdiv.polylines.squeeze();

    // decode polygons
    if(subdiv.hasPolygons) {
        pData = pRawData + opgon;
        pEnd  = pRawData + subdiv.rgn_end;
        while(pData < pEnd) {
            CGarminPolygon polygon;
            pData += polygon.decode(subdiv,false,pData);
            subdiv.polygons.insert(polygon.type,polygon);
            if(polygon.type == 0x4a && polygon.labels.size() > 1) {
                subfile.definitionAreas[polygon.labels[1]] = polygon;
            }
        }
    }
}

Generated by  Doxygen 1.6.0   Back to index