【问题标题】:How to extend TileLayer component in react-leaflet v3?如何在 react-leaflet v3 中扩展 TileLayer 组件?
【发布时间】:2021-01-11 08:55:23
【问题描述】:

我正在尝试在“react-leaflet”v3 中扩展 TileLayer 组件。有必要重写此函数以提供自定义磁贴 URL 命名方案。 我需要的一个例子,写在基本的传单中:

function initMap() {
    L.TileLayer.WebGis = L.TileLayer.extend({

        initialize: function (url, options) {
            options = L.setOptions(this, options);
            if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
                options.tileSize = Math.floor(options.tileSize / 2);
                options.zoomOffset++;
                if (options.minZoom > 0) {
                    options.minZoom--;
                }
                this.options.maxZoom--;
            }
            if (options.bounds) {
                options.bounds = L.latLngBounds(options.bounds);
            }
            this._url = url + "/gis_render/{x}_{y}_{z}/" + options.userId + "/tile.png";
            var subdomains = this.options.subdomains;
            if (typeof subdomains === 'string') {
                this.options.subdomains = subdomains.split('');
            }
        },

        getTileUrl: function (tilePoint) {
            return L.Util.template(this._url, L.extend({
                s: this._getSubdomain(tilePoint),
                z: 17 - this._map._zoom,
                x: tilePoint.x,
                y: tilePoint.y
            }, this.options));
        }
    });

    L.tileLayer.webGis = function (url, options) {
        return new L.TileLayer.WebGis(url, options);
    };

    // create a map in the "map" div, set the view to a given place and zoom
    var map = L.map('map').setView([53.9, 27.55], 10);

    // add an Gurtam Maps tile layer
    L.tileLayer.webGis(wialon.core.Session.getInstance().getBaseGisUrl('render'), {
        attribution: 'Gurtam Maps',
        minZoom: 4,
        userId: wialon.core.Session.getInstance().getCurrUser().getId()
    }).addTo(map);

}

如果我只是将 Gurtam 地图的 url 写入 TileLayer 组件的 'url' 属性,那么我的地图显示不正确(缩放和平铺错误)。

我不知道用什么来正确显示:

  1. 使用“useRef”挂钩获取当前 TileLayer 实例并对其进行扩展。
  2. 使用包“react-leaflet/core”中的一些钩子(可能是 createElementHook)并创建我自己的自定义组件
  3. 或者别的什么

如有任何解释,我将不胜感激。

【问题讨论】:

    标签: react-leaflet


    【解决方案1】:

    对于使用react-leafettypescript 的任何人:我以LeafletJS 样式和基于Seth Lutske's answer 的打字稿重新创建了kitten example from leaflet

    Javascript:

    import L from "leaflet";
    import { createLayerComponent } from "@react-leaflet/core";
    
    // @see https://stackoverflow.com/a/65713838/4295853
    // @ts-ignore
    L.TileLayer.Kitten = L.TileLayer.extend({
        getTileUrl: function(coords: L.Coords) {
            var i = Math.ceil( Math.random() * 4 );
            return "https://placekitten.com/256/256?image=" + i;
        },
        getAttribution: function() {
            return "<a href='https://placekitten.com/attribution.html'>PlaceKitten</a>"
        }
    });
    
    // @ts-ignore
    L.tileLayer.kitten = function() {
        // @ts-ignore
        return new L.TileLayer.Kitten();
    }
    
    // @ts-ignore
    const createKittenLayer = (props, context) => {
        // @ts-ignore
        const instance = L.tileLayer.kitten(props.url, {...props});
        return {instance, context};
    }
    
    // @ts-ignore
    const updateKittenLayer = (instance, props, prevProps) => {
        if (prevProps.url !== props.url) {
            if (instance.setUrl) instance.setUrl(props.url)
        }
    
        if (prevProps.userId !== props.userId) {
            if (instance.setUserId) instance.setUserId(props.userId)
        }
    }
    
    const KittenLayer = createLayerComponent(createKittenLayer, updateKittenLayer);
    export default KittenLayer;
    

    Typescript(实用但不是传单约定见this SO answer's comments):

    import L, { Coords, DoneCallback, GridLayer } from "leaflet";
    import {createLayerComponent, LayerProps } from "@react-leaflet/core";
    import { ReactNode } from "react";
    
    interface KittenProps extends LayerProps {
        userId: string,
        children?: ReactNode // PropsWithChildren is not exported by @react-leaflet/core
    }
    
    class Kitten extends L.TileLayer {
    
        getTileUrl(coords: L.Coords) {
            var i = Math.ceil( Math.random() * 4 );
            return "https://placekitten.com/256/256?image=" + i;
        }
    
        getAttribution() {
            return "<a href='https://placekitten.com/attribution.html'>PlaceKitten</a>"
        }
    
    }
    
    const createKittenLayer = (props: KittenProps, context:any) => {
        const instance = new Kitten("placeholder", {...props});
        return {instance, context};
    }
    
    const updateKittenLayer = (instance: any, props: KittenProps, prevProps: KittenProps) => {
          if (prevProps.userId !== props.userId) {
            if (instance.setUserId) instance.setUserId(props.userId)
          }
        
    }
    
    const KittenLayer = createLayerComponent(createKittenLayer, updateKittenLayer);
    export default KittenLayer;
    
    

    【讨论】:

      【解决方案2】:

      听起来您正在尝试在react-leaflet v3 中创建自定义组件。如果你对 react-leaflet 不是很熟悉,这可能会让人望而生畏。编写自定义组件的文档有点难以理解。我发现这部分很有帮助:Element hook factory

      所以您需要从 2 个基本功能入手。一个函数来创建你的元素,一个函数来更新它。要创建它,

      // Assuming you've already defined L.TileLayer.WebGis somewhere
      // and attached it to the global L
      
      const createWebGisLayer = (props, context) => {
      
        const instance = L.tileLayer.webGis(props.url, {...props})
      
        return { instance, context }
      
      }
      

      然后你需要另一个函数来处理你想要渗透到组件中的任何更新。例如,如果您的组件的某个 prop 发生更改,您需要明确告诉 react-leaflet 更新该组件的底层传单实例:

      const updateWebGisLayer = (instance, props, prevProps) => {
      
        if (prevProps.url !== props.url) {
          if (instance.setUrl) instance.setUrl(props.url)
        }
      
        if (prevProps.userId !== props.userId) {
          if (instance.setUserId) instance.setUserId(props.userId)
        }
      
      }
      

      您需要这些 setter 函数来告诉 react-leaflet,如果 urluserId(或其他) prop 在 react 中发生变化,则需要重新渲染传单层。 setUrl 已存在于 L.TileLayer 上,但您需要定义一个 setUserId 以更新 L.TileLayer.WebGis 实例的 userId 选项。如果你不包含这些,当它的 props 改变时你的组件不会更新。

      总而言之,您可以使用createLayerComponent 工厂函数:

      const WebGisLayer = createLayerComponent(createWebGisLayer, updateWebGisLayer)
      export WebGisLayer
      

      WebGisLayer 现在是一个反应组件,您可以将其用作MapContainer 的子组件

      const App = () => {
      
        const [userId, setUserId] = useState('user123')
      
        return (
          <MapContainer center={center} zoom={zoom}>
            <WebGisLayer 
              url="some_url" 
              userId={userId}
              otherPropsYouNeed={otherProps} />
          </MapContainer>
        )
      
      }
      

      当组件加载时,它将运行您的传单代码并将传单组件添加到地图中。如果 userId 由于某些 setstate 调用而发生变化,您的 updateWebGisLayer 函数会告诉 react-leaflet 更新底层的传单组件。

      有很多方法可以做到这一点,但这是我认为最简单的一种。我还没有机会测试此代码,因此您不可避免地必须尝试使用​​它才能使其正常工作,但这应该可以帮助您入门。

      【讨论】:

      • 尝试按照此处提到的步骤进行操作。我无法呈现我的自定义组件并收到错误No context provided: useLeafletContext() can only be used in a descendant of &lt;MapContainer&gt;。我是做错了什么,还是我错过了任何进口让它起作用。我正在使用react-leaflet ^3.2.0leaflet ^1.7.1
      • 我必须查看代码才能知道问题所在。您确定您的自定义组件被用作 MapContainer 的子组件吗?
      • 是的。我将它用作 mapp 容器的子组件。让我看看我能不能放一个这个的stackblitz链接。
      • 由于使用了@react-leaflet/core ^1.0.2,我收到了这个错误。将版本升级到@react-leaflet/core ^1.1.0 修复了它。谢谢你的帮助。在设置 stackblitz 链接时发现了这个问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多