Hey everyone so I'm learning React Native, using Expo VS code, and android studio
I was trying to get a location image with a pin using google maps.
But I cant see any map to pick my location at google maps in android studio.
Also, I cant see any marker after picking a location in iphone expo go google maps image.
What should I do?
Below is my code about getting image of map
export function getMapPreview(lat, lng) {
const imagePreviewUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lng}&zoom=13&size=400x200&maptype=roadmap&markers=color:red%7Clabel:S%7C${lat},${lng}
&key=${GOOGLE_API_KEY}`;
return imagePreviewUrl;
}
Map screen
import { useCallback, useLayoutEffect, useState } from "react";
import { StyleSheet, Alert } from "react-native";
import MapView, { Marker } from "react-native-maps";
import IconButton from "../components/UI/IconButton";
export default function Map({navigation}) {
const [selectedLocation, setSelectedLocation] = useState();
const region = {
latitude: 37.56,
longitude: 126.97,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}
function selectLocationHandler(event) {
console.log(event);
const lat = event.nativeEvent.coordinate.latitude;
const lng = event.nativeEvent.coordinate.longitude;
setSelectedLocation({lat: lat, lng: lng})
}
const savePickedLocationHandler = useCallback(() => {
if (!selectedLocation) {
Alert.alert(
"No location picked",
'You have to pick a loacation (by tapping on the map) first!'
)
return;
}
navigation.navigate('AddPlace', { pickedLat: selectedLocation.lat, pickedLng: selectedLocation.lng })
}, [navigation, selectedLocation])
useLayoutEffect(() => {
navigation.setOptions({
headerRight: ({tintColor}) => (
<IconButton icon="save" size={24} color={tintColor} onPress={savePickedLocationHandler} />
)
})
}, [navigation, savePickedLocationHandler])
return (
<MapView style={styles.map} initialRegion={region} onPress={selectLocationHandler}>
{selectedLocation &&
<Marker
title="Picked Location"
coordinate={{latitude: selectedLocation.lat,longitude: selectedLocation.lng}}
/>
}
</MapView>
)
}
const styles = StyleSheet.create({
map: {
flex: 1
}
})
I reviewed my code several times, but I cant find why
Try use width and height to map styles
You need to define the width and height of the map.
const styles = StyleSheet.create({
map: {
width: MAP_WIDTH_VALUE,
height:MAP_HEIGHT_VALUE
}
})
Related
currently, I'm displaying a single copy of the map. while panning at the edge of the graph or zooming out further from the makers changing their positions. I want to have the markers stay in their position even when these operations are done how can I achieve that.
codesanbox - https://codesandbox.io/s/mapbox-marker-issue-03cdc
index.js
import "mapbox-gl/dist/mapbox-gl.css";
import "react-map-gl-geocoder/dist/mapbox-gl-geocoder.css";
import React, { useState, useRef, useCallback } from "react";
import { render } from "react-dom";
import MapGL from "react-map-gl";
import Pins from "./pins";
import CITIES from "./cities.json";
const MAPBOX_TOKEN =
"pk.eyJ1Ijoic21peWFrYXdhIiwiYSI6ImNqcGM0d3U4bTB6dWwzcW04ZHRsbHl0ZWoifQ.X9cvdajtPbs9JDMG-CMDsA";
const App = () => {
const [viewport, setViewport] = useState({
latitude: 37.7577,
longitude: -122.4376,
zoom: 8
});
const mapRef = useRef();
const handleViewportChange = useCallback(
(newViewport) => setViewport(newViewport),
[]
);
return (
<div style={{ height: "100vh" }}>
<MapGL
ref={mapRef}
{...viewport}
width="90vw"
height="90vh"
onViewportChange={handleViewportChange}
mapboxApiAccessToken={MAPBOX_TOKEN}
mapOptions={{
renderWorldCopies: false,
maxBounds: [
[-180, -90], // Southwest coordinates
[180, 90] // Northeast coordinates
]
}}
>
<Pins data={CITIES} onClick={() => {}} />
</MapGL>
</div>
);
};
render(<App />, document.getElementById("root"));
pins.js
import * as React from "react";
import { Marker } from "react-map-gl";
const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3
c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9
C20.1,15.8,20.2,15.8,20.2,15.7z`;
const SIZE = 20;
// Important for perf: the markers never change, avoid rerender when the map viewport changes
function Pins(props) {
const { data, onClick } = props;
return data.map((city, index) => (
<Marker
key={`marker-${index}`}
longitude={city.longitude}
latitude={city.latitude}
>
<svg
height={SIZE}
viewBox="0 0 24 24"
style={{
cursor: "pointer",
fill: "#d00",
stroke: "none",
transform: `translate(${-SIZE / 2}px,${-SIZE}px)`
}}
>
<path d={ICON} />
</svg>
</Marker>
));
}
export default React.memo(Pins);
data - https://github.com/visgl/react-map-gl/raw/master/examples/.data/cities.json
You have run into a limitation with react-map-gl: https://github.com/visgl/react-map-gl/issues/786
you cannot use maxBounds because react-map-gl is not using mapbox-gl-js's built-in interaction handling logic. To constrain the map bounds you should do something like
_onViewportChange = (viewport) => {
const oldViewport = this.state.viewport;
if (Math.abs(oldViewport.longitude - viewport.longitude) > 180) {
// wrapped around the 180th meridian
return;
}
this.setState({viewport});
}
As a workaround, you can remove the settings maxBounds and renderWorldCopies. Or follow the advice in that thread.
The Google Maps API library I am using for this is https://github.com/JustFly1984/react-google-maps-api
What I have been asked to create is a map with multiple markers, each of which can be clicked on to open an InfoWindow centred on that marker. Much like the AirBnB search page, with the map on one side results on the other.
However, I seem to only be able to have an InfoWindow that is open at the loading of the page and then can't be opened again.
My research so far has generally shown that I would need to completely redo this component as a class instead. Although I don't really understand the class based model of components, so ideally I wouldn't have to mess around with that.
Is there a way to make the desired effect without reworking my program entirely?
This is the code for the component, in case it is relevant
import styles from '../../styles/Home.module.css';
import { Marker, LoadScriptNext, GoogleMap, InfoWindow } from '#react-google-maps/api';
import { Fragment, React } from 'react';
import { Typography } from '#material-ui/core';
export default function MapPanel(props) {
return (
<Fragment>
<LoadScriptNext id='google-map-example' googleMapsApiKey="MYAPIKEY">
<GoogleMap mapContainerStyle={{ width: "100%", height: "800px" }}
id='google-map'
center={props.centre}
heading="0"
zoom={12}>
{addMarkers(props.markerList)}
</GoogleMap>
</LoadScriptNext>
</Fragment>
);
}
function addMarkers(markerList) {
if (markerList != undefined) {
return (markerList.map((marker, index) => makeMarker(marker, index)))
}
}
function makeMarker(marker, index) {
const handleClick = () => {
console.log(`${marker.title} marker clicked`);
}
return (
<Marker id={index} position={marker.latLong} title={marker.title} onClick={handleClick}>
<InfoWindow id='Test' position={marker.latLong}>
<Typography variant='body2'>{marker.text}</Typography>
</InfoWindow>
</Marker>
);
}
To achieve your goal you would want to use states in your application. But since you want to deal with it using Function components, you would need to make use of react hooks to use states.
Here is a working sample for your reference: https://stackblitz.com/edit/marker-with-infowindow-react-api-o6357y
import ReactDOM from "react-dom";
import React from "react";
import {
GoogleMap,
Marker,
InfoWindow,
LoadScript
} from "#react-google-maps/api";
import data from "./data.json";
const { useState } = React;
const containerStyle = {
width: "400px",
height: "400px"
};
const center = { lat: 40.712775, lng: -74.005973 };
function Map() {
const [infoWindowID, setInfoWindowID] = useState("");
let markers;
if (data !== null) {
markers = data.map((location, i) => {
const marker = { lat: location.lat, lng: location.lng };
const index = i + 1;
return (
<Marker
key={index}
position={marker}
label={index.toString()}
onClick={() => {
setInfoWindowID(index);
}}
>
{infoWindowID === index && (
<InfoWindow>
<span>Something {index}</span>
</InfoWindow>
)}
</Marker>
);
});
}
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={10}>
{markers}
</GoogleMap>
</LoadScript>
);
}
export default React.memo(Map);
I have been working with Google Maps & Places API and have successfully got it working using react hooks to allow the user to drop markers, onClick info window, auto-complete place search and Geolocation.
However, what I need is that onClick it will only add a single marker which will change location every time the user clicks a new coord. Not add multiple markers on every click as it currently does.
Code below:
const [markers, setMarkers] = useState([]);
const [selected, setSelected] = useState(null);
const onMapClick = useCallback((event) => {
setMarkers((current) => [
...current,
{
lat: event.latLng.lat(),
lng: event.latLng.lng(),
time: new Date(),
},
]);
}, []);
...
return (
<GoogleMap
mapContainerStyle={mapContainerStyle}
zoom={13}
center={center}
onClick={onMapClick}
onLoad={onMapLoad}
>
{markers.map((marker) => (
<Marker
key={marker.time.toISOString()}
position={{ lat: marker.lat, lng: marker.lng }}
icon={{
url: "/crane-pin.svg",
scaledSize: new window.google.maps.Size(40, 40),
origin: new window.google.maps.Point(0, 0),
anchor: new window.google.maps.Point(15, 15),
}}
onClick={() => {
setSelected(marker);
}}
/>
))}
...
</GoogleMap>
any help would be appreciated!
To achieve your use case, you need to have access to the marker object and use the setPosition method to change the position of the marker to the clicked coordinates in the map.
Are you using any google maps react library? Here is a sample reactjs code that implements this without using any react libraries. Code snippet below:
import React, { Component } from "react";
import { render } from "react-dom";
import Map from "./Map";
import "./style.css";
class App extends Component {
render() {
return (
<div id="container">
<Map
id="myMap"
options={{
center: { lat: 37.769, lng: -122.446 },
zoom: 12,
}}
onMapLoad={(map) => {
let marker = new google.maps.Marker({
position: { lat: 37.769, lng: -122.446 },
map: map,
});
//Changing Marker position for clicked coordinate
map.addListener("click", (event) => {
marker.setPosition(event.latLng);
});
}}
/>
</div>
);
}
}
export default App;
Now react native maps only gets marker location on tap of the MapView
<MapView scrollEnabled={true} style={styles.map} region={region} onPress={selectLocationHandler}>
{markerCoordinates && <Marker title="picked Location" draggable={true}
isPreselected={true} coordinate={markerCoordinates}></Marker>}
</MapView>
I want to have my current location marked without tapping on the mapview
I have the following code for MapView and Marker
Can somebody help me with this?
You can do something like:
componentDidMount() {
this.watchID = navigator.geolocation.watchPosition(
position => {
const { latitude, longitude } = position.coords;
this.marker._component.animateMarkerToCoordinate(
{ latitude,longitude },
500);
});
}
with this._markeras your marker ref
I need an advice for the react-leaftlet port of leaflet. I am generating markers on a map and use marker clustering with react-leaflet-markercluster. Each markerdata is associated with some data. I want to filter that data based on the markers in the viewport.
My idea: Get the boundaries of the map and cross-check with each markers. Yes, it works. But the performance is extremly slow (> 4.5secs for calculating), when adding more than 500 markers.
What can I do to increase the performance?
Here is my code:
import React, { Component, Fragment } from 'react';
import CustomMarkers from './components/CustomMarkers';
import { Map, TileLayer } from 'react-leaflet';
import ImageContainer from './components/ImageContainer';
import { checkIfMarkerOnMap, createSampleData } from './utils/helpers';
import L from 'leaflet';
class App extends Component {
constructor(props){
super(props);
this.state = {
viewport: {
width: '100%',
height: '400px',
latitude: 40.00,
longitude: 20.00,
zoom: 5
},
visibleMarkers: {},
markers : {},
}
}
componentDidMount = () => {
const sampleData = createSampleData(1000);
this.setState({ markers: sampleData, visibleMarkers: sampleData });
const mapBoundaries = this.mapRef.contextValue.map.getBounds();
this.setState({ mapBoundaries });
}
getMapBoundaries = () => {
// Get map boundaries
const mapBoundaries = this.mapRef.contextValue.map.getBounds();
if(this.state.mapBoundaries !== mapBoundaries){
console.log("different");
this.setState({ mapBoundaries } );
} else return;
}
checkVisibleMarkers = () => {
console.time("checkVisibleMarkers");
const { markers, mapBoundaries } = this.state;
let visibleMarkers = Object.keys(markers)
.filter(key => (L.latLngBounds([[mapBoundaries._southWest.lat, mapBoundaries._southWest.lng], [mapBoundaries._northEast.lat, mapBoundaries._northEast.lng]]).contains([markers[key].coordinates.latitude,markers[key].coordinates.longitude])))
.map(key => { return { [key] : markers[key] } });
visibleMarkers = Object.assign({}, ...visibleMarkers);
console.log("visibleMarkers", visibleMarkers);
// this.setState({ visibleMarkers })
console.timeEnd("checkVisibleMarkers");
}
handleViewportChanged = () => {
this.getMapBoundaries();
this.checkVisibleMarkers();
}
render() {
console.log("this.mapRef", this.mapRef);
const { viewport, markers, visibleMarkers } = this.state;
const position = [viewport.latitude, viewport.longitude]
return (
<Fragment>
<Map
ref={(ref) => { this.mapRef = ref }}
center={position}
zoom={viewport.zoom}
maxZoom={15}
onViewportChanged={() => this.handleViewportChanged()}
style={{ height: '400px' }}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors"
/>
<CustomMarkers visibleMarkers={markers} />
</Map>
{/* <ImageContainer visibleMarkers={visibleMarkers} /> */}
</Fragment>
)
}
}
export default App;
CustomMarker.js:
import React, { Component } from 'react';
import { Marker, Tooltip } from 'react-leaflet';
import uuid from 'uuid-v4';
import {
heartIcon,
heartIconYellow,
heartIconLightblue,
heartIconDarkblue } from './../icons/icons';
import MarkerClusterGroup from 'react-leaflet-markercluster';
import L from 'leaflet';
class CustomMarkers extends Component {
render() {
const { visibleMarkers } = this.props;
let markers;
if(Object.keys(visibleMarkers).length > 0) {
markers = Object.keys(visibleMarkers).map(key => {
let latitude = visibleMarkers[key].coordinates.latitude;
let longitude = visibleMarkers[key].coordinates.longitude;
let icon = heartIcon;
if(visibleMarkers[key].category === 'fb') icon = heartIconLightblue;
if(visibleMarkers[key].category === 'blogs') icon = heartIconYellow;
if(visibleMarkers[key].category === 'artisan') icon = heartIcon;
if(visibleMarkers[key].category === 'website') icon = heartIconDarkblue;
return (
<Marker
key={uuid()}
position={ [latitude, longitude] }
icon={icon}
>
<Tooltip>{visibleMarkers[key].category}</Tooltip>
</Marker>
)
});
}
const createClusterCustomIcon = (cluster) => {
return L.divIcon({
html: `<span>${cluster.getChildCount()}</span>`,
className: 'marker-cluster-custom',
iconSize: L.point(40, 40, true),
});
}
return (
<MarkerClusterGroup
iconCreateFunction={createClusterCustomIcon}
disableClusteringAtZoom={10}
zoomToBoundsOnClick={true}
spiderfyOnMaxZoom={false}
removeOutsideVisibleBounds={true}
maxClusterRadius={150}
showCoverageOnHover={false}
>
{markers}
</MarkerClusterGroup>
)
}
}
export default CustomMarkers;
createSampleData takes the amount of sample data to generate as an input and creates a json structure for sample data { id: 1 { coordinates: {},...}
The bottleneck is the function checkVisibleMarkers. This function calculates if the marker is in viewport. Mathimatically its just two multiplications per marker.
I see a few potential issues - the performance of the checkVisibleMarkers function, and the use of uuid() to create unique (and different) key values on each rerender of a <Marker />.
checkVisibleMarkers
Regarding the checkVisibleMarkers function. There's a few calls and patterns in there which could be optimized. Here's what's currently happening:
Create an array of the markers keys
Loop through the keys, reference the corresponding marker and filter by location using L.latLngBounds().contains()
Loop through the filtered keys to create an array of objects as {key: marker}
Use Object.assign() to create an object from the array of objects
In the end, we have an object with each value being a marker.
I'm unsure of the internals of L.latLngBounds but it could be partly responsible for the bottleneck. Ignoring that, I'll focus on refactoring the Object.assign({}, ...Object.keys().filter().map()) pattern using a for...in statement.
checkVisibleMarkers = () => {
const visibleMarkers = {};
const { markers, mapBoundaries } = this.state;
for (let key in markers) {
const marker = markers[key];
const { latitude, longitude } = marker.coordinates;
const isVisible = mapBoundaries.contains([latitude, longitude]);
if (isVisible) {
visibleMarkers[key] = marker;
}
}
this.setState({ visibleMarkers });
}
A quick check on jsPerf shows the above method is ~50% faster than the method you're using, but it doesn't contain the L.latLngBounds().contains() call so it's not an exact comparison.
I also tried a method using Object.entries(markers).forEach(), which was slightly slower than the for...in method above.
The key prop of <Marker />
In the <Marker /> component you're using uuid() to generate unique keys. While unique, each rerender is generating a new key, and every time a component's key changes, React will create a new component instance. This means every <Marker /> is being recreated on every rerender.
The solution is to use a unique, and permanent, key for each <Marker />. Thankfully it sounds like you already have a value that will work for this, the key of visibleMarkers. So use this instead:
<Marker
key={key}
position={ [latitude, longitude] }
icon={icon}
>
<Tooltip>{visibleMarkers[key].category}</Tooltip>
</Marker>