facett-map — crate card
Reverse-engineered (from source — verify, don't trust the name)
-
One-line pitch: A geo viewer — a
(lon,lat)point layer plus a Web-Mercator vector basemap (line ways / filled silhouettes), CPU-painted with an optional wgpu GPU kernel ported from katana-osm's osm-viewer. -
Does:
MapViewplots points +Ways (roads/coastlines/water/rings,WayKind/WayStyle/Lod/classify) withmercator()projection. Two render paths behind oneui(): a default CPU painter (always available, headless-testable, never panics) and, behind featurewgpu, aGpuMapRenderer/MapPaintCallbackupload-once compute-cull +draw_indirectpipeline (cull.wgsl/draw.wgsl) plus an L0 SDF points lane (l0_points) routed through facett-core's sharedGpuSdfRenderer. Alsoindex,project,style,tessellatemodules. Graceful: no ways → just points; a host with no wgpu still gets a real basemap. -
Palette (
style):MapPalettecarries one colour per feature class (land/water/green/building/coast + road tiers +casing). Two sources:from_theme(re-themeable) andcartographic(day)— a fixed concrete-hex preset (light paper / dark skade basemap).classifyis a thin wrapper over the palette-drivenclassify_with(tags,&pal)(one tag-branch path, no twinning);PaletteMode {Theme, CartographicDay, CartographicNight}is the shared selector,MapIndex::recolor/MapPalette::recolor_wayreskin a built way set at runtime keyed on kind+width (geometry untouched, ≤1px).Way::from_lonlat_withbuilds a set directly in a preset. -
Line width (
style) — ONE formula, both lanes (studied from deck.gl):line_width_scale(zoom)+line_width_px(style_width, zoom)resolve a way's on-screen width as deck.gl'sPathLayerdoes —clamp(width × widthScale, widthMinPixels, widthMaxPixels)(LINE_WIDTH_MIN_PX1.0,LINE_WIDTH_MAX_PX24.0, scale ramp clamped[0.5, 3.0]aroundLINE_WIDTH_ZOOM_REF50 000). The GPU line lane applies it in the vertex shader every frame from awidth_scaleuniform (the vertex bake carries the RAW themed width and is zoom-independent), and the CPU basemap painter calls the same functions — so the two can never draw the same road at different widths. Guarded byline_width_is_pixel_clamped_and_shared_by_both_lanes(facett-demo). -
Dashes (
style) — ONE law, both lanes (HO-T3): aWayKind::Railcore is cut along the way's arc length atdash_period_px(w_px) = DASH_PERIOD_RATIO (4) × w_px,DASH_DUTY(0.5) lit, the gaps showing the casing under it — the Garmin rail.WayStyle::dashed()is the gate (roads: off). The GPU lane bakes the arc length ONCE per vertex (never per frame) intoLineVertex::col.aunder a dash flag inhalf_px's sign — the vertex stays 48 B — andline.wgsl's fill pass discards the gaps; the CPU painter walks the same arc length (layer::dash_polyline) and strokes oneShape::lineper lit run over the same whole casing — not epaint'sdashed_line_many, whose per-segmentline_segments are pixel-rounded to nothing on a dense polyline at a wide camera. Guarded on real pixels bytests/rail_dash_pixels.rs(horizontal and 45° rails carry the same dash count; a pan does not crawl; a road stays solid; the WGSL constants equal the Rust ones). -
What deck.gl (Uber's WebGL map lib) does that we did not — read from the bundled
PathLayer/ScatterplotLayershader source, and what we adopted:- Pixel-clamped width, resolved per frame. deck.gl:
vec2 widthPixels = vec2(clamp(project_size_to_pixel(instanceStrokeWidths * path.widthScale, path.widthUnits), path.widthMinPixels, path.widthMaxPixels) / 2.0);— a road never vanishes when zoomed out nor bloats when zoomed in, and every renderer of the same data agrees. Ours disagreed: the CPU painter scaledstyle.widthby a zoom factor in[0.5, 3.0]while the GPU bakedstyle.width × 1.0once into the vertex buffer (a constant that could never respond to zoom) — the same road drawn 2–3× apart depending on zoom, which reads as "the land and the lines are at two different scales". ADOPTED (see above). - Half-pixel smoothstep AA. deck.gl's shared primitive is
const float SMOOTH_EDGE_RADIUS = 0.5; float smoothedge(float edge, float x) { return smoothstep(edge - SMOOTH_EDGE_RADIUS, edge + SMOOTH_EDGE_RADIUS, x); }. Ours feathered with a linear ramp over a whole pixel, which is why our roads read softer. ADOPTED inline.wgsl(smoothstep(-r, r, edge_px - r),LINE_FEATHER_PX0.5-style radius). - Miter limit. deck.gl's default
miterLimitis 4 — ours already matched (MITER_LIMIT = 4.0). deck.gl additionally limits the inner corner by segment length (miterSize = min(miterSize, max(lenA, lenB) / cosHalfA)whencornerPosition < 0), which we do not yet do; the remaining artefact is inner-corner overshoot on switchbacks with segments shorter than the line width. NOT adopted (bounded by our 24 px ceiling; noted as the next gap). - Rounded joins/caps (
jointRounded/capRounded, both defaultfalsein deck.gl) — we are miter/bevel only, matching deck.gl's defaults. Not a gap at default settings. - PathLayer AA is MSAA +
discard, not analytic — its fragment shader only discards outside the joint (if (vJointType > 0.5 && length(vCornerOffset) > 1.0) discard;). Our SDF-feathered edge is arguably better than deck.gl's path AA; keep it. - High-zoom precision: deck.gl uses
fp64/offset-origin (project_uCoordinateOrigin,…64Lowattributes). We already solve this with the origin-local rebase againstMapIndex::origin— no gap (guarded bygpu_line_lane_and_cpu_basemap_share_one_scale_at_high_zoomin facett-geomap). - Label collision/declutter (
CollisionFilterExtension,getCollisionPriority): the biggest remaining visible gap was ours labelling every named OSMWay, so one street lettered its name 5–7× per view ("Feldkircher Strasse" ×7) and burned the label budget on repeats. ADOPTED a repeat-distance filter in facett-geomap'sosm2dlabel pass (MIN_REPEAT_PX380 + padded collision boxes), so the budget goes to distinct streets.
- Pixel-clamped width, resolved per frame. deck.gl:
-
Key deps:
facett-core(Facet, Theme; shared L0 GPU plumbing viafacett-core/wgpu),egui,serde_json; optionalegui-wgpu/wgpu/bytemuck(GPU kernel). -
Shape: lib; medium (gpu/index/l0_points/project/style/tessellate).
-
Observed role in the workspace: Re-exported by umbrella
facettasfacett::map; base layer reused by facett-geomap; fed OSM geometry by facett-osm; the demo installs its GPU renderer into eframe's RenderState. The umbrellamap-wgpufeature turns on itswgpulane.
Fill in (one line each — only the human knows these)
- Primary consumer(s):
- Why it exists (vs the obvious alternative):
- Hard constraint (airgap / no-C-deps / no-FFI / pure-Rust / etc.):
- Release cadence & independence (rides a parent? own repo someday?):
- Status (production / prototype / experimental / stubbed):
- Must-NOT-change invariant:
- Biggest known gap or TODO:
- Anything a newcomer would get wrong about it: