Wednesday, August 1, 2012

Adding HTML content over a Google Maps iframe



I am currently working on a social network web app that uses a Google Maps iframe that needs to be located under a layer of HTML content (avatar pictures, text, new controls for the Google Maps iframe src, etc.). I had no trouble displaying HTML content over it, and making it move in sync with the Google Maps content using the custom controls. I tested with a few cities around mine (Acton Vale, Qc), and everything worked fine. However, a bit later in my development, I tested with a farther city (Edmonton, Alberta), and noticed that the vertical value changed and my avatar was no more synched accurately with the map! Uhm. What would cause that? I implemented other maps systems, like Bing Maps, Yahoo! Maps, OpenLayers, MapQuest, and all behaved in the very same way in the same conditions.


So I went on Google Maps website directly. I repeated the experience, outside of an iframe. I noticed that the scale factor (which can be found at bottom-left of the map) was indeed changing. Damn. The whole project's future was now compromised, because I didn't know the Google Maps API enough to make the whole app within that API, and I absolutely needed my avatar to always remain attached to the exact place (coordinates) the user dropped it. With a lot of research over Google Search, I finally understood that there exists a correlation with the displayed scale factor and the target point's latitude on the map. This is because Google Maps, as well as all others I tested, are in fact using the Mercator projection system, and that cannot be changed. After some reading about Mercator projection on Wikipedia, I learned that there is important distortion towards both poles when the world map is unwrapped down to a 2D plane (like a paper map) because our planet is, guess what, a sphere.






Ref-Wikipedia.org: "(...) the Mercator projection distorts the size and shape of large objects, as the scale increases from the Equator to the poles, where it becomes infinite."


From this point, I also learned that:


  • Greenland takes as much space on the map as Africa, when in reality Africa's area is 14 times greater and Greenland's is comparable to Algeria's alone.
  • Alaska takes as much area on the map as Brazil, when Brazil's area is nearly 5 times that of Alaska.
  • Finland appears with a greater north-south extent than India, although India's is greater.
  • Antarctica appears as the biggest continent, although it is actually the fifth in terms of area.

Ref-Wikipedia.org: "Although the Mercator projection is still used commonly for navigation, due to its unique properties, cartographers agree that it is not suited to general reference world maps due to its distortion of land area. Mercator himself used the equal-area sinusoidal projection to show relative areas. As a result of these criticisms, modern atlases no longer use the Mercator projection for world maps or for areas distant from the equator, preferring other cylindrical projections, or forms of equal-area projection. The Mercator projection is still commonly used for areas near the equator, however, where distortion is minimal."

It now made a lot of sense that my HTML layer also needed to follow the Mercator projection algorythm.
But while it may look simple to find, this is actually very complex (because this is not constant) and the way I come to find it -even if I found some hints here and there over Google- is by using my own mathematical logic from what I studied. Logically, I needed to set my default reference point (grid origin) at the same origin as the Mercator projection system, at the Equator level (anywhere at latitude 0.0; longitude is not distorted so it is not important and can be adjusted with fixed integers that depends on the zoom level of the map). This would, theorically, set my relative reference to the same as Google Maps.

   var newlatitude = (any integer, let's say 45.32);
//this variable is actually what it says, a variable. Every time the city is changed, or the overlayed controls are activated (up/down buttons, for latitude), this value is changing. In my case, I get this value by extracting it from the latitude coordinate of the Google Map iframe's src.
   var equator = 117;
//where 117 is representing the offsetTop value of my avatar when the avatar is aligned at latitude 0.0 over the Google Map. You can replace this value in pixels to reflect your avatar's height. Ensure the 0.0 latitude is also centered in the Google Map iframe.
   var degreestoradiansconverter = Math.cos(newlatitude * Math.PI / 180);
//unlike many scientific calculators that already calculate in radians, Javascript always calculate in dregrees, so this formula is used to convert degrees to radians for cosinus. This line returns your latitude in radians in 2.5D space, in relation with Earth's center (where your coffee becomes too roasted).
   var avatarnewoffsettop = Math.round(equator * (1/degreestoradiansconverter));
//this is in fact the real formula. Much shorter than any algebra being illustrated on Wikipedia for the Mercator projection system.
   var allavatars = document.getElementById("allusers").offsetTop - avatarnewoffsettop;
//this line tells the Javascript engine to move the avatar graphic by x pixels relatively to its current offsetTop.
   document.getElementById("allusers").style.top = allavatars + "px";
//this line tells the avatar graphic to move to the proper coordinates.



Furthermore, in order to get the latitude and longitude of any place defined by the user (in an input field with id="city") and to display the proper Google Map in an iframe, I developed this extra bit of code that you must include in your page header:


<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=false"></script><script type="text/javascript">function updateiframesrconcitynamechangebyuser(){
var region = document.getElementById("city").value.replace(/[^a-zA-Z]/gi,'').toUpperCase();
document.getElementById("city").value = region;
//some formatting is good; this ensure all characters are only alpha characters, and that they are converted to uppercase. You are free to not use it, or to modify the regular expression to fit your specific needs.


var geocoder =  new google.maps.Geocoder();
geocoder.geocode( { 'address': region2}, function(results, status) {


if (status == google.maps.GeocoderStatus.OK) {
document.getElementById("lat").value = results[0].geometry.location.lat().toFixed(6);
document.getElementById("long").value = results[0].geometry.location.lng().toFixed(6);
var latx = document.getElementById("lat").value;
var longx = document.getElementById("long").value; 


document.getElementById("map").innerHTML = "<iframe id='mapid' width='480' height='726' frameborder='0' scrolling='no' marginheight='0' marginwidth='0' src='https://maps.google.ca/maps?f=q&amp;source=s_q&amp;hl=fr&amp;geocode=&amp;q=" + region + "&amp;aq=&amp;sll=49.891235,-97.15369&amp;sspn=39.794637,107.138672&amp;ie=UTF8&amp;hq=&amp;hnear=&amp;ll=" + latx + "," + longx + "&amp;spn=0.167769,0.41851&amp;t=m&amp;z=15&amp;output=embed'>
</iframe>";
//this is good for a zoom value of 15. If you change the zoom, you are better grabbing your own iframe src. Google Maps are providing the necessary Link codes; use the link icon at right of the print map icon.
}
else {
alert("Something got wrong " + status);
}
});
}
</script>



Hope this will help some other web developers someday. Enjoy! And don't be frustrated at Google for not providing us an exact representation of the Earth layout; the Mercator projection system is the most precise system when you zoom in at street level (and people use it more at close street-level). Your Garmin GPS most probably runs on the Mercator projection system, too.

What should change, however, is how schools are displaying the planet to their young students, because right now many adults still don't know that Greenland is in fact much much smaller than Australia; it appears so huge on so many atlas maps! We are assuming and percepting, through inaccurate medias, a very inaccurate layout of our mother Earth in terms of geographical proportions from one country to another. The best way to represent any planet, in my opinion, is still to inject or represent accurate data in 3D space instead of unwrapping its texture down to a 2D plane; be it either virtual (Google Earth) or physical (an actual globe).


1 comment: