Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: KML Translator
4 : * Purpose: Implements OGRLIBKMLDriver
5 : * Author: Brian Case, rush at winkey dot org
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2010, Brian Case
9 : * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : *****************************************************************************/
13 :
14 : #include "libkml_headers.h"
15 : #include "ogrlibkmlfeature.h"
16 :
17 : #include "gdal.h"
18 : #include "ogr_geometry.h"
19 : #include "ogr_libkml.h"
20 : #include "ogrlibkmlfield.h"
21 : #include "ogrlibkmlfeaturestyle.h"
22 : #include "ogrlibkmlgeometry.h"
23 : #include "ogrsf_frmts.h"
24 :
25 : using kmldom::AliasPtr;
26 : using kmldom::CameraPtr;
27 : using kmldom::ElementPtr;
28 : using kmldom::FeaturePtr;
29 : using kmldom::GeometryPtr;
30 : using kmldom::GroundOverlayPtr;
31 : using kmldom::IconPtr;
32 : using kmldom::ImagePyramidPtr;
33 : using kmldom::KmlFactory;
34 : using kmldom::LinkPtr;
35 : using kmldom::LocationPtr;
36 : using kmldom::ModelPtr;
37 : using kmldom::NetworkLinkPtr;
38 : using kmldom::OrientationPtr;
39 : using kmldom::PhotoOverlayPtr;
40 : using kmldom::PlacemarkPtr;
41 : using kmldom::ResourceMapPtr;
42 : using kmldom::ScalePtr;
43 : using kmldom::ViewVolumePtr;
44 :
45 203 : static CameraPtr feat2kmlcamera(const struct fieldconfig &oFC, int iHeading,
46 : int iTilt, int iRoll, OGRFeature *poOgrFeat,
47 : KmlFactory *poKmlFactory)
48 : {
49 : const int iCameraLongitudeField =
50 203 : poOgrFeat->GetFieldIndex(oFC.camera_longitude_field);
51 : const int iCameraLatitudeField =
52 203 : poOgrFeat->GetFieldIndex(oFC.camera_latitude_field);
53 : const int iCameraAltitudeField =
54 203 : poOgrFeat->GetFieldIndex(oFC.camera_altitude_field);
55 : const int iCameraAltitudeModeField =
56 203 : poOgrFeat->GetFieldIndex(oFC.camera_altitudemode_field);
57 :
58 : const bool bNeedCamera =
59 2 : iCameraLongitudeField >= 0 &&
60 2 : poOgrFeat->IsFieldSetAndNotNull(iCameraLongitudeField) &&
61 2 : iCameraLatitudeField >= 0 &&
62 207 : poOgrFeat->IsFieldSetAndNotNull(iCameraLatitudeField) &&
63 2 : ((iHeading >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHeading)) ||
64 0 : (iTilt >= 0 && poOgrFeat->IsFieldSetAndNotNull(iTilt)) ||
65 0 : (iRoll >= 0 && poOgrFeat->IsFieldSetAndNotNull(iRoll)));
66 :
67 203 : if (!bNeedCamera)
68 201 : return nullptr;
69 :
70 4 : CameraPtr const camera = poKmlFactory->CreateCamera();
71 2 : camera->set_latitude(poOgrFeat->GetFieldAsDouble(iCameraLatitudeField));
72 2 : camera->set_longitude(poOgrFeat->GetFieldAsDouble(iCameraLongitudeField));
73 2 : int isGX = FALSE;
74 :
75 4 : if (iCameraAltitudeModeField >= 0 &&
76 2 : poOgrFeat->IsFieldSetAndNotNull(iCameraAltitudeModeField))
77 : {
78 2 : const int nAltitudeMode = kmlAltitudeModeFromString(
79 : poOgrFeat->GetFieldAsString(iCameraAltitudeModeField), isGX);
80 2 : camera->set_altitudemode(nAltitudeMode);
81 : }
82 0 : else if (CPLTestBool(
83 : CPLGetConfigOption("LIBKML_STRICT_COMPLIANCE", "TRUE")))
84 : {
85 0 : CPLError(CE_Warning, CPLE_AppDefined,
86 : "Camera should define altitudeMode != 'clampToGround'");
87 : }
88 :
89 4 : if (iCameraAltitudeField >= 0 &&
90 2 : poOgrFeat->IsFieldSetAndNotNull(iCameraAltitudeField))
91 : {
92 2 : camera->set_altitude(poOgrFeat->GetFieldAsDouble(iCameraAltitudeField));
93 : }
94 0 : else if (CPLTestBool(
95 : CPLGetConfigOption("LIBKML_STRICT_COMPLIANCE", "TRUE")))
96 : {
97 0 : CPLError(CE_Warning, CPLE_AppDefined,
98 : "Camera should have an altitude/Z");
99 0 : camera->set_altitude(0.0);
100 : }
101 :
102 2 : if (iHeading >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHeading))
103 2 : camera->set_heading(poOgrFeat->GetFieldAsDouble(iHeading));
104 2 : if (iTilt >= 0 && poOgrFeat->IsFieldSetAndNotNull(iTilt))
105 2 : camera->set_tilt(poOgrFeat->GetFieldAsDouble(iTilt));
106 2 : if (iRoll >= 0 && poOgrFeat->IsFieldSetAndNotNull(iRoll))
107 2 : camera->set_roll(poOgrFeat->GetFieldAsDouble(iRoll));
108 :
109 2 : return camera;
110 : }
111 :
112 : /************************************************************************/
113 : /* OGRLIBKMLReplaceXYLevelInURL() */
114 : /************************************************************************/
115 :
116 0 : static CPLString OGRLIBKMLReplaceLevelXYInURL(const char *pszURL, int level,
117 : int x, int y)
118 : {
119 0 : CPLString osRet(pszURL);
120 0 : size_t nPos = osRet.find("$[level]");
121 0 : osRet = osRet.substr(0, nPos) + CPLSPrintf("%d", level) +
122 0 : osRet.substr(nPos + strlen("$[level]"));
123 :
124 0 : nPos = osRet.find("$[x]");
125 0 : osRet = osRet.substr(0, nPos) + CPLSPrintf("%d", x) +
126 0 : osRet.substr(nPos + strlen("$[x]"));
127 :
128 0 : nPos = osRet.find("$[y]");
129 0 : osRet = osRet.substr(0, nPos) + CPLSPrintf("%d", y) +
130 0 : osRet.substr(nPos + strlen("$[y]"));
131 :
132 0 : return osRet;
133 : }
134 :
135 : /************************************************************************/
136 : /* IsPowerOf2 */
137 : /************************************************************************/
138 :
139 1 : static bool IsPowerOf2(int nVal)
140 : {
141 1 : if (nVal < 1)
142 0 : return false;
143 :
144 1 : const unsigned int nTmp = static_cast<unsigned int>(nVal);
145 :
146 1 : return (nTmp & (nTmp - 1)) == 0;
147 : }
148 :
149 : /************************************************************************/
150 : /* OGRLIBKMLGetMaxDimensions() */
151 : /************************************************************************/
152 :
153 0 : static void OGRLIBKMLGetMaxDimensions(const char *pszURL, int nTileSize,
154 : int *panMaxWidth, int *panMaxHeight)
155 : {
156 : VSIStatBufL sStat;
157 0 : int nMaxLevel = 0;
158 0 : *panMaxWidth = 0;
159 0 : *panMaxHeight = 0;
160 : while (true)
161 : {
162 0 : CPLString osURL = OGRLIBKMLReplaceLevelXYInURL(pszURL, nMaxLevel, 0, 0);
163 0 : if (strstr(osURL, ".kmz/"))
164 0 : osURL = "/vsizip/" + osURL;
165 0 : if (VSIStatL(osURL, &sStat) == 0)
166 0 : nMaxLevel++;
167 : else
168 : {
169 0 : if (nMaxLevel == 0)
170 0 : return;
171 0 : break;
172 : }
173 0 : }
174 0 : nMaxLevel--;
175 :
176 : {
177 0 : int i = 0; // Used after for.
178 0 : for (;; i++)
179 : {
180 : CPLString osURL =
181 0 : OGRLIBKMLReplaceLevelXYInURL(pszURL, nMaxLevel, i + 1, 0);
182 0 : if (strstr(osURL, ".kmz/"))
183 0 : osURL = "/vsizip/" + osURL;
184 0 : if (VSIStatL(osURL, &sStat) != 0)
185 0 : break;
186 0 : }
187 0 : *panMaxWidth = (i + 1) * nTileSize;
188 : }
189 :
190 0 : int i = 0; // Used after for.
191 0 : for (;; i++)
192 : {
193 : CPLString osURL =
194 0 : OGRLIBKMLReplaceLevelXYInURL(pszURL, nMaxLevel, 0, i + 1);
195 0 : if (strstr(osURL, ".kmz/"))
196 0 : osURL = "/vsizip/" + osURL;
197 0 : if (VSIStatL(osURL, &sStat) != 0)
198 0 : break;
199 0 : }
200 0 : *panMaxHeight = (i + 1) * nTileSize;
201 : }
202 :
203 : /************************************************************************/
204 : /* feat2kml() */
205 : /************************************************************************/
206 :
207 218 : FeaturePtr feat2kml(OGRLIBKMLDataSource *poOgrDS, OGRLIBKMLLayer *poOgrLayer,
208 : OGRFeature *poOgrFeat, KmlFactory *poKmlFactory,
209 : int bUseSimpleField)
210 : {
211 436 : FeaturePtr poKmlFeature = nullptr;
212 218 : const auto &oFC = poOgrLayer->GetFieldConfig();
213 :
214 : /***** geometry *****/
215 218 : OGRGeometry *poOgrGeom = poOgrFeat->GetGeometryRef();
216 218 : const int iHeading = poOgrFeat->GetFieldIndex(oFC.headingfield);
217 218 : const int iTilt = poOgrFeat->GetFieldIndex(oFC.tiltfield);
218 218 : const int iRoll = poOgrFeat->GetFieldIndex(oFC.rollfield);
219 218 : const int iModel = poOgrFeat->GetFieldIndex(oFC.modelfield);
220 218 : const int iNetworkLink = poOgrFeat->GetFieldIndex(oFC.networklinkfield);
221 218 : const int iPhotoOverlay = poOgrFeat->GetFieldIndex(oFC.photooverlayfield);
222 436 : CameraPtr camera = nullptr;
223 :
224 : // PhotoOverlay.
225 2 : if (iPhotoOverlay >= 0 && poOgrFeat->IsFieldSetAndNotNull(iPhotoOverlay) &&
226 2 : poOgrGeom != nullptr && !poOgrGeom->IsEmpty() &&
227 222 : wkbFlatten(poOgrGeom->getGeometryType()) == wkbPoint &&
228 220 : (camera = feat2kmlcamera(oFC, iHeading, iTilt, iRoll, poOgrFeat,
229 2 : poKmlFactory)))
230 : {
231 2 : const int iLeftFovField = poOgrFeat->GetFieldIndex(oFC.leftfovfield);
232 2 : const int iRightFovField = poOgrFeat->GetFieldIndex(oFC.rightfovfield);
233 : const int iBottomFovField =
234 2 : poOgrFeat->GetFieldIndex(oFC.bottomfovfield);
235 2 : const int iTopFovField = poOgrFeat->GetFieldIndex(oFC.topfovfield);
236 2 : const int iNearField = poOgrFeat->GetFieldIndex(oFC.nearfield);
237 :
238 2 : const char *pszURL = poOgrFeat->GetFieldAsString(iPhotoOverlay);
239 : const int iImagePyramidTileSize =
240 2 : poOgrFeat->GetFieldIndex(oFC.imagepyramid_tilesize_field);
241 : const int iImagePyramidMaxWidth =
242 2 : poOgrFeat->GetFieldIndex(oFC.imagepyramid_maxwidth_field);
243 : const int iImagePyramidMaxHeight =
244 2 : poOgrFeat->GetFieldIndex(oFC.imagepyramid_maxheight_field);
245 : const int iImagePyramidGridOrigin =
246 2 : poOgrFeat->GetFieldIndex(oFC.imagepyramid_gridorigin_field);
247 :
248 2 : int nTileSize = 0;
249 2 : int nMaxWidth = 0;
250 2 : int nMaxHeight = 0;
251 2 : bool bIsTiledPhotoOverlay = false;
252 2 : bool bGridOriginIsUpperLeft = true;
253 : // OGC KML Abstract Test Case (ATC) 52 and 62
254 2 : if (strstr(pszURL, "$[x]") && strstr(pszURL, "$[y]") &&
255 1 : strstr(pszURL, "$[level]"))
256 : {
257 1 : bIsTiledPhotoOverlay = true;
258 1 : bool bErrorEmitted = false;
259 2 : if (iImagePyramidTileSize < 0 ||
260 1 : !poOgrFeat->IsFieldSetAndNotNull(iImagePyramidTileSize))
261 : {
262 0 : CPLDebug("LIBKML",
263 : "Missing ImagePyramid tileSize. Computing it");
264 0 : CPLString osURL = OGRLIBKMLReplaceLevelXYInURL(pszURL, 0, 0, 0);
265 0 : if (strstr(osURL, ".kmz/"))
266 0 : osURL = "/vsizip/" + osURL;
267 0 : GDALDatasetH hDS = GDALDataset::ToHandle(GDALDataset::Open(
268 : osURL, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
269 0 : if (hDS != nullptr)
270 : {
271 0 : nTileSize = GDALGetRasterXSize(hDS);
272 0 : if (nTileSize != GDALGetRasterYSize(hDS))
273 : {
274 0 : CPLError(CE_Failure, CPLE_AppDefined,
275 : "Non square tile : %dx%d",
276 : GDALGetRasterXSize(hDS),
277 : GDALGetRasterYSize(hDS));
278 0 : nTileSize = 0;
279 0 : bErrorEmitted = true;
280 : }
281 0 : GDALClose(hDS);
282 : }
283 : else
284 : {
285 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
286 : osURL.c_str());
287 0 : bErrorEmitted = true;
288 : }
289 : }
290 : else
291 : {
292 1 : nTileSize = poOgrFeat->GetFieldAsInteger(iImagePyramidTileSize);
293 : }
294 1 : if (!bErrorEmitted && (nTileSize <= 1 || !IsPowerOf2(nTileSize)))
295 : {
296 0 : CPLError(CE_Failure, CPLE_AppDefined,
297 : "Tile size is not a power of two: %d", nTileSize);
298 0 : nTileSize = 0;
299 : }
300 :
301 1 : if (nTileSize > 0)
302 : {
303 2 : if (iImagePyramidMaxWidth < 0 ||
304 1 : !poOgrFeat->IsFieldSetAndNotNull(iImagePyramidMaxWidth) ||
305 2 : iImagePyramidMaxHeight < 0 ||
306 1 : !poOgrFeat->IsFieldSetAndNotNull(iImagePyramidMaxHeight))
307 : {
308 0 : CPLDebug("LIBKML",
309 : "Missing ImagePyramid maxWidth and/or maxHeight. "
310 : "Computing it");
311 0 : OGRLIBKMLGetMaxDimensions(pszURL, nTileSize, &nMaxWidth,
312 : &nMaxHeight);
313 : }
314 : else
315 : {
316 1 : nMaxWidth =
317 1 : poOgrFeat->GetFieldAsInteger(iImagePyramidMaxWidth);
318 1 : nMaxHeight =
319 1 : poOgrFeat->GetFieldAsInteger(iImagePyramidMaxHeight);
320 : }
321 :
322 1 : if (nMaxWidth <= 0 || nMaxHeight <= 0)
323 : {
324 0 : CPLError(
325 : CE_Failure, CPLE_AppDefined,
326 : "Cannot generate PhotoOverlay object since there are "
327 : "missing information to generate ImagePyramid element");
328 : }
329 : }
330 :
331 1 : if (iImagePyramidGridOrigin >= 0 &&
332 0 : poOgrFeat->IsFieldSetAndNotNull(iImagePyramidGridOrigin))
333 : {
334 : const char *pszGridOrigin =
335 0 : poOgrFeat->GetFieldAsString(iImagePyramidGridOrigin);
336 0 : if (EQUAL(pszGridOrigin, "UpperLeft"))
337 : {
338 0 : bGridOriginIsUpperLeft = true;
339 : }
340 0 : else if (EQUAL(pszGridOrigin, "BottomLeft"))
341 : {
342 0 : bGridOriginIsUpperLeft = false;
343 : }
344 : else
345 : {
346 0 : CPLError(
347 : CE_Failure, CPLE_AppDefined,
348 : "Unhandled value for imagepyramid_gridorigin : %s. "
349 : "Assuming UpperLeft",
350 : pszGridOrigin);
351 : }
352 1 : }
353 : }
354 : else
355 : {
356 2 : if ((iImagePyramidTileSize >= 0 &&
357 1 : poOgrFeat->IsFieldSetAndNotNull(iImagePyramidTileSize)) ||
358 1 : (iImagePyramidMaxWidth >= 0 &&
359 1 : poOgrFeat->IsFieldSetAndNotNull(iImagePyramidMaxWidth)) ||
360 1 : (iImagePyramidMaxHeight >= 0 &&
361 2 : poOgrFeat->IsFieldSetAndNotNull(iImagePyramidMaxHeight)) ||
362 0 : (iImagePyramidGridOrigin >= 0 &&
363 0 : poOgrFeat->IsFieldSetAndNotNull(iImagePyramidGridOrigin)))
364 : {
365 0 : CPLError(
366 : CE_Warning, CPLE_AppDefined,
367 : "Ignoring any ImagePyramid information since the URL does "
368 : "not include $[x] and/or $[y] and/or $[level]");
369 : }
370 : }
371 :
372 : // OGC KML Abstract Test Case (ATC) 19 & 35.
373 2 : double dfNear = 0.0;
374 :
375 1 : if ((!bIsTiledPhotoOverlay ||
376 2 : (nTileSize > 0 && nMaxWidth > 0 && nMaxHeight > 0)) &&
377 2 : iLeftFovField >= 0 &&
378 2 : poOgrFeat->IsFieldSetAndNotNull(iLeftFovField) &&
379 2 : iRightFovField >= 0 &&
380 2 : poOgrFeat->IsFieldSetAndNotNull(iRightFovField) &&
381 2 : iBottomFovField >= 0 &&
382 2 : poOgrFeat->IsFieldSetAndNotNull(iBottomFovField) &&
383 2 : iTopFovField >= 0 &&
384 6 : poOgrFeat->IsFieldSetAndNotNull(iTopFovField) && iNearField >= 0 &&
385 2 : (dfNear = poOgrFeat->GetFieldAsDouble(iNearField)) > 0)
386 : {
387 : const PhotoOverlayPtr poKmlPhotoOverlay =
388 2 : poKmlFactory->CreatePhotoOverlay();
389 2 : poKmlFeature = poKmlPhotoOverlay;
390 :
391 2 : const IconPtr poKmlIcon = poKmlFactory->CreateIcon();
392 2 : poKmlPhotoOverlay->set_icon(poKmlIcon);
393 2 : poKmlIcon->set_href(pszURL);
394 :
395 : const ViewVolumePtr poKmlViewVolume =
396 2 : poKmlFactory->CreateViewVolume();
397 2 : poKmlPhotoOverlay->set_viewvolume(poKmlViewVolume);
398 :
399 2 : const double dfLeftFov = poOgrFeat->GetFieldAsDouble(iLeftFovField);
400 : const double dfRightFov =
401 2 : poOgrFeat->GetFieldAsDouble(iRightFovField);
402 : const double dfBottomFov =
403 2 : poOgrFeat->GetFieldAsDouble(iBottomFovField);
404 2 : const double dfTopFov = poOgrFeat->GetFieldAsDouble(iTopFovField);
405 :
406 2 : poKmlViewVolume->set_leftfov(dfLeftFov);
407 2 : poKmlViewVolume->set_rightfov(dfRightFov);
408 2 : poKmlViewVolume->set_bottomfov(dfBottomFov);
409 2 : poKmlViewVolume->set_topfov(dfTopFov);
410 2 : poKmlViewVolume->set_near(dfNear);
411 :
412 2 : if (bIsTiledPhotoOverlay)
413 : {
414 : const ImagePyramidPtr poKmlImagePyramid =
415 2 : poKmlFactory->CreateImagePyramid();
416 1 : poKmlPhotoOverlay->set_imagepyramid(poKmlImagePyramid);
417 :
418 1 : poKmlImagePyramid->set_tilesize(nTileSize);
419 1 : poKmlImagePyramid->set_maxwidth(nMaxWidth);
420 1 : poKmlImagePyramid->set_maxheight(nMaxHeight);
421 1 : poKmlImagePyramid->set_gridorigin(
422 : bGridOriginIsUpperLeft ? kmldom::GRIDORIGIN_UPPERLEFT
423 : : kmldom::GRIDORIGIN_LOWERLEFT);
424 : }
425 :
426 : const int iPhotoOverlayShapeField =
427 2 : poOgrFeat->GetFieldIndex(oFC.photooverlay_shape_field);
428 4 : if (iPhotoOverlayShapeField >= 0 &&
429 2 : poOgrFeat->IsFieldSetAndNotNull(iPhotoOverlayShapeField))
430 : {
431 : const char *pszShape =
432 2 : poOgrFeat->GetFieldAsString(iPhotoOverlayShapeField);
433 2 : if (EQUAL(pszShape, "rectangle"))
434 2 : poKmlPhotoOverlay->set_shape(kmldom::SHAPE_RECTANGLE);
435 0 : else if (EQUAL(pszShape, "cylinder"))
436 0 : poKmlPhotoOverlay->set_shape(kmldom::SHAPE_CYLINDER);
437 0 : else if (EQUAL(pszShape, "sphere"))
438 0 : poKmlPhotoOverlay->set_shape(kmldom::SHAPE_SPHERE);
439 : }
440 :
441 2 : ElementPtr poKmlElement = geom2kml(poOgrGeom, -1, poKmlFactory);
442 2 : if (!poKmlElement)
443 0 : return nullptr;
444 :
445 2 : poKmlPhotoOverlay->set_point(AsPoint(std::move(poKmlElement)));
446 : }
447 : }
448 :
449 : // NetworkLink.
450 221 : if (!poKmlFeature && iNetworkLink >= 0 &&
451 3 : poOgrFeat->IsFieldSetAndNotNull(iNetworkLink))
452 : {
453 : const NetworkLinkPtr poKmlNetworkLink =
454 6 : poKmlFactory->CreateNetworkLink();
455 3 : poKmlFeature = poKmlNetworkLink;
456 :
457 : const int iRefreshVisibility =
458 3 : poOgrFeat->GetFieldIndex(oFC.networklink_refreshvisibility_field);
459 :
460 6 : if (iRefreshVisibility >= 0 &&
461 3 : poOgrFeat->IsFieldSetAndNotNull(iRefreshVisibility))
462 : {
463 2 : poKmlNetworkLink->set_refreshvisibility(
464 1 : CPL_TO_BOOL(poOgrFeat->GetFieldAsInteger(iRefreshVisibility)));
465 : }
466 :
467 : const int iFlyToView =
468 3 : poOgrFeat->GetFieldIndex(oFC.networklink_flytoview_field);
469 :
470 3 : if (iFlyToView >= 0 && poOgrFeat->IsFieldSetAndNotNull(iFlyToView))
471 2 : poKmlNetworkLink->set_flytoview(
472 1 : CPL_TO_BOOL(poOgrFeat->GetFieldAsInteger(iFlyToView)));
473 :
474 6 : const LinkPtr poKmlLink = poKmlFactory->CreateLink();
475 3 : poKmlLink->set_href(poOgrFeat->GetFieldAsString(iNetworkLink));
476 3 : poKmlNetworkLink->set_link(poKmlLink);
477 :
478 : const int iRefreshMode =
479 3 : poOgrFeat->GetFieldIndex(oFC.networklink_refreshMode_field);
480 : const int iRefreshInterval =
481 3 : poOgrFeat->GetFieldIndex(oFC.networklink_refreshInterval_field);
482 : const int iViewRefreshMode =
483 3 : poOgrFeat->GetFieldIndex(oFC.networklink_viewRefreshMode_field);
484 : const int iViewRefreshTime =
485 3 : poOgrFeat->GetFieldIndex(oFC.networklink_viewRefreshTime_field);
486 : const int iViewBoundScale =
487 3 : poOgrFeat->GetFieldIndex(oFC.networklink_viewBoundScale_field);
488 : const int iViewFormat =
489 3 : poOgrFeat->GetFieldIndex(oFC.networklink_viewFormat_field);
490 : const int iHttpQuery =
491 3 : poOgrFeat->GetFieldIndex(oFC.networklink_httpQuery_field);
492 :
493 3 : double dfRefreshInterval = 0.0;
494 6 : if (iRefreshInterval >= 0 &&
495 3 : poOgrFeat->IsFieldSetAndNotNull(iRefreshInterval))
496 : {
497 1 : dfRefreshInterval = poOgrFeat->GetFieldAsDouble(iRefreshInterval);
498 1 : if (dfRefreshInterval < 0)
499 0 : dfRefreshInterval = 0.0;
500 : }
501 :
502 3 : double dfViewRefreshTime = 0.0;
503 6 : if (iViewRefreshTime >= 0 &&
504 3 : poOgrFeat->IsFieldSetAndNotNull(iViewRefreshTime))
505 : {
506 1 : dfViewRefreshTime = poOgrFeat->GetFieldAsDouble(iViewRefreshTime);
507 1 : if (dfViewRefreshTime < 0)
508 0 : dfViewRefreshTime = 0.0;
509 : }
510 :
511 3 : if (dfRefreshInterval > 0) // ATC 51
512 1 : poKmlLink->set_refreshmode(kmldom::REFRESHMODE_ONINTERVAL);
513 4 : else if (iRefreshMode >= 0 &&
514 2 : poOgrFeat->IsFieldSetAndNotNull(iRefreshMode))
515 : {
516 : const char *const pszRefreshMode =
517 1 : poOgrFeat->GetFieldAsString(iRefreshMode);
518 1 : if (EQUAL(pszRefreshMode, "onChange"))
519 0 : poKmlLink->set_refreshmode(kmldom::REFRESHMODE_ONCHANGE);
520 1 : else if (EQUAL(pszRefreshMode, "onInterval"))
521 0 : poKmlLink->set_refreshmode(kmldom::REFRESHMODE_ONINTERVAL);
522 1 : else if (EQUAL(pszRefreshMode, "onExpire"))
523 1 : poKmlLink->set_refreshmode(kmldom::REFRESHMODE_ONEXPIRE);
524 : }
525 :
526 3 : if (dfRefreshInterval > 0) // ATC 9
527 1 : poKmlLink->set_refreshinterval(dfRefreshInterval);
528 :
529 3 : if (dfViewRefreshTime > 0) // ATC 51
530 1 : poKmlLink->set_viewrefreshmode(kmldom::VIEWREFRESHMODE_ONSTOP);
531 4 : else if (iViewRefreshMode >= 0 &&
532 2 : poOgrFeat->IsFieldSetAndNotNull(iViewRefreshMode))
533 : {
534 : const char *const pszViewRefreshMode =
535 1 : poOgrFeat->GetFieldAsString(iViewRefreshMode);
536 1 : if (EQUAL(pszViewRefreshMode, "never"))
537 0 : poKmlLink->set_viewrefreshmode(kmldom::VIEWREFRESHMODE_NEVER);
538 1 : else if (EQUAL(pszViewRefreshMode, "onRequest"))
539 0 : poKmlLink->set_viewrefreshmode(
540 : kmldom::VIEWREFRESHMODE_ONREQUEST);
541 1 : else if (EQUAL(pszViewRefreshMode, "onStop"))
542 0 : poKmlLink->set_viewrefreshmode(kmldom::VIEWREFRESHMODE_ONSTOP);
543 1 : else if (EQUAL(pszViewRefreshMode, "onRegion"))
544 1 : poKmlLink->set_viewrefreshmode(
545 : kmldom::VIEWREFRESHMODE_ONREGION);
546 : }
547 :
548 3 : if (dfViewRefreshTime > 0) // ATC 9
549 1 : poKmlLink->set_viewrefreshtime(dfViewRefreshTime);
550 :
551 6 : if (iViewBoundScale >= 0 &&
552 3 : poOgrFeat->IsFieldSetAndNotNull(iViewBoundScale))
553 : {
554 : const double dfViewBoundScale =
555 1 : poOgrFeat->GetFieldAsDouble(iViewBoundScale);
556 1 : if (dfViewBoundScale > 0) // ATC 9
557 1 : poKmlLink->set_viewboundscale(dfViewBoundScale);
558 : }
559 :
560 3 : if (iViewFormat >= 0 && poOgrFeat->IsFieldSetAndNotNull(iViewFormat))
561 : {
562 : const char *const pszViewFormat =
563 1 : poOgrFeat->GetFieldAsString(iViewFormat);
564 1 : if (pszViewFormat[0] != '\0') // ATC 46
565 1 : poKmlLink->set_viewformat(pszViewFormat);
566 : }
567 :
568 3 : if (iHttpQuery >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHttpQuery))
569 : {
570 : const char *const pszHttpQuery =
571 1 : poOgrFeat->GetFieldAsString(iHttpQuery);
572 1 : if (strstr(pszHttpQuery, "[clientVersion]") != nullptr ||
573 0 : strstr(pszHttpQuery, "[kmlVersion]") != nullptr ||
574 0 : strstr(pszHttpQuery, "[clientName]") != nullptr ||
575 0 : strstr(pszHttpQuery, "[language]") != nullptr) // ATC 47
576 : {
577 1 : poKmlLink->set_httpquery(pszHttpQuery);
578 : }
579 : }
580 : }
581 :
582 : // Model.
583 428 : else if (!poKmlFeature && iModel >= 0 &&
584 2 : poOgrFeat->IsFieldSetAndNotNull(iModel) && poOgrGeom != nullptr &&
585 430 : !poOgrGeom->IsEmpty() &&
586 2 : wkbFlatten(poOgrGeom->getGeometryType()) == wkbPoint)
587 : {
588 4 : const PlacemarkPtr poKmlPlacemark = poKmlFactory->CreatePlacemark();
589 2 : poKmlFeature = poKmlPlacemark;
590 :
591 2 : const OGRPoint *const poOgrPoint = poOgrGeom->toPoint();
592 4 : ModelPtr model = poKmlFactory->CreateModel();
593 :
594 4 : LocationPtr location = poKmlFactory->CreateLocation();
595 2 : model->set_location(location);
596 2 : location->set_latitude(poOgrPoint->getY());
597 2 : location->set_longitude(poOgrPoint->getX());
598 2 : if (poOgrPoint->getCoordinateDimension() == 3)
599 1 : location->set_altitude(poOgrPoint->getZ());
600 :
601 2 : int isGX = FALSE;
602 : const int iAltitudeMode =
603 2 : poOgrFeat->GetFieldIndex(oFC.altitudeModefield);
604 2 : if (poOgrFeat->IsFieldSetAndNotNull(iAltitudeMode))
605 : {
606 1 : const int nAltitudeMode = kmlAltitudeModeFromString(
607 : poOgrFeat->GetFieldAsString(iAltitudeMode), isGX);
608 1 : model->set_altitudemode(nAltitudeMode);
609 :
610 : // ATC 55
611 2 : if (nAltitudeMode != kmldom::ALTITUDEMODE_CLAMPTOGROUND &&
612 1 : poOgrPoint->getCoordinateDimension() != 3)
613 : {
614 0 : if (CPLTestBool(
615 : CPLGetConfigOption("LIBKML_STRICT_COMPLIANCE", "TRUE")))
616 0 : CPLError(CE_Warning, CPLE_AppDefined,
617 : "Altitude should be defined");
618 : }
619 : }
620 :
621 2 : if ((iHeading >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHeading)) ||
622 5 : (iTilt >= 0 && poOgrFeat->IsFieldSetAndNotNull(iTilt)) ||
623 1 : (iRoll >= 0 && poOgrFeat->IsFieldSetAndNotNull(iRoll)))
624 : {
625 : OrientationPtr const orientation =
626 2 : poKmlFactory->CreateOrientation();
627 1 : model->set_orientation(orientation);
628 1 : if (iHeading >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHeading))
629 1 : orientation->set_heading(poOgrFeat->GetFieldAsDouble(iHeading));
630 : else
631 0 : orientation->set_heading(0);
632 1 : if (iTilt >= 0 && poOgrFeat->IsFieldSetAndNotNull(iTilt))
633 1 : orientation->set_tilt(poOgrFeat->GetFieldAsDouble(iTilt));
634 : else
635 0 : orientation->set_tilt(0);
636 1 : if (iRoll >= 0 && poOgrFeat->IsFieldSetAndNotNull(iRoll))
637 1 : orientation->set_roll(poOgrFeat->GetFieldAsDouble(iRoll));
638 : else
639 0 : orientation->set_roll(0);
640 : }
641 2 : const int iScaleX = poOgrFeat->GetFieldIndex(oFC.scalexfield);
642 2 : const int iScaleY = poOgrFeat->GetFieldIndex(oFC.scaleyfield);
643 2 : const int iScaleZ = poOgrFeat->GetFieldIndex(oFC.scalezfield);
644 :
645 4 : const ScalePtr scale = poKmlFactory->CreateScale();
646 2 : model->set_scale(scale);
647 2 : if (iScaleX >= 0 && poOgrFeat->IsFieldSetAndNotNull(iScaleX))
648 1 : scale->set_x(poOgrFeat->GetFieldAsDouble(iScaleX));
649 : else
650 1 : scale->set_x(1.0);
651 2 : if (iScaleY >= 0 && poOgrFeat->IsFieldSetAndNotNull(iScaleY))
652 1 : scale->set_y(poOgrFeat->GetFieldAsDouble(iScaleY));
653 : else
654 1 : scale->set_y(1.0);
655 2 : if (iScaleZ >= 0 && poOgrFeat->IsFieldSetAndNotNull(iScaleZ))
656 1 : scale->set_z(poOgrFeat->GetFieldAsDouble(iScaleZ));
657 : else
658 1 : scale->set_z(1.0);
659 :
660 2 : const LinkPtr link = poKmlFactory->CreateLink();
661 2 : model->set_link(link);
662 2 : const char *const pszURL = poOgrFeat->GetFieldAsString(oFC.modelfield);
663 2 : link->set_href(pszURL);
664 :
665 : // Collada 3D file?
666 3 : if (EQUAL(CPLGetExtensionSafe(pszURL).c_str(), "dae") &&
667 1 : CPLTestBool(CPLGetConfigOption("LIBKML_ADD_RESOURCE_MAP", "TRUE")))
668 : {
669 1 : VSILFILE *fp = nullptr;
670 1 : bool bIsURL = false;
671 1 : if (STARTS_WITH_CI(pszURL, "http://") ||
672 0 : STARTS_WITH_CI(pszURL, "https://"))
673 : {
674 1 : bIsURL = true;
675 1 : fp = VSIFOpenL(CPLSPrintf("/vsicurl/%s", pszURL), "rb");
676 : }
677 0 : else if (strstr(pszURL, ".kmz/") != nullptr)
678 : {
679 0 : fp = VSIFOpenL(CPLSPrintf("/vsizip/%s", pszURL), "rb");
680 : }
681 : else
682 : {
683 0 : fp = VSIFOpenL(pszURL, "rb");
684 : }
685 1 : if (fp != nullptr)
686 : {
687 0 : ResourceMapPtr resourceMap = nullptr;
688 0 : const char *pszLine = nullptr;
689 0 : while ((pszLine = CPLReadLineL(fp)) != nullptr)
690 : {
691 0 : const char *pszInitFrom = strstr(pszLine, "<init_from>");
692 0 : if (pszInitFrom)
693 : {
694 0 : pszInitFrom += strlen("<init_from>");
695 : const char *const pszInitFromEnd =
696 0 : strstr(pszInitFrom, "</init_from>");
697 0 : if (pszInitFromEnd)
698 : {
699 0 : CPLString osImage(pszInitFrom);
700 0 : osImage.resize(pszInitFromEnd - pszInitFrom);
701 : const std::string osExtension =
702 0 : CPLGetExtensionSafe(osImage);
703 0 : if (EQUAL(osExtension.c_str(), "jpg") ||
704 0 : EQUAL(osExtension.c_str(), "jpeg") ||
705 0 : EQUAL(osExtension.c_str(), "png") ||
706 0 : EQUAL(osExtension.c_str(), "gif"))
707 : {
708 0 : if (!resourceMap)
709 : resourceMap =
710 0 : poKmlFactory->CreateResourceMap();
711 : const AliasPtr alias =
712 0 : poKmlFactory->CreateAlias();
713 0 : if (bIsURL && CPLIsFilenameRelative(osImage))
714 : {
715 0 : if (STARTS_WITH(pszURL, "http"))
716 0 : alias->set_targethref(CPLSPrintf(
717 : "%s/%s",
718 0 : CPLGetPathSafe(pszURL).c_str(),
719 : osImage.c_str()));
720 : else
721 0 : alias->set_targethref(
722 0 : CPLFormFilenameSafe(
723 0 : CPLGetPathSafe(pszURL).c_str(),
724 : osImage, nullptr));
725 : }
726 : else
727 0 : alias->set_targethref(osImage);
728 0 : alias->set_sourcehref(osImage);
729 0 : resourceMap->add_alias(alias);
730 : }
731 : }
732 : }
733 : }
734 0 : if (resourceMap)
735 0 : model->set_resourcemap(resourceMap);
736 0 : VSIFCloseL(fp);
737 : }
738 : }
739 :
740 2 : poKmlPlacemark->set_geometry(AsGeometry(model));
741 : }
742 :
743 : // Camera.
744 424 : else if (!poKmlFeature && poOgrGeom != nullptr && !poOgrGeom->IsEmpty() &&
745 159 : wkbFlatten(poOgrGeom->getGeometryType()) == wkbPoint &&
746 477 : poOgrFeat->GetFieldIndex(oFC.camera_longitude_field) < 0 &&
747 53 : ((iHeading >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHeading)) ||
748 51 : (iTilt >= 0 && poOgrFeat->IsFieldSetAndNotNull(iTilt)) ||
749 0 : (iRoll >= 0 && poOgrFeat->IsFieldSetAndNotNull(iRoll))))
750 : {
751 2 : const PlacemarkPtr poKmlPlacemark = poKmlFactory->CreatePlacemark();
752 2 : poKmlFeature = poKmlPlacemark;
753 :
754 2 : const OGRPoint *const poOgrPoint = poOgrGeom->toPoint();
755 2 : camera = poKmlFactory->CreateCamera();
756 2 : camera->set_latitude(poOgrPoint->getY());
757 2 : camera->set_longitude(poOgrPoint->getX());
758 2 : int isGX = FALSE;
759 : const int iAltitudeMode =
760 2 : poOgrFeat->GetFieldIndex(oFC.altitudeModefield);
761 2 : if (poOgrFeat->IsFieldSetAndNotNull(iAltitudeMode))
762 : {
763 1 : const int nAltitudeMode = kmlAltitudeModeFromString(
764 : poOgrFeat->GetFieldAsString(iAltitudeMode), isGX);
765 1 : camera->set_altitudemode(nAltitudeMode);
766 : }
767 1 : else if (CPLTestBool(
768 : CPLGetConfigOption("LIBKML_STRICT_COMPLIANCE", "TRUE")))
769 : {
770 1 : CPLError(CE_Warning, CPLE_AppDefined,
771 : "Camera should define altitudeMode != 'clampToGround'");
772 : }
773 :
774 2 : if (poOgrPoint->getCoordinateDimension() == 3)
775 : {
776 1 : camera->set_altitude(poOgrPoint->getZ());
777 : }
778 1 : else if (CPLTestBool(
779 : CPLGetConfigOption("LIBKML_STRICT_COMPLIANCE", "TRUE")))
780 : {
781 1 : CPLError(CE_Warning, CPLE_AppDefined,
782 : "Camera should have an altitude/Z");
783 1 : camera->set_altitude(0.0);
784 : }
785 :
786 2 : if (iHeading >= 0 && poOgrFeat->IsFieldSetAndNotNull(iHeading))
787 2 : camera->set_heading(poOgrFeat->GetFieldAsDouble(iHeading));
788 2 : if (iTilt >= 0 && poOgrFeat->IsFieldSetAndNotNull(iTilt))
789 1 : camera->set_tilt(poOgrFeat->GetFieldAsDouble(iTilt));
790 2 : if (iRoll >= 0 && poOgrFeat->IsFieldSetAndNotNull(iRoll))
791 1 : camera->set_roll(poOgrFeat->GetFieldAsDouble(iRoll));
792 2 : poKmlPlacemark->set_abstractview(camera);
793 : }
794 211 : else if (!poKmlFeature)
795 : {
796 209 : const PlacemarkPtr poKmlPlacemark = poKmlFactory->CreatePlacemark();
797 209 : poKmlFeature = poKmlPlacemark;
798 :
799 209 : if (poOgrGeom)
800 : {
801 158 : ElementPtr poKmlElement = geom2kml(poOgrGeom, -1, poKmlFactory);
802 158 : if (!poKmlElement)
803 : {
804 13 : CPLError(CE_Failure, CPLE_AppDefined,
805 : "Cannot translate feature: %s",
806 26 : poOgrFeat->DumpReadableAsString().c_str());
807 13 : return nullptr;
808 : }
809 :
810 145 : poKmlPlacemark->set_geometry(AsGeometry(std::move(poKmlElement)));
811 : }
812 : }
813 :
814 205 : if (!camera)
815 402 : camera = feat2kmlcamera(oFC, iHeading, iTilt, iRoll, poOgrFeat,
816 201 : poKmlFactory);
817 205 : if (camera)
818 4 : poKmlFeature->set_abstractview(camera);
819 :
820 : /***** style *****/
821 205 : featurestyle2kml(poOgrDS, poOgrLayer, poOgrFeat, poKmlFactory,
822 : poKmlFeature);
823 :
824 : /***** fields *****/
825 205 : field2kml(poOgrFeat, poOgrLayer, poKmlFactory, poKmlFeature,
826 : bUseSimpleField, oFC);
827 :
828 205 : return poKmlFeature;
829 : }
830 :
831 : OGRFeature *
832 1292 : kml2feat(PlacemarkPtr poKmlPlacemark, OGRLIBKMLDataSource *poOgrDS,
833 : OGRLIBKMLLayer *poOgrLayer, OGRFeatureDefn *poOgrFeatDefn,
834 : OGRSpatialReference *poOgrSRS,
835 : const std::map<std::string, int> &mapSimpleFieldNameToOgrFieldIx)
836 : {
837 1292 : OGRFeature *poOgrFeat = new OGRFeature(poOgrFeatDefn);
838 :
839 : /***** style *****/
840 1292 : kml2featurestyle(poKmlPlacemark, poOgrDS, poOgrLayer, poOgrFeat);
841 :
842 : /***** geometry *****/
843 1292 : if (poKmlPlacemark->has_geometry())
844 : {
845 : OGRGeometry *const poOgrGeom =
846 1226 : kml2geom(poKmlPlacemark->get_geometry(), poOgrSRS);
847 1226 : poOgrFeat->SetGeometryDirectly(poOgrGeom);
848 : }
849 68 : else if (poKmlPlacemark->has_abstractview() &&
850 2 : poKmlPlacemark->get_abstractview()->IsA(kmldom::Type_Camera))
851 : {
852 4 : const CameraPtr &camera = AsCamera(poKmlPlacemark->get_abstractview());
853 2 : if (camera->has_longitude() && camera->has_latitude())
854 : {
855 2 : if (camera->has_altitude())
856 2 : poOgrFeat->SetGeometryDirectly(new OGRPoint(
857 2 : camera->get_longitude(), camera->get_latitude(),
858 2 : camera->get_altitude()));
859 : else
860 0 : poOgrFeat->SetGeometryDirectly(new OGRPoint(
861 0 : camera->get_longitude(), camera->get_latitude()));
862 2 : poOgrFeat->GetGeometryRef()->assignSpatialReference(poOgrSRS);
863 : }
864 : }
865 :
866 : /***** fields *****/
867 1292 : kml2field(poOgrFeat, AsFeature(poKmlPlacemark),
868 : poOgrLayer->GetFieldConfig(), mapSimpleFieldNameToOgrFieldIx);
869 :
870 1292 : return poOgrFeat;
871 : }
872 :
873 44 : OGRFeature *kmlgroundoverlay2feat(
874 : GroundOverlayPtr poKmlOverlay, OGRLIBKMLDataSource * /* poOgrDS */,
875 : OGRLIBKMLLayer *poOgrLayer, OGRFeatureDefn *poOgrFeatDefn,
876 : OGRSpatialReference *poOgrSRS,
877 : const std::map<std::string, int> &mapSimpleFieldNameToOgrFieldIx)
878 : {
879 44 : OGRFeature *poOgrFeat = new OGRFeature(poOgrFeatDefn);
880 :
881 : /***** geometry *****/
882 44 : if (poKmlOverlay->has_latlonbox())
883 : {
884 : OGRGeometry *const poOgrGeom =
885 44 : kml2geom_latlonbox(poKmlOverlay->get_latlonbox(), poOgrSRS);
886 44 : poOgrFeat->SetGeometryDirectly(poOgrGeom);
887 : }
888 0 : else if (poKmlOverlay->has_gx_latlonquad())
889 : {
890 : OGRGeometry *const poOgrGeom =
891 0 : kml2geom_latlonquad(poKmlOverlay->get_gx_latlonquad(), poOgrSRS);
892 0 : poOgrFeat->SetGeometryDirectly(poOgrGeom);
893 : }
894 :
895 : /***** fields *****/
896 44 : kml2field(poOgrFeat, AsFeature(poKmlOverlay), poOgrLayer->GetFieldConfig(),
897 : mapSimpleFieldNameToOgrFieldIx);
898 :
899 44 : return poOgrFeat;
900 : }
|