Skip to content
🔵Entwurf (gut)62%
Vollständigkeit:
80%
Korrektheit:
75%
⏳ Noch nicht geprüft

WFS Integration & Authentication

Status: ✅ Fully documented

Overview

The WFS integration in p2d2 provides a robust and secure connection to Geoserver WFS services with comprehensive authentication, CORS handling, and error handling. These modules enable dynamic access to geodata via standardized WFS protocols.

Main Modules

1. WFS Auth Client (wfs-auth.ts)

Secure client for WFS service integrations with authentication and proxy support.

Configuration Structure

typescript
export interface WFSCredentials {
  username: string;
  password: string;
}

export interface WFSConfig {
  endpoint: string;
  workspace: string;
  namespace: string;
  credentials: WFSCredentials;
}

export class WFSAuthClient {
  private config: WFSConfig;
  
  constructor(config: Partial<WFSConfig> = {})
}

Environment Detection

typescript
// Automatic environment detection
function detectEnvironment(): EnvironmentInfo {
  return {
    isDev: (process.env.NODE_ENV === "development") || 
           (window.location.hostname.includes("local")),
    nodeEnv: process.env.NODE_ENV,
    hostname: window.location.hostname
  };
}

2. WFS Layer Manager (wfs-layer-manager.ts)

Dynamic management of WFS vector layers with caching and state management.

Layer Management Class

typescript
export class WFSLayerManager {
  private map: OLMap;
  private activeLayer: VectorLayer<VectorSource> | null = null;
  private currentState: {
    kommune: KommuneData | null;
    categorySlug: string | null;
  };
  private layerCache = new Map<string, VectorLayer<VectorSource>>();
}

Practical Usage

Basic WFS Integration

typescript
import { wfsAuthClient } from '../utils/wfs-auth';
import WFSLayerManager from '../utils/wfs-layer-manager';

// 1. Initialize WFS client
const wfsClient = new WFSAuthClient({
  endpoint: "https://wfs.data-dna.eu/geoserver/ows",
  workspace: "Verwaltungsdaten",
  credentials: {
    username: "p2d2_wfs_user",
    password: "eif1nu4ao9Loh0oobeev"
  }
});

// 2. Create WFS layer manager
const wfsManager = new WFSLayerManager(map);

// 3. Get features
async function loadWFSFeatures() {
  const features = await wfsClient.getFeatures("p2d2_containers", {
    CQL_FILTER: "wp_name='Köln' AND container_type='cemetery'",
    maxFeatures: "100"
  });
  
  return features;
}

Dynamic Layer Control

typescript
// Show WFS layer for municipality and category
async function showWFSLayer(kommune: KommuneData, categorySlug: string) {
  try {
    await wfsManager.displayLayer(kommune, categorySlug);
    console.log(`WFS layer for ${kommune.slug} - ${categorySlug} displayed`);
  } catch (error) {
    console.error("WFS layer could not be loaded:", error);
  }
}

// Hide layer
function hideWFSLayer() {
  wfsManager.hideLayer();
}

// Toggle layer
async function toggleWFSLayer(kommune: KommuneData, categorySlug: string) {
  await wfsManager.toggleLayer(kommune, categorySlug);
}

Authenticated WFS Requests

typescript
// Different request types
async function demonstrateWFSRequests() {
  // 1. Standard GetFeature Request
  const standardFeatures = await wfsAuthClient.getFeatures(
    "p2d2_containers", 
    { maxFeatures: "50" }
  );
  
  // 2. BBox-based query
  const bboxFeatures = await wfsAuthClient.getFeaturesInBBox(
    "p2d2_containers",
    [6.8, 50.8, 7.1, 51.1], // Cologne BBox
    "EPSG:4326"
  );
  
  // 3. CQL filter for specific data
  const filteredFeatures = await wfsAuthClient.getFeatures(
    "p2d2_containers",
    {
       CQL_FILTER: "wp_name='Köln' AND container_type='administrative'",
      propertyName: "wp_name,container_type,geometry"
    }
  );
  
  return {
    standard: standardFeatures,
    bbox: bboxFeatures,
    filtered: filteredFeatures
  };
}

Configuration

WFS Endpoint Configuration

typescript
// Environment-specific configuration
const WFS_CONFIGS = {
  development: {
    endpoint: "https://wfs.data-dna.eu/geoserver/ows",
    workspace: "Verwaltungsdaten",
    namespace: "urn:data-dna:govdata",
    credentials: {
      username: "p2d2_wfs_user",
      password: "eif1nu4ao9Loh0oobeev"
    }
  },
  production: {
    endpoint: "https://wfs.data-dna.eu/geoserver/Verwaltungsdaten/ows",
    workspace: "Verwaltungsdaten",
    namespace: "urn:data-dna:govdata", 
    credentials: {
      username: "p2d2_wfs_user",
      password: "eif1nu4ao9Loh0oobeev"
    }
  },
  staging: {
    endpoint: "https://wfs-staging.data-dna.eu/geoserver/ows",
    workspace: "Verwaltungsdaten",
    namespace: "urn:data-dna:govdata",
    credentials: {
      username: "p2d2_wfs_user_staging",
      password: "staging_password"
    }
  }
};

Layer Styling Configuration

typescript
// Standard styles for WFS vector layers
const WFS_LAYER_STYLES = {
  default: new Style({
    stroke: new Stroke({
      color: "#FF6900",  // p2d2 Orange
      width: 2,
    }),
    fill: new Fill({
      color: "rgba(255, 105, 0, 0.1)",
    }),
  }),
  highlighted: new Style({
    stroke: new Stroke({
      color: "#FF4500",  // More intense orange
      width: 3,
    }),
    fill: new Fill({
      color: "rgba(255, 69, 0, 0.2)",
    }),
  }),
  selected: new Style({
    stroke: new Stroke({
      color: "#DC143C",  // Red for selection
      width: 4,
    }),
    fill: new Fill({
      color: "rgba(220, 20, 60, 0.3)",
    }),
  })
};

CQL Filter Conventions

typescript
// Standard CQL filters for various scenarios
const CQL_FILTERS = {
  // For cemeteries in Cologne
  CEMETERY_KOELN: "wp_name='Köln' AND container_type='cemetery' AND osm_admin_level=8",
  
  // For administrative boundaries
  ADMINISTRATIVE_KOELN: "wp_name='Köln' AND container_type='administrative' AND osm_admin_level=7",
  
  // For all container types in a municipality
  ALL_TYPES: (wpName: string, adminLevel: number) => 
    `wp_name='${wpName}' AND osm_admin_level=${adminLevel}`,
  
  // BBox-based filters
  IN_BBOX: (bbox: number[], crs: string = "EPSG:4326") => 
    `BBOX(geometry,${bbox.join(',')},'${crs}')`
};

Performance Optimizations

1. Layer Caching

typescript
// Efficient caching of WFS layers
private layerCache = new Map<string, VectorLayer<VectorSource>>();

async function getCachedWFSLayer(config: WFSLayerConfig): Promise<VectorLayer<VectorSource>> {
  const cacheKey = `${config.wpName}-${config.containerType}-${config.osmAdminLevel}`;
  
  // Check cache
  let layer = this.layerCache.get(cacheKey);
  if (!layer) {
    // Create and cache layer
    layer = await this.createWFSLayer(config);
    this.layerCache.set(cacheKey, layer);
    this.map.addLayer(layer);
  }
  
  return layer;
}

2. Request Batching

typescript
// Combine multiple WFS requests
async function batchWFSRequests(requests: Array<{
  typeName: string;
  params: Record<string, string>;
}>) {
  const results = await Promise.allSettled(
    requests.map(req => wfsAuthClient.getFeatures(req.typeName, req.params))
  );
  
  return results.map((result, index) => ({
    request: requests[index],
    success: result.status === 'fulfilled',
    data: result.status === 'fulfilled' ? result.value : null,
    error: result.status === 'rejected' ? result.reason : null
  }));
}

3. Connection Pooling

typescript
// Reuse HTTP connections
class WFSConnectionPool {
  private activeConnections: Map<string, Promise<Response>> = new Map();
  
  async getConnection(url: string, options: RequestInit = {}): Promise<Response> {
    const connectionKey = `${url}-${JSON.stringify(options)}`;
    
    // Reuse existing connection
    if (this.activeConnections.has(connectionKey)) {
      return this.activeConnections.get(connectionKey)!;
    }
    
    // Create new connection
    const connectionPromise = wfsAuthClient.fetchWithAuth(url, options);
    this.activeConnections.set(connectionKey, connectionPromise);
    
    // Remove connection after completion
    connectionPromise.finally(() => {
      this.activeConnections.delete(connectionKey);
    });
    
    return connectionPromise;
  }
}

Error Handling

Robust WFS Requests

typescript
// Comprehensive error handling for WFS operations
async function resilientWFSRequest(
  typeName: string,
  params: Record<string, string>,
  options: {
    maxRetries?: number;
    retryDelay?: number;
    timeout?: number;
  } = {}
): Promise<any> {
  const {
    maxRetries = 3,
    retryDelay = 1000,
    timeout = 30000
  } = options;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), timeout);
      
      const result = await wfsAuthClient.getFeatures(typeName, {
        ...params,
        signal: controller.signal
      });
      
      clearTimeout(timeoutId);
      return result;
      
    } catch (error) {
      console.warn(`WFS request failed (Attempt ${attempt + 1}/${maxRetries + 1})`, error);
      
      if (attempt === maxRetries) {
        throw new Error(`WFS request failed after ${maxRetries + 1} attempts: ${error.message}`);
      }
      
      // Exponential backoff
      await new Promise(resolve => 
        setTimeout(resolve, retryDelay * Math.pow(2, attempt))
      );
    }
  }
}

Graceful Degradation

typescript
// Fallback strategies for WFS errors
async function loadWFSDataWithFallback(
  kommune: KommuneData,
  categorySlug: string
) {
  try {
    // Primary: WFS service
    return await wfsManager.displayLayer(kommune, categorySlug);
  } catch (error) {
    console.error("WFS service unavailable, using fallback", error);
    
    // Fallback 1: Local GeoJSON data
    const localData = await loadLocalGeoJSON(kommune.slug, categorySlug);
    if (localData) {
      return displayLocalData(localData);
    }
    
    // Fallback 2: Show static boundaries
    return displayStaticBoundaries(kommune);
    
    // Fallback 3: Inform user
    showUserNotification({
      type: "warning",
      message: "Geodata temporarily unavailable",
      action: "retry"
    });
  }
}

Security Aspects

Credential Management

typescript
// Secure credential usage
class SecureWFSAuth {
  private encryptCredentials(credentials: WFSCredentials): string {
    // In production: Use secure encryption
    if (process.env.NODE_ENV === 'production') {
      return btoa(`${credentials.username}:${credentials.password}`);
    }
    
    // In development: Plaintext with warning
    console.warn(
      "Using unencrypted credentials in development environment. " +
      "In production, credentials should be provided via environment variables."
    );
    return btoa(`${credentials.username}:${credentials.password}`);
  }
  
  private getCredentialsFromEnv(): WFSCredentials {
    // Prefer environment variables
    return {
      username: process.env.WFS_USERNAME || this.config.credentials.username,
      password: process.env.WFS_PASSWORD || this.config.credentials.password
    };
  }
}

Request Validation

typescript
// Input validation for WFS parameters
function validateWFSParams(params: Record<string, string>): boolean {
  const allowedParams = [
    "bbox", "maxFeatures", "CQL_FILTER", "propertyName", 
    "sortBy", "srsName", "outputFormat"
  ];
  
  const maxLengths = {
    CQL_FILTER: 1000,
    propertyName: 500,
    bbox: 100
  };
  
  // Check allowed parameters
  for (const key of Object.keys(params)) {
    if (!allowedParams.includes(key)) {
      console.warn(`Disallowed WFS parameter: ${key}`);
      return false;
    }
    
    // Check length restrictions
    if (maxLengths[key] && params[key].length > maxLengths[key]) {
      console.warn(`WFS parameter too long: ${key}`);
      return false;
    }
  }
  
  return true;
}

Best Practices

1. CQL Filter Optimization

typescript
// ✅ Correct - Efficient CQL filters
function buildOptimizedCQLFilter(kommune: KommuneData, category: string): string {
  const containerType = getContainerType(category);
  const adminLevel = getOsmAdminLevel(kommune, containerType);
  
  return `wp_name='${kommune.wp_name}' AND container_type='${containerType}' AND osm_admin_level=${adminLevel}`;
}

// ❌ Avoid - Inefficient filters
// Complex OR/AND combinations without index

2. Error Handling

typescript
// ✅ Correct - Comprehensive error handling
async function loadWFSLayerSafely(kommune: KommuneData, category: string) {
  try {
    // Validation
    if (!hasValidOSMData(kommune)) {
      throw new Error(`Municipality ${kommune.slug} has no valid OSM data`);
    }
    
    // Set timeout
    const timeoutPromise = new Promise((_, reject) => 
      setTimeout(() => reject(new Error('WFS request timeout')), 30000)
    );
    
    // WFS request with timeout
    const layerPromise = wfsManager.displayLayer(kommune, category);
    return await Promise.race([layerPromise, timeoutPromise]);
    
  } catch (error) {
    logger.error("WFS layer loading failed", error, {
      kommune: kommune.slug,
      category: category
    });
    
    // User-friendly error message
    showErrorToUser("Geodata could not be loaded. Please try again later.");
    throw error;
  }
}

3. Performance Monitoring

typescript
// Track WFS performance
async function trackWFSPerformance<T>(
  operation: string,
  fn: () => Promise<T>
): Promise<T> {
  const startTime = performance.now();
  
  try {
    const result = await fn();
    const duration = performance.now() - startTime;
    
    // Logging for performance analysis
    logger.info(`WFS ${operation} completed`, {
      operation,
      duration: Math.round(duration),
      timestamp: new Date().toISOString()
    });
    
    // Warning for slow requests
    if (duration > 5000) {
      logger.warn(`Slow WFS request: ${operation}`, { duration });
    }
    
    return result;
  } catch (error) {
    const duration = performance.now() - startTime;
    logger.error(`WFS ${operation} failed`, error, {
      operation,
      duration: Math.round(duration)
    });
    throw error;
  }
}

Dependencies

External Libraries

  • OpenLayers - Map and layer management
  • proj4 - Coordinate transformations

Internal Dependencies

  • ../utils/logger - Logging infrastructure
  • ../utils/events - Event system
  • ../utils/kommune-utils - Municipality data management

These WFS Integration utilities provide secure, performant, and reliable access to geodata services in p2d2, with comprehensive error handling and optimized performance for large datasets.