├── Version.h ├── Images ├── DoP.png ├── KML.png ├── Path1.png ├── Path2.png ├── Path3.png ├── Speed.png ├── Stats.png ├── Strk.png ├── Altitude.png ├── Altitude2.png └── Screenshot.png ├── Ressources ├── Intro.JPG └── Intro.xcf.xz ├── Makefile ├── Generating video.md ├── LICENSE ├── .gitignore ├── gfxlib ├── Export.h ├── TrkStatGfx.h ├── SpeedGfx.h ├── SpeedTrkGfx.h ├── QualityGfx.h ├── PathGfx.h ├── AltitudeGfx.h ├── Makefile ├── Gfx.h ├── Gfx.cpp ├── Export.cpp ├── SpeedTrkGfx.cpp ├── PathGfx.cpp ├── SpeedGfx.cpp ├── QualityGfx.cpp └── AltitudeGfx.cpp ├── datalib ├── Context.h ├── Context.cpp ├── samplesCollection.h ├── Makefile ├── GPSCoordinate.cpp ├── GPVideo.h ├── GPSCoordinate.h ├── GPX.h ├── GPX.cpp └── GPVideo.cpp ├── remake.sh ├── GPMFMakeFile ├── MakefileLocal ├── CHANGES.md ├── GPMFMetersGenerator.cpp ├── mkStory.cpp └── README.md /Version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION 2 | #define VERSION "4.07.00" 3 | #endif 4 | -------------------------------------------------------------------------------- /Images/DoP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/DoP.png -------------------------------------------------------------------------------- /Images/KML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/KML.png -------------------------------------------------------------------------------- /Images/Path1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Path1.png -------------------------------------------------------------------------------- /Images/Path2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Path2.png -------------------------------------------------------------------------------- /Images/Path3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Path3.png -------------------------------------------------------------------------------- /Images/Speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Speed.png -------------------------------------------------------------------------------- /Images/Stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Stats.png -------------------------------------------------------------------------------- /Images/Strk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Strk.png -------------------------------------------------------------------------------- /Images/Altitude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Altitude.png -------------------------------------------------------------------------------- /Images/Altitude2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Altitude2.png -------------------------------------------------------------------------------- /Images/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Images/Screenshot.png -------------------------------------------------------------------------------- /Ressources/Intro.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Ressources/Intro.JPG -------------------------------------------------------------------------------- /Ressources/Intro.xcf.xz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/destroyedlolo/GPMFMetersGenerator/HEAD/Ressources/Intro.xcf.xz -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | $(MAKE) -f GPMFMakeFile 3 | $(MAKE) -C datalib 4 | $(MAKE) -C gfxlib 5 | $(MAKE) -f MakefileLocal 6 | -------------------------------------------------------------------------------- /Generating video.md: -------------------------------------------------------------------------------- 1 | To combine images to video, use : 2 | 3 | ``` 4 | ffmpeg -framerate 9 -i GX013064/alt%07d.png -vcodec png altitude.mov 5 | ``` 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GPMFMetersGenerator is covered by 2 | Creative Commons Attribution-NonCommercial 3.0 License 3 | (http://creativecommons.org/licenses/by-nc/3.0/) 4 | Consequently, you're free to use if for personal or non-profit usage, 5 | professional or commercial usage REQUIRES a commercial licence. 6 | 7 | Participations are welcome :) 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | 34 | # VIM 35 | *.sw? 36 | -------------------------------------------------------------------------------- /gfxlib/Export.h: -------------------------------------------------------------------------------- 1 | /* Export.h 2 | * Export GPMF as standard GPS files 3 | */ 4 | #ifndef EXPORT_H 5 | #define EXPORT_H 6 | 7 | #include "../datalib/Context.h" 8 | #include "../datalib/GPVideo.h" 9 | 10 | class Export { 11 | GPVideo &video; // source video 12 | 13 | public: 14 | Export( GPVideo &v ): video(v) {} 15 | 16 | void generateGPX( const char *fulltarget, char *filename, char *iname ); 17 | void generateKML( const char *fulltarget, char *filename, char *iname ); 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /datalib/Context.h: -------------------------------------------------------------------------------- 1 | /* Context 2 | * Shared (among all tools and libraries) context configuration 3 | */ 4 | 5 | #ifndef CONTEXT_H 6 | #define CONTEXT_H 7 | 8 | #include 9 | 10 | 11 | extern bool verbose; 12 | extern bool debug; 13 | 14 | extern bool enfquality; 15 | 16 | /* Utilities */ 17 | 18 | #include 19 | 20 | /* print the content of a tm */ 21 | extern void printtm( struct tm *, FILE *f=NULL ); 22 | 23 | /* Convert 2 chars to int */ 24 | extern unsigned char char2int( const char * ); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /datalib/Context.cpp: -------------------------------------------------------------------------------- 1 | #include "Context.h" 2 | 3 | bool verbose = false; 4 | bool debug = false; 5 | bool enfquality = false; 6 | 7 | void printtm( struct tm *t, FILE *f ){ 8 | if(!f) 9 | f = stdout; 10 | 11 | fprintf(f, "%4d-%02d-%02d %02d:%02d:%02d ", 12 | t->tm_year+1900, t->tm_mon+1, t->tm_mday, 13 | t->tm_hour, t->tm_min, t->tm_sec 14 | ); 15 | if(t->tm_gmtoff) 16 | fprintf(f, "(offset %6ld sec)", t->tm_gmtoff); 17 | } 18 | 19 | unsigned char char2int( const char *p ){ 20 | unsigned char ret = (*p - '0')*10; 21 | ret += *(++p) - '0'; 22 | return ret; 23 | } 24 | -------------------------------------------------------------------------------- /datalib/samplesCollection.h: -------------------------------------------------------------------------------- 1 | /* handle collection of samples 2 | */ 3 | 4 | #ifndef DTCOLLECTION_H 5 | #define DTCOLLECTION_H 6 | 7 | #include 8 | 9 | #include 10 | 11 | template class samplesCollection { 12 | DT min, max; 13 | 14 | protected: 15 | std::vector
samples; 16 | 17 | public: 18 | DT &getMin( void ){ return this->min; }; 19 | DT &getMax( void ){ return this->max; }; 20 | DT &getFirst(void){ return this->samples.front(); }; 21 | DT &getLast(void){ return this->samples.back(); }; 22 | DT &operator[](int i){ return this->samples[i]; }; 23 | uint32_t getSampleCount(void){ return this->samples.size(); }; 24 | 25 | std::vector
&getSamples(void){ return this->samples; }; 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /gfxlib/TrkStatGfx.h: -------------------------------------------------------------------------------- 1 | /* Generate trekking statistic "graphic" 2 | */ 3 | 4 | #ifndef TRKSTTGFX_H 5 | #define TRKSTTGFX_H 6 | 7 | #include "Gfx.h" 8 | 9 | class TrekkingStatGfx : public Gfx { 10 | public: 11 | enum gfxtype { NONE=0, HM=1, HMS=2 }; 12 | 13 | private: 14 | gfxtype type; 15 | 16 | long sDistance; // Original distance covered by the story 17 | time_t beginning; // Beginning of the journey 18 | 19 | int dst_x, dst_y; 20 | int duration_x, duration_y; 21 | 22 | protected: 23 | void calcScales( void ); 24 | void generateBackground( void ); 25 | void generateOneGfx( const char *, char *, int , GPMFdata & ); 26 | 27 | public: 28 | TrekkingStatGfx(GPVideo &v, GPX *, gfxtype); 29 | 30 | void GenerateAllGfx( const char *dir, char *file ); 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /gfxlib/SpeedGfx.h: -------------------------------------------------------------------------------- 1 | /* SpeedGraphic 2 | * Generate speed-o-meter graphics 3 | * 4 | */ 5 | #ifndef SPEEDGFX_H 6 | #define SPEEDGFX_H 7 | 8 | #include "Gfx.h" 9 | 10 | class SpeedGfx : public Gfx { 11 | int range; // data range 12 | double scale; // computed scale data->gfx 13 | int offlabel; // offset to display the label 14 | int offgrade; // offset for the grade label 15 | char type; // type of graphics to display 16 | 17 | double transforme( double angle ); 18 | 19 | protected: 20 | void calcScales( void ); 21 | void generateBackground( void ); 22 | void generateOneGfx( const char *, char *, int , GPMFdata & ){}; 23 | void generateOneGfx( const char *fulltarget, char *filename, int index, GPMFdata ¤t, int prc ); 24 | 25 | public: 26 | SpeedGfx(GPVideo &v, GPX *h, char atype='2'); 27 | 28 | void GenerateAllGfx( const char *dir, char *file ); 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /remake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # GPMF library 4 | 5 | LFMakeMaker -v +f=GPMFMakeFile -cc="gcc -Wall -O2" gpmf-parser/*.c gpmf-parser/demo/GPMF_mp4reader.c -ar=libGPMF.a > GPMFMakeFile 6 | 7 | # rebuild data utility library 8 | cd datalib 9 | 10 | LFMakeMaker -v +f=Makefile -cc="g++ -Wall -O2" --opts="-lm" *.cpp -ar=../libdatalib.a > Makefile 11 | 12 | cd .. 13 | 14 | # rebuild data utility library 15 | cd gfxlib 16 | 17 | LFMakeMaker -v +f=Makefile -cc="g++ -Wall -O2" --opts="-lm \$(shell pkg-config --cflags cairo ) \$(shell pkg-config --libs cairo )" *.cpp -ar=../libgfxlib.a > Makefile 18 | 19 | cd .. 20 | 21 | # Final executables 22 | 23 | LFMakeMaker -v +f=MakefileLocal +f=libdatalib.a +f=libgfxlib.a -cc="g++ -Wall -O2" --opts="-lm -L. -lgfxlib -ldatalib -lGPMF \$(shell pkg-config --cflags cairo ) \$(shell pkg-config --libs cairo )" GPMFMetersGenerator.cpp -t=GPMFMetersGenerator mkStory.cpp -t=mkStory > MakefileLocal 24 | -------------------------------------------------------------------------------- /gfxlib/SpeedTrkGfx.h: -------------------------------------------------------------------------------- 1 | /* SpeedTracker 2 | * Generate speed tracker graphicsi 3 | */ 4 | #ifndef SPEEDTRKFX_H 5 | #define SPEEDTRKFX_H 6 | 7 | #include "Gfx.h" 8 | 9 | class SpeedTrkGfx : public Gfx { 10 | int range; 11 | double scale_h, scale_w; // Scales 12 | char type; // 2 ou 3 13 | 14 | /* Draw GPMF data 15 | * -> offset : move the curve (used to draw the shadow) 16 | * -> current : current position. 17 | * if set, the color will not be the same in front and in back of this position 18 | * if not set, we are drawing the shadow : THE PATH IS PRESERVED 19 | */ 20 | void drawGPMF(cairo_t *cr, int offset, uint32_t current=-1); 21 | 22 | protected: 23 | void calcScales( void ); 24 | void generateBackground( void ); 25 | void generateOneGfx( const char *, char *, int , GPMFdata & ); 26 | 27 | public: 28 | SpeedTrkGfx(GPVideo &v, GPX *h, char type); 29 | 30 | void GenerateAllGfx( const char *dir, char *file ); 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /GPMFMakeFile: -------------------------------------------------------------------------------- 1 | # makefile created automaticaly by LFMakeMaker 2 | # LFMakeMaker 1.6 (May 7 2022 20:46:23) (c)LFSoft 1997 3 | 4 | gotoall: all 5 | 6 | 7 | #The compiler (may be customized for compiler's options). 8 | cc=gcc -Wall -O2 9 | opts= 10 | 11 | gpmf-parser/GPMF_parser.o : gpmf-parser/GPMF_parser.c GPMFMakeFile 12 | $(cc) -c -o gpmf-parser/GPMF_parser.o \ 13 | gpmf-parser/GPMF_parser.c $(opts) 14 | 15 | gpmf-parser/GPMF_utils.o : gpmf-parser/GPMF_utils.c GPMFMakeFile 16 | $(cc) -c -o gpmf-parser/GPMF_utils.o gpmf-parser/GPMF_utils.c \ 17 | $(opts) 18 | 19 | gpmf-parser/demo/GPMF_mp4reader.o : gpmf-parser/demo/GPMF_mp4reader.c \ 20 | GPMFMakeFile 21 | $(cc) -c -o gpmf-parser/demo/GPMF_mp4reader.o \ 22 | gpmf-parser/demo/GPMF_mp4reader.c $(opts) 23 | 24 | libGPMF.a : gpmf-parser/demo/GPMF_mp4reader.o gpmf-parser/GPMF_utils.o \ 25 | gpmf-parser/GPMF_parser.o GPMFMakeFile 26 | ar rcs libGPMF.a gpmf-parser/demo/GPMF_mp4reader.o \ 27 | gpmf-parser/GPMF_utils.o gpmf-parser/GPMF_parser.o 28 | 29 | all: libGPMF.a 30 | -------------------------------------------------------------------------------- /datalib/Makefile: -------------------------------------------------------------------------------- 1 | # makefile created automaticaly by LFMakeMaker 2 | # LFMakeMaker 1.6 (May 7 2022 20:46:23) (c)LFSoft 1997 3 | 4 | gotoall: all 5 | 6 | 7 | #The compiler (may be customized for compiler's options). 8 | cc=g++ -Wall -O2 9 | opts=-lm 10 | 11 | Context.o : Context.cpp Context.h Makefile 12 | $(cc) -c -o Context.o Context.cpp $(opts) 13 | 14 | GPSCoordinate.o : GPSCoordinate.cpp GPSCoordinate.h Makefile 15 | $(cc) -c -o GPSCoordinate.o GPSCoordinate.cpp $(opts) 16 | 17 | GPVideo.o : GPVideo.cpp ../gpmf-parser/GPMF_parser.h \ 18 | ../gpmf-parser/GPMF_utils.h ../gpmf-parser/demo/GPMF_mp4reader.h \ 19 | Context.h GPVideo.h GPSCoordinate.h samplesCollection.h Makefile 20 | $(cc) -c -o GPVideo.o GPVideo.cpp $(opts) 21 | 22 | GPX.o : GPX.cpp ../datalib/Context.h GPX.h GPSCoordinate.h \ 23 | samplesCollection.h Makefile 24 | $(cc) -c -o GPX.o GPX.cpp $(opts) 25 | 26 | ../libdatalib.a : GPX.o GPVideo.o GPSCoordinate.o Context.o Makefile 27 | ar rcs ../libdatalib.a GPX.o GPVideo.o GPSCoordinate.o \ 28 | Context.o 29 | 30 | all: ../libdatalib.a 31 | -------------------------------------------------------------------------------- /gfxlib/QualityGfx.h: -------------------------------------------------------------------------------- 1 | /* QualityGaphic 2 | * Generate quality gaphic 3 | */ 4 | 5 | #ifndef QUALITYGFX_H 6 | #define QUALITYGFX_H 7 | 8 | #include "Gfx.h" 9 | 10 | class QualityGfx : public Gfx { 11 | int posLabel; // Label offset 12 | int offx, offy; // Graphic's offset 13 | 14 | int min_h, max_h, range_h; // Range values 15 | int delta_h; 16 | double scale_h, scale_w; // Scales 17 | 18 | /* Draw GPMF data 19 | * -> offset : move the curve (used to draw the shadow) 20 | * -> current : current position. 21 | * if set, the color will not be the same in front and in back of this position 22 | * if not set, we are drawing the shadow : THE PATH IS PRESERVED 23 | */ 24 | void drawGPMF(cairo_t *cr, int offset, uint32_t current=-1); 25 | 26 | protected: 27 | void calcScales( void ); 28 | void generateBackground( void ); 29 | void generateOneGfx( const char *, char *, int , GPMFdata & ); 30 | 31 | double linearcomponent( uint16_t val, uint16_t u1, double v1, uint16_t u2, double v2 ); 32 | 33 | public: 34 | QualityGfx(GPVideo &v, GPX *h); 35 | 36 | void GenerateAllGfx( const char *dir, char *file ); 37 | }; 38 | #endif 39 | -------------------------------------------------------------------------------- /gfxlib/PathGfx.h: -------------------------------------------------------------------------------- 1 | /* PathGraphic 2 | * Generate path graphics 3 | */ 4 | #ifndef PATHGFX_H 5 | #define PATHGFX_H 6 | 7 | #include "Gfx.h" 8 | 9 | class PathGfx : public Gfx { 10 | int min_x, min_y; 11 | int max_x, max_y; 12 | 13 | int range_x, range_y; 14 | int off_x, off_y; // offsets 15 | double scale; 16 | 17 | /* Convert position to coordinates */ 18 | void posXY( double, double, int &, int &); 19 | 20 | /* Draw external GPX data 21 | * cr -> cairo context 22 | * offset -> offset the curve (do draw shadows) 23 | */ 24 | void drawGPX(cairo_t *cr, int offset); 25 | 26 | /* Draw GPMF data 27 | * -> offset : move the curve (used to draw the shadow) 28 | * -> current : current position. 29 | * if set, the color will not be the same in front and in back of this position 30 | * if not set, we are drawing the shadow : THE PATH IS PRESERVED 31 | */ 32 | void drawGPMF(cairo_t *cr, int offset, uint32_t current=-1); 33 | 34 | protected: 35 | void calcScales( void ); 36 | void generateBackground( void ); 37 | void generateOneGfx( const char *, char *, int , GPMFdata & ); 38 | 39 | public: 40 | PathGfx(GPVideo &v, GPX *); 41 | 42 | void GenerateAllGfx( const char *dir, char *file ); 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /MakefileLocal: -------------------------------------------------------------------------------- 1 | # makefile created automaticaly by LFMakeMaker 2 | # LFMakeMaker 1.6 (May 7 2022 20:46:23) (c)LFSoft 1997 3 | 4 | gotoall: all 5 | 6 | 7 | #The compiler (may be customized for compiler's options). 8 | cc=g++ -Wall -O2 9 | opts=-lm -L. -lgfxlib -ldatalib -lGPMF $(shell pkg-config --cflags cairo ) $(shell pkg-config --libs cairo ) 10 | 11 | GPMFMetersGenerator.o : GPMFMetersGenerator.cpp datalib/Context.h \ 12 | datalib/GPVideo.h datalib/GPX.h gfxlib/SpeedGfx.h \ 13 | gfxlib/AltitudeGfx.h gfxlib/PathGfx.h gfxlib/SpeedTrkGfx.h \ 14 | gfxlib/Export.h gfxlib/QualityGfx.h gfxlib/TrkStatGfx.h Version.h \ 15 | libgfxlib.a libdatalib.a MakefileLocal 16 | $(cc) -c -o GPMFMetersGenerator.o GPMFMetersGenerator.cpp \ 17 | $(opts) 18 | 19 | GPMFMetersGenerator : GPMFMetersGenerator.o libgfxlib.a libdatalib.a \ 20 | MakefileLocal 21 | $(cc) -o GPMFMetersGenerator GPMFMetersGenerator.o $(opts) 22 | 23 | mkStory.o : mkStory.cpp datalib/Context.h datalib/GPVideo.h \ 24 | datalib/GPX.h Version.h libgfxlib.a libdatalib.a MakefileLocal 25 | $(cc) -c -o mkStory.o mkStory.cpp $(opts) 26 | 27 | mkStory : mkStory.o libgfxlib.a libdatalib.a MakefileLocal 28 | $(cc) -o mkStory mkStory.o $(opts) 29 | 30 | all: mkStory GPMFMetersGenerator 31 | -------------------------------------------------------------------------------- /gfxlib/AltitudeGfx.h: -------------------------------------------------------------------------------- 1 | /* AltitudeGraphic 2 | * Generate altitude graphics 3 | */ 4 | #ifndef ALTITUDEGFX_H 5 | #define ALTITUDEGFX_H 6 | 7 | #include "Gfx.h" 8 | 9 | class AltitudeGfx : public Gfx { 10 | int posLabel; // Label offset 11 | int offx, offy; // Graphic's offset 12 | 13 | int min_h, max_h, range_h; // Range values 14 | int delta_h; 15 | double scale_h, scale_w; // Scales 16 | 17 | /* Story's overwriting */ 18 | int soffx; // Video's own offset 19 | double sscale_w; // Video sample to GPX ones 20 | 21 | bool forcegpx; // Use GPX for drawing the curve 22 | bool nogfx; // Generate value only 23 | 24 | /* Draw external GPX data 25 | * cr -> cairo context 26 | * offset -> offset the curve (do draw shadows) 27 | */ 28 | void drawGPX(cairo_t *cr, int offset); 29 | 30 | /* Draw GPMF data 31 | * -> offset : move the curve (used to draw the shadow) 32 | * -> current : current position. 33 | * if set, the color will not be the same in front and in back of this position 34 | * if not set, we are drawing the shadow : THE PATH IS PRESERVED 35 | */ 36 | void drawGPMF(cairo_t *cr, int offset, uint32_t current=-1); 37 | 38 | protected: 39 | void calcScales( void ); 40 | void generateBackground( void ); 41 | void generateOneGfx( const char *, char *, int , GPMFdata & ); 42 | 43 | public: 44 | AltitudeGfx(GPVideo &v, GPX *h, bool forcegpx, bool nogfx); 45 | 46 | void GenerateAllGfx( const char *dir, char *file ); 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /datalib/GPSCoordinate.cpp: -------------------------------------------------------------------------------- 1 | #include "GPSCoordinate.h" 2 | 3 | std::string GPSCoordinate::strLocalTime( void ){ 4 | char buf[26]; 5 | strftime(buf, sizeof(buf), "%FT%T%z", this->getLocalTime()); 6 | 7 | return std::string(buf); 8 | } 9 | 10 | std::string GPSCoordinate::strLocalHour( bool HM_only ){ 11 | char buf[9]; 12 | strftime(buf, sizeof(buf), HM_only ? "%R" : "%T", this->getLocalTime()); 13 | 14 | return std::string(buf); 15 | } 16 | 17 | std::string GPSCoordinate::diffTime( time_t begin, bool sec ){ 18 | int d = difftime( this->sample_time, begin ); 19 | 20 | int j = d / 86400; 21 | int h = (d % 86400)/3600; 22 | int m = (d % 3600)/60; 23 | int s = d % 60; 24 | 25 | char t[60]; 26 | if(j) 27 | sprintf(t, "%2d %02d:%02d", j, h, m); 28 | else 29 | sprintf(t, "%02d:%02d", h, m); 30 | 31 | if(sec){ 32 | char *x; 33 | for(x = t; *x; x++); 34 | sprintf(x, ":%02d", s); 35 | } 36 | 37 | return t; 38 | } 39 | 40 | double GPSCoordinate::diffTimeF( time_t begin ){ 41 | return difftime( this->sample_time, begin ); 42 | } 43 | 44 | double GPSCoordinate::Estrangement( GPSCoordinate &other ){ 45 | double a = this->toRadian(other.getLatitude()); 46 | double b = this->toRadian(this->getLatitude()); 47 | double c = this->toRadian(other.getLongitude()); 48 | double d = this->toRadian(this->getLongitude()); 49 | 50 | return( R*acos(sin(a)*sin(b) + cos(a)*cos(b)*cos(c-d)) ); 51 | } 52 | 53 | double GPSCoordinate::addDistance( GPSCoordinate &other ){ 54 | double est = this->Estrangement(other); 55 | this->cumulative_distance = other.getCumulativeDistance(); 56 | if(!std::isnan(est)) 57 | this->cumulative_distance += est; 58 | 59 | return( this->cumulative_distance ); 60 | } 61 | -------------------------------------------------------------------------------- /gfxlib/Makefile: -------------------------------------------------------------------------------- 1 | # makefile created automaticaly by LFMakeMaker 2 | # LFMakeMaker 1.6 (May 7 2022 20:46:23) (c)LFSoft 1997 3 | 4 | gotoall: all 5 | 6 | 7 | #The compiler (may be customized for compiler's options). 8 | cc=g++ -Wall -O2 9 | opts=-lm $(shell pkg-config --cflags cairo ) $(shell pkg-config --libs cairo ) 10 | 11 | AltitudeGfx.o : AltitudeGfx.cpp AltitudeGfx.h Gfx.h \ 12 | ../datalib/Context.h ../datalib/GPVideo.h ../datalib/GPX.h Makefile 13 | $(cc) -c -o AltitudeGfx.o AltitudeGfx.cpp $(opts) 14 | 15 | Export.o : Export.cpp Export.h ../datalib/Context.h \ 16 | ../datalib/GPVideo.h Makefile 17 | $(cc) -c -o Export.o Export.cpp $(opts) 18 | 19 | Gfx.o : Gfx.cpp Gfx.h ../datalib/Context.h ../datalib/GPVideo.h \ 20 | ../datalib/GPX.h Makefile 21 | $(cc) -c -o Gfx.o Gfx.cpp $(opts) 22 | 23 | PathGfx.o : PathGfx.cpp PathGfx.h Gfx.h ../datalib/Context.h \ 24 | ../datalib/GPVideo.h ../datalib/GPX.h Makefile 25 | $(cc) -c -o PathGfx.o PathGfx.cpp $(opts) 26 | 27 | QualityGfx.o : QualityGfx.cpp QualityGfx.h Gfx.h ../datalib/Context.h \ 28 | ../datalib/GPVideo.h ../datalib/GPX.h Makefile 29 | $(cc) -c -o QualityGfx.o QualityGfx.cpp $(opts) 30 | 31 | SpeedGfx.o : SpeedGfx.cpp SpeedGfx.h Gfx.h ../datalib/Context.h \ 32 | ../datalib/GPVideo.h ../datalib/GPX.h Makefile 33 | $(cc) -c -o SpeedGfx.o SpeedGfx.cpp $(opts) 34 | 35 | SpeedTrkGfx.o : SpeedTrkGfx.cpp SpeedTrkGfx.h Gfx.h \ 36 | ../datalib/Context.h ../datalib/GPVideo.h ../datalib/GPX.h Makefile 37 | $(cc) -c -o SpeedTrkGfx.o SpeedTrkGfx.cpp $(opts) 38 | 39 | TrkStatGfx.o : TrkStatGfx.cpp TrkStatGfx.h Gfx.h ../datalib/Context.h \ 40 | ../datalib/GPVideo.h ../datalib/GPX.h Makefile 41 | $(cc) -c -o TrkStatGfx.o TrkStatGfx.cpp $(opts) 42 | 43 | ../libgfxlib.a : TrkStatGfx.o SpeedTrkGfx.o SpeedGfx.o QualityGfx.o \ 44 | PathGfx.o Gfx.o Export.o AltitudeGfx.o Makefile 45 | ar rcs ../libgfxlib.a TrkStatGfx.o SpeedTrkGfx.o SpeedGfx.o \ 46 | QualityGfx.o PathGfx.o Gfx.o Export.o AltitudeGfx.o 47 | 48 | all: ../libgfxlib.a 49 | -------------------------------------------------------------------------------- /gfxlib/Gfx.h: -------------------------------------------------------------------------------- 1 | /* Gfx.h 2 | * 3 | * mother class for all graphics generation 4 | */ 5 | 6 | #ifndef GFX_H 7 | #define GFX_H 8 | 9 | #include "../datalib/Context.h" 10 | #include "../datalib/GPVideo.h" 11 | #include "../datalib/GPX.h" 12 | 13 | #include 14 | #include 15 | 16 | /* Flags related to GPMFMetersGenerator only */ 17 | extern bool genvideo; 18 | 19 | class Gfx { 20 | protected : 21 | GPVideo &video; // source video 22 | GPX *hiking; // Hiking full trace 23 | 24 | size_t SX, SY; // size of the generated graphics 25 | cairo_surface_t *background; // Background images 26 | 27 | /* Calculate scales to apply to convert 28 | * data to point on the graphics 29 | */ 30 | virtual void calcScales( void ) = 0; 31 | 32 | /* Generate background image. 33 | * It is generated once and reused in every image 34 | */ 35 | virtual void generateBackground( void ); 36 | 37 | /* Generate only one gfx 38 | * -> dir, file : as GenerateAllGfx 39 | * -> index : index of the current sample 40 | * -> current : The data being drawn 41 | */ 42 | virtual void generateOneGfx(const char *dir, char *file, int index, GPMFdata ¤t ) = 0; 43 | 44 | /* Generate the video and remove png 45 | * -> dir, file : as GenerateAllGfx 46 | * -> iname : image filename's root 47 | * -> vname : video's filename 48 | */ 49 | void generateVideo( const char *dir, char *file, const char *iname, const char *vname); 50 | 51 | public: 52 | Gfx( size_t x, size_t y, GPVideo &v, GPX *h ) : video(v), hiking(h), SX(x), SY(y), background(NULL) { 53 | Gfx::generateBackground(); 54 | }; 55 | 56 | ~Gfx(){ 57 | if(this->background) 58 | cairo_surface_destroy(this->background); 59 | } 60 | 61 | /* Generate sticker's video 62 | * -> dir : where to store the result 63 | * -> file : pointer where to put the file name (part of dir) 64 | * 65 | * Notez-bien : GenerateAllGfx() is not called inside the constructor 66 | * to let some change to modify scales if needed and/or target directory 67 | */ 68 | virtual void GenerateAllGfx( const char *dir, char *file ); 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /gfxlib/Gfx.cpp: -------------------------------------------------------------------------------- 1 | /* Gfx 2 | * 3 | * mother class for all graphics generation 4 | */ 5 | 6 | #include "Gfx.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | bool genvideo = true; 14 | 15 | void Gfx::generateBackground( void ){ 16 | this->background = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, this->SX, this->SY); 17 | if(cairo_surface_status(background) != CAIRO_STATUS_SUCCESS){ 18 | puts("*F* Can't create Cairo's surface"); 19 | exit(EXIT_FAILURE); 20 | } 21 | } 22 | 23 | void Gfx::generateVideo( const char *fulltarget, char *filename, const char *iname, const char *vname ){ 24 | *filename = 0; 25 | 26 | static const char *cmd = "ffmpeg -y -framerate 9 -i %s%s%%07d.png -vcodec png %s%s.mov"; 27 | char buf[ strlen(cmd) + 2*strlen(fulltarget) + strlen(iname) + 7 ]; // as '%?' are not subtracted so some security room left 28 | sprintf(buf, "ffmpeg -y -framerate 9 -i %s%s%%07d.png -vcodec png %s%s.mov", 29 | fulltarget, iname, fulltarget, vname 30 | ); 31 | 32 | int ret = system(buf); 33 | if(ret){ 34 | printf("*E* ffmpeg returned %d error code\n", ret); 35 | return; 36 | } 37 | 38 | /* Remove .png */ 39 | DIR *dp; 40 | struct dirent *entry; 41 | 42 | if(!(dp = opendir(fulltarget))){ 43 | printf("*F* cannot open directory: %s\n", fulltarget); 44 | exit(EXIT_FAILURE); 45 | } 46 | 47 | const size_t len = strlen(iname); 48 | while((entry = readdir(dp))){ 49 | if(!strncmp(iname, entry->d_name, len)){ 50 | size_t l = strlen(entry->d_name); 51 | if( l>4 && !strcmp(entry->d_name + l - 4, ".png") ){ 52 | sprintf(buf, "%s%s", fulltarget, entry->d_name); 53 | remove(buf); 54 | } 55 | } 56 | } 57 | 58 | closedir(dp); 59 | } 60 | 61 | /* Note : this one is overloaded for SpeedGfx */ 62 | void Gfx::GenerateAllGfx( const char *fulltarget, char *filename ){ 63 | this->generateBackground(); // Needed for custom background 64 | 65 | for(uint32_t i = 0; i < this->video.getSampleCount(); i++) 66 | generateOneGfx(fulltarget, filename, i, this->video[i]); 67 | 68 | if(verbose) 69 | puts(""); 70 | } 71 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # CHANGES / HISTORY of GPMFMetersGenerator 2 | 3 | If you are updating from a previous version, pay particular attention to the changes with :loudspeaker: sign : they're highlighting an ascendant compatibility break. 4 | 5 | ## 4.xx 6 | 7 | ### 4.06 8 | 9 | Add time when generating GPX from video. 10 | It eases inclusion of data from your GoPro video in a global GPX trace, especially to generate stories. 11 | 12 | ### 4.05 13 | 14 | Add slope grade in speed widget 15 | 16 | ### 4.04 17 | 18 | Smooth speed (average on SAMPLE's values) 19 | Correct "both" speed gfx 20 | 21 | ### 4.03 22 | 23 | Display seconds in trekking statistics 24 | 25 | ### 4.02 26 | 27 | Handle commulative distance on videos only story 28 | 29 | ### 4.01 30 | 31 | Correcte a bug on 1st video generation 32 | 33 | ### 4.00 34 | 35 | Create stories from videos only. 36 | 37 | ## 3.xx 38 | 39 | ### 3.05 40 | 41 | Some GPX editors (like https://gpx.studio/) don't keep the timezone and force 'Z' or ... nothing. 42 | As fallback, try to read the timestamp without timezone. 43 | 44 | ### 3.04 45 | 46 | - Add '**-A**' flag to force GPX data usage on altitude graphic 47 | 48 | ### 3.03 49 | 50 | - mkStory can guess video inclusion based on timestamps 51 | 52 | ### 3.02 53 | 54 | - Add **quality enforcement** flag to remove samples when DoP > 500 55 | 56 | ### 3.01 57 | 58 | - Add **hicking figures** (Distance & Duration) 59 | In a story, figures cover the full journey 60 | 61 | ### 3.00 62 | 63 | - Add **mkStory** 64 | - Add story support in **GPMFMetersGenerator** 65 | - Altitude and path stickers improved when using a story 66 | - Add **GPS quality** sticker 67 | 68 | ## 2.xx 69 | 70 | ### 2.03 71 | 72 | Detect automatically if a video is multi-parts
73 | :loudspeaker: Consequently : **Only one video can be provided** 74 | 75 | Handle AVC encoded video as well (GX and GH videos supported) 76 | 77 | ### 2.02 78 | 79 | Finalize C++ migration : redesign storage class 80 | 81 | ### 2.01 82 | 83 | GPX files are back. 84 | 85 | ### 2.00 86 | 87 | Code has been totally redesigned in C++ to ease implementation of future new features. 88 | As of v2.0, GPX import is temporary disabled. 89 | 90 | :loudspeaker: **:warning: CAUTION :warning: : Arguments changed and are not compatible with 1.xx.** Have a look on `GPMFMetersGenerator -h` for the new syntax. 91 | 92 | ## 1.xx 93 | Changes not tracked 94 | -------------------------------------------------------------------------------- /datalib/GPVideo.h: -------------------------------------------------------------------------------- 1 | /* GPVideo 2 | * Impersonates GoPro video 3 | */ 4 | 5 | #ifndef GPVIDEO_H 6 | #define GPVIDEO_H 7 | 8 | #include "GPSCoordinate.h" 9 | #include "samplesCollection.h" 10 | 11 | #include 12 | 13 | /* default number of samples per seconds (9) */ 14 | #define SAMPLE 9 15 | 16 | struct GPMFdata : public GPSCoordinate { 17 | double spd2d, spd3d; 18 | unsigned char gfix; // GPS fixe 19 | uint16_t dop; // GPS Dilution of Precision 20 | 21 | GPMFdata(){}; 22 | GPMFdata( 23 | double alatitude, double alongitude, 24 | double aaltitude, 25 | double aspd2d, double aspd3d, 26 | time_t asample_time, 27 | unsigned char agfix, 28 | uint16_t adop 29 | ) : GPSCoordinate(alatitude, alongitude, aaltitude, asample_time), 30 | spd2d(aspd2d), spd3d(aspd3d), gfix(agfix), dop(adop){ 31 | } 32 | }; 33 | 34 | class GPVideo : public samplesCollection { 35 | private: 36 | 37 | /* video's */ 38 | size_t mp4handle; 39 | uint32_t fr_num, fr_dem; // Video framerates 40 | 41 | /* GPMF's */ 42 | double nextsample; // timing of the next sample to store 43 | double sample; // Sample rate 44 | 45 | /* Multiparts' */ 46 | double voffset; // part's timing offset 47 | double lastTiming; // last timing 48 | 49 | /* fields to ensure each sample contains a pertinent value 50 | * when stored (e.g : for dop, the maximum value b/w sample) 51 | */ 52 | uint16_t dop; 53 | 54 | /* Accumulation to smooth values */ 55 | double calt; 56 | double cs2d, cs3d; 57 | 58 | unsigned int nbre; 59 | 60 | protected: 61 | void readGPMF( double cumul_dst ); 62 | 63 | /* Add a new sample 64 | * Min and Max are always took in account 65 | * The sample is stored only if its took at least SAMPLE seconds after the 66 | * last stored sample. 67 | */ 68 | double addSample( double sec, double lat, double lgt, double alt, double s2d, double s3d, time_t time, unsigned char gfix, uint16_t dop, double cumul_dst ); 69 | 70 | public: 71 | /* Read and parse 1st video 72 | * fch : first video sample 73 | * sample : number of samples per seconds 74 | * cumul_dst : initial cumulative distance 75 | */ 76 | GPVideo( char *fch, unsigned int sample=SAMPLE, double cumul_dst = 0.0); 77 | 78 | /* Read and parse another part */ 79 | void AddPart( char *, double cumul_dst ); 80 | 81 | void Dump( void ); 82 | }; 83 | #endif 84 | -------------------------------------------------------------------------------- /datalib/GPSCoordinate.h: -------------------------------------------------------------------------------- 1 | /* GPSCoordinate 2 | */ 3 | 4 | #ifndef GPSCOORD_H 5 | #define GPSCOORD_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #define R (6371000) // terrestrial radius 12 | 13 | class GPSCoordinate { 14 | double latitude, longitude; 15 | double altitude; 16 | time_t sample_time; 17 | double cumulative_distance; 18 | 19 | public : 20 | GPSCoordinate(){} 21 | GPSCoordinate( double alat, double along, double alt, time_t ast, double adst = 0): 22 | latitude(alat), longitude(along), altitude(alt), 23 | sample_time(ast), cumulative_distance(adst){} 24 | 25 | void set(double alat, double along, double alt, time_t ast) { 26 | this->latitude = alat; 27 | this->longitude = along; 28 | this->altitude = alt; 29 | this->sample_time = ast; 30 | } 31 | void set( GPSCoordinate &nv ){ 32 | this->latitude = nv.getLatitude(); 33 | this->longitude = nv.getLongitude(); 34 | this->altitude = nv.getAltitude(); 35 | this->sample_time = nv.getSampleTime(); 36 | } 37 | 38 | void setLatitude( double alat ){ this->latitude = alat; } 39 | void setLongitude( double along ){ this->longitude = along; } 40 | void setAltitude( double alt ){ this->altitude = alt; } 41 | void setSampleTime( time_t ast ){ this->sample_time = ast; } 42 | 43 | double addDistance( GPSCoordinate &other ); 44 | double addDistance( double d ){ 45 | return( this->cumulative_distance += d ); 46 | } 47 | 48 | double getLatitude( void ){ return this->latitude; } 49 | double getLongitude( void ){ return this->longitude; } 50 | double getAltitude( void ){ return this->altitude; } 51 | time_t getSampleTime( void ){ return this->sample_time; } 52 | double getCumulativeDistance( void ){ return this->cumulative_distance; } 53 | 54 | struct tm *getGMT( void ){ return gmtime(&this->sample_time); } 55 | struct tm *getLocalTime( void ){ return localtime(&this->sample_time); } 56 | std::string strLocalTime( void ); 57 | std::string strLocalHour( bool HM_only = false ); 58 | std::string diffTime( time_t begin, bool sec = false ); // return the duration begin -> this 59 | double diffTimeF( time_t begin ); 60 | 61 | /* from https://forums.futura-sciences.com/mathematiques-superieur/306536-calcul-de-distance-entre-2-points-dont-jai-coordonnees-geographiques-longitude-latitude.html#post2315609 */ 62 | double Estrangement( GPSCoordinate &other ); 63 | 64 | static double toRadian( double deg ){ return(deg * M_PI/180.0); } 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /datalib/GPX.h: -------------------------------------------------------------------------------- 1 | /* GPX 2 | * Impersonates GPX data and extended Story 3 | */ 4 | 5 | #ifndef GPX_H 6 | #define GPX_H 7 | 8 | #include "GPSCoordinate.h" 9 | #include "samplesCollection.h" 10 | 11 | #include 12 | 13 | struct GpxData : public GPSCoordinate { 14 | GpxData(){}; 15 | GpxData( 16 | double alatitude, double alongitude, 17 | double aaltitude, 18 | time_t asample_time, 19 | double adistance = 0 20 | ) : GPSCoordinate(alatitude, alongitude, aaltitude, asample_time, adistance) { 21 | } 22 | }; 23 | 24 | struct StoryVideo : public std::string { 25 | int start; // indexes of this video in this story 26 | int end; 27 | 28 | StoryVideo( const char *name, int astart, int aend ) : 29 | std::string(name), start(astart), end(aend) {} 30 | 31 | /* check if given index is part of the video 32 | * -1 : before 33 | * 0 : part of it 34 | * 1 : after 35 | */ 36 | int whithin( int idx ){ 37 | if(idx < this->start) 38 | return -1; 39 | else if(idx > this->end) 40 | return 1; 41 | else 42 | return 0; 43 | } 44 | }; 45 | 46 | class GPX : public samplesCollection { 47 | std::vector videos; 48 | int current_video_idx; // Current video index 49 | 50 | void readGPX( const char * ); 51 | void readStory( const char * ); 52 | 53 | void updMinMax( GpxData & ); 54 | 55 | public: 56 | /* Read hiking traces from a GPX file or from a Story file 57 | * -> file : file to be read 58 | * -> story : true if it'a a story file 59 | */ 60 | GPX( const char *, bool story = false ); 61 | 62 | void Dump( void ); 63 | 64 | /* Check if given coordinate is close to the footprint of the hiking. 65 | * It's a very quick and dirty test ... but enough to check a video 66 | * has been took in the same area 67 | * -> proximity_threshold in degree 68 | */ 69 | bool sameArea( GPSCoordinate &, uint32_t proximity_threshold = 0 ); 70 | 71 | bool isStory( void ){ return(!this->videos.empty()); } 72 | 73 | /* Update this->current_video_idx with the index of the given video 74 | * <- false if not found 75 | */ 76 | bool currentVideo(const char *); 77 | 78 | /* Retrieve current video information */ 79 | StoryVideo &getCurrentStoryVideo( void ){ 80 | return this->videos[ this->current_video_idx ]; 81 | } 82 | StoryVideo &getStoryVideo( int idx ){ 83 | return this->videos[ idx ]; 84 | } 85 | int getIndex(void){ 86 | return this->current_video_idx; 87 | } 88 | 89 | enum pkind { 90 | AFTERTRACE=0, // A trace outside any video and after the current video 91 | BEFORETRACE, // A trace outside any video and before the current video 92 | CURRENTVIDEO, // Inside the current video 93 | AFTERVIDEO, // Inside a video after the current one 94 | BEFOREVIDEO // Inside a video before the current one 95 | }; 96 | 97 | enum pkind positionKind(int idx); 98 | }; 99 | 100 | #endif 101 | 102 | -------------------------------------------------------------------------------- /gfxlib/Export.cpp: -------------------------------------------------------------------------------- 1 | /* Export.h 2 | * Export GPMF as standard GPS files 3 | */ 4 | 5 | #include "Export.h" 6 | 7 | #include 8 | 9 | void Export::generateGPX( const char *fulltarget, char *filename, char *iname ){ 10 | sprintf(filename, "%s.gpx", iname); 11 | 12 | FILE *f = fopen(fulltarget,"w"); 13 | if(!f){ 14 | perror(fulltarget); 15 | return; 16 | } 17 | 18 | fputs("\n" 19 | "\n", f); 20 | fprintf(f, "\t\n" 21 | "\t\t%s\n" 22 | "\t\n", iname 23 | ); 24 | fprintf(f, "\t\n" 25 | "\t\t%s\n" 26 | "\t\t\n", iname 27 | ); 28 | 29 | for(auto p : this->video.getSamples()){ 30 | fprintf(f, "\t\t\t\n", p.getLatitude(), p.getLongitude()); 31 | fprintf(f, "\t\t\t\t%f\n", p.getAltitude()); 32 | fprintf(f, "\t\t\t\t\n", p.strLocalTime().c_str()); 33 | fputs("\t\t\t\n", f); 34 | } 35 | 36 | fputs( "\t\t\n" 37 | "\t\n" 38 | "\n", f); 39 | 40 | fclose(f); 41 | 42 | if(verbose) 43 | printf("'%s' generated\n", fulltarget); 44 | } 45 | 46 | void Export::generateKML( const char *fulltarget, char *filename, char *iname ){ 47 | sprintf(filename, "%s.kml", iname); 48 | 49 | FILE *f = fopen(fulltarget,"w"); 50 | if(!f){ 51 | perror(fulltarget); 52 | return; 53 | } 54 | 55 | 56 | fputs("\n" 57 | "\n" 58 | "\t\n" 59 | "\t\ttelemetry\n" 60 | "\t\tCreated by GPMFMetersGenerator\n" 61 | , f); 62 | 63 | /* colors */ 64 | fputs("\t\t\n" 70 | , f); 71 | 72 | fputs("\t\t\n" 78 | , f); 79 | 80 | /* Trace itself */ 81 | fprintf(f, 82 | "\t\t\n" 83 | "\t\t\tTrace\n" 84 | "\t\t\t1\n" 85 | "\t\t\t\n" 86 | "\t\t\t\t1\n" 87 | "\t\t\t\t#trace\n" 88 | "\t\t\t\t%s\n" 89 | "\t\t\t\t\n" 90 | "\t\t\t\t\t1\n" 91 | "\t\t\t\t\tclampToGround\n" 92 | "\t\t\t\t\t" 93 | , iname); 94 | 95 | for(auto p : this->video.getSamples()){ 96 | fprintf(f, "%f,%f,%f\n", p.getLongitude(), p.getLatitude(), p.getAltitude()); 97 | } 98 | 99 | fputs( 100 | "\n" 101 | "\t\t\t\t\n" 102 | "\t\t\t\n" 103 | , f); 104 | 105 | fprintf(f, 106 | "\t\t\t\n" 107 | /* "\t\t\t#depart\n" */ 108 | "\t\t\t\tStarting point\n" 109 | "\t\t\t\t\n" 110 | "\t\t\t\t\t%f,%f\n" 111 | "\t\t\t\t\n" 112 | "\t\t\t\n" 113 | , this->video.getFirst().getLongitude(), this->video.getFirst().getLatitude()); 114 | 115 | fprintf(f, 116 | "\t\t\t\n" 117 | "\t\t\t#End\n" 118 | "\t\t\t\tFinish\n" 119 | "\t\t\t\t\n" 120 | "\t\t\t\t\t%f,%f\n" 121 | "\t\t\t\t\n" 122 | "\t\t\t\n" 123 | , this->video.getLast().getLongitude(), this->video.getLast().getLatitude()); 124 | 125 | fputs( 126 | "\t\t\n" 127 | "\t\n" 128 | "" 129 | , f); 130 | 131 | fclose(f); 132 | 133 | if(verbose) 134 | printf("'%s' generated\n", fulltarget); 135 | } 136 | -------------------------------------------------------------------------------- /gfxlib/SpeedTrkGfx.cpp: -------------------------------------------------------------------------------- 1 | /* AltitudeGraphic 2 | * Generate altitude graphics 3 | */ 4 | #include "SpeedTrkGfx.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | SpeedTrkGfx::SpeedTrkGfx(GPVideo &v, GPX *h, char atype) : Gfx( 600,300, v, h ), type(atype) { 13 | this->calcScales(); 14 | } 15 | 16 | void SpeedTrkGfx::calcScales( void ){ 17 | this->range = (((int)((type == '3') ? this->video.getMax().spd3d : this->video.getMax().spd2d))/10 + 1)*10; 18 | this->scale_h = (double)this->SY/(double)this->range; 19 | this->scale_w = (double)this->SX/(double)this->video.getSampleCount(); 20 | } 21 | 22 | void SpeedTrkGfx::drawGPMF(cairo_t *cr, int offset, uint32_t current){ 23 | if(current == uint32_t(-1)){ /* Drawing shadow */ 24 | cairo_set_source_rgba(cr, 0,0,0, 0.55); 25 | cairo_set_line_width(cr, 5); 26 | } else { /* Drawing curve */ 27 | cairo_set_line_width(cr, 3); 28 | cairo_set_source_rgb(cr, 0.11, 0.65, 0.88); /* Set white color */ 29 | } 30 | 31 | for(uint32_t i = 0; i < this->video.getSampleCount(); i++){ 32 | int x = i*this->scale_w + offset; 33 | int y = this->SY - ((type == '3') ? this->video[i].spd3d : this->video[i].spd2d)*this->scale_h + offset; 34 | 35 | if(!i) /* First plot */ 36 | cairo_move_to(cr, x, y); 37 | else 38 | cairo_line_to(cr, x, y); 39 | 40 | if(current == i){ 41 | cairo_stroke(cr); 42 | cairo_move_to(cr, x, y); 43 | cairo_set_source_rgb(cr, 1,1,1); 44 | } 45 | } 46 | 47 | if(current != uint32_t(-1)) 48 | cairo_stroke(cr); 49 | else 50 | cairo_stroke_preserve(cr); 51 | } 52 | 53 | void SpeedTrkGfx::generateBackground( void ){ 54 | cairo_t *cr = cairo_create(this->background); 55 | 56 | cairo_set_source_rgba(cr, 1,1,1, 0.80); /* Set white color */ 57 | cairo_set_line_width(cr, 1); 58 | 59 | cairo_move_to(cr, 0, 0); 60 | cairo_line_to(cr, 0, this->SY); 61 | 62 | cairo_set_font_size(cr, 27); 63 | for(int i = 0; i <= range; i += 10){ 64 | char t[11]; 65 | sprintf(t, "%d", i); 66 | 67 | int y = this->SY - i*this->scale_h; 68 | 69 | cairo_move_to(cr, 0, y); 70 | cairo_line_to(cr, this->SX, y); 71 | 72 | if(y > 30){ 73 | cairo_move_to(cr, 3, y - 3); 74 | cairo_show_text (cr, t); 75 | } 76 | } 77 | cairo_stroke(cr); 78 | 79 | drawGPMF(cr, 3); // Draw Shadow 80 | 81 | cairo_pattern_t *pat = cairo_pattern_create_linear(0,this->SY - ((type == '3') ? this->video.getMax().spd3d : this->video.getMax().spd2d)*this->scale_h, 0, this->SY); 82 | cairo_pattern_add_color_stop_rgba(pat, 0, 0,0,0, 0.25); 83 | cairo_pattern_add_color_stop_rgba(pat, 1, 0,0,0, 0.05); 84 | cairo_set_source(cr, pat); 85 | 86 | cairo_line_to(cr, this->SX, this->SY); 87 | cairo_line_to(cr, 0, this->SY); 88 | cairo_line_to(cr, 0, this->SY - ((type == '3') ? this->video.getFirst().spd3d : this->video.getFirst().spd2d)*this->scale_h); 89 | 90 | cairo_fill(cr); 91 | 92 | /* Cleaning */ 93 | cairo_pattern_destroy(pat); 94 | cairo_destroy(cr); 95 | 96 | } 97 | 98 | void SpeedTrkGfx::generateOneGfx(const char *fulltarget, char *filename, int index, GPMFdata ¤t){ 99 | 100 | /* 101 | * Initialise Cairo 102 | */ 103 | cairo_status_t err; 104 | 105 | cairo_surface_t *srf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, this->SX, this->SY); 106 | if(cairo_surface_status(srf) != CAIRO_STATUS_SUCCESS){ 107 | puts("*F* Can't create Cairo's surface"); 108 | exit(EXIT_FAILURE); 109 | } 110 | 111 | cairo_t *cr = cairo_create(srf); 112 | 113 | cairo_set_source_surface(cr, this->background, 0, 0); 114 | cairo_rectangle(cr, 0,0, this->SX, this->SY); 115 | cairo_fill(cr); 116 | cairo_stroke(cr); 117 | 118 | /* Draw Altitude curve */ 119 | drawGPMF(cr, 0, index); 120 | 121 | 122 | /* Display the spot */ 123 | cairo_set_line_width(cr, 5); 124 | cairo_arc(cr, index*this->scale_w, this->SY - ((type == '3') ? current.spd3d : current.spd2d)*this->scale_h , 8, 0, 2 * M_PI); 125 | cairo_stroke_preserve(cr); 126 | cairo_set_source_rgb(cr, 0.8, 0.2, 0.2); 127 | cairo_fill(cr); 128 | 129 | /* Writing the image */ 130 | sprintf(filename, "stk%07d.png", index); 131 | if(verbose) 132 | printf("*D* Writing '%s'\r", fulltarget); 133 | 134 | if((err = cairo_surface_write_to_png(srf, fulltarget)) != CAIRO_STATUS_SUCCESS){ 135 | printf("*F* Writing surface : %s / %s\n", cairo_status_to_string(err), strerror(errno)); 136 | exit(EXIT_FAILURE); 137 | } 138 | 139 | /* Cleaning */ 140 | cairo_destroy(cr); 141 | cairo_surface_destroy(srf); 142 | } 143 | 144 | void SpeedTrkGfx::GenerateAllGfx( const char *fulltarget, char *filename ){ 145 | Gfx::GenerateAllGfx( fulltarget, filename ); 146 | 147 | /* Generate video */ 148 | if(genvideo) 149 | generateVideo(fulltarget, filename, "stk", "strack"); 150 | } 151 | -------------------------------------------------------------------------------- /gfxlib/PathGfx.cpp: -------------------------------------------------------------------------------- 1 | /* PathGraphic 2 | * Generate path graphics 3 | * 4 | * Note about stories : 5 | * - background's shadow is not changed 6 | * - traces BEFORE the actual video are in white 7 | * - traces AFTER the actual video remain gray 8 | * - traces of other previous videos are in darker blue 9 | * - traces of the actual video remain blue 10 | * - traces of videos after the current one is in light blue 11 | * 12 | * The algorithm relies on the video to be part of the story 13 | * (otherwise, it is considered as the last) 14 | */ 15 | #include "PathGfx.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | PathGfx::PathGfx(GPVideo &v, GPX *h) : Gfx( 300,300, v, h ) { 24 | this->calcScales(); 25 | } 26 | 27 | /* From https://forums.futura-sciences.com/mathematiques-superieur/39838-conversion-lat-long-x-y.html */ 28 | void PathGfx::posXY( double lat, double lgt, int &x, int &y){ 29 | 30 | /* Degree -> Radian */ 31 | lat = GPSCoordinate::toRadian(lat); 32 | lgt = GPSCoordinate::toRadian(lgt); 33 | 34 | double sinlgt = sin(lgt), 35 | coslgt = cos(lgt), 36 | sintlat = sin(lat), 37 | coslat = cos(lat); 38 | 39 | x = (int)(2 * R * sinlgt*coslat / (1 + coslgt*coslat)); 40 | y = (int)(2 * R * sintlat / (1 + coslgt*coslat)); 41 | } 42 | 43 | void PathGfx::calcScales( void ){ 44 | if(this->hiking){ 45 | this->posXY( this->hiking->getMin().getLatitude(), this->hiking->getMin().getLongitude(), this->min_x, this->min_y); 46 | this->posXY( this->hiking->getMax().getLatitude(), this->hiking->getMax().getLongitude(), this->max_x, this->max_y); 47 | } else { 48 | this->posXY( this->video.getMin().getLatitude(), this->video.getMin().getLongitude(), this->min_x, this->min_y); 49 | this->posXY( this->video.getMax().getLatitude(), this->video.getMax().getLongitude(), this->max_x, this->max_y); 50 | } 51 | 52 | this->range_x = this->max_x - this->min_x; 53 | this->range_y = this->max_y - this->min_y; 54 | 55 | this->scale = (double)(this->SX - 20)/(double)((this->range_x > this->range_y) ? this->range_x : this->range_y); 56 | 57 | this->off_x = (this->SX - this->range_x * this->scale)/2; 58 | this->off_y = (this->SX - this->range_y * this->scale)/2; 59 | } 60 | 61 | void PathGfx::drawGPX(cairo_t *cr, int offset){ 62 | if(offset){ // drawing shadow 63 | cairo_set_line_width(cr, 4); 64 | cairo_set_source_rgba(cr, 0,0,0, 0.55); 65 | } else { // drawing path 66 | cairo_set_source_rgb(cr, 1,1,1); 67 | cairo_set_line_width(cr, 2); 68 | } 69 | 70 | bool first = true; 71 | GPX::pkind prec = GPX::pkind::AFTERTRACE; 72 | 73 | for(int idx=0; idx < (int)this->hiking->getSampleCount(); idx++){ 74 | int x,y; 75 | auto &p = this->hiking->getSamples()[idx]; 76 | 77 | posXY(p.getLatitude(), p.getLongitude(), x, y); 78 | x = this->off_x + (x-this->min_x) * this->scale + offset; 79 | y = this->SY - this->off_y - (y-this->min_y)*this->scale + offset; 80 | 81 | GPX::pkind kind = this->hiking->positionKind(idx); 82 | 83 | if(kind != prec && !offset){ 84 | prec = kind; 85 | if(!first){ 86 | cairo_stroke(cr); // Draw previous trace 87 | first = true; // start a new one 88 | } 89 | switch(kind){ 90 | case GPX::pkind::AFTERTRACE : 91 | cairo_set_source_rgb(cr, .85,.85,.85); 92 | break; 93 | case GPX::pkind::BEFOREVIDEO : 94 | cairo_set_source_rgb(cr, 0.11, 0.65, 0.88); 95 | break; 96 | case GPX::pkind::AFTERVIDEO : 97 | cairo_set_source_rgb(cr, 0.3, 0.4, 0.6); 98 | break; 99 | case GPX::pkind::BEFORETRACE : 100 | case GPX::pkind::CURRENTVIDEO : 101 | cairo_set_source_rgb(cr, 1,1,1); 102 | break; 103 | } 104 | } 105 | if(first){ 106 | first = false; 107 | cairo_move_to(cr, x, y); 108 | } else 109 | cairo_line_to(cr, x, y); 110 | } 111 | cairo_stroke(cr); 112 | } 113 | 114 | void PathGfx::drawGPMF(cairo_t *cr, int offset, uint32_t current){ 115 | cairo_save(cr); 116 | if(current != (uint32_t)-1) 117 | cairo_set_source_rgb(cr, 0.11, 0.65, 0.88); 118 | 119 | for(uint32_t i = 0; i < this->video.getSampleCount(); i++){ 120 | int x,y; 121 | 122 | this->posXY(this->video[i].getLatitude(), this->video[i].getLongitude(), x, y); 123 | x = this->off_x + (x-this->min_x) * this->scale + offset; 124 | y = this->SY - this->off_y - (y-this->min_y) * this->scale + offset; 125 | 126 | if(!i) 127 | cairo_move_to(cr, x, y); 128 | else 129 | cairo_line_to(cr, x, y); 130 | 131 | if(current == i){ 132 | cairo_stroke(cr); 133 | cairo_restore(cr); 134 | cairo_move_to(cr, x, y); 135 | } 136 | } 137 | cairo_stroke(cr); 138 | } 139 | 140 | void PathGfx::generateBackground( void ){ 141 | cairo_t *cr = cairo_create(this->background); 142 | 143 | if(this->hiking){ 144 | drawGPX(cr, 2); // Shadow 145 | drawGPX(cr, 0); // Path 146 | } else { 147 | /* Draw shadow */ 148 | cairo_set_line_width(cr, 4); 149 | cairo_set_source_rgba(cr, 0,0,0, 0.55); 150 | this->drawGPMF(cr, 2); 151 | } 152 | 153 | /* Cleaning */ 154 | cairo_destroy(cr); 155 | } 156 | 157 | void PathGfx::generateOneGfx( const char *fulltarget, char *filename, int index, GPMFdata ¤t ){ 158 | cairo_surface_t *srf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, this->SX, this->SY); 159 | if(cairo_surface_status(srf) != CAIRO_STATUS_SUCCESS){ 160 | puts("*F* Can't create Cairo's surface"); 161 | exit(EXIT_FAILURE); 162 | } 163 | 164 | cairo_t *cr = cairo_create(srf); 165 | cairo_set_source_surface(cr, this->background, 0, 0); 166 | cairo_rectangle(cr, 0, 0, this->SX, this->SY); 167 | cairo_fill(cr); 168 | cairo_stroke(cr); 169 | 170 | if(this->hiking) 171 | cairo_set_source_rgb(cr, 0.3, 0.4, 0.6); 172 | else 173 | cairo_set_source_rgb(cr, 1,1,1); 174 | cairo_set_line_width(cr, 3); 175 | this->drawGPMF(cr, 0, index); 176 | 177 | cairo_set_source_rgb(cr, 1,1,1); 178 | int x,y; 179 | this->posXY(current.getLatitude(), current.getLongitude(), x, y); 180 | x = this->off_x + (x-this->min_x) * this->scale; 181 | y = this->SY - this->off_y - (y-this->min_y) * this->scale; 182 | 183 | cairo_set_line_width(cr, 5); 184 | cairo_arc(cr, x, y, 5, 0, 2 * M_PI); 185 | cairo_stroke_preserve(cr); 186 | cairo_set_source_rgb(cr, 0.8, 0.2, 0.2); 187 | cairo_fill(cr); 188 | 189 | sprintf(filename, "pth%07d.png", index); 190 | if(verbose) 191 | printf("*D* Writing '%s'\r", fulltarget); 192 | 193 | cairo_status_t err; 194 | if((err = cairo_surface_write_to_png(srf, fulltarget)) != CAIRO_STATUS_SUCCESS){ 195 | printf("*F* Writing surface : %s / %s\n", cairo_status_to_string(err), strerror(errno)); 196 | exit(EXIT_FAILURE); 197 | } 198 | 199 | /* Cleaning */ 200 | cairo_destroy(cr); 201 | cairo_surface_destroy(srf); 202 | } 203 | 204 | void PathGfx::GenerateAllGfx( const char *fulltarget, char *filename ){ 205 | Gfx::GenerateAllGfx( fulltarget, filename ); 206 | 207 | /* Generate video */ 208 | if(genvideo) 209 | generateVideo(fulltarget, filename, "pth", "path"); 210 | } 211 | -------------------------------------------------------------------------------- /GPMFMetersGenerator.cpp: -------------------------------------------------------------------------------- 1 | /* GPMFMetersGenerator 2 | * 3 | * Generate images sequence from GPMF' meters 4 | * 5 | * 26/01/2022 - Starting development 6 | * 28/02/2022 - Handle multipart video 7 | * 07/03/2022 - Add GPX generation 8 | * 16/04/2022 - Bump to v1.0 (CAUTION, verbose and debug options changed !) 9 | * 10 | * 09/05/2022 - Switch to v2.0 11 | * 12 | * 23/05/2022 - Switch to v3.0 : adding stories support 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "datalib/Context.h" 26 | #include "datalib/GPVideo.h" 27 | #include "datalib/GPX.h" 28 | 29 | #include "gfxlib/SpeedGfx.h" 30 | #include "gfxlib/AltitudeGfx.h" 31 | #include "gfxlib/PathGfx.h" 32 | #include "gfxlib/SpeedTrkGfx.h" 33 | #include "gfxlib/Export.h" 34 | #include "gfxlib/QualityGfx.h" 35 | #include "gfxlib/TrkStatGfx.h" 36 | 37 | /* Configuration */ 38 | 39 | #include "Version.h" 40 | 41 | /* Which gfx to generate */ 42 | static char gfx_speed = 0; /* 0,2,3,b */ 43 | static char gfx_altitude = 0; /* A or a */ 44 | static bool gfx_maltitude = false; 45 | static bool gfx_path = false; 46 | static char gfx_strk = 0; /* 0,2,3 */ 47 | static bool gfx_GPX = false; 48 | static bool gfx_KML = false; 49 | static bool gfx_quality = false; 50 | static TrekkingStatGfx::gfxtype gfx_trkstt = TrekkingStatGfx::NONE; /* 0: none, 1: HH:MM, 2: HH:MM:SS */ 51 | 52 | GPX *hiking = NULL; // full hiking trace 53 | 54 | int main(int argc, char *argv[]){ 55 | bool force = false; 56 | 57 | /* Reading arguments */ 58 | int opt; 59 | while(( opt = getopt(argc, argv, ":vdhFs:aAlpk:VXKG:S:qtTQ")) != -1) { 60 | switch(opt){ 61 | case 'F': 62 | force = true; 63 | break; 64 | case 'd': // debug implies verbose 65 | debug = true; 66 | case 'v': 67 | verbose = true; 68 | break; 69 | case 'V': 70 | genvideo = false; 71 | break; 72 | case 's': 73 | switch(*optarg){ 74 | case '2': 75 | case '3': 76 | case 'b': 77 | gfx_speed = *optarg; 78 | break; 79 | default : 80 | fputs("*E* Only 2, 3, b are recognized as -s option\n", stderr); 81 | exit(EXIT_FAILURE); 82 | } 83 | break; 84 | case 'a': 85 | gfx_altitude = 'a'; 86 | break; 87 | case 'A': 88 | gfx_altitude = 'A'; 89 | break; 90 | case 'l': 91 | gfx_maltitude = true; 92 | break; 93 | case 'p': 94 | gfx_path = true; 95 | break; 96 | case 't': 97 | gfx_trkstt = TrekkingStatGfx::HM; 98 | break; 99 | case 'T': 100 | gfx_trkstt = TrekkingStatGfx::HMS; 101 | break; 102 | case 'k': 103 | switch(*optarg){ 104 | case '2': 105 | case '3': 106 | gfx_strk = *optarg; 107 | break; 108 | default : 109 | fputs("*E* Only 2, 3 are recognized as -k option\n", stderr); 110 | exit(EXIT_FAILURE); 111 | } 112 | break; 113 | case 'q': 114 | gfx_quality = true; 115 | break; 116 | case 'Q': 117 | enfquality = true; 118 | break; 119 | case 'X': 120 | gfx_GPX = true; 121 | break; 122 | case 'K': 123 | gfx_KML = true; 124 | break; 125 | case 'G': 126 | if(hiking){ 127 | fputs("*F* Only one GPX or Story file can be loaded at a time\n", stderr); 128 | exit(EXIT_FAILURE); 129 | } 130 | hiking = new GPX(optarg); 131 | hiking->Dump(); 132 | break; 133 | case 'S': 134 | if(hiking){ 135 | fputs("*F* Only one GPX or Story file can be loaded at a time\n", stderr); 136 | exit(EXIT_FAILURE); 137 | } 138 | hiking = new GPX(optarg, true); 139 | hiking->Dump(); 140 | break; 141 | case '?': // unknown option 142 | fprintf(stderr, "unknown option: -%c\n", optopt); 143 | case 'h': 144 | case ':': // no argument provided (or missing argument) 145 | if(optopt == 's'){ 146 | gfx_speed = '2'; 147 | break; 148 | } else if(optopt == 'k'){ 149 | gfx_strk = '2'; 150 | break; 151 | } 152 | 153 | puts( 154 | "GPMFMetersGenerator v" VERSION "\n" 155 | "(c) L.Faillie (destroyedlolo) 2022-23\n" 156 | "\nGPMFMetersGenerator [-options] Video.mp4\n" 157 | "\nKnown options :\n" 158 | "-s[2|3|b] : enable speed gfx (default 2d, 3: 3d, b: both)\n" 159 | "-k[2|3] : enable speed tracker gfx (default 2d, 3: 3d)\n" 160 | "-a : enable altitude gfx and draw curve from GoPro video\n" 161 | "-A : enable altitude gfx and draw curve from GPX/story\n" 162 | "-l : enable altitude metric only generation (with -a or -A)\n" 163 | "-p : enable path gfx\n" 164 | "-q : enable quality gfx\n" 165 | "-t : enable Trekking statistics (HH:MM)\n" 166 | "-T : enable Trekking statistics (HH:MM:SS)\n" 167 | "\n" 168 | "-X : export telemetry as GPX file\n" 169 | "-K : export telemetry as KML file\n" 170 | "\n" 171 | "-G : load a GPX file\n" 172 | "-S : load a story file\n" 173 | "\tOnly a GPX or a story can be loadded, not both\n" 174 | "\n" 175 | "-V : Don't generate video, keep PNG files\n" 176 | "-Q : enforce quality by removing samples where GoP is > 500\n" 177 | "-F : don't fail if the target directory exists\n" 178 | "-v : turn verbose on\n" 179 | "-d : turn debugging messages on\n" 180 | ); 181 | exit(EXIT_FAILURE); 182 | break; 183 | } 184 | } 185 | 186 | /* Handle first videos */ 187 | if(optind >= argc){ 188 | fputs("*F* No Video provided\n",stderr); 189 | exit(EXIT_FAILURE); 190 | } 191 | 192 | if(optind != argc-1){ 193 | fputs("*F* Only one video is expected\n",stderr); 194 | exit(EXIT_FAILURE); 195 | } 196 | 197 | /* Determine target directory name from the 1st video */ 198 | size_t len = strlen(argv[optind]) + 4; 199 | if( strlen("img0123456.png") > len ) 200 | len = strlen("img0123456.png"); 201 | len += strlen(argv[optind]) + 1; 202 | char targetDir[ len ]; /* Where files will be created */ 203 | strcpy(targetDir, argv[optind]); 204 | char *targetFile = strrchr(targetDir, '.'); /* target file name */ 205 | if(!targetFile){ 206 | puts("*F* Video filename doesn't have extension"); 207 | exit(EXIT_FAILURE); 208 | } 209 | 210 | 211 | /* Create the target directory */ 212 | *targetFile = 0; 213 | if(!force){ 214 | DIR* dir = opendir(targetDir); 215 | if(dir){ 216 | closedir(dir); 217 | printf("*F* '%s' alreay exists\n", targetDir); 218 | exit(EXIT_FAILURE); 219 | } else if(errno != ENOENT){ 220 | perror(targetDir); 221 | exit(EXIT_FAILURE); 222 | } 223 | } 224 | 225 | if(mkdir(targetDir, S_IRWXU | S_IRWXG | S_IRWXO) == -1){ 226 | if((verbose) && !force) 227 | perror(targetDir); 228 | if(!force) 229 | exit(EXIT_FAILURE); 230 | } 231 | 232 | /* Keep video name */ 233 | char *fname = basename(targetDir); 234 | char vname[strlen(fname) + 1]; 235 | strcpy(vname, fname); 236 | 237 | /* done with directory */ 238 | *(targetFile++) = '/'; 239 | *targetFile = 0; 240 | if(verbose) 241 | printf("*I* images will be generated in '%s'\n", targetDir); 242 | 243 | GPVideo video(argv[optind]); 244 | video.Dump(); 245 | 246 | if(hiking && hiking->isStory()) // Specify the current video 247 | if(!hiking->currentVideo(basename(argv[optind]))) 248 | fprintf(stderr, "*W* '%s' is not part of loaded story\n", basename(argv[optind])); 249 | 250 | /* Generate videos */ 251 | if(gfx_speed){ 252 | SpeedGfx gfx( video, hiking, gfx_speed ); 253 | gfx.GenerateAllGfx(targetDir, targetFile); 254 | } 255 | 256 | if(gfx_altitude){ 257 | if(gfx_altitude=='A' and !hiking){ 258 | fputs("*F* a GPX file or a story is needed with '-A'\n", stderr); 259 | exit(EXIT_FAILURE); 260 | } 261 | 262 | AltitudeGfx gfx( video, hiking, gfx_altitude=='A', gfx_maltitude ); 263 | gfx.GenerateAllGfx(targetDir, targetFile); 264 | } 265 | 266 | if(gfx_quality){ 267 | QualityGfx gfx( video, hiking ); 268 | gfx.GenerateAllGfx(targetDir, targetFile); 269 | } 270 | 271 | if(gfx_path){ 272 | PathGfx gfx( video, hiking ); 273 | gfx.GenerateAllGfx(targetDir, targetFile); 274 | } 275 | 276 | if(gfx_strk){ 277 | SpeedTrkGfx gfx( video, hiking, gfx_strk ); 278 | gfx.GenerateAllGfx(targetDir, targetFile); 279 | } 280 | 281 | if(gfx_trkstt){ 282 | TrekkingStatGfx gfx( video, hiking, gfx_trkstt ); 283 | gfx.GenerateAllGfx(targetDir, targetFile); 284 | } 285 | 286 | if(gfx_GPX){ 287 | Export gfx( video ); 288 | gfx.generateGPX(targetDir, targetFile, vname); 289 | } 290 | 291 | if(gfx_KML){ 292 | Export gfx( video ); 293 | gfx.generateKML(targetDir, targetFile, vname); 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /gfxlib/SpeedGfx.cpp: -------------------------------------------------------------------------------- 1 | /* SpeedGraphic 2 | * Generate speed-o-meter graphics 3 | * 4 | */ 5 | 6 | #include "SpeedGfx.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define LABEL_SZ 55 /* Label font size */ 15 | 16 | SpeedGfx::SpeedGfx(GPVideo &v, GPX *h, char atype) : Gfx( 300,300, v, h ), type(atype) { 17 | this->calcScales(); 18 | } 19 | 20 | double SpeedGfx::transforme( double angle ){ 21 | return -(1.5 * M_PI - angle); 22 | } 23 | 24 | void SpeedGfx::calcScales( void ){ 25 | 26 | /* Scales 27 | * As of speed, graphics's minimal is always 0 28 | */ 29 | if(this->type == 'b'){ 30 | this->range = ((int)(this->video.getMax().spd3d/10) + 1)*10; 31 | int range2 = ((int)(this->video.getMax().spd2d/10) + 1)*10; 32 | if(range2 > this->range) 33 | this->range = range2; 34 | } else 35 | this->range = (((int)((type == '3') ? this->video.getMax().spd3d : this->video.getMax().spd2d))/10 + 1)*10; 36 | this->scale = 3.0/2.0* M_PI/(double)this->range; 37 | 38 | /* Label's offset */ 39 | cairo_t *cr = cairo_create(this->background); 40 | 41 | cairo_text_extents_t extents; 42 | int speed = (int)(log10(range)) + 1; 43 | char t[11], *p = t; 44 | for(;speed; speed--) 45 | *(p++) = '8'; 46 | strcpy(p, ".8"); 47 | 48 | cairo_set_font_size(cr, LABEL_SZ); 49 | cairo_text_extents(cr, t, &extents); 50 | 51 | this->offlabel = (this->SX - extents.x_advance)/2; 52 | 53 | cairo_set_font_size(cr, 30); 54 | cairo_text_extents(cr, "-100%", &extents); 55 | this->offgrade = (this->SX - extents.x_advance)/2; 56 | 57 | cairo_destroy(cr); 58 | } 59 | 60 | void SpeedGfx::generateBackground( void ){ 61 | int speed = (int)(log10(range)) + 1; 62 | 63 | cairo_t *cr = cairo_create(this->background); 64 | 65 | cairo_pattern_t *pat = cairo_pattern_create_radial(this->SX/3,this->SX/3, this->SX/2, 0,0, this->SX/3); 66 | cairo_pattern_add_color_stop_rgba(pat, 0, 0,0,0, 0.15); 67 | cairo_pattern_add_color_stop_rgba(pat, 1, 0.6,0.6,0.6, 0.15); 68 | cairo_set_source(cr, pat); 69 | 70 | cairo_arc(cr, this->SX/2, this->SX/2 , this->SX/2, 0, 2 * M_PI); 71 | cairo_fill(cr); 72 | cairo_stroke(cr); 73 | 74 | cairo_set_source_rgba(cr, 1,1,1, 0.75); 75 | cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 76 | cairo_set_font_size(cr, 30); 77 | cairo_move_to(cr, 200, 250); 78 | cairo_show_text(cr, "km/h"); 79 | cairo_stroke(cr); 80 | 81 | cairo_set_font_size(cr, 27); 82 | 83 | double i; 84 | bool mid; 85 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); 86 | for(i = 0, mid = false, speed = 0; i <= 3 * M_PI/2; i += 3 * M_PI/2/(range/5), mid = mid ? false:true, speed += 5){ 87 | double x = cos( transforme(i) ) * this->SX/2; 88 | double y = sin( transforme(i) ) * this->SX/2; 89 | 90 | if(!mid){ 91 | char t[11]; 92 | cairo_text_extents_t extents; 93 | sprintf(t, "%d", speed); 94 | cairo_text_extents(cr, t, &extents); 95 | cairo_set_source_rgba(cr, 0.90,0.90,0.90, 0.85); 96 | cairo_move_to(cr, 97 | 0.6 * x + this->SX/2 - (extents.width/2 + extents.x_bearing), 98 | 0.6 * y + this->SX/2 - (extents.height/2 + extents.y_bearing) 99 | ); 100 | cairo_show_text (cr, t); 101 | 102 | cairo_set_source_rgba(cr, 0,0,0, 0.75); 103 | cairo_set_line_width(cr, 8); 104 | cairo_move_to(cr, 0.8*x + this->SX/2 + 4, 0.8 * y + this->SX/2 + 4); 105 | cairo_line_to(cr, x + this->SX/2 + 4, y + this->SX/2 + 4); 106 | cairo_stroke(cr); 107 | 108 | cairo_set_source_rgba(cr, 1,1,1, 1.00); 109 | cairo_move_to(cr, 0.8*x + this->SX/2, 0.8 * y + this->SX/2); 110 | } else { 111 | cairo_set_line_width(cr, 3); 112 | cairo_move_to(cr, 0.9*x + this->SX/2, 0.9 * y + this->SX/2); 113 | } 114 | cairo_line_to(cr, x + this->SX/2, y + this->SX/2); 115 | cairo_stroke(cr); 116 | } 117 | cairo_pattern_destroy(pat); 118 | cairo_destroy(cr); 119 | } 120 | 121 | void SpeedGfx::generateOneGfx( const char *fulltarget, char *filename, int index, GPMFdata ¤t, int prc ){ 122 | cairo_status_t err; 123 | 124 | cairo_surface_t *srf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, this->SX, this->SY); 125 | if(cairo_surface_status(srf) != CAIRO_STATUS_SUCCESS){ 126 | puts("*F* Can't create Cairo's surface"); 127 | exit(EXIT_FAILURE); 128 | } 129 | 130 | cairo_t *cr = cairo_create(srf); 131 | 132 | 133 | /* 134 | * Copy background 135 | */ 136 | 137 | cairo_set_source_surface(cr, this->background, 0, 0); 138 | cairo_rectangle(cr, 0,0, this->SX, this->SY); 139 | cairo_fill(cr); 140 | cairo_stroke(cr); 141 | 142 | 143 | /* 144 | * Generate image 145 | */ 146 | 147 | cairo_select_font_face(cr, "sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); 148 | char t[8]; 149 | 150 | cairo_set_font_size(cr, 30); 151 | sprintf(t, "%+4d%%", prc); 152 | 153 | int y = 102 + ((this->type == 'b') ? 0:8); 154 | cairo_set_source_rgb(cr, 0,0,0); /* Background */ 155 | cairo_move_to(cr, this->offgrade + 4, y + 4); 156 | cairo_show_text(cr, t); 157 | 158 | cairo_move_to(cr, this->offgrade, y); 159 | if(prc < 0) 160 | cairo_set_source_rgb(cr, 0.2, 0.9, 0.2); 161 | else if(prc > 0) 162 | cairo_set_source_rgb(cr, 0.9, 0.6, 0.2); 163 | else 164 | cairo_set_source_rgb(cr, 1,1,1); 165 | cairo_show_text(cr, t); 166 | cairo_stroke(cr); 167 | 168 | cairo_set_font_size(cr, LABEL_SZ); 169 | sprintf(t, "%4.1f", (type == '3'? current.spd3d : current.spd2d)); 170 | 171 | if(this->type == 'b') 172 | cairo_set_source_rgb(cr, 0.8, 0.2, 0.2); 173 | else { 174 | cairo_set_source_rgb(cr, 0,0,0); /* Background */ 175 | cairo_move_to(cr, this->offlabel + 4, 180 + 4); 176 | cairo_show_text(cr, t); 177 | 178 | cairo_set_source_rgb(cr, 1,1,1); /* Foreground */ 179 | } 180 | cairo_move_to(cr, this->offlabel, 180 - ((this->type == 'b') ? 25:0)); 181 | cairo_show_text(cr, t); 182 | cairo_stroke(cr); 183 | 184 | if(this->type == 'b'){ 185 | cairo_set_source_rgb(cr, 0.2, 0.8, 0.2); 186 | sprintf(t, "%4.1f", current.spd3d); 187 | cairo_move_to(cr, this->offlabel, 200); 188 | cairo_show_text(cr, t); 189 | cairo_stroke(cr); 190 | } 191 | 192 | double val; 193 | if(this->type == 'b'){ 194 | val = this->transforme(current.spd3d * scale); 195 | 196 | cairo_set_line_width(cr, 13); 197 | cairo_set_source_rgba(cr, 0.3, 0.6, 0.4, 0.75); 198 | cairo_arc(cr, this->SX/2, this->SY/2 , this->SX/2 - 35, transforme(0), val ); 199 | cairo_stroke(cr); 200 | 201 | cairo_set_source_rgb(cr, 1,1,1); 202 | cairo_set_line_width(cr, 10); 203 | cairo_arc(cr, cos( val ) * (this->SX/2 - 35) + this->SX/2, sin( val ) * (this->SX/2 - 35) + this->SX/2, 10, 0, 2 * M_PI); 204 | cairo_stroke_preserve(cr); 205 | cairo_set_source_rgb(cr, 0.2, 0.8, 0.2); 206 | cairo_fill(cr); 207 | cairo_stroke(cr); 208 | } 209 | 210 | val = this->transforme(((this->type == '3') ? current.spd3d : current.spd2d) * scale); 211 | 212 | cairo_set_line_width(cr, 13); 213 | cairo_set_source_rgba(cr, 0.3, 0.4, 0.6, 0.75); 214 | cairo_arc(cr, this->SX/2, this->SY/2 , this->SX/2 - 15, this->transforme(0), val ); 215 | cairo_stroke(cr); 216 | 217 | cairo_set_source_rgb(cr, 1,1,1); 218 | cairo_set_line_width(cr, 10); 219 | cairo_arc(cr, cos( val ) * (this->SX/2 - 15) + this->SX/2, sin( val ) * (this->SY/2 - 15) + this->SY/2, 10, 0, 2 * M_PI); 220 | cairo_stroke_preserve(cr); 221 | cairo_set_source_rgb(cr, 0.8, 0.2, 0.2); 222 | cairo_fill(cr); 223 | cairo_stroke(cr); 224 | 225 | sprintf(filename, "spd%07d.png", index); 226 | if(verbose) 227 | printf("*D* Writing '%s'\r", fulltarget); 228 | 229 | if((err = cairo_surface_write_to_png(srf, fulltarget)) != CAIRO_STATUS_SUCCESS){ 230 | printf("*F* Writing surface : %s / %s\n", cairo_status_to_string(err), strerror(errno)); 231 | exit(EXIT_FAILURE); 232 | } 233 | 234 | /* Cleaning */ 235 | cairo_destroy(cr); 236 | cairo_surface_destroy(srf); 237 | } 238 | 239 | void SpeedGfx::GenerateAllGfx( const char *fulltarget, char *filename ){ 240 | this->generateBackground(); // Needed for custom background 241 | 242 | uint32_t start = 0; 243 | int prc = 0; 244 | for(uint32_t i = 0; i < this->video.getSampleCount(); i++){ 245 | if(i && this->video[i].diffTimeF(this->video[start].getSampleTime()) >= 0.5 ){ 246 | double dst = this->video[i].Estrangement(this->video[start]); // covered distance 247 | double diffalt = this->video[i].getAltitude() - this->video[start].getAltitude(); // altitude delta 248 | 249 | if(dst && this->video[i].spd2d > 2) 250 | prc = (int)(diffalt/dst*100); 251 | else 252 | prc = 0; 253 | 254 | start = i; 255 | } 256 | this->generateOneGfx(fulltarget, filename, i, this->video[i], prc); 257 | } 258 | 259 | if(verbose) 260 | puts(""); 261 | 262 | /* Generate video */ 263 | if(genvideo) 264 | generateVideo(fulltarget, filename, "spd", "speed"); 265 | } 266 | -------------------------------------------------------------------------------- /datalib/GPX.cpp: -------------------------------------------------------------------------------- 1 | /* GPX 2 | * Impersonates GPX data 3 | * 4 | * DEV-NOTE : a clean way should have been to use libxml++ (or at least 5 | * libxml2) in SAX mode. 6 | * Unfortunately, I didn't found a way to use-it in a portable way without 7 | * using autoconf. Consequently, I decided for the moment to keep my 8 | * quick and (very) dirty v1.0 XML parser which is enough to parse 9 | * CORRECTLY FORMATED GPX files. 10 | */ 11 | 12 | #include "../datalib/Context.h" 13 | #include "GPX.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define BUFFLEN 1024 22 | 23 | char buff[BUFFLEN]; 24 | char *pbuff; /* Pointer inside the buffer */ 25 | 26 | /* clear the buffer */ 27 | static void clear(){ 28 | pbuff = buff; 29 | *pbuff = 0; 30 | } 31 | 32 | /* skip all char up incomming char (mostly for '<' and '>') 33 | * 34 | * <- false in case of error or end of file 35 | */ 36 | static bool skipTocChar(FILE *f, char c){ 37 | while((*pbuff = fgetc(f)) != c){ 38 | if(feof(f)) 39 | return false; 40 | } 41 | 42 | pbuff++; 43 | return true; 44 | } 45 | 46 | /* read and store characters up to c 47 | * 48 | * <- false : end of file reached 49 | */ 50 | static bool readUptoChar(FILE *f, char c){ 51 | while((*(pbuff++) = fgetc(f)) !=c){ 52 | if(feof(f)) 53 | return false; 54 | if(pbuff - buff >= BUFFLEN){ 55 | puts("*F* string too long"); 56 | exit(EXIT_FAILURE); 57 | } 58 | } 59 | 60 | *pbuff = 0; 61 | return true; 62 | } 63 | 64 | /* Look for "string" 65 | * 66 | * <- false : end of file 67 | */ 68 | static bool lookFor(FILE *f, const char *string){ 69 | while(true){ 70 | const char *s = string; 71 | 72 | clear(); 73 | 74 | if(!skipTocChar(f,*string)) /* Looks for the 1st char */ 75 | return false; 76 | 77 | for(s = string+1; *s; s++) /* reads remaining */ 78 | if((*(pbuff++) = fgetc(f)) != *s){ 79 | if(feof(f)) 80 | return false; 81 | else 82 | break; 83 | } 84 | 85 | if(!*s) /* string found */ 86 | break; 87 | } 88 | 89 | *pbuff = 0; 90 | return true; 91 | } 92 | 93 | void GPX::Dump( void ){ 94 | printf("*I* GPX min/max: (%u samples)\n", this->getSampleCount()); 95 | if(!this->getSampleCount()){ 96 | puts("*E* No sample recognised : wrong file format ?"); 97 | exit(EXIT_FAILURE); 98 | } 99 | 100 | printf("\tlatitude : %f -> %f (%f)\n", this->getMin().getLatitude(), this->getMax().getLatitude(), this->getMax().getLatitude() - this->getMin().getLatitude()); 101 | printf("\tlongitude : %f -> %f (%f)\n", this->getMin().getLongitude(), this->getMax().getLongitude(), this->getMax().getLongitude() - this->getMin().getLongitude()); 102 | printf("\taltitude: %f -> %f (%f)\n", this->getMin().getAltitude(), this->getMax().getAltitude(), this->getMax().getAltitude() - this->getMin().getAltitude()); 103 | printf("\tDistance covered : %f m\n", this->getLast().getCumulativeDistance() ); 104 | 105 | printf("\tTime : "); 106 | printtm(this->getMin().getGMT()); 107 | printf(" -> "); 108 | printtm(this->getMax().getGMT()); 109 | puts(""); 110 | 111 | if(this->isStory()) 112 | printf("\tIt's a story with %lu videos loaded\n", this->videos.size()); 113 | 114 | if(debug){ 115 | if(this->isStory()){ 116 | puts("\tStory's videos :"); 117 | for( auto v: this->videos ) 118 | printf("\t\t%s : %05d -> %05d\n", v.c_str(), v.start, v.end); 119 | } 120 | 121 | puts("*D* Memorized CPX data"); 122 | for(auto p : samples){ 123 | printf("\tLatitude : %.3f deg\n", p.getLatitude()); 124 | printf("\tLongitude : %.3f deg\n", p.getLongitude()); 125 | printf("\tAltitude : %.3f m\n", p.getAltitude()); 126 | printf("\tCom distance : %.0f m\n", p.getCumulativeDistance()); 127 | 128 | printf("\tTime : "); 129 | printtm(p.getGMT()); 130 | puts(""); 131 | } 132 | } 133 | 134 | printf("*I* %u memorised GPX\n", this->getSampleCount()); 135 | } 136 | 137 | void GPX::updMinMax( GpxData &nv ){ 138 | if(!this->getSampleCount()){ 139 | this->getMin().set( static_cast(nv) ); 140 | this->getMax().set( static_cast(nv) ); 141 | } else { 142 | if(nv.getLatitude() < this->getMin().getLatitude()) 143 | this->getMin().setLatitude(nv.getLatitude()); 144 | if(nv.getLatitude() > this->getMax().getLatitude()) 145 | this->getMax().setLatitude(nv.getLatitude()); 146 | 147 | if(nv.getLongitude() < this->getMin().getLongitude()) 148 | this->getMin().setLongitude(nv.getLongitude()); 149 | if(nv.getLongitude() > this->getMax().getLongitude()) 150 | this->getMax().setLongitude(nv.getLongitude()); 151 | 152 | if(nv.getAltitude() < this->getMin().getAltitude()) 153 | this->getMin().setAltitude(nv.getAltitude()); 154 | if(nv.getAltitude() > this->getMax().getAltitude()) 155 | this->getMax().setAltitude(nv.getAltitude()); 156 | 157 | if(this->getMin().getSampleTime() > nv.getSampleTime()) 158 | this->getMin().setSampleTime(nv.getSampleTime()); 159 | if(this->getMax().getSampleTime() < nv.getSampleTime()) 160 | this->getMax().setSampleTime(nv.getSampleTime()); 161 | } 162 | } 163 | 164 | void GPX::readGPX( const char *file ){ 165 | FILE *f; 166 | 167 | if(!(f = fopen(file, "r"))){ 168 | perror(file); 169 | exit(EXIT_FAILURE); 170 | } 171 | 172 | while(true){ 173 | double lat=NAN, lgt=NAN, alt=NAN; 174 | 175 | /* Read position */ 176 | 177 | if(!lookFor(f,"')) // read the full tag 181 | break; 182 | 183 | if(sscanf(buff, "", &lat, &lgt) != 2) 184 | puts("*E* can't read values"); 185 | 186 | /* Read elevation */ 187 | if(!lookFor(f,"")){ 188 | puts("*F* No elevation"); 189 | exit(EXIT_FAILURE); 190 | } 191 | 192 | if(!readUptoChar(f, '<')){ /* read up to closing tag */ 193 | puts("*F* malformed elevation"); 194 | exit(EXIT_FAILURE); 195 | } 196 | 197 | if(sscanf(buff, "%lf<", &alt) != 1) 198 | puts("*E* can't read altitude values"); 199 | 200 | 201 | /* Read timestamp */ 202 | if(!lookFor(f,"