/* -*-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 */ #ifndef OSGEARTH_COVERAGE_H #define OSGEARTH_COVERAGE_H 1 #include #include #include #include #include namespace osgEarth { /** * Grid of templated values. * * The parameter 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 class Coverage { public: using Ptr = std::shared_ptr>; // maps T to index for duplicate values (std::map so that T doesn't need std::hash) using LUT = std::map; // reverse lookup for reading; maps index to value using RLUT = std::unordered_map; // max out at 255 different indices. Make it a short if you want more. // TODO: consider templatizing this with something like // using Index = unsigned char; // raster of indices using Grid = std::vector; using pixel_type = T; //! create a new coverage (must be a shared_ptr) static Ptr create() { return Ptr(new Coverage()); } //! copy constructor Coverage(const Coverage& 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&& 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 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::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::Ptr _coverage; }; } #endif // OSGEARTH_COVERAGE_H