@@ -189,9 +189,11 @@ def matchup(
189189 always includes:
190190
191191 ``pc_id``
192- The original row index from the input dataframe, allowing
193- matchup rows to be traced back to their source point even
194- when a single point matches multiple granules.
192+ Point identifier. If the input dataframe contains a ``pc_id``
193+ column those values are preserved as-is; otherwise the row
194+ index from the input dataframe is used. Duplicate ``pc_id``
195+ values in the input are not allowed and raise a
196+ :class:`ValueError` during planning.
195197 ``granule_id``
196198 Identifier of the granule that provided this row's values.
197199 ``granule_lat``
@@ -206,7 +208,10 @@ def matchup(
206208 search result metadata rather than in the dataset itself.
207209 For zero-match rows, this column is ``pandas.NaT``.
208210
209- Points with zero matching granules contribute a single NaN row.
211+ Any extra columns present in the input dataframe are retained in
212+ the output. Points with zero matching granules contribute a
213+ single NaN row. The output is sorted to match the ``pc_id``
214+ order from the input dataframe.
210215
211216 Raises
212217 ------
@@ -483,6 +488,17 @@ def _execute_plan(
483488 save_path = pathlib .Path (save_dir )
484489 save_path .mkdir (parents = True , exist_ok = True )
485490
491+ # Determine whether the user supplied their own pc_id column. If so, use
492+ # those values as-is; otherwise assign the DataFrame row index as pc_id.
493+ has_user_pc_id : bool = "pc_id" in plan .points .columns
494+
495+ # Build a mapping from pc_id value → its position in the input DataFrame so
496+ # the output can be sorted to match the user's original point order.
497+ if has_user_pc_id :
498+ pc_id_order : dict = {val : pos for pos , val in enumerate (plan .points ["pc_id" ])}
499+ else :
500+ pc_id_order = {idx : pos for pos , idx in enumerate (plan .points .index )}
501+
486502 # Build granule_index → [point_indices] for all matched granules
487503 granule_to_points : dict [int , list [object ]] = {}
488504 zero_match_pt_indices : list [object ] = []
@@ -499,7 +515,8 @@ def _execute_plan(
499515 # Zero-match points → single NaN row each
500516 for pt_idx in zero_match_pt_indices :
501517 row : dict = plan .points .loc [pt_idx ].to_dict ()
502- row ["pc_id" ] = pt_idx
518+ if not has_user_pc_id :
519+ row ["pc_id" ] = pt_idx
503520 row ["granule_id" ] = float ("nan" )
504521 row ["granule_lat" ] = float ("nan" )
505522 row ["granule_lon" ] = float ("nan" )
@@ -665,7 +682,8 @@ def _execute_plan(
665682 rows_for_granule = []
666683 for pt_idx in pt_indices :
667684 row = plan .points .loc [pt_idx ].to_dict ()
668- row ["pc_id" ] = pt_idx
685+ if not has_user_pc_id :
686+ row ["pc_id" ] = pt_idx
669687 row ["granule_id" ] = gm .granule_id
670688 row ["granule_time" ] = granule_time
671689 rows_for_granule .append (row )
@@ -681,7 +699,8 @@ def _execute_plan(
681699 # ndpoint for the whole granule (and all future ones).
682700 def _make_row (pt_idx : object ) -> dict :
683701 r = plan .points .loc [pt_idx ].to_dict ()
684- r ["pc_id" ] = pt_idx
702+ if not has_user_pc_id :
703+ r ["pc_id" ] = pt_idx
685704 r ["granule_id" ] = gm .granule_id
686705 r ["granule_time" ] = granule_time
687706 return r
@@ -717,7 +736,8 @@ def _make_row(pt_idx: object) -> dict:
717736 else :
718737 for pt_idx in pt_indices :
719738 row = plan .points .loc [pt_idx ].to_dict ()
720- row ["pc_id" ] = pt_idx
739+ if not has_user_pc_id :
740+ row ["pc_id" ] = pt_idx
721741 row ["granule_id" ] = gm .granule_id
722742 row ["granule_time" ] = granule_time
723743 _extract_nearest (ds , row , variables , lon_name , lat_name , time_dim )
@@ -734,7 +754,8 @@ def _make_row(pt_idx: object) -> dict:
734754 failed_granule_time = gm .begin + (gm .end - gm .begin ) / 2
735755 for pt_idx in pt_indices :
736756 row = plan .points .loc [pt_idx ].to_dict ()
737- row ["pc_id" ] = pt_idx
757+ if not has_user_pc_id :
758+ row ["pc_id" ] = pt_idx
738759 row ["granule_id" ] = gm .granule_id
739760 row ["granule_lat" ] = float ("nan" )
740761 row ["granule_lon" ] = float ("nan" )
@@ -795,7 +816,8 @@ def _make_row(pt_idx: object) -> dict:
795816
796817 if not output_rows :
797818 empty = plan .points .iloc [:0 ].copy ()
798- empty ["pc_id" ] = pd .Series (dtype = object )
819+ if not has_user_pc_id :
820+ empty ["pc_id" ] = pd .Series (dtype = object )
799821 empty ["granule_id" ] = pd .Series (dtype = object )
800822 empty ["granule_lat" ] = pd .Series (dtype = float )
801823 empty ["granule_lon" ] = pd .Series (dtype = float )
@@ -813,6 +835,13 @@ def _make_row(pt_idx: object) -> dict:
813835 if expanded and var in df .columns :
814836 df = df .drop (columns = [var ])
815837
838+ # Sort by the pc_id order from the input DataFrame so that output rows
839+ # follow the same point ordering the user provided. A stable sort
840+ # preserves the relative order of rows with the same pc_id (e.g. multiple
841+ # granules for one point).
842+ df ["_pc_sort" ] = df ["pc_id" ].map (pc_id_order )
843+ df = df .sort_values ("_pc_sort" , kind = "stable" ).drop (columns = ["_pc_sort" ]).reset_index (drop = True )
844+
816845 return df
817846
818847
0 commit comments