@@ -694,7 +694,12 @@ def __init__(self):
694694 self .lon_range = [- 180.0 , 180.0 ]
695695 self .lat_range = [- 90.0 , 90.0 ]
696696 self .cached_cell_centers = None
697+ self ._cached_geometry_key = None
698+ #: Strong ref to the geometry the caches were built from, so nothing
699+ #: else can be allocated at the same address while its id() is a key.
700+ self ._cached_geometry = None
697701 self ._cached_output = None
702+ self ._cached_crop_key = None
698703 self ._last_was_cropped = False
699704
700705 def SetLongitudeRange (self , min , max ):
@@ -734,8 +739,25 @@ def RequestData(self, request, inInfo, outInfo):
734739 self ._last_was_cropped = False
735740 return 1
736741
737- if self .cached_cell_centers and self .cached_cell_centers .GetMTime () >= max (
738- inData .GetPoints ().GetMTime (), inData .GetCells ().GetMTime ()
742+ # Name the incoming geometry by identity as well as by modified
743+ # time. Modified times only order events within one object, and
744+ # upstream can legitimately hand back an *older* object than the
745+ # one these caches were built from: EAMCenterMeridian passes the
746+ # reader's own points straight through whenever the map already
747+ # sits in the reader's window. Comparing times alone then made a
748+ # cache built from a rotated mesh look fresh, and the cell centres
749+ # of one mesh were used to mask the cells of another.
750+ in_points = inData .GetPoints ()
751+ in_cells = inData .GetCells ()
752+ geometry_key = (
753+ id (in_points ),
754+ in_points .GetMTime (),
755+ id (in_cells ),
756+ in_cells .GetMTime (),
757+ )
758+ if (
759+ self .cached_cell_centers is not None
760+ and self ._cached_geometry_key == geometry_key
739761 ):
740762 cell_centers = self .cached_cell_centers
741763 else :
@@ -752,13 +774,14 @@ def RequestData(self, request, inInfo, outInfo):
752774 # previous cached_cell_centers, if any,
753775 # is available for garbage collection after this assignment
754776 self .cached_cell_centers = cell_centers
777+ self ._cached_geometry_key = geometry_key
778+ self ._cached_geometry = (in_points , in_cells )
755779
756780 # get the numpy array for cell centers
757781 cc = numpy_support .vtk_to_numpy (cell_centers )
758782
759- if self ._cached_output and self ._cached_output .GetMTime () >= max (
760- self .GetMTime (), inData .GetPoints ().GetMTime (), cell_centers .GetMTime ()
761- ):
783+ crop_key = (geometry_key , tuple (self .lon_range ), tuple (self .lat_range ))
784+ if self ._cached_output is not None and self ._cached_crop_key == crop_key :
762785 with _perf .timed ("extract.cache_hit" ):
763786 add_cell_and_point_arrays (inData , outData , self ._cached_output )
764787 else :
@@ -816,6 +839,7 @@ def RequestData(self, request, inInfo, outInfo):
816839
817840 self ._cached_output = outData .NewInstance ()
818841 self ._cached_output .ShallowCopy (outData )
842+ self ._cached_crop_key = crop_key
819843 self ._last_was_cropped = True
820844 return 1
821845
@@ -885,8 +909,10 @@ def SetMeridian(self, meridian_):
885909 )
886910 )
887911 return
888- self ._center_meridian = meridian_
889- self .Modified ()
912+ if self ._center_meridian != meridian_ :
913+ self ._center_meridian = meridian_
914+ self ._cached_output = None
915+ self .Modified ()
890916
891917 def SetLongitudeOrigin (self , origin ):
892918 """Left edge of the map; the right edge is 360 degrees further east."""
@@ -931,9 +957,13 @@ def RequestData(self, request, inInfo, outInfo):
931957 # Nothing to do when the input already sits in the requested
932958 # window -- the dycore reader's default case. Clipping here would
933959 # be a no-op that still rebuilds the points every pass, which also
934- # costs EAMProject its cache downstream.
960+ # costs EAMProject its cache downstream. The two origins have to
961+ # agree exactly: a window a whole turn away covers the same
962+ # meridians but names them 360 degrees apart, and everything
963+ # downstream -- the crop ranges, the projection's re-centring --
964+ # reads coordinates, not residues.
935965 origin = self ._center_meridian - 180.0
936- if ( origin - self . _input_origin ) % 360.0 == 0.0 :
966+ if origin == self . _input_origin :
937967 with _perf .timed ("center_meridian.passthrough" ):
938968 outData .ShallowCopy (inData )
939969 return 1
@@ -960,30 +990,39 @@ def RequestData(self, request, inInfo, outInfo):
960990 generate_ids .SetCellIdsArrayName ("PedigreeIds" )
961991
962992 cut , shift_low , shift_high = _longitude_window (
963- self . _center_meridian - 180.0 , self ._input_origin
993+ origin , self ._input_origin
964994 )
965- plane = vtkPlane ()
966- plane .SetOrigin ([cut , 0.0 , 0.0 ])
967- plane .SetNormal ([- 1 , 0 , 0 ])
968- # vtkClipPolyData hangs
969- clipL = vtkTableBasedClipDataSet ()
970- clipL .SetClipFunction (plane )
971- clipL .SetInputConnection (generate_ids .GetOutputPort ())
972- with _perf .timed ("center_meridian.clip_left" ):
973- clipL .Update ()
974-
975- plane .SetNormal ([1 , 0 , 0 ])
976- clipR = vtkTableBasedClipDataSet ()
977- clipR .SetClipFunction (plane )
978- clipR .SetInputConnection (generate_ids .GetOutputPort ())
979- with _perf .timed ("center_meridian.clip_right" ):
980- clipR .Update ()
981-
982- with _perf .timed ("center_meridian.transform" ):
983- halves = [
984- _translated (clipL .GetOutput (), shift_low ),
985- _translated (clipR .GetOutput (), shift_high ),
986- ]
995+ if (origin - self ._input_origin ) % 360.0 == 0.0 :
996+ # A whole turn away: the cut falls on the window's own
997+ # edge, so one side comes back empty and there is
998+ # nothing to rearrange. Sliding the whole mesh over is
999+ # cheaper than clipping it and keeps every cell intact.
1000+ generate_ids .Update ()
1001+ with _perf .timed ("center_meridian.transform" ):
1002+ halves = [_translated (generate_ids .GetOutput (), shift_high )]
1003+ else :
1004+ plane = vtkPlane ()
1005+ plane .SetOrigin ([cut , 0.0 , 0.0 ])
1006+ plane .SetNormal ([- 1 , 0 , 0 ])
1007+ # vtkClipPolyData hangs
1008+ clipL = vtkTableBasedClipDataSet ()
1009+ clipL .SetClipFunction (plane )
1010+ clipL .SetInputConnection (generate_ids .GetOutputPort ())
1011+ with _perf .timed ("center_meridian.clip_left" ):
1012+ clipL .Update ()
1013+
1014+ plane .SetNormal ([1 , 0 , 0 ])
1015+ clipR = vtkTableBasedClipDataSet ()
1016+ clipR .SetClipFunction (plane )
1017+ clipR .SetInputConnection (generate_ids .GetOutputPort ())
1018+ with _perf .timed ("center_meridian.clip_right" ):
1019+ clipR .Update ()
1020+
1021+ with _perf .timed ("center_meridian.transform" ):
1022+ halves = [
1023+ _translated (clipL .GetOutput (), shift_low ),
1024+ _translated (clipR .GetOutput (), shift_high ),
1025+ ]
9871026
9881027 append = vtkAppendFilter ()
9891028 for half in halves :
0 commit comments