LCOV - code coverage report
Current view: top level - ogr - ogr2kmlgeometry.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 115 132 87.1 %
Date: 2026-03-31 20:20:04 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  KML Driver
       4             :  * Purpose:  Implementation of OGR -> KML geometries writer.
       5             :  * Author:   Christopher Condit, condit@sdsc.edu
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2006, Christopher Condit
       9             :  * Copyright (c) 2007-2010, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "ogr_api.h"
      16             : 
      17             : #include <stddef.h>
      18             : #include <stdio.h>
      19             : #include <string.h>
      20             : #include <algorithm>
      21             : #include <cmath>
      22             : 
      23             : #include "cpl_conv.h"
      24             : #include "cpl_error.h"
      25             : #include "cpl_minixml.h"
      26             : #include "ogr_core.h"
      27             : #include "ogr_geometry.h"
      28             : #include "ogr_p.h"
      29             : 
      30             : /************************************************************************/
      31             : /*                         MakeKMLCoordinate()                          */
      32             : /************************************************************************/
      33             : 
      34         291 : static bool MakeKMLCoordinate(char *pszTarget, size_t /* nTargetLen*/, double x,
      35             :                               double y, double z, bool b3D)
      36             : 
      37             : {
      38         291 :     constexpr double EPSILON = 1e-8;
      39             : 
      40         291 :     if (y < -90 || y > 90)
      41             :     {
      42           5 :         if (y > 90 && y < 90 + EPSILON)
      43             :         {
      44           0 :             y = 90;
      45             :         }
      46           5 :         else if (y > -90 - EPSILON && y < -90)
      47             :         {
      48           0 :             y = -90;
      49             :         }
      50             :         else
      51             :         {
      52           5 :             CPLError(CE_Failure, CPLE_AppDefined,
      53             :                      "Latitude %f is invalid. Valid range is [-90,90].", y);
      54           5 :             return false;
      55             :         }
      56             :     }
      57             : 
      58         286 :     if (x < -180 || x > 180)
      59             :     {
      60           0 :         if (x > 180 && x < 180 + EPSILON)
      61             :         {
      62           0 :             x = 180;
      63             :         }
      64           0 :         else if (x > -180 - EPSILON && x < -180)
      65             :         {
      66           0 :             x = -180;
      67             :         }
      68             :         else
      69             :         {
      70             :             static bool bFirstWarning = true;
      71           0 :             if (bFirstWarning)
      72             :             {
      73           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
      74             :                          "Longitude %f has been modified to fit into "
      75             :                          "range [-180,180]. This warning will not be "
      76             :                          "issued any more",
      77             :                          x);
      78           0 :                 bFirstWarning = false;
      79             :             }
      80             : 
      81             :             // Trash drastically non-sensical values.
      82           0 :             if (x > 1.0e6 || x < -1.0e6 || std::isnan(x))
      83             :             {
      84           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
      85             :                          "Longitude %lf is unreasonable.", x);
      86           0 :                 return false;
      87             :             }
      88             : 
      89           0 :             if (x > 180)
      90           0 :                 x -= (static_cast<int>((x + 180) / 360) * 360);
      91           0 :             else if (x < -180)
      92           0 :                 x += (static_cast<int>(180 - x) / 360) * 360;
      93             :         }
      94             :     }
      95             : 
      96         286 :     OGRMakeWktCoordinate(pszTarget, x, y, z, b3D ? 3 : 2);
      97        3912 :     while (*pszTarget != '\0')
      98             :     {
      99        3626 :         if (*pszTarget == ' ')
     100         365 :             *pszTarget = ',';
     101        3626 :         pszTarget++;
     102             :     }
     103             : 
     104         286 :     return true;
     105             : }
     106             : 
     107             : /************************************************************************/
     108             : /*                            _GrowBuffer()                             */
     109             : /************************************************************************/
     110             : 
     111        1223 : static void _GrowBuffer(size_t nNeeded, char **ppszText, size_t *pnMaxLength)
     112             : 
     113             : {
     114        1223 :     if (nNeeded + 1 >= *pnMaxLength)
     115             :     {
     116          42 :         *pnMaxLength = std::max(*pnMaxLength * 2, nNeeded + 1);
     117          42 :         *ppszText = static_cast<char *>(CPLRealloc(*ppszText, *pnMaxLength));
     118             :     }
     119        1223 : }
     120             : 
     121             : /************************************************************************/
     122             : /*                            AppendString()                            */
     123             : /************************************************************************/
     124             : 
     125        1223 : static void AppendString(char **ppszText, size_t *pnLength, size_t *pnMaxLength,
     126             :                          const char *pszTextToAppend)
     127             : 
     128             : {
     129        1223 :     _GrowBuffer(*pnLength + strlen(pszTextToAppend) + 1, ppszText, pnMaxLength);
     130             : 
     131        1223 :     strcat(*ppszText + *pnLength, pszTextToAppend);
     132        1223 :     *pnLength += strlen(*ppszText + *pnLength);
     133        1223 : }
     134             : 
     135             : /************************************************************************/
     136             : /*                        AppendCoordinateList()                        */
     137             : /************************************************************************/
     138             : 
     139          96 : static bool AppendCoordinateList(const OGRLineString *poLine, char **ppszText,
     140             :                                  size_t *pnLength, size_t *pnMaxLength)
     141             : 
     142             : {
     143          96 :     char szCoordinate[256] = {0};
     144          96 :     const bool b3D = CPL_TO_BOOL(wkbHasZ(poLine->getGeometryType()));
     145             : 
     146          96 :     AppendString(ppszText, pnLength, pnMaxLength, "<coordinates>");
     147             : 
     148         349 :     for (int iPoint = 0; iPoint < poLine->getNumPoints(); iPoint++)
     149             :     {
     150         256 :         if (!MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
     151             :                                poLine->getX(iPoint), poLine->getY(iPoint),
     152             :                                poLine->getZ(iPoint), b3D))
     153             :         {
     154           3 :             return false;
     155             :         }
     156             : 
     157         253 :         if (iPoint > 0)
     158         161 :             AppendString(ppszText, pnLength, pnMaxLength, " ");
     159         253 :         AppendString(ppszText, pnLength, pnMaxLength, szCoordinate);
     160             :     }
     161             : 
     162          93 :     AppendString(ppszText, pnLength, pnMaxLength, "</coordinates>");
     163             : 
     164          93 :     return true;
     165             : }
     166             : 
     167             : /************************************************************************/
     168             : /*                       OGR2KMLGeometryAppend()                        */
     169             : /************************************************************************/
     170             : 
     171         196 : static bool OGR2KMLGeometryAppend(const OGRGeometry *poGeometry,
     172             :                                   char **ppszText, size_t *pnLength,
     173             :                                   size_t *pnMaxLength, char *szAltitudeMode)
     174             : 
     175             : {
     176         196 :     const auto eGeomType = poGeometry->getGeometryType();
     177             : 
     178             :     /* -------------------------------------------------------------------- */
     179             :     /*      2D Point                                                        */
     180             :     /* -------------------------------------------------------------------- */
     181         196 :     if (eGeomType == wkbPoint)
     182             :     {
     183          23 :         const OGRPoint *poPoint = poGeometry->toPoint();
     184             : 
     185          23 :         if (poPoint->IsEmpty())
     186             :         {
     187           1 :             AppendString(ppszText, pnLength, pnMaxLength, "<Point/>");
     188             :         }
     189             :         else
     190             :         {
     191          22 :             char szCoordinate[256] = {0};
     192          22 :             if (!MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
     193             :                                    poPoint->getX(), poPoint->getY(), 0.0,
     194             :                                    false))
     195             :             {
     196           1 :                 return false;
     197             :             }
     198             : 
     199          21 :             AppendString(ppszText, pnLength, pnMaxLength,
     200             :                          "<Point><coordinates>");
     201          21 :             AppendString(ppszText, pnLength, pnMaxLength, szCoordinate);
     202          21 :             AppendString(ppszText, pnLength, pnMaxLength,
     203             :                          "</coordinates></Point>");
     204             :         }
     205             :     }
     206             :     /* -------------------------------------------------------------------- */
     207             :     /*      3D Point                                                        */
     208             :     /* -------------------------------------------------------------------- */
     209         173 :     else if (eGeomType == wkbPoint25D)
     210             :     {
     211          13 :         char szCoordinate[256] = {0};
     212          13 :         const OGRPoint *poPoint = poGeometry->toPoint();
     213             : 
     214          13 :         if (!MakeKMLCoordinate(szCoordinate, sizeof(szCoordinate),
     215             :                                poPoint->getX(), poPoint->getY(),
     216             :                                poPoint->getZ(), true))
     217             :         {
     218           1 :             return false;
     219             :         }
     220             : 
     221          12 :         AppendString(ppszText, pnLength, pnMaxLength, "<Point>");
     222          12 :         if (szAltitudeMode)
     223          12 :             AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
     224          12 :         AppendString(ppszText, pnLength, pnMaxLength, "<coordinates>");
     225          12 :         AppendString(ppszText, pnLength, pnMaxLength, szCoordinate);
     226          12 :         AppendString(ppszText, pnLength, pnMaxLength, "</coordinates></Point>");
     227             :     }
     228             :     /* -------------------------------------------------------------------- */
     229             :     /*      LineString and LinearRing                                       */
     230             :     /* -------------------------------------------------------------------- */
     231         160 :     else if (eGeomType == wkbLineString || eGeomType == wkbLineString25D)
     232             :     {
     233          96 :         const bool bRing = EQUAL(poGeometry->getGeometryName(), "LINEARRING");
     234             : 
     235          96 :         if (bRing)
     236          32 :             AppendString(ppszText, pnLength, pnMaxLength, "<LinearRing>");
     237             :         else
     238          64 :             AppendString(ppszText, pnLength, pnMaxLength, "<LineString>");
     239             : 
     240          96 :         if (nullptr != szAltitudeMode)
     241             :         {
     242          96 :             AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
     243             :         }
     244             : 
     245          96 :         if (!AppendCoordinateList(poGeometry->toLineString(), ppszText,
     246             :                                   pnLength, pnMaxLength))
     247             :         {
     248           3 :             return false;
     249             :         }
     250             : 
     251          93 :         if (bRing)
     252          30 :             AppendString(ppszText, pnLength, pnMaxLength, "</LinearRing>");
     253             :         else
     254          93 :             AppendString(ppszText, pnLength, pnMaxLength, "</LineString>");
     255             :     }
     256             : 
     257             :     /* -------------------------------------------------------------------- */
     258             :     /*      Polygon                                                         */
     259             :     /* -------------------------------------------------------------------- */
     260          64 :     else if (eGeomType == wkbPolygon || eGeomType == wkbPolygon25D)
     261             :     {
     262          28 :         const OGRPolygon *poPolygon = poGeometry->toPolygon();
     263             : 
     264          28 :         AppendString(ppszText, pnLength, pnMaxLength, "<Polygon>");
     265             : 
     266          28 :         if (nullptr != szAltitudeMode)
     267             :         {
     268          28 :             AppendString(ppszText, pnLength, pnMaxLength, szAltitudeMode);
     269             :         }
     270             : 
     271          28 :         if (poPolygon->getExteriorRing() != nullptr)
     272             :         {
     273          27 :             AppendString(ppszText, pnLength, pnMaxLength, "<outerBoundaryIs>");
     274             : 
     275          27 :             if (!OGR2KMLGeometryAppend(poPolygon->getExteriorRing(), ppszText,
     276             :                                        pnLength, pnMaxLength, szAltitudeMode))
     277             :             {
     278           1 :                 return false;
     279             :             }
     280          26 :             AppendString(ppszText, pnLength, pnMaxLength, "</outerBoundaryIs>");
     281             :         }
     282             : 
     283          31 :         for (int iRing = 0; iRing < poPolygon->getNumInteriorRings(); iRing++)
     284             :         {
     285           5 :             const OGRLinearRing *poRing = poPolygon->getInteriorRing(iRing);
     286             : 
     287           5 :             AppendString(ppszText, pnLength, pnMaxLength, "<innerBoundaryIs>");
     288             : 
     289           5 :             if (!OGR2KMLGeometryAppend(poRing, ppszText, pnLength, pnMaxLength,
     290             :                                        szAltitudeMode))
     291             :             {
     292           1 :                 return false;
     293             :             }
     294           4 :             AppendString(ppszText, pnLength, pnMaxLength, "</innerBoundaryIs>");
     295             :         }
     296             : 
     297          26 :         AppendString(ppszText, pnLength, pnMaxLength, "</Polygon>");
     298             :     }
     299             : 
     300             :     /* -------------------------------------------------------------------- */
     301             :     /*      MultiPolygon                                                    */
     302             :     /* -------------------------------------------------------------------- */
     303          36 :     else if (wkbFlatten(eGeomType) == wkbMultiPolygon ||
     304          28 :              wkbFlatten(eGeomType) == wkbMultiLineString ||
     305          75 :              wkbFlatten(eGeomType) == wkbMultiPoint ||
     306          11 :              wkbFlatten(eGeomType) == wkbGeometryCollection)
     307             :     {
     308          34 :         const OGRGeometryCollection *poGC = poGeometry->toGeometryCollection();
     309             : 
     310          34 :         AppendString(ppszText, pnLength, pnMaxLength, "<MultiGeometry>");
     311             : 
     312          82 :         for (const auto *poMember : *poGC)
     313             :         {
     314          49 :             if (!OGR2KMLGeometryAppend(poMember, ppszText, pnLength,
     315             :                                        pnMaxLength, szAltitudeMode))
     316             :             {
     317           1 :                 return false;
     318             :             }
     319             :         }
     320          33 :         AppendString(ppszText, pnLength, pnMaxLength, "</MultiGeometry>");
     321             :     }
     322             :     else
     323             :     {
     324           2 :         CPLError(CE_Failure, CPLE_NotSupported,
     325             :                  "Unsupported geometry type in KML: %s",
     326             :                  OGRGeometryTypeToName(eGeomType));
     327           2 :         return false;
     328             :     }
     329             : 
     330         186 :     return true;
     331             : }
     332             : 
     333             : /************************************************************************/
     334             : /*                         OGR_G_ExportToKML()                          */
     335             : /************************************************************************/
     336             : 
     337             : /**
     338             :  * \brief Convert a geometry into KML format.
     339             :  *
     340             :  * The returned string should be freed with CPLFree() when no longer required.
     341             :  *
     342             :  * This method is the same as the C++ method OGRGeometry::exportToKML().
     343             :  *
     344             :  * @param hGeometry handle to the geometry.
     345             :  * @param pszAltitudeMode value to write in altitudeMode element, or NULL.
     346             :  * @return A KML fragment or NULL in case of error.
     347             :  */
     348             : 
     349         115 : char *OGR_G_ExportToKML(OGRGeometryH hGeometry, const char *pszAltitudeMode)
     350             : {
     351             :     char szAltitudeMode[128];
     352             : 
     353         115 :     if (hGeometry == nullptr)
     354           0 :         return nullptr;
     355             : 
     356         115 :     size_t nMaxLength = 128;
     357         115 :     char *pszText = static_cast<char *>(CPLMalloc(nMaxLength));
     358         115 :     pszText[0] = '\0';
     359             : 
     360         115 :     if (pszAltitudeMode &&
     361           1 :         strlen(pszAltitudeMode) < sizeof(szAltitudeMode) - (29 + 1))
     362             :     {
     363           1 :         snprintf(szAltitudeMode, sizeof(szAltitudeMode),
     364             :                  "<altitudeMode>%s</altitudeMode>", pszAltitudeMode);
     365             :     }
     366             :     else
     367             :     {
     368         114 :         szAltitudeMode[0] = 0;
     369             :     }
     370             : 
     371         115 :     size_t nLength = 0;
     372         115 :     if (!OGR2KMLGeometryAppend(OGRGeometry::FromHandle(hGeometry), &pszText,
     373             :                                &nLength, &nMaxLength, szAltitudeMode))
     374             :     {
     375           7 :         CPLFree(pszText);
     376           7 :         return nullptr;
     377             :     }
     378             : 
     379         108 :     return pszText;
     380             : }

Generated by: LCOV version 1.14