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"