Mapbox React queryTerrainElevation returns null - reactjs

I am attempting to get the elevation of certain coordinates on the map using mapbox.
Based on the documentation, i can use queryTerrainElevation.
Sample Code:
map.on("click", (data) => {
console.log(data);
const elevation = map?.queryTerrainElevation(data.lngLat, {
exaggerated: true,
});
console.log("Elevation: " + elevation)
});
Console logs:
Using the mapbox tilequery with the same coordinates:
https://api.mapbox.com/v4/mapbox.mapbox-terrain-v2/tilequery/95.9345,41.2565.json?access_token=<mapbox_token>
There is an elevation value in the response:

You must be adding the custom layer after just loading the style. So, the terrain value isn't updated at that time. That's why you get null.
Do it like this and it should work.
map.on('idle', () => {
const elevation = map.queryTerrainElevation(coordinates, {exaggerated: false});
})
This runs the layer code after the map is loaded. Instead of just the style.

Related

Next.js: What is the best way to solve the warning about different server & client styles based on react-spring?

I'm working with Next.js and using a react-spring library to get an animation for a bottomsheet component. It works, however there is a warning appears:
Warning: Prop style did not match. Server: "transform:translate3d(0,Infinitypx,0)" Client: "transform:translate3d(0,652px,0)"
I've carefully investigated this warning and know that it's about incorrect rendering of the HTML element on the server and on the client side. It's clear that on the server side there is no viewport height and thus react-spring can't calculate normally the final value and Next.js registers it as an one value with Infinity and then blames on the client side when the value is calculated correctly due to available viewport height.
I'm wondering what is the best way to rid of this error?
Unfortunatelly I can't catch the react-spring calculation stage and to set a correct value.Tere is no API to do it and basically I just don't know the user's viewport height.
I've thinking about the using indexOf for the value and check if the Infinity presented and replace it for ex: by 0
however it still doesn't solve a problem as the final value will be different anyway.
Maybe someone has an idea or some link to docs etc. where I could find a solution for that?
Basically it's just a warning but I'd like to fix it anyway.
Here is the example code:
import { a, config, useSpring } from '#react-spring/web';
export function BottomSheet({propsHeight}) {
const finalHeight = propsHeight || height - 62;
const display = y.to((py) => (py < finalHeight ? 'flex' : 'none'));
const [{ y }, api] = useSpring(() => ({ y: finalHeight }));
const open = (dragEvent?: any) => {
const canceled = dragEvent?.canceled;
// when cancel is true, it means that the user passed the upwards threshold
// so need to change the spring config to create a nice wobbly effect
api.start({
y: 0,
immediate: false,
config: canceled ? config.wobbly : config.stiff,
});
};
const close = (velocity = 0) => {
api.start({
y: finalHeight,
immediate: false,
onResolve() {
if (onClose) {
onClose();
}
},
config: { ...config.stiff, velocity },
});
};
useEffect(() => {
// provide open/close actions to parent control
getActions(open, close);
}, []);
// pseudo hmtl. Removed all other markup to simplify things
return (<a.div
style={{
y, // Here is the problem of the server & client incorrect values
}}
/>)
}
I highly appreciate any help!
Kind Regards
The only one solution I've found so far for this use case it's rendering the component on client side only and, basically, it makes sense because this code is based on the browser API. I don't see any other possible solutions
To achieve it you can use dymanic imports and Next.js supports them well: here is the docs
You should disable the SSR in the import options and the component will be rendered on the client side only.
Like this:
import dynamic from 'next/dynamic';
const BottomSheetDynamic = dynamic(
() =>
import('#mylibrary/components/bottomsheet/BottomSheet').then(
//this component doesn't have default import so should do it in this way
(mod) => mod.BottomSheetexport
),
{
ssr: false,
}
);

Material UI - Google Maps Autocomplete - Restrict to city and state?

I'm using the Google Maps Places Autocomplete that comes with Material UI and I'm stuck on trying restrict the results. When a user starts typing the only suggestions I want returned are City, State for them to select.
Here is a link to MUI's documentation with example:
Material UI - Google Places Autocomplete
Thanks!
It looks like the code from the library is using the AutocompleteService.getPlacePredictions. To achieve your use case you need to include the types properties with value of ['(cities)'] to your AutocompleteService.getPlacePredictions request. This will instructs the Places service to return results that match locality or administrative_area_level_3 as stated here
You can add it inside the fetch in the code sample just like below:
fetch({ input: inputValue, types: ['(cities)'] }, (results) => {
if (active) {
let newOptions = [];
if (value) {
newOptions = [value];
}
if (results) {
newOptions = [...newOptions, ...results];
}
setOptions(newOptions);
}
});
Here's the sample code. Make sure to use your API key for the sample to work.

ReactJS - Use this inside a Listener from Google Map

I use Google Map in pure JS (to use easily Clusters & Drawings tools).
I just face this problem, I want to use my state inside a listener :
_handleGoogleMapApi = async (map, google) => {
// ......
google.event.addListener(drawingManager, 'overlaycomplete', function(e) {
if (e.type !== google.drawing.OverlayType.MARKER) {
console.log(this);
const shapesCount = this.state.shapes.length;
this.state.shapes.push({shapesCount: e})
}
}, this);
// ......
And I have this error :
TypeError: Cannot read property 'shapes' of undefined
I've added the :
}, this);
At the end, but same result. I checked, the this is related to Google Map, not my react component.
How can I access the state inside this listener ?
To use the state inside your google maps event listener, you need to use the ES6 arrow function. It should look like this:
google.maps.event.addListener(drawingManager, 'overlaycomplete', (e) => {
if (event.type !== 'marker') {
console.log(this.state.shapes.length)
}
}
Here's a sample code.

How to set value of React Material UI Slider via Cypress?

I'm using slider from here: https://material-ui.com/components/slider/
Trying to set it's value like:
cy.get("[data-cy=slider-population]")
.as("range")
.invoke("val", val)
.trigger("change");
However it's not moving. Anyone manage to get this working?
To make it a bit simpler for future viewers, #kevin's answer points you to all the right places to extract a working test, however here's everything without their demo code:
<Slider data-cy="slider-population" />
First, you need to add this command:
Cypress.Commands.add("reactComponent", {
prevSubject: "element"
}, ($el) => {
if ($el.length !== 1) {
throw new Error(`cy.component() requires element of length 1 but got ${$el.length}`);
}
// Query for key starting with __reactInternalInstance$ for React v16.x
const key = Object.keys($el.get(0)).find((key) => key.startsWith("__reactFiber$"));
const domFiber = $el.prop(key);
Cypress.log({
name: "component",
consoleProps() {
return {
component: domFiber,
};
},
});
return domFiber.return;
});
Then in your test if you want to set the minimum to 0, and the max to 15:
cy.get('[data-cy="slider-population"]')
.reactComponent()
.its("memoizedProps")
.invoke("onChange", null, [0, 15]);
There is an example in the Cypress Real World App, a payment application to demonstrate real-world usage of Cypress testing methods, patterns, and workflows which demonstrates this for it's purposes.
cy.setTransactionAmountRange is a command built to interact with the Material UI Slider.
It uses another Cypress custom command, cy.reactComponent to gain access to the component internals and allow methods like onChange to be invoked directly on the component instance.
Here's a spartan riff on the solution provided by Shannon Hochkins.
Usage:
cy.get("[data-cy=slider-population]")
.setSlider([0,15])
Command:
Cypress.Commands.add('setSlider', { prevSubject: 'element' },
(subject, value) => {
const key = Object.keys(subject.get(0))
.find((key) =>
key.startsWith("__reactFiber$"))
const fiberNode = subject.prop(key)
fiberNode.return.memoizedProps.onChange(null, value)
return subject
})
Typescript:
declare namespace Cypress {
interface Chainable {
setSlider(value: number | number[]): Chainable<void>
}
}

Google maps draw route from given coordinates

I am using google-maps-react module and everything until the last stretch is fine. Using this package, I am able to click on map, set multiple markers and define my route "visually". I did not think that Polygon would not take actual streets and map geometry into consideration (stupid me). Could someone help me out and suggest on how to provide properly drawn routes on map, instead of straight lines connecting marker X to marker Y? This is what I have so far visually:
And this is the coordinates array that I am forming in my application and drawing polygon by:
I am using Google maps api and google-maps-react package.
As was correctly mentioned in comment, Directions API needs to be utilized for that purpose:
Directions are displayed as a polyline drawing the route on a map, or
additionally as a series of textual description within a element
(for example, "Turn right onto the Williamsburg Bridge ramp")
The following example demonstrates how to integrate Google Maps API Directions Service into google-maps-react to display a route.
It is assumed data prop contains coordinates represented in format
specified in provided question. Directions Service code has been adapted
from this example
Example
class MapContainer extends React.Component {
constructor(props) {
super(props);
this.handleMapReady = this.handleMapReady.bind(this);
}
handleMapReady(mapProps, map) {
this.calculateAndDisplayRoute(map);
}
calculateAndDisplayRoute(map) {
const directionsService = new google.maps.DirectionsService();
const directionsDisplay = new google.maps.DirectionsRenderer();
directionsDisplay.setMap(map);
const waypoints = this.props.data.map(item =>{
return{
location: {lat: item.lat, lng:item.lng},
stopover: true
}
})
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
directionsService.route({
origin: origin,
destination: destination,
waypoints: waypoints,
travelMode: 'DRIVING'
}, (response, status) => {
if (status === 'OK') {
directionsDisplay.setDirections(response);
} else {
window.alert('Directions request failed due to ' + status);
}
});
}
render() {
return (
<div className="map-container">
<Map
google={this.props.google}
className={"map"}
zoom={this.props.zoom}
initialCenter={this.props.center}
onReady={this.handleMapReady}
/>
</div>
);
}
}
Demo
You might want to check out this page
https://developers.google.com/maps/documentation/roads/snap
The response from that API call with the data you've already gotten from the lines drawn should give you the JSON data you need to map your path to the roads. This might not be the most elegant solution seeing as you might need to add a button or something to calculate the route to roads or something similar.
Alternatively you might be able to send out the API call after you have two points and have the map update after every line segment is placed. That would require a lot of API calls though. Hope that helps!

Resources