diff --git a/src/main/java/org/mtransit/parser/db/DBUtils.kt b/src/main/java/org/mtransit/parser/db/DBUtils.kt index 7451b5c..8650f8b 100644 --- a/src/main/java/org/mtransit/parser/db/DBUtils.kt +++ b/src/main/java/org/mtransit/parser/db/DBUtils.kt @@ -1,7 +1,10 @@ package org.mtransit.parser.db +import org.mtransit.commons.FeatureFlags import org.mtransit.commons.sql.SQLCreateBuilder +import org.mtransit.commons.sql.fromSQL import org.mtransit.commons.sql.getStringOrNull +import org.mtransit.commons.sql.toSQL import org.mtransit.parser.DefaultAgencyTools import org.mtransit.parser.FileUtils import org.mtransit.parser.MTLog @@ -84,6 +87,11 @@ object DBUtils { .appendColumn(GTripStop.TRIP_ID, SQLUtilsCommons.INT) .appendColumn(GTripStop.STOP_ID, SQLUtilsCommons.INT) .appendColumn(GTripStop.STOP_SEQUENCE, SQLUtilsCommons.INT) + .apply { + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + appendColumn(GTripStop.LAST_TRIP_STOP, SQLUtilsCommons.INT) // as BOOLEAN + } + } .build() ) SQLUtils.executeUpdate( @@ -332,12 +340,21 @@ object DBUtils { connection.createStatement().use { statement -> val rs = SQLUtils.executeUpdate( statement, - SQLUtilsCommons.INSERT_INTO + TRIP_STOPS_TABLE_NAME + SQLUtilsCommons.VALUES_P1 + - "${gTripStop.routeIdInt}," + - "${gTripStop.tripIdInt}," + - "${gTripStop.stopIdInt}," + - "${gTripStop.stopSequence}" + - SQLUtilsCommons.P2 + buildString { + append(SQLUtilsCommons.INSERT_INTO) + append(TRIP_STOPS_TABLE_NAME) + append( + buildList { + add(gTripStop.routeIdInt.toString()) + add(gTripStop.tripIdInt.toString()) + add(gTripStop.stopIdInt.toString()) + add(gTripStop.stopSequence.toString()) + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + add(gTripStop.isLastTripStop.toSQL().toString()) + } + }.joinToString(separator = ",", prefix = SQLUtilsCommons.VALUES_P1, postfix = (SQLUtilsCommons.P2)) + ) + } ) insertRowCount++ insertCount++ @@ -384,11 +401,7 @@ object DBUtils { query += " WHERE ${GTripStop.TRIP_ID} IN ${ tripIdInts .distinct() - .joinToString( - separator = ",", - prefix = "(", - postfix = ")" - ) { "$it" } + .joinToString(separator = ",", prefix = "(", postfix = ")") { "$it" } }" } limitMaxNbRow?.let { @@ -402,12 +415,22 @@ object DBUtils { val rs = SQLUtils.executeQuery(statement, query) while (rs.next()) { result.add( - GTripStop( - rs.getInt(GTripStop.ROUTE_ID), - rs.getInt(GTripStop.TRIP_ID), - rs.getInt(GTripStop.STOP_ID), - rs.getInt(GTripStop.STOP_SEQUENCE) - ) + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + GTripStop( + routeIdInt = rs.getInt(GTripStop.ROUTE_ID), + tripIdInt = rs.getInt(GTripStop.TRIP_ID), + stopIdInt = rs.getInt(GTripStop.STOP_ID), + stopSequence = rs.getInt(GTripStop.STOP_SEQUENCE), + isLastTripStop = rs.getInt(GTripStop.LAST_TRIP_STOP).fromSQL(), + ) + } else { + GTripStop( + routeIdInt = rs.getInt(GTripStop.ROUTE_ID), + tripIdInt = rs.getInt(GTripStop.TRIP_ID), + stopIdInt = rs.getInt(GTripStop.STOP_ID), + stopSequence = rs.getInt(GTripStop.STOP_SEQUENCE), + ) + } ) selectRowCount++ } diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GSpec.java b/src/main/java/org/mtransit/parser/gtfs/data/GSpec.java index a3cebeb..067d1c4 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GSpec.java +++ b/src/main/java/org/mtransit/parser/gtfs/data/GSpec.java @@ -514,7 +514,7 @@ public List getFrequencies(@Nullable Integer optTripIdInt) { } @NotNull - private Collection getFrequencyTripIds() { + private Collection getFrequencyTripIdInts() { if (USE_DB_ONLY) { return GIDs.getInts(GTFSDataBase.selectFrequencyTripIds()); } @@ -623,8 +623,9 @@ public void generateTripStops() { String uid; String tripUID; GStopTime gStopTime; + GStopTime gStopTimeNext; + boolean lastTripStop; List tripStopTimes; - GTripStop gTripStop; final int stopTimesCount = readStopTimesCount(); int tripStopsCount = 0; int offset = 0; @@ -639,6 +640,7 @@ public void generateTripStops() { offset += tripStopTimes.size(); for (int i = 0; i < tripStopTimes.size(); i++) { gStopTime = tripStopTimes.get(i); + gStopTimeNext = i < tripStopTimes.size() - 1 ? tripStopTimes.get(i + 1) : null; tripUID = this.tripIdIntsUIDs.get(gStopTime.getTripIdInt()); if (tripUID == null) { continue; @@ -648,8 +650,10 @@ public void generateTripStops() { MTLog.log("Generating GTFS trip stops from stop times... > (uid: %s) SKIP %s", uid, gStopTime); continue; } - gTripStop = new GTripStop(tripUID, gStopTime.getTripIdInt(), gStopTime.getStopIdInt(), gStopTime.getStopSequence()); - addTripStops(gTripStop); + lastTripStop = gStopTimeNext == null || gStopTimeNext.getTripIdInt() != gStopTime.getTripIdInt(); + addTripStops( + new GTripStop(tripUID, gStopTime.getTripIdInt(), gStopTime.getStopIdInt(), gStopTime.getStopSequence(), lastTripStop) + ); } MTLog.log("Generating GTFS trip stops from stop times... (created %s trip stops)", (this.tripStopsUIDs.size() - tripStopsCount)); tripStopsCount = this.tripStopsUIDs.size(); @@ -673,7 +677,7 @@ public void generateStopTimesFromFrequencies(@SuppressWarnings("unused") @NotNul int t = 0; int ts = 0; int st = 0; - for (Integer tripIdInt : getFrequencyTripIds()) { + for (Integer tripIdInt : getFrequencyTripIdInts()) { if (!this.tripIdIntsUIDs.containsKey(tripIdInt)) { continue; // excluded service ID } @@ -681,10 +685,10 @@ public void generateStopTimesFromFrequencies(@SuppressWarnings("unused") @NotNul if (gOriginalTrip == null) { throw new MTLog.Fatal("Cannot find original trip for ID '%s' (%d)!", GIDs.getString(tripIdInt), tripIdInt); } - List tripStopTimes = GStopTime.from(GTFSDataBase.selectStopTimes(Collections.singletonList(GIDs.getString(tripIdInt)))); - ArrayList newGStopTimes = new ArrayList<>(); - Calendar stopTimeCal = Calendar.getInstance(); - HashMap gStopTimeIncInSec = new HashMap<>(); + final List tripStopTimes = GStopTime.from(GTFSDataBase.selectStopTimes(Collections.singletonList(GIDs.getString(tripIdInt)))); + final ArrayList newGStopTimes = new ArrayList<>(); + final Calendar stopTimeCal = Calendar.getInstance(); + final HashMap gStopTimeIncInSec = new HashMap<>(); Integer previousStopTimeInSec = null; long lastFirstStopTimeInMs = -1L; for (GStopTime gStopTime : tripStopTimes) { @@ -716,7 +720,7 @@ public void generateStopTimesFromFrequencies(@SuppressWarnings("unused") @NotNul long firstStopTimeInMs = frequencyStartInMs; int f = 0; while (frequencyStartInMs <= firstStopTimeInMs && firstStopTimeInMs <= frequencyEndInMs) { - int newGeneratedTripIdInt = GIDs.getInt(GIDs.getString(tripIdInt) + "-" + f); // DB primary keys > [trip ID + sequence] + final int newGeneratedTripIdInt = GIDs.getInt(GIDs.getString(tripIdInt) + "-" + f); // DB primary keys > [trip ID + sequence] addTrip(new GTrip( newGeneratedTripIdInt, gOriginalTrip.getRouteIdInt(), @@ -732,14 +736,13 @@ public void generateStopTimesFromFrequencies(@SuppressWarnings("unused") @NotNul )); t++; stopTimeCal.setTimeInMillis(firstStopTimeInMs); - for (int i = 0; i < tripStopTimes.size(); i++) { - GStopTime gStopTime = tripStopTimes.get(i); + for (GStopTime gStopTime : tripStopTimes) { stopTimeCal.add(Calendar.SECOND, gStopTimeIncInSec.get(gStopTime.getUID())); - int newDepartureTime = getNewDepartureTime(stopTimeCal); - GPickupType pickupType = gStopTime.getPickupType(); - GDropOffType dropOffType = gStopTime.getDropOffType(); - GTimePoint timePoint = gStopTime.getTimePoint(); - GStopTime newGStopTime = new GStopTime( + final int newDepartureTime = getNewDepartureTime(stopTimeCal); + final GPickupType pickupType = gStopTime.getPickupType(); + final GDropOffType dropOffType = gStopTime.getDropOffType(); + final GTimePoint timePoint = gStopTime.getTimePoint(); + final GStopTime newGStopTime = new GStopTime( newGeneratedTripIdInt, newDepartureTime, newDepartureTime, @@ -759,9 +762,14 @@ public void generateStopTimesFromFrequencies(@SuppressWarnings("unused") @NotNul throw new MTLog.Fatal(e, "Error while generating stop times for frequency '%s'!", gFrequency); } } + GStopTime newGStopTime; + GStopTime gStopTimeNext; + boolean lastTripStop; String tripUID; String uid; - for (GStopTime newGStopTime : newGStopTimes) { + for (int i = 0; i < newGStopTimes.size(); i++) { + newGStopTime = newGStopTimes.get(i); + gStopTimeNext = i < newGStopTimes.size() - 1 ? newGStopTimes.get(i + 1) : null; addStopTime(newGStopTime, true); st++; tripUID = this.tripIdIntsUIDs.get(newGStopTime.getTripIdInt()); @@ -777,8 +785,9 @@ public void generateStopTimesFromFrequencies(@SuppressWarnings("unused") @NotNul MTLog.log("Generating GTFS trip stop from frequencies... > (uid: %s) SKIP %s", uid, newGStopTime); continue; } + lastTripStop = gStopTimeNext == null || gStopTimeNext.getTripIdInt() != newGStopTime.getTripIdInt(); addTripStops( - new GTripStop(tripUID, newGStopTime.getTripIdInt(), newGStopTime.getStopIdInt(), newGStopTime.getStopSequence()) + new GTripStop(tripUID, newGStopTime.getTripIdInt(), newGStopTime.getStopIdInt(), newGStopTime.getStopSequence(), lastTripStop) ); ts++; } diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GStopTime.kt b/src/main/java/org/mtransit/parser/gtfs/data/GStopTime.kt index 630867e..e6341c1 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GStopTime.kt +++ b/src/main/java/org/mtransit/parser/gtfs/data/GStopTime.kt @@ -11,8 +11,8 @@ import java.util.Date // https://gtfs.org/schedule/reference/#stop_timestxt data class GStopTime( val tripIdInt: Int, - private val _arrivalTime: Int, - private val _departureTime: Int, + private val _arrivalTime: Int, // HHmmss + private val _departureTime: Int, // HHmmss val stopIdInt: Int, val stopSequence: Int, val stopHeadsign: String?, diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GTrip.kt b/src/main/java/org/mtransit/parser/gtfs/data/GTrip.kt index bf28c5a..9fdd85b 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GTrip.kt +++ b/src/main/java/org/mtransit/parser/gtfs/data/GTrip.kt @@ -184,18 +184,17 @@ data class GTrip( private const val UID_SEPARATOR = "+" // int IDs can be negative - @Suppress("unused") @JvmStatic - fun extractRouteIdInt(tripUID: String) = split(tripUID).second + fun extractRouteIdInt(tripUID: String) = split(tripUID).first @Suppress("unused") @JvmStatic - fun extractTripIdInt(tripUID: String) = split(tripUID).first + fun extractTripIdInt(tripUID: String) = split(tripUID).second @JvmStatic fun split(tripUID: String) = try { tripUID.split(UID_SEPARATOR).let { s -> - Pair(s[0].toInt(), s[1].toInt()) + s[0].toInt() to s[1].toInt() } } catch (e: Exception) { throw MTLog.Fatal(e, "Error while trying to split $tripUID!") diff --git a/src/main/java/org/mtransit/parser/gtfs/data/GTripStop.kt b/src/main/java/org/mtransit/parser/gtfs/data/GTripStop.kt index 4542bde..8bd7413 100644 --- a/src/main/java/org/mtransit/parser/gtfs/data/GTripStop.kt +++ b/src/main/java/org/mtransit/parser/gtfs/data/GTripStop.kt @@ -9,46 +9,34 @@ data class GTripStop( val routeIdInt: Int, val tripIdInt: Int, val stopIdInt: Int, - val stopSequence: Int + val stopSequence: Int, + val isLastTripStop: Boolean = false, ) { - @Suppress("unused") - constructor( - routeAndTripUID: String, - stopId: Int, - stopSequence: Int - ) : this( - GTrip.split(routeAndTripUID), - stopId, - stopSequence - ) - - @Suppress("unused") constructor( routeAndTripUID: String, tripIdInt: Int, stopId: Int, - stopSequence: Int + stopSequence: Int, + lastTripStop: Boolean, ) : this( GTrip.extractRouteIdInt(routeAndTripUID), tripIdInt, stopId, - stopSequence - ) - - constructor( - routeAndTripUID: Pair, - stopId: Int, - stopSequence: Int - ) : this( - routeAndTripUID.first, - routeAndTripUID.second, - stopId, - stopSequence + stopSequence, + lastTripStop, ) val uID by lazy { getNewUID(routeIdInt, tripIdInt, stopIdInt, stopSequence) } + @Suppress("unused") + @get:Discouraged(message = "Not memory efficient") + val routeId: String get() = _routeId + + @Suppress("unused") + private val _routeId: String + get() = GIDs.getString(routeIdInt) + @Suppress("unused") @get:Discouraged(message = "Not memory efficient") val tripId: String get() = _tripId @@ -68,7 +56,8 @@ data class GTripStop( fun toStringPlus(): String { return toString() + "+(stopId:$_stopId)" + - "+(tripId:$_tripId)" + "+(tripId:$_tripId)" + + "+(routeId:$_routeId)" } companion object { @@ -76,6 +65,7 @@ data class GTripStop( const val TRIP_ID = GTrip.TRIP_ID const val STOP_ID = GStop.STOP_ID const val STOP_SEQUENCE = GStopTime.STOP_SEQUENCE + const val LAST_TRIP_STOP = "last_trip_stop" private const val UID_SEPARATOR = "+" // int IDs can be negative diff --git a/src/main/java/org/mtransit/parser/mt/GenerateMObjectsTask.java b/src/main/java/org/mtransit/parser/mt/GenerateMObjectsTask.java index 26dbb0d..7c53b64 100644 --- a/src/main/java/org/mtransit/parser/mt/GenerateMObjectsTask.java +++ b/src/main/java/org/mtransit/parser/mt/GenerateMObjectsTask.java @@ -49,6 +49,8 @@ import java.util.TreeMap; import java.util.concurrent.Callable; +import kotlin.Triple; + public class GenerateMObjectsTask implements Callable { @NotNull @@ -479,7 +481,7 @@ private void parseGTrips(HashMap mSchedules, routeGTFS.getRouteTrips(gRoute.getRouteIdInt()), this.routeGTripIdIntGTripStops::get ); - ///noinspection DiscouragedApi + //noinspection DiscouragedApi final String gRouteId = gRoute.getRouteId(); MTLog.log("%s: parsing %d trips for route ID '%s'... ", this.routeId, routeGTrips.size(), gRouteId); int g = 0; @@ -561,16 +563,12 @@ private void parseGTrips(HashMap mSchedules, MTLog.logDebug("%s: Skip merge because current direction stops list contains new stops list.", this.routeId); } else { MTLog.log("%s: Need to merge direction ID '%s' stops lists (sizes: %d in %d).", this.routeId, mDirection.getId(), mDirectionStopsList.size(), cDirectionStopsList.size()); - if (Constants.DEBUG) { - MTLog.logDebug("%s: - current stops list > %s.", this.routeId, MDirectionStop.printDirectionStops(cDirectionStopsList)); - MTLog.logDebug("%s: - new stops list > %s.", this.routeId, MDirectionStop.printDirectionStops(mDirectionStopsList)); - } final ArrayList resultDirectionStopsList = setMDirectionStopSequence( mergeMyDirectionStopLists(mDirection.getId(), mDirectionStopsList, cDirectionStopsList) ); directionIdToMDirectionStops.put(mDirection.getId(), resultDirectionStopsList); if (Constants.DEBUG) { - MTLog.logDebug("%s: - result stops list > %s.", this.routeId, MDirectionStop.printDirectionStops(resultDirectionStopsList)); + MTLog.logDebug("%s: - result stops list > %s.", this.routeId, MDirectionStop.toStringSimple(resultDirectionStopsList)); } } } @@ -636,20 +634,16 @@ private HashMap parseGTripStops(HashMap mSchedu MDirectionStop mDirectionStop; long mDirectionId; String directionStopTimesHeadsign; - Pair mDirectionsAndStopSequences; + Triple mDirectionsIdStopSeqLast; HashMap addedMDirectionIdAndGStopIds = new HashMap<>(); final List gTripStops = this.routeGTripIdIntGTripStops.get(gTrip.getTripIdInt()); if (gTripStops == null) { return splitDirectionStopTimesHeadSign; } for (GTripStop gTripStop : gTripStops) { - if (gTripStop.getTripIdInt() != gTrip.getTripIdInt()) { - continue; - } + if (gTripStop.getTripIdInt() != gTrip.getTripIdInt()) continue; gStop = routeGTFS.getStop(gTripStop.getStopIdInt()); - if (gStop == null) { // was excluded previously - continue; - } + if (gStop == null) continue; // was excluded previously mStopId = this.agencyTools.getStopId(gStop); this.gStopsCache.put(mStopId, gStop); if (mStopId < 0) { @@ -657,33 +651,29 @@ private HashMap parseGTripStops(HashMap mSchedu throw new MTLog.Fatal("%s: Can't find GTFS stop ID (%s) '%s' from trip ID '%s' (%s)", this.routeId, mStopId, gTripStop.getStopIdInt(), gTripStop.getTripId(), gStop.toStringPlus(true)); } - mDirectionsAndStopSequences = new Pair<>( + mDirectionsIdStopSeqLast = new Triple<>( new Long[]{splitDirections.get(0).getId()}, - new Integer[]{gTripStop.getStopSequence()} + new Integer[]{gTripStop.getStopSequence()}, + new Boolean[]{gTripStop.isLastTripStop()} ); - for (int i = 0; i < mDirectionsAndStopSequences.first.length; i++) { - mDirectionId = mDirectionsAndStopSequences.first[i]; - mDirectionStop = new MDirectionStop(mDirectionId, mStopId, mDirectionsAndStopSequences.second[i]); + for (int i = 0; i < mDirectionsIdStopSeqLast.getFirst().length; i++) { + mDirectionId = mDirectionsIdStopSeqLast.getFirst()[i]; + mDirectionStop = new MDirectionStop(mDirectionId, mStopId, mDirectionsIdStopSeqLast.getSecond()[i]); + mDirectionStop.setAlwaysLastTripStop(mDirectionsIdStopSeqLast.getThird()[i]); if (!splitDirectionStops.containsKey(mDirectionId)) { splitDirectionStops.put(mDirectionId, new HashMap<>()); } final HashMap uuidToDirectionStops = splitDirectionStops.get(mDirectionId); final MDirectionStop sameUuidDirectionStop = uuidToDirectionStops.get(mDirectionStop.getUID()); if (sameUuidDirectionStop != null) { - if (!sameUuidDirectionStop.equalsExceptStopSequence(mDirectionStop)) { - throw new MTLog.Fatal("%s: Different direction '%s' stop '%s' already in list (%s != %s)!", - this.routeId, - mDirectionStop.getDirectionId(), - mDirectionStop.getStopId(), - mDirectionStop.toString(), - sameUuidDirectionStop.toString()); + if (!sameUuidDirectionStop.equalsDirectionAndStop(mDirectionStop)) { + throw new MTLog.Fatal("%s: Different direction stop already in list (%s != %s)!", this.routeId, mDirectionStop.toStringSimple(), sameUuidDirectionStop.toStringSimple()); } else { - MTLog.logDebug("%s: Same direction '%s' stop '%s' already in list (sequence %s != %s).", - this.routeId, - mDirectionStop.getDirectionId(), - mDirectionStop.getStopId(), - mDirectionStop.getStopSequence(), - sameUuidDirectionStop.getStopSequence()); + MTLog.logDebug("%s: Same direction already in list: '%s' (%s).", this.routeId, mDirectionStop.toStringSimple(), sameUuidDirectionStop.toStringSimple()); + if (sameUuidDirectionStop.isAlwaysLastTripStop() != mDirectionStop.isAlwaysLastTripStop()) { + sameUuidDirectionStop.setAlwaysLastTripStop(false); + uuidToDirectionStops.put(sameUuidDirectionStop.getUID(), sameUuidDirectionStop); + } } } else { uuidToDirectionStops.put(mDirectionStop.getUID(), mDirectionStop); @@ -773,7 +763,7 @@ private String parseGStopTimes(HashMap mSchedules, } } final Pair times = this.agencyTools.getTimes(gStopTime, gTripStopTimes, TIME_FORMAT); - if (times == null) { + if (times == null) { if (this.agencyTools.allowIgnoreInvalidStopTimes()) { continue; // this is bad, some transit agency data can NOT be fixed :( } else { @@ -883,69 +873,76 @@ private void setDirectionStopNoPickup(@NotNull ArrayList mDirect @NotNull private ArrayList mergeMyDirectionStopLists(long directionId, @NotNull ArrayList list1, @NotNull ArrayList list2) { final String logTag = this.routeId + ": " + directionId; + if (Constants.DEBUG) { + MTLog.logDebug("%s: - stops list (1) > %s.", logTag, MDirectionStop.printDirectionStops(list1)); + MTLog.logDebug("%s: - stops list (2) > %s.", logTag, MDirectionStop.printDirectionStops(list2)); + } ArrayList newList = new ArrayList<>(); HashSet newListStopIds = new HashSet<>(); HashSet list1StopIds = new HashSet<>(); - for (MDirectionStop ts1 : list1) { - list1StopIds.add(ts1.getStopId()); + for (MDirectionStop ds1 : list1) { + list1StopIds.add(ds1.getStopId()); } HashSet list2StopIds = new HashSet<>(); - for (MDirectionStop ts2 : list2) { - list2StopIds.add(ts2.getStopId()); + for (MDirectionStop ds2 : list2) { + list2StopIds.add(ds2.getStopId()); } - MDirectionStop ts1, ts2; + MDirectionStop ds1, ds2; boolean s2InL1, s1InL2; boolean lastInL1, lastInL2; - GStop lastGStop, ts1GStop, ts2GStop; - GStop commonGStop, previousTs1GStop, previousTs2GStop; - double ts1Distance, ts2Distance; - double previousTs1Distance, previousTs2Distance; + GStop lastGStop, ds1GStop, ds2GStop; + GStop commonGStop, previousDs1GStop, previousDs2GStop; + double ds1Distance, ds2Distance; + double previousDs1Distance, previousDs2Distance; MDirectionStop[] commonStopAndPrevious; int i1 = 0, i2 = 0; MDirectionStop last = null; //noinspection ForLoopReplaceableByWhile for (; i1 < list1.size() && i2 < list2.size(); ) { - ts1 = list1.get(i1); - ts2 = list2.get(i2); - if (newListStopIds.contains(ts1.getStopId())) { - MTLog.logDebug("%s: Skipped %s because already in the merged list (1).", logTag, ts1.toStringSameDirection()); + ds1 = list1.get(i1); + ds2 = list2.get(i2); + if (newListStopIds.contains(ds1.getStopId())) { + MTLog.logDebug("%s: Skipped %s because already in the merged list (1).", logTag, ds1.toStringSameDirection()); i1++; // skip this stop because already in the merged list continue; } - if (newListStopIds.contains(ts2.getStopId())) { - MTLog.logDebug("%s: Skipped %s because already in the merged list (2).", logTag, ts2.toStringSameDirection()); + if (newListStopIds.contains(ds2.getStopId())) { + MTLog.logDebug("%s: Skipped %s because already in the merged list (2).", logTag, ds2.toStringSameDirection()); i2++; // skip this stop because already in the merged list continue; } - if (ts1.getStopId() == ts2.getStopId()) { - if (!ts1.equalsExceptStopSequence(ts2)) { // not loosing no pickup info (TODO really? added 2022-12-03) - throw new MTLog.Fatal("%s: Trying to merge different direction stop for same stop %s VS %s.", logTag, ts1.toStringSameDirection(), ts2.toStringSameDirection()); + if (ds1.getStopId() == ds2.getStopId()) { + if (!ds1.equalsDirectionAndStop(ds2)) { // not loosing no pickup info (TODO really? added 2022-12-03) + throw new MTLog.Fatal("%s: Trying to merge different direction stop for same stop %s VS %s.", logTag, ds1.toStringSameDirection(), ds2.toStringSameDirection()); + } + if (ds1.isAlwaysLastTripStop() != ds2.isAlwaysLastTripStop()) { + ds1.setAlwaysLastTripStop(false); } - // MTLog.logDebug("%s: Merged same stop %s (1=2).", logTag, ts1.toStringSimple()); - newList.add(ts1); - newListStopIds.add(ts1.getStopId()); - last = ts1; + // MTLog.logDebug("%s: Merged same stop %s (1:%s=2:%s).", logTag, ds1.getStopId(), ds1.toStringSimple(), ds2.toStringSimple()); + newList.add(ds1); + newListStopIds.add(ds1.getStopId()); + last = ds1; i1++; i2++; continue; } // find next match // look for stop in other list - s2InL1 = list1StopIds.contains(ts2.getStopId()); - s1InL2 = list2StopIds.contains(ts1.getStopId()); + s2InL1 = list1StopIds.contains(ds2.getStopId()); + s1InL2 = list2StopIds.contains(ds1.getStopId()); if (s2InL1 && !s1InL2) { - MTLog.logDebug("%s: Merged stop %s from 1st list NOT present in 2nd list (1).", logTag, ts1.toStringSameDirection()); - newList.add(ts1); - newListStopIds.add(ts1.getStopId()); - last = ts1; + MTLog.logDebug("%s: Merged stop %s from 1st list NOT present in 2nd list (1).", logTag, ds1.toStringSameDirection()); + newList.add(ds1); + newListStopIds.add(ds1.getStopId()); + last = ds1; i1++; continue; } if (!s2InL1 && s1InL2) { - MTLog.logDebug("%s: Merged stop %s from 2nd list NOT present in 1st list (2).", logTag, ts2.toStringSameDirection()); - newList.add(ts2); - newListStopIds.add(ts2.getStopId()); - last = ts2; + MTLog.logDebug("%s: Merged stop %s from 2nd list NOT present in 1st list (2).", logTag, ds2.toStringSameDirection()); + newList.add(ds2); + newListStopIds.add(ds2.getStopId()); + last = ds2; i2++; continue; } @@ -955,52 +952,52 @@ private ArrayList mergeMyDirectionStopLists(long directionId, @N lastInL2 = list2StopIds.contains(last.getStopId()); if (lastInL1 && !lastInL2) { MTLog.log(this.routeId - + ": pick t:" + ts1.getDirectionId() + ">s:" + ts1.getStopId() + + ": pick d:" + ds1.getDirectionId() + ">s:" + ds1.getStopId() + " in same list w/ s:" + last.getStopId() - + " instead of t:" + ts2.getDirectionId() + ">s:" + ts2.getStopId()); - newList.add(ts1); - newListStopIds.add(ts1.getStopId()); - last = ts1; + + " instead of d:" + ds2.getDirectionId() + ">s:" + ds2.getStopId()); + newList.add(ds1); + newListStopIds.add(ds1.getStopId()); + last = ds1; i1++; continue; } if (!lastInL1 && lastInL2) { MTLog.log(this.routeId - + ": pick t:" + ts2.getDirectionId() + ">s:" + ts2.getStopId() + + ": pick d:" + ds2.getDirectionId() + ">s:" + ds2.getStopId() + " in same list w/ s:" + last.getStopId() - + " instead of t:" + ts1.getDirectionId() + ">s:" + ts1.getStopId()); - newList.add(ts2); - newListStopIds.add(ts2.getStopId()); - last = ts2; + + " instead of d:" + ds1.getDirectionId() + ">s:" + ds1.getStopId()); + newList.add(ds2); + newListStopIds.add(ds2.getStopId()); + last = ds2; i2++; continue; } } - ts1GStop = this.gStopsCache.get(ts1.getStopId()); - ts2GStop = this.gStopsCache.get(ts2.getStopId()); + ds1GStop = this.gStopsCache.get(ds1.getStopId()); + ds2GStop = this.gStopsCache.get(ds2.getStopId()); if (last != null) { lastGStop = this.gStopsCache.get(last.getStopId()); - ts1GStop = this.gStopsCache.get(ts1.getStopId()); - ts2GStop = this.gStopsCache.get(ts2.getStopId()); - ts1Distance = findDistance(lastGStop.getStopLat(), lastGStop.getStopLong(), ts1GStop.getStopLat(), ts1GStop.getStopLong()); - ts2Distance = findDistance(lastGStop.getStopLat(), lastGStop.getStopLong(), ts2GStop.getStopLat(), ts2GStop.getStopLong()); - if (ts1Distance < ts2Distance) { + ds1GStop = this.gStopsCache.get(ds1.getStopId()); + ds2GStop = this.gStopsCache.get(ds2.getStopId()); + ds1Distance = findDistance(lastGStop.getStopLat(), lastGStop.getStopLong(), ds1GStop.getStopLat(), ds1GStop.getStopLong()); + ds2Distance = findDistance(lastGStop.getStopLat(), lastGStop.getStopLong(), ds2GStop.getStopLat(), ds2GStop.getStopLong()); + if (ds1Distance < ds2Distance) { MTLog.log(this.routeId - + ": pick t:" + ts1.getDirectionId() + ">" + ts1GStop.toStringPlus() - + " closest (" + (ts2Distance - ts1Distance) + ") to " + lastGStop.toStringPlus() - + " instead of t:" + ts2.getDirectionId() + ">" + ts2GStop.toStringPlus()); - newList.add(ts1); - newListStopIds.add(ts1.getStopId()); - last = ts1; + + ": pick d:" + ds1.getDirectionId() + ">" + ds1GStop.toStringPlus() + + " closest (" + (ds2Distance - ds1Distance) + ") to " + lastGStop.toStringPlus() + + " instead of d:" + ds2.getDirectionId() + ">" + ds2GStop.toStringPlus()); + newList.add(ds1); + newListStopIds.add(ds1.getStopId()); + last = ds1; i1++; } else { MTLog.log(this.routeId - + ": pick t:" + ts2.getDirectionId() + ">" + ts2GStop.toStringPlus() - + " closest (" + (ts1Distance - ts2Distance) + ") to " + lastGStop.toStringPlus() - + " instead of t:" + ts1.getDirectionId() + ">" + ts1GStop.toStringPlus()); - newList.add(ts2); - newListStopIds.add(ts2.getStopId()); - last = ts2; + + ": pick d:" + ds2.getDirectionId() + ">" + ds2GStop.toStringPlus() + + " closest (" + (ds1Distance - ds2Distance) + ") to " + lastGStop.toStringPlus() + + " instead of d:" + ds1.getDirectionId() + ">" + ds1GStop.toStringPlus()); + newList.add(ds2); + newListStopIds.add(ds2.getStopId()); + last = ds2; i2++; } continue; @@ -1009,41 +1006,41 @@ private ArrayList mergeMyDirectionStopLists(long directionId, @N commonStopAndPrevious = findFirstCommonStop(list1, list2); if (commonStopAndPrevious.length >= 3) { commonGStop = this.gStopsCache.get(commonStopAndPrevious[0].getStopId()); - previousTs1GStop = this.gStopsCache.get(ts1.getStopId()); - previousTs2GStop = this.gStopsCache.get(ts2.getStopId()); - previousTs1Distance = findDistance(commonGStop.getStopLat(), commonGStop.getStopLong(), previousTs1GStop.getStopLat(), - previousTs1GStop.getStopLong()); - previousTs2Distance = findDistance(commonGStop.getStopLat(), commonGStop.getStopLong(), previousTs2GStop.getStopLat(), - previousTs2GStop.getStopLong()); - MTLog.log(this.routeId + ": Resolved using 1st common stop direction ID:" + ts1.getDirectionId() + ", stop IDs:" - + ts1.getStopId() + "," + ts2.getStopId() + " (" - + commonStopAndPrevious[1].getStopId() + ":" + previousTs1Distance + ", " - + commonStopAndPrevious[2].getStopId() + ":" + previousTs2Distance + ")"); - if (previousTs1Distance > previousTs2Distance) { - newList.add(ts1); - newListStopIds.add(ts1.getStopId()); - last = ts1; + previousDs1GStop = this.gStopsCache.get(ds1.getStopId()); + previousDs2GStop = this.gStopsCache.get(ds2.getStopId()); + previousDs1Distance = findDistance(commonGStop.getStopLat(), commonGStop.getStopLong(), previousDs1GStop.getStopLat(), + previousDs1GStop.getStopLong()); + previousDs2Distance = findDistance(commonGStop.getStopLat(), commonGStop.getStopLong(), previousDs2GStop.getStopLat(), + previousDs2GStop.getStopLong()); + MTLog.log(this.routeId + ": Resolved using 1st common stop direction ID:" + ds1.getDirectionId() + ", stop IDs:" + + ds1.getStopId() + "," + ds2.getStopId() + " (" + + commonStopAndPrevious[1].getStopId() + ":" + previousDs1Distance + ", " + + commonStopAndPrevious[2].getStopId() + ":" + previousDs2Distance + ")"); + if (previousDs1Distance > previousDs2Distance) { + newList.add(ds1); + newListStopIds.add(ds1.getStopId()); + last = ds1; i1++; } else { - newList.add(ts2); - newListStopIds.add(ts2.getStopId()); - last = ts2; + newList.add(ds2); + newListStopIds.add(ds2.getStopId()); + last = ds2; i2++; } continue; } - if (ts1GStop.getStopLat() < ts2GStop.getStopLat() || ts1GStop.getStopLong() < ts2GStop.getStopLong()) { - MTLog.logDebug("%s: Merged stop %s using arbitrary lat long (1).", logTag, ts1.toStringSameDirection()); - newList.add(ts1); - newListStopIds.add(ts1.getStopId()); - last = ts1; + if (ds1GStop.getStopLat() < ds2GStop.getStopLat() || ds1GStop.getStopLong() < ds2GStop.getStopLong()) { + MTLog.logDebug("%s: Merged stop %s using arbitrary lat long (1).", logTag, ds1.toStringSameDirection()); + newList.add(ds1); + newListStopIds.add(ds1.getStopId()); + last = ds1; i1++; } else { - MTLog.logDebug("%s: Merged stop %s using arbitrary lat long (2).", logTag, ts2.toStringSameDirection()); - newList.add(ts2); - newListStopIds.add(ts2.getStopId()); - last = ts2; + MTLog.logDebug("%s: Merged stop %s using arbitrary lat long (2).", logTag, ds2.toStringSameDirection()); + newList.add(ds2); + newListStopIds.add(ds2.getStopId()); + last = ds2; i2++; } //noinspection UnnecessaryContinue @@ -1051,14 +1048,14 @@ private ArrayList mergeMyDirectionStopLists(long directionId, @N } // add remaining stops if (i1 < list1.size()) { - MTLog.logDebug("%s: Merged remaining %s stops from (1).", logTag, list1.size()); + MTLog.logDebug("%s: Merged remaining %s stops from (1).", logTag, list1.size() - i1); } //noinspection ForLoopReplaceableByWhile for (; i1 < list1.size(); ) { newList.add(list1.get(i1++)); } if (i2 < list2.size()) { - MTLog.logDebug("%s: Merged remaining %s stops from (2).", logTag, list2.size()); + MTLog.logDebug("%s: Merged remaining %s stops from (2).", logTag, list2.size() - i2); } //noinspection ForLoopReplaceableByWhile for (; i2 < list2.size(); ) { diff --git a/src/main/java/org/mtransit/parser/mt/data/MDirection.kt b/src/main/java/org/mtransit/parser/mt/data/MDirection.kt index 9e2a3b0..d097b42 100644 --- a/src/main/java/org/mtransit/parser/mt/data/MDirection.kt +++ b/src/main/java/org/mtransit/parser/mt/data/MDirection.kt @@ -5,51 +5,13 @@ import org.mtransit.commons.sql.SQLUtils import org.mtransit.parser.MTLog import org.mtransit.parser.db.SQLUtils.quotesEscape -data class MDirection( +data class MDirection @JvmOverloads constructor( val routeId: Long, var headsignId: Int = 0, // >= 0 (almost = direction ID) var headsignType: Int = HEADSIGN_TYPE_STRING, // 0=string, 1=direction, 2=inbound, 3=stopId, 4=descent-only var headsignValue: String = HEADSIGN_DEFAULT_VALUE, ) : Comparable { - constructor( - routeId: Long - ) : this( - routeId = routeId, - headsignId = 0, - headsignType = HEADSIGN_TYPE_STRING, - headsignValue = HEADSIGN_DEFAULT_VALUE, - ) - - constructor( - routeId: Long, - headsignString: String, - headsignId: Int - ) : this(routeId) { - setHeadsignString(headsignString, headsignId) - } - - constructor( - routeId: Long, - direction: MDirectionCardinalType - ) : this(routeId) { - setHeadsignDirection(direction) - } - - constructor( - routeId: Long, - inbound: MDirectionInboundType - ) : this(routeId) { - setHeadsignInbound(inbound) - } - - constructor( - routeId: Long, - stop: MStop - ) : this(routeId) { - setHeadsignStop(stop) - } - private var _id: Long = -1L val id: Long get() { diff --git a/src/main/java/org/mtransit/parser/mt/data/MDirectionStop.kt b/src/main/java/org/mtransit/parser/mt/data/MDirectionStop.kt index f5138a1..8fee395 100644 --- a/src/main/java/org/mtransit/parser/mt/data/MDirectionStop.kt +++ b/src/main/java/org/mtransit/parser/mt/data/MDirectionStop.kt @@ -1,45 +1,34 @@ package org.mtransit.parser.mt.data +import org.mtransit.commons.FeatureFlags import org.mtransit.commons.sql.SQLUtils +import org.mtransit.commons.sql.toSQL -data class MDirectionStop( +data class MDirectionStop @JvmOverloads constructor( val directionId: Long, val stopId: Int, var stopSequence: Int, - var isNoPickup: Boolean = false + var isAlwaysLastTripStop: Boolean = false, + var isNoPickup: Boolean = false, ) : Comparable { - // JAVA - constructor( - directionId: Long, - stopId: Int, - stopSequence: Int - ) : this( - directionId, - stopId, - stopSequence, - false - ) - val uID by lazy { getNewUID(directionId, stopId) } - fun equalsExceptStopSequence(ts: MDirectionStop): Boolean { - if (ts.directionId != 0L && ts.directionId != directionId) { - return false - } - @Suppress("RedundantIf") - if (ts.stopId != 0 && ts.stopId != stopId) { - return false - } + fun equalsDirectionAndStop(ts: MDirectionStop): Boolean { + if (ts.directionId != 0L && ts.directionId != directionId) return false + if (ts.stopId != 0 && ts.stopId != stopId) return false return true } - fun toFile() = listOf( - directionId.toString(), // DIRECTION ID - stopId.toString(), // STOP ID - stopSequence.toString(), // STOP SEQUENCE - (if (isNoPickup) 1 else 0).toString(), // DROP OFF ONLY - ).joinToString(SQLUtils.COLUMN_SEPARATOR) + fun toFile() = buildList { + add(directionId.toString()) + add(stopId.toString()) + add(stopSequence.toString()) + add(isNoPickup.toSQL().toString()) + if (FeatureFlags.F_EXPORT_DIRECTION_STOP_LAST) { + add(isAlwaysLastTripStop.toSQL().toString()) + } + }.joinToString(SQLUtils.COLUMN_SEPARATOR) override fun compareTo(other: MDirectionStop): Int { // sort by direction_id => stop_sequence @@ -49,23 +38,41 @@ data class MDirectionStop( } @Suppress("unused") - fun toStringSimple(): String { - return "DS{$directionId>$stopId[$stopSequence](${if (this.isNoPickup) 0 else 1})" + fun toStringSimple() = buildString { + append("DS{") + append(directionId) + append(">") + append(stopId) + append("[").append(stopSequence).append("]") + if (isNoPickup) { + append("(noPickup)") + } + if (isAlwaysLastTripStop) { + append("(last)") + } + append("}") } @Suppress("unused") - fun toStringSameDirection(): String { - return "${this.stopSequence}:${this.stopId}" + fun toStringSameDirection() = buildString { + append(stopSequence) + append(":") + append(stopId) + if (isNoPickup) { + append("(NP)") + } + if (isAlwaysLastTripStop) { + append("(last)") + } } companion object { private const val UID_SEPARATOR = "+" // int IDs can be negative - @Suppress("unused") @JvmStatic - fun containsStopIds(mainList: List, otherList: List): Boolean { - return toStopIds(mainList).contains(toStopIds(otherList)) + fun List.containsStopIds(otherList: List): Boolean { + return toStopIds(this).contains(toStopIds(otherList)) } @Suppress("unused") @@ -80,6 +87,12 @@ data class MDirectionStop( return "[${l.size}] > ${l.joinToString { it.toStringSameDirection() }}" } + @Suppress("unused") + @JvmStatic + fun toStringSimple(l: List): String { + return "[${l.size}] > ${l.joinToString { it.toStringSimple() }}" + } + @JvmStatic fun getNewUID( directionId: Long, diff --git a/src/main/java/org/mtransit/parser/mt/data/MSchedule.kt b/src/main/java/org/mtransit/parser/mt/data/MSchedule.kt index 0a39bff..2eaf138 100644 --- a/src/main/java/org/mtransit/parser/mt/data/MSchedule.kt +++ b/src/main/java/org/mtransit/parser/mt/data/MSchedule.kt @@ -9,7 +9,7 @@ import org.mtransit.parser.db.SQLUtils.quotesEscape import org.mtransit.parser.gtfs.GAgencyTools import org.mtransit.parser.gtfs.data.GIDs -data class MSchedule( +data class MSchedule( // MStopTime val routeId: Long, val serviceIdInt: Int, val directionId: Long, @@ -113,7 +113,7 @@ data class MSchedule( lastSchedule?.departure } ?: 0 if (FeatureFlags.F_SCHEDULE_IN_MINUTES) { - add((departure - lastDeparture).div(100).toString()) // truncates the time to an minute that is closer to 0 + add((departure - lastDeparture).div(100).toString()) // truncates the time to a minute that is closer to 0 } else { add((departure - lastDeparture).toString()) } @@ -121,7 +121,7 @@ data class MSchedule( if (FeatureFlags.F_EXPORT_ARRIVAL_W_TRIP_ID) { var arrivalDiff = (departure - arrival).takeIf { it > MIN_ARRIVAL_DIFF_IN_HH_MM_SS } if (FeatureFlags.F_SCHEDULE_IN_MINUTES) { - arrivalDiff = arrivalDiff?.div(100) // truncates the time to an minute that is closer to 0 + arrivalDiff = arrivalDiff?.div(100) // truncates the time to a minute that is closer to 0 } add(arrivalDiff?.toString().orEmpty()) }