@@ -226,7 +226,7 @@ function ensureRouteLayers(
226226 map : maplibregl . Map ,
227227 overlayKeysRef : OverlayKeysRef ,
228228 onSelectRoute : ( routeId : string ) => void ,
229- onSelectPath : ( kind : "visit" | "plan" ) => void
229+ onSelectPath : ( kind : "visit" | "plan" , date : string | null ) => void
230230) {
231231 if ( map . getSource ( ROUTES_SOURCE_ID ) ) return ;
232232
@@ -299,11 +299,16 @@ function ensureRouteLayers(
299299 onSelectRoute ( routeId ) ;
300300 return ;
301301 }
302- const pathKind = e . features ?. find (
302+ const pathFeature = e . features ?. find (
303303 ( f ) =>
304304 f . properties ?. pathKind === "visit" || f . properties ?. pathKind === "plan"
305- ) ?. properties ?. pathKind ;
306- if ( pathKind === "visit" || pathKind === "plan" ) onSelectPath ( pathKind ) ;
305+ ) ;
306+ const pathKind = pathFeature ?. properties ?. pathKind ;
307+ if ( pathKind === "visit" || pathKind === "plan" ) {
308+ // 訪問順の経路は日ごとに線が分かれているので、タップした線の日を渡す
309+ const pathDate = pathFeature ?. properties ?. pathDate ;
310+ onSelectPath ( pathKind , typeof pathDate === "string" ? pathDate : null ) ;
311+ }
307312 } ) ;
308313 map . on ( "mouseenter" , ROUTE_HIT_LAYER_ID , ( ) => {
309314 map . getCanvas ( ) . style . cursor = "pointer" ;
@@ -624,40 +629,57 @@ function visitedSpotIdsOn(visits: Visit[], filters: SpotFilters): Set<string> {
624629
625630/**
626631 * 訪問順の経路の対象期間(`filters.visitedDate`〜`visitedDateTo`。開始日がnullなら
627- * 表示しない)が選ばれているとき、その期間の訪問を訪問時刻の昇順に並べた経路を返す。
628- * **期間をまたいでも1本の経路にする**(旅行の何日ぶんかをそのまま辿れるように)。
632+ * 表示しない)が選ばれているとき、その期間の訪問を**日ごとに分けて**、それぞれ
633+ * 訪問時刻の昇順に並べた経路を返す(日付の昇順)。
634+ * **日をまたいでスポットを線で結ばない** —— 宿へ帰って翌朝また出る間の移動は
635+ * 実際には辿っていないので、繋ぐと1日の道のりが読めなくなるため。
636+ * 線・詳細・Google マップの経路検索のいずれも日ごとに別のものとして扱う。
629637 * 別のスポット種別のスポットも、座標を補完できていれば経路に含める(訪問予定リストと
630638 * 同じ扱い。補完は pathExtraSpots)。解決できないスポットだけを除く。
631639 * 同じスポットへの再訪はそのまま複数回現れる(行って戻る線になる)が、
632640 * 連続する同じスポットへの訪問(同じ場所で複数回記録した場合)はまとめる
633641 * (長さ0の線分になり、矢印の向きが定まらないため)。
634642 */
635- function buildVisitPath (
643+ function buildVisitPathsByDay (
636644 visits : Visit [ ] ,
637645 filters : SpotFilters ,
638646 spotById : Map < string , Spot >
639- ) : Spot [ ] {
647+ ) : { date : string ; path : Spot [ ] } [ ] {
640648 if ( ! filters . visitedDate ) return [ ] ;
641- return visits
642- . flatMap ( ( visit ) => {
643- if ( ! isInVisitedRange ( visit . visited_on , filters ) ) return [ ] ;
644- const spot = spotById . get ( visit . spot_id ) ;
645- return spot ? [ { time : Date . parse ( visit . visited_on ! ) , spot } ] : [ ] ;
646- } )
647- . sort ( ( a , b ) => a . time - b . time )
648- . map ( ( v ) => v . spot )
649- . filter ( ( spot , i , list ) => i === 0 || spot . id !== list [ i - 1 ] . id ) ;
649+ const byDay = new Map < string , { time : number ; spot : Spot } [ ] > ( ) ;
650+ for ( const visit of visits ) {
651+ if ( ! isInVisitedRange ( visit . visited_on , filters ) ) continue ;
652+ const spot = spotById . get ( visit . spot_id ) ;
653+ const date = toVisitDateKey ( visit . visited_on ) ;
654+ if ( ! spot || ! date ) continue ;
655+ const day = byDay . get ( date ) ?? [ ] ;
656+ day . push ( { time : Date . parse ( visit . visited_on ! ) , spot } ) ;
657+ byDay . set ( date , day ) ;
658+ }
659+ return [ ...byDay . entries ( ) ]
660+ . sort ( ( [ a ] , [ b ] ) => ( a < b ? - 1 : a > b ? 1 : 0 ) )
661+ . map ( ( [ date , entries ] ) => ( {
662+ date,
663+ path : entries
664+ . sort ( ( a , b ) => a . time - b . time )
665+ . map ( ( v ) => v . spot )
666+ . filter ( ( spot , i , list ) => i === 0 || spot . id !== list [ i - 1 ] . id ) ,
667+ } ) )
668+ . filter ( ( day ) => day . path . length > 0 ) ;
650669}
651670
652671/**
653672 * routeId はタップでルート詳細を開くのに使う。訪問順の経路・訪問予定リストの経路には
654673 * routeId の代わりに pathKind を付け、タップで対応する経路の詳細を開く。
674+ * 訪問順の経路は日ごとに別の線なので、どの日の線かを pathDate(`YYYY-MM-DD`)で持ち、
675+ * タップしたときにその日ぶんの詳細を出せるようにする。
655676 */
656677type RouteFeatureProps = {
657678 color : string ;
658679 icon : string ;
659680 routeId ?: string ;
660681 pathKind ?: "visit" | "plan" ;
682+ pathDate ?: string ;
661683} ;
662684
663685/**
@@ -699,6 +721,8 @@ function buildRouteGeoJSON(
699721 path : Spot [ ] ;
700722 color : string ;
701723 kind ?: "visit" | "plan" ;
724+ /** 訪問順の経路で、その線がどの日のものか(`YYYY-MM-DD`) */
725+ date ?: string ;
702726 start ?: [ number , number ] | null ;
703727 startColor ?: string ;
704728 } [ ]
@@ -732,6 +756,7 @@ function buildRouteGeoJSON(
732756 color : p . color ,
733757 icon : ensureRouteArrowImage ( map , p . color ) ,
734758 ...( p . kind ? { pathKind : p . kind } : { } ) ,
759+ ...( p . date ? { pathDate : p . date } : { } ) ,
735760 } ,
736761 } ) ) ;
737762
@@ -1365,7 +1390,9 @@ export default function MapView({
13651390 // いないことがある。その場合は解決できた分だけで移動し、補完が届いたあとの
13661391 // 経路の描き直しに合わせて地図を動かし直すことはしない
13671392 // (ユーザーの操作なしに地図が動くのを避けるため)
1368- fitMapToSpots ( buildVisitPath ( visits , next , pathSpotById ) ) ;
1393+ fitMapToSpots (
1394+ buildVisitPathsByDay ( visits , next , pathSpotById ) . flatMap ( ( d ) => d . path )
1395+ ) ;
13691396 } ,
13701397 [ filters , setFilters , visits , pathSpotById , fitMapToSpots ]
13711398 ) ;
@@ -1401,6 +1428,8 @@ export default function MapView({
14011428 const [ detailPathKind , setDetailPathKind ] = useState < "visit" | "plan" | null > (
14021429 null
14031430 ) ;
1431+ // 訪問順の経路は日ごとに線が分かれるため、詳細を開いた線がどの日かを覚える
1432+ const [ detailPathDate , setDetailPathDate ] = useState < string | null > ( null ) ;
14041433 // 経路詳細の「編集」で開く訪問予定リストの基本情報編集モーダルの対象
14051434 const [ editingPlanList , setEditingPlanList ] = useState < VisitPlanList | null > (
14061435 null
@@ -1414,7 +1443,8 @@ export default function MapView({
14141443 setOverlayDetailRouteId ( null ) ;
14151444 setDetailRouteId ( routeId ) ;
14161445 } , [ ] ) ;
1417- const openPathDetail = useCallback ( ( kind : "visit" | "plan" ) => {
1446+ const openPathDetail = useCallback ( ( kind : "visit" | "plan" , date : string | null ) => {
1447+ setDetailPathDate ( date ) ;
14181448 setDetailRouteId ( null ) ;
14191449 setOverlayDetailRouteId ( null ) ;
14201450 setDetailPathKind ( kind ) ;
@@ -2543,8 +2573,11 @@ export default function MapView({
25432573 isolate === null
25442574 ? filterVisibleRoutes ( routes , filters , seriesStyles , spotById )
25452575 : [ ] ;
2546- const visitPath =
2547- isolate === "plan" ? [ ] : buildVisitPath ( visits , filters , pathSpotById ) ;
2576+ // 訪問順の経路は日ごとに別の線にする(日をまたいで結ばない)
2577+ const visitPathsByDay =
2578+ isolate === "plan"
2579+ ? [ ]
2580+ : buildVisitPathsByDay ( visits , filters , pathSpotById ) ;
25482581 // 作成モード中に編集対象のリスト自身を経路表示していた場合は、更新前の経路が
25492582 // 下書きの経路と古い形のまま二重に残らないよう、保存済み側は描かない
25502583 const planListPath =
@@ -2560,7 +2593,12 @@ export default function MapView({
25602593 | undefined ;
25612594 source ?. setData (
25622595 buildRouteGeoJSON ( map , visibleRoutes , seriesStyles , [
2563- { path : visitPath , color : VISIT_PATH_COLOR , kind : "visit" } ,
2596+ ...visitPathsByDay . map ( ( day ) => ( {
2597+ path : day . path ,
2598+ color : VISIT_PATH_COLOR ,
2599+ kind : "visit" as const ,
2600+ date : day . date ,
2601+ } ) ) ,
25642602 {
25652603 path : planListPath ,
25662604 color : PLAN_LIST_PATH_COLOR ,
@@ -2787,6 +2825,8 @@ export default function MapView({
27872825 editList ?: VisitPlanList ;
27882826 points : {
27892827 key : string ;
2828+ /** タップでその位置へ移動したあと、このスポットの詳細を開く */
2829+ spotId : string ;
27902830 name : string ;
27912831 lng : number ;
27922832 lat : number ;
@@ -2799,6 +2839,7 @@ export default function MapView({
27992839 pointNoun : "経由地" ,
28002840 points : detailRoute . points . map ( ( p ) => ( {
28012841 key : `${ p . spot_id } -${ p . seq } ` ,
2842+ spotId : p . spot_id ,
28022843 name : p . spot_name ,
28032844 lng : p . lng ,
28042845 lat : p . lat ,
@@ -2807,20 +2848,20 @@ export default function MapView({
28072848 }
28082849 : detailPathKind === "visit"
28092850 ? ( ( ) => {
2810- const path = buildVisitPath ( visits , filters , pathSpotById ) ;
2811- if ( path . length === 0 ) return null ;
2851+ // 線は日ごとに分かれているので、詳細もタップした日の1日ぶんだけを出す
2852+ // (期間指定でも「その日に辿った道のり」が読めるように)。日が分からない
2853+ // 古い状態のときは先頭の日にフォールバックする
2854+ const days = buildVisitPathsByDay ( visits , filters , pathSpotById ) ;
2855+ const day =
2856+ days . find ( ( d ) => d . date === detailPathDate ) ?? days [ 0 ] ?? null ;
2857+ if ( ! day ) return null ;
28122858 return {
2813- title : "訪問順の経路" ,
2814- description : filters . visitedDate
2815- ? filters . visitedDateTo
2816- ? `${ formatVisitDate ( filters . visitedDate ) } 〜${ formatVisitDate (
2817- filters . visitedDateTo
2818- ) } に訪問したスポットを、訪問した順に並べています。`
2819- : `${ formatVisitDate ( filters . visitedDate ) } に訪問したスポットを、訪問した順に並べています。`
2820- : null ,
2859+ title : `訪問順の経路(${ formatVisitDate ( day . date ) } )` ,
2860+ description : `${ formatVisitDate ( day . date ) } に訪問したスポットを、訪問した順に並べています。` ,
28212861 pointNoun : "地点" ,
2822- points : path . map ( ( s , i ) => ( {
2862+ points : day . path . map ( ( s , i ) => ( {
28232863 key : `${ s . id } -${ i } ` ,
2864+ spotId : s . id ,
28242865 name : s . name ,
28252866 lng : s . lng ,
28262867 lat : s . lat ,
@@ -2839,6 +2880,7 @@ export default function MapView({
28392880 editList : list ,
28402881 points : path . map ( ( s , i ) => ( {
28412882 key : `${ s . id } -${ i } ` ,
2883+ spotId : s . id ,
28422884 name : s . name ,
28432885 lng : s . lng ,
28442886 lat : s . lat ,
@@ -2850,6 +2892,7 @@ export default function MapView({
28502892 setDetailRouteId ( null ) ;
28512893 setOverlayDetailRouteId ( null ) ;
28522894 setDetailPathKind ( null ) ;
2895+ setDetailPathDate ( null ) ;
28532896 } ;
28542897
28552898 // 今表示中のスポット種別の表示名(左下のチップに出す)。spotTypesは重ね表示
@@ -3696,7 +3739,11 @@ export default function MapView({
36963739 < span className = "w-6 shrink-0 text-right text-xs font-medium tabular-nums text-gray-500" >
36973740 { i + 1 }
36983741 </ span >
3699- { /* スポット名のタップでその位置へ飛ぶ(モーダルは閉じる) */ }
3742+ { /* スポット名のタップでその位置へ飛び、続けてそのスポットの
3743+ 詳細を開く(一覧から辿ったときに、そこが何なのかを
3744+ 見に行くまでが1タップで済むように)。詳細は本体種別の
3745+ スポットなら通常のモーダル、別種別なら読み取り専用
3746+ (ピンをタップしたときと同じ出し分け) */ }
37003747 < button
37013748 type = "button"
37023749 onClick = { ( ) => {
@@ -3705,6 +3752,11 @@ export default function MapView({
37053752 center : [ point . lng , point . lat ] ,
37063753 zoom : 16 ,
37073754 } ) ;
3755+ if ( spotById . has ( point . spotId ) ) {
3756+ setDetailSpotId ( point . spotId ) ;
3757+ } else {
3758+ setOverlayDetailSpotId ( point . spotId ) ;
3759+ }
37083760 } }
37093761 className = "min-w-0 truncate text-left font-medium text-blue-600 underline"
37103762 >
@@ -3727,7 +3779,7 @@ export default function MapView({
37273779 </ ol >
37283780 < p className = "pt-2 text-xs text-gray-500" >
37293781 { routeDetailView . pointNoun }
3730- { routeDetailView . points . length } 件。スポット名をタップすると、その位置に地図を移動します 。
3782+ { routeDetailView . points . length } 件。スポット名をタップすると、その位置へ移動して詳細を開きます 。
37313783 </ p >
37323784 { /* 経路全体をGoogle マップの経路検索で開く(先頭が出発地、
37333785 途中が経由地、最後が目的地) */ }
0 commit comments