File indexing completed on 2024-04-28 03:50:35

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2015 Constantin Mihalache <mihalache.c94@gmail.com>
0004 //
0005 
0006 //self
0007 #include "OpenLocationCodeSearchRunner.h"
0008 
0009 #include "GeoDataPlacemark.h"
0010 #include "GeoDataLinearRing.h"
0011 #include "GeoDataPolygon.h"
0012 #include "GeoDataStyle.h"
0013 #include "GeoDataLineStyle.h"
0014 #include "GeoDataPolyStyle.h"
0015 
0016 //Qt
0017 #include <QColor>
0018 #include <QVector>
0019 
0020 namespace Marble
0021 {
0022 
0023 OpenLocationCodeSearchRunner::OpenLocationCodeSearchRunner( QObject *parent ):
0024     SearchRunner( parent )
0025 {
0026     // initialize the charIndex map
0027     QString const acceptedChars = "23456789CFGHJMPQRVWX";
0028     for( int index = 0; index < acceptedChars.size(); index++ ) {
0029         charIndex[ acceptedChars[index] ] = index;
0030     }
0031 }
0032 
0033 void OpenLocationCodeSearchRunner::search( const QString &searchTerm, const GeoDataLatLonBox &preferred )
0034 {
0035     Q_UNUSED( preferred );
0036 
0037     QVector<GeoDataPlacemark*> result;
0038 
0039     if( isValidOLC( searchTerm.toUpper() ) ) {
0040         GeoDataLatLonBox boundingBox = decodeOLC( searchTerm.toUpper() );
0041         if( !boundingBox.isEmpty() ) {
0042             GeoDataPlacemark *placemark = new GeoDataPlacemark( searchTerm );
0043 
0044             GeoDataPolygon *geometry = new GeoDataPolygon( polygonFromLatLonBox( boundingBox ) );
0045             placemark->setGeometry( geometry );
0046 
0047             GeoDataStyle::Ptr style = GeoDataStyle::Ptr(new GeoDataStyle());
0048             GeoDataLineStyle lineStyle;
0049             GeoDataPolyStyle polyStyle;
0050             lineStyle.setColor( QColor( Qt::GlobalColor::red ) );
0051             lineStyle.setWidth( 2 );
0052             polyStyle.setFill( false );
0053             style->setLineStyle( lineStyle );
0054             style->setPolyStyle( polyStyle );
0055             placemark->setStyle( style );
0056 
0057             result.append( placemark );
0058         }
0059     }
0060 
0061     emit searchFinished( result );
0062 }
0063 
0064 GeoDataPolygon OpenLocationCodeSearchRunner::polygonFromLatLonBox( const GeoDataLatLonBox& boundingBox ) const
0065 {
0066     if( boundingBox.isEmpty() ) {
0067         return GeoDataPolygon();
0068     }
0069 
0070     GeoDataPolygon poly;
0071     GeoDataLinearRing outerBoundry;
0072     // north-west corner
0073     outerBoundry.append( GeoDataCoordinates( boundingBox.west(), boundingBox.north(), GeoDataCoordinates::Unit::Degree ) );
0074     // north-east corner
0075     outerBoundry.append( GeoDataCoordinates( boundingBox.east(), boundingBox.north(), GeoDataCoordinates::Unit::Degree ) );
0076     // south-east corner
0077     outerBoundry.append( GeoDataCoordinates( boundingBox.east(), boundingBox.south(), GeoDataCoordinates::Unit::Degree ) );
0078     // south-west corner
0079     outerBoundry.append( GeoDataCoordinates( boundingBox.west(), boundingBox.south(), GeoDataCoordinates::Unit::Degree ) );
0080 
0081     poly.setOuterBoundary( outerBoundry );
0082 
0083     return poly;
0084 }
0085 
0086 GeoDataLatLonBox OpenLocationCodeSearchRunner::decodeOLC( const QString &olc ) const
0087 {
0088     if( !isValidOLC( olc ) ) {
0089         return GeoDataLatLonBox();
0090     }
0091 
0092     // remove padding
0093     QString decoded = olc;
0094     decoded = decoded.remove( QRegExp("[0+]") );
0095     qreal southLatitude = 0;
0096     qreal westLongitude = 0;
0097 
0098     int digit = 0;
0099     qreal latitudeResolution = 400;
0100     qreal longitudeResolution = 400;
0101 
0102     while( digit < decoded.size() ) {
0103         if( digit < 10 ) {
0104             latitudeResolution /= 20;
0105             longitudeResolution /= 20;
0106             southLatitude += latitudeResolution * charIndex[ decoded[digit] ];
0107             westLongitude += longitudeResolution * charIndex[ decoded[digit+1] ];
0108             digit += 2;
0109         }
0110         else {
0111             latitudeResolution /= 5;
0112             longitudeResolution /= 4;
0113             southLatitude += latitudeResolution *( charIndex[ decoded[digit] ] / 4 );
0114             westLongitude += longitudeResolution *( charIndex[ decoded[digit] ] % 4 );
0115             digit += 1;
0116         }
0117     }
0118     return GeoDataLatLonBox( southLatitude - 90 + latitudeResolution, southLatitude - 90, westLongitude - 180 + longitudeResolution, westLongitude - 180, GeoDataCoordinates::Unit::Degree );
0119 }
0120 
0121 bool OpenLocationCodeSearchRunner::isValidOLC( const QString& olc ) const
0122 {
0123     // It must have only one SEPARATOR located at an even index in
0124     // the string.
0125     QChar const separator(QLatin1Char('+'));
0126     int separatorPos = olc.indexOf(separator);
0127     if( separatorPos == -1
0128         || separatorPos != olc.lastIndexOf(separator)
0129         || separatorPos % 2 != 0 )
0130     {
0131         return false;
0132     }
0133     int const separatorPosition = 8;
0134     // It must be a full open location code.
0135     if( separatorPos != separatorPosition ) {
0136         return false;
0137     }
0138 
0139     // Test the first two characters as only some characters of the
0140     // ACCEPTED_CHARS are allowed.
0141     //
0142     // First latitude character can only take one of the first 9 values.
0143     int index0 = charIndex.value( olc[0], -1 );
0144     if( index0 == -1 || index0 > 8 ) {
0145         return false;
0146     }
0147     // First longitude character can only take one of the first 18 values.
0148     int index1 = charIndex.value( olc[1], -1 );
0149     if( index1 == -1 || index1 > 17) {
0150         return false;
0151     }
0152 
0153     // Test the characters before the SEPARATOR.
0154     QChar const suffixPadding(QLatin1Char('0'));
0155     bool paddingBegun = false;
0156     for( int index = 0; index < separatorPos; index++ ) {
0157         if( paddingBegun ) {
0158         // Once padding has begun, there should be only padding.
0159         if( olc[index] != suffixPadding ) {
0160             return false;
0161         }
0162         continue;
0163         }
0164         if( charIndex.contains( olc[index] ) ) {
0165             continue;
0166         }
0167         if( olc[index] == suffixPadding ) {
0168             paddingBegun = true;
0169             // Padding can start only at an even index.
0170             if( index % 2 != 0 ) {
0171                 return false;
0172             }
0173             continue;
0174         }
0175     return false;
0176     }
0177 
0178     // Test the characters after the SEPARATOR.
0179     if( olc.size() > separatorPos +1 ) {
0180         if( paddingBegun ) {
0181             return false;
0182         }
0183         // Only one character after the SEPARATOR is not allowed.
0184         if( olc.size() == separatorPos + 2 ) {
0185             return false;
0186         }
0187         for( int index = separatorPos + 1; index < olc.size(); index++ ) {
0188             if( !charIndex.contains( olc[index] ) ) {
0189                 return false;
0190             }
0191         }
0192     }
0193 
0194     return true;
0195 }
0196 
0197 }
0198 
0199 #include "moc_OpenLocationCodeSearchRunner.cpp"