Intégration des couches de tuiles OpenLayers dans une application Vue3 avec TypeScript

Dans le développement web contemporain, l'affichage de cartes interactives est une exigence courante. OpenLayers se distingue comme une bibliothèque cartographique open source robuste et performante. Cependant, l'implémentation directe de la logique d'initialisation dans un composant Vue peut mener à un code volumineux et peu maintenable, limitant ainsi la réutilisabilité. Cet article détaille comment séparer cette logique en une fonction utilitaire dédiée dans un environnement Vite, Vue3 et TypeScript, favorisant un code modulaire et réutilisable.

Prérequis

Avant de commencer, assurez-vous d'avoir mis en place un projet de base avec Vite, Vue3 et TypeScript. Installez OpenLayers via npm :

npm install ol

Pour faciliter le développement local et l'accès hors ligne, placez les tuiles cartographiques dans le répertoire public du projet. Vite copie ces fichiers tels quels dans le dossier de construction final, permettant un accès via le chemin racine (/). Par exemple, une structure de répertoire pour les tuiles pourrait être :

public/
  └── Carte/
      ├── 10/
      ├── 11/
      ├── 12/
      └── ...

Implémentation initiale : initialisation dans un composant

Une approche courante consiste à intégrer la logique OpenLayers directement dans un composant Vue. Voici un exemple typique :

<template>
  <div id="map-container" class="map-view"></div>
</template>

<script setup lang="ts">
import { onMounted, ref, nextTick } from 'vue'
import 'ol/ol.css'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import { Map, View } from 'ol'
import { fromLonLat } from 'ol/proj'

const carteInstance = ref<Map | null>(null)
const tuileUrl = ref('/Carte/{z}/{x}/{y}.jpg')
const vueCarte = {
  center: fromLonLat([116.40, 39.90]),
  zoom: 13,
  minZoom: 1,
  maxZoom: 22
}

const initialiserCarte = () => {
  const coucheTuiles = new TileLayer({
    source: new XYZ({
      url: tuileUrl.value,
      crossOrigin: 'anonymous'
    })
  })
  carteInstance.value = new Map({
    target: 'map-container',
    layers: [coucheTuiles],
    view: new View(vueCarte),
    controls: []
  })
}

onMounted(() => {
  nextTick(() => {
    initialiserCarte()
  })
})
</script>

<style scoped lang="scss">
.map-view {
  width: 100%;
  height: 100vh;
}
</style>

Cette approche présente plusieurs défauts :

  • Couplage élevé : La création, la configuration et la logique de la vue sont étroitement liées au composant.
  • Difficulté de réutilisation : Dupliquer le code pour d'autres pages devient nécessaire, ce qui est inefficace.
  • Responsabilités confondues : Le composant Vue devrait se concentrer sur la gession de l'interface utilisateur, non sur l'initialisation complexe de la carte.

Restructuration : extraction en fonction utilitaire

Pour résoudre ces problèmes, nous isolons la logique d'initialisation dans un fichier TypeScript dédié.

1. Création du fichier utilitaire

Créez un fichier, par exemple src/utils/carteUtil.ts, pour encapsuler les opérations OpenLayers.

2. Migration et encapsualtion du code

Déplacez toutes les importations et la logique d'initialisation dans ce fichier, en exportant une fonction qui retourne une instance Map.

import 'ol/ol.css'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import { Map, View } from 'ol'
import { fromLonLat } from 'ol/proj'

const tuileSourceUrl = '/Carte/{z}/{x}/{y}.jpg'

const optionsVueParDefaut = {
  center: fromLonLat([116.40, 39.90]),
  zoom: 13,
  minZoom: 1,
  maxZoom: 22
}

/**
 * Crée et retourne une instance de carte OpenLayers.
 * @param identifiantCible ID de l'élément DOM cible
 * @returns Instance de la carte
 */
export function creerCarte(identifiantCible: string): Map {
  const coucheTuiles = new TileLayer({
    source: new XYZ({
      url: tuileSourceUrl,
      crossOrigin: 'anonymous'
    })
  })

  const carte = new Map({
    target: identifiantCible,
    layers: [coucheTuiles],
    view: new View(optionsVueParDefaut),
    controls: []
  })

  return carte
}

Avantages de cette encapsulation :

  • La fonction creerCarte accepte un paramètre cible, offrant une flexibilité pour le rendu dans différents conteneurs DOM.
  • Elle retourne l'instance Map, permettant au composant appelant de l'utiliser pour des interactions ultérieures comme l'ajout de marqueurs.
  • Les configurations par défaut sont centralisées dans le module, simplifiant les modifications.

3. Utilisation dans un composant

Le composant est ainsi simplifié, se concentrant uniquement sur le cycle de vie et l'appel à la fonction utilitarie.

<template>
  <div id="map-container" class="map-view"></div>
</template>

<script setup lang="ts">
import { onMounted, ref, nextTick } from 'vue'
import { creerCarte } from '@/utils/carteUtil'
import type { Map } from 'ol'

const instanceCarte = ref<Map | null>(null)

onMounted(() => {
  nextTick(() => {
    instanceCarte.value = creerCarte('map-container')
  })
})
</script>

<style scoped lang="scss">
.map-view {
  width: 100%;
  height: 100vh;
}
</style>

Limitation de l'étendue d'affichage

Pour restreindre la zone visible de la carte, configurez l'attribut extent dans la vue OpenLayers. Ce paramètre définit une boîte englobante pour les déplacements et le zoom. Les coordonnées géographiques peuvent être gérées via un store Pinia pour une gestion centralisée.

Exemple de configuration dans un store Pinia (src/store/parametres.ts) :

export const useParametresStore = defineStore('parametres', () => {
  const etendueCarte = {
    longitudeMin: 116.20,
    latitudeMin: 39.80,
    longitudeMax: 116.60,
    latitudeMax: 40.05
  }
  return { etendueCarte }
})

Modifiez ensuite la fonction utilitaire pour intégrer cette restriction :

import 'ol/ol.css'
import TileLayer from 'ol/layer/Tile'
import XYZ from 'ol/source/XYZ'
import { Map, View } from 'ol'
import { fromLonLat, transformExtent } from 'ol/proj'
import { useParametresStore } from '@/store/parametres'

const store = useParametresStore()
const tuileSourceUrl = '/Carte/{z}/{x}/{y}.jpg'
const etendueBrute = [
  store.etendueCarte.longitudeMin,
  store.etendueCarte.latitudeMin,
  store.etendueCarte.longitudeMax,
  store.etendueCarte.latitudeMax
]
const etendueTransformee = transformExtent(etendueBrute, 'EPSG:4326', 'EPSG:3857')

export function creerCarte(identifiantCible: string) {
  const optionsVue = {
    center: fromLonLat([116.40, 39.90]),
    zoom: 13,
    minZoom: 11,
    maxZoom: 22,
    extent: etendueTransformee
  }

  const coucheTuiles = new TileLayer({
    source: new XYZ({
      url: tuileSourceUrl,
      crossOrigin: 'anonymous'
    })
  })

  const carte = new Map({
    target: identifiantCible,
    layers: [coucheTuiles],
    view: new View(optionsVue),
    controls: []
  })

  return carte
}

Cette approche offre une séparation claire des responsabilités, une réutilisabilité accrue et une maintenabilité améliorée, essentielle pour les projets cartographiques complexes.

Étiquettes: OpenLayers Vue3 TypeScript vite Pinia

Publié le 22 juin à 20h44