diff --git a/package.json b/package.json index 04db913e0..a74fd6535 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "canvas": "^2.11.2", "canvg": "^4.0.2", "date-fns": "^3.6.0", + "dice-coefficient": "^2.1.1", "image-size": "^1.1.1", "isomorphic-unfetch": "^4.0.2", "isomorphic-xml2js": "^0.1.3", diff --git a/src/components/FeaturePanel/FeaturePanel.tsx b/src/components/FeaturePanel/FeaturePanel.tsx index 344f70a9c..2cc3aecb6 100644 --- a/src/components/FeaturePanel/FeaturePanel.tsx +++ b/src/components/FeaturePanel/FeaturePanel.tsx @@ -24,6 +24,7 @@ import { ClimbingRouteGrade } from './ClimbingRouteGrade'; import { Box } from '@mui/material'; import { ClimbingGuideInfo } from './Climbing/ClimbingGuideInfo'; import { ClimbingStructuredData } from './Climbing/ClimbingStructuredData'; +import { isPublictransportRoute } from '../../utils'; const Flex = styled.div` flex: 1; @@ -80,11 +81,11 @@ export const FeaturePanel = ({ headingRef }: FeaturePanelProps) => { {!isClimbingCrag && } - + {!isPublictransportRoute(feature) && } {advanced && } {isClimbingCrag && } - + diff --git a/src/components/FeaturePanel/PublicTransport/PublicTransport.tsx b/src/components/FeaturePanel/PublicTransport/PublicTransport.tsx index e48a76749..5e217756a 100644 --- a/src/components/FeaturePanel/PublicTransport/PublicTransport.tsx +++ b/src/components/FeaturePanel/PublicTransport/PublicTransport.tsx @@ -1,99 +1,19 @@ -import groupBy from 'lodash/groupBy'; -import { useQuery } from 'react-query'; import React from 'react'; -import { Typography } from '@mui/material'; -import { LineInformation, requestLines } from './requestRoutes'; -import { PublicTransportCategory } from './PublicTransportWrapper'; -import { FeatureTags } from '../../../services/types'; -import { DotLoader } from '../../helpers'; -import { sortByReference } from './helpers'; +import { PublicTransportInner } from './routes/Routes'; +import { PublicTransportRoute } from './route/PublicTransportRoute'; import { useFeatureContext } from '../../utils/FeatureContext'; -import { getOverpassSource } from '../../../services/mapStorage'; -import { EMPTY_GEOJSON_SOURCE } from '../../Map/consts'; +import { isPublictransportStop, isPublictransportRoute } from '../../../utils'; -interface PublicTransportProps { - tags: FeatureTags; -} - -const categories = [ - 'tourism', - 'subway', - 'commuter', - 'regional', - 'long_distance', - 'high_speed', - 'night', - 'car', - 'car_shuttle', - 'bus', - 'unknown', -]; - -const PublicTransportDisplay = ({ routes }) => { - const grouped = groupBy(routes, ({ service }) => { - const base = service?.split(';')[0]; - return categories.includes(base) ? base : 'unknown'; - }); - const entries = Object.entries(grouped) as [string, LineInformation[]][]; - const sorted = sortByReference(entries, categories, ([category]) => category); - - return ( - <> - {sorted.map(([category, lines]) => ( - 1} - /> - ))} - - ); -}; - -const PublicTransportInner = () => { +export const PublicTransport = () => { const { feature } = useFeatureContext(); - const { id, type } = feature.osmMeta; - - const { data, status } = useQuery([id, type], () => - requestLines(type, Number(id)), - ); - React.useEffect(() => { - if (!data) { - return; - } - - const source = getOverpassSource(); - source?.setData(data.geoJson); - - return () => { - source?.setData({ type: 'FeatureCollection', features: [] }); - }; - }, [data]); - - return ( -
- {(status === 'loading' || status === 'idle') && } - {status === 'success' && } - {status === 'error' && ( - - Error - - )} -
- ); -}; - -export const PublicTransport: React.FC = ({ tags }) => { - const isPublicTransport = - Object.keys(tags).includes('public_transport') || - tags.railway === 'station' || - tags.railway === 'halt'; + if (isPublictransportStop(feature)) { + return ; + } - if (!isPublicTransport) { - return null; + if (isPublictransportRoute(feature)) { + return ; } - return ; + return null; }; diff --git a/src/components/FeaturePanel/PublicTransport/route/PublicTransportRoute.tsx b/src/components/FeaturePanel/PublicTransport/route/PublicTransportRoute.tsx new file mode 100644 index 000000000..3550af89a --- /dev/null +++ b/src/components/FeaturePanel/PublicTransport/route/PublicTransportRoute.tsx @@ -0,0 +1,55 @@ +import { useFeatureContext } from '../../../utils/FeatureContext'; +import { Feature } from '../../../../services/types'; +import React from 'react'; +import { t } from '../../../../services/intl'; +import { getUrlOsmId } from '../../../../services/helpers'; +import { Stops } from './Stops'; + +export const PublicTransportRoute = () => { + const { feature } = useFeatureContext(); + + if (!feature.memberFeatures) return null; + if (!feature.members) return null; + + const stopIds = feature.members + .filter(({ role }) => role === 'stop') + .map(({ ref, type }) => getUrlOsmId({ id: ref, type })); + const stops = feature.memberFeatures.filter(({ osmMeta }) => + stopIds.includes(getUrlOsmId(osmMeta)), + ); + + if (stops.length === 0) { + return null; + } + + return ; +}; + +const StopList = ({ stops }: { stops: Feature[] }) => { + const [minimized, setMinimized] = React.useState(stops.length > 7); + const getStops = React.useCallback( + () => (minimized ? [stops[0], stops[stops.length - 1]] : stops), + [minimized, stops], + ); + const [renderedStops, setRenderedStops] = React.useState(getStops()); + + React.useEffect(() => { + setRenderedStops(getStops()); + }, [minimized, getStops]); + + return ( + <> +

{t('publictransport.route')}

+ { + setMinimized(false); + }} + onCollapse={() => { + setMinimized(true); + }} + /> + + ); +}; diff --git a/src/components/FeaturePanel/PublicTransport/route/Stop.tsx b/src/components/FeaturePanel/PublicTransport/route/Stop.tsx new file mode 100644 index 000000000..28e5ad5f2 --- /dev/null +++ b/src/components/FeaturePanel/PublicTransport/route/Stop.tsx @@ -0,0 +1,46 @@ +import TripOriginIcon from '@mui/icons-material/TripOrigin'; + +export type StopSection = 'start' | 'middle' | 'end'; + +type Props = { + color: string; + isFirst: boolean; + isLast: boolean; + showCircle?: boolean; +}; + +export const Stop = ({ color, isFirst, isLast, showCircle = true }: Props) => ( +
+
+ {showCircle && ( + + )} +
+
+); diff --git a/src/components/FeaturePanel/PublicTransport/route/Stops.tsx b/src/components/FeaturePanel/PublicTransport/route/Stops.tsx new file mode 100644 index 000000000..ad84ed4e2 --- /dev/null +++ b/src/components/FeaturePanel/PublicTransport/route/Stops.tsx @@ -0,0 +1,120 @@ +import Link from 'next/link'; +import { Feature } from '../../../../services/types'; +import { StationItem, StationsList } from './helpers'; +import { IconButton, Typography } from '@mui/material'; +import { t } from '../../../../services/intl'; +import { getUrlOsmId } from '../../../../services/helpers'; +import TurnLeftIcon from '@mui/icons-material/TurnLeft'; + +type ShowMoreLessButtonProps = { + type: 'collapse' | 'expand'; + stopCount: number; + onClick?: (e: unknown) => void; +}; + +const ShowMoreLessButton = ({ + type, + onClick, + stopCount, +}: ShowMoreLessButtonProps) => ( + + + + + {type === 'collapse' + ? t('publictransport.visible_stops', { + amount: stopCount - 2, + }) + : t('publictransport.hidden_stops', { + amount: stopCount - 2, + })} + +); + +const StationInner = ({ + stop, + stopCount, + onExpand, + onCollapse, +}: { + stop: Feature | 'collapse' | 'expand'; + stopCount: number; + onExpand: () => void; + onCollapse: () => void; +}) => { + if (stop === 'expand') { + return ( + + { + onExpand(); + }} + > + + + {t('publictransport.hidden_stops', { + amount: stopCount - 2, + })} + + ); + } + if (stop === 'collapse') { + return ( + + { + onCollapse(); + }} + > + {t('publictransport.visible_stops', { + amount: stopCount - 2, + })} + + ); + } + + return {stop.tags.name}; +}; + +type Props = { + stops: Feature[]; + stopCount: number; + onExpand: () => void; + onCollapse: () => void; +}; + +export const Stops = ({ stops, stopCount, onExpand, onCollapse }: Props) => { + const hasFullLength = stops.length === stopCount; + return ( + + {stops.map((stop, i) => ( + <> + + + + {i === 0 && ( + + + + )} + + ))} + + ); +}; diff --git a/src/components/FeaturePanel/PublicTransport/route/helpers.tsx b/src/components/FeaturePanel/PublicTransport/route/helpers.tsx new file mode 100644 index 000000000..f41214e75 --- /dev/null +++ b/src/components/FeaturePanel/PublicTransport/route/helpers.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import styled from '@emotion/styled'; +import { Stop } from './Stop'; +import { useFeatureContext } from '../../../utils/FeatureContext'; +import { useUserThemeContext } from '../../../../helpers/theme'; +import { getBgColor } from '../routes/LineNumber'; + +const StationsListWrapper = styled.ul` + list-style-type: none; + padding: 0 0.5rem; + display: flex; + flex-direction: column; +`; + +export const StationsList: React.FC = ({ children }) => { + return {children}; +}; + +type ItemProps = { + isFirst?: boolean; + isLast?: boolean; + showCircle?: boolean; +}; + +export const StationItem: React.FC = ({ + children, + isFirst = false, + isLast = false, + showCircle = true, +}) => { + const { feature } = useFeatureContext(); + const { currentTheme } = useUserThemeContext(); + const color = getBgColor(feature.tags.colour, currentTheme === 'dark'); + return ( +
  • + +
    + {children} +
    +
  • + ); +}; diff --git a/src/components/FeaturePanel/PublicTransport/LineNumber.tsx b/src/components/FeaturePanel/PublicTransport/routes/LineNumber.tsx similarity index 88% rename from src/components/FeaturePanel/PublicTransport/LineNumber.tsx rename to src/components/FeaturePanel/PublicTransport/routes/LineNumber.tsx index a3c3396b1..6148a172c 100644 --- a/src/components/FeaturePanel/PublicTransport/LineNumber.tsx +++ b/src/components/FeaturePanel/PublicTransport/routes/LineNumber.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import { useUserThemeContext } from '../../../helpers/theme'; +import { useUserThemeContext } from '../../../../helpers/theme'; import styled from '@emotion/styled'; -import { osmColorToHex, whiteOrBlackText } from '../helpers/color'; +import { osmColorToHex, whiteOrBlackText } from '../../helpers/color'; import { Tooltip } from '@mui/material'; import Link from 'next/link'; import { LineInformation } from './requestRoutes'; -const getBgColor = (color: string | undefined, darkmode: boolean) => { +export const getBgColor = (color: string | undefined, darkmode: boolean) => { if (color) return osmColorToHex(color); return darkmode ? '#898989' : '#dddddd'; diff --git a/src/components/FeaturePanel/PublicTransport/PublicTransportWrapper.tsx b/src/components/FeaturePanel/PublicTransport/routes/PublicTransportWrapper.tsx similarity index 97% rename from src/components/FeaturePanel/PublicTransport/PublicTransportWrapper.tsx rename to src/components/FeaturePanel/PublicTransport/routes/PublicTransportWrapper.tsx index ac4136a11..339736827 100644 --- a/src/components/FeaturePanel/PublicTransport/PublicTransportWrapper.tsx +++ b/src/components/FeaturePanel/PublicTransport/routes/PublicTransportWrapper.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { LineInformation } from './requestRoutes'; import { LineNumber } from './LineNumber'; -import { t } from '../../../services/intl'; +import { t } from '../../../../services/intl'; const PublicTransportWrapper = ({ children }) => { const divStyle: React.CSSProperties = { diff --git a/src/components/FeaturePanel/PublicTransport/routes/Routes.tsx b/src/components/FeaturePanel/PublicTransport/routes/Routes.tsx new file mode 100644 index 000000000..06da2f80f --- /dev/null +++ b/src/components/FeaturePanel/PublicTransport/routes/Routes.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { LineInformation, requestLines } from './requestRoutes'; +import { PublicTransportCategory } from './PublicTransportWrapper'; +import { DotLoader } from '../../../helpers'; +import { sortByReference } from './helpers'; +import { useFeatureContext } from '../../../utils/FeatureContext'; +import { getOverpassSource } from '../../../../services/mapStorage'; +import groupBy from 'lodash/groupBy'; +import { useQuery } from 'react-query'; +import { Typography } from '@mui/material'; + +const categories = [ + 'tourism', + 'subway', + 'commuter', + 'regional', + 'long_distance', + 'high_speed', + 'night', + 'car', + 'car_shuttle', + 'bus', + 'unknown', +]; + +const PublicTransportDisplay = ({ routes }) => { + const grouped = groupBy(routes, ({ service }) => { + const base = service?.split(';')[0]; + return categories.includes(base) ? base : 'unknown'; + }); + const entries = Object.entries(grouped) as [string, LineInformation[]][]; + const sorted = sortByReference(entries, categories, ([category]) => category); + + return ( + <> + {sorted.map(([category, lines]) => ( + 1} + /> + ))} + + ); +}; + +export const PublicTransportInner = () => { + const { feature } = useFeatureContext(); + const { id, type } = feature.osmMeta; + + const { data, status } = useQuery([id, type], () => + requestLines(type, Number(id)), + ); + + React.useEffect(() => { + if (!data) { + return; + } + + const source = getOverpassSource(); + source?.setData(data.geoJson); + return () => { + source?.setData({ type: 'FeatureCollection', features: [] }); + }; + }, [data]); + + return ( +
    + {(status === 'loading' || status === 'idle') && } + {status === 'success' && } + {status === 'error' && ( + + Error + + )} +
    + ); +}; diff --git a/src/components/FeaturePanel/PublicTransport/__tests__/helpers.test.ts b/src/components/FeaturePanel/PublicTransport/routes/__tests__/helpers.test.ts similarity index 100% rename from src/components/FeaturePanel/PublicTransport/__tests__/helpers.test.ts rename to src/components/FeaturePanel/PublicTransport/routes/__tests__/helpers.test.ts diff --git a/src/components/FeaturePanel/PublicTransport/__tests__/requestRoutes.test.ts b/src/components/FeaturePanel/PublicTransport/routes/__tests__/requestRoutes.test.ts similarity index 95% rename from src/components/FeaturePanel/PublicTransport/__tests__/requestRoutes.test.ts rename to src/components/FeaturePanel/PublicTransport/routes/__tests__/requestRoutes.test.ts index f6563649a..a16a640d1 100644 --- a/src/components/FeaturePanel/PublicTransport/__tests__/requestRoutes.test.ts +++ b/src/components/FeaturePanel/PublicTransport/routes/__tests__/requestRoutes.test.ts @@ -1,7 +1,7 @@ -import * as fetchModule from '../../../../services/fetch'; +import * as fetchModule from '../../../../../services/fetch'; import { requestLines } from '../requestRoutes'; -jest.mock('../../../../services/fetch', () => ({ +jest.mock('../../../../../services/fetch', () => ({ fetchJson: jest.fn(), })); diff --git a/src/components/FeaturePanel/PublicTransport/helpers.ts b/src/components/FeaturePanel/PublicTransport/routes/helpers.ts similarity index 100% rename from src/components/FeaturePanel/PublicTransport/helpers.ts rename to src/components/FeaturePanel/PublicTransport/routes/helpers.ts diff --git a/src/components/FeaturePanel/PublicTransport/requestRoutes.ts b/src/components/FeaturePanel/PublicTransport/routes/requestRoutes.ts similarity index 95% rename from src/components/FeaturePanel/PublicTransport/requestRoutes.ts rename to src/components/FeaturePanel/PublicTransport/routes/requestRoutes.ts index 2eec3d895..58e9f3334 100644 --- a/src/components/FeaturePanel/PublicTransport/requestRoutes.ts +++ b/src/components/FeaturePanel/PublicTransport/routes/requestRoutes.ts @@ -1,10 +1,10 @@ import groupBy from 'lodash/groupBy'; -import { fetchJson } from '../../../services/fetch'; +import { fetchJson } from '../../../../services/fetch'; import { getOverpassUrl, overpassGeomToGeojson, -} from '../../../services/overpassSearch'; -import { intl } from '../../../services/intl'; +} from '../../../../services/overpassSearch'; +import { intl } from '../../../../services/intl'; type WithTags = { tags: Record }; diff --git a/src/components/FeaturePanel/Runways/Runways.tsx b/src/components/FeaturePanel/Runways/Runways.tsx index 707d1a3eb..68f563c0c 100644 --- a/src/components/FeaturePanel/Runways/Runways.tsx +++ b/src/components/FeaturePanel/Runways/Runways.tsx @@ -9,7 +9,7 @@ import { DotLoader } from '../../helpers'; const RunwaysInner = () => { const { feature } = useFeatureContext(); - const { data, status } = useQuery('airport-runways', () => + const { data, status } = useQuery([feature.osmMeta], () => loadRunways(feature.osmMeta), ); diff --git a/src/components/SearchBox/options/preset.tsx b/src/components/SearchBox/options/preset.tsx index d0fd0b85c..6ce12985d 100644 --- a/src/components/SearchBox/options/preset.tsx +++ b/src/components/SearchBox/options/preset.tsx @@ -1,4 +1,8 @@ import match from 'autosuggest-highlight/match'; +import sum from 'lodash/sum'; +import orderBy from 'lodash/orderBy'; +import groupBy from 'lodash/groupBy'; +import { diceCoefficient } from 'dice-coefficient'; import FolderIcon from '@mui/icons-material/Folder'; import { Grid, Typography } from '@mui/material'; import React from 'react'; @@ -61,47 +65,66 @@ type PresetOptions = Promise<{ after: PresetOption[]; }>; -export const getPresetOptions = async (inputValue: string): PresetOptions => { +export const getPresetOptions = async ( + inputValue: string, + threshold = 0.3, +): PresetOptions => { if (inputValue.length <= 2) { return { before: [], after: [] }; } - const results = (await getPresetsForSearch()).map((preset) => { - const name = num(preset.name, inputValue) * 10; - const textsByOne = preset.texts.map((term) => num(term, inputValue)); - const sum = name + textsByOne.reduce((a, b) => a + b, 0); - return { name, textsByOne, sum, presetForSearch: preset }; + const presets = await getPresetsForSearch(); + const rawResults = presets.map((preset) => { + const nameSimilarity = diceCoefficient(preset.name, inputValue); + const textsByOneSimilarity = preset.texts.map((term) => + diceCoefficient(term, inputValue), + ); + return { + nameSimilarity, + textsByOneSimilarity, + sum: nameSimilarity * 10 + sum(textsByOneSimilarity), + presetForSearch: preset, + }; + }); + const grouped = groupBy(rawResults, ({ sum, nameSimilarity }) => { + if (nameSimilarity > threshold) { + return 'name'; + } + if (nameSimilarity === 0 && sum > threshold) { + return 'rest'; + } }); - const nameMatches = results - .filter((result) => result.name > 0) - .map((result) => ({ - type: 'preset' as const, - preset: result, - })); - - const rest = results - .filter((result) => result.name === 0 && result.sum > 0) - .map((result) => ({ - type: 'preset' as const, - preset: result, - })); - - const allResults = [...nameMatches, ...rest]; + const allResults = [ + ...orderBy(grouped.name, ({ sum }) => sum, 'desc'), + ...orderBy(grouped.rest, ({ sum }) => sum, 'desc'), + ].map((result) => ({ + type: 'preset' as const, + preset: result, + })); const before = allResults.slice(0, 2); const after = allResults.slice(2); return { before, after }; }; +const getAdditionalText = (preset: PresetOption['preset']) => { + const { textsByOneSimilarity } = preset; + const highestMatching = Math.max(...textsByOneSimilarity); + + if (preset.nameSimilarity >= highestMatching) { + return ''; + } + + const { texts } = preset.presetForSearch; + const matchingIndex = textsByOneSimilarity.indexOf(highestMatching); + const matchingText = texts[matchingIndex]; + return ` (${matchingText}…)`; +}; + export const renderPreset = ({ preset }: PresetOption, inputValue: string) => { const { name } = preset.presetForSearch; - const additionalText = - preset.name === 0 - ? ` (${preset.presetForSearch.texts.find( - (_, idx) => preset.textsByOne[idx] > 0, - )}…)` - : ''; + const additionalText = getAdditionalText(preset); return ( <> diff --git a/src/components/SearchBox/options/stars.tsx b/src/components/SearchBox/options/stars.tsx index 9e279efd1..dc97a6ca6 100644 --- a/src/components/SearchBox/options/stars.tsx +++ b/src/components/SearchBox/options/stars.tsx @@ -5,8 +5,8 @@ import React from 'react'; import { getHumanDistance, IconPart } from '../utils'; import type { Star } from '../../utils/StarsContext'; import { StarOption } from '../types'; -import match from 'autosuggest-highlight/match'; import { LonLat } from '../../../services/types'; +import { diceCoefficient } from 'dice-coefficient'; export const getStarsOptions = ( stars: Star[], @@ -14,19 +14,17 @@ export const getStarsOptions = ( ): StarOption[] => { const ratedStars = sortBy( stars - .map((star) => ({ - star, - // TODO matching is not optimal, maybe Sørensen–Dice coefficient - // https://www.npmjs.com/package/dice-coefficient - matching: - inputValue === '' - ? Infinity - : match(star.label, inputValue, { - insideWords: true, - findAllOccurrences: true, - }).length, - })) - .filter(({ matching }) => matching > 0), + .map((star) => { + if (inputValue === '') { + return { star, matching: Infinity }; + } + const matching = diceCoefficient(star.label, inputValue); + return { + star, + matching, + }; + }) + .filter(({ matching }) => matching > 0.2), ({ matching }) => matching, ); return ratedStars.map(({ star }) => ({ type: 'star', star })); diff --git a/src/components/SearchBox/types.ts b/src/components/SearchBox/types.ts index 18bc4a709..f85df47a3 100644 --- a/src/components/SearchBox/types.ts +++ b/src/components/SearchBox/types.ts @@ -48,8 +48,8 @@ export type OverpassOption = GenericOption< export type PresetOption = GenericOption< 'preset', { - name: number; - textsByOne: number[]; + nameSimilarity: number; + textsByOneSimilarity: number[]; sum: number; presetForSearch: { key: string; diff --git a/src/locales/de.js b/src/locales/de.js index f46c36b23..e0bb3ba1c 100644 --- a/src/locales/de.js +++ b/src/locales/de.js @@ -246,6 +246,10 @@ export default { 'publictransport.subway': 'U-Bahn', 'publictransport.unknown': 'Unbekannter Typ', + 'publictransport.route': 'Streckenverlauf', + 'publictransport.hidden_stops': '__amount__ weitere Haltestellen', + 'publictransport.visible_stops': 'Blende __amount__ Haltestellen aus', + 'runway.information': 'Landebahninformationen', 'runway.runway': 'Landebahn', 'runway.size': 'Länge (m) - Breite (m)', diff --git a/src/locales/vocabulary.js b/src/locales/vocabulary.js index a2da074e1..10b19d0c8 100644 --- a/src/locales/vocabulary.js +++ b/src/locales/vocabulary.js @@ -261,6 +261,10 @@ export default { 'publictransport.subway': 'Subway', 'publictransport.unknown': 'Unknown type', + 'publictransport.route': 'Route', + 'publictransport.hidden_stops': '__amount__ more stops', + 'publictransport.visible_stops': 'Hide __amount__ stops', + 'climbingpanel.draw_route': 'Draw route', 'climbingpanel.show_route_detail': 'Show route detail', diff --git a/src/services/osmApi.ts b/src/services/osmApi.ts index 5c5efff2c..77ac1fa9b 100644 --- a/src/services/osmApi.ts +++ b/src/services/osmApi.ts @@ -11,7 +11,12 @@ import { osmToFeature } from './osmToFeature'; import { getImageDefs, mergeMemberImageDefs } from './images/getImageDefs'; import * as Sentry from '@sentry/nextjs'; import { fetchOverpassCenter } from './overpass/fetchOverpassCenter'; -import { isClimbingRelation, isClimbingRoute, isRouteMaster } from '../utils'; +import { + isClimbingRelation, + isClimbingRoute, + isPublictransportRoute, + isRouteMaster, +} from '../utils'; import { getOverpassUrl } from './overpassSearch'; type GetOsmUrl = (object: OsmId) => string; @@ -224,7 +229,11 @@ export const addMembersAndParents = async ( return { ...feature, parentFeatures }; } - if (isRouteMaster(feature) || isClimbingRelation(feature)) { + if ( + isClimbingRelation(feature) || + isPublictransportRoute(feature) || + isRouteMaster(feature) + ) { const [parentFeatures, featureWithMemberFeatures] = await Promise.all([ fetchParentFeatures(feature.osmMeta), addMemberFeaturesToRelation(feature), diff --git a/src/utils.ts b/src/utils.ts index 1de3a2a10..6fad6c6be 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -82,3 +82,11 @@ export const isClimbingRoute = (feature: Feature) => export const isRouteMaster = ({ tags, osmMeta }: Feature) => tags.type === 'route_master' && osmMeta.type === 'relation'; + +export const isPublictransportStop = ({ tags }: Feature) => + Object.keys(tags).includes('public_transport') || + tags.railway === 'station' || + tags.railway === 'halt'; + +export const isPublictransportRoute = ({ tags }: Feature) => + tags.type === 'route'; diff --git a/yarn.lock b/yarn.lock index 825fc19d4..381218d4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3152,6 +3152,13 @@ detect-node@^2.0.4, detect-node@^2.1.0: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +dice-coefficient@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/dice-coefficient/-/dice-coefficient-2.1.1.tgz#d8ebb51f021ab6069994e7ef36842184771f616f" + integrity sha512-vPTcHmOQAuGvU6eyBtj7QCBwDJh2I7QpbBU51lbgfv7592KjBl6dm0baRBSh9ekt2X91MNAz7OpJrXCIUtDzlw== + dependencies: + n-gram "^2.0.0" + diff-sequences@^29.6.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -5925,6 +5932,11 @@ murmurhash-js@^1.0.0: resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" integrity sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E= +n-gram@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/n-gram/-/n-gram-2.0.2.tgz#e544a7dffefc49c22d898b2f491e787941b3a2ba" + integrity sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ== + nan@^2.17.0: version "2.20.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3"