Friday, December 10, 2010

Onshoring, the new offshoring

Us software developers have had to live with offshoring for years. And I can understand why companies do it. If software isn’t your raison d'ĂȘtre then why not outsource it, and if you’re going to outsource it, why not send it offshore where you can get probably the work done cheaper?

But there are problems with offshoring. There are the time differences and cultural differences that may make it not go as smoothly as hoped. But I’m not one to think there’s any reason why software developed in another country should be any worse than software produced at home. I’ve dealt with many developers, at home and overseas and there are good and bad developers everywhere.

But now things have changed again. The UK government introduced a scheme, the intra company transfer visa, which is intended to bring in people to the UK to fill skills gaps that can’t be filled by the UK population. But big business have found some sweet loopholes in this scheme that means they can pay pretty much minimum wage to their developers whilst claiming back their expenses and not paying NI for them. I can understand why this is attractive to companies, it saves them money after all, and why should they care about the greater good? I doubt Henry Ford would survive very long in the modern world. But I can’t see it’s particularly good for the country as a whole, with lower tax revenue and higher unemployment being the obvious outcome. So what the hell was the government thinking of? I’m quite happy to compete with anybody on a level playing field, but when the playing field has been slanted like this, I’m pretty baffled about what’s going on.

Yesterday we had students smashing up the streets because they think it’s unfair that they are going to be in massive debt after they complete their degrees. In the past, graduates would be the people going into these relatively low paid jobs. How are they going to react when they get out of university to find there are no jobs available for them, due to this wonderful scheme?

Monday, November 22, 2010

Adding a simple website hit counter using the Google Analytics API

Last time, I built a simple app to get the number of visitors to a site using the Google Analytics API. So the obvious next step is to get that data displayed on a web page. The simplest way to do this would be to add some server-side code to an ASP.NET page. But if you’ve run the code in the previous blog entry, you’ll notice it’s pretty slow, generally taking over a second to get the data back from Google. Adding a second to every page load isn’t such a good idea, so this is my slightly more complicated solution, which loads up the hit count via AJAX.

First we need a generic handler, looking something like this

<%@ WebHandler Language="C#" Class="visitorCounter" %>

using System;
using System.Web;
using Google.GData.Analytics;

public class visitorCounter : IHttpHandler 
{
  public void ProcessRequest (HttpContext context) 
  {
    AccountQuery feedQuery = new AccountQuery();
    AnalyticsService service = new AnalyticsService("DoogalAnalytics");
    service.setUserCredentials("email", "password");
    DataQuery pageViewQuery = new DataQuery("https://www.google.com/analytics/feeds/data");
    pageViewQuery.Ids = "ga:103173";
    pageViewQuery.Metrics = "ga:visits";
    pageViewQuery.GAStartDate = DateTime.Now.AddMonths(-1).ToString("yyyy-MM-dd");
    pageViewQuery.GAEndDate = DateTime.Now.ToString("yyyy-MM-dd");
    DataEntry pvEntry = service.Query(pageViewQuery).Entries[0] as DataEntry;

    context.Response.ContentType = "text/plain";
    context.Response.Write(pvEntry.Metrics[0].Value);
  }
 
  public bool IsReusable {
    get {
       return false;
    }
  }
}

Then we just need a bit of jQuery magic to show it on the page.

    <p>
      Visitors this month: <span id="visitorCount"></span>
      <script type="text/javascript">
        $(document).ready(function () {
          $.get('visitorCounter.ashx', function (data) {
            $('#visitorCount').html(data);
          });
        });

      </script>
    </p>

With a little work this could probably be wrapped up as an ASP.NET control, but I’ll leave that as an exercise for the reader.

Monday, November 15, 2010

Simple Google Analytics .NET API usage

So if you want to create your own version of Embedded analytics, the first thing to do is figure out how to use the Google Analytics API. If you’re wanting to use the .NET API, then this little snippet might help you get started. It loops through all the registered sites and shows the number of visits to each site over the last month.

  class Program
  {
    static void Main(string[] args)
    {
      AccountQuery feedQuery = new AccountQuery();
      AnalyticsService service = new AnalyticsService("DoogalAnalytics");
      service.setUserCredentials("email", "password");
      foreach (AccountEntry entry in service.Query(feedQuery).Entries)
      {
        Console.WriteLine(entry.Title.Text);

        DataQuery pageViewQuery = new DataQuery("https://www.google.com/analytics/feeds/data");
        pageViewQuery.Ids = entry.ProfileId.Value;
        pageViewQuery.Metrics = "ga:visits";
        pageViewQuery.GAStartDate = DateTime.Now.AddMonths(-1).ToString("yyyy-MM-dd");
        pageViewQuery.GAEndDate = DateTime.Now.ToString("yyyy-MM-dd");
        foreach (DataEntry pvEntry in service.Query(pageViewQuery).Entries)
        {
          Console.WriteLine(pvEntry.Metrics[0].Value);
        }

      }
      Console.ReadLine();
    }
  }

Sunday, November 14, 2010

Embedded Analytics

I’ve been working on a website for somebody and they wanted to show some information about the number of visitors to the site on the site itself. Since we already had Google Analytics installed, the obvious solution was something that hooked into that. I knew there was a Google Analytics API which I could hook into, but being lazy I hoped somebody else had already implemented something for me.

Luckily for me, there is a company called Embedded Analytics that do provide this kind of service. There are various free tools available although it does cost money if you want to use it for multiple sites. But the free service was good enough for my current needs.

Setup was simple, although you do obviously need to let them have access to your Google Analytics data. If you don’t feel comfortable with that then this isn’t the service for you. If you are OK with that, then you can quickly knock together a chart showing the number of visitors or page views in the recent past. Another nice little tool is the ability to get email alerts when somebody has linked to your site.

And now I think I might want to add this to other sites, but it’s going to start costing money to use on more than one site. When my laziness is confronted by my tight fistedness, my tight fistedness always wins out. So now I have to go back to that API and figure out how to do all this and more myself.

Thursday, November 11, 2010

Creating a valid file name in C#

This was new to me so I thought I’d share. If you want to generate a file name based on some text, but that text may contain characters that aren’t allowed in file names, what to do? In the past, I’ve fumbled around, replacing invalid characters as bugs have presented themselves. But there is a much more robust way, once I noticed the Path.GetInvalidFileNameChars method. That led to this very simple method.

    private string ReplaceInvalidFileNameChars(string fileName)
    {
      foreach (char invalidChar in Path.GetInvalidFileNameChars())
      {
        fileName = fileName.Replace(invalidChar, '_');
      }

      return fileName;
    }

Sunday, November 07, 2010

Styled maps–Showing urban areas

Styled Google Maps but I haven’t found a use for them up until now. But I had a desire to view urban areas in the UK and realised I could do that with styled maps, so here it is.

Loading map...

Tuesday, October 12, 2010

Uploading files and folders to a specific Google Docs folder using the .NET API

The .NET API for Google Docs is a strange beast. First, it has a DocumentsRequest class and a DocumentsService class. Sometimes you’ll need to use one class, sometimes the other and it’s not clear what the distinction is between the two. And I’d assumed the .NET API supported everything you could do with the raw HTTP API and hence I thought uploading a file or folder to a specific folder wasn’t possible. This led to further confusion when I tried to move a folder and it did move but left the original folder behind and I couldn’t figure out how to delete that folder. And as with most things from Google, the documentation is somewhat lacking.

So I eventually figured out I’d have to do a little work myself and came up with a couple of extension methods, one to create a folder and one to upload a file, but both accepting a parent folder.  When I say I needed to do a little work, what I really mean is I used Reflector to see how the current methods worked and employed some copy and paste…

  public static class GoogleDocsExtensions
  {
    public static Document CreateFolder(this DocumentsRequest docsRequest, string folderName, Document parentFolder)
    {
      Document doc = new Document();
      doc.Type = Google.Documents.Document.DocumentType.Folder;
      doc.Title = folderName;
      if (parentFolder == null)
        return docsRequest.Insert<Document>(new Uri(DocumentsListQuery.documentsBaseUri), doc);
      else
      {
        return docsRequest.Insert<Document>(new Uri(parentFolder.AtomEntry.Content.AbsoluteUri), doc);
      }
    }

    public static void UploadFile(this DocumentsService docsService, string file, Document folder)
    {
      FileInfo info = new FileInfo(file);
      using (FileStream input = info.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
      {
        Uri uri = new Uri(folder.AtomEntry.Content.AbsoluteUri);

        string str = info.Extension.ToUpper().Substring(1);
        string contentType = (string)DocumentsService.DocumentTypes[str];
        docsService.Insert(uri, input, contentType, info.Name);
      }
    }
  }

Sunday, October 10, 2010

Installing PHP 5.2 on Windows 7 IIS 7.5

I had a bit of fun upgrading my Windows 7 machine to PHP 5.2 the other day. There’s an installer available that goes most of the way but didn’t quite work for me (there’s also an installer available on www.php.net, I’m not sure how they differ).

The first problem was that my installation of IIS didn’t have the FastCGI module installed. This was pretty easy to rectify, in the IIS administrator go to Modules and then click the ‘Configure Native Modules’ link and then click the ‘Register’ button. The FastCGI module will live somewhere like ‘C:\Windows\System32\inetsrv\iisfcgi.dll’. Then I manually added a handler for PHP files, although if you add the module with the correct ‘FastCgiModule’ name, a re-install should correctly add the handler.

After this was set up, I was able to load up PHP pages but they weren’t being rendered properly, because it looked like the PHP code wasn’t being evaluated and was just getting outputted as plain text. This confused me for a while and I thought the installer hadn’t done its job. But after firing up Fiddler, I saw this header in the response from the server – X-Powered-By: PHP/5.2.14, which suggested the file was getting handed to PHP which was doing something with it. So I guessed there must be a problem somewhere in the PHP config file, php.ini. After searching around in there, I discovered this setting - short_open_tag = Off. This means any PHP files that use <? as their opening tag instead of <?php will not get evaluated, and turning that option on fixed my problems. One other thing to note is that an IIS reset seems to be required for any changes to that config file, since when using FastCGI the PHP executable stays resident in memory rather than restarting for every request.

Tuesday, October 05, 2010

WinForms ProgressBar with text

Progress bar with textSomebody asked how to use my transparent label on top of a ProgressBar control, since it disappeared when the progress bar changed its state. I had a look at it, but the transparent label wasn’t getting any notification of changes, even when I hooked into the WndProc method. So I wondered if it would be possible to sub-class the ProgressBar control and write some text on top of it that way. And that seems to work OK, as shown below.

Update – I’ve fixed the flickering

Update 2 – It now picks up the text colour from the ForeColor property

Update 3 – Should now work on Windows XP

Update 4 – Now lets you specify the font used for the text

using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System;

namespace WinFormControls
{
 
  public class TextProgressBar : ProgressBar
  {
    protected override CreateParams CreateParams
    {
      get
      {
        CreateParams result = base.CreateParams;
        if (Environment.OSVersion.Platform == PlatformID.Win32NT
            && Environment.OSVersion.Version.Major >= 6)
        {
          result.ExStyle |= 0x02000000; // WS_EX_COMPOSITED 
        }

        return result;
      }
    }

    protected override void WndProc(ref Message m)
    {
      base.WndProc(ref m);
      if (m.Msg == 0x000F)
      {
        using (Graphics graphics = CreateGraphics())
        using (SolidBrush brush = new SolidBrush(ForeColor))
        {
          SizeF textSize = graphics.MeasureString(Text, Font);
          graphics.DrawString(Text, Font, brush, (Width - textSize.Width) / 2, (Height - textSize.Height) / 2);
        }
      }
    }

    [EditorBrowsable(EditorBrowsableState.Always)]
    [Browsable(true)]
    public override string Text
    {
      get
      {
        return base.Text;
      }
      set
      {
        base.Text = value;
        Refresh();
      }
    }

    [EditorBrowsable(EditorBrowsableState.Always)]
    [Browsable(true)]
    public override Font Font
    {
      get
      {
        return base.Font;
      }
      set
      {
        base.Font = value;
        Refresh();
      }
    }
  }
}

Sunday, October 03, 2010

The problems with financial theories

We’re all looking for people to blame for the current mess we are in and apparently there are 38 causes of the current financial crisis. I’m not sure where to get hold of the original report so I’m not sure of what’s on the list but it made me think again about what I learned during my brief time working in the world of high finance a few years ago. I was there for a couple of years, so I'm clearly not hugely knowledgeable but the assumptions made when concocting the financial models used by many investors in the city always seemed a little fishy to me. Here are some of the main assumptions made and the problems I think are associated with them. Almost every financial theory will use one or more of these assumptions.

The market is efficientAll information about an asset is known and priced into the cost of that asset. Peston says this is one of the 38 causes. There are several issues with this theory, my favourite being this paradox – if the market is efficient then there is very little point in trying to play the market, since the market will always be priced accurately so there will be no opportunity to beat it by buying low, selling high. But if there are no players in the market, then it won’t be efficient. Which suggests it can never be perfectly efficient.

Another problem with the efficient market theory can be seen with something like the dot com bubble, where prices bore no relation to their underlying value, how is that sensible? The fact is share prices often bear no relation to the true worth of the company, because people think their value will continue to grow so they will be able to sell them on to a greater fool.

Investors are rational - Given two different assets with the same risk, investors will choose the one with the highest return. Clearly this isn't the case, otherwise bubbles would never happen. Another problem is that even a rational investor may not know the true risk/reward relationship. Shares may fall in value, making their dividend yield look very attractive, but there may be a good reason for this, like the economy is hitting trouble. Finally, some investors are just taking a punt, with no rational thought behind their trading.

Finally, if we decide that not all investors are rational, then we have another reason why the market will not be efficient.

There are no transaction charges – This may be nearly true for large institutional investors but for the man on the street this is very much not true, meaning any kind of investment strategy that involves a lot of trading will incur huge transaction costs, which will eat away at any profits made.

The market is liquid - This assumption basically says if you want to buy or sell, then you can. Whilst this holds true for most shares most of the time, it falls over at the most vital point, when markets are crashing, because everybody is trying to sell and there is nobody willing to buy. It is also not true for quite a few asset classes, as anyone who's tried to sell a house will know.

Normal distribution – A lot of financial theories use the normal distribution for the distribution of volatility/risk. Unfortunately this doesn’t seem to be the case. The black swans seem to occur a lot more frequently than a normal distribution would suggest. For the banks out there, this doesn’t seem to be such a problem, since they take the profits during the good times and get bailed out when those once in a lifetime occurrences strike a little too often.

And it’s the combination of all these flawed assumptions that caused at least some of the problems. They were used to build mathematical models that seemingly abolished risk or certainly incorrectly measured it.

Wednesday, September 29, 2010

IE9 hardware acceleration not working

I downloaded the IE9 beta a week or so ago. Though it looked pretty good, the performance was less than stellar. When I looked at the options dialog, I saw that ‘Use software rendering instead of GPU rendering’ was checked and also disabled, which made unchecking it quite difficult. Apparently it will be disabled if Internet Explorer decides your graphics card isn’t good enough. I’m not exactly sure what the minimum requirements are, although I have heard rumours that the card must support DirectX 11.

Anyway it seems my old Radeon X600 wasn’t up to the job so I purchased a Radeon HD5450. I have no idea if it’s a good graphics card, but it’s pretty cheap and it does support DirectX 11. After installing that, IE9 was still pretty slow. Until I remembered to uncheck that checkbox…

Thursday, September 09, 2010

Structured storage viewer

Before the advent of .NET, Windows development involved a lot of fiddling around with COM interfaces and trying to figure out HRESULT error codes. One prime example of this is the interfaces used to access structure storage. Structured storage is a strange beast itself, providing a way to stitch together composite files, sort of like a seriously complicated version of ZIP files. Of course this was a solution in search of a problem, it turns out ZIP files are pretty much adequate for most situations, which is probably why Office now uses them.

But these technologies have a habit of sticking around (I guess Windows will continue supporting structured storage for the rest of time or until Windows no longer exists, whichever comes sooner). So I was faced with writing some code that had to interact with structured storage. .NET wisely doesn’t have any support for it, so I was faced with dealing with the really horrible COM interfaces. And I was even thinking I’d have to write a little application to let me see inside these composite files, so I’d have a clue what was going on. But fortunately I discovered somebody had already done the job for me, and I finally get to the point of this post. If you need a structured storage viewer, download it from here. http://www.mitec.cz/ssv.html

Sunday, August 22, 2010

Converting a Cursor to a Bitmap in .NET

I’ve been playing around with a little tool I’ve been developing and needed to display a Cursor in a PictureBox. There doesn’t seem to be built-in way to convert a Cursor to a Bitmap, which kind of makes sense since it might be an animated cursor so it wouldn’t be sensible to convert it to a Bitmap. I couldn’t find any examples on the web, so thought I’d post this code that does the trick.

          System.Windows.Forms.Cursor cursor = new System.Windows.Forms.Cursor(stream);
          Bitmap bitmap = new Bitmap(cursor.Size.Width, cursor.Size.Height);
          using (Graphics gBmp = Graphics.FromImage(bitmap))
          {
            cursor.Draw(gBmp, new Rectangle(0, 0, cursor.Size.Width, cursor.Size.Height));
            pictureBox.Image = bitmap;
          }

Thursday, August 12, 2010

Storing and restoring the clipboard contents

My hacky method to get HTML into Word programmatically had a slight flaw in that it splatted anything the user had placed on the clipboard. I consider it to be bad manners to mess with the clipboard because it’s the user’s clipboard, not my application’s. Many years ago I was forced to use Lotus Notes, which used the clipboard whenever I replied to an email, so I can’t really write code that does something that has annoyed me in the past.

So I had to figure out how to fix it. The below did the trick, although I haven’t tested it with all clipboard formats so it may not work with all of them. And if you’re wondering why this is in VB.NET and the previous example was in C#, I was forced to convert all the code to VB.NET, much to my chagrin.

      ' store current contents of clipboard
      Dim currentData As IDataObject = Clipboard.GetDataObject()
      Dim formats As String() = currentData.GetFormats()
      Dim currentClipboard As Dictionary(Of String, Object) = New Dictionary(Of String, Object)
      For Each Format As String In formats
        currentClipboard.Add(Format, currentData.GetData(Format))
      Next

      ' do stuff...

      ' put back old clipboard contents
      Dim newData As DataObject = New DataObject()
      For Each savedFormat As KeyValuePair(Of String, Object) In currentClipboard
        newData.SetData(savedFormat.Key, savedFormat.Value)
      Next
      Clipboard.SetDataObject(newData)
  

Wednesday, August 11, 2010

FxCop 10

When I fired up FxCop 1.36 today, the update check, for probably the first time ever, told me there was a new version available. So I went off to get the update. The odd thing was that I wasn’t pointed towards an installer but a text file. That pointed out that FxCop was now part of the Windows SDK, so installing it is now something of a bigger job than it used to be. In fact, installing the SDK doesn’t install FxCop, it just copies the installer to your machine which then has to be run separately.

But the fact that the version number had increased so much got me excited, thinking there must be lots of improvements… After loading it up and running it against a project, I was a little disappointed. I didn’t spot any new rules and the only difference I noticed was that my custom rules assembly no longer worked.

Updating my rules assembly was pretty simple. First remove the references to the 1.36 assemblies (FxcopSdk.dll and Microsoft.Cci.dll), then update the project to target the .NET Framework 4 (of course this means it has to be built in VS 2010), add references to the version 10 assemblies and rebuild. And it all worked first time. My rules are pretty simple so I don’t know if it will be as easy to upgrade more complex rules assemblies but it all looks pretty good for backwards compatibility.

That said, there doesn’t seem to be any compelling reason to upgrade, unless I’ve missed something.

Saturday, July 31, 2010

Things are a bit quiet round here

If you are one of the many subscribers to my blog (where many is a value is slightly larger than zero) you may have noticed things have been a bit quiet round here. There is a reason for this. My proper website has suddenly become relatively popular for some reason, so I’ve made the decision to spend my spare time updating that more regularly, since it is actually producing income for me, which in the current climate is definitely a good thing. I’ll still post here, but it may not be so frequent.

Friday, July 16, 2010

Quick hack to convert HTML to Word documents

There are lots of products that will convert HTML files to Word format or RTF, but they mostly cost money. And there are so many of them, it’s hard to know which are good without testing them all. Word of course can open HTML files directly but one issue I found with this is if you then save the file as a DOC file any images are not embedded in the file (although they are in DOCX files).

But being the cheapskate I am, I thought there must be a way to implement this myself, so came up with this quick hack, which loads the HTML into IE, copies it to the clipboard, pastes it into Word and then saves it as a Word document. It does require that Word is installed on the user’s machine and it does splat any contents of the clipboard but other than that it seems reasonably robust. My only other thought is how horrible using the Word COM API is from .NET, bring on optional parameters!

    private bool loadComplete;
    public void ConvertToWord(string htmlFile, string filename)
    {
      // open in IE
      using (WebBrowser browser = new WebBrowser())
      {
        loadComplete = false;
        browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(
          browser_DocumentCompleted);
        browser.Navigate(htmlFile);
        while (!loadComplete)
        {
          Thread.Sleep(50);
          System.Windows.Forms.Application.DoEvents();
        }

        // copy to clipboard
        browser.Document.ExecCommand("SelectAll", true, null);
        browser.Document.ExecCommand("Copy", true, null);
        
        // open Word, paste and save...
        object dummy = Type.Missing;
        ApplicationClass wordApp = new ApplicationClass();
        try
        {
          Document newDoc = wordApp.Documents.Add(ref dummy, ref dummy, ref dummy, ref dummy);

          wordApp.Selection.Paste();

          object fileName = filename;
          newDoc.SaveAs(ref fileName, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, 
            ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, ref dummy, 
            ref dummy, ref dummy, ref dummy);
          ((_Document)newDoc).Close(ref dummy, ref dummy, ref dummy);
        }
        finally
        {
          wordApp.Quit(ref dummy, ref dummy, ref dummy);
        }
      }
    }

    void browser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
      loadComplete = true;
    }

Tuesday, June 29, 2010

Visio API ‘Cell is guarded’ exception

If you’re using the Visio COM API to generate or modify Visio diagrams, you may have come across the exception ‘Cell is guarded’ when trying to set a cell’s FormulaU property (and probably when setting the Formula property). For me it only happened on a machine with Visio 2007 installed, Visio 2003 was fine. The solution was pretty simple, use the FormulaForceU property instead (although potentially this may lead to unexpected behaviour apparently, worked fine for me though).

Thursday, June 24, 2010

Importing the Code-Point dataset into MySql

I recently realised that capturing the geocoding results from Google wasn’t such a good idea. It seems that postcodes that are only partially correct will still be geocoded, but will be returned with a default latitude and longitude which are kind of in the right area for that postcode area but obviously aren’t right, since the postcode doesn’t exist.

So I decided it was time to grasp the nettle and use the Code-Point dataset for my UK postcodes page, so the data should be much more accurate. The only issue with the Code-Point dataset is that it doesn’t contain postcodes in Northern Ireland, Guernsey, Jersey and the Isle of Man for some reason.

So the first thing to do was to download the .NET connector for MySql. Then I modified my SQL Server import code to use MySql. As mentioned in that post, the code has dependencies on this CSV parser and my .NET coordinates library. I made some modifications to add spaces to postcodes as necessary and also to not bother inserting postcodes with no geographical data (I’m not sure if this is missing data or if these are simply non-geographical postcodes).

The code is shown below. Although it works, this is a simple implementation and is not particularly fast. It will probably take a few hours running against a local MySql installation but running against a remote installation will probably take longer. I only plan to run it once, so I’m not particularly concerned with performance, but if performance is an issue, you’ll probably want to take a look at the MySqlBulkLoader class or peruse the MySql documentation on improving the performance of inserts.

using System;
using System.IO;
using DotNetCoords;
using LumenWorks.Framework.IO.Csv;
using MySql.Data.MySqlClient;

namespace ImportCodepoint
{
  class Program
  {
    static void Main(string[] args)
    {
      string[] files = Directory.GetFiles(@"C:\Users\Doogal\Downloads\codepo_gb\Code-Point Open\data\CSV");
      foreach (string file in files)
      {
        ReadFile(file);
      }

    }

    private static void ReadFile(string file)
    {
      using (StreamReader reader = new StreamReader(file))
      {
        CsvReader csvReader = new CsvReader(reader, false);
        using (MySqlConnection conn = new MySqlConnection(
          "server=127.0.0.1;uid=root;pwd=password;database=test;"))
        {
          conn.Open();
          foreach (string[] data in csvReader)
          {
            string postcode = data[0];
            // some postcodes have spaces, some don't
            if (postcode.IndexOf(' ') < 0)
              postcode = data[0].Substring(0, data[0].Length - 3) + " " + data[0].Substring(data[0].Length - 3);
            double easting = double.Parse(data[10]);
            double northing = double.Parse(data[11]);

            // there are some postcodes with no location
            if ((easting != 0) && (northing != 0))
            {
              // convert easting/northing to lat/long
              OSRef osRef = new OSRef(easting, northing);
              LatLng latLng = osRef.ToLatLng();
              latLng.ToWGS84();

              using (MySqlCommand command = conn.CreateCommand())
              {
                Console.WriteLine(postcode);
                command.CommandTimeout = 60;
                command.CommandText = string.Format("INSERT INTO Postcodes (Postcode, Latitude, Longitude) " +
                  "VALUES ('{0}', {1}, {2})",
                  postcode, latLng.Latitude, latLng.Longitude);
                command.ExecuteNonQuery();
              }
            }
          }
        }
      }
    }
  }
}

Tuesday, June 22, 2010

MySql Workbench

I’ve used phpMyAdmin for a long time to administer MySql databases on the web. I’ve been pretty happy with it, although it can occasionally behave a bit strangely if it times out. But I was at the MySql website the other day to download MySql for my laptop when I spotted MySql Workbench, so I thought I’d give it a go. And it’s a very nice piece of free software. Call me an old timer but I still prefer a desktop application and it delivers. It looks good and offers all the features I need. I’ve been spoiled by SQL Server Management Studio (and not spoiled by the Oracle admin tools…) but MySql Workbench seems comparable in terms of functionality and ease of use. The pre-release 5.2 seems quite a lot better than the official 5.1 release, so go for that if you feel brave.

Friday, June 18, 2010

FxCop custom rules

There are quite a few examples of custom rules for FxCop (this was the best I found) but I was unable to find an example Visual Studio project to download. So after I got my simple rules assembly up and running, I thought I’d upload it to the web, you can grab it from here. It just contains one rule, taken from the site mentioned before, which checks for the usage of the ArrayList class.

My only advice is if you are having trouble getting your rules loaded up into FxCop then debug your assembly through Visual Studio and make sure you have set up Visual Studio to break when CLR exceptions are thrown. If any exceptions are thrown when trying to load your assembly, FxCop will catch the exception and not display the rules.

Thursday, June 17, 2010

Getting the height of a background image

Getting hold of the physical height of an image would initially appear to be a pretty straightforward thing to do. This may well be the case with an image held in an <img> tag (at least if the height of the <img> tag hasn’t been set) since it will resize to fit the image so you just need to get the height of the <img> element. But with a background image it’s not quite so simple. I wanted to get the height of an image used as a background image so I could resize the header of some HTML output when users supplied their own custom background images.

Even so, this should be pretty easy I thought. Load up the image using the Image JavaScript object and get the height from there. There were a couple of issues with this. I decided to pick up the image URL from the background-image CSS, rather than hard coding it. But this was complicated by the fact that different browsers store the URL in different ways, some with quotes, some without.

The other crazy thing I encountered was Internet Explorer seemingly loading up an old version of the image and returning the dimensions of that even though a new different sized image had replaced it. Emptying my cache and fiddling with the options dialog made no difference, so I was forced to add a query string to stop this insane caching.

Another thing which seems to have tripped up other people when I was trawling the web is the need to set up the onload handler before setting the src property.

Here’s the code, hope it’s useful.

      // get the background image
      var backgroundImg = new Image();
      backgroundImg.onload = function() {
        headerHeight = backgroundImg.height;
        // do other stuff
      };
      var imgSrc = $('.header').css('background-image');
      // IE and FireFox have quotes, Safari and Chrome don't
      imgSrc = imgSrc.replace(/["']/g, '');
      imgSrc = imgSrc.substring(4);
      imgSrc = imgSrc.substring(0, imgSrc.length - 1);
      // IE seems to be too aggressive in caching
      backgroundImg.src = imgSrc + '?' + Math.random();

Sunday, June 13, 2010

Importing the Code-Point dataset into SQL Server

Many moons ago, I imported my own postcode dataset into SQL Server but ever since the Ordnance Survey have made their Code-Point dataset available for free, I’ve been meaning to import that, since it’s likely their data will be more correct than my own.

It comes in a series of CSV files and contains the postcode, easting, northing and various other bits of data. Unfortunately the documentation is somewhat lacking so it’s not entirely clear what all the other data is. But those three bits of data are all I was after.

So I wrote a little console application to import the data, that looks like this

using System;
using System.Data.SqlClient;
using System.IO;
using DotNetCoords;
using LumenWorks.Framework.IO.Csv;

namespace ImportCodepoint
{
  class Program
  {
    static void Main(string[] args)
    {
      string[] files = Directory.GetFiles(@"C:\Users\Doogal\Downloads\codepo_gb\Code-Point Open\data\CSV");
      foreach (string file in files)
      {
        Console.WriteLine("Importing file " + Path.GetFileName(file));
        ReadFile(file);
      }

    }

    private static void ReadFile(string file)
    {
      using (StreamReader reader = new StreamReader(file))
      {
        CsvReader csvReader = new CsvReader(reader, false);
        using (SqlConnection conn = new SqlConnection(
          "Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=Postcodes;Data Source=DOOGAL-LAPTOP\\SQLEXPRESS"))
        {
          conn.Open();
          foreach (string[] data in csvReader)
          {
            string postcode = data[0];
            double easting = double.Parse(data[10]);
            double northing = double.Parse(data[11]);

            // convert easting/northing to lat/long
            OSRef osRef = new OSRef(easting, northing);
            LatLng latLng = osRef.ToLatLng();
            latLng.ToWGS84();

            using (SqlCommand command = conn.CreateCommand())
            {
              command.CommandText = string.Format("INSERT INTO Postcodes (Postcode, Coordinates) " +
                "VALUES ('{0}', geography::STPointFromText('POINT({2} {1})', 4326))",
                data[0], latLng.Latitude, latLng.Longitude);
              command.ExecuteNonQuery();
            }
          }
        }
      }
    }
  }
}

A couple of points, I’ve used this CSV parser, rather than re-inventing the wheel. It would be pretty easy to do this parsing myself, since only a few fields are quoted and no fields contain any special characters, but I’ve been pretty happy with this CSV parser in the past so saw no point re-inventing the wheel.

I’ve also used my .Net coordinates library to convert GB OS references to latitude/longitude, but obviously you can store the postcodes using eastings and northings if you prefer.

The final thing to do is to check that the imported data looks sensible. The easiest way to do this is to query the table in SQL Server Management Studio and look at the spatial results. Unfortunately this is limited to 5000 results, so I used this little bit of SQL (based on this blog post) to select a subset of the data to make sure the general shape looked correct. Obviously this doesn’t check all the data but did build some confidence that what I’d done was correct.

SELECT 
   EXPR2.*
FROM 
   (
   SELECT 
      Postcode, Coordinates,
      RowNumber,
      (EXPR1.RowNumber % 400) AS ROW_MOD
   FROM
      (
      SELECT 
         Postcode, Coordinates, 
         ROW_NUMBER() OVER ( ORDER BY Postcode ) AS 'RowNumber'
      FROM 
         Postcodes 
      ) EXPR1
   ) EXPR2
WHERE 
   EXPR2.ROW_MOD = 0

Friday, June 11, 2010

Image file locking in the .NET Bitmap constructor

Consider the following code

      Dim bitmap As New Drawing.Bitmap(fileName)
      Return bitmap

It certainly looks fairly innocuous but it had a problem, or rather it caused a problem further down the track. We wanted to replace some of the image files that we had previously loaded into the application. But trying to do that threw an exception telling us “The process cannot access the file '…’ because it is being used by another process.”. Which wasn’t entirely accurate, actually we couldn’t access the file because it was in use by this process. But nitpicking aside, the reason for this was the Bitmap constructor above which loads the file but doesn’t unlock it, only a call to Bitmap.Dispose() unlocks it again.

So my first attempt to fix this looked something like this

      Dim fs As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
      Dim bitmap As New Drawing.Bitmap(fs)
      fs.Close()
      Return bitmap

Since this opened the image via a FileStream and closed it afterwards, this should solve the problem. And it did on my Windows 7 machine, but it caused even bigger problems on a Windows XP machine. We started getting an out of memory exception for no apparent reason.

I eventually came to the conclusion that this was down to the Bitmap constructor that took a FileStream instance. When it’s passed a file name, it can make a pretty good guess at what kind of file it’s loading based on the extension, but when it is just given a bunch of bytes, it has to make a guess as to what type of image it’s looking at and I presume the heuristics in XP weren’t as good as they are in Windows 7. I’m guessing here, since all this happens in the guts of the GDI+ API.

So my final attempt at fixing this looks like this

      Dim fileBitmap As New Drawing.Bitmap(fileName)
      Dim bitmap As New Drawing.Bitmap(fileBitmap)
      fileBitmap.Dispose()
      Return bitmap

Which so far hasn’t caused any more issues…

Saturday, June 05, 2010

Debugging KML loading problems in Google Maps

I was attempting to load some KML into Google Maps via the KmlLayer class and not having much luck. Nothing was appearing. Unfortunately the KmlLayer class doesn’t provide an event to indicate an error has occurred. So what is available to debug these problems and what are some of the issues that can cause load errors?

  • Fiddler may help, although it didn’t give any indication to me about what was going on. But I have seen some HTTP responses with 400 bad request status codes in the past. Unfortunately they didn’t say why the request was bad.
  • Check that the KML is valid using Feed Validator. I’ve found this doesn’t work too well with large KML files in IE, so use some other browser instead.
  • When Google Maps does server side rendering of KML it appears to do some caching of the data, so if you change your KML, changes may not appear immediately.
  • There are limits to the size of KML file supported by Google Maps, have a look at this page to see if this is your problem. I think this is what caused my issues.
  • Try loading the KML in Google Earth to see if it’s an issue with the KML or Google Maps’ handling of it.

Tuesday, June 01, 2010

Moving from Google Maps 2 to Google Maps 3

After playing with version 3 of the Google Maps API and trying to convert my ScratchPad application, I’m pretty impressed. It’s a much more consistent API than version 2 and provides quite a few new features. But one thing is for sure, converting an application from version 2 to version 3 will be a big chunk of work. Pretty much every line will need rewriting.

So I thought I’d provide a little crib sheet to help people who are trying to upgrade. I don’t claim that this list is in any way complete. I’ll update it as I find more things out, but even so I only intend it to be a pointer to the right class/method to use. In most cases the way methods or classes are used has also changed. If you have any other information to add, please add a comment.

Google Maps version 2 Google Maps version 3
GMap2 class google.maps.Map class
GMap2.disableDoubleClickZoom() MapOptions.disableDoubleClickZoom:true passed to Map constructor or Map.setOptions()
GMap2.getDragObject().setDraggableCursor() MapOptions.draggableCursor passed to Map constructor or Map.setOptions()
GMap2.addOverlay() Call setMap() on the layer to add
GMap2.clearOverlays() ???
GMap2.removeOverlay() Call setMap(null) on the layer to remove
GLatLng class google.maps.LatLng class
GMarker class google.maps.Marker class
GMarker.bindInfoWindowHtml() google.maps.InfoWindow class, but you need to manually open the info window using

    google.maps.event.addListener(marker, 'click', function () {
    infoWindow.open(map, marker);
  });

GEvent.bind(), GEvent.addListener google.maps.event.addListener(), although it doesn’t seem possible to bind the event to particular object instance (where the keyword this can be used in the event handler)
GEvent.removeListener() google.maps.event.removeListener()
GNavLabelControl class ???
GAdsManager class ???
GGeoXml class google.maps.KmlLayer class
GGeoXml.load event ???
GGeoXml.getDefaultCenter() KmlLayer.getDefaultViewport().getCenter()
GStreetviewOverlay class This is now integrated into the map, pass streetViewControl: true into MapOptions to make it appear
GClientGeocoder class google.maps.Geocoder class
GClientGeocoder.getLatLng() Geocoder.geocode()
GPolyline class google.maps.Polyline class
GPolyline.getLength() There is no direct replacement, but this forum post discusses how to implement something similar yourself.
GPolyline.getVertexCount() Polyline.getPath().length()
GDirections class google.maps.DirectionsService class to query for directions, google.maps.DirectionsRenderer class to render them on a map
GDirections.load() DirectionService.route()
GDirections.loadFromWaypoints() DirectionsRequest.waypoints property
GDirections.clear() No equivalent
GTrafficOverlay class google.maps.TrafficLayer class
GLayer class ??? – This is in the API issues database, star it if you want it implemented
GTileLayerOverlay class ???

Monday, May 31, 2010

Improving the performance of REGEXP queries in MySql

Say we’re trying to query a table of UK postcode and just want to return the postcodes in a particular area(the Birmingham area B for instance). A naive implementation of this may be something like this

SELECT * FROM Postcodes WHERE Postcode LIKE 'B%'

This works for some postcodes but doesn’t work for this particular example because it also returns any postcodes in the BA, BB, BT etc areas. So it looks like a regular expression is required, to ensure the area code is followed by a number, so we try the following

SELECT * FROM Postcodes WHERE Postcode REGEXP'^B[0-9]'

This works and returns what we expect but it is much slower than the LIKE query. So is there any way to speed it up? Actually, it’s pretty straightforward, just combine the LIKE and the REGEXP queries, like so

SELECT * FROM Postcodes WHERE Postcode LIKE 'B%' AND Postcode REGEXP'^B[0-9]'

This give MySql the chance to first filter the data based on the LIKE clause then only use the regular expression on the filtered data. It’s not quite as fast as the original LIKE implementation but it’s much quicker than REGEXP on its own and unlike the original LIKE implementation it actually works.

Sunday, May 30, 2010

The Fibonacci Sequence

For Christmas 1987, my brother gave me ‘yet another interesting book’ (his words), “The Penguin Dictionary of Curious and Interesting Numbers”. Clearly it must be pretty interesting, since I was surprised to see it is still available. Being something of a maths geek, I did enjoy reading it, but it has been languishing in my loft for a few years now.

Jump forward a couple of decades and I was reading “The Rabbit Problem” as a bedtime story to my daughter and was wondering why it was set in Fibonacci’s Field. Clearly this was related to the Fibonacci Sequence (and reading the back cover gave the game away) but I was unsure how the Fibonacci Sequence was related to rabbits. So time to climb up into the loft to find out. Turns out the Fibonacci Sequence was the solution to a problem about rabbits breeding. And it also appears a lot in nature, specifically in the number of petals etc in plants.

As if that wasn’t enough, the ratios of successive terms of the Fibonacci Sequence tend towards the Golden Ratio, another interesting number that you can find out about in the dictionary. And here’s a little Javascript example to show that convergence.


Free XML sitemap generator with unlimited pages

I’ve been happily using this free XML sitemap generator for a while, but as my sites have got bigger I keep hitting the 500 page limit. Since I’m too tight to actually pay for this kind of service I’ve been searching around for a while for another free alternative. I was even thinking of writing my own, but before I was forced down that road, I found this alternative generator. I’m not too keen on Java applets generally, but this one seems to work very well. If it fails to download any pages, you can go back and retry downloading the failed pages. It also gives details of how long pages take to download, so it can also be a useful performance checker of a website.

Even so, I’m a little ambivalent towards sitemaps, especially if they are generated using one of these tools that just crawl your website to find all the pages. What information does this provide to search engines that they can’t find for themselves by crawling the site themselves? And If I do want to provide any more useful information, I have to edit the thing by hand, which I really can’t be bothered doing.

Friday, May 28, 2010

Google Maps elevation API

Version 3 of the Google Maps API adds a new dimension to maps, the elevation of locations. I did plan to knock together an example of how this works, but the example provided by Google is pretty good itself.

I think this new API could be very useful. The obvious use would be when planning a walk or a bike ride and wanting to get an idea for the terrain, but I’m sure there are many more uses.

Google Maps styled maps

Version 3 of the Google Maps API adds support for styled maps, which means it is now possible to hide features or display them differently on the base map. This could lead to some interesting customisations, and also leads to some questions about how exactly Google have implemented this? Are they creating map tiles on the fly? Or have they stored every possible permutation of styling? Surely the former, but how come it doesn’t seem to make any difference to the performance of rendering?

Anyway I had a play with the styled map wizard (which doesn’t work in IE for some reason) and thought I’d experiment with a map displaying just rail lines. And I kind of got this working, but there was a fundamental problem. Rail lines are only displayed when the map is zoomed in below a certain threshold so the map wasn’t any use to get a broad overview of rail lines in the UK for instance. And the styled maps API doesn’t seem to provide any control over this thresholding.

So it looks good as an initial implementation, but I think more control is required to be a completely useful API.

Tuesday, May 25, 2010

Google Maps geolocation API

Google have had an API to get the user’s location for a while (via the Google loader), but I didn’t think it was very good. But now there are two new methods of figuring out the user’s location, one using the new W3C standard and the other using Google Gears. The good news is they seem to do a very good job of locating the user (scarily so in fact), the bad news is that neither are supported by Internet Explorer or Safari.

Google Chrome uses Google Gears to do the job, FireFox uses the W3C method and frankly I’m not too bothered what Opera supports, since so few people use it.

One annoyance is the prompting you get in the browser, although this is fairly understandable since this information could potentially be used for evil. And it won’t be an especially useful feature until it’s adopted by more browsers. But a combination of all three methods may produce a reasonable compromise. 

Friday, May 21, 2010

Google Maps geocoding still sucks

I spent a little time today starting to convert some of my pages to the Google Maps API version 3. At the same time I thought I’d convert my geocoding to use the latest version since I was sure it would have improved since it was added to the API and failed to work too well three years ago. But no, it still sucks big time for postcode geocoding, as this test page demonstrates. But what is strange is that the GlocalSearch class still does a fine job of geocoding. Same company, different APIs and different results. This can’t be down to the Royal Mail throwing their toys out of the pram since the Ordnance Survey now provides geocoded postcode data for free so I assume it’s down to Google stuffing up.

Converting to Google Maps version 3

Google Maps API version 3 has just moved from the Google Labs to live so I thought I’d try converting a simple version 2 map to version 3. This is what the version 2 code looks like

  <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjtZCgAx5i04BiZDO6HlxhRQUdBDpWCOMRMbgTcqadX0jQ8HOERSxXxhk24TIBUpivovAKLrnpSio9w" type="text/javascript"></script>
  <script type="text/javascript">
    window.onload = function () {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(51.49506473014368, -0.130462646484375), 10);
        map.addControl(new GSmallMapControl());
        map.addControl(new GMapTypeControl());
        map.enableDoubleClickZoom();
        map.enableScrollWheelZoom();
        var layer = new GGeoXml("http://www.doogal.co.uk/LondonStationsKML.php");
        map.addOverlay(layer);
      }
    }
  </script>

And this is what the version 3 code looks like

  <script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"></script>
  <script type="text/javascript">
    window.onload = function () {
      var latlng = new google.maps.LatLng(51.49506473014368, -0.130462646484375);
      var options = {
        zoom: 10,
        center: latlng,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };

      var map = new google.maps.Map(document.getElementById("map"), options);

      var georssLayer = new google.maps.KmlLayer('http://www.doogal.co.uk/LondonStationsKML.php');
      georssLayer.setMap(map);
    }
  </script>

The first thing I noticed is that the API key is no longer required which is very welcome, since it makes everything simpler (no more hassle when moving scripts between domains or trying to debug somewhere other than the domain or localhost). I guess the namespaces are a good thing and the class names seem more memorable. At this point I’m not sure if the API is any better than the old one, I’ll have to do some more digging before I can make a decision. But it does look like there’ll be quite a bit of work to convert old sites to the new API. Not that we’re forced to upgrade of course, at least for the moment.

Friday, April 23, 2010

JavaScript not executing in Internet Explorer 8

I’ve been caught out by this on numerous occasions and still haven’t learnt my lesson. So in an attempt to remember in the future and perhaps help somebody else out, here’s the situation. Take the following script declaration

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

This may look valid and probably should be, but Internet Explorer thinks the script tag hasn’t been closed, which means any script blocks after it will be ignored. The declaration needs to be

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

Sunday, April 11, 2010

Building a random postcode generator

Some time a go I noticed that the keywords ‘random postcode generator’ were landing quite a few people at my website, even though I didn’t have a random postcode generator. Which got me thinking there must be a need for such a tool, if people were ending up at a site that didn’t have one desperately looking for it. So I built one and it’s now the most visited page on my site.

So that’s my little success story. But then I started wondering why anybody would want a random postcode anyway. I guess some people are just after a fake postcode they can plug into a website registration form, for whatever reason, and that page will do the trick nicely. But I imagine there are other people who need to generate random postcodes programmatically for some reason, so I’m going to explain how my postcode generator works. It may not be the best approach, but it seems to work.

I guess one approach would be to have a complete list of UK postcodes and just select one at random. I didn’t have a complete list of postcodes and you probably won’t either, unless you’ve bought the PAF database from the Royal Mail. So I went for a more long winded approach that requires less initial data to get working. First I got together a table of postcode districts (original list and my more programmer friendly version), which is the first half of a postcode. Potentially you could try to create this part of the postcode randomly but you’d end up creating invalid postcodes a lot of the time. Also, not all postcode districts are of the same format.

So with this table in place, I get a random postcode district and then append a random second half of the postcode (which is always of the form digit-letter-letter). I did this in PHP using the following (this contains some code which is specific to my website but you should be able to modify for your own needs)

  $result = DbQuery("SELECT Postcode FROM PostcodeDistricts ORDER BY rand( ) LIMIT 1");
  $line = mysql_fetch_array($result, MYSQL_ASSOC);
  $num = rand(0,9);
  $firstChar = rand(1, 26);
  $secondChar = rand(1, 26);
  print($line["Postcode"] . " " . $num . chr(64+$firstChar) . chr(64+$secondChar));
  mysql_free_result($result);

The only problem with this is that it will sometimes generate invalid postcodes, so the next step is to check the postcode is valid. I did this using Google’s Local Search API from JavaScript, as so.

  <script type="text/javascript">
    /* <![CDATA[ */
    function CreateRandomPostcode()
    {
      document.getElementById("postcode").innerHTML = "Thinking";
      TryCreateRandomPostcode();
    }
 
    function TryCreateRandomPostcode()
    {
      document.getElementById("postcode").innerHTML += ".";
      var xmlhttp;
      if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest();     // Firefox, Safari, ...
      }
      else if (window.ActiveXObject)   // ActiveX version
      {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");  // Internet Explorer
      }
 
      xmlhttp.onreadystatechange = function() {
        if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
          Geocode(xmlhttp.responseText);
        }
      }
      xmlhttp.open("GET", "CreateRandomPostcode.php", true);
      xmlhttp.send();
    }
 
    function Geocode(postcode)
    {
      var localSearch = new GlocalSearch();
      localSearch.setSearchCompleteCallback(null,
        function()
        {
          if (localSearch.results[0])
          {
            var results = localSearch.results[0];
 
            if ((results.lat > 49) && (results.lat < 61) && (results.lng > -12) && (results.lng < 3)) {
              document.getElementById("postcode").innerHTML = "Your random postcode is " + postcode;
              document.getElementById("lat").value = results.lat;
              document.getElementById("long").value = results.lng;
            }
            else {
              TryCreateRandomPostcode();
            }
          }
          else
          {
            TryCreateRandomPostcode();
          }
        });
 
      localSearch.execute(postcode + ", UK");
    }
 
    /* ]]> */
  </script>

Basically it tries to geocode the postcode and if it fails (or it lies outside the UK), assumes the postcode is not valid and tries a different postcode until a valid one is produced. This could all be done server-side by hacking the Local Search API, but this met my simple needs.

Friday, April 02, 2010

Temporary file class for C#

Quite frequently I need to create a temporary file, do some processing on it, then delete it. In order to ensure the file gets deleted, I put the file deletion code in a try…finally, which got me thinking about writing a simple class that implements IDisposable to handle this scenario, allowing me to use using. It’s very simple, but here it is anyway.

  public class TemporaryFile : IDisposable
  {
    public TemporaryFile(string fileName)
    {
      this.fileName = fileName;
    }

    ~TemporaryFile()
    {
      DeleteFile();
    }

    public void Dispose()
    {
      DeleteFile();
      GC.SuppressFinalize(this);
    }

    private string fileName;
    public string FileName
    {
      get { return fileName;  }
    }

    private void DeleteFile()
    {
      if (File.Exists(fileName))
        File.Delete(fileName);
    }
  }

Saturday, March 27, 2010

jQuery zoom addin with image map support

In the past I fiddled around with the moozoom plugin to add support for image maps and on a new project I needed the same thing. But this new project uses a lot of jQuery stuff and jQuery and mootools don’t play together particularly well. Although there are a lot of image zooming plugins available for jQuery, none of them did exactly what I needed. I just wanted image zooming, panning and support for image maps. Anyway, this is what I came up with. This is my first adventure in the world of jQuery plugins, so it may not be a perfect implementation but it works for me…

Usage is pretty simple

$('.selector').zoomable();

where .selector is the selector for your image. View an example here (that page will also be the place I put all new versions of this code). You can now also programmatically zoom in and out using $('.selector').zoomable('zoomIn'); and $('.selector').zoomable('zoomOut'); which you can hook up to buttons for users without a mouse wheel

Here’s the code (requires jQuery and jQuery UI)

(function ($) {
  $.fn.zoomable = function (method) {
 
    return this.each(function (index, value) {
      // restore data, if there is any for this element
      var zoomData;
      if ($(this).data('zoomData') == null) {
        zoomData = {
          busy: false,
          x_fact: 1.2,
          currentZoom: 1,
          originalMap: null,
          currentX: 0,
          currentY: 0
        };
        $(this).data('zoomData', zoomData);
      }
      else
        zoomData = $(this).data('zoomData');
      
      var init = function() {
        if (value.useMap != "") {
          var tempOriginalMap = document.getElementById(value.useMap.substring(1));
          zoomData.originalMap = tempOriginalMap.cloneNode(true);
          // for IE6, we need to manually copy the areas' coords
          for (var i = 0; i < zoomData.originalMap.areas.length; i++)
            zoomData.originalMap.areas[i].coords = tempOriginalMap.areas[i].coords;
        }

        $(value).css('position', 'relative').css('left', '0').css('top', 0).css('margin', '0');

        $(value).draggable();

        // jquery mousewheel not working in FireFox for some reason
        if ($.browser.mozilla) {
          value.addEventListener('DOMMouseScroll', function (e) {
            e.preventDefault();
            zoomMouse(-e.detail);
          }, false);
          if (value.useMap != "") {
            $(value.useMap)[0].addEventListener('DOMMouseScroll', function (e) {
              e.preventDefault();
              zoomMouse(-e.detail);
            }, false);
          }
        }
        else {
          $(value).bind('mousewheel', function (e) {
            e.preventDefault();
            zoomMouse(e.wheelDelta);
          });
          if (value.useMap != "") {
            $(value.useMap).bind('mousewheel', function (e) {
              e.preventDefault();
              zoomMouse(e.wheelDelta);
            });
          }
        }

        $(value).bind('mousemove', function (e) {
          zoomData.currentX = e.pageX;
          zoomData.currentY = e.pageY;
        });
      };

      var left = function() {
        return parseInt($(value).css('left'));
      };
      
      var top = function() {
        return parseInt($(value).css('top'));
      }
      
      var zoomIn = function() {
        // zoom as if mouse is in centre of image
        var parent = $(value).parent()[0];
        zoom(zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomOut = function() {
        // zoom as if mouse is in centre of image
        var yi = parseInt($(value).css('top'));
        var parent = $(value).parent()[0];
        zoom(1 / zoomData.x_fact, left()+parent.offsetLeft+(value.width/2), top()+parent.offsetTop+(value.height/2));
      };
      
      var zoomMouse = function (delta) {

        // zoom out ---------------
        if (delta < 0) {
          zoom(1 / zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }

        // zoom in -----------
        else if (delta > 0) {
          zoom(zoomData.x_fact, zoomData.currentX, zoomData.currentY);
        }
      };

      var zoomMap = function () {
        // resize image map
        var map = document.getElementById(value.useMap.substring(1));
        if (map != null) {
          for (var i = 0; i < map.areas.length; i++) {
            var area = map.areas[i];
            var originalArea = zoomData.originalMap.areas[i];
            var coords = originalArea.coords.split(',');
            for (var j = 0; j < coords.length; j++) {
              coords[j] = Math.round(coords[j] * zoomData.currentZoom);
            }
            var coordsString = "";
            for (var k = 0; k < coords.length; k++) {
              if (k > 0)
                coordsString += ",";
              coordsString += coords[k];
            }
            area.coords = coordsString;
          }
        }
      };

      var zoom = function (fact, mouseX, mouseY) {
        if (!zoomData.busy) {
          zoomData.busy = true;

          var xi = left();
          var yi = top();

          var new_h = (value.height * fact);
          var new_w = (value.width * fact);
          zoomData.currentZoom = zoomData.currentZoom * fact;

          // calculate new X and y based on mouse position
          var parent = $(value).parent()[0];
          mouseX = mouseX - parent.offsetLeft
          var newImageX = (mouseX - xi) * fact;
          xi = mouseX - newImageX;

          mouseY = mouseY - parent.offsetTop
          var newImageY = (mouseY - yi) * fact;
          yi = mouseY - newImageY;

          $(value).animate({
            left: xi,
            top: yi,
            height: new_h,
            width: new_w
          }, 100, function () {
            zoomData.busy = false;
          });

          zoomMap();
        }
      };
      
      if (method == "zoomIn")
        zoomIn();
      else if (method == "zoomOut")
        zoomOut();
      else
        init();
    });
  };
})(jQuery);