Files

331 lines
9.7 KiB
Python
Executable File

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, getopt
import os, glob, errno
import math, random
from PIL import Image, ImageDraw
import svgwrite
import datetime
from sympy.geometry import Circle, Point
# __ ___ _
# / |/ /___ _(_)___
# / /|_/ / __ `/ / __ \
# / / / / /_/ / / / / /
# /_/ /_/\__,_/_/_/ /_/
def main(argv):
# Options
export_svg = False
export_bmp = False
try:
opts, args = getopt.getopt(argv,"n:",["number=", "svg", "bmp"])
print(opts)
except getopt.GetoptError as err:
print('mapgen.py [-n, --number][--svg][--bmp]')
print(str(err))
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print('mapgen.py [-n, --number]')
sys.exit()
elif opt in ("-n", "--number"):
number = int(arg)
elif opt in ("--svg"):
export_svg = True
elif opt in ("--bmp"):
export_bmp = True
if not export_svg and not export_bmp:
print('please explicitly provide --svg and/or --bmp option')
sys.exit()
# sys.exit()
# create maps export folder
base = "maps"
now = datetime.datetime.now()
now = now.strftime("%Y-%m-%d_%X")
print(now)
directory = base + "/" + now
try:
os.makedirs(directory)
except OSError as exception:
if exception.errno != errno.EEXIST:
raise
# generate n maps
for i in range(0, number):
index = str(i) if i > 9 else '0'+str(i)
print(index)
generateMap(index, directory, export_svg, export_bmp)
# __ ___
# / |/ /___ _____
# / /|_/ / __ `/ __ \
# / / / / /_/ / /_/ /
# /_/ /_/\__,_/ .___/
# /_/
def generateMap(index, directory, svg, bmp):
nv = random.randint(5,15)
points = generatePolygon( ctrX=500, ctrY=500, aveRadius=300, irregularity=0.7, spikeyness=0.3, numVerts=nv )
# print(points)
lines = fractalize(points)
# print(lines)
if svg:
generateSVG(lines, directory, index)
if bmp:
generateBmp(lines, directory, index)
# ____ __
# / __ \____ / /_ ______ _____ ____
# / /_/ / __ \/ / / / / __ `/ __ \/ __ \
# / ____/ /_/ / / /_/ / /_/ / /_/ / / / /
# /_/ \____/_/\__, /\__, /\____/_/ /_/
# /____//____/
# http://stackoverflow.com/questions/8997099/algorithm-to-generate-random-2d-polygon
def generatePolygon( ctrX, ctrY, aveRadius, irregularity, spikeyness, numVerts ) :
'''Start with the centre of the polygon at ctrX, ctrY,
then creates the polygon by sampling points on a circle around the centre.
Randon noise is added by varying the angular spacing between sequential points,
and by varying the radial distance of each point from the centre.
Params:
ctrX, ctrY - coordinates of the "centre" of the polygon
aveRadius - in px, the average radius of this polygon, this roughly controls how large the polygon is, really only useful for order of magnitude.
irregularity - [0,1] indicating how much variance there is in the angular spacing of vertices. [0,1] will map to [0, 2pi/numberOfVerts]
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]
numVerts - self-explanatory
Returns a list of vertices, in CCW order.
'''
irregularity = clip( irregularity, 0,1 ) * 2*math.pi / numVerts
spikeyness = clip( spikeyness, 0,1 ) * aveRadius
# generate n angle steps
angleSteps = []
lower = (2*math.pi / numVerts) - irregularity
upper = (2*math.pi / numVerts) + irregularity
sum = 0
for i in range(numVerts) :
tmp = random.uniform(lower, upper)
angleSteps.append( tmp )
sum = sum + tmp
# normalize the steps so that point 0 and point n+1 are the same
k = sum / (2*math.pi)
for i in range(numVerts) :
angleSteps[i] = angleSteps[i] / k
# now generate the points
points = []
angle = random.uniform(0, 2*math.pi)
for i in range(numVerts) :
r_i = clip( random.gauss(aveRadius, spikeyness), 0, 2*aveRadius )
x = ctrX + r_i*math.cos(angle)
y = ctrY + r_i*math.sin(angle)
points.append( (int(x),int(y)) )
angle = angle + angleSteps[i]
return points
def clip(x, min, max) :
if( min > max ) : return x
elif( x < min ) : return min
elif( x > max ) : return max
else : return x
# ______ __ ___
# / ____/________ ______/ /_____ _/ (_)___ ___
# / /_ / ___/ __ `/ ___/ __/ __ `/ / /_ / / _ \
# / __/ / / / /_/ / /__/ /_/ /_/ / / / / /_/ __/
# /_/ /_/ \__,_/\___/\__/\__,_/_/_/ /___/\___/
def fractalize(points) :
# print("Fractalize")
# print(points)
lines = []
# probality of line fractalization
proba_fract = random.randint(1,7)
# loop through points 2 by 2 to obtain lines
# line can be fractalized or not
for p in range(0, len(points)):
p1 = points[p]
p2 = points[0] if p >= len(points)-1 else points[p+1];
line_pts = [p1,p2]
# print(line_pts)
# fractalize the line or not (juste leave it as two points)
if random.randint(1,10) > proba_fract:
# fractal type
fract_types = "coast internal strait".split()
ft = random.choice(fract_types)
if ft == "coast":
fract_depth = 14
fract_intesity = random.randint(550,600)* 0.001
elif ft == "internal":
fract_depth = 9
fract_intesity = random.randint(520,550)* 0.001
elif ft == "strait":
fract_depth = 5
fract_intesity = random.randint(501,510)* 0.001
# number of fracatllization
for i in range(fract_depth):
# loop throug points to divide then
fpts = []
for v in range(0, len(line_pts)-1):
fp1 = line_pts[v]
fpts.append(fp1)
fp2 = line_pts[v+1];
d = distance(fp1,fp2)
# print(d)
# r = d*0.52
r = d*fract_intesity
fps1,fps2 = circle_intersection((fp1[0],fp1[1],r), (fp2[0],fp2[1],r))
# print(fps1)
# print(ps2)
if random.randint(1,2) == 2:
fpts.append(fps2)
else:
fpts.append(fps1)
# add the last point
fpts.append(fp2)
line_pts = fpts
# add the line, fractalized or not
lines.append(line_pts)
return lines
# Distance function
def distance(p1,p2):
sq1 = (p1[0]-p2[0])*(p1[0]-p2[0])
sq2 = (p1[1]-p2[1])*(p1[1]-p2[1])
return math.sqrt(sq1 + sq2)
def circle_intersection(circle1, circle2):
'''
@summary: calculates intersection points of two circles
@param circle1: tuple(x,y,radius)
@param circle2: tuple(x,y,radius)
@result: tuple of intersection points (which are (x,y) tuple)
'''
# return self.circle_intersection_sympy(circle1,circle2)
x1,y1,r1 = circle1
x2,y2,r2 = circle2
# http://stackoverflow.com/a/3349134/798588
dx,dy = x2-x1,y2-y1
d = math.sqrt(dx*dx+dy*dy)
if d > r1+r2:
print("#1")
return None # no solutions, the circles are separate
if d < abs(r1-r2):
print("#2")
return None # no solutions because one circle is contained within the other
if d == 0 and r1 == r2:
print("#3")
return None # circles are coincident and there are an infinite number of solutions
a = (r1*r1-r2*r2+d*d)/(2*d)
h = math.sqrt(r1*r1-a*a)
xm = x1 + a*dx/d
ym = y1 + a*dy/d
xs1 = xm + h*dy/d
xs2 = xm - h*dy/d
ys1 = ym - h*dx/d
ys2 = ym + h*dx/d
return (xs1,ys1),(xs2,ys2)
# ______ ________
# / ___/ | / / ____/
# \__ \| | / / / __
# ___/ /| |/ / /_/ /
# /____/ |___/\____/
def generateSVG(lines, directory, index):
svg = svgwrite.Drawing(filename = directory+"/map-"+index+".svg",size = ("1000px", "1000px"))
# polygone (white background)
polygone = []
for l in range(0, len(lines)):
for p in range(0, len(lines[l])):
polygone.append(lines[l][p])
bgline = svg.polyline(polygone,
stroke = "white",
stroke_width = "30",
stroke_linejoin= "round",
stroke_linecap = "round",
fill = "white")
svg.add(bgline)
# strokes
for l in range(0, len(lines)):
# change randomly stroke attributes
if len(lines[l]) < 3 or (random.randint(0,10) > 8 and len(lines[l]) < 10):
sw = "1"
sda = "4 4"
sdo = "5"
else:
sw = "1"
sda = "0 0"
sdo = "0"
line = svg.polyline(lines[l],
stroke = "black",
stroke_width = sw,
stroke_linejoin= "round",
stroke_linecap = "round",
stroke_dasharray = sda,
stroke_dashoffset = sdo,
fill = "none")
svg.add(line)
svg.save()
# __ _ __
# / /_ (_) /_____ ___ ____ _____
# / __ \/ / __/ __ `__ \/ __ `/ __ \
# / /_/ / / /_/ / / / / / /_/ / /_/ /
# /_.___/_/\__/_/ /_/ /_/\__,_/ .___/
# /_/
def generateBmp(lines, directory, index):
black = (0,0,0)
white=(255,255,255)
im = Image.new('RGB', (1000, 1000), white)
imPxAccess = im.load()
draw = ImageDraw.Draw(im)
# tupVerts = list(map(tuple,verts))
polygone = []
for l in range(0, len(lines)):
for p in range(0, len(lines[l])):
polygone.append(lines[l][p])
# either use .polygon(), if you want to fill the area with a solid colour
# draw.polygon( polygone, outline=black,fill=white )
# or .line() if you want to control the line thickness, or use both methods together!
draw.line( polygone+[polygone[0]], width=1, fill=black )
# im.show()
im.save(directory+'/map-'+str(index)+'.bmp')
# now you can save the image (im), or do whatever else you want with it.
if __name__ == "__main__":
main(sys.argv[1:])