如何在 react-leaflet v3 中扩展 TileLayer 组件?

4

我正在尝试扩展'react-leaflet' v3中的TileLayer组件。需要覆盖此函数以提供自定义的瓦片URL命名方案。 以下是基本leaflet示例:

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. 或者其他方式。
我会非常感激任何解释。
2个回答

6
听起来您想在 react-leaflet v3 中创建一个自定义组件。如果您对 react-leaflet 不是非常熟悉,这可能会有些令人生畏。撰写自定义组件的文档有点难以理解,但我发现了这个章节很有帮助: Element hook factory 因此,您需要从两个基本函数开始。一个用于创建您的元素,另一个用于更新它。要创建它,请使用以下内容:
// 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 }

}

然后,您需要另一个函数来处理您想要传递到组件中的任何更新。例如,如果组件的某个属性发生更改,则需要明确告诉 react-leaflet 更新该组件的底层 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,如果React中的urluserId(或其他任何属性)发生更改,它需要重新渲染leaflet图层。 L.TileLayer上已经存在setUrl,但您需要定义一个setUserId,以更新L.TileLayer.WebGis实例的userId选项。 如果不包括这些内容,则组件在其props更改时将不会更新。
要将所有内容放在一起,可以使用createLayerComponent工厂函数:
const WebGisLayer = createLayerComponent(createWebGisLayer, updateWebGisLayer)
export WebGisLayer

WebGisLayer 现在是一个 React 组件,您可以将其作为 MapContainer 的子组件使用。

const App = () => {

  const [userId, setUserId] = useState('user123')

  return (
    <MapContainer center={center} zoom={zoom}>
      <WebGisLayer 
        url="some_url" 
        userId={userId}
        otherPropsYouNeed={otherProps} />
    </MapContainer>
  )

}

组件加载时会运行您的leaflet代码并将leaflet组件添加到地图中。如果由于某些setState调用而更改了userId,您的updateWebGisLayer函数会告诉react-leaflet更新基础的leaflet组件。 有很多方法可以做到这一点,但我认为这是最直接的方法。我还没有测试过这段代码,所以您肯定需要调试它才能让它正常工作,但这应该能帮助您入门。

尝试按照这里提到的步骤操作,但无法呈现我的自定义组件,并出现错误“未提供上下文:useLeafletContext()只能在<MapContainer>的后代中使用”。我做错了什么或者我错过了某些导入使其工作。我正在使用react-leaflet ^3.2.0leaflet ^1.7.1 - Gunner
我需要看代码才能知道问题所在。你确定你的自定义组件作为MapContainer的子组件使用了吗? - Seth Lutske
是的,我正在将它用作 mapp 容器的子组件。让我看看是否可以放一个 StackBlitz 链接。 - Gunner
我遇到了这个错误,因为使用了@react-leaflet/core ^1.0.2。将版本升级到@react-leaflet/core ^1.1.0后问题得到解决。感谢您的帮助。在设置Stackblitz链接时解决了这个问题。 - Gunner

6

对于使用 react-leaflettypescript 的用户: 我基于 Seth Lutske的答案,重现了LeafletJS风格和typescript格式的kitten 示例

Javascript:

import L from "leaflet";
import { createLayerComponent } from "@react-leaflet/core";

// @see https://dev59.com/hr7pa4cB1Zd3GeqP9_v_#65713838
// @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(实用但不是常规的,见此 SO 回答的评论):

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;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接