diff --git a/core/src/main/java/org/eqasim/core/simulation/EqasimConfigurator.java b/core/src/main/java/org/eqasim/core/simulation/EqasimConfigurator.java index 1a822c2bd..f8b329b02 100644 --- a/core/src/main/java/org/eqasim/core/simulation/EqasimConfigurator.java +++ b/core/src/main/java/org/eqasim/core/simulation/EqasimConfigurator.java @@ -19,6 +19,12 @@ import org.eqasim.core.simulation.modes.feeder_drt.MultiModeFeederDrtModule; import org.eqasim.core.simulation.modes.feeder_drt.config.MultiModeFeederDrtConfigGroup; import org.eqasim.core.simulation.modes.feeder_drt.mode_choice.EqasimFeederDrtModeChoiceModule; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.TransitWithAbstractAccessModule; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.TransitWithAbstractAbstractAccessModuleConfigGroup; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.TransitWithAbstractAccessQSimModule; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.TransitWithAbstractAccessModeChoiceModule; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.AbstractAccessRouteFactory; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.DefaultAbstractAccessRoute; import org.eqasim.core.simulation.termination.EqasimTerminationConfigGroup; import org.eqasim.core.simulation.termination.EqasimTerminationModule; import org.eqasim.core.simulation.termination.mode_share.ModeShareModule; @@ -55,81 +61,87 @@ public class EqasimConfigurator { private final Map>> optionalQSimComponentConfigurationSteps = new HashMap<>(); private final Map optionalConfigGroups = new HashMap<>(); - public EqasimConfigurator() { - configGroups.addAll(Arrays.asList( // - new SwissRailRaptorConfigGroup(), // - new EqasimConfigGroup(), // - new DiscreteModeChoiceConfigGroup() // - )); - - modules.addAll(Arrays.asList( // - new SwissRailRaptorModule(), // - new EqasimTransitModule(), // - new DiscreteModeChoiceModule(), // - new EqasimComponentsModule(), // - new EpsilonModule() // - )); - - qsimModules.addAll(Arrays.asList( // - new EqasimTransitQSimModule(), // - new EqasimTrafficQSimModule() // - )); - - this.registerOptionalConfigGroup(new MultiModeDrtConfigGroup(), - Collections.singleton(new MultiModeDrtModule()), - Collections.emptyList(), - Collections.singletonList((controller, components) -> - DvrpQSimComponents.activateAllModes((MultiModal) controller.getConfig().getModules().get(MultiModeDrtConfigGroup.GROUP_NAME)).configure(components))); - - this.registerOptionalConfigGroup(new DvrpConfigGroup(), Collections.singleton(new DvrpModule())); - this.registerOptionalConfigGroup(new EqasimTerminationConfigGroup(), List.of(new EqasimTerminationModule(), new ModeShareModule())); - this.registerOptionalConfigGroup(new MultiModeFeederDrtConfigGroup(), List.of(new MultiModeFeederDrtModule(), new EqasimFeederDrtModeChoiceModule())); - } - - public ConfigGroup[] getConfigGroups() { - return configGroups.toArray(ConfigGroup[]::new); - } - - public List getModules() { - return modules; - } - - public List getQSimModules() { - return qsimModules; - } - - public void configureController(Controler controller) { - - // The optional modules are added after the non-optional ones because we consider that their bindings have less priority - this.optionalModules.entrySet().stream() - .filter(e -> controller.getConfig().getModules().containsKey(e.getKey())) - .map(Map.Entry::getValue) - .flatMap(Collection::stream) - .forEach(controller::addOverridingModule); - - for (AbstractModule module : getModules()) { - controller.addOverridingModule(module); - } - - this.optionalQSimModules.entrySet().stream() - .filter(e -> controller.getConfig().getModules().containsKey(e.getKey())) - .map(Map.Entry::getValue) - .flatMap(Collection::stream) - .forEach(controller::addOverridingQSimModule); - - for (AbstractQSimModule module : getQSimModules()) { - controller.addOverridingQSimModule(module); - } - - controller.configureQSimComponents(components -> { - optionalQSimComponentConfigurationSteps.entrySet().stream() - .filter(e -> controller.getConfig().getModules().containsKey(e.getKey())) - .map(Map.Entry::getValue) - .flatMap(Collection::stream) - .forEach(step -> step.accept(controller, components)); - EqasimTransitQSimModule.configure(components, controller.getConfig()); - }); - } + public EqasimConfigurator() { + configGroups.addAll(Arrays.asList( // + new SwissRailRaptorConfigGroup(), // + new EqasimConfigGroup(), // + new DiscreteModeChoiceConfigGroup() // + )); + + modules.addAll(Arrays.asList( // + new SwissRailRaptorModule(), // + new EqasimTransitModule(), // + new DiscreteModeChoiceModule(), // + new EqasimComponentsModule(), // + new EpsilonModule() // + )); + + qsimModules.addAll(Arrays.asList( // + new EqasimTransitQSimModule(), // + new EqasimTrafficQSimModule() // + )); + + this.registerOptionalConfigGroup(new MultiModeDrtConfigGroup(), + Collections.singleton(new MultiModeDrtModule()), + Collections.emptyList(), + Collections.singletonList((controller, components) -> + DvrpQSimComponents.activateAllModes((MultiModal) controller.getConfig().getModules().get(MultiModeDrtConfigGroup.GROUP_NAME)).configure(components))); + + this.registerOptionalConfigGroup(new DvrpConfigGroup(), Collections.singleton(new DvrpModule())); + this.registerOptionalConfigGroup(new EqasimTerminationConfigGroup(), List.of(new EqasimTerminationModule(), new ModeShareModule())); + this.registerOptionalConfigGroup(new MultiModeFeederDrtConfigGroup(), List.of(new MultiModeFeederDrtModule(), new EqasimFeederDrtModeChoiceModule())); + this.registerOptionalConfigGroup( + new TransitWithAbstractAbstractAccessModuleConfigGroup(), + List.of(new TransitWithAbstractAccessModule(), + new TransitWithAbstractAccessModeChoiceModule()), + List.of(new TransitWithAbstractAccessQSimModule()), + Collections.singletonList((controller, components) -> TransitWithAbstractAccessQSimModule.configure(components, controller.getConfig()))); + } + + public ConfigGroup[] getConfigGroups() { + return configGroups.toArray(ConfigGroup[]::new); + } + + public List getModules() { + return modules; + } + + public List getQSimModules() { + return qsimModules; + } + + public void configureController(Controler controller) { + + // The optional modules are added after the non-optional ones because we consider that their bindings have less priority + this.optionalModules.entrySet().stream() + .filter(e -> controller.getConfig().getModules().containsKey(e.getKey())) + .map(Map.Entry::getValue) + .flatMap(Collection::stream) + .forEach(controller::addOverridingModule); + + for (AbstractModule module : getModules()) { + controller.addOverridingModule(module); + } + + this.optionalQSimModules.entrySet().stream() + .filter(e -> controller.getConfig().getModules().containsKey(e.getKey())) + .map(Map.Entry::getValue) + .flatMap(Collection::stream) + .forEach(controller::addOverridingQSimModule); + + for (AbstractQSimModule module : getQSimModules()) { + controller.addOverridingQSimModule(module); + } + + controller.configureQSimComponents(components -> { + optionalQSimComponentConfigurationSteps.entrySet().stream() + .filter(e -> controller.getConfig().getModules().containsKey(e.getKey())) + .map(Map.Entry::getValue) + .flatMap(Collection::stream) + .forEach(step -> step.accept(controller, components)); + EqasimTransitQSimModule.configure(components, controller.getConfig()); + }); + } protected void registerOptionalConfigGroup(ConfigGroup configGroup) { registerOptionalConfigGroup(configGroup, new ArrayList<>()); @@ -147,41 +159,42 @@ protected void registerOptionalConfigGroup(ConfigGroup configGroup, Collection()); this.optionalModules.get(configGroup.getName()).addAll(modules); - this.optionalQSimModules.putIfAbsent(configGroup.getName(), new ArrayList<>()); - this.optionalQSimModules.get(configGroup.getName()).addAll(qsimModules); - - this.optionalQSimComponentConfigurationSteps.putIfAbsent(configGroup.getName(), new ArrayList<>()); - this.optionalQSimComponentConfigurationSteps.get(configGroup.getName()).addAll(componentsConsumers); - } - - public void addOptionalConfigGroups(Config config) { - for(ConfigGroup configGroup: optionalConfigGroups.values()) { - if(config.getModules().get(configGroup.getName()) != null) { - config.addModule(configGroup); - } - } - } - - public void configureScenario(Scenario scenario) { - scenario.getPopulation().getFactory().getRouteFactories().setRouteFactory(DrtRoute.class, new DrtRouteFactory()); - } - - public void adjustScenario(Scenario scenario) { - for (Household household : scenario.getHouseholds().getHouseholds().values()) { - for (Id memberId : household.getMemberIds()) { - Person person = scenario.getPopulation().getPersons().get(memberId); - - if (person != null) { - copyAttribute(household, person, "bikeAvailability"); - copyAttribute(household, person, "spRegion"); - } - } - } - } - - static protected void copyAttribute(Household household, Person person, String attribute) { - if (household.getAttributes().getAsMap().containsKey(attribute)) { - person.getAttributes().putAttribute(attribute, household.getAttributes().getAttribute(attribute)); - } - } + this.optionalQSimModules.putIfAbsent(configGroup.getName(), new ArrayList<>()); + this.optionalQSimModules.get(configGroup.getName()).addAll(qsimModules); + + this.optionalQSimComponentConfigurationSteps.putIfAbsent(configGroup.getName(), new ArrayList<>()); + this.optionalQSimComponentConfigurationSteps.get(configGroup.getName()).addAll(componentsConsumers); + } + + public void addOptionalConfigGroups(Config config) { + for (ConfigGroup configGroup : optionalConfigGroups.values()) { + if (config.getModules().get(configGroup.getName()) != null) { + config.addModule(configGroup); + } + } + } + + public void configureScenario(Scenario scenario) { + scenario.getPopulation().getFactory().getRouteFactories().setRouteFactory(DrtRoute.class, new DrtRouteFactory()); + scenario.getPopulation().getFactory().getRouteFactories().setRouteFactory(DefaultAbstractAccessRoute.class, new AbstractAccessRouteFactory()); + } + + public void adjustScenario(Scenario scenario) { + for (Household household : scenario.getHouseholds().getHouseholds().values()) { + for (Id memberId : household.getMemberIds()) { + Person person = scenario.getPopulation().getPersons().get(memberId); + + if (person != null) { + copyAttribute(household, person, "bikeAvailability"); + copyAttribute(household, person, "spRegion"); + } + } + } + } + + static protected void copyAttribute(Household household, Person person, String attribute) { + if (household.getAttributes().getAsMap().containsKey(attribute)) { + person.getAttributes().putAttribute(attribute, household.getAttributes().getAttribute(attribute)); + } + } } diff --git a/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/PtPredictor.java b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/PtPredictor.java index cbe36090b..1f1404d40 100644 --- a/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/PtPredictor.java +++ b/core/src/main/java/org/eqasim/core/simulation/mode_choice/utilities/predictors/PtPredictor.java @@ -15,13 +15,17 @@ import com.google.inject.name.Named; public class PtPredictor extends CachedVariablePredictor { - private CostModel costModel; + private final CostModel costModel; @Inject public PtPredictor(@Named("pt") CostModel costModel) { this.costModel = costModel; } + protected CostModel getCostModel() { + return this.costModel; + } + @Override public PtVariables predict(Person person, DiscreteModeChoiceTrip trip, List elements) { /* @@ -39,8 +43,7 @@ public PtVariables predict(Person person, DiscreteModeChoiceTrip trip, List linkId) { + if(agent.getMode().equals(TransitWithAbstractAccessRoutingModule.ABSTRACT_ACCESS_LEG_MODE_NAME)) { + Leg leg = (Leg) ((PlanAgent) agent).getCurrentPlanElement(); + AbstractAccessRoute abstractAccessRoute = (AbstractAccessRoute) leg.getRoute(); + this.eventsManager.processEvent(new AbstractAccessDepartureEvent(now, agent.getId(), abstractAccessRoute.getAbstractAccessItemId(), abstractAccessRoute.getStartLinkId(), abstractAccessRoute.getEndLinkId(), abstractAccessRoute.isLeavingAccessCenter(), abstractAccessRoute.isRouted(), abstractAccessRoute.getDistance())); + } + return false; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAbstractAccessModuleConfigGroup.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAbstractAccessModuleConfigGroup.java new file mode 100644 index 000000000..ed948403c --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAbstractAccessModuleConfigGroup.java @@ -0,0 +1,44 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access; + +import jakarta.validation.constraints.NotNull; +import org.matsim.core.config.ReflectiveConfigGroup; + +public class TransitWithAbstractAbstractAccessModuleConfigGroup extends ReflectiveConfigGroup { + + public static final String GROUP_NAME = "transitWithAbstractAccess"; + + private static final String ACCESS_ITEMS_FILE_PATH = "accessItemsFilePath"; + + + private static final String MODE_NAME = "modeName"; + + @NotNull + private String accessItemsFilePath; + + @NotNull + private String modeName; + + public TransitWithAbstractAbstractAccessModuleConfigGroup() { + super(GROUP_NAME); + } + + @StringSetter(ACCESS_ITEMS_FILE_PATH) + public void setAccessItemsFilePath(String accessItemsFilePath) { + this.accessItemsFilePath = accessItemsFilePath; + } + + @StringGetter(ACCESS_ITEMS_FILE_PATH) + public String getAccessItemsFilePath() { + return this.accessItemsFilePath; + } + + @StringSetter(MODE_NAME) + public void setModeName(String modeName) { + this.modeName = modeName; + } + + @StringGetter(MODE_NAME) + public String getModeName() { + return this.modeName; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAccessModule.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAccessModule.java new file mode 100644 index 000000000..e144beaf9 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAccessModule.java @@ -0,0 +1,56 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccesses; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessesFileReader; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.analysis.AbstractAccessAnalysisOutputListener; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.analysis.AbstractAccessLegListener; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.TransitWithAbstractAccessRoutingModule; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.population.PopulationFactory; +import org.matsim.core.config.ConfigGroup; +import org.matsim.core.router.RoutingModule; +import org.matsim.pt.transitSchedule.api.TransitSchedule; + +import java.net.URL; + +public class TransitWithAbstractAccessModule extends AbstractEqasimExtension { + + @Inject + private TransitWithAbstractAbstractAccessModuleConfigGroup configGroup; + + + @Override + protected void installEqasimExtension() { + addRoutingModuleBinding(this.configGroup.getModeName()).to(TransitWithAbstractAccessRoutingModule.class); + bind(AbstractAccessLegListener.class).toProvider(new Provider<>() { + + @Inject + private AbstractAccesses abstractAccesses; + @Override + public AbstractAccessLegListener get() { + return new AbstractAccessLegListener(abstractAccesses); + } + }).asEagerSingleton(); + addControlerListenerBinding().to(AbstractAccessAnalysisOutputListener.class); + } + + @Provides + public TransitWithAbstractAccessRoutingModule provideTransitWithAbstractAccessRoutingModule(TransitSchedule transitSchedule, @Named("pt") RoutingModule ptRoutingModule, Network network, PopulationFactory populationFactory, AbstractAccesses abstractAccesses) { + return new TransitWithAbstractAccessRoutingModule(transitSchedule, abstractAccesses, network, ptRoutingModule, populationFactory); + } + + @Provides + @Singleton + public AbstractAccesses provideAbstractAccesses(TransitSchedule transitSchedule) { + AbstractAccessesFileReader fileReader = new AbstractAccessesFileReader(transitSchedule); + URL inputAccessFilePath = ConfigGroup.getInputFileURL(getConfig().getContext(), this.configGroup.getAccessItemsFilePath()); + fileReader.readFile(inputAccessFilePath.getPath()); + return new AbstractAccesses(fileReader.getAccessItems()); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAccessQSimModule.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAccessQSimModule.java new file mode 100644 index 000000000..d9e0663a3 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/TransitWithAbstractAccessQSimModule.java @@ -0,0 +1,23 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access; + +import org.matsim.core.config.Config; +import org.matsim.core.mobsim.qsim.AbstractQSimModule; +import org.matsim.core.mobsim.qsim.components.QSimComponentsConfig; + +public class TransitWithAbstractAccessQSimModule extends AbstractQSimModule { + + public static final String COMPONENT_NAME = "AbstractAccess"; + + @Override + protected void configureQSim() { + if(getConfig().getModules().get(TransitWithAbstractAbstractAccessModuleConfigGroup.GROUP_NAME) != null) { + addQSimComponentBinding(COMPONENT_NAME).to(AbstractAccessDepartureEventCreator.class); + } + } + + static public void configure(QSimComponentsConfig components, Config config) { + if(config.getModules().get(TransitWithAbstractAbstractAccessModuleConfigGroup.GROUP_NAME) != null) { + components.addNamedComponent(COMPONENT_NAME); + } + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessItem.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessItem.java new file mode 100644 index 000000000..2ea0e3769 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessItem.java @@ -0,0 +1,136 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access; + +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.core.utils.geometry.CoordUtils; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class AbstractAccessItem { + + private final Id id; + private final TransitStopFacility centerStop; + private final double radius; + private final double avgSpeedToCenterStop; + private final boolean useRoutedDistance; + private final String accessType; + private final int frequency; + + + public AbstractAccessItem(Id id, TransitStopFacility centerStop, double radius, double avgSpeedToCenterStop, String accessType, boolean useRoutedDistance, int frequency) { + this.id = id; + this.centerStop = centerStop; + this.radius = radius; + this.avgSpeedToCenterStop = avgSpeedToCenterStop; + this.accessType = accessType; + this.useRoutedDistance = useRoutedDistance; + this.frequency = frequency; + } + + public Id getId() { + return this.id; + } + + public boolean applies(Coord coord) { + return CoordUtils.calcEuclideanDistance(coord, this.centerStop.getCoord()) <= this.radius; + } + + public boolean applies(double distanceToCenter) { + return distanceToCenter <= this.radius; + } + public double getTimeToCenter(Coord coord) { + double distanceToCenter = this.getDistanceToCenter(coord); + return this.getTimeToCenter(distanceToCenter); + } + + public double getTimeToCenter(double distanceToCenter) { + if(this.applies(distanceToCenter)) { + return distanceToCenter / this.avgSpeedToCenterStop; + } + return Double.MAX_VALUE; + } + + public double getDistanceToCenter(Coord coord) { + return CoordUtils.calcEuclideanDistance(coord, this.centerStop.getCoord()); + } + public double getRadius() { + return this.radius; + } + + public boolean isUsingRoutedDistance() { + return this.useRoutedDistance; + } + + public String getAccessType() { + return this.accessType; + } + + public static AbstractAccessItem getFastestAccessItemForCoord (Coord coord, Collection accessItems) { + AbstractAccessItem best = null; + double minAccessTime = Double.MAX_VALUE; + for(AbstractAccessItem accessItem: accessItems) { + if(accessItem.applies(coord)) { + double accessTime = accessItem.getTimeToCenter(coord); + if(accessTime < minAccessTime) { + minAccessTime = accessTime; + best = accessItem; + } + } + } + return best; + } + + public TransitStopFacility getCenterStop() { + return this.centerStop; + } + + public double getAvgSpeedToCenterStop() { + return this.avgSpeedToCenterStop; + } + + public boolean covers(AbstractAccessItem other) { + if(!this.centerStop.getId().equals(other.centerStop.getId())) { + return false; + } + return this.radius >= other.radius && this.avgSpeedToCenterStop <= other.avgSpeedToCenterStop; + } + + public static IdMap> getAbstractAccessItemsByTransitStop(Collection accessItems) { + IdMap> itemsByTransitStop = new IdMap<>(TransitStopFacility.class); + for(AbstractAccessItem item: accessItems) { + if(!itemsByTransitStop.containsKey(item.getCenterStop().getId())) { + itemsByTransitStop.put(item.getCenterStop().getId(), new ArrayList<>()); + } + itemsByTransitStop.get(item.getCenterStop().getId()).add(item); + } + return itemsByTransitStop; + } + + public double getWaitTime() { + return (double) this.frequency / 2; + } + + public int getFrequency() { + return this.frequency; + } + public static Collection removeRedundantAbstractAccessItems(Collection accessItems) { + Collection result = new ArrayList<>(); + for(AbstractAccessItem item1: accessItems) { + boolean covered = false; + for(AbstractAccessItem item2: accessItems) { + if(item2.covers(item1)) { + covered = true; + break; + } + } + if(!covered) { + result.add(item1); + } + } + return result; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccesses.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccesses.java new file mode 100644 index 000000000..e1866fd5f --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccesses.java @@ -0,0 +1,24 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access; + +import org.matsim.api.core.v01.IdMap; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +import java.util.List; + +public class AbstractAccesses { + public final IdMap abstractAccessItems; + public final IdMap> abstractAccessItemsByTransitStop; + + public AbstractAccesses(IdMap abstractAccessItems) { + this.abstractAccessItems = abstractAccessItems; + this.abstractAccessItemsByTransitStop = AbstractAccessItem.getAbstractAccessItemsByTransitStop(this.abstractAccessItems.values()); + } + + public IdMap getAbstractAccessItems() { + return this.abstractAccessItems; + } + + public IdMap> getAbstractAccessItemsByTransitStop() { + return this.abstractAccessItemsByTransitStop; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessesFileReader.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessesFileReader.java new file mode 100644 index 000000000..cacf04963 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessesFileReader.java @@ -0,0 +1,114 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.core.utils.io.MatsimXmlParser; +import org.matsim.pt.transitSchedule.api.TransitSchedule; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; +import org.xml.sax.Attributes; + +import java.util.Stack; + +public class AbstractAccessesFileReader extends MatsimXmlParser { + + + public final static String ABSTRACT_ACCESS_TAG_NAME = "abstractAccessItem"; + + public final static String ROOT_TAG_NAME = "abstractAccessItems"; + + public final static String TRANSIT_STOP_ID_ATTR_NAME = "transitStopId"; + + public final static String ACCESS_ID_ATTR_NAME = "id"; + + public final static String RADIUS_ATTR_NAME = "radius"; + + public final static String AVG_SPEED_ATTR_NAME = "averageSpeed"; + + public final static String ACCESS_TYPE_ATTR_NAME = "accessType"; + + public final static String USING_ROUTED_DISTANCE_ATTR_NAME = "usingRoutedDistance"; + public final static String FREQUENCY_ATTR_NAME = "frequency"; + + private final TransitSchedule transitSchedule; + + private final IdMap accessItems; + + public AbstractAccessesFileReader(TransitSchedule transitSchedule) { + super(ValidationType.NO_VALIDATION); + this.transitSchedule = transitSchedule; + this.accessItems = new IdMap<>(AbstractAccessItem.class); + this.setValidating(false); + } + + @Override + public void startTag(String name, Attributes atts, Stack context) { + if(name.equals(ABSTRACT_ACCESS_TAG_NAME)) { + Id transitStopFacilityId; + Id abstractAccessItemId; + double radius; + double averageSpeed; + boolean usingRoutedDistance; + String accessType; + int frequency; + + if(atts.getValue(ACCESS_ID_ATTR_NAME) != null) { + abstractAccessItemId = Id.create(atts.getValue(ACCESS_ID_ATTR_NAME), AbstractAccessItem.class); + if(this.accessItems.containsKey(abstractAccessItemId)) { + throw new IllegalStateException("abstract access item " + abstractAccessItemId.toString() + " defined more than once"); + } + } else { + throw new IllegalStateException(ACCESS_ID_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + if(atts.getValue(TRANSIT_STOP_ID_ATTR_NAME) != null) { + transitStopFacilityId = Id.create(atts.getValue(TRANSIT_STOP_ID_ATTR_NAME), TransitStopFacility.class); + if(!this.transitSchedule.getFacilities().containsKey(transitStopFacilityId)) { + throw new IllegalStateException("Transit stop facility " + transitStopFacilityId.toString() + " specified for access item " + abstractAccessItemId.toString() + " does not exist"); + } + } else { + throw new IllegalStateException(TRANSIT_STOP_ID_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + if(atts.getValue(RADIUS_ATTR_NAME) != null) { + radius = Double.parseDouble(atts.getValue(RADIUS_ATTR_NAME)); + } else { + throw new IllegalStateException(RADIUS_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + if(atts.getValue(AVG_SPEED_ATTR_NAME) != null) { + averageSpeed = Double.parseDouble(atts.getValue(AVG_SPEED_ATTR_NAME)); + } else { + throw new IllegalStateException(AVG_SPEED_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + if(atts.getValue(USING_ROUTED_DISTANCE_ATTR_NAME) != null) { + usingRoutedDistance = Boolean.parseBoolean(atts.getValue(USING_ROUTED_DISTANCE_ATTR_NAME)); + } else { + throw new IllegalStateException(USING_ROUTED_DISTANCE_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + if(atts.getValue(ACCESS_TYPE_ATTR_NAME) != null) { + accessType = atts.getValue(ACCESS_TYPE_ATTR_NAME); + } else { + throw new IllegalStateException(ACCESS_TYPE_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + if(atts.getValue(FREQUENCY_ATTR_NAME) != null) { + frequency = Integer.parseInt(atts.getValue(FREQUENCY_ATTR_NAME)); + } else { + throw new IllegalStateException(FREQUENCY_ATTR_NAME + " is required in " + ABSTRACT_ACCESS_TAG_NAME + " element"); + } + + this.accessItems.put(abstractAccessItemId, new AbstractAccessItem(abstractAccessItemId, this.transitSchedule.getFacilities().get(transitStopFacilityId), radius, averageSpeed, accessType, usingRoutedDistance, frequency)); + } + } + + @Override + public void endTag(String name, String content, Stack context) { + + } + + public IdMap getAccessItems(){ + return this.accessItems; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessesFileWriter.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessesFileWriter.java new file mode 100644 index 000000000..4153a3464 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/abstract_access/AbstractAccessesFileWriter.java @@ -0,0 +1,36 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access; + +import org.matsim.core.utils.collections.Tuple; +import org.matsim.core.utils.io.MatsimXmlWriter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +public class AbstractAccessesFileWriter extends MatsimXmlWriter { + private final Collection accessItems; + public AbstractAccessesFileWriter(Collection accessItems) { + this.accessItems = accessItems; + } + + public void write(String file) { + this.openFile(file); + this.writeStartTag(AbstractAccessesFileReader.ROOT_TAG_NAME, Collections.emptyList()); + this.accessItems.forEach(this::writeAccessItem); + this.writeEndTag(AbstractAccessesFileReader.ROOT_TAG_NAME); + this.close(); + } + + private synchronized void writeAccessItem(AbstractAccessItem accessItem) { + List> attributes = new ArrayList<>(); + attributes.add(Tuple.of(AbstractAccessesFileReader.ACCESS_ID_ATTR_NAME, accessItem.getId().toString())); + attributes.add(Tuple.of(AbstractAccessesFileReader.TRANSIT_STOP_ID_ATTR_NAME, accessItem.getCenterStop().getId().toString())); + attributes.add(Tuple.of(AbstractAccessesFileReader.RADIUS_ATTR_NAME, String.valueOf(accessItem.getRadius()))); + attributes.add(Tuple.of(AbstractAccessesFileReader.AVG_SPEED_ATTR_NAME, String.valueOf(accessItem.getAvgSpeedToCenterStop()))); + attributes.add(Tuple.of(AbstractAccessesFileReader.USING_ROUTED_DISTANCE_ATTR_NAME, String.valueOf(accessItem.isUsingRoutedDistance()))); + attributes.add(Tuple.of(AbstractAccessesFileReader.ACCESS_TYPE_ATTR_NAME, accessItem.getAccessType())); + attributes.add(Tuple.of(AbstractAccessesFileReader.FREQUENCY_ATTR_NAME, String.valueOf(accessItem.getFrequency()))); + this.writeStartTag(AbstractAccessesFileReader.ABSTRACT_ACCESS_TAG_NAME, attributes, true); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessAnalysisOutputListener.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessAnalysisOutputListener.java new file mode 100644 index 000000000..41d63b251 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessAnalysisOutputListener.java @@ -0,0 +1,70 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.analysis; + +import com.google.inject.Inject; +import org.eqasim.core.components.config.EqasimConfigGroup; +import org.matsim.core.controler.OutputDirectoryHierarchy; +import org.matsim.core.controler.events.IterationEndsEvent; +import org.matsim.core.controler.events.IterationStartsEvent; +import org.matsim.core.controler.events.ShutdownEvent; +import org.matsim.core.controler.listener.IterationEndsListener; +import org.matsim.core.controler.listener.IterationStartsListener; +import org.matsim.core.controler.listener.ShutdownListener; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class AbstractAccessAnalysisOutputListener implements IterationStartsListener, IterationEndsListener, ShutdownListener { + private final static String ABSTRACT_ACCESS_LEGS_FILE_NAME = "eqasim_abstract_access_legs.csv"; + + private final OutputDirectoryHierarchy outputDirectoryHierarchy; + + private final AbstractAccessLegListener abstractAccessLegListener; + + private final int analysisInterval; + + private boolean isAnalysisActive; + + @Inject + public AbstractAccessAnalysisOutputListener(EqasimConfigGroup eqasimConfigGroup, OutputDirectoryHierarchy outputDirectoryHierarchy, AbstractAccessLegListener abstractAccessLegListener) { + this.outputDirectoryHierarchy = outputDirectoryHierarchy; + this.abstractAccessLegListener = abstractAccessLegListener; + this.analysisInterval = eqasimConfigGroup.getAnalysisInterval(); + } + + + @Override + public void notifyIterationStarts(IterationStartsEvent event) { + this.isAnalysisActive = false; + if(analysisInterval > 0) { + if(event.getIteration() % this.analysisInterval == 0 || event.isLastIteration()) { + this.isAnalysisActive = true; + event.getServices().getEvents().addHandler(this.abstractAccessLegListener); + } + } + } + + @Override + public void notifyIterationEnds(IterationEndsEvent event) { + if(isAnalysisActive) { + event.getServices().getEvents().removeHandler(this.abstractAccessLegListener); + try { + new AbstractAccessLegWriter(this.abstractAccessLegListener.getAbstractAccessLegItems(), ";") + .write(outputDirectoryHierarchy.getIterationFilename(event.getIteration(), ABSTRACT_ACCESS_LEGS_FILE_NAME)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + + @Override + public void notifyShutdown(ShutdownEvent event) { + try { + Files.copy(new File(this.outputDirectoryHierarchy.getIterationFilename(event.getIteration(), ABSTRACT_ACCESS_LEGS_FILE_NAME)).toPath(), + new File(outputDirectoryHierarchy.getOutputFilename(ABSTRACT_ACCESS_LEGS_FILE_NAME)).toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegItem.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegItem.java new file mode 100644 index 000000000..b0a67d5b5 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegItem.java @@ -0,0 +1,28 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.analysis; + +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.population.Person; +import org.matsim.pt.transitSchedule.api.TransitStopFacility; + +public class AbstractAccessLegItem { + public Id personId; + public int personTripId; + public int legIndex; + public Id abstractAccessItemId; + public Id transitStopFacilityId; + public boolean leavingCenterStop; + public boolean isRouted; + public double distance; + + public AbstractAccessLegItem(Id personId, int personTripId, int legIndex, Id abstractAccessItemId, Id transitStopFacilityId, boolean leavingCenterStop, boolean isRouted, double distance) { + this.personId = personId; + this.personTripId = personTripId; + this.legIndex = legIndex; + this.abstractAccessItemId = abstractAccessItemId; + this.transitStopFacilityId = transitStopFacilityId; + this.leavingCenterStop = leavingCenterStop; + this.isRouted = isRouted; + this.distance = distance; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegListener.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegListener.java new file mode 100644 index 000000000..2842676c1 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegListener.java @@ -0,0 +1,81 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.analysis; + +import com.google.inject.Inject; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccesses; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.events.AbstractAccessDepartureEvent; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.events.AbstractAccessDepartureEventHandler; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.events.ActivityStartEvent; +import org.matsim.api.core.v01.events.PersonArrivalEvent; +import org.matsim.api.core.v01.events.PersonDepartureEvent; +import org.matsim.api.core.v01.events.handler.ActivityStartEventHandler; +import org.matsim.api.core.v01.events.handler.PersonArrivalEventHandler; +import org.matsim.api.core.v01.events.handler.PersonDepartureEventHandler; +import org.matsim.api.core.v01.population.Person; +import org.matsim.core.events.MobsimScopeEventHandler; +import org.matsim.core.router.TripStructureUtils; + +import java.util.ArrayList; +import java.util.Collection; + + +public class AbstractAccessLegListener implements MobsimScopeEventHandler, PersonDepartureEventHandler, ActivityStartEventHandler, AbstractAccessDepartureEventHandler, PersonArrivalEventHandler { + + private final IdMap tripIndices = new IdMap<>(Person.class); + private final IdMap legIndices = new IdMap<>(Person.class); + private final IdMap personDepartureEvents = new IdMap<>(Person.class); + private final AbstractAccesses abstractAccesses; + private final Collection abstractAccessLegItems = new ArrayList<>(); + + @Inject + public AbstractAccessLegListener(AbstractAccesses abstractAccesses) { + this.abstractAccesses = abstractAccesses; + } + + @Override + public void handleEvent(PersonDepartureEvent event) { + if (!tripIndices.containsKey(event.getPersonId())) { + tripIndices.put(event.getPersonId(), 0); + legIndices.put(event.getPersonId(), 0); + } else { + tripIndices.compute(event.getPersonId(), (k, v) -> v + 1); + legIndices.compute(event.getPersonId(), (k, v) -> v + 1); + } + this.personDepartureEvents.put(event.getPersonId(), event); + } + + @Override + public void handleEvent(ActivityStartEvent event) { + if (TripStructureUtils.isStageActivityType(event.getActType())) { + tripIndices.computeIfPresent(event.getPersonId(), (k, v) -> v - 1); + } + } + + @Override + public void reset(int iteration) { + this.tripIndices.clear(); + this.legIndices.clear(); + this.personDepartureEvents.clear(); + this.abstractAccessLegItems.clear(); + } + + @Override + public void handleEvent(AbstractAccessDepartureEvent event) { + Id personId = event.getPersonId(); + Id abstractAccessItemId = event.getAccessItemId(); + AbstractAccessItem abstractAccessItem = this.abstractAccesses.getAbstractAccessItems().get(abstractAccessItemId); + AbstractAccessLegItem abstractAccessLegItem = new AbstractAccessLegItem(personId, this.tripIndices.get(personId), this.legIndices.get(personId), event.getAccessItemId(), abstractAccessItem.getCenterStop().getId(), event.isLeavingAccessCenter(), event.isRouted(), event.getDistance()); + this.abstractAccessLegItems.add(abstractAccessLegItem); + } + + @Override + public void handleEvent(PersonArrivalEvent personArrivalEvent) { + this.personDepartureEvents.remove(personArrivalEvent.getPersonId()); + } + + public Collection getAbstractAccessLegItems() { + return this.abstractAccessLegItems; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegWriter.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegWriter.java new file mode 100644 index 000000000..bcf61160b --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/analysis/AbstractAccessLegWriter.java @@ -0,0 +1,55 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.analysis; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.Collection; + +public class AbstractAccessLegWriter { + private final Collection abstractAccessLegs; + private final String delimiter; + + + public AbstractAccessLegWriter(Collection abstractAccessLegs, String delimiter) { + this.abstractAccessLegs = abstractAccessLegs; + this.delimiter = delimiter; + } + + public void write(String outputPath) throws IOException { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputPath))); + + writer.write(formatHeader() + "\n"); + writer.flush(); + for(AbstractAccessLegItem abstractAccessLegItem: this.abstractAccessLegs) { + writer.write(formatLeg(abstractAccessLegItem) + "\n"); + writer.flush(); + } + writer.flush(); + writer.close(); + } + + private String formatHeader() { + return String.join(this.delimiter, new String[]{ + "person_id", + "person_trip_id", + "leg_index", + "transit_stop_id", + "leaving_transit_stop", + "is_distance_routed", + "distance" + }); + } + + private String formatLeg(AbstractAccessLegItem abstractAccessLeg) { + return String.join(this.delimiter, new String[]{ + abstractAccessLeg.personId.toString(), + String.valueOf(abstractAccessLeg.personTripId), + String.valueOf(abstractAccessLeg.legIndex), + abstractAccessLeg.transitStopFacilityId.toString(), + String.valueOf(abstractAccessLeg.leavingCenterStop), + String.valueOf(abstractAccessLeg.isRouted), + String.valueOf(abstractAccessLeg.distance) + }); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/events/AbstractAccessDepartureEvent.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/events/AbstractAccessDepartureEvent.java new file mode 100644 index 000000000..89e81cdc6 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/events/AbstractAccessDepartureEvent.java @@ -0,0 +1,63 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.events; + +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.events.Event; +import org.matsim.api.core.v01.events.HasPersonId; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Person; + +public class AbstractAccessDepartureEvent extends Event implements HasPersonId { + + public static final String EVENT_TYPE = "AbstractAccessArrivalEvent"; + private final Id personId; + private final Id accessItemId; + private final Id departureLinkId; + private final Id arrivalLinkId; + private final boolean leavingAccessCenter; + private final boolean isRouted; + private final double distance; + + public AbstractAccessDepartureEvent(double time, Id personId, Id accessItemId, Id departureLinkId, Id arrivalLinkId, boolean leavingAccessCenter, boolean isRouted, double distance) { + super(time); + this.personId = personId; + this.accessItemId = accessItemId; + this.departureLinkId = departureLinkId; + this.arrivalLinkId = arrivalLinkId; + this.leavingAccessCenter = leavingAccessCenter; + this.isRouted = isRouted; + this.distance = distance; + } + + public Id getAccessItemId() { + return this.accessItemId; + } + public Id getDepartureLinkId() { + return this.departureLinkId; + } + public Id getArrivalLinkId() { + return this.arrivalLinkId; + } + + public boolean isRouted() { + return this.isRouted; + } + + public double getDistance() { + return this.distance; + } + + @Override + public String getEventType() { + return EVENT_TYPE; + } + + @Override + public Id getPersonId() { + return this.personId; + } + + public boolean isLeavingAccessCenter() { + return leavingAccessCenter; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/events/AbstractAccessDepartureEventHandler.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/events/AbstractAccessDepartureEventHandler.java new file mode 100644 index 000000000..d72a2abd8 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/events/AbstractAccessDepartureEventHandler.java @@ -0,0 +1,7 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.events; + +import org.matsim.core.events.handler.EventHandler; + +public interface AbstractAccessDepartureEventHandler extends EventHandler { + void handleEvent(AbstractAccessDepartureEvent event); +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/TransitWithAbstractAccessModeAvailabilityWrapper.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/TransitWithAbstractAccessModeAvailabilityWrapper.java new file mode 100644 index 000000000..908f92013 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/TransitWithAbstractAccessModeAvailabilityWrapper.java @@ -0,0 +1,39 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.TransitWithAbstractAbstractAccessModuleConfigGroup; +import org.matsim.api.core.v01.population.Person; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; +import org.matsim.contribs.discrete_mode_choice.model.mode_availability.ModeAvailability; +import org.matsim.core.config.Config; + +import java.util.*; + +public class TransitWithAbstractAccessModeAvailabilityWrapper implements ModeAvailability { + + private final static Logger logger = LogManager.getLogger(TransitWithAbstractAccessModeAvailabilityWrapper.class); + private final Set extraModes; + private final ModeAvailability delegate; + + public TransitWithAbstractAccessModeAvailabilityWrapper(Config config, ModeAvailability delegate) { + this((TransitWithAbstractAbstractAccessModuleConfigGroup) config.getModules().get(TransitWithAbstractAbstractAccessModuleConfigGroup.GROUP_NAME), delegate); + } + + public TransitWithAbstractAccessModeAvailabilityWrapper(TransitWithAbstractAbstractAccessModuleConfigGroup config, ModeAvailability delegate) { + this.delegate = delegate; + this.extraModes = new HashSet<>(); + if(config != null) { + this.extraModes.add(config.getModeName()); + } else { + logger.warn(String.format("No '%s' config group was provided", TransitWithAbstractAbstractAccessModuleConfigGroup.GROUP_NAME)); + } + } + + @Override + public Collection getAvailableModes(Person person, List trips) { + Collection modes = this.delegate.getAvailableModes(person, trips); + modes.addAll(this.extraModes); + return modes; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/TransitWithAbstractAccessModeChoiceModule.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/TransitWithAbstractAccessModeChoiceModule.java new file mode 100644 index 000000000..cef855640 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/TransitWithAbstractAccessModeChoiceModule.java @@ -0,0 +1,18 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice; + +import org.eqasim.core.simulation.mode_choice.AbstractEqasimExtension; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.constraints.TransitWithAbstractAccessConstraint; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.utilities.estimators.TransitWithAbstractAccessUtilityEstimator; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.utilities.predictors.TransitWithAbstractAccessPredictor; + +public class TransitWithAbstractAccessModeChoiceModule extends AbstractEqasimExtension { + + public static final String TRANSIT_WITH_ABSTRACT_ACCESS_UTILITY_ESTIMATOR_NAME = "TransitWithAbstractAccessUtilityEstimator"; + + @Override + protected void installEqasimExtension() { + bind(TransitWithAbstractAccessPredictor.class); + bindUtilityEstimator(TRANSIT_WITH_ABSTRACT_ACCESS_UTILITY_ESTIMATOR_NAME).to(TransitWithAbstractAccessUtilityEstimator.class); + bindTripConstraintFactory(TransitWithAbstractAccessConstraint.NAME).to(TransitWithAbstractAccessConstraint.Factory.class); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/constraints/TransitWithAbstractAccessConstraint.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/constraints/TransitWithAbstractAccessConstraint.java new file mode 100644 index 000000000..371bd7bf8 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/constraints/TransitWithAbstractAccessConstraint.java @@ -0,0 +1,78 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.constraints; + +import com.google.inject.Inject; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.TransitWithAbstractAbstractAccessModuleConfigGroup; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.TransitWithAbstractAccessRoutingModule; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; +import org.matsim.contribs.discrete_mode_choice.model.trip_based.TripConstraint; +import org.matsim.contribs.discrete_mode_choice.model.trip_based.TripConstraintFactory; +import org.matsim.contribs.discrete_mode_choice.model.trip_based.candidates.RoutedTripCandidate; +import org.matsim.contribs.discrete_mode_choice.model.trip_based.candidates.TripCandidate; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigGroup; + +import java.util.Collection; +import java.util.List; + +public class TransitWithAbstractAccessConstraint implements TripConstraint { + + public static final String NAME = "AbstractAccessConstraint"; + + + private final String transitWithAbstractAccessModeName; + + public TransitWithAbstractAccessConstraint(String transitWithAbstractAccessModeName) { + this.transitWithAbstractAccessModeName = transitWithAbstractAccessModeName; + } + + @Override + public boolean validateBeforeEstimation(DiscreteModeChoiceTrip trip, String mode, List previousModes) { + return true; + } + + @Override + public boolean validateAfterEstimation(DiscreteModeChoiceTrip trip, TripCandidate candidate, List previousCandidates) { + RoutedTripCandidate routedTripCandidate = (RoutedTripCandidate) candidate; + if(!candidate.getMode().equals(this.transitWithAbstractAccessModeName)) { + return true; + } + boolean foundPt = false; + boolean foundAbstractAccess = false; + for(PlanElement planElement: routedTripCandidate.getRoutedPlanElements()) { + if(planElement instanceof Leg) { + String mode = ((Leg) planElement).getMode(); + if(mode.equals(TransitWithAbstractAccessRoutingModule.ABSTRACT_ACCESS_LEG_MODE_NAME)){ + foundAbstractAccess = true; + } + if(mode.equals(TransportMode.pt)) { + foundPt = true; + } + } + } + return foundAbstractAccess && foundPt; + } + + static public class Factory implements TripConstraintFactory { + + private final String transitWithAbstractAccessModeName; + + @Inject + public Factory(Config config) { + ConfigGroup configGroup = config.getModules().get(TransitWithAbstractAbstractAccessModuleConfigGroup.GROUP_NAME); + if(configGroup == null) { + throw new IllegalStateException("AbstractAccessConstraint cannot be used if the " + TransitWithAbstractAbstractAccessModuleConfigGroup.GROUP_NAME + " module is not specified in the config"); + } + this.transitWithAbstractAccessModeName = ((TransitWithAbstractAbstractAccessModuleConfigGroup) configGroup).getModeName(); + } + + @Override + public TripConstraint createConstraint(Person person, List planTrips, + Collection availableModes) { + return new TransitWithAbstractAccessConstraint(transitWithAbstractAccessModeName); + } + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/utilities/estimators/TransitWithAbstractAccessUtilityEstimator.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/utilities/estimators/TransitWithAbstractAccessUtilityEstimator.java new file mode 100644 index 000000000..83f45b2c4 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/utilities/estimators/TransitWithAbstractAccessUtilityEstimator.java @@ -0,0 +1,13 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.utilities.estimators; + +import com.google.inject.Inject; +import org.eqasim.core.simulation.mode_choice.parameters.ModeParameters; +import org.eqasim.core.simulation.mode_choice.utilities.estimators.PtUtilityEstimator; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.utilities.predictors.TransitWithAbstractAccessPredictor; + +public class TransitWithAbstractAccessUtilityEstimator extends PtUtilityEstimator { + @Inject + public TransitWithAbstractAccessUtilityEstimator(ModeParameters parameters, TransitWithAbstractAccessPredictor predictor) { + super(parameters, predictor); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/utilities/predictors/TransitWithAbstractAccessPredictor.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/utilities/predictors/TransitWithAbstractAccessPredictor.java new file mode 100644 index 000000000..27c5439c6 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/mode_choice/utilities/predictors/TransitWithAbstractAccessPredictor.java @@ -0,0 +1,95 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.utilities.predictors; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import org.eqasim.core.simulation.mode_choice.cost.CostModel; +import org.eqasim.core.simulation.mode_choice.utilities.predictors.PredictorUtils; +import org.eqasim.core.simulation.mode_choice.utilities.predictors.PtPredictor; +import org.eqasim.core.simulation.mode_choice.utilities.variables.PtVariables; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.DefaultAbstractAccessRoute; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.TransitWithAbstractAccessRoutingModule; +import org.matsim.api.core.v01.TransportMode; +import org.matsim.api.core.v01.population.Leg; +import org.matsim.api.core.v01.population.Person; +import org.matsim.api.core.v01.population.PlanElement; +import org.matsim.contribs.discrete_mode_choice.model.DiscreteModeChoiceTrip; +import org.matsim.pt.routes.TransitPassengerRoute; + +import java.util.List; + +public class TransitWithAbstractAccessPredictor extends PtPredictor { + + @Inject + public TransitWithAbstractAccessPredictor(@Named("pt") CostModel costModel) { + super(costModel); + } + + @Override + public PtVariables predict(Person person, DiscreteModeChoiceTrip trip, List elements) { + // Copy pasted from PtPredictor + + /* + * Note: For mode choice, we do not consider any waiting time *before* the first + * vehicular stage. This implies that agents are able to minimize their waiting + * time by departing at the proper time. + */ + + int numberOfVehicularTrips = 0; + boolean isFirstWaitingTime = true; + + // Track relevant variables + double inVehicleTime_min = 0.0; + double waitingTime_min = 0.0; + double accessEgressTime_min = 0.0; + + for (PlanElement element : elements) { + if (element instanceof Leg leg) { + + switch (leg.getMode()) { + case TransportMode.walk: + case TransportMode.non_network_walk: + accessEgressTime_min += leg.getTravelTime().seconds() / 60.0; + break; + case TransportMode.transit_walk: + waitingTime_min += leg.getTravelTime().seconds() / 60.0; + break; + case TransportMode.pt: + TransitPassengerRoute route = (TransitPassengerRoute) leg.getRoute(); + + double departureTime = leg.getDepartureTime().seconds(); + double waitingTime = route.getBoardingTime().seconds() - departureTime; + double inVehicleTime = leg.getTravelTime().seconds() - waitingTime; + + inVehicleTime_min += inVehicleTime / 60.0; + + if (!isFirstWaitingTime) { + waitingTime_min += waitingTime / 60.0; + } else { + isFirstWaitingTime = false; + } + + numberOfVehicularTrips++; + break; + case TransitWithAbstractAccessRoutingModule.ABSTRACT_ACCESS_LEG_MODE_NAME: + DefaultAbstractAccessRoute accessRoute = (DefaultAbstractAccessRoute) leg.getRoute(); + inVehicleTime_min += accessRoute.getTravelTime().seconds() / 60; + waitingTime_min += accessRoute.getWaitTime() / 60; + numberOfVehicularTrips++; + break; + default: + throw new IllegalStateException("Unknown mode in PT trip: " + leg.getMode()); + } + } + } + + int numberOfLineSwitches = Math.max(0, numberOfVehicularTrips - 1); + + // Calculate cost + double cost_CHF = getCostModel().calculateCost_MU(person, trip, elements); + + double euclideanDistance_km = PredictorUtils.calculateEuclideanDistance_km(trip); + + return new PtVariables(inVehicleTime_min, waitingTime_min, accessEgressTime_min, numberOfLineSwitches, cost_CHF, + euclideanDistance_km); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/AbstractAccessRoute.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/AbstractAccessRoute.java new file mode 100644 index 000000000..96a2ce8e0 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/AbstractAccessRoute.java @@ -0,0 +1,15 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.routing; + +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.population.Route; + +public interface AbstractAccessRoute extends Route { + Id getAbstractAccessItemId(); + + boolean isLeavingAccessCenter(); + + boolean isRouted(); + + double getWaitTime(); +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/AbstractAccessRouteFactory.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/AbstractAccessRouteFactory.java new file mode 100644 index 000000000..d5a702864 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/AbstractAccessRouteFactory.java @@ -0,0 +1,18 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.routing; + +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.population.Route; +import org.matsim.core.population.routes.RouteFactory; + +public class AbstractAccessRouteFactory implements RouteFactory { + @Override + public Route createRoute(Id startLinkId, Id endLinkId) { + return new DefaultAbstractAccessRoute(startLinkId, endLinkId); + } + + @Override + public String getCreatedRouteType() { + return "abstractAccess"; + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/DefaultAbstractAccessRoute.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/DefaultAbstractAccessRoute.java new file mode 100644 index 000000000..eb9a8dbf0 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/DefaultAbstractAccessRoute.java @@ -0,0 +1,100 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.routing; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.network.Link; +import org.matsim.core.population.routes.AbstractRoute; + +import java.io.IOException; + + +public class DefaultAbstractAccessRoute extends AbstractRoute implements AbstractAccessRoute{ + + public static final String ROUTE_TYPE = "abstractAccess"; + + private RouteDescription routeDescription; + + public DefaultAbstractAccessRoute(Id startLinkId, Id endLinkId) { + super(startLinkId, endLinkId); + } + + public DefaultAbstractAccessRoute(Id startLinkId, Id endLinkId, AbstractAccessItem accessItem) { + super(startLinkId, endLinkId); + this.routeDescription = new RouteDescription(); + routeDescription.accessItemId = accessItem.getId(); + routeDescription.isRouted = accessItem.isUsingRoutedDistance(); + Id accessLink = accessItem.getCenterStop().getLinkId(); + if(accessLink.equals(startLinkId)) { + this.routeDescription.leavingAccessCenter = true; + } else if(accessLink.equals(endLinkId)) { + this.routeDescription.leavingAccessCenter = false; + } else { + throw new IllegalStateException("Supplied accessItem should have a center located on one of startLinkId or endLinkId"); + } + } + + @Override + public String getRouteDescription() { + try { + return new ObjectMapper().writeValueAsString(routeDescription); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setRouteDescription(String routeDescription) { + try { + this.routeDescription = new ObjectMapper().readValue(routeDescription, RouteDescription.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Override + public String getRouteType() { + return ROUTE_TYPE; + } + + public boolean isLeavingAccessCenter() { + return this.routeDescription.leavingAccessCenter; + } + + @Override + public boolean isRouted() { + return this.routeDescription.isRouted; + } + + @Override + public double getWaitTime() { + return routeDescription.waitTime; + } + + public void setWaitTime(double waitTime) { + this.routeDescription.waitTime = waitTime; + } + + @Override + public Id getAbstractAccessItemId() { + return this.routeDescription.accessItemId; + } + + public static class RouteDescription { + public Id accessItemId; + public boolean leavingAccessCenter; + public boolean isRouted; + public double waitTime = 0; + + @JsonProperty("accessItemId") + public String getAccessItemId() { + return accessItemId == null ? null : accessItemId.toString(); + } + + @JsonProperty("accessItemId") + public void setAccessItemId(String accessItemId) { + this.accessItemId = accessItemId == null ? null : Id.create(accessItemId, AbstractAccessItem.class); + } + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/TransitWithAbstractAccessRoutingModule.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/TransitWithAbstractAccessRoutingModule.java new file mode 100644 index 000000000..e35c09065 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/routing/TransitWithAbstractAccessRoutingModule.java @@ -0,0 +1,210 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.routing; + +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccesses; +import org.matsim.api.core.v01.Coord; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.IdMap; +import org.matsim.api.core.v01.network.Link; +import org.matsim.api.core.v01.network.Network; +import org.matsim.api.core.v01.network.Node; +import org.matsim.api.core.v01.population.*; +import org.matsim.core.network.NetworkUtils; +import org.matsim.core.network.algorithms.TransportModeNetworkFilter; +import org.matsim.core.population.PopulationUtils; +import org.matsim.core.router.DefaultRoutingRequest; +import org.matsim.core.router.RoutingModule; +import org.matsim.core.router.RoutingRequest; +import org.matsim.core.router.costcalculators.OnlyTimeDependentTravelDisutility; +import org.matsim.core.router.speedy.SpeedyALTFactory; +import org.matsim.core.router.util.LeastCostPathCalculator; +import org.matsim.core.router.util.TravelTime; +import org.matsim.core.utils.collections.QuadTree; +import org.matsim.facilities.Facility; +import org.matsim.pt.transitSchedule.api.*; + +import java.util.*; + +public class TransitWithAbstractAccessRoutingModule implements RoutingModule { + + public static final String ABSTRACT_ACCESS_LEG_MODE_NAME = "abstractAccess"; + private final IdMap> accessItems; + private final IdMap pathCalculators; + private final QuadTree quadTree; + private final RoutingModule transitRoutingModule; + private final double maxRadius; + private final PopulationFactory populationFactory; + private final Network network; + private final IdMap> transitStopFacilityLinks; + + public TransitWithAbstractAccessRoutingModule(TransitSchedule transitSchedule, AbstractAccesses abstractAccesses, Network network, RoutingModule transitRoutingModule, PopulationFactory populationFactory) { + + // The provided network is cleaned to keep only the biggest cluster + // This is done to be able to compute paths to PT links from non-PT links. + // The formers are very often not connected to the car network + + this.network = NetworkUtils.createNetwork(); + new TransportModeNetworkFilter(network).filter(this.network, Collections.singleton("car")); + this.accessItems = new IdMap<>(TransitStopFacility.class); + this.pathCalculators = new IdMap<>(AbstractAccessItem.class); + this.transitStopFacilityLinks = new IdMap<>(TransitStopFacility.class); + double maxRadius = Double.MIN_VALUE; + boolean atLeastOneAccess = false; + + SpeedyALTFactory factory = new SpeedyALTFactory(); + + for (Map.Entry, List> entry : abstractAccesses.getAbstractAccessItemsByTransitStop().entrySet()) { + // In case there are facilities mentioned in the input map but with an empty list of access items + if (entry.getValue().size() > 0) { + atLeastOneAccess = true; + this.accessItems.put(entry.getKey(), entry.getValue()); + for (AbstractAccessItem abstractAccessItem : entry.getValue()) { + if (abstractAccessItem.getRadius() > maxRadius) { + maxRadius = abstractAccessItem.getRadius(); + } + if(!this.transitStopFacilityLinks.containsKey(entry.getKey())) { + Link link = NetworkUtils.getNearestLink(this.network, abstractAccessItem.getCenterStop().getCoord()); + this.transitStopFacilityLinks.put(entry.getKey(), link.getId()); + } + if(abstractAccessItem.isUsingRoutedDistance()) { + TravelTime travelTime = (link, time, person, vehicle) -> Math.min(link.getLength() / abstractAccessItem.getAvgSpeedToCenterStop(), link.getLength() / link.getFreespeed()); + LeastCostPathCalculator pathCalculator = factory.createPathCalculator(this.network, new OnlyTimeDependentTravelDisutility(travelTime), travelTime); + this.pathCalculators.put(abstractAccessItem.getId(), pathCalculator); + } + } + } + } + if (!atLeastOneAccess) { + throw new IllegalStateException("No abstract access defined for any of the transit stops"); + } + this.maxRadius = maxRadius; + + this.transitRoutingModule = transitRoutingModule; + double[] bounds = NetworkUtils.getBoundingBox(network.getNodes().values()); + this.quadTree = new QuadTree<>(bounds[0], bounds[1], bounds[2], bounds[3]); + + this.populationFactory = populationFactory; + + Set> processedFacilities = new HashSet<>(); + + for (TransitLine transitLine : transitSchedule.getTransitLines().values()) { + for (TransitRoute transitRoute : transitLine.getRoutes().values()) { + for (TransitRouteStop transitRouteStop : transitRoute.getStops()) { + TransitStopFacility transitStopFacility = transitRouteStop.getStopFacility(); + if (!processedFacilities.contains(transitStopFacility.getId())) { + processedFacilities.add(transitStopFacility.getId()); + this.quadTree.put(transitStopFacility.getCoord().getX(), transitStopFacility.getCoord().getY(), transitStopFacility); + } + } + } + } + } + + @Override + public List calcRoute(RoutingRequest routingRequest) { + // Let's first find the transit stops that provide a feasible access from the origin and to the destination of the trip + Coord fromFacilityCoord = this.network.getLinks().get(routingRequest.getFromFacility().getLinkId()).getCoord(); + IdMap bestAccessItemForOrigin = new IdMap<>(TransitStopFacility.class); + TransitStopFacility accessTransitStopFacility = this.getClosestTransitStopWithValidAccessItem(fromFacilityCoord, bestAccessItemForOrigin); + + Coord toFacilityCoord = this.network.getLinks().get(routingRequest.getToFacility().getLinkId()).getCoord(); + IdMap bestAccessItemForDestination = new IdMap<>(TransitStopFacility.class); + TransitStopFacility egresssTransitStopFacility = this.getClosestTransitStopWithValidAccessItem(toFacilityCoord, bestAccessItemForDestination); + + if(accessTransitStopFacility == null && egresssTransitStopFacility == null) { + return this.transitRoutingModule.calcRoute(routingRequest); + } + List plan = new ArrayList<>(); + + double departureTime = routingRequest.getDepartureTime(); + + if(accessTransitStopFacility != null) { + AbstractAccessItem abstractAccessItem = bestAccessItemForOrigin.get(accessTransitStopFacility.getId()); + Leg leg = this.createAbstractAccessLeg(abstractAccessItem, true, routingRequest.getFromFacility().getLinkId(), departureTime, routingRequest.getPerson()); + plan.add(leg); + departureTime+=leg.getTravelTime().seconds(); + Activity activity = this.populationFactory.createActivityFromLinkId("pt interaction", accessTransitStopFacility.getLinkId()); + activity.setStartTime(departureTime); + activity.setEndTime(departureTime); + plan.add(activity); + } else { + List ptRoute = this.transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(routingRequest.getFromFacility(), egresssTransitStopFacility, routingRequest.getDepartureTime(), routingRequest.getPerson())); + for(PlanElement element: ptRoute) { + if(element instanceof Leg leg) { + departureTime = leg.getDepartureTime().seconds(); + departureTime+=leg.getTravelTime().seconds(); + } + } + } + + if(egresssTransitStopFacility != null) { + List ptRoute; + if(accessTransitStopFacility != null) { + ptRoute = this.transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(accessTransitStopFacility, egresssTransitStopFacility, departureTime, routingRequest.getPerson())); + } else { + ptRoute = this.transitRoutingModule.calcRoute(DefaultRoutingRequest.withoutAttributes(routingRequest.getFromFacility(), egresssTransitStopFacility, departureTime, routingRequest.getPerson())); + } + for(PlanElement element: ptRoute) { + if(element instanceof Leg leg) { + departureTime = leg.getDepartureTime().seconds(); + departureTime+=leg.getTravelTime().seconds(); + } + } + plan.addAll(ptRoute); + Activity activity = this.populationFactory.createActivityFromLinkId("pt interaction", egresssTransitStopFacility.getLinkId()); + activity.setStartTime(departureTime); + activity.setEndTime(departureTime); + plan.add(activity); + AbstractAccessItem abstractAccessItem = bestAccessItemForDestination.get(egresssTransitStopFacility.getId()); + Leg leg = this.createAbstractAccessLeg(abstractAccessItem, false, routingRequest.getToFacility().getLinkId(), departureTime, routingRequest.getPerson()); + plan.add(leg); + } + return plan; + } + + private Leg createAbstractAccessLeg(AbstractAccessItem accessItem, boolean access, Id otherLinkId, double departureTime, Person person) { + Leg leg = PopulationUtils.createLeg(ABSTRACT_ACCESS_LEG_MODE_NAME); + leg.setDepartureTime(departureTime); + Id accessTransitStopFacilityLink = this.transitStopFacilityLinks.get(accessItem.getCenterStop().getId()); + DefaultAbstractAccessRoute abstractAccessRoute = new DefaultAbstractAccessRoute(access ? otherLinkId : accessItem.getCenterStop().getLinkId(), access ? accessItem.getCenterStop().getLinkId() : otherLinkId, accessItem); + leg.setRoute(abstractAccessRoute); + + Id fromLinkId = access ? otherLinkId : accessTransitStopFacilityLink; + Id toLinkId = access ? accessTransitStopFacilityLink : otherLinkId; + leg.getAttributes().putAttribute("accessId", accessItem.getId().toString()); + + if(accessItem.isUsingRoutedDistance()) { + Node fromNode = this.network.getLinks().get(fromLinkId).getFromNode(); + Node toNode = this.network.getLinks().get(toLinkId).getToNode(); + LeastCostPathCalculator.Path path = this.pathCalculators.get(accessItem.getId()).calcLeastCostPath(fromNode, toNode, departureTime, person, null); + double travelTime = path.travelTime; + abstractAccessRoute.setDistance(path.travelCost); + abstractAccessRoute.setTravelTime(travelTime); + } else { + double distance = accessItem.getDistanceToCenter(this.network.getLinks().get(otherLinkId).getCoord()); + abstractAccessRoute.setDistance(distance); + abstractAccessRoute.setTravelTime(accessItem.getTimeToCenter(distance)); + } + abstractAccessRoute.setWaitTime(accessItem.getWaitTime()); + // we add the wait time to the travel time of the leg since the current departure handler does not explicitly simulate waiting + leg.setTravelTime(abstractAccessRoute.getWaitTime() + abstractAccessRoute.getTravelTime().seconds()); + return leg; + } + + private TransitStopFacility getClosestTransitStopWithValidAccessItem(Coord coord, IdMap bestAccessesMap) { + return this.quadTree.getDisk(coord.getX(), coord.getY(), this.maxRadius). + stream(). + filter(transitStopFacility -> { + if (!accessItems.containsKey(transitStopFacility.getId())) { + return false; + } + AbstractAccessItem bestAccessItem = AbstractAccessItem.getFastestAccessItemForCoord(coord, accessItems.get(transitStopFacility.getId())); + if (bestAccessItem != null) { + bestAccessesMap.put(transitStopFacility.getId(), bestAccessItem); + return true; + } + return false; + }).min((Comparator) (o1, o2) -> + (int) (NetworkUtils.getEuclideanDistance(coord, o1.getCoord()) - NetworkUtils.getEuclideanDistance(coord, o2.getCoord()))).orElse(null); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/AdaptConfigForTransitWithAbstractAccess.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/AdaptConfigForTransitWithAbstractAccess.java new file mode 100644 index 000000000..17bacc8d0 --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/AdaptConfigForTransitWithAbstractAccess.java @@ -0,0 +1,49 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.utils; + +import org.eqasim.core.components.config.EqasimConfigGroup; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.TransitWithAbstractAbstractAccessModuleConfigGroup; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.TransitWithAbstractAccessModeChoiceModule; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.constraints.TransitWithAbstractAccessConstraint; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.routing.TransitWithAbstractAccessRoutingModule; +import org.matsim.contribs.discrete_mode_choice.modules.config.DiscreteModeChoiceConfigGroup; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.config.groups.ScoringConfigGroup; + +import java.nio.file.Path; + +public class AdaptConfigForTransitWithAbstractAccess { + + + + public static void main(String[] args) throws CommandLine.ConfigurationException { + CommandLine commandLine = new CommandLine.Builder(args) + .requireOptions("input-config-path", "output-config-path", "mode-name", "accesses-file-path") + .build(); + + Config config = ConfigUtils.loadConfig(commandLine.getOptionStrict("input-config-path"), new DiscreteModeChoiceConfigGroup(), new EqasimConfigGroup()); + String mode = commandLine.getOptionStrict("mode-name"); + String outputConfigPath = commandLine.getOptionStrict("output-config-path"); + + DiscreteModeChoiceConfigGroup dmcConfigGroup = (DiscreteModeChoiceConfigGroup) config.getModules().get(DiscreteModeChoiceConfigGroup.GROUP_NAME); + dmcConfigGroup.getTripConstraints().add(TransitWithAbstractAccessConstraint.NAME); + + EqasimConfigGroup eqasimConfigGroup = (EqasimConfigGroup) config.getModules().get(EqasimConfigGroup.GROUP_NAME); + eqasimConfigGroup.setEstimator(mode, TransitWithAbstractAccessModeChoiceModule.TRANSIT_WITH_ABSTRACT_ACCESS_UTILITY_ESTIMATOR_NAME); + + ScoringConfigGroup scoringConfigGroup = (ScoringConfigGroup) config.getModules().get(ScoringConfigGroup.GROUP_NAME); + ScoringConfigGroup.ModeParams modeParams = new ScoringConfigGroup.ModeParams(TransitWithAbstractAccessRoutingModule.ABSTRACT_ACCESS_LEG_MODE_NAME); + modeParams.setMarginalUtilityOfTraveling(-1); + scoringConfigGroup.addModeParams(modeParams); + + TransitWithAbstractAbstractAccessModuleConfigGroup transitWithAbstractAbstractAccessModuleConfigGroup = new TransitWithAbstractAbstractAccessModuleConfigGroup(); + transitWithAbstractAbstractAccessModuleConfigGroup.setModeName(mode); + + Path path = Path.of(outputConfigPath).getParent().toAbsolutePath().relativize(Path.of(commandLine.getOptionStrict("accesses-file-path")).toAbsolutePath()); + transitWithAbstractAbstractAccessModuleConfigGroup.setAccessItemsFilePath(path.toString()); + config.addModule(transitWithAbstractAbstractAccessModuleConfigGroup); + + ConfigUtils.writeConfig(config, outputConfigPath); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/CreateAbstractAccessItemsForTransitLines.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/CreateAbstractAccessItemsForTransitLines.java new file mode 100644 index 000000000..29d4f8d9b --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/CreateAbstractAccessItemsForTransitLines.java @@ -0,0 +1,88 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.utils; + +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessesFileReader; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessesFileWriter; +import org.matsim.api.core.v01.Id; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.pt.transitSchedule.api.*; + +import java.io.File; +import java.util.*; + +public class CreateAbstractAccessItemsForTransitLines { + + public static void main(String[] args) throws CommandLine.ConfigurationException { + CommandLine commandLine = new CommandLine.Builder(args) + .requireOptions("transit-schedule-path", "output-path", "radius", "average-speed", "access-type", "use-routed-distance") + .allowOptions("transit-lines-ids", "route-modes", "frequency", "append-to-output", "remove-redundant-items") + .build(); + + Config config = ConfigUtils.createConfig(); + Scenario scenario = ScenarioUtils.createScenario(config); + new TransitScheduleReader(scenario).readFile(commandLine.getOptionStrict("transit-schedule-path")); + + File outputFile = new File(commandLine.getOptionStrict("output-path")); + + TransitSchedule transitSchedule = scenario.getTransitSchedule(); + + Collection items = new ArrayList<>(); + if(Boolean.parseBoolean(commandLine.getOption("append-to-output").orElse("false")) && outputFile.isFile()) { + AbstractAccessesFileReader reader = new AbstractAccessesFileReader(transitSchedule); + reader.readFile(outputFile.getAbsolutePath()); + items.addAll(reader.getAccessItems().values()); + } + Set transitStopFacilities = new HashSet<>(); + if(commandLine.hasOption("transit-lines-ids")) { + for(String idString: commandLine.getOptionStrict("transit-lines-ids").split(",")) { + Id transitLineId = Id.create(idString, TransitLine.class); + TransitLine transitLine = transitSchedule.getTransitLines().get(transitLineId); + if(transitLine == null) { + throw new IllegalStateException("Transit line " + idString + " does not exist"); + } + for(TransitRoute transitRoute: transitLine.getRoutes().values()) { + for(TransitRouteStop transitRouteStop: transitRoute.getStops()) { + transitStopFacilities.add(transitRouteStop.getStopFacility()); + } + } + } + } + if(commandLine.hasOption("route-modes")) { + Collection routeModes = Arrays.asList(commandLine.getOptionStrict("route-modes").split(",")); + transitSchedule.getTransitLines().values().stream() + .flatMap(transitLine -> transitLine.getRoutes().values().stream()) + .filter(transitRoute -> routeModes.contains(transitRoute.getTransportMode())) + .flatMap(transitRoute -> transitRoute.getStops().stream()) + .map(TransitRouteStop::getStopFacility) + .forEach(transitStopFacilities::add); + } + + double radius = Double.parseDouble(commandLine.getOptionStrict("radius")); + double avgSpeed = Double.parseDouble(commandLine.getOptionStrict("average-speed")); + boolean usingRoutedDistance = Boolean.parseBoolean(commandLine.getOptionStrict("use-routed-distance")); + int frequency = Integer.parseInt(commandLine.getOption("frequency").orElse("600")); + + String accessType = commandLine.getOptionStrict("access-type"); + + HashSet> itemIds = new HashSet<>(); + for(AbstractAccessItem item: items) { + itemIds.add(item.getId()); + } + + for(TransitStopFacility transitStopFacility: transitStopFacilities) { + int id=-1; + Id itemId; + do { + id+=1; + itemId = Id.create(transitStopFacility.getId().toString()+"-"+id, AbstractAccessItem.class); + }while(itemIds.contains(itemId)); + AbstractAccessItem item = new AbstractAccessItem(itemId, transitStopFacility, radius, avgSpeed, accessType, usingRoutedDistance, frequency); + items.add(item); + } + new AbstractAccessesFileWriter(items).write(outputFile.getAbsolutePath()); + } +} diff --git a/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/ExportAbstractAccessItemsToShapefile.java b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/ExportAbstractAccessItemsToShapefile.java new file mode 100644 index 000000000..70e06110a --- /dev/null +++ b/core/src/main/java/org/eqasim/core/simulation/modes/transit_with_abstract_access/utils/ExportAbstractAccessItemsToShapefile.java @@ -0,0 +1,59 @@ +package org.eqasim.core.simulation.modes.transit_with_abstract_access.utils; + + +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessItem; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.abstract_access.AbstractAccessesFileReader; +import org.matsim.api.core.v01.Scenario; +import org.matsim.core.config.CommandLine; +import org.matsim.core.config.Config; +import org.matsim.core.config.ConfigUtils; +import org.matsim.core.scenario.ScenarioUtils; +import org.matsim.core.utils.geometry.geotools.MGC; +import org.matsim.core.utils.gis.PointFeatureFactory; +import org.matsim.core.utils.gis.ShapeFileWriter; +import org.matsim.pt.transitSchedule.api.TransitScheduleReader; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +import java.util.Collection; +import java.util.LinkedList; + +public class ExportAbstractAccessItemsToShapefile { + + public static void main(String[] args) throws CommandLine.ConfigurationException { + CommandLine commandLine = new CommandLine.Builder(args).requireOptions("schedule-path", "items-path", "crs", "output-path").build(); + Config config = ConfigUtils.createConfig(); + Scenario scenario = ScenarioUtils.createScenario(config); + new TransitScheduleReader(scenario).readFile(commandLine.getOptionStrict("schedule-path")); + AbstractAccessesFileReader reader = new AbstractAccessesFileReader(scenario.getTransitSchedule()); + reader.readFile(commandLine.getOptionStrict("items-path")); + + Collection features = new LinkedList<>(); + + CoordinateReferenceSystem crs = MGC.getCRS(commandLine.getOptionStrict("crs")); + + PointFeatureFactory pointFactory = new PointFeatureFactory.Builder() // + .setCrs(crs).setName("id") // + .addAttribute("id", String.class) + .addAttribute("centerStop", String.class)// + .addAttribute("radius", Double.class) + .addAttribute("speed", Double.class) + .addAttribute("type", String.class) + .addAttribute("routed", Boolean.class)// + .create(); + + for(AbstractAccessItem item: reader.getAccessItems().values()) { + SimpleFeature feature = pointFactory.createPoint(item.getCenterStop().getCoord(), + new Object[]{ + item.getId().toString(), + item.getCenterStop().getId().toString(), + item.getRadius(), + item.getAvgSpeedToCenterStop(), + item.getAccessType(), + item.isUsingRoutedDistance() + }, null); + features.add(feature); + } + ShapeFileWriter.writeGeometries(features, commandLine.getOptionStrict("output-path")); + } +} diff --git a/core/src/test/java/org/eqasim/TestSimulationPipeline.java b/core/src/test/java/org/eqasim/TestSimulationPipeline.java index dc5604b21..681cc4ba4 100644 --- a/core/src/test/java/org/eqasim/TestSimulationPipeline.java +++ b/core/src/test/java/org/eqasim/TestSimulationPipeline.java @@ -23,6 +23,9 @@ import org.eqasim.core.simulation.modes.feeder_drt.analysis.run.RunFeederDrtPassengerAnalysis; import org.eqasim.core.simulation.modes.feeder_drt.mode_choice.FeederDrtModeAvailabilityWrapper; import org.eqasim.core.simulation.modes.feeder_drt.utils.AdaptConfigForFeederDrt; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.mode_choice.TransitWithAbstractAccessModeAvailabilityWrapper; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.utils.AdaptConfigForTransitWithAbstractAccess; +import org.eqasim.core.simulation.modes.transit_with_abstract_access.utils.CreateAbstractAccessItemsForTransitLines; import org.eqasim.core.tools.ExportActivitiesToShapefile; import org.eqasim.core.tools.ExportNetworkToShapefile; import org.eqasim.core.tools.ExportPopulationToCSV; @@ -46,7 +49,7 @@ import com.google.inject.Provider; public class TestSimulationPipeline { - + @Before public void setUp() throws IOException { URL fixtureUrl = getClass().getClassLoader().getResource("melun"); @@ -82,9 +85,10 @@ protected void installEqasimExtension() { bindModeAvailability("DefaultModeAvailability").toProvider(new Provider<>() { @Inject private Config config; + @Override public ModeAvailability get() { - return new FeederDrtModeAvailabilityWrapper(config, (person, trips) -> { + FeederDrtModeAvailabilityWrapper feederDrtModeAvailabilityWrapper = new FeederDrtModeAvailabilityWrapper(config, (person, trips) -> { Set modes = new HashSet<>(); modes.add(TransportMode.walk); modes.add(TransportMode.pt); @@ -92,11 +96,12 @@ public ModeAvailability get() { modes.add(TransportMode.bike); // Add special mode "car_passenger" if applicable Boolean isCarPassenger = (Boolean) person.getAttributes().getAttribute("isPassenger"); - if(isCarPassenger) { + if (isCarPassenger) { modes.add("car_passenger"); } return modes; }); + return new TransitWithAbstractAccessModeAvailabilityWrapper(config, feederDrtModeAvailabilityWrapper); } }).asEagerSingleton(); } @@ -105,7 +110,7 @@ public ModeAvailability get() { } private void runAnalyses() throws CommandLine.ConfigurationException, IOException { - RunTripAnalysis.main(new String[] { + RunTripAnalysis.main(new String[]{ "--events-path", "melun_test/output/output_events.xml.gz", "--network-path", "melun_test/input/network.xml.gz", "--output-path", "melun_test/output/eqasim_trips_post_sim.csv" @@ -122,7 +127,7 @@ private void runAnalyses() throws CommandLine.ConfigurationException, IOExceptio assert CRCChecksum.getCRCFromFile("melun_test/output/eqasim_legs.csv") == CRCChecksum.getCRCFromFile("melun_test/output/eqasim_legs_post_sim.csv"); - RunPublicTransportLegAnalysis.main(new String[] { + RunPublicTransportLegAnalysis.main(new String[]{ "--events-path", "melun_test/output/output_events.xml.gz", "--schedule-path", "melun_test/input/transit_schedule.xml.gz", "--output-path", "melun_test/output/eqasim_pt_post_sim.csv" @@ -132,14 +137,14 @@ private void runAnalyses() throws CommandLine.ConfigurationException, IOExceptio } private void runExports() throws Exception { - ExportTransitLinesToShapefile.main(new String[] { + ExportTransitLinesToShapefile.main(new String[]{ "--schedule-path", "melun_test/input/transit_schedule.xml.gz", "--network-path", "melun_test/input/network.xml.gz", "--crs", "EPSG:2154", "--output-path", "melun_test/exports/lines.shp" }); - ExportTransitLinesToShapefile.main(new String[] { + ExportTransitLinesToShapefile.main(new String[]{ "--schedule-path", "melun_test/input/transit_schedule.xml.gz", "--network-path", "melun_test/input/network.xml.gz", "--crs", "EPSG:2154", @@ -147,7 +152,7 @@ private void runExports() throws Exception { "--output-path", "melun_test/exports/lines_rail.shp" }); - ExportTransitLinesToShapefile.main(new String[] { + ExportTransitLinesToShapefile.main(new String[]{ "--schedule-path", "melun_test/input/transit_schedule.xml.gz", "--network-path", "melun_test/input/network.xml.gz", "--crs", "EPSG:2154", @@ -155,7 +160,7 @@ private void runExports() throws Exception { "--output-path", "melun_test/exports/lines_line_ids.shp" }); - ExportTransitLinesToShapefile.main(new String[] { + ExportTransitLinesToShapefile.main(new String[]{ "--schedule-path", "melun_test/input/transit_schedule.xml.gz", "--network-path", "melun_test/input/network.xml.gz", "--crs", "EPSG:2154", @@ -163,13 +168,13 @@ private void runExports() throws Exception { "--output-path", "melun_test/exports/lines_route_ids.shp" }); - ExportTransitStopsToShapefile.main(new String[] { + ExportTransitStopsToShapefile.main(new String[]{ "--schedule-path", "melun_test/input/transit_schedule.xml.gz", "--crs", "EPSG:2154", "--output-path", "melun_test/exports/stops.shp" }); - ExportNetworkToShapefile.main(new String[] { + ExportNetworkToShapefile.main(new String[]{ "--network-path", "melun_test/input/network.xml.gz", "--crs", "EPSG:2154", "--output-path", "melun_test/exports/network.shp" @@ -203,7 +208,7 @@ public void testDrt() throws IOException, CommandLine.ConfigurationException { "--vehicle-id-prefix", "vehicle_drt_b_" }); - AdaptConfigForDrt.main(new String[] { + AdaptConfigForDrt.main(new String[]{ "--input-config-path", "melun_test/input/config.xml", "--output-config-path", "melun_test/input/config_drt.xml", "--mode-names", "drt_a,drt_b", @@ -212,14 +217,14 @@ public void testDrt() throws IOException, CommandLine.ConfigurationException { runMelunSimulation("melun_test/input/config_drt.xml", "melun_test/output_drt"); - RunDrtPassengerAnalysis.main(new String[] { + RunDrtPassengerAnalysis.main(new String[]{ "--events-path", "melun_test/output_drt/output_events.xml.gz", "--network-path", "melun_test/output_drt/output_network.xml.gz", "--modes", "drt_a,drt_b", "--output-path", "melun_test/output_drt/eqasim_drt_passenger_rides_standalone.csv" }); - RunDrtVehicleAnalysis.main(new String[] { + RunDrtVehicleAnalysis.main(new String[]{ "--events-path", "melun_test/output_drt/output_events.xml.gz", "--network-path", "melun_test/output_drt/output_network.xml.gz", "--movements-output-path", "melun_test/output_drt/eqasim_drt_vehicle_movements_standalone.csv", @@ -261,7 +266,7 @@ public void testFeeder() throws IOException, CommandLine.ConfigurationException runMelunSimulation("melun_test/input/config_feeder.xml", "melun_test/output_feeder"); - RunFeederDrtPassengerAnalysis.main(new String[] { + RunFeederDrtPassengerAnalysis.main(new String[]{ "--config-path", "melun_test/input/config_feeder.xml", "--events-path", "melun_test/output_feeder/output_events.xml.gz", "--network-path", "melun_test/output_feeder/output_network.xml.gz", @@ -271,7 +276,7 @@ public void testFeeder() throws IOException, CommandLine.ConfigurationException @Test public void testEpsilon() throws CommandLine.ConfigurationException { - AdaptConfigForEpsilon.main(new String[] { + AdaptConfigForEpsilon.main(new String[]{ "--input-config-path", "melun_test/input/config.xml", "--output-config-path", "melun_test/input/config_epsilon.xml" }); @@ -279,6 +284,30 @@ public void testEpsilon() throws CommandLine.ConfigurationException { runMelunSimulation("melun_test/input/config_epsilon.xml", "melun_test/output_epsilon"); } + @Test + public void testTransitWithAbstractAccess() throws CommandLine.ConfigurationException { + CreateAbstractAccessItemsForTransitLines.main(new String[]{ + "--transit-schedule-path", "melun_test/input/transit_schedule.xml.gz", + "--output-path", "melun_test/input/access_items.xml", + "--radius", "1000", + "--average-speed", "18", + "--route-modes", "rail", + "--use-routed-distance", "true", + "--access-type", "bus", + "--frequency", "60" + }); + + AdaptConfigForTransitWithAbstractAccess.main(new String[]{ + "--input-config-path", "melun_test/input/config.xml", + "--output-config-path", "melun_test/input/config_abstract_access.xml", + "--mode-name", "ptWithAbstractAccess", + "--accesses-file-path", "melun_test/input/access_items.xml" + }); + + + runMelunSimulation("melun_test/input/config_abstract_access.xml", "melun_test/output_abstract_access"); + } + @Test public void testPipeline() throws Exception { runMelunSimulation("melun_test/input/config.xml", "melun_test/output");