Skip to content

Instantly share code, notes, and snippets.

@darrenwiens
Created May 10, 2022 18:32
Show Gist options
  • Save darrenwiens/5369c959a7e1976132cdcc2ad92fad05 to your computer and use it in GitHub Desktop.
Save darrenwiens/5369c959a7e1976132cdcc2ad92fad05 to your computer and use it in GitHub Desktop.
Geographic font
import fontforge
import json
from shapely.geometry import MultiPolygon, shape
from shapely.affinity import translate
font_file = "path/to/font.sfd" # path to sfd font file, created in FontForge GUI
geo_file = "path/to/features.geojson" # path to geographic features file
font = fontforge.open(font_file)
glyph_w = 1000 # glyph width
glyph_h = 800 # glyph height
with open(geo_file, "r") as f:
features = json.load(f)["features"]
def scaled_xy(x, y, minx, miny, maxx, maxy): # scale coordinates to fit inside glyph
scaled_x = (x - minx)/(maxx - minx)
scaled_y = (y - miny)/(maxy - miny)
return (scaled_x * glyph_w, scaled_y * glyph_h)
for i, feature in enumerate(features): # create a glyph for each feature
mpoly = shape(feature["geometry"])
new_geoms = []
for part in mpoly: # handle features that straddle antimeridian
if part.centroid.x < 0:
new_geoms.append(translate(part, xoff=360.0, yoff=0.0, zoff=0.0))
else:
new_geoms.append(part)
new_mpoly = MultiPolygon(new_geoms)
bnds = new_mpoly.bounds # feature bbox
font.createChar(i) # create a new glyph
pen = font[i].glyphPen() # create a new glyph pen
for j, part in enumerate(new_mpoly): # draw a glyph part for each feature part
coords = scaled_xy(*part.exterior.coords[0], *bnds) # get glyph coordinates for feature point
pen.moveTo(coords) # move the pen to the starting location
for coords in part.exterior.coords[1:]:
pen.lineTo(scaled_xy(*coords, *bnds)) # move the pen, drawing a line
pen.closePath() # finally, close the glyph part
pen = None # delete the pen
font.generate(font_file) # generate the font
font.save() # save the font
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment