david hall icon

Blogofile GeoBlogging

Extending earlier principles used within Textpattern to populate your own Google Map with geo-located articles and reciprocal links, here I set out my current method of achieving the same with Blogofile. It actually turned out to much simpler and because Blogofile works with static files, this functions on MobileMe servers where php etc. is a no-no. Javascript is OK though :)

But, before we come to making the map, we have to provide an id for each marker. There is a bug presently with Blogofile not creating a guid (as may be seen if you look in an rss feed where the permalink is used instead of guid). It’s easily fixed though, by putting an id field (along with latitude and longitude) within the YAML header of the actual article you want to geo-locate:
id: 19
lat: -20.85911256083044
lng: 16.640982627868652
(See in the map code below where these come into play.)

There will be many ways people can set this up within their blogging structure, but at the outset, I’ll state that mine is a fullscreen affair, so the usual headers and footers have been taken out of the equation. Thus, I put the .css in the head, because for a full screen look it’s easier than mucking around with your site’s .css file, remove it and put the usual link to your standard .css file if you want that look.

Controller file

Firstly create a new Controller file, say map.py which contains:

from blogofile.cache import bf
def run():
    write_map(bf.posts, bf.util.path_join(bf.config.blog_path), "map.mako")
def write_map(posts, root, template):
    root = root.lstrip("/")
    path = bf.util.path_join(root,"map.html")
    bf.logger.info("Writing map: "+path)
    bf.writer.materialize_template(template, path, {"posts":posts, "root":root})

Map file

and now your map.mako template file:


    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
            <meta name="keywords" content="something about you" />
            <meta name="copyright" content="Copyright (c) Anon" />
            <meta name="generator" content="Blogofile" />
            <meta name="viewport" content="width=device-width" />
            <title>${bf.config.blog_name} map></title>
            <link rel="alternate" type="application/rss+xml" title="${bf.config.blog_name}" href="${bf.util.site_path_helper(bf.config.blog_path,'/feed/index.xml')}" />
    <style type="text/css">
        html, body, #map {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
            font-family: Helvetica, Arial sans-serif;
            font-size: 1em;
            color: #003300;
        }
        h2 {
            font-size:1.3em;
            text-shadow: 1px 2px 5px rgba(0, 0, 0, 0.5);
            text-align: center;
        }
        a {
            text-decoration:none;
            color: #6f8dac;
        }
        a:hover, a:active {
            color:#4b8462;
        }
    </style>
    <script type="text/javascript"  src="http://www.google.com/jsapi?key=your_own_key"></script>
    <script type="text/JavaScript">
            if (location.search.substring(1)) {
                var qs = location.search.substring(1);
                var nv = qs.split('+');
                var url = new Object();
                    for(i = 0; i < nv.length; i++) {
                        eq = nv[i].indexOf('=');
                        url[nv[i].substring(0,eq).toLowerCase()] = unescape(nv[i].substring(eq + 1));
                }
                var lat = parseFloat(url.lat);
                var lng = parseFloat(url.lng);
                var zmn = parseFloat(url.zmn);
                var vars = url.vars;
            }
            else {
                var lat = 42.94033923363181;
                var lng = -2.8125;
                var zmn = 3;
                var map;
            }
            google.load("maps");
            function initialize() {
            var icon = new google.maps.Icon();
                icon.image = 'http://www.yoursite.com/images/pin.png';
                icon.shadow = 'http://www.yoursite.com/images/pinsh.png';
                icon.iconSize = new google.maps.Size(12, 20);
                icon.shadowSize = new google.maps.Size(22, 20);
                icon.iconAnchor = new google.maps.Point(12, 20);
                icon.infoWindowAnchor = new google.maps.Point(10, 5);
            var map = new google.maps.Map2(document.getElementById('map'), {draggableCursor:"crosshair"});
                map.setCenter(new google.maps.LatLng(lat,lng), zmn, G_PHYSICAL_MAP);
                map.addMapType(G_PHYSICAL_MAP);
                map.addMapType(G_SATELLITE_3D_MAP);
                map.setUIToDefault();
            var mapui = map.getDefaultUI();
                map.setUI(mapui);
            var geoXml;
                Identica = new GGeoXml("http://identi.ca/api/statuses/user_timeline/118531.atom");
                map.addOverlay(Identica);
                geoXmlTiree = new GGeoXml("http://www.yoursite.com/tiree.kml");
                map.addOverlay(geoXmlTiree);
                geoXmlNamibia = new GGeoXml("http://www.yoursite.com/namibia.kml");
                map.addOverlay(geoXmlNamibia);
    % for post in posts:
    % if hasattr(post, "lat"):
                var mypoint = new google.maps.LatLng(${post.lat}, ${post.lng});
                var marker${post.id} = new google.maps.Marker((mypoint), icon);
                google.maps.Event.addListener(marker${post.id}, "click", function() {
                marker${post.id}.openInfoWindowHtml('<h2><a href="${bf.config.blog_url}${post.permapath()}">${post.title}</a></h2><h2><a href="/map.html/?lat=${post.lat}+lng=${post.lng}+zmn=15">+</a>  Zoom  <a href="/map.html/?lat=${post.lat}+lng=${post.lng}+zmn=8">-</a></h2>');});
                map.addOverlay(marker${post.id});
    % endif
    % endfor
                google.maps.Event.addListener (map, "moveend", function() {
                    var center = map.getCenter();
                    document.getElementById("message").innerHTML = center.toString();
                });
                var url = getAttribute("url");
        }
    </script>
    </head>
        <body onload="initialize()">
            <div id="map"></div>
                <a href="/" title="home"><img src="http://www.yoursite.com/images/logo.png" alt="home" style="border:0; position: absolute; top: 5px; left: 60px" /></a>
            <div id="message" style="position:absolute; bottom: 37px; left: 3px">Blogofile<img src="http://www.yoursite.com/images/pin.png" alt="red" />/ Identi.ca<img src="http://www.yoursite.com/images/blue.png" alt="blue" />Powered</div>
                <a href="http://www.yoursite.com/georss/index.xml" title="GeoRSS"><img src="http://www.yoursite.com/images/rss.png" alt="georss" style="position:absolute; bottom: 37px; right: 3px"></a>
        </body>
    </html>

Notes

the if hasattr(post, “lat”): argument has to be there to enable the map to only pick-up geotagged articles or it will not load

You may need to alter one or two things:

    path to your feed .xml file
  • adjust the initial zoom and centre coordinates (the first instance of else)
  • insert your own pins and shadows – there’s plenty around the net
  • if you don’t want the crosshairs, remove , {draggableCursor:“crosshair”}
  • my map is set to PHYSICAL at first
  • I’ve left in the section to show how to import an Identi.ca/Twitter feeds and .kml files
  • if you don’t want to use custom icons remove the 7 lines of code starting with var icon = in which case alter this line

var marker${post.id} = new google.maps.Marker((mypoint), icon);

to

var marker${post.id} = new google.maps.Marker(mypoint);

the InfoWindowHTML can be altered for content and zoom levels – perhaps article excerpts – after the map div, are the overlays for a home link, credits (which cannily are replaced by the map centre on a move), pin key and GeoRSS feed link – adjust as you see fit. (You will have to alter the standard Blogofile rss index.xml file to provide the geo information – I’ll post later about that)

Article link

This needs inserting somewhere in the Article meta – in the post.mako file – it actually only focusses onto the map where the marker has been placed by the map code. Thus, you may manually make links for the body of your posts using any lat, long and zoom values which are parsed by the map code to focus on any bit of the map.

% if hasattr(post, "lat"):
  <a href="http://www.yoursite.com/map.html/?lat=${post.lat}+lng=${post.lng}+zmn=13" title="location on my Map"><img src="http://www.yoursite.com/images/geotag.png" alt="geotag" /></a>
% endif

I have of course used one of our for the link ;)

2010-07-04