Skip to content

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.