-
Notifications
You must be signed in to change notification settings - Fork 12
Implementing a mobility trace parser
In order to implement a new mobility trace parser in MobEmu, the first step is to implement the Parser interface:
public interface Parser {
/**
* Number of milliseconds in a second.
*/
static final long MILLIS_PER_SECOND = 1000L;
/**
* Number of milliseconds in a minute.
*/
static final long MILLIS_PER_MINUTE = 60 * MILLIS_PER_SECOND;
/**
* Gets the contact data contained in a mobility trace.
*
* @return a {@link Trace} object containing the parsed trace
*/
Trace getTraceData();
/**
* Gets the context data contained in a mobility trace.
*
* @return a map of {@link Context} objects for each node
*/
Map<Integer, Context> getContextData();
/**
* Gets the social network contained in a mobility trace.
*
* @return a boolean matrix representing the social network
*/
boolean[][] getSocialNetwork();
/**
* Gets the number of nodes participating in this trace.
*
* @return the number of nodes in this trace
*/
int getNodesNumber();
}
Let's assume that we are parsing a trace that contains information about all the components we are interested in: contacts, social connections, and interests. We create a class that implements Parser and declare private variables for the three components, as well as one for the number of nodes in the trace. We also create a constructor where we call a private static method that parses the trace and fills in the variables declared:
public class MyParser implements Parser {
private int nodesCount = 0;
private Trace trace;
private Map<Integer, Context> context;
private boolean[][] socialNetwork;
public MyParser() {
parseTrace();
}
[...]
}
The parseTrace function will parse three separate files, one for each component. We begin with the contacts file, and let's assume it has the following format:
observer,observed,start,stop
observer is the ID of the node observing the contact, observed is the ID of the node seen, start is the start time of the contact, and stop is the time when the contact ends. For each contact specified as such in the file, we have to create a Contact object and add it to the Trace. Thus, the implementation for the first part of the parseTrace function would look as follows:
private void parseTrace() {
// parse contacts
trace = new Trace("My trace");
// start and end time of the trace
long end = Long.MIN_VALUE;
long start = Long.MAX_VALUE;
try {
FileInputStream fstream = new FileInputStream("traces" + File.separator + "mytrace_contacts.dat");
try (DataInputStream in = new DataInputStream(fstream)) {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
String[] tokens = line.split(",");
// IDs should start from 0 and be consecutive
int observerID = Integer.parseInt(tokens[0]);
int observedID = Integer.parseInt(tokens[1]);
// trace is in seconds from Linux epoch
long contactStart = Long.parseLong(tokens[2]) * MILLIS_PER_SECOND;
long contactEnd = Long.parseLong(tokens[3]) * MILLIS_PER_SECOND;
// compute trace duration
if (contactEnd > end) {
end = contactEnd;
}
// compute trace zero time
if (contactStart < start) {
start = contactStart;
}
if (observerID > nodesCount) {
nodesCount = observerID;
}
if (observedID > nodesCount) {
nodesCount = observedID;
}
trace.addContact(new Contact(observerID, observedID, contactStart, contactEnd));
}
}
} catch (IOException | NumberFormatException e) {
System.err.println("My Parser exception: " + e.getMessage());
}
// set start and end time for the trace
trace.setStartTime(start);
trace.setEndTime(end);
// set trace sample time
trace.setSampleTime(MILLIS_PER_SECOND);
// set the final nodes number
++nodesCount;
[...]
}
Node IDs should start from 0 and be consecutive, and the timestamps should be in milliseconds from the Linux epoch. If the trace is not in milliseconds, the timestamps should be converted before the Contact object is created, but the sample time can be set to the correct value, for efficiency. For example, in our code the timestamp is in seconds from Jan 1, 1970, so we have to multiply by 1000, and we also set the trace sample time to 1000. We also count the number of nodes in the trace, using the nodesCount variable.
For the social network file, let's assume the following format, where there is a social connection between two nodes if they appear as a pair:
node1,node2
For the implementation, we simply need to set the socialNetwork matrix to true where there is a social connection between two nodes:
private void parseTrace() {
[...]
// parse the social connections file
socialNetwork = new boolean[nodesCount][nodesCount];
try {
FileInputStream fstream = new FileInputStream("traces" + File.separator + "mytrace_social.dat");
try (DataInputStream in = new DataInputStream(fstream)) {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
String[] tokens = line.split(",");
int node1 = Integer.parseInt(tokens[0]);
int node2 = Integer.parseInt(tokens[1]);
socialNetwork[node1][node2] = true;
socialNetwork[node2][node1] = true;
}
}
} catch (IOException | NumberFormatException e) {
System.err.println("My Parser exception: " + e.getMessage());
}
[...]
}
Finally, let's assume that the interests file looks as follows:
node_id,interest1,interest2,...
There is the ID of a node on each line, followed by the node's interests, which are integer value starting from 0. The final part of the parseTrace function would thus look as follows:
private void parseTrace() {
[...]
// parse the interests file
context = new HashMap<>();
// initialize context map
for (int i = 0; i < nodesCount; i++) {
context.put(i, new Context(i));
}
try {
FileInputStream fstream = new FileInputStream("traces" + File.separator + "mytrace_interests.dat");
try (DataInputStream in = new DataInputStream(fstream)) {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = br.readLine()) != null) {
String[] tokens = line.split(",");
// read user ID and get Context object
int userID = Integer.parseInt(tokens[0]);
Context contextItem = context.get(userID);
// for each node interest, add topics
for (int i = 1; i < tokens.length; i++) {
contextItem.addTopic(new Topic(Integer.parseInt(tokens[i]), 0));
}
}
}
} catch (IOException | NumberFormatException e) {
System.err.println("My Parser exception: " + e.getMessage());
}
}
Thus, for each new topic parsed, a new Topic object is added to the Context object specific for the current node.
After the parsing is done, we simply implement the four functions of the interface, returning the local variables we previously filled:
@Override
public Trace getTraceData() {
// sort trace by contact start time
trace.sort();
return trace;
}
@Override
public Map<Integer, Context> getContextData() {
return context;
}
@Override
public boolean[][] getSocialNetwork() {
return socialNetwork;
}
@Override
public int getNodesNumber() {
return nodesCount;
}
MobEmu currently offers support for the following mobility traces or models: