Store locator by map area
A store locator loads the branches inside the currently visible map area and shows how far each one
is from the visitor. This example uses Search by rectangle
with the map's bounding box, and Addition to get the distance to a reference point.
Server-side only
Unlike the other SmartMaps services, Address Search authenticates with HTTP Basic Auth
(Authorization: Basic base64("<SystemPartner>:<SecurityID>")). Those credentials cannot be
restricted to a domain, so they must never reach the browser — anyone could read them from
the page source and use them from anywhere.
Call the API from your own backend and expose only the result to your frontend. The server-side integration below shows the pattern. This is also why this page has no runnable in-browser example.
Request
Lux/Luy are the upper-left and Rlx/Rly the lower-right corner of the map view. Branches
selects the categories to search for.
curl -H "Authorization: Basic <TOKEN>" \
"https://yellowmap.de/api_rst/v2/addresssearch/ByBranchesAndRectangle?\
Branches=GACP&Top=2\
&Lux=8.552856&Luy=49.553725&Rlx=8.659973&Rly=48.623831\
&Addition=LocXForDistanceCalculation%3D8.47029%26LocYForDistanceCalculation%3D49.00129"
Distance to a reference point
Addition carries the reference point the distance is measured from — typically the visitor's
position or the searched address:
| Key | Description |
|---|---|
LocXForDistanceCalculation |
Longitude of the reference point |
LocYForDistanceCalculation |
Latitude of the reference point |
Each result then contains BasicData.Geo.Distance — the straight-line distance in metres.
Without Addition, that field stays empty.
Encode the separators inside Addition
Addition holds its own key=value pairs, so the inner separators must be URL-encoded:
= becomes %3D and & becomes %26. Unencoded, the pairs are parsed as top-level query
parameters and the distance is not calculated.
Response
Trimmed to the fields a store locator needs:
{
"Paging": { "Page": 1, "MaxPage": 1, "Count": 2, "MaxCount": 5 },
"AddressItems": [
{
"BasicData": {
"Identifiers": { "YMID": "XrU/pHy5NmEm3LD6Z6wh1Q==", "YMIDDecoded": "800059122" },
"Address": {
"CompanyName": "Geldautomat BBBank eG",
"Street": "Schlossstr. 2 a",
"Zip": "76646",
"City": "Bruchsal",
"Country": "D"
},
"Contact": { "Phone": "", "Email": null, "Url": "" },
"Geo": { "XCoord": "8.59422", "YCoord": "49.12565", "Distance": "16514" },
"BranchListElements": [
{ "BranchCode": "B090010016", "BranchText": "Geldautomaten" }
]
}
}
]
}
Use Identifiers.YMID as the stable key for a record, Geo.XCoord/Geo.YCoord to place the
marker, and Geo.Distance to sort the list.
Server-side integration
Keep the credentials in your backend and let the browser talk only to your own endpoint.
The snippet needs Node.js 18 or newer (for the built-in fetch) and ESM — set
"type": "module" in your package.json:
// server.js — minimal proxy (Node.js with Express)
import express from 'express'
const app = express()
const BASE = 'https://yellowmap.de/api_rst/v2/addresssearch'
// Credentials stay on the server, e.g. from environment variables
const token = Buffer
.from(`${process.env.SM_SYSTEM_PARTNER}:${process.env.SM_SECURITY_ID}`)
.toString('base64')
app.get('/api/stores', async (req, res) => {
const { lux, luy, rlx, rly, lon, lat } = req.query
const params = new URLSearchParams({
Branches: 'GACP',
Top: '50',
Lux: lux, Luy: luy, Rlx: rlx, Rly: rly,
})
// URLSearchParams encodes the inner "=" and "&" of Addition for us
if (lon && lat) {
params.set('Addition',
`LocXForDistanceCalculation=${lon}&LocYForDistanceCalculation=${lat}`)
}
const upstream = await fetch(`${BASE}/ByBranchesAndRectangle?${params}`, {
headers: { Authorization: `Basic ${token}` },
})
if (!upstream.ok) {
return res.status(upstream.status).json({ error: 'Address Search request failed' })
}
const data = await upstream.json()
// Forward only what the frontend needs — not the full payload
res.json(data.AddressItems.map(({ BasicData: b }) => ({
id: b.Identifiers.YMID,
name: b.Address.CompanyName,
street: b.Address.Street,
zip: b.Address.Zip,
city: b.Address.City,
lng: Number(b.Geo.XCoord),
lat: Number(b.Geo.YCoord),
distance: b.Geo.Distance ? Number(b.Geo.Distance) : null,
})))
})
app.listen(3000)
The frontend then requests its own endpoint whenever the map stops moving:
map.on('moveend', async () => {
const bounds = map.getBounds()
const query = new URLSearchParams({
lux: bounds.getWest(), luy: bounds.getNorth(),
rlx: bounds.getEast(), rly: bounds.getSouth(),
lon: reference.lng, lat: reference.lat,
})
const stores = await fetch(`/api/stores?${query}`).then(r => r.json())
// Records without a distance (request sent without Addition) sort to the end —
// a plain `a.distance - b.distance` would put them first.
stores.sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity))
// … render markers and the result list
})
Attribution
Results carry a Copyright block per record and an AddressItemsCopyright block for the response.
Display the provided copyright notice with the data — see
Attribution & licensing.