diff --git a/src/osgEarth/Math b/src/osgEarth/Math
index 6da88afa99..fc6adaa0c2 100644
--- a/src/osgEarth/Math
+++ b/src/osgEarth/Math
@@ -16,14 +16,13 @@
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see
*/
-
-#ifndef OSGEARTH_MATH_H
-#define OSGEARTH_MATH_H 1
+#pragma once
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -512,6 +511,12 @@ namespace osgEarth
return x + 1;
}
+ template
+ inline osg::Vec4f mult(const osg::Vec4f& in, T scalar)
+ {
+ return osg::Vec4f(in.x()*scalar, in.y()*scalar, in.z()*scalar, in.w()*scalar);
+ }
+
// Adapted from Boost - see boost license
// https://www.boost.org/users/license.html
template inline std::size_t hash_value_unsigned(T val) {
@@ -631,7 +636,4 @@ namespace osgEarth
const osg::Matrix& m,
double& L, double& R, double& B, double& T, double& N, double& F);
};
-
}
-#endif
-
diff --git a/src/osgEarth/MetaTile b/src/osgEarth/MetaTile
index 924c566148..6349360f47 100644
--- a/src/osgEarth/MetaTile
+++ b/src/osgEarth/MetaTile
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
#include
namespace osgEarth { namespace Util
@@ -135,6 +136,10 @@ namespace osgEarth { namespace Util
inline const typename T::pixel_type* read(double u, double v);
inline const typename T::pixel_type* read(int s, int t);
+ //! Read the interpolated value at the geospatial coordinate (x,y)
+ //! (Bilinear interpolation is used)
+ inline bool readAtCoord(typename T::pixel_type& output, double x, double y);
+
//! The scale&bias of the tile relative to the key originally passed
//! to setCenterTileKey
inline const osg::Matrix& getScaleBias() const {
@@ -170,6 +175,9 @@ namespace osgEarth { namespace Util
Grid _tiles;
osg::Matrix _scale_bias; // scale/bias matrix of _centerKey
unsigned _width, _height;
+
+ typename Tile* getTile(int tx, int ty);
+ bool getTileAndPixelCoords(double u, double v, typename Tile*& tile, double& s, double& t);
};
template
@@ -402,6 +410,117 @@ namespace osgEarth { namespace Util
return tile._data.read((unsigned)s, (unsigned)t);
}
+ template
+ typename MetaTile::Tile* MetaTile::getTile(int tx, int ty)
+ {
+ Tile& tile = _tiles(tx, ty);
+
+ // if we already tried to load this tile and failed, bail out
+ if (tile._failed)
+ return nullptr;
+
+ // if we still need to load this tile, do so
+ if (!tile._data.valid() && _createTile != nullptr)
+ {
+ TileKey key = _centerKey.createNeighborKey(tx, -ty);
+ tile._data = _createTile(key, nullptr);
+ if (!tile._data.valid())
+ {
+ tile._failed = true;
+ }
+ }
+
+ if (tile._failed)
+ return nullptr;
+
+ return &tile;
+ }
+
+ template
+ bool MetaTile::readAtCoord(typename T::pixel_type& output, double x, double y)
+ {
+ // geoextents of center tile:
+ double xmin, ymin, xmax, ymax;
+ _centerKey.getExtent().getBounds(xmin, ymin, xmax, ymax);
+
+ // (u, v) ndc relative to center tile:
+ double u = (x - xmin) / (xmax - xmin);
+ double v = (y - ymin) / (ymax - ymin);
+
+ int tx = (int)u;
+ int ty = (int)v;
+
+ // Anchor:
+ typename Tile* p00_tile = getTile(tx, ty);
+ if (!p00_tile)
+ return false;
+ double p00_s = fract(u) * (double)_width, p00_t = fract(v) * (double)_height;
+
+ // Neighbors:
+ typename Tile* p10_tile = p00_tile;
+ double p10_s = p00_s + 1, p10_t = p00_t;
+ if (p10_s >= (double)_width)
+ {
+ p10_tile = getTile(tx + 1, ty);
+ p10_s -= (double)_width;
+ }
+
+ typename Tile* p01_tile = p00_tile;
+ double p01_s = p00_s, p01_t = p00_t + 1;
+ if (p01_t >= _height)
+ {
+ p01_tile = getTile(tx, ty + 1);
+ p01_t -= (double)_height;
+ }
+
+ typename Tile* p11_tile = p00_tile;
+ double p11_s = p00_s + 1, p11_t = p00_t + 1;
+ if (p11_s >= (double)_width || p11_t >= (double)_height)
+ {
+ p11_tile = getTile(tx + 1, ty + 1);
+ if (p11_s > (double)_width) p11_s -= (double)_width;
+ if (p11_t > (double)_height) p11_t -= (double)_height;
+ }
+
+ // calculate weights:
+ unsigned s00 = (unsigned)floor(p00_s);
+ unsigned t00 = (unsigned)floor(p00_t);
+ double left_weight = 1.0 - (p00_s - (double)s00);
+ double bottom_weight = 1.0 - (p00_t - (double)t00);
+
+ if (!p01_tile || !p10_tile || !p11_tile)
+ {
+ return p00_tile->_data.read(output, s00, t00);
+ }
+ else
+ {
+ typename T::pixel_type p00, p10, p01, p11;
+
+ unsigned s01 = clamp((unsigned)floor(p01_s), 0u, _width-1);
+ unsigned t01 = clamp((unsigned)floor(p01_t), 0u, _height-1);
+ unsigned s10 = clamp((unsigned)floor(p10_s), 0u, _width-1);
+ unsigned t10 = clamp((unsigned)floor(p10_t), 0u, _height-1);
+ unsigned s11 = clamp((unsigned)floor(p11_s), 0u, _width-1);
+ unsigned t11 = clamp((unsigned)floor(p11_t), 0u, _height-1);
+
+ if (!p00_tile->_data.read(p00, s00, t00) ||
+ !p01_tile->_data.read(p01, s01, t01) ||
+ !p10_tile->_data.read(p10, s10, t10) ||
+ !p11_tile->_data.read(p11, s11, t11))
+ {
+ return false;
+ }
+
+ // bilinear:
+ typename T::pixel_type p0, p1;
+ p0 = mult(p00, left_weight) + mult(p10, 1.0 - left_weight);
+ p1 = mult(p01, left_weight) + mult(p11, 1.0 - left_weight);
+ output = mult(p0, bottom_weight) + mult(p1, 1.0 - bottom_weight);
+ return true;
+ }
+ }
+
+
} }
#endif // OSGEARTH_METATILE_H
diff --git a/src/osgEarth/XYZ b/src/osgEarth/XYZ
index c9fab2f744..26a70f2ba1 100644
--- a/src/osgEarth/XYZ
+++ b/src/osgEarth/XYZ
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
/**
* XYZ layers. These are general purpose tiled layers that conform
@@ -67,8 +68,8 @@ namespace osgEarth { namespace XYZ
public:
META_LayerOptions(osgEarth, XYZImageLayerOptions, ImageLayer::Options);
OE_OPTION(URI, url);
- OE_OPTION(bool, invertY);
- OE_OPTION(std::string, format);
+ OE_OPTION(bool, invertY, false);
+ OE_OPTION(std::string, format, {});
static Config getMetadata();
virtual Config getConfig() const;
private:
@@ -81,9 +82,10 @@ namespace osgEarth { namespace XYZ
public:
META_LayerOptions(osgEarth, XYZElevationLayerOptions, ElevationLayer::Options);
OE_OPTION(URI, url);
- OE_OPTION(bool, invertY);
- OE_OPTION(std::string, format);
- OE_OPTION(std::string, elevationEncoding);
+ OE_OPTION(bool, invertY, false);
+ OE_OPTION(std::string, format, {});
+ OE_OPTION(std::string, elevationEncoding, {});
+ OE_OPTION(bool, stitchEdges, false);
static Config getMetadata();
virtual Config getConfig() const;
private:
@@ -212,6 +214,7 @@ namespace osgEarth
private:
osg::ref_ptr _imageLayer;
+ mutable Util::LRUCache _stitchingCache{ true, 64 };
};
} // namespace osgEarth
diff --git a/src/osgEarth/XYZ.cpp b/src/osgEarth/XYZ.cpp
index 21999a7ddd..7b54635293 100644
--- a/src/osgEarth/XYZ.cpp
+++ b/src/osgEarth/XYZ.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include "MetaTile"
#include
using namespace osgEarth;
@@ -156,6 +157,7 @@ XYZElevationLayerOptions::getConfig() const
conf.set("format", _format);
conf.set("invert_y", _invertY);
conf.set("elevation_encoding", _elevationEncoding);
+ conf.set("stitch_edges", stitchEdges());
return conf;
}
@@ -167,6 +169,7 @@ XYZElevationLayerOptions::fromConfig(const Config& conf)
conf.get("invert_y", _invertY);
conf.get("elevation_encoding", _elevationEncoding);
conf.get("interpretation", elevationEncoding()); // compat with QGIS
+ conf.get("stitch_edges", stitchEdges());
}
Config
@@ -218,6 +221,7 @@ XYZImageLayer::openImplementation()
return parent;
DataExtentList dataExtents;
+
Status status = _driver.open(
options().url().get(),
options().format().get(),
@@ -233,6 +237,11 @@ XYZImageLayer::openImplementation()
setProfile(Profile::create("spherical-mercator"));
}
+ if (dataExtents.empty())
+ {
+ dataExtents.push_back(DataExtent(getProfile()->getExtent()));
+ }
+
setDataExtents(dataExtents);
return Status::NoError;
@@ -308,39 +317,96 @@ XYZElevationLayer::openImplementation()
GeoHeightField
XYZElevationLayer::createHeightFieldImplementation(const TileKey& key, ProgressCallback* progress) const
{
- // Make an image, then convert it to a heightfield
- GeoImage geoImage = _imageLayer->createImageImplementation(key, progress);
- if (geoImage.valid())
+ if (!_imageLayer->mayHaveData(key))
+ {
+ return GeoHeightField::INVALID;
+ }
+
+ std::function decode;
+
+ if (options().elevationEncoding() == "mapbox")
+ {
+ decode = [](const osg::Vec4f& p) -> float
+ {
+ return -10000.0f + ((p.r() * 255.0f * 256.0f * 256.0f + p.g() * 255.0f * 256.0f + p.b() * 255.0f) * 0.1f);
+ };
+ }
+ else if (options().elevationEncoding() == "terrarium")
+ {
+ decode = [](const osg::Vec4f& p) -> float
+ {
+ return (p.r() * 255.0f * 256.0f + p.g() * 255.0f + p.b() * 255.0f / 256.0f) - 32768.0f;
+ };
+ }
+ else
+ {
+ decode = [](const osg::Vec4f& p) -> float
+ {
+ return p.r();
+ };
+ }
+
+ if (options().stitchEdges() == true)
{
- const osg::Image* image = geoImage.getImage();
+ MetaTile metaImage;
+ metaImage.setCreateTileFunction([this](const TileKey& key, ProgressCallback* progress)
+ {
+ Util::LRUCache::Record r;
+ if (_stitchingCache.get(key, r))
+ {
+ return r.value();
+ }
+ else
+ {
+ GeoImage image = _imageLayer->createImage(key, progress);
+ if (image.valid())
+ {
+ _stitchingCache.insert(key, image);
+ }
+ return image;
+ }
+ });
+ metaImage.setCenterTileKey(key, progress);
- if (options().elevationEncoding() == "mapbox")
+ if (!metaImage.getCenterTile().valid() || !metaImage.getScaleBias().isIdentity())
{
- // Allocate the heightfield.
- osg::HeightField* hf = new osg::HeightField();
- hf->allocate(image->s(), image->t());
+ return {};
+ }
- ImageUtils::PixelReader reader(image);
- osg::Vec4f pixel;
+ auto hf = new osg::HeightField();
+ hf->allocate(getTileSize(), getTileSize());
+ std::fill(hf->getHeightList().begin(), hf->getHeightList().end(), NO_DATA_VALUE);
- for (int c = 0; c < image->s(); c++)
+ double xmin, ymin, xmax, ymax;
+ key.getExtent().getBounds(xmin, ymin, xmax, ymax);
+
+ osg::Vec4f pixel;
+ for (int c = 0; c < getTileSize(); c++)
+ {
+ double u = (double)c / (double)(getTileSize() - 1);
+ double x = xmin + u * (xmax - xmin);
+ for (int r = 0; r < getTileSize(); r++)
{
- for (int r = 0; r < image->t(); r++)
+ double v = (double)r / (double)(getTileSize() - 1);
+ double y = ymin + v * (ymax - ymin);
+ if (metaImage.readAtCoord(pixel, x, y))
{
- reader(pixel, c, r);
- pixel.r() *= 255.0;
- pixel.g() *= 255.0;
- pixel.b() *= 255.0;
- float h = -10000.0f + ((pixel.r() * 256.0f * 256.0f + pixel.g() * 256.0f + pixel.b()) * 0.1f);
- hf->setHeight(c, r, h);
+ hf->setHeight(c, r, decode(pixel));
}
}
-
- return GeoHeightField(hf, key.getExtent());
}
- else if (options().elevationEncoding() == "terrarium")
+ return GeoHeightField(hf, key.getExtent());
+ }
+
+ else
+ {
+ // Make an image, then convert it to a heightfield
+ GeoImage geoImage = _imageLayer->createImageImplementation(key, progress);
+ if (geoImage.valid())
{
+ const osg::Image* image = geoImage.getImage();
+
// Allocate the heightfield.
osg::HeightField* hf = new osg::HeightField();
hf->allocate(image->s(), image->t());
@@ -353,25 +419,13 @@ XYZElevationLayer::createHeightFieldImplementation(const TileKey& key, ProgressC
for (int r = 0; r < image->t(); r++)
{
reader(pixel, c, r);
- pixel.r() *= 255.0;
- pixel.g() *= 255.0;
- pixel.b() *= 255.0;
- float h = (pixel.r() * 256.0f + pixel.g() + pixel.b() / 256.0f) - 32768.0f;
- hf->setHeight(c, r, h);
+ hf->setHeight(c, r, decode(pixel));
}
}
return GeoHeightField(hf, key.getExtent());
}
- else
- {
- ImageToHeightFieldConverter conv;
- osg::HeightField* hf = conv.convert(image);
- return GeoHeightField(hf, key.getExtent());
- }
+ return GeoHeightField::INVALID;
}
-
-
- return GeoHeightField::INVALID;
}
diff --git a/tests/mapzen.earth b/tests/mapzen.earth
new file mode 100644
index 0000000000..190f2de93e
--- /dev/null
+++ b/tests/mapzen.earth
@@ -0,0 +1,28 @@
+