mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-23 12:33:53 +00:00
Merge branch 'master' into ubilabs--tracking_app--develop
This commit is contained in:
commit
e54b337eec
42 files changed
+830
-579
No files matched your search
+17
-11
@@ -12,6 +12,9 @@ RETVAL=0
|
||||
|
||||
JAVA_START_INSTANCES="server"
|
||||
GIT_REPOSITORY=/home/sailing/code
|
||||
APACHE_CONFIG_DIR=/etc/httpd/conf.d
|
||||
rm /var/log/sailing.out
|
||||
rm /var/log/sailing.err
|
||||
|
||||
start_tmux() {
|
||||
su - sailing -c "/home/sailing/bin/tmuxConsole.sh unattended"
|
||||
@@ -29,9 +32,14 @@ start_servers() {
|
||||
(
|
||||
# execute env.sh in a subshell which now has the same set of variables that the server will see
|
||||
source /home/sailing/servers/$conf/env.sh
|
||||
|
||||
# Append Apache macro invocation for /internal-server-status based on mod_status and INSTANCE_DNS to /etc/httpd/conf.d/001-events.conf
|
||||
echo "Appending macro usage for /internal-server-status URL for mod_status based Apache monitoring to $APACHE_CONFIG_DIR/001-events.conf"
|
||||
echo "Use Status $INSTANCE_DNS internal-server-status" >>$APACHE_CONFIG_DIR/001-events.conf
|
||||
|
||||
launch_httpd
|
||||
echo "Starting Java server $conf"
|
||||
su - sailing -c "cd /home/sailing/servers/$conf && ./start" 2>/var/log/sailing.err >/var/log/sailing.out
|
||||
su - sailing -c "cd /home/sailing/servers/$conf && ./start" 2>>/var/log/sailing.err >>/var/log/sailing.out
|
||||
# terminating the subshell will forget all the variable assignments brought in by env.sh, clearing for the next server instance
|
||||
)
|
||||
RETVAL=$?
|
||||
@@ -50,16 +58,14 @@ stop_servers() {
|
||||
|
||||
# launch_httpd(server_name, port, event_id)
|
||||
launch_httpd() {
|
||||
if [ "$REPLICATE_ON_START" != "" ]; then
|
||||
echo "REPLICATE_ON_START is $REPLICATE_ON_START. Will try to launch httpd so this replica can work with an ELB easily."
|
||||
if [ -x /etc/init.d/httpd ]; then
|
||||
server_name="`echo $SERVER_NAME | tr [A-Z] [a-z]`"
|
||||
echo "Use Event $server_name.sapsailing.com \"$EVENT_ID\" 127.0.0.1 $SERVER_PORT" >>/etc/httpd/conf.d/001-events.conf
|
||||
echo "Launching httpd to map ${server_name}.sapsailing.com to event with ID $EVENT_ID with server running on local port $SERVER_PORT..."
|
||||
service httpd start
|
||||
else
|
||||
echo Can't launch httpd; start script doesn't seem to be installed at /etc/init.d/httpd
|
||||
fi
|
||||
echo "Will try to launch httpd so this replica can work with an ELB easily."
|
||||
if [ -x /etc/init.d/httpd ]; then
|
||||
server_name="`echo $SERVER_NAME | tr [A-Z] [a-z]`"
|
||||
echo "Use Event $server_name.sapsailing.com \"$EVENT_ID\" 127.0.0.1 $SERVER_PORT" >>$APACHE_CONFIG_DIR/001-events.conf
|
||||
echo "Launching httpd to map ${server_name}.sapsailing.com to event with ID $EVENT_ID with server running on local port $SERVER_PORT..."
|
||||
service httpd start
|
||||
else
|
||||
echo Can't launch httpd; start script doesn't seem to be installed at /etc/init.d/httpd
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
+11
-14
@@ -391,20 +391,17 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener {
|
||||
}
|
||||
|
||||
private void onCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent event) {
|
||||
if (trackedRace == null)
|
||||
return;
|
||||
|
||||
CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog()).analyze();
|
||||
List<Util.Pair<ControlPoint, PassingInstruction>> update = new ArrayList<>();
|
||||
|
||||
for (Waypoint waypoint : base.getWaypoints()) {
|
||||
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
|
||||
}
|
||||
|
||||
try {
|
||||
trackedRace.getRace().getCourse().update(update, params.getDomainFactory());
|
||||
} catch (PatchFailedException e) {
|
||||
logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName());
|
||||
if (trackedRace != null) {
|
||||
CourseBase base = new LastPublishedCourseDesignFinder(params.getRaceLog()).analyze();
|
||||
List<Util.Pair<ControlPoint, PassingInstruction>> update = new ArrayList<>();
|
||||
for (Waypoint waypoint : base.getWaypoints()) {
|
||||
update.add(new Util.Pair<>(waypoint.getControlPoint(), waypoint.getPassingInstructions()));
|
||||
}
|
||||
try {
|
||||
trackedRace.getRace().getCourse().update(update, params.getDomainFactory());
|
||||
} catch (PatchFailedException e) {
|
||||
logger.log(Level.WARNING, "Could not update course for race " + trackedRace.getRace().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ public class WeakRaceLogChangedVisitor extends AbstractRaceLogChangedVisitor {
|
||||
while (true) {
|
||||
try {
|
||||
Reference<?> ref = queue.remove();
|
||||
WeakRaceLogChangedVisitor visitor = referenceToVisitor.get(ref);
|
||||
WeakRaceLogChangedVisitor visitor = referenceToVisitor.remove(ref);
|
||||
visitor.removeListener();
|
||||
} catch (InterruptedException e) {
|
||||
logger.log(Level.SEVERE, "Error trying to clean weak reference", e);
|
||||
|
||||
+26
-16
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
@@ -18,7 +19,7 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.IsInFinishingPhase
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.IsIndividualRecallDisplayedAnalyzer;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogChangedVisitor;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.WeakRaceLogChangedVisitor;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEvent;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.RaceStateEventScheduler;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
|
||||
@@ -29,6 +30,7 @@ import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProce
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedureChangedListener;
|
||||
import com.sap.sailing.domain.base.configuration.RacingProcedureConfiguration;
|
||||
import com.sap.sailing.domain.common.racelog.Flags;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
/**
|
||||
@@ -40,8 +42,11 @@ import com.sap.sse.common.TimePoint;
|
||||
*/
|
||||
public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener implements RacingProcedure,
|
||||
RaceLogChangedListener {
|
||||
|
||||
private final static long individualRecallRemovalTimeout = 4 * 60 * 1000; // minutes * seconds * milliseconds
|
||||
private static final Logger logger = Logger.getLogger(BaseRacingProcedure.class.getName());
|
||||
/**
|
||||
* The time since race start after which the X-ray flag has to go down
|
||||
*/
|
||||
private final static Duration individualRecallRemovalTimeout = Duration.ONE_MINUTE.times(4);
|
||||
|
||||
private RaceStateEventScheduler scheduler;
|
||||
|
||||
@@ -57,9 +62,11 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
private final FinishingTimeFinder finishingTimeFinder;
|
||||
private final FinishedTimeFinder finishedTimeFinder;
|
||||
private final RaceLogEventVisitor raceLogListener;
|
||||
private final StartTimeFinder startTimeFinder;
|
||||
|
||||
private boolean cachedIsIndividualRecallDisplayed;
|
||||
|
||||
|
||||
/**
|
||||
* When calling me, call {@link BaseRacingProcedure#update()} afterwards!
|
||||
*
|
||||
@@ -72,24 +79,18 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
if (configuration == null) {
|
||||
throw new IllegalArgumentException("configuration must not be null");
|
||||
}
|
||||
|
||||
this.raceLog = raceLog;
|
||||
this.author = author;
|
||||
this.factory = factory;
|
||||
this.configuration = configuration;
|
||||
|
||||
this.changedListeners = createChangedListenerContainer();
|
||||
this.isRecallDisplayedAnalyzer = new IsIndividualRecallDisplayedAnalyzer(raceLog);
|
||||
this.recallDisplayedFinder = new IndividualRecallDisplayedFinder(raceLog);
|
||||
this.recallRemovedFinder = new IndividualRecallRemovedFinder(raceLog);
|
||||
this.finishingTimeFinder = new FinishingTimeFinder(raceLog);
|
||||
this.finishedTimeFinder = new FinishedTimeFinder(raceLog);
|
||||
// Use the following in order to initialize a start time finder once it's needed for start time-dependent individual recall timeouts
|
||||
// this.startTimeFinder = new StartTimeFinder(raceLogResolver, raceLog);
|
||||
|
||||
this.raceLogListener = new RaceLogChangedVisitor(this);
|
||||
this.raceLog.addListener(raceLogListener);
|
||||
|
||||
this.startTimeFinder = new StartTimeFinder(raceLogResolver, raceLog);
|
||||
this.raceLogListener = new WeakRaceLogChangedVisitor(this.raceLog, this);
|
||||
this.cachedIsIndividualRecallDisplayed = false;
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
break;
|
||||
case RUNNING:
|
||||
if (isIndividualRecallDisplayed()) {
|
||||
rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze());
|
||||
rescheduleIndividualRecallTimeout(getIndividualRecallRemovalTime());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -172,7 +173,16 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
}
|
||||
}
|
||||
if (displayed != null) {
|
||||
return displayed.plus(individualRecallRemovalTimeout);
|
||||
final TimePoint raceStartTime = startTimeFinder.analyze().getStartTime();
|
||||
final TimePoint referenceTimePoint;
|
||||
if (raceStartTime != null) {
|
||||
referenceTimePoint = raceStartTime;
|
||||
} else {
|
||||
logger.warning("An individual recall was set but we don't have a start time set for the race; using individual recall set time "+
|
||||
displayed+" + timeout ("+individualRecallRemovalTimeout+") for implicit termination of individual recall");
|
||||
referenceTimePoint = displayed;
|
||||
}
|
||||
return referenceTimePoint.plus(individualRecallRemovalTimeout);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -218,7 +228,7 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
cachedIsIndividualRecallDisplayed = isRecallDisplayed;
|
||||
if (cachedIsIndividualRecallDisplayed) {
|
||||
changedListeners.onIndividualRecallDisplayed(this);
|
||||
rescheduleIndividualRecallTimeout(recallDisplayedFinder.analyze());
|
||||
rescheduleIndividualRecallTimeout(getIndividualRecallRemovalTime());
|
||||
} else {
|
||||
changedListeners.onIndividualRecallRemoved(this);
|
||||
unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT);
|
||||
@@ -231,9 +241,9 @@ public abstract class BaseRacingProcedure extends BaseRaceStateChangedListener i
|
||||
changedListeners.onActiveFlagsChanged(this);
|
||||
}
|
||||
|
||||
private void rescheduleIndividualRecallTimeout(TimePoint displayTime) {
|
||||
private void rescheduleIndividualRecallTimeout(TimePoint removalTime) {
|
||||
unscheduleStateEvent(RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT);
|
||||
scheduleStateEvents(new RaceStateEventImpl(displayTime.plus(individualRecallRemovalTimeout), RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT));
|
||||
scheduleStateEvents(new RaceStateEventImpl(removalTime, RaceStateEvents.INDIVIDUAL_RECALL_TIMEOUT));
|
||||
}
|
||||
|
||||
protected void scheduleStateEvents(RaceStateEvent stateEvent) {
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ public class CourseDataImpl extends NamedImpl implements CourseBase {
|
||||
|
||||
@Override
|
||||
public List<Leg> getLegs() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -72,7 +71,6 @@ public class CourseDataImpl extends NamedImpl implements CourseBase {
|
||||
|
||||
@Override
|
||||
public Leg getFirstLeg() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -477,11 +477,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTrackedRace(TrackedRace trackedRace) {
|
||||
}
|
||||
@@ -536,6 +531,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
@Override
|
||||
public void unlockTrackedRacesAfterWrite() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRaceListener(RaceListener listener) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ public interface Leaderboard extends LeaderboardBase {
|
||||
*/
|
||||
public interface Entry {
|
||||
int getTrackedRank();
|
||||
Double getNetPoints() throws NoWindException;
|
||||
Double getNetPointsUncorrected() throws NoWindException;
|
||||
Double getTotalPoints() throws NoWindException;
|
||||
Double getNetPoints();
|
||||
Double getNetPointsUncorrected();
|
||||
Double getTotalPoints();
|
||||
MaxPointsReason getMaxPointsReason();
|
||||
boolean isDiscarded() throws NoWindException;
|
||||
boolean isDiscarded();
|
||||
/**
|
||||
* Tells if the net points have been corrected by a {@link ScoreCorrection}
|
||||
*/
|
||||
|
||||
+16
-22
@@ -219,7 +219,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
return fleet;
|
||||
}
|
||||
@Override
|
||||
public Double getNetPointsUncorrected() throws NoWindException {
|
||||
public Double getNetPointsUncorrected() {
|
||||
try {
|
||||
return netPointsUncorrectedProvider.call();
|
||||
} catch (Exception e) {
|
||||
@@ -1068,7 +1068,10 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) {
|
||||
Distance result = null;
|
||||
for (TrackedRace trackedRace : getTrackedRaces()) {
|
||||
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) {
|
||||
TimePoint startOfRace;
|
||||
if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) &&
|
||||
(startOfRace=trackedRace.getStartOfRace()) != null &&
|
||||
!startOfRace.after(timePoint)) {
|
||||
Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint);
|
||||
if (distanceSailedInRace != null) {
|
||||
if (result == null) {
|
||||
@@ -1277,21 +1280,12 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
final Set<RaceColumn> discardedRaceColumns = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint);
|
||||
for (final RaceColumn raceColumn : this.getRaceColumns()) {
|
||||
RunnableFuture<LeaderboardEntryDTO> future = new FutureTask<LeaderboardEntryDTO>(() -> {
|
||||
try {
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
|
||||
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
|
||||
fillNetPointsUncorrected, cache);
|
||||
} catch (NoWindException e) {
|
||||
logger.info("Exception trying to compute leaderboard entry for competitor "
|
||||
+ competitor.getName() + " in race column " + raceColumn.getName() + ": "
|
||||
+ e.getMessage());
|
||||
logger.throwing(AbstractSimpleLeaderboardImpl.class.getName(),
|
||||
"computeLeaderboardByName.future.call()", e);
|
||||
throw new NoWindError(e);
|
||||
}
|
||||
Entry entry = AbstractSimpleLeaderboardImpl.this.getEntry(competitor, raceColumn, timePoint, discardedRaceColumns);
|
||||
return getLeaderboardEntryDTO(entry, raceColumn, competitor, timePoint,
|
||||
namesOfRaceColumnsForWhichToLoadLegDetails != null
|
||||
&& namesOfRaceColumnsForWhichToLoadLegDetails.contains(raceColumn
|
||||
.getName()), waitForLatestAnalyses, legRanksCache, baseDomainFactory,
|
||||
fillNetPointsUncorrected, cache);
|
||||
});
|
||||
executor.execute(future);
|
||||
futuresForColumnName.put(raceColumn.getName(), future);
|
||||
@@ -1362,7 +1356,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
private LeaderboardEntryDTO getLeaderboardEntryDTO(Entry entry, RaceColumn raceColumn, Competitor competitor,
|
||||
TimePoint timePoint, boolean addLegDetails, boolean waitForLatestAnalyses,
|
||||
Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache, DomainFactory baseDomainFactory,
|
||||
boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
boolean fillNetPointsUncorrected, WindLegTypeAndLegBearingCache cache) {
|
||||
LeaderboardEntryDTO entryDTO = new LeaderboardEntryDTO();
|
||||
TrackedRace trackedRace = raceColumn.getTrackedRace(competitor);
|
||||
entryDTO.race = trackedRace == null ? null : trackedRace.getRaceIdentifier();
|
||||
@@ -1538,7 +1532,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
*/
|
||||
private RaceDetails getRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
|
||||
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
||||
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws NoWindException, InterruptedException, ExecutionException {
|
||||
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
|
||||
final RaceDetails raceDetails;
|
||||
if (trackedRace.getEndOfTracking() != null && trackedRace.getEndOfTracking().compareTo(timePoint) < 0) {
|
||||
raceDetails = getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(trackedRace, competitor, legRanksCache, rankingInfo, cache);
|
||||
@@ -1550,7 +1544,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
private RaceDetails getRaceDetailsForEndOfTrackingFromCacheOrCalculateAndCache(final TrackedRace trackedRace,
|
||||
final Competitor competitor, final Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
||||
RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws NoWindException, InterruptedException, ExecutionException {
|
||||
RankingInfo rankingInfo, final WindLegTypeAndLegBearingCache cache) throws InterruptedException, ExecutionException {
|
||||
final com.sap.sse.common.Util.Pair<TrackedRace, Competitor> key = new com.sap.sse.common.Util.Pair<TrackedRace, Competitor>(trackedRace, competitor);
|
||||
RunnableFuture<RaceDetails> raceDetails;
|
||||
synchronized (raceDetailsAtEndOfTrackingCache) {
|
||||
@@ -1582,7 +1576,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
private RaceDetails calculateRaceDetails(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint,
|
||||
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
||||
WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) throws NoWindException {
|
||||
WindLegTypeAndLegBearingCache cache, final RankingInfo rankingInfo) {
|
||||
final List<LegEntryDTO> legDetails = new ArrayList<LegEntryDTO>();
|
||||
final Course course = trackedRace.getRace().getCourse();
|
||||
course.lockForRead(); // hold back any course re-configurations while looping over the legs
|
||||
@@ -1630,7 +1624,7 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
|
||||
|
||||
private LegEntryDTO createLegEntry(TrackedLegOfCompetitor trackedLeg, TimePoint timePoint,
|
||||
boolean waitForLatestAnalyses, Map<Leg, LinkedHashMap<Competitor, Integer>> legRanksCache,
|
||||
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) throws NoWindException {
|
||||
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
|
||||
LegEntryDTO result;
|
||||
final Duration time = trackedLeg.getTime(timePoint);
|
||||
if (trackedLeg == null || time == null) {
|
||||
|
||||
+9
-7
@@ -77,9 +77,10 @@ public class MarkPassingCalculator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until an object is in the queue, then drains it entirely. After that, the fixes are sorted by the object
|
||||
* they are tracking in <code>competitorFixes</code> and <code>markFixes</code>. Finally, if <code>suspended</code>
|
||||
* is false, new passing are computed.
|
||||
* Waits until an object is in the queue, then drains it entirely. After that the information is sorted depending on
|
||||
* whether it is a fix, an updated waypoint or an updated fixed markpassing. Finally, if <code>suspended</code> is
|
||||
* false, new passing are computed. First new wayponts are evaluated, than new fixed markpassings. Than the
|
||||
* markpassings are computed using mark and competitor fixes.
|
||||
*
|
||||
* @author Nicolas Klose
|
||||
*
|
||||
@@ -139,6 +140,7 @@ public class MarkPassingCalculator {
|
||||
markFixes.clear();
|
||||
addedWaypoints.clear();
|
||||
removedWaypoints.clear();
|
||||
smallestChangedWaypointIndex = null;
|
||||
fixedMarkPassings.clear();
|
||||
removedFixedMarkPassings.clear();
|
||||
suppressedMarkPassings.clear();
|
||||
@@ -171,10 +173,10 @@ public class MarkPassingCalculator {
|
||||
|
||||
/**
|
||||
* The calculation has two steps. For every mark with new fixes those competitor fixes are calculated that may
|
||||
* have changed their status as {@link Candidate} (see {@code FixesAffectedByNewMarkFixes}). Then, the
|
||||
* {@link CandidateFinder} uses those fixes along with any new competitor fixes to calculate any new or wrong
|
||||
* Candidates. These are passed to the {@link CandidateChooser} to calculate any new {@link MarkPassing}s (see
|
||||
* {@link ComputeMarkPassings}).
|
||||
* have changed their status as a {@link Candidate} (see {@code FixesAffectedByNewMarkFixes}). These fixes,
|
||||
* along with any new competitor fixes, are passed into the executor, one task per competitor. The
|
||||
* {@link CandidateFinder} uses the fixes to calculate any new or wrong Candidates. These are passed to the
|
||||
* {@link CandidateChooser} to calculate any new {@link MarkPassing}s (see {@link ComputeMarkPassings}).
|
||||
*
|
||||
*/
|
||||
private void computeMarkPasses(Map<Competitor, List<GPSFix>> newCompetitorFixes,
|
||||
|
||||
+54
-34
@@ -19,9 +19,10 @@ import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.Util.Triple;
|
||||
|
||||
/**
|
||||
* Listens for changes that might affect the MarkPassingCalculator: new Fixes of a Competitor or a Mark and when the
|
||||
* race status changes to finished. New Fixes are put in queue to be evaluated by the {@link MarkPassingCalculator} and
|
||||
* the <code>end</code> object is passed through to signal the end of the race.
|
||||
* Listens for changes that might affect the MarkPassingCalculator: new Fixes of a Competitor or a Mark, updated
|
||||
* Markpassings or Waypoints and the end of the race. New information is put in to a queue to be evaluated by the
|
||||
* {@link MarkPassingCalculator}. To have only one queue each fix is stored in a StorePositionUpdateStrategy, which than
|
||||
* sorts itself in the MPC.
|
||||
*
|
||||
* @author Nicolas Klose
|
||||
*
|
||||
@@ -31,9 +32,10 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
private LinkedBlockingQueue<StorePositionUpdateStrategy> queue;
|
||||
private final StorePositionUpdateStrategy endMarker = new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex, List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
}
|
||||
};
|
||||
@@ -55,9 +57,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void competitorPositionChanged(final GPSFixMoving fix, final Competitor competitor) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
List<GPSFix> list = competitorFixes.get(competitor);
|
||||
if (list == null) {
|
||||
@@ -73,9 +77,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void markPositionChanged(final GPSFix fix, final Mark mark, boolean firstInTrack) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
List<GPSFix> list = markFixes.get(mark);
|
||||
if (list == null) {
|
||||
@@ -92,7 +98,7 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
logger.info("Stopping "+this);
|
||||
logger.info("Stopping " + this);
|
||||
queue.add(endMarker);
|
||||
}
|
||||
|
||||
@@ -100,9 +106,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void waypointAdded(final int zeroBasedIndex, final Waypoint waypointThatGotAdded) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
addedWaypoints.add(waypointThatGotAdded);
|
||||
if (smallestChangedWaypointIndex == null || smallestChangedWaypointIndex > zeroBasedIndex) {
|
||||
@@ -116,9 +124,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void waypointRemoved(final int zeroBasedIndex, final Waypoint waypointThatGotRemoved) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
removedWaypoints.add(waypointThatGotRemoved);
|
||||
if (smallestChangedWaypointIndex == null || smallestChangedWaypointIndex > zeroBasedIndex) {
|
||||
@@ -128,14 +138,18 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
});
|
||||
}
|
||||
|
||||
public void addFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint, final TimePoint timePointOfFixedPassing) {
|
||||
public void addFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint,
|
||||
final TimePoint timePointOfFixedPassing) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
fixedMarkPassings.add(new Triple<Competitor, Integer, TimePoint>(c, zeroBasedIndexOfWaypoint, timePointOfFixedPassing));
|
||||
fixedMarkPassings.add(new Triple<Competitor, Integer, TimePoint>(c, zeroBasedIndexOfWaypoint,
|
||||
timePointOfFixedPassing));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -143,9 +157,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void removeFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
removedMarkPassings.add(new Pair<Competitor, Integer>(c, zeroBasedIndexOfWaypoint));
|
||||
}
|
||||
@@ -156,12 +172,14 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void addSuppressedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
suppressedMarkPassings.add(new Pair<Competitor, Integer>(c, zeroBasedIndexOfWaypoint));
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -169,9 +187,11 @@ public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
public void removeSuppressedPassing(final Competitor c) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
unSuppressedMarkPassings.add(c);
|
||||
}
|
||||
|
||||
+85
-61
@@ -40,17 +40,19 @@ import com.sap.sse.common.Util;
|
||||
* The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all,
|
||||
* every time a competitor passes the crossing-bearing of a waypoint, a candidate is created using linear interpolation
|
||||
* to estimate the exact time the bearing was crossed. Secondly, all local distance minima to a waypoint are candidates.
|
||||
* The probability of a candidate depends on its distance , whether it is on the right side and if it passes in the
|
||||
* right direction of its waypoint.
|
||||
* The probability of a candidate depends on its distance, whether it is on the right side and if it passes in the right
|
||||
* direction of its waypoint.
|
||||
*
|
||||
* @author Nicolas Klose
|
||||
*
|
||||
*/
|
||||
public class CandidateFinderImpl implements CandidateFinder {
|
||||
|
||||
// TODO Parallelization?
|
||||
|
||||
// The higher this is, the closer the fixes have to be to waypoint to become a Candidate
|
||||
private final int strictnessOfDistanceBasedProbability = 8;
|
||||
|
||||
// All of the penalties are multiplied onto the propability of a Candidate. A value of 0 exludes Candidates that do
|
||||
// not fit, a value of 1 imposes no penalty on each criteria
|
||||
private final double penaltyForWrongSide = 0.7;
|
||||
private final double penaltyForWrongDirection = 0.7;
|
||||
private final double penaltyForDistanceCandidates = 0.7;
|
||||
@@ -120,8 +122,8 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
TimePoint end = null;
|
||||
for (Entry<Mark, List<GPSFix>> fixes : markFixes.entrySet()) {
|
||||
for (GPSFix fix : fixes.getValue()) {
|
||||
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey())
|
||||
.getEstimatedPositionTimePeriodAffectedBy(fix);
|
||||
TimeRange timePoints = race.getOrCreateTrack(fixes.getKey()).getEstimatedPositionTimePeriodAffectedBy(
|
||||
fix);
|
||||
TimePoint newStart = timePoints.from();
|
||||
TimePoint newEnd = timePoints.to();
|
||||
start = start == null || start.after(newStart) ? newStart : start;
|
||||
@@ -157,17 +159,23 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
|
||||
List<Candidate> newCans = new ArrayList<>();
|
||||
List<Candidate> wrongCans = new ArrayList<>();
|
||||
Util.Pair<List<Candidate>, List<Candidate>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes,
|
||||
race.getRace().getCourse().getWaypoints());
|
||||
Util.Pair<List<Candidate>, List<Candidate>> xteCandidates = checkForXTECandidatesChanges(c, fixes, race
|
||||
.getRace().getCourse().getWaypoints());
|
||||
logger.finest(distanceCandidates.getA().size() + " new Distance Candidates, " + xteCandidates.getA().size()
|
||||
+ " new XTE Candidates, " + distanceCandidates.getB().size() + " removed distance Candidates and "
|
||||
+ xteCandidates.getB().size() + " removed XTE Candidates.");
|
||||
newCans.addAll(xteCandidates.getA());
|
||||
newCans.addAll(distanceCandidates.getA());
|
||||
wrongCans.addAll(xteCandidates.getB());
|
||||
wrongCans.addAll(distanceCandidates.getB());
|
||||
Course course = race.getRace().getCourse();
|
||||
course.lockForRead();
|
||||
try {
|
||||
Util.Pair<List<Candidate>, List<Candidate>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes,
|
||||
race.getRace().getCourse().getWaypoints());
|
||||
Util.Pair<List<Candidate>, List<Candidate>> xteCandidates = checkForXTECandidatesChanges(c, fixes, race
|
||||
.getRace().getCourse().getWaypoints());
|
||||
logger.finest(distanceCandidates.getA().size() + " new Distance Candidates, " + xteCandidates.getA().size()
|
||||
+ " new XTE Candidates, " + distanceCandidates.getB().size() + " removed distance Candidates and "
|
||||
+ xteCandidates.getB().size() + " removed XTE Candidates.");
|
||||
newCans.addAll(xteCandidates.getA());
|
||||
newCans.addAll(distanceCandidates.getA());
|
||||
wrongCans.addAll(xteCandidates.getB());
|
||||
wrongCans.addAll(distanceCandidates.getB());
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
return new Util.Pair<Iterable<Candidate>, Iterable<Candidate>>(newCans, wrongCans);
|
||||
}
|
||||
|
||||
@@ -179,27 +187,33 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
xteCache.get(c).clear();
|
||||
}
|
||||
List<Waypoint> changedWaypoints = new ArrayList<>();
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
if (course.getIndexOfWaypoint(w) > indexOfChange - 2) {
|
||||
changedWaypoints.add(w);
|
||||
course.lockForRead();
|
||||
try {
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
if (course.getIndexOfWaypoint(w) > indexOfChange - 2) {
|
||||
changedWaypoints.add(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
List<Candidate> badCans = new ArrayList<>();
|
||||
List<Candidate> newCans = new ArrayList<>();
|
||||
for (Waypoint w : changedWaypoints) {
|
||||
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
|
||||
badCans.addAll(xteCans.values());
|
||||
xteCans.clear();
|
||||
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
|
||||
badCans.addAll(distanceCans.values());
|
||||
distanceCans.clear();
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
List<Candidate> badCans = new ArrayList<>();
|
||||
List<Candidate> newCans = new ArrayList<>();
|
||||
for (Waypoint w : changedWaypoints) {
|
||||
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
|
||||
badCans.addAll(xteCans.values());
|
||||
xteCans.clear();
|
||||
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
|
||||
badCans.addAll(distanceCans.values());
|
||||
distanceCans.clear();
|
||||
}
|
||||
Set<GPSFix> allFixes = getAllFixes(c);
|
||||
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
|
||||
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
|
||||
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
|
||||
}
|
||||
Set<GPSFix> allFixes = getAllFixes(c);
|
||||
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
|
||||
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
|
||||
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -215,7 +229,6 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
|
||||
private Map<Competitor, List<Candidate>> removeWaypoints(Iterable<Waypoint> waypoints) {
|
||||
// TODO Clear caches?
|
||||
Map<Competitor, List<Candidate>> result = new HashMap<>();
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
result.put(c, new ArrayList<Candidate>());
|
||||
@@ -237,7 +250,7 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private Map<GPSFix, Candidate> getDistanceCandidates(Competitor c, Waypoint w) {
|
||||
synchronized (distanceCandidates) {
|
||||
Map<GPSFix, Candidate> result = distanceCandidates.get(c).get(w);
|
||||
@@ -295,10 +308,10 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
try {
|
||||
track.lockForRead();
|
||||
for (GPSFix fix : track.getFixes(
|
||||
race.getStartOfTracking()==null?TimePoint.BeginningOfTime:race.getStartOfTracking(),
|
||||
/* fromInclusive */ true,
|
||||
race.getEndOfTracking()==null?TimePoint.EndOfTime:race.getEndOfTracking(),
|
||||
/* toInclusive */ true)) {
|
||||
race.getStartOfTracking() == null ? TimePoint.BeginningOfTime : race.getStartOfTracking(),
|
||||
/* fromInclusive */true,
|
||||
race.getEndOfTracking() == null ? TimePoint.EndOfTime : race.getEndOfTracking(),
|
||||
/* toInclusive */true)) {
|
||||
fixes.add(fix);
|
||||
}
|
||||
} finally {
|
||||
@@ -360,8 +373,10 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
List<Distance> waypointDistances = fixDistances.get(w);
|
||||
List<Distance> waypointDistancesBefore = fixDistancesBefore.get(w);
|
||||
List<Distance> waypointDistancesAfter = fixDistancesAfter.get(w);
|
||||
// due to course changes, waypoints that exist in the waypoints collection may not have a corresponding
|
||||
// key in passingInstructions' key set which is the basis for the waypoints for which getDistances(...)
|
||||
// due to course changes, waypoints that exist in the waypoints collection may not have a
|
||||
// corresponding
|
||||
// key in passingInstructions' key set which is the basis for the waypoints for which
|
||||
// getDistances(...)
|
||||
// computes results; so we have to check for null here:
|
||||
if (waypointDistances != null && waypointDistancesBefore != null && waypointDistancesAfter != null) {
|
||||
Iterator<Distance> disIter = waypointDistances.iterator();
|
||||
@@ -396,8 +411,8 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
wasCan = true;
|
||||
}
|
||||
if (!wasCan && isCan) {
|
||||
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t,
|
||||
probability, w);
|
||||
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
|
||||
t, probability, w);
|
||||
getDistanceCandidates(c, w).put(fix, newCan);
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added distance" + newCan.toString() + "for " + c);
|
||||
@@ -405,8 +420,8 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
getDistanceCandidates(c, w).remove(fix);
|
||||
result.getB().add(oldCan);
|
||||
} else if (wasCan && isCan && oldCan.getProbability() != probability) {
|
||||
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t,
|
||||
probability, w);
|
||||
Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1,
|
||||
t, probability, w);
|
||||
getDistanceCandidates(c, w).put(fix, newCan);
|
||||
result.getA().add(newCan);
|
||||
logger.finest("Added distance" + newCan.toString() + "for " + c);
|
||||
@@ -425,9 +440,15 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
if (result == null) {
|
||||
// Else calculate distances and put them into the cache
|
||||
result = new LinkedHashMap<>();
|
||||
for (Waypoint w : getWaypoints()) {
|
||||
List<Distance> distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint());
|
||||
result.put(w, distances);
|
||||
Course course = race.getRace().getCourse();
|
||||
course.lockForRead();
|
||||
try {
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
List<Distance> distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint());
|
||||
result.put(w, distances);
|
||||
}
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
distanceCache.get(c).put(fix, result);
|
||||
}
|
||||
@@ -560,24 +581,26 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
result = new HashMap<>();
|
||||
Position p = fix.getPosition();
|
||||
TimePoint t = fix.getTimePoint();
|
||||
for (Waypoint w : getWaypoints()) {
|
||||
List<Distance> distances = new ArrayList<>();
|
||||
result.put(w, distances);
|
||||
for (Util.Pair<Position, Bearing> crossingInfo : getCrossingInformation(w, t)) {
|
||||
if (crossingInfo.getA() != null && crossingInfo.getB() != null) {
|
||||
distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB()));
|
||||
Course course = race.getRace().getCourse();
|
||||
course.lockForRead();
|
||||
try {
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
List<Distance> distances = new ArrayList<>();
|
||||
result.put(w, distances);
|
||||
for (Util.Pair<Position, Bearing> crossingInfo : getCrossingInformation(w, t)) {
|
||||
if (crossingInfo.getA() != null && crossingInfo.getB() != null) {
|
||||
distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
course.unlockAfterRead();
|
||||
}
|
||||
xteCache.get(c).put(fix, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Iterable<Waypoint> getWaypoints() {
|
||||
return race.getRace().getCourse().getWaypoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the cross-track error of each fix to the position and crossing bearing of each waypoint. Gates have
|
||||
* two of these and lines always go from the port mark to the starboard mark.
|
||||
@@ -833,7 +856,8 @@ public class CandidateFinderImpl implements CandidateFinder {
|
||||
private Util.Pair<Mark, Mark> getPortAndStarboardMarks(TimePoint t, Waypoint w) {
|
||||
List<Position> markPositions = new ArrayList<Position>();
|
||||
for (Mark mark : w.getMarks()) {
|
||||
final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */ false);
|
||||
final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */
|
||||
false);
|
||||
if (estimatedMarkPosition == null) {
|
||||
return new Util.Pair<Mark, Mark>(null, null);
|
||||
}
|
||||
|
||||
+2
-2
@@ -195,7 +195,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
|
||||
* <code>timePoint</code>, returns the VMG value for this time point. If the competitor has already finished the
|
||||
* leg, the VMG at the time when the competitor finished the leg is returned.
|
||||
*/
|
||||
Speed getVelocityMadeGood(TimePoint timePoint, WindPositionMode windPositionMode) throws NoWindException;
|
||||
Speed getVelocityMadeGood(TimePoint timePoint, WindPositionMode windPositionMode);
|
||||
|
||||
/**
|
||||
* Same as {@link #getVelocityMadeGood(TimePoint, WindPositionMode)}, only that a cache for wind data and leg type and bearing
|
||||
@@ -212,7 +212,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
|
||||
/**
|
||||
* Same as {@link #getEstimatedTimeToNextMark(TimePoint, WindPositionMode)}, only that a cache for leg type calculation is passed.
|
||||
*/
|
||||
Duration getEstimatedTimeToNextMark(TimePoint timePoint, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) throws NoWindException;
|
||||
Duration getEstimatedTimeToNextMark(TimePoint timePoint, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> in case this leg's competitor hasn't started the leg yet. If in the leg at
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.sap.sailing.domain.tracking;
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
@@ -55,8 +54,6 @@ public interface TrackedRegatta extends Serializable {
|
||||
|
||||
void unlockTrackedRacesAfterWrite();
|
||||
|
||||
Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass);
|
||||
|
||||
/**
|
||||
* Creates a {@link TrackedRace} based on the parameter specified and {@link #addTrackedRace(TrackedRace) adds} it
|
||||
* to this tracked regatta. Afterwards, calling {@link #getTrackedRace(RaceDefinition) getTrackedRace(raceDefinition)}
|
||||
@@ -93,6 +90,8 @@ public interface TrackedRegatta extends Serializable {
|
||||
*/
|
||||
void addRaceListener(RaceListener listener);
|
||||
|
||||
void removeRaceListener(RaceListener listener);
|
||||
|
||||
int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
|
||||
|
||||
void removeTrackedRace(RaceDefinition raceDefinition);
|
||||
|
||||
+13
-9
@@ -284,11 +284,13 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
// as fallback in the absence of wind information, project to leg bearing
|
||||
projectToBearing = cache.getLegBearing(getTrackedLeg(), at);
|
||||
}
|
||||
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
|
||||
if (cos < 0) {
|
||||
projectToBearing = projectToBearing.reverse();
|
||||
if (speed.getBearing() != null && projectToBearing != null) {
|
||||
double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians());
|
||||
if (cos < 0) {
|
||||
projectToBearing = projectToBearing.reverse();
|
||||
}
|
||||
result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing);
|
||||
}
|
||||
result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -338,10 +340,12 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
}
|
||||
final Position endPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(to, /* extrapolate */false);
|
||||
if (endPos != null) {
|
||||
Distance d = getTrackedLeg().getAbsoluteWindwardDistance(
|
||||
getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false),
|
||||
endPos, to, WindPositionMode.EXACT, cache);
|
||||
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
|
||||
final Position startPos = getTrackedRace().getTrack(getCompetitor()).getEstimatedPosition(start.getTimePoint(), false);
|
||||
if (startPos != null) {
|
||||
Distance d = getTrackedLeg().getAbsoluteWindwardDistance(startPos, endPos, to,
|
||||
WindPositionMode.EXACT, cache);
|
||||
result = d.inTime(to.asMillis() - start.getTimePoint().asMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -613,7 +617,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode) throws NoWindException {
|
||||
public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode) {
|
||||
return getVelocityMadeGood(at, windPositionMode, new LeaderboardDTOCalculationReuseCache(at));
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1024,7 +1024,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
// competitor has finished race at or before the requested time point; use time point of crossing the finish line
|
||||
end = markPassings.last().getTimePoint();
|
||||
} else if ((trackedLegOfCompetitor=getTrackedLeg(competitor, timePoint)) == null ||
|
||||
(!trackedLegOfCompetitor.hasFinishedLeg(timePoint)
|
||||
(!trackedLegOfCompetitor.hasFinishedLeg(getEndOfTracking())
|
||||
&& ((getEndOfTracking() != null && timePoint.after(getEndOfTracking()))
|
||||
|| getStatus().getStatus() == TrackedRaceStatusEnum.FINISHED))) {
|
||||
// If the race is no longer tracking and hence no more data can be expected, and the competitor
|
||||
|
||||
+44
-38
@@ -5,17 +5,14 @@ import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.ObjectStreamException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
@@ -51,21 +48,19 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
*/
|
||||
private final Map<RaceDefinition, TrackedRace> trackedRaces;
|
||||
|
||||
private final Map<BoatClass, Collection<TrackedRace>> trackedRacesByBoatClass;
|
||||
private transient Set<RaceListener> raceListeners;
|
||||
private transient ConcurrentHashMap<RaceListener, RaceListener> raceListeners;
|
||||
|
||||
public TrackedRegattaImpl(Regatta regatta) {
|
||||
super();
|
||||
trackedRacesLock = new NamedReentrantReadWriteLock("trackeRaces lock for tracked regatta "+regatta.getName(), /* fair */ false);
|
||||
this.regatta = regatta;
|
||||
this.trackedRaces = new HashMap<RaceDefinition, TrackedRace>();
|
||||
this.trackedRacesByBoatClass = new HashMap<BoatClass, Collection<TrackedRace>>();
|
||||
raceListeners = new HashSet<RaceListener>();
|
||||
raceListeners = new ConcurrentHashMap<RaceListener, RaceListener>();
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
|
||||
ois.defaultReadObject();
|
||||
this.raceListeners = new HashSet<RaceListener>();
|
||||
this.raceListeners = new ConcurrentHashMap<RaceListener, RaceListener>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,7 +99,6 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
TrackedRegattaImpl result = new TrackedRegattaImpl(this.regatta);
|
||||
result.trackedRaces.putAll(this.trackedRaces);
|
||||
result.trackedRacesByBoatClass.putAll(this.trackedRacesByBoatClass);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -116,19 +110,11 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
logger.info("adding tracked race for "+trackedRace.getRace()+" to tracked regatta "+getRegatta().getName()+
|
||||
" with regatta hash code "+getRegatta().hashCode());
|
||||
oldTrackedRace = trackedRaces.put(trackedRace.getRace(), trackedRace);
|
||||
if (oldTrackedRace != trackedRace) {
|
||||
Collection<TrackedRace> coll = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass());
|
||||
if (coll == null) {
|
||||
coll = new ArrayList<TrackedRace>();
|
||||
trackedRacesByBoatClass.put(trackedRace.getRace().getBoatClass(), coll);
|
||||
}
|
||||
coll.add(trackedRace);
|
||||
}
|
||||
} finally {
|
||||
LockUtil.unlockAfterWrite(trackedRacesLock);
|
||||
}
|
||||
if (oldTrackedRace != trackedRace) {
|
||||
for (RaceListener listener : raceListeners) {
|
||||
for (RaceListener listener : raceListeners.keySet()) {
|
||||
listener.raceAdded(trackedRace);
|
||||
}
|
||||
}
|
||||
@@ -149,14 +135,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
LockUtil.lockForWrite(trackedRacesLock);
|
||||
try {
|
||||
trackedRaces.remove(trackedRace.getRace());
|
||||
Collection<TrackedRace> trbbc = trackedRacesByBoatClass.get(trackedRace.getRace().getBoatClass());
|
||||
if (trbbc != null) {
|
||||
trbbc.remove(trackedRace);
|
||||
if (trbbc.isEmpty()) {
|
||||
trackedRacesByBoatClass.remove(trackedRace.getRace().getBoatClass());
|
||||
}
|
||||
}
|
||||
for (RaceListener listener : raceListeners) {
|
||||
for (RaceListener listener : raceListeners.keySet()) {
|
||||
listener.raceRemoved(trackedRace);
|
||||
}
|
||||
} finally {
|
||||
@@ -177,23 +156,40 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
return trackedRaces.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass) {
|
||||
return trackedRacesByBoatClass.get(boatClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackedRace getTrackedRace(RaceDefinition race) {
|
||||
boolean interrupted = false;
|
||||
lockTrackedRacesForRead();
|
||||
try {
|
||||
TrackedRace result = trackedRaces.get(race);
|
||||
while (!interrupted && result == null) {
|
||||
if (!interrupted && result == null) {
|
||||
final Object mutex = new Object();
|
||||
final RaceListener listener = new RaceListener() {
|
||||
@Override
|
||||
public void raceRemoved(TrackedRace trackedRace) {}
|
||||
|
||||
@Override
|
||||
public void raceAdded(TrackedRace trackedRace) {
|
||||
synchronized (mutex) {
|
||||
mutex.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
addRaceListener(listener);
|
||||
try {
|
||||
trackedRaces.wait();
|
||||
result = trackedRaces.get(race);
|
||||
} catch (InterruptedException e) {
|
||||
interrupted = true;
|
||||
synchronized (mutex) {
|
||||
result = trackedRaces.get(race);
|
||||
while (!interrupted && result == null) {
|
||||
try {
|
||||
mutex.wait();
|
||||
result = trackedRaces.get(race);
|
||||
} catch (InterruptedException e) {
|
||||
interrupted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
removeRaceListener(listener);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -217,7 +213,7 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
final List<TrackedRace> trackedRacesCopy = new ArrayList<>();
|
||||
lockTrackedRacesForRead();
|
||||
try {
|
||||
raceListeners.add(listener);
|
||||
raceListeners.put(listener, listener);
|
||||
Util.addAll(getTrackedRaces(), trackedRacesCopy);
|
||||
} finally {
|
||||
unlockTrackedRacesAfterRead();
|
||||
@@ -227,6 +223,16 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRaceListener(RaceListener listener) {
|
||||
lockTrackedRacesForRead();
|
||||
try {
|
||||
raceListeners.remove(listener);
|
||||
} finally {
|
||||
unlockTrackedRacesAfterRead();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNetPoints(Competitor competitor, TimePoint timePoint) throws NoWindException {
|
||||
int result = 0;
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
<plugin id="com.sap.sailing.gwt.ui" autoStart="true" startLevel="5" />
|
||||
<plugin id="com.sap.sailing.kiworesultimport" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sailing.manage2sail.resultimport" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sailing.news" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sailing.monitoring" autoStart="true" startLevel="6" />
|
||||
<plugin id="com.sap.sailing.news" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sailing.polars" autoStart="true" startLevel="4" />
|
||||
<plugin id="com.sap.sailing.resultimport" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sailing.sailwave.resultimport" autoStart="true" startLevel="3" />
|
||||
@@ -63,7 +63,7 @@
|
||||
<plugin id="com.sap.sailing.xrr.resultimport" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sse.filestorage" autoStart="true" startLevel="3" />
|
||||
<plugin id="com.sap.sse.mail" autoStart="true" startLevel="4" />
|
||||
<plugin id="com.sap.sse.replication" autoStart="true" startLevel="4" />
|
||||
<plugin id="com.sap.sse.replication" autoStart="true" startLevel="5" />
|
||||
<plugin id="com.sap.sse.security.ui" autoStart="true" startLevel="5" />
|
||||
<plugin id="com.sap.sse.security.userstore.mongodb" autoStart="true" startLevel="3" />
|
||||
<plugin id="org.apache.felix.shell" autoStart="true" startLevel="0" />
|
||||
|
||||
+4
-6
@@ -11,7 +11,6 @@ import java.util.Set;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
|
||||
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
|
||||
import com.sap.sailing.domain.base.BoatClass;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.CourseBase;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
@@ -274,11 +273,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<TrackedRace> getTrackedRaces(BoatClass boatClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTrackedRace(TrackedRace trackedRace) {
|
||||
}
|
||||
@@ -333,6 +327,10 @@ public class MockedTrackedRace implements DynamicTrackedRace {
|
||||
@Override
|
||||
public void unlockTrackedRacesAfterWrite() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRaceListener(RaceListener listener) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ public interface BoatClassImageResources extends ClientBundle {
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/470.png")
|
||||
ImageResource _470Icon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.png")
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER_FX.jpg")
|
||||
ImageResource _49erFXIcon();
|
||||
|
||||
@Source("com/sap/sailing/gwt/ui/client/images/boatclass/49ER.png")
|
||||
|
||||
+3
-1
@@ -74,7 +74,9 @@ public abstract class RegattaLeaderboardDialog extends AbstractLeaderboardDialog
|
||||
}
|
||||
regattaDefinesDiscardsLabel.setVisible(true);
|
||||
} else {
|
||||
discardThresholdBoxes.getWidget().setVisible(true);
|
||||
if (discardThresholdBoxes != null) {
|
||||
discardThresholdBoxes.getWidget().setVisible(true);
|
||||
}
|
||||
regattaDefinesDiscardsLabel.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public class RegattaLeaderboardEditDialog extends RegattaLeaderboardDialog {
|
||||
|
||||
regattaListBox = createSortedRegattaListBox(existingRegattas, leaderboardDescriptor.getRegattaName());
|
||||
regattaListBox.setEnabled(false);
|
||||
if (leaderboardDescriptor.getDiscardThresholds() != null) {
|
||||
if (!getSelectedRegatta().definesSeriesDiscardThresholds()) {
|
||||
discardThresholdBoxes = new DiscardThresholdBoxes(this, leaderboardDescriptor.getDiscardThresholds(), stringMessages);
|
||||
} // else, the regatta leaderboard obtains its result discarding rule implicitly from the underlying regatta
|
||||
adjustVisibilityOfResultDiscardingRuleComponent();
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
+2
@@ -17,6 +17,7 @@ public class RegattaJsonSerializer implements JsonSerializer<Regatta> {
|
||||
public static final String FIELD_SERIES = "series";
|
||||
public static final String FIELD_COMPETITORS = "competitors";
|
||||
public static final String FIELD_TRACKED_RACES = "trackedRaces";
|
||||
public static final String FIELD_COURSE_AREA_ID = "courseAreaId";
|
||||
|
||||
private final JsonSerializer<Series> seriesSerializer;
|
||||
private final JsonSerializer<Competitor> competitorSerializer;
|
||||
@@ -38,6 +39,7 @@ public class RegattaJsonSerializer implements JsonSerializer<Regatta> {
|
||||
result.put(FIELD_END_DATE, regatta.getEndDate() != null ? regatta.getEndDate().asMillis() : null);
|
||||
result.put(FIELD_SCORINGSYSTEM, regatta.getScoringScheme().getType().name());
|
||||
result.put(FIELD_BOATCLASS, regatta.getBoatClass() != null ? regatta.getBoatClass().getName(): null);
|
||||
result.put(FIELD_COURSE_AREA_ID, regatta.getDefaultCourseArea() != null ? regatta.getDefaultCourseArea().getId().toString(): null);
|
||||
|
||||
if(seriesSerializer != null) {
|
||||
JSONArray seriesJson = new JSONArray();
|
||||
|
||||
+4
-8
@@ -287,14 +287,10 @@ public class RegattaAndRaceDataJsonGetServlet extends AbstractJsonHttpServlet {
|
||||
jsonCompetitorInLeg.put("distanceTraveledOverGroundInMeters",
|
||||
distanceTraveled.getMeters());
|
||||
}
|
||||
try {
|
||||
Speed velocityMadeGood = trackedLegOfCompetitor.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
|
||||
if (velocityMadeGood != null) {
|
||||
jsonCompetitorInLeg.put("velocityMadeGoodInKnots", velocityMadeGood.getKnots());
|
||||
jsonCompetitorInLeg.put("velocityMadeGoodInMetersPerSecond", velocityMadeGood.getMetersPerSecond());
|
||||
}
|
||||
} catch (NoWindException e) {
|
||||
// well, we don't know the wind direction... then no VMG will be shown...
|
||||
Speed velocityMadeGood = trackedLegOfCompetitor.getVelocityMadeGood(timePoint, WindPositionMode.EXACT);
|
||||
if (velocityMadeGood != null) {
|
||||
jsonCompetitorInLeg.put("velocityMadeGoodInKnots", velocityMadeGood.getKnots());
|
||||
jsonCompetitorInLeg.put("velocityMadeGoodInMetersPerSecond", velocityMadeGood.getMetersPerSecond());
|
||||
}
|
||||
Speed averageVelocityMadeGood = trackedLegOfCompetitor
|
||||
.getAverageVelocityMadeGood(timePoint);
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="org.eclipse.pde.ui.EquinoxLauncher">
|
||||
<booleanAttribute key="append.args" value="true"/>
|
||||
<booleanAttribute key="automaticAdd" value="false"/>
|
||||
<booleanAttribute key="automaticValidate" value="false"/>
|
||||
<stringAttribute key="bootstrap" value=""/>
|
||||
<stringAttribute key="checked" value="[NONE]"/>
|
||||
<booleanAttribute key="clearConfig" value="false"/>
|
||||
<stringAttribute key="configLocation" value="${workspace_loc}/target"/>
|
||||
<booleanAttribute key="default" value="true"/>
|
||||
<booleanAttribute key="default_auto_start" value="true"/>
|
||||
<intAttribute key="default_start_level" value="4"/>
|
||||
<booleanAttribute key="includeOptional" value="true"/>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -consoleLog -console -clean"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.pde.ui.workbenchClasspathProvider"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Djetty.port=8889 -Dreplication.exchangeName=sapsailinganalytics-replica -Declipse.ignoreApp=true -Dosgi.noShutdown=true -Dexpedition.udp.port=2011 -Xmx6000m -XX:+UseG1GC -Djetty.home=${project_loc:com.sap.sailing.server}/../target/configuration/jetty -Djava.util.logging.config.file=${project_loc:com.sap.sailing.server}/../target/configuration/logging_debug.properties -XX:+UseMembar -Dmongo.dbName=replica -Dreplicate.on.start=com.sap.sailing.server.impl.RacingEventServiceImpl,com.sap.sse.security.impl.SecurityServiceImpl,com.sap.sse.filestorage.impl.FileStorageManagementServiceImpl,com.sap.sse.mail.impl.MailServiceImpl -Dreplicate.master.queue.host=127.0.0.1 -Dreplicate.master.queue.port=5672 -Dreplicate.master.servlet.port=8888 -Dreplicate.master.servlet.host=127.0.0.1"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="${workspace_loc}"/>
|
||||
<stringAttribute key="pde.version" value="3.3"/>
|
||||
<booleanAttribute key="show_selected_only" value="false"/>
|
||||
<stringAttribute key="target_bundles" value="com.fasterxml.jackson.core.jackson-annotations@default:default,com.fasterxml.jackson.core.jackson-core@default:default,com.fasterxml.jackson.core.jackson-databind@default:default,com.fasterxml.jackson.jaxrs.jackson-jaxrs-base@default:default,com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider@default:default,com.rabbitmq.client@default:default,com.sun.jersey.contribs.jersey-multipart@default:default,com.sun.jersey@default:default,com.sun.mail.javax.mail@default:default,jackson-core-lgpl@default:default,jackson-jaxrs@default:default,jackson-mapper-lgpl@default:default,javax.servlet@default:default,javax.validation@default:default,javax.ws.rs@default:default,jcl.over.slf4j@default:default,org.apache.commons.beanutils@default:default,org.apache.commons.codec@default:default,org.apache.commons.collections@default:default,org.apache.commons.fileupload@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.commons.math@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.gogo.shell@default:default,org.apache.geronimo.specs.geronimo-annotation_1.0_spec@default:default,org.apache.geronimo.specs.geronimo-j2ee-management_1.1_spec@default:default,org.apache.geronimo.specs.geronimo-jms_1.1_spec@default:default,org.apache.httpcomponents.httpclient@default:default,org.apache.httpcomponents.httpcore@default:default,org.apache.servicemix.bundles.aws-java-sdk@default:default,org.apache.servicemix.bundles.ehcache@default:default,org.apache.servicemix.bundles.scribe@default:default,org.apache.servicemix.bundles.zxing@default:default,org.apache.shiro.core@default:default,org.apache.shiro.ehcache@default:default,org.apache.shiro.web@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.console@default:default,org.eclipse.jetty.continuation@default:default,org.eclipse.jetty.deploy@default:default,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.jmx@default:default,org.eclipse.jetty.osgi.boot@3:default,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.jetty.webapp@default:default,org.eclipse.jetty.websocket.api@default:default,org.eclipse.jetty.websocket.client@default:default,org.eclipse.jetty.websocket.common@default:default,org.eclipse.jetty.websocket.server@default:default,org.eclipse.jetty.websocket.servlet@default:default,org.eclipse.jetty.xml@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi@-1:true,org.jdom@default:default,org.jvnet.mimepull@default:default,org.mongodb.mongo-java-driver@default:default,org.objectweb.asm@default:default,rome@default:default,routeconverter@default:default,slf4j.api@default:default,slf4j.jdk14@default:false"/>
|
||||
<booleanAttribute key="tracing" value="false"/>
|
||||
<booleanAttribute key="useCustomFeatures" value="false"/>
|
||||
<booleanAttribute key="useDefaultConfigArea" value="false"/>
|
||||
<stringAttribute key="workspace_bundles" value="com.google.gwt.osgi@default:default,com.googlecode.java-diff-utils@default:default,com.googlecode.mgwt@default:default,com.sap.sailing.barbados.resultimport@default:default,com.sap.sailing.dashboards.gwt@5:true,com.sap.sailing.datamining.shared@default:default,com.sap.sailing.datamining@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.deckmanadapter@4:true,com.sap.sailing.domain.igtimiadapter.persistence@default:default,com.sap.sailing.domain.igtimiadapter@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.racelogtrackingadapter@3:default,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@4:true,com.sap.sailing.domain.swisstimingreplayadapter@4:true,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@4:true,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.gwt.ui@5:true,com.sap.sailing.kiworesultimport@default:default,com.sap.sailing.manage2sail.resultimport@default:default,com.sap.sailing.manage2sail@default:default,com.sap.sailing.news@default:default,com.sap.sailing.nmeaconnector@default:default,com.sap.sailing.polars@4:true,com.sap.sailing.resultimport@default:default,com.sap.sailing.sailwave.resultimport@default:default,com.sap.sailing.server.gateway.ess40@4:true,com.sap.sailing.server.gateway.serialization.shared.android@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.server.gateway@5:true,com.sap.sailing.server.trackfiles@default:default,com.sap.sailing.server@default:default,com.sap.sailing.simulator@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@default:default,com.sap.sailing.www.events@4:true,com.sap.sailing.www@4:true,com.sap.sailing.xrr.resultimport@default:default,com.sap.sailing.xrr.schema@default:default,com.sap.sailing.xrr.structureimport@default:default,com.sap.sse.common@default:default,com.sap.sse.datamining.shared@default:default,com.sap.sse.datamining@default:default,com.sap.sse.filestorage@default:default,com.sap.sse.gwt.adminconsole@default:default,com.sap.sse.gwt.theme@default:default,com.sap.sse.gwt@default:default,com.sap.sse.mail@4:true,com.sap.sse.mongodb@default:default,com.sap.sse.operationaltransformation@default:default,com.sap.sse.replication@4:true,com.sap.sse.security.common@default:default,com.sap.sse.security.ui@6:true,com.sap.sse.security.userstore.mongodb@4:true,com.sap.sse.security@5:true,com.sap.sse.shared.android@default:default,com.sap.sse@default:default,com.tractrac.clientmodule@default:default,net.sf.marineapi@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
|
||||
<stringAttribute key="yk-options" value=" additional-options2=onexit\=snapshot "/>
|
||||
</launchConfiguration>
|
||||
@@ -30,14 +30,24 @@ public interface ReplicationService {
|
||||
*/
|
||||
ReplicationMasterDescriptor getReplicatingFromMaster();
|
||||
|
||||
/**
|
||||
* Same as {@link #startToReplicateFrom(ReplicationMasterDescriptor, Iterable)}, where the list of replicables
|
||||
* is determined by asking this service's {@link ReplicablesProvider} which replicables are currently available.
|
||||
* Note that this is not a good idea during the OSGi start-up phase, particularly if there is a bundle that
|
||||
* starts after this bundle because then that {@link Replicable} will not yet be registered and won't become part
|
||||
* of replication.
|
||||
*/
|
||||
void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Performs a servlet request to the remote master server's {@link ReplicationServlet}, first registering this
|
||||
* replica, ensuring the message queue for replicating operations is created, then subscribing to the master's
|
||||
* replication message queue and asking the servlet for the stream containing the initial load which will then be
|
||||
* used to {@link Replicable#initiallyFillFrom(java.io.ObjectInputStream) replace} the current {@link Replicable}'s
|
||||
* used to {@link Replicable#initiallyFillFrom(java.io.ObjectInputStream) replace} the {@link Replicable replicables}'
|
||||
* state.
|
||||
*/
|
||||
void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException;
|
||||
void startToReplicateFrom(ReplicationMasterDescriptor master, Iterable<Replicable<?, ?>> replicables) throws IOException,
|
||||
ClassNotFoundException, InterruptedException;
|
||||
|
||||
/**
|
||||
* Registers a replica with this master instance. The <code>replica</code> will be considered in the result of
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.sap.sse.replication.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import com.sap.sse.replication.Replicable;
|
||||
import com.sap.sse.replication.ReplicablesProvider;
|
||||
import com.sap.sse.replication.ReplicationService;
|
||||
|
||||
@@ -129,12 +132,16 @@ public class Activator implements BundleActivator {
|
||||
new Thread("ServiceTracker waiting for Replicables "+Arrays.toString(replicableIdsAsStrings)) {
|
||||
@Override
|
||||
public void run() {
|
||||
logger.info("Waiting for Replicables " + replicableIdsAsStrings + " before firing up replication automatically...");
|
||||
logger.info("Waiting for Replicables " + Arrays.toString(replicableIdsAsStrings) +
|
||||
" before firing up replication automatically...");
|
||||
final List<Replicable<?, ?>> replicables = new ArrayList<>();
|
||||
for (String replicableIdAsString : replicableIdsAsStrings) {
|
||||
replicablesProvider.getReplicable(replicableIdAsString, /* wait */true);
|
||||
Replicable<?, ?> replicable = replicablesProvider.getReplicable(replicableIdAsString, /* wait */true);
|
||||
logger.info("Obtained Replicable " + replicableIdAsString);
|
||||
replicables.add(replicable);
|
||||
}
|
||||
logger.info("Configuration requested automatic replication. Starting it up...");
|
||||
logger.info("Configuration requested automatic replication for replicables "+
|
||||
Arrays.toString(replicableIdsAsStrings)+". Starting it up...");
|
||||
String replicateFromExchangeName = System.getProperty(PROPERTY_NAME_REPLICATE_MASTER_EXCHANGE_NAME);
|
||||
if (replicateFromExchangeName == null) {
|
||||
replicateFromExchangeName = masterExchangeName;
|
||||
@@ -147,8 +154,8 @@ public class Activator implements BundleActivator {
|
||||
System.getProperty(PROPERTY_NAME_REPLICATE_MASTER_SERVLET_HOST),
|
||||
Integer.valueOf(System.getProperty(PROPERTY_NAME_REPLICATE_MASTER_SERVLET_PORT).trim()));
|
||||
try {
|
||||
// TODO pass on the replicables to wait for
|
||||
serverReplicationMasterService.startToReplicateFrom(master);
|
||||
// TODO pass on the replicables to wait for; failure to do so can cause the symptoms of bug 3015
|
||||
serverReplicationMasterService.startToReplicateFrom(master, replicables);
|
||||
logger.info("Automatic replication has been started.");
|
||||
} catch (ClassNotFoundException | IOException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
|
||||
+11
-5
@@ -476,7 +476,14 @@ public class ReplicationServiceImpl implements ReplicationService {
|
||||
*/
|
||||
@Override
|
||||
public void startToReplicateFrom(ReplicationMasterDescriptor master) throws IOException, ClassNotFoundException, InterruptedException {
|
||||
logger.info("Starting to replicate from "+master);
|
||||
final Iterable<Replicable<?, ?>> replicables = getReplicables();
|
||||
startToReplicateFrom(master, replicables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startToReplicateFrom(ReplicationMasterDescriptor master, Iterable<Replicable<?, ?>> replicables)
|
||||
throws IOException, ClassNotFoundException, InterruptedException {
|
||||
logger.info("Starting to replicate from " + master);
|
||||
try {
|
||||
registerReplicaWithMaster(master);
|
||||
} catch (Exception ex) {
|
||||
@@ -497,14 +504,13 @@ public class ReplicationServiceImpl implements ReplicationService {
|
||||
throw ex;
|
||||
}
|
||||
logger.info("Connection to exchange successful.");
|
||||
final Iterable<Replicable<?, ?>> replicables = replicablesProvider.getReplicables();
|
||||
URL initialLoadURL = master.getInitialLoadURL(replicables);
|
||||
logger.info("Initial load URL is "+initialLoadURL);
|
||||
// start receiving messages already now, but start in suspended mode
|
||||
replicator = new ReplicationReceiver(master, replicablesProvider, /* startSuspended */true, consumer);
|
||||
// clear Replicable state here, before starting to receive and de-serialized operations which builds up
|
||||
// new state, e.g., in competitor store
|
||||
for (Replicable<?, ?> r : getReplicables()) {
|
||||
for (Replicable<?, ?> r : replicables) {
|
||||
r.clearReplicaState();
|
||||
r.startedReplicatingFrom(master);
|
||||
}
|
||||
@@ -517,7 +523,7 @@ public class ReplicationServiceImpl implements ReplicationService {
|
||||
RabbitInputStreamProvider rabbitInputStreamProvider = new RabbitInputStreamProvider(master.createChannel(), queueName);
|
||||
try {
|
||||
final GZIPInputStream gzipInputStream = new GZIPInputStream(rabbitInputStreamProvider.getInputStream());
|
||||
for (Replicable<?, ?> replicable : getReplicables()) {
|
||||
for (Replicable<?, ?> replicable : replicables) { // absolutely make sure to use the same sequence of replicables as for URL (bug 3015)
|
||||
logger.info("Starting to receive initial load for "+replicable.getId());
|
||||
try {
|
||||
replicable.initiallyFillFrom(gzipInputStream);
|
||||
@@ -527,9 +533,9 @@ public class ReplicationServiceImpl implements ReplicationService {
|
||||
}
|
||||
logger.info("Done receiving initial load for "+replicable.getId());
|
||||
}
|
||||
} finally {
|
||||
logger.info("Resuming replicator to apply queues");
|
||||
replicator.setSuspended(false); // apply queued operations
|
||||
} finally {
|
||||
// delete initial load queue
|
||||
DeleteOk deleteOk = consumer.getChannel().queueDelete(queueName);
|
||||
logger.info("Deleted queue " + queueName + " used for initial load: " + deleteOk.toString());
|
||||
|
||||
@@ -246,12 +246,16 @@ if [[ $OPERATION == "auto-install" ]]; then
|
||||
activate_user_data
|
||||
# then download and install environment and append to env.sh
|
||||
install_environment
|
||||
# finally, append user data to env.sh as it shall take precedence over the installed environment's defaults
|
||||
append_user_data_to_envsh
|
||||
# finally, append user data to env.sh as it shall take precedence over the installed environment's defaults
|
||||
append_user_data_to_envsh
|
||||
|
||||
# make sure to reload data
|
||||
source `pwd`/env.sh
|
||||
|
||||
# Append Apache macro invocation for /internal-server-status based on mod_status and INSTANCE_DNS to /etc/httpd/conf.d/001-events.conf
|
||||
APACHE_CONFIG_DIR=/etc/httpd/conf.d
|
||||
echo "Appending macro usage for /internal-server-status URL for mod_status based Apache monitoring to $APACHE_CONFIG_DIR/001-events.conf"
|
||||
echo "Use Status $INSTANCE_DNS internal-server-status" >>$APACHE_CONFIG_DIR/001-events.conf
|
||||
|
||||
if [[ $INSTALL_FROM_RELEASE == "" ]] && [[ $BUILD_BEFORE_START != "True" ]]; then
|
||||
echo "I could not find any option telling me to download a release or to build! Possible cause: Your environment contains empty values for these variables!"
|
||||
|
||||
@@ -40,13 +40,12 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/set_date_time"
|
||||
style="?attr/buttonSolidXSmall"
|
||||
style="?attr/buttonSolidSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/picker"
|
||||
android:layout_margin="@dimen/default_margin_twice"
|
||||
android:maxWidth="200dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="@dimen/default_margin_twice"
|
||||
android:text="@string/race_set_time"/>
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -331,7 +331,7 @@
|
||||
<string name="race_begin_finish_custom">Angegebene Zeit verwenden</string>
|
||||
<string name="wind_sensor"><![CDATA[um #AT# / von #FROM#° / #SPEED# kn]]></string>
|
||||
<string name="wind_no_data">Keine Daten</string>
|
||||
<string name="wind_panel"><![CDATA[von #FROM#°\n#SPEED# kn]]></string>
|
||||
<string name="wind_panel"><![CDATA[von %1$.0f°\n%2$.1f kn]]></string>
|
||||
<string name="settings_category_developer">Entwickler Optionen</string>
|
||||
<string name="preference_offline_header">Offline Modus</string>
|
||||
<string name="preference_offline_summary">Deaktiviert die Verbindung zum Server und benutzt statische Daten</string>
|
||||
@@ -405,7 +405,7 @@
|
||||
<string name="theme_changed_message">Die Änderung am Theme wird nach einem Neustart der App wirksam.</string>
|
||||
<string name="url_refresh_title">Die Server URL wurde geändert</string>
|
||||
<string name="url_refresh_message">Bitte melden Sie sich vom aktuellen Event ab, um die Daten des neuen Servers zu laden.</string>
|
||||
<string name="until_xray_removed">XRAY wird eingeholt in #TIME#</string>
|
||||
<string name="until_xray_removed">XRAY wird eingeholt in %s</string>
|
||||
<string name="send_photos">Fotos senden</string>
|
||||
<string name="error_wrong_degree_value_title">Fehler</string>
|
||||
<string name="error_wrong_degree_value_message">Bitte geben Sie einen Wert zwischen 0 und 360 an.</string>
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
<string name="wind_kn">kn</string>
|
||||
<string name="wind_address_hint">Enter Address</string>
|
||||
<string name="wind_sensor"><![CDATA[at #AT# / from #FROM#° / #SPEED# kn]]></string>
|
||||
<string name="wind_panel"><![CDATA[from #FROM#°\n#SPEED# kn]]></string>
|
||||
<string name="wind_panel"><![CDATA[from %1$.0f°\n%2$.1f kn]]></string>
|
||||
<string name="wind_no_data">No Data</string>
|
||||
|
||||
<string name="time_up">Up in #TIME#</string>
|
||||
@@ -426,7 +426,7 @@
|
||||
<string name="theme_changed_message">Changes to the theme will take action after an app restart.</string>
|
||||
<string name="url_refresh_title">Server URL Changed</string>
|
||||
<string name="url_refresh_message">Please logout from the current event to load the data from the new server.</string>
|
||||
<string name="until_xray_removed">XRAY will be removed in #TIME#</string>
|
||||
<string name="until_xray_removed">XRAY will be removed in %s</string>
|
||||
<string name="send_photos">Send Photos</string>
|
||||
<string name="error_wrong_degree_value_title">Error</string>
|
||||
<string name="error_wrong_degree_value_message">Please provide a value between 0 and 360</string>
|
||||
|
||||
+9
-2
@@ -1,5 +1,6 @@
|
||||
package com.sap.sailing.racecommittee.app.services;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
@@ -8,6 +9,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Pair;
|
||||
@@ -259,11 +261,16 @@ public class RaceStateService extends Service {
|
||||
intent.putExtra(AppConstants.EXTRAS_RACE_STATE_EVENT, event);
|
||||
return PendingIntent.getService(this, alarmManagerRequestCode++, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||
public void setAlarm(ManagedRace race, RaceStateEvent event) {
|
||||
PendingIntent intent = createAlarmPendingIntent(race, event);
|
||||
managedIntents.get(race.getId()).add(Pair.create(intent, event.getEventName()));
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, event.getTimePoint().asMillis(), intent);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
alarmManager.setExact(AlarmManager.RTC_WAKEUP, event.getTimePoint().asMillis(), intent);
|
||||
} else {
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, event.getTimePoint().asMillis(), intent);
|
||||
}
|
||||
ExLog.i(this, TAG, "The alarm " + event.getEventName() + " will be fired at " + event.getTimePoint());
|
||||
}
|
||||
|
||||
|
||||
+86
-78
@@ -1,15 +1,10 @@
|
||||
package com.sap.sailing.racecommittee.app.ui.adapters.racelist;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -19,10 +14,11 @@ import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sap.sailing.android.shared.logging.ExLog;
|
||||
import com.sap.sailing.android.shared.util.ViewHolder;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.RaceState;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure;
|
||||
import com.sap.sailing.domain.common.racelog.FlagPole;
|
||||
import com.sap.sailing.domain.common.racelog.Flags;
|
||||
import com.sap.sailing.racecommittee.app.AppConstants;
|
||||
@@ -32,11 +28,17 @@ import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceFilter.FilterS
|
||||
import com.sap.sailing.racecommittee.app.ui.utils.FlagsResources;
|
||||
import com.sap.sailing.racecommittee.app.utils.BitmapHelper;
|
||||
import com.sap.sailing.racecommittee.app.utils.ThemeHelper;
|
||||
import com.sap.sailing.racecommittee.app.utils.TimeUtils;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
|
||||
public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> implements FilterSubscriber {
|
||||
|
||||
private final static String TAG = ManagedRaceListAdapter.class.getName();
|
||||
private final static int FLAG_SIZE = 48;
|
||||
private final Object mLockObject = new Object();
|
||||
private List<RaceListDataType> mAllViewItems;
|
||||
private RaceFilter mFilter;
|
||||
@@ -71,15 +73,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
dateFormat = new SimpleDateFormat("HH:mm", getContext().getResources().getConfiguration().locale);
|
||||
}
|
||||
|
||||
public String fill2(int value) {
|
||||
String erg = String.valueOf(value);
|
||||
|
||||
if (erg.length() < 2) {
|
||||
erg = "0" + erg;
|
||||
}
|
||||
return erg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
synchronized (mLockObject) {
|
||||
@@ -87,46 +80,6 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
}
|
||||
}
|
||||
|
||||
public String getDuration(Date date1, Date date2) {
|
||||
TimeUnit timeUnit = TimeUnit.SECONDS;
|
||||
|
||||
long diffInMilli = date2.getTime() - date1.getTime();
|
||||
long s = timeUnit.convert(diffInMilli, TimeUnit.MILLISECONDS);
|
||||
|
||||
long days = s / (24 * 60 * 60);
|
||||
long rest = s - (days * 24 * 60 * 60);
|
||||
long hrs = rest / (60 * 60);
|
||||
long rest1 = rest - (hrs * 60 * 60);
|
||||
long min = rest1 / 60;
|
||||
long sec = s % 60;
|
||||
|
||||
String dates = "";
|
||||
if (days < 0 || hrs < 0 || min < 0 || sec < 0) {
|
||||
dates += "-";
|
||||
if (days < 0) {
|
||||
days *= -1;
|
||||
}
|
||||
if (hrs < 0) {
|
||||
hrs *= -1;
|
||||
}
|
||||
if (min < 0) {
|
||||
min *= -1;
|
||||
}
|
||||
if (sec < 0) {
|
||||
sec *= -1;
|
||||
}
|
||||
}
|
||||
if (days != 0) {
|
||||
dates = days + ":";
|
||||
}
|
||||
|
||||
dates += (days != 0 || hrs != 0) ? fill2((int) hrs) + ":" : "";
|
||||
dates += fill2((int) min) + ":";
|
||||
dates += fill2((int) sec);
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceFilter getFilter() {
|
||||
if (mFilter == null) {
|
||||
@@ -157,6 +110,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
raceListElement = getItem(position);
|
||||
|
||||
int type = getItemViewType(position);
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
|
||||
if (convertView == null) {
|
||||
if (type == ViewType.HEADER.index) {
|
||||
@@ -186,7 +140,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
fleetSeries += header.getSeries().getName();
|
||||
}
|
||||
fleet_series.setText(fleetSeries);
|
||||
protest_image.setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), 48));
|
||||
protest_image.setImageDrawable(FlagsResources.getFlagDrawable(getContext(), Flags.BRAVO.name(), FLAG_SIZE));
|
||||
protest_image.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -220,7 +174,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
String startTime = mResources.getString(startRes, dateFormat.format(state.getStartTime().asDate()));
|
||||
race_started.setText(startTime);
|
||||
if (state.getFinishedTime() == null) {
|
||||
String duration = getDuration(state.getStartTime().asDate(), Calendar.getInstance().getTime());
|
||||
String duration = TimeUtils.formatDuration(now, state.getStartTime());
|
||||
time.setText(duration);
|
||||
float textSize = getContext().getResources().getDimension(R.dimen.textSize_40);
|
||||
if (!TextUtils.isEmpty(duration) && duration.length() >= 6) {
|
||||
@@ -248,7 +202,7 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
update_badge.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
updateFlag(race.getRace());
|
||||
updateFlag(race.getRace(), now);
|
||||
}
|
||||
return convertView;
|
||||
}
|
||||
@@ -323,32 +277,86 @@ public class ManagedRaceListAdapter extends ArrayAdapter<RaceListDataType> imple
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFlag(ManagedRace race) {
|
||||
private void updateFlag(ManagedRace race, TimePoint now) {
|
||||
RaceState state = race.getState();
|
||||
if (state == null || state.getStartTime() == null) {
|
||||
return;
|
||||
}
|
||||
FlagPoleState flagPoleState = state.getTypedRacingProcedure()
|
||||
.getActiveFlags(state.getStartTime(), MillisecondsTimePoint.now());
|
||||
List<FlagPole> flagChanges = flagPoleState.computeUpcomingChanges();
|
||||
if (!flagChanges.isEmpty()) {
|
||||
TimePoint changeAt = flagPoleState.getNextStateValidFrom();
|
||||
FlagPole changePole = FlagPoleState.getMostInterestingFlagPole(flagChanges);
|
||||
|
||||
if (changeAt != null) {
|
||||
current_flag.setImageDrawable(
|
||||
FlagsResources.getFlagDrawable(getContext(), changePole.getUpperFlag().name(), 48));
|
||||
String text = getDuration(changeAt.asDate(), Calendar.getInstance().getTime());
|
||||
flag_timer.setText(text.replace("-", ""));
|
||||
Drawable arrow;
|
||||
if (changePole.isDisplayed()) {
|
||||
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up);
|
||||
} else {
|
||||
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down);
|
||||
RacingProcedure procedure = state.getTypedRacingProcedure();
|
||||
LayerDrawable flag = null;
|
||||
Drawable arrow = null;
|
||||
String timer = null;
|
||||
if (!procedure.isIndividualRecallDisplayed()) {
|
||||
FlagPoleState poleState = state.getRacingProcedure().getActiveFlags(state.getStartTime(), now);
|
||||
List<FlagPole> currentState = poleState.getCurrentState();
|
||||
List<FlagPole> upcoming = poleState.computeUpcomingChanges();
|
||||
FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming);
|
||||
TimePoint change = poleState.getNextStateValidFrom();
|
||||
Flags currentFlag;
|
||||
|
||||
if (change != null) {
|
||||
for (FlagPole pole : currentState) {
|
||||
int isNext = 0;
|
||||
|
||||
currentFlag = pole.getUpperFlag();
|
||||
if (isNextFlag(currentFlag, nextPole)) {
|
||||
isNext = 1;
|
||||
} else {
|
||||
currentFlag = pole.getLowerFlag();
|
||||
if (!Flags.NONE.equals(currentFlag)) {
|
||||
if (isNextFlag(currentFlag, nextPole)) {
|
||||
isNext = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNext != 0) {
|
||||
flag = FlagsResources.getFlagDrawable(getContext(), currentFlag.name(), FLAG_SIZE);
|
||||
switch (isNext) {
|
||||
case 1:
|
||||
if (nextPole.isDisplayed()) {
|
||||
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up);
|
||||
} else {
|
||||
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down);
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_up);
|
||||
break;
|
||||
|
||||
default:
|
||||
ExLog.i(getContext(), TAG, "unknown flag");
|
||||
}
|
||||
timer = TimeUtils.formatDuration(now, poleState.getNextStateValidFrom());
|
||||
}
|
||||
}
|
||||
arrow_direction.setImageDrawable(arrow);
|
||||
race_flag.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
TimePoint flagDown = procedure.getIndividualRecallRemovalTime();
|
||||
if (now.before(flagDown)) {
|
||||
flag = FlagsResources.getFlagDrawable(getContext(), Flags.XRAY.name(), FLAG_SIZE);
|
||||
arrow = BitmapHelper.getAttrDrawable(getContext(), R.attr.arrow_down);
|
||||
timer = TimeUtils.formatDuration(now, flagDown);
|
||||
}
|
||||
}
|
||||
if (timer != null) {
|
||||
timer = timer.replace("-", "");
|
||||
}
|
||||
showFlag(flag, arrow, timer);
|
||||
}
|
||||
|
||||
private boolean isNextFlag(Flags flag, FlagPole pole) {
|
||||
return pole != null && flag.equals(pole.getUpperFlag());
|
||||
}
|
||||
|
||||
private void showFlag(LayerDrawable flag, Drawable arrow, String timer) {
|
||||
if (flag != null && arrow != null && timer != null) {
|
||||
current_flag.setImageDrawable(flag);
|
||||
arrow_direction.setImageDrawable(arrow);
|
||||
flag_timer.setText(timer);
|
||||
race_flag.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-9
@@ -1,5 +1,13 @@
|
||||
package com.sap.sailing.racecommittee.app.ui.fragments;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
@@ -20,10 +28,16 @@ import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.*;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.AbsListView.OnScrollListener;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sap.sailing.android.shared.logging.ExLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener;
|
||||
@@ -41,16 +55,16 @@ import com.sap.sailing.racecommittee.app.ui.adapters.racelist.ManagedRaceListAda
|
||||
import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataType;
|
||||
import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeHeader;
|
||||
import com.sap.sailing.racecommittee.app.ui.adapters.racelist.RaceListDataTypeRace;
|
||||
import com.sap.sailing.racecommittee.app.ui.comparators.NaturalNamedComparator;
|
||||
import com.sap.sailing.racecommittee.app.ui.comparators.RaceListDataTypeComparator;
|
||||
import com.sap.sailing.racecommittee.app.ui.comparators.RegattaSeriesFleetComparator;
|
||||
import com.sap.sailing.racecommittee.app.ui.fragments.dialogs.ProtestTimeDialogFragment;
|
||||
import com.sap.sailing.racecommittee.app.utils.*;
|
||||
import com.sap.sailing.racecommittee.app.utils.BitmapHelper;
|
||||
import com.sap.sailing.racecommittee.app.utils.StringHelper;
|
||||
import com.sap.sailing.racecommittee.app.utils.ThemeHelper;
|
||||
import com.sap.sailing.racecommittee.app.utils.TickListener;
|
||||
import com.sap.sailing.racecommittee.app.utils.TickSingleton;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
public class RaceListFragment
|
||||
extends LoggableFragment
|
||||
implements OnItemClickListener, OnItemSelectedListener, TickListener, OnScrollListener {
|
||||
@@ -67,7 +81,7 @@ public class RaceListFragment
|
||||
private ActionBarDrawerToggle mDrawerToggle;
|
||||
private FilterMode mFilterMode;
|
||||
private ListView mListView;
|
||||
private HashMap<Serializable, ManagedRace> mManagedRacesById;
|
||||
private LinkedHashMap<String, ManagedRace> mManagedRacesById;
|
||||
private TreeMap<RaceGroupSeriesFleet, List<ManagedRace>> mRacesByGroup;
|
||||
private ManagedRace mSelectedRace;
|
||||
private IntentReceiver mReceiver;
|
||||
@@ -93,7 +107,7 @@ public class RaceListFragment
|
||||
public RaceListFragment() {
|
||||
mFilterMode = FilterMode.ACTIVE;
|
||||
mSelectedRace = null;
|
||||
mManagedRacesById = new HashMap<>();
|
||||
mManagedRacesById = new LinkedHashMap<>();
|
||||
mRacesByGroup = new TreeMap<>(new RegattaSeriesFleetComparator());
|
||||
mViewItems = new ArrayList<>();
|
||||
}
|
||||
@@ -420,7 +434,6 @@ public class RaceListFragment
|
||||
mViewItems.add(new RaceListDataTypeHeader(key));
|
||||
|
||||
List<ManagedRace> races = mRacesByGroup.get(key);
|
||||
Collections.sort(races, new NaturalNamedComparator());
|
||||
for (ManagedRace race : races) {
|
||||
// ... and add the race view!
|
||||
mViewItems.add(new RaceListDataTypeRace(race));
|
||||
|
||||
-2
@@ -87,9 +87,7 @@ public class ByNameCourseDesignDialog extends RaceDialogFragment {
|
||||
protected void onCourseSelectionButtonClicked(View v) {
|
||||
Button selectedButton = (Button) v;
|
||||
String internalCourseName = (String) selectedButton.getText();
|
||||
|
||||
CourseBase courseLayout = new CourseDataImpl(internalCourseName);
|
||||
|
||||
getRaceState().setCourseDesign(MillisecondsTimePoint.now(), courseLayout);
|
||||
dismiss();
|
||||
}
|
||||
|
||||
+1
-3
@@ -154,9 +154,7 @@ public class SetupPanelFragment extends BasePanelFragment {
|
||||
|
||||
Wind wind = getRaceState().getWindFix();
|
||||
if (mWindValue != null && wind != null) {
|
||||
String sensorData = getString(R.string.wind_panel);
|
||||
sensorData = sensorData.replace("#FROM#", String.format("%.0f", wind.getFrom().getDegrees()));
|
||||
sensorData = sensorData.replace("#SPEED#", String.format("%.1f", wind.getKnots()));
|
||||
String sensorData = getString(R.string.wind_panel, wind.getFrom().getDegrees(), wind.getKnots());
|
||||
mWindValue.setText(sensorData);
|
||||
}
|
||||
}
|
||||
|
||||
+99
-44
@@ -9,6 +9,8 @@ import android.widget.*;
|
||||
import com.sap.sailing.android.shared.util.ViewHolder;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure;
|
||||
import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.BaseRacingProcedureChangedListener;
|
||||
import com.sap.sailing.domain.common.racelog.FlagPole;
|
||||
import com.sap.sailing.domain.common.racelog.Flags;
|
||||
import com.sap.sailing.racecommittee.app.R;
|
||||
@@ -32,8 +34,12 @@ public class RaceFlagViewerFragment extends BaseFragment {
|
||||
private TextView mXrayCountdown;
|
||||
private Button mXrayButton;
|
||||
|
||||
public RaceFlagViewerFragment() {
|
||||
private ProcedureChangedListener mProcedureListener;
|
||||
private FlagsCache mFlagCache;
|
||||
|
||||
public RaceFlagViewerFragment() {
|
||||
mProcedureListener = new ProcedureChangedListener();
|
||||
mFlagCache = null;
|
||||
}
|
||||
|
||||
public static RaceFlagViewerFragment newInstance() {
|
||||
@@ -69,6 +75,20 @@ public class RaceFlagViewerFragment extends BaseFragment {
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
getRaceState().getRacingProcedure().addChangedListener(mProcedureListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
getRaceState().getRacingProcedure().removeChangedListener(mProcedureListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTick(TimePoint now) {
|
||||
super.notifyTick(now);
|
||||
@@ -76,7 +96,7 @@ public class RaceFlagViewerFragment extends BaseFragment {
|
||||
updateFlags(now);
|
||||
}
|
||||
|
||||
private void updateFlags(TimePoint timePoint) {
|
||||
private void updateFlags(TimePoint now) {
|
||||
if (getRace() == null || getRaceState() == null || getRaceState().getStartTime() == null) {
|
||||
return;
|
||||
}
|
||||
@@ -87,65 +107,84 @@ public class RaceFlagViewerFragment extends BaseFragment {
|
||||
if (mRecall != null) {
|
||||
mRecall.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
RacingProcedure procedure = getRaceState().getTypedRacingProcedure();
|
||||
if (!procedure.isIndividualRecallDisplayed()) {
|
||||
if (mLayout != null) {
|
||||
mLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
FlagPoleState poleState = getRaceState().getRacingProcedure().getActiveFlags(getRaceState().getStartTime(), timePoint);
|
||||
List<FlagPole> currentState = poleState.getCurrentState();
|
||||
List<FlagPole> upcoming = poleState.computeUpcomingChanges();
|
||||
FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming);
|
||||
mLayout.removeAllViews();
|
||||
int size = 0;
|
||||
Flags flag;
|
||||
for (FlagPole flagPole : currentState) {
|
||||
size++;
|
||||
flag = flagPole.getUpperFlag();
|
||||
mLayout.addView(renderFlag(timePoint, poleState, flag, isNextFlag(flag, nextPole), currentState.size() == size, flagPole.isDisplayed(), UPPER_FLAG));
|
||||
if (!flagPole.getLowerFlag().equals(Flags.NONE)) {
|
||||
flag = flagPole.getLowerFlag();
|
||||
mLayout.addView(renderFlag(timePoint, poleState, flag, isNextFlag(flag, nextPole), currentState.size() == size, false, LOWER_FLAG));
|
||||
if (mFlagCache == null) {
|
||||
mLayout.removeAllViews();
|
||||
FlagPoleState poleState = getRaceState().getRacingProcedure().getActiveFlags(getRaceState().getStartTime(), now);
|
||||
List<FlagPole> currentState = poleState.getCurrentState();
|
||||
List<FlagPole> upcoming = poleState.computeUpcomingChanges();
|
||||
FlagPole nextPole = FlagPoleState.getMostInterestingFlagPole(upcoming);
|
||||
mFlagCache = new FlagsCache(poleState.getNextStateValidFrom(), nextPole);
|
||||
int size = 0;
|
||||
Flags flag;
|
||||
for (FlagPole flagPole : currentState) {
|
||||
size++;
|
||||
flag = flagPole.getUpperFlag();
|
||||
mLayout.addView(createFlagView(now, poleState, flag, isNextFlag(nextPole, flag), currentState.size() == size, flagPole.isDisplayed(), UPPER_FLAG));
|
||||
if (!flagPole.getLowerFlag().equals(Flags.NONE)) {
|
||||
flag = flagPole.getLowerFlag();
|
||||
mLayout.addView(createFlagView(now, poleState, flag, isNextFlag(nextPole, flag), currentState.size() == size, false, LOWER_FLAG));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mLayout != null && mFlagCache.nextPole != null) {
|
||||
for (int i = 0; i < mLayout.getChildCount(); i++) {
|
||||
ImageView flagImage = ViewHolder.get(mLayout.getChildAt(i), R.id.flag);
|
||||
Flags flags = (Flags) flagImage.getTag();
|
||||
if (isNextFlag(mFlagCache.nextPole, flags)) {
|
||||
updateFlagView(now, mLayout.getChildAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String time = getString(R.string.until_xray_removed);
|
||||
if (mXrayCountdown != null) {
|
||||
if (mXrayCountdown != null && mRecall != null) {
|
||||
TimePoint flagDown = procedure.getIndividualRecallRemovalTime();
|
||||
if (timePoint.before(flagDown)) {
|
||||
time = time.replace("#TIME#", TimeUtils.formatDurationUntil(flagDown.minus(timePoint.asMillis()).asMillis()).replace("-", ""));
|
||||
mXrayCountdown.setText(time);
|
||||
} else {
|
||||
procedure.removeIndividualRecall(timePoint);
|
||||
if (now.before(flagDown)) {
|
||||
mRecall.setVisibility(View.VISIBLE);
|
||||
mXrayCountdown.setText(getString(R.string.until_xray_removed, TimeUtils.formatDuration(now, flagDown, false)));
|
||||
}
|
||||
}
|
||||
|
||||
if (mRecall != null) {
|
||||
mRecall.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNextFlag(Flags flag, FlagPole pole) {
|
||||
return pole != null && flag.equals(pole.getUpperFlag());
|
||||
private boolean isNextFlag(FlagPole flagPole, Flags flags) {
|
||||
return flagPole != null && flags.equals(flagPole.getUpperFlag());
|
||||
}
|
||||
|
||||
private RelativeLayout renderFlag(TimePoint timePoint, FlagPoleState poleState, final Flags flag, boolean isNext, boolean lastEntry, boolean isDisplayed, int flagType) {
|
||||
private View updateFlagView(TimePoint now, View flagView) {
|
||||
TimePoint change = mFlagCache.mChange;
|
||||
if (change != null) {
|
||||
TextView flag_text = ViewHolder.get(flagView, R.id.flag_text);
|
||||
String timer = TimeUtils.formatDuration(now, change, false);
|
||||
flag_text.setText(timer);
|
||||
}
|
||||
return flagView;
|
||||
}
|
||||
|
||||
private RelativeLayout createFlagView(TimePoint now, FlagPoleState poleState, final Flags flag, boolean isNext, boolean lastEntry,
|
||||
boolean isDisplayed, int flagType) {
|
||||
RelativeLayout layout = (RelativeLayout) getActivity().getLayoutInflater().inflate(R.layout.race_flag, mLayout, false);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);
|
||||
layout.setLayoutParams(layoutParams);
|
||||
|
||||
TimePoint changeAt = poleState.getNextStateValidFrom();
|
||||
ImageView flagView = (ImageView) layout.findViewById(R.id.flag);
|
||||
TextView textView = (TextView) layout.findViewById(R.id.flag_text);
|
||||
View downView = layout.findViewById(R.id.arrow_down);
|
||||
View upView = layout.findViewById(R.id.arrow_up);
|
||||
View line = layout.findViewById(R.id.line);
|
||||
ImageView flagView = ViewHolder.get(layout, R.id.flag);
|
||||
TextView textView = ViewHolder.get(layout, R.id.flag_text);
|
||||
View downView = ViewHolder.get(layout, R.id.arrow_down);
|
||||
View upView = ViewHolder.get(layout, R.id.arrow_up);
|
||||
View line = ViewHolder.get(layout, R.id.line);
|
||||
if (lastEntry) {
|
||||
line.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
flagView.setImageDrawable(FlagsResources.getFlagDrawable(getActivity(), flag.name(), 96));
|
||||
flagView.setTag(flag);
|
||||
if (flag == Flags.CLASS && getRace().getFleet().getColor() != null) {
|
||||
flagView.setBackgroundColor(getFleetColorId());
|
||||
}
|
||||
@@ -159,12 +198,7 @@ public class RaceFlagViewerFragment extends BaseFragment {
|
||||
textView.setText("");
|
||||
if (changeAt != null && isNext) {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
String timer;
|
||||
if (changeAt.after(timePoint)) {
|
||||
timer = TimeUtils.formatDurationUntil(changeAt.minus(timePoint.asMillis()).asMillis());
|
||||
} else {
|
||||
timer = TimeUtils.formatDurationSince(timePoint.minus(changeAt.asMillis()).asMillis());
|
||||
}
|
||||
String timer = TimeUtils.formatDuration(now, changeAt);
|
||||
textView.setText(timer.replace("-", ""));
|
||||
if (flagType == UPPER_FLAG) {
|
||||
if (isDisplayed) {
|
||||
@@ -192,7 +226,28 @@ public class RaceFlagViewerFragment extends BaseFragment {
|
||||
}
|
||||
|
||||
private int getFleetColorId() {
|
||||
Util.Triple<Integer, Integer, Integer> rgb = getRace().getFleet().getColor() == null ? new Util.Triple<>(0, 0, 0) : getRace().getFleet().getColor().getAsRGB();
|
||||
Util.Triple<Integer, Integer, Integer> rgb =
|
||||
getRace().getFleet().getColor() == null ? new Util.Triple<>(0, 0, 0) : getRace().getFleet().getColor().getAsRGB();
|
||||
return Color.rgb(rgb.getA(), rgb.getB(), rgb.getC());
|
||||
}
|
||||
|
||||
private class ProcedureChangedListener extends BaseRacingProcedureChangedListener {
|
||||
|
||||
@Override
|
||||
public void onActiveFlagsChanged(ReadonlyRacingProcedure racingProcedure) {
|
||||
super.onActiveFlagsChanged(racingProcedure);
|
||||
|
||||
mFlagCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class FlagsCache {
|
||||
private TimePoint mChange;
|
||||
public FlagPole nextPole;
|
||||
|
||||
public FlagsCache(TimePoint change, FlagPole pole) {
|
||||
mChange = change;
|
||||
nextPole = pole;
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
-5
@@ -26,13 +26,16 @@ public class RaceTimeChangeFragment extends BaseFragment implements View.OnClick
|
||||
public final static int FINISHING_TIME_MODE = 1;
|
||||
public final static int FINISHED_TIME_MODE = 2;
|
||||
private final static String START_MODE = "startMode";
|
||||
private static final int FUTURE_DAYS = 3;
|
||||
private static final int PAST_DAYS = -3;
|
||||
private static final int FUTURE_DAYS_DEFAULT = 3;
|
||||
private static final int PAST_DAYS_DEFAULT = -3;
|
||||
|
||||
private NumberPicker mDatePicker;
|
||||
private TimePicker mTimePicker;
|
||||
private NumberPicker mSecondPicker;
|
||||
|
||||
private int mFutureDays = FUTURE_DAYS_DEFAULT;
|
||||
private int mPastDays = PAST_DAYS_DEFAULT;
|
||||
|
||||
public static RaceTimeChangeFragment newInstance(int mode) {
|
||||
RaceTimeChangeFragment fragment = new RaceTimeChangeFragment();
|
||||
Bundle args = new Bundle();
|
||||
@@ -81,10 +84,37 @@ public class RaceTimeChangeFragment extends BaseFragment implements View.OnClick
|
||||
|
||||
mDatePicker = (NumberPicker) layout.findViewById(R.id.date_picker);
|
||||
if (mDatePicker != null) {
|
||||
Calendar start = (Calendar) calendar.clone();
|
||||
Calendar finishing = (Calendar) calendar.clone();
|
||||
Calendar finished = (Calendar) calendar.clone();
|
||||
switch (getArguments().getInt(START_MODE, 0)) {
|
||||
case START_TIME_MODE:
|
||||
finishing.setTime(getRaceState().getFinishingTime().asDate());
|
||||
mFutureDays = TimeUtils.daysBetween(finishing, calendar);
|
||||
break;
|
||||
|
||||
case FINISHING_TIME_MODE:
|
||||
start.setTime(getRaceState().getStartTime().asDate());
|
||||
mPastDays = TimeUtils.daysBetween(calendar, start);
|
||||
finished.setTime(getRaceState().getFinishedTime().asDate());
|
||||
mFutureDays = TimeUtils.daysBetween(finished, calendar);
|
||||
break;
|
||||
|
||||
case FINISHED_TIME_MODE:
|
||||
finishing.setTime(getRaceState().getFinishingTime().asDate());
|
||||
mPastDays = TimeUtils.daysBetween(finishing, calendar);
|
||||
break;
|
||||
|
||||
default:
|
||||
// use default values
|
||||
}
|
||||
ThemeHelper.setPickerTextColor(getActivity(), mDatePicker, ThemeHelper.getColor(getActivity(), R.attr.white));
|
||||
TimeUtils.initDatePicker(getActivity(), mDatePicker, calendar, PAST_DAYS, FUTURE_DAYS, false);
|
||||
mDatePicker.setValue(Math.abs(PAST_DAYS));
|
||||
TimeUtils.initDatePicker(getActivity(), mDatePicker, calendar, mPastDays, mFutureDays, false);
|
||||
mDatePicker.setValue(Math.abs(mPastDays));
|
||||
mDatePicker.setTag(calendar);
|
||||
if (mDatePicker.getMinValue() == mDatePicker.getMaxValue()) {
|
||||
mDatePicker.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
mTimePicker = (TimePicker) layout.findViewById(R.id.time_picker);
|
||||
@@ -174,7 +204,7 @@ public class RaceTimeChangeFragment extends BaseFragment implements View.OnClick
|
||||
|
||||
private TimePoint getPickerTime() {
|
||||
Calendar calendar = (Calendar) mDatePicker.getTag();
|
||||
calendar.add(Calendar.DAY_OF_MONTH, mDatePicker.getValue() + PAST_DAYS);
|
||||
calendar.add(Calendar.DAY_OF_MONTH, mDatePicker.getValue() + mPastDays);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, mTimePicker.getCurrentHour());
|
||||
calendar.set(Calendar.MINUTE, mTimePicker.getCurrentMinute());
|
||||
calendar.set(Calendar.SECOND, mSecondPicker.getValue());
|
||||
|
||||
+4
-1
@@ -86,6 +86,9 @@ public class CompassView extends RelativeLayout {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
}
|
||||
|
||||
private void generateAndShowAlert() {
|
||||
@@ -166,7 +169,7 @@ public class CompassView extends RelativeLayout {
|
||||
if (Math.abs(degree) == 360) {
|
||||
degree = 0;
|
||||
}
|
||||
degreeView.clearFocus();
|
||||
requestFocus();
|
||||
changeListener.onDirectionChanged(degree);
|
||||
}
|
||||
}
|
||||
|
||||
+189
-165
@@ -1,166 +1,190 @@
|
||||
package com.sap.sailing.racecommittee.app.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.View;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.TextView;
|
||||
import com.sap.sailing.racecommittee.app.R;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
private TimeUtils() {
|
||||
// only static methods
|
||||
}
|
||||
|
||||
public static long timeUntil(TimePoint targetTime) {
|
||||
return targetTime.asMillis() - MillisecondsTimePoint.now().asMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats your time to 'kk:mm:ss'.
|
||||
*
|
||||
* @param timePoint timestamp to format
|
||||
*/
|
||||
public static String formatTime(TimePoint timePoint) {
|
||||
return formatTime(timePoint, "kk:mm:ss");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats your time with the help of {@link DateFormat}.
|
||||
*
|
||||
* @param timePoint timestamp to format
|
||||
* @param format format as defined by {@link DateFormat}
|
||||
* @return timestamp formatted as {@link String}
|
||||
*/
|
||||
public static String formatTime(TimePoint timePoint, String format) {
|
||||
return DateFormat.format(format, timePoint.asDate()).toString();
|
||||
}
|
||||
|
||||
public static String formatDurationSince(long milliseconds) {
|
||||
int secondsTillStart = (int) Math.floor(milliseconds / 1000f);
|
||||
return formatDuration(secondsTillStart);
|
||||
}
|
||||
|
||||
public static String formatDurationUntil(long milliseconds) {
|
||||
int secondsTillStart = (int) Math.ceil(milliseconds / 1000f);
|
||||
return formatDuration(secondsTillStart);
|
||||
}
|
||||
|
||||
public static String calcDuration(Calendar from, Calendar to) {
|
||||
String retValue;
|
||||
|
||||
long millis = to.getTimeInMillis() - from.getTimeInMillis();
|
||||
|
||||
long min = millis / (1000 * 60);
|
||||
long sec = (millis - (min * 60 * 1000)) / 1000;
|
||||
|
||||
retValue = String.valueOf(sec) + "\"";
|
||||
if (retValue.length() == 2) {
|
||||
retValue = "0" + retValue;
|
||||
}
|
||||
if (min > 0) {
|
||||
retValue = String.valueOf(min) + "' " + retValue;
|
||||
}
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
public static Calendar floorTime(Calendar calendar) {
|
||||
if (calendar == null) {
|
||||
calendar = Calendar.getInstance();
|
||||
}
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
public static int daysBetween(Calendar day1, Calendar day2) {
|
||||
Calendar dayOne = (Calendar) day1.clone();
|
||||
Calendar dayTwo = (Calendar) day2.clone();
|
||||
|
||||
if (dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR)) {
|
||||
return dayOne.get(Calendar.DAY_OF_YEAR) - dayTwo.get(Calendar.DAY_OF_YEAR);
|
||||
} else {
|
||||
if (dayTwo.get(Calendar.YEAR) > dayOne.get(Calendar.YEAR)) {
|
||||
//swap them
|
||||
Calendar temp = dayOne;
|
||||
dayOne = dayTwo;
|
||||
dayTwo = temp;
|
||||
}
|
||||
int extraDays = 0;
|
||||
|
||||
while (dayOne.get(Calendar.YEAR) > dayTwo.get(Calendar.YEAR)) {
|
||||
dayOne.add(Calendar.YEAR, -1);
|
||||
// getActualMaximum() important for leap years
|
||||
extraDays += dayOne.getActualMaximum(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
|
||||
return extraDays - dayTwo.get(Calendar.DAY_OF_YEAR) + dayOne.get(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDuration(int secondsTillStart) {
|
||||
int hours = secondsTillStart / 3600;
|
||||
int minutes = (secondsTillStart % 3600) / 60;
|
||||
int seconds = (secondsTillStart % 60);
|
||||
boolean negative = (hours < 0 || minutes < 0 || seconds < 0);
|
||||
String timePattern = ((negative) ? "-" : "") + "%s:%s:%s";
|
||||
String secondsString = seconds < 10 ? "0" + Math.abs(seconds) : "" + Math.abs(seconds);
|
||||
String minutesString = minutes < 10 ? "0" + Math.abs(minutes) : "" + Math.abs(minutes);
|
||||
String hoursString = hours < 10 ? "0" + Math.abs(hours) : "" + Math.abs(hours);
|
||||
return String.format(timePattern, hoursString, minutesString, secondsString);
|
||||
}
|
||||
|
||||
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays) {
|
||||
initDatePicker(context, datePicker, time, pastDays, futureDays, true);
|
||||
}
|
||||
|
||||
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays, boolean useWords) {
|
||||
java.text.DateFormat dateFormat = java.text.DateFormat.getDateInstance();
|
||||
ArrayList<String> dates = new ArrayList<>();
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(time.getTime());
|
||||
// Past
|
||||
calendar.add(Calendar.DAY_OF_MONTH, pastDays);
|
||||
for (int i = pastDays; i < -1; i++) {
|
||||
dates.add(dateFormat.format(calendar.getTime()));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
if (useWords) {
|
||||
dates.add(context.getString(R.string.yesterday));
|
||||
dates.add(context.getString(R.string.today));
|
||||
dates.add(context.getString(R.string.tomorrow));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 3);
|
||||
} else {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
dates.add(dateFormat.format(calendar.getTime()));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
}
|
||||
// Future
|
||||
for (int i = 3; i <= futureDays + 1; i++) {
|
||||
dates.add(dateFormat.format(calendar.getTime()));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
datePicker.setDisplayedValues(dates.toArray(new String[dates.size()]));
|
||||
datePicker.setMinValue(0);
|
||||
datePicker.setMaxValue(dates.size() - 1);
|
||||
datePicker.setWrapSelectorWheel(false);
|
||||
datePicker.setValue(TimeUtils.daysBetween(calendar, Calendar.getInstance()) + Math.abs(pastDays));
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
TextView date = new TextView(context);
|
||||
date.setText(dateFormat.format(calendar.getTime()));
|
||||
int padding = context.getResources().getDimensionPixelSize(R.dimen.default_padding_half);
|
||||
date.setPadding(padding, padding, padding, padding);
|
||||
date.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
datePicker.getLayoutParams().width = date.getMeasuredWidth();
|
||||
}
|
||||
}
|
||||
package com.sap.sailing.racecommittee.app.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.View;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.TextView;
|
||||
import com.sap.sailing.racecommittee.app.R;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
private TimeUtils() {
|
||||
// only static methods
|
||||
}
|
||||
|
||||
public static long timeUntil(TimePoint targetTime) {
|
||||
return targetTime.asMillis() - MillisecondsTimePoint.now().asMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats your time to 'kk:mm:ss'.
|
||||
*
|
||||
* @param timePoint timestamp to format
|
||||
*/
|
||||
public static String formatTime(TimePoint timePoint) {
|
||||
return formatTime(timePoint, "kk:mm:ss");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats your time with the help of {@link DateFormat}.
|
||||
*
|
||||
* @param timePoint timestamp to format
|
||||
* @param format format as defined by {@link DateFormat}
|
||||
* @return timestamp formatted as {@link String}
|
||||
*/
|
||||
public static String formatTime(TimePoint timePoint, String format) {
|
||||
return DateFormat.format(format, timePoint.asDate()).toString();
|
||||
}
|
||||
|
||||
public static String formatDuration(TimePoint now, TimePoint timePoint) {
|
||||
return formatDuration(now, timePoint, true);
|
||||
}
|
||||
|
||||
public static String formatDuration(TimePoint now, TimePoint timePoint, boolean inclSign) {
|
||||
String duration;
|
||||
if (timePoint.after(now)) {
|
||||
String sign = (inclSign) ? "-" : "";
|
||||
duration = sign + TimeUtils.formatDurationUntil(timePoint.minus(now.asMillis()).asMillis(), false);
|
||||
} else {
|
||||
duration = TimeUtils.formatDurationSince(now.minus(timePoint.asMillis()).asMillis(), false);
|
||||
}
|
||||
return duration;
|
||||
}
|
||||
|
||||
public static String formatDurationSince(long milliseconds) {
|
||||
return formatDurationSince(milliseconds, true);
|
||||
}
|
||||
|
||||
public static String formatDurationSince(long milliseconds, boolean emptyHours) {
|
||||
int secondsTillStart = (int) Math.floor(milliseconds / 1000f);
|
||||
return formatDuration(secondsTillStart, emptyHours);
|
||||
}
|
||||
|
||||
public static String formatDurationUntil(long milliseconds) {
|
||||
return formatDurationUntil(milliseconds, true);
|
||||
}
|
||||
|
||||
public static String formatDurationUntil(long milliseconds, boolean emptyHours) {
|
||||
int secondsTillStart = (int) Math.ceil(milliseconds / 1000f);
|
||||
return formatDuration(secondsTillStart, emptyHours);
|
||||
}
|
||||
|
||||
public static String calcDuration(Calendar from, Calendar to) {
|
||||
String retValue;
|
||||
|
||||
long millis = to.getTimeInMillis() - from.getTimeInMillis();
|
||||
|
||||
long min = millis / (1000 * 60);
|
||||
long sec = (millis - (min * 60 * 1000)) / 1000;
|
||||
|
||||
retValue = String.valueOf(sec) + "\"";
|
||||
if (retValue.length() == 2) {
|
||||
retValue = "0" + retValue;
|
||||
}
|
||||
if (min > 0) {
|
||||
retValue = String.valueOf(min) + "' " + retValue;
|
||||
}
|
||||
|
||||
return retValue;
|
||||
}
|
||||
|
||||
public static Calendar floorTime(Calendar calendar) {
|
||||
if (calendar == null) {
|
||||
calendar = Calendar.getInstance();
|
||||
}
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
public static int daysBetween(Calendar day1, Calendar day2) {
|
||||
Calendar dayOne = (Calendar) day1.clone();
|
||||
Calendar dayTwo = (Calendar) day2.clone();
|
||||
|
||||
if (dayOne.get(Calendar.YEAR) == dayTwo.get(Calendar.YEAR)) {
|
||||
return dayOne.get(Calendar.DAY_OF_YEAR) - dayTwo.get(Calendar.DAY_OF_YEAR);
|
||||
} else {
|
||||
if (dayTwo.get(Calendar.YEAR) > dayOne.get(Calendar.YEAR)) {
|
||||
//swap them
|
||||
Calendar temp = dayOne;
|
||||
dayOne = dayTwo;
|
||||
dayTwo = temp;
|
||||
}
|
||||
int extraDays = 0;
|
||||
|
||||
while (dayOne.get(Calendar.YEAR) > dayTwo.get(Calendar.YEAR)) {
|
||||
dayOne.add(Calendar.YEAR, -1);
|
||||
// getActualMaximum() important for leap years
|
||||
extraDays += dayOne.getActualMaximum(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
|
||||
return extraDays - dayTwo.get(Calendar.DAY_OF_YEAR) + dayOne.get(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatDuration(int secondsTillStart, boolean emptyHours) {
|
||||
int hours = secondsTillStart / 3600;
|
||||
int minutes = (secondsTillStart % 3600) / 60;
|
||||
int seconds = (secondsTillStart % 60);
|
||||
boolean negative = (hours < 0 || minutes < 0 || seconds < 0);
|
||||
String timePattern = ((negative) ? "-" : "") + "%s%s:%s";
|
||||
String secondsString = seconds < 10 ? "0" + Math.abs(seconds) : "" + Math.abs(seconds);
|
||||
String minutesString = minutes < 10 ? "0" + Math.abs(minutes) : "" + Math.abs(minutes);
|
||||
String hoursString = hours < 10 ? "0" + Math.abs(hours) : "" + Math.abs(hours);
|
||||
hoursString = !emptyHours && hoursString.equals("00") ? "" : hoursString + ":";
|
||||
return String.format(timePattern, hoursString, minutesString, secondsString);
|
||||
}
|
||||
|
||||
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays) {
|
||||
initDatePicker(context, datePicker, time, pastDays, futureDays, true);
|
||||
}
|
||||
|
||||
public static void initDatePicker(Context context, NumberPicker datePicker, Calendar time, int pastDays, int futureDays, boolean useWords) {
|
||||
java.text.DateFormat dateFormat = java.text.DateFormat.getDateInstance();
|
||||
ArrayList<String> dates = new ArrayList<>();
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(time.getTime());
|
||||
// Past
|
||||
calendar.add(Calendar.DAY_OF_MONTH, pastDays);
|
||||
for (int i = pastDays; i < -1; i++) {
|
||||
dates.add(dateFormat.format(calendar.getTime()));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
if (useWords) {
|
||||
dates.add(context.getString(R.string.yesterday));
|
||||
dates.add(context.getString(R.string.today));
|
||||
dates.add(context.getString(R.string.tomorrow));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 3);
|
||||
} else {
|
||||
for (int i = (pastDays == 0 ? pastDays : -1); i < (futureDays < 2 ? futureDays + 1 : 2); i++) {
|
||||
dates.add(dateFormat.format(calendar.getTime()));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
}
|
||||
// Future
|
||||
for (int i = 2; i <= futureDays; i++) {
|
||||
dates.add(dateFormat.format(calendar.getTime()));
|
||||
calendar.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
datePicker.setDisplayedValues(dates.toArray(new String[dates.size()]));
|
||||
datePicker.setMinValue(0);
|
||||
datePicker.setMaxValue(dates.size() - 1);
|
||||
datePicker.setWrapSelectorWheel(false);
|
||||
datePicker.setValue(TimeUtils.daysBetween(calendar, Calendar.getInstance()) + Math.abs(pastDays));
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
TextView date = new TextView(context);
|
||||
date.setText(dateFormat.format(calendar.getTime()));
|
||||
int padding = context.getResources().getDimensionPixelSize(R.dimen.default_padding_half);
|
||||
date.setPadding(padding, padding, padding, padding);
|
||||
date.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
|
||||
datePicker.getLayoutParams().width = date.getMeasuredWidth();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user