mapgen.py 9.7 KB

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