// SPDX-License-Identifier: MIT
// Copyright 1999-2003, Daniel Morissette
// Copyright (c) 1999-2001, Frank Warmerdam
// Implementation translation between MIF CoordSys format, and
// and OGRSpatialRef format.

/*! @cond Doxygen_Suppress */

#include "ogrmitabspatialref.h"

#include "cpl_port.h"

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "ogr_spatialref.h"
#include "ogr_srs_api.h"

typedef struct
{
    int nDatumEPSGCode;
    int nMapInfoDatumID;
    const char *pszOGCDatumName;
    int nEllipsoid;
    double dfShiftX;
    double dfShiftY;
    double dfShiftZ;
    double dfDatumParm0; /* RotX */
    double dfDatumParm1; /* RotY */
    double dfDatumParm2; /* RotZ */
    double dfDatumParm3; /* Scale Factor */
    double dfDatumParm4; /* Prime Meridian */
} MapInfoDatumInfo;

typedef struct
{
    int nMapInfoId;
    const char *pszMapinfoName;
    double dfA;             /* semi major axis in meters */
    double dfInvFlattening; /* Inverse flattening */
} MapInfoSpheroidInfo;

/**********************************************************************
 * MapInfo Units string to numeric ID conversion
 **********************************************************************/
typedef struct
{
    int nUnitId;
    const char *pszAbbrev;
} MapInfoUnitsInfo;

static const MapInfoUnitsInfo gasUnitsList[] = {
    {0, "mi"},        {1, "km"},          {2, "in"},  {3, "ft"},
    {4, "yd"},        {5, "mm"},          {6, "cm"},  {7, "m"},
    {8, "survey ft"}, {8, "survey foot"},  // alternate
    {13, nullptr},    {9, "nmi"},         {30, "li"}, {31, "ch"},
    {32, "rd"},       {-1, nullptr}};

/**********************************************************************
 *                       TABUnitIdToString()
 *
 * Return the MIF units name for specified units id.
 * Return "" if no match found.
 *
 * The returned string should not be freed by the caller.
 **********************************************************************/
static const char *TABUnitIdToString(int nId)
{
    const MapInfoUnitsInfo *psList = gasUnitsList;

    while (psList->nUnitId != -1)
    {
        if (psList->nUnitId == nId)
            return psList->pszAbbrev;
        psList++;
    }

    return "";
}

/**********************************************************************
 *                       TABUnitIdFromString()
 *
 * Return the units ID for specified MIF units name
 *
 * Returns -1 if no match found.
 **********************************************************************/
static int TABUnitIdFromString(const char *pszName)
{
    if (pszName == nullptr)
        return 13;

    const MapInfoUnitsInfo *psList = gasUnitsList;

    while (psList->nUnitId != -1)
    {
        if (psList->pszAbbrev != nullptr && EQUAL(psList->pszAbbrev, pszName))
            return psList->nUnitId;
        psList++;
    }

    return -1;
}

/* -------------------------------------------------------------------- */
/*      This table was automatically generated by doing translations    */
/*      between mif and tab for each datum, and extracting the          */
/*      parameters from the tab file.  The EPSG codes and OGC names     */
/*      were added afterwards and may be incomplete or inaccurate.       */
/* -------------------------------------------------------------------- */

extern const MapInfoDatumInfo asDatumInfoList[];
extern const MapInfoSpheroidInfo asSpheroidInfoList[];

/* EPSG code, MapInfo datum ID (or 9999), OGC Name, datum parameters... */
const MapInfoDatumInfo asDatumInfoList[] = {

    {0, 104, "WGS_1984", 28, 0, 0, 0, 0, 0, 0, 0, 0},
    {6269, 74, "North_American_Datum_1983", 0, 0, 0, 0, 0, 0, 0, 0, 0},

    {0, 0, "", 29, 0, 0, 0, 0, 0, 0, 0, 0},  // Datum ignore

    {6201, 1, "Adindan", 6, -162, -12, 206, 0, 0, 0, 0, 0},
    {6205, 2, "Afgooye", 3, -43, -163, 45, 0, 0, 0, 0, 0},
    {6204, 3, "Ain_el_Abd_1970", 4, -150, -251, -2, 0, 0, 0, 0, 0},
    {0, 4, "Anna_1_Astro_1965", 2, -491, -22, 435, 0, 0, 0, 0, 0},
    {6209, 5, "Arc_1950", 15, -143, -90, -294, 0, 0, 0, 0, 0},
    {6210, 6, "Arc_1960", 6, -160, -8, -300, 0, 0, 0, 0, 0},
    {0, 7, "Ascension_Islands", 4, -207, 107, 52, 0, 0, 0, 0, 0},
    {0, 8, "Astro_Beacon_E", 4, 145, 75, -272, 0, 0, 0, 0, 0},
    {0, 9, "Astro_B4_Sorol_Atoll", 4, 114, -116, -333, 0, 0, 0, 0, 0},
    {0, 10, "Astro_Dos_71_4", 4, -320, 550, -494, 0, 0, 0, 0, 0},
    {0, 11, "Astronomic_Station_1952", 4, 124, -234, -25, 0, 0, 0, 0, 0},
    {6202, 12, "Australian_Geodetic_Datum_66", 2, -133, -48, 148, 0, 0, 0, 0,
     0},
    {6203, 13, "Australian_Geodetic_Datum_84", 2, -134, -48, 149, 0, 0, 0, 0,
     0},
    {0, 14, "Bellevue_Ign", 4, -127, -769, 472, 0, 0, 0, 0, 0},
    {6216, 15, "Bermuda_1957", 7, -73, 213, 296, 0, 0, 0, 0, 0},
    {6218, 16, "Bogota", 4, 307, 304, -318, 0, 0, 0, 0, 0},
    {6221, 17, "Campo_Inchauspe", 4, -148, 136, 90, 0, 0, 0, 0, 0},
    {0, 18, "Canton_Astro_1966", 4, 298, -304, -375, 0, 0, 0, 0, 0},
    {6222, 19, "Cape", 6, -136, -108, -292, 0, 0, 0, 0, 0},
    {6717, 20, "Cape_Canaveral", 7, -2, 150, 181, 0, 0, 0, 0, 0},
    {6223, 21, "Carthage", 6, -263, 6, 431, 0, 0, 0, 0, 0},
    {6672, 22, "Chatham_1971", 4, 175, -38, 113, 0, 0, 0, 0, 0},
    {6224, 23, "Chua", 4, -134, 229, -29, 0, 0, 0, 0, 0},
    {6225, 24, "Corrego_Alegre", 4, -206, 172, -6, 0, 0, 0, 0, 0},
    {6211, 25, "Batavia", 10, -377, 681, -50, 0, 0, 0, 0, 0},
    {0, 26, "Dos_1968", 4, 230, -199, -752, 0, 0, 0, 0, 0},
    {6719, 27, "Easter_Island_1967", 4, 211, 147, 111, 0, 0, 0, 0, 0},
    {6230, 28, "European_Datum_1950", 4, -87, -98, -121, 0, 0, 0, 0, 0},
    {6668, 29, "European_Datum_1979", 4, -86, -98, -119, 0, 0, 0, 0, 0},
    {6233, 30, "Gandajika_1970", 4, -133, -321, 50, 0, 0, 0, 0, 0},
    {6272, 31, "New_Zealand_GD49", 4, 84, -22, 209, 0, 0, 0, 0, 0},
    {6272, 31, "New_Zealand_Geodetic_Datum_1949", 4, 84, -22, 209, 0, 0, 0, 0,
     0},
    {0, 32, "GRS_67", 21, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 33, "GRS_80", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6171, 33, "Reseau_Geodesique_Francais_1993", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6619, 33, "SWEREF99", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6675, 34, "Guam_1963", 7, -100, -248, 259, 0, 0, 0, 0, 0},
    {0, 35, "Gux_1_Astro", 4, 252, -209, -751, 0, 0, 0, 0, 0},
    {6254, 36, "Hito_XVIII_1963", 4, 16, 196, 93, 0, 0, 0, 0, 0},
    {6658, 37, "Hjorsey_1955", 4, -73, 46, -86, 0, 0, 0, 0, 0},
    {6738, 38, "Hong_Kong_1963", 4, -156, -271, -189, 0, 0, 0, 0, 0},
    {6236, 39, "Hu_Tzu_Shan", 4, -634, -549, -201, 0, 0, 0, 0, 0},
    {0, 40, "Indian_Thailand_Vietnam", 11, 214, 836, 303, 0, 0, 0, 0, 0},
    {0, 41, "Indian_Bangladesh", 11, 289, 734, 257, 0, 0, 0, 0, 0},
    {6299, 42, "Ireland_1965", 13, 506, -122, 611, 0, 0, 0, 0, 0},
    {0, 43, "ISTS_073_Astro_1969", 4, 208, -435, -229, 0, 0, 0, 0, 0},
    {6725, 44, "Johnston_Island_1961", 4, 191, -77, -204, 0, 0, 0, 0, 0},
    {6244, 45, "Kandawala", 11, -97, 787, 86, 0, 0, 0, 0, 0},
    {0, 46, "Kerguyelen_Island", 4, 145, -187, 103, 0, 0, 0, 0, 0},
    {6245, 47, "Kertau", 17, -11, 851, 5, 0, 0, 0, 0, 0},
    {0, 48, "L_C_5_Astro", 7, 42, 124, 147, 0, 0, 0, 0, 0},
    {6251, 49, "Liberia_1964", 6, -90, 40, 88, 0, 0, 0, 0, 0},
    {0, 50, "Luzon_Phillippines", 7, -133, -77, -51, 0, 0, 0, 0, 0},
    {0, 51, "Luzon_Mindanao_Island", 7, -133, -79, -72, 0, 0, 0, 0, 0},
    {6256, 52, "Mahe_1971", 6, 41, -220, -134, 0, 0, 0, 0, 0},
    {0, 53, "Marco_Astro", 4, -289, -124, 60, 0, 0, 0, 0, 0},
    {6262, 54, "Massawa", 10, 639, 405, 60, 0, 0, 0, 0, 0},
    {6261, 55, "Merchich", 16, 31, 146, 47, 0, 0, 0, 0, 0},
    {0, 56, "Midway_Astro_1961", 4, 912, -58, 1227, 0, 0, 0, 0, 0},
    {6263, 57, "Minna", 6, -92, -93, 122, 0, 0, 0, 0, 0},
    {0, 58, "Nahrwan_Masirah_Island", 6, -247, -148, 369, 0, 0, 0, 0, 0},
    {0, 59, "Nahrwan_Un_Arab_Emirates", 6, -249, -156, 381, 0, 0, 0, 0, 0},
    {0, 60, "Nahrwan_Saudi_Arabia", 6, -231, -196, 482, 0, 0, 0, 0, 0},
    {6271, 61, "Naparima_1972", 4, -2, 374, 172, 0, 0, 0, 0, 0},
    {6267, 62, "NAD_1927", 7, -8, 160, 176, 0, 0, 0, 0, 0},
    {6267, 62, "North_American_Datum_1927", 7, -8, 160, 176, 0, 0, 0, 0, 0},
    {0, 63, "NAD_27_Alaska", 7, -5, 135, 172, 0, 0, 0, 0, 0},
    {0, 64, "NAD_27_Bahamas", 7, -4, 154, 178, 0, 0, 0, 0, 0},
    {0, 65, "NAD_27_San_Salvador", 7, 1, 140, 165, 0, 0, 0, 0, 0},
    {0, 66, "NAD_27_Canada", 7, -10, 158, 187, 0, 0, 0, 0, 0},
    {0, 67, "NAD_27_Canal_Zone", 7, 0, 125, 201, 0, 0, 0, 0, 0},
    {0, 68, "NAD_27_Caribbean", 7, -7, 152, 178, 0, 0, 0, 0, 0},
    {0, 69, "NAD_27_Central_America", 7, 0, 125, 194, 0, 0, 0, 0, 0},
    {0, 70, "NAD_27_Cuba", 7, -9, 152, 178, 0, 0, 0, 0, 0},
    {0, 71, "NAD_27_Greenland", 7, 11, 114, 195, 0, 0, 0, 0, 0},
    {0, 72, "NAD_27_Mexico", 7, -12, 130, 190, 0, 0, 0, 0, 0},
    {0, 73, "NAD_27_Michigan", 8, -8, 160, 176, 0, 0, 0, 0, 0},
    {0, 75, "Observatorio_1966", 4, -425, -169, 81, 0, 0, 0, 0, 0},
    {0, 76, "Old_Egyptian", 22, -130, 110, -13, 0, 0, 0, 0, 0},
    {6135, 77, "Old_Hawaiian", 7, 61, -285, -181, 0, 0, 0, 0, 0},
    {0, 78, "Oman", 6, -346, -1, 224, 0, 0, 0, 0, 0},
    {6277, 79, "OSGB_1936", 9, 375, -111, 431, 0, 0, 0, 0, 0},
    {0, 80, "Pico_De_Las_Nieves", 4, -307, -92, 127, 0, 0, 0, 0, 0},
    {6729, 81, "Pitcairn_Astro_1967", 4, 185, 165, 42, 0, 0, 0, 0, 0},
    {6248, 82, "Provisional_South_American", 4, -288, 175, -376, 0, 0, 0, 0, 0},
    {6139, 83, "Puerto_Rico", 7, 11, 72, -101, 0, 0, 0, 0, 0},
    {6614, 84, "Qatar_National", 4, -128, -283, 22, 0, 0, 0, 0, 0},
    {6287, 85, "Qornoq", 4, 164, 138, -189, 0, 0, 0, 0, 0},
    {6627, 86, "Reunion", 4, 94, -948, -1262, 0, 0, 0, 0, 0},
    {6265, 87, "Monte_Mario", 4, -225, -65, 9, 0, 0, 0, 0, 0},
    {0, 88, "Santo_Dos", 4, 170, 42, 84, 0, 0, 0, 0, 0},
    {0, 89, "Sao_Braz", 4, -203, 141, 53, 0, 0, 0, 0, 0},
    {6292, 90, "Sapper_Hill_1943", 4, -355, 16, 74, 0, 0, 0, 0, 0},
    {6293, 91, "Schwarzeck", 14, 616, 97, -251, 0, 0, 0, 0, 0},
    {6618, 92, "South_American_Datum_1969", 24, -57, 1, -41, 0, 0, 0, 0, 0},
    {0, 93, "South_Asia", 19, 7, -10, -26, 0, 0, 0, 0, 0},
    {0, 94, "Southeast_Base", 4, -499, -249, 314, 0, 0, 0, 0, 0},
    {0, 95, "Southwest_Base", 4, -104, 167, -38, 0, 0, 0, 0, 0},
    {6298, 96, "Timbalai_1948", 11, -689, 691, -46, 0, 0, 0, 0, 0},
    {6301, 97, "Tokyo", 10, -128, 481, 664, 0, 0, 0, 0, 0},
    {0, 98, "Tristan_Astro_1968", 4, -632, 438, -609, 0, 0, 0, 0, 0},
    {6731, 99, "Viti_Levu_1916", 6, 51, 391, -36, 0, 0, 0, 0, 0},
    {0, 100, "Wake_Entiwetok_1960", 23, 101, 52, -39, 0, 0, 0, 0, 0},
    {0, 101, "WGS_60", 26, 0, 0, 0, 0, 0, 0, 0, 0},
    {6760, 102, "WGS_66", 27, 0, 0, 0, 0, 0, 0, 0, 0},
    {6322, 103, "WGS_1972", 1, 0, 8, 10, 0, 0, 0, 0, 0},
    {6322, 103, "World_Geodetic_System_1972", 1, 0, 8, 10, 0, 0, 0, 0, 0},
    {6326, 104, "WGS_1984", 28, 0, 0, 0, 0, 0, 0, 0, 0},
    {6309, 105, "Yacare", 4, -155, 171, 37, 0, 0, 0, 0, 0},
    {6311, 106, "Zanderij", 4, -265, 120, -358, 0, 0, 0, 0, 0},
    {0, 107, "NTF", 30, -168, -60, 320, 0, 0, 0, 0, 0},
    {6231, 108, "European_Datum_1987", 4, -83, -96, -113, 0, 0, 0, 0, 0},
    {0, 109, "Netherlands_Bessel", 10, 593, 26, 478, 0, 0, 0, 0, 0},
    {0, 110, "Belgium_Hayford", 4, 81, 120, 129, 0, 0, 0, 0, 0},
    {0, 111, "NWGL_10", 1, -1, 15, 1, 0, 0, 0, 0, 0},
    {6124, 112, "Rikets_koordinatsystem_1990", 10, 498, -36, 568, 0, 0, 0, 0,
     0},
    {0, 113, "Lisboa_DLX", 4, -303, -62, 105, 0, 0, 0, 0, 0},
    {0, 114, "Melrica_1973_D73", 4, -223, 110, 37, 0, 0, 0, 0, 0},
    {6258, 115, "European_Terrestrial_Reference_System_1989", 0, 0, 0, 0, 0, 0,
     0, 0, 0},
    {6258, 115, "Euref_89", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6180, 115, "Estonia_1997", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6283, 116, "GDA94", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6283, 116, "Geocentric_Datum_of_Australia_1994", 0, 0, 0, 0, 0, 0, 0, 0,
     0},
    {6167, 117, "NZGD2000", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6167, 117, "New_Zealand_Geodetic_Datum_2000", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {6169, 118, "America_Samoa", 7, -115, 118, 426, 0, 0, 0, 0, 0},
    {0, 119, "Antigua_Astro_1965", 6, -270, 13, 62, 0, 0, 0, 0, 0},
    {6713, 120, "Ayabelle_Lighthouse", 6, -79, -129, 145, 0, 0, 0, 0, 0},
    {6219, 121, "Bukit_Rimpah", 10, -384, 664, -48, 0, 0, 0, 0, 0},
    {0, 122, "Estonia_1937", 10, 374, 150, 588, 0, 0, 0, 0, 0},
    {6155, 123, "Dabola", 6, -83, 37, 124, 0, 0, 0, 0, 0},
    {6736, 124, "Deception_Island", 6, 260, 12, -147, 0, 0, 0, 0, 0},
    {0, 125, "Fort_Thomas_1955", 6, -7, 215, 225, 0, 0, 0, 0, 0},
    {0, 126, "Graciosa_base_1948", 4, -104, 167, -38, 0, 0, 0, 0, 0},
    {6255, 127, "Herat_North", 4, -333, -222, 114, 0, 0, 0, 0, 0},
    {0, 128, "Hermanns_Kogel", 10, 682, -203, 480, 0, 0, 0, 0, 0},
    {6240, 129, "Indian", 50, 283, 682, 231, 0, 0, 0, 0, 0},
    {6239, 130, "Indian_1954", 11, 217, 823, 299, 0, 0, 0, 0, 0},
    {6131, 131, "Indian_1960", 11, 198, 881, 317, 0, 0, 0, 0, 0},
    {6240, 132, "Indian_1975", 11, 210, 814, 289, 0, 0, 0, 0, 0},
    {6238, 133, "Indonesian_Datum_1974", 4, -24, -15, 5, 0, 0, 0, 0, 0},
    {0, 134, "ISTS061_Astro_1968", 4, -794, 119, -298, 0, 0, 0, 0, 0},
    {0, 135, "Kusaie_Astro_1951", 4, 647, 1777, -1124, 0, 0, 0, 0, 0},
    {6250, 136, "Leigon", 6, -130, 29, 364, 0, 0, 0, 0, 0},
    {0, 137, "Montserrat_Astro_1958", 6, 174, 359, 365, 0, 0, 0, 0, 0},
    {6266, 138, "Mporaloko", 6, -74, -130, 42, 0, 0, 0, 0, 0},
    {0, 139, "North_Sahara_1959", 6, -186, -93, 310, 0, 0, 0, 0, 0},
    {0, 140, "Observatorio_Met_1939", 4, -425, -169, 81, 0, 0, 0, 0, 0},
    {6620, 141, "Point_58", 6, -106, -129, 165, 0, 0, 0, 0, 0},
    {6282, 142, "Pointe_Noire", 6, -148, 51, -291, 0, 0, 0, 0, 0},
    {6615, 143, "Porto_Santo_1936", 4, -499, -249, 314, 0, 0, 0, 0, 0},
    {6616, 144, "Selvagem_Grande_1938", 4, -289, -124, 60, 0, 0, 0, 0, 0},
    {0, 145, "Sierra_Leone_1960", 6, -88, 4, 101, 0, 0, 0, 0, 0},
    {6156, 146, "S_JTSK_Ferro", 10, 589, 76, 480, 0, 0, 0, 0, 0},
    {6297, 147, "Tananarive_1925", 4, -189, -242, -91, 0, 0, 0, 0, 0},
    {6811, 148, "Voirol_1874", 6, -73, -247, 227, 0, 0, 0, 0, 0},
    {0, 149, "Virol_1960", 6, -123, -206, 219, 0, 0, 0, 0, 0},
    {6148, 150, "Hartebeesthoek94", 28, 0, 0, 0, 0, 0, 0, 0, 0},
    {6122, 151, "ATS77", 51, 0, 0, 0, 0, 0, 0, 0, 0},
    {6612, 152, "JGD2000", 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {0, 153, "HGRS87", 0, -199.87, 74.79, 246.62, 0, 0, 0, 0, 0},
    {6214, 154, "Beijing 1954", 3, -31.4, 144.3, 81.2, 0, 0, 0, 0, 0},
    {6754, 155, "Libya (LGD 2006)", 4, 208.4058, 109.8777, 2.5764, 0, 0, 0, 0,
     0},
    {6317, 156, "Dealul Piscului 1970", 3, 28, -121, -77, 0, 0, 0, 0, 0},
    {0, 157, "WGS_1984", 54, 0, 0, 0, 0, 0, 0, 0, 0},  // Google merc
    {6150, 158, "CH1903+ datum for Switzerland", 10, 674.374, 15.056, 405.346,
     0, 0, 0, 0, 0},
    {0, 159, "Schwarzeck (updated) datum for Namibia", 14, 616.8, 103.3, -256.9,
     0, 0, 0, 0, 0},
    {0, 161, "NOAA GCS_Sphere", 55, 0, 0, 0, 0, 0, 0, 0, 0},
    // Ellipsoid 40 got from https://docs.precisely.com/docs/sftw/mapinfo-pro/v2021/en-us/pdf/mapinfo-pro-v2021-user-guide.pdf
    // Ellipsoid 40 is "Everest (India 1956)", 6377301.243, 300.80174
    // but EPSG uses "Everest 1830 (RSO 1969)",6377295.664,300.8017
    {6751, 164, "Kertau (RSO)", 40, -11, 851, 5, 0, 0, 0, 0, 0},
    {0, 1000, "DHDN_Potsdam_Rauenberg", 10, 582, 105, 414, -1.04, -0.35, 3.08,
     8.3, 0},
    {6284, 1001, "Pulkovo_1942", 3, 24, -123, -94, -0.02, 0.25, 0.13, 1.1, 0},
    {6807, 1002, "NTF_Paris_Meridian", 30, -168, -60, 320, 0, 0, 0, 0,
     2.337229166667},
    {6149, 1003, "Switzerland_CH_1903", 10, 660.077, 13.551, 369.344, 0.804816,
     0.577692, 0.952236, 5.66, 0},
    {6237, 1004, "Hungarian_Datum_1972", 21, -56, 75.77, 15.31, -0.37, -0.2,
     -0.21, -1.01, 0},
    {0, 1005, "Cape_7_Parameter", 28, -134.73, -110.92, -292.66, 0, 0, 0, 1, 0},
    {6203, 1006, "AGD84_7_Param_Aust", 2, -117.763, -51.51, 139.061, -0.292,
     -0.443, -0.277, -0.191, 0},
    {0, 1007, "AGD66_7_Param_ACT", 2, -129.193, -41.212, 130.73, -0.246, -0.374,
     -0.329, -2.955, 0},
    {0, 1008, "AGD66_7_Param_TAS", 2, -120.271, -64.543, 161.632, -0.2175,
     0.0672, 0.1291, 2.4985, 0},
    {0, 1009, "AGD66_7_Param_VIC_NSW", 2, -119.353, -48.301, 139.484, -0.415,
     -0.26, -0.437, -0.613, 0},
    {6272, 1010, "NZGD_7_Param_49", 4, 59.47, -5.04, 187.44, -0.47, 0.1, -1.024,
     -4.5993, 0},
    {0, 1011, "Rikets_Tri_7_Param_1990", 10, 419.3836, 99.3335, 591.3451,
     -0.850389, -1.817277, 7.862238, -0.99496, 0},
    {0, 1012, "Russia_PZ90", 52, -1.08, -0.27, -0.9, 0, 0, -0.16, -0.12, 0},
    {0, 1013, "Russia_SK42", 52, 23.92, -141.27, -80.9, 0, -0.35, -0.82, -0.12,
     0},
    {0, 1014, "Russia_SK95", 52, 24.82, -131.21, -82.66, 0, 0, -0.16, -0.12, 0},
    {6301, 1015, "Tokyo", 10, -146.414, 507.337, 680.507, 0, 0, 0, 0, 0},
    {6123, 1016, "Kartastokoordinaattijarjestelma_1966", 4, -96.062, -82.428,
     -121.754, -4.801, -0.345, 1.376, 1.496, 0},
    {6610, 1017, "Xian 1980", 53, 24, -123, -94, -0.02, -0.25, 0.13, 1.1, 0},
    {0, 1018, "Lithuanian Pulkovo 1942", 4, -40.59527, -18.54979, -69.33956,
     -2.508, -1.8319, 2.6114, -4.2991, 0},
    {6313, 1019, "Belgian 1972 7 Parameter", 4, -99.059, 53.322, -112.486,
     -0.419, 0.83, -1.885, 0.999999, 0},
    {6818, 1020, "S-JTSK with Ferro prime meridian", 10, 589, 76, 480, 0, 0, 0,
     0, -17.666666666667},
    {1031, 1021, "Serbia datum MGI 1901", 10, 574.027, 170.175, 401.545,
     4.88786, -0.66524, -13.24673, 6.88933, 0},
    {0, 1022, "North Sahara 7-parameter", 6, -38.7086, -128.8054, 118.8837,
     0.83822, 7.38459, -1.57989, 3.9904, 0},
    {0, 1023, "Hungarian Projection System (EOV) - updated", 21, 52.684,
     -71.194, -13.975, 0.312, 0.1063, 0.3729, 1.0191, 0},
    {1052, 1024, "S-JTSK (Krovak) Coordinate system - updated", 10, 570.6934,
     85.6936, 462.8393, -4.99825, -1.58663, -5.26114, 3.5430155, 0},
    {0, 1025, "JTSK03 (Slovak Republic)", 10, 485.014055, 169.473618,
     483.842943, -7.78625453, -4.39770887, -4.10248899, 0, 0},
    {1168, 1028, "Geocentric Datum of Australia 2020", 0, -0.06155, 0.01087,
     0.04019, 0.0394924, 0.0327221, 0.0328979, 0.009994, 0},
    // For some weird reason, MapInfo uses nEllipsoid=8 "Clarke 1866 (modified
    // for Michigan)" cf
    // https://docs.precisely.com/docs/sftw/mapinfo-pro/v2021.1/en-us/pdf/mapinfo-pro-v2021.1-release-notes.pdf
    // page 8 whereas EPSG uses the regular Clarke 1866 ellipsoid.
    {6683, 1031, "Philippine Reference System 1992", 8, -127.62, -67.24, -47.04,
     -3.068, 4.903, 1.578, -1.06, 0},
    {0, 9999, "Bosnia-Herzegovina", 10, 472.8677, 187.8769, 544.7084,
     -5.76198422, -5.3222842, 12.80666941, 1.54517287, 0},
    {6181, 9999, "Luxembourg 1930 / Gauss", 4, -192.986, 13.673, -39.309,
     0.4099, 2.9332, -2.6881, 0.43, 0},
    {1168, 9999, "Geocentric Datum of Australia 2020", 0, -0.06155, 0.01087,
     0.04019, 0.0394924, 0.0327221, 0.0328979, 0.009994, 0},
    {-1, -1, nullptr, 0, 0, 0, 0, 0, 0, 0, 0, 0}};

/* -------------------------------------------------------------------- */
/*      This table was hand entered from Appendix I of the mapinfo 6    */
/*      manuals.                                                        */
/* -------------------------------------------------------------------- */

const MapInfoSpheroidInfo asSpheroidInfoList[] = {
    {9, "Airy 1930", 6377563.396, 299.3249646},
    {13, "Airy 1930 (modified for Ireland 1965", 6377340.189, 299.3249646},
    {51, "ATS77 (Average Terrestrial System 1977)", 6378135, 298.257},
    {2, "Australian", 6378160.0, 298.25},
    {10, "Bessel 1841", 6377397.155, 299.1528128},
    {35, "Bessel 1841 (modified for NGO 1948)", 6377492.0176, 299.15281},
    {14, "Bessel 1841 (modified for Schwarzeck)", 6377483.865, 299.1528128},
    {36, "Clarke 1858", 6378293.639, 294.26068},
    {7, "Clarke 1866", 6378206.4, 294.9786982},
    {8, "Clarke 1866 (modified for Michigan)", 6378450.047484481, 294.9786982},
    {6, "Clarke 1880", 6378249.145, 293.465},
    {15, "Clarke 1880 (modified for Arc 1950)", 6378249.145326, 293.4663076},
    {30, "Clarke 1880 (modified for IGN)", 6378249.2, 293.4660213},
    {37, "Clarke 1880 (modified for Jamaica)", 6378249.136, 293.46631},
    {16, "Clarke 1880 (modified for Merchich)", 6378249.2, 293.46598},
    {38, "Clarke 1880 (modified for Palestine)", 6378300.79, 293.46623},
    {39, "Everest (Brunei and East Malaysia)", 6377298.556, 300.8017},
    {11, "Everest (India 1830)", 6377276.345, 300.8017},
    {40, "Everest (India 1956)", 6377301.243, 300.80174},
    {50, "Everest (Pakistan)", 6377309.613, 300.8017},
    {17, "Everest (W. Malaysia and Singapore 1948)", 6377304.063, 300.8017},
    {48, "Everest (West Malaysia 1969)", 6377304.063, 300.8017},
    {18, "Fischer 1960", 6378166.0, 298.3},
    {19, "Fischer 1960 (modified for South Asia)", 6378155.0, 298.3},
    {20, "Fischer 1968", 6378150.0, 298.3},
    {21, "GRS 67", 6378160.0, 298.247167427},
    {0, "GRS 80", 6378137.0, 298.257222101},
    {56, "GSK2011", 6378136.5, 298.2564151},
    {5, "Hayford", 6378388.0, 297.0},
    {22, "Helmert 1906", 6378200.0, 298.3},
    {23, "Hough", 6378270.0, 297.0},
    {31, "IAG 75", 6378140.0, 298.257222},
    {41, "Indonesian", 6378160.0, 298.247},
    {4, "International 1924", 6378388.0, 297.0},
    {49, "Irish (WOFO)", 6377542.178, 299.325},
    {3, "Krassovsky", 6378245.0, 298.3},
    {32, "MERIT 83", 6378137.0, 298.257},
    {33, "New International 1967", 6378157.5, 298.25},
    {42, "NWL 9D", 6378145.0, 298.25},
    {43, "NWL 10D", 6378135.0, 298.26},
    {44, "OSU86F", 6378136.2, 298.25722},
    {45, "OSU91A", 6378136.3, 298.25722},
    {46, "Plessis 1817", 6376523.0, 308.64},
    {52, "PZ90", 6378136.0, 298.257839303},
    {57, "PZ90.11", 6378136.0, 298.25784},
    {24, "South American", 6378160.0, 298.25},
    {12, "Sphere", 6370997.0, 0.0},
    {47, "Struve 1860", 6378297.0, 294.73},
    {34, "Walbeck", 6376896.0, 302.78},
    {25, "War Office", 6378300.583, 296.0},
    {26, "WGS 60", 6378165.0, 298.3},
    {27, "WGS 66", 6378145.0, 298.25},
    {1, "WGS 72", 6378135.0, 298.26},
    {28, "WGS 84", 6378137.0, 298.257223563},
    {29, "WGS 84 (MAPINFO Datum 0)", 6378137.01, 298.257223563},
    {54, "WGS 84 (MAPINFO Datum 157)", 6378137.01, 298.257223563},
    {-1, nullptr, 0.0, 0.0}};

/* For LCC, standard parallel 1 and 2 can be switched indifferently */
/* So the MapInfo order and the EPSG order are not generally identical */
/* which may cause recognition problems when reading in MapInfo */
/* This table contains the parameters in the order expected by MapInfo */
typedef struct
{
    int nEPSGCode;
    int bReverseStdP;
    int nMapInfoDatumID;
    double dfCenterLong;
    double dfCenterLat;
    double dfStdP1;
    double dfStdP2;
} MapInfoLCCSRS;

static const MapInfoLCCSRS asMapInfoLCCSRSList[] = {
    {2154, 1, 33, 3, 46.5, 44, 49},
    {2154, 1, 33, 3, 46.5, 44, 49.00000000001},
    {2154, 1, 33, 3, 46.5, 44, 49.00000000002},
    {2225, 1, 74, -122, 39.3333333333, 40, 41.6666666667},
    {2226, 1, 74, -122, 37.6666666667, 38.3333333333, 39.8333333333},
    {2227, 1, 74, -120.5, 36.5, 37.0666666667, 38.4333333333},
    {2228, 1, 74, -119, 35.3333333333, 36, 37.25},
    {2229, 1, 74, -118, 33.5, 34.0333333333, 35.4666666667},
    {2230, 1, 74, -116.25, 32.1666666667, 32.7833333333, 33.8833333333},
    {2231, 1, 74, -105.5, 39.3333333333, 39.7166666667, 40.7833333333},
    {2232, 1, 74, -105.5, 37.8333333333, 38.45, 39.75},
    {2233, 1, 74, -105.5, 36.6666666667, 37.2333333333, 38.4333333333},
    {2234, 1, 74, -72.75, 40.8333333333, 41.2, 41.8666666667},
    {2238, 1, 74, -84.5, 29, 29.5833333333, 30.75},
    {2246, 0, 74, -84.25, 37.5, 37.9666666667, 38.9666666667},
    {2247, 1, 74, -85.75, 36.3333333333, 36.7333333333, 37.9333333333},
    {2248, 1, 74, -77, 37.6666666667, 38.3, 39.45},
    {2249, 1, 74, -71.5, 41, 41.7166666667, 42.6833333333},
    {2250, 1, 74, -70.5, 41, 41.2833333333, 41.4833333333},
    {2251, 1, 74, -87, 44.7833333333, 45.4833333333, 47.0833333333},
    {2252, 1, 74, -84.3666666667, 43.3166666667, 44.1833333333, 45.7},
    {2253, 1, 74, -84.3666666667, 41.5, 42.1, 43.6666666667},
    {2256, 1, 74, -109.5, 44.25, 45, 49},
    {2263, 1, 74, -74, 40.1666666667, 40.6666666667, 41.0333333333},
    {2264, 1, 74, -79, 33.75, 34.3333333333, 36.1666666667},
    {2265, 1, 74, -100.5, 47, 47.4333333333, 48.7333333333},
    {2266, 1, 74, -100.5, 45.6666666667, 46.1833333333, 47.4833333333},
    {2267, 1, 74, -98, 35, 35.5666666667, 36.7666666667},
    {2268, 1, 74, -98, 33.3333333333, 33.9333333333, 35.2333333333},
    {2269, 1, 74, -120.5, 43.6666666667, 44.3333333333, 46},
    {2270, 1, 74, -120.5, 41.6666666667, 42.3333333333, 44},
    {2271, 1, 74, -77.75, 40.1666666667, 40.8833333333, 41.95},
    {2272, 1, 74, -77.75, 39.3333333333, 39.9333333333, 40.9666666667},
    {2273, 1, 74, -81, 31.8333333333, 32.5, 34.8333333333},
    {2274, 1, 74, -86, 34.3333333333, 35.25, 36.4166666667},
    {2275, 1, 74, -101.5, 34, 34.65, 36.1833333333},
    {2276, 1, 74, -98.5, 31.6666666667, 32.1333333333, 33.9666666667},
    {2277, 1, 74, -100.3333333333, 29.6666666667, 30.1166666667, 31.8833333333},
    {2278, 1, 74, -99, 27.8333333333, 28.3833333333, 30.2833333333},
    {2279, 1, 74, -98.5, 25.6666666667, 26.1666666667, 27.8333333333},
    {2280, 1, 74, -111.5, 40.3333333333, 40.7166666667, 41.7833333333},
    {2281, 1, 74, -111.5, 38.3333333333, 39.0166666667, 40.65},
    {2282, 1, 74, -111.5, 36.6666666667, 37.2166666667, 38.35},
    {2283, 1, 74, -78.5, 37.6666666667, 38.0333333333, 39.2},
    {2284, 1, 74, -78.5, 36.3333333333, 36.7666666667, 37.9666666667},
    {2285, 1, 74, -120.8333333333, 47, 47.5, 48.7333333333},
    {2286, 1, 74, -120.5, 45.3333333333, 45.8333333333, 47.3333333333},
    {2287, 1, 74, -90, 45.1666666667, 45.5666666667, 46.7666666667},
    {2288, 1, 74, -90, 43.8333333333, 44.25, 45.5},
    {2289, 1, 74, -90, 42, 42.7333333333, 44.0666666667},
    {26740, 1, 63, -176, 51, 51.8333333333, 53.8333333333},
    {26741, 1, 62, -122, 39.3333333333, 40, 41.6666666667},
    {26742, 1, 62, -122, 37.6666666667, 38.3333333333, 39.8333333333},
    {26743, 1, 62, -120.5, 36.5, 37.0666666667, 38.4333333333},
    {26744, 1, 62, -119, 35.3333333333, 36, 37.25},
    {26745, 1, 62, -118, 33.5, 34.0333333333, 35.4666666667},
    {26746, 1, 62, -116.25, 32.1666666667, 32.7833333333, 33.8833333333},
    {26747, 1, 62, -118.3333333333, 34.1333333333, 33.8666666667,
     34.4166666667},
    {26751, 1, 62, -92, 34.3333333333, 34.9333333333, 36.2333333333},
    {26752, 1, 62, -92, 32.6666666667, 33.3, 34.7666666667},
    {26753, 0, 62, -105.5, 39.3333333333, 39.7166666667, 40.7833333333},
    {26754, 1, 62, -105.5, 37.8333333333, 38.45, 39.75},
    {26755, 1, 62, -105.5, 36.6666666667, 37.2333333333, 38.4333333333},
    {26756, 1, 62, -72.75, 40.8333333333, 41.2, 41.8666666667},
    {26760, 1, 62, -84.5, 29, 29.5833333333, 30.75},
    {26775, 1, 62, -93.5, 41.5, 42.0666666667, 43.2666666667},
    {26776, 1, 62, -93.5, 40, 40.6166666667, 41.7833333333},
    {26777, 1, 62, -98, 38.3333333333, 38.7166666667, 39.7833333333},
    {26778, 0, 62, -98.5, 36.6666666667, 38.5666666667, 37.2666666667},
    {26779, 0, 62, -84.25, 37.5, 37.9666666667, 38.9666666667},
    {26780, 0, 62, -85.75, 36.3333333333, 36.7333333333, 37.9333333333},
    {26781, 0, 62, -92.5, 30.6666666667, 31.1666666667, 32.6666666667},
    {26785, 0, 62, -77, 37.8333333333, 38.3, 39.45},
    {26786, 0, 62, -71.5, 41, 41.7166666667, 42.6833333333},
    {26788, 0, 73, -87, 44.7833333333, 45.4833333333, 47.0833333333},
    {26789, 0, 73, -84.3333333333, 43.3166666667, 44.1833333333, 45.7},
    {26790, 0, 73, -84.3333333333, 41.5, 42.1, 43.6666666667},
    {26791, 0, 62, -93.1, 46.5, 47.0333333333, 48.6333333333},
    {26792, 0, 62, -94.25, 45, 45.6166666667, 47.05},
    {26793, 0, 62, -94, 43, 43.7833333333, 45.2166666667},
    {26940, 1, 74, -176, 51, 51.8333333333, 53.8333333333},
    {26941, 1, 74, -122, 39.3333333333, 40, 41.6666666667},
    {26942, 1, 74, -122, 37.6666666667, 38.3333333333, 39.8333333333},
    {26943, 1, 74, -120.5, 36.5, 37.0666666667, 38.4333333333},
    {26944, 1, 74, -119, 35.3333333333, 36, 37.25},
    {26945, 1, 74, -118, 33.5, 34.0333333333, 35.4666666667},
    {26946, 1, 74, -116.25, 32.1666666667, 32.7833333333, 33.8833333333},
    {26951, 1, 74, -92, 34.3333333333, 34.9333333333, 36.2333333333},
    {26952, 1, 74, -92, 32.6666666667, 33.3, 34.7666666667},
    {26953, 1, 74, -105.5, 39.3333333333, 39.7166666667, 40.7833333333},
    {26954, 1, 74, -105.5, 37.8333333333, 38.45, 39.75},
    {26955, 1, 74, -105.5, 36.6666666667, 37.2333333333, 38.4333333333},
    {26956, 1, 74, -72.75, 40.8333333333, 41.2, 41.8666666667},
    {26960, 1, 74, -84.5, 29, 29.5833333333, 30.75},
    {26975, 1, 74, -93.5, 41.5, 42.0666666667, 43.2666666667},
    {26976, 1, 74, -93.5, 40, 40.6166666667, 41.7833333333},
    {26977, 1, 74, -98, 38.3333333333, 38.7166666667, 39.7833333333},
    {26978, 0, 74, -98.5, 36.6666666667, 38.5666666667, 37.2666666667},
    {26980, 1, 74, -85.75, 36.3333333333, 36.7333333333, 37.9333333333},
    {26981, 1, 74, -92.5, 30.5, 31.1666666667, 32.6666666667},
    {26982, 1, 74, -91.3333333333, 28.5, 29.3, 30.7},
    {26985, 1, 74, -77, 37.6666666667, 38.3, 39.45},
    {26986, 1, 74, -71.5, 41, 41.7166666667, 42.6833333333},
    {26987, 1, 74, -70.5, 41, 41.2833333333, 41.4833333333},
    {26988, 1, 74, -87, 44.7833333333, 45.4833333333, 47.0833333333},
    {26989, 1, 74, -84.3666666667, 43.3166666667, 44.1833333333, 45.7},
    {26990, 1, 74, -84.3666666667, 41.5, 42.1, 43.6666666667},
    {26991, 1, 74, -93.1, 46.5, 47.0333333333, 48.6333333333},
    {26992, 1, 74, -94.25, 45, 45.6166666667, 47.05},
    {26993, 1, 74, -94, 43, 43.7833333333, 45.2166666667},
    {3111, 0, 116, 145, -37, -36, -38},
    {31370, 1, 1019, 4.3674866667, 90, 49.8333339000, 51.1666672333},
    {32001, 1, 62, -109.5, 47, 47.85, 48.7166666667},
    {32002, 1, 62, -109.5, 45.8333333333, 46.45, 47.8833333333},
    {32003, 1, 62, -109.5, 44, 44.8666666667, 46.4},
    {32005, 0, 62, -100, 41.3333333333, 41.85, 42.8166666667},
    {32006, 0, 62, -99.5, 39.6666666667, 40.2833333333, 41.7166666667},
    {32018, 1, 62, -74, 40.5, 40.6666666667, 41.0333333333},
    {32019, 0, 62, -79, 33.75, 34.3333333333, 36.1666666667},
    {32020, 0, 62, -100.5, 47, 47.4333333333, 48.7333333333},
    {32021, 0, 62, -100.5, 45.6666666667, 46.1833333333, 47.4833333333},
    {32022, 0, 62, -82.5, 39.6666666667, 40.4333333333, 41.7},
    {32023, 0, 62, -82.5, 38, 38.7333333333, 40.0333333333},
    {32024, 0, 62, -98, 35, 35.5666666667, 36.7666666667},
    {32025, 0, 62, -98, 33.3333333333, 33.9333333333, 35.2333333333},
    {32026, 0, 62, -120.5, 43.6666666667, 44.3333333333, 46},
    {32027, 0, 62, -120.5, 41.6666666667, 42.3333333333, 44},
    {32028, 0, 62, -77.75, 40.1666666667, 40.8833333333, 41.95},
    {32031, 0, 62, -81, 33, 33.7666666667, 34.9666666667},
    {32033, 0, 62, -81, 31.8333333333, 32.3333333333, 33.6666666667},
    {32034, 0, 62, -100, 43.8333333333, 44.4166666667, 45.6833333333},
    {32035, 0, 62, -100.3333333333, 42.3333333333, 42.8333333333, 44.4},
    {32036, 0, 62, -86, 34.6666666667, 35.25, 36.4166666667},
    {32037, 0, 62, -101.5, 34, 34.65, 36.1833333333},
    {32038, 0, 62, -97.5, 31.6666666667, 32.1333333333, 33.9666666667},
    {32039, 0, 62, -100.3333333333, 29.6666666667, 30.1166666667,
     31.8833333333},
    {32040, 0, 62, -99, 27.8333333333, 28.3833333333, 30.2833333333},
    {32041, 0, 62, -98.5, 25.6666666667, 26.1666666667, 27.8333333333},
    {32042, 0, 62, -111.5, 40.3333333333, 40.7166666667, 41.7833333333},
    {32043, 0, 62, -111.5, 38.3333333333, 39.0166666667, 40.65},
    {32044, 0, 62, -111.5, 36.6666666667, 37.2166666667, 38.35},
    {32046, 0, 62, -78.5, 37.6666666667, 38.0333333333, 39.2},
    {32047, 0, 62, -78.5, 36.3333333333, 36.7666666667, 37.9666666667},
    {32048, 0, 62, -120.8333333333, 47, 47.5, 48.7333333333},
    {32049, 0, 62, -120.5, 45.3333333333, 45.8333333333, 47.3333333333},
    {32050, 0, 62, -79.5, 38.5, 39, 40.25},
    {32051, 0, 62, -81, 37, 37.4833333333, 38.8833333333},
    {32052, 0, 62, -90, 45.1666666667, 45.5666666667, 46.7666666667},
    {32053, 0, 62, -90, 43.8333333333, 44.25, 45.5},
    {32054, 0, 62, -90, 42, 42.7333333333, 44.0666666667},
    {32059, 0, 62, -66.4333333333, 18.4333333333, 18.0333333333, 18.4333333333},
    {32060, 0, 62, -66.4333333333, 18.4333333333, 18.0333333333, 18.4333333333},
    {32100, 1, 74, -109.5, 44.25, 45, 49},
    {32104, 1, 74, -100, 39.8333333333, 40, 43},
    {32118, 1, 74, -74, 40.1666666667, 40.6666666667, 41.0333333333},
    {32119, 1, 74, -79, 33.75, 34.3333333333, 36.1666666667},
    {32120, 1, 74, -100.5, 47, 47.4333333333, 48.7333333333},
    {32121, 1, 74, -100.5, 45.6666666667, 46.1833333333, 47.4833333333},
    {32122, 1, 74, -82.5, 39.6666666667, 40.4333333333, 41.7},
    {32123, 1, 74, -82.5, 38, 38.7333333333, 40.0333333333},
    {32124, 1, 74, -98, 35, 35.5666666667, 36.7666666667},
    {32125, 1, 74, -98, 33.3333333333, 33.9333333333, 35.2333333333},
    {32126, 1, 74, -120.5, 43.6666666667, 44.3333333333, 46},
    {32127, 1, 74, -120.5, 41.6666666667, 42.3333333333, 44},
    {32128, 1, 74, -77.75, 40.1666666667, 40.8833333333, 41.95},
    {32129, 1, 74, -77.75, 39.3333333333, 39.9333333333, 40.9666666667},
    {32133, 1, 74, -81, 31.8333333333, 32.5, 34.8333333333},
    {32134, 1, 74, -100, 43.8333333333, 44.4166666667, 45.6833333333},
    {32135, 1, 74, -100.3333333333, 42.3333333333, 42.8333333333, 44.4},
    {32136, 1, 74, -86, 34.3333333333, 35.25, 36.4166666667},
    {32137, 1, 74, -101.5, 34, 34.65, 36.1833333333},
    {32138, 1, 74, -98.5, 31.6666666667, 32.1333333333, 33.9666666667},
    {32139, 1, 74, -100.3333333333, 29.6666666667, 30.1166666667,
     31.8833333333},
    {32140, 1, 74, -99, 27.8333333333, 28.3833333333, 30.2833333333},
    {32141, 1, 74, -98.5, 25.6666666667, 26.1666666667, 27.8333333333},
    {32142, 1, 74, -111.5, 40.3333333333, 40.7166666667, 41.7833333333},
    {32143, 1, 74, -111.5, 38.3333333333, 39.0166666667, 40.65},
    {32144, 1, 74, -111.5, 36.6666666667, 37.2166666667, 38.35},
    {32146, 1, 74, -78.5, 37.6666666667, 38.0333333333, 39.2},
    {32147, 1, 74, -78.5, 36.3333333333, 36.7666666667, 37.9666666667},
    {32148, 1, 74, -120.8333333333, 47, 47.5, 48.7333333333},
    {32149, 1, 74, -120.5, 45.3333333333, 45.8333333333, 47.3333333333},
    {32150, 1, 74, -79.5, 38.5, 39, 40.25},
    {32151, 1, 74, -81, 37, 37.4833333333, 38.8833333333},
    {32152, 1, 74, -90, 45.1666666667, 45.5666666667, 46.7666666667},
    {32153, 1, 74, -90, 43.8333333333, 44.25, 45.5},
    {32154, 1, 74, -90, 42, 42.7333333333, 44.0666666667},
    {32161, 1, 74, -66.4333333333, 17.8333333333, 18.0333333333, 18.4333333333},
    {3300, 1, 115, 24, 57.51755394, 58, 59.33333333},
    {3301, 1, 115, 24, 57.51755393056, 58, 59.33333333},
    {3797, 0, 66, -70, 44, 50, 46},
    {3798, 0, 74, -70, 44, 50, 46},
    {3799, 0, 74, -70, 44, 50, 46},
    {3942, 0, 33, 3, 42, 41.25, 42.75},
    {3943, 0, 33, 3, 43, 42.25, 43.75},
    {3944, 0, 33, 3, 44, 43.25, 44.75},
    {3945, 0, 33, 3, 45, 44.25, 45.75},
    {3946, 0, 33, 3, 46, 45.25, 46.75},
    {3947, 0, 33, 3, 47, 46.25, 47.75},
    {3948, 0, 33, 3, 48, 47.25, 48.75},
    {3949, 0, 33, 3, 49, 48.25, 49.75},
    {3950, 0, 33, 3, 50, 49.25, 50.75},
    {42101, 0, 104, -95, 0, 49, 77},
    {42103, 0, 104, -100, 0, 33, 45},
    {42304, 0, 74, -95, 49, 49, 77},
    {0, 0, 0, 110, 10, 25, 40},
    {0, 0, 0, 132.5, -10, -21.5, -33.5},
    {0, 0, 0, 25, 35, 40, 65},
    {0, 0, 0, 47.5, 25, 15, 35},
    {0, 0, 0, 95, 40, 20, 60},
    {0, 0, 1002, 0, 42.165, 41.5603877778, 42.76766333},
    {0, 0, 1002, 0, 42.165, 41.5603877778, 42.767663333},
    {0, 0, 1002, 0, 42.165, 41.560387778, 42.76766333},
    {0, 0, 1002, 0, 42.165, 41.560387778, 42.767663333},
    {0, 0, 1002, 0, 42.165, 41.56038778, 42.76766333},
    {0, 0, 1002, 0, 42.165, 41.560387840948, 42.76766346965},
    {0, 0, 1002, 0, 44.1, 43.199291275544, 44.996093814511},
    {0, 0, 1002, 0, 44.1, 43.1992913889, 44.99609389},
    {0, 0, 1002, 0, 44.1, 43.199291389, 44.99609389},
    {0, 0, 1002, 0, 44.1, 43.19929139, 44.99609389},
    {0, 0, 1002, 0, 46.8, 45.8989188889, 47.69601444},
    {0, 0, 1002, 0, 46.8, 45.898918889, 47.69601444},
    {0, 0, 1002, 0, 46.8, 45.89891889, 47.69601444},
    {0, 0, 1002, 0, 46.8, 45.898918964419, 47.696014502038},
    {0, 0, 1002, 0, 49.5, 48.5985227778, 50.39591167},
    {0, 0, 1002, 0, 49.5, 48.598522778, 50.39591167},
    {0, 0, 1002, 0, 49.5, 48.59852278, 50.39591167},
    {0, 0, 1002, 0, 49.5, 48.598522847174, 50.395911631678},
    {0, 0, 1005, 23, -23, -18, -32},
    {0, 0, 1022, 2.7, 36, 37.575, 34.425},
    {0, 0, 104, 13.33333333, 47.5, 46, 49},
    {0, 0, 104, 13.33333333, 48, 46, 49},
    {0, 0, 104, -19, 65, 64.25, 65.75},
    {0, 0, 104, 36.0, 25.0, 37.5, 40.5},
    {0, 0, 104, 36, 25, 37.5, 40.5},
    {0, 0, 104, 70, -50, -68.5, -74.5},
    {0, 0, 110, 4.367975, 90, 49.8333333333, 51.1666666667},
    {0, 0, 115, 10, 52, 35, 45},
    {0, 0, 116, 135, -24, -18, -36},
    {0, 0, 116, 135, -32, -28, -36},
    {0, 0, 12, 135, -24, -18, -36},
    {0, 0, 12, 145, -37, -36, -38},
    {0, 0, 13, 135, -24, -18, -36},
    {0, 0, 19, 23, -23, -18, -32},
    {0, 0, 28, 17, 29.77930555, 42, 56},
    {0, 0, 28, 19, 29.77930555, 42, 56},
    {0, 0, 28, 36.0, 25.0, 37.5, 40.5},
    {0, 0, 33, 13.5, 0, 52.6666666667, 55.3333333333},
    {0, 0, 33, 15, 0, 56.5, 60.5},
    {0, 0, 33, 15, 0, 58, 66},
    {0, 0, 33, 15, 0, 63.5, 67.5},
    {0, 0, 33, 15.5, 0, 56.6666666667, 59.3333333333},
    {0, 0, 33, 15.5, 0, 60.6666666667, 63.3333333333},
    {0, 0, 33, 16.5, 0, 60.6666666667, 63.3333333333},
    {0, 0, 33, 18.5, 0, 64.6666666667, 67.3333333333},
    {0, 0, 33, 19, 0, 64.6666666667, 67.3333333333},
    {0, 0, 55, -5.4, 22.5, 20.9075742561, 24.0921050540},
    {0, 0, 55, -5.4, 26.1, 24.5075340813, 27.6921073632},
    {0, 0, 55, -5.4, 29.7, 28.1063294800, 31.2932791054},
    {0, 0, 55, -5.4, 33.3, 31.72786641202, 34.8717272112},
    {0, 0, 62, -70.5, 41, 41.2833333333, 41.4833333333},
    {0, 0, 62, -77.75, 39.3333333333, 39.9333333333, 40.9666666667},
    {0, 0, 62, -91.3333333333, 25.6666666667, 26.1666666667, 27.8333333333},
    {0, 0, 62, -91.3333333333, 28.6666666667, 29.3, 30.67},
    {0, 0, 62, -96, 23, 20, 60},
    {0, 0, 62, -96, 23, 33, 45},
    {0, 0, 62, -96, 39, 33, 45},
    {0, 0, 66, -68.5, 44, 46, 60},
    {0, 0, 74, -100.3333333333, 42.3333333333, 42.8333333333, 44.4},
    {0, 0, 74, -100, 39.8333333333, 40, 43},
    {0, 0, 74, -100, 43.8333333333, 44.4166666667, 45.6833333333},
    {0, 0, 74, -109.5, 44.25, 45, 49},
    {0, 0, 74, -111.5, 36.6666666667, 37.2166666667, 38.35},
    {0, 0, 74, -111.5, 38.3333333333, 39.0166666667, 40.65},
    {0, 0, 74, -111.5, 40.3333333333, 40.7166666667, 41.7833333333},
    {0, 0, 74, -120.5, 41.6666666667, 42.3333333333, 44},
    {0, 0, 74, -120.5, 43.6666666667, 44.3333333333, 46},
    {0, 0, 74, -176, 51, 51.8333333333, 53.8333333333},
    {0, 0, 74, -66.4333333333, 17.8333333333, 18.0333333333, 18.4333333333},
    {0, 0, 74, -68.5, 44, 46, 60},
    {0, 0, 74, -79.5, 38.5, 39, 40.25},
    {0, 0, 74, -81, 31.8333333333, 32.5, 34.8333333333},
    {0, 0, 74, -81, 37, 37.4833333333, 38.8833333333},
    {0, 0, 74, -82.5, 38, 38.7333333333, 40.0333333333},
    {0, 0, 74, -82.5, 39.6666666667, 40.4333333333, 41.7},
    {0, 0, 74, -84.25, 37.5, 37.9666666667, 38.9666666667},
    {0, 0, 74, -84.3666666667, 41.5, 42.1, 43.6666666667},
    {0, 0, 74, -84.3666666667, 43.3166666667, 44.1833333333, 45.7},
    {0, 0, 74, -87, 44.7833333333, 45.4833333333, 47.0833333333},
    {0, 0, 74, -91.3333333333, 25.5, 26.1666666667, 27.8333333333},
    {0, 0, 74, -91.3333333333, 28.5, 29.3, 30.7},
    {0, 0, 74, -92, 32.6666666667, 33.3, 34.7666666667},
    {0, 0, 74, -92, 34.3333333333, 34.9333333333, 36.2333333333},
    {0, 0, 74, -92.5, 30.5, 31.1666666667, 32.6666666667},
    {0, 0, 74, -93.1, 46.5, 47.0333333333, 48.6333333333},
    {0, 0, 74, -93.5, 40, 40.6166666667, 41.7833333333},
    {0, 0, 74, -93.5, 41.5, 42.0666666667, 43.2666666667},
    {0, 0, 74, -94.25, 45, 45.6166666667, 47.05},
    {0, 0, 74, -94, 43, 43.7833333333, 45.2166666667},
    {0, 0, 74, -98, 38.3333333333, 38.7166666667, 39.7833333333},
    {0, 0, 74, -98.5, 36.6666666667, 38.5666666667, 37.2666666667},
};

/**********************************************************************
 *                   TABFile::GetSpatialRefFromTABProj()
 **********************************************************************/

static bool TAB_EQUAL(double a, double b)
{
    return std::fabs(a - b) < 1.0e-10;
}

OGRSpatialReference *
TABFileGetSpatialRefFromTABProj(const TABProjInfo &sTABProj)
{
    /*-----------------------------------------------------------------
     * Get the units name, and translation factor.
     *----------------------------------------------------------------*/
    const char *pszUnitsName = nullptr;
    const char *pszUnitsConv = nullptr;
    /* double      dfConv = 1.0; */

    switch (sTABProj.nUnitsId)
    {
        case 0:
            pszUnitsName = "Mile";
            pszUnitsConv = "1609.344";
            break;

        case 1:
            pszUnitsName = "Kilometer";
            pszUnitsConv = "1000.0";
            break;

        case 2:
            pszUnitsName = "IINCH";
            pszUnitsConv = "0.0254";
            break;

        case 3:
            pszUnitsName = SRS_UL_FOOT;
            pszUnitsConv = SRS_UL_FOOT_CONV;
            break;

        case 4:
            pszUnitsName = "IYARD";
            pszUnitsConv = "0.9144";
            break;

        case 5:
            pszUnitsName = "Millimeter";
            pszUnitsConv = "0.001";
            break;

        case 6:
            pszUnitsName = "Centimeter";
            pszUnitsConv = "0.01";
            break;

        case 7:
            pszUnitsName = SRS_UL_METER;
            pszUnitsConv = "1.0";
            break;

        case 8:
            pszUnitsName = SRS_UL_US_FOOT;
            pszUnitsConv = SRS_UL_US_FOOT_CONV;
            break;

        case 9:
            pszUnitsName = SRS_UL_NAUTICAL_MILE;
            pszUnitsConv = SRS_UL_NAUTICAL_MILE_CONV;
            break;

        case 30:
            pszUnitsName = SRS_UL_LINK;
            pszUnitsConv = SRS_UL_LINK_CONV;
            break;

        case 31:
            pszUnitsName = SRS_UL_CHAIN;
            pszUnitsConv = SRS_UL_CHAIN_CONV;
            break;

        case 32:
            pszUnitsName = SRS_UL_ROD;
            pszUnitsConv = SRS_UL_ROD_CONV;
            break;

        default:
            pszUnitsName = SRS_UL_METER;
            pszUnitsConv = "1.0";
            break;
    }

    /* dfConv = CPLAtof(pszUnitsConv); */

    /*-----------------------------------------------------------------
     * Transform them into an OGRSpatialReference.
     *----------------------------------------------------------------*/
    OGRSpatialReference *poSpatialRef = new OGRSpatialReference;
    poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

    /*-----------------------------------------------------------------
     * Handle the PROJCS style projections, but add the datum later.
     *----------------------------------------------------------------*/
    switch (sTABProj.nProjId)
    {
        case 0:
            poSpatialRef->SetLocalCS("Nonearth");
            poSpatialRef->SetLinearUnits(pszUnitsName, CPLAtof(pszUnitsConv));
            break;

            /*--------------------------------------------------------------
             * lat/long .. just add the GEOGCS later.
             *-------------------------------------------------------------*/
        case 1:
            break;

            /*--------------------------------------------------------------
             * Cylindrical Equal Area
             *-------------------------------------------------------------*/
        case 2:
            poSpatialRef->SetCEA(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3]);
            break;

            /*--------------------------------------------------------------
             * Lambert Conic Conformal
             *-------------------------------------------------------------*/
        case 3:
            poSpatialRef->SetLCC(
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[4], sTABProj.adProjParams[5]);
            break;

            /*--------------------------------------------------------------
             * Lambert Azimuthal Equal Area
             *-------------------------------------------------------------*/
        case 4:
        case 29:
            poSpatialRef->SetLAEA(sTABProj.adProjParams[1],
                                  sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Azimuthal Equidistant (Polar aspect only)
             *-------------------------------------------------------------*/
        case 5:
        case 28:
            poSpatialRef->SetAE(sTABProj.adProjParams[1],
                                sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Equidistant Conic
             *-------------------------------------------------------------*/
        case 6:
            poSpatialRef->SetEC(
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[4], sTABProj.adProjParams[5]);
            break;

            /*--------------------------------------------------------------
             * Hotine Oblique Mercator
             *-------------------------------------------------------------*/
        case 7:
            poSpatialRef->SetHOM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], 90.0, sTABProj.adProjParams[3],
                sTABProj.adProjParams[4], sTABProj.adProjParams[5]);
            break;

            /*--------------------------------------------------------------
             * Hotine Oblique Mercator with Angle from Rectified to Skew Grid
             *-------------------------------------------------------------*/
        case 35:
            poSpatialRef->SetHOM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4], sTABProj.adProjParams[5],
                sTABProj.adProjParams[6]);
            break;

            /*--------------------------------------------------------------
             * Transverse Mercator
             *-------------------------------------------------------------*/
        case 8:
        case 34:  // Extended Transverse Mercator
            poSpatialRef->SetTM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*----------------------------------------------------------------
             * Transverse Mercator,(modified for Danish System 34 Jylland-Fyn)
             *---------------------------------------------------------------*/
        case 21:
            // poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_21,
            poSpatialRef->SetTM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*--------------------------------------------------------------
             * Transverse Mercator,(modified for Danish System 34 Sjaelland)
             *-------------------------------------------------------------*/
        case 22:
            // poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_22,
            poSpatialRef->SetTM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*----------------------------------------------------------------
             * Transverse Mercator,(modified for Danish System 34/45 Bornholm)
             *---------------------------------------------------------------*/
        case 23:
            // poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_23,
            poSpatialRef->SetTM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*--------------------------------------------------------------
             * Transverse Mercator,(modified for Finnish KKJ)
             *-------------------------------------------------------------*/
        case 24:
            // poSpatialRef->SetTMVariant( SRS_PT_TRANSVERSE_MERCATOR_MI_24,
            poSpatialRef->SetTM(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*--------------------------------------------------------------
             * Albers Conic Equal Area
             *-------------------------------------------------------------*/
        case 9:
            poSpatialRef->SetACEA(
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[4], sTABProj.adProjParams[5]);
            break;

            /*--------------------------------------------------------------
             * Mercator
             *-------------------------------------------------------------*/
        case 10:
            poSpatialRef->SetMercator(0.0, sTABProj.adProjParams[0], 1.0, 0.0,
                                      0.0);
            break;

            /*--------------------------------------------------------------
             * Miller Cylindrical
             *-------------------------------------------------------------*/
        case 11:
            poSpatialRef->SetMC(0.0, sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Robinson
             *-------------------------------------------------------------*/
        case 12:
            poSpatialRef->SetRobinson(sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Mollweide
             *-------------------------------------------------------------*/
        case 13:
            poSpatialRef->SetMollweide(sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Eckert IV
             *-------------------------------------------------------------*/
        case 14:
            poSpatialRef->SetEckertIV(sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Eckert VI
             *-------------------------------------------------------------*/
        case 15:
            poSpatialRef->SetEckertVI(sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Sinusoidal
             *-------------------------------------------------------------*/
        case 16:
            poSpatialRef->SetSinusoidal(sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Gall Stereographic
             *-------------------------------------------------------------*/
        case 17:
            poSpatialRef->SetGS(sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * New Zealand Map Grid
             *-------------------------------------------------------------*/
        case 18:
            poSpatialRef->SetNZMG(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3]);
            break;

            /*--------------------------------------------------------------
             * Lambert Conic Conformal (Belgium)
             *-------------------------------------------------------------*/
        case 19:
            poSpatialRef->SetLCCB(
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[4], sTABProj.adProjParams[5]);
            break;

            /*--------------------------------------------------------------
             * Stereographic
             *-------------------------------------------------------------*/
        case 20:
            poSpatialRef->SetStereographic(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*--------------------------------------------------------------
             * Swiss Oblique Mercator / Cylindrical
             *-------------------------------------------------------------*/
        case 25:
            poSpatialRef->SetSOC(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3]);
            break;

            /*--------------------------------------------------------------
             * Regional Mercator (regular mercator with a latitude).
             *-------------------------------------------------------------*/
        case 26:
            poSpatialRef->SetMercator2SP(sTABProj.adProjParams[1], 0.0,
                                         sTABProj.adProjParams[0], 0.0, 0.0);
            break;

            /*--------------------------------------------------------------
             * Polyconic
             *-------------------------------------------------------------*/
        case 27:
            poSpatialRef->SetPolyconic(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3]);
            break;

            /*--------------------------------------------------------------
             * Cassini/Soldner
             *-------------------------------------------------------------*/
        case 30:
            poSpatialRef->SetCS(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3]);
            break;

            /*--------------------------------------------------------------
             * Oblique Stereographic
             *-------------------------------------------------------------*/
        case 31:
            poSpatialRef->SetOS(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3],
                sTABProj.adProjParams[4]);
            break;

            /*--------------------------------------------------------------
             * Krovak
             *-------------------------------------------------------------*/
        case 32:
            poSpatialRef->SetKrovak(
                sTABProj.adProjParams[1],   // dfCenterLat
                sTABProj.adProjParams[0],   // dfCenterLong
                sTABProj.adProjParams[3],   // dfAzimuth
                sTABProj.adProjParams[2],   // dfPseudoStdParallelLat
                1.0,                        // dfScale
                sTABProj.adProjParams[4],   // dfFalseEasting
                sTABProj.adProjParams[5]);  // dfFalseNorthing
            break;

            /*--------------------------------------------------------------
             * Equidistant Cylindrical / Equirectangular
             *-------------------------------------------------------------*/
        case 33:
            poSpatialRef->SetEquirectangular(
                sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                sTABProj.adProjParams[2], sTABProj.adProjParams[3]);
            break;

        default:
            poSpatialRef->SetProjection(CPLSPrintf(
                "Unhandled MapInfo projection method %d", sTABProj.nProjId));
            break;
    }

    /*-----------------------------------------------------------------
     * Local (nonearth) coordinate systems have no Geographic relationship
     * so we just return from here.
     *----------------------------------------------------------------*/
    if (sTABProj.nProjId == 0)
        return poSpatialRef;

    /*-----------------------------------------------------------------
     * Set the datum.  We are only given the X, Y and Z shift for
     * the datum, so for now we just synthesize a name from this.
     * It would be better if we could lookup a name based on the shift.
     *
     * Since we have already encountered files in which adDatumParams[] values
     * were in the order of 1e-150 when they should have actually been zeros,
     * we will use an epsilon in our scan instead of looking for equality.
     *----------------------------------------------------------------*/
    const MapInfoDatumInfo *psDatumInfo = nullptr;

    for (int iDatumInfo = 0; asDatumInfoList[iDatumInfo].nMapInfoDatumID != -1;
         iDatumInfo++)
    {
        psDatumInfo = asDatumInfoList + iDatumInfo;

        if (TAB_EQUAL(psDatumInfo->nEllipsoid, sTABProj.nEllipsoidId) &&
            ((sTABProj.nDatumId > 0 &&
              sTABProj.nDatumId == psDatumInfo->nMapInfoDatumID) ||
             (sTABProj.nDatumId <= 0 &&
              TAB_EQUAL(psDatumInfo->dfShiftX, sTABProj.dDatumShiftX) &&
              TAB_EQUAL(psDatumInfo->dfShiftY, sTABProj.dDatumShiftY) &&
              TAB_EQUAL(psDatumInfo->dfShiftZ, sTABProj.dDatumShiftZ) &&
              TAB_EQUAL(psDatumInfo->dfDatumParm0, sTABProj.adDatumParams[0]) &&
              TAB_EQUAL(psDatumInfo->dfDatumParm1, sTABProj.adDatumParams[1]) &&
              TAB_EQUAL(psDatumInfo->dfDatumParm2, sTABProj.adDatumParams[2]) &&
              TAB_EQUAL(psDatumInfo->dfDatumParm3, sTABProj.adDatumParams[3]) &&
              TAB_EQUAL(psDatumInfo->dfDatumParm4, sTABProj.adDatumParams[4]))))
            break;

        psDatumInfo = nullptr;
    }

    char szDatumName[200] = {};
    if (psDatumInfo == nullptr)
    {
        if (sTABProj.adDatumParams[0] == 0.0 &&
            sTABProj.adDatumParams[1] == 0.0 &&
            sTABProj.adDatumParams[2] == 0.0 &&
            sTABProj.adDatumParams[3] == 0.0 &&
            sTABProj.adDatumParams[4] == 0.0)
        {
            snprintf(szDatumName, sizeof(szDatumName),
                     "MIF 999,%u,%.15g,%.15g,%.15g", sTABProj.nEllipsoidId,
                     sTABProj.dDatumShiftX, sTABProj.dDatumShiftY,
                     sTABProj.dDatumShiftZ);
        }
        else
        {
            snprintf(
                szDatumName, sizeof(szDatumName),
                "MIF 9999,%u,%.15g,%.15g,%.15g,%.15g,%.15g,%.15g,%.15g,%.15g",
                sTABProj.nEllipsoidId, sTABProj.dDatumShiftX,
                sTABProj.dDatumShiftY, sTABProj.dDatumShiftZ,
                sTABProj.adDatumParams[0], sTABProj.adDatumParams[1],
                sTABProj.adDatumParams[2], sTABProj.adDatumParams[3],
                sTABProj.adDatumParams[4]);
        }
    }
    else if (strlen(psDatumInfo->pszOGCDatumName) > 0)
    {
        CPLStrlcpy(szDatumName, psDatumInfo->pszOGCDatumName,
                   sizeof(szDatumName));
    }
    else
    {
        snprintf(szDatumName, sizeof(szDatumName), "MIF %d",
                 psDatumInfo->nMapInfoDatumID);
    }

    /*-----------------------------------------------------------------
     * Set the spheroid.
     *----------------------------------------------------------------*/
    double dfSemiMajor = 0.0;
    double dfInvFlattening = 0.0;
    const char *pszSpheroidName = nullptr;

    for (int i = 0; asSpheroidInfoList[i].nMapInfoId != -1; i++)
    {
        if (asSpheroidInfoList[i].nMapInfoId == sTABProj.nEllipsoidId)
        {
            dfSemiMajor = asSpheroidInfoList[i].dfA;
            dfInvFlattening = asSpheroidInfoList[i].dfInvFlattening;
            pszSpheroidName = asSpheroidInfoList[i].pszMapinfoName;
            break;
        }
    }

    // use WGS 84 if nothing is known.
    if (pszSpheroidName == nullptr)
    {
        pszSpheroidName = "unknown";
        dfSemiMajor = 6378137.0;
        dfInvFlattening = 298.257223563;
    }

    /*-----------------------------------------------------------------
     * Set the prime meridian.
     *----------------------------------------------------------------*/
    double dfPMOffset = 0.0;
    const char *pszPMName = "Greenwich";

    if (/*sTABProj.nDatumId == 9999 ||*/ sTABProj.adDatumParams[4] != 0.0)
    {
        dfPMOffset = sTABProj.adDatumParams[4];

        if (fabs(dfPMOffset - 2.337229166667) < 1e-10)
            pszPMName = "Paris";
        else
            pszPMName = "non-Greenwich";
    }

    /*-----------------------------------------------------------------
     * Create a GEOGCS definition.
     *----------------------------------------------------------------*/

    poSpatialRef->SetGeogCS("unnamed", szDatumName, pszSpheroidName,
                            dfSemiMajor, dfInvFlattening, pszPMName, dfPMOffset,
                            SRS_UA_DEGREE, CPLAtof(SRS_UA_DEGREE_CONV));

    if (psDatumInfo != nullptr)
    {
        if (CPLTestBool(
                CPLGetConfigOption("MITAB_SET_TOWGS84_ON_KNOWN_DATUM", "NO")))
        {
            poSpatialRef->SetTOWGS84(
                psDatumInfo->dfShiftX, psDatumInfo->dfShiftY,
                psDatumInfo->dfShiftZ,
                psDatumInfo->dfDatumParm0 == 0
                    ? 0
                    : -psDatumInfo->dfDatumParm0, /* avoids 0 to be transformed
                                                     into -0 */
                psDatumInfo->dfDatumParm1 == 0 ? 0 : -psDatumInfo->dfDatumParm1,
                psDatumInfo->dfDatumParm2 == 0 ? 0 : -psDatumInfo->dfDatumParm2,
                psDatumInfo->dfDatumParm3);
        }
    }
    else
    {
        poSpatialRef->SetTOWGS84(
            sTABProj.dDatumShiftX, sTABProj.dDatumShiftY, sTABProj.dDatumShiftZ,
            sTABProj.adDatumParams[0] == 0 ? 0 : -sTABProj.adDatumParams[0],
            sTABProj.adDatumParams[1] == 0 ? 0 : -sTABProj.adDatumParams[1],
            sTABProj.adDatumParams[2] == 0 ? 0 : -sTABProj.adDatumParams[2],
            sTABProj.adDatumParams[3]);
    }

    /*-----------------------------------------------------------------
     * Special case for Google Mercator (datum=157, ellipse=54, gdal #4115)
     *----------------------------------------------------------------*/
    if (sTABProj.nProjId == 10 && sTABProj.nDatumId == 157 &&
        sTABProj.nEllipsoidId == 54)
    {
        poSpatialRef->SetNode("PROJCS", "WGS 84 / Pseudo-Mercator");
        poSpatialRef->SetExtension(
            "PROJCS", "PROJ4",
            "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 "
            "+y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  +no_defs");
    }

    /*-----------------------------------------------------------------
     * Special case for France Lambert-93
     *----------------------------------------------------------------*/
    if (sTABProj.nProjId == 3 && sTABProj.nDatumId == 33 &&
        sTABProj.nEllipsoidId == 0 &&
        TAB_EQUAL(poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0),
                  3.0) &&
        TAB_EQUAL(poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0),
                  46.5))
    {
        poSpatialRef->SetNode("PROJCS", "RGF93 / Lambert-93");
        poSpatialRef->SetNode("PROJCS|GEOGCS", "RGF93");
        poSpatialRef->SetNode("PROJCS|GEOGCS|DATUM",
                              "Reseau_Geodesique_Francais_1993");
    }

    if (sTABProj.nProjId == 3)
    {
        // If the LCC_2SP can be turned into a LCC_1SP that has the same
        // latitude of origin, then it is a better candidate
        OGRSpatialReference *poLCC1SP = poSpatialRef->convertToOtherProjection(
            SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP);
        if (poLCC1SP)
        {
            if (TAB_EQUAL(
                    poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN,
                                                  0.0),
                    poLCC1SP->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)))
            {
                delete poSpatialRef;
                poSpatialRef = poLCC1SP;
            }
            else
            {
                delete poLCC1SP;
            }
        }
    }

    /* For LCC, standard parallel 1 and 2 can be switched indifferently */
    /* So the MapInfo order and the EPSG order are not generally identical */
    /* which may cause recognition problems when reading in MapInfo */
    int nEPSGCandidateCode = 0;
    if (sTABProj.nProjId == 3)
    {
        double dfCenterLong = sTABProj.adProjParams[0];
        double dfCenterLat = sTABProj.adProjParams[1];
        double dfStdP1 = sTABProj.adProjParams[2];
        double dfStdP2 = sTABProj.adProjParams[3];

        for (size_t i = 0;
             i < sizeof(asMapInfoLCCSRSList) / sizeof(asMapInfoLCCSRSList[0]);
             i++)
        {
            if (sTABProj.nDatumId == asMapInfoLCCSRSList[i].nMapInfoDatumID &&
                TAB_EQUAL(dfCenterLong, asMapInfoLCCSRSList[i].dfCenterLong) &&
                TAB_EQUAL(dfCenterLat, asMapInfoLCCSRSList[i].dfCenterLat))
            {
                if (TAB_EQUAL(dfStdP1, asMapInfoLCCSRSList[i].dfStdP1) &&
                    (TAB_EQUAL(dfStdP2, asMapInfoLCCSRSList[i].dfStdP2) ||
                     // EPSG:3301 "Estonian Coordinate System of 1997"
                     // MapInfo uses a less accurate value of StdP2 than EPSG
                     (TAB_EQUAL(asMapInfoLCCSRSList[i].dfStdP2, 59.33333333) &&
                      TAB_EQUAL(dfStdP2, 59.3333333333333))))
                {
                    if (nEPSGCandidateCode)
                    {
                        poSpatialRef->SetAuthority("PROJCS", "EPSG",
                                                   nEPSGCandidateCode);
                        nEPSGCandidateCode = 0;
                        break;
                    }
                    if (asMapInfoLCCSRSList[i].bReverseStdP)
                    {
                        CPLDebug("MITAB",
                                 "Switching standard parallel 1 and 2");
                        poSpatialRef->SetLCC(
                            sTABProj.adProjParams[3], sTABProj.adProjParams[2],
                            sTABProj.adProjParams[1], sTABProj.adProjParams[0],
                            sTABProj.adProjParams[4], sTABProj.adProjParams[5]);
                    }
                    if (asMapInfoLCCSRSList[i].nEPSGCode > 0)
                        nEPSGCandidateCode = asMapInfoLCCSRSList[i].nEPSGCode;
                    else
                        break;
                }
            }
        }
    }

    /*-----------------------------------------------------------------
     * Apply linear units. Do that only after all above manipulations of
     * projection parameters.
     *----------------------------------------------------------------*/
    if (sTABProj.nProjId != 0 && sTABProj.nProjId != 1 &&
        CPLAtof(pszUnitsConv) != 1)
    {
        poSpatialRef->SetTargetLinearUnits(nullptr, pszUnitsName,
                                           CPLAtof(pszUnitsConv));
    }

    if (nEPSGCandidateCode)
    {
        OGRSpatialReference oTmp;
        oTmp.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
        if (oTmp.importFromEPSG(nEPSGCandidateCode) == OGRERR_NONE)
        {
            *poSpatialRef = std::move(oTmp);
        }
    }

    /*-----------------------------------------------------------------
     * Special case for Philippine Reference System 1992, to override
     * the MapInfo ellipsoid=8 "Clarke 1866 (modified for Michigan)"
     * by the regular Clarke 1866 of EPSG
     *----------------------------------------------------------------*/
    if (sTABProj.nDatumId == 1031 && sTABProj.nEllipsoidId == 8)
    {
        OGRSpatialReference oSRS_EPSG_4683;
        if (oSRS_EPSG_4683.importFromEPSG(4683) == OGRERR_NONE)
            poSpatialRef->CopyGeogCSFrom(&oSRS_EPSG_4683);
    }

    /*-----------------------------------------------------------------
     * Special case for Kertau (RSO), to override
     * the MapInfo ellipsoid=40 "Everest (India 1956)", 6377301.243, 300.80174
     * with EPSG's "Everest 1830 (RSO 1969)",6377295.664,300.8017
     *----------------------------------------------------------------*/
    if (sTABProj.nDatumId == 164 && sTABProj.nEllipsoidId == 40)
    {
        OGRSpatialReference oSRS_EPSG_4751;
        if (oSRS_EPSG_4751.importFromEPSG(4751) == OGRERR_NONE)
            poSpatialRef->CopyGeogCSFrom(&oSRS_EPSG_4751);
    }

    return poSpatialRef;
}

static int MITABGetCustomDatum(const OGRSpatialReference *poSpatialRef,
                               TABProjInfo &sTABProj)
{
    double adfTOWGS[7] = {0};
    if (OGRERR_NONE != poSpatialRef->GetTOWGS84(
                           adfTOWGS, sizeof(adfTOWGS) / sizeof(adfTOWGS[0])))
    {
        return FALSE;
    }
    sTABProj.nDatumId = 9999;
    sTABProj.dDatumShiftX = adfTOWGS[0];
    sTABProj.dDatumShiftY = adfTOWGS[1];
    sTABProj.dDatumShiftZ = adfTOWGS[2];
    sTABProj.adDatumParams[0] = -adfTOWGS[3];
    sTABProj.adDatumParams[1] = -adfTOWGS[4];
    sTABProj.adDatumParams[2] = -adfTOWGS[5];
    sTABProj.adDatumParams[3] = adfTOWGS[6];

    int nSpheroidId = -1;

    const char *pszWKTSpheroid = poSpatialRef->GetAttrValue("SPHEROID");
    for (int i = 0; asSpheroidInfoList[i].nMapInfoId != -1; i++)
    {
        if (EQUAL(pszWKTSpheroid, asSpheroidInfoList[i].pszMapinfoName))
        {
            nSpheroidId = asSpheroidInfoList[i].nMapInfoId;
            break;
        }
    }

    if (nSpheroidId == -1)
    {
        double adSemiMajor = poSpatialRef->GetSemiMajor();
        double adInvFlattening = poSpatialRef->GetInvFlattening();

        for (int i = 0; asSpheroidInfoList[i].nMapInfoId != -1; i++)
        {
            if (CPLIsEqual(adSemiMajor, asSpheroidInfoList[i].dfA) &&
                CPLIsEqual(adInvFlattening,
                           asSpheroidInfoList[i].dfInvFlattening))
            {
                nSpheroidId = asSpheroidInfoList[i].nMapInfoId;
                break;
            }
        }
    }
    if (nSpheroidId == -1)
    {
        CPLDebug(
            "MITAB",
            "Cannot find MapInfo spheroid matching %s. Defaulting to WGS 84",
            pszWKTSpheroid);
        nSpheroidId = 28; /* WGS 84 */
    }
    sTABProj.nEllipsoidId = static_cast<GByte>(nSpheroidId);
    return TRUE;
}

int TABFileGetTABProjFromSpatialRef(const OGRSpatialReference *poSpatialRef,
                                    TABProjInfo &sTABProj, int &nParamCount)
{
    /*-----------------------------------------------------------------
     * Initialize TABProjInfo
     *----------------------------------------------------------------*/
    sTABProj.nProjId = 0;
    sTABProj.nEllipsoidId = 0; /* how will we set this? */
    sTABProj.nUnitsId = 7;
    sTABProj.adProjParams[0] = sTABProj.adProjParams[1] = 0.0;
    sTABProj.adProjParams[2] = sTABProj.adProjParams[3] = 0.0;
    sTABProj.adProjParams[4] = sTABProj.adProjParams[5] = 0.0;

    sTABProj.nDatumId = 0;
    sTABProj.dDatumShiftX = 0.0;
    sTABProj.dDatumShiftY = 0.0;
    sTABProj.dDatumShiftZ = 0.0;
    sTABProj.adDatumParams[0] = 0.0;
    sTABProj.adDatumParams[1] = 0.0;
    sTABProj.adDatumParams[2] = 0.0;
    sTABProj.adDatumParams[3] = 0.0;
    sTABProj.adDatumParams[4] = 0.0;

    sTABProj.nAffineFlag = 0;
    sTABProj.nAffineUnits = 7;
    sTABProj.dAffineParamA = 0.0;
    sTABProj.dAffineParamB = 0.0;
    sTABProj.dAffineParamC = 0.0;
    sTABProj.dAffineParamD = 0.0;
    sTABProj.dAffineParamE = 0.0;
    sTABProj.dAffineParamF = 0.0;

    /*-----------------------------------------------------------------
     * Get the linear units and conversion.
     *----------------------------------------------------------------*/
    const char *pszLinearUnits = nullptr;
    double dfLinearConv = poSpatialRef->GetLinearUnits(&pszLinearUnits);
    if (dfLinearConv == 0.0)
        dfLinearConv = 1.0;

    // Get datum information
    const char *pszWKTDatum = poSpatialRef->GetAttrValue("DATUM");

    const auto GetDatumCode = [](const OGRSpatialReference *poSRS)
    {
        const char *pszDatumAuthority = poSRS->GetAuthorityName("DATUM");
        const char *pszDatumCode = poSRS->GetAuthorityCode("DATUM");
        if (pszDatumCode && pszDatumAuthority &&
            EQUAL(pszDatumAuthority, "EPSG"))
        {
            return atoi(pszDatumCode);
        }
        return -1;
    };

    int nDatumEPSGCode = GetDatumCode(poSpatialRef);
    if (nDatumEPSGCode < 0)
    {
        const auto GetDatumCodeFromCRSIndirect =
            [&GetDatumCode](const OGRSpatialReference *poSRS,
                            const char *pszNode)
        {
            const char *pszAuthorityName = poSRS->GetAuthorityName(pszNode);
            const char *pszAuthorityCode = poSRS->GetAuthorityCode(pszNode);
            if (pszAuthorityName && pszAuthorityCode)
            {
                OGRSpatialReference oSRSTmp;
                if (oSRSTmp.SetFromUserInput(CPLSPrintf(
                        "%s:%s", pszAuthorityName, pszAuthorityCode)) ==
                    OGRERR_NONE)
                {
                    return GetDatumCode(&oSRSTmp);
                }
            }
            return -1;
        };

        // When the CRS is built from WKT2 CRS string, the DATUM code will
        // typically be absent from the CRS string.
        // Try to get the AUTHORITY:CODE from the CRS to instantiate
        // a temporary CRS and get its DATUM code.
        nDatumEPSGCode = GetDatumCodeFromCRSIndirect(poSpatialRef, nullptr);
        if (nDatumEPSGCode < 0 && !poSpatialRef->IsGeographic())
        {
            // If there's no AUTHORITY:CODE on the CRS, then retry with its
            // geographic CRS
            nDatumEPSGCode =
                GetDatumCodeFromCRSIndirect(poSpatialRef, "GEOGCS");
        }
    }

    /*-----------------------------------------------------------------
     * Transform the projection and projection parameters.
     *----------------------------------------------------------------*/
    const char *pszProjection = poSpatialRef->GetAttrValue("PROJECTION");
    double *params = sTABProj.adProjParams;
    nParamCount = 0;

    if (pszProjection == nullptr &&
        poSpatialRef->GetAttrNode("GEOGCS") == nullptr)
    {
        /* nonearth */
        sTABProj.nProjId = 0;
    }

    else if (pszProjection == nullptr)
    {
        sTABProj.nProjId = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_ALBERS_CONIC_EQUAL_AREA))
    {
        sTABProj.nProjId = 9;
        params[0] =
            poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
        params[2] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        params[3] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 6;
    }

    else if (EQUAL(pszProjection, SRS_PT_AZIMUTHAL_EQUIDISTANT))
    {
        sTABProj.nProjId = 5;
        params[0] =
            poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
        params[2] = 90.0;
        nParamCount = 3;

        if (std::abs((std::abs(params[1]) - 90)) > 0.001)
            sTABProj.nProjId = 28;
    }

    else if (EQUAL(pszProjection, SRS_PT_CYLINDRICAL_EQUAL_AREA))
    {
        sTABProj.nProjId = 2;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        nParamCount = 2;
    }

    else if (EQUAL(pszProjection, SRS_PT_ECKERT_IV))
    {
        sTABProj.nProjId = 14;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_ECKERT_VI))
    {
        sTABProj.nProjId = 15;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_EQUIDISTANT_CONIC))
    {
        sTABProj.nProjId = 6;
        params[0] =
            poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
        params[2] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        params[3] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 6;
    }

    else if (EQUAL(pszProjection, SRS_PT_GALL_STEREOGRAPHIC))
    {
        sTABProj.nProjId = 17;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR))
    {
        if (std::abs(poSpatialRef->GetNormProjParm(SRS_PP_RECTIFIED_GRID_ANGLE,
                                                   90.0) -
                     90.0) < 1e-8)
        {
            sTABProj.nProjId = 7;
            params[0] =
                poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
            params[1] =
                poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
            params[2] = poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
            params[3] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
            params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
            params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
            nParamCount = 6;
        }
        else
        {
            sTABProj.nProjId = 35;
            params[0] =
                poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
            params[1] =
                poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
            params[2] = poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
            params[3] =
                poSpatialRef->GetNormProjParm(SRS_PP_RECTIFIED_GRID_ANGLE, 0.0);
            params[4] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
            params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
            params[6] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
            nParamCount = 7;
        }
    }

    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
    {
        sTABProj.nProjId = 4;
        params[0] =
            poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
        params[2] = 90.0;
        nParamCount = 3;

        if (std::abs((std::abs(params[1]) - 90)) > 0.001)
            sTABProj.nProjId = 29;
    }

    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
    {
        sTABProj.nProjId = 3;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        params[3] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 6;
    }

    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
    {
        OGRSpatialReference *poOtherSRS =
            poSpatialRef->convertToOtherProjection(
                SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP);
        if (poOtherSRS)
        {
            sTABProj.nProjId = 3;
            params[0] =
                poOtherSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
            params[1] =
                poOtherSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
            params[2] =
                poOtherSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
            params[3] =
                poOtherSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
            params[4] = poOtherSRS->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
            params[5] = poOtherSRS->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
            nParamCount = 6;
            delete poOtherSRS;
        }
    }

    else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP_BELGIUM))
    {
        sTABProj.nProjId = 19;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        params[3] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 6;
    }

    else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
    {
        sTABProj.nProjId = 10;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        nParamCount = 1;  // FIXME for MIF export ?

        if (params[1] != 0.0)
        {
            sTABProj.nProjId = 26;
            nParamCount = 2;  // FIXME for MIF export ?
        }
    }

    else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
    {
        sTABProj.nProjId = 26;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
        nParamCount = 2;  // FIXME for MIF export ?
    }

    else if (EQUAL(pszProjection, SRS_PT_MILLER_CYLINDRICAL))
    {
        sTABProj.nProjId = 11;
        params[0] =
            poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_MOLLWEIDE))
    {
        sTABProj.nProjId = 13;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_NEW_ZEALAND_MAP_GRID))
    {
        sTABProj.nProjId = 18;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 4;
    }

    else if (EQUAL(pszProjection, SRS_PT_SWISS_OBLIQUE_CYLINDRICAL))
    {
        sTABProj.nProjId = 25;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 4;
    }

    // Swiss Oblique expressed as Hotine Oblique Mercator Azimuth Center
    else if (EQUAL(pszProjection,
                   SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER) &&
             std::abs(poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH, 90.0) -
                      90.0) < 1e-8 &&
             std::abs(poSpatialRef->GetNormProjParm(SRS_PP_RECTIFIED_GRID_ANGLE,
                                                    90.0) -
                      90.0) < 1e-8 &&
             std::abs(poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0) -
                      1.0) < 1e-8)
    {
        sTABProj.nProjId = 25;
        params[0] =
            poSpatialRef->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 4;
    }

    else if (EQUAL(pszProjection, SRS_PT_ROBINSON))
    {
        sTABProj.nProjId = 12;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
    {
        sTABProj.nProjId = 16;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        nParamCount = 1;
    }

    else if (EQUAL(pszProjection, SRS_PT_STEREOGRAPHIC))
    {
        sTABProj.nProjId = 20;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection, SRS_PT_OBLIQUE_STEREOGRAPHIC))
    {
        sTABProj.nProjId = 31;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    {
        sTABProj.nProjId = 8;
        if ((pszWKTDatum &&
             EQUAL(pszWKTDatum, "Kartastokoordinaattijarjestelma_1966")) ||
            nDatumEPSGCode == 6123)
        {
            // Special case for Finnish KKJ
            sTABProj.nProjId = 24;
        }
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection,
                   SRS_PT_TRANSVERSE_MERCATOR_MI_21))  // Encom 2003
    {
        sTABProj.nProjId = 21;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection,
                   SRS_PT_TRANSVERSE_MERCATOR_MI_22))  // Encom 2003
    {
        sTABProj.nProjId = 22;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection,
                   SRS_PT_TRANSVERSE_MERCATOR_MI_23))  // Encom 2003
    {
        sTABProj.nProjId = 23;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection,
                   SRS_PT_TRANSVERSE_MERCATOR_MI_24))  // Encom 2003
    {
        sTABProj.nProjId = 24;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_SCALE_FACTOR, 1.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 5;
    }

    else if (EQUAL(pszProjection, SRS_PT_CASSINI_SOLDNER))
    {
        sTABProj.nProjId = 30;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 4;
    }

    else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
    {
        sTABProj.nProjId = 27;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 4;
    }

    else if (EQUAL(pszProjection, SRS_PT_KROVAK))
    {
        sTABProj.nProjId = 32;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] =
            poSpatialRef->GetNormProjParm(SRS_PP_PSEUDO_STD_PARALLEL_1, 0.0);
        params[3] = poSpatialRef->GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
        params[4] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[5] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 6;
    }

    else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
    {
        sTABProj.nProjId = 33;
        params[0] = poSpatialRef->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
        params[1] =
            poSpatialRef->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
        params[2] = poSpatialRef->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
        params[3] = poSpatialRef->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
        nParamCount = 4;
    }

    else
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "No translation from %s to MapInfo known", pszProjection);
    }

    /* ==============================================================
     * Translate Datum and Ellipsoid
     * ============================================================== */
    const MapInfoDatumInfo *psDatumInfo = nullptr;

    /*-----------------------------------------------------------------
     * Default to WGS84 if we have no datum at all.
     *----------------------------------------------------------------*/
    if (pszWKTDatum == nullptr)
    {
        CPLDebug("MITAB",
                 "Cannot find MapInfo datum matching %d. Defaulting to WGS 84",
                 nDatumEPSGCode);
        psDatumInfo = asDatumInfoList + 0; /* WGS 84 */
        // From MIF export code. FIXME?
        // if( nProjection == 1 )
        //    nProjection = 0;
    }

    /*-----------------------------------------------------------------
     * We know the MIF datum number, and need to look it up to
     * translate into datum parameters.
     *----------------------------------------------------------------*/
    else if (STARTS_WITH_CI(pszWKTDatum, "MIF ") &&
             atoi(pszWKTDatum + 4) != 999 && atoi(pszWKTDatum + 4) != 9999)
    {
        int nDatum = atoi(pszWKTDatum + 4);
        for (int i = 0; asDatumInfoList[i].nMapInfoDatumID != -1; i++)
        {
            if (nDatum == asDatumInfoList[i].nMapInfoDatumID)
            {
                psDatumInfo = asDatumInfoList + i;
                break;
            }
        }

        if (psDatumInfo == nullptr)
        {
            CPLDebug(
                "MITAB",
                "Cannot find MapInfo datum matching %s. Defaulting to WGS 84",
                pszWKTDatum);
            psDatumInfo = asDatumInfoList + 0; /* WGS 84 */
        }
    }

    /*-----------------------------------------------------------------
     * We have the MIF datum parameters, and apply those directly.
     *----------------------------------------------------------------*/
    else if (STARTS_WITH_CI(pszWKTDatum, "MIF ") &&
             (atoi(pszWKTDatum + 4) == 999 || atoi(pszWKTDatum + 4) == 9999))
    {
        sTABProj.nDatumId = static_cast<GInt16>(atoi(pszWKTDatum + 4));
        char **papszFields =
            CSLTokenizeStringComplex(pszWKTDatum + 4, ",", FALSE, TRUE);

        if (CSLCount(papszFields) >= 5)
        {
            sTABProj.nEllipsoidId = static_cast<GByte>(atoi(papszFields[1]));
            sTABProj.dDatumShiftX = CPLAtof(papszFields[2]);
            sTABProj.dDatumShiftY = CPLAtof(papszFields[3]);
            sTABProj.dDatumShiftZ = CPLAtof(papszFields[4]);
        }

        if (CSLCount(papszFields) >= 10)
        {
            sTABProj.adDatumParams[0] = CPLAtof(papszFields[5]);
            sTABProj.adDatumParams[1] = CPLAtof(papszFields[6]);
            sTABProj.adDatumParams[2] = CPLAtof(papszFields[7]);
            sTABProj.adDatumParams[3] = CPLAtof(papszFields[8]);
            sTABProj.adDatumParams[4] = CPLAtof(papszFields[9]);
        }

        if (CSLCount(papszFields) < 5)
        {
            CPLDebug(
                "MITAB",
                "Cannot find MapInfo datum matching %s. Defaulting to WGS 84",
                pszWKTDatum);
            psDatumInfo = asDatumInfoList + 0; /* WGS 84 */
        }

        CSLDestroy(papszFields);
    }

    /*-----------------------------------------------------------------
     * We have a "real" datum name, and possibly an EPSG code for the
     * datum.  Try to look it up (using EPSG code first) and get the
     * parameters.  If we don't find it with either just use WGS84.
     *----------------------------------------------------------------*/
    else
    {
        for (int i = 0; asDatumInfoList[i].nMapInfoDatumID != -1; i++)
        {
            if ((nDatumEPSGCode > 0 &&
                 asDatumInfoList[i].nDatumEPSGCode == nDatumEPSGCode) ||
                EQUAL(pszWKTDatum, asDatumInfoList[i].pszOGCDatumName))
            {
                psDatumInfo = asDatumInfoList + i;
                break;
            }
        }

        if (psDatumInfo == nullptr &&
            !MITABGetCustomDatum(poSpatialRef, sTABProj))
        {
            CPLDebug("MITAB",
                     "Cannot find MapInfo datum matching %s,%d. Defaulting to "
                     "WGS 84",
                     pszWKTDatum, nDatumEPSGCode);
            psDatumInfo = asDatumInfoList + 0; /* WGS 84 */
        }
    }

    if (psDatumInfo != nullptr)
    {
        sTABProj.nEllipsoidId = static_cast<GByte>(psDatumInfo->nEllipsoid);
        sTABProj.nDatumId = static_cast<GInt16>(psDatumInfo->nMapInfoDatumID);
        sTABProj.dDatumShiftX = psDatumInfo->dfShiftX;
        sTABProj.dDatumShiftY = psDatumInfo->dfShiftY;
        sTABProj.dDatumShiftZ = psDatumInfo->dfShiftZ;
        sTABProj.adDatumParams[0] = psDatumInfo->dfDatumParm0;
        sTABProj.adDatumParams[1] = psDatumInfo->dfDatumParm1;
        sTABProj.adDatumParams[2] = psDatumInfo->dfDatumParm2;
        sTABProj.adDatumParams[3] = psDatumInfo->dfDatumParm3;
        sTABProj.adDatumParams[4] = psDatumInfo->dfDatumParm4;

        /* For LCC, standard parallel 1 and 2 can be switched indifferently */
        /* So the MapInfo order and the EPSG order are not generally identical
         */
        /* which may cause recognition problems when reading in MapInfo */
        if (sTABProj.nProjId == 3)
        {
            const double dfCenterLong = params[0];
            const double dfCenterLat = params[1];
            const double dfStdP1 = params[2];
            const double dfStdP2 = params[3];

            // EPSG:3301 "Estonian Coordinate System of 1997"
            if (std::fabs(dfCenterLong - 24) <= 1e-10 &&
                std::fabs(dfCenterLat - 57.51755393056) <= 1e-10 &&
                // MapInfo uses a less accurate value of StdP2 than EPSG
                std::fabs(dfStdP1 - 59.33333333) <= 1e-8 &&
                std::fabs(dfStdP2 - 58) <= 1e-8)
            {
                CPLDebug("MITAB", "Switching standard parallel 1 and 2");
                std::swap(params[2], params[3]);
            }
            else
            {
                for (size_t i = 0; i < sizeof(asMapInfoLCCSRSList) /
                                           sizeof(asMapInfoLCCSRSList[0]);
                     i++)
                {
                    if (sTABProj.nDatumId ==
                            asMapInfoLCCSRSList[i].nMapInfoDatumID &&
                        TAB_EQUAL(dfCenterLong,
                                  asMapInfoLCCSRSList[i].dfCenterLong) &&
                        TAB_EQUAL(dfCenterLat,
                                  asMapInfoLCCSRSList[i].dfCenterLat))
                    {
                        if (TAB_EQUAL(dfStdP1,
                                      asMapInfoLCCSRSList[i].dfStdP1) &&
                            TAB_EQUAL(dfStdP2, asMapInfoLCCSRSList[i].dfStdP2))
                        {
                            break;
                        }
                        else if (TAB_EQUAL(dfStdP1,
                                           asMapInfoLCCSRSList[i].dfStdP2) &&
                                 TAB_EQUAL(dfStdP2,
                                           asMapInfoLCCSRSList[i].dfStdP1))
                        {
                            CPLDebug("MITAB",
                                     "Switching standard parallel 1 and 2");
                            std::swap(params[2], params[3]);
                            break;
                        }
                    }
                }
            }
        }
    }

    // Google Merc
    const char *pszAuthorityName = nullptr;
    const char *pszAuthorityCode = nullptr;
    const char *pszExtension = nullptr;
    if (((pszAuthorityName = poSpatialRef->GetAuthorityName(nullptr)) !=
             nullptr &&
         EQUAL(pszAuthorityName, "EPSG") &&
         (pszAuthorityCode = poSpatialRef->GetAuthorityCode(nullptr)) !=
             nullptr &&
         atoi(pszAuthorityCode) == 3857) ||
        ((pszExtension = poSpatialRef->GetExtension(nullptr, "PROJ4")) !=
             nullptr &&
         (EQUAL(pszExtension, "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 "
                              "+lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m "
                              "+nadgrids=@null +wktext  +no_defs") ||
          EQUAL(pszExtension,
                "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 "
                "+y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs"))))
    {
        sTABProj.nDatumId = 157;
        sTABProj.nEllipsoidId = 54;
    }

    /*-----------------------------------------------------------------
     * Translate the units
     *----------------------------------------------------------------*/
    if (sTABProj.nProjId == 1 || pszLinearUnits == nullptr)
        sTABProj.nUnitsId = 13;
    else if (dfLinearConv == 1000.0)
        sTABProj.nUnitsId = 1;
    else if (dfLinearConv == 0.0254 || EQUAL(pszLinearUnits, "Inch") ||
             EQUAL(pszLinearUnits, "IINCH"))
        sTABProj.nUnitsId = 2;
    else if (fabs(dfLinearConv - CPLAtof(SRS_UL_FOOT_CONV)) <
                 1e-15 * dfLinearConv ||
             EQUAL(pszLinearUnits, SRS_UL_FOOT))
        sTABProj.nUnitsId = 3;
    else if (EQUAL(pszLinearUnits, "YARD") || EQUAL(pszLinearUnits, "IYARD") ||
             dfLinearConv == 0.9144)
        sTABProj.nUnitsId = 4;
    else if (dfLinearConv == 0.001)
        sTABProj.nUnitsId = 5;
    else if (dfLinearConv == 0.01)
        sTABProj.nUnitsId = 6;
    else if (dfLinearConv == 1.0)
        sTABProj.nUnitsId = 7;
    else if (fabs(dfLinearConv - CPLAtof(SRS_UL_US_FOOT_CONV)) <
                 1e-15 * dfLinearConv ||
             EQUAL(pszLinearUnits, SRS_UL_US_FOOT))
        sTABProj.nUnitsId = 8;
    else if (dfLinearConv == 1852.0 ||
             EQUAL(pszLinearUnits, SRS_UL_NAUTICAL_MILE))
        sTABProj.nUnitsId = 9;
    else if (EQUAL(pszLinearUnits, SRS_UL_LINK) ||
             EQUAL(pszLinearUnits, "GUNTERLINK"))
        sTABProj.nUnitsId = 30;
    else if (EQUAL(pszLinearUnits, SRS_UL_CHAIN) ||
             EQUAL(pszLinearUnits, "GUNTERCHAIN"))
        sTABProj.nUnitsId = 31;
    else if (EQUAL(pszLinearUnits, SRS_UL_ROD))
        sTABProj.nUnitsId = 32;
    else if (EQUAL(pszLinearUnits, "Mile") || EQUAL(pszLinearUnits, "IMILE"))
        sTABProj.nUnitsId = 0;
    else
        sTABProj.nUnitsId = 7;

    return 0;
}

extern const MapInfoDatumInfo asDatumInfoList[];
extern const MapInfoSpheroidInfo asSpheroidInfoList[];

/************************************************************************/
/*                      MITABCoordSys2SpatialRef()                      */
/*                                                                      */
/*      Convert a MIF COORDSYS string into a new OGRSpatialReference    */
/*      object.                                                         */
/************************************************************************/

OGRSpatialReference *MITABCoordSys2SpatialRef(const char *pszCoordSys)

{
    TABProjInfo sTABProj;
    if (MITABCoordSys2TABProjInfo(pszCoordSys, &sTABProj) < 0)
        return nullptr;
    OGRSpatialReference *poSR = TABFileGetSpatialRefFromTABProj(sTABProj);

    // Report on translation.
    char *pszWKT = nullptr;

    poSR->exportToWkt(&pszWKT);
    if (pszWKT != nullptr)
    {
        CPLDebug("MITAB", "This CoordSys value:\n%s\nwas translated to:\n%s",
                 pszCoordSys, pszWKT);
        CPLFree(pszWKT);
    }

    return poSR;
}

/************************************************************************/
/*                      MITABSpatialRef2CoordSys()                      */
/*                                                                      */
/*      Converts a OGRSpatialReference object into a MIF COORDSYS       */
/*      string.                                                         */
/*                                                                      */
/*      The function returns a newly allocated string that should be    */
/*      CPLFree()'d by the caller.                                      */
/************************************************************************/

char *MITABSpatialRef2CoordSys(const OGRSpatialReference *poSR)

{
    if (poSR == nullptr)
        return nullptr;

    TABProjInfo sTABProj;
    int nParamCount = 0;
    TABFileGetTABProjFromSpatialRef(poSR, sTABProj, nParamCount);

    // Do coordsys lookup.
    double dXMin = 0.0;
    double dYMin = 0.0;
    double dXMax = 0.0;
    double dYMax = 0.0;
    bool bHasBounds = false;
    if (sTABProj.nProjId > 1 &&
        MITABLookupCoordSysBounds(&sTABProj, dXMin, dYMin, dXMax, dYMax, true))
    {
        bHasBounds = true;
    }

    // Translate the units.
    const char *pszMIFUnits = TABUnitIdToString(sTABProj.nUnitsId);

    // Build coordinate system definition.
    CPLString osCoordSys;

    if (sTABProj.nProjId != 0)
    {
        osCoordSys.Printf("Earth Projection %d", sTABProj.nProjId);
    }
    else
    {
        osCoordSys.Printf("NonEarth Units");
    }

    // Append Datum.
    if (sTABProj.nProjId != 0)
    {
        osCoordSys += CPLSPrintf(", %d", sTABProj.nDatumId);

        if (sTABProj.nDatumId == 999 || sTABProj.nDatumId == 9999)
        {
            osCoordSys +=
                CPLSPrintf(", %d, %.15g, %.15g, %.15g", sTABProj.nEllipsoidId,
                           sTABProj.dDatumShiftX, sTABProj.dDatumShiftY,
                           sTABProj.dDatumShiftZ);
        }

        if (sTABProj.nDatumId == 9999)
        {
            osCoordSys +=
                CPLSPrintf(", %.15g, %.15g, %.15g, %.15g, %.15g",
                           sTABProj.adDatumParams[0], sTABProj.adDatumParams[1],
                           sTABProj.adDatumParams[2], sTABProj.adDatumParams[3],
                           sTABProj.adDatumParams[4]);
        }
    }

    // Append units.
    if (sTABProj.nProjId != 1 && pszMIFUnits != nullptr)
    {
        if (sTABProj.nProjId != 0)
            osCoordSys += ",";

        osCoordSys += CPLSPrintf(" \"%s\"", pszMIFUnits);
    }

    // Append Projection Params.
    for (int iParam = 0; iParam < nParamCount; iParam++)
        osCoordSys += CPLSPrintf(", %.15g", sTABProj.adProjParams[iParam]);

    // Append user bounds.
    if (bHasBounds)
    {
        if (fabs(dXMin - floor(dXMin + 0.5)) < 1e-8 &&
            fabs(dYMin - floor(dYMin + 0.5)) < 1e-8 &&
            fabs(dXMax - floor(dXMax + 0.5)) < 1e-8 &&
            fabs(dYMax - floor(dYMax + 0.5)) < 1e-8)
        {
            osCoordSys +=
                CPLSPrintf(" Bounds (%d, %d) (%d, %d)", static_cast<int>(dXMin),
                           static_cast<int>(dYMin), static_cast<int>(dXMax),
                           static_cast<int>(dYMax));
        }
        else
        {
            osCoordSys += CPLSPrintf(" Bounds (%f, %f) (%f, %f)", dXMin, dYMin,
                                     dXMax, dYMax);
        }
    }

    // Report on translation.
    char *pszWKT = nullptr;

    poSR->exportToWkt(&pszWKT);
    if (pszWKT != nullptr)
    {
        CPLDebug("MITAB", "This WKT Projection:\n%s\n\ntranslates to:\n%s",
                 pszWKT, osCoordSys.c_str());
        CPLFree(pszWKT);
    }

    return CPLStrdup(osCoordSys.c_str());
}

/************************************************************************/
/*                      MITABExtractCoordSysBounds                      */
/*                                                                      */
/* Return true if MIF coordsys string contains a BOUNDS parameter and   */
/* Set x/y min/max values.                                              */
/************************************************************************/

bool MITABExtractCoordSysBounds(const char *pszCoordSys, double &dXMin,
                                double &dYMin, double &dXMax, double &dYMax)

{
    if (pszCoordSys == nullptr)
        return false;

    char **papszFields =
        CSLTokenizeStringComplex(pszCoordSys, " ,()", TRUE, FALSE);

    int iBounds = CSLFindString(papszFields, "Bounds");

    if (iBounds >= 0 && iBounds + 4 < CSLCount(papszFields))
    {
        dXMin = CPLAtof(papszFields[++iBounds]);
        dYMin = CPLAtof(papszFields[++iBounds]);
        dXMax = CPLAtof(papszFields[++iBounds]);
        dYMax = CPLAtof(papszFields[++iBounds]);
        CSLDestroy(papszFields);
        return true;
    }

    CSLDestroy(papszFields);
    return false;
}

/**********************************************************************
 *                     MITABCoordSys2TABProjInfo()
 *
 * Convert a MIF COORDSYS string into a TABProjInfo structure.
 *
 * Returns 0 on success, -1 on error.
 **********************************************************************/
int MITABCoordSys2TABProjInfo(const char *pszCoordSys, TABProjInfo *psProj)

{
    // Set all fields to zero, equivalent of NonEarth Units "mi"
    memset(psProj, 0, sizeof(TABProjInfo));

    if (pszCoordSys == nullptr)
        return -1;

    // Parse the passed string into words.
    while (*pszCoordSys == ' ')
        pszCoordSys++;  // Eat leading spaces.
    if (STARTS_WITH_CI(pszCoordSys, "CoordSys") && pszCoordSys[8] != '\0')
        pszCoordSys += 9;

    char **papszFields =
        CSLTokenizeStringComplex(pszCoordSys, " ,", TRUE, FALSE);

    // Clip off Bounds information.
    int iBounds = CSLFindString(papszFields, "Bounds");

    while (iBounds != -1 && papszFields[iBounds] != nullptr)
    {
        CPLFree(papszFields[iBounds]);
        papszFields[iBounds] = nullptr;
        iBounds++;
    }

    // Fetch the projection.
    char **papszNextField = nullptr;

    if (CSLCount(papszFields) >= 3 && EQUAL(papszFields[0], "Earth") &&
        EQUAL(papszFields[1], "Projection"))
    {
        int nProjId = atoi(papszFields[2]);
        if (nProjId >= 3000)
            nProjId -= 3000;
        else if (nProjId >= 2000)
            nProjId -= 2000;
        else if (nProjId >= 1000)
            nProjId -= 1000;

        psProj->nProjId = static_cast<GByte>(nProjId);
        papszNextField = papszFields + 3;
    }
    else if (CSLCount(papszFields) >= 2 && EQUAL(papszFields[0], "NonEarth"))
    {
        // NonEarth Units "..." Bounds (x, y) (x, y)
        psProj->nProjId = 0;
        papszNextField = papszFields + 2;

        if (papszNextField[0] != nullptr && EQUAL(papszNextField[0], "Units"))
            papszNextField++;
    }
    else
    {
        // Invalid projection string ???
        if (CSLCount(papszFields) > 0)
            CPLError(CE_Warning, CPLE_IllegalArg,
                     "Failed parsing CoordSys: '%s'", pszCoordSys);
        CSLDestroy(papszFields);
        return -1;
    }

    // Fetch the datum information.
    int nDatum = 0;

    if (psProj->nProjId != 0 && CSLCount(papszNextField) > 0)
    {
        nDatum = atoi(papszNextField[0]);
        papszNextField++;
    }

    if ((nDatum == 999 || nDatum == 9999) && CSLCount(papszNextField) >= 4)
    {
        psProj->nEllipsoidId = static_cast<GByte>(atoi(papszNextField[0]));
        psProj->dDatumShiftX = CPLAtof(papszNextField[1]);
        psProj->dDatumShiftY = CPLAtof(papszNextField[2]);
        psProj->dDatumShiftZ = CPLAtof(papszNextField[3]);
        papszNextField += 4;

        if (nDatum == 9999 && CSLCount(papszNextField) >= 5)
        {
            psProj->adDatumParams[0] = CPLAtof(papszNextField[0]);
            psProj->adDatumParams[1] = CPLAtof(papszNextField[1]);
            psProj->adDatumParams[2] = CPLAtof(papszNextField[2]);
            psProj->adDatumParams[3] = CPLAtof(papszNextField[3]);
            psProj->adDatumParams[4] = CPLAtof(papszNextField[4]);
            papszNextField += 5;
        }
    }
    else if (nDatum != 999 && nDatum != 9999)
    {
        // Find the datum, and collect its parameters if possible.
        const MapInfoDatumInfo *psDatumInfo = nullptr;

        int iDatum = 0;  // Used after for.
        for (; asDatumInfoList[iDatum].nMapInfoDatumID != -1; iDatum++)
        {
            if (asDatumInfoList[iDatum].nMapInfoDatumID == nDatum)
            {
                psDatumInfo = asDatumInfoList + iDatum;
                break;
            }
        }

        if (asDatumInfoList[iDatum].nMapInfoDatumID == -1)
        {
            // Use WGS84.
            psDatumInfo = asDatumInfoList + 0;
        }

        if (psDatumInfo != nullptr)
        {
            psProj->nEllipsoidId = static_cast<GByte>(psDatumInfo->nEllipsoid);
            psProj->nDatumId =
                static_cast<GInt16>(psDatumInfo->nMapInfoDatumID);
            psProj->dDatumShiftX = psDatumInfo->dfShiftX;
            psProj->dDatumShiftY = psDatumInfo->dfShiftY;
            psProj->dDatumShiftZ = psDatumInfo->dfShiftZ;
            psProj->adDatumParams[0] = psDatumInfo->dfDatumParm0;
            psProj->adDatumParams[1] = psDatumInfo->dfDatumParm1;
            psProj->adDatumParams[2] = psDatumInfo->dfDatumParm2;
            psProj->adDatumParams[3] = psDatumInfo->dfDatumParm3;
            psProj->adDatumParams[4] = psDatumInfo->dfDatumParm4;
        }
    }

    // Fetch the units string.
    if (CSLCount(papszNextField) > 0)
    {
        if (isdigit(static_cast<unsigned char>(papszNextField[0][0])))
        {
            psProj->nUnitsId = static_cast<GByte>(atoi(papszNextField[0]));
        }
        else
        {
            psProj->nUnitsId =
                static_cast<GByte>(TABUnitIdFromString(papszNextField[0]));
        }
        papszNextField++;
    }

    // Finally the projection parameters.
    for (int iParam = 0; iParam < 7 && CSLCount(papszNextField) > 0; iParam++)
    {
        psProj->adProjParams[iParam] = CPLAtof(papszNextField[0]);
        papszNextField++;
    }

    CSLDestroy(papszFields);

    return 0;
}

/*! @endcond */
