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 : }
|