One file, one job: why four map apps
With KML Explore, ObjectGraph has four map apps on the Mac, iPhone and iPad that open each other’s files: GPX Explore, KML Explore, Geo Measure and Geo Elevation. Around them sit Geo Tracker, which records a walk as GPX, and Geo Marker, which saves places. People ask, fairly, why this isn’t one app with four tabs. It would be simpler to ship, and from the outside the four look alike: a map, something drawn on it, a panel of numbers.
The short answer is that each app is built around a different kind of data, and each kind comes with rules that the others would break. A GPX file is a recording. A KML file is a map someone made. A measurement is geometry I own and can change at will. An elevation profile is a question asked of the ground. This post goes through what each app keeps at its center, what it must never do, and what that means for the code.
Four data models
| App | What a document is | Its first rule |
|---|---|---|
| GPX Explore | GPX 1.1: tracks of timed points, routes, waypoints | Never make data up |
| KML Explore | A KML or KMZ map: an XML tree of folders, placemarks, styles and overlays | Never lose anything in the file |
| Geo Measure | Its own .geomeasure file: areas, distances and circles |
Measure on the ellipsoid, exactly |
| Geo Elevation | Its own .geoelevation file: shapes with the heights fetched for them |
Say where every height came from |
Two of them edit someone else’s format in place. Two of them have formats of their own and read everyone else’s as a starting point. That split alone decides most of the architecture.
GPX Explore: a recording
A GPX track is a sequence of points with times, one after another, from a watch, a phone or a bike computer:
<trkpt lon="-77.02016168273985" lat="38.92747367732227">
<ele>25.600000381469727</ele>
<time>2012-10-24T23:29:40.000Z</time>
<extensions>
<gpxtpx:TrackPointExtension>
<gpxtpx:hr>130</gpxtpx:hr>
</gpxtpx:TrackPointExtension>
</extensions>
</trkpt>
That is the first point of a run in Washington, D.C., from one of the public samples GPX Explore is tested on: a position, a height, a time and a heart rate.
The interesting numbers are all derived from time: moving time, pace, speed, climbing, heart rate against grade. So GPX Explore’s engine is a set of statistics, and they are where the work went. A stop is slower than 0.5 m/s held for at least 5 seconds. Maximum speed is the median of five, so one bad fix can’t set it. Climbing uses hysteresis of 0.75 m or twenty times the recording’s own jitter, whichever is larger, after spikes are taken out with a median of five within 12 seconds and 40 meters. Climbs are found by the rules riders know from their bike computers (ClimbPro’s 500 m at 3 %, Wahoo’s 250 m at 7 %) over a profile resampled every 10 meters.
The rule that matters most is that nothing is fabricated. A route point has no time, so GPX Explore
never gives it one. Treating a phone’s zero heights as missing changes how the file is read and
nothing else: the saved file still says <ele>0</ele>. Until the first edit, saving writes back the
exact bytes that were opened; after it, the writer keeps the extension elements it doesn’t understand.
None of that has anything to do with styles, folders or balloons. A KML viewer that opened a GPX file would draw the line and lose the reason the file exists.

KML Explore: a map someone made
A KML file has no time series to speak of. It is a document: folders of placemarks, each with a style, a description, a table of attributes, sometimes a picture laid on the ground or a legend pinned to the screen. Here is one placemark from the Census Bureau’s county file, Washington County, Maryland, shortened:
<Placemark id="cb_2023_us_county_20m">
<name><at><openparen>Washington<closeparen></name>
<visibility>1</visibility>
<description><![CDATA[<center><table>…</table></center>]]></description>
<LookAt>…</LookAt>
<styleUrl>#KMLStyler</styleUrl>
<ExtendedData>
<SchemaData schemaUrl="#kml_schema_ft_cb_2023_us_county_20m">
<SimpleData name="GEOID">24043</SimpleData>
<SimpleData name="NAMELSAD">Washington County</SimpleData>
<SimpleData name="ALAND">1185655254</SimpleData>
<SimpleData name="AWATER">24820607</SimpleData>
</SchemaData>
</ExtendedData>
<Polygon>
<tessellate>1</tessellate>
<altitudeMode>clampToGround</altitudeMode>
<outerBoundaryIs><LinearRing><coordinates>-78.342593,39.722658,0.0 …</coordinates></LinearRing></outerBoundaryIs>
</Polygon>
</Placemark>
Everything that makes a KML file look the way its author meant lives in that indirection. #KMLStyler can
lead to a Style or to a StyleMap with a normal and a highlighted style; an inline Style then
overrides it field by field; nothing passes from a folder to what is inside it. KML Explore resolves
styles the way Google Earth does, because a map that comes out in the wrong colors is a wrong map.
Descriptions are HTML, and a BalloonStyle can be a template with $[name] and $[GEOID] filled in
per placemark from its SchemaData, so the balloon is rendered as a web page, with scripts off and a content policy that
lets web pictures in only once you allow them. Folders can be radio folders, where showing one child
hides its siblings. Overlays are pictures in the file’s KMZ or on the web. Network links point at
other KML. None of this exists in GPX.

The document is the XML tree
The rule here is the opposite of a converter’s: open a file, change one point, save, and everything
else in the file must still be there, including extensions KML Explore has never heard of. Google’s
gx: elements, a vendor’s own namespace, a Region, a tour. So the document isn’t a model of KML at
all. It is the XML tree, read losslessly, and the model (features, geometries, resolved styles,
measurements) is read from the tree and never replaces it. Saving writes the tree back. The one thing
not kept is attribute order, because the system parser hands attributes over as a dictionary.
Edits work on the tree too. Every change runs inside doc.edit { e in … }, and before touching an
element the edit calls e.touch(element), which snapshots exactly that element. Undo restores the
snapshots, so undoing everything gives back exactly the tree that was read.
New elements take the indentation of their neighbors and go in KML 2.2’s schema order. After an edit,
only the features at or above what changed are read again: changing one county of 3,222 doesn’t
reparse the file.

The test for this is unglamorous and catches everything. A command-line build of the app’s KML core makes random edits of every kind on every sample file, with undos and redos in between, checks each state against a full rebuild and a write and read, and requires the exact original after undoing it all. The US counties file takes minutes.
What KML files are like in the wild
Real KML is messier than the specification, and a viewer that refuses a file because of a stray
ampersand is no use to the person who was sent it. KML Explore reads strictly first. When that fails,
it repairs a copy: the declared encoding, or UTF-8 with stray bytes read as Windows Latin 1; text
before the XML declaration removed; raw HTML in a description wrapped in CDATA; a bare & escaped and
HTML entities turned into character references; a file cut short closed where closing what is open
makes it whole, and refused where it doesn’t. Each repair is a sentence you can read over the map. An
unedited file is never rewritten. KMZ archives get the same treatment: a damaged directory is read
from the local headers as far as the files are whole, and sizes from the headers are checked against the archive before
anything is allocated, so a hostile Zip64 field can’t make the app reserve memory the file couldn’t fill.
Some files are odd in their own consistent way. The placemark above is one: the Census Bureau
publishes its KML through FME, which writes names as <at><openparen>Washington<closeparen>. KML
Explore shows “Washington” in the list, on the map and in the title, and keeps the file’s text for
saving.
Looking is not editing
One more rule that a GPX app never needs: switching layers on and off is the viewer’s business, not
the file’s. KML has a <visibility> element, and an app that wrote every eye you clicked into it
would change files you only looked at. In KML Explore the switches start from the file and stay on
your device. Writing them into the file is a separate, deliberate command, Save Shown Layers, and it
can be undone.
Geo Measure: geometry you own
Geo Measure answers a different question: how big is this, and how far is that. Its document is its
own .geomeasure file, a versioned GeoJSON, because its data is its own: areas, distances and
circles, in drawing order, each one a layer you can hide. GPX, KML and GeoJSON files open in it as
new, unsaved documents. It reads them as a starting point and never writes them back, which is the
right behavior for a measuring tool and exactly the wrong one for KML Explore.
The engine is geodesy. Distances are Vincenty’s inverse formula on the WGS84 ellipsoid, iterated to 10⁻¹², with a fallback near antipodal points where Vincenty doesn’t converge. Areas are the exact spherical excess on the WGS84 authalic sphere, the sphere with the ellipsoid’s surface area, which agrees with the ellipsoid to a part in 100,000. Both are tested against GeographicLib. Distances can follow roads and paths, and on an iPhone or iPad you can walk a boundary with GPS. The walk touches the document only when you stop, as one undo step, and a journal brings it back if the app quits halfway round a field.

KML Explore uses Geo Measure’s earth model for its own areas and lengths, so a county measures the same in both. The difference is what each app is for. In Geo Measure the shapes are the point and the file serves them. In KML Explore the file is the point and the measurements describe it.
Geo Elevation: the ground under it
Geo Elevation draws a path or an area and asks the ground about it. That makes it a raster problem rather than a vector one. A profile samples a height every 30 meters, between 100 and 1,000 samples, sent in batches of 500 to our own elevation server, which runs OpenTopoData over ASTER’s 30-meter global model and Mapzen’s mosaic of SRTM, NED, EU-DEM and others, with bathymetry. I wrote about that server in Two terabytes of terrain on one box. Areas get a grid of heights instead of points: 66,900 heights in 0.15 seconds, where the same work as single lookups took 3.1 seconds for 4,000. From the grid come slope bands (the avalanche maps’ 30°, 35° and 45°), contours at an interval of 1, 2, 2.5 or 5 times a power of ten, line of sight over the Earth’s bulge with standard refraction, and the terrain in 3D, on Apple Vision Pro at true size.
Its document is its own .geoelevation file, and the heights fetched for each shape are stored in it,
rounded to decimeters. A profile opened next year shows the same numbers, and Quick Look can draw the
file’s relief without asking the server anything.

Why not one app
Put the four rules side by side and the conflict is plain:
- GPX Explore must never invent a value. Geo Measure invents geometry all day; that’s what drawing is.
- KML Explore must never drop anything from a file. Geo Measure and Geo Elevation convert every foreign file into their own format, and should.
- Geo Elevation stores fetched data in its documents. KML Explore never fetches anything just because a file says so; web icons, overlay pictures and balloon images wait until you allow them.
A single app would need one document model. Either it’s a lossless KML tree, and GPX’s time series and Geo Measure’s shapes get shoehorned into placemarks, or it’s a neutral geometry model, and every KML file loses its styles and extensions the moment it’s saved. Apps that try to be one viewer for every format usually end up with the second: they open everything and save it as something else. That is fine for a quick look and wrong for someone whose job is the file.
The interface goes the same way. Each app has one object at its center: a track with its chart, a layer list, a measurement with its panel, a profile with its 3D view. Four of those in one window means modes, and modes mean a person editing a KML file can end up in a state that only makes sense for a recording.
What Apple’s platforms add
Two practical reasons come from the platforms rather than the data.
A file type has one owner. On the Mac and in Files, Launch Services follows one app’s declaration of a type, and that app’s Quick Look extensions draw the file’s thumbnail and preview. Geo Measure and Geo Elevation import KML and GPX as XML so they can open them; declared as XML, a type gets the system’s text thumbnail and the extension is never asked. So KML Explore owns KML and KMZ, declared as data rather than text, GPX Explore owns GPX, and each measuring app owns its own format. Every file type gets a picture of what is in it, drawn by the app that understands it, in that app’s color: violet for KML, green for GPX, sand for Geo Measure, the file’s own relief for Geo Elevation.
People search for the job. Someone with a KML file types “KML viewer”. Someone measuring a field types “area calculator”. An app named for one job is found by the people who have that job, and it can be judged on it.
There is a third, quieter one: each app ships on its own schedule. A fix to Geo Measure never waits for a KML Explore release, and a bug in KML’s repair code never holds up Geo Measure.
What they share, and how
The four share a great deal of code, but they don’t share a library. Engine code is ported: Geo Measure’s earth model is in GPX Explore, KML Explore and Geo Elevation; GPX Explore’s track editor follows Geo Measure’s editor; its routing went the other way; the GPS filter for walking a boundary is GPX Explore’s, on the rule that a fix found on one walk belongs in both. Each copy keeps the same file and type names, so comparing two apps’ versions shows only the real differences, and each is adapted to its own app. A shared package would tie every app’s build to a second codebase and every release to its schedule. Copying costs some duplication, and in exchange each app can be changed and shipped on its own.
They also hand work to each other. Geo Tracker records a walk and opens it in GPX Explore to edit. KML Explore exports placemarks as GPX, GeoJSON or CSV, and says what each format can’t hold. Geo Measure and Geo Elevation open GPX and KML files and start a measurement or a profile from them. KML has no circle, so KML Explore writes one the way Geo Measure does: a center point, a 120-point outline at the radius, and the radius as data, which Geo Measure reads back as a circle.
Which one to open
| You have | You want | Open |
|---|---|---|
| A GPX from a watch, phone or bike computer | The ride or walk: pace, climbs, heart rate, a cleaned-up track | GPX Explore |
| A KML or KMZ someone sent you | The map as it was made, layer by layer, and to edit it without breaking it | KML Explore |
| A field, a lot, a route on a map | How big, how far, printed or exported | Geo Measure |
| A trail or a hillside | The profile, the slope, what you can see from the top, the terrain in 3D | Geo Elevation |
| Nothing yet | To record where you go | Geo Tracker |
GPX Explore, KML Explore, Geo Measure and Geo Elevation are free to open files and look. Editing, exporting and the heavier tools are a one-time Pro purchase in each app. Geo Tracker is free.


