I am currently adapting and cleaning up a very simple Google Maps application for users to get walking directions from their current location (determined by GeoLocation) to preset markers designating artworks that are defined with specific LatLng values to the sixth decimal.
There is still a lot of extraneous code in the example that needs to be removed, but the core function is working as expected - almost.
When the user selects a destination from the values supplied in a dropdown menu and hits enter, a lined path appears from where they are to the selected destination - using the calcRoute function.
However, the path is consistently directing the user to the nearest building entrance - which in some cases is several hundred feet away from the LatLng defined in the code. This does not appear to be random inaccuracy, as the path always ends at a building entrance.
I am sure this is a very simple mistake I am making, but I haven't found any posts that seem to address this odd behavior.
I am using Windows 8 and Chrome at the moment for the base development in an attempt to get a working version and then test on other browsers. I appreciate any advice, and will supply all the example code if needed. Here is a sample of some of the typical lines in this app:
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true">
</script>
<script>
var locTreeWrapIII = new google.maps.LatLng(48.006640, -122.203680);
</script>
// Tree Wrap III
var markerTreeWrapIII = new google.maps.Marker({
map: map,
position: locTreeWrapIII,
title: 'Tree Wrap III'
});
var infoTreeWrapIII = new google.maps.InfoWindow({ // info window
maxWidth: 400,
content:
'<img alt="" src="https:\/thumbnail-clothespins.jpg" />' +
'<strong>' + markerTreeWrapIII.title + '<\/strong><br \/><br \/><br \/>
Read more about Tree Wrap III'
});
google.maps.event.addListener(markerTreeWrapIII, "click", function () {
document.getElementById('end').value = "(48.006500,-122.203500)";
infoTreeWrapIII.open(map, markerTreeWrapIII);
infoTreeWrapIII.setZIndex(999);
});
google.maps.event.addListener(markerTreeWrapIII, "dblclick", function () {
map.panTo(locTreeWrapIII);
});
google.maps.event.addListener(infoTreeWrapIII, "closeclick", function () {
infoTreeWrapIII.setZIndex(1);
infoTreeWrapIII.close();
});
function calcRoute() {
// Retrieve the start and end locations and create
// a DirectionsRequest using WALKING directions.
var start = document.getElementById('start').value;
var end = document.getElementById('end').value;
var request = {
origin: start,
destination: end,
travelMode: google.maps.TravelMode.WALKING
};
// Route the directions and pass the response to a
// function to create markers for each step.
directionsService.route(request, function (response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
}
});
}
function attachInstructionText(marker, text) {
google.maps.event.addListener(marker, 'click', function () {
// Open an info window when the marker is clicked on,
// containing the text of the step.
stepDisplay.setContent(text);
stepDisplay.open(map, marker);
});
}
My guess is that the WALKING travelling mode restricts paths to sidewalks, and thus to the entrances of buildings. Your code looks fine.
According to the API documentation:
walking requests walking directions via pedestrian paths & sidewalks (where available).
so it is very likely that Google has geocoded the the specific LatLng as an address, and is attempting to deliver the walker directly to that address.
A possible, though crude, solution would be to append a final LatLng to the path returned by the Directions Service that is the desired position.
Related
So google maps infowindow always displays the content of the last map item. Based on some research I stumbled accross this. However it still doesn't work for me here is my code.
function bindInfoWindow(marker, map, infowindow, html) {
marker.addListener('click', function () {
infowindow.setContent(html);
infowindow.open(map, this);
});
}
for (var x = 0; x < filtered_pins.length; x++) {
var links = filtered_pins[x].description + 'read more..';
links_arr.push(links);
$.getJSON('http://maps.googleapis.com/maps/api/geocode/json?address=' + filtered_pins[x].city + '&sensor=false', null, function (data) {
//console.log('4th'+ links);
var p = data.results[0].geometry.location
var latlng = new google.maps.LatLng(p.lat, p.lng);
var marker = new google.maps.Marker({
position: latlng,
map: map,
});
var infowindow = new google.maps.InfoWindow();
//marker.addListener('click', function () {
//infoWindow.setContent(links);
// infowindow.open(map, this);
//});
bindInfoWindow(marker, map, infowindow, links);
my_markers.push(marker);
});
}
I have gone through quite a number of related items on Stackoverflow but they don't seem to be of service.They all seem to already have access to the latlang so their structure is different. I have to use the .getJson method to retrieve an addresses latlang first then create markers.
Each iteration of your for loop calls $.getJSON(), however there is only a single links variable that every iteration shares. The for loop runs to completion before any of the $.getJSON() calls ever call your callback function. So all of them use the last value that was assigned into links.
It really is exactly the same problem as in the question you linked to; in the code in that question the problem variable is club. The reason the solution from that question didn't work is that your code has the $.getJSON() call which is asynchronous, just as the click handler is asynchronous.
To fix this, you can simply move the entire loop body into a function. The local variables inside that function will all be unique to each iteration of the loop.
The use of filtered_pins[x] seems OK here, but this could also be a problem if it were used inside the $.getJSON() callback, and it makes the code simpler to only have that at one point, where addMarker() is called.
And at this point you don't need bindInfoWindow() to be a separate function, so I moved it inline.
I also made a couple of other little changes to keep the line lengths shorter.
for (var i = 0; i < filtered_pins.length; i++) {
addMarker( filtered_pins[i] );
}
function addMarker( pin ) {
var links = pin.description +
'read more..';
links_arr.push(links);
var url = 'http://maps.googleapis.com/maps/api/geocode/json?address=' +
pin.city + '&sensor=false';
$.getJSON( url, null, function (data) {
var p = data.results[0].geometry.location
var latlng = new google.maps.LatLng(p.lat, p.lng);
var marker = new google.maps.Marker({
position: latlng,
map: map,
});
var infowindow = new google.maps.InfoWindow();
marker.addListener('click', function () {
infowindow.setContent(links);
infowindow.open(map, this);
});
my_markers.push(marker);
});
}
I'm not sure what the links_arr and my_markers arrays are used for, so there could be some problems there as well, depending.
In modern browsers, you could also fix this by using let instead of var for your variables inside the loop. Then you wouldn't need the addMarkers() function. But I think this separate function makes the code cleaner, and it's still compatible with old browsers.
Google Maps doesn't provide a way to break apart multiple markers that are at the same location. This can occur with a people or businesses at a multiple residency location such as an apartment building or professional services building. Depending at zoom level it can also occur at shopping malls, etc.
The way around that is to "spiderfy" them: when clicking on the first it breaks them out with a line to the location. This is done in Google Earth and George MacKerron wrote a package to do that for Google Maps. (https://github.com/jawj/OverlappingMarkerSpiderfier)
It can be integrated with markerclusterer, although it doesn't support marker clusterer's batch creation of markers.
My issue is that the application I'm working on wants to have specific icons for different types of activities. Spiderfier puts one of the markers on top. A person looking at the map has no way of knowing that there can be 10 or more other markers underneath the top marker.
Ideally, there would be a way to put a top marker that displays when there are multiple markers similar to the different icon in markercluster. It isn't a direct 1-to-1 since spiderfier also works when they are close but not exactly at the same location (default is 20 pixels) and markercluster has no provision for accessing multiple markers at the exact same location.
The ideal behavior would be have a special icon for spiders that broke into the spiderfied individual icons when clicked. Similar to markerclusterer, but without the zoom change and handling the same location. The special icon ideally would indicate how many other markers are at the spot, again like markerclusterer. The special icon could be hidden or become part of the spiderfied group.
Without some accommodation users would have no way of knowing multiple activities are at the location. They may even assume that the activity they want is not at that location because another activities marker is shown.
This is a plunker that has the problem: http://plnkr.co/edit/vimZNq?p=info
var markers = [];
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < 100; ++i) {
var latLng = new google.maps.LatLng(Math.floor(Math.random() * 10) / 10 + 39,
Math.floor(Math.random() * 10) / 10 - 100);
var marker = new google.maps.Marker({
position: latLng,
title: "marker " + i + " pos: " + latLng,
maxZoom: 8,
map: map
});
marker.desc = marker.getTitle();
bounds.extend(latLng);
markers.push(marker);
oms.addMarker(marker);
}
map.fitBounds(bounds);
var markerCluster = new MarkerClusterer(map, markers);
Thanks for your help,
David
Here's how I got it to work. Where map is a Gmap instance and oms is an Overlapping Marker Spiderfier instance. We're also using Marker Clusterer on the initial zoom which buys us a break.
map.addListener('zoom_changed', function() {
map.addListenerOnce('idle', function() {
// change spiderable markers to plus sign markers
// we are lucky here in that initial map is completely clustered
// for there is no init listener in oms :(
// so we swap on the first zoom/idle
// and subsequently any other zoom/idle
var spidered = oms.markersNearAnyOtherMarker();
for (var i = 0; i < spidered.length; i ++) {
// this was set when we created the markers
url = spidered[i].icon.url;
// code to manipulate your spidered icon url
};
});
});
oms.addListener('unspiderfy', function(markers) {
var spidered = markers;
for (var i = 0; i < spidered.length; i ++) {
url = spidered[i].icon.url;
// change it back
};
});
oms.addListener('click', function(marker) {
// put the clicked-on marker on top
// when oms un-spiders
marker.zIndex=999;
// set infowindow, panning etc.
});
I managed to match the following Versions:
MarkerClusterer 2.0.13
OverlappingMarkerSpiderfier 3.27
On every creation of a new Marker, i store the initialIconUrl in the Marker Object
var marker = new google.maps.Marker({
position: //some position
});
marker.setIcon(iconUrl);
marker.initialIconUrl = iconUrl;
When declaring the OverlappingMarkerSpiderfier, set the nearbyDistance to 0.001 (or some other very small value).
this.oms = new OverlappingMarkerSpiderfier(this.map, {
markersWontMove: true,
markersWontHide: true,
nearbyDistance: 0.001 //This will only spiderfy the Markers if they have the exact same position
});
Then, we need a listener on the maps 'idle' Event, to format the Markers manually.
I needed this because my SPIDERFIABLE Marker wouldn't show correctly on the first step, when transferring from the Clustered Marker to the seperate Markers.
var me = this;
google.maps.event.addListener(this.map, 'idle', function () {
me.oms.formatMarkers();
});
Listen to the oms 'format' Event and set the iconURL for Markers that are SPIDERFIABLE.
If the Marker is not spiderfiable, reset the Icon to the initial Url.
var spiderfiableIconUrl = //Whatever you need
this.oms.addListener('format', function (marker, status) {
var iconURL = status == OverlappingMarkerSpiderfier.markerStatus.SPIDERFIABLE
? spiderfiableIconUrl :
marker.initialIconUrl;
marker.setIcon(iconURL);
});
Hope this helps.
Some methods seems to be interesting like markersNearAnyOtherMarker but I cannot get it work.
An interesting way could be to use spiderfy and unspiderfy events and change marker when it's fired
overlappingMarkers = new OverlappingMarkerSpiderfier(map, overlapOptions);
overlappingMarkers.addListener('spiderfy', function (markers) {
markers.forEach(function (marker) {
marker.setLabel('*');
marker.setIcon(myNormalIcon);
})
})
overlappingMarkers.addListener('unspiderfy', function (markers) {
markers.forEach(function (marker) {
marker.setLabel(''+markers.length);
marker.setIcon(myOverlapIcon);
})
})
Unfortunatly, the unspiderfy event isn't fired until we open then close the overlap marker. If I find a conclusion to this solution I will update this post.
it's a couple of days i'm struggling with this problem i cannot solve.
I have an Android application sending me a certain number of coordinates; each coordinate has a value related to it and what i need to do is to report this points on a map, visualizing different marker colors depending on this value.
I'm tryng to do it via Google maps API.
I have all the points stored in a fusion table and i'm able to build a layer with proper style, but the problem is that these points do not correspond on real streets on the map and it is obviously necessary that they do.
What i am tryng is to use the DirectionsService, as suggested here:(google maps geocoder - point on a street) and i know that gps cannot be so precise.
The way i tried til now is to put all my points in an array (without using the fusion table layer, cause i dunno how to connect the two tehings) and then for each point make a request to the DirectionsService (i know it's terribly slow, but for now i need it to be precise and if you have suggestions it's well accepted) but in this way only certain points are well represented while other have a null response from the service and are not reported at all.
Any suggestion?
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;
function initialize() {
directionsDisplay = new google.maps.DirectionsRenderer();
var urbino = new google.maps.LatLng(43.72333292, 12.63552457);
var mapOptions = {
zoom:10,
center: urbino
}
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
directionsDisplay.setMap(map);
}
function calcRoute() {
var start, end;
var waypts = [new google.maps.LatLng(43.72333292,12.63552457), new google.maps.LatLng(43.72333292,12.63552457), new google.maps.LatLng(43.72333292,12.63552457), new google.maps.LatLng(43.72333292,12.63552457)............];
console.log(waypts.length);
$.each(waypts, function(index){
if(index < waypts.length -1){
var request = {
origin:waypts[index],
destination:waypts[index+1],
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
console.log(index);
console.log(response);
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
console.log('ok');
}
else{
console.log('non ok');
}
});
}
});
}
google.maps.event.addDomListener(window, 'load', initialize);
$(document).ready(function(){
calcRoute();
});
EDIT
Came out that supplied points were totally wrong (but that was not my job so i just took them), now with new points directionsService works fine even with just some waypoints.
The problem still remains in part, even if much smaller, cause points supplied from the fusion table are not precisely on the street on all the route. So i still need to figure out how to put pieces together, meaning using the route of the directionsService, styled with the fusion table layer. :)
Used points were totally wrong, now with right set of coordinates, the path supplied by fusion Table is almost perfect, on all the streets long, even though there are some points not really on the streets.
So, what i have to figure out now is how to put fusionTable layer (styled) with directionsService (precise).
I am trying to build web app where you input your address and it will give you list of bus stops in your area. I want to use Google Maps for this, but i can't find the way to use them for this. Is there any way to get list of points on maps in, lets say, JSON or XML format? I tried Google Maps Places API, but it didn't work this way. Only thing i found is this example - http://gmaps-samples-v3.googlecode.com/svn/trunk/localsearch/places.html but thats not what i need.
So, anyone knows?
The Google Places API does have stop information on there but you need to form your query very specifically to make it work.
The key is using the nearby search with rankby=distance and radius= and types=bus_station
The default rankby prominence rarely shows bus stations.
Sample query:
https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=49.89458,-97.14137&sensor=true&key=your_key&rankby=distance&types=bus_station
I think it can help you
<script>
// This example requires the Places library. Include the libraries=places
// parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">
var map;
var infowindow;
function initMap() {
var pyrmont = {lat: 38.06908229560463, lng: 46.31730562744135};
map = new google.maps.Map(document.getElementById('map'), {
center: pyrmont,
zoom: 15
});
infowindow = new google.maps.InfoWindow();
var service = new google.maps.places.PlacesService(map);
service.nearbySearch({
location: pyrmont,
radius: 1000,
types: ['bus_station','transit_station']
}, callback);
}
function callback(results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
for (var i = 0; i < results.length; i++) {
createMarker(results[i]);
}
}
}
function createMarker(place) {
var placeLoc = place.geometry.location;
var marker = new google.maps.Marker({
map: map,
position: place.geometry.location
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.setContent(place.name);
infowindow.open(map, this);
});
}
</script>
Ali Seify's answer is correct except that the API document states that only the first element in the types parameter will be used by the API and the transit_station type is the correct type for bus stop.
Also, in order to get the nearest bus stop, suggest to use parameter rankBy: google.maps.places.RankBy.DISTANCE and radius parameter cannot be used in this case.
service.nearbySearch({
location: pyrmont,
rankBy: google.maps.places.RankBy.DISTANCE,
types: ['transit_station']
}, callback);
This is not a service that Google provides. They surely have all of the stuff you need on record, but they do all of their calculations internally.
One option (which might be a bit difficult) is to mine public transportation schedules for their bus stop locations. It might be an option if you have a small region (ie. a city) that your web app is to support. It's risky because if the pages change then you'll have to reconfigure the data mining application, but you'd still have the same problem trying to mine the data from Google (or somewhere else) - if you could find a way to get a bus stop list with locations and built your app around it, it could change at any time and break your application.
You could use Google Fusion Tables for this. You would have to enter the data yourself though, unless someone else already have entered it. Google maps API supports Google Fusion Tables.
I have a Google Maps V3 polyline. I can detect click events on the entire polyline, but can I do anything more advanced with the click event?
What I'd like to do is detect which section of the polyline has been clicked, and show this in an alert.
routePath = new google.maps.Polyline({
path: routeCoordinates,
strokeColor: "#CC33FF",
strokeWeight: 3
});
routePath.setMap(map);
google.maps.event.addListener(routePath, 'click', function() {
alert(routePath);
// TODO: display which section of the polyline has been clicked?
});
Does anyone know how to do this in Google Maps?
thanks!
On the click event you can receive a LatLng of the coordinate that was clicked. However, since that will probably not be an exact point that is creating the polyline you need to find the closest point. You can use the computeDistanceBetween in the Google Maps library or you can use Pythagoras theorem as it should give you a good enough accuracy in this case.
You can find more information on computeDistanceBetween here:
https://developers.google.com/maps/documentation/javascript/reference#spherical
Here is a code example how you could do it with the computeDistanceBetween.
google.maps.event.addListener(routePath, 'click', function(h) {
var latlng=h.latLng;
alert(routePath);
var needle = {
minDistance: 9999999999, //silly high
index: -1,
latlng: null
};
routePath.getPath().forEach(function(routePoint, index){
var dist = google.maps.geometry.spherical.computeDistanceBetween(latlng, routePoint);
if (dist < needle.minDistance){
needle.minDistance = dist;
needle.index = index;
needle.latlng = routePoint;
}
});
// The closest point in the polyline
alert("Closest index: " + needle.index);
// The clicked point on the polyline
alert(latlng);
});
I ran into the same, issue here is how I dealt with it:
when setting up the handler:
google.maps.event.addListener(routePath, 'click', function(e) {
handlePolyClick(e, this)
});
var handlePolyClick(eventArgs, polyLine) {
// now you can access the polyLine
alert(polyLine.strokeColor);
});
Or if you want to access a related object set it by creating a variable on the polyLine:
routePath.car = $.extend({}, cars[1]); // shallow copy of cars[1]
then you can access your car from the event:
alert(this.car.color);
Finding the closest point by distance analysis will fail in a lot of cases where a path crosses back over or near itself.
You can use it to identify candidates, but you should confirm them by comparing the cross product and/or dot product of the 2 lines created if you use the click point to split 2 consecutive polyline points