diff --git a/README.md b/README.md index 0bf2324..5c58df1 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ Technically, to unfold the earth into an icosahedron, we must first consider sli All images in `/docs` and on this page are created by running `python3 -m dymax.examples`. +SVG files suitable for printing and folding into an icosahedron are in `/svg`. They reference images in `/docs`. Open the SVG file in a text editor and change the image reference to use a different map. + +![Icosahedron net](svg/a4_dymaxion.svg) + ## To-Do List 1) Replace spherical coordinate conversion with Earth-Centered-Earth-Fixed for much better accuracy. 2) Get documentation working correctly. @@ -49,6 +53,6 @@ xy_projection = dymax.lonlat2dymax(-118.0367, 34.8951) *pydymax* code is [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/) ## References -* [Mike Bostock's](https://github.com/mbostock) [javascript code](http://mbostock.github.io/protovis/ex/dymax.js) and [http://mbostock.github.io/protovis/ex/dymax.html](live demo) +* [Mike Bostock's](https://github.com/mbostock) [javascript code](http://mbostock.github.io/protovis/ex/dymax.js) and [live demo](https://mbostock.github.io/protovis/ex/dymax.html) * [general info on dymax/fuller projections](http://www.progonos.com/furuti/MapProj/Normal/ProjPoly/projPoly3.html) * [The big wiki article on Dymaxion Maps](http://en.wikipedia.org/wiki/Dymaxion_map) diff --git a/docs/dymax_net_bmng.png b/docs/dymax_net_bmng.png new file mode 100644 index 0000000..bec6691 Binary files /dev/null and b/docs/dymax_net_bmng.png differ diff --git a/docs/dymax_ocean_etopo1.png b/docs/dymax_ocean_etopo1.png new file mode 100644 index 0000000..1eca7b2 Binary files /dev/null and b/docs/dymax_ocean_etopo1.png differ diff --git a/dymax/constants.py b/dymax/constants.py index 7ef84cd..283e2e3 100644 --- a/dymax/constants.py +++ b/dymax/constants.py @@ -3,10 +3,17 @@ '''Constants for Dymaxion Projection Module''' import math import numpy as np +from enum import Enum ### Quick Vector Functions magnitude = lambda vector: np.sqrt(np.dot(vector, vector)) +### Face arrangement +class Unfolding(Enum): + LAND = 0 + OCEAN = 1 + NET = 2 + ### Icosahedron Time facecount, vertexcount = 20, 12 @@ -61,20 +68,42 @@ [1.5, 4 * math.sqrt(3) / 3.0, 300], [1.0, 5 / (2 * math.sqrt(3)), 300], [1.5, 2 / math.sqrt(3), 0], - [2.0, 1 / (2 * math.sqrt(3)), 0], + [1.5, 1 / math.sqrt(3), 300], [2.5, 1 / math.sqrt(3), 60], [3.5, 1 / math.sqrt(3), 60], [3.5, 2 / math.sqrt(3), 120], [4.0, 5 / (2 * math.sqrt(3)), 60], [4.0, 7 / (2 * math.sqrt(3)), 0], [5.0, 7 / (2 * math.sqrt(3)), 0], - [5.5, 2 / math.sqrt(3), 0], + [0.5, 1 / math.sqrt(3), 60], [1.0, 1 / (2 * math.sqrt(3)), 0], [4.0, 1 / (2 * math.sqrt(3)), 120], [4.5, 2 / math.sqrt(3), 0], [5.0, 5 / (2 * math.sqrt(3)), 60]]) -dymax_translate08_special = np.array([1.5, 1 / math.sqrt(3), 300]) # if LCD < 4 -dymax_translate15_special = np.array([0.5, 1 / math.sqrt(3), 60]) # if LCD < 3 +dymax_translate08_special = np.array([2.0, 1 / (2 * math.sqrt(3)), 0]) # if LCD >= 4 +dymax_translate15_special = np.array([5.5, 2 / math.sqrt(3), 0]) # if LCD >= 3 + +dymax_ocean_translate = np.array([ [5.0, 5 / (2 * math.sqrt(3)), 300], + [0.5, 4 / (2 * math.sqrt(3)), 0], + [1.0, 5 / (2 * math.sqrt(3)), 60], + [1.0, 7 / (2 * math.sqrt(3)), 120], + [4.5, 8 / (2 * math.sqrt(3)), 300], + [4.5, 4 / (2 * math.sqrt(3)), 0], + [4.0, 1 / (2 * math.sqrt(3)), 240], + [1.0, 1 / (2 * math.sqrt(3)), 120], + [1.5, 2 / (2 * math.sqrt(3)), 60], + [1.5, 4 / (2 * math.sqrt(3)), 120], + [2.0, 5 / (2 * math.sqrt(3)), 60], + [2.0, 7 / (2 * math.sqrt(3)), 120], + [3.0, 7 / (2 * math.sqrt(3)), 0], + [4.0, 7 / (2 * math.sqrt(3)), 240], + [4.0, 5 / (2 * math.sqrt(3)), 300], + [3.5, 2 / (2 * math.sqrt(3)), 300], + [2.5, 2 / (2 * math.sqrt(3)), 180], + [2.5, 4 / (2 * math.sqrt(3)), 120], + [3.0, 5 / (2 * math.sqrt(3)), 300], + [3.5, 4 / (2 * math.sqrt(3)), 0]]) +dymax_ocean_translate08_special = np.array([2.0, 1 / (2 * math.sqrt(3)), 120]) # if LCD == 2 or LCD == 3 ### Optimizations garc = 2 * math.asin(math.sqrt( (5 - math.sqrt(5)) / 10)) diff --git a/dymax/convert.py b/dymax/convert.py index 2d9d1f7..0c99afe 100644 --- a/dymax/convert.py +++ b/dymax/convert.py @@ -27,7 +27,7 @@ def euclidean(vec_a, vec_b): ### Dymax Conversion Main Routine @lru_cache(maxsize=2**12) -def lonlat2dymax(lon, lat, getlcd=False): +def lonlat2dymax(lon, lat, getlcd=False, unfolding=constants.Unfolding.LAND): ''' Lon Lat 2 Dymax XY @@ -71,7 +71,7 @@ def lonlat2dymax(lon, lat, getlcd=False): tri, lcd = fuller_triangle(XYZ) # Determine the corresponding Fuller map plane(x, y) point - x_pos, y_pos = dymax_point(tri, lcd, XYZ) + x_pos, y_pos = dymax_point(tri, lcd, XYZ, unfolding) if getlcd: return x_pos, y_pos, lcd else: return x_pos, y_pos @@ -102,7 +102,7 @@ def vert2dymax(vert, vertset, push=.9999): x_pos, y_pos = dymax_point(tri, hlcd, XYZ) return x_pos, y_pos -def face2dymax(face_idx, push=.9999, atomic=False): +def face2dymax(face_idx, push=.9999, atomic=False, unfolding=constants.Unfolding.LAND): ''' Convert Icosahedron Face to (4) XY Vertices @@ -141,13 +141,13 @@ def face2dymax(face_idx, push=.9999, atomic=False): XYZ = np.mean([up, down], axis=0) XYZ = XYZ * push + constants.XYZcenters[face_idx] * (1-push) tri, hlcd = fuller_triangle(XYZ) - points[jdx] = dymax_point(tri, hlcd, XYZ) + points[jdx] = dymax_point(tri, hlcd, XYZ, unfolding) else: points = np.zeros((3+1, 2)) for jdx in range(3): XYZ = constants.vertices[constants.vert_indices[face_idx, jdx]] * push + constants.XYZcenters[face_idx] * (1-push) tri, hlcd = fuller_triangle(XYZ) - points[jdx] = dymax_point(tri, hlcd, XYZ) + points[jdx] = dymax_point(tri, hlcd, XYZ, unfolding) points[-1] = points[0] # Loop Back to Start return points @@ -246,7 +246,7 @@ def fuller_triangle(XYZ): elif h_dist3 <= h_dist2 <= h_dist1: h_lcd = 3 return h_tri, h_lcd -def dymax_point(tri, lcd, XYZ): +def dymax_point(tri, lcd, XYZ, unfolding=constants.Unfolding.LAND): ''' In order to rotate the given point into the template spherical triangle, we need the spherical polar coordinates of the center @@ -261,6 +261,8 @@ def dymax_point(tri, lcd, XYZ): Dymaxion sub-triangle where we want to be. XYZ : tuple of floats Pseudo-ECEF coordinate that will be projected to dymaxion. + unfolding: enum + Either LAND, OCEAN or NET. Returns ------- @@ -320,10 +322,18 @@ def dymax_point(tri, lcd, XYZ): ### Move and Rotate as Appropriate # You can disable the special translations for uniform triangles - if tri == 8 and lcd < 4: - xtranslate, ytranslate, rotation = constants.dymax_translate08_special - elif tri == 15 and lcd < 3: - xtranslate, ytranslate, rotation = constants.dymax_translate15_special + if unfolding == constants.Unfolding.LAND: + if tri == 8 and lcd >= 4: + xtranslate, ytranslate, rotation = constants.dymax_translate08_special + elif tri == 15 and lcd >= 3: + xtranslate, ytranslate, rotation = constants.dymax_translate15_special + else: + xtranslate, ytranslate, rotation = constants.dymax_translate[tri] + elif unfolding == constants.Unfolding.OCEAN: + if tri == 8 and (lcd == 2 or lcd == 3): + xtranslate, ytranslate, rotation = constants.dymax_ocean_translate08_special + else: + xtranslate, ytranslate, rotation = constants.dymax_ocean_translate[tri] else: xtranslate, ytranslate, rotation = constants.dymax_translate[tri] @@ -437,4 +447,4 @@ def benchmark(verbose=True): dymax_centers = np.zeros((constants.facecount, 2)) for fdx in range(constants.facecount): tri, hlcd = fuller_triangle(constants.XYZcenters[fdx]) - dymax_centers[fdx] = dymax_point(tri, hlcd, constants.XYZcenters[fdx]) + dymax_centers[fdx] = dymax_point(tri, hlcd, constants.XYZcenters[fdx], constants.Unfolding.LAND) diff --git a/dymax/examples.py b/dymax/examples.py index 7e495a0..fa6f8dd 100644 --- a/dymax/examples.py +++ b/dymax/examples.py @@ -247,7 +247,7 @@ def plot_coastline_vectors(verbose=True, save=False, show=True, dpi=300, resolut plt.show() else: plt.close() -def convert_rectimage_2_dymaximage(inFilename, outFilename, verbose=True, scale=300, speedup=1, save=False, show=True): +def convert_rectimage_2_dymaximage(inFilename, outFilename, verbose=True, scale=300, speedup=1, unfolding=constants.Unfolding.LAND, save=False, show=True): ''' Convert rectilinear image to dymax projection image. @@ -282,7 +282,7 @@ def convert_rectimage_2_dymaximage(inFilename, outFilename, verbose=True, scale= stdout.flush() # I would add flush=True to print, but thats only in python3.3+ for j, lat in enumerate(np.linspace(90, -90, ysize/speedup, endpoint=True)): j *= speedup - newx, newy = convert.lonlat2dymax(lon, lat) + newx, newy = convert.lonlat2dymax(lon, lat, unfolding=unfolding) newx = int(newx*scale) - 1 newy = int(newy*scale) try: dymaximg.putpixel((newx, newy), pix[i, j]) @@ -361,7 +361,7 @@ def plot_face_hq(resolution='i', save=False, show=True, verbose=True, dpi=300): # Draw final figure bits plt.axis('off') plt.gca().set_aspect('equal') - if save: plt.savefig('dymax_earthmeridianstriangles.png', bbox_inches='tight', dpi=dpi, transparent=True, pad_inches=0) + if save: plt.savefig('dymax_face.png', bbox_inches='tight', dpi=dpi, transparent=True, pad_inches=0) if show: plt.tight_layout() plt.show() @@ -381,7 +381,9 @@ def run_examples(resolution='c', save=False, show=True, verbose=True): plot_coastline_vectors(resolution=resolution, save=save, show=show) plot_face_hq(resolution=resolution, save=save, show=show) convert_rectimage_2_dymaximage(io.PKG_DATA+'bmng.jpg', 'dymax_bmng.png', save=save, show=show) + convert_rectimage_2_dymaximage(io.PKG_DATA+'bmng.jpg', 'dymax_net_bmng.png', unfolding=constants.Unfolding.NET, save=save, show=show) convert_rectimage_2_dymaximage(io.PKG_DATA+'etopo1.jpg', 'dymax_etopo1.png', save=save, show=show) + convert_rectimage_2_dymaximage(io.PKG_DATA+'etopo1.jpg', 'dymax_ocean_etopo1.png', unfolding=constants.Unfolding.OCEAN, save=save, show=show) if __name__ == '__main__': run_examples() diff --git a/svg/a2_dymaxion_a.svg b/svg/a2_dymaxion_a.svg new file mode 100644 index 0000000..507dce3 --- /dev/null +++ b/svg/a2_dymaxion_a.svg @@ -0,0 +1,728 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + Score all the way + + + + + + + + + + + + + + + + + + + Mirror of DWire002 + + + + DWire001 + + + + DWire002 + + + + DWire014 + + + + DWire003 + + + + Mirror of DWire001 + + + + DWire008 + + Uses NASA Blue Marble and pydymax. + + + + Mirror of Mirror of DWire026 + + + + Mirror of Mirror of DWire022 + + + + Mirror of Mirror of DWire027 + + + + DWire021 + + + + Mirror of Mirror of DWire026 + + + + + + + diff --git a/svg/a2_dymaxion_b.svg b/svg/a2_dymaxion_b.svg new file mode 100644 index 0000000..ebb49ea --- /dev/null +++ b/svg/a2_dymaxion_b.svg @@ -0,0 +1,785 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DWire009 + + + + DWire007 + + + + DWire005 + + + + DWire004 + + + + DWire006 + + + + DWire004 + + + + DWire005 + + + + + + + + + + + + + + + diff --git a/svg/a3_dymaxion.svg b/svg/a3_dymaxion.svg new file mode 100644 index 0000000..d11d142 --- /dev/null +++ b/svg/a3_dymaxion.svg @@ -0,0 +1,899 @@ + + + + + + image/svg+xml + + + + + + + + + + + + Mirror of Mirror of DWire025 + + + + Mirror of DWire028 + + + + Mirror of Mirror of DWire026 + + + + Mirror of Mirror of DWire022 + + + + Mirror of Mirror of DWire024 + + + + Mirror of Mirror of Mirror of DWire024 + + + + Mirror of Mirror of DWire027 + + + + DWire021 + + Generated with pydymaxhttps://github.com/Teque5/pydymax + + + + DWire009 + + + + Mirror of DWire002 + + + + DWire007 + + + + DWire001 + + + + DWire002 + + + + DWire005 + + + + DWire004 + + + + DWire014 + + + + DWire003 + + + + Mirror of DWire001 + + + + DWire017 + + + + DWire008 + + + + DWire006 + + + + DWire009 + + + + DWire009 + + + + + Mirror of Mirror of DWire026 + + + + + + + + + + Score where needed, then cut + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/svg/a4_dymaxion.svg b/svg/a4_dymaxion.svg new file mode 100644 index 0000000..eb27088 --- /dev/null +++ b/svg/a4_dymaxion.svg @@ -0,0 +1,900 @@ + + + + + + image/svg+xml + + + + + + + + + + + + Mirror of Mirror of DWire025 + + + + Mirror of DWire028 + + + + Mirror of Mirror of DWire026 + + + + Mirror of Mirror of DWire022 + + + + Mirror of Mirror of DWire024 + + + + Mirror of Mirror of Mirror of DWire024 + + + + Mirror of Mirror of DWire027 + + + + DWire021 + + Generated with pydymaxhttps://github.com/Teque5/pydymax + + + + DWire009 + + + + Mirror of DWire002 + + + + DWire007 + + + + DWire001 + + + + DWire002 + + + + DWire005 + + + + DWire004 + + + + DWire014 + + + + DWire003 + + + + Mirror of DWire001 + + + + DWire017 + + + + DWire008 + + + + DWire006 + + + + DWire009 + + + + DWire009 + + + + + Mirror of Mirror of DWire026 + + + + + + + + + + Score where needed, then cut + + + + + + + + + + + + + + + + + + + + + + + + +