X-GIS

Reference

Language reference.

Every snippet below is a real .xgis sample taken from the playground demos or compiler tests — copy any block and it parses + compiles as-is. Click Try this → next to a code block to launch the matching live demo.

— 01

Quick start

Two top-level blocks — a source that points at data, a layer that styles it. The pipe-prefixed lines are utility classes; multiple lines are concatenated into one stack.

quick-start.xgis

Try this
source world {
  type: geojson
  url: "ne_110m_countries.geojson"
}

layer countries {
  source: world
  | fill-stone-200 stroke-stone-400 stroke-1
}

— 02

Sources

A `source` block declares where data comes from. Three transports are supported. The runtime picks the loader from the `type` keyword, not the URL extension.

sources.xgis

Try this
// GeoJSON — full-file load, runtime tessellation
source land {
  type: geojson
  url: "land.geojson"
}

// PMTiles — single archive, per-MVT-layer slicing
source pm {
  type: pmtiles
  url: "https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles"
}

// Raster basemap — XYZ tile URL template
source basemap {
  type: raster
  url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
}

— 03

Layers + utility classes

Each `layer` references a source by name and stacks utility classes on `|` lines. Multiple `|` blocks compose left-to-right; later utilities win on conflict.

layers.xgis

Try this
layer roads {
  source: city
  | stroke-stone-400 stroke-1 opacity-90
  | stroke-dasharray-8-4
}

— 04

Zoom-driven values

Wrap any utility value in `interpolate(zoom, z1, v1, z2, v2, …)` to make it a function of camera zoom. The runtime samples the function each frame and linearly blends between adjacent stops — same builtin works on feature properties, so `interpolate(.population, …)` gives a continuous gradient with no extra syntax.

modifiers.xgis

Try this
layer countries {
  source: world
  | fill-purple-400 stroke-purple-200 stroke-1
  | opacity-[interpolate(zoom, 2, 30, 5, 60, 8, 90)]
}

— 05

Filters

Each layer can carry a `filter:` predicate. Field accessors use the `.FIELD` syntax. Multiple layers can share one source — common pattern for highlighting subsets.

filters.xgis

Try this
// All countries as dark base
layer all {
  source: countries
  | fill-slate-900 stroke-slate-700 stroke-0.5
}

// Top economies highlighted on top
layer top_economies {
  source: countries
  filter: .GDP_MD_EST > 5000000
  | fill-yellow-500 stroke-yellow-300 stroke-2
}

— 06

Data-driven values: match()

Bind feature property values to colors (or any utility value) inline. The compiler classifies each branch and routes to the GPU shader so the lookup happens per-fragment.

match.xgis

Try this
layer continents {
  source: countries
  | fill match(.CONTINENT) {
      "Africa"        -> amber-600,
      "Asia"          -> rose-500,
      "Europe"        -> sky-500,
      "North America" -> emerald-500,
      "South America" -> lime-500,
      "Oceania"       -> violet-500,
      "Antarctica"    -> slate-300,
      _               -> gray-400
    }
    stroke-slate-700 stroke-0.5
}

— 07

Background

A top-level `background { ... }` block sets the canvas clear color (Mapbox-style). Renders before any layer; only the resolved fill color is consumed.

background.xgis

Try this
background { fill: stone-100 }

source pm {
  type: pmtiles
  url: "/world.pmtiles"
}

layer water {
  source: pm
  sourceLayer: "water"
  | fill-sky-900 stroke-sky-700 stroke-0.5
}

— 08

Presets — reusable utility stacks

A `preset` block names a utility stack you can splat into any layer with `apply-<preset>`. Layer-level utilities placed after the apply override the preset values.

presets.xgis

preset alert {
  | fill-red-500 stroke-black stroke-2
}

layer tracks {
  source: data
  | apply-alert opacity-80
}

— 09

Symbols

Define point glyphs with SVG-style path strings. Reference the symbol with `shape-<name>`. Stroke and fill are signed-distance-field rendered, so shapes stay crisp at every zoom.

symbols.xgis

Try this
symbol arrow {
  path "M 0 -1 L 0.4 0.4 L 0 0.1 L -0.4 0.4 Z"
}

layer capitals {
  source: cities
  filter: .featurecla == "Admin-0 capital"
  | shape-arrow fill-emerald-400 stroke-emerald-600 stroke-1 size-16
}

— 10

Animation — keyframes

Declare a top-level `keyframes` block, then attach it to a layer with `animation-<name>`. The lifecycle modifiers all share the `animation-` prefix so they group together.

animation.xgis

Try this
keyframes pulse {
  0%:   opacity-100
  50%:  opacity-30
  100%: opacity-100
}

layer pulsing_coast {
  source: coast
  | stroke-amber-300 stroke-3
  | animation-pulse animation-duration-1500 animation-ease-in-out animation-infinite
}

— 11

Projections

Seven projections ship in both CPU and WGSL form. Switching is a uniform write — same source, no re-tessellation. The runtime exposes them via `getProjection(name, ...args)` from `@xgis/runtime`.

projections.xgis

Try this
// Available names (string keys for getProjection):
//
//   mercator                   — Web Mercator, the default
//   equirectangular            — flat plate carrée
//   natural_earth              — pseudo-cylindrical, low-distortion world
//   orthographic(lon, lat)     — globe view, requires center
//   azimuthal_equidistant(lon, lat)
//   stereographic(lon, lat)
//   oblique_mercator(lon, lat) — tilted Mercator centered on (lon, lat)

— 12

JavaScript API

Call `new XGISMap(canvas)` then `await map.run(source, baseUrl)` where `source` is a `.xgis` source string and `baseUrl` resolves any relative `url:` references in your declarations. WebGPU is preferred; Canvas 2D engages as a fallback when no adapter is available.

js-api.xgis

Try this
import { XGISMap } from "@xgis/runtime"

const canvas = document.querySelector("canvas")
const map = new XGISMap(canvas)

await map.run(`
  source world {
    type: geojson
    url: "ne_110m_countries.geojson"
  }
  layer countries {
    source: world
    | fill-stone-200 stroke-stone-400 stroke-1
  }
`, "/data/")

Was this page helpful?

Tell us what's missing