From 528da69120ad59774711eed4d31a0d190b9d9ad3 Mon Sep 17 00:00:00 2001 From: Peter Karich Date: Thu, 20 Aug 2026 13:24:03 +0200 Subject: [PATCH 01/26] initial version --- src/layers/UseQueryPointsLayer.tsx | 16 ++++--- src/layers/createMarkerSVG.ts | 30 +++++++++---- src/map/ContextMenuContent.module.css | 5 +++ src/map/ContextMenuContent.tsx | 6 +-- src/map/Marker.tsx | 54 ++++++++++++++++++----- src/sidebar/MobileSidebar.tsx | 8 +++- src/sidebar/instructions/Instructions.tsx | 16 +++---- src/sidebar/search/Search.module.css | 5 +++ 8 files changed, 101 insertions(+), 39 deletions(-) diff --git a/src/layers/UseQueryPointsLayer.tsx b/src/layers/UseQueryPointsLayer.tsx index 398807c6..0c6d5d11 100644 --- a/src/layers/UseQueryPointsLayer.tsx +++ b/src/layers/UseQueryPointsLayer.tsx @@ -10,9 +10,10 @@ import Dispatcher from '@/stores/Dispatcher' import { SetPoint } from '@/actions/Actions' import { coordinateToText } from '@/Converters' import { Icon, Style } from 'ol/style' -import { createSvg } from '@/layers/createMarkerSVG' +import { createCircle, createSvg } from '@/layers/createMarkerSVG' const MARKER_SIZE = 35 +const VIA_MARKER_SIZE = 26 export default function useQueryPointsLayer(map: Map, queryPoints: QueryPoint[]) { useEffect(() => { @@ -44,11 +45,13 @@ function addQueryPointsLayer(map: Map, queryPoints: QueryPoint[]) { const feature = new Feature({ geometry: new Point(fromLonLat([indexPoint.point.coordinate.lng, indexPoint.point.coordinate.lat])), }) + const isVia = indexPoint.point.type == QueryPointType.Via feature.set('gh:query_point', indexPoint.point) feature.set('gh:marker_props', { color: indexPoint.point.color, - number: indexPoint.point.type == QueryPointType.Via ? i : undefined, - size: MARKER_SIZE, + via: isVia, + number: isVia ? i : undefined, + size: isVia ? VIA_MARKER_SIZE : MARKER_SIZE, }) return feature }) @@ -62,13 +65,14 @@ function addQueryPointsLayer(map: Map, queryPoints: QueryPoint[]) { const cachedStyles: { [id: string]: Style } = {} queryPointsLayer.setStyle(feature => { const props = feature.get('gh:marker_props') - const key = props.number + '-' + props.color + '-' + props.size + const key = (props.via ? 'via' : 'marker') + '-' + props.number + '-' + props.color + '-' + props.size let style = cachedStyles[key] if (style) return style style = new Style({ image: new Icon({ - src: 'data:image/svg+xml;utf8,' + createSvg(props), - displacement: [0, MARKER_SIZE / 2], + src: 'data:image/svg+xml;utf8,' + (props.via ? createCircle(props) : createSvg(props)), + // the circle is centered on the coordinate, the marker points to it with its tip + displacement: props.via ? [0, 0] : [0, MARKER_SIZE / 2], }), }) cachedStyles[key] = style diff --git a/src/layers/createMarkerSVG.ts b/src/layers/createMarkerSVG.ts index 527c7e6b..bc48dde3 100644 --- a/src/layers/createMarkerSVG.ts +++ b/src/layers/createMarkerSVG.ts @@ -8,6 +8,26 @@ interface MarkerProps { size?: number } +// depending on the number of digits the font must be smaller so that e.g. '10' still fits into the circle +function circleFontSize(number: number | undefined) { + if (number === undefined || ('' + number).length <= 1) return 230 + return ('' + number).length === 2 ? 170 : 120 +} + +// draws a circle with a thick colored ring and a white center, used for via points. If a number is given it is +// displayed inside the circle. +export function createCircle({ color, number, size = 0 }: MarkerProps) { + return ` + ${ + number === undefined + ? '' + : `${number}` + } + ` +} + export function createPOI(pathD: string) { return ` @@ -24,18 +44,12 @@ export function createPOIMarker(pathD: string) { // todo: this is mostly duplicated from `Marker.tsx`, but we use a more elongated shape (MARKER_PATH). // To use `Marker.tsx` we would probably need to add ol.Overlays, i.e. create a div for each marker and insert the svg from `Marker.tsx`. -export function createSvg({ color, number, size = 0 }: MarkerProps) { +export function createSvg({ color, size = 0 }: MarkerProps) { return `` + ` } // todo: for some weird reason the markers are not shown when the color is given in hex format #012345 diff --git a/src/map/ContextMenuContent.module.css b/src/map/ContextMenuContent.module.css index c49163f8..e2fbe09f 100644 --- a/src/map/ContextMenuContent.module.css +++ b/src/map/ContextMenuContent.module.css @@ -7,6 +7,11 @@ button.entry div { margin-bottom: 2px; padding-right: 8px; + width: 20px; + display: flex; + justify-content: center; + align-items: center; + box-sizing: content-box; } .entry { diff --git a/src/map/ContextMenuContent.tsx b/src/map/ContextMenuContent.tsx index 657b2b59..4fec751a 100644 --- a/src/map/ContextMenuContent.tsx +++ b/src/map/ContextMenuContent.tsx @@ -7,7 +7,7 @@ import { AddPoint, SetPoint, MoveMapToPoint } from '@/actions/Actions' import { RouteStoreState } from '@/stores/RouteStore' import { findNextWayPoint } from '@/map/findNextWayPoint' import { tr } from '@/translation/Translation' -import { MarkerComponent } from '@/map/Marker' +import { CircleComponent, MarkerComponent } from '@/map/Marker' import { Coordinate } from '@/utils' export function ContextMenuContent({ @@ -92,7 +92,7 @@ export function ContextMenuContent({ diff --git a/src/map/Marker.tsx b/src/map/Marker.tsx index a047d072..f86e5379 100644 --- a/src/map/Marker.tsx +++ b/src/map/Marker.tsx @@ -16,11 +16,52 @@ interface MarkerProps { cursor?: string | undefined } +// depending on the number of digits the font must be smaller so that e.g. '10' still fits into the circle +function circleFontSize(number: string | undefined) { + if (number === undefined || number.length <= 1) return 230 + return number.length === 2 ? 170 : 120 +} + +/** + * This component draws a circle with a thick colored ring and a white center, used for via points. If a number is + * passed it is displayed inside the circle. + */ +export function CircleComponent({ color, number, size = 0, cursor }: MarkerProps) { + return ( + + ) +} + /** - * This component draws a marker. If a number is passed, the white circle of the marker is larger and displays the - * number. Otherwise the default marker from https://fontawesome.com/v5.15/icons/map-marker-alt?style=solid is taken + * This component draws the default marker from https://fontawesome.com/v5.15/icons/map-marker-alt?style=solid. + * If a number is passed the marker is a via point and drawn as a circle displaying the number. */ export function MarkerComponent({ color, number, size = 0, cursor }: MarkerProps) { + if (number !== undefined) return return ( ) } diff --git a/src/sidebar/MobileSidebar.tsx b/src/sidebar/MobileSidebar.tsx index c7cb5e72..62afbe10 100644 --- a/src/sidebar/MobileSidebar.tsx +++ b/src/sidebar/MobileSidebar.tsx @@ -6,7 +6,7 @@ import styles from './MobileSidebar.module.css' import Search from '@/sidebar/search/Search' import ErrorMessage from '@/sidebar/ErrorMessage' import { useMediaQuery } from 'react-responsive' -import { MarkerComponent } from '@/map/Marker' +import { CircleComponent, MarkerComponent } from '@/map/Marker' import RoutingProfiles from '@/sidebar/search/routingProfiles/RoutingProfiles' import OpenInputsIcon from './unfold.svg' import CloseInputsIcon from './unfold_less.svg' @@ -125,7 +125,11 @@ function SmallQueryPoint({ text, color, position }: { text: string; color: strin return (
- + {position === QueryPointType.Via ? ( + + ) : ( + + )}
{text}
diff --git a/src/sidebar/instructions/Instructions.tsx b/src/sidebar/instructions/Instructions.tsx index 3d74ec6e..b75bf589 100644 --- a/src/sidebar/instructions/Instructions.tsx +++ b/src/sidebar/instructions/Instructions.tsx @@ -20,7 +20,7 @@ import ptTransferTo from './pt_transfer_to.png' import ptEndTrip from './pt_end_trip.png' import { metersToText } from '@/Converters' import { Instruction } from '@/api/graphhopper' -import { MarkerComponent } from '@/map/Marker' +import { CircleComponent, MarkerComponent } from '@/map/Marker' import QueryStore, { QueryPointType } from '@/stores/QueryStore' import Dispatcher from '@/stores/Dispatcher' import { InstructionClicked } from '@/actions/Actions' @@ -66,20 +66,16 @@ const Line = function ({ instruction, index, us }: { instruction: Instruction; i function getTurnSign(sign: number, index: number) { // from, via and to signs are special if (index === 0 || sign === 4 || sign === 5) { - let markerColor + let icon if (index === 0) { - markerColor = QueryStore.getMarkerColor(QueryPointType.From) + icon = } else if (sign === 4) { - markerColor = QueryStore.getMarkerColor(QueryPointType.To) + icon = } else { - markerColor = QueryStore.getMarkerColor(QueryPointType.Via) + icon = } - return ( -
- -
- ) + return
{icon}
} return {'turn } diff --git a/src/sidebar/search/Search.module.css b/src/sidebar/search/Search.module.css index 06052702..b28592ae 100644 --- a/src/sidebar/search/Search.module.css +++ b/src/sidebar/search/Search.module.css @@ -120,6 +120,11 @@ outline: none; } +/* the via circle has a square aspect ratio and would appear too small with the 1rem of .markerContainer */ +.markerContainer:has(> :global(.viaCircle)) { + width: 1.25rem; +} + .markerTarget:hover svg { fill: black; } From c7200ea06b95496a21690d36fde52f3c32914d47 Mon Sep 17 00:00:00 2001 From: Peter Karich Date: Thu, 20 Aug 2026 13:34:23 +0200 Subject: [PATCH 02/26] simplify --- src/layers/UseQueryPointsLayer.tsx | 27 ++++++++++++--------------- src/layers/createMarkerSVG.ts | 11 ++++++----- src/map/Marker.tsx | 20 +++++--------------- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/layers/UseQueryPointsLayer.tsx b/src/layers/UseQueryPointsLayer.tsx index 0c6d5d11..3884fe8d 100644 --- a/src/layers/UseQueryPointsLayer.tsx +++ b/src/layers/UseQueryPointsLayer.tsx @@ -10,10 +10,10 @@ import Dispatcher from '@/stores/Dispatcher' import { SetPoint } from '@/actions/Actions' import { coordinateToText } from '@/Converters' import { Icon, Style } from 'ol/style' -import { createCircle, createSvg } from '@/layers/createMarkerSVG' +import { createSvg } from '@/layers/createMarkerSVG' const MARKER_SIZE = 35 -const VIA_MARKER_SIZE = 26 +const VIA_MARKER_SIZE = 23 export default function useQueryPointsLayer(map: Map, queryPoints: QueryPoint[]) { useEffect(() => { @@ -37,19 +37,16 @@ function removeQueryPoints(map: Map) { function addQueryPointsLayer(map: Map, queryPoints: QueryPoint[]) { const features: Feature[] = queryPoints + .filter(point => point.isInitialized) .map((point, i) => { - return { index: i, point: point } - }) - .filter(indexPoint => indexPoint.point.isInitialized) - .map((indexPoint, i) => { const feature = new Feature({ - geometry: new Point(fromLonLat([indexPoint.point.coordinate.lng, indexPoint.point.coordinate.lat])), + geometry: new Point(fromLonLat([point.coordinate.lng, point.coordinate.lat])), }) - const isVia = indexPoint.point.type == QueryPointType.Via - feature.set('gh:query_point', indexPoint.point) + const isVia = point.type == QueryPointType.Via + feature.set('gh:query_point', point) feature.set('gh:marker_props', { - color: indexPoint.point.color, - via: isVia, + color: point.color, + // a number is only displayed for via points and turns the marker into a circle number: isVia ? i : undefined, size: isVia ? VIA_MARKER_SIZE : MARKER_SIZE, }) @@ -65,14 +62,14 @@ function addQueryPointsLayer(map: Map, queryPoints: QueryPoint[]) { const cachedStyles: { [id: string]: Style } = {} queryPointsLayer.setStyle(feature => { const props = feature.get('gh:marker_props') - const key = (props.via ? 'via' : 'marker') + '-' + props.number + '-' + props.color + '-' + props.size + const key = props.number + '-' + props.color + '-' + props.size let style = cachedStyles[key] if (style) return style style = new Style({ image: new Icon({ - src: 'data:image/svg+xml;utf8,' + (props.via ? createCircle(props) : createSvg(props)), - // the circle is centered on the coordinate, the marker points to it with its tip - displacement: props.via ? [0, 0] : [0, MARKER_SIZE / 2], + src: 'data:image/svg+xml;utf8,' + createSvg(props), + // the via circle is centered on the coordinate, the marker points to it with its tip + displacement: props.number !== undefined ? [0, 0] : [0, MARKER_SIZE / 2], }), }) cachedStyles[key] = style diff --git a/src/layers/createMarkerSVG.ts b/src/layers/createMarkerSVG.ts index bc48dde3..f7895b5a 100644 --- a/src/layers/createMarkerSVG.ts +++ b/src/layers/createMarkerSVG.ts @@ -9,9 +9,9 @@ interface MarkerProps { } // depending on the number of digits the font must be smaller so that e.g. '10' still fits into the circle -function circleFontSize(number: number | undefined) { - if (number === undefined || ('' + number).length <= 1) return 230 - return ('' + number).length === 2 ? 170 : 120 +export function circleFontSize(number: string) { + if (number.length <= 1) return 230 + return number.length === 2 ? 170 : 120 } // draws a circle with a thick colored ring and a white center, used for via points. If a number is given it is @@ -22,7 +22,7 @@ export function createCircle({ color, number, size = 0 }: MarkerProps) { number === undefined ? '' : `${number}` } ` @@ -44,7 +44,8 @@ export function createPOIMarker(pathD: string) { // todo: this is mostly duplicated from `Marker.tsx`, but we use a more elongated shape (MARKER_PATH). // To use `Marker.tsx` we would probably need to add ol.Overlays, i.e. create a div for each marker and insert the svg from `Marker.tsx`. -export function createSvg({ color, size = 0 }: MarkerProps) { +export function createSvg({ color, number, size = 0 }: MarkerProps) { + if (number !== undefined) return createCircle({ color, number, size }) return `
+ {markedQueryPoint && ( + + )} {showAddLocation && ( )} - )} - {showAddLocation && ( - + {!markedQueryPoint && ( + <> + {showAddLocation && ( + + )} + + + + )} - - -