mapgen.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import sys, getopt
  4. import os, glob, errno
  5. import math, random
  6. from PIL import Image, ImageDraw
  7. import svgwrite
  8. import datetime
  9. from sympy.geometry import Circle, Point
  10. # __ ___ _
  11. # / |/ /___ _(_)___
  12. # / /|_/ / __ `/ / __ \
  13. # / / / / /_/ / / / / /
  14. # /_/ /_/\__,_/_/_/ /_/
  15. def main(argv):
  16. # Options
  17. export_svg = False
  18. export_bmp = False
  19. try:
  20. opts, args = getopt.getopt(argv,"n:",["number=", "svg", "bmp"])
  21. print(opts)
  22. except getopt.GetoptError as err:
  23. print('mapgen.py [-n, --number][--svg][--bmp]')
  24. print(str(err))
  25. sys.exit(2)
  26. for opt, arg in opts:
  27. if opt == '-h':
  28. print('mapgen.py [-n, --number]')
  29. sys.exit()
  30. elif opt in ("-n", "--number"):
  31. number = int(arg)
  32. elif opt in ("--svg"):
  33. export_svg = True
  34. elif opt in ("--bmp"):
  35. export_bmp = True
  36. if not export_svg and not export_bmp:
  37. print('please explicitly provide --svg and/or --bmp option')
  38. sys.exit()
  39. # sys.exit()
  40. # create maps export folder
  41. base = "maps"
  42. now = datetime.datetime.now()
  43. now = now.strftime("%Y-%m-%d_%X")
  44. print(now)
  45. directory = base + "/" + now
  46. try:
  47. os.makedirs(directory)
  48. except OSError as exception:
  49. if exception.errno != errno.EEXIST:
  50. raise
  51. for i in range(0, number):
  52. index = str(i) if i > 9 else '0'+str(i)
  53. print(index)
  54. generateMap(index, directory, export_svg, export_bmp)
  55. # __ ___
  56. # / |/ /___ _____
  57. # / /|_/ / __ `/ __ \
  58. # / / / / /_/ / /_/ /
  59. # /_/ /_/\__,_/ .___/
  60. # /_/
  61. def generateMap(index, directory, svg, bmp):
  62. nv = random.randint(5,15)
  63. points = generatePolygon( ctrX=500, ctrY=500, aveRadius=300, irregularity=0.7, spikeyness=0.3, numVerts=nv )
  64. # print(points)
  65. lines = fractalize(points)
  66. # print(lines)
  67. if svg:
  68. generateSVG(lines, directory, index)
  69. if bmp:
  70. generateBmp(lines, directory, index)
  71. # ____ __
  72. # / __ \____ / /_ ______ _____ ____
  73. # / /_/ / __ \/ / / / / __ `/ __ \/ __ \
  74. # / ____/ /_/ / / /_/ / /_/ / /_/ / / / /
  75. # /_/ \____/_/\__, /\__, /\____/_/ /_/
  76. # /____//____/
  77. # http://stackoverflow.com/questions/8997099/algorithm-to-generate-random-2d-polygon
  78. def generatePolygon( ctrX, ctrY, aveRadius, irregularity, spikeyness, numVerts ) :
  79. '''Start with the centre of the polygon at ctrX, ctrY,
  80. then creates the polygon by sampling points on a circle around the centre.
  81. Randon noise is added by varying the angular spacing between sequential points,
  82. and by varying the radial distance of each point from the centre.
  83. Params:
  84. ctrX, ctrY - coordinates of the "centre" of the polygon
  85. aveRadius - in px, the average radius of this polygon, this roughly controls how large the polygon is, really only useful for order of magnitude.
  86. irregularity - [0,1] indicating how much variance there is in the angular spacing of vertices. [0,1] will map to [0, 2pi/numberOfVerts]
  87. spikeyness - [0,1] indicating how much variance there is in each vertex from the circle of radius aveRadius. [0,1] will map to [0, aveRadius]
  88. numVerts - self-explanatory
  89. Returns a list of vertices, in CCW order.
  90. '''
  91. irregularity = clip( irregularity, 0,1 ) * 2*math.pi / numVerts
  92. spikeyness = clip( spikeyness, 0,1 ) * aveRadius
  93. # generate n angle steps
  94. angleSteps = []
  95. lower = (2*math.pi / numVerts) - irregularity
  96. upper = (2*math.pi / numVerts) + irregularity
  97. sum = 0
  98. for i in range(numVerts) :
  99. tmp = random.uniform(lower, upper)
  100. angleSteps.append( tmp )
  101. sum = sum + tmp
  102. # normalize the steps so that point 0 and point n+1 are the same
  103. k = sum / (2*math.pi)
  104. for i in range(numVerts) :
  105. angleSteps[i] = angleSteps[i] / k
  106. # now generate the points
  107. points = []
  108. angle = random.uniform(0, 2*math.pi)
  109. for i in range(numVerts) :
  110. r_i = clip( random.gauss(aveRadius, spikeyness), 0, 2*aveRadius )
  111. x = ctrX + r_i*math.cos(angle)
  112. y = ctrY + r_i*math.sin(angle)
  113. points.append( (int(x),int(y)) )
  114. angle = angle + angleSteps[i]
  115. return points
  116. def clip(x, min, max) :
  117. if( min > max ) : return x
  118. elif( x < min ) : return min
  119. elif( x > max ) : return max
  120. else : return x
  121. # ______ __ ___
  122. # / ____/________ ______/ /_____ _/ (_)___ ___
  123. # / /_ / ___/ __ `/ ___/ __/ __ `/ / /_ / / _ \
  124. # / __/ / / / /_/ / /__/ /_/ /_/ / / / / /_/ __/
  125. # /_/ /_/ \__,_/\___/\__/\__,_/_/_/ /___/\___/
  126. def fractalize(points) :
  127. # print("Fractalize")
  128. # print(points)
  129. lines = []
  130. cote_fract = random.randint(1,10)
  131. # loop through points 2 by 2 to obtain lines
  132. # line can be fractalized or not
  133. for p in range(0, len(points)):
  134. p1 = points[p]
  135. p2 = points[0] if p >= len(points)-1 else points[p+1];
  136. line_pts = [p1,p2]
  137. # print(line_pts)
  138. # fractalize the line
  139. if random.randint(1,10) > cote_fract:
  140. # number of fracatllization
  141. for i in range(10):
  142. # loop throug points to divide then
  143. fpts = []
  144. for v in range(0, len(line_pts)-1):
  145. fp1 = line_pts[v]
  146. fpts.append(fp1)
  147. fp2 = line_pts[v+1];
  148. d = distance(fp1,fp2)
  149. # print(d)
  150. # r = d*0.52
  151. fract_intesity = random.randint(501,800)* 0.001
  152. r = d*fract_intesity
  153. fps1,fps2 = circle_intersection((fp1[0],fp1[1],r), (fp2[0],fp2[1],r))
  154. # print(fps1)
  155. # print(ps2)
  156. if random.randint(1,2) == 2:
  157. fpts.append(fps2)
  158. else:
  159. fpts.append(fps1)
  160. # add the last point
  161. fpts.append(fp2)
  162. line_pts = fpts
  163. # add the line, fractalized or not
  164. lines.append(line_pts)
  165. return lines
  166. # Distance function
  167. def distance(p1,p2):
  168. sq1 = (p1[0]-p2[0])*(p1[0]-p2[0])
  169. sq2 = (p1[1]-p2[1])*(p1[1]-p2[1])
  170. return math.sqrt(sq1 + sq2)
  171. def circle_intersection(circle1, circle2):
  172. '''
  173. @summary: calculates intersection points of two circles
  174. @param circle1: tuple(x,y,radius)
  175. @param circle2: tuple(x,y,radius)
  176. @result: tuple of intersection points (which are (x,y) tuple)
  177. '''
  178. # return self.circle_intersection_sympy(circle1,circle2)
  179. x1,y1,r1 = circle1
  180. x2,y2,r2 = circle2
  181. # http://stackoverflow.com/a/3349134/798588
  182. dx,dy = x2-x1,y2-y1
  183. d = math.sqrt(dx*dx+dy*dy)
  184. if d > r1+r2:
  185. print("#1")
  186. return None # no solutions, the circles are separate
  187. if d < abs(r1-r2):
  188. print("#2")
  189. return None # no solutions because one circle is contained within the other
  190. if d == 0 and r1 == r2:
  191. print("#3")
  192. return None # circles are coincident and there are an infinite number of solutions
  193. a = (r1*r1-r2*r2+d*d)/(2*d)
  194. h = math.sqrt(r1*r1-a*a)
  195. xm = x1 + a*dx/d
  196. ym = y1 + a*dy/d
  197. xs1 = xm + h*dy/d
  198. xs2 = xm - h*dy/d
  199. ys1 = ym - h*dx/d
  200. ys2 = ym + h*dx/d
  201. return (xs1,ys1),(xs2,ys2)
  202. # ______ ________
  203. # / ___/ | / / ____/
  204. # \__ \| | / / / __
  205. # ___/ /| |/ / /_/ /
  206. # /____/ |___/\____/
  207. def generateSVG(lines, directory, index):
  208. svg = svgwrite.Drawing(filename = directory+"/map-"+index+".svg",size = ("1000px", "1000px"))
  209. # polygone (white background)
  210. polygone = []
  211. for l in range(0, len(lines)):
  212. for p in range(0, len(lines[l])):
  213. polygone.append(lines[l][p])
  214. bgline = svg.polyline(polygone,
  215. stroke = "white",
  216. stroke_width = "30",
  217. stroke_linejoin= "round",
  218. stroke_linecap = "round",
  219. fill = "white")
  220. svg.add(bgline)
  221. # strokes
  222. for l in range(0, len(lines)):
  223. # change randomly stroke attributes
  224. if random.randint(0,10) > 8: # and len(lines[L]) < 3:
  225. sw = "1"
  226. sda = "4 4"
  227. sdo = "5"
  228. else:
  229. sw = "1"
  230. sda = "0 0"
  231. sdo = "0"
  232. line = svg.polyline(lines[l],
  233. stroke = "black",
  234. stroke_width = sw,
  235. stroke_linejoin= "round",
  236. stroke_linecap = "round",
  237. stroke_dasharray = sda,
  238. stroke_dashoffset = sdo,
  239. fill = "none")
  240. svg.add(line)
  241. svg.save()
  242. # __ _ __
  243. # / /_ (_) /_____ ___ ____ _____
  244. # / __ \/ / __/ __ `__ \/ __ `/ __ \
  245. # / /_/ / / /_/ / / / / / /_/ / /_/ /
  246. # /_.___/_/\__/_/ /_/ /_/\__,_/ .___/
  247. # /_/
  248. def generateBmp(lines, directory, index):
  249. black = (0,0,0)
  250. white=(255,255,255)
  251. im = Image.new('RGB', (1000, 1000), white)
  252. imPxAccess = im.load()
  253. draw = ImageDraw.Draw(im)
  254. # tupVerts = list(map(tuple,verts))
  255. polygone = []
  256. for l in range(0, len(lines)):
  257. for p in range(0, len(lines[l])):
  258. polygone.append(lines[l][p])
  259. # either use .polygon(), if you want to fill the area with a solid colour
  260. # draw.polygon( polygone, outline=black,fill=white )
  261. # or .line() if you want to control the line thickness, or use both methods together!
  262. draw.line( polygone+[polygone[0]], width=1, fill=black )
  263. # im.show()
  264. im.save(directory+'/map-'+str(index)+'.bmp')
  265. # now you can save the image (im), or do whatever else you want with it.
  266. if __name__ == "__main__":
  267. main(sys.argv[1:])