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.
评论
?
参与讨论