sources/webrtc-reviewed/src_front/src/map/mapController.ts

Текстовый файл из учебного комплекта, не свидетельство установленного runtime. Вернуться к учебнику · Исходный файл

   1 import {
   2   Map as MapLibreMap,
   3   NavigationControl,
   4   setWorkerUrl,
   5   type GeoJSONSource,
   6   type LngLatBoundsLike,
   7 } from "maplibre-gl";
   8 import mapLibreWorkerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url";
   9 
  10 setWorkerUrl(mapLibreWorkerUrl);
  11 
  12 export type Airport = {
  13   id?: string;
  14   code?: string;
  15   name?: string;
  16   lat: number;
  17   lon: number;
  18   status?: string;
  19 };
  20 
  21 export type RouteArtifact = {
  22   from?: Record<string, unknown>;
  23   to?: Record<string, unknown>;
  24   geometry: [number, number][];
  25   distance_km?: number;
  26 };
  27 
  28 export class MapController {
  29   private map: MapLibreMap | null = null;
  30   private ready = false;
  31   private pendingAirports: Airport[] | null = null;
  32   private pendingPos: { lat: number; lon: number } | null = null;
  33   private pendingRoute: RouteArtifact | null = null;
  34 
  35   init(containerId: string): void {
  36     if (this.map) return;
  37 
  38     const map = new MapLibreMap({
  39       container: containerId,
  40       style: "https://demotiles.maplibre.org/style.json",
  41       center: [37.6173, 55.7558],
  42       zoom: 6,
  43     });
  44     this.map = map;
  45 
  46     map.addControl(new NavigationControl({ showCompass: true }), "top-left");
  47 
  48     map.on("load", () => {
  49       if (this.map !== map) return;
  50 
  51       map.addSource("airports", {
  52         type: "geojson",
  53         data: { type: "FeatureCollection", features: [] },
  54       });
  55       map.addLayer({
  56         id: "airports",
  57         type: "circle",
  58         source: "airports",
  59         paint: {
  60           "circle-radius": 5,
  61           "circle-color": "#ffb020",
  62           "circle-stroke-color": "#1d1d1f",
  63           "circle-stroke-width": 1,
  64         },
  65       });
  66 
  67       map.addSource("position", {
  68         type: "geojson",
  69         data: { type: "FeatureCollection", features: [] },
  70       });
  71       map.addLayer({
  72         id: "position",
  73         type: "circle",
  74         source: "position",
  75         paint: {
  76           "circle-radius": 7,
  77           "circle-color": "#007aff",
  78           "circle-stroke-color": "#ffffff",
  79           "circle-stroke-width": 2,
  80         },
  81       });
  82 
  83       map.addSource("route", {
  84         type: "geojson",
  85         data: { type: "FeatureCollection", features: [] },
  86       });
  87       map.addLayer({
  88         id: "route",
  89         type: "line",
  90         source: "route",
  91         paint: {
  92           "line-color": "#007aff",
  93           "line-width": 4,
  94         },
  95       });
  96 
  97       this.ready = true;
  98       if (this.pendingAirports) this.setAirports(this.pendingAirports);
  99       if (this.pendingPos) this.setCurrentPosition(this.pendingPos.lat, this.pendingPos.lon);
 100       if (this.pendingRoute) this.drawRoute(this.pendingRoute);
 101     });
 102   }
 103 
 104   setAirports(airports: Airport[]): void {
 105     if (!this.map || !this.ready) {
 106       this.pendingAirports = airports;
 107       return;
 108     }
 109     const source = this.map.getSource("airports") as GeoJSONSource;
 110     source.setData({
 111       type: "FeatureCollection",
 112       features: airports.map((a) => ({
 113         type: "Feature",
 114         geometry: { type: "Point", coordinates: [a.lon, a.lat] },
 115         properties: {
 116           id: a.id ?? "",
 117           code: a.code ?? "",
 118           name: a.name ?? "",
 119           status: a.status ?? "",
 120         },
 121       })),
 122     });
 123   }
 124 
 125   setCurrentPosition(lat: number, lon: number): void {
 126     if (!this.map || !this.ready) {
 127       this.pendingPos = { lat, lon };
 128       return;
 129     }
 130     const source = this.map.getSource("position") as GeoJSONSource;
 131     source.setData({
 132       type: "FeatureCollection",
 133       features: [
 134         {
 135           type: "Feature",
 136           geometry: { type: "Point", coordinates: [lon, lat] },
 137           properties: {},
 138         },
 139       ],
 140     });
 141   }
 142 
 143   drawRoute(route: RouteArtifact): void {
 144     if (!this.map || !this.ready) {
 145       this.pendingRoute = route;
 146       return;
 147     }
 148     const source = this.map.getSource("route") as GeoJSONSource;
 149     source.setData({
 150       type: "FeatureCollection",
 151       features: [
 152         {
 153           type: "Feature",
 154           geometry: { type: "LineString", coordinates: route.geometry },
 155           properties: { distance_km: route.distance_km ?? null },
 156         },
 157       ],
 158     });
 159 
 160     const bounds = this.boundsForCoords(route.geometry);
 161     if (bounds) {
 162       this.map.fitBounds(bounds, { padding: 60, duration: 800 });
 163     }
 164   }
 165 
 166   private boundsForCoords(coords: [number, number][]): LngLatBoundsLike | null {
 167     if (!coords.length) return null;
 168     let minLon = coords[0][0];
 169     let maxLon = coords[0][0];
 170     let minLat = coords[0][1];
 171     let maxLat = coords[0][1];
 172     for (const [lon, lat] of coords) {
 173       minLon = Math.min(minLon, lon);
 174       maxLon = Math.max(maxLon, lon);
 175       minLat = Math.min(minLat, lat);
 176       maxLat = Math.max(maxLat, lat);
 177     }
 178     return [
 179       [minLon, minLat],
 180       [maxLon, maxLat],
 181     ];
 182   }
 183 }