Skip to content

Commit

Permalink
Merge 6581ccb into 6e2c75e
Browse files Browse the repository at this point in the history
  • Loading branch information
cka-y authored Feb 3, 2025
2 parents 6e2c75e + 6581ccb commit 0ad6a39
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2025 Google LLC
*
* 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.mobilitydata.gtfsvalidator.validator;

import static org.mobilitydata.gtfsvalidator.notice.SeverityLevel.*;
import static org.mobilitydata.gtfsvalidator.util.S2Earth.getDistanceMeters;

import com.google.common.geometry.S2Point;
import javax.inject.Inject;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidationNotice;
import org.mobilitydata.gtfsvalidator.annotation.GtfsValidator;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.notice.ValidationNotice;
import org.mobilitydata.gtfsvalidator.table.*;
import org.mobilitydata.gtfsvalidator.util.StopUtil;

/**
* Validates that the transfer distance between two stops is not too large.
*
* <p>Generated notice: {@link TransferDistanceTooLargeNotice}.
*
* <p>Generated notice: {@link TransferDistanceAbove_2KmNotice}.
*/
@GtfsValidator
public class TransferDistanceValidator extends FileValidator {
private final GtfsTransferTableContainer transferTableContainer;
private final GtfsStopTableContainer stopTableContainer;

@Inject
TransferDistanceValidator(
GtfsTransferTableContainer transferTableContainer,
GtfsStopTableContainer stopTableContainer) {
this.transferTableContainer = transferTableContainer;
this.stopTableContainer = stopTableContainer;
}

@Override
public void validate(NoticeContainer noticeContainer) {
for (GtfsTransfer transfer : transferTableContainer.getEntities()) {
if (transfer.hasFromStopId() && transfer.hasToStopId()) {
S2Point fromCoordinates =
StopUtil.getStopOrParentLatLng(stopTableContainer, transfer.fromStopId()).toPoint();
S2Point toCoordinates =
StopUtil.getStopOrParentLatLng(stopTableContainer, transfer.toStopId()).toPoint();
double distanceMeters = getDistanceMeters(fromCoordinates, toCoordinates);
if (distanceMeters > 10_000) {
noticeContainer.addValidationNotice(
new TransferDistanceTooLargeNotice(transfer, distanceMeters / 1_000));

} else if (distanceMeters > 2_000) {
noticeContainer.addValidationNotice(
new TransferDistanceAbove_2KmNotice(transfer, distanceMeters / 1_000));
}
}
}
}

@Override
public boolean shouldCallValidate() {
return transferTableContainer != null
&& stopTableContainer != null
&& transferTableContainer.hasColumn(GtfsTransfer.FROM_STOP_ID_FIELD_NAME)
&& transferTableContainer.hasColumn(GtfsTransfer.TO_STOP_ID_FIELD_NAME);
}

/** The transfer distance from stop to stop in `transfers.txt` is larger than 2 km. */
@GtfsValidationNotice(severity = INFO)
public static class TransferDistanceAbove_2KmNotice extends ValidationNotice {

/** The row number from `transfers.txt` for the faulty entry. */
private final int csvRowNumber;

/** The ID of the stop in `from_stop_id`. */
private final String fromStopId;

/** The ID of the stop in `to_stop_id`. */
private final String toStopId;

/** The distance between the two stops in km. */
private final double distanceKm;

public TransferDistanceAbove_2KmNotice(GtfsTransfer transfer, double distanceKm) {
this.csvRowNumber = transfer.csvRowNumber();
this.fromStopId = transfer.fromStopId();
this.toStopId = transfer.toStopId();
this.distanceKm = distanceKm;
}
}

/** The transfer distance from stop to stop in `transfers.txt` is larger than 10 km. */
@GtfsValidationNotice(severity = WARNING)
public static class TransferDistanceTooLargeNotice extends ValidationNotice {

/** The row number from `transfers.txt` for the faulty entry. */
private final int csvRowNumber;

/** The ID of the stop in `from_stop_id`. */
private final String fromStopId;

/** The ID of the stop in `to_stop_id`. */
private final String toStopId;

/** The distance between the two stops in km. */
private final double distanceKm;

public TransferDistanceTooLargeNotice(GtfsTransfer transfer, double distanceKm) {
this.csvRowNumber = transfer.csvRowNumber();
this.fromStopId = transfer.fromStopId();
this.toStopId = transfer.toStopId();
this.distanceKm = distanceKm;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public void testNoticeClassFieldNames() {
"fileNameB",
"filename",
"firstIndex",
"fromStopId",
"geoDistanceToShape",
"geoJsonType",
"geographyId",
Expand Down Expand Up @@ -213,6 +214,7 @@ public void testNoticeClassFieldNames() {
"tableName",
"time",
"timeframeGroupId",
"toStopId",
"transferCount",
"tripCsvRowNumber",
"tripFieldName",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.mobilitydata.gtfsvalidator.validator;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.mobilitydata.gtfsvalidator.notice.NoticeContainer;
import org.mobilitydata.gtfsvalidator.table.*;
import org.mobilitydata.gtfsvalidator.table.GtfsTransfer.Builder;

public class TransferDistanceValidatorTest {

private final NoticeContainer noticeContainer = new NoticeContainer();

@Test
public void testDistanceAbove10KmGeneratesWarning() {
GtfsStopTableContainer stops =
GtfsStopTableContainer.forEntities(
ImmutableList.of(
new GtfsStop.Builder().setStopId("s0").setStopLat(0.0).setStopLon(0.0).build(),
new GtfsStop.Builder().setStopId("s1").setStopLat(0.0).setStopLon(0.1).build()),
noticeContainer);
GtfsTransferTableContainer transfers =
GtfsTransferTableContainer.forEntities(
ImmutableList.of(
new Builder().setCsvRowNumber(1).setFromStopId("s0").setToStopId("s1").build()),
noticeContainer);

new TransferDistanceValidator(transfers, stops).validate(noticeContainer);
assertThat(noticeContainer.getValidationNotices())
.containsExactly(
new TransferDistanceValidator.TransferDistanceTooLargeNotice(
transfers.getEntities().get(0), 11.119510117748394));
}

@Test
public void testDistanceAbove2KmGeneratesNotice() {
GtfsStopTableContainer stops =
GtfsStopTableContainer.forEntities(
ImmutableList.of(
new GtfsStop.Builder().setStopId("s0").setStopLat(0.0).setStopLon(0.0).build(),
new GtfsStop.Builder().setStopId("s1").setStopLat(0.0).setStopLon(0.02).build()),
noticeContainer);
GtfsTransferTableContainer transfers =
GtfsTransferTableContainer.forEntities(
ImmutableList.of(
new Builder().setCsvRowNumber(1).setFromStopId("s0").setToStopId("s1").build()),
noticeContainer);

new TransferDistanceValidator(transfers, stops).validate(noticeContainer);
assertThat(noticeContainer.getValidationNotices())
.containsExactly(
new TransferDistanceValidator.TransferDistanceAbove_2KmNotice(
transfers.getEntities().get(0), 2.2239020235496785));
}

@Test
public void testDistanceBellow2KmYieldsNoNotice() {
GtfsStopTableContainer stops =
GtfsStopTableContainer.forEntities(
ImmutableList.of(
new GtfsStop.Builder().setStopId("s0").setStopLat(0.0).setStopLon(0.0).build(),
new GtfsStop.Builder().setStopId("s1").setStopLat(0.0).setStopLon(0.01).build()),
noticeContainer);
GtfsTransferTableContainer transfers =
GtfsTransferTableContainer.forEntities(
ImmutableList.of(
new Builder().setCsvRowNumber(1).setFromStopId("s0").setToStopId("s1").build()),
noticeContainer);

new TransferDistanceValidator(transfers, stops).validate(noticeContainer);
assertThat(noticeContainer.getValidationNotices()).isEmpty();
}
}

0 comments on commit 0ad6a39

Please sign in to comment.