How an Offline Map Knows Which Paris You Mean
Search in a map app looks like one operation: type a place, get a result. In an offline map it is two decisions. First, understand what the words mean. Then decide which local map files are worth searching.
GLMap keeps streets, addresses, and places in detailed regional maps. Alongside them is a compact world map containing countries, regions, and important cities. One C++ search engine uses these sources on iOS, Android, and in our online search service.
The map center normally tells the engine where to start. If you are in Warsaw and type cafe, that
is exactly what you want. But suppose you are planning a trip and type
Eiffel Tower Paris France. The map is still centered on Warsaw; the words clearly point to Paris.
Search only around Warsaw and the Eiffel Tower never appears. Search every regional map and even a simple autocomplete request may open files across the planet. The rule we arrived at is: the map center is a bias; geography written by the user is intent.
Over the past months we rebuilt GLMap search around that rule. It can now route the Eiffel Tower
query to France, find Wrocław from the keyboard-friendly spelling Wroclaw, keep Paris Optique
in Warsaw, and find a small village missing from the world map — all while the phone is in airplane
mode.
Two obvious answers, both wrong
Most map search starts near a point: the user's location, the center of the viewport, or a point
chosen by the application. That point is an excellent ranking signal. If the user types cafe, the
cafe around the corner should beat one in Paris.
It is a poor boundary.
People use the same search box to explore a trip, paste an address from a message, or find a hotel
before downloading a route. The Ritz London United Kingdom is explicit about where to look. A
search engine that insists on staying near Warsaw is not being local; it is ignoring part of the
query.
The opposite approach is just as bad. An offline app may have hundreds of regional map files. Open
every file for every query and a correct result eventually appears, but autocomplete becomes an
I/O benchmark. Worse, broad requests such as hotel lose their local meaning.
There is a third trap: treating every word as an ordinary name token. That works for an object
whose own indexed text contains every word. It does not work for the Eiffel Tower: Paris and
France describe where the object is, not what it is called. Conversely, in Paris Optique, Paris
really is part of the business name. The same word can be a subject or geographic context.
So the problem is not merely ranking candidates. Before ranking can begin, search has to decide where candidates should come from.
The world map became a directory
GLMap offline maps already include a compact world map. It knows countries, regions, and important localities, but not every street, address, cafe, or hotel. We use that asymmetry deliberately.
For a query with strong administrative context — a city plus a country, a comma-separated place, or an address with a postcode — the engine first resolves the geographic part only in the compact world map. It does not apply the application's category or custom object filters at this stage; they describe the final result, not the place that should contain it.
Once the engine resolves Paris, France, it searches the remaining subject inside the Paris area
in the detailed regional maps. The original center in Warsaw is preserved for ranking, but it no
longer decides which map is allowed to participate.
"Eiffel Tower Paris France" center: Warsaw
│
▼
compact world map: Paris, France
│
▼
bounded search around Paris
│
▼
Eiffel Tower
If the whole query is the locality — Paris France or Paris, France — the world-map result is
already the answer. There is no need to invent a separate POI or street subject just to enter the
second stage.
This is routing, not a second search implementation. The detailed search still uses the ordinary matcher, fallback ladder, scorer, and display rules. We changed the source of candidates, not the meaning of a good candidate.
Context has to earn the right to route
A heuristic that recognizes city and country names can easily become overconfident. We therefore defined the negative cases alongside the impressive ones.
Paris Optique from Warsaw must remain a local business search. A category-filtered query must not
reinterpret category text as a destination. hotel and bed and breakfast must stay near the map
center unless the user adds strong geography. The presence of a famous place name is not enough on
its own to move the search across a continent.
A complete one-word locality is a useful exception. If someone in Warsaw finishes typing
Wroclaw, the engine merges the exact Wrocław locality from the world map into the ordinary local
results. An unfinished prefix stays local, as do category and custom-filter requests. The matcher
accepts the keyboard-friendly spelling without requiring different map data.
At the same time, punctuation is useful evidence. People paste Paris, France; addresses arrive as
comma-separated fragments; postcodes are unusually strong geographic anchors. The planner uses
those signals without requiring users to learn a formal query grammar.
This produces behavior that feels unsurprising even though the engine is making a real planning decision:
| Query from Warsaw | Search area | Expected result |
|---|---|---|
cafe | Warsaw | nearby cafes |
Paris Optique | Warsaw | Paris Optique in Warsaw |
Wroclaw | world locality index | Wrocław, Poland |
Paris France | world locality index | Paris, France |
Eiffel Tower Paris France | Paris | Eiffel Tower |
221B Baker Street London United Kingdom | London | the London address |
The same cases run in both full search and autocomplete. That matters because autocomplete sees unfinished final words and returns only a small visible list; it is often where an apparently sound search plan first breaks.
The world map is intentionally incomplete
Using the compact world map as a directory creates an obvious edge case: small localities may not be in it.
The tempting fix is to fall back to a global scan of every detailed map. We deliberately did not do that. A rare village name should not turn one autocomplete keystroke into unbounded work across the planet.
Instead, explicit higher-level context supplies a bounded fallback. For Mochty-Smok Poland, the
engine can resolve Poland first, then inspect a limited number of relevant detailed maps inside that
country. The fallback has a fixed search budget; if it cannot establish a plausible geographic
anchor within that budget, it stops.
This distinction is important. The world map is the normal routing layer. The bounded regional fallback repairs omissions in that layer. Neither path means “search everything and hope.”
For ordinary global text search, map-level Bloom filters solve a different problem: they cheaply
reject detailed maps whose vocabulary definitely cannot match the query. The filters are useful for
candidate discovery, but they are not a substitute for understanding that London United Kingdom
is an explicit destination.
Eight visible results are not eight candidates
One of the more deceptive failures appeared after the geographic routing itself was working.
Production requests return at most 30 search results and 8 autocomplete suggestions. Initially, those limits also constrained parts of the internal search. Candidates rejected later by context validation or the autocomplete visible-text guard had already consumed the small pool. A valid result just below them never reached the final merge.
The fix was not to expose more results. It was to separate the candidate budget from the presentation limit. The engine now gathers a larger internal pool, merges and ranks it, and only then returns 30 or 8 items to the caller.
That sounds like an implementation detail, but it changed correctness. A production limit belongs at the boundary of the system; apply it earlier and it quietly becomes a ranking rule.
One engine, online or offline
The routing logic lives in the shared C++ search path used by modern iOS, Android, and server requests. Online and offline differ in where map data is stored, not in how the query is interpreted.
Offline search also reads the same memory-mapped map file that the renderer draws from. The word index, deduplicated tags, administrative relations, and renderable objects remain one artifact. A downloaded map does not need a separate search database to catch up before its streets and places become findable.
Other parts of the engine follow the same rule. Prominence is baked into map data so a globally important city can beat an obscure namesake without a live Wikipedia request. Localized street names are joined to addressed objects at map-build time. Synonyms and a controlled fallback ladder handle abbreviations and incomplete pasted addresses without switching the matcher to a noisy OR.
Geographic routing composes with those mechanisms. It does not replace them with an online-only parser or a platform-specific shortcut.
The corpus is the specification
Search changes tend to fix the query in front of you and break the query immediately beside it. We therefore keep the intended behavior in an executable corpus rather than a collection of anecdotes.
The current suite contains 228 real cases: city and POI ranking, multilingual names, complete addresses, keystroke-by-keystroke autocomplete flows, category searches, distant geographic context, and queries that must remain local. Of those, 227 are expected to pass. The remaining known failure is documented as a data or engine gap; if it starts passing unexpectedly, the run fails until we inspect and reclassify the behavior.
The geographic-context group contains 30 search and autocomplete cases. It covers Warsaw and
Sydney origins, Paris, Berlin, London and Wrocław destinations, localized and keyboard-friendly
names, a distant hotel, a street, a full address, the local Paris Optique counterexample, category
filters, and the bounded small-locality fallback. All 30 pass.
On the full corpus, the current engine runs at 130 ms p50 and 758 ms p95 on our test host. Simple city-plus-country resolution usually takes 4–14 ms; the bounded small-locality cases take about 22 ms. The harder complete-address autocomplete case is slower, which is precisely why the suite records latency instead of letting a global fallback hide behind a correct result.
The corpus is not a claim that search is finished. It is a growing, reviewable definition of what “better” means — including the cases where better means refusing to search farther.
A location should help, not trap
The important result is not that Eiffel Tower Paris France now works from Warsaw. It is that the
same rule also finds exact Wrocław from Wroclaw, keeps cafe and Paris Optique in Warsaw,
survives an 8-result autocomplete limit, and does not scan every downloaded map when the locality
is small.
That is the shape we want from offline infrastructure: use compact global data to make a plan, use detailed local data to answer it, and keep every fallback bounded. The user can search the place on screen, the place around them, or the place they explicitly named. Their location helps with the decision; it does not make the decision for them.
If you are building search, offline maps, navigation, or travel planning, try the same engine in your own product. Register at user.globus.software, create an API key, or write to [email protected] to discuss an offline or custom deployment.
