{"id":22724446,"url":"https://github.com/vineyardbovines/openstreetmap-api","last_synced_at":"2025-03-29T23:18:03.710Z","repository":{"id":267342432,"uuid":"900950289","full_name":"vineyardbovines/openstreetmap-api","owner":"vineyardbovines","description":"Zero-dependency TypeScript package for working with openstreetmap data via the Overpass API","archived":false,"fork":false,"pushed_at":"2024-12-18T23:59:51.000Z","size":379,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-05T00:42:35.137Z","etag":null,"topics":["geojson","openstreetmap","openstreetmap-api","openstreetmap-data","overpass","overpass-api","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vineyardbovines.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-09T19:10:11.000Z","updated_at":"2024-12-18T23:59:54.000Z","dependencies_parsed_at":"2024-12-09T20:35:05.728Z","dependency_job_id":null,"html_url":"https://github.com/vineyardbovines/openstreetmap-api","commit_stats":null,"previous_names":["vineyardbovines/openstreetmap-api"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vineyardbovines%2Fopenstreetmap-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vineyardbovines%2Fopenstreetmap-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vineyardbovines%2Fopenstreetmap-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vineyardbovines%2Fopenstreetmap-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vineyardbovines","download_url":"https://codeload.github.com/vineyardbovines/openstreetmap-api/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246254149,"owners_count":20747949,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["geojson","openstreetmap","openstreetmap-api","openstreetmap-data","overpass","overpass-api","typescript"],"created_at":"2024-12-10T15:06:32.495Z","updated_at":"2025-03-29T23:18:03.690Z","avatar_url":"https://github.com/vineyardbovines.png","language":"TypeScript","readme":"# openstreetmap-api\n\nZero-dependency TypeScript package for working with [openstreetmap](https://www.openstreetmap.org) data via the [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API).\n\n## Features\n\n- 🏗️ **Query Builder** - Build complex Overpass queries with a type-safe interface\n- 🌐 **API Service** - Handle overpass API requests with automatic retries and fallbacks\n- 🗺️ **GeoJSON Support** - Direct conversion to GeoJSON formats\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cbold\u003eTable of Contents\u003c/bold\u003e\u003c/summary\u003e\n\n- [openstreetmap-api](#openstreetmap-api)\n  - [Features](#features)\n  - [Installation](#installation)\n  - [Quick Start](#quick-start)\n  - [API Service](#api-service)\n    - [Configuration Options](#configuration-options)\n    - [Output Formats](#output-formats)\n    - [Error Handling](#error-handling)\n    - [Fallback Endpoints](#fallback-endpoints)\n  - [Query Builder](#query-builder)\n    - [Tag Matching](#tag-matching)\n    - [Partial Key Matching](#partial-key-matching)\n    - [Geographic Filters](#geographic-filters)\n      - [Bounding Box](#bounding-box)\n      - [Around](#around)\n    - [Output Formats](#output-formats-1)\n    - [Complex Queries](#complex-queries)\n    - [Configuration](#configuration)\n    - [Error Handling](#error-handling-1)\n  - [Recipes](#recipes)\n    - [Find Cafes Within Walking Distance](#find-cafes-within-walking-distance)\n    - [Find Parks with Playgrounds and Water Features](#find-parks-with-playgrounds-and-water-features)\n    - [Find Cycle-Friendly Routes](#find-cycle-friendly-routes)\n    - [Find Historical Buildings by Age](#find-historical-buildings-by-age)\n    - [Count Amenities by Type](#count-amenities-by-type)\n\n\u003c/details\u003e\n\n## Installation\n\nWith your package manager of choice:\n\n```bash\nnpm install @vineyardbovines/openstreetmap\nyarn add @vineyardbovines/openstreetmap\npnpm add @vineyardbovines/openstreetmap\nbun add @vineyardbovines/openstreetmap\n```\n\n## Quick Start\n\n```typescript\nimport { OverpassQueryBuilder, overpass, OverpassOutput } from '@vineyardbovines/openstreetmap';\n\nconst query = new OverpassQueryBuilder()\n  .setTimeout(25)\n  .node()\n  .withTags([\n    { key: 'amenity', operator: '=', value: 'restaurant' },\n    { key: 'cuisine', operator: '=', value: 'italian' }\n  ])\n  .bbox([-0.1, 51.5, 0.0, 51.6])\n  .out('qt', true)\n  .build();\n\nconst response = await overpass(query, {\n  output: OverpassOutput.GeoJSON,\n  verbose: true\n});\n```\n\n## API Service\n\n**Basic usage**:\n\n```typescript\nimport { overpass, OverpassOutput } from '@vineyardbovines/openstreetmap-api';\n\n// Simple query\nconst response = await overpass(\n  '[out:json];node[\"amenity\"=\"cafe\"](51.5,-0.1,51.6,0.0);out;'\n);\n\n// With GeoJSON output\nconst geojson = await overpass(\n  '[out:json];node[\"amenity\"=\"cafe\"](51.5,-0.1,51.6,0.0);out;',\n  { output: OverpassOutput.GeoJSON }\n);\n```\n\n### Configuration Options\n\n```typescript\ninterface OverpassFetchOptions\u003cT extends OverpassOutput\u003e {\n  endpoint?: string;                 // Primary endpoint to use\n  fallbackEndpoints?: string[];      // Custom fallback endpoints\n  verbose?: boolean;                 // Enable detailed logging\n  userAgent?: string;                // Custom User-Agent header\n  output?: T;                        // Output format\n  retry?: {\n    maxRetries: number;              // Maximum retry attempts\n    initialDelay: number;            // Initial delay in ms\n    maxDelay: number;                // Maximum delay in ms\n    backoff: number;                 // Backoff multiplier\n  };\n}\n```\n\n**Default retry options:**\n\n```typescript\n{\n  maxRetries: 3,\n  initialDelay: 1000,  // 1 second\n  maxDelay: 10000,     // 10 seconds\n  backoff: 2           // Exponential backoff multiplier\n}\n```\n\n### Output Formats\n\nThe service supports multiple output formats through the `OverpassOutput` enum:\n\n```typescript\nenum OverpassOutput {\n  Raw = 'raw',                      // Raw Overpass JSON response\n  GeoJSON = 'geojson',              // Converted to GeoJSON format\n  Parsed = 'parsed',                // Parsed element tags\n  ParsedGeoJSON = 'parsedgeojson'   // Parsed tags in GeoJSON format\n}\n```\n\n'Parsed' converts string values to primitive values (string, number, boolean) when appropriate, i.e.\n\n```typescript\n{\n  building: \"yes\",\n  level: \"1\"\n}\n// becomes\n{\n  building: true,\n  level: 1\n}\n```\n\n### Error Handling\n\nThe API service uses `fetch`, so error handling is the same. There is a custom overpass error class to handle specific statuses.\n\n```typescript\ntry {\n    const response = await overpass(query, {\n        verbose: true,\n    retry: {\n        maxRetries: 5,\n      initialDelay: 2000\n    }\n  });\n} catch (error) {\n    if (error instanceof OverpassError) {\n        // Handle Overpass-specific errors\n    console.error('Overpass error:', error.message);\n  } else {\n      // Handle other errors\n    console.error('Request failed:', error);\n  }\n}\n```\n\nSetting `verbose: true` enables console logging for development, and will log:\n\n- Retry attempts\n- Endpoint switches\n- Error messages\n- Delay durations\n\n### Fallback Endpoints\n\nThe service automatically handles fallback endpoints based on the primary endpoint:\n\n- Main endpoints: Falls back to MainAlt1 → MainAlt2\n- Kumi endpoints: Falls back to KumiAlt1 → KumiAlt2 → KumiAlt3\n- Others: Falls back to Main → Kumi → France\n\nCustom fallback sequences can be specified:\n\n```typescript\nconst response = await overpass(query, {\n  endpoint: OverpassEndpoint.Main,\n  fallbackEndpoints: ['CustomAlt1', 'CustomAlt2']\n});\n```\n\n## Query Builder\n\n**Basic usage**:\n\n```typescript\nimport { OverpassQueryBuilder } from 'overpass-query-builder';\n\n// Create a new builder instance\nconst builder = new OverpassQueryBuilder();\n\n// Build a simple query for restaurants\nconst query = builder\n  .node()\n  .hasTag('amenity', 'restaurant')\n  .out('qt')\n  .build();\n```\n\n**Element types**:\n\n- `node()`: Query nodes\n- `way()`: Query ways\n- `relation()`: Query relations\n\n```typescript\n// Query specific node by ID\nbuilder.node('(123)');\n\n// Query multiple nodes\nbuilder.node('(123,456,789)');\n```\n\n### Tag Matching\n\n```typescript\n// Check tag existence\nbuilder.hasTag('amenity');\n\n// Match exact value\nbuilder.hasTag('amenity', 'restaurant');\n\nbuilder.withTag({\n  key: 'name',\n  operator: '~',\n  value: 'cafe.*',\n  regexModifier: 'case_insensitive'\n});\n\n// Numeric comparisons\nbuilder.withTag({\n  key: 'lanes',\n  operator: '\u003e=',\n  value: 2\n});\n```\n\n### Partial Key Matching\n\nUseful for finding elements where the key contains, starts with, or ends with a specific string:\n\n```typescript\n// Match any key containing 'toilet'\nbuilder.withPartialKey('toilet', 'contains');\n\n// Combined exact and partial matching\nbuilder.withTags([\n  { key: 'amenity', operator: '=', value: 'toilets' },\n  { key: 'toilets', keyMatchStrategy: 'contains' }\n], false); // false for OR logic\n```\n\n### Geographic Filters\n\n#### Bounding Box\n\nSupports both GeoJSON format and object format:\n\n```typescript\n// GeoJSON format [west, south, east, north]\nbuilder.bbox([-0.1, 51.5, 0.0, 51.6]);\n\n// Object format\nbuilder.bbox({\n  south: 51.5,\n  west: -0.1,\n  north: 51.6,\n  east: 0.0\n});\n```\n\n#### Around\n\n```typescript\n// Search within radius (meters) of a point\nbuilder.around(100, 51.5, -0.1);\n```\n\n### Output Formats\n\n```typescript\n// Default output\nbuilder.out();\n\n// Skeleton output (minimal data)\nbuilder.out('skel');\n\n// Quick output with tags\nbuilder.out('qt');\n\n// Include body\nbuilder.out('qt', true);\n```\n\n### Complex Queries\n\n```typescript\n// Find Italian restaurants in London with outdoor seating\nconst restaurantQuery = new OverpassQueryBuilder()\n  .setTimeout(30)\n  .node()\n  .withTags([\n    { key: 'amenity', operator: '=', value: 'restaurant' },\n    { key: 'cuisine', operator: '=', value: 'italian' },\n    { key: 'outdoor_seating', operator: '=', value: 'yes' }\n  ])\n  .bbox([-0.1, 51.5, 0.0, 51.6])\n  .out('qt', true)\n  .build();\n\n// Find all toilet facilities including partial matches\nconst toiletQuery = new OverpassQueryBuilder()\n  .node()\n  .withTags([\n    { key: 'amenity', operator: '=', value: 'toilets' },\n    { key: 'toilets', keyMatchStrategy: 'contains' }\n  ], false)\n  .around(100, 51.5, -0.1)\n  .out('qt', true)\n  .build();\n```\n\n### Configuration\n\nYou can set global options when creating the builder:\n\n```typescript\nconst builder = new OverpassQueryBuilder({\n  timeout: 30, // seconds\n  maxsize: 536870912 // bytes (512MB)\n});\n\n// Or update them later\nbuilder.setTimeout(25);\nbuilder.setMaxSize(1000000);\n```\n\n### Error Handling\n\nThe builder methods use method chaining and return `this`, allowing you to catch any errors at the build stage:\n\n```typescript\ntry {\n  const query = builder\n    .node()\n    .hasTag('amenity', 'restaurant')\n    .build();\n} catch (error) {\n  console.error('Failed to build query:', error);\n}\n```\n\n## Recipes\n\nExamples using the query builder with the API service.\n\n### Find Cafes Within Walking Distance\n\n```typescript\nimport { OverpassQueryBuilder, overpass, OverpassOutput } from '@vineyardbovines/openstreetmap-api';\n\nasync function findNearbyCafes(lat: number, lon: number, radius: number = 500) {\n  const query = new OverpassQueryBuilder()\n    .setTimeout(30)\n    .node()\n    .withTags([\n      { key: 'amenity', operator: '=', value: 'cafe' },\n      { key: 'opening_hours', existence: 'exists' }  // Only get cafes with opening hours\n    ])\n    .around(radius, lat, lon)\n    .out('qt', true)\n    .build();\n\n  return await overpass(query, {\n    output: OverpassOutput.ParsedGeoJSON,\n    retry: {\n      maxRetries: 3,\n      initialDelay: 1000\n    }\n  });\n}\n\n// Usage\nconst cafes = await findNearbyCafes(51.5074, -0.1278);\n```\n\n### Find Parks with Playgrounds and Water Features\n\n```typescript\nasync function findEquippedParks(bbox: [number, number, number, number]) {\n  const query = new OverpassQueryBuilder()\n    .way()\n    .withTags([\n      { key: 'leisure', operator: '=', value: 'park' },\n      { key: 'playground', existence: 'exists' }\n    ])\n    .bbox(bbox)\n    .out('qt')\n    .recurse('down')  // Get all nodes making up the ways\n    .out('qt')\n    .build();\n\n  return await overpass(query, {\n    output: OverpassOutput.ParsedGeoJSON\n  });\n}\n```\n\n### Find Cycle-Friendly Routes\n\n```typescript\nasync function findCycleRoutes(start: [number, number], radius: number) {\n  const query = new OverpassQueryBuilder()\n    .way()\n    .withTags([\n      { key: 'highway', existence: 'exists' },\n      {\n        key: 'bicycle',\n        operator: '~',\n        value: 'yes|designated',\n        regexModifier: 'case_insensitive'\n      }\n    ])\n    .around(radius, start[0], start[1])\n    .out('body')\n    .recurse('down')\n    .out('skel')\n    .build();\n\n  return await overpass(query, {\n    output: OverpassOutput.ParsedGeoJSON\n  });\n}\n```\n\n### Find Historical Buildings by Age\n\n```typescript\nasync function findHistoricalBuildings(\n  area: [number, number, number, number],\n  minYear: number\n) {\n  const query = new OverpassQueryBuilder()\n    .way()\n    .withTags([\n      { key: 'building', existence: 'exists' },\n      { key: 'historic', existence: 'exists' },\n      {\n        key: 'start_date',\n        operator: '\u003c',\n        value: minYear.toString()\n      }\n    ])\n    .bbox(area)\n    .out('body')\n    .build();\n\n  return await overpass(query, {\n    output: OverpassOutput.ParsedGeoJSON\n  });\n}\n```\n\n### Count Amenities by Type\n\n```typescript\nasync function countAmenitiesByType(bbox: [number, number, number, number]) {\n  const query = new OverpassQueryBuilder()\n    .node()\n    .withTag({\n      key: 'amenity',\n      existence: 'exists'\n    })\n    .bbox(bbox)\n    .out('qt', true)\n    .build();\n\n  const response = await overpass(query, {\n    output: OverpassOutput.Parsed\n  });\n\n  // Group by amenity type\n  return response.elements.reduce((acc, element) =\u003e {\n    const type = element.tags?.amenity;\n    if (type) {\n      acc[type] = (acc[type] || 0) + 1;\n    }\n    return acc;\n  }, {} as Record\u003cstring, number\u003e);\n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvineyardbovines%2Fopenstreetmap-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvineyardbovines%2Fopenstreetmap-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvineyardbovines%2Fopenstreetmap-api/lists"}