Each layer's props should extend the MetacityLayerProps interface:
import { MetacityLayerProps } from 'metacitygl';
export function CustomLayer(props: MetacityLayerProps) {
const { context, onLoaded, enableUI } = props;
return (
<div>
<h1>Custom Layer</h1>
<p>This is a custom layer</p>
</div>
)
}
The returned nodes will get automatically attached to the Metacity UI panel. You can make the UI look however you want.
Each layer gets these props by default:
context is a class encapsulating the graphics context, you can use it to modify what gets rendered
onLoaded is a callback you must use to signal the application that your layer is ready to display
enableUI is a flag that signals you whether the layer UI should get displayed or not
Parsing data
There is a recommended way how to handle any data in MetacityGL. In your custom layer, split the process into 3 parts:
//... other imports
import axios from "axios";
import { Utils } from "metacitygl"
export function CustomLayer(props: MetacityLayerProps) {
const { context, onLoaded, enableUI } = props;
React.useEffect(() => {
if (!context)
return;
//get your data
const data = (await axios.get(api)).data as Data;
//parse it using Metacity's Assemblers
const asm = new Utils.Assemblers.SomeAssembler();
for (const item in data) {
//pass the item to the assembler
}
if (asm.empty)
return;
const buffers = asm.toBuffers();
//transform it into a Model
const model = Graphics.Models.SomeModel.create(buffers);
//pass it to context
context.add(model);
}, [context]);
return (...)
}
Transforming the data into a renderable model can be tricky. MetacityGL provides built-in tools called Assemblers that can make your life a little easier. The Assemblers allow you to parse the data incrementally. When you are done, you can export the data from the Assembler in a form that can be directly passed into one of Metacity's Models.
Pass the created model into the graphics context.
Workers
The data-parsing steps usually require some number-crunching. It is best to isolate these steps into a separate worker process and pass the result back to the layer component.
dataWorker.ts
import axios from "axios";
import { Utils } from "metacitygl"
declare var self: any;
interface Data {
//TODO
}
self.onmessage = async function (e: any) {
const { api, ... } = e.data;
const data = (await axios.get(api)).data as Data;
const asm = new Utils.Assemblers.SomeAssembler();
for (const item in data) {
//pass the item to the assembler
}
if (!asm.empty) {
const buffers = asm.toBuffers();
const transferables = asm.pickTransferables(buffers);
self.postMessage(buffers, transferables);
}
}
With this worker, you can structure the layer the following way:
import Worker from "./dataWorker?worker&inline";
import { Graphics } from "metacitygl"
export function CustomLayer(props: MetacityLayerProps) {
const { context, onLoaded, enableUI } = props;
React.useEffect(() => {
if (!context)
return;
// instantiate a worker
const worker = new Worker();
//start a job
worker.postMessage({
api: "https://my.api"
});
//process the job result
worker.onmessage = (e) => {
worker.terminate();
const model = Graphics.Models.SomeModel.create(e.data);
context.add(model);
};
}, [context]);
return (...)
}
There is much more you could do:
store the reference to the Model in the layer and allow modifying it with the UI
you could create a pool of workers and reuse them between layers instead of creating and terminating them for better performance, etc.
See the other pages for a better grasp of Models, Assemblers, and the context.