1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2025-07-01 21:39:15 +02:00

Compare commits

...

225 Commits
3.0 ... 4.8

Author SHA1 Message Date
9e798e626c Decreased outlier detection window 2017-05-25 01:55:09 +02:00
cb70f0dded Merge pull request #27 from eson57/patch-1
Update gpxsee_sv.ts
2017-05-23 23:30:38 +02:00
400003f684 Update gpxsee_sv.ts 2017-05-23 21:50:21 +02:00
69db510b10 Decreased default speed filter value 2017-05-23 16:06:05 +02:00
cd9ea0f8a9 Unified settingsorder 2017-05-23 13:43:40 +02:00
0f6c8eb896 Code cleanup 2017-05-23 13:23:14 +02:00
6b3c0d05ca Merge branch 'master' of https://github.com/tumic0/GPXSee 2017-05-23 13:22:41 +02:00
b0cc028c4c Localization update 2017-05-23 13:03:28 +02:00
1513130435 Settings polishing on OS X 2017-05-23 12:50:46 +02:00
ec798451e8 Fixed POI radius setting handling 2017-05-23 00:26:59 +02:00
ffc61552d2 Code cleanup 2017-05-22 23:42:15 +02:00
aff7b1c13f Allow only odd window sizes for moving average filter 2017-05-22 23:41:01 +02:00
d54f22d3f0 Fixed crash with 0 window width 2017-05-22 22:37:35 +02:00
dba6291f3e Better (consistent) graph definition 2017-05-22 18:42:23 +02:00
e96cee2ea8 Improved filtering settings layout 2017-05-22 17:04:37 +02:00
1eaaea98a3 Version++ 2017-05-22 16:12:30 +02:00
a90073c422 Added missing icon 2017-05-22 16:10:44 +02:00
223a13583c Added data filtering settings 2017-05-22 14:54:22 +02:00
4386e1f2e1 Code cleanup 2017-05-16 13:18:57 +02:00
5bfe9c2527 Added missing ellipses 2017-05-16 12:59:59 +02:00
efa2b1c366 Code cleanup 2017-05-16 12:59:40 +02:00
e0ad64a902 Added missing units type setup for new items 2017-05-16 11:51:48 +02:00
f0a1acb96a Added missing libgeotrans Source Code Disclaimer 2017-05-08 20:39:42 +02:00
5f3bdb87f8 Added support for British, Irish and Swedish grid projections 2017-05-08 20:17:14 +02:00
90d062e097 Switched to latitude origin capable transverse mercator implementation 2017-05-08 19:53:50 +02:00
58b4c87d46 Fixed broken southern hemisphere reference points conversion
Fixed broken UTM zone setup
Added New Zealand TM 2000 "projection" support
2017-05-06 00:53:39 +02:00
02654ec862 Removed obsolete check 2017-05-05 09:09:32 +02:00
ff015c8714 Added missing data validity checks 2017-05-05 09:00:57 +02:00
c339116cd1 Added support for Albers Equal-Area projection 2017-05-04 20:25:47 +02:00
d8d70bfd8b Added missing MB->kB conversion 2017-05-04 00:04:28 +02:00
7d8149ace3 Fixed typo 2017-05-04 00:01:27 +02:00
4460b454c4 Added pixmap cache configuration to options. 2017-05-03 23:55:16 +02:00
211310f377 Version++ 2017-05-03 22:00:10 +02:00
1e4bcd0ac1 Added support for OZF3 map files 2017-05-03 21:34:13 +02:00
1a66ed0a36 Use the proper layer when displaying a single waypoint on an atlas. 2017-05-01 22:49:01 +02:00
273a127069 Version++ 2017-05-01 13:04:15 +02:00
1c0a0fd0b3 Fixed broken print/PDF export
(wrong map scale/waypoints size)
2017-05-01 12:59:56 +02:00
713e331b2a Some more OZFx extensions 2017-04-30 18:27:12 +02:00
96f406aad7 Removed Thunderforest map (requires API key)
Added USGS maps (topo & imagery)
2017-04-30 13:21:32 +02:00
0aedec66c4 Fixed atlas ll2xy() caching 2017-04-30 00:19:53 +02:00
b500031713 Atlas ll2xy() optimization 2017-04-29 23:15:44 +02:00
699e4f32d5 Version++ 2017-04-23 18:15:08 +02:00
992c8aaaf9 Improved error reporting 2017-04-23 13:37:17 +02:00
68a72c5809 Added proper OZF files detection 2017-04-23 12:49:40 +02:00
eace308774 Properly open all selected files (tar content/atlas structure is however still case sensitive) 2017-04-23 12:26:01 +02:00
e4e3f7d143 Added missing atlas maps unloading on atlas unload 2017-04-22 10:19:02 +02:00
acc49d015e Use only the "Map" word for maps/atlases in the GUI 2017-04-21 21:57:29 +02:00
b48652cb78 Localization update 2017-04-21 21:25:56 +02:00
0808f6679e Added "runtime" offline map loading
Offline map loading & error handling cleanup
2017-04-21 21:15:58 +02:00
610ac3d73f Fixed atlas zoom fitting algorithm 2017-04-19 00:07:01 +02:00
1d719fdcc8 Localization files update 2017-04-17 22:13:30 +02:00
1746e1cf08 Merge pull request #25 from mkroehnert/german-translation
add german translation
2017-04-17 21:58:05 +02:00
5d5a5365cd add german translation 2017-04-17 20:56:56 +02:00
7dfdeac1da Version++ 2017-04-17 19:57:52 +02:00
1ff13ee9f8 Added datum files to packages 2017-04-17 19:56:29 +02:00
34b1fb6b5d Cosmetics 2017-04-17 19:37:03 +02:00
02bf85780a Improved OZF files handling. 2017-04-17 19:34:44 +02:00
47199348d8 Improved error reporting 2017-04-17 19:15:20 +02:00
3b55bc0efc Added missing Lat/Lon based calibration points conversion to WGS84 2017-04-17 19:01:01 +02:00
4de3ddf71b Switched from hardcoded values to CSV files for datum definitions.
Non-WGS84 maps now fit correctly when defined using map grid.
2017-04-17 18:03:04 +02:00
e26fa92ce6 Code cleanup 2017-04-15 13:13:26 +02:00
dfb69b2755 Yet another error handling fix. 2017-04-15 11:47:30 +02:00
c2a30738cb Update README.md 2017-04-15 09:11:16 +02:00
fd53e89ea5 Improved error handling 2017-04-15 08:59:31 +02:00
ccf91bb29f Added support for ozf2 files 2017-04-14 22:39:33 +02:00
75bd388be0 Try to load maps with incorrect (windows absolute) image paths. 2017-04-13 20:00:27 +02:00
aef0357204 Version++ 2017-04-12 18:19:03 +02:00
d073d606d0 Added missing cache reset (causing huge redraw rects causing system memory exhaustion) 2017-04-12 18:11:53 +02:00
8822bf7e5f Code cleanup 2017-04-09 10:26:09 +02:00
051a3ed303 Replaced wrong NAD27 ellipsoid with the correct one 2017-04-07 18:19:56 +02:00
96437ae6ab Cosmetics 2017-04-07 10:16:14 +02:00
89c25bbc3a Update README.md 2017-04-07 10:02:41 +02:00
322792ea04 Improved digital zoom/fullscreen interaction 2017-04-06 19:54:50 +02:00
ce1c76a315 translation cleanup 2017-04-06 09:11:36 +02:00
804e9594ad Merge pull request #21 from eson57/patch-4
Update gpxsee_sv.ts
2017-04-06 09:10:13 +02:00
651973c524 Merge branch 'master' into patch-4 2017-04-06 09:09:51 +02:00
533af66080 Fixed typo 2017-04-06 09:08:25 +02:00
bab5750cf7 Update gpxsee_sv.ts 2017-04-06 06:36:18 +02:00
0d6f3ea162 Version++ 2017-04-05 22:56:37 +02:00
b915302e2f Localization update 2017-04-05 22:53:44 +02:00
03ef7a9147 Added digital zoom 2017-04-05 22:53:25 +02:00
33cb944e36 Code cleanup/optimization 2017-04-04 22:03:42 +02:00
8bc575aef2 Fixed map fitting algorithm 2017-04-04 01:19:17 +02:00
f0b7abdb72 Added SAD69 ellipsoid 2017-04-03 20:59:17 +02:00
6ce14734cd Use ellipsoids defined by the map file for the projection computation 2017-04-03 20:29:35 +02:00
925a0e2951 version++ 2017-04-02 22:19:24 +02:00
e46bba18f2 Optimized projection computation 2017-04-02 22:18:03 +02:00
a56aa4e706 Added support for Lambert Conformal Conic projection 2017-04-02 22:17:16 +02:00
c4b3a81b0b Make coverity happy 2017-04-02 09:32:40 +02:00
957cc6f4f3 Code cleanup 2017-04-02 00:39:12 +02:00
ebad0832ee Update README.md 2017-04-01 19:48:39 +02:00
186c135ad5 Update README.md 2017-04-01 19:47:14 +02:00
72b5b1ea97 Update README.md 2017-04-01 19:46:11 +02:00
106d4c6e0b Installer polishing 2017-04-01 18:02:34 +02:00
8f815a6af0 Missing file changes... 2017-04-01 16:55:46 +02:00
60f107d7cd Added possibility to compute the UTM zone from lat/lon reference points 2017-04-01 16:25:33 +02:00
e87fff4bf8 Added UTM projection support
Fixed broken map fitting
2017-04-01 15:58:32 +02:00
e6547d92f0 Fixed zoom speed issue on touchpads 2017-04-01 06:56:50 +02:00
bfcc577f64 Added missing destructor 2017-04-01 06:10:36 +02:00
a56ad8a933 Code cleanup/optimizations 2017-04-01 05:59:55 +02:00
b1748c848b Fixed broken atlas maps fitting in non-orthogonal projections. 2017-03-31 19:18:01 +02:00
3763d44662 Added Latitude/Longitude projection 2017-03-29 22:51:32 +02:00
b3e8081942 Added support for Transversal Mercator projection 2017-03-29 00:17:47 +02:00
b3dc886afc Localization update 2017-03-27 23:59:26 +02:00
431002fd62 Added offline map info to data sources info page 2017-03-27 23:58:36 +02:00
63c3a50ca6 Fixed scene (map) bounds not updating issue
Code cleanup
2017-03-27 23:52:24 +02:00
7fd30bbda3 Improved error handling 2017-03-27 10:31:41 +02:00
c1844f9557 Atlas loading optimization 2017-03-27 02:41:30 +02:00
0c7601c831 Fixed TAR parser 2017-03-26 18:08:37 +02:00
19eb3fba7f Merge branch 'master' of https://github.com/tumic0/GPXSee 2017-03-26 15:33:49 +02:00
bde3c8cc3d Added support for Trekbuddy atlases 2017-03-26 15:32:55 +02:00
b0ce471ea8 Markup syntax fix 2017-03-24 22:53:26 +01:00
bd23120c2c Improved image/tiles info fetching 2017-03-21 20:51:23 +01:00
ac4ef0631e Do not depend on a specific tile name when gathering tile info. 2017-03-21 19:02:29 +01:00
60d9a172b6 Refactoring 2017-03-21 09:27:44 +01:00
b8eede21c0 Improved tared maps scrolling performance 2017-03-21 09:01:30 +01:00
70ddbe192f Added support for tared maps 2017-03-21 01:15:29 +01:00
d8477571cc Added missing coordinates transformation 2017-03-20 22:52:39 +01:00
92545acba0 Added support for tiled offline maps 2017-03-20 10:05:07 +01:00
2bf66ea912 Tile loading code cleanup 2017-03-20 09:25:05 +01:00
b21c2267cf Added support for OziExplorer offline maps 2017-03-18 01:30:31 +01:00
278aa904c1 Human-friendly links 2017-03-05 10:39:50 +01:00
b369985f0a Updated Linux packages download URL 2017-03-05 10:35:19 +01:00
6a5781771f Updated program screenshot 2017-02-28 11:53:25 +01:00
1b616f3c81 Added installer executable description 2017-02-16 18:59:37 +01:00
99dfbc1d2f Added Windows installer code sign certificate 2017-02-13 23:58:19 +01:00
f83e3376b4 Version++ 2017-02-13 20:17:05 +01:00
311aeecb27 Fixed broken OpenSSL/ANGLE install when MSVC runtime is installed. 2017-02-13 20:13:13 +01:00
338ace6dff Added missing inicialization 2017-02-13 20:12:48 +01:00
51e9ef4416 Merge pull request #20 from eson57/patch-3
Update gpxsee_sv.ts
2017-02-13 10:19:29 +01:00
ebefe54510 Update gpxsee_sv.ts 2017-02-13 04:19:23 +01:00
1ec732c78d Version++ 2017-02-12 20:13:04 +01:00
ff733b2705 Indicate moving time in status bar 2017-02-12 20:11:36 +01:00
99e32b1a15 Made the moving time switch affect all related values
More standardized time type switching (menu)
2017-02-12 19:57:55 +01:00
f4a992a66f Added missing background color definition (OpenGL issue fix) 2017-02-12 17:49:54 +01:00
f52fa9a9ef Removed obsolete stuff 2017-02-12 17:38:20 +01:00
61f3a1c932 Added OpenSSL to Windows builds
Skipped Windows XP support in Windows builds
2017-02-12 17:34:13 +01:00
93313da01d Fixed QNetworkManager related crashes on program termination. 2017-02-07 23:36:06 +01:00
a253ac1796 Localization update 2017-02-05 16:10:42 +01:00
e6a0eeefcc Better Time/Moving Time display approach. 2017-02-05 16:01:54 +01:00
214d7c40dc Added missing coordinates bounding rectangle normalization
(Fixes issue #19)
2017-02-03 00:54:57 +01:00
cd89706d74 Do not use hardcoded control keys info 2017-02-02 20:06:17 +01:00
961eac9324 Added moving time info.
Added special track data processing for "pause states".
2017-01-31 09:37:01 +01:00
ddacac8d2e Added Open Topo Map to default map sources 2017-01-26 09:46:36 +01:00
bd9b5dbc6a Merge pull request #17 from eson57/patch-2
Update gpxsee_sv.ts
2017-01-24 23:57:29 +01:00
8b813b5879 Update gpxsee_sv.ts 2017-01-24 21:32:49 +01:00
b79de29464 Added missing swedish string 2017-01-24 18:30:00 +01:00
ae2765528d Updated obsolete strings 2017-01-24 18:23:56 +01:00
0356917790 Version++ 2017-01-24 18:12:08 +01:00
7352b24473 Added missing item update on content change 2017-01-24 18:11:08 +01:00
3a9ec6247c Removed Czech strings from Swedish translation 2017-01-23 10:01:27 +01:00
e74ac78138 Added missing swedish translation resource 2017-01-23 00:08:10 +01:00
5fa91be4ac Localization update 2017-01-23 00:03:48 +01:00
536b4fd121 Added swedish translation 2017-01-23 00:02:16 +01:00
0556ae0f58 Merge pull request #16 from eson57/patch-1
Swedish translation
2017-01-22 23:51:41 +01:00
e38fdb26d7 Version 3.7 2017-01-22 22:54:42 +01:00
e420602c69 Fixed scene bounding rect reset 2017-01-22 22:53:38 +01:00
b2a49eaa23 Version++ 2017-01-22 22:14:59 +01:00
211a4e4cef Create gpxsee_sv.ts 2017-01-21 09:49:51 +01:00
8ff8d4bf16 Code cleanup 2017-01-20 01:17:22 +01:00
46598a85fc Fixed redirect loop handling logic 2017-01-17 10:37:02 +01:00
32cbd33c91 Fixed tile area computation 2017-01-16 21:45:50 +01:00
0e356d0222 Added redirect loop check 2017-01-16 21:45:27 +01:00
1a29ab6304 Enabled free map scroll. 2017-01-16 09:54:12 +01:00
5581cff55b Fixed download error handling in case of redirects
Do not download tiles multiple times.
2017-01-16 09:53:01 +01:00
a458b82e37 Code cleanup 2017-01-11 22:14:01 +01:00
bd946fb477 Fixed bounding rect computation in special case (rect + 1 waypoint) 2017-01-09 00:20:06 +01:00
7e6ed0933c Code cleanup 2017-01-08 20:02:51 +01:00
1586a5e912 Optimization 2017-01-07 22:20:04 +01:00
3e340ab941 Improved error reporting.
Cosmetics.
2017-01-02 23:01:50 +01:00
ea178d1acb Fixed benign memory leak 2017-01-02 09:42:34 +01:00
f61488fcfa Version++ 2016-12-20 08:39:40 +01:00
b68ca92add OpenGL code cleanup 2016-12-20 00:11:30 +01:00
0448ae5eea Added basic print/export formating options 2016-12-16 02:30:58 +01:00
2bc112c7b4 Fixed broken latitude handling 2016-12-13 18:54:58 +01:00
ccd92edb8d More code cleanup 2016-12-10 11:52:18 +01:00
0cbf79870b Code cleanup 2016-12-10 11:50:53 +01:00
7e8530555d Unified AA samples count for QGLFormat and QSurfaceFormat. 2016-12-10 11:45:16 +01:00
2fd16e5e31 Version++ 2016-12-10 01:20:17 +01:00
a5033c8b19 Fixed QOpenGLWidget usage (wrong minimal QT version) 2016-12-10 01:18:31 +01:00
b0c6176ddd Fixed switched default values 2016-12-08 00:28:44 +01:00
6edd7a8c61 Code cleanup 2016-12-07 22:01:45 +01:00
8a299be65b Translations 2016-12-07 22:01:33 +01:00
cde4c65c53 GUI polishing 2016-12-07 21:38:36 +01:00
b24136a580 Use OpenGL also for graph views when selected 2016-12-07 02:02:39 +01:00
da1b2bb90a Use a reasonable number of samples per pixel 2016-12-07 00:54:14 +01:00
30e198cf46 Cache the graph grid 2016-12-07 00:44:32 +01:00
0f2deca4fa Fixed broken graph width setting 2016-12-07 00:03:36 +01:00
1055c4fd98 Added missing OpenGL related libs to Windows package.
Version++.
2016-12-06 22:05:26 +01:00
18c501b610 Removed path item caching (made obsolete by OpenGL rendering) 2016-12-06 21:28:45 +01:00
71310116e3 Cosmetics 2016-12-06 21:03:19 +01:00
34e48199b2 Fixed options/settings handling isses. 2016-12-06 21:02:44 +01:00
4cf027df58 Fixed widget rendering issues 2016-12-06 21:01:06 +01:00
a9668ca86e Added options dialogue 2016-12-06 01:48:26 +01:00
181f60ed40 README update 2016-11-23 19:54:46 +01:00
307405d661 Improved track date handling 2016-11-23 18:45:06 +01:00
1242423ca8 Fixed possible invalid memory access 2016-11-23 18:44:22 +01:00
2bdab0f449 NMEA parser improvements 2016-11-22 00:13:41 +01:00
4e23df3a66 Fixed broken date handling 2016-11-21 21:59:21 +01:00
23f5a317d0 Fixed graph bounds issue 2016-11-18 18:02:40 +01:00
80bc9f1f01 Fixed bounds check 2016-11-18 09:23:43 +01:00
dec3f10df3 Localization update 2016-11-18 08:10:23 +01:00
296d4c6c9d Added NMEA icons 2016-11-18 08:03:04 +01:00
668558cf2e Improved error reporting 2016-11-17 23:31:09 +01:00
a6053a4d7b Made the NMEA parser more robust 2016-11-17 19:43:02 +01:00
535361dada Added support for NMEA files 2016-11-16 23:54:15 +01:00
e9e7660beb Fixed path/distances size difference issue 2016-11-15 18:08:44 +01:00
eb5692a0ab Fixed path marker inaccuracy issue. 2016-11-14 22:12:43 +01:00
367427b26a Code cleanup 2016-11-13 09:49:31 +01:00
371fa13bc6 Resize optimization 2016-11-12 09:09:42 +01:00
5a8250ed63 Fixed special cases scene rect computation 2016-11-11 23:48:38 +01:00
c8a39a607b Fixed error checking 2016-11-11 22:04:26 +01:00
215cf03b68 Fixed error handling 2016-11-11 21:53:23 +01:00
9cadb8d0a5 Make coverity happy 2016-11-11 21:37:35 +01:00
20c82deda7 README update 2016-11-11 20:15:11 +01:00
7c9768e83a Added missing IGC integration.
Version 3.1.
2016-11-11 19:54:53 +01:00
736566b12c Fixed path view clipping 2016-11-11 18:35:22 +01:00
540339bf22 Fixed zoom computation.
Code cleanup.
2016-11-11 17:58:18 +01:00
81a9743064 Added support for IGC routes (C records) 2016-11-11 17:57:49 +01:00
818fa11fd3 Added IGC icons 2016-11-10 08:56:38 +01:00
dd80d34e58 Code cleanup 2016-11-10 08:46:59 +01:00
17c791d753 Added support for IGC files 2016-11-10 00:08:11 +01:00
9f06b042ca Added support for MultiGeometry objects in KML 2016-11-08 20:50:42 +01:00
d201101587 Updated README 2016-11-08 08:47:09 +01:00
da842bd21d Fixed FIT MIME type 2016-11-08 08:41:48 +01:00
139 changed files with 10609 additions and 1436 deletions

View File

@ -74,7 +74,7 @@
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/octet-stream</string>
<string>application/vnd.ant.fit</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>fit.icns</string>
@ -83,6 +83,38 @@
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>igc</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/vnd.fai.igc</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>igc.icns</string>
<key>CFBundleTypeName</key>
<string>Flight Recorder Data Format</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>nmea</string>
</array>
<key>CFBundleTypeMIMETypes</key>
<array>
<string>application/vnd.nmea.nmea</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>nmea.icns</string>
<key>CFBundleTypeName</key>
<string>NMEA 0183 data</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
<key>UTImportedTypeDeclarations</key>
@ -168,7 +200,49 @@
<string>fit</string>
</array>
<key>public.mime-type</key>
<string>application/octet-stream</string>
<string>application/vnd.ant.fit</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.fai.igc</string>
<key>UTTypeReferenceURL</key>
<string>http://www.fai.org/gnss-recording-devices/igc-approved-flight-recorders</string>
<key>UTTypeDescription</key>
<string>Flight Recorder Data Format</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>igc</string>
</array>
<key>public.mime-type</key>
<string>application/vnd.fai.igc</string>
</dict>
</dict>
<dict>
<key>UTTypeIdentifier</key>
<string>org.nmea.nmea</string>
<key>UTTypeReferenceURL</key>
<string>http://www.nmea.org/content/nmea_standards/nmea_0183_v_410.asp</string>
<key>UTTypeDescription</key>
<string>NMEA 0183 data</string>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>nmea</string>
</array>
<key>public.mime-type</key>
<string>application/vnd.nmea.nmea</string>
</dict>
</dict>
</array>

View File

@ -1,33 +1,33 @@
# GPXSee
GPX viewer and analyzer.
GPXSee is a Qt-based GPS log file viewer and analyzer that supports GPX, TCX,
KML, FIT, IGC and NMEA files.
* User-definable map sources.
* Track and elevation/speed/heart rate graphs.
## Features
* User-definable online maps.
* Offline maps (OziExplorer maps and TrekBuddy maps/atlases).
* Elevation, speed, heart rate, cadence, power and temperature graphs.
* Support for multiple tracks in one view.
* Support for POI files.
* Export to PDF.
* Full-screen mode
* Print/export to PDF.
* Full-screen mode.
* Native GUI for Windows, Mac OS X and Linux.
* Opens GPX, TCX, FIT, KML, IGC, NMEA and Garmin CSV files.
![GPXSee - Linux](https://a.fsdn.com/con/app/proj/gpxsee/screenshots/linux.png)
![GPXSee - Linux](https://a.fsdn.com/con/app/proj/gpxsee/screenshots/linux2.png)
## Build
### Linux/OS X
```shell
lrelease gpxsee.pro
qmake gpxsee.pro
make
```
### Windows
```shell
lrelease gpxsee.pro
qmake gpxsee.pro
nmake release
```
## Binaries
* Windows & OS X builds: http://sourceforge.net/projects/gpxsee
* Linux packages: https://build.opensuse.org/project/repositories/home:tumic:GPXSee
## Download
* [Windows & OS X builds](http://sourceforge.net/projects/gpxsee)
* [Linux packages](http://software.opensuse.org/download.html?project=home%3Atumic%3AGPXSee&package=gpxsee)
## Changelog
[Changelog](https://build.opensuse.org/package/view_file/home:tumic:GPXSee/gpxsee/gpxsee.changes)
## Homepage
GPXSee homepage: http://tumic.wz.cz/gpxsee

View File

@ -1,6 +0,0 @@
CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
set PATH=C:\qt\qtbase\bin;%PATH%
set PATH=C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin;%PATH%
set INCLUDE=C:\Program Files\Microsoft SDKs\Windows\v7.1\Include;%INCLUDE%
set LIB=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib;%LIB%
set CL=/D_USING_V110_SDK71_

View File

@ -1,6 +0,0 @@
CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
set PATH=C:\qt64\qtbase\bin;%PATH%
set PATH=C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin;%PATH%
set INCLUDE=C:\Program Files\Microsoft SDKs\Windows\v7.1\Include;%INCLUDE%
set LIB=C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib\x64;%LIB%
set CL=/D_USING_V110_SDK71_

View File

@ -1,10 +1,12 @@
TARGET = GPXSee
VERSION = 3.0
VERSION = 4.8
QT += core \
gui \
network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
greaterThan(QT_MAJOR_VERSION, 4): QT += printsupport
lessThan(QT_VERSION, 5.4): QT += opengl
macx: QT += opengl
HEADERS += src/config.h \
src/icons.h \
src/gui.h \
@ -20,7 +22,7 @@ HEADERS += src/config.h \
src/sliderinfoitem.h \
src/filebrowser.h \
src/map.h \
src/maplist.h \
src/onlinemap.h \
src/downloader.h \
src/units.h \
src/scaleitem.h \
@ -68,7 +70,31 @@ HEADERS += src/config.h \
src/path.h \
src/assert.h \
src/cadencegraph.h \
src/powergraph.h
src/powergraph.h \
src/igcparser.h \
src/nmeaparser.h \
src/optionsdialog.h \
src/colorbox.h \
src/stylecombobox.h \
src/opengl.h \
src/timetype.h \
src/emptymap.h \
src/offlinemap.h \
src/matrix.h \
src/tar.h \
src/atlas.h \
src/projection.h \
src/mercator.h \
src/transversemercator.h \
src/latlon.h \
src/utm.h \
src/lambertconic.h \
src/ellipsoid.h \
src/ozf.h \
src/datum.h \
src/maplist.h \
src/albersequal.h \
src/oddspinbox.h
SOURCES += src/main.cpp \
src/gui.cpp \
src/poi.cpp \
@ -80,8 +106,7 @@ SOURCES += src/main.cpp \
src/speedgraph.cpp \
src/sliderinfoitem.cpp \
src/filebrowser.cpp \
src/map.cpp \
src/maplist.cpp \
src/onlinemap.cpp \
src/downloader.cpp \
src/scaleitem.cpp \
src/track.cpp \
@ -116,9 +141,32 @@ SOURCES += src/main.cpp \
src/format.cpp \
src/graph.cpp \
src/cadencegraph.cpp \
src/powergraph.cpp
src/powergraph.cpp \
src/igcparser.cpp \
src/path.cpp \
src/nmeaparser.cpp \
src/optionsdialog.cpp \
src/colorbox.cpp \
src/stylecombobox.cpp \
src/emptymap.cpp \
src/offlinemap.cpp \
src/matrix.cpp \
src/tar.cpp \
src/atlas.cpp \
src/mercator.cpp \
src/transversemercator.cpp \
src/utm.cpp \
src/lambertconic.cpp \
src/ellipsoid.cpp \
src/ozf.cpp \
src/datum.cpp \
src/maplist.cpp \
src/albersequal.cpp \
src/oddspinbox.cpp
RESOURCES += gpxsee.qrc
TRANSLATIONS = lang/gpxsee_cs.ts
TRANSLATIONS = lang/gpxsee_cs.ts \
lang/gpxsee_sv.ts \
lang/gpxsee_de.ts
macx {
ICON = icons/gpxsee.icns
QMAKE_INFO_PLIST = Info.plist
@ -126,7 +174,11 @@ macx {
icons/tcx.icns \
icons/kml.icns \
icons/fit.icns \
pkg/maps.txt
icons/igc.icns \
icons/nmea.icns \
pkg/maps.txt \
pkg/ellipsoids.csv \
pkg/datums.csv
APP_RESOURCES.path = Contents/Resources
QMAKE_BUNDLE_DATA += APP_RESOURCES
}
@ -135,6 +187,8 @@ win32 {
icons/gpx.ico \
icons/tcx.ico \
icons/kml.ico \
icons/fit.ico
icons/fit.ico \
icons/igc.ico \
icons/nmea.ico
}
DEFINES += APP_VERSION=\\\"$$VERSION\\\"

View File

@ -14,7 +14,14 @@
<file>icons/arrow-left-double.png</file>
<file>icons/arrow-right-double.png</file>
<file>icons/view-fullscreen.png</file>
<file>icons/office-chart-line-stacked.png</file>
<file>icons/office-chart-line.png</file>
<file>icons/preferences-desktop-display.png</file>
<file>icons/flag_48.png</file>
<file>icons/system-run.png</file>
<file>icons/document-print-preview.png</file>
<file>icons/view-filter.png</file>
<file>lang/gpxsee_cs.qm</file>
<file>lang/gpxsee_sv.qm</file>
<file>lang/gpxsee_de.qm</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
icons/flag_48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
icons/igc.icns Normal file

Binary file not shown.

BIN
icons/igc.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

145
icons/igc.svg Normal file
View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
width="185"
height="185"
id="svg3390"
version="1.1">
<metadata
id="metadata3404">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3402" />
<path
d="m 128.23996,2.2548915 -95.377018,0 0,181.0580085 134.394868,0 0,-141.313567 z m 0,0 0,39.7444415 39.01785,0"
id="path3392"
style="fill:#ffffff;stroke:#999999;stroke-width:2.18774867" />
<rect
style="fill:#ff3300;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3426"
width="120.3215"
height="49.84631"
x="16.573463"
y="124.9782" />
<g
transform="matrix(0.89093793,0,0,0.84489692,3.5017807,-51.565424)"
id="g3828">
<g
id="g3715">
<circle
r="7.0209999"
id="ellipse3717"
cy="90.875"
cx="113"
style="fill:#000000" />
<circle
r="7.0209999"
id="ellipse3719"
cy="90.875"
cx="113"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2;stroke-linejoin:round" />
</g>
<polyline
id="polyline3721"
points="62.3563,178.566 73.5,125.854 96,151.875 113,90.875 136.5,172.375 148.831,160.03 "
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:4" />
<g
id="g3723">
<circle
r="7.0209999"
id="ellipse3725"
cy="125.854"
cx="73.5"
style="fill:#000000" />
<circle
r="7.0209999"
id="ellipse3727"
cy="125.854"
cx="73.5"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
</g>
<g
id="g3729">
<circle
r="7.0209999"
id="ellipse3731"
cy="172.375"
cx="136.5"
style="fill:#000000" />
<circle
r="7.0209999"
id="ellipse3733"
cy="172.375"
cx="136.5"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
</g>
<g
id="g3735">
<circle
r="7.0209999"
id="ellipse3737"
cy="186.39999"
cx="60.700001"
style="fill:#000000" />
<circle
r="7.0209999"
id="ellipse3739"
cy="186.39999"
cx="60.700001"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
</g>
<g
id="g3741">
<circle
r="7.0209999"
id="ellipse3743"
cy="154.354"
cx="154.5"
style="fill:#000000" />
<circle
r="7.0209999"
id="ellipse3745"
cy="154.354"
cx="154.5"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
</g>
<g
id="g3747">
<circle
r="7.0209999"
id="ellipse3749"
cy="151.875"
cx="96"
style="fill:#000000" />
<circle
r="7.0209999"
id="ellipse3751"
cy="151.875"
cx="96"
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2" />
</g>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;line-height:100%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="40.149723"
y="155.86783"
id="text3921"
transform="scale(0.9437456,1.0596076)"><tspan
y="155.86783"
x="40.149723"
id="tspan3448">IGC</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
icons/nmea.icns Normal file

Binary file not shown.

BIN
icons/nmea.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

145
icons/nmea.svg Normal file
View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="svg3390"
height="185"
width="185">
<metadata
id="metadata3404">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs3402" />
<path
style="fill:#ffffff;stroke:#999999;stroke-width:2.18774867"
id="path3392"
d="m 128.23996,2.2548915 -95.377018,0 0,181.0580085 134.394868,0 0,-141.313567 z m 0,0 0,39.7444415 39.01785,0" />
<rect
y="124.9782"
x="16.573463"
height="49.84631"
width="131.6837"
id="rect3426"
style="fill:#0083d7;fill-opacity:1;stroke:none;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<g
id="g3828"
transform="matrix(0.89093793,0,0,0.84489692,3.5017807,-51.565424)">
<g
id="g3715">
<circle
style="fill:#000000"
cx="113"
cy="90.875"
id="ellipse3717"
r="7.0209999" />
<circle
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2;stroke-linejoin:round"
cx="113"
cy="90.875"
id="ellipse3719"
r="7.0209999" />
</g>
<polyline
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:4"
points="62.3563,178.566 73.5,125.854 96,151.875 113,90.875 136.5,172.375 148.831,160.03 "
id="polyline3721" />
<g
id="g3723">
<circle
style="fill:#000000"
cx="73.5"
cy="125.854"
id="ellipse3725"
r="7.0209999" />
<circle
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
cx="73.5"
cy="125.854"
id="ellipse3727"
r="7.0209999" />
</g>
<g
id="g3729">
<circle
style="fill:#000000"
cx="136.5"
cy="172.375"
id="ellipse3731"
r="7.0209999" />
<circle
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
cx="136.5"
cy="172.375"
id="ellipse3733"
r="7.0209999" />
</g>
<g
id="g3735">
<circle
style="fill:#000000"
cx="60.700001"
cy="186.39999"
id="ellipse3737"
r="7.0209999" />
<circle
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
cx="60.700001"
cy="186.39999"
id="ellipse3739"
r="7.0209999" />
</g>
<g
id="g3741">
<circle
style="fill:#000000"
cx="154.5"
cy="154.354"
id="ellipse3743"
r="7.0209999" />
<circle
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
cx="154.5"
cy="154.354"
id="ellipse3745"
r="7.0209999" />
</g>
<g
id="g3747">
<circle
style="fill:#000000"
cx="96"
cy="151.875"
id="ellipse3749"
r="7.0209999" />
<circle
style="fill:none;fill-opacity:0;stroke:#000000;stroke-width:2"
cx="96"
cy="151.875"
id="ellipse3751"
r="7.0209999" />
</g>
</g>
<text
transform="scale(0.9437456,1.0596076)"
id="text3921"
y="155.86783"
x="21.076782"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;line-height:100%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="155.86783"
x="21.076782"
id="tspan3429">NMEA</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 B

BIN
icons/office-chart-line.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
icons/system-run.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
icons/view-filter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because it is too large Load Diff

1204
lang/gpxsee_de.ts Normal file

File diff suppressed because it is too large Load Diff

1204
lang/gpxsee_sv.ts Normal file

File diff suppressed because it is too large Load Diff

123
pkg/datums.csv Normal file
View File

@ -0,0 +1,123 @@
Adindan,4201,5,-162,-12,206
Afgooye,4205,15,-43,-163,45
Ain el Abd 1970,4204,14,-150,-251,-2
Anna 1 Astro 1965,4708,2,-491,-22,435
Arc 1950,4209,5,-143,-90,-294
Arc 1960,4210,5,-160,-8,-300
Ascension Island 1958,4712,14,-207,107,52
Astro B4 Sorol Atoll,4707,14,114,-116,-333
Astro Beacon 1945,4709,14,145,75,-272
Astro DOS 71/4,4710,14,-320,550,-494
Astronomic Stn 1952,4711,14,124,-234,-25
Australian Geodetic 1966,4202,2,-133,-48,148
Australian Geodetic 1984,4203,2,-134,-48,149
Australian Geocentric 1994 (GDA94),4283,11,0,0,0
Austrian,4312,3,594,84,471
Bellevue (IGN),4714,14,-127,-769,472
Bermuda 1957,4216,4,-73,213,296
Bogota Observatory,4218,14,307,304,-318
Campo Inchauspe,4221,14,-148,136,90
Canton Astro 1966,4716,14,298,-304,-375
Cape,4222,5,-136,-108,-292
Cape Canaveral,4717,4,-2,150,181
Carthage,4223,5,-263,6,431
CH-1903,4149,3,674,15,405
Chatham 1971,4672,14,175,-38,113
Chua Astro,4224,14,-134,229,-29
Corrego Alegre,4225,14,-206,172,-6
Djakarta (Batavia),4211,3,-377,681,-50
DOS 1968,,14,230,-199,-752
Easter Island 1967,4719,14,211,147,111
Egypt,,14,-130,-117,-151
European 1950,4230,14,-87,-98,-121
European 1950 (Mean France),,14,-87,-96,-120
European 1950 (Spain and Portugal),,14,-84,-107,-120
European 1979,4668,14,-86,-98,-119
Finland Hayford,4123,14,-78,-231,-97
Gandajika Base,4233,14,-133,-321,50
Geodetic Datum 1949,4272,14,84,-22,209
GGRS 87,4121,11,-199.87,74.79,246.62
Guam 1963,4675,4,-100,-248,259
GUX 1 Astro,4718,14,252,-209,-751
Hartebeeshoek94,4148,20,0,0,0
Hermannskogel,3906,3,653,-212,449
Hjorsey 1955,4658,14,-73,46,-86
Hong Kong 1963,4739,14,-156,-271,-189
Hu-Tzu-Shan,4236,14,-634,-549,-201
Indian Bangladesh,4682,6,289,734,257
Indian Thailand,4240,6,214,836,303
Israeli,4281,23,-235,-85,264
Ireland 1965,4299,1,506,-122,611
ISTS 073 Astro 1969,4724,14,208,-435,-229
Johnston Island,4725,14,191,-77,-204
Kandawala,4244,6,-97,787,86
Kerguelen Island,4698,14,145,-187,103
Kertau 1948,4245,7,-11,851,5
L.C. 5 Astro,4726,4,42,124,147
Liberia 1964,4251,5,-90,40,88
Luzon Mindanao,,4,-133,-79,-72
Luzon Philippines,4253,4,-133,-77,-51
Mahe 1971,4256,5,41,-220,-134
Marco Astro,4616,14,-289,-124,60
Massawa,4262,3,639,405,60
Merchich,4261,5,31,146,47
Midway Astro 1961,4727,14,912,-58,1227
Minna,4263,5,-92,-93,122
NAD27 Alaska,,4,-5,135,172
NAD27 Bahamas,,4,-4,154,178
NAD27 Canada,,4,-10,158,187
NAD27 Canal Zone,,4,0,125,201
NAD27 Caribbean,,4,-7,152,178
NAD27 Central,,4,0,125,194
NAD27 CONUS,,4,-8,160,176
NAD27 Cuba,,4,-9,152,178
NAD27 Greenland,,4,11,114,195
NAD27 Mexico,,4,-12,130,190
NAD27 San Salvador,,4,1,140,165
NAD83,4269,11,0,0,0
Nahrwn Masirah Ilnd,,5,-247,-148,369
Nahrwn Saudi Arbia,,5,-231,-196,482
Nahrwn United Arab,,5,-249,-156,381
Naparima BWI,4271,14,-2,374,172
NGO1948,4273,27,315,-217,528
NTF France,4275,24,-168,-60,320
Norsk,4817,27,278,93,474
NZGD1949,4272,14,84,-22,209
NZGD2000,4167,20,0,0,0
Observatorio 1966,4182,14,-425,-169,81
Old Egyptian,4229,12,-130,110,-13
Old Hawaiian,4135,4,61,-285,-181
Oman,4232,5,-346,-1,224
Ord Srvy Grt Britn,4277,0,375,-111,431
Pico De Las Nieves,4728,14,-307,-92,127
Pitcairn Astro 1967,4729,14,185,165,42
Potsdam Rauenberg DHDN,4314,3,606,23,413
Prov So Amrican 1956,4248,14,-288,175,-376
Prov So Chilean 1963,4254,14,16,196,93
Puerto Rico,4139,4,11,72,-101
Pulkovo 1942 (1),4284,15,28,-130,-95
Pulkovo 1942 (2),4284,15,28,-130,-95
Qatar National,4285,14,-128,-283,22
Qornoq,4287,14,164,138,-189
Reunion,4626,14,94,-948,-1262
Rijksdriehoeksmeting,4289,3,593,26,478
Rome 1940,4806,14,-225,-65,9
RT 90,4124,3,498,-36,568
S42,4179,15,28,-121,-77
Santo (DOS),4730,14,170,42,84
Sao Braz,4184,14,-203,141,53
Sapper Hill 1943,4292,14,-355,16,74
Schwarzeck,4293,21,616,97,-251
South American 1969,4291,16,-57,1,-41
South Asia,,8,7,-10,-26
Southeast Base,4615,14,-499,-249,314
Southwest Base,4183,14,-104,167,-38
Timbalai 1948,4298,6,-689,691,-46
Tokyo,4301,3,-128,481,664
Tristan Astro 1968,4734,14,-632,438,-609
Viti Levu 1916,4731,5,51,391,-36
Wake-Eniwetok 1960,4732,13,101,52,-39
WGS 72,4322,19,0,0,5
WGS 84,4326,20,0,0,0
Yacare,4309,14,-155,171,37
Zanderij,4311,14,-265,120,-358
1 Adindan 4201 5 -162 -12 206
2 Afgooye 4205 15 -43 -163 45
3 Ain el Abd 1970 4204 14 -150 -251 -2
4 Anna 1 Astro 1965 4708 2 -491 -22 435
5 Arc 1950 4209 5 -143 -90 -294
6 Arc 1960 4210 5 -160 -8 -300
7 Ascension Island 1958 4712 14 -207 107 52
8 Astro B4 Sorol Atoll 4707 14 114 -116 -333
9 Astro Beacon 1945 4709 14 145 75 -272
10 Astro DOS 71/4 4710 14 -320 550 -494
11 Astronomic Stn 1952 4711 14 124 -234 -25
12 Australian Geodetic 1966 4202 2 -133 -48 148
13 Australian Geodetic 1984 4203 2 -134 -48 149
14 Australian Geocentric 1994 (GDA94) 4283 11 0 0 0
15 Austrian 4312 3 594 84 471
16 Bellevue (IGN) 4714 14 -127 -769 472
17 Bermuda 1957 4216 4 -73 213 296
18 Bogota Observatory 4218 14 307 304 -318
19 Campo Inchauspe 4221 14 -148 136 90
20 Canton Astro 1966 4716 14 298 -304 -375
21 Cape 4222 5 -136 -108 -292
22 Cape Canaveral 4717 4 -2 150 181
23 Carthage 4223 5 -263 6 431
24 CH-1903 4149 3 674 15 405
25 Chatham 1971 4672 14 175 -38 113
26 Chua Astro 4224 14 -134 229 -29
27 Corrego Alegre 4225 14 -206 172 -6
28 Djakarta (Batavia) 4211 3 -377 681 -50
29 DOS 1968 14 230 -199 -752
30 Easter Island 1967 4719 14 211 147 111
31 Egypt 14 -130 -117 -151
32 European 1950 4230 14 -87 -98 -121
33 European 1950 (Mean France) 14 -87 -96 -120
34 European 1950 (Spain and Portugal) 14 -84 -107 -120
35 European 1979 4668 14 -86 -98 -119
36 Finland Hayford 4123 14 -78 -231 -97
37 Gandajika Base 4233 14 -133 -321 50
38 Geodetic Datum 1949 4272 14 84 -22 209
39 GGRS 87 4121 11 -199.87 74.79 246.62
40 Guam 1963 4675 4 -100 -248 259
41 GUX 1 Astro 4718 14 252 -209 -751
42 Hartebeeshoek94 4148 20 0 0 0
43 Hermannskogel 3906 3 653 -212 449
44 Hjorsey 1955 4658 14 -73 46 -86
45 Hong Kong 1963 4739 14 -156 -271 -189
46 Hu-Tzu-Shan 4236 14 -634 -549 -201
47 Indian Bangladesh 4682 6 289 734 257
48 Indian Thailand 4240 6 214 836 303
49 Israeli 4281 23 -235 -85 264
50 Ireland 1965 4299 1 506 -122 611
51 ISTS 073 Astro 1969 4724 14 208 -435 -229
52 Johnston Island 4725 14 191 -77 -204
53 Kandawala 4244 6 -97 787 86
54 Kerguelen Island 4698 14 145 -187 103
55 Kertau 1948 4245 7 -11 851 5
56 L.C. 5 Astro 4726 4 42 124 147
57 Liberia 1964 4251 5 -90 40 88
58 Luzon Mindanao 4 -133 -79 -72
59 Luzon Philippines 4253 4 -133 -77 -51
60 Mahe 1971 4256 5 41 -220 -134
61 Marco Astro 4616 14 -289 -124 60
62 Massawa 4262 3 639 405 60
63 Merchich 4261 5 31 146 47
64 Midway Astro 1961 4727 14 912 -58 1227
65 Minna 4263 5 -92 -93 122
66 NAD27 Alaska 4 -5 135 172
67 NAD27 Bahamas 4 -4 154 178
68 NAD27 Canada 4 -10 158 187
69 NAD27 Canal Zone 4 0 125 201
70 NAD27 Caribbean 4 -7 152 178
71 NAD27 Central 4 0 125 194
72 NAD27 CONUS 4 -8 160 176
73 NAD27 Cuba 4 -9 152 178
74 NAD27 Greenland 4 11 114 195
75 NAD27 Mexico 4 -12 130 190
76 NAD27 San Salvador 4 1 140 165
77 NAD83 4269 11 0 0 0
78 Nahrwn Masirah Ilnd 5 -247 -148 369
79 Nahrwn Saudi Arbia 5 -231 -196 482
80 Nahrwn United Arab 5 -249 -156 381
81 Naparima BWI 4271 14 -2 374 172
82 NGO1948 4273 27 315 -217 528
83 NTF France 4275 24 -168 -60 320
84 Norsk 4817 27 278 93 474
85 NZGD1949 4272 14 84 -22 209
86 NZGD2000 4167 20 0 0 0
87 Observatorio 1966 4182 14 -425 -169 81
88 Old Egyptian 4229 12 -130 110 -13
89 Old Hawaiian 4135 4 61 -285 -181
90 Oman 4232 5 -346 -1 224
91 Ord Srvy Grt Britn 4277 0 375 -111 431
92 Pico De Las Nieves 4728 14 -307 -92 127
93 Pitcairn Astro 1967 4729 14 185 165 42
94 Potsdam Rauenberg DHDN 4314 3 606 23 413
95 Prov So Amrican 1956 4248 14 -288 175 -376
96 Prov So Chilean 1963 4254 14 16 196 93
97 Puerto Rico 4139 4 11 72 -101
98 Pulkovo 1942 (1) 4284 15 28 -130 -95
99 Pulkovo 1942 (2) 4284 15 28 -130 -95
100 Qatar National 4285 14 -128 -283 22
101 Qornoq 4287 14 164 138 -189
102 Reunion 4626 14 94 -948 -1262
103 Rijksdriehoeksmeting 4289 3 593 26 478
104 Rome 1940 4806 14 -225 -65 9
105 RT 90 4124 3 498 -36 568
106 S42 4179 15 28 -121 -77
107 Santo (DOS) 4730 14 170 42 84
108 Sao Braz 4184 14 -203 141 53
109 Sapper Hill 1943 4292 14 -355 16 74
110 Schwarzeck 4293 21 616 97 -251
111 South American 1969 4291 16 -57 1 -41
112 South Asia 8 7 -10 -26
113 Southeast Base 4615 14 -499 -249 314
114 Southwest Base 4183 14 -104 167 -38
115 Timbalai 1948 4298 6 -689 691 -46
116 Tokyo 4301 3 -128 481 664
117 Tristan Astro 1968 4734 14 -632 438 -609
118 Viti Levu 1916 4731 5 51 391 -36
119 Wake-Eniwetok 1960 4732 13 101 52 -39
120 WGS 72 4322 19 0 0 5
121 WGS 84 4326 20 0 0 0
122 Yacare 4309 14 -155 171 37
123 Zanderij 4311 14 -265 120 -358

30
pkg/ellipsoids.csv Normal file
View File

@ -0,0 +1,30 @@
0,Airy 1830,6377563.396,299.3249646
1,Modified Airy,6377340.189,299.3249646
2,Australian National,6378160.0,298.25
3,Bessel 1841,6377397.155,299.1528128
4,Clarke 1866,6378206.4,294.9786982
5,Clarke 1880,6378249.145,293.465
6,Everest (India 1830),6377276.345,300.8017
7,Everest (1948),6377304.063,300.8017
8,Modified Fischer 1960,6378155.0,298.3
9,Everest (Pakistan),6377309.613,300.8017
10,Indonesian 1974,6378160.0,298.247
11,GRS 80,6378137.0,298.257222101
12,Helmert 1906,6378200.0,298.3
13,Hough 1960,6378270.0,297.0
14,International 1924,6378388.0,297.0
15,Krassovsky 1940,6378245.0,298.3
16,South American 1969,6378160.0,298.25
17,Everest (Malaysia 1969),6377295.664,300.8017
18,Everest (Sabah Sarawak),6377298.556,300.8017
19,WGS 72,6378135.0,298.26
20,WGS 84,6378137.0,298.257223563
21,Bessel 1841 (Namibia),6377483.865,299.1528128
22,Everest (India 1956),6377301.243,300.8017
23,Clarke 1880 Palestine,6378300.789,293.466
24,Clarke 1880 IGN,6378249.2,293.466021
25,Hayford 1909,6378388.0,296.959263
26,Clarke 1858,6378350.87,294.26
27,Bessel 1841 (Norway),6377492.0176,299.1528
28,Plessis 1817 (France),6376523.0,308.6409971
29,Hayford 1924,6378388.0,297.0
1 0 Airy 1830 6377563.396 299.3249646
2 1 Modified Airy 6377340.189 299.3249646
3 2 Australian National 6378160.0 298.25
4 3 Bessel 1841 6377397.155 299.1528128
5 4 Clarke 1866 6378206.4 294.9786982
6 5 Clarke 1880 6378249.145 293.465
7 6 Everest (India 1830) 6377276.345 300.8017
8 7 Everest (1948) 6377304.063 300.8017
9 8 Modified Fischer 1960 6378155.0 298.3
10 9 Everest (Pakistan) 6377309.613 300.8017
11 10 Indonesian 1974 6378160.0 298.247
12 11 GRS 80 6378137.0 298.257222101
13 12 Helmert 1906 6378200.0 298.3
14 13 Hough 1960 6378270.0 297.0
15 14 International 1924 6378388.0 297.0
16 15 Krassovsky 1940 6378245.0 298.3
17 16 South American 1969 6378160.0 298.25
18 17 Everest (Malaysia 1969) 6377295.664 300.8017
19 18 Everest (Sabah Sarawak) 6377298.556 300.8017
20 19 WGS 72 6378135.0 298.26
21 20 WGS 84 6378137.0 298.257223563
22 21 Bessel 1841 (Namibia) 6377483.865 299.1528128
23 22 Everest (India 1956) 6377301.243 300.8017
24 23 Clarke 1880 Palestine 6378300.789 293.466
25 24 Clarke 1880 IGN 6378249.2 293.466021
26 25 Hayford 1909 6378388.0 296.959263
27 26 Clarke 1858 6378350.87 294.26
28 27 Bessel 1841 (Norway) 6377492.0176 299.1528
29 28 Plessis 1817 (France) 6376523.0 308.6409971
30 29 Hayford 1924 6378388.0 297.0

BIN
pkg/gpxsee.cer Normal file

Binary file not shown.

View File

@ -1,10 +1,11 @@
!include "MUI2.nsh"
!include "x64.nsh"
!include "WinVer.nsh"
; The name of the installer
Name "GPXSee"
; Program version
!define VERSION "3.0"
!define VERSION "4.8"
; The file to write
OutFile "GPXSee-${VERSION}.exe"
@ -15,6 +16,14 @@ RequestExecutionLevel admin
; The default installation directory
InstallDir "$PROGRAMFILES\GPXSee"
; Installer executable info
VIProductVersion "${VERSION}.0.0"
VIAddVersionKey "ProductVersion" ${VERSION}
VIAddVersionKey "FileVersion" "${VERSION}.0.0"
VIAddVersionKey "ProductName" "GPXSee"
VIAddVersionKey "LegalCopyright" "GPXSee project"
VIAddVersionKey "FileDescription" "GPXSee installer"
; Registry key to check for directory (so if you install again, it will
; overwrite the old one automatically)
InstallDirRegKey HKLM "Software\GPXSee" "Install_Dir"
@ -26,6 +35,8 @@ InstallDirRegKey HKLM "Software\GPXSee" "Install_Dir"
!define REGTCX "GPXSee.tcx"
!define REGKML "GPXSee.kml"
!define REGFIT "GPXSee.fit"
!define REGIGC "GPXSee.igc"
!define REGNMEA "GPXSee.nmea"
; Start menu page configuration
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
@ -53,9 +64,15 @@ Var StartMenuFolder
; Languages
!insertmacro MUI_LANGUAGE "English"
Function .onInit
${IfNot} ${AtLeastWin7}
MessageBox MB_OK "GPXSee can only be installed on Windows 7 or later."
Abort
${EndIf}
FunctionEnd
; The stuff to install
Section "GPXSee (required)" SEC_APP
Section "GPXSee" SEC_APP
SectionIn RO
@ -65,6 +82,8 @@ Section "GPXSee (required)" SEC_APP
; Put the files there
File "gpxsee.exe"
File "maps.txt"
File "ellipsoids.csv"
File "datums.csv"
; Create start menu entry and add links
SetShellVarContext all
@ -107,19 +126,28 @@ Section "GPXSee (required)" SEC_APP
WriteRegStr HKCR "${REGFIT}" "" "Flexible and Interoperable Data Transfer"
WriteRegStr HKCR "${REGFIT}\DefaultIcon" "" "$INSTDIR\GPXSee.exe,4"
WriteRegStr HKCR "${REGFIT}\shell\open\command" "" "$\"$INSTDIR\GPXSee.exe$\" $\"%1$\""
WriteRegStr HKCR ".igc" "" "${REGIGC}"
WriteRegStr HKCR "${REGIGC}" "" "Flight Recorder Data Format"
WriteRegStr HKCR "${REGIGC}\DefaultIcon" "" "$INSTDIR\GPXSee.exe,5"
WriteRegStr HKCR "${REGIGC}\shell\open\command" "" "$\"$INSTDIR\GPXSee.exe$\" $\"%1$\""
WriteRegStr HKCR ".nmea" "" "${REGNMEA}"
WriteRegStr HKCR "${REGNMEA}" "" "NMEA 0183 data"
WriteRegStr HKCR "${REGNMEA}\DefaultIcon" "" "$INSTDIR\GPXSee.exe,6"
WriteRegStr HKCR "${REGNMEA}\shell\open\command" "" "$\"$INSTDIR\GPXSee.exe$\" $\"%1$\""
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
SectionEnd
Section "QT libs" SEC_QT
Section "QT framework" SEC_QT
SectionIn RO
File "Qt5Core.dll"
File "Qt5Gui.dll"
File "Qt5Widgets.dll"
File "Qt5PrintSupport.dll"
File "Qt5Network.dll"
File "libGLESv2.dll"
File /r "platforms"
File /r "imageformats"
File /r "printsupport"
@ -128,6 +156,8 @@ SectionEnd
Section "MSVC runtime" SEC_MSVC
SectionIn RO
DetailPrint "Checking whether Visual C++ 2015 Redistributable is already installed..."
${If} ${RunningX64}
ReadRegDword $R0 HKLM "SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x86" "Installed"
@ -143,10 +173,26 @@ Section "MSVC runtime" SEC_MSVC
SetOutPath $TEMP
File "VC_redist.x86.exe"
ExecWait '"$TEMP/VC_redist.x86.exe" /install /quiet /norestart'
SetOutPath $INSTDIR
done:
SectionEnd
Section "OpenSSL" SEC_OPENSSL
File "libeay32.dll"
File "ssleay32.dll"
SectionEnd
Section "ANGLE" SEC_ANGLE
File "libGLESv2.dll"
File "libEGL.dll"
File "D3DCompiler_47.dll"
SectionEnd
;--------------------------------
; Uninstaller
@ -175,6 +221,10 @@ Section "Uninstall"
DeleteRegKey HKCR ".kml"
DeleteRegKey HKCR "${REGFIT}"
DeleteRegKey HKCR ".fit"
DeleteRegKey HKCR "${REGIGC}"
DeleteRegKey HKCR ".igc"
DeleteRegKey HKCR "${REGNMEA}"
DeleteRegKey HKCR ".nmea"
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
SectionEnd
@ -185,15 +235,21 @@ SectionEnd
;Language strings
LangString DESC_QT ${LANG_ENGLISH} \
"QT Library. Unselct only if you have QT already installed!"
"QT cross-platform application framework."
LangString DESC_MSVC ${LANG_ENGLISH} \
"Visual C++ 2015 runtime components. Unselct only if you have the runtime already installed!"
"Visual C++ 2015 runtime components. If already installed, will be skipped."
LangString DESC_OPENSSL ${LANG_ENGLISH} \
"OpenSSL library. Required for HTTPS to work."
LangString DESC_ANGLE ${LANG_ENGLISH} \
"ANGLE (OpenGL via Direct3D). Enables OpenGL on systems without native OpenGL drivers."
LangString DESC_APP ${LANG_ENGLISH} \
"GPXSee application"
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_QT} $(DESC_QT)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_OPENSSL} $(DESC_OPENSSL)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_ANGLE} $(DESC_ANGLE)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_MSVC} $(DESC_MSVC)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_APP} $(DESC_APP)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
!insertmacro MUI_FUNCTION_DESCRIPTION_END

View File

@ -1,10 +1,11 @@
!include "MUI2.nsh"
!include "x64.nsh"
!include "WinVer.nsh"
; The name of the installer
Name "GPXSee"
; Program version
!define VERSION "3.0"
!define VERSION "4.8"
; The file to write
OutFile "GPXSee-${VERSION}_x64.exe"
@ -15,6 +16,14 @@ RequestExecutionLevel admin
; The default installation directory
InstallDir "$PROGRAMFILES64\GPXSee"
; Installer executable info
VIProductVersion "${VERSION}.0.0"
VIAddVersionKey "ProductVersion" ${VERSION}
VIAddVersionKey "FileVersion" "${VERSION}.0.0"
VIAddVersionKey "ProductName" "GPXSee"
VIAddVersionKey "LegalCopyright" "GPXSee project"
VIAddVersionKey "FileDescription" "GPXSee installer (x64)"
; Registry key to check for directory (so if you install again, it will
; overwrite the old one automatically)
InstallDirRegKey HKLM "Software\GPXSee" "Install_Dir"
@ -26,6 +35,8 @@ InstallDirRegKey HKLM "Software\GPXSee" "Install_Dir"
!define REGTCX "GPXSee.tcx"
!define REGKML "GPXSee.kml"
!define REGFIT "GPXSee.fit"
!define REGIGC "GPXSee.igc"
!define REGNMEA "GPXSee.nmea"
; Start menu page configuration
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
@ -53,7 +64,12 @@ Var StartMenuFolder
; Languages
!insertmacro MUI_LANGUAGE "English"
Function .onInit
Function .onInit
${IfNot} ${AtLeastWin7}
MessageBox MB_OK "GPXSee can only be installed on Windows 7 or later."
Abort
${EndIf}
${If} ${RunningX64}
SetRegView 64
${Else}
@ -63,7 +79,7 @@ Function .onInit
FunctionEnd
; The stuff to install
Section "GPXSee (required)" SEC_APP
Section "GPXSee" SEC_APP
SectionIn RO
@ -73,6 +89,8 @@ Section "GPXSee (required)" SEC_APP
; Put the files there
File "gpxsee.exe"
File "maps.txt"
File "ellipsoids.csv"
File "datums.csv"
; Create start menu entry and add links
SetShellVarContext all
@ -90,7 +108,7 @@ Section "GPXSee (required)" SEC_APP
WriteRegStr HKLM SOFTWARE\GPXSee "Install_Dir" "$INSTDIR"
; Write the uninstall keys for Windows
WriteRegStr HKLM "${REGENTRY}" "DisplayName" "GPXSee"
WriteRegStr HKLM "${REGENTRY}" "DisplayName" "GPXSee (x64)"
WriteRegStr HKLM "${REGENTRY}" "Publisher" "Martin Tuma"
WriteRegStr HKLM "${REGENTRY}" "DisplayVersion" "${VERSION}"
WriteRegStr HKLM "${REGENTRY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
@ -115,19 +133,28 @@ Section "GPXSee (required)" SEC_APP
WriteRegStr HKCR "${REGFIT}" "" "Flexible and Interoperable Data Transfer"
WriteRegStr HKCR "${REGFIT}\DefaultIcon" "" "$INSTDIR\GPXSee.exe,4"
WriteRegStr HKCR "${REGFIT}\shell\open\command" "" "$\"$INSTDIR\GPXSee.exe$\" $\"%1$\""
WriteRegStr HKCR ".igc" "" "${REGIGC}"
WriteRegStr HKCR "${REGIGC}" "" "Flight Recorder Data Format"
WriteRegStr HKCR "${REGIGC}\DefaultIcon" "" "$INSTDIR\GPXSee.exe,5"
WriteRegStr HKCR "${REGIGC}\shell\open\command" "" "$\"$INSTDIR\GPXSee.exe$\" $\"%1$\""
WriteRegStr HKCR ".nmea" "" "${REGNMEA}"
WriteRegStr HKCR "${REGNMEA}" "" "NMEA 0183 data"
WriteRegStr HKCR "${REGNMEA}\DefaultIcon" "" "$INSTDIR\GPXSee.exe,6"
WriteRegStr HKCR "${REGNMEA}\shell\open\command" "" "$\"$INSTDIR\GPXSee.exe$\" $\"%1$\""
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
SectionEnd
Section "QT libs" SEC_QT
Section "QT framework" SEC_QT
SectionIn RO
File "Qt5Core.dll"
File "Qt5Gui.dll"
File "Qt5Widgets.dll"
File "Qt5PrintSupport.dll"
File "Qt5Network.dll"
File "libGLESv2.dll"
File /r "platforms"
File /r "imageformats"
File /r "printsupport"
@ -136,6 +163,8 @@ SectionEnd
Section "MSVC runtime" SEC_MSVC
SectionIn RO
DetailPrint "Checking whether Visual C++ 2015 Redistributable is already installed..."
ReadRegDword $R0 HKLM "SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64" "Installed"
StrCmp $R0 "1" 0 +3
@ -146,10 +175,26 @@ Section "MSVC runtime" SEC_MSVC
SetOutPath $TEMP
File "VC_redist.x64.exe"
ExecWait '"$TEMP/VC_redist.x64.exe" /install /quiet /norestart'
SetOutPath $INSTDIR
done:
SectionEnd
Section "OpenSSL" SEC_OPENSSL
File "libeay32.dll"
File "ssleay32.dll"
SectionEnd
Section "ANGLE" SEC_ANGLE
File "libGLESv2.dll"
File "libEGL.dll"
File "D3DCompiler_47.dll"
SectionEnd
;--------------------------------
; Uninstaller
@ -179,6 +224,10 @@ Section "Uninstall"
DeleteRegKey HKCR ".kml"
DeleteRegKey HKCR "${REGFIT}"
DeleteRegKey HKCR ".fit"
DeleteRegKey HKCR "${REGIGC}"
DeleteRegKey HKCR ".igc"
DeleteRegKey HKCR "${REGNMEA}"
DeleteRegKey HKCR ".nmea"
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
SectionEnd
@ -189,15 +238,21 @@ SectionEnd
;Language strings
LangString DESC_QT ${LANG_ENGLISH} \
"QT Library. Unselct only if you have QT already installed!"
"QT cross-platform application framework."
LangString DESC_MSVC ${LANG_ENGLISH} \
"Visual C++ 2015 runtime components. Unselct only if you have the runtime already installed!"
"Visual C++ 2015 runtime components. If already installed, will be skipped."
LangString DESC_OPENSSL ${LANG_ENGLISH} \
"OpenSSL library. Required for HTTPS to work."
LangString DESC_ANGLE ${LANG_ENGLISH} \
"ANGLE (OpenGL via Direct3D). Enables OpenGL on systems without native OpenGL drivers."
LangString DESC_APP ${LANG_ENGLISH} \
"GPXSee application"
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_QT} $(DESC_QT)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_OPENSSL} $(DESC_OPENSSL)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_ANGLE} $(DESC_ANGLE)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_MSVC} $(DESC_MSVC)
!insertmacro MUI_DESCRIPTION_TEXT ${SEC_APP} $(DESC_APP)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
!insertmacro MUI_FUNCTION_DESCRIPTION_END

View File

@ -1,2 +1,4 @@
Thunderforest http://tile.thunderforest.com/outdoors/$z/$x/$y.png
Open Topo Map https://a.tile.opentopomap.org/$z/$x/$y.png
Open Street Map http://tile.openstreetmap.org/$z/$x/$y.png
USGS Topo https://navigator.er.usgs.gov/tiles/tcr.cgi/$z/$x/$y.png
USGS Imagery https://navigator.er.usgs.gov/tiles/aerial_Imagery.cgi/$z/$x/$y

229
src/albersequal.cpp Normal file
View File

@ -0,0 +1,229 @@
/*
* Based on libgeotrans with the following Source Code Disclaimer:
1. The GEOTRANS source code ("the software") is provided free of charge by
the National Imagery and Mapping Agency (NIMA) of the United States
Department of Defense. Although NIMA makes no copyright claim under Title 17
U.S.C., NIMA claims copyrights in the source code under other legal regimes.
NIMA hereby grants to each user of the software a license to use and
distribute the software, and develop derivative works.
2. Warranty Disclaimer: The software was developed to meet only the internal
requirements of the U.S. National Imagery and Mapping Agency. The software
is provided "as is," and no warranty, express or implied, including but not
limited to the implied warranties of merchantability and fitness for
particular purpose or arising by statute or otherwise in law or from a
course of dealing or usage in trade, is made by NIMA as to the accuracy and
functioning of the software.
3. NIMA and its personnel are not required to provide technical support or
general assistance with respect to the software.
4. Neither NIMA nor its personnel will be liable for any claims, losses, or
damages arising from or connected with the use of the software. The user
agrees to hold harmless the United States National Imagery and Mapping
Agency. The user's sole and exclusive remedy is to stop using the software.
5. NIMA requests that products developed using the software credit the
source of the software with the following statement, "The product was
developed using GEOTRANS, a product of the National Imagery and Mapping
Agency and U.S. Army Engineering Research and Development Center."
6. For any products developed using the software, NIMA requires a disclaimer
that use of the software does not indicate endorsement or approval of the
product by the Secretary of Defense or the National Imagery and Mapping
Agency. Pursuant to the United States Code, 10 U.S.C. Sec. 2797, the name of
the National Imagery and Mapping Agency, the initials "NIMA", the seal of
the National Imagery and Mapping Agency, or any colorable imitation thereof
shall not be used to imply approval, endorsement, or authorization of a
product without prior written permission from United States Secretary of
Defense.
*/
#include "ellipsoid.h"
#include "rd.h"
#include "albersequal.h"
#ifndef M_PI_2
#define M_PI_2 1.57079632679489661923
#endif // M_PI_2
#define ONE_MINUS_SQR(x) (1.0 - (x) * (x))
#define ALBERS_Q(slat, one_minus_sqr_es_sin, es_sin) \
(_one_minus_es2 * ((slat) / (one_minus_sqr_es_sin) - \
(1 / (_two_es)) * log((1 - (es_sin)) / (1 + (es_sin)))))
#define ALBERS_M(clat, one_minus_sqr_es_sin) \
((clat) / sqrt(one_minus_sqr_es_sin))
AlbersEqual::AlbersEqual(const Ellipsoid &ellipsoid, double standardParallel1,
double standardParallel2, double latitudeOrigin, double longitudeOrigin,
double falseEasting, double falseNorthing)
{
double sin_lat, sin_lat1, sin_lat2, cos_lat1, cos_lat2;
double m1, m2, sqr_m1, sqr_m2;
double q0, q1, q2;
double es_sin, es_sin1, es_sin2;
double one_minus_sqr_es_sin1, one_minus_sqr_es_sin2;
double nq0;
double sp1, sp2;
_e = ellipsoid;
_latitudeOrigin = deg2rad(latitudeOrigin);
_longitudeOrigin = deg2rad(longitudeOrigin);
_falseEasting = falseEasting;
_falseNorthing = falseNorthing;
sp1 = deg2rad(standardParallel1);
sp2 = deg2rad(standardParallel2);
_es2 = 2 * _e.flattening() - _e.flattening() * _e.flattening();
_es = sqrt(_es2);
_one_minus_es2 = 1 - _es2;
_two_es = 2 * _es;
sin_lat = sin(_latitudeOrigin);
es_sin = _es * sin_lat;
q0 = ALBERS_Q(sin_lat, ONE_MINUS_SQR(es_sin), es_sin);
sin_lat1 = sin(sp1);
cos_lat1 = cos(sp1);
es_sin1 = _es * sin_lat1;
one_minus_sqr_es_sin1 = ONE_MINUS_SQR(es_sin1);
m1 = ALBERS_M(cos_lat1, one_minus_sqr_es_sin1);
q1 = ALBERS_Q(sin_lat1, one_minus_sqr_es_sin1, es_sin1);
sqr_m1 = m1 * m1;
if (fabs(sp1 - sp2) > 1.0e-10) {
sin_lat2 = sin(sp2);
cos_lat2 = cos(sp2);
es_sin2 = _es * sin_lat2;
one_minus_sqr_es_sin2 = ONE_MINUS_SQR(es_sin2);
m2 = ALBERS_M(cos_lat2, one_minus_sqr_es_sin2);
q2 = ALBERS_Q(sin_lat2, one_minus_sqr_es_sin2, es_sin2);
sqr_m2 = m2 * m2;
_n = (sqr_m1 - sqr_m2) / (q2 - q1);
} else
_n = sin_lat1;
_C = sqr_m1 + _n * q1;
_a_over_n = _e.radius() / _n;
nq0 = _n * q0;
_rho0 = (_C < nq0) ? 0 : _a_over_n * sqrt(_C - nq0);
}
QPointF AlbersEqual::ll2xy(const Coordinates &c) const
{
double dlam;
double sin_lat;
double es_sin;
double q;
double rho;
double theta;
double nq;
dlam = deg2rad(c.lon()) - _longitudeOrigin;
if (dlam > M_PI)
dlam -= 2.0 * M_PI;
if (dlam < -M_PI)
dlam += 2.0 * M_PI;
sin_lat = sin(deg2rad(c.lat()));
es_sin = _es * sin_lat;
q = ALBERS_Q(sin_lat, ONE_MINUS_SQR(es_sin), es_sin);
nq = _n * q;
rho = (_C < nq) ? 0 : _a_over_n * sqrt(_C - nq);
theta = _n * dlam;
return QPointF(rho * sin(theta) + _falseEasting,
_rho0 - rho * cos(theta) + _falseNorthing);
}
Coordinates AlbersEqual::xy2ll(const QPointF &p) const
{
double dy, dx;
double rho0_minus_dy;
double q, qc, q_over_2;
double rho, rho_n;
double phi, delta_phi = 1.0;
double sin_phi;
double es_sin, one_minus_sqr_es_sin;
double theta = 0.0;
int count = 30;
double tolerance = 4.85e-10;
double lat, lon;
dy = p.y() - _falseNorthing;
dx = p.x() - _falseEasting;
rho0_minus_dy = _rho0 - dy;
rho = sqrt(dx * dx + rho0_minus_dy * rho0_minus_dy);
if (_n < 0) {
rho *= -1.0;
dx *= -1.0;
rho0_minus_dy *= -1.0;
}
if (rho != 0.0)
theta = atan2(dx, rho0_minus_dy);
rho_n = rho * _n;
q = (_C - (rho_n * rho_n) / (_e.radius() * _e.radius())) / _n;
qc = 1 - ((_one_minus_es2) / (_two_es)) * log((1.0 - _es) / (1.0 + _es));
if (fabs(fabs(qc) - fabs(q)) > 1.0e-6) {
q_over_2 = q / 2.0;
if (q_over_2 > 1.0)
lat = M_PI_2;
else if (q_over_2 < -1.0)
lat = -M_PI_2;
else {
phi = asin(q_over_2);
if (_es < 1.0e-10)
lat = phi;
else {
while ((fabs(delta_phi) > tolerance) && count) {
sin_phi = sin(phi);
es_sin = _es * sin_phi;
one_minus_sqr_es_sin = ONE_MINUS_SQR(es_sin);
delta_phi = (one_minus_sqr_es_sin * one_minus_sqr_es_sin)
/ (2.0 * cos(phi)) * (q / (_one_minus_es2) - sin_phi
/ one_minus_sqr_es_sin + (log((1.0 - es_sin)
/ (1.0 + es_sin)) / (_two_es)));
phi += delta_phi;
count --;
}
lat = phi;
}
if (lat > M_PI_2)
lat = M_PI_2;
else if (lat < -M_PI_2)
lat = -M_PI_2;
}
} else {
if (q >= 0.0)
lat = M_PI_2;
else
lat = -M_PI_2;
}
lon = _longitudeOrigin + theta / _n;
if (lon > M_PI)
lon -= M_PI * 2;
if (lon < -M_PI)
lon += M_PI * 2;
if (lon > M_PI)
lon = M_PI;
else if (lon < -M_PI)
lon = -M_PI;
return Coordinates(rad2deg(lon), rad2deg(lat));
}

36
src/albersequal.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef ALBERSEQUAL_H
#define ALBERSEQUAL_H
#include "projection.h"
class Ellipsoid;
class AlbersEqual : public Projection
{
public:
AlbersEqual(const Ellipsoid &ellipsoid, double standardParallel1,
double standardParallel2, double latitudeOrigin, double longitudeOrigin,
double falseEasting, double falseNorthing);
virtual QPointF ll2xy(const Coordinates &c) const;
virtual Coordinates xy2ll(const QPointF &p) const;
private:
Ellipsoid _e;
double _latitudeOrigin;
double _longitudeOrigin;
double _falseEasting;
double _falseNorthing;
double _rho0;
double _C;
double _n;
double _es;
double _es2;
double _a_over_n;
double _one_minus_es2;
double _two_es;
};
#endif // ALBERSEQUAL_H

View File

@ -3,23 +3,28 @@
#include <QLocale>
#include <QFileOpenEvent>
#include <QNetworkProxyFactory>
#include "opengl.h"
#include "gui.h"
#include "onlinemap.h"
#include "downloader.h"
#include "app.h"
App::App(int &argc, char **argv) : QApplication(argc, argv),
_argc(argc), _argv(argv)
{
_translator = new QTranslator();
QTranslator *translator = new QTranslator(this);
QString locale = QLocale::system().name();
_translator->load(QString(":/lang/gpxsee_") + locale);
installTranslator(_translator);
translator->load(QString(":/lang/gpxsee_") + locale);
installTranslator(translator);
#ifdef Q_OS_MAC
setAttribute(Qt::AA_DontShowIconsInMenus);
#endif // Q_OS_MAC
QNetworkProxyFactory::setUseSystemConfiguration(true);
OnlineMap::setDownloader(new Downloader(this));
OPENGL_SET_SAMPLES(4);
_gui = new GUI();
}
@ -27,7 +32,6 @@ App::App(int &argc, char **argv) : QApplication(argc, argv),
App::~App()
{
delete _gui;
delete _translator;
}
void App::run()

View File

@ -4,7 +4,6 @@
#include <QApplication>
class GUI;
class QTranslator;
class App : QApplication
{
@ -22,7 +21,6 @@ private:
int &_argc;
char **_argv;
GUI *_gui;
QTranslator *_translator;
};
#endif // APP_H

320
src/atlas.cpp Normal file
View File

@ -0,0 +1,320 @@
#include <QDir>
#include <QtAlgorithms>
#include <QPainter>
#include "tar.h"
#include "atlas.h"
#define ZOOM_THRESHOLD 0.9
#define TL(m) ((m)->xy2pp((m)->bounds().topLeft()))
#define BR(m) ((m)->xy2pp((m)->bounds().bottomRight()))
static bool resCmp(const OfflineMap *m1, const OfflineMap *m2)
{
qreal r1, r2;
r1 = m1->resolution(m1->bounds().center());
r2 = m2->resolution(m2->bounds().center());
return r1 > r2;
}
static bool xCmp(const OfflineMap *m1, const OfflineMap *m2)
{
return TL(m1).x() < TL(m2).x();
}
static bool yCmp(const OfflineMap *m1, const OfflineMap *m2)
{
return TL(m1).y() > TL(m2).y();
}
bool Atlas::isAtlas(Tar &tar, const QString &path)
{
QFileInfo fi(path);
QByteArray ba;
QString suffix = fi.suffix().toLower();
if (suffix == "tar") {
if (!tar.load(path)) {
_errorString = "Error reading tar file";
return false;
}
QString tbaFileName = fi.completeBaseName() + ".tba";
ba = tar.file(tbaFileName);
} else if (suffix == "tba") {
QFile tbaFile(path);
if (!tbaFile.open(QIODevice::ReadOnly)) {
_errorString = QString("Error opening tba file: %1")
.arg(tbaFile.errorString());
return false;
}
ba = tbaFile.readAll();
}
if (ba.startsWith("Atlas 1.0"))
return true;
else {
_errorString = "Missing or invalid tba file";
return false;
}
}
void Atlas::computeZooms()
{
qSort(_maps.begin(), _maps.end(), resCmp);
_zooms.append(QPair<int, int>(0, _maps.count() - 1));
for (int i = 1; i < _maps.count(); i++) {
qreal last = _maps.at(i-1)->resolution(_maps.at(i)->bounds().center());
qreal cur = _maps.at(i)->resolution(_maps.at(i)->bounds().center());
if (cur < last * ZOOM_THRESHOLD) {
_zooms.last().second = i-1;
_zooms.append(QPair<int, int>(i, _maps.count() - 1));
}
}
}
void Atlas::computeBounds()
{
QList<QPointF> offsets;
for (int i = 0; i < _maps.count(); i++)
offsets.append(QPointF());
for (int z = 0; z < _zooms.count(); z++) {
qreal w = 0, h = 0;
QList<OfflineMap*> m;
for (int i = _zooms.at(z).first; i <= _zooms.at(z).second; i++)
m.append(_maps.at(i));
qSort(m.begin(), m.end(), xCmp);
offsets[_maps.indexOf(m.first())].setX(w);
for (int i = 1; i < m.size(); i++) {
w += round(m.at(i-1)->pp2xy(TL(m.at(i))).x());
offsets[_maps.indexOf(m.at(i))].setX(w);
}
qSort(m.begin(), m.end(), yCmp);
offsets[_maps.indexOf(m.first())].setY(h);
for (int i = 1; i < m.size(); i++) {
h += round(m.at(i-1)->pp2xy(TL(m.at(i))).y());
offsets[_maps.indexOf(m.at(i))].setY(h);
}
}
for (int i = 0; i < _maps.count(); i++)
_bounds.append(QPair<QRectF, QRectF>(QRectF(TL(_maps.at(i)),
BR(_maps.at(i))), QRectF(offsets.at(i), _maps.at(i)->bounds().size())));
}
Atlas::Atlas(const QString &fileName, QObject *parent) : Map(parent)
{
Tar tar;
QFileInfo fi(fileName);
_valid = false;
_zoom = 0;
_name = fi.dir().dirName();
_ci = -1; _cz = -1;
if (!isAtlas(tar, fileName))
return;
QDir dir(fi.absolutePath());
QFileInfoList layers = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (int n = 0; n < layers.count(); n++) {
QDir zdir(layers.at(n).absoluteFilePath());
QFileInfoList maps = zdir.entryInfoList(QDir::Dirs
| QDir::NoDotAndDotDot);
for (int i = 0; i < maps.count(); i++) {
QString mapFile = maps.at(i).absoluteFilePath() + "/"
+ maps.at(i).fileName() + ".map";
OfflineMap *map;
if (tar.isOpen())
map = new OfflineMap(mapFile, tar, this);
else
map = new OfflineMap(mapFile, this);
if (map->isValid())
_maps.append(map);
else {
_errorString = QString("Error loading map: %1: %2")
.arg(mapFile, map->errorString());
return;
}
}
}
if (_maps.isEmpty()) {
_errorString = "No maps found in atlas";
return;
}
computeZooms();
computeBounds();
_valid = true;
}
Atlas::~Atlas()
{
for (int i = 0; i < _maps.size(); i++)
delete _maps.at(i);
}
QRectF Atlas::bounds() const
{
QSizeF s(0, 0);
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
if (_bounds.at(i).second.right() > s.width())
s.setWidth(_bounds.at(i).second.right());
if (_bounds.at(i).second.bottom() > s.height())
s.setHeight(_bounds.at(i).second.bottom());
}
return QRectF(QPointF(0, 0), s);
}
qreal Atlas::resolution(const QPointF &p) const
{
int idx = _zooms.at(_zoom).first;
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
if (_bounds.at(i).second.contains(_maps.at(i)->xy2pp(p))) {
idx = i;
break;
}
}
return _maps.at(idx)->resolution(p);
}
qreal Atlas::zoom() const
{
return _zoom;
}
qreal Atlas::zoomFit(const QSize &size, const QRectF &br)
{
_zoom = 0;
if (br.isNull()) {
_zoom = _zooms.size() - 1;
return _zoom;
}
for (int z = 0; z < _zooms.count(); z++) {
for (int i = _zooms.at(z).first; i <= _zooms.at(z).second; i++) {
if (!_bounds.at(i).first.contains(_maps.at(i)->ll2pp(br.center())))
continue;
QRect sbr = QRectF(_maps.at(i)->ll2xy(br.topLeft()),
_maps.at(i)->ll2xy(br.bottomRight())).toRect().normalized();
if (sbr.size().width() > size.width()
|| sbr.size().height() > size.height())
return _zoom;
_zoom = z;
break;
}
}
return _zoom;
}
qreal Atlas::zoomIn()
{
_zoom = qMin(_zoom + 1, _zooms.size() - 1);
return _zoom;
}
qreal Atlas::zoomOut()
{
_zoom = qMax(_zoom - 1, 0);
return _zoom;
}
QPointF Atlas::ll2xy(const Coordinates &c)
{
QPointF pp;
if (_cz != _zoom) {
_ci = -1;
_cz = _zoom;
}
if (_ci >= 0)
pp = _maps.at(_ci)->ll2pp(c);
if (_ci < 0 || !_bounds.at(_ci).first.contains(pp)) {
_ci = _zooms.at(_zoom).first;
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
pp = _maps.at(i)->ll2pp(c);
if (_bounds.at(i).first.contains(pp)) {
_ci = i;
break;
}
}
}
QPointF p = _maps.at(_ci)->pp2xy(pp);
return p + _bounds.at(_ci).second.topLeft();
}
Coordinates Atlas::xy2ll(const QPointF &p)
{
int idx = _zooms.at(_zoom).first;
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
if (_bounds.at(i).second.contains(_maps.at(i)->xy2pp(p))) {
idx = i;
break;
}
}
QPointF p2 = p - _bounds.at(idx).second.topLeft();
return _maps.at(idx)->xy2ll(p2);
}
void Atlas::draw(QPainter *painter, const QRectF &rect)
{
// All in one map
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
QRectF ir = rect.intersected(_bounds.at(i).second);
if (ir == rect) {
draw(painter, rect, i);
return;
}
}
// Multiple maps
painter->fillRect(rect, Qt::white);
for (int i = _zooms.at(_zoom).first; i <= _zooms.at(_zoom).second; i++) {
QRectF ir = rect.intersected(_bounds.at(i).second);
if (!ir.isNull())
draw(painter, ir, i);
}
}
void Atlas::draw(QPainter *painter, const QRectF &rect, int mapIndex)
{
OfflineMap *map = _maps.at(mapIndex);
const QPointF offset = _bounds.at(mapIndex).second.topLeft();
QRectF pr = QRectF(rect.topLeft() - offset, rect.size());
map->load();
painter->translate(offset);
map->draw(painter, pr);
painter->translate(-offset);
}
void Atlas::unload()
{
for (int i = 0; i < _maps.count(); i++)
_maps.at(i)->unload();
}

55
src/atlas.h Normal file
View File

@ -0,0 +1,55 @@
#ifndef ATLAS_H
#define ATLAS_H
#include <QFileInfoList>
#include "map.h"
#include "offlinemap.h"
class Atlas : public Map
{
Q_OBJECT
public:
Atlas(const QString &fileName, QObject *parent = 0);
~Atlas();
const QString &name() const {return _name;}
QRectF bounds() const;
qreal resolution(const QPointF &p) const;
qreal zoom() const;
qreal zoomFit(const QSize &size, const QRectF &br);
qreal zoomIn();
qreal zoomOut();
QPointF ll2xy(const Coordinates &c);
Coordinates xy2ll(const QPointF &p);
void draw(QPainter *painter, const QRectF &rect);
void unload();
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
private:
void draw(QPainter *painter, const QRectF &rect, int mapIndex);
bool isAtlas(Tar &tar, const QString &path);
void computeZooms();
void computeBounds();
QString _name;
bool _valid;
QString _errorString;
QList<OfflineMap*> _maps;
QVector<QPair<int, int> > _zooms;
QVector<QPair<QRectF, QRectF> > _bounds;
int _zoom;
int _ci, _cz;
};
#endif // ATLAS_H

68
src/colorbox.cpp Normal file
View File

@ -0,0 +1,68 @@
#include <QStylePainter>
#include <QStyleOptionComboBox>
#include <QMouseEvent>
#include <QColorDialog>
#include <QComboBox>
#include "colorbox.h"
ColorBox::ColorBox(QWidget *parent) : QWidget(parent)
{
_color = Qt::red;
setSizePolicy(QSizePolicy::QSizePolicy::Minimum, QSizePolicy::Fixed);
}
QSize ColorBox::sizeHint() const
{
static QSize size;
if (size.isValid())
return size;
QComboBox cb;
size = cb.sizeHint();
return size;
}
void ColorBox::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QStylePainter painter(this);
QStyleOptionComboBox option;
option.initFrom(this);
#if defined(Q_OS_MAC) || defined(Q_OS_WIN32)
painter.setBrush(_color);
painter.drawPrimitive(QStyle::PE_Frame, option);
#else // Q_OS_MAC || Q_OS_WIN32
// Fallback for some broken QT4 styles that do not draw the background
painter.setBrush(_color);
painter.setPen(Qt::NoPen);
painter.drawRect(event->rect().adjusted(2, 2, -2, -2));
// If works (QT5 and most QT4 styles) overpaints the previous rectangle
option.palette.setBrush(QPalette::Base, _color);
painter.drawPrimitive(QStyle::PE_PanelLineEdit, option);
painter.drawPrimitive(QStyle::PE_FrameLineEdit, option);
#endif // Q_OS_MAC || Q_OS_WIN32
}
void ColorBox::mousePressEvent(QMouseEvent *event)
{
if (event->button() != Qt::LeftButton)
return;
QColor color = QColorDialog::getColor(_color, this, QString(),
QColorDialog::ShowAlphaChannel);
if (color.isValid()) {
_color = color;
update();
emit colorChanged(_color);
}
}
void ColorBox::setColor(const QColor &color)
{
_color = color;
update();
}

29
src/colorbox.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef COLORBOX_H
#define COLORBOX_H
#include <QWidget>
class ColorBox : public QWidget
{
Q_OBJECT
public:
ColorBox(QWidget *parent = 0);
const QColor &color() const {return _color;}
void setColor(const QColor &color);
QSize sizeHint() const;
signals:
void colorChanged(const QColor &color);
protected:
void mousePressEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
QColor _color;
};
#endif // COLORBOX_H

View File

@ -13,7 +13,10 @@
#define FONT_SIZE 12
#define SCREEN_DPI 96.0
#define ELLIPSOID_FILE QString("ellipsoids.csv")
#define DATUM_FILE QString("datums.csv")
#define MAP_FILE QString("maps.txt")
#define MAP_DIR QString("maps")
#define POI_DIR QString("POI")
#if defined(Q_OS_WIN32)
@ -28,10 +31,16 @@
#define GLOBAL_DIR QString("/usr/share/gpxsee")
#endif
#define USER_MAP_FILE USER_DIR + QString("/") + MAP_FILE
#define USER_POI_DIR USER_DIR + QString("/") + POI_DIR
#define GLOBAL_MAP_FILE GLOBAL_DIR + QString("/") + MAP_FILE
#define GLOBAL_POI_DIR GLOBAL_DIR + QString("/") + POI_DIR
#define TILES_DIR USER_DIR + QString("/tiles")
#define USER_ELLIPSOID_FILE USER_DIR + QString("/") + ELLIPSOID_FILE
#define USER_DATUM_FILE USER_DIR + QString("/") + DATUM_FILE
#define USER_MAP_DIR USER_DIR + QString("/") + MAP_DIR
#define USER_MAP_FILE USER_DIR + QString("/") + MAP_FILE
#define USER_POI_DIR USER_DIR + QString("/") + POI_DIR
#define GLOBAL_ELLIPSOID_FILE GLOBAL_DIR + QString("/") + ELLIPSOID_FILE
#define GLOBAL_DATUM_FILE GLOBAL_DIR + QString("/") + DATUM_FILE
#define GLOBAL_MAP_DIR GLOBAL_DIR + QString("/") + MAP_DIR
#define GLOBAL_MAP_FILE GLOBAL_DIR + QString("/") + MAP_FILE
#define GLOBAL_POI_DIR GLOBAL_DIR + QString("/") + POI_DIR
#define TILES_DIR USER_DIR + QString("/tiles")
#endif /* CONFIG_H */

View File

@ -2,6 +2,10 @@
#include "wgs84.h"
#include "coordinates.h"
#define MIN_LAT deg2rad(-90.0)
#define MAX_LAT deg2rad(90.0)
#define MIN_LON deg2rad(-180.0)
#define MAX_LON deg2rad(180.0)
qreal Coordinates::distanceTo(const Coordinates &c) const
{
@ -13,25 +17,39 @@ qreal Coordinates::distanceTo(const Coordinates &c) const
return (WGS84_RADIUS * (2.0 * atan2(sqrt(a), sqrt(1.0 - a))));
}
QPointF Coordinates::toMercator() const
{
return QPointF(_lon, rad2deg(log(tan(M_PI/4.0 + deg2rad(_lat)/2.0))));
}
Coordinates Coordinates::fromMercator(const QPointF &m)
{
return Coordinates(m.x(), rad2deg(2 * atan(exp(deg2rad(m.y()))) - M_PI/2));
}
bool operator==(const Coordinates &c1, const Coordinates &c2)
{
return (c1.lat() == c2.lat() && c1.lon() == c2.lon());
}
QDebug operator<<(QDebug dbg, const Coordinates &coordinates)
{
dbg.nospace() << "Coordinates(" << coordinates.lon() << ", "
<< coordinates.lat() << ")";
return dbg.maybeSpace();
return dbg.space();
}
QPair<Coordinates, Coordinates> Coordinates::boundingRect(qreal distance) const
{
qreal radDist = distance / WGS84_RADIUS;
qreal minLat = deg2rad(_lat) - radDist;
qreal maxLat = deg2rad(_lat) + radDist;
qreal minLon, maxLon;
if (minLat > MIN_LAT && maxLat < MAX_LAT) {
qreal deltaLon = asin(sin(radDist) / cos(_lat));
minLon = deg2rad(_lon) - deltaLon;
if (minLon < MIN_LON)
minLon += 2.0 * M_PI;
maxLon = deg2rad(_lon) + deltaLon;
if (maxLon > MAX_LON)
maxLon -= 2.0 * M_PI;
} else {
// a pole is within the distance
minLat = qMax(minLat, MIN_LAT);
maxLat = qMin(maxLat, MAX_LAT);
minLon = MIN_LON;
maxLon = MAX_LON;
}
return QPair<Coordinates, Coordinates>(Coordinates(rad2deg(qMin(minLon,
maxLon)), rad2deg(qMin(minLat, maxLat))), Coordinates(rad2deg(qMax(minLon,
maxLon)), rad2deg(qMax(minLat, maxLat))));
}

View File

@ -12,6 +12,9 @@ public:
Coordinates(const Coordinates &c) {_lon = c._lon; _lat = c._lat;}
Coordinates(qreal lon, qreal lat) {_lon = lon; _lat = lat;}
Coordinates(const QPointF &p) {_lon = p.x(), _lat = p.y();}
QPointF toPointF() const {return QPointF(_lon, _lat);}
qreal &rlon() {return _lon;}
qreal &rlat() {return _lat;}
void setLon(qreal lon) {_lon = lon;}
@ -26,15 +29,16 @@ public:
&& _lat <= 90.0) ? true : false;}
qreal distanceTo(const Coordinates &c) const;
QPointF toMercator() const;
static Coordinates fromMercator(const QPointF &m);
QPair<Coordinates, Coordinates> boundingRect(qreal distance) const;
private:
qreal _lat, _lon;
};
bool operator==(const Coordinates &c1, const Coordinates &c2);
inline bool operator==(const Coordinates &c1, const Coordinates &c2)
{return (c1.lat() == c2.lat() && c1.lon() == c2.lon());}
inline bool operator!=(const Coordinates &c1, const Coordinates &c2)
{return !(c1 == c2);}
QDebug operator<<(QDebug dbg, const Coordinates &trackpoint);
#endif // COORDINATES_H

View File

@ -3,9 +3,8 @@
bool CSVParser::loadFile(QFile *file)
{
bool res;
int ln = 1;
_errorLine = 0;
_errorLine = 1;
_errorString.clear();
while (!file->atEnd()) {
@ -13,20 +12,17 @@ bool CSVParser::loadFile(QFile *file)
QList<QByteArray> list = line.split(',');
if (list.size() < 3) {
_errorString = "Parse error";
_errorLine = ln;
return false;
}
qreal lat = list[0].trimmed().toDouble(&res);
if (!res || (lat < -90.0 || lat > 90.0)) {
_errorString = "Invalid latitude";
_errorLine = ln;
return false;
}
qreal lon = list[1].trimmed().toDouble(&res);
if (!res || (lon < -180.0 || lon > 180.0)) {
_errorString = "Invalid longitude";
_errorLine = ln;
return false;
}
Waypoint wp(Coordinates(lon, lat));
@ -41,7 +37,7 @@ bool CSVParser::loadFile(QFile *file)
}
_waypoints.append(wp);
ln++;
_errorLine++;
}
return true;

View File

@ -6,6 +6,8 @@
#include "csvparser.h"
#include "kmlparser.h"
#include "fitparser.h"
#include "igcparser.h"
#include "nmeaparser.h"
#include "data.h"
@ -21,6 +23,10 @@ Data::Data() : _errorLine(0)
_waypointData));
_parsers.insert("csv", new CSVParser(_trackData, _routeData,
_waypointData));
_parsers.insert("igc", new IGCParser(_trackData, _routeData,
_waypointData));
_parsers.insert("nmea", new NMEAParser(_trackData, _routeData,
_waypointData));
}
Data::~Data()
@ -76,9 +82,9 @@ bool Data::loadFile(const QString &fileName)
file.reset();
}
fprintf(stderr, "Error loading data file: %s:\n", qPrintable(fileName));
qWarning("Error loading data file: %s:\n", qPrintable(fileName));
for (it = _parsers.begin(); it != _parsers.end(); it++)
fprintf(stderr, "%s: line %d: %s\n", qPrintable(it.key()),
qWarning("%s: line %d: %s\n", qPrintable(it.key()),
it.value()->errorLine(), qPrintable(it.value()->errorString()));
_errorLine = 0;

83
src/datum.cpp Normal file
View File

@ -0,0 +1,83 @@
#include <QFile>
#include "wgs84.h"
#include "datum.h"
static QMap<QString, Datum> WGS84()
{
QMap<QString, Datum> map;
map.insert("WGS 84", Datum(Ellipsoid(WGS84_RADIUS, WGS84_FLATTENING),
0, 0, 0));
return map;
}
QMap<QString, Datum> Datum::_datums = WGS84();
QString Datum::_errorString;
int Datum::_errorLine = 0;
Datum Datum::datum(const QString &name)
{
QMap<QString, Datum>::const_iterator it = _datums.find(name);
if (it == _datums.end())
return Datum();
return it.value();
}
bool Datum::loadList(const QString &path)
{
QFile file(path);
bool res;
if (!file.open(QFile::ReadOnly)) {
_errorString = qPrintable(file.errorString());
return false;
}
_errorLine = 1;
_errorString.clear();
while (!file.atEnd()) {
QByteArray line = file.readLine();
QList<QByteArray> list = line.split(',');
if (list.size() != 6) {
_errorString = "Format error";
return false;
}
int eid = list[2].trimmed().toInt(&res);
if (!res) {
_errorString = "Invalid ellipsoid id";
return false;
}
double dx = list[3].trimmed().toDouble(&res);
if (!res) {
_errorString = "Invalid dx";
return false;
}
double dy = list[4].trimmed().toDouble(&res);
if (!res) {
_errorString = "Invalid dy";
return false;
}
double dz = list[5].trimmed().toDouble(&res);
if (!res) {
_errorString = "Invalid dz";
return false;
}
Ellipsoid e = Ellipsoid::ellipsoid(eid);
if (e.isNull()) {
_errorString = "Unknown ellipsoid ID";
return false;
}
Datum d(e, dx, dy, dz);
_datums.insert(list[0].trimmed(), d);
_errorLine++;
}
return true;
}

38
src/datum.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef DATUM_H
#define DATUM_H
#include <QMap>
#include "ellipsoid.h"
class Datum
{
public:
Datum() : _ellipsoid(Ellipsoid()), _dx(0), _dy(0), _dz(0) {}
Datum(const Ellipsoid &ellipsoid, double dx, double dy, double dz)
: _ellipsoid(ellipsoid), _dx(dx), _dy(dy), _dz(dz) {}
const Ellipsoid &ellipsoid() const {return _ellipsoid;}
double dx() const {return _dx;}
double dy() const {return _dy;}
double dz() const {return _dz;}
bool isNull() const {return _ellipsoid.isNull();}
bool isWGS84() const
{return _ellipsoid.isWGS84() && _dx == 0 && _dy == 0 && _dz == 0;}
static bool loadList(const QString &path);
static const QString &errorString() {return _errorString;}
static int errorLine() {return _errorLine;}
static Datum datum(const QString &name);
private:
Ellipsoid _ellipsoid;
double _dx, _dy, _dz;
static QMap<QString, Datum> _datums;
static QString _errorString;
static int _errorLine;
};
#endif // DATUM_H

View File

@ -1,5 +1,7 @@
#include <QFile>
#include <QFileInfo>
#include <QNetworkRequest>
#include <QNetworkReply>
#include "config.h"
#include "downloader.h"
@ -17,25 +19,39 @@
#define USER_AGENT \
APP_NAME "/" APP_VERSION " (" PLATFORM_STR "; Qt " QT_VERSION_STR ")"
Downloader::Downloader()
#define ATTR_REDIRECT QNetworkRequest::RedirectionTargetAttribute
#define ATTR_FILE QNetworkRequest::User
#define ATTR_ORIGIN (QNetworkRequest::Attribute)(QNetworkRequest::User + 1)
#define ATTR_LEVEL (QNetworkRequest::Attribute)(QNetworkRequest::User + 2)
#define MAX_REDIRECT_LEVEL 5
Downloader::Downloader(QObject *parent) : QObject(parent)
{
connect(&_manager, SIGNAL(finished(QNetworkReply*)),
SLOT(downloadFinished(QNetworkReply*)));
}
bool Downloader::doDownload(const Download &dl)
bool Downloader::doDownload(const Download &dl, const Redirect &redirect)
{
QUrl url(dl.url());
if (_errorDownloads.contains(url))
return false;
if (_currentDownloads.contains(url))
return false;
QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::User, QVariant(dl.file()));
request.setAttribute(ATTR_FILE, QVariant(dl.file()));
if (!redirect.isNull()) {
request.setAttribute(ATTR_ORIGIN, QVariant(redirect.origin()));
request.setAttribute(ATTR_LEVEL, QVariant(redirect.level()));
}
request.setRawHeader("User-Agent", USER_AGENT);
QNetworkReply *reply = _manager.get(request);
_currentDownloads.append(reply);
_currentDownloads.insert(url, reply);
return true;
}
@ -45,7 +61,7 @@ bool Downloader::saveToDisk(const QString &filename, QIODevice *data)
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
fprintf(stderr, "Error writing map tile: %s: %s\n",
qWarning("Error writing map tile: %s: %s\n",
qPrintable(filename), qPrintable(file.errorString()));
return false;
}
@ -58,26 +74,49 @@ bool Downloader::saveToDisk(const QString &filename, QIODevice *data)
void Downloader::downloadFinished(QNetworkReply *reply)
{
QUrl url = reply->url();
QUrl url = reply->request().url();
if (reply->error()) {
_errorDownloads.insert(url);
fprintf(stderr, "Error downloading map tile: %s: %s\n",
url.toEncoded().constData(), qPrintable(reply->errorString()));
QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl();
if (origin.isEmpty()) {
_errorDownloads.insert(url);
qWarning("Error downloading map tile: %s: %s\n",
url.toEncoded().constData(), qPrintable(reply->errorString()));
} else {
_errorDownloads.insert(origin);
qWarning("Error downloading map tile: %s -> %s: %s\n",
origin.toEncoded().constData(), url.toEncoded().constData(),
qPrintable(reply->errorString()));
}
} else {
QUrl redirect = reply->attribute(
QNetworkRequest::RedirectionTargetAttribute).toUrl();
QString filename = reply->request().attribute(QNetworkRequest::User)
QUrl location = reply->attribute(ATTR_REDIRECT).toUrl();
QString filename = reply->request().attribute(ATTR_FILE)
.toString();
if (!redirect.isEmpty()) {
Download dl(redirect, filename);
doDownload(dl);
if (!location.isEmpty()) {
QUrl origin = reply->request().attribute(ATTR_ORIGIN).toUrl();
int level = reply->request().attribute(ATTR_LEVEL).toInt();
if (location == url) {
_errorDownloads.insert(url);
qWarning("Error downloading map tile: %s: "
"redirect loop\n", url.toEncoded().constData());
} else if (level >= MAX_REDIRECT_LEVEL) {
_errorDownloads.insert(origin);
qWarning("Error downloading map tile: %s: "
"redirect level limit reached\n",
origin.toEncoded().constData());
} else {
Redirect redirect(origin.isEmpty() ? url : origin, level + 1);
Download dl(location, filename);
doDownload(dl, redirect);
}
} else
if (!saveToDisk(filename, reply))
_errorDownloads.insert(url);
}
_currentDownloads.removeAll(reply);
_currentDownloads.remove(url);
reply->deleteLater();
if (_currentDownloads.isEmpty())

View File

@ -2,13 +2,14 @@
#define DOWNLOADER_H
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QUrl>
#include <QList>
#include <QMap>
#include <QSet>
class QNetworkReply;
class Download
{
public:
@ -28,8 +29,8 @@ class Downloader : public QObject
Q_OBJECT
public:
static Downloader& instance()
{static Downloader i; return i;}
Downloader(QObject *parent = 0);
bool get(const QList<Download> &list);
signals:
@ -39,15 +40,28 @@ private slots:
void downloadFinished(QNetworkReply *reply);
private:
Downloader();
Downloader(Downloader const&);
void operator=(Downloader const&);
class Redirect
{
public:
Redirect() : _level(0) {}
Redirect(const QUrl &origin, int level) :
_origin(origin), _level(level) {}
bool doDownload(const Download &dl);
const QUrl &origin() const {return _origin;}
int level() const {return _level;}
bool isNull() const {return (_level == 0);}
private:
QUrl _origin;
int _level;
};
bool doDownload(const Download &dl, const Redirect &redirect = Redirect());
bool saveToDisk(const QString &filename, QIODevice *data);
QNetworkAccessManager _manager;
QList<QNetworkReply *> _currentDownloads;
QMap<QUrl, QNetworkReply *> _currentDownloads;
QSet<QUrl> _errorDownloads;
};

62
src/ellipsoid.cpp Normal file
View File

@ -0,0 +1,62 @@
#include <QFile>
#include "ellipsoid.h"
QMap<int, Ellipsoid> Ellipsoid::_ellipsoids;
QString Ellipsoid::_errorString;
int Ellipsoid::_errorLine = 0;
Ellipsoid Ellipsoid::ellipsoid(int id)
{
QMap<int, Ellipsoid>::const_iterator it = _ellipsoids.find(id);
if (it == _ellipsoids.end())
return Ellipsoid();
return it.value();
}
bool Ellipsoid::loadList(const QString &path)
{
QFile file(path);
bool res;
if (!file.open(QFile::ReadOnly)) {
_errorString = qPrintable(file.errorString());
return false;
}
_errorLine = 1;
_errorString.clear();
while (!file.atEnd()) {
QByteArray line = file.readLine();
QList<QByteArray> list = line.split(',');
if (list.size() != 4) {
_errorString = "Format error";
return false;
}
int id = list[0].trimmed().toInt(&res);
if (!res) {
_errorString = "Invalid ellipsoid id";
return false;
}
double radius = list[2].trimmed().toDouble(&res);
if (!res) {
_errorString = "Invalid ellipsoid radius";
return false;
}
double flattening = list[3].trimmed().toDouble(&res);
if (!res) {
_errorString = "Invalid ellipsoid flattening";
return false;
}
Ellipsoid e(radius, 1.0/flattening);
_ellipsoids.insert(id, e);
_errorLine++;
}
return true;
}

37
src/ellipsoid.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef ELLIPSOID_H
#define ELLIPSOID_H
#include <QString>
#include <QMap>
#include "wgs84.h"
class Ellipsoid
{
public:
Ellipsoid() : _radius(-1.0), _flattening(-1.0) {}
Ellipsoid(double radius, double flattening)
: _radius(radius), _flattening(flattening) {}
double radius() const {return _radius;}
double flattening() const {return _flattening;}
bool isNull() const {return _radius < 0 || _flattening < 0;}
bool isWGS84() const
{return _radius == WGS84_RADIUS && _flattening == WGS84_FLATTENING;}
static bool loadList(const QString &path);
static const QString &errorString() {return _errorString;}
static int errorLine() {return _errorLine;}
static Ellipsoid ellipsoid(int id);
private:
double _radius;
double _flattening;
static QMap<int, Ellipsoid> _ellipsoids;
static QString _errorString;
static int _errorLine;
};
#endif // ELLIPSOID_H

77
src/emptymap.cpp Normal file
View File

@ -0,0 +1,77 @@
#include <QtGlobal>
#include <QPainter>
#include "misc.h"
#include "rd.h"
#include "wgs84.h"
#include "coordinates.h"
#include "mercator.h"
#include "emptymap.h"
#define SCALE_MIN 0.5
#define SCALE_MAX 1.0E-6
EmptyMap::EmptyMap(QObject *parent) : Map(parent)
{
_scale = SCALE_MAX;
}
QRectF EmptyMap::bounds() const
{
return scaled(QRectF(QPointF(-180, -180), QSizeF(360, 360)), 1.0/_scale);
}
qreal EmptyMap::zoomFit(const QSize &size, const QRectF &br)
{
if (br.isNull())
_scale = SCALE_MAX;
else {
Coordinates topLeft(br.topLeft());
Coordinates bottomRight(br.bottomRight());
QRectF tbr(Mercator().ll2xy(topLeft), Mercator().ll2xy(bottomRight));
QPointF sc(tbr.width() / size.width(), tbr.height() / size.height());
_scale = qMax(sc.x(), sc.y());
}
_scale = qMax(_scale, SCALE_MAX);
_scale = qMin(_scale, SCALE_MIN);
return _scale;
}
qreal EmptyMap::resolution(const QPointF &p) const
{
return (WGS84_RADIUS * 2 * M_PI * _scale / 360.0
* cos(2.0 * atan(exp(deg2rad(-p.y() * _scale))) - M_PI/2));
}
qreal EmptyMap::zoomIn()
{
_scale = qMax(_scale / 2.0, SCALE_MAX);
return _scale;
}
qreal EmptyMap::zoomOut()
{
_scale = qMin(_scale * 2.0, SCALE_MIN);
return _scale;
}
void EmptyMap::draw(QPainter *painter, const QRectF &rect)
{
painter->fillRect(rect, Qt::white);
}
QPointF EmptyMap::ll2xy(const Coordinates &c)
{
QPointF m = Mercator().ll2xy(c);
return QPointF(m.x() / _scale, m.y() / -_scale);
}
Coordinates EmptyMap::xy2ll(const QPointF &p)
{
QPointF m(p.x() * _scale, -p.y() * _scale);
return Mercator().xy2ll(m);
}

33
src/emptymap.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef EMPTYMAP_H
#define EMPTYMAP_H
#include "map.h"
class EmptyMap : public Map
{
Q_OBJECT
public:
EmptyMap(QObject *parent = 0);
const QString &name() const {return _name;}
QRectF bounds() const;
qreal resolution(const QPointF &p) const;
qreal zoom() const {return _scale;}
qreal zoomFit(const QSize &size, const QRectF &br);
qreal zoomIn();
qreal zoomOut();
QPointF ll2xy(const Coordinates &c);
Coordinates xy2ll(const QPointF &p);
void draw(QPainter *painter, const QRectF &rect);
private:
QString _name;
qreal _scale;
};
#endif // EMPTYMAP_H

View File

@ -11,23 +11,19 @@
#include <QMessageBox>
#include <QTabWidget>
#include <QDoubleSpinBox>
#include <QLocale>
#include "fileselectwidget.h"
#include "units.h"
#include "exportdialog.h"
ExportDialog::ExportDialog(QPrinter *printer, QWidget *parent)
: QDialog(parent), _printer(printer)
ExportDialog::ExportDialog(Export *exp, QWidget *parent)
: QDialog(parent), _export(exp)
{
int index;
_units = (QLocale::system().measurementSystem()
== QLocale::ImperialSystem) ? QPrinter::Inch : QPrinter::Millimeter;
_fileSelect = new FileSelectWidget();
_fileSelect->setFilter(tr("PDF files (*.pdf);;All files (*)"));
_fileSelect->setFile(_printer->outputFileName());
_fileSelect->setFile(_export->fileName);
_paperSize = new QComboBox();
_paperSize->addItem("A3", QPrinter::A3);
@ -36,7 +32,7 @@ ExportDialog::ExportDialog(QPrinter *printer, QWidget *parent)
_paperSize->addItem("Tabloid", QPrinter::Tabloid);
_paperSize->addItem("Legal", QPrinter::Legal);
_paperSize->addItem("Letter", QPrinter::Letter);
if ((index = _paperSize->findData(_printer->paperSize())) >= 0)
if ((index = _paperSize->findData(_export->paperSize)) >= 0)
_paperSize->setCurrentIndex(index);
_portrait = new QRadioButton(tr("Portrait"));
@ -44,32 +40,34 @@ ExportDialog::ExportDialog(QPrinter *printer, QWidget *parent)
QHBoxLayout *orientationLayout = new QHBoxLayout();
orientationLayout->addWidget(_portrait);
orientationLayout->addWidget(_landscape);
if (_printer->orientation() == QPrinter::Portrait)
if (_export->orientation == QPrinter::Portrait)
_portrait->setChecked(true);
else
_landscape->setChecked(true);
qreal top, bottom, left, right;
_printer->getPageMargins(&left, &top, &right, &bottom, _units);
QString us = _units == QPrinter::Inch ? tr("in") : tr("mm");
_topMargin = new QDoubleSpinBox();
_bottomMargin = new QDoubleSpinBox();
_leftMargin = new QDoubleSpinBox();
_rightMargin = new QDoubleSpinBox();
_topMargin->setValue(top);
QString us = (_export->units == Imperial) ? tr("in") : tr("mm");
_topMargin->setSuffix(UNIT_SPACE + us);
_bottomMargin->setValue(bottom);
_bottomMargin->setSuffix(UNIT_SPACE + us);
_leftMargin->setValue(left);
_leftMargin->setSuffix(UNIT_SPACE + us);
_rightMargin->setValue(right);
_rightMargin->setSuffix(UNIT_SPACE + us);
if (_units == QPrinter::Inch) {
if (_export->units == Imperial) {
_topMargin->setValue(_export->margins.top() * MM2IN);
_bottomMargin->setValue(_export->margins.bottom() * MM2IN);
_leftMargin->setValue(_export->margins.left() * MM2IN);
_rightMargin->setValue(_export->margins.right() * MM2IN);
_topMargin->setSingleStep(0.1);
_bottomMargin->setSingleStep(0.1);
_leftMargin->setSingleStep(0.1);
_rightMargin->setSingleStep(0.1);
} else {
_topMargin->setValue(_export->margins.top());
_bottomMargin->setValue(_export->margins.bottom());
_leftMargin->setValue(_export->margins.left());
_rightMargin->setValue(_export->margins.right());
}
QGridLayout *marginsLayout = new QGridLayout();
@ -164,12 +162,16 @@ void ExportDialog::accept()
QPrinter::PaperSize paperSize = static_cast<QPrinter::PaperSize>
(_paperSize->itemData(_paperSize->currentIndex()).toInt());
_printer->setOutputFormat(QPrinter::PdfFormat);
_printer->setOutputFileName(_fileSelect->file());
_printer->setPaperSize(paperSize);
_printer->setOrientation(orientation);
_printer->setPageMargins(_leftMargin->value(), _topMargin->value(),
_rightMargin->value(), _bottomMargin->value(), _units);
_export->fileName = _fileSelect->file();
_export->paperSize = paperSize;
_export->orientation = orientation;
if (_export->units == Imperial)
_export->margins = MarginsF(_leftMargin->value() / MM2IN,
_topMargin->value() / MM2IN, _rightMargin->value() / MM2IN,
_bottomMargin->value() / MM2IN);
else
_export->margins = MarginsF(_leftMargin->value(), _topMargin->value(),
_rightMargin->value(), _bottomMargin->value());
QDialog::accept();
}

View File

@ -3,18 +3,29 @@
#include <QDialog>
#include <QPrinter>
#include "margins.h"
#include "units.h"
class QComboBox;
class QRadioButton;
class FileSelectWidget;
class QDoubleSpinBox;
struct Export {
QString fileName;
QPrinter::PaperSize paperSize;
QPrinter::Orientation orientation;
MarginsF margins;
Units units;
};
class ExportDialog : public QDialog
{
Q_OBJECT
public:
ExportDialog(QPrinter *printer, QWidget *parent = 0);
ExportDialog(Export *exp, QWidget *parent = 0);
public slots:
void accept();
@ -22,9 +33,7 @@ public slots:
private:
bool checkFile();
QPrinter *_printer;
QPrinter::Unit _units;
Export *_export;
FileSelectWidget *_fileSelect;
QComboBox *_paperSize;

View File

@ -14,6 +14,11 @@ FITParser::FITParser(QList<TrackData> &tracks, QList<RouteData> &routes,
QList<Waypoint> &waypoints) : Parser(tracks, routes, waypoints)
{
memset(_defs, 0, sizeof(_defs));
_device = 0;
_endian = 0;
_timestamp = 0;
_len = 0;
}
void FITParser::clearDefinitions()
@ -31,8 +36,7 @@ void FITParser::clearDefinitions()
void FITParser::warning(const char *text) const
{
const QFile *file = static_cast<QFile *>(_device);
fprintf(stderr, "%s:%d: %s\n", qPrintable(file->fileName()),
_len, text);
qWarning("%s:%d: %s\n", qPrintable(file->fileName()), _len, text);
}
bool FITParser::readData(char *data, size_t size)
@ -335,10 +339,11 @@ bool FITParser::parseHeader()
bool FITParser::loadFile(QFile *file)
{
bool ret = true;
_device = file;
_endian = 0;
_timestamp = 0;
bool ret = true;
if (!parseHeader())
return false;

View File

@ -1,9 +1,9 @@
#include "graph.h"
QDebug operator<<(QDebug dbg, const GraphPoint &graphpoint)
QDebug operator<<(QDebug dbg, const GraphPoint &point)
{
dbg.nospace() << "GraphPoint(" << graphpoint.s() << ", "
<< graphpoint.t() << ", " << graphpoint.y() << ")";
dbg.nospace() << "GraphPoint(" << point.s() << ", " << point.t() << ", "
<< point.y() << ")";
return dbg.maybeSpace();
}

View File

@ -29,24 +29,8 @@ private:
};
Q_DECLARE_TYPEINFO(GraphPoint, Q_PRIMITIVE_TYPE);
QDebug operator<<(QDebug dbg, const GraphPoint &graphpoint);
QDebug operator<<(QDebug dbg, const GraphPoint &point);
class Graph : public QVector<GraphPoint>
{
public:
Graph() : QVector<GraphPoint>() {_time = true;}
void append(const GraphPoint &p)
{
if (std::isnan(p.t()))
_time = false;
QVector<GraphPoint>::append(p);
}
bool hasTime() const {return _time;}
private:
bool _time;
};
typedef QVector<GraphPoint> Graph;
#endif // GRAPH_H

View File

@ -2,20 +2,28 @@
#include "graphitem.h"
#define GRAPH_WIDTH 1
#define HOVER_WIDTH 2
GraphItem::GraphItem(const Graph &graph, QGraphicsItem *parent)
: QGraphicsObject(parent)
{
_id = 0;
_width = 1;
_pen = QPen(Qt::black, GRAPH_WIDTH);
_pen = QPen(Qt::black, _width);
_type = Distance;
_graph = graph;
_sx = 1.0; _sy = 1.0;
_time = true;
for (int i = 0; i < _graph.size(); i++) {
if (std::isnan(_graph.at(i).t())) {
_time = false;
break;
}
}
setZValue(1.0);
updatePath();
updateBounds();
}
@ -51,6 +59,14 @@ void GraphItem::setColor(const QColor &color)
update();
}
void GraphItem::setWidth(int width)
{
prepareGeometryChange();
_width = width;
_pen.setWidth(width);
}
qreal GraphItem::yAtX(qreal x)
{
int low = 0;
@ -116,13 +132,13 @@ qreal GraphItem::distanceAtTime(qreal time)
void GraphItem::emitSliderPositionChanged(qreal pos)
{
if (_type == Time) {
if (_graph.hasTime()) {
if (pos <= _graph.last().t())
if (_time) {
if (pos >= _graph.first().t() && pos <= _graph.last().t())
emit sliderPositionChanged(distanceAtTime(pos));
else
emit sliderPositionChanged(_graph.last().s() + 1);
emit sliderPositionChanged(NAN);
} else
emit sliderPositionChanged(_graph.last().s() + 1);
emit sliderPositionChanged(NAN);
} else
emit sliderPositionChanged(pos);
}
@ -130,10 +146,10 @@ void GraphItem::emitSliderPositionChanged(qreal pos)
void GraphItem::selected(bool selected)
{
if (selected) {
_pen.setWidth(HOVER_WIDTH);
_pen.setWidth(_width + 1);
setZValue(zValue() + 1.0);
} else {
_pen.setWidth(GRAPH_WIDTH);
_pen.setWidth(_width);
setZValue(zValue() - 1.0);
}
@ -155,7 +171,7 @@ void GraphItem::updatePath()
{
_path = QPainterPath();
if (_type == Time && !_graph.hasTime())
if (_type == Time && !_time)
return;
_path.moveTo(_graph.first().x(_type) * _sx, -_graph.first().y() * _sy);
@ -165,7 +181,7 @@ void GraphItem::updatePath()
void GraphItem::updateBounds()
{
if (_type == Time && !_graph.hasTime()) {
if (_type == Time && !_time) {
_bounds = QRectF();
return;
}

View File

@ -24,6 +24,7 @@ public:
int id() const {return _id;}
void setId(int id) {_id = id;}
void setColor(const QColor &color);
void setWidth(int width);
qreal yAtX(qreal x);
qreal distanceAtTime(qreal time);
@ -41,6 +42,7 @@ private:
int _id;
QPen _pen;
int _width;
Graph _graph;
GraphType _type;
@ -48,6 +50,8 @@ private:
QPainterPath _path;
QRectF _bounds;
qreal _sx, _sy;
bool _time;
};
#endif // GRAPHITEM_H

View File

@ -4,6 +4,7 @@
#include <QList>
#include "graphview.h"
#include "units.h"
#include "timetype.h"
class Data;
class PathItem;
@ -18,10 +19,11 @@ public:
virtual QString label() const = 0;
virtual void loadData(const Data &data, const QList<PathItem *> &paths) = 0;
virtual void clear() = 0;
virtual void setUnits(enum Units units) = 0;
virtual void showTracks(bool show) = 0;
virtual void showRoutes(bool show) = 0;
virtual void clear() {}
virtual void setUnits(enum Units units) {Q_UNUSED(units)}
virtual void setTimeType(enum TimeType type) {Q_UNUSED(type)}
virtual void showTracks(bool show) {Q_UNUSED(show)}
virtual void showRoutes(bool show) {Q_UNUSED(show)}
};
#endif // GRAPHTAB_H

View File

@ -3,6 +3,7 @@
#include <QMouseEvent>
#include <QPaintEngine>
#include <QPaintDevice>
#include "opengl.h"
#include "config.h"
#include "axisitem.h"
#include "slideritem.h"
@ -20,26 +21,31 @@
GraphView::GraphView(QWidget *parent)
: QGraphicsView(parent)
{
_scene = new QGraphicsScene();
_scene = new QGraphicsScene(this);
setScene(_scene);
setBackgroundBrush(QBrush(Qt::white));
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setRenderHint(QPainter::Antialiasing, true);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
_xAxis = new AxisItem(AxisItem::X);
_xAxis->setZValue(1.0);
_xAxis->setZValue(2.0);
_yAxis = new AxisItem(AxisItem::Y);
_yAxis->setZValue(1.0);
_yAxis->setZValue(2.0);
_slider = new SliderItem();
_slider->setZValue(2.0);
_slider->setZValue(3.0);
_sliderInfo = new SliderInfoItem(_slider);
_sliderInfo->setZValue(2.0);
_sliderInfo->setZValue(3.0);
_info = new InfoItem();
_grid = new GridItem();
connect(_slider, SIGNAL(positionChanged(const QPointF&)), this,
SLOT(emitSliderPositionChanged(const QPointF&)));
_width = 1;
_xScale = 1;
_yScale = 1;
_yOffset = 0;
@ -169,7 +175,8 @@ void GraphView::loadGraph(const Graph &graph, PathItem *path, int id)
GraphItem *gi = new GraphItem(graph);
gi->setGraphType(_graphType);
gi->setId(id);
gi->setColor(_palette.color());
gi->setColor(_palette.nextColor());
gi->setWidth(_width);
connect(this, SIGNAL(sliderPositionChanged(qreal)), gi,
SLOT(emitSliderPositionChanged(qreal)));
@ -400,6 +407,8 @@ void GraphView::emitSliderPositionChanged(const QPointF &pos)
return;
_sliderPos = (pos.x() / _slider->area().width()) * bounds().width();
_sliderPos = qMax(_sliderPos, bounds().left());
_sliderPos = qMin(_sliderPos, bounds().right());
updateSliderPosition();
emit sliderPositionChanged(_sliderPos);
@ -429,3 +438,28 @@ void GraphView::clearInfo()
{
_info->clear();
}
void GraphView::setPalette(const Palette &palette)
{
_palette = palette;
_palette.reset();
for (int i = 0; i < _graphs.count(); i++)
_graphs.at(i)->setColor(_palette.nextColor());
}
void GraphView::setGraphWidth(int width)
{
_width = width;
for (int i = 0; i < _graphs.count(); i++)
_graphs.at(i)->setWidth(width);
}
void GraphView::useOpenGL(bool use)
{
if (use)
setViewport(new OPENGL_WIDGET);
else
setViewport(new QWidget);
}

View File

@ -35,6 +35,9 @@ public:
void setUnits(Units units);
void showGrid(bool show);
void setPalette(const Palette &palette);
void setGraphWidth(int width);
const QString &yLabel() const {return _yLabel;}
const QString &yUnits() const {return _yUnits;}
qreal yScale() const {return _yScale;}
@ -52,6 +55,8 @@ public:
void plot(QPainter *painter, const QRectF &target);
void useOpenGL(bool use);
signals:
void sliderPositionChanged(qreal);
@ -60,7 +65,7 @@ protected:
void redraw(const QSizeF &size);
void addInfo(const QString &key, const QString &value);
void clearInfo();
void skipColor() {_palette.color();}
void skipColor() {_palette.nextColor();}
private slots:
void emitSliderPositionChanged(const QPointF &pos);
@ -99,6 +104,7 @@ private:
QSet<int> _hide;
QRectF _bounds;
Palette _palette;
int _width;
Units _units;
GraphType _graphType;

View File

@ -6,7 +6,9 @@
GridItem::GridItem(QGraphicsItem *parent) : QGraphicsItem(parent)
{
#ifndef Q_OS_MAC
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
#endif // Q_OS_MAC
}
void GridItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,12 @@
#include <QDate>
#include <QPrinter>
#include "units.h"
#include "timetype.h"
#include "graph.h"
#include "poi.h"
#include "margins.h"
#include "exportdialog.h"
#include "optionsdialog.h"
class QMenu;
class QToolBar;
@ -23,13 +26,14 @@ class FileBrowser;
class GraphTab;
class PathView;
class Map;
class MapList;
class GUI : public QMainWindow
{
Q_OBJECT
public:
GUI(QWidget *parent = 0);
GUI();
~GUI();
bool openFile(const QString &fileName);
@ -45,16 +49,17 @@ private slots:
void reloadFile();
void openPOIFile();
void closePOIFiles();
void showMap(bool show);
void showGraphs(bool show);
void showGraphGrids(bool show);
void showToolbars(bool show);
void showFullscreen(bool show);
void showTracks(bool show);
void showRoutes(bool show);
void loadMap();
void clearMapCache();
void nextMap();
void prevMap();
void openOptions();
void mapChanged(int);
void graphChanged(int);
@ -65,6 +70,8 @@ private slots:
void last();
void first();
void setTotalTime() {setTimeType(Total);}
void setMovingTime() {setTimeType(Moving);}
void setMetricUnits() {setUnits(Metric);}
void setImperialUnits() {setUnits(Imperial);}
void setDistanceGraph() {setGraphType(Distance);}
@ -75,6 +82,7 @@ private slots:
private:
typedef QPair<QDate, QDate> DateRange;
void loadDatums();
void loadMaps();
void loadPOIs();
void closeFiles();
@ -100,11 +108,15 @@ private:
void updateGraphTabs();
void updatePathView();
TimeType timeType() const;
Units units() const;
void setTimeType(TimeType type);
void setUnits(Units units);
void setGraphType(GraphType type);
qreal distance() const;
qreal time() const;
qreal movingTime() const;
int mapIndex(const QString &name);
void readSettings();
void writeSettings();
@ -120,9 +132,11 @@ private:
QToolBar *_showToolBar;
QToolBar *_navigationToolBar;
QMenu *_poiFilesMenu;
QMenu *_mapMenu;
QActionGroup *_fileActionGroup;
QActionGroup *_navigationActionGroup;
QActionGroup *_mapsActionGroup;
QAction *_exitAction;
QAction *_keysAction;
QAction *_dataSourcesAction;
@ -140,6 +154,7 @@ private:
QAction *_showPOILabelsAction;
QAction *_showMapAction;
QAction *_fullscreenAction;
QAction *_loadMapAction;
QAction *_clearMapCacheAction;
QAction *_showGraphsAction;
QAction *_showGraphGridAction;
@ -152,6 +167,8 @@ private:
QAction *_firstAction;
QAction *_metricUnitsAction;
QAction *_imperialUnitsAction;
QAction *_totalTimeAction;
QAction *_movingTimeAction;
QAction *_nextMapAction;
QAction *_prevMapAction;
QAction *_showTracksAction;
@ -159,10 +176,13 @@ private:
QAction *_showWaypointsAction;
QAction *_showWaypointLabelsAction;
QAction *_showRouteWaypointsAction;
QAction *_openOptionsAction;
QAction *_mapsEnd;
QList<QAction*> _mapActions;
QList<QAction*> _poiFilesActions;
QSignalMapper *_poiFilesSM;
QSignalMapper *_poiFilesSignalMapper;
QSignalMapper *_mapsSignalMapper;
QLabel *_fileNameLabel;
QLabel *_distanceLabel;
@ -173,29 +193,29 @@ private:
QList<GraphTab*> _tabs;
POI *_poi;
QList<Map*> _maps;
MapList *_ml;
FileBrowser *_browser;
QList<QString> _files;
Map *_currentMap;
Map *_map;
int _trackCount;
int _routeCount;
int _waypointCount;
qreal _trackDistance;
qreal _routeDistance;
qreal _time;
qreal _movingTime;
DateRange _dateRange;
QString _pathName;
qreal _sliderPos;
int _frameStyle;
bool _showGraphs;
QString _exportFileName;
QPrinter::PaperSize _exportPaperSize;
QPrinter::Orientation _exportOrientation;
MarginsF _exportMargins;
Export _export;
Options _options;
};
#endif // GUI_H

View File

@ -13,9 +13,7 @@ public:
QString label() const {return tr("Heart rate");}
void loadData(const Data &data, const QList<PathItem *> &paths);
void clear();
void setUnits(enum Units) {}
void showTracks(bool show);
void showRoutes(bool show) {Q_UNUSED(show);}
private:
qreal avg() const;

View File

@ -1,20 +1,29 @@
#ifndef ICONS_H
#define ICONS_H
#define APP_ICON ":/icons/gpxsee.png"
#define OPEN_FILE_ICON ":/icons/document-open.png"
#define EXPORT_FILE_ICON ":/icons/document-export.png"
#define PRINT_FILE_ICON ":/icons/document-print.png"
#define CLOSE_FILE_ICON ":/icons/dialog-close.png"
#define SHOW_POI_ICON ":/icons/flag.png"
#define SHOW_MAP_ICON ":/icons/applications-internet.png"
#define SHOW_GRAPHS_ICON ":/icons/office-chart-line-stacked.png"
#define QUIT_ICON ":/icons/application-exit.png"
#define RELOAD_FILE_ICON ":/icons/view-refresh.png"
#define NEXT_FILE_ICON ":/icons/arrow-right.png"
#define PREV_FILE_ICON ":/icons/arrow-left.png"
#define LAST_FILE_ICON ":/icons/arrow-right-double.png"
#define FIRST_FILE_ICON ":/icons/arrow-left-double.png"
#define FULLSCREEN_ICON ":/icons/view-fullscreen.png"
#define APP_ICON ":/icons/gpxsee.png"
// Toolbar/menu icons
#define OPEN_FILE_ICON ":/icons/document-open.png"
#define EXPORT_FILE_ICON ":/icons/document-export.png"
#define PRINT_FILE_ICON ":/icons/document-print.png"
#define CLOSE_FILE_ICON ":/icons/dialog-close.png"
#define SHOW_POI_ICON ":/icons/flag.png"
#define SHOW_MAP_ICON ":/icons/applications-internet.png"
#define SHOW_GRAPHS_ICON ":/icons/office-chart-line.png"
#define QUIT_ICON ":/icons/application-exit.png"
#define RELOAD_FILE_ICON ":/icons/view-refresh.png"
#define NEXT_FILE_ICON ":/icons/arrow-right.png"
#define PREV_FILE_ICON ":/icons/arrow-left.png"
#define LAST_FILE_ICON ":/icons/arrow-right-double.png"
#define FIRST_FILE_ICON ":/icons/arrow-left-double.png"
#define FULLSCREEN_ICON ":/icons/view-fullscreen.png"
// Options dialog icons
#define APPEARANCE_ICON ":/icons/preferences-desktop-display.png"
#define POI_ICON ":/icons/flag_48.png"
#define SYSTEM_ICON ":/icons/system-run.png"
#define PRINT_EXPORT_ICON ":/icons/document-print-preview.png"
#define DATA_ICON ":/icons/view-filter.png"
#endif /* ICONS_H */

249
src/igcparser.cpp Normal file
View File

@ -0,0 +1,249 @@
#include <cstring>
#include "misc.h"
#include "igcparser.h"
static bool readLat(const char *data, qreal &lat)
{
int d = str2int(data, 2);
int mi = str2int(data + 2, 2);
int mf = str2int(data + 4, 3);
if (d < 0 || mi < 0 || mf < 0)
return false;
if (!(data[7] == 'N' || data[7] == 'S'))
return false;
lat = d + (((qreal)mi + (qreal)mf/1000) / 60);
if (lat > 90)
return false;
if (data[7] == 'S')
lat = -lat;
return true;
}
static bool readLon(const char *data, qreal &lon)
{
int d = str2int(data, 3);
int mi = str2int(data + 3, 2);
int mf = str2int(data + 5, 3);
if (d < 0 || mi < 0 || mf < 0)
return false;
if (!(data[8] == 'E' || data[8] == 'W'))
return false;
lon = d + (((qreal)mi + (qreal)mf/1000) / 60);
if (lon > 180)
return false;
if (data[8] == 'W')
lon = -lon;
return true;
}
static bool readAltitude(const char *data, qreal &ele)
{
int p;
if (!(data[0] == 'A' || data[0] == 'V'))
return false;
if (data[1] == '-')
p = str2int(data + 2, 4);
else
p = str2int(data + 1, 5);
int g = str2int(data + 6, 5);
if (p < 0 || g < 0)
return false;
if (data[0] == 'A')
ele = (qreal)g;
else
ele = NAN;
return true;
}
static bool readTimestamp(const char *data, QTime &time)
{
int h = str2int(data, 2);
int m = str2int(data + 2, 2);
int s = str2int(data + 4, 2);
if (h < 0 || m < 0 || s < 0)
return false;
time = QTime(h, m, s);
if (!time.isValid())
return false;
return true;
}
static bool readARecord(const char *line, qint64 len)
{
if (len < 9 || line[0] != 'A')
return false;
for (int i = 1; i < 7; i++)
if (!::isprint(line[i]))
return false;
return true;
}
bool IGCParser::readHRecord(const char *line, int len)
{
if (len < 10 || ::strncmp(line, "HFDTE", 5))
return true;
int d = str2int(line + 5, 2);
int m = str2int(line + 7, 2);
int y = str2int(line + 9, 2);
if (y < 0 || m < 0 || d < 0) {
_errorString = "Invalid date header format";
return false;
}
_date = QDate(y + 2000 < QDate::currentDate().year() ? 2000 + y : 1900 + y,
m, d);
if (!_date.isValid()) {
_errorString = "Invalid date";
return false;
}
return true;
}
bool IGCParser::readBRecord(const char *line, int len)
{
qreal lat, lon, ele;
QTime time;
if (len < 35)
return false;
if (!readTimestamp(line + 1, time)) {
_errorString = "Invalid timestamp";
return false;
}
if (!readLat(line + 7, lat)) {
_errorString = "Invalid latitude";
return false;
}
if (!readLon(line + 15, lon)) {
_errorString = "Invalid longitude";
return false;
}
if (!readAltitude(line + 24, ele)) {
_errorString = "Invalid altitude";
return false;
}
if (time < _time)
_date = _date.addDays(1);
_time = time;
Trackpoint t(Coordinates(lon, lat));
t.setTimestamp(QDateTime(_date, _time, Qt::UTC));
t.setElevation(ele);
_tracks.last().append(t);
return true;
}
bool IGCParser::readCRecord(const char *line, int len)
{
qreal lat, lon;
if (len < 18)
return false;
if (!readLat(line + 1, lat)) {
_errorString = "Invalid latitude";
return false;
}
if (!readLon(line + 9, lon)) {
_errorString = "Invalid longitude";
return false;
}
if (!(lat == 0 && lon == 0)) {
QByteArray ba(line + 18, len - 19);
Waypoint w(Coordinates(lon, lat));
w.setName(QString(ba.trimmed()));
_routes.last().append(w);
}
return true;
}
bool IGCParser::loadFile(QFile *file)
{
qint64 len;
char line[76 + 2 + 1 + 1];
bool route = false, track = false;
_errorLine = 1;
_errorString.clear();
while (!file->atEnd()) {
len = file->readLine(line, sizeof(line));
if (len < 0) {
_errorString = "I/O error";
return false;
} else if (len > (qint64)sizeof(line) - 1) {
_errorString = "Line limit exceeded";
return false;
}
if (_errorLine == 1) {
if (!readARecord(line, len)) {
_errorString = "Invalid/missing A record";
return false;
}
} else {
if (line[0] == 'H') {
if (!readHRecord(line, len))
return false;
} else if (line[0] == 'C') {
if (route) {
if (!readCRecord(line, len))
return false;
} else {
route = true;
_routes.append(RouteData());
}
} else if (line[0] == 'B') {
if (_date.isNull()) {
_errorString = "Missing date header";
return false;
}
if (!track) {
_tracks.append(TrackData());
_time = QTime(0, 0);
track = true;
}
if (!readBRecord(line, len))
return false;
}
}
_errorLine++;
}
return true;
}

33
src/igcparser.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef IGCPARSER_H
#define IGCPARSER_H
#include <QDate>
#include <QTime>
#include "parser.h"
class IGCParser : public Parser
{
public:
IGCParser(QList<TrackData> &tracks, QList<RouteData> &routes,
QList<Waypoint> &waypoints) : Parser(tracks, routes, waypoints)
{_errorLine = 0;}
~IGCParser() {}
bool loadFile(QFile *file);
QString errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
private:
bool readHRecord(const char *line, int len);
bool readBRecord(const char *line, int len);
bool readCRecord(const char *line, int len);
int _errorLine;
QString _errorString;
QDate _date;
QTime _time;
};
#endif // IGCPARSER_H

View File

@ -7,7 +7,9 @@
InfoItem::InfoItem(QGraphicsItem *parent) : QGraphicsItem(parent)
{
#ifndef Q_OS_MAC
setCacheMode(QGraphicsItem::DeviceCoordinateCache);
#endif // Q_OS_MAC
}
void InfoItem::updateBoundingRect()
@ -76,6 +78,7 @@ void InfoItem::insert(const QString &key, const QString &value)
_list[i] = kv;
updateBoundingRect();
update();
}
void InfoItem::clear()

View File

@ -10,18 +10,22 @@
#define LAST_KEY Qt::Key_End
#define MODIFIER Qt::ShiftModifier
#define QUIT_SHORTCUT QKeySequence::Quit
#define OPEN_SHORTCUT QKeySequence::Open
#define CLOSE_SHORTCUT QKeySequence::Close
#define RELOAD_SHORTCUT QKeySequence::Refresh
#define ZOOM_IN QKeySequence::ZoomIn
#define ZOOM_OUT QKeySequence::ZoomOut
#define QUIT_SHORTCUT QKeySequence(QKeySequence::Quit)
#define OPEN_SHORTCUT QKeySequence(QKeySequence::Open)
#define CLOSE_SHORTCUT QKeySequence(QKeySequence::Close)
#define RELOAD_SHORTCUT QKeySequence(QKeySequence::Refresh)
#define EXPORT_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_E)
#define SHOW_POI_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_P)
#define SHOW_MAP_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_M)
#define NEXT_MAP_SHORTCUT QKeySequence::Forward
#define PREV_MAP_SHORTCUT QKeySequence::Back
#define NEXT_MAP_SHORTCUT QKeySequence(QKeySequence::Forward)
#define PREV_MAP_SHORTCUT QKeySequence(QKeySequence::Back)
#define SHOW_GRAPHS_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_G)
#define DISTANCE_GRAPH_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_D)
#define TIME_GRAPH_SHORTCUT QKeySequence(Qt::CTRL + Qt::Key_T)
#ifdef Q_OS_MAC
#define FULLSCREEN_SHORTCUT QKeySequence(Qt::META + Qt::CTRL + Qt::Key_F)
#else // Q_OS_MAC

View File

@ -87,7 +87,7 @@ bool KMLParser::pointCoordinates(Waypoint &waypoint)
for (vp = cp; cp <= ep; cp++) {
if (*cp == ',') {
if (c > 2)
if (c > 1)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
@ -145,7 +145,7 @@ bool KMLParser::lineCoordinates(TrackData &track)
for (vp = cp; cp <= ep; cp++) {
if (*cp == ',') {
if (c > 2)
if (c > 1)
return false;
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
@ -225,7 +225,7 @@ void KMLParser::point(Waypoint &waypoint)
_reader.raiseError("Missing Point coordinates");
}
void KMLParser::Track(TrackData &track)
void KMLParser::track(TrackData &track)
{
const char mismatchError[] = "gx:coord/when element count mismatch";
int i = track.size();
@ -251,6 +251,28 @@ void KMLParser::Track(TrackData &track)
_reader.raiseError(mismatchError);
}
void KMLParser::multiGeometry(const QString &name, const QString &desc,
const QDateTime timestamp)
{
while (_reader.readNextStartElement()) {
if (_reader.name() == "Point") {
_waypoints.append(Waypoint());
Waypoint &w = _waypoints.last();
w.setName(name);
w.setDescription(desc);
w.setTimestamp(timestamp);
point(w);
} else if (_reader.name() == "LineString") {
_tracks.append(TrackData());
TrackData &t = _tracks.last();
t.setName(name);
t.setDescription(desc);
lineString(t);
} else
_reader.skipCurrentElement();
}
}
void KMLParser::placemark()
{
QString name, desc;
@ -263,25 +285,27 @@ void KMLParser::placemark()
desc = _reader.readElementText();
else if (_reader.name() == "TimeStamp")
timestamp = timeStamp();
else if (_reader.name() == "MultiGeometry")
multiGeometry(name, desc, timestamp);
else if (_reader.name() == "Point") {
_waypoints.append(Waypoint());
Waypoint &waypoint = _waypoints.last();
waypoint.setName(name);
waypoint.setDescription(desc);
waypoint.setTimestamp(timestamp);
point(waypoint);
Waypoint &w = _waypoints.last();
w.setName(name);
w.setDescription(desc);
w.setTimestamp(timestamp);
point(w);
} else if (_reader.name() == "LineString") {
_tracks.append(TrackData());
TrackData &track = _tracks.last();
track.setName(name);
track.setDescription(desc);
lineString(track);
TrackData &t = _tracks.last();
t.setName(name);
t.setDescription(desc);
lineString(t);
} else if (_reader.name() == "Track") {
_tracks.append(TrackData());
TrackData &track = _tracks.last();
track.setName(name);
track.setDescription(desc);
Track(track);
TrackData &t = _tracks.last();
t.setName(name);
t.setDescription(desc);
track(t);
} else
_reader.skipCurrentElement();
}

View File

@ -22,7 +22,9 @@ private:
void document();
void folder();
void placemark();
void Track(TrackData &track);
void multiGeometry(const QString &name, const QString &desc,
const QDateTime timestamp);
void track(TrackData &track);
void lineString(TrackData &track);
void point(Waypoint &waypoint);
bool pointCoordinates(Waypoint &waypoint);

81
src/lambertconic.cpp Normal file
View File

@ -0,0 +1,81 @@
#include <cmath>
#include "rd.h"
#include "lambertconic.h"
#ifndef M_PI_2
#define M_PI_2 1.57079632679489661923
#endif // M_PI_2
#ifndef M_PI_4
#define M_PI_4 0.78539816339744830962
#endif // M_PI_4
static double q(const Ellipsoid &el, double b)
{
double e = sqrt(el.flattening() * (2. - el.flattening()));
double esb = e * sin(b);
return log(tan(M_PI_4 + b / 2.) * pow((1. - esb) / (1. + esb), e / 2.));
}
static double iq(const Ellipsoid &el, double q)
{
double e = sqrt(el.flattening() * (2. - el.flattening()));
double b0 = 0.;
double b = 2. * atan(exp(q)) - M_PI_2;
do {
b0 = b;
double esb = e * sin(b);
b = 2. * atan(exp(q) * pow((1. - esb) / (1. + esb), -e / 2.)) - M_PI_2;
} while (fabs(b - b0) > 1e-10);
return b;
}
static double nradius(const Ellipsoid &el, double phi)
{
double sin_phi = sin(phi);
return (el.radius() / sqrt(1. - (el.flattening() * (2. - el.flattening()))
* sin_phi * sin_phi));
}
LambertConic::LambertConic(const Ellipsoid &ellipsoid, double standardParallel1,
double standardParallel2, double latitudeOrigin, double longitudeOrigin,
double scale, double falseEasting, double falseNorthing) : _e(ellipsoid)
{
_cm = longitudeOrigin;
_fe = falseEasting;
_fn = falseNorthing;
double sp1 = deg2rad(standardParallel1);
double sp2 = deg2rad(standardParallel2);
double N1 = nradius(_e, sp1);
double N2 = nradius(_e, sp2);
_q0 = q(_e, deg2rad(latitudeOrigin));
double q1 = q(_e, sp1);
double q2 = q(_e, sp2);
_n = log((N1 * cos(sp1)) / (N2 * cos(sp2))) / (q2 - q1);
double R1 = N1 * cos(sp1) / _n;
_R0 = scale * R1 * exp(_n * (q1 - _q0));
}
QPointF LambertConic::ll2xy(const Coordinates &c) const
{
double dl = _n * (deg2rad(c.lon()) - deg2rad(_cm));
double R = _R0 * exp(_n * (_q0 - q(_e, deg2rad(c.lat()))));
return QPointF(_fe + R * sin(dl), _fn + _R0 - R * cos(dl));
}
Coordinates LambertConic::xy2ll(const QPointF &p) const
{
double dl = atan((p.x() - _fe) / (_fn + _R0 - p.y()));
double dx = p.x() - _fe;
double dy = p.y() - _fn - _R0;
double R = sqrt(dx * dx + dy * dy);
double q = _q0 - log(R / _R0) / _n;
return Coordinates(rad2deg(deg2rad(_cm) + dl / _n), rad2deg(iq(_e, q)));
}

29
src/lambertconic.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef LAMBERTCONIC_H
#define LAMBERTCONIC_H
#include "ellipsoid.h"
#include "projection.h"
class LambertConic : public Projection
{
public:
LambertConic(const Ellipsoid &ellipsoid, double standardParallel1,
double standardParallel2, double latitudeOrigin, double longitudeOrigin,
double scale, double falseEasting, double falseNorthing);
virtual QPointF ll2xy(const Coordinates &c) const;
virtual Coordinates xy2ll(const QPointF &p) const;
private:
Ellipsoid _e;
double _cm;
double _fe;
double _fn;
double _q0;
double _R0;
double _n;
};
#endif // LAMBERTCONIC_H

15
src/latlon.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef LATLON_H
#define LATLON_H
#include "projection.h"
class LatLon : public Projection
{
public:
virtual QPointF ll2xy(const Coordinates &c) const
{return c.toPointF();}
virtual Coordinates xy2ll(const QPointF &p) const
{return Coordinates(p);}
};
#endif // LATLON_H

View File

@ -1,134 +0,0 @@
#include <QFileInfo>
#include <QDir>
#include "downloader.h"
#include "config.h"
#include "map.h"
Map::Map(QObject *parent, const QString &name, const QString &url)
: QObject(parent)
{
_name = name;
_url = url;
connect(&Downloader::instance(), SIGNAL(finished()), this,
SLOT(emitLoaded()));
QString path = TILES_DIR + QString("/") + _name;
if (!QDir().mkpath(path))
fprintf(stderr, "Error creating tiles dir: %s\n", qPrintable(path));
}
void Map::emitLoaded()
{
emit loaded();
}
void Map::loadTiles(QList<Tile> &list, bool block)
{
if (block)
loadTilesSync(list);
else
loadTilesAsync(list);
}
void Map::loadTilesAsync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists()) {
fillTile(t);
dl.append(Download(tileUrl(t), file));
} else
loadTileFile(t, file);
}
if (!dl.empty())
Downloader::instance().get(dl);
}
void Map::loadTilesSync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists())
dl.append(Download(tileUrl(t), file));
else
loadTileFile(t, file);
}
if (dl.empty())
return;
QEventLoop wait;
connect(&Downloader::instance(), SIGNAL(finished()), &wait, SLOT(quit()));
if (Downloader::instance().get(dl))
wait.exec();
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
if (t.pixmap().isNull()) {
QString file = tileFile(t);
QFileInfo fi(file);
if (!(fi.exists() && loadTileFile(t, file)))
fillTile(t);
}
}
}
void Map::fillTile(Tile &tile)
{
tile.pixmap() = QPixmap(Tile::size(), Tile::size());
tile.pixmap().fill();
}
bool Map::loadTileFile(Tile &tile, const QString &file)
{
if (!tile.pixmap().load(file)) {
fprintf(stderr, "%s: error loading tile file\n", qPrintable(file));
return false;
}
return true;
}
QString Map::tileUrl(const Tile &tile)
{
QString url(_url);
url.replace("$z", QString::number(tile.zoom()));
url.replace("$x", QString::number(tile.xy().x()));
url.replace("$y", QString::number(tile.xy().y()));
return url;
}
QString Map::tileFile(const Tile &tile)
{
QString file = TILES_DIR + QString("/%1/%2-%3-%4").arg(_name)
.arg(tile.zoom()).arg(tile.xy().x()).arg(tile.xy().y());
return file;
}
void Map::clearCache()
{
QString path = TILES_DIR + QString("/") + _name;
QDir dir = QDir(path);
QStringList list = dir.entryList();
for (int i = 0; i < list.count(); i++)
dir.remove(list.at(i));
}

View File

@ -1,37 +1,42 @@
#ifndef MAP_H
#define MAP_H
#include "tile.h"
#include <QObject>
#include <QString>
#include <QRectF>
class QPainter;
class Coordinates;
class Map : public QObject
{
Q_OBJECT
public:
Map(QObject *parent = 0, const QString &name = QString(),
const QString &url = QString());
Map(QObject *parent = 0) : QObject(parent) {}
const QString &name() const {return _name;}
void loadTiles(QList<Tile> &list, bool block);
void clearCache();
virtual const QString &name() const = 0;
virtual QRectF bounds() const = 0;
virtual qreal resolution(const QPointF &p) const = 0;
virtual qreal zoom() const = 0;
virtual qreal zoomFit(const QSize &size, const QRectF &br) = 0;
virtual qreal zoomIn() = 0;
virtual qreal zoomOut() = 0;
virtual QPointF ll2xy(const Coordinates &c) = 0;
virtual Coordinates xy2ll(const QPointF &p) = 0;
virtual void draw(QPainter *painter, const QRectF &rect) = 0;
virtual void setBlockingMode(bool block) {Q_UNUSED(block);}
virtual void clearCache() {}
virtual void load() {}
virtual void unload() {}
signals:
void loaded();
private slots:
void emitLoaded();
private:
QString tileUrl(const Tile &tile);
QString tileFile(const Tile &tile);
bool loadTileFile(Tile &tile, const QString &file);
void fillTile(Tile &tile);
void loadTilesAsync(QList<Tile> &list);
void loadTilesSync(QList<Tile> &list);
QString _name;
QString _url;
};
#endif // MAP_H

View File

@ -1,41 +1,100 @@
#include <QFile>
#include <QFileInfo>
#include "map.h"
#include <QDir>
#include "atlas.h"
#include "offlinemap.h"
#include "onlinemap.h"
#include "maplist.h"
QList<Map*> MapList::load(QObject *parent, const QString &fileName)
bool MapList::loadListEntry(const QByteArray &line)
{
QList<Map*> mapList;
QFileInfo fi(fileName);
QList<QByteArray> list = line.split('\t');
if (list.size() != 2)
return false;
if (!fi.exists())
return mapList;
QByteArray ba1 = list[0].trimmed();
QByteArray ba2 = list[1].trimmed();
if (ba1.isEmpty() || ba2.isEmpty())
return false;
QFile file(fileName);
_maps.append(new OnlineMap(QString::fromUtf8(ba1.data(), ba1.size()),
QString::fromLatin1(ba2.data(), ba2.size()), this));
return true;
}
bool MapList::loadList(const QString &path)
{
QFile file(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
fprintf(stderr, "Error opening map list file: %s: %s\n",
qPrintable(fileName), qPrintable(file.errorString()));
return mapList;
_errorString = file.errorString();
return false;
}
int ln = 0;
while (!file.atEnd()) {
ln++;
QByteArray line = file.readLine();
QList<QByteArray> list = line.split('\t');
if (list.size() != 2) {
fprintf(stderr, "Invalid map list entry on line %d\n", ln);
continue;
if (!loadListEntry(line)) {
_errorString = QString("Invalid map list entry on line %1.")
.arg(QString::number(ln));
return false;
}
QByteArray ba1 = list[0].trimmed();
QByteArray ba2 = list[1].trimmed();
mapList.append(new Map(parent, QString::fromUtf8(ba1.data(), ba1.size()),
QString::fromLatin1(ba2.data(), ba2.size())));
}
return mapList;
return true;
}
bool MapList::loadMap(const QString &path)
{
QFileInfo fi(path);
QString suffix = fi.suffix().toLower();
if (suffix == "map") {
OfflineMap *om = new OfflineMap(path, this);
if (om->isValid()) {
_maps.append(om);
return true;
} else {
_errorString = om->errorString();
delete om;
return false;
}
} else if (suffix == "tba") {
Atlas *atlas = new Atlas(path, this);
if (atlas->isValid()) {
_maps.append(atlas);
return true;
} else {
_errorString = atlas->errorString();
delete atlas;
return false;
}
} else if (suffix == "tar") {
Atlas *atlas = new Atlas(path, this);
if (atlas->isValid()) {
_maps.append(atlas);
return true;
} else {
_errorString = atlas->errorString();
delete atlas;
OfflineMap *om = new OfflineMap(path, this);
if (om->isValid()) {
_maps.append(om);
return true;
} else {
qWarning("%s: %s", qPrintable(path), qPrintable(_errorString));
qWarning("%s: %s", qPrintable(path),
qPrintable(om->errorString()));
_errorString = "Not a map/atlas file";
delete om;
return false;
}
}
} else {
_errorString = "Not a map/atlas file";
return false;
}
}

View File

@ -1,17 +1,28 @@
#ifndef MAPLIST_H
#define MAPLIST_H
#include <QList>
#include <QString>
#include <QObject>
#include <QString>
#include "map.h"
class Map;
class MapList
class MapList : public QObject
{
Q_OBJECT
public:
static QList<Map*> load(QObject *parent = 0,
const QString &fileName = QString());
MapList(QObject *parent = 0) : QObject(parent) {}
bool loadMap(const QString &path);
bool loadList(const QString &path);
QList<Map*> &maps() {return _maps;}
const QString &errorString() const {return _errorString;}
private:
bool loadListEntry(const QByteArray &line);
QList<Map*> _maps;
QString _errorString;
};
#endif // MAPLIST_H

117
src/matrix.cpp Normal file
View File

@ -0,0 +1,117 @@
#include "matrix.h"
#define abs(x) ((x)<0 ? -(x) : (x))
Matrix::~Matrix()
{
if (isNull())
return;
delete[] _m;
}
Matrix::Matrix(size_t h, size_t w)
{
_h = h; _w = w;
if (isNull())
_m = 0;
else
_m = new double[_h * _w];
}
Matrix::Matrix(const Matrix& M)
{
_h = M._h; _w = M._w;
if (isNull())
_m = 0;
else
_m = new double[_h * _w];
for (size_t i = 0; i < _h; i++)
for (size_t j = 0; j < _w; j++)
m(i,j) = M.m(i,j);
}
Matrix &Matrix::operator=(const Matrix &M)
{
if (_h != M._h || _w != M._w) {
if (!isNull())
delete[] _m;
_h = M._h; _w = M._w;
if (isNull())
_m = 0;
else
_m = new double[_h * _w];
}
for (size_t i = 0; i < _h; i++)
for (size_t j = 0; j < _w; j++)
m(i,j) = M.m(i,j);
return *this;
}
bool Matrix::eliminate(double epsilon)
{
size_t i, j, k, maxrow;
double temp;
for (i = 0; i < _h; i++) {
maxrow = i;
for (j = i+1; j < _h; j++)
if (abs(m(j, i)) > abs(m(maxrow, i)))
maxrow = j;
for (j = 0; j < _w; j++) {
temp = m(i, j);
m(i, j) = m(maxrow, j);
m(maxrow, j) = temp;
}
if (abs(m(i, i)) <= epsilon)
return false;
for (j = i+1; j<_h; j++) {
temp = m(j, i) / m(i, i);
for (k = i; k < _w; k++)
m(j, k) -= m(i, k) * temp;
}
}
for (i = _h-1; i < i+1; i--) {
temp = m(i, i);
for (j = 0; j < i; j++)
for (k = _w-1; k >= i; k--)
m(j, k) -= m(i, k) * m(j, i) / temp;
m(i, i) /= temp;
for (j = _h; j < _w; j++)
m(i, j) /= temp;
}
return true;
}
Matrix Matrix::augemented(const Matrix &M) const
{
if (_h != M._h)
return Matrix();
Matrix A(_h, _w + M._w);
for (size_t i = 0; i < _h; i++)
for (size_t j = 0; j < _w; j++)
A.m(i, j) = m(i, j);
for (size_t i = 0; i < _h; i++)
for (size_t j = _w; j < A._w; j++)
A.m(i, j) = M.m(i, j-_w);
return A;
}
void Matrix::zeroize()
{
for (size_t i = 0; i < _h * _w; i++)
_m[i] = 0;
}

33
src/matrix.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef MATRIX_H
#define MATRIX_H
#include <cstddef>
#include <cfloat>
class Matrix {
public:
Matrix() {_h = 0; _w = 0; _m = 0;}
Matrix(size_t h, size_t w);
Matrix(const Matrix& M);
~Matrix();
Matrix &operator=(const Matrix &M);
size_t h() const {return _h;}
size_t w() const {return _w;}
double &m(size_t i, size_t j) {return _m[_w * i + j];}
double const &m(size_t i, size_t j) const {return _m[_w * i + j];}
bool isNull() const {return (_h == 0 || _w == 0);}
void zeroize();
bool eliminate(double epsilon = DBL_EPSILON);
Matrix augemented(const Matrix &M) const;
private:
double *_m;
size_t _h;
size_t _w;
};
#endif // MATRIX_H

13
src/mercator.cpp Normal file
View File

@ -0,0 +1,13 @@
#include <cmath>
#include "rd.h"
#include "mercator.h"
QPointF Mercator::ll2xy(const Coordinates &c) const
{
return QPointF(c.lon(), rad2deg(log(tan(M_PI/4.0 + deg2rad(c.lat())/2.0))));
}
Coordinates Mercator::xy2ll(const QPointF &p) const
{
return Coordinates(p.x(), rad2deg(2 * atan(exp(deg2rad(p.y()))) - M_PI/2));
}

13
src/mercator.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef MERCATOR_H
#define MERCATOR_H
#include "projection.h"
class Mercator : public Projection
{
public:
virtual QPointF ll2xy(const Coordinates &c) const;
virtual Coordinates xy2ll(const QPointF &p) const;
};
#endif // MERCATOR_H

View File

@ -1,4 +1,5 @@
#include <cmath>
#include <cctype>
#include "misc.h"
@ -33,3 +34,23 @@ double niceNum(double x, int round)
return nf * pow(10.0, expv);
}
int str2int(const char *str, int len)
{
int res = 0;
for (const char *sp = str; sp < str + len; sp++) {
if (::isdigit(*sp))
res = res * 10 + *sp - '0';
else
return -1;
}
return res;
}
QRectF scaled(const QRectF &rect, qreal factor)
{
return QRectF(QPointF(rect.left() * factor, rect.top() * factor),
QSizeF(rect.width() * factor, rect.height() * factor));
}

View File

@ -1,6 +1,13 @@
#ifndef MISC_H
#define MISC_H
#include <QRectF>
#define ARRAY_SIZE(array) \
(sizeof(array) / sizeof((array)[0]))
double niceNum(double x, int round);
int str2int(const char *str, int len);
QRectF scaled(const QRectF &rect, qreal factor);
#endif // MISC_H

527
src/nmeaparser.cpp Normal file
View File

@ -0,0 +1,527 @@
#include <cstring>
#include "misc.h"
#include "nmeaparser.h"
static bool validSentence(const char *line, int len)
{
const char *lp;
if (len < 12 || line[0] != '$')
return false;
for (lp = line + len - 1; lp > line + 3; lp--)
if (!::isspace(*lp))
break;
if (*(lp-2) != '*' || !::isalnum(*(lp-1)) || !::isalnum(*(lp)))
return false;
return true;
}
static bool readFloat(const char *data, int len, qreal &f)
{
bool ok;
f = QString(QByteArray::fromRawData(data, len)).toFloat(&ok);
return ok;
}
bool NMEAParser::readAltitude(const char *data, int len, qreal &ele)
{
if (!len) {
ele = NAN;
return true;
}
if (!readFloat(data, len, ele)) {
_errorString = "Invalid altitude";
return false;
}
return true;
}
bool NMEAParser::readGeoidHeight(const char *data, int len, qreal &gh)
{
if (!len) {
gh = 0;
return true;
}
if (!readFloat(data, len, gh)) {
_errorString = "Invalid geoid height";
return false;
}
return true;
}
bool NMEAParser::readTime(const char *data, int len, QTime &time)
{
int h, m, s, ms = 0;
if (!len) {
time = QTime();
return true;
}
if (len < 6)
goto error;
h = str2int(data, 2);
m = str2int(data + 2, 2);
s = str2int(data + 4, 2);
if (h < 0 || m < 0 || s < 0)
goto error;
if (len > 7 && data[6] == '.') {
if ((ms = str2int(data + 7, len - 7)) < 0)
goto error;
}
time = QTime(h, m, s, ms);
if (!time.isValid())
goto error;
return true;
error:
_errorString = "Invalid time";
return false;
}
bool NMEAParser::readDate(const char *data, int len, QDate &date)
{
int y, m, d;
if (!len) {
date = QDate();
return true;
}
if (len < 6)
goto error;
d = str2int(data, 2);
m = str2int(data + 2, 2);
y = str2int(data + 4, len - 4);
if (d < 0 || m < 0 || y < 0)
goto error;
if (len - 4 == 2)
date = QDate(y + 2000 < QDate::currentDate().year()
? 2000 + y : 1900 + y, m, d);
else
date = QDate(y, m, d);
if (!date.isValid())
goto error;
return true;
error:
_errorString = "Invalid date";
return false;
}
bool NMEAParser::readLat(const char *data, int len, qreal &lat)
{
int d, mi;
qreal mf;
bool ok;
if (!len) {
lat = NAN;
return true;
}
if (len < 7 || data[4] != '.')
goto error;
d = str2int(data, 2);
mi = str2int(data + 2, 2);
mf = QString(QByteArray::fromRawData(data + 4, len - 4)).toFloat(&ok);
if (d < 0 || mi < 0 || !ok)
goto error;
lat = d + (((qreal)mi + mf) / 60.0);
if (lat > 90)
goto error;
return true;
error:
_errorString = "Invalid ltitude";
return false;
}
bool NMEAParser::readNS(const char *data, int len, qreal &lat)
{
if (!len) {
lat = NAN;
return true;
}
if (len != 1 || !(*data == 'N' || *data == 'S')) {
_errorString = "Invalid N/S value";
return false;
}
if (*data == 'S')
lat = -lat;
return true;
}
bool NMEAParser::readLon(const char *data, int len, qreal &lon)
{
int d, mi;
qreal mf;
bool ok;
if (!len) {
lon = NAN;
return true;
}
if (len < 8 || data[5] != '.')
goto error;
d = str2int(data, 3);
mi = str2int(data + 3, 2);
mf = QString(QByteArray::fromRawData(data + 5, len - 5)).toFloat(&ok);
if (d < 0 || mi < 0 || !ok)
goto error;
lon = d + (((qreal)mi + mf) / 60.0);
if (lon > 180)
goto error;
return true;
error:
_errorString = "Invalid longitude";
return false;
}
bool NMEAParser::readEW(const char *data, int len, qreal &lon)
{
if (!len) {
lon = NAN;
return true;
}
if (len != 1 || !(*data == 'E' || *data == 'W')) {
_errorString = "Invalid E/W value";
return false;
}
if (*data == 'W')
lon = -lon;
return true;
}
bool NMEAParser::readRMC(const char *line, int len)
{
int col = 1;
const char *vp = line;
qreal lat, lon;
QTime time;
QDate date;
bool valid = true;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readTime(vp, lp - vp, time))
return false;
break;
case 2:
if (*vp != 'A')
valid = false;
break;
case 3:
if (!readLat(vp, lp - vp, lat))
return false;
break;
case 4:
if (!readNS(vp, lp - vp, lat))
return false;
break;
case 5:
if (!readLon(vp, lp - vp, lon))
return false;
break;
case 6:
if (!readEW(vp, lp - vp, lon))
return false;
break;
case 9:
if (!readDate(vp, lp - vp, date))
return false;
break;
}
col++;
vp = lp + 1;
}
}
if (col < 9) {
_errorString = "Invalid RMC sentence";
return false;
}
if (!date.isNull()) {
if (_date.isNull() && !_time.isNull() && !_tracks.last().isEmpty())
_tracks.last().last().setTimestamp(QDateTime(date, _time, Qt::UTC));
_date = date;
}
if (valid && !_GGA && !std::isnan(lat) && !std::isnan(lon)) {
Trackpoint t(Coordinates(lon, lat));
if (!_date.isNull() && !time.isNull())
t.setTimestamp(QDateTime(_date, time, Qt::UTC));
_tracks.last().append(t);
}
return true;
}
bool NMEAParser::readGGA(const char *line, int len)
{
int col = 1;
const char *vp = line;
qreal lat, lon, ele, gh;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readTime(vp, lp - vp, _time))
return false;
break;
case 2:
if (!readLat(vp, lp - vp, lat))
return false;
break;
case 3:
if (!readNS(vp, lp - vp, lat))
return false;
break;
case 4:
if (!readLon(vp, lp - vp, lon))
return false;
break;
case 5:
if (!readEW(vp, lp - vp, lon))
return false;
break;
case 9:
if (!readAltitude(vp, lp - vp, ele))
return false;
break;
case 10:
if ((lp - vp) && !((lp - vp) == 1 && *vp == 'M')) {
_errorString = "Invalid altitude units";
return false;
}
break;
case 11:
if (!readGeoidHeight(vp, lp - vp, gh))
return false;
break;
case 12:
if ((lp - vp) && !((lp - vp) == 1 && *vp == 'M')) {
_errorString = "Invalid geoid height units";
return false;
}
break;
}
col++;
vp = lp + 1;
}
}
if (col < 12) {
_errorString = "Invalid GGA sentence";
return false;
}
if (!std::isnan(lat) && !std::isnan(lon)) {
Trackpoint t(Coordinates(lon, lat));
if (!(_time.isNull() || _date.isNull()))
t.setTimestamp(QDateTime(_date, _time, Qt::UTC));
if (!std::isnan(ele))
t.setElevation(ele - gh);
_tracks.last().append(t);
_GGA = true;
}
return true;
}
bool NMEAParser::readWPL(const char *line, int len)
{
int col = 1;
const char *vp = line;
qreal lat, lon;
QString name;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readLat(vp, lp - vp, lat))
return false;
break;
case 2:
if (!readNS(vp, lp - vp, lat))
return false;
break;
case 3:
if (!readLon(vp, lp - vp, lon))
return false;
break;
case 4:
if (!readEW(vp, lp - vp, lon))
return false;
break;
case 5:
name = QString(QByteArray(vp, lp - vp));
break;
}
col++;
vp = lp + 1;
}
}
if (col < 4) {
_errorString = "Invalid WPL sentence";
return false;
}
if (!std::isnan(lat) && !std::isnan(lon)) {
Waypoint w(Coordinates(lon, lat));
w.setName(name);
_waypoints.append(w);
}
return true;
}
bool NMEAParser::readZDA(const char *line, int len)
{
int col = 1;
const char *vp = line;
int d, m, y;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 2:
if (!(lp - vp))
return true;
if ((d = str2int(vp, lp - vp)) < 0) {
_errorString = "Invalid day";
return false;
}
break;
case 3:
if (!(lp - vp))
return true;
if ((m = str2int(vp, lp - vp)) < 0) {
_errorString = "Invalid month";
return false;
}
break;
case 4:
if (!(lp - vp))
return true;
if ((y = str2int(vp, lp - vp)) < 0) {
_errorString = "Invalid year";
return false;
}
break;
}
col++;
vp = lp + 1;
}
}
if (col < 4) {
_errorString = "Invalid ZDA sentence";
return false;
}
_date = QDate(y, m, d);
if (!_date.isValid()) {
_errorString = "Invalid date";
return false;
}
return true;
}
bool NMEAParser::loadFile(QFile *file)
{
qint64 len;
char line[80 + 2 + 1 + 1];
_errorLine = 1;
_errorString.clear();
_date = QDate();
_time = QTime();
_GGA = false;
_tracks.append(TrackData());
while (!file->atEnd()) {
len = file->readLine(line, sizeof(line));
if (len < 0) {
_errorString = "I/O error";
return false;
} else if (len > (qint64)sizeof(line) - 1) {
_errorString = "Line limit exceeded";
return false;
}
if (validSentence(line, len)) {
if (!memcmp(line + 3, "RMC,", 4)) {
if (!readRMC(line + 7, len))
return false;
} else if (!memcmp(line + 3, "GGA,", 4)) {
if (!readGGA(line + 7, len))
return false;
} else if (!memcmp(line + 3, "WPL,", 4)) {
if (!readWPL(line + 7, len))
return false;
} else if (!memcmp(line + 3, "ZDA,", 4)) {
if (!readZDA(line + 7, len))
return false;
}
}
_errorLine++;
}
if (!_tracks.last().size() && !_waypoints.size()) {
_errorString = "No usable NMEA sentence found";
return false;
}
return true;
}

43
src/nmeaparser.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef NMEAPARSER_H
#define NMEAPARSER_H
#include <QDate>
#include "parser.h"
class NMEAParser : public Parser
{
public:
NMEAParser(QList<TrackData> &tracks, QList<RouteData> &routes,
QList<Waypoint> &waypoints) : Parser(tracks, routes, waypoints)
{_errorLine = 0; _GGA = false;}
~NMEAParser() {}
bool loadFile(QFile *file);
QString errorString() const {return _errorString;}
int errorLine() const {return _errorLine;}
private:
bool readEW(const char *data, int len, qreal &lon);
bool readLon(const char *data, int len, qreal &lon);
bool readNS(const char *data, int len, qreal &lat);
bool readLat(const char *data, int len, qreal &lat);
bool readDate(const char *data, int len, QDate &date);
bool readTime(const char *data, int len, QTime &time);
bool readAltitude(const char *data, int len, qreal &ele);
bool readGeoidHeight(const char *data, int len, qreal &gh);
bool readRMC(const char *line, int len);
bool readGGA(const char *line, int len);
bool readWPL(const char *line, int len);
bool readZDA(const char *line, int len);
int _errorLine;
QString _errorString;
QDate _date;
QTime _time;
bool _GGA;
};
#endif // NMEAPARSER_H

20
src/oddspinbox.cpp Normal file
View File

@ -0,0 +1,20 @@
#include "oddspinbox.h"
OddSpinBox::OddSpinBox(QWidget *parent) : QSpinBox(parent)
{
setSingleStep(2);
setMinimum(1);
}
QValidator::State OddSpinBox::validate(QString &text, int &pos) const
{
Q_UNUSED(pos);
bool ok;
int val;
val = text.toInt(&ok);
if (!ok || val < 0 || val % 2 == 0)
return QValidator::Invalid;
return QValidator::Acceptable;
}

15
src/oddspinbox.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef ODDSPINBOX_H
#define ODDSPINBOX_H
#include <QSpinBox>
class OddSpinBox : public QSpinBox
{
public:
OddSpinBox(QWidget *parent = 0);
protected:
QValidator::State validate(QString &text, int &pos) const;
};
#endif // ODDSPINBOX_H

677
src/offlinemap.cpp Normal file
View File

@ -0,0 +1,677 @@
#include <QtGlobal>
#include <QPainter>
#include <QFileInfo>
#include <QMap>
#include <QDir>
#include <QBuffer>
#include <QImage>
#include <QImageReader>
#include <QPixmapCache>
#include "misc.h"
#include "rd.h"
#include "wgs84.h"
#include "coordinates.h"
#include "matrix.h"
#include "datum.h"
#include "latlon.h"
#include "mercator.h"
#include "transversemercator.h"
#include "utm.h"
#include "lambertconic.h"
#include "albersequal.h"
#include "ozf.h"
#include "offlinemap.h"
// Abridged Molodensky transformation
static Coordinates toWGS84(Coordinates c, const Datum &datum)
{
double rlat = deg2rad(c.lat());
double rlon = deg2rad(c.lon());
double slat = sin(rlat);
double clat = cos(rlat);
double slon = sin(rlon);
double clon = cos(rlon);
double ssqlat = slat * slat;
double from_f = datum.ellipsoid().flattening();
double df = WGS84_FLATTENING - from_f;
double from_a = datum.ellipsoid().radius();
double da = WGS84_RADIUS - from_a;
double from_esq = datum.ellipsoid().flattening()
* (2.0 - datum.ellipsoid().flattening());
double adb = 1.0 / (1.0 - from_f);
double rn = from_a / sqrt(1 - from_esq * ssqlat);
double rm = from_a * (1 - from_esq) / pow((1 - from_esq * ssqlat), 1.5);
double from_h = 0.0;
double dlat = (-datum.dx() * slat * clon - datum.dy() * slat * slon
+ datum.dz() * clat + da * rn * from_esq * slat * clat / from_a + df
* (rm * adb + rn / adb) * slat * clat) / (rm + from_h);
double dlon = (-datum.dx() * slon + datum.dy() * clon) / ((rn + from_h)
* clat);
return Coordinates(c.lon() + rad2deg(dlon), c.lat() + rad2deg(dlat));
}
int OfflineMap::parse(QIODevice &device, QList<ReferencePoint> &points,
QString &projection, ProjectionSetup &setup, QString &datum)
{
bool res;
int ln = 1;
while (!device.atEnd()) {
QByteArray line = device.readLine();
if (ln == 1) {
if (!line.trimmed().startsWith("OziExplorer Map Data File"))
return ln;
} else if (ln == 2)
_name = line.trimmed();
else if (ln == 3)
_imgPath = line.trimmed();
else if (ln == 5)
datum = line.split(',').at(0).trimmed();
else {
QList<QByteArray> list = line.split(',');
QString key(list.at(0).trimmed());
bool ll = true; bool pp = true;
if (key.startsWith("Point") && list.count() == 17
&& !list.at(2).trimmed().isEmpty()) {
int x = list.at(2).trimmed().toInt(&res);
if (!res)
return ln;
int y = list.at(3).trimmed().toInt(&res);
if (!res)
return ln;
int latd = list.at(6).trimmed().toInt(&res);
if (!res)
ll = false;
qreal latm = list.at(7).trimmed().toFloat(&res);
if (!res)
ll = false;
int lond = list.at(9).trimmed().toInt(&res);
if (!res)
ll = false;
qreal lonm = list.at(10).trimmed().toFloat(&res);
if (!res)
ll = false;
if (ll && list.at(8).trimmed() == "S") {
latd = -latd;
latm = -latm;
}
if (ll && list.at(11).trimmed() == "W") {
lond = -lond;
lonm = -lonm;
}
setup.zone = list.at(13).trimmed().toInt(&res);
if (!res)
setup.zone = 0;
qreal ppx = list.at(14).trimmed().toFloat(&res);
if (!res)
pp = false;
qreal ppy = list.at(15).trimmed().toFloat(&res);
if (!res)
pp = false;
if (list.at(16).trimmed() == "S")
setup.zone = -setup.zone;
ReferencePoint p;
p.xy = QPoint(x, y);
if (ll) {
p.ll = Coordinates(lond + lonm/60.0, latd + latm/60.0);
if (p.ll.isValid())
points.append(p);
else
return ln;
} else if (pp) {
p.pp = QPointF(ppx, ppy);
points.append(p);
} else
return ln;
} else if (key == "IWH") {
int w = list.at(2).trimmed().toInt(&res);
if (!res)
return ln;
int h = list.at(3).trimmed().toInt(&res);
if (!res)
return ln;
_size = QSize(w, h);
} else if (key == "Map Projection") {
projection = list.at(1);
} else if (key == "Projection Setup") {
if (list.count() < 8)
return ln;
setup.latitudeOrigin = list.at(1).trimmed().toFloat(&res);
if (!res)
setup.latitudeOrigin = 0;
setup.longitudeOrigin = list.at(2).trimmed().toFloat(&res);
if (!res)
setup.longitudeOrigin = 0;
setup.scale = list.at(3).trimmed().toFloat(&res);
if (!res)
setup.scale = 1.0;
setup.falseEasting = list.at(4).trimmed().toFloat(&res);
if (!res)
setup.falseEasting = 0;
setup.falseNorthing = list.at(5).trimmed().toFloat(&res);
if (!res)
setup.falseNorthing = 0;
setup.standardParallel1 = list.at(6).trimmed().toFloat(&res);
if (!res)
setup.standardParallel1 = 0;
setup.standardParallel2 = list.at(7).trimmed().toFloat(&res);
if (!res)
setup.standardParallel2 = 0;
}
}
ln++;
}
return 0;
}
bool OfflineMap::parseMapFile(QIODevice &device, QList<ReferencePoint> &points,
QString &projection, ProjectionSetup &setup, QString &datum)
{
int el;
if (!device.open(QIODevice::ReadOnly)) {
_errorString = QString("Error opening map file: %1")
.arg(device.errorString());
return false;
}
if ((el = parse(device, points, projection, setup, datum))) {
_errorString = QString("Map file parse error on line %1").arg(el);
return false;
}
return true;
}
bool OfflineMap::createProjection(const QString &datum,
const QString &projection, const ProjectionSetup &setup,
QList<ReferencePoint> &points)
{
if (points.count() < 2) {
_errorString = "Insufficient number of reference points";
return false;
}
Datum d = Datum::datum(datum);
if (d.isNull()) {
_errorString = QString("%1: Unknown datum").arg(datum);
return false;
}
if (setup.latitudeOrigin < -90.0 || setup.latitudeOrigin > 90.0
|| setup.longitudeOrigin < -180.0 || setup.longitudeOrigin > 180.0
|| setup.standardParallel1 < -90 || setup.standardParallel1 > 90
|| setup.standardParallel2 < -90 || setup.standardParallel2 > 90) {
_errorString = "Invalid projection setup";
return false;
}
if (projection == "Mercator")
_projection = new Mercator();
else if (projection == "Transverse Mercator")
_projection = new TransverseMercator(d.ellipsoid(),
setup.latitudeOrigin, setup.longitudeOrigin, setup.scale,
setup.falseEasting, setup.falseNorthing);
else if (projection == "Latitude/Longitude")
_projection = new LatLon();
else if (projection == "Lambert Conformal Conic")
_projection = new LambertConic(d.ellipsoid(),
setup.standardParallel1, setup.standardParallel2,
setup.latitudeOrigin, setup.longitudeOrigin, setup.scale,
setup.falseEasting, setup.falseNorthing);
else if (projection == "Albers Equal Area")
_projection = new AlbersEqual(d.ellipsoid(), setup.standardParallel1,
setup.standardParallel2, setup.latitudeOrigin, setup.longitudeOrigin,
setup.falseEasting, setup.falseNorthing);
else if (projection == "(UTM) Universal Transverse Mercator") {
if (setup.zone)
_projection = new UTM(d.ellipsoid(), setup.zone);
else if (!points.first().ll.isNull())
_projection = new UTM(d.ellipsoid(), points.first().ll);
else {
_errorString = "Can not determine UTM zone";
return false;
}
} else if (projection == "(NZTM2) New Zealand TM 2000")
_projection = new TransverseMercator(d.ellipsoid(), 0, 173.0, 0.9996,
1600000, 10000000);
else if (projection == "(BNG) British National Grid")
_projection = new TransverseMercator(d.ellipsoid(), 49, -2, 0.999601,
400000, -100000);
else if (projection == "(IG) Irish Grid")
_projection = new TransverseMercator(d.ellipsoid(), 53.5, -8, 1.000035,
200000, 250000);
else if (projection == "(SG) Swedish Grid")
_projection = new TransverseMercator(d.ellipsoid(), 0, 15.808278, 1,
1500000, 0);
else {
_errorString = QString("%1: Unknown map projection").arg(projection);
return false;
}
for (int i = 0; i < points.size(); i++) {
if (points.at(i).ll.isNull()) {
if (d.isWGS84())
points[i].ll = _projection->xy2ll(points.at(i).pp);
else
points[i].ll = toWGS84(_projection->xy2ll(points.at(i).pp), d);
} else {
if (!d.isWGS84())
points[i].ll = toWGS84(points[i].ll, d);
}
}
return true;
}
bool OfflineMap::computeTransformation(const QList<ReferencePoint> &points)
{
Q_ASSERT(points.size() >= 2);
Matrix c(3, 2);
c.zeroize();
for (size_t j = 0; j < c.w(); j++) {
for (size_t k = 0; k < c.h(); k++) {
for (int i = 0; i < points.size(); i++) {
double f[3], t[2];
QPointF p = _projection->ll2xy(points.at(i).ll);
f[0] = p.x();
f[1] = p.y();
f[2] = 1.0;
t[0] = points.at(i).xy.x();
t[1] = points.at(i).xy.y();
c.m(k,j) += f[k] * t[j];
}
}
}
Matrix Q(3, 3);
Q.zeroize();
for (int qi = 0; qi < points.size(); qi++) {
double v[3];
QPointF p = _projection->ll2xy(points.at(qi).ll);
v[0] = p.x();
v[1] = p.y();
v[2] = 1.0;
for (size_t i = 0; i < Q.h(); i++)
for (size_t j = 0; j < Q.w(); j++)
Q.m(i,j) += v[i] * v[j];
}
Matrix M = Q.augemented(c);
if (!M.eliminate()) {
_errorString = "Singular transformation matrix";
return false;
}
_transform = QTransform(M.m(0,3), M.m(0,4), M.m(1,3), M.m(1,4), M.m(2,3),
M.m(2,4));
_inverted = _transform.inverted();
return true;
}
bool OfflineMap::computeResolution(QList<ReferencePoint> &points)
{
Q_ASSERT(points.count() >= 2);
int maxLon = 0, minLon = 0, maxLat = 0, minLat = 0;
qreal dLon, pLon, dLat, pLat;
for (int i = 1; i < points.size(); i++) {
if (points.at(i).ll.lon() < points.at(minLon).ll.lon())
minLon = i;
if (points.at(i).ll.lon() > points.at(maxLon).ll.lon())
maxLon = i;
if (points.at(i).ll.lat() < points.at(minLat).ll.lon())
minLat = i;
if (points.at(i).ll.lat() > points.at(maxLat).ll.lon())
maxLat = i;
}
dLon = points.at(minLon).ll.distanceTo(points.at(maxLon).ll);
pLon = QLineF(points.at(minLon).xy, points.at(maxLon).xy).length();
dLat = points.at(minLat).ll.distanceTo(points.at(maxLat).ll);
pLat = QLineF(points.at(minLat).xy, points.at(maxLat).xy).length();
_resolution = (dLon/pLon + dLat/pLat) / 2.0;
return true;
}
bool OfflineMap::getImageInfo(const QString &path)
{
QFileInfo ii(_imgPath);
if (ii.isRelative())
ii.setFile(path + "/" + _imgPath);
if (!ii.exists()) {
int last = _imgPath.lastIndexOf('\\');
if (last >= 0 && last < _imgPath.length() - 1) {
QStringRef fn(&_imgPath, last + 1, _imgPath.length() - last - 1);
ii.setFile(path + "/" + fn.toString());
}
}
if (ii.exists())
_imgPath = ii.absoluteFilePath();
else {
_errorString = QString("%1: No such image file").arg(_imgPath);
return false;
}
QString suffix = ii.suffix().toLower();
if (suffix == "ozf4" || suffix == "ozfx4") {
_errorString = QString("%1: OZF4 image files not supported")
.arg(QFileInfo(_imgPath).fileName());
return false;
} else if (suffix == "ozf2" || suffix == "ozfx2" || suffix == "ozf3"
|| suffix == "ozfx3") {
_ozf.load(_imgPath);
_size = _ozf.size();
} else {
QImageReader img(_imgPath);
_size = img.size();
}
if (!_size.isValid()) {
_errorString = QString("%1: Error reading map image")
.arg(QFileInfo(_imgPath).fileName());
return false;
}
return true;
}
bool OfflineMap::getTileInfo(const QStringList &tiles, const QString &path)
{
QRegExp rx("_[0-9]+_[0-9]+\\.");
for (int i = 0; i < tiles.size(); i++) {
if (tiles.at(i).contains(rx)) {
_tileName = QString(tiles.at(i)).replace(rx, "_%1_%2.");
if (path.isNull()) {
QByteArray ba = _tar.file(tiles.at(i));
QBuffer buffer(&ba);
_tileSize = QImageReader(&buffer).size();
} else {
_tileName = path + "/" + _tileName;
_tileSize = QImageReader(path + "/" + tiles.at(i)).size();
}
if (!_tileSize.isValid()) {
_errorString = QString("Error retrieving tile size: "
"%1: Invalid image").arg(QFileInfo(tiles.at(i)).fileName());
return false;
}
return true;
}
}
_errorString = "Invalid/missing tile set";
return false;
}
bool OfflineMap::totalSizeSet()
{
if (!_size.isValid()) {
_errorString = "Missing total image size (IWH)";
return false;
} else
return true;
}
OfflineMap::OfflineMap(const QString &fileName, QObject *parent)
: Map(parent)
{
QList<ReferencePoint> points;
QString proj, datum;
ProjectionSetup setup;
QFileInfo fi(fileName);
QString suffix = fi.suffix().toLower();
_valid = false;
_img = 0;
_projection = 0;
_resolution = 0;
if (suffix == "tar") {
if (!_tar.load(fileName)) {
_errorString = "Error reading tar file";
return;
}
QString mapFileName = fi.completeBaseName() + ".map";
QByteArray ba = _tar.file(mapFileName);
if (ba.isNull()) {
_errorString = "Map file not found";
return;
}
QBuffer mapFile(&ba);
if (!parseMapFile(mapFile, points, proj, setup, datum))
return;
} else if (suffix =="map") {
QFile mapFile(fileName);
if (!parseMapFile(mapFile, points, proj, setup, datum))
return;
} else {
_errorString = "Not a map file";
return;
}
if (!createProjection(datum, proj, setup, points))
return;
if (!computeTransformation(points))
return;
computeResolution(points);
if (_tar.isOpen()) {
if (!totalSizeSet())
return;
if (!getTileInfo(_tar.files()))
return;
_imgPath = QString();
} else {
QDir set(fi.absolutePath() + "/" + "set");
if (set.exists()) {
if (!totalSizeSet())
return;
if (!getTileInfo(set.entryList(), set.absolutePath()))
return;
_imgPath = QString();
} else {
if (!getImageInfo(fi.absolutePath()))
return;
}
}
_valid = true;
}
OfflineMap::OfflineMap(const QString &fileName, Tar &tar, QObject *parent)
: Map(parent)
{
QList<ReferencePoint> points;
QString proj, datum;
ProjectionSetup setup;
QFileInfo fi(fileName);
_valid = false;
_img = 0;
_projection = 0;
QFileInfo map(fi.absolutePath());
QFileInfo layer(map.absolutePath());
QString mapFile = layer.fileName() + "/" + map.fileName() + "/"
+ fi.fileName();
QByteArray ba = tar.file(mapFile);
if (ba.isNull()) {
_errorString = "Map file not found";
return;
}
QBuffer buffer(&ba);
if (!parseMapFile(buffer, points, proj, setup, datum))
return;
if (!createProjection(datum, proj, setup, points))
return;
if (!totalSizeSet())
return;
if (!computeTransformation(points))
return;
computeResolution(points);
_tarPath = fi.absolutePath() + "/" + fi.completeBaseName() + ".tar";
_imgPath = QString();
_valid = true;
}
OfflineMap::~OfflineMap()
{
if (_img)
delete _img;
if (_projection)
delete _projection;
}
void OfflineMap::load()
{
if (!_tarPath.isNull() && !_tileSize.isValid()) {
if (!_tar.load(_tarPath)) {
qWarning("%s: error loading tar file", qPrintable(_tarPath));
return;
}
if (!getTileInfo(_tar.files()))
qWarning("%s: %s", qPrintable(_tarPath), qPrintable(_errorString));
return;
}
if (!_img && !_imgPath.isNull() && !_ozf.isOpen()) {
_img = new QImage(_imgPath);
if (_img->isNull())
qWarning("%s: error loading map image", qPrintable(_imgPath));
}
}
void OfflineMap::unload()
{
if (_img) {
delete _img;
_img = 0;
}
}
void OfflineMap::drawTiled(QPainter *painter, const QRectF &rect)
{
QPoint tl = QPoint((int)floor(rect.left() / (qreal)_tileSize.width())
* _tileSize.width(), (int)floor(rect.top() / _tileSize.height())
* _tileSize.height());
QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
for (int i = 0; i < ceil(s.width() / _tileSize.width()); i++) {
for (int j = 0; j < ceil(s.height() / _tileSize.height()); j++) {
int x = tl.x() + i * _tileSize.width();
int y = tl.y() + j * _tileSize.height();
if (!QRectF(QPointF(x, y), _ozf.tileSize()).intersects(bounds())) {
painter->fillRect(QRectF(QPoint(x, y), _tileSize), Qt::white);
continue;
}
QString tileName(_tileName.arg(QString::number(x),
QString::number(y)));
QPixmap pixmap;
if (_tar.isOpen()) {
QString key = _tar.fileName() + "/" + tileName;
if (!QPixmapCache::find(key, &pixmap)) {
QByteArray ba = _tar.file(tileName);
pixmap = QPixmap::fromImage(QImage::fromData(ba));
if (!pixmap.isNull())
QPixmapCache::insert(key, pixmap);
}
} else
pixmap = QPixmap(tileName);
if (pixmap.isNull()) {
qWarning("%s: error loading tile image", qPrintable(
_tileName.arg(QString::number(x), QString::number(y))));
painter->fillRect(QRectF(QPoint(x, y), _tileSize), Qt::white);
} else
painter->drawPixmap(QPoint(x, y), pixmap);
}
}
}
void OfflineMap::drawOZF(QPainter *painter, const QRectF &rect)
{
QPoint tl = QPoint((int)floor(rect.left() / _ozf.tileSize().width())
* _ozf.tileSize().width(), (int)floor(rect.top()
/ _ozf.tileSize().height()) * _ozf.tileSize().height());
QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
for (int i = 0; i < ceil(s.width() / _ozf.tileSize().width()); i++) {
for (int j = 0; j < ceil(s.height() / _ozf.tileSize().height()); j++) {
int x = tl.x() + i * _ozf.tileSize().width();
int y = tl.y() + j * _ozf.tileSize().height();
if (!QRectF(QPointF(x, y), _ozf.tileSize()).intersects(bounds())) {
painter->fillRect(QRectF(QPoint(x, y), _tileSize), Qt::white);
continue;
}
QPixmap pixmap;
QString key = _ozf.fileName() + "/" + QString::number(x)
+ "_" + QString::number(y);
if (!QPixmapCache::find(key, &pixmap)) {
pixmap = _ozf.tile(x, y);
if (!pixmap.isNull())
QPixmapCache::insert(key, pixmap);
}
if (pixmap.isNull()) {
qWarning("%s: error loading tile image", qPrintable(key));
painter->fillRect(QRectF(QPoint(x, y), _tileSize), Qt::white);
} else
painter->drawPixmap(QPoint(x, y), pixmap);
}
}
}
void OfflineMap::drawImage(QPainter *painter, const QRectF &rect)
{
if (!_img || _img->isNull())
painter->fillRect(rect, Qt::white);
else {
QPoint p = rect.topLeft().toPoint();
QImage crop = _img->copy(QRect(p, rect.size().toSize()));
painter->drawImage(rect.topLeft(), crop);
}
}
void OfflineMap::draw(QPainter *painter, const QRectF &rect)
{
if (_ozf.isOpen())
drawOZF(painter, rect);
else if (_tileSize.isValid())
drawTiled(painter, rect);
else
drawImage(painter, rect);
}

106
src/offlinemap.h Normal file
View File

@ -0,0 +1,106 @@
#ifndef OFFLINEMAP_H
#define OFFLINEMAP_H
#include <QTransform>
#include "map.h"
#include "tar.h"
#include "ozf.h"
#include "coordinates.h"
#include "projection.h"
class QIODevice;
class QImage;
class OfflineMap : public Map
{
Q_OBJECT
public:
OfflineMap(const QString &fileName, QObject *parent = 0);
OfflineMap(const QString &fileName, Tar &tar, QObject *parent = 0);
~OfflineMap();
const QString &name() const {return _name;}
QRectF bounds() const {return QRectF(QPointF(0, 0), _size);}
qreal resolution(const QPointF &) const {return _resolution;}
qreal zoom() const {return 1.0;}
qreal zoomFit(const QSize &, const QRectF &) {return 1.0;}
qreal zoomIn() {return 1.0;}
qreal zoomOut() {return 1.0;}
QPointF ll2xy(const Coordinates &c)
{return _transform.map(_projection->ll2xy(c));}
Coordinates xy2ll(const QPointF &p)
{return _projection->xy2ll(_inverted.map(p));}
void draw(QPainter *painter, const QRectF &rect);
void load();
void unload();
bool isValid() const {return _valid;}
const QString &errorString() const {return _errorString;}
QPointF ll2pp(const Coordinates &c) const
{return _projection->ll2xy(c);}
QPointF xy2pp(const QPointF &p) const
{return _inverted.map(p);}
QPointF pp2xy(const QPointF &p) const
{return _transform.map(p);}
private:
typedef struct {
QPoint xy;
Coordinates ll;
QPointF pp;
} ReferencePoint;
typedef struct {
double latitudeOrigin;
double longitudeOrigin;
double scale;
double falseEasting;
double falseNorthing;
double standardParallel1;
double standardParallel2;
int zone;
} ProjectionSetup;
int parse(QIODevice &device, QList<ReferencePoint> &points,
QString &projection, ProjectionSetup &setup, QString &datum);
bool parseMapFile(QIODevice &device, QList<ReferencePoint> &points,
QString &projection, ProjectionSetup &setup, QString &datum);
bool totalSizeSet();
bool createProjection(const QString &datum, const QString &projection,
const ProjectionSetup &setup, QList<ReferencePoint> &points);
bool computeTransformation(const QList<ReferencePoint> &points);
bool computeResolution(QList<ReferencePoint> &points);
bool getTileInfo(const QStringList &tiles, const QString &path = QString());
bool getImageInfo(const QString &path);
void drawTiled(QPainter *painter, const QRectF &rect);
void drawOZF(QPainter *painter, const QRectF &rect);
void drawImage(QPainter *painter, const QRectF &rect);
QString _name;
bool _valid;
QString _errorString;
QSize _size;
Projection *_projection;
QTransform _transform, _inverted;
qreal _resolution;
OZF _ozf;
Tar _tar;
QString _tarPath;
QImage *_img;
QString _imgPath;
QSize _tileSize;
QString _tileName;
};
#endif // OFFLINEMAP_H

245
src/onlinemap.cpp Normal file
View File

@ -0,0 +1,245 @@
#include <QFileInfo>
#include <QDir>
#include <QPainter>
#include "downloader.h"
#include "config.h"
#include "rd.h"
#include "wgs84.h"
#include "misc.h"
#include "coordinates.h"
#include "mercator.h"
#include "onlinemap.h"
#define ZOOM_MAX 18
#define ZOOM_MIN 3
#define TILE_SIZE 256
static QPoint mercator2tile(const QPointF &m, int z)
{
QPoint tile;
tile.setX((int)(floor((m.x() + 180.0) / 360.0 * (1<<z))));
tile.setY((int)(floor((1.0 - (m.y() / 180.0)) / 2.0 * (1<<z))));
return tile;
}
static int scale2zoom(qreal scale)
{
int zoom = (int)log2(360.0/(scale * (qreal)TILE_SIZE));
if (zoom < ZOOM_MIN)
return ZOOM_MIN;
if (zoom > ZOOM_MAX)
return ZOOM_MAX;
return zoom;
}
Downloader *OnlineMap::downloader;
OnlineMap::OnlineMap(const QString &name, const QString &url, QObject *parent)
: Map(parent)
{
_name = name;
_url = url;
_block = false;
_scale = ((360.0/(qreal)(1<<ZOOM_MAX))/(qreal)TILE_SIZE);
connect(downloader, SIGNAL(finished()), this, SLOT(emitLoaded()));
QString path = TILES_DIR + QString("/") + name;
if (!QDir().mkpath(path))
qWarning("Error creating tiles dir: %s\n", qPrintable(path));
}
void OnlineMap::emitLoaded()
{
emit loaded();
}
void OnlineMap::loadTilesAsync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists()) {
fillTile(t);
dl.append(Download(tileUrl(t), file));
} else
loadTileFile(t, file);
}
if (!dl.empty())
downloader->get(dl);
}
void OnlineMap::loadTilesSync(QList<Tile> &list)
{
QList<Download> dl;
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
QString file = tileFile(t);
QFileInfo fi(file);
if (!fi.exists())
dl.append(Download(tileUrl(t), file));
else
loadTileFile(t, file);
}
if (dl.empty())
return;
QEventLoop wait;
connect(downloader, SIGNAL(finished()), &wait, SLOT(quit()));
if (downloader->get(dl))
wait.exec();
for (int i = 0; i < list.size(); i++) {
Tile &t = list[i];
if (t.pixmap().isNull()) {
QString file = tileFile(t);
QFileInfo fi(file);
if (!(fi.exists() && loadTileFile(t, file)))
fillTile(t);
}
}
}
void OnlineMap::fillTile(Tile &tile)
{
tile.pixmap() = QPixmap(TILE_SIZE, TILE_SIZE);
tile.pixmap().fill();
}
bool OnlineMap::loadTileFile(Tile &tile, const QString &file)
{
if (!tile.pixmap().load(file)) {
qWarning("%s: error loading tile file\n", qPrintable(file));
return false;
}
return true;
}
QString OnlineMap::tileUrl(const Tile &tile)
{
QString url(_url);
url.replace("$z", QString::number(tile.zoom()));
url.replace("$x", QString::number(tile.xy().x()));
url.replace("$y", QString::number(tile.xy().y()));
return url;
}
QString OnlineMap::tileFile(const Tile &tile)
{
QString file = TILES_DIR + QString("/%1/%2-%3-%4").arg(name())
.arg(tile.zoom()).arg(tile.xy().x()).arg(tile.xy().y());
return file;
}
void OnlineMap::clearCache()
{
QString path = TILES_DIR + QString("/") + name();
QDir dir = QDir(path);
QStringList list = dir.entryList();
for (int i = 0; i < list.count(); i++)
dir.remove(list.at(i));
}
QRectF OnlineMap::bounds() const
{
return scaled(QRectF(QPointF(-180, -180), QSizeF(360, 360)), 1.0/_scale);
}
qreal OnlineMap::zoomFit(const QSize &size, const QRectF &br)
{
if (br.isNull())
_scale = ((360.0/(qreal)(1<<ZOOM_MAX))/(qreal)TILE_SIZE);
else {
Coordinates topLeft(br.topLeft());
Coordinates bottomRight(br.bottomRight());
QRectF tbr(Mercator().ll2xy(topLeft), Mercator().ll2xy(bottomRight));
QPointF sc(tbr.width() / size.width(), tbr.height() / size.height());
_scale = ((360.0/(qreal)(1<<scale2zoom(qMax(sc.x(), sc.y()))))
/ (qreal)TILE_SIZE);
}
return _scale;
}
qreal OnlineMap::resolution(const QPointF &p) const
{
return (WGS84_RADIUS * 2 * M_PI * _scale / 360.0
* cos(2.0 * atan(exp(deg2rad(-p.y() * _scale))) - M_PI/2));
}
qreal OnlineMap::zoomIn()
{
int zoom = qMin(scale2zoom(_scale) + 1, ZOOM_MAX);
_scale = ((360.0/(qreal)(1<<zoom))/(qreal)TILE_SIZE);
return _scale;
}
qreal OnlineMap::zoomOut()
{
int zoom = qMax(scale2zoom(_scale) - 1, ZOOM_MIN);
_scale = ((360.0/(qreal)(1<<zoom))/(qreal)TILE_SIZE);
return _scale;
}
void OnlineMap::draw(QPainter *painter, const QRectF &rect)
{
int zoom = scale2zoom(_scale);
QPoint tile = mercator2tile(QPointF(rect.topLeft().x() * _scale,
-rect.topLeft().y() * _scale), zoom);
QPoint tl = QPoint((int)floor(rect.left() / (qreal)TILE_SIZE)
* TILE_SIZE, (int)floor(rect.top() / TILE_SIZE) * TILE_SIZE);
QList<Tile> tiles;
QSizeF s(rect.right() - tl.x(), rect.bottom() - tl.y());
for (int i = 0; i < ceil(s.width() / TILE_SIZE); i++)
for (int j = 0; j < ceil(s.height() / TILE_SIZE); j++)
tiles.append(Tile(QPoint(tile.x() + i, tile.y() + j), zoom));
if (_block)
loadTilesSync(tiles);
else
loadTilesAsync(tiles);
for (int i = 0; i < tiles.count(); i++) {
Tile &t = tiles[i];
QPoint tp(tl.x() + (t.xy().x() - tile.x()) * TILE_SIZE,
tl.y() + (t.xy().y() - tile.y()) * TILE_SIZE);
painter->drawPixmap(tp, t.pixmap());
}
}
QPointF OnlineMap::ll2xy(const Coordinates &c)
{
QPointF m = Mercator().ll2xy(c);
return QPointF(m.x() / _scale, m.y() / -_scale);
}
Coordinates OnlineMap::xy2ll(const QPointF &p)
{
QPointF m(p.x() * _scale, -p.y() * _scale);
return Mercator().xy2ll(m);
}

56
src/onlinemap.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef ONLINEMAP_H
#define ONLINEMAP_H
#include "map.h"
#include "tile.h"
class Downloader;
class OnlineMap : public Map
{
Q_OBJECT
public:
OnlineMap(const QString &name, const QString &url, QObject *parent = 0);
const QString &name() const {return _name;}
QRectF bounds() const;
qreal resolution(const QPointF &p) const;
qreal zoom() const {return _scale;}
qreal zoomFit(const QSize &size, const QRectF &br);
qreal zoomIn();
qreal zoomOut();
QPointF ll2xy(const Coordinates &c);
Coordinates xy2ll(const QPointF &p);
void draw(QPainter *painter, const QRectF &rect);
void setBlockingMode(bool block) {_block = block;}
void clearCache();
static void setDownloader(Downloader *downloader)
{OnlineMap::downloader = downloader;}
private slots:
void emitLoaded();
private:
QString tileUrl(const Tile &tile);
QString tileFile(const Tile &tile);
bool loadTileFile(Tile &tile, const QString &file);
void fillTile(Tile &tile);
void loadTilesAsync(QList<Tile> &list);
void loadTilesSync(QList<Tile> &list);
qreal _scale;
QString _name;
QString _url;
bool _block;
static Downloader *downloader;
};
#endif // ONLINEMAP_H

26
src/opengl.h Normal file
View File

@ -0,0 +1,26 @@
#include <QtGlobal>
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) || defined(Q_OS_MAC)
#include <QGLWidget>
#include <QGLFormat>
#else
#include <QOpenGLWidget>
#include <QSurfaceFormat>
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) || defined(Q_OS_MAC)
#define OPENGL_WIDGET QGLWidget
#else
#define OPENGL_WIDGET QOpenGLWidget
#endif
#if QT_VERSION < QT_VERSION_CHECK(5, 4, 0) || defined(Q_OS_MAC)
#define OPENGL_SET_SAMPLES(samples) \
{QGLFormat fmt; \
fmt.setSamples(samples); \
QGLFormat::setDefaultFormat(fmt);}
#else
#define OPENGL_SET_SAMPLES(samples) \
{QSurfaceFormat fmt; \
fmt.setSamples(samples);\
QSurfaceFormat::setDefaultFormat(fmt);}
#endif

407
src/optionsdialog.cpp Normal file
View File

@ -0,0 +1,407 @@
#include <QVBoxLayout>
#include <QFormLayout>
#include <QDialogButtonBox>
#include <QListWidget>
#include <QListWidgetItem>
#include <QStackedWidget>
#include <QTabWidget>
#include <QSpinBox>
#include <QGroupBox>
#include <QCheckBox>
#include <QComboBox>
#include <QLabel>
#include <QSysInfo>
#include "config.h"
#include "icons.h"
#include "colorbox.h"
#include "stylecombobox.h"
#include "oddspinbox.h"
#include "optionsdialog.h"
#define MENU_MARGIN 20
#define MENU_ICON_SIZE 32
QWidget *OptionsDialog::createAppearancePage()
{
_baseColor = new ColorBox();
_baseColor->setColor(_options->palette.color());
_colorOffset = new QDoubleSpinBox();
_colorOffset->setMinimum(0);
_colorOffset->setMaximum(1.0);
_colorOffset->setSingleStep(0.01);
_colorOffset->setValue(_options->palette.shift());
QFormLayout *paletteLayout = new QFormLayout();
paletteLayout->addRow(tr("Base color:"), _baseColor);
paletteLayout->addRow(tr("Palette shift:"), _colorOffset);
QWidget *colorTab = new QWidget();
colorTab->setLayout(paletteLayout);
_trackWidth = new QSpinBox();
_trackWidth->setValue(_options->trackWidth);
_trackWidth->setMinimum(1);
_trackStyle = new StyleComboBox();
_trackStyle->setValue(_options->trackStyle);
QFormLayout *trackLayout = new QFormLayout();
trackLayout->addRow(tr("Track width:"), _trackWidth);
trackLayout->addRow(tr("Track style:"), _trackStyle);
#ifndef Q_OS_MAC
QGroupBox *trackBox = new QGroupBox(tr("Tracks"));
trackBox->setLayout(trackLayout);
#endif
_routeWidth = new QSpinBox();
_routeWidth->setValue(_options->routeWidth);
_routeWidth->setMinimum(1);
_routeStyle = new StyleComboBox();
_routeStyle->setValue(_options->routeStyle);
QFormLayout *routeLayout = new QFormLayout();
routeLayout->addRow(tr("Route width:"), _routeWidth);
routeLayout->addRow(tr("Route style:"), _routeStyle);
#ifndef Q_OS_MAC
QGroupBox *routeBox = new QGroupBox(tr("Routes"));
routeBox->setLayout(routeLayout);
#endif // Q_OS_MAC
_pathAA = new QCheckBox(tr("Use anti-aliasing"));
_pathAA->setChecked(_options->pathAntiAliasing);
QFormLayout *pathAALayout = new QFormLayout();
pathAALayout->addWidget(_pathAA);
QWidget *pathTab = new QWidget();
QVBoxLayout *pathTabLayout = new QVBoxLayout();
#ifdef Q_OS_MAC
QFrame *l1 = new QFrame();
l1->setFrameShape(QFrame::HLine);
l1->setFrameShadow(QFrame::Sunken);
QFrame *l2 = new QFrame();
l2->setFrameShape(QFrame::HLine);
l2->setFrameShadow(QFrame::Sunken);
pathTabLayout->addLayout(trackLayout);
pathTabLayout->addWidget(l1);
pathTabLayout->addLayout(routeLayout);
pathTabLayout->addWidget(l2);
#else // Q_OS_MAC
pathTabLayout->addWidget(trackBox);
pathTabLayout->addWidget(routeBox);
#endif // Q_OS_MAC
pathTabLayout->addLayout(pathAALayout);
pathTabLayout->addStretch();
pathTab->setLayout(pathTabLayout);
_graphWidth = new QSpinBox();
_graphWidth->setValue(_options->graphWidth);
_graphWidth->setMinimum(1);
QFormLayout *graphLayout = new QFormLayout();
graphLayout->addRow(tr("Line width:"), _graphWidth);
_graphAA = new QCheckBox(tr("Use anti-aliasing"));
_graphAA->setChecked(_options->graphAntiAliasing);
QFormLayout *graphAALayout = new QFormLayout();
graphAALayout->addWidget(_graphAA);
QWidget *graphTab = new QWidget();
QVBoxLayout *graphTabLayout = new QVBoxLayout();
graphTabLayout->addLayout(graphLayout);
graphTabLayout->addLayout(graphAALayout);
graphTabLayout->addStretch();
graphTab->setLayout(graphTabLayout);
QTabWidget *appearancePage = new QTabWidget();
appearancePage->addTab(colorTab, tr("Colors"));
appearancePage->addTab(pathTab, tr("Paths"));
appearancePage->addTab(graphTab, tr("Graphs"));
return appearancePage;
}
QWidget *OptionsDialog::createDataPage()
{
QString filterToolTip = tr("Moving average window size");
_elevationFilter = new OddSpinBox();
_elevationFilter->setValue(_options->elevationFilter);
_elevationFilter->setToolTip(filterToolTip);
_speedFilter = new OddSpinBox();
_speedFilter->setValue(_options->speedFilter);
_speedFilter->setToolTip(filterToolTip);
_heartRateFilter = new OddSpinBox();
_heartRateFilter->setValue(_options->heartRateFilter);
_heartRateFilter->setToolTip(filterToolTip);
_cadenceFilter = new OddSpinBox();
_cadenceFilter->setValue(_options->cadenceFilter);
_cadenceFilter->setToolTip(filterToolTip);
_powerFilter = new OddSpinBox();
_powerFilter->setValue(_options->powerFilter);
_powerFilter->setToolTip(filterToolTip);
QFormLayout *smoothLayout = new QFormLayout();
smoothLayout->addRow(tr("Elevation:"), _elevationFilter);
smoothLayout->addRow(tr("Speed:"), _speedFilter);
smoothLayout->addRow(tr("Heart rate:"), _heartRateFilter);
smoothLayout->addRow(tr("Cadence:"), _cadenceFilter);
smoothLayout->addRow(tr("Power:"), _powerFilter);
#ifndef Q_OS_MAC
QGroupBox *smoothBox = new QGroupBox(tr("Smoothing"));
smoothBox->setLayout(smoothLayout);
#endif // Q_OS_MAC
_outlierEliminate = new QCheckBox(tr("Eliminate GPS outliers"));
_outlierEliminate->setChecked(_options->outlierEliminate);
QFormLayout *outlierLayout = new QFormLayout();
outlierLayout->addWidget(_outlierEliminate);
#ifndef Q_OS_MAC
QGroupBox *outlierBox = new QGroupBox(tr("Outlier elimination"));
outlierBox->setLayout(outlierLayout);
#endif // Q_OS_MAC
QWidget *filterTab = new QWidget();
QVBoxLayout *filterTabLayout = new QVBoxLayout();
#ifdef Q_OS_MAC
QLabel *label = new QLabel(tr("Smoothing:"));
QFrame *line = new QFrame();
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
filterTabLayout->addWidget(label);
filterTabLayout->addLayout(smoothLayout);
filterTabLayout->addWidget(line);
filterTabLayout->addLayout(outlierLayout);
#else // Q_OS_MAC
filterTabLayout->addWidget(smoothBox);
filterTabLayout->addWidget(outlierBox);
#endif // Q_OS_MAC
filterTabLayout->addStretch();
filterTab->setLayout(filterTabLayout);
_pauseSpeed = new QDoubleSpinBox();
_pauseSpeed->setDecimals(1);
_pauseSpeed->setSingleStep(0.1);
_pauseSpeed->setMinimum(0.1);
if (_options->units == Imperial) {
_pauseSpeed->setValue(_options->pauseSpeed * MS2MIH);
_pauseSpeed->setSuffix(UNIT_SPACE + tr("mi/h"));
} else {
_pauseSpeed->setValue(_options->pauseSpeed * MS2KMH);
_pauseSpeed->setSuffix(UNIT_SPACE + tr("km/h"));
}
_pauseInterval = new QSpinBox();
_pauseInterval->setMinimum(1);
_pauseInterval->setSuffix(UNIT_SPACE + tr("s"));
_pauseInterval->setValue(_options->pauseInterval);
QFormLayout *pauseLayout = new QFormLayout();
pauseLayout->addRow(tr("Minimal speed:"), _pauseSpeed);
pauseLayout->addRow(tr("Minimal duration:"), _pauseInterval);
QWidget *pauseTab = new QWidget();
pauseTab->setLayout(pauseLayout);
QTabWidget *filterPage = new QTabWidget();
filterPage->addTab(filterTab, tr("Filtering"));
filterPage->addTab(pauseTab, tr("Pause detection"));
return filterPage;
}
QWidget *OptionsDialog::createPOIPage()
{
_poiRadius = new QDoubleSpinBox();
_poiRadius->setSingleStep(1);
_poiRadius->setDecimals(1);
if (_options->units == Imperial) {
_poiRadius->setValue(_options->poiRadius / MIINM);
_poiRadius->setSuffix(UNIT_SPACE + tr("mi"));
} else {
_poiRadius->setValue(_options->poiRadius / KMINM);
_poiRadius->setSuffix(UNIT_SPACE + tr("km"));
}
QFormLayout *poiLayout = new QFormLayout();
poiLayout->addRow(tr("POI radius:"), _poiRadius);
QWidget *poiTab = new QWidget();
poiTab->setLayout(poiLayout);
QTabWidget *poiPage = new QTabWidget();
poiPage->addTab(poiTab, tr("POI"));
return poiPage;
}
QWidget *OptionsDialog::createExportPage()
{
_name = new QCheckBox(tr("Name"));
_name->setChecked(_options->printName);
_date = new QCheckBox(tr("Date"));
_date->setChecked(_options->printDate);
_distance = new QCheckBox(tr("Distance"));
_distance->setChecked(_options->printDistance);
_time = new QCheckBox(tr("Time"));
_time->setChecked(_options->printTime);
_movingTime = new QCheckBox(tr("Moving time"));
_movingTime->setChecked(_options->printMovingTime);
_itemCount = new QCheckBox(tr("Item count (>1)"));
_itemCount->setChecked(_options->printItemCount);
QFormLayout *headerTabLayout = new QFormLayout();
headerTabLayout->addWidget(_name);
headerTabLayout->addWidget(_date);
headerTabLayout->addWidget(_distance);
headerTabLayout->addWidget(_time);
headerTabLayout->addWidget(_movingTime);
headerTabLayout->addItem(new QSpacerItem(10, 10));
headerTabLayout->addWidget(_itemCount);
QWidget *headerTab = new QWidget();
headerTab->setLayout(headerTabLayout);
_separateGraphPage = new QCheckBox(tr("Separate graph page"));
_separateGraphPage->setChecked(_options->separateGraphPage);
QFormLayout *graphTabLayout = new QFormLayout();
graphTabLayout->addWidget(_separateGraphPage);
QWidget *graphTab = new QWidget();
graphTab->setLayout(graphTabLayout);
QTabWidget *exportPage = new QTabWidget();
exportPage->addTab(headerTab, tr("Header"));
exportPage->addTab(graphTab, tr("Graphs"));
return exportPage;
}
QWidget *OptionsDialog::createSystemPage()
{
_useOpenGL = new QCheckBox(tr("Use OpenGL"));
#ifdef Q_OS_WIN32
if (QSysInfo::WindowsVersion < QSysInfo::WV_VISTA) {
_useOpenGL->setChecked(false);
_useOpenGL->setEnabled(false);
} else
#endif // Q_OS_WIN32
_useOpenGL->setChecked(_options->useOpenGL);
_pixmapCache = new QSpinBox();
_pixmapCache->setMinimum(16);
_pixmapCache->setMaximum(1024);
_pixmapCache->setSuffix(UNIT_SPACE + tr("MB"));
_pixmapCache->setValue(_options->pixmapCache);
QFormLayout *cacheLayout = new QFormLayout();
cacheLayout->addRow(tr("Image cache size:"), _pixmapCache);
QFormLayout *openGLLayout = new QFormLayout();
openGLLayout->addWidget(_useOpenGL);
QWidget *systemTab = new QWidget();
QVBoxLayout *systemTabLayout = new QVBoxLayout();
systemTabLayout->addLayout(cacheLayout);
systemTabLayout->addLayout(openGLLayout);
systemTabLayout->addStretch();
systemTab->setLayout(systemTabLayout);
QTabWidget *systemPage = new QTabWidget();
systemPage->addTab(systemTab, tr("System"));
return systemPage;
}
OptionsDialog::OptionsDialog(Options *options, QWidget *parent)
: QDialog(parent), _options(options)
{
QStackedWidget *pages = new QStackedWidget();
pages->addWidget(createAppearancePage());
pages->addWidget(createDataPage());
pages->addWidget(createPOIPage());
pages->addWidget(createExportPage());
pages->addWidget(createSystemPage());
QListWidget *menu = new QListWidget();
menu->setIconSize(QSize(MENU_ICON_SIZE, MENU_ICON_SIZE));
new QListWidgetItem(QIcon(QPixmap(APPEARANCE_ICON)), tr("Appearance"),
menu);
new QListWidgetItem(QIcon(QPixmap(DATA_ICON)), tr("Data"), menu);
new QListWidgetItem(QIcon(QPixmap(POI_ICON)), tr("POI"), menu);
new QListWidgetItem(QIcon(QPixmap(PRINT_EXPORT_ICON)), tr("Print & Export"),
menu);
new QListWidgetItem(QIcon(QPixmap(SYSTEM_ICON)), tr("System"), menu);
QHBoxLayout *contentLayout = new QHBoxLayout();
contentLayout->addWidget(menu);
contentLayout->addWidget(pages);
menu->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
menu->setMaximumWidth(menu->sizeHintForColumn(0) + 2 * menu->frameWidth()
+ MENU_MARGIN);
pages->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
pages->setMinimumWidth(2 * menu->size().width());
connect(menu, SIGNAL(currentRowChanged(int)), pages,
SLOT(setCurrentIndex(int)));
menu->item(0)->setSelected(true);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(contentLayout);
layout->addWidget(buttonBox);
setLayout(layout);
setWindowTitle(tr("Options"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
void OptionsDialog::accept()
{
_options->palette.setColor(_baseColor->color());
_options->palette.setShift(_colorOffset->value());
_options->trackWidth = _trackWidth->value();
_options->trackStyle = (Qt::PenStyle) _trackStyle->itemData(
_trackStyle->currentIndex()).toInt();
_options->routeWidth = _routeWidth->value();
_options->routeStyle = (Qt::PenStyle) _routeStyle->itemData(
_routeStyle->currentIndex()).toInt();
_options->pathAntiAliasing = _pathAA->isChecked();
_options->graphWidth = _graphWidth->value();
_options->graphAntiAliasing = _graphAA->isChecked();
_options->elevationFilter = _elevationFilter->value();
_options->speedFilter = _speedFilter->value();
_options->heartRateFilter = _heartRateFilter->value();
_options->cadenceFilter = _cadenceFilter->value();
_options->powerFilter = _powerFilter->value();
_options->outlierEliminate = _outlierEliminate->isChecked();
_options->pauseSpeed = (_options->units == Imperial)
? _pauseSpeed->value() / MS2MIH : _pauseSpeed->value() / MS2KMH;
_options->pauseInterval = _pauseInterval->value();
_options->poiRadius = (_options->units == Imperial)
? _poiRadius->value() * MIINM : _poiRadius->value() * KMINM;
_options->useOpenGL = _useOpenGL->isChecked();
_options->pixmapCache = _pixmapCache->value();
_options->printName = _name->isChecked();
_options->printDate = _date->isChecked();
_options->printDistance = _distance->isChecked();
_options->printTime = _time->isChecked();
_options->printMovingTime = _movingTime->isChecked();
_options->printItemCount = _itemCount->isChecked();
_options->separateGraphPage = _separateGraphPage->isChecked();
QDialog::accept();
}

105
src/optionsdialog.h Normal file
View File

@ -0,0 +1,105 @@
#ifndef OPTIONSDIALOG_H
#define OPTIONSDIALOG_H
#include <QDialog>
#include "palette.h"
#include "units.h"
class ColorBox;
class StyleComboBox;
class OddSpinBox;
class QSpinBox;
class QDoubleSpinBox;
class QComboBox;
class QCheckBox;
struct Options {
// Appearance
Palette palette;
int trackWidth;
int routeWidth;
Qt::PenStyle trackStyle;
Qt::PenStyle routeStyle;
int graphWidth;
bool pathAntiAliasing;
bool graphAntiAliasing;
// Data
int elevationFilter;
int speedFilter;
int heartRateFilter;
int cadenceFilter;
int powerFilter;
bool outlierEliminate;
qreal pauseSpeed;
int pauseInterval;
// POI
int poiRadius;
// System
bool useOpenGL;
int pixmapCache;
// Print/Export
bool printName;
bool printDate;
bool printDistance;
bool printTime;
bool printMovingTime;
bool printItemCount;
bool separateGraphPage;
Units units;
};
class OptionsDialog : public QDialog
{
Q_OBJECT
public:
OptionsDialog(Options *options, QWidget *parent = 0);
public slots:
void accept();
private:
QWidget *createAppearancePage();
QWidget *createDataPage();
QWidget *createPOIPage();
QWidget *createSystemPage();
QWidget *createExportPage();
Options *_options;
// Appearance
ColorBox *_baseColor;
QDoubleSpinBox *_colorOffset;
QSpinBox *_trackWidth;
StyleComboBox *_trackStyle;
QSpinBox *_routeWidth;
StyleComboBox *_routeStyle;
QCheckBox *_pathAA;
QSpinBox *_graphWidth;
QCheckBox *_graphAA;
// Data
OddSpinBox *_elevationFilter;
OddSpinBox *_speedFilter;
OddSpinBox *_heartRateFilter;
OddSpinBox *_cadenceFilter;
OddSpinBox *_powerFilter;
QCheckBox *_outlierEliminate;
QDoubleSpinBox *_pauseSpeed;
QSpinBox *_pauseInterval;
// POI
QDoubleSpinBox *_poiRadius;
// System
QSpinBox *_pixmapCache;
QCheckBox *_useOpenGL;
// Print/Export
QCheckBox *_name;
QCheckBox *_date;
QCheckBox *_distance;
QCheckBox *_time;
QCheckBox *_movingTime;
QCheckBox *_itemCount;
QCheckBox *_separateGraphPage;
};
#endif // OPTIONSDIALOG_H

234
src/ozf.cpp Normal file
View File

@ -0,0 +1,234 @@
#include <QtEndian>
#include <QFile>
#include "ozf.h"
#define OZF2_MAGIC 0x7778
#define OZF3_MAGIC 0x7780
#define SEPARATOR 0x77777777
static const quint8 XKEY[] =
{
0x2D, 0x4A, 0x43, 0xF1, 0x27, 0x9B, 0x69, 0x4F,
0x36, 0x52, 0x87, 0xEC, 0x5F, 0x42, 0x53, 0x22,
0x9E, 0x8B, 0x2D, 0x83, 0x3D, 0xD2, 0x84, 0xBA,
0xD8, 0x5B
};
static void decrypt(void *data, size_t size, quint8 init)
{
for (size_t i = 0; i < size; i++)
reinterpret_cast<quint8*>(data)[i] ^= XKEY[i % sizeof(XKEY)] + init;
}
template<class T> bool OZF::readValue(T &val)
{
T data;
if (_file.read((char*)&data, sizeof(T)) < (qint64)sizeof(T))
return false;
if (_decrypt)
decrypt(&data, sizeof(T), _key);
if (sizeof(T) > 1)
val = qFromLittleEndian(data);
else
val = data;
return true;
}
bool OZF::read(void *data, size_t size)
{
if (_file.read((char*)data, size) < (qint64)size)
return false;
if (_decrypt)
decrypt(data, size, _key);
return true;
}
bool OZF::readKey()
{
quint8 randomNumber, initial;
quint32 keyblock;
if (!_file.seek(14))
return false;
if (!readValue(randomNumber))
return false;
if (!_file.seek(162))
return false;
if (!readValue(initial))
return false;
_decrypt = true; _key = initial;
if (!_file.seek(15 + randomNumber))
return false;
if (!readValue(keyblock))
return false;
switch (keyblock & 0xFF) {
case 0xf1:
initial += 0x8a;
break;
case 0x18:
case 0x54:
initial += 0xa0;
break;
case 0x56:
initial += 0xb9;
break;
case 0x43:
initial += 0x6a;
break;
case 0x83:
initial += 0xa4;
break;
case 0xc5:
initial += 0x7e;
break;
case 0x38:
initial += 0xc1;
break;
default:
break;
}
_key = initial;
return true;
}
bool OZF::readHeaders()
{
quint16 magic;
quint32 separator;
if (!readValue(magic))
return false;
if (magic == OZF2_MAGIC) {
if (!_file.seek(_file.pos() + 52))
return false;
if (!readValue(separator) || separator != SEPARATOR)
return false;
} else if (magic == OZF3_MAGIC) {
if (!readKey())
return false;
} else
return false;
return true;
}
bool OZF::readTileTable()
{
quint32 offset, bgr0, w, h;
quint16 x, y;
if (!_file.seek(_file.size() - 4))
return false;
// table offset
if (!readValue(offset))
return false;
if (!_file.seek(offset))
return false;
// tiles offset (zoom level 0)
if (!readValue(offset))
return false;
if (!_file.seek(offset))
return false;
if (!readValue(w))
return false;
if (!readValue(h))
return false;
if (!readValue(x))
return false;
if (!readValue(y))
return false;
_size = QSize(w, h);
_dim = QSize(x, y);
_palette = QVector<quint32>(256);
if (!read(&(_palette[0]), sizeof(quint32) * 256))
return false;
for (int i = 0; i < _palette.size(); i++) {
bgr0 = qFromLittleEndian(_palette.at(i));
quint32 b = (bgr0 & 0x000000FF);
quint32 g = (bgr0 & 0x0000FF00) >> 8;
quint32 r = (bgr0 & 0x00FF0000) >> 16;
_palette[i] = 0xFF000000 | r << 16 | g << 8 | b;
}
_tiles = QVector<quint32>(_dim.width() * _dim.height() + 1);
for (int i = 0; i < _tiles.size(); i++)
if (!readValue(_tiles[i]))
return false;
return true;
}
bool OZF::load(const QString &path)
{
if (_file.isOpen())
_file.close();
_file.setFileName(path);
if (!_file.open(QIODevice::ReadOnly))
return false;
if (!readHeaders()) {
qWarning("%s: not a OZF2/OZF3 file", qPrintable(_file.fileName()));
_file.close();
return false;
}
if (!readTileTable()) {
qWarning("%s: file format error", qPrintable(_file.fileName()));
_file.close();
_size = QSize();
return false;
}
return true;
}
QPixmap OZF::tile(int x, int y)
{
Q_ASSERT(_file.isOpen());
int i = (y/tileSize().height()) * _dim.width() + (x/tileSize().width());
if (i >= _tiles.size() - 1 || i < 0)
return QPixmap();
int size = _tiles.at(i+1) - _tiles.at(i);
if (!_file.seek(_tiles.at(i)))
return QPixmap();
QByteArray ba = _file.read(size);
if (ba.size() != size)
return QPixmap();
if (_decrypt)
decrypt(ba.data(), qMin(16, ba.size()), _key);
quint32 bes = qToBigEndian(tileSize().width() * tileSize().height());
ba.prepend(QByteArray((char*)&bes, sizeof(bes)));
QByteArray uba = qUncompress(ba);
if (uba.size() != tileSize().width() * tileSize().height())
return QPixmap();
QImage img((const uchar*)uba.constData(), tileSize().width(),
tileSize().height(), QImage::Format_Indexed8);
img.setColorTable(_palette);
return QPixmap::fromImage(img.mirrored());
}

43
src/ozf.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef OZF_H
#define OZF_H
#include <QString>
#include <QSize>
#include <QColor>
#include <QVector>
#include <QFile>
#include <QPixmap>
class OZF
{
public:
OZF() : _decrypt(false), _key(0) {}
bool load(const QString &path);
QString fileName() const {return _file.fileName();}
bool isOpen() const {return _file.isOpen();}
QSize size() const {return _size;}
QSize tileSize() const {return QSize(64, 64);}
QPixmap tile(int x, int y);
private:
template<class T> bool readValue(T &val);
bool read(void *data, size_t size);
bool readKey();
bool readHeaders();
bool readTileTable();
bool _decrypt;
quint8 _key;
QSize _size;
QSize _dim;
QVector<QRgb> _palette;
QVector<quint32> _tiles;
QFile _file;
};
#endif // OZF_H

View File

@ -1,62 +1,46 @@
#include "palette.h"
#define HUE_INIT 0.1f
#define HUE_INCREMENT 0.62f
#define SATURATION 0.99f
#define VALUE 0.99f
static unsigned hsv2rgb(float h, float s, float v)
Palette::Palette(const QColor &color, qreal shift)
{
unsigned hi;
float r = 0, g = 0, b = 0, p, q, t, f;
_h = 0; _s = 0; _v = 0; _a = 1.0;
hi = (unsigned)(h * 6.0f);
f = h * 6.0f - hi;
p = v * (1.0f - s);
q = v * (1.0f - f * s);
t = v * (1.0f - (1.0f - f) * s);
setColor(color);
setShift(shift);
switch (hi) {
case 0:
r = v; g = t; b = p;
break;
case 1:
r = q; g = v; b = p;
break;
case 2:
r = p; g = v; b = t;
break;
case 3:
r = p; g = q; b = v;
break;
case 4:
r = t; g = p; b = v;
break;
case 5:
r = v; g = p; b = q;
break;
}
return ((unsigned)(r * 256) << 16)
+ ((unsigned)(g * 256) << 8)
+ (unsigned)(b * 256);
_state = _h;
}
Palette::Palette()
void Palette::setColor(const QColor &color)
{
_hueState = HUE_INIT;
if (color.isValid())
color.getHsvF(&_h, &_s, &_v, &_a);
}
QColor Palette::color()
void Palette::setShift(qreal shift)
{
_hueState += HUE_INCREMENT;
_hueState -= (int) _hueState;
if (shift >= 0 && shift <= 1.0)
_shift = shift;
}
return QColor(hsv2rgb(_hueState, SATURATION, VALUE));
QColor Palette::nextColor()
{
QColor ret = QColor::fromHsvF(_state, _s, _v, _a);
_state += _shift;
_state -= (int) _state;
return ret;
}
void Palette::reset()
{
_hueState = HUE_INIT;
_state = _h;
}
QDebug operator<<(QDebug dbg, const Palette &palette)
{
dbg.nospace() << "Palette(" << palette.color() << ", " << palette.shift()
<< ")";
return dbg.maybeSpace();
}

View File

@ -2,16 +2,32 @@
#define PALETTE_H
#include <QColor>
#include <QDebug>
class Palette
{
public:
Palette();
QColor color();
Palette(const QColor &color = Qt::blue, qreal shift = 0.62);
QColor color() const {return QColor::fromHsvF(_h, _s, _v, _a).toRgb();}
qreal shift() const {return _shift;}
void setColor(const QColor &color);
void setShift(qreal shift);
QColor nextColor();
void reset();
bool operator==(const Palette &other) const
{return (_h == other._h && _s == other._s && _v == other._v
&& _a == other._a && _shift == other._shift);}
bool operator!=(const Palette &other) const
{return !(*this == other);}
private:
float _hueState;
qreal _h, _s, _v, _a, _shift;
qreal _state;
};
QDebug operator<<(QDebug dbg, const Palette &palette);
#endif // PALLETE_H

33
src/path.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "path.h"
QRectF Path::boundingRect() const
{
if (size() < 2)
return QRectF();
QPointF topLeft(at(0).coordinates().lon(), at(0).coordinates().lat());
QPointF bottomRight(topLeft);
for (int i = 1; i < size(); i++) {
qreal x = at(i).coordinates().lon();
qreal y = at(i).coordinates().lat();
if (x < topLeft.x())
topLeft.setX(x);
if (y < topLeft.y())
topLeft.setY(y);
if (x > bottomRight.x())
bottomRight.setX(x);
if (y > bottomRight.y())
bottomRight.setY(y);
}
return QRectF(topLeft, bottomRight);
}
QDebug operator<<(QDebug dbg, const PathPoint &point)
{
dbg.nospace() << "PathPoint(" << point.distance() << ", "
<< point.coordinates() << ")";
return dbg.maybeSpace();
}

View File

@ -2,8 +2,33 @@
#define PATH_H
#include <QVector>
#include <QRectF>
#include "coordinates.h"
typedef QVector<Coordinates> Path;
class PathPoint
{
public:
PathPoint() :
_coordinates(Coordinates()), _distance(NAN) {}
PathPoint(const Coordinates &coordinates, qreal distance)
: _coordinates(coordinates), _distance(distance) {}
const Coordinates &coordinates() const {return _coordinates;}
qreal distance() const {return _distance;}
private:
Coordinates _coordinates;
qreal _distance;
};
Q_DECLARE_TYPEINFO(PathPoint, Q_PRIMITIVE_TYPE);
QDebug operator<<(QDebug dbg, const PathPoint &point);
class Path : public QVector<PathPoint>
{
public:
QRectF boundingRect() const;
};
#endif // PATH_H

Some files were not shown because too many files have changed in this diff Show More