Skip to content

Commit 769c623

Browse files
committed
fix(geocoding-maptiler): request POI types so stations and POIs surface
MapTiler omits POIs from its default geocoding response, so "Köln Hbf" returned only addresses (Hauptbahnhof Düren, Am Hauptbahnhof Bonn, …) and never Köln Hauptbahnhof. proximity/country/language/fuzzyMatch made no difference — only an explicit `types` whitelist that includes `poi` does. With it, MapTiler returns "Köln Hauptbahnhof" at relevance 1.0 as the top hit while still returning addresses, places and admin areas. Send a fixed `types` whitelist (poi + address + admin levels) on geocode and autocomplete. Also document the (previously undocumented) INTEGRATION_GEOCODING_PROVIDER fallback chain in .env.example.
1 parent 7eef1e0 commit 769c623

3 files changed

Lines changed: 49 additions & 2 deletions

File tree

apps/api/src/services/__tests__/maptiler-geocoding.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,32 @@ describe("geocode", () => {
139139
});
140140
});
141141

142+
// request params
143+
144+
describe("request includes POI types", () => {
145+
it("geocode requests `types` including poi so station/POIs surface", async () => {
146+
mockFetch.mockResolvedValueOnce(mockOk(makeMaptilerResponse([])));
147+
const { maptilerGeocodingService } = await loadModule();
148+
149+
await maptilerGeocodingService.geocode("Köln Hbf");
150+
151+
// MapTiler omits POIs from its default response, so "Köln Hbf" returns only
152+
// addresses (Düren Hbf, …) unless we explicitly ask for poi.
153+
const types = new URL(String(mockFetch.mock.calls[0][0])).searchParams.get("types");
154+
expect(types?.split(",")).toContain("poi");
155+
});
156+
157+
it("autocomplete requests `types` including poi", async () => {
158+
mockFetch.mockResolvedValueOnce(mockOk(makeMaptilerResponse([])));
159+
const { maptilerGeocodingService } = await loadModule();
160+
161+
await maptilerGeocodingService.autocomplete("Köln Hb");
162+
163+
const types = new URL(String(mockFetch.mock.calls[0][0])).searchParams.get("types");
164+
expect(types?.split(",")).toContain("poi");
165+
});
166+
});
167+
142168
// autocomplete
143169

144170
describe("autocomplete", () => {

infra/docker/.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ MAPILLARY_TOKEN=
216216
# - MOTIS Transitous config generation during `pnpm openmapx services build motis`
217217
# TRANSITOUS_COUNTRIES=de,at,ch
218218
#
219+
# Geocoding provider order — comma-separated fallback chain, tried left to
220+
# right (on error OR zero results, the next provider is used). Valid names:
221+
# maptiler, photon, nominatim, pelias, motis, transitous, entur, db-ris.
222+
# Defaults to `maptiler`. `photon,maptiler` queries the self-hosted Photon
223+
# first (strong on OSM POIs / stations) and falls back to MapTiler for global
224+
# coverage Photon's regional extract doesn't include.
225+
# INTEGRATION_GEOCODING_PROVIDER=photon,maptiler
226+
219227
# Photon runtime download region.
220228
PHOTON_REGION=planet
221229

integrations/geocoding-maptiler/provider.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ import { resolvePoiIconPath } from "@openmapx/core";
1515

1616
const BASE_URL = "https://api.maptiler.com/geocoding";
1717

18+
// MapTiler omits POIs from its default geocoding response, so a query like
19+
// "Köln Hbf" returns only addresses (Düren Hbf, …) and never the station POI.
20+
// Requesting an explicit `types` whitelist that includes `poi` surfaces it
21+
// ("Köln Hauptbahnhof" at relevance 1.0) while keeping addresses, places, and
22+
// admin areas. Every name here is a valid MapTiler feature type — an unknown
23+
// one makes the API return 400.
24+
const SEARCH_TYPES =
25+
"poi,address,street,neighbourhood,municipality,municipal_district,locality,subregion,region,county,country,postal_code,place";
26+
1827
// Populated by setup(ctx) from the resolved integration config cascade.
1928
let apiKey: string | undefined;
2029
export function setMaptilerApiKey(value: string | undefined): void {
@@ -97,7 +106,7 @@ async function fetchMaptilerReverse(
97106

98107
export const maptilerGeocodingService: GeocodingProviderImpl = {
99108
async geocode(query: string, lang?: string, proximity?: LngLat): Promise<SearchResult[]> {
100-
const params: Record<string, string> = { limit: "10" };
109+
const params: Record<string, string> = { limit: "10", types: SEARCH_TYPES };
101110
if (proximity) params.proximity = `${proximity[0]},${proximity[1]}`;
102111
const data = await fetchMaptiler(query, params, lang);
103112
return data.features.map((f) => ({
@@ -128,7 +137,11 @@ export const maptilerGeocodingService: GeocodingProviderImpl = {
128137
},
129138

130139
async autocomplete(query: string, lang?: string): Promise<AutocompleteResult[]> {
131-
const data = await fetchMaptiler(query, { limit: "6", autocomplete: "true" }, lang);
140+
const data = await fetchMaptiler(
141+
query,
142+
{ limit: "6", autocomplete: "true", types: SEARCH_TYPES },
143+
lang,
144+
);
132145
return data.features.map((f) => {
133146
const category = f.properties?.categories?.[0];
134147
return {

0 commit comments

Comments
 (0)