maposter 0.2.0 copy "maposter: ^0.2.0" to clipboard
maposter: ^0.2.0 copied to clipboard

Turn a city name into a minimalist OpenStreetMap poster: geocoding, Overpass map data, Mercator projection, CustomPainter rendering, and PNG/PDF export.

πŸ—ΊοΈ maposter #

pub package pub points license: MIT

Turn any city in the world into a beautiful, minimalist map poster β€” straight from OpenStreetMap data.

maposter is a framework-agnostic Flutter package that takes a city name and produces a print-ready map poster. It geocodes the place, pulls roads, water and parks from OpenStreetMap, projects them with Web Mercator, renders them onto a Canvas, and exports to high-resolution PNG or PDF. Seventeen hand-tuned themes are bundled β€” and you can add your own in a single line.

No state-management or storage dependency is forced on you, and caching is fully pluggable.

Real posters generated with maposter β€” each in a different bundled theme:

Kolkata, India β€” monochrome_blue theme
Kolkata Β· monochrome_blue
Dubai, UAE β€” midnight_blue theme
Dubai Β· midnight_blue
Bangalore, India β€” forest theme
Bangalore Β· forest
Alexandria, Egypt β€” autumn theme
Alexandria Β· autumn

✨ Features #

  • 🌍 Geocoding β€” city + country β†’ coordinates via OpenStreetMap Nominatim, rate-limited per policy.
  • πŸ›£οΈ Map data β€” roads, water bodies and parks fetched from the Overpass API and parsed off the main thread in an isolate.
  • πŸ“ Web Mercator projection β€” accurate lat/lon β†’ screen math, with aspect-ratio-aware framing.
  • 🎨 17 bundled themes β€” from noir to neon_cyberpunk β€” plus first-class custom themes.
  • πŸ–ΌοΈ Canvas rendering β€” a ready-to-use CustomPainter you can drop into any widget tree.
  • πŸ“€ PNG & PDF export β€” print-ready output at configurable DPI, with a built-in memory guard.
  • 🧩 Framework-agnostic β€” plain Dart API + a single MaposterEngine facade. Works with Riverpod, BLoC, Provider, or nothing at all.
  • πŸ’Ύ Pluggable caching β€” ships with an in-memory cache; bring your own (Hive, sqflite, files…) via one interface.
  • πŸ›‘ Cancellable requests β€” pass a CancelToken to abort in-flight map fetches.
  • πŸ–₯️ Every platform β€” Android, iOS, web, macOS, Windows and Linux.

πŸ“¦ Installation #

flutter pub add maposter

Or add it to your pubspec.yaml (see the latest version):

dependencies:
  maposter: <latest-version>

πŸš€ Quick start #

import 'package:flutter/widgets.dart';
import 'package:maposter/maposter.dart';

final engine = MaposterEngine(
  // userAgent is REQUIRED β€” OpenStreetMap's Nominatim policy needs a real,
  // identifying agent (your app name + a contact).
  const MaposterConfig(userAgent: 'myapp/1.0 (com.example.myapp)'),
);

// 1. City name β†’ coordinates
final coords = await engine.geocode('Kolkata', 'India');

// 2. Coordinates β†’ map geometry (roads, water, parks)
final data = await engine.fetchMapData((coords.latitude, coords.longitude));

// 3. Pick a theme
final theme = await engine.getTheme('noir');

// 4. Build the painter
final painter = engine.buildPainter(
  mapData: data,
  theme: theme,
  cityName: 'Kolkata',
  countryName: 'India',
  latitude: coords.latitude,
  longitude: coords.longitude,
);

// 5a. Render it live in a widget tree:
//     CustomPaint(painter: painter)

// 5b. …or export it:
final png = await engine.exportPng(painter, const Size(1200, 1600));
final pdf = await engine.exportPdf(png, const Size(1200, 1600));

🧭 Step by step #

Configure the engine #

MaposterConfig carries everything the engine needs to talk to OpenStreetMap. Only userAgent is required:

final engine = MaposterEngine(
  const MaposterConfig(
    userAgent: 'myapp/1.0 (com.example.myapp)',
    defaultRadiusMeters: 18000, // how much of the city to capture
  ),
);

Geocode a place #

final coords = await engine.geocode('Paris', 'France');
print('${coords.displayName} @ ${coords.latitude}, ${coords.longitude}');

Fetch map data #

final data = await engine.fetchMapData(
  (coords.latitude, coords.longitude),
  radiusMeters: 20000, // optional; defaults to config.defaultRadiusMeters
);
print('${data.roads.length} roads, '
      '${data.waterFeatures.length} water, '
      '${data.parkFeatures.length} parks');

Render & export #

buildPainter returns a MaposterPainter (a CustomPainter). Render it live with CustomPaint, or rasterize it:

// 3Γ— scale β‰ˆ 300 DPI for a 1200Γ—1600 poster
final png = await engine.exportPng(painter, const Size(1200, 1600), scale: 3.0);
final pdf = await engine.exportPdf(png, const Size(1200, 1600));

🎨 Themes #

Seventeen themes ship with the package:

noir blueprint autumn emerald
forest ocean sunset terracotta
warm_beige midnight_blue monochrome_blue neon_cyberpunk
japanese_ink pastel_dream copper_patina gradient_roads
contrast_zones
final all = await engine.getAllThemes(); // List<MapTheme>
final noir = await engine.getTheme('noir');

Custom themes #

Define your own in code with hex strings β€” they merge with the built-ins. Reuse a built-in id to override it:

final engine = MaposterEngine(
  config,
  customThemes: [
    MapTheme.fromHex(
      id: 'brand',
      name: 'Brand',
      bg: '#0A0A0A',
      text: '#E0FF00',
      gradientColor: '#0A0A0A',
      water: '#111111',
      parks: '#1A1A1A',
      roadMotorway: '#E0FF00',
      roadPrimary: '#C8E000',
      roadSecondary: '#96A800',
      roadTertiary: '#647000',
      roadResidential: '#323800',
      roadDefault: '#647000',
    ),
  ],
);

final mine = await engine.getTheme('brand'); // your theme

πŸ’Ύ Caching #

The engine caches geocoding and map-data results through a CacheStore. By default it uses an in-memory cache (InMemoryCacheStore) that lasts for the life of the process. Provide your own to persist results across launches and avoid re-hitting the network:

import 'package:hive_flutter/hive_flutter.dart';
import 'package:maposter/maposter.dart';

class HiveCacheStore implements CacheStore {
  HiveCacheStore(this._box);
  final Box<dynamic> _box;

  @override
  Future<String?> read(String key) async => _box.get(key) as String?;

  @override
  Future<void> write(String key, String value) => _box.put(key, value);
}

// …after opening the box:
final engine = MaposterEngine(config, cache: HiveCacheStore(myBox));

Want to disable caching entirely? Use NoopCacheStore().


βš™οΈ Configuration reference #

MaposterConfig options:

Option Default Description
userAgent required Identifying UA sent to Nominatim (its policy requires a real one).
defaultRadiusMeters 18000 Default capture radius when none is passed to fetchMapData.
nominatimBaseUrl https://nominatim.openstreetmap.org Geocoding endpoint.
overpassBaseUrl https://overpass-api.de/api/interpreter Map-data endpoint.
nominatimMinGap 1100 ms Minimum gap between geocoding requests.
overpassMinGap 2 s Minimum gap between Overpass requests.
connectTimeout 30 s Connection timeout.
receiveTimeout 30 s Receive timeout (Nominatim).
overpassReceiveTimeout 60 s Receive timeout for Overpass (queries can be slow).

πŸ›‘ Cancellation #

fetchMapData accepts a CancelToken (re-exported from dio) so you can abort an in-flight request β€” e.g. when the user starts a new search:

final token = CancelToken();
final future = engine.fetchMapData(center, token: token);
// …later:
token.cancel();

⏳ Progress reporting #

The first fetch for a city runs several slow, rate-limited Overpass queries, so a bare spinner can feel stuck. Pass onProgress to fetchMapData to drive a staged progress UI β€” it fires as each stage (roads β†’ water β†’ parks) begins, and flags stages served from cache (which resolve instantly):

await engine.fetchMapData(
  center,
  onProgress: (p) {
    // p.stage is MapDataStage.roads | .water | .parks
    // p.index / p.total β†’ e.g. 2 of 3;  p.fromCache β†’ instant?
    print('${p.stage.name} (${p.index + 1}/${p.total})');
  },
);

See example/ for a labeled progress bar built on this.


🧯 Error handling #

The engine throws a sealed AppException. Switch over its subtypes for precise handling, or just show message:

try {
  final coords = await engine.geocode(city, country);
} on GeocodingException catch (e) {
  // no match for that place
} on NetworkException catch (e) {
  // e.statusCode is available
} on AppException catch (e) {
  showError(e.message);
}

Subtypes: NetworkException, GeocodingException, OverpassException, CacheException, RenderException, AssetException.


πŸ–₯️ Platform support #

Android iOS Web macOS Windows Linux
βœ… βœ… βœ… βœ… βœ… βœ…

πŸ“± Example app #

A complete demo app β€” city input, theme picker, live preview and an export sheet (save to gallery / share PNG / share PDF) β€” lives in example/. It shows how to wire maposter into a real app (here with Riverpod + Hive-backed caching).


πŸ—ΊοΈ Attribution & usage policy #

Map data is Β© OpenStreetMap contributors, available under the Open Database License (ODbL). The painter already renders an β€œΒ© OpenStreetMap contributors” credit onto every poster β€” please keep it visible.

This package calls the public Nominatim and Overpass endpoints, which enforce usage policies (rate limits and a required, identifying User-Agent). Always set a real userAgent in MaposterConfig. For heavy or production traffic, point nominatimBaseUrl / overpassBaseUrl at your own hosted instances.


🀝 Contributing #

Issues and pull requests are welcome over on GitHub. If you build something with maposter, I'd love to see it!

πŸ“„ License #

MIT Β© Sitam Sardar

2
likes
140
points
21
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Turn a city name into a minimalist OpenStreetMap poster: geocoding, Overpass map data, Mercator projection, CustomPainter rendering, and PNG/PDF export.

Repository (GitHub)
View/report issues

Topics

#maps #openstreetmap #poster #cartography

License

MIT (license)

Dependencies

dio, flutter, freezed_annotation, google_fonts, json_annotation, pdf

More

Packages that depend on maposter