Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix location keys derived from json, use datablue types #391

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 24 additions & 106 deletions src/app/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@

import { NgRedux, select } from '@angular-redux/store';
import { HttpClient } from '@angular/common/http';
import { Directive, EventEmitter, Injectable, Output } from '@angular/core';
import { EventEmitter, Injectable, Output } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Feature, FeatureCollection } from 'geojson';
import distance from 'haversine';
import _ from 'lodash';
import { Observable } from 'rxjs';
import { environment } from '../environments/environment';
import { versions as buildInfo } from '../environments/versions';
import { ADD_APP_ERROR, GET_DIRECTIONS_SUCCESS, PROCESSING_ERRORS_LOADED, SELECT_FOUNTAIN_SUCCESS } from './actions';
Expand All @@ -21,28 +22,25 @@ import { essenceOf, getId, getImageUrl, replaceFountain, sanitizeTitle } from '.
// Import data from fountain_properties.ts.
import { fountain_properties } from './fountain_properties';
// Import data from locations.ts.
import { locations } from './locations';
import { locationsCollection, LocationsCollection, City, cities, Location } from './locations';
import { FountainSelector, IAppState } from './store';
import { AppError, DataIssue, FilterData, PropertyMetadataCollection } from './types';

@Directive()
@Injectable()
export class DataService {
apiUrl = buildInfo.branch === 'stable' ? environment.apiUrlStable : environment.apiUrlBeta;
private _currentFountainSelector: FountainSelector = null;
private _fountainsAll: FeatureCollection<any> = null;
private _fountainsFiltered: any[] = null;
private _filter: FilterData = defaultFilter;
private _city: string = null;
private _propertyMetadataCollection: PropertyMetadataCollection = null;
private _propertyMetadataCollectionPromise: Promise<PropertyMetadataCollection>;
private _locationInfo: any = null;
private _locationInfoPromise: Promise<any>;
private _city: City | null = null;
private _propertyMetadataCollection: PropertyMetadataCollection = fountain_properties;
private _locationsCollection = locationsCollection;
@select() fountainId;
@select() userLocation;
@select() mode;
@select('lang') lang$;
@select('city') city$;
@select() mode: Observable<string>;
@select('lang') lang$: Observable<string>;
@select('city') city$: Observable<City | null>;
@select('travelMode') travelMode$;
@Output() fountainSelectedSuccess: EventEmitter<Feature<any>> = new EventEmitter<Feature<any>>();
@Output() apiError: EventEmitter<AppError[]> = new EventEmitter<AppError[]>();
Expand All @@ -51,96 +49,26 @@ export class DataService {
@Output() directionsLoadedSuccess: EventEmitter<object> = new EventEmitter<object>();
@Output() fountainHighlightedEvent: EventEmitter<Feature<any>> = new EventEmitter<Feature<any>>();

// Use location from locations.ts.
private _locations = locations;

// Use fountain_properties from fountain_properties.ts.
private _fountain_properties: any = fountain_properties;

// public observables used by external components
get fountainsAll() {
return this._fountainsAll;
}

get propMeta() {
// todo: this souldn't return null if the api request is still pending
return this._propertyMetadataCollection || this._propertyMetadataCollectionPromise;
return this._propertyMetadataCollection;
}

get currentLocationInfo() {
// todo: this souldn't return null if the api request is still pending
return this._locationInfo[this._city];
get currentLocationsCollection(): Location | null {
const city = this._city;
if (city != null) {
return this._locationsCollection[city];
} else {
return null;
}
}
constructor(private translate: TranslateService, private http: HttpClient, private ngRedux: NgRedux<IAppState>) {
console.log('constuctor start ' + new Date().toISOString());

// Load metadata
this._locationInfoPromise = new Promise<any>(resolve => {
this._locationInfo = this._locations;
console.log('constuctor location info done ' + new Date().toISOString());
resolve(this._locations);
/*
// Use location from server (DEPRECATED).
let metadataUrl = `${this.apiUrl}api/v1/metadata/locations`;
this.http.get(metadataUrl)
.subscribe(
(data: any) => {
this._locationInfo = data;
console.log("constuctor location info done "+new Date().toISOString());
if (null == data) {
console.log("data.service.js: constuctor location null "+new Date().toISOString());
} else {
if (null == data.gak) {
console.log("data.service.js: constuctor location.gak null "+new Date().toISOString());
} else {
environment.gak = data.gak;
}
}
resolve(data);
},(httpResponse)=>{
let err = 'error loading location metadata';
console.log("constuctor: "+err +" "+new Date().toISOString());
this.registerApiError(err, '', httpResponse, metadataUrl);
}
);
*/
});

this._propertyMetadataCollectionPromise = new Promise<PropertyMetadataCollection>(resolve => {
try {
this._propertyMetadataCollection = this._fountain_properties;
console.log('constuctor fountain properties done ' + new Date().toISOString());
resolve(this._fountain_properties);
} catch (err: unknown) {
// eslint-disable-next-line no-console
console.trace(err + ' ' + new Date().toISOString());
}

/*
// Use fountain_properties from server (DEPRECATED).
let metadataUrl = `${this.apiUrl}api/v1/metadata/fountain_properties`;
console.log(metadataUrl+' '+new Date().toISOString());
this.http.get(metadataUrl)
.subscribe(
(data: PropertyMetadataCollection) => {
try {
this._propertyMetadataCollection = data;
console.log("constuctor fountain properties done "+new Date().toISOString());
resolve(data);
} catch (err:unknown) {
console.trace(err+ ' '+new Date().toISOString());
}
}, httpResponse=>{
// if in development mode, show a message.
let err = 'error loading fountain properties';
console.log("constuctor: "+err +" "+new Date().toISOString());
this.registerApiError(err, '', httpResponse, metadataUrl);
reject(httpResponse);
}
);
*/
});

// Subscribe to changes in application state
this.userLocation.subscribe(() => {
this.sortByProximity();
Expand Down Expand Up @@ -170,14 +98,14 @@ export class DataService {
return this._fountainsAll.features.length;
}

getLocationBounds(city) {
getLocationBounds(city: City) {
return new Promise((resolve, reject) => {
if (city !== null) {
const waiting = () => {
if (this._locationInfo === null) {
if (this._locationsCollection === null) {
setTimeout(waiting, 200);
} else {
const bbox = this._locationInfo[city].bounding_box;
const bbox = this._locationsCollection[city].bounding_box;
resolve([
[bbox.lngMin, bbox.latMin],
[bbox.lngMax, bbox.latMax],
Expand Down Expand Up @@ -225,26 +153,16 @@ export class DataService {

// fetch fountain property metadata or return
fetchPropertyMetadata() {
if (this._propertyMetadataCollection === null) {
return this._propertyMetadataCollectionPromise;
// if data already loaded, just resolve
} else {
return Promise.resolve(this._propertyMetadataCollection);
}
return Promise.resolve(this._propertyMetadataCollection);
}

// fetch location metadata
fetchLocationMetadata() {
if (this._locationInfo === null) {
return this._locationInfoPromise;
// if data already loaded, just resolve
} else {
return Promise.resolve(this._locationInfo);
}
fetchLocationMetadata(): Promise<[LocationsCollection, City[]]> {
return Promise.resolve([this._locationsCollection, cities]);
}

// Get the initial data
loadCityData(city, force_refresh = false) {
loadCityData(city: City | null, force_refresh = false) {
if (city !== null) {
console.log(city + ' loadCityData ' + new Date().toISOString());
const fountainsUrl = `${this.apiUrl}api/v1/fountains?city=${city}&refresh=${force_refresh}`;
Expand Down
18 changes: 10 additions & 8 deletions src/app/detail/detail.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { IAppState } from '../store';
import { PropertyMetadata, PropertyMetadataCollection, QuickLink } from '../types';
import { galleryOptions } from './detail.gallery.options';
import * as consts from '../constants';
import { Observable } from 'rxjs';
import { City } from '../locations';
const wm_cat_url_root = 'https://commons.wikimedia.org/wiki/Category:';

const maxCaptionPartLgth = consts.maxWikiCiteLgth; // 150;
Expand All @@ -34,9 +36,9 @@ export class DetailComponent implements OnInit {
public isMetadataLoaded = false;
public propMeta: PropertyMetadataCollection = null;
@select('fountainSelected') fountain$;
@select() mode;
@select() city$;
@select() lang$;
@select() mode: Observable<string>;
@select() city$: Observable<City | null>;
@select() lang$: Observable<string>;
lang = 'de';
@select('userLocation') userLocation$;
@Output() closeDetails = new EventEmitter<boolean>();
Expand All @@ -50,8 +52,8 @@ export class DetailComponent implements OnInit {
@ViewChild('gallery') galleryElement: NgxGalleryComponent;
nearestStations = [];
videoUrls: any = [];
issue_api_img_url: '';
issue_api_url: '';
issue_api_img_url = '';
issue_api_url = '';
constas = consts;
imageCaptionData: any = {
caption: '',
Expand Down Expand Up @@ -169,10 +171,10 @@ export class DetailComponent implements OnInit {
console.log('no videoUrls ' + new Date().toISOString());
}
// update issue api
const cityMetadata = this.dataService.currentLocationInfo;
const cityMetadata = this.dataService.currentLocationsCollection;
if (
cityMetadata.issue_api.operator !== null &&
cityMetadata.issue_api.operator === fProps.operator_name.value
cityMetadata?.issue_api.operator !== null &&
cityMetadata?.issue_api.operator === fProps.operator_name.value
) {
console.log('cityMetadata.issue_api.operator !== null ' + new Date().toISOString());
this.issue_api_img_url = cityMetadata.issue_api.thumbnail_url;
Expand Down
5 changes: 3 additions & 2 deletions src/app/issue-list/issue-list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { select } from '@angular-redux/store';
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { City } from '../locations';
import { AppError, DataIssue } from '../types';

@Component({
Expand All @@ -18,7 +19,7 @@ import { AppError, DataIssue } from '../types';
export class IssueListComponent {
@select('dataIssues') dataIssues$: Observable<DataIssue[]>;
@select('appErrors') appErrors$: Observable<AppError[]>;
@select('lang') lang$;
@select('city') city$;
@select('lang') lang$: Observable<string>;
@select('city') city$: Observable<City | null>;
// issue_count:number;
}
51 changes: 50 additions & 1 deletion src/app/locations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,55 @@
*/

// import location.json from assets folder.
import { environment } from '../environments/environment';

//TODO it would make more sense to just share the typescript constant instead of using json IMO
import * as locationsJSON from './../assets/locations.json';

export const locations = locationsJSON;
// TODO it would make more sense to move common types to an own library which is consumed by both, datablue and proximap
// if you change something here, then you need to change it in datablue as well
export interface Location {
name: string;
description: Translated<string>;
description_more: Translated<string>;
bounding_box: BoundingBox;
operator_fountain_catalog_qid: string;
issue_api: IssueApi;
}

// TODO it would make more sense to move common types to an own library which is consumed by both, datablue and proximap
// if you change something here, then you need to change it in datablue as well
export interface BoundingBox {
latMin: number;
lngMin: number;
latMax: number;
lngMax: number;
}

// TODO it would make more sense to move common types to an own library which is consumed by both, datablue and proximap
// if you change something here, then you need to change it in datablue as well
export interface IssueApi {
operator: string | null;
//TODO @ralfhauser, is always null at definition site, do we still use this information somehwere?
qid: null;
thumbnail_url: string;
url_template: string | null;
}

// TODO it would make more sense to move common types to an own library which is consumed by both, datablue and proximap
// if you change something here, then you need to change it in datablue as well
export interface Translated<T> {
en: T;
de: T;
fr: T;
it: T;
tr: T;
}

export const locationsCollection = locationsJSON;
export type City = keyof typeof locationsCollection;
export type LocationsCollection = Record<City, Location>;

export const cities: City[] = Object.keys(locationsCollection).filter(
city => city !== 'default' && (city !== 'test' || !environment.production)
) as City[];
11 changes: 6 additions & 5 deletions src/app/map/map.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Feature } from 'geojson';
import * as M from 'mapbox-gl/dist/mapbox-gl.js';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { EMPTY_LINESTRING } from '../../assets/defaultData';
import { environment } from '../../environments/environment';
import { SET_USER_LOCATION } from '../actions';
import { DataService } from '../data.service';
import { City } from '../locations';
import { IAppState } from '../store';
import { DeviceMode } from '../types';
import { MapConfig } from './map.config';
Expand All @@ -36,9 +37,9 @@ export class MapComponent implements OnInit {
private directionsGeoJson = EMPTY_LINESTRING;
private satelliteShown = false;
@select() showList;
@select() mode$;
@select() lang$;
@select() city$;
@select() mode$: Observable<string>;
@select() lang$: Observable<string>;
@select() city$: Observable<City | null>;
@select() device$;
device: BehaviorSubject<DeviceMode> = new BehaviorSubject<DeviceMode>('mobile');
@select() fountainId;
Expand Down Expand Up @@ -71,7 +72,7 @@ export class MapComponent implements OnInit {
}

// Zoom to city bounds (only if current map bounds are outside of new city's bounds)
zoomToCity(city: string): void {
zoomToCity(city: City): void {
const options = {
maxDuration: 500,
pitch: 0,
Expand Down
14 changes: 9 additions & 5 deletions src/app/mobile-menu/mobile-menu.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,23 @@ <h2 translate>intro.legend-title</h2>

<!-- Add city-specific info for https://github.com/water-fountains/proximap/issues/277 -->
<div
*ngIf="locationInfo && (city$ | async)"
[innerHTML]="locationInfo[city$ | async].description[lang$ | async]"
*ngIf="locationsCollection !== null && (city$ | async)"
[innerHTML]="locationsCollection[city$ | async].description[lang$ | async]"
></div>
<mat-expansion-panel
class="mat-elevation-z0"
*ngIf="locationInfo && (city$ | async) && locationInfo[city$ | async].description_more[lang$ | async]"
*ngIf="
locationsCollection !== null &&
(city$ | async) &&
locationsCollection[city$ | async].description_more[lang$ | async]
"
>
<mat-expansion-panel-header>
<mat-panel-title>
{{ 'action.more' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<div [innerHTML]="locationInfo[city$ | async].description_more[lang$ | async]"></div>
<div [innerHTML]="locationsCollection[city$ | async].description_more[lang$ | async]"></div>
</mat-expansion-panel>

<h2 translate>intro.disclaimer-title</h2>
Expand Down Expand Up @@ -81,7 +85,7 @@ <h3>{{ 'settings.lang' | translate }}</h3>
[options]="publicSharedConsts.LANGS"
></app-state-selector>
<h3>{{ 'settings.city' | translate }}</h3>
<app-state-selector class="full-size" controlVariable="city" [options]="locationOptions"></app-state-selector>
<app-state-selector class="full-size" controlVariable="city" [options]="cities"></app-state-selector>
<p>
{{ 'settings.city_last_scan' | translate }}: {{ last_scan | date: 'long':'':(lang$ | async) }}.
{{ 'settings.city_next_scan_info' | translate }} {{ publicSharedConsts?.CACHE_FOR_HRS_i45db }}
Expand Down
Loading