Google Maps, Webcams and Frogans

Back when I was a young lad, just learning about Frogans, I designed a Frogans site that displayed an image from a webcam, even though I had no idea how such a thing could work at the time. So, it is with great pleasure that I present a Frogans site-let called “World Views.”

I call it a “site-let” because I published it as a part of frogans*lab, the accompanying Frogans site for this blog. So, check out World Views on frogans*lab in the Gallery section.

World Views uses a map-based interface to find and view daily images from webcams all around the world.

It’s something of a mashup of APIs from Google (Google Maps Static API, Google Geocoding API) and Webcams.travel.

I wanted to be able to display a navigable map, to display webcam locations on that map and to allow the user to see webcam images by clicking on webcam locations on the map.

The map

The Google Maps Static API outputs a GIF, JPG or PNG image of a map in the size you like and at the location that you designate. All you have to do is load a specifically-formatted URI that contains all of the necessary parameters, for example:

https://maps.googleapis.com/maps/api/staticmap?center=34.11834341,-118.3003935&zoom=10&scale=2&size=250x187&markers=icon:http://frogans-lab.com/frogans-star-lab/mapcam/crosshairs.png|34.11834341,-118.300393&key=xxxxxxxxxxxxxxxxxxxxxxxxxxx

In the case of Frogans, where you cannot load an image into your slide from a remote source, you can use ImageMagick, a software suite that is built into PHP, to copy that image to the home folder of your Frogans site and load it from there.

The static map image provided by Google is not drag-able, nor is it instantly zoom-able as we have come to expect with online maps. That would require JavaScript, which for security reasons, Frogans does not speak. Instead, you click on navigation buttons to load a new image with its new coordinates or zoom.

Map navigation buttons

The API lets you choose your map’s location by longitude and latitude or by the name of the location, such as the city name, street address, etc. I realized that, in order to scroll the map left and right, up and down, I would need to know the longitude and latitude for every image. What’s more, the Webcams.travel API uses longitude and latitude for choosing which webcams to display.

At the same time, I couldn’t expect users to be entering longitude and latitude coordinates. No, they’ll want to enter city names, street addresses or landmarks. So, I’d need a way to start with that and then obtain the longitude and latitude coordinates for navigating the map and placing the webcam locations.

Geocoding

I found a solution in the Google Geocoding API. It finds the longitude and latitude coordinates for specific locations, such as street addresses, city names, etc. This way, a user can enter a location name and the API can produce the corresponding longitude and latitude.

So, if a user enters Alvera Street, Los Angeles, the API will return the longitude of -118.3003935 and the latitude of 34.1184341.

The Google Geocoding API is available through a service called RapidAPI https://rapidapi.com which allows you to choose the language you wish to use to call the API. Here, I used cURL via PHP. To be honest, I don’t know how it works. I only know how to make it work. For example:

$curl = curl_init();
curl_setopt_array($curl, array(
	CURLOPT_URL => CURLOPT_URL => "https://google-maps-geocoding.p.rapidapi.com/geocode/json?language=en&address=".$search, //where $search is the street address, city, etc. entered by the user
	CURLOPT_RETURNTRANSFER => true,
	CURLOPT_FOLLOWLOCATION => true,
	CURLOPT_ENCODING => "",
	CURLOPT_MAXREDIRS => 10,
	CURLOPT_TIMEOUT => 30,
	CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
	CURLOPT_CUSTOMREQUEST => "GET",
	CURLOPT_HTTPHEADER => array(
		"x-rapidapi-host: google-maps-geocoding.p.rapidapi.com",
		"x-rapidapi-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
	),
      )
);

$geocode_response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
	echo "cURL Error #:" . $err;
} 

$response_array=json_decode($geocode_response,true);

$geocode_lng=$response_array["results"][0]["geometry"]["location"]["lng"]; // this is our longitude
$geocode_lat=$response_array["results"][0]["geometry"]["location"]["lat"];  // this is our latittude

The webcams

Webcam data is obtained from the Webcams.travel API. Here I once again used RapidAPI, but I queried via Unirest instead of cURL. This requires keeping a copy of a Unirest library (“Unirest.php”) on my hosting server.

require_once '../../unirest-php/src/Unirest.php';
$response = Unirest\Request::get("https://webcamstravel.p.rapidapi.com/webcams/list/nearby=".$geocode_lat.",".$geocode_lng.",".$radius."?lang=en&show=webcams%3Aimage%2Clocation",
  array(
    "X-RapidAPI-Host" => "webcamstravel.p.rapidapi.com",
    "X-RapidAPI-Key" => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  )
);
$decoded = json_decode(json_encode($response),true);  //this is a trick for converting a StdClass object to an array.

Now I have an array called $decoded containing all sorts of information about the webcams within a radius of $radius around the coordinates of $geocode_lat and $geocode_lng.

Putting it together

Now comes the tricky part. I have a map of a certain zoom (that’s Google Maps talk for the scale of the map) centered at a particular longitude and latitude. I want to place a button on this map for every webcam in the array $decoded.

$decoded contains identifiers, image urls and longitude and latitude coordinates for each webcam in the array. Now, with the longitude and latitude, these webcam locations can be shown in the Google map. All you have to do is add a marker in the URI with the coordinates indicted. But I want to indicate these webcams in FSDL, because I want for them to be buttons. So I need to find the distance pixels of each webcam from the center of the map.

To do this, I have to

  1. determine the distance in longitude and latitude,
  2. convert that to miles or kilometers (I chose kilometers) and
  3. determine the distance in kilometers as pixels.

It was here that I made the unpleasant discovery that, while my map was flat, the Earth is round.

The Earth is round

On a static Google map, the longitude and latitude lines are nice and straight such that the distance between longitude lines always appears to be the same. In reality, longitude lines converge at the North and South poles such that the further to the North or South you go, the shorter the distance between the longitude lines. At the North and South poles, the distance is zero.

Here, Mercator projection is used to project the Earth onto a flat map. In a nutshell, the further away your map view get from the Equator, the bigger the image. The geographical proportions of any local feature is not distorted, but if you look at the map of the whole Earth you see that Antarctica, Alaska and Greenland are huge compared to what they really are on the globe.

c 2011 Daniel R. Strebe

So, let’s take a second look at that to-do list from up above:

  1. determine the distance in longitude and latitude,
  2. convert that to kilometers, taking into account the change in distance between longitudes, depending on the latitude (distance from the Equator) and
  3. determine the distance in kilometers as pixels, taking into account that the difference in the projected horizontal scale on the map, depending on the latitude.

For converting latitude and longitude to kilometers, the formulae are:

latitude to kilometers = latitude distance * 111.320

and

longitude to kilometers = longitude distance *111.320 * cos(camera latitude * M_PI /180)

(M_PI gives you π (pi) in PHP.)

Then, to determine the distance in pixels for kilometers, you will need the latitude of the center of the map and the zoom level of the map:

$pix_per_km=(pow(2,$zoom_level+1))/(156.54304
* cos(deg2rad($geocode_lat)));

That last one I reached with a little bit of trial and error.

So, to get my distances in kilometers and then in pixels:

$num_webcams=count($decoded["body"]["result"]["webcams"]);
$cam_coords=array();
for($i=0;$i<$num_webcams;$i++){
            $my_lat=$decoded["body"]["result"]["webcams"][$i]["location"]["latitude"];
            $my_lng=$decoded["body"]["result"]["webcams"][$i]["location"]["longitude"];
            $markers=$markers."&markers=color:green%7C".$my_lat.",".$my_lng;
            $cam_coords[$i]=array(); //0-lat, 1-lng, 2-diffy in lat, 3-diffx in lon, 4-diffy in km, 5-diffx in km, 6-diffy in pixels, 7-diffx in pixels
            $cam_coords[$i]['cam_lat']=$my_lat;                 //lat
            $cam_coords[$i]['cam_lng']=$my_lng;                 //lng
 
            $cam_coords[$i]['diffy_lat']=$my_lat-$geocode_lat;    //diffy in lat
            $cam_coords[$i]['diffx_lng']=$my_lng-$geocode_lng;    //diffx in lon

            $cam_coords[$i]['diffy_km']=$cam_coords[$i]['diffy_lat']*111.320; //diffy in km
            $cam_coords[$i]['diffx_km']=$cam_coords[$i]['diffx_lng']*111.320*cos($cam_coords[$i]['cam_lat']*M_PI/180); //diffx in km
            $cam_coords[$i]['diffy_px']=$cam_coords[$i]['diffy_km']*$pix_per_km; //diffy in px
            $cam_coords[$i]['diffx_px']=$cam_coords[$i]['diffx_km']*$pix_per_km; //diffx in px

            $cam_coords[$i]['diffy_px']=$cam_coords[$i]['diffy_km']*$pix_per_km; //diffy in px
            $cam_coords[$i]['diffx_px']=$cam_coords[$i]['diffx_km']*$pix_per_km; //diffx in px
}

Putting it together pt.2

With the number of pixels per kilometer, I could place my camera buttons accurately on the map. But watch out! Sometimes several webcams are located at the same coordinates, meaning that their buttons are hidden behind other buttons. And that can present problems in FSDL, since every button must contain a 20×20 pixel area that is visible and clickable. That’s why, for each webcam button, there is also a clickable area on the right side of the slide.

Now that I have buttons for each webcam, You might notice that there are never more than 10 webcams displayed on any one slide. That’s because the API automatically chooses a maximum of 10 webcams.

The image from any one webcam is displayed when you click on its button. Its URL can be found in the $decoded array:

$decoded["body"]["result"]["webcams"][CAMERA_INDEX]['image']['daylight']['preview']

where CAMERA_INDEX is a number from 0-10 indicating the webcam on the slide.

Click on a webcam button and see what it sees.

You might also notice that all of the images are taken during the day, no matter where on the planet they are, meaning that these are not taken in real time, but once per day. You can change that by changing ‘daylight’ to ‘current’.

In conclusion

That’s about all I have to say about this particular site-let, other than this is really just a demo to give you a glimpse of fun, fun possible ways using Frogans slides to display data and taking you across the world.

Be the first to comment

Leave a Reply

Your email address will not be published.


*