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) 2011-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 
32 #include <set>
33 #include <string>
34 
35 #include "ogr_featurestyle.h"
36 #include "ogrlibkmlstyle.h"
37 #include "ogr_libkml.h"
38 
39 CPL_CVSID("$Id: ogrlibkmlstyle.cpp 86933038c3926cd4dc3ff37c431b317abb69e602 2021-03-27 23:20:49 +0100 Even Rouault $")
40 
41 using kmlbase::Color32;
42 using kmldom::BalloonStylePtr;
43 using kmldom::ContainerPtr;
44 using kmldom::DocumentPtr;
45 using kmldom::ElementPtr;
46 using kmldom::FeaturePtr;
47 using kmldom::HotSpotPtr;
48 using kmldom::ItemIconPtr;
49 using kmldom::IconStyleIconPtr;
50 using kmldom::IconStylePtr;
51 using kmldom::KmlFactory;
52 using kmldom::KmlPtr;
53 using kmldom::LabelStylePtr;
54 using kmldom::LineStylePtr;
55 using kmldom::ListStylePtr;
56 using kmldom::ObjectPtr;
57 using kmldom::PairPtr;
58 using kmldom::PolyStylePtr;
59 using kmldom::StyleMapPtr;
60 using kmldom::StylePtr;
61 using kmldom::StyleSelectorPtr;
62 using kmldom::STYLESTATE_HIGHLIGHT;
63 using kmldom::STYLESTATE_NORMAL;
64 
65 /******************************************************************************
66  Generic function to parse a stylestring and add to a kml style.
67 
68 Args:
69             pszStyleString  the stylestring to parse
70             poKmlStyle      the kml style to add to (or NULL)
71             poKmlFactory    the kml dom factory
72 
73 Returns:
74             the kml style
75 
76 ******************************************************************************/
77 
addstylestring2kml(const char * pszStyleString,StylePtr poKmlStyle,KmlFactory * poKmlFactory,FeaturePtr poKmlFeature)78 StylePtr addstylestring2kml(
79     const char *pszStyleString,
80     StylePtr poKmlStyle,
81     KmlFactory * poKmlFactory,
82     FeaturePtr poKmlFeature )
83 {
84     /***** just bail now if stylestring is empty *****/
85     if( !pszStyleString || !*pszStyleString )
86     {
87         return poKmlStyle;
88     }
89 
90     LineStylePtr poKmlLineStyle = nullptr;
91     PolyStylePtr poKmlPolyStyle = nullptr;
92     IconStylePtr poKmlIconStyle = nullptr;
93     LabelStylePtr poKmlLabelStyle = nullptr;
94 
95     /***** create and init a style mamager with the style string *****/
96     OGRStyleMgr * const poOgrSM = new OGRStyleMgr;
97 
98     poOgrSM->InitStyleString( pszStyleString );
99 
100     /***** loop though the style parts *****/
101     for( int i = 0; i < poOgrSM->GetPartCount( nullptr ); i++ )
102     {
103         OGRStyleTool *poOgrST = poOgrSM->GetPart( i, nullptr );
104 
105         if( !poOgrST )
106         {
107             continue;
108         }
109 
110         switch( poOgrST->GetType() )
111         {
112             case OGRSTCPen:
113             {
114                 poKmlLineStyle = poKmlFactory->CreateLineStyle();
115 
116                 OGRStylePen *poStylePen = cpl::down_cast<OGRStylePen *>(poOgrST);
117 
118                 /***** pen color *****/
119                 GBool nullcheck = FALSE;
120                 const char * const pszColor = poStylePen->Color( nullcheck );
121 
122                 int nR = 0;
123                 int nG = 0;
124                 int nB = 0;
125                 int nA = 0;
126                 if( !nullcheck &&
127                     poStylePen->GetRGBFromString( pszColor, nR, nG, nB, nA ) )
128                 {
129                     poKmlLineStyle->set_color(
130                         Color32( static_cast<GByte>(nA),
131                                  static_cast<GByte>(nB),
132                                  static_cast<GByte>(nG),
133                                  static_cast<GByte>(nR) ) );
134                 }
135                 poStylePen->SetUnit(OGRSTUPixel);
136                 double dfWidth = poStylePen->Width( nullcheck );
137 
138                 if( nullcheck )
139                     dfWidth = 1.0;
140 
141                 poKmlLineStyle->set_width( dfWidth );
142 
143                 break;
144             }
145             case OGRSTCBrush:
146             {
147                 OGRStyleBrush * const poStyleBrush =
148                     cpl::down_cast<OGRStyleBrush *>(poOgrST);
149 
150                 /***** brush color *****/
151                 GBool nullcheck = FALSE;
152                 const char *pszColor = poStyleBrush->ForeColor( nullcheck );
153 
154                 int nR = 0;
155                 int nG = 0;
156                 int nB = 0;
157                 int nA = 0;
158                 if( !nullcheck &&
159                     poStyleBrush->GetRGBFromString( pszColor, nR, nG, nB, nA ) )
160                 {
161                     poKmlPolyStyle = poKmlFactory->CreatePolyStyle();
162                     poKmlPolyStyle->set_color(
163                         Color32( static_cast<GByte>(nA),
164                                  static_cast<GByte>(nB),
165                                  static_cast<GByte>(nG),
166                                  static_cast<GByte>(nR) ) );
167                 }
168                 break;
169             }
170             case OGRSTCSymbol:
171             {
172                 OGRStyleSymbol * const poStyleSymbol =
173                     cpl::down_cast<OGRStyleSymbol *>(poOgrST);
174 
175                 /***** id (kml icon) *****/
176                 GBool nullcheck = FALSE;
177                 const char *pszId = poStyleSymbol->Id( nullcheck );
178 
179                 if( !nullcheck )
180                 {
181                     if( !poKmlIconStyle)
182                         poKmlIconStyle = poKmlFactory->CreateIconStyle();
183 
184                     /***** split it at the ,'s *****/
185                     char **papszTokens =
186                         CSLTokenizeString2(
187                             pszId, ",",
188                             CSLT_HONOURSTRINGS | CSLT_STRIPLEADSPACES |
189                             CSLT_STRIPENDSPACES );
190 
191                     if( papszTokens )
192                     {
193                         // Just take the first one.
194                         // TODO: Come up with a better idea.
195                         if( papszTokens[0] )
196                         {
197                             IconStyleIconPtr poKmlIcon =
198                                 poKmlFactory->CreateIconStyleIcon();
199                             poKmlIcon->set_href( papszTokens[0] );
200                             poKmlIconStyle->set_icon( poKmlIcon );
201                         }
202 
203                         CSLDestroy( papszTokens );
204                     }
205                 }
206 
207                 /***** heading *****/
208                 double heading = poStyleSymbol->Angle( nullcheck );
209 
210                 if( !nullcheck )
211                 {
212                     if( !poKmlIconStyle)
213                         poKmlIconStyle = poKmlFactory->CreateIconStyle();
214                     poKmlIconStyle->set_heading( heading );
215                 }
216 
217                 /***** scale *****/
218                 double dfScale = poStyleSymbol->Size( nullcheck );
219 
220                 if( !nullcheck )
221                 {
222                     if( !poKmlIconStyle )
223                         poKmlIconStyle = poKmlFactory->CreateIconStyle();
224 
225                     poKmlIconStyle->set_scale( dfScale );
226                 }
227 
228                 /***** color *****/
229                 const char * const pszcolor =
230                     poStyleSymbol->Color( nullcheck );
231 
232                 int nR = 0;
233                 int nG = 0;
234                 int nB = 0;
235                 int nA = 0;
236                 if( !nullcheck &&
237                     poOgrST->GetRGBFromString( pszcolor, nR, nG, nB, nA ) )
238                 {
239                     poKmlIconStyle->set_color(
240                         Color32 ( static_cast<GByte>(nA),
241                                   static_cast<GByte>(nB),
242                                   static_cast<GByte>(nG),
243                                   static_cast<GByte>(nR) ) );
244                 }
245 
246                 /***** hotspot *****/
247 
248                 double dfDx = poStyleSymbol->SpacingX( nullcheck );
249                 GBool nullcheck2 = FALSE;
250                 double dfDy = poStyleSymbol->SpacingY( nullcheck2 );
251 
252                 if( !nullcheck && !nullcheck2 )
253                 {
254                     if( !poKmlIconStyle)
255                         poKmlIconStyle = poKmlFactory->CreateIconStyle();
256 
257                     HotSpotPtr poKmlHotSpot = poKmlFactory->CreateHotSpot();
258 
259                     poKmlHotSpot->set_x( dfDx );
260                     poKmlHotSpot->set_y( dfDy );
261 
262                     poKmlIconStyle->set_hotspot( poKmlHotSpot );
263                 }
264 
265                 break;
266             }
267         case OGRSTCLabel:
268             {
269                 GBool nullcheck;
270                 GBool nullcheck2;
271 
272                 OGRStyleLabel *poStyleLabel =
273                     cpl::down_cast<OGRStyleLabel *>(poOgrST);
274 
275                 /***** color *****/
276                 const char *pszcolor = poStyleLabel->ForeColor( nullcheck );
277 
278                 int nR = 0;
279                 int nG = 0;
280                 int nB = 0;
281                 int nA = 0;
282                 if( !nullcheck &&
283                     poStyleLabel->GetRGBFromString( pszcolor, nR, nG, nB, nA ) )
284                 {
285                     if( !poKmlLabelStyle )
286                         poKmlLabelStyle = poKmlFactory->CreateLabelStyle();
287                     poKmlLabelStyle->set_color(
288                         Color32 ( static_cast<GByte>(nA),
289                                   static_cast<GByte>(nB),
290                                   static_cast<GByte>(nG),
291                                   static_cast<GByte>(nR) ) );
292                 }
293 
294                 /***** scale *****/
295                 double dfScale = poStyleLabel->Stretch( nullcheck );
296 
297                 if( !nullcheck )
298                 {
299                     dfScale /= 100.0;
300                     if( !poKmlLabelStyle )
301                         poKmlLabelStyle = poKmlFactory->CreateLabelStyle();
302                     poKmlLabelStyle->set_scale( dfScale );
303                 }
304 
305                 /***** heading *****/
306                 const double heading = poStyleLabel->Angle( nullcheck );
307 
308                 if( !nullcheck )
309                 {
310                     if( !poKmlIconStyle)
311                     {
312                         poKmlIconStyle = poKmlFactory->CreateIconStyle();
313                         const IconStyleIconPtr poKmlIcon =
314                             poKmlFactory->CreateIconStyleIcon();
315                         poKmlIconStyle->set_icon( poKmlIcon );
316                     }
317 
318                     poKmlIconStyle->set_heading( heading );
319                 }
320 
321                 /***** hotspot *****/
322                 const double dfDx = poStyleLabel->SpacingX( nullcheck );
323                 const double dfDy = poStyleLabel->SpacingY( nullcheck2 );
324 
325                 if( !nullcheck && !nullcheck2 )
326                 {
327                     if( !poKmlIconStyle)
328                     {
329                         poKmlIconStyle = poKmlFactory->CreateIconStyle();
330                         const IconStyleIconPtr poKmlIcon =
331                             poKmlFactory->CreateIconStyleIcon();
332                         poKmlIconStyle->set_icon( poKmlIcon );
333                     }
334 
335                     HotSpotPtr poKmlHotSpot = poKmlFactory->CreateHotSpot();
336 
337                     poKmlHotSpot->set_x( dfDx );
338                     poKmlHotSpot->set_y( dfDy );
339 
340                     poKmlIconStyle->set_hotspot( poKmlHotSpot );
341                 }
342 
343                 /***** label text *****/
344                 const char * const pszText =
345                     poStyleLabel->TextString( nullcheck );
346 
347                 if( !nullcheck && poKmlFeature )
348                 {
349                         poKmlFeature->set_name( pszText );
350                 }
351 
352                 break;
353             }
354             case OGRSTCNone:
355             default:
356             {
357                 break;
358             }
359         }
360 
361         delete poOgrST;
362     }
363 
364     if( poKmlLineStyle || poKmlPolyStyle || poKmlIconStyle || poKmlLabelStyle )
365     {
366         if( !poKmlStyle )
367             poKmlStyle = poKmlFactory->CreateStyle();
368 
369         if( poKmlLineStyle )
370             poKmlStyle->set_linestyle( poKmlLineStyle );
371 
372         if( poKmlPolyStyle )
373             poKmlStyle->set_polystyle( poKmlPolyStyle );
374 
375         if( poKmlIconStyle )
376             poKmlStyle->set_iconstyle( poKmlIconStyle );
377 
378         if( poKmlLabelStyle )
379             poKmlStyle->set_labelstyle( poKmlLabelStyle );
380     }
381 
382     delete poOgrSM;
383 
384     return poKmlStyle;
385 }
386 
387 /******************************************************************************
388  kml2pen
389 ******************************************************************************/
390 
391 static
kml2pen(LineStylePtr poKmlLineStyle,OGRStylePen * poOgrStylePen)392 OGRStylePen *kml2pen( LineStylePtr poKmlLineStyle, OGRStylePen *poOgrStylePen )
393 {
394     if( !poOgrStylePen )
395         poOgrStylePen = new OGRStylePen();
396 
397     /***** <LineStyle> should always have a width in pixels *****/
398     poOgrStylePen->SetUnit(OGRSTUPixel);
399 
400     /***** width *****/
401     if( poKmlLineStyle->has_width() )
402         poOgrStylePen->SetWidth( poKmlLineStyle->get_width() );
403 
404     /***** color *****/
405     if( poKmlLineStyle->has_color() )
406     {
407         Color32 poKmlColor = poKmlLineStyle->get_color();
408         char szColor[10] = {};
409         snprintf( szColor, sizeof( szColor ), "#%02X%02X%02X%02X",
410                   poKmlColor.get_red(),
411                   poKmlColor.get_green(),
412                   poKmlColor.get_blue(),
413                   poKmlColor.get_alpha() );
414         poOgrStylePen->SetColor( szColor );
415     }
416 
417     return poOgrStylePen;
418 }
419 
420 /******************************************************************************
421  kml2brush
422 ******************************************************************************/
423 
424 static
kml2brush(PolyStylePtr poKmlPolyStyle,OGRStyleBrush * poOgrStyleBrush)425 OGRStyleBrush *kml2brush(
426     PolyStylePtr poKmlPolyStyle,
427     OGRStyleBrush *poOgrStyleBrush )
428 {
429     if( !poOgrStyleBrush )
430         poOgrStyleBrush = new OGRStyleBrush();
431 
432     /***** color *****/
433     if( poKmlPolyStyle->has_color() )
434     {
435         Color32 poKmlColor = poKmlPolyStyle->get_color();
436         char szColor[10] = {};
437         snprintf( szColor, sizeof( szColor ), "#%02X%02X%02X%02X",
438                   poKmlColor.get_red(),
439                   poKmlColor.get_green(),
440                   poKmlColor.get_blue(),
441                   poKmlColor.get_alpha() );
442         poOgrStyleBrush->SetForeColor( szColor );
443     }
444 
445     return poOgrStyleBrush;
446 }
447 
448 /******************************************************************************
449  kml2symbol
450 ******************************************************************************/
451 
452 static
kml2symbol(IconStylePtr poKmlIconStyle,OGRStyleSymbol * poOgrStyleSymbol)453 OGRStyleSymbol *kml2symbol(
454     IconStylePtr poKmlIconStyle,
455     OGRStyleSymbol *poOgrStyleSymbol )
456 {
457     if( !poOgrStyleSymbol )
458         poOgrStyleSymbol = new OGRStyleSymbol();
459 
460     /***** id (kml icon) *****/
461     if( poKmlIconStyle->has_icon() )
462     {
463         IconStyleIconPtr poKmlIcon = poKmlIconStyle->get_icon();
464 
465         if( poKmlIcon->has_href() )
466         {
467             std::string oIcon = "\"";
468             oIcon.append( poKmlIcon->get_href().c_str() );
469             oIcon.append( "\"" );
470             poOgrStyleSymbol->SetId( oIcon.c_str() );
471         }
472     }
473 
474     /***** heading *****/
475     if( poKmlIconStyle->has_heading() )
476         poOgrStyleSymbol->SetAngle( poKmlIconStyle->get_heading() );
477 
478     /***** scale *****/
479     if( poKmlIconStyle->has_scale() )
480         poOgrStyleSymbol->SetSize( poKmlIconStyle->get_scale() );
481 
482     /***** color *****/
483     if( poKmlIconStyle->has_color() )
484     {
485         Color32 poKmlColor = poKmlIconStyle->get_color();
486         char szColor[10] = {};
487         snprintf( szColor, sizeof( szColor ), "#%02X%02X%02X%02X",
488                   poKmlColor.get_red(),
489                   poKmlColor.get_green(),
490                   poKmlColor.get_blue(),
491                   poKmlColor.get_alpha() );
492         poOgrStyleSymbol->SetColor( szColor );
493     }
494 
495     /***** hotspot *****/
496     if( poKmlIconStyle->has_hotspot() )
497     {
498         const HotSpotPtr poKmlHotSpot = poKmlIconStyle->get_hotspot();
499 
500         if( poKmlHotSpot->has_x() )
501             poOgrStyleSymbol->SetSpacingX( poKmlHotSpot->get_x() );
502         if( poKmlHotSpot->has_y() )
503             poOgrStyleSymbol->SetSpacingY( poKmlHotSpot->get_y() );
504     }
505 
506     return poOgrStyleSymbol;
507 }
508 
509 /******************************************************************************
510  kml2label
511 ******************************************************************************/
512 
513 static
kml2label(LabelStylePtr poKmlLabelStyle,OGRStyleLabel * poOgrStyleLabel)514 OGRStyleLabel *kml2label(
515     LabelStylePtr poKmlLabelStyle,
516     OGRStyleLabel *poOgrStyleLabel )
517 {
518     if( !poOgrStyleLabel )
519         poOgrStyleLabel = new OGRStyleLabel();
520 
521     /***** color *****/
522     if( poKmlLabelStyle->has_color() )
523     {
524         Color32 poKmlColor = poKmlLabelStyle->get_color();
525         char szColor[10] = {};
526         snprintf( szColor, sizeof( szColor ), "#%02X%02X%02X%02X",
527                   poKmlColor.get_red(),
528                   poKmlColor.get_green(),
529                   poKmlColor.get_blue(),
530                   poKmlColor.get_alpha() );
531         poOgrStyleLabel->SetForColor( szColor );
532     }
533 
534     if( poKmlLabelStyle->has_scale() )
535     {
536         double dfScale = poKmlLabelStyle->get_scale();
537         dfScale *= 100.0;
538 
539         poOgrStyleLabel->SetStretch(dfScale);
540     }
541 
542     return poOgrStyleLabel;
543 }
544 
545 /******************************************************************************
546  Function to add a kml style to a style table.
547 ******************************************************************************/
548 
kml2styletable(OGRStyleTable * poOgrStyleTable,StylePtr poKmlStyle)549 static void kml2styletable(
550     OGRStyleTable * poOgrStyleTable,
551     StylePtr poKmlStyle )
552 {
553     /***** No reason to add it if it don't have an id. *****/
554     if( !poKmlStyle->has_id() )
555     {
556         CPLError( CE_Warning, CPLE_AppDefined,
557                   "ERROR parsing kml Style: No id" );
558         return;
559     }
560 
561     OGRStyleMgr *poOgrSM = new OGRStyleMgr( poOgrStyleTable );
562 
563     poOgrSM->InitStyleString( nullptr );
564 
565     /***** read the style *****/
566     kml2stylestring( poKmlStyle, poOgrSM );
567 
568     /***** add the style to the style table *****/
569     const std::string oName = poKmlStyle->get_id();
570 
571     poOgrSM->AddStyle(
572         CPLString().Printf( "%s", oName.c_str() ), nullptr );
573 
574     /***** Cleanup the style manager. *****/
575     delete poOgrSM;
576 }
577 
578 /******************************************************************************
579  Function to follow the kml stylemap if one exists.
580 ******************************************************************************/
581 
StyleFromStyleSelector(const StyleSelectorPtr & poKmlStyleSelector,OGRStyleTable * poStyleTable)582 StyleSelectorPtr StyleFromStyleSelector(
583     const StyleSelectorPtr& poKmlStyleSelector,
584     OGRStyleTable * poStyleTable )
585 {
586     /***** Is it a style? *****/
587     if( poKmlStyleSelector->IsA( kmldom::Type_Style) )
588         return poKmlStyleSelector;
589 
590     /***** Is it a style map? *****/
591 
592     else if( poKmlStyleSelector->IsA( kmldom::Type_StyleMap ) )
593         return StyleFromStyleMap(
594             kmldom::AsStyleMap(poKmlStyleSelector), poStyleTable);
595 
596     /***** Not a style or a style map. *****/
597     return nullptr;
598 }
599 
600 /******************************************************************************
601  kml2stylemgr
602 ******************************************************************************/
603 
kml2stylestring(StylePtr poKmlStyle,OGRStyleMgr * poOgrSM)604 void kml2stylestring( StylePtr poKmlStyle, OGRStyleMgr * poOgrSM )
605 
606 {
607     OGRStyleMgr * const poOgrNewSM = new OGRStyleMgr( nullptr );
608 
609     /***** linestyle / pen *****/
610     if( poKmlStyle->has_linestyle() )
611     {
612         poOgrNewSM->InitStyleString( nullptr );
613 
614         LineStylePtr poKmlLineStyle = poKmlStyle->get_linestyle();
615 
616         OGRStyleTool *poOgrTmpST = nullptr;
617         for( int i = 0; i < poOgrSM->GetPartCount( nullptr ); i++ )
618         {
619             OGRStyleTool *poOgrST = poOgrSM->GetPart( i, nullptr );
620 
621             if( !poOgrST )
622                 continue;
623 
624             if( poOgrST->GetType() == OGRSTCPen &&
625                 poOgrTmpST == nullptr )
626             {
627                 poOgrTmpST = poOgrST;
628             }
629             else
630             {
631                 poOgrNewSM->AddPart( poOgrST );
632                 delete poOgrST;
633             }
634         }
635 
636         OGRStylePen *poOgrStylePen =
637             kml2pen( poKmlLineStyle,
638                      ( OGRStylePen *) poOgrTmpST);
639 
640         poOgrNewSM->AddPart( poOgrStylePen );
641 
642         delete poOgrStylePen;
643         poOgrSM->InitStyleString( poOgrNewSM->GetStyleString(nullptr) );
644     }
645 
646     /***** polystyle / brush *****/
647     if( poKmlStyle->has_polystyle() )
648     {
649         poOgrNewSM->InitStyleString( nullptr );
650 
651         PolyStylePtr poKmlPolyStyle = poKmlStyle->get_polystyle();
652 
653         OGRStyleTool *poOgrTmpST = nullptr;
654         for( int i = 0; i < poOgrSM->GetPartCount( nullptr ); i++ )
655         {
656             OGRStyleTool *poOgrST = poOgrSM->GetPart( i, nullptr );
657 
658             if( !poOgrST )
659                 continue;
660 
661             if( poOgrST->GetType() == OGRSTCBrush &&
662                 poOgrTmpST == nullptr )
663             {
664                 poOgrTmpST = poOgrST;
665             }
666             else
667             {
668                 poOgrNewSM->AddPart( poOgrST );
669                 delete poOgrST;
670             }
671         }
672 
673         OGRStyleBrush *poOgrStyleBrush =
674             kml2brush( poKmlPolyStyle,
675                        ( OGRStyleBrush *) poOgrTmpST );
676 
677         poOgrNewSM->AddPart( poOgrStyleBrush );
678 
679         delete poOgrStyleBrush;
680         poOgrSM->InitStyleString( poOgrNewSM->GetStyleString(nullptr) );
681     }
682 
683     /***** iconstyle / symbol *****/
684     if( poKmlStyle->has_iconstyle() )
685     {
686         poOgrNewSM->InitStyleString( nullptr );
687 
688         IconStylePtr poKmlIconStyle = poKmlStyle->get_iconstyle();
689 
690         OGRStyleTool *poOgrTmpST = nullptr;
691         for( int i = 0; i < poOgrSM->GetPartCount( nullptr ); i++ )
692         {
693             OGRStyleTool *poOgrST = poOgrSM->GetPart( i, nullptr );
694 
695             if( !poOgrST )
696                 continue;
697 
698             if( poOgrST->GetType() == OGRSTCSymbol &&
699                 poOgrTmpST == nullptr )
700             {
701                 poOgrTmpST = poOgrST;
702             }
703             else
704             {
705                 poOgrNewSM->AddPart( poOgrST );
706                 delete poOgrST;
707             }
708         }
709 
710         OGRStyleSymbol *poOgrStyleSymbol =
711             kml2symbol( poKmlIconStyle,
712                         ( OGRStyleSymbol *) poOgrTmpST );
713 
714         poOgrNewSM->AddPart( poOgrStyleSymbol );
715 
716         delete poOgrStyleSymbol;
717         poOgrSM->InitStyleString( poOgrNewSM->GetStyleString(nullptr) );
718     }
719 
720     /***** labelstyle / label *****/
721     if( poKmlStyle->has_labelstyle() )
722     {
723         poOgrNewSM->InitStyleString( nullptr );
724 
725         LabelStylePtr poKmlLabelStyle = poKmlStyle->get_labelstyle();
726 
727         OGRStyleTool *poOgrTmpST = nullptr;
728         for( int i = 0; i < poOgrSM->GetPartCount( nullptr ); i++ )
729         {
730             OGRStyleTool *poOgrST = poOgrSM->GetPart( i, nullptr );
731 
732             if( !poOgrST )
733                 continue;
734 
735             if( poOgrST->GetType() == OGRSTCLabel &&
736                 poOgrTmpST == nullptr )
737             {
738                 poOgrTmpST = poOgrST;
739             }
740             else
741             {
742                 poOgrNewSM->AddPart( poOgrST );
743                 delete poOgrST;
744             }
745         }
746 
747         OGRStyleLabel *poOgrStyleLabel =
748             kml2label( poKmlLabelStyle,
749                        ( OGRStyleLabel *) poOgrTmpST );
750 
751         poOgrNewSM->AddPart( poOgrStyleLabel );
752 
753         delete poOgrStyleLabel;
754         poOgrSM->InitStyleString( poOgrNewSM->GetStyleString(nullptr) );
755     }
756 
757     delete poOgrNewSM;
758 }
759 
760 /******************************************************************************
761  Function to get the container from the kmlroot.
762 
763  Args:          poKmlRoot   the root element
764 
765  Returns:       root if its a container, if its a kml the container it
766                 contains, or NULL
767 
768 ******************************************************************************/
769 
MyGetContainerFromRoot(KmlFactory * m_poKmlFactory,ElementPtr poKmlRoot)770 static ContainerPtr MyGetContainerFromRoot(
771     KmlFactory *m_poKmlFactory, ElementPtr poKmlRoot )
772 {
773     ContainerPtr poKmlContainer = nullptr;
774 
775     if( poKmlRoot )
776     {
777         /***** skip over the <kml> we want the container *****/
778         if( poKmlRoot->IsA( kmldom::Type_kml ) )
779         {
780             KmlPtr poKmlKml = AsKml( poKmlRoot );
781 
782             if( poKmlKml->has_feature() )
783             {
784                 FeaturePtr poKmlFeat = poKmlKml->get_feature();
785 
786                 if( poKmlFeat->IsA( kmldom::Type_Container ) )
787                 {
788                     poKmlContainer = AsContainer( poKmlFeat );
789                 }
790                 else if( poKmlFeat->IsA( kmldom::Type_Placemark ) )
791                 {
792                     poKmlContainer = m_poKmlFactory->CreateDocument();
793                     poKmlContainer->add_feature(
794                         kmldom::AsFeature(kmlengine::Clone(poKmlFeat)) );
795                 }
796             }
797         }
798         else if( poKmlRoot->IsA( kmldom::Type_Container ) )
799         {
800             poKmlContainer = AsContainer( poKmlRoot );
801         }
802     }
803 
804     return poKmlContainer;
805 }
806 
StyleFromStyleURL(const StyleMapPtr & stylemap,const string & styleurl,OGRStyleTable * poStyleTable)807 static StyleSelectorPtr StyleFromStyleURL(
808     const StyleMapPtr& stylemap,
809     const string& styleurl,
810     OGRStyleTable * poStyleTable )
811 {
812     // TODO:: Parse the styleURL.
813     char *pszUrl = CPLStrdup( styleurl.c_str() );
814     char *pszStyleMapId = CPLStrdup( stylemap->get_id().c_str() );
815 
816     /***** Is it an internal style ref that starts with a #? *****/
817     if( *pszUrl == '#' && poStyleTable )
818     {
819         /***** Search the style table for the style we *****/
820         /***** want and copy it back into the table.   *****/
821         const char *pszTest = poStyleTable->Find( pszUrl + 1 );
822         if( pszTest )
823         {
824             poStyleTable->AddStyle(pszStyleMapId, pszTest);
825         }
826     }
827 
828     /***** We have a real URL and need to go out and fetch it *****/
829     /***** FIXME this could be a relative path in a kmz *****/
830     else if( strchr(pszUrl, '#') )
831     {
832         const char *pszFetch =
833             CPLGetConfigOption( "LIBKML_EXTERNAL_STYLE", "no" );
834         if( CPLTestBool(pszFetch) )
835         {
836             /***** Lets go out and fetch the style from the external URL *****/
837             char *pszUrlTmp = CPLStrdup(pszUrl);
838             char *pszPound = strchr(pszUrlTmp, '#');
839             char *pszRemoteStyleName = nullptr;
840             // Chop off the stuff (style id) after the URL
841             if( pszPound != nullptr )
842             {
843                 *pszPound = '\0';
844                 pszRemoteStyleName = pszPound + 1;
845             }
846 
847             /***** try it as a url then a file *****/
848             VSILFILE *fp = nullptr;
849             if( (fp = VSIFOpenL( CPLFormFilename( "/vsicurl/",
850                                                    pszUrlTmp,
851                                                    nullptr), "r" )) != nullptr
852                  ||  (fp = VSIFOpenL( pszUrlTmp, "r" )) != nullptr )
853             {
854                 char szbuf[1025] = {};
855                 std::string oStyle = "";
856 
857                 /***** loop, read and copy to a string *****/
858                 do {
859                     const size_t nRead =
860                         VSIFReadL(szbuf, 1, sizeof(szbuf) - 1, fp);
861                     if(nRead == 0)
862                         break;
863 
864                     /***** copy buf to the string *****/
865                     szbuf[nRead] = '\0';
866                     oStyle.append( szbuf );
867                 } while (!VSIFEofL(fp));
868 
869                 VSIFCloseL(fp);
870 
871                 /***** parse the kml into the dom *****/
872                 std::string oKmlErrors;
873                 ElementPtr poKmlRoot = kmldom::Parse( oStyle, &oKmlErrors );
874 
875                 if( !poKmlRoot )
876                 {
877                     CPLError( CE_Warning, CPLE_OpenFailed,
878                               "ERROR parsing style kml %s :%s",
879                               pszUrlTmp, oKmlErrors.c_str() );
880                     CPLFree(pszUrlTmp);
881                     CPLFree(pszUrl);
882                     CPLFree(pszStyleMapId);
883 
884                     return nullptr;
885                 }
886 
887                 /***** get the root container *****/
888                 kmldom::KmlFactory* poKmlFactory =
889                     kmldom::KmlFactory::GetFactory();
890                 ContainerPtr poKmlContainer;
891                 if( !( poKmlContainer =
892                       MyGetContainerFromRoot( poKmlFactory, poKmlRoot ) ) )
893                 {
894                     CPLFree(pszUrlTmp);
895                     CPLFree(pszUrl);
896                     CPLFree(pszStyleMapId);
897 
898                     return nullptr;
899                 }
900 
901                 /**** parse the styles into the table *****/
902                 ParseStyles( AsDocument( poKmlContainer ), &poStyleTable );
903 
904                 /***** look for the style we need to map to in the table *****/
905                 const char *pszTest = poStyleTable->Find(pszRemoteStyleName);
906 
907                 /***** if found copy it to the table as a new style *****/
908                 if( pszTest )
909                     poStyleTable->AddStyle(pszStyleMapId, pszTest);
910             }
911             CPLFree(pszUrlTmp);
912         }
913     }
914 
915     /***** FIXME Add support here for relative links inside KML. *****/
916     CPLFree( pszUrl );
917     CPLFree( pszStyleMapId );
918 
919     return nullptr;
920 }
921 
StyleFromStyleMap(const StyleMapPtr & poKmlStyleMap,OGRStyleTable * poStyleTable)922 StyleSelectorPtr StyleFromStyleMap(
923     const StyleMapPtr& poKmlStyleMap,
924     OGRStyleTable * poStyleTable )
925 {
926     /***** check the config option to see if the    *****/
927     /***** user wants normal or highlighted mapping *****/
928     const char *pszStyleMapKey =
929         CPLGetConfigOption( "LIBKML_STYLEMAP_KEY", "normal" );
930     const int nStyleMapKey =
931         EQUAL(pszStyleMapKey, "highlight") ?
932             STYLESTATE_HIGHLIGHT: STYLESTATE_NORMAL;
933 
934     /*****  Loop through the stylemap pairs and look for the "normal" one *****/
935     for( size_t i = 0; i < poKmlStyleMap->get_pair_array_size(); ++i )
936     {
937         PairPtr myPair = poKmlStyleMap->get_pair_array_at(i);
938 
939         /***** is it the right one of the pair? *****/
940         if( myPair->get_key() == nStyleMapKey )
941         {
942             if( myPair->has_styleselector() )
943                 return StyleFromStyleSelector(myPair->get_styleselector(),
944                                               poStyleTable);
945             else if(myPair->has_styleurl() )
946                 return StyleFromStyleURL(poKmlStyleMap, myPair->get_styleurl(),
947                                          poStyleTable);
948         }
949     }
950 
951     return nullptr;
952 }
953 
954 /******************************************************************************
955  Function to parse a style table out of a document.
956 ******************************************************************************/
957 
ParseStyles(DocumentPtr poKmlDocument,OGRStyleTable ** poStyleTable)958 void ParseStyles(
959     DocumentPtr poKmlDocument,
960     OGRStyleTable ** poStyleTable )
961 {
962     /***** if document is null just bail now *****/
963     if( !poKmlDocument )
964         return;
965 
966     /***** loop over the Styles *****/
967     const size_t nKmlStyles = poKmlDocument->get_styleselector_array_size();
968 
969     /***** Lets first build the style table.    *****/
970     /***** to begin this is just proper styles. *****/
971     for( size_t iKmlStyle = 0; iKmlStyle < nKmlStyles; iKmlStyle++ )
972     {
973         StyleSelectorPtr poKmlStyle =
974             poKmlDocument->get_styleselector_array_at( iKmlStyle );
975 
976         /***** Everything that is not a style you skip *****/
977         if( !poKmlStyle->IsA( kmldom::Type_Style ) )
978             continue;
979 
980         /***** We need to check to see if this is the first style. if it *****/
981         /***** is we will not have a style table and need to create one  *****/
982 
983         if( !*poStyleTable )
984             *poStyleTable = new OGRStyleTable();
985 
986         /***** TODO:: Not sure we need to do this as we seem *****/
987         /***** to cast to element and then back to style.    *****/
988         ElementPtr poKmlElement = AsElement( poKmlStyle );
989         kml2styletable( *poStyleTable, AsStyle( poKmlElement ) );
990     }
991 
992     /***** Now we have to loop back around and get the style maps. We    *****/
993     /***** have to do this a second time since the stylemap might matter *****/
994     /***** and we are just looping reference styles that are farther     *****/
995     /***** down in the file. Order through the XML as it is parsed.      *****/
996 
997     for( size_t iKmlStyle = 0; iKmlStyle < nKmlStyles; iKmlStyle++ )
998     {
999         StyleSelectorPtr poKmlStyle =
1000             poKmlDocument->get_styleselector_array_at( iKmlStyle );
1001 
1002         /***** Everything that is not a stylemap you skip *****/
1003         if( !poKmlStyle->IsA( kmldom::Type_StyleMap ) )
1004             continue;
1005 
1006         /***** We need to check to see if this is the first style. if it *****/
1007         /***** is we will not have a style table and need to create one  *****/
1008         if( !*poStyleTable )
1009             *poStyleTable = new OGRStyleTable();
1010 
1011         /***** copy the style the style map points to since *****/
1012 
1013         char *pszStyleMapId = CPLStrdup( poKmlStyle->get_id().c_str() );
1014         poKmlStyle =
1015             StyleFromStyleMap(kmldom::AsStyleMap(poKmlStyle), *poStyleTable);
1016         if( !poKmlStyle )
1017         {
1018             CPLFree(pszStyleMapId);
1019             continue;
1020         }
1021         char *pszStyleId = CPLStrdup( poKmlStyle->get_id().c_str() );
1022 
1023         /***** TODO:: Not sure we need to do this as we seem *****/
1024         /***** to cast to element and then back to style.    *****/
1025         ElementPtr poKmlElement = AsElement( poKmlStyle );
1026         kml2styletable( *poStyleTable, AsStyle( poKmlElement ) );
1027 
1028         // Change the name of the new style in the style table
1029 
1030         const char *pszTest = (*poStyleTable)->Find(pszStyleId);
1031         // If we found the style we want in the style table we...
1032         if( pszTest )
1033         {
1034             (*poStyleTable)->AddStyle(pszStyleMapId, pszTest);
1035             (*poStyleTable)->RemoveStyle( pszStyleId );
1036         }
1037         CPLFree( pszStyleId );
1038         CPLFree( pszStyleMapId );
1039     }
1040 }
1041 
1042 /******************************************************************************
1043  Function to add a style table to a kml container.
1044 ******************************************************************************/
1045 
styletable2kml(OGRStyleTable * poOgrStyleTable,KmlFactory * poKmlFactory,ContainerPtr poKmlContainer,char ** papszOptions)1046 void styletable2kml(
1047     OGRStyleTable * poOgrStyleTable,
1048     KmlFactory * poKmlFactory,
1049     ContainerPtr poKmlContainer,
1050     char** papszOptions )
1051 {
1052     /***** just return if the styletable is null *****/
1053     if( !poOgrStyleTable )
1054         return;
1055 
1056     std::set<CPLString> aoSetNormalStyles;
1057     std::set<CPLString> aoSetHighlightStyles;
1058     poOgrStyleTable->ResetStyleStringReading();
1059 
1060     // Collect styles that end with _normal or _highlight.
1061     while( poOgrStyleTable->GetNextStyle() != nullptr )
1062     {
1063         const char *pszStyleName = poOgrStyleTable->GetLastStyleName();
1064 
1065         if( strlen(pszStyleName) > strlen("_normal") &&
1066             EQUAL(pszStyleName + strlen(pszStyleName) -
1067                   strlen("_normal"), "_normal") )
1068         {
1069             CPLString osName(pszStyleName);
1070             osName.resize(strlen(pszStyleName) - strlen("_normal"));
1071             aoSetNormalStyles.insert(osName);
1072         }
1073         else if( strlen(pszStyleName) > strlen("_highlight") &&
1074                  EQUAL(pszStyleName + strlen(pszStyleName) -
1075                        strlen("_highlight"), "_highlight") )
1076         {
1077             CPLString osName(pszStyleName);
1078             osName.resize(strlen(pszStyleName) - strlen("_highlight"));
1079             aoSetHighlightStyles.insert(osName);
1080         }
1081     }
1082 
1083     /***** parse the style table *****/
1084     poOgrStyleTable->ResetStyleStringReading();
1085 
1086     const char *pszStyleString = nullptr;
1087     while( ( pszStyleString = poOgrStyleTable->GetNextStyle() ) != nullptr )
1088     {
1089         const char *pszStyleName = poOgrStyleTable->GetLastStyleName();
1090 
1091         if( aoSetNormalStyles.find(pszStyleName) != aoSetNormalStyles.end() &&
1092             aoSetHighlightStyles.find(pszStyleName) !=
1093                 aoSetHighlightStyles.end() )
1094         {
1095             continue;
1096         }
1097 
1098         /***** add the style header to the kml *****/
1099         StylePtr poKmlStyle = poKmlFactory->CreateStyle();
1100 
1101         poKmlStyle->set_id( pszStyleName );
1102 
1103         /***** parse the style string *****/
1104         addstylestring2kml( pszStyleString, poKmlStyle, poKmlFactory, nullptr );
1105 
1106         /***** add balloon style *****/
1107         const char* pszBalloonStyleBgColor =
1108             CSLFetchNameValue(papszOptions,
1109                               CPLSPrintf("%s_balloonstyle_bgcolor",
1110                                          pszStyleName));
1111         const char* pszBalloonStyleText =
1112             CSLFetchNameValue(papszOptions,
1113                               CPLSPrintf("%s_balloonstyle_text", pszStyleName));
1114         int nR = 0;
1115         int nG = 0;
1116         int nB = 0;
1117         int nA = 0;
1118         OGRStylePen oStyleTool;
1119         if( (pszBalloonStyleBgColor != nullptr &&
1120              oStyleTool.GetRGBFromString( pszBalloonStyleBgColor,
1121                                           nR, nG, nB, nA ) ) ||
1122             pszBalloonStyleText != nullptr )
1123         {
1124             const BalloonStylePtr poKmlBalloonStyle =
1125                 poKmlFactory->CreateBalloonStyle();
1126             if( pszBalloonStyleBgColor != nullptr &&
1127                 oStyleTool.GetRGBFromString( pszBalloonStyleBgColor,
1128                                              nR, nG, nB, nA ) )
1129                 poKmlBalloonStyle->set_bgcolor(
1130                     Color32( static_cast<GByte>(nA),
1131                              static_cast<GByte>(nB),
1132                              static_cast<GByte>(nG),
1133                              static_cast<GByte>(nR) ) );
1134             if( pszBalloonStyleText != nullptr )
1135                 poKmlBalloonStyle->set_text(pszBalloonStyleText);
1136             poKmlStyle->set_balloonstyle( poKmlBalloonStyle );
1137         }
1138 
1139         /***** add the style to the container *****/
1140         const DocumentPtr poKmlDocument = AsDocument( poKmlContainer );
1141         poKmlDocument->add_styleselector( poKmlStyle );
1142     }
1143 
1144     // Find style name that end with _normal and _highlight to create
1145     // a StyleMap from both.
1146     std::set<CPLString>::iterator aoSetNormalStylesIter =
1147         aoSetNormalStyles.begin();
1148     for( ;
1149          aoSetNormalStylesIter != aoSetNormalStyles.end();
1150          ++aoSetNormalStylesIter )
1151     {
1152         CPLString osStyleName(*aoSetNormalStylesIter);
1153         if( aoSetHighlightStyles.find(osStyleName) !=
1154                 aoSetHighlightStyles.end() )
1155         {
1156             StyleMapPtr poKmlStyleMap = poKmlFactory->CreateStyleMap();
1157             poKmlStyleMap->set_id( osStyleName );
1158 
1159             PairPtr poKmlPairNormal = poKmlFactory->CreatePair();
1160             poKmlPairNormal->set_key(STYLESTATE_NORMAL);
1161             poKmlPairNormal->set_styleurl(
1162                 CPLSPrintf("#%s_normal", osStyleName.c_str()));
1163             poKmlStyleMap->add_pair(poKmlPairNormal);
1164 
1165             PairPtr poKmlPairHighlight = poKmlFactory->CreatePair();
1166             poKmlPairHighlight->set_key(STYLESTATE_HIGHLIGHT);
1167             poKmlPairHighlight->set_styleurl(
1168                 CPLSPrintf("#%s_highlight", osStyleName.c_str()));
1169             poKmlStyleMap->add_pair(poKmlPairHighlight);
1170 
1171             /***** add the style to the container *****/
1172             DocumentPtr poKmlDocument = AsDocument( poKmlContainer );
1173             poKmlDocument->add_styleselector( poKmlStyleMap );
1174         }
1175     }
1176 }
1177 
1178 /******************************************************************************
1179  Function to add a ListStyle and select it to a container.
1180 ******************************************************************************/
1181 
createkmlliststyle(KmlFactory * poKmlFactory,const char * pszBaseName,ContainerPtr poKmlLayerContainer,DocumentPtr poKmlDocument,const CPLString & osListStyleType,const CPLString & osListStyleIconHref)1182 void createkmlliststyle(
1183     KmlFactory * poKmlFactory,
1184     const char* pszBaseName,
1185     ContainerPtr poKmlLayerContainer,
1186     DocumentPtr poKmlDocument,
1187     const CPLString& osListStyleType,
1188     const CPLString& osListStyleIconHref)
1189 {
1190     if( !osListStyleType.empty() || !osListStyleIconHref.empty() )
1191     {
1192         StylePtr poKmlStyle = poKmlFactory->CreateStyle();
1193 
1194         const char* pszStyleName =
1195             CPLSPrintf("%s_liststyle",
1196                        OGRLIBKMLGetSanitizedNCName(pszBaseName).c_str());
1197         poKmlStyle->set_id( pszStyleName );
1198 
1199         ListStylePtr poKmlListStyle = poKmlFactory->CreateListStyle();
1200         poKmlStyle->set_liststyle( poKmlListStyle );
1201         if( !osListStyleType.empty() )
1202         {
1203             if( EQUAL(osListStyleType, "check") )
1204                 poKmlListStyle->set_listitemtype( kmldom::LISTITEMTYPE_CHECK );
1205             else if( EQUAL(osListStyleType, "radioFolder") )
1206                 poKmlListStyle->set_listitemtype(
1207                     kmldom::LISTITEMTYPE_RADIOFOLDER );
1208             else if( EQUAL(osListStyleType, "checkOffOnly") )
1209                 poKmlListStyle->set_listitemtype(
1210                     kmldom::LISTITEMTYPE_CHECKOFFONLY );
1211             else if( EQUAL(osListStyleType, "checkHideChildren") )
1212                 poKmlListStyle->set_listitemtype(
1213                     kmldom::LISTITEMTYPE_CHECKHIDECHILDREN );
1214             else
1215             {
1216                 CPLError(
1217                     CE_Warning, CPLE_AppDefined,
1218                     "Invalid value for list style type: %s. "
1219                     "Defaulting to Check",
1220                     osListStyleType.c_str());
1221                 poKmlListStyle->set_listitemtype( kmldom::LISTITEMTYPE_CHECK );
1222             }
1223         }
1224 
1225         if( !osListStyleIconHref.empty() )
1226         {
1227             ItemIconPtr poItemIcon = poKmlFactory->CreateItemIcon();
1228             poItemIcon->set_href( osListStyleIconHref.c_str() );
1229             poKmlListStyle->add_itemicon(poItemIcon);
1230         }
1231 
1232         poKmlDocument->add_styleselector( poKmlStyle );
1233         poKmlLayerContainer->set_styleurl( CPLSPrintf("#%s", pszStyleName) );
1234     }
1235 }
1236