Skip to content

Add ocean-centric arrangement and an icosahedron net #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Binary file added docs/dymax_net_bmng.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/dymax_ocean_etopo1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 33 additions & 4 deletions dymax/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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))
Expand Down
32 changes: 21 additions & 11 deletions dymax/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
-------
Expand Down Expand Up @@ -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]

Expand Down Expand Up @@ -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)
8 changes: 5 additions & 3 deletions dymax/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Loading