# Usage Examples - GeolocPlugin

This file contains complete usage examples of the plugin with all the latest features.

---

## 📋 Complete Example: Route Management Application

### Project Structure

```text
my-cap-app/
├── db/
│   └── schema.cds
├── srv/
│   ├── routes-service.cds
│   └── routes-service.js
├── app/
│   └── routes/
│       └── annotations.cds
├── package.json
└── .env
```

---

## Example 1

This example uses **all automatic features** - no manual action declarations needed!

### 1. Data Schema (`db/schema.cds`)

```cds
namespace myapp;

using { cuid } from '@sap/cds/common';

entity Routes : cuid {
  // General information
  name : String(100);
  description : String(500);
  transportMode : String default 'car';  // car | bike | foot

  // Starting point (with automatic address search popup)
  @AddressSearch: true
  startingPoint : String(500);
  startingPointCoordinates : String(50);

  // Destination point (with automatic address search popup)
  @AddressSearch: true
  endingPoint : String(500);
  endingPointCoordinates : String(50);

  // Automatic route calculation on save when coordinates change
  @RouteCalculation: {
    from: 'startingPointCoordinates',
    to: 'endingPointCoordinates',
    mode: 'transportMode',
    distanceField: 'distance',
    durationField: 'duration',
    autoCalculate: true  // NEW: Auto-calculate on CREATE/UPDATE
  }
  distance : Decimal(10, 2);  // in km
  duration : String;          // Format: "4h 57min"

  // Metadata
  calculatedAt : DateTime;
  mapImageURL : String(1000); // Optional: Static map image URL
}
```

### 2. Service (`srv/routes-service.cds`)

```cds
using { myapp as db } from '../db/schema';

service RoutesService {
  @odata.draft.enabled
  entity Routes as projection on db.Routes;

  // The plugin automatically creates:
  // - AddressSuggestions entity
  // - searchAddress_startingPoint action
  // - searchAddress_endingPoint action
  // - calculateRoute action
  // - selectAddress action
}
```

### 3. JavaScript Handler (`srv/routes-service.js`) - OPTIONAL

The plugin works automatically with annotations only! You only need a handler if you want custom validation:

```javascript
module.exports = function() {
  // ========================================
  // Optional: Custom validation
  // ========================================
  this.before('CREATE', 'Routes', async (req) => {
    // Validate transport mode
    const validModes = ['car', 'bike', 'foot'];
    if (req.data.transportMode && !validModes.includes(req.data.transportMode)) {
      req.error(400, `Invalid transport mode. Allowed: ${validModes.join(', ')}`);
    }
  });

  // Routes are automatically calculated on save when coordinates change!
  // No need to write calculateRoute handler - it's automatic ✅
};
```

### 4. Fiori UI Annotations (`app/routes/annotations.cds`)

```cds
using { RoutesService } from '../../srv/routes-service';


// ========================================
// Action Buttons
// ========================================
annotate RoutesService.Routes with @(
  UI.Identification : [
    {
      $Type : 'UI.DataFieldForAction',
      Action : 'RoutesService.searchAddress_startingPoint',
      Label : 'Search Start',
      ![@UI.Hidden] : {$edmJson: {$Path: 'IsActiveEntity'}}
    },
    {
      $Type : 'UI.DataFieldForAction',
      Action : 'RoutesService.searchAddress_endingPoint',
      Label : 'Search Destination',
      ![@UI.Hidden] : {$edmJson: {$Path: 'IsActiveEntity'}}
    },
    {
      $Type : 'UI.DataFieldForAction',
      Action : 'RoutesService.calculateRoute',
      Label : 'Calculate Route',
      ![@UI.Hidden] : {$edmJson: {$Path: 'IsActiveEntity'}}
    }
  ]
);

// ========================================
// Object Details
// ========================================
annotate RoutesService.Routes with @(
  UI.HeaderInfo : {
    TypeName : 'Route',
    TypeNamePlural : 'Routes',
    Title : { Value : name },
    Description : { Value : description }
  },
  UI.Facets : [
    {
      $Type : 'UI.ReferenceFacet',
      ID : 'GeneralInfo',
      Label : 'General Information',
      Target : '@UI.FieldGroup#General'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID : 'Addresses',
      Label : 'Addresses',
      Target : '@UI.FieldGroup#Addresses'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID : 'Results',
      Label : 'Results',
      Target : '@UI.FieldGroup#Results'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID : 'Map',
      Label : 'Map',
      Target : '@UI.FieldGroup#Map'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID : 'AddressSuggestionsFacet',
      Label : 'Address Suggestions',
      Target : 'AddressSuggestions/@UI.LineItem'
    }
  ],
  UI.FieldGroup #General : {
    Data : [
      { Value : name, Label : 'Name' },
      { Value : description, Label : 'Description' },
      { Value : transportMode, Label : 'Transport Mode' }
    ]
  },
  UI.FieldGroup #Addresses : {
    Data : [
      { Value : startingPoint, Label : 'Starting Point' },
      { Value : startingPointCoordinates, Label : 'Start Coordinates' },
      { Value : endingPoint, Label : 'Destination Point' },
      { Value : endingPointCoordinates, Label : 'Destination Coordinates' }
    ]
  },
  UI.FieldGroup #Results : {
    Data : [
      { Value : distance, Label : 'Distance (km)' },
      { Value : duration, Label : 'Duration' },
      { Value : calculatedAt, Label : 'Calculated At' }
    ]
  },
  UI.FieldGroup #Map : {
    Data : [
      { Value : mapImageURL, Label : 'Route Map' }
    ]
  }
);

// ========================================
// SideEffects (Auto Refresh) - OPTIONAL
// ========================================
// When autoCalculate: true, SideEffects are automatically injected!
// You only need to add these manually if you want to customize them:

// annotate RoutesService.Routes actions {
//   calculateRoute @(
//     Common.SideEffects : {
//       TargetProperties : ['distance', 'duration', 'calculatedAt', 'mapImageURL']
//     }
//   );
// };

// For address search, you can optionally add these for better UX:
annotate RoutesService.Routes actions {
  searchAddress_startingPoint @(
    Common.SideEffects : {
      TargetEntities : ['/AddressSuggestions']
    }
  );
  searchAddress_endingPoint @(
    Common.SideEffects : {
      TargetEntities : ['/AddressSuggestions']
    }
  );
};

annotate RoutesService.AddressSuggestions actions {
  selectAddress @(
    Common.SideEffects : {
      TargetEntities : ['/Routes']
    }
  );
};
```

### 5. Configuration (`.env`)

```bash
# ========================================
# GeolocPlugin Configuration
# ========================================

# Geocoding Provider (nominatim by default - free)
GEOLOC_GEOCODING_PROVIDER=nominatim

# Routing Provider (osrm by default - free)
GEOLOC_ROUTING_PROVIDER=osrm
GEOLOC_ROUTING_PROFILE=car
GEOLOC_ROUTING_FALLBACK=true

# Static Maps Provider (optional)
GEOLOC_STATICMAP_PROVIDER=locationiq
GEOLOC_STATICMAP_SIZE=800x600
GEOLOC_STATICMAP_ZOOM=auto
GEOLOC_STATICMAP_MARKERS=true
GEOLOC_STATICMAP_SHOW_ROUTE=true

# API Keys (for production providers)
# LOCATIONIQ_API_KEY=your-api-key-here
# OPENROUTESERVICE_API_KEY=your-api-key-here
# GOOGLE_MAPS_API_KEY=your-api-key-here
# MAPBOX_API_KEY=your-api-key-here
# HERE_API_KEY=your-api-key-here

# Cache Configuration
GEOLOC_CACHE_ENABLED=true
GEOLOC_CACHE_TTL=2592000  # 30 days
GEOLOC_CACHE_MAX_SIZE=1000
```

### 6. Package.json

```json
{
  "name": "my-routes-app",
  "version": "1.0.0",
  "dependencies": {
    "@sap/cds": "^8.0.0",
    "cap-js-geoloc-plugin": "latest"
  },
  "cds": {
    "requires": {
      "cap-js-geoloc-plugin": {}
    }
  },
  "scripts": {
    "start": "cds run"
  }
}
```

---

## Example 2: Manual Route Calculation

If you prefer manual calculation (set `autoCalculate: false` or omit it):

### Schema

```cds
entity Routes : cuid {
  @AddressSearch: true
  startingPoint : String(500);
  startingPointCoordinates : String(50);

  @AddressSearch: true
  endingPoint : String(500);
  endingPointCoordinates : String(50);

  @RouteCalculation: {
    from: 'startingPointCoordinates',
    to: 'endingPointCoordinates',
    mode: 'transportMode',
    distanceField: 'distance',
    durationField: 'duration'
    // autoCalculate: false (or omit)
  }
  distance : Decimal(10, 2);
  duration : String;
  transportMode : String default 'car';
}
```

### User Workflow

1. Validate both addresses (starting point + destination)
2. **Click "Calculate Route"** button manually
3. Distance and duration are calculated
4. Save

---

## Example 3: Address Auto-Correction

Automatically geocode and normalize addresses when users type them:

### Data Model

```cds
entity Deliveries : cuid {
  // User types raw address
  @AddressAutoCorrect: {
    correctedField: 'deliveryAddressCorrected',
    coordinatesField: 'deliveryCoordinates',
    cityField: 'deliveryCity',
    streetField: 'deliveryStreet'
  }
  deliveryAddress : String(500);

  // Auto-filled fields
  deliveryAddressCorrected : String(500);
  deliveryCoordinates : String(50);
  deliveryCity : String(100);
  deliveryStreet : String(100);
}
```

### How it works

1. User types: "21 rue liberte dijon" (with typos, no accents)
2. On CREATE/UPDATE, the plugin automatically:
   - Geocodes the address
   - Fills `deliveryAddressCorrected`: "21 Rue de la Liberté, 21000 Dijon, France"
   - Fills `deliveryCoordinates`: "47.3220, 5.0415"
   - Fills `deliveryCity`: "Dijon"
   - Fills `deliveryStreet`: "Rue de la Liberté"

---

## Example 4: Manual Override for Non-Existent Addresses

For new construction sites, future developments, or addresses not yet in mapping databases:

### Schema Definition

```cds
entity ConstructionSites : cuid {
  siteName : String(200);

  // Address with manual override option
  @AddressAutoCorrect: {
    correctedField: 'siteAddressCorrected',
    coordinatesField: 'siteCoordinates',
    cityField: 'siteCity',
    manualOverrideField: 'isManualAddress'  // Enable manual override
  }
  siteAddress : String(500);

  // Auto-filled (or manually filled if override is enabled)
  siteAddressCorrected : String(500);
  siteCoordinates : String(50);
  siteCity : String(100);

  // Manual override flag
  isManualAddress : Boolean default false;
}
```

### Usage Scenarios

#### Scenario 1: Normal address (geocoding enabled)

1. User enters: `isManualAddress = false`
2. User types: "Future Avenue, New District" → geocoding fails (doesn't exist yet)
3. User switches: `isManualAddress = true`
4. User manually enters:
   - `siteAddress`: "Future Avenue, New District, Paris"
   - `siteCoordinates`: "48.8566, 2.3522" (from Google Maps or approximate location)
   - `siteCity`: "Paris"

#### Scenario 2: Skip geocoding completely

1. User checks `isManualAddress = true` from the start
2. Enter all address details manually without API calls
3. Useful for saving API quota when you already have the coordinates

### UI Annotations (Optional)

```cds
annotate ConstructionSites with @(
  UI.FieldGroup #AddressGroup : {
    Data : [
      { Value : siteAddress },
      { Value : isManualAddress, Label: 'Manual Entry' },
      { Value : siteCoordinates, ![@UI.Hidden] : {$edmJson: {$Not: [{$Path: 'isManualAddress'}]}} },
      { Value : siteCity }
    ]
  }
);
```

---

## Advanced Tips

### Tip 1: Custom Transport Modes

```javascript
// srv/routes-service.js
module.exports = function() {
  const customModes = {
    'truck': 'driving-hgv',
    'motorcycle': 'driving',
    'scooter': 'cycling-regular'
  };

  this.before('CREATE', 'Routes', async (req) => {
    // Map custom mode to provider profile
    if (req.data.transportMode && customModes[req.data.transportMode]) {
      req.data.transportMode = customModes[req.data.transportMode];
    }
  });
};
```

### Tip 2: Programmatic API

```javascript
const GeolocPlugin = require('cap-js-geoloc-plugin');

// Search for addresses
const results = await GeolocPlugin.plugin.geocoding.searchAddress('Paris', 10);

// Validate an address
const validated = await GeolocPlugin.plugin.geocoding.validateAddress('Paris, France');

// Calculate route
const route = await GeolocPlugin.plugin.routing.calculateRoute(
  '48.8566,2.3522',  // Paris
  '45.7640,4.8357',  // Lyon
  'car'
);
// → { distanceKm: 465.87, durationMinutes: 297, formattedDuration: "4h 57min" }

// Generate static map URL
const mapURL = await GeolocPlugin.plugin.map.generateRouteMapURL(
  '48.8566,2.3522',
  '45.7640,4.8357'
);
```

### Tip 3: Error Handling

```javascript
this.on('calculateRoute', 'Routes', async (req) => {
  try {
    // Your logic here
  } catch (error) {
    if (error.message.includes('coordinates')) {
      return req.error(400, 'Please validate addresses before calculating route');
    } else if (error.message.includes('timeout')) {
      return req.error(503, 'Service temporarily unavailable. Please try again.');
    } else {
      return req.error(500, `Calculation error: ${error.message}`);
    }
  }
});
```

### Tip 4: Performance Optimization

The plugin automatically caches:

- Geocoding results (30 days by default)
- Routing calculations (30 days by default)
- Static map URLs

To clear cache:

```javascript
const GeolocPlugin = require('cap-js-geoloc-plugin');

// Clear all cache
GeolocPlugin.plugin.cache.flushAll();

// Get cache statistics
const stats = GeolocPlugin.plugin.getStats();
console.log(stats);
// → { routing: {...}, geocoding: {...}, cache: {...} }
```

---

## Provider Configuration Examples

### LocationIQ (Production)

```bash
GEOLOC_GEOCODING_PROVIDER=locationiq
GEOLOC_ROUTING_PROVIDER=locationiq
LOCATIONIQ_API_KEY=your-api-key
```

### OpenRouteService (Production)

```bash
GEOLOC_GEOCODING_PROVIDER=openrouteservice
GEOLOC_ROUTING_PROVIDER=openrouteservice
OPENROUTESERVICE_API_KEY=your-api-key
```

### Google Maps (Geocoding)

```bash
GEOLOC_GEOCODING_PROVIDER=googlemaps
GOOGLE_MAPS_API_KEY=your-api-key
```

### HERE (Geocoding)

```bash
GEOLOC_GEOCODING_PROVIDER=here
HERE_API_KEY=your-api-key
```

### Mapbox (Geocoding)

```bash
GEOLOC_GEOCODING_PROVIDER=mapbox
MAPBOX_API_KEY=your-api-key
```

---

## 🔗 Resources

### Plugin Documentation

- [README.md](README.md) - Main documentation
- [CHANGELOG.md](CHANGELOG.md) - Version history
- [SAP CAP Documentation](https://cap.cloud.sap/docs/)

### Free Providers

- [Nominatim API](https://nominatim.org/release-docs/latest/) - Free geocoding (no API key required)
- [OSRM API](http://project-osrm.org/) - Free routing (no API key required)

### Commercial Providers

- [LocationIQ](https://locationiq.com/) - Geocoding & Routing
  - [API Documentation](https://locationiq.com/docs)
  - [Get API Key](https://my.locationiq.com/register)
  - Free tier: 5,000 requests/day

- [OpenRouteService](https://openrouteservice.org/) - Geocoding & Routing
  - [API Documentation](https://openrouteservice.org/dev/#/api-docs)
  - [Get API Key](https://openrouteservice.org/dev/#/signup)
  - Free tier: 2,000 requests/day

- [Google Maps Platform](https://developers.google.com/maps) - Geocoding, Routing & Maps
  - [Geocoding API](https://developers.google.com/maps/documentation/geocoding)
  - [Directions API](https://developers.google.com/maps/documentation/directions)
  - [Static Maps API](https://developers.google.com/maps/documentation/maps-static)
  - [Get API Key](https://console.cloud.google.com/google/maps-apis)
  - Free tier: $200 credit/month

- [HERE Maps](https://www.here.com/) - Geocoding & Routing
  - [Geocoding API](https://developer.here.com/documentation/geocoding-search-api/dev_guide/index.html)
  - [Routing API](https://developer.here.com/documentation/routing-api/dev_guide/index.html)
  - [Get API Key](https://developer.here.com/sign-up)
  - Free tier: 250,000 transactions/month

- [Mapbox](https://www.mapbox.com/) - Geocoding & Maps
  - [Geocoding API](https://docs.mapbox.com/api/search/geocoding/)
  - [Static Images API](https://docs.mapbox.com/api/maps/static-images/)
  - [Get API Key](https://account.mapbox.com/auth/signup/)
  - Free tier: 100,000 requests/month

---
