
Ordnance Survey API Integration Tutorial
The Ordnance Survey (OS) APIs provide access to Britain’s most detailed and accurate geospatial data. This comprehensive tutorial will guide you through integrating OS APIs into your applications, from basic setup to advanced implementations.
Getting Started
Prerequisites
Before integrating with OS APIs, ensure you have:
- A valid OS Data Hub account
- API credentials (API key)
- Basic understanding of RESTful APIs
- Familiarity with HTTP requests and JSON handling
Setting Up Your Account
- Visit the OS Data Hub
- Create a free account or sign in
- Navigate to “My Account” β “API Keys”
- Generate a new API key for your project
- Note down your API key securely
Rate Limits and Quotas
OS APIs have different rate limits depending on your subscription tier:
- Free Tier: 1,000 requests per month
- Premium Tiers: Higher limits based on your plan
Always check your current usage in the Data Hub dashboard.
Authentication
OS APIs use API key authentication. Include your API key in every request using one of these methods:
Query Parameter (Recommended)
GET https://api.os.uk/maps/raster/v1/zxy/Road_3857/7/63/41.png?key=YOUR_API_KEY
Header Authentication
http
GET /maps/raster/v1/zxy/Road_3857/7/63/41.png HTTP/1.1
Host: api.os.uk
Authorization: Bearer YOUR_API_KEY
Available APIs
The OS Data Hub provides several APIs for different use cases:
1. OS Maps API
- Purpose: Raster and vector map tiles
- Base URL:
https://api.os.uk/maps/
- Use Cases: Web mapping, mobile apps, print maps
2. OS Places API
- Purpose: Address and place data
- Base URL:
https://api.os.uk/search/places/v1/
- Use Cases: Geocoding, address validation, postcode lookup
3. OS Names API
- Purpose: Geographic place names and features
- Base URL:
https://api.os.uk/search/names/v1/
- Use Cases: Finding geographic features, place name searches
4. OS Features API
- Purpose: Detailed geographic feature data
- Base URL:
https://api.os.uk/features/v1/
- Use Cases: Spatial queries, feature analysis
Basic Integration Examples
Example 1: Displaying OS Map Tiles
Here’s how to integrate OS map tiles with popular mapping libraries:
Using Leaflet
html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
</head>
<body>
<div id="map" style="height: 400px;"></div>
<script>
const API_KEY = 'YOUR_API_KEY';
// Initialize map
const map = L.map('map').setView([51.505, -0.09], 13);
// Add OS Road layer
L.tileLayer(`https://api.os.uk/maps/raster/v1/zxy/Road_3857/{z}/{x}/{y}.png?key=${API_KEY}`, {
maxZoom: 20,
attribution: 'Β© Crown copyright and database rights ' + new Date().getFullYear() + ' OS.'
}).addTo(map);
</script>
</body>
</html>
Using OpenLayers
javascript
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import XYZ from 'ol/source/XYZ.js';
const API_KEY = 'YOUR_API_KEY';
const map = new Map({
target: 'map',
layers: [
new TileLayer({
source: new XYZ({
url: `https://api.os.uk/maps/raster/v1/zxy/Road_3857/{z}/{x}/{y}.png?key=${API_KEY}`,
attributions: 'Β© Crown copyright and database rights ' + new Date().getFullYear() + ' OS.'
})
})
],
view: new View({
center: [-11000, 6700000],
zoom: 8
})
});
Example 2: Address Search with OS Places API
javascript
async function searchAddress(query) {
const API_KEY = 'YOUR_API_KEY';
const endpoint = 'https://api.os.uk/search/places/v1/find';
try {
const response = await fetch(`${endpoint}?query=${encodeURIComponent(query)}&key=${API_KEY}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data.results;
} catch (error) {
console.error('Error searching for address:', error);
throw error;
}
}
// Usage example
searchAddress('10 Downing Street, London')
.then(results => {
results.forEach(result => {
console.log(`${result.DPA.ADDRESS} - ${result.DPA.POSTCODE}`);
});
})
.catch(error => {
console.error('Search failed:', error);
});
Example 3: Postcode Lookup
javascript
async function lookupPostcode(postcode) {
const API_KEY = 'YOUR_API_KEY';
const endpoint = 'https://api.os.uk/search/places/v1/postcode';
try {
const response = await fetch(`${endpoint}?postcode=${postcode}&key=${API_KEY}`);
const data = await response.json();
return {
postcode: data.postcode,
coordinates: [data.lng, data.lat],
country: data.country
};
} catch (error) {
console.error('Postcode lookup failed:', error);
throw error;
}
}
// Usage
lookupPostcode('SW1A 2AA')
.then(result => {
console.log(`${result.postcode}: ${result.coordinates}`);
});
Advanced Use Cases
Spatial Queries with OS Features API
javascript
async function findNearbyFeatures(longitude, latitude, radius = 1000) {
const API_KEY = 'YOUR_API_KEY';
const endpoint = 'https://api.os.uk/features/v1/wfs';
const params = new URLSearchParams({
service: 'WFS',
request: 'GetFeature',
version: '2.0.0',
typeNames: 'Topography_TopographicArea',
outputFormat: 'GEOJSON',
srsName: 'EPSG:4326',
filter: `<Filter>
<DWithin>
<PropertyName>SHAPE</PropertyName>
<gml:Point>
<gml:coordinates>${longitude},${latitude}</gml:coordinates>
</gml:Point>
<Distance units="m">${radius}</Distance>
</DWithin>
</Filter>`,
key: API_KEY
});
try {
const response = await fetch(`${endpoint}?${params}`);
return await response.json();
} catch (error) {
console.error('Spatial query failed:', error);
throw error;
}
}
Creating a Custom Geocoding Service
javascript
class OSGeocoder {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.os.uk/search/places/v1';
}
async geocode(address, options = {}) {
const params = new URLSearchParams({
query: address,
key: this.apiKey,
maxresults: options.maxResults || 10,
...options
});
const response = await fetch(`${this.baseUrl}/find?${params}`);
const data = await response.json();
return data.results.map(result => ({
address: result.DPA.ADDRESS,
postcode: result.DPA.POSTCODE,
coordinates: [result.DPA.LNG, result.DPA.LAT],
confidence: result.DPA.MATCH_DESCRIPTION
}));
}
async reverseGeocode(lng, lat) {
const params = new URLSearchParams({
point: `${lng},${lat}`,
key: this.apiKey
});
const response = await fetch(`${this.baseUrl}/nearest?${params}`);
return await response.json();
}
}
// Usage
const geocoder = new OSGeocoder('YOUR_API_KEY');
Best Practices
1. API Key Security
- Never expose API keys in client-side code
- Use environment variables for server-side applications
- Implement a proxy server for client-side applications
- Rotate keys regularly
2. Caching Strategies
javascript
class OSApiCache {
constructor(ttl = 300000) { // 5 minutes default
this.cache = new Map();
this.ttl = ttl;
}
async get(key, fetchFn) {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
const data = await fetchFn();
this.cache.set(key, {
data,
timestamp: Date.now()
});
return data;
}
}
3. Request Debouncing
javascript
function debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(null, args), delay);
};
}
const debouncedSearch = debounce(searchAddress, 300);
4. Batch Processing
When dealing with multiple requests, batch them efficiently:
javascript
async function batchGeocode(addresses, batchSize = 10) {
const results = [];
for (let i = 0; i < addresses.length; i += batchSize) {
const batch = addresses.slice(i, i + batchSize);
const promises = batch.map(address => searchAddress(address));
try {
const batchResults = await Promise.all(promises);
results.push(...batchResults);
} catch (error) {
console.error(`Batch ${i/batchSize + 1} failed:`, error);
}
// Add delay between batches to respect rate limits
if (i + batchSize < addresses.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
Error Handling
Implement robust error handling for common scenarios:
javascript
class OSApiError extends Error {
constructor(message, status, code) {
super(message);
this.name = 'OSApiError';
this.status = status;
this.code = code;
}
}
async function apiRequest(url) {
try {
const response = await fetch(url);
if (!response.ok) {
switch (response.status) {
case 401:
throw new OSApiError('Invalid API key', 401, 'UNAUTHORIZED');
case 403:
throw new OSApiError('API quota exceeded', 403, 'QUOTA_EXCEEDED');
case 404:
throw new OSApiError('Resource not found', 404, 'NOT_FOUND');
case 429:
throw new OSApiError('Rate limit exceeded', 429, 'RATE_LIMIT');
default:
throw new OSApiError(`API request failed: ${response.status}`, response.status, 'API_ERROR');
}
}
return await response.json();
} catch (error) {
if (error instanceof OSApiError) {
throw error;
}
// Network or parsing errors
throw new OSApiError('Network request failed', 0, 'NETWORK_ERROR');
}
}
Performance Optimization
1. Use Appropriate Map Styles
Choose the right map style for your use case:
Road_3857
: General purpose road mapOutdoor_3857
: Outdoor activities and hikingLight_3857
: Minimal design for data overlayNight_3857
: Dark theme for night mode
2. Optimize Tile Loading
javascript
// Preload tiles for better performance
function preloadTiles(map, bounds, minZoom, maxZoom) {
for (let z = minZoom; z <= maxZoom; z++) {
// Calculate tile bounds and preload
// Implementation depends on your mapping library
}
}
3. Monitor Usage
javascript
class OSApiMonitor {
constructor() {
this.requestCount = 0;
this.errors = 0;
this.startTime = Date.now();
}
logRequest(success = true) {
this.requestCount++;
if (!success) this.errors++;
}
getStats() {
const duration = Date.now() - this.startTime;
return {
requests: this.requestCount,
errors: this.errors,
successRate: ((this.requestCount - this.errors) / this.requestCount * 100).toFixed(2),
requestsPerMinute: (this.requestCount / (duration / 60000)).toFixed(2)
};
}
}
The Ordnance Survey APIs provide powerful geospatial capabilities for UK-focused applications. By following this tutorial and implementing the best practices outlined above, you can create robust, performant applications that leverage OS’s authoritative geographic data.
Remember to:
- Always respect rate limits and quotas
- Implement proper error handling and fallbacks
- Cache responses where appropriate
- Keep your API keys secure
- Monitor your usage regularly
For the most up-to-date documentation and API changes, always refer to the official OS Data Hub documentation.