423 lines
12 KiB
Plaintext
423 lines
12 KiB
Plaintext
|
/* -*-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 <http://www.gnu.org/licenses/>
|
||
|
*/
|
||
|
#ifndef OSGEARTH_COVERAGE_H
|
||
|
#define OSGEARTH_COVERAGE_H 1
|
||
|
|
||
|
#include <osgEarth/GeoData>
|
||
|
#include <osgEarth/ImageUtils>
|
||
|
#include <unordered_map>
|
||
|
#include <vector>
|
||
|
#include <map>
|
||
|
|
||
|
namespace osgEarth
|
||
|
{
|
||
|
/**
|
||
|
* Grid of templated values.
|
||
|
*
|
||
|
* The parameter <T> must follow implement the interface:
|
||
|
*
|
||
|
* // default constructor, which will result in valid()==false
|
||
|
* T() { }
|
||
|
*
|
||
|
* // return true for valid data, false for NO DATA.
|
||
|
* bool valid() const { return ...has a valid data value...; }
|
||
|
*
|
||
|
* // "less than" operator for sorting
|
||
|
* bool operator < (const T& rhs) const
|
||
|
*
|
||
|
* Note: This is a sparse grid for memory conservation purposes,
|
||
|
* but includes some optimizations for fast mass-insertion.
|
||
|
*/
|
||
|
template<typename T>
|
||
|
class Coverage
|
||
|
{
|
||
|
public:
|
||
|
using Ptr = std::shared_ptr<Coverage<T>>;
|
||
|
|
||
|
// maps T to index for duplicate values (std::map so that T doesn't need std::hash)
|
||
|
using LUT = std::map<T, int>;
|
||
|
|
||
|
// reverse lookup for reading; maps index to value
|
||
|
using RLUT = std::unordered_map<int, T>;
|
||
|
|
||
|
// max out at 255 different indices. Make it a short if you want more.
|
||
|
// TODO: consider templatizing this with something like
|
||
|
// <typename T, typename Index=unsigned char>
|
||
|
using Index = unsigned char;
|
||
|
|
||
|
// raster of indices
|
||
|
using Grid = std::vector<Index>;
|
||
|
|
||
|
using pixel_type = T;
|
||
|
|
||
|
//! create a new coverage (must be a shared_ptr)
|
||
|
static Ptr create()
|
||
|
{
|
||
|
return Ptr(new Coverage<T>());
|
||
|
}
|
||
|
|
||
|
//! copy constructor
|
||
|
Coverage(const Coverage<T>& rhs) :
|
||
|
_width(rhs._width),
|
||
|
_height(rhs._height),
|
||
|
_valid_count(rhs._valid_count),
|
||
|
_pixels(rhs._pixels),
|
||
|
_lut(rhs._lut),
|
||
|
_rlut(rhs._rlut)
|
||
|
{
|
||
|
//nop
|
||
|
}
|
||
|
|
||
|
//! move constructor
|
||
|
Coverage(Coverage<T>&& rhs) :
|
||
|
_width(rhs._width),
|
||
|
_height(rhs._height),
|
||
|
_valid_count(rhs._valid_count),
|
||
|
_pixels(std::move(rhs._pixels)),
|
||
|
_lut(std::move(rhs._lut)),
|
||
|
_rlut(std::move(rhs._rlut))
|
||
|
{
|
||
|
//nop
|
||
|
}
|
||
|
|
||
|
//! allocate width*height pixels
|
||
|
void allocate(unsigned w, unsigned h)
|
||
|
{
|
||
|
_width = w, _height = h;
|
||
|
_lut.clear();
|
||
|
_rlut.clear();
|
||
|
_pixels.assign(w*h, 0);
|
||
|
_valid_count = 0;
|
||
|
}
|
||
|
|
||
|
//! raster width
|
||
|
unsigned s() const
|
||
|
{
|
||
|
return _width;
|
||
|
}
|
||
|
|
||
|
//! raster height
|
||
|
unsigned t() const
|
||
|
{
|
||
|
return _height;
|
||
|
}
|
||
|
|
||
|
//! Number of pixels containing "NO DATA" value
|
||
|
unsigned nodataCount() const
|
||
|
{
|
||
|
return (_width*_height) - _valid_count;
|
||
|
}
|
||
|
|
||
|
//! Is this coverage empty?
|
||
|
bool empty() const
|
||
|
{
|
||
|
return _valid_count == 0;
|
||
|
}
|
||
|
|
||
|
//! Write a value to a pixel.
|
||
|
//! Optional optimization: This method returns a value "k". If you
|
||
|
//! are writing the same value again on the next call, pass "k" back
|
||
|
//! in as your final argument, and this will dramatically speed up
|
||
|
//! performance. This helps a lot when writing the same value many
|
||
|
//! times in a row.
|
||
|
int write(const T& value, unsigned s, unsigned t, int k = -1)
|
||
|
{
|
||
|
int ptr = t * _width + s;
|
||
|
if (value.valid())
|
||
|
{
|
||
|
if (_pixels[ptr] > 0)
|
||
|
--_valid_count;
|
||
|
|
||
|
if (k < 0)
|
||
|
{
|
||
|
typename LUT::iterator i = _lut.find(value);
|
||
|
if (i != _lut.end())
|
||
|
{
|
||
|
k = i->second;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
k = _lut.size() + 1; // skip 0, 0=>nodata
|
||
|
_lut[value] = k;
|
||
|
_rlut[k] = value;
|
||
|
}
|
||
|
}
|
||
|
++_valid_count;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
k = 0;
|
||
|
if (_pixels[ptr] > 0)
|
||
|
--_valid_count;
|
||
|
}
|
||
|
|
||
|
_pixels[ptr] = k;
|
||
|
return k;
|
||
|
}
|
||
|
|
||
|
bool read(T& output, unsigned s, unsigned t) const
|
||
|
{
|
||
|
const T* value = read(s, t);
|
||
|
if (value) output = *value;
|
||
|
return value != nullptr;
|
||
|
}
|
||
|
|
||
|
bool read(T& output, double u, double v) const
|
||
|
{
|
||
|
const T* value = read(u, v);
|
||
|
if (value) output = *value;
|
||
|
return value != nullptr;
|
||
|
}
|
||
|
|
||
|
const T* read(double u, double v) const
|
||
|
{
|
||
|
unsigned s, t;
|
||
|
ImageUtils::nnUVtoST(u, v, s, t, _width, _height);
|
||
|
return read(s, t);
|
||
|
}
|
||
|
|
||
|
const T* read(unsigned s, unsigned t) const
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(s < _width && t < _height, nullptr);
|
||
|
int ptr = t * _width + s;
|
||
|
Index i = _pixels[ptr];
|
||
|
if (i > 0)
|
||
|
{
|
||
|
return &_rlut[i];
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
bool hasDataAt(unsigned s, unsigned t) const
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(s < _width && t < _height, false);
|
||
|
int ptr = t * _width + s;
|
||
|
return _pixels[ptr] > 0;
|
||
|
}
|
||
|
|
||
|
Config getConfig() const
|
||
|
{
|
||
|
Config root("coverage");
|
||
|
root.set("width", _width);
|
||
|
root.set("height", _height);
|
||
|
|
||
|
Config values("values");
|
||
|
for (auto& entry : _lut)
|
||
|
{
|
||
|
Config value_conf = entry.first.getConfig();
|
||
|
value_conf.set("__coverage_index", entry.second);
|
||
|
values.add(value_conf);
|
||
|
}
|
||
|
root.add(values);
|
||
|
|
||
|
std::ostringstream pixels_string;
|
||
|
int count = 0;
|
||
|
int last_value = -1;
|
||
|
for(auto& pixel : _pixels)
|
||
|
{
|
||
|
if (last_value >= 0 && pixel != last_value) {
|
||
|
pixels_string << (int)count << ' ' << (int)last_value << ' ';
|
||
|
count = 0;
|
||
|
}
|
||
|
last_value = pixel;
|
||
|
++count;
|
||
|
}
|
||
|
if (count > 0) {
|
||
|
pixels_string << (int)count << ' ' << (int)last_value;
|
||
|
}
|
||
|
root.add(Config("pixels", pixels_string.str()));
|
||
|
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
void setConfig(const Config& root)
|
||
|
{
|
||
|
unsigned w = 256, h = 256;
|
||
|
root.get("width", w);
|
||
|
root.get("height", h);
|
||
|
allocate(w, h);
|
||
|
|
||
|
Config values = root.child("values");
|
||
|
for (auto& value_conf : values.children())
|
||
|
{
|
||
|
T value(value_conf);
|
||
|
int index;
|
||
|
value_conf.get("__coverage_index", index);
|
||
|
_lut[value] = index;
|
||
|
_rlut[index] = value;
|
||
|
}
|
||
|
|
||
|
std::string pixels_string = root.value("pixels");
|
||
|
std::istringstream pixels_stream(pixels_string);
|
||
|
int count, pixel;
|
||
|
int ptr = 0;
|
||
|
while (!pixels_stream.eof() && ptr < (int)(w*h))
|
||
|
{
|
||
|
pixels_stream >> count >> pixel;
|
||
|
for (int i = 0; i < count; ++i) {
|
||
|
_pixels[ptr++] = pixel;
|
||
|
if (pixel > 0)
|
||
|
++_valid_count;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
Coverage()
|
||
|
{
|
||
|
allocate(0, 0);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
unsigned _width, _height;
|
||
|
unsigned _valid_count;
|
||
|
Grid _pixels;
|
||
|
LUT _lut;
|
||
|
mutable RLUT _rlut;
|
||
|
};
|
||
|
|
||
|
template<typename T>
|
||
|
class GeoCoverage
|
||
|
{
|
||
|
public:
|
||
|
using pixel_type = T;
|
||
|
|
||
|
//! Construct a GeoCoverage
|
||
|
//! @param coverage Coverage to include in the geocoverage
|
||
|
//! @param extent geospatial extent of the coverage data
|
||
|
GeoCoverage(typename Coverage<T>::Ptr coverage, const GeoExtent& extent)
|
||
|
: _coverage(coverage), _extent(extent), _valid(true)
|
||
|
{
|
||
|
if (coverage == nullptr)
|
||
|
_valid = false;
|
||
|
}
|
||
|
|
||
|
GeoCoverage()
|
||
|
: _valid(false)
|
||
|
{
|
||
|
//nop
|
||
|
}
|
||
|
|
||
|
inline bool valid() const {
|
||
|
return _valid;
|
||
|
}
|
||
|
|
||
|
inline unsigned s() const {
|
||
|
return _coverage ? _coverage->s() : 0u;
|
||
|
}
|
||
|
|
||
|
inline unsigned t() const {
|
||
|
return _coverage ? _coverage->t() : 0u;
|
||
|
}
|
||
|
|
||
|
inline unsigned nodataCount() const {
|
||
|
return _coverage ? _coverage->nodataCount() : 0u;
|
||
|
}
|
||
|
|
||
|
inline unsigned empty() const {
|
||
|
return _coverage ? _coverage->empty() : true;
|
||
|
}
|
||
|
|
||
|
void write(const T& value, unsigned s, unsigned t)
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(_coverage != nullptr, void());
|
||
|
_coverage->write(value, s, t);
|
||
|
}
|
||
|
|
||
|
bool read(T& value, unsigned s, unsigned t) const
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(_coverage != nullptr, false);
|
||
|
return _coverage->read(value, s, t);
|
||
|
}
|
||
|
|
||
|
bool read(T& value, double u, double v) const
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(_coverage != nullptr, false);
|
||
|
return _coverage->read(value, u, v);
|
||
|
}
|
||
|
|
||
|
const T* read(unsigned s, unsigned t) const
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(_coverage != nullptr, nullptr);
|
||
|
return _coverage->read(s, t);
|
||
|
}
|
||
|
|
||
|
const T* read(double u, double v) const
|
||
|
{
|
||
|
OE_SOFT_ASSERT_AND_RETURN(_coverage != nullptr, nullptr);
|
||
|
return _coverage->read(u, v);
|
||
|
}
|
||
|
|
||
|
bool readAtCoords(T& value, double x, double y) const
|
||
|
{
|
||
|
if (_extent.contains(x, y))
|
||
|
{
|
||
|
unsigned xs = (unsigned)((double)(s() - 1) * (x - _extent.xMin()) / _extent.width());
|
||
|
unsigned yt = (unsigned)((double)(t() - 1) * (y - _extent.yMin()) / _extent.height());
|
||
|
if (xs >= 0 && xs < s() && yt >= 0 && yt < t())
|
||
|
{
|
||
|
read(value, xs, yt);
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const T* readAtCoords(double x, double y) const
|
||
|
{
|
||
|
if (_extent.contains(x, y))
|
||
|
{
|
||
|
unsigned xs = (unsigned)((double)(s() - 1) * (x - _extent.xMin()) / _extent.width());
|
||
|
unsigned yt = (unsigned)((double)(t() - 1) * (y - _extent.yMin()) / _extent.height());
|
||
|
if (xs >= 0 && xs < s() && yt >= 0 && yt < t())
|
||
|
{
|
||
|
return read(xs, yt);
|
||
|
}
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
const GeoExtent& getExtent() const
|
||
|
{
|
||
|
return _extent;
|
||
|
}
|
||
|
|
||
|
Config getConfig() const
|
||
|
{
|
||
|
Config root("geocoverage");
|
||
|
root.set("extent", _extent.getConfig());
|
||
|
root.add(_coverage->getConfig());
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
void setConfig(const Config& in)
|
||
|
{
|
||
|
_extent.fromConfig(in.child("extent"));
|
||
|
_coverage->setConfig(in.child("coverage"));
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
GeoExtent _extent;
|
||
|
bool _valid;
|
||
|
typename Coverage<T>::Ptr _coverage;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
#endif // OSGEARTH_COVERAGE_H
|