Free map services for environmental data in Europe

imageThis is just a note for a little gem I just discovered. The European Environmental Agency (EEA) has made available a set of map services for environmental data. Coming under the interesting name of Discomap (??) this free service is described as:

The EEA provides geographic information system (GIS) application programming interfaces (APIs) to obtain a wide range of environmental data for Europe, and helps users create their own map services. Map services available from Discomap are freely available for reuse. EEA content can be integrated in many different ways by developers or by end users who wish to combine EEA’s information with their own or other public map services (mash-ups).

You can find it at http://discomap.eea.europa.eu/

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>

Address Geocoding in .NET using the Bing Maps REST Services API

I came across this excellent post by Jatin Kacha on how to call the Google Map WebService API from asp.net and though to change it slightly to instead call the Bing Maps REST service.

The main changes made on Jatin’s code is the calling URL and the way the returned XML is handled due to the differences between Google’s and Bing’s services plus a small change when retrieving the Lat/Lon values to cater for non-english number formats.

All you need to do is create a class file and copy/paste the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Net;
using System.IO;
using System.Configuration;

namespace Gaiocorp.Geocoding
{
    /// Resolve addresses into latitude/longitude coordinates using Google MAP API webservices
    public static class BingGeocoder
    {
        private static string _BingMapsKey = ConfigurationManager.AppSettings["BingMapsKey"];
        /// Bing Maps Geocoder
        /// Url request to
        ///http://dev.virtualearth.net/REST/v1/Locations?countryRegion=countryRegion&adminDistrict=adminDistrict&locality=locality&postalCode=postalCode&addressLine=addressLine&key=BingMapsKey;
        public static BingGeolocation? ResolveAddress(string query)
        {
            if (string.IsNullOrEmpty(_BingMapsKey))
            {
                _BingMapsKey = System.Configuration.ConfigurationManager.AppSettings["BingMapsKey"];
            }
            string url = "http://dev.virtualearth.net/REST/v1/Locations?o=xml&q={0}&key=" + _BingMapsKey;
            url = String.Format(url, query);
            XmlNode coords = null;
            try
            {
                string xmlString = GetUrl(url);
                XmlDocument xd = new XmlDocument();
                xd.LoadXml(xmlString);
                XmlNamespaceManager xnm = new XmlNamespaceManager(xd.NameTable);
                coords = xd.GetElementsByTagName("Point")[0];
             }

             catch { }
             BingGeolocation? gl = null;
             if (coords != null & coords.HasChildNodes)
             {
                string Lat = coords.ChildNodes[0].InnerText;
                string Lon = coords.ChildNodes[1].InnerText;
                gl = new BingGeolocation(Convert.ToDecimal(Lat, System.Globalization.CultureInfo.InvariantCulture), Convert.ToDecimal(Lon, System.Globalization.CultureInfo.InvariantCulture));
             }
             return gl;
         }

        ///<summary>
        /// Retrieve a Url via WebClient
        /// 
        public static string GetUrl(string url)
        {
            string result = string.Empty;
            System.Net.WebClient Client = new WebClient();
            using (Stream strm = Client.OpenRead(url))
            {
                StreamReader sr = new StreamReader(strm);
                result = sr.ReadToEnd();
            }

            return result;
        }

        /// <summary>
        /// Returns the Lat
        /// </summary>
        /// <param name="address"></param>
        /// <param name="city"></param>
        /// <param name="state"></param>
        /// <param name="postcode"></param>
        /// <param name="country"></param>
        /// <returns></returns>
        public static BingGeolocation? ResolveAddress(string address, string city, string state, string postcode, string country)
        {
            return ResolveAddress("&addressLine=" + address + "&locality=" + city + "&adminDistrict=" + state + "&postalCode=" + postcode + "&countryRegion=" + country);
        }

    }

    public struct BingGeolocation
    {
        public decimal Lat;

        public decimal Lon;

        public BingGeolocation(decimal lat, decimal lon)
        {
            Lat = lat;
            Lon = lon;
        }

        public override string ToString()
        {
            return "Latitude: " + Lat.ToString() + " Longitude: " + Lon.ToString();
        }
        public string ToQueryString()
        {
            return "+to:" + Lat + "%2B" + Lon;
        }
    }
}

And there you have it. You could then call the function in the following manner, say from a button click. The only potential problem may be to parse correctly the input address since the Bing Maps API requires all address components (street, city,zip etc) to be provided separately.

protected void Button4_Click(object sender, EventArgs e) //Get latlon using Bing
        {
            if (txbAddress.Text.Trim() != string.Empty)
            {
               BingGeolocation? bl= BingGeocoder.ResolveAddress(txbAddress.Text.Split(',')[0].Trim(), txbAddress.Text.Split(',')[1].Trim(), string.Empty, string.Empty, string.Empty);
               if (bl != null)
               {
                   txbBing.Text = "Lat: " + bl.Value.Lat.ToString() + " Lon:" + bl.Value.Lon;
               }
            }
        }

You could also easily extend it to also add reverse address geocoding or routing. For more information you can check the Bing Maps REST Services API Reference.

Enjoy!