Skip to content
sntranPublic

About

A Phoenix LiveView Component for displaying an interactive map with dynamic data.

Resources

Stars

24 stars

Watchers

2 watching

Forks

Latest commit

 

History

58 Commits

Folders and files

Repository files navigation

LiveMap

A Phoenix LiveView Component for displaying an interactive map with dynamic data.

The library requires Elixir 1.16+, Phoenix 1.8+, and Phoenix LiveView 1.1+.

By rendering the map on the server, it avoids the client-side map libraries for simple mapping needs. Utilizing LiveView, we can also update map data on the server, and let the browser do what it does best—rendering markup.

The map is rendered as an SVG. Raster and standalone SVG sources are emitted as <image> tiles, while Shortbread-compatible vector sources are fetched and decoded on the server into nested SVG tiles. LiveMap progressively injects standalone SVG tiles so page-level CSS can style them; the external image remains the no-JavaScript fallback.

Please consult and follow usage policies of the tile servers.

Usage

A LiveMap can be added to a LiveView by:

<.live_component
  module={LiveMap} id="live-map"
  title="Example Live Map"
  width="800" height="600"
  center="10.4197639,107.1070841" zoom="11"
  rendering-type="vector"
>
  <%# Styles slot %>
  <:style>
    /* CSS custom variables or class selectors to customize map colors */
    :root {
      --live-map-water-fill: #38bdf8;
      --live-map-land-fill: #fef08a;
    }
    .live-map-shortbread-role-building {
      fill: #cbd5e1;
    }
  </:style>

  <%# Optional custom HTML map controls %>
  <:map_control action="pan-up">
    <span class="inline-flex h-6 w-6 items-center justify-center rounded bg-white text-slate-900">↑</span>
  </:map_control>

  <:map_control action="zoom-in">
    <span class="inline-flex h-6 w-6 items-center justify-center rounded bg-white text-slate-900">+</span>
  </:map_control>

  <:map_control action="zoom-out">
    <span class="inline-flex h-6 w-6 items-center justify-center rounded bg-white text-slate-900">-</span>
  </:map_control>

  <:map_control action="fullscreen">
    <span class="inline-flex h-6 w-6 items-center justify-center text-slate-900">⛶</span>
  </:map_control>

  <%# Optional SVG overlays projected from map coordinates %>
  <:polygon
    id="district"
    label="Sample district"
    points={[
      %{latitude: 10.34, longitude: 107.07},
      %{latitude: 10.35, longitude: 107.09},
      %{latitude: 10.33, longitude: 107.11}
    ]}
  />

  <:polyline
    id="route"
    label="Sample route"
    points={[
      %{latitude: 10.34, longitude: 107.07},
      %{latitude: 10.36, longitude: 107.10},
      %{latitude: 10.38, longitude: 107.13}
    ]}
  />

  <%# A single explicit marker %>
  <:marker
    id="harbor"
    position="10.411379,107.136224"
    title="Harbor"
  />

  <%# Add a slot body to <:marker> when you want custom HTML marker UI. %>

  <%# Multiple markers via :for %>
  <:marker
    :for={marker <- @visible_markers}
    id={marker.id}
    position={{marker.latitude, marker.longitude}}
    title={marker.label}
  />
</.live_component>

Run examples/live_maps.exs for a single-file LiveView example powered by Mix.install/1.

Map controls are opt-in: LiveMap renders no navigation buttons by default. Add repeated :map_control slots with an action and HTML content to enable any combination of zoom-in, zoom-out, pan-up, pan-right, pan-down, pan-left, and fullscreen. Pan controls initially render as a compact D-pad launcher. Activating it reveals the directional controls, with zoom in above the right control and zoom out below it. The optional positive step defaults to 1. A zoom step changes one zoom level; a pan step moves half the map width or height at the current zoom.

The fullscreen action toggles a server-rendered, CSS viewport-filling mode and does not require a custom JavaScript hook. Browser-native fullscreen, which also hides browser chrome, requires the client-side Fullscreen API and is therefore outside LiveMap's server-rendered controls.

Use on_bounds_changed when the parent LiveView needs to retain or persist control-driven bounds changes. The server callback receives the map id, triggering action, new center, and new zoom; it can send that value back to the parent process for URL patching or other state updates.

Each :marker slot entry must provide position and title. The optional id is used to generate a stable DOM id. LiveMap only projects and renders the markers it receives; deciding which markers to pass remains the responsibility of the parent LiveView. When the :marker slot body is omitted, LiveMap renders a default SVG marker pin with the marker title exposed through the SVG title. If a body is provided, it must be HTML content; LiveMap wraps it in a <foreignObject> automatically. This keeps the public API decoupled from the internal SVG rendering details while still allowing rich HTML marker UIs. No :let or projected slot assigns are required. You can pass a single marker directly, or emit multiple marker slots with :for.

Like Google's <gmp-map> and <gmp-advanced-marker> elements, center and position accept a "latitude,longitude" string. Elixir callers may also pass a {latitude, longitude} tuple or %{lat: latitude, lng: longitude} map. The map-level latitude and longitude attributes and the marker-level latitude, longitude, and label attributes are deprecated compatibility fallbacks. When both forms are supplied, center, position, and title win.

Custom map controls use HTML content only. LiveMap wraps that content for display inside the SVG control chrome. The old :zoom_in and :zoom_out slots remain as deprecated one-step aliases for the corresponding map-control actions.

Polygon and polyline overlays are projected in map coordinates and rendered as SVG shapes on their own layer. Each :polygon or :polyline slot accepts a points list of %{latitude: ..., longitude: ...} maps, with optional id and label attributes. LiveMap renders default SVG <polygon> and <polyline> elements for these overlays. It drops each point that is less than half a pixel from the point before it at the zoom of the map (the last point stays): a long line at a low zoom has many such points, and they change nothing on the screen.

A :polygon can have holes: holes is a list of rings, each a list of points as points. LiveMap renders a polygon with holes as a <path> with fill-rule="evenodd", one subpath for each ring. The holes move with the points to the copy of the world of the map. A hole with fewer than three points (after the half-pixel rule above) has no area, and is not in the path.

<:polygon
  id="lake"
  points={shore}
  holes={[island]}
  class="fill-sky-500/30"
/>

A :polygon or a :polyline can have a group. The consecutive shapes with the same group are in one <g class="live-map-group" data-live-map-group="...">. Give the group an opacity in CSS, and its shapes become one layer: two shapes that overlap do not make the overlap darker, and shapes that touch show no light line on their edge. LiveView also tracks the shapes of a group apart from the other shapes: a new shape in a group does not move the other shapes in the list, so they are not sent again.

<:polygon :for={tile <- @tiles} id={tile.id} points={tile.points} group="fog" />

[data-live-map-group="fog"] { opacity: 0.45; }

HTML marker example:

<:marker id="harbor" position="10.411379,107.136224" title="Harbor">
  <button class="rounded-full bg-emerald-700 px-3 py-1 text-xs font-semibold text-white">
    Harbor
  </button>
</:marker>

Moving the map

Each layer of the map (the background tiles, the tiles, the shapes and the markers) is in a group with the class live-map-layer. The tiles, the points of the shapes and the markers have their place from a fixed origin, and a CSS transform on the group moves the layer to the view. The origin is the corner of a cell of 220 pixels: up to zoom 12 it is the corner of the world, and at higher zooms the numbers stay small enough for the floats of a browser.

So a new center at the same zoom changes only the transform of the groups, while the center stays in the same cell. The markers and the points of the shapes do not change, and LiveView sends only the new transform. A map that follows a moving point, or a map that a hook pans, sends a small update for each move, not all of the points of its lines again. When new tiles come into the view, LiveView sends the list of the tiles again. Each tile has an id from its zoom and its place, so the browser keeps the tiles that stay in the view, with their images.

The id of each group has the zoom and the cell of the origin (MAP_ID-tile-layer-ZOOM-X-Y). A new zoom or a new cell gives new groups. This lets an app animate a move of the map with CSS only, with no animation of a zoom:

#trip-map .live-map-layer {
  transition: transform 250ms linear;
}

Animate the markers that move too (.live-map-marker has a CSS transform for its place), with the same time, so that they move with the map.

Rendering Type and Tile Sources

Set rendering-type to the raster|vector enum to select LiveMap's built-in OpenStreetMap raster or Shortbread vector source. LiveMap keeps raster as the default for backward compatibility:

<.live_component
  module={LiveMap}
  id="live-map"
  center="10.4197639,107.1070841"
  zoom={11}
  rendering-type="raster"
/>

Use rendering-type="vector" to switch to the built-in OSM vector source without configuring tile_source. The optional Req dependency is required for vector rendering.

tile_source remains supported for backward compatibility and custom tile servers. If both attributes are supplied, rendering-type selects the built-in source.

Tile source type is inferred from the URL by default: .mvt, .pbf and .pmtiles URLs are treated as MVT sources, .svg URLs are treated as standalone SVG sources, and everything else is treated as raster.

Raster and Shortbread MVT sources support overzoom above their max_zoom by cropping the appropriate parent tile. Shortbread MVT sources are server-rendered:

<.live_component
  module={LiveMap}
  id="live-map"
  center="10.4197639,107.1070841"
  zoom={15}
  tile_source={%{
    url: "https://vector.openstreetmap.org/$VERSION/{zoom}/{x}/{y}.mvt",
    version: "shortbread_v1",
    max_zoom: 14,
    headers: [{"x-example-header", "demo"}]
  }}
/>

Standalone SVG tile sources

Point tile_source at a display-ready SVG tile endpoint to keep tile bytes out of LiveView diffs:

<.live_component
  module={LiveMap}
  id="svg-map"
  center="10.4197639,107.1070841"
  zoom={11}
  base-style="detailed"
  styles={@map_styles}
  tile_source={%{
    url: "/vector/{zoom}/{x}/{y}.svg",
    attribution: "© OpenStreetMap contributors"
  }}
/>

Do not also set rendering-type: when present, it intentionally selects one of LiveMap's built-in sources instead of tile_source. Root-relative URLs are accepted for raster and SVG browser-loaded tiles. Server-fetched MVT sources must remain absolute HTTP(S) URLs.

LiveMap's built-in runtime colocated hook puts the SVG of each tile into the page. It uses the Cache API of the browser according to the HTTP freshness headers, and scopes the SVG IDs. No asset import or custom hook setup is necessary.

The browser downloads each tile one time:

  • The first render of the page (for a browser with no JavaScript yet) shows each tile as an <image>. The hook waits for this image, then takes the same response from the HTTP cache.
  • The tiles of a later render (over the socket) have no <image>. The hook downloads them.

Give the SVG tiles an HTTP cache lifetime (Cache-Control with max-age): with no-store, the hook downloads a tile of the first render a second time. With no JavaScript, each tile stays an image. When the hook cannot put a tile into the page (a request that failed, an SVG that is not valid), the tile shows as an image.

Security: Injected SVG is trusted application code. LiveMap deliberately does not sanitize it; elements, attributes, scripts, styles, and external references are preserved apart from collision-safe fragment rewriting. Never point an SVG tile source at content an untrusted party can control.

Strict script-src Content Security Policies must supply the same per-response nonce used by the application:

<.live_component
  module={LiveMap}
  id="svg-map"
  tile_source={%{url: "/vector/{z}/{x}/{y}.svg"}}
  script_csp_nonce={@script_csp_nonce}
/>

Cross-origin SVGs need CORS permission for injection even when the browser can display them as an image. When CORS stops the hook, the tile shows as an image. Browser image requests cannot apply tile_source.headers; use a same-origin proxy such as LiveMap.VectorTile.Plug when upstream credentials or headers are required.

Optional MVT-to-SVG Plug

Applications without an existing SVG tile service can mount the included Plug:

# router.ex
forward "/vector", LiveMap.VectorTile.Plug,
  source: LiveMap.Tile.default_vector_source(),
  base_style: "detailed",
  styles: [],
  max_display_zoom: 22,
  max_age: 86_400,
  compress: true

The route accepts GET and HEAD at /{zoom}/{x}/{y}.svg, validates tile coordinates, fetches only the MVT source fixed in the router configuration, and returns standalone SVG with Cache-Control and ETag validators. Gzip content negotiation is enabled by default, with Vary: Accept-Encoding and a representation-specific ETag; set compress: false when a reverse proxy should own compression instead. Above the source's max_zoom, the Plug fetches and crops the appropriate parent while styling labels for the requested display zoom. The optional Req dependency is required.

The path data of an SVG tile has relative commands, with 3 decimals of the tile (0.26 px of a tile of 256 px), and with 4 decimals above the max_zoom of the source. With brotli, the tiles of a view were 2 to 3 times smaller than with absolute commands and 4 decimals.

The Plug does not retain rendered tiles in process. Applications can place it behind their preferred reverse-proxy, CDN, or Plug cache. A wrapper Plug can also initialize LiveMap.VectorTile.Plug once and call it after checking its own cache.

The Plug's base_style and styles produce the complete no-JavaScript tile. Pass the same choices to LiveMap when the injected result should have matching page-level overrides. Use versioned endpoint URLs whenever a long-lived style or source profile changes.

Tiles in the schema of OpenMapTiles

The CSS and the styles of LiveMap know the layers of Shortbread. A source with schema: "openmaptiles" takes the tiles of OpenMapTiles, for example the tiles of OpenFreeMap: LiveMap.MVT.OpenMapTiles gives each feature the layer and the properties of Shortbread before the render.

forward "/vector", LiveMap.VectorTile.Plug,
  source: %{
    type: :mvt,
    url: "https://tiles.openfreemap.org/planet/20261004_113936_pt/{z}/{x}/{y}.pbf",
    max_zoom: 14,
    schema: "openmaptiles"
  },
  base_style: "detailed"

The conversion keeps the water, the land cover, the land use, the roads, the railways, the ferries, the buildings, the borders of the countries and of the provinces (admin levels 2 and 4), and the names of the places and of the water. The name of a place is its Latin name (name:latin) when the tile has one. OpenMapTiles has no population: the rank of a city gives it the priority of a large city. The conversion drops the points of interest, the house numbers and the names of the roads.

The URL of the tiles of OpenFreeMap has the date of its data, and changes each week: get it from the TileJSON of OpenFreeMap (https://tiles.openfreemap.org/planet), and get it again each day.

Tiles in a PMTiles archive, and the schema of Protomaps

A PMTiles archive (version 3) has all the tiles of a map in one file. A source with a URL that ends in .pmtiles reads its tiles from the archive with LiveMap.PMTiles: the URL has no {z}, {x} and {y}. The URL is an http or https URL of a server that gives ranges (for example R2, S3, or the builds of Protomaps), or a file:// URL of a local file.

A source with schema: "protomaps" takes the tiles of the Protomaps basemap (version 4): LiveMap.MVT.Protomaps gives each feature the layer and the properties of Shortbread before the render.

forward "/vector", LiveMap.VectorTile.Plug,
  source: %{
    url: "https://build.protomaps.com/20261019.pmtiles",
    max_zoom: 15,
    schema: "protomaps"
  },
  base_style: "detailed"

The reader reads only the parts of the archive that a tile needs, each with a request of a range:

  1. For the first tile, the header and the root directory (the first 16 KiB of the file).
  2. For a tile in a new part of the map, a leaf directory (about 115 KB for the planet).
  3. For each tile, the data of the tile.

The reader keeps the header, the root directory and the last 4 leaf directories in an ETS table of the application :live_map. A leaf directory of the planet uses about 1.7 MB of memory. To change the number of leaf directories:

config :live_map, :pmtiles_leaf_cache, 8

Each request after the first one has the ETag of the file in If-Match. When the file at the URL changes, the server answers 412: the reader forgets the old file and reads the tile from the new file. A server that does not give ranges causes the error :no_ranges. The reader knows the tiles of type MVT, with no compression or with gzip.

The conversion keeps the water, the land cover, the land use, the roads, the railways, the ferries, the aerial ways, the piers, the buildings, the borders of the countries and of the provinces (kind_detail 2 and 4), and the names of the places and of the water. The name of a place in a script that is not Latin is its English name (name:en) when the tile has one. The first zoom of the label of a country or of a province gives its priority. The conversion drops the land under the map (earth), the points of interest and the house numbers.

Protomaps makes a new build of the planet each day, at https://build.protomaps.com/YYYYMMDD.pmtiles (about 139 GB). The list of the builds is at https://build-metadata.protomaps.dev/builds.json. Get the newest key with a version 4.x from the list, and get the list again each day: an old build can go. Before you read the tiles directly from build.protomaps.com in production, read the terms of Protomaps. A copy of a build in your bucket, or an extract of an area (pmtiles extract), works with the same source.

Large tiles at low zooms

LiveMap reads all the data of a tile before the render, also the labels that its CSS hides at that zoom. Some sources have very large tiles at low zooms. The size of the data of the same tiles, with no compression, on 9 October 2026:

Source 2/3/1 2/2/1 4/12/7 6/50/28
OpenFreeMap (OpenMapTiles) 1.3 MB 1.6 MB 474 KB 229 KB
vector.openstreetmap.org (Shortbread) 138 KB 431 KB 267 KB 302 KB
VersaTiles (Shortbread) 64 KB 99 KB 63 KB 73 KB
Protomaps (basemap v4, build 20261019) 85 KB 179 KB 79 KB 90 KB

A tile of OpenFreeMap at zoom 2 took LiveMap 1 to 4 seconds on a laptop, nearly all of it to read the data. In a VM in WebAssembly (a Durable Object of Cloudflare Workers), it took 12 to 55 seconds. A map of the world needs about 12 tiles at the same time: half of them failed. In the same tile, 1,226 of the 1,279 labels were the names of states and provinces, which the CSS shows only from zoom 7: 412 KB of an SVG of 587 KB. A tile of VersaTiles at zoom 2 took about 30 ms.

For a map of the world, get the tiles of the low zooms from a source with small tiles. A Plug in front of LiveMap.VectorTile.Plug can choose the source by the zoom of the path:

defmodule MyAppWeb.VectorTiles do
  @behaviour Plug

  # Zoom 0 to 5: the small Shortbread tiles of VersaTiles.
  @world %{type: :mvt, url: "https://tiles.versatiles.org/tiles/osm/{z}/{x}/{y}", max_zoom: 14}

  @impl true
  def init(opts) do
    %{
      world: LiveMap.VectorTile.Plug.init(Keyword.put(opts, :source, @world)),
      roads: LiveMap.VectorTile.Plug.init(opts)
    }
  end

  @impl true
  def call(%Plug.Conn{path_info: [zoom | _]} = conn, plugs) do
    case Integer.parse(zoom) do
      {zoom, ""} when zoom <= 5 -> LiveMap.VectorTile.Plug.call(conn, plugs.world)
      _other -> LiveMap.VectorTile.Plug.call(conn, plugs.roads)
    end
  end

  def call(conn, plugs), do: LiveMap.VectorTile.Plug.call(conn, plugs.roads)
end

Give the attribution of each source on the map.

Built-in vector style

Vector maps use an SVG adaptation of the open source VersaTiles Colorful style for Shortbread tiles out of the box (base-style="colorful"). The defaults include its land, water, road, building, boundary, and label palette plus a conservative label policy: country labels appear first, followed by capitals, cities, towns, and smaller places as the map zooms in. State/region labels are held back until zoom 7, and dense address and point-of-interest layers are hidden. Shortbread's English name is preferred when one is available.

Use base-style="detailed" for a denser overview closer to a conventional Google-style basemap. It strengthens the road and administrative-boundary hierarchy, distinguishes more land-cover classes, shows state/region labels at their earliest useful source zoom, and promotes larger regions plus high-population capitals, cities, and towns. Overview and regional zooms use progressive label-density tiers based on Shortbread area and population metadata. The preset is still server-rendered SVG, so it cannot add details that are absent from the source tile or perform MapLibre/Google-style global label collision detection.

Use base-style="physical" with vector rendering for a Google-like physical overview. This hybrid preset renders an NPS Natural Earth physical raster underlay beneath the detailed Shortbread roads, boundaries, labels, and LiveMap overlays:

<.live_component
  module={LiveMap}
  id="physical-map"
  center="29.7604,-95.3698"
  zoom={5}
  rendering-type="vector"
  base-style="physical"
/>

The built-in underlay uses Esri's World Physical Map, whose source is the U.S. National Park Service. It stays fully visible through zoom 6, fades across levels 7–9, crops its maximum zoom-8 parent tiles at level 9, and is removed at zoom 10 so the normal detailed vector fills take over. Source attribution is rendered in the lower-left corner.

To use another physical or terrain service, set background-tile-source to an absolute raster tile template. Attribution metadata may live on the source or be overridden with the component's attribution and attribution-url attributes:

<.live_component
  module={LiveMap}
  id="custom-physical-map"
  rendering-type="vector"
  base-style="physical"
  background-tile-source={%{
    url: "https://tiles.example.com/physical/{z}/{x}/{y}.jpg",
    max_zoom: 8,
    attribution: "Example physical tiles",
    attribution_url: "https://tiles.example.com/terms"
  }}
/>

The selected base-style rules are applied before styles, so a Google Maps style JSON from a service such as Snazzy Maps can recolor the map or explicitly show and hide features:

styles = "priv/map_style.json" |> File.read!() |> Jason.decode!()

<.live_component
  module={LiveMap}
  id="styled-map"
  center="10.4197639,107.1070841"
  zoom={11}
  rendering-type="vector"
  base-style="detailed"
  styles={styles}
/>

LiveMap supports the common Google style fields used for feature visibility, color, and stroke weight. The CSS custom properties shown in the main usage example remain available for smaller overrides without a style JSON.

The tile source URL may use {zoom} or {z}, plus {x}, {y}, {version}, and $VERSION placeholders. version is only required when the URL contains a version placeholder. Absolute HTTP(S) URLs are accepted for every source; root-relative URLs are also accepted for browser-loaded raster and SVG sources.

If you use vector sources from another application, include the optional Req dependency there as well:

def deps do
  [
    {:live_map, "~> 0.0.1"},
    {:req, "~> 0.6.2"}
  ]
end

For server-side tile fetches, configure an identifying default User-Agent:

config :live_map, :tile_user_agent,
  "MyApp/1.0 (contact@example.com)"

Per-source headers are optional and are merged with that default.

Live components publish each decoded vector source tile as soon as it is ready. Concurrent fetch/decode work defaults to the smaller of eight tasks or the number of online schedulers, and can be tuned for the tile service and host:

config :live_map, :vector_tile_concurrency, 4

Decoded vector tiles are retained in a per-map LRU cache, so panning back to a recent area does not fetch or decode those tiles again. While zooming in, the nearest cached parent tile is cropped and scaled as a placeholder until the requested child tile finishes loading. The cache defaults to 64 display tiles; set it to 0 to disable retention or tune it for the memory available to each LiveView process:

config :live_map, :vector_tile_cache_size, 96

CLI

The escript still renders raster output by default:

./live_map --latitude 10.4197639 --longitude 107.1070841 --zoom 11 --width 640 --height 360 > map.svg

To emit self-contained vector SVG, point the CLI at an MVT source:

./live_map \
  --latitude 10.4197639 \
  --longitude 107.1070841 \
  --zoom 15 \
  --width 640 \
  --height 360 \
  --tile-url 'https://vector.openstreetmap.org/$VERSION/{zoom}/{x}/{y}.mvt' \
  --tile-version shortbread_v1 \
  --tile-user-agent 'MyApp/1.0 (contact@example.com)' \
  > map.svg

Operational Notes

  • Vector tile rendering moves tile fetching and decoding onto your server. Treat tile_source as trusted application configuration, not as untrusted request input.
  • Injected SVG becomes active page DOM. LiveMap intentionally does not sanitize it and preserves embedded elements, styles, scripts, and references while rewriting local IDs. Only inject SVG endpoints you trust as application code; an untrusted source can create an XSS vulnerability.
  • LiveMap.VectorTile.Plug never accepts an upstream source from request parameters. Keep its configured source URL and headers trusted, version styles in the endpoint URL, and place a CDN or reverse proxy in front when appropriate.
  • A PMTiles source keeps the directories of its archive in memory, and reads the data of each tile with a request of a range: see "Tiles in a PMTiles archive, and the schema of Protomaps".
  • LiveMap fetches vector tiles through Req and does not write them to disk. The component keeps its recent tiles in memory. For LiveMap.VectorTile.Plug, the Cache-Control and ETag headers let the browser and a CDN keep the tiles: put a cache in front of it to follow the tile policy of the source.
  • Remote tile sources can increase server load and can expose SSRF risks if you allow untrusted users to control URLs or headers.
  • LiveMap reads and renders a tile in the process of the request. A large tile at a low zoom can take seconds, and much longer in a VM in WebAssembly: see "Large tiles at low zooms".
  • Continue to display proper OpenStreetMap attribution and follow the upstream tile usage policies for whatever raster or vector service you configure.
  • The OpenStreetMap vector service at vector.openstreetmap.org requires a valid identifying User-Agent, local caching, and no no-cache request headers. Review the current policy before shipping against it.

Manual SVG enhancement smoke checks:

  • Load an SVG map normally and confirm the nested tile SVGs receive page-level style overrides.
  • Disable JavaScript and confirm the endpoint-styled <image> tiles remain visible.
  • Pan and zoom rapidly and confirm obsolete requests are aborted without replacing newer tiles.
  • Pan across the antimeridian and confirm repeated wrapped tiles have unique scoped IDs.
  • Test strict CSP with a matching script_csp_nonce, then without it to verify fallback behavior.
  • Test a CORS-enabled cross-origin source and a blocked source; the latter must retain <image>.

Installation

If available in Hex, the package can be installed by adding live_map to your list of dependencies in mix.exs:

def deps do
  [
    {:live_map, "~> 0.0.1"}
  ]
end

Documentation is generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/live_map.

About

A Phoenix LiveView Component for displaying an interactive map with dynamic data.

Resources

Stars

24 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages