# Services

# Geocoder service

TIP

Available as a global service

WARNING

create method is the only one allowed from the client/server side

Rely on node-geocoder (opens new window) under-the-hood.

# Data model

The data model of a geocoding request as used by the API only contains the address field specifying the string to be looked for. The request response depends on the geocoding provider although there is a set of common properties, see example below.

const service = app.getService('geocoder')
const results = await service.create({ address: '29 champs elysée paris' })
results.forEach(element => {
  let { latitude, longitude, country, state,
  		streetNumber, streetName, city, zipcode, administrativeLevels } = element
  ...
})

Reverse geocoding works the same way except the input is based on a lon/longitude and lat/latitude fields, or a GeoJson feature, specifying the location to be looked for.

const service = app.getService('geocoder')
const results = await service.create({ lat: 45.767, lon: 4.833 })
results.forEach(element => {
  let { latitude, longitude, country, state,
      streetNumber, streetName, city, zipcode, administrativeLevels } = element
  ...
})

# Hooks

No hooks are executed on the geocoder service for now.

# Catalog service

This service aims at storing the definition of the available map layers or map view contexts (extent and active layers).

TIP

Available as a global and a contextual service

The service can be created using the global createCatalogService(context, db) function, if no arguments provided it will be available as a global service otherwise as a contextual service (e.g. attached to a specific organization), please also refer to core module createService().

Here is a sample code to retrieve all data layer descriptors available in the catalog:

const catalogService = app.getService('catalog')
let response = await catalogService.find()
_.forEach(response.data, (layer) => {
	if (layer[engine]) {
	  // Process i18n if you'd like to
	  if (this.$t(layer.name)) layer.name = this.$t(layer.name)
	  if (this.$t(layer.description)) layer.description = this.$t(layer.description)
	  // Create the underlying layer object in the map/globe
	  this.addLayer(layer)
	}
})

TIP

By default the catalog filters contexts and return layers only if the target object type is unspecified.

Here is a sample code to retrieve the default context (i.e. home view) available in the catalog:

const catalogService = app.getService('catalog')
let response = await catalogService.find({ query: { type: 'Context', isDefault: true } })
this.loadContext(response.data[0])

# Data model

# Layer

The data model of a layer descriptor as used by the API is detailed below.

Catalog data model

The catalog is typically populated at application startup with a default set of layers, as a consequence the best is to have a look at some of our application configuration files like the one of Kano (opens new window).

The details of each property are the following:

  • name : the layer name, typically used in the catalog panel
  • description : the layer short description, typically used in the catalog panel
  • i18n: layer translations, see application internationalization for details
    • locale: translations for a target locale eg FR
      • Layers: translation keys/values for layer fields
      • Variables: translation keys/values for variable fields
  • type : usually BaseLayer for map backgrounds, TerrainLayer for 3D terrain, OverlayLayer for additionnal data layers
  • tags : list of tags (i.e. array of strings) used to classify the layer
  • icon : a Quasar icon (opens new window) for the layer, typically used in the layers panel
  • iconUrl : a link to an image to be used as icon for the layer, typically used in the layers panel
  • attribution : data attribution informaiton to be displayed along with the layer
  • leaflet : options to be passed to the underlying Leaflet layer constructor
    • type: the type of Leaflet layer to be constructed (i.e. class/constructor function name), e.g. tileLayer or geoJson
    • source: if provided this property is reserved for the first argument to be passed to the underlying Leaflet layer constructor, typically the URL or the GeoJson data, in this case the options will be passed as the second argument
    • option: all others fields are considered as options to be passed to the layer constructor
  • cesium: options to be passed to the underlying Cesium layer constructor
    • type: the type of Cesium layer to be constructed (i.e. class/constructor function name), e.g. EllipsoidTerrainProvider or BingImageryProvider
    • option: all others fields are considered as options to be passed to the layer constructor

TIP

The type and tags attributes are typically used by the catalog panel to organize the layer selection in a meaningful way.

If the layer is a feature layer based on a feature service the additional properties are the following:

  • service: the name of the underlying feature service,
  • probeService: the name of the underlying feature service containing probe locations,
  • featureId: the name of the unique feature identifier in feature (relative to the nested properties object),
  • from: the oldest stored feature age in history as ISO 8601 duration (opens new window)
  • to: the newest stored feature age in history as ISO 8601 duration (opens new window)
  • every: the sample frequency of the features as ISO 8601 duration (opens new window)
  • queryFrom: the period to search for data around the current time when displaying the features as ISO 8601 duration (opens new window)
  • variables: array of available properties in feature to be aggregated over time, for each entry the following options are available:
    • name: property name in feature (relative to the nested properties object),
    • label: property label to use in UI,
    • units: array of target units to be available in the legend, the first unit is the one used to store the data, others will be converted from using math.js (opens new window)
    • chartjs: options to be passed to chart.js (opens new window) when drawing timeseries

# User context

A user context consists in a map extent and a set of layers to be activated. It is used to restore a specific user view context at anytime.

The data model of a user context as used by the API is the following:

  • name : the context name, typically used when listing favorite views
  • type : Context
  • south,west,north,east: context extent,
  • layers: array of active layer names

# Service

A service consists in a description of a third-party web service providing support for OGC (opens new window) protocols. It is used to easily import layers coming from these services.

The data model of a service as used by the API is the following:

  • name : the context name, typically used when listing favorite views
  • type : Service
  • request: full request to perform GetCapabilities operation
  • baseUrl: base service URL
  • searchParams: additional parameters to be used to perform requests to the service
  • version: OGC protocol version to be used (e.g. 1.1.1)
  • protocol: OGC protocol to be used (e.g. WMS)

# Hooks

The main hooks executed on the catalog service for layers are used to convert from/to JS/MongoDB data types:

Additional hooks are run to update contexts whenever a layer name is updated.

# Features service

TIP

Available as a global and a contextual service

The service can be created using the global createFeaturesService(options) function, if no context provided it will be available as a global service otherwise as a contextual service (e.g. attached to a specific organization), please also refer to core module createService().

WARNING

This service does not emit events with individual features because importing large batch of features could lead to big performance drop. For the same reason, any operation result contains only the feature identifier not the whole object as usual.

# Data model

The common model is a GeoJSON feature (opens new window) with a GeoJSON geometry (opens new window). However, it also supports time-stamped features to manage the temporal evolution of either the geometry (e.g. a moving aircraft) or the attributes/properties (e.g. a probe). As a consequence it can also contains the following additional fields:

The raw data model of a feature (i.e. when no aggregation is performed) as used by the API is detailed below.

Feature data model

TIP

The default features service response is a GeoJson Feature Collection (opens new window) so that it can be directly put on a map. You can avoid this by setting the asFeatureCollection query paramter to false on your request.

Example of non aggregated features request result
{
  "type": "FeatureCollection",
  "features": [
    {
      "_id": "5f23ce6071b0b00008dff53f",
      "type": "Feature",
      "time": "2020-07-31T07:30:00.000Z",
      "geometry": {
          "type": "Point",
          "coordinates": [
              -0.12606156299146137,
              45.02191991211479
          ]
      },
      "properties": {
          "name": "L'Isle à Abzac",
          "code_station": "#P726151001",
          "H": 0.397
      }
    },
    {
      "_id": "5f23caea71b0b00008dfe457",
      "type": "Feature",
      "time": "2020-07-31T07:25:00.000Z",
      "geometry": {
          "type": "Point",
          "coordinates": [
              -0.12606156299146137,
              45.02191991211479
          ]
      },
      "properties": {
          "name": "L'Isle à Abzac",
          "code_station": "#P726151001",
          "H": 0.398
      }
    },
    ...
  ]
}

# Measure networks

It is usual to create two different feature services for measure networks to get:

  • the list of stations (i.e. measurement probes) in the network, which does not handle time information,
  • the list of observations (i.e. measures) made by the different stations, which contains time information.

Typically, hydrological data from Hub'eau are managed using two services:

  • hubeau-stations for the stations,
  • hubeau-observations for the observations.

Each service is associated under-the-hood to a MongoDB collection storing the related data.

# Time-based feature aggregation

It can be useful to retrieve a single result aggregating all the times for a given feature (i.e. timeseries) instead of multiple single results (i.e. one per time). It can also ease data management when a sensor generates different features for different variables (i.e. physical or logical measured elements), e.g. one feature owing the measure of one variable and another feature the measure of another variable because they are not probed at the same frequency.

You can perform such an aggregation based on MongoDB capabilities (opens new window) like this:

let result = await app.getService('features').find({
  query: {
    time: { // Target time range
      $gte: '2017-05-24T12:00:00.000Z',
      $lte: '2017-05-25T12:00:00.000Z'
    },
    $groupBy: 'properties.iata_code', // Will perform aggregation based on this unique feature identifier
    'properties.iata_code': 'LFBO', // The target feature to aggregate, if omitted all will be or you can provide a list with $in
    $aggregate: ['geometry', 'speed'] // List of properties to aggregate over time
  }
})
// Do something with the aggregated feature

When performing aggregation, time will become an object containing a key per aggregated element according to its name (e.g. geometry, speed). The value of each key will be the ordered list of available times for the element (unless you specified a different $sort order). The value of each aggregated element property (e.g. properties.speed) will become an array of values ordered according to ascending time (replacing the scalar property of the raw feature).

The data model of a feature as returned by the API when some elements of the feature are aggregated over time is detailed below.

Aggregated feature data model

Example of aggregated features request result
{
  "type": "FeatureCollection",
  "features": [
    {
      "_id": {
        "code_station": "#X331001001"
      },
      "time": {
        "H": [
          "2020-07-29T13:40:00.000Z",
          "2020-07-29T13:45:00.000Z",
          "2020-07-29T13:50:00.000Z",
          ...
        ],
        "Q": [
          "2020-07-29T13:40:00.000Z",
          "2020-07-29T13:45:00.000Z",
          "2020-07-29T13:50:00.000Z",
          ...
        ]
      },
      "type": "Feature",
      "properties": {
        "name": "La Durance à Cavaillon",
        "code_station": "#X331001001",
        "H": [
          0.815,
          0.815,
          0.821,
          ...
        ],
        "Q": [
          36.87,
          36.87,
          37.458,
          ...
        ]
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          5.032432216493836,
          43.82748690979179
        ]
      }
    }
  ]
}

# Advanced feature filtering

Features are spatially indexed based on MongoDB capabilities (opens new window). So you can perform a geospatial query (opens new window) on your results like this:

import api from 'src/api'

const collection = await app.getService('features').find({
  query: {
    geometry: {
      $near: {
        $geometry: {
          type: 'Point',
          coordinates: [longitude, latitude]
        },
        $maxDistance: 10000 // in meters, i.e. 10 Kms around
      }
    }
  }
})
// Do something with the resulting feature collection

Such a complex query is fine when using the Feathers isomorphic API (opens new window) but if to be expressed manually as HTTP REST request you have to convert nested objects to array notation like this: /api/features?probeId=59248de38bb91d28d8155b3c&forecastTime=2017-05-27T07:00:00.000Z&geometry[$near][$maxDistance]=1000000&geometry[$near][$geometry][type]=Point&geometry[$near][$geometry][coordinates][]=5&geometry[$near][$geometry][coordinates][]=43.

The service also supports some shortcuts for the most useful types of spatial requests:

  • proximity filter will trigger a $near (opens new window) or $geoNear (opens new window) operation (if aggregation is performed)
    • centerLon: location longitude
    • centerLat: location latitude
    • distance: query distance from location in meters
  • bounding box filter will trigger a $geoIntersects (opens new window) operation
    • south: south coordinates of the bounding box in degrees
    • north: north coordinates of the bounding box in degrees
    • west: west coordinates of the bounding box in degrees
    • east: east coordinates of the bounding box in degrees

WARNING

Complex spatial queries can raise an issue when using REST HTTP requests due to their length, in that case use shortcuts presented above.

You can also filter results according to the computed element values by using the Feathers common database query API (opens new window), e.g. to get results with a speed between 2 m/s and 5 m/s in a given area:

import api from 'src/api'

const collection = await api.getService('features').find({
  query: {
    'properties.speed': {
      $gte: 2,
      $lte: 5
    },
    south: 45,
    north: 50,
    west: 5,
    east: 10
  }
})
// Do something with the resulting feature collection

# Hooks

The following hooks are executed on the features service:

These are mainly hooks to convert from/to JS/MongoDB data types.

# Alerts service

TIP

Available as a global and a contextual service

The service can be created using the global createAlertsService(options) function, if no context provided it will be available as a global service otherwise as a contextual service (e.g. attached to a specific organization), please also refer to core module createService().

WARNING

update method is not allowed now, you have to recreate the alert if you'd like to update it. patch method is only allowed from the server side in order to update the alert status.

Alerts are user-defined conditions automatically and continuously evaluated as new time-based data are gathered and target sensor measures or forecast data. It can be viewed as an automated query of the feature aggregation API or Weacast probe API (opens new window) that will raise an event or trigger a webhook whenever a matching result is found. With alerts you can create triggers which will fire on an occurrence of the selected measure or weather conditions (temperature, humidity, pressure, etc.) in a specified location and period of time.

# Data model

The common model is a GeoJSON feature (opens new window) with the geometry of the source feature for measurements or a GeoJSON point (opens new window) for weather.

The data model of an alert as used by the API is detailed below.

Alert data model

The details of some of the properties are the following:

  • cron: a CRON pattern (opens new window) to schedule the alert check at regular intervals, e.g. * 0 * * * * will run it every minute
  • feature: the ID of the feature to be checked by the alert
  • layer: the layer object owing the feature to be checked by the alert
  • elements: array of names corresponding to the probed forecast element values to be checked by the alert (each element has to be declared in the weacast configuration (opens new window))
  • period: time interval used to do the matching between the forecast data and the trigger conditions
    • start: offset which needs to be added to the current timestamp at the moment of validation to compute the beginning of the time interval used to check the conditions, specified as moment.js duration object (opens new window)
    • end: offset which needs to be added to the current timestamp at the moment of validation to compute the end of the time interval used to check the conditions, specified as moment.js duration object (opens new window)
  • conditions: query specification which contains nested objects:
  • geometry specification of a geospatial query (opens new window)
  • webhook: webhook to be triggered
    • url: webhook target URL
    • xxx: request body field values that will be POST on the URL

# Hooks

The following hooks are executed on the alerts service:

These are mainly hooks to convert from/to JS/MongoDB data types.