So how much does Greek Local Government really spend? Find out on an interactive map

diavgeia_maps_enAnd where else would a better place be, you may ask. If you head over to Diavgeia Spending Maps and after you hit the “It’s all Greek to me” button (Aren’t I a clever boy – get the pun?) you should be able to query spending data for every municipality in Greece for a time period of your choosing. Make sure to check the FAQ first.

Data is coming from the Greek Government’s “Transparency  Portal” (“Diavgeia” stands for transparency in Greek)

This has been my toy project for a bit now trying to work on it on my spare time. This is the first release. Do have a play and let me know what you think. If you want more details on how it was done, read on.

The background

Under the Greek Transparency Program initiative, beginning October 1st, 2010, all government institutions are obliged to upload their acts and decisions on the “Transparency Portal”. In fact, NO administrative act or decision is valid unless published on this portal. More info here.

challenge_accepted_barneyThe good folks at the Ministry of Administrative Reform and e-Governance who run this portal, have also provided an API so external apps can use their data. Now, although there were some external sites utilizing the API, none of them had any maps. So I raised to the challenge. In a nutshell, the app makes subsequent calls to the API, one for each municipality, for decisions and acts relating to spending data and simply adds them together. The result is the total amount spent and its the value used to color the map.

There are two -or three very important caveats here – at least in this first version:

The API will only return a maximum of 500 results per query. This means that if -for the date range you selected- the municipality has issued more than 500 decisions relating to spending, the total amount shown for this municipality will be incorrect. However, one can get around it by selecting shorter time periods. In any case the application will report on the number of results returned as well as the number of actual results for each query so you should be able to know if all data was included.

The second is more serious, and in all probability due to my ignorance of the inner workings and processes within the municipalities and their interaction with the Transparency Portal.

To begin with, some of the decisions have a zero amount next to them. And that’s despite the fact that in some cases, even the title of the particular decision states the amount (which of course is not zero). At first I thought it was some sort of bug in my code, but no, it WAS the data stupid. I haven’t got the foggiest why this is.

On the other hand, I noticed that some decisions are duplicated. Exact same title and amount but different decision id. Again, not a clue.

I do intend to try and contact the Diavgeia people to find out but till then -and its plastered all over the site- do use the results with EXTREME caution. Results and total amounts are indicative and should be used for informational purposes only!

The technical bits

Diavgeia Spending Maps was developed using Leaflet (based on the Interactive Choropleth Map example) and jQuery. Controls were based on the JQuery UI tools. Data was returned in Json format. Administrative boundaries were downloaded from geodata.gov.gr as a single shapefile and converted to GeoJson after some generalization to reduce in size.

Greek Version

Storing your last map location in the Bing Maps AJAX control using cookies

This is an example of an easy and clean way of storing the last map location and zoom level for a user. The location is saved in a cookie, so next time the user will open you app the map will zoom to the last used location. And since I much prefer using ASP.NET than plain HTML, this example is implemented in the default ASP.NET Web template using Master Pages.

So here is goes:

The first thing you need to do is create a new ASP.NET Web Site in VS2010 using the Default template. Next open your Default.aspx page.

Add a reference to the Bing Maps control javascript API under the header content:

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>

Next, add a reference to the jQuery script (it should be under your Styles folder:

<script type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>

Remove (if you wish), all the default template text under the DefaultContent and create a <div> which will hold your map, i.e.

<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
   <div id="map"></div>
</asp:Content>

Now create a few of the helper functions within a <script> tag. The first one to use would be a couple of functions to create and read cookie values as per Peter-Paul Koch’s post

function createCookie(name, value, days) {
     if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        var expires = "; expires=" + date.toGMTString();
     }
     else var expires = "";
         document.cookie = name + "=" + value + expires + "; path=/";
}
function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
         }
          return null;
     }

Create the setInitialView() function. This function sets a cookie with a name of ‘LastLocation’ and returns its value. If no location cookie is set, a default location will be used

function setinitialView() {
     if (readCookie('LastLocation') == null) {
         // Default map center
         createCookie('LastLocation', '40.6318' + ':' + '22.94758' + ':' + '18', 30);
     }
     var lastloc = readCookie('LastLocation');
      return lastloc;
}

Create the getMap() function which will display the Bing Maps control.

function GetMap() {
    // Get cookie
    var s = setinitialView();
    var loc = s.split(":");
    var mapSettings = {
         // MapOptions
         credentials: 'YOURBINGMAPSKEYHERE',
          // ViewOptions
          // default to roughly the center of Thessaloniki.
          center: new Microsoft.Maps.Location(loc[0], loc[1]),
          mapTypeId: Microsoft.Maps.MapTypeId.birdseye,
          padding: 1,
          zoom: parseInt(loc[2])
    };
    map = new Microsoft.Maps.Map(document.getElementById("map"), mapSettings);
    // Store a cookie with the current position
    Microsoft.Maps.Events.addHandler(map, 'viewchangeend', mapViewChangeEnd);
}

Note that we have wired up the viewchangeend event to the mapViewChangeEnd function. This event will fire every time we pan and zoom on the map and will set the LastLocation cookie to the current map center and zoom level:

function mapViewChangeEnd(e) {
    var curLocation = map.getCenter();
    var curLat = curLocation.latitude;
    var curLon = curLocation.longitude;
    createCookie('LastLocation', curLat + ':' + curLon + ':' + map.getZoom(), 30);
}

Finally, use a bit of jQuery to load the map when the DOM is fully loaded:

var map = null;
$(document).ready(function () {
    GetMap();
 });

All set! Your aspx page should now look this:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
    <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
     <script type="text/javascript" src="Scripts/jquery-1.4.1.min.js"></script>
    <script type="text/javascript">
        function createCookie(name, value, days) {
            if (days) {
                var date = new Date();
                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                var expires = "; expires=" + date.toGMTString();
            }
            else var expires = "";
            document.cookie = name + "=" + value + expires + "; path=/";
        }
 
        function readCookie(name) {
            var nameEQ = name + "=";
            var ca = document.cookie.split(';');
            for (var i = 0; i < ca.length; i++) {
                var c = ca[i];
                while (c.charAt(0) == ' ') c = c.substring(1, c.length);
                if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
            }
            return null;
        }
        function setinitialView() {
            if (readCookie('LastLocation') == null) {
                // Default map center
                createCookie('LastLocation', '40.6318' + ':' + '22.94758' + ':' + '18', 30);
            }
            var lastloc = readCookie('LastLocation');
            return lastloc;
        }
 
        function mapViewChangeEnd(e) {
            var curLocation = map.getCenter();
            var curLat = curLocation.latitude;
            var curLon = curLocation.longitude;
            createCookie('LastLocation', curLat + ':' + curLon + ':' + map.getZoom(), 30);
        }
        function GetMap() {
            // Get cookie
            var s = setinitialView();
            var loc = s.split(":");
            var mapSettings = {
                // MapOptions
                credentials: 'YOURBINGMAPSKEYHERE',
                // ViewOptions
                // default to roughly the center of Thessaloniki.
                center: new Microsoft.Maps.Location(loc[0], loc[1]),
                mapTypeId: Microsoft.Maps.MapTypeId.birdseye,
                padding: 1,
                zoom: parseInt(loc[2])
            };
            map = new Microsoft.Maps.Map(document.getElementById("map"), mapSettings);
            // Store a cookie with the current position
            Microsoft.Maps.Events.addHandler(map, 'viewchangeend', mapViewChangeEnd);
        }
        var map = null;
        $(document).ready(function () {
            GetMap();
        });
 
    </script>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
   <div id="map"></div>
</asp:Content>