diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 80531a5de..f516e09e6 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ on:
branches: [ master ]
jobs:
- test:
+ build:
runs-on: ubuntu-latest
@@ -16,10 +16,10 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v1
with:
- java-version: 8
+ java-version: 11
- name: Cache Maven dependencies
- uses: actions/cache@v2
+ uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
@@ -27,4 +27,14 @@ jobs:
${{ runner.os }}-maven-
- name: Test project with Maven
- run: mvn --no-transfer-progress test verify
+ run: mvn --no-transfer-progress test install
+
+ - name: Build documentation
+ run: mvn --no-transfer-progress site
+
+ - name: Deploy documentation to Github Pages
+ # only deploy after merging to master
+ if: github.repository_owner == 'OneBusAway' && github.event_name == 'push' && github.ref == 'refs/heads/master'
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: target/site/
diff --git a/onebusaway-gtfs-hibernate-cli/pom.xml b/onebusaway-gtfs-hibernate-cli/pom.xml
index 798757094..a4823578a 100644
--- a/onebusaway-gtfs-hibernate-cli/pom.xml
+++ b/onebusaway-gtfs-hibernate-cli/pom.xml
@@ -3,7 +3,7 @@
org.onebusaway
onebusaway-gtfs-modules
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
onebusaway-gtfs-hibernate-cli
onebusaway-gtfs-hibernate-cli
@@ -28,7 +28,8 @@
org.slf4j
- slf4j-log4j12
+ slf4j-simple
+ ${slf4j_version}
diff --git a/onebusaway-gtfs-hibernate-cli/src/main/resources/log4j.properties b/onebusaway-gtfs-hibernate-cli/src/main/resources/log4j.properties
deleted file mode 100644
index 6df8853de..000000000
--- a/onebusaway-gtfs-hibernate-cli/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2008 Brian Ferris
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-
-log4j.rootLogger = INFO, stdout
-
-log4j.appender.stdout = org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Threshold = DEBUG
-log4j.appender.stdout.Target = System.out
-log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
-
-
-
diff --git a/onebusaway-gtfs-hibernate-cli/src/main/resources/simplelogger.properties b/onebusaway-gtfs-hibernate-cli/src/main/resources/simplelogger.properties
new file mode 100644
index 000000000..712780a1e
--- /dev/null
+++ b/onebusaway-gtfs-hibernate-cli/src/main/resources/simplelogger.properties
@@ -0,0 +1,25 @@
+# SLF4J's SimpleLogger configuration file
+# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
+# Default logging detail level for all instances of SimpleLogger.
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, defaults to "info".
+org.slf4j.simpleLogger.defaultLogLevel=info
+# Logging detail level for a SimpleLogger instance named "xxxxx".
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, the default logging detail level is used.
+#org.slf4j.simpleLogger.log.xxxxx=
+# Set to true if you want the current date and time to be included in output messages.
+# Default is false, and will output the number of milliseconds elapsed since startup.
+org.slf4j.simpleLogger.showDateTime=true
+# The date and time format to be used in the output messages.
+# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
+# If the format is not specified or is invalid, the default format is used.
+# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
+#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
+org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
+# Set to true if you want to output the current thread name.
+# Defaults to true.
+org.slf4j.simpleLogger.showThreadName=false
+# Set to true if you want the last component of the name to be included in output messages.
+# Defaults to false.
+org.slf4j.simpleLogger.showShortLogName=true
diff --git a/onebusaway-gtfs-hibernate/pom.xml b/onebusaway-gtfs-hibernate/pom.xml
index 84c86ce61..c72304fed 100644
--- a/onebusaway-gtfs-hibernate/pom.xml
+++ b/onebusaway-gtfs-hibernate/pom.xml
@@ -9,7 +9,7 @@
org.onebusaway
onebusaway-gtfs-modules
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
@@ -32,9 +32,9 @@
test
- mysql
- mysql-connector-java
- 5.1.48
+ com.mysql
+ mysql-connector-j
+ 8.2.0
junit
@@ -43,8 +43,8 @@
org.slf4j
- slf4j-log4j12
- test
+ slf4j-simple
+ ${slf4j_version}
com.sun.xml.bind
diff --git a/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java b/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java
index f75f59cac..89f7a5881 100644
--- a/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java
+++ b/onebusaway-gtfs-hibernate/src/main/java/org/onebusaway/gtfs/impl/HibernateGtfsRelationalDaoImpl.java
@@ -113,6 +113,16 @@ public List getAllRoutes() {
return _ops.find("FROM Route route");
}
+ @Override
+ public List getAllRouteStops() {
+ return _ops.find("FROM RouteStop routeStop");
+ }
+
+ @Override
+ public List getAllRouteShapes() {
+ return _ops.find("FROM RouteShape routeShape");
+ }
+
@Override
public List getAllStops() {
return _ops.find("FROM Stop");
@@ -151,6 +161,17 @@ public Collection getAllTransfers() {
@Override
public Collection getAllRiderships() { return _ops.find("FROM Ridership"); }
+
+ @Override
+ public Collection getAllDirectionEntries() {
+ return _ops.find("FROM DirectionEntry");
+ }
+
+ @Override
+ public Collection getAllWrongWayConcurrencies() {
+ return _ops.find("FROM WrongWayConcurrency");
+ }
+
@Override
public Agency getAgencyForId(String id) {
return (Agency) _ops.get(Agency.class, id);
@@ -182,8 +203,8 @@ public FareProduct getFareProductForId(AgencyAndId id) {
}
@Override
- public Collection getAllFareContainers() {
- return _ops.find("FROM FareContainer");
+ public Collection getAllFareMedia() {
+ return _ops.find("FROM FareMedium");
}
@Override
@@ -266,6 +287,7 @@ public Collection getAllAreas() {
return _ops.find("from Area");
}
+ @Deprecated
@Override
public Collection getAllLocationGroupElements() {
Collection groups = _ops.find("FROM LocationGroup");
@@ -278,11 +300,25 @@ public Collection getAllLocationGroupElements() {
})).collect(Collectors.toList());
}
+ @Override
+ public Collection getAllStopAreaElements() {
+ Collection groups = _ops.find("FROM StopArea");
+ return groups.stream().flatMap(group -> group.getLocations().stream().map(stopLocation -> {
+ var stopAreaElement = new StopAreaElement();
+ stopAreaElement.setId(group.getId());
+ stopAreaElement.setStopLocation(stopLocation);
+ return stopAreaElement;
+ })).collect(Collectors.toList());
+ }
+
@Override
public Collection getAllLocationGroups() {
return _ops.find("FROM LocationGroup");
}
-
+ @Override
+ public Collection getAllStopAreas() {
+ return _ops.find("from StopArea");
+ }
@Override
public Collection getAllLocations() {
return _ops.find("FROM Location");
@@ -299,8 +335,23 @@ public Collection getAllTranslations() {
}
@Override
- public Collection getAllStopAreas() {
- return _ops.find("from StopArea");
+ public List getOptionalMetadataFilenames() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public boolean hasMetadata(String filename) {
+ return false;
+ }
+
+ @Override
+ public String getMetadata(String filename) {
+ return null;
+ }
+
+ @Override
+ public void addMetadata(String filename, String content) {
+
}
/****
diff --git a/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml b/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml
index e70ded90e..d17e6902d 100644
--- a/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml
+++ b/onebusaway-gtfs-hibernate/src/main/resources/org/onebusaway/gtfs/model/GtfsMapping.hibernate.xml
@@ -83,6 +83,8 @@
+
+
@@ -206,6 +208,7 @@
+
diff --git a/onebusaway-gtfs-hibernate/src/test/resources/log4j.properties b/onebusaway-gtfs-hibernate/src/test/resources/log4j.properties
deleted file mode 100644
index 6df8853de..000000000
--- a/onebusaway-gtfs-hibernate/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,25 +0,0 @@
-# Copyright 2008 Brian Ferris
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-
-log4j.rootLogger = INFO, stdout
-
-log4j.appender.stdout = org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Threshold = DEBUG
-log4j.appender.stdout.Target = System.out
-log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
-
-
-
diff --git a/onebusaway-gtfs-hibernate/src/test/resources/simplelogger.properties b/onebusaway-gtfs-hibernate/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..712780a1e
--- /dev/null
+++ b/onebusaway-gtfs-hibernate/src/test/resources/simplelogger.properties
@@ -0,0 +1,25 @@
+# SLF4J's SimpleLogger configuration file
+# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
+# Default logging detail level for all instances of SimpleLogger.
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, defaults to "info".
+org.slf4j.simpleLogger.defaultLogLevel=info
+# Logging detail level for a SimpleLogger instance named "xxxxx".
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, the default logging detail level is used.
+#org.slf4j.simpleLogger.log.xxxxx=
+# Set to true if you want the current date and time to be included in output messages.
+# Default is false, and will output the number of milliseconds elapsed since startup.
+org.slf4j.simpleLogger.showDateTime=true
+# The date and time format to be used in the output messages.
+# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
+# If the format is not specified or is invalid, the default format is used.
+# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
+#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
+org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
+# Set to true if you want to output the current thread name.
+# Defaults to true.
+org.slf4j.simpleLogger.showThreadName=false
+# Set to true if you want the last component of the name to be included in output messages.
+# Defaults to false.
+org.slf4j.simpleLogger.showShortLogName=true
diff --git a/onebusaway-gtfs-merge-cli/pom.xml b/onebusaway-gtfs-merge-cli/pom.xml
index b9157592d..c19772239 100644
--- a/onebusaway-gtfs-merge-cli/pom.xml
+++ b/onebusaway-gtfs-merge-cli/pom.xml
@@ -3,7 +3,7 @@
onebusaway-gtfs-modules
org.onebusaway
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
..
onebusaway-gtfs-merge-cli
@@ -29,7 +29,8 @@
org.slf4j
- slf4j-log4j12
+ slf4j-simple
+ ${slf4j_version}
diff --git a/onebusaway-gtfs-merge-cli/src/main/resources/log4j.properties b/onebusaway-gtfs-merge-cli/src/main/resources/log4j.properties
deleted file mode 100644
index 2c9757ccc..000000000
--- a/onebusaway-gtfs-merge-cli/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-log4j.rootLogger = INFO, stdout
-
-log4j.appender.stdout = org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Threshold = DEBUG
-log4j.appender.stdout.Target = System.out
-log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
-
-
diff --git a/onebusaway-gtfs-merge-cli/src/main/resources/simplelogger.properties b/onebusaway-gtfs-merge-cli/src/main/resources/simplelogger.properties
new file mode 100644
index 000000000..712780a1e
--- /dev/null
+++ b/onebusaway-gtfs-merge-cli/src/main/resources/simplelogger.properties
@@ -0,0 +1,25 @@
+# SLF4J's SimpleLogger configuration file
+# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
+# Default logging detail level for all instances of SimpleLogger.
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, defaults to "info".
+org.slf4j.simpleLogger.defaultLogLevel=info
+# Logging detail level for a SimpleLogger instance named "xxxxx".
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, the default logging detail level is used.
+#org.slf4j.simpleLogger.log.xxxxx=
+# Set to true if you want the current date and time to be included in output messages.
+# Default is false, and will output the number of milliseconds elapsed since startup.
+org.slf4j.simpleLogger.showDateTime=true
+# The date and time format to be used in the output messages.
+# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
+# If the format is not specified or is invalid, the default format is used.
+# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
+#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
+org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
+# Set to true if you want to output the current thread name.
+# Defaults to true.
+org.slf4j.simpleLogger.showThreadName=false
+# Set to true if you want the last component of the name to be included in output messages.
+# Defaults to false.
+org.slf4j.simpleLogger.showShortLogName=true
diff --git a/onebusaway-gtfs-merge/pom.xml b/onebusaway-gtfs-merge/pom.xml
index f4b77f13c..5dba66aa9 100644
--- a/onebusaway-gtfs-merge/pom.xml
+++ b/onebusaway-gtfs-merge/pom.xml
@@ -3,7 +3,7 @@
onebusaway-gtfs-modules
org.onebusaway
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
..
onebusaway-gtfs-merge
@@ -24,8 +24,8 @@
org.slf4j
- slf4j-log4j12
- test
+ slf4j-simple
+ ${slf4j_version}
junit
diff --git a/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/GtfsMerger.java b/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/GtfsMerger.java
index 77be3e311..a5aa02f76 100644
--- a/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/GtfsMerger.java
+++ b/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/GtfsMerger.java
@@ -34,20 +34,7 @@
import org.onebusaway.gtfs.model.Node;
import org.onebusaway.gtfs.serialization.GtfsReader;
import org.onebusaway.gtfs.serialization.GtfsWriter;
-import org.onebusaway.gtfs_merge.strategies.AgencyMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.AreaMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.EntityMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.FareAttributeMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.FareRuleMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.FeedInfoMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.FrequencyMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.RouteMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.ServiceCalendarMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.ShapePointMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.StopMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.TransferMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.TripMergeStrategy;
-import org.onebusaway.gtfs_merge.strategies.ZoneMergeStrategy;
+import org.onebusaway.gtfs_merge.strategies.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -87,6 +74,8 @@ public class GtfsMerger {
private EntityMergeStrategy _zoneStrategy = new ZoneMergeStrategy();
+ private EntityMergeStrategy _metadataStrategy = new MetadataMergeStrategy();
+
public void setAgencyStrategy(EntityMergeStrategy agencyStrategy) {
_agencyStrategy = agencyStrategy;
}
@@ -136,7 +125,8 @@ public void setAreaStrategy(AreaMergeStrategy areaStrategy) {
public void setZoneStrategy(EntityMergeStrategy zoneStrategy) { _zoneStrategy = zoneStrategy; }
-
+ public void setMetadataStrategy(EntityMergeStrategy metadataStrategy) { _metadataStrategy = metadataStrategy; }
+
public EntityMergeStrategy getEntityMergeStrategyForEntityType(
Class> entityType) {
List strategies = new ArrayList();
@@ -237,7 +227,7 @@ public void run(List inputPaths, File outputPath) throws IOException {
"lastModifiedTime",
FileTime.fromMillis(newestFile));
} else {
- _log.info("outputPath not a file, skipping");
+ _log.info("outputPath not a file, skipping setting lastModified");
}
}
@@ -262,6 +252,7 @@ private void buildStrategies(List strategies) {
strategies.add(_fareRuleStrategy);
strategies.add(_feedInfoStrategy);
strategies.add(_zoneStrategy);
+ strategies.add(_metadataStrategy);
}
}
diff --git a/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies/MetadataMergeStrategy.java b/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies/MetadataMergeStrategy.java
new file mode 100644
index 000000000..5444f3a5b
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/main/java/org/onebusaway/gtfs_merge/strategies/MetadataMergeStrategy.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_merge.strategies;
+
+import org.onebusaway.gtfs.model.Agency;
+import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
+import org.onebusaway.gtfs.services.GtfsRelationalDao;
+import org.onebusaway.gtfs_merge.GtfsMergeContext;
+
+import java.util.Collection;
+
+/**
+ * Merge metadata files such as modifications.txt
+ */
+public class MetadataMergeStrategy implements EntityMergeStrategy {
+ @Override
+ public void getEntityTypes(Collection> entityTypes) {
+ // no-op, metadata is not represented via entityTypes
+ }
+
+ @Override
+ public void merge(GtfsMergeContext context) {
+ GtfsRelationalDao source = context.getSource();
+ GtfsMutableRelationalDao target = context.getTarget();
+ String agencyStr = getAgencyStr(source);
+ for (String filename : source.getOptionalMetadataFilenames()) {
+ if (source.hasMetadata(filename)) {
+ StringBuffer content = new StringBuffer();
+ content.append("\n====== ");
+ content.append(agencyStr);
+ content.append(" ======\n");
+ content.append(source.getMetadata(filename));
+ if (target.hasMetadata(filename)) {
+ content.append(target.getMetadata(filename));
+ }
+ target.addMetadata(filename, content.toString());
+ }
+ }
+ }
+
+ private String getAgencyStr(GtfsRelationalDao source) {
+ int agencyCount = source.getAllAgencies().size();
+ if (agencyCount == 0)
+ return "";
+ if (agencyCount == 1)
+ return source.getAllAgencies().iterator().next().getId();
+ StringBuffer sb = new StringBuffer();
+ sb.append("[");
+ for (Agency agency : source.getAllAgencies()) {
+ sb.append(agency).append(",");
+ }
+ return sb.substring(0, sb.length()-2) + "]";
+ }
+}
diff --git a/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java
new file mode 100644
index 000000000..02be0698d
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/java/org/onebusaway/gtfs_merge/MergeExpectedFilesTest.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_merge;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onebusaway.gtfs.impl.FileSupport;
+import org.onebusaway.gtfs.impl.ZipHandler;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test appending metadata inputs as part of merge.
+ */
+public class MergeExpectedFilesTest {
+
+ private GtfsMerger _merger;
+
+ private FileSupport _support = new FileSupport();
+
+ @Before
+ public void before() throws IOException {
+ _merger = new GtfsMerger();
+ }
+
+ @After
+ public void after() {
+ _support.cleanup();
+ }
+
+ @Test
+ public void testDirectoryMerge() throws Exception {
+ File path0 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_merge/testagency").toURI());
+ File path1 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_merge/testagency1").toURI());
+ File path2 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_merge/testagency2").toURI());
+
+ List paths = new ArrayList();
+ paths.add(path0);
+ paths.add(path1);
+ paths.add(path2);
+
+ File gtfsDirectory = merge(paths, createTempDirectory());
+ String modLocation = gtfsDirectory.getAbsolutePath() + File.separator + "modifications.txt";
+ File expectedFile = new File(modLocation);
+ // verify modifications.txt is there!!!!
+ assertTrue("expected modifications.txt to be present!", expectedFile.exists());
+ assertTrue("expected modifications.txt to be a file!", expectedFile.isFile());
+ StringBuffer sb = new StringBuffer();
+ BufferedReader br = new BufferedReader(new FileReader(expectedFile));
+ sb.append(br.lines().collect(Collectors.joining(System.lineSeparator())));
+ assertTrue(sb.toString().contains("testagency"));
+ assertTrue(sb.toString().contains("testagency1"));
+ assertTrue(sb.toString().contains("testagency2"));
+ }
+
+ @Test
+ public void testZipMerge() throws Exception {
+ File path0 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_merge/testagency.zip").toURI());
+ File path1 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_merge/testagency1.zip").toURI());
+ File path2 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_merge/testagency2.zip").toURI());
+ List paths = new ArrayList();
+ paths.add(path0);
+ paths.add(path1);
+ paths.add(path2);
+
+ File gtfsZip = merge(paths, createTempFile());
+
+ ZipHandler zip = new ZipHandler(gtfsZip);
+ String content = zip.readTextFromFile("modifications.txt");
+ assertTrue(content.contains("testagency "));
+ assertTrue(content.contains("testagency1"));
+ assertTrue(content.contains("testagency2"));
+
+ }
+
+ private File createTempDirectory() throws IOException {
+ File tmpDirectory = File.createTempFile("MergeExpectedFilesTest-", "-tmp");
+ if (tmpDirectory.exists())
+ _support.deleteFileRecursively(tmpDirectory);
+ tmpDirectory.mkdirs();
+ _support.markForDeletion(tmpDirectory);
+ return tmpDirectory;
+ }
+
+ private File createTempFile() throws IOException {
+ File tmpZipFileDirectory = File.createTempFile("CarryForwardExpectedFilesTestZip-", "-tmp");
+ if (tmpZipFileDirectory.exists())
+ _support.deleteFileRecursively(tmpZipFileDirectory);
+ tmpZipFileDirectory.mkdirs();
+
+ File zipFile = new File(tmpZipFileDirectory.getAbsolutePath() + File.separator + "gtfs.zip");
+ _support.markForDeletion(zipFile);
+ return zipFile;
+ }
+
+ private File merge(List paths, File destination) throws IOException {
+ _merger.run(paths, destination);
+ return destination;
+ }
+}
diff --git a/onebusaway-gtfs-merge/src/test/resources/log4j.properties b/onebusaway-gtfs-merge/src/test/resources/log4j.properties
deleted file mode 100644
index 2c9757ccc..000000000
--- a/onebusaway-gtfs-merge/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-log4j.rootLogger = INFO, stdout
-
-log4j.appender.stdout = org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Threshold = DEBUG
-log4j.appender.stdout.Target = System.out
-log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
-
-
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency.zip b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency.zip
new file mode 100644
index 000000000..a515a6901
Binary files /dev/null and b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency.zip differ
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/agency.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/agency.txt
new file mode 100644
index 000000000..31e2a82e4
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/agency.txt
@@ -0,0 +1,2 @@
+agency_id,agency_name,agency_url,agency_timezone
+agency,Fake Agency,http://fake.example.com,America/New_York
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/calendar.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/calendar.txt
new file mode 100644
index 000000000..6b351dffa
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/calendar.txt
@@ -0,0 +1,3 @@
+service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
+alldays,1,1,1,1,1,1,1,20090101,20500101
+weekdays,1,1,1,1,1,0,0,20090101,20500101
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/modifications.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/modifications.txt
new file mode 100644
index 000000000..9bf255385
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/modifications.txt
@@ -0,0 +1 @@
+A simple comment for testagency.
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/routes.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/routes.txt
new file mode 100644
index 000000000..0a4dd22de
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/routes.txt
@@ -0,0 +1,8 @@
+route_id,route_short_name,route_long_name,route_type
+1,1,1,3
+2,2,2,3
+3,3,3,3
+4,4,4,4
+5,5,5,5
+6,6,6,6
+7,7,7,7
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/shapes.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/shapes.txt
new file mode 100644
index 000000000..5e0159c3f
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/shapes.txt
@@ -0,0 +1,10 @@
+shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
+4,41.0,-75.0,1,
+4,42.0,-75.0,2,
+4,42.5,-75.3,3,
+4,43.0,-75.0,4,
+5,41.0,-72.0,1,0
+5,41.5,-72.5,2,1.234
+5,41.0,-73.0,3,17.62
+5,41.5,-73.5,4,35.234
+5,41.0,-74.0,5,52.01
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/stop_times.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/stop_times.txt
new file mode 100644
index 000000000..228561b53
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/stop_times.txt
@@ -0,0 +1,44 @@
+trip_id,arrival_time,departure_time,stop_id,stop_sequence,shape_dist_traveled,pickup_type,drop_off_type
+1.1,00:00:00,00:00:00,A,1,,,
+1.1,00:10:00,00:10:00,B,2,,,
+1.1,00:20:00,00:20:00,C,3,,,
+1.2,00:20:00,00:20:00,A,1,0,,
+1.2,,,B,2,,3,2
+1.2,00:40:00,00:40:00,C,3,52.1,,
+1.3,08:00:00,08:00:00,A,1,,,
+1.3,08:10:00,08:20:00,B,2,,,
+1.3,08:30:00,08:30:00,C,3,,,
+2.1,00:20:00,00:20:00,B,1,,,
+2.1,00:30:00,00:30:00,C,2,,,
+2.1,00:40:00,00:40:00,D,3,,,
+2.2,00:50:00,00:50:00,B,1,,,
+2.2,01:00:00,01:00:00,C,2,,,
+2.2,01:10:00,01:10:00,D,3,,,
+3.1,00:40:00,00:40:00,B,1,,,
+3.1,00:50:00,00:50:00,C,2,,,
+3.1,01:00:00,01:00:00,D,3,,,
+3.1,01:10:00,01:10:00,E,4,,,
+3.2,01:00:00,01:00:00,B,1,,,
+3.2,01:10:00,01:10:00,C,2,,,
+3.2,01:20:00,01:20:00,D,3,,,
+3.2,01:30:00,01:30:00,E,4,,,
+4.1,05:00:00,05:00:00,F,1,,,
+4.1,05:30:00,05:30:00,G,2,,,
+4.1,06:00:00,06:00:00,H,3,,,
+4.2,23:00:00,23:00:00,F,1,,,
+4.2,23:30:00,23:30:00,G,2,,,
+4.2,24:00:00,24:00:00,H,3,,,
+4.3,23:40:00,23:40:00,F,1,,,
+4.3,24:10:00,24:10:00,G,2,,,
+4.3,24:40:00,24:40:00,H,3,,,
+5.1,08:00:00,08:00:00,I,1,0,,
+5.1,08:10:00,08:10:00,J,2,22.5,,
+5.1,08:20:00,08:20:00,K,3,52.01,,
+6.1,12:00:00,12:00:00,I,1,,,
+6.1,12:10:00,12:10:00,J,2,,,
+6.2,13:00:00,13:00:00,I,1,,,
+6.2,13:10:00,13:10:00,J,2,,,
+7.1,12:20:00,12:20:00,J,1,,,
+7.1,12:30:00,12:30:00,K,2,,,
+7.2,13:20:00,13:20:00,J,1,,,
+7.2,13:30:00,13:30:00,K,2,,,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/stops.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/stops.txt
new file mode 100644
index 000000000..37f31d1a3
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/stops.txt
@@ -0,0 +1,14 @@
+stop_id,stop_name,stop_lat,stop_lon,wheelchair_boarding
+A,A,40,-73,1
+B,B,40,-74,1
+C,C,40,-75,0
+D,D,40,-76,1
+E,E,40,-77,1
+F,F,41,-75,
+G,G,42,-75,
+H,H,43,-75,
+I,I,41,-72,
+J,J,41,-73,
+K,K,41,-74,
+L,L,41.000001,-73.000001,
+M,M,41.000002,-73.000002,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/transfers.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/transfers.txt
new file mode 100644
index 000000000..f085afa3a
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/transfers.txt
@@ -0,0 +1,5 @@
+to_stop_id,from_stop_id,transfer_type,min_transfer_time
+K,L,0,
+L,K,0,
+M,K,3,
+K,M,3,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/trips.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/trips.txt
new file mode 100644
index 000000000..40f94cba4
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency/trips.txt
@@ -0,0 +1,16 @@
+route_id,service_id,trip_id,shape_id,block_id,wheelchair_accessible
+1,alldays,1.1,,,1
+1,alldays,1.2,,,1
+1,alldays,1.3,,,1
+2,alldays,2.1,,,0
+2,alldays,2.2,,,0
+3,alldays,3.1,,,1
+3,alldays,3.2,,,1
+4,weekdays,4.1,4,,
+4,weekdays,4.2,4,,
+4,weekdays,4.3,4,,
+5,alldays,5.1,5,,
+6,alldays,6.1,,block.1,
+7,alldays,7.1,,block.1,
+6,alldays,6.2,,block.2,
+7,alldays,7.2,,block.2,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1.zip b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1.zip
new file mode 100644
index 000000000..92eafb976
Binary files /dev/null and b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1.zip differ
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/agency.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/agency.txt
new file mode 100644
index 000000000..eaceaf9df
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/agency.txt
@@ -0,0 +1,2 @@
+agency_id,agency_name,agency_url,agency_timezone
+agency1,Fake Agency1,http://fake.example.com,America/New_York
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/calendar.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/calendar.txt
new file mode 100644
index 000000000..6b351dffa
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/calendar.txt
@@ -0,0 +1,3 @@
+service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
+alldays,1,1,1,1,1,1,1,20090101,20500101
+weekdays,1,1,1,1,1,0,0,20090101,20500101
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/modifications.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/modifications.txt
new file mode 100644
index 000000000..5ea4b46a3
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/modifications.txt
@@ -0,0 +1,5 @@
+This is long text
+that forms
+the basis
+
+of testagency1
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/routes.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/routes.txt
new file mode 100644
index 000000000..0a4dd22de
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/routes.txt
@@ -0,0 +1,8 @@
+route_id,route_short_name,route_long_name,route_type
+1,1,1,3
+2,2,2,3
+3,3,3,3
+4,4,4,4
+5,5,5,5
+6,6,6,6
+7,7,7,7
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/shapes.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/shapes.txt
new file mode 100644
index 000000000..5e0159c3f
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/shapes.txt
@@ -0,0 +1,10 @@
+shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
+4,41.0,-75.0,1,
+4,42.0,-75.0,2,
+4,42.5,-75.3,3,
+4,43.0,-75.0,4,
+5,41.0,-72.0,1,0
+5,41.5,-72.5,2,1.234
+5,41.0,-73.0,3,17.62
+5,41.5,-73.5,4,35.234
+5,41.0,-74.0,5,52.01
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/stop_times.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/stop_times.txt
new file mode 100644
index 000000000..228561b53
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/stop_times.txt
@@ -0,0 +1,44 @@
+trip_id,arrival_time,departure_time,stop_id,stop_sequence,shape_dist_traveled,pickup_type,drop_off_type
+1.1,00:00:00,00:00:00,A,1,,,
+1.1,00:10:00,00:10:00,B,2,,,
+1.1,00:20:00,00:20:00,C,3,,,
+1.2,00:20:00,00:20:00,A,1,0,,
+1.2,,,B,2,,3,2
+1.2,00:40:00,00:40:00,C,3,52.1,,
+1.3,08:00:00,08:00:00,A,1,,,
+1.3,08:10:00,08:20:00,B,2,,,
+1.3,08:30:00,08:30:00,C,3,,,
+2.1,00:20:00,00:20:00,B,1,,,
+2.1,00:30:00,00:30:00,C,2,,,
+2.1,00:40:00,00:40:00,D,3,,,
+2.2,00:50:00,00:50:00,B,1,,,
+2.2,01:00:00,01:00:00,C,2,,,
+2.2,01:10:00,01:10:00,D,3,,,
+3.1,00:40:00,00:40:00,B,1,,,
+3.1,00:50:00,00:50:00,C,2,,,
+3.1,01:00:00,01:00:00,D,3,,,
+3.1,01:10:00,01:10:00,E,4,,,
+3.2,01:00:00,01:00:00,B,1,,,
+3.2,01:10:00,01:10:00,C,2,,,
+3.2,01:20:00,01:20:00,D,3,,,
+3.2,01:30:00,01:30:00,E,4,,,
+4.1,05:00:00,05:00:00,F,1,,,
+4.1,05:30:00,05:30:00,G,2,,,
+4.1,06:00:00,06:00:00,H,3,,,
+4.2,23:00:00,23:00:00,F,1,,,
+4.2,23:30:00,23:30:00,G,2,,,
+4.2,24:00:00,24:00:00,H,3,,,
+4.3,23:40:00,23:40:00,F,1,,,
+4.3,24:10:00,24:10:00,G,2,,,
+4.3,24:40:00,24:40:00,H,3,,,
+5.1,08:00:00,08:00:00,I,1,0,,
+5.1,08:10:00,08:10:00,J,2,22.5,,
+5.1,08:20:00,08:20:00,K,3,52.01,,
+6.1,12:00:00,12:00:00,I,1,,,
+6.1,12:10:00,12:10:00,J,2,,,
+6.2,13:00:00,13:00:00,I,1,,,
+6.2,13:10:00,13:10:00,J,2,,,
+7.1,12:20:00,12:20:00,J,1,,,
+7.1,12:30:00,12:30:00,K,2,,,
+7.2,13:20:00,13:20:00,J,1,,,
+7.2,13:30:00,13:30:00,K,2,,,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/stops.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/stops.txt
new file mode 100644
index 000000000..37f31d1a3
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/stops.txt
@@ -0,0 +1,14 @@
+stop_id,stop_name,stop_lat,stop_lon,wheelchair_boarding
+A,A,40,-73,1
+B,B,40,-74,1
+C,C,40,-75,0
+D,D,40,-76,1
+E,E,40,-77,1
+F,F,41,-75,
+G,G,42,-75,
+H,H,43,-75,
+I,I,41,-72,
+J,J,41,-73,
+K,K,41,-74,
+L,L,41.000001,-73.000001,
+M,M,41.000002,-73.000002,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/transfers.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/transfers.txt
new file mode 100644
index 000000000..f085afa3a
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/transfers.txt
@@ -0,0 +1,5 @@
+to_stop_id,from_stop_id,transfer_type,min_transfer_time
+K,L,0,
+L,K,0,
+M,K,3,
+K,M,3,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/trips.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/trips.txt
new file mode 100644
index 000000000..40f94cba4
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency1/trips.txt
@@ -0,0 +1,16 @@
+route_id,service_id,trip_id,shape_id,block_id,wheelchair_accessible
+1,alldays,1.1,,,1
+1,alldays,1.2,,,1
+1,alldays,1.3,,,1
+2,alldays,2.1,,,0
+2,alldays,2.2,,,0
+3,alldays,3.1,,,1
+3,alldays,3.2,,,1
+4,weekdays,4.1,4,,
+4,weekdays,4.2,4,,
+4,weekdays,4.3,4,,
+5,alldays,5.1,5,,
+6,alldays,6.1,,block.1,
+7,alldays,7.1,,block.1,
+6,alldays,6.2,,block.2,
+7,alldays,7.2,,block.2,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2.zip b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2.zip
new file mode 100644
index 000000000..209caa41b
Binary files /dev/null and b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2.zip differ
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/agency.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/agency.txt
new file mode 100644
index 000000000..9e71d386f
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/agency.txt
@@ -0,0 +1,2 @@
+agency_id,agency_name,agency_url,agency_timezone
+agency2,Fake Agency 2,http://fake.example.com,America/New_York
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/calendar.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/calendar.txt
new file mode 100644
index 000000000..6b351dffa
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/calendar.txt
@@ -0,0 +1,3 @@
+service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
+alldays,1,1,1,1,1,1,1,20090101,20500101
+weekdays,1,1,1,1,1,0,0,20090101,20500101
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/modifications.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/modifications.txt
new file mode 100644
index 000000000..821201d3a
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/modifications.txt
@@ -0,0 +1,3 @@
+This is long text that forms the basis
+of testagency2
+
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/routes.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/routes.txt
new file mode 100644
index 000000000..0a4dd22de
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/routes.txt
@@ -0,0 +1,8 @@
+route_id,route_short_name,route_long_name,route_type
+1,1,1,3
+2,2,2,3
+3,3,3,3
+4,4,4,4
+5,5,5,5
+6,6,6,6
+7,7,7,7
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/shapes.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/shapes.txt
new file mode 100644
index 000000000..5e0159c3f
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/shapes.txt
@@ -0,0 +1,10 @@
+shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
+4,41.0,-75.0,1,
+4,42.0,-75.0,2,
+4,42.5,-75.3,3,
+4,43.0,-75.0,4,
+5,41.0,-72.0,1,0
+5,41.5,-72.5,2,1.234
+5,41.0,-73.0,3,17.62
+5,41.5,-73.5,4,35.234
+5,41.0,-74.0,5,52.01
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/stop_times.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/stop_times.txt
new file mode 100644
index 000000000..228561b53
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/stop_times.txt
@@ -0,0 +1,44 @@
+trip_id,arrival_time,departure_time,stop_id,stop_sequence,shape_dist_traveled,pickup_type,drop_off_type
+1.1,00:00:00,00:00:00,A,1,,,
+1.1,00:10:00,00:10:00,B,2,,,
+1.1,00:20:00,00:20:00,C,3,,,
+1.2,00:20:00,00:20:00,A,1,0,,
+1.2,,,B,2,,3,2
+1.2,00:40:00,00:40:00,C,3,52.1,,
+1.3,08:00:00,08:00:00,A,1,,,
+1.3,08:10:00,08:20:00,B,2,,,
+1.3,08:30:00,08:30:00,C,3,,,
+2.1,00:20:00,00:20:00,B,1,,,
+2.1,00:30:00,00:30:00,C,2,,,
+2.1,00:40:00,00:40:00,D,3,,,
+2.2,00:50:00,00:50:00,B,1,,,
+2.2,01:00:00,01:00:00,C,2,,,
+2.2,01:10:00,01:10:00,D,3,,,
+3.1,00:40:00,00:40:00,B,1,,,
+3.1,00:50:00,00:50:00,C,2,,,
+3.1,01:00:00,01:00:00,D,3,,,
+3.1,01:10:00,01:10:00,E,4,,,
+3.2,01:00:00,01:00:00,B,1,,,
+3.2,01:10:00,01:10:00,C,2,,,
+3.2,01:20:00,01:20:00,D,3,,,
+3.2,01:30:00,01:30:00,E,4,,,
+4.1,05:00:00,05:00:00,F,1,,,
+4.1,05:30:00,05:30:00,G,2,,,
+4.1,06:00:00,06:00:00,H,3,,,
+4.2,23:00:00,23:00:00,F,1,,,
+4.2,23:30:00,23:30:00,G,2,,,
+4.2,24:00:00,24:00:00,H,3,,,
+4.3,23:40:00,23:40:00,F,1,,,
+4.3,24:10:00,24:10:00,G,2,,,
+4.3,24:40:00,24:40:00,H,3,,,
+5.1,08:00:00,08:00:00,I,1,0,,
+5.1,08:10:00,08:10:00,J,2,22.5,,
+5.1,08:20:00,08:20:00,K,3,52.01,,
+6.1,12:00:00,12:00:00,I,1,,,
+6.1,12:10:00,12:10:00,J,2,,,
+6.2,13:00:00,13:00:00,I,1,,,
+6.2,13:10:00,13:10:00,J,2,,,
+7.1,12:20:00,12:20:00,J,1,,,
+7.1,12:30:00,12:30:00,K,2,,,
+7.2,13:20:00,13:20:00,J,1,,,
+7.2,13:30:00,13:30:00,K,2,,,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/stops.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/stops.txt
new file mode 100644
index 000000000..37f31d1a3
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/stops.txt
@@ -0,0 +1,14 @@
+stop_id,stop_name,stop_lat,stop_lon,wheelchair_boarding
+A,A,40,-73,1
+B,B,40,-74,1
+C,C,40,-75,0
+D,D,40,-76,1
+E,E,40,-77,1
+F,F,41,-75,
+G,G,42,-75,
+H,H,43,-75,
+I,I,41,-72,
+J,J,41,-73,
+K,K,41,-74,
+L,L,41.000001,-73.000001,
+M,M,41.000002,-73.000002,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/transfers.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/transfers.txt
new file mode 100644
index 000000000..f085afa3a
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/transfers.txt
@@ -0,0 +1,5 @@
+to_stop_id,from_stop_id,transfer_type,min_transfer_time
+K,L,0,
+L,K,0,
+M,K,3,
+K,M,3,
diff --git a/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/trips.txt b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/trips.txt
new file mode 100644
index 000000000..40f94cba4
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/org/onebusaway/gtfs_merge/testagency2/trips.txt
@@ -0,0 +1,16 @@
+route_id,service_id,trip_id,shape_id,block_id,wheelchair_accessible
+1,alldays,1.1,,,1
+1,alldays,1.2,,,1
+1,alldays,1.3,,,1
+2,alldays,2.1,,,0
+2,alldays,2.2,,,0
+3,alldays,3.1,,,1
+3,alldays,3.2,,,1
+4,weekdays,4.1,4,,
+4,weekdays,4.2,4,,
+4,weekdays,4.3,4,,
+5,alldays,5.1,5,,
+6,alldays,6.1,,block.1,
+7,alldays,7.1,,block.1,
+6,alldays,6.2,,block.2,
+7,alldays,7.2,,block.2,
diff --git a/onebusaway-gtfs-merge/src/test/resources/simplelogger.properties b/onebusaway-gtfs-merge/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..712780a1e
--- /dev/null
+++ b/onebusaway-gtfs-merge/src/test/resources/simplelogger.properties
@@ -0,0 +1,25 @@
+# SLF4J's SimpleLogger configuration file
+# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
+# Default logging detail level for all instances of SimpleLogger.
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, defaults to "info".
+org.slf4j.simpleLogger.defaultLogLevel=info
+# Logging detail level for a SimpleLogger instance named "xxxxx".
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, the default logging detail level is used.
+#org.slf4j.simpleLogger.log.xxxxx=
+# Set to true if you want the current date and time to be included in output messages.
+# Default is false, and will output the number of milliseconds elapsed since startup.
+org.slf4j.simpleLogger.showDateTime=true
+# The date and time format to be used in the output messages.
+# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
+# If the format is not specified or is invalid, the default format is used.
+# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
+#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
+org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
+# Set to true if you want to output the current thread name.
+# Defaults to true.
+org.slf4j.simpleLogger.showThreadName=false
+# Set to true if you want the last component of the name to be included in output messages.
+# Defaults to false.
+org.slf4j.simpleLogger.showShortLogName=true
diff --git a/onebusaway-gtfs-transformer-cli-aws/pom.xml b/onebusaway-gtfs-transformer-cli-aws/pom.xml
index 9db397554..ff4db43f6 100644
--- a/onebusaway-gtfs-transformer-cli-aws/pom.xml
+++ b/onebusaway-gtfs-transformer-cli-aws/pom.xml
@@ -9,7 +9,7 @@
org.onebusaway
onebusaway-gtfs-modules
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
@@ -27,7 +27,7 @@
org.onebusaway
onebusaway-cloud-aws
- 0.0.10
+ ${onebusaway_cloud_version}
commons-cli
@@ -36,7 +36,8 @@
org.slf4j
- slf4j-log4j12
+ slf4j-simple
+ ${slf4j_version}
diff --git a/onebusaway-gtfs-transformer-cli/pom.xml b/onebusaway-gtfs-transformer-cli/pom.xml
index 81548cad9..e482d49f9 100644
--- a/onebusaway-gtfs-transformer-cli/pom.xml
+++ b/onebusaway-gtfs-transformer-cli/pom.xml
@@ -9,7 +9,7 @@
org.onebusaway
onebusaway-gtfs-modules
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
@@ -31,7 +31,8 @@
org.slf4j
- slf4j-log4j12
+ slf4j-simple
+ ${slf4j_version}
diff --git a/onebusaway-gtfs-transformer-cli/src/main/resources/log4j.properties b/onebusaway-gtfs-transformer-cli/src/main/resources/log4j.properties
deleted file mode 100644
index 2c9757ccc..000000000
--- a/onebusaway-gtfs-transformer-cli/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-log4j.rootLogger = INFO, stdout
-
-log4j.appender.stdout = org.apache.log4j.ConsoleAppender
-log4j.appender.stdout.Threshold = DEBUG
-log4j.appender.stdout.Target = System.out
-log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
-log4j.appender.stdout.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
-
-
diff --git a/onebusaway-gtfs-transformer-cli/src/main/resources/simplelogger.properties b/onebusaway-gtfs-transformer-cli/src/main/resources/simplelogger.properties
new file mode 100644
index 000000000..712780a1e
--- /dev/null
+++ b/onebusaway-gtfs-transformer-cli/src/main/resources/simplelogger.properties
@@ -0,0 +1,25 @@
+# SLF4J's SimpleLogger configuration file
+# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
+# Default logging detail level for all instances of SimpleLogger.
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, defaults to "info".
+org.slf4j.simpleLogger.defaultLogLevel=info
+# Logging detail level for a SimpleLogger instance named "xxxxx".
+# Must be one of ("trace", "debug", "info", "warn", or "error").
+# If not specified, the default logging detail level is used.
+#org.slf4j.simpleLogger.log.xxxxx=
+# Set to true if you want the current date and time to be included in output messages.
+# Default is false, and will output the number of milliseconds elapsed since startup.
+org.slf4j.simpleLogger.showDateTime=true
+# The date and time format to be used in the output messages.
+# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
+# If the format is not specified or is invalid, the default format is used.
+# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
+#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
+org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
+# Set to true if you want to output the current thread name.
+# Defaults to true.
+org.slf4j.simpleLogger.showThreadName=false
+# Set to true if you want the last component of the name to be included in output messages.
+# Defaults to false.
+org.slf4j.simpleLogger.showShortLogName=true
diff --git a/onebusaway-gtfs-transformer/pom.xml b/onebusaway-gtfs-transformer/pom.xml
index 7567d9712..0e7b7a0b3 100644
--- a/onebusaway-gtfs-transformer/pom.xml
+++ b/onebusaway-gtfs-transformer/pom.xml
@@ -9,14 +9,14 @@
org.onebusaway
onebusaway-gtfs-modules
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
org.onebusaway
onebusaway-gtfs
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
org.onebusaway
@@ -32,11 +32,11 @@
junit
test
-
- org.slf4j
- slf4j-log4j12
- test
-
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j_version}
+
org.mockito
mockito-core
@@ -48,10 +48,15 @@
wsf-api
1.0
+
+ org.onebusaway
+ onebusaway-cloud-api
+ ${onebusaway_cloud_version}
+
org.onebusaway
onebusaway-cloud-noop
- 0.0.10
+ ${onebusaway_cloud_version}
javax.xml.bind
@@ -84,8 +89,8 @@
org.apache.maven.plugins
maven-compiler-plugin
-
- 1.8
+
+ 11
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/csv/MTAStation.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/csv/MTAStation.java
new file mode 100644
index 000000000..ad97e4254
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/csv/MTAStation.java
@@ -0,0 +1,260 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.csv;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+
+/**
+ * Metadata about an MTA Station.
+ *
+ * See https://new.mta.info/developers/display-elevators-NYCT
+ */
+public class MTAStation {
+
+ public static final int ADA_NOT_ACCESSIBLE = 0;
+ public static final int ADA_FULLY_ACCESSIBLE = 1;
+ public static final int ADA_PARTIALLY_ACCESSIBLE = 2;
+
+ public static final int GTFS_WHEELCHAIR_UNKNOWN = 0;
+ public static final int GTFS_WHEELCHAIR_ACCESSIBLE = 1;
+ public static final int GTFS_WHEELCHAIR_NOT_ACCESSIBLE = 2;
+ public static final int GTFS_WHEELCHAIR_EXPERIMENTAL_PARTIALLY_ACCESSIBLE = 3;
+
+ private static final int MISSING_VALUE = -999;
+ @CsvField(name = "Station ID")
+ private int id;
+
+ @CsvField(name = "Complex ID")
+ private int complexId;
+
+ @CsvField(name = "GTFS Stop ID")
+ private String stopId;
+
+ @CsvField(name = "Division")
+ private String division;
+
+ @CsvField(name = "Line")
+ private String line;
+
+ @CsvField(name = "Stop Name")
+ private String stopName;
+
+ @CsvField(name = "Borough")
+ private String borough;
+
+ @CsvField(name = "Daytime Routes")
+ private String daytimeRoutes;
+
+ @CsvField(name = "Structure")
+ private String structure;
+
+ @CsvField(name = "GTFS Latitude")
+ private double lat;
+
+ @CsvField(name = "GTFS Longitude")
+ private double lon;
+
+ @CsvField(name = "North Direction Label", optional = true)
+ private String northDirection;
+
+ @CsvField(name = "South Direction Label", optional = true)
+ private String southDirection;
+
+ /**
+ * Look at the ADA column.
+ *
+ * 0 means it’s not accessible,
+ * 1 means it is fully accessible, and
+ * 2 means it is partially accessible. Partially accessible stations are usually accessible in one direction.
+ */
+ @CsvField(name = "ADA")
+ private int ada;
+
+ @CsvField(name = "ADA Direction Notes", optional = true)
+ private String adaDirectionNotes;
+
+ /**
+ * If ADA_PARTIALLY_ACCESSIBLE and this is 1, this
+ * station is accessible
+ */
+ @CsvField(name = "ADA NB", optional = true)
+ private int adaNorthBound = MISSING_VALUE;
+
+ /**
+ * If ADA_PARTIALLY_ACCESSIBLE and this is 1, this
+ * station is accessible
+ */
+ @CsvField(name = "ADA SB", optional = true)
+ private int adaSouthBound = MISSING_VALUE;
+
+ @CsvField(name = "Capital Outage NB", optional = true)
+ private String capitalOutageNB;
+
+ @CsvField(name = "Capital Outage SB", optional = true)
+ private String capitalOutageSB;
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public int getComplexId() {
+ return complexId;
+ }
+
+ public void setComplexId(int complexId) {
+ this.complexId = complexId;
+ }
+
+ public String getStopId() {
+ return stopId;
+ }
+
+ public void setStopId(String stopId) {
+ this.stopId = stopId;
+ }
+
+ public String getDivision() {
+ return division;
+ }
+
+ public void setDivision(String division) {
+ this.division = division;
+ }
+
+ public String getLine() {
+ return line;
+ }
+
+ public void setLine(String line) {
+ this.line = line;
+ }
+
+ public String getStopName() {
+ return stopName;
+ }
+
+ public void setStopName(String stopName) {
+ this.stopName = stopName;
+ }
+
+ public String getBorough() {
+ return borough;
+ }
+
+ public void setBorough(String borough) {
+ this.borough = borough;
+ }
+
+ public String getDaytimeRoutes() {
+ return daytimeRoutes;
+ }
+
+ public void setDaytimeRoutes(String daytimeRoutes) {
+ this.daytimeRoutes = daytimeRoutes;
+ }
+
+ public String getStructure() {
+ return structure;
+ }
+
+ public void setStructure(String structure) {
+ this.structure = structure;
+ }
+
+ public double getLat() {
+ return lat;
+ }
+
+ public void setLat(double lat) {
+ this.lat = lat;
+ }
+
+ public double getLon() {
+ return lon;
+ }
+
+ public void setLon(double lon) {
+ this.lon = lon;
+ }
+
+ public String getNorthDirection() {
+ return northDirection;
+ }
+
+ public void setNorthDirection(String northDirection) {
+ this.northDirection = northDirection;
+ }
+
+ public String getSouthDirection() {
+ return southDirection;
+ }
+
+ public void setSouthDirection(String southDirection) {
+ this.southDirection = southDirection;
+ }
+
+ public int getAda() {
+ return ada;
+ }
+
+ public void setAda(int ada) {
+ this.ada = ada;
+ }
+
+ public String getAdaDirectionNotes() {
+ return adaDirectionNotes;
+ }
+
+ public void setAdaDirectionNotes(String adaDirectionNotes) {
+ this.adaDirectionNotes = adaDirectionNotes;
+ }
+
+ public int getAdaNorthBound() {
+ return adaNorthBound;
+ }
+
+ public void setAdaNorthBound(int adaNorthBound) {
+ this.adaNorthBound = adaNorthBound;
+ }
+
+ public int getAdaSouthBound() {
+ return adaSouthBound;
+ }
+
+ public void setAdaSouthBound(int adaSouthBound) {
+ this.adaSouthBound = adaSouthBound;
+ }
+
+ public String getCapitalOutageNB() {
+ return capitalOutageNB;
+ }
+
+ public void setCapitalOutageNB(String capitalOutageNB) {
+ this.capitalOutageNB = capitalOutageNB;
+ }
+
+ public String getCapitalOutageSB() {
+ return capitalOutageSB;
+ }
+
+ public void setCapitalOutageSB(String capitalOutageSB) {
+ this.capitalOutageSB = capitalOutageSB;
+ }
+}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFile.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFile.java
new file mode 100644
index 000000000..c4b387d00
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFile.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.impl;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
+import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy;
+import org.onebusaway.gtfs_transformer.services.TransformContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * Insert a GTFS extension into a GTFS file via a transformation.
+ * See the unit test for example usage.
+ */
+
+public class AddExtensionFile implements GtfsTransformStrategy {
+ private static Logger _log = LoggerFactory.getLogger(AddExtensionFile.class);
+
+
+ @CsvField(optional = false)
+ private String extensionFilename;
+ @CsvField(optional = false)
+ private String extensionName;
+ @Override
+ public String getName() {
+ return this.getClass().getName();
+ }
+
+ public void setExtensionFilename(String extensionFilename) {
+ this.extensionFilename = extensionFilename;
+ }
+ public void setExtensionName(String extensionName) {
+ this.extensionName = extensionName;
+ }
+
+ @Override
+ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
+ // lookup the file
+ if (extensionFilename == null)
+ throw new IllegalStateException("missing required param extensionFilename");
+ if (extensionName == null)
+ throw new IllegalStateException("missing required param extensionName");
+ _log.info("AddExtensionFile entered with {} to {}", extensionName, extensionFilename);
+ File extension = new File(extensionFilename);
+ if (!extension.exists()) {
+ throw new IllegalStateException("attempt to add non-existant extension file:" + extension.getName());
+ }
+ String content = null;
+ try {
+ content = Files.readString(extension.toPath());
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ if (content == null)
+ throw new IllegalStateException("no content for specified file " + extensionFilename);
+
+ _log.info("AddExtensionFile copying {} to {}", extensionName, extensionFilename);
+ dao.addMetadata(extensionName, content);
+ }
+}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CheckForFutureService.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CheckForFutureService.java
index 5e734168f..23c8883e9 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CheckForFutureService.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CheckForFutureService.java
@@ -17,16 +17,17 @@
import org.onebusaway.cloud.api.ExternalServices;
import org.onebusaway.cloud.api.ExternalServicesBridgeFactory;
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
import org.onebusaway.gtfs.model.*;
import org.onebusaway.gtfs.model.calendar.ServiceDate;
import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
import org.onebusaway.gtfs_transformer.services.CloudContextService;
import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy;
import org.onebusaway.gtfs_transformer.services.TransformContext;
+import org.onebusaway.gtfs_transformer.util.CalendarFunctions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.Calendar;
import java.util.Date;
/* Checks the numbers of Trips with service today and next four days
@@ -37,6 +38,9 @@ public class CheckForFutureService implements GtfsTransformStrategy {
private final Logger _log = LoggerFactory.getLogger(CheckForFutureService.class);
+ @CsvField(ignore = true)
+ private CalendarFunctions helper = new CalendarFunctions();
+
@Override
public String getName() {
return this.getClass().getSimpleName();
@@ -49,20 +53,20 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
int tripsTomorrow = 0;
int tripsNextDay = 0;
int tripsDayAfterNext = 0;
- Date today = removeTime(new Date());
- Date tomorrow = removeTime(addDays(new Date(), 1));
- Date nextDay = removeTime(addDays(new Date(), 2));
- Date dayAfterNext = removeTime(addDays(new Date(), 3));
+ Date today = helper.removeTime(new Date());
+ Date tomorrow = helper.removeTime(helper.addDays(new Date(), 1));
+ Date nextDay = helper.removeTime(helper.addDays(new Date(), 2));
+ Date dayAfterNext = helper.removeTime(helper.addDays(new Date(), 3));
String feed = CloudContextService.getLikelyFeedName(dao);
- ExternalServices es = new ExternalServicesBridgeFactory().getExternalServices();
+ ExternalServices es = new ExternalServicesBridgeFactory().getExternalServices();
String agency = dao.getAllAgencies().iterator().next().getId();
String agencyName = dao.getAllAgencies().iterator().next().getName();
tripsToday = hasServiceForDate(dao, today);
tripsTomorrow = hasServiceForDate(dao, tomorrow);
tripsNextDay = hasServiceForDate(dao, nextDay);
- tripsDayAfterNext = hasServiceForDate(dao,dayAfterNext);
+ tripsDayAfterNext = hasServiceForDate(dao, dayAfterNext);
_log.info("Feed for metrics: {}, agency id: {}", feed, agencyName);
es.publishMetric(CloudContextService.getNamespace(), "TripsToday", "feed", feed, tripsToday);
@@ -91,72 +95,13 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
}
int hasServiceForDate(GtfsMutableRelationalDao dao, Date testDate) {
+ ServiceDate serviceDate = new ServiceDate(testDate);
int numTripsOnDate = 0;
for (Trip trip : dao.getAllTrips()) {
- //check for service
- boolean hasCalDateException = false;
- //are there calendar dates?
- if (!dao.getCalendarDatesForServiceId(trip.getServiceId()).isEmpty()) {
- //calendar dates are not empty
- for (ServiceCalendarDate calDate : dao.getCalendarDatesForServiceId(trip.getServiceId())) {
- Date date = constructDate(calDate.getDate());
- if (date.equals(testDate)) {
- hasCalDateException = true;
- if (calDate.getExceptionType() == 1) {
- //there is service for date
- numTripsOnDate++;
- break;
- }
- if (calDate.getExceptionType() == 2) {
- //service has been excluded for date
- break;
- }
- }
- }
- }
-
- //if there are no entries in calendarDates, check serviceCalendar
- if (!hasCalDateException) {
- ServiceCalendar servCal = dao.getCalendarForServiceId(trip.getServiceId());
- if (servCal != null) {
- //check for service using calendar
- Date start = removeTime(servCal.getStartDate().getAsDate());
- Date end = removeTime(servCal.getEndDate().getAsDate());
- if (testDate.equals(start) || testDate.equals(end) ||
- (testDate.after(start) && testDate.before(end))) {
- numTripsOnDate++;
- }
- }
- }
+ if (helper.isTripActive(dao, serviceDate, trip, false))
+ numTripsOnDate++;
}
return numTripsOnDate;
}
- private Date addDays(Date date, int daysToAdd) {
- Calendar cal = Calendar.getInstance();
- cal.setTime(date);
- cal.add(Calendar.DATE, daysToAdd);
- return cal.getTime();
- }
-
- private Date constructDate(ServiceDate date) {
- Calendar calendar = Calendar.getInstance();
- calendar.set(Calendar.YEAR, date.getYear());
- calendar.set(Calendar.MONTH, date.getMonth()-1);
- calendar.set(Calendar.DATE, date.getDay());
- Date date1 = calendar.getTime();
- date1 = removeTime(date1);
- return date1;
- }
-
- private Date removeTime(Date date) {
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(date);
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
- date = calendar.getTime();
- return date;
- }
}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CompareToReferenceService.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CompareToReferenceService.java
new file mode 100644
index 000000000..3fa11c8d5
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/CompareToReferenceService.java
@@ -0,0 +1,198 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.impl;
+
+import org.onebusaway.cloud.api.ExternalServices;
+import org.onebusaway.cloud.api.ExternalServicesBridgeFactory;
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.gtfs.model.*;
+import org.onebusaway.gtfs.model.calendar.ServiceDate;
+import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
+import org.onebusaway.gtfs_transformer.services.CloudContextService;
+import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy;
+import org.onebusaway.gtfs_transformer.services.TransformContext;
+
+import org.onebusaway.gtfs_transformer.util.CalendarFunctions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.*;
+
+/**
+ * Compare GTFS trips and report on gaps at a depot level.
+ * The concept of a depot is inferred from the trip id and is
+ * currently specific to the MTA.
+ */
+public class CompareToReferenceService implements GtfsTransformStrategy {
+
+ private static final int MAX_MESSAGE_SIZE = 250 * 1024;
+ private static final Logger _log = LoggerFactory.getLogger(CompareToReferenceService.class);
+ @Override
+ public String getName() {
+ return this.getClass().getSimpleName();
+ }
+
+ @CsvField(ignore = true)
+ private String defaultAgencyId = "1";
+
+ private String s3BasePath = null;
+ public void setS3BasePath(String path) {
+ s3BasePath = path;
+ }
+
+ @CsvField(ignore = true)
+ private CalendarFunctions helper = new CalendarFunctions();
+ @Override
+ public void run(TransformContext context, GtfsMutableRelationalDao gtfsDao) {
+ try {
+
+ defaultAgencyId = CloudContextService.getLikelyFeedName(gtfsDao);
+ String summaryTopic = CloudContextService.getTopic() + "-atis-summary";
+ String detailTopic = CloudContextService.getTopic() + "-atis-detail";
+ ExternalServices es = new ExternalServicesBridgeFactory().getExternalServices();
+
+ String summaryHeader = "depot,unmatched_gtfs_trips,unmatched_reference_trips\n";
+ String detailHeader = "depot,unmatched_gtfs_trip_ds,unmatched_reference_trip_ids\n";
+
+ StringBuffer summaryReport = new StringBuffer();
+ StringBuffer detailReport = new StringBuffer();
+
+ GtfsMutableRelationalDao referenceDao = (GtfsMutableRelationalDao) context.getReferenceReader().getEntityStore();
+
+ // get active trip ids for service date from base GTFS
+ Map> activeTripsByDepot = getTripsByDepot(context, gtfsDao);
+ // get active trip ids for service date for reference GTFS
+ Map> referenceTripsByDepot = getTripsByDepot(context, referenceDao);
+ String summaryFilename = System.getProperty("java.io.tmpdir") + File.separator + "summary.csv";
+ FileWriter summaryFile = new FileWriter(summaryFilename);
+ summaryFile.write(summaryHeader);
+ String detailFilename = System.getProperty("java.io.tmpdir") + File.separator + "detail.csv";
+ FileWriter detailFile = new FileWriter(detailFilename);
+ detailFile.write(detailHeader);
+
+ Set allDepots = new HashSet<>();
+ allDepots.addAll(activeTripsByDepot.keySet());
+ allDepots.addAll(referenceTripsByDepot.keySet());
+
+ for (String depot : allDepots) {
+ HashSet gtfsTripIds = new HashSet<>();
+ HashSet referenceTripIds = new HashSet<>();
+ if (activeTripsByDepot.containsKey(depot)) {
+ gtfsTripIds.addAll(activeTripsByDepot.get(depot));
+ }
+ if (referenceTripsByDepot.containsKey(depot)) {
+ referenceTripIds.addAll(referenceTripsByDepot.get(depot));
+ }
+
+ // now do some set operations to determine the differences
+ HashSet unmatchedReferenceTrips = new HashSet<>(referenceTripIds);
+ HashSet unmatchedGtfsTrips = new HashSet<>(gtfsTripIds);
+
+ unmatchedGtfsTrips.removeAll(referenceTripIds);
+
+ unmatchedReferenceTrips.removeAll(gtfsTripIds);
+ summaryReport.append(depot).append(",").append(unmatchedGtfsTrips.size()).append(",").append(unmatchedReferenceTrips.size()).append("\n");
+ detailReport.append(depot).append(",\"").append(unmatchedGtfsTrips).append("\",\"").append(unmatchedReferenceTrips).append("\"\n");
+// es.publishMessage(detailTopic, truncate(detailReport.toString()));
+ detailFile.write(detailReport.toString());
+ detailReport = new StringBuffer();
+ }
+// es.publishMessage(summaryTopic, summaryReport.toString());
+ summaryFile.write(summaryReport.toString());
+
+ summaryFile.close();
+ detailFile.close();
+
+ // now copy the reports to an S3 reports directory
+ Calendar cal = Calendar.getInstance();
+ String year = "" + cal.get(Calendar.YEAR);
+ String month = lpad(cal.get(Calendar.MONTH)+1, 2);
+ String day = lpad(cal.get(Calendar.DAY_OF_MONTH), 2);
+ String time = lpad(cal.get(Calendar.HOUR_OF_DAY), 2) + ":" + lpad(cal.get(Calendar.MINUTE),2 );
+ String baseurl = s3BasePath
+ + "/" + year + "/" + month + "/" + day + "/" + time + "-";
+ es.putFile(baseurl + "summary.csv", summaryFilename);
+ es.putFile(baseurl + "detail.csv", detailFilename);
+
+
+ _log.error("{} Unmatched Summary", getName());
+ _log.error(summaryReport.toString());
+ } catch (Throwable t) {
+ _log.error("{} failed: {}", getName(), t, t);
+ }
+
+ }
+
+ private String lpad(int numberToFormat, int totalDigits) {
+ String text = String.valueOf(numberToFormat);
+ while (text.length() < totalDigits)
+ text = "0" + text;
+ return text;
+ }
+
+ private String truncate(String message) {
+ if (message.length() > MAX_MESSAGE_SIZE)
+ return message.substring(0, MAX_MESSAGE_SIZE);
+ return message;
+ }
+
+ private String getDepot(String tripId) {
+ String depot = null;
+ if (tripId.indexOf("-") < 0) {
+ depot = "MISSING";
+ } else {
+ depot = tripId.split("-")[1];
+ if ("Weekday".equals(depot) || "Saturday".equals(depot) || "Sunday".equals(depot))
+ depot = tripId.split("-")[0]; // it moves around!
+ }
+ if (depot.length() <= 1) {
+ depot = "MISSING";
+ }
+ return depot;
+ }
+
+
+ private Map> getTripsByDepot(TransformContext context, GtfsMutableRelationalDao dao) {
+ Map> tripsByDepot = new HashMap<>();
+ for (Trip trip : dao.getAllTrips()) {
+ if (trip.getServiceId() != null) {
+ boolean isActive = helper.isTripActive(dao, new ServiceDate(), trip, true);
+ if (isActive) {
+ AgencyAndId tripId = trip.getId();
+ if (trip.getMtaTripId() != null) {
+ tripId = new AgencyAndId(trip.getId().getAgencyId(), trip.getMtaTripId());
+ }
+ String depot = getDepot(tripId.getId());
+ if (!tripsByDepot.containsKey(depot))
+ tripsByDepot.put(depot, new ArrayList<>());
+ tripsByDepot.get(depot).add(sanitize(tripId));
+ }
+ }
+ }
+ return tripsByDepot;
+ }
+
+
+
+ private AgencyAndId sanitize(AgencyAndId tripId) {
+ if (tripId.getId().contains("-SDon")) {
+ return new AgencyAndId(defaultAgencyId, tripId.getId().replaceAll("-SDon", ""));
+ }
+ return new AgencyAndId(defaultAgencyId, tripId.getId());
+ }
+}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/FeedInfoFromAgencyStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/FeedInfoFromAgencyStrategy.java
index 754c09dfb..c45516638 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/FeedInfoFromAgencyStrategy.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/FeedInfoFromAgencyStrategy.java
@@ -31,6 +31,7 @@
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
+import java.util.stream.Collectors;
public class FeedInfoFromAgencyStrategy implements GtfsTransformStrategy {
@@ -77,9 +78,13 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
}
private FeedInfo getFeedInfoFromAgency(GtfsMutableRelationalDao dao, Agency agency) {
- FeedInfo info = dao.getFeedInfoForId(agencyId);
- if (info == null) {
- info = new FeedInfo();
+ // cannot just use dao.getFeedInfoFromAgencyForId if it needs to be compatable with "update" SimpleModificationStrategy
+ FeedInfo info = dao.getAllFeedInfos().stream().
+ filter(feed->feed.getId().equals(agencyId))
+ .collect(Collectors.toMap(feed->feed.getId(), feed -> feed))
+ .get(agency.getId());
+ if (info==null) {
+ info = new FeedInfo();
}
info.setId(agencyId);
info.setPublisherName(agency.getName());
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAEntrancesStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAEntrancesStrategy.java
index f7f2781b0..e2eebf49d 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAEntrancesStrategy.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAEntrancesStrategy.java
@@ -68,8 +68,8 @@ public class MTAEntrancesStrategy implements GtfsTransformStrategy {
private static final int LOCATION_TYPE_PAYGATE = 3;
private static final int LOCATION_TYPE_GENERIC = 4;
- private static final int WHEELCHAIR_ACCESSIBLE = 1;
- private static final int NOT_WHEELCHAIR_ACCESSIBLE = 2;
+ static final int WHEELCHAIR_ACCESSIBLE = 1;
+ static final int NOT_WHEELCHAIR_ACCESSIBLE = 2;
private static final String DEFAULT_MEZZ = "default";
@@ -82,7 +82,10 @@ public class MTAEntrancesStrategy implements GtfsTransformStrategy {
@CsvField(ignore = true)
private Set stopIdsWithPathways = new HashSet();
-
+
+ @CsvField(ignore = true)
+ private Map complexStopIds = new HashMap<>();
+
@CsvField(ignore = true)
private String agencyId;
@@ -110,6 +113,10 @@ public class MTAEntrancesStrategy implements GtfsTransformStrategy {
private boolean contextualAccessibility;
+ @CsvField(optional = true)
+ private boolean markStopsAccessible = false;
+
+
@CsvField(optional = true)
private boolean skipStopsWithExistingPathways = true;
@@ -160,6 +167,7 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
}
}
+ _log.info("elevatorCsv={}, entrancesCsv={}, accessibleComplexFile={}", elevatorsCsv, entrancesCsv, accessibleComplexFile);
agencyId = dao.getAllAgencies().iterator().next().getId();
newStops = new HashSet<>();
@@ -226,6 +234,17 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
if (elevatorsCsv != null) {
readElevatorData(stopGroups, getComplexList(dao));
}
+
+ _log.info("found {} complex stops to mark as accessible and mark={}",
+ complexStopIds.size(), markStopsAccessible);
+ if (markStopsAccessible) {
+ for (String idOnly : complexStopIds.keySet()) {
+ Stop stop = complexStopIds.get(idOnly);
+ stop.setWheelchairBoarding(WHEELCHAIR_ACCESSIBLE);
+ _log.info("marking stop {} as accessible", stop.getId());
+ dao.updateEntity(stop);
+ }
+ }
for (Stop s : newStops) {
dao.saveEntity(s);
@@ -572,6 +591,7 @@ private Map> getComplexList(GtfsDao dao) {
if (stop == null)
_log.info("null stop: {}", id);
complex.add(stop);
+ this.complexStopIds.put(id, stop);
}
complexes.put("complex-" + UUID.randomUUID(), complex);
}
@@ -584,7 +604,7 @@ private Map> getComplexList(GtfsDao dao) {
private Map getStopMap(GtfsDao dao) {
Map map = new HashMap<>();
for (Stop stop : dao.getAllStops()) {
- if (stop.getLocationType() == 0) {
+ if (stop.getLocationType() == LOCATION_TYPE_STOP) {
map.put(stop.getId().getId(), stop);
}
}
@@ -743,5 +763,9 @@ public void setContextualAccessibility(boolean contextualAccessibility) {
private String getNamespace(){
return System.getProperty("cloudwatch.namespace");
}
+
+ public void setMarkStopsAccessible(boolean flag) {
+ markStopsAccessible = flag;
+ }
}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAStationAccessibilityStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAStationAccessibilityStrategy.java
new file mode 100644
index 000000000..5b813ec2a
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/MTAStationAccessibilityStrategy.java
@@ -0,0 +1,156 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.impl;
+
+import org.onebusaway.cloud.api.ExternalServices;
+import org.onebusaway.cloud.api.ExternalServicesBridgeFactory;
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.gtfs.model.FeedInfo;
+import org.onebusaway.gtfs.model.Stop;
+import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
+import org.onebusaway.gtfs_transformer.csv.MTAStation;
+import org.onebusaway.gtfs_transformer.services.GtfsTransformStrategy;
+import org.onebusaway.gtfs_transformer.services.TransformContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+import static org.onebusaway.gtfs_transformer.csv.CSVUtil.readCsv;
+import static org.onebusaway.gtfs_transformer.csv.MTAStation.*;
+
+/**
+ * Based on a CSV of MTAStations set the associated stops accessible as specified.
+ */
+public class MTAStationAccessibilityStrategy implements GtfsTransformStrategy {
+
+ private static final Logger _log = LoggerFactory.getLogger(MTAStationAccessibilityStrategy.class);
+ private String stationsCsv;
+
+ @CsvField(ignore = true)
+ private Set accessibleStops = new HashSet<>();
+
+ @CsvField(ignore = true)
+ private Map idToStopMap = new HashMap<>();
+
+ @Override
+ public String getName() {
+ return this.getClass().getName();
+ }
+
+ @Override
+ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
+
+ ExternalServices es = new ExternalServicesBridgeFactory().getExternalServices();
+ Collection feedInfos = dao.getAllFeedInfos();
+
+ // name the feed for logging/reference
+ String feed = null;
+ if(feedInfos.size() > 0)
+ feed = feedInfos.iterator().next().getPublisherName();
+
+ // stops are unqualified, build up a map of them for lookups
+ for (Stop stop : dao.getAllStops()) {
+ idToStopMap.put(stop.getId().getId(), stop);
+ }
+
+ File stationsFile = new File(stationsCsv);
+ if (!stationsFile.exists()) {
+ es.publishMultiDimensionalMetric(getNamespace(), "MissingControlFiles",
+ new String[]{"feed", "controlFileName"},
+ new String[]{feed, stationsCsv}, 1);
+ throw new IllegalStateException(
+ "Entrances file does not exist: " + stationsFile.getName());
+ }
+
+ // see MTAStationAccessibilityStrategyTest for discussion of how this works
+ List stations = getStations();
+ for (MTAStation station : stations) {
+ markStopAccessible(dao, station.getStopId(), "", station.getAda());
+ if (ADA_NOT_ACCESSIBLE == station.getAda()
+ || ADA_FULLY_ACCESSIBLE == station.getAda()) {
+ markStopAccessible(dao, station.getStopId(), "N", station.getAda());
+ markStopAccessible(dao, station.getStopId(), "S", station.getAda());
+ } else if (ADA_PARTIALLY_ACCESSIBLE == station.getAda()) {
+ if (station.getAdaNorthBound() < 0) {
+ markStopAccessible(dao, station.getStopId(), "N", ADA_NOT_ACCESSIBLE);
+ } else {
+ markStopAccessible(dao, station.getStopId(), "N", station.getAdaNorthBound());
+ }
+ if (station.getAdaSouthBound() < 0) {
+ markStopAccessible(dao, station.getStopId(), "S", ADA_NOT_ACCESSIBLE);
+ } else {
+ markStopAccessible(dao, station.getStopId(), "S", station.getAdaSouthBound());
+ }
+ }
+ }
+
+ _log.info("marking {} stops as accessible", accessibleStops.size());
+ for (Stop accessibleStop : this.accessibleStops) {
+ // save the changes
+ dao.updateEntity(accessibleStop);
+ }
+
+ }
+
+ private void markStopAccessible(GtfsMutableRelationalDao dao, String stopId, String compassDirection,
+ int accessibilityQualifier) {
+ int gtfsValue = convertMTAccessibilityToGTFS(accessibilityQualifier);
+ String unqualifiedStopId = stopId + compassDirection;
+ Stop stopForId = idToStopMap.get(unqualifiedStopId);
+ if (stopForId == null) {
+ _log.error("no such stop for stopId {}", unqualifiedStopId);
+ return;
+ }
+ stopForId.setWheelchairBoarding(gtfsValue);
+ this.accessibleStops.add(stopForId);
+ }
+
+ /**
+ * MTA 0 -> GTFS 2
+ * MTA 1 -> GTFS 1
+ * MTA 2 -> GTFS 3 (experimental)
+ * @param accessibilityQualifier
+ * @return
+ */
+ public int convertMTAccessibilityToGTFS(int accessibilityQualifier) {
+ switch (accessibilityQualifier) {
+ case ADA_NOT_ACCESSIBLE:
+ return GTFS_WHEELCHAIR_NOT_ACCESSIBLE;
+ case ADA_FULLY_ACCESSIBLE:
+ return GTFS_WHEELCHAIR_ACCESSIBLE;
+ case ADA_PARTIALLY_ACCESSIBLE:
+ return GTFS_WHEELCHAIR_EXPERIMENTAL_PARTIALLY_ACCESSIBLE;
+ default:
+ return GTFS_WHEELCHAIR_UNKNOWN;
+ }
+ }
+
+
+ private List getStations() {
+ return readCsv(MTAStation.class, stationsCsv);
+ }
+
+ public void setStationsCsv(String stationsCsv) {
+ this.stationsCsv = stationsCsv;
+ }
+
+ private String getNamespace(){
+ return System.getProperty("cloudwatch.namespace");
+ }
+
+}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateRouteNames.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateRouteNames.java
index 26c655f73..1e38518df 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateRouteNames.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateRouteNames.java
@@ -35,6 +35,9 @@ public String getName() {
public void run(TransformContext context, GtfsMutableRelationalDao dao) {
for (Route route: dao.getAllRoutes()) {
+ if(route.getLongName()==null){
+ route.setLongName(route.getShortName());
+ }
if (route.getLongName().endsWith(" Line")) {
route.setLongName(route.getLongName().replace(" Line", ""));
}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateStopIdFromControlStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateStopIdFromControlStrategy.java
index e64d5d161..004bd7160 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateStopIdFromControlStrategy.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/impl/UpdateStopIdFromControlStrategy.java
@@ -163,7 +163,11 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
for (AgencyAndId id : stopsToRemove) {
Stop stop = dao.getStopForId(id);
//removeEntityLibrary.removeStop(dao, stop);
- dao.removeEntity(stop);
+ if (stop != null) {
+ dao.removeEntity(stop);
+ } else {
+ _log.info("expecting stop {} but it was not found", id);
+ }
}
}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategy.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategy.java
index 0d3d1b7dd..11465f2f4 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategy.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategy.java
@@ -18,6 +18,7 @@
import java.util.List;
import java.util.Map;
+import org.onebusaway.collections.tuple.T2;
import org.onebusaway.gtfs.model.StopTime;
import org.onebusaway.gtfs.model.Trip;
import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
@@ -41,7 +42,7 @@ public void run(TransformContext context, GtfsMutableRelationalDao dao) {
int removed = 0;
int total = 0;
- Map> tripsByBlockId = TripsByBlockInSortedOrder.getTripsByBlockInSortedOrder(dao);
+ Map> tripsByBlockId = TripsByBlockInSortedOrder.getTripsByBlockAndServiceIdInSortedOrder(dao);
for (List trips : tripsByBlockId.values()) {
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/TripsByBlockInSortedOrder.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/TripsByBlockInSortedOrder.java
index 7f3932417..84a9b5af2 100644
--- a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/TripsByBlockInSortedOrder.java
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/updates/TripsByBlockInSortedOrder.java
@@ -22,6 +22,9 @@
import java.util.List;
import java.util.Map;
+import org.onebusaway.collections.tuple.Pair;
+import org.onebusaway.collections.tuple.T2;
+import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.StopTime;
import org.onebusaway.gtfs.model.Trip;
import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
@@ -91,6 +94,66 @@ public static Map> getTripsByBlockInSortedOrder(
return tripsByBlockId;
}
+ public static Map> getTripsByBlockAndServiceIdInSortedOrder(
+ GtfsMutableRelationalDao dao) {
+
+ Map> tripsByBlockAndServiceId = new HashMap>();
+ Map averageStopTimeByTrip = new HashMap();
+
+ int totalTrips = 0;
+ int tripsWithoutStopTimes = 0;
+
+ for (Trip trip : dao.getAllTrips()) {
+
+ totalTrips++;
+
+ String blockId = trip.getBlockId();
+
+ // Generate a random block id if none is present so we get no collisions
+ if (blockId == null)
+ blockId = trip.getId() + "-" + Math.random();
+ T2 key = new T2Impl(trip.getServiceId(), blockId);
+
+ List trips = tripsByBlockAndServiceId.get(key);
+ if (trips == null) {
+ trips = new ArrayList();
+ tripsByBlockAndServiceId.put(key, trips);
+ }
+ trips.add(trip);
+
+ List stopTimes = dao.getStopTimesForTrip(trip);
+ if (stopTimes.isEmpty()) {
+ tripsWithoutStopTimes++;
+ } else {
+
+ int arrivalTimes = 0;
+ int arrivalTimeCount = 0;
+
+ for (StopTime stopTime : stopTimes) {
+ if (stopTime.isArrivalTimeSet()) {
+ arrivalTimes += stopTime.getArrivalTime();
+ arrivalTimeCount++;
+ }
+ }
+
+ if (arrivalTimeCount > 0) {
+ int averageArrivalTime = arrivalTimes / arrivalTimeCount;
+ averageStopTimeByTrip.put(trip, averageArrivalTime);
+ }
+ }
+ }
+
+ _log.info("trips=" + totalTrips + " withoutStopTimes="
+ + tripsWithoutStopTimes);
+
+ TripComparator c = new TripComparator(averageStopTimeByTrip);
+
+ for (List tripsInBlock : tripsByBlockAndServiceId.values()) {
+ Collections.sort(tripsInBlock, c);
+ }
+ return tripsByBlockAndServiceId;
+ }
+
private static class TripComparator implements Comparator {
private Map _averageArrivalTimesByTrip;
@@ -114,4 +177,37 @@ else if (st2 == null)
}
}
+ private static class T2Impl implements T2 {
+ private String first;
+ private String second;
+
+ public T2Impl(AgencyAndId serviceId, String blockId) {
+ this.first = null;
+ if (serviceId != null)
+ first = serviceId.toString();
+ this.second = blockId;
+ }
+
+ @Override
+ public Object getFirst() {
+ return first;
+ }
+
+ @Override
+ public Object getSecond() {
+ return second;
+ }
+
+ @Override
+ public int hashCode() {
+ return first.hashCode() + second.hashCode();
+ }
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ T2 t2 = (T2)o;
+ return this.first.equals(t2.getFirst())
+ && this.second.equals(t2.getSecond());
+ }
+ }
}
diff --git a/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/util/CalendarFunctions.java b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/util/CalendarFunctions.java
new file mode 100644
index 000000000..f917454e5
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/main/java/org/onebusaway/gtfs_transformer/util/CalendarFunctions.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.util;
+
+import org.onebusaway.gtfs.model.ServiceCalendar;
+import org.onebusaway.gtfs.model.ServiceCalendarDate;
+import org.onebusaway.gtfs.model.Trip;
+import org.onebusaway.gtfs.model.calendar.ServiceDate;
+import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * Common calendaring functions for Strategies/Transformations.
+ */
+public class CalendarFunctions {
+
+ public boolean isTripActive(GtfsMutableRelationalDao dao, ServiceDate serviceDate, Trip trip, boolean matchDayInCalendar) {
+ Date testDate = serviceDate.getAsDate();
+ //check for service
+ boolean hasCalDateException = false;
+ //are there calendar dates?
+ if (!dao.getCalendarDatesForServiceId(trip.getServiceId()).isEmpty()) {
+ //calendar dates are not empty
+ for (ServiceCalendarDate calDate : dao.getCalendarDatesForServiceId(trip.getServiceId())) {
+ Date date = constructDate(calDate.getDate());
+ if (date.equals(testDate)) {
+ hasCalDateException = true;
+ if (calDate.getExceptionType() == 1) {
+ //there is service for date
+ return true;
+ }
+ }
+ }
+ }
+ //if there are no entries in calendarDates, check serviceCalendar
+ if (!hasCalDateException) {
+ ServiceCalendar servCal = dao.getCalendarForServiceId(trip.getServiceId());
+ if (servCal == null) {
+ // do a brute force lookup as agencyIds are tricky
+ for (ServiceCalendar calendar : dao.getAllCalendars()) {
+ if (calendar.getServiceId().getId().equals(trip.getServiceId().getId())) {
+ servCal = calendar;
+ }
+ }
+
+ }
+ if (servCal != null) {
+ //check for service using calendar
+ Date start = removeTime(servCal.getStartDate().getAsDate());
+ Date end = removeTime(servCal.getEndDate().getAsDate());
+ if (testDate.equals(start) || testDate.equals(end) ||
+ (testDate.after(start) && testDate.before(end))) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(testDate);
+ if (!matchDayInCalendar) return true;
+ switch (cal.get(Calendar.DAY_OF_WEEK)) {
+ case Calendar.SUNDAY:
+ return servCal.getSunday() == 1;
+ case Calendar.MONDAY:
+ return servCal.getMonday() == 1;
+ case Calendar.TUESDAY:
+ return servCal.getTuesday() == 1;
+ case Calendar.WEDNESDAY:
+ return servCal.getWednesday() == 1;
+ case Calendar.THURSDAY:
+ return servCal.getThursday() == 1;
+ case Calendar.FRIDAY:
+ return servCal.getFriday() == 1;
+ case Calendar.SATURDAY:
+ return servCal.getSaturday() == 1;
+ default:
+ throw new IllegalStateException("unexected value " + cal.get(Calendar.DAY_OF_WEEK));
+ }
+ }
+ }
+ }
+ return false;
+ }
+ public Date addDays(Date date, int daysToAdd) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ cal.add(Calendar.DATE, daysToAdd);
+ return cal.getTime();
+ }
+
+ public Date constructDate(ServiceDate date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.YEAR, date.getYear());
+ calendar.set(Calendar.MONTH, date.getMonth()-1);
+ calendar.set(Calendar.DATE, date.getDay());
+ Date date1 = calendar.getTime();
+ date1 = removeTime(date1);
+ return date1;
+ }
+
+ public Date removeTime(Date date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ date = calendar.getTime();
+ return date;
+ }
+
+}
diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java
new file mode 100644
index 000000000..c4e816503
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/impl/AddExtensionFileTest.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onebusaway.gtfs.impl.FileSupport;
+import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl;
+import org.onebusaway.gtfs.serialization.GtfsWriter;
+import org.onebusaway.gtfs.services.GtfsRelationalDao;
+import org.onebusaway.gtfs_transformer.AbstractTestSupport;
+import org.onebusaway.gtfs_transformer.services.TransformContext;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.junit.Assert.*;
+
+/**
+ * test we can insert an extension into a GTFS file via a transformation.
+ */
+public class AddExtensionFileTest extends AbstractTestSupport {
+
+ private static final String TXT_STRING = "route_id,stop_id,direction_id,name\n";
+ private FileSupport _support = new FileSupport();
+ private GtfsRelationalDaoImpl dao = new GtfsRelationalDaoImpl();
+ private AddExtensionFile test = new AddExtensionFile();
+ private TransformContext context = new TransformContext();
+ private GtfsWriter writer = new GtfsWriter();
+
+ @Before
+ public void setup() {
+ _gtfs.putAgencies(1);
+ _gtfs.putStops(1);
+ _gtfs.putRoutes(1);
+ _gtfs.putTrips(1, "r0", "sid0");
+ _gtfs.putStopTimes("t0", "s0");
+
+ }
+ @After
+ public void teardown() {
+ _support.cleanup();
+ }
+
+ @Test
+ public void run() throws IOException {
+ File extensionFile = File.createTempFile("subwayRouteStop-", ".csv");
+ if (extensionFile.exists())
+ extensionFile.deleteOnExit();
+
+ String extensionFilename = extensionFile.getAbsolutePath();;
+ BufferedWriter bwriter = new BufferedWriter(new FileWriter(extensionFilename));
+ bwriter.write(TXT_STRING);
+ bwriter.close();
+
+ String extensionName = "route_stop.txt";
+ test.setExtensionFilename(extensionFilename);
+ test.setExtensionName(extensionName);
+
+ test.run(context, dao);
+
+ File tmpFileDirectory = File.createTempFile("AddExtensionFileTest-", "-tmp");
+ if (tmpFileDirectory.exists())
+ _support.deleteFileRecursively(tmpFileDirectory);
+ tmpFileDirectory.mkdirs();
+ _support.markForDeletion(tmpFileDirectory);
+ writer.setOutputLocation(tmpFileDirectory);
+ writer.run(dao);
+ writer.close();
+
+ String modLocation = tmpFileDirectory.getAbsolutePath() + File.separator + extensionName;
+ File expectedFile = new File(modLocation);
+ // verify file is there
+ assertTrue("expected extension to be present!", expectedFile.exists());
+ assertTrue("expected extension to be a file!", expectedFile.isFile());
+ String actualText = Files.readString(Path.of(modLocation));
+ assertEquals(TXT_STRING, actualText);
+
+ }
+}
\ No newline at end of file
diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java
new file mode 100644
index 000000000..8fcef1bcd
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/CarryForwardExpectedFilesTest.java
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.updates;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onebusaway.gtfs.impl.FileSupport;
+import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl;
+import org.onebusaway.gtfs.impl.ZipHandler;
+import org.onebusaway.gtfs.serialization.GtfsReader;
+import org.onebusaway.gtfs.serialization.GtfsWriter;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Ensure metadata files are still present in typical read/write cycle.
+ */
+public class CarryForwardExpectedFilesTest {
+
+ private GtfsRelationalDaoImpl _dao;
+ private File _tmpFileDirectory;
+
+ private File _tmpZipFileDirectory;
+
+ private FileSupport _support = new FileSupport();
+
+ @Before
+ public void setup() throws IOException, URISyntaxException {
+ _dao = new GtfsRelationalDaoImpl();
+ }
+
+ @After
+ public void teardown() {
+ _support.cleanup();
+ }
+
+ @Test
+ public void testFile() throws Exception {
+ GtfsReader reader = new GtfsReader();
+ File path = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_transformer/testagency").toURI());
+ reader.setInputLocation(path);
+ reader.setEntityStore(_dao);
+ reader.run();
+
+ _tmpFileDirectory = File.createTempFile("CarryForwardExpectedFilesTest-", "-tmp");
+ if (_tmpFileDirectory.exists())
+ _support.deleteFileRecursively(_tmpFileDirectory);
+ _tmpFileDirectory.mkdirs();
+ _support.markForDeletion(_tmpFileDirectory);
+
+ // write out the file
+ GtfsWriter writer = new GtfsWriter();
+ writer.setOutputLocation(_tmpFileDirectory);
+ writer.run(_dao);
+ writer.close();
+ String modLocation = _tmpFileDirectory.getAbsolutePath() + File.separator + "modifications.txt";
+ File expectedFile = new File(modLocation);
+ // verify modifications.txt is there!!!!
+ assertTrue("expected modifications.txt to be present!", expectedFile.exists());
+ assertTrue("expected modifications.txt to be a file!", expectedFile.isFile());
+ }
+
+ @Test
+ public void testZipFile() throws Exception {
+ GtfsReader reader = new GtfsReader();
+ File inputZipFile = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_transformer/testagency.zip").toURI());
+ reader.setInputLocation(inputZipFile);
+ reader.setEntityStore(_dao);
+ reader.run();
+
+ _tmpZipFileDirectory = File.createTempFile("CarryForwardExpectedFilesTestZip-", "-tmp");
+ if (_tmpZipFileDirectory.exists())
+ _support.deleteFileRecursively(_tmpZipFileDirectory);
+ _tmpZipFileDirectory.mkdirs();
+ _support.markForDeletion(_tmpZipFileDirectory);
+
+ String zipFileName = _tmpZipFileDirectory.getAbsolutePath() + File.separator + "gtfs.zip";
+ // write out the file
+ GtfsWriter writer = new GtfsWriter();
+ writer.setOutputLocation(new File(zipFileName));
+ writer.run(_dao);
+ writer.close();
+
+ ZipHandler zip = new ZipHandler(new File(zipFileName));
+ String content = zip.readTextFromFile("modifications.txt");
+ // look for some content inside file to verify its correct
+ assertTrue(content.contains("characters"));
+ }
+}
diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java
new file mode 100644
index 000000000..176e8eceb
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/MTAStationAccessibilityStrategyTest.java
@@ -0,0 +1,102 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.updates;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onebusaway.gtfs.model.AgencyAndId;
+import org.onebusaway.gtfs.model.Stop;
+import org.onebusaway.gtfs.services.GtfsRelationalDao;
+import org.onebusaway.gtfs_transformer.AbstractTestSupport;
+
+import java.io.File;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.onebusaway.gtfs_transformer.csv.MTAStation.*;
+
+/**
+ * Document and test partial and fully accessible MTAStations.
+ */
+public class MTAStationAccessibilityStrategyTest extends AbstractTestSupport {
+
+ @Before
+ public void setup() throws Exception {
+ File stations_csv = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_transformer/stations/stations.csv").toURI());
+ addModification("{\"op\": \"transform\", \"class\":\"org.onebusaway.gtfs_transformer.impl.MTAStationAccessibilityStrategy\", \"stations_csv\": \""+stations_csv+"\"}");
+ }
+
+ @Test
+ public void testStations() throws Exception {
+ _gtfs.putAgencies(1);
+ _gtfs.putNamedStops("R15,49 St", "A25,50 St", "233,Hoyt St", "626,86 St",
+ "R15N,49 St", "A25N,50 St", "233N,Hoyt St", "626N,86 St",
+ "R15S,49 St", "A25S,50 St", "233S,Hoyt St", "626S,86 St");
+ _gtfs.putRoutes(1);
+ _gtfs.putTrips(12, "r0", "sid0");
+ _gtfs.putStopTimes("t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10,t11", "R15,A25,233,626,R15N,A25N,233N,626N,R15S,A25S,233S,626S");
+ _gtfs.putCalendars(1, "start_date=20120903", "end_date=20120916");
+
+ GtfsRelationalDao dao = transform();
+ assertStation(dao, "R15", 2, 1, 0); // 49 St (ADA=2, ADA NB=1, ADA SB=0)
+ assertStation(dao, "A25", 2, 0, 1); // 50 St (ADA=2, ADA NB=0, ADA SB=1
+ assertStation(dao, "233", 2, null, null); // Hoyt St (ADA=2, ADA NB=blank, ADA SB=blank)
+ assertStation(dao, "626", 2, 2, 0); // 86 St (ADA=2, ADA NB=2, ADA SB=0)
+ }
+
+ private void assertStation(GtfsRelationalDao dao, String stopId, int ada, Integer northBoundFlag, Integer southBoundFlag) {
+ Stop parentStop = dao.getStopForId(new AgencyAndId("a0", stopId));
+ assertNotNull(parentStop);
+ Stop nStop = dao.getStopForId(new AgencyAndId("a0", stopId+"N"));
+ assertNotNull(nStop);
+ Stop sStop = dao.getStopForId(new AgencyAndId("a0", stopId+"S"));
+ assertNotNull(sStop);
+
+ assertEquals("expecting ada flag to match wheelchairBoarding flag for stop " + parentStop.getId(),
+ ada, converGTFSccessibilityToMTA(parentStop.getWheelchairBoarding()));
+ if (northBoundFlag == null) {
+ assertEquals("expecting N/A wheelchairBoarding for northbound stop " + nStop,0, converGTFSccessibilityToMTA(nStop.getWheelchairBoarding())); // default is 0
+ } else {
+ assertEquals("expecting northBoundFlag to match wheelchairBoarding flag for stop" + nStop, northBoundFlag.intValue(), converGTFSccessibilityToMTA(nStop.getWheelchairBoarding()));
+ }
+ if (southBoundFlag == null) {
+ assertEquals("expecting N/A wheelchairBoarding for southbound stop " + sStop,0, converGTFSccessibilityToMTA(sStop.getWheelchairBoarding()));
+ } else {
+ assertEquals("expecting southBoundFlag to match wheelchairBoarding flag for stop" + sStop, southBoundFlag.intValue(), converGTFSccessibilityToMTA(sStop.getWheelchairBoarding()));
+ }
+ }
+
+ /**
+ * GTFS 1 -> MTA 1
+ * GTFS 2 -> MTA 0
+ * GTFS 3 -> MTA 2
+ * @param gtfsValue
+ * @return
+ */
+ private int converGTFSccessibilityToMTA(int gtfsValue) {
+ switch (gtfsValue) {
+ case 1:
+ return ADA_FULLY_ACCESSIBLE;
+ case 2:
+ return ADA_NOT_ACCESSIBLE;
+ case 3:
+ return ADA_PARTIALLY_ACCESSIBLE;
+ default:
+ return 0;// unknown
+ }
+ }
+}
diff --git a/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java
new file mode 100644
index 000000000..c7cf84b9f
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/test/java/org/onebusaway/gtfs_transformer/updates/RemoveRepeatedStopTimesStrategyTest.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs_transformer.updates;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onebusaway.collections.tuple.T2;
+import org.onebusaway.gtfs.impl.GtfsRelationalDaoImpl;
+import org.onebusaway.gtfs.model.AgencyAndId;
+import org.onebusaway.gtfs.model.StopTime;
+import org.onebusaway.gtfs.model.Trip;
+import org.onebusaway.gtfs.serialization.GtfsReader;
+import org.onebusaway.gtfs.services.GenericMutableDao;
+import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
+import org.onebusaway.gtfs_transformer.services.TransformContext;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.onebusaway.gtfs_transformer.updates.TripsByBlockInSortedOrder.getTripsByBlockAndServiceIdInSortedOrder;
+
+/**
+ * test removing repeated stops when blocks are not distinct across service ids.
+ */
+public class RemoveRepeatedStopTimesStrategyTest {
+
+ private GtfsRelationalDaoImpl _dao;
+ private TransformContext _context = new TransformContext();
+ @Before
+ public void before() throws IOException {
+ _dao = new GtfsRelationalDaoImpl();
+ }
+
+ @Test
+ public void testSort() throws Exception {
+ GtfsReader reader = new GtfsReader();
+ File path0 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_transformer/updates/cut.zip").toURI());
+ reader.setInputLocation(path0);
+ reader.setEntityStore(_dao);
+ reader.run();
+
+ Map> trips = getTripsByBlockAndServiceIdInSortedOrder(_dao);
+ boolean case1 = false;
+ for (List partitionedTrips : trips.values()) {
+ int tripIndex = -1;
+ for (Trip trip : partitionedTrips) {
+ tripIndex++;
+ if ("10514090".equals(trip.getId().getId())) {
+ // ensure sort worked!
+ assertEquals("SM-62", partitionedTrips.get(tripIndex).getBlockId());
+ assertEquals("10514090", partitionedTrips.get(tripIndex).getId().getId());
+ assertEquals("SM-62", partitionedTrips.get(tripIndex+1).getBlockId());
+ assertEquals("7438090", partitionedTrips.get(tripIndex+1).getId().getId());
+ assertEquals("SM-62", partitionedTrips.get(tripIndex+2).getBlockId());
+ assertEquals("6986090", partitionedTrips.get(tripIndex+2).getId().getId());
+ assertEquals("SM-62", partitionedTrips.get(tripIndex+3).getBlockId());
+ assertEquals("32069090", partitionedTrips.get(tripIndex+3).getId().getId());
+ assertEquals("SM-62", partitionedTrips.get(tripIndex+4).getBlockId());
+ assertEquals("682090", partitionedTrips.get(tripIndex+4).getId().getId());
+ case1 = true;
+ }
+ }
+
+ }
+
+ assertTrue(case1);
+ }
+
+ @Test
+ public void test() throws Exception {
+ GtfsReader reader = new GtfsReader();
+ File path0 = new File(getClass().getResource(
+ "/org/onebusaway/gtfs_transformer/updates/cut.zip").toURI());
+ reader.setInputLocation(path0);
+ reader.setEntityStore(_dao);
+ reader.run();
+ GenericMutableDao dao = reader.getEntityStore();
+ RemoveRepeatedStopTimesStrategy strat = new RemoveRepeatedStopTimesStrategy();
+ strat.run(_context, (GtfsMutableRelationalDao) dao);
+
+ boolean case1 = false;
+ boolean case2 = false;
+ for (Trip trip : ((GtfsMutableRelationalDao) dao).getAllTrips()) {
+ if ("7438090".equals(trip.getId().getId())) {
+ // confirm the last stop on the trip was removed
+ // and the first stop on the next trip has arrival modified
+ // block SM-62
+ List stopTimesForTrip = ((GtfsMutableRelationalDao) dao).getStopTimesForTrip(trip);
+ StopTime lastStopTrip1 = stopTimesForTrip.get(stopTimesForTrip.size()-1);
+ assertEquals("4128", lastStopTrip1.getStop().getId().getId()); // if this is 18938 we failed!
+ case1 = true;
+ } else if ("6986090".equals(trip.getId().getId())) {
+ // block SM-62
+ List stopTimesForTrip = ((GtfsMutableRelationalDao) dao).getStopTimesForTrip(trip);
+ StopTime firstStopTrip2 = stopTimesForTrip.get(0);
+ assertEquals("18938", firstStopTrip2.getStop().getId().getId());
+ assertEquals(23400, firstStopTrip2.getArrivalTime()); // arrival is now that of previous removed stops
+ assertEquals(24300, firstStopTrip2.getDepartureTime());
+ case2 = true;
+ }
+ }
+ assertTrue(case1);
+ assertTrue(case2);
+ }
+}
diff --git a/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/stations/stations.csv b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/stations/stations.csv
new file mode 100644
index 000000000..fa95d282e
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/stations/stations.csv
@@ -0,0 +1,5 @@
+Station ID,Complex ID,GTFS Stop ID,Division,Line,Stop Name,Borough,Daytime Routes,Structure,GTFS Latitude,GTFS Longitude,North Direction Label,South Direction Label,ADA,ADA Direction Notes,ADA NB,ADA SB,Capital Outage NB,Capital Outage SB
+10,10,R15,BMT,Broadway - Brighton,49 St,M,N R W,Subway,40.759901,-73.984139,Uptown & Queens,Downtown & Brooklyn,2,Uptown & Queens,1,0,,
+162,162,A25,IND,8th Av - Fulton St,50 St,M,C E,Subway,40.762456,-73.985984,Uptown - Queens,Downtown & Brooklyn,2,Downtown & Brooklyn only,0,1,,
+336,336,233,IRT,Eastern Pky,Hoyt St,Bk,2 3,Subway,40.690545,-73.985065,Manhattan,Flatbush - New Lots,2,,,,,
+397,397,626,IRT,Lexington Av,86 St,M,4 5 6,Subway,40.779492,-73.955589,Uptown & The Bronx,Downtown & Brooklyn,2,Uptown & The Bronx local only,2,0,,
diff --git a/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/testagency.zip b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/testagency.zip
new file mode 100644
index 000000000..cd6480431
Binary files /dev/null and b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/testagency.zip differ
diff --git a/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/testagency/modifications.txt b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/testagency/modifications.txt
new file mode 100644
index 000000000..7ebc39357
--- /dev/null
+++ b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/testagency/modifications.txt
@@ -0,0 +1,5 @@
+# this is free form text belonging to Fake Agency (agency)
+This text may have commas, but we can't expect it.
+This text may include:
+* bullets
+\ and lots of other characters
diff --git a/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/updates/cut.zip b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/updates/cut.zip
new file mode 100644
index 000000000..df90a2acc
Binary files /dev/null and b/onebusaway-gtfs-transformer/src/test/resources/org/onebusaway/gtfs_transformer/updates/cut.zip differ
diff --git a/onebusaway-gtfs/pom.xml b/onebusaway-gtfs/pom.xml
index b9487c292..2ec9d470a 100644
--- a/onebusaway-gtfs/pom.xml
+++ b/onebusaway-gtfs/pom.xml
@@ -9,28 +9,39 @@
org.onebusaway
onebusaway-gtfs-modules
- 1.3.112-openmove-8
+ 1.4.15-openmove-1
org.onebusaway
onebusaway-csv-entities
+
+
+ slf4j-api
+ org.slf4j
+
+
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j_version}
com.fasterxml.jackson.core
jackson-databind
- 2.12.0
+ 2.14.0
de.grundid.opendatalab
geojson-jackson
- 1.8.1
+ 1.14
org.slf4j
- slf4j-log4j12
- test
+ slf4j-simple
+ ${slf4j_version}
junit
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/FileSupport.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/FileSupport.java
new file mode 100644
index 000000000..4b9805229
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/FileSupport.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.impl;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileSupport {
+
+ private List toDelete = new ArrayList<>();
+ public FileSupport() {
+ }
+
+ public void markForDeletion(File file) {
+ toDelete.add(file);
+ }
+
+ public void cleanup() {
+ for (File file : toDelete) {
+ deleteFileRecursively(file);
+ }
+ toDelete.clear();
+ }
+
+ public void deleteFileRecursively(File file) {
+ if (file == null)
+ return;
+
+ if (!file.exists())
+ return;
+
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files)
+ deleteFileRecursively(child);
+ }
+ }
+
+ file.delete();
+ }
+
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java
index 7d10aea1a..89d411605 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDaoImpl.java
@@ -16,8 +16,7 @@
package org.onebusaway.gtfs.impl;
import java.io.Serializable;
-import java.util.Collection;
-import java.util.Map;
+import java.util.*;
import org.onebusaway.gtfs.model.*;
import org.onebusaway.gtfs.services.GenericMutableDao;
@@ -26,6 +25,7 @@
public class GtfsDaoImpl extends GenericDaoImpl implements GtfsMutableDao {
+ public static final String[] OPTIONAL_FILE_NAMES = {"modifications.txt"};
private StopTimeArray stopTimes = new StopTimeArray();
private ShapePointArray shapePoints = new ShapePointArray();
@@ -34,6 +34,19 @@ public class GtfsDaoImpl extends GenericDaoImpl implements GtfsMutableDao {
private boolean packShapePoints = false;
+ private List _optionalMetadataFilenames = null;
+
+ private Map metadataByFilename = new HashMap<>();
+
+ public GtfsDaoImpl() {
+ _optionalMetadataFilenames = new ArrayList<>();
+ if (OPTIONAL_FILE_NAMES != null) {
+ for (String optionalFileName : OPTIONAL_FILE_NAMES) {
+ _optionalMetadataFilenames.add(optionalFileName);
+ }
+ }
+ }
+
public boolean isPackStopTimes() {
return packStopTimes;
}
@@ -95,6 +108,14 @@ public Collection getAllRoutes() {
return getAllEntitiesForType(Route.class);
}
+ public Collection getAllRouteStops() {
+ return getAllEntitiesForType(RouteStop.class);
+ }
+
+ public Collection getAllRouteShapes() {
+ return getAllEntitiesForType(RouteShape.class);
+ }
+
public Collection getAllShapePoints() {
if (packShapePoints) {
return shapePoints;
@@ -163,8 +184,8 @@ public FareProduct getFareProductForId(AgencyAndId id) {
}
@Override
- public Collection getAllFareContainers() {
- return getAllEntitiesForType(FareContainer.class);
+ public Collection getAllFareMedia() {
+ return getAllEntitiesForType(FareMedium.class);
}
@Override
@@ -239,7 +260,11 @@ public Level getLevelForId(AgencyAndId id) {
public FacilityPropertyDefinition getFacilityPropertiesDefinitionsForId(AgencyAndId id) { return getEntityForId(FacilityPropertyDefinition.class, id);}
public RouteNameException getRouteNameExceptionForId(AgencyAndId id) { return getEntityForId(RouteNameException.class, id);}
public DirectionNameException getDirectionNameExceptionForId(AgencyAndId id) { return getEntityForId(DirectionNameException.class, id);}
+ public AlternateStopNameException getAlternateStopNameExceptionForId(AgencyAndId id) { return getEntityForId(AlternateStopNameException.class, id);}
+ public Collection getAllDirectionEntries() {
+ return getAllEntitiesForType(DirectionEntry.class);
+ }
public Collection getAllFacilities() {
return getAllEntitiesForType(Facility.class);
}
@@ -255,7 +280,13 @@ public Collection getAllRouteNameExceptions() {
public Collection getAllDirectionNameExceptions() {
return getAllEntitiesForType(DirectionNameException.class);
}
+ public Collection getAllAlternateStopNameExceptions(){
+ return getAllEntitiesForType(AlternateStopNameException.class);
+ }
+ public Collection getAllWrongWayConcurrencies() {
+ return getAllEntitiesForType(WrongWayConcurrency.class);
+ }
public Collection getAllAreas() {
return getAllEntitiesForType(Area.class);
}
@@ -268,6 +299,16 @@ public Collection getAllLocationGroups() {
return getAllEntitiesForType(LocationGroup.class);
}
+ @Override
+ public Collection getAllStopAreaElements() {
+ return getAllEntitiesForType(StopAreaElement.class);
+ }
+
+ @Override
+ public Collection getAllStopAreas() {
+ return getAllEntitiesForType(StopArea.class);
+ }
+
public Collection getAllLocations() {
return getAllEntitiesForType(Location.class);
}
@@ -280,11 +321,6 @@ public Collection getAllTranslations() {
return getAllEntitiesForType(Translation.class);
}
- @Override
- public Collection getAllStopAreas() {
- return getAllEntitiesForType(StopArea.class);
- }
-
/****
* {@link GenericMutableDao} Interface
****/
@@ -364,6 +400,26 @@ public void close() {
super.close();
}
+ @Override
+ public List getOptionalMetadataFilenames() {
+ return _optionalMetadataFilenames;
+ }
+ @Override
+ public boolean hasMetadata(String filename) {
+ return metadataByFilename.containsKey(filename);
+ }
+ @Override
+ public String getMetadata(String filename) {
+ return metadataByFilename.get(filename);
+ }
+ @Override
+ public void addMetadata(String filename, String content) {
+ metadataByFilename.put(filename, content);
+ if (!_optionalMetadataFilenames.contains(filename))
+ _optionalMetadataFilenames.add(filename);
+ }
+
+
/****
* Private Methods
****/
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java
index 460bf903e..033eb3976 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsDataServiceImpl.java
@@ -105,8 +105,8 @@ public FareProduct getFareProductForId(AgencyAndId id) {
}
@Override
- public Collection getAllFareContainers() {
- return _dao.getAllFareContainers();
+ public Collection getAllFareMedia() {
+ return _dao.getAllFareMedia();
}
@Override
@@ -224,6 +224,15 @@ public Collection getAllRoutes() {
return _dao.getAllRoutes();
}
+ @Override
+ public Collection getAllRouteStops() {
+ return _dao.getAllRouteStops();
+ }
+
+ @Override
+ public Collection getAllRouteShapes() {
+ return _dao.getAllRouteShapes();
+ }
@Override
public Route getRouteForId(AgencyAndId id) {
return _dao.getRouteForId(id);
@@ -354,6 +363,16 @@ public Collection getAllLocationGroupElements() {
return _dao.getAllLocationGroupElements();
}
+ @Override
+ public Collection getAllStopAreaElements() {
+ return _dao.getAllStopAreaElements();
+ }
+
+ @Override
+ public Collection getAllStopAreas() {
+ return _dao.getAllStopAreas();
+ }
+
@Override
public Collection getAllLocationGroups() {
return _dao.getAllLocationGroups();
@@ -375,8 +394,23 @@ public Collection getAllTranslations() {
}
@Override
- public Collection getAllStopAreas() {
- return _dao.getAllStopAreas();
+ public List getOptionalMetadataFilenames() {
+ return _dao.getOptionalMetadataFilenames();
+ }
+
+ @Override
+ public boolean hasMetadata(String filename) {
+ return _dao.hasMetadata(filename);
+ }
+
+ @Override
+ public String getMetadata(String filename) {
+ return _dao.getMetadata(filename);
+ }
+
+ @Override
+ public void addMetadata(String filename, String content) {
+ _dao.addMetadata(filename, content);
}
@Override
@@ -444,6 +478,12 @@ public List getServiceDateArrivalsWithinRange(LocalizedServiceId serviceId
return _calendarService.getServiceDateArrivalsWithinRange(serviceId, interval, from, to);
}
+ @Override
+ public boolean isLocalizedServiceIdActiveInRange(LocalizedServiceId serviceId,
+ ServiceInterval scheduledService,
+ AgencyServiceInterval serviceInterval) {
+ return _calendarService.isLocalizedServiceIdActiveInRange(serviceId, scheduledService, serviceInterval);
+ }
@Override
public Map> getServiceDateArrivalsWithinRange(ServiceIdIntervals serviceIdIntervals, Date from, Date to) {
return _calendarService.getServiceDateArrivalsWithinRange(serviceIdIntervals, from, to);
@@ -479,7 +519,7 @@ public void setData(CalendarServiceData data) {
public FacilityPropertyDefinition getFacilityPropertiesDefinitionsForId(AgencyAndId id) { return getEntityForId(FacilityPropertyDefinition.class, id);}
public RouteNameException getRouteNameExceptionForId(AgencyAndId id) { return getEntityForId(RouteNameException.class, id);}
public DirectionNameException getDirectionNameExceptionForId(AgencyAndId id) { return getEntityForId(DirectionNameException.class, id);}
-
+ public AlternateStopNameException getAlternateStopNameExceptionForId(AgencyAndId id) { return getEntityForId(AlternateStopNameException.class, id);}
public Collection getAllFacilities() {
return getAllEntitiesForType(Facility.class);
}
@@ -495,4 +535,13 @@ public Collection getAllRouteNameExceptions() {
public Collection getAllDirectionNameExceptions() {
return getAllEntitiesForType(DirectionNameException.class);
}
+ public Collection getAllAlternateStopNameExceptions(){
+ return getAllEntitiesForType(AlternateStopNameException.class);
+ }
+ public Collection getAllDirectionEntries() {
+ return _dao.getAllDirectionEntries();
+ }
+ public Collection getAllWrongWayConcurrencies() {
+ return _dao.getAllWrongWayConcurrencies();
+ }
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImpl.java
index 3b5881f8a..78c1abd39 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImpl.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/GtfsRelationalDaoImpl.java
@@ -29,19 +29,7 @@
import org.onebusaway.csv_entities.exceptions.EntityInstantiationException;
import org.onebusaway.csv_entities.schema.BeanWrapper;
import org.onebusaway.csv_entities.schema.BeanWrapperFactory;
-import org.onebusaway.gtfs.model.Agency;
-import org.onebusaway.gtfs.model.AgencyAndId;
-import org.onebusaway.gtfs.model.FareAttribute;
-import org.onebusaway.gtfs.model.FareRule;
-import org.onebusaway.gtfs.model.Frequency;
-import org.onebusaway.gtfs.model.Ridership;
-import org.onebusaway.gtfs.model.Route;
-import org.onebusaway.gtfs.model.ServiceCalendar;
-import org.onebusaway.gtfs.model.ServiceCalendarDate;
-import org.onebusaway.gtfs.model.ShapePoint;
-import org.onebusaway.gtfs.model.Stop;
-import org.onebusaway.gtfs.model.StopTime;
-import org.onebusaway.gtfs.model.Trip;
+import org.onebusaway.gtfs.model.*;
import org.onebusaway.gtfs.services.GtfsMutableRelationalDao;
/**
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java
index 3de5ab0d4..a2a73f1be 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/StopTimeArray.java
@@ -258,11 +258,31 @@ public StopLocation getStop() {
return stops[index];
}
+ @Override
+ public StopLocation getLocation() {
+ return stops[index];
+ }
+
+ @Override
+ public StopLocation getLocationGroup() {
+ return stops[index];
+ }
+
@Override
public void setStop(StopLocation stop) {
stops[index] = stop;
}
+ @Override
+ public void setLocation(StopLocation location) {
+ stops[index] = location;
+ }
+
+ @Override
+ public void setLocationGroup(StopLocation group) {
+ stops[index] = group;
+ }
+
@Override
public boolean isArrivalTimeSet() {
return arrivalTimes[index] != StopTime.MISSING_VALUE;
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/ZipHandler.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/ZipHandler.java
new file mode 100644
index 000000000..5185be452
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/ZipHandler.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+
+/**
+ * Zip file support.
+ */
+public class ZipHandler {
+
+ public static final String ZIP_SCHEME = "jar:file:";
+
+ private File zipFile;
+
+ public ZipHandler(File zipFile) {
+ this.zipFile = zipFile;
+ }
+
+ /**
+ * read metadata files from a text file.
+ * @param fileInZip
+ * @return
+ * @throws IOException
+ */
+ public String readTextFromFile(String fileInZip) throws IOException {
+ final StringBuffer content = new StringBuffer();
+ FileSystem zipFileSystem = null;
+ try {
+ URI uri = URI.create(ZIP_SCHEME + zipFile.getAbsolutePath());
+ // java 7 introduced native support for zip files
+ zipFileSystem = FileSystems.newFileSystem(uri, new HashMap<>());
+ Path root = zipFileSystem.getPath(fileInZip);
+
+ Files.walkFileTree(root, new SimpleFileVisitor() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ content.append(Files.lines(file).collect(Collectors.joining(System.lineSeparator())));
+ return FileVisitResult.TERMINATE;
+ }
+ });
+ return content.toString();
+ } finally {
+ if (zipFileSystem != null) {
+ zipFileSystem.close();
+ }
+ }
+ }
+
+ /**
+ * write metatdata files to an existing zip file.
+ * @param fileInZip
+ * @param content
+ * @throws IOException
+ */
+ public void writeTextToFile(String fileInZip, String content) throws IOException {
+ FileSystem zipFileSystem = null;
+ try {
+ URI uri = URI.create(ZIP_SCHEME + zipFile.toURI().getPath());
+ // java 7 introduced native support for zip files
+ zipFileSystem = FileSystems.newFileSystem(uri, new HashMap<>());
+ Path newZipEntry = zipFileSystem.getPath(fileInZip);
+ Writer writer = Files.newBufferedWriter(newZipEntry, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
+ writer.write(content);
+ writer.close();
+ } finally {
+ if (zipFileSystem != null) {
+ zipFileSystem.close();
+ }
+ }
+ }
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImpl.java
index e6d8db2be..4336f4381 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImpl.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/calendar/CalendarServiceImpl.java
@@ -27,11 +27,7 @@
import java.util.TimeZone;
import org.onebusaway.gtfs.model.AgencyAndId;
-import org.onebusaway.gtfs.model.calendar.CalendarServiceData;
-import org.onebusaway.gtfs.model.calendar.LocalizedServiceId;
-import org.onebusaway.gtfs.model.calendar.ServiceDate;
-import org.onebusaway.gtfs.model.calendar.ServiceIdIntervals;
-import org.onebusaway.gtfs.model.calendar.ServiceInterval;
+import org.onebusaway.gtfs.model.calendar.*;
import org.onebusaway.gtfs.services.calendar.CalendarService;
import org.onebusaway.gtfs.services.calendar.CalendarServiceDataFactory;
@@ -126,6 +122,34 @@ public boolean isLocalizedServiceIdActiveOnDate(
return Collections.binarySearch(dates, serviceDate) >= 0;
}
+ /**
+ * test if the given calendar servieId is active in the union of the activeService
+ * window and the agencyServiceInterval.
+ * @param localizedServiceId
+ * @param activeService
+ * @param agencyServiceInterval
+ * @return
+ */
+ public boolean isLocalizedServiceIdActiveInRange(LocalizedServiceId localizedServiceId,
+ ServiceInterval activeService,
+ AgencyServiceInterval agencyServiceInterval) {
+ if (agencyServiceInterval == null || agencyServiceInterval.getServiceDate() == null) {
+ throw new IllegalStateException("agencyServiceInterval cannot be null");
+ }
+ ServiceInterval serviceInterval = agencyServiceInterval.getServiceInterval(localizedServiceId.getId().getAgencyId());
+
+ boolean active = isLocalizedServiceIdActiveOnDate(localizedServiceId, agencyServiceInterval.getServiceDate().getAsDate());
+ if (active) {
+ // even if a match is found enforce overlap in service intervals
+ if (Math.max(activeService.getMinArrival(), serviceInterval.getMinArrival())
+ <= Math.min(activeService.getMaxDeparture(), serviceInterval.getMaxDeparture())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
@Override
public List getServiceDateArrivalsWithinRange(
LocalizedServiceId serviceId, ServiceInterval interval, Date from, Date to) {
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/translation/TranslationServiceDataFactoryImpl.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/translation/TranslationServiceDataFactoryImpl.java
index 5728bcca9..34afea4c3 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/translation/TranslationServiceDataFactoryImpl.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/impl/translation/TranslationServiceDataFactoryImpl.java
@@ -55,6 +55,12 @@ public class TranslationServiceDataFactoryImpl implements TranslationServiceData
private static final String FACILITY_PROPERTY_DEFINITION_TABLE_NAME = "facilities_properties_definitions";
+ private static final String WRONG_WAY_TABLE_NAME = "wrong_way_concurrencies";
+
+ private static final String DIRECTION_ENTRY_TABLE_NAME = "direction_entry";
+
+ private static final String ALTERNATE_STOP_NAME_EXCEPTION_TABLE_NAME = "alternate_stop_name_exception";
+
private GtfsRelationalDao _dao;
public static TranslationService getTranslationService(GtfsRelationalDao dao) {
@@ -128,6 +134,12 @@ private Class> getEntityTypeForTableName(String name) {
return FacilityProperty.class;
case FACILITY_PROPERTY_DEFINITION_TABLE_NAME:
return FacilityPropertyDefinition.class;
+ case WRONG_WAY_TABLE_NAME:
+ return WrongWayConcurrency.class;
+ case DIRECTION_ENTRY_TABLE_NAME:
+ return DirectionEntry.class;
+ case ALTERNATE_STOP_NAME_EXCEPTION_TABLE_NAME:
+ return AlternateStopNameException.class;
}
return null;
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java
new file mode 100644
index 000000000..4dd04882f
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNameException.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (C) 2022 Cambridge Systematics
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+
+@CsvFields(filename = "alternate_stop_names_exceptions.txt", required = false)
+public class AlternateStopNameException extends IdentityBean {
+
+ @CsvField(ignore = true)
+ private int id;
+
+ @CsvField(optional = true)
+ int routeId;
+
+ @CsvField(optional = true)
+ int directionId;
+
+ @CsvField(optional = true)
+ int stopId;
+
+ @CsvField(optional = true)
+ String alternateStopName;
+
+ public AlternateStopNameException() {
+ }
+
+ public AlternateStopNameException(AlternateStopNameException asne) {
+ this.routeId = asne.routeId;
+ this.directionId = asne.directionId;
+ this.stopId = asne.stopId;
+ this.alternateStopName = asne.alternateStopName;
+ }
+
+
+ @Override
+ public Integer getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public int getRouteId() {
+ return routeId;
+ }
+
+ public void setRouteId(int routeId) {
+ this.routeId = routeId;
+ }
+
+ public int getDirectionId() {
+ return directionId;
+ }
+
+ public void setDirectionId(int directionId) {
+ this.directionId = directionId;
+ }
+
+ public int getStopId() {
+ return stopId;
+ }
+
+ public void setStopId(int stopId) {
+ this.stopId = stopId;
+ }
+
+ public String getAlternateStopName() {
+ return alternateStopName;
+ }
+
+ public void setAlternateStopName(String alternateStopName) {
+ this.alternateStopName = alternateStopName;
+ }
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNamesExceptions.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNamesExceptions.java
new file mode 100644
index 000000000..ab2093779
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/AlternateStopNamesExceptions.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+
+@CsvFields(filename = "alternate_stop_names_exceptions.txt", required = false)
+public class AlternateStopNamesExceptions {
+
+ @CsvField(ignore = true)
+ private int id;
+
+ @CsvField(optional = true)
+ int routeId;
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java
index 5f7426db7..b09828c10 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Area.java
@@ -30,6 +30,7 @@ public final class Area extends IdentityBean {
@CsvField(name="area_name", optional = true)
private String name;
+ @CsvField(name="wkt", optional = true)
private String wkt;
public Area() {
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/DirectionEntry.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/DirectionEntry.java
new file mode 100644
index 000000000..0637fe52d
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/DirectionEntry.java
@@ -0,0 +1,146 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+
+@CsvFields(filename = "direction_names.txt", required = false)
+public class DirectionEntry extends IdentityBean {
+
+ @CsvField(ignore = true)
+ private int id;
+
+ @CsvField(name="agency_id")
+ private String agencyId;
+
+ @CsvField(name="station_id")
+ private String stationId;
+
+ @CsvField(name="stop_id_0")
+ private String gtfsStopIdDirection0;
+
+ @CsvField(name="stop_id_1")
+ private String gtfsStopIdDirection1;
+
+ @CsvField(name="line")
+ private String line;
+
+ @CsvField(name="stop_name")
+ private String stopName;
+
+ @CsvField(name="daytime_routes")
+ private String daytimeRoutes;
+
+ @CsvField(name="headsign_direction_0", optional = true)
+ private String headsignDirection0;
+
+ @CsvField(name="headsign_direction_1", optional = true)
+ private String headsignDirection1;
+
+ @CsvField(name="notes", ignore=true)
+ private String notes;
+
+ @Override
+ public Integer getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getAgencyId() {
+ return agencyId;
+ }
+
+ public void setAgencyId(String agencyId) {
+ this.agencyId = agencyId;
+ }
+
+ public String getStationId() {
+ return stationId;
+ }
+
+ public void setStationId(String stationId) {
+ this.stationId = stationId;
+ }
+
+ public String getGtfsStopIdDirection0() {
+ return gtfsStopIdDirection0;
+ }
+
+ public void setGtfsStopIdDirection0(String gtfsStopIdDirection0) {
+ this.gtfsStopIdDirection0 = gtfsStopIdDirection0;
+ }
+
+ public String getGtfsStopIdDirection1() {
+ return gtfsStopIdDirection1;
+ }
+
+ public void setGtfsStopIdDirection1(String gtfsStopIdDirection1) {
+ this.gtfsStopIdDirection1 = gtfsStopIdDirection1;
+ }
+
+ public String getLine() {
+ return line;
+ }
+
+ public void setLine(String line) {
+ this.line = line;
+ }
+
+ public String getStopName() {
+ return stopName;
+ }
+
+ public void setStopName(String stopName) {
+ this.stopName = stopName;
+ }
+
+ public String getDaytimeRoutes() {
+ return daytimeRoutes;
+ }
+
+ public void setDaytimeRoutes(String daytimeRoutes) {
+ this.daytimeRoutes = daytimeRoutes;
+ }
+
+ public String getHeadsignDirection0() {
+ return headsignDirection0;
+ }
+
+ public void setHeadsignDirection0(String headsignDirection0) {
+ this.headsignDirection0 = headsignDirection0;
+ }
+
+ public String getHeadsignDirection1() {
+ return headsignDirection1;
+ }
+
+ public void setHeadsignDirection1(String headsignDirection1) {
+ this.headsignDirection1 = headsignDirection1;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Facility.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Facility.java
index 3a6ff1a05..c9c5d5a78 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Facility.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Facility.java
@@ -41,7 +41,7 @@ public class Facility extends IdentityBean{
@CsvField(optional = true)
private String facilityType;
- @CsvField(name = "stop_id", mapping = EntityFieldMappingFactory.class)
+ @CsvField(name = "stop_id", mapping = EntityFieldMappingFactory.class, optional = true)
private Stop stop;
@CsvField(optional = true)
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareLegRule.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareLegRule.java
index 2f30dc25d..620fd6064 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareLegRule.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareLegRule.java
@@ -15,58 +15,64 @@
*/
package org.onebusaway.gtfs.model;
-import java.util.Optional;
import org.onebusaway.csv_entities.schema.annotations.CsvField;
import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory;
import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory;
-import org.onebusaway.gtfs.serialization.mappings.FareProductFieldMappingFactory;
+
+import java.util.Optional;
@CsvFields(filename = "fare_leg_rules.txt", required = false)
public final class FareLegRule extends IdentityBean {
-
- @CsvField(name = "fare_product_id", mapping = FareProductFieldMappingFactory.class)
- private FareProduct fareProduct;
-
- @CsvField(optional = true, name = "leg_group_id")
- private String legGroupId;
+ @CsvField(optional = true, name = "leg_group_id", mapping = DefaultAgencyIdFieldMappingFactory.class)
+ private AgencyAndId legGroupId;
@CsvField(optional = true, name = "network_id")
private String networkId;
- @CsvField(optional = true, name = "from_area_id")
- private String fromAreaId;
+ @CsvField(optional = true, name = "from_area_id", mapping = EntityFieldMappingFactory.class)
+ private Area fromArea;
- @CsvField(optional = true, name = "to_area_id")
- private String toAreaId;
+ @CsvField(optional = true, name = "to_area_id", mapping = EntityFieldMappingFactory.class)
+ private Area toArea;
- @CsvField(name = "fare_container_id", optional = true, mapping = EntityFieldMappingFactory.class)
- private FareContainer fareContainer;
+ @CsvField(name = "min_distance", optional = true)
+ private Double minDistance;
- @CsvField(name = "rider_category_id", optional = true, mapping = EntityFieldMappingFactory.class)
- private RiderCategory riderCategory;
+ @CsvField(name = "max_distance", optional = true)
+ private Double maxDistance;
- public String getLegGroupId() {
+ @CsvField(name = "distance_type", optional = true)
+ private Integer distanceType;
+
+ @CsvField(
+ name = "fare_product_id",
+ mapping = DefaultAgencyIdFieldMappingFactory .class
+ )
+ private AgencyAndId fareProductId;
+
+ public AgencyAndId getLegGroupId() {
return legGroupId;
}
- public void setLegGroupId(String legGroupId) {
+ public void setLegGroupId(AgencyAndId legGroupId) {
this.legGroupId = legGroupId;
}
- public String getFromAreaId() {
- return fromAreaId;
+ public Area getFromArea() {
+ return fromArea;
}
- public void setFromAreaId(String fromAreaId) {
- this.fromAreaId = fromAreaId;
+ public void setFromArea(Area fromArea) {
+ this.fromArea = fromArea;
}
- public String getToAreaId() {
- return toAreaId;
+ public Area getToArea() {
+ return toArea;
}
- public void setToAreaId(String toAreaId) {
- this.toAreaId = toAreaId;
+ public void setToArea(Area toArea) {
+ this.toArea = toArea;
}
public String getNetworkId() {
@@ -79,11 +85,13 @@ public void setNetworkId(String networkId) {
@Override
public String getId() {
- String containerId = Optional.ofNullable(fareContainer).map(c -> c.getId().getId()).orElse(null);
- String categoryId = Optional.ofNullable(riderCategory).map(c -> c.getId().getId()).orElse(null);
+ String fromAreaId = Optional.ofNullable(fromArea).map(Area::getAreaId).orElse(null);
+ String toAreaId = Optional.ofNullable(toArea).map(Area::getAreaId).orElse(null);
+ String baseLegGroupId = Optional.ofNullable(legGroupId).map(AgencyAndId::getId).orElse(null);
+ String baseProductId = Optional.ofNullable(fareProductId).map(AgencyAndId::getId).orElse(null);
return String.format(
- "id=%s|network=%s|fromArea=%s|toArea=%s|container=%s|category=%s",
- fareProduct.getFareProductId().getId(), networkId, fromAreaId, toAreaId, containerId, categoryId
+ "groupId=%s|product=%s|network=%s|fromArea=%s|toArea=%s",
+ baseLegGroupId, baseProductId, networkId, fromAreaId, toAreaId
);
}
@@ -91,27 +99,34 @@ public String getId() {
public void setId(String id) {
}
- public FareProduct getFareProduct() {
- return fareProduct;
+ public AgencyAndId getFareProductId() {
+ return fareProductId;
}
- public void setFareProduct(FareProduct fareProduct) {
- this.fareProduct = fareProduct;
+ public void setFareProductId(AgencyAndId fareProductId) {
+ this.fareProductId = fareProductId;
+ }
+
+ public void setMinDistance(Double minDistance) {
+ this.minDistance = minDistance;
+ }
+ public Double getMinDistance() {
+ return minDistance;
}
- public FareContainer getFareContainer() {
- return fareContainer;
+ public Double getMaxDistance() {
+ return maxDistance;
}
- public void setFareContainer(FareContainer fareContainer) {
- this.fareContainer = fareContainer;
+ public void setMaxDistance(Double maxDistance) {
+ this.maxDistance = maxDistance;
}
- public RiderCategory getRiderCategory() {
- return riderCategory;
+ public Integer getDistanceType() {
+ return distanceType;
}
- public void setRiderCategory(RiderCategory riderCategory) {
- this.riderCategory = riderCategory;
+ public void setDistanceType(Integer distanceType) {
+ this.distanceType = distanceType;
}
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareContainer.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareMedium.java
similarity index 61%
rename from onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareContainer.java
rename to onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareMedium.java
index 9d0bdfc29..70507d6c6 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareContainer.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareMedium.java
@@ -20,53 +20,22 @@
import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory;
import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory;
-/**
- * As of July 2022 this file is not yet part of the main GTFS spec.
- */
-@CsvFields(filename = "fare_containers.txt", required = false)
-public final class FareContainer extends IdentityBean {
+@CsvFields(filename = "fare_media.txt", required = false)
+public final class FareMedium extends IdentityBean {
public static final int MISSING_VALUE = -999;
- @CsvField(name = "fare_container_id", mapping = DefaultAgencyIdFieldMappingFactory.class)
+ @CsvField(name = "fare_media_id", mapping = DefaultAgencyIdFieldMappingFactory.class)
private AgencyAndId id;
- @CsvField(name = "fare_container_name")
- private String name;
- @CsvField(name = "rider_category_id", optional = true, mapping = EntityFieldMappingFactory.class)
- private RiderCategory riderCategory;
-
- @CsvField(optional = true)
- private String currency;
-
- @CsvField(optional = true)
- private float amount = MISSING_VALUE;
-
- @CsvField(optional = true)
- private float minimumInitialPurchase = MISSING_VALUE;
-
- public String getCurrency() {
- return currency;
- }
-
- public void setCurrency(String currency) {
- this.currency = currency;
- }
- public float getAmount() {
- return amount;
- }
-
- public void setAmount(float amount) {
- this.amount = amount;
- }
+ @CsvField(name = "fare_media_name", optional = true)
+ private String name;
- public float getMinimumInitialPurchase() {
- return minimumInitialPurchase;
- }
+ @CsvField
+ private int fareMediaType;
- public void setMinimumInitialPurchase(float minimumInitialPurchase) {
- this.minimumInitialPurchase = minimumInitialPurchase;
- }
+ @CsvField(name = "rider_category_id", optional = true, mapping = EntityFieldMappingFactory.class)
+ private RiderCategory riderCategory;
public String getName() {
return name;
@@ -86,6 +55,14 @@ public void setId(AgencyAndId id) {
this.id = id;
}
+ public int getFareMediaType() {
+ return fareMediaType;
+ }
+
+ public void setFareMediaType(int fareMediaType) {
+ this.fareMediaType = fareMediaType;
+ }
+
public RiderCategory getRiderCategory() {
return riderCategory;
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareProduct.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareProduct.java
index 158c8e0ca..4bd6397c1 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareProduct.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareProduct.java
@@ -32,30 +32,25 @@ public final class FareProduct extends IdentityBean {
private AgencyAndId fareProductId;
@CsvField(optional = true, name = "fare_product_name")
private String name;
- @CsvField(optional = true)
+ @CsvField
private float amount = MISSING_VALUE;
- @CsvField(optional = true)
+ @CsvField
private String currency;
- // not in the main GTFS spec yet (as of June 2022)
@CsvField(optional = true)
private int durationAmount = MISSING_VALUE;
- // not in the main GTFS spec yet (as of June 2022)
@CsvField(optional = true)
private int durationUnit = MISSING_VALUE;
- // not in the main GTFS spec yet (as of June 2022)
@CsvField(optional = true)
private int durationType = MISSING_VALUE;
- // not in the main GTFS spec yet (as of June 2022)
@CsvField(name = "rider_category_id", optional = true, mapping = EntityFieldMappingFactory.class)
private RiderCategory riderCategory;
- // not in the main GTFS spec yet (as of June 2022)
- @CsvField(name = "fare_container_id", optional = true, mapping = EntityFieldMappingFactory.class)
- private FareContainer fareContainer;
+ @CsvField(name = "fare_media_id", optional = true, mapping = EntityFieldMappingFactory.class)
+ private FareMedium fareMedium;
public AgencyAndId getFareProductId() {
return fareProductId;
@@ -67,10 +62,14 @@ public void setFareProductId(AgencyAndId fareProductId) {
public FareContainer getFareContainer() {
return fareContainer;
+
+}
+ public FareMedium getFareMedium() {
+ return fareMedium;
}
- public void setFareContainer(FareContainer fareContainer) {
- this.fareContainer = fareContainer;
+ public void setFareMedium(FareMedium fareMedium) {
+ this.fareMedium = fareMedium;
}
public int getDurationAmount() {
@@ -124,8 +123,13 @@ public void setCurrency(String currency) {
@Override
public AgencyAndId getId() {
String riderCategoryId = Optional.ofNullable(riderCategory).map(c -> c.getId().getId()).orElse(null);
- String fareContainerId = Optional.ofNullable(fareContainer).map(c -> c.getId().getId()).orElse(null);
- return FareProductFieldMappingFactory.fareProductId(fareProductId.getAgencyId(), fareProductId.getId(), riderCategoryId, fareContainerId);
+ String fareMediumId = Optional.ofNullable(fareMedium).map(c -> c.getId().getId()).orElse(null);
+ return FareProductFieldMappingFactory.fareProductId(
+ fareProductId.getAgencyId(),
+ fareProductId.getId(),
+ riderCategoryId,
+ fareMediumId
+ );
}
@Override
@@ -139,4 +143,16 @@ public RiderCategory getRiderCategory() {
public void setRiderCategory(RiderCategory riderCategory) {
this.riderCategory = riderCategory;
}
+
+ public boolean isDurationUnitSet() {
+ return durationUnit != MISSING_VALUE;
+ }
+
+ public boolean isDurationTypeSet() {
+ return durationType != MISSING_VALUE;
+ }
+
+ public boolean isDurationAmountSet() {
+ return durationAmount != MISSING_VALUE;
+ }
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareTransferRule.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareTransferRule.java
index e3154d6ed..76e031693 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareTransferRule.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FareTransferRule.java
@@ -109,4 +109,19 @@ public String getId() {
public void setId(String id) {
}
+ public boolean isTransferCountSet() {
+ return transferCount != MISSING_VALUE;
+ }
+
+ public boolean isDurationLimitSet() {
+ return durationLimit != MISSING_VALUE;
+ }
+
+ public boolean isDurationLimitTypeSet() {
+ return durationLimitType != MISSING_VALUE;
+ }
+
+ public boolean isFareTransferTypeSet() {
+ return fareTransferType != MISSING_VALUE;
+ }
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FeedInfo.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FeedInfo.java
index 9b1404d18..073389103 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FeedInfo.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/FeedInfo.java
@@ -46,6 +46,12 @@ public final class FeedInfo extends IdentityBean {
@CsvField(optional = true, name = "default_lang")
private String defaultLang;
+ @CsvField(optional = true)
+ private String contactEmail;
+
+ @CsvField(optional = true)
+ private String contactUrl;
+
public FeedInfo() {
}
@@ -58,6 +64,8 @@ public FeedInfo(FeedInfo fi) {
this.endDate = fi.endDate;
this.version = fi.version;
this.defaultLang = fi.defaultLang;
+ this.contactEmail = fi.contactEmail;
+ this.contactUrl = fi.contactUrl;
}
public String getPublisherName() {
@@ -116,6 +124,22 @@ public void setDefaultLang(String defaultLang) {
this.defaultLang = defaultLang;
}
+ public String getContactEmail() {
+ return contactEmail;
+ }
+
+ public void setContactEmail(String contactEmail) {
+ this.contactEmail = contactEmail;
+ }
+
+ public String getContactUrl() {
+ return contactUrl;
+ }
+
+ public void setContactUrl(String contactUrl) {
+ this.contactUrl = contactUrl;
+ }
+
/****
* {@link IdentityBean} Interface
****/
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java
index 05cba1665..f12f17c08 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/LocationGroupElement.java
@@ -31,7 +31,7 @@ public class LocationGroupElement extends IdentityBean {
@CsvField(name = "location_group_id", mapping = DefaultAgencyIdFieldMappingFactory.class)
private AgencyAndId locationGroupId;
- @CsvField(name = "location_id", mapping = StopLocationFieldMappingFactory.class)
+ @CsvField(name = "stop_id", mapping = StopLocationFieldMappingFactory.class)
private StopLocation location;
@CsvField(optional = true)
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RiderCategory.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RiderCategory.java
index 4c645eb6e..36b4ab774 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RiderCategory.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RiderCategory.java
@@ -80,4 +80,12 @@ public AgencyAndId getId() {
public void setId(AgencyAndId id) {
this.id = id;
}
+
+ public boolean isMinAgeSet() {
+ return minAge != MISSING_VALUE;
+ }
+
+ public boolean isMaxAgeSet() {
+ return maxAge != MISSING_VALUE;
+ }
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java
index 0912b7fef..b2451d6c9 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Route.java
@@ -56,7 +56,7 @@ public final class Route extends IdentityBean {
@CsvField(name = "network_id", optional = true)
private String networkId;
- @CsvField(name = "eligibility_restricted", optional = true)
+ @CsvField(name = "eligibility_restricted", optional = true, defaultValue = "-999")
private int eligibilityRestricted = MISSING_VALUE;
@Deprecated
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RouteShape.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RouteShape.java
new file mode 100644
index 000000000..62c2c30dd
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RouteShape.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+
+/**
+ * experimental support for canonical/idealized route shapes such
+ * as a map's representation of service
+ */
+@CsvFields(filename = "route_shape.txt", required = false)
+public final class RouteShape extends IdentityBean {
+ private static final long serialVersionUID = 1L;
+
+ @CsvField(ignore = true)
+ private int id;
+
+ @CsvField
+ private String routeId;
+ @CsvField(optional = true)
+ private String directionId;
+ @CsvField
+ private String type;
+ @CsvField
+ private String encodedShape;
+
+ public String getRouteId() {
+ return routeId;
+ }
+
+ public void setRouteId(String routeId) {
+ this.routeId = routeId;
+ }
+
+ public String getDirectionId() {
+ return directionId;
+ }
+
+ public void setDirectionId(String directionId) {
+ this.directionId = directionId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getEncodedShape() {
+ return encodedShape;
+ }
+
+ public void setEncodedShape(String encodedShape) {
+ this.encodedShape = encodedShape;
+ }
+
+ @Override
+ public Integer getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RouteStop.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RouteStop.java
new file mode 100644
index 000000000..44ac241ab
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/RouteStop.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+
+/**
+ * experimental support for canonical/idealized route stops such
+ * as a strip map's representation of service
+ */
+@CsvFields(filename = "route_stop.txt", required = false)
+public final class RouteStop extends IdentityBean {
+
+ private static final long serialVersionUID = 1L;
+
+ @CsvField(ignore = true)
+ private int id;
+
+ @CsvField(name = "route_id")
+ private String routeId;
+
+ @CsvField(name = "stop_id")
+ private String stopId;
+
+ @CsvField(name = "stop_sequence")
+ private int stopSequence;
+
+ @CsvField(optional = true, defaultValue = "")
+ private String directionId;
+
+ @CsvField(optional = true, defaultValue = "")
+ private String name;
+
+ public String getRouteId() {
+ return routeId;
+ }
+
+ public void setRouteId(String routeId) {
+ this.routeId = routeId;
+ }
+
+ public String getStopId() {
+ return stopId;
+ }
+
+ public void setStopId(String stopId) {
+ this.stopId = stopId;
+ }
+
+ public int getStopSequence() {
+ return stopSequence;
+ }
+
+ public void setStopSequence(int stopSequence) {
+ this.stopSequence = stopSequence;
+ }
+
+ public String getDirectionId() {
+ return directionId;
+ }
+
+ public void setDirectionId(String directionId) {
+ this.directionId = directionId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public Integer getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Stop.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Stop.java
index 499e33b70..099943b89 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Stop.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/Stop.java
@@ -96,6 +96,8 @@ public final class Stop extends IdentityBean implements StopLocatio
@CsvField(optional = true, name = "regional_fare_card", defaultValue = "0")
private int regionalFareCardAccepted;
+ @CsvField(optional = true, name = "tts_stop_name")
+ private String ttsStopName;
public Stop() {
@@ -120,6 +122,7 @@ public Stop(Stop obj) {
this.level = obj.level;
this.mtaStopId = obj.mtaStopId;
this.regionalFareCardAccepted = obj.regionalFareCardAccepted;
+ this.ttsStopName = obj.ttsStopName;
}
public AgencyAndId getId() {
@@ -290,4 +293,12 @@ public int getRegionalFareCardAccepted() {
public void setRegionalFareCardAccepted(int regionalFareCardAccepted) {
this.regionalFareCardAccepted = regionalFareCardAccepted;
}
+
+ public String getTtsStopName() {
+ return ttsStopName;
+ }
+
+ public void setTtsStopName(String ttsStopName) {
+ this.ttsStopName = ttsStopName;
+ }
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java
index a81576cfd..2d2d74a64 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopArea.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2022 Leonard Ehrenfried
+ * Copyright (C) 2023 Leonard Ehrenfried
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,40 +15,48 @@
*/
package org.onebusaway.gtfs.model;
-import org.onebusaway.csv_entities.schema.annotations.CsvField;
-import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+import java.util.HashSet;
+import java.util.Set;
-@CsvFields(filename = "stop_areas.txt", required = false)
-public final class StopArea extends IdentityBean {
+public class StopArea extends IdentityBean implements StopLocation {
- @CsvField(name = "area_id")
- private String areaId;
- @CsvField(name = "stop_id")
- private String stopId;
+ private static final long serialVersionUID = 1L;
- public String getAreaId() {
- return areaId;
+ private Area area;
+
+ private Set stops = new HashSet<>();
+
+ @Override
+ public AgencyAndId getId() {
+ return area.getId();
}
- public void setAreaId(String areaId) {
- this.areaId = areaId;
+ @Override
+ public void setId(AgencyAndId id) {
}
- public String getStopId() {
- return stopId;
+ public void setArea(Area area) {
+ this.area = area;
}
- public void setStopId(String stopId) {
- this.stopId = stopId;
+ public Set getLocations() {
+ return stops;
}
- @Override
- public String getId() {
- return String.format("%s_%s", areaId, stopId);
+ private void setLocations(Set stops) {
+ this.stops = stops;
+ }
+
+ public void addLocation(StopLocation location) {
+ this.stops.add(location);
+ }
+
+ public String getName() {
+ return area.getName();
}
@Override
- public void setId(String id) {
+ public void setName(String name) {
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java
new file mode 100644
index 000000000..b0ee1cf61
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopAreaElement.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2022 Leonard Ehrenfried
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory;
+import org.onebusaway.gtfs.serialization.mappings.StopLocationFieldMappingFactory;
+
+@CsvFields(filename = "stop_areas.txt", required = false)
+public final class StopAreaElement extends IdentityBean {
+
+ @CsvField(name = "area_id", mapping = EntityFieldMappingFactory.class)
+ private Area area;
+ @CsvField(name = "stop_id", mapping = StopLocationFieldMappingFactory.class)
+ private StopLocation stopLocation;
+
+ public void setArea(Area area) {
+ this.area = area;
+ }
+
+ public Area getArea() {
+ return area;
+ }
+
+ @Override
+ public AgencyAndId getId() {
+ return new AgencyAndId(getArea().getId().getAgencyId(), String.format("%s_%s", area.getId().getId(), stopLocation.getId().getId()));
+ }
+
+ @Override
+ public void setId(AgencyAndId id) {
+
+ }
+
+ public void setStopLocation(StopLocation stopLocation) {
+ this.stopLocation = stopLocation;
+ }
+
+ public StopLocation getStopLocation() {
+ return stopLocation;
+ }
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java
index a774ce25a..7e9bb9a72 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTime.java
@@ -21,11 +21,15 @@
import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory;
import org.onebusaway.gtfs.serialization.mappings.StopTimeFieldMappingFactory;
import org.onebusaway.gtfs.serialization.mappings.StopLocationFieldMappingFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@CsvFields(filename = "stop_times.txt")
public final class StopTime extends IdentityBean implements
Comparable, StopTimeProxy {
+ private static Logger _log = LoggerFactory.getLogger(StopTime.class);
+
private static final long serialVersionUID =2L;
public static final int MISSING_VALUE = -999;
@@ -38,9 +42,19 @@ public final class StopTime extends IdentityBean implements
@CsvField(name = "trip_id", mapping = EntityFieldMappingFactory.class)
private Trip trip;
- @CsvField(name = "stop_id", mapping = StopLocationFieldMappingFactory.class)
+ /**
+ * This is optional because in flex you can also have location_id and location_group_id.
+ */
+ @CsvField(name = "stop_id", optional = true, mapping = StopLocationFieldMappingFactory.class)
private StopLocation stop;
+ @CsvField(name = "location_id", optional = true, mapping = StopLocationFieldMappingFactory.class)
+ private StopLocation location;
+
+ @CsvField(name = "location_group_id", optional = true, mapping = StopLocationFieldMappingFactory.class)
+ private StopLocation locationGroup;
+
+
@CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class)
private int arrivalTime = MISSING_VALUE;
@@ -52,24 +66,40 @@ public final class StopTime extends IdentityBean implements
* GTFS-Flex v2.1 renamed this field. Use {@link #startPickupDropOffWindow} instead.
*/
@Deprecated
- @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class)
+ @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999")
private int minArrivalTime = MISSING_VALUE;
- @CsvField(optional = true, name = "start_pickup_dropoff_window", mapping = StopTimeFieldMappingFactory.class)
+ @CsvField(optional = true, name = "start_pickup_drop_off_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999")
private int startPickupDropOffWindow = MISSING_VALUE;
+ /**
+ * @deprecated
+ * GTFS-Flex v2.1 renamed "dropoff" to "drop off": https://github.com/MobilityData/gtfs-flex/commit/547200dfb580771265ae14b07d9bfd7b91c16ed2
+ */
+ @Deprecated
+ @CsvField(optional = true, name = "start_pickup_dropoff_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999")
+ public int oldSpellingOfStartPickupDropOffWindow = MISSING_VALUE;
+
/**
* @deprecated
* GTFS-Flex v2.1 renamed this field. Use {@link #endPickupDropOffWindow} instead.
*/
@Deprecated
- @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class)
+ @CsvField(optional = true, mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999")
private int maxDepartureTime = MISSING_VALUE;
- @CsvField(optional = true, name = "end_pickup_dropoff_window", mapping = StopTimeFieldMappingFactory.class)
+ @CsvField(optional = true, name = "end_pickup_drop_off_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999")
private int endPickupDropOffWindow = MISSING_VALUE;
- @CsvField(optional = true)
+ /**
+ * @deprecated
+ * GTFS-Flex v2.1 renamed "dropoff" to "drop off": https://github.com/MobilityData/gtfs-flex/commit/547200dfb580771265ae14b07d9bfd7b91c16ed2
+ */
+ @Deprecated
+ @CsvField(optional = true, name = "end_pickup_dropoff_window", mapping = StopTimeFieldMappingFactory.class, defaultValue = "-999")
+ public int oldSpellingOfEndPickupDropOffWindow = MISSING_VALUE;
+
+ @CsvField(optional = true, defaultValue = "-999")
private int timepoint = MISSING_VALUE;
private int stopSequence;
@@ -89,13 +119,13 @@ public final class StopTime extends IdentityBean implements
@CsvField(optional = true, defaultValue = "0")
private int dropOffType;
- @CsvField(optional = true)
+ @CsvField(optional = true, defaultValue = "-999")
private double shapeDistTraveled = MISSING_VALUE;
- @CsvField(optional = true)
+ @CsvField(optional = true, defaultValue = "1")
private int continuousPickup = MISSING_FLEX_VALUE;
- @CsvField(optional = true)
+ @CsvField(optional = true, defaultValue = "1")
private int continuousDropOff = MISSING_FLEX_VALUE;
@CsvField(optional = true, name = "start_service_area_id", mapping = EntityFieldMappingFactory.class, order = -2)
@@ -104,10 +134,10 @@ public final class StopTime extends IdentityBean implements
@CsvField(optional = true, name = "end_service_area_id", mapping = EntityFieldMappingFactory.class, order = -2)
private Area endServiceArea;
- @CsvField(optional = true)
+ @CsvField(optional = true, defaultValue = "-999.0")/*note defaultValue quirk for non-proxied comparison*/
private double startServiceAreaRadius = MISSING_VALUE;
- @CsvField(optional = true)
+ @CsvField(optional = true, defaultValue = "-999.0")/*note defaultValue quirk for non-proxied comparison*/
private double endServiceAreaRadius = MISSING_VALUE;
@CsvField(ignore = true)
@@ -125,7 +155,7 @@ public final class StopTime extends IdentityBean implements
private String farePeriodId;
/** Extension to support departure buffer https://groups.google.com/forum/#!msg/gtfs-changes/sHTyliLgMQk/gfpaGkI_AgAJ */
- @CsvField(optional = true, defaultValue = "-1")
+ @CsvField(optional = true, defaultValue = "-999")
private int departureBuffer;
/** Support track extension */
@@ -137,16 +167,16 @@ public final class StopTime extends IdentityBean implements
private Note note;
// See https://github.com/MobilityData/gtfs-flex/blob/master/spec/reference.md
- @CsvField(optional = true, name = "mean_duration_factor")
+ @CsvField(optional = true, name = "mean_duration_factor", defaultValue = "-999.0")/*note defaultValue quirk for non-proxied comparison*/
private double meanDurationFactor = MISSING_VALUE;
- @CsvField(optional = true, name = "mean_duration_offset")
+ @CsvField(optional = true, name = "mean_duration_offset", defaultValue = "-999.0")/*note defaultValue quirk for non-proxied comparison*/
private double meanDurationOffset = MISSING_VALUE;
- @CsvField(optional = true, name = "safe_duration_factor")
+ @CsvField(optional = true, name = "safe_duration_factor", defaultValue = "-999.0")/*note defaultValue quirk for non-proxied comparison*/
private double safeDurationFactor = MISSING_VALUE;
- @CsvField(optional = true, name = "safe_duration_offset")
+ @CsvField(optional = true, name = "safe_duration_offset", defaultValue = "-999.0")
private double safeDurationOffset = MISSING_VALUE;
@CsvField(optional = true, name = "free_running_flag")
@@ -172,6 +202,8 @@ public StopTime(StopTime st) {
this.shapeDistTraveled = st.shapeDistTraveled;
this.farePeriodId = st.farePeriodId;
this.stop = st.stop;
+ this.location = st.location;
+ this.locationGroup = st.locationGroup;
this.stopHeadsign = st.stopHeadsign;
this.stopSequence = st.stopSequence;
this.toStopSequence = st.toStopSequence;
@@ -246,6 +278,7 @@ public void setToStopSequence(Integer toStopSequence) {
this.toStopSequence = toStopSequence;
}
+ @Override
public StopLocation getStop() {
if (proxy != null) {
return proxy.getStop();
@@ -253,6 +286,41 @@ public StopLocation getStop() {
return stop;
}
+ @Override
+ public StopLocation getLocation() {
+ if (proxy != null) {
+ return proxy.getLocation();
+ }
+ return location;
+ }
+
+ @Override
+ public StopLocation getLocationGroup() {
+ if (proxy != null) {
+ return proxy.getLocationGroup();
+ }
+ return locationGroup;
+ }
+
+ /**
+ * Returns possible entity for the stop location in this order:
+ * - stop
+ * - location
+ * - location group
+ */
+ public StopLocation getStopLocation(){
+ if(stop != null){
+ return stop;
+ }
+ else if(location != null) {
+ return location;
+ }
+ else if(locationGroup != null){
+ return locationGroup;
+ }
+ return null;
+ }
+
public void setStop(StopLocation stop) {
if (proxy != null) {
proxy.setStop(stop);
@@ -261,6 +329,22 @@ public void setStop(StopLocation stop) {
this.stop = stop;
}
+ public void setLocation(StopLocation location) {
+ if (proxy != null) {
+ proxy.setLocation(location);
+ return;
+ }
+ this.location = location;
+ }
+
+ public void setLocationGroup(StopLocation group) {
+ if (proxy != null) {
+ proxy.setLocationGroup(group);
+ return;
+ }
+ this.locationGroup = group;
+ }
+
public boolean isArrivalTimeSet() {
if (proxy != null) {
return proxy.isArrivalTimeSet();
@@ -340,6 +424,8 @@ public void setMinArrivalTime(int minArrivalTime) {
public int getStartPickupDropOffWindow() {
if (startPickupDropOffWindow != MISSING_VALUE) {
return startPickupDropOffWindow;
+ } else if(oldSpellingOfStartPickupDropOffWindow != MISSING_VALUE){
+ return oldSpellingOfStartPickupDropOffWindow;
} else {
return minArrivalTime;
}
@@ -362,7 +448,11 @@ public void setMaxDepartureTime(int maxDepartureTime) {
public int getEndPickupDropOffWindow() {
if (endPickupDropOffWindow != MISSING_VALUE) {
return endPickupDropOffWindow;
- } else {
+ }
+ else if (oldSpellingOfEndPickupDropOffWindow != MISSING_VALUE) {
+ return oldSpellingOfEndPickupDropOffWindow;
+ }
+ else {
return maxDepartureTime;
}
}
@@ -648,7 +738,7 @@ public String displayArrival() {
@Override
public String toString() {
- return "StopTime(seq=" + getStopSequence() + " stop=" + (getStop()==null?"NuLl":getStop().getId())
+ return "StopTime(seq=" + getStopSequence() + " stop=" + (getStopLocation()==null?"NuLl":getStop().getId())
+ " trip=" + (getTrip()==null?"NuLl":getTrip().getId()) + " times="
+ StopTimeFieldMappingFactory.getSecondsAsString(getArrivalTime())
+ "-"
@@ -716,4 +806,30 @@ public void setFreeRunningFlag(String freeRunningFlag) {
}
this.freeRunningFlag = freeRunningFlag;
}
+ @Deprecated
+ public void setOldSpellingOfStartPickupDropOffWindow(int time) {
+ oldDropOffSpellingWarning("start");
+ this.oldSpellingOfStartPickupDropOffWindow = time;
+ }
+
+ @Deprecated
+ public void setOldSpellingOfEndPickupDropOffWindow(int time) {
+ oldDropOffSpellingWarning("end");
+ this.oldSpellingOfEndPickupDropOffWindow = time;
+ }
+
+ private static void oldDropOffSpellingWarning(String type) {
+ _log.warn("This feed uses the old spelling of '{}_pickup_drop_off_window' ('dropoff' instead of 'drop_off'). "
+ + "Compatibility will be removed in the future, so please update your feed to be in line with the latest Flex V2 spec:"
+ + " https://github.com/MobilityData/gtfs-flex/commit/547200dfb", type);
+ }
+ @Deprecated
+ public int getOldSpellingOfStartPickupDropOffWindow() {
+ return this.oldSpellingOfStartPickupDropOffWindow;
+ }
+
+ @Deprecated
+ public int getOldSpellingOfEndPickupDropOffWindow() {
+ return oldSpellingOfEndPickupDropOffWindow;
+ }
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTimeProxy.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTimeProxy.java
index 8d8b29179..21f8f5251 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTimeProxy.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/StopTimeProxy.java
@@ -45,8 +45,15 @@ public interface StopTimeProxy {
public StopLocation getStop();
+ public StopLocation getLocation();
+ public StopLocation getLocationGroup();
+
public void setStop(StopLocation stop);
+ public void setLocation(StopLocation stop);
+
+ public void setLocationGroup(StopLocation stop);
+
public boolean isArrivalTimeSet();
public int getArrivalTime();
@@ -122,4 +129,5 @@ public interface StopTimeProxy {
public String getFreeRunningFlag();
public void setFreeRunningFlag(String freeRunningFlag);
+
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/WrongWayConcurrency.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/WrongWayConcurrency.java
new file mode 100644
index 000000000..5d99702c5
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/WrongWayConcurrency.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (C) 2023 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model;
+
+import org.onebusaway.csv_entities.schema.annotations.CsvField;
+import org.onebusaway.csv_entities.schema.annotations.CsvFields;
+import org.onebusaway.gtfs.serialization.mappings.DefaultAgencyIdFieldMappingFactory;
+import org.onebusaway.gtfs.serialization.mappings.EntityFieldMappingFactory;
+
+/**
+ * An GTFS extension that allows for re-mapping of route + direction + stop
+ * tuples to a replacement stop. The specific use case example is for
+ * Subway real-time service that reports an invalid stop and needs to be
+ * corrected to an appropriate stop. Not to be confused with stop consolidation!
+ */
+@CsvFields(filename = "wrong_way_concurrencies.txt", required = false)
+public class WrongWayConcurrency extends IdentityBean {
+
+
+ @CsvField(ignore = true)
+ private int id;
+ @CsvField(name = "route_id")
+ private String routeId;
+ @CsvField(name = "direction_id")
+ private String directionId;
+ @CsvField(name = "from_stop_id", mapping = DefaultAgencyIdFieldMappingFactory.class)
+ private AgencyAndId fromStopId;
+ @CsvField(name = "to_stop_id", mapping = DefaultAgencyIdFieldMappingFactory.class)
+ private AgencyAndId toStopId;
+
+ @Override
+ public Integer getId() {
+ return id;
+ }
+
+ @Override
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getRouteId() {
+ return routeId;
+ }
+
+ public void setRouteId(String routeId) {
+ this.routeId = routeId;
+ }
+
+ public String getDirectionId() {
+ return directionId;
+ }
+
+ public void setDirectionId(String directionId) {
+ this.directionId = directionId;
+ }
+
+ public AgencyAndId getFromStopId() {
+ return fromStopId;
+ }
+
+ public void setFromStopId(AgencyAndId fromStopId) {
+ this.fromStopId = fromStopId;
+ }
+
+ public AgencyAndId getToStopId() {
+ return toStopId;
+ }
+
+ public void setToStopId(AgencyAndId toStopId) {
+ this.toStopId = toStopId;
+ }
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/calendar/AgencyServiceInterval.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/calendar/AgencyServiceInterval.java
new file mode 100644
index 000000000..4d10844d3
--- /dev/null
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/model/calendar/AgencyServiceInterval.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright (C) 2024 Cambridge Systematics, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onebusaway.gtfs.model.calendar;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represent a service interval (a period of defined transit both scheduled and dynamic)
+ * that exists on a given service date and window relative to that date. The times may differ
+ * based on the specified agency. The net effect is that the period of consideration can
+ * be smaller for more frequent service (such as a subway/BRT provider) vs a traditional
+ * fixed bus service.
+ */
+public class AgencyServiceInterval implements Serializable {
+
+ public static final int SECONDS_IN_DAY = 24 * 60 * 60;
+
+ private final long _referenceTime;
+ private final ServiceDate _serviceDate;
+
+ /**
+ * Map of overrides that have a different window of applicability of service relative
+ * to the reference time. The default is the entire service date. Values should be
+ * AGENCY_ID, MINUTES_AFTER_REFERENCE_TIME.
+ *
+ */
+ private final Map _overridesByAgencyId = new HashMap<>();
+
+
+ public AgencyServiceInterval(long referenceTime) {
+ _referenceTime = referenceTime;
+ _serviceDate = new ServiceDate(new Date(referenceTime));
+ }
+
+ public AgencyServiceInterval(ServiceDate serviceDate) {
+ _referenceTime = serviceDate.getAsDate().getTime();
+ _serviceDate = serviceDate;
+ }
+
+ public AgencyServiceInterval(long referenceTime, Map agencyIdOverrides) {
+ _referenceTime = referenceTime;
+ _serviceDate = new ServiceDate(new Date(referenceTime));
+ if (agencyIdOverrides != null)
+ _overridesByAgencyId.putAll(agencyIdOverrides);
+ }
+
+ public ServiceDate getServiceDate() {
+ return _serviceDate;
+ }
+
+ public ServiceInterval getServiceInterval(String agencyId) {
+
+ if (_overridesByAgencyId.containsKey(agencyId)) {
+ // override will be referenceTime, referenceTime+window (in minutes)
+ ServiceDate serviceDate = new ServiceDate(new Date(_referenceTime));
+ int startSecondsIntoDay = Math.toIntExact(_referenceTime - serviceDate.getAsDate().getTime()) / 1000;
+ int endSecondsIntoDay = startSecondsIntoDay + (_overridesByAgencyId.get(agencyId) * 60);
+ return new ServiceInterval(startSecondsIntoDay, endSecondsIntoDay);
+ }
+ // default will be 0, endOfDay (aka entire service day)
+ return new ServiceInterval(0, SECONDS_IN_DAY);
+ }
+ public Date getFrom(String agencyId) {
+ if (_overridesByAgencyId.containsKey(agencyId))
+ return new Date(_referenceTime);
+ return new Date(_referenceTime);
+ }
+
+ public Date getTo(String agencyId) {
+ if (_overridesByAgencyId.containsKey(agencyId))
+ return new Date(_referenceTime + _overridesByAgencyId.get(agencyId) * 60 * 1000);
+ return endOfDay(_serviceDate);
+
+ }
+
+ private Date endOfDay(ServiceDate serviceDate) {
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(serviceDate.getAsDate());
+ cal.set(Calendar.HOUR_OF_DAY, 23);
+ cal.set(Calendar.MINUTE, 59);
+ cal.set(Calendar.SECOND, 59);
+ cal.set(Calendar.MILLISECOND, 999);
+ return cal.getTime();
+ }
+ private Date startOfDay(ServiceDate serviceDate) {
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(serviceDate.getAsDate());
+ cal.set(Calendar.HOUR_OF_DAY, 0);
+ cal.set(Calendar.MINUTE, 0);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 000);
+ return cal.getTime();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof AgencyServiceInterval)) {
+ return false;
+ }
+ AgencyServiceInterval that = (AgencyServiceInterval) other;
+ return that._referenceTime == _referenceTime;
+ }
+
+ @Override
+ public int hashCode() {
+ return new Long(_referenceTime).hashCode();
+ }
+}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java
index 3bd9fd0dd..fa954a31e 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsEntitySchemaFactory.java
@@ -42,9 +42,11 @@ public static List> getEntityClasses() {
entityClasses.add(BookingRule.class);
entityClasses.add(ShapePoint.class);
entityClasses.add(Route.class);
+ entityClasses.add(RouteStop.class);
+ entityClasses.add(RouteShape.class);
entityClasses.add(Level.class);
entityClasses.add(Stop.class);
- entityClasses.add(StopArea.class);
+ entityClasses.add(StopAreaElement.class);
entityClasses.add(LocationGroupElement.class);
entityClasses.add(Trip.class);
entityClasses.add(Note.class);
@@ -52,7 +54,7 @@ public static List> getEntityClasses() {
entityClasses.add(ServiceCalendar.class);
entityClasses.add(ServiceCalendarDate.class);
entityClasses.add(RiderCategory.class);
- entityClasses.add(FareContainer.class);
+ entityClasses.add(FareMedium.class);
entityClasses.add(FareProduct.class);
entityClasses.add(FareLegRule.class);
entityClasses.add(FareAttribute.class);
@@ -71,6 +73,9 @@ public static List> getEntityClasses() {
entityClasses.add(DirectionNameException.class);
entityClasses.add(Zone.class);
entityClasses.add(Node.class);
+ entityClasses.add(WrongWayConcurrency.class);
+ entityClasses.add(DirectionEntry.class);
+ entityClasses.add(AlternateStopNameException.class);
return entityClasses;
}
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java
index 0b57455be..14c22680f 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsReader.java
@@ -16,13 +16,10 @@
*/
package org.onebusaway.gtfs.serialization;
-import java.io.IOException;
-import java.io.Reader;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.util.*;
import org.onebusaway.csv_entities.CsvEntityContext;
import org.onebusaway.csv_entities.CsvEntityReader;
@@ -32,6 +29,7 @@
import org.onebusaway.csv_entities.exceptions.CsvEntityIOException;
import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory;
import org.onebusaway.gtfs.impl.GtfsDaoImpl;
+import org.onebusaway.gtfs.impl.ZipHandler;
import org.onebusaway.gtfs.model.*;
import org.onebusaway.gtfs.services.GenericMutableDao;
import org.slf4j.Logger;
@@ -60,6 +58,8 @@ public class GtfsReader extends CsvEntityReader {
private boolean _overwriteDuplicates = false;
+ private File _inputLocation = null;
+
public GtfsReader() {
_entityClasses.add(Agency.class);
@@ -69,18 +69,20 @@ public GtfsReader() {
_entityClasses.add(Area.class);
_entityClasses.add(BookingRule.class);
_entityClasses.add(Route.class);
+ _entityClasses.add(RouteStop.class);
+ _entityClasses.add(RouteShape.class);
_entityClasses.add(Level.class);
_entityClasses.add(Stop.class);
_entityClasses.add(Location.class);
_entityClasses.add(LocationGroupElement.class);
_entityClasses.add(Trip.class);
- _entityClasses.add(StopArea.class);
+ _entityClasses.add(StopAreaElement.class);
_entityClasses.add(StopTime.class);
_entityClasses.add(ServiceCalendar.class);
_entityClasses.add(Zone.class);
_entityClasses.add(ServiceCalendarDate.class);
_entityClasses.add(RiderCategory.class);
- _entityClasses.add(FareContainer.class);
+ _entityClasses.add(FareMedium.class);
_entityClasses.add(FareProduct.class);
_entityClasses.add(FareLegRule.class);
_entityClasses.add(FareAttribute.class);
@@ -99,6 +101,9 @@ public GtfsReader() {
_entityClasses.add(FacilityProperty.class);
_entityClasses.add(RouteNameException.class);
_entityClasses.add(DirectionNameException.class);
+ _entityClasses.add(WrongWayConcurrency.class);
+ _entityClasses.add(DirectionEntry.class);
+ _entityClasses.add(AlternateStopNameException.class);
CsvTokenizerStrategy tokenizerStrategy = new CsvTokenizerStrategy();
tokenizerStrategy.getCsvParser().setTrimInitialWhitespace(true);
@@ -118,6 +123,11 @@ public GtfsReader() {
addEntityHandler(new EntityHandlerImpl());
}
+ public void setInputLocation(File path) throws IOException {
+ super.setInputLocation(path);
+ _inputLocation = path;
+ }
+
public void setLastModifiedTime(Long lastModifiedTime) {
if (lastModifiedTime != null)
getContext().put("lastModifiedTime", lastModifiedTime);
@@ -202,6 +212,54 @@ public void run(CsvInputSource source) throws IOException {
}
_entityStore.close();
+
+ // support metadata files that are not CSV
+ // but only if we have a GtfsDao
+ if (_entityStore instanceof GtfsDaoImpl) {
+ List filenames = ((GtfsDaoImpl) _entityStore).getOptionalMetadataFilenames();
+ if (filenames != null) {
+ for (String metaFile : filenames) {
+ if (source.hasResource(metaFile)) {
+ _log.info("reading metadata file: " + metaFile);
+ ((GtfsDaoImpl) _entityStore).addMetadata(metaFile, readContent(_inputLocation, metaFile));
+ }
+ }
+ }
+ }
+ }
+
+ private String readContent(File inputLocation, String filename) {
+ if (inputLocation.getAbsoluteFile().getName().endsWith(".zip")) {
+ // zip file
+ return readContentFromZip(inputLocation,
+ filename);
+ } else {
+ // file in directory
+ return readContentFromFile(new File(inputLocation.getAbsolutePath()
+ + File.separator
+ + filename));
+ }
+ }
+
+ private String readContentFromFile(File filePath) {
+ StringBuffer sb = new StringBuffer();
+ try {
+ byte[] bytes = Files.readAllBytes(filePath.toPath());
+ sb.append(new String(bytes, StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ System.err.println("issue reading content from " + filePath);
+ }
+ return sb.toString();
+ }
+
+ private String readContentFromZip(File zipFilePath, String zipEntryName) {
+ try {
+ ZipHandler zip = new ZipHandler(zipFilePath);
+ return zip.readTextFromFile(zipEntryName);
+ } catch (IOException e) {
+ System.err.println("issue reading content from " + zipFilePath + ":" + zipEntryName);
+ }
+ return null;
}
/****
@@ -285,9 +343,9 @@ public void handleEntity(Object entity) {
} else if (entity instanceof FareProduct) {
FareProduct product = (FareProduct) entity;
registerAgencyId(FareProduct.class, product.getId());
- } else if (entity instanceof FareContainer) {
- FareContainer container = (FareContainer) entity;
- registerAgencyId(FareContainer.class, container.getId());
+ } else if (entity instanceof FareMedium) {
+ FareMedium medium = (FareMedium) entity;
+ registerAgencyId(FareMedium.class, medium.getId());
} else if (entity instanceof RiderCategory) {
RiderCategory category = (RiderCategory) entity;
registerAgencyId(RiderCategory.class, category.getId());
@@ -300,6 +358,7 @@ public void handleEntity(Object entity) {
} else if (entity instanceof Area) {
Area area = (Area) entity;
registerAgencyId(Area.class, area.getId());
+
} else if (entity instanceof Location) {
Location location = (Location) entity;
registerAgencyId(Location.class, location.getId());
@@ -313,6 +372,15 @@ public void handleEntity(Object entity) {
_entityStore.saveEntity(locationGroup);
}
locationGroup.addLocation(locationGroupElement.getLocation());
+ } else if (entity instanceof StopAreaElement) {
+ var stopAreaElement = (StopAreaElement) entity;
+ var stopArea = _entityStore.getEntityForId(StopArea.class, stopAreaElement.getArea().getId());
+ if (stopArea == null) {
+ stopArea = new StopArea();
+ stopArea.setArea(stopAreaElement.getArea());
+ _entityStore.saveEntity(stopArea);
+ }
+ stopArea.addLocation(stopAreaElement.getStopLocation());
} else if (entity instanceof Vehicle) {
Vehicle vehicle = (Vehicle) entity;
registerAgencyId(Vehicle.class, vehicle.getId());
diff --git a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsWriter.java b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsWriter.java
index 9e807e3f9..174805354 100644
--- a/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsWriter.java
+++ b/onebusaway-gtfs/src/main/java/org/onebusaway/gtfs/serialization/GtfsWriter.java
@@ -15,6 +15,8 @@
*/
package org.onebusaway.gtfs.serialization;
+import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -23,9 +25,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
import org.onebusaway.csv_entities.CsvEntityWriter;
import org.onebusaway.csv_entities.schema.DefaultEntitySchemaFactory;
+import org.onebusaway.gtfs.impl.ZipHandler;
import org.onebusaway.gtfs.services.GtfsDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,6 +41,13 @@ public class GtfsWriter extends CsvEntityWriter {
private List> _entityClasses = new ArrayList>();
+ private File _outputLocation = null;
+
+ public void setOutputLocation(File path) {
+ super.setOutputLocation(path);
+ _outputLocation = path;
+ }
+
private Map, Comparator>> _entityComparators = new HashMap, Comparator>>();
public GtfsWriter() {
@@ -75,8 +84,47 @@ public void run(GtfsDao dao) throws IOException {
}
close();
+
+ // now copy any metadata files
+ List filenames = dao.getOptionalMetadataFilenames();
+ for (String metadataFile : filenames) {
+ if (dao.hasMetadata(metadataFile)) {
+ _log.info("writing metadata file : " + metadataFile);
+ writeContent(metadataFile, dao.getMetadata(metadataFile));
+ }
+ }
+ }
+
+ private void writeContent(String srcFilename, String content) {
+ if (content == null) {
+ return;
+ }
+ // outputLocation may be a zip file!
+ if (_outputLocation.getName().endsWith(".zip")) {
+ copyToZipFile(srcFilename, content);
+ } else {
+ try {
+ String location = _outputLocation.getAbsolutePath() + File.separator + srcFilename;
+ FileWriter fw = new FileWriter(location);
+ fw.write(content);
+ fw.close();
+ } catch (IOException e) {
+ // don't let metadata issue kill the entire process
+ System.err.println("issue copying metadata: "+ e);
+ }
+ }
}
+ private void copyToZipFile(String srcFilename, String content) {
+ try {
+ new ZipHandler(_outputLocation).writeTextToFile(srcFilename, content);
+ } catch (IOException e) {
+ // don't let metadata issue kill the entire process
+ System.err.println("issue copying metadata to zipfile: "+ e);
+ }
+ }
+
+
@SuppressWarnings("unchecked")
private Collection
org.onebusaway
onebusaway-collections
${onebusaway_collections_version}
+
+ org.slf4j
+ slf4j
+ ${slf4j_version}
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j_version}
+
org.slf4j
- slf4j-api
- 1.7.5
-
-
- org.slf4j
- slf4j-log4j12
- 1.7.5
+ slf4j-simple
+ ${slf4j_version}
junit
@@ -122,72 +135,89 @@
1.8.0
test
-
- javax.xml.bind
- jaxb-api
- 2.3.0
-
-
- com.sun.xml.bind
- jaxb-core
- 2.3.0
-
-
- com.sun.xml.bind
- jaxb-impl
- 2.3.0
-
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+
+
+ com.sun.xml.bind
+ jaxb-core
+ 2.3.0
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ 2.3.0
+
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 3.0.1
-
-
- none
-
- false
-
-
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- 1.8
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- 3.0.1
-
-
- com.mycila
- license-maven-plugin
-
-
-
- **/ci.yml
-
-
-
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 3.4.2
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.4.1
+
+
+ -Xdoclint:none
+
+ false
+
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.10.1
+
+
+ 11
+
+
+
+ org.apache.maven.plugins
+ maven-site-plugin
+ 3.12.1
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.0.1
+
+
+ com.mycila
+ license-maven-plugin
+
+
+
+ **/ci.yml
+ **/simplelogger.properties
+
+
+
+
+
diff --git a/src/site/site.xml b/src/site/site.xml
index 84be4e4cc..05d5ad552 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -1,12 +1,12 @@
-
+
-
+