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