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
Related
i am working on this React-Leaflet Map. I want to update value of "center" prop of MapContainer based on input provided by User, but it is not working.
I know, methods like flyTo(), panTo() are used in such situations but i don't know where/how to apply them... Please help.
Here's the link to codesandbox https://codesandbox.io/s/stoic-lamport-kk8mj?file=/src/App.js
From the official docs:
Except for its children, MapContainer props are immutable: changing
them after they have been set a first time will have no effect on the
Map instance or its container.
As a result, when you change center variable the center does not change. Create a function that changes the map center upon dropdown selection change
function SetViewOnClick({ coords }) {
const map = useMap();
map.setView(coords, map.getZoom());
return null;
}
Include it on MapComp
function MapComp({ coords }) {
return (
<MapContainer
classsName="map"
center={coords}
zoom={4}
scrollWheelZoom={false}
>
...
<SetViewOnClick coords={coords} />
</MapContainer>
);
}
Demo
Note that the coordinates for USA and Canada are not correct so I changed them. They should be
{
USA: [39.7837304, -100.4458825]
},
{
Canada: [61.0666922, -107.9917071]
},
and moreover the countries variable does not have to be a state variable as you do not change it. It should be a constant.
Also there is an error in the console because you are using an array on the select element which expects multi selection when using arrays but obviously you do not want that.
Last but not least you should handle the none selection somehow because an error occurs when selecting none.
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);
});
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.
All I want to do is add a class to a div the first time it scrolls into view, then keep it there. (So, I don't want to toggle it - just fire it once). I have an animation that's based on adding a class to the parent div, and even though I specified a direction, when I check it in inspector, it's still removing the class on the reverse scroll. I don't want the animation to run every time it's scrolled over, but only the FIRST time (and then stay there in the completed-animation state).
var controller = new ScrollMagic.Controller();
var scene = new ScrollMagic.Scene({
triggerElement: '#intro',
offset: 50
})
.on("start", function(e) {
if (e.scrollDirection === "FORWARD") {
$('#welcome').addClass('animated');}
})
.addTo(controller);
I did try adding a duration but it had no effect. Any suggestions?
Yes, you can add the .reverse(false) in your scene. This will make the animation happen only one time, so if you scroll back up it won't toggle the class off.
Here is an example of how to use it and a link below to a very simple demo using it. Also for even more information here is another link to the documentation.
reverse(false)
$(document).ready(function(){
var controller = new ScrollMagic.Controller();
var scene = new ScrollMagic.Scene({
triggerElement: '.title',
triggerHook: .6
})
.setClassToggle('.title', 'animate')
.reverse(false)
.addIndicators()
.addTo(controller);
});
CodePen Demo
I am working with angularjs 1.6, leaflet and mapbox. I am trying to recreate a box whose value changes based on which polygon you are hovering over in an angularjs way. The behaviour I want to recreate is in this example
http://leafletjs.com/examples/choropleth/
You can see the value in the box in the top right of the map changes based on which polygon you are hovering over. When I try to recreate it in an angular way, the view does not update but I can see the value in my component updating.
html:
<div class="data">{{vm.polygonValue}}</div>
Controller (same as the highlight feature in the example):
function highlightFeature(e) {
var layer = e.target;
layer.setStyle({
weight: 3,
color: '#666',
fillOpacity: 0.5
});
if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
layer.bringToFront();
}
addValue(layer.feature.properties);
}
function addValue(props){
vm.polygonValue = props.value;
}
All runs in the controller and I can see the vm.polygonValue change when you hover over a polygon but the view doesn't change. Can someone help? Wondering is it because the value changes too frequently or something and angular cant handle it?
Probably your function is triggered outside of the angular digest cycle, to notify angular about changes you need to execute it within $scope.$apply function.
Something like $scope.$apply(() => addValue(layer.feature.properties)) should work. You can find out more about $apply in the docs: https://code.angularjs.org/1.6.9/docs/api/ng/type/$rootScope.Scope#$apply