Engineering
Shrinking an Offline Atlas Without Losing Detail
A smaller app should still know the same places. Lossless resource packing, a targeted FTS5 change, and comparisons against the previous artifacts reduced Mapsake's offline atlas without trimming the world.
Smaller should still contain the same world
Mapsake carries a substantial amount of geography inside the app. The place directory supports offline search and photo matching. Boundary files draw the map and help decide which country or region contains a coordinate. Those resources are part of the reason the atlas remains useful without a network geocoder.
They also occupy real space on the phone. Once storage became a focus, there were two obvious ways to make that number smaller: ship less geography, or change how the existing geography is represented. The recent work concentrated on the second.
The constraint was specific. Keep the places, names, coordinates, search behavior, and decoded boundary data. Save space through packaging and redundant bookkeeping, then prove that the smaller artifacts still contain what the app depends on.
Separate the installed atlas from the travel record
The first step was to distinguish three storage problems that can produce the same complaint.
The installed app includes read-only reference data. The user's travel record lives in writable stores. Temporary files belong to operations such as sharing or exporting. Shrinking one does not automatically shrink the others, and the safety rules are different.
A build can replace a bundled gazetteer after comparing it with the previous artifact. It cannot treat someone's saved trips as disposable reference material. Likewise, an old temporary file needs a known owner and lifecycle before a cleanup operation can remove it.
This article is about the bundled atlas. It is separate from the Storage Inspector and temporary-copy cleanup described in the iPhone storage guide. No user-store migration was needed for these packaging changes.
Compress the files the app already knows how to read
The app already had canonical geometry and reference JSON resources. Their repeated keys, coordinate syntax, and structural similarities made them reasonable candidates for lossless compression.
Five resources totaled 16,572,430 bytes in their canonical form. Their packed LZFSE copies total 5,657,390 bytes, a reduction of 10,915,040 bytes, or about 66% for those files.
La LZFSE algorithm in Apple's Compression framework lets the app expand the packed data locally. There is no server request for an unpacked world file and no requirement to download geography before the first offline trip.
The canonical files remain in source control. A build-time packer generates their compressed counterparts, and only the packed copies ship in the app. At runtime, the existing data-loading path expands them in memory before decoding them.
That last choice avoids trading one disk problem for another. Keeping a permanent expanded copy beside every compressed resource would recover much of the space that packaging had saved.
Lossless is a stronger claim than visually similar
Boundary simplification is a legitimate mapping technique, and Mapsake's earlier gazetteer work already uses it where appropriate. It changes the geometry, however. A tolerance that looks harmless at world scale can affect a small island or a point close to a border.
This storage pass did not simplify coordinates again. The acceptance condition was byte equivalence after decompression.
The validation compares each expanded resource with the exact canonical contents from the preceding build. SHA-256 checks make a changed decoded file visible even if a screenshot looks identical. The build gates also verify that the packed files match the canonical sources, so editing a JSON file without regenerating its packed copy cannot silently ship stale data.
This is a useful distinction between testing a transformation and regenerating its expectations. If both the input and expected result are replaced together, an accidental change can appear to pass. Keeping the preceding contents as the reference makes the claim meaningful: these are the same decoded resources in a smaller package.
A smaller SQLite index without fewer search results
The gazetteer database needed a different treatment. It is queried directly as a read-only SQLite file, so the improvement came from the structure of its full-text search index.
FTS5 normally stores per-column token counts in an additional backing table. SQLite's columnsize option can omit that bookkeeping. Mapsake's generated index now uses columnsize=0 while retaining its content and token-position information.
That is a targeted choice, not a general recommendation to strip every FTS option. SQLite can calculate missing token counts from stored text when asked, at additional cost. Ranking functions that depend on those counts can therefore become slower. Dropping position detail would be another change with different consequences for phrase and proximity queries.
Mapsake's search path was checked against the smaller index rather than assuming all FTS workloads would benefit. The relevant query behavior, aliases, phrase matching, and result identities remained part of validation.
The database decreased from 74,715,136 bytes to 69,586,944 bytes, saving 5,128,192 bytes. It still contains 234,045 cities, 3,861 regions, 252 country entries, 4,564 airports, and seven continents. Those country entries describe the app's geographic catalog, not a claim that every entry is a sovereign state or belongs in every personal country count.
Compare content, identity, and search structure
A row-count check is useful but insufficient. A table can contain the right number of rows with the wrong names, coordinates, or identifiers. An index can return a familiar city while dropping an alternate spelling used elsewhere.
The verification therefore compares several layers:
- Database integrity. The generated file must pass SQLite's integrity checks.
- Rows and row identifiers. Exhaustive comparisons protect both the values and the stable identities connecting search results to place records.
- Token positions. Digests cover the indexed terms and their positions, not just the visible display names.
- Search behavior. Phrase and alias tests check the query features the app actually uses.
- Timing. Existing search benchmarks check that a space saving did not introduce an unacceptable lookup cost.
The full-text comparison covers 242,729 search rows. This is a more useful acceptance condition than a few successful searches entered by hand, although interactive checks still matter for the screens consuming those results.
Keep the measurements attached to the thing measured
The two artifact changes can be summarized in decimal megabytes, with the exact byte counts above:
| Artifact | Before | After | Saved |
|---|---|---|---|
| Five reference files | 16.57 MB | 5.66 MB | 10.92 MB |
| Gazetteer database | 74.72 MB | 69.59 MB | 5.13 MB |
These are uncompressed database and packaged-resource measurements from the build work. They are not the App Store download size, a guarantee about Apple's device-specific install size, or the total shown for Mapsake plus a person's data in iPhone Storage. Packaging, executable changes, installed extensions, and local history affect those other numbers.
The gazetteer search benchmark also stayed close to its previous result: median 7.681 ms and p95 8.110 ms, compared with 7.719 ms et 8.120 ms before the index change. Those are simulator measurements for that search workload. Their value is the comparison under the same benchmark, not a promise that every search on every device takes eight milliseconds.
Clear measurement boundaries make a smaller result easier to trust. A modest, verified saving is more useful than a large percentage assembled from unrelated storage categories.
Compression moves work as well as bytes
Packing a resource saves disk space, but the app must decompress it before use. The decoded data still consumes memory, and loading remains real work. Lossless packaging does not make a large geometry object free to parse or draw.
That is why the resource change went through the existing correctness and performance gates as well as the byte checks. The relevant question is whether the app gets a worthwhile storage reduction while preserving its behavior and staying within its runtime budgets.
The same caution applies to the database. Omitting bookkeeping helps this measured search implementation. Another app using different ranking functions might make a different tradeoff after benchmarking its own queries.
Preserve the useful part of being offline
The goal was a smaller installed atlas that still knows the same places. The successful changes have narrow explanations: the same resource bytes in a compressed package, and the same searchable records without an unused layer of stored token counts.
Those are improvements the user should not have to manage. There is no extra geography download, no new choice about which regions to keep, and no request to rebuild a personal travel history to receive the smaller bundle.
The earlier offline gazetteer article explains how the names, points, and boundaries fit together. This work keeps that agreement intact while asking a more specific question: how much of the installed representation is necessary to preserve it?