Duncan's blog

October 16, 2017

Animated Google Maps paths from a .csv file

Filed under: Google Maps,Javascript,jQuery — duncan @ 10:06 pm
Tags: ,

I got this question on one of my previous posts, Animated paths with Google Maps, from Shivani Arora, which I thought needed its own blog post to fully answer it:

“I am working on a project in which I have to plot a moving path on the map using the coordinates. I have more than 76000 coordinates. So I tried your second example with source and destination. With that code I am getting the path, but it is taking the shorter route to reach the destination.

In my project I need to show a particular moving path, but not the short route.
Is there any way by which we can do that? Also, I have a .csv file for my coordinates. Can you show a code by which I can access the coordinates from a .csv file using your code?”

So, there’s two parts here.  Firstly how to deal with lots of coordinates.  And secondly how to read them from a .csv file.

For the first part, I’d say the first method from my previous post is the better solution, than using the Directions Service.  Using the Service saves you from having to store all the coordinates yourself, provided you can rely on Google’s directions matching what you need.

But if you’ve already got all the coordinates sorted, and/or you need to plot your own path rather than be able to rely on the Directions Service, then just use your own coordinates entirely.

In my example I’d hardcoded something like 300 coordinates into the javascript in the page.  More likely you should use something server-side to take care of that for you, perhaps reading them from a database.

However if you’ve got them all in a .csv file, you could just use javascript to read that for you.  Here I’m just making an AJAX request to read the file directly, and then splitting it on line endings and commas to get the data into a giant 2D array.

So firstly just load the map, then call the getCoords function.

function initialize() {
	map = new google.maps.Map(document.getElementById("map"), {
	  center: {lat: 51.4788639, lng: -0.0062897},
	  zoom: 3,
	  mapTypeId: google.maps.MapTypeId.ROADMAP
	});
	
	getCoords();
}

Secondly, the getCoords function. I’m using jQuery to make an ajax request directly to the .csv file. Just setting the dataType to text, doesn’t seem to be any problem.

function getCoords() {
	$.ajax({
		url: 'coordinates4.csv',
		dataType: 'text',
		success: function(data) {
			var lines = data.split(/[\r\n]+/);
									
			lines.forEach(function(line){
				allCoords.push(line.split(','));
			});

			var bounds = new google.maps.LatLngBounds();

			allCoords.forEach(function(coords){
				bounds.extend(new google.maps.LatLng(coords[0], coords[1]));
			});
			
			map.fitBounds(bounds);
									
			drawLine();
		}
	});
}

My CSV file contents just look like:

8.52411,76.93534000000001
8.524170000000002,76.93523
8.52424,76.93537

So I use the String.split() function to split it into an array based on line endings.
I then loop over that array, and split again, to separate out the latitude and longitude.
At this point I’m also creating a Bounds object, and extending it with each of the coordinates. This is just so the map will be fully zoomed to a level where we can see all our path at once. I’ve differed from my previous example, in that I’m not constantly updating the centre of the map here.

Once the coordinates are loaded, I call drawLine:

function drawLine() {
	route = new google.maps.Polyline({
		path: [],
		geodesic : true,
		strokeColor: '#FF0000',
		strokeOpacity: 0.7,
		strokeWeight: 2,
		editable: false,
		map:map
	});
	
	marker = new google.maps.Marker({
		map: map,
		icon: "http://maps.google.com/mapfiles/ms/micons/blue.png"
	});
	
	for (var i = 0; i < allCoords.length; i++) {
		window.setTimeout(updatePath(coords), 100 * i, allCoords[i]);
	}
}

function updatePath(coords) {
	var latLng = new google.maps.LatLng(coords[0], coords[1]);
	route.getPath().push(latLng);
	marker.setPosition(latLng);
}

So much like before; starting out with an empty Polyline. Looping over all my coordinates, making a call to the updatePath function every 100 milliseconds. Or rather, I simultaneously setup thousands of calls to the function, to take place in 100ms, 200ms, 300ms, etc.

And that’s it! I tested it initially with a few hundred coordinates. I then wrote a little script to generate 70,000 slightly random coordinates. The file’s quite large, 2.5Mb, which might take a few seconds to read via ajax. And that’s a lot of coordinates to animate on the map. I’m adding a new point to the line every 100 milliseconds, which seems pretty fast, but it means it would still take nearly 2 hours to plot all the points. It also seemed to get slightly slower and jerkier the longer it went on.

For the record, here’s the script I used to generate all those coordinates. It’s not very sophisticated, and you could get something faster in any server-side language, but I just use javascript to dump it to a textarea where I then copy it to a text editor.

function getCoords() {
	var max = 70000;
	var start = {lat: 0, lng: 0};
	
	coords = start.lat + ',' + start.lng;
	
	var lat = start.lat;
	var lng = start.lng;
	var randomLat,randomLng, newLat, newLng;
	var plusOrMinusLat = 1;
	
	var maxLat = 85;
	var minLat = -85;
		
	for (var i = 0; i < max; i++) {
		randomLat = Math.random();
		randomLng = Math.random();
		
		newLat = lat + (randomLat * plusOrMinusLat);
		newLng = lng + randomLng;
		
		if (newLat > maxLat) {
			plusOrMinusLat = -1;
			continue;
		} else if (newLat < minLat) {
			plusOrMinusLat = 1;
			continue;
		}
		
		lat = newLat;
		lng = newLng;
		coords += '\n' + lat + ',' + lng;
	}
		
	document.getElementById('coords').value = coords;
}

If the latitude exceeds 90 or -90 degrees, the map stops working. So I check we don’t exceed that limit. I actually reduced that to 85 / -85 degrees; anything beyond that goes off the map with Google Maps. I don’t need to do anything similar with the longitude; if you go beyond 360 degrees, e.g. 365 degrees, it just wraps back round to 5 degrees.

Initially I was just picking random numbers either randomly added or subtracted from the previous coordinates, but that ended up like a giant scribble. So I deliberately just keep adding until I reach the upper limit, then just keep subtracting until I reach the lower limit. The end result is a slightly squiggly pattern like this:

random map

I’d be curious to see how Shivani’s map ends up!

Update – Here’s the map I ended up with using Shivani’s coordinates:
Chrome Legacy Window 24102017 230220

Advertisements

October 29, 2016

Crossword solver

Filed under: Javascript,jQuery,Web — duncan @ 4:12 pm

Here’s a little something I built for myself, with the intention of helping to complete crosswords. It’s quite annoying when you’ve got 80 – 90% through one and with only a few obscure words left to complete.

This lets you:

  • create a blank crossword
  • fill out any letters in it you’ve already got
  • look up words for any you’ve only got some of the letters for

It doesn’t as yet let you save them and re-load.

The front end uses jQuery; server-side it’s using ColdFusion, with a dictionary of 90,000+ words.

Anyway, here it is; let me know any feedback:

http://www.duncancumming.co.uk/crossword/

September 17, 2011

Using jQuery to animate table rows

Filed under: Javascript,jQuery — duncan @ 8:00 am
Tags: , , ,

jQuery has some nice ways to animate content, e.g. sliding or fading it into view. These don’t really work on <tr> or <td> tags though. I had a situation recently where we wanted to add an extra <tr> into a table, and make it slide in. The answer’s very simple (thanks to stackoverflow) – wrap the table content in a div, and add the slide animation to that instead.

So imagine you have a very simple example table like this:

<table>
	<tr id="row1">
		<td><a href="">Show another row</a></td>
	</tr>
</table>

This javascript would then make the second <tr> slide into view.

<script type="text/javascript">
$(document).ready(function() {
	// hidden content to add after the current row
	$('#row1').after("<tr id='row2'><td><div style='display:none'>There's a voice that keeps on calling me. Down the road, that's where I'll always be. Every stop I make, I make a new friend. Can't stay for long, just turn around and I'm gone again. Maybe tomorrow, I'll want to settle down, Until tomorrow, I'll just keep moving on.</div></td></tr>");

	$('#row1 a').click(function() {
		if ($('#row2 div').is(":visible")) {
			// hide the div
			$('#row2 div').slideUp(700);
			
		} else {
			// slide the div into view
			$('#row2 div').slideDown(700);
		}
		
		// prevent the click on the link from propagating
		return false;
	});
});
</script>

Of course it gets more complicated if you have more than one <td>. For example:

<table>
	<tr id="row1">
		<td>A</td>
		<td>One</td>
		<td>Red</td>
		<td>Apple</td>
		<td>$0.99</td>
		<td><a href="">Show row 2</a></td>
	</tr>
</table>

In that case you can use the .wrapInner function to add a hidden div to all the <td> tags.

<script type="text/javascript">
$(document).ready(function() {
	// content to add after the current row
	var $row2 =	$('<tr id="row2">' +
					'<td>B</td>' +
					'<td>Two</td>' +
					'<td>Yellow</td>' +
					'<td>Banana</td>' +
					'<td>$1.23</td>' +
					'<td> </td>' +
				'</tr>');
	
	// add hidden divs around the content of all the TD tags
	$row2.find('td').wrapInner('<div style="display:none" />');
	
	// add this row after the first row
	$('#row1').after($row2);

	$('#row1 a').click(function() {
		if ($('#row2 div').is(":visible")) {
			// hide the div
			$('#row2 div').slideUp(700);
			
			// update link text
			$('#row1 a').text('show row 2');
			
		} else {
			// slide the div into view
			$('#row2 div').slideDown(700);
			
			// update link text
			$('#row1 a').text('hide row 2');
		}
		
		// prevent the click on the link from propagating
		return false;
	});
});
</script>

Blog at WordPress.com.