File indexing completed on 2024-03-24 03:53:35

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 #include <QDebug>
0030 #include <QVector>
0031 #include <QFile>
0032 #include <QDataStream>
0033 #include <QApplication>
0034  
0035 #include <MarbleWidget.h>
0036 #include <MarbleModel.h>
0037 #include <ParsingRunnerManager.h>
0038 #include <GeoDataTreeModel.h>
0039 #include <GeoDataFeature.h>
0040 #include <GeoDataDocument.h>
0041 #include <GeoDataStyle.h>
0042 #include <GeoDataPolyStyle.h>
0043 #include <GeoDataSchema.h>
0044 #include <GeoDataSimpleField.h>
0045 #include <GeoDataPlacemark.h>
0046 #include <GeoDataLineString.h>
0047 #include <GeoDataLinearRing.h>
0048 #include <GeoDataPolygon.h>
0049 #include <GeoDataPoint.h>
0050 #include <GeoDataGeometry.h>
0051 #include <GeoDataMultiGeometry.h>
0052 
0053 using namespace Marble;
0054 
0055 qreal epsilon   =   1.0;
0056 
0057 // Polygon header flags, representing the type of polygon
0058 enum polygonFlagType { LINESTRING = 0, LINEARRING = 1, OUTERBOUNDARY = 2, INNERBOUNDARY = 3, MULTIGEOMETRY = 4 };
0059 
0060 qreal latDistance( const GeoDataCoordinates &A, const GeoDataCoordinates &B ) {
0061     qreal latA = A.latitude( GeoDataCoordinates::Degree );
0062     qreal latB = B.latitude( GeoDataCoordinates::Degree );
0063     return latB - latA;
0064 }
0065 
0066 qreal lonDistance( const GeoDataCoordinates &A, const GeoDataCoordinates &B ) {
0067     qreal lonA = A.longitude( GeoDataCoordinates::Degree );
0068     qreal lonB = B.longitude( GeoDataCoordinates::Degree );
0069     return lonB - lonA;
0070 }
0071 
0072 qreal nodeDistance( const GeoDataCoordinates &A, const GeoDataCoordinates &B ) {
0073     return qMax( qAbs( latDistance( A, B ) ), qAbs( lonDistance( A, B ) ) );
0074 }
0075 
0076 
0077 qint16 printFormat16( qreal X ) {
0078     return ( ( qint16 )( X * 120 ) );
0079 }
0080 
0081 qint8 printFormat8( qreal X ) {
0082     return ( ( qint8 )( X * 120 ) );
0083 }
0084 
0085 quint32 getParentNodes( const QVector<GeoDataCoordinates>::Iterator& begin, const QVector<GeoDataCoordinates>::Iterator& end )
0086 {
0087     quint32 parentNodes = 0;
0088 
0089     QVector<GeoDataCoordinates>::Iterator it = begin;
0090     QVector<GeoDataCoordinates>::Iterator itAux = begin;
0091 
0092     for ( ; it != end && itAux != end; ++itAux ) {
0093         if ( ( nodeDistance( (*it), (*itAux) ) > epsilon ) || ( itAux == begin ) ) { // absolute nodes
0094             it = itAux;
0095             ++parentNodes;
0096         }
0097     }
0098 
0099     return parentNodes;
0100 }
0101 
0102 void printAllNodes( const QVector<GeoDataCoordinates>::Iterator& begin, const QVector<GeoDataCoordinates>::Iterator& end, QDataStream &stream ) 
0103 {
0104 
0105     qint16 nrChildNodes; 
0106 
0107     QVector<GeoDataCoordinates>::Iterator it = begin;
0108     QVector<GeoDataCoordinates>::Iterator itAux = begin;
0109 
0110     for ( ; it != end && itAux != end; ++itAux ) {
0111         if ( ( nodeDistance( (*it), (*itAux) ) > epsilon ) || ( itAux == begin ) ) { // absolute nodes
0112             it = itAux;
0113             nrChildNodes = 0;
0114             QVector<GeoDataCoordinates>::Iterator itAux2 = it + 1;
0115             for ( ; itAux2 != end && nodeDistance( (*it), (*itAux2) ) <= epsilon; ++itAux2 )
0116                 ++nrChildNodes;
0117 
0118             qint16 lat = printFormat16( it->latitude( GeoDataCoordinates::Degree ) );
0119             qint16 lon = printFormat16( it->longitude( GeoDataCoordinates::Degree ) );
0120 
0121             stream << lat << lon << nrChildNodes;
0122         }
0123         else { // relative nodes
0124             qint8 lat = printFormat8( latDistance( (*it), (*itAux) ) );
0125             qint8 lon = printFormat8( lonDistance( (*it), (*itAux) ) );
0126             stream << lat << lon;
0127         }
0128     }
0129 }
0130  
0131 int main(int argc, char** argv)
0132 {
0133     QApplication app(argc,argv);
0134 
0135 
0136     qDebug() << " Syntax: shp2pn2 [-i shp-sourcefile -o pn2-targetfile]";
0137 
0138     QString inputFilename;
0139     int inputIndex = app.arguments().indexOf( "-i" );
0140     if ( inputIndex > 0 && inputIndex + 1 < argc )
0141         inputFilename = app.arguments().at( inputIndex + 1 );
0142     else {
0143     qWarning() << "Input file missing.";
0144     return 1;
0145     }
0146 
0147     QString outputFilename = "output.pn2";
0148     int outputIndex = app.arguments().indexOf("-o");
0149     if ( outputIndex > 0 && outputIndex + 1 < argc )
0150         outputFilename = app.arguments().at( outputIndex + 1 );
0151     
0152 
0153     MarbleModel *model = new MarbleModel;
0154     ParsingRunnerManager* manager = new ParsingRunnerManager( model->pluginManager() );
0155 
0156     GeoDataDocument* document = manager->openFile( inputFilename );
0157     if (!document) { 
0158       qWarning() << "Could not parse document (have you installed libshape?) !";
0159       return 1;
0160     }
0161 
0162     QFile file( outputFilename );
0163     file.open( QIODevice::WriteOnly );
0164     QDataStream stream( &file );
0165 
0166     quint8 fileHeaderVersion;
0167     quint32 fileHeaderPolygons;
0168     bool isMapColorField;
0169 
0170     fileHeaderVersion = 2;
0171     fileHeaderPolygons = 0; // This variable counts the number of polygons inside the document
0172     isMapColorField = false; // Whether the file contains mapcolor field or not.
0173 
0174     QVector<GeoDataFeature*>::Iterator i = document->begin();
0175     QVector<GeoDataFeature*>::Iterator const end = document->end();
0176 
0177     for (; i != end; ++i) {
0178         GeoDataPlacemark* placemark = static_cast<GeoDataPlacemark*>( *i );
0179 
0180         // Types of placemarks
0181         GeoDataPolygon* polygon = dynamic_cast<GeoDataPolygon*>( placemark->geometry() );
0182         GeoDataLineString* linestring = dynamic_cast<GeoDataLineString*>( placemark->geometry() );
0183         GeoDataMultiGeometry* multigeom = dynamic_cast<GeoDataMultiGeometry*>( placemark->geometry() );
0184 
0185         if ( polygon ) {
0186             fileHeaderPolygons += 1 + polygon->innerBoundaries().size(); // outer boundary + number of inner boundaries of the polygon
0187         }
0188 
0189         if ( linestring ) {
0190             ++fileHeaderPolygons;
0191         }
0192 
0193         if ( multigeom ) {
0194             QVector<GeoDataGeometry*>::Iterator multi = multigeom->begin();
0195             QVector<GeoDataGeometry*>::Iterator multiEnd = multigeom->end();
0196             for ( ; multi != multiEnd; ++multi ) {
0197                 /**
0198                  * Handle the no. of polygons in multigeom according to whether
0199                  * it contains GeoDataLineString or GeoDataPolygon
0200                  */
0201                 GeoDataLineString *lineString = dynamic_cast<GeoDataLineString*>( *multi );
0202                 GeoDataPolygon *poly  = dynamic_cast<GeoDataPolygon*>( *multi );
0203                 if ( lineString ) {
0204                     ++fileHeaderPolygons;
0205                 }
0206                 if ( poly ) {
0207                     fileHeaderPolygons += 1 + poly->innerBoundaries().size();
0208                 }
0209             }
0210         }
0211     }
0212 
0213     GeoDataSchema schema = document->schema( QString("default") );
0214     if (schema.simpleField("mapcolor13").name() == QLatin1String("mapcolor13")) {
0215         isMapColorField = true;
0216     }
0217 
0218     // Write in the beginnig whether the file contains mapcolor or not.
0219     stream << fileHeaderVersion << fileHeaderPolygons << isMapColorField;
0220 
0221     i = document->begin();
0222 
0223     quint32 placemarkCurrentID = 0;
0224     quint32 polyParentNodes;
0225     quint8 polyFlag;
0226 
0227     for ( ; i != end; ++i ) {
0228         GeoDataPlacemark* placemark = static_cast<GeoDataPlacemark*>( *i );
0229 
0230         ++placemarkCurrentID;
0231 
0232         // Types of placemarks
0233         GeoDataPolygon* polygon = dynamic_cast<GeoDataPolygon*>( placemark->geometry() );
0234         GeoDataLineString* linestring = dynamic_cast<GeoDataLineString*>( placemark->geometry() );
0235         GeoDataMultiGeometry* multigeom = dynamic_cast<GeoDataMultiGeometry*>( placemark->geometry() );
0236 
0237         /**
0238          * For every placemark write a color index ( if isMapColorField is true )
0239          * and a placemarkID. In pn2 runner we will parse a color
0240          * index ( if it exists ) for every different placemarkID.
0241          * The general pattern is for writing values in pn2 file is:
0242          * flag -> placemarkID -> colorIndex -> polyParentNodes -> all nodes
0243          */
0244 
0245         if ( polygon ) {
0246             // Outer boundary
0247             QVector<GeoDataCoordinates>::Iterator jBegin = polygon->outerBoundary().begin();
0248             QVector<GeoDataCoordinates>::Iterator jEnd = polygon->outerBoundary().end();
0249             polyParentNodes = getParentNodes( jBegin, jEnd );
0250             polyFlag = OUTERBOUNDARY;
0251 
0252             stream << polyFlag << placemarkCurrentID;
0253 
0254             if ( isMapColorField ) {
0255                 quint8 colorIndex = placemark->style()->polyStyle().colorIndex();
0256                 stream << colorIndex;
0257             }
0258 
0259             stream << polyParentNodes;
0260 
0261             printAllNodes( jBegin, jEnd, stream );
0262 
0263             // Inner boundaries
0264             QVector<GeoDataLinearRing>::Iterator inner = polygon->innerBoundaries().begin();
0265             QVector<GeoDataLinearRing>::Iterator innerEnd = polygon->innerBoundaries().end();
0266 
0267             for ( ; inner != innerEnd; ++inner ) {
0268                 GeoDataLinearRing linearring = static_cast<GeoDataLinearRing>( *inner );
0269 
0270                 jBegin = linearring.begin();
0271                 jEnd = linearring.end();
0272                 polyParentNodes = getParentNodes( jBegin, jEnd );
0273                 polyFlag = INNERBOUNDARY;
0274 
0275                 stream << polyFlag << placemarkCurrentID << polyParentNodes;
0276 
0277                 printAllNodes( jBegin, jEnd, stream );
0278             }
0279 
0280         }
0281 
0282         if ( linestring ) {
0283             QVector<GeoDataCoordinates>::Iterator jBegin = linestring->begin();
0284             QVector<GeoDataCoordinates>::Iterator jEnd = linestring->end();
0285             polyParentNodes = getParentNodes( jBegin, jEnd );
0286             if ( linestring->isClosed() )
0287                 polyFlag = LINEARRING;
0288             else
0289                 polyFlag = LINESTRING;
0290 
0291             stream << polyFlag << placemarkCurrentID;
0292 
0293             if ( isMapColorField ) {
0294                 quint8 colorIndex = placemark->style()->polyStyle().colorIndex();
0295                 stream << colorIndex;
0296             }
0297 
0298             stream << polyParentNodes;
0299 
0300             printAllNodes( jBegin, jEnd, stream );
0301         }
0302 
0303         if ( multigeom ) {
0304             QVector<GeoDataGeometry*>::Iterator multi = multigeom->begin();
0305             QVector<GeoDataGeometry*>::Iterator const multiEnd = multigeom->end();
0306 
0307             quint8 multiGeomSize = 0;
0308 
0309             for ( ; multi != multiEnd; ++multi ) {
0310                 GeoDataLineString *lineString = dynamic_cast<GeoDataLineString*>( *multi );
0311                 GeoDataPolygon *poly  = dynamic_cast<GeoDataPolygon*>( *multi );
0312                 if ( lineString ) {
0313                     ++multiGeomSize;
0314                 }
0315                 if ( poly ) {
0316                     multiGeomSize += 1 + poly->innerBoundaries().size();
0317                 }
0318             }
0319 
0320             multi = multigeom->begin();
0321 
0322             /**
0323              * While parsing pn2 whenever we encounter a MULTIGEOMETRY
0324              * flag we will proceed differently parsing @p multiGeomSize
0325              * GeoDataGeometry objects
0326              */
0327 
0328             polyFlag = MULTIGEOMETRY;
0329 
0330             stream << polyFlag << placemarkCurrentID;
0331 
0332             if ( isMapColorField ) {
0333                 quint8 colorIndex = placemark->style()->polyStyle().colorIndex();
0334                 stream << colorIndex;
0335             }
0336 
0337             stream << multiGeomSize;            
0338 
0339             for ( ; multi != multiEnd; ++multi ) {
0340                 GeoDataLineString* currLineString = dynamic_cast<GeoDataLineString*>( *multi );
0341                 GeoDataPolygon *currPolygon = dynamic_cast<GeoDataPolygon*>( *multi );
0342 
0343                 if ( currLineString ) {
0344                     QVector<GeoDataCoordinates>::Iterator jBegin = currLineString->begin();
0345                     QVector<GeoDataCoordinates>::Iterator jEnd = currLineString->end();
0346                     polyParentNodes = getParentNodes( jBegin, jEnd );
0347                     if ( currLineString->isClosed() )
0348                         polyFlag = LINEARRING;
0349                     else
0350                         polyFlag = LINESTRING;
0351 
0352                     stream << polyFlag << placemarkCurrentID << polyParentNodes;
0353 
0354                     printAllNodes( jBegin, jEnd, stream );
0355                 }
0356 
0357                 else if ( currPolygon ) {
0358                     // Outer boundary
0359                     QVector<GeoDataCoordinates>::Iterator jBegin = currPolygon->outerBoundary().begin();
0360                     QVector<GeoDataCoordinates>::Iterator jEnd = currPolygon->outerBoundary().end();
0361                     polyParentNodes = getParentNodes( jBegin, jEnd );
0362                     polyFlag = OUTERBOUNDARY;
0363 
0364                     stream << polyFlag << placemarkCurrentID << polyParentNodes;
0365 
0366                     printAllNodes( jBegin, jEnd, stream );
0367 
0368                     // Inner boundaries
0369                     QVector<GeoDataLinearRing>::Iterator inner = currPolygon->innerBoundaries().begin();
0370                     QVector<GeoDataLinearRing>::Iterator innerEnd = currPolygon->innerBoundaries().end();
0371 
0372                     for ( ; inner != innerEnd; ++inner ) {
0373                         GeoDataLinearRing linearring = static_cast<GeoDataLinearRing>( *inner );
0374 
0375                         jBegin = linearring.begin();
0376                         jEnd = linearring.end();
0377                         polyParentNodes = getParentNodes( jBegin, jEnd );
0378                         polyFlag = INNERBOUNDARY;
0379 
0380                         stream << polyFlag << placemarkCurrentID << polyParentNodes;
0381 
0382                         printAllNodes( jBegin, jEnd, stream );
0383                     }
0384                 }
0385             }
0386         }
0387     }
0388 }