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

Municipalities Content Collections

Overview

The municipalities system uses Astro Content Collections to manage structured data about cities and municipalities. Each municipality is stored as a Markdown file with frontmatter and validated through a Zod schema. The system enables management of geographic data, OSM integration, and dynamic visualization.

Collection Definition

Zod Schema

typescript
// src/content.config.ts
const kommunen = defineCollection({
  schema: z.object({
    title: z.string(),
    colorStripe: z.string().default("#FF6900"),
    osmAdminLevels: z.array(z.number()).optional(),
    wp_name: z
      .string()
      .min(3, "Wikipedia identifier must be at least 3 characters")
      .regex(/^[a-z]{2,3}-/, "Must start with language code and hyphen")
      .refine((val) => {
        const parts = val.split("-", 2);
        return parts.length === 2 && parts[1].length > 0;
      }, "Must contain exactly one hyphen separating language code and article name"),
    osm_refinement: z.string().optional(),
    icon: z.string().optional(),
    order: z.number().optional(),
    map: z.object({
      center: z.tuple([z.number(), z.number()]).optional(), // [lon, lat] WGS84
      zoom: z.number().optional(),
      extent: z
        .tuple([z.number(), z.number(), z.number(), z.number()])
        .optional(),
      projection: z.string().optional(),
      extra: z.record(z.any()).optional(),
    }),
  }),
});

Frontmatter Fields

Required Fields

title

  • Type: string
  • Description: Display name of the municipality
  • Example: "Cologne", "Bonn"
  • Purpose: Primary display name in the user interface

wp_name

  • Type: string
  • Format: {language-code}-{City-Name}
  • Example: "de-Köln", "de-Bonn"
  • Validation:
    • Minimum 3 characters
    • Must start with language code and hyphen (/^[a-z]{2,3}-/)
    • Must contain exactly one hyphen
  • Purpose: Wikipedia name for external referencing and OSM queries

Optional Fields

colorStripe

  • Type: string
  • Default: "#FF6900"
  • Format: Hex-Color #RRGGBB
  • Example: "#FF6900"
  • Purpose: Color coding for visual representation in maps and UI elements

osmAdminLevels

  • Type: number[] (optional)
  • Allowed Values: 2, 4, 6, 7, 8, 9, 10
  • Example: [6, 9, 10]
  • Purpose: OSM Administrative Levels for polygon queries and geodata synchronization

osm_refinement

  • Type: string (optional)
  • Example: "boundary=administrative"
  • Purpose: Overpass API query refinement for more specific OSM queries

icon

  • Type: string (optional)
  • Purpose: Icon identifier for visual representation

order

  • Type: number (optional)
  • Default: 0
  • Purpose: Sorting order in lists and grids

map

  • Type: object (optional)
  • Fields:
    • center: [number, number] - Map center in WGS84 coordinates [lon, lat]
    • zoom: number - Initial zoom level
    • extent: [number, number, number, number] - Map extent [minx, miny, maxx, maxy]
    • projection: string - CRS code (e.g., "EPSG:25832", "EPSG:3857")
    • extra: Record<string, any> - Additional map configuration

Example:

yaml
map:
  center: [6.9603, 50.9375]
  zoom: 11
  projection: "EPSG:25832"
  extent: [6.8, 50.8, 7.2, 51.1]

OSM Admin Levels

Level Hierarchy

LevelTypeDescriptionExample Germany
2CountryNational stateGermany
4StateFederal stateNorth Rhine-Westphalia
6Government districtGovernment districtCologne
7DistrictIndependent cityCity of Cologne
8Municipal associationMunicipal association-
9MunicipalityMunicipality-
10DistrictCity district/boroughEhrenfeld, Nippes

Typical Combinations

Major city (e.g., Cologne):

yaml
osmAdminLevels: [6, 9, 10]

Small town:

yaml
osmAdminLevels: [7, 9]

TypeScript Interfaces

KommuneData

typescript
// src/utils/kommune-utils.ts
export interface KommuneData {
  slug: string;
  title: string;
  osmAdminLevels?: number[];
  wp_name: string;
  osm_refinement?: string;
  colorStripe: string;
  map: {
    center: [number, number];
    zoom: number;
    projection: string;
    extent?: [number, number, number, number];
    extra?: Record<string, any>;
  };
  order?: number;
  icon?: string;
}

OSM Polygon Interfaces

typescript
// src/types/admin-polygon.ts
export interface OSMPolygonFeature extends GeoJSON.Feature {
  id: number;
  properties: {
    name: string;
    admin_level: number;
    wikipedia?: string;
    wikidata?: string;
    type: string;
    timestamp: string;
    version: number;
    changeset: number;
    user: string;
    uid: number;
  };
  geometry: GeoJSON.Geometry;
}

export interface OSMPolygonCollection extends GeoJSON.FeatureCollection {
  features: OSMPolygonFeature[];
}

export interface OverpassResponse {
  version: number;
  generator: string;
  osm3s: {
    timestamp_osm_base: string;
    copyright: string;
  };
  elements: Array<{
    type: "node" | "way" | "relation";
    id: number;
    tags?: Record<string, string>;
    geometry?: Array<{ lat: number; lon: number }>;
    members?: Array<{
      type: "node" | "way" | "relation";
      ref: number;
      role: string;
      geometry?: Array<{ lat: number; lon: number }>;
    }>;
  }>;
}

Collection Queries

Get All Municipalities

typescript
import { getCollection } from 'astro:content';

const kommunen = await getCollection('kommunen');

Return:

typescript
Array<{
  id: string;        // e.g., "koeln"
  slug: string;      // e.g., "koeln"
  data: {
    title: string;
    colorStripe: string;
    osmAdminLevels?: number[];
    wp_name: string;
    osm_refinement?: string;
    icon?: string;
    order?: number;
    map: {
      center?: [number, number];
      zoom?: number;
      extent?: [number, number, number, number];
      projection?: string;
      extra?: Record<string, any>;
    };
  };
  body: string;      // Markdown content
}>

Filter by Admin Level

typescript
const cities = await getCollection('kommunen', (entry) => {
  return entry.data.osmAdminLevels?.includes(7) ?? false;
});

Sort by Order

typescript
const sorted = kommunen.sort((a, b) => {
  return (a.data.order ?? 0) - (b.data.order ?? 0);
});

Single Municipality

typescript
import { getEntry } from 'astro:content';

const koeln = await getEntry('kommunen', 'koeln');

Markdown File Structure

File Naming Convention

Format: {slug}.md
Examples:

  • koeln.md
  • bonn.md
  • berlin.md

Slug Extraction:

typescript
const slug = filename.replace(/\.md$/, '');

Complete Example

markdown
---
title: "Cologne"
slug: "koeln"
colorStripe: "#FF6900"
osmAdminLevels: [6,9,10]
wp_name: "de-Köln"
map:
  center: [6.9603, 50.9375]
  zoom: 11
  projection: "EPSG:25832"
order: 10
---

Cologne is one of the largest cities in Germany, known for its cathedral and vibrant cultural and economic scene. The city offers numerous opportunities for civic engagement and innovative projects.

Validation

Zod Validation Rules

title:

typescript
z.string() // Required field

colorStripe:

typescript
z.string().default("#FF6900") // Default value if not specified

osmAdminLevels:

typescript
z.array(z.number()).optional() // Optional array of numbers

wp_name:

typescript
z.string()
  .min(3, "Wikipedia identifier must be at least 3 characters")
  .regex(/^[a-z]{2,3}-/, "Must start with language code and hyphen")
  .refine((val) => {
    const parts = val.split("-", 2);
    return parts.length === 2 && parts[1].length > 0;
  }, "Must contain exactly one hyphen separating language code and article name")

map.center:

typescript
z.tuple([z.number(), z.number()]).optional() // Exactly 2 numbers

Error Handling

Validation Error:

[KommuneSchema] Invalid frontmatter in koeln.md:
  - wp_name: Must start with language code and hyphen
  - map.center: Expected tuple of length 2

Usage in Astro Components

Municipalities Grid Component

astro
---
// src/components/KommunenGrid.astro
import { getCollection } from "astro:content";

const kommunen = await getCollection("kommunen");
const sorted = kommunen.sort(
    (a, b) => (a.data.order ?? 0) - (b.data.order ?? 0),
);

// Create client-side accessible kommune data map
const kommuneDataMap: Record<
    string,
    { wp_name: string; osmAdminLevels: number[] }
> = {};
kommunen.forEach((kommune) => {
    kommuneDataMap[kommune.slug] = {
        wp_name: kommune.data.wp_name,
        osmAdminLevels: kommune.data.osmAdminLevels || [],
    };
});
---

<div
    class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8"
    data-kommune-map={JSON.stringify(kommuneDataMap)}
>
    {sorted.map((entry) => (
        <button
            type="button"
            class="kommunen-card"
            style={`--color-stripe: ${entry.data.colorStripe ?? "#FF6900"}`}
            data-slug={entry.slug}
            data-kommune-slug={entry.slug}
        >
            <h3>{entry.data.title}</h3>
            <div>
                {entry.body || `Discover projects in ${entry.data.title}`}
            </div>
        </button>
    ))}
</div>

Feature Editor Integration

astro
---
// src/pages/feature-editor/[featureId].astro
import { getCollection } from "astro:content";

try {
    const kommunen = await getCollection("kommunen");
    const kommune = kommunen.find((k) => k.data.wp_name === wp_name);

    if (kommune?.data.map?.projection) {
        targetProjection = kommune.data.map.projection;
    }
} catch (error) {
    console.warn("Could not load kommunen collection:", error);
}
---

<!-- Use targetProjection for map configuration -->

Best Practices

File Organization

  1. Naming: Slug = filename without .md
  2. Encoding: Always UTF-8
  3. Order: Meaningful sorting with order field
  4. Consistency: Uniform frontmatter structure

Frontmatter Quality

  1. title: Use official spelling
  2. wp_name: Correct Wikipedia article name with language code
  3. osmAdminLevels: Only specify relevant levels
  4. colorStripe: Use consistent color palette
  5. map.center: Precise coordinates (WGS84)

Validation

  1. Test Zod schema before deployment
  2. Validate all municipality files
  3. Correct invalid frontmatter data
  4. Use TypeScript interfaces for type safety

Dependencies

Astro:

  • astro:content - Collection system
  • defineCollection, z (Zod) - Schema definition

Own Modules:

  • src/utils/kommune-utils.ts - Helper functions and interfaces
  • src/types/admin-polygon.ts - OSM-specific TypeScript interfaces

External:

  • zod - Schema validation
  • gray-matter - Frontmatter parsing (in utils)

Extension Possibilities

Additional Fields

typescript
// Example: Population and area
schema: z.object({
  // ... existing fields
  population: z.number().optional(),
  area: z.number().optional(), // km²
  website: z.string().url().optional(),
  established: z.number().optional() // Foundation year
})

Multilingual Support

typescript
title: z.object({
  de: z.string(),
  en: z.string().optional(),
  fr: z.string().optional()
})

Geodata Extensions

typescript
geodata: z.object({
  boundingBox: z.tuple([z.number(), z.number(), z.number(), z.number()]),
  area: z.number(),
  populationDensity: z.number()
}).optional()

Performance Optimizations

  1. Lazy Loading: Load municipality data only when needed
  2. Caching: Cache collection queries
  3. Tree Shaking: Only retrieve required fields
  4. Pagination: Implement pagination for many municipalities

The Content Collections system provides a robust foundation for managing municipality data with complete type safety and validation.