Skip to content

Add support for Datastream SWE format parsing (currently throws "not applicable") #49

@Sam-Bolling

Description

@Sam-Bolling

Problem

The DatastreamParser currently does NOT support parsing Datastream resources in SWE Common format, despite the assessment claiming "Schema extraction from SWE" support. When attempting to parse Datastream data in SWE format, the parser throws an error: "SWE format not applicable for Datastream resources". This prevents extraction of observation schemas from SWE DataRecord/DataStream definitions, limiting the parser's ability to handle sensor data definitions encoded in SWE Common.

Current Behavior:

export class DatastreamParser extends CSAPIParser<DatastreamFeature> {
  parseGeoJSON(data: Feature | FeatureCollection): DatastreamFeature {
    // ✅ Works
  }

  parseSensorML(data: Record<string, unknown>): DatastreamFeature {
    throw new CSAPIParseError('Datastreams not defined in SensorML format');
  }

  parseSWE(data: Record<string, unknown>): DatastreamFeature {
    throw new CSAPIParseError('SWE format not applicable for Datastream resources');
    // ❌ THROWS ERROR - Should extract schema
  }
}

Expected Behavior:

  • parseSWE() should extract schema information from SWE Common DataStream or DataRecord definitions
  • Should convert SWE schema to GeoJSON Feature with schema properties
  • Should handle nested SWE components (DataRecord with fields, DataArray, etc.)

Context

This issue was identified during the comprehensive validation conducted January 27-28, 2026.

Related Validation Issues: #10 - Validate: Multi-Format Parsers
Work Item ID: 26 from Remaining Work Items
Repository: https://github.com/OS4CSAPI/ogc-client-CSAPI

Validated Commit: a71706b9592cad7a5ad06e6cf8ddc41fa5387732
File: src/ogc-api/csapi/parsers/resources.ts (494 lines)
Specific Commit: e83292af5bb7933793bb5e95aab43ffb33431904

Current Test Coverage:

  • Total parser tests: 166 (31 base + 79 resources + 56 swe-common)
  • DatastreamParser tests: 3 (all GeoJSON format)
  • No SWE format tests for Datastream

Detailed Findings

1. Assessment Claim vs. Reality

From Validation Report - Parser Classes Matrix:

Parser GeoJSON SensorML SWE Notes
DatastreamParser ⚠️ Claimed: "Schema extraction from SWE"
Actual: Throws error "not applicable"

Validation Verdict: ⚠️ PARTIALLY CONFIRMED

Evidence from Validation Report:

DatastreamParser (GeoJSON ✓, SensorML ✗, SWE ✗)

Verdict: ⚠️ PARTIALLY CONFIRMED - Assessment claims SWE support, but code says "not applicable"

Evidence from resources.ts:

export class DatastreamParser extends CSAPIParser<DatastreamFeature> {
  parseGeoJSON(data: Feature | FeatureCollection): DatastreamFeature {
    if (data.type === 'FeatureCollection') {
      throw new CSAPIParseError('Expected single Feature, got FeatureCollection');
    }
    return data as DatastreamFeature;
  }

  parseSensorML(data: Record<string, unknown>): DatastreamFeature {
    throw new CSAPIParseError('Datastreams not defined in SensorML format');
  }

  parseSWE(data: Record<string, unknown>): DatastreamFeature {
    throw new CSAPIParseError('SWE format not applicable for Datastream resources');
  }
}

✅ GeoJSON parsing works
DISCREPANCY: Assessment claims SWE support ("Schema extraction from SWE"), but implementation throws error
⚠️ This appears to be a TODO or future feature not yet implemented

2. What SWE Format Support Should Provide

OGC API - Connected Systems defines Datastreams with schema information that describes the structure of observations. In SWE Common format, this schema is represented as:

  1. DataStream Component - Top-level SWE DataStream with:

    • elementType: DataRecord or other component describing observation structure
    • encoding: How data is encoded (TextEncoding, BinaryEncoding, JSONEncoding)
    • values: The actual observation data
  2. DataRecord Component - Describes observation structure with:

    • fields: Array of field definitions (Quantity, Count, Text, etc.)
    • Each field has semantic definition, UoM, constraints

Example SWE DataStream:

{
  "type": "DataStream",
  "definition": "http://example.org/datastreams/temperature-humidity",
  "label": "Temperature and Humidity Observations",
  "elementType": {
    "type": "DataRecord",
    "definition": "http://example.org/observation-schema",
    "label": "Observation",
    "fields": [
      {
        "name": "timestamp",
        "component": {
          "type": "Time",
          "definition": "http://www.opengis.net/def/property/OGC/0/SamplingTime",
          "label": "Sampling Time",
          "uom": {
            "code": "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian"
          }
        }
      },
      {
        "name": "temperature",
        "component": {
          "type": "Quantity",
          "definition": "http://example.org/def/temperature",
          "label": "Temperature",
          "uom": { "code": "Cel" }
        }
      },
      {
        "name": "humidity",
        "component": {
          "type": "Quantity",
          "definition": "http://example.org/def/humidity",
          "label": "Relative Humidity",
          "uom": { "code": "%" }
        }
      }
    ]
  },
  "encoding": {
    "type": "JSONEncoding"
  }
}

What parseSWE() should do:

  1. Extract schema information from elementType (DataRecord or other component)
  2. Convert to GeoJSON Feature format with schema as properties
  3. Preserve field definitions, UoM, semantic definitions
  4. Handle encoding information
  5. Optionally validate schema structure

3. Existing SWE Common Parser Infrastructure

Good News: The codebase already has a comprehensive SWE Common parser!

From Validation Report - Bonus Discovery:

BONUS: SWECommonParser (Not mentioned in assessment!)

Discovery: Found an additional, comprehensive SWE Common parser not mentioned in the assessment!

File: swe-common-parser.ts (540 lines, 56 tests)

Features:

  • 15 explicit component parsers: parseQuantityComponent(), parseCountComponent(), parseBooleanComponent(), parseTextComponent(), parseCategoryComponent(), parseTimeComponent(), parseQuantityRangeComponent(), parseCountRangeComponent(), parseCategoryRangeComponent(), parseTimeRangeComponent(), parseDataRecordComponent(), parseVectorComponent(), parseDataChoiceComponent(), parseDataArrayComponent(), parseMatrixComponent(), parseDataStreamComponent(), parseGeometryComponent()
  • Generic dispatcher: parseDataComponent()
  • Recursive parsing for nested structures (DataRecord, Vector, DataChoice, DataArray, Matrix)
  • Path tracking in error messages for nested validation
  • Type checking and required property validation

This means we can leverage existing infrastructure:

  • parseDataStreamComponent() already exists
  • parseDataRecordComponent() handles nested fields
  • ✅ All component parsers available
  • ✅ Path tracking and error handling built-in

We just need to integrate this into DatastreamParser.parseSWE()!

4. Use Cases That Are Currently Broken

Scenario 1: Fetching Datastream schema from CSAPI server

// User wants to understand what fields are in a datastream
const response = await fetch('https://api.example.com/datastreams/temp-humidity', {
  headers: { 'Accept': 'application/swe+json' }
});

const datastreamParser = new DatastreamParser();
const result = datastreamParser.parse(await response.json());
// ❌ THROWS: "SWE format not applicable for Datastream resources"
// ✅ SHOULD: Return GeoJSON Feature with schema information

Scenario 2: Converting SWE schema to GeoJSON for storage

// User wants to store SWE schema in GeoJSON-based database
const sweSchema = { /* SWE DataStream definition */ };

const datastreamParser = new DatastreamParser();
const geojsonFeature = datastreamParser.parseSWE(sweSchema);
// ❌ THROWS: "SWE format not applicable for Datastream resources"
// ✅ SHOULD: Return GeoJSON Feature with schema as properties

Scenario 3: Building observation submission client

// User needs to know what fields to submit for observations
const schema = await getDatastreamSchema(datastreamId);
const fields = schema.properties.schema.fields;
// ❌ FAILS: Cannot parse SWE schema
// ✅ SHOULD: Get field list with types and constraints

5. Impact on Users

Why This Matters:

  1. Schema Discovery: Users cannot programmatically discover observation schema from SWE definitions
  2. Format Interoperability: Cannot convert between SWE and GeoJSON representations
  3. Client Development: Building observation submission clients requires manual schema parsing
  4. Validation: Cannot validate observations against SWE schema without parsing it first
  5. Documentation Generation: Cannot auto-generate documentation from SWE schema
  6. Incomplete Implementation: Assessment claims SWE support but it doesn't exist

Current Workarounds:

  • Parse SWE format manually using swe-common-parser.ts functions directly
  • Only use GeoJSON format for Datastreams (requires server support)
  • Hardcode schema information instead of extracting from SWE

Proposed Solution

1. Implement DatastreamParser.parseSWE()

Update resources.ts DatastreamParser:

import { parseDataStreamComponent, parseDataRecordComponent, parseDataComponent } from './swe-common-parser.js';

export class DatastreamParser extends CSAPIParser<DatastreamFeature> {
  parseGeoJSON(data: Feature | FeatureCollection): DatastreamFeature {
    if (data.type === 'FeatureCollection') {
      throw new CSAPIParseError('Expected single Feature, got FeatureCollection');
    }
    return data as DatastreamFeature;
  }

  parseSensorML(data: Record<string, unknown>): DatastreamFeature {
    throw new CSAPIParseError('Datastreams not defined in SensorML format');
  }

  parseSWE(data: Record<string, unknown>): DatastreamFeature {
    try {
      // Parse SWE DataStream or DataRecord component
      let parsedComponent: any;
      
      if (data.type === 'DataStream') {
        parsedComponent = parseDataStreamComponent(data);
      } else if (data.type === 'DataRecord') {
        // Some servers may return DataRecord directly as schema
        parsedComponent = parseDataRecordComponent(data);
      } else {
        // Try generic component parser
        parsedComponent = parseDataComponent(data);
      }

      // Extract schema information
      const properties: Record<string, unknown> = {
        featureType: 'Datastream',
        definition: parsedComponent.definition,
        name: parsedComponent.label,
        description: parsedComponent.description,
        schema: this.extractSchema(parsedComponent),
      };

      // Add encoding information if present
      if ('encoding' in parsedComponent && parsedComponent.encoding) {
        properties.encoding = parsedComponent.encoding;
      }

      // Add elementCount if present (for DataStream)
      if ('elementCount' in parsedComponent && parsedComponent.elementCount !== undefined) {
        properties.elementCount = parsedComponent.elementCount;
      }

      // Build GeoJSON Feature
      return {
        type: 'Feature',
        id: (parsedComponent as any).id || (parsedComponent as any).uniqueId,
        geometry: null, // Datastreams don't have geometry
        properties,
      } as unknown as DatastreamFeature;
      
    } catch (error) {
      if (error instanceof Error) {
        throw new CSAPIParseError(
          `Failed to parse SWE Datastream schema: ${error.message}`,
          'swe',
          error
        );
      }
      throw error;
    }
  }

  /**
   * Extract schema structure from SWE component
   * Handles DataStream (with elementType) and DataRecord (with fields)
   */
  private extractSchema(component: any): Record<string, unknown> {
    const schema: Record<string, unknown> = {
      type: component.type,
      definition: component.definition,
      label: component.label,
    };

    // For DataStream, extract elementType schema
    if (component.type === 'DataStream' && component.elementType) {
      schema.elementType = this.extractSchema(component.elementType);
    }

    // For DataRecord, extract field schemas
    if (component.type === 'DataRecord' && component.fields) {
      schema.fields = component.fields.map((field: any) => ({
        name: field.name,
        definition: field.component?.definition,
        label: field.component?.label,
        type: field.component?.type,
        uom: field.component?.uom,
        constraint: field.component?.constraint,
      }));
    }

    // For DataArray, extract elementType
    if (component.type === 'DataArray') {
      schema.elementCount = component.elementCount;
      if (component.elementType) {
        schema.elementType = this.extractSchema(component.elementType);
      }
    }

    // For Vector, extract coordinates
    if (component.type === 'Vector' && component.coordinates) {
      schema.referenceFrame = component.referenceFrame;
      schema.coordinates = component.coordinates.map((coord: any) => ({
        name: coord.name,
        component: {
          type: coord.component?.type,
          definition: coord.component?.definition,
          label: coord.component?.label,
          uom: coord.component?.uom,
        },
      }));
    }

    // Add UoM for quantity-based components
    if ('uom' in component && component.uom) {
      schema.uom = component.uom;
    }

    // Add constraint information
    if ('constraint' in component && component.constraint) {
      schema.constraint = component.constraint;
    }

    return schema;
  }
}

Key Implementation Details:

  1. Leverage Existing Parser: Use parseDataStreamComponent() and parseDataRecordComponent() from swe-common-parser.ts
  2. Handle Multiple Input Types: Support DataStream, DataRecord, or generic component
  3. Extract Schema Recursively: extractSchema() helper handles nested structures
  4. Preserve Semantic Information: Keep definition, label, UoM, constraints
  5. Error Handling: Wrap parsing errors with context
  6. No Geometry: Datastreams don't have spatial geometry (set to null)

2. Add Comprehensive Tests

Add to resources.spec.ts:

describe('DatastreamParser.parseSWE()', () => {
  const parser = new DatastreamParser();

  it('should parse SWE DataStream with DataRecord elementType', () => {
    const sweDataStream = {
      type: 'DataStream',
      id: 'ds-001',
      definition: 'http://example.org/datastreams/temp-humidity',
      label: 'Temperature and Humidity',
      description: 'Continuous temperature and humidity measurements',
      elementType: {
        type: 'DataRecord',
        definition: 'http://example.org/observation-schema',
        label: 'Observation',
        fields: [
          {
            name: 'timestamp',
            component: {
              type: 'Time',
              definition: 'http://www.opengis.net/def/property/OGC/0/SamplingTime',
              label: 'Sampling Time',
              uom: {
                code: 'http://www.opengis.net/def/uom/ISO-8601/0/Gregorian'
              }
            }
          },
          {
            name: 'temperature',
            component: {
              type: 'Quantity',
              definition: 'http://example.org/def/temperature',
              label: 'Temperature',
              uom: { code: 'Cel' }
            }
          }
        ]
      },
      encoding: {
        type: 'JSONEncoding'
      }
    };

    const result = parser.parse(sweDataStream, { contentType: 'application/swe+json' });

    expect(result.data.type).toBe('Feature');
    expect(result.data.geometry).toBeNull();
    expect(result.data.properties.featureType).toBe('Datastream');
    expect(result.data.properties.name).toBe('Temperature and Humidity');
    expect(result.data.properties.schema).toBeDefined();
    expect(result.data.properties.schema.type).toBe('DataStream');
    expect(result.data.properties.schema.elementType.type).toBe('DataRecord');
    expect(result.data.properties.schema.elementType.fields).toHaveLength(2);
    expect(result.data.properties.schema.elementType.fields[0].name).toBe('timestamp');
    expect(result.data.properties.schema.elementType.fields[0].type).toBe('Time');
    expect(result.data.properties.schema.elementType.fields[1].name).toBe('temperature');
    expect(result.data.properties.schema.elementType.fields[1].type).toBe('Quantity');
    expect(result.data.properties.encoding).toEqual({ type: 'JSONEncoding' });
    expect(result.format.format).toBe('swe');
  });

  it('should parse SWE DataRecord schema directly', () => {
    const sweDataRecord = {
      type: 'DataRecord',
      id: 'schema-001',
      definition: 'http://example.org/observation-schema',
      label: 'Observation Schema',
      fields: [
        {
          name: 'value',
          component: {
            type: 'Quantity',
            definition: 'http://example.org/def/measurement',
            label: 'Measurement',
            uom: { code: 'm' }
          }
        }
      ]
    };

    const result = parser.parse(sweDataRecord, { contentType: 'application/swe+json' });

    expect(result.data.type).toBe('Feature');
    expect(result.data.properties.schema.type).toBe('DataRecord');
    expect(result.data.properties.schema.fields).toHaveLength(1);
    expect(result.data.properties.schema.fields[0].name).toBe('value');
    expect(result.data.properties.schema.fields[0].type).toBe('Quantity');
    expect(result.data.properties.schema.fields[0].uom).toEqual({ code: 'm' });
  });

  it('should handle nested DataRecord in DataStream', () => {
    const nestedSchema = {
      type: 'DataStream',
      definition: 'http://example.org/complex-stream',
      label: 'Complex Stream',
      elementType: {
        type: 'DataRecord',
        definition: 'http://example.org/complex-record',
        label: 'Complex Record',
        fields: [
          {
            name: 'metadata',
            component: {
              type: 'DataRecord',
              definition: 'http://example.org/metadata',
              label: 'Metadata',
              fields: [
                {
                  name: 'quality',
                  component: {
                    type: 'Text',
                    definition: 'http://example.org/quality',
                    label: 'Quality Flag'
                  }
                }
              ]
            }
          }
        ]
      }
    };

    const result = parser.parse(nestedSchema, { contentType: 'application/swe+json' });

    expect(result.data.properties.schema.elementType.fields[0].name).toBe('metadata');
    expect(result.data.properties.schema.elementType.fields[0].type).toBe('DataRecord');
    // Nested DataRecord should be present (recursive extraction)
  });

  it('should handle DataArray elementType', () => {
    const dataArraySchema = {
      type: 'DataStream',
      definition: 'http://example.org/trajectory',
      label: 'Trajectory',
      elementType: {
        type: 'DataArray',
        definition: 'http://example.org/positions',
        label: 'Positions',
        elementCount: 100,
        elementType: {
          type: 'Vector',
          definition: 'http://example.org/position',
          label: 'Position',
          referenceFrame: 'http://www.opengis.net/def/crs/EPSG/0/4979',
          coordinates: [
            {
              name: 'lat',
              component: {
                type: 'Quantity',
                definition: 'http://example.org/latitude',
                label: 'Latitude',
                uom: { code: 'deg' }
              }
            }
          ]
        }
      }
    };

    const result = parser.parse(dataArraySchema, { contentType: 'application/swe+json' });

    expect(result.data.properties.schema.elementType.type).toBe('DataArray');
    expect(result.data.properties.schema.elementType.elementCount).toBe(100);
    expect(result.data.properties.schema.elementType.elementType.type).toBe('Vector');
  });

  it('should preserve constraints in schema', () => {
    const constrainedSchema = {
      type: 'DataRecord',
      definition: 'http://example.org/constrained',
      label: 'Constrained Schema',
      fields: [
        {
          name: 'temperature',
          component: {
            type: 'Quantity',
            definition: 'http://example.org/temperature',
            label: 'Temperature',
            uom: { code: 'Cel' },
            constraint: {
              intervals: [[-40, 60]],
              significantFigures: 2
            }
          }
        }
      ]
    };

    const result = parser.parse(constrainedSchema, { contentType: 'application/swe+json' });

    expect(result.data.properties.schema.fields[0].constraint).toEqual({
      intervals: [[-40, 60]],
      significantFigures: 2
    });
  });

  it('should handle parsing errors gracefully', () => {
    const invalidSWE = {
      type: 'InvalidType',
      // Missing required properties
    };

    expect(() => {
      parser.parse(invalidSWE, { contentType: 'application/swe+json' });
    }).toThrow(CSAPIParseError);
  });

  it('should include encoding information', () => {
    const streamWithEncoding = {
      type: 'DataStream',
      definition: 'http://example.org/stream',
      label: 'Stream',
      elementType: {
        type: 'Quantity',
        definition: 'http://example.org/value',
        label: 'Value',
        uom: { code: 'm' }
      },
      encoding: {
        type: 'TextEncoding',
        tokenSeparator: ',',
        blockSeparator: '\n'
      }
    };

    const result = parser.parse(streamWithEncoding, { contentType: 'application/swe+json' });

    expect(result.data.properties.encoding).toEqual({
      type: 'TextEncoding',
      tokenSeparator: ',',
      blockSeparator: '\n'
    });
  });
});

Test Coverage Goals:

  • 7-8 new tests for SWE format parsing
  • Cover DataStream, DataRecord, nested structures, DataArray, Vector, constraints, encoding
  • Test error handling
  • Bring total DatastreamParser tests from 3 → 10-11

3. Update Documentation

Add JSDoc to parseSWE():

/**
 * Parse Datastream from SWE Common format.
 * 
 * Extracts schema information from SWE DataStream or DataRecord components
 * and converts to GeoJSON Feature format with schema as properties.
 * 
 * Supported SWE types:
 * - DataStream (with elementType describing observation structure)
 * - DataRecord (schema definition with fields)
 * - Any other SWE component (treated as schema)
 * 
 * @param data - SWE Common component (DataStream, DataRecord, etc.)
 * @returns GeoJSON Feature with schema information in properties
 * @throws CSAPIParseError if SWE component is invalid or cannot be parsed
 * 
 * @example
 * const sweDataStream = {
 *   type: 'DataStream',
 *   definition: 'http://example.org/datastreams/temp-humidity',
 *   label: 'Temperature and Humidity',
 *   elementType: {
 *     type: 'DataRecord',
 *     fields: [
 *       {
 *         name: 'temperature',
 *         component: {
 *           type: 'Quantity',
 *           uom: { code: 'Cel' }
 *         }
 *       }
 *     ]
 *   }
 * };
 * 
 * const feature = parser.parseSWE(sweDataStream);
 * // Returns GeoJSON Feature with schema in properties
 */
parseSWE(data: Record<string, unknown>): DatastreamFeature {
  // Implementation...
}

Acceptance Criteria

  • DatastreamParser.parseSWE() successfully parses SWE DataStream components
  • DatastreamParser.parseSWE() successfully parses SWE DataRecord components (direct schema)
  • Schema extraction handles nested structures recursively (DataRecord within DataRecord, etc.)
  • Schema extraction preserves all semantic information (definition, label, UoM, constraints)
  • Handles DataStream with DataRecord elementType
  • Handles DataStream with DataArray elementType
  • Handles DataStream with Vector elementType
  • Handles nested DataRecord fields (recursive)
  • Preserves encoding information (TextEncoding, BinaryEncoding, JSONEncoding)
  • Preserves constraint information (intervals, allowedValues, significantFigures, patterns)
  • Returns GeoJSON Feature with schema in properties
  • Sets geometry to null (Datastreams have no spatial geometry)
  • Error handling wraps parsing errors with context
  • Test suite includes 7-8 new tests covering:
    • DataStream with DataRecord elementType
    • DataRecord direct schema
    • Nested DataRecord structures
    • DataArray elementType
    • Vector elementType
    • Constraint preservation
    • Encoding information
    • Error handling
  • All tests pass in CI pipeline
  • JSDoc documentation added with examples
  • Assessment documentation updated to reflect SWE support implementation

Implementation Notes

Files to Modify

  1. src/ogc-api/csapi/parsers/resources.ts (494 lines):

    • Update DatastreamParser.parseSWE() method
    • Add extractSchema() private helper method
    • Import functions from swe-common-parser.ts
    • Estimated additions: ~80-100 lines
  2. src/ogc-api/csapi/parsers/resources.spec.ts:

    • Add 7-8 new tests for SWE format parsing
    • Estimated additions: ~200-250 lines

Dependencies

Imports Required:

import {
  parseDataStreamComponent,
  parseDataRecordComponent,
  parseDataComponent,
  ParseError
} from './swe-common-parser.js';

Existing Infrastructure to Leverage:

  • swe-common-parser.ts - Comprehensive SWE parser (540 lines, 56 tests)
  • parseDataStreamComponent() - Handles DataStream with validation
  • parseDataRecordComponent() - Handles nested fields recursively
  • parseDataComponent() - Generic component dispatcher
  • ✅ Error handling with path tracking built-in

Code Patterns to Follow

Schema Extraction Pattern:

  • Recursive extraction for nested components
  • Preserve all semantic information (definition, label, description)
  • Extract UoM for quantity-based components
  • Extract constraints (intervals, patterns, allowed values)
  • Handle both DataStream (with elementType) and DataRecord (with fields)

Error Handling Pattern:

try {
  const parsedComponent = parseDataStreamComponent(data);
  // ... conversion ...
} catch (error) {
  if (error instanceof Error) {
    throw new CSAPIParseError(
      `Failed to parse SWE Datastream schema: ${error.message}`,
      'swe',
      error
    );
  }
  throw error;
}

Feature Structure:

{
  type: 'Feature',
  id: component.id || component.uniqueId,
  geometry: null,
  properties: {
    featureType: 'Datastream',
    definition: component.definition,
    name: component.label,
    description: component.description,
    schema: { /* Extracted schema structure */ },
    encoding: component.encoding, // Optional
    elementCount: component.elementCount // Optional
  }
}

Testing Requirements

Test Categories:

  1. Basic Parsing (2 tests):

    • DataStream with DataRecord elementType
    • DataRecord direct schema
  2. Nested Structures (2 tests):

    • Nested DataRecord within DataRecord
    • DataArray with complex elementType
  3. Complex ElementTypes (2 tests):

    • Vector elementType with coordinates
    • DataChoice elementType with options
  4. Metadata Preservation (2 tests):

    • Constraint preservation
    • Encoding information preservation
  5. Error Handling (1 test):

    • Invalid SWE component

Integration Testing:

  • Verify parseSWE() works with parse() method (auto format detection)
  • Verify schema can be used for validation
  • Verify schema can be used to build observation submission forms

Special Considerations

  1. DataStream vs DataRecord:

    • DataStream has elementType property (schema for observations)
    • DataRecord can be used directly as schema (fields describe structure)
    • Both should be supported
  2. Encoding Information:

    • DataStream may include encoding (TextEncoding, BinaryEncoding, JSONEncoding)
    • Should be preserved in properties for later use
    • Not all servers provide encoding (optional)
  3. Schema Depth:

    • Schemas can be deeply nested (DataRecord → DataRecord → DataArray → Vector)
    • extractSchema() must handle recursion
    • Consider depth limit to prevent infinite loops (unlikely but possible)
  4. Constraint Information:

    • AllowedValues (intervals, discrete values, significant figures)
    • AllowedTokens (patterns, regex)
    • AllowedTimes (temporal intervals)
    • All should be preserved for client-side validation
  5. Compatibility:

    • Ensure GeoJSON output is compatible with existing DatastreamFeature type
    • May need to update DatastreamFeature type definition to include schema property
    • Check type definitions in src/ogc-api/csapi/types/geojson/datastream.ts

Priority Justification

Priority Level: Low

Why Low Priority:

  1. Workaround Available: Users can parse SWE format manually using swe-common-parser.ts functions directly
  2. GeoJSON Support Exists: DatastreamParser works with GeoJSON format
  3. Limited Use Cases: Most CSAPI servers return Datastreams in GeoJSON format by default
  4. Non-Critical Feature: Schema extraction is useful but not essential for basic operations
  5. Implementation Effort: Moderate - requires ~80-100 lines of code and 7-8 tests (~3-4 hours)

Should Be Higher Priority If:

  • Many CSAPI servers only provide SWE format for Datastream schemas
  • Users actively requesting SWE format support for Datastreams
  • Building schema discovery tools that require SWE parsing
  • Integration with SWE-based systems (e.g., SOS, SensorThings API with SWE encoding)

Dependencies:

Quick Win Analysis:

  • ✅ Clear specification requirements (OGC Connected Systems)
  • ✅ Existing infrastructure available (swe-common-parser.ts)
  • ✅ Straightforward implementation (leverage existing parsers)
  • ✅ Well-defined test coverage
  • ⚠️ Requires understanding of SWE Common schema structure
  • ⚠️ Need to verify DatastreamFeature type compatibility

Effort Estimate: 3-4 hours

  • parseSWE() implementation: 1.5 hours
  • extractSchema() helper: 1 hour
  • Tests: 1 hour
  • Documentation: 30 minutes
  • Testing/verification: 30 minutes

Risk of Not Implementing:

  • ⚠️ Assessment claim remains incorrect ("Schema extraction from SWE")
  • ⚠️ Users must manually parse SWE schemas (developer burden)
  • ⚠️ Cannot programmatically discover schemas from SWE endpoints
  • ⚠️ Limits interoperability with SWE-based systems
  • ⚠️ Schema discovery tools cannot be built
  • LOW IMPACT: Workaround exists, GeoJSON format available

When This Should Be Prioritized:

  • Active user requests for SWE format support
  • Building schema discovery or documentation generation tools
  • Integration with SWE-heavy systems (SOS servers, legacy systems)
  • After higher priority validation improvements (work items Validate: OGC API Endpoint Integration (endpoint.ts) #20-25)

Labels:

  • priority: low
  • enhancement
  • parser
  • swe-format
  • todo

Estimated Effort: 3-4 hours
Related Work Items: #27 (ControlStream SWE support)
Depends On: None
Blocks: None

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions