Reactjs =>leaflet-routing-machine waypoint render 2 times - javascript

here I get a bug when trying the leaflet-routing-machine lib.
The bug is that the "Waypoints" section renders 2 times.
why is it able to render 2 times? Can you guys help me? Thank you
My Code in below =>

My Code =
import { useEffect } from "react";
import L from "leaflet";
import "leaflet-routing-machine/dist/leaflet-routing-machine.css";
import "leaflet-routing-machine";
import { useMap } from "react-leaflet";
L.Marker.prototype.options.icon = L.icon({
iconUrl: "https://unpkg.com/leaflet#1.7.1/dist/images/marker-icon.png",
});
export default function Routing() {
const map = useMap();
const routingControl = L.Routing.control({
waypoints: [
L.latLng(-6.3094117, 106.8240261),
L.latLng(-6.2185648, 106.7996082),
],
lineOptions: {
styles: [{ color: "#6FA1EC", weight: 4 }],
},
routeWhileDragging: true,
draggableWaypoints: true,
fitSelectedRoutes: true,
}).addTo(map);
useEffect(() => {
return () => map.removeControl(routingControl);
}, [map, routingControl]);
function createButton(label, container) {
let btn = L.DomUtil.create("button", "", container);
btn.setAttribute("type", "button");
btn.innerHTML = label;
return btn;
}
map.on("click", function (e) {
let container = L.DomUtil.create("div"),
startBtn = createButton("Start from this location", container),
destBtn = createButton("Go to this location", container);
container.setAttribute("class", "leaflet-popup-btn-box");
L.DomEvent.on(startBtn, "click", function () {
routingControl.spliceWaypoints(0, 1, e.latlng);
map.closePopup();
});
L.DomEvent.on(destBtn, "click", function () {
routingControl.spliceWaypoints(
routingControl.getWaypoints().length - 1,
1,
e.latlng
);
map.closePopup();
});
L.popup().setContent(container).setLatLng(e.latlng).openOn(map);
});
return null;
}

Related

'TypeError: t is undefined' and no heatmap color appears on the map

I was copying code from this solution: Is there a way to use leaflet.heat in react? and I'm getting an error.
TypeError: t is undefined
enter image description here
in the currentPosition function
export const currentPosition = atom({
key: "currentPosition",
default: [-2.600, -11.01],
});
in the getLocationCity function
export const getLocationCity = async (params) => {
try {
const body = DataCustom(params);
const result = await instanceArcgis.post(
"/rest/HomeLocation/11/query",
body,
);
return await result?.data;
}catch (error) {
console.log(error);
}
But I tried different options and it still doesn't work. And it outputs the results from the API but does not come out the Heatmap color on the map which instead comes out only the words "Marker".
enter image description here
Full JS code:
import React, { useEffect, useState, useRef } from "react";
import { GeoJSON } from "react-leaflet";
import { useRecoilValue, useSetRecoilState } from "recoil";
import HeatmapOverlay from "leaflet-heatmap";
import { arcgisToken } from "../recoil";
import { mapBounds, loadingMap } from "../state";
import { getLocationCity } from "../data/arcgis";
import "leaflet.heat";
export default function HSales() {
const [data, setData] = useState();
const setIsLoading = useSetRecoilState(loadingMap);
const bounds = useRecoilValue(mapBounds);
const tokenArcgis = useRecoilValue(arcgisToken);
const geoJsonLayer = useRef(null);
useEffect(() => {
const fetchDataSpeedtestKel = () => {
const body = {
returnGeometry: true,
rollbackOnFailure: true,
geometry: JSON.stringify({
xmin: bounds.west,
ymin: bounds.south,
xmax: bounds.east,
ymax: bounds.north,
spatialReference: { wkid: 4326 },
}),
geometryType: "esriGeometryEnvelope",
token: tokenArcgis,
};
setIsLoading(true);
getLocationCity(body)
.then((response) => {
let array = [];
let points = [];
response.features.forEach((element) => {
points.push([
element["geometry"]["x"],
element["geometry"]["y"]
])
array.push({
type: "Feature",
properties: element["attributes"],
geometry: {
type: "Point",
coordinates: [
element["geometry"]["x"],
element["geometry"]["y"],
],
},
});
});
const FeatureCollection = {
type: "FeatureCollection",
features: array,
};
if (geoJsonLayer.current) {
geoJsonLayer.current.clearLayers().addData(FeatureCollection);
}
// setPosition(FeatureCollection);
setData(FeatureCollection);
L.heatLayer(points).addTo(data);
})
.catch((err) => console.log(err))
.finally(() => setIsLoading(false))
};
fetchDataSpeedtestKel();
}, [bounds, setIsLoading, tokenArcgis, position]);
if (data) {
return (
<>
<GeoJSON
ref={geoJsonLayer}
data={data}
/>
</>
);
}
}
Thank you so much!

Why is the Heatmap Leaflet in Map not showing up?

SITUATION IMAGE:
SITUATION:
Not showing heatMap in my Map not showing properly.
What have I done wrong ?
Why doesn't the Heatmap Leaflet appear on the Map?
And part heatmapLayer.setData(FeatureCollection); if removed heatmapLayer so setData(FeatureCollection); it pops up but instead appears a marker on the map like this:
CODE:
import React, { useEffect, useState, useRef } from "react";
import { GeoJSON } from "react-leaflet";
import { useRecoilValue, useSetRecoilState } from "recoil";
import HeatmapOverlay from "leaflet-heatmap";
import { arcgisToken } from "../recoil";
import { mapBounds, loadingMap } from "../state";
import { getLocationCity } from "../data/arcgis";
import "leaflet.heat";
export default function HSales() {
const [data, setData] = useState();
const setIsLoading = useSetRecoilState(loadingMap);
const bounds = useRecoilValue(mapBounds);
const tokenArcgis = useRecoilValue(arcgisToken);
const geoJsonLayer = useRef(null);
useEffect(() => {
const fetchDataSpeedtestKel = () => {
const body = {
returnGeometry: true,
rollbackOnFailure: true,
geometry: JSON.stringify({
xmin: bounds.west,
ymin: bounds.south,
xmax: bounds.east,
ymax: bounds.north,
spatialReference: { wkid: 4326 },
}),
geometryType: "esriGeometryEnvelope",
token: tokenArcgis,
};
setIsLoading(true);
getLocationCity(body)
.then((response) => {
const array = [];
response.features.forEach((element) => {
array.push({
type: "Feature",
properties: element["attributes"],
geometry: {
type: "Point",
coordinates: [
element["geometry"]["x"],
element["geometry"]["y"],
],
},
});
});
const FeatureCollection = {
type: "FeatureCollection",
features: array,
};
if (geoJsonLayer.current) {
geoJsonLayer.current.clearLayers().addData(FeatureCollection);
}
var cfg = {
radius: 0.1,
maxOpacity: 1,
scaleRadius: true,
useLocalExtrema: true,
latField: "x",
lngField: "y",
};
var heatmapLayer = new HeatmapOverlay(cfg);
setData(FeatureCollection);
// setData(FeatureCollection);
})
.catch((err) => console.log(err))
.finally(() => setIsLoading(false));
};
fetchDataSpeedtestKel();
}, [bounds, setIsLoading, tokenArcgis]);
if (data) {
return (
<>
<GeoJSON
ref={geoJsonLayer}
data={data}
/>
</>
);
}
}
your response from getLocationCity in the example:
export const getLocationCity = async (params) => {
try {
const body = DataCustom(params);
const result = await instanceArcgis.post(
"/rest/HomeLocation/4/query",
body,
);
return await result?.data;
}
Earlier I read and sample coding via the link:
https://www.patrick-wied.at/static/heatmapjs/example-heatmap-leaflet.html
https://codesandbox.io/s/suspicious-chandrasekhar-sybk67?file=/src/index.js

Fit google maps bounds using vue3-google-map

I am trying to build a Vue component that takes in input an array of gps (lat/lng) data and which have to be drawn on a map using a google maps polyline. It is working until this point. The problem rises when I try to use map.fitbounds in order to manage the zoom and center out the map to that polyline. As per vue3-google-map documentation I tried to create a ref to the map object with ref="mapRef". Unfortunately in the mounted() hook it seems that I can'f find that object, inside Vue DevTools it will later show up.
<template>
<GoogleMap ref="mapRef" api-key="<myapikey>" style="width: 100%; height: 500px" :center="center" :zoom="15">
<Polyline :options="path" />
</GoogleMap>
</template>
<script>
import {
defineComponent,
ref
} from 'vue'
import {
GoogleMap,
Polyline
} from 'vue3-google-map'
export default defineComponent({
components: {
GoogleMap,
Polyline
},
setup() {
const mapRef = ref(null)
return {
mapRef
}
},
methods: {
managebounds(path) {
this.intervalid = setInterval(function () {
if (!this.bounds && !this.loaded) {
if (window.google && window.google.maps && path) {
this.bounds = new window.google.maps.LatLngBounds()
this.loaded = true
clearInterval(this.intervalid)
console.log("AFTER CLEAR")
this.bounds.extend(path.path[0]) //i take the first and last point of the polyline
this.bounds.extend(path.path[path.path.length - 1])
var extendBy = 0.001;
console.log("EXTEND1")
var point1 = new window.google.maps.LatLng(
this.bounds.getNorthEast().lat() + extendBy,
this.bounds.getNorthEast().lng() + extendBy
)
var point2 = new window.google.maps.LatLng(
this.bounds.getSouthWest().lat() - extendBy,
this.bounds.getSouthWest().lng() - extendBy
)
this.bounds.extend(point1);
console.log("EXTEND2")
this.bounds.extend(point2);
console.log("FITTING BOUNDS")
this.intervalid = setInterval(function () {
if (this.$refs.mapRef.value?.ready) {
console.log("LOADED")
this.$refs.mapRef.value.map.fitBounds(this.bounds); //here mapRef is undefined
clearInterval(this.intervalid)
} else {
console.log("NOT LOADED")
}
}, 1000)
} else {
console.log("OUT")
}
}
}, 500)
}
},
mounted() {
this.$nextTick(function () {
this.managebounds(this.path)
})
},
data() {
return {
path: {
path: this.gpspath.path,
strokeColor: this.gpspath.color
},
bounds: null,
loaded: false,
intervalid: -1
}
},
props: {
"gpspath": {
type: [],
default: [{}]
}
}
})
</script>
Any hint about fixing this issue?
I had to read again the Vue documentation about computed properties and figured out is was a bad idea trying to build a Method.
I made a function inside the setup() hook and called that from my computed data.
The working result is in the following code:
setup() {
const mapRef = ref(null)
function centermap(start, end){
if (mapRef.value?.ready) {
const gmap = mapRef.value.map;
const api = mapRef.value.api;
this.bounds = new api.LatLngBounds();
this.bounds.extend(start);
this.bounds.extend(end);
gmap.fitBounds(this.bounds);
}else{
console.log("NOT READY")
}
}
return {
mapRef, centermap
}
},
computed: {
path() {
if(this.gpspath){
let filteredpath = this.gpspath.path.filter(x=>Math.abs(x.lat)>1)
if(filteredpath && filteredpath.length>1)
this.centermap(filteredpath[0], filteredpath[filteredpath.length-1])
return {
path: filteredpath,
strokeColor: this.gpspath.color
}
}else{
return {
path: [],
strokeColor: "#ffffff"
}
}
}
}
I also faced this problem; here is my solution:
Page with map:
import { mapFitBounds } from "#/composables/mapFitBounds";
const mapRef = ref(null);
watch(
() => mapRef.value?.ready,
(ready) => {
if (!ready) return;
mapFitBounds(mapRef, markersArray);
}
);
composables/mapFitBounds.js:
export function mapFitBounds(mapRef, markers) {
let bounds;
const api = mapRef.value.api;
const map = mapRef.value.map;
bounds = new api.LatLngBounds();
for (let i = 0; i < markers.length; i++) {
bounds.extend(markers[i]);
}
map.fitBounds(bounds);
}

Unable to smoothly animate the icons on map when coordinates changes using mapbox-gl

this is my react map component:
import 'mapbox-gl/dist/mapbox-gl.css';
import './switcher/switcher.css';
import mapboxgl from 'mapbox-gl';
import React, { useRef, useLayoutEffect, useEffect, useState } from 'react';
import { deviceCategories } from '../common/deviceCategories';
import { loadIcon, loadImage } from './mapUtil';
import { styleCarto} from './mapStyles';
import { useAttributePreference } from '../common/preferences';
const element = document.createElement('div');
element.style.width = '100%';
element.style.height = '100%';
export const map = new mapboxgl.Map({
container: element,
style: styleCarto(),
center: [80.379370, 23.846870],
zoom: 4.8
});
let ready = false;
const readyListeners = new Set();
const addReadyListener = listener => {
readyListeners.add(listener);
listener(ready);
};
const removeReadyListener = listener => {
readyListeners.delete(listener);
};
const updateReadyValue = value => {
ready = value;
readyListeners.forEach(listener => listener(value));
};
const initMap = async () => {
const background = await loadImage('images/background.svg');
await Promise.all(deviceCategories.map(async category => {
if (!map.hasImage(category)) {
const imageData = await loadIcon(category, background, `images/icon/car.png`);
map.addImage(category, imageData, { pixelRatio: window.devicePixelRatio });
}
}));
updateReadyValue(true);
};
map.on('load', initMap);
map.addControl(new mapboxgl.NavigationControl({
showCompass: false,
}));
const Map = ({ children }) => {
const containerEl = useRef(null);
const [mapReady, setMapReady] = useState(false);
const mapboxAccessToken = useAttributePreference('mapboxAccessToken');
useEffect(() => {
mapboxgl.accessToken = mapboxAccessToken;
}, [mapboxAccessToken]);
useEffect(() => {
const listener = ready => setMapReady(ready);
addReadyListener(listener);
return () => {
removeReadyListener(listener);
};
}, []);
useLayoutEffect(() => {
const currentEl = containerEl.current;
currentEl.appendChild(element);
if (map) {
map.resize();
}
return () => {
currentEl.removeChild(element);
};
}, [containerEl]);
return (
<div style={{ width: '100%', height: '100%' }} ref={containerEl}>
{mapReady && children}
</div>
);
};
export default Map;
I am fetching coordinates from api endpoint using socket controller, there is redux store that handle the changes in the data, however the position of the icons changes but its not smooth ,i have been trying to make it done since 5 days but i dont find any way how to do it, i am not finding mapbox documentation helpful
Below is the position map component , here the positions are being refreshed and updated to new cordinates, but i want to animate the changes on screen like movement of car on uber/ola app.
import React, { useCallback, useEffect } from 'react';
import ReactDOM from 'react-dom';
import mapboxgl from 'mapbox-gl';
import { Provider, useSelector } from 'react-redux';
import { map } from './Map';
import store from '../store';
import { useHistory } from 'react-router-dom';
import StatusView from './StatusView';
const PositionsMap = ({ positions }) => {
const id = 'positions';
const history = useHistory();
const devices = useSelector(state => state.devices.items);
const createFeature = (devices, position) => {
const device = devices[position.deviceId] || null;
return {
deviceId: position.deviceId,
name: device ? device.name : '',
category: device && (device.category || 'default'),
}
};
const onMouseEnter = () => map.getCanvas().style.cursor = 'pointer';
const onMouseLeave = () => map.getCanvas().style.cursor = '';
const onClickCallback = useCallback(event => {
const feature = event.features[0];
let coordinates = feature.geometry.coordinates.slice();
while (Math.abs(event.lngLat.lng - coordinates[0]) > 180) {
coordinates[0] += event.lngLat.lng > coordinates[0] ? 360 : -360;
}
const placeholder = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<StatusView deviceId={feature.properties.deviceId} onShowDetails={positionId => history.push(`/position/${positionId}`)} />
</Provider>,
placeholder
);
new mapboxgl.Popup({
offset: 20,
anchor: 'bottom-left',
closeButton: false,
className: 'popup'
})
.setDOMContent(placeholder)
.setLngLat(coordinates)
.addTo(map);
}, [history]);
useEffect(() => {
map.addSource(id, {
'type': 'geojson',
'data': {
type: 'FeatureCollection',
features: [],
}
});
map.addLayer({
'id': id,
'type': 'symbol',
'source': id,
'layout': {
'icon-image': '{category}',
'icon-allow-overlap': true,
'text-field': '{name}',
'text-allow-overlap': true,
'text-anchor': 'bottom',
'text-offset': [0, -2],
'text-font': ['Roboto Regular'],
'text-size': 12,
}
});
map.on('mouseenter', id, onMouseEnter);
map.on('mouseleave', id, onMouseLeave);
map.on('click', id, onClickCallback);
return () => {
Array.from(map.getContainer().getElementsByClassName('mapboxgl-popup')).forEach(el => el.remove());
map.off('mouseenter', id, onMouseEnter);
map.off('mouseleave', id, onMouseLeave);
map.off('click', id, onClickCallback);
map.removeLayer(id);
map.removeSource(id);
};
}, [onClickCallback]);
useEffect(() => {
map.getSource(id).setData({
type: 'FeatureCollection',
features: positions.map(position => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [position.longitude, position.latitude]
},
properties: createFeature(devices, position),
}))
});
}, [devices, positions]);
return null;
}
export default PositionsMap;
can any body help on thin to figure what i have been missing

Adding functions from another file and updating parent's state in react

I've got App.js which has a state called layers and I want to update the layers state from layers class.
Below is my layers class which doesn't return anything.
What is the best practise to do this in react js?
const layers = createReactClass({
getInitialState() {
return {
layers: [],
}
},
addTileLayer: function () {
esriPromise([ 'esri/layers/TileLayer']).then(([TileLayer]) => {
let tileLayer = new TileLayer(
{
id: 'XX',
opacity: '0.55',
visible: false
});
this.setState({
layers: [...this.state.layers, tileLayer]
})
}).catch((err) => console.error(err));
},
});
export default layers;
Please note I've added the app.js below. Right now addTileLayer function resides inside the app.js
let createReactClass = require('create-react-class');
const App = createReactClass({
getInitialState() {
return {
status: 'loading',
extent: {},
layers: [],
searchWidget: null,
map: null,
view: null,
}
this.handleFail = this.handleFail.bind(this);
},
componentWillMount(){
this.addTileLayer();
this.addFeatureLayer();
},
addTileLayer: function () {
esriPromise([ 'esri/layers/TileLayer']).then(([TileLayer]) => {
let tileLayer = new TileLayer(
{
id: 'xxx',
opacity: '0.55',
visible: false
});
this.setState({
layers: [...this.state.layers, tileLayer]
})
}).catch((err) => console.error(err));
},
render(){
const maxZoom = 15;
const minZoom = 4;
return(
<div id = 'container'>
<div id="searchWidget"></div>
<div id = 'main-content'>
<Map
class = 'full-screen-map'
mapProperties = {
{
basemap: 'dark-gray',
showLabels : true,
logo: false,
sliderPosition: 'bottom-left',
layers: this.state.layers,
}
}
viewProperties = {
{
extent: this.state.extent,
zoom: 12,
minZoom: minZoom,
maxZoom: maxZoom,
ui: {
components: [this.state.searchWidget, 'compass']
}
}
}
onFail={this.handleFail}
onLoad={this.handleMapLoad}
>
</Map>
</div>
</div>
)
}
I think a clean way to do this is to maybe have a callback function passed into layers.addTileLayer
layers.addTileLayer((layer) => this.setState({layer:layer}))
and your new layers class could be
const layers = createReactClass({
addTileLayer: function (updateLayers) {
esriPromise([ 'esri/layers/TileLayer']).then(([TileLayer]) => {
let tileLayer = new TileLayer(
{
id: 'XX',
opacity: '0.55',
visible: false
});
updateLayers(tileLayer) //this is new
}).catch((err) => console.error(err));
},
});
export default layers;
this way you don't need to keep track of the state in two places! and still get to encapsulate this functionality in another class :)

Categories