04
mapix
Interaction toolkit for canvas maps
Overview
mapix began with applying lightly maintained drawing and comparison libraries in the product. The basic interactions existed, but TypeScript support was incomplete, so I started evolving them for our product needs.
After stabilising area drawing and two-date comparison, I bundled in spatial-file parsing and shaped it into our own library, released under MIT. There is no product screen here — the API is the interface.
Starting pointsRectangle mode originalCompare original
Problem
The existing drawing and comparison libraries did not provide enough validation, constraints or type safety for the product. As each app added what it needed, map-code copies drifted and a bug fixed in one stayed alive in another.
The task was not only to centralise the code, but to redesign it as a typed library API that assumes nothing about a particular app.
Constraint
You cannot know the consuming app
Internal code may assume our styles and our layout. A package may not. Warnings have to show up in an app that injected no styles, and the slider has to stay usable in a layout where a panel covers half the screen.
Not everyone has a pointer
A comparison slider is a thing you drag. It still has to be operable by someone who cannot drag.
The maps belong to someone else
The slider borrows two maps the consumer created. When it is done it has to hand them back exactly as it found them — even if it is removed mid-drag.
Decision
Register the warning layer on the map itself
The package puts the self-intersection warning’s source and layers on the map itself, so a bad shape turns red even in an app that injected no styles. The stroke is one step thicker than the stack consumers commonly draw, so it covers without leaving a fringe.
Sharing an endpoint is not an intersection
Segment intersection is decided with strict inequalities. Neighbouring edges always share an endpoint, so allowing equality would flag every valid polygon. Coordinates are approximated as planar — at area-of-interest scale the verdict does not turn on curvature.
Finish the slider on the keyboard
The handle is a role="slider" in the tab order with arrows, PageUp/PageDown and Home/End. Holding a key does not spray events: one completion fires when the key is released or focus leaves.
Make it reversible
remove() writes back the inline styles each map container had at construction and drops every listener it added, so a map lent to the slider can be reused elsewhere with no residue.
Interface
import MapboxDraw from '@mapbox/mapbox-gl-draw';
import { DrawPolygon, DrawRectangle } from '@telepix-lab/mapix';
const draw = new MapboxDraw({
displayControlsDefault: false,
modes: { ...MapboxDraw.modes, draw_polygon: DrawPolygon },
});
map.addControl(draw);
draw.changeMode('draw_polygon', { maxVertices: 1000 });It slots in as a mode override, so nothing new has to be learned to use it. The vertex cap is set per mode entry.
const compare = new Compare(beforeMap, afterMap, container, {
orientation: 'vertical',
bounds: { min: 0.25, max: 0.95 },
initialRatio: 0.6,
});
compare.on('slideend', (event) => {
console.log(event.currentPosition);
});
compare.remove();bounds constrains each end separately, so a layout obstructed on one side keeps its reserve on the other. The initial position is a constructor option so the divider never shows at the centre for a frame first.
const result = await parseFile(file);
if (result.success && result.data) {
const vertexCount = countVertices(result.data);
// result.correction: 구조를 고쳤다면 무엇을 고쳤는지
} else {
// result.errorCode: 고정된 기계 코드. 문구는 쓰는 쪽에서 정한다
console.error(result.errorCode);
}Errors come back as stable codes rather than sentences, so an app running four locales can attach its own copy. Upload policy is left to the consumer.
Engineering
- FileGeoJSON, KML and Shapefile come in
- NormalizeNormalized to a polygon collection, structure repaired
- DrawSelf-intersection is judged while drawing
- CompareTwo map cameras are locked together
One path for mouse, touch and pen
Pointer Events handle all three through one path, and the handle captures the pointer on press. A drag survives the cursor leaving, and a second pointer can neither take it over nor end it. touch-action is disabled so the browser does not claim the drag for panning.
Resizing keeps the ratio
When the container resizes, the split ratio is preserved rather than the pixel offset — including a resize to zero and back, as when a tab or panel closes.
Padding travels with the camera
Camera sync carries padding too. Padding shifts the projection centre, so two maps holding the same center under different padding would draw it at different screen positions.
Write down what it cannot do
Draw modes do not work on MapLibre: the default mapbox-gl-draw theme uses a line-dasharray literal MapLibre rejects. The compatibility table states both the answer and the reason. File parsing has no engine dependency and works anywhere.
Result
Released under MIT in June 2026, with 0.2.0 following in September.
What began by filling gaps in existing drawing and comparison libraries became a map-interaction library for drawing, comparison and file handling, now consumed by SatCHAT. The README says up front that the public API may still change while it is 0.x.
This is an open source project. The code and documentation are quoted as published.