Angular Google Maps Directive zoom too close after "place_changed" event - javascript

I'm currently working on a store finder app for DHL which can be viewed at storefinder.hashfff.com/app/index.html
In this app I am using the angular-google-maps library which provides some neat features, although I think working with the Google Maps API straight up would have been a better option as Googles API documentation is more detailed, however, being new to Angular I thought it would help.
My searchbox is tied to an event listener called "place_changed", which fires after the Autocomplete is set which takes autocomplete as a parameter.
events: {
place_changed: function(autocomplete) {
var searchString = autocomplete.gm_accessors_.place.Sc.formattedPrediction;
var searchCountry = searchString.split(',').pop().trim();
var searchCity = searchString.split(',');
var jsonQuery = "http://dhl.hashfff.com/api/dhl_store_finder_api.php/?country=" + searchCountry;
// Filter search results by search term. City, Address or Country
$.getJSON(jsonQuery , function(data) {
$scope.$apply(function() {
$scope.stores = _.filter(data, function(search) {
console.log(search.address2.toLowerCase().indexOf(searchCity[0].toLowerCase()));
return search.city.toLowerCase() == searchCity[0].toLowerCase() || search.address2.toLowerCase().indexOf(searchCity[0].toLowerCase()) > -1 || search.country.toLowerCase().indexOf(searchCity[0].toLowerCase()) > -1;
});
$('.cd-panel-search').addClass('is-visible');
});
});
place = autocomplete.getPlace();
if (place.address_components) {
// For each place, get the icon, place name, and location.
newMarkers = [];
var bounds = new google.maps.LatLngBounds();
var marker = {
id:place.place_id,
place_id: place.place_id,
name: place.address_components[0].long_name,
latitude: place.geometry.location.lat(),
longitude: place.geometry.location.lng(),
options: {
visible:false
},
templateurl:'window.tpl.html',
templateparameter: place
};
newMarkers.push(marker);
bounds.extend(place.geometry.location);
$scope.map.bounds = {
northeast: {
latitude: bounds.getNorthEast().lat(),
longitude: bounds.getNorthEast().lng()
},
southwest: {
latitude: bounds.getSouthWest().lat(),
longitude: bounds.getSouthWest().lng()
}
}
_.each(newMarkers, function(marker) {
marker.closeClick = function() {
$scope.selected.options.visible = false;
marker.options.visble = false;
return $scope.$apply();
};
marker.onClicked = function() {
$scope.selected.options.visible = false;
$scope.selected = marker;
$scope.selected.options.visible = true;
};
});
$scope.map.markers = newMarkers;
}
}
}
What happens is that after the autocomplete fires, it goes to the searched place but the zoom is set to maximum which is too close. I am aware that map.setZoom(5) is the usual answer but I do not have the map object available in this event listener.
I hope somebody has experience with the Google Maps Angular directive and could give me a hand. If you require any other code I'll be happy to update the query.

The creation of the bounds is useless, because when a LatLngBounds does have the same NE and SW(that's the case in your example, because you only have a single place/location), you may simply set the center of the map:
$scope.map.center={
latitude: bounds.getNorthEast().lat(),
longitude: bounds.getNorthEast().lng()
};
The difference : the zoom of the map will not be modified(as it does when fitBounds will be used)
when you want to set a zoom use e.g.:
$scope.map.zoom=5;

Related

Google Maps Disable Zoom on Cluster Click

I have a map with cluster based markers. I want a way when the user clicks on a cluster with more than 100 markers, don't zoom in and do something else like I am opening a popup; else just decluster.
On Load Page, I am calling a service and getting list of customers with locations. I am passing the customers to the Clustering function. Now when the user clicks on the cluster I want to put some conditions. if true open a popup but do not zoom in neither decluster. I am not able to do this. This is the code
Customers.html
<div [hidden]="!MapView" style="height: 100%" #map id="map"></div>
Customers.ts
#ViewChild('map') mapElement: ElementRef;
ngOnInit() {
this.initMap();
}
initMap = () => {
this._customerservice.GetCustomersWithLocations().subscribe(res => {
if (res != null || res != undefined) {
this.CustomersLocation = res;
let latLng = new google.maps.LatLng(-31.563910, 147.154312);
let mapOptions = {
center: latLng,
zoom: 6,
mapTypeId: google.maps.MapTypeId.TERRAIN,
fullscreenControl: false
}
setTimeout(() => { // LOAD THE MAP FIRST
this.map = new google.maps.Map(this.mapElement.nativeElement, mapOptions);
}, 1000);
setTimeout(() => { //LOAD THE CLUSTER
this.mapCluster.addCluster(res, this.map);
}, 3000);
}
}, error => {
}, () => {
});
}
GoogleMapsCluster.ts
addCluster(Customers: any, map) {
//console.log(Customers);
if (google.maps) {
//Convert locations into array of markers
let markers = Customers.map((location) => {
var marker = new google.maps.Marker({
position: location.loc,
label: location.name,
});
return marker;
});
this.markerCluster = new MarkerClusterer(map, markers, { imagePath: 'assets/m' });
google.maps.event.addListener(this.markerCluster, 'clusterclick', (cluster) => {
var markers = cluster.getMarkers();
var CustomerInsideCluster = [];
for (var i = 0; i < markers.length; i++) {
CustomerInsideCluster.push({BusinessName: markers[i].label})
}
this.OpenCustomerDetails(CustomerInsideCluster);
//I OPEN A POPUP HERE. I DONT WANT TO ZOOM IN TO DECLUSTER. AT THEN END IF THE CLUSTER HAS MORE THAN 100 CUSTOMERS I DONT WANT TO ZOOM IN WHEN I CLICK ON IT.
//map.setZoom(8); GIVING ME ERROR
});
} else {
console.warn('Google maps needs to be loaded before adding a cluster');
}
}
OpenCustomerDetails(Customers: any) {
let popover = this.popoverCtrl.create(MapPopover, { Customers: Customers }, { cssClass: 'custom-popover' });
popover.present({
ev: event
});
}
This is a possible solution. Just set the markerClusterer property zoomOnClick to false.
var markerCluster = new MarkerClusterer(map, markers, {
imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m',
zoomOnClick: false
});
Within your clusterclick event, pass the counter object to the callback function and by using a conditional statement based on your requirements, open an infowindow (or else if you prefer).
markerCluster.addListener('clusterclick', function(cluster){
if (markers.length > 5){ // change #5 if you need to test different scenarions
infowindow.setPosition(cluster.getCenter());
infowindow.setContent("Simple Test");
infowindow.open(map);
}
If the markers are less or more than the required parameters, reset the zoomOnClick to true again and use the following map and clusterObject methods:
else {
markerCluster.zoomOnClick = true;
map.fitBounds(cluster.getBounds());
}
Proof of concept in this JSBIN

How to store user selected zoom for future sessions without using cookies Google Geocoder API

Using v.3 of the Google geocoder api with a Java back end, Velocity front end and an Oracle db.
Our current spec specifies that when a user selects a marker (lat/lng) that their zoom should be saved as well for future sessions. I can't for the life of me figure out how to do this. I have seen some information about bounds which I think I may be able to use in a hackey way, but I don't want to define the bounds of the map, I just want to save the zoom (like the lat/lng) and be able to pass it to the back end.
map.js
var geocoder;
var map;
var siteLocation;
var marker;
function initMap() {
var lat = parseFloat($("#newLat").val());
var lng = parseFloat($("#newLng").val());
geocoder = new google.maps.Geocoder();
siteLocation = { lat: lat, lng: lng };
map = new google.maps.Map(document.getElementById('map'),
{
center: siteLocation,
zoom: 19,
}
);
//set crosshair
console.log('setting waypoint marker');
crosshair = new google.maps.Marker(
{
position: siteLocation,
map: map,
draggable: false,
shape: { coords: [0, 0, 0, 0], type: 'rect' },
icon: "https://www.daftlogic.com/images/cross-hairs.gif"
}
);
crosshair.bindTo('position', map, 'center');
geocodeLatLng();
}
//use new selection to
function geocodeLatLng() {
var lat = crosshair.getPosition().lat();
var lng = crosshair.getPosition().lng();
var newLocation = crosshair.getPosition();
geocoder.geocode({ location : crosshair.position}, function(results, status) {
if (status == 'OK') {
results[0].geometry.location.lat();
results[0].geometry.location.lng();
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
//get new user selected map options, [drop marker] (optional)
$("#addGeolocation").on("click", function (evt) {
geocodeLatLng();
evt.preventDefault();
var newZoom = map.getZoom();
var newLat = crosshair.getPosition().lat();
var newLng = crosshair.getPosition().lng();
$("#newLat").val(newLat);
$("#newLng").val(newLng);
newLocation = new google.maps.LatLng(newLat, newLng);
map.setCenter(newLocation);
map.setZoom(newZoom);
//make sure no marker exists
if ( marker !== undefined) {
marker.setPosition(newLocation);
} else {
marker = new google.maps.Marker({
position: newLocation,
map: map,
draggable: true
});
}
});
Velocity Macro
#macro(map $ADDRESS)
<script defer
src="https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&callback=initMap">
</script>
<div id="map"></div>
<span class="innerBlock smallBlock" id="map" ></span>
<button type="button" onclick="geocodeLatLng()" id="addGeolocation"> $BTN_ADD_GEOLOCATION</button>
#inp_hidden("newLat" "$context.getSite().getLatitude()")
#inp_hidden("newLng" "$context.getSite().getLongitude()")
#inp_hidden("newZoom")
#end
I am completely stumped. Any ideas? Most of the solutions I have seen involve cookies but we cannot use those. Any advice would be appreciated. Thanks!
I think I understand what you mean but correct me if I'm wrong.
You could save the zoom level in a variable.
var zoom = 16;
and then save this in the local storage
localStorage.setItem("zoomLevel", zoom);
then use getItem method to retrieve it
var savedZoom = localStorage.getItem("zoomLevel");
So if you were planning to have the zoom levels in a select box or something, you can save the users choice into the local storage and then retrieve it when they return by setting to zoom to 'savedZoom' for example.
I'm not completely sure if this is what you were after but hopefully it helps. I've tried not to go into too much detail just incase it isn't.

How to bind google maps using HotTowel?

I am trying to show a simple map in HotTowel.
In home.html page I have that:
<section>
<div id="map-canvas" data-bind="map: map"></div>
</section>
In home.js I have that:
define(['services/logger'], function (logger) {
var vm = {
activate: activate,
title: 'Home View',
map: map
};
return vm;
function activate() {
google.maps.event.addDomListener(window, 'load', initialize);
logger.log('Home View Activated', null, 'home', true);
return true;
}
var map;
function initialize() {
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
}
});
How to bind model with view to show map?
EDIT**
The below answer was for Durandal 1.2. In durandal 2.0 the viewAttached event was renamed to attached. You can read Durandals documentation about it here.
Durandal has a viewAttached event that is called on your viewmodel once the view has been databound and attached to the dom. That would be a good place to call the google maps api.
define(['services/logger'], function (logger) {
var vm = {
viewAttached: initialize
title: 'Home View',
map: map
};
return vm;
var map;
function initialize(view) {
logger.log('Home View Activated', null, 'home', true);
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(-34.397, 150.644),
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
}
});
EDIT AGAIN to address peoples comments**
As per Jamie Hammond's comment it is a better practice to scope your DOM transversal to the view that's being attached. If the DOM element is apart of the view.
So, inside your viewAttached (in durandal 1.2) or attached (in durandal 2.0) you would:
var map;
var mapOptions = { /*map options*/ };
var vm = {
attached: function (view) {
var mapCanvas = $('#map-canvas', view).get(0);
map = new google.maps.Map(mapCanvas, mapOptions);
}
}
I haven't messed with Durandal 2.0 at all because I've been pretty busy with work and stuff and when I was messing around with Durandal 1.0 it was just for fun but I do love the framework and hope to one day get to play with 2.0. With that said I did have an issue with generating a map in the viewattached in Durandal 1.0. But, I wasn't using Google maps. I was using Leafletjs. My solution to the problem was creating a delay in the viewAttached that would redraw the map after a short delay. This was because Durandal's transitioning in the view was not working well with leaflets ability to draw the map in the dom element as it was flying and fading in.
So, inside the viewAttached I would draw the map like so:
window.setTimeout(drawMap, 10);
Again, this was a very specific problem I had and not a problem with Durandal. This was more of a problem with Leafletjs not rendering the map correctly when the DOM element was still transitioning in.
Evan,
I'm also trying to get this working but no joy.
I have my html as and viewmodel exactly as you have, and I know the viewAttached composition is being called because I'm getting my logger event - but no map!
The only other thing I can think of is where you call your googlemaps from? I'm doing in in my index.html are you doing the same?
Regards
BrettH,
For me, the problem was that the height was 0px. My module that works looks like this:
define(['plugins/router', 'knockout', 'plugins/utility'], function (router, ko, utility) {
var vm = { };
vm.map = undefined;
vm.compositionComplete = function () {
var myLatlng = new google.maps.LatLng(29.4000, 69.1833);
var mapOptions = {
zoom: 6,
center: myLatlng
}
vm.map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var georssLayer = new google.maps.KmlLayer({
url: 'http://www.visualtravelguide.com/Pakistan-Islamabad.kmz'
});
georssLayer.setMap(vm.map);
utility.resizeElementHeight(document.getElementById('map-canvas'), 10);
$(window).resize(function () {
utility.resizeElementHeight(document.getElementById('map-canvas'), 10);
});
};
return vm;
});
My utility module looks like this:
define(['jquery','knockout'], function ($,ko) {
return {
//resizes an element so height goes to bottom of screen, got this from a stack overflow
//usage:
// resizeElementHeight(document.getElementById('projectSelectDiv'));
//$(window).resize(function () {
// resizeElementHeight(document.getElementById('projectSelectDiv'));
//});
//adjustpixels is an optional parameter if you want to leave room at the bottom
resizeElementHeight: function (element,adjustPixels) {
var height = 0;
var adjust = 0;
if (adjustPixels != undefined)
adjust = adjustPixels;
var body = window.document.body;
if (window.innerHeight) {
height = window.innerHeight;
} else if (body.parentElement.clientHeight) {
height = body.parentElement.clientHeight;
} else if (body && body.clientHeight) {
height = body.clientHeight;
}
element.style.height = ((height - element.offsetTop-adjust) + "px");
},
//looks up name by id, returns blank string if not found
//pass in a list and an id (they can be observables)
LookupNameById: function (l, wId) {
var list = ko.utils.unwrapObservable(l);
var id = ko.utils.unwrapObservable(wId);
var name = '';
$.each(list, function (key, value) {
if (value.Id() == id)
name = value.Name();
});
return name;
},
//sets the widths of the columns of headertable to those of basetable
setHeaderTableWidth: function (headertableid,basetableid) {
$("#"+headertableid).width($("#"+basetableid).width());
$("#"+headertableid+" tr th").each(function (i) {
$(this).width($($("#"+basetableid+" tr:first td")[i]).width());
});
$("#" + headertableid + " tr td").each(function (i) {
$(this).width($($("#" + basetableid + " tr:first td")[i]).width());
});
}
};
});
Hope this helps you.
first go to your main.js and add 'async': '../Scripts/async',
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'mapping': '../Scripts/knockout.mapping-latest',
'async': '../Scripts/async',
'transitions': '../Scripts/durandal/transitions'
},
shim: { mapping: { deps: ['knockout'] } }
});
notice that we need to add async.js in the scripts folder so go to Download async.js download the file and save it in hottowel script folder as async.js
the in the main.js add this
// convert Google Maps into an AMD module
define('gmaps', ['async!http://maps.google.com/maps/api/js?v=3&sensor=false'],
function(){
// return the gmaps namespace for brevity
return window.google.maps;
});
in any viewmodel you can now use it like this
define(['plugins/router', 'knockout', 'services/logger', 'durandal/app', 'gmaps'], function (router, ko, logger, app, gmaps) {
i hope this will help:

How to auto-update a marker on a leaflet map, with meteor

For a school project we are having this idea of making a geospatial tag-game. You log in on our app, your location is shown on the map, and whenever you get close to another player, you tag that person. (Like children's tag but with meteor)
The issue we are having, we seem not able to auto-update our marker on the leaflet map. There's an marker showing it's just not updating.
We tried using Player.update in a time but it doesn't work.
Any suggestions?
The code
if (Meteor.isClient) {
var userLatitude;
var userLongitude;
var map;
Template.map.rendered = function () {
// Setup map
map = new L.map('map', {
dragging: false,
zoomControl: false,
scrollWheelZoom: false,
doubleClickZoom: false,
boxZoom: false,
touchZoom: false
});
map.setView([52.35873, 4.908228], 17);
//map.setView([51.9074877, 4.4550772], 17);
L.tileLayer('http://{s}.tile.cloudmade.com/9950b9eba41d491090533c541f170f3e/997#2x/256/{z}/{x}/{y}.png', {
maxZoom: 17
}).addTo(map);
// If user has location then place marker on map
if (userLatitude && userLongitude) {
var marker = L.marker([userLatitude, userLongitude]).addTo(map);
}
var playersList = players.find().fetch();
playersList.forEach(function(players) {
// Change position of all markers
var marker = L.marker([players.latitude, players.longitude], options={"id" : 666}).addTo(map);
});
};
// If the collection of players changes (location or amount of players)
Meteor.autorun(function() {
var playersList = players.find().fetch();
playersList.forEach(function(players) {
// Change position of all markers
var marker = L.marker([players.latitude, players.longitude]).addTo(map);
});
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
/*
Template.hello.events({
'click input' : function () {
// template data, if any, is available in 'this'
if (typeof console !== 'undefined')
console.log("You pressed the button");
}
});
*/
/*
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
userLatitude = 52.35873;
userLongitude = 4.908228;
players.insert({
name: "Martijn",
latitude: userLatitude,
longitude: userLongitude
});
});
}
*/
You need to clear the existing markers, otherwise they remain on the map. The easiest / most efficient way to do this is to attach the markers to a LayerGroup when you're creating them. Then, when you want to update, clear all the markers, and then add them again.
Add layer group declaration at the top, so you have
var map, markers;
After initialising the map,
markers = new L.LayerGroup().addTo(map);
Change this line:
var marker = L.marker([userLatitude, userLongitude]).addTo(map);
to:
var marker = L.marker([userLatitude, userLongitude]).addTo(markers);
in your autorun, before the forEach,
markers.clearLayers();
then in your foreach,
var marker = L.marker([players.latitude, players.longitude]).addTo(markers);

Google Maps Zoom and Description dialog not working right

Well I'm having a couple problems getting google maps to work using the v3 API.
Look here: [Removed by Poster]
Both maps are, in fact, working but the zoom level seems like it is random. The zoom is set to 12 when the map is initialized. Also, if you click on the marker, the description box is missing corners and is unable to be closed. Here is the javascript functions I am using to activate these maps:
var mapHash = [];
var bound = new google.maps.LatLngBounds();
finishedCoding = false;
function initMap(map_container_div,lat,lng) {
var latlng = new google.maps.LatLng(lat,lng);
var myOptions = {
zoom:12,
center:latlng,
mapTypeId:google.maps.MapTypeId.ROADMAP,
streetViewControl: false
};
var map = new google.maps.Map(document.getElementById(map_container_div), myOptions);
if (!getMap(map_container_div)) {
var mapInfo = {
mapkey:'',
map:'',
geocoder : new google.maps.Geocoder()
};
mapInfo.map = map;
mapInfo.geocoder = new google.maps.Geocoder();
mapInfo.mapKey = map_container_div;
mapHash.push(mapInfo);
}
}
function placeMarker(myAddress, mapId, description, title) {
mapIndex = getMap(mapId)
//alert(myAddress + mapId + map)
mapHash[mapIndex].geocoder.geocode({
'address':myAddress
}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
mapIndex = getMap(mapId)
var marker = new google.maps.Marker({
map:mapHash[mapIndex].map,
position:results[0].geometry.location,
title: title
});
bound.extend(results[0].geometry.location);
mapHash[mapIndex].map.fitBounds(bound);
finishedCoding = true;
placeDesc(marker,description,mapId);
}
});
}
function placeDesc(marker,myDesc,mapId) {
mapIndex = getMap(mapId);
var infowindow = new google.maps.InfoWindow({
content: myDesc
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(mapHash[mapIndex],marker);
});
}
function getMap(mapKey) {
for (var i = 0 ; i < mapHash.length ; i++) {
if (mapHash[i].mapKey == mapKey) {
return i;
}
}
return false;
}
function startmap(mapidd,address,description,title,lat,lng) {
initMap(mapidd,lat,lng)
placeMarker(address,mapidd,description,title)
}
by just removeing
body img {
max-width: 520px !important;
height: auto !important;}
from style sheet
http://www.wppassport.com/wp-content/plugins/easyfanpagedesign/default.theme/style.css
your problem is resolved now
Your dialog boxes aren't closing because of a javascript error.
Something is wrong with infowindow.open(mapHash[mapIndex],marker); inside your click listener. It's displaying the window, which makes you think that the error is happening after, but I'm pretty sure it's in the call itself. When I debugged you weren't making an obvious mistake, but I still think that that line of code is the culprit.
I solved this issue myself and am kicking myself for not thinking of this. :)
Just had to add mapHash[mapIndex].map.setZoom(12);
And I removed the following 2 codes:
bound.extend(results[0].geometry.location);
mapHash[mapIndex].map.fitBounds(bound);

Categories