diff --git a/.gitignore b/.gitignore index 6b50c0d..b4ce1be 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ Thumbs.db /.qmake.cache /.qmake.stash Build/ +build/ # qtcreator generated files *.pro.user* diff --git a/CMakeLists.txt b/CMakeLists.txt index e53feaa..769c0bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,14 +39,14 @@ ExternalProject_Add(libcorrect CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} ) -find_package(OpenCV REQUIRED PATHS "D:/Programozas/opencv/own_build/install/") +find_package(OpenCV) link_directories( ${CMAKE_BINARY_DIR}/sgp4-build/libsgp4 ${CMAKE_BINARY_DIR}/libcorrect-build/lib ) -add_definitions(-D_USE_MATH_DEFINES) +add_definitions(-D_USE_MATH_DEFINES -D_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING) add_executable(meteordemod main.cpp @@ -124,12 +124,14 @@ if(WIN32) ${OpenCV_LIBS} sgp4.lib correct.lib + stdc++fs ) else() target_link_libraries(meteordemod ${OpenCV_LIBS} sgp4.a correct.a + stdc++fs ) endif() @@ -148,8 +150,8 @@ if(UNIX AND NOT APPLE) if (DEBIAN_FOUND) SET(CPACK_GENERATOR "DEB") SET(CPACK_PACKAGE_VERSION_MAJOR "2") - SET(CPACK_PACKAGE_VERSION_MINOR "3") - SET(CPACK_PACKAGE_VERSION_PATCH "1") + SET(CPACK_PACKAGE_VERSION_MINOR "4") + SET(CPACK_PACKAGE_VERSION_PATCH "4") SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Digitelektro") SET(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/Digitelektro/MeteorDemod") SET(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Russian Meteor M2 weather satellite data decoder") diff --git a/GIS/shaperenderer.cpp b/GIS/shaperenderer.cpp index e533050..4233a23 100644 --- a/GIS/shaperenderer.cpp +++ b/GIS/shaperenderer.cpp @@ -10,7 +10,8 @@ GIS::ShapeRenderer::ShapeRenderer(const std::string shapeFile, const cv::Scalar , mAltitude(altitude) , mThicknes(5) , mPointRadius(10) - , mFontScale(2) + , mFontHeight(40) + , mFontLineWidth(2) { } @@ -25,7 +26,7 @@ void GIS::ShapeRenderer::setTextFieldName(const std::string &name) mTextFieldName = name; } -void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float yStart) +void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float yStart, float scale) { if(!load()) { return; @@ -43,7 +44,7 @@ void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float ySt for(polyLineIterator->begin(); *polyLineIterator != polyLineIterator->end(); ++(*polyLineIterator)) { //std::cout << polyLineIterator->point.x << " " << polyLineIterator->point.y << std::endl; - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection(polyLineIterator->point.y, polyLineIterator->point.x, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection({polyLineIterator->point.y, polyLineIterator->point.x, 0}, mEarthRadius + mAltitude, scale); coordinate.x += -xStart; coordinate.y += -yStart; @@ -65,7 +66,7 @@ void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float ySt for(recordIterator->begin(); *recordIterator != recordIterator->end(); ++(*recordIterator)) { ShapeReader::Point point(*recordIterator); - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection(point.y, point.x, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection({point.y, point.x, 0}, mEarthRadius + mAltitude, scale); coordinate.x += -xStart; coordinate.y += -yStart; @@ -83,7 +84,7 @@ void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float ySt ShapeReader::Point point(*recordIterator); std::vector fieldValues = dbFilereader.getFieldValues(i); - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection(point.y, point.x, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection({point.y, point.x, 0}, mEarthRadius + mAltitude, scale); coordinate.x += -xStart; coordinate.y += -yStart; @@ -113,10 +114,11 @@ void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float ySt } if(drawName) { + double fontScale = cv::getFontScaleFromHeight(cv::FONT_ITALIC, mFontHeight, mFontLineWidth); int baseLine; - cv::Size size = cv::getTextSize(fieldValues[namePos], cv::FONT_ITALIC, mFontScale, mThicknes, &baseLine); - cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, mFontScale, cv::Scalar(0,0,0), mThicknes+1, cv::LINE_AA); - cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, mFontScale, mColor, mThicknes, cv::LINE_AA); + cv::Size size = cv::getTextSize(fieldValues[namePos], cv::FONT_ITALIC, fontScale, mFontLineWidth, &baseLine); + cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, fontScale, cv::Scalar(0,0,0), mFontLineWidth+1, cv::LINE_AA); + cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, fontScale, mColor, mFontLineWidth, cv::LINE_AA); } } } @@ -124,7 +126,7 @@ void GIS::ShapeRenderer::drawShapeMercator(cv::Mat &src, float xStart, float ySt } } -void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float yStart, float centerLatitude, float centerLongitude) +void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float yStart, float centerLatitude, float centerLongitude, float scale) { if(!load()) { return; @@ -142,7 +144,7 @@ void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float for(polyLineIterator->begin(); *polyLineIterator != polyLineIterator->end(); ++(*polyLineIterator)) { //std::cout << polyLineIterator->point.x << " " << polyLineIterator->point.y << std::endl; - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(polyLineIterator->point.y, polyLineIterator->point.x, centerLatitude, centerLongitude, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection({polyLineIterator->point.y, polyLineIterator->point.x, 0}, {centerLatitude, centerLongitude, 0}, mEarthRadius + mAltitude, scale); coordinate.x += -xStart; coordinate.y += -yStart; @@ -166,7 +168,7 @@ void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float for(recordIterator->begin(); *recordIterator != recordIterator->end(); ++(*recordIterator)) { ShapeReader::Point point(*recordIterator); - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(point.y, point.x, centerLatitude, centerLongitude, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection({point.y, point.x, 0}, {centerLatitude, centerLongitude, 0}, mEarthRadius + mAltitude, scale); coordinate.x += -xStart; coordinate.y += -yStart; @@ -188,7 +190,7 @@ void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float ShapeReader::Point point(*recordIterator); std::vector fieldValues = dbFilereader.getFieldValues(i); - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(point.y, point.x, centerLatitude, centerLongitude, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection({point.y, point.x, 0}, {centerLatitude, centerLongitude, 0}, mEarthRadius + mAltitude, scale); coordinate.x += -xStart; coordinate.y += -yStart; @@ -222,10 +224,11 @@ void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float } if(drawName) { + double fontScale = cv::getFontScaleFromHeight(cv::FONT_ITALIC, mFontHeight, mFontLineWidth); int baseLine; - cv::Size size = cv::getTextSize(fieldValues[namePos], cv::FONT_ITALIC, mFontScale, mThicknes, &baseLine); - cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, mFontScale, cv::Scalar(0,0,0), mThicknes+1, cv::LINE_AA); - cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, mFontScale, mColor, mThicknes, cv::LINE_AA); + cv::Size size = cv::getTextSize(fieldValues[namePos], cv::FONT_ITALIC, fontScale, mFontLineWidth, &baseLine); + cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, fontScale, cv::Scalar(0,0,0), mFontLineWidth+1, cv::LINE_AA); + cv::putText(src, fieldValues[namePos], cv::Point2d(coordinate.x - (size.width/2), coordinate.y - size.height + baseLine), cv::FONT_ITALIC, fontScale, mColor, mFontLineWidth, cv::LINE_AA); } } } @@ -235,36 +238,17 @@ void GIS::ShapeRenderer::drawShapeEquidistant(cv::Mat &src, float xStart, float bool GIS::ShapeRenderer::equidistantCheck(float latitude, float longitude, float centerLatitude, float centerLongitude) { - bool longResult = true; - bool latResult = true; - - int minLongitude = static_cast(centerLongitude - 90); - int maxLongitude = static_cast(centerLongitude + 90); - int minLatitude = static_cast(centerLatitude - 45); - int maxLatitude = static_cast(centerLatitude + 45); - - //Normalize - minLongitude = ((minLongitude + 540) % 360 - 180); - maxLongitude = ((maxLongitude + 540) % 360 - 180); - minLatitude = ((minLatitude + 270) % 180 - 90); - maxLatitude = ((maxLatitude + 270) % 180 - 90); - - if(maxLatitude < minLatitude) - { - latResult = latitude > minLatitude || latitude < maxLatitude; - } - else - { - latResult = latitude > minLatitude && latitude < maxLatitude; - } - if(maxLongitude < minLongitude) - { - longResult = longitude < minLongitude || longitude < maxLongitude; - } - else + //Degree To radian + latitude = M_PI * latitude / 180.0f; + longitude = M_PI * longitude / 180.0f; + centerLatitude= M_PI * centerLatitude / 180.0f; + centerLongitude= M_PI * centerLongitude / 180.0f; + + float deltaSigma = std::sin(centerLatitude) * std::sin(latitude) + std::cos(latitude) * std::cos(longitude - centerLongitude); + if (deltaSigma < 0.0) { - longResult = longitude > minLongitude && longitude < maxLongitude; + return false; } - return longResult && latResult; + return true; } diff --git a/GIS/shaperenderer.h b/GIS/shaperenderer.h index e1f7809..6edeafa 100644 --- a/GIS/shaperenderer.h +++ b/GIS/shaperenderer.h @@ -20,8 +20,8 @@ class ShapeRenderer : public ShapeReader void addNumericFilter(const std::string name, int value); void setTextFieldName(const std::string &name); - void drawShapeMercator(cv::Mat &src, float xStart, float yStart); - void drawShapeEquidistant(cv::Mat &src, float xStart, float yStart, float centerLatitude, float centerLongitude); + void drawShapeMercator(cv::Mat &src, float xStart, float yStart, float scale); + void drawShapeEquidistant(cv::Mat &src, float xStart, float yStart, float centerLatitude, float centerLongitude, float scale); public: //setters void setThickness(int thickness) { @@ -30,8 +30,11 @@ class ShapeRenderer : public ShapeReader void setPointRadius(int radius) { mPointRadius = radius; } - void setFontScale(int scale) { - mFontScale = scale; + void setFontHeight(int height) { + mFontHeight = height; + } + void setFontLineWidth(int width) { + mFontLineWidth = width; } private: @@ -45,7 +48,8 @@ class ShapeRenderer : public ShapeReader std::string mTextFieldName; int mThicknes; int mPointRadius; - int mFontScale; + int mFontHeight; + int mFontLineWidth; }; } //namespace GIS diff --git a/MeteorDemod.pro b/MeteorDemod.pro index 329c82c..9127b41 100644 --- a/MeteorDemod.pro +++ b/MeteorDemod.pro @@ -7,6 +7,9 @@ CONFIG -= app_bundle # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS LIQUID_BUILD_CPLUSPLUS DEFINES +=_USE_MATH_DEFINES +DEFINES += _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING + +QMAKE_LFLAGS += -lstdc++fs # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. diff --git a/common/settings.cpp b/common/settings.cpp index e7ad3d3..283a77a 100644 --- a/common/settings.cpp +++ b/common/settings.cpp @@ -61,7 +61,18 @@ void Settings::parseIni(const std::string &path) ini::extract(mIniParser.sections["Program"]["RollM2"], mM2Roll, -2.9f); ini::extract(mIniParser.sections["Program"]["PitchM2"], mM2Pitch, 0.3f); ini::extract(mIniParser.sections["Program"]["YawM2"], mM2Yaw, 0.0f); + ini::extract(mIniParser.sections["Program"]["TimeOffsetM2"], mTimeOffsetM2Sec, 0); ini::extract(mIniParser.sections["Program"]["NightPassTreshold"], mNightPassTreshold, 10.0f); + ini::extract(mIniParser.sections["Program"]["ProjectionScale"], mProjectionScale, 0.75f); + ini::extract(mIniParser.sections["Program"]["CompositeProjectionScale"], mCompositeProjectionScale, 0.75f); + ini::extract(mIniParser.sections["Program"]["CompositeAzimuthalEquidistantProjection"], mCompositeEquadistantProjection, true); + ini::extract(mIniParser.sections["Program"]["CompositeMercatorProjection"], mCompositeMercatorProjection, false); + ini::extract(mIniParser.sections["Program"]["GenerateComposite123"], mGenerateComposite123, true); + ini::extract(mIniParser.sections["Program"]["GenerateComposite125"], mGenerateComposite125, true); + ini::extract(mIniParser.sections["Program"]["GenerateComposite221"], mGenerateComposite221, true); + ini::extract(mIniParser.sections["Program"]["GenerateComposite68"], mGenerateComposite68, true); + ini::extract(mIniParser.sections["Program"]["GenerateCompositeThermal"], mGenerateCompositeThermal, true); + ini::extract(mIniParser.sections["Program"]["GenerateComposite68Rain"], mGenerateComposite68Rain, true); ini::extract(mIniParser.sections["Demodulator"]["CostasBandwidth"], mCostasBw, 50); ini::extract(mIniParser.sections["Demodulator"]["RRCFilterOrder"], mRRCFilterOrder, 64); @@ -71,7 +82,8 @@ void Settings::parseIni(const std::string &path) ini::extract(mIniParser.sections["Watermark"]["Place"], mWaterMarkPlace); ini::extract(mIniParser.sections["Watermark"]["Color"], mWaterMarkColor, HTMLColor(0xAD880C)); - ini::extract(mIniParser.sections["Watermark"]["Size"], mWaterMarkSize, 5); + ini::extract(mIniParser.sections["Watermark"]["Size"], mWaterMarkSize, 80); + ini::extract(mIniParser.sections["Watermark"]["Width"], mWaterMarkLineWidth, 3); ini::extract(mIniParser.sections["Watermark"]["Text"], mWaterMarkText); ini::extract(mIniParser.sections["ReceiverLocation"]["Draw"], mDrawreceiver, false); @@ -84,20 +96,20 @@ void Settings::parseIni(const std::string &path) ini::extract(mIniParser.sections["ShapeFileGraticules"]["FileName"], mShapeGraticulesFile); ini::extract(mIniParser.sections["ShapeFileGraticules"]["Color"], mShapeGraticulesColor, HTMLColor(0xC8C8C8)); - ini::extract(mIniParser.sections["ShapeFileGraticules"]["Thickness"], mShapeGraticulesThickness, 5); + ini::extract(mIniParser.sections["ShapeFileGraticules"]["Thickness"], mShapeGraticulesThickness, 1); ini::extract(mIniParser.sections["ShapeFileCoastLines"]["FileName"], mShapeCoastLinesFile); ini::extract(mIniParser.sections["ShapeFileCoastLines"]["Color"], mShapeCoastLinesColor, HTMLColor(0x808000)); - ini::extract(mIniParser.sections["ShapeFileCoastLines"]["Thickness"], mShapeCoastLinesThickness, 5); + ini::extract(mIniParser.sections["ShapeFileCoastLines"]["Thickness"], mShapeCoastLinesThickness, 2); ini::extract(mIniParser.sections["ShapeFileBoundaryLines"]["FileName"], mShapeBoundaryLinesFile); ini::extract(mIniParser.sections["ShapeFileBoundaryLines"]["Color"], mShapeBoundaryLinesColor, HTMLColor(0xC8C8C8)); - ini::extract(mIniParser.sections["ShapeFileBoundaryLines"]["Thickness"], mShapeBoundaryLinesThickness, 5); + ini::extract(mIniParser.sections["ShapeFileBoundaryLines"]["Thickness"], mShapeBoundaryLinesThickness, 2); ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["FileName"], mShapePopulatedPlacesFile); ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["Color"], mShapePopulatedPlacesColor, HTMLColor(0x5A42F5)); - ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["Thickness"], mShapePopulatedPlacesThickness, 5); - ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["FontScale"], mShapePopulatedPlacesFontScale, 2); + ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["Width"], mShapePopulatedPlacesFontWidth, 2); + ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["Size"], mShapePopulatedPlacesFontSize, 40); ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["PointRadius"], mShapePopulatedPlacesPointradius, 10); ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["FilterColumnName"], mShapePopulatedPlacesFilterColumnName, std::string("ADM0CAP")); ini::extract(mIniParser.sections["ShapeFilePopulatedPlaces"]["NumericFilter"], mShapePopulatedPlacesNumbericFilter, 1); diff --git a/common/settings.h b/common/settings.h index adfcf87..3dbbda3 100644 --- a/common/settings.h +++ b/common/settings.h @@ -113,6 +113,19 @@ class Settings bool spreadImage() const { return mSpreadImage; } bool addRainOverlay() const { return mAddRainOverlay; } float getNightPassTreshold() const { return mNightPassTreshold; } + float getProjectionScale() const {return mProjectionScale; } + float getCompositeProjectionScale() const { return mCompositeProjectionScale; } + + bool compositeEquadistantProjection() const { return mCompositeEquadistantProjection; } + bool compositeMercatorProjection() const { return mCompositeMercatorProjection; } + bool generateComposite123() const { return mGenerateComposite123; } + bool generateComposite125() const { return mGenerateComposite125; } + bool generateComposite221() const { return mGenerateComposite221; } + bool generateComposite68() const { return mGenerateComposite68; } + bool generateCompositeThermal() const { return mGenerateCompositeThermal; } + bool generateComposite68Rain() const { return mGenerateComposite68Rain; } + + int getTimeOffsetM2Sec() const { return mTimeOffsetM2Sec; } int getCostasBandwidth() const { return mCostasBw; } int getRRCFilterOrder() const { return mRRCFilterOrder; } @@ -123,6 +136,7 @@ class Settings const std::string &getWaterMarkPlace() const { return mWaterMarkPlace; } const HTMLColor &getWaterMarkColor() const { return mWaterMarkColor; } int getWaterMarkSize() const { return mWaterMarkSize; } + int getWaterMarkLineWidth() const { return mWaterMarkLineWidth; } const std::string &getWaterMarkText() const { return mWaterMarkText; } bool drawReceiver() const { return mDrawreceiver; } @@ -147,8 +161,8 @@ class Settings const std::string &getShapePopulatedPlacesFile() const { return mShapePopulatedPlacesFile; } const HTMLColor &getShapePopulatedPlacesColor() const { return mShapePopulatedPlacesColor; } - int getShapePopulatedPlacesThickness() const { return mShapePopulatedPlacesThickness; } - int getShapePopulatedPlacesFontScale() const { return mShapePopulatedPlacesFontScale; } + int getShapePopulatedPlacesFontWidth() const { return mShapePopulatedPlacesFontWidth; } + int getShapePopulatedPlacesFontSize() const { return mShapePopulatedPlacesFontSize; } int getShapePopulatedPlacesPointradius() const { return mShapePopulatedPlacesPointradius; } const std::string &getShapePopulatedPlacesFilterColumnName() const { return mShapePopulatedPlacesFilterColumnName; } int getShapePopulatedPlacesNumbericFilter() const { return mShapePopulatedPlacesNumbericFilter; } @@ -170,6 +184,19 @@ class Settings bool mSpreadImage; bool mAddRainOverlay; float mNightPassTreshold; + float mProjectionScale; + float mCompositeProjectionScale; + + bool mCompositeEquadistantProjection; + bool mCompositeMercatorProjection; + bool mGenerateComposite123; + bool mGenerateComposite125; + bool mGenerateComposite221; + bool mGenerateComposite68; + bool mGenerateCompositeThermal; + bool mGenerateComposite68Rain; + + int mTimeOffsetM2Sec; //ini section: Demodulator int mCostasBw; @@ -183,6 +210,7 @@ class Settings std::string mWaterMarkPlace; HTMLColor mWaterMarkColor; int mWaterMarkSize; + int mWaterMarkLineWidth; std::string mWaterMarkText; //ini section: ReceiverLocation @@ -212,8 +240,8 @@ class Settings //ini section: ShapeFilePopulatedPlaces std::string mShapePopulatedPlacesFile; HTMLColor mShapePopulatedPlacesColor; - int mShapePopulatedPlacesThickness; - int mShapePopulatedPlacesFontScale; + int mShapePopulatedPlacesFontWidth; + int mShapePopulatedPlacesFontSize; int mShapePopulatedPlacesPointradius; std::string mShapePopulatedPlacesFilterColumnName; int mShapePopulatedPlacesNumbericFilter; diff --git a/common/version.h b/common/version.h index cce6f5b..8c3b9c4 100644 --- a/common/version.h +++ b/common/version.h @@ -2,7 +2,7 @@ #define VERSION_H #define VERSION_MAJOR 2 -#define VERSION_MINOR 3 -#define VERSION_FIX 1 +#define VERSION_MINOR 4 +#define VERSION_FIX 4 #endif // VERSION_H diff --git a/imageproc/spreadimage.cpp b/imageproc/spreadimage.cpp index 5a9f660..76a17e9 100644 --- a/imageproc/spreadimage.cpp +++ b/imageproc/spreadimage.cpp @@ -1,8 +1,10 @@ #include "spreadimage.h" #include "GIS/shaperenderer.h" #include "settings.h" -//#include +#include #include +#include +//#include std::map SpreadImage::MarkerLookup { { "STAR", cv::MARKER_STAR }, @@ -89,15 +91,20 @@ cv::Mat SpreadImage::stretch(const cv::Mat &image) return strechedImage; } -cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, ProgressCallback progressCallback) +cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, float scale, ProgressCallback progressCallback) { cv::Point2f srcTri[3]; cv::Point2f dstTri[3]; - double MinX = std::min(geolocationCalculator.getTopLeftMercator().x, std::min(geolocationCalculator.getTopRightMercator().x, std::min(geolocationCalculator.getBottomLeftMercator().x, geolocationCalculator.getBottomRightMercator().x))); - double MinY = std::min(geolocationCalculator.getTopLeftMercator().y, std::min(geolocationCalculator.getTopRightMercator().y, std::min(geolocationCalculator.getBottomLeftMercator().y, geolocationCalculator.getBottomRightMercator().y))); - double MaxX = std::max(geolocationCalculator.getTopLeftMercator().x, std::max(geolocationCalculator.getTopRightMercator().x, std::max(geolocationCalculator.getBottomLeftMercator().x, geolocationCalculator.getBottomRightMercator().x))); - double MaxY = std::max(geolocationCalculator.getTopLeftMercator().y, std::max(geolocationCalculator.getTopRightMercator().y, std::max(geolocationCalculator.getBottomLeftMercator().y, geolocationCalculator.getBottomRightMercator().y))); + PixelGeolocationCalculator::CartesianCoordinateF topLeft = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateTopLeft(), geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF topRight = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateTopRight(), geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomLeft = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateBottomLeft(), geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomRight = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateBottomRight(), geolocationCalculator.getSatelliteHeight(), scale); + + double MinX = std::min(topLeft.x, std::min(topRight.x, std::min(bottomLeft.x, bottomRight.x))); + double MinY = std::min(topLeft.y, std::min(topRight.y, std::min(bottomLeft.y, bottomRight.y))); + double MaxX = std::max(topLeft.x, std::max(topRight.x, std::max(bottomLeft.x, bottomRight.x))); + double MaxY = std::max(topLeft.y, std::max(topRight.y, std::max(bottomLeft.y, bottomRight.y))); int width = static_cast(std::abs(MaxX - MinX)); int height = static_cast(std::abs(MaxY - MinY)); @@ -117,9 +124,9 @@ cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeoloca } for (int x = 0; x < image.size().width - 10; x += 10) { - const PixelGeolocationCalculator::CartesianCoordinateF &p1 = geolocationCalculator.getMercatorAt(x, y); - const PixelGeolocationCalculator::CartesianCoordinateF &p2 = geolocationCalculator.getMercatorAt(x + 10, y); - const PixelGeolocationCalculator::CartesianCoordinateF &p3 = geolocationCalculator.getMercatorAt(x, y + 10); + const PixelGeolocationCalculator::CartesianCoordinateF p1 = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateAt(x, y), geolocationCalculator.getSatelliteHeight(), scale); + const PixelGeolocationCalculator::CartesianCoordinateF p2 = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateAt(x + 10, y), geolocationCalculator.getSatelliteHeight(), scale); + const PixelGeolocationCalculator::CartesianCoordinateF p3 = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator.getCoordinateAt(x, y + 10), geolocationCalculator.getSatelliteHeight(), scale); srcTri[0] = cv::Point2f( x, y ); srcTri[1] = cv::Point2f( x + 10, y ); @@ -135,26 +142,26 @@ cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeoloca Settings &settings = Settings::getInstance(); GIS::ShapeRenderer graticules(settings.getResourcesPath() + settings.getShapeGraticulesFile(), cv::Scalar(settings.getShapeGraticulesColor().B, settings.getShapeGraticulesColor().G, settings.getShapeGraticulesColor().R)); graticules.setThickness(settings.getShapeGraticulesThickness()); - graticules.drawShapeMercator(newImage, xStart, yStart); + graticules.drawShapeMercator(newImage, xStart, yStart, scale); GIS::ShapeRenderer coastLines(settings.getResourcesPath() + settings.getShapeCoastLinesFile(), cv::Scalar(settings.getShapeCoastLinesColor().B, settings.getShapeCoastLinesColor().G, settings.getShapeCoastLinesColor().R)); coastLines.setThickness(settings.getShapeCoastLinesThickness()); - coastLines.drawShapeMercator(newImage, xStart, yStart); + coastLines.drawShapeMercator(newImage, xStart, yStart, scale); GIS::ShapeRenderer countryBorders(settings.getResourcesPath() + settings.getShapeBoundaryLinesFile(), cv::Scalar(settings.getShapeBoundaryLinesColor().B, settings.getShapeBoundaryLinesColor().G, settings.getShapeBoundaryLinesColor().R)); countryBorders.setThickness(settings.getShapeBoundaryLinesThickness()); - countryBorders.drawShapeMercator(newImage, xStart, yStart); + countryBorders.drawShapeMercator(newImage, xStart, yStart, scale); GIS::ShapeRenderer cities(settings.getResourcesPath() + settings.getShapePopulatedPlacesFile(), cv::Scalar(settings.getShapePopulatedPlacesColor().B, settings.getShapePopulatedPlacesColor().G, settings.getShapePopulatedPlacesColor().R)); cities.addNumericFilter(settings.getShapePopulatedPlacesFilterColumnName(), settings.getShapePopulatedPlacesNumbericFilter()); cities.setTextFieldName(settings.getShapePopulatedPlacesTextColumnName()); - cities.setFontScale(settings.getShapePopulatedPlacesFontScale()); - cities.setThickness(settings.getShapePopulatedPlacesThickness()); - cities.setPointRadius(settings.getShapePopulatedPlacesPointradius()); - cities.drawShapeMercator(newImage, xStart, yStart); + cities.setFontHeight(settings.getShapePopulatedPlacesFontSize() * scale); + cities.setFontLineWidth(settings.getShapePopulatedPlacesFontWidth()); + cities.setPointRadius(settings.getShapePopulatedPlacesPointradius() * scale); + cities.drawShapeMercator(newImage, xStart, yStart, scale); if(settings.drawReceiver()) { - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection(settings.getReceiverLatitude(), settings.getReceiverLongitude(), mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection({settings.getReceiverLatitude(), settings.getReceiverLongitude(), 0}, mEarthRadius + mAltitude, scale); coordinate.x -= xStart; coordinate.y -= yStart; cv::drawMarker(newImage, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(0, 0, 0), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness() + 1); @@ -165,15 +172,22 @@ cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeoloca } // Concept for using ThinPlateSplineShapeTransform. Unfortunately it is extremly slow for big images -/*cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, ProgressCallback progressCallback) +/*cv::Mat SpreadImage::equidistantProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, float scale, ProgressCallback progressCallback) { cv::Point2f srcTri[3]; cv::Point2f dstTri[3]; - double MinX = std::min(geolocationCalculator.getTopLeftMercator().x, std::min(geolocationCalculator.getTopRightMercator().x, std::min(geolocationCalculator.getBottomLeftMercator().x, geolocationCalculator.getBottomRightMercator().x))); - double MinY = std::min(geolocationCalculator.getTopLeftMercator().y, std::min(geolocationCalculator.getTopRightMercator().y, std::min(geolocationCalculator.getBottomLeftMercator().y, geolocationCalculator.getBottomRightMercator().y))); - double MaxX = std::max(geolocationCalculator.getTopLeftMercator().x, std::max(geolocationCalculator.getTopRightMercator().x, std::max(geolocationCalculator.getBottomLeftMercator().x, geolocationCalculator.getBottomRightMercator().x))); - double MaxY = std::max(geolocationCalculator.getTopLeftMercator().y, std::max(geolocationCalculator.getTopRightMercator().y, std::max(geolocationCalculator.getBottomLeftMercator().y, geolocationCalculator.getBottomRightMercator().y))); + const CoordGeodetic ¢er = geolocationCalculator.getCenterCoordinate(); + + PixelGeolocationCalculator::CartesianCoordinateF topLeft = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateTopLeft(), center, geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF topRight = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateTopRight(), center, geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomLeft = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateBottomLeft(), center, geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomRight = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateBottomRight(), center, geolocationCalculator.getSatelliteHeight(), scale); + + double MinX = std::min(topLeft.x, std::min(topRight.x, std::min(bottomLeft.x, bottomRight.x))); + double MinY = std::min(topLeft.y, std::min(topRight.y, std::min(bottomLeft.y, bottomRight.y))); + double MaxX = std::max(topLeft.x, std::max(topRight.x, std::max(bottomLeft.x, bottomRight.x))); + double MaxY = std::max(topLeft.y, std::max(topRight.y, std::max(bottomLeft.y, bottomRight.y))); int width = static_cast(std::abs(MaxX - MinX)); int height = static_cast(std::abs(MaxY - MinY)); @@ -183,16 +197,17 @@ cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeoloca int imageWithGeorefHeight = geolocationCalculator.getGeorefMaxImageHeight() < image.size().height ? geolocationCalculator.getGeorefMaxImageHeight() : image.size().height; - cv::Mat newImage; - cv::Mat paddedImage = cv::Mat::zeros(height, width, image.type()); - image.copyTo(paddedImage.rowRange(0, image.size().height).colRange(0, image.size().width)); + cv::Mat newImage = cv::Mat::zeros(height, width, image.type()); + //cv::Mat paddedImage = cv::Mat::zeros(height, width, image.type()); + //image.copyTo(paddedImage.rowRange(0, image.size().height).colRange(0, image.size().width)); - auto tpsTransform = cv::createThinPlateSplineShapeTransformer(); + auto tpsTransform = cv::createAffineTransformer(true); std::vector sourcePoints, targetPoints; for (int y = 0; y < imageWithGeorefHeight; y += 50) { for (int x = 0; x < image.size().width; x += 50) { - const PixelGeolocationCalculator::CartesianCoordinateF p1 = geolocationCalculator.getMercatorAt(x, y); + const PixelGeolocationCalculator::CartesianCoordinateF p1 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateAt(x, y), center, geolocationCalculator.getSatelliteHeight(), scale); + sourcePoints.push_back(cv::Point2f(x, y)); targetPoints.push_back(cv::Point2f((int)(p1.x + (-xStart)), (int)(p1.y + (-yStart)))); } @@ -206,22 +221,220 @@ cv::Mat SpreadImage::mercatorProjection(const cv::Mat &image, const PixelGeoloca tpsTransform->estimateTransformation(targetPoints, sourcePoints, matches); std::vector transPoints; tpsTransform->applyTransformation(sourcePoints, transPoints); - tpsTransform->warpImage(paddedImage, newImage, cv::INTER_LINEAR); + tpsTransform->warpImage(image, newImage, cv::INTER_LINEAR); + + float centerLatitude = static_cast(geolocationCalculator.getCenterCoordinate().latitude * (180.0 / M_PI)); + float centerLongitude = static_cast(geolocationCalculator.getCenterCoordinate().longitude * (180.0 / M_PI)); + + Settings &settings = Settings::getInstance(); + + GIS::ShapeRenderer graticules(settings.getResourcesPath() + settings.getShapeGraticulesFile(), cv::Scalar(settings.getShapeGraticulesColor().B, settings.getShapeGraticulesColor().G, settings.getShapeGraticulesColor().R)); + graticules.setThickness(settings.getShapeGraticulesThickness()); + graticules.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); + + GIS::ShapeRenderer coastLines(settings.getResourcesPath() + settings.getShapeCoastLinesFile(), cv::Scalar(settings.getShapeCoastLinesColor().B, settings.getShapeCoastLinesColor().G, settings.getShapeCoastLinesColor().R)); + coastLines.setThickness(settings.getShapeCoastLinesThickness()); + coastLines.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); + + GIS::ShapeRenderer countryBorders(settings.getResourcesPath() + settings.getShapeBoundaryLinesFile(), cv::Scalar(settings.getShapeBoundaryLinesColor().B, settings.getShapeBoundaryLinesColor().G, settings.getShapeBoundaryLinesColor().R)); + countryBorders.setThickness(settings.getShapeBoundaryLinesThickness()); + countryBorders.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); - //Todo: overlays here + GIS::ShapeRenderer cities(settings.getResourcesPath() + settings.getShapePopulatedPlacesFile(), cv::Scalar(settings.getShapePopulatedPlacesColor().B, settings.getShapePopulatedPlacesColor().G, settings.getShapePopulatedPlacesColor().R)); + cities.setFontScale(settings.getShapePopulatedPlacesFontScale()); + cities.setThickness(settings.getShapePopulatedPlacesThickness()); + cities.setPointRadius(settings.getShapePopulatedPlacesPointradius()); + cities.addNumericFilter(settings.getShapePopulatedPlacesFilterColumnName(), settings.getShapePopulatedPlacesNumbericFilter()); + cities.setTextFieldName(settings.getShapePopulatedPlacesTextColumnName()); + cities.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); + + if(settings.drawReceiver()) { + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection({settings.getReceiverLatitude(), settings.getReceiverLongitude(), 0}, {centerLatitude, centerLongitude, 0}, mEarthRadius + mAltitude, scale); + coordinate.x -= xStart; + coordinate.y -= yStart; + cv::drawMarker(newImage, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(0, 0, 0), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness() + 1); + cv::drawMarker(newImage, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(settings.getReceiverColor().B, settings.getReceiverColor().G, settings.getReceiverColor().R), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness()); + } return newImage; }*/ -cv::Mat SpreadImage::equidistantProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, ProgressCallback progressCallback) +cv::Mat SpreadImage::mercatorProjection(const std::list &images, const std::list &geolocationCalculators, float scale, SpreadImage::ProgressCallback progressCallback) +{ + float MinX = std::numeric_limits::max(); + float MinY = std::numeric_limits::max(); + float MaxX = std::numeric_limits::min(); + float MaxY = std::numeric_limits::min(); + float corner; + + if (images.size() != geolocationCalculators.size()) { + return cv::Mat(); + } + + std::list::const_iterator geolocationCalculator; + for (geolocationCalculator = geolocationCalculators.begin(); geolocationCalculator != geolocationCalculators.end(); ++geolocationCalculator) { + + PixelGeolocationCalculator::CartesianCoordinateF topLeft = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateTopLeft(), geolocationCalculator->getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF topRight = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateTopRight(), geolocationCalculator->getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomLeft = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateBottomLeft(), geolocationCalculator->getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomRight = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateBottomRight(), geolocationCalculator->getSatelliteHeight(), scale); + + corner = std::min(topLeft.x, std::min(topRight.x, std::min(bottomLeft.x, bottomRight.x))); + MinX = corner < MinX ? corner : MinX; + + corner = std::min(topLeft.y, std::min(topRight.y, std::min(bottomLeft.y, bottomRight.y))); + MinY = corner < MinY ? corner : MinY; + + corner = std::max(topLeft.x, std::max(topRight.x, std::max(bottomLeft.x, bottomRight.x))); + MaxX = corner > MaxX ? corner : MaxX; + + corner = std::max(topLeft.y, std::max(topRight.y, std::max(bottomLeft.y, bottomRight.y))); + MaxY = corner > MaxY ? corner : MaxY; + } + + int width = static_cast(std::abs(MaxX - MinX)); + int height = static_cast(std::abs(MaxY - MinY)); + float xStart = std::min(MinX, MaxX); + float yStart = std::min(MinY, MaxY); + + std::list::const_iterator image; + image = images.begin(); + geolocationCalculator = geolocationCalculators.begin(); + + std::vector newImages; + PixelGeolocationCalculator::CartesianCoordinateF p1, p2, p3; + + for (int i = 0; image != images.end(); ++image, ++geolocationCalculator, i++) { + cv::Mat newImage = cv::Mat::zeros(height, width, image->type()); + int imageWithGeorefHeight = geolocationCalculator->getGeorefMaxImageHeight() < image->size().height ? geolocationCalculator->getGeorefMaxImageHeight() : image->size().height; + + for (int y = 0; y < imageWithGeorefHeight - 10; y += 10) + { + if(progressCallback) { + progressCallback((static_cast(y) + (i * imageWithGeorefHeight)) / (images.size() * imageWithGeorefHeight) * 100.0f); + } + for (int x = 0; x < image->size().width - 10; x += 10) + { + const PixelGeolocationCalculator::CartesianCoordinateF p1 = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateAt(x, y), geolocationCalculator->getSatelliteHeight(), scale); + const PixelGeolocationCalculator::CartesianCoordinateF p2 = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateAt(x + 10, y), geolocationCalculator->getSatelliteHeight(), scale); + const PixelGeolocationCalculator::CartesianCoordinateF p3 = PixelGeolocationCalculator::coordinateToMercatorProjection(geolocationCalculator->getCoordinateAt(x, y + 10), geolocationCalculator->getSatelliteHeight(), scale); + + cv::Point2f srcTri[3]; + cv::Point2f dstTri[3]; + + srcTri[0] = cv::Point2f( x, y ); + srcTri[1] = cv::Point2f( x + 10, y ); + srcTri[2] = cv::Point2f( x, y + 10 ); + + dstTri[0] = cv::Point2f(p1.x, p1.y); + dstTri[1] = cv::Point2f(p2.x, p2.y); + dstTri[2] = cv::Point2f(p3.x, p3.y); + affineTransform(*image, newImage, srcTri, dstTri, -xStart, -yStart); + } + } + + newImages.push_back(newImage); + } + + std::vector::iterator it = newImages.begin(); + + cv::Mat composite = it->clone(); + ++it; + + composite.convertTo(composite, CV_32FC3); + + for(; it != newImages.end(); ++it) { + it->convertTo(*it, CV_32FC3); + + cv::Mat grayScale1; + cv::Mat alpha1; + cv::medianBlur(composite, grayScale1, 5); + cv::cvtColor(grayScale1, grayScale1, cv::COLOR_BGR2GRAY); + + cv::threshold(grayScale1, alpha1, 0, 255, cv::THRESH_BINARY); + grayScale1.release(); + + cv::Mat grayScale2; + cv::Mat alpha2; + cv::medianBlur(*it, grayScale2, 5); + cv::cvtColor(grayScale2, grayScale2, cv::COLOR_BGR2GRAY); + + cv::threshold(grayScale2, alpha2, 0, 255, cv::THRESH_BINARY); + grayScale2.release(); + + cv::Mat mask; + cv::bitwise_and(alpha1, alpha2, mask); + alpha1.release(); + alpha2.release(); + + std::vector channels; + channels.push_back(mask); + channels.push_back(mask); + channels.push_back(mask); + cv::merge(channels, mask); + + mask.convertTo(mask, CV_32FC3, 1/255.0); + + int start0 = findImageStart(composite); + int start1 = findImageStart(*it); + bool leftToRight = start0 < start1; + + cv::Mat blendmask = blendMask(mask, leftToRight); + cv::multiply(cv::Scalar::all(1.0)-blendmask, composite, composite); + blendmask = blendMask(mask, !leftToRight); + cv::multiply(cv::Scalar::all(1.0)-blendmask, *it, *it); + + cv::add(composite, *it, composite); + } + + Settings &settings = Settings::getInstance(); + GIS::ShapeRenderer graticules(settings.getResourcesPath() + settings.getShapeGraticulesFile(), cv::Scalar(settings.getShapeGraticulesColor().B, settings.getShapeGraticulesColor().G, settings.getShapeGraticulesColor().R)); + graticules.setThickness(settings.getShapeGraticulesThickness()); + graticules.drawShapeMercator(composite, xStart, yStart, scale); + + GIS::ShapeRenderer coastLines(settings.getResourcesPath() + settings.getShapeCoastLinesFile(), cv::Scalar(settings.getShapeCoastLinesColor().B, settings.getShapeCoastLinesColor().G, settings.getShapeCoastLinesColor().R)); + coastLines.setThickness(settings.getShapeCoastLinesThickness()); + coastLines.drawShapeMercator(composite, xStart, yStart, scale); + + GIS::ShapeRenderer countryBorders(settings.getResourcesPath() + settings.getShapeBoundaryLinesFile(), cv::Scalar(settings.getShapeBoundaryLinesColor().B, settings.getShapeBoundaryLinesColor().G, settings.getShapeBoundaryLinesColor().R)); + countryBorders.setThickness(settings.getShapeBoundaryLinesThickness()); + countryBorders.drawShapeMercator(composite, xStart, yStart, scale); + + GIS::ShapeRenderer cities(settings.getResourcesPath() + settings.getShapePopulatedPlacesFile(), cv::Scalar(settings.getShapePopulatedPlacesColor().B, settings.getShapePopulatedPlacesColor().G, settings.getShapePopulatedPlacesColor().R)); + cities.addNumericFilter(settings.getShapePopulatedPlacesFilterColumnName(), settings.getShapePopulatedPlacesNumbericFilter()); + cities.setTextFieldName(settings.getShapePopulatedPlacesTextColumnName()); + cities.setFontHeight(settings.getShapePopulatedPlacesFontSize() * scale); + cities.setFontLineWidth(settings.getShapePopulatedPlacesFontWidth()); + cities.setPointRadius(settings.getShapePopulatedPlacesPointradius() * scale); + cities.drawShapeMercator(composite, xStart, yStart, scale); + + if(settings.drawReceiver()) { + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToMercatorProjection({settings.getReceiverLatitude(), settings.getReceiverLongitude(), 0}, mEarthRadius + mAltitude, scale); + coordinate.x -= xStart; + coordinate.y -= yStart; + cv::drawMarker(composite, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(0, 0, 0), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness() + 1); + cv::drawMarker(composite, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(settings.getReceiverColor().B, settings.getReceiverColor().G, settings.getReceiverColor().R), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness()); + } + + return composite; +} + +cv::Mat SpreadImage::equidistantProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, float scale, ProgressCallback progressCallback) { cv::Point2f srcTri[3]; cv::Point2f dstTri[3]; - double MinX = std::min(geolocationCalculator.getTopLeftEquidistant().x, std::min(geolocationCalculator.getTopRightEquidistant().x, std::min(geolocationCalculator.getBottomLeftEquidistant().x, geolocationCalculator.getBottomRightEquidistant().x))); - double MinY = std::min(geolocationCalculator.getTopLeftEquidistant().y, std::min(geolocationCalculator.getTopRightEquidistant().y, std::min(geolocationCalculator.getBottomLeftEquidistant().y, geolocationCalculator.getBottomRightEquidistant().y))); - double MaxX = std::max(geolocationCalculator.getTopLeftEquidistant().x, std::max(geolocationCalculator.getTopRightEquidistant().x, std::max(geolocationCalculator.getBottomLeftEquidistant().x, geolocationCalculator.getBottomRightEquidistant().x))); - double MaxY = std::max(geolocationCalculator.getTopLeftEquidistant().y, std::max(geolocationCalculator.getTopRightEquidistant().y, std::max(geolocationCalculator.getBottomLeftEquidistant().y, geolocationCalculator.getBottomRightEquidistant().y))); + const CoordGeodetic ¢er = geolocationCalculator.getCenterCoordinate(); + + PixelGeolocationCalculator::CartesianCoordinateF topLeft = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateTopLeft(), center, geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF topRight = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateTopRight(), center, geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomLeft = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateBottomLeft(), center, geolocationCalculator.getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomRight = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateBottomRight(), center, geolocationCalculator.getSatelliteHeight(), scale); + + double MinX = std::min(topLeft.x, std::min(topRight.x, std::min(bottomLeft.x, bottomRight.x))); + double MinY = std::min(topLeft.y, std::min(topRight.y, std::min(bottomLeft.y, bottomRight.y))); + double MaxX = std::max(topLeft.x, std::max(topRight.x, std::max(bottomLeft.x, bottomRight.x))); + double MaxY = std::max(topLeft.y, std::max(topRight.y, std::max(bottomLeft.y, bottomRight.y))); int width = static_cast(std::abs(MaxX - MinX)); int height = static_cast(std::abs(MaxY - MinY)); @@ -240,9 +453,9 @@ cv::Mat SpreadImage::equidistantProjection(const cv::Mat &image, const PixelGeol } for (int x = 0; x < image.size().width - 10; x += 10) { - const PixelGeolocationCalculator::CartesianCoordinateF &p1 = geolocationCalculator.getEquidistantAt(x, y); - const PixelGeolocationCalculator::CartesianCoordinateF &p2 = geolocationCalculator.getEquidistantAt(x + 10, y); - const PixelGeolocationCalculator::CartesianCoordinateF &p3 = geolocationCalculator.getEquidistantAt(x, y + 10); + const PixelGeolocationCalculator::CartesianCoordinateF p1 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateAt(x, y), center, geolocationCalculator.getSatelliteHeight(), scale); + const PixelGeolocationCalculator::CartesianCoordinateF p2 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateAt(x + 10, y), center, geolocationCalculator.getSatelliteHeight(), scale); + const PixelGeolocationCalculator::CartesianCoordinateF p3 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator.getCoordinateAt(x, y + 10), center, geolocationCalculator.getSatelliteHeight(), scale); srcTri[0] = cv::Point2f( x, y ); srcTri[1] = cv::Point2f( x + 10, y ); @@ -262,26 +475,26 @@ cv::Mat SpreadImage::equidistantProjection(const cv::Mat &image, const PixelGeol GIS::ShapeRenderer graticules(settings.getResourcesPath() + settings.getShapeGraticulesFile(), cv::Scalar(settings.getShapeGraticulesColor().B, settings.getShapeGraticulesColor().G, settings.getShapeGraticulesColor().R)); graticules.setThickness(settings.getShapeGraticulesThickness()); - graticules.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude); + graticules.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); GIS::ShapeRenderer coastLines(settings.getResourcesPath() + settings.getShapeCoastLinesFile(), cv::Scalar(settings.getShapeCoastLinesColor().B, settings.getShapeCoastLinesColor().G, settings.getShapeCoastLinesColor().R)); coastLines.setThickness(settings.getShapeCoastLinesThickness()); - coastLines.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude); + coastLines.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); GIS::ShapeRenderer countryBorders(settings.getResourcesPath() + settings.getShapeBoundaryLinesFile(), cv::Scalar(settings.getShapeBoundaryLinesColor().B, settings.getShapeBoundaryLinesColor().G, settings.getShapeBoundaryLinesColor().R)); countryBorders.setThickness(settings.getShapeBoundaryLinesThickness()); - countryBorders.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude); + countryBorders.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); GIS::ShapeRenderer cities(settings.getResourcesPath() + settings.getShapePopulatedPlacesFile(), cv::Scalar(settings.getShapePopulatedPlacesColor().B, settings.getShapePopulatedPlacesColor().G, settings.getShapePopulatedPlacesColor().R)); - cities.setFontScale(settings.getShapePopulatedPlacesFontScale()); - cities.setThickness(settings.getShapePopulatedPlacesThickness()); - cities.setPointRadius(settings.getShapePopulatedPlacesPointradius()); + cities.setFontHeight(settings.getShapePopulatedPlacesFontSize() * scale); + cities.setFontLineWidth(settings.getShapePopulatedPlacesFontWidth()); + cities.setPointRadius(settings.getShapePopulatedPlacesPointradius() * scale); cities.addNumericFilter(settings.getShapePopulatedPlacesFilterColumnName(), settings.getShapePopulatedPlacesNumbericFilter()); cities.setTextFieldName(settings.getShapePopulatedPlacesTextColumnName()); - cities.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude); + cities.drawShapeEquidistant(newImage, xStart, yStart, centerLatitude, centerLongitude, scale); if(settings.drawReceiver()) { - PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(settings.getReceiverLatitude(), settings.getReceiverLongitude(), centerLatitude, centerLongitude, mEarthRadius + mAltitude); + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection({settings.getReceiverLatitude(), settings.getReceiverLongitude(), 0}, {centerLatitude, centerLongitude, 0}, mEarthRadius + mAltitude, scale); coordinate.x -= xStart; coordinate.y -= yStart; cv::drawMarker(newImage, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(0, 0, 0), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness() + 1); @@ -291,6 +504,178 @@ cv::Mat SpreadImage::equidistantProjection(const cv::Mat &image, const PixelGeol return newImage; } +cv::Mat SpreadImage::equidistantProjection(const std::list &images, const std::list &geolocationCalculators, float scale, SpreadImage::ProgressCallback progressCallback) +{ + float MinX = std::numeric_limits::max(); + float MinY = std::numeric_limits::max(); + float MaxX = std::numeric_limits::min(); + float MaxY = std::numeric_limits::min(); + float corner; + + if (images.size() != geolocationCalculators.size()) { + return cv::Mat(); + } + + std::list centerCoordinates; + for(const auto &c : geolocationCalculators) { + centerCoordinates.push_back(c.getCenterCoordinate()); + } + CoordGeodetic sumOfCenterCoordinates = std::accumulate(centerCoordinates.begin(), centerCoordinates.end(), CoordGeodetic(0, 0, 0)); + + float centerLatitude = static_cast(sumOfCenterCoordinates.latitude / centerCoordinates.size() * (180.0 / M_PI)); + float centerLongitude = static_cast(sumOfCenterCoordinates.longitude / centerCoordinates.size() * (180.0 / M_PI)); + CoordGeodetic center = CoordGeodetic(centerLatitude, centerLongitude, 0); + + std::list::const_iterator geolocationCalculator; + for (geolocationCalculator = geolocationCalculators.begin(); geolocationCalculator != geolocationCalculators.end(); ++geolocationCalculator) { + + PixelGeolocationCalculator::CartesianCoordinateF topLeft = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateTopLeft(), center, geolocationCalculator->getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF topRight = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateTopRight(), center, geolocationCalculator->getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomLeft = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateBottomLeft(), center, geolocationCalculator->getSatelliteHeight(), scale); + PixelGeolocationCalculator::CartesianCoordinateF bottomRight = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateBottomRight(), center, geolocationCalculator->getSatelliteHeight(),scale); + + corner = std::min(topLeft.x, std::min(topRight.x, std::min(bottomLeft.x, bottomRight.x))); + MinX = corner < MinX ? corner : MinX; + + corner = std::min(topLeft.y, std::min(topRight.y, std::min(bottomLeft.y, bottomRight.y))); + MinY = corner < MinY ? corner : MinY; + + corner = std::max(topLeft.x, std::max(topRight.x, std::max(bottomLeft.x, bottomRight.x))); + MaxX = corner > MaxX ? corner : MaxX; + + corner = std::max(topLeft.y, std::max(topRight.y, std::max(bottomLeft.y, bottomRight.y))); + MaxY = corner > MaxY ? corner : MaxY; + } + + int width = static_cast(std::abs(MaxX - MinX)); + int height = static_cast(std::abs(MaxY - MinY)); + float xStart = std::min(MinX, MaxX); + float yStart = std::min(MinY, MaxY); + + std::list::const_iterator image; + image = images.begin(); + geolocationCalculator = geolocationCalculators.begin(); + + std::vector newImages; + PixelGeolocationCalculator::CartesianCoordinateF p1, p2, p3; + + for (int i = 0; image != images.end(); ++image, ++geolocationCalculator, i++) { + cv::Mat newImage = cv::Mat::zeros(height, width, image->type()); + int imageWithGeorefHeight = geolocationCalculator->getGeorefMaxImageHeight() < image->size().height ? geolocationCalculator->getGeorefMaxImageHeight() : image->size().height; + + for (int y = 0; y < imageWithGeorefHeight - 10; y += 10) + { + if(progressCallback) { + progressCallback((static_cast(y) + (i * imageWithGeorefHeight)) / (images.size() * imageWithGeorefHeight) * 100.0f); + } + for (int x = 0; x < image->size().width - 10; x += 10) + { + p1 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateAt(x, y), center, geolocationCalculator->getSatelliteHeight(), scale); + p2 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateAt(x + 10, y), center, geolocationCalculator->getSatelliteHeight(), scale); + p3 = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection(geolocationCalculator->getCoordinateAt(x, y + 10), center, geolocationCalculator->getSatelliteHeight(), scale); + + + cv::Point2f srcTri[3]; + cv::Point2f dstTri[3]; + + srcTri[0] = cv::Point2f( x, y ); + srcTri[1] = cv::Point2f( x + 10, y ); + srcTri[2] = cv::Point2f( x, y + 10 ); + + dstTri[0] = cv::Point2f(p1.x, p1.y); + dstTri[1] = cv::Point2f(p2.x, p2.y); + dstTri[2] = cv::Point2f(p3.x, p3.y); + affineTransform(*image, newImage, srcTri, dstTri, -xStart, -yStart); + } + } + + newImages.push_back(newImage); + } + + std::vector::iterator it = newImages.begin(); + + cv::Mat composite = it->clone(); + ++it; + + composite.convertTo(composite, CV_32FC3); + + for(; it != newImages.end(); ++it) { + it->convertTo(*it, CV_32FC3); + + cv::Mat grayScale1; + cv::Mat alpha1; + cv::medianBlur(composite, grayScale1, 5); + cv::cvtColor(grayScale1, grayScale1, cv::COLOR_BGR2GRAY); + + cv::threshold(grayScale1, alpha1, 0, 255, cv::THRESH_BINARY); + grayScale1.release(); + + cv::Mat grayScale2; + cv::Mat alpha2; + cv::medianBlur(*it, grayScale2, 5); + cv::cvtColor(grayScale2, grayScale2, cv::COLOR_BGR2GRAY); + + cv::threshold(grayScale2, alpha2, 0, 255, cv::THRESH_BINARY); + grayScale2.release(); + + cv::Mat mask; + cv::bitwise_and(alpha1, alpha2, mask); + alpha1.release(); + alpha2.release(); + + std::vector channels; + channels.push_back(mask); + channels.push_back(mask); + channels.push_back(mask); + cv::merge(channels, mask); + + mask.convertTo(mask, CV_32FC3, 1/255.0); + + int start0 = findImageStart(composite); + int start1 = findImageStart(*it); + bool leftToRight = start0 < start1; + + cv::Mat blendmask = blendMask(mask, leftToRight); + cv::multiply(cv::Scalar::all(1.0)-blendmask, composite, composite); + blendmask = blendMask(mask, !leftToRight); + cv::multiply(cv::Scalar::all(1.0)-blendmask, *it, *it); + + cv::add(composite, *it, composite); + } + + Settings &settings = Settings::getInstance(); + + GIS::ShapeRenderer graticules(settings.getResourcesPath() + settings.getShapeGraticulesFile(), cv::Scalar(settings.getShapeGraticulesColor().B, settings.getShapeGraticulesColor().G, settings.getShapeGraticulesColor().R)); + graticules.setThickness(settings.getShapeGraticulesThickness()); + graticules.drawShapeEquidistant(composite, xStart, yStart, centerLatitude, centerLongitude, scale); + + GIS::ShapeRenderer coastLines(settings.getResourcesPath() + settings.getShapeCoastLinesFile(), cv::Scalar(settings.getShapeCoastLinesColor().B, settings.getShapeCoastLinesColor().G, settings.getShapeCoastLinesColor().R)); + coastLines.setThickness(settings.getShapeCoastLinesThickness()); + coastLines.drawShapeEquidistant(composite, xStart, yStart, centerLatitude, centerLongitude, scale); + + GIS::ShapeRenderer countryBorders(settings.getResourcesPath() + settings.getShapeBoundaryLinesFile(), cv::Scalar(settings.getShapeBoundaryLinesColor().B, settings.getShapeBoundaryLinesColor().G, settings.getShapeBoundaryLinesColor().R)); + countryBorders.setThickness(settings.getShapeBoundaryLinesThickness()); + countryBorders.drawShapeEquidistant(composite, xStart, yStart, centerLatitude, centerLongitude, scale); + + GIS::ShapeRenderer cities(settings.getResourcesPath() + settings.getShapePopulatedPlacesFile(), cv::Scalar(settings.getShapePopulatedPlacesColor().B, settings.getShapePopulatedPlacesColor().G, settings.getShapePopulatedPlacesColor().R)); + cities.addNumericFilter(settings.getShapePopulatedPlacesFilterColumnName(), settings.getShapePopulatedPlacesNumbericFilter()); + cities.setTextFieldName(settings.getShapePopulatedPlacesTextColumnName()); + cities.setFontHeight(settings.getShapePopulatedPlacesFontSize() * scale); + cities.setFontLineWidth(settings.getShapePopulatedPlacesFontWidth()); + cities.setPointRadius(settings.getShapePopulatedPlacesPointradius() * scale); + cities.drawShapeEquidistant(composite, xStart, yStart, centerLatitude, centerLongitude, scale); + + if(settings.drawReceiver()) { + PixelGeolocationCalculator::CartesianCoordinateF coordinate = PixelGeolocationCalculator::coordinateToAzimuthalEquidistantProjection({settings.getReceiverLatitude(), settings.getReceiverLongitude(), 0}, center, mEarthRadius + mAltitude, scale); + coordinate.x -= xStart; + coordinate.y -= yStart; + cv::drawMarker(composite, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(0, 0, 0), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness() + 1); + cv::drawMarker(composite, cv::Point2d(coordinate.x, coordinate.y), cv::Scalar(settings.getReceiverColor().B, settings.getReceiverColor().G, settings.getReceiverColor().R), stringToMarkerType(settings.getReceiverMarkType()), settings.getReceiverSize(), settings.getReceiverThickness()); + } + + return composite; +} + void SpreadImage::affineTransform(const cv::Mat& src, cv::Mat& dst, const cv::Point2f source[], const cv::Point2f destination[], int originX, int originY) { // Create an empty 3x1 matrix for storing original frame coordinates @@ -410,6 +795,65 @@ void SpreadImage::affineTransform(const cv::Mat& src, cv::Mat& dst, const cv::Po } } +cv::Mat SpreadImage::blendMask(const cv::Mat &mask, bool leftToRight) +{ + int xStart = 0; + int xEnd = 0; + int blendWidth = 0; + float alpha; + + cv::Mat blendedMask = cv::Mat::zeros(mask.size().height, mask.size().width, mask.type()); + + for(int y = 0; y < mask.size().height; ++y) { + bool foundStart = false; + bool foundEnd = false; + for(int x = 0; x < mask.size().width; x++) { + if(!foundStart && mask.at(y, x) != cv::Vec3f(0, 0, 0)) { + foundStart = true; + xStart = x; + + for (int temp = x; temp < mask.size().width; temp++) { + if(mask.at(y, temp) != cv::Vec3f(1, 1, 1)) { + xEnd = temp; + foundEnd = true; + blendWidth = xEnd - xStart; + break; + } + } + } + + if(foundStart && foundEnd && (x >= xStart && x <= xEnd)) { + alpha = static_cast(x - xStart) / blendWidth; + alpha = leftToRight ? alpha : 1.0f - alpha; + + blendedMask.at(y, x) = mask.at(y, x) * alpha; + } else if(foundStart && foundEnd && (x > xEnd)) { + foundStart = false; + foundEnd = false; + } + } + } + + + return blendedMask; +} + +int SpreadImage::findImageStart(const cv::Mat &img) +{ + int i = img.size().width; + for(int y = 0; y < img.size().height; ++y) { + for(int x = 0; x < img.size().width; x++) { + if(img.at(y, x) != cv::Vec3f(0, 0, 0)) { + if(x < i) { + i = x; + } + } + } + } + + return i; +} + cv::MarkerTypes SpreadImage::stringToMarkerType(const std::string &markerType) { auto itr = MarkerLookup.find(markerType); diff --git a/imageproc/spreadimage.h b/imageproc/spreadimage.h index ce6d42e..8c2af14 100644 --- a/imageproc/spreadimage.h +++ b/imageproc/spreadimage.h @@ -16,13 +16,17 @@ class SpreadImage explicit SpreadImage(int earthRadius = 6378, int altitude = 825); cv::Mat stretch(const cv::Mat &image); - cv::Mat mercatorProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, ProgressCallback progressCallback = nullptr); - cv::Mat equidistantProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, ProgressCallback progressCallback = nullptr); + cv::Mat mercatorProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, float scale, ProgressCallback progressCallback = nullptr); + cv::Mat mercatorProjection(const std::list &images, const std::list &geolocationCalculators, float scale, SpreadImage::ProgressCallback progressCallback = nullptr); + cv::Mat equidistantProjection(const cv::Mat &image, const PixelGeolocationCalculator &geolocationCalculator, float scale, ProgressCallback progressCallback = nullptr); + cv::Mat equidistantProjection(const std::list &images, const std::list &geolocationCalculators, float scale, SpreadImage::ProgressCallback progressCallback = nullptr); cv::MarkerTypes stringToMarkerType(const std::string &markerType); private: void affineTransform(const cv::Mat& src, cv::Mat& dst, const cv::Point2f source[], const cv::Point2f destination[], int originX, int originY); + cv::Mat blendMask(const cv::Mat &mask, bool leftToRight); + int findImageStart(const cv::Mat &img); private: int mEarthRadius; diff --git a/imageproc/threatimage.cpp b/imageproc/threatimage.cpp index 88bd42d..04a5f47 100644 --- a/imageproc/threatimage.cpp +++ b/imageproc/threatimage.cpp @@ -79,7 +79,50 @@ cv::Mat ThreatImage::irToRain(const cv::Mat &irImage, const cv::Mat &ref) } } - return rainImage; + cv::Mat result = cv::Mat::zeros(irImage.size(), irImage.type()); + cv::Mat grayScale; + cv::Mat alpha; + + cv::cvtColor(irImage, grayScale, cv::COLOR_BGR2GRAY); + std::vector > contours; + cv::findContours(grayScale, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); + for (std::size_t i = 0; i < contours.size(); i++) { + cv::drawContours(grayScale, contours, i, cv::Scalar(255, 255, 255), -1); + } + + //create alpha mask + cv::threshold(grayScale, alpha, 0, 255, cv::THRESH_BINARY); + + //apply mask + cv::bitwise_and(rainImage, rainImage, result, alpha); + + + return result; +} + +cv::Mat ThreatImage::invertIR(const cv::Mat &image) +{ + cv::Mat result = cv::Mat::zeros(image.size(), image.type()); + cv::Mat grayScale; + cv::Mat alpha; + + cv::cvtColor(image, grayScale, cv::COLOR_BGR2GRAY); + std::vector > contours; + cv::findContours(grayScale, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE); + for (std::size_t i = 0; i < contours.size(); i++) { + cv::drawContours(grayScale, contours, i, cv::Scalar(255, 255, 255), -1); + } + + //create mask + cv::threshold(grayScale, alpha, 0, 255, cv::THRESH_BINARY); + + //invert colors + cv::bitwise_not(image, image); + + //apply mask + cv::bitwise_and(image, image, result, alpha); + + return result; } cv::Mat ThreatImage::addRainOverlay(const cv::Mat &image, const cv::Mat &rain) @@ -116,11 +159,27 @@ cv::Mat ThreatImage::gamma(const cv::Mat &image, double gamma) return newImage; } +cv::Mat ThreatImage::sharpen(const cv::Mat &image) +{ + cv::Mat result(image.size().height, image.size().width, image.type()); + cv::GaussianBlur(image, result, cv::Size(0, 0), 11); + cv::addWeighted(image, 1.5, result, -0.5, 0, result); + return result; +} + +cv::Mat ThreatImage::contrast(const cv::Mat &image, double contrast, double brightness) +{ + cv::Mat result; + image.convertTo(result, image.type(), contrast, brightness); + return result; +} + void ThreatImage::drawWatermark(cv::Mat image, const std::string &date) { int x = 0; int y = 0; Settings &settings = Settings::getInstance(); + double fontScale = cv::getFontScaleFromHeight(cv::FONT_ITALIC, settings.getWaterMarkSize() * settings.getProjectionScale(), settings.getWaterMarkLineWidth()); std::string watermarkText = settings.getWaterMarkText(); @@ -140,7 +199,7 @@ void ThreatImage::drawWatermark(cv::Mat image, const std::string &date) std::istringstream istream(watermarkText); while (getline(istream, line, '\n')) { int baseLine; - cv::Size textSize = cv::getTextSize(line, cv::FONT_ITALIC, settings.getWaterMarkSize(), 10, &baseLine); + cv::Size textSize = cv::getTextSize(line, cv::FONT_ITALIC, fontScale, settings.getWaterMarkLineWidth(), &baseLine); int textHeight = baseLine + textSize.height; int margin = textSize.height; @@ -171,8 +230,8 @@ void ThreatImage::drawWatermark(cv::Mat image, const std::string &date) break; } - cv::putText(image, line, cv::Point2d(x, y), cv::FONT_HERSHEY_COMPLEX, settings.getWaterMarkSize(), cv::Scalar(0,0,0), 10+1, cv::LINE_AA); - cv::putText(image, line, cv::Point2d(x, y), cv::FONT_HERSHEY_COMPLEX, settings.getWaterMarkSize(), cv::Scalar(settings.getWaterMarkColor().B, settings.getWaterMarkColor().G, settings.getWaterMarkColor().R), 10, cv::LINE_AA); + cv::putText(image, line, cv::Point2d(x, y), cv::FONT_HERSHEY_COMPLEX, fontScale, cv::Scalar(0,0,0), settings.getWaterMarkLineWidth()+1, cv::LINE_AA); + cv::putText(image, line, cv::Point2d(x, y), cv::FONT_HERSHEY_COMPLEX, fontScale, cv::Scalar(settings.getWaterMarkColor().B, settings.getWaterMarkColor().G, settings.getWaterMarkColor().R), settings.getWaterMarkLineWidth(), cv::LINE_AA); n++; } diff --git a/imageproc/threatimage.h b/imageproc/threatimage.h index 2fe109a..aea37cc 100644 --- a/imageproc/threatimage.h +++ b/imageproc/threatimage.h @@ -21,8 +21,13 @@ class ThreatImage static void fillBlackLines(cv::Mat &image, int minimumHeight, int maximumHeight); static cv::Mat irToTemperature(const cv::Mat &irImage, const cv::Mat &ref); static cv::Mat irToRain(const cv::Mat &irImage, const cv::Mat &ref); + static cv::Mat invertIR(const cv::Mat &image); static cv::Mat addRainOverlay(const cv::Mat &image, const cv::Mat &rain); static cv::Mat gamma(const cv::Mat &image, double gamma); + static cv::Mat sharpen(const cv::Mat &image); + + //Constrast 0.0-inf, brightnes -225 to 255 + static cv::Mat contrast(const cv::Mat &image, double contrast, double brightness); static void drawWatermark(cv::Mat image, const std::string &date); static bool isNightPass(const cv::Mat &image, float treshold); diff --git a/main.cpp b/main.cpp index 306069c..dab696d 100644 --- a/main.cpp +++ b/main.cpp @@ -7,19 +7,25 @@ #include "deinterleaver.h" #include "pixelgeolocationcalculator.h" +#include +#include #include #include +#include #include +#include +#include #include -#include "spreadimage.h" +#include "spreadimage.h" #include "GIS/shapereader.h" #include "GIS/shaperenderer.h" #include "tlereader.h" #include "settings.h" - #include "threadpool.h" +namespace fs = std::experimental::filesystem; + struct ImageForSpread { ImageForSpread(cv::Mat img, std::string fileNamebase) : image(img) @@ -30,6 +36,7 @@ struct ImageForSpread { std::string fileNameBase; }; +void searchForImages(std::list &imagesOut, std::list &geolocationCalculatorsOut, const std::string &channelName); void saveImage(const std::string fileName, const cv::Mat &image); void writeSymbolToFile(std::ostream &stream, const Wavreader::complex &sample); int mean(int cur, int prev); @@ -89,6 +96,7 @@ int main(int argc, char *argv[]) mSettings.parseIni(mSettings.getResourcesPath() + "settings.ini"); mThreadPool.start(); + TleReader reader(mSettings.getTlePath()); TleReader::TLE tle; reader.processFile(); @@ -237,6 +245,9 @@ int main(int argc, char *argv[]) TimeSpan passStartTime = mPacketParser.getFirstTimeStamp(); TimeSpan passLength = mPacketParser.getLastTimeStamp() - passStartTime; + passStartTime = passStartTime.Add(TimeSpan(0, 0, mSettings.getTimeOffsetM2Sec())); + passLength = passLength.Add(TimeSpan(0, 0, mSettings.getTimeOffsetM2Sec())); + passDate = passDate.AddHours(3); //Convert UTC 0 to Moscow time zone (UTC + 3) //Satellite's date time @@ -254,13 +265,16 @@ int main(int argc, char *argv[]) if(mPacketParser.isChannel64Available() && mPacketParser.isChannel65Available() && mPacketParser.isChannel68Available()) { cv::Mat threatedImage1 = mPacketParser.getRGBImage(PacketParser::APID_65, PacketParser::APID_65, PacketParser::APID_64, mSettings.fillBackLines()); - cv::Mat irImage = mPacketParser.getChannelImage(PacketParser::APID_68, mSettings.fillBackLines()); - cv::Mat threatedImage2 = mPacketParser.getRGBImage(PacketParser::APID_64, PacketParser::APID_65, PacketParser::APID_68, mSettings.fillBackLines()); + cv::Mat irImage = mPacketParser.getChannelImage(PacketParser::APID_68, mSettings.fillBackLines()); + cv::Mat threatedImage2 = mPacketParser.getRGBImage(PacketParser::APID_64, PacketParser::APID_65, PacketParser::APID_68, mSettings.fillBackLines()); cv::Mat rainRef = cv::imread(mSettings.getResourcesPath() + "rain.bmp"); cv::Mat rainOverlay = ThreatImage::irToRain(irImage, rainRef); if(!ThreatImage::isNightPass(threatedImage1, mSettings.getNightPassTreshold())) { + threatedImage1 = ThreatImage::sharpen(threatedImage1); + threatedImage2 = ThreatImage::sharpen(threatedImage2); + imagesToSpread.push_back(ImageForSpread(threatedImage1, "221_")); imagesToSpread.push_back(ImageForSpread(threatedImage2, "125_")); @@ -269,26 +283,28 @@ int main(int argc, char *argv[]) imagesToSpread.push_back(ImageForSpread(ThreatImage::addRainOverlay(threatedImage2, rainOverlay), "rain_125_")); } - saveImage(mSettings.getOutputPath() + fileNameDate + "_221.bmp", threatedImage1); - saveImage(mSettings.getOutputPath() + fileNameDate + "_125.bmp", threatedImage2); + saveImage(mSettings.getOutputPath() + fileNameDate + "_221.bmp", threatedImage1); + saveImage(mSettings.getOutputPath() + fileNameDate + "_125.bmp", threatedImage2); } else { std::cout << "Night pass, RGB image skipped, threshold set to: " << mSettings.getNightPassTreshold() << std::endl; } cv::Mat ch64 = mPacketParser.getChannelImage(PacketParser::APID_64, mSettings.fillBackLines()); - cv::Mat ch65 = mPacketParser.getChannelImage(PacketParser::APID_65, mSettings.fillBackLines()); - cv::Mat ch68 = mPacketParser.getChannelImage(PacketParser::APID_68, mSettings.fillBackLines()); + cv::Mat ch65 = mPacketParser.getChannelImage(PacketParser::APID_65, mSettings.fillBackLines()); + cv::Mat ch68 = mPacketParser.getChannelImage(PacketParser::APID_68, mSettings.fillBackLines()); - saveImage(mSettings.getOutputPath() + fileNameDate + "_64.bmp", ch64); - saveImage(mSettings.getOutputPath() + fileNameDate + "_65.bmp", ch65); - saveImage(mSettings.getOutputPath() + fileNameDate + "_68.bmp", ch68); + saveImage(mSettings.getOutputPath() + fileNameDate + "_64.bmp", ch64); + saveImage(mSettings.getOutputPath() + fileNameDate + "_65.bmp", ch65); + saveImage(mSettings.getOutputPath() + fileNameDate + "_68.bmp", ch68); cv::Mat thermalRef = cv::imread(mSettings.getResourcesPath() + "thermal_ref.bmp"); cv::Mat thermalImage = ThreatImage::irToTemperature(irImage, thermalRef); imagesToSpread.push_back(ImageForSpread(thermalImage, "thermal_")); - cv::bitwise_not(irImage, irImage); - irImage = ThreatImage::gamma(irImage, 1.8); + irImage = ThreatImage::invertIR(irImage); + irImage = ThreatImage::gamma(irImage, 1.4); + irImage = ThreatImage::contrast(irImage, 1.3, -40); + irImage = ThreatImage::sharpen(irImage); imagesToSpread.push_back(ImageForSpread(irImage, "IR_")); if(mSettings.addRainOverlay()) { @@ -300,6 +316,9 @@ int main(int argc, char *argv[]) cv::Mat threatedImage2 = mPacketParser.getRGBImage(PacketParser::APID_65, PacketParser::APID_65, PacketParser::APID_64, mSettings.fillBackLines()); if(!ThreatImage::isNightPass(threatedImage1, mSettings.getNightPassTreshold())) { + threatedImage1 = ThreatImage::sharpen(threatedImage1); + threatedImage2 = ThreatImage::sharpen(threatedImage2); + imagesToSpread.push_back(ImageForSpread(threatedImage1, "321_")); saveImage(mSettings.getOutputPath() + fileNameDate + "_321.bmp", threatedImage1); @@ -324,6 +343,8 @@ int main(int argc, char *argv[]) cv::Mat threatedImage = mPacketParser.getRGBImage(PacketParser::APID_65, PacketParser::APID_65, PacketParser::APID_64, mSettings.fillBackLines()); if(!ThreatImage::isNightPass(threatedImage, mSettings.getNightPassTreshold())) { + threatedImage = ThreatImage::sharpen(threatedImage); + imagesToSpread.push_back(ImageForSpread(threatedImage, "221_")); saveImage(mSettings.getOutputPath() + fileNameDate + "_221.bmp", threatedImage); } else { @@ -342,8 +363,10 @@ int main(int argc, char *argv[]) cv::Mat rainRef = cv::imread(mSettings.getResourcesPath() + "rain.bmp"); cv::Mat rainOverlay = ThreatImage::irToRain(ch68, rainRef); - cv::bitwise_not(ch68, ch68); - ch68 = ThreatImage::gamma(ch68, 1.8); + ch68 = ThreatImage::invertIR(ch68); + ch68 = ThreatImage::gamma(ch68, 1.4); + ch68 = ThreatImage::contrast(ch68, 1.3, -40); + ch68 = ThreatImage::sharpen(ch68); imagesToSpread.push_back(ImageForSpread(ch68, "IR_")); if(mSettings.addRainOverlay()) { @@ -380,7 +403,7 @@ int main(int argc, char *argv[]) if(mSettings.mercatorProjection()) { mThreadPool.addJob([=]() { SpreadImage spreadImage; - cv::Mat mercator = spreadImage.mercatorProjection((*it).image, calc); + cv::Mat mercator = spreadImage.mercatorProjection((*it).image, calc, mSettings.getProjectionScale()); if(!mercator.empty()) { ThreatImage::drawWatermark(mercator, dateStr); @@ -394,7 +417,7 @@ int main(int argc, char *argv[]) if(mSettings.equadistantProjection()) { mThreadPool.addJob([=]() { SpreadImage spreadImage; - cv::Mat equidistant = spreadImage.equidistantProjection((*it).image, calc); + cv::Mat equidistant = spreadImage.equidistantProjection((*it).image, calc, mSettings.getProjectionScale()); if(!equidistant.empty()) { ThreatImage::drawWatermark(equidistant, dateStr); @@ -407,12 +430,250 @@ int main(int argc, char *argv[]) } std::cout << "Generate images" << std::endl; - mThreadPool.stop(); + mThreadPool.waitForAllJobsDone(); std::cout << "Generate images done" << std::endl; + imagesToSpread.clear(); + + std::cout << "Generate composite images" << std::endl; + std::time_t now = std::time(nullptr); + std::stringstream compositeFileNameDateSS; + compositeFileNameDateSS << std::put_time(std::localtime(&now), "%Y-%m-%d-%H-%M-%S"); + + if(mSettings.generateComposite123()) { + std::list images123; + std::list geolocationCalculators123; + searchForImages(images123, geolocationCalculators123, "123"); + + if(images123.size() > 1 && images123.size() == geolocationCalculators123.size()) { + if(mSettings.compositeEquadistantProjection() || mSettings.compositeMercatorProjection()) { + for(auto &img : images123) { + img = ThreatImage::sharpen(img); + } + } + + SpreadImage spreadImage; + if(mSettings.compositeEquadistantProjection()) { + cv::Mat composite = spreadImage.equidistantProjection(images123, geolocationCalculators123, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate equidistant channel 123 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "equidistant_" + compositeFileNameDateSS.str() + "_123_composite.jpg", composite); + } + if(mSettings.compositeMercatorProjection()) { + cv::Mat composite = spreadImage.mercatorProjection(images123, geolocationCalculators123, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate mercator channel 123 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "mercator_" + compositeFileNameDateSS.str() + "_123_composite.jpg", composite); + } + + } + } + + if(mSettings.generateComposite125()) { + std::list images125; + std::list geolocationCalculators125; + searchForImages(images125, geolocationCalculators125, "125"); + + if(images125.size() > 1 && images125.size() == geolocationCalculators125.size()) { + SpreadImage spreadImage; + if(mSettings.compositeEquadistantProjection()) { + cv::Mat composite = spreadImage.equidistantProjection(images125, geolocationCalculators125, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate equidistant channel 125 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "equidistant_" + compositeFileNameDateSS.str() + "_125_composite.jpg", composite); + } + if(mSettings.compositeMercatorProjection()) { + cv::Mat composite = spreadImage.mercatorProjection(images125, geolocationCalculators125, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate mercator channel 125 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "mercator_" + compositeFileNameDateSS.str() + "_125_composite.jpg", composite); + } + } + } + + if(mSettings.generateComposite221()) { + std::list images221; + std::list geolocationCalculators221; + searchForImages(images221, geolocationCalculators221, "221"); + + if(images221.size() > 1 && images221.size() == geolocationCalculators221.size()) { + SpreadImage spreadImage; + if(mSettings.compositeEquadistantProjection() || mSettings.compositeMercatorProjection()) { + for(auto &img : images221) { + img = ThreatImage::sharpen(img); + } + } + + if(mSettings.compositeEquadistantProjection()) { + cv::Mat composite = spreadImage.equidistantProjection(images221, geolocationCalculators221, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate equidistant channel 221 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "equidistant_" + compositeFileNameDateSS.str() + "_221_composite.jpg", composite); + } + if(mSettings.compositeMercatorProjection()) { + cv::Mat composite = spreadImage.mercatorProjection(images221, geolocationCalculators221, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate mercator channel 221 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "mercator_" + compositeFileNameDateSS.str() + "_221_composite.jpg", composite); + } + } + } + + if(mSettings.generateComposite68()) { + std::list images68; + std::list geolocationCalculators68; + searchForImages(images68, geolocationCalculators68, "68"); + + if(images68.size() > 1 && images68.size() == geolocationCalculators68.size()) { + if(mSettings.compositeEquadistantProjection() || mSettings.compositeMercatorProjection()) { + for(auto &img : images68) { + img = ThreatImage::invertIR(img); + img = ThreatImage::gamma(img, 1.4); + img = ThreatImage::contrast(img, 1.3, -40); + img = ThreatImage::sharpen(img); + } + } + + SpreadImage spreadImage; + if(mSettings.compositeEquadistantProjection()) { + cv::Mat composite = spreadImage.equidistantProjection(images68, geolocationCalculators68, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate equidistant channel 68 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "equidistant_" + compositeFileNameDateSS.str() + "_68_composite.jpg", composite); + } + if(mSettings.compositeMercatorProjection()) { + cv::Mat composite = spreadImage.mercatorProjection(images68, geolocationCalculators68, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate mercator channel 68 composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "mercator_" + compositeFileNameDateSS.str() + "_68_composite.jpg", composite); + } + } + } + + if(mSettings.generateComposite68Rain()) { + std::list images68; + std::list geolocationCalculators68; + searchForImages(images68, geolocationCalculators68, "68"); + + if(images68.size() > 1 && images68.size() == geolocationCalculators68.size()) { + if(mSettings.compositeEquadistantProjection() || mSettings.compositeMercatorProjection()) { + cv::Mat rainRef = cv::imread(mSettings.getResourcesPath() + "rain.bmp"); + for(auto &img : images68) { + cv::Mat rainOverlay = ThreatImage::irToRain(img, rainRef); + img = ThreatImage::invertIR(img); + img = ThreatImage::gamma(img, 1.4); + img = ThreatImage::contrast(img, 1.3, -40); + img = ThreatImage::sharpen(img); + img = ThreatImage::addRainOverlay(img, rainOverlay); + } + } + + SpreadImage spreadImage; + if(mSettings.compositeEquadistantProjection()) { + cv::Mat composite = spreadImage.equidistantProjection(images68, geolocationCalculators68, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate equidistant channel 68 rain composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "equidistant_" + compositeFileNameDateSS.str() + "_68_rain_composite.jpg", composite); + } + if(mSettings.compositeMercatorProjection()) { + cv::Mat composite = spreadImage.mercatorProjection(images68, geolocationCalculators68, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate mercator channel 68 rain composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "mercator_" + compositeFileNameDateSS.str() + "_68_rain_composite.jpg", composite); + } + } + } + + if(mSettings.generateCompositeThermal()) { + std::list images68; + std::list geolocationCalculators68; + searchForImages(images68, geolocationCalculators68, "68"); + + if(images68.size() > 1 && images68.size() == geolocationCalculators68.size()) { + if(mSettings.compositeEquadistantProjection() || mSettings.compositeMercatorProjection()) { + cv::Mat thermalRef = cv::imread(mSettings.getResourcesPath() + "thermal_ref.bmp"); + for(auto &img : images68) { + img = ThreatImage::irToTemperature(img, thermalRef); + } + } + + SpreadImage spreadImage; + if(mSettings.compositeEquadistantProjection()) { + cv::Mat composite = spreadImage.equidistantProjection(images68, geolocationCalculators68, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate equidistant thermal composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "equidistant_" + compositeFileNameDateSS.str() + "_thermal_composite.jpg", composite); + } + if(mSettings.compositeMercatorProjection()) { + cv::Mat composite = spreadImage.mercatorProjection(images68, geolocationCalculators68, mSettings.getCompositeProjectionScale(), [](float progress){ + std::cout << "Generate mercator thermal composite image " << (int)progress << "% \t\t\r" << std::flush; + }); + std::cout << std::endl; + saveImage(mSettings.getOutputPath() + "mercator_" + compositeFileNameDateSS.str() + "_thermal_composite.jpg", composite); + } + } + } + + std::cout << "Generate composite images done" << std::endl; + mThreadPool.stop(); return 0; } +void searchForImages(std::list &imagesOut, std::list &geolocationCalculatorsOut, const std::string &channelName) +{ + std::time_t now = std::time(nullptr); + std::map> map; + + for(const auto & entry : fs::directory_iterator(mSettings.getOutputPath())) { + auto ftime = fs::last_write_time(entry); + std::time_t cftime = std::chrono::system_clock::to_time_t((ftime)); + std::time_t fileCreatedSec = now - cftime; + + if(entry.path().extension() == ".gcp" && fileCreatedSec < 21600) { //6h + std::string folder = entry.path().parent_path().generic_string(); + std::string gcpFileName = entry.path().filename().generic_string(); + std::string fileNameBase = gcpFileName.substr(0, gcpFileName.size() - 4); + + do { + fs::path fileJPG(folder + "/" + fileNameBase + "_" + channelName + ".jpg"); + + if(fs::exists(fileJPG)) { + map[cftime] = std::make_tuple(entry.path().generic_string(), fileJPG.generic_string()); + break; + } + + fs::path fileBMP(folder + "/" + fileNameBase + "_" + channelName + ".bmp"); + + if(fs::exists(fileBMP)) { + map[cftime] = std::make_tuple(entry.path().generic_string(), fileBMP.generic_string()); + + break; + } + } while(false); + } + } + + if(map.size() > 1) { + for (auto const &[time, paths] : map) { + std::cout << std::get<1>(paths) << std::endl; + + geolocationCalculatorsOut.emplace_back(PixelGeolocationCalculator::load(std::get<0>(paths))); + imagesOut.emplace_back(cv::imread(std::get<1>(paths))); + } + } +} + void saveImage(const std::string fileName, const cv::Mat &image) { std::unique_locklock(saveImageMutex); diff --git a/media/equidistant_ir_composite.jpg b/media/equidistant_ir_composite.jpg new file mode 100644 index 0000000..a2f4526 Binary files /dev/null and b/media/equidistant_ir_composite.jpg differ diff --git a/readme.md b/readme.md index bffdfb6..9aeb91f 100644 --- a/readme.md +++ b/readme.md @@ -92,10 +92,6 @@ Other settings can be found in the settings.ini file. ## Development Master branch is for the latest stable version, beta branch for beta versions, development is ongoing on other branches. - -## Future developments - - Composite output from multiple passes - ## Example Outputs @@ -118,3 +114,7 @@ Master branch is for the latest stable version, beta branch for beta versions, d ### Temperature Image ![Temperature Image](media/equidistant_thermal.jpg) + +### Composite IR Image + +![IR Composite Image](media/equidistant_ir_composite.jpg) \ No newline at end of file diff --git a/resources/settings.ini b/resources/settings.ini index f992273..286e745 100644 --- a/resources/settings.ini +++ b/resources/settings.ini @@ -8,7 +8,20 @@ ScanAngleM2=110.8 RollM2=-2.9 PitchM2=0.3 YawM2=0 +# Onboard time offset in seconds +TimeOffsetM2=0 NightPassTreshold=20.0 +# Scale output image, it helps to reduce memory usage and generates smaller images +ProjectionScale=0.75 +CompositeProjectionScale=0.7 +CompositeAzimuthalEquidistantProjection=true +CompositeMercatorProjection=false +GenerateComposite123=true +GenerateComposite125=true +GenerateComposite221=true +GenerateComposite68=true +GenerateCompositeThermal=true +GenerateComposite68Rain=true [Demodulator] ;Increasing bandwidth allow the pll to lock faster on signal, while decreasing it makes the pll lock more stable @@ -25,7 +38,10 @@ FillBlackLines=true ;Place options: top_left, top_center, top_right, bottom_left, bottom_center, bottom_right Place=top_center Color=#AD880C -Size=5 +#Font height in pixels +Size=80 +# Font line width in pixels +Width=3 ;Text options: use '%date%' for date, '\n' for new line Text=MeteorDemod by Digitelektro\nMETEOR M2\n%date% @@ -42,23 +58,25 @@ MarkType=TILTED_CROSS [ShapeFileGraticules] FileName=ShapeFiles/ne_110m_graticules_10.shp Color=#C8C8C8 -Thickness=3 +Thickness=1 [ShapeFileCoastLines] FileName=ShapeFiles/ne_50m_coastline.shp Color=#808000 -Thickness=3 +Thickness=2 [ShapeFileBoundaryLines] FileName=ShapeFiles/ne_50m_admin_0_boundary_lines_land.shp Color=#C8C8C8 -Thickness=5 +Thickness=2 [ShapeFilePopulatedPlaces] FileName=ShapeFiles/ne_50m_populated_places.shp Color=#5A42F5 -Thickness=3 -FontScale=2 +#Font height in pixels +Size=40 +# Font line width in pixels +Width=2 PointRadius=10 FilterColumnName=ADM0CAP NumericFilter=1 diff --git a/tools/pixelgeolocationcalculator.cpp b/tools/pixelgeolocationcalculator.cpp index 1dc04a2..f00bd83 100644 --- a/tools/pixelgeolocationcalculator.cpp +++ b/tools/pixelgeolocationcalculator.cpp @@ -1,6 +1,34 @@ #include "pixelgeolocationcalculator.h" +#include "settings.h" #include +PixelGeolocationCalculator PixelGeolocationCalculator::load(const std::string &path) +{ + Settings &settings = Settings::getInstance(); + TleReader reader (settings.getTlePath()); + reader.processFile(); + TleReader::TLE tle; + reader.getTLE("METEOR-M 2", tle); + PixelGeolocationCalculator calc(tle, DateTime(), TimeSpan(0), settings.getM2ScanAngle(), settings.getM2Roll(), settings.getM2Pitch(), settings.getM2Yaw()); + std::ifstream gcpReader(path); + + if(!gcpReader) { + std::cout << "Open GCP file failed"; + return calc; + } + + calc.mCoordinates.clear(); + + int i, n; + double longitude, latitude; + while (gcpReader >> i >> n >> latitude >> longitude) + { + calc.mCoordinates.push_back(CoordGeodetic(latitude, longitude, 0, false)); + } + + return calc; +} + PixelGeolocationCalculator::PixelGeolocationCalculator(const TleReader::TLE &tle, const DateTime &passStart, const TimeSpan &passLength, double scanAngle, double roll, double pitch, double yaw, int earthRadius, int satelliteAltitude) : mTle(tle.satellite, tle.line1, tle.line2) , mSgp4(mTle) @@ -51,8 +79,6 @@ void PixelGeolocationCalculator::calcPixelCoordinates() mCoordinates.push_back(coordinate); } } - - calculateCartesionCoordinates(); } void PixelGeolocationCalculator::save(const std::string &path) @@ -79,50 +105,6 @@ void PixelGeolocationCalculator::save(const std::string &path) file.close(); } -void PixelGeolocationCalculator::load(const std::string &path) -{ - std::ifstream gcpReader(path); - - if(!gcpReader) { - std::cout << "Open GCP file failed"; - return; - } - - mCoordinates.clear(); - - int i, n; - double longitude, latitude; - while (gcpReader >> i >> n >> latitude >> longitude) - { - mCoordinates.push_back(CoordGeodetic(latitude, longitude, 0, false)); - } - - calculateCartesionCoordinates(); -} - -void PixelGeolocationCalculator::calculateCartesionCoordinates() -{ - double radius = mEarthradius + mSatelliteAltitude; - - mMercatorCartesianCoordinates.clear(); - mEquidistantCartesianCoordinates.clear(); - - mMercatorCartesianCoordinates.resize(mCoordinates.size()); - mEquidistantCartesianCoordinates.resize(mCoordinates.size()); - - mCenterCoordinate.latitude = mCoordinates[mCoordinates.size() / 2 + 79].latitude; - mCenterCoordinate.longitude = mCoordinates[mCoordinates.size() / 2 + 79].longitude; - - for (unsigned int i = 0; i < mMercatorCartesianCoordinates.size(); i++) { - - //Azimuthal Equidistant Projection - mEquidistantCartesianCoordinates[i] = coordinateToAzimuthalEquidistantProjection(mCoordinates[i], mCenterCoordinate, radius); - - //Mercator Projection - mMercatorCartesianCoordinates[i] = coordinateToMercatorProjection(mCoordinates[i], radius); - } -} - Vector PixelGeolocationCalculator::locationToVector(const CoordGeodetic &location) { double cosLat = cos(location.latitude); diff --git a/tools/pixelgeolocationcalculator.h b/tools/pixelgeolocationcalculator.h index 5f7366e..5556118 100644 --- a/tools/pixelgeolocationcalculator.h +++ b/tools/pixelgeolocationcalculator.h @@ -10,96 +10,90 @@ #include "vector.h" #include "tlereader.h" +inline static CoordGeodetic operator+(const CoordGeodetic& coord1, const CoordGeodetic& coord2) +{ + CoordGeodetic result(0, 0, 0) ; + result.latitude = coord1.latitude + coord2.latitude ; + result.longitude = coord1.longitude + coord2.longitude; + return result; +} + class PixelGeolocationCalculator { public: template - struct CartesianCoordinateT { + struct CartesianCoordinate { T x; T y; }; template - friend std::ostream& operator << (std::ostream &o, const CartesianCoordinateT &coord) { + friend std::ostream& operator << (std::ostream &o, const CartesianCoordinate &coord) { return o << "x: " << coord.x << "\ty: " << coord.y; } - typedef CartesianCoordinateT CartesianCoordinate; - typedef CartesianCoordinateT CartesianCoordinateF; - typedef CartesianCoordinateT CartesianCoordinateD; + typedef CartesianCoordinate CartesianCoordinateF; + typedef CartesianCoordinate CartesianCoordinateD; private: PixelGeolocationCalculator(); +public: + static PixelGeolocationCalculator load(const std::string &path); + public: PixelGeolocationCalculator(const TleReader::TLE &tle, const DateTime &passStart, const TimeSpan &passLength, double scanAngle, double roll, double pitch, double yaw, int earthRadius = 6378, int satelliteAltitude = 825); void calcPixelCoordinates(); void save(const std::string &path); - void load(const std::string &path); public: int getGeorefMaxImageHeight() const { - return (mEquidistantCartesianCoordinates.size() / 158) * 10; - } - - const CartesianCoordinateF &getTopLeftEquidistant() const { - return mEquidistantCartesianCoordinates[0]; - } - - const CartesianCoordinateF &getTopRightEquidistant() const { - return mEquidistantCartesianCoordinates[157]; + return (mCoordinates.size() / 158) * 10; } - const CartesianCoordinateF &getBottomLeftEquidistant() const { - return mEquidistantCartesianCoordinates[mEquidistantCartesianCoordinates.size() - 158]; + const CoordGeodetic &getCenterCoordinate() const { + return mCoordinates[mCoordinates.size() / 2 + 79]; } - const CartesianCoordinateF &getBottomRightEquidistant() const { - return mEquidistantCartesianCoordinates[mEquidistantCartesianCoordinates.size() - 1]; + inline const CoordGeodetic &getCoordinateAt(unsigned int x, unsigned int y) const { + return mCoordinates[((x / 10)) + ((y / 10) * 158)]; } - const CartesianCoordinateF &getEquidistantAt(unsigned int x, unsigned int y) const { - return mEquidistantCartesianCoordinates[((x / 10)) + ((y / 10) * 158)]; + inline const CoordGeodetic &getCoordinateTopLeft() const { + return mCoordinates[0]; } - const CartesianCoordinateF &getTopLeftMercator() const { - return mMercatorCartesianCoordinates[0]; + inline const CoordGeodetic &getCoordinateTopRight() const { + return mCoordinates[157]; } - const CartesianCoordinateF &getTopRightMercator() const { - return mMercatorCartesianCoordinates[157]; + inline const CoordGeodetic &getCoordinateBottomLeft() const { + return mCoordinates[mCoordinates.size() - 158]; } - const CartesianCoordinateF &getBottomLeftMercator() const { - return mMercatorCartesianCoordinates[mMercatorCartesianCoordinates.size() - 158]; + inline const CoordGeodetic &getCoordinateBottomRight() const { + return mCoordinates[mCoordinates.size() - 1]; } - const CartesianCoordinateF &getBottomRightMercator() const { - return mMercatorCartesianCoordinates[mMercatorCartesianCoordinates.size() - 1]; + inline int getEarthRadius() const { + return mEarthradius; } - const CoordGeodetic &getCenterCoordinate() const { - return mCenterCoordinate; + inline int getSatelliteAltitude() const { + return mSatelliteAltitude; } - const CartesianCoordinateF &getMercatorAt(unsigned int x, unsigned int y) const { - return mMercatorCartesianCoordinates[((x / 10)) + ((y / 10) * 158)]; + inline int getSatelliteHeight() const { + return mEarthradius + mSatelliteAltitude; } public: - static CartesianCoordinateF coordinateToMercatorProjection(double latitude, double longitude, double radius) { - return coordinateToMercatorProjection(CoordGeodetic(latitude, longitude, 0), radius); - } - - static CartesianCoordinateF coordinateToAzimuthalEquidistantProjection(double latitude, double longitude, double centerLatitude, double centerLongitude, double radius) { - return coordinateToAzimuthalEquidistantProjection(CoordGeodetic(latitude, longitude, 0), CoordGeodetic(centerLatitude, centerLongitude, 0), radius); - } - - static CartesianCoordinateF coordinateToMercatorProjection(const CoordGeodetic &coordinate, double radius) { - CartesianCoordinateF cartesianCoordinate; + template + static CartesianCoordinate coordinateToMercatorProjection(const CoordGeodetic &coordinate, double radius, float scale) { + CartesianCoordinate cartesianCoordinate; CoordGeodetic correctedCoordinate = coordinate; if (coordinate.latitude > degreeToRadian(85.05113)) @@ -113,14 +107,15 @@ class PixelGeolocationCalculator cartesianCoordinate.x = radius * (M_PI + correctedCoordinate.longitude); cartesianCoordinate.y = radius * (M_PI - log(tan(M_PI / 4.0 + (correctedCoordinate.latitude) / 2.0))); - return cartesianCoordinate; + return {cartesianCoordinate.x * scale, cartesianCoordinate.y * scale}; } - static CartesianCoordinateF coordinateToAzimuthalEquidistantProjection(const CoordGeodetic &coordinate, const CoordGeodetic ¢erCoordinate, double radius) { - CartesianCoordinateF cartesianCoordinate; + template + static CartesianCoordinate coordinateToAzimuthalEquidistantProjection(const CoordGeodetic &coordinate, const CoordGeodetic ¢erCoordinate, double radius, float scale) { + CartesianCoordinate cartesianCoordinate; cartesianCoordinate.x = radius * (cos(coordinate.latitude) * sin(coordinate.longitude - centerCoordinate.longitude)); cartesianCoordinate.y = -radius * (cos(centerCoordinate.latitude) * sin(coordinate.latitude) - sin(centerCoordinate.latitude) * cos(coordinate.latitude) * cos(coordinate.longitude - centerCoordinate.longitude)); - return cartesianCoordinate; + return {cartesianCoordinate.x * scale, cartesianCoordinate.y * scale}; } private: @@ -132,12 +127,12 @@ class PixelGeolocationCalculator double calculateBearingAngle(const CoordGeodetic &start, const CoordGeodetic &end); Matrix4x4 lookAt(const Vector3 &position, const Vector3 &target, const Vector3 &up); - static double degreeToRadian(double degree) + static inline double degreeToRadian(double degree) { return (M_PI * degree / 180.0); } - static double radioanToDegree(double radian) + static inline double radioanToDegree(double radian) { return radian * (180.0 / M_PI); } @@ -151,9 +146,6 @@ class PixelGeolocationCalculator int mEarthradius; int mSatelliteAltitude; std::vector mCoordinates; - std::vector mMercatorCartesianCoordinates; - std::vector mEquidistantCartesianCoordinates; - CoordGeodetic mCenterCoordinate; static constexpr double PIXELTIME_MINUTES = 0.02564876089324618736383442265795; //Just a rough calculation for every 10 pixel in minutes