source: josm/trunk/src/org/openstreetmap/josm/data/gpx/GpxImageCorrelation.java@ 14211

Last change on this file since 14211 was 14211, checked in by Don-vip, 6 years ago

sonarqube - squid:S1604 - Anonymous inner classes containing only one method should become lambdas

  • Property svn:eol-style set to native
File size: 13.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.gpx;
3
4import java.util.ArrayList;
5import java.util.Date;
6import java.util.List;
7import java.util.concurrent.TimeUnit;
8
9import org.openstreetmap.josm.spi.preferences.Config;
10import org.openstreetmap.josm.tools.Logging;
11import org.openstreetmap.josm.tools.Pair;
12
13/**
14 * Correlation logic for {@code CorrelateGpxWithImages}.
15 * @since 14205
16 */
17public final class GpxImageCorrelation {
18
19 private GpxImageCorrelation() {
20 // Hide public constructor
21 }
22
23 /**
24 * Match a list of photos to a gpx track with a given offset.
25 * All images need a exifTime attribute and the List must be sorted according to these times.
26 * @param images images to match
27 * @param selectedGpx selected GPX data
28 * @param offset offset
29 * @param forceTags force tagging of all photos, otherwise prefs are used
30 * @return number of matched points
31 */
32 public static int matchGpxTrack(List<? extends GpxImageEntry> images, GpxData selectedGpx, long offset, boolean forceTags) {
33 int ret = 0;
34
35 long prevWpTime = 0;
36 WayPoint prevWp = null;
37
38 List<List<List<WayPoint>>> trks = new ArrayList<>();
39
40 for (GpxTrack trk : selectedGpx.tracks) {
41 List<List<WayPoint>> segs = new ArrayList<>();
42 for (GpxTrackSegment seg : trk.getSegments()) {
43 List<WayPoint> wps = new ArrayList<>(seg.getWayPoints());
44 if (!wps.isEmpty()) {
45 //remove waypoints at the beginning of the track/segment without timestamps
46 int wp;
47 for (wp = 0; wp < wps.size(); wp++) {
48 if (wps.get(wp).setTimeFromAttribute() != null) {
49 break;
50 }
51 }
52 if (wp == 0) {
53 segs.add(wps);
54 } else if (wp < wps.size()) {
55 segs.add(wps.subList(wp, wps.size()));
56 }
57 }
58 }
59 //sort segments by first waypoint
60 if (!segs.isEmpty()) {
61 segs.sort((o1, o2) -> {
62 if (o1.isEmpty() || o2.isEmpty())
63 return 0;
64 return Double.compare(o1.get(0).time, o2.get(0).time);
65 });
66 trks.add(segs);
67 }
68 }
69 //sort tracks by first waypoint of first segment
70 trks.sort((o1, o2) -> {
71 if (o1.isEmpty() || o1.get(0).isEmpty()
72 || o2.isEmpty() || o2.get(0).isEmpty())
73 return 0;
74 return Double.compare(o1.get(0).get(0).time, o2.get(0).get(0).time);
75 });
76
77 boolean trkInt, trkTag, segInt, segTag;
78 int trkTime, trkDist, trkTagTime, segTime, segDist, segTagTime;
79
80 if (forceTags) { //temporary option to override advanced settings and activate all possible interpolations / tagging methods
81 trkInt = trkTag = segInt = segTag = true;
82 trkTime = trkDist = trkTagTime = segTime = segDist = segTagTime = Integer.MAX_VALUE;
83 } else {
84 // Load the settings
85 trkInt = Config.getPref().getBoolean("geoimage.trk.int", false);
86 trkTime = Config.getPref().getBoolean("geoimage.trk.int.time", false) ?
87 Config.getPref().getInt("geoimage.trk.int.time.val", 60) : Integer.MAX_VALUE;
88 trkDist = Config.getPref().getBoolean("geoimage.trk.int.dist", false) ?
89 Config.getPref().getInt("geoimage.trk.int.dist.val", 50) : Integer.MAX_VALUE;
90
91 trkTag = Config.getPref().getBoolean("geoimage.trk.tag", true);
92 trkTagTime = Config.getPref().getBoolean("geoimage.trk.tag.time", true) ?
93 Config.getPref().getInt("geoimage.trk.tag.time.val", 2) : Integer.MAX_VALUE;
94
95 segInt = Config.getPref().getBoolean("geoimage.seg.int", true);
96 segTime = Config.getPref().getBoolean("geoimage.seg.int.time", true) ?
97 Config.getPref().getInt("geoimage.seg.int.time.val", 60) : Integer.MAX_VALUE;
98 segDist = Config.getPref().getBoolean("geoimage.seg.int.dist", true) ?
99 Config.getPref().getInt("geoimage.seg.int.dist.val", 50) : Integer.MAX_VALUE;
100
101 segTag = Config.getPref().getBoolean("geoimage.seg.tag", true);
102 segTagTime = Config.getPref().getBoolean("geoimage.seg.tag.time", true) ?
103 Config.getPref().getInt("geoimage.seg.tag.time.val", 2) : Integer.MAX_VALUE;
104 }
105 boolean isFirst = true;
106
107 for (int t = 0; t < trks.size(); t++) {
108 List<List<WayPoint>> segs = trks.get(t);
109 for (int s = 0; s < segs.size(); s++) {
110 List<WayPoint> wps = segs.get(s);
111 for (int i = 0; i < wps.size(); i++) {
112 WayPoint curWp = wps.get(i);
113 Date parsedTime = curWp.setTimeFromAttribute();
114 // Interpolate timestamps in the segment, if one or more waypoints miss them
115 if (parsedTime == null) {
116 //check if any of the following waypoints has a timestamp...
117 if (i > 0 && wps.get(i - 1).time != 0) {
118 long prevWpTimeNoOffset = wps.get(i - 1).getTime().getTime();
119 double totalDist = 0;
120 List<Pair<Double, WayPoint>> nextWps = new ArrayList<>();
121 for (int j = i; j < wps.size(); j++) {
122 totalDist += wps.get(j - 1).getCoor().greatCircleDistance(wps.get(j).getCoor());
123 nextWps.add(new Pair<>(totalDist, wps.get(j)));
124 final Date nextTime = wps.get(j).setTimeFromAttribute();
125 if (nextTime != null) {
126 // ...if yes, interpolate everything in between
127 long timeDiff = nextTime.getTime() - prevWpTimeNoOffset;
128 for (Pair<Double, WayPoint> pair : nextWps) {
129 pair.b.setTime(new Date((long) (prevWpTimeNoOffset + (timeDiff * (pair.a / totalDist)))));
130 }
131 break;
132 }
133 }
134 parsedTime = curWp.setTimeFromAttribute();
135 if (parsedTime == null) {
136 break; //It's pointless to continue with this segment, because none of the following waypoints had a timestamp
137 }
138 } else {
139 // Timestamps on waypoints without preceding timestamps in the same segment can not be interpolated, so try next one
140 continue;
141 }
142 }
143
144 final long curWpTime = parsedTime.getTime() + offset;
145 boolean interpolate = true;
146 int tagTime = 0;
147 if (i == 0) {
148 if (s == 0) { //First segment of the track, so apply settings for tracks
149 if (!trkInt || isFirst || prevWp == null ||
150 Math.abs(curWpTime - prevWpTime) > TimeUnit.MINUTES.toMillis(trkTime) ||
151 prevWp.getCoor().greatCircleDistance(curWp.getCoor()) > trkDist) {
152 isFirst = false;
153 interpolate = false;
154 if (trkTag) {
155 tagTime = trkTagTime;
156 }
157 }
158 } else { //Apply settings for segments
159 if (!segInt || prevWp == null ||
160 Math.abs(curWpTime - prevWpTime) > TimeUnit.MINUTES.toMillis(segTime) ||
161 prevWp.getCoor().greatCircleDistance(curWp.getCoor()) > segDist) {
162 interpolate = false;
163 if (segTag) {
164 tagTime = segTagTime;
165 }
166 }
167 }
168 }
169 ret += matchPoints(images, prevWp, prevWpTime, curWp, curWpTime, offset, interpolate, tagTime, false);
170 prevWp = curWp;
171 prevWpTime = curWpTime;
172 }
173 }
174 }
175 if (trkTag) {
176 ret += matchPoints(images, prevWp, prevWpTime, prevWp, prevWpTime, offset, false, trkTagTime, true);
177 }
178 return ret;
179 }
180
181 private static Double getElevation(WayPoint wp) {
182 String value = wp.getString(GpxConstants.PT_ELE);
183 if (value != null && !value.isEmpty()) {
184 try {
185 return Double.valueOf(value);
186 } catch (NumberFormatException e) {
187 Logging.warn(e);
188 }
189 }
190 return null;
191 }
192
193 private static int matchPoints(List<? extends GpxImageEntry> images, WayPoint prevWp, long prevWpTime, WayPoint curWp, long curWpTime,
194 long offset, boolean interpolate, int tagTime, boolean isLast) {
195
196 int ret = 0;
197
198 // i is the index of the timewise last photo that has the same or earlier EXIF time
199 int i;
200 if (isLast) {
201 i = images.size() - 1;
202 } else {
203 i = getLastIndexOfListBefore(images, curWpTime);
204 }
205
206 // no photos match
207 if (i < 0)
208 return 0;
209
210 Double speed = null;
211 Double prevElevation = null;
212
213 if (prevWp != null && interpolate) {
214 double distance = prevWp.getCoor().greatCircleDistance(curWp.getCoor());
215 // This is in km/h, 3.6 * m/s
216 if (curWpTime > prevWpTime) {
217 speed = 3600 * distance / (curWpTime - prevWpTime);
218 }
219 prevElevation = getElevation(prevWp);
220 }
221
222 Double curElevation = getElevation(curWp);
223
224 if (!interpolate || isLast) {
225 final long half = Math.abs(curWpTime - prevWpTime) / 2;
226 while (i >= 0) {
227 final GpxImageEntry curImg = images.get(i);
228 final GpxImageEntry curTmp = curImg.getTmp();
229 final long time = curImg.getExifTime().getTime();
230 if ((!isLast && time > curWpTime) || time < prevWpTime) {
231 break;
232 }
233 long tagms = TimeUnit.MINUTES.toMillis(tagTime);
234 if (curTmp.getPos() == null &&
235 (Math.abs(time - curWpTime) <= tagms
236 || Math.abs(prevWpTime - time) <= tagms)) {
237 if (prevWp != null && time < curWpTime - half) {
238 curTmp.setPos(prevWp.getCoor());
239 } else {
240 curTmp.setPos(curWp.getCoor());
241 }
242 curTmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
243 curTmp.flagNewGpsData();
244 ret++;
245 }
246 i--;
247 }
248 } else if (prevWp != null) {
249 // This code gives a simple linear interpolation of the coordinates between current and
250 // previous track point assuming a constant speed in between
251 while (i >= 0) {
252 GpxImageEntry curImg = images.get(i);
253 GpxImageEntry curTmp = curImg.getTmp();
254 final long imgTime = curImg.getExifTime().getTime();
255 if (imgTime < prevWpTime) {
256 break;
257 }
258 if (curTmp.getPos() == null) {
259 // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless variable
260 double timeDiff = (double) (imgTime - prevWpTime) / Math.abs(curWpTime - prevWpTime);
261 curTmp.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
262 curTmp.setSpeed(speed);
263 if (curElevation != null && prevElevation != null) {
264 curTmp.setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
265 }
266 curTmp.setGpsTime(new Date(curImg.getExifTime().getTime() - offset));
267 curTmp.flagNewGpsData();
268
269 ret++;
270 }
271 i--;
272 }
273 }
274 return ret;
275 }
276
277 private static int getLastIndexOfListBefore(List<? extends GpxImageEntry> images, long searchedTime) {
278 int lstSize = images.size();
279
280 // No photos or the first photo taken is later than the search period
281 if (lstSize == 0 || searchedTime < images.get(0).getExifTime().getTime())
282 return -1;
283
284 // The search period is later than the last photo
285 if (searchedTime > images.get(lstSize - 1).getExifTime().getTime())
286 return lstSize-1;
287
288 // The searched index is somewhere in the middle, do a binary search from the beginning
289 int curIndex;
290 int startIndex = 0;
291 int endIndex = lstSize-1;
292 while (endIndex - startIndex > 1) {
293 curIndex = (endIndex + startIndex) / 2;
294 if (searchedTime > images.get(curIndex).getExifTime().getTime()) {
295 startIndex = curIndex;
296 } else {
297 endIndex = curIndex;
298 }
299 }
300 if (searchedTime < images.get(endIndex).getExifTime().getTime())
301 return startIndex;
302
303 // This final loop is to check if photos with the exact same EXIF time follows
304 while ((endIndex < (lstSize - 1)) && (images.get(endIndex).getExifTime().getTime()
305 == images.get(endIndex + 1).getExifTime().getTime())) {
306 endIndex++;
307 }
308 return endIndex;
309 }
310}
Note: See TracBrowser for help on using the repository browser.