From be675f3fc185209f9f4cf6bbd92725d70faf4a72 Mon Sep 17 00:00:00 2001 From: cka-y <60586858+cka-y@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:42:25 -0400 Subject: [PATCH] feat: changes + added documentation to features (#1844) --- .../report/JsonReportSummary.java | 2 + .../report/model/FeatureMetadata.java | 38 ++++++++ .../report/model/FeedMetadata.java | 87 ++++++++++--------- main/src/main/resources/report.html | 5 +- .../report/model/FeedMetadataTest.java | 19 ++-- .../report/model/JsonReportSummaryTest.java | 7 +- 6 files changed, 108 insertions(+), 50 deletions(-) create mode 100644 main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeatureMetadata.java diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/report/JsonReportSummary.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/report/JsonReportSummary.java index b5ba4ed1a9..4480cc544f 100644 --- a/main/src/main/java/org/mobilitydata/gtfsvalidator/report/JsonReportSummary.java +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/report/JsonReportSummary.java @@ -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; @@ -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( diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeatureMetadata.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeatureMetadata.java new file mode 100644 index 0000000000..47721e8f76 --- /dev/null +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeatureMetadata.java @@ -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); + } +} diff --git a/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadata.java b/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadata.java index b2a4813eee..a646265f0d 100644 --- a/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadata.java +++ b/main/src/main/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadata.java @@ -47,7 +47,7 @@ public class FeedMetadata { public Map feedInfo = new LinkedHashMap<>(); - public Map specFeatures = new LinkedHashMap<>(); + public Map specFeatures = new LinkedHashMap<>(); public ArrayList agencies = new ArrayList<>(); private ImmutableSortedSet filenames; @@ -55,24 +55,28 @@ public class FeedMetadata { public double validationTimeSeconds; // List of features that only require checking the presence of one record in the file. - private final List> FILE_BASED_FEATURES = + private final List> 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() {} @@ -164,7 +168,7 @@ private void loadSpecFeatures(GtfsFeedContainer feedContainer) { } private void loadSpecFeaturesBasedOnFilePresence(GtfsFeedContainer feedContainer) { - for (Pair entry : FILE_BASED_FEATURES) { + for (Pair entry : FILE_BASED_FEATURES) { specFeatures.put(entry.getKey(), hasAtLeastOneRecordInFile(feedContainer, entry.getValue())); } } @@ -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) { @@ -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) { @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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::hasWheelchairAccessible)) - || hasAtLeastOneRecordForFields( - feedContainer, - GtfsStop.FILENAME, - List.of((Function) GtfsStop::hasWheelchairBoarding))); + feedContainer, + GtfsStop.FILENAME, + List.of((Function) GtfsStop::hasWheelchairBoarding))); + specFeatures.put( + new FeatureMetadata("Trips Wheelchair Accessibility", "Accessibility"), + hasAtLeastOneRecordForFields( + feedContainer, + GtfsTrip.FILENAME, + List.of((Function) GtfsTrip::hasWheelchairAccessible))); } private void loadHeadsignsFeature(GtfsFeedContainer feedContainer) { specFeatures.put( - "Headsigns", + new FeatureMetadata("Headsigns", null), hasAtLeastOneRecordForFields( feedContainer, GtfsTrip.FILENAME, @@ -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, diff --git a/main/src/main/resources/report.html b/main/src/main/resources/report.html index a22ab42dc5..48430b4da4 100644 --- a/main/src/main/resources/report.html +++ b/main/src/main/resources/report.html @@ -123,6 +123,7 @@ padding: 2px 5px; margin-right: 2px; margin-bottom: 2px; + text-align: center; } .tooltip { @@ -306,7 +307,9 @@


- + + +
diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadataTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadataTest.java index df9844ff86..3c6dc8d98a 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadataTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/FeedMetadataTest.java @@ -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); } } @@ -250,24 +251,24 @@ 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)); } @@ -275,7 +276,7 @@ public void omitsPathwaysFeatureTest() throws IOException, InterruptedException @Test public void omitsFeatures() throws IOException, InterruptedException { validateSpecFeature( - "Pathways (basic)", + "Pathway Connections", false, ImmutableList.of(GtfsPathwayTableDescriptor.class, GtfsAgencyTableDescriptor.class)); validateSpecFeature( @@ -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)); } @@ -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)); } @@ -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)); } diff --git a/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/JsonReportSummaryTest.java b/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/JsonReportSummaryTest.java index b0e0617c4d..70ee5e5293 100644 --- a/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/JsonReportSummaryTest.java +++ b/main/src/test/java/org/mobilitydata/gtfsvalidator/report/model/JsonReportSummaryTest.java @@ -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; }