playground.acmarmol.matsim2030.microcensus2010.MZPopulationUtils.java Source code

Java tutorial

Introduction

Here is the source code for playground.acmarmol.matsim2030.microcensus2010.MZPopulationUtils.java

Source

/* *********************************************************************** *
 * project: org.matsim.*
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 * copyright       : (C) 2012 by the members listed in the COPYING,        *
 *                   LICENSE and WARRANTY file.                            *
 * email           : info at matsim dot org                                *
 *                                                                         *
 * *********************************************************************** *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *   See also COPYING, LICENSE and WARRANTY file                           *
 *                                                                         *
 * *********************************************************************** */

package playground.acmarmol.matsim2030.microcensus2010;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;

import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.log4j.Logger;
import org.matsim.api.core.v01.Coord;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.TransportMode;
import org.matsim.api.core.v01.population.Activity;
import org.matsim.api.core.v01.population.Leg;
import org.matsim.api.core.v01.population.Person;
import org.matsim.api.core.v01.population.Plan;
import org.matsim.api.core.v01.population.PlanElement;
import org.matsim.api.core.v01.population.Population;
import org.matsim.core.population.ActivityImpl;
import org.matsim.core.population.LegImpl;
import org.matsim.core.population.PersonImpl;
import org.matsim.core.population.routes.GenericRouteImpl;
import org.matsim.core.utils.collections.Tuple;
import org.matsim.core.utils.geometry.CoordImpl;
import org.matsim.core.utils.misc.Time;
import org.matsim.utils.objectattributes.ObjectAttributes;

import playground.acmarmol.utils.MyCollectionUtils;

import com.google.common.base.Supplier;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
* 
* Helper class with methods to filter/handle the population
* 
*
* @author acmarmol
* 
*/

public class MZPopulationUtils {

    //////////////////////////////////////////////////////////////////////
    //member variables
    //////////////////////////////////////////////////////////////////////

    private final static Logger log = Logger.getLogger(MZPopulationUtils.class);

    //////////////////////////////////////////////////////////////////////
    //public methods
    //////////////////////////////////////////////////////////////////////   

    public static void removePlans(final Population population, final Set<Id<Person>> ids) {
        for (Id<Person> id : ids) {
            Person p = population.getPersons().remove(id);
            if (p == null) {
                throw new RuntimeException("pid=" + id + ": id not found in the plans DB!");
            }
        }
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyPlansWithoutActivities(final Population population) {
        Set<Id<Person>> ids = new HashSet<Id<Person>>();
        for (Person person : population.getPersons().values()) {
            if (person.getSelectedPlan() == null) {
                ids.add(person.getId());
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyNonHomeBasedPlans(final Population population) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person p : population.getPersons().values()) {
            Plan plan = p.getSelectedPlan();
            ActivityImpl last = (ActivityImpl) plan.getPlanElements().get(plan.getPlanElements().size() - 1);
            ActivityImpl first = (ActivityImpl) plan.getPlanElements().get(0);
            if (!last.getType().equals(MZConstants.HOME) | !first.getType().equals(MZConstants.HOME)) {
                ids.add(p.getId());
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyNonRoundPlans(final Population population) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person p : population.getPersons().values()) {
            Plan plan = p.getSelectedPlan();
            ActivityImpl last = (ActivityImpl) plan.getPlanElements().get(plan.getPlanElements().size() - 1);
            ActivityImpl first = (ActivityImpl) plan.getPlanElements().get(0);
            if (!last.getType().equals(first.getType())) {
                ids.add(p.getId());
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyPlansWithNegCoords(final Population population) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person person : population.getPersons().values()) {
            Plan plan = person.getSelectedPlan();
            if (plan != null) { //avoid persons without activities
                for (PlanElement pe : plan.getPlanElements()) {
                    if (pe instanceof ActivityImpl) {
                        ActivityImpl act = (ActivityImpl) pe;
                        if ((act.getCoord().getX() < 0) || (act.getCoord().getY() < 0)) {
                            ids.add(person.getId());
                        }
                    }
                }
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyPlansWithTooLongWalkTrips(final Population population) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person person : population.getPersons().values()) {
            Plan plan = person.getSelectedPlan();
            if (plan != null) { //avoid persons without activities
                for (PlanElement pe : plan.getPlanElements()) {
                    if (pe instanceof Leg) {
                        Leg leg = (Leg) pe;
                        if ((leg.getMode().equals(MZConstants.WALK)) && (leg.getRoute().getDistance() > 10000.0)) {
                            ids.add(person.getId());
                        }
                    }
                }
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static void setHomeLocations(final Population population, final ObjectAttributes householdAttributes,
            final ObjectAttributes populationAttributes) {
        int counter = 0;
        for (Person person : population.getPersons().values()) {
            Plan plan = person.getSelectedPlan();
            String hhnr = (String) populationAttributes.getAttribute(person.getId().toString(),
                    MZConstants.HOUSEHOLD_NUMBER);
            CoordImpl homeCoord = (CoordImpl) householdAttributes.getAttribute(hhnr, MZConstants.COORD);

            if (plan != null) { //avoid persons without activities
                for (int i = 0; i < plan.getPlanElements().size(); i = i + 2) {
                    Activity act = (ActivityImpl) plan.getPlanElements().get(i);
                    if ((act.getCoord().getX() == homeCoord.getX())
                            && (act.getCoord().getY() == homeCoord.getY())) {
                        if (!act.getType().equals(MZConstants.HOME)) {
                            act.setType(MZConstants.HOME);
                            counter++;
                            //                  System.out.println("        pid=" + p.getId() + "; act_nr=" + (i/2) + ": set type to '"+HOME+"'");
                        }
                    } else if (act.getType().equals(MZConstants.HOME)
                            & !((act.getCoord().getX() == homeCoord.getX())
                                    && (act.getCoord().getY() == homeCoord.getY()))) {
                        act.getCoord().setXY(homeCoord.getX(), homeCoord.getY());
                    }
                }
            }
        }
        System.out.println("      Number of activities set to home: " + counter);
    }

    //////////////////////////////////////////////////////////////////////   

    public static void setWorkLocations(final Population population, final ObjectAttributes populationAttributes) {
        int counter = 0;
        for (Person person : population.getPersons().values()) {

            if (((PersonImpl) person).isEmployed()) {

                Plan plan = person.getSelectedPlan();
                CoordImpl workCoord = (CoordImpl) populationAttributes.getAttribute(person.getId().toString(),
                        MZConstants.WORK_LOCATION_COORD);

                if (plan != null) { //avoid persons without activities
                    for (int i = 0; i < plan.getPlanElements().size(); i = i + 2) {
                        Activity act = (ActivityImpl) plan.getPlanElements().get(i);
                        if ((act.getCoord().getX() == workCoord.getX())
                                && (act.getCoord().getY() == workCoord.getY())) {
                            if (!act.getType().equals(MZConstants.WORK)) {
                                act.setType(MZConstants.WORK);
                                counter++;
                                //                  System.out.println("        pid=" + p.getId() + "; act_nr=" + (i/2) + ": set type to '"+HOME+"'");
                            }
                        }
                    }
                }
            }
        }
        System.out.println("      Number of activities set to work: " + counter);
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyPlansWithUndefinedNegCoords(final Population population) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person person : population.getPersons().values()) {
            Plan plan = person.getSelectedPlan();
            if (plan != null) { //avoid persons without activities
                for (PlanElement pe : plan.getPlanElements()) {
                    if (pe instanceof ActivityImpl) {
                        ActivityImpl act = (ActivityImpl) pe;
                        if (((act.getCoord().getX() == -97) || (act.getCoord().getY() == -97))) {
                            ids.add(person.getId());
                        }
                    }
                }
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyPlansWithoutBestPrecision(final Population population) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person person : population.getPersons().values()) {
            Plan plan = person.getSelectedPlan();
            if (plan != null) { //avoid persons without activities
                for (PlanElement pe : plan.getPlanElements()) {
                    if (pe instanceof Leg) {
                        Leg leg = (Leg) pe;
                        if (leg.getRoute().getDistance() == -99000) {
                            ids.add(person.getId());
                        }
                    }
                }
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////
    /**
    *  
    * This method handles legs that cross the border.
    * 
    * @see  #handleTripGoingOutOfCountry
    * @see  #handleTripEnteringCountry
    *  
    */

    public static void HandleBorderCrossingTrips(final Population population, final ObjectAttributes wegeAttributes,
            Set<String> border_crossing_wids, String countryCode) {

        final String COUNTRY = countryCode;
        HashMap<Id, ArrayList<Tuple<Integer, PlanElement>>> toAdd = new HashMap<Id, ArrayList<Tuple<Integer, PlanElement>>>();
        HashMap<Id, ArrayList<PlanElement>> toRemove = new HashMap<Id, ArrayList<PlanElement>>();
        Set<Id<Person>> overnight_pids = new HashSet<>();

        for (String wid : border_crossing_wids) {

            String sland = (String) wegeAttributes.getAttribute(wid.toString(), MZConstants.START_COUNTRY);
            String zland = (String) wegeAttributes.getAttribute(wid.toString(), MZConstants.END_COUNTRY);

            if (sland.equals(COUNTRY) && !zland.equals(COUNTRY)) {
                handleTripGoingOutOfCountry(population, wid, COUNTRY, wegeAttributes);
                overnight_pids.add(
                        Id.create(wid.toString().substring(0, wid.toString().indexOf('-')).trim(), Person.class));
            } else if (!sland.equals(COUNTRY) && zland.equals(COUNTRY)) {
                handleTripEnteringCountry(population, wid, COUNTRY, wegeAttributes, toAdd, toRemove);
                overnight_pids.remove(
                        Id.create(wid.toString().substring(0, wid.toString().indexOf('-')).trim(), Person.class));
            } else {
                throw new RuntimeException("This should never happen! Wege " + wid + " doesn't cross the border");
            }

        } //end loop for all wids

        addPlanElements(population, toAdd);
        removePlanElements(population, toRemove);
        removePlanElementsAfterLastBorderCrossing(population, overnight_pids);

    }//end method
    //////////////////////////////////////////////////////////////////////

    /**
    *  
    * This method goes through the stages of the leg, identifies 
    * the border crossing , and modifies the leg start_time so that
    * it only considers the data after the border crossing.
    * It also eliminates all previous activities and legs that
    * are executed outside the country.
    *<p> 
    *For cases of people that are re-entering the country (went out in the same plan),
    *if the border crossings are different, then adds a virtual new leg with mode
    *"abroad: teleport" between both border locations/activities
    *
    *
    *
    * @param  population the population 
    * @param  wid the id of the leg going out of the country
    * @param  country the country code  
    * @param  wegeAttributes attributes of the leg and it's stages
    */

    @SuppressWarnings("deprecation")
    private static void handleTripEnteringCountry(Population population, String wid, String country,
            ObjectAttributes wegeAttributes, HashMap<Id, ArrayList<Tuple<Integer, PlanElement>>> toAdd,
            HashMap<Id, ArrayList<PlanElement>> toRemove) {

        Id<Person> pid = Id.create(wid.toString().substring(0, wid.toString().indexOf('-')).trim(), Person.class);

        if (pid.toString().equals("87011")) {
            System.out.println(pid);
        }

        int legNumber = Integer.parseInt(wid.toString().substring(wid.toString().indexOf('-') + 1));
        //maybe legNumber is not the best way to index, because some planElements are deleted (the ones outside Switzerland)
        // to overcome this issue, all plan elements are stored first in toRemove, and only in the end are deleted.
        ArrayList<PlanElement> elementsToRemove = null;

        List<PlanElement> planElements = population.getPersons().get(pid).getSelectedPlan().getPlanElements();
        LegImpl leg = (LegImpl) planElements.get(2 * legNumber - 1);
        ActivityImpl previousActivity = (ActivityImpl) planElements.get(2 * legNumber - 2);

        boolean immediate_return = false;

        String type = previousActivity.getType();
        //if goes in via plane, specify previous activity as airport, otherwise as border
        if (leg.getMode().equals(MZConstants.PLANE)) {
            if (!previousActivity.getType().contains(MZConstants.AIRPORT)
                    && !previousActivity.getType().contains(MZConstants.BORDER)) {
                previousActivity.setType(MZConstants.AIRPORT.concat(": ").concat(type));
            } else
                immediate_return = true;
        } else {
            if (!previousActivity.getType().contains(MZConstants.AIRPORT)
                    && !previousActivity.getType().contains(MZConstants.BORDER)) {
                previousActivity.setType(MZConstants.BORDER.concat(": ").concat(type));
            } else
                immediate_return = true;
        }

        int curr_mode = Integer.MAX_VALUE;
        Coord curr_start_coord = previousActivity.getCoord();
        boolean start = false;

        for (int i = 1; i <= (Integer) wegeAttributes.getAttribute(wid.toString(),
                MZConstants.NUMBER_STAGES); i++) {

            Etappe etappe = (Etappe) wegeAttributes.getAttribute(wid.toString(),
                    MZConstants.STAGE.concat(String.valueOf(i)));

            if (!etappe.getStartCountry().equals(country) && etappe.getEndCountry().equals(country)) {
                leg.setDepartureTime(etappe.getDepartureTime());
                leg.setTravelTime(etappe.getArrivalTime() - leg.getDepartureTime());
                previousActivity.setEndTime(leg.getDepartureTime());
                curr_start_coord = (etappe.getStartCoord());
                start = true;
            } else if (start) {
                if (etappe.getModeInteger() < curr_mode) {
                    curr_mode = etappe.getModeInteger();
                    leg.setMode(etappe.getMode());
                }
                leg.setArrivalTime(etappe.getArrivalTime());
                leg.setTravelTime(etappe.getArrivalTime() - leg.getDepartureTime());
                curr_start_coord = (etappe.getStartCoord());
            }
        }

        //identify if out and in - border crossings are the same, otherwise it necessary to create new virtual activity. 
        boolean different_border_crossing = false;
        if (immediate_return && !curr_start_coord.equals(previousActivity.getCoord())) {
            different_border_crossing = true;
        } else {
            previousActivity.setCoord(curr_start_coord);
        }

        //--------------------------------------------------------------------------------------------------------------------------------------

        int index = planElements.indexOf(previousActivity);
        String crossing_type = previousActivity.getType().substring(0, previousActivity.getType().indexOf(':'))
                .trim();

        if (toRemove.containsKey(pid)) {
            elementsToRemove = toRemove.get(pid);
        } else {
            elementsToRemove = new ArrayList<PlanElement>(10);
        }

        if (index > 0) {
            boolean cont = true;
            if (immediate_return) {
                //immediate return, only do something if different border crossing is used
                // if different border crossing is used, a new virtual activity is created with a connecting teleport leg
                if (different_border_crossing) {
                    LegImpl teleportLeg = new LegImpl(leg);
                    teleportLeg.setDepartureTime(previousActivity.getEndTime());
                    teleportLeg.setArrivalTime(previousActivity.getEndTime());
                    teleportLeg.setTravelTime(0);
                    teleportLeg.setMode(MZConstants.ABROAD_TELEPORT);
                    GenericRouteImpl route = new GenericRouteImpl(null, null);
                    teleportLeg.setRoute(route);
                    route.setDistance(0);
                    route.setTravelTime(leg.getTravelTime());

                    if (toAdd.get(pid) == null) {
                        toAdd.put(pid, new ArrayList<Tuple<Integer, PlanElement>>());
                    }
                    toAdd.get(pid).add(new Tuple<Integer, PlanElement>(index + 1, teleportLeg));

                    ActivityImpl virtualActivity = new ActivityImpl(previousActivity);
                    virtualActivity.setStartTime(previousActivity.getEndTime());
                    virtualActivity.setCoord(curr_start_coord);
                    toAdd.get(pid).add(new Tuple<Integer, PlanElement>(index + 2, virtualActivity));

                }

            } else {
                //not immediate return, therefore there are activities and legs outside switzerland that need to be eliminated
                //remove previous plan elements (executed outside switzerland) until "airport" or "border" activity is found 
                // Possibilities:   
                //1) all previous activities and legs are outside switzerland -> remove all
                //2) there's a previous "airport" or "border" activity, thus the person left and entered switzerland on the same plan. 
                //   2.1) if the coords of these two match (same airport or border pass) merge both activities and fix start and end times accordingly
                //  2.2) if the coords don't match, create intermediate leg with mode "teleport" to keep consistency on plan.

                for (int j = index - 1; cont && j >= 0; j--) {

                    boolean delete = true;
                    PlanElement pe = planElements.get(j);
                    if (pe instanceof Activity) {
                        Activity activity = (Activity) pe;
                        if (activity.getType().contains(MZConstants.AIRPORT)
                                || activity.getType().contains(MZConstants.BORDER)) {

                            //going in to switzerland via the same path that went out -> merge activites  (2.1)
                            if (activity.getType().contains(crossing_type)
                                    && activity.getCoord().equals(previousActivity.getCoord())) {
                                previousActivity.setStartTime(activity.getStartTime());
                                cont = false;

                            } else {
                                //going in to switzerland via other path -> create "teleport" leg  (2.2)
                                LegImpl teleportLeg = new LegImpl(leg);
                                teleportLeg.setDepartureTime(activity.getEndTime());
                                teleportLeg.setArrivalTime(previousActivity.getStartTime());
                                teleportLeg.setMode(MZConstants.ABROAD_TELEPORT);
                                GenericRouteImpl route = new GenericRouteImpl(null, null);
                                teleportLeg.setRoute(route);
                                route.setDistance(0);
                                route.setTravelTime(leg.getTravelTime());
                                if (toAdd.get(pid) == null) {
                                    toAdd.put(pid, new ArrayList<Tuple<Integer, PlanElement>>());
                                }
                                toAdd.get(pid).add(new Tuple<Integer, PlanElement>(index, teleportLeg));
                                delete = false;
                                cont = false;

                            }
                        }
                    }
                    if (delete) {
                        elementsToRemove.add(pe);
                    }

                }
            }

        }

        toRemove.put(pid, elementsToRemove);
    }

    //////////////////////////////////////////////////////////////////////
    /**
     *  
     * This method goes through the stages of the leg, identifies 
     * the border crossing , and modifies the leg end_time so that
     * it only considers the data before the border crossing.
     * It also adds at the beginning of next activity's type,
     * either if the person it's at an "aiport" (if leg's mode is plane)
     * or at a "border" (for any other mode).
     *
     * @param  population the population 
     * @param  wid the id of the leg going out of the country
     * @param  country the country code  
     * @param  wegeAttributes attributes of the leg and it's stages
     */

    private static void handleTripGoingOutOfCountry(Population population, String wid, String country,
            ObjectAttributes wegeAttributes) {

        Id<Person> pid = Id.create(wid.toString().substring(0, wid.toString().indexOf('-')).trim(), Person.class);
        int legNumber = Integer.parseInt(wid.toString().substring(wid.toString().indexOf('-') + 1));
        //maybe legNumber is not the best way to index, because some planElements are deleted (the ones outside Switzerland)
        // to overcome this issue, all plan elements are stored first in toRemove, and only in the end are deleted.

        List<PlanElement> planElements = population.getPersons().get(pid).getSelectedPlan().getPlanElements();
        LegImpl leg = (LegImpl) planElements.get(2 * legNumber - 1);
        ActivityImpl nextActivity = (ActivityImpl) planElements.get(2 * legNumber);

        boolean cont = true;
        int etappen = 1;
        int curr_mode = Integer.MAX_VALUE;
        String type = nextActivity.getType();

        //if goes out via plane, specify next activity as airport, otherwise as border
        if (leg.getMode().equals(MZConstants.PLANE)) {
            nextActivity.setType(MZConstants.AIRPORT.concat(": ").concat(type));
        } else {
            nextActivity.setType(MZConstants.BORDER.concat(": ").concat(type));
        }

        //modify leg and replace with information from before the border crossing (in MZ2010, a new etappe always start at border crossing!)

        while (cont) {
            if (etappen > (Integer) wegeAttributes.getAttribute(wid.toString(), MZConstants.NUMBER_STAGES)) {
                throw new RuntimeException(
                        "This should never happen!  Wege id (" + wid + ") doesn't cross border!");
            }

            Etappe etappe = (Etappe) wegeAttributes.getAttribute(wid.toString(),
                    MZConstants.STAGE.concat(String.valueOf(etappen)));

            if (etappe.getStartCountry().equals(country) && etappe.getEndCountry().equals(country)) {
                if (etappe.getModeInteger() < curr_mode) {// && (leg.getMode().equals("plane")? !etappe.getMode().equals("plane"):true)){
                    curr_mode = etappe.getModeInteger();
                    leg.setMode(etappe.getMode());
                }
                leg.setArrivalTime(etappe.getArrivalTime());
                leg.setTravelTime(etappe.getArrivalTime() - leg.getDepartureTime());
                nextActivity.setCoord(etappe.getEndCoord());
                nextActivity.setStartTime(leg.getArrivalTime());

            } else {
                cont = false;
            }

            etappen++;
        }

    }

    //////////////////////////////////////////////////////////////////////

    private static void removePlanElementsAfterLastBorderCrossing(Population population,
            Set<Id<Person>> overnight_pids) {

        for (Id<Person> id : overnight_pids) {
            List<PlanElement> planElements = population.getPersons().get(id).getSelectedPlan().getPlanElements();

            boolean cont = true;

            for (int i = planElements.size() - 1; cont; i--) {

                PlanElement pe = planElements.get(i);

                if (planElements.get(i) instanceof Activity) {
                    Activity activity = (Activity) pe;
                    if (activity.getType().contains(MZConstants.BORDER)
                            || activity.getType().contains(MZConstants.AIRPORT)) {
                        cont = false;
                    } else {
                        planElements.remove(i);
                    }

                } else {
                    planElements.remove(i);
                }

            }
        }
    }

    //////////////////////////////////////////////////////////////////////

    private static void addPlanElements(Population population,
            HashMap<Id, ArrayList<Tuple<Integer, PlanElement>>> toAdd) {

        for (Id id : toAdd.keySet()) {

            ArrayList<Tuple<Integer, PlanElement>> elementsToAdd = toAdd.get(id);
            Person person = population.getPersons().get(id);

            for (Tuple<Integer, PlanElement> pair : elementsToAdd) {
                person.getSelectedPlan().getPlanElements().add(pair.getFirst(), pair.getSecond());
            }

        }

    }

    //////////////////////////////////////////////////////////////////////

    private static void removePlanElements(Population population, HashMap<Id, ArrayList<PlanElement>> toRemove) {

        for (Id id : toRemove.keySet()) {

            Person person = population.getPersons().get(id);
            person.getSelectedPlan().getPlanElements().removeAll(toRemove.get(id));

        }

    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id<Person>> identifyPlansOutOfSwitzerland(final Population population,
            final ObjectAttributes wegeAttributes, String countryCode) {
        Set<Id<Person>> ids = new HashSet<>();
        for (Person person : population.getPersons().values()) {
            Plan plan = person.getSelectedPlan();
            if (plan != null) { //avoid persons without activities
                boolean out = true;
                int legCounter = 0;
                for (PlanElement pe : plan.getPlanElements()) {
                    if (pe instanceof Leg) {
                        legCounter++;
                        String sland = (String) wegeAttributes.getAttribute(
                                person.getId().toString().concat("-").concat(String.valueOf(legCounter)),
                                MZConstants.START_COUNTRY);
                        String zland = (String) wegeAttributes.getAttribute(
                                person.getId().toString().concat("-").concat(String.valueOf(legCounter)),
                                MZConstants.END_COUNTRY);
                        if (sland.equals(countryCode) || zland.equals(countryCode)) {
                            out = false;
                            break;
                        }
                    }
                }
                if (out) {
                    ids.add(person.getId());
                    person.getPlans().clear();
                }

            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static ArrayList<Set<?>> identifyCrossBorderWeges(final Population population,
            final ObjectAttributes wegeAttributes, String countryCode) {
        Set<String> wids = new LinkedHashSet<String>();
        Set<Id<Person>> pids = new LinkedHashSet<>();

        for (Person person : population.getPersons().values()) {

            Plan plan = person.getSelectedPlan();
            if (plan != null) { //avoid persons without activities
                int legCounter = 0;
                for (PlanElement pe : plan.getPlanElements()) {
                    if (pe instanceof Leg) {
                        @SuppressWarnings("unused")
                        Leg leg = (Leg) pe;
                        legCounter++;
                        String wid = person.getId().toString().concat("-").concat(String.valueOf(legCounter));
                        String sland = (String) wegeAttributes.getAttribute(wid, MZConstants.START_COUNTRY);
                        String zland = (String) wegeAttributes.getAttribute(wid, MZConstants.END_COUNTRY);
                        if ((!sland.equals(countryCode) ^ !zland.equals(countryCode))) {
                            wids.add(wid);
                            pids.add(person.getId());

                        }
                    }
                }

            }
        }
        ArrayList<Set<?>> ids = new ArrayList<Set<?>>();
        ids.add(pids);
        ids.add(wids);
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static Set<Id> removeWegesOutsideSwitzerland(final Population population, final Set<Id> ids,
            ObjectAttributes wegeAttributes) {

        Set<PlanElement> elements = new LinkedHashSet<PlanElement>();

        for (Id id : ids) {
            int legCounter = 0;
            Person person = population.getPersons().get(id);
            Plan plan = person.getSelectedPlan();
            for (int i = 0; i < plan.getPlanElements().size(); i++) {

                PlanElement pe = plan.getPlanElements().get(i);
                if (pe instanceof Leg) {
                    legCounter++;
                    String wid = person.getId().toString().concat("-").concat(String.valueOf(legCounter));

                    Leg leg = (Leg) pe;
                    Activity act = (Activity) plan.getPlanElements().get(i + 1);

                    String sland = (String) wegeAttributes.getAttribute(wid, MZConstants.START_COUNTRY);
                    String zland = (String) wegeAttributes.getAttribute(wid, MZConstants.END_COUNTRY);
                    if ((!sland.equals(MZConstants.SWISS_CODE) && !zland.equals(MZConstants.SWISS_CODE))) {
                        elements.add(leg);
                        if (act.getType().contains(MZConstants.AIRPORT)) {
                            for (int j = i - 1; j == 0; j -= 2) {
                                PlanElement pel = plan.getPlanElements().get(j);
                                if (pel instanceof Activity) {
                                    Activity activity = (Activity) pel;
                                    if (activity.getType().contains(MZConstants.AIRPORT)) {
                                        activity.setEndTime(act.getEndTime());
                                    }
                                }
                            }
                        }
                        elements.add(act);
                    }

                }
            }

            plan.getPlanElements().removeAll(elements);
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static final Set<Id> identifyPlansWithRoundTrips(final Population plans) {
        Set<Id> ids = new HashSet<Id>();
        for (Person p : plans.getPersons().values()) {
            Plan plan = p.getSelectedPlan();
            Activity prevAct = null;
            for (PlanElement pe : plan.getPlanElements()) {
                if (pe instanceof Activity) {
                    Activity act = (Activity) pe;
                    if (prevAct != null) {
                        Coord prevc = prevAct.getCoord();
                        Coord currc = act.getCoord();
                        if ((currc.getX() == prevc.getX()) && (currc.getY() == currc.getY())) {
                            ids.add(p.getId());
                        }
                    }
                    prevAct = act;
                }
            }
        }
        return ids;
    }

    //////////////////////////////////////////////////////////////////////

    public static void classifyActivityChains(Population population) {

        HashMap<String, Integer> actChains = new HashMap<String, Integer>();

        for (Person person : population.getPersons().values()) {

            StringBuilder actChain = new StringBuilder();
            Plan plan = person.getSelectedPlan();

            if (plan != null) {
                for (PlanElement pe : plan.getPlanElements()) {

                    if (pe instanceof Activity) {

                        ActivityImpl act = (ActivityImpl) pe;
                        String type = act.getType();

                        if (type.contains(MZConstants.WORK) || type.contains(MZConstants.BUSINESS)
                                || type.contains(MZConstants.DIENSTFAHRT)) {
                            actChain.append("W");
                        }

                        else if (type.contains(MZConstants.BUSINESS) || type.contains(MZConstants.DIENSTFAHRT)) {
                            actChain.append("B");
                        }

                        else if (type.contains(MZConstants.LEISURE)
                                || type.contains(MZConstants.ACCOMPANYING_CHILDREN)
                                || type.contains(MZConstants.ACCOMPANYING_NOT_CHILDREN)
                                || type.contains(MZConstants.ERRANDS) || type.contains(MZConstants.OTHER)
                                || type.contains(MZConstants.FOREIGN_PROPERTY)
                                || type.contains(MZConstants.OVERNIGHT)
                                || type.contains(MZConstants.PSEUDOETAPPE)) {
                            actChain.append("L");
                        }

                        else if (type.contains(MZConstants.ACCOMPANYING_CHILDREN)
                                || type.contains(MZConstants.ACCOMPANYING_NOT_CHILDREN)
                                || type.contains(MZConstants.ACCOMPANYING) || type.contains(MZConstants.ERRANDS)
                                || type.contains(MZConstants.OTHER) || type.contains(MZConstants.FOREIGN_PROPERTY)
                                || type.contains(MZConstants.OVERNIGHT)
                                || type.contains(MZConstants.PSEUDOETAPPE)) {
                            actChain.append("O");
                        }

                        else if (type.contains(MZConstants.SHOPPING)) {
                            actChain.append("S");
                        }

                        else if (type.contains(MZConstants.EDUCATION)) {
                            actChain.append("E");
                        }

                        else if (type.contains(MZConstants.HOME)) {
                            actChain.append("H");
                        }

                        else {
                            throw new RuntimeException(
                                    "This should never happen! Type: " + type + " doesn't exist " + person.getId());
                        }
                    }

                }

                if (actChains.containsKey(actChain.toString())) {
                    int total = actChains.get(actChain.toString());
                    actChains.put(actChain.toString(), total + 1);
                } else {
                    actChains.put(actChain.toString(), 1);
                }

            }

        }
        MyCollectionUtils.printMap(actChains);
        System.out.println("Total of different activity chains: " + actChains.keySet().size());

    }

    //////////////////////////////////////////////////////////////////////   

    public static void changeToMatsimModes(Population population) {
        Set<String> unknownModes = new HashSet<String>();

        for (Person person : population.getPersons().values()) {

            Plan plan = person.getSelectedPlan();

            if (plan != null) {
                for (PlanElement pe : plan.getPlanElements()) {

                    if (pe instanceof Leg) {
                        Leg leg = (Leg) pe;

                        String mode = leg.getMode();
                        if (mode.equals(MZConstants.PLANE) || mode.equals(MZConstants.TRAIN)
                                || mode.equals(MZConstants.SHIP) || mode.equals(MZConstants.TAXI)
                                || mode.equals(MZConstants.TRAM) || mode.equals(MZConstants.BUS)
                                || mode.equals(MZConstants.SONSTINGER_OEV) || mode.equals(MZConstants.POSTAUTO)
                                || mode.equals(MZConstants.REISECAR) || mode.equals(MZConstants.TRAIN)) {
                            leg.setMode(TransportMode.pt); //PUBLIC TRANSPORT
                        } else if (mode.equals(MZConstants.WALK)) {
                            leg.setMode(TransportMode.walk); //WALK

                        } else if (mode.equals(MZConstants.BICYCLE) || mode.equals(MZConstants.SKATEBOARD)
                                || mode.equals(MZConstants.MOFA)) {
                            leg.setMode(TransportMode.bike); //BICYCLE

                        } else if (mode.equals(MZConstants.CAR) || mode.equals(MZConstants.MOTORCYCLE)
                                || mode.equals(MZConstants.TRUCK)) {
                            leg.setMode(TransportMode.car); //CAR

                        } else {
                            unknownModes.add(mode);
                        }
                    }
                }
            }
        }

        if (!unknownModes.isEmpty()) {
            log.warn("Unhandled modes: " + unknownModes.toString());
        }
    }

    public static void removePersonsWithoutPlan(Population population) {
        final Map<Id<Person>, ? extends Person> persons = population.getPersons();
        ArrayList<Id<Person>> toRemove = new ArrayList<>(persons.size());

        for (Id<Person> id : persons.keySet()) {
            Person person = persons.get(id);
            Plan plan = person.getSelectedPlan();

            if (plan == null)
                toRemove.add(id);
        }

        for (Id<Person> id : toRemove)
            persons.remove(id);

        log.info("Removed " + toRemove.size() + " persons without plan.");
    }

    public static void analyzeActivityTypesAndLengths(Population population) throws ExecutionException {
        LoadingCache<String, SummaryStatistics> activityDuration = CacheBuilder.newBuilder()
                .build(CacheLoader.from(new Supplier<SummaryStatistics>() {
                    @Override
                    public SummaryStatistics get() {
                        return new SummaryStatistics();
                    }
                }));

        for (Person p : population.getPersons().values()) {
            if (p.getPlans().size() == 0)
                continue;

            Plan plan = p.getPlans().get(0);

            List<PlanElement> planElements = plan.getPlanElements();
            for (PlanElement pe : planElements) {
                if (!(pe instanceof Activity))
                    continue;
                Activity activity = (Activity) pe;

                double startTime = activity.getStartTime();
                double endTime = activity.getEndTime();

                SummaryStatistics typeStats = activityDuration.get(activity.getType());
                if (endTime != Time.UNDEFINED_TIME) {
                    if (startTime == Time.UNDEFINED_TIME)
                        startTime = 0;
                    typeStats.addValue(endTime - startTime);
                }
            }
        }

        ConcurrentMap<String, SummaryStatistics> activityDurationMap = activityDuration.asMap();
        {
            int i = 0;
            final StringBuffer s = new StringBuffer();
            for (final String actType : activityDurationMap.keySet()) {
                final SummaryStatistics stats = activityDurationMap.get(actType);

                s.append(String.format("<param name=\"activityType_%d\" value=\"%s\" />\n", i, actType));
                s.append(String.format("<param name=\"activityPriority_%d\" value=\"1\" />\n", i));
                s.append(String.format("<param name=\"activityTypicalDuration_%d\" value=\"%s\" />\n", i,
                        Time.writeTime(stats.getMean())));
                s.append("\n");
                i++;
            }
            log.info("All activities:\n" + s.toString());
        }
    }

    public static void setMZ2000LegModesWithEtappenInfo(Population population,
            TreeMap<String, ArrayList<playground.acmarmol.matsim2030.forecasts.timeSeriesUpdate.loaders.etappes.Etappe>> etappes) {

        for (Person person : population.getPersons().values()) {

            Id id = person.getId();

            Plan plan = person.getSelectedPlan();

            if (plan == null)
                continue;

            ArrayList<playground.acmarmol.matsim2030.forecasts.timeSeriesUpdate.loaders.etappes.Etappe> etappen = etappes
                    .get(id.toString());
            int legCounter = 0;
            for (PlanElement pe : plan.getPlanElements()) {

                if (!(pe instanceof Leg))
                    continue;

                legCounter++;
                LegImpl leg = (LegImpl) pe;
                int curr_mode = Integer.MAX_VALUE;
                String mode = null;

                for (playground.acmarmol.matsim2030.forecasts.timeSeriesUpdate.loaders.etappes.Etappe etappe : etappen) {

                    if (etappe.getWegeNr() > legCounter)
                        break;

                    if (etappe.getModeInteger() < curr_mode) {
                        curr_mode = etappe.getModeInteger();
                        mode = etappe.getWegeEquivalentModeFromEtappeMode();

                    }

                }

                leg.setMode(mode);
            }
        }
    }

    //////////////////////////////////////////////////////////////////////

    public static void setHomeLocationsMZ2000FromHouseholdAdress(final Population population,
            final ObjectAttributes populationAttributes, final ObjectAttributes householdAttributes,
            final ObjectAttributes wegeAttributes) {

        HashMap<Id, ArrayList<Tuple<Integer, Integer>>> homes_info = new HashMap<Id, ArrayList<Tuple<Integer, Integer>>>();
        int counter = 0;

        for (Person person : population.getPersons().values()) {
            String hhnr = (String) populationAttributes.getAttribute(person.getId().toString(),
                    MZConstants.HOUSEHOLD_NUMBER);
            String hh_address = (String) householdAttributes.getAttribute(hhnr, MZConstants.ADDRESS);

            Plan plan = person.getSelectedPlan();

            if (plan == null) //avoid persons without activities
                continue;

            int legCounter = 0;
            for (PlanElement pe : plan.getPlanElements()) {
                if (pe instanceof Leg) {

                    legCounter++;
                    String address_start = (String) wegeAttributes.getAttribute(
                            person.getId().toString().concat("-").concat(String.valueOf(legCounter)),
                            MZConstants.ADDRESS_START);
                    String adress_end = (String) wegeAttributes.getAttribute(
                            person.getId().toString().concat("-").concat(String.valueOf(legCounter)),
                            MZConstants.ADDRESS_END);

                    if (!(address_start.equals(hh_address) || adress_end.equals(hh_address)))
                        continue;

                    int pos = 0;
                    ;
                    if (address_start.equals(hh_address))
                        pos = -1;
                    counter++;
                    if (homes_info.get(person.getId()) == null)
                        homes_info.put(person.getId(), new ArrayList<Tuple<Integer, Integer>>());
                    homes_info.get(person.getId()).add(new Tuple<Integer, Integer>(legCounter, pos));
                }
            }

        }

        setHomeLocations(population, homes_info);

        System.out.println("      Number of activities set to work: " + counter);
    }

    private static void setHomeLocations(Population population,
            HashMap<Id, ArrayList<Tuple<Integer, Integer>>> homes_info) {

        Iterator it = homes_info.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry pairs = (Map.Entry) it.next();
            Id id = (Id) pairs.getKey();
            ArrayList<Tuple<Integer, Integer>> legs = (ArrayList<Tuple<Integer, Integer>>) pairs.getValue();

            Person person = population.getPersons().get(id);
            List<PlanElement> pes = person.getSelectedPlan().getPlanElements();
            PlanElement[] pesArray = pes.toArray(new PlanElement[pes.size()]);

            for (Tuple<Integer, Integer> leg : legs) {

                ActivityImpl activity = (ActivityImpl) pesArray[2 * (leg.getFirst() + leg.getSecond())];
                activity.setType(MZConstants.HOME);
            }

            it.remove(); // avoids a ConcurrentModificationException
        }

    }

    public static void setHomeLocationsMZ2000AccordingToActivitySequence(Population population) {

        for (Person person : population.getPersons().values()) {

            Plan plan = person.getSelectedPlan();

            if (plan == null)
                continue;

            String lastActivityType = null;

            for (PlanElement pe : plan.getPlanElements()) {

                if (pe instanceof ActivityImpl) {
                    ActivityImpl act = (ActivityImpl) pe;
                    if (act.getType().equals(lastActivityType))
                        act.setType(MZConstants.HOME);

                    lastActivityType = act.getType();
                }

            }

        }

    }

    public static void eliminateToursWithUndefinedWegeTimesMZ1989(Population population) {
        //removes only home-to-home tours

        for (Person person : population.getPersons().values()) {

            if (person.getId().toString().equals("100071")) {
                System.out.println();
            }

            Plan plan = person.getSelectedPlan();

            if (plan == null)
                continue;

            boolean erase = false;
            ArrayList<PlanElement> toRemove = new ArrayList<PlanElement>();
            ArrayList<PlanElement> tour = new ArrayList<PlanElement>();

            for (PlanElement pe : plan.getPlanElements()) {
                System.out.println(pe);
                tour.add(pe);
                if (pe instanceof ActivityImpl) {
                    ActivityImpl act = (ActivityImpl) pe;
                    if (act.getType().equals(MZConstants.HOME)) {
                        if (erase) {

                            toRemove.addAll(tour);
                            erase = false;
                        }

                        tour.clear();
                    }

                }

                if (!(pe instanceof Leg))
                    continue;

                Leg leg = (LegImpl) pe;
                if (leg.getDepartureTime() < 0 || leg.getTravelTime() < 0)
                    erase = true;

            }
            if (erase)
                toRemove.addAll(tour);

            plan.getPlanElements().removeAll(toRemove);

        }

    }

    //////////////////////////////////////////////////////////////////////   

    public static void recodeActivityTypesHWELS(Population population) {

        for (Person person : population.getPersons().values()) {

            Plan plan = person.getSelectedPlan();

            if (plan != null) {
                for (PlanElement pe : plan.getPlanElements()) {

                    if (pe instanceof Activity) {

                        ActivityImpl act = (ActivityImpl) pe;
                        String type = act.getType();

                        if (type.contains(MZConstants.WORK) || type.contains(MZConstants.BUSINESS)
                                || type.contains(MZConstants.DIENSTFAHRT)) {
                            act.setType(MZConstants.WORK);
                        }

                        else if (type.contains(MZConstants.LEISURE)
                                || type.contains(MZConstants.ACCOMPANYING_CHILDREN)
                                || type.contains(MZConstants.ACCOMPANYING_NOT_CHILDREN)
                                || type.contains(MZConstants.ERRANDS) || type.contains(MZConstants.OTHER)
                                || type.contains(MZConstants.FOREIGN_PROPERTY)
                                || type.contains(MZConstants.ACCOMPANYING) || type.contains(MZConstants.OVERNIGHT)
                                || type.contains(MZConstants.PSEUDOETAPPE)) {
                            act.setType(MZConstants.LEISURE);
                        }

                        //////////////////////////////////////////////////////////////////////
                        // code added by staheale

                        else if (type.contains(MZConstants.BORDER.concat(": ").concat(MZConstants.SHOPPING))) {
                            act.setType(MZConstants.SHOPPING);
                        }

                        else if (type.contains(MZConstants.BORDER.concat(": ").concat(MZConstants.HOME))) {
                            act.setType(MZConstants.HOME);
                        }

                        else if (type.contains(MZConstants.BORDER.concat(": ").concat(MZConstants.EDUCATION))) {
                            act.setType(MZConstants.EDUCATION);
                        }

                        else if (type.contains(MZConstants.AIRPORT.concat(": ").concat(MZConstants.SHOPPING))) {
                            act.setType(MZConstants.SHOPPING);
                        }

                        else if (type.contains(MZConstants.AIRPORT.concat(": ").concat(MZConstants.HOME))) {
                            act.setType(MZConstants.HOME);
                        }

                        //////////////////////////////////////////////////////////////////////

                        //                  else if(type.contains(MZConstants.SHOPPING))
                        //                  {act.setType(MZConstants.SHOPPING);}
                        //                  
                        //                  else if(type.contains(MZConstants.EDUCATION))
                        //                  {act.setType(MZConstants.EDUCATION);}
                        //                  
                        //                  else if(type.contains(MZConstants.HOME))
                        //                  {act.setType(MZConstants.HOME);}

                    }

                }

            }

        }
    }
    //////////////////////////////////////////////////////////////////////

    @SuppressWarnings("deprecation")
    public static void removeDuplicateActivities(Population population) {
        for (Person person : population.getPersons().values()) {

            Plan plan = person.getSelectedPlan();

            if (plan == null)
                continue;

            Stack<PlanElement> newPlanElements = new Stack<PlanElement>();

            ActivityImpl prevAct = null;

            List<PlanElement> planElements = plan.getPlanElements();
            for (PlanElement pe : planElements) {

                PlanElement newPe = pe;

                if (pe instanceof Activity) {

                    ActivityImpl act = (ActivityImpl) pe;

                    if (prevAct != null) {
                        String type = act.getType();

                        if (type.equals(prevAct.getType())) {
                            PlanElement leg = newPlanElements.pop();
                            assert (leg instanceof Leg);
                            PlanElement prevActPopped = newPlanElements.pop();
                            assert (prevActPopped == prevAct);

                            // Choose activity with longest duration
                            if (act.getEndTime() - act.getStartTime() > prevAct.getEndTime()
                                    - prevAct.getStartTime())
                                act.setStartTime(prevAct.getStartTime());
                            else {
                                prevAct.setEndTime(act.getEndTime());
                                act = prevAct;
                            }

                            newPe = act;
                        }
                    }

                    prevAct = act;
                }

                newPlanElements.add(newPe);
            }

            planElements.clear();
            planElements.addAll(newPlanElements);

        }
    }
}