osgEarth 2.1.1
|
00001 /* -*-c++-*- */ 00002 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph 00003 * Copyright 2008-2010 Pelican Mapping 00004 * http://osgearth.org 00005 * 00006 * osgEarth is free software; you can redistribute it and/or modify 00007 * it under the terms of the GNU Lesser General Public License as published by 00008 * the Free Software Foundation; either version 2 of the License, or 00009 * (at your option) any later version. 00010 * 00011 * This program is distributed in the hope that it will be useful, 00012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 * GNU Lesser General Public License for more details. 00015 * 00016 * You should have received a copy of the GNU Lesser General Public License 00017 * along with this program. If not, see <http://www.gnu.org/licenses/> 00018 */ 00019 #include <osgEarthFeatures/BuildTextOperator> 00020 #include <osgEarth/Utils> 00021 #include <osgDB/ReadFile> 00022 #include <osgDB/ReaderWriter> 00023 #include <osg/Geometry> 00024 #include <osg/MatrixTransform> 00025 #include <osg/Material> 00026 #include <osg/Geode> 00027 #include <osg/Version> 00028 #include <osg/Geometry> 00029 #include <osg/LineWidth> 00030 #include <osg/Point> 00031 #include <osg/Depth> 00032 #include <osg/PolygonOffset> 00033 #include <osg/ClusterCullingCallback> 00034 #include <osgText/Text> 00035 #include <osgUtil/IntersectionVisitor> 00036 #include <osgUtil/PolytopeIntersector> 00037 00038 #define LC "[BuildTextOperator] " 00039 00040 using namespace osgEarth::Symbology; 00041 using namespace osgEarth::Features; 00042 00043 // Parse a content string and replace attributes (marked with delim) with their value 00044 // eg. "City [name], [state]" -> "City San Fransisco, California" if delim is "[]" 00045 static std::string parseAttributes(Feature* feature, 00046 const std::string& content, 00047 const std::string& delim) 00048 { 00049 if (delim.size()<2) 00050 return content; 00051 00052 std::string out; 00053 size_t pos = 0; 00054 while(true) { 00055 // Search for an attribute delimiter 00056 size_t d = content.find_first_of(delim[0], pos); 00057 if (d==std::string::npos) { 00058 // No more delimiters? Add remaining text and we're done 00059 out += content.substr(pos); 00060 break; 00061 } 00062 // An attribute was found! 00063 // Add non-attribute text 00064 out += content.substr(pos, d-pos); 00065 pos = d+1; 00066 // Extract attribute name 00067 d = content.find_first_of(delim[1], pos); 00068 if (d==std::string::npos) { 00069 // No more delimiters? Add remaining text and we're done 00070 out += content.substr(pos); 00071 break; 00072 } 00073 // Add attribute and continue 00074 out += feature->getString( content.substr(pos, d-pos) ); 00075 pos = d+1; 00076 } 00077 00078 return out; 00079 } 00080 00081 osg::Node* BuildTextOperator::operator()(const FeatureList& features, 00082 const TextSymbol* symbol, 00083 const FilterContext& context) 00084 { 00085 if (!symbol) return 0; 00086 00087 std::set< std::string > labelNames; 00088 00089 bool removeDuplicateLabels = symbol->removeDuplicateLabels().isSet() ? symbol->removeDuplicateLabels().get() : false; 00090 00091 StringExpression contentExpr = *symbol->content(); 00092 00093 osg::Geode* result = new osg::Geode; 00094 for (FeatureList::const_iterator itr = features.begin(); itr != features.end(); ++itr) 00095 { 00096 Feature* feature = itr->get(); 00097 if (!feature->getGeometry()) continue; 00098 00099 std::string text; 00100 if (symbol->content().isSet()) 00101 { 00102 //Get the text from the specified content and referenced attributes 00103 text = feature->eval( contentExpr ); 00104 } 00105 00106 if (text.empty()) continue; 00107 00108 //See if there is a duplicate name 00109 if (removeDuplicateLabels && labelNames.find(text) != labelNames.end()) continue; 00110 00111 bool rotateToScreen = symbol->rotateToScreen().isSet() ? symbol->rotateToScreen().value() : false; 00112 00113 // find the centroid 00114 osg::Vec3d position; 00115 osg::Quat orientation; 00116 00117 GeometryIterator gi( feature->getGeometry() ); 00118 while( gi.hasMore() ) 00119 { 00120 Geometry* geom = gi.next(); 00121 00122 TextSymbol::LinePlacement linePlacement = symbol->linePlacement().isSet() ? symbol->linePlacement().get() : TextSymbol::LINEPLACEMENT_ALONG_LINE; 00123 if (geom->getType() == Symbology::Geometry::TYPE_LINESTRING && linePlacement == TextSymbol::LINEPLACEMENT_ALONG_LINE) 00124 { 00125 //Compute the "middle" of the line string 00126 LineString* lineString = static_cast<LineString*>(geom); 00127 double length = lineString->getLength(); 00128 double center = length / 2.0; 00129 osg::Vec3d start, end; 00130 if (lineString->getSegment(center, start, end)) 00131 { 00132 TextSymbol::LineOrientation lineOrientation = symbol->lineOrientation().isSet() ? symbol->lineOrientation().get() : TextSymbol::LINEORIENTATION_HORIZONTAL; 00133 00134 position = (end + start) / 2.0; 00135 //We don't want to orient the text at all if we are rotating to the screen 00136 if (!rotateToScreen && lineOrientation != TextSymbol::LINEORIENTATION_HORIZONTAL) 00137 { 00138 osg::Vec3d dir = (end-start); 00139 dir.normalize(); 00140 00141 if (lineOrientation == TextSymbol::LINEORIENTATION_PERPENDICULAR) 00142 { 00143 osg::Vec3d up(0,0,1); 00144 const SpatialReference* srs = context.profile()->getSRS(); 00145 if (srs && context.isGeocentric() && srs->getEllipsoid()) 00146 { 00147 osg::Vec3d w = context.toWorld( position ); 00148 up = srs->getEllipsoid()->computeLocalUpVector(w.x(), w.y(), w.z()); 00149 } 00150 dir = up ^ dir; 00151 } 00152 orientation.makeRotate(osg::Vec3d(1,0,0), dir); 00153 } 00154 } 00155 else 00156 { 00157 //Fall back on using the center 00158 position = lineString->getBounds().center(); 00159 } 00160 } 00161 else 00162 { 00163 position = geom->getBounds().center(); 00164 } 00165 } 00166 00167 osgText::Text* t = new osgText::Text(); 00168 t->setText( text ); 00169 00170 std::string font = "fonts/arial.ttf"; 00171 if (symbol->font().isSet() && !symbol->font().get().empty()) 00172 { 00173 font = symbol->font().value(); 00174 } 00175 00176 t->setFont( font ); 00177 t->setAutoRotateToScreen( rotateToScreen ); 00178 00179 TextSymbol::SizeMode sizeMode = symbol->sizeMode().isSet() ? symbol->sizeMode().get() : TextSymbol::SIZEMODE_SCREEN; 00180 if (sizeMode == TextSymbol::SIZEMODE_SCREEN) { 00181 t->setCharacterSizeMode( osgText::TextBase::SCREEN_COORDS ); 00182 } 00183 else if (sizeMode == TextSymbol::SIZEMODE_OBJECT) { 00184 t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS ); 00185 } 00186 float size = symbol->size().isSet() ? symbol->size().get() : 32.0f; 00187 t->setCharacterSize( size ); 00188 /* 00189 //TODO: We need to do something to account for autotransformed text that is under a LOD. Setting the initial bound works sometimes but not all the time. 00190 if (rotateToScreen) 00191 { 00192 //Set the initial bound so that OSG will traverse the text even if it's under an LOD. 00193 osg::BoundingBox bb; 00194 bb.expandBy( osg::BoundingSphere(position, size)); 00195 t->setInitialBound( bb); 00196 }*/ 00197 //t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT ); 00198 //t->setCharacterSize( 300000.0f ); 00199 t->setPosition( position ); 00200 t->setRotation( orientation); 00201 t->setAlignment( osgText::TextBase::CENTER_CENTER ); 00202 t->getOrCreateStateSet()->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS), osg::StateAttribute::ON ); 00203 t->getOrCreateStateSet()->setRenderBinDetails( 99999, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS ); 00204 00205 // apply styling as appropriate: 00206 osg::Vec4f textColor = symbol->fill()->color(); 00207 osg::Vec4f haloColor = symbol->halo()->color(); 00208 00209 t->setColor( textColor ); 00210 t->setBackdropColor( haloColor ); 00211 t->setBackdropType( osgText::Text::OUTLINE ); 00212 00213 if ( context.isGeocentric() ) 00214 { 00215 // install a cluster culler 00216 t->setCullCallback( new CullDrawableByNormal(position * context.inverseReferenceFrame()) ); 00217 } 00218 00219 if (_hideClutter) 00220 { 00221 osg::BoundingBox tBound = t->getBound(); 00222 osg::ref_ptr<osgUtil::PolytopeIntersector> intersector = new osgUtil::PolytopeIntersector(osgUtil::Intersector::MODEL, tBound.xMin(), tBound.yMin(), tBound.xMax(), tBound.yMax()); 00223 osgUtil::IntersectionVisitor intersectVisitor(intersector.get()); 00224 result->accept(intersectVisitor); 00225 00226 if (!intersector->containsIntersections()) 00227 { 00228 result->addDrawable( t ); 00229 if (removeDuplicateLabels) labelNames.insert(text); 00230 } 00231 } 00232 else 00233 { 00234 result->addDrawable( t ); 00235 if (removeDuplicateLabels) labelNames.insert(text); 00236 } 00237 } 00238 return result; 00239 }