From 37995e194bc6f1b47656db36c45f2ecf68edf586 Mon Sep 17 00:00:00 2001 From: rakow Date: Tue, 4 Jun 2024 22:09:13 +0200 Subject: [PATCH] re-worked the plan builder --- .../prepare/RunOpenBerlinCalibration.java | 2 +- .../choices/AssignReferencePopulation.java | 68 ---- .../matsim/prepare/choices/PlanBuilder.java | 2 + .../population/AssignReferencePopulation.java | 138 ++++++++ .../matsim/prepare/population/Attributes.java | 2 + .../prepare/population/PersonMatcher.java | 33 +- .../prepare/population/PlanBuilder.java | 330 ++++++++++++++++++ .../population/RunActivitySampling.java | 60 ++-- 8 files changed, 545 insertions(+), 90 deletions(-) delete mode 100644 src/main/java/org/matsim/prepare/choices/AssignReferencePopulation.java create mode 100644 src/main/java/org/matsim/prepare/population/AssignReferencePopulation.java create mode 100644 src/main/java/org/matsim/prepare/population/PlanBuilder.java diff --git a/src/main/java/org/matsim/prepare/RunOpenBerlinCalibration.java b/src/main/java/org/matsim/prepare/RunOpenBerlinCalibration.java index ea308718..133a3be8 100644 --- a/src/main/java/org/matsim/prepare/RunOpenBerlinCalibration.java +++ b/src/main/java/org/matsim/prepare/RunOpenBerlinCalibration.java @@ -44,7 +44,7 @@ import org.matsim.core.scoring.SumScoringFunction; import org.matsim.core.scoring.functions.*; import org.matsim.core.utils.geometry.CoordUtils; -import org.matsim.prepare.choices.AssignReferencePopulation; +import org.matsim.prepare.population.AssignReferencePopulation; import org.matsim.prepare.choices.ComputePlanChoices; import org.matsim.prepare.choices.ComputeTripChoices; import org.matsim.prepare.counts.CreateCountsFromGeoPortalBerlin; diff --git a/src/main/java/org/matsim/prepare/choices/AssignReferencePopulation.java b/src/main/java/org/matsim/prepare/choices/AssignReferencePopulation.java deleted file mode 100644 index e6b01584..00000000 --- a/src/main/java/org/matsim/prepare/choices/AssignReferencePopulation.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.matsim.prepare.choices; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.matsim.api.core.v01.population.Population; -import org.matsim.application.MATSimAppCommand; -import org.matsim.application.options.ShpOptions; -import org.matsim.core.population.PopulationUtils; -import picocli.CommandLine; - -import java.nio.file.Files; -import java.nio.file.Path; - -@CommandLine.Command( - name = "assign-reference-population", - description = "Assigns persons from reference data to a population." -) -public class AssignReferencePopulation implements MATSimAppCommand { - - private static final Logger log = LogManager.getLogger(AssignReferencePopulation.class); - - - @CommandLine.Option(names = "--population", description = "Input population path.", required = true) - private String populationPath; - - @CommandLine.Option(names = "--persons", description = "Input persons from survey data, in matsim-python-tools format.", required = true) - private Path persons; - - @CommandLine.Option(names = "--trips", description = "Input trips from survey data, in matsim-python-tools format.", required = true) - private Path trips; - - @CommandLine.Option(names = "--facilities", description = "Shp file with facilities", required = true) - private Path facilities; - - @CommandLine.Option(names = "--output", description = "Output population path.", required = true) - private Path output; - - @CommandLine.Mixin - private ShpOptions shp; - - public static void main(String[] args) { - new AssignReferencePopulation().execute(args); - } - - @Override - public Integer call() throws Exception { - - if (!shp.isDefined()) { - log.error("No shapefile defined. Please specify a shapefile for the zones using the --shp option."); - return 2; - } - - if (!Files.exists(trips)) { - log.error("Input trip file does not exist: {}", trips); - return 2; - } - - Population population = PopulationUtils.readPopulation(populationPath); - - PlanBuilder builder = new PlanBuilder(shp, new ShpOptions(facilities, null, null), population.getFactory()); - - builder.mergePlans(population, trips, persons); - - PopulationUtils.writePopulation(population, output.toString()); - - return 0; - } -} diff --git a/src/main/java/org/matsim/prepare/choices/PlanBuilder.java b/src/main/java/org/matsim/prepare/choices/PlanBuilder.java index 09558fb0..4c02c9f6 100644 --- a/src/main/java/org/matsim/prepare/choices/PlanBuilder.java +++ b/src/main/java/org/matsim/prepare/choices/PlanBuilder.java @@ -34,7 +34,9 @@ /** * Utility class to build plans from data. + * @deprecated use new plan builder in org.matsim.prepare.population */ +@Deprecated public class PlanBuilder { private static final Logger log = LogManager.getLogger(PlanBuilder.class); diff --git a/src/main/java/org/matsim/prepare/population/AssignReferencePopulation.java b/src/main/java/org/matsim/prepare/population/AssignReferencePopulation.java new file mode 100644 index 00000000..ae852195 --- /dev/null +++ b/src/main/java/org/matsim/prepare/population/AssignReferencePopulation.java @@ -0,0 +1,138 @@ +package org.matsim.prepare.population; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import me.tongfei.progressbar.ProgressBar; +import org.apache.commons.csv.CSVRecord; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.Plan; +import org.matsim.api.core.v01.population.Population; +import org.matsim.application.MATSimAppCommand; +import org.matsim.application.options.ShpOptions; +import org.matsim.core.population.PopulationUtils; +import org.matsim.core.router.TripStructureUtils; +import picocli.CommandLine; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.SplittableRandom; + +@CommandLine.Command( + name = "assign-reference-population", + description = "Assigns persons from reference data to a population." +) +public class AssignReferencePopulation implements MATSimAppCommand { + + private static final Logger log = LogManager.getLogger(AssignReferencePopulation.class); + + + @CommandLine.Option(names = "--population", description = "Input population path.", required = true) + private String populationPath; + + @CommandLine.Option(names = "--persons", description = "Input persons from survey data, in matsim-python-tools format.", required = true) + private Path personsPath; + + @CommandLine.Option(names = "--activities", description = "Path to activity table from survey data.", required = true) + private Path activityPath; + + @CommandLine.Option(names = "--facilities", description = "Path to facilities file.", required = true) + private Path facilityPath; + + @CommandLine.Option(names = "--network", description = "Path to network file", required = true) + private Path networkPath; + + @CommandLine.Option(names = "--output", description = "Output population path.", required = true) + private Path output; + + @CommandLine.Mixin + private ShpOptions shp; + + private FacilityIndex facilities; + + private PersonMatcher persons; + + public static void main(String[] args) { + new AssignReferencePopulation().execute(args); + } + + @Override + public Integer call() throws Exception { + + if (!shp.isDefined()) { + log.error("No shapefile defined. Please specify a shapefile for the zones using the --shp option."); + return 2; + } + + if (!Files.exists(activityPath)) { + log.error("Input activity file does not exist: {}", activityPath); + return 2; + } + + Population population = PopulationUtils.readPopulation(populationPath); + + SplittableRandom rnd = new SplittableRandom(0); + persons = new PersonMatcher("idx", personsPath); + facilities = new FacilityIndex(facilityPath.toString()); + + PlanBuilder planBuilder = new PlanBuilder(shp, facilities, activityPath); + + Long2ObjectMap> homeIndex = planBuilder.createHomeIndex(population); + + // Remove persons without legs, these can not be assigned + for (List list : homeIndex.values()) { + list.removeIf(p -> TripStructureUtils.getLegs(p.getSelectedPlan()).isEmpty()); + } + + RunActivitySampling sampling = new RunActivitySampling(persons, planBuilder.getActivities(), population.getFactory(), 1); + + for (Map.Entry e : ProgressBar.wrap(persons, "Assigning reference population")) { + + CSVRecord p = e.getValue(); + if (!p.get("seq").equals("0")) + continue; + + if (p.get("mobile_on_day").equals("false")) + continue; + + long zone = planBuilder.findHomeZone(e.getKey()); + + // No home zone known + if (zone < 0) + continue; + + List refPersons = homeIndex.get(zone); + + if (refPersons == null) + continue; + + Person person = persons.matchEntry(e.getValue(), refPersons, rnd); + + // No persons matched + if (person == null) + continue; + + // Create the base daily plan (without locations) + Plan plan = sampling.createPlan(Attributes.getHomeCoord(person), e.getKey()); + + boolean success = planBuilder.assignLocationsFromZones(e.getKey(), plan); + + if (success) { + sampling.copyAttributes(p, person); + person.getAttributes().putAttribute(Attributes.REF_WEIGHT, p.get("p_weight")); + person.removePlan(person.getSelectedPlan()); + person.addPlan(plan); + person.setSelectedPlan(plan); + + // remove person that have been used as reference + refPersons.remove(person); + } + } + + PopulationUtils.writePopulation(population, output.toString()); + + return 0; + } +} diff --git a/src/main/java/org/matsim/prepare/population/Attributes.java b/src/main/java/org/matsim/prepare/population/Attributes.java index cd17fc90..b2d6bb8e 100644 --- a/src/main/java/org/matsim/prepare/population/Attributes.java +++ b/src/main/java/org/matsim/prepare/population/Attributes.java @@ -36,6 +36,8 @@ public final class Attributes { public static final String RESTRICTED_MOBILITY = "restricted_mobility"; public static final String ECONOMIC_STATUS = "economic_status"; public static final String HOUSEHOLD_SIZE = "household_size"; + public static final String REF_WEIGHT = "ref_weight"; + public static final String REF_MODES = "ref_modes"; public static final String ATTRACTION_WORK = "attraction_work"; public static final String ATTRACTION_OTHER = "attraction_other"; diff --git a/src/main/java/org/matsim/prepare/population/PersonMatcher.java b/src/main/java/org/matsim/prepare/population/PersonMatcher.java index a14355cf..e010e427 100644 --- a/src/main/java/org/matsim/prepare/population/PersonMatcher.java +++ b/src/main/java/org/matsim/prepare/population/PersonMatcher.java @@ -5,6 +5,7 @@ import org.apache.commons.csv.CSVRecord; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.matsim.api.core.v01.population.Person; import org.matsim.application.options.CsvOptions; import org.matsim.core.population.PersonUtils; @@ -13,13 +14,14 @@ import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; /** * This class is used to read and match persons from the reference data in csv format. */ -public class PersonMatcher { +public class PersonMatcher implements Iterable> { private static final Logger log = LogManager.getLogger(PersonMatcher.class); @@ -60,6 +62,29 @@ public String matchPerson(Person person, SplittableRandom rnd) { return subgroup.get(rnd.nextInt(subgroup.size())); } + /** + * Matches a person csv entry to one person in a list. + * @return null if no match was found + */ + public Person matchEntry(CSVRecord p, List refPersons, SplittableRandom rnd) { + + int regionType = Integer.parseInt(p.get("region_type")); + String gender = p.get("gender"); + String employment = p.get("employment"); + int age = Integer.parseInt(p.get("age")); + + Set keys = createKey(gender, age, regionType, employment).collect(Collectors.toSet()); + + List matched = refPersons.stream() + .filter(person -> keys.contains(createKey(person))) + .toList(); + + if (matched.isEmpty()) + return null; + + return matched.get(rnd.nextInt(matched.size())); + } + /** * Return reference person with given index. */ @@ -135,6 +160,12 @@ private Key createKey(Person person) { return new Key(gender, age, regionType, employed); } + @NotNull + @Override + public Iterator> iterator() { + return persons.entrySet().iterator(); + } + /** * Key used to match persons. */ diff --git a/src/main/java/org/matsim/prepare/population/PlanBuilder.java b/src/main/java/org/matsim/prepare/population/PlanBuilder.java new file mode 100644 index 00000000..c0d4156f --- /dev/null +++ b/src/main/java/org/matsim/prepare/population/PlanBuilder.java @@ -0,0 +1,330 @@ +package org.matsim.prepare.population; + +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.geotools.api.feature.simple.SimpleFeature; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.api.core.v01.population.*; +import org.matsim.application.options.CsvOptions; +import org.matsim.application.options.ShpOptions; +import org.matsim.core.router.TripStructureUtils; +import org.matsim.core.utils.geometry.CoordUtils; +import org.matsim.facilities.ActivityFacility; +import org.matsim.vehicles.Vehicle; +import org.matsim.vehicles.VehicleType; +import tech.tablesaw.api.Row; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Utility class to build plans from activity data. + */ +public class PlanBuilder { + + private static final Logger log = LogManager.getLogger(PlanBuilder.class); + private static final CsvOptions csv = new CsvOptions(CSVFormat.Predefined.Default); + + /** + * Stores of warning for a zone was generated. + */ + private final Set warnings = new HashSet<>(); + + /** + * Maps zone ids to contained facilities. + */ + private final Long2ObjectMap> zones = new Long2ObjectOpenHashMap<>(); + + private final ShpOptions.Index zoneIndex; + + /** + * Maps location key to zone id. + */ + private final Object2LongMap features = new Object2LongOpenHashMap<>(); + + private final Map> activities = new HashMap<>(); + + private final SplittableRandom rnd = new SplittableRandom(); + + /** + * Drop plans with more than this number of trips. + */ + private int maxTripNumber = 0; + + public PlanBuilder(ShpOptions zones, FacilityIndex facilities, Path activityPath) throws IOException { + // Collect all zones + for (SimpleFeature ft : zones.readFeatures()) { + features.put(new Location((String) ft.getAttribute("raum_id"), (String) ft.getAttribute("zone")), + (long) ft.getAttribute("id")); + } + + zoneIndex = zones.createIndex("id"); + + for (ActivityFacility ft : facilities.all.getFacilities().values()) { + Long result = zoneIndex.query(ft.getCoord()); + if (result != null) { + this.zones.computeIfAbsent(result, k -> new HashSet<>()).add(ft); + } + } + + try (CSVParser parser = csv.createParser(activityPath)) { + readActivities(parser, "p_id"); + } + } + + private void readActivities(CSVParser csv, String idColumn) { + + String currentId = null; + List current = null; + + int i = 0; + for (CSVRecord r : csv) { + + String pId = r.get(idColumn); + + if (!Objects.equals(pId, currentId)) { + if (current != null) + activities.put(currentId, current); + + currentId = pId; + current = new ArrayList<>(); + } + + current.add(r); + i++; + } + + if (current != null && !current.isEmpty()) { + activities.put(currentId, current); + } + + log.info("Read {} activities for {} persons", i, activities.size()); + } + + /** + * Return all read activities. + */ + public Map> getActivities() { + return activities; + } + + /** + * Add necesarry vehicles to the scenario. + */ + public static void addVehiclesToScenario(Scenario scenario) { + Id car = Id.createVehicleId("car"); + Vehicle vehicle = scenario.getVehicles().getFactory().createVehicle( + car, scenario.getVehicles().getVehicleTypes().get(Id.create("car", VehicleType.class)) + ); + scenario.getVehicles().addVehicle(vehicle); + + Id ride = Id.createVehicleId("ride"); + vehicle = scenario.getVehicles().getFactory().createVehicle( + ride, scenario.getVehicles().getVehicleTypes().get(Id.create("ride", VehicleType.class)) + ); + scenario.getVehicles().addVehicle(vehicle); + + } + + /** + * Create a map for each zone to set of persons living there. + */ + public Long2ObjectMap> createHomeIndex(Population population) { + + Long2ObjectMap> personsByHome = new Long2ObjectOpenHashMap<>(); + for (Person person : population.getPersons().values()) { + + double homeX = (double) person.getAttributes().getAttribute(Attributes.HOME_X); + double homeY = (double) person.getAttributes().getAttribute(Attributes.HOME_Y); + + Long home = zoneIndex.query(new Coord(homeX, homeY)); + + if (home != null) { + personsByHome.computeIfAbsent(home, k -> new ArrayList<>()).add(person); + } + } + + return personsByHome; + } + + /** + * Find the home zone for a person. + * @return -1 if not known + */ + public long findHomeZone(String personId) { + + List activities = this.activities.get(personId); + + Optional home = activities.stream().filter(r -> r.get("type").equals("home")).findFirst(); + if (home.isEmpty()) + return -1; + + Location loc = new Location(home.get().get("location"), home.get().get("zone")); + return features.getOrDefault(loc, -1); + } + + /** + * Assigns location from reference data to a person. + * @return whether the assignment was successful + */ + public boolean assignLocationsFromZones(String personId, Plan plan) { + + List activities = this.activities.get(personId); + List existing = TripStructureUtils.getActivities(plan, TripStructureUtils.StageActivityHandling.ExcludeStageActivities); + + // If activities don't match, this entry is skipped + // this can happen if and end home activity has been added at the end + if (activities.size() != existing.size()) + return false; + + // The location happen at the same place (if in same zone) + Map fixedLocations = new HashMap<>(); + + for (int i = 0; i < activities.size(); i++) { + + CSVRecord ref = activities.get(i); + Activity activity = existing.get(i); + + String type = activity.getType(); + + TypeLocation tLoc = new TypeLocation(type, ref.get("location"), ref.get("zone")); + if (fixedLocations.containsKey(tLoc)) { + activity.setFacilityId(fixedLocations.get(tLoc).getId()); + continue; + } + + + Location loc = new Location(ref.get("location"), ref.get("zone")); + long id = features.getOrDefault(loc, -1); + if (id == -1) { + return false; + } + + Set facilities = zones.get(id); + + // TODO: think of matching and selection here + + + + if (type.equals("work") || type.startsWith("edu")) { + // TODO: store the location + } + } + + return true; + } + + /** + * Sample pair of from and to facility. Tries to find relation with similar distance to the input. + */ + private Pair sampleOD(Row row) { + + Set from = matchLocation(row.getString("from_location"), row.getString("from_zone")); + Set to = matchLocation(row.getString("to_location"), row.getString("to_zone")); + if (from == null || from.isEmpty() || to == null || to.isEmpty()) + return null; + + double targetDist = InitLocationChoice.beelineDist(row.getDouble("gis_length") * 1000); + + double dist = Double.POSITIVE_INFINITY; + Coord f = null; + Coord t = null; + int i = 0; + do { + + Coord newF = from.stream().skip(rnd.nextInt(from.size())).findFirst().orElseThrow(); + Coord newT = to.stream().skip(rnd.nextInt(to.size())).findFirst().orElseThrow(); + + double newDist = CoordUtils.calcEuclideanDistance(newF, newT); + if (Math.abs(newDist - targetDist) < Math.abs(dist - targetDist)) { + dist = newDist; + f = newF; + t = newT; + } + + i++; + } while (i < 8); + + + return Pair.of(f, t); + } + + /** + * Sample destination from a row and existing location. + */ + private Coord sampleDest(Row row, Coord coord) { + + Set to = matchLocation(row.getString("to_location"), row.getString("to_zone")); + if (to == null || to.isEmpty()) + return null; + + double targetDist = InitLocationChoice.beelineDist(row.getDouble("gis_length") * 1000); + double dist = Double.POSITIVE_INFINITY; + Coord f = null; + + int i = 0; + do { + + Coord newT = to.stream().skip(rnd.nextInt(to.size())).findFirst().orElseThrow(); + + double newDist = CoordUtils.calcEuclideanDistance(coord, newT); + + if (Math.abs(newDist - targetDist) < Math.abs(dist - targetDist)) { + dist = newDist; + f = newT; + } + + i++; + } while (i < 8); + + + return f; + } + + + /** + * Select a random facility for a person. + */ + private Set matchLocation(String location, String zone) { + + Location loc = new Location(location, zone); + long id = features.getOrDefault(loc, -1); + + if (id == -1) { + if (warnings.add(loc)) + log.error("No zone found for location {} and zone {}", location, zone); + + return null; + } + + Set facilities = zones.get(id); + + if (facilities == null) { + if (warnings.add(loc)) + log.error("No facilities found in zone {}", loc); + + return null; + } + + return facilities.stream().map(ActivityFacility::getCoord).collect(Collectors.toSet()); + } + + private record Location(String name, String zone) { + } + + private record TypeLocation(String type, String name, String zone) { + } + +} diff --git a/src/main/java/org/matsim/prepare/population/RunActivitySampling.java b/src/main/java/org/matsim/prepare/population/RunActivitySampling.java index 682063dd..2aa7e78e 100644 --- a/src/main/java/org/matsim/prepare/population/RunActivitySampling.java +++ b/src/main/java/org/matsim/prepare/population/RunActivitySampling.java @@ -1,9 +1,5 @@ package org.matsim.prepare.population; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -24,8 +20,6 @@ import java.nio.file.Path; import java.util.*; -import java.util.stream.IntStream; -import java.util.stream.Stream; @CommandLine.Command( name = "activity-sampling", @@ -56,6 +50,23 @@ public class RunActivitySampling implements MATSimAppCommand, PersonAlgorithm { private PopulationFactory factory; private PersonMatcher matcher; + /** + * Default constructor. Can only be used with command line. + */ + public RunActivitySampling() { + } + + /** + * Constructor that allows to use the run method directly and not as command. + */ + RunActivitySampling(PersonMatcher matcher, Map> activities, PopulationFactory factory, long seed) { + this.matcher = matcher; + this.activities.putAll(activities); + this.factory = factory; + this.seed = seed; + this.ctxs = ThreadLocal.withInitial(() -> new Context(new SplittableRandom(seed))); + } + public static void main(String[] args) { new RunActivitySampling().execute(args); } @@ -123,27 +134,15 @@ private void readActivities(CSVParser csv) { } - @Override public void run(Person person) { SplittableRandom rnd = ctxs.get().rnd; - String idx = matcher.matchPerson(person, rnd); + String idx = matcher.matchPerson(person, rnd); CSVRecord row = matcher.getPerson(idx); - PersonUtils.setCarAvail(person, row.get("car_avail").equals("True") ? "always" : "never"); - PersonUtils.setLicence(person, row.get("driving_license").toLowerCase()); - PersonUtils.setIncome(person, Math.max(499, Double.parseDouble(row.get("income")))); - - person.getAttributes().putAttribute(Attributes.BIKE_AVAIL, row.get("bike_avail").equals("True") ? "always" : "never"); - person.getAttributes().putAttribute(Attributes.PT_ABO_AVAIL, row.get("pt_abo_avail").equals("True") ? "always" : "never"); - - person.getAttributes().putAttribute(Attributes.EMPLOYMENT, row.get("employment")); - person.getAttributes().putAttribute(Attributes.RESTRICTED_MOBILITY, row.get("restricted_mobility").equals("True")); - person.getAttributes().putAttribute(Attributes.ECONOMIC_STATUS, row.get("economic_status")); - person.getAttributes().putAttribute(Attributes.HOUSEHOLD_SIZE, Integer.parseInt(row.get("n_persons"))); - + copyAttributes(row, person); String mobile = row.get("mobile_on_day"); @@ -174,6 +173,20 @@ public void run(Person person) { } } + public void copyAttributes(CSVRecord row, Person person) { + PersonUtils.setCarAvail(person, row.get("car_avail").equals("True") ? "always" : "never"); + PersonUtils.setLicence(person, row.get("driving_license").toLowerCase()); + PersonUtils.setIncome(person, Math.max(499, Double.parseDouble(row.get("income")))); + + person.getAttributes().putAttribute(Attributes.BIKE_AVAIL, row.get("bike_avail").equals("True") ? "always" : "never"); + person.getAttributes().putAttribute(Attributes.PT_ABO_AVAIL, row.get("pt_abo_avail").equals("True") ? "always" : "never"); + + person.getAttributes().putAttribute(Attributes.EMPLOYMENT, row.get("employment")); + person.getAttributes().putAttribute(Attributes.RESTRICTED_MOBILITY, row.get("restricted_mobility").equals("True")); + person.getAttributes().putAttribute(Attributes.ECONOMIC_STATUS, row.get("economic_status")); + person.getAttributes().putAttribute(Attributes.HOUSEHOLD_SIZE, Integer.parseInt(row.get("n_persons"))); + } + /** * Randomize the duration slightly, depending on total duration. */ @@ -190,6 +203,13 @@ private int randomizeDuration(int minutes, SplittableRandom rnd) { return minutes * 60 + rnd.nextInt(1200) - 600; } + /** + * Create plan for a person using given id. + */ + public Plan createPlan(Coord homeCoord, String personId) { + return createPlan(homeCoord, activities.get(personId), ctxs.get().rnd); + } + private Plan createPlan(Coord homeCoord, List activities, SplittableRandom rnd) { Plan plan = factory.createPlan();