001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io.rtklib;
003
004import java.io.BufferedReader;
005import java.io.IOException;
006import java.io.InputStream;
007import java.io.InputStreamReader;
008import java.nio.charset.StandardCharsets;
009import java.text.ParseException;
010import java.text.SimpleDateFormat;
011import java.util.ArrayList;
012import java.util.Collection;
013import java.util.Collections;
014import java.util.Locale;
015import java.util.Objects;
016
017import org.openstreetmap.josm.data.coor.LatLon;
018import org.openstreetmap.josm.data.gpx.GpxConstants;
019import org.openstreetmap.josm.data.gpx.GpxData;
020import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
021import org.openstreetmap.josm.data.gpx.WayPoint;
022import org.openstreetmap.josm.io.IGpxReader;
023import org.openstreetmap.josm.tools.Logging;
024import org.openstreetmap.josm.tools.date.DateUtils;
025import org.xml.sax.SAXException;
026
027/**
028 * Reads a RTKLib Positioning Solution file.
029 * <p>
030 * See <a href="https://github.com/tomojitakasu/RTKLIB/blob/rtklib_2.4.3/doc/manual_2.4.2.pdf">RTKLIB Manual</a>.
031 * @since 15247
032 */
033public class RtkLibPosReader implements IGpxReader {
034
035    private static final int IDX_DATE = 0;
036    private static final int IDX_TIME = 1;
037    private static final int IDX_LAT = 2;
038    private static final int IDX_LON = 3;
039    private static final int IDX_HEIGHT = 4;
040    private static final int IDX_Q = 5;
041    private static final int IDX_NS = 6;
042    private static final int IDX_SDN = 7;
043    private static final int IDX_SDE = 8;
044    private static final int IDX_SDU = 9;
045    private static final int IDX_SDNE = 10;
046    private static final int IDX_SDEU = 11;
047    private static final int IDX_SDUN = 12;
048    private static final int IDX_AGE = 13;
049    private static final int IDX_RATIO = 14;
050
051    private final SimpleDateFormat dateTimeFmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss.SSS", Locale.ENGLISH); // 2019/06/08 08:23:15.000
052
053    private final InputStream source;
054    private GpxData data;
055    private int success; // number of successfully parsed lines
056
057    /**
058     * Constructs a new {@code RtkLibPosReader}
059     * @param source RTKLib .pos file input stream
060     * @throws IOException if an I/O error occurs
061     */
062    public RtkLibPosReader(InputStream source) throws IOException {
063        this.source = Objects.requireNonNull(source);
064        dateTimeFmt.setTimeZone(DateUtils.UTC);
065    }
066
067    @Override
068    public boolean parse(boolean tryToFinish) throws SAXException, IOException {
069        data = new GpxData();
070        Collection<Collection<WayPoint>> currentTrack = new ArrayList<>();
071        Collection<WayPoint> waypoints = new ArrayList<>();
072        try (BufferedReader rd = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8))) {
073            String line = null;
074            do {
075                line = rd.readLine();
076                if (line != null) {
077                    if (line.startsWith("% ref pos   :")) {
078                        // TODO add marker
079                    } else if (!line.startsWith("%")) {
080                        try {
081                            String[] fields = line.split("[ ]+");
082                            WayPoint currentwp = new WayPoint(new LatLon(
083                                    Double.parseDouble(fields[IDX_LAT]),
084                                    Double.parseDouble(fields[IDX_LON])));
085                            currentwp.put(GpxConstants.PT_ELE, fields[IDX_HEIGHT]);
086                            currentwp.setTime(dateTimeFmt.parse(fields[IDX_DATE]+" "+fields[IDX_TIME]));
087                            currentwp.put(GpxConstants.RTKLIB_Q, Integer.parseInt(fields[IDX_Q]));
088                            currentwp.put(GpxConstants.PT_SAT, fields[IDX_NS]);
089                            currentwp.put(GpxConstants.RTKLIB_SDN, fields[IDX_SDN]);
090                            currentwp.put(GpxConstants.RTKLIB_SDE, fields[IDX_SDE]);
091                            currentwp.put(GpxConstants.RTKLIB_SDE, fields[IDX_SDU]);
092                            currentwp.put(GpxConstants.RTKLIB_SDNE, fields[IDX_SDNE]);
093                            currentwp.put(GpxConstants.RTKLIB_SDEU, fields[IDX_SDEU]);
094                            currentwp.put(GpxConstants.RTKLIB_SDUN, fields[IDX_SDUN]);
095                            currentwp.put(GpxConstants.RTKLIB_AGE, fields[IDX_AGE]);
096                            currentwp.put(GpxConstants.RTKLIB_RATIO, fields[IDX_RATIO]);
097                            double sdn = Double.parseDouble(fields[IDX_SDN]);
098                            double sde = Double.parseDouble(fields[IDX_SDN]);
099                            currentwp.put(GpxConstants.PT_HDOP, (float) Math.sqrt(sdn*sdn + sde*sde));
100                            waypoints.add(currentwp);
101                            success++;
102                        } catch (ParseException | IllegalArgumentException e) {
103                            Logging.error(e);
104                        }
105                    }
106                }
107            } while (line != null);
108        }
109        currentTrack.add(waypoints);
110        data.tracks.add(new ImmutableGpxTrack(currentTrack, Collections.<String, Object>emptyMap()));
111        return true;
112    }
113
114    @Override
115    public GpxData getGpxData() {
116        return data;
117    }
118
119    /**
120     * Returns the number of coordinates that have been successfuly read.
121     * @return the number of coordinates that have been successfuly read
122     */
123    public int getNumberOfCoordinates() {
124        return success;
125    }
126}