Skip to content

Commit

Permalink
feat: changes + added documentation to features (#1844)
Browse files Browse the repository at this point in the history
  • Loading branch information
cka-y authored Oct 1, 2024
1 parent ed02ed1 commit be675f3
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Set;
import java.util.stream.Collectors;
import org.mobilitydata.gtfsvalidator.report.model.AgencyMetadata;
import org.mobilitydata.gtfsvalidator.report.model.FeatureMetadata;
import org.mobilitydata.gtfsvalidator.report.model.FeedMetadata;
import org.mobilitydata.gtfsvalidator.runner.ValidationRunnerConfig;
import org.mobilitydata.gtfsvalidator.util.VersionInfo;
Expand Down Expand Up @@ -97,6 +98,7 @@ public JsonReportSummary(
: feedMetadata.specFeatures.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.map(FeatureMetadata::getFeatureName)
.collect(Collectors.toList());
} else {
logger.atSevere().log(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.mobilitydata.gtfsvalidator.report.model;

import java.util.Objects;

public class FeatureMetadata {
private final String featureName;
private final String featureGroup;
private static final String BASE_DOC_URL = "https://gtfs.org/getting_started/features/";

public FeatureMetadata(String featureName, String featureGroup) {
this.featureName = featureName;
this.featureGroup = featureGroup != null ? featureGroup : "base_add-ons";
}

public String getFeatureName() {
return featureName;
}

public String getDocUrl() {
String formattedFeatureName = featureName.toLowerCase().replace(' ', '-');
String formattedFeatureGroup = featureGroup.toLowerCase().replace(' ', '_');
return BASE_DOC_URL + formattedFeatureGroup + "/#" + formattedFeatureName;
}

// Override equals and hashCode to use featureName as key
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FeatureMetadata that = (FeatureMetadata) o;
return Objects.equals(featureName, that.featureName);
}

@Override
public int hashCode() {
return Objects.hash(featureName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,36 @@ public class FeedMetadata {

public Map<String, String> feedInfo = new LinkedHashMap<>();

public Map<String, Boolean> specFeatures = new LinkedHashMap<>();
public Map<FeatureMetadata, Boolean> specFeatures = new LinkedHashMap<>();

public ArrayList<AgencyMetadata> agencies = new ArrayList<>();
private ImmutableSortedSet<String> filenames;

public double validationTimeSeconds;

// List of features that only require checking the presence of one record in the file.
private final List<Pair<String, String>> FILE_BASED_FEATURES =
private final List<Pair<FeatureMetadata, String>> FILE_BASED_FEATURES =
List.of(
new Pair<>("Pathways (basic)", GtfsPathway.FILENAME),
new Pair<>("Transfers", GtfsTransfer.FILENAME),
new Pair<>("Fares V1", GtfsFareAttribute.FILENAME),
new Pair<>("Fare Products", GtfsFareProduct.FILENAME),
new Pair<>("Shapes", GtfsShape.FILENAME),
new Pair<>("Frequencies", GtfsFrequency.FILENAME),
new Pair<>("Feed Information", GtfsFeedInfo.FILENAME),
new Pair<>("Attributions", GtfsAttribution.FILENAME),
new Pair<>("Translations", GtfsTranslation.FILENAME),
new Pair<>("Fare Media", GtfsFareMedia.FILENAME),
new Pair<>("Zone-Based Fares", GtfsArea.FILENAME),
new Pair<>("Transfer Fares", GtfsFareTransferRule.FILENAME),
new Pair<>("Time-Based Fares", GtfsTimeframe.FILENAME),
new Pair<>("Levels", GtfsLevel.FILENAME),
new Pair<>("Booking Rules", GtfsBookingRules.FILENAME),
new Pair<>("Fixed-Stops Demand Responsive Transit", GtfsLocationGroups.FILENAME));
new Pair<>(new FeatureMetadata("Pathway Connections", "Pathways"), GtfsPathway.FILENAME),
new Pair<>(new FeatureMetadata("Levels", "Pathways"), GtfsLevel.FILENAME),
new Pair<>(new FeatureMetadata("Transfers", null), GtfsTransfer.FILENAME),
new Pair<>(new FeatureMetadata("Shapes", null), GtfsShape.FILENAME),
new Pair<>(new FeatureMetadata("Frequency-Based Service", null), GtfsFrequency.FILENAME),
new Pair<>(new FeatureMetadata("Feed Information", null), GtfsFeedInfo.FILENAME),
new Pair<>(new FeatureMetadata("Attributions", null), GtfsAttribution.FILENAME),
new Pair<>(new FeatureMetadata("Translations", null), GtfsTranslation.FILENAME),
new Pair<>(new FeatureMetadata("Fares V1", "Fares"), GtfsFareAttribute.FILENAME),
new Pair<>(new FeatureMetadata("Fare Products", "Fares"), GtfsFareProduct.FILENAME),
new Pair<>(new FeatureMetadata("Fare Media", "Fares"), GtfsFareMedia.FILENAME),
new Pair<>(new FeatureMetadata("Zone-Based Fares", "Fares"), GtfsArea.FILENAME),
new Pair<>(
new FeatureMetadata("Fares Transfers", "Fares"), GtfsFareTransferRule.FILENAME),
new Pair<>(new FeatureMetadata("Time-Based Fares", "Fares"), GtfsTimeframe.FILENAME),
new Pair<>(
new FeatureMetadata("Booking Rules", "Flexible Services"), GtfsBookingRules.FILENAME),
new Pair<>(
new FeatureMetadata("Fixed-Stops Demand Responsive Transit", "Flexible Services"),
GtfsLocationGroups.FILENAME));

protected FeedMetadata() {}

Expand Down Expand Up @@ -164,7 +168,7 @@ private void loadSpecFeatures(GtfsFeedContainer feedContainer) {
}

private void loadSpecFeaturesBasedOnFilePresence(GtfsFeedContainer feedContainer) {
for (Pair<String, String> entry : FILE_BASED_FEATURES) {
for (Pair<FeatureMetadata, String> entry : FILE_BASED_FEATURES) {
specFeatures.put(entry.getKey(), hasAtLeastOneRecordInFile(feedContainer, entry.getValue()));
}
}
Expand All @@ -186,7 +190,9 @@ private void loadSpecFeaturesBasedOnFieldPresence(GtfsFeedContainer feedContaine
}

private void loadDeviatedFixedRouteFeature(GtfsFeedContainer feedContainer) {
specFeatures.put("Deviated Fixed Route", hasAtLeastOneTripWithAllFields(feedContainer));
specFeatures.put(
new FeatureMetadata("Predefined Routes with Deviation", "Flexible Services"),
hasAtLeastOneTripWithAllFields(feedContainer));
}

private boolean hasAtLeastOneTripWithAllFields(GtfsFeedContainer feedContainer) {
Expand Down Expand Up @@ -214,7 +220,8 @@ private boolean hasAtLeastOneTripWithAllFields(GtfsFeedContainer feedContainer)

private void loadZoneBasedDemandResponsiveTransitFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Zone-Based Demand Responsive Transit", hasAtLeastOneTripWithOnlyLocationId(feedContainer));
new FeatureMetadata("Zone-Based Demand Responsive Services", "Flexible Services"),
hasAtLeastOneTripWithOnlyLocationId(feedContainer));
}

private boolean hasAtLeastOneTripWithOnlyLocationId(GtfsFeedContainer feedContainer) {
Expand All @@ -239,7 +246,7 @@ private boolean hasAtLeastOneTripWithOnlyLocationId(GtfsFeedContainer feedContai

private void loadContinuousStopsFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Continuous Stops",
new FeatureMetadata("Continuous Stops", "Flexible Services"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsRoute.FILENAME,
Expand All @@ -260,7 +267,7 @@ private void loadContinuousStopsFeature(GtfsFeedContainer feedContainer) {

private void loadRouteBasedFaresFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Route-Based Fares",
new FeatureMetadata("Route-Based Fares", "Fares"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsRoute.FILENAME,
Expand All @@ -270,7 +277,7 @@ private void loadRouteBasedFaresFeature(GtfsFeedContainer feedContainer) {

private void loadPathwayDirectionsFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Pathways Directions",
new FeatureMetadata("Pathway Signs", "Pathways"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsPathway.FILENAME,
Expand All @@ -281,7 +288,7 @@ private void loadPathwayDirectionsFeature(GtfsFeedContainer feedContainer) {

private void loadPathwayExtraFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Pathways (extra)",
new FeatureMetadata("Pathway Details", "Pathways"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsPathway.FILENAME,
Expand All @@ -302,7 +309,7 @@ private void loadPathwayExtraFeature(GtfsFeedContainer feedContainer) {

private void loadTraversalTimeFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Traversal Time",
new FeatureMetadata("In-station Traversal Time", "Pathways"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsPathway.FILENAME,
Expand All @@ -311,7 +318,7 @@ private void loadTraversalTimeFeature(GtfsFeedContainer feedContainer) {

private void loadLocationTypesFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Location Types",
new FeatureMetadata("Location Types", null),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsStop.FILENAME,
Expand All @@ -320,7 +327,7 @@ private void loadLocationTypesFeature(GtfsFeedContainer feedContainer) {

private void loadBikeAllowanceFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Bikes Allowance",
new FeatureMetadata("Bike Allowed", null),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsTrip.FILENAME,
Expand All @@ -329,7 +336,7 @@ private void loadBikeAllowanceFeature(GtfsFeedContainer feedContainer) {

private void loadTTSFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Text-To-Speech",
new FeatureMetadata("Text-to-Speech", "Accessibility"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsStop.FILENAME,
Expand All @@ -338,20 +345,22 @@ private void loadTTSFeature(GtfsFeedContainer feedContainer) {

private void loadWheelchairAccessibilityFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Wheelchair Accessibility",
new FeatureMetadata("Stops Wheelchair Accessibility", "Accessibility"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsTrip.FILENAME,
List.of((Function<GtfsTrip, Boolean>) GtfsTrip::hasWheelchairAccessible))
|| hasAtLeastOneRecordForFields(
feedContainer,
GtfsStop.FILENAME,
List.of((Function<GtfsStop, Boolean>) GtfsStop::hasWheelchairBoarding)));
feedContainer,
GtfsStop.FILENAME,
List.of((Function<GtfsStop, Boolean>) GtfsStop::hasWheelchairBoarding)));
specFeatures.put(
new FeatureMetadata("Trips Wheelchair Accessibility", "Accessibility"),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsTrip.FILENAME,
List.of((Function<GtfsTrip, Boolean>) GtfsTrip::hasWheelchairAccessible)));
}

private void loadHeadsignsFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Headsigns",
new FeatureMetadata("Headsigns", null),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsTrip.FILENAME,
Expand All @@ -364,7 +373,7 @@ private void loadHeadsignsFeature(GtfsFeedContainer feedContainer) {

private void loadRouteColorsFeature(GtfsFeedContainer feedContainer) {
specFeatures.put(
"Route Colors",
new FeatureMetadata("Route Colors", null),
hasAtLeastOneRecordForFields(
feedContainer,
GtfsRoute.FILENAME,
Expand Down
5 changes: 4 additions & 1 deletion main/src/main/resources/report.html
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
padding: 2px 5px;
margin-right: 2px;
margin-bottom: 2px;
text-align: center;
}

.tooltip {
Expand Down Expand Up @@ -306,7 +307,9 @@ <h4>
</h4>
<hr />
<div>
<span class="spec-feature" th:each="feature: ${metadata.specFeatures}" th:if="${feature.value}" th:text="${feature.key}" />
<span class="spec-feature" th:each="feature: ${metadata.specFeatures}" th:if="${feature.value}">
<a th:href="${feature.key.getDocUrl()}" th:text="${feature.key.getFeatureName()}" target="_blank"></a>
</span>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ private void validateSpecFeature(
new DefaultValidatorProvider(validationContext, validatorLoader),
new NoticeContainer());
FeedMetadata feedMetadata = FeedMetadata.from(feedContainer, gtfsInput.getFilenames());
assertThat(feedMetadata.specFeatures.get(specFeature)).isEqualTo(expectedValue);
assertThat(feedMetadata.specFeatures.get(new FeatureMetadata(specFeature, null)))
.isEqualTo(expectedValue);
}
}

Expand Down Expand Up @@ -250,32 +251,32 @@ public void omitsRouteColorsFeatureTest5() throws IOException, InterruptedExcept
}

@Test
public void containsPathwaysFeatureTest() throws IOException, InterruptedException {
public void containsPathwayConnectionFeatureTest() throws IOException, InterruptedException {
String pathwayContent =
"pathway_id,from_stop_id,to_stop_id,pathway_mode,is_bidirectional\n"
+ "pathway1,stop1,stop2,1,1\n"
+ "pathway2,stop2,stop3,2,0\n";
createDataFile("pathways.txt", pathwayContent);
validateSpecFeature(
"Pathways (basic)",
"Pathway Connections",
true,
ImmutableList.of(GtfsPathwayTableDescriptor.class, GtfsAgencyTableDescriptor.class));
}

@Test
public void omitsPathwaysFeatureTest() throws IOException, InterruptedException {
public void omitsPathwayConnectionsFeatureTest() throws IOException, InterruptedException {
String pathwayContent = "pathway_id,from_stop_id,to_stop_id,pathway_mode,is_bidirectional\n";
createDataFile("pathways.txt", pathwayContent);
validateSpecFeature(
"Pathways (basic)",
"Pathway Connections",
false,
ImmutableList.of(GtfsPathwayTableDescriptor.class, GtfsAgencyTableDescriptor.class));
}

@Test
public void omitsFeatures() throws IOException, InterruptedException {
validateSpecFeature(
"Pathways (basic)",
"Pathway Connections",
false,
ImmutableList.of(GtfsPathwayTableDescriptor.class, GtfsAgencyTableDescriptor.class));
validateSpecFeature(
Expand Down Expand Up @@ -330,7 +331,7 @@ public void containsFrequencyBasedTripFeatureTest() throws IOException, Interrup
"trip_id, start_time, end_time, headway_secs\n" + "dummy1, 01:01:01, 01:01:02, 1\n";
createDataFile(GtfsFrequency.FILENAME, content);
validateSpecFeature(
"Frequencies",
"Frequency-Based Service",
true,
ImmutableList.of(GtfsFrequencyTableDescriptor.class, GtfsAgencyTableDescriptor.class));
}
Expand All @@ -340,7 +341,7 @@ public void omitsFrequencyBasedTripFeatureTest() throws IOException, Interrupted
String content = "trip_id, start_time, end_time, headway_secs\n";
createDataFile(GtfsFrequency.FILENAME, content);
validateSpecFeature(
"Frequencies",
"Frequency-Based Service",
false,
ImmutableList.of(GtfsFrequencyTableDescriptor.class, GtfsAgencyTableDescriptor.class));
}
Expand Down Expand Up @@ -508,7 +509,7 @@ public void containsWheelchairAccessibilityFeature() throws IOException, Interru
String content = "route_id, service_id, trip_id, wheelchair_accessible\n" + "1, 2, 3, 1\n";
createDataFile(GtfsTrip.FILENAME, content);
validateSpecFeature(
"Wheelchair Accessibility",
"Trips Wheelchair Accessibility",
true,
ImmutableList.of(GtfsAgencyTableDescriptor.class, GtfsTripTableDescriptor.class));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ private static FeedMetadata generateFeedMetaData() {
"Illegal Key",
3 // Should not be present in the resulting GSON
);
feedMetadata.specFeatures = Map.of("Feature1", false, "Feature2", true);
feedMetadata.specFeatures =
Map.of(
new FeatureMetadata("Feature1", null),
false,
new FeatureMetadata("Feature2", null),
true);
feedMetadata.validationTimeSeconds = 100.0;
return feedMetadata;
}
Expand Down

0 comments on commit be675f3

Please sign in to comment.