From 774ed78051e0a4b04a460e06d41fc4fe10bff433 Mon Sep 17 00:00:00 2001 From: Rizq0 Date: Tue, 5 Aug 2025 22:59:48 +0100 Subject: [PATCH] fix: hex layer now only updates once when changed --- src/app/map/map.component.ts | 259 +++++++++++++++++++---------------- 1 file changed, 138 insertions(+), 121 deletions(-) diff --git a/src/app/map/map.component.ts b/src/app/map/map.component.ts index 0b37fe4..77605e1 100644 --- a/src/app/map/map.component.ts +++ b/src/app/map/map.component.ts @@ -1,38 +1,47 @@ -import {AfterViewInit, Component, effect, ElementRef, inject, OnDestroy, ViewChild, WritableSignal} from '@angular/core'; -import {MapInfoComponent} from '../map-info/map-info.component'; +import { + AfterViewInit, + Component, + effect, + ElementRef, + inject, + OnDestroy, + ViewChild, + WritableSignal, +} from '@angular/core'; +import { MapInfoComponent } from '../map-info/map-info.component'; import Map from 'ol/Map'; -import {SettingsService} from '../../services/settings.service'; +import { SettingsService } from '../../services/settings.service'; import { HexOverlayService, LayerService, RegionLabelsService, RegionSectorsService, - StructureIconsService + StructureIconsService, } from '../../layers'; -import {Shard} from '../../services/war-api.service'; +import { Shard } from '../../services/war-api.service'; import proj4 from 'proj4'; -import {register} from 'ol/proj/proj4'; -import {Projection} from 'ol/proj'; +import { register } from 'ol/proj/proj4'; +import { Projection } from 'ol/proj'; import TileLayer from 'ol/layer/Tile'; import TileDebug from 'ol/source/TileDebug'; -import {TileGrid} from 'ol/tilegrid'; +import { TileGrid } from 'ol/tilegrid'; import LayerSwitcher from 'ol-layerswitcher'; -import {DragPan, Link, MouseWheelZoom} from 'ol/interaction'; -import {Kinetic} from 'ol'; +import { DragPan, Link, MouseWheelZoom } from 'ol/interaction'; +import { Kinetic } from 'ol'; import View from 'ol/View'; import XYZ from 'ol/source/XYZ'; import LayerGroup from 'ol/layer/Group'; import VectorLayer from 'ol/layer/Vector'; -import {DrawingToolbarComponent} from '../drawing-toolbar/drawing-toolbar.component'; -import {DrawingService, DrawingType} from '../../services/drawing.service'; -import {DrawingContextMenuComponent} from '../drawing-context-menu/drawing-context-menu.component'; -import {LayerGroupsToolbarComponent} from '../layer-groups-toolbar/layer-groups-toolbar.component'; -import {LayerGroupsService} from '../../services/layer-groups.service'; +import { DrawingToolbarComponent } from '../drawing-toolbar/drawing-toolbar.component'; +import { DrawingService, DrawingType } from '../../services/drawing.service'; +import { DrawingContextMenuComponent } from '../drawing-context-menu/drawing-context-menu.component'; +import { LayerGroupsToolbarComponent } from '../layer-groups-toolbar/layer-groups-toolbar.component'; +import { LayerGroupsService } from '../../services/layer-groups.service'; import Feature from 'ol/Feature'; -import {Geometry} from 'ol/geom'; -import {initializeMapTooltip, MapTooltip} from './features'; -import {HotkeyService} from '../../services/hotkey.service'; -import {HotkeyDisplayComponent} from '../hotkey-display/hotkey-display.component'; +import { Geometry } from 'ol/geom'; +import { initializeMapTooltip, MapTooltip } from './features'; +import { HotkeyService } from '../../services/hotkey.service'; +import { HotkeyDisplayComponent } from '../hotkey-display/hotkey-display.component'; @Component({ selector: 'app-map', @@ -41,10 +50,10 @@ import {HotkeyDisplayComponent} from '../hotkey-display/hotkey-display.component DrawingToolbarComponent, DrawingContextMenuComponent, LayerGroupsToolbarComponent, - HotkeyDisplayComponent + HotkeyDisplayComponent, ], templateUrl: './map.component.html', - styleUrl: './map.component.css' + styleUrl: './map.component.css', }) export class MapComponent implements AfterViewInit, OnDestroy { @ViewChild('map', { static: true }) mapElement!: ElementRef; @@ -56,14 +65,18 @@ export class MapComponent implements AfterViewInit, OnDestroy { private readonly settingsService: SettingsService = inject(SettingsService); private readonly drawingService: DrawingService = inject(DrawingService); - private readonly layerGroupsService: LayerGroupsService = inject(LayerGroupsService); - private readonly structureIconsService: StructureIconsService = inject(StructureIconsService); - private readonly regionSectorsService: RegionSectorsService = inject(RegionSectorsService); + private readonly layerGroupsService: LayerGroupsService = + inject(LayerGroupsService); + private readonly structureIconsService: StructureIconsService = inject( + StructureIconsService + ); + private readonly regionSectorsService: RegionSectorsService = + inject(RegionSectorsService); private readonly hotkeyService: HotkeyService = inject(HotkeyService); private readonly staticLayers: LayerService[] = [ inject(HexOverlayService), inject(RegionLabelsService), - inject(StructureIconsService) + inject(StructureIconsService), ]; shard: WritableSignal = this.settingsService.selectedShard; @@ -75,22 +88,22 @@ export class MapComponent implements AfterViewInit, OnDestroy { this.map = this.initialiseMap(); this.setupHotkeys(); - this.hotkeyService.enableContext("map"); + this.hotkeyService.enableContext('map'); this.layerSwitcher = new LayerSwitcher({ startActive: false, // Start closed tipLabel: 'Layer list', // Tooltip for button - collapseTipLabel: 'Close layer list' // Tooltip when open + collapseTipLabel: 'Close layer list', // Tooltip when open }); this.drawingService.initialize(this.map); this.mapTooltip = initializeMapTooltip(this.map, { tooltipElement: 'tooltip', - hitTolerance: 5 + hitTolerance: 5, }); - this.drawingService.featureRightClicked$.subscribe(featureClick => { + this.drawingService.featureRightClicked$.subscribe((featureClick) => { if (featureClick) { this.contextMenu.show(featureClick.event, featureClick.feature); } @@ -108,68 +121,68 @@ export class MapComponent implements AfterViewInit, OnDestroy { this.hotkeyService.register({ key: '?', modifiers: { - shift: true + shift: true, }, description: 'Show keyboard shortcuts', action: () => this.toggleHotkeyDisplay(), - context: 'map' + context: 'map', }); this.hotkeyService.register({ - key: "r", - description: "Reset map layers", + key: 'r', + description: 'Reset map layers', action: () => this.layerGroupsService.resetToDefaults(), - context: 'map' - }) + context: 'map', + }); this.hotkeyService.register({ key: '1', - description: "Toggle Overview Layer", + description: 'Toggle Overview Layer', action: () => this.layerGroupsService.toggleLayerGroup('overview'), - context: 'map' + context: 'map', }); this.hotkeyService.register({ key: '2', - description: "Toggle Logistics Layer", + description: 'Toggle Logistics Layer', action: () => this.layerGroupsService.toggleLayerGroup('logistics'), - context: 'map' - }) + context: 'map', + }); this.hotkeyService.register({ key: '3', - description: "Toggle Resource Layer", + description: 'Toggle Resource Layer', action: () => this.layerGroupsService.toggleLayerGroup('production'), - context: 'map' - }) + context: 'map', + }); this.hotkeyService.register({ key: '4', - description: "Toggle Defence Layer", + description: 'Toggle Defence Layer', action: () => this.layerGroupsService.toggleLayerGroup('defence'), - context: 'map' - }) + context: 'map', + }); this.hotkeyService.register({ key: '5', - description: "Toggle Intelligence Layer", + description: 'Toggle Intelligence Layer', action: () => this.layerGroupsService.toggleLayerGroup('intelligence'), - context: 'map' + context: 'map', }); this.hotkeyService.register({ key: '6', - description: "Toggle Heavy Weapons Layer", + description: 'Toggle Heavy Weapons Layer', action: () => this.layerGroupsService.toggleLayerGroup('weapons'), - context: 'map' + context: 'map', }); this.hotkeyService.register({ key: '7', - description: "Toggle Hex Sectors Layer", + description: 'Toggle Hex Sectors Layer', action: () => this.layerGroupsService.toggleLayerGroup('hex-sectors'), - context: 'map' - }) + context: 'map', + }); } private resetMapView(): void { @@ -193,7 +206,9 @@ export class MapComponent implements AfterViewInit, OnDestroy { onLayerSwitcherToggle(): void { if (this.layerSwitcher && this.map) { const controls = this.map.getControls(); - const existingLayerSwitcher = controls.getArray().find(control => control instanceof LayerSwitcher); + const existingLayerSwitcher = controls + .getArray() + .find((control) => control instanceof LayerSwitcher); if (existingLayerSwitcher) { this.map.removeControl(existingLayerSwitcher); @@ -204,86 +219,96 @@ export class MapComponent implements AfterViewInit, OnDestroy { } } - - readonly PROJECTION_CODE = "GAME_SIMPLE"; + readonly PROJECTION_CODE = 'GAME_SIMPLE'; readonly TILE_SIZE = 256; readonly MAX_ZOOM = 8; readonly maxTileScale = Math.pow(2, this.MAX_ZOOM); - readonly tileExtent = [0, 0, this.TILE_SIZE * this.maxTileScale, this.TILE_SIZE * this.maxTileScale]; + readonly tileExtent = [ + 0, + 0, + this.TILE_SIZE * this.maxTileScale, + this.TILE_SIZE * this.maxTileScale, + ]; readonly gameProjection = new Projection({ code: this.PROJECTION_CODE, extent: this.tileExtent, units: 'pixels', metersPerUnit: 1, - global: false + global: false, }); - readonly resolutions = Array.from( - { length: this.MAX_ZOOM + 1 }, - (_, i) => Math.pow(2, this.MAX_ZOOM - i) + readonly resolutions = Array.from({ length: this.MAX_ZOOM + 1 }, (_, i) => + Math.pow(2, this.MAX_ZOOM - i) ); readonly mapLink = new Link({ - params: ["x", "y", "z"] + params: ['x', 'y', 'z'], }); readonly mapLinkUpdater = effect(() => { - this.mapLink.update("shard", this.shard()) - }) + this.mapLink.update('shard', this.shard()); + }); private initialiseMap(): Map { - const baseLayer = this.createTileLayer(this.gameProjection, this.resolutions); + const baseLayer = this.createTileLayer( + this.gameProjection, + this.resolutions + ); baseLayer.setZIndex(1); - this.mapLink.track("shard", (newValue) => { + this.mapLink.track('shard', (newValue) => { this.shard.set(newValue as Shard); - }) + }); const map = new Map({ target: 'map', - layers: [ - baseLayer - ], + layers: [baseLayer], controls: [], interactions: [ new DragPan({ kinetic: new Kinetic(-0.005, 0.05, 100), condition: (event) => { - return event.originalEvent.button === 0 || event.originalEvent.button === 1; - } + return ( + event.originalEvent.button === 0 || + event.originalEvent.button === 1 + ); + }, }), new MouseWheelZoom(), this.mapLink, ], view: new View({ projection: this.gameProjection, - center: [this.tileExtent[2]/2, this.tileExtent[3]/2], + center: [this.tileExtent[2] / 2, this.tileExtent[3] / 2], zoom: 2, minZoom: 0, maxZoom: this.MAX_ZOOM, extent: this.tileExtent, resolutions: this.resolutions, - constrainResolution: false - }) + constrainResolution: false, + }), }); map.on('click', (evt) => { - const pixel = map.getEventPixel(evt.originalEvent) + const pixel = map.getEventPixel(evt.originalEvent); const features = map.getFeaturesAtPixel(pixel); }); - map.once("loadend", () => { + map.once('loadend', () => { this.initialiseLayers(this.shard()); }); return map; } - private createTileLayer(projection: Projection, resolutions: number[]): TileLayer { + private createTileLayer( + projection: Projection, + resolutions: number[] + ): TileLayer { return new TileLayer({ extent: this.tileExtent, source: new XYZ({ - url: "https://raw.githubusercontent.com/Kastow/Foxhole-Map-Tiles/master/Tiles/{z}/{z}_{x}_{y}.png", + url: 'https://raw.githubusercontent.com/Kastow/Foxhole-Map-Tiles/master/Tiles/{z}/{z}_{x}_{y}.png', interpolate: true, wrapX: false, minZoom: 0, @@ -293,13 +318,16 @@ export class MapComponent implements AfterViewInit, OnDestroy { extent: this.tileExtent, origin: [0, this.tileExtent[3]], resolutions, - tileSize: this.TILE_SIZE - }) - }) + tileSize: this.TILE_SIZE, + }), + }), }); } - private createDebugLayer(projection: Projection, resolutions: number[]): TileLayer { + private createDebugLayer( + projection: Projection, + resolutions: number[] + ): TileLayer { return new TileLayer({ source: new TileDebug({ projection: projection, @@ -307,50 +335,36 @@ export class MapComponent implements AfterViewInit, OnDestroy { extent: this.tileExtent, origin: [0, this.tileExtent[3]], resolutions: resolutions, - tileSize: this.TILE_SIZE - }) + tileSize: this.TILE_SIZE, + }), }), zIndex: 2, - opacity: 1 + opacity: 1, }); } _ = effect(() => this.initialiseLayers(this.shard())); hexSectorsEffect = effect(() => { - const isHexSectorsVisible = this.layerGroupsService.isLayerGroupVisible('hex-sectors'); - this.handleHexSectorsVisibility(isHexSectorsVisible); + const isHexSectorsVisible = + this.layerGroupsService.isLayerGroupVisible('hex-sectors'); + const currentShard = this.shard(); + this.handleHexSectorsVisibility(isHexSectorsVisible, currentShard); }); - private async handleHexSectorsVisibility(visible: boolean): Promise { + private async handleHexSectorsVisibility( + visible: boolean, + shard: Shard + ): Promise { if (!this.map) return; - - if (visible) { - if (!this.hexSectorsLayer) { - this.hexSectorsLayer = await this.regionSectorsService.getLayer(this.shard()); - this.map.addLayer(this.hexSectorsLayer); - } else if (!this.map.getLayers().getArray().includes(this.hexSectorsLayer)) { - this.map.addLayer(this.hexSectorsLayer); - } - } else { - if (this.hexSectorsLayer && this.map.getLayers().getArray().includes(this.hexSectorsLayer)) { - this.map.removeLayer(this.hexSectorsLayer); - } - } - } - - private async updateHexSectorsLayer(shard: Shard): Promise { - if (!this.map) return; - - const isVisible = this.layerGroupsService.isLayerGroupVisible('hex-sectors'); - - if (this.hexSectorsLayer && this.map.getLayers().getArray().includes(this.hexSectorsLayer)) { + if ( + this.hexSectorsLayer && + this.map.getLayers().getArray().includes(this.hexSectorsLayer) + ) { this.map.removeLayer(this.hexSectorsLayer); } - - this.hexSectorsLayer = await this.regionSectorsService.getLayer(shard); - - if (isVisible) { + if (visible) { + this.hexSectorsLayer = await this.regionSectorsService.getLayer(shard); this.map.addLayer(this.hexSectorsLayer); } } @@ -361,24 +375,27 @@ export class MapComponent implements AfterViewInit, OnDestroy { return; } - this.updateHexSectorsLayer(shard); - - this.staticLayers.forEach(layerService => { - layerService.getLayer(shard).then(group => { + this.staticLayers.forEach((layerService) => { + layerService.getLayer(shard).then((group) => { if (!!this.map) - console.warn("Map not initialised, skipping layer initialisation (post layer get)"); + console.warn( + 'Map not initialised, skipping layer initialisation (post layer get)' + ); const layers = group.getLayers(); for (const layer of layers.getArray()) { const existingLayer = this.map .getAllLayers() - .find((mapLayer) => layer.get("title") === mapLayer.get("title")); + .find((mapLayer) => layer.get('title') === mapLayer.get('title')); if (existingLayer) { if (layer instanceof VectorLayer) { existingLayer.setSource(layer.getSource()); } else { - console.warn('Layer is not a VectorLayer, skipping source update', layer); + console.warn( + 'Layer is not a VectorLayer, skipping source update', + layer + ); } } else { this.map.addLayer(layer);