Guide · Maps & terrain12 min read

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.

GPX Explore on the Mac charting heart rate under a run, with the route card showing distance, moving time, pace, gain, loss and heart rate

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>&lt;at&gt;&lt;openparen&gt;Washington&lt;closeparen&gt;</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.

A search for Washington in the US counties KMZ in KML Explore on the Mac: one Search Results group of 70 matching counties in the layer list, and only those counties on the map

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.

Editing the playing field at Camden Yards in KML Explore on the Mac: its outline and the infield’s points as handles on the map, the edit bar at the top, and the Points table under the map

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.

Geo Measure on the Mac: LaGuardia Airport outlined in forty-five points on a satellite map, with the panel showing its area and perimeter

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.

Geo Elevation on the Mac: the Mount Washington area with slope bands and contour lines, two trails crossing it and their profiles in the panel

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.

Gavi Narra ·

All guides →