summaryrefslogtreecommitdiffstats
path: root/gis/qmapshack/qmt_map2jnx.patch
blob: 50d5a2666626f4de19eb78e536c00f9fd0e1396a (plain)
From 79a266943a40bee8fa5e71776c6a76c4d46bfbf8 Mon Sep 17 00:00:00 2001
From: Oliver Eichler <oliver.eichler@dspsolutions.de>
Date: Thu, 12 Sep 2019 20:31:26 +0200
Subject: [PATCH] [QMS-3] Add qmt_map2jnx from former sub-repo

---
 src/qmt_map2jnx/CMakeLists.txt |   59 ++
 src/qmt_map2jnx/argv.cpp       |   45 ++
 src/qmt_map2jnx/argv.h         |   16 +
 src/qmt_map2jnx/main.cpp       | 1039 ++++++++++++++++++++++++++++++++
 4 files changed, 1159 insertions(+)
 create mode 100644 src/qmt_map2jnx/CMakeLists.txt
 create mode 100644 src/qmt_map2jnx/argv.cpp
 create mode 100644 src/qmt_map2jnx/argv.h
 create mode 100644 src/qmt_map2jnx/main.cpp

diff --git a/src/qmt_map2jnx/CMakeLists.txt b/src/qmt_map2jnx/CMakeLists.txt
new file mode 100644
index 00000000..12b29d94
--- /dev/null
+++ b/src/qmt_map2jnx/CMakeLists.txt
@@ -0,0 +1,59 @@
+
+
+set(APPLICATION_NAME qmt_map2jnx)
+set(MAP2JNX_VERSION_MAJOR 1)
+set(MAP2JNX_VERSION_MINOR 0)
+set(MAP2JNX_VERSION_PATCH 0)
+
+add_definitions(
+    -DVER_MAJOR=${MAP2JNX_VERSION_MAJOR}
+    -DVER_MINOR=${MAP2JNX_VERSION_MINOR}
+    -DVER_STEP=${MAP2JNX_VERSION_PATCH}
+    -DVER_TWEAK=${VERSION_SUFFIX}
+    -DAPPLICATION_NAME=${APPLICATION_NAME}
+)
+
+
+#if you don't want the full compiler output, remove the following line
+SET(CMAKE_VERBOSE_MAKEFILE ON)
+SET(SRCS main.cpp argv.cpp)
+SET(HDRS argv.h)
+
+
+include_directories(
+  ${CMAKE_BINARY_DIR}
+  ${CMAKE_CURRENT_BINARY_DIR}
+  ${GDAL_INCLUDE_DIRS}
+  ${PROJ4_INCLUDE_DIRS}
+  ${JPEG_INCLUDE_DIRS}
+)
+
+if(APPLE)
+     INCLUDE_DIRECTORIES(/System/Library/Frameworks/Foundation.framework)
+     INCLUDE_DIRECTORIES(/System/Library/Frameworks/DiskArbitration.framework)
+endif(APPLE)
+
+if(WIN32)
+    include_directories(
+        ${CMAKE_SOURCE_DIR}/Win32/
+    )
+endif(WIN32)
+
+#list all source files here
+ADD_EXECUTABLE( ${APPLICATION_NAME} ${SRCS} ${HDRS})
+
+#add definitions, compiler switches, etc.
+IF(UNIX)
+  ADD_DEFINITIONS(-Wall)  
+ENDIF(UNIX)
+
+IF(WIN32)
+  ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE)
+ENDIF(WIN32)
+
+TARGET_LINK_LIBRARIES(${APPLICATION_NAME} ${GDAL_LIBRARIES} ${PROJ4_LIBRARIES} ${JPEG_LIBRARIES})
+
+install(
+    TARGETS ${APPLICATION_NAME} DESTINATION ${BIN_INSTALL_DIR}
+)
+
diff --git a/src/qmt_map2jnx/argv.cpp b/src/qmt_map2jnx/argv.cpp
new file mode 100644
index 00000000..a7f7939c
--- /dev/null
+++ b/src/qmt_map2jnx/argv.cpp
@@ -0,0 +1,45 @@
+/**********************************************************************************************
+    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 3 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, see <http://www.gnu.org/licenses/>.
+
+**********************************************************************************************/
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+char* get_argv(const int index, char** argv)
+{
+    char* result = NULL;
+    int len;
+
+#ifdef WIN32
+    int numargs;
+    wchar_t** argw = CommandLineToArgvW(GetCommandLineW(), &numargs);
+
+    // determine the buffer length first (including the trailing null)
+    len = WideCharToMultiByte(CP_UTF8, 0, argw[index], -1, NULL, 0, NULL, NULL);
+    result = (char*)calloc(len, 1);
+    WideCharToMultiByte(CP_UTF8, 0, argw[index], -1, result, len, NULL, NULL);
+
+    GlobalFree(argw);
+#else
+    len = strlen(argv[index]) + 1;
+    result = (char*)calloc(len, 1);
+    strcpy(result, argv[index]);
+#endif
+
+    return result;
+}
diff --git a/src/qmt_map2jnx/argv.h b/src/qmt_map2jnx/argv.h
new file mode 100644
index 00000000..0967d0b7
--- /dev/null
+++ b/src/qmt_map2jnx/argv.h
@@ -0,0 +1,16 @@
+/**********************************************************************************************
+    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 3 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, see <http://www.gnu.org/licenses/>.
+
+**********************************************************************************************/
+char* get_argv(const int index, char** argv);
diff --git a/src/qmt_map2jnx/main.cpp b/src/qmt_map2jnx/main.cpp
new file mode 100644
index 00000000..bef9cc43
--- /dev/null
+++ b/src/qmt_map2jnx/main.cpp
@@ -0,0 +1,1039 @@
+/**********************************************************************************************
+    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 3 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, see <http://www.gnu.org/licenses/>.
+
+**********************************************************************************************/
+
+#include "config.h"
+
+#ifdef _MSC_VER
+#define fseeko _fseeki64
+#define ftello _ftelli64
+#else
+#define _FILE_OFFSET_BITS 64
+#endif //
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <math.h>
+#include <wctype.h>
+
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include <gdal_priv.h>
+#include <proj_api.h>
+#include <ogr_spatialref.h>
+
+extern "C"
+{
+#include <jpeglib.h>
+}
+
+#include "argv.h"
+
+
+#ifndef _MKSTR_1
+#define _MKSTR_1(x)         #x
+#define _MKSTR(x)           _MKSTR_1(x)
+#endif
+
+#define VER_STR             _MKSTR(VER_MAJOR) "." _MKSTR(VER_MINOR) "." _MKSTR(VER_STEP)
+#define WHAT_STR            "qmt_map2jnx, Version " VER_STR
+
+#define JNX_MAX_TILES       50000 //6250
+#define JNX_MAX_TILE_SIZE   1024
+
+#define JPG_BLOCK_SIZE      (JNX_MAX_TILE_SIZE * JNX_MAX_TILE_SIZE)
+
+#define HEADER_BLOCK_SIZE   1024
+
+#pragma pack(1)
+
+struct jnx_hdr_t
+{
+    jnx_hdr_t(): version(0x00000004), devid(0), expire(0), productId(0), crc(0), signature(0), signature_offset(0), zorder(25){}
+    uint32_t version;           // byte 00000000..00000003
+    uint32_t devid;             // byte 00000004..00000007
+    int32_t  top;               // byte 00000014..00000017
+    int32_t  right;             // byte 00000010..00000013
+    int32_t  bottom;            // byte 0000000C..0000000F
+    int32_t  left;              // byte 00000008..0000000B
+    uint32_t details;           // byte 00000018..0000001B
+    uint32_t expire;            // byte 0000001C..0000001F
+    uint32_t productId;         // byte 00000020..00000023
+    uint32_t crc;               // byte 00000024..00000027
+    uint32_t signature;         // byte 00000028..0000002B
+    uint32_t signature_offset;  // byte 0000002C..0000002F
+    uint32_t zorder;            // byte 00000030..00000033
+};
+
+
+struct jnx_level_t
+{
+    jnx_level_t(): nTiles(0), offset(0), scale(0), dummy(2){}
+
+    uint32_t nTiles;
+    uint32_t offset;
+    uint32_t scale;
+    uint32_t dummy;
+};
+
+struct jnx_tile_t
+{
+    jnx_tile_t() : top(0), right(0), bottom(0), left(0), width(0), height(0), size(0), offset(0){}
+    int32_t  top;
+    int32_t  right;
+    int32_t  bottom;
+    int32_t  left;
+    uint16_t width;
+    uint16_t height;
+    uint32_t size;
+    uint32_t offset;
+};
+
+
+#ifdef WIN32
+#pragma pack()
+#else
+#pragma pack(0)
+#endif
+
+struct file_t
+{
+    file_t(): dataset(0), pj(0){memset(colortable,0, sizeof(colortable));}
+    ~file_t()
+    {
+        //if(dataset) delete dataset;
+        if(pj) pj_free(pj);
+    }
+
+    bool operator<(const file_t& other)  const
+    {
+        return (xscale > other.xscale);
+    }
+
+    std::string     filename;
+    std::string     projection;
+    GDALDataset *   dataset;
+    projPJ          pj;
+    uint32_t        width;
+    uint32_t        height;
+    double          xscale;
+    double          yscale;
+    double          scale;
+    double          xref1;
+    double          yref1;
+    double          xref2;
+    double          yref2;
+
+    double          lon1;
+    double          lat1;
+    double          lon2;
+    double          lat2;
+
+    uint32_t        colortable[256];
+
+};
+
+struct level_t : public jnx_level_t
+{
+    std::list<file_t *> files;
+    uint32_t tileSize;
+};
+
+struct scale_t
+{
+    double scale;
+    uint32_t jnxScale;
+};
+
+/// number of used levels
+static int32_t nLevels;
+/// up to five levels. nLevels gives the actual count
+static level_t levels[5];
+/// information about all files
+static std::list<file_t> files;
+/// the target lon/lat WGS84 projection
+static projPJ wgs84;
+/// the JNX file header to be copied to the outfile
+static jnx_hdr_t jnx_hdr;
+/// the tile information table for all 5 levels
+static jnx_tile_t tileTable[JNX_MAX_TILES * 5];
+/// tile buffer for 8 bit palette tiles, private to readTile
+static uint8_t  tileBuf8Bit[JNX_MAX_TILE_SIZE * JNX_MAX_TILE_SIZE] = {0};
+/// tile buffer for 24 bit raw RGB tiles, private to writeTile
+static uint8_t tileBuf24Bit[JNX_MAX_TILE_SIZE * JNX_MAX_TILE_SIZE * 3] = {0};
+/// tile buffer for 32 bit raw RGBA tiles
+static uint32_t tileBuf32Bit[JNX_MAX_TILE_SIZE * JNX_MAX_TILE_SIZE] = {0};
+/// internal jpeg buffer used by write tile.
+static std::vector<JOCTET> jpgbuf;
+
+static void prinfFileinfo(const file_t& file)
+{
+    printf("\n\n----------------------");
+    printf("\n%s:", file.filename.c_str());
+    printf("\nprojection: %s", file.projection.c_str());
+    printf("\nwidth: %i pixel height: %i pixel", file.width, file.height);
+
+    if(pj_is_latlong(file.pj))
+    {
+        printf("\narea (top/left, bottom/right): %f %f, %f %f", file.lat1, file.lon1, file.lat2, file.lon2);
+        printf("\nxscale: %f °/px, yscale: %f °/px", file.xscale, file.yscale);
+    }
+    else
+    {
+        printf("\narea (top/left, bottom/right): %f %f, %f %f", file.lat1, file.lon1, file.lat2, file.lon2);
+        printf("\nxscale: %f m/px, yscale: %f m/px", file.xscale, file.yscale);
+    }
+    printf("\nreal scale: %f m/px", file.scale);
+}
+
+bool readTile(uint32_t xoff, uint32_t yoff, uint32_t xsize, uint32_t ysize, file_t& file, uint32_t * output)
+{
+    GDALDataset * dataset = file.dataset;
+    int32_t rasterBandCount = dataset->GetRasterCount();
+
+    memset(output,-1, sizeof(uint32_t) * xsize * ysize);
+
+    if(rasterBandCount == 1)
+    {
+        GDALRasterBand * pBand;
+        pBand = dataset->GetRasterBand(1);
+        if(pBand->RasterIO(GF_Read,(int)xoff,(int)yoff, xsize, ysize, tileBuf8Bit,xsize,ysize,GDT_Byte,0,0) == CE_Failure)
+        {
+            return false;
+        }
+
+        for(unsigned int i = 0; i < (xsize * ysize); i++)
+        {
+            output[i] = file.colortable[tileBuf8Bit[i]];
+        }
+    }
+    else
+    {
+        for(int b = 1; b <= rasterBandCount; ++b)
+        {
+            GDALRasterBand * pBand;
+            pBand = dataset->GetRasterBand(b);
+
+            uint32_t mask = ~(0x000000FF << (8*(b-1)));
+
+            if(pBand->RasterIO(GF_Read,(int)xoff,(int)yoff, xsize, ysize, tileBuf8Bit,xsize,ysize,GDT_Byte,0,0) == CE_Failure)
+            {
+                return false;
+            }
+
+            for(unsigned int i = 0; i < (xsize * ysize); i++)
+            {
+                uint32_t pixel = output[i];
+
+                pixel &= mask;
+                pixel |= tileBuf8Bit[i] << (8*(b-1));
+                output[i] = pixel;
+            }
+        }
+    }
+
+    return true;
+}
+
+
+
+static void init_destination (j_compress_ptr cinfo)
+{
+    jpgbuf.resize(JPG_BLOCK_SIZE);
+    cinfo->dest->next_output_byte   = &jpgbuf[0];
+    cinfo->dest->free_in_buffer     = jpgbuf.size();
+}
+
+static boolean empty_output_buffer (j_compress_ptr cinfo)
+{
+    size_t oldsize = jpgbuf.size();
+    jpgbuf.resize(oldsize + JPG_BLOCK_SIZE);
+    cinfo->dest->next_output_byte   = &jpgbuf[oldsize];
+    cinfo->dest->free_in_buffer     = jpgbuf.size() - oldsize;
+    return true;
+}
+
+static void term_destination (j_compress_ptr cinfo)
+{
+    jpgbuf.resize(jpgbuf.size() - cinfo->dest->free_in_buffer);
+}
+
+
+static uint32_t writeTile(uint32_t xsize, uint32_t ysize, uint32_t * raw_image, FILE * fid, int quality, int subsampling)
+{
+    uint32_t size = 0;
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+    JSAMPROW row_pointer[1];
+
+    jpeg_destination_mgr destmgr    = {0};
+    destmgr.init_destination        = init_destination;
+    destmgr.empty_output_buffer     = empty_output_buffer;
+    destmgr.term_destination        = term_destination;
+
+    // convert from RGBA to RGB
+    for(uint32_t r = 0; r < ysize; r++)
+    {
+        for(uint32_t c = 0; c < xsize; c++)
+        {
+            uint32_t pixel = raw_image[r * xsize + c];
+            tileBuf24Bit[r * xsize * 3 + c * 3]     =  pixel        & 0x0FF;
+            tileBuf24Bit[r * xsize * 3 + c * 3 + 1] = (pixel >>  8) & 0x0FF;
+            tileBuf24Bit[r * xsize * 3 + c * 3 + 2] = (pixel >> 16) & 0x0FF;
+        }
+    }
+
+    cinfo.err = jpeg_std_error( &jerr );
+    jpeg_create_compress(&cinfo);
+
+    cinfo.dest              = &destmgr;
+    cinfo.image_width       = xsize;
+    cinfo.image_height      = ysize;
+    cinfo.input_components  = 3;
+    cinfo.in_color_space    = JCS_RGB;
+
+    jpeg_set_defaults( &cinfo );
+
+    if (subsampling != -1)
+    {
+        switch (subsampling)
+        {
+        case 422:  // 2x1, 1x1, 1x1 (4:2:2) : Medium
+            {
+                cinfo.comp_info[0].h_samp_factor = 2;
+                cinfo.comp_info[0].v_samp_factor = 1;
+                cinfo.comp_info[1].h_samp_factor = 1;
+                cinfo.comp_info[1].v_samp_factor = 1;
+                cinfo.comp_info[2].h_samp_factor = 1;
+                cinfo.comp_info[2].v_samp_factor = 1;
+                break;
+            }
+        case 411:  // 2x2, 1x1, 1x1 (4:1:1) : High
+            {
+                cinfo.comp_info[0].h_samp_factor = 2;
+                cinfo.comp_info[0].v_samp_factor = 2;
+                cinfo.comp_info[1].h_samp_factor = 1;
+                cinfo.comp_info[1].v_samp_factor = 1;
+                cinfo.comp_info[2].h_samp_factor = 1;
+                cinfo.comp_info[2].v_samp_factor = 1;
+                break;
+            }
+        case 444:  // 1x1 1x1 1x1 (4:4:4) : None
+            {
+                cinfo.comp_info[0].h_samp_factor = 1;
+                cinfo.comp_info[0].v_samp_factor = 1;
+                cinfo.comp_info[1].h_samp_factor = 1;
+                cinfo.comp_info[1].v_samp_factor = 1;
+                cinfo.comp_info[2].h_samp_factor = 1;
+                cinfo.comp_info[2].v_samp_factor = 1;
+                break;
+            }
+        }
+    }
+
+    if (quality != -1)
+    {
+        jpeg_set_quality( &cinfo, quality, TRUE );
+    }
+
+    jpeg_start_compress( &cinfo, TRUE );
+
+    while( cinfo.next_scanline < cinfo.image_height )
+    {
+        row_pointer[0] = (JSAMPLE*)&tileBuf24Bit[ cinfo.next_scanline * cinfo.image_width *  cinfo.input_components];
+        jpeg_write_scanlines( &cinfo, row_pointer, 1 );
+    }
+    /* similar to read file, clean up after we're done compressing */
+    jpeg_finish_compress( &cinfo );
+    jpeg_destroy_compress( &cinfo );
+
+    // write data to output file
+    size = jpgbuf.size() - 2;
+    fwrite(&jpgbuf[2], size, 1, fid);
+
+    return size;
+}
+
+static double distance(const double u1, const double v1, const double u2, const double v2)
+{
+    double dU = u2 - u1; // lambda
+    double dV = v2 - v1; // roh
+
+    double d = 2*asin(sqrt(sin(dV/2) * sin(dV/2) + cos(v1) * cos(v2) * sin(dU/2) * sin(dU/2)));
+
+    return 6371010 * d;
+}
+
+static uint32_t scale2jnx(double scale)
+{
+    /*
+    Ok, I've made some calculations, and got the following formula to
+    calculate the JNX scale (S) depending on the map's meters/pixel
+    ratio (R):
+
+      S(R) =
+        qRound(
+          76437 *
+          exp(
+            ln(2.000032708011) *
+            qRound(
+              ln(R * 130.2084 / 76437) /
+              ln(2.000032708011)
+            )
+          )
+        )
+
+
+    where
+      qRound - is a function which returns the closest integer from
+        floating point value, [unfortunately its defined in C99 but not standard C++]
+      exp - exponent,
+      ln - natural logarithm.
+
+    Magic number 130.2084 - is an average value for
+      (JNX scale) / (maps meters per pixel)
+    ratio among all zoom levels in metric system.
+
+    Magic number 2.000032708011 is a ratio on which our standard scale
+    table is built. It is (76437 / 4777) ^ (1/4).
+    */
+
+    return (uint32_t)floor(0.5 + 76437 * exp(log(2.000032708011) * floor(0.5 + log(scale * 10 * 130.2084 / 76437) / log(2.000032708011) ) ) );
+}
+
+static char randChar()
+{
+    char buf[2];
+#if defined(HAVE_ARC4RANDOM)
+    int r = (int)((arc4random() * 16.0) / UINT_MAX);
+#else
+    int r = (int)((rand() * 16.0) / RAND_MAX);
+#endif
+    sprintf(buf,"%X", r & 0x0F);
+    return buf[0];
+}
+
+static void createGUID(char * guid)
+{
+#if !defined(HAVE_ARC4RANDOM)
+    srand((unsigned int)time(0));
+#endif
+
+    guid[0]     = randChar();
+    guid[1]     = randChar();
+    guid[2]     = randChar();
+    guid[3]     = randChar();
+    guid[4]     = randChar();
+    guid[5]     = randChar();
+    guid[6]     = randChar();
+    guid[7]     = randChar();
+    guid[8]     = '-';
+    guid[9]     = randChar();
+    guid[10]    = randChar();
+    guid[11]    = randChar();
+    guid[12]    = randChar();
+    guid[13]    = '-';
+    guid[14]    = randChar();
+    guid[15]    = randChar();
+    guid[16]    = randChar();
+    guid[17]    = randChar();
+    guid[18]    = '-';
+    guid[19]    = randChar();
+    guid[20]    = randChar();
+    guid[21]    = randChar();
+    guid[22]    = randChar();
+    guid[23]    = '-';
+    guid[24]    = randChar();
+    guid[25]    = randChar();
+    guid[26]    = randChar();
+    guid[27]    = randChar();
+    guid[28]    = randChar();
+    guid[29]    = randChar();
+    guid[30]    = randChar();
+    guid[31]    = randChar();
+    guid[32]    = randChar();
+    guid[33]    = randChar();
+    guid[34]    = randChar();
+    guid[35]    = randChar();
+    guid[36]    = 0;
+
+}
+
+/// this code is from the GDAL project
+static void printProgress(int current, int total)
+{
+    double dfComplete = double(current)/double(total);
+
+    static int nLastTick = -1;
+    int nThisTick = (int) (dfComplete * 40.0);
+
+    nThisTick = MIN(40,MAX(0,nThisTick));
+
+    // Have we started a new progress run?
+    if( nThisTick < nLastTick && nLastTick >= 39 )
+    {
+        nLastTick = -1;
+    }
+
+    if( nThisTick <= nLastTick )
+    {
+        return;
+    }
+
+    while( nThisTick > nLastTick )
+    {
+        nLastTick++;
+        if( nLastTick % 4 == 0 )
+        {
+            fprintf( stdout, "%d", (nLastTick / 4) * 10 );
+        }
+        else
+        {
+            fprintf( stdout, "." );
+        }
+    }
+
+    if( nThisTick == 40 )
+    {
+        fprintf( stdout, " - done.\n" );
+    }
+    else
+    {
+        fflush( stdout );
+    }
+
+}
+
+
+int main(int argc, char ** argv)
+{
+    uint16_t tmp16;
+    const uint8_t dummy = 0;
+    uint32_t tileTableStart = 0;
+    uint32_t tileCnt    = 0;
+    uint32_t tilesTotal = 0;
+    char projstr[1024];
+    OGRSpatialReference oSRS;
+    int quality         = -1;
+    int subsampling     = -1;
+
+    const char *copyright = "Unknown";
+    const char *subscname = "BirdsEye";
+    const char *mapname   = "Unknown";
+
+    char *copyright_buf = NULL;
+    char *subscname_buf = NULL;
+    char *mapname_buf   = NULL;
+
+    std::vector<int> forced_scale_values;
+
+    printf("\n****** %s ******\n", WHAT_STR);
+
+    if(argc < 2)
+    {
+        fprintf(stderr,"\nusage: qmt_map2jnx -q <1..100> -s <411|422|444> -p <0..> -c \"copyright notice\" -m \"BirdsEye\" -n \"Unknown\" -x file1_scale,file2_scale,...,fileN_scale <file1> <file2> ... <fileN> <outputfile>\n");
+        fprintf(stderr,"\n");
+        fprintf(stderr,"  -q The JPEG quality from 1 to 100. Default is 75 \n");
+        fprintf(stderr,"  -s The chroma subsampling. Default is 411  \n");
+        fprintf(stderr,"  -p The product ID. Default is 0  \n");
+        fprintf(stderr,"  -c The copyright notice. Default is \"Unknown\"  \n");
+        fprintf(stderr,"  -m The subscription product name. Default is \"BirdsEye\"  \n");
+        fprintf(stderr,"  -n The map name. Default is \"Unknown\"  \n");
+        fprintf(stderr,"  -z The z order (drawing order). Default is 25\n");
+        fprintf(stderr,"  -x Override levels scale. Default: autodetect\n");
+        fprintf(stderr,"\n");
+        fprintf(stderr,"\nThe projection of the input files must have the same latitude along");
+        fprintf(stderr,"\na pixel row. Mecator and Longitude/Latitude projections match this");
+        fprintf(stderr,"\nthis property. Transversal Merkator or Lambert projections do not.");
+        fprintf(stderr,"\n");
+        fprintf(stderr,"\nTo rectify a geotiff map, you can use the gdalwarp command, e.g.");
+        fprintf(stderr,"\ngdalwarp -t_srs \"EPSG:4326\" <inputfile> <outputfile>");
+        fprintf(stderr,"\n");
+        fprintf(stderr,"Scale levels must be pass in same order as level files pointed.\n");
+        fprintf(stderr,"Empty and zero values equal to autodetect. We can point only needed\n");
+        fprintf(stderr,"levels, like:\n");
+        fprintf(stderr,"  -x 45356,,,75; -x ,,,,75\n");
+        fprintf(stderr,"Calculated levels table can be found:\n");
+        fprintf(stderr,"  English: http://whiter.brinkster.net/en/JNX.shtml\n");
+        fprintf(stderr,"  Russian: http://whiter.brinkster.net/JNX.shtml\n");
+        fprintf(stderr,"Most common values for different map scales:\n");
+        fprintf(stderr,"  JNX scale              Map scale\n");
+        fprintf(stderr,"  -------------          ---------\n");
+        fprintf(stderr,"  78125-31250            1:1 000 000\n");
+        fprintf(stderr,"  20834-7813             1:500 000\n");
+        fprintf(stderr,"  7813-3125              1:200 000\n");
+        fprintf(stderr,"  3125-2084              1:100 000\n");
+        fprintf(stderr,"  2084-782               1:50 000\n");
+        fprintf(stderr,"  782-32                 1:25 000\n");
+        fprintf(stderr,"  32-21                  1:10 000\n");
+        fprintf(stderr,"  21-14                  1:5000, 1:2000\n");
+        fprintf(stderr,"\n");
+        fprintf(stderr,"\n");
+        exit(-1);
+    }
+
+    GDALAllRegister();
+    wgs84 = pj_init_plus("+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs");
+
+    // read geo information from input files
+    //files.resize(argc - 2);
+    int skip_next_arg = 0;
+    int files_count = 0;
+
+    for(int i = 1; i < (argc - 1); i++)
+    {
+        if (skip_next_arg)
+        {
+            skip_next_arg = 0;
+            continue;
+        }
+
+        if (argv[i][0] == '-')
+        {
+            if (towupper(argv[i][1]) == 'Q')
+            {
+                quality = atol(argv[i+1]);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'S')
+            {
+                subsampling = atol(argv[i+1]);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'P')
+            {
+                jnx_hdr.productId = atol(argv[i+1]);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'C')
+            {
+                copyright = copyright_buf = get_argv(i + 1, argv);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'M')
+            {
+                subscname = subscname_buf = get_argv(i + 1, argv);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'N')
+            {
+                mapname = mapname_buf = get_argv(i + 1, argv);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'Z')
+            {
+                jnx_hdr.zorder = atol(argv[i+1]);
+                skip_next_arg = 1;
+                continue;
+            }
+            else if (towupper(argv[i][1]) == 'X')
+            {
+                skip_next_arg = 1;
+
+                std::string scales_buf(get_argv(i + 1, argv));
+                size_t pos = 0;
+                size_t last_pos = 0;
+
+                pos = scales_buf.find_first_of(',');
+                std::string val;
+                while (pos != std::string::npos)
+                {
+                    val = scales_buf.substr(last_pos, pos - last_pos);
+                    last_pos = pos + 1;
+                    pos = scales_buf.find_first_of(',', pos + 1);
+
+                    //printf("val: %s : %d\n", val.c_str(), pos);
+                    forced_scale_values.push_back(atol(val.c_str()));
+                }
+                val = scales_buf.substr(last_pos, pos);
+                //printf("val: %s : %d\n", val.c_str(), pos);
+                forced_scale_values.push_back(atol(val.c_str()));
+
+                continue;
+            }
+
+        }
+
+        files_count++;
+        files.resize(files_count);
+
+        double dist;
+
+        GDALDataset * dataset = (GDALDataset*)GDALOpen(argv[i],GA_ReadOnly);
+        if(dataset == 0)
+        {
+            fprintf(stderr,"\nFailed to open %s\n", argv[i]);
+            exit(-1);
+        }
+
+        projPJ   pj;
+        char * ptr = projstr;
+
+        if(dataset->GetProjectionRef())
+        {
+            strncpy(projstr,dataset->GetProjectionRef(),sizeof(projstr));
+        }
+        oSRS.importFromWkt(&ptr);
+        oSRS.exportToProj4(&ptr);
+
+        pj = pj_init_plus(ptr);
+        if(pj == 0)
+        {
+            fprintf(stderr,"\nUnknown projection in file %s\n", argv[i]);
+            exit(-1);
+        }
+
+        double adfGeoTransform[6];
+        dataset->GetGeoTransform( adfGeoTransform );
+
+        std::list<file_t>::iterator f = files.begin();
+        std::advance(f, files_count - 1);
+
+        file_t& file    = *f;
+        file.filename   = argv[i];
+        file.projection = ptr;
+        file.dataset    = dataset;
+        file.pj         = pj;
+        file.width      = dataset->GetRasterXSize();
+        file.height     = dataset->GetRasterYSize();
+        file.xscale     = adfGeoTransform[1];
+        file.yscale     = adfGeoTransform[5];
+        file.xref1      = adfGeoTransform[0];
+        file.yref1      = adfGeoTransform[3];
+        file.xref2      = file.xref1 + file.width  * file.xscale;
+        file.yref2      = file.yref1 + file.height * file.yscale;
+
+        if(pj_is_latlong(file.pj))
+        {
+            file.lon1 = file.xref1;
+            file.lat1 = file.yref1;
+            file.lon2 = file.xref2;
+            file.lat2 = file.yref2;
+        }
+        else
+        {
+            file.lon1 = file.xref1;
+            file.lat1 = file.yref1;
+            file.lon2 = file.xref2;
+            file.lat2 = file.yref2;
+
+            pj_transform(pj,wgs84,1,0,&file.lon1,&file.lat1,0);
+            pj_transform(pj,wgs84,1,0,&file.lon2,&file.lat2,0);
+
+            file.lon1 *= RAD_TO_DEG;
+            file.lat1 *= RAD_TO_DEG;
+            file.lon2 *= RAD_TO_DEG;
+            file.lat2 *= RAD_TO_DEG;
+        }
+
+        dist = distance(file.lon1 * DEG_TO_RAD, file.lat1 * DEG_TO_RAD, file.lon2 * DEG_TO_RAD, file.lat1 * DEG_TO_RAD);
+        file.scale = dist/file.width;
+
+        // fill color table if necessary
+        GDALRasterBand * pBand;
+        pBand = dataset->GetRasterBand(1);
+
+        if(pBand->GetColorInterpretation() == GCI_PaletteIndex)
+        {
+            GDALColorTable * pct = pBand->GetColorTable();
+            for(int c=0; c < pct->GetColorEntryCount(); ++c)
+            {
+                const GDALColorEntry& e = *pct->GetColorEntry(c);
+                file.colortable[c] = e.c1 | (e.c2 << 8) | (e.c3 << 16) | (e.c4 << 24);
+            }
+        }
+        else if(pBand->GetColorInterpretation() ==  GCI_GrayIndex )
+        {
+            for(int c=0; c < 256; ++c)
+            {
+                file.colortable[c] = c | (c << 8) | (c << 16) | 0xFF000000;
+            }
+        }
+
+        int success = 0;
+        int idx = (int)pBand->GetNoDataValue(&success);
+
+        if(success)
+        {
+            file.colortable[idx] &= 0x00FFFFFF;
+        }
+    }
+
+    // apply sorted files to levels and extract file header data
+    double right    = -180.0;
+    double top      =  -90.0;
+    double left     =  180.0;
+    double bottom   =   90.0;
+
+    double scale = 0.0;
+    files.sort();
+    std::list<file_t>::iterator f;
+    for(f = files.begin(); f != files.end(); f++)
+    {
+        file_t& file = *f;
+        prinfFileinfo(file);
+
+        if(file.lon1 < left)    left   = file.lon1;
+        if(file.lat1 > top)     top    = file.lat1;
+        if(file.lat2 < bottom)  bottom = file.lat2;
+        if(file.lon2 > right)   right  = file.lon2;
+
+        if(scale != 0.0 && ((fabs(scale - file.xscale)) / scale) > 0.02)
+        {
+            nLevels++;
+            if(nLevels > 4)
+            {
+                fprintf(stderr,"\nToo many different detail levels.\n");
+                exit(-1);
+            }
+        }
+        scale = file.xscale;
+
+        levels[nLevels].files.push_back(&file);
+    }
+    nLevels++;
+
+    FILE * fid = fopen(argv[argc-1],"wb");
+    if(fid == 0)
+    {
+        fprintf(stderr,"\nFailed to create file %s\n", argv[argc-1]);
+        exit(-1);
+    }
+
+    jnx_hdr.left    = (int32_t)((left   * 0x7FFFFFFF) / 180);
+    jnx_hdr.top     = (int32_t)((top    * 0x7FFFFFFF) / 180);
+    jnx_hdr.right   = (int32_t)((right  * 0x7FFFFFFF) / 180);
+    jnx_hdr.bottom  = (int32_t)((bottom * 0x7FFFFFFF) / 180);
+
+    jnx_hdr.details = nLevels;
+
+    printf("\n\n======== map header ========");
+    printf("\nmap area (top/left, bottom/right): %f %f, %f %f", left, top, right, bottom);
+    printf("\n                                   %08X %08X, %08X %08X", jnx_hdr.left, jnx_hdr.top, jnx_hdr.right, jnx_hdr.bottom);
+    printf("\nnumber of detail levels:           %i", jnx_hdr.details);
+    printf("\nz-order:                           %i\n", jnx_hdr.zorder);
+
+
+    for(int i=0; i<HEADER_BLOCK_SIZE; i++)
+    {
+        fwrite(&dummy, sizeof(dummy), 1, fid);
+    }
+    fseeko(fid,0,SEEK_SET);
+    fwrite(&jnx_hdr, sizeof(jnx_hdr), 1, fid);
+
+    // --------------------------------------------------------------
+    // get all information to write the table of detail levels and the dummy tile table
+    for(int i = 0; i < nLevels; i++)
+    {
+        uint32_t size   = 256;
+        level_t& level  = levels[i];
+        std::list<file_t *>::iterator f;
+        double scale    = 0.0;
+
+        while(size <= JNX_MAX_TILE_SIZE)
+        {
+            level.nTiles    = 0;
+            level.tileSize  = size;
+            for(f = level.files.begin(); f != level.files.end(); f++)
+            {
+                file_t& file  = *(*f);
+                double xTiles = file.width  / double(size);
+                double yTiles = file.height / double(size);
+                level.nTiles += int(ceil(xTiles)) * int(ceil(yTiles));
+
+                scale         = file.scale;
+            }
+
+            if(level.nTiles < JNX_MAX_TILES)
+            {
+                break;
+            }
+            size <<= 1;
+        }
+
+
+        level.offset    = tilesTotal * sizeof(jnx_tile_t) + HEADER_BLOCK_SIZE; // still has to be offset by complete header
+        if (forced_scale_values.size() == 0 || (unsigned)i >= forced_scale_values.size() ||  forced_scale_values[i] == 0)
+        {
+            level.scale     = scale2jnx(scale);
+        }
+        else
+        {
+            level.scale     = forced_scale_values[i];
+        }
+        tilesTotal     += level.nTiles;
+
+        fwrite(&level.nTiles, sizeof(level.nTiles), 1, fid);
+        fwrite(&level.offset, sizeof(level.offset), 1, fid);
+        fwrite(&level.scale, sizeof(level.scale), 1, fid);
+        fwrite(&level.dummy, sizeof(level.dummy), 1, fid);
+        fwrite(copyright, strlen(copyright) + 1, 1, fid);
+
+
+        printf("\n    Level %i: % 5i tiles, offset %08X, scale: %i, %ix%i", i, level.nTiles, level.offset, level.scale, level.tileSize, level.tileSize);
+
+    }
+
+    // --------------------------------------------------------------
+    // write map loader info block
+    uint32_t blockVersion = 0x00000009;
+    char GUID[40];
+    createGUID(GUID);
+
+    tmp16 = jnx_hdr.productId;
+
+    fwrite(&blockVersion, sizeof(blockVersion), 1, fid);
+    fwrite(GUID, 37, 1, fid);
+    fwrite(subscname, strlen(subscname) + 1, 1, fid);
+    fwrite(&dummy, sizeof(dummy), 1, fid);
+    fwrite(&tmp16, sizeof(tmp16), 1, fid);
+    fwrite(mapname, strlen(mapname) + 1, 1, fid);
+    fwrite(&nLevels , sizeof(nLevels), 1, fid);
+    for(int i = 1; i <= nLevels; i++)
+    {
+        char str[40];
+        sprintf(str,"Level %i", i);
+        fwrite(str, strlen(str) + 1, 1, fid);
+        fwrite(str, strlen(str) + 1, 1, fid);
+        fwrite(copyright, strlen(copyright) + 1, 1, fid);
+        fwrite(&i,sizeof(i), 1, fid);
+    }
+
+    // --------------------------------------------------------------
+    // write dummy tile table
+    tileTableStart = HEADER_BLOCK_SIZE;
+    fseeko(fid, tileTableStart, SEEK_SET);
+    fwrite(tileTable, sizeof(jnx_tile_t), tilesTotal, fid);
+
+    // --------------------------------------------------------------
+    // read tiles from input files and write jpeg coded tiles to output file
+    printf("\n\nStart conversion:\n");
+    for(int l = 0; l < nLevels; l++)
+    {
+        level_t& level = levels[l];
+
+        std::list<file_t *>::iterator f;
+        for(f = level.files.begin(); f != level.files.end(); f++)
+        {
+            file_t& file  = *(*f);
+
+            uint32_t xoff = 0;
+            uint32_t yoff = 0;
+
+            uint32_t xsize = level.tileSize;
+            uint32_t ysize = level.tileSize;
+
+            while(yoff < file.height)
+            {
+                if(ysize > (file.height - yoff))
+                {
+                    ysize = file.height - yoff;
+                }
+
+                xsize = level.tileSize;
+                xoff  = 0;
+
+                while(xoff < file.width)
+                {
+                    if(xsize > (file.width - xoff))
+                    {
+                        xsize = (file.width - xoff);
+                    }
+
+                    // //
+                    if(!readTile(xoff, yoff, xsize, ysize, file, tileBuf32Bit))
+                    {
+                        fprintf(stderr,"\nError reading tiles from map file\n");
+                        exit(-1);
+                    }
+
+                    jnx_tile_t& tile = tileTable[tileCnt++];
+                    if(pj_is_latlong(file.pj))
+                    {
+
+                        double u1 = file.lon1 + xoff * file.xscale;
+                        double v1 = file.lat1 + yoff * file.yscale;
+                        double u2 = file.lon1 + (xoff + xsize) * file.xscale;
+                        double v2 = file.lat1 + (yoff + ysize) * file.yscale;
+
+
+                        tile.left   = (int32_t)(u1 * 0x7FFFFFFF / 180);
+                        tile.top    = (int32_t)(v1 * 0x7FFFFFFF / 180);
+                        tile.right  = (int32_t)(u2 * 0x7FFFFFFF / 180);
+                        tile.bottom = (int32_t)(v2 * 0x7FFFFFFF / 180);
+
+                    }
+                    else
+                    {
+                        double u1 = file.xref1 + xoff * file.xscale;
+                        double v1 = file.yref1 + yoff * file.yscale;
+                        double u2 = file.xref1 + (xoff + xsize) * file.xscale;
+                        double v2 = file.yref1 + (yoff + ysize) * file.yscale;
+
+                        pj_transform(file.pj,wgs84,1,0,&u1,&v1,0);
+                        pj_transform(file.pj,wgs84,1,0,&u2,&v2,0);
+
+                        tile.left    = (int32_t)((u1 * RAD_TO_DEG) * 0x7FFFFFFF / 180);
+                        tile.top     = (int32_t)((v1 * RAD_TO_DEG) * 0x7FFFFFFF / 180);
+                        tile.right   = (int32_t)((u2 * RAD_TO_DEG) * 0x7FFFFFFF / 180);
+                        tile.bottom  = (int32_t)((v2 * RAD_TO_DEG) * 0x7FFFFFFF / 180);
+                    }
+
+                    tile.width  = xsize;
+                    tile.height = ysize;
+                    tile.offset = (uint32_t)(ftello(fid) & 0x0FFFFFFFF);
+                    tile.size   = writeTile(xsize, ysize, tileBuf32Bit, fid, quality, subsampling);
+
+                    printProgress(tileCnt, tilesTotal);
+                    // //
+                    xoff += xsize;
+                }
+
+                yoff += ysize;
+            }
+        }
+    }
+
+    // terminate output file
+    fwrite("BirdsEye", 8, 1, fid);
+
+    // write final tile table
+    fseeko(fid, tileTableStart, SEEK_SET);
+    fwrite(tileTable, sizeof(jnx_tile_t), tilesTotal, fid);
+    // done
+    fclose(fid);
+
+    // clean up
+    pj_free(wgs84);
+    GDALDestroyDriverManager();
+    if (copyright_buf)
+        free(copyright_buf);
+    if (subscname_buf)
+        free(subscname_buf);
+    if (mapname_buf)
+        free(mapname_buf);
+    printf("\n\n");
+    return 0;
+}