Build With EMDS

Extend EMDS across both products β€” EMDS for ArcGIS Pro and EMDS for QGIS β€” through shared workflow formats, custom connectors, scripting, and published engine contracts. Write in Python, C#, and R.

One Workflow Definition, Both Products

EMDS workflows are plain JSON documents. The same workflow-definition file runs unmodified on EMDS for ArcGIS Pro and EMDS for QGIS β€” identical step types, identical operators, identical variable semantics. Author once, run on either product.

Verified parity

Both products execute the same six step types and resolve {{variable}} substitutions identically:

  • task β€” run an engine, connector, or Python script
  • set_variable β€” assign or compute a workflow variable
  • for_loop β€” iterate over a list or each feature
  • if_else β€” branch on a condition
  • log β€” write to the execution log
  • try_catch β€” handle errors and keep going

Conditions use the same 13 operators on both products:

equals Β· not_equals Β· less_than Β· greater_than Β· less_than_or_equals Β· greater_than_or_equals Β· is_empty Β· is_not_empty Β· is_truthy Β· is_falsy Β· contains Β· in_list Β· between

workflow-definition.json
{
  "name": "Run a model on every feature",
  "steps": [
    {
      "step_type": "for_loop",
      "config": {
        "loop_type": "each",
        "variable_name": "feature",
        "index_variable_name": "feature_index",
        "list_value": "{{study_area_features}}"
      },
      "children": [
        {
          "step_type": "try_catch",
          "config": { "error_variable": "feature_error" },
          "children": [
            {
              "step_type": "task",
              "config": {
                "input_source": "named_result",
                "input_variable": "feature",
                "store_result_as": "feature_result"
              }
            }
          ],
          "else_children": [
            {
              "step_type": "log",
              "config": {
                "level": "warning",
                "message": "Feature {{feature_index}} failed: {{feature_error}}"
              }
            }
          ]
        }
      ]
    }
  ]
}

What is β€” and isn't β€” interoperable

EMDS is built for honest cross-product interoperability at the data and workflow layer:

  • Shared & mutually inspectable: the metadata database schema and the workflow-definition JSON are common to both products. A study's metadata authored in one product can be read by the other.
  • Not interchangeable: the project files are product-specific β€” .qgz for QGIS, .aprx for ArcGIS Pro. You cannot open one product's project in the other, and studies do not execute cross-product.

In short: portable formats and a shared schema β€” not a shared project file. Move workflows and data between products; run each study on its own product.

Connect Any Data Source

A connector translates raw spatial or tabular data into inputs the analytical engines can consume. Write connectors in Python on both products, or in C# for the deepest integration on ArcGIS Pro.

Python Connector (both products)
from emds import Connector, DataLayer

class MyForestConnector(Connector):
    name = "Forest Condition Layer"

    def compute(self, context) -> DataLayer:
        # Load your spatial data
        gdf = context.load_vector("forest_stands.shp")

        # Transform to EMDS scale [0, 1]
        gdf["condition"] = normalize(
            gdf["basal_area"], method="zscore"
        )
        return DataLayer.from_geodataframe(gdf, "condition")
C# Connector (ArcGIS Pro)
using EmdsFramework.Connectors;
using EmdsFramework.Layers;

public class ForestConnector : IEmdsConnector
{
    public string Name => "Forest Condition";

    public DataLayer Compute(EmdsContext ctx)
    {
        var fc = ctx.OpenFeatureClass("ForestStands");
        var values = fc.Select("BasalArea")
                       .Normalize(NormMethod.ZScore);
        return new DataLayer("condition", values);
    }
}

Drive the Engines From Python

EMDS for QGIS ships with the analytical engines as bundled .NET CLIs alongside a Python bridge. Call them headless for batch and server automation, or run your own Python inside a workflow Task.

Headless engine calling

The python-bridge exposes NetWeaverBridge and CDPBridge, which drive the bundled .NET CLI engines over Apache Arrow IPC β€” efficient, near-zero-copy data transfer that scales to large datasets. It is a clean fit for unattended batch jobs and server-side automation.

Custom Python script tasks

A workflow task step can run a developer's own Python script in its own interpreter. The contract is deliberately simple:

  • Workflow variables arrive as JSON in the EMDS_WORKFLOW_VARIABLES_JSON environment variable.
  • A JSON object written to stdout becomes the task result (and any store_result_as variable).
  • stderr streams line-by-line into the execution log, so users see live progress.
  • A non-zero exit code signals failure (unless the step sets continue_on_error).

The plugin and CLIs ship together as three platform distributions: Windows x64, macOS (Intel + Apple Silicon), and Linux x64.

CSV β†’ model β†’ CSV via the bridge
import pandas as pd
from engine_bridge import NetWeaverBridge

# Bundled .NET CLI shipped with the plugin
nw = NetWeaverBridge("NetWeaverCLI.dll")

# Read inputs
data = pd.read_csv("sites.csv")

# Run the model β€” data moves over Arrow IPC
results = nw.process_data(
    data=data,
    model_path="ConditionAssessment.nw2",
    use_arrow=True,
)

# Write outputs
results.to_csv("scored_sites.csv", index=False)

The QGIS extension surface is workflows plus Python script tasks β€” there is no plugin-to-plugin API. That keeps the contract small, stable, and scriptable.

Extend a Layered .NET 10 Design

EMDS for ArcGIS Pro is built on published contracts. Partners extend it by implementing those interfaces β€” add an engine, build on the data model, or reuse the domain library β€” without forking the application.

Engine contracts

Every engine implements the generic IEngineService<TParams, TResult>. Five engine contracts ship today β€” ICdpEngine, INetWeaverEngine, ILpaEngine, IPortfolioEngine, and ISmileEngine. Implement an adapter against the service interface to add your own engine.

Repository layer

A repository layer sits over an EF Core / SQLite metadata database β€” IWorkspaceRepository, IStudyRepository, ITaskRepository, IWorkflowRepository, and more. Build on the EMDS data model directly; the libraries target net10.0 with no ArcGIS dependency.

Reusable domain library

Emds.Core is the reusable domain model that ties it together: Workspace β†’ Study β†’ Analysis β†’ Task β†’ Result.

IEngineService<TParams, TResult>
public interface IEngineService<TParams, TResult>
{
    Task<EngineRunHandle> StartAsync(
        TParams parameters,
        CancellationToken cancellationToken);

    IAsyncEnumerable<EngineProgress> ObserveProgress(
        EngineRunHandle handle,
        CancellationToken cancellationToken);

    Task<TResult> AwaitResultAsync(
        EngineRunHandle handle,
        CancellationToken cancellationToken);
}

Write in the Language You Know

🐍

Python

Full connector SDK, the engine bridge, and workflow script tasks. Supported on both ArcGIS Pro and QGIS.

Both Products
C#

C#

Native connectors and engine adapters against the published .NET 10 contracts. Deepest integration.

ArcGIS Pro
R

R

Statistical computing connectors for research-grade ecological modeling and analysis.

Research Connectors
JS

JavaScript

Web connector framework for browser-based integrations and custom tools.

EMDS Online

Ready to build?

Download EMDS, explore the documentation, or get in touch with the team for developer support.

An unhandled error has occurred. Reload πŸ—™