如何在使用React的Mapbox GL JS中切换图层

huangapple go评论70阅读模式
英文:

How to toggle layers in Mapbox GL JS using React

问题

我使用react-map-gl来显示一个带有多个图层的Mapbox地图。我现在想让用户通过地图上的某种控制面板切换这些图层的可见性。我非常喜欢Mapbox网站上的切换图层示例的外观,但它完全是原生JS,而不是React。

我找到的最好的示例使用复选框来调整图层,但是它是通过使用具有多个内置图层的默认样式,修改定义该样式的JSON,然后将修改后的JSON传递给Map组件的mapStyle属性来实现的。目前,我正在使用此属性来指向我在Mapbox Studio中创建的样式:

<Map
    initialViewState={{
        longitude: -0.239348,
        latitude: 51.592045,
        zoom: 11.5
    }}
    style={{ width: "100%", height: 400 }}
    mapStyle="mapbox://styles/mapboxuser/xxxxxxxxx"
    styleDiffing
    mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN}
    onMouseMove={onHover}
    interactiveLayerIds={['protectedSegments']}
>
    <Source type="geojson" data={boundaries}>
        <Layer {...boundaryStyle} />
    </Source>
    <Source type="geojson" data={majorRoads}>
        <Layer {...majorRoadsStyle} />
    </Source>
    <Source type="geojson" data={protectedSegments}>
        <Layer {...protectedSegmentStyle} />
    </Source>
</Map>

显然,该URL不可路由,因此我无法例如在其上使用fetch

而且,这种方法似乎很奇怪,因为我正在客户端添加到地图上的图层。事实上,Layer组件的API文档中说:

一旦<Layer>被挂载,以下属性不应更改。如果您动态添加/删除多个JSX图层,请确保使用React的key属性为每个元素提供稳定的标识。

示例代码似乎没有处理这个问题,因为它正在调整的图层没有React的<Layer>组件。

我觉得我肯定漏掉了一些明显的东西,因为这是构建Web地图的人最常想要做的事情之一。在Leaflet中,甚至有一个内置控件来让您调整各个图层的可见性。在react-map-gl中该如何实现呢?

英文:

I am using react-map-gl to display a Mapbox map with several layers. I now want to let the user toggle the visibility of these layers using some kind of control panel on the map. I really like the look of the Toggle Layers example on the Mapbox website, but it's all in vanilla JS not React.

The best example I've found uses checkboxes to adjust layers, but it does so by using a default style with several built-in layers, modifying the JSON which defines that style, and then passing the modified JSON to the mapStyle property of the Map component. Presently, I'm using this property to point to a style I have created in Mapbox Studio:

&lt;Map
    initialViewState={{
        longitude: -0.239348,
        latitude: 51.592045,
        zoom: 11.5
    }}
    style={{ width: &quot;100%&quot;, height: 400 }}
    mapStyle=&quot;mapbox://styles/mapboxuser/xxxxxxxxx&quot;
    styleDiffing
    mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN}
    onMouseMove={onHover}
    interactiveLayerIds={[&#39;protectedSegments&#39;]}
&gt;
    &lt;Source type=&quot;geojson&quot; data={boundaries}&gt;
        &lt;Layer {...boundaryStyle} /&gt;
    &lt;/Source&gt;
    &lt;Source type=&quot;geojson&quot; data={majorRoads}&gt;
        &lt;Layer {...majorRoadsStyle} /&gt;
    &lt;/Source&gt;
    &lt;Source type=&quot;geojson&quot; data={protectedSegments}&gt;
        &lt;Layer {...protectedSegmentStyle} /&gt;
    &lt;/Source&gt;
&lt;/Map&gt;

Obviously, that URL isn't routeable, so I couldn't for example use fetch on it.

And anyway, this approach seems odd, since the layers I'm adjusting have been added to the map on the client side. In fact, the API docs for the Layer component say:

> Once a &lt;Layer&gt; is mounted, the following props should not change. If you add/remove multiple JSX layers dynamically, make sure you use React's key prop to give each element a stable identity.

The sample code doesn't seem to deal with this, since the layers that it is adjusting don't have React &lt;Layer&gt; components.

I feel like I must be missing something obvious, since this is one of the most common things someone building a web map would want to do. In Leaflet, there's even a built-in control to let you adjust the visibility of the various layer. How does one do this in react-map-gl?

答案1

得分: 0

你可以使用<Layer>组件的layout属性来调整单个图层的可见性。不过,需要注意的是,你需要将字符串[visiblenone] 1 提供给visibility属性。

Map.js

const [layersVisibility, setLayersVisibility] = React.useReducer(
  (state, updates) => ({ ...state, ...updates }
), {});

return (
  <Map ...>
    <Source key="boundaries" type="geojson" data={boundaries}>
      <Layer key="boundaries"
        {...boundaryStyle}
        layout={{ visibility: layersVisibility["boundaries"] }} />
    </Source>
    <ControlPanel onChange={setLayersVisibility} />
  </Map>
)

ControlPanel.js

function ControlPanel(props) {
  const [visibility, setVisibility] = useState({
    boundaries: true,
    protectedSegments: true,
    majorRoads: true
  });

  useEffect(() => {
    // 将true/false转换为"visible"/"none"
    const visibilityState = Object.fromEntries(
      Object.entries(visibility).map(([k, v]) => [k, v ? "visible" : "none"])
    );
    props.onChange(visibilityState);
  }, [visibility]);

  const onVisibilityChange = (name, value) => {
    setVisibility({ ...visibility, [name]: value });
  };

  return (
    <div className={styles["control-panel"]}>
      <h3>图层</h3>
      <label><input type="checkbox"
        checked={visibility["boundaries"]}
        onChange={evt => onVisibilityChange("boundaries", evt.target.checked)}
      /> 边界</label>
    </div>
  )
}

export default React.memo(ControlPanel);
英文:

You can use the layout property of the &lt;Layer&gt; component to adjust visibility of an individual layer. The trick, though, is that you need to provide the strings visible or none to the visibility property.

Map.js

const [layersVisibility, setLayersVisibility] = React.useReducer(
    (state, updates) =&gt; ({ ...state, ...updates }
), {});

return (
&lt;Map ...&gt;
    &lt;Source key=&quot;boundaries&quot; type=&quot;geojson&quot; data={boundaries}&gt;
        &lt;Layer key=&quot;boundaries&quot;
            {...boundaryStyle}
            layout={{ visibility: layersVisibility[&quot;boundaries&quot;] }} /&gt;
    &lt;/Source&gt;
    &lt;ControlPanel onChange={setLayersVisibility} /&gt;
&lt;/Map&gt;
)

ControlPanel.js

function ControlPanel(props) {
    const [visibility, setVisibility] = useState({
        boundaries: true,
        protectedSegments: true,
        majorRoads: true
    });

    useEffect(() =&gt; {
        // Convert true/false to &quot;visible&quot;/&quot;none&quot;
        const visibilityState = Object.fromEntries(
            Object.entries(visibility).map(([k, v]) =&gt; [k, v ? &quot;visible&quot; : &quot;none&quot;])
        );
        props.onChange(visibilityState);
    }, [visibility]);

    const onVisibilityChange = (name, value) =&gt; {
        setVisibility({ ...visibility, [name]: value });
    };

    return(
        &lt;div className={styles[&quot;control-panel&quot;]}&gt;
            &lt;h3&gt;Layers&lt;/h3&gt;
            &lt;label&gt;&lt;input type=&quot;checkbox&quot;
                checked={visibility[&quot;boundaries&quot;]}
                onChange={evt =&gt; onVisibilityChange(&quot;boundaries&quot;, evt.target.checked)}
            /&gt; Boundaries&lt;/label&gt;
        &lt;/div&gt;
    )
}

export default React.memo(ControlPanel);

huangapple
  • 本文由 发表于 2023年3月10日 01:31:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/75688118.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定