/* * Copyright (C) 2019-2021 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import {ElementRef, Injectable} from '@angular/core'; import { SCBuilding, SCSearchFilter, SCSearchQuery, SCSearchResponse, SCThingType, SCUuid, } from '@openstapps/core'; import {Point, Polygon} from 'geojson'; import {divIcon, geoJSON, icon, LatLng, Map, marker, Marker} from 'leaflet'; import {DataProvider} from '../data/data.provider'; import {MapPosition, PositionService} from './position.service'; import {hasValidLocation} from '../data/types/place/place-types'; import {ConfigProvider} from '../config/config.provider'; /** * Provides methods for presenting the map */ @Injectable({ providedIn: 'root', }) export class MapProvider { /** * Area to show when the map is initialized (shown for the first time) */ defaultPolygon: Polygon; /** * Provide a point marker for a leaflet map * * @param point Point to get marker for */ static getPointMarker(point: Point) { return marker(geoJSON(point).getBounds().getCenter(), { icon: icon({ iconAnchor: [13, 41], iconSize: [25, 41], iconUrl: '../assets/marker-icon.png', shadowUrl: '../assets/marker-shadow.png', }), }); } /** * Provide a position marker for a leaflet map * * @param position Current position * @param className CSS class name * @param iconSize Size of the position icon */ static getPositionMarker( position: MapPosition, className: string, iconSize: number, ) { return new Marker(new LatLng(position.latitude, position.longitude), { icon: divIcon({ className: className, html: typeof position.heading !== 'undefined' ? ` ` : '', iconSize: [iconSize, iconSize], }), zIndexOffset: 1000, }); } /** * Fixes the issue of missing tiles when map renders before its container element * * @param map The initialized map * @param element The element containing the map * @param interval Interval to clear when map's appearance is corrected */ static invalidateWhenRendered = ( map: Map, element: ElementRef, interval: number, ) => { if (element.nativeElement.offsetWidth === 0) { return; } // map's container is ready map.invalidateSize(); // stop repeating when it's rendered and invalidateSize done clearInterval(interval); }; constructor( private dataProvider: DataProvider, private positionService: PositionService, private configProvider: ConfigProvider, ) { this.defaultPolygon = this.configProvider.getValue( 'campusPolygon', ) as Polygon; } /** * Provide the specific place by its UID * * @param uid UUID of the place to look for */ async searchPlace(uid: SCUuid): Promise { const uidFilter: SCSearchFilter = { arguments: { field: 'uid', value: uid, }, type: 'value', }; return this.dataProvider.search({filter: uidFilter}); } /** * Provide places (buildings and canteens) const result = await this.dataProvider.search(query); * * @param contextFilter Additional contextual filter (e.g. from the context menu) * @param queryText Query (text) of the search query */ async searchPlaces( contextFilter?: SCSearchFilter, queryText?: string, ): Promise { const buildingFilter: SCSearchFilter = { arguments: { field: 'type', value: SCThingType.Building, }, type: 'value', }; const mensaFilter: SCSearchFilter = { arguments: { filters: [ { arguments: { field: 'categories', value: 'canteen', }, type: 'value', }, { arguments: { field: 'categories', value: 'student canteen', }, type: 'value', }, { arguments: { field: 'categories', value: 'cafe', }, type: 'value', }, { arguments: { field: 'categories', value: 'restaurant', }, type: 'value', }, ], operation: 'or', }, type: 'boolean', }; // initial filter for the places const baseFilter: SCSearchFilter = { arguments: { operation: 'or', filters: [buildingFilter, mensaFilter], }, type: 'boolean', }; let filter = baseFilter; if (typeof contextFilter !== 'undefined') { filter = { arguments: { operation: 'and', filters: [baseFilter, contextFilter], }, type: 'boolean', }; } const query: SCSearchQuery = { filter, }; if (queryText && queryText.length > 0) { query.query = queryText; } if (this.positionService.position) { query.sort = [ { type: 'distance', order: 'asc', arguments: { field: 'geo.point.coordinates', position: [ this.positionService.position.longitude, this.positionService.position.latitude, ], }, }, ]; } const result = await this.dataProvider.search(query); result.data = result.data.filter(place => hasValidLocation(place as SCBuilding), ); return result; } }