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