resium

repository·main·Indexed 20 days ago

https://github.com/reearth/resium

A React component library for CesiumJS that provides a declarative interface for building 3D geospatial applications. It features full TypeScript support, Hot Module Replacement (HMR), and utilities like the useCesium hook and createCesiumComponent for wrapping native Cesium classes. Version 1.24.0.

Tokens
33.4K
Snippets
126
Records
214
Agent score
74%

What's inside resium

  1. Introduction to Resium

    main

    Resium is a React component library for Cesium. It allows you to build highly maintainable Cesium applications using a declarative React approach.

    Key features include:

    • Declarative Cesium: Manage Cesium objects as React components.
    • Fast Development: Supports Hot Module Replacement (HMR).
    • Strongly Typed: Full TypeScript support.
  2. Understand the Resium component lifecycle

    main

    Resium maps React's lifecycle to Cesium elements. Understanding this sequence is crucial for managing when elements are created or destroyed:

    1. Render: No Cesium element is initialized yet.
    2. Initialize Cesium element: The element is initialized and added to its parent (if a parent exists).
    3. Re-render: Children of the component are rendered. Note that the DOM is only rendered for root components (Viewer and CesiumWidget).
    4. Update: Changed properties are applied. Warning: Changing 'Cesium read-only properties' causes the element to be destroyed and reinitialized.
    5. Unmount: The Cesium element is destroyed.
  3. Manage Cesium properties and avoid reinitialization

    main

    Resium components support four types of properties:

    1. Cesium properties: Direct mappings to Cesium properties. These are variable and update seamlessly when changed in React.
    2. Cesium read-only properties: Immutable properties available only during initialization. Changing these causes the Cesium element to be destroyed and reinitialized, which can severely impact performance.
    3. Cesium events: Cesium events renamed to follow React conventions (e.g., Viewer#trackedEntityChanged becomes onTrackedEntityChange).
    4. Other properties: Convenience properties specific to Resium that do not exist on the underlying Cesium element.

    Handling Read-Only Properties

    To prevent performance degradation when using read-only properties (like imageryProvider on ImageryLayer), do not define them inline. Use useMemo to ensure the object reference remains stable unless its dependencies change.

    import { useMemo } from "react";
    import { Viewer, ImageryLayer } from "resium";
    import { ArcGisMapServerImageryProvider } from "cesium";
    
    const ExampleComponent = ({ url }) => {
      // Use useMemo to keep the provider instance stable
      const imageryProvider = useMemo(
        () => new ArcGisMapServerImageryProvider({ url }),
        [url],
      );
    
      return (
        <Viewer>
          <ImageryLayer imageryProvider={imageryProvider} />
        </Viewer>
      );
    };
  4. How component context and availability work

    main

    Resium uses React's Context API to provide Cesium elements to child components.

    Root Components

    All components (except root components) must be mounted inside one of the two root components:

    • Viewer
    • CesiumWidget

    Contextual Hierarchy

    Components look for the closest provider in the tree. For example:

    • An Entity mounted under Viewer is added to Viewer#entities.
    • An Entity mounted under a CustomDataSource (which itself is under Viewer) is added to CustomDataSource#entities.

    If a component is mounted without a valid parent context, it will not render.

  5. Use modular @cesium/engine and @cesium/widgets with Resium

    main

    Resium is built to work with the monolithic cesium package, but you can use the modular @cesium/engine and @cesium/widgets packages instead by aliasing cesium to a local re-export module. This allows for smaller bundles.

    Engine-only vs. Full Setup

    • Full Setup (with <Viewer>): Requires both @cesium/engine and @cesium/widgets. You must also import the widgets CSS.
    • Engine-only Setup (with <CesiumWidget>): Requires only @cesium/engine. You can omit @cesium/widgets entirely, skip the CSS import, and avoid the widgets re-export. This is a lighter setup.

    Warning: If using both packages, ensure @cesium/engine and @cesium/widgets are on matching versions to avoid runtime errors.

    # Full setup (with <Viewer>)
    npm install --save @cesium/engine @cesium/widgets resium
    
    # Engine-only setup (use <CesiumWidget> instead of <Viewer>)
    npm install --save @cesium/engine resium
  6. Quickstart with Resium

    main

    Resium provides a declarative way to build Cesium applications using React components. You can wrap your Cesium scene in a <Viewer> component and define entities, like a point in Tokyo, using the <Entity> component. This approach allows you to leverage React's lifecycle and state management alongside Cesium's powerful geospatial capabilities.

    <Viewer full>
      <Entity
        name="tokyo"
        description="test"
        position={Cartesian3.fromDegrees(139.767052, 35.681167, 100)}
      />
    </Viewer>
  7. Access a raw Cesium element using refs

    main

    You can access the underlying Cesium instance of a component using the ref prop. The actual Cesium object is located at ref.current.cesiumElement.

    Important: Initialization Timing

    cesiumElement is not available immediately during the first render. For root components like Viewer, initialization may even be asynchronous.

    Best Practices:

    • Always read cesiumElement inside useEffect, event handlers, or other callbacks.
    • Do not attempt to access it synchronously during the render phase.
    • Check if cesiumElement is not undefined before using it.

    TypeScript Usage

    Use the CesiumComponentRef<T> type to provide type safety for the ref.

    import { useEffect, useRef } from "react";
    import { Viewer as CesiumViewer } from "cesium";
    import { Viewer, CesiumComponentRef } from "resium";
    
    const ExampleComponent = () => {
      // Use CesiumComponentRef for type safety
      const ref = useRef<CesiumComponentRef<CesiumViewer>>(null);
    
      useEffect(() => {
        if (ref.current?.cesiumElement) {
          // ref.current.cesiumElement is the actual Cesium Viewer
          console.log(ref.current.cesiumElement);
        }
      }, []);
    
      return <Viewer ref={ref} />;
    };
  8. Set up a local Resium development environment

    main

    To contribute to Resium, you need an editor that supports TypeScript and ESLint. Follow these steps to set up your local environment:

    1. Fork the resium repository.
    2. Clone your fork locally.
    3. Install dependencies using npm install.

    Common development commands:

    • Run tests: npm test
    • Start Storybook: npm run storybook
    • Build the project: npm run build
    npm install
    npm test
    npm run storybook
    npm run build
  9. Load GeoJSON and KML data

    main

    Resium provides data source components to load external files or objects. Use <GeoJsonDataSource /> for GeoJSON data and <KmlDataSource /> for KML data. Both accept a data prop which can be a URL string or a data object.

    import { Viewer, GeoJsonDataSource, KmlDataSource } from "resium";
    
    const data = {
      type: "Feature",
      properties: {
        name: "Coors Field",
      },
      geometry: {
        type: "Point",
        coordinates: [-104.99404, 39.75621],
      },
    };
    
    function App() {
      return (
        <Viewer full>
          <GeoJsonDataSource data="your_geo_json.geojson" />
          <KmlDataSource data="your_geo_json.kml" />
          <GeoJsonDataSource data={data} />
        </Viewer>
      );
    }
    
    export default App;
  10. Use React Suspense with DataSources

    main

    The GeoJsonDataSource, KmlDataSource, and CzmlDataSource components support React Suspense. By passing the suspense prop, these components will trigger a Suspense boundary while fetching data if the data prop is a URL or a Cesium Resource.

    When suspense is enabled:

    • A parent <Suspense> component's fallback will be displayed during the fetch.
    • A parent Error Boundary will catch any loading failures.
    • Fetched results are cached by URL automatically.

    To manage the cache, use the cacheKey prop:

    • Use a unique cacheKey to bust the cache when content at a stable URL changes.
    • Use the same cacheKey across different components to deduplicate fetches for the same resource.

    If the suspense prop is omitted, the components use their default behavior of asynchronous loading via onLoad and onError callbacks.

    import { Suspense } from "react";
    import { Viewer, GeoJsonDataSource } from "resium";
    
    const App = () => (
      <Viewer>
        <Suspense fallback={<Loading />}>
          <GeoJsonDataSource data="/path/to/data.geojson" suspense />
        </Suspense>
      </Viewer>
    );
    
    // Example with cacheKey to bust cache or dedupe
    <GeoJsonDataSource data={url} suspense cacheKey="data-v2" />
  11. Setup Resium with Webpack (Option A: Load Cesium via HTML)

    main

    In this approach, Cesium is loaded via script tags in your HTML rather than being bundled into your JS files. This requires marking cesium as an external in Webpack.

    1. Install required plugins: copy-webpack-plugin, html-webpack-plugin, and html-webpack-tags-plugin.
    2. Set externals: { cesium: "Cesium" } in your Webpack config.
    3. Use CopyWebpackPlugin to move the entire cesium/Build/Cesium directory to your output folder.
    4. Use HtmlTagsPlugin to inject cesium/Widgets/widgets.css and cesium/Cesium.js into your index.html.
    5. Use webpack.DefinePlugin to set CESIUM_BASE_URL to the correct path (e.g., "/cesium").
    // webpack.config.js snippet
    {
      externals: {
        cesium: "Cesium"
      },
      plugins: [
        new CopyWebpackPlugin({
          patterns: [
            { from: "node_modules/cesium/Build/Cesium", to: "cesium" },
          ],
        }),
        new HtmlPlugin({ template: "index.html" }),
        new HtmlTagsPlugin({
          append: false,
          tags: ["cesium/Widgets/widgets.css", "cesium/Cesium.js"],
        }),
        new webpack.DefinePlugin({
          CESIUM_BASE_URL: JSON.stringify("/cesium"),
        }),
      ]
    }