A go language library for interacting with MapVision
Find a file
Eli Ribble 63ed070c29
Add per-request context cancellation to the HTTP client
Thread a context.Context through every HTTP request via a request()
helper bound with SetContext. WithContext sets it at construction; a
canceled context now aborts in-flight requests and the flexigrid page
loops stop dispatching further pages. This lets callers (e.g. nidus-sync
MapVisionSyncRun) cancel a long sync on shutdown instead of waiting for
every page to complete.
2026-08-26 21:36:03 +00:00
cmd Fix mapvision-go module path to source.gleipnir.technology/Gleipnir 2026-08-24 15:55:25 +00:00
db Add sample database schema for all the layers we capture 2026-08-21 23:51:20 +00:00
.gitignore Get to where we can get all the zones 2026-08-21 22:00:12 +00:00
access_group.go Add access groups flexigrid list 2026-07-21 04:20:35 +00:00
action.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
adult_trap.go adult_trap: support exporting all pages and JSON output 2026-08-22 18:47:23 +00:00
carcass.go Add batch-2 list clients: carcass, PIP, treatment plan/route, and master reference lists 2026-08-26 04:48:30 +00:00
client.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
contact.go Add support for parsing contact API 2026-07-21 04:10:46 +00:00
container_type.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
dashboard.go Add parsing of dashboard data 2026-07-21 01:31:01 +00:00
flexigrid.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
flexigrid_test.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
go.mod Fix mapvision-go module path to source.gleipnir.technology/Gleipnir 2026-08-24 15:55:25 +00:00
go.sum Add my_task parser 2026-07-20 21:14:32 +00:00
inv_item_lot.go Add APIs for a adult traps, inventory, larval samples, and more 2026-07-21 04:59:37 +00:00
inv_location.go Add APIs for a adult traps, inventory, larval samples, and more 2026-07-21 04:59:37 +00:00
inv_material.go Add APIs for a adult traps, inventory, larval samples, and more 2026-07-21 04:59:37 +00:00
larval_sample.go Add APIs for a adult traps, inventory, larval samples, and more 2026-07-21 04:59:37 +00:00
manage_pool_tests.go Add APIs for a adult traps, inventory, larval samples, and more 2026-07-21 04:59:37 +00:00
map_cache.go Add file-backed MapCache for streaming downloaded layers 2026-08-22 01:18:45 +00:00
map_cache_test.go Add file-backed MapCache for streaming downloaded layers 2026-08-22 01:18:45 +00:00
map_export.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
map_export_test.go Update from more layer captures 2026-08-21 23:51:06 +00:00
map_preferences.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
map_preferences_test.go Add hazard flag download and raw dumps 2026-08-21 22:58:35 +00:00
master_lists.go Add batch-2 list clients: carcass, PIP, treatment plan/route, and master reference lists 2026-08-26 04:48:30 +00:00
my_task.go Add my_task parser 2026-07-20 21:14:32 +00:00
operation_treatment.go Add OperationTreatment list client for /operation/treatment 2026-08-26 03:29:33 +00:00
operation_treatment_test.go Add OperationTreatment list client for /operation/treatment 2026-08-26 03:29:33 +00:00
pip.go Add batch-2 list clients: carcass, PIP, treatment plan/route, and master reference lists 2026-08-26 04:48:30 +00:00
public_sr.go service request, public service request, inventory material 2026-07-21 04:44:52 +00:00
README.md Fix mapvision-go module path to source.gleipnir.technology/Gleipnir 2026-08-24 15:55:25 +00:00
service_request.go service request, public service request, inventory material 2026-07-21 04:44:52 +00:00
site.go Add per-request context cancellation to the HTTP client 2026-08-26 21:36:03 +00:00
site_block.go Add site_block_adv list 2026-07-21 04:26:28 +00:00
treatment_plan.go Add batch-2 list clients: carcass, PIP, treatment plan/route, and master reference lists 2026-08-26 04:48:30 +00:00
user.go Add support for parsing contact API 2026-07-21 04:10:46 +00:00
wnv_response_log.go Add APIs for a adult traps, inventory, larval samples, and more 2026-07-21 04:59:37 +00:00
workflow.go Add workflow API 2026-07-21 04:28:20 +00:00

MapVision Go

A Go client library and CLI for MapVision Enterprise, a mosquito and vector control management platform. Since MapVision has no public API, this project scrapes data by authenticating via its HTML login flow and then either parsing server-rendered HTML pages or POSTing to the JSON endpoints that power its flexigrid data tables.

Installation

go install source.gleipnir.technology/Gleipnir/mapvision-go/cmd/mapvision@latest

Or build from source:

git clone https://source.gleipnir.technology/Gleipnir/mapvision-go
cd mapvision-go
go build -o mapvision ./cmd/mapvision

Quick Start

# Store credentials (saved to ~/.config/mapvision/config.json)
mapvision login -username myuser -password mypass

# List service requests from the My Task page
mapvision my_task sr list

# List all users (paginated)
mapvision user list --per-page 50

# Search sites by name
mapvision site list --search "Roseville"

Authentication

All data-fetching commands require login. Credentials are resolved in this order:

  1. CLI flags -username / -password
  2. Environment variables MAPVISION_USERNAME / MAPVISION_PASSWORD
  3. Saved config file at ~/.config/mapvision/config.json (respects $XDG_CONFIG_HOME)

Only mapvision login writes credentials to disk (pass --dry-run to skip the save). Every other command uses the credentials you supply for that invocation and never saves them.

mapvision login -username myuser -password mypass

The client stores session cookies in an in-memory cookie jar, so a single Login() call authenticates all subsequent requests.

CLI Commands

Core

Command Description
login Authenticate with MapVision
password-reset Request password reset for a username
Command MapVision Page
user View Users
contact View Contacts
access_group Access Groups
site View Sites
site_block View Site Block
site_block_adv View Site Block (Advanced)
workflow Workflow List
action Activity Cards
service_request Browse Service Requests
public_sr Pending Public Queue
inv_material Material Master
inv_item Item Master (same data as material)
inv_item_lot Inventory Item Lot
inv_location Inventory Location
wnv_response_log WNV Response Log
larval_sample Manage Larval Samples
adult_trap Adult Surveillance/Traps
pool_test Manage Pools and Tests

Map Data Export (GeoJSON)

Command Description
zones List the zones visible to the logged-in account (IDs feed map_export -zones)
map_export Export all map layers as GeoJSON FeatureCollections

The map_export command POSTs to POST /map/technician (the same endpoint the browser-side Google Maps JS uses) with a mode parameter for each data layer. It produces RFC 7946 GeoJSON files with properties attached to each feature.

# Export all layers as individual GeoJSON files
mapvision map_export -username myuser -password mypass

# Export specific zones, only open & closed SRs, WNV data for the past month
mapvision map_export -zones 68,63,64 -sr-status 2,3 -carcass-period month -mosquito-period month

# Write a single combined GeoJSON file
mapvision map_export --combined -out-dir ./export
Flag Default Description
-zones all zones from map preferences Comma-separated zone IDs (e.g. 68,63,64)
-property unset (no filter) Service provider ID (iSPId). Unset by default so the request returns all sites, matching the browser; setting it filters to that provider.
-sr-status all statuses SR status IDs: 2=Open, 3=Closed, 7=Review, 8=Reinspect
-carcass-period week WNV carcass time window: week, month, quarter, year
-mosquito-period week WNV mosquito time window: week, month, quarter, year
-out-dir . Output directory for GeoJSON files
-combined false Write a single map_export.geojson instead of per-layer files
-inactive-sites false Send InactiveSites=1 (include inactive sites)
-layer all layers Comma-separated layers to export: zones, sites, catch_basins, service_requests, site_blocks, treatment_plans, traps, wnv_carcasses, wnv_mosquito_pools. Useful to test one asset type at a time.
-dump-raw Write the raw /map/technician response JSON to a path and exit (diagnostic). Inspect with jq to see exactly which layers the server returns.
-dump-mode zones The mode to fetch when -dump-raw is set (e.g. catch_basins, treatment_plan).
# Export only traps (useful for testing one asset type)
mapvision map_export -layer traps -out-dir ./export

# Dump the raw catch_basins response to inspect its shape
mapvision map_export -dump-raw ./cb.json -dump-mode catch_basins

Output Files (per-layer mode)

File Geometry Type Contents
zones.geojson Polygon Zone boundary polygons with iZoneId, vZoneName, vDistrict
sites.geojson Point Site markers from the map's rs_site payload with lat/lng, iSiteId, vName, vTypeName, etc.
catch_basins.geojson Polygon Catch-basin subzone grid polygons with iZoneId, iSZoneId, vSubZone
service_requests.geojson Point SR markers with iSRId, vStatus, sr_type, contact info
site_blocks.geojson Polygon Site block polygons with iBlockId, vBlockName, rArea
treatment_plans.geojson Polygon Treatment plan polygons with iTreatmentPlanId, vPlanName, vScheduledActivity
traps.geojson Point Trap locations with iATTId, vTrapName, dTrapPlaced, dTrapCollected
wnv_carcasses.geojson Point WNV-positive carcass markers with iWRLId, tDescription, radius meters
wnv_mosquito_pools.geojson Point WNV-positive mosquito pool markers with iATPId, SpecieName, vPoolNo

Library Usage

params := mapvision.DefaultMapExportParams()
params.ZoneIDs = []int{68, 63}
params.CarcassPastMonth = true

result, err := client.MapExport(params)

// Access individual layers
for _, f := range result.Sites.Features {
    fmt.Printf("Site %s at %v\n",
        f.Properties["iSiteId"],
        f.Geometry.Coordinates,
    )
}

PostGIS Storage

The exported layers can be loaded into a PostGIS database. db/schema.sql creates one table per layer (all geometries in SRID 4326) with a GIST spatial index and an idempotent natural-key; db/load.sql streams the exported .geojson files in.

# Requires a PostgreSQL server with the postgis extension
psql -d mapvision -f db/schema.sql

# From the directory containing the exported *.geojson files
cd ./export
psql -d mapvision -f /path/to/mapvision-go/db/load.sql

db/load.sql uses jq to flatten each FeatureCollection to one feature per line and \copy ... FROM PROGRAM to stream them (handles the ~50MB sites.geojson), then upserts. Note two quirks it handles for you:

  • PostgreSQL COPY interprets backslash escapes, so a literal \t in a site name would otherwise corrupt the row; the loader doubles backslashes first.
  • site_blocks.i_block_id / i_site_id can be comma-separated lists, and a treatment_plans.i_treatment_plan_id can span multiple polygons, so those two tables have no unique key and are truncated on reload.
Table Layer Geometry
zones zone boundaries Polygon
sites site points Point
catch_basins subzone grid polygons Polygon
service_requests SR markers Point
site_blocks block polygons Polygon
treatment_plans plan polygons Polygon
traps trap locations Point
wnv_carcasses WNV carcasses Point
wnv_mosquito_pools WNV pools Point

Map API

MapVision exposes the interactive map as a single endpoint:

POST /map/technician
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest

Every layer is a mode=<layer> in the form body; a successful response is a JSON object whose keys are the layer payload arrays (e.g. rs_zone, rs_site, rs_at, rs_sr). The map page's filter panel renders as checkboxes whose name/value map directly onto the form fields, so the browser's request body is the authoritative parameter list.

Zone context dependency. Most layers return nothing unless the zone boundaries/sites request has been issued for the current selection. The browser fires mode=zones (the large payload, ~100 MB when all zones are selected) whenever a layer is toggled, and the zone/subzone selectors it carries gate the other layers.

Layer → request mapping.

Layer Body
zones + sites mode=zones&iZoneId=…&iSZoneId=…&gear_siteid=undefined&siterow=0&bNoSpray=…&bNotify=…&InactiveSites=…
catch basins mode=catch_basins&iSZoneId=…
service requests mode=sr&iZoneId=…&iSRSMId=2,7,3,8
site blocks mode=site_block&all_blocks=1&site_blocks=0&recent_site_block=0&exclude_site_block=0
treatment plans mode=treatment_plan
traps mode=trap&…
WNV carcasses mode=positive_wmv_carcas&wmv_past_week=…&wmv_year_date=0&iSRRRId=
WNV mosquito pools mode=positive_pool_mosquitos&mos_past_week=…&mos_year_date=0&iATPDId=

Filters. The checkbox-derived selectors, and what they do:

  • iZoneId / iSZoneId — zone and zone_subzone (e.g. 68_5) selectors. Subzones are required to enumerate every site; without them the server returns only a single default zone.
  • iSTypeId / iSSTypeId — parent site-type and sub-type IDs. Send the full list (all types) or the server returns almost nothing. Sub-type values arrive as raw checkbox values, where "Unknown" rows carry 0||<parent> — that's why the iSSTypeId field is ||-delimited.
  • iFCodeId — hazard-flag filter. Sending all hazard flags narrows sites to only those carrying a hazard (e.g. 63,805 → ~5,814).
  • iSPId / iSPId_arr — service-provider filter. Leave empty for the full site catalog; setting it restricts to that provider (this was the gate that silently returned 4 sites).
  • iSRSMId — SR status IDs (2=Open, 7=Review, 3=Closed, 8=Reinspect). Omit it and mode=sr returns no SRs.
  • bNoSpray, bNotify, InactiveSites — boolean toggles.
  • mode=site_block&all_blocks=0&site_blocks=0&recent_site_block=0&exclude_site_block=0 is the no-op the browser fires when deselecting a layer; it returns {"rs_blocks_poly":[]}.

Response shapes. mode=zones returns rs_zone (each zone embeds its sites array — the bulk of the payload) plus a small top-level rs_site. The dedicated modes return their own arrays: rs_sr, rs_blocks_poly, rs_treatment_plan_poly, rs_at (traps), rs_carcass, rs_tp_details (pools), and rs_subzone (catch-basin grid polygons).

Field quirks. Geometry comes in two formats: PShape as lng lat, lng lat pairs (zones, catch basins) and PShape1 as ((lng,lat),(lng,lat)) WKT (site blocks, treatment plans). rRadius_meter and the SR action field may arrive as a JSON number or string, and the hazard-flag checkboxes repeat a trailing value="" attribute — all handled by the exporter.

HTML-Scraped Pages (table subcommand required)

Command Tables
my_task sr, ls/lab_surveillance, wf/workflow, pip/parcel_inspection
dashboard sra/sr_assigned, pdb/positive_dead_birds, ann/announcements, pmp/positive_mosquito_pools, sr/service_requests

Library Usage

import mapvision "source.gleipnir.technology/Gleipnir/mapvision-go"

func main() {
    client := mapvision.NewClient("https://placer.leateamapps.com",
        mapvision.WithDebug(true),
    )

    // Authenticate
    if _, err := client.Login("username", "password"); err != nil {
        log.Fatal(err)
    }

    // Flexigrid-backed list
    users, _ := client.UserList(mapvision.DefaultUserListParams())
    for _, u := range users.Items {
        fmt.Printf("%s: %s <%s>\n", u.ID, u.Name, u.Email)
    }

    // Or fetch the full my_task HTML page and parse all sections
    page, _ := client.MyTask()
    for _, sr := range page.ServiceRequests {
        fmt.Printf("SR #%s: %s (%s)\n", sr.SRID, sr.Status, sr.SRType)
    }
}

Downloading the map to a file cache

Client.DownloadMapCache fetches all geographic layers over the network once, writes each to a GeoJSON file in a local directory, and returns a MapCache whose features are streamed back through a Go iterator — so the ~50 MB sites.geojson is never held in memory at once. Subsequent reads reuse the files via OpenMapCache with no network access.

import mapvision "source.gleipnir.technology/Gleipnir/mapvision-go"

// client is the logged-in client from the example above.

// 1. Download every layer into ./cache (hits the network; ~2 min for all zones).
cache, err := client.DownloadMapCache(mapvision.DefaultMapExportParams(), "./cache")
if err != nil {
    log.Fatal(err)
}

// 2. Iterate a cached layer — streams features from disk, no network.
for feat := range cache.Features(mapvision.LayerSites) {
    fmt.Printf("site %v at %v\n",
        feat.Properties["iSiteId"],
        feat.Geometry.Coordinates,
    )
}

fmt.Printf("total sites: %d\n", cache.Count(mapvision.LayerSites))
for _, layer := range cache.LayerNames() {
    fmt.Printf("cached %s: %d features\n", layer, cache.Count(layer))
}

// 3. Later (new process), reopen the cache without re-downloading.
cache = mapvision.OpenMapCache("./cache")
for feat := range cache.Features(mapvision.LayerTraps) {
    fmt.Printf("trap %v\n", feat.Properties["iATTId"])
}

DefaultMapExportParams() downloads the full catalog (all zones, subzones, and site types for the account). To cache a subset, set params.Layers — e.g. params.Layers = []string{"sites", "traps"} — or pin params.ZoneIDs.

Architecture

The project has two packages:

mapvision — Library package (root directory)

  • client.go — HTTP client with cookie jar, Login(), MyTask(), Dashboard(), and all list methods
  • flexigrid.go — Generic pagination engine for flexigrid JSON endpoints
  • my_task.go / dashboard.go — HTML DOM parsers using golang.org/x/net/html
  • One file per MapVision entity (e.g., user.go, site.go, workflow.go) — struct definition + list method
  • map_export.go — Map export client methods, WKT polygon parser, GeoJSON serialization for all map layers (zones, sites, SRs, blocks, treatment plans, traps, WNV markers)

cmd/mapvision — CLI binary

  • main.go — Subcommand dispatch, tabular output using text/tabwriter, credential resolution
  • config.go — XDG config file read/write for persistent credentials

Patterns

Flexigrid List Pattern

Most MapVision data tables are powered by the flexigrid jQuery plugin. The JS sends a POST with form-encoded pagination params (pg, rp, sortname, sortorder, query, qtype) to ?mode=List and receives a JSON response:

{
  "page": 1,
  "total": 42,
  "rows": [
    { "id": "335", "cell": ["335", "Nidus", "nidus@example.com", ...] }
  ]
}

The cell value can be either a positional array or a keyed object. The generic FlexigridList[T]() function handles both:

func FlexigridList[T any](
    c *Client,
    endpoint string,
    params FlexigridListParams,
    defaultSortName string,
    defaultSortOrder string,
    mapRow func(rowID string, cells []string, obj map[string]string) T,
) (*FlexigridListResult[T], error)

Each entity file defines a struct, a row mapper, and a thin public method:

type Entity struct { ... }

func (c *Client) EntityList(params EntityListParams) (*EntityListResult, error) {
    return FlexigridList(c, "/path?mode=List", params, "defaultSort", "DESC", entityRowMapper)
}

HTML Page Parsing

The My Task and Dashboard pages have no JSON endpoints — the server renders full HTML pages. These are parsed into typed structs using golang.org/x/net/html:

  • Table rows are extracted by DOM ID traversal (findByID()findRows()findCells())
  • Priority colors are parsed from inline style="background-color:#XXXXXX" attributes
  • <br> tags in cell text are preserved as newlines
  • Dashboard alert boxes are located by heading text (e.g., "SR Assigned Alert") and navigated upward to the parent .alertbox div

Credential Storage

Credentials are stored as JSON at ~/.config/mapvision/config.json (respects $XDG_CONFIG_HOME). The config file has 0600 permissions. Only mapvision login writes credentials to disk — pass --dry-run to skip the save. All other commands resolve credentials from flags, then MAPVISION_USERNAME/MAPVISION_PASSWORD, then the saved config, and never write them back.

Adding a New Flexigrid Entity

  1. Create a new file in the root package (e.g., new_entity.go)
  2. Define a struct with JSON tags matching the flexigrid colModel
  3. Create type aliases: NewEntityListParams = FlexigridListParams, NewEntityListResult = FlexigridListResult[NewEntity]
  4. Write a NewEntityList() method on Client that calls FlexigridList()
  5. Write a newEntityRowMapper() that populates the struct from either the object map or positional cells
  6. Add a CLI handler in cmd/mapvision/main.go: switch case, usage entry, handleNewEntity(), displayNewEntities()