File indexing completed on 2025-01-05 03:59:30
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2012 Torsten Rahn <rahn@kde.org> 0004 // SPDX-FileCopyrightText: 2012 Cezar Mocan <mocancezar@gmail.com> 0005 // SPDX-FileCopyrightText: 2014 Abhinav Gangwar <abhgang@gmail.com> 0006 // 0007 // For the Natural Earth Layer providing the Default data set at 0.5 arcminute resolution should be enough. 0008 // This fileformat allows for even better packed data than the PNT format. For detailed polygons at arcminute 0009 // scale on average it should use only 33% of the amount used by PNT. 0010 // 0011 // Description of the file format 0012 // 0013 // In the fileformat initially a file header is provided that provides the file format version and the number 0014 // of polygons stored inside the file. A Polygon starts with the Polygon Header which provides the feature id 0015 // and the number of so called "absolute nodes" that are about to follow. Absolute nodes always contain 0016 // absolute geodetic coordinates. The Polygon Header also provides a flag that allows to specify whether the 0017 // polygon is supposed to represent a line string ("0") or a linear ring ("1"). Each absolute node can be followed 0018 // by relative nodes: These relative nodes are always nodes that follow in correct order inside the polygon after 0019 // "their" absolute node. Each absolute node specifies the number of relative nodes which contain relative 0020 // coordinates in reference to their absolute node. So an absolute node provides the absolute reference for 0021 // relative nodes across a theoretical area of 2x2 squaredegree-area (which in practice frequently might rather 0022 // amount to 1x1 square degrees). 0023 // 0024 // So much of the compression works by just referencing lat/lon diffs to special "absolute nodes". Hence the 0025 // compression will especially work well for polygons with many nodes with a high node density. 0026 // 0027 // The parser has to convert these relative coordinates to absolute coordinates. 0028 // 0029 0030 #include "Pn2Runner.h" 0031 0032 #include <QFile> 0033 #include <QFileInfo> 0034 0035 #include "GeoDataDocument.h" 0036 #include "GeoDataPlacemark.h" 0037 #include "GeoDataStyle.h" 0038 #include "GeoDataPolyStyle.h" 0039 #include "GeoDataLinearRing.h" 0040 #include "GeoDataPolygon.h" 0041 #include "GeoDataMultiGeometry.h" 0042 0043 #include "digikam_debug.h" 0044 0045 namespace Marble 0046 { 0047 // Polygon header flags, representing the type of polygon 0048 enum polygonFlagType { LINESTRING = 0, LINEARRING = 1, OUTERBOUNDARY = 2, INNERBOUNDARY = 3, MULTIGEOMETRY = 4 }; 0049 0050 0051 Pn2Runner::Pn2Runner(QObject *parent) : 0052 ParsingRunner(parent) 0053 { 0054 } 0055 0056 Pn2Runner::~Pn2Runner() 0057 { 0058 } 0059 0060 bool Pn2Runner::errorCheckLat( qint16 lat ) 0061 { 0062 return !(lat >= -10800 && lat <= +10800); 0063 } 0064 0065 bool Pn2Runner::errorCheckLon( qint16 lon ) 0066 { 0067 return !(lon >= -21600 && lon <= +21600); 0068 } 0069 0070 bool Pn2Runner::importPolygon( QDataStream &stream, GeoDataLineString* linestring, quint32 nrAbsoluteNodes ) 0071 { 0072 qint16 lat, lon, nrRelativeNodes; 0073 qint8 relativeLat, relativeLon; 0074 bool error = false; 0075 0076 0077 for ( quint32 absoluteNode = 1; absoluteNode <= nrAbsoluteNodes; absoluteNode++ ) { 0078 stream >> lat >> lon >> nrRelativeNodes; 0079 0080 error = error | errorCheckLat( lat ) | errorCheckLon( lon ); 0081 0082 qreal degLat = ( 1.0 * lat / 120.0 ); 0083 qreal degLon = ( 1.0 * lon / 120.0 ); 0084 0085 GeoDataCoordinates coord( degLon / 180 * M_PI, degLat / 180 * M_PI ); 0086 linestring->append( coord ); 0087 0088 for ( qint16 relativeNode = 1; relativeNode <= nrRelativeNodes; ++relativeNode ) { 0089 stream >> relativeLat >> relativeLon; 0090 0091 qint16 currLat = relativeLat + lat; 0092 qint16 currLon = relativeLon + lon; 0093 0094 0095 error = error | errorCheckLat( currLat ) | errorCheckLon( currLon ); 0096 0097 qreal currDegLat = ( 1.0 * currLat / 120.0 ); 0098 qreal currDegLon = ( 1.0 * currLon / 120.0 ); 0099 0100 0101 GeoDataCoordinates currCoord( currDegLon / 180 * M_PI, currDegLat / 180 * M_PI ); 0102 linestring->append( currCoord ); 0103 } 0104 } 0105 0106 *linestring = linestring->optimized(); 0107 0108 return error; 0109 } 0110 0111 GeoDataDocument *Pn2Runner::parseFile(const QString &fileName, DocumentRole role, QString &error) 0112 { 0113 QFileInfo fileinfo( fileName ); 0114 if (fileinfo.suffix().compare(QLatin1String("pn2"), Qt::CaseInsensitive) != 0) { 0115 error = QStringLiteral("File %1 does not have a pn2 suffix").arg(fileName); 0116 qCDebug(DIGIKAM_MARBLE_LOG) << error; 0117 return nullptr; 0118 } 0119 0120 QFile file( fileName ); 0121 if ( !file.exists() ) { 0122 error = QStringLiteral("File %1 does not exist").arg(fileName); 0123 qCDebug(DIGIKAM_MARBLE_LOG) << error; 0124 return nullptr; 0125 } 0126 0127 file.open( QIODevice::ReadOnly ); 0128 m_stream.setDevice( &file ); // read the data serialized from the file 0129 0130 m_stream >> m_fileHeaderVersion >> m_fileHeaderPolygons >> m_isMapColorField; 0131 0132 switch( m_fileHeaderVersion ) { 0133 case 1: return parseForVersion1( fileName, role ); 0134 case 2: return parseForVersion2( fileName, role ); 0135 default: qCDebug(DIGIKAM_MARBLE_LOG) << "File can't be parsed. We don't have parser for file header version:" << m_fileHeaderVersion; 0136 break; 0137 } 0138 0139 return nullptr; 0140 } 0141 0142 GeoDataDocument* Pn2Runner::parseForVersion1(const QString& fileName, DocumentRole role) 0143 { 0144 GeoDataDocument *document = new GeoDataDocument(); 0145 document->setDocumentRole( role ); 0146 0147 bool error = false; 0148 0149 quint32 ID, nrAbsoluteNodes; 0150 quint8 flag, prevFlag = -1; 0151 0152 GeoDataStyle::Ptr style; 0153 GeoDataPolygon *polygon = new GeoDataPolygon; 0154 0155 for ( quint32 currentPoly = 1; ( currentPoly <= m_fileHeaderPolygons ) && ( !error ) && ( !m_stream.atEnd() ); currentPoly++ ) { 0156 0157 m_stream >> ID >> nrAbsoluteNodes >> flag; 0158 0159 if ( flag != INNERBOUNDARY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) { 0160 0161 GeoDataPlacemark *placemark = new GeoDataPlacemark; 0162 placemark->setGeometry( polygon ); 0163 if ( m_isMapColorField ) { 0164 if ( style ) { 0165 placemark->setStyle( style ); 0166 } 0167 } 0168 document->append( placemark ); 0169 } 0170 0171 if ( flag == LINESTRING ) { 0172 GeoDataLineString *linestring = new GeoDataLineString; 0173 error = error | importPolygon( m_stream, linestring, nrAbsoluteNodes ); 0174 0175 GeoDataPlacemark *placemark = new GeoDataPlacemark; 0176 placemark->setGeometry( linestring ); 0177 document->append( placemark ); 0178 } 0179 0180 if ( ( flag == LINEARRING ) || ( flag == OUTERBOUNDARY ) || ( flag == INNERBOUNDARY ) ) { 0181 if ( flag == OUTERBOUNDARY && m_isMapColorField ) { 0182 quint8 colorIndex; 0183 m_stream >> colorIndex; 0184 style = GeoDataStyle::Ptr(new GeoDataStyle); 0185 GeoDataPolyStyle polyStyle; 0186 polyStyle.setColorIndex( colorIndex ); 0187 style->setPolyStyle( polyStyle ); 0188 } 0189 0190 GeoDataLinearRing* linearring = new GeoDataLinearRing; 0191 error = error | importPolygon( m_stream, linearring, nrAbsoluteNodes ); 0192 0193 if ( flag == LINEARRING ) { 0194 GeoDataPlacemark *placemark = new GeoDataPlacemark; 0195 placemark->setGeometry( linearring ); 0196 document->append( placemark ); 0197 } 0198 0199 if ( flag == OUTERBOUNDARY ) { 0200 polygon = new GeoDataPolygon; 0201 polygon->setOuterBoundary( *linearring ); 0202 } 0203 0204 if ( flag == INNERBOUNDARY ) { 0205 polygon->appendInnerBoundary( *linearring ); 0206 } 0207 } 0208 0209 if ( flag == MULTIGEOMETRY ) { 0210 // not implemented yet, for now elements inside a multigeometry are separated as individual geometries 0211 } 0212 0213 prevFlag = flag; 0214 } 0215 0216 if ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) { 0217 GeoDataPlacemark *placemark = new GeoDataPlacemark; 0218 if ( m_isMapColorField ) { 0219 if ( style ) { 0220 placemark->setStyle( style ); 0221 } 0222 } 0223 placemark->setGeometry( polygon ); 0224 document->append( placemark ); 0225 } 0226 0227 if ( error ) { 0228 delete document; 0229 document = nullptr; 0230 return nullptr; 0231 } 0232 document->setFileName( fileName ); 0233 return document; 0234 } 0235 0236 GeoDataDocument* Pn2Runner::parseForVersion2( const QString &fileName, DocumentRole role ) 0237 { 0238 GeoDataDocument *document = new GeoDataDocument(); 0239 document->setDocumentRole( role ); 0240 0241 bool error = false; 0242 0243 quint32 nrAbsoluteNodes; 0244 quint32 placemarkCurrentID = 1; 0245 quint32 placemarkPrevID = 0; 0246 quint8 flag, prevFlag = -1; 0247 0248 GeoDataPolygon *polygon = new GeoDataPolygon; 0249 GeoDataStyle::Ptr style; 0250 GeoDataPlacemark *placemark =nullptr; // new GeoDataPlacemark; 0251 0252 quint32 currentPoly; 0253 for ( currentPoly = 1; ( currentPoly <= m_fileHeaderPolygons ) && ( !error ) && ( !m_stream.atEnd() ); currentPoly++ ) { 0254 m_stream >> flag >> placemarkCurrentID; 0255 0256 if ( flag == MULTIGEOMETRY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) { 0257 if ( placemark ) { 0258 placemark->setGeometry( polygon ); 0259 } 0260 } 0261 0262 if ( flag != MULTIGEOMETRY && flag != INNERBOUNDARY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) { 0263 if ( placemark ) { 0264 placemark->setGeometry( polygon ); 0265 } 0266 } 0267 /** 0268 * If the parsed placemark id @p placemarkCurrentID is different 0269 * from the id of previous placemark @p placemarkPrevID, it means 0270 * we have encountered a new placemark. So, prepare a style @p style 0271 * if file has color indices 0272 */ 0273 if ( placemarkCurrentID != placemarkPrevID ) { 0274 placemark = new GeoDataPlacemark; 0275 0276 // Handle the color index 0277 if( m_isMapColorField ) { 0278 quint8 colorIndex; 0279 m_stream >> colorIndex; 0280 style = GeoDataStyle::Ptr(new GeoDataStyle); 0281 GeoDataPolyStyle polyStyle; 0282 polyStyle.setColorIndex( colorIndex ); 0283 polyStyle.setFill( true ); 0284 style->setPolyStyle( polyStyle ); 0285 placemark->setStyle( style ); 0286 } 0287 0288 document->append( placemark ); 0289 } 0290 0291 placemarkPrevID = placemarkCurrentID; 0292 0293 if ( flag != MULTIGEOMETRY ) { 0294 m_stream >> nrAbsoluteNodes; 0295 0296 if ( flag == LINESTRING ) { 0297 GeoDataLineString *linestring = new GeoDataLineString; 0298 error = error | importPolygon( m_stream, linestring, nrAbsoluteNodes ); 0299 if ( placemark ) { 0300 placemark->setGeometry( linestring ); 0301 } 0302 } 0303 0304 if ( ( flag == LINEARRING ) || ( flag == OUTERBOUNDARY ) || ( flag == INNERBOUNDARY ) ) { 0305 GeoDataLinearRing* linearring = new GeoDataLinearRing; 0306 error = error || importPolygon( m_stream, linearring, nrAbsoluteNodes ); 0307 0308 if ( flag == LINEARRING ) { 0309 if ( placemark ) { 0310 placemark->setGeometry( linearring ); 0311 } 0312 } else { 0313 if ( flag == OUTERBOUNDARY ) { 0314 polygon = new GeoDataPolygon; 0315 polygon->setOuterBoundary( *linearring ); 0316 } 0317 0318 if ( flag == INNERBOUNDARY ) { 0319 polygon->appendInnerBoundary( *linearring ); 0320 } 0321 0322 delete linearring; 0323 } 0324 } 0325 prevFlag = flag; 0326 } 0327 0328 else { 0329 quint32 placemarkCurrentIDInMulti; 0330 quint8 flagInMulti; 0331 quint8 prevFlagInMulti = -1; 0332 quint8 multiSize = 0; 0333 0334 m_stream >> multiSize; 0335 0336 GeoDataMultiGeometry *multigeom = new GeoDataMultiGeometry; 0337 0338 /** 0339 * Read @p multiSize GeoDataGeometry objects 0340 */ 0341 for ( int iter = 0; iter < multiSize; ++iter ) { 0342 m_stream >> flagInMulti >> placemarkCurrentIDInMulti >> nrAbsoluteNodes; 0343 if ( flagInMulti != INNERBOUNDARY && ( prevFlagInMulti == INNERBOUNDARY || prevFlagInMulti == OUTERBOUNDARY ) ) { 0344 multigeom->append( polygon ); 0345 } 0346 0347 if ( flagInMulti == LINESTRING ) { 0348 GeoDataLineString *linestring = new GeoDataLineString; 0349 error = error || importPolygon( m_stream, linestring, nrAbsoluteNodes ); 0350 multigeom->append( linestring ); 0351 } 0352 0353 if ( ( flagInMulti == LINEARRING ) || ( flagInMulti == OUTERBOUNDARY ) || ( flagInMulti == INNERBOUNDARY ) ) { 0354 GeoDataLinearRing* linearring = new GeoDataLinearRing; 0355 error = error | importPolygon( m_stream, linearring, nrAbsoluteNodes ); 0356 0357 if ( flagInMulti == LINEARRING ) { 0358 multigeom->append( linearring ); 0359 } else { 0360 if ( flagInMulti == OUTERBOUNDARY ) { 0361 polygon = new GeoDataPolygon; 0362 polygon->setOuterBoundary( *linearring ); 0363 } 0364 0365 if ( flagInMulti == INNERBOUNDARY ) { 0366 polygon->appendInnerBoundary( *linearring ); 0367 } 0368 0369 delete linearring; 0370 } 0371 } 0372 prevFlagInMulti = flagInMulti; 0373 } 0374 0375 if ( prevFlagInMulti == INNERBOUNDARY || prevFlagInMulti == OUTERBOUNDARY ) { 0376 multigeom->append( polygon ); 0377 } 0378 if ( placemark ) { 0379 placemark->setGeometry( multigeom ); 0380 } 0381 prevFlag = MULTIGEOMETRY; 0382 } 0383 } 0384 0385 if ( (prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY) && prevFlag != MULTIGEOMETRY ) { 0386 placemark->setGeometry( polygon ); 0387 } 0388 0389 if ( error ) { 0390 delete document; 0391 document = nullptr; 0392 return nullptr; 0393 } 0394 document->setFileName( fileName ); 0395 return document; 0396 } 0397 0398 } 0399 0400 0401 #include "moc_Pn2Runner.cpp"