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.
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
- Install from Google Play. Updates arrive automatically.
- On a Fire tablet, or any device without Google Play, install from the Amazon Appstore instead. It handles installation and updates for you.
- 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.
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.
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.
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.
| Group | Layers | Good for |
|---|---|---|
| Topo & terrain | USGS Topo, USGS Imagery + Topo, OpenTopoMap, NatGeo World, Esri World Topo, Esri USA Topo (Historical) | Contours, trails and terrain; historical USGS quadrangles |
| Imagery | Esri Satellite, VersaTiles Satellite, USGS Imagery, Esri Firefly Imagery, Sentinel-2 Cloudless, Google Satellite, Google Hybrid | Aerial and satellite views; Firefly is muted so your markers stand out |
| Aeronautical | FAA VFR Sectional | Airspace and aviation planning in the US |
| Water | Esri Ocean Basemap, EMODnet Bathymetry, NOAA Nautical Charts (ENC) | Depths, coastlines and water search |
| Street & general | OpenStreetMap, Esri World Street, OSM Humanitarian, CyclOSM | Roads, towns and cycling |
| Vector | VersaTiles Eclipse (default) | A detailed dark map that stays sharp at every zoom |
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.
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).
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
- Frame the area you need.
- Open Layers → Save Terrain and confirm the size estimate.
- From then on, 3D uses the saved terrain for that area, with no connection.
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
.pmtilesfile, 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:
| Format | Example |
|---|---|
| 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 |
| UTM | 12S 741234 4328765 |
| MGRS | 12SVJ4123428765 |
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.
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
- In Saved Maps, tap Share on the map you want to send.
- The other person taps Scan in their own Saved Maps and points their camera at your screen.
- 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:
- Off: no GPS; the button is gray.
- Follow: the map keeps you centered as you move.
- 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.
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.
| Connection | Where it works |
|---|---|
| USB serial | Android app; Chrome and Edge on a computer |
| Bluetooth LE | Android app; Chrome and Edge on a computer |
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.
Search & Point Info
Place search
Tap search and type a place name, an address or coordinates in any supported format (DD, DMS, DDM, UTM or MGRS). Results come from OpenStreetMap; tap one to go there. The points of interest saved on your device are searchable offline.
Point Info
Tools → Point Info, then tap the map. For that spot you get:
- Coordinates in your format, and the elevation (online).
- Weather: current conditions, temperature and wind (online).
- Sun: sunrise, sunset, twilight and daylight left, worked out on your device so it works offline.
- Moon: rise, set and phase, with a drawing of the actual phase.
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
.pmtilesor.mbtilesfile you import;.mbtilesare 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
- Open Settings → Offline Maps → Import, or Menu → Map Data → Import.
- Pick a
.pmtilesor.mbtilesfile. Files of many gigabytes are fine. - 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
- Open Settings → Offline Maps → Select Area on Map, or tap the download button in the top bar. A green box appears.
- Drag the box over the area and pull its corners to size it.
- Choose the lowest and highest zoom. A live estimate shows the size before you commit.
- Tap Download Area. The download runs in the background and carries on after a restart if it is interrupted.
- 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?
| Area | Zoom range | Approximate size |
|---|---|---|
| One county (about 50 × 50 km) | 6–14 | about 120 MB |
| One county | 6–16 | about 600 MB |
| One state | 6–14 | about 1 GB |
| One state | 6–16 | about 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.
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.
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.
| Link | Needs internet | Reaches |
|---|---|---|
| Session code | ✓ | ZYRNTOPO users anywhere |
| Nearby TAK | ✗ | ATAK, WinTAK and iTAK on the same Wi-Fi |
| TAK server | Depends on the server | Everyone 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.
Team Sync — Over the Internet
- In Team Sync, set your name, role and team color.
- Create a session code, or type the one you were given. Codes are 10 characters of letters, digits and symbols.
- Share it with your team: show the QR code, copy it, or invite friends from your account.
- Tap Join Session. Everyone with the code sees the same map.
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.
Team Sync — Drones (MAVLink)
Drones and other vehicles that speak MAVLink (ArduPilot, PX4) appear on the map with their position, heading, altitude, armed state and battery. Open Team Sync → Drone tracking (MAVLink).
| Connection | How |
|---|---|
| Telemetry radio or flight controller over USB | Connect USB / Serial, with automatic port and baud detection (Android app; Chrome and Edge on a computer) |
| DroneBridge ESP32 | ws://192.168.4.1:5760 |
| mavproxy | --out=wsserver:0.0.0.0:5760, then ws://<computer>:5760 |
| Any MAVLink WebSocket | ws://<address>:<port> |
Set the flight controller's telemetry port to MAVLink at the same baud rate as your adapter (commonly 57600, 115200 or 921600). On Android, common USB serial adapters (FTDI, CP210x, CH340, PL2303 and CDC/ACM) work through USB-C or OTG.
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.
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 | shOn Windows, in PowerShell:
irm https://zyrntopo.com/tak-server.ps1 | iexIt 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.
| Port | Used by |
|---|---|
8090 | ZYRNTOPO |
8089 | ATAK, WinTAK and iTAK |
8443 | The dashboard, and file sharing between TAK apps |
8446 | Connection packages and certificate enrollment |
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.
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
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.
Print to PDF
Menu → Print to PDF makes a map sheet you can print at true scale, the way a USGS quad is printed:
- A ground scale such as 1:24,000, a UTM / MGRS or latitude-longitude grid labeled in the margins, and a legend of what is on the map.
- Paper from Letter and A4 up to wall size: Legal, Tabloid, A3, A2, A1, A0, Arch C and Arch D, portrait or landscape.
- 150, 200 or 300 DPI. The box on the map shows exactly what will print, and the panel shows the scale and the ground it covers.
It uses the map tiles you already have, so it works offline. The result is a real PDF file: save it or share it from Android, or download it in the browser.
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.
- Open Tools → Plugins.
- Tap Get plugins to see the published list.
- 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.
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.
| Permission | Used for |
|---|---|
| Location (background only while recording a track) | Your position, tracks and route following, and sharing your position when you choose to |
| Camera | Scanning QR codes of shared maps |
| Microphone | Voice notes and voice messages in Team Chat |
| Bluetooth | Meshtastic radios and Bluetooth GNSS receivers |
| Notifications | The notice while a track records or Team Sync runs, and new chat and items |
| Nearby Wi-Fi traffic | Seeing ATAK, WinTAK and iTAK devices on the same network |
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.
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.
Requirements
| Item | Requirement |
|---|---|
| Android version | Android 7.0 or newer |
| Graphics | Vulkan 1.0.3 or newer |
| Package name | com.zyrntopo.maps |
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
.pmtilesmap 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
- Install the plugin's APK — from a store, with
adb install, or by opening the file on the device. - Open ZYRNMAPS ▸ Menu ▸ Plugins. The plugin appears in the list.
- 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 says | What it means | What to do |
|---|---|---|
| Not enabled | Found, allowed, and waiting for you | Switch it on |
| Needs a newer ZYRNMAPS | Built against a host API this version does not have | Update ZYRNMAPS |
| Built for an older ZYRNMAPS | Built against a host API no longer supported | Update the plugin |
| No entry point | Its manifest names no plugin class, so there is nothing to load | Report it to whoever built it |
| Could not be loaded | The named class is missing, or is not a ZYRNTOPO plugin | Report it, quoting the reason shown |
| Requires Pro | The plugin declares itself Pro and this account has no entitlement | Unlock 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.
| Level | Meaning |
|---|---|
| First-party | Signed with the same certificate as ZYRNMAPS itself — shipped by us, or built by somebody holding our key |
| Third-party | Signed by somebody else. The fingerprint is shown, so you can check it against what the author published |
| Unknown | The signature could not be read at all. Rare, and it means the package manager refused — not that the APK is suspect |
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.
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>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.apkThen ZYRNMAPS ▸ Menu ▸ Plugins ▸ enable it.
Plugin SDK — the interface and lifecycle
What you declare
| Member | Type | Why the host wants it |
|---|---|---|
id | String | Namespaces your storage and your map objects |
name | String | What the Plugins screen shows |
minHostApi | Int | The lowest host API you work against, so the host can refuse rather than crash |
capabilities | Set<Capability> | What you need. Declared, never assumed |
tier | Tier | FREE (default) or PRO. The host refuses a Pro plugin without entitlement, and says so |
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 beUndo 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.
| Reason | Meaning |
|---|---|
HostTooOld | Built against a newer host than this one. The user needs a ZYRNMAPS update |
PluginTooOld | Built against a host API this version no longer speaks. The plugin needs updating |
NoEntryPoint | The manifest names no plugin class |
NotLoadable | The class could not be loaded, or is not a ZyrnPlugin. Carries the reason |
NotEnabled | Discovered 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.
| Capability | What it grants |
|---|---|
MAP_LAYERS | Add sources and layers to the map |
MAP_CAMERA | Read the camera and request moves. Never writes it directly |
MAP_QUERY | Query rendered features under a point |
MAP_OBJECTS | Put objects on the map and take them off again |
MAP_EVENTS | Be told where the user tapped — read-only |
MAP_ACTION | Contribute an entry to the map's context menu |
PANEL | Contribute a panel to the UI |
SEARCH | Contribute results to the search sheet |
NOTIFICATION | Post a notification |
LOCATION | Read the user's current position |
STORAGE | Namespaced persistent storage |
BUS | Subscribe 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
| Row | Renders 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.
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.
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.
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
| Constant | Value | Meaning |
|---|---|---|
HostApi.VERSION | 2 | The current host API |
PluginManifest.MIN_PLUGIN_API | 1 | The 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
| Key | Value |
|---|---|
zyrn-plugin-api | The host API you built against. Doubles as the marker that this APK is a plugin |
zyrn-plugin-class | Fully-qualified class implementing ZyrnPlugin |
com.zyrntopo.plugin.ACTION_PLUGIN | The 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.
}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.