flutter_map_vector_tiles 2.6.2
flutter_map_vector_tiles: ^2.6.2 copied to clipboard
Vector tiles for flutter_map: a clean, self-contained MapLibre-style vector tile layer with isolate-based decoding, raster tile caching and screen-space label collision.
Changelog #
2.6.2 #
Surviving an app backgrounding on iOS.
- π Magenta tiles after resuming an app: iOS revokes GPU access
while an app is in the background, and Impeller fills a texture whose
Metal work was rejected with solid magenta rather than failing β so a
tile rasterized on the way out or on the way back in returned a
perfectly ordinary-looking image that was pure magenta, and the
process-wide finished-tile cache then served it for the rest of the
session. The layer now watches the app lifecycle: it stops rasterizing
from
inactiveonwards (parking the work rather than dropping it), and on the way back it discards the finished-tile cache, the raster-source images and the pattern stamps, then re-rasterizes what is on screen. Recovery reads no network and decodes nothing β the geometry never left the Dart heap β and the old imagery stays up until its replacement lands, so there is no blank frame. A map screen rebuilt during the return is covered too: the "has been away" mark is process-wide, not per layer. This works around an engine gap rather than fixing it β Impeller parks an asyncPicture.toImagesnapshot while the GPU is disabled but rasterizesPicture.toImageSyncregardless β so it is written to be deleted once flutter#191255 lands.
2.6.1 #
Documentation only β the style support section, rewritten to be scannable. The library is unchanged from 2.6.0.
- π Style support: layer types are a table with a row per type, icons and expressions sit together as the reference block they are, and the label behaviour becomes a Labels subsection of bold-led bullets instead of five dense paragraphs β with the zoom-handover guarantees split out from the general fade rule, since they answer a different question. No behaviour claim changed.
2.6.0 #
Everything a third-party tile source needs: providers can now be
substituted into a hosted style, opt out of the disk cache, and coalesce
their own requests β enough that
flutter_map_vector_tiles_mbtiles
adds MBTiles archives without this package taking on SQLite. Labels also
hold the spot they are sitting on across a zoom crossing.
- β¨ Bring your own provider:
StyleReader(resolveProvider: β¦)swaps in a provider for any source by id, so a local archive can back a hosted style while that style still supplies the theme, sprites and attribution. Returning null falls through to the style's own URL, and it applies to raster sources too. This replaces the need for per-format URL schemes: a device-absolute path is not something a portable style document can name. - β¨
SingleFlightis exported, so a provider outside this package can coalesce concurrent loads the same way the built-in ones do rather than hand-rolling an in-flight map. - β‘ Providers backed by local storage no longer duplicate their tiles
into the on-disk cache, or leave a zero-byte marker for every
coordinate they do not cover. Opt a custom provider out with the new
VectorTileProvider.cacheBytesToDisk;MemoryVectorTileProvideralready does. The opt-out covers reads as well as writes, so an entry written before a source became local cannot shadow it. - π Street names no longer flip to the other side of their street, and
labels at a
text-variable-anchorno longer hop, at the moment a zoom level hands over or a tile is re-rendered. The choices a label makes when it is placed β which anchor it sits at, which way it reads β were kept on the symbol instance, and a new zoom level's copy of a street is a new instance: it decided again from scratch, at full opacity, with no fade to cover the change. They are now remembered per label and position, so the arriving copy inherits what the label it replaces was sitting on. - π The offline documentation now points at a real MBTiles implementation instead of describing one you would have to write.
2.5.0 #
Labels that hold still. Every appearance and disappearance animates through one per-label fade, and which labels win their space is decided on a timer instead of on every frame β so gestures no longer flicker labels out and back, or walk them around the map.
- β¨ Per-label fades:
labelFadeDurationnow drives one fade state per label identity (layer, text, icon) instead of one per tile cohort β rising while the label is on screen, falling once it no longer is, whatever the cause. Labels fade in when a tile arrives, when they win collision space mid-gesture, or when panning brings them in; they fade out when the tileset stops carrying them at the next zoom, when denser labelling crowds them out, or when a zoom crossing cuts their layer at itsminzoomβ cases that used to pop in one frame. A departing label frees its space immediately, so its replacement crossfades in over it rather than popping in when the fade ends, and a label mid-fade that comes back resumes from its current opacity instead of restarting. - π¨ Labels ramp out over the last quarter zoom level before their
layer's declared
maxzoominstead of snapping away, and ramp back in when you zoom out across it.minzoomgets no zoom-based ramp β it is inclusive, so one would leave aminzoom: 14layer invisible on a map resting at exactly zoom 14 β but crossing it now eases labels out through the time-based fade above. - β‘ Frames between two collision passes lay out only the labels that are on screen, skipping the candidates that lost β most of them on a dense screen β and reserve no collision space at all.
- π Labels no longer wink out for a moment and return while you zoom.
Collision was re-decided on every painted frame, and label boxes scale
with the zoom, so every brush past a neighbour was acted on: a label
lost its spot for a few frames and took it straight back. The
collision pass now runs once per
labelFadeDuration(at most every 300 ms) and its decision is held in between, with the labels allowed to overlap for those frames β the trade MapLibre makes. Labels from a tile that just landed still place immediately. - π Labels no longer wander while you pan. A name that can be drawn
from more than one feature β a street label on both carriageways, or
the same name coming from two zoom levels β jumped between them as
their screen order changed; a label with
text-variable-anchorhopped to its second anchor and back when a neighbour brushed past; and a road running near vertical flipped its label's reading direction on alternate frames, mirroring it (and any perpendiculartext-offset) to the other side of the street each time. Each of those choices is now kept from the previous placement unless it genuinely stops fitting. - π Labels no longer blink β or visibly fade into themselves β when you cross a zoom level. Crossing one replaces the whole display level with new tiles whose labels used to carry fresh fade state, so a label present at both levels could be re-faded against its own still-visible copy, with collision deciding between the two on a sub-pixel anchor difference. The two copies now share one fade state by construction: whichever wins collision draws at the same opacity, and the swap is invisible.
- π Street names no longer flash up in the moment between a zoom-out
crossing and the new level's tiles arriving. The crossing cuts whole
symbol layers at their
minzoom(POIs, typically), and the space their labels held went to whatever the outgoing level had been suppressing β street names never previously on screen appeared, only to be faded straight back out. An outgoing level is now pinned to the labels that were actually visible: it keeps them on screen until the new level covers them, but can no longer introduce new ones.
2.4.0 #
What a full review of 2.3.0 turned up: tiles honour their freshness deadline again, the finished-tile cache stops stranding textures across map opens, and labels switch exactly at the zoom their layer declares.
- β¨
SpriteAtlas.signature: a sprite sheet now identifies its content β the URLStyleReaderloaded it from, or its layout for a hand-built atlas β andSpriteAtlasaccepts an optionalcacheKey. - π¨ Stroked polygon outlines keep the corner at a ring's first vertex when clipped at deep zoom. The two halves of the contour were stroked separately, so butted end caps left a notch there.
- π¨ Labels appear and disappear at exactly their layer's
minzoomandmaxzoom. The threshold was tested against a zoom rounded to 1/8 of a level, so labels could switch up to 1/16 of a level early or late. - β‘ Reopening a map over the same style paints from the finished-tile cache as documented. Its key changed on every style read, so styles with sprites β nearly all of them β re-rendered every open, and each open stranded the previous one's textures for the process lifetime. The number of retained caches is now bounded as well.
- β‘ Fading labels draw through layers bounded to what they cover, instead of a full-screen offscreen pass per opacity step per frame.
- β‘ Tiles held over from the previous zoom level release their cross-fade underlay at once, and render jobs queued for a tile that leaves the viewport are dropped rather than waiting for the pump β both used to hold a full tile texture longer than it was needed.
- π An expired tile whose cached bytes are corrupt is refetched. It was served, failed to decode, and every retry re-read the same bad entry, so the tile stayed blank until the cache evicted it.
- π A tile the source reports missing is re-checked when the revalidation meant to confirm that could not reach the source. A single failed request used to hide the tile for the whole session.
- π A background revalidation is no longer lost to a tile load that was already in flight: the older load could finish first, publish the replaced content and make the reload look like a no-op.
- π Content replaced by a revalidation can no longer be written back into the cache that revalidation just invalidated by a render job queued before it.
- π Display tiles served from the finished-tile cache are revalidated
too. They never reach the tile stores, so
diskCacheTtldid not apply to them for as long as they stayed cached. - π A retry no longer replaces imagery that has already arrived. It could flip a finished tile back to loading and β when the retry recovered nothing β leave it there, pinning the previous zoom level's tiles and labels on screen.
- π Labels held over from the previous zoom level no longer stay hidden for several frames when a visible tile is re-rendered mid-transition.
- π A
labelFadeDurationshorter than a millisecond no longer throws during paint, and a fade no longer spends its first frame invisible. - π Setting
rasterCacheMaxBytesto0at runtime releases the textures actually being held; changing the style in the same rebuild made it clear a different, empty cache instead. - βοΈ Browsing an expired area offline no longer starts a request per cache miss: failed revalidations are throttled. Failed loads still are not, so stale tiles keep painting.
- βοΈ Tiles that finish downloading while a map is closing are written to the disk cache instead of dropped, so reopening does not refetch them.
- π The README now documents the public API it never mentioned:
tappable attribution links (
StyleAttribution.spans/AttributionSpan.url), the exceptionsStyleReader.read()andPmTilesVectorTileProvider.openthrow, the full option sets ofNetworkVectorTileProviderandopen()(headers, zoom clamps, retries, customhttp.Client),StyleReader.httpClient, and manualRasterTileSourcewiring for raster imagery. - π
VectorTileLayer.clearMemoryCache()is now covered in the README and the architecture guide β it had been public since 2.2.0 with no mention outside its dartdoc.
2.3.0 #
Smooth zooming where labels live: text is shaped once and scaled, zoom-level crossings stagger their work, finished tiles are cached for instant re-crossings β plus expired tiles that paint instantly and refresh in the background, deep-zoom performance in dense cities, and pub.dev gallery screenshots.
- β¨ Finished-tile cache (
rasterCacheMaxBytes, default 64 MiB, 0 disables): rasterized display tiles and their symbols are kept in a process-wide LRU, so zooming out and back in β or reopening a map on the same style β swaps the level back in instead of re-rendering it. These are GPU texture bytes (~1 MiB per tile at devicePixelRatio 2, ~2.25 MiB at 3); the default holds roughly two phone-screen zoom levels at dpr 2.VectorTileLayer.clearMemoryCache()releases it. - β¨ Label fade-in (
labelFadeDuration, default 150 ms, 0 restores the old instant pop): newly appearing labels and icons now fade in, masking the pop when a zoom level first shows symbol layers. Fading labels reserve their full collision space, so placements never shift mid-fade. - β¨ Stale-while-revalidate tiles: disk-cached tiles older than
diskCacheTtlare no longer refetched before they can paint β the expired tile is shown immediately and revalidated in the background. When the server returns changed data the tile cross-fades to the new imagery; when the fetch fails (e.g. offline) the old tile simply stays. Previously the map held back such tiles for the full network round-trip (plus retries) and fell back to the expired copy only after it failed. - π¨ A re-rasterized tile that replaces visible imagery (background refresh, a source recovered by a retry, provisionalβfinal) now keeps the previous raster underneath while the new one fades in, instead of fading in over the map background.
- β‘ Scale-invariant text shaping: label text used to be re-shaped
(two
TextPainter.layoutpasses per label, inside the paint phase) roughly every 0.1 px of atext-sizezoom ramp β a full-screen re-shape ~10Γ per pinched zoom level, felt as stutter wherever labels were visible. Text is now shaped once at a 16 px reference size and drawn through the canvas transform, which keeps it vector-crisp at every fractional zoom;text-opacityandtext-halo-widthramps re-shape a bounded handful of times per label ever instead of per zoom step. The per-frame style memo compares evaluated primitives, so a pinch frame no longer builds cache-key strings per symbol. - β‘ Two-phase zoom crossings: crossing an integer zoom used to rasterize geometry and extract symbols for every tile in one un-preemptible job each, and the first frame with new labels shaped all their text inside the paint phase β the visible stutter at the zoom where text and icons appear. Rasters and symbol extraction are now separate budgeted jobs (geometry for the whole viewport lands first, labels follow a frame or two later), text shaping is prewarmed in the same budgeted tick that publishes a tile's symbols, and the retained-level label bookkeeping is memoized instead of recomputed per frame during transitions.
- β‘ Overzoom culling and clipping: zoomed past the source's maxzoom (z15+ for typical OpenMapTiles sources), every display tile used to re-process all features of its data tile for every style layer β in a dense city that meant millions of filter evaluations and full-tile path building per zoom crossing, and dash/line-pattern cost that grew 4Γ per zoom level. Features are now rejected on decode-time bounds before any expression work, and geometry is clipped to the tile's visible window from two levels of overzoom, with dash and line-pattern phase preserved across tile seams. A dense city tile at z20 over z14 data rasterizes ~80Γ faster in the bundled benchmark (85 ms β 1 ms).
- β‘ Along-line label anchors are enumerated only within the display tile's window (their global spacing is kept, so labels never shift between tiles), and symbol layout skips features outside the tile before evaluating any style expressions β previously every display tile scanned every POI, house number and road of the whole data tile.
- β‘
line-gap-widthcasings no longer allocate a full-tile offscreen buffer per feature β the compositing layer is bounded to the stroked path, which in a city of casing-styled roads was a large hidden raster-thread cost. - β‘ Smooth labels during pinch zoom in dense areas: the label pass now evaluates at a zoom quantized to 1/8-level steps and the text/glyph caches grew (800β2500 and 1500β4000 entries). Previously any fractional zoom change invalidated every label's cached style on every frame, and a dense city screen overflowed the text cache and re-laid-out every label every frame.
- π Labels respect their layer's zoom range continuously: zooming
out toward a symbol layer's
minzoom, street names used to burst on all at once just before the threshold and then all vanish together β the range was only checked at integer tile zoom, so labels (including the retained previous level's) kept painting until the tile grid flipped. The label pass now enforcesminzoom/maxzoomevery frame at the fractional style zoom, exactly where the style author set the cut. - π Text whose
text-sizeramps toward zero is skipped instead of inflated to a 4 px floor β the near-invisible labels used to flood the collision grid at the bottom of the ramp (the other half of the burst). Text sized between 1 px and 4 px now draws at its true size. - π Symbol layers with a fractional
minzoom(e.g. 14.5) now appear at exactly that zoom when zooming in, instead of one whole zoom level late. - π When current- and previous-level copies of a label tie for the same spot during a zoom crossing, the current level now wins deterministically instead of the winner flickering between the two.
- π¦ The pub.dev page now shows three screenshots (
screenshots/), framed in an iPhone bezel: Munich in OpenFreeMap Liberty, Zermatt with an Esri hillshade raster blended into the vector style, and Munich in the dark Fiord style. - π§Ή DevTools timeline events (
VT render pump,VT rasterize,VT symbols,VT labels) around the render pipeline's UI-thread stages, for profiling zoom behaviour. - π§Ή The example app draws its attribution as a rounded translucent pill centered at the bottom of the map instead of a flush white bar in the bottom-right corner.
2.2.0 #
Mapbox-hosted and header-authenticated styles now load, over a broad correctness and performance pass.
- β¨
StyleReadernow actually resolvesmapbox://URIs β style ids, sprite bases and bare tileset ids expand to theirapi.mapbox.comequivalents withapiKeyas the access token. The capability was documented but never implemented. - β¨
StyleReaderacceptsheaders, sent with the style, TileJSON and sprite requests and forwarded to the created tile providers β style loading now works end-to-end against header-authenticated services. - β‘ MVT tile decoding is about twice as fast (measured 2.0β2.3Γ on
real tiles): geometry vertices accumulate into a reusable typed-data
scratch buffer instead of a growable
List<double>, which boxed every coordinate on the VM. - β‘ PMTiles tile blobs are no longer gzip-inflated on the UI isolate on native platforms: the worker isolate inflates them just before decoding (0.5β3 ms per tile that used to compete with the frame budget while panning on a cold cache), and the disk cache now stores the smaller compressed form. As a side effect, servers that send gzip-compressed MVT without declaring it now decode instead of failing. Feature properties are also filtered during decoding instead of being fully materialized and then trimmed.
- β‘ Tile bytes travel lighter: downloaded tiles are no longer copied a second time before decoding, tiles a source has said don't exist (oceans, sparse zooms) are remembered on disk instead of being re-requested every app session, the offline fallback reads the disk once instead of probing existence first, and PMTiles leaf-directory caching is bounded by bytes rather than entry count alone.
- β‘ The style engine does far less work per evaluation: zoom-only
paint properties (pure zoom ramps β the most common shape) are
detected at compile time and memoized against the zoom, so a tile
pass evaluates each once instead of per feature; parsed CSS colours
are cached; legacy
infilters use set lookups instead of linear scans; literalget/haskeys read the property map directly; operator dispatch moved from evaluation to parse time; exponential ramps precompute their per-intervalpowfactors; and legacy{token}templates are split once instead of regex-replaced per feature. - β‘ Less per-frame and per-feature work: zoom-only line layers resolve their stroke style once per tile instead of per feature, circle paints are built per feature instead of per point, literal list properties (fonts, offsets, dash arrays) are converted once at parse time, each symbol's text style is re-evaluated only when the zoom changes instead of every frame, the label collision index no longer allocates an iterator per collision box, curve-safety of label text is computed once per symbol instead of re-scanned per frame, the label pass sorts the per-frame symbol list in place instead of copying it first, and grid maintenance (visible-key enumeration and sorting) is skipped on the ~95% of gesture frames where the integer tile bounds are unchanged.
- π Coalesced tile loads no longer inherit the first requester's cancellation: a display tile disposed mid-flight (panned away, zoomed past) used to cancel the shared load and leave every other tile waiting on it permanently blank until recreated. Loads now poll a token joined over all waiters β cancelled only when nobody is left β in both tile stores and both network providers.
- π Tiles whose source failed transiently (network loss, 5xx) now retry: visible tiles re-request missing sources a bounded number of times, just past the stores' failure throttle, instead of staying blank or partial until the zoom level changes. Already-rendered content is not re-rasterized when a retry recovers nothing.
- π A raster tile load could return no image despite succeeding when the decoded image was evicted by a concurrent load in the same instant; callers now receive a handle that cannot be evicted out from under them.
- π On devices where isolate spawning fails, the decode pipeline now falls back to the event loop permanently instead of hanging every tile enqueued after the first batch; a decode isolate killed by the OS no longer strands its tile and shrinks the pool β it is replaced and the tile retried.
- π Turning
showLabelson now lays out labels on the tiles already on screen (they used to appear only on tiles loaded afterwards), and changingspritesre-rasterizes live tiles so fill/line patterns from the old atlas don't linger. Neither refresh refetches or re-fades anything. - π MVT decoding: a packed field ending in a continuation byte now
fails as a decode error instead of silently reading past the field β
which could render corrupt geometry β or crashing at the buffer end;
and negative
int_valueproperties (10-byte varints) decode to the correct negative number on web instead of a garbage positive one. - π The label painter's text and glyph caches now dispose their
TextPainters on eviction and teardown instead of leaking the native paragraph memory (flagged under Flutter leak tracking; panning across label-dense areas grew it unboundedly). - π Legacy
intervalfunctions evaluated exactly on a middle stop returned the previous band's output (wrong style values at every integer zoom boundary); they now select the greatest stop β€ input, matching MapLibre. - π Legacy
identityfunctions now return the feature property's raw value; they β like every stop-less legacy function β used to be silently compiled to a constant raw JSON map, making typed paint properties fall back to their spec defaults with no warning. Uncompilable function shapes now degrade with a parser warning. - π A tile property carrying
Infinityno longer crashes the layer when stringified into atext-fieldtoken. - π
MemoryVectorTileProviderno longer defaults every instance to the samecacheKey: two providers bundling different regions shared decoded-tile and disk cache entries, rendering one region's tiles inside the other. The default key is now derived from the tile set (deterministic, so identical data still shares); pass an explicitcacheKeyto control sharing. Disk entries cached under the old constant key are re-fetched once. - π
VectorTileLayer.clearMemoryCacheduring an open map no longer orphans the caches that in-flight loads keep refilling β refilled entries used to be unreachable by every later clear (a boundedui.Imageleak, worst on exactly the memory-pressure path the call serves).memoryCacheMaxBytesnow actually applies when the shared cache for a source already exists (latest layer wins, including viadidUpdateWidget); it used to be fixed by whichever layer mounted first in the process. - π¦ pub.dev topics retagged for discoverability:
mapreplacesmaps(the topic page the flutter_map ecosystem actually uses) andpmtilesreplacesmvt.
2.1.1 #
Style attribution and MVT decode performance.
- β¨ Style attribution:
Style.attributionsexposes what the style's sources (or the TileJSON they point at) declare, deduplicated and in document order. Attribution is HTML by convention, so eachStyleAttributioncarries the flattenedtextfor a plainTextwidget and thespansβ text plus link β if you want it tappable. A source's ownattributionoverrides its TileJSON's, as in MapLibre. The example app now shows this instead of a hardcoded string. - β‘ Zigzag decoding is branchless again, recovering the ~5% of whole-tile decode time that 2.0.0's web-safe rewrite spent on mispredicted branches. Output is unchanged and still bit-identical between native and web; a latent wrong-sign overflow at the int64 maximum is fixed as a side effect.
2.1.0 #
PMTiles support: Protomaps-style single-file tile archives now work.
- β¨ PMTiles v3 archives: styles with
pmtiles://https://β¦/planet.pmtilessource URLs load out of the box, andPmTilesVectorTileProvider.open(url)serves any archive directly. Tiles come from HTTP range requests β the header and root directory are fetched once, leaf directories are LRU-cached, and each tile costs at most two range requests. Works on all six platforms; on web the archive host must allow ranged CORS requests, and gunzip uses the browser's nativeDecompressionStream. Archives with brotli or zstd internal compression are rejected with a clear error (gzip and uncompressed are supported β Protomaps builds ship gzip). - The source's
minzoom/maxzoomin the style override the archive header, matching the behaviour of TileJSON sources.
2.0.1 #
Real-world style compatibility: ArcGIS/Esri root.json styles now work.
- π Relative tile URL templates from TileJSON documents now resolve
against the TileJSON URL instead of the style URL. ArcGIS vector tile
services (
β¦/VectorTileServer/resources/styles/root.json) declare their source as"url": "../../"with a relativetile/{z}/{y}/{x}.pbftemplate, which previously produced 404s for every tile. - π Relative tile templates no longer break on
{z}/{x}/{y}placeholders: URL resolution percent-encoded the braces to%7Bz%7D, defeating placeholder substitution. - π
{key}is now substituted in TileJSON source URLs ("url": "https://β¦/tiles.json?key={key}"), not only in the style URL and the tile templates. - π The README provider table now lists ArcGIS/Esri and self-hosted
servers as verified (Esri
World_Basemap_v2, MapLibre demo tiles) and documents that Protomapspmtiles://archives are not supported.
2.0.0 #
The layer now runs on Flutter web.
- π Web support: rendering, styles, labels and raster sources work
identically in the browser (CanvasKit/Skwasm renderer β the default
since Flutter 3.29; the removed HTML renderer lacks
Picture.toImageSync). Tile decoding runs on a yielding event-loop queue instead of worker isolates (concurrencyis ignored), and there is no persistent cache on web:cachePath,diskCacheTtl,diskCacheMaximumSizeInBytesandStyleReader(cache: β¦)are no-ops there β tiles and the style bundle rely on the in-memory caches plus the browser's own HTTP cache, so the disk-backed offline behaviour remains native-only. Style, TileJSON, sprite and tile hosts must sendAccess-Control-Allow-Origin(MapTiler, OpenFreeMap and Stadia do). - π₯ Breaking: the
cacheFolderparameter (Future<Directory> Function()?) onVectorTileLayerandStyleReaderis nowcachePath(Future<String> Function()?), so the public API no longer carries adart:iotype. Migration:cacheFolder: () async => dirbecomescachePath: () async => dir.path; if you never passedcacheFolder, nothing changes β cache locations and defaults are identical, and existing on-disk caches are reused as-is. - π MVT varint and zigzag decoding no longer relies on 64-bit bitwise operations, which JavaScript truncates to 32 bits β negative coordinate deltas decoded as huge positive values on web. Native results are bit-identical.
1.2.0 #
Two style features that used to be skipped now render.
- π¨
line-pattern: the sprite is stamped along the line, rotated to the local direction and scaled so its height matches the line width (MapLibre semantics, data-driven patterns included). Per spec the pattern replacesline-colorandline-dasharray;line-opacitystill applies. A missing sprite falls back to the color stroke. - π°οΈ Raster sources inside vector styles (satellite/hybrid imagery,
pre-rendered hillshade tiles):
rasterlayers draw their tiles at the correct position in the layer order.raster-opacity,raster-brightness-min/-max,raster-contrast,raster-saturationandraster-hue-rotatereplicate MapLibre's fragment-shader math (including its factor curves and spin weights). Raster tiles share the tile disk cache, are decoded once and cached process-wide (released byVectorTileLayer.clearMemoryCache()), overzoom past the source maximum, respect the sourcetileSize(512/256) underTileOffset, and render in-memory ancestors as provisional imagery during zoom β the same lifecycle vector tiles get.StyleReaderpicks raster sources up automatically (inlinetilesor TileJSONurl); passstyle.rasterSourcesto the newVectorTileLayer.rasterSourcesparameter.
1.1.0 #
Reopening a map is now nearly instant. Both caches used to die with the layer, so every time a map screen was pushed it started from nothing.
- β‘ The disk cache is awaited rather than raced. It initializes asynchronously β its directory comes from a platform channel β but tiles are requested on the very first frame, and the load path read it as a plain field that was still null. Every map open therefore fetched its opening screenful over the network, with the tiles already on disk. The stores now hold the pending cache and await it.
- β‘ Decoded tiles are cached process-wide instead of per layer, so a
reopened map paints without decoding anything again. Entries are keyed by
source and by the properties the theme reads β two styles over one
source do not share trimmed tiles β and are capped by
VectorTileLayer.memoryCacheMaxBytesper key. They outlive the layer by design;VectorTileLayer.clearMemoryCache()releases them, e.g. from a memory-pressure handler. - β‘
DiskCacheinstances are shared per directory, so a second layer over the same folder reuses an initialized cache instead of setting up its own. - π
VectorTileProvider.cacheKeynow also identifies the in-memory cache. Providers serving different bytes must not share one.
1.0.0 #
First stable release: the public API is now considered settled and will follow semantic versioning from here.
- β¨
icon-color,icon-halo-colorandicon-halo-widthare supported for SDF sprite sheets (see the rendering fix below). - π The map no longer shakes while zooming and no longer slides out from under its labels while dragging. Tile rectangles are now made camera-relative in Dart before they reach the canvas: world pixel coordinates pass 2^24 around zoom 16, and the canvas transform is float32, so absolute coordinates were snapping the imagery onto a grid β 0.25 px at zoom 14, but 4 px at zoom 18 and 16 px at zoom 20. The label pass does its own float64 arithmetic, so the two drifted apart. This is why the artifacts only appeared when heavily zoomed in.
- π SDF sprites are rendered correctly instead of as dark blobs.
Sheets flagged
"sdf": truestore a distance field in the alpha channel over flat RGB; they were being blitted directly, which painted the raw field. They are now thresholded and tinted, adding support foricon-color,icon-halo-colorandicon-halo-width. Ordinary sprites are unaffected. Dark MapLibre styles are typically entirely SDF, which is why icons looked correct in light styles and wrong in dark ones. - π
text-opacityis now applied. Labels the style fades out are also no longer laid out at all, so they stop reserving collision space and suppressing the visible labels around them.
0.4.1 #
Nothing on screen disappears before its replacement is ready β the blanking, flashing and dropped frames around zoom changes are gone.
- π The map no longer blanks and reloads moments after first paint: the async disk-cache initialization now attaches to the running tile stores instead of rebuilding them (which disposed every rendered tile).
- π No more flash on zoom level changes: the previous level's imagery is kept until the new tiles are rasterized and fully faded in β it used to be dropped mid-fade, dipping to the background color.
- π Labels no longer blink out on zoom level changes: the previous level's labels keep drawing wherever the new level has no label data yet (current-level labels win collisions as they arrive).
- β‘ Smoother zooming: tiles are rasterized through a priority queue with a per-frame time budget (viewport centre first) instead of all in one frame. With warm caches, a zoom change used to rasterize the entire grid synchronously in a single frame, dropping frames.
0.4.0 #
Places you have already visited keep working without a network.
- βοΈ Offline support for recently visited places:
StyleReadernow caches the style bundle (style.json, TileJSON, sprites) on disk with stale-while-revalidate: the cached copy is served instantly β including fully offline β and refreshed in the background once older thanrefreshAfter(12 h default). Opt out withcache: false.- Tiles: when a network fetch fails, an expired disk-cache entry is
served instead of a blank tile.
diskCacheTtlis now a freshness window β stale tiles are kept (up to the size cap, evicted oldest first) as the offline fallback instead of being deleted by age. - The default cache folder moved from the system temp directory to the application support directory, which the OS doesn't purge. Existing temp-dir caches are simply abandoned and refetched once.
0.3.0 #
Road labels now follow the curve of their line.
- β¨ Curved line text: road labels now follow their line glyph by glyph,
with MapLibre semantics β
text-max-angle(labels on too-sharp bends are not placed),text-keep-upright(reading direction flips so text is never upside-down) andtext-rotation-alignment: viewport(horizontal shield text). Glyph spacing/kerning comes from the full string layout; per-glyph collision boxes; nearly straight labels are drawn as a single rotated string for speed. - Scripts with contextual shaping (Arabic, Indic, Thai, β¦) fall back to straight placement so glyphs are never mis-joined; labels longer than their line are dropped instead of sticking out (matches MapLibre).
- Along-line labels no longer wrap to multiple lines.
- β¬οΈ
latlong2constraint widened to>=0.9.1 <0.11.0, so 0.10.x resolves. Still compatible with flutter_map 8.2.0+, which pins^0.9.1until 8.3.1. - π§Ή Source reformatted with the Dart formatter (no behaviour change).
0.2.0 #
Denser labels and pattern fills, plus a parser fix that quietly broke font stacks and label offsets in most real styles.
- β¨
text-variable-anchor+text-radial-offset: labels try alternate anchors before being dropped on collision β dense areas keep far more POI and place labels visible (matches MapLibre behaviour). - β¨
fill-pattern(andfill-extrusion-pattern): polygon fills render repeating sprite patterns (wetlands, glaciers, pedestrian zones), world-grid aligned across tile seams; missing sprites fall back to the color fill. - π Bare literal arrays in style JSON (
text-offset: [0, 0.6],text-font: [...],line-dasharray: [2, 1], anchor lists) were mis-parsed as expressions and silently fell back to defaults β affected font stacks and label offsets in most real styles. LabelPainter.paintnow returns the symbols that were actually drawn.
0.1.0 #
Initial release. π
VectorTileLayerfor flutter_map β₯ 8 rendering MapLibre/Mapbox GL styles from MVT vector tile sources.- Self-contained: built-in MVT decoder, style/expression engine (modern expressions + legacy filters/functions/tokens), sprite support.
- GPU-resident per-tile rasterization (
Picture.toImageSync) with fade-in, ancestor-tile retention and provisional rendering from cached parent tiles (no white flashes on zoom). - Screen-space label pass with global cross-tile collision, upright text under rotation, halo and sprite icon support.
- Worker-isolate decode pipeline with priority ordering and silent cancellation.
- Deterministic LRU memory caches (byte budgets) and TTL + size-capped disk cache.
StyleReaderfor style.json / TileJSON / sprites with{key}substitution, tolerant of real-world provider quirks (MapTiler, OpenFreeMap, Stadia, ArcGIS, Protomaps).TileOffset.maplibredefault for correct 512px-convention rendering.
