The Surly Admin

Father, husband, IT Pro, cancer survivor

More With Google Maps

Had a great time creating the Out-LastEarthquake.ps1 script, but let’s face it, other than it’s cool factor it doesn’t have much use.  But what we do have is the framework for the ability to place markers on a Google Map, and there are definitely some uses for that.  I created a proof of concept script that shows just what you could do.

Longitude and Latitude

The great thing about the Last Earthquake script is the data feed we had already had the longitude and latitude data in it, but to have any useful application the more likely data we’re going to have is address.  So we need to figure out a way of translating address information to longitude and latitude.  I really wanted to use Google for this, because one thing I’ve learned over the years is scripts often outlast the time you expect them to.  That means if you are using an outside source you want it to be around for a long time.  There are a lot of great websites out there, but so many of them just don’t last and I wanted to make sure this one does.  Besides, we’re using the Google Maps API so it just makes sense to keep it in the house.

A quick Google search and I locate inside the Google Maps API a simple feed that will do this translation for you without too much trouble!  It’s called Geocoding (translating an Address to coordinates) and is pretty easy to use.  First, let’s test with this address:

4 Yawkey Way
Boston,  MA
02215

Now, no peeking!  To find out what this is you have to use your own script (or the one below)!  First thing is we need to remove the comma’s and replace the space’s with the “+” symbol.  For this proof of concept I just omitted the comma from the data, and I get to use a new method of inputting data into a script that I ran across a couple of weeks ago.  But first, let’s do some Geocoding and see what we get!

geocodeIn this example I pulled the data into $Geocode and then went exploring.  Everything’s under the Results property, including a very useful property called formatted_address but chasing down the geometry property I eventually found the longitude and latitude data we’re looking for!  And that’s really all there is, isn’t it?  The rest is just window dressing, such as wrapping this in a loop, putting the actual HTML code (taken from the Last Earthquake script) and we’re all set.  We now have the ability to create web pages that will have a map on it and points of our choosing all plotted out.

Data Input

So let’s get some test data and start playing.  There will come a time that you want to use some test data, but you may not want to create a separate CSV file and input it every time.  It’s just a pain and your test folder ends up cluttered with tons of miscellaneous files.  Admittedly, there are worse problems in the world but here’s a technique for getting data into your script without that extra file.

It starts with a here-string that you format like a CSV (including the header row) and then use the ConvertFrom-CSV to convert that here-string into a collection of objects.  Here’s the example I used for this script:


$Data = @"
Place,Address,Item,Qty,Tot
Gillette Stadium,1 Patriot Pl Foxborough MA 02035,Green Frogs,550,1100
Fenway Park,4 Yawkey Way Boston MA 02215,Yellow Toads,100,5000
Boston Garden,100 Legends Way Boston MA 02114,Blue Dragons,5,10000
"@
$Locations = ForEach ($Addr in ($Data | ConvertFrom-Csv ))

As a proof of concept I added some labels and some other data just to show how things can be done.  In most scripts you’ll see a line like $MyData = $Data | ConvertFrom-CSV and that’s fine, as $MyData will now be a collection of objects with our data in it but I like to cut out any variables I don’t really need.  I only need to run through this data once so why keep a variable with the data in it for the entire lifetime of the script?!

Now we need to loop through that data and create our PSCustomObject with what we need in it:


$Locations = ForEach ($Addr in ($Data | ConvertFrom-Csv ))
{ $Addr.Address = $Addr.Address -replace " ","+"
$Location = Invoke-RestMethod -uri "http://maps.googleapis.com/maps/api/geocode/json?address=$($Addr.Address)&sensor=false"
[PSCustomObject]@{
Place = $Addr.Place
Location = $Location.Results.Formatted_Address
Lat = $Location.Results.Geometry.Location.Lat
Lng = $Location.Results.Geometry.Location.Lng
Item = $Addr.Item
Qty = $Addr.Qty
Tot = $Addr.Tot
}
}

After that we’ll need to create the text for the JavaScript array, just like in the Last Earthquake script (in fact, it’s almost exactly the same so I won’t show it again).  But I have more data this time, and I wanted to have it displayed all the time.  I’ve mentioned it before, but my HTML-fu is pretty basic, so for those HTML guru’s out there, please don’t laugh at me!  But the first thing I do is create a table, with 2 cells in it.  The left cell is for our Google Map and the right will display our data.


<table>
<td id="map-canvas"></td>
<td><b><ol>$TableInfo</ol></b></td>
</table>
</body>

view raw

MapCoord.html

hosted with ❤ by GitHub

I also put the second set of data in an ordered list so it will be numbered (you’ll see why soon).  So now I just need to create the $TableInfo string to have the data in it we need:


ForEach ($Location in $Locations)
{ $TableInfo += "<li><p>$($Location.Place)<br>Item: $($Location.Item)<br>Qty: $($Location.Qty)<br>Tot: `$$($Location.Tot)</p></li>"
}

Pretty simple really, each <li> in the ordered list represents a record in our data.  I discovered that if I wrap all of my data in a <p></p> and then use <br> to do new lines within it it will give me new lines without 1.5 line spacing that HTML always uses!  May not sound like much but I’ve been trying to figure that out for along time.

Numbered Markers

So we have our map with all of the standard markers on it, and a numbered list to the right with our data on it.  Wouldn’t it be nice if we could create markers with numbers on them so you could correlate the data with the placement on the map?  Turns out, Google has that built into the API as well, in fact you can insert just about any data into this!


var image = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=&#39; + (i + 1) + '|FF0000|000000';
marker = new google.maps.Marker({
position: position,
map: map,
title: markers[i][0] + '\n' + markers[i][1],
icon: image
});

view raw

MapCoord2.js

hosted with ❤ by GitHub

The image variable (line 1) simply points to the place where the image can be found, which is a dynamic image created by Google based on the data you pass to it.  So I take advantage of the loop I’m already using to place all the markers to feed the variable into the image creation API.  Easy peesy!

The final output is a simple map with coordinates to the left, data to the right.  Imagine a display screen in your Customer Service area or warehouse showing the last 10 customers shipped to?  Or maybe you have drivers that you want to show people where they’re going today?  Maybe you’re a service provider and want to show a map of all the clients being visited today with the times on the right?  For these kinds of visualizations this script would work very well.  Of course, you’d have to work out where the data is coming from and what format it’s in but you can see where we’re going with this one.

Here’s the full proof of concept for those who are interested:

MapCoord-Output


$Data = @"
Place,Address,Item,Qty,Tot
Gillette Stadium,1 Patriot Pl Foxborough MA 02035,Green Frogs,550,1100
Fenway Park,4 Yawkey Way Boston MA 02215,Yellow Toads,100,5000
Boston Garden,100 Legends Way Boston MA 02114,Blue Dragons,5,10000
"@
$Path = Split-path $MyInvocation.MyCommand.Definition
$Locations = ForEach ($Addr in ($Data | ConvertFrom-Csv ))
{ $Addr.Address = $Addr.Address -replace " ","+"
$Location = Invoke-RestMethod -uri "http://maps.googleapis.com/maps/api/geocode/json?address=$($Addr.Address)&sensor=false&quot;
[PSCustomObject]@{
Place = $Addr.Place
Location = $Location.Results.Formatted_Address
Lat = $Location.Results.Geometry.Location.Lat
Lng = $Location.Results.Geometry.Location.Lng
Item = $Addr.Item
Qty = $Addr.Qty
Tot = $Addr.Tot
}
}
$Markers = "var markers = [`n"
ForEach ($Num in (0..($Locations.Count – 1)))
{ $Location = $Locations[$Num].Location.Replace("`'","\'")
$Markers += " ['$($Locations[$Num].Place)','$Location',$($Locations[$Num].Lat),$($Locations[$Num].Lng)]"
If ($Num -lt ($Locations.Count – 1))
{ $Markers += ",`n"
}
}
$Markers += "`n ];`n"
ForEach ($Location in $Locations)
{ $TableInfo += "<li><p>$($Location.Place)<br>Item: $($Location.Item)<br>Qty: $($Location.Qty)<br>Tot: `$$($Location.Tot)</p></li>"
}
$HTML = @"
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Address Mapper</title>
<style>
html, body {
margin: 0px;
padding: 0px
}
#map-canvas {
height: 500px;
width: 800px;
margin: 0px;
padding: 0px
}
td {
vertical-align: top;
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script&gt;
<script>
function initialize() {
$Markers
var bounds = new google.maps.LatLngBounds();
var mapOptions = {
mapTypeId: google.maps.MapTypeId.HYBRID,
}
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
for( i = 0; i < markers.length; i++ ) {
var position = new google.maps.LatLng(markers[i][2], markers[i][3]);
bounds.extend(position);
var image = 'http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=&#39; + (i + 1) + '|FF0000|000000';
marker = new google.maps.Marker({
position: position,
map: map,
title: markers[i][0] + '\n' + markers[i][1],
icon: image
});
}
if (markers.length > 1) {
map.fitBounds(bounds);
}
else {
map.setCenter(new google.maps.LatLng(markers[0][1],markers[0][2]));
map.setZoom(4);
}
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<table>
<td id="map-canvas"></td>
<td><b><ol>$TableInfo</ol></b></td>
</table>
</body>
</html>
"@
Write-Verbose "$(Get-Date): Saving file…"
Try {
$HTML | Out-File $Path\AddrMap.html -Encoding ASCII -ErrorAction Stop
}
Catch {
Write-Warning "Unable to save HTML at $Path\AddrMap.html because $($Error[0])"
Exit
}
& $Path\AddrMap.html

January 7, 2014 - Posted by | PowerShell | , ,

11 Comments »

  1. […] post, I’m going to talk about how this project morphed into something else!  A type of project I’ve been wanting to do before but never really got around to it but I […]

    Pingback by Creating Google Maps with PowerShell « The Surly Admin | January 10, 2014 | Reply

  2. […]  I created a simple CSV type here-string and used ConvertFrom-CSV to make objects out of it (a handy way of quickly creating a large array of objects).  Added the Get-SavedCredential function and had a working script.  Then it […]

    Pingback by Parameter Sets « The Surly Admin | February 10, 2014 | Reply

  3. Martin, this looks awesome, in my case I only get it to work in IE8 Compatibility mode, what can be done to get the code to generate an HTML file that will support IE 9+

    Comment by Murray Wall | March 13, 2014 | Reply

    • Typical IE! Have you tried 10? I did all my testing on Chrome and since it was really just a demonstration I didn’t check for browser compatibility. For being such a “common” language there is an amazing level of incompatibility between browsers! I feel for our web developers!

      Comment by Martin9700 | March 13, 2014 | Reply

  4. This is a great script and tutorial. Thank you very much for taking the time to post this!

    Comment by JLo | April 11, 2014 | Reply

  5. I write only this
    $Data = @”
    Place,Address
    MyHome, 175 carrer de Provenza Barcelona 08036
    “@
    and I cannot display the map even when in the html page, I can see my place coordinates that agree with my GPS

    Comment by Emilio Mari | April 27, 2014 | Reply

    • Remember that the script is just a proof of concept. I added the other 3 fields that this script needs to your data and it worked great for me.

      $Data = @”
      Place,Address,Item,Qty,Tot
      MyHome, 175 carrer de Provenza Barcelona 08036,Red Dinosaur’s,1000,80
      Gillette Stadium,1 Patriot Pl Foxborough MA 02035,Green Frogs,550,1100
      Fenway Park,4 Yawkey Way Boston MA 02215,Yellow Toads,100,5000
      Boston Garden,100 Legends Way Boston MA 02114,Blue Dragons,5,10000
      “@

      You forgot to put in the Item, Qty, and Tot numbers. I just threw some dummy numbers in there and ran the script with this data and can see you just off of Carrer d’Aribau 🙂

      Comment by Martin9700 | April 27, 2014 | Reply

  6. Thanks a lot Martin. It works for me too!!. I like this kind of programs. As a former Captain of the Spanish Merchand F. and Air Traffic Controller and Pilot, I like all this kind of programs. I even programmed with Visual Basis and Map Objects how to move by car inside the airport taxiways with fog and worked perfectly. Now retired, I just start practicing PowerShell, just to have fun but following to you and Bob McCoy it is a pleasure. The trick here was how to write my address correctly so as to be plotted on map as we always write Provenza 175 and 08036 Barcelona. Maybe it works too. I will check. Kind regards Emilio

    Comment by Emilio Mari | April 27, 2014 | Reply

  7. Martin, I change this and also works very well. I wanted to experiment and I got it. Regards
    $Data = @”
    Place,Address
    OtraCasa, 242 carrer de Provenza Barcelona 08036
    MiCasa, 167 carrer de Provenza Barcelona 08036
    “@

    [PSCustomObject]@{
    Place = $Addr.Place
    Location = $Location.Results.Formatted_Address
    Lat = $Location.Results.Geometry.Location.Lat
    Lng = $Location.Results.Geometry.Location.Lng
    #Item = $Addr.Item
    #Qty = $Addr.Qty
    #Tot = $Addr.Tot

    ForEach ($Location in $Locations)
    { $TableInfo += “$($Location.Place)Latitud__:$($Location.Lat)Longitud :$($Location.Lng)”
    }

    Comment by Emilio Mari | April 27, 2014 | Reply

  8. One of the ways that may interrupt this code is when google maps returns Multiple addresses for the one you have provided(Plug in your address to google maps manually and you will see this), when this happens the map does not show correctly. One solution, though not always ideal takes the first result from google instead of leaving an array of items
    Location = $Location.Results.Formatted_Address|Select -first 1
    Lat = $Location.Results.Geometry.Location.Lat|Select -first 1
    Lng = $Location.Results.Geometry.Location.Lng|Select -first 1
    Adding the |Select -first 1 solves this nicely
    Before the code we see we have 2 locations (one is with a West, one isnt)
    Place : Site 8
    Location : {110 Souris Avenue, Weyburn, SK S4H 2Z9, Canada, 110 Souris Avenue, Weyburn, SK S4H 2Z8, Canada}
    Lat : {49.6622956, 49.6613164}
    Lng : {-103.8560219, -103.8558935}
    After the code, Powershell just grabs the first entry
    Place : Site 8
    Location : 110 Souris Avenue, Weyburn, SK S4H 2Z9, Canada
    Lat : 49.6622956
    Lng : -103.8560219
    This made my mapping and pulling in data work VERY nicely

    Comment by Murray Wall | May 23, 2014 | Reply

    • Great point. I’ve always assumed that I’d be giving Google enough information (address, city state zip, country, etc) that that wouldn’t happen.

      Comment by Martin9700 | May 23, 2014 | Reply


Leave a reply to Murray Wall Cancel reply