File indexing completed on 2023-05-30 09:06: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 #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 }