Using ng-repeat with markers in ngMap - angularjs

I want to use ngMap to add Google Maps to my app.
The demos are "static" in the sense that they have only hard coded HTML. I want my code to be "dynamic" in the sense that it will periodically ask a server to look in its database and return me a bunch of coordinates to plot, which will change with time. I hope that that is clear; if not, please ask for more detail.
I modified the ngmap markers demo to generate some random lat/long coordinates every two seconds (rather than going to my server, as my final app will). Please see the Plunk.
There are no errors in the console, and it seems that ngMap is trying to add my markers, as I see a lot of this sort of thing in the console ...
adding marker with options,
Object {ngRepeat: "myMarker in markers", position: O}
clickable: true
ngRepeat: "myMarker in markers"
position: O
A: 103.96749299999999
k: 1.387454
__proto__: O
visible: true
__proto__: Object
where K and A are the Lat/Long as I expect them to be.
BUT ... I don't see any markers on the map. What am I doing wrong?
[Update] An excellent answer, for which I gladly awarded a bounty afterwards. For anyone else reading this and wishing to use ngMap as #allenhwkim said in another stackoverflow question and, I think, on his blog, ngMap just creates the map for you & after that you manipulate it with the standard Google Maps API.
For instance, just before looping to add the markers, I declared
var bounds = new google.maps.LatLngBounds(); and in the loop, after adding the marker to the map, I bounds.extend(latlng); and, finally, after the loop, I
var centre = bounds.getCenter();
$scope.map.setCenter(centre);
I forked the answer and created a new Plunk to show this. Not the world's most useful functionality, but the point is just to show how to use $scope.map with the Google Maps API. Thanks again, Allen, for ngMap.

Answer is here
http://plnkr.co/edit/Widr0o?p=preview
Please remember that ngMap is not replacing Google Maps V3 API.
Let me know if you have further questions.
The following is code block of the controller.
// $scope.map .. this exists after the map is initialized
var markers = [];
for (var i=0; i<8 ; i++) {
markers[i] = new google.maps.Marker({
title: "Hi marker " + i
})
}
$scope.GenerateMapMarkers = function() {
$scope.date = Date(); // Just to show that we are updating
var numMarkers = Math.floor(Math.random() * 4) + 4; // betwween 4 & 8 of them
for (i = 0; i < numMarkers; i++) {
var lat = 1.280095 + (Math.random()/100);
var lng = 103.850949 + (Math.random()/100);
// You need to set markers according to google api instruction
// you don't need to learn ngMap, but you need to learn google map api v3
// https://developers.google.com/maps/documentation/javascript/marker
var latlng = new google.maps.LatLng(lat, lng);
markers[i].setPosition(latlng);
markers[i].setMap($scope.map)
}
$timeout(function() {
$scope.GenerateMapMarkers();
}, 2000); // update every 2 seconds
};
$scope.GenerateMapMarkers();

Why not do something like
<map zoom="2" center="[40.74, -74.18]">
<marker position="{{destination.position}}" ng-repeat="destination in destinations"></marker>
</map>
If you asking for ng-repeat that would work. And you would populate the destinations with a simple http call to your backend:
$http.get(url + '/destinations', config).success(function (data) {
if (data != null && data.total > 0) {
$scope.destinations = data.destinations;
} else {
$scope.destinations = []
}
});

Related

Tag Manager to two ga properties

I have implemented Google enhanced Ecommerce Via GTM for GA Property (New), keeping the old classic analytics code in the webiste, Now I removed the old classic code and pushing the events data from the same GTM account to (old) GA property (Replicated the Tags with different GA Property, Reference url : http://www.kristaseiden.com/step-by-step-adding-a-second-ga-property-via-google-tag-manager/).
The first GA property transactions are used to track properly, But after adding another GA property the transactions and all other events are not tracking accurately. In both the accounts Transactions are dropped to 50 percent.
Could someone help me. Thanks in advance.
You can create a custom JS variable:
function() {
var newTrackingId = 'UA-XXXXXX-XX'; // Replace here
var globalSendTaskName = '_' + newTrackingId + '_originalSendTask';
return function(customModel) {
window[globalSendTaskName] = window[globalSendTaskName] || customModel.get('sendHitTask');
customModel.set('sendHitTask', function(sendModel) {
var hitPayload = sendModel.get('hitPayload');
var trackingId = new RegExp(sendModel.get('trackingId'), 'gi');
window[globalSendTaskName](sendModel);
sendModel.set('hitPayload', hitPayload.replace(trackingId, newTrackingId), true);
window[globalSendTaskName](sendModel);
});
};
}
And then add this as a custom task on fields to set:
Hope it helps!
PS: Here is a more detail post from Simo Ahava.

How to get the coordinates from the OpenLayers Map in ReactJS?

Some three locations are connected in a triangle shape in Open Layer Map. I am trying to get all the three locations (latitude and longitude) with the help of OpenLayers and React JS. But unfortunately, I am able to get the Latitude and Longitude of visible view and not the marked layers.
When I used the below code, it is not fetching the expected long and lat and it is resulting the visible map long and lat.
var glbox = map.getView().calculateExtent(map.getSize());
var box = proj.transformExtent(glbox,'EPSG:3857','EPSG:4326');
console.log("Latitude and longitude :",box);
So, I have tried with the below options as well and it is not resulting the expected long and lat.
console.log("Long and Lat :",map.getFeaturesAtPixel()); //--> null
console.log("Long and Lat :",map.getLayers());
console.log("Long and Lat :",map.getFeaturesAtPixel()); //--> null
How can I get the latitude and longitude of the all three locations that are shown in the image?
It will never work the way you are currently doing things.
What do I mean? I mean that going through map.getFeaturesAtPixel is one way that can work but you didn't read the API docs. You need at least to provide pixel (x, y screen coordinates) to the function.
You can get pixel using the following
map.on('click', evt => {
console.log(evt.pixel);
})
I've done a simple demo to illustrate. Go to http://openlayers.org/en/latest/examples/gpx.html and paste the following code in the browser debugger console. Click on point(s) and observe the behavior in the console.
map.on('click', evt => {
var features = map.getFeaturesAtPixel(evt.pixel);
if (features) {
// Get name (but it depends of your data attributes)
console.log(features
.filter(feature => feature.getGeometry().getType() == 'Point')
.map(feature => feature.get('name')));
// Get the features, filter to only get Point, then get geometry
// and coordinates, change projection to lon lat
console.log(features
.filter(feature => feature.getGeometry().getType() == 'Point')
.map(feature => `Longitude, latitude: ${ol.proj.toLonLat(feature.getGeometry().getCoordinates()).join(', ')}`));
}
})
Edit due to feedback.
To get the points from a LineString, just do
var featuresLinestringPointsCoordinates = vector.getSource().getFeatures()
.map(feature => {
return feature
.getGeometry()
.clone()
.transform('EPSG:3857','EPSG:4326')
.getCoordinates();
});
console.log(featuresLinestringPointsCoordinates);
// More readable and we only get the first linestring
console.table(featuresLinestringPointsCoordinates[0])
Tested on the official snap demo after drawing a LineString

How to prepare arrays for insertion into Firebase database?

I have a question about adding arrays to Firebase using AngularFire. Let's start with a quick example. What I tend to do when my users on the front end create a list is something like this:
angular.module("app", ["firebase"])
.controller("createListCtrl", function($scope, $firebaseArray) {
console.log("controller loaded");
$scope.newList = [];
$scope.addItemToList = function(itemlist) {
console.log(itemlist);
$scope.newList.push({
"item": itemlist,
"done": false
});
}
$scope.sendToDb = function() {
var ref = new Firebase("https://xxxxxx.firebaseio.com");
var list = $firebaseArray(ref);
list.$add({
"list": $scope.newList
}).then(function(ref) {
var id = ref.key();
console.log("added record with id " + id);
console.log(list.$indexFor(id)); // returns location in the array
})
}
Ok all nice and dandy and it all works great but I then I read this article:
https://www.firebase.com/blog/2014-04-28-best-practices-arrays-in-firebase.html
And I heard more people say to avoid arrays and I see the problem with array in Firebase, but what is the alternative, the article says this structure:
{foo: {counter: 1}, bar: {counter: 1}, baz: {counter: 1}};
Is that really a better structure? I think it gets messy and I don't even know how I would achieve this structure starting with something like this:$scope.newList = {};. Is it really a problem doing it with an array. Are arrays really evil in Firebase? Thanks in advance for an explanation or a better alternative.
edit
This is how the list is stored in Firebase, which does not seem very good:
---uniqueID
---list
---0
---done:false
---item:"item1"
---1
---done:false
---item:"item2"
---2
---done:false
---item:"item3"
The $firebaseArray class, which you're already using, provides a mapping between Firebase's ordered collections (which use push ids for their keys) and AngularJS's array (which use regular arrays).
So in your controller's constructor instead of creating a local array for itemList, create a two-way synchronized $firebaseArray:
$scope.newList = $firebaseArray(new Firebase("https://xxxxxx.firebaseio.com"));
The blog post you're referring to served as the basis for quite a few changes to AngularFire since then. I highly recommend that you work through the AngularFire development guide. It will take at most a few hours and will answer many more questions than just this one (which is covered in the section on synchronized arrays).
Update
Thanks for the update. I now get what you're trying to do. So you initially want to keep the list of items client-side only, and then all at once save it to Firebase.
In that case, I'd write sendToDb like this:
$scope.sendToDb = function () {
var ref = new Firebase("https://xxxxxx.firebaseio.com");
var listRef = ref.push();
$scope.newList.forEach(function(item) {
var itemRef = listRef.push({ item: item.item, done: item.done });
console.log('Added item with key: '+itemRef.key());
});
}
This uses the regular Firebase JavaScript SDK. But since AngularFire is built on top of that, they will co-exist without problems.
So instead of pushing the array in one go, I simply loop over the items in it and push each of them.
Working fiddle here: https://jsfiddle.net/frankvanpuffelen/vnh5dbwq/11/

AngularFire - manage child_added and other events on $asArray

Hello guys !
I wanted to do something pretty simple : display the last 10 posts with the newer at the top, and when someone posts a new one, display a bar "click here to see x new posts" that when clicked displays the new ones.
My problem : when a new posts enters Firebase, it immediately displays on screen (using the ng-repeat on the array of the scope linked to the array from Firebase), and takes the older of the 10 elements out.
$firebase(ref.limitToLast(10)).$asArray().$loaded().then(function(messagesData) { ... }
I can detect the change using
messagesData.$watch(function(data) {
console.log("data changed!", data, messagesData);
if(data.event == "child_added") {
// work here
}
});
But I can't figure out how to do what I'm trying to, nor did I find it in the doc. Thanks for any help !
Okay, so, there is a solution. It's possible not to have everything in sync while still enjoying the use of AngularFire for the current elements. Here is how.
Start by getting the messages you want to display at the beginning :
$firebase(ref.orderByKey().limitToLast(nbMessages)).$asArray().$loaded().then(function(messagesData) { ......
Then link them to another array one by one using foreach :
angular.forEach(messagesData, function(value, key) {
messagesDataReturn[key] = value;
// get any other data you need for that message (user name, etc...
});
This will keep every element iterated in sync. So if there is something like a counter of likes, they will be updated live.
Finally add a watcher :
messagesData.$watch(function(data) {
if(data.event == "child_added") {
var newLine = messagesData.filter(function ( obj ) {
return obj.$id === data.key;
})[0];
// add any needed data for this new line
$rootScope.$broadcast('list:updated', newLine);
}
});
In your controller you just have to listen to the broadcast :
$scope.newData = [];
$scope.$on('list:updated', function (event, data) {
$scope.newData.push(data);
});
You can then use $scope.newData to display the way to show the new messages and onclick merge this array with your main one on the scope, so the new messages appears.
Hope this helps someone !

Find a marker in a leaflet markercluster group

I want to find a marker by it's marker.title property in a leaflet markercluster group. I'm using the angular-leaflet directive (v 0.7.5) with angular (1.2.8). The marker data is provided by a service. Here is the code I'm using:
$timeout(function(){
leafletData.getLayers().then(function(layers) {
$scope.markerClusterGrp = layers.overlays.locations;
var clusters = $scope.markerClusterGrp.getLayers();
for (var i in clusters){
if (marker.title == clusters[i].options.title) {
childMarker = clusters[i];
break;
}
}
});
},1000);
This code works. However after I apply a filter to $scope.markers, the list of options.titles returned by above function is different from the list of marker.titles that's in $scope.markers. I've created a jsfiddle to illustrate this behavior. Please look at the console.log statements to see it in action. I am not sure if this is a bug or some error in my code.
http://jsfiddle.net/mukhtyar/z2Ucr/
My end goal is to find the visible parent of the marker i'm interested in and highlight the parent cluster in a mouseover event. Any help in explaining this behavior or suggestions on a different way to approach this problem are much appreciated! Thank you.
Edit: I got around this by using the clusters latlng instead of title.
//Iterate through all the markers
for (var i in clusters){
if ((marker.lat == clusters[i]._latlng.lat) &&
(marker.lng == clusters[i]._latlng.lng)) {
childMarker = clusters[i];
break;
}
}

Resources