GB 16689 | Regional flag of Hong Kong special administrative region

import math
# ============================================================
# Geometry utilities
# ============================================================
def circle_circle_intersection(c1, r1, c2, r2):
"""Return the intersection points of two circles."""
dx = c2[0] - c1[0]; dy = c2[1] - c1[1]
d = math.hypot(dx, dy)
if d > r1 + r2 or d < abs(r1 - r2) or d == 0:
return []
a = (r1*r1 - r2*r2 + d*d) / (2*d)
h_sq = r1*r1 - a*a
if h_sq < 0:
return []
h = math.sqrt(h_sq)
x2 = c1[0] + a*dx/d; y2 = c1[1] + a*dy/d
rx = -dy*(h/d); ry = dx*(h/d)
return [(x2 + rx, y2 + ry), (x2 - rx, y2 - ry)]
def line_circle_intersection(p1, p2, c, r):
"""Return intersection points of segment p1-p2 with circle (c, r)."""
dx = p2[0]-p1[0]; dy = p2[1]-p1[1]
fx = p1[0]-c[0]; fy = p1[1]-c[1]
a = dx*dx + dy*dy
if a == 0:
return []
b = 2*(fx*dx + fy*dy)
cv = fx*fx + fy*fy - r*r
disc = b*b - 4*a*cv
if disc < 0:
return []
disc = math.sqrt(disc)
return [(p1[0]+t*dx, p1[1]+t*dy)
for t in ((-b-disc)/(2*a), (-b+disc)/(2*a)) if 0 <= t <= 1]
def line_line_intersection(p1, p2, p3, p4, check_bounds=False):
"""Intersection of lines p1-p2 and p3-p4; optionally check segment bounds."""
x1, y1 = p1; x2, y2 = p2; x3, y3 = p3; x4, y4 = p4
denom = (x1-x2)*(y3-y4) - (y1-y2)*(x3-x4)
if abs(denom) < 1e-12:
return None
px = ((x1*y2 - y1*x2)*(x3-x4) - (x1-x2)*(x3*y4 - y3*x4)) / denom
py = ((x1*y2 - y1*x2)*(y3-y4) - (y1-y2)*(x3*y4 - y3*x4)) / denom
if check_bounds:
ok = lambda a, b, c: min(a, b)-1e-9 <= c <= max(a, b)+1e-9
if not (ok(x1, x2, px) and ok(y1, y2, py)
and ok(x3, x4, px) and ok(y3, y4, py)):
return None
return (px, py)
def line_vertical_intersection(x, p1, p2):
"""Intersection of vertical line x=const with segment p1-p2."""
if abs(p1[0]-p2[0]) < 1e-12:
return None
t = (x - p1[0]) / (p2[0] - p1[0])
if 0 <= t <= 1:
return (x, p1[1] + t*(p2[1]-p1[1]))
return None
def angle_of(center, pt):
return math.atan2(pt[1]-center[1], pt[0]-center[0])
def point_on_arc(pt, center, ps, pe, ccw):
"""Return True if pt lies on the arc from ps to pe around center."""
a1 = angle_of(center, ps); a2 = angle_of(center, pe); a = angle_of(center, pt)
if ccw:
while a2 <= a1: a2 += 2*math.pi
while a < a1: a += 2*math.pi
while a > a2: a -= 2*math.pi
return a1 <= a <= a2
else:
while a2 >= a1: a2 -= 2*math.pi
while a > a1: a -= 2*math.pi
while a < a2: a += 2*math.pi
return a2 <= a <= a1
def arc_bezier(center, radius, ps, pe, ccw):
"""Approximate an arc with cubic Bezier segments (each <= 90 degrees).
Returns a list of segments, each as (P0, P1, P2, P3).
Control-point offset uses the standard k = 4/3 * tan(theta/4).
"""
a1 = angle_of(center, ps); a2 = angle_of(center, pe)
if ccw:
while a2 <= a1 + 1e-15: a2 += 2*math.pi
else:
while a2 >= a1 - 1e-15: a2 -= 2*math.pi
sweep = a2 - a1
n = max(1, int(math.ceil(abs(sweep) / (math.pi/2))))
step = sweep / n
segs = []
for i in range(n):
s1 = a1 + i*step
s2 = s1 + step
k = 4.0/3.0 * math.tan(step/4.0)
p0 = (center[0] + radius*math.cos(s1), center[1] + radius*math.sin(s1))
p3 = (center[0] + radius*math.cos(s2), center[1] + radius*math.sin(s2))
t1 = (-math.sin(s1), math.cos(s1))
t2 = (-math.sin(s2), math.cos(s2))
p1 = (p0[0] + radius*k*t1[0], p0[1] + radius*k*t1[1])
p2 = (p3[0] - radius*k*t2[0], p3[1] - radius*k*t2[1])
segs.append((p0, p1, p2, p3))
return segs
def arc_mid_y(center, radius, ps, pe, ccw):
"""Y-coordinate of the arc midpoint (used to pick the lower C5 arc)."""
a1 = angle_of(center, ps); a2 = angle_of(center, pe)
if ccw:
while a2 <= a1 + 1e-15: a2 += 2*math.pi
else:
while a2 >= a1 - 1e-15: a2 -= 2*math.pi
mid = (a1 + a2)/2
return center[1] + radius*math.sin(mid)
def rotate_pts(pts, ang):
ca, sa = math.cos(ang), math.sin(ang)
return [(x*ca - y*sa, x*sa + y*ca) for x, y in pts]
def translate_segs(segs, dx, dy):
return [((a[0]+dx, a[1]+dy), (b[0]+dx, b[1]+dy),
(c[0]+dx, c[1]+dy), (d[0]+dx, d[1]+dy))
for a, b, c, d in segs]
def rotate_segs(segs, ang):
ca, sa = math.cos(ang), math.sin(ang)
R = lambda p: (p[0]*ca - p[1]*sa, p[0]*sa + p[1]*ca)
return [(R(a), R(b), R(c), R(d)) for a, b, c, d in segs]
# ============================================================
# 1. Base geometry
# ============================================================
R1 = 60.0
C1 = (0.0, 0.0)
C_pt = (0.0, 60.0) # top point of circle C1
H = (R1*math.cos(math.radians( 18)), R1*math.sin(math.radians( 18)))
E = (R1*math.cos(math.radians(-54)), R1*math.sin(math.radians(-54)))
I = (R1*math.cos(math.radians(-126)), R1*math.sin(math.radians(-126)))
K = (R1*math.cos(math.radians( 162)), R1*math.sin(math.radians( 162)))
# J = intersection of segments CE and KH (inner vertex of the pentagram)
J = line_line_intersection(C_pt, E, K, H, check_bounds=True)
assert J is not None, "Cannot compute point J"
# Circle C2 with diameter A-C1
C2 = (-30.0, 0.0); R2 = 30.0
F = line_circle_intersection(C2, C_pt, C2, R2)[0]
G = line_circle_intersection(C2, E, C2, R2)[0]
# Circle C3 with diameter C-F
C3 = ((C_pt[0]+F[0])/2, (C_pt[1]+F[1])/2)
R3 = math.hypot(C_pt[0]-F[0], C_pt[1]-F[1]) / 2
P = next(pt for pt in circle_circle_intersection(C1, R1, C3, R3)
if math.hypot(pt[0]-C_pt[0], pt[1]-C_pt[1]) > 1e-9)
# Circle C4 with diameter E-G
C4 = ((E[0]+G[0])/2, (E[1]+G[1])/2)
R4 = math.hypot(E[0]-G[0], E[1]-G[1]) / 2
C7 = next(pt for pt in circle_circle_intersection(C1, R1, C4, R4)
if math.hypot(pt[0]-E[0], pt[1]-E[1]) > 1e-9)
# Circle C5
C5 = C2; R5 = 10.5
# Point C6: intersection of vertical line through J with segment HI
C6 = line_vertical_intersection(J[0], H, I)
assert C6 is not None, "Cannot compute point C6"
R_in = math.hypot(C6[0]-C2[0], C6[1]-C2[1]) # inner circle radius
R_out = R_in + R1 / 60.0 # ring width
# Lower intersection points of circle C5 with the inner / outer circles
lower = lambda pts: min(pts, key=lambda p: p[1]) if pts else None
S_in = lower(circle_circle_intersection(C5, R5, C6, R_in))
S_out = lower(circle_circle_intersection(C5, R5, C6, R_out))
assert S_in and S_out, "Cannot compute S_in / S_out"
# U: intersection of inner circle with circle C4, on arc G -> C7 (CCW)
U = None
for pt in circle_circle_intersection(C6, R_in, C4, R4):
if point_on_arc(pt, C4, G, C7, ccw=True):
U = pt; break
assert U is not None, "Cannot compute point U"
# Q: first CCW intersection of outer circle with circle C1 starting from S_out
cand = circle_circle_intersection(C6, R_out, C1, R1)
assert cand, "Outer circle does not intersect circle C1"
a_s = angle_of(C6, S_out)
Q, best = None, 2*math.pi
for pt in cand:
d = (angle_of(C6, pt) - a_s) % (2*math.pi)
if d < best:
best = d; Q = pt
assert Q is not None
# Pick the C5 arc that lies below (smaller average y)
c5_ccw = (arc_mid_y(C5, R5, S_out, S_in, True)
< arc_mid_y(C5, R5, S_out, S_in, False))
# ============================================================
# 2. Build the outline (arcs in order)
# ============================================================
outline_arcs = [
(C1, R1, P, Q, True ), # circle C1: P -> Q, CCW
(C6, R_out, Q, S_out, False), # outer circle: Q -> S_out, CW
(C5, R5, S_out, S_in, c5_ccw), # circle C5: lower short arc
(C6, R_in, S_in, U, True ), # inner circle: S_in -> U, CCW
(C4, R4, U, G, False), # circle C4: U -> G, CW
(C2, R2, G, F, True ), # circle C2: G -> F, CCW
(C3, R3, F, P, False), # circle C3: F -> P, CW
]
segs = []
for center, radius, a, b, ccw in outline_arcs:
segs.extend(arc_bezier(center, radius, a, b, ccw))
# ============================================================
# 3. Pentagram: only 5 outer vertices in star order
# ============================================================
star_outer = [(C5[0] + R5*math.cos(math.radians(a)),
C5[1] + R5*math.sin(math.radians(a)))
for a in (0, 72, 144, 216, 288)]
# Star order: 0 -> 2 -> 4 -> 1 -> 3 -> 0
star5 = [star_outer[0], star_outer[2], star_outer[4],
star_outer[1], star_outer[3]]
# ============================================================
# 4. Translate C7 to origin, rotate so P sits at N14E
# ============================================================
dx, dy = -C7[0], -C7[1]
segs_t = translate_segs(segs, dx, dy)
star_t = [(x+dx, y+dy) for x, y in star5]
P_shift = (P[0]+dx, P[1]+dy)
# "North 14 degrees East" -> polar angle 90 - 14 = 76 degrees
rot_a = math.radians(90 - 14) - math.atan2(P_shift[1], P_shift[0])
segs_r = rotate_segs(segs_t, rot_a)
star_r = rotate_pts(star_t, rot_a)
# ============================================================
# 5. Five rotated copies (72 degrees apart)
# ============================================================
copies_segs, copies_star = [], []
for i in range(5):
ang = math.radians(i * 72)
copies_segs.append(rotate_segs(segs_r, ang))
copies_star.append(rotate_pts(star_r, ang))
# ============================================================
# 6. Red background rectangle (no extra padding)
# ============================================================
r_c7 = math.hypot(P[0]-C7[0], P[1]-C7[1])
rect_w = 2.5 * (2 * r_c7) # width = 2.5 * diameter of circle C7
rect_h = rect_w * 2.0 / 3.0 # height = 2/3 of width
half_w, half_h = rect_w / 2.0, rect_h / 2.0
view_x = -half_w
view_y = -half_h # inner coords are y-up; scale(1,-1) flips at display
view_w = rect_w
view_h = rect_h
# ============================================================
# 7. SVG path builders
# ============================================================
FMT = ".15g" # 15 significant digits
def segs_to_path(segs):
"""Bezier segments -> closed SVG path string."""
if not segs:
return ""
d = f"M{segs[0][0][0]:{FMT}},{segs[0][0][1]:{FMT}}"
for p0, p1, p2, p3 in segs:
d += (f"C{p1[0]:{FMT}},{p1[1]:{FMT}}"
f" {p2[0]:{FMT}},{p2[1]:{FMT}}"
f" {p3[0]:{FMT}},{p3[1]:{FMT}}")
return d + "Z"
def poly_to_path(pts):
"""Polygon vertices -> closed SVG path string."""
d = f"M{pts[0][0]:{FMT}},{pts[0][1]:{FMT}}"
for p in pts[1:]:
d += f"L{p[0]:{FMT}},{p[1]:{FMT}}"
return d + "Z"
# ============================================================
# 8. Write SVG
# ============================================================
out = []
out.append('<?xml version="1.0" encoding="UTF-8"?>')
out.append(
f'<svg xmlns="http://www.w3.org/2000/svg" '
f'viewBox="{view_x:{FMT}} {view_y:{FMT}} {view_w:{FMT}} {view_h:{FMT}}" '
f'preserveAspectRatio="xMidYMid meet" '
f'style="display:block;position:absolute;top:0;left:0;'
f'width:100vw;height:100vh;margin:0;padding:0;background:#fff">'
)
# Inner coordinates are y-up; scale(1,-1) flips for display
out.append('')
# Red background rectangle
out.append(
f'<rect x="{-half_w:{FMT}}" y="{-half_h:{FMT}}" '
f'width="{rect_w:{FMT}}" height="{rect_h:{FMT}}" '
f'fill="#ee1c25" stroke="none"/>'
)
# Five white hollow figures
for s in copies_segs:
out.append(f'')
# Pentagrams: 5 points only. With the default nonzero fill rule, the
# self-intersecting pentagon fills the whole star in one pass.
for poly in copies_star:
out.append(f'')
out.append('</g>')
out.append('</svg>')
with open("output.svg", "w", encoding="utf-8") as f:
f.write("\n".join(out))
print("Wrote output.svg")
print(f"r(C7) = {r_c7:.15g}")
print(f"rect = {rect_w:.15g} x {rect_h:.15g}")
print(f"viewBox = {view_x:.15g} {view_y:.15g} {view_w:.15g} {view_h:.15g}")

Loading Sorry, something went wrong. Reload? Sorry, we cannot display this file. Sorry, this file is invalid so it cannot be displayed.

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论