Reputation: 530
First, I want to say I'm not too experienced with Google Maps Javascript API v3
(my only experience are these last 3 days). I have managed to display a map with a list of markers and connect them using DirectionsService
, but sometimes my list is pretty large and I get the following error:
Failed to load resource: the server responded with a status of 413 (Request Entity Too Large)
This is my code:
// List of all locations for device
var locations = [];
// example: locations = [
// {datetime: '2014/09/28 20:20', location: '41.99999 21.99999 30.0'},
// ... {...} ... may be more than 200 entries for locations
// {datetime: '2014/09/28 20:25', location: '41.99999 21.99999 30.0'}
// ]
var map;
var markers = [];
var bounds = new google.maps.LatLngBounds();
var pathPoints = [];
var infoWindow = new google.maps.InfoWindow();
var accuracy = new google.maps.Circle({
fillColor: '#ff4080',
fillOpacity: 0.5,
strokeOpacity: 0,
zIndex: 0
});
var path = new google.maps.Polyline(polyOptions);
var geocoder;
var directionsService = new google.maps.DirectionsService();
var directionsDisplay;
var polyOptions = {
geodesic: true,
strokeColor: '#28b8b8',
strokeOpacity: 1.0,
strokeWeight: 8,
zIndex: 1
}
function showInfoWindow(marker, datetime, acc){
geocoder = new google.maps.Geocoder();
geocoder.geocode({
'latLng': marker.getPosition()
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[1]) {
infoWindow.close();
var date = datetime.split(" ")[0];
var time = datetime.split(" ")[1];
var content = '<div class="infowindow">'
+ results[1].formatted_address.trim() + '<br />'
+ 'Date: ' + date + '<br />'
+ 'Time: ' + time + '<br />'
+ 'Accuracy: ' + acc + 'm'
+ '</div>';
infoWindow.setContent(content);
infoWindow.open(map, marker);
accuracy.setMap(null);
accuracy.setMap(map);
accuracy.setCenter(marker.getPosition());
accuracy.setRadius(acc/1.6);
} else {
// alert('No results found');
}
} else {
alert('Geocoder failed due to: ' + status);
}
});
}
function addMultiMarker(latLng, num, datetime, acc){
// Create marker at provided location
var marker = new google.maps.Marker({
position: latLng,
map: map,
icon: image_circle,
title: 'Location #' + num,
zIndex: num + 1
});
// On marker click center it inside map and show infoWindow
google.maps.event.addListener(marker, 'click', function() {
map.panTo(marker.getPosition());
showInfoWindow(marker, datetime, acc);
});
return marker;
}
function showRoute() {
var rendererOptions = {
draggable: false,
hideRouteList: true,
suppressMarkers: true,
infoWindow: infoWindow,
polylineOptions: polyOptions,
map: map
};
directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);
var len = markers.length;
var start = markers[0].getPosition();
var end = markers[len - 1].getPosition();
new google.maps.event.trigger(markers[len - 1], 'click');
var wayPts = [];
for(var i = 1; i < len - 1; i++){
wayPts.push({
location: markers[i].getPosition(),
stopover: true
});
}
var request = {
origin: start,
destination: end,
waypoints: wayPts,
optimizeWaypoints: true,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsDisplay.setDirections(response);
}
});
}
function showMapPeriod(periodStart, periodEnd){
// Simple map options
var mapOptions = {
zoom: 15,
center: new google.maps.LatLng(41, 21),
mapTypeId : google.maps.MapTypeId.ROADMAP,
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
position: google.maps.ControlPosition.TOP_RIGHT
},
zoomControl: true,
zoomControlOptions: {
style: google.maps.ZoomControlStyle.LARGE,
position: google.maps.ControlPosition.LEFT_CENTER
},
panControl: false,
streetViewControl: false
};
$("#map").html("");
// Create and show map
openMapContainer(); // just a function that shows the map <div> element
map = new google.maps.Map(document.getElementById('map'), mapOptions);
// Create and display markers
bounds = new google.maps.LatLngBounds();
markers = [];
var len = 0;
for(var i = periodStart; i <= periodEnd; i++, len++){
var loc_vals = locations[i].location.trim().split(" ");
var lat = parseFloat(loc_vals[0]);
var lng = parseFloat(loc_vals[1]);
var acc = parseFloat(loc_vals[2]);
// Create marker at provided location
var datetime = locations[i].datetime;
var latLng = new google.maps.LatLng(lat, lng);
markers[len] = addMultiMarker(latLng, len+1, datetime, acc);
bounds.extend(latLng);
}
showRoute();
map.fitBounds(bounds);
}
Well my code works partially and if someone can help me to eliminate the problem I would very much appreciate it. To explain it better, I need some kind of solution to request Directions
with a lot (200+) waypoints or some way to connect the locations through the roads (I don't really need the directions, but I don't want to connect the locations with a simple Polyline
).
EDIT: Here I have provided a simple demo, to see the problem just uncomment at line 15
.
Upvotes: 1
Views: 3202
Reputation: 530
Since nobody was able to help me with my problem, I had to solve it myself. I'm answering my own question because this seems to be quite a problem for a lot of people and it's not well enough explained on the web, so this may help developers that have similar problems.
Anyway, let's get to the point. The error was thrown because of Google limitations as OxyDesign suggested, the error was:
google.maps.DirectionsResult.MAX_WAYPOINTS_EXCEEDED
- This is because I sent a request with origin, destination and more than 8 (Google limit) waypoints.that I easily solved (I just split the array in chunks of 10 points with the last point of one request as the first point of the next), but then I got another error:
google.maps.DirectionsResult.OVER_QUERY_LIMIT
- This is because I tried to send more than 10 (again, Google limit) requests per second.that took a little more experimenting, searching and testing to solve, but I got it solved.
Now, here is the code that works for me (at least at the time of posting, until Google changes something):
// list of randomly generated locations (max 288)
var locations = [];
for(var i=0; i<288; i++){
locations[i] = {
datetime: '2014/10/01 12:10',
location: (51 + Math.random()) +
' ' + (-0.5 + Math.random()) + ' 30.0'
};
}
// Marker images
var image_pin = new google.maps.MarkerImage(
'http://stedkomerc.com.mk/gpslocator/images/mPin.svg',
new google.maps.Size(25, 41), // size
new google.maps.Point(0, 0), // origin, top-left corner
new google.maps.Point(12, 40) // anchor
);
var image_circle = new google.maps.MarkerImage(
'http://stedkomerc.com.mk/gpslocator/images/mCircle.svg',
new google.maps.Size(19, 19), // size
new google.maps.Point(0, 0), // origin, top-left corner
new google.maps.Point(9, 9) // anchor
);
// Variables
var map;
var bounds = new google.maps.LatLngBounds();
var markers = [];
var pathPoints = [];
var geocoder;
var infoWindow = new google.maps.InfoWindow();
var accuracy = new google.maps.Circle({
fillColor: '#ff4080',
fillOpacity: 0.4,
strokeOpacity: 0,
zIndex: 0
});
var polyOptions = {
geodesic: true,
strokeColor: '#28b8b8',
strokeOpacity: 1.0,
strokeWeight: 8,
zIndex: 1
};
var path = new google.maps.Polyline(polyOptions);
var directionsService = new google.maps.DirectionsService();
var directions = [];
var rendererOptions = {
draggable: false,
hideRouteList: true,
suppressMarkers: true,
preserveViewport: true,
infoWindow: infoWindow,
polylineOptions: polyOptions
};
var requests = [];
var MAX_POINTS_PER_REQUEST = 10; // 8*waypts Google limit + start + end
var MAX_REQUESTS_PER_SECOND = 10; // Google limit
// functions
function showInfoWindow(marker, datetime, acc){
geocoder = new google.maps.Geocoder();
geocoder.geocode({
'latLng': marker.getPosition()
}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[1]) {
infoWindow.close();
var date = datetime.split(" ")[0];
var time = datetime.split(" ")[1];
var content = '<div class="infowindow">' +
results[1].formatted_address.trim() + '<br />' +
'Date: ' + date + '<br />' +
'Time: ' + time + '<br />' +
'Accuracy: ' + acc + 'm' +
'</div>';
infoWindow.setContent(content);
infoWindow.open(map, marker);
accuracy.setMap(null);
accuracy.setMap(map);
accuracy.setCenter(marker.getPosition());
accuracy.setRadius(acc);
}
} else {
console.log('Geocoder failed due to: ' + status);
}
});
}
function addMultiMarker(latLng, num, datetime, acc){
// Create marker at provided location
var marker = new google.maps.Marker({
position: latLng,
map: map,
icon: image_circle,
title: 'Location #' + num,
zIndex: num + 1
});
// On marker click center it inside map and show infoWindow
google.maps.event.addListener(marker, 'click', function() {
//map.panTo(marker.getPosition());
showInfoWindow(marker, datetime, acc);
});
return marker;
}
function connectMarkersPolyline(mrkrs){
path.setMap(map);
pathPoints = path.getPath();
len = mrkrs.length;
for(var i = 0; i < len; i++){
pathPoints.push(mrkrs[i].getPosition());
}
}
function connectMarkersRoute(mrkrs, part, maxParts){
var directionsDisplay = new google.maps.DirectionsRenderer(rendererOptions);
directionsDisplay.setMap(map);
var len = mrkrs.length;
var start = mrkrs[0].getPosition();
var end = mrkrs[len - 1].getPosition();
var wayPts = [];
for(var i = 1; i < len - 1; i++){
wayPts.push({
location: mrkrs[i].getPosition(),
stopover: true
});
}
var request = {
origin: start,
destination: end,
waypoints: wayPts,
optimizeWaypoints: false,
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
// request status OK, display route
directionsDisplay.setDirections(response);
// save it in array in case we want to remove it later
directions.push(directionsDisplay);
// if not last chunk, send next chunk after 100ms
// 1 request per 100ms => 10 requests per 1s
if(part+1 < maxParts)
setTimeout(connectMarkersRoute(requests[part+1], part+1, maxParts), 100);
else showLastMarker();
} else if (status == google.maps.DirectionsStatus.OVER_QUERY_LIMIT) {
// if we get error, send same request after bigger delay (120ms)
setTimeout(connectMarkersRoute(requests[part], part, maxParts), 120);
} else {
// if all fails, connect with simple polyline
console.log('Directions failed due to: ' + status);
connectMarkersPolyline(mrkrs);
}
});
}
function connectMarkers(markers){
path.setMap(null);
path.setPath([]);
directions = [];
requests = [];
var len = markers.length;
console.log('connecting ' + len + ' markers');
var i, j;
// split markers array into chunks of 10 (start + waypts + end)
for(i=0; i<len; i+=MAX_POINTS_PER_REQUEST-1){
if(i<len-1)
requests.push(markers.slice(i, i+MAX_POINTS_PER_REQUEST));
}
// send first chunk to connectMarkersRoute()
connectMarkersRoute(requests[0], 0, requests.length);
}
function showMapPeriod(periodStart, periodEnd){
// Map options
var mapOptions = {
zoom: 16,
center: new google.maps.LatLng(41.995922, 21.431465),
mapTypeId : google.maps.MapTypeId.ROADMAP,
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
position: google.maps.ControlPosition.TOP_RIGHT
},
zoomControl: true,
zoomControlOptions: {
style: google.maps.ZoomControlStyle.LARGE,
position: google.maps.ControlPosition.LEFT_CENTER
},
panControl: false,
streetViewControl: false
};
$("#map").html("");
//openMapContainer();
// Create and show map
map = new google.maps.Map(document.getElementById('map'), mapOptions);
// Create and display markers
bounds = new google.maps.LatLngBounds();
markers = [];
var len = 0;
for(var i = periodStart; i <= periodEnd; i++, len++){
var loc_vals = locations[i].location.trim().split(" ");
var lat = parseFloat(loc_vals[0]);
var lng = parseFloat(loc_vals[1]);
var acc = parseFloat(loc_vals[2]);
// Create marker at provided location
var datetime = locations[i].datetime;
var latLng = new google.maps.LatLng(lat, lng);
markers[len] = addMultiMarker(latLng, len+1, datetime, acc);
bounds.extend(latLng);
}
connectMarkers(markers);
map.fitBounds(bounds);
if(map.getZoom() > 16) map.setZoom(16);
}
function showLastMarker(){
new google.maps.event.trigger(markers[markers.length - 1], 'click');
}
// show map
showMapPeriod(1, 280);
// --------
As you can see, the only real change I made was the showRoute()
function became connectMarkers()
combined with connectMarkersRoute()
and connectMarkersPolyline()
that handle both errors.
Working example can be seen here. It takes a little time to load all route chunks, but at least it works. Hope this helps somebody else too.
Note that there is another Google limit of 2500 Directions
requests per day, so be careful. That being said, this answer works even for a lot larger requests than 200 locations, I have tested with 1250 locations and it worked.
Upvotes: 6
Reputation: 754
It seems there a limitation for this service Gmaps Waypoints (8 or 23, I tried and it worked until 39, then 40 didn't) and there's no way to do it in other way except with polylines but you don't want it so I think there's no viable solution ...
Upvotes: 3