📍 The map (GeoMap)
Updated
The shared map control for the SPA: tiles + an overlay, zoom / pan / Fit to
data / Default view. Sales agents desk (/field-dispatch, ribbon caption Sales
agents desk (Пульт торговых агентов)), the dashboard widget (Пульт торговых агентов), and the settings page all
draw it; the next screen with a map does not copy Mercator.
What it is#
Not Leaflet and not a second engine. Web Mercator, tile images from a
{z}/{x}/{y} template (the optional {s} = a/b/c), and an SVG or any children
overlay on top. Pan is pointer + preventDefault / dragstart — otherwise a
second drag with the mouse drags the browser's native ghost of the tile over the
map. The overlay: pointerEvents: 'none' on the SVG, auto only on the pins.
World points and the viewport are computed in
frontend/src/kit/map/geoMapMath.ts.
<GeoMap settings={settings} fitPoints={points} fitKey={selected ?? 'all'} controls="full">
{({ project, width, height }) => (
<svg width={width} height={height} style={{ position: 'absolute', inset: 0 }}>
<circle cx={project(lng, lat).x} cy={project(lng, lat).y} r={6} />
</svg>
)}
</GeoMap>
fitKey rebuilds the frame (a change of agent). Live points do not move the
pan: the Fit to data button takes the current set. follow +
followPoint / followKey keep the pin centred without changing the zoom; a
pan or the wheel drops the following. controls: full | compact | none.
Zoom in half steps. A wheel notch or a press of + / − changes the
zoom by 0.5 (ZOOM_STEP), not by a whole level: a whole level doubles the
scale and jumped past the street the user was looking for. Tile servers have
only whole levels, so a fractional zoom draws the nearest whole level scaled —
at 13.5 the tiles of level 14 at 0.71×.
Where the viewer is. locateViewer (on by default) asks the browser for the
position once, with its usual permission prompt, then follows it. The map shows
a blue dot with a translucent disc for the accuracy and a My location
button that centres on it at street zoom. A map with nothing of its own to fit
opens on the viewer instead of the stand's default centre. A refused or
unavailable position leaves the dot out and greys the button. Browsers give a
position only over HTTPS or on localhost. Pass locateViewer={false} where the
viewer's position means nothing, for example a document's static route.
The stand's settings#
Administrator → Map (/map-settings). This is infrastructure, like SMTP,
not an object of a model.
MetaSystemSettings key |
Config | Meaning |
|---|---|---|
Map.TileUrl |
Map:TileUrl |
The tile template, http(s), {z}{x}{y} are mandatory |
Map.Attribution |
Map:Attribution |
The credit in the corner |
Map.DefaultLat / Lng / Zoom |
the same through : |
The empty frame |
Map.MinZoom / MaxZoom |
the same | The range 1…22 |
A database row overrides the config, and the config beats the built-in OSM / Kyiv. The API:
| Method | Who | Path |
|---|---|---|
| GET | anyone signed in | /api/map-settings |
| PUT | Admin | /api/map-settings |
| DELETE | Admin | /api/map-settings — drop the overrides |
A commercial tile service's key inside the URL is visible to everyone who opens
the map. Restrict it by referrer at the provider; secrets are not put into
MetaSystemSettings.
Where it already sits#
- Sales agents desk
/field-dispatch— page title Sales agents desk (Пульт торговых агентов): KPI strip, the pin, the day's track, the visits - The Sales agents desk dashboard widget (Пульт торговых агентов) — the same pins, compact layout without the four KPI tiles
- The admin preview on the settings page