mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-07 21:01:03 +00:00
fixed bugs in device mapping finder
This commit is contained in:
1 parent
a0ab78a905
commit
33fedad405
5 files changed
+119
-75
No files matched your search
+44
-6
@@ -1,8 +1,12 @@
|
||||
package com.sap.sailing.domain.racelog.tracking.analyzing.test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
@@ -12,6 +16,7 @@ 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;
|
||||
import com.sap.sailing.domain.racelog.RaceLogEventAuthor;
|
||||
@@ -26,18 +31,24 @@ public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
private final Mark mark = new MarkImpl("mark");
|
||||
private final DeviceIdentifier device = new SmartphoneImeiIdentifier("imei");
|
||||
|
||||
private int time = 0;
|
||||
|
||||
private List<DeviceMapping<Competitor>> getMappings() {
|
||||
return new DeviceCompetitorMappingFinder(log).analyze().get(competitor);
|
||||
}
|
||||
|
||||
private TimePoint t() {
|
||||
return new MillisecondsTimePoint(time++);
|
||||
}
|
||||
|
||||
private void addMapping(RaceLogEventAuthor author, DeviceIdentifier device, long from, long to, Competitor item) {
|
||||
RaceLogEvent mapping = factory.createDeviceCompetitorMappingEvent(now, author, device, item, 0,
|
||||
RaceLogEvent mapping = factory.createDeviceCompetitorMappingEvent(t(), author, t(), UUID.randomUUID(), device, item, 0,
|
||||
new MillisecondsTimePoint(from), new MillisecondsTimePoint(to));
|
||||
log.add(mapping);
|
||||
}
|
||||
|
||||
private void addMapping(RaceLogEventAuthor author, DeviceIdentifier device, long from, long to, Mark item) {
|
||||
RaceLogEvent mapping = factory.createDeviceMarkMappingEvent(now, author, device, item, 0,
|
||||
RaceLogEvent mapping = factory.createDeviceMarkMappingEvent(t(), author, t(), UUID.randomUUID(), device, item, 0,
|
||||
new MillisecondsTimePoint(from), new MillisecondsTimePoint(to));
|
||||
log.add(mapping);
|
||||
}
|
||||
@@ -93,9 +104,6 @@ public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
addMapping(author, device, Long.MIN_VALUE, 5, competitor);
|
||||
mappings = getMappings();
|
||||
assertEquals(2, mappings.size());
|
||||
mapping = mappings.get(0);
|
||||
assertEquals(0, mapping.getTimeRange().from().asMillis());
|
||||
assertEquals(5, mapping.getTimeRange().to().asMillis());
|
||||
|
||||
//another closing mapping, that is closer, but is merged
|
||||
addMapping(author, device, Long.MIN_VALUE, 3, competitor);
|
||||
@@ -113,4 +121,34 @@ public class DeviceMappingFinderTest extends AbstractRaceLogTrackingTest {
|
||||
addMapping(author, device, -10, Long.MAX_VALUE, 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());
|
||||
}
|
||||
}
|
||||
+60
-69
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.racelog.tracking.analyzing.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -70,10 +71,10 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
return new DeviceMappingImpl<ItemT>(item, device, range);
|
||||
}
|
||||
|
||||
private <T> List<DeviceMapping<ItemT>> getItemSet(Map<T, List<DeviceMapping<ItemT>>> map, T item) {
|
||||
List<DeviceMapping<ItemT>> list = map.get(item);
|
||||
private <T, U> List<U> getItemSet(Map<T, List<U>> map, T item) {
|
||||
List<U> list = map.get(item);
|
||||
if (list == null) {
|
||||
list = new ArrayList<DeviceMapping<ItemT>>();
|
||||
list = new ArrayList<U>();
|
||||
map.put(item, list);
|
||||
}
|
||||
return list;
|
||||
@@ -97,7 +98,7 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
if (otherTimeRange.intersects(timeRange)) {
|
||||
|
||||
//same device
|
||||
if (otherMapping.getDevice() == device) {
|
||||
if (otherMapping.getDevice().equals(device)) {
|
||||
|
||||
//two open ranges that close each other
|
||||
if ((otherTimeRange.openEnd() && timeRange.openBeginning() && otherTimeRange.from().before(timeRange.to())) ||
|
||||
@@ -117,11 +118,11 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
} else {
|
||||
if (otherTimeRange.startsBefore(timeRange)) {
|
||||
//add the part of the lower-prio mapping, that lies before the higher-prio mapping
|
||||
result.add(getMapping(device, item, otherTimeRange.from(), timeRange.from().minus(1)));
|
||||
result.add(getMapping(otherMapping.getDevice(), item, otherTimeRange.from(), timeRange.from().minus(1)));
|
||||
}
|
||||
if (otherTimeRange.endsAfter(timeRange)) {
|
||||
//add the part of the lower-prio mapping, that lies after the higher-prio mapping
|
||||
result.add(getMapping(device, item, timeRange.to().plus(1), otherTimeRange.to()));
|
||||
result.add(getMapping(otherMapping.getDevice(), item, timeRange.to().plus(1), otherTimeRange.to()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,95 +141,85 @@ public class DeviceMappingFinder<ItemT extends WithID> extends RaceLogAnalyzer<M
|
||||
|
||||
@Override
|
||||
protected Map<ItemT, List<DeviceMapping<ItemT>>> performAnalysis() {
|
||||
Map<ItemT, List<DeviceMapping<ItemT>>> preliminary = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
|
||||
Map<ItemT, List<DeviceMapping<ItemT>>> result = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
|
||||
Map<ItemT, List<DeviceMappingEvent<ItemT>>> events = new HashMap<ItemT, List<DeviceMappingEvent<ItemT>>>();
|
||||
Map<ItemT, List<DeviceMapping<ItemT>>> mappings = new HashMap<ItemT, List<DeviceMapping<ItemT>>>();
|
||||
|
||||
/* iterate over events in order
|
||||
* -> events with higher importance (later, higher author prio) are located towards the end, and should
|
||||
* therefore override those before
|
||||
*
|
||||
* group the events by item, as conflicts have to be resolved within these per-item groups
|
||||
*/
|
||||
for (RaceLogEvent e : raceLog.getUnrevokedEvents()) {
|
||||
if (e instanceof DeviceMappingEvent && isValidMapping(((DeviceMappingEvent<?>) e))) {
|
||||
@SuppressWarnings("unchecked")
|
||||
DeviceMappingEvent<ItemT> mappingEvent = (DeviceMappingEvent<ItemT>) e;
|
||||
ItemT item = mappingEvent.getMappedTo();
|
||||
getItemSet(preliminary, item).add(getMapping(mappingEvent));
|
||||
getItemSet(events, mappingEvent.getMappedTo()).add(mappingEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//combine mappings that close each other
|
||||
for (ItemT item : preliminary.keySet()) {
|
||||
preliminary.put(item, combineOpenRanges(preliminary.get(item), item));
|
||||
}
|
||||
|
||||
//resolve overlaps
|
||||
for (ItemT item : preliminary.keySet()) {
|
||||
List<DeviceMapping<ItemT>> temp = new ArrayList<DeviceMapping<ItemT>>();
|
||||
for (DeviceMapping<ItemT> mapping : preliminary.get(item)) {
|
||||
temp = getOverlapFreeMappings(temp, mapping);
|
||||
for (ItemT item : events.keySet()) {
|
||||
List<DeviceMapping<ItemT>> closedRanges = combineOpenRanges(events.get(item), item);
|
||||
|
||||
List<DeviceMapping<ItemT>> iterativelyResolve = new ArrayList<DeviceMapping<ItemT>>();
|
||||
for (DeviceMapping<ItemT> mapping : closedRanges) {
|
||||
iterativelyResolve = getOverlapFreeMappings(iterativelyResolve, mapping);
|
||||
}
|
||||
result.put(item, temp);
|
||||
|
||||
mappings.put(item, iterativelyResolve);
|
||||
}
|
||||
|
||||
return result;
|
||||
return mappings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combines mappings with open ranges for same device, that also close each other.
|
||||
* If no matching open-ended mapping is found at all, the mapping is left untouched.
|
||||
* One open-ended mapping can close multiple other open-ended mappings, if it is the closest for those.
|
||||
* Mappings without open end are left untouched.
|
||||
*/
|
||||
private List<DeviceMapping<ItemT>> combineOpenRanges(List<DeviceMapping<ItemT>> list, ItemT item) {
|
||||
//sort by devices
|
||||
Map<DeviceIdentifier, List<DeviceMapping<ItemT>>> byDevice = new HashMap<DeviceIdentifier, List<DeviceMapping<ItemT>>>();
|
||||
for (DeviceMapping<ItemT> mapping : list) {
|
||||
getItemSet(byDevice, mapping.getDevice()).add(mapping);
|
||||
private List<DeviceMapping<ItemT>> combineOpenRanges(List<DeviceMappingEvent<ItemT>> events, ItemT item) {
|
||||
final Map<DeviceMapping<ItemT>, DeviceMappingEvent<ItemT>> mappingToEvent = new HashMap<DeviceMapping<ItemT>, DeviceMappingEvent<ItemT>>();
|
||||
List<DeviceMapping<ItemT>> openEnds = new ArrayList<DeviceMapping<ItemT>>();
|
||||
List<DeviceMapping<ItemT>> openBeginnings = new ArrayList<DeviceMapping<ItemT>>();
|
||||
List<DeviceMapping<ItemT>> result = new ArrayList<DeviceMapping<ItemT>>();
|
||||
|
||||
for (DeviceMappingEvent<ItemT> event : events) {
|
||||
DeviceMapping<ItemT> mapping = getMapping(event);
|
||||
mappingToEvent.put(mapping, event);
|
||||
result.add(mapping);
|
||||
}
|
||||
|
||||
List<DeviceMapping<ItemT>> overallResult = new ArrayList<DeviceMapping<ItemT>>();
|
||||
//find open-ended
|
||||
for (DeviceMapping<ItemT> mapping : result) {
|
||||
if (mapping.getTimeRange().openEnd()) openEnds.add(mapping);
|
||||
else if (mapping.getTimeRange().openBeginning()) openBeginnings.add(mapping);
|
||||
}
|
||||
|
||||
for (DeviceIdentifier device : byDevice.keySet()) {
|
||||
List<DeviceMapping<ItemT>> result = new ArrayList<DeviceMapping<ItemT>>();
|
||||
List<DeviceMapping<ItemT>> openEnds = new ArrayList<DeviceMapping<ItemT>>();
|
||||
List<DeviceMapping<ItemT>> openBeginnings = new ArrayList<DeviceMapping<ItemT>>();
|
||||
Collections.sort(openEnds, Collections.reverseOrder(TimedComparator.INSTANCE));
|
||||
|
||||
//find open-ended
|
||||
for (DeviceMapping<ItemT> mapping : list) {
|
||||
if (mapping.getTimeRange().openEnd()) openEnds.add(mapping);
|
||||
else if (mapping.getTimeRange().openBeginning()) openBeginnings.add(mapping);
|
||||
else result.add(mapping);
|
||||
}
|
||||
for (DeviceMapping<ItemT> openBeginning : openBeginnings) {
|
||||
TimeRange openBeginningRange = openBeginning.getTimeRange();
|
||||
DeviceIdentifier device = openBeginning.getDevice();
|
||||
|
||||
Collections.sort(openEnds, Collections.reverseOrder(new TimedComparator()));
|
||||
List<DeviceMapping<ItemT>> usedOpenEnds = new ArrayList<DeviceMapping<ItemT>>();
|
||||
List<DeviceMapping<ItemT>> usedOpenBeginnings = new ArrayList<DeviceMapping<ItemT>>();
|
||||
for (DeviceMapping<ItemT> openEnd : openEnds) {
|
||||
TimeRange openEndRange = openEnd.getTimeRange();
|
||||
|
||||
//now find closest matching
|
||||
for (DeviceMapping<ItemT> openBeginning : openBeginnings) {
|
||||
TimeRange openBeginningRange = openBeginning.getTimeRange();
|
||||
for (DeviceMapping<ItemT> openEnd : openEnds) {
|
||||
TimeRange openEndRange = openEnd.getTimeRange();
|
||||
if (openEndRange.from().before(openBeginningRange.to())) {
|
||||
usedOpenEnds.add(openEnd);
|
||||
usedOpenBeginnings.add(openBeginning);
|
||||
result.add(getMapping(device, item, openEndRange.intersection(openBeginningRange)));
|
||||
break;
|
||||
}
|
||||
if (device.equals(openEnd.getDevice()) && openEndRange.from().before(openBeginningRange.to())) {
|
||||
result.remove(openBeginning);
|
||||
result.remove(openEnd);
|
||||
|
||||
DeviceMapping<ItemT> combined = getMapping(device, item, openEndRange.intersection(openBeginningRange));
|
||||
DeviceMappingEvent<ItemT> higherPrioEvent = RaceLogEventComparator.INSTANCE.compare(mappingToEvent.get(openBeginning),
|
||||
mappingToEvent.get(openEnd)) > 0 ? mappingToEvent.get(openBeginning) : mappingToEvent.get(openEnd);
|
||||
mappingToEvent.put(combined, higherPrioEvent);
|
||||
result.add(combined);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
openEnds.removeAll(usedOpenEnds);
|
||||
openBeginnings.removeAll(usedOpenBeginnings);
|
||||
result.addAll(openEnds);
|
||||
result.addAll(openBeginnings);
|
||||
|
||||
overallResult.addAll(result);
|
||||
}
|
||||
|
||||
Collections.sort(overallResult, RaceLogEventComparator.INSTANCE);
|
||||
return overallResult;
|
||||
|
||||
Collections.sort(result, new Comparator<DeviceMapping<ItemT>>() {
|
||||
@Override
|
||||
public int compare(DeviceMapping<ItemT> arg0, DeviceMapping<ItemT> arg1) {
|
||||
return RaceLogEventComparator.INSTANCE.compare(mappingToEvent.get(arg0), mappingToEvent.get(arg1));
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+5
@@ -42,4 +42,9 @@ DeviceMappingEvent<ItemType> {
|
||||
public TimePoint getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getShortInfo() {
|
||||
return "device: " + device + ", mapped to: " + mappedTo + ", from: " + from + ", to: " + to;
|
||||
}
|
||||
}
|
||||
+5
@@ -44,4 +44,9 @@ public class DeviceMappingImpl<ItemType extends WithID> implements DeviceMapping
|
||||
public static <T extends WithID> DeviceMapping<T> convertToDeviceMapping(DeviceMappingEvent<T> event) {
|
||||
return new DeviceMappingImpl<T>(event.getMappedTo(), event.getDevice(), new TimeRangeImpl(event.getFrom(), event.getTo()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Device Mapping - device: " + device + ", mapped to: " + mappedTo + ", time range: " + timeRange;
|
||||
}
|
||||
}
|
||||
+5
@@ -91,6 +91,11 @@ public class TimeRangeTest {
|
||||
assertEquals(5, union2.from().asMillis());
|
||||
assertEquals(Long.MAX_VALUE, union.to().asMillis());
|
||||
assertEquals(Long.MAX_VALUE, union2.to().asMillis());
|
||||
|
||||
one = create(Long.MIN_VALUE, 10);
|
||||
union = two.union(one);
|
||||
assertTrue(union.openBeginning());
|
||||
assertTrue(union.openEnd());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user