• 【科技与狠活】如何利用Python绘制足球场


     

    卡塔尔世界杯赛程近半,朋友圈都在晒中奖的体育彩票,而我在搬砖🧱。

    今天我将介绍如何使用Python Matplotlib创建一个足球场,本文设计球场尺寸为105×68。

    首先导入所需的依赖包:

    1. import pandas as pd
    2. import numpy as np
    3. import matplotlib.pyplot as plt
    4. from matplotlib.patches import Arc

    其实看似复杂的足球场一旦将其分解成各个组成部分,就不是什么特别复杂的事情了。足球场只是线条和形状的集合,下面我将一步步实现其包含的各个元素并在最后将它们全部封装在一个函数中。

    首先,设置一些会频繁用到的全局变量。

    1. # 足球场的边界长宽
    2. x_min = 0
    3. x_max = 105
    4. y_min = 0
    5. y_max = 68
    6. line_color = "grey"
    7. line_thickness = 1.5
    8. background = "w" # w表示white
    9. point_size = 20
    10. arc_angle = 0
    11. first = 0
    12. second = 1

    接下来创建列表pitch_x、pitch_y标记球场上各个元素的坐标。

    1. pitch_x = [0,5.8,11.5,17,50,83,88.5,94.2,100] # pitch x markings
    2. """
    3. [goal line, six yard box, penalty spot, edge of box,
    4. halfway line, edge of box, penalty spot, six yard box, goal line]
    5. """
    6. pitch_y = [0, 21.1, 36.6, 50, 63.2, 78.9, 100]
    7. """
    8. [sideline, edge of box, six yard box, centre of pitch,
    9. six yard box, edge of box, sideline ]
    10. """
    11. goal_y = [45.2, 54.8] # goal posts

    上述定义的x、y为100x100, 需要对球场进行同比例转换为105x68,当然球场元素坐标也会同比例变化:

    1. x_conversion = 105 / 100
    2. y_conversion = 68 / 100
    3. pitch_x = [item * x_conversion for item in pitch_x]
    4. pitch_y = [item * y_conversion for item in pitch_y]
    5. goal_y = [item * y_conversion for item in goal_y]

    现在已经对元素的点坐标进行了转换,下面就可以通过 x 和 y 坐标列表来绘制足球场的各个元素了。例如,这是如何绘制球场边线的代码:

    1. lx1 = [x_min, x_max, x_max, x_min, x_min]
    2. ly1 = [y_min, y_min, y_max, y_max, y_min]
    3. fig, ax = plt.subplots(figsize=(11,7))
    4. ax.axis("off")
    5. ax.plot(lx1,ly1, color=line_color, lw=line_thickness, zorder=-1)
    6. plt.tight_layout()
    7. plt.show()

    c7a98a47714d3c362d9e3adf4517f7ff.png

    下面可以遍历每个元素的标记并将它们绘制成对应的球场元素。

    1. # side and goal lines
    2. lx1 = [x_min, x_max, x_max, x_min, x_min]
    3. ly1 = [y_min, y_min, y_max, y_max, y_min]
    4. ax.plot(lx1, ly1, color=line_color, lw=line_thickness, zorder=-1)
    5. # outer boxed
    6. lx2 = [x_max, pitch_x[5], pitch_x[5], x_max]
    7. ly2 = [pitch_y[1], pitch_y[1], pitch_y[5], pitch_y[5]]
    8. ax.plot(lx2, ly2, color=line_color, lw=line_thickness, zorder=-1)
    9. lx3 = [0, pitch_x[3], pitch_x[3], 0]
    10. ly3 = [pitch_y[1], pitch_y[1], pitch_y[5], pitch_y[5]]
    11. ax.plot(lx3, ly3, color=line_color, lw=line_thickness, zorder=-1)
    12. # .....

    当然这不是最好的方法,因为这意味着如果我想绘制垂直方向足球场,我需要调整每一项数据。与其一一绘制,不如构建坐标列表并将它们添加到主列表中:

    1. # side and goal lines
    2. lx1 = [x_min, x_max, x_max, x_min, x_min]
    3. ly1 = [y_min, y_min, y_max, y_max, y_min]
    4. # outer boxed
    5. lx2 = [x_max, pitch_x[5], pitch_x[5], x_max]
    6. ly2 = [pitch_y[1], pitch_y[1], pitch_y[5], pitch_y[5]]
    7. lx3 = [0, pitch_x[3], pitch_x[3], 0]
    8. ly3 = [pitch_y[1], pitch_y[1], pitch_y[5], pitch_y[5]]
    9. # goals
    10. lx4 = [x_max, x_max+2, x_max+2, x_max]
    11. ly4 = [goal_y[0], goal_y[0], goal_y[1], goal_y[1]]
    12. lx5 = [0, -2, -2, 0]
    13. ly5 = [goal_y[0], goal_y[0], goal_y[1], goal_y[1]]
    14. # 6 yard boxes
    15. lx6 = [x_max, pitch_x[7], pitch_x[7], x_max]
    16. ly6 = [pitch_y[2], pitch_y[2], pitch_y[4], pitch_y[4]]
    17. lx7 = [0, pitch_x[1], pitch_x[1], 0]
    18. ly7 = [pitch_y[2], pitch_y[2], pitch_y[4], pitch_y[4]]
    19. # Halfway line, penalty spots, and kickoff spot
    20. lx8 = [pitch_x[4], pitch_x[4]]
    21. ly8 = [0, y_max]
    22. lines = [
    23. [lx1, ly1],
    24. [lx2, ly2],
    25. [lx3, ly3],
    26. [lx4, ly4],
    27. [lx5, ly5],
    28. [lx6, ly6],
    29. [lx7, ly7],
    30. [lx8, ly8],
    31. ]

    通过循环遍历lines列表来绘制上面的图:

    1. fig, ax = plt.subplots(figsize=(11,7))
    2. ax.axis("off")
    3. for line in lines:
    4. ax.plot(line[0], line[1],
    5. color=line_color,
    6. lw=line_thickness,
    7. zorder=-1)
    8. plt.tight_layout()
    9. plt.show()

    2dc85617762e170dc8fdc2653b72d33c.png

    这样绘图代码看起来更清晰,以同样的方式绘制开球点和罚球点:

    1. points = [
    2. [pitch_x[6], pitch_y[3]],
    3. [pitch_x[2], pitch_y[3]],
    4. [pitch_x[4], pitch_y[3]]
    5. ]

    然后遍历绘图代码中的点:

    1. fig, ax = plt.subplots(figsize=(11,7))
    2. ax.axis("off")
    3. for line in lines:
    4. ax.plot(line[0], line[1],
    5. color=line_color,
    6. lw=line_thickness,
    7. zorder=-1)
    8. for point in points:
    9. ax.scatter(point[0], point[1],
    10. color=line_color,
    11. s=point_size,
    12. zorder=-1)
    13. plt.tight_layout()
    14. plt.show()

    0b127daacc9707da62d6dcec68715e24.png

    在每个"box"上添加中心圆。

    1. circle_points = [pitch_x[4], pitch_y[3]]
    2. arc_points1 = [pitch_x[6], pitch_y[3]]
    3. arc_points2 = [pitch_x[2], pitch_y[3]]
    4. fig, ax = plt.subplots(figsize=(11,7))
    5. ax.axis("off")
    6. for line in lines:
    7. ax.plot(line[0], line[1],
    8. color=line_color,
    9. lw=line_thickness,
    10. zorder=-1)
    11. for point in points:
    12. ax.scatter(point[0], point[1],
    13. color=line_color,
    14. s=point_size,
    15. zorder=-1)
    16. circle = plt.Circle((circle_points[0], circle_points[1]),
    17. x_max * 0.088,
    18. lw=line_thickness,
    19. color=line_color,
    20. fill=False,
    21. zorder=-1)
    22. ax.add_artist(circle)
    23. arc1 = Arc((arc_points1[0], arc_points1[1]),
    24. height=x_max * 0.088 * 2,
    25. width=x_max * 0.088 * 2,
    26. angle=arc_angle,
    27. theta1=128.75,
    28. theta2=231.25,
    29. color=line_color,
    30. lw=line_thickness,
    31. zorder=-1)
    32. ax.add_artist(arc1)
    33. arc2 = Arc((arc_points2[0], arc_points2[1]),
    34. height=x_max * 0.088 * 2,
    35. width=x_max * 0.088 * 2,
    36. angle=arc_angle,
    37. theta1=308.75,
    38. theta2=51.25,
    39. color=line_color,
    40. lw=line_thickness,
    41. zorder=-1)
    42. ax.add_artist(arc2)
    43. ax.set_aspect("equal")
    44. plt.tight_layout()
    45. plt.show()

    737562edc08e1311c7b23901deed51bb.png

    这就是创建足球场的整个过程!然而,这还没有结束。我添加了几个变量并调整了代码以绘制逻辑更加灵活。

    1. fig, ax = plt.subplots(figsize=(11,7))
    2. ax.axis("off")
    3. for line in lines:
    4. ax.plot(line[first], line[second],
    5. color=line_color,
    6. lw=line_thickness,
    7. zorder=-1)
    8. for point in points:
    9. ax.scatter(point[first], point[second],
    10. color=line_color,
    11. s=point_size,
    12. zorder=-1)
    13. circle = plt.Circle((circle_points[first], circle_points[second]),
    14. x_max * 0.088,
    15. lw=line_thickness,
    16. color=line_color,
    17. fill=False,
    18. zorder=-1)
    19. ax.add_artist(circle)
    20. arc1 = Arc((arc_points1[first], arc_points1[second]),
    21. height=x_max * 0.088 * 2,
    22. width=x_max * 0.088 * 2,
    23. angle=arc_angle,
    24. theta1=128.75,
    25. theta2=231.25,
    26. color=line_color,
    27. lw=line_thickness,
    28. zorder=-1)
    29. ax.add_artist(arc1)
    30. arc2 = Arc((arc_points2[first], arc_points2[second]),
    31. height=x_max * 0.088 * 2,
    32. width=x_max * 0.088 * 2,
    33. angle=arc_angle,
    34. theta1=308.75,
    35. theta2=51.25,
    36. color=line_color,
    37. lw=line_thickness,
    38. zorder=-1)
    39. ax.add_artist(arc2)
    40. ax.set_aspect("equal")
    41. plt.tight_layout()
    42. plt.show()

    还记得文章开头设置的几个变量吧:arc_angle = 0、first = 0 和 second = 1。通过将这些设置为变量而不是硬编码,我们可以修改这几个变量为first = 1、second = 0和arc_angle = 90,这样就可以绘制一个垂直方向的足球场了。

    1. first = 1
    2. second = 0
    3. arc_angle = 90
    4. fig, ax = plt.subplots(figsize=(7,11))
    5. ax.axis("off")
    6. for line in lines:
    7. ax.plot(line[first], line[second],
    8. color=line_color,
    9. lw=line_thickness,
    10. zorder=-1)
    11. for point in points:
    12. ax.scatter(point[first], point[second],
    13. color=line_color,
    14. s=point_size,
    15. zorder=-1)
    16. circle = plt.Circle((circle_points[first], circle_points[second]),
    17. x_max * 0.088,
    18. lw=line_thickness,
    19. color=line_color,
    20. fill=False,
    21. zorder=-1)
    22. ax.add_artist(circle)
    23. arc1 = Arc((arc_points1[first], arc_points1[second]),
    24. height=x_max * 0.088 * 2,
    25. width=x_max * 0.088 * 2,
    26. angle=arc_angle,
    27. theta1=128.75,
    28. theta2=231.25,
    29. color=line_color,
    30. lw=line_thickness,
    31. zorder=-1)
    32. ax.add_artist(arc1)
    33. arc2 = Arc((arc_points2[first], arc_points2[second]),
    34. height=x_max * 0.088 * 2,
    35. width=x_max * 0.088 * 2,
    36. angle=arc_angle,
    37. theta1=308.75,
    38. theta2=51.25,
    39. color=line_color,
    40. lw=line_thickness,
    41. zorder=-1)
    42. ax.add_artist(arc2)
    43. ax.set_aspect("equal")
    44. plt.tight_layout()
    45. plt.show()

    b2d1e5f8596e052b8c1cad6e85532c3f.png

    代码封装

    下面将上面零散的代码封装成函数。

    1. def draw_pitch(x_min=0, x_max=105,
    2. y_min=0, y_max=68,
    3. pitch_color="w",
    4. line_color="grey",
    5. line_thickness=1.5,
    6. point_size=20,
    7. orientation="horizontal",
    8. aspect="full",
    9. ax=None
    10. ):
    11. if not ax:
    12. raise TypeError("This function is intended to be used with an existing fig and ax in order to allow flexibility in plotting of various sizes and in subplots.")
    13. if orientation.lower().startswith("h"):
    14. first = 0
    15. second = 1
    16. arc_angle = 0
    17. if aspect == "half":
    18. ax.set_xlim(x_max / 2, x_max + 5)
    19. elif orientation.lower().startswith("v"):
    20. first = 1
    21. second = 0
    22. arc_angle = 90
    23. if aspect == "half":
    24. ax.set_ylim(x_max / 2, x_max + 5)
    25. else:
    26. raise NameError("You must choose one of horizontal or vertical")
    27. ax.axis("off")
    28. rect = plt.Rectangle((x_min, y_min),
    29. x_max, y_max,
    30. facecolor=pitch_color,
    31. edgecolor="none",
    32. zorder=-2)
    33. ax.add_artist(rect)
    34. x_conversion = x_max / 100
    35. y_conversion = y_max / 100
    36. pitch_x = [0,5.8,11.5,17,50,83,88.5,94.2,100] # pitch x markings
    37. pitch_x = [x * x_conversion for x in pitch_x]
    38. pitch_y = [0, 21.1, 36.6, 50, 63.2, 78.9, 100] # pitch y markings
    39. pitch_y = [x * y_conversion for x in pitch_y]
    40. goal_y = [45.2, 54.8] # goal posts
    41. goal_y = [x * y_conversion for x in goal_y]
    42. # side and goal lines
    43. lx1 = [x_min, x_max, x_max, x_min, x_min]
    44. ly1 = [y_min, y_min, y_max, y_max, y_min]
    45. # outer boxed
    46. lx2 = [x_max, pitch_x[5], pitch_x[5], x_max]
    47. ly2 = [pitch_y[1], pitch_y[1], pitch_y[5], pitch_y[5]]
    48. lx3 = [0, pitch_x[3], pitch_x[3], 0]
    49. ly3 = [pitch_y[1], pitch_y[1], pitch_y[5], pitch_y[5]]
    50. # goals
    51. lx4 = [x_max, x_max+2, x_max+2, x_max]
    52. ly4 = [goal_y[0], goal_y[0], goal_y[1], goal_y[1]]
    53. lx5 = [0, -2, -2, 0]
    54. ly5 = [goal_y[0], goal_y[0], goal_y[1], goal_y[1]]
    55. # 6 yard boxes
    56. lx6 = [x_max, pitch_x[7], pitch_x[7], x_max]
    57. ly6 = [pitch_y[2],pitch_y[2], pitch_y[4], pitch_y[4]]
    58. lx7 = [0, pitch_x[1], pitch_x[1], 0]
    59. ly7 = [pitch_y[2],pitch_y[2], pitch_y[4], pitch_y[4]]
    60. # Halfway line, penalty spots, and kickoff spot
    61. lx8 = [pitch_x[4], pitch_x[4]]
    62. ly8 = [0, y_max]
    63. lines = [
    64. [lx1, ly1],
    65. [lx2, ly2],
    66. [lx3, ly3],
    67. [lx4, ly4],
    68. [lx5, ly5],
    69. [lx6, ly6],
    70. [lx7, ly7],
    71. [lx8, ly8],
    72. ]
    73. points = [
    74. [pitch_x[6], pitch_y[3]],
    75. [pitch_x[2], pitch_y[3]],
    76. [pitch_x[4], pitch_y[3]]
    77. ]
    78. circle_points = [pitch_x[4], pitch_y[3]]
    79. arc_points1 = [pitch_x[6], pitch_y[3]]
    80. arc_points2 = [pitch_x[2], pitch_y[3]]
    81. for line in lines:
    82. ax.plot(line[first], line[second],
    83. color=line_color,
    84. lw=line_thickness,
    85. zorder=-1)
    86. for point in points:
    87. ax.scatter(point[first], point[second],
    88. color=line_color,
    89. s=point_size,
    90. zorder=-1)
    91. circle = plt.Circle((circle_points[first], circle_points[second]),
    92. x_max * 0.088,
    93. lw=line_thickness,
    94. color=line_color,
    95. fill=False,
    96. zorder=-1)
    97. ax.add_artist(circle)
    98. arc1 = Arc((arc_points1[first], arc_points1[second]),
    99. height=x_max * 0.088 * 2,
    100. width=x_max * 0.088 * 2,
    101. angle=arc_angle,
    102. theta1=128.75,
    103. theta2=231.25,
    104. color=line_color,
    105. lw=line_thickness,
    106. zorder=-1)
    107. ax.add_artist(arc1)
    108. arc2 = Arc((arc_points2[first], arc_points2[second]),
    109. height=x_max * 0.088 * 2,
    110. width=x_max * 0.088 * 2,
    111. angle=arc_angle,
    112. theta1=308.75,
    113. theta2=51.25,
    114. color=line_color,
    115. lw=line_thickness,
    116. zorder=-1)
    117. ax.add_artist(arc2)
    118. ax.set_aspect("equal")
    119. return ax

    现在只需要调用函数来绘制想要的足球场!

    1. background = "#09b309"
    2. fig, ax = plt.subplots(figsize=(11, 7))
    3. fig.set_facecolor(background)
    4. draw_pitch(orientation="h",
    5. aspect="full",
    6. pitch_color=background,
    7. line_color="lightgrey",
    8. ax=ax)
    9. plt.tight_layout()
    10. plt.show()

    db80e051ac7dcac3661c1c30082ff516.png

     

    下面这张图是绘制过程gif。

    8618f5759408690f5a5fb5e4dbd48099.gif



    代码仓库:How-To-Python: 人生苦短,我用Python

     

     

  • 相关阅读:
    npm运行vue项目出现禁止运行脚本
    3-2数据链路层-差错控制
    Day802.JVM热点问题 -Java 性能调优实战
    jira查询user详细信息
    【案例实战】SpringBoot整合Redis连接池生成图形验证码
    Visual Studio 2022开发Arduino详述
    云计算基础技术
    【Codeforces Round #811 (Div. 3)】【题目解析+AK代码】
    Rust开发——闭包使用示例
    N-HiTS: Neural Hierarchical Interpolation for Time Series Forecasting
  • 原文地址:https://blog.csdn.net/csd11311/article/details/128178369