Optimisation of iris.analysis.cartography._quadrant_area

Hello,

this isn't really ready for a pull, but I figured it may as well start a pull request.  Could you advise me what needs doing to get this into an acceptable form.

I'm regridding some large (30 second global) data sets using area weighted regridding.  Profiling I noticed quite a bit of time was being spent in _quadrant_area.  I've refactored this routine to use numpy array operations rather than the explicit loops. 

Some sample timeit code and results can be found in the gist: https://gist.github.com/jkettleb/eee213e66b71679c9907.  I think this speed up is worth having - do you agree?  Or are there other factors I need to worry about?  I also, for now, seperated out some tests of _quadrant_area into a stand alone module (it just meant I could run the regression on the unit frequently).  I also changed the assertion to an assert_allclose and added an extra test (to make sure I really was doing the right thing). I can't think of any more obvious test cases - but let me know if you think there are any.

Here's what I think I need to do - but please correct/add as necessary.

1. remove routine _areas_by_loop
2. inline routine _areas_by_products
3. inline the test code (or should this go more into the unittests?)

Thanks for any help you can give over this.  If its easiest I can come and chat to someone.

:+1: for adding some checks of the failure modes.

These are more readable using `self.assertRaises` as a context manager. For example:
```python
with self.assertRaises(ValueError):
    _quadrant_area(np.array([0, 10]), np.array([[0, 10]]), 1.)
```

Alternatively, if there should be a relevant error message we can use `self.assertRaisesRegexp`:
```python
with self.assertRaisesRegexp(ValueError, 'Bounds must be [n,2]'):
    _quadrant_area(np.array([0, 10]), np.array([[0, 10]]), 1.)
```