Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. 3rd Party Software
  4. Weird collateral effect when building a QT application using shapelib
Forum Updated to NodeBB v4.3 + New Features

Weird collateral effect when building a QT application using shapelib

Scheduled Pinned Locked Moved Unsolved 3rd Party Software
4 Posts 2 Posters 665 Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    bleriot13
    wrote on 7 Mar 2022, 13:05 last edited by
    #1

    Hi!

    Since 2018 I have been developing a suite of applications using Qt5 on Windows. Many of these applications read the so-called "ESRI shapefile" format. This is a quite old format that is still very actively used in geo-applications.

    In fact, a shapefile is a set of several files, one of them being stored in the venerable dBase format. The extension for dBase files is “.dbf”.

    To read and write shapefiles (and the several kinds of files these are composed of) in C or C++ a seasoned library, the “shapelib”, has been used for about two decades now. Of course, my applications use the shapelib library too to do it. The shapelib may be obtained here.

    This combination, C++, Qt5, shapelib and other open-source libraries has worked as a charm for 5 years now. Until now, that I decided to migrate these applications to Linux.

    The problem I’m facing is that, in some cases that I will explain later, the digits after the decimal point for all the float data stored in a shapelib are lost. In short, if the file stores something like 123.4567, what I get when I read (or write) this value is just 123.0000.

    Weird, isn’t it?

    After carefully debugging my applications, I concluded that instantiating a QApplication (GUI apps) or QCoreApplication (command line, no GUI apps) was the reason why this problem happened.

    The problem is that I can live without a QCoreApplication object in a command-line, non-GUI app, but it is impossible to avoid QApplication objects in GUI-based apps.

    I have attached the source code required to build a very simple command line application by yourselves and see how this happens. (this is not a minimalistic application devised to show the problem, not the real ones that I'm trying to migrate to Linux).

    The six source files making the shapelib may be downloaded from the link above (to avoid cluttering too much this post). The names of the files required are listed in the .pro file, but I reproduce these here for the sake of convenience:

    dbfopen.c, shapefil.h, safileio.c, shpopen.c, sbnsearch.c and shptree.c

    The quickest way to rebuild the project is to copy the whole set of files (the ones shown later in this post plus the six from the shapelib) in the same folder. The .pro file is prepared to work this way.

    The problem is that I had prepared a very simple .dbf file (5 rows, 5 float values each row) to let you check the app but I’ve found no way to upload it. I can send it via email, Google Driver or whatever means you prefer, but tell me how.

    Below I’ll copy the source code as well as some screenshots showing the results of executing several versions (Windows vs. Linux, using QCoreApplication or not...). If any of you has any idea about how to solve this problem, please let me know.

    Oh! I forgot to say: I'm using QT 5.15.2 in both platforms (Windows, Linux), Visual Studio 2015 (Windows 10) and gcc (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0 in my Linux Mint 20.3 Cinnamon box.

    Thank you very much for your help...

    File: dbf_reader_cmd.pro

    QT -= gui
    
    TARGET = dbf_reader_cmd
    CONFIG += c++11 console
    CONFIG -= app_bundle
    DEFINES += QT_DEPRECATED_WARNINGS
    
    #
    # Configure the destination directories here.
    #
    # Destinations are dependent on the configuration and platform
    # being used.
    #
    
    ROOT_DIRECTORY = $$PWD
    
    CONFIG(release, debug|release): CURRENT_CONFIG = Release
    else: CONFIG(debug, debug|release): CURRENT_CONFIG = Debug
    
    # We only compile 64-bit executables.
    
    PLATFORM = x64
    
    # Build and other directories.
    
    win32: BUILD_DIRECTORY = $${ROOT_DIRECTORY}/$${PLATFORM}/$${CURRENT_CONFIG}
    unix:!macx: BUILD_DIRECTORY = $${ROOT_DIRECTORY}/$${CURRENT_CONFIG}
    
    OBJECTS_DIR = $${BUILD_DIRECTORY}
    MOC_DIR     = $${BUILD_DIRECTORY}
    RCC_DIR     = $${BUILD_DIRECTORY}
    UI_DIR      = $${BUILD_DIRECTORY}
    DESTDIR     = $${BUILD_DIRECTORY}
    
    HEADERS += \
        dbf_utils.hpp \
        shapefil.h
    
    SOURCES += \
        dbf_utils.cpp \
        dbfopen.c \
        main_cmd.cpp \
        safileio.c \
        sbnsearch.c \
        shpopen.c \
        shptree.c
    

    File: dbf_utils.cpp

    /** \file dbf_utils.cpp
    \brief Implementation file for dbf_utils.hpp
    */
    
    #include "dbf_utils.hpp"
    
    DBF_utils::DBF_utils(void)
    {
      {
        // Intentionally left blank.
      }
    }
    
    bool
    DBF_utils::
    read_DBF_header
    (DBFHandle&	hDBF,
     string&    field_names,
     int&       n_rows,
     int&       n_columns)
    {
      {
        char*        field_name;
        DBFFieldType field_type;
        string       s_field_name;
    
        //
        // Get the number of fields in the DBF file
        // (that is, the number of columns in the file).
        //
    
        n_columns = DBFGetFieldCount(hDBF);
    
        // Number of rows.
    
        n_rows = DBFGetRecordCount(hDBF);
    
        //
        // Iterate for all the fields in the header to
        // get their names. These are stored in our
        // output "header" parameter.
        //
    
        field_names = "";
    
        for (int i = 0; i < n_columns; i++)
        {
          field_name = new char[12];
    
          field_type = DBFGetFieldInfo(hDBF, i, field_name, nullptr, nullptr);
          if (field_type == FTInvalid) return false;
    
          s_field_name = field_name;
    
          field_names += s_field_name;
          if (i != (n_columns-1)) field_names += " ";
        }
    
        // That's all.
    
        return true;
      }
    }
    
    void
    DBF_utils::
    read_DBF_row
    (      DBFHandle&      hDBF,
     const int             row_number,
     const int             columns_to_read,
           vector<double>& values)
    {
      {
        double value;
    
        // Reset the output vector.
    
        values.clear();
    
        //
        // Read as many columns as requested from the row selected.
        // Store the successively read values into the output vector.
        //
    
        for (int field_column = 0; field_column < columns_to_read; field_column++)
        {
          value =  DBFReadDoubleAttribute(hDBF, row_number, field_column);
          values.push_back(value);
        }
      }
    
      return;
    }
    

    File: dbf_utils.hp

    /** \file dbf_utils.hpp
    \brief Simple utilities for reading still simpler dbf files.
    */
    
    #ifndef DBF_UTILS_HPP
    #define DBF_UTILS_HPP
    
    #include "shapefil.h"
    
    #include <vector>
    #include <string>
    
    using namespace std;
    
    
    class DBF_utils
    {
      public:
    
        /// \brief Default constructor.
    
        DBF_utils (void);
    
        /// \brief Read the DBF header to retrieve the list of
        ///        field names, the number of fields (columns)
        ///        and rows (data records).
        /**
           \param hDBF Handle to an already opened DBF file.
           \param field_names A string containing the list of
                  field names (separated by blanks).
           \param Number of data rows in the DBF file.
           \param Number of data columns (fields) in the DBF file.
           \return True if the header may be read, false otherwise.
    
          Note that the DBF file must have been opened before calling
          this method.
         */
    
        bool     read_DBF_header (DBFHandle&	hDBF,
                                  string&     field_names,
                                  int&        n_rows,
                                  int&        n_columns);
    
        /// \brief Read a single row from the DBF file.
        /**
          \param hDBF A handle to the already opened DBF file.
          \param row_number Number of the row that must be read.
          \param columns_to_read Number of columns (starting from the
                 first one) that must be read from the given row.
          \param values The set of values read from the DBF file
    
          Note that the DBF file must have been opened before calling
          this method.
    
         */
    
        void     read_DBF_row    (      DBFHandle&       hDBF,
                                  const int              row_number,
                                  const int              columns_to_read,
                                        vector<double>&  values);
    };
    
    #endif // DBF_UTILS_HPP
    

    File: main_cmd.cpp - Look for the "QCoreApplication a(argc, argv)" sentence and comment it (to get an app that works properly) or uncomment it (to make all decimal values become 0).

    /** \file main.cpp
    \brief Main entry point for the dump_dbf_cmd application.
    */
    
    //
    // The following inclusion is not really needed. It is included
    // here to let you uncomment the "QCoreApplication a(argc, argv)"
    // at the beginning of the main function.
    //
    
    #include <QCoreApplication>
    
    #include "dbf_utils.hpp"
    #include "shapefil.h"
    
    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    /// \brief ADAfinder application's main function.
    /**
      \param argc Number of command line parameters.
      \param argv The command line parameters.
      \return 0 if the process finished successfully, any other
             value otherwise.
    
      This is the dbf_reader_cmd application's main entry point.
      At least one parameter must be provided: the name of the
      dbf file to dump.
     */
    
    int
    main
    (int   argc,
     char* argv[])
    {
      {
        //
        // If the following sentence is uncommented, the program stops
        // working properly. All the values after the decimal point
        // simply vanish. For instance, a value that should be
        // 1.2 becomes 1.0.
        //
    
        //QCoreApplication a(argc, argv);
    
        string    dbfile;
        DBF_utils dbu;
        DBFHandle hDBF;
        string    header;
        int       n_cols;
        int       n_rows;
        string    the_list_of_fields;
    
        //
        // Check that we've got at least one parameter: the name
        // of the dbf file to dump.
        //
    
        if (argc < 2)
        {
          cout << "dump_dbf_cmd <dbf_file_name>" << endl;
          return 1;
        }
    
        // Name of the dbf file.
    
        dbfile = argv[1];
    
        // Open it.
    
        hDBF = DBFOpen(dbfile.c_str(), "rb");
        if (hDBF == nullptr)
        {
          // Unable to open the DBF file.
    
          cout << "Unable to open the input dbf file" << endl;
          return 1;
        }
    
        //
        // Read the header, thus retrieving the list of field
        // names as well as the number of rows (data records)
        // and columns (data fields) in the file.
        //
    
        if (!dbu.read_DBF_header (hDBF, the_list_of_fields, n_rows, n_cols))
        {
          cout << "Error reading the header" << endl;
          return 1;
        }
    
        // Print the header.
    
        cout << the_list_of_fields << endl;
    
        //
        // Read as many rows (records) and columns (fields per record)
        // as stated by the header. Print them.
        //
    
        for (int the_row = 0; the_row < n_rows; the_row++)
        {
          vector<double> values;
          dbu.read_DBF_row(hDBF, the_row, n_cols, values);
    
          for (int the_col = 0; the_col < n_cols; the_col++)
            cout << values[the_col] << " ";
    
          cout << endl;
        }
    
        // Close the dbf file.
    
        DBFClose(hDBF);
    
      }
    }
    

    The next picture shows the sample dbf file used for all the tests (opened with LibreOffice Calc). Note how all values have at least one decimal:

    DBF_with_LibreOffice_Calc.png

    The next one shows a Linux GUI-based application (using the same code to read the dbf file as the command line one whose source code you've seen above). Note how all numbers end in ".0000".

    Linux-GUI.png

    But if this very same code is run on Windows, the numbers are read properly:

    Windows-GUI.png

    The same problem shows up when the app whose code has been posted above is run on Linux if the QCoreApplication sentence is present in the code (no decimal part!!!)

    Linux-shell-QCoreApplication.png

    But If the QCoreApplication sentence is removed, then everything works as expected:

    Linux-shell-noQCoreApplication.png

    When running these command line on Windows, it does not matter whether the QCoreApplication sentence is present or not, the result is always correct:

    Windows-shell-noQCoreApplication.png

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on 3 May 2022, 07:28 last edited by
      #2

      Hi,

      Can you provide a sample dbf file that allows to reproduce this behaviour ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      1 Reply Last reply
      0
      • B Offline
        B Offline
        bleriot13
        wrote on 3 May 2022, 11:41 last edited by
        #3

        Hi, thanks for your interest!

        I solved the problem avoiding the use of threads and embedding the command-line versions of the software in the GUI ones, using processes instead.

        I destroyed the whole set of files (code, data) used for the example I'd posted, but, in fact, the problem was a generalised one. Reading any .dbf should produce the weird results, since it happened to me no matter what file I tried to load. To guarantee that the code I uploaded would work, the .dbf to read, however, should have just a series of an arbitrary number of DOUBLE fields to work (adding, for instance, boolean, integer or char attributes would make my example fail, because the example code expects finding only double values).

        I would have liked to be able to upload a sample, but, as I said above, I solved the problem using a different approach and now I'm assigned to a different project... so, I have to apologize, I have not the time to prepare a synthetic dataset again. Please, accept my most sincere apologies. The pressure to finish my current tasks is too strong.

        Thanks again for your interest.

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on 3 May 2022, 20:09 last edited by
          #4

          No worries !

          You manage to find a solution so that's the essential point.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved