Module for spatial data and map visualization.
spatial is a map engine: it configures, builds, projects, reads and draws on
maps. That is config; from core, the factory with its Leaflet plugins,
http, util, the store, crs, projection, Map and createMap; all of
data, the layer switcher included; and all of tools.
Its React components are frozen: all of ui, and from core, control(),
renderCycle, MapContainer, connect and Provider. They keep working on
the page's map as they do now and get a fix when something breaks, but nothing
new, and nothing teaches them about a second map. A later release removes them,
once nothing that loads spatial.js uses them. A new component, or a change to
one, belongs in perun-atlas, whose controls read the map they are on.
Everything that differs between two installations — the coordinate reference system, where the map opens, how far it zooms, which units the scale bar uses — is a setting, and settings arrive through one door:
import { configure } from 'spatial/config';
configure({
crs: 'EPSG:3857',
center: { lat: 35.126411, lng: 33.429859 },
bounds: [{ lat: 34.55, lng: 32.27 }, { lat: 35.70, lng: 34.60 }],
zoom: 8,
minZoom: 0,
maxZoom: 18,
measurementSystem: 'metric',
switchBboxOrder: false
});Partial updates are fine and the order of calls does not matter. Settings that
describe the map itself are applied to it as they change, so configure() works
before the map is drawn and after.
crs takes 'EPSG:3857', 'EPSG:3395' or 'EPSG:4326', which the engine
resolves itself, or { code, def, opt } carrying a proj4 definition for a
national grid. A national grid needs opt.origin — its own top-left corner, in
its own units — and one of opt.scales, opt.resolutions or opt.distances.
Leave either out and the engine warns and falls back to Web Mercator's, which
will not be right.
Nothing is read from the page. The window.sysCrs, window.sysCenter,
window.sysBounds, window.measurementSystem and window.switchBboxOrder
globals were removed in 4.2.1; a deployment on svarog keeps these values as
SPATIAL_* system parameters, and perun-atlas resolves them and calls
configure() at startup. config.SYS_CENTER, config.SYS_BOUNDS and
MAP_CONFIG.minZoom and maxZoom are still there for bundles built against
4.2.0, and answer with the settings; new code reads setting().
Configure nothing and you get the whole world on the Web Mercator tile grid, which is the grid basemaps are published on and belongs to no country. Before 4.2.1 you got Moldova's projection, centre and bounds instead.
settings() returns everything as it currently stands, for a console when a
deployment is behaving oddly.
core.Map is the page's map, built when spatial.js loads.
core.createMap(element, options) builds another each time it is called:
const { createMap } = spatial.core;
const map = createMap(container, { center: { lat: 41.99, lng: 21.43 }, zoom: 9 });element is the element to build the map in, or its id. You own it and give it
its size, and nothing else on the page is touched. options are Leaflet map
options, laid over the engine's own and over the settings as they stand at the
call: crs, center, zoom, minZoom and maxZoom. crs takes any form
configure() takes, or a CRS. An option given as undefined leaves the setting
under it in place. A later configure() moves only the page's map; a created
map is yours, and you move it with setCRS, setView and the zoom limits.
Every map, the page's included, has getCRS(), setCRS(crs),
transform(latlng), untransform(point), getBBox() and setCursor(type).
Each works on the map it belongs to, and still does when passed on unbound.
map.draw and map.edit are the map's own drawing and editing tools, the same
tools tools.draw and tools.edit hold. Each set is built the first time it is
read. A drawing tool draws on its own map, and its events fire there:
map.on('new_shape', ({ shape, layer }) => { /* ... */ });
map.draw.polygon.enable();An editing tool works on the map of the layer it is given, and snapping snaps
to the layers on that map only. The module-level tools.draw and tools.edit
are the page's map's sets (tools.draw === Map.draw). Only what is drawn on
the page's map writes the store, the digitising minZoom and the measurement
totals; tools on a created map leave it alone.
A GeoJSON layer reads its stored coordinates through the page's map's CRS, as it stands when data is added. A layer for a created map on another CRS says so:
factory.geoJSON(data, { crs: map.getCRS() }).addTo(map);crs takes any form configure() takes, or a CRS. A dbCRSCode in the store
still wins wherever it names one of the three CRSs Leaflet carries.
These work on the page's map only, as they always have: Map.render and the
app builder ui.init; the render cycle and MapContainer, with the store's
map slice that it applies; every control added through control(); the older
measurement toolbar in ui/measurement; and tools.limits.
When whatever built a map goes, call map.remove(). It takes with it
everything the engine added:
- the drawing and editing tools in
map.drawandmap.edit, turned off first if any is on, including a drawing tool toldrepeatable; - every control on the map, the layer switcher and fullscreen included;
- every layer, and the listeners the engine and its tools put on them.
What you put on something outside the map is still yours to take off: a
listener on a layer you keep, on document, on window, or on another map.
A layer you keep outlives the map and can be added to another one.
The version is the one in backend/pom.xml: the OSGi bundle svarog runs, with
spatial.js inside it, carries it as its Bundle-Version. Between releases the
pom names the next one with -SNAPSHOT, as Maven expects.
package.json names the last version cut, a release or a release candidate,
and spatial.version reports it at runtime. Each is tagged with its number, as
a lightweight tag, on the commit whose package.json says it.
- A candidate sets
package.jsonto the pom's number with-rc.N(4.2.1-rc.1), rebuildsspatial.js, and is tagged. The pom keeps its-SNAPSHOT, so nothing is released to Maven. - A release drops the
-SNAPSHOTfrom the pom, setspackage.jsonto the same number, rebuilds, and is tagged (4.2.1). The next commit moves the pom to the next snapshot, andpackage.jsonstays until the next candidate. Push the release commit on its own first: CI deploys only the commit at the head of a push, so pushed together with the snapshot it would never be deployed.
Push each tag by name (git push origin 4.2.1); --follow-tags skips
lightweight tags. A consumer depends on releases by tag:
"spatial": "git+https://git@gitlab.prtech.mk/svarog4/svarog-spatial#semver:^4.2.1"pnpm takes the newest tag in the range, and the lockfile records its commit.
#4.2.1 pins one release. ^4.2.1 skips candidates; #semver:^4.2.1-rc.1
takes them too, and once 4.2.1 is tagged, pnpm update spatial moves it there.
The tags have to stay lightweight: for an annotated tag pnpm 9 and 10 record
the tag's own hash and pnpm 11 the commit's, and pnpm 11 then refuses a lockfile
the others wrote.
Node 22.12 or newer, and pnpm.
pnpm install
pnpm run build # backend/www/spatial.js, which is committed with each change
pnpm run dev # the same, then again on every save
pnpm run lint # what CI asks; lint:fix repairs your working tree insteadspatial.js is one UMD file publishing window.spatial, with perun-core left
to the shell's global. Its stylesheets travel inside it and are added to
<head> as it loads.
The drawing, editing and snapping tools are adapted from leaflet-geoman (MIT).
NOTICE.md says which files, what changed from upstream, and lists the other
files that carry a licence of their own.