File indexing completed on 2024-07-14 07:23:10

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 "GeoDataDocument.h"
0033 #include "GeoDataPlacemark.h"
0034 #include "GeoDataStyle.h"
0035 #include "GeoDataPolyStyle.h"
0036 #include "GeoDataLinearRing.h"
0037 #include "GeoDataPolygon.h"
0038 #include "GeoDataMultiGeometry.h"
0039 #include "MarbleDebug.h"
0040 
0041 #include <QFile>
0042 #include <QFileInfo>
0043 
0044 namespace Marble
0045 {
0046 // Polygon header flags, representing the type of polygon
0047 enum polygonFlagType { LINESTRING = 0, LINEARRING = 1, OUTERBOUNDARY = 2, INNERBOUNDARY = 3, MULTIGEOMETRY = 4 };
0048 
0049 
0050 Pn2Runner::Pn2Runner(QObject *parent) :
0051     ParsingRunner(parent)
0052 {
0053 }
0054 
0055 Pn2Runner::~Pn2Runner()
0056 {
0057 }
0058 
0059 bool Pn2Runner::errorCheckLat( qint16 lat ) 
0060 {
0061     return !(lat >= -10800 && lat <= +10800);
0062 }
0063 
0064 bool Pn2Runner::errorCheckLon( qint16 lon )
0065 {
0066     return !(lon >= -21600 && lon <= +21600);
0067 }
0068 
0069 bool Pn2Runner::importPolygon( QDataStream &stream, GeoDataLineString* linestring, quint32 nrAbsoluteNodes ) 
0070 {
0071     qint16 lat, lon, nrRelativeNodes;
0072     qint8 relativeLat, relativeLon;
0073     bool error = false;
0074 
0075 
0076     for ( quint32 absoluteNode = 1; absoluteNode <= nrAbsoluteNodes; absoluteNode++ ) {
0077         stream >> lat >> lon >> nrRelativeNodes;
0078 
0079         error = error | errorCheckLat( lat ) | errorCheckLon( lon );
0080 
0081         qreal degLat = ( 1.0 * lat / 120.0 );
0082         qreal degLon = ( 1.0 * lon / 120.0 );
0083 
0084         GeoDataCoordinates coord( degLon / 180 * M_PI, degLat / 180 * M_PI );
0085         linestring->append( coord );
0086 
0087         for ( qint16 relativeNode = 1; relativeNode <= nrRelativeNodes; ++relativeNode ) {
0088             stream >> relativeLat >> relativeLon;
0089 
0090             qint16 currLat = relativeLat + lat; 
0091             qint16 currLon = relativeLon + lon;
0092 
0093 
0094             error = error | errorCheckLat( currLat ) | errorCheckLon( currLon );
0095 
0096             qreal currDegLat = ( 1.0 * currLat / 120.0 );
0097             qreal currDegLon = ( 1.0 * currLon / 120.0 );
0098 
0099 
0100             GeoDataCoordinates currCoord( currDegLon / 180 * M_PI, currDegLat / 180 * M_PI );
0101             linestring->append( currCoord );
0102         }
0103     }
0104 
0105     *linestring = linestring->optimized();
0106 
0107     return error;
0108 }
0109 
0110 GeoDataDocument *Pn2Runner::parseFile(const QString &fileName, DocumentRole role, QString &error)
0111 {
0112     QFileInfo fileinfo( fileName );
0113     if (fileinfo.suffix().compare(QLatin1String("pn2"), Qt::CaseInsensitive) != 0) {
0114         error = QStringLiteral("File %1 does not have a pn2 suffix").arg(fileName);
0115         mDebug() << error;
0116         return nullptr;
0117     }
0118 
0119     QFile  file( fileName );
0120     if ( !file.exists() ) {
0121         error = QStringLiteral("File %1 does not exist").arg(fileName);
0122         mDebug() << error;
0123         return nullptr;
0124     }
0125 
0126     file.open( QIODevice::ReadOnly );
0127     m_stream.setDevice( &file );  // read the data serialized from the file
0128 
0129     m_stream >> m_fileHeaderVersion >> m_fileHeaderPolygons >> m_isMapColorField;
0130 
0131     switch( m_fileHeaderVersion ) {
0132         case 1: return parseForVersion1( fileName, role );
0133         case 2: return parseForVersion2( fileName, role );
0134         default: qDebug() << "File can't be parsed. We don't have parser for file header version:" << m_fileHeaderVersion;
0135                 break;
0136     }
0137 
0138     return nullptr;
0139 }
0140 
0141 GeoDataDocument* Pn2Runner::parseForVersion1(const QString& fileName, DocumentRole role)
0142 {
0143     GeoDataDocument *document = new GeoDataDocument();
0144     document->setDocumentRole( role );
0145 
0146     bool error = false;
0147 
0148     quint32 ID, nrAbsoluteNodes;
0149     quint8 flag, prevFlag = -1;
0150 
0151     GeoDataStyle::Ptr style;
0152     GeoDataPolygon *polygon = new GeoDataPolygon;
0153 
0154     for ( quint32 currentPoly = 1; ( currentPoly <= m_fileHeaderPolygons ) && ( !error ) && ( !m_stream.atEnd() ); currentPoly++ ) {
0155 
0156         m_stream >> ID >> nrAbsoluteNodes >> flag;
0157 
0158         if ( flag != INNERBOUNDARY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) {
0159 
0160             GeoDataPlacemark *placemark = new GeoDataPlacemark;
0161             placemark->setGeometry( polygon );
0162             if ( m_isMapColorField ) {
0163                 if ( style ) {
0164                     placemark->setStyle( style );
0165                 }
0166             }
0167             document->append( placemark );
0168         }
0169 
0170         if ( flag == LINESTRING ) {
0171             GeoDataLineString *linestring = new GeoDataLineString;
0172             error = error | importPolygon( m_stream, linestring, nrAbsoluteNodes );
0173 
0174             GeoDataPlacemark *placemark = new GeoDataPlacemark;
0175             placemark->setGeometry( linestring );
0176             document->append( placemark );
0177         }
0178 
0179         if ( ( flag == LINEARRING ) || ( flag == OUTERBOUNDARY ) || ( flag == INNERBOUNDARY ) ) {
0180             if ( flag == OUTERBOUNDARY && m_isMapColorField ) {
0181                 quint8 colorIndex;
0182                 m_stream >> colorIndex;
0183                 style = GeoDataStyle::Ptr(new GeoDataStyle);
0184                 GeoDataPolyStyle polyStyle;
0185                 polyStyle.setColorIndex( colorIndex );
0186                 style->setPolyStyle( polyStyle );
0187             }
0188 
0189             GeoDataLinearRing* linearring = new GeoDataLinearRing;
0190             error = error | importPolygon( m_stream, linearring, nrAbsoluteNodes );
0191 
0192             if ( flag == LINEARRING ) {
0193                 GeoDataPlacemark *placemark = new GeoDataPlacemark;
0194                 placemark->setGeometry( linearring );
0195                 document->append( placemark );
0196             }
0197 
0198             if ( flag == OUTERBOUNDARY ) {
0199                 polygon = new GeoDataPolygon;
0200                 polygon->setOuterBoundary( *linearring );
0201             }
0202 
0203             if ( flag == INNERBOUNDARY ) {
0204                 polygon->appendInnerBoundary( *linearring );
0205             }
0206         }
0207 
0208         if ( flag == MULTIGEOMETRY ) {
0209             // not implemented yet, for now elements inside a multigeometry are separated as individual geometries
0210         }
0211 
0212         prevFlag = flag;
0213     }
0214 
0215     if ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) {
0216         GeoDataPlacemark *placemark = new GeoDataPlacemark;
0217         if ( m_isMapColorField ) {
0218             if ( style ) {
0219                 placemark->setStyle( style );
0220             }
0221         }
0222         placemark->setGeometry( polygon );
0223         document->append( placemark );
0224     }
0225 
0226     if ( error ) {
0227         delete document;
0228         document = nullptr;
0229         return nullptr;
0230     }
0231     document->setFileName( fileName );
0232     return document;
0233 }
0234 
0235 GeoDataDocument* Pn2Runner::parseForVersion2( const QString &fileName, DocumentRole role )
0236 {
0237     GeoDataDocument *document = new GeoDataDocument();
0238     document->setDocumentRole( role );
0239 
0240     bool error = false;
0241 
0242     quint32 nrAbsoluteNodes;
0243     quint32 placemarkCurrentID = 1;
0244     quint32 placemarkPrevID = 0;
0245     quint8 flag, prevFlag = -1;
0246 
0247     GeoDataPolygon *polygon = new GeoDataPolygon;
0248     GeoDataStyle::Ptr style;
0249     GeoDataPlacemark *placemark =nullptr; // new GeoDataPlacemark;
0250 
0251     quint32 currentPoly;
0252     for ( currentPoly = 1; ( currentPoly <= m_fileHeaderPolygons ) && ( !error ) && ( !m_stream.atEnd() ); currentPoly++ ) {
0253         m_stream >> flag >> placemarkCurrentID;
0254 
0255         if ( flag == MULTIGEOMETRY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) {
0256             if ( placemark ) {
0257                 placemark->setGeometry( polygon );
0258             }
0259         }
0260 
0261         if ( flag != MULTIGEOMETRY && flag != INNERBOUNDARY && ( prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY ) ) {
0262             if ( placemark ) {
0263                 placemark->setGeometry( polygon );
0264             }
0265         }
0266         /**
0267          * If the parsed placemark id @p placemarkCurrentID is different
0268          * from the id of previous placemark @p placemarkPrevID, it means
0269          * we have encountered a new placemark. So, prepare a style @p style
0270          * if file has color indices
0271          */
0272         if ( placemarkCurrentID != placemarkPrevID ) {
0273             placemark = new GeoDataPlacemark;
0274 
0275             // Handle the color index
0276             if( m_isMapColorField ) {
0277                 quint8 colorIndex;
0278                 m_stream >> colorIndex;
0279                 style = GeoDataStyle::Ptr(new GeoDataStyle);
0280                 GeoDataPolyStyle polyStyle;
0281                 polyStyle.setColorIndex( colorIndex );
0282                 polyStyle.setFill( true );
0283                 style->setPolyStyle( polyStyle );
0284                 placemark->setStyle( style );
0285             }
0286 
0287             document->append( placemark );
0288         }
0289 
0290         placemarkPrevID = placemarkCurrentID;
0291 
0292         if ( flag != MULTIGEOMETRY ) {
0293             m_stream >> nrAbsoluteNodes;
0294 
0295             if ( flag == LINESTRING ) {
0296                 GeoDataLineString *linestring = new GeoDataLineString;
0297                 error = error | importPolygon( m_stream, linestring, nrAbsoluteNodes );
0298                 if ( placemark ) {
0299                     placemark->setGeometry( linestring );
0300                 }
0301             }
0302 
0303             if ( ( flag == LINEARRING ) || ( flag == OUTERBOUNDARY ) || ( flag == INNERBOUNDARY ) ) {
0304                 GeoDataLinearRing* linearring = new GeoDataLinearRing;
0305                 error = error || importPolygon( m_stream, linearring, nrAbsoluteNodes );
0306 
0307                 if ( flag == LINEARRING ) {
0308                     if ( placemark ) {
0309                         placemark->setGeometry( linearring );
0310                     }
0311                 } else {
0312                     if ( flag == OUTERBOUNDARY ) {
0313                         polygon = new GeoDataPolygon;
0314                         polygon->setOuterBoundary( *linearring );
0315                     }
0316 
0317                     if ( flag == INNERBOUNDARY ) {
0318                         polygon->appendInnerBoundary( *linearring );
0319                     }
0320 
0321                     delete linearring;
0322                 }
0323             }
0324             prevFlag = flag;
0325         }
0326 
0327         else {
0328             quint32 placemarkCurrentIDInMulti;
0329             quint8 flagInMulti;
0330             quint8 prevFlagInMulti = -1;
0331             quint8 multiSize = 0;
0332 
0333             m_stream >> multiSize;
0334 
0335             GeoDataMultiGeometry *multigeom = new GeoDataMultiGeometry;
0336 
0337             /**
0338              * Read @p multiSize GeoDataGeometry objects
0339              */
0340             for ( int iter = 0; iter < multiSize; ++iter ) {
0341                 m_stream >> flagInMulti >> placemarkCurrentIDInMulti >> nrAbsoluteNodes;
0342                 if ( flagInMulti != INNERBOUNDARY && ( prevFlagInMulti == INNERBOUNDARY || prevFlagInMulti == OUTERBOUNDARY ) ) {
0343                     multigeom->append( polygon );
0344                 }
0345 
0346                 if ( flagInMulti == LINESTRING ) {
0347                     GeoDataLineString *linestring = new GeoDataLineString;
0348                     error = error || importPolygon( m_stream, linestring, nrAbsoluteNodes );
0349                     multigeom->append( linestring );
0350                 }
0351 
0352                 if ( ( flagInMulti == LINEARRING ) || ( flagInMulti == OUTERBOUNDARY ) || ( flagInMulti == INNERBOUNDARY ) ) {
0353                     GeoDataLinearRing* linearring = new GeoDataLinearRing;
0354                     error = error | importPolygon( m_stream, linearring, nrAbsoluteNodes );
0355 
0356                     if ( flagInMulti == LINEARRING ) {
0357                         multigeom->append( linearring );
0358                     } else {
0359                         if ( flagInMulti == OUTERBOUNDARY ) {
0360                             polygon = new GeoDataPolygon;
0361                             polygon->setOuterBoundary( *linearring );
0362                         }
0363 
0364                         if ( flagInMulti == INNERBOUNDARY ) {
0365                             polygon->appendInnerBoundary( *linearring );
0366                         }
0367 
0368                         delete linearring;
0369                     }
0370                 }
0371                 prevFlagInMulti = flagInMulti;
0372             }
0373 
0374             if ( prevFlagInMulti == INNERBOUNDARY || prevFlagInMulti == OUTERBOUNDARY ) {
0375                 multigeom->append( polygon );
0376             }
0377             if ( placemark ) {
0378                 placemark->setGeometry( multigeom );
0379             }
0380             prevFlag = MULTIGEOMETRY;
0381         }
0382     }
0383 
0384     if ( (prevFlag == INNERBOUNDARY || prevFlag == OUTERBOUNDARY) && prevFlag != MULTIGEOMETRY ) {
0385         placemark->setGeometry( polygon );
0386     }
0387 
0388     if ( error ) {
0389         delete document;
0390         document = nullptr;
0391         return nullptr;
0392     }
0393     document->setFileName( fileName );
0394     return document;
0395 }
0396 
0397 }
0398 
0399 
0400 #include "moc_Pn2Runner.cpp"