/* -*-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. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace osg { class Camera; class Polytope; } namespace osgEarth { class TerrainResolver; class GeoExtent; /** * A georeferenced 3D point. */ class OSGEARTH_EXPORT GeoPoint { public: /** * Constructs a GeoPoint. */ GeoPoint( const SpatialReference* srs, double x, double y, double z, const AltitudeMode& mode ); /** * Constructs a GeoPoint with an absolute Z. */ GeoPoint( const SpatialReference* srs, double x, double y, double z ); /** * Constructs a GeoPoint with X and Y coordinates. The Z defaults * to zero with an ALTMODE_RELATIVE altitude mode (i.e., 0 meters * above the terrain). */ GeoPoint( const SpatialReference* srs, double x, double y ); /** * Constructs a GeoPoint from a vec3. */ GeoPoint( const SpatialReference* srs, const osg::Vec3d& xyz, const AltitudeMode& mode ); /** * Constructs a GeoPoint from a vec3 with absolute Z. */ GeoPoint( const SpatialReference* srs, const osg::Vec3d& xyz); /** * Constructs a new GeoPoint by transforming an existing GeoPoint into * the specified spatial reference. */ GeoPoint( const SpatialReference* srs, const GeoPoint& rhs ); /** * Copy constructor */ GeoPoint(const GeoPoint& rhs); /** * Constructs an empty (and invalid) geopoint. */ GeoPoint(); /** * Constructs a GeoPoint at 0,0,0 absolute. */ GeoPoint(const SpatialReference* srs); /** * Constructs a geopoint from serialization */ GeoPoint( const Config& conf, const SpatialReference* srs =0L ); /** dtor */ virtual ~GeoPoint() { } /** * Sets the SRS and coords */ void set( const SpatialReference* srs, const osg::Vec3d& xyz, const AltitudeMode& mode ); void set( const SpatialReference* srs, double x, double y, double z, const AltitudeMode& mode ); // component getter/setters double& x() { return _p.x(); } double x() const { return _p.x(); } double& y() { return _p.y(); } double y() const { return _p.y(); } double& z() { return _p.z(); } double z() const { return _p.z(); } double& alt() { return _p.z(); } double alt() const { return _p.z(); } osg::Vec3d& vec3d() { return _p; } const osg::Vec3d& vec3d() const { return _p; } const SpatialReference* getSRS() const { return _srs.get(); } /** * AltitudeMode reflects whether the Z coordinate is absolute with * respect to MSL or relative to the terrain elevation at that * point. When using relative points, GeoPoint usually requires * access to a TerrainProvider in order to resolve the altitude. */ AltitudeMode& altitudeMode() { return _altMode; } const AltitudeMode& altitudeMode() const { return _altMode; } bool isRelative() const { return _altMode == ALTMODE_RELATIVE; } bool isAbsolute() const { return _altMode == ALTMODE_ABSOLUTE; } /** * Returns a copy of this geopoint transformed into another SRS. */ GeoPoint transform(const SpatialReference* outSRS) const; /** * Transforms this geopoint into another SRS. */ bool transform(const SpatialReference* outSRS, GeoPoint& output) const; /** * Transforms this point in place to another SRS. */ bool transformInPlace(const SpatialReference* srs); /** * Transforms this geopoint's Z coordinate (in place) */ bool transformZ(const AltitudeMode& altMode, const TerrainResolver* t ); /** * Transforms and returns the geopoints Z coordinate. */ bool transformZ(const AltitudeMode& altMode, const TerrainResolver* t, double& out_z) const; //! Transforms a resolution distance to a cartesian value. Distance transformResolution(const Distance& d, const UnitsType& outUnits) const; /** * Transforms this geopoint's Z to be absolute w.r.t. the vertical datum */ bool makeAbsolute(const TerrainResolver* t) { return transformZ(ALTMODE_ABSOLUTE, t); } /** * Transforms this geopoint's Z to be terrain-relative. */ bool makeRelative(const TerrainResolver* t) { return transformZ(ALTMODE_RELATIVE, t); } /** * Transforms this GeoPoint to geographic (lat/long) coords in place. */ bool makeGeographic(); /** * Outputs world coordinates corresponding to this point. If the point * is ALTMODE_RELATIVE, this will fail because there's no way to resolve * the actual Z coordinate. Use the variant of toWorld that takes a * Terrain* instead. */ bool toWorld( osg::Vec3d& out_world ) const; /** * Outputs world coordinates corresponding to this point, passing in a Terrain * object that will be used if the point needs to be converted to absolute * altitude */ bool toWorld( osg::Vec3d& out_world, const TerrainResolver* terrain ) const; /** * Converts world coordinates into a geopoint */ bool fromWorld(const SpatialReference* srs, const osg::Vec3d& world); /** * geopoint into absolute world coords. */ bool createLocalToWorld( osg::Matrixd& out_local2world ) const; /** * Outputs a matrix that will transform absolute world coordiantes so they are * localized into a local tangent place around this geopoint. */ bool createWorldToLocal( osg::Matrixd& out_world2local ) const; /** * Converts this point to the same point in a local tangent plane. */ GeoPoint toLocalTangentPlane() const; /** * Outputs an "up" vector corresponding to the given point. The up vector * is orthogonal to a local tangent plane at that point on the map. */ bool createWorldUpVector( osg::Vec3d& out_up ) const; //! Geodesic distance from this point to another. //! This is the distance along the real-world ellipsoidal surface //! of the Earth (or other body), regardless of map projection. //! It does not account for Z/altitude. //! @param rhs Other point //! @return Geodesic distance between the two points Distance geodesicDistanceTo(const GeoPoint& rhs) const; /** * @deprecated - ambiguous, will be removed. Use geodesicDistanceTo() or toWorld()/length instead. * Calculates the distance in meters from this geopoint to another. */ double distanceTo(const GeoPoint& rhs) const; /** * Interpolates a point between this point and another point * using the parameter t [0..1]. */ GeoPoint interpolate(const GeoPoint& rhs, double t) const; //! Convert this point to screen coordinates. osg::Vec2d toScreen(const osg::Camera* camera) const; //! Convenience function to return xy units const UnitsType& getXYUnits() const; bool operator == (const GeoPoint& rhs) const; bool operator != (const GeoPoint& rhs) const { return !operator==(rhs); } bool isValid() const { return _srs.valid(); } Config getConfig() const; /** * Represent this point as a string */ std::string toString() const; public: static GeoPoint INVALID; protected: osg::Vec3d _p; osg::ref_ptr _srs; AltitudeMode _altMode; }; /** * A simple circular bounding area consiting of a GeoPoint and a linear radius. */ class OSGEARTH_EXPORT GeoCircle { public: /** Construct an INVALID GeoCircle */ GeoCircle(); /** Copy another GoeCircle */ GeoCircle(const GeoCircle& rhs); /** Construct a new GeoCircle */ GeoCircle( const GeoPoint& center, double radius ); virtual ~GeoCircle() { } /** The center point of the circle */ const GeoPoint& getCenter() const { return _center; } void setCenter( const GeoPoint& value ) { _center = value; } /** Circle's radius, in linear map units (or meters for a geographic SRS) */ double getRadius() const { return _radius; } void setRadius( double value ) { _radius = value; } /** SRS of the center point */ const SpatialReference* getSRS() const { return _center.getSRS(); } /** equality test */ bool operator == ( const GeoCircle& rhs ) const; /** inequality test */ bool operator != ( const GeoCircle& rhs ) const { return !operator==(rhs); } /** validity test */ bool isValid() const { return _center.isValid() && _radius > 0.0; } /** transform the GeoCircle to another SRS */ GeoCircle transform( const SpatialReference* srs ) const; /** transform the GeoCircle to another SRS */ bool transform( const SpatialReference* srs, GeoCircle& out_circle ) const; /** does this GeoCircle intersect another? */ bool intersects( const GeoCircle& rhs ) const; public: static GeoCircle INVALID; protected: GeoPoint _center; double _radius; }; } OSGEARTH_SPECIALIZE_CONFIG(osgEarth::GeoPoint); namespace osgEarth { /** * An axis-aligned geospatial extent. A bounding box that is aligned with a * spatial reference's coordinate system. */ class OSGEARTH_EXPORT GeoExtent { public: /** Default ctor creates an "invalid" extent */ GeoExtent(); /** Contructs a valid extent */ GeoExtent( const SpatialReference* srs, double west, double south, double east, double north); /** Contructs an invalid extent that you can grow with the expandToInclude method */ GeoExtent(const SpatialReference* srs); /** Copy ctor */ GeoExtent(const GeoExtent& rhs); /** create from Bounds object */ GeoExtent(const SpatialReference* srs, const Bounds& bounds); /** dtor */ virtual ~GeoExtent() { } //! Set from the SW and NE corners. void set(double west, double south, double east, double north); bool operator == (const GeoExtent& rhs) const; bool operator != (const GeoExtent& rhs) const; /** Gets the spatial reference system underlying this extent. */ const SpatialReference* getSRS() const { return _srs.get(); } //! Coordinates of the bounding edges, normalized for the lat/long frame if necessary inline double west() const; inline double east() const; inline double south() const; inline double north() const; //! Coordinates of the bounds, NOT normalized to the lat/long frame. inline double xMin() const { return _west; } inline double xMax() const { return _west + _width; } inline double yMin() const { return _south; } inline double yMax() const { return _south + _height; } //! East-to-west span of the extent inline double width() const { return _width; } //! East-to-est span in specified units double width(const UnitsType& units) const; //! North-to-south span of the extent inline double height() const { return _height; } //! North-to-south span in specified units double height(const UnitsType& units) const; //! Gets the centroid of the bounds GeoPoint getCentroid() const; //! Legacy @deprecated bool getCentroid(double& out_x, double& out_y) const; //! True if the extent is geographic and crosses the 180 degree meridian. bool crossesAntimeridian() const; //! Raw bounds of the extent (unnormalized) void getBounds(double& xmin, double& ymin, double& xmax, double& ymax) const; /** True if this object defines a real, valid extent with positive area */ inline bool isValid() const { return _srs.valid() && _width >= 0.0 && _height >= 0.0; } inline bool isInvalid() const { return !isValid(); } /** * If this extent crosses the international date line, populates two extents, one for * each side, and returns true. Otherwise returns false and leaves the reference * parameters untouched. */ bool splitAcrossAntimeridian(GeoExtent& first, GeoExtent& second) const; /** * Returns this extent transformed into another spatial reference. * * NOTE! It is possible that the target SRS will not be able to accomadate the * extents of the source SRS. (For example, transforming a full WGS84 extent * to Mercator will resultin an error since Mercator does not cover the entire * globe.) Consider using Profile:clampAndTransformExtent() instead of using * this method directly. */ GeoExtent transform(const SpatialReference* to_srs) const; /** * Same as transform(srs) but puts the result in the output extent */ bool transform(const SpatialReference* to_srs, GeoExtent& output) const; /** * Returns true if the specified point falls within the bounds of the extent. * * @param x, y * Coordinates to test * @param xy_srs * SRS of input x and y coordinates; if null, the method assumes x and y * are in the same SRS as this object. */ bool contains(double x, double y, const SpatialReference* srs = 0L) const; template bool contains(const VEC& a, const SpatialReference* srs = nullptr) const { return contains(a.x(), a.y(), srs); } /** * Returns true if the point falls within this extent. */ bool contains(const GeoPoint& rhs) const; /** * Returns true if this extent fully contains another extent. */ bool contains(const GeoExtent& rhs) const; /** * Returns true if this extent fully contains the target bounds. */ bool contains(const Bounds& rhs) const; /** * Returns TRUE if this extent intersects another extent. * @param[in ] rhs Extent against which to perform intersect test * @param[in ] checkSRS If false, assume the extents have the same SRS (don't check). */ bool intersects(const GeoExtent& rhs, bool checkSRS = true) const; /** * Copy of the anonymous bounding box */ Bounds bounds() const; /** * Gets a geo circle bounding this extent. */ GeoCircle computeBoundingGeoCircle() const; /** * Grow this extent to include the specified point (which is assumed to be * in the extent's SRS. */ void expandToInclude(double x, double y); void expandToInclude(const osg::Vec3d& v) { expandToInclude(v.x(), v.y()); } //void expandToInclude( const Bounds& bounds ); /** * Expand this extent to include the bounds of another extent. */ bool expandToInclude(const GeoExtent& rhs); /** * Intersect this extent with another extent in the same SRS and return the * result. */ GeoExtent intersectionSameSRS(const GeoExtent& rhs) const; //const Bounds& rhs ) const; /** * Returns a human-readable string containing the extent data (without the SRS) */ std::string toString() const; /** *Inflates this GeoExtent by the given ratios */ void scale(double x_scale, double y_scale); /** * Expands the extent by x and y. */ void expand(double x, double y); /** * Expands the extent by x and y. */ void expand(const Distance& x, const Distance& y); /** *Gets the area of this GeoExtent */ double area() const; /** * Generate a polytope in world coordinates that bounds the extent. * Return false if the extent it invalid. */ bool createPolytope(osg::Polytope& out) const; /** * Computes a scale/bias matrix that transforms parametric coordinates [0..1] * from this extent into the target extent. Return false if the extents are * incompatible (different SRS, etc.) * * For example, if this extent is 100m wide, and the target extent is * 200m wide, the output matrix will have an x_scale = 0.5. * * Note! For the sake of efficiency, this method does NOT check for * validity nor for SRS equivalence -- so be sure to validate those beforehand. * It also assumes the output matrix is preset to the identity. */ bool createScaleBias(const GeoExtent& target, osg::Matrixd& output) const; bool createScaleBias(const GeoExtent& target, osg::Matrixf& output) const; /** * Generates a BoundingSphere encompassing the extent and a vertical * volume, in world coordinates. */ osg::BoundingSphered createWorldBoundingSphere(double minElev, double maxElev) const; /** * Returns true if the extent is the entire Earth, for geographic * extents. Otherwise returns false. */ bool isWholeEarth() const; public: static GeoExtent INVALID; public: // config Config getConfig() const; void fromConfig(const Config& conf); private: double _west, _width, _south, _height; osg::ref_ptr _srs; double normalizeX(double longitude) const; void clamp(); bool isGeographic() const; void setOriginAndSize(double west, double south, double width, double height); }; inline double GeoExtent::west() const { return _west; } // if east is exactly on the antimeridian, return 180.0 inline double GeoExtent::east() const { double a = normalizeX(_west + _width); return (isValid() && _srs->isGeographic() && a == -180.0) ? 180.0 : a; } inline double GeoExtent::south() const { return _south; } inline double GeoExtent::north() const { return _south + _height; } /** * A geospatial area with tile data LOD extents */ class OSGEARTH_EXPORT DataExtent : public GeoExtent { public: DataExtent(); DataExtent(const GeoExtent& extent); DataExtent(const GeoExtent& extent, const std::string &description); DataExtent(const GeoExtent& extent, unsigned minLevel); DataExtent(const GeoExtent& extent, unsigned minLevel, const std::string &description); DataExtent(const GeoExtent& extent, unsigned minLevel, unsigned maxLevel); DataExtent(const GeoExtent& extent, unsigned minLevel, unsigned maxLevel, const std::string &description); /** dtor */ virtual ~DataExtent() { } /** The minimum LOD of the extent */ optional& minLevel() { return _minLevel; } const optional& minLevel() const { return _minLevel; } /** The maximum LOD of the extent */ optional& maxLevel() { return _maxLevel; } const optional& maxLevel() const { return _maxLevel; } /** description for the data extents */ const osgEarth::optional& description() const { return _description; } private: optional _minLevel; optional _maxLevel; optional _description; }; typedef std::vector< DataExtent > DataExtentList; /** * A georeferenced image; i.e. an osg::Image and an associated GeoExtent with SRS. */ class OSGEARTH_EXPORT GeoImage { public: using pixel_type = osg::Vec4f; /** Construct an empty (invalid) geoimage. */ GeoImage(); //! Construct an image with an error status GeoImage(const Status&); //! Constructs a new goereferenced image. GeoImage(const osg::Image* image, const GeoExtent& extent); //! Constructs a new goereferenced image from a future. GeoImage(jobs::future> image, const GeoExtent& extent); /** dtor */ virtual ~GeoImage() { } static GeoImage INVALID; public: /** * True if this is a valid geo image. */ bool valid() const; //! Error status if set const Status& getStatus() const { return _status; } /** * Gets a pointer to the underlying OSG image. */ const osg::Image* getImage() const; /** * Gets the geospatial extent of the image. */ const GeoExtent& getExtent() const; /** * Shortcut to get the spatial reference system describing * the projection of the image. */ const SpatialReference* getSRS() const; /** * Crops the image to a new geospatial extent. * * @param extent * New extent to which to crop the image. * @param exact * If "exact" is true, the output image will have exactly the extents requested; * this process may require resampling and will therefore be more expensive. If * "exact" is false, we do a simple crop of the image that is rounded to the nearest * pixel. The resulting extent will be close but usually not exactly what was * requested - however, this method is faster. * @param width, height * New pixel size for the output image. By default, the method will automatically * calculate a new pixel size. */ GeoImage crop( const GeoExtent& extent, bool exact = false, unsigned int width = 0, unsigned int height = 0, bool useBilinearInterpolation = true) const; /** * Warps the image into a new spatial reference system. * * @param to_srs * SRS into which to warp the image. * @param to_extent * Supply this extent if you wish to warp AND crop the image in one step. This is * faster than calling reproject() and then crop(). * @param width, height * New pixel size for the output image. Be default, the method will automatically * calculate a new pixel size. */ GeoImage reproject( const SpatialReference* to_srs, const GeoExtent* to_extent = 0, unsigned int width = 0, unsigned int height = 0, bool useBilinearInterpolation = true) const; /** * Returns the underlying OSG image and releases the reference pointer. */ osg::ref_ptr takeImage(); /** * Gets the units per pixel of this geoimage */ double getUnitsPerPixel() const; //! Gets the coordinate at the image's s,t bool getCoord(int s, int t, double& out_x, double& out_y) const; //! Sets a token object in this GeoImage. You can use this to //! keep a weak reference to the object after creating it, //! for example to detect destruction. void setTrackingToken(osg::Object* token); osg::Object* getTrackingToken() const; unsigned s() const { return _myimage.valid() ? _myimage->s() : 0; } unsigned t() const { return _myimage.valid() ? _myimage->t() : 0; } bool read(pixel_type& output, unsigned s, unsigned t) const { _read(output, s, t); return output.r() != NO_DATA_VALUE; } bool read(pixel_type& output, double u, double v) const { _read(output, u, v); return output.r() != NO_DATA_VALUE; } ImageUtils::PixelReader& getReader() { return _read; }; const ImageUtils::PixelReader& getReader() const { return _read; } //! Read the value of a pixel at a geopoint. bool read( pixel_type& output, const GeoPoint& p) const; private: GeoExtent _extent; Status _status; osg::ref_ptr _myimage; mutable optional>> _future; osg::ref_ptr _token; ImageUtils::PixelReader _read; }; typedef std::vector GeoImageVector; struct GeoImageIterator : public ImageUtils::ImageIteratorWithExtent { GeoImageIterator(const GeoImage& im) : ImageUtils::ImageIteratorWithExtent( im.getImage(), im.getExtent()) { } }; struct GeoImagePixelReader : public ImageUtils::PixelReaderWithExtent { GeoImagePixelReader(const GeoImage& im) : ImageUtils::PixelReaderWithExtent( im.getImage(), im.getExtent()) { } }; /** * A georeferenced heightfield and associated normal/curvature map. */ class OSGEARTH_EXPORT GeoHeightField { public: /** Constructs an empty (invalid) heightfield. */ GeoHeightField(); //! Construct a heightfield with an error status GeoHeightField(const Status&); /** * Constructs a new georeferenced heightfield. */ GeoHeightField( const osg::HeightField* heightField, const GeoExtent& extent); /** dtor */ virtual ~GeoHeightField() { } static GeoHeightField INVALID; /** * True if this is a valid heightfield. */ bool valid() const; //! Status indicator const Status& getStatus() const { return _status; } /** * Gets the elevation value at a specified point. * * @param srs * Spatial reference of the query coordinates. (If you pass in NULL, the method * will assume that the SRS is equivalent to that of the GeoHeightField. Be sure * this is case of you will get incorrect results.) * @param x, y * Coordinates at which to query the elevation value. * @param interp * Interpolation method for the elevation query. * @param srsWithOutputVerticalDatum * Transform the output elevation value according to the vertical datum * associated with this SRS. If the SRS is NULL, assume a geodetic vertical datum * relative to this object's reference ellipsoid. * @param out_elevation * Output: the elevation value * @return * True if the elevation query was succesful; false if not (e.g. if the query * fell outside the geospatial extent of the heightfield) */ bool getElevation( const SpatialReference* inputSRS, double x, double y, RasterInterpolation interp, const SpatialReference* srsWithOutputVerticalDatum, float& out_elevation) const; //! Gets the elevation at a point (must be in the same SRS; bilinear interpolation) float getElevation(double x, double y, RasterInterpolation interp = INTERP_BILINEAR) const; /** * Subsamples the heightfield, returning a new heightfield corresponding to * the destEx extent. The destEx must be a smaller, inset area of sourceEx. */ GeoHeightField createSubSample(const GeoExtent& destEx, unsigned int width, unsigned int height, RasterInterpolation interpolation) const; /** * Gets the geospatial extent of the heightfield. */ const GeoExtent& getExtent() const; /** * The minimum height in the heightfield */ float getMinHeight() const { return _minHeight; } /** * The maximum height in the heightfield */ float getMaxHeight() const { return _maxHeight; } /** * Gets a pointer to the underlying OSG heightfield. */ const osg::HeightField* getHeightField() const; /** * Gets the X interval of this GeoHeightField */ double getXInterval() const; /** * Gets the Y interval of this GeoHeightField */ double getYInterval() const; //Sort GeoHeightField's by resolution struct SortByResolutionFunctor { inline bool operator() (const GeoHeightField& lhs, const GeoHeightField& rhs) const { return lhs.getXInterval() < rhs.getXInterval(); } }; protected: GeoExtent _extent; Status _status; float _minHeight, _maxHeight; osg::ref_ptr _heightField; void init(); }; typedef std::vector GeoHeightFieldVector; /** * A georeferenced node. */ class OSGEARTH_EXPORT GeoNode { public: //! Construct an empty (invalid) GeoNode GeoNode(); //! Construct an image with an error status GeoNode(const Status&); //! Constructs a new goereferenced node GeoNode(const osg::Node* node, const GeoExtent& extent); //! Global invalid node static GeoNode INVALID; public: //! True if this is a valid object bool valid() const; //! Error status if set const Status& getStatus() const { return _status; } //! The underlying OSG node const osg::Node* getNode() const { return _node.get(); } //! The node's extent const GeoExtent& getExtent() const { return _extent; } private: GeoExtent _extent; Status _status; osg::ref_ptr _node; }; typedef std::vector GeoNodeVector; }