source: osm/applications/editors/josm/plugins/NanoLog/src/nanolog/Correlator.java@ 35221

Last change on this file since 35221 was 35221, checked in by donvip, 5 years ago

see #josm16796 - update to JOSM 15502 - use IGpxTrack in GpxData (patch by Bjoeni)

File size: 11.5 KB
Line 
1package nanolog;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4
5import java.util.ArrayList;
6import java.util.Collections;
7import java.util.List;
8
9import javax.swing.JOptionPane;
10
11import org.openstreetmap.josm.data.coor.EastNorth;
12import org.openstreetmap.josm.data.coor.LatLon;
13import org.openstreetmap.josm.data.gpx.GpxData;
14import org.openstreetmap.josm.data.gpx.IGpxTrack;
15import org.openstreetmap.josm.data.gpx.IGpxTrackSegment;
16import org.openstreetmap.josm.data.gpx.WayPoint;
17import org.openstreetmap.josm.data.projection.ProjectionRegistry;
18import org.openstreetmap.josm.gui.MainApplication;
19import org.openstreetmap.josm.tools.Logging;
20import org.openstreetmap.josm.tools.UncheckedParseException;
21import org.openstreetmap.josm.tools.date.DateUtils;
22
23/**
24 * A class that establishes correlation between GPS trace and NanoLog. Mostly copied from
25 * {@link org.openstreetmap.josm.gui.layer.geoimage.CorrelateGpxWithImages}, thus licensed GPL.
26 *
27 * @author zverik
28 */
29public final class Correlator {
30
31 private Correlator() {
32 // Hide default constructor for utilities classes
33 }
34
35 /**
36 * Matches entries to GPX so most points are on the trace.
37 */
38 public static long crudeMatch(List<NanoLogEntry> entries, GpxData data) {
39 List<NanoLogEntry> sortedEntries = new ArrayList<>(entries);
40 Collections.sort(sortedEntries);
41 long firstExifDate = sortedEntries.get(0).getTime().getTime();
42 long firstGPXDate = -1;
43 outer:
44 for (IGpxTrack trk : data.tracks) {
45 for (IGpxTrackSegment segment : trk.getSegments()) {
46 for (WayPoint curWp : segment.getWayPoints()) {
47 String curDateWpStr = (String) curWp.attr.get("time");
48 if (curDateWpStr == null) {
49 continue;
50 }
51
52 try {
53 firstGPXDate = DateUtils.fromString(curDateWpStr).getTime();
54 break outer;
55 } catch (Exception e) {
56 Logging.warn(e);
57 }
58 }
59 }
60 }
61
62 // No GPX timestamps found, exit
63 if (firstGPXDate < 0) {
64 JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
65 tr("The selected GPX track does not contain timestamps. Please select another one."),
66 tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE);
67 return 0;
68 }
69
70 return firstExifDate - firstGPXDate;
71 }
72
73 public static void revertPos(List<NanoLogEntry> entries) {
74 for (NanoLogEntry entry : entries) {
75 entry.setPos(entry.getBasePos());
76 }
77 }
78
79 /**
80 * Offset is in 1/1000 of a second.
81 */
82 public static void correlate(List<NanoLogEntry> entries, GpxData data, long offset) {
83 List<NanoLogEntry> sortedEntries = new ArrayList<>(entries);
84 //int ret = 0;
85 Collections.sort(sortedEntries);
86 for (IGpxTrack track : data.tracks) {
87 for (IGpxTrackSegment segment : track.getSegments()) {
88 long prevWpTime = 0;
89 WayPoint prevWp = null;
90
91 for (WayPoint curWp : segment.getWayPoints()) {
92
93 String curWpTimeStr = (String) curWp.attr.get("time");
94 if (curWpTimeStr != null) {
95 try {
96 long curWpTime = DateUtils.fromString(curWpTimeStr).getTime() + offset;
97 /*ret +=*/ matchPoints(sortedEntries, prevWp, prevWpTime, curWp, curWpTime);
98
99 prevWp = curWp;
100 prevWpTime = curWpTime;
101
102 } catch (UncheckedParseException e) {
103 Logging.error("Error while parsing date \"" + curWpTimeStr + '"');
104 Logging.error(e);
105 prevWp = null;
106 prevWpTime = 0;
107 }
108 } else {
109 prevWp = null;
110 prevWpTime = 0;
111 }
112 }
113 }
114 }
115 }
116
117 private static int matchPoints(List<NanoLogEntry> entries, WayPoint prevWp, long prevWpTime,
118 WayPoint curWp, long curWpTime) {
119 // Time between the track point and the previous one, 5 sec if first point, i.e. photos take
120 // 5 sec before the first track point can be assumed to be take at the starting position
121 long interval = prevWpTime > 0 ? Math.abs(curWpTime - prevWpTime) : 5 * 1000;
122 int ret = 0;
123
124 // i is the index of the timewise last photo that has the same or earlier EXIF time
125 int i = getLastIndexOfListBefore(entries, curWpTime);
126
127 // no photos match
128 if (i < 0)
129 return 0;
130
131 Integer direction = null;
132 if (prevWp != null) {
133 direction = Long.valueOf(Math.round(180.0 / Math.PI * -prevWp.getCoor().bearing(curWp.getCoor()))).intValue();
134 }
135
136 // First trackpoint, then interval is set to five seconds, i.e. photos up to five seconds
137 // before the first point will be geotagged with the starting point
138 if (prevWpTime == 0 || curWpTime <= prevWpTime) {
139 while (true) {
140 if (i < 0) {
141 break;
142 }
143 final NanoLogEntry curImg = entries.get(i);
144 long time = curImg.getTime().getTime();
145 if (time > curWpTime || time < curWpTime - interval) {
146 break;
147 }
148 if (curImg.getPos() == null) {
149 curImg.setPos(curWp.getCoor());
150 curImg.setDirection(direction);
151 ret++;
152 }
153 i--;
154 }
155 return ret;
156 }
157
158 // This code gives a simple linear interpolation of the coordinates between current and
159 // previous track point assuming a constant speed in between
160 while (true) {
161 if (i < 0) {
162 break;
163 }
164 NanoLogEntry curImg = entries.get(i);
165 long imgTime = curImg.getTime().getTime();
166 if (imgTime < prevWpTime) {
167 break;
168 }
169
170 if (curImg.getPos() == null && prevWp != null) {
171 // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless variable
172 double timeDiff = (double) (imgTime - prevWpTime) / interval;
173 curImg.setPos(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
174 curImg.setDirection(direction);
175
176 ret++;
177 }
178 i--;
179 }
180 return ret;
181 }
182
183 private static int getLastIndexOfListBefore(List<NanoLogEntry> entries, long searchedTime) {
184 int lstSize = entries.size();
185
186 // No photos or the first photo taken is later than the search period
187 if (lstSize == 0 || searchedTime < entries.get(0).getTime().getTime())
188 return -1;
189
190 // The search period is later than the last photo
191 if (searchedTime > entries.get(lstSize - 1).getTime().getTime())
192 return lstSize-1;
193
194 // The searched index is somewhere in the middle, do a binary search from the beginning
195 int curIndex = 0;
196 int startIndex = 0;
197 int endIndex = lstSize-1;
198 while (endIndex - startIndex > 1) {
199 curIndex = (endIndex + startIndex) / 2;
200 if (searchedTime > entries.get(curIndex).getTime().getTime()) {
201 startIndex = curIndex;
202 } else {
203 endIndex = curIndex;
204 }
205 }
206 if (searchedTime < entries.get(endIndex).getTime().getTime())
207 return startIndex;
208
209 // This final loop is to check if photos with the exact same EXIF time follows
210 while ((endIndex < (lstSize-1)) && (entries.get(endIndex).getTime().getTime()
211 == entries.get(endIndex + 1).getTime().getTime())) {
212 endIndex++;
213 }
214 return endIndex;
215 }
216
217 /**
218 * Returns date of a potential point on GPX track (which can be between points).
219 */
220 public static long getGpxDate(GpxData data, LatLon pos) {
221 EastNorth en = ProjectionRegistry.getProjection().latlon2eastNorth(pos);
222 for (IGpxTrack track : data.tracks) {
223 for (IGpxTrackSegment segment : track.getSegments()) {
224 long prevWpTime = 0;
225 WayPoint prevWp = null;
226 for (WayPoint curWp : segment.getWayPoints()) {
227 String curWpTimeStr = (String) curWp.attr.get("time");
228 if (curWpTimeStr != null) {
229 try {
230 long curWpTime = DateUtils.fromString(curWpTimeStr).getTime();
231 if (prevWp != null) {
232 EastNorth c1 = ProjectionRegistry.getProjection().latlon2eastNorth(prevWp.getCoor());
233 EastNorth c2 = ProjectionRegistry.getProjection().latlon2eastNorth(curWp.getCoor());
234 if (!c1.equals(c2)) {
235 EastNorth middle = getSegmentAltitudeIntersection(c1, c2, en);
236 if (middle != null && en.distance(middle) < 1) {
237 // found our point, no further search is neccessary
238 double prop = c1.east() == c2.east()
239 ? (middle.north() - c1.north()) / (c2.north() - c1.north())
240 : (middle.east() - c1.east()) / (c2.east() - c1.east());
241 if (prop >= 0 && prop <= 1) {
242 return Math.round(prevWpTime + prop * (curWpTime - prevWpTime));
243 }
244 }
245 }
246 }
247
248 prevWp = curWp;
249 prevWpTime = curWpTime;
250 } catch (UncheckedParseException e) {
251 Logging.error("Error while parsing date \"" + curWpTimeStr + '"');
252 Logging.error(e);
253 prevWp = null;
254 prevWpTime = 0;
255 }
256 } else {
257 prevWp = null;
258 prevWpTime = 0;
259 }
260 }
261 }
262 }
263 return 0;
264 }
265
266 /**
267 * Returns the coordinate of intersection of segment p1-p2 and an altitude
268 * to it starting at point p. If the line defined with p1-p2 intersects
269 * its altitude out of p1-p2, null is returned.
270 * @return Intersection coordinate or null
271 **/
272 public static EastNorth getSegmentAltitudeIntersection(EastNorth p1, EastNorth p2, EastNorth point) {
273 double ldx = p2.getX() - p1.getX();
274 double ldy = p2.getY() - p1.getY();
275
276 if (ldx == 0 && ldy == 0) //segment zero length
277 return p1;
278
279 double pdx = point.getX() - p1.getX();
280 double pdy = point.getY() - p1.getY();
281
282 double offset = (pdx * ldx + pdy * ldy) / (ldx * ldx + ldy * ldy);
283
284 if (offset < -1e-8 || offset > 1+1e-8) return null;
285 if (offset < 1e-8)
286 return p1;
287 else if (offset > 1-1e-8)
288 return p2;
289 else
290 return new EastNorth(p1.getX() + ldx * offset, p1.getY() + ldy * offset);
291 }
292
293}
Note: See TracBrowser for help on using the repository browser.