reearth / resium

React components for 🌏 Cesium
https://resium.reearth.io
MIT License
727 stars 134 forks source link

ImageryLayer does not re-render when WMS imagery provider changes #634

Open wxmann opened 1 year ago

wxmann commented 1 year ago

I am trying to show some meteorological data on a Cesium viewer, where the user can toggle the vertical level and the kind of data being shown coming from a WMS data source. I have a component which takes the level and the layers (kind of data) attribute, and whenever the user toggles either I want the viewer to update the meteorological data accordingly. However, this is not happening. Here is the basic gist of the code:

const BASE_IMAGERY_ATTRS = {
  url: 'blah',
  // some other attributes
}
export default function Layer({ level, layers }) {
  const [imageryProvider, setImageryProvider] = useState();
  useEffect(() => {
    setImageryProvider(new WebMapServiceImageryProvider({
      ...BASE_IMAGERY_ATTRS,
      layers,
      parameters: { level },
    }));
  }, [level, layers];

  return (
    <ImageryLayer 
       imageryProvider={imageryProvider} 
       // other props for alpha, show, etc.
     /> 
  )
}

And of course this component is a child under a Viewer.

When I inspect/log the Cesium elements under the hood and the observe the outgoing HTTP requests to the WMS, I can see the layers and parameters don't change from the default values. However, according to the guide, if a readonly property (such as the imageryProvider) changes or gets reinstantiated, the Cesium component should be "destroyed and reinitialized." But it's not doing so here.

If I work around this and do this imperatively using Cesium classes by capturing a ref of the ImageryLayerCollection, the components get swapped out and the meteorological data updates correctly. So I think the lack of rerendering is happening on Resium's side. Here's the imperative code that works:

export default function Layer2({ level, layers }) {
  const collectionRef = useRef();
  useEffect(() => {
    const collection = collectionRef?.current?.cesiumElement;
    if (collection) {
      const index = //some hard-coded index;
      const prevLayer = collection.get(index);
      collection.remove(prevLayer);

      const newLayer = new WebMapServiceImageryProvider({
        ...BASE_IMAGERY_ATTRS,
        layers,
        parameters: { level },
      }));
      collection.add(new ImageryLayer(newLayer, {// alpha/show/etc options}), index);
    }
  }, [level, layers, collectionRef];

  return (
    <ImageryLayerCollection ref={collectionRef} />
  )
}

Versions: Cesium: 1.108.0 Resium: 1.17.1 Next.js: 13.4.19 (but not using the /app directory or server-side components)

BenDDev commented 7 months ago

I've just stumbled into this issue after updating to 1.17.x (from 1.15.x). We're using the WMS sld_body parameter with GeoServer to dynamically style layers (by selecting configurable styles). This completely breaks this functionality as any new SLD Body (which causes a new imagery provider to be created) doesn't trigger a re-render.

I've found a workaround by passing a key which is based on the sld_body (which is the dynamic part). It works but the SLD string has the potential to be very long, so feels a bit hacky.

You used to have to try to stop it from re-rendering, whereas now you have to trick into re-rendering!

Versions: Cesium: 1.114.0 Resium: 1.17.2 Remix (w/ Vite) - I checked using just Vite (no frameworks) and found the same issue.