cap-js-geoloc-plugin
A CAP plugin for geocoding, address correction and route calculation, driven by three annotations on the model, with the geocoder and router chosen in configuration from providers such as OSRM and Nominatim.
Three annotations on a CAP entity do the work: @AddressSearch puts a selection popup on an address field, @AddressAutoCorrect geocodes what was typed and writes the corrected address back into a second field, and @RouteCalculation fills in distance and duration between two coordinate fields. The provider is named in configuration rather than in code, a geocoder and a router chosen from OSRM, Nominatim, OpenRouteService, LocationIQ, HERE, Mapbox and Google Maps and set through GEOLOC_ environment variables; the defaults, OSRM for routing and Nominatim for geocoding, need no API key. Answers are cached, thirty days by default, and the pause between geocoding calls is configurable; the free Nominatim tier allows one request per second.
Facts
Where it sits
Who is behind it
Before choosing this
Conditions
LicenceSAP Developer License Agreement 3.2 CAP§ 1, 2, 3A cds-plugin is used with SAP CAP materials, or an artifact is explicitly identified as a CAP plugin or application.
- § 1 License. Productive customer applications are limited to SAP BTP or another SAP-licensed platform, and mass extraction to a non-SAP product is restricted except where solely required for SAP interoperability.
- § 2 Intellectual Property. The agreement retains SAP's rights in the covered materials and limits making APIs, tools or software available to third parties, while preserving ownership of customer applications subject to SAP's underlying rights.
- § 3 Artificial Intelligence Training. The agreement prohibits use of the covered materials and customer applications for training artificial-intelligence models or systems.
PracticalFunctional boundary
Nothing about it is readable from outside the package: there is no repository, no issue tracker, and the npm page carries no readme, so the README, the examples and the changelog can be read only from the tarball or a CDN mirror, and a bug has nowhere to be filed. Provider URLs are called from the plugin with API keys taken from environment variables in a .env file, so on BTP no call resolves through a destination and the keys live in the environment. The npm line stops at 1.0.1 on 3 November 2025; the changelog shipped inside that version is headed v2.1, so the version npm shows and the version the author counts are not the same.
What it declares
Every artifact
- Type
- CDS plugin
- Origin
- Independently maintained
- Runtime
- Node.js
As a CDS plugin
- Declared for
- >=8
- Distributed through
- npm
Connections
Describing a service so a Fiori client renders it without UI code
Geocoding, base maps and routes for business data
Download
Install
npm add cap-js-geoloc-pluginDerived from the package name. No recipe recorded; the README has the configuration.
README
quoted from cap-js-geoloc-plugin@1.0.1 under MIT, read🌍 GeolocPlugin - address search/validation and route calculation CAP Plugin
Geolocation plugin for SAP CAP.
Add annotations → The plugin allows, address search, address auto correct and route calculation (duration, distance)
Features
- Address Search: Automatic selection popup
- Address Auto-Correction: Automatic geocoding and normalization
- Route Calculation: Distances and durations in one click
- Optimized Cache: Drastic reduction of API calls
- Multi-transport: Car, bike, pedestrian (depends on chosen provider)
Installation
npm install cap-js-geoloc-plugin
In your package.json:
{
"cds": {
"requires": {
"cap-js-geoloc-plugin": {}
}
}
}
Create .env file based on .env.example define PROVIDERS and API keys
Usage
Annotate your fields
// db/schema.cds
entity Routes {
key ID : UUID;
// Automatic address search with @AddressSearch
@AddressSearch: true
startingPoint : String(500);
startingPointCoordinates : String(50);
@AddressSearch: true
endingPoint : String(500);
endingPointCoordinates : String(50);
// Automatic calculation with @RouteCalculation
@RouteCalculation: {
from: 'startingPointCoordinates',
to: 'endingPointCoordinates',
mode: 'transportMode',
distanceField: 'distance',
durationField: 'duration',
autoCalculate: true // Automatic calculation on save when coordinates change
}
distance : Decimal(10, 2);
duration : String;
transportMode : String default 'car';
}
Declare your service (as usual)
// srv/routes-service.cds
using { myapp as db } from '../db/schema';
service RoutesService {
@odata.draft.enabled
entity Routes as projection on db.Routes;
}
No JavaScript code needed!** The plugin works automatically with annotations only
The plugin automatically detects and creates:
@AddressSearchfields → Search popups@RouteCalculationfields → Calculate Route button- All actions (
searchAddress_*,calculateRoute,selectAddress) → Auto-generated - All handlers → Auto-registered
@AddressAutoCorrectautomatic correction adress according to country format and can be parsed to additional fields (country, street...)@AddressSearchand@AddressAutoCorrectcan be combined
Optional: If you need custom validation, create a service handler:
// srv/routes-service.js (OPTIONAL)
module.exports = function() {
this.before('CREATE', 'Routes', async (req) => {
// Your custom validation here
});
};
User Workflow
Address Search
- Type "Paris" in the
startingPointfield - Click on "🔍 Search Starting Point"
- Choose an address from the popup
- Done:
startingPointandstartingPointCoordinatesare automatically filled
Route Calculation
- Validate both addresses (starting point + destination)
- Click on "🚗 Calculate Route"
- Done:
distanceanddurationare automatically calculated
Advanced Features
Automatic Route Calculation on Save
Enable automatic route calculation when coordinates change by setting autoCalculate: true:
entity Routes {
@RouteCalculation: {
from: 'startingPointCoordinates',
to: 'endingPointCoordinates',
mode: 'transportMode',
distanceField: 'distance',
durationField: 'duration',
autoCalculate: true // Enables auto-calculation AND auto-refresh in Fiori UI
}
distance : Decimal(10, 2);
duration : String;
calculatedAt : DateTime; // Optional: will auto-refresh if present
mapImageURL : String; // Optional: will auto-refresh if present
}
How it works:
- When you CREATE a route with both coordinates filled, distance and duration are automatically calculated
- When you UPDATE the
fromortocoordinates, the route is automatically recalculated - Fiori UI auto-refreshes: SideEffects are automatically injected - no manual annotations needed!
- If the calculation fails, the save operation continues (non-blocking)
- The "Calculate Route" button is still available for manual recalculation
Auto-Correction with @AddressAutoCorrect
Automatically geocode and normalize addresses when users type them:
entity Routes {
@AddressAutoCorrect: {
correctedField: 'correctedStartingPoint',
coordinatesField: 'startingPointCoordinates',
cityField: 'startingPointCity',
streetField: 'startingPointStreet'
}
startingPoint : String(500); // User input
correctedStartingPoint : String(500); // Auto-filled with formatted address
startingPointCoordinates : String(50); // Auto-filled with coordinates
startingPointCity : String(100); // Auto-filled with city
startingPointStreet : String(100); // Auto-filled with street
}
How it works:
- User types: "21 rue liberte dijon" (with typos)
- On CREATE/UPDATE, the plugin automatically:
- Geocodes the address
- Fills
correctedStartingPoint: "21 Rue de la Liberté, 21000 Dijon, France" (formatted by country) - Fills
startingPointCoordinates: "47.3220, 5.0415" - Fills
startingPointCity: "Dijon" - Fills
startingPointStreet: "Rue de la Liberté"
Country-specific formatting:
The plugin automatically formats addresses according to each country's conventions:
- France: "23 rue des Pigeons, 67000 Strasbourg"
- Germany: "Musterstraße 23, 10115 Berlin"
- UK: "10 Downing Street, London SW1A 2AA"
- USA: "123 Main Street, New York, NY 10001"
- Spain: "Calle Mayor 23, 28013 Madrid"
- Italy: "Via Roma 123, 00100 Roma"
- And 20+ more countries...
The plugin detects the country from geocoding results (works with country names in multiple languages: English, French, German, Spanish, Italian, Dutch, and native names).
Manual override for non-existent addresses:
For new streets or addresses not yet in geocoding databases, you can bypass automatic geocoding:
entity Routes {
@AddressAutoCorrect: {
correctedField: 'correctedStartingPoint',
coordinatesField: 'startingPointCoordinates',
manualOverrideField: 'isManualAddress' // Flag to skip geocoding
}
startingPoint : String(500);
correctedStartingPoint : String(500);
startingPointCoordinates : String(50);
isManualAddress : Boolean default false; // Set to true to skip geocoding
}
How it works:
- If
isManualAddress = true→ geocoding is skipped, you can enter any address and coordinates manually - If
isManualAddress = false→ normal geocoding and validation - Useful for construction sites, new developments, or addresses not yet in mapping databases
Simple notation (only corrected field):
@AddressAutoCorrect: 'correctedStartingPoint'
startingPoint : String(500);
correctedStartingPoint : String(500);
Fiori Interface (Optional)
If you want to customize the Fiori UI, add annotations:
// app/routes/annotations.cds
using { RoutesService } from '../../srv/routes-service';
// Buttons in the header
annotate RoutesService.Routes with @(
UI.Identification : [
{
$Type : 'UI.DataFieldForAction',
Action : 'RoutesService.calculateRoute',
Label : '🚗 Calculate Route',
![@UI.Hidden] : {$edmJson: {$Path: 'IsActiveEntity'}}
}
]
);
Configuration (Optional)
Create a .env file to customize:
# Geocoding (Free Nominatim by default)
GEOLOC_GEOCODING_PROVIDER=nominatim
GEOLOC_GEOCODING_DELAY=1000 # Rate limit: ms between requests (1000 = 1 req/sec)
# Routing (Free OSRM by default)
GEOLOC_ROUTING_PROVIDER=osrm
GEOLOC_ROUTING_PROFILE=car # car | bike | foot
# Cache
GEOLOC_CACHE_ENABLED=true
GEOLOC_CACHE_TTL=2592000 # 30 days
.env.example:
# =============================================================================
# GeolocPlugin Configuration
# =============================================================================
# -----------------------------------------------------------------------------
# ROUTING - Real distance calculation by route
# -----------------------------------------------------------------------------
# Routing provider: 'osrm' | 'openrouteservice' | 'locationiq' | 'googlemaps' | 'mapbox' | 'here'
# - osrm: FREE, unlimited, no API key required (recommended)
# - openrouteservice: Free up to 2,500 req/day (API key required)
# - locationiq: Free up to 5,000 req/day (API key required)
# - googlemaps: Pay-as-you-go (API key required, $5 per 1000 requests)
# - mapbox: Free up to 100,000 req/month (API key required)
# - here: Free up to 250,000 req/month (API key required)
GEOLOC_ROUTING_PROVIDER=osrm
# Default transport profile: 'car' | 'bike' | 'foot'
GEOLOC_ROUTING_PROFILE=car
# Fallback to OSRM on error: 'true' | 'false'
GEOLOC_ROUTING_FALLBACK=true
# -----------------------------------------------------------------------------
# API KEYS (optional - only if you're not using OSRM)
# -----------------------------------------------------------------------------
# OpenRouteService API Key (free: https://openrouteservice.org/dev/#/signup)
OPENROUTESERVICE_API_KEY=
# LocationIQ API Key (free: https://locationiq.com/register)
LOCATIONIQ_API_KEY=
# Google Maps API Key (pay-as-you-go: https://console.cloud.google.com/google/maps-apis)
# Enable "Directions API" in your Google Cloud Console
GOOGLE_MAPS_API_KEY=
# Mapbox API Key (free: https://account.mapbox.com/auth/signup/)
# Free tier: 100,000 requests/month
MAPBOX_API_KEY=
# HERE API Key (free: https://developer.here.com/sign-up)
# Free tier: 250,000 requests/month
HERE_API_KEY=
# -----------------------------------------------------------------------------
# GEOCODING - Address validation and normalization
# -----------------------------------------------------------------------------
# Geocoding provider: 'nominatim' | 'openrouteservice' | 'locationiq' | 'googlemaps' | 'here' | 'mapbox'
# - nominatim: FREE, limited to 1 req/sec (no API key required, recommended for development)
# - openrouteservice: Uses your existing quota (API key required, shared with routing)
# does not allow full address search in free mode
# - locationiq: Uses your existing quota (API key required, shared with routing)
# - googlemaps: Pay-as-you-go, excellent quality (API key required, enable "Geocoding API")
# - here: Free up to 250,000 req/month, excellent quality (API key required)
# - mapbox: Free up to 100,000 req/month, excellent quality (API key required)
# Note: API keys for googlemaps, here, and mapbox are shared with routing if configured
GEOLOC_GEOCODING_PROVIDER=nominatim
# Rate limiting: delay in milliseconds between geocoding requests (applies to ALL providers)
# Default: 1000ms (1 second) - respects Nominatim usage policy
# Set to 0 to disable rate limiting (useful for paid APIs with higher limits)
# Examples: 1000 = 1 req/sec | 500 = 2 req/sec | 200 = 5 req/sec | 0 = no limit
GEOLOC_GEOCODING_DELAY=1000
# -----------------------------------------------------------------------------
# CACHE - API call optimization
# -----------------------------------------------------------------------------
# Enable cache: 'true' | 'false'
GEOLOC_CACHE_ENABLED=true
# Cache time-to-live in seconds (30 days = 2592000)
GEOLOC_CACHE_TTL=2592000
# Maximum number of cache entries
GEOLOC_CACHE_MAX_SIZE=1000
# =============================================================================
# CONFIGURATION EXAMPLES
# =============================================================================
# -----------------------------------------------------------------------------
# Example 1: FREE configuration (OSRM + Nominatim)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=osrm
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_GEOCODING_PROVIDER=nominatim
# GEOLOC_CACHE_ENABLED=true
#
# → 100% free, unlimited (OSRM)
# → No API key required
# -----------------------------------------------------------------------------
# Example 2: Production with OpenRouteService (2,500 req/day)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=openrouteservice
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_ROUTING_FALLBACK=true
# GEOLOC_GEOCODING_PROVIDER=openrouteservice
# OPENROUTESERVICE_API_KEY=your-api-key-here
# GEOLOC_CACHE_ENABLED=true
#
# → Routing AND Geocoding via OpenRouteService (shared quota)
# → Fallback to OSRM if routing quota exceeded
# -----------------------------------------------------------------------------
# Example 3: Production with LocationIQ (5,000 req/day)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=locationiq
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_ROUTING_FALLBACK=true
# GEOLOC_GEOCODING_PROVIDER=locationiq
# LOCATIONIQ_API_KEY=your-api-key-here
# GEOLOC_CACHE_ENABLED=true
#
# → Routing AND Geocoding via LocationIQ (shared quota)
# → Fallback to OSRM if routing quota exceeded
# -----------------------------------------------------------------------------
# Example 4: Bike application
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=osrm
# GEOLOC_ROUTING_PROFILE=bike
# GEOLOC_CACHE_ENABLED=true
#
# → Uses bike paths for routing
# -----------------------------------------------------------------------------
# Example 5: Mix OSRM (routing) + LocationIQ (geocoding)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=osrm
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_GEOCODING_PROVIDER=locationiq
# LOCATIONIQ_API_KEY=your-api-key-here
# GEOLOC_CACHE_ENABLED=true
#
# → Free unlimited routing (OSRM)
# → Geocoding via LocationIQ (5,000 req/day)
# → Optimal: LocationIQ quota saved for geocoding only
# -----------------------------------------------------------------------------
# Example 6: Disable cache (development/debug)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=osrm
# GEOLOC_GEOCODING_PROVIDER=nominatim
# GEOLOC_CACHE_ENABLED=false
#
# → All requests call the API (useful for testing)
# -----------------------------------------------------------------------------
# Example 7: Production with Google Maps Directions API
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=googlemaps
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_ROUTING_FALLBACK=true
# GOOGLE_MAPS_API_KEY=your-api-key-here
# GEOLOC_GEOCODING_PROVIDER=nominatim
# GEOLOC_CACHE_ENABLED=true
#
# → High accuracy routing via Google Maps (pay-as-you-go)
# → Fallback to OSRM if quota exceeded
# → Free geocoding via Nominatim
# -----------------------------------------------------------------------------
# Example 8: Production with Mapbox Directions API (100k req/month free)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=mapbox
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_ROUTING_FALLBACK=true
# MAPBOX_API_KEY=your-api-key-here
# GEOLOC_GEOCODING_PROVIDER=nominatim
# GEOLOC_CACHE_ENABLED=true
#
# → Routing via Mapbox (100,000 free requests/month)
# → Fallback to OSRM if quota exceeded
# → Free geocoding via Nominatim
# -----------------------------------------------------------------------------
# Example 9: Production with HERE Maps Routing API (250k req/month free)
# -----------------------------------------------------------------------------
# GEOLOC_ROUTING_PROVIDER=here
# GEOLOC_ROUTING_PROFILE=car
# GEOLOC_ROUTING_FALLBACK=true
# HERE_API_KEY=your-api-key-here
# GEOLOC_GEOCODING_PROVIDER=nominatim
# GEOLOC_CACHE_ENABLED=true
#
# → Routing via HERE Maps (250,000 free requests/month)
# → Fallback to OSRM if quota exceeded
# → Free geocoding via Nominatim
# → Best free tier option (highest quota)
Available Providers
Routing Providers
| Provider | Free | Limit | API Key | Use Case |
|---|---|---|---|---|
| OSRM | Yes | Unlimited* | No | Development |
| OpenRouteService | 2500/day | 2500 req/day | Yes | Production |
| LocationIQ | 5000/day | 5000 req/day | Yes | Production |
| Google Maps | Pay-as-you-go | $5/1000 req | Yes | Enterprise |
| HERE | 250000/month | 250k req/month | Yes | Production |
| Mapbox | 100000/month | 100k req/month | Yes | Production |
Geocoding Providers (Address Search)
| Provider | Free | Limit | API Key | Quality |
|---|---|---|---|---|
| Nominatim | Yes | 1 req/sec* | No | Good (OSM data) |
| LocationIQ | 5000/day | 5,000 req/day | Yes | Good (OSM data) |
| OpenRouteService | 2500/day | 2,500 req/day | Yes | Limited features |
| Google Maps | Pay-as-you-go | $5/1,000 req | Yes | Excellent |
| HERE | 250000/month | 250k req/month | Yes | Excellent |
| Mapbox | 100,000/month | 100k req/month | Yes | Excellent |
*Fair use policy applies (no SLA)
Programmatic API (Optional)
If you want to use the plugin directly in your code:
Geocoding
const GeolocPlugin = require('geoloc-plugin');
// Validate an address
const result = await GeolocPlugin.geocoding.validateAddress('Paris, France');
// → { formatted: "Paris, Île-de-France, France", coordinates: "48.8566,2.3522", ... }
// Search for multiple suggestions
const results = await GeolocPlugin.geocoding.searchAddress('Lyon', 10);
// → { found: true, count: 10, results: [...] }
Routing
// Calculate a route
const route = await GeolocPlugin.routing.calculateRoute(
'48.8566,2.3522', // Paris
'45.7640,4.8357', // Lyon
'car'
);
// → { distanceKm: 465.87, durationMinutes: 297, formattedDuration: "4h 57min" }
🎓 Complete Documentation
- README.md - This file
- EXAMPLES.md - Detailed examples
- CHANGELOG.md - Version history
Development
Contributing
Contributions are welcome! Create an issue or pull request.
History
Other 2
Notes
Users need to:
- Annotate fields with
@AddressSearch: true - Include the
AddressSuggestionsentity viausing { geoloc.plugin } from 'geoloc-plugin/cds/address-suggestions-mixin' - Declare actions in the CDS service
🔮 Future Roadmap
- Automatic generation of actions in CSN
- Support for additional providers (on request)
Others providing
- com.sap.cds:cds-services-bomcom.sap.cds:cds-services-bomThe Maven bill of materials for CAP Java, pinning every com.sap.cds module in a release, from the API and core runtime to the Spring Boot starters and the CQN engine, to the same version.sap official, capMaven bill of materials pinning CAP Java module versions
- urp-vocaburp-vocabA CDS vocabulary for User-Rendered Patterns, with @urp.display annotations describing views, card templates, layouts and sections on an entity, consumed by the author's own matching renderer.independent, capCDS vocabulary for User-Rendered Patterns display annotations
- @mpah/enum-value-help@mpah/enum-value-helpA CAP plugin that generates value help for enum fields from an @enumValueHelp annotation, serving the enum's values from one generated entity instead of hand-written OData annotations.independent, capCAP plugin generating value help for enum fields from an annotation
- @sap/cds-fiori@sap/cds-fioriFiori draft handling and UI-facing behaviour for CAP services.sap official, capFiori draft handling and UI-facing behaviour for CAP services
- @sap/cds@sap/cdsThe CAP Node.js runtime, which compiles the CDS model, serves the services declared in it, answers generic requests before any handler runs, and loads every cds-plugin.sap official, capCAP Node.js runtime
- @sap/generator-fiori@sap/generator-fioriThe single entry point for generating a Fiori application — the user picks a template and the generator produces the project around it.sap official, capEntry point for generating a Fiori application from a template
- @sap-ux/cds-annotation-parser@sap-ux/cds-annotation-parserParses annotations out of CDS source, so tooling can read what a model says about its UI without compiling it.sap official, capParser for annotations in CDS source
- @sap/ux-cds-compiler-facade@sap/ux-cds-compiler-facadeWraps the CAP CDS compiler behind one set of APIs for annotation handling, so tooling reads a model without tracking which compiler version is installed.sap official, capWrapper around the CDS compiler for annotation handling
- @sap-ux/cds-odata-annotation-converter@sap-ux/cds-odata-annotation-converterConverts CDS annotations into their OData form, which is the translation Fiori tooling needs to reason about a service the way a client sees it.sap official, capConverter from CDS annotations to their OData form
- @sap/ux-cds-odata-language-server-extension@sap/ux-cds-odata-language-server-extensionAdds the OData annotation features to @sap/vscode-cds, the CDS plug-in for Visual Studio Code: completion, micro-snippets, diagnostics, vocabulary documentation, Go to Definition and an i18n check inside a .cds file.sap official, capOData annotation features for the @sap/vscode-cds extension
- @sap-ux/fiori-annotation-api@sap-ux/fiori-annotation-apiThe programmatic API Fiori tooling uses to read and write annotations on a project, rather than editing the files as text.sap official, capProgrammatic API for reading and writing project annotations
- @sap-ux/odata-vocabularies@sap-ux/odata-vocabulariesCarries the OASIS and SAP OData vocabularies as a package, with a VocabularyService for asking which terms exist, where they apply and what their types and properties are.sap official, ui5OASIS and SAP OData vocabularies with a VocabularyService
- cap-user-infocap-user-infoResolves the createdBy and modifiedBy of a managed entity to a stored user record of email, given name and family name, and renders the two fields as links with a quickview.independent, capUser details and quickview links for createdBy and modifiedBy
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
History in the atlas
- The package was re-read on 2026-09-18. Its README, examples file and changelog name no destination and no destination service; providers are addressed by URL with API keys read from GEOLOC_ environment variables in a .env file, which is what the entry's own when_not_to_use already said. A requires edge points at what the README names, and this one pointed at the opposite.
- capability:fiori-annotations: generated actions, SideEffects under autoCalculate, search SideEffects left to the developer
- one_line, what_it_solves, when_not_to_use
- capability:fiori-annotations, capability:location-services
- First seen.
For your agent
- Ref
artifact:sebsst-cap-js-geoloc-plugin- JSON
- /artifacts/sebsst-cap-js-geoloc-plugin.json
- MCP
- Connecting an agent