Skip to content

Commit

Permalink
implemented location assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
rakow committed Jun 5, 2024
1 parent 37995e1 commit a919cae
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.apache.commons.csv.CSVRecord;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Coord;
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.Population;
Expand All @@ -19,6 +21,7 @@
import java.util.List;
import java.util.Map;
import java.util.SplittableRandom;
import java.util.stream.Collectors;

@CommandLine.Command(
name = "assign-reference-population",
Expand Down Expand Up @@ -88,6 +91,7 @@ public Integer call() throws Exception {

RunActivitySampling sampling = new RunActivitySampling(persons, planBuilder.getActivities(), population.getFactory(), 1);

int i = 0;
for (Map.Entry<String, CSVRecord> e : ProgressBar.wrap(persons, "Assigning reference population")) {

CSVRecord p = e.getValue();
Expand Down Expand Up @@ -115,9 +119,10 @@ public Integer call() throws Exception {
continue;

// Create the base daily plan (without locations)
Plan plan = sampling.createPlan(Attributes.getHomeCoord(person), e.getKey());
Coord homeCoord = Attributes.getHomeCoord(person);
Plan plan = sampling.createPlan(homeCoord, e.getKey());

boolean success = planBuilder.assignLocationsFromZones(e.getKey(), plan);
boolean success = planBuilder.assignLocationsFromZones(e.getKey(), plan, homeCoord);

if (success) {
sampling.copyAttributes(p, person);
Expand All @@ -126,11 +131,17 @@ public Integer call() throws Exception {
person.addPlan(plan);
person.setSelectedPlan(plan);

String refModes = TripStructureUtils.getLegs(plan).stream().map(Leg::getMode).collect(Collectors.joining("-"));
person.getAttributes().putAttribute(Attributes.REF_MODES, refModes);

// remove person that have been used as reference
refPersons.remove(person);
i++;
}
}

log.info("Assigned {}/{} reference persons", i, population.getPersons().size());

PopulationUtils.writePopulation(population, output.toString());

return 0;
Expand Down
194 changes: 78 additions & 116 deletions src/main/java/org/matsim/prepare/population/PlanBuilder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.matsim.prepare.population;

import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
Expand All @@ -14,20 +15,21 @@
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.api.core.v01.population.Activity;
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.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.
Expand Down Expand Up @@ -84,6 +86,24 @@ public PlanBuilder(ShpOptions zones, FacilityIndex facilities, Path activityPath
}
}

/**
* Add necesarry vehicles to the scenario.
*/
public static void addVehiclesToScenario(Scenario scenario) {
Id<Vehicle> 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<Vehicle> ride = Id.createVehicleId("ride");
vehicle = scenario.getVehicles().getFactory().createVehicle(
ride, scenario.getVehicles().getVehicleTypes().get(Id.create("ride", VehicleType.class))
);
scenario.getVehicles().addVehicle(vehicle);

}

private void readActivities(CSVParser csv, String idColumn) {

String currentId = null;
Expand Down Expand Up @@ -120,24 +140,6 @@ public Map<String, List<CSVRecord>> getActivities() {
return activities;
}

/**
* Add necesarry vehicles to the scenario.
*/
public static void addVehiclesToScenario(Scenario scenario) {
Id<Vehicle> 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<Vehicle> 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.
*/
Expand All @@ -161,6 +163,7 @@ public Long2ObjectMap<List<Person>> createHomeIndex(Population population) {

/**
* Find the home zone for a person.
*
* @return -1 if not known
*/
public long findHomeZone(String personId) {
Expand All @@ -177,9 +180,10 @@ public long findHomeZone(String personId) {

/**
* Assigns location from reference data to a person.
*
* @return whether the assignment was successful
*/
public boolean assignLocationsFromZones(String personId, Plan plan) {
public boolean assignLocationsFromZones(String personId, Plan plan, Coord homeCoord) {

List<CSVRecord> activities = this.activities.get(personId);
List<Activity> existing = TripStructureUtils.getActivities(plan, TripStructureUtils.StageActivityHandling.ExcludeStageActivities);
Expand All @@ -189,8 +193,12 @@ public boolean assignLocationsFromZones(String personId, Plan plan) {
if (activities.size() != existing.size())
return false;

// The location happen at the same place (if in same zone)
Map<TypeLocation, ActivityFacility> fixedLocations = new HashMap<>();
ActLocation home = new ActLocation(null, homeCoord);

List<List<ActLocation>> possibleLocations = new ArrayList<>();

// Distances between activities in meter
DoubleList dists = new DoubleArrayList();

for (int i = 0; i < activities.size(); i++) {

Expand All @@ -199,13 +207,13 @@ public boolean assignLocationsFromZones(String personId, Plan plan) {

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());
dists.add(InitLocationChoice.beelineDist(Double.parseDouble(ref.get("leg_dist"))));

if (type.equals("home")) {
possibleLocations.add(List.of(home));
continue;
}


Location loc = new Location(ref.get("location"), ref.get("zone"));
long id = features.getOrDefault(loc, -1);
if (id == -1) {
Expand All @@ -214,117 +222,71 @@ public boolean assignLocationsFromZones(String personId, Plan plan) {

Set<ActivityFacility> facilities = zones.get(id);

// TODO: think of matching and selection here



if (type.equals("work") || type.startsWith("edu")) {
// TODO: store the location
List<ActivityFacility> subSet = facilities.stream().filter(f -> f.getActivityOptions().containsKey(type)).toList();

if (subSet.isEmpty()) {
// If there is no location with the correct type, choose from all possible coordinates
possibleLocations.add(
facilities.stream().map(f -> new ActLocation(null, f.getCoord())).toList()
);
} else {
possibleLocations.add(
subSet.stream().map(f -> new ActLocation(f, f.getCoord())).toList()
);
}
}

return true;
}

/**
* Sample pair of from and to facility. Tries to find relation with similar distance to the input.
*/
private Pair<Coord, Coord> sampleOD(Row row) {

Set<Coord> from = matchLocation(row.getString("from_location"), row.getString("from_zone"));
Set<Coord> to = matchLocation(row.getString("to_location"), row.getString("to_zone"));
if (from == null || from.isEmpty() || to == null || to.isEmpty())
return null;
List<ActLocation> chosen = sampleLocation(possibleLocations, dists);

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();
for (int i = 0; i < chosen.size(); i++) {
ActLocation loc = chosen.get(i);
Activity activity = existing.get(i);

double newDist = CoordUtils.calcEuclideanDistance(newF, newT);
if (Math.abs(newDist - targetDist) < Math.abs(dist - targetDist)) {
dist = newDist;
f = newF;
t = newT;
activity.setLinkId(null);
if (loc.facility() != null) {
activity.setFacilityId(loc.facility().getId());
} else {
activity.setCoord(loc.coord());
}

i++;
} while (i < 8);
}


return Pair.of(f, t);
return true;
}

/**
* Sample destination from a row and existing location.
* Chooses from a list of possible locations such that difference to the references distances is minimized.
*/
private Coord sampleDest(Row row, Coord coord) {
private List<ActLocation> sampleLocation(List<List<ActLocation>> locations, DoubleList dists) {

Set<Coord> to = matchLocation(row.getString("to_location"), row.getString("to_zone"));
if (to == null || to.isEmpty())
return null;
double err = Double.POSITIVE_INFINITY;
List<ActLocation> best = 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;
for (int k = 0; k < 100; k++) {
List<ActLocation> current = new ArrayList<>();
for (List<ActLocation> locs : locations) {
current.add(locs.get(rnd.nextInt(locs.size())));
}

i++;
} while (i < 8);


return f;
}


/**
* Select a random facility for a person.
*/
private Set<Coord> 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<ActivityFacility> facilities = zones.get(id);

if (facilities == null) {
if (warnings.add(loc))
log.error("No facilities found in zone {}", loc);
double currentErr = 0;
for (int i = 1; i < current.size(); i++) {
double dist = CoordUtils.calcEuclideanDistance(current.get(i - 1).coord(), current.get(i).coord());
currentErr += Math.abs(dist - dists.getDouble(i));
}

return null;
if (currentErr < err || best == null) {
err = currentErr;
best = current;
}
}

return facilities.stream().map(ActivityFacility::getCoord).collect(Collectors.toSet());
return best;
}

private record Location(String name, String zone) {
}

private record TypeLocation(String type, String name, String zone) {
private record ActLocation(ActivityFacility facility, Coord coord) {
}

}

0 comments on commit a919cae

Please sign in to comment.