File indexing completed on 2024-06-09 03:50:52

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Thibaut Gridel <tgridel@free.fr>
0004 
0005 #include "ShpRunner.h"
0006 
0007 #include "GeoDataDocument.h"
0008 #include "GeoDataPlacemark.h"
0009 #include "GeoDataPolygon.h"
0010 #include "GeoDataLinearRing.h"
0011 #include "GeoDataPoint.h"
0012 #include "GeoDataMultiGeometry.h"
0013 #include "GeoDataSchema.h"
0014 #include "GeoDataSimpleField.h"
0015 #include "GeoDataStyle.h"
0016 #include "GeoDataPolyStyle.h"
0017 #include "MarbleDebug.h"
0018 
0019 #include <QFileInfo>
0020 
0021 #include <shapefil.h>
0022 
0023 #include <memory>
0024 
0025 namespace Marble
0026 {
0027 
0028 ShpRunner::ShpRunner(QObject *parent) :
0029     ParsingRunner(parent)
0030 {
0031 }
0032 
0033 ShpRunner::~ShpRunner()
0034 {
0035 }
0036 
0037 GeoDataDocument *ShpRunner::parseFile(const QString &fileName, DocumentRole role, QString &error)
0038 {
0039     QFileInfo fileinfo( fileName );
0040     if (fileinfo.suffix().compare(QLatin1String("shp"), Qt::CaseInsensitive) != 0) {
0041         error = QStringLiteral("File %1 does not have a shp suffix").arg(fileName);
0042         mDebug() << error;
0043         return nullptr;
0044     }
0045 
0046     SHPHandle handle = SHPOpen( fileName.toStdString().c_str(), "rb" );
0047     if ( !handle ) {
0048         error = QStringLiteral("Failed to read %1").arg(fileName);
0049         mDebug() << error;
0050         return nullptr;
0051     }
0052     int entities;
0053     int shapeType;
0054     SHPGetInfo( handle, &entities, &shapeType, nullptr, nullptr );
0055     mDebug() << " SHP info " << entities << " Entities "
0056              << shapeType << " Shape Type ";
0057 
0058     DBFHandle dbfhandle;
0059     dbfhandle = DBFOpen( fileName.toStdString().c_str(), "rb");
0060     int nameField = DBFGetFieldIndex( dbfhandle, "Name" );
0061     int noteField = DBFGetFieldIndex( dbfhandle, "Note" );
0062     int mapColorField = DBFGetFieldIndex( dbfhandle, "mapcolor13" );
0063 
0064     GeoDataDocument *document = new GeoDataDocument;
0065     document->setDocumentRole( role );
0066 
0067     if ( mapColorField != -1 ) {
0068         GeoDataSchema schema;
0069         schema.setId(QStringLiteral("default"));
0070         GeoDataSimpleField simpleField;
0071         simpleField.setName(QStringLiteral("mapcolor13"));
0072         simpleField.setType( GeoDataSimpleField::Double );
0073         schema.addSimpleField( simpleField );
0074         document->addSchema( schema );
0075     }
0076 
0077     for ( int i=0; i< entities; ++i ) {
0078         GeoDataPlacemark  *placemark = nullptr;
0079         placemark = new GeoDataPlacemark;
0080         document->append( placemark );
0081 
0082         std::unique_ptr<SHPObject, decltype(&SHPDestroyObject)> shape(SHPReadObject( handle, i ), &SHPDestroyObject);
0083         if (nameField != -1) {
0084             const char* info = DBFReadStringAttribute( dbfhandle, i, nameField );
0085             // TODO: defaults to utf-8 encoding, but could be also something else, optionally noted in a .cpg file
0086             placemark->setName( info );
0087             mDebug() << "name " << placemark->name();
0088         }
0089         if (noteField != -1) {
0090             const char* note = DBFReadStringAttribute( dbfhandle, i, noteField );
0091             // TODO: defaults to utf-8 encoding, see comment for name
0092             placemark->setDescription( note );
0093             mDebug() << "desc " << placemark->description();
0094         }
0095 
0096         double mapColor = DBFReadDoubleAttribute( dbfhandle, i, mapColorField );
0097         if ( mapColor ) {
0098             GeoDataStyle::Ptr style(new GeoDataStyle);
0099             if ( mapColor >= 0 && mapColor <=255 ) {
0100                 quint8 colorIndex = quint8( mapColor );
0101                 style->polyStyle().setColorIndex( colorIndex );
0102             }
0103             else {
0104                 quint8 colorIndex = 0;     // mapColor is undefined in this case
0105                 style->polyStyle().setColorIndex( colorIndex );
0106             }
0107             placemark->setStyle( style );
0108         }
0109 
0110         switch ( shapeType ) {
0111             case SHPT_POINT: {
0112                 GeoDataPoint *point = new GeoDataPoint( *shape->padfX, *shape->padfY, 0, GeoDataCoordinates::Degree );
0113                 placemark->setGeometry( point );
0114                 mDebug() << "point " << placemark->name();
0115                 break;
0116             }
0117 
0118             case SHPT_MULTIPOINT: {
0119                 GeoDataMultiGeometry *geom = new GeoDataMultiGeometry;
0120                 for( int j=0; j<shape->nVertices; ++j ) {
0121                     geom->append( new GeoDataPoint( GeoDataCoordinates(
0122                                   shape->padfX[j], shape->padfY[j],
0123                                   0, GeoDataCoordinates::Degree ) ) );
0124                 }
0125                 placemark->setGeometry( geom );
0126                 mDebug() << "multipoint " << placemark->name();
0127                 break;
0128             }
0129 
0130             case SHPT_ARC: {
0131                 if ( shape->nParts != 1 ) {
0132                     GeoDataMultiGeometry *geom = new GeoDataMultiGeometry;
0133                     for( int j=0; j<shape->nParts; ++j ) {
0134                         GeoDataLineString *line = new GeoDataLineString;
0135                         int itEnd = (j + 1 < shape->nParts) ? shape->panPartStart[j+1] : shape->nVertices;
0136                         for( int k=shape->panPartStart[j]; k<itEnd; ++k ) {
0137                             line->append( GeoDataCoordinates(
0138                                           shape->padfX[k], shape->padfY[k],
0139                                           0, GeoDataCoordinates::Degree ) );
0140                         }
0141                         geom->append( line );
0142                     }
0143                     placemark->setGeometry( geom );
0144                     mDebug() << "arc " << placemark->name() << " " << shape->nParts;
0145 
0146                 } else {
0147                     GeoDataLineString *line = new GeoDataLineString;
0148                     for( int j=0; j<shape->nVertices; ++j ) {
0149                         line->append( GeoDataCoordinates(
0150                                       shape->padfX[j], shape->padfY[j],
0151                                       0, GeoDataCoordinates::Degree ) );
0152                     }
0153                     placemark->setGeometry( line );
0154                     mDebug() << "arc " << placemark->name() << " " << shape->nParts;
0155                 }
0156                 break;
0157             }
0158 
0159             case SHPT_POLYGON: {
0160                 if ( shape->nParts != 1 ) {
0161                     bool isRingClockwise = false;
0162                     GeoDataMultiGeometry *multigeom = new GeoDataMultiGeometry;
0163                     GeoDataPolygon *poly = nullptr;
0164                     int polygonCount = 0;
0165                     for( int j=0; j<shape->nParts; ++j ) {
0166                         GeoDataLinearRing ring;
0167                         int itStart = shape->panPartStart[j];
0168                         int itEnd = (j + 1 < shape->nParts) ? shape->panPartStart[j+1] : shape->nVertices;
0169                         for( int k = itStart; k<itEnd; ++k ) {
0170                             ring.append( GeoDataCoordinates(
0171                                          shape->padfX[k], shape->padfY[k],
0172                                          0, GeoDataCoordinates::Degree ) );
0173                         }
0174                         isRingClockwise = ring.isClockwise();
0175                         if ( j == 0 || isRingClockwise ) {
0176                             poly = new GeoDataPolygon;
0177                             ++polygonCount;
0178                             poly->setOuterBoundary( ring );
0179                             if ( polygonCount > 1 ) {
0180                                 multigeom->append( poly );
0181                             }
0182                         }
0183                         else {
0184                             poly->appendInnerBoundary( ring );
0185                         }
0186                     }
0187                     if ( polygonCount > 1 ) {
0188                         placemark->setGeometry( multigeom );
0189                     }
0190                     else {
0191                         placemark->setGeometry( poly );
0192                         delete multigeom;
0193                         multigeom = nullptr;
0194                     }
0195                     mDebug() << "donut " << placemark->name() << " " << shape->nParts;
0196 
0197                 } else {
0198                     GeoDataPolygon *poly = new GeoDataPolygon;
0199                     GeoDataLinearRing ring;
0200                     for( int j=0; j<shape->nVertices; ++j ) {
0201                         ring.append( GeoDataCoordinates(
0202                                          shape->padfX[j], shape->padfY[j],
0203                                          0, GeoDataCoordinates::Degree ) );
0204                     }
0205                     poly->setOuterBoundary( ring );
0206                     placemark->setGeometry( poly );
0207                     mDebug() << "poly " << placemark->name() << " " << shape->nParts;
0208                 }
0209                 break;
0210             }
0211         }
0212     }
0213 
0214     SHPClose( handle );
0215 
0216     DBFClose( dbfhandle );
0217 
0218     if (!document->isEmpty()) {
0219         document->setFileName( fileName );
0220         return document;
0221     } else {
0222         delete document;
0223         return nullptr;
0224     }
0225 }
0226 
0227 }
0228 
0229 #include "moc_ShpRunner.cpp"