Reputation: 41
I am trying to extend the TileLayer component in 'react-leaflet' v3. It is necessary to override this function to provide custom tile URL naming scheme. An example of what I need, written in basic 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);
}
If I just write a url of Gurtam maps to a 'url' prop of TileLayer component, then my map incorrectly displayed (zoom and tile errors).
I can't figure out what to use for the correct display:
I would be grateful for any explanations.
Upvotes: 4
Views: 5497
Reputation: 281
For anyone using react-leaflet
and typescript
: I recreated the kitten example from leaflet in LeafletJS style and typescript based on Seth Lutske's answer.
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 (practical but not leaflet convention see in 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;
Upvotes: 7
Reputation: 10792
It sounds like you're trying to create a custom component in react-leaflet v3
. If you're not super familiar with react-leaflet, this might be daunting. The docs on writing a custom component are a little hard to follow. I found this section helpful: Element hook factory
So you need to start with 2 basic functions. One function to create your element, and one to update it. To create it,
// 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 }
}
Then you need another function that will handle any updates you want to trickle down into your component. For example, if a certain prop of your component changes, you need to explicitly tell react-leaflet to update the underlying leaflet instance of that component:
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)
}
}
You need these setter functions to tell react-leaflet that if the url
or userId
(or whatever) prop changes in react, it needs to rerender the leaflet layer. setUrl
already exists on L.TileLayer
, but you'll need to define a setUserId
that updated the userId
option of the L.TileLayer.WebGis
instance. If you don't include these, your component will not update when its props changes.
To put it all together, you can use the createLayerComponent
factory function:
const WebGisLayer = createLayerComponent(createWebGisLayer, updateWebGisLayer)
export WebGisLayer
WebGisLayer
is now a react component that you can use as a child of a MapContainer
const App = () => {
const [userId, setUserId] = useState('user123')
return (
<MapContainer center={center} zoom={zoom}>
<WebGisLayer
url="some_url"
userId={userId}
otherPropsYouNeed={otherProps} />
</MapContainer>
)
}
When the component loads it will run your leaflet code and add the leaflet component to the map. If the userId
changes due to some setstate call, your updateWebGisLayer
function tells react-leaflet to update the underlying leaflet component.
There's a lot of ways to do this, but this is the one I think is most straightforward. I haven't had a chance to test this code so you'll inevitably have to play around with it to get it working, but this should get you started.
Upvotes: 6