Skip to content

Implementing a mobility trace parser

Radu Ioan Ciobanu edited this page Sep 13, 2016 · 5 revisions

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: