1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2012 Torsten Rahn <rahn@kde.org>
4 // SPDX-FileCopyrightText: 2012 Cezar Mocan <mocancezar@gmail.com>
5 // SPDX-FileCopyrightText: 2014 Abhinav Gangwar <abhgang@gmail.com>
6 //
7 // For the Natural Earth Layer providing the Default data set at 0.5 arcminute resolution should be enough.
8 // This fileformat allows for even better packed data than the PNT format. For detailed polygons at arcminute
9 // scale on average it should use only 33% of the amount used by PNT.
10 //
11 // Description of the file format
12 //
13 // In the fileformat initially a file header is provided that provides the file format version and the number
14 // of polygons stored inside the file. A Polygon starts with the Polygon Header which provides the feature id
15 // and the number of so called "absolute nodes" that are about to follow. Absolute nodes always contain
16 // absolute geodetic coordinates. The Polygon Header also provides a flag that allows to specify whether the
17 // polygon is supposed to represent a line string ("0") or a linear ring ("1"). Each absolute node can be followed
18 // by relative nodes: These relative nodes are always nodes that follow in correct order inside the polygon after
19 // "their" absolute node. Each absolute node specifies the number of relative nodes which contain relative
20 // coordinates in reference to their absolute node. So an absolute node provides the absolute reference for
21 // relative nodes across a theoretical area of 2x2 squaredegree-area (which in practice frequently might rather
22 // amount to 1x1 square degrees).
23 //
24 // So much of the compression works by just referencing lat/lon diffs to special "absolute nodes". Hence the
25 // compression will especially work well for polygons with many nodes with a high node density.
26 //
27 // The parser has to convert these relative coordinates to absolute coordinates.
28 //
29
30 #include "Pn2Runner.h"
31
32 #include "GeoDataDocument.h"
33 #include "GeoDataPlacemark.h"
34 #include "GeoDataStyle.h"
35 #include "GeoDataPolyStyle.h"
36 #include "GeoDataLinearRing.h"
37 #include "GeoDataPolygon.h"
38 #include "GeoDataMultiGeometry.h"
39 #include "MarbleDebug.h"
40
41 #include <QFile>
42 #include <QFileInfo>
43
44 namespace Marble
45 {
46 // Polygon header flags, representing the type of polygon
47 enum polygonFlagType { LINESTRING = 0, LINEARRING = 1, OUTERBOUNDARY = 2, INNERBOUNDARY = 3, MULTIGEOMETRY = 4 };
48
49
Pn2Runner(QObject * parent)50 Pn2Runner::Pn2Runner(QObject *parent) :
51 ParsingRunner(parent)
52 {
53 }
54
~Pn2Runner()55 Pn2Runner::~Pn2Runner()
56 {
57 }
58
errorCheckLat(qint16 lat)59 bool Pn2Runner::errorCheckLat( qint16 lat )
60 {
61 return !(lat >= -10800 && lat <= +10800);
62 }
63
errorCheckLon(qint16 lon)64 bool Pn2Runner::errorCheckLon( qint16 lon )
65 {
66 return !(lon >= -21600 && lon <= +21600);
67 }
68
importPolygon(QDataStream & stream,GeoDataLineString * linestring,quint32 nrAbsoluteNodes)69 bool Pn2Runner::importPolygon( QDataStream &stream, GeoDataLineString* linestring, quint32 nrAbsoluteNodes )
70 {
71 qint16 lat, lon, nrRelativeNodes;
72 qint8 relativeLat, relativeLon;
73 bool error = false;
74
75
76 for ( quint32 absoluteNode = 1; absoluteNode <= nrAbsoluteNodes; absoluteNode++ ) {
77 stream >> lat >> lon >> nrRelativeNodes;
78
79 error = error | errorCheckLat( lat ) | errorCheckLon( lon );
80
81 qreal degLat = ( 1.0 * lat / 120.0 );
82 qreal degLon = ( 1.0 * lon / 120.0 );
83
84 GeoDataCoordinates coord( degLon / 180 * M_PI, degLat / 180 * M_PI );
85 linestring->append( coord );
86
87 for ( qint16 relativeNode = 1; relativeNode <= nrRelativeNodes; ++relativeNode ) {
88 stream >> relativeLat >> relativeLon;
89
90 qint16 currLat = relativeLat + lat;
91 qint16 currLon = relativeLon + lon;
92
93
94 error = error | errorCheckLat( currLat ) | errorCheckLon( currLon );
95
96 qreal currDegLat = ( 1.0 * currLat / 120.0 );
97 qreal currDegLon = ( 1.0 * currLon / 120.0 );
98
99
100 GeoDataCoordinates currCoord( currDegLon / 180 * M_PI, currDegLat / 180 * M_PI );
101 linestring->append( currCoord );
102 }
103 }
104
105 *linestring = linestring->optimized();
106
107 return error;
108 }
109
parseFile(const QString & fileName,DocumentRole role,QString & error)110 GeoDataDocument *Pn2Runner::parseFile(const QString &fileName, DocumentRole role, QString &error)
111 {
112 QFileInfo fileinfo( fileName );
113 if (fileinfo.suffix().compare(QLatin1String("pn2"), Qt::CaseInsensitive) != 0) {
114 error = QStringLiteral("File %1 does not have a pn2 suffix").arg(fileName);
115 mDebug() << error;
116 return nullptr;
117 }
118
119 QFile file( fileName );
120 if ( !file.exists() ) {
121 error = QStringLiteral("File %1 does not exist").arg(fileName);
122 mDebug() << error;
123 return nullptr;
124 }
125
126 file.open( QIODevice::ReadOnly );
127 m_stream.setDevice( &file ); // read the data serialized from the file
128
129 m_stream >> m_fileHeaderVersion >> m_fileHeaderPolygons >> m_isMapColorField;
130
131 switch( m_fileHeaderVersion ) {
132 case 1: return parseForVersion1( fileName, role );
133 case 2: return parseForVersion2( fileName, role );
134 default: qDebug() << "File can't be parsed. We don't have parser for file header version:" << m_fileHeaderVersion;
135 break;
136 }
137
138 return nullptr;
139 }
140
parseForVersion1(const QString & fileName,DocumentRole role)141 GeoDataDocument* Pn2Runner::parseForVersion1(const QString& fileName, DocumentRole role)
142 {
143 GeoDataDocument *document = new GeoDataDocument();
144 document->setDocumentRole( role );
145
146 bool error = false;
147
148 quint32 ID, nrAbsoluteNodes;
149 quint8 flag, prevFlag = -1;
150
151 GeoDataStyle::Ptr style;
152 GeoDataPolygon *polygon = new GeoDataPolygon;
153
154 for ( quint32 currentPoly = 1; ( currentPoly <= m_fileHeaderPolygons ) && ( !error ) && ( !m_stream.atEnd() ); currentPoly++ ) {
155
156 m_stream >> ID >> nrAbsoluteNodes >> flag;
157
158 if ( flag != INNERBOUNDARY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) {
159
160 GeoDataPlacemark *placemark = new GeoDataPlacemark;
161 placemark->setGeometry( polygon );
162 if ( m_isMapColorField ) {
163 if ( style ) {
164 placemark->setStyle( style );
165 }
166 }
167 document->append( placemark );
168 }
169
170 if ( flag == LINESTRING ) {
171 GeoDataLineString *linestring = new GeoDataLineString;
172 error = error | importPolygon( m_stream, linestring, nrAbsoluteNodes );
173
174 GeoDataPlacemark *placemark = new GeoDataPlacemark;
175 placemark->setGeometry( linestring );
176 document->append( placemark );
177 }
178
179 if ( ( flag == LINEARRING ) || ( flag == OUTERBOUNDARY ) || ( flag == INNERBOUNDARY ) ) {
180 if ( flag == OUTERBOUNDARY && m_isMapColorField ) {
181 quint8 colorIndex;
182 m_stream >> colorIndex;
183 style = GeoDataStyle::Ptr(new GeoDataStyle);
184 GeoDataPolyStyle polyStyle;
185 polyStyle.setColorIndex( colorIndex );
186 style->setPolyStyle( polyStyle );
187 }
188
189 GeoDataLinearRing* linearring = new GeoDataLinearRing;
190 error = error | importPolygon( m_stream, linearring, nrAbsoluteNodes );
191
192 if ( flag == LINEARRING ) {
193 GeoDataPlacemark *placemark = new GeoDataPlacemark;
194 placemark->setGeometry( linearring );
195 document->append( placemark );
196 }
197
198 if ( flag == OUTERBOUNDARY ) {
199 polygon = new GeoDataPolygon;
200 polygon->setOuterBoundary( *linearring );
201 }
202
203 if ( flag == INNERBOUNDARY ) {
204 polygon->appendInnerBoundary( *linearring );
205 }
206 }
207
208 if ( flag == MULTIGEOMETRY ) {
209 // not implemented yet, for now elements inside a multigeometry are separated as individual geometries
210 }
211
212 prevFlag = flag;
213 }
214
215 if ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) {
216 GeoDataPlacemark *placemark = new GeoDataPlacemark;
217 if ( m_isMapColorField ) {
218 if ( style ) {
219 placemark->setStyle( style );
220 }
221 }
222 placemark->setGeometry( polygon );
223 document->append( placemark );
224 }
225
226 if ( error ) {
227 delete document;
228 document = nullptr;
229 return nullptr;
230 }
231 document->setFileName( fileName );
232 return document;
233 }
234
parseForVersion2(const QString & fileName,DocumentRole role)235 GeoDataDocument* Pn2Runner::parseForVersion2( const QString &fileName, DocumentRole role )
236 {
237 GeoDataDocument *document = new GeoDataDocument();
238 document->setDocumentRole( role );
239
240 bool error = false;
241
242 quint32 nrAbsoluteNodes;
243 quint32 placemarkCurrentID = 1;
244 quint32 placemarkPrevID = 0;
245 quint8 flag, prevFlag = -1;
246
247 GeoDataPolygon *polygon = new GeoDataPolygon;
248 GeoDataStyle::Ptr style;
249 GeoDataPlacemark *placemark =nullptr; // new GeoDataPlacemark;
250
251 quint32 currentPoly;
252 for ( currentPoly = 1; ( currentPoly <= m_fileHeaderPolygons ) && ( !error ) && ( !m_stream.atEnd() ); currentPoly++ ) {
253 m_stream >> flag >> placemarkCurrentID;
254
255 if ( flag == MULTIGEOMETRY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) {
256 if ( placemark ) {
257 placemark->setGeometry( polygon );
258 }
259 }
260
261 if ( flag != MULTIGEOMETRY && flag != INNERBOUNDARY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) {
262 if ( placemark ) {
263 placemark->setGeometry( polygon );
264 }
265 }
266 /**
267 * If the parsed placemark id @p placemarkCurrentID is different
268 * from the id of previous placemark @p placemarkPrevID, it means
269 * we have encountered a new placemark. So, prepare a style @p style
270 * if file has color indices
271 */
272 if ( placemarkCurrentID != placemarkPrevID ) {
273 placemark = new GeoDataPlacemark;
274
275 // Handle the color index
276 if( m_isMapColorField ) {
277 quint8 colorIndex;
278 m_stream >> colorIndex;
279 style = GeoDataStyle::Ptr(new GeoDataStyle);
280 GeoDataPolyStyle polyStyle;
281 polyStyle.setColorIndex( colorIndex );
282 polyStyle.setFill( true );
283 style->setPolyStyle( polyStyle );
284 placemark->setStyle( style );
285 }
286
287 document->append( placemark );
288 }
289
290 placemarkPrevID = placemarkCurrentID;
291
292 if ( flag != MULTIGEOMETRY ) {
293 m_stream >> nrAbsoluteNodes;
294
295 if ( flag == LINESTRING ) {
296 GeoDataLineString *linestring = new GeoDataLineString;
297 error = error | importPolygon( m_stream, linestring, nrAbsoluteNodes );
298 if ( placemark ) {
299 placemark->setGeometry( linestring );
300 }
301 }
302
303 if ( ( flag == LINEARRING ) || ( flag == OUTERBOUNDARY ) || ( flag == INNERBOUNDARY ) ) {
304 GeoDataLinearRing* linearring = new GeoDataLinearRing;
305 error = error || importPolygon( m_stream, linearring, nrAbsoluteNodes );
306
307 if ( flag == LINEARRING ) {
308 if ( placemark ) {
309 placemark->setGeometry( linearring );
310 }
311 } else {
312 if ( flag == OUTERBOUNDARY ) {
313 polygon = new GeoDataPolygon;
314 polygon->setOuterBoundary( *linearring );
315 }
316
317 if ( flag == INNERBOUNDARY ) {
318 polygon->appendInnerBoundary( *linearring );
319 }
320
321 delete linearring;
322 }
323 }
324 prevFlag = flag;
325 }
326
327 else {
328 quint32 placemarkCurrentIDInMulti;
329 quint8 flagInMulti;
330 quint8 prevFlagInMulti = -1;
331 quint8 multiSize = 0;
332
333 m_stream >> multiSize;
334
335 GeoDataMultiGeometry *multigeom = new GeoDataMultiGeometry;
336
337 /**
338 * Read @p multiSize GeoDataGeometry objects
339 */
340 for ( int iter = 0; iter < multiSize; ++iter ) {
341 m_stream >> flagInMulti >> placemarkCurrentIDInMulti >> nrAbsoluteNodes;
342 if ( flagInMulti != INNERBOUNDARY && ( prevFlagInMulti == INNERBOUNDARY || prevFlagInMulti == OUTERBOUNDARY ) ) {
343 multigeom->append( polygon );
344 }
345
346 if ( flagInMulti == LINESTRING ) {
347 GeoDataLineString *linestring = new GeoDataLineString;
348 error = error || importPolygon( m_stream, linestring, nrAbsoluteNodes );
349 multigeom->append( linestring );
350 }
351
352 if ( ( flagInMulti == LINEARRING ) || ( flagInMulti == OUTERBOUNDARY ) || ( flagInMulti == INNERBOUNDARY ) ) {
353 GeoDataLinearRing* linearring = new GeoDataLinearRing;
354 error = error | importPolygon( m_stream, linearring, nrAbsoluteNodes );
355
356 if ( flagInMulti == LINEARRING ) {
357 multigeom->append( linearring );
358 } else {
359 if ( flagInMulti == OUTERBOUNDARY ) {
360 polygon = new GeoDataPolygon;
361 polygon->setOuterBoundary( *linearring );
362 }
363
364 if ( flagInMulti == INNERBOUNDARY ) {
365 polygon->appendInnerBoundary( *linearring );
366 }
367
368 delete linearring;
369 }
370 }
371 prevFlagInMulti = flagInMulti;
372 }
373
374 if ( prevFlagInMulti == INNERBOUNDARY || prevFlagInMulti == OUTERBOUNDARY ) {
375 multigeom->append( polygon );
376 }
377 if ( placemark ) {
378 placemark->setGeometry( multigeom );
379 }
380 prevFlag = MULTIGEOMETRY;
381 }
382 }
383
384 if ( (prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY) && prevFlag != MULTIGEOMETRY ) {
385 placemark->setGeometry( polygon );
386 }
387
388 if ( error ) {
389 delete document;
390 document = nullptr;
391 return nullptr;
392 }
393 document->setFileName( fileName );
394 return document;
395 }
396
397 }
398
399
400 #include "moc_Pn2Runner.cpp"
401