mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-23 04:23:54 +00:00
simplified derivation of device mappings from mapping events
This commit is contained in:
1 parent
7508b7ccee
commit
83610c20bb
6 files changed
+17
-175
No files matched your search
-68
@@ -3,9 +3,6 @@ package com.sap.sailing.domain.racelog.tracking.analyzing.test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -14,9 +11,7 @@ import junit.framework.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sailing.domain.base.impl.MarkImpl;
|
||||
import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.impl.MillisecondsTimePoint;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEvent;
|
||||
@@ -29,7 +24,6 @@ import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifie
|
||||
public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
private final Competitor competitor = new CompetitorImpl("comp", "Comp", null, null, null);
|
||||
private final Competitor competitor2 = new CompetitorImpl("comp2", "Comp2", null, null, null);
|
||||
private final Mark mark = new MarkImpl("mark");
|
||||
private final DeviceIdentifier device = new SmartphoneImeiIdentifier("imei");
|
||||
|
||||
private int time = 0;
|
||||
@@ -53,13 +47,6 @@ public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
return mapping.getId();
|
||||
}
|
||||
|
||||
private Serializable addMapping(RaceLogEventAuthor author, DeviceIdentifier device, Long from, Long to, Mark item) {
|
||||
RaceLogEvent mapping = factory.createDeviceMarkMappingEvent(t(), author, t(), UUID.randomUUID(), device, item, 0,
|
||||
t(from), t(to));
|
||||
log.add(mapping);
|
||||
return mapping.getId();
|
||||
}
|
||||
|
||||
private void closeMapping(RaceLogEventAuthor author, DeviceIdentifier device, Serializable mappingId, long millis) {
|
||||
RaceLogEvent mapping = factory.createCloseOpenEndedDeviceMappingEvent(t(), author, t(), UUID.randomUUID(), 0, mappingId,
|
||||
new MillisecondsTimePoint(millis));
|
||||
@@ -75,30 +62,9 @@ public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
addMapping(author, device, 50L, 90L, competitor);
|
||||
Assert.assertEquals(2, getMappings().size());
|
||||
|
||||
//are mappings merged? (mark mapping should not "distract")
|
||||
addMapping(author, device, 5L, 100L, competitor);
|
||||
addMapping(author, device, 15L, 90L, mark);
|
||||
Assert.assertEquals(1, getMappings().size());
|
||||
|
||||
//overlap with same device -> should be merged
|
||||
addMapping(author, device, 30L, 101L, competitor);
|
||||
Assert.assertEquals(1, getMappings().size());
|
||||
|
||||
//non-overlap with new device
|
||||
addMapping(author, new SmartphoneImeiIdentifier("imei2"), 110L, 150L, competitor);
|
||||
Assert.assertEquals(2, getMappings().size());
|
||||
|
||||
//different device at start with overlap
|
||||
addMapping(author, new SmartphoneImeiIdentifier("imei3"), 0L, 50L, competitor);
|
||||
Assert.assertEquals(3, getMappings().size());
|
||||
|
||||
//different device in middle -> split
|
||||
addMapping(author, new SmartphoneImeiIdentifier("imei3"), 60L, 70L, competitor);
|
||||
Assert.assertEquals(5, getMappings().size());
|
||||
|
||||
//different device at end with overlap
|
||||
addMapping(author, new SmartphoneImeiIdentifier("imei4"), 140L, 160L, competitor);
|
||||
Assert.assertEquals(6, getMappings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,39 +92,5 @@ public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
//open-ended at end, not closed
|
||||
addMapping(author, device, 30L, null, competitor);
|
||||
assertEquals(3, getMappings().size());
|
||||
|
||||
//open-ended at beginning, eats others up
|
||||
addMapping(author, device, -10L, null, competitor);
|
||||
assertEquals(1, getMappings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesLaterMappingThatIncludesOtherOverwrite() throws ParseException {
|
||||
DeviceIdentifier cde = new SmartphoneImeiIdentifier("cde");
|
||||
DeviceIdentifier abc = new SmartphoneImeiIdentifier("abc");
|
||||
DateFormat df = new SimpleDateFormat("mm:ss");
|
||||
long m1600 = df.parse("16:00").getTime();
|
||||
long m1550 = df.parse("15:50").getTime();
|
||||
long m1615 = df.parse("16:15").getTime();
|
||||
long m1610 = df.parse("16:10").getTime();
|
||||
addMapping(author, abc, m1600, m1610, competitor);
|
||||
addMapping(author, cde, m1550, m1615, competitor);
|
||||
|
||||
assertEquals(1, getMappings().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesLaterMappingThatLiesWithinBreakOtherIntoPieces() throws ParseException {
|
||||
DeviceIdentifier cde = new SmartphoneImeiIdentifier("cde");
|
||||
DeviceIdentifier abc = new SmartphoneImeiIdentifier("abc");
|
||||
DateFormat df = new SimpleDateFormat("mm:ss");
|
||||
long m1600 = df.parse("16:00").getTime();
|
||||
long m1550 = df.parse("15:50").getTime();
|
||||
long m1615 = df.parse("16:15").getTime();
|
||||
long m1610 = df.parse("16:10").getTime();
|
||||
addMapping(author, cde, m1550, m1615, competitor);
|
||||
addMapping(author, abc, m1600, m1610, competitor);
|
||||
|
||||
assertEquals(3, getMappings().size());
|
||||
}
|
||||
}
|
||||
+1
@@ -104,6 +104,7 @@ public interface RaceLog extends Track<RaceLogEvent>, WithID {
|
||||
|
||||
/**
|
||||
* Search for the event by its {@link RaceLogEvent#getId() id}.
|
||||
* Caller needs to hold the read lock.
|
||||
*/
|
||||
RaceLogEvent getEventById(Serializable id);
|
||||
|
||||
|
||||
+2
-1
@@ -6,9 +6,10 @@ import java.util.List;
|
||||
import com.sap.sailing.domain.base.Timed;
|
||||
import com.sap.sailing.domain.common.TimeRange;
|
||||
import com.sap.sailing.domain.common.WithID;
|
||||
import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DeviceMappingFinder;
|
||||
|
||||
/**
|
||||
* Refer to the documentation of {@link DeviceMappingEvent} for details on how have-open ranges are resolved.
|
||||
* @see DeviceMappingFinder {@code DeviceMappings} are derived from {@code DeviceMappingEvents}.
|
||||
* @author Fredrik Teschke
|
||||
*/
|
||||
public interface DeviceMapping<ItemType extends WithID> extends Timed {
|
||||
|
||||
+3
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.common.TimePoint;
|
||||
import com.sap.sailing.domain.common.WithID;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEvent;
|
||||
import com.sap.sailing.domain.racelog.Revokable;
|
||||
import com.sap.sailing.domain.racelog.tracking.analyzing.impl.DefinedMarkFinder;
|
||||
|
||||
/**
|
||||
* Event type for mapping {@link DeviceIdentifier devices} to {@code items} ({@link Competitor competitors} or {@link Mark marks}).
|
||||
@@ -15,6 +16,8 @@ import com.sap.sailing.domain.racelog.Revokable;
|
||||
* <p>
|
||||
* If one end of the time range enclosed by {@link #getFrom()} and {@link #getTo()} is open, this can be closed
|
||||
* by a {@link CloseOpenEndedDeviceMappingEvent}.
|
||||
*
|
||||
* @see DefinedMarkFinder for rules by which {@link DeviceMapping}s are derived from these events.
|
||||
* @author Fredrik Teschke
|
||||
*/
|
||||
public interface DeviceMappingEvent<ItemType extends WithID> extends RaceLogEvent, Revokable {
|
||||
|
||||
+9
-106
@@ -14,7 +14,6 @@ import com.sap.sailing.domain.common.impl.TimeRangeImpl;
|
||||
import com.sap.sailing.domain.racelog.RaceLog;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEvent;
|
||||
import com.sap.sailing.domain.racelog.analyzing.impl.RaceLogAnalyzer;
|
||||
import com.sap.sailing.domain.racelog.impl.RaceLogEventComparator;
|
||||
import com.sap.sailing.domain.racelog.tracking.CloseOpenEndedDeviceMappingEvent;
|
||||
import com.sap.sailing.domain.racelog.tracking.DeviceIdentifier;
|
||||
import com.sap.sailing.domain.racelog.tracking.DeviceMapping;
|
||||
@@ -24,25 +23,15 @@ import com.sap.sailing.domain.racelog.tracking.impl.DeviceMappingImpl;
|
||||
/**
|
||||
* Extracts the {@link DeviceMapping}s with the appropriate {@link TimeRange}s from the {@link RaceLog}.
|
||||
* Based on the {@link DeviceMappingEvent}s found in the {@code RaceLog} for the tracked {@code items}
|
||||
* ({@code Competitor}s and {@code Marks}), the actual mappings are created by removing conflicting
|
||||
* overlaps of these mappings by the rules defined below.
|
||||
* ({@code Competitor}s and {@code Marks}), the actual mappings are created.
|
||||
* <p>
|
||||
* Mappings can be defined with an open-ended time range (e.g. track this from now on), which should then be
|
||||
* closed by a corresponding {@link CloseOpenEndedDeviceMappingEvent}.
|
||||
* <p>
|
||||
* The events in the {@code RaceLog} are ordered by their priority through the {@link RaceLogEventComparator}.
|
||||
* Therefore, the rules for resolving conflicts are applied by inspecting the events in order of increasing
|
||||
* priority one by one. Each inspected {@code DeviceMappingEvent} is resolved against the set of mappings
|
||||
* deducted so far from the events with lower priority. The resulting set of mappings from this conflict
|
||||
* resolution is then used as the new mapping basis against which the next higher-priority event is resolved.
|
||||
* <ol>
|
||||
* <li>Mappings that have no overlap in time are never in conflict.</li>
|
||||
* <li>Mappings for different items are never in conflict.</li>
|
||||
* <li>Mappings for both the same device and item with overlapping {@code TimeRanges} can be merged.</li>
|
||||
* <li>Mappings for the same item, but different devices, are in conflict if they have overlapping {@code TimeRanges}.
|
||||
* The higher-priority mapping of the two takes precedence, and overwrites the lower-priority mapping for the
|
||||
* duration of the overlap.</li>
|
||||
* </ol>
|
||||
* The process of removing conflicts between mappings to ensure that only one mapping was active for
|
||||
* one tracked item has been removed. Instead, every {@code DeviceMappingEvent} now results directly in one
|
||||
* {@code DeviceMapping}, the only changes being introduced by closing open time ranges in cases where an
|
||||
* according {@code CloseOpenEndedDeviceMappingEvent} exists.
|
||||
*/
|
||||
public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<Map<ItemT, List<DeviceMapping<ItemT>>>> {
|
||||
public DeviceMappingFinder(RaceLog raceLog) {
|
||||
@@ -53,24 +42,10 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
return mapping instanceof DeviceMappingEvent;
|
||||
}
|
||||
|
||||
protected DeviceMapping<ItemT> getMapping(DeviceMappingEvent<ItemT> event) {
|
||||
return new DeviceMappingImpl<ItemT>(event.getMappedTo(), event.getDevice(),
|
||||
new TimeRangeImpl(event.getFrom(), event.getTo()), event.getId());
|
||||
}
|
||||
|
||||
protected DeviceMapping<ItemT> getMapping(DeviceIdentifier device, ItemT item,
|
||||
TimePoint from, TimePoint to, List<Serializable> originalEventIds) {
|
||||
return new DeviceMappingImpl<ItemT>(item, device, new TimeRangeImpl(from, to), originalEventIds);
|
||||
}
|
||||
|
||||
protected DeviceMapping<ItemT> getMapping(DeviceIdentifier device, ItemT item,
|
||||
TimePoint from, TimePoint to, Serializable originalEventId) {
|
||||
return getMapping(device, item, from, to, Collections.singletonList(originalEventId));
|
||||
}
|
||||
|
||||
protected DeviceMapping<ItemT> getMapping(DeviceIdentifier device, ItemT item,
|
||||
TimeRange range, List<Serializable> originalEventIds) {
|
||||
return new DeviceMappingImpl<ItemT>(item, device, range, originalEventIds);
|
||||
return new DeviceMappingImpl<ItemT>(item, device, new TimeRangeImpl(from, to),
|
||||
Collections.singletonList(originalEventId));
|
||||
}
|
||||
|
||||
private <T, U> List<U> getItemSet(Map<T, List<U>> map, T item) {
|
||||
@@ -81,72 +56,6 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves conflicts between an existing set of mappings for one {@code item}, and a new, higher-priority mapping for the same {@code item}.
|
||||
*/
|
||||
private List<DeviceMapping<ItemT>> getOverlapFreeMappings(List<DeviceMapping<ItemT>> initial,
|
||||
DeviceMapping<ItemT> toBeAdded) {
|
||||
TimeRange timeRange = toBeAdded.getTimeRange();
|
||||
ItemT item = toBeAdded.getMappedTo();
|
||||
DeviceIdentifier device = toBeAdded.getDevice();
|
||||
List<DeviceMapping<ItemT>> result = new ArrayList<DeviceMapping<ItemT>>();
|
||||
|
||||
List<Serializable> originalEventIds = new ArrayList<Serializable>();
|
||||
originalEventIds.addAll(toBeAdded.getOriginalRaceLogEventIds());
|
||||
|
||||
|
||||
for (DeviceMapping<ItemT> otherMapping : initial) {
|
||||
TimeRange otherTimeRange = otherMapping.getTimeRange();
|
||||
|
||||
//time ranges intersect? further investigate
|
||||
if (otherTimeRange.intersects(timeRange)) {
|
||||
|
||||
//same device
|
||||
if (otherMapping.getDevice().equals(device)) {
|
||||
|
||||
//two open ranges that close each other
|
||||
if ((otherTimeRange.openEnd() && timeRange.openBeginning() && otherTimeRange.from().before(timeRange.to())) ||
|
||||
(timeRange.openEnd() && otherTimeRange.openBeginning() && timeRange.from().before(otherTimeRange.to()))) {
|
||||
//shouldn't happen here any more -> dealt with in combineOpenRanges
|
||||
//timeRange = timeRange.intersection(otherTimeRange);
|
||||
|
||||
//otherwise merge
|
||||
} else {
|
||||
timeRange = timeRange.union(otherTimeRange);
|
||||
originalEventIds.addAll(otherMapping.getOriginalRaceLogEventIds());
|
||||
}
|
||||
|
||||
//different device: higher prio overwrites other
|
||||
} else {
|
||||
if (otherTimeRange.liesWithin(timeRange)) {
|
||||
//just ignore the other mapping, as it is completely overwritten by the new one
|
||||
} else {
|
||||
if (otherTimeRange.startsBefore(timeRange)) {
|
||||
//add the part of the lower-prio mapping, that lies before the higher-prio mapping
|
||||
result.add(getMapping(otherMapping.getDevice(), item, otherTimeRange.from(), timeRange.from().minus(1),
|
||||
otherMapping.getOriginalRaceLogEventIds()));
|
||||
}
|
||||
if (otherTimeRange.endsAfter(timeRange)) {
|
||||
//add the part of the lower-prio mapping, that lies after the higher-prio mapping
|
||||
result.add(getMapping(otherMapping.getDevice(), item, timeRange.to().plus(1), otherTimeRange.to(),
|
||||
otherMapping.getOriginalRaceLogEventIds()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//no intersection -> add both
|
||||
} else {
|
||||
result.add(otherMapping);
|
||||
}
|
||||
}
|
||||
|
||||
//other mappings have been checked for conflicts, now add toBeAdded (time range may have grown through merges)
|
||||
result.add(getMapping(device, item, timeRange, toBeAdded.getOriginalRaceLogEventIds()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<ItemT, List<DeviceMapping<ItemT>>> performAnalysis() {
|
||||
Map<ItemT, List<DeviceMappingEvent<ItemT>>> events = new HashMap<ItemT, List<DeviceMappingEvent<ItemT>>>();
|
||||
@@ -159,20 +68,14 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
DeviceMappingEvent<ItemT> mappingEvent = (DeviceMappingEvent<ItemT>) e;
|
||||
getItemSet(events, mappingEvent.getMappedTo()).add(mappingEvent);
|
||||
} else if (e instanceof CloseOpenEndedDeviceMappingEvent) {
|
||||
//a higher priority closing events for the same mapping event overwrites the lower priority one
|
||||
CloseOpenEndedDeviceMappingEvent closingEvent = (CloseOpenEndedDeviceMappingEvent) e;
|
||||
closingEvents.put(closingEvent.getDeviceMappingEventId(), closingEvent);
|
||||
}
|
||||
}
|
||||
|
||||
for (ItemT item : events.keySet()) {
|
||||
List<DeviceMapping<ItemT>> closedRanges = closeOpenRanges(events.get(item), item, closingEvents);
|
||||
|
||||
List<DeviceMapping<ItemT>> iterativelyResolved = new ArrayList<DeviceMapping<ItemT>>();
|
||||
for (DeviceMapping<ItemT> mapping : closedRanges) {
|
||||
iterativelyResolved = getOverlapFreeMappings(iterativelyResolved, mapping);
|
||||
}
|
||||
|
||||
mappings.put(item, iterativelyResolved);
|
||||
mappings.put(item, closeOpenRanges(events.get(item), item, closingEvents));
|
||||
}
|
||||
|
||||
return mappings;
|
||||
|
||||
+2
@@ -4360,7 +4360,9 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
|
||||
List<Serializable> eventIds) throws NotRevokableException {
|
||||
RaceLog raceLog = getRaceLog(leaderboardName, raceColumnName, fleetName);
|
||||
for (Serializable idToRevoke : eventIds) {
|
||||
raceLog.lockForRead();
|
||||
RaceLogEvent event = raceLog.getEventById(idToRevoke);
|
||||
raceLog.unlockAfterRead();
|
||||
if (event instanceof Revokable) {
|
||||
raceLog.revokeEvent(getService().getServerAuthor(), (Revokable) event);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user