Happy 2022! Hopefully this year will be better than the last two -- that shouldn't be difficult.

Welcome back to my blog series on implementing procedurally-generated map compasses! So far I've implemented the language and interpreter for all the capabilities needed to draw compasses like this example:

# Radial text # RTEXT(start, repeats, font, size, color, style, orientation, texts) rtextElement -> "RTEXT"i WS "(" WS decimal WS "," WS decimal WS "," WS dqstring WS "," WS decimal WS "," WS dqstring WS "," WS dqstring WS "," WS dqstring WS "," WS dqstring WS "," WS dqList WS ")" WS {% data => ({op: "RTEXT", start: data[4], repeats: data[8], font: data[12], size: data[16], color: data[20], style: data[24], weight: data[28], orientation: data[32], texts: data[36]}) %}

function rtext(svg, center, radius, startAngle, repeats, angle, iteration, op) { let x = center[0]; let y = center[1]-radius; // If orientation == vertical, then rotate [x, y] around the center by // angle to find the spot for the label. if (op.orientation == 'vertical') { [x, y] = rotate(center, [x, y], angle-Math.PI/2); }; // Now draw the label let label = svg.append('text') .attr('x', x) .attr('y', y) .style('font-family', op.font) .style('fill', op.color) .style('font-size', op.size) .style('font-style', op.style) .style('font-weight', op.weight) .style('text-anchor', 'middle') .text(op.texts[iteration]); // If orientation == radial, then we've drawn the label at 12 o'clock // and need to use SVG transform to rotate it around to the correct // spot. if (op.orientation != 'vertical') { label.attr('transform', 'rotate('+rad2degrees(angle-Math.PI/2)+','+center[0]+','+center[1]+')'); }; };

# Radial arc # RARC(start, repeats, subtend, width, color) rarcElement -> "RARC"i WS "(" WS decimal WS "," WS decimal WS "," WS decimal WS "," WS decimal WS "," WS dqstring WS ")" WS {% data => ({op: "RARC", start: data[4], repeats: data[8], subtend: data[12], width: data[16], color: data[20]}) %}

// Make a circular polygon function makeCircle(center, radius, num) { const result = []; const step = (2*Math.PI)/num; for(let t=0;t<(2*Math.PI);t += step) { result.push([radius*Math.cos(t)+center[0],radius*Math.sin(t)+center[1]]); }; // Close off the circle result.push(result[0]); return result; };

// Make a circular arc function makeCircularArc(center, radius, start, end, num) { const [x, y] = center; const result = []; const step = (end-start)/num; for(let t=start;t<end;t += step) { result.push([radius*Math.cos(t)+x,radius*Math.sin(t)+y]); }; result.push([radius*Math.cos(end)+x,radius*Math.sin(end)+y]); return result; };

function rarc(svg, center, radius, startAngle, repeats, angle, iteration, op) { const arcStart = angle-0.5*op.subtend; const arcEnd = angle+0.5*op.subtend; const outsideEdge = makeCircularArc(center, radius, arcStart, arcEnd, 20); const insideEdge = makeCircularArc(center, radius-op.width, arcStart, arcEnd, 20); };

function rarc(svg, center, radius, startAngle, repeats, angle, iteration, op) { const arcStart = angle-0.5*op.subtend; const arcEnd = angle+0.5*op.subtend; const outsideEdge = makeCircularArc(center, radius, arcStart, arcEnd, 20); const insideEdge = makeCircularArc(center, radius-op.width, arcStart, arcEnd, 20); const polygon = outsideEdge.concat(insideEdge.reverse()); svg.append('path') .style('stroke-width', 0) .style('stroke', 'none') .style('fill', op.color) .attr('d', lineFunc(polygon)); };And here is a result:

- I've suggested that RARC can be used to draw the alternating black and white scale as used in this compass:

Write the CDL for this compass and generate it. How did you handle the lines around the white arcs? - For map borders, I sometimes generate a scale with three colors:

Create a compass with a three-colored scale with all the colors outlined. How did you handle the lines between the arcs? - This compass has triangles at the ordinal points on the outer scale that have black and white halves like compass points:

Implement this as an option in RTRI. Ignoring the hatched shading, can you now recreate the entire compass?