Google maps api with React - reactjs

I am currently working on a project and have tried multiple times to include clickable markers into my map. I have use google-maps-react library, however I have not managed to get a marker showing information and being able to click on a button on this information window. I have read that Marker by default isn't clickable but is there an other way to do so ? or are there any tutorials or code that explain so
thank you in advance...

You need to use .addListener() on a marker and tell it to open an infowindow (or whichever action you want to take on marker click).
You can find an example in the Google API documentation here
// Add a marker to the map
const marker = new google.maps.Marker({
// position of the marker on map
position: { lat: 53.3, lng: -6.3 },
// the map object you want the marker on
map: map,
});
// add a listener to the marker which listens for a 'click'
// and runs the callback when it 'hears' one
marker.addListener('click', () => {
// set the content of the info window. This can be formatted using HTML
infowindow.setContent('I have been clicked!');
// tells the infowindow on which map to open and which marker to anchor to
infowindow.open(map, marker);
});

Related

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!

Fit map to feature layer bounds in react-leaflet

What I want to achieve:
Have a <Map><FeatureGroup><Circle />[1 or more]...</FeatureGroup></Map> hierarchy and fit the map bounds to the feature group so that all the circles are in the viewport.
If there is only one circle, it should fit the bounds (ie: zoom in on) to that circle.
What I've tried:
giving FeatureGroup a ref and calling getBounds on it to pass onto Map. Because of the lifecycle FeatureGroup doesn't exist at the time componentDidMount is called - it gets rendered later (https://github.com/PaulLeCam/react-leaflet/issues/106#issuecomment-161594328).
Storing Circle in state and calling getBounds on that (assuming, in this case, that there is only one circle. That didn't work either.
I think I might need to do something with the React Context but I'm not sure that I fully understand it right now, so I need some help.
Other information
I'm using react-leaflet#2.1.2
Thanks for any help offered!
Because the contents of the Map are unavailable at componentDidMount-time (https://github.com/PaulLeCam/react-leaflet/issues/106#issuecomment-161594328) you cannot get the bounds of the FeatureGroup at that point, and out of all the refs you assign, only the Map ref will be available in this.refs.
However, as per this GitHub comment: https://github.com/PaulLeCam/react-leaflet/issues/106#issuecomment-366263225 you can give a FeatureGroup an onAdd handler function:
<FeatureGroup ref="features" onAdd={this.onFeatureGroupAdd}>...
and you can then use the Map refs to access the leafletElement and call fitBounds with the bounds of the incoming event target, which will be the FeatureGroup:
onFeatureGroupAdd = (e) => {
this.refs.map.leafletElement.fitBounds(e.target.getBounds());
}
This will then "zoom" the map into the bounds of your FeatureGroup, as desired.
Update
I modified my React component so that zoom and centre are controlled by query parameters. The problem with the above solution was that if you zoomed in on a MarkerClusterGroup by clicking on it, for example, it would update the zoom in the url, re-render the map and re-call onFeatureGroupAdd, thus undoing all the marker cluster goodness.
What I needed was to access the zoom level required to keep the newly drawn circle nicely in bounds, then update the url with the correct zoom level and center.
onDrawCircle = (e) => {
...
var targetZoom = this.refs.map.leafletElement.getBoundsZoom(e.layer.getBounds());
// Call function to update url here:
functionToUpdateUrl(targetZoom, e.layer.getBounds().getCenter());
}
}
In order to be able to control the whole map I also call functionToUpdateUrl in onZoomEnd and onDragEnd event handlers, like so:
onChangeView = (e) => {
functionToUpdateUrl(e.target._zoom, this.refs.map.leafletElement.getCenter());
}
and one for handling cluster clicks:
onClusterClick = (e) => {
// This time we want the center of the layer, not the map?
functionToUpdateUrl(e.target._zoom, (e.layer ? e.layer.getBounds().getCenter() : e.target.getBounds().getCenter()));
}
Then, when rendering the Map element, pass these properties:
<Map
center={center}
ref='map'
zoom={zoom}
maxZoom={18}
onZoomEnd={this.onChangeView}
onDragEnd={this.onChangeView}
>
....
</Map>
And remember to give any MarkerClusterGroups their onClusterClick callback:
<MarkerClusterGroup onAdd={this.onMarkerGroupAdd} onClusterClick={this.onClusterClick}>
Have you tried doing getBounds in the componentDidMount function instead of componentWillMount? If that doesn't work then I'd suggest extending the FeatureGroup component and adding an onLoaded function as as prop and call that function in the componentDidMount function of your extended component. And by extending the FeatureGroup component I actually mean copying/pasting it from here. (if you care about why you need to copy that whole file check this thread)
This isn't tested but your code will probably look something like
import { FeatureGroup } from 'leaflet';
import { withLeaflet, Path } from 'react-leaflet';
class CustomFeatureGroup extends Path {
createLeafletElement(prop) {
const el = new FeatureGroup(this.getOptions(props));
this.contextValue = {
...props.leaflet,
layerContainer: el,
popupContainer: el,
};
return el;
}
componentDidMount() {
super.componentDidMount();
this.setStyle(this.props);
/*
Here you can do your centering logic with an onLoad callback or just
by using this.leafletElement.map or whatever
*/
this.props.onLoaded();
}
}
export default withLeaflet(CustomFeatureGroup)
Note: If you are using react-leaflet V1 this is actually way easier and I can edit this answer with that code if needed.

reactjs leaflet kml wrong behaviour

I'm using https://github.com/shramov/leaflet-plugins display kml in my map, it work's great, but when I'm using reactjs kml not displaying as should be and map.fitBounds(e.target.getBounds()); not working, there is no problem if not using reactjs, here is my code
componentDidMount() {
mymap = new L.Map('map', {center: new L.LatLng(-2.6025, 140.6927), zoom: 11});
var baselayer = new L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'OSM ',
maxZoom: 18,
});
mymap.addLayer(baselayer);
baselayer.on('load', function (event) {
var track = new L.KML("/files/1/Layer/Administrasi/Administrasi Kota Jayapura.kml", {async: true});
mymap.addLayer(track);
});
}
here is the result when using reactJS
Do I Miss something ?
I don't know that KML was threaded as svg or not, but but global style for SVG seems affected KML as well, so remove/modify css for svg solve this problems.

React Native Maps custom marker is not touchable after region change

I am using custom markers with React Native Maps. On the initial load touching the markers will reveal the callout (title and description) of the marker. However, after dragging the map to a new region, the markers are no longer touchable. To show the marker's title and description after changing regions, the user must tap exactly on the marker coordinate.
While dragging the map, the marker images flash in and out. I think this might have something to do with them becoming untouchable. How can I prevent this?
<MapView
style={styles.homeMapView}
region={this.state.currentLocation}
onRegionChange={this.onRegionChange}
showsUserLocation={true}
onPress={(evt) => console.log(evt.nativeEvent.coordinate)}
>
{this.state.nearbyLocations.map((marker, key) => (
<MapView.Marker
key={marker.id}
coordinate={marker.coordinate}
title={marker.title}
description={marker.description}
onPress={(evt) => console.log('pressed ', evt)}
image={require('../Public/existingPins.png')}
centerOffset={{x: 0, y: -20}}
/>
))}
</MapView>
EDIT
It seems the mark is actually stil touchable after the map region is changed. The marker's onPress function still console logs every time the marker is touched even if the map region changes. After reading the React Native Maps docs a little more, it looks like the area that showCallout can be invoked by touching is the only part that shrinks. This touchable area only shrinks if a custom marker is used.
Have you tried to use onSelect prop instead of onPress prop?

How to change marker option in angularGM

I'm using http://angulargm.herokuapp.com/documentation/angulargm-0.3.1/api/angulargm.directive:gmMarkers directive to make a google map with markers with angularjs.
I want to be able to change markerOptions after their initial set or somehow get the google marker object from outside, so on a button click I can set animations to it, change the icon, etc'.
It seems that the directive parameter gm-get-marker-options is only used in the initial creation of the marker, because if I change the options it doesn't have any effect on the markers.
The only solution I have so far is to use the directive parameter gm-events to simulate a click on the required marker location, then use the marker object in the listener function to change the marker appearance, but this is problematic as I could have more than one marker with the same coordinates.
Perhaps I'm missing something?
This is now possible in the new angular-gm version 1.0.0. See the documentations about how to specify marker ids and use them. http://dylanfprice.github.io/angular-gm/1.0.0/docs/#/api/angulargm.directive:gmMarkers
The only solution I have so far is to use the directive parameter gm-events to simulate a click on the required marker location, then use the marker object in the listener function to change the marker appearance, but this is problematic as I could have more than one marker with the same coordinates.
Perhaps I'm missing something?
No, you're not missing anything, this is a design flaw in the current version of AngularGM in that it uses the location as the single piece of identifying information for an object/marker. The markers-by-id branch will address this issue and hopefully I will be landing that soon.
As notsure said, the way to update the markers if you change one of your objects is to force a redraw via $scope.$broadcast('gmMarkersRedraw', 'myObjects').
it is pretty easy to change the markers. As the angular plugin iterates through some list of markers and as you should use a scope variable angular handles the two way bindings.
long story short:
Here is a scope variable 'map_markers' which is a list of displayed markers.
A function 'getPinImage' which returns a google maps icon.
And a function which is triggered when you click on a marker on the map; it changes the color of the clicked marker.
$scope.map_markers = [
{
title: 'Marker one',
location: {
lat: 47.1212,
lng: 10.3434
}
},
{
title: 'Marker two',
location: {
lat: 57.1212,
lng: 20.3434
}
}
];
$scope.getPinImage = function(color) {
// helper which returns a valid google maps marker image in
// the given color
color = color || '4EB1E8';
var icon_api = "http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|";
return new google.maps.MarkerImage(
icon_api + color,
new google.maps.Size(21, 34),
new google.maps.Point(0, 0),
new google.maps.Point(10, 34));
};
$scope.triggerMe = function(object, marker, map) {
// is executed on click, when clicking on a single marker on the map
// changes the color of the selected marker and resets a prev clicked marker
console.log('Click ', object, marker, map)
if ($scope.prev_selected_marker) {
$scope.prev_selected_marker.setOptions({icon: $scope.getPinImage()});
}
$scope.prev_selected_marker = marker;
marker.setOptions({icon: $scope.getPinImage('FFFF00')});
};
HTML Code:
<gm-map gm-map-id="myMap">
<gm-markers gm-objects="map_markers"
gm-get-lat-lng="{ lat: object.location.lat, lng: object.location.lng }"
gm-get-marker-options="{ icon: object.icon }"
gm-on-click="triggerMe(object, marker, marker.getMap());">
</gm-markers>
</gm-map>
To get a little deeper: When the marker object is edited AFTER your initial creation an event called 'gmMarkersUpdated' is triggerd, which you could listen to like this:
$scope.$on('gmMarkersUpdated', function(event, objects) {
if (objects === 'myObjects') {
...
}
});
However it is often not required to listen to these events, but if you really need to manually remove and redraw your markers you can broadcast this event and force it like this:
$scope.$broadcast('gmMarkersRedraw', 'myObjects');
Get Some Example Code here

Resources