A resolved view: the centre, zoom, size and scale of one rendered image.
The viewport converts world pixels to output pixels. Everything that places something on
the image goes through to_output/2, so tiles and overlays share one rounding rule and
cannot disagree about where a coordinate lands.
fit/1 works out the centre and zoom when the caller gives bounds or markers instead.
Summary
Types
What fit/1 fits the view to. :center and :zoom are kept when given; :zooms is
the range to choose a zoom from, highest first. :reserve is a band of that many
logical pixels along the bottom, such as the attribution strip, that nothing is fitted
into.
Functions
Resolves the centre and zoom of a view.
Builds a viewport centred on center.
The size of the output image in pixels, size * scale.
Converts a point in world pixels to output pixels, rounding halves up.
Types
@type fit_request() :: %{ optional(:reserve) => non_neg_integer(), center: StaticMaps.Projection.lat_lng() | nil, zoom: non_neg_integer() | nil, bounds: {StaticMaps.Projection.lat_lng(), StaticMaps.Projection.lat_lng()} | nil, markers: [StaticMaps.Marker.t()], padding: non_neg_integer(), size: {pos_integer(), pos_integer()}, zooms: Range.t() }
What fit/1 fits the view to. :center and :zoom are kept when given; :zooms is
the range to choose a zoom from, highest first. :reserve is a band of that many
logical pixels along the bottom, such as the attribution strip, that nothing is fitted
into.
@type t() :: %StaticMaps.Viewport{ center: StaticMaps.Projection.lat_lng(), origin: StaticMaps.Projection.point(), scale: pos_integer(), size: {pos_integer(), pos_integer()}, zoom: non_neg_integer() }
:center—{lat, lng}of the middle of the image:zoom— integer zoom, in the 256-pixel convention:size—{width, height}in logical pixels, before:scale:scale— output pixels per logical pixel:origin— the top-left corner of the view, in world pixels
Functions
@spec fit(fit_request()) :: {StaticMaps.Projection.lat_lng(), non_neg_integer()}
Resolves the centre and zoom of a view.
- With
:centerand:zoom, returns them as given. - With neither, picks the highest zoom at which the bounds and every marker, pin
included, fit inside
:sizeless:paddingon every side, and centres on them. - With
:centeronly, keeps it and picks the highest zoom at which everything fits around it. - With
:zoomonly, centres everything at that zoom.
The centre is the middle of what is shown in world pixels, not in degrees: Mercator stretches latitudes unevenly, so the midpoint in degrees is off-centre on the map.
Longitudes are fitted by the shortest arc that covers them all, so markers either side
of the antimeridian frame the antimeridian rather than the whole world. A :bounds
with west > east crosses the antimeridian. Everything at a single point gets zoom
15, within :zooms, or lower if a given centre is too far from it
for the point to show. When nothing fits even at the lowest zoom, the lowest zoom is
used.
iex> {{lat, lng}, zoom} =
...> StaticMaps.Viewport.fit(%{center: nil, zoom: nil, bounds: {{0, 0}, {10, 10}},
...> markers: [], padding: 0, size: {256, 256}, zooms: 0..19})
iex> {Float.round(lat, 4), Float.round(lng, 4), zoom}
{5.0191, 5.0, 5}
@spec new( StaticMaps.Projection.lat_lng(), non_neg_integer(), {pos_integer(), pos_integer()}, pos_integer() ) :: t()
Builds a viewport centred on center.
iex> vp = StaticMaps.Viewport.new({0, 0}, 0, {256, 256}, 1)
iex> vp.origin
{0.0, 0.0}
@spec output_size(t()) :: {pos_integer(), pos_integer()}
The size of the output image in pixels, size * scale.
Converts a point in world pixels to output pixels, rounding halves up.
iex> vp = StaticMaps.Viewport.new({0, 0}, 1, {100, 100}, 2)
iex> StaticMaps.Viewport.to_output(vp, {256.0, 256.0})
{100, 100}