Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DT-5452 Show closed stops on map using badge icons #4970

Open
wants to merge 12 commits into
base: v3
Choose a base branch
from
119 changes: 97 additions & 22 deletions app/component/map/tile-layer/Stops.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
import { VectorTile } from '@mapbox/vector-tile';
import Protobuf from 'pbf';
import pick from 'lodash/pick';
import { graphql, fetchQuery } from 'react-relay';
import moment from 'moment';
import {
drawTerminalIcon,
drawStopIcon,
drawHybridStopIcon,
drawHybridStationIcon,
} from '../../../util/mapIconUtils';
import { ExtendedRouteTypes } from '../../../constants';
import { ExtendedRouteTypes, DATE_FORMAT } from '../../../constants';
import {
isFeatureLayerEnabled,
getLayerBaseUrl,
} from '../../../util/mapLayerUtils';
import { PREFIX_ITINERARY_SUMMARY, PREFIX_ROUTES } from '../../../util/path';
import { fetchWithLanguageAndSubscription } from '../../../util/fetchUtils';

const stopAlertsQuery = graphql`
query StopsQuery($stopId: String!, $date: String!) {
stop: stop(id: $stopId) {
gtfsId
alerts: alerts(types: [STOP]) {
alertEffect
}
stoptimes: stoptimesForServiceDate(date: $date, omitCanceled: false) {
stoptimes {
serviceDay
}
}
}
}
`;

function isNull(val) {
return val === 'null' || val === undefined || val === null;
}
Expand Down Expand Up @@ -52,11 +70,11 @@ class Stops {
this.tile.hilightedStops.includes(feature.properties.gtfsId);
let hasTrunkRoute = false;
let hasLocalTramRoute = false;
const routes = JSON.parse(feature.properties.routes);
if (
feature.properties.type === 'BUS' &&
this.config.useExtendedRouteTypes
) {
const routes = JSON.parse(feature.properties.routes);
if (routes.some(p => p.gtfsType === ExtendedRouteTypes.BusExpress)) {
hasTrunkRoute = true;
}
Expand All @@ -65,7 +83,6 @@ class Stops {
feature.properties.type === 'TRAM' &&
this.config.useExtendedRouteTypes
) {
const routes = JSON.parse(feature.properties.routes);
if (routes.some(p => p.gtfsType === ExtendedRouteTypes.SpeedTram)) {
hasLocalTramRoute = true;
}
Expand Down Expand Up @@ -93,21 +110,33 @@ class Stops {
if (hasLocalTramRoute) {
mode = 'speedtram';
}
const stopOutOfService = !!feature.properties.closedByServiceAlert;
const noServiceOnServiceDay =
feature.properties.servicesRunningOnServiceDate === undefined
? false
: !feature.properties.servicesRunningOnServiceDate;

drawStopIcon(
this.tile,
feature.geom,
mode,
!isNull(feature.properties.platform)
? feature.properties.platform
: false,
isHilighted,
!!(
feature.properties.type === 'FERRY' &&
!isNull(feature.properties.code)
),
this.config.colors.iconColors,
);
if (isHilighted && zoom <= minZoom) {
// Fetch stop details only when stop is highlighted and realtime layer is not used (zoom level)
this.drawHighlighted(feature, mode, isHilighted);
} else {
drawStopIcon(
this.tile,
feature.geom,
mode,
!isNull(feature.properties.platform)
? feature.properties.platform
: false,
isHilighted,
!!(
feature.properties.type === 'FERRY' &&
!isNull(feature.properties.code)
),
this.config.colors.iconColors,
stopOutOfService,
noServiceOnServiceDay,
);
}
}
}

Expand All @@ -123,8 +152,13 @@ class Stops {
}

getPromise(lang) {
const zoom = this.tile.coords.z + (this.tile.props.zoomOffset || 0);
const stopsUrl =
zoom >= this.config.stopsMinZoom
? this.config.URL.REALTIME_STOP_MAP
: this.config.URL.STOP_MAP;
return fetchWithLanguageAndSubscription(
`${getLayerBaseUrl(this.config.URL.STOP_MAP, lang)}${
`${getLayerBaseUrl(stopsUrl, lang)}${
this.tile.coords.z + (this.tile.props.zoomOffset || 0)
}/${this.tile.coords.x}/${this.tile.coords.y}.pbf`,
this.config,
Expand All @@ -145,19 +179,19 @@ class Stops {
this.tile.hilightedStops.length &&
this.tile.hilightedStops[0]
);
const stopLayer = vt.layers.stops || vt.layers.realtimeStops;

if (
vt.layers.stops != null &&
stopLayer != null &&
(this.tile.coords.z >= this.config.stopsMinZoom ||
hasHilightedStops)
) {
const featureByCode = {};
const hybridGtfsIdByCode = {};
const zoom = this.tile.coords.z + (this.tile.props.zoomOffset || 0);
const drawPlatforms = this.config.terminalStopsMaxZoom - 1 <= zoom;
const drawRailPlatforms = this.config.railPlatformsMinZoom <= zoom;
for (let i = 0, ref = vt.layers.stops.length - 1; i <= ref; i++) {
const feature = vt.layers.stops.feature(i);
for (let i = 0, ref = stopLayer.length - 1; i <= ref; i++) {
const feature = stopLayer.feature(i);
if (
isFeatureLayerEnabled(feature, 'stop', this.mapLayers) &&
feature.properties.type &&
Expand Down Expand Up @@ -305,6 +339,47 @@ class Stops {
);
});
}

drawHighlighted = (feature, mode, isHilighted) => {
const date = moment().format(DATE_FORMAT);
const callback = ({ stop: result }) => {
if (result) {
const stopOutOfService = result.alerts.some(
alert => alert.alertEffect === 'NO_SERVICE',
);
const noServiceOnServiceDay = !result.stoptimes.some(
stoptimes => stoptimes.stoptimes.length > 0,
);

drawStopIcon(
this.tile,
feature.geom,
mode,
!isNull(feature.properties.platform)
? feature.properties.platform
: false,
isHilighted,
!!(
feature.properties.type === 'FERRY' &&
!isNull(feature.properties.code)
),
this.config.colors.iconColors,
stopOutOfService,
noServiceOnServiceDay,
);
}
return this;
};

fetchQuery(
this.relayEnvironment,
stopAlertsQuery,
{ stopId: feature.properties.gtfsId, date },
{ force: true },
)
.toPromise()
.then(callback);
};
}

export default Stops;
2 changes: 1 addition & 1 deletion app/component/map/tile-layer/TileContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ class TileContainer {
}
if (
index < features.length - 1 &&
features[index + 1].feature.properties.code ===
features[index + 1]?.feature.properties.code ===
feature.feature.properties.code
) {
isCombo = true;
Expand Down
4 changes: 4 additions & 0 deletions app/configurations/config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export default {
default: `${POI_MAP_PREFIX}/fi/stops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/stops,stations/`,
},
REALTIME_STOP_MAP: {
default: `${POI_MAP_PREFIX}/fi/realtimeStops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/realtimeStops,stations/`,
},
RENTAL_STATION_MAP: {
default: `${POI_MAP_PREFIX}/fi/rentalStations/`,
},
Expand Down
4 changes: 4 additions & 0 deletions app/configurations/config.hsl.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export default {
default: `${POI_MAP_PREFIX}/fi/stops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/stops,stations/`,
},
REALTIME_STOP_MAP: {
default: `${POI_MAP_PREFIX}/fi/realtimeStops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/realtimeStops,stations/`,
},
RENTAL_STATION_MAP: {
default: `${POI_MAP_PREFIX}/fi/rentalStations/`,
},
Expand Down
4 changes: 4 additions & 0 deletions app/configurations/config.kela.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export default configMerger(matkaConfig, {
default: `${POI_MAP_PREFIX}/fi/stops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/stops,stations/`,
},
REALTIME_STOP_MAP: {
default: `${POI_MAP_PREFIX}/fi/realtimeStops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/realtimeStops,stations/`,
},
},

favicon: './app/configurations/images/default/default-favicon.png',
Expand Down
4 changes: 4 additions & 0 deletions app/configurations/config.varely.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export default configMerger(walttiConfig, {
default: `${POI_MAP_PREFIX}/fi/stops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/stops,stations/`,
},
REALTIME_STOP_MAP: {
default: `${POI_MAP_PREFIX}/fi/realtimeStops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/realtimeStops,stations/`,
},
},

feedIds: ['VARELY', 'FOLI', 'Rauma'],
Expand Down
4 changes: 4 additions & 0 deletions app/configurations/config.waltti.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export default {
default: `${POI_MAP_PREFIX}/fi/stops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/stops,stations/`,
},
REALTIME_STOP_MAP: {
default: `${POI_MAP_PREFIX}/fi/realtimeStops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/realtimeStops,stations/`,
},
RENTAL_STATION_MAP: {
default: `${POI_MAP_PREFIX}/fi/rentalStations/`,
},
Expand Down
4 changes: 4 additions & 0 deletions app/configurations/config.walttiOpas.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export default configMerger(walttiConfig, {
default: `${POI_MAP_PREFIX}/fi/stops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/stops,stations/`,
},
REALTIME_STOP_MAP: {
default: `${POI_MAP_PREFIX}/fi/realtimeStops,stations/`,
sv: `${POI_MAP_PREFIX}/sv/realtimeStops,stations/`,
},
RENTAL_STATION_MAP: {
default: `${POI_MAP_PREFIX}/fi/rentalStations/`,
},
Expand Down
55 changes: 55 additions & 0 deletions app/util/mapIconUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,19 @@ function drawIconImageBadge(
);
}

function drawTopRightCornerIconBadge(
image,
tile,
iconTopLeftCornerX,
iconTopLeftCornerY,
width,
badgeSize,
) {
const badgeX = iconTopLeftCornerX + width / 2; // badge left corner placed at the horizontal center of the icon
const badgeY = iconTopLeftCornerY - badgeSize / 3; // badge left corner placed a third above the icon
tile.ctx.drawImage(image, badgeX, badgeY);
}

function getSelectedIconCircleOffset(zoom, ratio) {
if (zoom > 15) {
return 94 / ratio;
Expand Down Expand Up @@ -350,6 +363,38 @@ function getSmallStopIcon(type, radius, color) {
});
}

/**
* Draw a badge icon on top of the icon.
*/
function drawStopStatusBadge(
tile,
x,
y,
iconWidth,
stopOutOfService,
noServiceOnServiceDay,
) {
const badgeSize = iconWidth * 0.75; // badge size is 75% of the icon size
const badgeImageId = stopOutOfService
? `icon-icon_stop-closed-badge`
: `icon-icon_stop-temporarily-closed-badge`;

if (noServiceOnServiceDay || stopOutOfService) {
getImageFromSpriteCache(badgeImageId, badgeSize, badgeSize).then(
badgeImage => {
drawTopRightCornerIconBadge(
badgeImage,
tile,
x,
y,
iconWidth,
badgeSize,
);
},
);
}
}

const getMemoizedStopIcon = memoize(
getSmallStopIcon,
(type, radius, color, isHilighted) =>
Expand All @@ -369,6 +414,8 @@ export function drawStopIcon(
isHilighted,
isFerryTerminal,
modeIconColors,
stopOutOfService,
noServiceOnServiceDay,
) {
if (type === 'SUBWAY') {
return;
Expand Down Expand Up @@ -417,6 +464,14 @@ export function drawStopIcon(
color,
).then(image => {
tile.ctx.drawImage(image, x, y);
drawStopStatusBadge(
tile,
x,
y,
width,
stopOutOfService,
noServiceOnServiceDay,
);
if (drawNumber && platformNumber) {
x += radius;
y += radius;
Expand Down
8 changes: 8 additions & 0 deletions static/assets/svg-sprite.default.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions static/assets/svg-sprite.hsl.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading