Zum Inhalt

Filialsuche über Kartenausschnitt

Eine Filialsuche lädt die Standorte im aktuell sichtbaren Kartenausschnitt und zeigt, wie weit jeder davon entfernt ist. Dieses Beispiel nutzt Suche per Rechteck mit der Bounding-Box der Karte sowie Addition für die Entfernung zu einem Referenzpunkt.

Nur serverseitig

Anders als die übrigen SmartMaps-Dienste authentifiziert sich Address Search über HTTP Basic Auth (Authorization: Basic base64("<SystemPartner>:<SecurityID>")). Diese Zugangsdaten lassen sich nicht auf eine Domain beschränken und dürfen daher niemals in den Browser gelangen — sie wären im Seitenquelltext lesbar und von überall nutzbar.

Rufen Sie die API aus Ihrem eigenen Backend auf und geben Sie nur das Ergebnis an Ihr Frontend weiter. Das Muster zeigt der Abschnitt Serverseitige Integration. Aus demselben Grund hat diese Seite kein im Browser lauffähiges Beispiel.

Anfrage

Lux/Luy sind die obere linke, Rlx/Rly die untere rechte Ecke des Kartenausschnitts. Branches wählt die zu suchenden Kategorien.

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"

Entfernung zu einem Referenzpunkt

Addition enthält den Punkt, von dem aus die Entfernung gemessen wird — typischerweise die Position des Besuchers oder die gesuchte Adresse:

Schlüssel Beschreibung
LocXForDistanceCalculation Längengrad des Referenzpunkts, von dem aus gemessen wird
LocYForDistanceCalculation Breitengrad des Referenzpunkts

Jeder Treffer enthält dann BasicData.Geo.Distance — die Luftlinienentfernung in Metern. Ohne Addition bleibt das Feld leer.

Trennzeichen innerhalb von Addition kodieren

Addition enthält eigene key=value-Paare, deren Trennzeichen URL-kodiert werden müssen: = wird zu %3D, & wird zu %26. Unkodiert werden die Paare als normale Query-Parameter gelesen und die Entfernung wird nicht berechnet.

Antwort

Auf die Felder gekürzt, die eine Filialsuche benötigt:

{
  "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" }
        ]
      }
    }
  ]
}

Nutzen Sie Identifiers.YMID als stabilen Schlüssel eines Datensatzes, Geo.XCoord/Geo.YCoord zum Setzen des Markers und Geo.Distance zum Sortieren der Trefferliste.

Serverseitige Integration

Die Zugangsdaten bleiben im Backend, der Browser spricht ausschließlich mit Ihrem eigenen Endpunkt. Der Ausschnitt setzt Node.js 18 oder neuer voraus (wegen des eingebauten fetch) sowie ESM — dazu "type": "module" in der package.json setzen:

// 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)

Das Frontend fragt dann den eigenen Endpunkt ab, sobald die Karte zur Ruhe kommt:

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

Jeder Datensatz enthält einen Copyright-Block, die Antwort zusätzlich AddressItemsCopyright. Zeigen Sie den mitgelieferten Copyright-Hinweis zusammen mit den Daten an — siehe Attribution & Lizenzierung.