Skip to content

Intégrer des points JSON externes

Objectif

Pattern associé: application -> carte.

Cette intégration est faisable sans créer de composant dans l’API Geoviewer. Le principe est de charger le viewer côté application, récupérer viewerService, ajouter une couche runtime GRAPHICS, puis transformer un tableau JSON métier en features cartographiques cliquables.

Objets API utilisés

Flux de données

  1. l’application charge ou reçoit un tableau JSON externe
  2. elle récupère viewerService via le Web Component
  3. elle crée une couche GRAPHICS avec mapManager.addGraphicMapService(...)
  4. elle transforme chaque entrée JSON en ApiFeaturePoint
  5. elle choisit un marker selon travelModes
  6. elle écoute le clic sur la couche pour zoomer et ouvrir une popup

Démo visuelle

La page ci-dessous est une vraie instanciation du viewer tutoriel JSON_POINT avec:

  • un jeu de données JSON servi par cette webapp doc
  • trois markers SVG servis par cette webapp doc
  • une symbolisation pilotée par travelModes
  • une couche GRAPHICS ajoutée à chaud
  • un clic marker => zoom et infoWindow

Fichiers hébergés pour cette démo:

  • doc/public/tutorials/json-point-demo/index.html
  • doc/public/tutorials/json-point-demo/data/eco-vision-counters.json
  • doc/public/tutorials/json-point-demo/markers/marker-pedestrian.svg
  • doc/public/tutorials/json-point-demo/markers/marker-bike.svg
  • doc/public/tutorials/json-point-demo/markers/marker-mixed.svg

Snippet minimal

L’exemple ci-dessous part d’un tableau JSON:

[
{
"id": "100056099",
"coords": [50.459927476934546, 4.871012439320255],
"name": "L'Enjambée Jambes",
"domain": {
"id": "4320",
"name": "Ville de Namur"
},
"travelModes": ["pedestrian", "bike"]
}
]

Important: dans cet exemple, coords est fourni sous la forme [latitude, longitude]. Pour créer un point Geoviewer, il faut passer x = longitude et y = latitude.

Règle de symbolisation utilisée dans la démo:

  • ["pedestrian"] => marker piéton
  • ["bike"] => marker vélo
  • ["pedestrian", "bike"] => marker mixte
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Compteurs externes</title>
<style>
html,
body,
geoviewer-esri-element {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
<script src="https://geoservices.wallonie.be/geoviewer-api/api/<TOKEN>/<CODE_VIEWER>"></script>
</head>
<body>
<geoviewer-esri-element id="viewer"></geoviewer-esri-element>
<script type="module">
const MARKERS = {
pedestrian: '/markers/marker-pedestrian.svg',
bike: '/markers/marker-bike.svg',
mixed: '/markers/marker-mixed.svg',
};
function resolveTravelModeKey(travelModes) {
const normalized = [...new Set((travelModes ?? []).map((mode) => String(mode).toLowerCase()))].sort();
if (normalized.includes('pedestrian') && normalized.includes('bike')) {
return 'mixed';
}
if (normalized.includes('bike')) {
return 'bike';
}
return 'pedestrian';
}
function resolveMarkerSymbol(travelModes) {
return {
type: 'picture-point',
source: MARKERS[resolveTravelModeKey(travelModes)],
width: 30,
height: 30,
};
}
const viewerElement = document.getElementById('viewer');
viewerElement.onViewerServiceReady((viewerService) => {
viewerService.onReady(async () => {
const response = await fetch('https://mon-api.exemple/eco-vision/counters');
const counters = await response.json();
const mapManager = viewerService.mapManager;
const featureFactory = mapManager.tools.featureFactory;
const zoom = mapManager.tools.zoom;
const events = mapManager.tools.events;
const countersLayer = mapManager.addGraphicMapService({
id: 'eco-vision-counters',
type: 'GRAPHICS',
label: 'Compteurs',
visible: true,
removable: true,
identifiable: true,
opacity: 1,
toc: {
visible: true,
open: false,
},
objectIdProperty: 'id',
editing: false,
isDraw: false,
symbols: {
point: {
type: 'simple-point',
color: '#0f766e',
size: 10,
},
polyline: {
type: 'simple-polyline',
color: '#0f766e',
width: 2,
style: 'SOLID',
},
polygon: {
type: 'simple-polygon',
color: [15, 118, 110, 0.2],
border: {
type: 'simple-polyline',
color: '#0f766e',
width: 1,
style: 'SOLID',
},
},
},
});
const features = counters.map((counter) =>
featureFactory.createPoint({
x: counter.coords[1],
y: counter.coords[0],
wkid: 4326,
symbol: resolveMarkerSymbol(counter.travelModes),
attributes: {
id: counter.id,
name: counter.name,
domainId: counter.domain?.id ?? null,
domainName: counter.domain?.name ?? null,
travelModes: (counter.travelModes ?? []).join(', '),
markerType: resolveTravelModeKey(counter.travelModes),
raw: counter,
},
}),
);
countersLayer.addFeatures(features);
events.listenGraphicMapServiceClick(countersLayer, async (clickedFeatures) => {
const [feature] = clickedFeatures;
if (!feature) {
return;
}
await zoom.zoomToFeature(feature);
await mapManager.openPopup(
feature.attributes.name,
`
<div>
<strong>ID:</strong> ${feature.attributes.id}<br />
<strong>Marker:</strong> ${feature.attributes.markerType}<br />
<strong>Modes:</strong> ${feature.attributes.travelModes}
</div>
`,
{
location: feature,
popupPosition: 'top-right',
},
);
});
});
});
</script>
</body>
</html>

Mettre à jour les points

Si les données changent, il n’est pas nécessaire de recréer le viewer. Il suffit de réutiliser la couche GRAPHICS:

function replaceCounters(countersLayer, counters, featureFactory) {
countersLayer.removeAll();
const features = counters.map((counter) =>
featureFactory.createPoint({
x: counter.coords[1],
y: counter.coords[0],
wkid: 4326,
symbol: resolveMarkerSymbol(counter.travelModes),
attributes: {
id: counter.id,
name: counter.name,
markerType: resolveTravelModeKey(counter.travelModes),
},
}),
);
countersLayer.addFeatures(features);
}

Pièges fréquents

  • Vérifiez l’ordre des coordonnées. Les API renvoient parfois [latitude, longitude], alors que Geoviewer attend x = longitude, y = latitude.
  • Gardez la couche GRAPHICS et remplacez seulement ses features quand les données changent. Inutile de recréer le viewer.
  • Le marker peut être porté par feature.symbol, ce qui est pratique pour une symbolisation pilotée par attribut.
  • Si vous avez besoin d’une UI métier embarquée dans le viewer, ce n’est plus une simple intégration externe: il faudra envisager un widget dédié.

Liens vers l’API technique

Aller plus loin