@tinkatinka/static-map

Render static map images

Usage no npm install needed!

<script type="module">
  import tinkatinkaStaticMap from 'https://cdn.skypack.dev/@tinkatinka/static-map';
</script>

README

static-map

A node library to generate static map images.

Acknowledgements

The code is heavily inspired by the python-based library staticmap.

To generate the final image node-canvas is being used.

Installation

$ npm install @tinkatinka/static-map

Usage

import { StaticMap } from '@tinkatinka/static-map';
const map = new StaticMap(...);

The constructor can be called with an optional StaticMapOptions object, overriding the defaults:

{
  width: number;            // width of the image in pixels [default: 512]
  height: number;           // height of the image in pixels [default: 512]
  padding: number;          // padding in pixels [default: 0]
  paddingX?: number;        // horizontal padding (overrides `padding`)
  paddingY?: number;        // vertical padding (overrides `paddings`)
  paddingLeft?: number;     // left padding (overrides `paddingX`)
  paddingRight?: number;    // right padding (overrides `paddingX`)
  paddingTop?: number;      // top padding (overrides `paddingY`)
  paddingBottom?: number;   // bottom padding (overrides `paddingY`)
  extent?: LatLngBounds;    // extent of the map [default: `undefined`]
  scaling: boolean;         // scale image to match size and padding exactly [default: `true`]
  tileURL: string;          // template string for tile URL [default: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png']
  tileSize: number;         // size of tiles [default: 256]
  tileMaxZoom: number;      // maximum zoom available for tiles [default: 20]
  tileCache?: string;       // path to a cache directory for tiles [default: `undefined`]
  backgroundColor?: string; // image background color (CSS string) [default: `undefined`]
  grayscale: boolean;       // apply a grayscale filter to map tiles (not overlays) [default: `false`]
}

Note: Either an explicit extent of the map or at least one overlay (see below) must be defined, otherwise the image generation will fail.

Types

LatLng

{
  lat: number; // latitude
  lng: number; // longitude
}

LatLngBounds

{ 
  min: LatLng; // minimum point
  max: LatLng; // maximum point
}

Overlays

Lines

.addLine(src: LatLng, dst: LatLng, options?: Partial<StaticMapLineOptions>);

.addLines(points: LatLng[], options?: Partial<StaticMapLineOptions>);

Options:

interface StaticMapLineOptions {
  strokeStyle: string;                  // css stroke style [default: 'black']
  lineWidth: number;                    // line width in pixels [default: 1.0]
  lineCap: 'butt' | 'round' | 'square'  // [default: 'round']
  lineJoin: 'bevel' | 'round' | 'miter' // [default: 'round']
}

Circles

.addCircle(center: LatLng, options?: Partial<StaticMapCircleOptions>);

Options:

interface StaticMapCircleOptions {
  radius: number;       // in pixels [default: 5.0]
  strokeStyle?: string; // css stroke style or undefined for no stroke [default: 'black']
  lineWidth: number;    // line width in pixels [default: 1.0]
  fillStyle?: string;   // css fill style or undefined for no fill [default: 'rgba(0, 0, 0, 0.3)']
}

Text

.addText(text: string, anchor: LatLng, options?: Partial<StaticMapTextOptions>);

Options:

interface StaticMapTextOptions {
  px: number;           // padding x [default: 0]
  py: number;           // padding y [default: 0]
  maxWidth?: number;    // text max width [default: undefined]
  direction: string;    // default: 'ltr'
  font: string;         // default: '12px sans-serif'
  textAlign: string;    // default: 'center'
  textBaseline: string; // default: 'middle'
  strokeStyle?: string; // css stroke style or undefined for no stroke [default: undefined]
  lineWidth: number;    // line width in pixels [default: 1.0]
  fillStyle?: string;   // css fill style or undefined for no fill [default: 'black']
}

Images

.addImage(src: string, bounds: LatLngBounds);

Output

Can be obtained via async functions

.renderToDataURL(): Promise<string>

.renderToBuffer(): Promise<Buffer>

.renderToCanvas(): Promise<Canvas>

All of them will fail if the .extent of the map is not explicitly defined or cannot be computed (because there are no overlays).

Examples

with explicit extent

import { StaticMap } from '@tinkatinka/static-map';
const map = new StaticMap({
  width: 512,
  height: 512,
  extent: {
    min: { lat: 52.4, lng: 13.3 },
    max: { lat: 52.6, lng: 13.5 }
  }
});
const src = await map.renderToDataURL();
...

with overlay image and computed extent

import { StaticMap } from '@tinkatinka/static-map';
const map = new StaticMap({
  width: 512,
  height: 512,
  paddingX: 32,
  paddingY: 32,
  tileCache: '/tmp/tiles'
})
  .addImage('https://my.funky.src/image.png', {
    min: { lat: 1.0, lng: 2.0 },
    max: { lat: 2.0, lng: 3.0 }
  });
const buffer = await map.renderToBuffer();
...

rendering to Canvas for custom drawing

import { StaticMap } from '@tinkatinka/static-map'
const map = new StaticMap({
  extent: {
    min: { lat: 52.4, lng: 13.3 },
    max: { lat: 52.6, lng: 13.5 }
  },
  grayscale: true,
  tileCache: '/tmp/tiles'
});
const canvas = await map.renderToCanvas();
const ctx = canvas.getContext('2d');
ctx.textAlign = 'end';
ctx.textBaseline = 'bottom';
ctx.font = '16px sans-serif';
ctx.fillStyle = 'blue';
ctx.fillText('© OpenStreetMap contributors', canvas.width-8, canvas.height-8);
const buffer = await canvas.toBuffer();
...

Attribution

Depending on which tiles are being used, attribution of the data has to be provided. When using the static map image you are responsible to do so.

E.g. see OpenStreetMap for information regarding the copyright and attribution of OpenStreetMap tiles.