How to Create 3D Paper Model of Poľana Volcano
Slovakia has no active volcanos, but we have some inactive ones and probably the most famous is Poľana (more info, map) due to its disctinctive shape of caldera.
In this article, I will explain how to create a SVG paper model of the volcano ready for blade cutter machine. The source codes below can be used to generate such a 3D model of any terrain.
Rear view with caldera well visible:
For a comparison, here is rendered 3D panorama view:
Following photo exhibits several prototypes. On left side is my first Poľana prototype assembled with slightly different technique not explained in this blogpost. In the middle is second generation of Poľana and on the right side is Mt. Everest model.
How to Produce the SVG for Paper Cutting
We will not create the paper model by hand but use a bit of programming. The source code below is Ruby. First, we need to determine the latitude/longitude coordinates of a rectangle:
lat0, lon0 = 48.60113, 19.29473 # left bottom
lat1, lon1 = 48.70047, 19.52991 # right top
and the number of “cuts” through the terrain in x
(latitude) and y
(longitude):
lat_steps, lon_steps = 80, 24
lat_diff, lon_diff = ((lat1 - lat0) / lat_steps.to_f), ((lon1 - lon0) / lon_steps.to_f)
Picture below depicts that we create 24 paper pieces in x-axis (that is latitude). For each paper sheet we will take 80 elevation points in longitudal axis so that shape of each paper is as real as possible.
Now we create a 2–dimensional table called elevations
(size 80x24), where x
is latitude, y
is longitude, and value is elevation in meters. We use mapquestapi.com HTTP API to get the elevation:
uri = URI.parse('http://open.mapquestapi.com/elevation/v1/profile')
# you will need to unescape your key characters, e.g. using this site: http://www.w3schools.com/tags/ref_urlencode.asp
params = { 'key' => "your-key-here", 'shapeFormat' => "raw", }
elevations = []
(0...lat_steps).each do |i|
points = []
lat = lat0 + lat_diff * i
(0...lon_steps).each do |j|
lon = lon0 + lon_diff * j
points << lat
points << lon
end
params['latLngCollection'] = points.join(',')
uri.query = URI.encode_www_form params
response = uri.open.read
json_response = JSON.parse response
elevations << json_response['elevationProfile'].collect{|h| h['height']}
end
Next we convert the elevations from meters to svg points:
one_cm_in_pts = 33 # one centimeter is 33 points in SVG (at least in Inskcape)
z_cms = 6 # we want our model height to be 6 cm
cm_to_svg_point_ratio = one_cm_in_pts * z_cms
ele_min, ele_max = elevations.flatten.min, elevations.flatten.max
ele_diff = (ele_max - ele_min).to_f
elevations_in_pixels = []
elevations.each do |eline|
eline_relative = []
eline.each do |e|
eline_relative << ((1.0 - ((ele_max - e) / ele_diff)) * cm_to_svg_point_ratio).to_i
end
elevations_in_pixels << eline_relative
end
Now we are ready to produce the SVG polylines for cutter machine.
SVG polyline is simply a set of [x,y]
pairs which will be connected by lines.
svg_polylines = [] # for each of the 24 paper sheets we want to cut we will create a polyline and store all of them here
total_length_in_south_north_direction_in_cm = 10 # width is 10cm. our rectangle has 3:2 ratio so that length will be 15cm.
y_offset_between_two_slices = 200 # points
elevations_in_pixels = elevations_in_pixels.transpose # [lat, lon] -> [lon, lat]
x_offset_between_points = ((total_length_in_south_north_direction_in_cm / lat_steps.to_f) * one_cm_in_pts).to_i
(0...lon_steps).each do |i| # for each of the 24 papers sheets
svg_polyline_points = []
svg_polyline_points << [0, (y_offset_between_two_slices*i - one_cm_in_pts*2)]
svg_polyline_points << [0, elevations_in_pixels[i][0] + y_offset_between_two_slices*i]
(0...lat_steps).each do |j| # for each of the 80 elevation points of one sheet
x = x_offset_between_points * j
y = elevations_in_pixels[i][j] + y_offset_between_two_slices*i
svg_polyline_points << [x,y]
end
# we have all the 80 points of one polyline, create corresponding xml element
svg_polyline = "<polyline points=\"#{svg_polyline_points.collect{|a,b| "#{a},#{b}"}.join(' ')}\" style=\"fill:white;stroke:red;stroke-width:4\" />"
svg_polylines << svg_polyline
end
The complete source code and SVG files are here.
Here are sheets after cutting, before assembly: