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