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

Code Style ​

Overview ​

This documentation describes the code style conventions and best practices for p2d2 development. The focus is on TypeScript, Astro components, and OpenLayers integration.

TypeScript Configuration ​

tsconfig.json ​

json
{
  "extends": "astro/tsconfigs/strict",
  "include": [".astro/types.d.ts", "**/*"],
  "exclude": ["dist"],
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "*": ["node_modules/*"],
      "@/*": ["./src/*"],
      "@utils/*": ["./src/utils/*"]
    },
    "jsx": "preserve"
  }
}

Important Settings:

  • strict: true - Strict TypeScript checking
  • module: esnext - ES modules for modern browsers
  • paths - Aliases for better import structure
  • jsx: preserve - JSX support for Astro components

Path Aliases ​

typescript
// ❌ Avoid
import { registerUtm } from '../../../utils/crs';

// ✅ Recommended
import { registerUtm } from '@utils/crs';
import { MapConfig } from '@/types/map';

ESLint & Prettier ​

Status: Currently not configured

bash
# ESLint with TypeScript and Astro support
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-astro

# Prettier for code formatting
npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

Example Configuration ​

.eslintrc.json:

json
{
  "extends": [
    "eslint:recommended",
    "@typescript-eslint/recommended",
    "plugin:astro/recommended",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "rules": {
    "@typescript-eslint/no-unused-vars": "error",
    "@typescript-eslint/explicit-function-return-type": "warn",
    "prefer-const": "error"
  }
}

.prettierrc:

json
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}

Naming Conventions ​

Files ​

Utility Functions: kebab-case

src/utils/crs.ts
src/utils/layer-management.ts
src/utils/events.ts

Astro Components: PascalCase

src/components/Map.astro
src/components/KommuneCard.astro
src/layouts/BaseLayout.astro

TypeScript Types: PascalCase

src/types/map.ts
src/types/kommunen.ts
src/types/api.ts

Variables and Functions ​

camelCase for variables and functions:

typescript
// ✅ Good
const mapInstance = new Map();
const isValidCoordinate = isValidWgs84Coordinate(coord);
function transformCoordinate(coord: number[], targetEpsg: string) { ... }

// ❌ Avoid
const MAP_INSTANCE = new Map();
function Transform_Coordinate(coord: number[]) { ... }

UPPER_CASE for constants:

typescript
// ✅ Good
const MAPCONFIG = {
  center: [6.9578, 50.9375],
  zoom: 10
};

const STORAGE_KEYS = {
  SELECTED_CRS: 'p2d2_selected_crs',
  SELECTED_KOMMUNE: 'p2d2_selected_kommune'
};

PascalCase for types and interfaces:

typescript
// ✅ Good
interface MapConfig {
  center: [number, number];
  zoom: number;
  projection: string;
}

type Coordinate = [number, number];
type Extent = [number, number, number, number];

OpenLayers-specific Conventions ​

Layer Names:

typescript
// ✅ Good
const luftbildLayer = new TileLayer({ /* ... */ });
const wfsLayer = new VectorLayer({ /* ... */ });
const kommunenLayer = new VectorLayer({ /* ... */ });

// Layer Styles
const highlightStyle = new Style({ /* ... */ });
const defaultStyle = new Style({ /* ... */ });

Event Handlers:

typescript
// ✅ Good
function handleMapMoveEnd(event: MapEvent) {
  const center = map.getView().getCenter();
  console.log('Map center:', center);
}

function handleFeatureSelect(event: SelectEvent) {
  const feature = event.selected[0];
  // Feature processing
}

Import Conventions ​

Import Order ​

typescript
// 1. External Libraries (alphabetical)
import Map from 'ol/Map';
import View from 'ol/View';
import proj4 from 'proj4';

// 2. TypeScript Types (if separate imports)
import type { MapConfig } from '@/types/map';
import type { Coordinate } from '@/types/geo';

// 3. Internal Utilities (alphabetical)
import { registerUtm } from '@utils/crs';
import { dispatchKommunenFocus } from '@utils/events';
import { createLuftbildLayer } from '@utils/layer-management';

// 4. Local Imports (relative)
import { MAPCONFIG } from '../config/map-config';
import KommuneCard from '../components/KommuneCard.astro';

Type-only Imports ​

typescript
// ✅ Good - Type-only import (no runtime dependency)
import type { MapConfig } from '@/types/map';
import type { Feature } from 'ol';

// ❌ Avoid - unnecessary runtime imports
import { MapConfig } from '@/types/map'; // When only type is used

Code Organization ​

Utility Functions ​

Single responsibility per function:

typescript
// ✅ Good - Clear responsibility
export function isValidWgs84Coordinate(coord: any): boolean {
  return (
    Array.isArray(coord) &&
    coord.length === 2 &&
    coord.every(Number.isFinite) &&
    coord[0] >= -180 && coord[0] <= 180 &&
    coord[1] >= -90 && coord[1] <= 90
  );
}

export function transformCoordinate(
  coord: number[], 
  sourceEpsg: string, 
  targetEpsg: string
): number[] | null {
  // Transformation logic
}

Error handling:

typescript
// ✅ Good - Explicit error handling
export function registerUtm(crs: string): boolean {
  if (registeredProjections.has(crs)) {
    return true; // Already registered
  }

  if (predefinedUtmDefs[crs]) {
    proj4.defs(crs, predefinedUtmDefs[crs]);
    registeredProjections.add(crs);
    register(proj4);
    return true;
  }

  console.error(`[crs] Unknown UTM projection: ${crs}`);
  return false;
}

Astro Components ​

Props typing:

astro
---
// ✅ Good - Explicit props typing
interface Props {
  kommune: KommuneData;
  showMap?: boolean;
  className?: string;
}

const { kommune, showMap = true, className = '' } = Astro.props;
---

<div class={`kommune-card ${className}`}>
  <h2>{kommune.name}</h2>
  {showMap && <Map.kommune={kommune} />}
</div>

Conditional rendering:

astro
---
// ✅ Good - Clear conditional logic
const hasDescription = kommune.description && kommune.description.length > 0;
const hasPopulation = kommune.population > 0;
---

<div>
  {hasDescription && (
    <p class="description">{kommune.description}</p>
  )}
  
  {hasPopulation ? (
    <span class="population">{kommune.population} inhabitants</span>
  ) : (
    <span class="no-data">No data</span>
  )}
</div>

Comments and Documentation ​

JSDoc for Utility Functions ​

typescript
/**
 * Validates WGS84 coordinates
 * @param coord - Coordinate to validate as [lng, lat]
 * @returns true if coordinate is valid
 * @example
 * isValidWgs84Coordinate([6.9578, 50.9375]) // true
 * isValidWgs84Coordinate([200, 100]) // false
 */
export function isValidWgs84Coordinate(coord: any): boolean {
  // Implementation
}

/**
 * Transforms coordinates between different CRS
 * @param coord - Source coordinate [x, y]
 * @param sourceEpsg - Source CRS (e.g. 'EPSG:4326')
 * @param targetEpsg - Target CRS (e.g. 'EPSG:25832')
 * @returns Transformed coordinate or null on error
 */
export function transformCoordinate(
  coord: number[], 
  sourceEpsg: string, 
  targetEpsg: string
): number[] | null {
  // Implementation
}

Inline Comments ​

typescript
// ✅ Good - Explains "why", not "what"
// Proj4 must be registered before OpenLayers can use it
proj4.defs('EPSG:25832', '+proj=utm +zone=32 +ellps=GRS80 +units=m +no_defs');
register(proj4);

// ❌ Bad - Commenting the obvious
// Validate coordinate
if (isValidWgs84Coordinate(coord)) {
  // Coordinate is valid
  return coord;
}

Best Practices ​

Error Handling ​

Consistent error messages:

typescript
// ✅ Good - Structured error messages
console.error('[crs] Invalid coordinate for transformation:', coord);
console.warn('[events] Event system not ready, queuing event:', eventName);
console.info('[map] Map initialized with projection:', projection);

Graceful degradation:

typescript
// ✅ Good - Robust against missing environments
export function getSelectedCRS(): string | null {
  if (typeof window === 'undefined') return null;
  return localStorage.getItem(STORAGE_KEYS.SELECTED_CRS);
}

export function dispatchThrottledEvent(eventName: string, detail: any = {}): void {
  if (typeof window === 'undefined') {
    console.warn(`[events] cannot dispatch ${eventName} - window undefined`);
    return;
  }
  // Event dispatch logic
}

Performance Optimizations ​

Event throttling:

typescript
// ✅ Good - Avoids excessive event triggering
function throttle<T extends (...args: any[]) => void>(
  func: T,
  delay: number
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
  let lastExecTime = 0;

  return (...args: Parameters<T>) => {
    const currentTime = Date.now();
    if (currentTime - lastExecTime < delay) {
      if (timeoutId) clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        lastExecTime = currentTime;
        func(...args);
      }, delay - (currentTime - lastExecTime));
    } else {
      lastExecTime = currentTime;
      func(...args);
    }
  };
}

Memory management:

typescript
// ✅ Good - Cleanup of event listeners
export function setupMapEvents(map: Map): () => void {
  const moveEndHandler = () => {
    console.log('Map moved');
  };

  map.on('moveend', moveEndHandler);

  // Return cleanup function
  return () => {
    map.un('moveend', moveEndHandler);
  };
}

// Usage
const cleanup = setupMapEvents(map);
// Clean up later
cleanup();

VS Code Configuration ​

.vscode/settings.json:

json
{
  "typescript.preferences.importModuleSpecifier": "relative",
  "typescript.suggest.autoImports": true,
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "files.associations": {
    "*.astro": "astro"
  }
}

.vscode/extensions.json:

json
{
  "recommendations": [
    "astro-build.astro-vscode",
    "bradlc.vscode-tailwindcss",
    "esbenp.prettier-vscode"
  ]
}

Debugging ​

TypeScript Debugging ​

Identify type errors:

typescript
// ✅ Good - Explicit types for better error messages
interface Coordinate {
  x: number;
  y: number;
}

function transformCoordinate(coord: Coordinate): Coordinate | null {
  // Instead of any[] we use the explicit interface
  if (!coord || typeof coord.x !== 'number' || typeof coord.y !== 'number') {
    console.error('[debug] Invalid coordinate:', coord);
    return null;
  }
  // Transformation logic
}

Runtime validation:

typescript
// ✅ Good - Validation for unsafe data
function isValidWgs84Coordinate(coord: any): coord is [number, number] {
  return (
    Array.isArray(coord) &&
    coord.length === 2 &&
    coord.every(Number.isFinite) &&
    coord[0] >= -180 && coord[0] <= 180 &&
    coord[1] >= -90 && coord[1] <= 90
  );
}

// Usage with type guard
if (isValidWgs84Coordinate(userInput)) {
  // TypeScript now knows it's [number, number]
  const transformed = transformCoordinate(userInput);
}

OpenLayers Debugging ​

Monitor map status:

typescript
// Map events for debugging
map.on('moveend', () => {
  const view = map.getView();
  console.log('[map] Center:', view.getCenter());
  console.log('[map] Zoom:', view.getZoom());
  console.log('[map] Projection:', view.getProjection().getCode());
});

// Monitor layer status
const layers = map.getLayers().getArray();
layers.forEach((layer, index) => {
  console.log(`[map] Layer ${index}:`, layer.get('name'), 'visible:', layer.getVisible());
});

Feature debugging:

typescript
// Log feature information
map.on('click', (event) => {
  const features = map.getFeaturesAtPixel(event.pixel);
  console.log('[map] Click features:', features?.length || 0);
  
  features?.forEach((feature, index) => {
    console.log(`[map] Feature ${index}:`, feature.getId(), feature.getProperties());
  });
});

Browser DevTools Debugging ​

Console logging with context:

typescript
// Check development environment
if (import.meta.env.DEV) {
  console.group('[p2d2] Debug Information');
  console.log('Environment:', import.meta.env.MODE);
  console.log('Map initialized:', !!window.map);
  console.log('Projection registered:', registeredProjections);
  console.groupEnd();
}

// Performance measurement
const startTime = performance.now();
// Execute code
const endTime = performance.now();
console.log(`[perf] Operation took ${endTime - startTime}ms`);

Network debugging:

typescript
// Monitor WFS requests
console.log('[wfs] Request URL:', url);
console.log('[wfs] Request parameters:', params);

fetch(url, params)
  .then(response => {
    console.log('[wfs] Response status:', response.status);
    console.log('[wfs] Response headers:', Object.fromEntries(response.headers));
    return response.text();
  })
  .then(data => {
    console.log('[wfs] Response data length:', data.length);
    // Data processing
  })
  .catch(error => {
    console.error('[wfs] Request failed:', error);
  });

VS Code Debugging ​

Set breakpoints:

typescript
// Debugger statement for complex logic
function complexTransformation(coord: number[], sourceCrs: string, targetCrs: string) {
  // Set breakpoint here
  debugger;
  
  // Complex transformation logic
  const intermediate = transformToWgs84(coord, sourceCrs);
  const result = transformFromWgs84(intermediate, targetCrs);
  
  return result;
}

Watch expressions:

// In VS Code Watch Panel:
window.map?.getView().getCenter()
window.map?.getView().getZoom()
registeredProjections.size

Common Debugging Scenarios ​

Coordinate transformation:

typescript
function debugCoordinateTransformation(coord: number[], sourceCrs: string, targetCrs: string) {
  console.group('[debug] Coordinate Transformation');
  console.log('Source coordinate:', coord);
  console.log('Source CRS:', sourceCrs);
  console.log('Target CRS:', targetCrs);
  
  try {
    const result = transformCoordinate(coord, sourceCrs, targetCrs);
    console.log('Transformation result:', result);
  } catch (error) {
    console.error('Transformation failed:', error);
  }
  
  console.groupEnd();
  return result;
}

Layer loading:

typescript
function debugLayerLoading(layerName: string, layer: any) {
  console.group(`[debug] Layer: ${layerName}`);
  console.log('Layer instance:', layer);
  console.log('Visible:', layer.getVisible());
  console.log('Z-Index:', layer.getZIndex());
  console.log('Source:', layer.getSource());
  console.groupEnd();
}

Code Review Checklist ​

✅ Must be fulfilled ​

  • [ ] TypeScript typing correct
  • [ ] No any types (except in exceptional cases)
  • [ ] Import order followed
  • [ ] Naming conventions followed
  • [ ] Error handling implemented
  • [ ] No console logs in production code

✅ Should be fulfilled ​

  • [ ] JSDoc for public functions
  • [ ] Unit tests for utility functions
  • [ ] Performance considerations addressed
  • [ ] Browser compatibility checked

✅ Can be fulfilled ​

  • [ ] Accessibility (a11y) considered
  • [ ] Internationalization (i18