Packages

A configurable spherical geodesic grid data model designed for simulations GEOF Planet offers an ideal data model for simulations of 2½D spherical phenomena like oceanography and climate. It is ported from the JavaScript project g-e-o-f/peels.

Current section

Files

Jump to
geof_planet lib geometry field_centroids.ex
Raw

lib/geometry/field_centroids.ex

defmodule GEOF.Planet.Geometry.FieldCentroids do
@moduledoc """
Functions for computing the positions of the centroids of
each Field on a Sphere.
"""
import :math
alias GEOF.Planet.{
Field,
Geometry,
Sphere
}
###
#
# ATTRIBUTES
#
###
@doc "The arclength of an edge of an icosahedron."
@l acos(sqrt(5) / 5)
def l, do: @l
###
#
# TYPES
#
###
@typedoc "Maps Field indexes to `position`s."
@type centroid_sphere :: %{Field.index() => Geometry.position()}
###
#
# FUNCTIONS
#
###
# Note: `divisions` is abbreviated as `d` in private functions.
##
# Utility functions
##
# Adds an entry to a `centroid_sphere`
@spec set_position(centroid_sphere, Field.index(), Geometry.position()) :: centroid_sphere
defp set_position(sphere, {:sxy, s, x, y}, {:pos, lat, lon}) do
Map.put(sphere, {:sxy, s, x, y}, {:pos, lat, lon})
end
##
#
# Centroid sphere computation
#
##
@doc "Computes a new `centroid_sphere`."
@spec field_centroids(Sphere.divisions()) :: centroid_sphere
def field_centroids(divisions) when is_integer(divisions) and divisions > 0 do
d = divisions
max_x = 2 * d - 1
Sphere.for_sections(
# Initialize the map with positions for polar fields
%{
north: {:pos, pi() / 2, 0.0},
south: {:pos, pi() / -2, 0.0}
},
# Set positions for the 2 tropical fields per section
fn sphere, s ->
set_position(
sphere,
{:sxy, s, d - 1, 0},
{:pos, pi() / 2 - @l, s * 2 / 5 * pi()}
)
|> set_position(
{:sxy, s, max_x, 0},
{:pos, pi() / -2 + @l, s * 2 / 5 * pi() + pi() / 5}
)
end
)
# Set positions for fields between polar fields and tropical fields (d > 1)
|> centroids_at_edge_fields(d)
# Set positions for all other fields (d > 2)
|> centroids_between_edges(d)
end
##
# Centroids at edges
##
defp centroids_at_edge_fields(sphere, d) when is_integer(d) and d > 1 do
max_x = 2 * d - 1
Sphere.for_sections(sphere, fn sphere, s ->
p = rem(s + 4, 5)
snP = Map.get(sphere, :north)
ssP = Map.get(sphere, :south)
cnT = Map.get(sphere, {:sxy, s, d - 1, 0})
pnT = Map.get(sphere, {:sxy, p, d - 1, 0})
csT = Map.get(sphere, {:sxy, s, max_x, 0})
psT = Map.get(sphere, {:sxy, p, max_x, 0})
## Set position for fields...
# ...from north pole to current north tropical pentagon
Geometry.interpolate(
sphere,
d,
snP,
cnT,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, i - 1, 0}, position)
end
)
# ...from current north tropical pentagon to previous north tropical pentagon
|> Geometry.interpolate(
d,
cnT,
pnT,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, d - 1 - i, i}, position)
end
)
# ...from current north tropical pentagon to previous south tropical pentagon
|> Geometry.interpolate(
d,
cnT,
psT,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, d - 1, i}, position)
end
)
# ...from current north tropical pentagon to current south tropical pentagon
|> Geometry.interpolate(
d,
cnT,
csT,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, d - 1 + i, 0}, position)
end
)
# ...from current south tropical pentagon to previous south tropical pentagon
|> Geometry.interpolate(
d,
csT,
psT,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, max_x - i, i}, position)
end
)
# ...from current south tropical pentagon to south pole
|> Geometry.interpolate(
d,
csT,
ssP,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, max_x, i}, position)
end
)
end)
end
defp centroids_at_edge_fields(sphere, d) when is_integer(d) and d <= 1 do
sphere
end
##
# Centroids between edges
##
defp centroids_between_edges(sphere, d) when is_integer(d) and d > 2 do
Sphere.for_sections(sphere, fn sphere, s ->
Sphere.for_columns(sphere, d, fn sphere, x ->
set_positions_between_edges(sphere, d, s, x)
end)
end)
end
defp centroids_between_edges(sphere, d) when is_integer(d) and d <= 2 do
sphere
end
defp set_positions_between_edges(sphere, d, s, x) when rem(x + 1, d) > 0 do
j = d - rem(x + 1, d)
n1 = j - 1
n2 = d - 1 - j
f1 = Map.get(sphere, {:sxy, s, x, 0})
f2 = Map.get(sphere, {:sxy, s, x, j})
f3_index = Map.get(GEOF.Planet.Field.adjacents({:sxy, s, x, d - 1}, d), :sw)
f3 = Map.get(sphere, f3_index)
Geometry.interpolate(
sphere,
n1 + 1,
f1,
f2,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, x, i}, position)
end
)
|> Geometry.interpolate(
n2 + 1,
f2,
f3,
fn sphere, i, position ->
set_position(sphere, {:sxy, s, x, i + j}, position)
end
)
end
defp set_positions_between_edges(sphere, _, _, _) do
sphere
end
end