# Dynamic District & Governorate System - Complete Guide

## Overview

This system provides a **fully dynamic** way to import and manage hierarchical location data (districts and governorates) with multi-language support, content management, and efficient geometry handling.

**Key Features:**
- ✅ **100% Dynamic** - No hardcoded values, all data from configuration
- 🌍 **Multi-language** - Arabic, English, French translations
- 📝 **Content Management** - Images, text, metadata for each location
- 🗺️ **Geometry Support** - Optional GeoJSON geometry loading
- ⚡ **Lazy Loading** - Efficient data retrieval for large datasets
- 🔄 **Idempotent** - Safe to run multiple times without duplicates

---

## Backend Implementation

### 1. DTOs (Data Transfer Objects)

#### `DistrictMappingDTO.java`
Represents a single district with translations and member governorates.

```java
{
  "name_ar": "الإقليم الأول",
  "name_en": "First District",
  "name_fr": "Premier District",
  "members": ["Bizerte", "Beja", "Jendoubا", "Le Kef"],
  "members_ar": ["بنزرت", "باجة", "جندوبة", "الكاف"]
}
```

#### `DynamicImportRequestDTO.java`
Request format for importing districts dynamically.

```java
{
  "mapId": 1,
  "districts": {
    "district1": { /* DistrictMappingDTO */ },
    "district2": { /* DistrictMappingDTO */ }
  },
  "loadGeometriesFromGeoJson": true,
  "geoJsonPath": "static/tunisia_adm1.geojson"
}
```

#### `TranslationDTO.java`
Multi-language name translations.

```java
{
  "ar": "الإقليم الأول",
  "en": "First District",
  "fr": "Premier District"
}
```

### 2. Database Schema

The `locations` table now includes:

```sql
ALTER TABLE locations ADD COLUMN translations TEXT;
```

Stores JSON with multi-language names:
```json
{"ar":"الإقليم الأول","en":"First District","fr":"Premier District"}
```

### 3. Backend Services

#### `DynamicDistrictImportService.java`
Handles dynamic import:
- Parses district mapping from request
- Creates parent district locations
- Creates child governorate locations
- Loads geometries from GeoJSON (optional)
- Uses idempotent logic (no duplicates)

#### `LocationService.java` (Enhanced)
New methods:
- `getDistrictsWithGovernorates()` - Hierarchical data with lazy loading
- `convertToDTOWithoutGeometry()` - Lightweight conversion for lists
- `getLocationWithDetails()` - Full details for single location
- `getGovernoratesByDistrict()` - Children of a district

### 4. REST API Endpoints

#### Dynamic Import
```http
POST /api/locations/dynamic/import
Content-Type: application/json

{
  "mapId": 1,
  "districts": {
    "district1": {
      "name_ar": "الإقليم الأول",
      "name_en": "First District",
      "name_fr": "Premier District",
      "members": ["Bizerte", "Beja"],
      "members_ar": ["بنزرت", "باجة"]
    }
  },
  "loadGeometriesFromGeoJson": true
}
```

#### Get Hierarchical Data (Efficient)
```http
GET /api/locations/map/1/hierarchy?includeGeometry=false
```

Response:
```json
[
  {
    "id": 1,
    "name": "First District",
    "translations": {
      "ar": "الإقليم الأول",
      "en": "First District",
      "fr": "Premier District"
    },
    "children": [
      {
        "id": 10,
        "name": "Bizerte",
        "translations": {
          "ar": "بنزرت",
          "en": "Bizerte",
          "fr": "Bizerte"
        },
        "contents": [...]
      }
    ]
  }
]
```

#### Get Location Details
```http
GET /api/locations/123/details
```

Returns full location with geometry, contents, and translations.

---

## Angular/Frontend Implementation

### 1. Updated Models

#### `location.model.ts`

```typescript
export interface LocationDTO {
  id?: number;
  name: string;
  translations?: TranslationDTO;
  children?: LocationDTO[];  // Hierarchical data
  contents?: LocationContentDTO[];
  geometry?: GeometryDTO;
  // ... other fields
}

export interface TranslationDTO {
  ar: string;
  en: string;
  fr: string;
}
```

### 2. Service Methods

#### `locations.service.ts`

```typescript
// Import dynamic districts
importDynamicDistricts(request: DynamicImportRequest): Observable<string>

// Get hierarchical data (efficient)
getDistrictsHierarchy(mapId: number, includeGeometry = false): Observable<LocationDTO[]>

// Get single location with full details
getLocationDetails(id: number): Observable<LocationDTO>

// Helper to build import request
buildDynamicImportRequest(mapId: number, districtsMapping: any): DynamicImportRequest
```

### 3. Usage Examples

#### Example 1: Import Districts Dynamically

```typescript
// In your component
const districtsMapping = {
  district1: {
    name_ar: 'الإقليم الأول',
    name_en: 'First District',
    name_fr: 'Premier District',
    members: ['Bizerte', 'Beja', 'Jendouba', 'Le Kef'],
    members_ar: ['بنزرت', 'باجة', 'جندوبة', 'الكاف']
  },
  district2: {
    name_ar: 'الإقليم الثاني',
    name_en: 'Second District',
    name_fr: 'Deuxième District',
    members: ['Tunis', 'Ariana', 'Manubah', 'Ben Arous'],
    members_ar: ['تونس', 'أريانة', 'منوبة', 'بن عروس']
  }
};

// Build request
const request = this.locationsService.buildDynamicImportRequest(
  this.currentMap.id,
  districtsMapping
);

// Import
this.locationsService.importDynamicDistricts(request).subscribe({
  next: (result) => console.log('Import successful:', result),
  error: (err) => console.error('Import failed:', err)
});
```

#### Example 2: Display Hierarchical Data (Efficient)

```typescript
// Load districts with governorates (no geometry for performance)
this.locationsService.getDistrictsHierarchy(mapId, false).subscribe({
  next: (districts) => {
    districts.forEach(district => {
      console.log('District:', district.translations?.en);
      
      district.children?.forEach(governorate => {
        console.log('  - Governorate:', governorate.translations?.en);
      });
    });
  }
});
```

#### Example 3: Get Full Location Details

```typescript
// When user clicks on a governorate, load full details
this.locationsService.getLocationDetails(governorateId).subscribe({
  next: (location) => {
    // Has geometry, contents, translations
    this.selectedLocation = location;
    
    // Display on map
    if (location.geometry) {
      this.displayOnMap(location);
    }
    
    // Show contents (images, text)
    if (location.contents && location.contents.length > 0) {
      this.displayContents(location.contents);
    }
  }
});
```

#### Example 4: Multi-language Display

```typescript
// Display name in current language
getLocalizedName(location: LocationDTO, language: string): string {
  if (!location.translations) {
    return location.name;
  }
  
  switch (language) {
    case 'ar': return location.translations.ar;
    case 'fr': return location.translations.fr;
    default: return location.translations.en;
  }
}

// In template
{{ getLocalizedName(location, currentLanguage) }}
```

---

## Performance Optimization

### 1. Lazy Loading Geometries

For **list views** (showing many locations):
```typescript
// Don't load geometry - much faster
getDistrictsHierarchy(mapId, false)
```

For **detail views** (single location):
```typescript
// Load full geometry
getLocationDetails(locationId)
```

### 2. Geometry Simplification

Add to `GeometryService.java`:

```java
public Geometry simplifyGeometry(Geometry geom, double tolerance) {
    return TopologyPreservingSimplifier.simplify(geom, tolerance);
}
```

Use tolerance of `0.01` for map display (reduces geometry size by ~90%).

### 3. Content Pagination

For locations with many contents:

```java
@GetMapping("/{id}/contents")
public ResponseEntity<Page<LocationContentDTO>> getContents(
    @PathVariable Long id,
    @PageableDefault(size = 10) Pageable pageable
) {
    // Return paginated contents
}
```

---

## Complete Workflow Example

### Step 1: Define District Mapping (Component)

```typescript
private tunisiaDistrictsMapping = {
  district1: {
    name_ar: 'الإقليم الأول',
    name_en: 'First District',
    name_fr: 'Premier District',
    members: ['Bizerte', 'Beja', 'Jendouba', 'Le Kef'],
    members_ar: ['بنزرت', 'باجة', 'جندوبة', 'الكاف']
  },
  district2: { /* ... */ },
  district3: { /* ... */ },
  district4: { /* ... */ },
  district5: { /* ... */ }
};
```

### Step 2: Import to Backend

```typescript
async importDistricts() {
  const request = this.locationsService.buildDynamicImportRequest(
    this.currentMap.id,
    this.tunisiaDistrictsMapping
  );
  
  try {
    const result = await this.locationsService
      .importDynamicDistricts(request)
      .toPromise();
    console.log('Import complete:', result);
  } catch (error) {
    console.error('Import failed:', error);
  }
}
```

### Step 3: Load and Display Districts

```typescript
async loadDistricts() {
  // Get hierarchical data without geometry (fast)
  const districts = await this.locationsService
    .getDistrictsHierarchy(this.currentMap.id, false)
    .toPromise();
  
  this.allDistricts = districts;
  this.displayDistrictsOnMap(districts);
}
```

### Step 4: Handle User Interaction

```typescript
async selectDistrict(district: LocationDTO) {
  // Load full details with geometry when user clicks
  const districtDetails = await this.locationsService
    .getLocationDetails(district.id!)
    .toPromise();
  
  this.selectedDistrict = districtDetails;
  
  // Zoom camera to district
  this.viewer.flyTo(districtEntity, { duration: 1.5 });
  
  // Show child governorates
  districtDetails.children?.forEach(gov => {
    this.displayGovernorateOnMap(gov);
  });
}
```

### Step 5: Edit Content

```typescript
async updateDistrictContent(districtId: number, newContent: any) {
  const request: LocationRequest = {
    location: { id: districtId, /* ... */ },
    contents: [{ content: newContent }]
  };
  
  await this.locationsService.updateLocation(districtId, request).toPromise();
  
  // Reload details
  this.selectedDistrict = await this.locationsService
    .getLocationDetails(districtId)
    .toPromise();
}
```

---

## Best Practices

1. **Use hierarchy endpoint for lists** - Fast, minimal data
2. **Use details endpoint when user clicks** - Full data only when needed
3. **Cache translations** - Don't reload repeatedly
4. **Simplify geometries** - Reduce polygon complexity for display
5. **Paginate contents** - Don't load all contents at once
6. **Use idempotent imports** - Safe to run multiple times

---

## Troubleshooting

### Import fails with "Map not found"
- Ensure mapId exists: `GET /api/maps/{mapId}`

### Geometries not loading
- Check GeoJSON file path in `application.properties`
- Verify GeoJSON structure matches expected format

### Translations not appearing
- Check `translations` column in database
- Verify JSON format in database

### Memory issues with large geometries
- Use `includeGeometry=false` for lists
- Simplify geometries before storing
- Increase JVM heap: `-Xmx2g`

---

## API Reference

### Dynamic Import
- `POST /api/locations/dynamic/import` - Import districts dynamically

### Hierarchical Data
- `GET /api/locations/map/{mapId}/hierarchy?includeGeometry=false` - Get hierarchy
- `GET /api/locations/{id}/details` - Get single location with full details
- `GET /api/locations/district/{districtId}/governorates/detailed?includeGeometry=true` - Get governorates

### CRUD Operations
- `POST /api/locations` - Create location
- `PUT /api/locations/{id}` - Update location
- `DELETE /api/locations/{id}` - Delete location

---

## Database Migration

If upgrading from old system:

```sql
-- Add translations column
ALTER TABLE locations ADD COLUMN translations TEXT;

-- Migrate existing data (example)
UPDATE locations 
SET translations = json_object(
  'ar', name,  -- Replace with actual Arabic name if available
  'en', name,
  'fr', name
)
WHERE translations IS NULL;
```

---

## Summary

This dynamic system eliminates all hardcoded values and provides:
- ✅ Flexible district/governorate definitions
- ✅ Multi-language support out of the box
- ✅ Efficient data loading with lazy loading
- ✅ Complete content management
- ✅ Safe, idempotent imports

**No more code changes needed when adding new districts or governorates!**
