Resources Numerical accuracy Calculation policy

Map layers under the drawing

Draws map pictures and tiles under the drawing, warped from the map's coordinate system to the drawing's.

How the precision is kept

This code only draws. Pictures and tiles are warped onto the canvas in patches to a tolerance of a quarter of a screen pixel. Nothing computed here is stored in the drawing.

Calculations under this policy

2 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

maplayers.js CadCraft 0.8.2 32-bit, display only 2 calculations Reviewed

Tests bin/test-maplayers.mjs

Warp plan for a picture or tile

32-bit, display only

How finely a map image is cut into patches so that it lands in the right place on screen.

tolerance = 0.25 ÷ zoom project units, rounded to a half power of 2

The tolerance is a quarter of a screen pixel at the current zoom. The subdivision depth handed to basemap.js is 0 for a layer already in the drawing's system and 7 otherwise.

  • maplayers.js
  • lines 153 to 165
  • CadCraft 0.8.2
// ---------------------------------------------------------------- painting
// V = { T (project → CSS px), w, h, dpr, ox, oy, zoom (CSS px per project unit) }
function planFor(store, key, w, h, pixToSrc, cv, zoom) {
  const tolProj = 0.25 / zoom, bucket = Math.round(Math.log2(tolProj) * 2) / 2;
  const k = key + '|' + bucket + '|' + fcache.gen;
  let plan = store.get(k);
  if (!plan) {
    plan = B.planWarp((u, v) => { const s = pixToSrc(u, v); try { return cv.fromSrc(s[0], s[1]); } catch { return null; } }, w, h, { tol: 2 ** bucket, maxDepth: cv.same ? 0 : 7 });
    if (store.size > 8) store.clear();
    store.set(k, plan);
  }
  return plan;
}

Drawing the patches

32-bit, display only

Each patch of a map image drawn on the canvas.

The image-to-screen transform of each patch is the product of the view transform and the patch's own, set on the 2D canvas. When there is more than one patch, each patch's clip outline is pushed outward by 0.6 device pixel.

  • maplayers.js
  • lines 166 to 193
  • CadCraft 0.8.2
function drawPlan(ctx, V, img, plan, sw, sh) {
  const T = V.T, d = V.dpr;
  // device px = d·(T·p + o)
  const D = [d * T[0], d * T[1], d * T[2], d * T[3], d * (T[4] + V.ox), d * (T[5] + V.oy)];
  const S = (x, y) => [D[0] * x + D[2] * y + D[4], D[1] * x + D[3] * y + D[5]];
  const single = plan.length === 1;
  for (const c of plan) {
    const m = c.m;   // image px → project
    const a = D[0] * m[0] + D[2] * m[1], b = D[1] * m[0] + D[3] * m[1], cc = D[0] * m[2] + D[2] * m[3], dd = D[1] * m[2] + D[3] * m[3];
    const e = D[0] * m[4] + D[2] * m[5] + D[4], f = D[1] * m[4] + D[3] * m[5] + D[5];
    ctx.save();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    if (!single) {
      const q = c.dst.map(([x, y]) => S(x, y)), mx = (q[0][0] + q[1][0] + q[2][0] + q[3][0]) / 4, my = (q[0][1] + q[1][1] + q[2][1] + q[3][1]) / 4;
      ctx.beginPath();
      q.forEach(([x, y], i) => { const dx = x - mx, dy = y - my, L = Math.hypot(dx, dy) || 1, gx = x + dx / L * 0.6, gy = y + dy / L * 0.6; if (i) ctx.lineTo(gx, gy); else ctx.moveTo(gx, gy); });
      ctx.closePath(); ctx.clip();
    }
    ctx.setTransform(a, b, cc, dd, e, f);
    if (single) {
      const W = Math.hypot(a, b) * sw, H = Math.hypot(cc, dd) * sh;
      if (W > 2 && H > 2) { ctx.translate(sw / 2, sh / 2); ctx.scale((W + 1) / W, (H + 1) / H); ctx.translate(-sw / 2, -sh / 2); }
    }
    const u0 = Math.max(0, Math.floor(c.src[0][0]) - 1), v0 = Math.max(0, Math.floor(c.src[0][1]) - 1), u1 = Math.min(sw, Math.ceil(c.src[2][0]) + 1), v1 = Math.min(sh, Math.ceil(c.src[2][1]) + 1);
    if (u1 > u0 && v1 > v0) ctx.drawImage(img, u0, v0, u1 - u0, v1 - v0, u0, v0, u1 - u0, v1 - v0);
    ctx.restore();
  }
}

In detail

Method
A warp plan from basemap.js, kept per zoom step; each patch is drawn with its own canvas transform.
Number types
64-bit numbers for the transforms; the pixels are drawn by the browser's 2D canvas.
Tolerances
0.25 screen pixel, rounded to half powers of 2; up to 7 levels of subdivision.

Approach: Display only

Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.

Every policy that takes this approach