- added support for loading addtional race information, e.g. time offset

- made time zone explicit wich is used to convert start time which comes without time zone information --> to be added from additional race information
This commit is contained in:
Jens Rommel committed 2012-12-13 14:57:17 +01:00
1 parent 0fb43c35a5
commit 6b275d49ba
8 files changed
+327 -99

No files matched your search

@@ -0,0 +1,24 @@
package com.sap.sailing.domain.swisstimingreplayadapter;
public class SwissTimingRaceConfig {
public final String latitude;
public final String longitude;
public final String country_code;
public final String gmt_offset;
public final String location;
public final String event_name;
public final String race_start_ts;
public SwissTimingRaceConfig(String latitude, String longitude, String country_code, String gmt_offset,
String location, String event_name, String race_start_ts) {
this.latitude = latitude;
this.longitude = longitude;
this.country_code = country_code;
this.gmt_offset = gmt_offset;
this.location = location;
this.event_name = event_name;
this.race_start_ts = race_start_ts;
}
}
@@ -1,44 +1,93 @@
package com.sap.sailing.domain.swisstimingreplayadapter;
/**
* Implementations are passed to {@link SwissTimingReplayService#loadRaceData(String, SwissTimingReplayListener)} for getting notified of about content parsed from SwissTiming's binary replay file format.
*
* The protocol is
* 1 x keyFrameIndex
* n x keyFrameIndexPosition
* 1 x eot
* n x [
* 1 x referenceTimestamp
* 1 x referenceLocation
* 1 x rsc_cid
* 1 x competitorsCount
* competitorsCount x competitor
* n x mark
* 1 x trackersCount
* trackersCount x trackers
* 1 x rankingsCount
* rankingsCount x ranking [
* n x rankingMark
* ]
* 1 x eot
*
* ]
*
*
* @author D047974 - Jens Rommel
*
*/
public interface SwissTimingReplayListener {
void referenceTimestamp(long referenceTimestamp);
void referenceLocation(int latitude, int longitude);
void keyFrameIndex(int keyFrameIndex);
void keyFrameIndexPosition(int keyFrameIndexPosition);
void referenceTimestamp(long referenceTimestamp);
void referenceLocation(int latitude, int longitude);
void rsc_cid(String text);
void mark(MarkType markType, String identifier, byte index, String id1, String id2, short windSpeed, short windDirection);
void frameMetaData(byte cid, int raceTime, int startTime, int estimatedStartTime, RaceStatus raceStatus, short distanceToNextMark,
Weather weather, short humidity, short temperature, String messageText, byte cFlag, byte rFlag,
byte duration, short nm);
void competitorsCount(short competitorsCount);
void competitor(int hashValue, String nation, String sailNumber, String name, CompetitorStatus competitorStatus, BoatType boatType,
short cRank_Bracket, short cnPoints_x10_Bracket, short ctPoints_x10_Winner);
void frame(byte cid, int raceTime, int startTime, int estimatedStartTime, RaceStatus raceStatus, short distanceToNextMark,
Weather weather, short humidity, short temperature, String messageText, byte cFlag, byte rFlag,
byte duration, short nm);
void mark(MarkType markType, String identifier, byte index, String id1, String id2, short windSpeed_Knots, short windDirection);
void trackersCount(short trackersCount);
void trackers(int hashValue, int latitude, int longitude, short cog, short sog_Knots_x10, short average_sog, short vmg_Knots_x10,
CompetitorStatus competitorStatus, short rank, short distanceToLeader, short distanceToNextMark, short nextMark, short pRank, short ptPoints, short pnPoints);
void rankingsCount(short entriesCount);
void ranking(int hashValue, short rank, short rankIndex, short racePoints, CompetitorStatus competitorStatus, short finishRank,
short finishRankIndex, int gap, int raceTime);
void rankingsCount(short entriesCount);
void rankingMark(short marksRank, short marksRankIndex, int marksGap, int marksRaceTime);
void trackersCount(short trackersCount);
void trackers(int hashValue, int latitude, int longitude, short cog, short sog, short average_sog, short vmg,
CompetitorStatus competitorStatus, short rank, short dtl, short dtnm, short nm, short pRank, short ptPoints, short pnPoints);
void illegalState(String message);
void unknownMessageIdentificationCode(byte messageIdentificationCode);
void eot();
/**
* Called when an unknown message identification codes has been encountered.
* See {@link MessageIdentificationCodes} for supported codes.
* @param messageIdentificationCode
*/
void unknownMessageIdentificationCode(byte messageIdentificationCode);
/**
* Called when STX was expected but startByte has been encountered.
* @param startByte
*/
void unexpectedStartByte(byte startByte);
/**
* Called when the actual payload was smaller than announced in the message header.
* @param remainingPayloadSize
*/
void payloadMismatch(short remainingPayloadSize);
/**
* Called when the end of a message was expected but a non ETX/EOT byte has been encountered.
* @param endByte
*/
void prematureEndOfData(byte endByte);
}
@@ -7,89 +7,140 @@ import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
public class SwissTimingReplayService {
static final String RACE_CONFIG_URL_TEMPLATE = "http://live.ota.st-sportservice.com/configuration?_race={0}&effective=1&additional=config";
private static final int STX = 0x02;
private static final int ETX = 0x03;
private static final int EOT = 0x04;
private static final Charset CHARSET = Charset.forName("ISO-8859-1");
public static final DateFormat SWISSTIMING_DATEFORMAT = new SimpleDateFormat("dd.MM.yyyy HH:mm");
public static final String SWISSTIMING_DATEFORMAT_PATTERN = "dd.MM.yyyy HH:mm";
public static final TimeZone DEFAULT_TIMEZONE = TimeZone.getTimeZone("GMT");
public static List<SwissTimingReplayRace> listReplayRaces(String swissTimingUrlText) {
URL url;
URL raceListUrl;
try {
url = new URL(swissTimingUrlText);
return parseJSONObject(url.openStream(), swissTimingUrlText);
} catch (Exception e) { //MalformedURLException | IOException | ParseException | org.json.simple.parser.ParseException)
raceListUrl = new URL(swissTimingUrlText);
List<SwissTimingReplayRace> races = parseJSONObject(raceListUrl.openStream(), swissTimingUrlText);
// loadRaceConfigs(races);
return races;
} catch (Exception e) { // MalformedURLException | IOException | ParseException |
// org.json.simple.parser.ParseException)
throw new RuntimeException(e);
}
}
static List<SwissTimingReplayRace> parseJSONObject(InputStream inputStream, String swissTimingUrlText) throws IOException, ParseException, org.json.simple.parser.ParseException {
public static SwissTimingRaceConfig loadRaceConfig(String raceId) {
try {
URL configUrl = new URL(MessageFormat.format(RACE_CONFIG_URL_TEMPLATE, raceId));
InputStream configDataStream = configUrl.openStream();
return loadRaceConfig(configDataStream);
} catch (Exception e) { // MalformedURLException | ParseException | org.json.simple.parser.ParseException)
throw new RuntimeException(e);
}
}
static SwissTimingRaceConfig loadRaceConfig(InputStream configDataStream) throws IOException,
org.json.simple.parser.ParseException {
JSONObject jsonRaceConfig = (JSONObject) new JSONParser().parse(new InputStreamReader(configDataStream));
JSONObject jsonConfigEntry = (JSONObject) jsonRaceConfig.get("config");
String latitude = (String) jsonRaceConfig.get("latitude");
String longitude = (String) jsonRaceConfig.get("longitude");
String country_code = (String) jsonRaceConfig.get("country_code");
String gmt_offset = (String) jsonRaceConfig.get("gmt_offset");
String location = (String) jsonRaceConfig.get("location");
String event_name = (String) (jsonConfigEntry != null ? jsonConfigEntry.get("event_name") : null);
String race_start_ts = (String) (jsonConfigEntry != null ? jsonConfigEntry.get("race_start_ts") : null);
SwissTimingRaceConfig raceConfig = new SwissTimingRaceConfig(latitude, longitude, country_code, gmt_offset,
location, event_name, race_start_ts);
return raceConfig;
}
/**
*
* @param inputStream
* The stream to read the JSON content from
* @param swissTimingUrlText
* The URL where the stream has been taken from. Only used as information for later reference.
* @return
* @throws IOException
* @throws ParseException
* @throws org.json.simple.parser.ParseException
*/
static List<SwissTimingReplayRace> parseJSONObject(InputStream inputStream, String swissTimingUrlText)
throws IOException, ParseException, org.json.simple.parser.ParseException {
JSONArray json = (JSONArray) new JSONParser().parse(new InputStreamReader(inputStream));
List<SwissTimingReplayRace> result = new ArrayList<SwissTimingReplayRace>();
DateFormat startTimeFormat = getStartTimeFormat();
for (Object raceEntry : json) {
JSONObject jsonRaceEntry = (JSONObject) raceEntry;
String startTimeText = (String) jsonRaceEntry.get("start");
Date startTime = startTimeText == null ? null : SWISSTIMING_DATEFORMAT.parse(startTimeText);
SwissTimingReplayRace replayRace = new SwissTimingReplayRace(
swissTimingUrlText,
(String) jsonRaceEntry.get("flight_number"),
(String) jsonRaceEntry.get("race_id"),
(String) jsonRaceEntry.get("rsc"),
(String) jsonRaceEntry.get("name"),
(String) jsonRaceEntry.get("class"),
startTime ,
Date startTime = startTimeText == null ? null : startTimeFormat.parse(startTimeText);
SwissTimingReplayRace replayRace = new SwissTimingReplayRace(swissTimingUrlText,
(String) jsonRaceEntry.get("flight_number"), (String) jsonRaceEntry.get("race_id"),
(String) jsonRaceEntry.get("rsc"), (String) jsonRaceEntry.get("name"),
(String) jsonRaceEntry.get("class"), startTime,
(String) jsonRaceEntry.get("link"));
result.add(replayRace);
}
return result;
}
public static DateFormat getStartTimeFormat() {
DateFormat startTimeFormat = new SimpleDateFormat(SWISSTIMING_DATEFORMAT_PATTERN);
startTimeFormat.setTimeZone(DEFAULT_TIMEZONE);
return startTimeFormat;
}
public static void loadRaceData(String link, SwissTimingReplayListener replayListener) {
URL raceDataUrl;
try {
raceDataUrl = new URL("http://" + link);
InputStream urlInputStream = raceDataUrl.openStream();
readData(urlInputStream, replayListener);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static void readData(InputStream urlInputStream, SwissTimingReplayListener replayListener) throws IOException {
DataInputStream data = new DataInputStream(urlInputStream);
byte[] startByteBuffer = new byte[1];
byte[] startByteBuffer = new byte[1];
int readSuccess = data.read(startByteBuffer);
while (readSuccess != -1) {
byte startByte = startByteBuffer[0];
if (startByte != STX) {
replayListener.illegalState("Expected STX (0x02), found: " + startByte);
replayListener.unexpectedStartByte(startByte);
}
byte messageIdentificationCode = data.readByte();
short messageLength = data.readShort();
if (messageLength > 0) {
short payloadSize = (short) (messageLength - 5); //payload is message minus STX, MIC, message length and ETX/EOT --> = 5 byte
short payloadSize = (short) (messageLength - 5); // payload is message minus STX, MIC, message length
// and ETX/EOT --> = 5 byte
switch (messageIdentificationCode) {
case MessageIdentificationCodes.MIC_00_Reference_Timestamp:
while (payloadSize > 0) {
@@ -113,7 +164,7 @@ public class SwissTimingReplayService {
replayListener.keyFrameIndex(keyFrameIndex);
payloadSize -= 4;
while (payloadSize > 0) {
int keyFrameIndexPosition = data.readInt();
@@ -140,7 +191,8 @@ public class SwissTimingReplayService {
short windSpeed = data.readShort();
short windDirection = data.readShort();
replayListener.mark(new MarkType(markType), identifier, index, id1, id2, windSpeed, windDirection);
replayListener.mark(new MarkType(markType), identifier, index, id1, id2, windSpeed,
windDirection);
payloadSize -= 1 + 8 + 1 + 8 + 8 + 2 + 2;
}
break;
@@ -160,8 +212,10 @@ public class SwissTimingReplayService {
short cRank_Bracket = data.readShort();
short cnPoints_x10_Bracket = data.readShort();
short ctPoints_x10_Winner = data.readShort();
replayListener.competitor(hashValue, nation, sailNumber, name, CompetitorStatus.byCode(competitorStatus), BoatType.byCode(boatType), cRank_Bracket, cnPoints_x10_Bracket, ctPoints_x10_Winner);
replayListener.competitor(hashValue, nation, sailNumber, name,
CompetitorStatus.byCode(competitorStatus), BoatType.byCode(boatType), cRank_Bracket,
cnPoints_x10_Bracket, ctPoints_x10_Winner);
payloadSize -= 4 + 3 + 1 + sailNumberLength + 1 + nameLength + 1 + 1 + 2 + 2 + 2;
}
break;
@@ -182,9 +236,10 @@ public class SwissTimingReplayService {
byte rFlag = data.readByte();
byte duration = data.readByte();
short nm = data.readShort();
replayListener.frame(cid, raceTime, startTime, estimatedStartTime, RaceStatus.byCode(raceStatus), distanceToNextMark, Weather.byCode(weather), humidity, temperature,
messageText, cFlag, rFlag, duration, nm);
replayListener.frameMetaData(cid, raceTime, startTime, estimatedStartTime,
RaceStatus.byCode(raceStatus), distanceToNextMark, Weather.byCode(weather), humidity,
temperature, messageText, cFlag, rFlag, duration, nm);
payloadSize -= 1 + 3 + 3 + 3 + 1 + 2 + 1 + 2 + 2 + 1 + messageTextLength + 1 + 1 + 1 + 2;
}
break;
@@ -203,7 +258,8 @@ public class SwissTimingReplayService {
int gap = read3ByteInt(data);
int raceTime = read3ByteInt(data);
short marksCount = data.readShort();
replayListener.ranking(hashValue, rank, rankIndex, racePoints, CompetitorStatus.byCode(competitorStatus), finishRank, finishRankIndex, gap, raceTime);
replayListener.ranking(hashValue, rank, rankIndex, racePoints,
CompetitorStatus.byCode(competitorStatus), finishRank, finishRankIndex, gap, raceTime);
payloadSize -= 4 + 2 + 2 + 2 + 1 + 2 + 2 + 3 + 3 + 2;
for (int i = 0; i < marksCount; i++) {
short marksRank = data.readShort();
@@ -213,7 +269,7 @@ public class SwissTimingReplayService {
replayListener.rankingMark(marksRank, marksRankIndex, marksGap, marksRaceTime);
payloadSize -= 2 + 2 + 3 + 3;
}
}
break;
case MessageIdentificationCodes.MIC_124_Trackers:
@@ -236,8 +292,10 @@ public class SwissTimingReplayService {
short pRank = data.readShort();
short ptPoints = data.readShort();
short pnPoints = data.readShort();
replayListener.trackers(hashValue, latitude, longitude, cog, sog, average_sog, vmg, CompetitorStatus.byCode(competitorStatus), rank, dtl, dtnm, nm, pRank, ptPoints, pnPoints);
replayListener.trackers(hashValue, latitude, longitude, cog, sog, average_sog, vmg,
CompetitorStatus.byCode(competitorStatus), rank, dtl, dtnm, nm, pRank, ptPoints,
pnPoints);
payloadSize -= 4 + 4 + 4 + 2 + 2 + 2 + 2 + 1 + 2 + 2 + 2 + 2 + 2 + 2 + 2;
}
@@ -248,35 +306,34 @@ public class SwissTimingReplayService {
}
if (payloadSize != 0) {
replayListener.illegalState("Payload mismatch.");
replayListener.payloadMismatch(payloadSize);
}
}
byte endByte = data.readByte();
if (endByte == EOT) {
replayListener.eot();
} else if (endByte == ETX) {
//observe whether ETX is interesting for anybody.
} else {
replayListener.illegalState("Premature end of data section.");
}
readSuccess = data.read(startByteBuffer);
byte endByte = data.readByte();
if (endByte == EOT) {
replayListener.eot();
} else if (endByte == ETX) {
// observe whether ETX is interesting for anybody.
} else {
replayListener.prematureEndOfData(endByte);
}
readSuccess = data.read(startByteBuffer);
}
}
private static int read3ByteInt(DataInputStream data) throws IOException {
byte highByte = data.readByte();
byte midByte = data.readByte();
byte highByte = data.readByte();
byte midByte = data.readByte();
byte lowByte = data.readByte();
//turn signed to unsigned, then shift left
// turn signed to unsigned, then shift left
int highInt = (highByte & 0xFF) << 16;
int midInt = (midByte & 0xFF) << 8;
int lowInt = lowByte & 0xFF;
return highInt | midInt | lowInt;
}