ZYRNTOPO
Documentation

ZYRNTOPO Documentation

How to use ZYRNTOPO, the offline topographic map for Android and the browser, and ZYRNMAPS, its native Android sibling. The core app is free and needs no account. Your map, your tracks and your markers stay on your device unless you choose to share them.

The desktop app has its own page: ZYRN Workstation. So does the free TAK server you can run yourself: TAK server.

Free and Pro. Free: offline maps with unlimited downloads, GPS and track recording, drawing and measuring, coordinates and the MGRS grid, 3D terrain, viewshed, true-scale PDF map sheets, photo waypoints, live weather radar, your own WMS, WMTS and WFS map services, GPX, KML, GeoJSON and CSV in and out, and your markers and tracks synced between your own signed-in devices. ZYRNTOPO Pro adds Team Sync with Team Chat, nearby TAK devices, TAK servers, the Meshtastic radio, MAVLink drones and the power lines overlay. Pro is $79.99 once, permanent, or $2.99 a month until you cancel; either covers Android, the web app and ZYRN Workstation on one account. Anything that needs Pro carries a PRO tag before you tap it. See Account & Pro.

Getting Started

Install, first launch, and finding your way around.

Map Interface

Base layers, overlays, 3D terrain and controls.

Offline Maps

Download an area before you lose signal.

Team Sync

Internet, TAK, Meshtastic and drones.

Search & Rescue

Periods, team members, clues and gear.

Import & Export

GPX, KML, GeoJSON, CSV and PDF.

ZYRNMAPS

The native Android client.

Plugins

Add tools to ZYRNTOPO, or build your own.


Installation

ZYRNTOPO runs on Android and in the browser. Use whichever fits the device in your hand; your account and Pro carry across both.

Android

  1. Install from Google Play. Updates arrive automatically.
  2. On a Fire tablet, or any device without Google Play, install from the Amazon Appstore instead. It handles installation and updates for you.
  3. Open the app. A short setup asks how you will use it, your units and your starting map; then it is ready, with no sign-in.

The points-of-interest dataset (peaks, water, trailheads, shelters and more, about 340 MB) is not part of the download from the store. The app fetches it once in the background the first time it runs, so POI search needs a connection on that first launch and none after it. When a newer dataset is published, the saved copy updates itself.

Web / PWA

Open app.zyrntopo.com in any modern browser. For offline use, install it as an app: in Chrome or Edge, click Install app in the address bar; in Safari, use Share → Add to Home Screen. See Web / PWA for what the browser version can and cannot do.

Desktop and server

On Windows and Linux, the desktop app is ZYRN Workstation, installed with one command from its page. To run your own TAK server for your team, see the TAK server page. Both install commands check what they download before running it.

First Launch

The first time you open ZYRNTOPO, a short setup asks three things: how you will use it (recreation, search and rescue, and so on), your theme and units, and which map to start on. Without Pro, a last step shows what Pro adds; you can skip it. Everything you choose can be changed later in Settings.

The app then explains why it wants your location and asks with the system dialog. Location is used for your position on the map, tracks, route following and nearby field information. It is foreground-only except while you record a track: the first time you tap Record Track, the app asks separately for background location so the recording continues with the screen off. The microphone is requested the first time you record a voice note, the camera the first time you scan a QR code, and Bluetooth the first time you connect a radio or receiver.

You can decline any permission. The app still works; only the feature that needed it is unavailable until you grant it in Settings.

The default map is VersaTiles Eclipse, a detailed street and terrain map. It is hosted, so it needs a connection until you download an area. Your base layer, map position and zoom are remembered and restored every time you open the app.

Finding Your Way Around

  • The map fills the screen. Drag to pan, pinch or scroll to zoom, twist with two fingers to rotate.
  • The bottom bar holds Tools (drop a marker, draw a line, measure, see point info), Layers (base map, overlays, Map Styling) and Items (everything you have created, in folders).
  • The menu is grouped by task:
    • Map: Save Map, Clear Map, Map Items, Saved Maps and Map Data (import a map, download a region, open one from a link).
    • Tools: Measure, Point Info, Download Map Area, Print to PDF, Gear / Pack List and Plugins.
    • Map setup: Map Layers, Settings, Import Map, Export Map and Clear Tile Cache.
    • Team: Team Members, Team Sync, Meshtastic radio and Team Chat.
    • Links to this documentation, your Account, Help & FAQ and Send feedback.

Long-press anywhere on the map to hide the on-screen controls for an uninterrupted view; long-press again, or right-click on a computer, to bring them back. They also come back the next time the app opens.

Help & FAQ in the menu answers the common questions and works offline, because it is built into the app.

Account & Pro

An account is optional. Without one, everything free works and nothing leaves your device unless you share it. Signing in adds two things:

  • Your map items on all your devices. Markers, lines, areas and tracks sync between the devices you are signed in on, in real time. Turn it off with Sync map items on the Account page.
  • Pro, if you buy it. The license is checked on your device and keeps working offline; it refreshes in the background when you are online.

Sign in with an email address and password, or with Google, Microsoft or Yahoo. Manage your account, devices and purchases from Menu → Account or on zyrntopo.com/account.

Bought Pro but the app says Free? Open Settings → Account and use Restore purchase. Use the same after reinstalling or moving to a new device. If it still shows Free, send feedback with the email address you bought it under.

Base Layers

Tap Layers in the bottom bar. The current base map is the top row; tap it to open the picker, grouped by category with a preview of each layer. Twenty-three base layers are built in, and a catalog adds more than 1,300 regional and specialist imagery sources.

GroupLayersGood for
Topo & terrainUSGS Topo, USGS Imagery + Topo, OpenTopoMap, NatGeo World, Esri World Topo, Esri USA Topo (Historical)Contours, trails and terrain; historical USGS quadrangles
ImageryEsri Satellite, VersaTiles Satellite, USGS Imagery, Esri Firefly Imagery, Sentinel-2 Cloudless, Google Satellite, Google HybridAerial and satellite views; Firefly is muted so your markers stand out
AeronauticalFAA VFR SectionalAirspace and aviation planning in the US
WaterEsri Ocean Basemap, EMODnet Bathymetry, NOAA Nautical Charts (ENC)Depths, coastlines and water search
Street & generalOpenStreetMap, Esri World Street, OSM Humanitarian, CyclOSMRoads, towns and cycling
VectorVersaTiles Eclipse (default)A detailed dark map that stays sharp at every zoom
Google Satellite and Google Hybrid use your own Google Map Tiles API key, which the app asks for the first time you choose one. They stream online only: Google does not allow its tiles to be saved, so use Esri Satellite or VersaTiles Satellite for an area you need offline.
Zoom in all the way. Every base layer keeps zooming to z22. Past a source's deepest detail, its last tiles are scaled up rather than the map going blank.

Map Styling

Map Styling tweaks in the Layers panel restyle any base map, including satellite and your downloaded maps: all roads, highways with route shields and names, train tracks, borders with names, national and state parks, power lines, contour lines with a choice of interval, water and rivers, trails, offroad and gravel, campgrounds and viewpoints, live OpenStreetMap peaks, fishing spots and boat launches, and business labels. They stay on when you switch base maps and work offline once the area is saved. Map Legend, beside the Map Styling title, explains every symbol.

Stacking maps and quick setups

Add Base Layer stacks a second base map or downloaded map over the first, each with its own opacity, for example USGS Topo contours over satellite. The Catalog (Customize) lets you choose which layers the menu offers, with Reset To Defaults one tap away.

One-tap presets set up a sensible base map and overlays for an activity: Hike, Snow / Ski (hillshade, slope angle and avalanche danger), SAR and Off-road. The same panel has a brightness slider and the night mode switch.

Imagery Catalog

The catalog holds over 1,300 worldwide sources from the OpenStreetMap Editor Layer Index: regional aerial photos, government topo maps, historical photos and maps, and elevation layers. Open Layers → Custom WMS / WMTS → Browse Imagery Catalog and filter by name, type, category or country. Add turns an entry into one of your own map sources, which then appears in the picker with the built-in layers.

Catalog sources belong to the organizations that publish them, and many were licensed with OpenStreetMap editing in mind. Check a source's terms before relying on it for your own work.

To add any other map service, tap Add WMS/WMTS Source at the bottom of the Layers panel and enter its address. Your own WMS, WMTS and WFS services are free to use.

Overlays

Overlays draw on top of any base map. Add one from Layers → Add an overlay; each active overlay gets its own opacity slider, and you can run as many at once as you like. Overlays marked online-only need a connection and hide themselves while you are offline.

Terrain

  • Hillshade (worldwide), Shaded relief (USGS, US only) and Elevation tint (USGS 3DEP).
  • Slope Angle: terrain colored by steepness, computed on your device. The avalanche-oriented scale keeps gentle ground clear and heats up through the critical band: 27–29° yellow, 30–34° orange, 35–45° red, where most slab avalanches release, 46–50° purple, 51–59° blue, 60° and over gray.
  • Slope Aspect: the compass direction each slope faces, in eight colors, for sun exposure, snow loading and avalanche aspect.
  • Sun & shade: which slopes are in sun or shade for a time you choose.
  • Custom terrain coloring: color the ground by elevation or slope bands you set.

Slope, aspect and the UTM / MGRS grid are calculated on your device from elevation data, so they work offline once the terrain for the area is saved.

Topographic & reference

  • UTM / MGRS grid: a labeled reference grid, see Coordinate Display.
  • Quad index (USGS 7.5′ quadrangles) and Reference labels for imagery.
  • Country and state borders are always drawn and work offline.

Route discovery, water & marine

  • Public Lands (BLM) and Ski pistes.
  • OpenSeaMap seamarks, Hydrography (USGS NHD), Maritime limits / EEZ (NOAA) and Watersheds (USGS WBD).

Weather & hazards online only

  • Wind barbs: standard meteorological barbs across the view, refreshed every 15 minutes. A half barb is 5 kt, a full barb 10, a pennant 50; the barbs point along the true wind and do not turn with the map.
  • Weather radar (NEXRAD), refreshed about every 5 minutes.
  • Smoke forecast near the surface (NOAA).
  • Active wildfires (NIFC perimeters) and Satellite fire detections from the last 48 hours (VIIRS). Tap a perimeter for its name, size and start date.
  • Avalanche danger: US forecast zones colored by the current danger rating, with a legend.
  • Snow depth (NOAA NOHRSC).

United States field data

  • Trails & recreation: trails on national forests (USFS), trails nationwide (USGS), the USFS Motor Vehicle Use Map and USFS recreation sites.
  • Access & transport: USFS roads, and roads including 4WD (USGS).
  • Land status: wilderness areas, tribal lands and reservations, hunt units on wildlife refuges.
  • Hazards: FEMA flood zones and earthquakes from the last 24 hours (USGS).
  • Ground cover: land cover (NLCD) and cropland (USDA).
  • Survey & names: the PLSS survey grid (BLM), USGS place names, and wetlands (NWI).
Any map as an overlay. Beyond the overlays above, any base map, imported map or downloaded map can be stacked with its own opacity: tap Add Base Layer in the Layers panel.

3D Terrain View

Tap 3D in the Layers panel to tilt the map into real terrain. Any base layer and your map items drape over it.

  • Drag with two fingers to tilt, twist to rotate; the tilt slider does the same.
  • Raise the terrain exaggeration in Settings to make gentle ground easier to read.
  • The viewshed tool and the slope overlays use the same elevation data.

3D without a connection

  1. Frame the area you need.
  2. Open Layers → Save Terrain and confirm the size estimate.
  3. From then on, 3D uses the saved terrain for that area, with no connection.
For a fully offline 3D map, download the area and use Save Terrain in the same view. The two are saved separately and combine whenever you switch to 3D.

Vector Maps

Besides picture-tile maps, ZYRNTOPO shows vector maps, which stay sharp at every zoom and take up less space. The default VersaTiles Eclipse map is one, and it is ready to use with no import.

  • Import any .pmtiles file, picture or vector, with Settings → Offline Maps → Import. Archives from Protomaps, OpenFreeMap and BBBike work, as does your own build.
  • An imported vector map gets a style made for it automatically from the layers it contains, and drapes over 3D terrain like any other map.
  • Choose it in the base-layer picker under My Maps (offline).

Controls & Compass

  • Zoom buttons (+/−) sit in the bottom-right corner.
  • Download map area is in the top bar: tap it, frame the box and download. See Downloading an Area.
  • Compass: tap to turn the map back to north-up; long-press for heading-up, where the map turns with you.
  • Scale bar: metric and imperial together, so either reading is there at a glance.
  • Long-press the map to hide the on-screen controls; long-press again to bring them back.
  • Tools in the bottom bar holds every drawing, measuring and field tool, grouped by task.

Night Mode

The night button in the Layers panel cycles the map through Off → Dim → Red light, to protect your night vision without touching the controls. In Settings → Display you can also choose Auto, which follows your device's dark mode. Your choice is remembered.

Coordinate Display

Your position, or the map center when GPS is off, is shown at the bottom of the screen together with bearing and elevation. Choose the format in Settings → Coordinates → Coordinate Format:

FormatExample
Decimal Degrees (DD)39.0835° N, 108.5601° W
Degrees Minutes Seconds (DMS)39°05'00" N, 108°33'36" W
Degrees Decimal Minutes (DDM)39° 05.010' N, 108° 33.606' W
UTM12S 741234 4328765
MGRS12SVJ4123428765

Tap the coordinates to copy them. Use the format the people you work with use: mismatched formats are a classic way to send someone to the wrong place.

UTM / MGRS grid overlay

Add UTM / MGRS grid from Layers → Add an overlay → Topographic / Reference for a labeled reference grid over any map. The spacing follows the zoom (100 km, 10 km, 1 km), it has its own opacity slider, and it works offline.


Markers & Waypoints

Tap Tools → Marker, then tap the map where it goes. To mark exactly where you stand, use the drop-marker button beside the GPS button. The marker's details open straight away.

Marker properties

  • Name and notes.
  • Icon: dozens of typed symbols, the same on every device, including camp, water, summit, hazard, medical and wildlife. A marker with no icon of its own shows as a red dot.
  • Color, folder, and photo: a photo waypoint keeps where it was taken and which way the camera faced.
  • SAR properties: mark it as a clue or a find, and assign it to an operational period.

Tap a marker to open its details. In Items, every item has Go To, and point items also have Point Info, Directions, Navigate To and Transform. Tools → Photo waypoint takes a photo and pins it where you stand.

Averaged waypoint

For a more accurate point, use Tools → Averaged Survey Waypoint. Hold the device still; the dialog shows the number of fixes, the running average and an estimated accuracy that tightens as fixes accumulate. Save waypoint drops a marker at the averaged position with its accuracy noted.

Lines & Routes

Tap Tools → Line, then tap the map to place points. The running distance shows as you go; tap Finish when done.

  • Each segment shows its length in your units.
  • Edit a line to drag, insert or delete points, and to set its name, color and folder.
  • Free Draw (Tools → Free Draw): drag on the map to sketch. Each stroke saves as a line you can rename, recolor or move later.
  • Bearing Line (Tools → Bearing Line): a line from a point along a bearing you enter.

Elevation profile

Open any line, track or route and tap Elevation for its profile: total distance, gain, loss, lowest and highest point in your units. It needs a connection to fetch the elevation.

Polygons & Areas

Tools → Polygon: tap to place the corners and Finish to close it. Its area shows in acres, hectares, square kilometers or square miles.

  • Highlight (Tools → Highlight): shade an area with a see-through fill to mark a search segment, a hazard or an area of interest without hiding the map.
  • Circle (Tools → Circle): press at the center and drag out; the radius shows as you size it.
  • Buffer (Tools → Buffer): an area a set distance around a point, line or area.
  • Sector (Tools → Sector): a pie-wedge search area from a bearing and radius.
  • Assignments (Tools → Assignment — Line or Assignment — Area): search assignments that join an operational period.

Range Rings

Tools → Range Ring draws concentric rings around a point. Set the number of rings (1 to 10), the spacing, or your own radius for each ring. The set is one item you can edit or delete from Items.

Folders & Organization

Everything you create is listed under Items in the bottom bar, in folders such as "Day 1 Search", "Water Sources" or "Hazards".

  • Create a folder with a name and color, and move any item into it.
  • Hide or show a whole folder on the map at once.
  • Export one folder on its own.
  • Select several items for bulk actions.

Clearing caches never deletes your items: items, downloaded maps and the tile cache are stored separately.

Operational Periods

An operational period is a planning cycle, usually one 12- or 24-hour shift. Create one with Tools → Search Period.

  • Give it a name, a start time and a status.
  • Assign items to it from their details.
  • Hide the items of periods that are not active.
  • Export one period on its own.
For an incident run under ICS, create one period per official operational period and assign that period's assignments and finds to it before exporting for the Situation Unit.

Saved Maps & Sharing

Menu → Save Map saves what you have drawn (items, folders and search periods) as a named map. Menu → Saved Maps lists them; open one to load it, and Clear Map starts a fresh one.

Share a map by QR code, with no signal

  1. In Saved Maps, tap Share on the map you want to send.
  2. The other person taps Scan in their own Saved Maps and points their camera at your screen.
  3. A small map is one code; a big one cycles through several. Hold steady until it says every part is captured; a missed part is picked up on the next pass.

Everything travels inside the codes, so it needs no internet, no account and no cable. Items, areas and folders come across; downloaded map tiles do not, as they are far too big for a code.

Files and map packages

A saved map can also be exported as a .zyrn file and opened on another device with Saved Maps → Import .zyrn.

To send a whole map in one file, use Settings → Data → Send map package. It writes one .zip in ATAK's data package format: the map itself, a KML copy that ATAK imports, and, if you choose, the downloaded maps that cover the area in view. ZYRNTOPO opens it as a saved map, and ATAK imports it like any other data package.


GPS Modes & Receivers

The GPS button cycles through three states:

  1. Off: no GPS; the button is gray.
  2. Follow: the map keeps you centered as you move.
  3. Heading-up: the map also turns to match the direction you are facing.

A circle around your position shows its accuracy. Under heavy canopy or in a canyon it grows large; check your position against landmarks before trusting it.

Accuracy depends on your device's GPS and how much sky it can see. On critical operations, carry a dedicated GPS as a backup.

External GNSS receivers

A separate receiver, over USB or Bluetooth, can replace the phone's own GPS. Once connected it is the position source for everything: your position, track recording and what you share with your team. Disconnect it and the device GPS takes over again. Connect one from Settings → GPS Receiver.

Receivers are read over NMEA 0183, which nearly every GNSS unit speaks. Sentences that fail their checksum are discarded rather than guessed at.

ConnectionWhere it works
USB serialAndroid app; Chrome and Edge on a computer
Bluetooth LEAndroid app; Chrome and Edge on a computer
The baud rate is found for you. Auto-detect tries the common rates in turn and tells you which of three things happened: it found the rate, something is connected but not sending NMEA, or nothing is arriving at all.
Classic Bluetooth (SPP) receivers are not supported. Many units sold as "Bluetooth GPS" use classic Bluetooth; connect those over USB instead. On Android, a USB receiver and a USB drone link cannot both be connected at once. NMEA carries no accuracy figure, so no accuracy circle is drawn for an external receiver; satellite count, fix quality and HDOP are shown instead.

Track Recording

Tools → Record Track starts recording; your track draws on the map as you move, with live distance, elevation gain, elapsed time and average speed.

On Android the recording continues with the screen off, behind a notification, once you allow background location; on the web, keep the screen on. Tap Stop for a summary (distance, gain, duration and top speed). The track is saved with your items, ready to export as GPX or GeoJSON, with its elevation profile one tap away.

Measurement Tools

Tools → Measure, then tap points on the map:

  • Distance between points, with a running total, and the bearing of the last leg.
  • Bearing between two points, from true, magnetic and grid north at once, in your bearing units.
  • Area: close the shape to get its area, in your area units.
  • Elevation profile along the path.
  • Navigate: tap the point you want to go to and guidance starts. See Navigate & Route.
  • Circular scale: three range rings around the crosshair at round distances for the current zoom, with a compass rose on the inner ring that turns with the map. Tap it again to hide it.
  • Viewshed / line of sight: tap an observer point and the ground visible from it is shaded green, computed on your device. Set the observer and target height, a maximum radius, and an optional radio mode that models how radio bends over the horizon, for siting repeaters, lookouts or signal mirrors.

Results stay on the map in a card while you keep working. Tap its × to clear it.

Field Shortcuts

  • Bearing From Heading (Tools): a line from where you stand along the way you are facing, at a length you choose (500 m, 1 km, 0.5 mi, 1000 ft and so on).
  • Bearing sight (Tools, compass icon): a full-screen compass showing the azimuth you are pointing at, magnetic or true, in degrees and mils, with the inclination angle. Shoot azimuth → map drops a bearing line from your position. It uses the device's compass and works offline.
  • Tap to measure from GPS (Settings → Map Behavior): with it on, tapping open map draws a line from your position to that spot with the bearing and distance on it. The line follows you as you move; its label has Navigate and Clear.
  • Share Field Location (Tools): share your position, or the map center if GPS is off, as a card with DD, DMS, UTM and MGRS coordinates and a map link.

How Offline Maps Work

A downloaded map is a single .pmtiles file on your device. One file per area is sturdier than thousands of cached pictures: the system does not clear it to make room, it survives restarts, and you can copy it to a teammate like any other file.

Nothing downloads behind your back: you choose the area and the zoom range. The default map is hosted and needs a connection until you save the area you care about. Do it before you leave service, not after.

What you can save

  • Every base map except Google Satellite and Google Hybrid, which Google does not allow to be saved.
  • The overlays you have on: they are saved with the base map for the same area and zooms.
  • Your own map sources, if their servers allow it.
  • 3D terrain, with Layers → Save Terrain.
  • Slope, aspect and the UTM / MGRS grid, which need nothing extra: they are worked out from the saved terrain.
  • Any .pmtiles or .mbtiles file you import; .mbtiles are converted on import.

The points-of-interest dataset is saved on your device after the first run, so POI search works offline from then on.

Using a downloaded map

A downloaded map does not switch itself on. Open the base-layer picker and choose it under My Maps (offline). To prove it works, turn off Wi-Fi and mobile data: the map should still draw, straight from the file.

Importing a file

  1. Open Settings → Offline Maps → Import, or Menu → Map Data → Import.
  2. Pick a .pmtiles or .mbtiles file. Files of many gigabytes are fine.
  3. It appears in your list with its area and zoom range, ready under My Maps (offline).

Archives from Protomaps, OpenFreeMap and BBBike work, as does your own build. Map Data → From link downloads one from an address.

Downloading an Area

  1. Open Settings → Offline Maps → Select Area on Map, or tap the download button in the top bar. A green box appears.
  2. Drag the box over the area and pull its corners to size it.
  3. Choose the lowest and highest zoom. A live estimate shows the size before you commit.
  4. Tap Download Area. The download runs in the background and carries on after a restart if it is interrupted.
  5. The map lands in My Maps (offline).

Download a whole region

Menu → Map Data → Download region saves the current base map for a country, continent or mountain range in one go, with the same zoom choice and size estimate. The same screen lists ready-made map packages.

How big will it be?

AreaZoom rangeApproximate size
One county (about 50 × 50 km)6–14about 120 MB
One county6–16about 600 MB
One state6–14about 1 GB
One state6–16about 5 GB

Each extra zoom level roughly quadruples the size. Areas of sea, forest canopy or empty ground are stored once, so real downloads are often smaller. Download on Wi-Fi.

Every zoom level for the whole world would be hundreds of terabytes, so no app ships it. Save the area you will actually be in, at the zooms you need.

Viewed Tile Cache

While you are online, map tiles you look at are also kept in a temporary cache, so recently viewed places keep drawing when the signal drops and panning stays smooth. The system may clear this cache when storage runs low, which is why a downloaded map is the dependable way to work offline. Manage it in Settings → Viewed Tile Cache.

Storage

  • Settings → Offline Maps lists every downloaded map with its size, area and zoom range. Each has Use and Remove; downloaded maps are usually by far the biggest thing the app stores.
  • Clear Tile Cache (in the menu, or Settings → Viewed Tile Cache) drops only the temporary cache and leaves your downloaded maps alone.
Your items, your downloaded maps and the tile cache are stored separately. Clearing one never touches the others.

Team Sync — Overview

Team Sync puts everyone on the same live map: positions, markers, lines, areas and tracks, plus chat. Open Menu → Team Sync. Team Sync and its links are Pro; the Team Members roster is free.

Pick your name, role and team color. The colors are the TAK team colors (Cyan, White, Yellow, Orange, Magenta, Red, Maroon, Purple, Dark Blue, Blue, Teal and Green), so you show up the same way to ATAK users. Teammates draw as contact balls with their callsign, and a teammate who reports a heading gets a direction cone. GPS switches on when you join, so your position goes out straight away.

LinkNeeds internetReaches
Session code✓ZYRNTOPO users anywhere
Nearby TAK✗ATAK, WinTAK and iTAK on the same Wi-Fi
TAK serverDepends on the serverEveryone on that server, in any TAK app
Meshtastic radio✗Radios on the same LoRa channel, for miles
MAVLink drone✗A drone or vehicle you connect

The links run side by side: you can be in a session, on a TAK server and on a radio at once, and the app passes what it hears between them.

If a teammate cannot see you, check that everyone is on a current version of the app and typed the same code. On Android, Team Sync keeps running with the screen off, and a notification tells you about new messages and items.

Team Sync — Over the Internet

  1. In Team Sync, set your name, role and team color.
  2. Create a session code, or type the one you were given. Codes are 10 characters of letters, digits and symbols.
  3. Share it with your team: show the QR code, copy it, or invite friends from your account.
  4. Tap Join Session. Everyone with the code sees the same map.
The code is the key. Anyone who has it can join and see your team, so share it only with people who should. Start a new session with a new code for each operation.

Team Sync — Nearby TAK Devices

On the same Wi-Fi as ATAK, WinTAK or iTAK users, the ZYRNTOPO Android app joins the local TAK network by itself. They appear on your map as TAK contacts with their callsigns, and you appear on theirs.

  • Markers, lines, areas, circles and range rings go both ways. Markers made in ATAK, iTAK and WinTAK show up in ZYRNTOPO, and deleting one reaches TAK.
  • Chat goes both ways too, including direct messages to one TAK user.
  • No server and no internet are involved; it is the network the TAK apps already use among themselves.
Browsers cannot join a local TAK network, so the web app does not see nearby TAK devices. Use a TAK server to link the web app with TAK users.

Team Sync — TAK Servers

A TAK server links everyone connected to it, whatever app they use: ZYRNTOPO, ATAK, WinTAK or iTAK all see each other's positions, markers and chat.

Connecting

Team Sync → TAK server. It is set to the ZYRNTOPO server, but nothing is sent until you press Connect: joining a server shares your position, markers and chat with everyone on it. It never connects on its own, and does not reconnect by itself the next time you open the app.

To use another server, choose Custom TAK Server and enter its address.

Servers are independent. Connecting to one does not disconnect another, and positions are not forwarded between them. If you want your team's data to stay entirely on your own hardware, connect only to your own server.

Running your own

The ZYRN TAK server is free and keeps everything on hardware you control. It is one program for Linux, macOS, Windows and Raspberry Pi, with no Docker. One command installs it, creates the certificates and a first user, starts it as a service, and tests itself before saying it worked:

curl -fsSL https://zyrntopo.com/tak-server.sh | sh

On Windows, in PowerShell:

irm https://zyrntopo.com/tak-server.ps1 | iex

It prints the address for ZYRNTOPO, the first user's password and a QR code for ATAK. Running it again updates or repairs it. Its dashboard, users, ports and the details for ATAK, WinTAK and iTAK are on the TAK server page.

PortUsed by
8090ZYRNTOPO
8089ATAK, WinTAK and iTAK
8443The dashboard, and file sharing between TAK apps
8446Connection packages and certificate enrollment
Before you open it to the internet, put it on a VPN such as Tailscale (zyrn-tak setup --tailscale) or behind a reverse proxy with TLS. The ZYRNTOPO web app needs a wss:// address, which either gives you. If a device cannot connect, run zyrn-tak status on the server.

Team Sync — Meshtastic Radio

A Meshtastic radio carries positions and chat over LoRa, for miles and with no phone signal. Open Menu → Meshtastic radio and connect your radio over Bluetooth, USB or Wi-Fi (by its address, such as meshtastic.local).

  • ZYRNTOPO users and ATAK users with the Meshtastic plugin on the same channel appear on your map.
  • Anything the radio hears is passed on to your TAK server and to nearby TAK devices, so one radio can link the people out of signal with everyone else.

Team Sync — Without Internet

When there is no internet at all, three links still work:

  • Nearby TAK devices on the same Wi-Fi network or hotspot, see above.
  • Your own TAK server on a laptop or Raspberry Pi on the local network: everyone connects to its local address, and nothing leaves the network.
  • Meshtastic radios where there is no network at all.
For an incident command post: a laptop running the ZYRN TAK server and sharing a Wi-Fi hotspot links ZYRNTOPO, ATAK, WinTAK and iTAK users with no internet service.

Team Sync — Sensor Detections

Search systems that report detections as Cursor on Target, such as a thermal drone, can send them to ZYRNTOPO through a TAK server or the local TAK network. Each detection appears as a colored marker with its scores, and is removed when its time runs out.

The color comes from the confidence score, against the thresholds the sender includes (by default 85% and 60%):

  • Red: at or above the alert threshold. Check it now.
  • Orange: between the two thresholds. Needs a look.
  • Slate: below the review threshold. Logged, low confidence.
  • Friendly: a friendly unit's position rather than a detection.

Color is taken from confidence, not from the CoT affiliation, so a missing person is never drawn as a hostile track.

Team Chat

Chat works across every Team Sync link. Open Menu → Team Chat, or tap the chat bubble.

  • Text to everyone in the session.
  • Voice messages: hold the microphone button to record, release to send.
  • TAK chat: on a TAK server or with nearby TAK devices, your messages land in ATAK's chat and theirs in yours.
  • An unread badge shows on the chat button, and on Android a notification arrives while the app is in the background.

SAR — Operational Periods

SAR mode. The search and rescue tools (search periods, assignments and clue markers) appear in Tools when SAR mode is on. Choose search and rescue in the first-run setup, or switch between Recreation and SAR in Settings → Display.

Create a period with Tools → Search Period. Each period is a planning cycle in a search, usually 12 or 24 hours; assign items to it to keep the map organized.

  • Name, start time and status.
  • Show or hide a period's items with one switch.
  • Export a period's items on their own for the incident record.

SAR — Team Members & Roles

Menu → Team Members is a free roster, with no account needed. Each member has:

  • Name, role and phone.
  • Medical and emergency information: blood type, allergies and an emergency contact.
  • Specializations and notes.

Rosters import and export as JSON or CSV for briefings and ICS forms. The roster stays on your device; it is separate from the people in a Team Sync session.

SAR — Gear / Pack List

Menu → Gear / Pack List keeps an inventory of field equipment:

  • Item, category, quantity and weight, grouped by category (shelter, navigation, medical, comms and so on).
  • Total pack weight.
  • Export and import as JSON or CSV, to reuse a list from a previous operation.

SAR — Clue & Find Logging

Tools → Clue Marker drops a clue: a yellow marker that stands apart from ordinary markers. Like any marker it takes a name, notes, a photo and its operational period, so each find records what was found, where, and in which shift.

Clues keep all their details when exported as GeoJSON, and print on PDF map sheets with the rest of the map.


Import & Export — GeoJSON

GeoJSON keeps the most detail and is the best format for moving data in and out of ZYRNTOPO.

Export

Menu → Export Map, then pick the format. Export everything, one folder or one operational period. On Android the file goes to the place you choose or the share sheet; in the browser it downloads.

Import

Menu → Import Map and pick a GeoJSON, GPX, KML, KMZ or .zyrn file. Imported items merge into your map: items you already have are updated and new ones are added. In the web app you can also drag a file onto the window.

Import & Export — KML / KMZ

KML suits Google Earth, CalTopo and ATAK. Exported KML keeps points, lines and areas with their names and colors; KMZ files import too.

Import & Export — GPX

GPX is what handheld GPS units such as Garmin and Suunto expect. Markers export as waypoints and recorded tracks as tracks; importing reads waypoints, tracks and routes.

Import & Export — CSV

CSV writes markers with their coordinates and details as a spreadsheet, for ArcGIS, QGIS or agencies that work in spreadsheets. Team rosters and gear lists also import and export as CSV.

POI Data

Points of interest are the peaks, springs and water, trailheads, passes, caves, fishing spots, boat launches, parking, power lines, parks, helipads and airstrips, and SAR shelters, rescue points and lookouts the app shows and searches.

  • Gather POI Data pulls the current OpenStreetMap points for the whole view and keeps them offline. Zoom out until the region you want fills the screen: one gather over a region beats a dozen small ones.
  • Settings → Data → Export POI Data writes everything you hold to one file, and Import POI Data merges a file back in. Importing only adds; it never removes what you already have, and a damaged file is refused.

ZYRNTOPO Plugins

Plugins add tools to ZYRNTOPO: extra map layers, calculators, field forms and links to other systems. They are the same plugins as in ZYRN Workstation, so a plugin works the same on your phone and your desktop.

  1. Open Tools → Plugins.
  2. Tap Get plugins to see the published list.
  3. Tap Install on the one you want. When a newer version is published, Update appears beside it.

Installed plugins add their tools to the main menus. Each plugin runs in its own sandbox inside the app and can only use what ZYRNTOPO hands it. Remove one from the same list.

The Plugins page lists what is available. For the native Android app, see ZYRNMAPS plugins and the Plugin SDK.


Settings — Coordinate Formats

Settings → Coordinates → Coordinate Format: DD, DMS, DDM, UTM or MGRS. The choice applies everywhere: the coordinate readout, Point Info, marker details and CSV export.

Settings — Units & Display

Settings opens on a list of pages: Display, Map Display, Coordinates, Map Behavior, Appearance, GPS Receiver, Track Recording, Map Sources, Offline Maps, Viewed Tile Cache, Data, Account and Version.

  • Units (Coordinates): distance in imperial or metric, elevation in feet or meters.
  • Area units (Coordinates): metric with ares or hectares, imperial with square yards and acres, or nautical. Match distance units keeps acres or km².
  • Bearing units (Coordinates): degrees (0–360, ±180 or quadrants such as S 72° W), gradians, or mils (NATO 6400, exact 6283, Swedish 6300, Warsaw Pact 6000).
  • Reference north (Coordinates): true, grid (UTM) or magnetic. Every bearing the app shows is measured from it and marked T, G or M.
  • Mode (Display): Recreation or SAR, which shows the search and rescue tools.
  • Glove mode (Display): bigger buttons for gloves, cold hands or a moving vehicle.
  • Color-blind palette (Display): switches the slope and aspect colors to a color-blind-safe scale, with matching legends.
  • Map Display: show or hide the coordinate readout, the weather widget, the scale bar, the on-map buttons and the Tools button.
  • Map title: the name printed on PDF sheets, also editable at the top of the menu.
Offline-aware. Features that need a connection (trail routing, the weather widget, and the weather and hazard overlays) hide themselves while you are offline and come back when you reconnect, so what you see is what works right now.

Your base layer, map position and zoom, and settings are saved on the device and restored every time you open the app. Settings → Version has a Location diagnostics check for when GPS will not lock.

Settings — Permissions

The app asks for each sensitive permission the first time a feature needs it, and you can say no. To change one later:

  • Android: Settings → Apps → ZYRNTOPO → Permissions.
  • Chrome or Edge: the site icon in the address bar → Permissions.
PermissionUsed for
Location (background only while recording a track)Your position, tracks and route following, and sharing your position when you choose to
CameraScanning QR codes of shared maps
MicrophoneVoice notes and voice messages in Team Chat
BluetoothMeshtastic radios and Bluetooth GNSS receivers
NotificationsThe notice while a track records or Team Sync runs, and new chat and items
Nearby Wi-Fi trafficSeeing ATAK, WinTAK and iTAK devices on the same network
Photos for waypoints come through the system photo picker. Your position leaves the device only when you share it: in a Team Sync session, on a TAK server, over a radio, or in a location you send.

Platforms — Android

Android 7.0 or newer, on phones and tablets of every kind, from Google Play or the Amazon Appstore.

  • No hardware is required: a Wi-Fi-only Fire tablet with no GPS receiver still installs it, and the features that need missing hardware simply stay off.
  • Track recording, Team Sync and chat keep working with the screen off, behind a notification.
  • USB serial devices (GNSS receivers, drone telemetry radios) plug in through USB-C or an OTG adapter.
  • The ATAK-style notification and nearby TAK devices work only in the Android app.

Platforms — Web / PWA

The web version runs in any modern browser at app.zyrntopo.com. Install it as an app for offline use:

  • Chrome / Edge: the install icon in the address bar.
  • Safari on iPhone and iPad: Share → Add to Home Screen.
  • Firefox: Menu → Install as App, where offered.

Once installed, the app itself works offline, along with your downloaded maps.

What the browser cannot do. Browsers pause location when the screen locks or the tab is in the background, so keep the screen on while recording a track. They cannot join a local TAK network, so use a TAK server to link with TAK users. USB and Bluetooth devices work in Chrome and Edge on a computer.

ZYRNMAPS — the native Android client

ZYRNMAPS is ZYRNTOPO built as a native Android app, drawing the map with the GPU rather than inside a web view. It is a separate app from ZYRNTOPO, with its own store listing and package name (com.zyrntopo.maps), and both can be installed side by side. Your account and Pro work in both.

Which one should I use? ZYRNTOPO is the same app on a phone and in the browser. ZYRNMAPS is for Android specifically: it draws faster, keeps a steady frame rate on low-end hardware, starts quicker, and takes native Android plugins.

Requirements

ItemRequirement
Android versionAndroid 7.0 or newer
GraphicsVulkan 1.0.3 or newer
Package namecom.zyrntopo.maps
Vulkan is required. Some older and lower-end devices that run ZYRNTOPO perfectly well lack it. If ZYRNMAPS will not install, or shows a black map on a device that otherwise works, use ZYRNTOPO on that device.

ZYRNMAPS — feature reference

ZYRNMAPS covers the same ground as ZYRNTOPO. Where it differs, it says so here.

The map

  • The built-in base layers, the imagery catalog, and any .pmtiles map you import, with Map Styling on any of them and a choice of contour interval.
  • 3D terrain, viewshed and line of sight, worked out on the device.
  • The overlays: terrain, reference grids, weather and hazards, and the US field data.
  • Map labels follow the device language, and night mode follows the device's dark mode.

Location and GPS

  • GPS modes (off, follow and heading-up) and external GNSS receivers, as in ZYRNTOPO.
  • Track recording continues with the screen off, behind a notification.
  • Averaged waypoints and the bearing sight.

Items, drawing and measuring

  • Markers, lines, areas and tracks in folders, with the full SAR properties.
  • Distance, area, elevation profile, range rings, buffers, heading lines and bearings.
  • Point Info, and identify: tap a map feature to see what it is.

Search and rescue

  • Scent cone, routes, clue logging, search periods, photo waypoints, viewshed, gear list and team roster.
  • True-scale PDF map sheets.

Offline

  • Downloads with a size estimate before you commit, that resume if interrupted, and a list of what you already hold.
  • Offline 3D terrain from imported elevation data.
  • A downloaded area draws on a cold start in airplane mode.

Sharing and interchange

  • Share a saved map by QR code: open the map's card, choose Show code, and the other person scans it from their own Saved Maps. No signal, account or cable needed.
  • GeoJSON, GPX, KML/KMZ and CSV in and out, and data packages for moving a whole working set at once.

Team Sync and TAK

  • Team Sync sessions, TAK servers and nearby TAK devices in both directions, and MAVLink drones.
  • Nothing is shared until you press Connect.

ZYRNMAPS — installing and managing plugins

A plugin is an ordinary Android app that extends ZYRNMAPS. It ships as its own APK, installs the way any app does, and can add layers, panels, map objects, search results and context-menu actions. Building one is covered in the Plugin SDK below; this section is about running them.

Installing one

  1. Install the plugin's APK — from a store, with adb install, or by opening the file on the device.
  2. Open ZYRNMAPS ▸ Menu ▸ Plugins. The plugin appears in the list.
  3. Switch it on. Nothing runs until you do — a discovered plugin is off until a person enables it, with no allow-list that pre-approves anything.

What the Plugins screen tells you

The list shows every plugin found on the device, including ones ZYRNMAPS has decided not to run, each with its reason. That is deliberate: a plugin that is switched off and a plugin that is missing are different problems, and a screen that showed nothing for both would send you looking in the wrong place.

What it saysWhat it meansWhat to do
Not enabledFound, allowed, and waiting for youSwitch it on
Needs a newer ZYRNMAPSBuilt against a host API this version does not haveUpdate ZYRNMAPS
Built for an older ZYRNMAPSBuilt against a host API no longer supportedUpdate the plugin
No entry pointIts manifest names no plugin class, so there is nothing to loadReport it to whoever built it
Could not be loadedThe named class is missing, or is not a ZYRNTOPO pluginReport it, quoting the reason shown
Requires ProThe plugin declares itself Pro and this account has no entitlementUnlock Pro, or remove the plugin

Who signed it

Every row shows where the plugin came from, and ZYRNMAPS records the SHA-256 of its signing certificate either way.

LevelMeaning
First-partySigned with the same certificate as ZYRNMAPS itself — shipped by us, or built by somebody holding our key
Third-partySigned by somebody else. The fingerprint is shown, so you can check it against what the author published
UnknownThe signature could not be read at all. Rare, and it means the package manager refused — not that the APK is suspect
The signature is provenance, not a gate. ZYRNMAPS will load a third-party plugin. It reads the signature, records it and shows it, because who built something is worth knowing — but that is not what decides whether it runs. You are. A plugin runs code inside ZYRNMAPS with access to the map, your position and its own storage, so enable ones you have a reason to trust, and check the fingerprint when the author publishes one.

Turning one off

Switch it off in the same screen. Everything the plugin added — layers, sources, map objects, panels, its search provider, tap handlers and subscriptions — is removed by the host, whether or not the plugin cleaned up after itself, and whether or not its own teardown threw. Objects it drew are namespaced to it, so they go and nothing you drew goes with them.

Uninstalling the APK does the same, and also sweeps any objects it left behind.


Plugin SDK — overview

The ZYRNTOPO Plugin SDK lets you extend ZYRNMAPS with a separately built, separately signed Android APK. Your plugin can add map sources and layers, put objects on the map, contribute a panel, answer search queries, add context-menu actions, react to taps, post notifications and talk on the app's event bus.

This is not the ATAK plugin, and the two get confused constantly. The ATAK plugin is an APK that loads inside ATAK, lives in a separate build, and shares only the word "plugin". This page is about ZYRNTOPO's own extension surface, loading inside ZYRNMAPS. If you are looking to extend ATAK, you are on the wrong page.

The one design decision worth knowing first

Everything your plugin creates through the context it is handed is tracked by the host, and released when the plugin is deactivated — whether or not your teardown ran, and whether or not it threw.

This is deliberate, and it is aimed at a specific well-known failure. ATAK plugins are notorious for leaking listeners across reloads, because onDestroyImpl has to manually undo whatever onCreate did and a human has to remember every line. Here the context records every source, layer, subscription, object, panel and cleanup callback you create, and deactivate() releases them regardless. A plugin that forgets to clean up cannot leak, because it was never the plugin's job.

The practical consequence: your onDeactivate can be empty, and in most plugins it should be. Register things through the context and let the host own their lifetime.

What a plugin cannot do

Named up front, because discovering a wall later is worse than being told about it now.

  • It cannot draw its own UI. A panel is described as rows and the host draws them, in the host's own controls. A plugin that needs a chart or a camera preview cannot have one yet — that is the stated gap, and the reason is in Panels.
  • It cannot consume a map tap. Tap delivery is read-only; a plugin that could swallow the map's primary gesture could make the app look broken with no way to find out why.
  • It cannot set the camera. It requests a move, and the host's camera controller remains the only writer.
  • It cannot choose a notification channel, importance or full-screen intent. A notice is a title and a body. A map layer should not be able to hold the screen on and buzz through a silent profile.
  • It cannot touch MapLibre directly. It gets a narrowed facade, so an engine upgrade is not a plugin-breaking change.

Plugin SDK — quick start

1. Depend on the API, and only on the API

dependencies {
    compileOnly(project(":plugin:api"))   // or the published artifact
}
compileOnly is load-bearing — this is the mistake that costs an afternoon. A plugin that bundles the API ships a second copy of every class under the same names. Loaded through a DexClassLoader, those are not the same types the host holds, so an is ZyrnPlugin check fails and the error reads as a ClassCastException between a type and itself. Compile against the API; never package it. No Compose, no MapLibre, no host internals either.

2. Declare yourself in the manifest

<application>
    <meta-data android:name="zyrn-plugin-api"   android:value="2" />
    <meta-data android:name="zyrn-plugin-class" android:value="com.example.MyPlugin" />

    <!-- Never started. Exists so ZYRNMAPS can SEE this package. -->
    <activity android:name=".PluginMarker" android:exported="true" android:enabled="false">
        <intent-filter>
            <action android:name="com.zyrntopo.plugin.ACTION_PLUGIN" />
        </intent-filter>
    </activity>
</application>
Remove that activity and your plugin becomes undiscoverable, with no error anywhere. Android 11 (API 30) stopped getInstalledApplications returning other people's packages, so a host that only reads manifest meta-data finds nothing at all. The way through is <queries>, which filters by intent rather than by package — so a plugin declares an activity for this action purely to be visible, and the host declares a matching <queries><intent>. The activity is never started and needs no class behind it. ATAK's own template carries the same warning about its equivalent entry, for exactly the same reason.

3. Implement ZyrnPlugin

class MyPlugin : ZyrnPlugin {
    override val id = "com.example.myplugin"
    override val name = "My Plugin"
    override val minHostApi = 2
    override val capabilities = setOf(Capability.PANEL, Capability.MAP_OBJECTS)

    override suspend fun onActivate(ctx: PluginContext) {
        ctx.addObject(PluginObject("home", """
            {"type":"Feature",
             "geometry":{"type":"Point","coordinates":[-122.68,45.52]},
             "properties":{"name":"Base"}}
        """.trimIndent()))
    }
}

4. Build, install, enable

./gradlew :myplugin:assembleDebug
adb install -r myplugin/build/outputs/apk/debug/myplugin-debug.apk

Then ZYRNMAPS ▸ Menu ▸ Plugins ▸ enable it.


Plugin SDK — the interface and lifecycle

What you declare

MemberTypeWhy the host wants it
idStringNamespaces your storage and your map objects
nameStringWhat the Plugins screen shows
minHostApiIntThe lowest host API you work against, so the host can refuse rather than crash
capabilitiesSet<Capability>What you need. Declared, never assumed
tierTierFREE (default) or PRO. The host refuses a Pro plugin without entitlement, and says so
Declare minHostApi honestly. Understating it to load in more places means loading into an older host and then calling a method that is not there — a crash, instead of the plain sentence the Plugins screen would otherwise have shown. A version mismatch found at load time is a sentence; found at runtime it is a stack trace.

Activation

suspend fun onActivate(ctx: PluginContext)

It suspends, and that removes a distinction you would otherwise have to make. A plugin that lazily loads a large payload cannot finish inside a synchronous call, and a naive host marks such a plugin active the instant activation returns — leaving a failed load reporting as active with nothing behind it. A suspend function has one shape and cannot have that bug.

Deactivation

fun onDeactivate() {}   // may be empty, and usually should be

Undo only what you created outside the context — your own timers, threads, or state the API does not model. Everything registered through the context is released by the host afterwards, whether or not this ran, and whether or not it threw.

Why a plugin might be refused

Each of these is a different thing to tell the user, which is why they are separate values rather than one error string.

ReasonMeaning
HostTooOldBuilt against a newer host than this one. The user needs a ZYRNMAPS update
PluginTooOldBuilt against a host API this version no longer speaks. The plugin needs updating
NoEntryPointThe manifest names no plugin class
NotLoadableThe class could not be loaded, or is not a ZyrnPlugin. Carries the reason
NotEnabledDiscovered and allowed — the user simply has not switched it on

Plugin SDK — capabilities

A closed set on purpose. An open string would let a plugin ask for something no host version knows how to grant or refuse, which fails the same way as not declaring anything at all.

CapabilityWhat it grants
MAP_LAYERSAdd sources and layers to the map
MAP_CAMERARead the camera and request moves. Never writes it directly
MAP_QUERYQuery rendered features under a point
MAP_OBJECTSPut objects on the map and take them off again
MAP_EVENTSBe told where the user tapped — read-only
MAP_ACTIONContribute an entry to the map's context menu
PANELContribute a panel to the UI
SEARCHContribute results to the search sheet
NOTIFICATIONPost a notification
LOCATIONRead the user's current position
STORAGENamespaced persistent storage
BUSSubscribe and publish on the integration bus

The last four in the list above — map objects, map events, notifications and search — come from a survey of what ATAK plugins actually register, rather than from invention. Capabilities with no ZYRNTOPO equivalent (radios, contacts, the GL item factory, video layers) are deliberately absent: a capability a plugin can declare and the host cannot honor is worse than one it cannot declare at all.


Plugin SDK — the context

PluginContext is what you are handed on activation. Every registering method on it is tracked, and released on deactivate.

Map sources and layers

ctx.addSource("my-src", SourceSpec(type = "geojson", json = """{"type":"geojson","data":{...}}"""))
ctx.addLayer(LayerSpec(id = "my-layer", json = """{"id":"my-layer","type":"line","source":"my-src"}"""))

Sources and layers are style JSON as text, not SDK objects, for the same reason the map is a facade: a plugin compiled against RasterSource breaks the day the SDK renames it. On teardown, layers are always removed before their sources.

The map facade

interface MapFacade {
    fun hasSource(sourceId: String): Boolean
    fun addSource(sourceId: String, spec: SourceSpec)
    fun removeSource(sourceId: String)
    fun hasLayer(layerId: String): Boolean
    fun addLayer(spec: LayerSpec)
    fun removeLayer(layerId: String)
    fun requestCamera(intent: CameraIntent)   // intent, not a command
    fun camera(): CameraIntent                // read-only
}

Deliberately small: every method here is a promise to keep working across engine upgrades, so the bar for adding one is that a plugin cannot do its job without it. Note there is no camera setter — requestCamera records intent, and the host's camera controller stays the only writer.

Storage, bus and entitlements

ctx.storage.set("last-run", System.currentTimeMillis().toString())
val sub = ctx.subscribe("position") { payload -> /* … */ }
ctx.publish("my.topic", payload)
if (ctx.entitlements.isPro) { /* … */ }

Storage is namespaced by plugin id, so two plugins cannot collide on a key. Subscriptions are released for you; canceling one twice is not an error, because the host may already have released it.

Anything the API does not model

ctx.onCleanup { myTimer.cancel() }

Plugin SDK — panels

A plugin describes rows; the host draws them, in its own controls, honoring night mode, glove mode and the accent color.

Why not just hand you a View?

Two reasons, and the cost of the choice is stated rather than hidden. A View crossing a DexClassLoader boundary pins the host's UI toolkit into the plugin contract — every plugin would then compile against our Compose and our theme, and an upgrade to either becomes a breaking change for every plugin installed. And a map app has one design system: a panel drawn by a plugin in its own style does not read as an extension of the app, it reads as a different app inside it, including in the wrong colors on a night-mode map.

The cost, plainly: a plugin that needs a chart or a camera preview cannot have one here yet.

The rows

RowRenders as
Reading(label, value)A label and a value, in the shape the diagnostics screens use
Note(text)A paragraph — an explanation, or a refusal with its reason in it
Section(title)A heading between groups of readings
Action(id, label, enabled, onInvoke)A chip that does something. enabled = false draws it dimmed rather than hiding it
Toggle(id, label, on, onChange)A chip that is lit or not, reporting its new state when pressed

rows is a lambda, and you must call refreshPanels()

ctx.registerPanel(PluginPanel(
    id = "mine",
    title = "My Plugin",
    rows = { listOf(PanelRow.Reading("Queue", "$queued")) },   // read at DRAW time
))

rows is a lambda rather than a list so a panel showing a count, a status or a reading updates when the underlying thing moves — a snapshot taken at registration is a panel that is correct exactly once.

A live lambda is still not enough on its own. The lambda closes over your plugin's own fields — ordinary fields in an ordinary class, which the UI toolkit is not watching. So a panel showing a queue that grew because your backend sent an event keeps showing the old count until something unrelated redraws the screen. Call ctx.refreshPanels() whenever something a row displays has moved. It is cheap, idempotent, and safe to call from a background thread. Calling it when nothing changed costs one recomposition of one screen; not calling it is a panel that is correct once — the exact failure the lambda was chosen to avoid, arriving through the other door.

Plugin SDK — objects, taps, notices and search

Map objects

ctx.addObject(PluginObject(id = "beacon", featureJson = """
    {"type":"Feature",
     "geometry":{"type":"Point","coordinates":[-122.6765,45.5231]},
     "properties":{"name":"Beacon"}}
""".trimIndent()))
ctx.removeObject("beacon")

Geometry is a GeoJSON Feature as text — what the object store already speaks and what every export path understands. The host rejects anything that is not one. Your id is namespaced to plugin:<your-id>:<yours>, so two plugins choosing the same name cannot collide, a deactivate removes exactly what you added, and nothing the user drew is ever touched.

Map taps

ctx.onMapTap { tap -> /* tap.lat, tap.lon, tap.zoom */ }

Read-only, and you cannot consume the event.

Notifications

ctx.notify(PluginNotice(title = "My Plugin", body = "Something happened."))

A title and a body, and nothing else — see what a plugin cannot do for why.

Search providers

ctx.registerSearch { query ->
    if (!query.equals("beacon", ignoreCase = true)) emptyList()
    else listOf(PluginSearchResult("Beacon", "A place only this plugin knows", 45.5231, -122.6765))
}

The case this exists for is a team with its own gazetteer — grid squares, hut names, a numbered pole network — that no general geocoder has heard of, and which is exactly what somebody types into a search box first.

Search providers must be fast and offline. Yours runs beside the coordinate parser, which answers before anything is debounced. A provider that needs the network belongs behind a panel button, not here.

Plugin SDK — signing, trust and versioning

Signing

Sign with your own key. ZYRNMAPS reads the signature, records the SHA-256 fingerprint and shows it, and loads your plugin either way — see Who signed it for what users are told. Publish your fingerprint so people can check it.

Do not test with Android's shared debug keystore. Every debug build uses it, including ZYRNMAPS' own, so under it your APK reads as first-party — and the one path that was never in doubt becomes the only one you have tested. Generate a key of your own and confirm your plugin loads as third-party.
keytool -genkeypair -v -keystore my-plugin.keystore \
  -alias myplugin -keyalg RSA -keysize 2048 -validity 10000 \
  -dname "CN=My Plugin, O=My Org, C=US"

Host API versioning

ConstantValueMeaning
HostApi.VERSION2The current host API
PluginManifest.MIN_PLUGIN_API1The oldest plugin API still loaded

Version 2 is where the surface grew map objects, map taps, notifications, search providers and panel content. A plugin built against 1 declares minHostApi = 1 and still runs, because the comparison is minHostApi > VERSION; a plugin that needs the newer surface says so and is refused with a sentence rather than a NoSuchMethodError at some later moment.

The floor stays at 1 until something is actually removed from the contract. Refusing an old plugin costs a user a working tool; loading one that calls a deleted method costs them a crash — so the floor moves only when the second becomes true.

Manifest keys, in one place

KeyValue
zyrn-plugin-apiThe host API you built against. Doubles as the marker that this APK is a plugin
zyrn-plugin-classFully-qualified class implementing ZyrnPlugin
com.zyrntopo.plugin.ACTION_PLUGINThe intent action your marker activity declares, so the package is visible on Android 11+

The API version and the "is a plugin" marker are one key on purpose — a separate boolean would be a second thing that can disagree with the first.


Plugin SDK — a complete example

This exercises every surface the host honors today: it drops a marker, adds a panel with live counts, answers one search word, listens for taps and posts a notice on the third one.

class SamplePlugin : ZyrnPlugin {
    override val id = "com.example.sample"
    override val name = "Sample Plugin"
    override val minHostApi = 2
    override val capabilities = setOf(
        Capability.PANEL, Capability.STORAGE, Capability.MAP_OBJECTS,
        Capability.MAP_EVENTS, Capability.NOTIFICATION, Capability.SEARCH,
    )

    private var taps = 0
    private var lastTap: MapTap? = null

    override suspend fun onActivate(ctx: PluginContext) {
        ctx.addObject(PluginObject("beacon", """
            {"type":"Feature",
             "geometry":{"type":"Point","coordinates":[-122.6765,45.5231]},
             "properties":{"name":"Sample plugin beacon"}}
        """.trimIndent()))

        ctx.onMapTap { tap ->
            taps++
            lastTap = tap
            ctx.refreshPanels()          // without this the panel is frozen
            if (taps == 3) ctx.notify(
                PluginNotice("Sample plugin", "You have tapped the map $taps times.")
            )
        }

        ctx.registerSearch { query ->
            if (!query.trim().equals("beacon", ignoreCase = true)) emptyList()
            else listOf(PluginSearchResult(
                "Sample plugin beacon", "A place only this plugin knows about",
                45.5231, -122.6765,
            ))
        }

        ctx.registerPanel(PluginPanel(
            id = "sample",
            title = "Sample plugin",
            rows = {
                listOf(
                    PanelRow.Reading("Taps seen", "$taps"),
                    PanelRow.Reading("Last tap", lastTap?.let { "%.5f, %.5f".format(it.lat, it.lon) } ?: "none yet"),
                    PanelRow.Note("Search \"beacon\" to find this plugin's marker."),
                    PanelRow.Action("reset", "Reset count") {
                        taps = 0; lastTap = null; ctx.refreshPanels()
                    },
                )
            },
        ))
    }

    // Nothing here on purpose. The object, the tap handler, the search provider
    // and the panel all went through the context, which tracks them — they are
    // released on deactivate whether or not this class remembers them.
}
Note the empty teardown. That is not an oversight — it is the property the whole API was built around, and a sample that cleaned up by hand would demonstrate the opposite of the point.

Verifying your plugin actually loads

Unit tests cannot prove the part that really fails. Activating an object your test constructed says nothing about whether a separately compiled, separately signed APK loaded through a DexClassLoader sees ZyrnPlugin as the same interface the host holds. Install the real APK on a real device, enable it, and confirm the Plugins screen reports it as third-party and running.