polylinedacorator with react leaflet 4 - reactjs

I am trying to include arrows to the Polyline in react-leaft. For that I am using polylinedecorator plugin. There is a similar post on this platform. However, it uses withLeaflet module which is not supported in react-leaflet 4.0. How can I make it run without using 'withLeaflet'.
I have tried to implement it with the hooks. However, it does not work and need some assistance, how can I make it run.
export default function App(): JSX.Element {
const polylineRef = useRef<any>(null);
const arrow = [
{
offset: "100%",
repeat: 0,
symbol: L.Symbol.arrowHead({
pixelSize: 15,
polygon: false,
pathOptions: { stroke: true }
})
}];
useEffect(()=>{
L.polylineDecorator(polylineRef.current,{
patterns: arrow
})
}, [polylineRef]);
return (
<MapContainer center={center} zoom={13} scrollWheelZoom={true} style={{height: 'calc(100% - 30px)'}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
{currentData?.movingActors.map(line =>(<Polyline key={line.id}
positions={[line.startLocation, line.endLocation] } ref={polylineRef}
color={modeColor(line.mode)}
/>
))}
</MapContainer>
</>);}
CHANGES MADE TO THE ACCEPTED ANSWER TO MAKE IT RUN
function PolylineDecorator({ patterns, polyline,color }) {
const map = useMap();
useEffect(() => {
if (!map) return;
L.polyline(polyline, {color}).addTo(map); // added color property
L.polylineDecorator(polyline, {
patterns,
}).addTo(map);
}, [map]);
return null;
}
{currentData?.movingActors.map(line =>(<PolylineDecorator key={line.id} patterns ={arrow} polyline={position} color = {modeColor(line.mode)} />) ) } //here I used color parameters to dynamically add colors

What you need is a custom react functional component that returns null and has a useEffect with the code to initialize the plugin:
function PolylineDecorator({ patterns, polyline }) {
const map = useMap();
useEffect(() => {
if (!map) return;
L.polyline(polyline).addTo(map);
L.polylineDecorator(polyline, {
patterns
}).addTo(map);
}, [map]);
return null;
}
and then use it like:
<MapContainer...>
<TileLayer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" />
<PolylineDecorator patterns={arrow} polyline={polyline} />
</MapContainer>
Demo

Related

Show vector layers through Leaflet.MagnifyingGlass on react-leaflet 4

I am implementing the 'Leaflet.MagnifyingGlass' on my typescript react-leaflet v4 project. I am able to show the magnifying glass on the tile layer however, I am not sure, how to implement it on the already overlaid vector layer.
function Lense (){
const lenseLayer = useMap();
const magnifiedTiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
useEffect(() => {
L.magnifyingGlass({
layers: [magnifiedTiles]
//layers: [PolylineDecorator]
}).addTo(lenseLayer)
}, [lenseLayer]);
return <></>;
}
export default function App(): JSX.Element {
<MapContainer center={center} zoom={13} scrollWheelZoom={true} style={{height: 'calc(100% - 30px)'}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' />
{currentData?.movingActors.map(line =>(<PolylineDecorator key={line.id}
polyline={positions} color={modeColor(line.mode)}/>))}
<Lense />
</MapContainer>
</>
);
}
As one can see, that at the moment, magnifying glass only is laid to tile object. I need to lay it on top of the polylindedecorator object. The function is defined as
function PolylineDecorator({ polyline,color }) {
const map = useMap();
useEffect(() => {
if (!map) return;
var x = L.polyline(polyline, {color}).arrowheads({ size: '5%' });
x.addTo(map);
}, [map]);
return null;
}
Any suggestions, how can I achieve that.
PS I have already tried react-leaflet-magnifying-glass, but it is not compatible with the latest react-leaflet version.

Rendering a MapContainer on several subpages results in error

I'm having some issues with MapContainer from react-leaflet throwing errors at me in my app.
I have a tabel of events, that links to a page with event details, this details page implements a map with the following component:
export function MapWithPoints(props: Props) {
const { points } = props
return (
<MapContainer scrollWheelZoom={false} style={{ height: 300, width: '100vw' }}>
<InnerMap points={points} />
</MapContainer>
)
}
InnerMap is defined as:
function InnerMap(props: Props) {
const { points } = props
const leafletMap = useMap()
React.useEffect(() => {
if (leafletMap && points.length) {
const pointArray: L.LatLngTuple[] = points.map((item) => [
item.lat,
item.lng,
])
const bounds = L.latLngBounds(pointArray)
leafletMap.fitBounds(bounds)
}
}, [points, leafletMap])
return (
<React.Fragment>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{points
.filter((poi) => poi.lat && poi.lng)
.map((poi) => (
<Marker
position={[poi.lat, poi.lng]}
key={`${poi.id}_${poi.lat}_${poi.lng}`}
icon={poi.icon ? poi.icon : DefaultIcon}
>
{poi.description ? <Popup>{poi.description}</Popup> : null}
</Marker>
))}
</React.Fragment>
)
}
The map works when I go to the first (random) entry in my list of events, the event page loads like it should, but if I go back and then choose another event page, MapContainer throws an error:
Uncaught Error: Map container is already initialized.
at NewClass._initContainer (Map.js:1092:1)
at NewClass.initialize (Map.js:136:1)
at new NewClass (Class.js:24:1)
at MapContainer.js:31:1
at commitAttachRef (react-dom.development.js:23645:1)
at safelyAttachRef (react-dom.development.js:22891:1)
at reappearLayoutEffectsOnFiber (react-dom.development.js:23545:1)
at reappearLayoutEffects_complete (react-dom.development.js:24838:1)
at reappearLayoutEffects_begin (react-dom.development.js:24826:1)
at commitLayoutEffects_begin (react-dom.development.js:24649:1)
I'm using react 18, and react-leaflet 4.0.0
Is there something I have missed in the setup?
There is a bug in 4.0.0, but even when using the latest version, I still get the error now and then. A workaround is to have a unique key on Map Container, for example timestamp. But this result in a memory leak, as each time a map is loaded it consumes more memory

Deckgl Wrapper Covering map Preventing Map Interaction

I'm working on a fairly basic implementation of Deckgl with ReactMapGl. I'd like to render a polygon overlay that outlines an array of coordinate and I'm following the documentation as well as examples that I found online. Unfortunately in my current implementation the Deckgl Wrapper div covers the entire map preventing the user from being able to interact with it. See Codesandbox example here:
https://codesandbox.io/s/react-map-5dnkoz
const data = [
{
contour: [
[-86.83446165702009, 36.17150121813963],
[-86.8287327938404, 36.15548883458097],
[-86.85771573862695, 36.15235867540224],
[-86.84962906703987, 36.139957124954705],
[-86.86694827924185, 36.1401699318269],
[-86.86802690445148, 36.15681345538646],
[-86.88323041951918, 36.16074825898015],
[-86.86222486725711, 36.17675839228444],
[-86.8486865848925, 36.16747822232059],
[-86.83960001512133, 36.166995304396785],
[-86.83446165702009, 36.17150121813963]
]
}
];
const layer = new PolygonLayer({
id: "polygon-layer",
data,
pickable: false,
stroked: true,
filled: false,
lineWidthMinPixels: 2,
getPolygon: (d) => d.contour,
getLineColor: [85, 119, 242]
});
export default function IndexPage() {
const viewport = {
latitude: 36.139691,
longitude: -86.803268,
zoom: 11
};
return (
<div>
<Map
initialViewState={viewport}
style={{ width: 800, height: 600 }}
mapStyle="mapbox://styles/mapbox/streets-v9"
mapboxAccessToken={
"pk.eyJ1IjoicGhpbGZlbGl4IiwiYSI6ImNrZTdsc3FkZzA4b3IyeWswbHhueTRkb28ifQ.MtAPCLJVyCsMHIDuXTbQGQ"
}
>
<DeckGL viewState={viewport} layers={[layer]} />
<Marker longitude={-86.803268} latitude={36.139691} color="red" />
</Map>
</div>
);
}

React Leaflet v3 : Good way to create a control

I'm displaying a map with react leaflet v3 in my react app.
I just want to add a custom control, but I can't figure out what's the good way to do that.
Actually, i do it like that, but it seems to be not working.
function DptCtl(props) {
// Control
const map = useMap();
// List of dpts and provinces
const dpts = useSelector(dptsSelector);
L.Control.Dpts = L.Control.extend({
onAdd(map) {
const container = L.DomUtil.create('div');
const input = L.DomUtil.create('input', container);
return container;
},
onRemove(map) {}
})
L.Control.dpts = function (opts) {
return new L.Control.Dpts(opts);
}
useEffect(() => {
const control = L.Control.dpts({ position: props.position })
map.addControl(control)
return () => {
map.removeControl(control)
}
}, [dpts])
return null;
}
React-Leaflet v3 provides the createControlComponent Hook in the Core API that takes in an instance of a Leaflet control and returns a Leaflet element.
Here is an example using Leaflet's Zoom control:
import L from 'leaflet';
import { createControlComponent } from '#react-leaflet/core';
const createControlLayer = (props) => {
// Set up an instance of the control:
const controlInstance = new L.Control.Zoom(props);
return controlInstance;
};
// Pass the control instance to the React-Leaflet createControlComponent hook:
const customControl = createControlComponent(createControlLayer);
export default customControl;
Then, add the new custom control layer to the Map:
<MapContainer
center={[37.0902, -95.7129]}
zoom={3}
zoomControl={false}
style={{ height: '100vh', width: '100%', padding: 0 }}
whenCreated={(map) => setMap(map)}
>
<CustomControl />
<LayersControl position="topright">
<LayersControl.BaseLayer checked name="Map">
<TileLayer
attribution='© OpenStreetMap contributors'
url={maps.base}
/>
</LayersControl.BaseLayer>
</LayersControl>
</MapContainer>
DEMO
https://react-leaflet.js.org/docs/core-api/#createcontrolcomponent
https://javascript.plainenglish.io/how-to-create-a-react-leaflet-control-component-with-leaflet-routing-machine-8eef98259f20

How to find all Mapbox layers in DeckGL?

I'm following this example that puts Mapbox labels on top of a layer. This seems to be written using the plain Mapbox package. I'm hoping to do the same for a map component in DeckGL.
The relevant code from the example:
const map = new mapboxgl.Map({
container: document.body,
style: 'mapbox://styles/mapbox/light-v9',
center: [-122.4, 37.79],
zoom: 15,
pitch: 60
});
map.on('load', () => {
const firstLabelLayerId = map.getStyle().layers.find(layer => layer.type === 'symbol').id;
My code using DeckGL is:
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
layers={layers}
onClick={expandTooltip}
onViewStateChange={hideTooltip}
onWebGLInitialized={onInitialized}
views={MAP_VIEW}
controller={{
touchRotate: true,
inertia: 600,
}}
>
<StaticMap
reuseMaps
mapStyle={MAP_STYLE}
preventStyleDiffing={true}
mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN}
/>
</DeckGL>
How can I access getStyle().layers in the above components? I tried using useRef, as in this simplified component:
const mapRef = useRef();
<DeckGL
{...viewport}
maxZoom={20}
mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN}
ref={mapRef}
>
but found that it doesn't contain information about the component.
You will need to wait until mapLoads, something like:
1 - Define a new ref:
const mapRef = useRef();
2 - Wait for map loads:
<StaticMap
ref={mapRef}
onLoad={onMapLoad}
...otherProps
/>
3 - Use getMap method. Now we are sure that Mapbox instance exists:
const onMapLoad = useCallback(() => {
const map = mapRef.current.getMap();
const mapboxLayers = map.getStyle().layers;
console.log(mapboxLayers);
}, []);
You have to use deckgl layer after map gets loaded into the browser. For that purpose, you can use:
Map.on('load', callback function)
For more reference use this https://youtu.be/x6UcMcAWNMo

Resources