/* -*-c++-*- */ /* osgEarth - Geospatial SDK for OpenSceneGraph * Copyright 2020 Pelican Mapping * http://osgearth.org * * osgEarth is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #ifndef OSGEARTH_HEIGHTFIELDUTILS_H #define OSGEARTH_HEIGHTFIELDUTILS_H #include #include #include #include #include namespace osgEarth { namespace Util { struct HeightFieldNeighborhood { osg::ref_ptr _center; osg::ref_ptr _neighbors[8]; osg::HeightField* getNeighbor(int xoffset, int yoffset) const { if ( xoffset == 0 && yoffset == 0 ) return _center.get(); int index = 3*(yoffset+1)+(xoffset+1); if (index > 4) index--; return _neighbors[index].get(); } void setNeighbor(int xoffset, int yoffset, osg::HeightField* hf) { if ( xoffset == 0 && yoffset == 0 ) { _center = hf; } else { int index = 3*(yoffset+1)+(xoffset+1); if (index > 4) index--; _neighbors[index] = hf; } } bool getNeighborForNormalizedLocation(double nx, double ny, osg::HeightField*& hfPtr, double& out_nx, double& out_ny) const { int xoffset = nx < 0.0 ? -1 : nx > 1.0 ? 1 : 0; int yoffset = ny < 0.0 ? 1 : ny > 1.0 ? -1 : 0; if ( xoffset != 0 || yoffset != 0 ) hfPtr = getNeighbor(xoffset, yoffset); else hfPtr = _center.get(); out_nx = nx < 0.0 ? 1.0+nx : nx > 1.0 ? nx-1.0 : nx; out_ny = ny < 0.0 ? 1.0+ny : ny > 1.0 ? ny-1.0 : ny; return hfPtr != 0L; } }; class OSGEARTH_EXPORT HeightFieldUtils { public: /** * Gets the interpolated height value at the specified fractional pixel position. */ static float getHeightAtPixel( const osg::HeightField* hf, double c, double r, RasterInterpolation interpolation = INTERP_BILINEAR); /** * Gets the height value at the specified column and row, but instead of reading * the actual height, interpolates a height based on the neighbors. */ static bool getInterpolatedHeight( const osg::HeightField* hf, unsigned c, unsigned r, float& out_height, RasterInterpolation interpoltion = INTERP_BILINEAR); /** * Gets the interpolated height value at the specific geolocation. */ static float getHeightAtLocation( const osg::HeightField* hf, double x, double y, double llx, double lly, double dx, double dy, RasterInterpolation interpolation = INTERP_BILINEAR); /** * Gets the normal vector at a geolocation */ static osg::Vec3 getNormalAtLocation( const HeightFieldNeighborhood& hood, double x, double y, double llx, double lly, double dx, double dy, RasterInterpolation interpolation = INTERP_BILINEAR); /** * Gets the interpolated elevation at the specified "normalized unit location". * i.e., nx => [0.0, 1.0], ny => [0.0, 1.0] where 0.0 and 1.0 are the opposing * endposts of the heightfield. */ static float getHeightAtNormalizedLocation( const osg::HeightField* hf, double nx, double ny, RasterInterpolation interp = INTERP_BILINEAR); /** * Gets the interpolated elevation at the specified "normalized unit location". * i.e., nx => [-1.0...2.0], ny => [-1.0...2.0] since it can query neighbors * as well. Returns FALSE if there's no heightfield in the hood. */ static bool getHeightAtNormalizedLocation( const HeightFieldNeighborhood& hood, double nx, double ny, float& output, RasterInterpolation interp = INTERP_BILINEAR); /** * Scales all the height values in a heightfield from scalar units to "linear degrees". * The only purpose of this is to show reasonable height values in a projected * Plate Carre map (in which vertical units are not well defined). */ static void scaleHeightFieldToDegrees( osg::HeightField* hf ); /** * Creates a heightfield containing MSL heights for the specified extent. * * @param numCols Width of the heightfield, not including the border * @param numRows Height of the heightfield, not including the border * @param border Size (in samples) of the border (on each side). This will increase the * actual numRows and numCols. * @param expressHeightsAsHAE * If the SRS (in GeoExtent) has a vertical datum, and expressHeightsAsHAE==true, * the height values will be those of its reference geoid. If there is no vertical * datum, or is expressHeightsAsHAE==false, we assume that MSL == the reference * ellipsoid and all the HF values will be zero. * @param initializeValue * When expressHeightsAsHAE==false, this value will be used to initialize the heightfield. */ static osg::ref_ptr createReferenceHeightField( const GeoExtent& ex, unsigned numCols, unsigned numRows, unsigned border, bool expressHeightsAsHAE = true, float initializeValue = 0.0f); /** * Subsamples a heightfield to the specified extent. */ static osg::HeightField* createSubSample( const osg::HeightField* input, const GeoExtent& inputEx, const GeoExtent& outputEx, RasterInterpolation interpolation = INTERP_BILINEAR); /** * Resizes a heightfield, keeping the corner values the same and * resampling the internal posts. */ static osg::HeightField* resampleHeightField( osg::HeightField* input, const GeoExtent& inputEx, int newX, int newY, RasterInterpolation interp = INTERP_BILINEAR ); /** * Resolves any "invalid" height values in the hieghtfield, replacing them * with geodetic (ellipsoid) relative values from a Geoid (or zero if no geoid). */ static void resolveInvalidHeights( osg::HeightField* grid, const GeoExtent& extent, float invalidValue, const Geoid* geoid ); /** * Creates a new cluster culler based on a heightfield. * Cluster cullers are for geocentric maps only, and therefore requires * the ellipsoid model. */ static osg::NodeCallback* createClusterCullingCallback( const osg::HeightField* grid, const Ellipsoid& em, float verticalScale =1.0f ); /** * Utility function that will take sample points used for interpolation and copy valid values into any of the samples that are NO_DATA_VALUE. * Returns false if all values are NO_DATA_VALUE. * Returns true if all the values are valid or if we were able to replace NO_DATA_VALUE samples with valid values. **/ static bool validateSamples(float &a, float &b, float &c, float &d); }; } } #endif //OSGEARTH_HEIGHTFIELDUTILS_H