FIMViz.js

App startup & file upload — usage reference

Part 2 (APP_STARTUP_ADVANCED.md): the map-provider seam, the materialize/decode extension seam, and FimMap's fuller instance API.

Contents

Mounting · Options · Two boot paths · Multiple maps on one page · Listening before resolution · Namespace-level parse · Teardown · Uploading files · Getting it on the map · The CRS check

Mounting

import { FimViz, mount } from 'fimviz';   // or: FimViz.mount / FimViz.create

const fim = await mount(target, options);        // === FimViz.mount === FimViz.create (all aliases)

Everything on this page comes off the fimviz barrel; for the other published specifiers (fimviz/ui, fimviz/src, fimviz/src/*) see USAGE.md → Subpaths.

target: an Element, its id string, or a CSS selector. Returns a Promise<FimMap> that ALSO has .on(evt, fn)/.off(evt, fn) glued on before it resolves — so mount(el, opts).on('ready', fn) works even though mount() hasn't finished yet.

await it before calling instance methods. Because .on/.off work on the un-awaited promise, it looks partly like a FimMap, and treating it as one is an easy mistake:

const fim = mount('#el', opts);          // ✗ fim is a Promise
await fim.addLayer(ds);                  // → throws, naming the fix

const fim = await mount('#el', opts);    // ✓

Accessing any FimMap member on an un-awaited promise throws, naming both the cause and the fix. Only .on()/.off() are available before it resolves.

Options

Key Default Notes
provider — (required) 'leaflet' | 'google' | a registered name. No default — the backends differ in credentials and capability ('leaflet': no key, map + vectors + static rasters; 'google': needs apiKey, adds velocity, damage markers and ArcGIS depth), so the engine never picks for you. Omitting it throws config-invalid naming both.
apiKey '' (build-time GOOGLE_MAPS_API_KEY) Required when provider needs one (google does; leaflet doesn't) — throws config-invalid otherwise.
center null (provider default) {lat, lng}.
zoom null (provider default) number.
mapId null Google-only; required for AdvancedMarkerElement.
mapOptions {} Raw provider options, merged last — wins over every derived option above.
dataSource / corsProxy '' Deployment topology the engine ships none of — see resolveUrl.
resolveUrl null (url, cfg) => string — override every fetched URL (CORS proxy/auth/mirrors). null = identity (no rewriting) unless a host runtime supplies its own.
gdalPath version-pinned jsDelivr CDN Where gdal3.js loads its wasm/data from (lazy — only fetched on first real reproject).
storage null {name, version?, tables?}fim.storage throws until set; the engine names no database.
theme {bgColor, olColor, hlColor, bColor} → the widget's CSS custom properties.
isolated false true gives this map its own FimViz instance instead of the shared default — see "Multiple maps" below.

Config is set-once: create() locks it the moment a boot starts. A later app.configure(...) is then ignored with a console warning rather than reconfiguring — and a second mount() on the same app never gets that far, since the single-map guard throws already-mounted first (see Multiple maps).

Three things mount() rejects before booting anything, all as config-invalid: a missing provider, a provider that requiresApiKey with no apiKey given, and a target that resolves to no element.

Two boot paths

FimViz.registerRuntime({ bootstrap, getMountedMap, teardownMap, createPanel, markup });   // call BEFORE mount()
await mount('#el', { apiKey });

registerRuntime must run before mount()create() reads the registered runtime once, at boot. bootstrap(fim) receives the FimMap being mounted, so a runtime never has to look up which instance it is booting. fim.map is still null at that point — the engine adopts the map once bootstrap() resolves — so hold the reference, but read fim.map only afterwards.

Markup injection

mount() injects markup into your container, unless that container already holds a #map. A container you have already populated is left untouched, so you can supply the widget DOM inline. The check is scoped to the container: a #map elsewhere on the page is unrelated and does not suppress injection, which is what lets two widgets mount on one page.

What gets injected depends on the boot path: with no runtime it's a bare <div id="map" style="width:100%;height:100%"> for the engine to boot into; with a runtime it's that runtime's markup. A runtime that registers no markup while the container has no #map is an error, not a silent empty map — mount() throws config-invalid naming both fixes (pass markup, or put #map in the page yourself).

Multiple maps on one page

The default app allows one mounted map (app.hasMaps guards it — a second mount() on the default app throws "already mounted"). To run a second, independent map (a different provider, a different config), give it its own app:

const fim1 = await mount('#lmap', { provider: 'leaflet' });
const fim2 = await mount('#gmap', { provider: 'google', apiKey: '...', isolated: true });   // own FimViz

Listening before resolution

mount('#el', opts)
  .on('ready', () => console.log('booted'))
  .on('error', (e) => console.error(e.code, e.message));

Namespace-level parse (no map needed)

const ds = await FimViz.parseFile(source, options);   // pure — no FimMap instance, no registration

Teardown

fim.destroy();   // detaches the map, clears injected markup, releases this map from its app

Uploading files (drag-and-drop)

let file = undefined;
let f1 = undefined;
const dropzone = document.getElementById('dropzone');
dropzone.addEventListener('dragover', (e) => e.preventDefault());   // required, or the browser rejects the drop
dropzone.addEventListener('drop', async (e) => {
  e.preventDefault();
  file = e.dataTransfer.files[0];
  if (file) f1 =  await fim.addDataset(file);   // ⚠️ addDataset is ASYNC - needs await or then() 
});

fim.addDataset(source, options?) accepts File | Blob | ArrayBuffer | string (URL). It parses (format auto-detected from filename/extension unless options.format is given) into a Dataset and pushes it onto fim.datasets — it does not render anything yet.

Recognised formats: geotiff (.tif/.tiff), geojson (.geojson/.json, incl. HAZUS damage), kml, kmz, shp (a .zip carrying .shp/.dbf/.shx), csv, and xyz. The last two take their own options — a latField/lngField column pair or a geometryField of WKT/GeoJSON for CSV, swapXY for northing-first XYZ — and csvHeaders(text) reads a CSV's column names before parsing so you can build a mapping picker; see LAYER_SUBTYPES.md → Building from CSV/XYZ.

Getting it on the map

let l1 = await fim.addLayer(f1);   // infers 'raster'/'vector' from f1.kind (works with no type arg)
// or, if f1's raw file is what you have and haven't called addDataset yet:
let l1 = await fim.addLayer(file);   // addLayer parses it via addDataset() for you, same effect

The CRS check

addDataset/parseFile never reprojects — a geotiff Dataset comes back in its own native CRS. If that CRS isn't one the active map provider can render (google/leaflet accept the WGS84 family, EPSG:4326/4269), addLayer/render() throws an actionable error naming the fix:

let f1 = await fim.addDataset(file);
f1.crs;                                    // e.g. 'EPSG:26914' — check before rendering
let l1 = await fim.addLayer(f1.reproject('EPSG:4326'));   // reproject() is lazy; forcing (inside
                                                            // addLayer→render→compute) triggers the
                                                            // actual warp, auto-loaded, no setup call

See DATASET_OPERATIONS.md for every other Dataset op you can chain in before rendering (clip/mask/resampleTo/...), and LAYERS.md / LAYER_SUBTYPES.md for what to do with l1 once it's up.