- Notifications
You must be signed in to change notification settings - Fork0
Zero-dependency TypeScript package for working with openstreetmap data via the Overpass API
License
vineyardbovines/openstreetmap-api
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Zero-dependency TypeScript package for working withopenstreetmap data via theOverpass API.
- 🏗️Query Builder - Build complex Overpass queries with a type-safe interface
- 🌐API Service - Handle overpass API requests with automatic retries and fallbacks
- 🗺️GeoJSON Support - Direct conversion to GeoJSON formats
With your package manager of choice:
npm install @vineyardbovines/openstreetmapyarn add @vineyardbovines/openstreetmappnpm add @vineyardbovines/openstreetmapbun add @vineyardbovines/openstreetmap
import{OverpassQueryBuilder,overpass,OverpassOutput}from'@vineyardbovines/openstreetmap';constquery=newOverpassQueryBuilder().setTimeout(25).node().withTags([{key:'amenity',operator:'=',value:'restaurant'},{key:'cuisine',operator:'=',value:'italian'}]).bbox([-0.1,51.5,0.0,51.6]).out('qt',true).build();constresponse=awaitoverpass(query,{output:OverpassOutput.GeoJSON,verbose:true});
Basic usage:
import{overpass,OverpassOutput}from'@vineyardbovines/openstreetmap-api';// Simple queryconstresponse=awaitoverpass('[out:json];node["amenity"="cafe"](51.5,-0.1,51.6,0.0);out;');// With GeoJSON outputconstgeojson=awaitoverpass('[out:json];node["amenity"="cafe"](51.5,-0.1,51.6,0.0);out;',{output:OverpassOutput.GeoJSON});
interfaceOverpassFetchOptions<TextendsOverpassOutput>{endpoint?:string;// Primary endpoint to usefallbackEndpoints?:string[];// Custom fallback endpointsverbose?:boolean;// Enable detailed logginguserAgent?:string;// Custom User-Agent headeroutput?:T;// Output formatretry?:{maxRetries:number;// Maximum retry attemptsinitialDelay:number;// Initial delay in msmaxDelay:number;// Maximum delay in msbackoff:number;// Backoff multiplier};}
Default retry options:
{maxRetries:3,initialDelay:1000,// 1 secondmaxDelay:10000,// 10 secondsbackoff:2// Exponential backoff multiplier}
The service supports multiple output formats through theOverpassOutput
enum:
enumOverpassOutput{Raw='raw',// Raw Overpass JSON responseGeoJSON='geojson',// Converted to GeoJSON formatParsed='parsed',// Parsed element tagsParsedGeoJSON='parsedgeojson'// Parsed tags in GeoJSON format}
'Parsed' converts string values to primitive values (string, number, boolean) when appropriate, i.e.
{building:"yes",level:"1"}// becomes{building:true,level:1}
The API service usesfetch
, so error handling is the same. There is a custom overpass error class to handle specific statuses.
try{constresponse=awaitoverpass(query,{verbose:true,retry:{maxRetries:5,initialDelay:2000}});}catch(error){if(errorinstanceofOverpassError){// Handle Overpass-specific errorsconsole.error('Overpass error:',error.message);}else{// Handle other errorsconsole.error('Request failed:',error);}}
Settingverbose: true
enables console logging for development, and will log:
- Retry attempts
- Endpoint switches
- Error messages
- Delay durations
The service automatically handles fallback endpoints based on the primary endpoint:
- Main endpoints: Falls back to MainAlt1 → MainAlt2
- Kumi endpoints: Falls back to KumiAlt1 → KumiAlt2 → KumiAlt3
- Others: Falls back to Main → Kumi → France
Custom fallback sequences can be specified:
constresponse=awaitoverpass(query,{endpoint:OverpassEndpoint.Main,fallbackEndpoints:['CustomAlt1','CustomAlt2']});
Basic usage:
import{OverpassQueryBuilder}from'overpass-query-builder';// Create a new builder instanceconstbuilder=newOverpassQueryBuilder();// Build a simple query for restaurantsconstquery=builder.node().hasTag('amenity','restaurant').out('qt').build();
Element types:
node()
: Query nodesway()
: Query waysrelation()
: Query relations
// Query specific node by IDbuilder.node('(123)');// Query multiple nodesbuilder.node('(123,456,789)');
// Check tag existencebuilder.hasTag('amenity');// Match exact valuebuilder.hasTag('amenity','restaurant');builder.withTag({key:'name',operator:'~',value:'cafe.*',regexModifier:'case_insensitive'});// Numeric comparisonsbuilder.withTag({key:'lanes',operator:'>=',value:2});
Useful for finding elements where the key contains, starts with, or ends with a specific string:
// Match any key containing 'toilet'builder.withPartialKey('toilet','contains');// Combined exact and partial matchingbuilder.withTags([{key:'amenity',operator:'=',value:'toilets'},{key:'toilets',keyMatchStrategy:'contains'}],false);// false for OR logic
Supports both GeoJSON format and object format:
// GeoJSON format [west, south, east, north]builder.bbox([-0.1,51.5,0.0,51.6]);// Object formatbuilder.bbox({south:51.5,west:-0.1,north:51.6,east:0.0});
// Search within radius (meters) of a pointbuilder.around(100,51.5,-0.1);
// Default outputbuilder.out();// Skeleton output (minimal data)builder.out('skel');// Quick output with tagsbuilder.out('qt');// Include bodybuilder.out('qt',true);
// Find Italian restaurants in London with outdoor seatingconstrestaurantQuery=newOverpassQueryBuilder().setTimeout(30).node().withTags([{key:'amenity',operator:'=',value:'restaurant'},{key:'cuisine',operator:'=',value:'italian'},{key:'outdoor_seating',operator:'=',value:'yes'}]).bbox([-0.1,51.5,0.0,51.6]).out('qt',true).build();// Find all toilet facilities including partial matchesconsttoiletQuery=newOverpassQueryBuilder().node().withTags([{key:'amenity',operator:'=',value:'toilets'},{key:'toilets',keyMatchStrategy:'contains'}],false).around(100,51.5,-0.1).out('qt',true).build();
You can set global options when creating the builder:
constbuilder=newOverpassQueryBuilder({timeout:30,// secondsmaxsize:536870912// bytes (512MB)});// Or update them laterbuilder.setTimeout(25);builder.setMaxSize(1000000);
The builder methods use method chaining and returnthis
, allowing you to catch any errors at the build stage:
try{constquery=builder.node().hasTag('amenity','restaurant').build();}catch(error){console.error('Failed to build query:',error);}
Examples using the query builder with the API service.
import{OverpassQueryBuilder,overpass,OverpassOutput}from'@vineyardbovines/openstreetmap-api';asyncfunctionfindNearbyCafes(lat:number,lon:number,radius:number=500){constquery=newOverpassQueryBuilder().setTimeout(30).node().withTags([{key:'amenity',operator:'=',value:'cafe'},{key:'opening_hours',existence:'exists'}// Only get cafes with opening hours]).around(radius,lat,lon).out('qt',true).build();returnawaitoverpass(query,{output:OverpassOutput.ParsedGeoJSON,retry:{maxRetries:3,initialDelay:1000}});}// Usageconstcafes=awaitfindNearbyCafes(51.5074,-0.1278);
asyncfunctionfindEquippedParks(bbox:[number,number,number,number]){constquery=newOverpassQueryBuilder().way().withTags([{key:'leisure',operator:'=',value:'park'},{key:'playground',existence:'exists'}]).bbox(bbox).out('qt').recurse('down')// Get all nodes making up the ways.out('qt').build();returnawaitoverpass(query,{output:OverpassOutput.ParsedGeoJSON});}
asyncfunctionfindCycleRoutes(start:[number,number],radius:number){constquery=newOverpassQueryBuilder().way().withTags([{key:'highway',existence:'exists'},{key:'bicycle',operator:'~',value:'yes|designated',regexModifier:'case_insensitive'}]).around(radius,start[0],start[1]).out('body').recurse('down').out('skel').build();returnawaitoverpass(query,{output:OverpassOutput.ParsedGeoJSON});}
asyncfunctionfindHistoricalBuildings(area:[number,number,number,number],minYear:number){constquery=newOverpassQueryBuilder().way().withTags([{key:'building',existence:'exists'},{key:'historic',existence:'exists'},{key:'start_date',operator:'<',value:minYear.toString()}]).bbox(area).out('body').build();returnawaitoverpass(query,{output:OverpassOutput.ParsedGeoJSON});}
asyncfunctioncountAmenitiesByType(bbox:[number,number,number,number]){constquery=newOverpassQueryBuilder().node().withTag({key:'amenity',existence:'exists'}).bbox(bbox).out('qt',true).build();constresponse=awaitoverpass(query,{output:OverpassOutput.Parsed});// Group by amenity typereturnresponse.elements.reduce((acc,element)=>{consttype=element.tags?.amenity;if(type){acc[type]=(acc[type]||0)+1;}returnacc;},{}asRecord<string,number>);}
About
Zero-dependency TypeScript package for working with openstreetmap data via the Overpass API
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.