+ ##
+ # raised when bounding box is invalid
+ class APIBadBoundingBox < APIError
+ def status
+ :bad_request
+ end
+ end
+
+ ##
+ # raised when an API call is made using a method not supported on that URI
+ class APIBadMethodError < APIError
+ def initialize(supported_method)
+ super "Only method #{supported_method} is supported on this URI"
+ end
+
+ def status
+ :method_not_allowed
+ end
+ end
+
+ ##
+ # raised when an API call takes too long
+ class APITimeoutError < APIError
+ def initialize
+ super "Request timed out"
+ end
+
+ def status
+ :request_timeout
+ end
+ end
+
+ ##
+ # raised when someone tries to redact a current version of
+ # an element - only historical versions can be redacted.
+ class APICannotRedactError < APIError
+ def initialize
+ super "Cannot redact current version of element, only historical versions may be redacted."
+ end
+
+ def status
+ :bad_request
+ end
+ end
+
+ # Raised when the note provided is already closed
+ class APINoteAlreadyClosedError < APIError
+ def initialize(note)
+ @note = note
+
+ super "The note #{note.id} was closed at #{note.closed_at}"
+ end
+
+ attr_reader :note
+
+ def status
+ :conflict
+ end
+ end
+
+ # Raised when the note provided is already open
+ class APINoteAlreadyOpenError < APIError
+ def initialize(note)
+ @note = note
+
+ super "The note #{note.id} is already open"
+ end
+
+ attr_reader :note
+
+ def status
+ :conflict
+ end
+ end
+
+ # raised when a two preferences have a duplicate key string.
+ class APIDuplicatePreferenceError < APIError
+ def initialize(key)
+ @key = key
+
+ super "Duplicate preferences with key #{key}"
+ end
+
+ attr_reader :key
+
+ def status
+ :bad_request
+ end
+ end
+
+ # Helper methods for going to/from mercator and lat/lng.
+ class Mercator
+ include Math
+
+ # init me with your bounding box and the size of your image
+ def initialize(min_lat, min_lon, max_lat, max_lon, width, height)
+ xsize = xsheet(max_lon) - xsheet(min_lon)
+ ysize = ysheet(max_lat) - ysheet(min_lat)
+ xscale = xsize / width
+ yscale = ysize / height
+ scale = [xscale, yscale].max
+
+ xpad = (width * scale) - xsize
+ ypad = (height * scale) - ysize
+
+ @width = width
+ @height = height
+
+ @tx = xsheet(min_lon) - (xpad / 2)
+ @ty = ysheet(min_lat) - (ypad / 2)
+
+ @bx = xsheet(max_lon) + (xpad / 2)
+ @by = ysheet(max_lat) + (ypad / 2)
+ end
+
+ # the following two functions will give you the x/y on the entire sheet
+
+ def ysheet(lat)
+ log(tan((PI / 4) + (lat * PI / 180 / 2))) / (PI / 180)
+ end
+
+ def xsheet(lon)
+ lon
+ end
+
+ # and these two will give you the right points on your image. all the constants can be reduced to speed things up. FIXME
+ # If the bbox has no extent, return the centre of the image to avoid dividing by zero.
+
+ def y(lat)
+ return @height / 2 if (@by - @ty).zero?
+
+ @height - ((ysheet(lat) - @ty) / (@by - @ty) * @height)
+ end
+
+ def x(lon)
+ return @width / 2 if (@bx - @tx).zero?
+
+ ((xsheet(lon) - @tx) / (@bx - @tx) * @width)
+ end