Skip to content

sitmun/sitna-npm-test

Repository files navigation

SITNA + Angular Integration Guide

This project demonstrates best practices for embedding the SITNA mapping library in an Angular application. This is an example implementation showing how SITNA integration can be done, not a complete production solution.

Key Features Demonstrated:

  • Type-safe configuration with dedicated services
  • Asset overriding for customizing SITNA defaults
  • Custom configuration for layout and UI components
  • Runtime behavior changes using meld
  • Centralized JSON configuration for all SITNA settings
  • Modern Angular patterns and dependency injection

Table of Contents


Project Purpose

This project serves as a demonstration and learning resource, not a production-ready solution.

What This Project Demonstrates

  1. Integration Patterns: How to integrate SITNA with Angular using service-based architecture
  2. Customization Approaches: Different ways to customize SITNA behavior and appearance
  3. Runtime Patching: How to modify SITNA behavior without changing source code
  4. Type Safety: How to implement type-safe SITNA integration
  5. Asset Management: How to override and customize SITNA assets
  6. Configuration Management: How to externalize all settings to JSON files

What This Project Is NOT

  • Production Ready: This is a demonstration, not a complete application
  • Comprehensive: Does not cover all SITNA features or use cases
  • One-Size-Fits-All: Your specific requirements may need different approaches
  • Maintained Solution: This is an example to learn from, not a maintained library

How to Use This Project

  1. Study the Patterns: Understand the integration approaches demonstrated
  2. Adapt to Your Needs: Modify the patterns to fit your specific requirements
  3. Extend Functionality: Add features based on your application needs
  4. Test Thoroughly: Ensure the adapted solution works for your use case
  5. Maintain Independently: This project is not maintained as a dependency

Project Structure

src/
├── app/
│   ├── services/
│   │   ├── sitna-config.service.ts      # Main SITNA configuration service
│   │   ├── sitna-config-loader.service.ts # JSON configuration loader
│   │   ├── sitna-meld-patch.ts          # Runtime patching with meld
│   │   └── app-initializer.service.ts   # Application initialization
│   └── components/
│       └── sitna-map/                   # Map component
│           ├── sitna-map.component.ts
│           ├── sitna-map.component.html
│           └── sitna-map.component.scss
├── types/                               # Centralized type definitions
│   ├── sitna.types.ts                   # SITNA interfaces
│   ├── meld.types.ts                    # meld library types
│   ├── meld.d.ts                        # meld module declaration
│   └── index.ts                         # Export all types
├── environments/
│   └── sitna-config.json               # Centralized configuration
├── assets/
│   ├── js/api-sitna/                    # SITNA library files (overriding defaults)
│   │   └── config/                       # Custom configuration files
│   └── layout/sample-layout/             # Custom layout configuration
│       ├── config.json                   # Layout configuration
│       ├── markup.html                   # HTML template
│       ├── script.js                     # Custom JavaScript
│       ├── style.css                     # Custom styles
│       └── resources/                    # Layout-specific resources
└── main.ts                              # Application bootstrap

Installation & Quick Start

Installation

  1. Clone the repository:

    git clone <repository-url>
    cd sitna-npm-test
  2. Install dependencies:

    npm install
  3. Start the development server:

    npm start
  4. Open your browser and navigate to http://localhost:4200

Quick Start

The project is pre-configured with SITNA integration. Here's how it works:

Application Bootstrap (src/main.ts): The application starts by importing SITNA and bootstrapping Angular. Runtime patches are applied after configuration is loaded.

// Import SITNA first to ensure it's available
import 'api-sitna';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

Application Initialization (src/app/services/app-initializer.service.ts): The application automatically configures SITNA during startup:

async initializeApp(): Promise<void> {
  // Load configuration first
  await this.configLoader.loadConfig().toPromise();
  
  // Initialize SITNA configuration
  await this.initializeSITNAConfiguration();
  
  // Apply meld patches after configuration is loaded
  patchSitnaMapLogging(this.configLoader);
}

Configuration Service (src/app/services/sitna-config.service.ts): This service provides a type-safe interface for all SITNA operations:

@Injectable({
  providedIn: 'root'
})
export class SitnaConfigService {
  setLayout(layoutPath: string): void
  addWMSLayer(layer: Omit<SITNALayer, 'type'>): void
  initializeMap(elementId: string): any
  setDefaultMapOptions(options: Partial<SITNAMapOptions>): void
  getConfig(): SITNAConfig
}

Automatic Configuration: When the application starts, it automatically configures SITNA with:

  • Custom layout (assets/layout/sample-layout) - A completely custom UI layout
  • Overridden SITNA assets (assets/js/api-sitna/) - Custom configuration files
  • JSON configuration (environments/sitna-config.json) - All settings externalized
  • Runtime patching - All SITNA method calls are logged for debugging
  • Type-safe interfaces - Full TypeScript support for SITNA objects

Integration Approaches

This project demonstrates three main approaches to customizing SITNA:

Asset Overriding

The src/assets/js/api-sitna/ directory is an example of overriding some SITNA defaults. This approach allows you to:

What can be overridden?

  1. Configuration Files (config/)

    • browser-versions.json - Custom browser compatibility settings
    • predefined-layers.json - Custom layer definitions
  2. Styling and Assets (css/)

    • Custom CSS files and styles
    • Font files and icons
    • Image assets and markers
  3. Layout Templates (layout/)

    • Custom HTML templates
    • Responsive layouts
    • UI component configurations
  4. Library Files (lib/)

    • Custom JavaScript libraries
    • Third-party integrations
    • Utility functions
  5. Resources and Data (resources/)

    • Custom coordinate reference systems (CRS)
    • Language files and translations
    • Geographic data files
  6. WMTS Configurations (wmts/)

    • Custom tile service configurations
    • Map provider settings

Benefits of Asset Overriding

  • Customization: Tailor SITNA to your specific needs
  • Version Control: Track customizations in your repository
  • Deployment Control: Ensure consistent behavior across environments
  • Maintainability: Keep customizations separate from core library
  • Upgrade Safety: Preserve customizations during SITNA updates

How Overriding Works

The overriding mechanism works through Angular's asset serving and SITNA's file loading system:

Angular Asset Serving: Angular automatically serves all files from the src/assets/ directory at the root URL of your application. When you place files in src/assets/js/api-sitna/, they become available at /js/api-sitna/ in your web application.

SITNA File Loading: SITNA loads its configuration files, styles, and resources from relative paths within its library structure. When SITNA requests a file like /js/api-sitna/config/predefined-layers.json, Angular serves your custom file from src/assets/js/api-sitna/config/predefined-layers.json instead of SITNA's original file.

The Override Process:

  1. SITNA requests a file from its expected path
  2. Angular intercepts this request and serves your custom file from the assets directory
  3. SITNA uses your custom configuration instead of its default

Directory Structure Mirroring: Your override directory structure must mirror SITNA's internal structure. If SITNA expects files in /js/api-sitna/config/, you place your custom files in src/assets/js/api-sitna/config/.

Custom Configuration

The src/assets/layout/sample-layout/ directory is an example of custom configuration for SITNA layouts. This approach allows you to create entirely new layouts and UI configurations.

What is Custom Configuration

Custom configuration involves creating new layouts and UI components that extend SITNA's capabilities:

  1. Layout Configuration (config.json)

    • Define layout structure and components
    • Configure UI element positioning
    • Set default behaviors and options
  2. HTML Templates (markup.html)

    • Define the structure of your custom layout
    • Create custom UI components
    • Integrate with Angular components
  3. Custom Styles (style.css)

    • Define the visual appearance
    • Create responsive designs
    • Implement custom themes
  4. Custom JavaScript (script.js)

    • Add custom functionality
    • Handle user interactions
    • Integrate with external services
  5. Layout Resources (resources/)

    • Language files for the layout
    • Custom icons and assets
    • Layout-specific configurations

Benefits of Custom Configuration

  • Complete Control: Design layouts from scratch
  • Brand Consistency: Match your application's design
  • Functionality: Add custom features and behaviors
  • Flexibility: Create layouts for specific use cases
  • Maintainability: Keep custom layouts separate from core SITNA

How Custom Configuration Works

Custom layouts work through SITNA's layout system and Angular's asset serving:

Layout Registration: When you call setLayout('assets/layout/sample-layout') in your Angular service, you're telling SITNA to use a custom layout instead of its default interface.

Layout File Loading: SITNA expects custom layouts to include several specific files: a configuration file that defines the layout structure, an HTML template that provides the markup, custom styles for visual appearance, JavaScript for custom functionality, and optional resources like language files or icons.

The Layout Loading Process:

  1. SITNA receives your layout specification
  2. SITNA requests the necessary files from the specified path
  3. Angular serves those files from your assets directory
  4. SITNA uses your custom layout instead of its default

Implementation in AppInitializerService:

// src/app/services/app-initializer.service.ts
private async initializeSITNAConfiguration(): Promise<void> {
      // Set custom layout from configuration - this enforces the sample layout
  const layout = this.configLoader.getLayout();
  this.sitnaConfigService.setLayout(layout);
  
  // Set default map options from configuration
  const defaultMapOptions = this.configLoader.getDefaultMapOptions();
  this.sitnaConfigService.setDefaultMapOptions(defaultMapOptions);
  
  // Configure default layers from configuration
  await this.configureDefaultLayers();
}

The custom configuration is enforced during application initialization, ensuring:

  • Custom layout is loaded before any map components
  • Configuration is applied consistently across the application
  • Custom layout takes precedence over SITNA defaults

Runtime Behavior Changes

For logging, analytics, or custom behavior, use meld@1 to patch SITNA methods at runtime. This is robust, non-intrusive, and works for third-party libraries.

Current Patches

The project includes pre-configured patches in src/app/services/sitna-meld-patch.ts that modify the default behavior of SITNA:

import meld from 'meld';

export function patchSitnaMapLogging(configLoader?: SitnaConfigLoaderService) {
  const waitForSITNA = () => {
    const mapProto = (window as any)?.SITNA?.Map?.prototype;
    const coordinatesProto = (window as any)?.TC?.control?.Coordinates?.prototype;
    
    if (mapProto && coordinatesProto) {
      applyPatches(mapProto, coordinatesProto, configLoader);
    } else {
      setTimeout(waitForSITNA, 100);
    }
  };
  
  waitForSITNA();
}

Behavior Changes Implemented

The patches in sitna-meld-patch.ts modify SITNA's default behavior in the following ways:

  1. SITNA.Map.addLayer Method:

    • Default Behavior: Silently adds layers to the map
    • Modified Behavior: Logs every layer addition with arguments
    • Impact: Provides visibility into layer management operations
  2. TC.control.Coordinates Constructor:

    • Default Behavior: Creates coordinate controls without logging
    • Modified Behavior: Logs constructor calls with arguments
    • Impact: Tracks coordinate control creation
  3. TC.control.Coordinates.isPointerOver Method:

    • Default Behavior: Checks pointer position without logging
    • Modified Behavior: Logs pointer checks and maintains original functionality
    • Impact: Provides debugging information for pointer interactions

Configuration-Driven Patching

The patches respect configuration settings from sitna-config.json:

function applyPatches(mapProto: any, coordinatesProto: any, configLoader?: SitnaConfigLoaderService): void {
  try {
    // Get patching configuration if available
    let patchingConfig;
    try {
      patchingConfig = configLoader?.getPatchingConfig();
    } catch (error) {
      // If configuration is not available, use default patching behavior
      patchingConfig = {
        enabled: true,
        logAddLayer: true,
        logCoordinates: true
      };
    }

    if (!patchingConfig.enabled) {
      console.log('meld: Patching disabled by configuration');
      return;
    }

    // Apply patches based on configuration
    if (patchingConfig.logAddLayer) {
      meld.before(mapProto, 'addLayer', function (...args: any[]) {
        console.log('meld: addLayer called with:', ...args);
      });
    }
  } catch (error) {
    console.error('meld: Failed to apply patches:', error);
  }
}

Patching Patterns

  • meld.before - Log arguments before the original method runs
  • meld.around - Full control: log, modify arguments, or skip the original method
  • meld.after - Log results after the method completes

Difference Between Approaches

Aspect Asset Overriding Custom Configuration Runtime Behavior Changes
Purpose Replace SITNA defaults Create new layouts Modify method behavior
Location assets/js/api-sitna/ assets/layout/custom-name/ sitna-meld-patch.ts
Scope Override existing files Create new layouts Modify runtime behavior
Flexibility Limited to SITNA structure Complete freedom Full control over methods
Maintenance Must follow SITNA conventions Independent structure Must test after upgrades

Configuration Management

The project uses a centralized JSON configuration file for all SITNA settings.

Configuration File

Location: src/environments/sitna-config.json

This file contains all SITNA configuration including:

  • Base URL: SITNA library location
  • Layout: Custom layout path
  • Map Options: Default projection, center, zoom levels
  • Layers: Default WMS layers to load
  • Controls: Layer catalog configuration
  • Patching: Runtime patching settings

Configuration Structure

{
  "sitna": {
    "baseUrl": "assets/js/api-sitna/",
    "layout": "assets/layout/sample-layout",
    "defaultMapOptions": {
      "projection": "EPSG:4326",
      "center": [0, 0],
      "zoom": 2,
      "minZoom": 1,
      "maxZoom": 18
    },
    "layers": {
      "default": [
        {
          "id": "idena",
          "title": "IDENA",
          "hideTitle": true,
          "url": "//idena.navarra.es/ogc/wms",
          "hideTree": false
        },
        {
          "id": "sismica",
          "title": "Información sísmica y volcánica",
          "url": "//www.ign.es/wms-inspire/geofisica",
          "layerNames": ["Ultimos10dias", "Ultimos30dias", "Ultimos365dias"],
          "hideTree": false
        }
      ]
    },
    "controls": {
      "layerCatalog": {
        "div": "slot2",
        "enableSearch": true
      }
    },
    "patching": {
      "enabled": true,
      "logAddLayer": true,
      "logCoordinates": true
    }
  }
}

Configuration Service

The SitnaConfigLoaderService handles loading and accessing configuration:

// Load configuration
await this.configLoader.loadConfig().toPromise();

// Access configuration
const layout = this.configLoader.getLayout();
const layers = this.configLoader.getDefaultLayers();
const mapOptions = this.configLoader.getDefaultMapOptions();

Benefits

  • Environment-specific: Different configs for dev/prod
  • Centralized: All settings in one place
  • Type-safe: TypeScript interfaces for configuration
  • No Defaults: Service throws errors if config is missing
  • Flexible: Easy to modify without code changes

SITNA Configuration in Angular

All SITNA configuration is handled via the SitnaConfigService. This service provides a type-safe, Angular-friendly interface for all SITNA operations.

Basic Configuration

import { SitnaConfigService } from './services/sitna-config.service';

@Component({...})
export class MyComponent {
  constructor(private sitnaConfigService: SitnaConfigService) {}

  ngOnInit() {
    // Set custom layout
    this.sitnaConfigService.setLayout('assets/layout/sample-layout');
    
    // Configure default map options
    this.sitnaConfigService.setDefaultMapOptions({
      projection: 'EPSG:4326',
      center: [0, 0],
      zoom: 2
    });
    
    // Add WMS layers
    this.sitnaConfigService.addWMSLayer({
      id: 'idena',
      title: 'IDENA',
      url: '//idena.navarra.es/ogc/wms',
      hideTree: false
    });
  }
}

Note: In this project, configuration is handled by the AppInitializerService during application startup, so components only need to initialize the map.

Available Configuration Methods

  • setLayout(layoutPath: string) - Set the SITNA layout (custom or default)
  • setDefaultMapOptions(options: Partial<SITNAMapOptions>) - Configure default map settings
  • addWMSLayer(layer: Omit<SITNALayer, 'type'>) - Add WMS layers
  • initializeMap(elementId: string) - Initialize map in a DOM element
  • initializeMapWithOptions(elementId: string, options: Partial<SITNAMapOptions>) - Initialize map with custom options
  • getConfig() - Get current configuration
  • getSITNAInstance() - Access the SITNA instance
  • getDefaultMapOptions() - Get current default map options
  • configureLayerCatalog(config: SITNALayerCatalog) - Configure the layer catalog

Type Safety

The project includes comprehensive TypeScript interfaces:

export interface SITNALayer {
  id: string;
  title: string;
  hideTitle?: boolean;
  type: any;
  url: string;
  layerNames?: string[];
  hideTree?: boolean;
}

export interface SITNALayerCatalog {
  div: string;
  enableSearch: boolean;
  layers: SITNALayer[];
}

export interface SITNAControls {
  layerCatalog?: SITNALayerCatalog;
}

export interface SITNAConfig {
  layout?: string;
  controls?: SITNAControls;
}

export interface SITNAMapOptions {
  projection?: string;
  center?: [number, number];
  zoom?: number;
  minZoom?: number;
  maxZoom?: number;
  extent?: [number, number, number, number];
  layers?: any[];
  controls?: any[];
  [key: string]: any;
}

Project Architecture

Service-Based Design

The project follows Angular best practices with service-based architecture:

  1. SitnaConfigService - Centralized configuration management
  2. SitnaConfigLoaderService - JSON configuration loading
  3. AppInitializerService - Application-wide initialization
  4. sitna-meld-patch.ts - Runtime patching logic
  5. Component integration - Clean separation of concerns

Asset Management

  • SITNA Library: Located in src/assets/js/api-sitna/ (overriding defaults)
  • Custom Layouts: Located in src/assets/layout/ (custom configurations)
  • Configuration: Located in src/environments/ (JSON configuration)
  • Type Definitions: src/types/ (centralized types)

Initialization Flow

// 1. Application bootstrap (main.ts)
import 'api-sitna';
platformBrowserDynamic().bootstrapModule(AppModule);

// 2. Service initialization (app-initializer.service.ts)
await this.configLoader.loadConfig().toPromise();
await this.initializeSITNAConfiguration();
patchSitnaMapLogging(this.configLoader);

// 3. Component usage (sitna-map.component.ts)
this.map = this.sitnaConfigService.initializeMap('mapa');

The custom configuration is enforced in initializeSITNAConfiguration():

  • Custom Layout: setLayout() - Enforces custom layout from configuration
  • Map Options: setDefaultMapOptions() - Sets default map configuration
  • Default Layers: configureDefaultLayers() - Adds predefined WMS layers

Best Practices

Configuration

  • Use services for all SITNA interaction
  • Configure before map initialization
  • Use TypeScript interfaces for type safety
  • Keep configuration centralized in JSON files
  • No hardcoded defaults in services

Asset Overriding

  • Override assets in src/assets/js/api-sitna/
  • Maintain the same directory structure as SITNA defaults
  • Document customizations for team members
  • Test overrides after SITNA upgrades

Custom Configuration

  • Create custom layouts in src/assets/layout/
  • Follow SITNA layout conventions for compatibility
  • Document layout structure and dependencies
  • Test custom layouts thoroughly

Runtime Behavior Changes

  • Patch only after SITNA is loaded
  • Never modify SITNA source code directly
  • Keep all patching logic in dedicated files
  • Document all patches and their purpose
  • Use TypeScript for all patching code
  • Test behavior changes thoroughly
  • Document the original vs modified behavior
  • Use configuration to control patching behavior

Development

  • Follow Angular dependency injection patterns
  • Use service-based architecture
  • Maintain type safety throughout
  • Test patches after SITNA upgrades
  • Externalize all configuration to JSON files

Troubleshooting

Common Issues

Configuration not loading:

  • Check that src/environments/sitna-config.json exists
  • Verify the file is copied to dist/assets/environments/
  • Ensure angular.json includes environments in assets

Patch not applied:

  • Check that SITNA prototypes are available at patch time
  • Ensure patch function is called after configuration is loaded
  • Verify configuration includes patching settings

Configuration not working:

  • Ensure configuration is done before map initialization
  • Check that services are properly injected
  • Verify asset paths are correct

Map not displaying:

  • Check browser console for errors
  • Verify DOM element exists before initialization
  • Ensure SITNA assets are properly loaded

Asset overrides not working:

  • Verify files are in the correct src/assets/js/api-sitna/ structure
  • Check that Angular is serving the overridden files
  • Ensure file names match SITNA's expected names

Custom layout not loading:

  • Verify layout files are in the correct src/assets/layout/ structure
  • Check that all required files exist (config.json, markup.html, etc.)
  • Ensure layout path is correctly specified in configuration

Debugging Tips

  1. Enable console logging - Patches provide detailed logging
  2. Check network requests - Verify SITNA assets are loading
  3. Inspect DOM - Ensure map container exists
  4. Review service injection - Verify Angular DI is working
  5. Check asset paths - Verify overridden files are being served
  6. Validate layout files - Ensure custom layout files are properly structured
  7. Check configuration loading - Verify JSON config is accessible

For Future Developers

Understanding This Project

This is an example implementation, not a production solution. Use it to:

  • Learn Integration Patterns: Understand how SITNA can be integrated with Angular
  • Study Customization Approaches: See different ways to customize SITNA behavior
  • Reference Implementation: Use as a reference when building your own solution
  • Experiment Safely: Test different approaches in a controlled environment

Key Principles

  • Always use services for configuration and patching
  • Use meld for runtime patching/logging of third-party libraries
  • Keep all patching logic isolated and well-documented
  • Test patches after SITNA upgrades
  • Prefer meld.around for full control, meld.before/meld.after for logging
  • Externalize all configuration to JSON files
  • No hardcoded defaults in services

Extension Points

  • Add new configuration methods to SitnaConfigService or similar.
  • Create custom patches in sitna-meld-patch.ts or similar.
  • Extend type definitions in src/types/ or similar.
  • Create custom layouts in assets/layout/ or similar.
  • Override SITNA assets in assets/js/api-sitna/ or similar.
  • Add new configuration options to sitna-config.json.

Adaptation Guidelines

When adapting this example for your project:

  1. Assess Your Needs: Determine which patterns fit your requirements
  2. Simplify or Extend: Remove unnecessary features or add missing ones
  3. Customize Thoroughly: Adapt to your specific use case and data
  4. Test Extensively: Ensure reliability with your data and users
  5. Document Changes: Keep track of what you've modified and why

Maintenance

  • Keep dependencies updated
  • Monitor SITNA library updates
  • Test patches after upgrades
  • Maintain type safety
  • Document asset overrides and custom configurations
  • Maintain your own fork: This project is not maintained as a dependency

Production Considerations

Before using any patterns from this example in production:

  • Security: Review all customizations for security implications
  • Performance: Test with your expected load and data volumes
  • Compatibility: Ensure compatibility with your target environments
  • Accessibility: Verify customizations meet accessibility requirements
  • Error Handling: Implement robust error handling for your use case
  • Monitoring: Add appropriate logging and monitoring for production use

This approach ensures your Angular+SITNA integration is robust, maintainable, and easy to extend or debug, while understanding that this project serves as a learning resource rather than a complete solution.


References

About

Dummy project to test the installation of the Sitna API Library through NPM

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors