QVariant QgsFilter::propertyIndexValue( const QgsFeature& f ) const { QgsAttributeMap featureAttributes = f.attributeMap(); QgsAttributeMap::const_iterator f_it = featureAttributes.find( mPropertyIndex ); if ( f_it == featureAttributes.constEnd() ) { return QVariant(); } return f_it.value(); }
int QgsDiagramRenderer::classificationValue( const QgsFeature& f, QVariant& value ) const { //find out attribute value of the feature QgsAttributeMap featureAttributes = f.attributeMap(); QgsAttributeMap::const_iterator iter; if ( value.type() == QVariant::String ) //string type { //we can only handle one classification field for strings if ( mClassificationAttributes.size() > 1 ) { return 1; } iter = featureAttributes.find( mClassificationAttributes.first() ); if ( iter == featureAttributes.constEnd() ) { return 2; } value = iter.value(); } else //numeric type { double currentValue; double totalValue = 0; QList<int>::const_iterator list_it = mClassificationAttributes.constBegin(); for ( ; list_it != mClassificationAttributes.constEnd(); ++list_it ) { QgsAttributeMap::const_iterator iter = featureAttributes.find( *list_it ); if ( iter == featureAttributes.constEnd() ) { continue; } currentValue = iter.value().toDouble(); totalValue += currentValue; } value = QVariant( totalValue ); } return 0; }
QString QgsPointDisplacementRenderer::getLabel( const QgsFeature& f ) { QString attribute; QgsAttributeMap attMap = f.attributeMap(); if ( attMap.size() > 0 ) { QgsAttributeMap::const_iterator valIt = attMap.find( mLabelIndex ); if ( valIt != attMap.constEnd() ) { attribute = valIt->toString(); } } return attribute; }
QSizeF QgsLinearlyInterpolatedDiagramRenderer::diagramSize( const QgsAttributeMap& attributes, const QgsRenderContext& c ) { Q_UNUSED( c ); QgsAttributeMap::const_iterator attIt = attributes.find( mClassificationAttribute ); if ( attIt == attributes.constEnd() ) { return QSizeF(); //zero size if attribute is missing } double value = attIt.value().toDouble(); //interpolate size double ratio = ( value - mLowerValue ) / ( mUpperValue - mLowerValue ); return QSizeF( mUpperSize.width() * ratio + mLowerSize.width() * ( 1 - ratio ), mUpperSize.height() * ratio + mLowerSize.height() * ( 1 - ratio ) ); }
QSizeF QgsPieDiagram::diagramSize( const QgsAttributeMap& attributes, const QgsRenderContext& c, const QgsDiagramSettings& s, const QgsDiagramInterpolationSettings& is ) { Q_UNUSED( c ); QgsAttributeMap::const_iterator attIt = attributes.find( is.classificationAttribute ); if ( attIt == attributes.constEnd() ) { return QSizeF(); //zero size if attribute is missing } double scaledValue = attIt.value().toDouble(); double scaledLowerValue = is.lowerValue; double scaledUpperValue = is.upperValue; double scaledLowerSizeWidth = is.lowerSize.width(); double scaledLowerSizeHeight = is.lowerSize.height(); double scaledUpperSizeWidth = is.upperSize.width(); double scaledUpperSizeHeight = is.upperSize.height(); // interpolate the squared value if scale by area if ( s.scaleByArea ) { scaledValue = sqrt( scaledValue ); scaledLowerValue = sqrt( scaledLowerValue ); scaledUpperValue = sqrt( scaledUpperValue ); scaledLowerSizeWidth = sqrt( scaledLowerSizeWidth ); scaledLowerSizeHeight = sqrt( scaledLowerSizeHeight ); scaledUpperSizeWidth = sqrt( scaledUpperSizeWidth ); scaledUpperSizeHeight = sqrt( scaledUpperSizeHeight ); } //interpolate size double scaledRatio = ( scaledValue - scaledLowerValue ) / ( scaledUpperValue - scaledLowerValue ); QSizeF size = QSizeF( is.upperSize.width() * scaledRatio + is.lowerSize.width() * ( 1 - scaledRatio ), is.upperSize.height() * scaledRatio + is.lowerSize.height() * ( 1 - scaledRatio ) ); // Scale, if extension is smaller than the specified minimum if ( size.width() <= s.minimumSize && size.height() <= s.minimumSize ) { size.scale( s.minimumSize, s.minimumSize, Qt::KeepAspectRatio ); } return size; }
int QgsInterpolator::cacheBaseData() { if ( mLayerData.size() < 1 ) { return 0; } //reserve initial memory for 100000 vertices mCachedBaseData.clear(); mCachedBaseData.reserve( 100000 ); QList<LayerData>::iterator v_it = mLayerData.begin(); for ( ; v_it != mLayerData.end(); ++v_it ) { if ( v_it->vectorLayer == 0 ) { continue; } QgsVectorDataProvider* provider = v_it->vectorLayer->dataProvider(); if ( !provider ) { return 2; } QgsAttributeList attList; if ( !v_it->zCoordInterpolation ) { attList.push_back( v_it->interpolationAttribute ); } provider->select( attList ); QgsFeature theFeature; double attributeValue = 0.0; bool attributeConversionOk = false; while ( provider->nextFeature( theFeature ) ) { if ( !v_it->zCoordInterpolation ) { QgsAttributeMap attMap = theFeature.attributeMap(); QgsAttributeMap::const_iterator att_it = attMap.find( v_it->interpolationAttribute ); if ( att_it == attMap.end() ) //attribute not found, something must be wrong (e.g. NULL value) { continue; } attributeValue = att_it.value().toDouble( &attributeConversionOk ); if ( !attributeConversionOk || qIsNaN( attributeValue ) ) //don't consider vertices with attributes like 'nan' for the interpolation { continue; } } if ( addVerticesToCache( theFeature.geometry(), v_it->zCoordInterpolation, attributeValue ) != 0 ) { return 3; } } } return 0; }
QImage* QgsPieDiagramFactory::createDiagram( int size, const QgsFeature& f, const QgsRenderContext& renderContext ) const { QgsAttributeMap dataValues = f.attributeMap(); double sizeScaleFactor = diagramSizeScaleFactor( renderContext ); //create transparent QImage int imageSideLength = size * sizeScaleFactor * renderContext.rasterScaleFactor() + 2 * mMaximumPenWidth + 2 * mMaximumGap; QImage* diagramImage = new QImage( QSize( imageSideLength, imageSideLength ), QImage::Format_ARGB32_Premultiplied ); diagramImage->fill( qRgba( 0, 0, 0, 0 ) ); //transparent background QPainter p; p.begin( diagramImage ); p.setRenderHint( QPainter::Antialiasing ); p.setPen( Qt::NoPen ); //calculate sum of data values double sum = 0; QList<double> valueList; //cash the values to use them in drawing later QgsAttributeMap::const_iterator value_it; QList<QgsDiagramCategory>::const_iterator it = mCategories.constBegin(); for ( ; it != mCategories.constEnd(); ++it ) { value_it = dataValues.find( it->propertyIndex() ); valueList.push_back( value_it->toDouble() ); if ( value_it != dataValues.constEnd() ) { sum += value_it->toDouble(); } } if ( doubleNear( sum, 0.0 ) ) { p.end(); delete diagramImage; return 0; } //draw pies int totalAngle = 0; int currentAngle, currentGap; int xGapOffset = 0; int yGapOffset = 0; QList<QgsDiagramCategory>::const_iterator category_it = mCategories.constBegin(); QList<double>::const_iterator valueList_it = valueList.constBegin(); for ( ; category_it != mCategories.constEnd() && valueList_it != valueList.constEnd(); ++category_it, ++valueList_it ) { p.setPen( category_it->pen() ); currentAngle = ( int )(( *valueList_it ) / sum * 360 * 16 ); p.setBrush( category_it->brush() ); xGapOffset = 0; yGapOffset = 0; currentGap = category_it->gap(); if ( currentGap != 0 ) { //qt angles are degrees*16 gapOffsetsForPieSlice( currentGap, totalAngle + currentAngle / 2, xGapOffset, yGapOffset ); } p.drawPie( mMaximumPenWidth * renderContext.rasterScaleFactor() + mMaximumGap + xGapOffset, mMaximumPenWidth * renderContext.rasterScaleFactor() + mMaximumGap - yGapOffset, sizeScaleFactor * renderContext.rasterScaleFactor() * size, sizeScaleFactor * renderContext.rasterScaleFactor() * size, totalAngle, currentAngle ); totalAngle += currentAngle; } p.end(); return diagramImage; }
void QgsWFSData::endElement( const XML_Char* el ) { QString elementName( el ); QString localName = elementName.section( NS_SEPARATOR, 1, 1 ); if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "coordinates" ) { if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } } else if ( localName == mAttributeName ) //add a thematic attribute to the feature { if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } //find index with attribute name QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.find( mAttributeName ); if ( att_it != mThematicAttributes.constEnd() ) { QVariant var; switch ( att_it.value().second.type() ) { case QVariant::Double: var = QVariant( mStringCash.toDouble() ); break; case QVariant::Int: var = QVariant( mStringCash.toInt() ); break; case QVariant::LongLong: var = QVariant( mStringCash.toLongLong() ); break; default: //string type is default var = QVariant( mStringCash ); break; } mCurrentFeature->addAttribute( att_it.value().first, QVariant( mStringCash ) ); } } else if ( localName == mGeometryAttribute ) { if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "boundedBy" && mParseModeStack.top() == QgsWFSData::boundingBox ) { //create bounding box from mStringCash if ( createBBoxFromCoordinateString( mExtent, mStringCash ) != 0 ) { QgsDebugMsg( "creation of bounding box failed" ); } if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "featureMember" ) { //MH090531: Check if all feature attributes are initialised, sometimes attribute values are missing. //We fill the not initialized ones with empty strings, otherwise the feature cannot be exported to shp later QgsAttributeMap currentFeatureAttributes = mCurrentFeature->attributeMap(); QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constBegin(); for ( ; att_it != mThematicAttributes.constEnd(); ++att_it ) { int attIndex = att_it.value().first; QgsAttributeMap::const_iterator findIt = currentFeatureAttributes.find( attIndex ); if ( findIt == currentFeatureAttributes.constEnd() ) { mCurrentFeature->addAttribute( attIndex, QVariant( "" ) ); } } mCurrentFeature->setGeometryAndOwnership( mCurrentWKB, mCurrentWKBSize ); mFeatures.insert( mCurrentFeature->id(), mCurrentFeature ); if ( !mCurrentFeatureId.isEmpty() ) { mIdMap.insert( mCurrentFeature->id(), mCurrentFeatureId ); } ++mFeatureCount; mParseModeStack.pop(); } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "Point" ) { std::list<QgsPoint> pointList; if ( pointsFromCoordinateString( pointList, mStringCash ) != 0 ) { //error } if ( mParseModeStack.top() != QgsWFSData::multiPoint ) { //directly add WKB point to the feature if ( getPointWKB( &mCurrentWKB, &mCurrentWKBSize, *( pointList.begin() ) ) != 0 ) { //error } if ( *mWkbType != QGis::WKBMultiPoint ) //keep multitype in case of geometry type mix { *mWkbType = QGis::WKBPoint; } } else //multipoint, add WKB as fragment { unsigned char* wkb = 0; int wkbSize = 0; std::list<unsigned char*> wkbList; std::list<int> wkbSizeList; if ( getPointWKB( &wkb, &wkbSize, *( pointList.begin() ) ) != 0 ) { //error } mCurrentWKBFragments.rbegin()->push_back( wkb ); mCurrentWKBFragmentSizes.rbegin()->push_back( wkbSize ); //wkbList.push_back(wkb); //wkbSizeList.push_back(wkbSize); //mCurrentWKBFragments.push_back(wkbList); //mCurrentWKBFragmentSizes.push_back(wkbSizeList); } } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "LineString" ) { //add WKB point to the feature std::list<QgsPoint> pointList; if ( pointsFromCoordinateString( pointList, mStringCash ) != 0 ) { //error } if ( mParseModeStack.top() != QgsWFSData::multiLine ) { if ( getLineWKB( &mCurrentWKB, &mCurrentWKBSize, pointList ) != 0 ) { //error } if ( *mWkbType != QGis::WKBMultiLineString )//keep multitype in case of geometry type mix { *mWkbType = QGis::WKBLineString; } } else //multiline, add WKB as fragment { unsigned char* wkb = 0; int wkbSize = 0; std::list<unsigned char*> wkbList; std::list<int> wkbSizeList; if ( getLineWKB( &wkb, &wkbSize, pointList ) != 0 ) { //error } mCurrentWKBFragments.rbegin()->push_back( wkb ); mCurrentWKBFragmentSizes.rbegin()->push_back( wkbSize ); //wkbList.push_back(wkb); //wkbSizeList.push_back(wkbSize); //mCurrentWKBFragments.push_back(wkbList); //mCurrentWKBFragmentSizes.push_back(wkbSizeList); } } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "LinearRing" ) { std::list<QgsPoint> pointList; if ( pointsFromCoordinateString( pointList, mStringCash ) != 0 ) { //error } unsigned char* wkb; int wkbSize; if ( getRingWKB( &wkb, &wkbSize, pointList ) != 0 ) { //error } mCurrentWKBFragments.rbegin()->push_back( wkb ); mCurrentWKBFragmentSizes.rbegin()->push_back( wkbSize ); } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "Polygon" ) { if ( *mWkbType != QGis::WKBMultiPolygon )//keep multitype in case of geometry type mix { *mWkbType = QGis::WKBPolygon; } if ( mParseModeStack.top() != QgsWFSData::multiPolygon ) { createPolygonFromFragments(); } } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "MultiPoint" ) { *mWkbType = QGis::WKBMultiPoint; if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } createMultiPointFromFragments(); } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "MultiLineString" ) { *mWkbType = QGis::WKBMultiLineString; if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } createMultiLineFromFragments(); } else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "MultiPolygon" ) { *mWkbType = QGis::WKBMultiPolygon; if ( !mParseModeStack.empty() ) { mParseModeStack.pop(); } createMultiPolygonFromFragments(); } }
void QgsMapToolRotatePointSymbols::canvasPressEvent( QMouseEvent *e ) { if ( !mCanvas ) { return; } mActiveLayer = currentVectorLayer(); if ( !mActiveLayer ) { notifyNotVectorLayer(); return; } if ( !mActiveLayer->isEditable() ) { notifyNotEditableLayer(); return; } if ( mActiveLayer->geometryType() != QGis::Point ) { return; } //find the closest feature to the pressed position QgsMapCanvasSnapper canvasSnapper( mCanvas ); QList<QgsSnappingResult> snapResults; if ( canvasSnapper.snapToCurrentLayer( e->pos(), snapResults, QgsSnapper::SnapToVertex, -1 ) != 0 || snapResults.size() < 1 ) { QMessageBox::critical( 0, tr( "No point feature" ), tr( "No point feature was detected at the clicked position. Please click closer to the feature or enhance the search tolerance under Settings->Options->Digitizing->Serch radius for vertex edits" ) ); return; //error during snapping } mFeatureNumber = snapResults.at( 0 ).snappedAtGeometry; //get list with renderer rotation attributes if ( layerRotationAttributes( mActiveLayer, mCurrentRotationAttributes ) != 0 ) { return; } if ( mCurrentRotationAttributes.size() < 1 ) { QMessageBox::critical( 0, tr( "No rotation Attributes" ), tr( "The active point layer does not have a rotation attribute" ) ); return; } mSnappedPoint = toCanvasCoordinates( snapResults.at( 0 ).snappedVertex ); //find out initial arrow direction QgsFeature pointFeature; if ( !mActiveLayer->featureAtId( mFeatureNumber, pointFeature, false, true ) ) { return; } const QgsAttributeMap pointFeatureAttributes = pointFeature.attributeMap(); const QgsAttributeMap::const_iterator attIt = pointFeatureAttributes.find( mCurrentRotationAttributes.at( 0 ) ); if ( attIt == pointFeatureAttributes.constEnd() ) { return; } mCurrentRotationFeature = attIt.value().toDouble(); createPixmapItem( pointFeature ); if ( mRotationItem ) { mRotationItem->setPointLocation( snapResults.at( 0 ).snappedVertex ); } mCurrentMouseAzimut = calculateAzimut( e->pos() ); setPixmapItemRotation(( int )( mCurrentMouseAzimut ) ); mRotating = true; }
int QgsTINInterpolator::insertData( QgsFeature* f, bool zCoord, int attr, InputType type ) { if ( !f ) { return 1; } QgsGeometry* g = f->geometry(); { if ( !g ) { return 2; } } //check attribute value double attributeValue = 0; bool attributeConversionOk = false; if ( !zCoord ) { QgsAttributeMap attMap = f->attributeMap(); QgsAttributeMap::const_iterator att_it = attMap.find( attr ); if ( att_it == attMap.end() ) //attribute not found, something must be wrong (e.g. NULL value) { return 3; } attributeValue = att_it.value().toDouble( &attributeConversionOk ); if ( !attributeConversionOk || qIsNaN( attributeValue ) ) //don't consider vertices with attributes like 'nan' for the interpolation { return 4; } } //parse WKB. It is ugly, but we cannot use the methods with QgsPoint because they don't contain z-values for 25D types bool hasZValue = false; double x, y, z; unsigned char* currentWkbPtr = g->asWkb(); //maybe a structure or break line Line3D* line = 0; QGis::WkbType wkbType = g->wkbType(); switch ( wkbType ) { case QGis::WKBPoint25D: hasZValue = true; case QGis::WKBPoint: { currentWkbPtr += ( 1 + sizeof( int ) ); x = *(( double * )( currentWkbPtr ) ); currentWkbPtr += sizeof( double ); y = *(( double * )( currentWkbPtr ) ); if ( zCoord && hasZValue ) { currentWkbPtr += sizeof( double ); z = *(( double * )( currentWkbPtr ) ); } else { z = attributeValue; } Point3D* thePoint = new Point3D( x, y, z ); if ( mTriangulation->addPoint( thePoint ) == -100 ) { return -1; } break; } case QGis::WKBMultiPoint25D: hasZValue = true; case QGis::WKBMultiPoint: { currentWkbPtr += ( 1 + sizeof( int ) ); int* npoints = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); for ( int index = 0; index < *npoints; ++index ) { currentWkbPtr += ( 1 + sizeof( int ) ); //skip endian and point type x = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); y = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); if ( hasZValue ) //skip z-coordinate for 25D geometries { z = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); } else { z = attributeValue; } } break; } case QGis::WKBLineString25D: hasZValue = true; case QGis::WKBLineString: { if ( type != POINTS ) { line = new Line3D(); } currentWkbPtr += ( 1 + sizeof( int ) ); int* npoints = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); for ( int index = 0; index < *npoints; ++index ) { x = *(( double * )( currentWkbPtr ) ); currentWkbPtr += sizeof( double ); y = *(( double * )( currentWkbPtr ) ); currentWkbPtr += sizeof( double ); if ( zCoord && hasZValue ) //skip z-coordinate for 25D geometries { z = *(( double * )( currentWkbPtr ) ); } else { z = attributeValue; } if ( hasZValue ) { currentWkbPtr += sizeof( double ); } if ( type == POINTS ) { //todo: handle error code -100 mTriangulation->addPoint( new Point3D( x, y, z ) ); } else { line->insertPoint( new Point3D( x, y, z ) ); } } if ( type != POINTS ) { mTriangulation->addLine( line, type == BREAK_LINES ); } break; } case QGis::WKBMultiLineString25D: hasZValue = true; case QGis::WKBMultiLineString: { currentWkbPtr += ( 1 + sizeof( int ) ); int* nlines = ( int* )currentWkbPtr; int* npoints = 0; currentWkbPtr += sizeof( int ); for ( int index = 0; index < *nlines; ++index ) { if ( type != POINTS ) { line = new Line3D(); } currentWkbPtr += ( sizeof( int ) + 1 ); npoints = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); for ( int index2 = 0; index2 < *npoints; ++index2 ) { x = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); y = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); if ( hasZValue ) //skip z-coordinate for 25D geometries { z = *(( double* ) currentWkbPtr ); currentWkbPtr += sizeof( double ); } else { z = attributeValue; } if ( type == POINTS ) { //todo: handle error code -100 mTriangulation->addPoint( new Point3D( x, y, z ) ); } else { line->insertPoint( new Point3D( x, y, z ) ); } } if ( type != POINTS ) { mTriangulation->addLine( line, type == BREAK_LINES ); } } break; } case QGis::WKBPolygon25D: hasZValue = true; case QGis::WKBPolygon: { currentWkbPtr += ( 1 + sizeof( int ) ); int* nrings = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); int* npoints; for ( int index = 0; index < *nrings; ++index ) { if ( type != POINTS ) { line = new Line3D(); } npoints = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); for ( int index2 = 0; index2 < *npoints; ++index2 ) { x = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); y = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); if ( hasZValue ) //skip z-coordinate for 25D geometries { z = *(( double* )currentWkbPtr );; currentWkbPtr += sizeof( double ); } else { z = attributeValue; } if ( type == POINTS ) { //todo: handle error code -100 mTriangulation->addPoint( new Point3D( x, y, z ) ); } else { line->insertPoint( new Point3D( x, y, z ) ); } } if ( type != POINTS ) { mTriangulation->addLine( line, type == BREAK_LINES ); } } break; } case QGis::WKBMultiPolygon25D: hasZValue = true; case QGis::WKBMultiPolygon: { currentWkbPtr += ( 1 + sizeof( int ) ); int* npolys = ( int* )currentWkbPtr; int* nrings; int* npoints; currentWkbPtr += sizeof( int ); for ( int index = 0; index < *npolys; ++index ) { currentWkbPtr += ( 1 + sizeof( int ) ); //skip endian and polygon type nrings = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); for ( int index2 = 0; index2 < *nrings; ++index2 ) { if ( type != POINTS ) { line = new Line3D(); } npoints = ( int* )currentWkbPtr; currentWkbPtr += sizeof( int ); for ( int index3 = 0; index3 < *npoints; ++index3 ) { x = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); y = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); if ( hasZValue ) //skip z-coordinate for 25D geometries { z = *(( double* )currentWkbPtr ); currentWkbPtr += sizeof( double ); } else { z = attributeValue; } if ( type == POINTS ) { //todo: handle error code -100 mTriangulation->addPoint( new Point3D( x, y, z ) ); } else { line->insertPoint( new Point3D( x, y, z ) ); } } if ( type != POINTS ) { mTriangulation->addLine( line, type == BREAK_LINES ); } } } break; } default: //should not happen... break; } return 0; }