1 /*
2     SPDX-FileCopyrightText: 2008 Patrick Spendrin <ps_ml@gmx.de>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "KmlCoordinatesTagHandler.h"
8 
9 #include <QStringList>
10 #include <QRegExp>
11 
12 #include "MarbleDebug.h"
13 #include "KmlElementDictionary.h"
14 #include "GeoDataTrack.h"
15 #include "GeoDataPlacemark.h"
16 #include "GeoDataPoint.h"
17 #include "GeoDataModel.h"
18 #include "GeoDataLineString.h"
19 #include "GeoDataLinearRing.h"
20 #include "GeoDataMultiGeometry.h"
21 #include "GeoDataLatLonQuad.h"
22 #include "GeoParser.h"
23 #include "MarbleGlobal.h"
24 
25 namespace Marble
26 {
27 namespace kml
28 {
29 KML_DEFINE_TAG_HANDLER( coordinates )
30 
31 static const bool kmlStrictSpecs = false;
32 
33 // We can't use KML_DEFINE_TAG_HANDLER_GX22 because the name of the tag ("coord")
34 // and the TagHandler ("KmlcoordinatesTagHandler") don't match
35 static GeoTagHandlerRegistrar s_handlercoordkmlTag_nameSpaceGx22(GeoParser::QualifiedName(QLatin1String(kmlTag_coord), QLatin1String(kmlTag_nameSpaceGx22)),
36                                                                  new KmlcoordinatesTagHandler());
37 
parse(GeoParser & parser) const38 GeoNode* KmlcoordinatesTagHandler::parse( GeoParser& parser ) const
39 {
40     Q_ASSERT(parser.isStartElement()
41              && (parser.isValidElement(QLatin1String(kmlTag_coordinates))
42                  || parser.isValidElement(QLatin1String(kmlTag_coord))));
43 
44     GeoStackItem parentItem = parser.parentElement();
45 
46     if( parentItem.represents( kmlTag_Point )
47      || parentItem.represents( kmlTag_LineString )
48      || parentItem.represents( kmlTag_MultiGeometry )
49      || parentItem.represents( kmlTag_LinearRing )
50      || parentItem.represents( kmlTag_LatLonQuad ) ) {
51         QStringList  coordinatesLines;// = parser.readElementText().trimmed().split( QRegExp("\\s"), QString::SkipEmptyParts );
52         // Splitting using the "\\s" regexp is slow, split manually instead.
53         QString text = parser.readElementText().trimmed();
54 
55         if ( !kmlStrictSpecs ) {
56             // Removing spaces before and after commas
57             for ( int i = 1; i < text.size() - 1; ++i ) {
58                 if (text[i] == QLatin1Char(',')) {
59                     // Before
60                     int l = i - 1;
61                     while ( l > 0 && text[l].isSpace() ) {
62                         --l;
63                     }
64 
65                     // After
66                     int r = i + 1;
67                     while ( r < text.size() && text[r].isSpace() ) {
68                         ++r;
69                     }
70 
71                     text.remove(l + 1, r - l - 1).insert(l + 1, QLatin1Char(','));
72                 }
73             }
74         }
75 
76         int index = 0;
77         bool inside = true;
78         int const size = text.size();
79         for ( int i=0; i<size; ++i ) {
80             if ( text[i].isSpace() ) {
81                 if ( inside ) {
82                     coordinatesLines.append( text.mid( index, i-index ) );
83                     inside = false;
84                 }
85                 index = i+1;
86             } else {
87                 inside = true;
88             }
89         }
90         coordinatesLines.append( text.mid( index ) );
91         int coordinatesIndex = 0;
92         for( const QString& line: coordinatesLines ) {
93             const QStringList coordinates = line.trimmed().split(QLatin1Char(','));
94             if ( parentItem.represents( kmlTag_Point ) && parentItem.is<GeoDataFeature>() ) {
95                 GeoDataCoordinates coord;
96                 if ( coordinates.size() == 2 ) {
97                     coord.set( coordinates.at( 0 ).toDouble(),
98                               coordinates.at( 1 ).toDouble(), 0.0, GeoDataCoordinates::Degree );
99                 } else if( coordinates.size() == 3 ) {
100                     coord.set( coordinates.at( 0 ).toDouble(),
101                                coordinates.at( 1 ).toDouble(),
102                                coordinates.at( 2 ).toDouble(),
103                                GeoDataCoordinates::Degree );
104                 }
105                 parentItem.nodeAs<GeoDataPlacemark>()->setCoordinate( coord );
106             } else {
107                 GeoDataCoordinates coord;
108                 if ( coordinates.size() == 2 ) {
109                     coord.set( DEG2RAD * coordinates.at( 0 ).toDouble(),
110                               DEG2RAD * coordinates.at( 1 ).toDouble() );
111                 } else if( coordinates.size() == 3 ) {
112                     coord.set( DEG2RAD * coordinates.at( 0 ).toDouble(),
113                               DEG2RAD * coordinates.at( 1 ).toDouble(),
114                               coordinates.at( 2 ).toDouble() );
115                 }
116 
117                 if ( parentItem.represents( kmlTag_LineString ) ) {
118                     parentItem.nodeAs<GeoDataLineString>()->append( coord );
119                 } else if ( parentItem.represents( kmlTag_LinearRing ) ) {
120                     parentItem.nodeAs<GeoDataLinearRing>()->append( coord );
121                 } else if ( parentItem.represents( kmlTag_MultiGeometry ) ) {
122                     GeoDataPoint *point = new GeoDataPoint( coord );
123                     parentItem.nodeAs<GeoDataMultiGeometry>()->append( point );
124                 } else if ( parentItem.represents( kmlTag_Model) ) {
125                     parentItem.nodeAs<GeoDataModel>()->setCoordinates( coord);
126                 } else if ( parentItem.represents( kmlTag_Point ) ) {
127                     // photo overlay
128                     parentItem.nodeAs<GeoDataPoint>()->setCoordinates( coord );
129                 } else if ( parentItem.represents( kmlTag_LatLonQuad ) ) {
130                     switch ( coordinatesIndex ) {
131                     case 0:
132                         parentItem.nodeAs<GeoDataLatLonQuad>()->setBottomLeft( coord );
133                         break;
134                     case 1:
135                         parentItem.nodeAs<GeoDataLatLonQuad>()->setBottomRight( coord );
136                         break;
137                     case 2:
138                         parentItem.nodeAs<GeoDataLatLonQuad>()->setTopRight( coord );
139                         break;
140                     case 3:
141                         parentItem.nodeAs<GeoDataLatLonQuad>()->setTopLeft( coord );
142                         break;
143                     case 4:
144                         mDebug() << "Ignoring excessive coordinates in LatLonQuad (must not have more than 4 pairs)";
145                         break;
146                     default:
147                         // Silently ignore any more coordinates
148                         break;
149                     }
150                 } else {
151                     // raise warning as coordinates out of valid parents found
152                 }
153             }
154 
155             ++coordinatesIndex;
156         }
157     }
158 
159     if( parentItem.represents( kmlTag_Track ) ) {
160         QString input = parser.readElementText().trimmed();
161         if ( !kmlStrictSpecs ) {
162             input.replace(QRegExp(QStringLiteral("\\s*,\\s*")), QStringLiteral(","));
163         }
164         const QStringList coordinates = input.split(QLatin1Char(' '));
165 
166         GeoDataCoordinates coord;
167         if ( coordinates.size() == 2 ) {
168             coord.set( DEG2RAD * coordinates.at( 0 ).toDouble(),
169                        DEG2RAD * coordinates.at( 1 ).toDouble() );
170         } else if( coordinates.size() == 3 ) {
171             coord.set( DEG2RAD * coordinates.at( 0 ).toDouble(),
172                        DEG2RAD * coordinates.at( 1 ).toDouble(),
173                        coordinates.at( 2 ).toDouble() );
174         }
175         parentItem.nodeAs<GeoDataTrack>()->appendCoordinates( coord );
176     }
177 
178     return nullptr;
179 }
180 
181 }
182 }
183