I have tried on example from Youtube tutorial "Mapbox Marker clustering". I want to add getLeaves method to grab all of the items inside the cluster in order to visualize a popup with each details for every individual marker.
console.log(cluster.id) returns id of each cluster id, but
const items = supercluster.getLeaves(cluster.id) gets an error that "Error: No cluster with the specified id."
Is it possible to use getLeaves method with react mapbox gl?
For this react code and link to youtube video.
--
import React, { useState, useRef } from 'react';
import useSwr from "swr";
import ReactMapGL, { Marker, FlyToInterpolator, NavigationControl } from 'react-map-gl';
import useSupercluster from 'use-supercluster';
import Supercluster from 'supercluster';
import './App.css'
const fetcher = (...args) => fetch(...args).then(response => response.json());
export default function App(){
//navigation control
const navStyle = {
position: "absolute",
top: 0,
right: 10,
padding: "10px"
};
//set up map
const [viewport, setViewport ] = useState({
latitude: 52.6376,
longitude: -1.135171,
width: "100vw",
height: "100vh",
zoom: 12
});
const mapRef = useRef();
// load and prepare data
const url =
"https://data.police.uk/api/crimes-street/all-crime?lat=52.629729&lng=-1.131592&date=2019-10";
const {data, error} = useSwr(url, fetcher);
const crimes = data && !error ? data.slice(0, 200) : [];
const points = crimes.map(crime => ({
type: "Feature",
properties: {
cluster: false,
crimeId: crime.id,
category: crime.category
},
geometry: {
type: "Point",
coordinates: [
parseFloat(crime.location.longitude),
parseFloat(crime.location.latitude)
]
}
}));
// get map bounds
const bounds = mapRef.current
? mapRef.current
.getMap()
.getBounds()
.toArray()
.flat()
: null;
// get clusters
const {clusters, supercluster } = useSupercluster({
points,
zoom: viewport.zoom,
bounds,
options: { radius: 75, maxZoom: 20 }
});
// return map
return(
<div>
<ReactMapGL
{...viewport}
maxZoom={20}
mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_TOKEN123}
mapStyle="mapbox://styles/shunsukeito/ckaanisi33g1k1ipowgmg8tdd"
onViewportChange={newViewport => {
setViewport({...newViewport});
}}
ref={mapRef}
>
<div className="nav" style={navStyle}>
<NavigationControl
/>
</div>
{clusters.map(cluster => {
const [longitude, latitude] = cluster.geometry.coordinates;
const {
cluster: isCluster,
point_count: pointCount
} = cluster.properties;
//where I get the error!!!
console.log(cluster.id)
const items = supercluster.getLeaves(cluster.id)
if (isCluster){
return(
<Marker key={cluster.id} latitude={latitude} longitude={longitude}>
<div className="cluster-marker"
style={{
width: '${10 + PointCount / points.lenght) * 50}px',
height: '${10 + PointCount / points.lenght) * 50}px'
}}
onClick={() => {
const expansionZoom = Math.min(
supercluster.getClusterExpansionZoom(cluster.id),
20
);
setViewport({
...viewport,
latitude,
longitude,
zoom: expansionZoom,
transitionInterpolator: new FlyToInterpolator({
spped: 2
}),
transitionDuration: "auto"
});
}}
>
{pointCount}
</div>
</Marker>
);
}
return (
<Marker
key={cluster.properties.crimeId}
latitude={latitude}
longitude={longitude}
>
<button className="crime-marker"
>
<img src="/custody.svg" alt="crime doesn't pay" />
</button>
</Marker>
);
})}
</ReactMapGL>
</div>
);
}
--
https://www.youtube.com/watch?v=3HYvbP2pQRA&t=12s
Any suggestions are welcomed.
Thank you,
Related
I'm using the user-supercluster library to portray clusters on my map. I've upgraded React-map-gl to the newest version. The map shows up but I cannot get my clusters or markers to show up. When I console the cluster array it's completely empty.
When I console log the points, the data is there. I'm not sure what is the issue.
import React, { useRef } from "react";
import Map, {
Marker,
FullscreenControl,
NavigationControl,
} from "react-map-gl";
import HotelsQueryContext from "#Src/routes/Hotels/HotelsQueryContext";
import { useSelector } from "react-redux";
import { hotelsSelectors } from "#Src/redux/rootSelectors";
import useSupercluster from "use-supercluster";
import HotelMarker from "./HotelMarker";
import HotelCardWeb from "../../Web/components/HotelCardWeb";
import HotelCardPhone from "../../Phone/components/HotelCardPhone";
import "mapbox-gl/dist/mapbox-gl.css";
import "./Map.scss";
function HotelMap() {
const mapRef = useRef();
const hotelsQuery = React.useContext(HotelsQueryContext);
const filteredHotels = useSelector(hotelsSelectors.getHotelsFilteredList);
const [viewport, setViewport] = React.useState({
latitude: parseFloat(hotelsQuery.lat),
longitude: parseFloat(hotelsQuery.lon),
zoom: 11.3,
});
const [selectedHotel, setSelectedHotel] = React.useState(null);
//Covert filtered hotels to geojson data objects
const points = filteredHotels.map((hotel) => ({
type: "Feature",
properties: {
cluster: false,
hotel_id: hotel.access_hotel_id,
category: hotel.name,
},
geometry: {
type: "Point",
coordinates: [hotel.location.lon, hotel.location.lat],
},
}));
const bounds = mapRef.current
? mapRef.current.getMap().getBounds().toArray().flat()
: null;
const { clusters, supercluster } = useSupercluster({
points,
bounds,
zoom: viewport.zoom,
options: { radius: 100, maxZoom: 20, minPoints: 9 },
});
//Handles zoom when user clicks on cluster
const clusterZoom = (clusterID, latitude, longitude) => {
mapRef.current?.flyTo({ cener: [longitude, latitude], duration: 2000 });
const expansionZoom = Math.min(
supercluster.getClusterExpansionZoom(clusterID),
20
);
setViewport({
...viewport,
latitude,
longitude,
zoom: expansionZoom,
transitionInterpolator: new FlyToInterpolator({
speed: 2,
}),
transitionDuration: "auto",
});
};
console.log({ clusters });
return (
<div className="searchResults--map-wrapper">
<div className="Map">
<Map
initialViewState={{
...viewport,
}}
mapStyle={process.env.REACT_APP_MAPBOX_STYLE_URL}
mapboxAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN}
onMove={(e) => setViewport(e.viewState)}
onClick={() => setSelectedHotel(null)}
style={{
width: "100%",
height: "100%",
}}
ref={mapRef}
>
{clusters.map((cluster, idx) => {
const hotel = filteredHotels.find(
(el) => el.access_hotel_id === cluster.properties.hotel_id
);
const [longitude, latitude] = cluster.geometry.coordinates;
const { cluster: isCluster, point_count } = cluster.properties;
if (isCluster) {
return (
<Marker
key={cluster.id}
latitude={latitude}
longitude={longitude}
>
<div
className="Map-clusterMarker"
onClick={() => clusterZoom(cluster.id, latitude, longitude)}
>
{point_count} hotels
</div>
</Marker>
);
}
return (
hotel && (
<HotelMarker
key={hotel.access_hotel_id}
hotel={hotel}
selectedHotel={selectedHotel}
setSelectedHotel={setSelectedHotel}
/>
)
);
})}
<div className="Map-fullScreenCtrl">
<FullscreenControl />
</div>
<div className="Map-navigationCtrl">
<NavigationControl showCompass={false} />
</div>
</Map>
</div>
</div>
);
}
export default HotelMap;
```React
I am new to React-mapbox GL. I have tried for a while now to look at the examples but can't figure out how to change the layer's color on enter/hover. I have 2 questions so far.
map.on('mouseenter', 'clusters', () => {
map.getCanvas().style.cursor = 'pointer';
});
How can I define the the cluster element for each function in Reactmapbox gl? I don't quite understand how the interactiveLayerIds works I suppose?
question 2.
const onMouseEnter = useCallback(event =>{
if (event.features[0].layer.id==="unclustered-point"){
/* console.log(event.features[0].layer.paint.'circle-color') */
}
})
I have attempted this so far(the whole code is below) but it tells me that circle-color is a unexpected token. OnEnter this unclustered-point layer I want to change the color of the element so the user can clearly see what element they are hovering over? How would I go about doing this in React mapbox gl if I cant change the circle color?
THE WHOLE CODE:
import React, { useContext, useEffect, useRef,useState,useCallback } from 'react';
import './MapViews.css';
import { useNavigate } from 'react-router-dom';
import ReactMapGL, { Marker, Layer, Source } from 'react-map-gl';
import SourceFinder from '../../Apis/SourceFinder';
import { SourceContext } from '../../context/SourceContext';
import { clusterLayer, clusterCountLayer, unclusteredPointLayer } from './Layers';
const MapView = () => {
const navigate = useNavigate()
const { sources, setSources } = useContext(SourceContext)
const [viewport, setViewport] = React.useState({
longitude: 10.757933,
latitude: 59.91149,
zoom: 12,
bearing: 0,
pitch: 0
});
const mapRef = useRef(null);
function getCursor({isHovering, isDragging}) {
return isDragging ? 'grabbing' : isHovering ? 'pointer' : 'default';
}
useEffect(() => {
const fetchData = async () => {
try {
const response = await SourceFinder.get("/sources");
setSources(response.data.data.plass);
} catch (error) { }
};
fetchData();
}, [])
const onMouseEnter = useCallback(event =>{
if (event.features[0].layer.id==="unclustered-point"){
/* console.log(event.features[0].layer.paint.'circle-color') */
}
})
const ShowMore = event => {
if(event.features[0].layer.id==="unclustered-point"){
const feature = event.features[0];
console.log(feature)
mapRef.current.getMap().getCanvas().style.cursor="pointer"
}else{
const feature = event.features[0];
const clusterId = feature.properties.cluster_id;
const mapboxSource = mapRef.current.getMap().getSource('stasjoner');
mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => {
if (err) {
return;
}
setViewport({
...viewport,
longitude: feature.geometry.coordinates[0],
latitude: feature.geometry.coordinates[1],
zoom,
transitionDuration: 500
});
});
}
};
return (
<ReactMapGL {...viewport} width="100%" height="100%" getCursor={getCursor} onMouseEnter={onMouseEnter} interactiveLayerIds={[clusterLayer.id,unclusteredPointLayer.id]} mapboxApiAccessToken={"SECRET"} clickRadius={2} onViewportChange={setViewport} mapStyle="mapbox://styles/mapbox/streets-v11" onClick={ShowMore} ref={mapRef}>
<Source id="stasjoner" type="geojson" data={sources} cluster={true} clusterMaxZoom={14} clusterRadius={50} >
<Layer {...clusterLayer} />
<Layer {...clusterCountLayer} />
<Layer {...unclusteredPointLayer}/>
</Source>
</ReactMapGL>
);
};
export default MapView;
LAYERS.JS
//Hvergang vi skal ha 2 eller flere baller
export const clusterLayer = {
id: 'clusters',
type: 'circle',
source: 'stasjoner',
filter: ['has', 'point_count'],
paint: {
'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 500, '#f28cb1'],
'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40],
}
};
//Dette er tallene som er inne i ballene
export const clusterCountLayer = {
id: 'cluster-count',
type: 'symbol',
source: 'stasjoner',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
'text-size': 12,
}
};
//Per punkt
export const unclusteredPointLayer = {
id: 'unclustered-point',
type: 'circle',
source: 'stasjoner',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#11b4da',
'circle-radius': 8,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff',
}
};
I need to focus on the marker based on dynamic search results. As far as I understood from other discussions, what I need is to make a use of panTo function.
I gave a try, and it works partially.
Here is the code example and the codesandbox link
Note: Provide your Google Maps key in .env file to test the codesandbox.
Map.js
import { GoogleMap, LoadScript, Marker } from "#react-google-maps/api";
import React, { useEffect } from "react";
const containerStyle = {
width: "100%",
height: "500px"
};
const center = { lat: 45.4211, lng: -75.6903 };
const Maps = ({ filteredPark }) => {
const key = process.env.REACT_APP_GOOGLE_KEY; //test with your key please
if (!key) {
throw new Error("Google token is not set");
}
const mapRef = React.useRef();
const onMapLoad = React.useCallback((map) => {
mapRef.current = map;
}, []);
const panTo = React.useCallback(({ lat, lng }) => {
mapRef?.current?.panTo({ lat, lng });
mapRef?.current?.setZoom(18);
}, []);
useEffect(() => {
filteredPark.map((a) => {
const lat = a.coordinates[1];
const lng = a.coordinates[0];
return panTo({ lat, lng });
});
}, [filteredPark, panTo]);
return (
<div style={{ width: "100%", height: "100%" }}>
<LoadScript googleMapsApiKey={key}>
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
zoom={6}
onLoad={onMapLoad}
>
{filteredPark &&
filteredPark.map((park) => (
<Marker
key={park.PARK_ID}
position={{
lat: park.coordinates[1],
lng: park.coordinates[0]
}}
/>
))}
</GoogleMap>
</LoadScript>
</div>
);
};
export default React.memo(Maps);
custom search hook component
import { useMemo } from "react";
import * as parkData from "./data/skateboard-parks.json";
const useParkFIlter = (DESCRIPT_1) => {
const gridRows = parkData.features.map((park) => park.properties);
const filteredParks = useMemo(() => {
return (
gridRows &&
gridRows.filter((dispo) =>
dispo.DESCRIPT_1.toLowerCase().includes(DESCRIPT_1.toLowerCase())
)
);
}, [DESCRIPT_1, gridRows]);
return [filteredParks];
};
export default useParkFIlter;
Any help will be appreciated.
I have made changes in your code. If it doesn't work, do let me know.
import { GoogleMap, LoadScript, Marker } from "#react-google-maps/api";
import React, { useEffect } from "react";
const containerStyle = {
width: "100%",
height: "500px"
};
const center = { lat: 45.4211, lng: -75.6903 };
const Maps = ({ filteredPark }) => {
const key = process.env.REACT_APP_GOOGLE_KEY; //test with your key please
if (!key) {
throw new Error("Google token is not set");
}
//const mapRef = React.useRef();
const [mapInstance, setMapInstance] = useState(null);
const[zoom, setZoom] = useState(6);
const onMapLoad = React.useCallback((map) => {
//mapRef.current = map;
setMapInstance(map);
}, []);
//const panTo = React.useCallback(({ lat, lng }) => {
//mapRef?.current?.panTo({ lat, lng });
//mapRef?.current?.setZoom(18);
//}, []);
useEffect(()=>{
if(mapInstance){
mapInstance.panTo({ lat, lng });
setZoom(18);
}
},[mapInstance]);
useEffect(() => {
filteredPark.map((a) => {
const lat = a.coordinates[1];
const lng = a.coordinates[0];
return panTo({ lat, lng });
});
}, [filteredPark, panTo]);
return (
<div style={{ width: "100%", height: "100%" }}>
<LoadScript googleMapsApiKey={key}>
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
//zoom={6}
zoom={zoom}
onLoad={onMapLoad}
>
{filteredPark &&
filteredPark.map((park) => (
<Marker
key={park.PARK_ID}
position={{
lat: park.coordinates[1],
lng: park.coordinates[0]
}}
/>
))}
</GoogleMap>
</LoadScript>
</div>
);
};
export default React.memo(Maps);
I have an input (on children component) that return coordinates by props (text):
import React, { useEffect, useState, useRef, useMemo } from 'react'
import { useEnderecoValue } from '../../contexts/EnderecoContext'
import 'leaflet/dist/leaflet.css'
import Leaflet from 'leaflet'
import { MapContainer, Marker, useMap, TileLayer, Popup } from 'react-leaflet'
export default function App(text: any) {
const [lat, setLat] = useState(48.856614)
const [lng, setLng] = useState(2.3522219)
const [state, dispatch] = useEnderecoValue()
const icon = new Leaflet.DivIcon({
className: 'custom-div-icon',
html:
"<div style='background-color:#c30b82;' class='marker-pin'></div><i class='material-icons'><img src='img/marker-icon.png'></i>",
iconSize: [30, 42],
iconAnchor: [15, 42],
popupAnchor: [-3, -42]
})
useEffect(() => {
if (text.text) {
setLat(text.text.features[0].geometry.coordinates[1])
setLng(text.text.features[0].geometry.coordinates[0])
}
}, [text])
function SetViewOnClick({ coords }: any) {
const map = useMap()
map.flyTo(coords, map.getZoom())
return null
}
My Marker is draggable and the popup display address and coords if I search address on input, or if the Marker is dragded:
const markerRef = useRef(null)
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current
if (marker != null) {
const { lat, lng } = marker.getLatLng()
setLat(lat)
setLng(lng)
}
}
}),
[]
)
const popup = () => {
if (text.text) {
return text.text.query + ' ' + `lat: ${lat}, long: ${lng}`
}
return (
"Address by default" +
' ' +
`lat: ${lat}, long: ${lng}`
)
}
return (
<MapContainer
center={[lat, lng]}
attributionControl={false}
zoomControl={false}
zoom={18}
style={{
height: '350px',
position: 'relative',
outline: 'none',
maxWidth: '696px',
display: 'block',
margin: '15px auto',
width: '100%'
}}
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<Marker
position={[lat, lng]}
icon={icon}
draggable={true}
ref={markerRef}
eventHandlers={eventHandlers}
>
<Popup>
<span>{popup()}</span>
</Popup>
<SetViewOnClick coords={[lat, lng]} />
</Marker>
</MapContainer>
)
}
How to place my dispatch function for return coords when I search address and whenthe Marker is dragged ? (just when the value change)
dispatch({
type: 'SET_COORDS',
latitude: lat,
longitude: lng
})
On your Search comp place dispatch inside searchLocation to be able to change lat long.
const searchLocation = async () => {
fetch(
"https://api-adresse.data.gouv.fr/search?" +
new URLSearchParams({
q: state.location,
})
)
.then((data) => data.json())
.then((text) => {
setResp({ text });
dispatch({
type: "SET_COORDS",
latitude: text.features[0].geometry.coordinates[1],
longitude: text.features[0].geometry.coordinates[0],
});
})
.catch(function (error) {
console.log("request failed", error);
});
};
Replace <Map {...resp} /> with <Map text={resp} /> because it causes multiple rerenders and the page becomes unresponsive. Not sure what you were trying to do.
Demo
I am using #react-google-maps/api and loading the script from cdn to my public directory index.html file.
I get InvalidValueError: setMap: not an instance of Map; and not an instance of StreetViewPanorama and the markers do not appear on my map. Weird thing is that it is totally random to get this error. Sometimes it works without any problem. That is what I do not understand in the first place.
import React, { useEffect } from 'react';
import { GoogleMap, Marker } from '#react-google-maps/api';
import mapStyles from './mapStyles';
//google map height 100
import './MapDisplay.css';
import { connect } from 'react-redux';
const mapContainerStyle = {
height: '100%',
width: '100%',
};
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
};
const MapDisplay = ({
userLocation,
mouseHoverIdR,
selectedInfoR,
searchedResults,
unAuthNoSearchResults,
selectedLocationInfoWindowS,
}) => {
const onClickMarker = (e) => {
selectedLocationInfoWindowS({
selectedInfoR: e,
type: 'infoWindowLocations',
});
};
const onClickMap = () => {
selectedLocationInfoWindowS({
type: '',
selectedInfoR: {
_id: '',
},
});
};
return (
<div id='map'>
<GoogleMap
id='map_canvas'
mapContainerStyle={mapContainerStyle}
zoom={12}
center={userLocation}
options={options}
onClick={() => onClickMap()}
>
{!unAuthNoSearchResults &&
searchedResults.map((searchedResult) => {
if (
mouseHoverIdR === searchedResult._id ||
selectedInfoR._id === searchedResult._id
) {
var a = window.google.maps.Animation.BOUNCE;
}
return (
<Marker
position={searchedResult.coordinates}
key={searchedResult._id}
clickable
onClick={() => onClickMarker(searchedResult)}
animation={a}
id={'hello'}
/>
);
})}
</GoogleMap>
</div>
);
};
How can I fix the error that I get when I try to display the marker on the map?