その2:プロバイダプラグインの骨格を作成で作成したプロバイダプラグインの中身を埋めていきます。
コンストラクタ
プロバイダプラグインのコンストラクタの役割は、データソースが有効かどうかを調べて、
有効であればデータソースから要素数、属性数と型、データ範囲を調べて保持することです。
また、データソースに空間参照情報があればそれを読み取ったりします。
データを読み取ってQGIS要素を構築する部分は後述のnextFeature()
関数でそのつど行うのが定石のようですが、
本例ではコンストラクタでQGIS要素を構築してメンバ変数として溜め込む作戦を採用します。
というわけで、まずメンバ変数にQgsFeature
型の配列を作成します。
QVector<QgsFeature> mFeatures;
コンストラクタはファイルを読み込んで測線データを解析し、要素を作成します。
このとき、同時に要素数のカウントとデータの存在範囲計算も行います。
また、属性テーブルの定義もコンストラクタで行います。
以下の例では、属性は固定であるとしてID、測線名、総測線長、閉合比を定義していますが、
通常は属性数、各フィールドの名称、型を調べる必要があるでしょう。
正しく読み込むことができたら、上記のメンバ変数mValid
にtrue
をセットします。
また、選択範囲を表すメンバ変数mSelectionRectangle
も初期化します。
1: bool QgsCompassSurveyDataProvider::readSurveyData( QString strFName ) 2: { 3: QVariant val; 4: QgsFeature feature; 5: QFile file( strFName ); 6: int nStep = 0, nFid = 0; 7: QByteArray buffer; 8: QgsPolyline vPLine; 9: QDataStream dstr( &buffer, static_cast<QIODevice::OpenMode>( QIODevice::WriteOnly ) ); 10: double dX, dY, dL, dRad, dTotLen; 11: 12: if ( !file.open( QIODevice::ReadOnly ) ) 13: { 14: return false; 15: } 16: 17: mExtent = QgsRectangle(); 18: 19: QTextStream fstr( &file ); 20: while ( !fstr.atEnd() ) 21: { 22: QString strLine = fstr.readLine(); 23: QStringList listCol = strLine.split( "," ); 24: 25: if ( nStep == 0 ) 26: { 27: val = QVariant( listCol[0] ); 28: feature.setFeatureId( ++nFid ); 29: feature.addAttribute( 0, mFeatures.size()+1 ); 30: feature.addAttribute( 1, val ); 31: dTotLen = 0.0; 32: vPLine.clear(); 33: nStep++; 34: } 35: else if ( nStep == 1 ) 36: { 37: dX = listCol[0].toDouble(); 38: dY = listCol[1].toDouble(); 39: QgsPoint pnt( dX, dY ); 40: vPLine.append( pnt ); 41: if ( mFeatures.size() == 0 ) 42: { 43: mExtent.set( pnt, pnt ); 44: } 45: else 46: { 47: mExtent.combineExtentWith( dX, dY ); 48: } 49: nStep++; 50: } 51: else if ( nStep == 2 ) 52: { 53: if ( listCol[0].indexOf( "end", 0, Qt::CaseInsensitive ) >= 0 ) 54: { 55: /* もしかしたらこっちのほうが安全かもしれません 56: QByteArray buffer; 57: QDataStream s( &buffer, static_cast<QIODevice::OpenMode>( QIODevice::WriteOnly ) ); 58: switch ( QgsApplication::endian() ) 59: { 60: case QgsApplication::NDR: 61: s.setByteOrder( QDataStream::LittleEndian ); 62: s << ( quint8 )1; 63: break; 64: case QgsApplication::XDR: 65: s << ( quint8 )0; 66: break; 67: default: 68: return false; 69: } 70: 71: s << ( quint32 )QGis::WKBLineString; 72: s << ( quint32 )vPLine.size(); 73: 74: for ( QVector<QgsPoint>::const_iterator it = vPLine.begin(); it != vPLine.end(); it++ ) 75: { 76: s << it->x(); 77: s << it->y(); 78: } 79: 80: unsigned char* geometry = new unsigned char[buffer.size()]; 81: memcpy( geometry, buffer.data(), buffer.size() ); 82: feature.setGeometryAndOwnership( geometry, buffer.size() ); 83: */ 84: QgsGeometry *pGeometry = QgsGeometry::fromPolyline( vPLine ); 85: feature.setGeometry( pGeometry ); 86: 87: val = QVariant( dTotLen ); 88: feature.addAttribute( 2, val ); 89: dL = _hypot( (vPLine.front().x()-vPLine.back().x()), (vPLine.front().y()-vPLine.back().y()) ); 90: val = QVariant( dL / dTotLen ); 91: feature.addAttribute( 3, val ); 92: feature.setValid( true ); 93: mFeatures.append( feature ); 94: // buffer.clear(); 95: nStep = 0; 96: } 97: else 98: { 99: dL = listCol[0].toDouble() * cos( listCol[2].toDouble()*M_PI/180 ); 100: dRad = listCol[1].toDouble()*M_PI/180; 101: dX = dL * sin( dRad ); 102: dY = dL * cos( dRad ); 103: QgsPoint pnt( vPLine.back().x()+dX, vPLine.back().y()+dY ); 104: vPLine.push_back( pnt ); 105: mExtent.combineExtentWith( pnt.x(), pnt.y() ); 106: dTotLen += dL; 107: } 108: } 109: } 110: 111: file.close(); 112: 113: return true; 114: } 115: 116: QgsCompassSurveyDataProvider::QgsCompassSurveyDataProvider( QString uri ) 117: : QgsVectorDataProvider( uri ) 118: { 119: mValid = false; 120: 121: if ( uri.isEmpty() ) 122: { 123: return; 124: } 125: 126: QString strFileName = QUrl::fromPercentEncoding( uri.toUtf8() ); 127: 128: if ( !QFile::exists( strFileName ) ) 129: { 130: return; 131: } 132: 133: attributeFields[0] = QgsField( "id", QVariant::Int ); 134: attributeFields[1] = QgsField( "name", QVariant::String ); 135: attributeFields[2] = QgsField( "length", QVariant::Double ); 136: attributeFields[3] = QgsField( "closure_difference", QVariant::Double ); 137: 138: mSelectionRectangle = QgsRectangle(); 139: 140: if ( !readSurveyData( strFileName ) ) 141: { 142: return; 143: } 144: 145: mValid = true; 146: m_nCurFeature = 0; 147: }
nextFeature()関数
QGISが描画や選択を行う際にQGISから要素データを要求されるときに呼び出されます。
関数の引数feature
に要素データを1つつめて返します。
この関数はfalse
を返すまで連続してQGIS側から呼び出されるので、全ての要素を返したらfalse
を返すようにします。
要素を順次返すときに、select()
関数で設定された条件で要素を振り分けるようにします。
以下の例は、とりあえず条件を無視してQGISに全要素を返すようにしています(が、条件を無視すると後で困ります)。
1: bool QgsCompassSurveyDataProvider::nextFeature( QgsFeature& feature ) 2: { 3: feature.setValid( false ); 4: if ( m_nCurFeature >= mFeatures.size() ) 5: { 6: return false; 7: } 8: 9: feature = mFeatures[m_nCurFeature]; 10: m_nCurFeature++; 11: feature.setValid( true ); 12: 13: return true; 14: }
その他の関数
featureCount()
関数は要素数を返すようにします。
long QgsCompassSurveyDataProvider::featureCount() const { return mFeatures.size(); }
また、rewind()
関数は次にnextFeature()
関数が呼び出されたときに最初の要素を返すようにします。
本例では要素はメンバに保持しているので、インデックスを0に戻すだけですが、
そのつどファイルを読み込むように実装されているプラグインでは、ファイルポインタを先頭に戻したりする処理をここに記述します。
void QgsCompassSurveyDataProvider::rewind() { m_nCurFeature = 0; }
ここまでできたら描画してみることができます。
以下の絵のように描画できたら第一段階は成功です。
さらに、属性テーブルを開いてみて、フィールドと属性が正しく割り当てられていれば第二段階成功です。
しかし、この段階では要素を選択しようとすると、キャンバスのどこをクリックしても全て選択されてしまいます。
これはselect()
関数で設定された選択条件をnextFeature()
関数に反映していないためです。
そこで、最後に仕上げとしてselect()
関数の実装と、それをnextFeature()
関数に反映するように変更します。