Azimlib0.3.0 · documentation

Temporal maps and regional atlas

Azimlib 0.3 development uses its own finite frame model, Artist updates, native GUI scheduler and multi-page PDF writer. It does not import another animator, GIS backend, PDF merger or Matplotlib implementation. The default figure remains light and clean: no playback controls, grid, north, scale or legend are inserted unless explicitly requested.

Frames and instants

import azimlib as azl
from azimlib.animation import FuncAnimation

series = azl.TemporalSeries(
    [2000, 2010, 2020],
    [[[1, 2], [3, 4]], [[4, 8], [12, 16]], [[9, 18], [27, 36]]],
    unit="year", name="Synthetic population index",
)
fig, ax = azl.subplots()
image = ax.imshow(series[0].data, extent=(-54, -42, -28, -16), origin="lower")
ax.set_extent((-54, -42, -28, -16))
fig.colorbar(image, ax=ax, label="Population index")
binding = series.bind(image, scale="global")
ani = FuncAnimation(fig, lambda i: binding.apply(i), len(series), interval=400)
# Keep ani alive; start the native event loop when desired:
# azl.show()

import azimlib.pyplot as plt provides the same plotting state. Import animation from azimlib.animation, or use azl.animation. frames accepts a positive count, bounded sequence/NumPy array or TemporalSeries. With TemporalSeries the callback receives an immutable Frame with time and data; with a count it receives indices. Generators and infinite sources are rejected, not silently cached forever. Frame payloads contain immutable nested numeric/text/date/array or string-keyed mapping values; no arbitrary mutable application objects.

Numeric times require an explicit nonempty unit. They are not implicitly converted or assumed to be longitude; dates/datetimes normalize to UTC (naive dates assume UTC). Times must be strictly increasing, unique and finite. series.at(time, method="exact"|"nearest"|"previous"|"next") and index_at select a frame. Nearest clamps outside the series and chooses the earlier frame on a tie; previous/next raise outside their respective available bounds. No implicit time interpolation is performed. Playback cadence is interval in milliseconds, independent of gaps between source dates.

Update existing Artists and color policies

series.bind(artist, scale="global"|"frame", norm=None) prepares every frame before altering an Artist. Supported targets: scalar imshow with fixed shape, pcolormesh, scatter colors with fixed point count, existing choropleth features, and experimental SurfaceArtist scalar colors with fixed mesh topology. apply(i) edits scalar values/norm; boundaries, IDs, point positions, camera, limits, visibility and existing components remain owned by the same Artists. Moving points, proportional sizes, text, routes or geometry can instead be edited in a reproducible custom callback through their existing setters.

General callback code can update arbitrary state and cannot be rolled back by Azimlib. Use callbacks that reproduce any requested frame, without accumulating new layers or irreversible external side effects. A failed callback pauses playback; its partial application changes remain the callback's responsibility.

Playback and optional controls

ani.seek(index), step(), pause(), resume(), set_interval(milliseconds) and close() are explicit. Home/Back/Forward continue to control the map/camera, not the time index. Native Tk/Qt auto-play starts on the first native draw; autoplay=False disables this. repeat=False stops after the last frame.

slider_ax = fig.add_axes((0.2, 0.02, 0.5, 0.05))
play_ax = fig.add_axes((0.75, 0.02, 0.15, 0.05))
controls = ani.add_controls(slider_ax, play_ax, label="Frame")

Controls require distinct empty axes and are optional. Seeking with the slider pauses playback. Keep room for map ticks/labels. Controls are independently visible/editable through controls.slider and controls.play; disconnect with controls.close(). An animation close disconnects its controls/subscriptions. Figure close stops native timers and removes scheduled callbacks. An explicit external event_source follows start/stop/add_callback/remove_callback/interval; it is stopped/detached but not destroyed by animation close.

fig.canvas.new_timer(interval=200) returns an owned timer. Callbacks run on the native GUI thread via Tk after or Qt QTimer; no worker thread changes Artists. No hard real-time frame rate, blitting, GPU or dropped-frame catch-up is promised. Slow drawing stretches playback. Notebook, static HTML and headless Figures support manual seek/step/exports, not an implicit background event loop. Browser- live remains useful for Python-side manual updates; automatic timers in this cut require Tk or Qt. Linux/macOS native GUI validation remains checklist 7.08.

Frames, GIF and an explicit video encoder

ani.save_frames("outputs/frames")        # new directory, frame-00000.png ...
ani.save("outputs/map.gif", fps=4)       # optional Pillow encoder
# Optional, separately installed encoder, never auto-downloaded:
# ani.save("outputs/map.mp4", writer=azl.animation.FFMpegWriter(), fps=4)

Exports use the full own scene, no toolbar or pan-preview simplification. Files are atomically replaced only after successful encoding; directory exports require a new target and commit the complete sequence. On success or failure the selected frame is reapplied, and prior playback resumes if possible. Arbitrary callback side effects outside the reproducible frame cannot be restored. Exceptions do not overwrite existing final files; encoder.abort cleans up owned processes.

GIF is RGB/palette output; antialiasing/color quantization differ from PNG. Its frame delays are multiples of 10 ms; identical consecutive images may coalesce while preserving their total duration. The default GIF loops; GUI repeat and GIF loop metadata are separate contracts in this initial writer. Budgets: 1..10000 playback frames; 2 million bound scalar values; 8 million output pixels per frame; GIF at most 1000 frames/100 million total pixels. PNG core export requires Pillow.

A custom FrameWriter implements setup(path, width, height, fps, count), write_frame(Pillow_image), finish(), abort(). It must consume/copy the image synchronously; Azimlib closes it after each call. FFMpegWriter uses a fixed argument list, raw RGBA stdin, no shell, libx264 MP4, even dimensions, and bounded finish/cleanup waits. It checks executable availability; no actual installed video codec/build is validated by this batch. Encoder/codecs keep their own licenses and possible patent obligations; see third-party notices.

Regional and multipage atlas

regions = [
    azl.Region("Southeast", (-54, -40, -26, -14)),
    azl.Region("North", (-74, -45, -13, 6)),
]
def draw(ax, region):
    ax.map("brazil", facecolor="white", edgecolor="black", linewidth=0.5)
    ax.set_title(region.name)

atlas = azl.Atlas.from_regions(regions, draw, style="default")
atlas.savefig("outputs/atlas.pdf")
atlas.save_pages("outputs/atlas-svg", format="svg")

Region extent is west,east,south,north; cylindrical crossings are supported. A common projection/style/size is used for all pages and the region limits are reapplied after the draw callback. No implicit basemap, grid or decorations are added. The Figures remain editable; Atlas([fig1, fig2], labels=...) also accepts existing compositions, including multiple maps and mixed 2D/3D on each page. PNG/SVG/PDF sequences and own multipage PDF are supported; no PDF merger is used.

from azimlib.backends.backend_pdf import PdfPages
with PdfPages("outputs/pages.pdf") as pdf:
    pdf.savefig(fig)
    # Edit the figure and capture a second page:
    ani.seek(1)
    pdf.savefig(fig)

PdfPages captures each page's own PDF objects at savefig time, preserving image streams/vector text outlines/physical page size/material and DejaVu notices. The final writer rebases only its own object dictionaries; binary streams are unchanged. It does not parse/import arbitrary third-party PDF files. Context failure leaves a previous target unchanged. Empty PdfPages writes nothing. Budgets: 256 pages and 64 MB of captured PDF objects. Font outlines are not searchable text, as in the existing single-page writer.

Reproducible climate/population/region example, gallery and contracts. Only synthetic temporal fields are bundled; optional geographic data/font terms remain applicable. Historical stage proofs retain their original hashes; the current installed runtime is checked by audit_temporal.py.