Geo
Les quatre opérations geo typées du SDK Plateforme — résoudre une coordonnée localement, découvrir les couches autorisées et interroger des entités autour d'une zone sans jamais inventer de précision
geo est une capacité de la plateforme, au même titre que le LLM ou la
recherche web. Votre organisation choisit un fournisseur geo (une extension
installée, par exemple un point de terminaison ArcGIS ou GeoServer) ; votre code
appelle quatre opérations typées et ne se ramifie jamais selon le fournisseur
qui a répondu.
import { createHttpPlatformTransport, createScrydonSDK } from '@scrydon/sdk/platform'
const platform = createScrydonSDK(
createHttpPlatformTransport({
baseUrl: 'https://scrydon.com',
headers: () => ({ authorization: `Bearer ${accessToken}` }),
})
)
const { location } = await platform.geo.locations.resolve({
location: { kind: 'mgrs', reference: '33UUP9005' },
})| Opération | HTTP | Rôle |
|---|---|---|
geo.locations.resolve | POST /api/platform/v1/geo/locations/resolve | Convertit une localisation typée en WGS84, en interne. Aucun fournisseur n'est contacté. |
geo.layers.list | POST /api/platform/v1/geo/layers/list | Liste les couches servies par le fournisseur sélectionné. |
geo.layers.describe | POST /api/platform/v1/geo/layers/describe | Décrit une couche : champs, type de géométrie, CRS source, emprise, attribution. |
geo.features.query | POST /api/platform/v1/geo/features/query | Renvoie les entités contenues dans une zone, éventuellement filtrées. |
Les quatre opérations sont en lecture seule et peuvent être réessayées sans risque.
Les localisations sont typées — la plateforme ne devine jamais
Il existe une seule forme de localisation : une union discriminée sur kind.
Vous nommez toujours le système de coordonnées ; une paire de nombres sans
étiquette n'est acceptée nulle part.
kind | Champs | Exemple |
|---|---|---|
latLng | lat, lng (nombres, degrés WGS84) | { kind: 'latLng', lat: 47.89, lng: 13.53 } |
dms | lat, lng (chaînes) | { kind: 'dms', lat: "47°53'36\"N", lng: "13°32'06\"E" } |
mgrs | reference (chaîne) | { kind: 'mgrs', reference: '33UUP9005' } |
utm | zone, hemisphere, easting, northing | { kind: 'utm', zone: 33, hemisphere: 'north', easting: 390000, northing: 5305000 } |
ups | hemisphere, easting, northing | { kind: 'ups', hemisphere: 'north', easting: 2000000, northing: 2200000 } |
Une référence mal formée donne lieu à un rejet typé, jamais à une supposition.
Elle revient sous la forme d'un INVALID_REQUEST accompagné d'un
details.geoParseCode qui nomme le défaut : empty_input, malformed,
out_of_range, invalid_zone ou odd_digit_count.
geo.locations.resolve — une précision digne de confiance
Une référence de quadrillage nomme une cellule, pas un point. 33UUP est un
carré de 100 km ; 33UUP9005 est un carré de 1 km à l'intérieur. Le résultat
porte donc la cellule à côté du point représentatif, de sorte qu'aucun
consommateur en aval ne puisse revendiquer une précision que l'opérateur n'a
jamais fournie.
const { location, executionId } = await platform.geo.locations.resolve({
location: { kind: 'mgrs', reference: '33UUP' },
})
location.point // [lng, lat] — le CENTRE de la cellule, pas un coin
location.extent // [west, south, east, north] — présent car l'entrée nommait une cellule
location.precisionMeters // 100000 — la taille de cellule réellement déclarée par la référence
location.original // l'entrée, renvoyée telle quelle
location.provenance // 'local' — converti en interne, aucun fournisseur contactéextentetprecisionMeterssont présents exactement quand l'entrée nomme une cellule (mgrs). Une entréelatLng,dms,utmouupsnomme un point et ne porte ni l'un ni l'autre.precisionMetersvaut1,10,100,1000,10000ou100000.- Les positions suivent l'ordre GeoJSON —
[lng, lat], jamais[lat, lng]— et sont en 2D. L'altitude est écartée avant d'atteindre ce contrat.
Cette opération s'exécute à l'intérieur de la plateforme, contre le noyau de conversion local. Une référence de quadrillage saisie par un opérateur ne quitte jamais la plateforme pour être convertie, et aucun fournisseur geo n'a besoin d'être configuré pour qu'elle fonctionne.
geo.layers.list et geo.layers.describe
const { layers, vendor } = await platform.geo.layers.list({})
const { layer } = await platform.geo.layers.describe({ layer: layers[0].id })
layer.id // opaque, propre au fournisseur — à renvoyer tel quel
layer.geometryType // 'point' | 'line' | 'polygon' | 'multi' | 'unknown'
layer.fields // [{ name, type, alias? }] — noms de requête et d'affichage
layer.sourceCrs // ce que publie le fournisseur, par exemple 'EPSG:3857'
layer.extent // [west, south, east, north], lorsque le fournisseur en déclare une
layer.attribution // à afficher lorsque vous rendez la couche
layer.supportedOperations // ['listLayers', 'describeLayer', 'queryFeatures']Les résultats sont toujours en WGS84, quel que soit sourceCrs : l'ordre des
axes et la projection sont normalisés avant que quoi que ce soit ne vous
parvienne.
supportedOperations s'entend par couche. Un fournisseur peut implémenter
la requête d'entités alors qu'une couche donnée la refuse encore ; vérifiez donc
la couche avant de proposer une requête dessus. Savoir si le fournisseur
implémente l'opération est une autre question, à laquelle la plateforme répond
pour vous — voir Erreurs.
geo.features.query
const page = await platform.geo.features.query({
layer: layer.id,
area: { kind: 'location', location: { kind: 'mgrs', reference: '33UUP9005' } },
filters: [{ field: 'status', op: 'eq', value: 'active' }],
fields: ['status', 'name'],
limit: 200,
})
page.features // [{ id?, geometry, properties }] — géométrie GeoJSON bornée
page.truncated // true lorsqu'il reste plus que ce que cette page contient
page.cursor // à renvoyer dans `cursor` pour la page suivante
page.retrievedAt // quand la PLATEFORME a récupéré cette page (ISO 8601)
page.attribution // à afficher à côté des résultatsLa zone est une bbox, ou une localisation qui nomme une cellule
area: { kind: 'bbox', bbox: [13.5, 47.8, 13.6, 47.9] } // [west, south, east, north]
area: { kind: 'location', location: { kind: 'mgrs', reference: '33UUP9005' } }Une zone de type location fonctionne en prenant l'emprise de la cellule
résolue comme enveloppe de recherche. Un type de localisation qui nomme un point
— latLng, dms, utm, ups — n'a pas d'emprise : il est donc refusé avec un
INVALID_REQUEST plutôt que transformé en zone par l'invention d'un rayon que
vous n'avez jamais fourni. Si vous voulez un rayon, dites-le avec une bbox
explicite.
Les filtres sont structurés, jamais une chaîne de requête
filters: [
{ field: 'status', op: 'eq', value: 'active' },
{ field: 'altitude', op: 'gte', value: 1000 },
{ field: 'callsign', op: 'in', value: ['ALPHA', 'BRAVO'] },
]op vaut eq, ne, gt, gte, lt, lte, like ou in ; value est une
chaîne, un nombre, un booléen, ou un tableau de ceux-ci. Il n'existe aucune
entrée CQL, SQL ou clause where brute — le langage de requête du fournisseur
est compilé à l'intérieur de l'extension, de sorte que rien de ce que vous
envoyez n'y est interpolé.
La troncature est explicite
truncated est toujours présent et toujours honnête : une page plafonnée par le
fournisseur se lit comme tronquée, jamais comme complète. Si un cursor est
présent, renvoyez-le avec la même requête pour continuer. Si un résultat tronqué
ne porte pas de curseur, réduisez la zone ou affinez les filtres ; ne considérez
pas cette page comme le jeu de données complet.
retrievedAt indique quand la plateforme a récupéré la page. Ce n'est
délibérément pas l'âge des données sous-jacentes : si une couche publie des
horodatages d'observation ou de mise à jour, ils arrivent dans les properties
de chaque entité, et c'est ce champ qu'il faut montrer à un opérateur qui
demande la fraîcheur d'une entité.
Choisir un fournisseur
Toutes les opérations sauf locations.resolve acceptent un provider
facultatif. Omettez-le — ou envoyez "auto" ou "" — et le fournisseur geo
configuré pour votre organisation répond. Envoyez un fournisseur explicitement
et ce choix est épinglé pour l'appel : les identifiants d'accès, la vérification
de prise en charge et la répartition se résolvent tous vers le même.
Limites
| Limite | Valeur |
|---|---|
| Positions par géométrie | 4096 |
| Anneaux par polygone | 32 |
| Géométries par multi-partie | 256 |
| Filtres par requête | 32 |
| Entités par page | 500 |
Ce sont des bornes contractuelles, pas des conseils. Une réponse de fournisseur
qui les dépasse est signalée comme PROVIDER_CONTRACT_VIOLATION plutôt que
tronquée en silence.
Erreurs
Chaque échec est une PlatformApiError portant un code stable. Geo n'ajoute
aucun code nouveau.
| Code | HTTP | Quand |
|---|---|---|
INVALID_REQUEST | 400 | Une localisation ou une référence mal formée (details.geoParseCode), une localisation de type point utilisée comme zone, ou un corps qui échoue au contrat. |
CAPABILITY_NOT_CONFIGURED | 409 | Aucun fournisseur geo n'est configuré pour cette portée, aucune liaison n'existe, ou le fournisseur sélectionné ne sert pas l'opération appelée. |
POLICY_BLOCKED | 422 | Une décision de gouvernance — lisez details.policyCode. |
PROVIDER_CONTRACT_VIOLATION | 502 | Le fournisseur a renvoyé une géométrie ou une page hors contrat. |
PROVIDER_UNAVAILABLE | 503 | Le fournisseur n'a pas pu être joint. |
DEADLINE_EXCEEDED | 504 | L'appel a dépassé son délai. |
Une destination que votre organisation n'a pas autorisée n'est pas une 503.
Elle est refusée avant que la requête ne quitte la plateforme, par
POLICY_BLOCKED (422) avec details.policyCode : "EGRESS_BLOCKED_BY_POLICY" et
retryable : false — car une nouvelle tentative ne modifie pas une liste
d'autorisation. Ajoutez le nom d'hôte exact du point de terminaison dans
Paramètres → Gouvernance → Egress. Ce cas est distinct de
EGRESS_BLOCKED_BY_CLEARANCE ci-dessous, qui est une décision de classification
portant sur les données et non sur la destination.
Un tableau features vide est un succès, pas une erreur : la zone ne
contenait rien de correspondant. Ce cas est distinct de non pris en charge
(409), indisponible (503) et refusé (422), et la distinction est verrouillée par
des tests de contrat.
import { PlatformApiError } from '@scrydon/sdk/platform'
try {
await platform.geo.features.query({ layer, area })
} catch (err) {
if (err instanceof PlatformApiError && err.code === 'CAPABILITY_NOT_CONFIGURED') {
// Demandez à un administrateur de configurer un fournisseur geo, ou choisissez une autre couche.
}
}Un CAPABILITY_NOT_CONFIGURED sur une requête alors que layers.list a
fonctionné signifie en général que le fournisseur sélectionné implémente le
listage des couches mais pas la requête d'entités. La plateforme refuse avant
de contacter le fournisseur, ce qui se lit comme une réponse de configuration
plutôt que comme une panne du fournisseur. Les details de l'erreur nomment
l'opération demandée et les méthodes que le fournisseur sert effectivement.
Utiliser Geo dans Cortex
Cortex expose les mêmes opérations de plateforme via trois outils :
| Outil | Utilisation |
|---|---|
resolve_location | Convertir une coordonnée explicitement typée en conservant la précision et l'emprise d'une cellule de quadrillage. |
list_geo_layers | Découvrir les couches du fournisseur et leurs opérations prises en charge, ou décrire une couche sélectionnée. |
query_geo_features | Interroger une couche du fournisseur dans une bbox explicite ou une cellule résolue et afficher les résultats sur une carte. |
Par exemple, demandez à Cortex de résoudre la référence MGRS 33UUP9005, de
lister les couches geo disponibles, puis d'interroger une couche choisie dans
cette cellule. Une coordonnée ponctuelle nécessite une emprise explicite avant
la requête d'entités ; Cortex n'invente pas de rayon.
Ces outils utilisent votre connexion de plateforme dans le périmètre courant.
query_map recherche les objets de l'ontologie de votre organisation ; il
n'interroge pas les couches ArcGIS ou GeoServer. Un choix explicite de
fournisseur et de couche reste associé à la requête.
La carte affiche l'attribution du fournisseur et les contours des polygones. Une cellule résolue apparaît sous forme d'enveloppe englobante, et non de son empreinte exacte. Les résultats partiels sont signalés comme tronqués ; utilisez le curseur de continuation renvoyé pour demander une autre page. L'heure de récupération indique quand la plateforme a obtenu la page ; les dates d'observation restent des propriétés des entités sources.
Un résultat vide signifie que la requête a réussi sans entité correspondante. Un fournisseur absent, une requête refusée ou un service indisponible sont signalés comme des échecs.
Gouvernance
Les coordonnées ne sont pas expurgées. Une référence de quadrillage réécrite est
une position corrompue, pas une position protégée : les positions ne sont donc
jamais gouvernées par une expurgation de contenu. Le texte est gouverné de la
manière habituelle : les valeurs des filtres d'attribut à l'entrée, et les
propriétés des entités, les titres et descriptions de couches, les alias de
champs et l'attribution à la sortie. Les field et op d'un filtre sont
structurels et restent intacts — un nom de champ expurgé n'adresserait aucune
colonne.
La manière dont une position est gouvernée dépend de la sortie ou non de
l'appel hors de la plateforme. geo.locations.resolve convertit en interne et
ne contacte aucun fournisseur : il n'y a donc aucune frontière de sortie à
gouverner. geo.layers.list, geo.layers.describe et geo.features.query
s'adressent au fournisseur géospatial installé, et ceux-là sont soumis au
contrôle d'habilitation : la classification affirmée par l'exécution est
comparée au niveau attribué à la connexion résolue, avant tout contact avec
le fournisseur. Une connexion que personne n'a classée compte comme non
classée, ce que toute exécution classifiée dépasse.
Ce qui se produit en cas de violation dépend du mode de classification de votre organisation :
| Mode | En cas de violation |
|---|---|
enforce | L'appel est refusé par POLICY_BLOCKED avec details.policyCode: "EGRESS_BLOCKED_BY_CLEARANCE". |
audit, test_with_notifications | L'appel se poursuit ; la violation est consignée dans le journal d'audit. |
disabled | Aucune comparaison n'est effectuée. |
Une exécution qui n'affirme aucune classification n'est pas contrôlée du tout — il n'y a rien à déclasser.
disabled désactive la comparaison, pas la lecture : votre politique de
classification est lue avant que le mode ne soit connu, donc si cette lecture
échoue, l'appel est refusé par INTERNAL_ERROR quel que soit le mode. L'échec
de lecture du niveau attribué à la connexion elle-même est traité différemment :
il n'est fatal qu'en mode enforce ; dans les autres modes, la connexion est
considérée comme non classée.