React Leaflet Collision - react-leaflet

I am using map without any label of town. I am putting the labels, i need these labels appear and dissapear on zoom in/out. When zoom out appear only big towns when zoom in appear all towns. Is there fuctionality for this in existing react-leaflet?
Also i am trying make plugin for Leaflet.LayerGroup.Collision,
I tried overriding LayerGroup from react-leaflet
import React, { PropTypes } from 'react';
import { MapLayer } from 'react-leaflet';
import { layerGroup } from 'leaflet';
import './leaflet-layergroup-collision';
export default class LayerGroupCollision extends MapLayer {
static childContextTypes = {
layerContainer: PropTypes.shape({
addLayer: PropTypes.func.isRequired,
removeLayer: PropTypes.func.isRequired,
})
}
getChildContext() {
return {
layerContainer: this.leafletElement,
}
}
createLeafletElement() {
return layerGroup(this.getOptions()).collision({margin:5});
}
}
But i am getting error
Uncaught TypeError: (0 , _leaflet.layerGroup)(...).collision is not a function
Any help how implement this or any alternative idea?

layerGroup should be with capital L, LayerGroup
import { LayerGroup } from 'leaflet';
or an alternative try:
import L from 'leaflet';
and then
L.LayerGroup(this.getOptions()).collision({margin:5});

Related

Multiple Doughnut chart as markers over Openstreetmap

I'm in trouble while implementing the doughnut chart over OpenStreetMap. I'm using react-chartjs2 for the doughnut chart and react-leaflet for Openstreetmap. Like we use the location icon on different coordinates over the map but here I want to use a Doughnut graph over the map instead of the location icon.
I want to achieve something like this
As per the react-leaflet documentation, the Marker icon property accepts two types of icons that is icon strings like image URL and divIcon which can be some HTML elements but while I'm rendering react component it does not accept and not showing it.
Here you can check in codesandbox I have added code to make it easy to try
https://codesandbox.io/s/doughnut-chart-over-osm-map-1indvl?file=/src/App.js
For what I know marker Icons can only be static, I use a function to create my only markers based on icons and plain html. Will be hard to do that with a component in your case.
My icon render function
import { divIcon } from "leaflet";
import { ReactElement } from "react";
import { renderToString } from "react-dom/server";
export const createLeafletIcon = (
icon: ReactElement,
size: number,
className?: string,
width: number = size,
height: number = size
) => {
return divIcon({
html: renderToString(icon),
iconSize: [width, height],
iconAnchor: [width / 2, height],
popupAnchor: [0, -height],
className: className ? className : "",
});
};
In your case I would try to cheese it and create blank markers and show the graph in popups instead and just force the popups to alway stay open.
EDIT: Added my custom Marker code below that have some nice options.
You can just use the defaultOpen option, and add the graph as a child component to the marker and it will show up in the popup. You can the change the styling of you liking to make it look like the graph is the marker.
import { LatLngLiteral } from "leaflet";
import React, { Children, ReactElement, useEffect, useRef } from "react";
import { Marker, Popup, useMap } from "react-leaflet";
import { MapPin } from "tabler-icons-react";
import { createLeafletIcon } from "./utils";
export interface LeafletMarkerProps {
position: LatLngLiteral;
flyToPosition?: boolean;
size?: number;
color?: string;
icon?: ReactElement;
defaultOpen?: boolean;
onOpen?: () => void;
children?: React.ReactNode;
markerType?: string;
zIndexOffset?: number;
}
const LeafletMarker: React.FC<LeafletMarkerProps> = ({
position,
flyToPosition = false,
children,
size = 30,
color,
defaultOpen = false,
onOpen,
icon = <MapPin size={size} color={color} />,
markerType,
zIndexOffset,
}) => {
const map = useMap();
const markerRef = useRef(null);
position && flyToPosition && map.flyTo(position);
const markerIcon = createLeafletIcon(icon, size, markerType); // Important to not get default styling
useEffect(() => {
if (defaultOpen) {
try {
// #ts-ignore
if (markerRef.current !== null && !markerRef.current.isPopupOpen()) {
// #ts-ignore
markerRef.current.openPopup();
}
} catch (error) {}
}
}, [defaultOpen, position.lat, position.lng]);
return (
<Marker
eventHandlers={{
popupopen: () => onOpen && onOpen(),
}}
ref={markerRef}
icon={markerIcon}
position={position}
zIndexOffset={zIndexOffset}
>
{/* autoPan important to not have jittering */}
<Popup autoPan={false}>{children}</Popup>
</Marker>
);
};
export default LeafletMarker;

How to import Lottie component?

Remix is prone to the following error when using import on top-level components TypeError: Cannot read properties of undefined (reading 'root').
So I've done as they recommend and have the following imports.server.tsx file.
export * from "lottie-react";
Then my component app.tsx looks exactly like this lottie example.
import React from "react";
import * as Lottie from "../imports.server";
import groovyWalkAnimation from "../../public/assets/102875-cinema-clap.json";
export default function App() {
return (
<>
<h1>lottie-react - Component</h1>
<Lottie animationData={groovyWalkAnimation} />;
</>
);
}
but I get the following error
JSX element type 'Lottie' does not have any construct or call
signatures.ts(2604)
Edit 1:
The following seems to have worked for imports:
imports.server.tsx
import Lottie from "lottie-react";
export default Lottie;
AppTry.tsx
import React from "react";
import Lottie from "../imports.server";
import groovyWalkAnimation from "../../public/assets/102875-cinema-clap.json";
export default function AppTry() {
// console.log(LottieModule);
return (
<>
<h1>lottie-react - Component</h1>
<Lottie animationData={groovyWalkAnimation}></Lottie>
</>
);
}
Now the various paramaters like "animationData" and "autoPlay" pop up on the Lottie component which I assume means the import is working? However I am now getting this error when rendering AppTry.tsx?
react.development.js:220 Warning: React.createElement: type is invalid
-- expected a string (for built-in components) or a class/function (for composite components) but got: object. You likely forgot to
export your component from the file it's defined in, or you might have
mixed up default and named imports.
Check the render method of AppTry.
Edit 2:
import { useLottie } from "lottie-react";
import Lottie from "lottie-react";
import groovyWalkAnimation from "../../public/assets/102875-cinema-clap.json";
const Example = () => {
const options = {
animationData: groovyWalkAnimation,
loop: true,
autoplay: true,
};
const { View } = useLottie(options);
return View;
};
const Example1 = () => {
return <Lottie animationData={groovyWalkAnimation} />;
};
export const TopicOverview = () => {
return (
<div className="space-y-20">
<Example1></Example1>
<Example></Example>
</div>
);
};
Looks like it has to do with your way of importing Lottie.
Shouldn't you import Lottie like this?:
import Lottie from "lottie-react";
I also struggled to get this working in Remix.
You can do the lazy load import somewhere higher up in the tree too.
import type { LottiePlayer } from "#lottiefiles/lottie-player";
import { useEffect } from "react";
interface LottieCompProps {
src: LottiePlayer["src"];
style?: Partial<LottiePlayer["style"]>;
}
function LottieComp({ src, style = {} }: LottieCompProps): JSX.Element | null {
// NB: otherwise, will cause app to crash. see https://remix.run/docs/en/v1/guides/constraints#third-party-module-side-effects
useEffect(() => {
import("#lottiefiles/lottie-player");
},[]);
if (typeof document === "undefined") return null;
return (
//#ts-expect-error dynamic import
<lottie-player
autoplay
loop
mode="normal"
src={typeof src === "string" ? src : JSON.stringify(src)}
style={{
...{
width: "100%",
backgroundColor: "transparent",
},
...style,
}}
/>
);
}
export default LottieComp;
The issue was in my root.tsx, an ErrorBoundary() function that called an <UnexpectedErrors/> component.
This same component was being called in various slug.tsx files. For some reason remix did not like this.
Having two different <UnexpectedErrors/> and <UnexpectedErrors2/> components - one for the slug.tsx files and one for the index.tsx files fixed this.

Change the center of React-leaflet

I am trying to dynamically change the center of a map-container with data provided externally. I get the data as a string, and then parse it to get it as numbers instead. But when I enter lat to the const center, I get a NaN when trying use it.
import React from 'react'
import { useCasparData } from 'caspar-graphics'
import { useTimeline } from '#nxtedition/graphics-kit'
import './style.css'
import { MapContainer, TileLayer, Marker, Popup, useMap } from 'react-leaflet'
import './leaflet.css'
export default function Lowerthird () {
const { text01, text02, text03, text04 } = useCasparData()
const lat = parseFloat(text01)
const zoom = 15
const center = [lat, 13.440222]
function onLoad(timeline) {
timeline
.add('start')
.from('.name', { x: -2000 }, 'start')
.from('.titel', { x: -1000 }, 'start')
}
function onStop(timeline) {
timeline
.reverse()
}
//useTimeline(onLoad, onStop)
return (
<MapContainer center={center} zoom={zoom} zoomControl={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
)
}
export const previewData = {
text01: '59.392133',
text02: '13.440222',
text03: '15',
text04: '[59.392133, 13.440222]'
}
I have looked through several threads here, but I have not found a answer that solves this for me... I do realize that the map-container is immutable - I just can't seem to figure out how to update it or set a new center...
(Oh... I am a total noob to react/leaflet, I am just trying to find a simple way to use Openstreetmap as a overlay in our broadcasts (tv))
You can use useMap() from a child component.
And with map, you can use functions such as flyTo to move around on the map.
Sadly useMap needs to be used from the child scope of MapContainer, however, you can just create an invisible component and add as a child and provide it with the new position when you want it to move.
So something like this (I have not tested this code but I have done similar stuff with markers):
interface ChangeCenterProps {
position: { lat: number; lng: number };
}
const ChangeCenter: React.FC<ChangeCenterProps> = ({ position }) => {
const map = useMap();
map.flyTo(position);
return <></>;
};

Support for a permanent clipped AppDrawer

I'm trying to make a permanent clipped navigation drawer with Material UI as per https://material.io/guidelines/patterns/navigation-drawer.html
Seems that there is a pull request out for this but not yet merged: https://github.com/callemall/material-ui/pull/6878
At this stage I'm trying to override with styles but can not get my left nav (paper) to apply the style marginTop: '50px',
Are there some samples out there on how to achieve this with v1.0.0-alpha.21?
They changed the way you have to override certain styles in v1. The inline styles no longer work. Certain parts of a component can be overridden with a simple className applied to the component. See this link for further details https://material-ui-1dab0.firebaseapp.com/customization/overrides.
Some deeper nested properties of certain components i.e the height of the Drawer can only be accessed by overriding the class itself. In this case the paper class of the drawer element.
This is a simple example
import React, { Component } from "react";
import Drawer from "material-ui/Drawer";
import { withStyles, createStyleSheet } from "material-ui/styles";
import PropTypes from 'prop-types';
const styleSheet = createStyleSheet("SideNav", {
paper: {
marginTop: '50px'
}
});
class SideNav extends Component {
....
render() {
return (
<Drawer
classes={{paper: this.props.classes.paper}}
docked={true}
>
....
</Drawer>
);
}
}
SideNav.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styleSheet)(SideNav);

Render mapbox vector tiles inside react-leaflet?

Is there a way to use vector tiles from react-leaflet?
I am aware of Leaflet.VectorGrid, but it is not written for react-leaflet?
For react-leaflet v2, export the MapBoxGLLayer component wrapped with HOC withLeaflet() to get it working.
Steps:
1.Install mapbox-gl-leaflet.
npm i mapbox-gl-leaflet
2.Add mapbox-gl js and css to index.html
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.51.0/mapbox-gl.css' rel='stylesheet' />
3.Add this component.
import L from "leaflet";
import {} from "mapbox-gl-leaflet";
import PropTypes from "prop-types";
import { GridLayer, withLeaflet } from "react-leaflet";
class MapBoxGLLayer extends GridLayer {
createLeafletElement(props) {
return L.mapboxGL(props);
}
}
/*
* Props are the options supported by Mapbox Map object
* Find options here:https://www.mapbox.com/mapbox-gl-js/api/#new-mapboxgl-map-options-
*/
MapBoxGLLayer.propTypes = {
accessToken: PropTypes.string.isRequired,
style: PropTypes.string
};
MapBoxGLLayer.defaultProps = {
style: "mapbox://styles/mapbox/streets-v9"
};
export default withLeaflet(MapBoxGLLayer);
4.Use the MapBoxGLLayer component.
class App extends Component {
state = {
center: [51.505, -0.091],
zoom: 13
};
render() {
return (
<div>
<Map center={this.state.center} zoom={this.state.zoom}>
<MapBoxGLLayer
accessToken={MAPBOX_ACCESS_TOKEN}
style="mapbox://styles/mapbox/streets-v9"
/>
</Map>
</div>
);
}
}
Find the working code here (Add your own mapbox token): https://codesandbox.io/s/ooypokn26y
There are some really nice vector tiles examples in this react-leaflet issue (mapbox-gl example reproduced below).
// #flow
import L from 'leaflet'
import {} from 'mapbox-gl-leaflet'
import {PropTypes} from 'react'
import { GridLayer } from 'react-leaflet'
export default class MapBoxGLLayer extends GridLayer {
static propTypes = {
opacity: PropTypes.number,
accessToken: PropTypes.string.isRequired,
style: PropTypes.string,
zIndex: PropTypes.number,
}
createLeafletElement(props: Object): Object {
return L.mapboxGL(props)
}
}
and the usage of the above component:
<Map>
<MapBoxGLLayer
url={url}
accessToken={MAPBOX_ACCESS_TOKEN}
style='https://style.example.com/style.json'
/>
</Map>
NOTE: you may also need to npm install mapbox-gl and import that library and assign into to the global window.mapboxgl = mapboxgl to avoid issues with mapboxgl being undefined.
You can create a custom component by extending the MapLayer component. You can see an example of how this is done in react-leaflet 1.0 in a project I contributed to here.
In case anyone finds this question and is wondering how to do this with MapLibre GL JS (FOSS fork of Mapbox GL JS) as the backend renderer, you can do this but it's not immediately obvious. The MapLibre equivalent plugin is actively maintained now while the Mapbox one is not.
Here is the component code (in TypeScript) for a MapLibre tile layer that you can use instead of TileLayer in your React Leaflet MapContainer:
import {
type LayerProps,
createElementObject,
createTileLayerComponent,
updateGridLayer,
withPane,
} from '#react-leaflet/core'
import L from 'leaflet'
import '#maplibre/maplibre-gl-leaflet'
export interface MapLibreTileLayerProps extends L.LeafletMaplibreGLOptions, LayerProps {
url: string,
attribution: string,
}
export const MapLibreTileLayer = createTileLayerComponent<
L.MaplibreGL,
MapLibreTileLayerProps
>(
function createTileLayer({ url, attribution, ...options }, context) {
const layer = L.maplibreGL({style: url, attribution: attribution, noWrap: true}, withPane(options, context))
return createElementObject(layer, context)
},
function updateTileLayer(layer, props, prevProps) {
updateGridLayer(layer, props, prevProps)
const { url, attribution } = props
if (url != null && url !== prevProps.url) {
layer.getMaplibreMap().setStyle(url)
}
if (attribution != null && attribution !== prevProps.attribution) {
layer.options.attribution = attribution
}
},
)
Full sample code lives in this repo on GitHub: https://github.com/stadiamaps/react-leaflet-demo