HEX
Server: nginx/1.24.0
System: Linux nowruzgan 6.8.0-57-generic #59-Ubuntu SMP PREEMPT_DYNAMIC Sat Mar 15 17:40:59 UTC 2025 x86_64
User: babak (1000)
PHP: 8.3.6
Disabled: NONE
Upload Files
File: /var/dev/nowruzgan/travelogue/src/app/services/endpoint.service.ts
import { inject, Injectable } from '@angular/core';
import { Dataset, Source, Note, Poi, PoiRel, Tag } from '../data.types';
import { environment } from '../../environments/environment';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class EndpointService {
  readonly http = inject(HttpClient);

  dataset: Dataset = {
    sources: [],
    notes: [],
    pois: [],
    tags: [],
    timestamp: 0
  };
  datasetCallbacks: Function[] = [];
  loadingData: boolean = false;
  static calendar: string = 'gregorian';

  raw: any = {};

  async getSummary(): Promise<Dataset> {
    if(this.dataset.timestamp) return this.dataset;

    let result = await firstValueFrom(this.http.get(`${environment.apiBase}/travelogue/public/summary`)).catch(error => error);
    if(result instanceof HttpErrorResponse)
      return this.dataset;

    this.raw = result;
    await this.normlize();
    return this.dataset;


    /*return new Promise((resolve, reject) => {
      if(this.dataset.timestamp) return resolve(this.dataset);

      if(this.loadingData) {
        this.datasetCallbacks.push(() => resolve(this.dataset));
      }else {
        this.loadingData = true;
        this.http
          .get(`${environment.apiBase}/travelogue/public/summary`)
          .subscribe({
            next: async data => {
              await this.normlize(data);
              resolve(this.dataset);
              this.datasetCallbacks.forEach(callback => callback());
            },
            error: error => {
              console.log('http error', {status: error.status, text: error.statusText});
              resolve(this.dataset);
              this.datasetCallbacks.forEach(callback => callback());
            }
          });
      }

      // this.normlize(result)

      // return this.dataset;
    });*/
  }

  async normlize() {
    let data = this.raw;
    data.sources = this.convertObject(data.sources);
    data.notes = this.convertObject(data.notes);
    data.pois = this.convertObject(data.pois);
    data.notePois = this.convertObject(data.notePois);
    data.noteTags = this.convertObject(data.noteTags);
    data.tags = this.convertObject(data.tags);

    this.dataset.sources = data.sources.map((source: any) => new Source(source));
    this.dataset.notes = data.notes.map((note: any) => {
      let result: Note = new Note(note);
      result.source = this.dataset.sources.find(source => source.id == note.source) || new Source();
      result.source.notes.push(result);
      return result;
    });
    this.dataset.pois = data.pois.map((poi: any) => new Poi(poi));

    for (let rel of data.notePois) {
      let poi = this.dataset.pois.find(poi => poi.id == rel.poi);
      if(!poi) continue;
      let note = this.dataset.notes.find(note => note.id == rel.note);
      if(!note) continue;
      
      rel.poi = poi;
      poi.notes.push(note);
      if(!poi.sources.includes(note.source))
        poi.sources.push(note.source);

      poi.meta.waypointSources = poi.meta.waypointSources || [];
      if(poi.granularity == 40 && !rel.flagMilestone && !rel.flagNoway) {
        poi.meta.waypointSources[note.source.id] = true;

        if(!note.source.borderBox) {
          note.source.borderBox = {minLat: poi.lat, maxLat: poi.lat, minLon: poi.lon, maxLon: poi.lon};
        }else {
          note.source.borderBox.minLat = Math.min(note.source.borderBox.minLat, poi.lat);
          note.source.borderBox.maxLat = Math.max(note.source.borderBox.maxLat, poi.lat);
          note.source.borderBox.minLon = Math.min(note.source.borderBox.minLon, poi.lon);
          note.source.borderBox.maxLon = Math.max(note.source.borderBox.maxLon, poi.lon);
        }
      }

      delete rel.note;
      note.poiRels.push(rel);
    }

    this.dataset.notes.forEach(note => {
      note.poiRels = note.poiRels.sort((a: PoiRel, b: PoiRel) => a.order - b.order);
    });

    this.dataset.tags = data.tags
      .map((tag: any) => new Tag(tag));

    for(let source of this.dataset.sources)
      source.tags = this.dataset.tags.filter(tag => tag.meta.sources.includes(source.id));

    for(let rel of data.noteTags) {
      let note = this.dataset.notes.find(note => note.id == rel.note);
      if(!note) continue;
      let tag = this.dataset.tags.find(tag => tag.id == rel.tag);
      if(!tag) continue;


      tag.notes.push(note);
      if(tag.taxonomy == 'travelogue-note-tag')
        note.tags.push(tag);
      if(tag.taxonomy == 'travelogue-note-index-place')
        note.indexPlace.push(tag);
      if(tag.taxonomy == 'travelogue-note-index-person')
        note.indexPerson.push(tag);
      if(tag.taxonomy == 'travelogue-note-index-other')
        note.indexOther.push(tag);
    }

    this.dataset.sources.forEach(source => source.notes = source.notes.sort((a, b) => a.order - b.order));

    for(let source of this.dataset.sources) {
      if(!source.borderBox) continue;
      let offsetLon = (source.borderBox.maxLon - source.borderBox.minLon)*.1;
      source.borderBox.minLon -= offsetLon ;
      source.borderBox.maxLon += offsetLon ;
      let offsetLat = (source.borderBox.maxLat - source.borderBox.minLat)*.1;
      source.borderBox.minLat -= offsetLat ;
      source.borderBox.maxLat += offsetLat ;
    }

    this.dataset.timestamp = Date.now();
  }

  convertObject(data: any[]) {
    let cols = data.shift();
    return data.map(record => {
      let obj: any = {};
      for(let i=0; i<cols.length; i++)
        obj[cols[i]] = record[i];
      return obj;
    })
  }
}